rb-scrpt 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +497 -0
  3. data/README.md +8 -0
  4. data/Rakefile +32 -0
  5. data/doc/aem-manual/01_introduction.html +60 -0
  6. data/doc/aem-manual/02_apioverview.html +107 -0
  7. data/doc/aem-manual/03_packingandunpackingdata.html +135 -0
  8. data/doc/aem-manual/04_references.html +409 -0
  9. data/doc/aem-manual/05_targetingapplications.html +164 -0
  10. data/doc/aem-manual/06_buildingandsendingevents.html +229 -0
  11. data/doc/aem-manual/07_findapp.html +63 -0
  12. data/doc/aem-manual/08_examples.html +94 -0
  13. data/doc/aem-manual/aemreferenceinheritance.gif +0 -0
  14. data/doc/aem-manual/index.html +56 -0
  15. data/doc/appscript-manual/01_introduction.html +94 -0
  16. data/doc/appscript-manual/02_aboutappscripting.html +247 -0
  17. data/doc/appscript-manual/03_quicktutorial.html +167 -0
  18. data/doc/appscript-manual/04_gettinghelp.html +188 -0
  19. data/doc/appscript-manual/05_keywordconversion.html +106 -0
  20. data/doc/appscript-manual/06_classesandenums.html +192 -0
  21. data/doc/appscript-manual/07_applicationobjects.html +211 -0
  22. data/doc/appscript-manual/08_realvsgenericreferences.html +96 -0
  23. data/doc/appscript-manual/09_referenceforms.html +241 -0
  24. data/doc/appscript-manual/10_referenceexamples.html +154 -0
  25. data/doc/appscript-manual/11_applicationcommands.html +245 -0
  26. data/doc/appscript-manual/12_commandexamples.html +138 -0
  27. data/doc/appscript-manual/13_performanceissues.html +142 -0
  28. data/doc/appscript-manual/14_notes.html +80 -0
  29. data/doc/appscript-manual/application_architecture.gif +0 -0
  30. data/doc/appscript-manual/application_architecture2.gif +0 -0
  31. data/doc/appscript-manual/finder_to_textedit_event.gif +0 -0
  32. data/doc/appscript-manual/index.html +62 -0
  33. data/doc/appscript-manual/relationships_example.gif +0 -0
  34. data/doc/appscript-manual/ruby_to_itunes_event.gif +0 -0
  35. data/doc/full.css +106 -0
  36. data/doc/index.html +45 -0
  37. data/doc/mactypes-manual/01_introduction.html +54 -0
  38. data/doc/mactypes-manual/02_aliasclass.html +124 -0
  39. data/doc/mactypes-manual/03_fileurlclass.html +126 -0
  40. data/doc/mactypes-manual/04_unitsclass.html +100 -0
  41. data/doc/mactypes-manual/index.html +53 -0
  42. data/doc/osax-manual/01_introduction.html +67 -0
  43. data/doc/osax-manual/02_interface.html +147 -0
  44. data/doc/osax-manual/03_examples.html +73 -0
  45. data/doc/osax-manual/04_notes.html +61 -0
  46. data/doc/osax-manual/index.html +53 -0
  47. data/doc/rb-appscript-logo.png +0 -0
  48. data/extconf.rb +63 -0
  49. data/rb-scrpt.gemspec +16 -0
  50. data/sample/AB_export_vcard.rb +31 -0
  51. data/sample/AB_list_people_with_emails.rb +13 -0
  52. data/sample/Add_iCal_event.rb +21 -0
  53. data/sample/Create_daily_iCal_todos.rb +75 -0
  54. data/sample/Export_Address_Book_phone_numbers.rb +59 -0
  55. data/sample/Hello_world.rb +21 -0
  56. data/sample/List_iTunes_playlist_names.rb +11 -0
  57. data/sample/Make_Mail_message.rb +33 -0
  58. data/sample/Open_file_in_TextEdit.rb +13 -0
  59. data/sample/Organize_Mail_messages.rb +61 -0
  60. data/sample/Print_folder_tree.rb +16 -0
  61. data/sample/Select_all_HTML_files.rb +14 -0
  62. data/sample/Set_iChat_status.rb +24 -0
  63. data/sample/Simple_Finder_GUI_Scripting.rb +18 -0
  64. data/sample/Stagger_Finder_windows.rb +25 -0
  65. data/sample/TextEdit_demo.rb +130 -0
  66. data/sample/iTunes_top40_to_html.rb +69 -0
  67. data/src/SendThreadSafe.c +380 -0
  68. data/src/SendThreadSafe.h +139 -0
  69. data/src/lib/_aem/aemreference.rb +1021 -0
  70. data/src/lib/_aem/codecs.rb +661 -0
  71. data/src/lib/_aem/connect.rb +205 -0
  72. data/src/lib/_aem/encodingsupport.rb +74 -0
  73. data/src/lib/_aem/findapp.rb +85 -0
  74. data/src/lib/_aem/mactypes.rb +250 -0
  75. data/src/lib/_aem/send.rb +279 -0
  76. data/src/lib/_aem/typewrappers.rb +59 -0
  77. data/src/lib/_appscript/defaultterminology.rb +277 -0
  78. data/src/lib/_appscript/referencerenderer.rb +242 -0
  79. data/src/lib/_appscript/reservedkeywords.rb +116 -0
  80. data/src/lib/_appscript/safeobject.rb +250 -0
  81. data/src/lib/_appscript/terminology.rb +470 -0
  82. data/src/lib/aem.rb +253 -0
  83. data/src/lib/kae.rb +1489 -0
  84. data/src/lib/osax.rb +659 -0
  85. data/src/lib/rb-scrpt.rb +1073 -0
  86. data/src/lib/version.rb +3 -0
  87. data/src/rbae.c +979 -0
  88. data/test/README +3 -0
  89. data/test/test_aemreference.rb +115 -0
  90. data/test/test_appscriptcommands.rb +149 -0
  91. data/test/test_appscriptreference.rb +103 -0
  92. data/test/test_codecs.rb +181 -0
  93. data/test/test_findapp.rb +23 -0
  94. data/test/test_mactypes.rb +77 -0
  95. data/test/test_osax.rb +52 -0
  96. metadata +146 -0
