rb-scpt 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +497 -0
  3. data/doc/aem-manual/01_introduction.html +60 -0
  4. data/doc/aem-manual/02_apioverview.html +107 -0
  5. data/doc/aem-manual/03_packingandunpackingdata.html +135 -0
  6. data/doc/aem-manual/04_references.html +409 -0
  7. data/doc/aem-manual/05_targetingapplications.html +164 -0
  8. data/doc/aem-manual/06_buildingandsendingevents.html +229 -0
  9. data/doc/aem-manual/07_findapp.html +63 -0
  10. data/doc/aem-manual/08_examples.html +94 -0
  11. data/doc/aem-manual/aemreferenceinheritance.gif +0 -0
  12. data/doc/aem-manual/index.html +56 -0
  13. data/doc/appscript-manual/01_introduction.html +94 -0
  14. data/doc/appscript-manual/02_aboutappscripting.html +247 -0
  15. data/doc/appscript-manual/03_quicktutorial.html +167 -0
  16. data/doc/appscript-manual/04_gettinghelp.html +188 -0
  17. data/doc/appscript-manual/05_keywordconversion.html +106 -0
  18. data/doc/appscript-manual/06_classesandenums.html +192 -0
  19. data/doc/appscript-manual/07_applicationobjects.html +211 -0
  20. data/doc/appscript-manual/08_realvsgenericreferences.html +96 -0
  21. data/doc/appscript-manual/09_referenceforms.html +241 -0
  22. data/doc/appscript-manual/10_referenceexamples.html +154 -0
  23. data/doc/appscript-manual/11_applicationcommands.html +245 -0
  24. data/doc/appscript-manual/12_commandexamples.html +138 -0
  25. data/doc/appscript-manual/13_performanceissues.html +142 -0
  26. data/doc/appscript-manual/14_notes.html +80 -0
  27. data/doc/appscript-manual/application_architecture.gif +0 -0
  28. data/doc/appscript-manual/application_architecture2.gif +0 -0
  29. data/doc/appscript-manual/finder_to_textedit_event.gif +0 -0
  30. data/doc/appscript-manual/index.html +62 -0
  31. data/doc/appscript-manual/relationships_example.gif +0 -0
  32. data/doc/appscript-manual/ruby_to_itunes_event.gif +0 -0
  33. data/doc/full.css +106 -0
  34. data/doc/index.html +45 -0
  35. data/doc/mactypes-manual/01_introduction.html +54 -0
  36. data/doc/mactypes-manual/02_aliasclass.html +124 -0
  37. data/doc/mactypes-manual/03_fileurlclass.html +126 -0
  38. data/doc/mactypes-manual/04_unitsclass.html +100 -0
  39. data/doc/mactypes-manual/index.html +53 -0
  40. data/doc/osax-manual/01_introduction.html +67 -0
  41. data/doc/osax-manual/02_interface.html +147 -0
  42. data/doc/osax-manual/03_examples.html +73 -0
  43. data/doc/osax-manual/04_notes.html +61 -0
  44. data/doc/osax-manual/index.html +53 -0
  45. data/doc/rb-appscript-logo.png +0 -0
  46. data/extconf.rb +65 -0
  47. data/rb-scpt.gemspec +14 -0
  48. data/sample/AB_export_vcard.rb +31 -0
  49. data/sample/AB_list_people_with_emails.rb +13 -0
  50. data/sample/Add_iCal_event.rb +21 -0
  51. data/sample/Create_daily_iCal_todos.rb +75 -0
  52. data/sample/Export_Address_Book_phone_numbers.rb +59 -0
  53. data/sample/Hello_world.rb +21 -0
  54. data/sample/List_iTunes_playlist_names.rb +11 -0
  55. data/sample/Make_Mail_message.rb +33 -0
  56. data/sample/Open_file_in_TextEdit.rb +13 -0
  57. data/sample/Organize_Mail_messages.rb +61 -0
  58. data/sample/Print_folder_tree.rb +16 -0
  59. data/sample/Select_all_HTML_files.rb +14 -0
  60. data/sample/Set_iChat_status.rb +24 -0
  61. data/sample/Simple_Finder_GUI_Scripting.rb +18 -0
  62. data/sample/Stagger_Finder_windows.rb +25 -0
  63. data/sample/TextEdit_demo.rb +130 -0
  64. data/sample/iTunes_top40_to_html.rb +71 -0
  65. data/src/SendThreadSafe.c +380 -0
  66. data/src/SendThreadSafe.h +139 -0
  67. data/src/lib/_aem/aemreference.rb +1022 -0
  68. data/src/lib/_aem/codecs.rb +662 -0
  69. data/src/lib/_aem/connect.rb +205 -0
  70. data/src/lib/_aem/encodingsupport.rb +77 -0
  71. data/src/lib/_aem/findapp.rb +85 -0
  72. data/src/lib/_aem/mactypes.rb +251 -0
  73. data/src/lib/_aem/send.rb +279 -0
  74. data/src/lib/_aem/typewrappers.rb +59 -0
  75. data/src/lib/_appscript/defaultterminology.rb +277 -0
  76. data/src/lib/_appscript/referencerenderer.rb +245 -0
  77. data/src/lib/_appscript/reservedkeywords.rb +116 -0
  78. data/src/lib/_appscript/safeobject.rb +249 -0
  79. data/src/lib/_appscript/terminology.rb +471 -0
  80. data/src/lib/aem.rb +253 -0
  81. data/src/lib/appscript.rb +1075 -0
  82. data/src/lib/kae.rb +1489 -0
  83. data/src/lib/osax.rb +659 -0
  84. data/src/rbae.c +979 -0
  85. data/test/README +3 -0
  86. data/test/test_aemreference.rb +118 -0
  87. data/test/test_appscriptcommands.rb +152 -0
  88. data/test/test_appscriptreference.rb +106 -0
  89. data/test/test_codecs.rb +186 -0
  90. data/test/test_findapp.rb +26 -0
  91. data/test/test_mactypes.rb +79 -0
  92. data/test/test_osax.rb +54 -0
  93. data/test/testall.sh +10 -0
  94. metadata +145 -0
