Rob’s blog

  • Rob on Twitter
  • rss
  • archive
  • Setting a date with TextExpander

    I suck at dates. Badly.

    Thankfully, there are a few ways to get around this in software — not least TextExpander, Smile’s ace text-substitution program. (I don’t use it nearly enough, but I’m looking to fix that.)

    TextExpander has a lot of date placeholders, so it’s obvious that I’m not the only one that needs help with this — but I may well be the first to be this bad. Here’s a list of the snippets in my Time & Date folder:

    ;mdate   ->   March 6 2013
    ;*ddate  ->   Wednesday March 6 2013
    ;ds      ->   _0306_0914
    ;time    ->   09:14
    ;bd      ->   20130306
    ;ldate   ->   March 6, 2013
    ;sdate   ->   06/03/2013
    ;dt      ->   09:14, March 6, 2013
    

    Those eight snippets include seven different ways to get the date, and three different ways to get the time. The worst part about this is that I can never remember which abbreviation corresponds to which date format.

    Ok, let’s fix this. How many formats do I really need? I need a short date, a more human-friendly long date, the time, and the format used at work.

    So that’s four — only half as bad as before!

    Time

    I’m sticking with my old ;time snippet here, a colon-separated 24-hour time. If I need a more human-friendly time, typing 10am or 8pm or whatever is as easy as typing an abbreviation (and TextExpander only does uppercase AM/PM, which I dislike). Here’s the snippet:

    %H:%M   ->    10:23
    

    Short date

    Because, well, XKCD, I’m going to use the ISO 8601 date format for this. It means I can dump my separate backwards date snippet and avoids any confusion across DD/MM/YY and MM/DD/YY countries.

    %Y-%m-%d    ->  2013-03-06
    

    Long date

    Since this is implicitly not the format to use when you’re counting characters and one that’s explicit enough to avoid confusion, there’s quite a bit of personal freedom here.

    This one’s going to be more complicated than the previous two, as I want to use date suffixes (March 2nd, April 21st, etc). But TextExpander doesn’t include a suffix placeholder, so let’s take advantage of its script support.

    AppleScript’s built-in current date command returns an object from which we can extract all the pieces we need to construct a date, like so:

    set theDate to the day of the (current date)
    set theDay to the weekday of the (current date)
    set theMonth to the month of the (current date)
    set theYear to the year of the (current date)
    

    Now to build the suffix. Most dates have the suffix “th”, so we want to focus on the special cases: 1, 2, 3, 21, 22, and 31. We could check for each of these but there’s a smarter way.

    We need to pull the date’s last character, which determines the suffix, so we can work with both single and double-digit dates:

    set lastChar to (the last character of (theDate as string))¬
     as number
    

    Now we check for the most common situation (“th”) by testing lastChar against a range:

    if lastChar is 0 or lastChar > 3 or (theDate > 10 and¬
     theDate < 21) then
        set theDate to (theDate as string) & "th"
    

    If we put the other suffixes in order and in a list, we can use the last character of the date itself to extract the correct suffix:

    set theSuffixes to {"st", "nd", "rd"}
    set theDate to (theDate as string) & (item lastChar¬
     of theSuffixes)
    

    Now let’s roll it up and return a constructed date:

    set theDate to the day of the (current date)
    set theDay to the weekday of the (current date)
    set theMonth to the month of the (current date)
    set theYear to the year of the (current date)
    
    set lastChar to (the last character of (theDate as string))¬
     as number
    
    if lastChar > 3 or lastChar is 0 or (theDate > 10 and¬
     theDate < 21) then
        set theDate to (theDate as string) & "th"
    else
        set theSuffixes to {"st", "nd", "rd"}
        set theDate to (theDate as string) & (item lastChar¬
         of theSuffixes)
    end if
    
    return (theDay & ", " & theMonth & " " & theDate & ", " &¬
     theYear) as string
    

    End result

    Hey! You made it! I was sure I’d lose everyone in the middle of breaking down that AppleScript.

    So, here’s what I’m left with:

    ;iso    ->    2013-03-06
    ;ldt    ->    Wednesday, March 6th, 2013
    ;tm     ->    10:21
    

    Mix in one for work:

    ;*dt    ->    Wednesday March 6 2013
    

    And we’re all set.

    • 2 months ago
    • #technology
  • Blackjack!

    For the last few months I’ve been working, on and off, on a little web project — a basic blackjack game you can play in your browser. It’s not going to win any awards but it was a fun exercise to teach myself some JavaScript, HTML and CSS.

    I got the idea for it after finishing Codecademy’s original JavaScript course, where you spend some time working on the basics behind such a game. At the very end of the course, once you’ve put all the pieces together, you’re encouraged to keep working on it to see what you can improve.

    One of the leads is to improve the way the players’ hands are displayed by printing a more human-friendly message to the console. But I thought perhaps there was a way to improve the interface further, by breaking the game out of the console and hooking it up to a visual display in the browser proper. And, well, this is what I’ve got — and I encourage you to poke around the code as well.

    The core game logic is more or less the same, though I’ve taken time to improve certain aspects — dealing cards from a depleting deck, having the dealer hit on “soft” 17 (mostly for the challenge), better feedback, hiding the dealer’s hole card, and a few other bits.

    Since it hasn’t changed radically, I was surprised to find that the JavaScript file is now over four times longer than what I had when I finished the Codecademy track.

    I think that mirrors my surprise at how much work it actually was to get everything hooked up, working right and looking good enough to put out into the world without me dying of embarrassment. (And most of the extra JavaScript lines are related to connecting with HTML & CSS.)

    Man, web design is hard. Well, it was hard for me — I guess because what little I did know was all geared towards making “regular” web pages, where you’ve got some text and maybe some images and maybe a list or two and not enough moving parts to construct a Rube Goldberg Machine.

    I actually would have been finished a little while ago but I decided to junk my original design, which looked like the layout you get now when using a wider window, because cutting off screens thinner than 800px was just an enormous cop-out and I couldn’t stand it. It was made worse by the fact I’d started reading Ethan Marcotte’s Responsive Web Design.

    So now you’ll see a “narrow” layout with devices thinner than 800px, and a luxuriously spacious design on anything wider. If you have an iPad you can see both very easily: thin design in portrait, wide in landscape.

    Speaking of the iPad, one of my goals was to not use any images that would get pixellated on high-DPI (retina) displays. I also wanted to keep the total file size as low as possible — one of the reasons I didn’t use jQuery and instead wrote my own helper functions.

    So step in SVG. The seven images used are all SVG files, weighing in at a whopping 12KB (and just 6KB when compressed).

    The biggest file by far is the League Gothic web font file, which I like a lot and wouldn’t change but it has caused me some headaches. It can display oddly, appearing overly bold in Chrome for example. And finding a fallback font was basically impossible, though the new designs get around that by not including fragile elements designed around the font’s particular shape and size.

    The about button, styled as a red casino chip, is the only thing left of those fragile elements. Originally I had two other chips, with the number on their faces displaying the number of hands won and lost. I liked the look but they were ultimately so fiddly I ditched them for the simpler, clearer text-only counters.

    These also display better in Firefox, as the tabs around the chips’ outside — originally faked using a dashed border — displayed as a solid ring (because of how Firefox handles high border-radius values).

    Having said that, compatibility was not a priority for me. That would be stupid in the real world but I think I can get away with it here. This freed me up to use recent CSS features such as transforms, transitions, shadows, animation, new units, etc. (Since I mention animation, the overlays’ bounce effects come from Daniel Eden’s awesome animate.css.)

    While this has been liberating, it’s also made clear that there are some oddities in CSS and browsers’ implementations of it that can sometimes trip you up, with problems seemingly coming out of nowhere.

    But practice looks to be the best way to clear up any confusion and I’m eager to learn and do more. I’ll probably make my own theme for this blog next, though I’m going to work through Jennifer Robbins’s Learning Web Design first to make sure I have a firm footing.

    I think that’s about it, so thanks for reading and if you have any questions or comments I’m @robjwells on Twitter.

    • 2 months ago
    • #web stuff
  • Dishonored by the numbers

    Dishonored is so good. So incredibly good. It’s really fantastic. It came out (here, at least) on Friday and I’ve already completed it, with my first playthrough taking about 22 hours.

    It’s so good I was playing it until 5am yesterday morning and until 4am this morning. I just couldn’t stop, I couldn’t put it down.

    I can’t really think of a comparison, as in “If you liked game X then you’ll love Dishonored” or “It’s like the lovechild of game X and game Y”. At one point I thought: “This really does feel like Thief with a knife.” But it is so much more and really any simple comparison like that does a great disservice to what Arkane have created.

    Simply put: you should buy Dishonored right now.


    After each mission you get a stats screen but unfortunately there’s not one for the entire game. I went through and totted up the figures for my first playthrough.

    Mission stats
    Hostiles killed 1
    Civilians killed 2
    Alarms rung 0
    Bodies found 7
    No kills 6/9
    Not detected 6/9
    Chaos Low

    I was always surprised to see bodies found, because I was always fairly careful. However what I think happened is that when you take out a guard the AI seems to compensate by rotating another guard into that position. So if you take someone out and leave them where they were, there’s a chance they’ll be found.


    Assassination target status
    High Overseer Campbell Dead
    The Pendleton Twins Alive
    Lady Boyle Dead
    Lord Regent Alive
    Daud Alive
    Spoilerific last target Alive

    Looking back, I’m surprised how many of the main targets I left alive. There are non-lethal options for all of them and I was planning to do that every time. However, I had to ditch my plan for Campbell as things sort of got out of hand and Lady Boyle’s non-lethal option seemed a bit too creepy for me. (So I stabbed her instead. I’m such a gentleman.) And the last target in the low-chaos ending is optional, though it looks like the high-chaos ending requires you to go after more targets.

    Man, what a game.

    • 7 months ago
    • #games
  • Restart in Windows: Revenge of the Script

    Want to quickly restart your Mac in your Windows Bootcamp partition, without having to hold down option/alt as the computer starts?

    Click this link, which will open an AppleScript in your default editor, and replace “YourBootcampPartition” in the first line with your Windows partition’s actual name (making sure that is exactly the same, as it’s case sensitive). Then save it as a .scpt file in your user scripts folder, which you can access through the system-wide script menu (which is turned on in AppleScript Editor’s preferences).

    Then, to restart, just pick it from that script menu.

    Extra, extra

    Using Leopard (10.5) or earlier? You’ll have to change the first line as “diskutil info” doesn’t work with volume names. Try this:

    set deviceID to (do shell script "diskutil list ¬
    | grep YourBootcampPartition | awk '{print $8}'")
    

    That should return something like “disk0s4”. If it doesn’t change “print $8” to a different number, probably 7. (I recommend you try this in the Terminal until you’ve got it right, then adjust the AppleScript. The number refers to the column of the information.)

    Don’t want to have to type your password in every time? Add “password “YourPassword” ” after “ -nextonly” ” on the second line, then save your script as run-only. I do not recommend this. Your password will not be encrypted. It is only slightly less insecure than keeping your password in a text file. Please, do not do this.

    Using it

    I’ve got it saved to my ~/Library/Scripts folder, and have Launchbar set to index that folder so I can just type “Restart in Windows” into the prompt, hit enter, type my password and then I’m set.

    You could also use FastScripts, a really nice replacement for the system script menu that you can use to give the script a keyboard shortcut.

    I don’t recommend that you save it as an application, which would let you add it to your dock. I did that once and got stuck in a restart loop, thanks to Lion’s ability to re-open applications that were running when you restarted. You can disable that feature in Mountain Lion, but I still strongly advise that you keep it as a plain old script file.

    The code

    set deviceID to (do shell script "diskutil info ¬
    YourBootcampPartition | grep Identifier | awk '{print $3}'")
    
    do shell script "bless -device /dev/" & deviceID & ¬
    " -legacy -setBoot -nextonly" with administrator privileges
    
    tell application "Finder" to restart
    

    The Story

    I talk about the origins in my first post about the script. Basically, holding down option on my keyboard was unreliable because the computer had booted up too fast for the Bluetooth connection. I’d been using Bootchamp, a menubar utility, for a while but you couldn’t trigger a restart from Launchbar and had to mount the partition first.

    The first version relied on the disk identifier never changing. You can’t rely on a partition having the same identifier after a restart, so the script would silently fail.

    The next version relied on the disk identifier being in a certain character position, which (aside from being a dirty hack) was also unreliable if you had more than 10 disks connected.

    The most recent (and probably final) version uses grep and awk on the command line to fetch the disk identifier after pulling up the information for the partition. (In Leopard it has to get the identifier from the list of every partition, as “diskutil info [PartitionName]” only works in Snow Leopard and later. As such it’s a little less reliable and needs a bit more tweaking by users.)

    For such a simple script (three whole lines!) this has taken more work than I originally anticipated. But it’s also taught me a fair bit about the command line and how to retrieve information from it.

    So, yeah. I think it’s done now. If you have any questions or comments please message me on Twitter or add a comment to the script’s gist.

    • 7 months ago
    • #AppleScript
  • What’s in the box!?

    Become vengeance, David. Become wrath.

    Accessing AppleScript records using a variable

    AppleScript gets a lot of stick for being a rubbish language. And it is. But before last night there wasn’t anything that really threw me and made me think “wow, that’s incredibly, impressively, mind-meltingly dumb”. (Admittedly, I’m not a programmer.)

    But it happened. Here’s the example code:

    set myRecords to {Abc:"1", Def:"2", Ghi:"3"}
    set myVar to "Abc"
    get myVar of myRecords
    

    Try that and you’ll get a big fat error message. Why? Because there’s no property in myRecords that’s literally called myVar.

    “But it’s a variable,” I hear you sob quietly into Script Debugger. I know, I know. You were after the value of property “Abc”. Unfortunately AppleScript doesn’t let you access records in this way. If you want to access that property you need to refer to its “real” name directly in the code.

    Even to me this is ridiculous. I’ve learnt a little JavaScript with Codecademy recently and you can do this very easily:

    var theRecords = {
        Abc: 1,
        Def: 2,
        Ghi: 3
    };
    var theVar = "Abc";
    console.log(theRecords[theVar]);
    

    This becomes important when you want to do something else and then refer back to the record.

    What I wanted to do is store a simple name for a more complex string, and then later pass the simple name to the record and have it return the longer, hard-to-remember string. But there was no way I could hard-code the property name without defeating the entire purpose of the script I was writing. I just wanted to pass a variable containing the simple name string.

    But no. Not in AppleScript.

    Well, not quite. The language does not support doing this, true. But the language does let you contain a script within a script — which allows us to get past the “no variables to records” rubbish.

    Thanks go to the heroic jobu on the MacScripter forums, who posted this code in 2006. (Despite being so long ago, this technique doesn’t seem to have gotten much attention, as looking at many, many forum posts about this issue has shown.)

    Let’s have a look:

    set myRecords to {Abc:"1", Def:"2", Ghi:"3"}
    set myVar to "Abc"
    
    return (searchRecord(myVar, myRecords))
    
    to searchRecord(theKey, theRecord)
        run script "on run{theKey,theRecord}
            return (" & theKey & " of theRecord )
            end" with parameters {theKey, theRecord}
    end searchRecord
    

    The important thing to note is that when the variable (in this case, myVar) is passed to the run script code, the variable’s contents are passed. That’s what you’d expect it to do, but what that means is that the variable is essentially hard-coded into this new script — you’re not recreating myVar, you’re just passing along myVar’s contents.

    Boom. A regular “get value of property x of the record.” And that data is then returned from the internal script to the parent, containing script. You can then call this as you would a subroutine handler, whenever you need to get a value for an unknown-at-compile-time property.

    Essentially, the shopkeeper won’t sell to you in person, but if you step outside and ring him then you’re set.

    Yeah. It’s a hack. Thankfully a short, understandable one, but a hack nonetheless. It’s crazy and this should be built into the language. Here’s JavaScript’s way again, for reference:

    objectName[myVariable];
    

    Why is this missing? God knows.

    I’m pretty sympathetic towards AppleScript. It’s the first language I’ve ever done any kind of programming in, and it’s been — and continues to be — useful at home and essential at work. I do like it, and there’s things you can’t do without it — for better and for worse.

    I plan to learn Python at some point so I may end up leaning towards “for worse” quite heavily in the future, but at the moment I’m happy using AppleScript to accomplish the (mostly work-related) things I need to do, even with all of its oddities.

    (My “favourite” weird things are:

    • Getting the wrong tell target (generally)
      In BBEdit (for example) do I target “text window 1” or “text of text window 1”? It seems to be “text of” but I’m sure there are situations where the other is needed. The reasons why they’re two separate things aren’t clear.
    • Bizarrely-scoped InDesign properties
      Did you know that “page range,” used when you export a .pdf, is an application-wide setting that you have to specify beforehand, not when you actually export the .pdf? Yeah.
    • Setting page-item InDesign properties
      Let’s create a new frame. When we make it we can give it a bunch of properties, such as its position, size, stroke, fill, layer. How about object style? It’s listed in the options! Let’s set it alongside the position and size. Nope! It fails silently and you’re left with an unstyled frame. The way around this is to assign your new frame to a variable, and then apply the object style after you create it.

    There’s a bunch more, like slash-separated paths being treated differently from colon-separated paths — often requiring a line of code to convert them, because in many cases you can only get one type. But whatever. I’m a little worried that “proper” programming languages won’t be masochistic enough.)

    Jesus Christ. Somebody call somebody. Call somebody.

    • 7 months ago
    • #AppleScript
  • “Learning AppleScript can really pay off. Once you’ve suffered through its oddities enough to be able to write it without weeping, you’ll be tempted to write scripts that achieve things you can do with copy and paste, like this one.”

    Maybe I should have thought harder about that first line.

    • 8 months ago
  • Everyday automation

    Learning AppleScript can really pay off.

    It isn’t always the easiest language to work with, as anyone who’s ever used it will tell you. Commands that look like they should work sometimes don’t, you occasionally have to use bizarre methods to refer to that thing right there. It’s not a picnic.

    But I’ve been using it for a little while and am pretty comfortable with it. I’ve ended up using it more and more for everyday tasks, or one-off situations that would usually require a lot of donkey work.

    Let me share one that’s just happened.

    I was browsing the New Yorker’s print shop and — I thought — was opening up the individual pages for covers I liked and wanted to go back to after I’d finished browsing.

    It turns out that I was just opening up the preview images in tabs. Whoops. And their URLs don’t contain a unique code that’s shared with their purchase pages. Ah. And there are 10 of them. Erm.

    The one similarity they do have is that the filename is kinda sorta repeated on the purchase page, but it’s hyphen-separated so (I thought, wrongly) you can’t just copy and paste it into a search field.

    So I cooked up this little script in about a minute:

    tell application "Safari"
        set theURL to (get the URL of the front document)
    end tell
    
    set printsOffset to (get the offset of "/prints/" in theURL)
    set jpgOffset to (get the offset of ".jpg" in theURL)
    
    set hyphenString to characters (printsOffset + 8) ¬
    through (jpgOffset - 1) of theURL as text
    set AppleScript's text item delimiters to "-"
    set theWords to (get the text items of hyphenString)
    set AppleScript's text item delimiters to " "
    set searchString to theWords as string
    set AppleScript's text item delimiters to "" -- return to default
    
    tell application "Safari"
        tell the front document
            search the web for searchString
        end tell
    end tell
    

    Then I saved it, gave it a shortcut in FastScripts, opened up the first tab in Safari, hit the shortcut, tabbed to select the first URL in the search results, then hit enter to load it. Rinse, repeat.

    As I was writing this I tried just pasting the filename into Google and — shocker — that works. Which makes me look a little dumb.

    And the script isn’t even that sophisticated; I could have used a repeat command to loop through each tab, for example.

    However my wider point about getting your teeth into automation still stands. I’ve written loads of little scripts like this over the past few months and, combined, they’ve saved me bags of time and plenty of frustration.

    Hell, the InDesign script I wrote to set a text frame’s name (≥CS5) to its script label (≤CS4) alone saved me half an hour.

    The problem of AppleScript being frustrating to learn — and continually frustrating with certain applications’ opaque AppleScript dictionaries and command parsing — does stand in the way of more people using it (which is why Automator exists, as limited as it is).

    But if you’re tempted, even a little, give it a go. If you don’t have any programming experience I recommend AppleScript 1-2-3. (Don’t buy the Kindle edition. You want the paper copy.)

    Also make sure to check out Script Debugger 5, which at $199 (about £170 including VAT) is a bit pricey if you’re just starting but makes things so much easier it’s a joke. It’s like switching from MSPaint to Photoshop. You’ll never go back to (Apple)Script Editor.

    • 8 months ago
    • 1 notes
    • #AppleScript
    • #technology
  • Speaking of AppleScript, this is what 450 lines of it look like.

(When you paste it into BBEdit, set the font size to 2pt and switch on syntax highlighting.)

    Speaking of AppleScript, this is what 450 lines of it look like.

    (When you paste it into BBEdit, set the font size to 2pt and switch on syntax highlighting.)

    • 8 months ago
    • #AppleScript
  • I’m on GitHub

    I’m planning to write about the AppleScript work I’ve been doing but for now this is the best place to look if you’re interested.

    GitHub and the GitBox Mac client have really influenced me to share this stuff, as well as to think about learning new languages. More on that later.

    • 8 months ago
  • Update: Restart in Windows

    UPDATE 06/10/2012: Please ignore this post and look at my most recent post about the script.


    I came a across a more sophisticated method of getting your Bootcamp partition’s device ID for my restart in Windows AppleScript, so you may want to grab the new code (also below).

    It no longer uses my hack to pull the device ID from a certain character position in the result of a shell command — which would break if you had more than 10 disks or partitions anyway — and instead just uses grep and awk to strip out the identifier. It should be pretty solid.

    Click here to open the script in your script editor.

    set deviceID to (do shell script "diskutil info [Your Bootcamp ¬
    partition's name] | grep Identifier | awk '{print $3}'")
    do shell script "bless -device /dev/" & deviceID & " -legacy -setBoot ¬
    -nextonly" password "XXXXXXX" with administrator privileges
    tell application "Finder" to restart
    • 8 months ago
    • #AppleScript
Next page
  • Page 1 / 2