├── Date.scptd
└── Contents
│ ├── Info.plist
│ └── Resources
│ ├── Date.sdef
│ ├── Scripts
│ └── main.scpt
│ └── description.rtfd
│ └── TXT.rtf
├── File.scptd
└── Contents
│ ├── Info.plist
│ └── Resources
│ ├── File.sdef
│ ├── Scripts
│ └── main.scpt
│ └── description.rtfd
│ └── TXT.rtf
├── List.scptd
└── Contents
│ ├── Info.plist
│ └── Resources
│ ├── List.sdef
│ ├── Scripts
│ └── main.scpt
│ └── description.rtfd
│ └── TXT.rtf
├── Number.scptd
└── Contents
│ ├── Info.plist
│ └── Resources
│ ├── Number.sdef
│ ├── Scripts
│ └── main.scpt
│ └── description.rtfd
│ └── TXT.rtf
├── Objects.scptd
└── Contents
│ ├── Info.plist
│ └── Resources
│ ├── Objects.sdef
│ ├── Scripts
│ └── main.scpt
│ └── description.rtfd
│ └── TXT.rtf
├── TODOs.txt
├── TestTools.scptd
└── Contents
│ ├── Info.plist
│ └── Resources
│ ├── Script Libraries
│ └── TestSupport.scpt
│ ├── Scripts
│ └── main.scpt
│ ├── TestTools.sdef
│ ├── bin
│ └── osatest
│ └── description.rtfd
│ └── TXT.rtf
├── Text.scptd
└── Contents
│ ├── Info.plist
│ └── Resources
│ ├── Scripts
│ └── main.scpt
│ ├── Text.sdef
│ └── description.rtfd
│ └── TXT.rtf
├── TypeSupport.scptd
└── Contents
│ ├── Info.plist
│ └── Resources
│ ├── Scripts
│ └── main.scpt
│ └── description.rtfd
│ └── TXT.rtf
├── Web.scptd
└── Contents
│ ├── Info.plist
│ └── Resources
│ ├── Scripts
│ └── main.scpt
│ ├── Web.sdef
│ └── description.rtfd
│ └── TXT.rtf
├── bin
└── asdiff
└── unittests
├── Date.unittest.scpt
├── File.unittest.scpt
├── List.unittest.scpt
├── Number.unittest.scpt
├── Objects.unittest.scpt
├── Text.unittest.scpt
├── TypeSupport.unittest.scpt
└── Web.unittest.scpt
/Date.scptd/Contents/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 | format date
command converts a date object to canonical ISO8601-formatted text, for example:
format date (date "Thursday, 7 January 2016 at 01:41:34")
26 | → "2016-01-07T01:41:34Z"
27 |
28 | Unlike the date text used by AppleScript’s date TEXT
specifier, where the language and format changes according to each user’s current locale and time zone settings, ISO8601-formatted date text has a standard structure that is widely recognized and does not vary, making it highly portable. This allows ISO8601-style date text to be safely stored as plain text, sent to users in other countries, and converted back to a date object anywhere at any time, without risk of errors or incorrect results due to language differences, ambiguous ordering, or unknown time zone.
By default, dates are formatted for Universal Time (a.k.a. GMT). If the time zone
parameter is given, the date is formatted for that time zone instead:
format date (date "Thursday, 7 January 2016 at 01:41:34") time zone (5 * hours)
33 | → "2016-01-07T06:41:34+05:00"
34 |
35 | format date (date "Thursday, 7 January 2016 at 01:41:34") time zone "Africa/Addis_Ababa"
36 | → "2016-01-07T04:41:34+03:00"
37 |
38 | To format a date for the host machine’s local time zone, use the current time zone
command to retrieve the local time zone’s name first:
format date (current date) time zone (current time zone)
41 |
42 | For information on formatting dates using custom formats, see the Customizing Date Formats section below.
43 | ]]> 44 | 45 | 46 | 47 | 48 |parse date
command creates a date object from canonical ISO8601-formatted text, for example (assuming the user’s current time zone is GMT):
64 |
65 | parse date "2016-01-07T00:41:34-0800"
66 | → date "Thursday, 7 January 2016 at 08:41:34" (note: AS dates always display local time)
67 |
68 | For information on formatting dates using custom formats, see the Customizing Date Formats section below.
69 | ]]> 70 |split date (date "Friday, 1 January 2016 at 12:00:00")
102 | → {class:date component record, year:2016, month:January, day:1, hours:0, minutes:0, seconds:0, timezone:"America/New_York"}
103 |
104 | To split the date into a record that describes the same date and time information using Greenwich Mean Time (which is 5 hours ahead of America/New_York):
105 | 106 |split date (date "Friday, 1 January 2016 at 12:00:00") time zone "GMT"
107 | → {class:date component record, year:2016, month:1, day:1, hours:5, minutes:0, seconds:0, timezone:"GMT"}
108 |
109 | ]]>
110 | join date {year:1990, month:June, day:15, hours:7, minutes:45, seconds:0, timezone:"America/New_York"}
126 | → date "Friday, 15 June 1990 at 07:45:00" (assuming the current time zone is GMT+5)
127 |
128 | All record properties are optional, thus the following creates a date object describing midnight local time on January 1, 2016:
129 | 130 |join date {year:2016}
131 | → date "Friday, 1 January 2016 at 00:00:00"
132 |
133 | If the date components record does not contain a timezone
property then it is interpreted as local time by default. The optional time zone
parameter can be used to specify a different default time zone if necessary. The following example creates a date object describing midnight in New York on January 1, 2016:
join date {year:2016} time zone "America/New_York"
136 |
137 | If the local time zone is “GMT”, AppleScript will display the resulting date object as follows:
138 | 139 |→ date "Friday, 1 January 2016 at 05:00:00" (GMT is 5 hours ahead of New York)
140 |
141 | Or, if the local time zone is “America/Los_Angeles”:
142 | 143 |→ date "Thursday, 31 December 2015 at 17:00:00" (Los Angeles is 3 hours behind New York)
144 |
145 | ]]>
146 | { class : date component record,
154 | year : integer,
155 | month : integer or constant,
156 | day : integer,
157 | hours : integer,
158 | minutes : integer,
159 | seconds : integer
160 | timezone: text or integer }
161 |
162 | All properties are optional. The month
property can be a month constant (January
, February
, etc) or integer. The timezone
property should be a time zone ID or offset to GMT in seconds. Other properties must be integers.
locale info for "en_GB" in language "de_DE"
211 | → { localeIdentifier:"en_GB",
212 | localeName:"Englisch (Vereinigtes Königreich)",
213 | languageCode:"en",
214 | countryCode:"GB"}
215 | ]]>
216 | time zone info for "GMT" in language "de_DE"
241 | → { timeZoneID:"Europe/London",
242 | secondsToGMT:0,
243 | standardName:"Mittlere Greenwich-Zeit",
244 | standardAbbreviation:"GMT",
245 | daylightSavingName:"Britische Sommerzeit",
246 | daylightSavingAbbreviation:"BST",
247 | isDaylightSaving:false,
248 | daylightSavingOffset:0,
249 | nextDaylightSavingTransition:date "Sunday, 27 March 2016 at 02:00:00" }
250 | ]]>
251 | time zone offset
command returns different results depending on whether none, one, or both parameters are given:
271 |
272 | If no parameters are given (i.e. both are missing value
), the result is the number of seconds between the current time zone and GMT (equivalent to time zone offset to "GMT"
):
(time zone offset) / hours → 3.0
If one parameter is given, the result is the number of seconds between that time zone and the current time zone. For example, if the current time zone is “Africa/Addis_Ababa”:
278 | 279 |(time zone offset from "Asia/Shanghai") / hours → 5.0
280 |
281 | (time zone offset from "America/New_York") / hours → -8.0
282 |
283 | (time zone offset to "America/New_York") / hours → 8.0
If both parameters are given, the result is the number of seconds between the two time zones:
286 | 287 |(time zone offset from "Africa/Addis_Ababa" to "GMT") / hours
288 | → 3.0 (Addis Ababa is 3 hours, or 10800 seconds, ahead of Greenwich Mean Time)
289 |
290 | (time zone offset from "America/New_York" to "Africa/Addis_Ababa") / hours
291 | → -8.0 (New York is 8 hours behind Addis Ababa)
Time zones can also be given as offsets to GMT in seconds (e.g. Central Europe is 1 hour, or 3600 seconds, ahead of GMT):
295 | 296 |(time zone offset from (1 * hours) to "America/New_York") / hours → 6.0
297 |
298 | ]]>
299 | parse date
and format date
commands allow custom formats to be specified via their optional using
parameters. These parameters accept either a text value containing special character-based date format patterns, or one or a pair of predefined constants representing commonly used formats.
312 |
313 | For example, the default ISO8601 date format is defined by the pattern “yyyy-MM-dd'T'HH:mm:ssZZ”, where each letter sequence describes a particular date field, except for the “T” letter which is wrapped in single quotes (the escape character) so appears unchanged.
314 | 315 |For a complete list of supported patterns, see Appendix F: Date Format Patterns of Unicode Technical Standard #35. Commonly used patterns are listed below for convenience.
316 | 317 |Value | Format | Pattern | Example | Notes |
---|---|---|---|---|
year | number | y | 2016 | |
year | 2-digit number | yy | 16 | |
year | 4-digit number | yyyy | 2016 | |
month | number | M | 9 | |
month | 2-digit number | MM | 09 | |
month | abbreviated name | MMM | Sept | [1] |
month | full name | MMMM | September | [1] |
day | number | d | 6 | |
day | 2-digit number | dd | 06 | |
weekday | abbreviated name | eee | Tues | [2] |
weekday | full name | eeee | Tuesday | [2] |
hour | number (1-12) | h | 7 | |
hour | 2-digit number (01-12) | hh | 07 | |
hour | 2-digit number (00-23) | HH | 07 | |
minute | number | m | 5 | |
minute | 2-digit number | mm | 05 | |
second | number | s | 0 | |
second | 2-digit number | ss | 00 | |
period | AM or PM | a | AM | |
timezone | number | ZZ | -0800 | |
timezone | full name | zzzz | Pacific Daylight Time | [2] |
[1] If the parse date
or format date
command’s for locale
parameter is “none” (the default), the “MMM” and “MMMM” patterns will use a generic “Mnn” representation instead of the month’s actual name, e.g. “M09” instead of “September”. To parse/format the month name using a particular language, pass the appropriate locale ID via the for locale
parameter. For example, pass “current” to use the language specified by the user’s current locale, “en_US” for US English, “fr_FR” for French, and so on.
[2] As with month names, to parse and format weekday and time zone names using a particular language, use the for locale
parameter to specify the appropriate locale.
The parse date
and format date
commands also accept the following predefined constants which are convenient when presenting date information to users:
Constant | Basic Pattern [3] | Example |
---|---|---|
canonical date format | yyyy-MM-dd'T'HH:mm:ssZZ | 2016-09-06T070109-0800 |
short date format | dd/MM/y | 06/09/2016 |
medium date format | d MMM y | 6 Sept 2016 |
long date format | d MMMM y | 6 September 2016 |
full date format | EEEE, d MMMM y | Tuesday, 6 September 2016 |
short time format | HH:mm | 07:05 |
medium time format | HH:mm:ss | 07:05:00 |
long time format | HH:mm:ss z | 07:05:00 PT |
full time format | HH:mm:ss zzzz | 07:05:00 Pacific Daylight Time |
[3] Except for canonical date format
, which is locale- and language-independent, the exact pattern and language used automatically adjusts for the specified locale. For example, if the locale is “none” (the default), the short date format
uses the pattern “yyyy-MM-dd” (e.g. 2016-09-06); if the locale is “en_US” then it uses a US-style pattern, “M/d/y” (e.g. 9/6/16); if it is “en_GB” then “dd/MM/y” is used (as shown above); and so on.
convert path "/Users/jsmith/User Guide.txt" to HFS path format
24 | → "Macintosh HD:Users:jsmith:User Guide.txt"
25 |
26 | convert path "Macintosh HD:Users:jsmith:User Guide.txt" from HFS path format
27 | → "/Users/jsmith/User Guide.txt"
28 |
29 | convert path "/Users/jsmith/User Guide.txt" to alias file object
30 | → alias "Macintosh HD:Users:jsmith:User Guide.txt"
31 |
32 | set theFile to POSIX file "/Users/jsmith/User Guide.txt"
33 | convert path theFile to file URL format -- (a ‘from’ parameter isn't needed here)
34 | → "file:///Users/jsmith/User%20Guide.txt"
35 | ]]>
36 | normalize path "/Users/jsmith/Pictures//./../Movies/"
69 | → "/Users/jsmith/Movies"
70 |
71 | normalize path "~/Movies/" with tilde expansion
72 | → "/Users/jsmith/Movies"
73 |
74 | normalize path "Applications" with absolute expansion
75 | → "/Applications" (the exact result depends on the current working directory)
76 |
77 | normalize path "./Music/iTunes/"
78 | → "Music/iTunes"
79 |
80 | This command normalizes a path by performing the following steps as needed:
81 | 82 |tilde expansion
is trueabsolute expansion
is trueFor absolute paths, it also resolves references to the parent folder (that is, the component “..”) to the real parent folder if possible. For relative paths, references to the parent folder are left in place.
95 | 96 |If a relative path is given and absolute expansion
is true but the current working directory is unknown, an error (-1728) will occur.
join path {"/", "Users", "jsmith", "Desktop"}
114 | → "/Users/jsmith/Desktop"
115 |
116 | join path {"Documents", "ReadMe"} using file extension "txt"
117 | → "Documents/ReadMe.txt"
118 |
119 | join path {POSIX file "/Users/jsmith", "Documents/ReadMe.txt"}
120 | → "/Users/jsmith/Documents/ReadMe.txt"
121 |
122 | To construct an absolute path, the first item should be/start with "/" (the first item may also be an alias or file object); the remaining items are always treated as relative paths.
123 | 124 |If the using file extension
parameter is not empty, it will be added to the end of the last path component, ignoring any trailing slashes. A period separator (.
) will be inserted automatically; do not supply it yourself.
set {folderPath, fileName} to split path "/Users/jsmith/Documents/ReadMe.txt"
140 | → {"/Users/jsmith/Documents", "ReadMe.txt"}
141 |
142 | split path (POSIX file "/Users/jsmith/Documents/ReadMe.txt") at all components
143 | → {"/", "Users", "jsmith", "Documents", "ReadMe.txt"}
144 |
145 | split path "/Users/jsmith/Documents/ReadMe.txt" at file extension
146 | → {"/Users/jsmith/Documents/ReadMe", "txt"}
147 | ]]>
148 | read from file (alias "Macintosh HD:Users:jsmith:Documents:welcome.txt")
175 | → "Hello!"
176 |
177 | For convenience, the read from file
command also accepts a POSIX path string as its direct parameter:
read from file "/Users/jsmith/Documents/welcome.txt"
180 |
181 | While nowadays UTF8 is the preferred encoding for plain text files, the read from file
command can also read plain text files written in a variety of older legacy encodings. For example, to read a plain text file that was written in the legacy MacOSRoman encoding:
read from file myFile using MacOSRoman encoding
184 |
185 | Or to read the file using the host machine's default legacy encoding (equivalent to Standard Additions' read myFile as string
):
186 |
187 |
read from file myFile using primary encoding
188 |
189 | The read from file
command can also be used to read AppleScript values previously written to file using write to file
or Standard Additions' write
command. For example, to read a data file containing an AppleScript record:
read from file "/Users/jsmith/script.data" as record
192 | → {name: "Bob", age:42}
193 | ]]>
194 | write to file (alias "Macintosh HD:Users:jsmith:Documents:welcome.txt") data "Hello!"
210 |
211 | For convenience, the write to file
command also accepts a POSIX path string as its direct parameter:
write to file "/Users/jsmith/Documents/welcome.txt" data "Hello!"
214 |
215 | The read from file
command can also write plain text files in a variety of legacy text encodings. For example, to write a plain text file in the legacy MacOSRoman encoding:
write file myFile data theText using MacOSRoman encoding
218 |
219 | Or to write the file using the host machine's default legacy encoding (equivalent to Standard Additions' write theText to myFile as string
):
220 |
221 |
write to file myFile data theText using primary encoding
222 |
223 | The write to file
command can also be used as a convenient shortcut for Standard Additions' write
command to write AppleScript values to file. For example, to write a data file containing an AppleScript record:
write to file "/Users/jsmith/script.data" data {name: "Bob", age:42} as record
226 |
227 | ]]>
228 | #!/usr/bin/osascript
281 |
282 | use script "File"
283 |
284 | to run pathsList
285 | if pathsList is {} then set pathsList to {(current working directory)'s POSIX path}
286 | repeat with pathRef in pathsList
287 | set aFile to (normalize path pathRef's contents with ¬
288 | tilde expansion, absolute expansion and link expansion) as POSIX file
289 | -- process the file...
290 | end repeat
291 | end run
292 |
293 | Throws error number -1728 if the current working directory is unknown.
294 | ]]> 295 |user interaction
option is false, the command will wait until the previous process has finished writing to the script’s stdin before returning. If it is true, it will return a line of text (minus the trailing linefeed) as soon as the Return key is pressed, or ‘missing value’ if there is no more text to be read.
315 | ]]>
316 | #!/usr/bin/osascript
329 |
330 | use script "File"
331 |
332 | write to standard output "What is your name?"
333 | set userName to read from standard input with user interaction
334 | if userName is "" then set userName to "stranger"
335 | write to standard output "Hello, " & userName & ". How are you feeling today?"
336 | set userMood to read from standard input with user interaction
337 | if userMood contains "happy" or userMood contains "good" then
338 | write to standard output "I’m delighted to hear that."
339 | else
340 | write to standard output "C’est la vie."
341 | end if
342 | ]]>
343 | parse command line arguments
works:
363 |
364 | set argv to {"-f", "plain", "-r", "/Users/jsmith/file1.rtf", "~/file2.rtf"}
365 |
366 | parse command line arguments argv ¬
367 | options {¬
368 | {propertyName:"fileFormat", shortName:"f"}, ¬
369 | {propertyName:"outputFolder", shortName:"o", valueType:file, defaultValue:missing value}, ¬
370 | {propertyName:"replacesExistingFiles", shortName:"r", valueType:boolean}} ¬
371 | arguments {¬
372 | {propertyName:"fileList", valueType:file, isList:true}}
373 |
374 | Given a list of shell command arguments (argv
), parse command line arguments
reads each item in the list, initially checking it against a list of supported options (in this case -f
, -o
, and -r
), then against a list of supported arguments once all of the given options have been processed. On completion, the result is a record of all supported options and arguments, with the supplied values converted to the correct types and default values provided when optional options/arguments are omitted:
{ fileFormat:"plain",
377 | outputFolder:missing value,
378 | replacesExistingFiles:true,
379 | fileList:{file "Macintosh HD:Users:jsmith:file1.rtf",
380 | file "Macintosh HD:Users:jsmith:file2.rtf"},
381 | |help|:false,
382 | |version|:false}
383 | ]]>
384 | { propertyName : text,
413 | shortName : text,
414 | longName : text,
415 | valueType : type or list of text,
416 | isList : boolean,
417 | defaultValue : any,
418 | valuePlaceholder : type,
419 | valueDescription : text }
420 |
421 | The record must contain a propertyName
property, plus a shortName
and/or longName
property. All other properties are optional.
propertyName
shortName
longName
valueType
boolean
, integer
, real
, number
, text
, alias
, file
. If boolean
is used, the option does not take a value but instead changes the option’s value from false to true (or the opposite of defaultValue
, if given). Alternatively, if a list of text values is given (e.g. {"play", "pause", "skip"}
), this is treated as an enumeration of allowed values, in which case the given option must be one of the values in this list.isList
defaultValue
missing value
or any of the supported value types. If omitted, and the option is not a boolean flag, a user error will be reported indicating the value is required. (default: error)valuePlaceholder
format command line help
. If omitted, a default placeholder is chosen according to valueType
(INT
, NUM
, TEXT
, FILE
). Used by format command line help
only.valueDescription
format command line help
. Used by format command line help
only.If the options
parameter doesn’t include -h
/--help
and/or -v
/--version
definitions, default definitions will be added automatically.
{ propertyName : text,
454 | valueType : type or list of text,
455 | isList : boolean,
456 | defaultValue : any,
457 | valuePlaceholder : type,
458 | valueDescription : text }
459 |
460 | Argument definition properties have the same meaning as those in option definition records, except that they apply to any arguments that remain after the options have been parsed. The propertyName
property is required, and should be different to the property names used by the option definitions. If the last argument definition record has an isList:true
property, a list of all remaining arguments is assigned to that property.
An AppleScript-based shell script is normally saved as an uncompiled plain text file without an .applescript
extension, made executable by running a chmod +x SCRIPT
on it, and placed into a directory that appears on the user’s $PATH
(e.g. /usr/local/bin
) so that it can be executed as a shell command directly from Terminal.
For convenience, here is a standard template for creating AppleScript-based shell scripts:
479 | 480 |#!/usr/bin/osascript
481 |
482 | use script "File"
483 |
484 | property _version : "xx.xx.xx" -- the script’s version (e.g. "1.2.0")
485 | property _summary : "a one-line summary of the command’s purpose"
486 | property _description : "detailed instructions for use, examples, etc."
487 |
488 | property _optionDefinitions : { ... }
489 |
490 | property _argumentDefinitions : { ... }
491 |
492 | on run argv -- called by osascript
493 | -- argv : list of text -- any command-line arguments supplied by user
494 | set parameterRecord to parse command line arguments argv ¬
495 | options _optionDefinitions arguments _argumentDefinitions
496 | if parameterRecord's help then -- output help text
497 | return format command line help ¬
498 | summary _summary description _description ¬
499 | options _optionDefinitions arguments _argumentDefinitions
500 | else if parameterRecord's |version| then -- output version text
501 | return _version
502 | else -- perform the command and output its result, if any
503 | return runCommand(parameterRecord)
504 | end if
505 | end run
506 |
507 | to runCommand(params)
508 | -- params : record -- the processed option and argument values, ready to use
509 | -- your code goes here...
510 | end runCommand
511 |
512 | The _version
property should contain a text value representing the script's current version, typically written as "majorUpdate.minorUpdate.bugFix"
. (Version numbers written this way are easily compared using a considering punctuation and numeric strings ... end considering
block.) The version number is automatically written to standard output when the shell script is executed with the -v
(--version
) flag.
The _summary
property contains a one-line summary that appears at the top of the help text generated when the -h
(--help
) option is given, which the _description
property can contain any additional information to append to the help text.
The _optionDefinitions
and _argumentDefinitions
properties describe any options and/or arguments that can be passed to the command, and are used both to convert the values passed by the shell to their AppleScript equivalents (text, numbers, booleans, etc) and to generate the corresponding help text when the -h
(--help
) option is used.
The run
handler requires no modification, as all it does is receive the list of raw arguments supplied by the shell command, converts it into a record of ready-to-use AppleScript objects, then deals with it automatically if a -h
(--help
) option or the -v
(--version
) option was given or else passes it to the runCommand
handler for your own code to process normally.
itunes-remote
The following AppleScript-based shell script allows basic control of the iTunes application from Terminal:
524 | 525 |#!/usr/bin/osascript
526 |
527 | use script "File"
528 | use script "Text"
529 |
530 | property _version : "1.0.0"
531 | property _summary : "Control iTunes from the command line."
532 | property _description : "Examples of use:
533 |
534 | $ itunes-remote -v
535 | 1.0.0
536 |
537 | $ itunes-remote --add 'some file.mp3' --add 'another file.mp3'
538 |
539 | $ itunes-remote play
540 |
541 | $ itunes-remote -i
542 | >> skip
543 | >> pause
544 | >> info
545 | Not playing.
546 | >> exit
547 | $
548 |
549 | Interactive mode recognizes the same actions as non-interactive mode
550 | (“play”, “pause”, etc.) plus “exit” to end the interactive session."
551 |
552 | property _optionDefinitions : {¬
553 | {propertyName:"filesToAdd", shortName:"a", longName:"add", ¬
554 | valueType:alias, isList:true, defaultValue:{}, ¬
555 | valueDescription:"Import a media file into iTunes."}, ¬
556 | {propertyName:"isInteractive", shortName:"i", valueType:boolean, ¬
557 | valueDescription:"Run in interactive mode."}}
558 |
559 | property _argumentDefinitions : {¬
560 | {propertyName:"actionName", ¬
561 | valueType:{"play", "pause", "skip", "info"}, defaultValue:"info", ¬
562 | valuePlaceholder:"ACTION", valueDescription:"The action to perform."}}
563 |
564 | on run argv
565 | set parameterRecord to parse command line arguments argv ¬
566 | options _optionDefinitions arguments _argumentDefinitions
567 | if parameterRecord's help then -- output help text
568 | return format command line help ¬
569 | summary _summary description _description ¬
570 | options _optionDefinitions arguments _argumentDefinitions
571 | else if parameterRecord's |version| then -- output version text
572 | return _version
573 | else -- perform the command and output its result, if any
574 | return runCommand(parameterRecord)
575 | end if
576 | end run
577 |
578 | --
579 |
580 | to runCommand(params)
581 | if (length of filesToAdd of params) > 0 then -- process any -a options
582 | tell application "iTunes" to add (filesToAdd of params)
583 | end if
584 | doItunesAction(actionName of params) -- process the given/default argument
585 | if isInteractive of params then -- was the -i option given?
586 | repeat -- run the interactive loop
587 | set actionName to read from standard input with user interaction
588 | if actionName is "exit" then exit repeat
589 | doItunesAction(actionName)
590 | end repeat
591 | end if
592 | end runCommand
593 |
594 | to doItunesAction(actionName)
595 | if actionName is "play" then
596 | tell application "iTunes" to play
597 | else if actionName is "pause" then
598 | tell application "iTunes" to pause
599 | else if actionName is "skip" then
600 | tell application "iTunes" to next track
601 | else if actionName is "info" then
602 | tell application "iTunes"
603 | if player state is playing then
604 | set trackInfo to {name, artist, time} of current track
605 | write to standard output ¬
606 | (format text "Now playing “\\1” by \\2 (\\3)." using trackInfo)
607 | else
608 | write to standard output "Not playing."
609 | end if
610 | end tell
611 | else
612 | write to standard output "Unknown action."
613 | end if
614 | end doItunesAction
615 |
616 | To use this script, save it as an uncompiled text file named itunes-remote
, make it executable (chmod +x itunes-remote
), and place it into a directory on the shell's search path (e.g. /usr/local/bin
.
before item
or after item
parameter should be given. For example:
17 |
18 | insert into list {1, 2, 3} value 8 before item 2 → {1, 8, 2, 3}
19 |
20 | insert into list {1, 2, 3} value 8 after item -3 → {1, 8, 2, 3}
21 |
22 | insert into list {1, 2, 3} value {7, 8, 9} after item 2 → {1, 2, {7, 8, 9}, 3}
23 |
24 | insert into list {1, 2, 3} value {7, 8, 9} after item 2 with concatenation → {1, 2, 7, 8, 9, 3}
25 |
26 | If neither before item
nor after item
parameters are given, the insert into list
automatically appends the new value(s) to the end of the list:
insert into list {1, 2, 3} value 4 → {1, 2, 3, 4}
29 |
30 | The before item
or after item
parameter must be a valid index, with the following special exceptions:
To make the after item
parameter insert the new value(s) before the first item in the list, use after item 0
or after item -N
where N = the number of items in list + 1:
insert into list {1, 2, 3} value 8 after item 0 → {8, 1, 2, 3}
37 | insert into list {1, 2, 3} value 8 after item -4 → {8, 1, 2, 3}
38 | To make the before item
parameter insert the new value(s) after the last item in the list, use before item 0
or after item N
where N = the number of items in list + 1:
insert into list {1, 2, 3} value 8 before item 0 → {1, 2, 3, 8}
44 | insert into list {1, 2, 3} value 8 before item 4 → {1, 2, 3, 8}
45 | Any other out-of-range index will result in error -1728.
49 | ]]> 50 |item
parameter:
63 |
64 | delete from list {1, 2, 3, 4, 5} item 1 → {2, 3, 4, 5}
65 |
66 | delete from list {1, 2, 3, 4, 5} item -2 → {1, 2, 3, 5}
67 |
68 | (Tip: You can also use get rest of theList
to remove the first item from a list.)
To delete a range of items from the list, use the from item
and/or to item
parameters:
delete from list {1, 2, 3, 4, 5} from item 2 to item 4 → {1, 5}
73 |
74 | delete from list {1, 2, 3, 4, 5} to item 3 → {4, 5}
75 |
76 | If no index is specified, the last item in the list is automatically removed:
77 | 78 |delete from list {1, 2, 3, 4, 5} → {1, 2, 3, 4}
79 |
80 | If the start index is greater than the end index, the list is copied but no items are removed:
81 | 82 |delete from list {1, 2, 3, 4, 5} from item 4 to item 2 → {1, 2, 3, 4, 5}
83 |
84 | If the list is empty or an index is out of range, error -1728 will occur.
85 | ]]> 86 |using
parameter's script object must implement a handler named mapItem
that takes a value as its input and returns a new value as its output. The following example demonstrates how to map a list of numbers to create a new list where each item is the square of its original value:
97 |
98 | script SquareNumbers
99 | to mapItem(aValue)
100 | return aValue ^ 2
101 | end mapItem
102 | end script
103 |
104 | map list {1, 2, 3, 4, 5} using SquareNumbers → {1, 4, 9, 16, 25}
105 | ]]>
106 | using
parameter's script object must implement a handler named reduceItem
that takes two values as its input and returns a new value as its output. The following example demonstrates how to reduce a list of numbers to the sum of all its items:
117 |
118 | script SumNumbers
119 | to reduceItem(partialResult, aValue)
120 | return partialResult + aValue
121 | end reduceItem
122 | end script
123 |
124 | reduce list {5, 1, 9, 3} using SumNumbers → 18
125 | ]]>
126 | remove duplicates from list {"A", "b", "c", B", "E", "b"} → {"A", "b", "c", "E"}
138 |
139 | Be aware that this command uses AppleScript’s standard is in
operator to check if each item has previously appeared in the list. When working with lists of text, for example, you may want to wrap the remove duplicates from list
command in an appropriate considering
/ignoring
block to ensure predictable behavior whenever that code is executed. For example, wrapping the previous command in a considering case
block alters the way in which text items are compared:
considering case
142 | remove duplicates from list {"A", "b", "c", B", "E", "b"} → {"A", "b", "c", "B", "E"}
143 | end considering
144 |
145 | (Similarly, when working with lists of real numbers, be aware that two fractional numbers that appear identical can sometimes compare as false
due to the limited accuracy of that data type. For example, 0.7 * 0.7 ≠ 0.49
(!) due to tiny imprecisions in the CPU’s floating point math calculations.)
get items i thru j of theList
, except that it returns an empty list if the start index is greater than the end index, and doesn’t throw an error if an index is out of range. (Index 0 still raises error -1728 though as it is not a valid AppleScript list index.) For example:
159 |
160 | slice list {"a", "b", "c", "d", "e"} from item 4 → {"d", "e"}
161 |
162 | slice list {"a", "b", "c", "d", "e"} from item 2 to item -2 → {"b", "c", "d"}
163 |
164 | slice list {"a", "b", "c", "d", "e"} from item 3 to item 3 → {"c"}
165 |
166 | slice list {"a", "b", "c", "d", "e"} to item 2 → {"a", "b"}
167 |
168 | slice list {"a", "b", "c", "d", "e"} from item 10 to item 15 → {}
169 |
170 | slice list {"a", "b", "c", "d", "e"} from item 4 to item 3 → {}
171 | ]]>
172 | transpose list
command treats a list of lists as a 2D matrix, rearranging it so that rows become columns and columns become rows.
188 |
189 | transpose list {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}} → {{1, 4, 7}, {2, 5, 8}, {3, 6, 9}}
190 |
191 | This command is particularly useful when getting large amounts of data from scriptable applications, as it is much quicker to ask the application to get property of every element
than to get every element, then iterate over the returned list of references asking for each element’s properties one at a time. For example, to obtain name, album, and artist information for every track in iTunes, then rearrange it into an easier-to-use form:
tell application "iTunes"
194 | tell every track
195 | set allNames to its name
196 | set allAlbums to its album
197 | set allArtists to its artist
198 | end tell
199 | end tell
200 | set trackInfo to {allNames, allAlbum, allArtists}
201 | -- trackInfo is a list of form {{name, name, ...}, {album, album, ...}, {artist, artist, ...}}
202 |
203 | set trackInfo to transpose list trackInfo
204 | -- trackInfo is now a list of form {{name, album, artist}, {name, album, artist}, ...}
205 | ]]>
206 | set orderedList to {}
225 | repeat with i from 1 to 100
226 | set end of orderedList to i
227 | end repeat
228 |
229 | set unorderedList to unsort list orderedList
230 | ]]>
231 | using
parameter's script object must implement a handler named filterItem
that takes a value as its input and returns true
or false
indicating whether or not the item should appear in the output list. The following example demonstrates how to filter a list of numbers to obtain a new list containing only the items that are greater than 18:
247 |
248 | script IsOverEighteen
249 | to filterItem(aValue)
250 | return aValue > 18
251 | end filterItem
252 | end script
253 |
254 | filter list {12, 23, 17, 22, 14} using IsOverEighteen → {23, 22}
255 | ]]>
256 | find in list {"A", "b", "c", B", "E", "b"} value "b" → {2, 4, 6}
271 |
272 | find in list {"A", "b", "c", B", "E", "b"} value "b" returning first occurrence → {2}
273 |
274 | find in list {"A", "b", "c", B", "E", "b"} value "b" returning first occurrence → {2}
275 |
276 | The value
parameter uses AppleScript’s standard is equal to
operator to compare each list item to the specified value, adding the item’s index to the result list if true. As with remove duplicates from list
, remember that AppleScript’s is equal to
operation is subject to additional rules and restrictions when comparing certain types. For example, wrapping the previous command in a considering case
block alters the way in which text items are compared:
considering case
279 | find in list {"A", "b", "c", B", "E", "b"} value "b" → {2, 6}
280 | end considering
281 |
282 | For more complex tests than simple comparison, the using
parameter can be used to supply a custom matching handler. Like the filter list
command’s using
parameter, this takes a script object containing a filterItem
handler that takes the item to check as its sole parameter and returns true
or false
to indicate a match or non-match. For example:
script IsOverEighteen
285 | to filterItem(aValue)
286 | return aValue > 18
287 | end filterItem
288 | end script
289 |
290 | find in list {12, 23, 17, 22, 14} using IsOverEighteen returning last occurrence → {4}
291 | ]]>
292 | sort list
command sorts a list of numbers, text, or dates and returns a new list of values in ascending order; for example:
314 |
315 | sort list {2, 7, 4, 1, 9, 4} → {1, 2, 4, 4, 7, 9}
316 |
317 | sort list {"Mary White", "Bob Green", "Sue Black"} → {"Bob Green", "Mary White", "Sue Black"}
318 |
319 | The sort list
command’s sorting behavior can be customized by supplying a ‘sort comparator’ script object as its using
parameter. A sort comparator must define the following handlers:
makeKey(anItem)
Given an item from the list to be sorted, this handler should convert it into a form suitable for use in the object’s compareKeys
handler, and return the result. Called once for each item in the list.
compareKeys(key1, key2)
Given any two keys previously generated by the makeKey
handler above, compare them against each other as appropriate and return a negative, zero, or positive number that indicates the order in which they should appear. The corresponding items in the original list are then reordered the same way.
The List library includes commands for constructing a variety of basic sort comparator objects, or you can define your own as needed. For example, to sort a list of playing card values, aces (“A”) low:
342 | 343 |use script "List"
344 | use script "Number"
345 |
346 | script PlayingCardComparator
347 |
348 | property _rank : {"A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"}
349 |
350 | to makeKey(anItem)
351 | -- return a number indicating the given card’s rank
352 | return find in list _rank value (anItem as text) returning first occurrence
353 | end makeKey
354 |
355 | to compareKeys(key1, key2)
356 | -- use the Number library’s ‘cmp’ command to compare any two card rank numbers
357 | return cmp {key1, key2} -- returns -1, 0, or +1 to indicate ordering
358 | end compareKeys
359 | end script
360 |
361 |
362 | sort list {"3", "Q", "10", "K", "2", "4", "J", "A"} using PlayingCardComparator
363 |
364 | → {"A", "2", "3", "4", "10", "J", "Q", "K"}
365 | ]]>
366 | When creating a default comparator in your own code, avoid reusing it across multiple sorts, as once it is used to sort a list of one type (e.g. list of text), it cannot be used to sort a list of another (e.g. list of number).
380 | ]]> 381 |sort list {7.4, -25, 0.03, 912, 0, -0.01} using (number comparator)
402 | → {-25, -0.01, 0, 0.03, 7.4, 912}
403 |
404 | When given a list of numerical text values, the number comparator coerces each item to a number before comparing them, ensuring that the resulting list is ordered numerically rather than by character order: 405 | 406 |
sort list {"22", "5", "17", "-6", "-9"} -- sort by comparing characters
407 | → {"-6", "-9", "17", "22", "5"}
408 |
409 | sort list {"22", "5", "17", "-6", "-9"} using (number comparator) -- sort by comparing numeric values
410 | → {"-9", "-6", "5", "17", "22"}
411 |
412 | Be aware that when given a list of decimal text values to sort, e.g. {"3.14", "-1.2", "0.09", ...}
, AppleScript coerces each text item to a number according to the current user’s localization settings. For example, a US-localized machine expects numerical text to use periods as decimal separators, e.g. "3.14"
, whereas a German-localized machine uses commas, e.g. "3,14"
, so will fail if given "3.14"
instead. If you need precise control of text-to-number conversions, use the Number library’s parse number
command, for example, to sort a list of German-style numerical text:
use script "List"
415 | use script "Number"
416 |
417 | to makeNumericalTextComparator(decimalSeparator)
418 | script NumericalTextComparator
419 | property parent : number comparator
420 |
421 | to makeKey(anItem)
422 | if anItem's class is text then
423 | -- use Number library to parse real numbers consistently
424 | return parse number anItem using ¬
425 | {basicFormat:decimal format, decimalSeparator:decimalSeparator}
426 | else
427 | return continue makeKey(anItem)
428 | end if
429 | end makeKey
430 | end script
431 | end makeNumericalTextComparator
432 |
433 |
434 | sort list {"3,14", "99", "4,2", "-0,01"} using my makeNumericalTextComparator(",")
435 | → {"-0,01", "3,14", "4,2", "99"}
436 | ]]>
437 | text
. If any item cannot be coerced to text, the `sort list` command will report a coercion error (-1700). This value is only used to determine sorting order; it will not appear in the final list.The optional for
parameter can customize text comparison behavior as follows:
exact ordering
– Case, diacriticals, hyphens, punctuation, and white space are all considered, while numeric strings are ignored (i.e. matched character for character).case insensitive ordering
– Case and numeric strings are ignored, while all other attributes are considered.current ordering
– Text is sorted using whatever considering/ignoring settings are currently applied when sort list
is called. (By default, AppleScript ignores case and numeric strings, and considers diacriticals, hyphens, punctuation, and white space. To alter these settings – or to guarantee predictable sorting at all times – wrap your sort list
command in the appropriate considering
and/or ignoring
block.)For example:
459 | 460 |sort list {"Fu", "Foo", "FOO", "foo", "fOO", "fu", "FU"} ¬
461 | using (text comparator for exact ordering)
462 | → {"foo", "fOO", "Foo", "FOO", "fu", "Fu", "FU"}
463 |
464 | (Be aware that text values are ordered intelligently according to standard Unicode rules; thus text items that are identical except for case are grouped together, with lowercase characters coming first.)
465 | 466 |Be aware that when AppleScript coerces numbers and dates to text, the results can vary according to the current user’s localization settings; coercing lists to text likewise produces different results depending on AppleScript’s current text item delimiters. This may affect sorting order when sorting lists of mixed types, e.g. sort list {"3.1.4", 3.2} using text comparator
will return {3.2, "3.1.4"} on systems that use commas as decimal separators, as 3.2
coerces to "3,2"
, and comma characters are ordered before periods. If this is a concern, define a custom comparator object whose makeKey
handler converts non-text values to text in a consistent format, for example:
script MixedTextAndNumberComparator
469 | property parent : text comparator
470 |
471 | to makeKey(anItem)
472 | if anItem's class is real then
473 | -- use Number library to format real numbers consistently
474 | return format number anItem using decimal format
475 | else
476 | return continue makeKey(anItem)
477 | end if
478 | end makeKey
479 | end script
480 | ]]>
481 | sort list {{3}, {1, 1, 5}, {}, {2, 1}, {2}, {1, 3}, {1, 2}} ¬
505 | using (list comparator for number comparator)
506 | → {{}, {1, 1, 5}, {1, 2}, {1, 3}, {2}, {2, 1}, {3}}
507 |
508 | It is also possible to sort a list of lists of mixed types by specifying exactly which items to sort on and how each one should be compared. For example, consider the following list where each sublist is of form {name, married, age}
:
set myList to { ¬
511 | {"Bob", false, 33}, ¬
512 | {"Jane", true, 25}, ¬
513 | {"Andi", true, 33}}
514 |
515 | To sort this first by comparing item 3 of each sublist numerically, then by comparing item 1 of each sublist as text:
516 |
517 | sort list myList using (list comparator for { ¬
518 | {itemIndex:3, itemComparator: number comparator}, ¬
519 | {itemIndex:1, itemComparator: text comparator}})
520 |
521 | The resulting list of lists is ordered first by ages then by names:
522 | 523 |→ {{"Jane", true, 25}, {"Andi", true, 33}, {"Bob", false, 33}}
524 |
525 | A ListComparator
object can also be used to sort a list of records by extending its standard makeKey
handler to first extract the property values on which to sort into a list:
script AgeAndNameComparator
528 | (* Comparator object for sorting a list of records of form:
529 |
530 | {{name:TEXT, ..., age:NUMBER}, ...},
531 |
532 | Records are ordered first by age, then by name.
533 |
534 | Any other properties are ignored.
535 | *)
536 |
537 | -- Create a list comparator that orders on a 2-item key list of form {NUMBER, TEXT}...
538 | property parent : list comparator for {number comparator, text comparator}
539 |
540 | to makeKey(aRecord)
541 | -- ... then extend its existing `makeKey` handler to extract each record’s
542 | -- age and name properties into the corresponding {NUMBER, TEXT} key list:
543 | return continue makeKey({age, name} of aRecord)
544 | end makeKey
545 |
546 | end script
547 |
548 |
549 | set myList to { ¬
550 | {name:"Bob", married:false, age:33}, ¬
551 | {name:"Jane", married:true, age:25}, ¬
552 | {name:"Andi", married:true, age:33}}
553 |
554 | sort list myList using AgeAndNameComparator
555 |
556 | → {{name:"Jane", married:true, age:25},
557 | {name:"Andi", married:true, age:33},
558 | {name:"Bob", married:false, age:33}}
559 | ]]>
560 | { itemIndex : integer,
568 | itemComparator: script }
569 | ]]>
570 | sort list {2, 7, 4, 1, 9, 4} using (reverse comparator for number comparator)
582 | → {9, 7, 4, 4, 2, 1}
583 | ]]>
584 | format number
command converts an integer/real number to numeric text in canonical format. Unlike coercing an integer or real value to text
, which formats the text according to the current user’s localization settings, format number
always uses the same numerical syntax as the AppleScript language, unless additional formatting and/or locale settings are explicitly given.
22 |
23 | For example, on a US-localized system, coercing 3.14
to text produces "3.14"
:
3.14 as text → "3.14" (localized conversion)
26 |
27 | format number 3.14 → "3.14" (canonical conversion)
28 |
29 | On a German system, however, the same number-to-text coercion uses a comma instead of a period as the decimal separator:
30 | 31 |3.14 as text → "3,14" (localized conversion)
32 |
33 | format number 3.14 → "3.14" (canonical conversion)
34 |
35 | Using the format number
command instead of coercing the number to text ensures a consistent result, no matter where the script is run.
For information on formatting numbers using custom formats, see the Customizing Number Formats section below.
38 | ]]> 39 |parse number
command converts numeric text in canonical format to an integer/real number. Unlike coercing a text value to number
, which parses the text according to the current user’s localization settings, format number
always uses the same numerical syntax as the AppleScript language, unless additional formatting and/or locale settings are explicitly given.
56 |
57 | For example, on a US-localized system, coercing "3.14"
to a number produces 3.14
:
"3.14" as number → 3.14 (localized conversion)
60 |
61 | parse number "3.14" → 3.14 (canonical conversion)
62 |
63 | On a German system, however, the same text-to-number coercion requires the decimal separator to be a comma, not a period:
64 | 65 |"3.14" as number → Error: Can’t make "3.14" into type number.
66 |
67 | "3,14" as number → 3.14 (localized conversion)
68 |
69 | parse number "3.14" → 3.14 (canonical conversion)
70 |
71 | Using the parse number
command instead of coercing the number to text ensures a consistent result, no matter where the script is run.
For information on parsing numbers using custom formats, see the Customizing Number Formats section below.
74 | ]]> 75 |{ class : number format record,
83 | basicFormat : constant or text,
84 | minimumDecimalPlaces : integer,
85 | maximumDecimalPlaces : integer,
86 | minimumSignificantDigits : integer,
87 | maximumSignificantDigits : integer,
88 | decimalSeparator : text,
89 | groupingSeparator : text,
90 | roundingBehavior : constant }
91 |
92 | The basicFormat
property is required and should be one of the format constants accepted by the format number
and parse number
commands (canonical number format
, integer format
, etc) or format text. All other properties are optional. The roundingBehavior
property accepts the same rounding constants used in the round number
command (rounding up
, rounding down
, etc).
format hex 526 width 4 with prefix → "0x020E"
126 |
127 | format hex {1, 11, 6, 13, 0, 8} width 2 → "010B060D0008"
128 |
129 | If the ‘width’ parameter is given, the hexadecimal value will be padded to that number of digits (not including sign or prefix) unless the number is too large to represent within that number of hexadecimal digits, in which case an error is raised instead:
130 | 131 |format hex 526 width 2 with prefix
132 | → error: Number is too large to represent as 2-digit hexadecimal text
133 | (not between -256 and 255).
134 | ]]>
135 | parse hex "-0x020E" → -526
152 |
153 | parse hex "010B060D0008" width 2 → {1, 11, 6, 13, 0, 8}
154 |
155 | The parse text
will return a non-fractional real number if a hexadecimal value is too large to represent using AppleScript’s native 30-bit integer type, normally raising an error if it can’t be accurately represented as a real number either (although this may be overridden if some loss of precision is acceptable):
parse hex "0xFFFF" -- 2^16-1
158 | → 65535
159 |
160 | parse hex "0xFFFFFFFF" -- 2^32-1
161 | →- 4.294967295E+9
162 |
163 | parse hex "0xFFFFFFFFFFFFFFFF" -- 2^64-1
164 | → error: Hexadecimal text is too large to convert to number without losing precision.
165 |
166 | parse hex "0xFFFFFFFFFFFFFFFF" with precision loss
167 | → 1.84467440737096E+19 (approximate only)
168 |
169 | ]]>
170 | abs 3.1 → 3.1
198 |
199 | abs -3.1 → 3.1
200 |
201 | ]]>
202 | =
’ operator, which compares two numbers for exact equality, the cmp
command allows a small margin of error (±1.0e-9), so ignores any slight differences due to the limited precision of real (a.k.a. floating point) numbers. For example:
214 |
215 | (0.7 * 0.7) = 0.49 → false (probably not what you wanted!)
216 |
217 | cmp {(0.7 * 0.7), 0.49} → 0 (i.e. the numbers are "equal")
218 |
219 | ]]>
220 | max {5, 1, 3, -8, 5, 4, -2} → 5
232 | ]]>
233 | min {5, 1, 3, -8, 5, 4, -2} → -8
245 | ]]>
246 | round number 4.145 to places 2 → 4.14
259 |
260 | round number -8.51 to places 1 → -8.6 by rounding away from zero
261 |
262 | round number 7949.0 to places -2 → 7900
263 | ]]>
264 | parse number
and format number
commands allow custom formats to be specified via their optional using
parameters. These parameters accept either a text value containing special character-based number format patterns, a predefined constant representing a commonly used format, or a number format record
containing a predefined format constant plus additional customizations.
361 |
362 | For a full explanation of pattern syntax, see Part 3: Numbers of Unicode Technical Standard #35. Recognized patterns are listed below for convenience.
363 | 364 | 365 |Value/Position | Format | Pattern | Notes |
---|---|---|---|
number | any digit | 0 | |
number | digit; zero is omitted | # | |
number | digit with rounding | 1–9 | |
number | significant digit | @ | [1] |
number | decimal separator | . | |
number | grouping separator | , | |
number | minus sign | - | |
number | 379 |separates mantissa and exponent in scientific notation | 380 |E | 381 |[2] | 382 |
exponent | 385 |prefix positive exponents with localized plus sign | 386 |+ | 387 |[2] | 388 |
prefix or suffix | 391 |multiply by 100 and show as percentage | 392 |% | 393 ||
prefix or suffix | 396 |multiply by 1000 and show as per mille | 397 |‰ | 398 |(\u2030) | 399 |
prefix or suffix | 402 |currency symbol | 403 |¤ | 404 |(\u00A4) [3] | 405 |
prefix or suffix | 408 |quote-escape special characters | 409 |' | 410 |e.g. “'#'”, “o''clock” | 411 |
prefix or suffix boundary | 414 |precedes a pad character | 415 |* | 416 |e.g. “* ###0” | 417 |
subpattern boundary | 420 |separates positive and negative subpatterns | 421 |; | 422 |
[1] For example, “@@” will display a number to 2-significant digits, e.g. “12345” → “12000”. When given, the “0” and “.” patterns are not allowed.
428 | 429 |[2] The “E” and “+” characters do need not be quote-escaped when they appear in a prefix or suffix.
430 | 431 |[3] Use “¤” for currency symbol, “¤¤” for international currency symbol, or “¤¤¤” for the long form of the decimal symbol. If given, monetary decimal and grouping separators (if available) are used instead of the numeric ones.
432 | 433 | 434 | [TO DO: include summary of template text syntax plus examples] 435 | ]]> 436 |The following script uses a dictionary to store and retrieve RGB color values by name:
20 | 21 |use script "Objects"
22 |
23 | -- create a new dictionary
24 | set obj to dictionary collection
25 |
26 | -- add some key-value pairs
27 | obj's addItem("red", {255, 0, 0})
28 | obj's addItem("yellow", {255, 255, 0})
29 | obj's addItem("green", {0, 255, 0})
30 | obj's addItem("blue", {0, 0, 255})
31 |
32 | -- get the number of items in the collection
33 | log obj's countItems() --> 4
34 |
35 | -- get the value that is currently stored under the key "green"
36 | log obj's getItem("green") --> {0, 255, 0}
37 |
38 | -- attempting to get or remove a non-existent item raises an error
39 | log obj's getItem("banana") -- Error -1728: "Item not found."
40 |
41 |
42 | DictionaryCollection
script objects recognize the following commands:
countItems()
Count the number of key-value pairs in the collection
50 |containsItem(theKey)
Does the collection contain a key-value pair with the specified key?
57 |addItem(theKey, theValue)
Add a key-value pair to the collection
65 |removeItem(theKey)
Remove a key-value pair from the collection
73 |getItem(theKey)
Get the value for the given key from the collection
81 |addDictionary(theDictionary)
Add another DictionaryObject's key-value pairs to the collection
90 |addKeysAndValues(keyValueList)
Add a list of key-value pairs to the collection
97 |{{KEY, VALUE},...}
getKeysAndValues()
Get a list of key-value pairs from the collection
104 |{{KEY, VALUE},...}
getKeys()
Get a list of keys from the collection
111 |getValues()
Get a list of values from the collection
118 |deleteAllItems()
125 | Delete all key-value pairs from the collection
copyObject()
Returns a shallow copy of the object
130 |objectDescription()
Returns a brief description of the object
137 |Numeric keys are compared for numeric equality (e.g. 1
and 1.0
are the same). Other types of keys, including text-based keys, are compared exactly (e.g. "foo"
and "Foo"
are different).
use script "Objects"
165 |
166 | set obj to queue collection
167 |
168 | obj's addItem("a")
169 | obj's addItem("b")
170 | obj's addItem("c")
171 | log obj's removeItem() --> "a"
172 |
173 | obj's addItem("d")
174 | log obj's removeItem() --> "b"
175 | log obj's removeItem() --> "c"
176 | log obj's removeItem() --> "d"
177 |
178 | log obj's countItems() --> 0
179 | log obj's removeItem() -- Error -1728: "Queue is empty."
180 |
181 |
182 | QueueCollection
script objects recognize the following commands:
countItems()
Count the number of items in the collection
190 |addItem(theValue)
Push an item onto the back of the queue
197 |removeItem()
Pull an item from the front of the queue
204 |getItem()
Return the item at the front of the queue without removing it
211 |deleteAllItems()
217 | Delete all items from the collection
copyObject()
Returns a shallow copy of the object
222 |objectDescription()
Returns a brief description of the object
229 |use script "Objects"
252 |
253 | set obj to stack collection
254 |
255 | obj's addItem("a")
256 | obj's addItem("b")
257 | obj's addItem("c")
258 | log obj's removeItem() --> "c"
259 |
260 | obj's addItem("d")
261 | log obj's removeItem() --> "d"
262 | log obj's removeItem() --> "b"
263 | log obj's removeItem() --> "a"
264 |
265 | log obj's countItems() --> 0
266 | log obj's removeItem() -- Error -1728: "Stack is empty."
267 |
268 |
269 | StackCollection
script objects recognize the following commands:
countItems()
Count the number of items in the collection
277 |addItem(theValue)
Push an item onto the top of the stack
284 |removeItem()
Pop an item from the top of the stack
291 |getItem()
Return the item at the top of the stack without removing it
298 |deleteAllItems()
304 | Delete all items from the collection
copyObject()
Returns a shallow copy of the object
309 |objectDescription()
Returns a brief description of the object
316 |The following script uses a timer object to measure the time it takes to create 1000 random numbers:
343 | 344 |use script "Objects"
345 | use scripting additions
346 |
347 | set theTimer to timer object
348 |
349 | set n to 1000
350 | theTimer's startTimer()
351 | repeat n times
352 | random number from -99999 to 99999
353 | end repeat
354 | theTimer's stopTimer()
355 |
356 | set millisecs to theTimer's totalTime() * 1000 div 1
357 | display alert "Created " & n & " random numbers in " & millisecs & "ms."
358 |
359 |
360 | TimerObject
script objects recognize the following commands:
timerName()
the timer name, if given
368 |startTimer()
[re]start the timer (this does nothing if the timer is currently running)
375 |(timer object)'s startTimer()
stopTimer()
stop the timer (this does nothing if the timer is already stopped)
382 |elapsedTime()
returns the number of seconds since running timer was last started
389 |totalTime()
returns the total number of seconds the timer has been running
396 |copyObject()
Returns a shallow copy of the object
404 |objectDescription()
Returns a brief description of the object
411 |to test_uppercaseText
23 | assert test result for (uppercase text "foøbår") is "FOØBÅR"
24 | end test_uppercaseText
25 |
26 | If the note
parameter is given, its text is included in the generate test report. For example, an assert test result
command could use this parameter to describe the type of bugs that particular test is designed to detect.
to test_uppercaseText
43 | assert test error for {a:"foo"} is {errorNumber:-1703} ¬
44 | note "Check unsuitable value types are rejected."
45 | end test_uppercaseText
46 |
47 | to call_uppercaseText(usingParam)
48 | uppercase text usingParam
49 | end call_uppercaseText
50 |
51 | ]]>
52 | { errorNumber : integer,
60 | errorMessage : text,
61 | fromValue : any,
62 | toType : type,
63 | partialResult : any }
64 |
65 | The errorNumber property is required; other properties are recommended where appropriate.
66 | ]]> 67 |assert test result
is the preferred choice for checking whether or not a result value is correct, assert test passed
and assert test failed
may occasionally be used in situations that require complex checking logic beyond the capabilities of a standard ‘check’ object.
78 | ]]>
79 | 0.7 * 0.7
returning a real (a.k.a. floating point) number that is very nearly but not precisely 0.49:
104 |
105 | assert test result for (0.7 * 0.7) is 0.49 -- fails even though it shouldn’t
106 |
107 | To allow for the inherent imprecision of real numbers, pass a NumericEqualityCheck
object as the assert test result
command’s using
parameter:
assert test result for (0.7 * 0.7) is 0.49 using (numeric equality check) -- passes
110 | ]]>
111 | assert test result
command’s is
parameter should be a two-number list of form {MIN, MAX}
. For example:
121 |
122 | assert test result for aResult
is {0.49, 0.51} -- result must be 0.5±0.01
123 | ]]>
124 | assert test error
command’s is
property is checked for exact equality with the corresponding property in the actual error. Occasionally, it may be necessary to customize the way in which a particular error property is compared, in which case the an alternate check object may be specfied to check that property.
140 |
141 | To illustrate, the following assert test error
command makes two checks: 1. that the error number is exactly 3000, and 2. that the error value is 0.49 allowing for any slight differences due to real numbers’ limited precision:
assert test error is {errorNumber:3000 fromValue:0.49} ¬
144 | using {fromValue:numeric equality check}
145 |
146 | Thus, if the error value was produced by the calculation 0.7 * 0.7
– which returns a real number that is very nearly but not precisely 0.49 – this test will pass as expected.
on run
169 | run unit tests (path to me)
170 | end run
171 |
172 | ]]>
173 | A unit test script is saved as a compiled TESTNAME.unittest.scpt
file and has the following basic structure:
use script "TestTools"
191 |
192 | use script "FILENAME" -- the script being tested
193 |
194 | script suite_SUITE -- a group of related tests
195 |
196 | to test_HANDLER()
197 | -- assertions to test a command
198 | end
199 |
200 | to call_HANDLER(PARAM)
201 | -- the command being tested (error checking only)
202 | end
203 |
204 | end
205 |
206 | A test script must contain one or more suite_NAME
script objects, each of which contains one or more test_NAME
handlers. As a rule of thumb, each test handler should thoroughly test a single command, asserting it returns the correct result/error responses for one or more sets of good/bad inputs.
Each suite object’s name must have a suite_
prefix; each test handler’s name must have a test_
prefix. The rest of each name can be anything: short descriptions of the purpose of the suite and the command being tested are strongly recommended as these will appear in the generated test report. When the osatest
test runner loads the script, it searches for these suite_
and test_
prefixes and will invoke each test handler automatically; the script should not call test handlers itself.
To minimize the risk of unintended interactions between tests, prior to calling each test handler, osatest
creates a new component instance and loads a fresh copy of the test script into it. This ensures text item delimiters, loaded libraries, etc. are not shared between tests. If the code being tested (or the tests themselves) also interact with external data – shared files, scriptable applications, etc. — it is the test script’s responsibility to ensure that these external data sources are put into a known state prior to running each test.
The goal of unit testing is to confirm that each handler in your own script returns the correct results and error responses for a representative range of normal and abnormal inputs, covering both general and corner cases. (For example, in addition to testing what happens when correct values are given, it's essential to check that failures are dealt with appropriately too: for instance, what if a handler expects a list of values to process but receives an empty list instead, or needs to access a data file but the file is missing or has the wrong permissions?)
217 | 218 |Testing for a correct result is done using TestTools’ assert test result
command, while testing for an expected error is done using its assert test error
command. Each test_NAME
handler can contain any number of assert...
commands. For example, to check that a squareNumber
handler’s actual result exactly matches the expected return value:
to test_squareNumber()
221 | assert test result for (squareNumber(4)) is (16)
222 | assert test result for (squareNumber(0)) is (0)
223 | assert test result for (squareNumber(1.0)) is (1.0)
224 | assert test result for (squareNumber(-12345)) is (152399025)
225 | ...
226 | end test_squareNumber
227 |
228 | (Note that you may have to parenthesize the code being tested to avoid AppleScript misreading the assert command’s is
parameter as an is
operator.)
Similarly, testing for an expected error is done using the assert test error
command:
to test_squareRoot()
233 | assert test result for (squareRoot(64)) is (8.0)
234 | assert test result for (squareRoot(0)) is (0.0)
235 | ...
236 | assert test error for ({a:1}) is ¬
237 | {errorNumber:-1700, errorMessage:"Bad parameter: not a number."}
238 | assert test error for (-1) is ¬
239 | {errorNumber:-1703, errorMessage:"Bad parameter: negative numbers not allowed."}
240 | end test_squareRoot
241 |
242 | This time, however, the assert test error
command’s for
parameter should contain only data needed to perform the test. Since the goal is to generate a deliberate error, the command being tested must be wrapped in a separate call_NAME
handler which assert test error
then calls automatically:
to call_squareRoot(aValue)
245 | squareRoot(aValue)
246 | end call_squareRoot
247 |
248 | If the assert test error
command includes a for
parameter, the value is passed to the call_HANDLER
handler as its sole parameter, otherwise the handler receives no parameters. To pass multiple parameters, use a record or list:
assert test error for {param1, param2, param3} is ...
251 |
252 | to call_foo({param1, param2, param3})
253 | ...
254 | end call_foo
255 |
256 | TestTools catches the error thrown in the call_NAME
handler and compares it against the assert test error
command’s is
record parameter. This expected error information
record must contain an errorNumber
property, plus any additional properties to be checked – errorMessage
, fromValue
, toType
and/or partialResult
— corresponding to an AppleScript error’s number
, message, from
, to
and/or partial result
attributes.
If all assertions within a test handler succeed, TestTools will report that test as passed once the handler returns:
259 | 260 |ModifyText's normalizeText: OK (performed 34 assertions)261 | 262 |
However, if the code being tested returns an incorrect result or throws an unexpected or incorrect error, TestTools will report that test as failed, along with a description of the problem:
263 | 264 |JoinSplitDate's joinDate: FAILED on assertion 32 in ‘assert test result’ received incorrect result: 265 | • actual result: date "Thursday, 30 April 1959 at 18:11:01" 266 | • expected result: date "Thursday, 30 April 1959 at 14:11:01"267 | 268 |
Once the cause of the problem is identified in the code being tested (assuming it’s not a bug in the test script itself), it can be corrected and the test script re-run — and the cycle repeated until all tests finally pass.
269 | 270 | 271 |While test handlers can include any code, not just assert commands, it’s a good idea to keep such code to a minimum so that any bugs in the test script are not confused for defects in code being tested. For example, rather than writing code to connect to a live database to obtain test data, it is often simpler to hardcode a mockup object of known values within the test script, and to use that. In addition, rather than preparing all this test data within the test handler itself, it is possible to use a separate set-up handler to prepare this test data and store it in a property of the suite object, prior to the test handler being called. The next section describes the additional support handlers that may be included in suite objects.
272 | 273 | 274 |In addition to test_NAME
and associated call_NAME
handlers, a suite object may also contain zero or more of the following optional handlers:
configure_setUp()
Called by TestTools before it calls a test_NAME
handler in a suite. Use this handler to perform any pre-test preparation of common test data; for example, to create AppleScript objects, temporary files, database connections, etc. that will be used by each test in that suite. If a set-up handler throws an error (e.g. if a permissions error prevents a temp file being written) the rest of the test is aborted, including any post-test cleanup, in which case the set-up handler should perform its own cleanup of any partially created test resources.
configure_tearDown()
Called by TestTools after it calls a test_NAME
handler in a suite. Use this handler to perform any post-test cleanup; for example, to delete temporary files, close database connections, etc. This handler is called regardless of whether the test handler succeeds or fails, so should be designed to clean up any partially consumed or unused test data left by a failed test, as well as to clean up after a successful test.
configure_skipTests()
Called before running each suite. Use this handler to temporarily disable selected test handlers (e.g. if a test should only run on newer OS versions), or even the entire suite:
291 | 292 |missing value
, all tests are run.{test_NAME: text or missing value, ...}
, the named handlers will either be skipped or run according to the property value. Test handlers not named in the record will run as normal.For example, to skip the test handler named test_Foo
if the OS version is older than 10.9 while allowing all other tests in the suite to run normally:
use scripting additions
301 |
302 | to configure_skipTests()
303 | considering numeric strings
304 | return {test_Foo:(system info)'s system version < "10.9"}
305 | end considering
306 | end configure_doTest
307 |
308 |
309 | configure_doTest(testObject)
By default, TestTools calls each test_NAME
directly, as soon as the configure_setUp()
(if any) returns. Some configuration options – for example, considering/ignoring
blocks – cannot be applied by set-up and tear-down handlers, but must instead be applied directly to the test_NAME
call itself. To do this, add a configure_doTest
handler to the suite that takes a script object as its sole parameter. The script object contains a doTest
handler, which takes no parameter and returns no result. The configure_doTest
handler should perform any additional configuration, then send the script object a doTest
command to run the test itself. For example, to run each test in a suite using custom considering/ignoring options:
to configure_doTest(testObject)
314 | considering case, diacriticals and numeric strings ¬
315 | but ignoring hyphens, punctuation and white space
316 | testObject's doTest() -- perform the test
317 | end considering
318 | end configure_doTest
319 |
320 | When the doTest
command returns, the configure_doTest
handler may perform any related clean up and/or additional assertion checks. If the doTest
command raises an error, the configure_doTest
may temporaily handle it if necessary, but must re-throw that original error when done:
to configure_doTest(testObject)
323 | -- do normal preparation here
324 | try
325 | testObject's doTest()
326 | on error eText number eNumber from eValue to eType
327 | -- do essential clean up here...
328 | error eText number eNumber from eValue to eType -- ...then rethrow original error
329 | end try
330 | -- do normal clean up here
331 | end configure_doTest
332 |
333 |
334 |
335 | By default, assert test result
compares values exactly, including text case and object type (e.g. if 3
is expected then 3.0
will be reported as incorrect). This can be sometimes be too precise; for example, when comparing two real numbers, a small amount of leeway may be necessary to allow for tiny rounding errors that are an unavoidable consequence of the limited precision of floating-point CPU math. For instance, 0.7 * 0.7
returns a real number that displays as 0.49 but is actually fractionally less:
to squareNumber(n)
340 | return n ^ 2
341 | end squareNumber
342 |
343 | squareNumber(0.7) -- i.e. '0.7 * 0.7'
344 | result = 0.49 → false(!)
345 |
346 | assert test result for (squareNumber(0.7)) is (0.49) -- assertion fails!
347 |
348 | To modify the way in which assert test result
compares expected vs actual results, pass a custom ‘result comparator’ script object as its using
parameter. Several custom comparators are included as standard in TestTools. For example, to allow for a small amount of imprecision when comparing reals, use a NumericEqualityCheck
object:
assert test result for (squareNumber(0.7)) is (0.49) using (numeric equality check) -- passes
351 |
352 | TestTools also provides basic assert test passed
and assert test failed
commands for use in situations where even greater flexibility is needed, but for most testing tasks the standard assert commands are simple, reliable, and produce the most detailed test results.
split url "http://jsmith@example.com/some/path?x=1&y=2"
19 |
20 | → { urlScheme:"http",
21 | networkLocation:"jsmith@example.com",
22 | resourcePath:"/some/path",
23 | parameterString:"",
24 | queryString:"x=1&y=2", fragmentIdentifier:"" }
25 | ]]>
26 | join url { urlScheme:"http", ¬
40 | networkLocation:"jsmith@example.com", ¬
41 | resourcePath:"/some/path", ¬
42 | queryString:"x=1&y=2" }
43 |
44 | → "http://jsmith@example.com/some/path?x=1&y=2"
45 | ]]>
46 | { urlScheme : text,
54 | networkLocation : text or network location record,
55 | resourcePath : text,
56 | parameterString : text,
57 | queryString : text,
58 | fragmentIdentifier : text }
59 | ]]>
60 | { userName : text,
68 | userPassword : text,
69 | hostName : text,
70 | portNumber : text }
71 | ]]>
72 | A-Za-z0-9_.-
to UTF8-based %XX
escape codes. For example, to escape a resource path, preserving path separators:
83 |
84 | encode URL characters "/foo bar/ø.txt" preserving "/" → "/foo%20bar/%C3%B8.txt"
85 | ]]>
86 | %XX
escape codes will be automatically replaced with the corresponding characters. If an escape sequence does not represent a valid UTF8 codepoint, an error is raised instead. For example:
96 |
97 | decode URL characters "/foo%20bar/%C3%B8.txt" → "/foo bar/ø.txt"
98 | ]]>
99 | %XX
escape codes will be automatically replaced with the corresponding characters. For example:
109 |
110 | split URL query string "age=23&name=Jan+M%C3%BCller"
111 | → {{"age", "23"}, {"name", "Jan Müller"}}
112 | ]]>
113 | A-Za-z0-9_.-
will be automatically replaced with UTF8-based %XX
escape codes. For example:
122 |
123 | join URL query string {{"age", "23"}, {"name", "Jan Müller"}}
124 | → "age=23&name=Jan+M%C3%BCller"
125 | ]]>
126 | Content-Type
header):
181 |
182 | send HTTP request to "http://apple.com/index.html"
183 |
184 | To download the document to file without any automatic decoding:
185 | 186 |set filePath to "/path/to/file.html"
187 |
188 | set httpResponse to send HTTP request to "http://apple.com/index.html" returning data
189 |
190 | -- on success, the returned record's `responseBody` property contains an NSData object
191 | if httpResponse's statusCode div 100 = 2 then -- 2xx status indicates success (e.g. 200)
192 | set {didSucceed, asocError} to httpResponse's responseBody's writeToFile:filePath ¬
193 | options:(current application's NSDataWritingAtomic) |error|:(specifier)
194 | -- confirm file was written successfully
195 | if not didSucceed then error (asocError's localizedDescription()) number (asocError's code())
196 | else
197 | -- deal with other status codes (e.g. 3xx, 4xx, 5xx) here...
198 | end if
199 |
200 | HTTP headers can be added to the HTTP request via the send HTTP request
command's headers
parameter. For example, the following list of HTTP header records tells the web server that your script wants either HTML (preferred) or plain text, encoded as UTF8 (preferred) or Latin1, in response:
{{headerName:"Accept", headerValue:"text/html, text/plain;q=0.5"}, 203 | {headerName:"Accept-Charset", headerValue:"utf-8, iso-8859-1;q=0.5"}}204 | 205 |
By default, send HTTP request
sends a GET
request to the web server. Other HTTP methods (POST
, PUT
, DELETE
etc.) can be specified via the method
parameter. For example, to send a POST
request with UTF8-encoded JSON data as the request body and ask for UTF8-encoded JSON data in response:
set requestData to {someKey:101, anotherKey:"hello"}
208 |
209 | set httpResponse to send HTTP request to "http://example.org/some-path" ¬
210 | method "POST" ¬
211 | headers {{headerName:"Accept", headerValue:"application/json"}, ¬
212 | {headerName:"Accept-Charset", headerValue:"utf-8"}, ¬
213 | {headerName:"Content-Type", headerValue:"application/json;charset=utf-8"}} ¬
214 | body (encode JSON requestData)
215 |
216 | if (httpResponse's statusCode) div 100 ≠ 2 then -- non-2xx status = failure (e.g. 403)
217 | error (HTTP status name httpResponse's statusCode) number httpResponse's statusCode
218 | end if
219 |
220 | set responseData to decode JSON httpResponse's responseBody
221 |
222 | ]]>
223 | { headerName:text,
231 | headerValue:text }
232 |
233 | For example:
234 | 235 |{headerName:"Content-Type", headerValue:"application/json; charset=utf-8"}
236 | ]]>
237 | { statusCode:integer,
245 | responseHeaders:list of HTTP header record,
246 | responseBody:text, NSData, or missing value }
247 | ]]>
248 | HTTP status name 404 → “not found”
260 | ]]>
261 |