CodeMunki.com
Mostly AppleScript With Some Other Programming Stuff Thrown In For Good Measure
Getting Dimensions of EPS and JPEG
Raster Images Saved From Photoshop CS5
18 Dec 2012 12:15 PM

The above header is rather specific, but only because I've not taken time to try other file types or applications. Feel free to try this idea in other situations. You'll need to experiment a bit and tweak things to get it to do what you need.

I needed to determine X and Y pixel dimensions and the resolution (in pixels per inch) for EPS and JPEG image files. I decided to use the AppleScript read file command, so as to avoid opening the files in an application, such as Photoshop. After reading each file, its content is parsed using text manipulation to extract the desired dimensions.

The logic uses two variations of the read file command—with the using delimiter option, and without. With the using delimiter option, the delimiter can only be a single character, which is not terribly useful. However, in the case of EPS files, it works well enough to use the forward slash ("/") as the delimiter. For JPEG files, I chose to read the entire file, then split it myself using multiple delimiters, each containing multiple characters.

Here's the code:

        set {dimX, dimY, resX, resY} to {0.0, 0.0, 0.0, 0.0} -- default
        set f to open for access alias imagePath --HFS path to EPS or JPEG file
        if imagePath ends with ".eps" then
                set parts to read f using delimiter "/"
                close access f
                repeat with i from 1 to (length of parts)
                        set thisText to item i of parts
                        if thisText contains "" then
                                set dimX to getTextItem(thisText, {"<", ">"}, 4)
                        else if thisText contains "" then
                                set dimY to getTextItem(thisText, {"<", ">"}, 4)
                        else if thisText contains "" then
                                set resX to getTextItem(thisText, {"<", ">"}, -1) / 10000
                        else if thisText contains "" then
                                set resY to getTextItem(thisText, {"<", ">"}, -1) / 10000
                        end if
                        if 0.0 is not in {dimX, dimY, resX, resY} then exit repeat
                end repeat
        else if (imagePath ends with ".jpg") or (imagePath ends with ".jpeg") then
                set txt to read f
                close access f
                set AppleScript's text item delimiters to {" exif:", " tiff:"}
                set parts to text items 2 thru -2 of txt
                set AppleScript's text item delimiters to ""
                repeat with i from 1 to (length of parts)
                        set thisText to item i of parts
                        if thisText starts with "PixelXDimension=" then
                                set dimX to getTextItem(thisText, {"\""}, 2)
                        else if thisText starts with "PixelYDimension=" then
                                set dimY to getTextItem(thisText, {"\""}, 2)
                        else if thisText starts with "XResolution=" then
                                set resX to getTextItem(thisText, {"\"", "/"}, 2) / 10000
                        else if thisText starts with "YResolution=" then
                                set resY to getTextItem(thisText, {"\"", "/"}, 2) / 10000
                        end if
                        if 0.0 is not in {dimX, dimY, resX, resY} then exit repeat
                end repeat
        end if
        if resX is not resY then error "Asymmetrical image resolution." number 300 from {resX, resY}
        return {dimX, dimY, resX}

        on getTextItem(theText, theDelims, itemNumber)
                set AppleScript's text item delimiters to theDelims
                set textItem to text item itemNumber of theText
                set AppleScript's text item delimiters to ""
                return textItem
        end getTextItem
Obfuscation of Hard-Coded Data in an AppleScript
16 Nov 2012 11:00 AM

If you, like me, are hesitant to put hard-coded user IDs, passwords, or whatever into your scripts, this bit of obfuscation may be of interest. The bad news is that the information is still in the script and decipherable by an astute intruder. The good news is that the typical computer user will find the ciphertext stored in your scripts to be utterly mysterious and incomprehensible.

Let me state clearly that this method is one of "security by obscurity." It is NOT going to provide genuine security, so use it at your own risk. I suppose one could slightly improve the pseudosecurity of this technique by writing the ciphertext to a text file, thus separating the information from the deciphering handlers.

As to the methodology employed, the code is my own spin on the ROT18 cipher with some extra twists thrown in. One twist is that spaces, periods, commas, and question marks are enciphered, along with letters and numbers (making it ROT20, I guess). Other characters, such as parentheses, will appear in the ciphertext as is. Another twist is the breaking up of the text into five chunks (or three, if the text is short) and shuffling the order of those chunks. If the text is really short, the order of the characters is simply reversed. Like ROT 18, this logic is self-inverting. That is, the same logic both enciphers plaintext and deciphers ciphertext.

