rb-scpt 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/bin/rb-scpt-1.0.1.gem +0 -0
  3. data/extconf.rb +12 -12
  4. data/rb-scpt.gemspec +10 -10
  5. data/sample/AB_export_vcard.rb +16 -16
  6. data/sample/AB_list_people_with_emails.rb +4 -4
  7. data/sample/Add_iCal_event.rb +12 -12
  8. data/sample/Create_daily_iCal_todos.rb +28 -28
  9. data/sample/Export_Address_Book_phone_numbers.rb +52 -52
  10. data/sample/Hello_world.rb +10 -10
  11. data/sample/List_iTunes_playlist_names.rb +3 -3
  12. data/sample/Make_Mail_message.rb +24 -24
  13. data/sample/Open_file_in_TextEdit.rb +5 -5
  14. data/sample/Organize_Mail_messages.rb +46 -46
  15. data/sample/Print_folder_tree.rb +5 -5
  16. data/sample/Select_all_HTML_files.rb +6 -6
  17. data/sample/Set_iChat_status.rb +12 -12
  18. data/sample/Simple_Finder_GUI_Scripting.rb +6 -6
  19. data/sample/Stagger_Finder_windows.rb +9 -9
  20. data/sample/TextEdit_demo.rb +71 -71
  21. data/sample/iTunes_top40_to_html.rb +28 -30
  22. data/src/SendThreadSafe.c +293 -293
  23. data/src/SendThreadSafe.h +108 -108
  24. data/src/lib/_aem/aemreference.rb +997 -998
  25. data/src/lib/_aem/codecs.rb +609 -610
  26. data/src/lib/_aem/connect.rb +197 -197
  27. data/src/lib/_aem/encodingsupport.rb +67 -67
  28. data/src/lib/_aem/findapp.rb +75 -75
  29. data/src/lib/_aem/mactypes.rb +241 -242
  30. data/src/lib/_aem/send.rb +268 -268
  31. data/src/lib/_aem/typewrappers.rb +52 -52
  32. data/src/lib/_appscript/defaultterminology.rb +266 -266
  33. data/src/lib/_appscript/referencerenderer.rb +230 -233
  34. data/src/lib/_appscript/reservedkeywords.rb +106 -106
  35. data/src/lib/_appscript/safeobject.rb +125 -125
  36. data/src/lib/_appscript/terminology.rb +448 -449
  37. data/src/lib/aem.rb +238 -238
  38. data/src/lib/kae.rb +1487 -1487
  39. data/src/lib/osax.rb +647 -647
  40. data/src/lib/rb-scpt.rb +1065 -1065
  41. data/src/rbae.c +595 -595
  42. data/test/test_aemreference.rb +104 -107
  43. data/test/test_appscriptcommands.rb +131 -134
  44. data/test/test_appscriptreference.rb +96 -99
  45. data/test/test_codecs.rb +166 -168
  46. data/test/test_findapp.rb +13 -16
  47. data/test/test_mactypes.rb +70 -72
  48. data/test/test_osax.rb +46 -48
  49. data/test/testall.sh +4 -4
  50. metadata +8 -7
@@ -8,652 +8,652 @@ require "rb-scpt"
8
8
 
9
9
  module OSAX
10
10
 