@@ -0,0 +1,471 @@
1
+ #
2
+ # rb-appscript
3
+ #
4
+ # terminology -- retrieve and convert an application's terminology into lookup tables
5
+ #
6
+
7
+ ######################################################################
8
+ # TERMINOLOGY PARSER
9
+ ######################################################################
10
+
11
+ module TerminologyParser
12
+
13
+ require "ae"
14
+ require "kae"
15
+ require "_appscript/reservedkeywords" # names of all existing methods on ASReference::Application
16
+
17
+ class BigEndianParser
18
+ @@_name_cache = {}
19
+ LegalFirst = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_'
20
+ LegalRest = LegalFirst + '0123456789'
21
+ @@_reserved_keywords = {} # ersatz set
22
+ ReservedKeywords.each { |name| @@_reserved_keywords[name] = nil }
23
+
24
+ def initialize
25
+ # terminology tables; order is significant where synonym definitions occur
26
+ @commands = {}
27
+ @properties = []
28
+ @elements = []
29
+ @classes = []
30
+ @enumerators = []
31
+ # use ersatz sets to record previously found definitions, and avoid adding duplicates to lists
32
+ # (i.e. 'name+code not in <set>' is quicker than using 'name+code not in <list>')
33
+ @_found_properties = {} # set
34
+ @_found_elements = {} # set
35
+ @_found_classes = {} # set
36
+ @_found_enumerators = {} # set
37
+ # ideally, aetes should define both singular and plural names for each class, but
38
+ # some define only one or the other so we need to fill in any missing ones afterwards
39
+ @_spare_class_names = {} # names by code
40
+ @_found_class_codes = {} # set
41
+ @_found_element_codes = {} # set
42
+ end
43
+
44
+ def _integer
45
+ # Read a 2-byte integer.
46
+ @_ptr += 2
47
+ return @_str[@_ptr - 2, 2].unpack('S')[0]
48
+ end
49
+
50
+ def _word
51
+ # Read a 4-byte string (really a long, but represented as an 4-character 8-bit string for readability).
52
+ @_ptr += 4
53
+ return @_str[@_ptr - 4, 4] # big-endian
54
+ end
55
+
56
+ # Ruby 1.9 has changed the way that a character ordinal is obtained
57
+ maj, min, rev = RUBY_VERSION.split('.')
58
+ if (maj == '1' and min.to_i < 9) # is Ruby 1.8
59
+ def _length
60
+ return @_str[@_ptr]
61
+ end
62
+ else
63
+ def _length
64
+ return @_str[@_ptr].ord
65
+ end
66
+ end
67
+
68
+ def _name
69
+ # Read a MacRoman-encoded Pascal keyword string.
70
+ count = _length
71
+ @_ptr += 1 + count
72
+ s = @_str[@_ptr - count, count]
73
+ if not @@_name_cache.has_key?(s)
74
+ legal = LegalFirst
75
+ res = ''
76
+ s.split(//).each do |c|
77
+ if legal[c]
78
+ res += c
79
+ else
80
+ case c
81
+ when ' ', '-', '/'
82
+ res += '_'
83
+ when '&'
84
+ res += 'and'
85
+ else
86
+ if res == ''
87
+ res = '_'
88
+ end
89
+ res += "0x#{c.unpack('HXh')}"
90
+ end
91
+ end
92
+ legal = LegalRest
93
+ end
94
+ if res[0, 3] == 'AS_' or @@_reserved_keywords.has_key?(res) or res[0, 1] == '_'
95
+ res += '_'
96
+ end
97
+ @@_name_cache[s] = res
98
+ end
99
+ return @@_name_cache[s]
100
+ end
101
+
102
+ ##
103
+
104
+ def parse_command
105
+ name = _name
106
+ @_ptr += 1 + _length # description string
107
+ @_ptr += @_ptr & 1 # align
108
+ code = _word + _word # event class + event id
109
+ # skip result
110
+ @_ptr += 4 # datatype word
111
+ @_ptr += 1 + _length # description string
112
+ @_ptr += @_ptr & 1 # align
113
+ @_ptr += 2 # flags integer
114
+ # skip direct parameter
115
+ @_ptr += 4 # datatype word
116
+ @_ptr += 1 + _length # description string
117
+ @_ptr += @_ptr & 1 # align
118
+ @_ptr += 2 # flags integer
119
+ #
120
+ current_command_args = []
121
+ # Note: overlapping command definitions (e.g. InDesign) should be processed as follows:
122
+ # - If their names and codes are the same, only the last definition is used; other definitions are ignored and will not compile.
123
+ # - 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.
124
+ # - If a dictionary-defined command has the same name but different code to a built-in definition, escape its name so it doesn't conflict with the default built-in definition.
125
+ if not @commands.has_key?(name) or @commands[name][1] == code
126
+ @commands[name] = [name, code, current_command_args]
127
+ end
128
+ # add labelled parameters
129
+ _integer.times do
130
+ parameter_name = _name
131
+ @_ptr += @_ptr & 1 # align
132
+ parameter_code = _word
133
+ @_ptr += 4 # datatype word
134
+ @_ptr += 1 + _length # description string
135
+ @_ptr += @_ptr & 1 # align
136
+ @_ptr += 2 # flags integer
137
+ current_command_args.push([parameter_name, parameter_code])
138
+ end
139
+ end
140
+
141
+ def parse_class
142
+ name = _name
143
+ @_ptr += @_ptr & 1 # align
144
+ code = _word
145
+ @_ptr += 1 + _length # description string
146
+ @_ptr += @_ptr & 1 # align
147
+ is_plural = false
148
+ _integer.times do # properties
149
+ propname = _name
150
+ @_ptr += @_ptr & 1 # align
151
+ propcode = _word
152
+ @_ptr += 4 # datatype word
153
+ @_ptr += 1 + _length # description string
154
+ @_ptr += @_ptr & 1 # align
155
+ flags = _integer
156
+ if propcode != 'c@#^' # not a superclass definition (see kAEInheritedProperties)
157
+ if flags & 1 == 1 # indicates class name is plural (see kAESpecialClassProperties)
158
+ is_plural = true
159
+ elsif not @_found_properties.has_key?(propname + propcode)
160
+ @properties.push([propname, propcode]) # preserve ordering
161
+ @_found_properties[propname + propcode] = nil # add to found set
162
+ end
163
+ end
164
+ end
165
+ _integer.times do # skip elements
166
+ @_ptr += 4 # code word
167
+ count = _integer
168
+ @_ptr += 4 * count # reference forms
169
+ end
170
+ if is_plural
171
+ if not @_found_elements.has_key?(name + code)
172
+ @elements.push([name, code])
173
+ @_found_elements[name + code] = nil # add to found set
174
+ @_found_element_codes[code] = nil # add to found set
175
+ end
176
+ else
177
+ if not @_found_classes.has_key?(name + code)
178
+ @classes.push([name, code])
179
+ @_found_classes[name + code] = nil # add to found set
180
+ @_found_class_codes[code] = nil # add to found set
181
+ end
182
+ end
183
+ @_spare_class_names[code] = name
184
+ end
185
+
186
+ def parse_comparison # comparison info isn't used
187
+ @_ptr += 1 + _length # name string
188
+ @_ptr += @_ptr & 1 # align
189
+ @_ptr += 4 # code word
190
+ @_ptr += 1 + _length # description string
191
+ @_ptr += @_ptr & 1 # align
192
+ end
193
+
194
+ def parse_enumeration
195
+ @_ptr += 4 # code word
196
+ _integer.times do # enumerators
197
+ name = _name
198
+ @_ptr += @_ptr & 1 # align
199
+ code = _word
200
+ @_ptr += 1 + _length # description string
201
+ @_ptr += @_ptr & 1 # align
202
+ if not @_found_enumerators.has_key?(name + code)
203
+ @enumerators.push([name, code])
204
+ @_found_enumerators[name + code] = nil # add to found set
205
+ end
206
+ end
207
+ end
208
+
209
+ def parse_suite
210
+ @_ptr += 1 + _length # name string
211
+ @_ptr += 1 + _length # description string
212
+ @_ptr += @_ptr & 1 # align
213
+ @_ptr += 4 # code word
214
+ @_ptr += 4 # level, version integers
215
+ _integer.times { parse_command }
216
+ _integer.times { parse_class }
217
+ _integer.times { parse_comparison }
218
+ _integer.times { parse_enumeration }
219
+ end
220
+
221
+ def parse(aetes)
222
+ aetes.each do |aete|
223
+ if aete.is_a?(AE::AEDesc) and aete.type == KAE::TypeAETE and aete.data != ''
224
+ @_str = aete.data
225
+ @_ptr = 6 # version, language, script integers
226
+ _integer.times { parse_suite }
227
+ end
228
+ end
229
+ # singular names are normally used in the classes table and plural names in the elements table. However, if an aete defines a singular name but not a plural name then the missing plural name is substituted with the singular name; and vice-versa if there's no singular equivalent for a plural name.
230
+ missing_elements = @_found_class_codes.keys - @_found_element_codes.keys
231
+ missing_classes = @_found_element_codes.keys - @_found_class_codes.keys
232
+ missing_elements.each do |code|
233
+ @elements.push([@_spare_class_names[code], code])
234
+ end
235
+ missing_classes.each do |code|
236
+ @classes.push([@_spare_class_names[code], code])
237
+ end
238
+ return [@classes, @enumerators, @properties, @elements, @commands.values]
239
+ end
240
+ end
241
+
242
+
243
+ class LittleEndianParser < BigEndianParser
244
+ def _word
245
+ # Read a 4-byte string (really a long, but represented as an 4-character 8-bit string for readability).
246
+ return super.reverse # little-endian
247
+ end
248
+ end
249
+
250
+
251
+ #######
252
+ # Public
253
+
254
+ def TerminologyParser.build_tables_for_aetes(aetes)
255
+ if [1].pack('S') == "\000\001" # is it big-endian?
256
+ return BigEndianParser.new.parse(aetes)
257
+ else
258
+ return LittleEndianParser.new.parse(aetes)
259
+ end
260
+ end
261
+
262
+ end
263
+
264
+
265
+ ######################################################################
266
+ # TERMINOLOGY TABLES BUILDER
267
+ ######################################################################
268
+
269
+ module Terminology
270
+
271
+ require "aem"
272
+ require "_appscript/defaultterminology"
273
+
274
+ @@_terminology_cache = {}
275
+
276
+ def Terminology._make_type_table(classes, enums, properties)
277
+ # builds tables used for converting symbols to/from AEType, AEEnums
278
+ type_by_code = DefaultTerminology::TypeByCode.clone
279
+ type_by_name = DefaultTerminology::TypeByName.clone
280
+ [[AEM::AEType, properties], [AEM::AEEnum, enums], [AEM::AEType, classes]].each do |klass, table|
281
+ table.each_with_index do |item, i|
282
+ name, code = item
283
+ # If an application-defined name overlaps an existing type name but has a different code, append '_' to avoid collision:
284
+ name += '_' if DefaultTerminology::TypeCodeByName.fetch(name, code) != code
285
+ begin
286
+ type_by_code[code] = name.intern # to handle synonyms, if same code appears more than once then use name from last definition in list
287
+ rescue ArgumentError # ignore #intern error if name is empty string
288
+ end
289
+ name, code = table[-i - 1]
290
+ name += '_' if DefaultTerminology::TypeCodeByName.fetch(name, code) != code
291
+ begin
292
+ type_by_name[name.intern] = klass.new(code) # to handle synonyms, if same name appears more than once then use code from first definition in list
293
+ rescue ArgumentError # ignore #intern error if name is empty string
294
+ end
295
+ end
296
+ end
297
+ return [type_by_code, type_by_name]
298
+ end
299
+
300
+ def Terminology._make_reference_table(properties, elements, commands)
301
+ # builds tables used for constructing references and commands
302
+ reference_by_code = DefaultTerminology::ReferenceByCode.clone
303
+ reference_by_name = DefaultTerminology::ReferenceByName.clone
304
+ [[:element, elements, 'e'], [:property, properties, 'p']].each do |kind, table, prefix|
305
+ # note: if property and element names are same (e.g. 'file' in BBEdit), will pack as property specifier unless it's a special case (i.e. see :text below). Note that there is currently no way to override this, i.e. to force appscript to pack it as an all-elements specifier instead (in AS, this would be done by prepending the 'every' keyword), so clients would need to use aem for that (but could add an 'all' method to Reference class if there was demand for a built-in workaround)
306
+ table.each_with_index do |item, i|
307
+ name, code = item
308
+ # If an application-defined name overlaps an existing type name but has a different code, append '_' to avoid collision:
309
+ name += '_' if DefaultTerminology::TypeCodeByName.fetch(name, code) != code
310
+ reference_by_code[prefix + code] = name # to handle synonyms, if same code appears more than once then use name from last definition in list
311
+ name, code = table[-i - 1]
312
+ name += '_' if DefaultTerminology::TypeCodeByName.fetch(name, code) != code
313
+ begin
314
+ reference_by_name[name.intern] = [kind, code] # to handle synonyms, if same name appears more than once then use code from first definition in list
315
+ rescue ArgumentError # ignore #intern error if name is empty string
316
+ end
317
+ end
318
+ end
319
+ if reference_by_name.has_key?(:text) # special case: AppleScript always packs 'text of...' as all-elements specifier
320
+ reference_by_name[:text][0] = :element
321
+ end
322
+ commands.reverse.each do |name, code, args| # to handle synonyms, if two commands have same name but different codes, only the first definition should be used (iterating over the commands list in reverse ensures this)
323
+ # Avoid collisions between default commands and application-defined commands with same name but different code (e.g. 'get' and 'set' in InDesign CS2):
324
+ name += '_' if DefaultTerminology::CommandCodeByName.fetch(name, code) != code
325
+ dct = {}
326
+ args.each do |arg_name, arg_code|
327
+ begin
328
+ dct[arg_name.intern] = arg_code
329
+ rescue ArgumentError # ignore #intern error if name is empty string
330
+ end
331
+ end
332
+ begin
333
+ reference_by_name[name.intern] = [:command, [code, dct]]
334
+ rescue ArgumentError # ignore #intern error if name is empty string
335
+ end
336
+ end
337
+ return reference_by_code, reference_by_name
338
+ end
339
+
340
+ def Terminology.dump_tables(tables, module_name, source_path, out_path)
341
+ # Parse aete(s) into intermediate tables, suitable for use by Terminology#tables_for_module
342
+ if not(/^[A-Z][A-Za-z0-9_]*$/ === module_name)
343
+ raise RuntimeError, "Invalid module name."
344
+ end
345
+ # Write module
346
+ File.open(out_path, "w") do |f|
347
+ f.puts "module #{module_name}"
348
+ f.puts "\tVersion = 1.1"
349
+ f.puts "\tPath = #{source_path.inspect}"
350
+ f.puts
351
+ (["Classes", "Enumerators", "Properties", "Elements"].zip(tables[0,4])).each do |name, table|
352
+ f.puts "\t#{name} = ["
353
+ table.sort.each do |item|
354
+ f.puts "\t\t#{item.inspect},"
355
+ end
356
+ f.puts "\t]"
357
+ f.puts
358
+ end
359
+ f.puts "\tCommands = ["
360
+ tables[4].sort.each do |name, code, params|
361
+ f.puts "\t\t[#{name.inspect}, #{code.inspect}, ["
362
+ params.each do |item|
363
+ f.puts "\t\t\t#{item.inspect},"
364
+ end
365
+ f.puts "\t\t]],"
366
+ end
367
+ f.puts "\t]"
368
+ f.puts "end"
369
+ end
370
+ end
371
+
372
+ #######
373
+ # public
374
+
375
+ def Terminology.default_tables
376
+ # [typebycode, typebyname, referencebycode, referencebyname]
377
+ return _make_type_table([], [], []) + _make_reference_table([], [], [])
378
+ end
379
+
380
+ def Terminology.tables_for_aetes(aetes)
381
+ # Build terminology tables from a list of unpacked aete byte strings.
382
+ # Result : list of hash -- [typebycode, typebyname, referencebycode, referencebyname]
383
+ aetes = aetes.reject { |aete| not(aete.is_a?(AE::AEDesc) and aete.type == KAE::TypeAETE and aete.data != '') }
384
+ classes, enums, properties, elements, commands = TerminologyParser.build_tables_for_aetes(aetes)
385
+ return _make_type_table(classes, enums, properties) + _make_reference_table(properties, elements, commands)
386
+ end
387
+
388
+ ##
389
+
390
+ def Terminology.tables_for_module(terms)
391
+ # Build terminology tables from a dumped terminology module.
392
+ # Result : list of hash -- [typebycode, typebyname, referencebycode, referencebyname]
393
+ if terms::Version != 1.1
394
+ raise RuntimeError, "Unsupported terminology module version: #{terms::Version} (requires version 1.1)."
395
+ end
396
+ return _make_type_table(terms::Classes, terms::Enumerators, terms::Properties) \
397
+ + _make_reference_table(terms::Properties, terms::Elements, terms::Commands)
398
+ end
399
+
400
+ def Terminology.tables_for_parsed_sdef(terms)
401
+ # Build terminology tables from an SdefParser instance.
402
+ # Result : list of hash -- [typebycode, typebyname, referencebycode, referencebyname]
403
+ return _make_type_table(terms.classes, terms.enumerators, terms.properties) \
404
+ + _make_reference_table(terms.properties, terms.elements, terms.commands)
405
+ end
406
+
407
+ def Terminology.aetes_for_app(aem_app)
408
+ begin
409
+ begin
410
+ aetes = aem_app.event('ascrgdte', {'----' => 0}).send(120 * 60)
411
+ rescue AEM::EventError => e
412
+ if e.number == -192 # aete resource not found
413
+ aetes = []
414
+ else
415
+ raise
416
+ end
417
+ end
418
+ rescue => err
419
+ raise RuntimeError, "Can't get terminology for application (#{aem_app}): #{err}"
420
+ end
421
+ aetes = [aetes] if not aetes.is_a?(Array)
422
+ return aetes
423
+ end
424
+
425
+ def Terminology.tables_for_app(aem_app)
426
+ # Build terminology tables for an application.
427
+ # app : AEM::Application
428
+ # Result : list of hash -- [typebycode, typebyname, referencebycode, referencebyname]
429
+ if not @@_terminology_cache.has_key?(aem_app.identity)
430
+ aetes = Terminology.aetes_for_app(aem_app)
431
+ @@_terminology_cache[aem_app.identity] = Terminology.tables_for_aetes(aetes)
432
+ end
433
+ return @@_terminology_cache[aem_app.identity]
434
+ end
435
+
436
+ #######
437
+ # public
438
+
439
+ def Terminology.dump(app_name, module_name, out_path)
440
+ # Export application terminology tables as a Ruby module
441
+ # app_path : string -- name or path of application
442
+ # module_name : string -- name of generated module (must be a valid Ruby constant)
443
+ # out_path : string -- module file to write
444
+ #
445
+ # Generates a Ruby module containing an application's basic terminology
446
+ # (names and codes) as used by appscript.
447
+ #
448
+ # Call the #dump method to dump faulty aetes to Ruby module, e.g.:
449
+ #
450
+ # Terminology.dump('MyApp', 'MyAppGlue', '/path/to/ruby/modules/myappglue.rb')
451
+ #
452
+ # Patch any errors by hand, then import the patched module into your script
453
+ # and pass it to appscript's app() constructor via its 'terms' argument, e.g.:
454
+ #
455
+ # require 'appscript'; include Appscript
456
+ # require 'myappglue'
457
+ #
458
+ # myapp = app('MyApp', terms => MyAppGlue)
459
+ #
460
+ # Note that dumped terminologies aren't used by appscript's built-in help system.
461
+ #
462
+ app_path = FindApp.by_name(app_name)
463
+ # Get aete(s)
464
+ aetes = Terminology.aetes_for_app(Application.by_path(app_path))
465
+ aetes.delete_if { |aete| not(aete.is_a?(AE::AEDesc) and aete.type == KAE::TypeAETE) }
466
+ tables = TerminologyParser.build_tables_for_aetes(aetes)
467
+ Terminology.dump_tables(tables, module_name, app_path, out_path)
468
+ end
469
+
470
+ end
471
+