Maybe you won't use it for passwords. You and a friend could generate coded messages to email to back and forth. Anyway, for whatever good it is, here is the code. Enjoy!

        set theText to "Quick Brown Fox, 73, Jumps(?) Over Lazy Dog 129."
        set cipherText to applyCodec(theText)
        set plaintextAgain to applyCodec(cipherText)
        return {cipherText, plaintextAgain}

        on applyCodec(theText) -- mutant ROT20 retains case of plaintext
                set lowerCharSet to "abcdefghijklmnopqrstuvwxyz.,? 0123456789"
                set lowerCipherSet to "uvwxyz.,? 0123456789abcdefghijklmnopqrst"
                set upperCharSet to "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                set upperCipherSet to "NOPQRSTUVWXYZABCDEFGHIJKLM"
                set cipherText to {}
                repeat with i from 1 to (length of theText)
                        set thisChar to character i of theText
                        considering case
                                if thisChar is in lowerCharSet then
                                        set x to offset of thisChar in lowerCharSet
                                        set end of cipherText to character x of lowerCipherSet
                                else if thisChar is in upperCharSet then
                                        set x to offset of thisChar in upperCharSet
                                        set end of cipherText to character x of upperCipherSet
                                else
                                        set end of cipherText to thisChar
                                end if
                        end considering
                end repeat
                return shuffle(cipherText as text)
        end applyCodec

        on shuffle(theText)
                set usingFifths to false
                if usingFifths then
                        set x to 5
                else
                        set x to 3
                end if
                set len to count of theText
                if len < x then
                        set shuffledChars to reverse of characters of theText
                else
                        if usingFifths then
                                set {a, b, c, d, e} to splitIntoFifths(len)
                        else
                                set {a, b, c} to splitIntoThirds(len)
                        end if
                        set shuffledChars to {}
                        set end of shuffledChars to reverse of characters (a + 1) thru (a + b) of theText
                        set end of shuffledChars to reverse of characters 1 thru a of theText
                        set end of shuffledChars to reverse of characters ¬
                                (a + b + 1) thru (a + b + c) of theText
                        if usingFifths then
                                set end of shuffledChars to reverse of characters ¬
                                        (a + b + c + d + 1) thru (a + b + c + d + e) of theText
                                set end of shuffledChars to reverse of characters ¬
                                        (a + b + c + 1) thru (a + b + c + d) of theText
                        end if
                end if
                return shuffledChars as text
        end shuffle

        on splitIntoFifths(n)
                set a to round (n / 5) rounding up
                set b to round ((n - a) / 4)
                set c to round (n - a - b) / 3
                set d to round (n - a - b - c) / 2
                set e to round (n - a - b - c - d)
                return {a, b, c, d, e}
        end splitIntoFifths

        on splitIntoThirds(n)
                set a to round (n / 3) rounding up
                set b to round ((n - a) / 2)
                set c to round (n - a - b)
                return {a, b, c}
        end splitIntoThirds
Mixing Science and Religion
08 Mar 2012 05:00 PM

Some is to Sum as None is to Nun.
Arc is to Noah as Atom is to Eve.
Angle is to Angel as Arc is to Ark.
Checking an AppleScript for Unused and Undefined Variables
16 Jan 2012 06:00 PM

Thanks to a recent thread on the AppleScript User's mailing list, I learned of a very useful function in Smile, the free AppleScript development software from Satimage Software, available here. Smile is not just a script editor, it's a complex development and working environment. My ignorance shows when I say that I've never quite "made sense" of the Smile environment. But then, I've never needed the advanced capabilities found in Smile—until now.