11
-
12
- ######################################################################
13
- # PRIVATE
14
- ######################################################################
15
-
16
- require 'rexml/document'
17
- require "ae"
18
- require "kae"
19
- require "aem"
20
- require "_appscript/reservedkeywords" # names of all existing methods on ASReference::Application
21
-
22
- StandardAdditionsEnums = [
23
- ["stop", "\000\000\000\000"],
24
- ["note", "\000\000\000\001"],
25
- ["caution", "\000\000\000\002"]]
26
-
27
- UTF8ToMacRoman = {
28
- 0 => 0,
29
- 1 => 1,
30
- 2 => 2,
31
- 3 => 3,
32
- 4 => 4,
33
- 5 => 5,
34
- 6 => 6,
35
- 7 => 7,
36
- 8 => 8,
37
- 9 => 9,
38
- 10 => 10,
39
- 11 => 11,
40
- 12 => 12,
41
- 13 => 13,
42
- 14 => 14,
43
- 15 => 15,
44
- 16 => 16,
45
- 17 => 17,
46
- 18 => 18,
47
- 19 => 19,
48
- 20 => 20,
49
- 21 => 21,
50
- 22 => 22,
51
- 23 => 23,
52
- 24 => 24,
53
- 25 => 25,
54
- 26 => 26,
55
- 27 => 27,
56
- 28 => 28,
57
- 29 => 29,
58
- 30 => 30,
59
- 31 => 31,
60
- 32 => 32,
61
- 33 => 33,
62
- 34 => 34,
63
- 35 => 35,
64
- 36 => 36,
65
- 37 => 37,
66
- 38 => 38,
67
- 39 => 39,
68
- 40 => 40,
69
- 41 => 41,
70
- 42 => 42,
71
- 43 => 43,
72
- 44 => 44,
73
- 45 => 45,
74
- 46 => 46,
75
- 47 => 47,
76
- 48 => 48,
77
- 49 => 49,
78
- 50 => 50,
79
- 51 => 51,
80
- 52 => 52,
81
- 53 => 53,
82
- 54 => 54,
83
- 55 => 55,
84
- 56 => 56,
85
- 57 => 57,
86
- 58 => 58,
87
- 59 => 59,
88
- 60 => 60,
89
- 61 => 61,
90
- 62 => 62,
91
- 63 => 63,
92
- 64 => 64,
93
- 65 => 65,
94
- 66 => 66,
95
- 67 => 67,
96
- 68 => 68,
97
- 69 => 69,
98
- 70 => 70,
99
- 71 => 71,
100
- 72 => 72,
101
- 73 => 73,
102
- 74 => 74,
103
- 75 => 75,
104
- 76 => 76,
105
- 77 => 77,
106
- 78 => 78,
107
- 79 => 79,
108
- 80 => 80,
109
- 81 => 81,
110
- 82 => 82,
111
- 83 => 83,
112
- 84 => 84,
113
- 85 => 85,
114
- 86 => 86,
115
- 87 => 87,
116
- 88 => 88,
117
- 89 => 89,
118
- 90 => 90,
119
- 91 => 91,
120
- 92 => 92,
121
- 93 => 93,
122
- 94 => 94,
123
- 95 => 95,
124
- 96 => 96,
125
- 97 => 97,
126
- 98 => 98,
127
- 99 => 99,
128
- 100 => 100,
129
- 101 => 101,
130
- 102 => 102,
131
- 103 => 103,
132
- 104 => 104,
133
- 105 => 105,
134
- 106 => 106,
135
- 107 => 107,
136
- 108 => 108,
137
- 109 => 109,
138
- 110 => 110,
139
- 111 => 111,
140
- 112 => 112,
141
- 113 => 113,
142
- 114 => 114,
143
- 115 => 115,
144
- 116 => 116,
145
- 117 => 117,
146
- 118 => 118,
147
- 119 => 119,
148
- 120 => 120,
149
- 121 => 121,
150
- 122 => 122,
151
- 123 => 123,
152
- 124 => 124,
153
- 125 => 125,
154
- 126 => 126,
155
- 127 => 127,
156
- 196 => 128,
157
- 197 => 129,
158
- 199 => 130,
159
- 201 => 131,
160
- 209 => 132,
161
- 214 => 133,
162
- 220 => 134,
163
- 225 => 135,
164
- 224 => 136,
165
- 226 => 137,
166
- 228 => 138,
167
- 227 => 139,
168
- 229 => 140,
169
- 231 => 141,
170
- 233 => 142,
171
- 232 => 143,
172
- 234 => 144,
173
- 235 => 145,
174
- 237 => 146,
175
- 236 => 147,
176
- 238 => 148,
177
- 239 => 149,
178
- 241 => 150,
179
- 243 => 151,
180
- 242 => 152,
181
- 244 => 153,
182
- 246 => 154,
183
- 245 => 155,
184
- 250 => 156,
185
- 249 => 157,
186
- 251 => 158,
187
- 252 => 159,
188
- 8224 => 160,
189
- 176 => 161,
190
- 162 => 162,
191
- 163 => 163,
192
- 167 => 164,
193
- 8226 => 165,
194
- 182 => 166,
195
- 223 => 167,
196
- 174 => 168,
197
- 169 => 169,
198
- 8482 => 170,
199
- 180 => 171,
200
- 168 => 172,
201
- 8800 => 173,
202
- 198 => 174,
203
- 216 => 175,
204
- 8734 => 176,
205
- 177 => 177,
206
- 8804 => 178,
207
- 8805 => 179,
208
- 165 => 180,
209
- 181 => 181,
210
- 8706 => 182,
211
- 8721 => 183,
212
- 8719 => 184,
213
- 960 => 185,
214
- 8747 => 186,
215
- 170 => 187,
216
- 186 => 188,
217
- 937 => 189,
218
- 230 => 190,
219
- 248 => 191,
220
- 191 => 192,
221
- 161 => 193,
222
- 172 => 194,
223
- 8730 => 195,
224
- 402 => 196,
225
- 8776 => 197,
226
- 8710 => 198,
227
- 171 => 199,
228
- 187 => 200,
229
- 8230 => 201,
230
- 160 => 202,
231
- 192 => 203,
232
- 195 => 204,
233
- 213 => 205,
234
- 338 => 206,
235
- 339 => 207,
236
- 8211 => 208,
237
- 8212 => 209,
238
- 8220 => 210,
239
- 8221 => 211,
240
- 8216 => 212,
241
- 8217 => 213,
242
- 247 => 214,
243
- 9674 => 215,
244
- 255 => 216,
245
- 376 => 217,
246
- 8260 => 218,
247
- 8364 => 219,
248
- 8249 => 220,
249
- 8250 => 221,
250
- 64257 => 222,
251
- 64258 => 223,
252
- 8225 => 224,
253
- 183 => 225,
254
- 8218 => 226,
255
- 8222 => 227,
256
- 8240 => 228,
257
- 194 => 229,
258
- 202 => 230,
259
- 193 => 231,
260
- 203 => 232,
261
- 200 => 233,
262
- 205 => 234,
263
- 206 => 235,
264
- 207 => 236,
265
- 204 => 237,
266
- 211 => 238,
267
- 212 => 239,
268
- 63743 => 240,
269
- 210 => 241,
270
- 218 => 242,
271
- 219 => 243,
272
- 217 => 244,
273
- 305 => 245,
274
- 710 => 246,
275
- 732 => 247,
276
- 175 => 248,
277
- 728 => 249,
278
- 729 => 250,
279
- 730 => 251,
280
- 184 => 252,
281
- 733 => 253,
282
- 731 => 254,
283
- 711 => 255}
284
-
285
- class SdefParser
286
- @@_name_cache = {}
287
- LegalFirst = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_'
288
- LegalRest = LegalFirst + '0123456789'
289
- @@_reserved_keywords = {} # ersatz set
290
- ReservedKeywords.each { |name| @@_reserved_keywords[name] = nil }
291
-
292
- attr_reader :properties, :elements, :classes, :enumerators
293
-
294
- def commands
295
- return @commands.values
296
- end
297
-
298
- def initialize
299
- # terminology tables; order is significant where synonym definitions occur
300
- @commands = {}
301
- @properties = []
302
- @elements = []
303
- @classes = []
304
- @enumerators = []
305
- end
306
-
307
- def _name(s)
308
- # Read a MacRoman-encoded Pascal keyword string.
309
- if not @@_name_cache.has_key?(s)
310
- legal = LegalFirst
311
- res = ''
312
- s.split(//).each do |c|
313
- if legal[c]
314
- res += c
315
- else
316
- case c
317
- when ' ', '-', '/'
318
- res += '_'
319
- when '&'
320
- res += 'and'
321
- else
322
- if res == ''
323
- res = '_'
324
- end
325
- res += "0x#{c.unpack('HXh')}"
326
- end
327
- end
328
- legal = LegalRest
329
- end
330
- if res[0, 3] == 'AS_' or @@_reserved_keywords.has_key?(res) or res[0, 1] == '_'
331
- res += '_'
332
- end
333
- @@_name_cache[s] = res
334
- end
335
- return @@_name_cache[s]
336
- end
337
-
338
- def _code(s)
339
- return s.unpack('U*').collect do |c| # unpack UTF8-encoded byte string
340
- OSAX::UTF8ToMacRoman.fetch(c)
341
- end .pack('C*') # pack as MacRoman-encoded byte string (four- or eight-char code)
342
- end
343
-
344
- def _addnamecode(node, collection)
345
- name = _name(node.attributes['name'])
346
- code = _code(node.attributes['code'])
347
- if name != '' and code.size == 4 and not collection.include?([name, code])
348
- collection.push([name, code])
349
- end
350
- end
351
-
352
- def _addcommand(node)
353
- name = _name(node.attributes['name'])
354
- code = _code(node.attributes['code'])
355
- parameters = []
356
- # Note: overlapping command definitions (e.g. 'path to') should be processed as follows:
357
- # - If their names and codes are the same, only the last definition is used; other definitions are ignored and will not compile.
358
- # - If their names are the same but their codes are different, only the first definition is used; other definitions are ignored and will not compile.
359
- if name != '' and code.size == 8 and (not @commands.has_key?(name) or @commands[name][1] == code)
360
- @commands[name] = [name, code, parameters]
361
- node.each_element('parameter') do |pnode|
362
- _addnamecode(pnode, parameters)
363
- end
364
- end
365
- end
366
-
367
- def parse(xml)
368
- # Extract name-code mappings from an sdef.
369
- #
370
- # xml : String -- sdef data
371
- xml = REXML::Document.new(xml)
372
- xml.root.each_element('suite/*') do |node|
373
- begin
374
- if ['command', 'event'].include?(node.name)
375
- _addcommand(node)
376
- elsif ['class', 'record-type', 'value-type'].include?(node.name)
377
- _addnamecode(node, @classes)
378
- node.each_element('property') do |prop|
379
- _addnamecode(prop, @properties)
380
- end
381
- if node.name == 'class' # elements
382
- name = node.attributes['plural']
383
- if name == nil
384
- name = node.attributes['name']
385
- name = "#{name}s" if name != nil
386
- end
387
- name = _name(name)
388
- code = _code(node.attributes['code'])
389
- if name != '' and code.size == 4
390
- @elements.push([name, code])
391
- end
392
- end
393
- elsif node.name == 'enumeration'
394
- node.each_element('enumerator') do |enum|
395
- _addnamecode(enum, @enumerators)
396
- end
397
- end
398
- rescue # ignore problem definitions
399
- end
400
- end
401
- end
402
-
403
- def parse_file(path)
404
- # Extract name-code mappings from an sdef.
405
- #
406
- # path : String -- path to .sdef file
407
- parse(AE.copy_scripting_definition(path))
408
- end
409
-
410
- end
411
-
412
- #######
413
- # cache; stores osax paths and previously parsed terminology (if any) by osax name
414
-
415
- OSAXCache = {}
416
- OSAXNames = []
417
-
418
- #######
419
- # modified AppData class
420
-
421
- class OSAXData < Appscript::AppData
422
-
423
- def initialize(constructor, identifier, terms)
424
- super(AEM::Application, constructor, identifier, terms)
425
- end
426
-
427
- def connect
428
- super
429
- begin
430
- @target.event('ascrgdut').send(60 * 60) # make sure target application has loaded event handlers for all installed OSAXen
431
- rescue AEM::EventError => e
432
- if e.number != -1708 # ignore 'event not handled' error
433
- raise
434
- end
435
- end
436
- end
437
-
438
- end
439
-
440
- @_standard_additions = nil
441
-
442
- def OSAX._init_caches
443
- se = AEM::Application.by_path(FindApp.by_id('com.apple.systemevents'))
444
- ['flds', 'fldl', 'fldu'].each do |domain_code|
445
- osaxen = AEM.app.property(domain_code).property('$scr').elements('file').by_filter(
446
- AEM.its.property('asty').eq('osax').or(AEM.its.property('extn').eq('osax')))
447
- if se.event('coredoex', {'----' => osaxen.property('pnam')}).send # domain has ScriptingAdditions folder
448
- names = se.event('coregetd', {'----' => osaxen.property('pnam')}).send
449
- paths = se.event('coregetd', {'----' => osaxen.property('posx')}).send
450
- names.zip(paths).each do |name, path|
451
- name = name.sub(/(?i)\.osax$/, '') # remove name extension, if any
452
- OSAXNames.push(name)
453
- OSAXCache[name.downcase] = [path, nil]
454
- end
455
- end
456
- end
457
- OSAXNames.sort!.uniq!
458
- end
459
-
460
- ######################################################################
461
- # PUBLIC
462
- ######################################################################
463
-
464
- def OSAX.scripting_additions
465
- # list names of all currently installed scripting additions
466
- OSAX._init_caches if OSAXNames == []
467
- return OSAXNames.clone
468
- end
469
-
470
- def OSAX.osax(name=nil, app_name=nil)
471
- # Convenience method for creating a new ScriptingAddition instance.
472
- # name : String | nil -- scripting addition's name; nil = 'StandardAdditions'
473
- # app_name : String | nil -- target application's name/path, or nil for current application
474
- # Result : ScriptingAddition
475
- #
476
- # If both arguments are nil, a ScriptingAddition object for StandardAdditions is created
477
- # and returned. This object is cached for efficiency and returned in subsequent calls;
478
- # thus clients can conveniently write (e.g):
479
- #
480
- # osax.some_command
481
- # osax.another_command
482
- #
483
- # instead of:
484
- #
485
- # sa = osax
486
- # sa.some_command
487
- # sa.another_command
488
- #
489
- # without the additional overhead of creating a new ScriptingAddition object each time.
490
- #
491
- if name == nil and app_name == nil
492
- if @_standard_additions == nil
493
- @_standard_additions = ScriptingAddition.new('StandardAdditions')
494
- end
495
- addition = @_standard_additions
496
- else
497
- if name == nil
498
- name = 'StandardAdditions'
499
- end
500
- addition = ScriptingAddition.new(name)
501
- if app_name
502
- addition = addition.by_name(app_name)
503
- end
504
- end
505
- return addition
506
- end
507
-
508
- # allow methods to be included via 'include OSAX'
509
-
510
- def scripting_additions
511
- return OSAX.scripting_additions
512
- end
513
-
514
- def osax(*args)
515
- return OSAX.osax(*args)
516
- end
517
-
518
- #######
519
-
520
- class ScriptingAddition < Appscript::Reference
521
- # Represents a single scripting addition.
522
-
523
- def initialize(name, terms=nil)
524
- # name: string -- a scripting addition's name, e.g. "StandardAdditions";
525
- # basically its filename minus the '.osax' suffix
526
- #
527
- # terms : module or nil -- an optional terminology glue module,
528
- # as exported by Terminology.dump; if given, ScriptingAddition
529
- # will use this instead of retrieving the terminology dynamically
530
- #
531
- # Note that name is case-insensitive and an '.osax' suffix is ignored if given.
532
- @_osax_name = name
533
- if not terms
534
- osax_name = name.downcase.sub(/(?i)\.osax$/, '')
535
- OSAX._init_caches if OSAXCache == {}
536
- path, terminology_tables = OSAXCache[osax_name]
537
- if not path
538
- raise ArgumentError, "Scripting addition not found: #{name.inspect}"
539
- end
540
- if not terminology_tables
541
- sp = OSAX::SdefParser.new
542
- sp.parse_file(path)
543
- if osax_name == 'standardadditions'
544
- OSAX::StandardAdditionsEnums.each { |o| sp.enumerators.push(o)}
545
- end
546
- terminology_tables = Terminology.tables_for_parsed_sdef(sp)
547
- OSAXCache[osax_name][1] = terminology_tables
548
- end
549
- @_terms = terminology_tables
550
- terms = OSAXData.new(:current, nil, @_terms)
551
- elsif not terms.is_a?(OSAX::OSAXData) # assume it's a glue module
552
- terminology_tables = Terminology.tables_for_module(terms)
553
- @_terms = terminology_tables
554
- terms = OSAXData.new(:current, nil, @_terms)
555
- end
556
- super(terms, AEM.app)
557
- end
558
-
559
- def to_s
560
- return "#<OSAX::ScriptingAddition name=#{@_osax_name.inspect} target=#{@AS_app_data.target.inspect}>"
561
- end
562
-
563
- alias_method :inspect, :to_s
564
-
565
- ##
566
-
567
- def method_missing(name, *args)
568
- begin
569
- super
570
- rescue Appscript::CommandError => e
571
- if e.to_i == -1713 # 'No user interaction allowed' error (e.g. user tried to send a 'display dialog' command to a non-GUI ruby process), so convert the target process to a full GUI process and try again
572
- AE.transform_process_to_foreground_application
573
- activate
574
- super
575
- else
576
- raise
577
- end
578
- end
579
- end
580
-
581
- # A client-created scripting addition is automatically targetted at the current application.
582
- # Clients can specify another application as target by calling one of the following methods:
583
-
584
- def by_name(name)
585
- # name : string -- name or full path to application
586
- return ScriptingAddition.new(@_osax_name,
587
- OSAXData.new(:by_path, FindApp.by_name(name), @_terms))
588
- end
589
-
590
- def by_id(id)
591
- # id : string -- bundle id of application
592
- return ScriptingAddition.new(@_osax_name,
593
- OSAXData.new(:by_path, FindApp.by_id(id), @_terms))
594
- end
595
-
596
- def by_creator(creator)
597
- # creator : string -- four-character creator code of application
598
- return ScriptingAddition.new(@_osax_name,
599
- OSAXData.new(:by_path, FindApp.by_creator(creator), @_terms))
600
- end
601
-
602
- def by_pid(pid)
603
- # pid : integer -- Unix process id
604
- return ScriptingAddition.new(@_osax_name, OSAXData.new(:by_pid, pid, @_terms))
605
- end
606
-
607
- def by_url(url)
608
- # url : string -- eppc URL of application
609
- return ScriptingAddition.new(@_osax_name, OSAXData.new(:by_url, url, @_terms))
610
- end
611
-
612
- def by_aem_app(aem_app)
613
- # aem_app : AEM::Application -- an AEM::Application instance
614
- return ScriptingAddition.new(@_osax_name, OSAXData.new(:by_aem_app, aem_app, @_terms))
615
- end
616
-
617
- def current
618
- return ScriptingAddition.new(@_osax_name, OSAXData.new(:current, nil, @_terms))
619
- end
620
- end
621
-
622
- #######
623
-
624
- def OSAX.dump(osax_name, module_name, out_path)
625
- # Export scripting addition terminology tables as a Ruby module
626
- # osaxname : string -- name of installed scripting addition
627
- # module_name : string -- name of generated module (must be a valid Ruby constant)
628
- # out_path : string -- module file to write
629
- #
630
- # Generates a Ruby module containing a scripting addition's basic terminology
631
- # (names and codes).
632
- #
633
- # Call the #dump method to dump faulty sdefs to Ruby module, e.g.:
634
- #
635
- # dump('MyOSAX', 'MyOSAXGlue', '/path/to/site-packages/myosaxglue.py')
636
- #
637
- # Patch any errors by hand, then import the patched module into your script
638
- # and pass it to OSAX.osax via its 'terms' argument, e.g.:
639
- #
640
- # require 'osax'
641
- # require 'MyOSAXGlue'
642
- #
643
- # myapp = OSAX.osax('MyOSAX', terms => MyOSAXGlue)
644
- #
645
- OSAX._init_caches if OSAXNames == []
646
- original_name = osax_name
647
- osax_name = osax_name.downcase
648
- m = /^(.+).osax$/.match(osax_name)
649
- osax_name = m[1] if m != nil
650
- osax_path, terms = OSAXCache.fetch(osax_name) do
651
- raise ArgumentError, "Scripting addition not found: #{original_name.inspect}"
652
- end
653
- sp = OSAX::SdefParser.new
654
- sp.parsefile(osax_path)
655
- Terminology.dump_tables([sp.classes, sp.enumerators, sp.properties, sp.elements, sp.commands],
656
- module_name, osax_path, out_path)
657
- end
11
+
12
+ ######################################################################
13
+ # PRIVATE
14
+ ######################################################################
15
+
16
+ require 'rexml/document'
17
+ require "ae"
18
+ require "kae"
19
+ require "aem"
20
+ require "_appscript/reservedkeywords" # names of all existing methods on ASReference::Application
21
+
22
+ StandardAdditionsEnums = [
23
+ ["stop", "\000\000\000\000"],
24
+ ["note", "\000\000\000\001"],
25
+ ["caution", "\000\000\000\002"]]
26
+
27
+ UTF8ToMacRoman = {
28
+ 0 => 0,
29
+ 1 => 1,
30
+ 2 => 2,
31
+ 3 => 3,
32
+ 4 => 4,
33
+ 5 => 5,
34
+ 6 => 6,
35
+ 7 => 7,
36
+ 8 => 8,
37
+ 9 => 9,
38
+ 10 => 10,
39
+ 11 => 11,
40
+ 12 => 12,
41
+ 13 => 13,
42
+ 14 => 14,
43
+ 15 => 15,
44
+ 16 => 16,
45
+ 17 => 17,
46
+ 18 => 18,
47
+ 19 => 19,
48
+ 20 => 20,
49
+ 21 => 21,
50
+ 22 => 22,
51
+ 23 => 23,
52
+ 24 => 24,
53
+ 25 => 25,
54
+ 26 => 26,
55
+ 27 => 27,
56
+ 28 => 28,
57
+ 29 => 29,
58
+ 30 => 30,
59
+ 31 => 31,
60
+ 32 => 32,
61
+ 33 => 33,
62
+ 34 => 34,
63
+ 35 => 35,
64
+ 36 => 36,
65
+ 37 => 37,
66
+ 38 => 38,
67
+ 39 => 39,
68
+ 40 => 40,
69
+ 41 => 41,
70
+ 42 => 42,
71
+ 43 => 43,
72
+ 44 => 44,
73
+ 45 => 45,
74
+ 46 => 46,
75
+ 47 => 47,
76
+ 48 => 48,
77
+ 49 => 49,
78
+ 50 => 50,
79
+ 51 => 51,
80
+ 52 => 52,
81
+ 53 => 53,
82
+ 54 => 54,
83
+ 55 => 55,
84
+ 56 => 56,
85
+ 57 => 57,
86
+ 58 => 58,
87
+ 59 => 59,
88
+ 60 => 60,
89
+ 61 => 61,
90
+ 62 => 62,
91
+ 63 => 63,
92
+ 64 => 64,
93
+ 65 => 65,
94
+ 66 => 66,
95
+ 67 => 67,
96
+ 68 => 68,
97
+ 69 => 69,
98
+ 70 => 70,
99
+ 71 => 71,
100
+ 72 => 72,
101
+ 73 => 73,
102
+ 74 => 74,
103
+ 75 => 75,
104
+ 76 => 76,
105
+ 77 => 77,
106
+ 78 => 78,
107
+ 79 => 79,
108
+ 80 => 80,
109
+ 81 => 81,
110
+ 82 => 82,
111
+ 83 => 83,
112
+ 84 => 84,
113
+ 85 => 85,
114
+ 86 => 86,
115
+ 87 => 87,
116
+ 88 => 88,
117
+ 89 => 89,
118
+ 90 => 90,
119
+ 91 => 91,
120
+ 92 => 92,
121
+ 93 => 93,
122
+ 94 => 94,
123
+ 95 => 95,
124
+ 96 => 96,
125
+ 97 => 97,
126
+ 98 => 98,
127
+ 99 => 99,
128
+ 100 => 100,
129
+ 101 => 101,
130
+ 102 => 102,
131
+ 103 => 103,
132
+ 104 => 104,
133
+ 105 => 105,
134
+ 106 => 106,
135
+ 107 => 107,
136
+ 108 => 108,
137
+ 109 => 109,
138
+ 110 => 110,
139
+ 111 => 111,
140
+ 112 => 112,
141
+ 113 => 113,
142
+ 114 => 114,
143
+ 115 => 115,
144
+ 116 => 116,
145
+ 117 => 117,
146
+ 118 => 118,
147
+ 119 => 119,
148
+ 120 => 120,
149
+ 121 => 121,
150
+ 122 => 122,
151
+ 123 => 123,
152
+ 124 => 124,
153
+ 125 => 125,
154
+ 126 => 126,
155
+ 127 => 127,
156
+ 196 => 128,
157
+ 197 => 129,
158
+ 199 => 130,
159
+ 201 => 131,
160
+ 209 => 132,
161
+ 214 => 133,
162
+ 220 => 134,
163
+ 225 => 135,
164
+ 224 => 136,
165
+ 226 => 137,
166
+ 228 => 138,
167
+ 227 => 139,
168
+ 229 => 140,
169
+ 231 => 141,
170
+ 233 => 142,
171
+ 232 => 143,
172
+ 234 => 144,
173
+ 235 => 145,
174
+ 237 => 146,
175
+ 236 => 147,
176
+ 238 => 148,
177
+ 239 => 149,
178
+ 241 => 150,
179
+ 243 => 151,
180
+ 242 => 152,
181
+ 244 => 153,
182
+ 246 => 154,
183
+ 245 => 155,
184
+ 250 => 156,
185
+ 249 => 157,
186
+ 251 => 158,
187
+ 252 => 159,
188
+ 8224 => 160,
189
+ 176 => 161,
190
+ 162 => 162,
191
+ 163 => 163,
192
+ 167 => 164,
193
+ 8226 => 165,
194
+ 182 => 166,
195
+ 223 => 167,
196
+ 174 => 168,
197
+ 169 => 169,
198
+ 8482 => 170,
199
+ 180 => 171,
200
+ 168 => 172,
201
+ 8800 => 173,
202
+ 198 => 174,
203
+ 216 => 175,
204
+ 8734 => 176,
205
+ 177 => 177,
206
+ 8804 => 178,
207
+ 8805 => 179,
208
+ 165 => 180,
209
+ 181 => 181,
210
+ 8706 => 182,
211
+ 8721 => 183,
212
+ 8719 => 184,
213
+ 960 => 185,
214
+ 8747 => 186,
215
+ 170 => 187,
216
+ 186 => 188,
217
+ 937 => 189,
218
+ 230 => 190,
219
+ 248 => 191,
220
+ 191 => 192,
221
+ 161 => 193,
222
+ 172 => 194,
223
+ 8730 => 195,
224
+ 402 => 196,
225
+ 8776 => 197,
226
+ 8710 => 198,
227
+ 171 => 199,
228
+ 187 => 200,
229
+ 8230 => 201,
230
+ 160 => 202,
231
+ 192 => 203,
232
+ 195 => 204,
233
+ 213 => 205,
234
+ 338 => 206,
235
+ 339 => 207,
236
+ 8211 => 208,
237
+ 8212 => 209,
238
+ 8220 => 210,
239
+ 8221 => 211,
240
+ 8216 => 212,
241
+ 8217 => 213,
242
+ 247 => 214,
243
+ 9674 => 215,
244
+ 255 => 216,
245
+ 376 => 217,
246
+ 8260 => 218,
247
+ 8364 => 219,
248
+ 8249 => 220,
249
+ 8250 => 221,
250
+ 64257 => 222,
251
+ 64258 => 223,
252
+ 8225 => 224,
253
+ 183 => 225,
254
+ 8218 => 226,
255
+ 8222 => 227,
256
+ 8240 => 228,
257
+ 194 => 229,
258
+ 202 => 230,
259
+ 193 => 231,
260
+ 203 => 232,
261
+ 200 => 233,
262
+ 205 => 234,
263
+ 206 => 235,
264
+ 207 => 236,
265
+ 204 => 237,
266
+ 211 => 238,
267
+ 212 => 239,
268
+ 63743 => 240,
269
+ 210 => 241,
270
+ 218 => 242,
271
+ 219 => 243,
272
+ 217 => 244,
273
+ 305 => 245,
274
+ 710 => 246,
275
+ 732 => 247,
276
+ 175 => 248,
277
+ 728 => 249,
278
+ 729 => 250,
279
+ 730 => 251,
280
+ 184 => 252,
281
+ 733 => 253,
282
+ 731 => 254,
283
+ 711 => 255}
284
+
285
+ class SdefParser
286
+ @@_name_cache = {}
287
+ LegalFirst = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_'
288
+ LegalRest = LegalFirst + '0123456789'
289
+ @@_reserved_keywords = {} # ersatz set
290
+ ReservedKeywords.each { |name| @@_reserved_keywords[name] = nil }
291
+
292
+ attr_reader :properties, :elements, :classes, :enumerators
293
+
294
+ def commands
295
+ return @commands.values
296
+ end
297
+
298
+ def initialize
299
+ # terminology tables; order is significant where synonym definitions occur
300
+ @commands = {}
301
+ @properties = []
302
+ @elements = []
303
+ @classes = []
304
+ @enumerators = []
305
+ end
306
+
307
+ def _name(s)
308
+ # Read a MacRoman-encoded Pascal keyword string.
309
+ if not @@_name_cache.has_key?(s)
310
+ legal = LegalFirst
311
+ res = ''
312
+ s.split(//).each do |c|
313
+ if legal[c]
314
+ res += c
315
+ else
316
+ case c
317
+ when ' ', '-', '/'
318
+ res += '_'
319
+ when '&'
320
+ res += 'and'
321
+ else
322
+ if res == ''
323
+ res = '_'
324
+ end
325
+ res += "0x#{c.unpack('HXh')}"
326
+ end
327
+ end
328
+ legal = LegalRest
329
+ end
330
+ if res[0, 3] == 'AS_' or @@_reserved_keywords.has_key?(res) or res[0, 1] == '_'
331
+ res += '_'
332
+ end
333
+ @@_name_cache[s] = res
334
+ end
335
+ return @@_name_cache[s]
336
+ end
337
+
338
+ def _code(s)
339
+ return s.unpack('U*').collect do |c| # unpack UTF8-encoded byte string
340
+ OSAX::UTF8ToMacRoman.fetch(c)
341
+ end .pack('C*') # pack as MacRoman-encoded byte string (four- or eight-char code)
342
+ end
343
+
344
+ def _addnamecode(node, collection)
345
+ name = _name(node.attributes['name'])
346
+ code = _code(node.attributes['code'])
347
+ if name != '' and code.size == 4 and not collection.include?([name, code])
348
+ collection.push([name, code])
349
+ end
350
+ end
351
+
352
+ def _addcommand(node)
353
+ name = _name(node.attributes['name'])
354
+ code = _code(node.attributes['code'])
355
+ parameters = []
356
+ # Note: overlapping command definitions (e.g. 'path to') should be processed as follows:
357
+ # - If their names and codes are the same, only the last definition is used; other definitions are ignored and will not compile.
358
+ # - If their names are the same but their codes are different, only the first definition is used; other definitions are ignored and will not compile.
359
+ if name != '' and code.size == 8 and (not @commands.has_key?(name) or @commands[name][1] == code)
360
+ @commands[name] = [name, code, parameters]
361
+ node.each_element('parameter') do |pnode|
362
+ _addnamecode(pnode, parameters)
363
+ end
364
+ end
365
+ end
366
+
367
+ def parse(xml)
368
+ # Extract name-code mappings from an sdef.
369
+ #
370
+ # xml : String -- sdef data
371
+ xml = REXML::Document.new(xml)
372
+ xml.root.each_element('suite/*') do |node|
373
+ begin
374
+ if ['command', 'event'].include?(node.name)
375
+ _addcommand(node)
376
+ elsif ['class', 'record-type', 'value-type'].include?(node.name)
377
+ _addnamecode(node, @classes)
378
+ node.each_element('property') do |prop|
379
+ _addnamecode(prop, @properties)
380
+ end
381
+ if node.name == 'class' # elements
382
+ name = node.attributes['plural']
383
+ if name == nil
384
+ name = node.attributes['name']
385
+ name = "#{name}s" if name != nil
386
+ end
387
+ name = _name(name)
388
+ code = _code(node.attributes['code'])
389
+ if name != '' and code.size == 4
390
+ @elements.push([name, code])
391
+ end
392
+ end
393
+ elsif node.name == 'enumeration'
394
+ node.each_element('enumerator') do |enum|
395
+ _addnamecode(enum, @enumerators)
396
+ end
397
+ end
398
+ rescue # ignore problem definitions
399
+ end
400
+ end
401
+ end
402
+
403
+ def parse_file(path)
404
+ # Extract name-code mappings from an sdef.
405
+ #
406
+ # path : String -- path to .sdef file
407
+ parse(AE.copy_scripting_definition(path))
408
+ end
409
+
410
+ end
411
+
412
+ #######
413
+ # cache; stores osax paths and previously parsed terminology (if any) by osax name
414
+
415
+ OSAXCache = {}
416
+ OSAXNames = []
417
+
418
+ #######
419
+ # modified AppData class
420
+
421
+ class OSAXData < Appscript::AppData
422
+
423
+ def initialize(constructor, identifier, terms)
424
+ super(AEM::Application, constructor, identifier, terms)
425
+ end
426
+
427
+ def connect
428
+ super
429
+ begin
430
+ @target.event('ascrgdut').send(60 * 60) # make sure target application has loaded event handlers for all installed OSAXen
431
+ rescue AEM::EventError => e
432
+ if e.number != -1708 # ignore 'event not handled' error
433
+ raise
434
+ end
435
+ end
436
+ end
437
+
438
+ end
439
+
440
+ @_standard_additions = nil
441
+
442
+ def OSAX._init_caches
443
+ se = AEM::Application.by_path(FindApp.by_id('com.apple.systemevents'))
444
+ ['flds', 'fldl', 'fldu'].each do |domain_code|
445
+ osaxen = AEM.app.property(domain_code).property('$scr').elements('file').by_filter(
446
+ AEM.its.property('asty').eq('osax').or(AEM.its.property('extn').eq('osax')))
447
+ if se.event('coredoex', {'----' => osaxen.property('pnam')}).send # domain has ScriptingAdditions folder
448
+ names = se.event('coregetd', {'----' => osaxen.property('pnam')}).send
449
+ paths = se.event('coregetd', {'----' => osaxen.property('posx')}).send
450
+ names.zip(paths).each do |name, path|
451
+ name = name.sub(/(?i)\.osax$/, '') # remove name extension, if any
452
+ OSAXNames.push(name)
453
+ OSAXCache[name.downcase] = [path, nil]
454
+ end
455
+ end
456
+ end
457
+ OSAXNames.sort!.uniq!
458
+ end
459
+
460
+ ######################################################################
461
+ # PUBLIC
462
+ ######################################################################
463
+
464
+ def OSAX.scripting_additions
465
+ # list names of all currently installed scripting additions
466
+ OSAX._init_caches if OSAXNames == []
467
+ return OSAXNames.clone
468
+ end
469
+
470
+ def OSAX.osax(name=nil, app_name=nil)
471
+ # Convenience method for creating a new ScriptingAddition instance.
472
+ # name : String | nil -- scripting addition's name; nil = 'StandardAdditions'
473
+ # app_name : String | nil -- target application's name/path, or nil for current application
474
+ # Result : ScriptingAddition
475
+ #
476
+ # If both arguments are nil, a ScriptingAddition object for StandardAdditions is created
477
+ # and returned. This object is cached for efficiency and returned in subsequent calls;
478
+ # thus clients can conveniently write (e.g):
479
+ #
480
+ # osax.some_command
481
+ # osax.another_command
482
+ #
483
+ # instead of:
484
+ #
485
+ # sa = osax
486
+ # sa.some_command
487
+ # sa.another_command
488
+ #
489
+ # without the additional overhead of creating a new ScriptingAddition object each time.
490
+ #
491
+ if name == nil and app_name == nil
492
+ if @_standard_additions == nil
493
+ @_standard_additions = ScriptingAddition.new('StandardAdditions')
494
+ end
495
+ addition = @_standard_additions
496
+ else
497
+ if name == nil
498
+ name = 'StandardAdditions'
499
+ end
500
+ addition = ScriptingAddition.new(name)
501
+ if app_name
502
+ addition = addition.by_name(app_name)
503
+ end
504
+ end
505
+ return addition
506
+ end
507
+
508
+ # allow methods to be included via 'include OSAX'
509
+
510
+ def scripting_additions
511
+ return OSAX.scripting_additions
512
+ end
513
+
514
+ def osax(*args)
515
+ return OSAX.osax(*args)
516
+ end
517
+
518
+ #######
519
+
520
+ class ScriptingAddition < Appscript::Reference
521
+ # Represents a single scripting addition.
522
+
523
+ def initialize(name, terms=nil)
524
+ # name: string -- a scripting addition's name, e.g. "StandardAdditions";
525
+ # basically its filename minus the '.osax' suffix
526
+ #
527
+ # terms : module or nil -- an optional terminology glue module,
528
+ # as exported by Terminology.dump; if given, ScriptingAddition
529
+ # will use this instead of retrieving the terminology dynamically
530
+ #
531
+ # Note that name is case-insensitive and an '.osax' suffix is ignored if given.
532
+ @_osax_name = name
533
+ if not terms
534
+ osax_name = name.downcase.sub(/(?i)\.osax$/, '')
535
+ OSAX._init_caches if OSAXCache == {}
536
+ path, terminology_tables = OSAXCache[osax_name]
537
+ if not path
538
+ raise ArgumentError, "Scripting addition not found: #{name.inspect}"
539
+ end
540
+ if not terminology_tables
541
+ sp = OSAX::SdefParser.new
542
+ sp.parse_file(path)
543
+ if osax_name == 'standardadditions'
544
+ OSAX::StandardAdditionsEnums.each { |o| sp.enumerators.push(o)}
545
+ end
546
+ terminology_tables = Terminology.tables_for_parsed_sdef(sp)
547
+ OSAXCache[osax_name][1] = terminology_tables
548
+ end
549
+ @_terms = terminology_tables
550
+ terms = OSAXData.new(:current, nil, @_terms)
551
+ elsif not terms.is_a?(OSAX::OSAXData) # assume it's a glue module
552
+ terminology_tables = Terminology.tables_for_module(terms)
553
+ @_terms = terminology_tables
554
+ terms = OSAXData.new(:current, nil, @_terms)
555
+ end
556
+ super(terms, AEM.app)
557
+ end
558
+
559
+ def to_s
560
+ return "#<OSAX::ScriptingAddition name=#{@_osax_name.inspect} target=#{@AS_app_data.target.inspect}>"
561
+ end
562
+
563
+ alias_method :inspect, :to_s
564
+
565
+ ##
566
+
567
+ def method_missing(name, *args)
568
+ begin
569
+ super
570
+ rescue Appscript::CommandError => e
571
+ if e.to_i == -1713 # 'No user interaction allowed' error (e.g. user tried to send a 'display dialog' command to a non-GUI ruby process), so convert the target process to a full GUI process and try again
572
+ AE.transform_process_to_foreground_application
573
+ activate
574
+ super
575
+ else
576
+ raise
577
+ end
578
+ end
579
+ end
580
+
581
+ # A client-created scripting addition is automatically targetted at the current application.
582
+ # Clients can specify another application as target by calling one of the following methods:
583
+
584
+ def by_name(name)
585
+ # name : string -- name or full path to application
586
+ return ScriptingAddition.new(@_osax_name,
587
+ OSAXData.new(:by_path, FindApp.by_name(name), @_terms))
588
+ end
589
+
590
+ def by_id(id)
591
+ # id : string -- bundle id of application
592
+ return ScriptingAddition.new(@_osax_name,
593
+ OSAXData.new(:by_path, FindApp.by_id(id), @_terms))
594
+ end
595
+
596
+ def by_creator(creator)
597
+ # creator : string -- four-character creator code of application
598
+ return ScriptingAddition.new(@_osax_name,
599
+ OSAXData.new(:by_path, FindApp.by_creator(creator), @_terms))
600
+ end
601
+
602
+ def by_pid(pid)
603
+ # pid : integer -- Unix process id
604
+ return ScriptingAddition.new(@_osax_name, OSAXData.new(:by_pid, pid, @_terms))
605
+ end
606
+
607
+ def by_url(url)
608
+ # url : string -- eppc URL of application
609
+ return ScriptingAddition.new(@_osax_name, OSAXData.new(:by_url, url, @_terms))
610
+ end
611
+
612
+ def by_aem_app(aem_app)
613
+ # aem_app : AEM::Application -- an AEM::Application instance
614
+ return ScriptingAddition.new(@_osax_name, OSAXData.new(:by_aem_app, aem_app, @_terms))
615
+ end
616
+
617
+ def current
618
+ return ScriptingAddition.new(@_osax_name, OSAXData.new(:current, nil, @_terms))
619
+ end
620
+ end
621
+
622
+ #######
623
+
624
+ def OSAX.dump(osax_name, module_name, out_path)
625
+ # Export scripting addition terminology tables as a Ruby module
626
+ # osaxname : string -- name of installed scripting addition
627
+ # module_name : string -- name of generated module (must be a valid Ruby constant)
628
+ # out_path : string -- module file to write
629
+ #
630
+ # Generates a Ruby module containing a scripting addition's basic terminology
631
+ # (names and codes).
632
+ #
633
+ # Call the #dump method to dump faulty sdefs to Ruby module, e.g.:
634
+ #
635
+ # dump('MyOSAX', 'MyOSAXGlue', '/path/to/site-packages/myosaxglue.py')
636
+ #
637
+ # Patch any errors by hand, then import the patched module into your script
638
+ # and pass it to OSAX.osax via its 'terms' argument, e.g.:
639
+ #
640
+ # require 'osax'
641
+ # require 'MyOSAXGlue'
642
+ #
643
+ # myapp = OSAX.osax('MyOSAX', terms => MyOSAXGlue)
644
+ #
645
+ OSAX._init_caches if OSAXNames == []
646
+ original_name = osax_name
647
+ osax_name = osax_name.downcase
648
+ m = /^(.+).osax$/.match(osax_name)
649
+ osax_name = m[1] if m != nil
650
+ osax_path, terms = OSAXCache.fetch(osax_name) do
651
+ raise ArgumentError, "Scripting addition not found: #{original_name.inspect}"
652
+ end
653
+ sp = OSAX::SdefParser.new
654
+ sp.parsefile(osax_path)
655
+ Terminology.dump_tables([sp.classes, sp.enumerators, sp.properties, sp.elements, sp.commands],
656
+ module_name, osax_path, out_path)
657
+ end
658
658
 
659
659
  end