@@ -0,0 +1,1073 @@
1
+ #
2
+ # rb-appscript
3
+ #
4
+ # appscript -- syntactically sugared wrapper around the mid-level aem API;
5
+ # provides a high-level, easy-to-use API for creating and sending Apple events
6
+ #
7
+
8
+ require "_aem/mactypes"
9
+
10
+ module Appscript
11
+
12
+ # The following methods and classes are of interest to end users:
13
+ # app, con, its, CommandError, ApplicationNotFoundError, CantLaunchApplicationError
14
+ # Other classes are only of interest to implementors who need to hook in their own code.
15
+
16
+ require "kae"
17
+ require "aem"
18
+ require "_aem/aemreference"
19
+ require "_appscript/referencerenderer"
20
+ require "_appscript/terminology"
21
+ require "_appscript/safeobject"
22
+
23
+ ######################################################################
24
+ # APPDATA
25
+ ######################################################################
26
+
27
+ module AppDataAccessors
28
+ attr_reader :target, :type_by_name, :type_by_code, :reference_by_name, :reference_by_code
29
+ end
30
+
31
+ class AppData < AEM::Codecs
32
+
33
+ ASDictionaryBundleID = 'net.sourceforge.appscript.asdictionary'
34
+
35
+ attr_reader :constructor, :identifier, :reference_codecs
36
+ attr_writer :reference_codecs
37
+
38
+ def initialize(aem_application_class, constructor, identifier, terms)
39
+ super()
40
+ @_aem_application_class = aem_application_class # AEM::Application class or subclass to use when constructing target
41
+ @_terms = terms # user-supplied terminology tables/true/false
42
+ @constructor = constructor # name of AEM::Application constructor to use/:by_aem_app
43
+ @identifier = identifier # argument for AEM::Application constructor
44
+ @reference_codecs = AEM::Codecs.new # low-level Codecs object used to unpack references; used by AppData#unpack_object_specifier, AppData#unpack_insertion_loc. Note: this is a bit kludgy, and it's be better to use AppData for all unpacking, but it should be 'good enough' in practice.
45
+ @_help_agent = nil
46
+ end
47
+
48
+ def connect # initialize AEM::Application instance and terminology tables the first time they are needed
49
+ case @constructor
50
+ when :by_aem_app
51
+ @target = @identifier
52
+ when :current
53
+ @target = @_aem_application_class.current
54
+ else
55
+ @target = @_aem_application_class.send(@constructor, @identifier)
56
+ end
57
+ case @_terms
58
+ when true # obtain terminology from application
59
+ @type_by_code, @type_by_name, @reference_by_code, @reference_by_name = Terminology.tables_for_app(@target)
60
+ when false # use built-in terminology only (e.g. use this when running AppleScript applets)
61
+ @type_by_code, @type_by_name, @reference_by_code, @reference_by_name = Terminology.default_tables
62
+ when nil # [developer-only] make Application#methods return names of built-in methods only (needed to generate reservedkeywords.rb file)
63
+ @type_by_code, @type_by_name, @reference_by_code, @reference_by_name = {}, {}, {}, {}
64
+ when Array # ready-to-use terminology tables
65
+ @type_by_code, @type_by_name, @reference_by_code, @reference_by_name = @_terms
66
+ else # @_terms is [assumed to be] a module containing dumped terminology, so use that
67
+ @type_by_code, @type_by_name, @reference_by_code, @reference_by_name = Terminology.tables_for_module(@_terms)
68
+ end
69
+ extend(AppDataAccessors)
70
+ end
71
+
72
+ #######
73
+
74
+ def dont_cache_unpacked_specifiers
75
+ @reference_codecs.dont_cache_unpacked_specifiers
76
+ end
77
+
78
+ #######
79
+
80
+ Constructors = {
81
+ :by_path => 'path',
82
+ :by_pid => 'pid',
83
+ :by_url => 'url',
84
+ :by_aem_app => 'aemapp',
85
+ :current => 'current',
86
+ }
87
+
88
+ def _init_help_agent
89
+ begin
90
+ apppath = FindApp.by_id(ASDictionaryBundleID)
91
+ asdictionary_is_running = AEM::Application.process_exists_for_path?(apppath)
92
+ @_help_agent = AEM::Application.by_path(apppath)
93
+ if not asdictionary_is_running # hide ASDictionary after launching it
94
+ AEM::Application.by_path(FindApp.by_id('com.apple.systemevents')).event('coresetd', {
95
+ '----' => AEM.app.elements('prcs').by_name('ASDictionary').property('pvis'),
96
+ 'data' => false}).send
97
+ end
98
+ return true
99
+ rescue FindApp::ApplicationNotFoundError => e
100
+ $stderr.puts("No help available: ASDictionary application not found (#{e}).")
101
+ rescue AEM::CantLaunchApplicationError => e
102
+ $stderr.puts("No help available: can't launch ASDictionary application (#{e}).")
103
+ end
104
+ return false
105
+ end
106
+
107
+ def _display_help(flags, ref)
108
+ begin
109
+ $stderr.puts(@_help_agent.event('AppSHelp', {
110
+ 'Cons' => Constructors[@constructor],
111
+ 'Iden' => @identifier,
112
+ 'Styl' => 'rb-appscript',
113
+ 'Flag' => flags,
114
+ 'aRef' => pack(ref),
115
+ }).send)
116
+ return nil
117
+ rescue AEM::EventError => e
118
+ return e
119
+ end
120
+ end
121
+
122
+ def help(flags, ref)
123
+ begin
124
+ if not @_help_agent
125
+ return ref if not _init_help_agent
126
+ end
127
+ e = _display_help(flags, ref)
128
+ if e and [-600, -609].include?(e.number) # not running
129
+ return ref if not _init_help_agent
130
+ e = _display_help(flags, ref)
131
+ end
132
+ $stderr.puts("No help available: ASDictionary raised an error: #{e}.") if e
133
+ rescue => err
134
+ $stderr.puts("No help available: unknown error: #{err}")
135
+ end
136
+ return ref
137
+ end
138
+
139
+ #######
140
+
141
+ def target
142
+ connect
143
+ return @target
144
+ end
145
+
146
+ def type_by_name
147
+ connect
148
+ return @type_by_name
149
+ end
150
+
151
+ def type_by_code
152
+ connect
153
+ return @type_by_code
154
+ end
155
+
156
+ def reference_by_name
157
+ connect
158
+ return @reference_by_name
159
+ end
160
+
161
+ def reference_by_code
162
+ connect
163
+ return @reference_by_code
164
+ end
165
+
166
+ #######
167
+
168
+ def pack(data)
169
+ if data.is_a?(GenericReference)
170
+ data = data.AS_resolve(self)
171
+ end
172
+ if data.is_a?(Reference)
173
+ data = data.AS_aem_reference
174
+ elsif data.is_a?(Symbol)
175
+ data = self.type_by_name.fetch(data) { raise IndexError, "Unknown Keyword: #{data.inspect}" }
176
+ end
177
+ return super(data)
178
+ end
179
+
180
+ ##
181
+
182
+ ClassType = AEM::AEType.new(KAE::PClass)
183
+
184
+ def pack_hash(val)
185
+ record = AE::AEDesc.new_list(true)
186
+ if val.has_key?(:class_) or val.has_key?(ClassType)
187
+ # if hash contains a 'class' property containing a class name, coerce the AEDesc to that class
188
+ new_val = Hash[val]
189
+ if new_val.has_key?(:class_)
190
+ val2 = new_val.delete(:class_)
191
+ else
192
+ val2 = new_val.delete(ClassType)
193
+ end
194
+ if val2.is_a?(Symbol) # get the corresponding AEType (assuming there is one)
195
+ val2 = @type_by_name.fetch(val2, val2)
196
+ end
197
+ if val2.is_a?(AEM::AEType) # coerce the record to the desired type
198
+ record = record.coerce(val2.code)
199
+ val = new_val
200
+ end # else value wasn't a class name, so it'll be packed as a normal record property instead
201
+ end
202
+ usrf = nil
203
+ val.each do | key, value |
204
+ if key.is_a?(Symbol)
205
+ key_type = @type_by_name.fetch(key) { raise IndexError, "Unknown keyword: #{key.inspect}" }
206
+ record.put_param(key_type.code, pack(value))
207
+ elsif key.is_a?(AEM::AETypeBase)
208
+ record.put_param(key.code, pack(value))
209
+ else
210
+ if usrf == nil
211
+ usrf = AE::AEDesc.new_list(false)
212
+ end
213
+ usrf.put_item(0, pack(key))
214
+ usrf.put_item(0, pack(value))
215
+ end
216
+ end
217
+ if usrf
218
+ record.put_param(KAE::KeyASUserRecordFields, usrf)
219
+ end
220
+ return record
221
+ end
222
+
223
+ def unpack_type(desc)
224
+ aem_value = super(desc)
225
+ return @type_by_code.fetch(aem_value.code, aem_value)
226
+ end
227
+
228
+ def unpack_enumerated(desc)
229
+ aem_value = super(desc)
230
+ return @type_by_code.fetch(aem_value.code, aem_value)
231
+ end
232
+
233
+ def unpack_property(desc)
234
+ aem_value = super(desc)
235
+ return @type_by_code.fetch(aem_value.code, aem_value)
236
+ end
237
+
238
+ def unpack_aerecord(desc)
239
+ dct = {}
240
+ desc.length().times do |i|
241
+ key, value = desc.get_item(i + 1, KAE::TypeWildCard)
242
+ if key == KAE::KeyASUserRecordFields
243
+ lst = unpack_aelist(value)
244
+ (lst.length / 2).times do |j|
245
+ dct[lst[j * 2]] = lst[j * 2 + 1]
246
+ end
247
+ else
248
+ dct[@type_by_code.fetch(key) { AEM::AEType.new(key) }] = unpack(value)
249
+ end
250
+ end
251
+ return dct
252
+ end
253
+
254
+ def unpack_object_specifier(desc)
255
+ return Reference.new(self, @reference_codecs.unpack(desc))
256
+ end
257
+
258
+ def unpack_insertion_loc(desc)
259
+ return Reference.new(self, @reference_codecs.unpack(desc))
260
+ end
261
+
262
+ def unpack_contains_comp_descriptor(op1, op2)
263
+ if op1.is_a?(Appscript::Reference) and op1.AS_aem_reference.AEM_root == AEMReference::Its
264
+ return op1.contains(op2)
265
+ else
266
+ return super
267
+ end
268
+ end
269
+
270
+ def unpack_unknown(desc)
271
+ return case desc.type
272
+ when KAE::TypeApplicationBundleID
273
+ Appscript.app.by_id(desc.data)
274
+ when KAE::TypeApplicationURL
275
+ if desc.data[0, 4] == 'file' # workaround for converting AEAddressDescs containing file:// URLs to application paths, since AEAddressDescs containing file URLs don't seem to work correctly
276
+ Appscript.app(MacTypes::FileURL.url(desc.data).path)
277
+ else # presumably contains an eppc:// URL
278
+ Appscript.app.by_url(desc.data)
279
+ end
280
+ when KAE::TypeApplSignature
281
+ Appscript.app.by_creator(AEM::Codecs.four_char_code(desc.data))
282
+ when KAE::TypeKernelProcessID
283
+ Appscript.app.by_pid(desc.data.unpack('L')[0])
284
+ when KAE::TypeMachPort, KAE::TypeProcessSerialNumber
285
+ Appscript.app.by_aem_app(AEM::Application.by_desc(desc))
286
+ else
287
+ super
288
+ end
289
+ end
290
+ end
291
+
292
+
293
+ ######################################################################
294
+ # GENERIC REFERENCE
295
+ ######################################################################
296
+
297
+ class GenericReference < AS_SafeObject
298
+
299
+ attr_reader :_call
300
+ protected :_call
301
+
302
+ def initialize(call)
303
+ super()
304
+ @_call = call
305
+ end
306
+
307
+ def ==(v)
308
+ return (self.class == v.class and @_call == v._call)
309
+ end
310
+
311
+ alias_method :eql?, :==
312
+
313
+ def hash
314
+ return @_call.hash
315
+ end
316
+
317
+ def method_missing(name, *args)
318
+ return Appscript::GenericReference.new(@_call + [[name, args]])
319
+ end
320
+
321
+ def to_s
322
+ s= @_call[0]
323
+ @_call[1, @_call.length].each do |name, args|
324
+ if name == :[]
325
+ if args.length == 1
326
+ s += "[#{args[0].inspect}]"
327
+ else
328
+ s += "[#{args[0].inspect}, #{args[1].inspect}]"
329
+ end
330
+ else
331
+ if args.length > 0
332
+ s += ".#{name.to_s}(#{(args.map { |arg| arg.inspect }).join(', ')})"
333
+ else
334
+ s += ".#{name.to_s}"
335
+ end
336
+ end
337
+ end
338
+ return s
339
+ end
340
+
341
+ def inspect
342
+ return to_s
343
+ end
344
+
345
+ def AS_resolve(app_data)
346
+ ref = Reference.new(app_data, {'app' => AEM.app, 'con' => AEM.con, 'its' => AEM.its}[@_call[0]])
347
+ @_call[1, @_call.length].each do |name, args|
348
+ ref = ref.send(name, *args)
349
+ end
350
+ return ref
351
+ end
352
+ end
353
+
354
+
355
+ ######################################################################
356
+ # REFERENCE
357
+ ######################################################################
358
+
359
+ class Reference < AS_SafeObject
360
+
361
+ # users may occasionally require access to the following for creating workarounds to problem apps
362
+ # note: calling #AS_app_data on a newly created application object will return an AppData instance
363
+ # that is not yet fully initialised, so remember to call its #connect method before use
364
+ attr_reader :AS_aem_reference, :AS_app_data
365
+ attr_writer :AS_aem_reference, :AS_app_data
366
+
367
+ def initialize(app_data, aem_reference)
368
+ super()
369
+ @AS_app_data = app_data
370
+ @AS_aem_reference = aem_reference
371
+ end
372
+
373
+ def _resolve_range_boundary(selector)
374
+ if selector.is_a?(Appscript::GenericReference)
375
+ return selector.AS_resolve(@AS_app_data).AS_aem_reference
376
+ elsif selector.is_a?(Reference)
377
+ return selector.AS_aem_reference
378
+ else
379
+ return selector
380
+ end
381
+ end
382
+
383
+ #######
384
+
385
+ def help(flags='-t')
386
+ return @AS_app_data.help(flags, self)
387
+ end
388
+
389
+ def Reference._pack_uint32(n) # used to pack csig attributes
390
+ return AE::AEDesc.new(KAE::TypeUInt32, [n].pack('L'))
391
+ end
392
+
393
+ # 'csig' attribute flags (see ASRegistry.h; note: there's no option for 'numeric strings' in 10.4)
394
+
395
+ IgnoreEnums = [
396
+ [:case, KAE::KAECaseConsiderMask, KAE::KAECaseIgnoreMask],
397
+ [:diacriticals, KAE::KAEDiacriticConsiderMask, KAE::KAEDiacriticIgnoreMask],
398
+ [:whitespace, KAE::KAEWhiteSpaceConsiderMask, KAE::KAEWhiteSpaceIgnoreMask],
399
+ [:hyphens, KAE::KAEHyphensConsiderMask, KAE::KAEHyphensIgnoreMask],
400
+ [:expansion, KAE::KAEExpansionConsiderMask, KAE::KAEExpansionIgnoreMask],
401
+ [:punctuation, KAE::KAEPunctuationConsiderMask, KAE::KAEPunctuationIgnoreMask],
402
+ ]
403
+
404
+ # default cons, csig attributes
405
+
406
+ DefaultConsiderations = AEM::DefaultCodecs.pack([AEM::AEEnum.new(KAE::KAECase)])
407
+ DefaultConsidersAndIgnores = _pack_uint32(KAE::KAECaseIgnoreMask)
408
+
409
+ ##
410
+
411
+ def _send_command(args, name, code, labelled_arg_terms)
412
+ atts = {KAE::KeySubjectAttr => nil}
413
+ params = {}
414
+ case args.length
415
+ when 0
416
+ keyword_args = {}
417
+ when 1 # note: if a command takes a hash as its direct parameter, user must pass {} as a second arg otherwise hash will be assumed to be keyword parameters
418
+ if args[0].is_a?(Hash)
419
+ keyword_args = args[0]
420
+ else
421
+ params[KAE::KeyDirectObject] = args[0]
422
+ keyword_args = {}
423
+ end
424
+ when 2
425
+ params[KAE::KeyDirectObject], keyword_args = args
426
+ else
427
+ raise ArgumentError, "Too many direct parameters."
428
+ end
429
+ if not keyword_args.is_a?(Hash)
430
+ raise ArgumentError, "Second argument must be a Hash containing zero or more keyword parameters."
431
+ end
432
+ # get user-specified timeout, if any
433
+ timeout = (keyword_args.delete(:timeout) {60}).to_i
434
+ if timeout <= 0
435
+ timeout = KAE::KNoTimeOut
436
+ else
437
+ timeout *= 60
438
+ end
439
+ # default send flags
440
+ send_flags = KAE::KAECanSwitchLayer
441
+ # ignore application's reply?
442
+ send_flags += keyword_args.delete(:wait_reply) == false ? KAE::KAENoReply : KAE::KAEWaitReply
443
+ # add considering/ignoring attributes
444
+ ignore_options = keyword_args.delete(:ignore)
445
+ if ignore_options == nil
446
+ atts[KAE::EnumConsiderations] = DefaultConsiderations
447
+ atts[KAE::EnumConsidsAndIgnores] = DefaultConsidersAndIgnores
448
+ else
449
+ atts[KAE::EnumConsiderations] = ignore_options
450
+ csig = 0
451
+ IgnoreEnums.each do |option, consider_mask, ignore_mask|
452
+ csig += ignore_options.include?(option) ? ignore_mask : consider_mask
453
+ end
454
+ atts[KAE::EnumConsidsAndIgnores] = Reference._pack_uint32(csig)
455
+ end
456
+ # optionally specify return value type
457
+ if keyword_args.has_key?(:result_type)
458
+ params[KAE::KeyAERequestedType] = keyword_args.delete(:result_type)
459
+ end
460
+ # extract labelled parameters, if any
461
+ keyword_args.each do |param_name, param_value|
462
+ param_code = labelled_arg_terms[param_name]
463
+ if param_code == nil
464
+ raise ArgumentError, "Unknown keyword parameter: #{param_name.inspect}"
465
+ end
466
+ params[param_code] = param_value
467
+ end
468
+ # apply special cases
469
+ # Note: appscript does not replicate every little AppleScript quirk when packing event attributes and parameters (e.g. AS always packs a make command's tell block as the subject attribute, and always includes an each parameter in count commands), but should provide sufficient consistency with AS's habits and give good usability in their own right.
470
+ if @AS_aem_reference != AEM.app # If command is called on a Reference, rather than an Application...
471
+ if code == 'coresetd'
472
+ # if ref.set(...) contains no 'to' argument, use direct argument for 'to' parameter and target reference for direct parameter
473
+ if params.has_key?(KAE::KeyDirectObject) and not params.has_key?(KAE::KeyAEData)
474
+ params[KAE::KeyAEData] = params[KAE::KeyDirectObject]
475
+ params[KAE::KeyDirectObject] = @AS_aem_reference
476
+ elsif not params.has_key?(KAE::KeyDirectObject)
477
+ params[KAE::KeyDirectObject] = @AS_aem_reference
478
+ else
479
+ atts[KAE::KeySubjectAttr] = @AS_aem_reference
480
+ end
481
+ elsif code == 'corecrel'
482
+ # this next bit is a bit tricky:
483
+ # - While it should be possible to pack the target reference as a subject attribute, when the target is of typeInsertionLoc, CocoaScripting stupidly tries to coerce it to typeObjectSpecifier, which causes a coercion error.
484
+ # - While it should be possible to pack the target reference as the 'at' parameter, some less-well-designed applications won't accept this and require it to be supplied as a subject attribute (i.e. how AppleScript supplies it).
485
+ # One option is to follow the AppleScript approach and force users to always supply subject attributes as target references and 'at' parameters as 'at' parameters, but the syntax for the latter is clumsy and not backwards-compatible with a lot of existing appscript code (since earlier versions allowed the 'at' parameter to be given as the target reference). So for now we split the difference when deciding what to do with a target reference: if it's an insertion location then pack it as the 'at' parameter (where possible), otherwise pack it as the subject attribute (and if the application doesn't like that then it's up to the client to pack it as an 'at' parameter themselves).
486
+ #
487
+ # if ref.make(...) contains no 'at' argument and target is an insertion reference, use target reference for 'at' parameter...
488
+ if @AS_aem_reference.is_a?(AEMReference::InsertionSpecifier) \
489
+ and not params.has_key?(KAE::KeyAEInsertHere)
490
+ params[KAE::KeyAEInsertHere] = @AS_aem_reference
491
+ else # ...otherwise pack the target reference as the subject attribute
492
+ atts[KAE::KeySubjectAttr] = @AS_aem_reference
493
+ end
494
+ elsif params.has_key?(KAE::KeyDirectObject)
495
+ # if user has already supplied a direct parameter, pack that reference as the subject attribute
496
+ atts[KAE::KeySubjectAttr] = @AS_aem_reference
497
+ else
498
+ # pack that reference as the direct parameter
499
+ params[KAE::KeyDirectObject] = @AS_aem_reference
500
+ end
501
+ end
502
+ # build and send the Apple event, returning its result, if any
503
+ begin
504
+ return @AS_app_data.target.event(code, params, atts,
505
+ KAE::KAutoGenerateReturnID, @AS_app_data).send(timeout, send_flags)
506
+ rescue AEM::EventError => e
507
+ if e.number == -1708 and code == 'ascrnoop'
508
+ return # 'launch' events always return 'not handled' errors; just ignore these
509
+ elsif [-600, -609].include?(e.number) and @AS_app_data.constructor == :by_path
510
+ #
511
+ # Event was sent to a local app for which we no longer have a valid address
512
+ # (i.e. the application has quit since this AEM::Application object was made).
513
+ #
514
+ # - If application is running under a new process id, we just update the
515
+ # AEM::Application object and resend the event.
516
+ #
517
+ # - If application isn't running, then we see if the event being sent is one of
518
+ # those allowed to relaunch the application (i.e. 'run' or 'launch'). If it is, the
519
+ # application is relaunched, the process id updated and the event resent;
520
+ # if not, the error is rethrown.
521
+ #
522
+ if not AEM::Application.process_exists_for_path?(@AS_app_data.identifier)
523
+ if code == 'ascrnoop'
524
+ AEM::Application.launch(@AS_app_data.identifier)
525
+ elsif code != 'aevtoapp'
526
+ raise CommandError.new(self, name, args, e, @AS_app_data)
527
+ end
528
+ end
529
+ # update AEMApplication object's AEAddressDesc
530
+ @AS_app_data.target.reconnect
531
+ # re-send command
532
+ begin
533
+ return @AS_app_data.target.event(code, params, atts,
534
+ KAE::KAutoGenerateReturnID, @AS_app_data).send(timeout, send_flags)
535
+ rescue AEM::EventError => e
536
+ raise CommandError.new(self, name, args, e, @AS_app_data)
537
+ end
538
+ end
539
+ end
540
+ raise CommandError.new(self, name, args, e, @AS_app_data)
541
+ end
542
+
543
+
544
+ #######
545
+ # introspection
546
+
547
+ def respond_to?(name, includePriv=false)
548
+ if Object.respond_to?(name)
549
+ return true
550
+ else
551
+ return @AS_app_data.reference_by_name.has_key?(name.is_a?(String) ? name.intern : name)
552
+ end
553
+ end
554
+
555
+ def methods
556
+ return (Object.instance_methods + @AS_app_data.reference_by_name.keys.collect { |name| name.to_s }).uniq
557
+ end
558
+
559
+ def commands
560
+ return (@AS_app_data.reference_by_name.collect { |name, info| info[0] == :command ? name.to_s : nil }).compact.sort
561
+ end
562
+
563
+ def parameters(command_name)
564
+ if not @AS_app_data.reference_by_name.has_key?(command_name.intern)
565
+ raise ArgumentError, "Command not found: #{command_name}"
566
+ end
567
+ return (@AS_app_data.reference_by_name[command_name.intern][1][1].keys.collect { |name| name.to_s }).sort
568
+ end
569
+
570
+ def properties
571
+ return (@AS_app_data.reference_by_name.collect { |name, info| info[0] == :property ? name.to_s : nil }).compact.sort
572
+ end
573
+
574
+ def elements
575
+ return (@AS_app_data.reference_by_name.collect { |name, info| info[0] == :element ? name.to_s : nil }).compact.sort
576
+ end
577
+
578
+ def keywords
579
+ return (@AS_app_data.type_by_name.collect { |name, code| name.to_s }).sort
580
+ end
581
+
582
+ #######
583
+ # standard object methods
584
+
585
+ def ==(val)
586
+ return (self.class == val.class and @AS_app_data.target == val.AS_app_data.target \
587
+ and @AS_aem_reference == val.AS_aem_reference)
588
+ end
589
+
590
+ alias_method :eql?, :==
591
+
592
+ def hash
593
+ if not defined? @_hash
594
+ @_hash = [@AS_app_data.target, @AS_aem_reference].hash
595
+ end
596
+ return @_hash
597
+ end
598
+
599
+ def to_s
600
+ if not defined? @_to_s
601
+ @_to_s = ReferenceRenderer.render(@AS_app_data, @AS_aem_reference)
602
+ end
603
+ return @_to_s
604
+ end
605
+
606
+ alias_method :inspect, :to_s
607
+
608
+ #######
609
+ # Utility methods
610
+
611
+ def is_running?
612
+ identifier = @AS_app_data.identifier
613
+ case @AS_app_data.constructor
614
+ when :by_path
615
+ return AEM::Application.process_exists_for_path?(identifier)
616
+ when :by_pid
617
+ return AEM::Application.process_exists_for_pid?(identifier)
618
+ when :by_url
619
+ return AEM::Application.process_exists_for_url?(identifier)
620
+ when :by_aem_app
621
+ return AEM::Application.process_exists_for_desc?(identifier.address_desc)
622
+ else # when :current
623
+ return true
624
+ end
625
+ end
626
+
627
+ #######
628
+ # Public properties and methods; these are called by end-user and other clients (e.g. generic references)
629
+
630
+ def method_missing(name, *args)
631
+ selector_type, code = @AS_app_data.reference_by_name[name]
632
+ case selector_type # check if name is a property/element/command name, and if it is handle accordingly
633
+ when :property
634
+ raise ArgumentError, "wrong number of arguments for '#{name}' property (1 for 0)" if args != []
635
+ return Reference.new(@AS_app_data, @AS_aem_reference.property(code))
636
+ when :element
637
+ raise ArgumentError, "wrong number of arguments for '#{name}' elements (1 for 0)" if args != []
638
+ return Reference.new(@AS_app_data, @AS_aem_reference.elements(code))
639
+ when :command
640
+ return _send_command(args, name, code[0], code[1])
641
+ else
642
+ # see if it's a method that has been added to Object class [presumably] at runtime, but excluded
643
+ # by AS_SafeObject to avoid potential conflicts with property/element/command names
644
+ begin
645
+ # Notes:
646
+ # rb-appscript has to prevent arbitrary methods that are added to Ruby's base Object class
647
+ # by client code from automatically appearing in Appscript::Reference as well, as these new
648
+ # methods may inadvertently mask property/element/command names, causing appscript to
649
+ # behave incorrectly. However, once it is confirmed that a given method will not mask an existing
650
+ # property/element/command name, it can be added retrospectively to the Reference instance
651
+ # upon which it was called, which is what happens here.
652
+ #
653
+ # This means that methods such as #pretty_print and #pretty_inspect, which are
654
+ # injected into Object when the 'pp' module is loaded, will still be available in appscript
655
+ # references, even though they are not on AS_SafeObject's official list of permitted methods,
656
+ # *as long as* properties/elements/commands of the same name do not already exist for that
657
+ # reference.
658
+ #
659
+ # Where properties/elements/commands of the same name do already exist, appscript
660
+ # will still defer to those, of course, and this may cause problems for the caller if
661
+ # they were wanting the other behaviour. (But, that's the risk one runs with any sort
662
+ # of subclassing exercise when the contents of the superclass are not known for certain
663
+ # beforehand.) Clients that require access to these methods will need to add their names
664
+ # to the ReservedKeywords list (see _appscript/reservedkeywords.rb) at runtime, thereby
665
+ # forcing appscript to append underscores to the conflicting property/element/command
666
+ # names in order to disambiguate them, and modifying any code that refers to those
667
+ # properties/elements/commands accordingly.
668
+ meth = Object.instance_method(name)
669
+ rescue NameError # message not handled
670
+ msg = "Unknown property, element or command: '#{name}'"
671
+ if @AS_app_data.reference_by_name.has_key?("#{name}_".intern)
672
+ msg += " (Did you mean '#{name}_'?)"
673
+ end
674
+ raise RuntimeError, msg
675
+ end
676
+ return meth.bind(self).call(*args)
677
+ end
678
+ end
679
+
680
+ def [](selector, end_range_selector=nil)
681
+ raise TypeError, "Bad selector: nil not allowed." if selector == nil
682
+ if end_range_selector != nil
683
+ new_ref = @AS_aem_reference.by_range(
684
+ self._resolve_range_boundary(selector),
685
+ self._resolve_range_boundary(end_range_selector))
686
+ else
687
+ case selector
688
+ when String
689
+ new_ref = @AS_aem_reference.by_name(selector)
690
+ when Appscript::GenericReference, Appscript::Reference, AEMReference::Test
691
+ case selector
692
+ when Appscript::GenericReference
693
+ test_clause = selector.AS_resolve(@AS_app_data)
694
+ begin
695
+ test_clause = test_clause.AS_aem_reference
696
+ rescue NoMethodError
697
+ raise ArgumentError, "Not a valid its-based test: #{selector}"
698
+ end
699
+ when Appscript::Reference
700
+ test_clause = selector.AS_aem_reference
701
+ else
702
+ test_clause = selector
703
+ end
704
+ if not test_clause.is_a?(AEMReference::Test)
705
+ raise TypeError, "Not an its-based test: #{selector}"
706
+ end
707
+ new_ref = @AS_aem_reference.by_filter(test_clause)
708
+ else
709
+ new_ref = @AS_aem_reference.by_index(selector)
710
+ end
711
+ end
712
+ return Reference.new(@AS_app_data, new_ref)
713
+ end
714
+
715
+ def first
716
+ return Reference.new(@AS_app_data, @AS_aem_reference.first)
717
+ end
718
+
719
+ def middle
720
+ return Reference.new(@AS_app_data, @AS_aem_reference.middle)
721
+ end
722
+
723
+ def last
724
+ return Reference.new(@AS_app_data, @AS_aem_reference.last)
725
+ end
726
+
727
+ def any
728
+ return Reference.new(@AS_app_data, @AS_aem_reference.any)
729
+ end
730
+
731
+ def beginning
732
+ return Reference.new(@AS_app_data, @AS_aem_reference.beginning)
733
+ end
734
+
735
+ def end
736
+ return Reference.new(@AS_app_data, @AS_aem_reference.end)
737
+ end
738
+
739
+ def before
740
+ return Reference.new(@AS_app_data, @AS_aem_reference.before)
741
+ end
742
+
743
+ def after
744
+ return Reference.new(@AS_app_data, @AS_aem_reference.after)
745
+ end
746
+
747
+ def previous(klass)
748
+ return Reference.new(@AS_app_data, @AS_aem_reference.previous(
749
+ @AS_app_data.type_by_name.fetch(klass).code))
750
+ end
751
+
752
+ def next(klass)
753
+ return Reference.new(@AS_app_data, @AS_aem_reference.next(
754
+ @AS_app_data.type_by_name.fetch(klass).code))
755
+ end
756
+
757
+ def ID(id)
758
+ return Reference.new(@AS_app_data, @AS_aem_reference.by_id(id))
759
+ end
760
+
761
+ # Following methods will be called by its-based generic references
762
+ # Note that rb-appscript's comparison 'operator' names are gt/ge/eq/ne/lt/le, not >/>=/==/!=/</<= as in py-appscript. Unlike Python, Ruby's != operator isn't overridable, and a mixture of styles would be confusing to users. On the plus side, it does mean that rb-appscript's generic refs can be compared for equality.
763
+
764
+ def gt(operand)
765
+ return Reference.new(@AS_app_data, @AS_aem_reference.gt(operand))
766
+ end
767
+
768
+ def ge(operand)
769
+ return Reference.new(@AS_app_data, @AS_aem_reference.ge(operand))
770
+ end
771
+
772
+ def eq(operand) # avoid colliding with comparison operators, which are normally used to compare two references
773
+ return Reference.new(@AS_app_data, @AS_aem_reference.eq(operand))
774
+ end
775
+
776
+ def ne(operand)
777
+ return Reference.new(@AS_app_data, @AS_aem_reference.ne(operand))
778
+ end
779
+
780
+ def lt(operand)
781
+ return Reference.new(@AS_app_data, @AS_aem_reference.lt(operand))
782
+ end
783
+
784
+ def le(operand)
785
+ return Reference.new(@AS_app_data, @AS_aem_reference.le(operand))
786
+ end
787
+
788
+ def begins_with(operand)
789
+ return Reference.new(@AS_app_data, @AS_aem_reference.begins_with(operand))
790
+ end
791
+
792
+ def ends_with(operand)
793
+ return Reference.new(@AS_app_data, @AS_aem_reference.ends_with(operand))
794
+ end
795
+
796
+ def contains(operand)
797
+ return Reference.new(@AS_app_data, @AS_aem_reference.contains(operand))
798
+ end
799
+
800
+ def is_in(operand)
801
+ return Reference.new(@AS_app_data, @AS_aem_reference.is_in(operand))
802
+ end
803
+
804
+ def does_not_begin_with(operand)
805
+ return self.begins_with(operand).not
806
+ end
807
+
808
+ def does_not_end_with(operand)
809
+ return self.ends_with(operand).not
810
+ end
811
+
812
+ def does_not_contain(operand)
813
+ return self.contains(operand).not
814
+ end
815
+
816
+ def is_not_in(operand)
817
+ return self.is_in(operand).not
818
+ end
819
+
820
+ def and(*operands)
821
+ return Reference.new(@AS_app_data, @AS_aem_reference.and(*operands))
822
+ end
823
+
824
+ def or(*operands)
825
+ return Reference.new(@AS_app_data, @AS_aem_reference.or(*operands))
826
+ end
827
+
828
+ def not
829
+ return Reference.new(@AS_app_data, @AS_aem_reference.not)
830
+ end
831
+ end
832
+
833
+
834
+ ######################################################################
835
+ # APPLICATION
836
+ ######################################################################
837
+
838
+ class Application < Reference
839
+
840
+ private_class_method :new
841
+
842
+ def _aem_application_class # hook
843
+ return AEM::Application
844
+ end
845
+
846
+ def initialize(constructor, identifier, terms)
847
+ super(AppData.new(_aem_application_class, constructor, identifier, terms), AEM.app)
848
+ end
849
+
850
+ # constructors
851
+
852
+ def Application.by_name(name, terms=true)
853
+ return new(:by_path, FindApp.by_name(name), terms)
854
+ end
855
+
856
+ def Application.by_id(id, terms=true)
857
+ return new(:by_path, FindApp.by_id(id), terms)
858
+ end
859
+
860
+ def Application.by_creator(creator, terms=true)
861
+ return new(:by_path, FindApp.by_creator(creator), terms)
862
+ end
863
+
864
+ def Application.by_pid(pid, terms=true)
865
+ return new(:by_pid, pid, terms)
866
+ end
867
+
868
+ def Application.by_url(url, terms=true)
869
+ return new(:by_url, url, terms)
870
+ end
871
+
872
+ def Application.by_aem_app(aem_app, terms=true)
873
+ return new(:by_aem_app, aem_app, terms)
874
+ end
875
+
876
+ def Application.current(terms=true)
877
+ return new(:current, nil, terms)
878
+ end
879
+
880
+ #
881
+
882
+ def AS_new_reference(ref)
883
+ if ref.is_a?(Appscript::GenericReference)
884
+ return ref.AS_resolve(@AS_app_data)
885
+ elsif ref.is_a?(AEMReference::Query)
886
+ return Reference.new(@AS_app_data, ref)
887
+ elsif ref == nil
888
+ return Reference.new(@AS_app_data, AEM.app)
889
+ else
890
+ return Reference.new(@AS_app_data, AEM.custom_root(ref))
891
+ end
892
+ end
893
+
894
+ def begin_transaction(session=nil)
895
+ @AS_app_data.target.begin_transaction(session)
896
+ end
897
+
898
+ def abort_transaction
899
+ @AS_app_data.target.abort_transaction
900
+ end
901
+
902
+ def end_transaction
903
+ @AS_app_data.target.end_transaction
904
+ end
905
+
906
+ def launch
907
+ if @AS_app_data.constructor == :by_path
908
+ AEM::Application.launch(@AS_app_data.identifier)
909
+ @AS_app_data.target.reconnect
910
+ else
911
+ begin
912
+ @AS_app_data.target.event('ascrnoop').send # will send launch event to app if already running; else will error
913
+ rescue AEM::EventError => e
914
+ raise if e.to_i != -1708
915
+ end
916
+ end
917
+ end
918
+ end
919
+
920
+ ##
921
+
922
+ class GenericApplication < GenericReference
923
+
924
+ def initialize(app_class)
925
+ @_app_class = app_class
926
+ super(['app'])
927
+ end
928
+
929
+ def by_name(name, terms=true)
930
+ return @_app_class.by_name(name, terms)
931
+ end
932
+
933
+ def by_id(id, terms=true)
934
+ return @_app_class.by_id(id, terms)
935
+ end
936
+
937
+ def by_creator(creator, terms=true)
938
+ return @_app_class.by_creator(creator, terms)
939
+ end
940
+
941
+ def by_pid(pid, terms=true)
942
+ return @_app_class.by_pid(pid, terms)
943
+ end
944
+
945
+ def by_url(url, terms=true)
946
+ return @_app_class.by_url(url, terms)
947
+ end
948
+
949
+ def by_aem_app(aem_app, terms=true)
950
+ return @_app_class.by_aem_app(aem_app, terms)
951
+ end
952
+
953
+ def current(terms=true)
954
+ return @_app_class.current(terms)
955
+ end
956
+ end
957
+
958
+ #######
959
+
960
+ AS_App = Appscript::GenericApplication.new(Application)
961
+ AS_Con = Appscript::GenericReference.new(['con'])
962
+ AS_Its = Appscript::GenericReference.new(['its'])
963
+
964
+
965
+ ######################################################################
966
+ # REFERENCE ROOTS
967
+ ######################################################################
968
+ # public (note: Application & GenericApplication classes may also be accessed if subclassing Application class is required)
969
+
970
+ def Appscript.app(*args)
971
+ if args == []
972
+ return AS_App
973
+ else
974
+ return AS_App.by_name(*args)
975
+ end
976
+ end
977
+
978
+ def Appscript.con
979
+ return AS_Con
980
+ end
981
+
982
+ def Appscript.its
983
+ return AS_Its
984
+ end
985
+
986
+ # also define app, con, its as instance methods so that clients can 'include Appscript'
987
+
988
+ def app(*args)
989
+ if args == []
990
+ return AS_App
991
+ else
992
+ return AS_App.by_name(*args)
993
+ end
994
+ end
995
+
996
+ def con
997
+ return AS_Con
998
+ end
999
+
1000
+ def its
1001
+ return AS_Its
1002
+ end
1003
+
1004
+
1005
+ ######################################################################
1006
+ # COMMAND ERROR
1007
+ ######################################################################
1008
+ # public
1009
+
1010
+ class CommandError < RuntimeError
1011
+
1012
+ attr_reader :reference, :name, :parameters, :real_error
1013
+
1014
+ def initialize(reference, command_name, parameters, real_error, codecs)
1015
+ @reference, @command_name, @parameters = reference, command_name, parameters
1016
+ @real_error, @codecs = real_error, codecs
1017
+ super()
1018
+ end
1019
+
1020
+ def to_s
1021
+ if @real_error.is_a?(AEM::EventError)
1022
+ err = "CommandError\n\t\tOSERROR: #{error_number}"
1023
+ err += "\n\t\tMESSAGE: #{error_message}" if error_message != ''
1024
+ [
1025
+ ["\n\t\tOFFENDING OBJECT", KAE::KOSAErrorOffendingObject],
1026
+ ["\n\t\tEXPECTED TYPE", KAE::KOSAErrorExpectedType],
1027
+ ["\n\t\tPARTIAL RESULT", KAE::KOSAErrorPartialResult],
1028
+ ].each do |label, key|
1029
+ desc = @real_error.raw[key]
1030
+ err += "#{label}: #{@codecs.unpack(desc).inspect}" if desc
1031
+ end
1032
+ else
1033
+ err = @real_error
1034
+ end
1035
+ return "#{err}\n\t\tCOMMAND: #{@reference}.#{@command_name}(#{(@parameters.collect { |item| item.inspect }).join(', ')})\n"
1036
+ end
1037
+
1038
+ def error_number
1039
+ if @real_error.is_a?(AE::MacOSError) or @real_error.is_a?(AEM::EventError)
1040
+ return @real_error.to_i
1041
+ else
1042
+ return -2700
1043
+ end
1044
+ end
1045
+
1046
+ alias_method :to_i, :error_number
1047
+
1048
+ def error_message
1049
+ return @real_error.message
1050
+ end
1051
+
1052
+ def offending_object
1053
+ return nil if not @real_error.is_a?(AEM::EventError)
1054
+ desc = @real_error.raw[KAE::KOSAErrorOffendingObject]
1055
+ return desc ? @codecs.unpack(desc) : nil
1056
+ end
1057
+
1058
+ def expected_type
1059
+ return nil if not @real_error.is_a?(AEM::EventError)
1060
+ desc = @real_error.raw[KAE::KOSAErrorExpectedType]
1061
+ return desc ? @codecs.unpack(desc) : nil
1062
+ end
1063
+
1064
+ def partial_result
1065
+ return nil if not @real_error.is_a?(AEM::EventError)
1066
+ desc = @real_error.raw[KAE::KOSAErrorPartialResult]
1067
+ return desc ? @codecs.unpack(desc) : nil
1068
+ end
1069
+ end
1070
+
1071
+ ApplicationNotFoundError = FindApp::ApplicationNotFoundError
1072
+ CantLaunchApplicationError = Connect::CantLaunchApplicationError
1073
+ end