The following script uses the functionality of Smile's validate command to check for both unused and undefined variables. It also uses the scriptability of Script Debugger, my editor of choice, to determine the line number of each problem's occurrence, making it easy to locate within the script window. [If you don't use Debugger, you should be able to rework the script for use with AppleScript Editor or Smile.]

I've added this script to Script Debugger's script menu so, when invoked, it will check the frontmost script window. The result is returned by a dialog box and, from there, can be copied to the clipboard or written to a text file.

        -- get script text
        tell application "Script Debugger 4.5"
                set {scriptName} to name of windows whose index is 1
                set scriptSource to script source of document of window scriptName
                set scriptFilePath to (file spec of document of window scriptName) as text
        end tell
        set dialogMessage to "Validate script \"" & scriptName & "\"?"
        displayDialog(dialogMessage, {"Cancel", "OK"}, 2, 1)
        tell application "System Events"
                set fileExt to name extension of alias scriptFilePath
        end tell
        set baseName to text 1 thru -((length of fileExt) + 2) of scriptName
        -- hide Smile and its windows
        tell application "System Events"
                if not (exists application process "Smile") then
                        activate application "Smile"
                end if
                set visible of application process "Smile" to false
        end tell
        -- do validation of script
        tell application "Smile"
                set background to true
                set scriptWindow to make new script window
                set text of scriptWindow to scriptSource
                validate scriptWindow
                set textWindow to last text window whose name is "Script errors: "
                set validationResult to text of textWindow -- validation result
                close textWindow saving no
                close scriptWindow saving no
        end tell
        -- determine affected line numbers
        set affectedLineNumbers to {}
        tid({"show window \"\" selection "})
        set blah to text items of validationResult
        tid("")
        repeat with i from 1 to (length of blah)
                set thisInstance to item i of blah
                if thisInstance starts with "{" then
                        tid(", ")
                        set x to text item 1 of (text 2 thru -1 of thisInstance)
                        tid("")
                        set scriptChunk to text 1 thru x of scriptSource
                        set end of affectedLineNumbers to length of (paragraphs of scriptChunk)
                end if
        end repeat
        set defaultAnswer to convertAnythingToString({validationResult:validationResult, ¬
                affectedLineNumbers:affectedLineNumbers})
        set action to button returned of displayDialogWithText("Validation result:", ¬
                defaultAnswer, {"Write to Text File", "OK"}, 2, 1)
        if action is "Write to Text File" then
                set filePath to ((path to desktop) as text)
                set fileRef to open for access ((filePath & "temp_validation.txt") ¬
                        as file specification) with write permission
                set eof of fileRef to 0
                write defaultAnswer to fileRef as «class utf8»
                close access fileRef
                tell application "System Events"
                        if exists alias (filePath & baseName & "_validation.applescript") then
                                delete alias (filePath & baseName & "_validation.applescript")
                        end if
                        set name of alias (filePath & "temp_validation.txt") to ¬
                                (baseName & "_validation.applescript")
                end tell
                set dialogMessage to "Text file \"" & baseName & ¬
                        "_validation.applescript\" is on the Desktop."
                displayDialog(dialogMessage, "OK", 1, 1)
        end if

        -- handlers follow
        on convertAnythingToString(something)
                if class of something is in {string, text, Unicode text} then
                        set listString to ("\"" & something & "\"")
                else if something is {} then
                        set listString to "{}"
                else if something is in {missing value, null, true, false} then
                        set listString to something as text
                else
                        try
                                something as integer
                                set listString to something as text
                        on error errText
                                set opening to offset of "{" in errText
                                set closing to offset of "}" in "" & (reverse of (characters of errText))
                                set listString to text opening thru -closing of errText
                        end try
                end if
                return listString
        end convertAnythingToString

        on displayDialog(dialogMessage, buttonList, defButton, iconNum)
                tell application "Script Debugger 4.5"
                        activate
                        with timeout of 17700 seconds
                                display dialog dialogMessage buttons buttonList ¬
                                        default button defButton with icon iconNum
                        end timeout
                end tell
        end displayDialog

        on displayDialogWithText(dialogMessage, defaultAnswer, buttonList, defButton, iconNum)
                tell application "Script Debugger 4.5"
                        activate
                        with timeout of 17700 seconds
                                display dialog dialogMessage default answer defaultAnswer ¬
                                        buttons buttonList default button defButton with icon iconNum
                        end timeout
                end tell
        end displayDialogWithText

        on tid(d)
                set AppleScript's text item delimiters to d
        end tid

This has found problems in my scripts, saving me some potential headaches. Enjoy!

Using 'load script' With Scripts Saved In Text Format
20 Jan 2011 06:30 PM

Often I use the 'load script' command to bring in the code from another script file, so its handlers are available in the current script. It works something like this:

         load script alias "MacHD:Users:stanc:repository:Some Project:Some Script.app"

This works great unless the script you want to load is saved in text format (with the ".applescript" extension). In that case, you get error number -1752 and the message "Script doesn’t seem to belong to AppleScript." Great! So now what? This is a problem for me, because I store all my AppleScript source code in text format, which makes things much easier for version control and dealing with absent applications.

Just use the following handler, which will attempt to load the script file normally and, if that fails, will read the file as text and turn that into the desired script object.

        on loadScript(scriptFileToLoad)
                set scriptFileToLoad to scriptFileToLoad as text -- to be safe
                try
                        set scriptObject to load script alias scriptFileToLoad
                on error number -1752 -- text format script
                        set scriptObject to run script ("script s" & return & ¬
                                (read alias scriptFileToLoad as «class utf8») & ¬
                                return & "end script " & return & "return s")
                end try
                return scriptObject
        end loadScript

Just pass the script's path to the handler as a string or alias. Like so:

        set scriptObject to loadScript("MacHD:Users:stanc:MyScript.applescript")

Hope you can use this little bit of trickery.

Happy New Year, 2011!
31 Dec 2010 09:30 AM

Our plan is to find more time in the coming year to devote to sharing some of our thoughts and knowledge about AppleScript and programming. Though time is never actually "found," improvements in organizing our schedule should free up some time from other relatively unimportant activities.

Greetings!
30 Aug 2010 02:00 PM

This is a fine example of minimalism, no? But we're just getting started (and we're slow, not to mention lazy), so don't hold your breath or anything. Okay?