rb-appscript 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +59 -0
- data/LICENSE +65 -0
- data/README +1 -1
- data/TODO +13 -13
- data/doc/aem-manual/04_references.html +13 -13
- data/doc/aem-manual/05_targettingapplications.html +7 -5
- data/doc/aem-manual/06_buildingandsendingevents.html +1 -1
- data/doc/aem-manual/08_examples.html +6 -6
- data/doc/aem-manual/index.html +3 -4
- data/doc/appscript-manual/02_aboutappscripting.html +2 -10
- data/doc/appscript-manual/04_gettinghelp.html +32 -18
- data/doc/appscript-manual/05_keywordconversion.html +7 -7
- data/doc/appscript-manual/06_classesandenums.html +2 -21
- data/doc/appscript-manual/07_applicationobjects.html +11 -2
- data/doc/appscript-manual/08_realvsgenericreferences.html +1 -1
- data/doc/appscript-manual/09_referenceforms.html +13 -13
- data/doc/appscript-manual/10_referenceexamples.html +7 -7
- data/doc/appscript-manual/11_applicationcommands.html +30 -28
- data/doc/appscript-manual/13_performanceissues.html +3 -3
- data/doc/appscript-manual/{15_notes.html → 14_notes.html} +18 -13
- data/doc/appscript-manual/full.css +1 -2
- data/doc/appscript-manual/index.html +3 -4
- data/doc/index.html +2 -1
- data/doc/mactypes-manual/index.html +23 -13
- data/doc/osax-manual/index.html +27 -5
- data/rb-appscript.gemspec +1 -1
- data/sample/AB_list_people_with_emails.rb +2 -1
- data/sample/Add_iCal_event.rb +18 -0
- data/sample/Export_Address_Book_phone_numbers.rb +56 -0
- data/sample/Hello_world.rb +9 -1
- data/sample/Select_all_HTML_files.rb +4 -2
- data/sample/iTunes_top40_to_html.rb +7 -4
- data/src/lib/_aem/aemreference.rb +50 -51
- data/src/lib/_aem/codecs.rb +148 -178
- data/src/lib/_aem/connect.rb +0 -2
- data/src/lib/_aem/findapp.rb +1 -1
- data/src/lib/_aem/mactypes.rb +2 -9
- data/src/lib/_aem/send.rb +2 -2
- data/src/lib/_appscript/defaultterminology.rb +2 -2
- data/src/lib/_appscript/referencerenderer.rb +119 -14
- data/src/lib/_appscript/reservedkeywords.rb +5 -0
- data/src/lib/_appscript/safeobject.rb +190 -0
- data/src/lib/_appscript/terminology.rb +195 -90
- data/src/lib/aem.rb +8 -9
- data/src/lib/appscript.rb +175 -159
- data/src/lib/osax.rb +65 -29
- data/src/rbae.c +42 -2
- data/test/test_aemreference.rb +3 -3
- data/test/test_appscriptcommands.rb +135 -0
- data/test/test_appscriptreference.rb +10 -8
- data/test/test_mactypes.rb +7 -1
- data/test/test_osax.rb +57 -0
- data/test/testall.sh +2 -1
- metadata +10 -9
- data/doc/aem-manual/09_notes.html +0 -41
- data/doc/appscript-manual/14_problemapps.html +0 -192
- data/misc/adobeunittypes.rb +0 -14
- data/misc/dump.rb +0 -72
- data/rb-appscript-0.2.0.gem +0 -0
@@ -14,26 +14,43 @@ module TerminologyParser
|
|
14
14
|
@@_name_cache = {}
|
15
15
|
LegalFirst = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_'
|
16
16
|
LegalRest = LegalFirst + '0123456789'
|
17
|
+
@@_reserved_keywords = {} # ersatz set
|
18
|
+
ReservedKeywords.each { |name| @@_reserved_keywords[name] = nil }
|
17
19
|
|
18
20
|
def initialize
|
19
|
-
|
20
|
-
@properties = {}
|
21
|
+
# terminology tables; order is significant where synonym definitions occur
|
21
22
|
@commands = {}
|
22
|
-
@
|
23
|
-
@
|
23
|
+
@properties = []
|
24
|
+
@elements = []
|
25
|
+
@classes = []
|
26
|
+
@enumerators = []
|
27
|
+
# use ersatz sets to record previously found definitions, and avoid adding duplicates to lists
|
28
|
+
# (i.e. 'name+code not in <set>' is quicker than using 'name+code not in <list>')
|
29
|
+
@_found_properties = {} # set
|
30
|
+
@_found_elements = {} # set
|
31
|
+
@_found_classes = {} # set
|
32
|
+
@_found_enumerators = {} # set
|
33
|
+
# ideally, aetes should define both singular and plural names for each class, but
|
34
|
+
# some define only one or the other so we need to fill in any missing ones afterwards
|
35
|
+
@_spare_class_names = {} # names by code
|
36
|
+
@_found_class_codes = {} # set
|
37
|
+
@_found_element_codes = {} # set
|
24
38
|
end
|
25
39
|
|
26
40
|
def _integer
|
41
|
+
# Read a 2-byte integer.
|
27
42
|
@_ptr += 2
|
28
43
|
return @_str[@_ptr - 2, 2].unpack('S')[0]
|
29
44
|
end
|
30
45
|
|
31
46
|
def _word
|
47
|
+
# Read a 4-byte string (really a long, but represented as an 4-character 8-bit string for readability).
|
32
48
|
@_ptr += 4
|
33
|
-
return @_str[@_ptr - 4, 4]
|
49
|
+
return @_str[@_ptr - 4, 4] # big-endian
|
34
50
|
end
|
35
51
|
|
36
52
|
def _name
|
53
|
+
# Read a MacRoman-encoded Pascal keyword string.
|
37
54
|
count = @_str[@_ptr]
|
38
55
|
@_ptr += 1 + count
|
39
56
|
s = @_str[@_ptr - count, count]
|
@@ -58,7 +75,7 @@ module TerminologyParser
|
|
58
75
|
end
|
59
76
|
legal = LegalRest
|
60
77
|
end
|
61
|
-
if res[0, 3] == 'AS_' or
|
78
|
+
if res[0, 3] == 'AS_' or @@_reserved_keywords.has_key?(res) or res[0, 1] == '_'
|
62
79
|
res += '_'
|
63
80
|
end
|
64
81
|
@@_name_cache[s] = res
|
@@ -70,96 +87,115 @@ module TerminologyParser
|
|
70
87
|
|
71
88
|
def parse_command
|
72
89
|
name = _name
|
73
|
-
@_ptr += 1 + @_str[@_ptr]
|
74
|
-
@_ptr += @_ptr & 1
|
75
|
-
code = _word + _word
|
90
|
+
@_ptr += 1 + @_str[@_ptr] # description string
|
91
|
+
@_ptr += @_ptr & 1 # align
|
92
|
+
code = _word + _word # event class + event id
|
76
93
|
# skip result
|
77
|
-
@_ptr += 4
|
78
|
-
@_ptr += 1 + @_str[@_ptr]
|
79
|
-
@_ptr += @_ptr & 1
|
80
|
-
@_ptr += 2
|
81
|
-
# skip direct
|
82
|
-
@_ptr += 4
|
83
|
-
@_ptr += 1 + @_str[@_ptr]
|
84
|
-
@_ptr += @_ptr & 1
|
85
|
-
@_ptr += 2
|
94
|
+
@_ptr += 4 # datatype word
|
95
|
+
@_ptr += 1 + @_str[@_ptr] # description string
|
96
|
+
@_ptr += @_ptr & 1 # align
|
97
|
+
@_ptr += 2 # flags integer
|
98
|
+
# skip direct parameter
|
99
|
+
@_ptr += 4 # datatype word
|
100
|
+
@_ptr += 1 + @_str[@_ptr] # description string
|
101
|
+
@_ptr += @_ptr & 1 # align
|
102
|
+
@_ptr += 2 # flags integer
|
86
103
|
#
|
87
104
|
current_command_args = []
|
88
|
-
|
89
|
-
#
|
105
|
+
# Note: overlapping command definitions (e.g. InDesign) should be processed as follows:
|
106
|
+
# - If their names and codes are the same, only the last definition is used; other definitions are ignored and will not compile.
|
107
|
+
# - 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.
|
108
|
+
# - 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.
|
109
|
+
if not @commands.has_key?(name) or @commands[name][1] == code
|
110
|
+
@commands[name] = [name, code, current_command_args]
|
111
|
+
end
|
112
|
+
# add labelled parameters
|
90
113
|
_integer.times do
|
91
114
|
parameter_name = _name
|
92
|
-
@_ptr += @_ptr & 1
|
115
|
+
@_ptr += @_ptr & 1 # align
|
93
116
|
parameter_code = _word
|
94
|
-
@_ptr += 4
|
95
|
-
@_ptr += 1 + @_str[@_ptr]
|
96
|
-
@_ptr += @_ptr & 1
|
97
|
-
@_ptr += 2
|
117
|
+
@_ptr += 4 # datatype word
|
118
|
+
@_ptr += 1 + @_str[@_ptr] # description string
|
119
|
+
@_ptr += @_ptr & 1 # align
|
120
|
+
@_ptr += 2 # flags integer
|
98
121
|
current_command_args.push([parameter_name, parameter_code])
|
99
122
|
end
|
100
123
|
end
|
101
124
|
|
102
125
|
def parse_class
|
103
126
|
name = _name
|
104
|
-
@_ptr += @_ptr & 1
|
127
|
+
@_ptr += @_ptr & 1 # align
|
105
128
|
code = _word
|
106
|
-
@_ptr += 1 + @_str[@_ptr]
|
107
|
-
@_ptr += @_ptr & 1
|
129
|
+
@_ptr += 1 + @_str[@_ptr] # description string
|
130
|
+
@_ptr += @_ptr & 1 # align
|
108
131
|
is_plural = false
|
109
|
-
_integer.times do
|
132
|
+
_integer.times do # properties
|
110
133
|
propname = _name
|
111
|
-
@_ptr += @_ptr & 1
|
134
|
+
@_ptr += @_ptr & 1 # align
|
112
135
|
propcode = _word
|
113
|
-
@_ptr += 4
|
114
|
-
@_ptr += 1 + @_str[@_ptr]
|
115
|
-
@_ptr += @_ptr & 1
|
136
|
+
@_ptr += 4 # datatype word
|
137
|
+
@_ptr += 1 + @_str[@_ptr] # description string
|
138
|
+
@_ptr += @_ptr & 1 # align
|
116
139
|
flags = _integer
|
117
|
-
if propcode != 'c@#^'
|
118
|
-
if flags & 1 == 1
|
140
|
+
if propcode != 'c@#^' # not a superclass definition (see kAEInheritedProperties)
|
141
|
+
if flags & 1 == 1 # indicates class name is plural (see kAESpecialClassProperties)
|
119
142
|
is_plural = true
|
120
|
-
|
121
|
-
@properties[propcode]
|
143
|
+
elsif not @_found_properties.has_key?(propname + propcode)
|
144
|
+
@properties.push([propname, propcode]) # preserve ordering
|
145
|
+
@_found_properties[propname + propcode] = nil # add to found set
|
122
146
|
end
|
123
147
|
end
|
124
148
|
end
|
125
|
-
_integer.times do
|
126
|
-
@_ptr += 4
|
149
|
+
_integer.times do # skip elements
|
150
|
+
@_ptr += 4 # code word
|
127
151
|
count = _integer
|
128
|
-
@_ptr += 4 * count
|
152
|
+
@_ptr += 4 * count # reference forms
|
129
153
|
end
|
130
154
|
if is_plural
|
131
|
-
|
155
|
+
if not @_found_elements.has_key?(name + code)
|
156
|
+
@elements.push([name, code])
|
157
|
+
@_found_elements[name + code] = nil # add to found set
|
158
|
+
@_found_element_codes[code] = nil # add to found set
|
159
|
+
end
|
132
160
|
else
|
133
|
-
|
161
|
+
if not @_found_classes.has_key?(name + code)
|
162
|
+
@classes.push([name, code])
|
163
|
+
@_found_classes[name + code] = nil # add to found set
|
164
|
+
@_found_class_codes[code] = nil # add to found set
|
165
|
+
end
|
134
166
|
end
|
167
|
+
@_spare_class_names[code] = name
|
135
168
|
end
|
136
169
|
|
137
|
-
def parse_comparison
|
138
|
-
@_ptr += 1 + @_str[@_ptr]
|
139
|
-
@_ptr += @_ptr & 1
|
140
|
-
@_ptr += 4
|
141
|
-
@_ptr += 1 + @_str[@_ptr]
|
142
|
-
@_ptr += @_ptr & 1
|
170
|
+
def parse_comparison # comparison info isn't used
|
171
|
+
@_ptr += 1 + @_str[@_ptr] # name string
|
172
|
+
@_ptr += @_ptr & 1 # align
|
173
|
+
@_ptr += 4 # code word
|
174
|
+
@_ptr += 1 + @_str[@_ptr] # description string
|
175
|
+
@_ptr += @_ptr & 1 # align
|
143
176
|
end
|
144
177
|
|
145
178
|
def parse_enumeration
|
146
|
-
@_ptr += 4
|
147
|
-
_integer.times do
|
179
|
+
@_ptr += 4 # code word
|
180
|
+
_integer.times do # enumerators
|
148
181
|
name = _name
|
149
|
-
@_ptr += @_ptr & 1
|
182
|
+
@_ptr += @_ptr & 1 # align
|
150
183
|
code = _word
|
151
|
-
@_ptr += 1 + @_str[@_ptr]
|
152
|
-
@_ptr += @_ptr & 1
|
153
|
-
|
184
|
+
@_ptr += 1 + @_str[@_ptr] # description string
|
185
|
+
@_ptr += @_ptr & 1 # align
|
186
|
+
if not @_found_enumerators.has_key?(name + code)
|
187
|
+
@enumerators.push([name, code])
|
188
|
+
@_found_enumerators[name + code] = nil # add to found set
|
189
|
+
end
|
154
190
|
end
|
155
191
|
end
|
156
192
|
|
157
193
|
def parse_suite
|
158
|
-
@_ptr += 1 + @_str[@_ptr]
|
159
|
-
@_ptr += 1 + @_str[@_ptr]
|
160
|
-
@_ptr += @_ptr & 1
|
161
|
-
@_ptr += 4
|
162
|
-
@_ptr += 4
|
194
|
+
@_ptr += 1 + @_str[@_ptr] # name string
|
195
|
+
@_ptr += 1 + @_str[@_ptr] # description string
|
196
|
+
@_ptr += @_ptr & 1 # align
|
197
|
+
@_ptr += 4 # code word
|
198
|
+
@_ptr += 4 # level, version integers
|
163
199
|
_integer.times { parse_command }
|
164
200
|
_integer.times { parse_class }
|
165
201
|
_integer.times { parse_comparison }
|
@@ -169,24 +205,30 @@ module TerminologyParser
|
|
169
205
|
def parse(aetes)
|
170
206
|
aetes.each do |aete|
|
171
207
|
@_str = aete.data
|
172
|
-
@_ptr = 6
|
208
|
+
@_ptr = 6 # version, language, script integers
|
173
209
|
_integer.times { parse_suite }
|
174
210
|
if not @_ptr == @_str.length
|
175
211
|
raise RuntimeError, "aete was not fully parsed."
|
176
212
|
end
|
177
213
|
end
|
178
|
-
classes
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
214
|
+
# 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.
|
215
|
+
missing_elements = @_found_class_codes.keys - @_found_element_codes.keys
|
216
|
+
missing_classes = @_found_element_codes.keys - @_found_class_codes.keys
|
217
|
+
missing_elements.each do |code|
|
218
|
+
@elements.push([@_spare_class_names[code], code])
|
219
|
+
end
|
220
|
+
missing_classes.each do |code|
|
221
|
+
@classes.push([@_spare_class_names[code], code])
|
222
|
+
end
|
223
|
+
return [@classes, @enumerators, @properties, @elements, @commands.values]
|
183
224
|
end
|
184
225
|
end
|
185
226
|
|
186
227
|
|
187
228
|
class LittleEndianParser < BigEndianParser
|
188
229
|
def _word
|
189
|
-
|
230
|
+
# Read a 4-byte string (really a long, but represented as an 4-character 8-bit string for readability).
|
231
|
+
return super.reverse # little-endian
|
190
232
|
end
|
191
233
|
end
|
192
234
|
|
@@ -195,7 +237,7 @@ module TerminologyParser
|
|
195
237
|
# Public
|
196
238
|
|
197
239
|
def TerminologyParser.build_tables_for_aetes(aetes)
|
198
|
-
if [1].pack('S') == "\000\001"
|
240
|
+
if [1].pack('S') == "\000\001" # is it big-endian?
|
199
241
|
return BigEndianParser.new.parse(aetes)
|
200
242
|
else
|
201
243
|
return LittleEndianParser.new.parse(aetes)
|
@@ -217,35 +259,47 @@ module Terminology
|
|
217
259
|
@@_terminology_cache = {}
|
218
260
|
|
219
261
|
def Terminology._make_type_table(classes, enums, properties)
|
262
|
+
# builds tables used for converting symbols to/from AEType, AEEnums
|
220
263
|
type_by_code = DefaultTerminology::TypeByCode.clone
|
221
264
|
type_by_name = DefaultTerminology::TypeByName.clone
|
222
265
|
[[AEM::AEType, properties], [AEM::AEEnum, enums], [AEM::AEType, classes]].each do |klass, table|
|
223
|
-
table.
|
266
|
+
table.each_with_index do |item, i|
|
267
|
+
name, code = item
|
268
|
+
# If an application-defined name overlaps an existing type name but has a different code, append '_' to avoid collision:
|
269
|
+
if DefaultTerminology::TypeByName.has_key?(name) and \
|
270
|
+
DefaultTerminology::TypeByName[name].code != code
|
271
|
+
name += '_'
|
272
|
+
end
|
273
|
+
type_by_code[code] = name.intern # to handle synonyms, if same code appears more than once then use name from last definition in list
|
274
|
+
name, code = table[-i - 1]
|
224
275
|
if DefaultTerminology::TypeByName.has_key?(name) and \
|
225
276
|
DefaultTerminology::TypeByName[name].code != code
|
226
277
|
name += '_'
|
227
278
|
end
|
228
|
-
|
229
|
-
type_by_name[name.intern] = klass.new(code)
|
279
|
+
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
|
230
280
|
end
|
231
281
|
end
|
232
282
|
return [type_by_code, type_by_name]
|
233
283
|
end
|
234
284
|
|
235
285
|
def Terminology._make_reference_table(properties, elements, commands)
|
286
|
+
# builds tables used for constructing references and commands
|
236
287
|
reference_by_code = DefaultTerminology::ReferenceByCode.clone
|
237
288
|
reference_by_name = DefaultTerminology::ReferenceByName.clone
|
238
289
|
[[:element, elements, 'e'], [:property, properties, 'p']].each do |kind, table, prefix|
|
239
290
|
# 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)
|
240
|
-
table.
|
241
|
-
|
242
|
-
|
291
|
+
table.each_with_index do |item, i|
|
292
|
+
name, code = item
|
293
|
+
reference_by_code[prefix + code] = name # to handle synonyms, if same code appears more than once then use name from last definition in list
|
294
|
+
name, code = table[-i - 1]
|
295
|
+
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
|
243
296
|
end
|
244
297
|
end
|
245
298
|
if reference_by_name.has_key?(:text) # special case: AppleScript always packs 'text of...' as all-elements specifier
|
246
299
|
reference_by_name[:text][0] = :element
|
247
300
|
end
|
248
|
-
commands.reverse.each do |name, code, args|
|
301
|
+
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)
|
302
|
+
# Avoid collisions between default commands and application-defined commands with same name but different code (e.g. 'get' and 'set' in InDesign CS2):
|
249
303
|
if DefaultTerminology::DefaultCommands.has_key?(name) and \
|
250
304
|
code != DefaultTerminology::DefaultCommands[name]
|
251
305
|
name += '_'
|
@@ -261,17 +315,22 @@ module Terminology
|
|
261
315
|
# public
|
262
316
|
|
263
317
|
def Terminology.default_tables
|
318
|
+
# [typebycode, typebyname, referencebycode, referencebyname]
|
264
319
|
return _make_type_table([], [], []) + _make_reference_table([], [], [])
|
265
320
|
end
|
266
321
|
|
267
322
|
def Terminology.tables_for_aetes(aetes)
|
268
|
-
|
323
|
+
# Build terminology tables from a list of unpacked aete byte strings.
|
324
|
+
# Result : list of hash -- [typebycode, typebyname, referencebycode, referencebyname]
|
325
|
+
classes, enums, properties, elements, commands = TerminologyParser.build_tables_for_aetes(aetes.delete_if { |aete| not (aete.is_a?(AE::AEDesc) and aete.type == KAE::TypeAETE) })
|
269
326
|
return _make_type_table(classes, enums, properties) + _make_reference_table(properties, elements, commands)
|
270
327
|
end
|
271
328
|
|
272
329
|
##
|
273
330
|
|
274
331
|
def Terminology.tables_for_module(terms)
|
332
|
+
# Build terminology tables from a dumped terminology module.
|
333
|
+
# Result : list of hash -- [typebycode, typebyname, referencebycode, referencebyname]
|
275
334
|
if terms::Version != 1.1
|
276
335
|
raise RuntimeError, "Unsupported terminology module version: #{terms::Version} (requires version 1.1)."
|
277
336
|
end
|
@@ -279,20 +338,14 @@ module Terminology
|
|
279
338
|
+ _make_reference_table(terms::Properties, terms::Elements, terms::Commands)
|
280
339
|
end
|
281
340
|
|
282
|
-
def Terminology.tables_for_app(
|
283
|
-
|
341
|
+
def Terminology.tables_for_app(aem_app)
|
342
|
+
# Build terminology tables for an application.
|
343
|
+
# app : AEM::Application
|
344
|
+
# Result : list of hash -- [typebycode, typebyname, referencebycode, referencebyname]
|
345
|
+
if not @@_terminology_cache.has_key?(aem_app.identity)
|
284
346
|
begin
|
285
|
-
if path
|
286
|
-
app = AEM::Application.by_path(path)
|
287
|
-
elsif pid
|
288
|
-
app = AEM::Application.by_pid(pid)
|
289
|
-
elsif url
|
290
|
-
app = AEM::Application.by_url(url)
|
291
|
-
else
|
292
|
-
app = AEM::Application.current
|
293
|
-
end
|
294
347
|
begin
|
295
|
-
aetes =
|
348
|
+
aetes = aem_app.event('ascrgdte', {'----' => 0}).send(60 * 60)
|
296
349
|
rescue AEM::CommandError => e
|
297
350
|
if e.number == -192 # aete resource not found
|
298
351
|
aetes = []
|
@@ -304,11 +357,63 @@ module Terminology
|
|
304
357
|
aetes = [aetes]
|
305
358
|
end
|
306
359
|
rescue => err
|
307
|
-
raise RuntimeError, "Can't get terminology for application (#{
|
360
|
+
raise RuntimeError, "Can't get terminology for application (#{aem_app}): #{err}"
|
361
|
+
end
|
362
|
+
@@_terminology_cache[aem_app.identity] = Terminology.tables_for_aetes(aetes)
|
363
|
+
end
|
364
|
+
return @@_terminology_cache[aem_app.identity]
|
365
|
+
end
|
366
|
+
|
367
|
+
#######
|
368
|
+
|
369
|
+
def Terminology.dump(app_name, module_name, out_path)
|
370
|
+
# Export terminology tables as a Ruby module
|
371
|
+
# app_path : string -- name or path of application
|
372
|
+
# module_name : string -- name of generated module (must be a valid Ruby constant)
|
373
|
+
# out_path : string -- module file to write
|
374
|
+
app_path = FindApp.by_name(app_name)
|
375
|
+
if not /^[A-Z][A-Za-z0-9_]*$/ === module_name
|
376
|
+
raise RuntimeError, "Invalid module name."
|
377
|
+
end
|
378
|
+
# Write module
|
379
|
+
File.open(out_path, "w") do |f|
|
380
|
+
# Get aete(s)
|
381
|
+
begin
|
382
|
+
aetes = AEM::Codecs.new.unpack(AE.get_app_terminology(app_path).coerce(KAE::TypeAEList))
|
383
|
+
rescue AE::MacOSError => e
|
384
|
+
if e.to_i == -192 # aete resource not found
|
385
|
+
raise RuntimeError, "No terminology found."
|
386
|
+
else
|
387
|
+
raise
|
388
|
+
end
|
389
|
+
end
|
390
|
+
aetes.delete_if { |aete| not (aete.is_a?(AE::AEDesc) and aete.type == KAE::TypeAETE) }
|
391
|
+
# Parse aete(s) into intermediate tables, suitable for use by Terminology#tables_for_module
|
392
|
+
tables = TerminologyParser.build_tables_for_aetes(aetes)
|
393
|
+
# Write module code
|
394
|
+
f.puts "module #{module_name}"
|
395
|
+
f.puts "\tVersion = 1.1"
|
396
|
+
f.puts "\tPath = #{app_path.inspect}"
|
397
|
+
f.puts
|
398
|
+
(["Classes", "Enumerators", "Properties", "Elements"].zip(tables[0,4])).each do |name, table|
|
399
|
+
f.puts "\t#{name} = ["
|
400
|
+
table.sort.each do |item|
|
401
|
+
f.puts "\t\t#{item.inspect},"
|
402
|
+
end
|
403
|
+
f.puts "\t]"
|
404
|
+
f.puts
|
405
|
+
end
|
406
|
+
f.puts "\tCommands = ["
|
407
|
+
tables[4].sort.each do |name, code, params|
|
408
|
+
f.puts "\t\t[#{name.inspect}, #{code.inspect}, ["
|
409
|
+
params.each do |item|
|
410
|
+
f.puts "\t\t\t#{item.inspect},"
|
411
|
+
end
|
412
|
+
f.puts "\t\t]],"
|
308
413
|
end
|
309
|
-
|
414
|
+
f.puts "\t]"
|
415
|
+
f.puts "end"
|
310
416
|
end
|
311
|
-
return @@_terminology_cache[[path, pid, url]]
|
312
417
|
end
|
313
418
|
end
|
314
419
|
|
data/src/lib/aem.rb
CHANGED
@@ -58,8 +58,7 @@ module AEM
|
|
58
58
|
require "weakref"
|
59
59
|
|
60
60
|
private_class_method :new
|
61
|
-
attr_reader :hash, :identity
|
62
|
-
protected :identity
|
61
|
+
attr_reader :hash, :identity, :address_desc
|
63
62
|
|
64
63
|
#######
|
65
64
|
# Workaround for lack of proper destructors in Ruby; see #initialize method.
|
@@ -80,7 +79,7 @@ module AEM
|
|
80
79
|
# identity is used by #inspect, #hash, #==
|
81
80
|
@_transaction = KAE::KAnyTransactionID
|
82
81
|
@_path = path
|
83
|
-
@
|
82
|
+
@address_desc = address_desc
|
84
83
|
@identity = identity
|
85
84
|
@hash = identity.hash
|
86
85
|
# workaround for lack of proper destructors; if a transaction is still open when Application instance is garbage collected, the following finalizer will automatically close it. Note: object IDs were different for some reason, so class maintains its own unique ids.
|
@@ -89,7 +88,7 @@ module AEM
|
|
89
88
|
ObjectSpace.define_finalizer(WeakRef.new(self), proc do
|
90
89
|
transaction_id = @@_transaction_ids_by_app_no.delete(app_number)
|
91
90
|
if transaction_id != KAE::KAnyTransactionID
|
92
|
-
self.class::Event.new(@
|
91
|
+
self.class::Event.new(@address_desc, 'miscendt', {}, {}, transaction_id).send(60, KAE::KAENoReply)
|
93
92
|
end
|
94
93
|
end)
|
95
94
|
end
|
@@ -166,7 +165,7 @@ module AEM
|
|
166
165
|
# Note that this only works for Application objects created via the by_path constructor.
|
167
166
|
# Also note that any Event objects created prior to calling #reconnect will still be invalid.
|
168
167
|
if @_path
|
169
|
-
@
|
168
|
+
@address_desc = Connect.local_app(@_path)
|
170
169
|
end
|
171
170
|
return
|
172
171
|
end
|
@@ -178,7 +177,7 @@ module AEM
|
|
178
177
|
# atts : hash -- a dict of form {AE_code:anything,...} containing zero or more event attributes (event info)
|
179
178
|
# return_id : integer -- reply event's ID
|
180
179
|
# codecs : Codecs -- codecs object to use when packing/unpacking this event
|
181
|
-
return self.class::Event.new(@
|
180
|
+
return self.class::Event.new(@address_desc, event, params, atts, @_transaction, return_id, codecs)
|
182
181
|
end
|
183
182
|
|
184
183
|
def start_transaction(session=nil)
|
@@ -186,7 +185,7 @@ module AEM
|
|
186
185
|
if @_transaction != KAE::KAnyTransactionID
|
187
186
|
raise RuntimeError, "Transaction is already active."
|
188
187
|
end
|
189
|
-
@_transaction = self.class::Event.new(@
|
188
|
+
@_transaction = self.class::Event.new(@address_desc, 'miscbegi', session != nil ? {'----' => session} : {}).send
|
190
189
|
@@_transaction_ids_by_app_no[@app_number] = @_transaction
|
191
190
|
return
|
192
191
|
end
|
@@ -196,7 +195,7 @@ module AEM
|
|
196
195
|
if @_transaction == KAE::KAnyTransactionID
|
197
196
|
raise RuntimeError, "No transaction is active."
|
198
197
|
end
|
199
|
-
self.class::Event.new(@
|
198
|
+
self.class::Event.new(@address_desc, 'miscttrm', {}, {}, @_transaction).send
|
200
199
|
@_transaction = KAE::KAnyTransactionID
|
201
200
|
@@_transaction_ids_by_app_no[@app_number] = KAE::KAnyTransactionID
|
202
201
|
return
|
@@ -207,7 +206,7 @@ module AEM
|
|
207
206
|
if @_transaction == KAE::KAnyTransactionID
|
208
207
|
raise RuntimeError, "No transaction is active."
|
209
208
|
end
|
210
|
-
self.class::Event.new(@
|
209
|
+
self.class::Event.new(@address_desc, 'miscendt', {}, {}, @_transaction).send
|
211
210
|
@_transaction = KAE::KAnyTransactionID
|
212
211
|
@@_transaction_ids_by_app_no[@app_number] = KAE::KAnyTransactionID
|
213
212
|
return
|