├── unittests
├── web.unittest.scpt
├── date.unittest.scpt
├── file.unittest.scpt
├── list.unittest.scpt
├── number.unittest.scpt
├── text.unittest.scpt
└── objects.unittest.scpt
├── Date.scptd
└── Contents
│ ├── Resources
│ ├── Scripts
│ │ └── main.scpt
│ ├── description.rtfd
│ │ └── TXT.rtf
│ └── Date.sdef
│ └── Info.plist
├── File.scptd
└── Contents
│ ├── Resources
│ ├── Scripts
│ │ └── main.scpt
│ ├── description.rtfd
│ │ └── TXT.rtf
│ └── File.sdef
│ └── Info.plist
├── List.scptd
└── Contents
│ ├── Resources
│ ├── Scripts
│ │ └── main.scpt
│ ├── description.rtfd
│ │ └── TXT.rtf
│ └── List.sdef
│ └── Info.plist
├── TestTools.scptd
└── Contents
│ ├── Resources
│ ├── bin
│ │ └── osatest
│ ├── Scripts
│ │ └── main.scpt
│ ├── description.rtfd
│ │ └── TXT.rtf
│ ├── Script Libraries
│ │ └── TestSupport.scpt
│ └── TestTools.sdef
│ └── Info.plist
├── Text.scptd
└── Contents
│ ├── Resources
│ ├── Scripts
│ │ └── main.scpt
│ └── description.rtfd
│ │ └── TXT.rtf
│ └── Info.plist
├── Web.scptd
└── Contents
│ ├── Resources
│ ├── Scripts
│ │ └── main.scpt
│ ├── description.rtfd
│ │ └── TXT.rtf
│ └── Web.sdef
│ └── Info.plist
├── Number.scptd
└── Contents
│ ├── Resources
│ ├── Scripts
│ │ └── main.scpt
│ ├── description.rtfd
│ │ └── TXT.rtf
│ └── Number.sdef
│ └── Info.plist
├── Objects.scptd
└── Contents
│ ├── Resources
│ ├── Scripts
│ │ └── main.scpt
│ ├── description.rtfd
│ │ └── TXT.rtf
│ └── Objects.sdef
│ └── Info.plist
├── TypeSupport.scptd
└── Contents
│ ├── Resources
│ ├── Scripts
│ │ └── main.scpt
│ ├── description.rtfd
│ │ └── TXT.rtf
│ └── TypeSupport.sdef
│ └── Info.plist
├── bin
└── asdiff
└── TODOs.txt
/unittests/web.unittest.scpt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattneub/applescript-stdlib/HEAD/unittests/web.unittest.scpt
--------------------------------------------------------------------------------
/unittests/date.unittest.scpt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattneub/applescript-stdlib/HEAD/unittests/date.unittest.scpt
--------------------------------------------------------------------------------
/unittests/file.unittest.scpt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattneub/applescript-stdlib/HEAD/unittests/file.unittest.scpt
--------------------------------------------------------------------------------
/unittests/list.unittest.scpt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattneub/applescript-stdlib/HEAD/unittests/list.unittest.scpt
--------------------------------------------------------------------------------
/unittests/number.unittest.scpt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattneub/applescript-stdlib/HEAD/unittests/number.unittest.scpt
--------------------------------------------------------------------------------
/unittests/text.unittest.scpt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattneub/applescript-stdlib/HEAD/unittests/text.unittest.scpt
--------------------------------------------------------------------------------
/unittests/objects.unittest.scpt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattneub/applescript-stdlib/HEAD/unittests/objects.unittest.scpt
--------------------------------------------------------------------------------
/Date.scptd/Contents/Resources/Scripts/main.scpt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattneub/applescript-stdlib/HEAD/Date.scptd/Contents/Resources/Scripts/main.scpt
--------------------------------------------------------------------------------
/File.scptd/Contents/Resources/Scripts/main.scpt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattneub/applescript-stdlib/HEAD/File.scptd/Contents/Resources/Scripts/main.scpt
--------------------------------------------------------------------------------
/List.scptd/Contents/Resources/Scripts/main.scpt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattneub/applescript-stdlib/HEAD/List.scptd/Contents/Resources/Scripts/main.scpt
--------------------------------------------------------------------------------
/TestTools.scptd/Contents/Resources/bin/osatest:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattneub/applescript-stdlib/HEAD/TestTools.scptd/Contents/Resources/bin/osatest
--------------------------------------------------------------------------------
/Text.scptd/Contents/Resources/Scripts/main.scpt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattneub/applescript-stdlib/HEAD/Text.scptd/Contents/Resources/Scripts/main.scpt
--------------------------------------------------------------------------------
/Web.scptd/Contents/Resources/Scripts/main.scpt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattneub/applescript-stdlib/HEAD/Web.scptd/Contents/Resources/Scripts/main.scpt
--------------------------------------------------------------------------------
/Number.scptd/Contents/Resources/Scripts/main.scpt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattneub/applescript-stdlib/HEAD/Number.scptd/Contents/Resources/Scripts/main.scpt
--------------------------------------------------------------------------------
/Objects.scptd/Contents/Resources/Scripts/main.scpt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattneub/applescript-stdlib/HEAD/Objects.scptd/Contents/Resources/Scripts/main.scpt
--------------------------------------------------------------------------------
/Date.scptd/Contents/Resources/description.rtfd/TXT.rtf:
--------------------------------------------------------------------------------
1 | {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460
2 | {\fonttbl}
3 | {\colortbl;\red255\green255\blue255;}
4 | }
--------------------------------------------------------------------------------
/File.scptd/Contents/Resources/description.rtfd/TXT.rtf:
--------------------------------------------------------------------------------
1 | {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460
2 | {\fonttbl}
3 | {\colortbl;\red255\green255\blue255;}
4 | }
--------------------------------------------------------------------------------
/List.scptd/Contents/Resources/description.rtfd/TXT.rtf:
--------------------------------------------------------------------------------
1 | {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460
2 | {\fonttbl}
3 | {\colortbl;\red255\green255\blue255;}
4 | }
--------------------------------------------------------------------------------
/TestTools.scptd/Contents/Resources/Scripts/main.scpt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattneub/applescript-stdlib/HEAD/TestTools.scptd/Contents/Resources/Scripts/main.scpt
--------------------------------------------------------------------------------
/Text.scptd/Contents/Resources/description.rtfd/TXT.rtf:
--------------------------------------------------------------------------------
1 | {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460
2 | {\fonttbl}
3 | {\colortbl;\red255\green255\blue255;}
4 | }
--------------------------------------------------------------------------------
/Web.scptd/Contents/Resources/description.rtfd/TXT.rtf:
--------------------------------------------------------------------------------
1 | {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460
2 | {\fonttbl}
3 | {\colortbl;\red255\green255\blue255;}
4 | }
--------------------------------------------------------------------------------
/Number.scptd/Contents/Resources/description.rtfd/TXT.rtf:
--------------------------------------------------------------------------------
1 | {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460
2 | {\fonttbl}
3 | {\colortbl;\red255\green255\blue255;}
4 | }
--------------------------------------------------------------------------------
/Objects.scptd/Contents/Resources/description.rtfd/TXT.rtf:
--------------------------------------------------------------------------------
1 | {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460
2 | {\fonttbl}
3 | {\colortbl;\red255\green255\blue255;}
4 | }
--------------------------------------------------------------------------------
/TestTools.scptd/Contents/Resources/description.rtfd/TXT.rtf:
--------------------------------------------------------------------------------
1 | {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf410
2 | {\fonttbl}
3 | {\colortbl;\red255\green255\blue255;}
4 | }
--------------------------------------------------------------------------------
/TypeSupport.scptd/Contents/Resources/Scripts/main.scpt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattneub/applescript-stdlib/HEAD/TypeSupport.scptd/Contents/Resources/Scripts/main.scpt
--------------------------------------------------------------------------------
/TypeSupport.scptd/Contents/Resources/description.rtfd/TXT.rtf:
--------------------------------------------------------------------------------
1 | {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460
2 | {\fonttbl}
3 | {\colortbl;\red255\green255\blue255;}
4 | }
--------------------------------------------------------------------------------
/TestTools.scptd/Contents/Resources/Script Libraries/TestSupport.scpt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattneub/applescript-stdlib/HEAD/TestTools.scptd/Contents/Resources/Script Libraries/TestSupport.scpt
--------------------------------------------------------------------------------
/List.scptd/Contents/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 | specifier (a.k.a. reference). If the given object is a record or script object, returns record or script respectively, ignoring its custom class property if it has one. Otherwise, returns the value of the object's class property.
native type for {class:document, name:"Untitled"} → record
20 | native type for (document 1 of application "TextEdit") → specifier
21 | ]]>
22 |
23 |
24 |
25 | theValue's class is TYPE, which returns the wrong result for some types of objects (e.g. record, script, specifier, application). For example:
34 |
35 | check type for {class:list} is list → false (it's a record)
36 | check type for (document 1 of application "TextEdit") is list → false (it's a specifier)
37 |
38 | check type for {class:document, name:"ReadMe.txt"} is record → true
39 | check type for (document 1 of application "TextEdit") is specifier → true
40 | check type for 3.14 is {number, text, date} → true
41 |
42 | (Tip: To check for an AppleScript-ObjC specifier in particular, use «class ocid».)
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 | 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 |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 | basedOnFormat : 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 basedOnFormat 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 |format date command converts a date object to canonical ISO8601-formatted text, for example:
24 |
25 | 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 |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.0If 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.0If 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.
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.scptfile 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.
insert into list {1, 2, 3} value 4 → {1, 2, 3, 4}
19 |
20 | insert into list {1, 2, 3} value 8 before item 2 → {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 | delete from list {1, 2, 3, 4, 5} → {1, 2, 3, 4}
39 |
40 | delete from list {1, 2, 3, 4, 5} item 2 → {1, 3, 4, 5}
41 |
42 | delete from list {1, 2, 3, 4, 5} from item 2 to item 4 → {1, 5}
43 |
44 | delete from list {1, 2, 3, 4, 5} to item 3 → {4, 5}
45 |
46 | Returns a copy of the full list if the start index is greater than the end index. Throws error -1728 if an index is out of range.
47 | ]]> 48 |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:
59 |
60 | script SquareNumbers
61 | to mapItem(aValue)
62 | return aValue ^ 2
63 | end mapItem
64 | end script
65 |
66 | map list {1, 2, 3, 4, 5} using SquareNumbers → {1, 4, 9, 16, 25}
67 | ]]>
68 | 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:
79 |
80 | script SumNumbers
81 | to reduceItem(partialResult, aValue)
82 | return partialResult + aValue
83 | end reduceItem
84 | end script
85 |
86 | reduce list {5, 1, 9, 3} using SumNumbers → 18
87 | ]]>
88 | remove duplicates from list {"A", "b", "c", B", "E", "b"} → {"A", "b", "c", "E"}
100 |
101 | 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
104 | remove duplicates from list {"A", "b", "c", B", "E", "b"} → {"A", "b", "c", "B", "E"}
105 | end considering
106 |
107 | (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 is still forbidden though, as AppleScript lists are 1-indexed.) For example:
121 |
122 | slice list {"a", "b", "c", "d", "e"} from item 4 → {"d", "e"}
123 |
124 | slice list {"a", "b", "c", "d", "e"} from item 2 to item -2 → {"b", "c", "d"}
125 |
126 | slice list {"a", "b", "c", "d", "e"} from item 3 to item 3 → {"c"}
127 |
128 | slice list {"a", "b", "c", "d", "e"} to item 2 → {"a", "b"}
129 |
130 | slice list {"a", "b", "c", "d", "e"} from item 10 to item 15 → {}
131 |
132 | slice list {"a", "b", "c", "d", "e"} from item 4 to item 3 → {}
133 |
134 |
135 | ]]>
136 | transpose list command treats a list of lists as a 2D matrix, rearranging it so that rows become columns and columns become rows.
152 |
153 | transpose list {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}} → {{1, 4, 7}, {2, 5, 8}, {3, 6, 9}}
154 |
155 | 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"
158 | tell every track
159 | set allNames to its name
160 | set allAlbums to its album
161 | set allArtists to its artist
162 | end tell
163 | end tell
164 | set trackInfo to {allNames, allAlbum, allArtists}
165 | -- trackInfo is a list of form {{name, name, ...}, {album, album, ...}, {artist, artist, ...}}
166 |
167 | set trackInfo to transpose list trackInfo
168 | -- trackInfo is now a list of form {{name, album, artist}, {name, album, artist}, ...}
169 | ]]>
170 | 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:
193 |
194 | script IsOverEighteen
195 | to filterItem(aValue)
196 | return aValue > 18
197 | end filterItem
198 | end script
199 |
200 | filter list {12, 23, 17, 22, 14} using IsOverEighteen → {23, 22}
201 |
202 | ]]>
203 | find in list {"A", "b", "c", B", "E", "b"} value "b" → {2, 4, 6}
218 |
219 | find in list {"A", "b", "c", B", "E", "b"} value "b" returning first occurrence → {2}
220 |
221 | find in list {"A", "b", "c", B", "E", "b"} value "b" returning first occurrence → {2}
222 |
223 | 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
226 | find in list {"A", "b", "c", B", "E", "b"} value "b" → {2, 6}
227 | end considering
228 |
229 | 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
232 | to filterItem(aValue)
233 | return aValue > 18
234 | end filterItem
235 | end script
236 |
237 | find in list {12, 23, 17, 22, 14} using IsOverEighteen returning last occurrence → {4}
238 | ]]>
239 | sort list {2, 7, 4, 1} → {1, 2, 4, 7}
261 | ]]>
262 | use script "List"
273 |
274 | set orderedList to {}
275 | repeat with i from 1 to 100
276 | set end of orderedList to i
277 | end repeat
278 |
279 | set unorderedList to unsort list orderedList
280 | ]]>
281 | 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).
292 | ]]> 293 |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 match – Case, diacriticals, hyphens, punctuation, and white space are all considered, while numeric strings are ignored. (Considering numeric strings treats e.g. "7" and "007" as equal; ignoring numeric strings matches them character for character.)case sensitivity – Only diacriticals are ignored, while all other settings, including case and numeric strings are considered. (Be aware that AppleScript orders characters according to Unicode, not ASCII, rules, so "a" comes before "A", and "A" comes before "b", and so on.)case insensitivity – Case and diacriticals are ignored, while all other settings, including numeric strings are considered.current considerations – 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:
336 | 337 |sort list {"Fu", "Foo", "FOO", "foo", "fOO", "fu", "FU"} using (text comparator for case sensitivity)
338 | → {"foo", "fOO", "Foo", "FOO", "fu", "Fu", "FU"}
339 |
340 | (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.)
341 | 342 |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
345 | property parent : text comparator
346 |
347 | to makeKey(anItem)
348 | if anItem's class is real then
349 | -- use Number library to format real numbers consistently
350 | return format number anItem using decimal format
351 | else
352 | return continue makeKey(anItem)
353 | end if
354 | end makeKey
355 | end script
356 |
357 | ]]>
358 | sort list {{3}, {1, 1, 5}, {}, {2, 1}, {2}, {1, 3}, {1, 2}} ¬
383 | using (list comparator for number comparator)
384 | → {{}, {1, 1, 5}, {1, 2}, {1, 3}, {2}, {2, 1}, {3}}
385 |
386 | 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, female, age}:
set myList to { ¬
389 | {"Bob", false, 33}, ¬
390 | {"Jane", true, 25}, ¬
391 | {"Andi", true, 33}}
392 |
393 | To sort this first by comparing item 3 of each sublist numerically, then by comparing item 1 of each sublist as text:
394 |
395 | sort list myList using (list comparator for { ¬
396 | {itemIndex:3, itemComparator: number comparator}, ¬
397 | {itemIndex:1, itemComparator: text comparator}})
398 |
399 | The resulting list of lists is ordered first by ages then by names:
400 | 401 |→ {{"Jane", true, 25}, {"Andi", true, 33}, {"Bob", false, 33}}
402 |
403 | [TO DO: describe how to extend list comparator to sort a list of records:]
404 | 405 |script PersonRecordComparator
406 |
407 | property parent : list comparator for { ¬
408 | {itemIndex:3, itemComparator:number comparator}, ¬
409 | {itemIndex:1, itemComparator:text comparator}}
410 |
411 | to makeKey(aRecord)
412 | set keyList to {name, female, age} of aRecord
413 | return continue makeKey(keyList)
414 | end makeKey
415 |
416 | end script
417 |
418 |
419 | set myList to { ¬
420 | {name:"Bob", female:false, age:33}, ¬
421 | {name:"Jane", female:true, age:25}, ¬
422 | {name:"Andi", female:true, age:33}}
423 |
424 | sort list myList using PersonRecordComparator
425 |
426 | → {{name:"Jane", female:true, age:25},
427 | {name:"Andi", female:true, age:33},
428 | {name:"Bob", female:false, age:33}}
429 |
430 | ]]>
431 | { itemIndex : integer,
439 | itemComparator: script }
440 | ]]>
441 | 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,
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.
propertyNameshortNamelongNamevalueTypeboolean, 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).isListdefaultValuemissing 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)valuePlaceholderformat command line help. If omitted, a default placeholder is chosen according to valueType (INT, NUM, TEXT, FILE). Used by format command line help only.valueDescriptionformat 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,
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.
.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.
474 |
475 | For convenience, here is a standard template for creating AppleScript-based shell scripts:
476 | 477 |#!/usr/bin/osascript
478 |
479 | use script "File"
480 |
481 | property _version : "xx.xx.xx" -- the script’s version (e.g. "1.2.0")
482 | property _summary : "a one-line summary of the command’s purpose"
483 | property _description : "detailed instructions for use, examples, etc."
484 |
485 | property _optionDefinitions : { ... }
486 |
487 | property _argumentDefinitions : { ... }
488 |
489 | on run argv
490 | set parameterRecord to parse command line arguments argv ¬
491 | options _optionDefinitions arguments _argumentDefinitions
492 | if parameterRecord's help then -- output help text
493 | return format command line help ¬
494 | summary _summary description _description ¬
495 | options _optionDefinitions arguments _argumentDefinitions
496 | else if parameterRecord's |version| then -- output version text
497 | return _version
498 | else -- perform the command and output its result, if any
499 | return doCommand(parameterRecord)
500 | end if
501 | end run
502 |
503 | to doCommand(params)
504 | -- your code goes here...
505 | end doCommand
506 |
507 | 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. [[ TO DO: simple example of each type of record, taken from complete example (see TODO below) ]]
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 doCommand handler for your own code to process normally.