rb-appscript 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +243 -0
- data/LICENSE +1 -0
- data/README +42 -0
- data/TODO +31 -0
- data/doc/aem-manual/01_introduction.html +48 -0
- data/doc/aem-manual/02_apioverview.html +89 -0
- data/doc/aem-manual/03_packingandunpackingdata.html +98 -0
- data/doc/aem-manual/04_references.html +401 -0
- data/doc/aem-manual/05_targettingapplications.html +133 -0
- data/doc/aem-manual/06_buildingandsendingevents.html +175 -0
- data/doc/aem-manual/07_findapp.html +54 -0
- data/doc/aem-manual/08_examples.html +85 -0
- data/doc/aem-manual/09_notes.html +41 -0
- data/doc/aem-manual/aemreferenceinheritance.gif +0 -0
- data/doc/aem-manual/full.css +21 -0
- data/doc/aem-manual/index.html +43 -0
- data/doc/appscript-manual/01_introduction.html +82 -0
- data/doc/appscript-manual/02_aboutappscripting.html +244 -0
- data/doc/appscript-manual/03_quicktutorial.html +154 -0
- data/doc/appscript-manual/04_gettinghelp.html +101 -0
- data/doc/appscript-manual/05_keywordconversion.html +91 -0
- data/doc/appscript-manual/06_classesandenums.html +174 -0
- data/doc/appscript-manual/07_applicationobjects.html +181 -0
- data/doc/appscript-manual/08_realvsgenericreferences.html +86 -0
- data/doc/appscript-manual/09_referenceforms.html +232 -0
- data/doc/appscript-manual/10_referenceexamples.html +142 -0
- data/doc/appscript-manual/11_applicationcommands.html +204 -0
- data/doc/appscript-manual/12_commandexamples.html +129 -0
- data/doc/appscript-manual/13_performanceissues.html +115 -0
- data/doc/appscript-manual/14_problemapps.html +193 -0
- data/doc/appscript-manual/15_notes.html +84 -0
- data/doc/appscript-manual/application_architecture.gif +0 -0
- data/doc/appscript-manual/application_architecture2.gif +0 -0
- data/doc/appscript-manual/finder_to_textedit_event.gif +0 -0
- data/doc/appscript-manual/full.css +21 -0
- data/doc/appscript-manual/index.html +49 -0
- data/doc/appscript-manual/relationships_example.gif +0 -0
- data/doc/appscript-manual/ruby_to_itunes_event.gif +0 -0
- data/doc/index.html +30 -0
- data/doc/mactypes-manual/index.html +216 -0
- data/doc/osax-manual/index.html +169 -0
- data/extconf.rb +54 -0
- data/misc/adobeunittypes.rb +14 -0
- data/misc/dump.rb +72 -0
- data/rb-appscript.gemspec +20 -0
- data/sample/AB_list_people_with_emails.rb +8 -0
- data/sample/Create_daily_iCal_todos.rb +72 -0
- data/sample/Hello_world.rb +9 -0
- data/sample/List_iTunes_playlist_names.rb +7 -0
- data/sample/Make_Mail_message.rb +29 -0
- data/sample/Open_file_in_TextEdit.rb +9 -0
- data/sample/Organize_Mail_messages.rb +57 -0
- data/sample/Print_folder_tree.rb +12 -0
- data/sample/Select_all_HTML_files.rb +8 -0
- data/sample/Set_iChat_status.rb +20 -0
- data/sample/Simple_Finder_GUI_Scripting.rb +14 -0
- data/sample/Stagger_Finder_windows.rb +21 -0
- data/sample/TextEdit_demo.rb +126 -0
- data/sample/iTunes_top40_to_html.rb +64 -0
- data/src/lib/_aem/aemreference.rb +1006 -0
- data/src/lib/_aem/codecs.rb +617 -0
- data/src/lib/_aem/connect.rb +100 -0
- data/src/lib/_aem/findapp.rb +83 -0
- data/src/lib/_aem/mactypes.rb +228 -0
- data/src/lib/_aem/send.rb +257 -0
- data/src/lib/_aem/typewrappers.rb +57 -0
- data/src/lib/_appscript/defaultterminology.rb +245 -0
- data/src/lib/_appscript/referencerenderer.rb +132 -0
- data/src/lib/_appscript/reservedkeywords.rb +107 -0
- data/src/lib/_appscript/terminology.rb +314 -0
- data/src/lib/aem.rb +216 -0
- data/src/lib/appscript.rb +830 -0
- data/src/lib/kae.rb +1484 -0
- data/src/lib/osax.rb +171 -0
- data/src/rbae.c +766 -0
- data/test/README +1 -0
- data/test/test_aemreference.rb +112 -0
- data/test/test_appscriptreference.rb +102 -0
- data/test/test_codecs.rb +159 -0
- data/test/test_findapp.rb +24 -0
- data/test/test_mactypes.rb +67 -0
- data/test/testall.sh +9 -0
- metadata +143 -0
@@ -0,0 +1,1006 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
# Copyright (C) 2006 HAS.
|
3
|
+
# Released under MIT License.
|
4
|
+
|
5
|
+
######################################################################
|
6
|
+
# Endianness support
|
7
|
+
|
8
|
+
module BigEndianPackers
|
9
|
+
|
10
|
+
def pack_type(code)
|
11
|
+
return AE::AEDesc.new(KAE::TypeType, code)
|
12
|
+
end
|
13
|
+
|
14
|
+
def pack_enum(code)
|
15
|
+
return AE::AEDesc.new(KAE::TypeEnumeration, code)
|
16
|
+
end
|
17
|
+
|
18
|
+
def pack_absolute_ordinal(code)
|
19
|
+
return AE::AEDesc.new(KAE::TypeAbsoluteOrdinal, code)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
module SmallEndianPackers
|
26
|
+
|
27
|
+
def pack_type(code)
|
28
|
+
return AE::AEDesc.new(KAE::TypeType, code.reverse)
|
29
|
+
end
|
30
|
+
|
31
|
+
def pack_enum(code)
|
32
|
+
return AE::AEDesc.new(KAE::TypeEnumeration, code.reverse)
|
33
|
+
end
|
34
|
+
|
35
|
+
def pack_absolute_ordinal(code)
|
36
|
+
return AE::AEDesc.new(KAE::TypeAbsoluteOrdinal, code.reverse)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
######################################################################
|
43
|
+
|
44
|
+
module AEMReference
|
45
|
+
|
46
|
+
require "ae"
|
47
|
+
require "kae"
|
48
|
+
|
49
|
+
######################################################################
|
50
|
+
# SUPPORT FUNCTIONS
|
51
|
+
######################################################################
|
52
|
+
|
53
|
+
extend([1].pack('s') == "\001\000" ? SmallEndianPackers : BigEndianPackers)
|
54
|
+
|
55
|
+
def AEMReference.pack_list_as(type, lst)
|
56
|
+
# used to pack object specifiers, etc.
|
57
|
+
# pack key-value pairs into an AEListDesc, then coerce it to the desired type
|
58
|
+
# (there are other AEM APIs for packing obj specs, but this way is easiest)
|
59
|
+
desc = AE::AEDesc.new_list(true)
|
60
|
+
lst.each { |key, value| desc.put_param(key, value) }
|
61
|
+
return desc.coerce(type)
|
62
|
+
end
|
63
|
+
|
64
|
+
class CollectComparable
|
65
|
+
# obtains the data needed to perform equality tests on references
|
66
|
+
# uses AEM_resolve to walk a reference, building up a list of method call names and their arguments
|
67
|
+
|
68
|
+
attr_reader :result
|
69
|
+
|
70
|
+
def initialize
|
71
|
+
@result = []
|
72
|
+
end
|
73
|
+
|
74
|
+
def method_missing(name, *args)
|
75
|
+
self.result.push([name] + args)
|
76
|
+
return self
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
######################################################################
|
81
|
+
# BASE CLASS
|
82
|
+
######################################################################
|
83
|
+
|
84
|
+
class Base
|
85
|
+
|
86
|
+
def AEM_comparable
|
87
|
+
# called by Base#==; returns the data needed to compare two aem references
|
88
|
+
if not @_comparable
|
89
|
+
collector = AEMReference::CollectComparable.new
|
90
|
+
AEM_resolve(collector)
|
91
|
+
@_comparable = collector.result
|
92
|
+
end
|
93
|
+
return @_comparable
|
94
|
+
end
|
95
|
+
|
96
|
+
def ==(val)
|
97
|
+
return (self.equal?(val) or (
|
98
|
+
self.class == val.class and
|
99
|
+
self.AEM_comparable == val.AEM_comparable))
|
100
|
+
end
|
101
|
+
|
102
|
+
alias_method :eql?, :==
|
103
|
+
|
104
|
+
def hash
|
105
|
+
return to_s.hash
|
106
|
+
end
|
107
|
+
|
108
|
+
def inspect
|
109
|
+
return to_s
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
######################################################################
|
115
|
+
# BASE CLASS FOR ALL REFERENCE FORMS
|
116
|
+
######################################################################
|
117
|
+
|
118
|
+
class Specifier < Base
|
119
|
+
# Base class for insertion specifier and all object specifier classes.
|
120
|
+
|
121
|
+
attr_reader :_key, :_container
|
122
|
+
protected :_key, :_container
|
123
|
+
|
124
|
+
def initialize(container, key)
|
125
|
+
@_container = container
|
126
|
+
@_key = key
|
127
|
+
end
|
128
|
+
|
129
|
+
def AEM_root
|
130
|
+
# Get reference's root node. Used by range and filter specifiers when determining type of reference
|
131
|
+
# passed as argument(s): range specifiers require absolute (app-based) or container (con-based)
|
132
|
+
# references; filter specifiers require an item (its-based) reference.
|
133
|
+
return @_container.AEM_root
|
134
|
+
end
|
135
|
+
|
136
|
+
def AEM_true_self
|
137
|
+
# Called by specifier classes when creating a reference to sub-element(s) of the current reference.
|
138
|
+
# - An AllElements specifier (which contains 'want', 'form', 'seld' and 'from' values) will return an UnkeyedElements object (which contains 'want' and 'from' data only). The new specifier object (ElementByIndex, ElementsByRange, etc.) wraps itself around this stub and supply its own choice of 'form' and 'seld' values.
|
139
|
+
# - All other specifiers simply return themselves.
|
140
|
+
#
|
141
|
+
#This sleight-of-hand allows foo.elements('bar ') to produce a legal reference to all elements, so users don't need to write foo.elements('bar ').all to achieve the same goal. This isn't a huge deal for aem, but makes a significant difference to the usability of user-friendly wrappers like appscript, and dealing with the mechanics of it here helps keep other layers simple.
|
142
|
+
return self
|
143
|
+
end
|
144
|
+
|
145
|
+
def AEM_set_desc(desc)
|
146
|
+
@_desc = desc
|
147
|
+
end
|
148
|
+
|
149
|
+
def AEM_pack_self(codecs)
|
150
|
+
# Pack this Specifier; called by Codecs#pack, which passes itself so that specifiers in this reference can pack their selectors.
|
151
|
+
if not @_desc
|
152
|
+
@_desc = _pack_self(codecs) # once packed, cache this AEDesc for efficiency
|
153
|
+
end
|
154
|
+
return @_desc
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
######################################################################
|
160
|
+
# INSERTION POINT REFERENCE FORM
|
161
|
+
######################################################################
|
162
|
+
|
163
|
+
class InsertionSpecifier < Specifier
|
164
|
+
# A reference to an element insertion point.
|
165
|
+
|
166
|
+
# Syntax: all_elements_ref.start / all_elements_ref.end / element_ref.before / element_ref.after
|
167
|
+
|
168
|
+
def initialize(container, key, keyname)
|
169
|
+
super(container, key)
|
170
|
+
@_keyname = keyname
|
171
|
+
end
|
172
|
+
|
173
|
+
def to_s
|
174
|
+
return "#{@_container}.#{@_keyname}"
|
175
|
+
end
|
176
|
+
|
177
|
+
def _pack_self(codecs)
|
178
|
+
return AEMReference.pack_list_as(KAE::TypeInsertionLoc, [
|
179
|
+
[KAE::KeyAEObject, @_container.AEM_pack_self(codecs)],
|
180
|
+
[KAE::KeyAEPosition, @_key],
|
181
|
+
])
|
182
|
+
end
|
183
|
+
|
184
|
+
def AEM_resolve(obj)
|
185
|
+
return @_container.AEM_resolve(obj).send(@_keyname)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
######################################################################
|
191
|
+
# BASE CLASS FOR ALL OBJECT REFERENCE FORMS
|
192
|
+
######################################################################
|
193
|
+
|
194
|
+
class PositionSpecifier < Specifier
|
195
|
+
# All property and element reference forms inherit from this class. It provides most
|
196
|
+
# of the reference building methods; the rest are supplied by MultipleElements to
|
197
|
+
# those reference forms for which they're valid.
|
198
|
+
|
199
|
+
# Note that comparison and logic 'operator' methods are implemented on this class
|
200
|
+
# - these are only for use in constructing its-based references and shouldn't be used
|
201
|
+
# on app- and con-based references. Aem doesn't enforce this rule itself so as to
|
202
|
+
# minimise runtime overhead (the target application will raise an error if the user
|
203
|
+
# does something foolish).
|
204
|
+
|
205
|
+
Beginning = AEMReference.pack_enum(KAE::KAEBeginning)
|
206
|
+
End = AEMReference.pack_enum(KAE::KAEEnd)
|
207
|
+
Before = AEMReference.pack_enum(KAE::KAEBefore)
|
208
|
+
After = AEMReference.pack_enum(KAE::KAEAfter)
|
209
|
+
Previous = AEMReference.pack_enum(KAE::KAEPrevious)
|
210
|
+
Next = AEMReference.pack_enum(KAE::KAENext)
|
211
|
+
|
212
|
+
attr_reader :AEM_want
|
213
|
+
|
214
|
+
def initialize(wantcode, container, key)
|
215
|
+
@AEM_want = wantcode
|
216
|
+
super(container, key)
|
217
|
+
end
|
218
|
+
|
219
|
+
def to_s
|
220
|
+
return "#{@_container}.#{self.class::By}(#{@_key.inspect})"
|
221
|
+
end
|
222
|
+
|
223
|
+
def _pack_self(codecs)
|
224
|
+
return AEMReference.pack_list_as(KAE::TypeObjectSpecifier, [
|
225
|
+
[KAE::KeyAEDesiredClass, AEMReference.pack_type(@AEM_want)],
|
226
|
+
[KAE::KeyAEKeyForm, self.class::KeyForm],
|
227
|
+
[KAE::KeyAEKeyData, _pack_key(codecs)],
|
228
|
+
[KAE::KeyAEContainer, @_container.AEM_pack_self(codecs)],
|
229
|
+
])
|
230
|
+
end
|
231
|
+
|
232
|
+
# Comparison tests; these should only be used on its-based references:
|
233
|
+
|
234
|
+
# Each of these methods returns a ComparisonTest subclass
|
235
|
+
|
236
|
+
def gt(val)
|
237
|
+
return GreaterThan.new(self, val)
|
238
|
+
end
|
239
|
+
|
240
|
+
def ge(val)
|
241
|
+
return GreaterOrEquals.new(self, val)
|
242
|
+
end
|
243
|
+
|
244
|
+
def eq(val)
|
245
|
+
return Equals.new(self, val)
|
246
|
+
end
|
247
|
+
|
248
|
+
def ne(val)
|
249
|
+
return NotEquals.new(self, val)
|
250
|
+
end
|
251
|
+
|
252
|
+
def lt(val)
|
253
|
+
return LessThan.new(self, val)
|
254
|
+
end
|
255
|
+
|
256
|
+
def le(val)
|
257
|
+
return LessOrEquals.new(self, val)
|
258
|
+
end
|
259
|
+
|
260
|
+
def starts_with(val)
|
261
|
+
return StartsWith.new(self, val)
|
262
|
+
end
|
263
|
+
|
264
|
+
def ends_with(val)
|
265
|
+
return EndsWith.new(self, val)
|
266
|
+
end
|
267
|
+
|
268
|
+
def contains(val)
|
269
|
+
return Contains.new(self, val)
|
270
|
+
end
|
271
|
+
|
272
|
+
def is_in(val)
|
273
|
+
return IsIn.new(self, val)
|
274
|
+
end
|
275
|
+
|
276
|
+
# Logic tests; these should only be used on its-based references:
|
277
|
+
|
278
|
+
# Note: these are convenience methods allowing boolean tests to be written in shorthand form;
|
279
|
+
# e.g. AEM.its.visible.and(...) will automatically expand to AEM.its.visible.eq(true).and(...)
|
280
|
+
# The Test class implements the actual logic test methods
|
281
|
+
|
282
|
+
def and(*operands)
|
283
|
+
return Equals.new(self, true).and(*operands)
|
284
|
+
end
|
285
|
+
|
286
|
+
def or(*operands)
|
287
|
+
return Equals.new(self, true).or(*operands)
|
288
|
+
end
|
289
|
+
|
290
|
+
def not
|
291
|
+
return Equals.new(self, true).not
|
292
|
+
end
|
293
|
+
|
294
|
+
|
295
|
+
# Insertion references:
|
296
|
+
|
297
|
+
# Thes can be called on any kind of element reference, and also on property references where the
|
298
|
+
# property represents a one-to-one relationship, e.g. textedit.documents[1].text.end is valid
|
299
|
+
|
300
|
+
def start
|
301
|
+
return InsertionSpecifier.new(self, Beginning, 'start')
|
302
|
+
end
|
303
|
+
|
304
|
+
def end
|
305
|
+
return InsertionSpecifier.new(self, End, 'end')
|
306
|
+
end
|
307
|
+
|
308
|
+
def before
|
309
|
+
return InsertionSpecifier.new(self, Before, 'before')
|
310
|
+
end
|
311
|
+
|
312
|
+
def after
|
313
|
+
return InsertionSpecifier.new(self, After, 'after')
|
314
|
+
end
|
315
|
+
|
316
|
+
# Property and element references can be used on any type of object reference:
|
317
|
+
|
318
|
+
def property(propertycode)
|
319
|
+
return Property.new(KAE::CProperty, self, propertycode)
|
320
|
+
end
|
321
|
+
|
322
|
+
def userproperty(name)
|
323
|
+
return UserProperty.new(KAE::CProperty, self, name)
|
324
|
+
end
|
325
|
+
|
326
|
+
def elements(elementcode)
|
327
|
+
return AllElements.new(elementcode, self)
|
328
|
+
end
|
329
|
+
|
330
|
+
# Relative position references
|
331
|
+
|
332
|
+
# these are unlikely to work on one-to-one relationships - but what the hey, putting them here
|
333
|
+
# simplifies the class structure a bit. As with all reference forms, it's mostly left to the client to
|
334
|
+
# ensure the references they construct can be understood by the target application.
|
335
|
+
|
336
|
+
def previous(elementcode)
|
337
|
+
return ElementByRelativePosition.new(elementcode, self, Previous, 'previous')
|
338
|
+
end
|
339
|
+
|
340
|
+
def next(elementcode)
|
341
|
+
return ElementByRelativePosition.new(elementcode, self, Next, 'next')
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
|
346
|
+
######################################################################
|
347
|
+
# PROPERTY REFERENCE FORMS
|
348
|
+
######################################################################
|
349
|
+
|
350
|
+
class Property < PositionSpecifier
|
351
|
+
# A reference to a user-defined property, where code is the code identifying the property.
|
352
|
+
|
353
|
+
# Syntax: ref.property(code)
|
354
|
+
|
355
|
+
By = 'property'
|
356
|
+
KeyForm = AEMReference.pack_enum(KAE::FormPropertyID)
|
357
|
+
|
358
|
+
def _pack_key(codecs)
|
359
|
+
return AEMReference.pack_type(@_key)
|
360
|
+
end
|
361
|
+
|
362
|
+
def AEM_resolve(obj)
|
363
|
+
return @_container.AEM_resolve(obj).property(@_key)
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
|
368
|
+
class UserProperty < PositionSpecifier
|
369
|
+
# A reference to a user-defined property, where name is a string representing the property's name.
|
370
|
+
|
371
|
+
# Scriptable applications shouldn't use this reference form, but OSA script applets can.
|
372
|
+
# Note that OSA languages may have additional rules regarding case sensitivity/conversion.
|
373
|
+
|
374
|
+
# Syntax: ref.userproperty(name)
|
375
|
+
|
376
|
+
By = 'userproperty'
|
377
|
+
KeyForm = AEMReference.pack_enum('usrp')
|
378
|
+
|
379
|
+
def _pack_key(codecs)
|
380
|
+
return codecs.pack(@_key).coerce(KAE::TypeChar)
|
381
|
+
end
|
382
|
+
|
383
|
+
def AEM_resolve(obj)
|
384
|
+
return @_container.AEM_resolve(obj).userproperty(@_key)
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
|
389
|
+
######################################################################
|
390
|
+
# ELEMENT REFERENCE FORMS
|
391
|
+
######################################################################
|
392
|
+
|
393
|
+
###################################
|
394
|
+
# Single elements
|
395
|
+
|
396
|
+
class SingleElement < PositionSpecifier
|
397
|
+
# Base class for all single element specifiers.
|
398
|
+
|
399
|
+
def initialize(wantcode, container, key)
|
400
|
+
super(wantcode, container.AEM_true_self, key)
|
401
|
+
end
|
402
|
+
|
403
|
+
def _pack_key(codecs)
|
404
|
+
return codecs.pack(@_key)
|
405
|
+
end
|
406
|
+
|
407
|
+
def AEM_resolve(obj)
|
408
|
+
return @_container.AEM_resolve(obj).send(self.class::By, @_key)
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
|
413
|
+
#######
|
414
|
+
|
415
|
+
class ElementByName < SingleElement
|
416
|
+
# A reference to a single element by its name, where name is a string.
|
417
|
+
|
418
|
+
# Syntax: elements_ref.by_name(string)
|
419
|
+
|
420
|
+
By = 'by_name'
|
421
|
+
KeyForm = AEMReference.pack_enum(KAE::FormName)
|
422
|
+
end
|
423
|
+
|
424
|
+
|
425
|
+
class ElementByIndex < SingleElement
|
426
|
+
# A reference to a single element by its index, where index is a non-zero whole number.
|
427
|
+
|
428
|
+
# Syntax: elements_ref.by_index(integer)
|
429
|
+
|
430
|
+
# Note that a few apps (e.g. Finder) may allow other values as well (e.g. Aliases/FSRefs)
|
431
|
+
|
432
|
+
By = 'by_index'
|
433
|
+
KeyForm = AEMReference.pack_enum(KAE::FormAbsolutePosition)
|
434
|
+
end
|
435
|
+
|
436
|
+
|
437
|
+
class ElementByID < SingleElement
|
438
|
+
# A reference to a single element by its id.
|
439
|
+
|
440
|
+
# Syntax: elements_ref.by_id(anything)
|
441
|
+
|
442
|
+
By = 'by_id'
|
443
|
+
KeyForm = AEMReference.pack_enum(KAE::FormUniqueID)
|
444
|
+
end
|
445
|
+
|
446
|
+
##
|
447
|
+
|
448
|
+
class ElementByOrdinal < SingleElement
|
449
|
+
# A reference to first/middle/last/any element.
|
450
|
+
|
451
|
+
# Syntax: elements_ref.first / elements_ref.middle / elements_ref.last / elements_ref.any
|
452
|
+
|
453
|
+
KeyForm = AEMReference.pack_enum(KAE::FormAbsolutePosition)
|
454
|
+
|
455
|
+
def initialize(wantcode, container, key, keyname)
|
456
|
+
@_keyname = keyname
|
457
|
+
super(wantcode, container, key)
|
458
|
+
end
|
459
|
+
|
460
|
+
def to_s
|
461
|
+
return "#{@_container}.#{@_keyname}"
|
462
|
+
end
|
463
|
+
|
464
|
+
def AEM_resolve(obj)
|
465
|
+
return @_container.AEM_resolve(obj).send(@_keyname)
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
|
470
|
+
class ElementByRelativePosition < PositionSpecifier
|
471
|
+
# A relative reference to previous/next element, where code
|
472
|
+
# is the class code of element to get (e.g. 'docu').
|
473
|
+
|
474
|
+
# Syntax: elements_ref.previous(code) / elements_ref.next(code)
|
475
|
+
|
476
|
+
# Note: this class subclasses PositionSpecifier, not SingleElement,
|
477
|
+
# as it needs the container reference intact. (SingleElement#initialize
|
478
|
+
# calls the container's AEM_true_self method, which breaks up
|
479
|
+
# AllElements specifiers - not what we want here.)
|
480
|
+
|
481
|
+
KeyForm = AEMReference.pack_enum(KAE::FormRelativePosition)
|
482
|
+
|
483
|
+
def initialize(wantcode, container, key, keyname)
|
484
|
+
@_keyname = keyname
|
485
|
+
super(wantcode, container, key)
|
486
|
+
end
|
487
|
+
|
488
|
+
def _pack_key(codecs)
|
489
|
+
return codecs.pack(@_key)
|
490
|
+
end
|
491
|
+
|
492
|
+
def to_s
|
493
|
+
return "#{@_container}.#{@_keyname}(#{@AEM_want.inspect})"
|
494
|
+
end
|
495
|
+
|
496
|
+
def AEM_resolve(obj)
|
497
|
+
return @_container.AEM_resolve(obj).send(@_keyname, @AEM_want)
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
|
502
|
+
###################################
|
503
|
+
# Multiple elements
|
504
|
+
|
505
|
+
class MultipleElements < PositionSpecifier
|
506
|
+
# Base class for all multiple element specifiers.
|
507
|
+
|
508
|
+
First = AEMReference.pack_absolute_ordinal(KAE::KAEFirst)
|
509
|
+
Middle = AEMReference.pack_absolute_ordinal(KAE::KAEMiddle)
|
510
|
+
Last = AEMReference.pack_absolute_ordinal(KAE::KAELast)
|
511
|
+
Any = AEMReference.pack_absolute_ordinal(KAE::KAEAny)
|
512
|
+
|
513
|
+
def first
|
514
|
+
return ElementByOrdinal.new(@AEM_want, self, First, 'first')
|
515
|
+
end
|
516
|
+
|
517
|
+
def middle
|
518
|
+
return ElementByOrdinal.new(@AEM_want, self, Middle, 'middle')
|
519
|
+
end
|
520
|
+
|
521
|
+
def last
|
522
|
+
return ElementByOrdinal.new(@AEM_want, self, Last, 'last')
|
523
|
+
end
|
524
|
+
|
525
|
+
def any
|
526
|
+
return ElementByOrdinal.new(@AEM_want, self, Any, 'any')
|
527
|
+
end
|
528
|
+
|
529
|
+
def by_name(name)
|
530
|
+
return ElementByName.new(@AEM_want, self, name)
|
531
|
+
end
|
532
|
+
|
533
|
+
def by_index(index)
|
534
|
+
return ElementByIndex.new(@AEM_want, self, index)
|
535
|
+
end
|
536
|
+
|
537
|
+
def by_id(id)
|
538
|
+
return ElementByID.new(@AEM_want, self, id)
|
539
|
+
end
|
540
|
+
|
541
|
+
def by_range(start, stop)
|
542
|
+
return ElementsByRange.new(@AEM_want, self, [start, stop])
|
543
|
+
end
|
544
|
+
|
545
|
+
def by_filter(expression)
|
546
|
+
return ElementsByFilter.new(@AEM_want, self, expression)
|
547
|
+
end
|
548
|
+
end
|
549
|
+
|
550
|
+
|
551
|
+
#######
|
552
|
+
|
553
|
+
class ElementsByRange < MultipleElements
|
554
|
+
# A reference to a range of elements
|
555
|
+
|
556
|
+
# Syntax: elements_ref.by_range(start, stop)
|
557
|
+
|
558
|
+
# The start and stop args are con-based relative references to the first and last elements in range.
|
559
|
+
# Note that absolute (app-based) references are also acceptable.
|
560
|
+
|
561
|
+
KeyForm = AEMReference.pack_enum(KAE::FormRange)
|
562
|
+
|
563
|
+
def initialize(wantcode, container, key)
|
564
|
+
key.each do |item|
|
565
|
+
if not (item.is_a?(Specifier) and item.AEM_root != Its)
|
566
|
+
raise TypeError, "Bad selector: not an application (app) or container (con) based reference: #{item.inspect}"
|
567
|
+
end
|
568
|
+
end
|
569
|
+
super(wantcode, container.AEM_true_self, key)
|
570
|
+
end
|
571
|
+
|
572
|
+
def to_s
|
573
|
+
return "#{@_container}.by_range(#{@_key[0].inspect}, #{@_key[1].inspect})"
|
574
|
+
end
|
575
|
+
|
576
|
+
def _pack_key(codecs)
|
577
|
+
return AEMReference.pack_list_as(KAE::TypeRangeDescriptor, [
|
578
|
+
[KAE::KeyAERangeStart, codecs.pack(@_key[0])],
|
579
|
+
[KAE::KeyAERangeStop, codecs.pack(@_key[1])]
|
580
|
+
])
|
581
|
+
end
|
582
|
+
|
583
|
+
def AEM_resolve(obj)
|
584
|
+
return @_container.AEM_resolve(obj).by_range(*@_key)
|
585
|
+
end
|
586
|
+
end
|
587
|
+
|
588
|
+
|
589
|
+
class ElementsByFilter < MultipleElements
|
590
|
+
# A reference to all elements that match a condition
|
591
|
+
|
592
|
+
# Syntax: elements_ref.by_filter(test)
|
593
|
+
|
594
|
+
# The test argument is a Test object constructed from an its-based reference.
|
595
|
+
# For convenience, an its-based reference can also be passed directly - this will
|
596
|
+
# be expanded to a Boolean equality test, e.g. AEM.its.visible -> AEM.its.visible.eq(true)
|
597
|
+
|
598
|
+
KeyForm = AEMReference.pack_enum(KAE::FormTest)
|
599
|
+
|
600
|
+
def initialize(wantcode, container, key)
|
601
|
+
if not key.is_a?(Test)
|
602
|
+
if key.is_a?(Specifier) and key.AEM_root == Its
|
603
|
+
key = key.eq(true)
|
604
|
+
else
|
605
|
+
raise TypeError, "Bad selector: not a test (its) based specifier: #{key.inspect}"
|
606
|
+
end
|
607
|
+
end
|
608
|
+
super(wantcode, container.AEM_true_self, key)
|
609
|
+
end
|
610
|
+
|
611
|
+
def to_s
|
612
|
+
return "#{@_container}.by_filter(#{@_key.inspect})"
|
613
|
+
end
|
614
|
+
|
615
|
+
def _pack_key(codecs)
|
616
|
+
return codecs.pack(@_key)
|
617
|
+
end
|
618
|
+
|
619
|
+
def AEM_resolve(obj)
|
620
|
+
return @_container.AEM_resolve(obj).by_filter(@_key)
|
621
|
+
end
|
622
|
+
end
|
623
|
+
|
624
|
+
|
625
|
+
class AllElements < MultipleElements
|
626
|
+
# A reference to all elements of the container reference.
|
627
|
+
|
628
|
+
# Syntax: ref.elements(code)
|
629
|
+
|
630
|
+
# The 'code' argument is the four-character class code of the desired elements (e.g. 'docu').
|
631
|
+
|
632
|
+
# Note that an AllElements object is a wrapper around an UnkeyedElements object.
|
633
|
+
# When sub-selecting these elements, e.g. ref.elements('docu').by_index(1), the AllElements
|
634
|
+
# wrapper is ignored and the UnkeyedElements object is used as the basis for the
|
635
|
+
# new specifier. e.g.
|
636
|
+
#
|
637
|
+
# AEM.app.elements('docu') # every document of application
|
638
|
+
#
|
639
|
+
# produces the following chain:
|
640
|
+
#
|
641
|
+
# ApplicationRoot -> UnkeyedElements -> AllElements
|
642
|
+
#
|
643
|
+
# Subselecting these elements, e.g.
|
644
|
+
#
|
645
|
+
# AEM.app.elements('docu').by_index(1) # document 1 of application
|
646
|
+
#
|
647
|
+
# produces the following chain:
|
648
|
+
#
|
649
|
+
# ApplicationRoot -> UnkeyedElements -> ElementByIndex
|
650
|
+
#
|
651
|
+
# As you can see, the previous UnkeyedElements object is retained, but the AllElements
|
652
|
+
# object isn't.
|
653
|
+
#
|
654
|
+
# The result of all this is that users can legally write a reference to all elements as (e.g.):
|
655
|
+
#
|
656
|
+
# AEM.app.elements('docu')
|
657
|
+
# AS.app.documents
|
658
|
+
#
|
659
|
+
# instead of:
|
660
|
+
#
|
661
|
+
# AEM.app.elements('docu').all
|
662
|
+
# AS.app.documents.all
|
663
|
+
#
|
664
|
+
# Compare with some other bridges (e.g. Frontier), where (e.g.) 'ref.documents' is not
|
665
|
+
# a legitimate reference in itself, and users must remember to add '.all' in order to specify
|
666
|
+
# all elements, or else it won't work correctly. This maps directly onto the underlying AEM
|
667
|
+
# API, which is easy to implement but isn't so good for usability. Whereas aem trades
|
668
|
+
# a bit of increased internal complexity for a simpler, more intuitive and foolproof external API.
|
669
|
+
|
670
|
+
KeyForm = AEMReference.pack_enum(KAE::FormAbsolutePosition)
|
671
|
+
All = AEMReference.pack_absolute_ordinal(KAE::KAEAll)
|
672
|
+
|
673
|
+
def initialize(wantcode, container)
|
674
|
+
super(wantcode, UnkeyedElements.new(wantcode, container), All)
|
675
|
+
end
|
676
|
+
|
677
|
+
def to_s
|
678
|
+
return @_container.to_s
|
679
|
+
end
|
680
|
+
|
681
|
+
def _pack_key(codecs)
|
682
|
+
return All
|
683
|
+
end
|
684
|
+
|
685
|
+
def AEM_true_self
|
686
|
+
# override default implementation to return the UnkeyedElements object stored inside of this AllElements instance
|
687
|
+
return @_container
|
688
|
+
end
|
689
|
+
|
690
|
+
def AEM_resolve(obj)
|
691
|
+
return @_container.AEM_resolve(obj) # forward to UnkeyedElements
|
692
|
+
end
|
693
|
+
end
|
694
|
+
|
695
|
+
|
696
|
+
######################################################################
|
697
|
+
# SHIMS
|
698
|
+
######################################################################
|
699
|
+
|
700
|
+
###################################
|
701
|
+
# Multiple element shim
|
702
|
+
|
703
|
+
class UnkeyedElements < Specifier
|
704
|
+
# A partial elements reference, containing element code but no keyform/keydata. A shim.
|
705
|
+
# User is never exposed to this class directly. See comments in AllElements class.
|
706
|
+
|
707
|
+
attr_reader :AEM_want, :_container
|
708
|
+
protected :AEM_want, :_container
|
709
|
+
|
710
|
+
def initialize(wantcode, container)
|
711
|
+
@AEM_want = wantcode
|
712
|
+
@_container = container
|
713
|
+
end
|
714
|
+
|
715
|
+
def to_s
|
716
|
+
return "#{@_container}.elements(#{@AEM_want.inspect})"
|
717
|
+
end
|
718
|
+
|
719
|
+
def AEM_pack_self(codecs)
|
720
|
+
return @_container.AEM_pack_self(codecs)
|
721
|
+
end
|
722
|
+
|
723
|
+
def AEM_resolve(obj)
|
724
|
+
return @_container.AEM_resolve(obj).elements(@AEM_want)
|
725
|
+
end
|
726
|
+
end
|
727
|
+
|
728
|
+
|
729
|
+
###################################
|
730
|
+
# Unresolved reference
|
731
|
+
|
732
|
+
class DeferredSpecifier < Base
|
733
|
+
def initialize(desc, codecs)
|
734
|
+
@_desc = desc
|
735
|
+
@_codecs = codecs
|
736
|
+
end
|
737
|
+
|
738
|
+
def _real_ref
|
739
|
+
if not @_ref
|
740
|
+
@_ref = @_codecs.fully_unpack_object_specifier(@_desc)
|
741
|
+
end
|
742
|
+
return @_ref
|
743
|
+
end
|
744
|
+
|
745
|
+
def AEM_true_self
|
746
|
+
return self
|
747
|
+
end
|
748
|
+
|
749
|
+
def to_s
|
750
|
+
return _real_ref.to_s
|
751
|
+
end
|
752
|
+
|
753
|
+
def AEM_root
|
754
|
+
return _real_ref.AEM_root
|
755
|
+
end
|
756
|
+
|
757
|
+
def AEM_resolve(obj)
|
758
|
+
return _real_ref.AEM_resolve(obj)
|
759
|
+
end
|
760
|
+
end
|
761
|
+
|
762
|
+
|
763
|
+
######################################################################
|
764
|
+
# TEST CLAUSES
|
765
|
+
######################################################################
|
766
|
+
|
767
|
+
###################################
|
768
|
+
# Base class
|
769
|
+
|
770
|
+
class Test < Base
|
771
|
+
|
772
|
+
# Logical tests
|
773
|
+
|
774
|
+
def and(operand2, *operands)
|
775
|
+
return AND.new([self, operand2] + operands)
|
776
|
+
end
|
777
|
+
|
778
|
+
def or(operand2, * operands)
|
779
|
+
return OR.new([self, operand2] + operands)
|
780
|
+
end
|
781
|
+
|
782
|
+
def not
|
783
|
+
return NOT.new([self])
|
784
|
+
end
|
785
|
+
end
|
786
|
+
|
787
|
+
|
788
|
+
###################################
|
789
|
+
# Comparison tests
|
790
|
+
|
791
|
+
class ComparisonTest < Test
|
792
|
+
attr_reader :_operand1, :_operand2
|
793
|
+
protected :_operand1, :_operand2
|
794
|
+
|
795
|
+
def initialize(operand1, operand2)
|
796
|
+
@_operand1 = operand1
|
797
|
+
@_operand2 = operand2
|
798
|
+
end
|
799
|
+
|
800
|
+
def to_s
|
801
|
+
return "#{@_operand1.inspect}.#{self.class::Name}(#{@_operand2.inspect})"
|
802
|
+
end
|
803
|
+
|
804
|
+
def AEM_resolve(obj)
|
805
|
+
return @_operand1.AEM_resolve(obj).send(self.class::Name, @_operand2)
|
806
|
+
end
|
807
|
+
|
808
|
+
def AEM_pack_self(codecs)
|
809
|
+
return AEMReference.pack_list_as(KAE::TypeCompDescriptor, [
|
810
|
+
[KAE::KeyAEObject1, codecs.pack(@_operand1)],
|
811
|
+
[KAE::KeyAECompOperator, self.class::Operator],
|
812
|
+
[KAE::KeyAEObject2, codecs.pack(@_operand2)]
|
813
|
+
])
|
814
|
+
end
|
815
|
+
end
|
816
|
+
|
817
|
+
##
|
818
|
+
|
819
|
+
|
820
|
+
|
821
|
+
class GreaterThan < ComparisonTest
|
822
|
+
Name = 'gt'
|
823
|
+
Operator = AEMReference.pack_enum(KAE::KAEGreaterThan)
|
824
|
+
end
|
825
|
+
|
826
|
+
class GreaterOrEquals < ComparisonTest
|
827
|
+
Name = 'ge'
|
828
|
+
Operator = AEMReference.pack_enum(KAE::KAEGreaterThanEquals)
|
829
|
+
end
|
830
|
+
|
831
|
+
class Equals < ComparisonTest
|
832
|
+
Name = 'eq'
|
833
|
+
Operator = AEMReference.pack_enum(KAE::KAEEquals)
|
834
|
+
end
|
835
|
+
|
836
|
+
class NotEquals < Equals
|
837
|
+
Name = 'ne'
|
838
|
+
OperatorNOT = AEMReference.pack_enum(KAE::KAENOT)
|
839
|
+
|
840
|
+
def AEM_pack_self(codecs)
|
841
|
+
return @_operand1.eq(@_operand2).not.AEM_pack_self(codecs)
|
842
|
+
end
|
843
|
+
end
|
844
|
+
|
845
|
+
class LessThan < ComparisonTest
|
846
|
+
Name = 'lt'
|
847
|
+
Operator = AEMReference.pack_enum(KAE::KAELessThan)
|
848
|
+
end
|
849
|
+
|
850
|
+
class LessOrEquals < ComparisonTest
|
851
|
+
Name = 'le'
|
852
|
+
Operator = AEMReference.pack_enum(KAE::KAELessThanEquals)
|
853
|
+
end
|
854
|
+
|
855
|
+
class StartsWith < ComparisonTest
|
856
|
+
Name = 'starts_with'
|
857
|
+
Operator = AEMReference.pack_enum(KAE::KAEBeginsWith)
|
858
|
+
end
|
859
|
+
|
860
|
+
class EndsWith < ComparisonTest
|
861
|
+
Name = 'ends_with'
|
862
|
+
Operator = AEMReference.pack_enum(KAE::KAEEndsWith)
|
863
|
+
end
|
864
|
+
|
865
|
+
class Contains < ComparisonTest
|
866
|
+
Name = 'contains'
|
867
|
+
Operator = AEMReference.pack_enum(KAE::KAEContains)
|
868
|
+
end
|
869
|
+
|
870
|
+
class IsIn < Contains
|
871
|
+
Name = 'is_in'
|
872
|
+
|
873
|
+
def AEM_pack_self(codecs)
|
874
|
+
return AEMReference.pack_list_as(KAE::TypeCompDescriptor, [
|
875
|
+
[KAE::KeyAEObject1, codecs.pack(@_operand2)],
|
876
|
+
[KAE::KeyAECompOperator, self.class::Operator],
|
877
|
+
[KAE::KeyAEObject2, codecs.pack(@_operand1)]
|
878
|
+
])
|
879
|
+
end
|
880
|
+
end
|
881
|
+
|
882
|
+
###################################
|
883
|
+
# Logical tests
|
884
|
+
|
885
|
+
class LogicalTest < Test
|
886
|
+
attr_reader :_operands
|
887
|
+
protected :_operands
|
888
|
+
|
889
|
+
def initialize(operands)
|
890
|
+
@_operands = operands
|
891
|
+
end
|
892
|
+
|
893
|
+
def to_s
|
894
|
+
op_str = (@_operands[1, @_operands.length].collect { |o| o.inspect }).join(', ')
|
895
|
+
return "#{@_operands[0].inspect}.#{self.class::Name}(#{op_str})"
|
896
|
+
end
|
897
|
+
|
898
|
+
def AEM_resolve(obj)
|
899
|
+
return @_operands[0].AEM_resolve(obj).send(self.class::Name, *@_operands[1, @_operands.length])
|
900
|
+
end
|
901
|
+
|
902
|
+
def AEM_pack_self(codecs)
|
903
|
+
return AEMReference.pack_list_as(KAE::TypeLogicalDescriptor, [
|
904
|
+
[KAE::KeyAELogicalOperator, self.class::Operator],
|
905
|
+
[KAE::KeyAELogicalTerms, codecs.pack(@_operands)]
|
906
|
+
])
|
907
|
+
end
|
908
|
+
end
|
909
|
+
|
910
|
+
##
|
911
|
+
|
912
|
+
class AND < LogicalTest
|
913
|
+
Operator = AEMReference.pack_enum(KAE::KAEAND)
|
914
|
+
Name = 'and'
|
915
|
+
end
|
916
|
+
|
917
|
+
class OR < LogicalTest
|
918
|
+
Operator = AEMReference.pack_enum(KAE::KAEOR)
|
919
|
+
Name = 'or'
|
920
|
+
end
|
921
|
+
|
922
|
+
class NOT < LogicalTest
|
923
|
+
Operator = AEMReference.pack_enum(KAE::KAENOT)
|
924
|
+
Name = 'not'
|
925
|
+
|
926
|
+
def to_s
|
927
|
+
return "#{@_operands[0].inspect}.not"
|
928
|
+
end
|
929
|
+
|
930
|
+
def AEM_resolve(obj)
|
931
|
+
return @_operands[0].AEM_resolve(obj).not
|
932
|
+
end
|
933
|
+
end
|
934
|
+
|
935
|
+
|
936
|
+
######################################################################
|
937
|
+
# REFERENCE ROOTS
|
938
|
+
######################################################################
|
939
|
+
|
940
|
+
###################################
|
941
|
+
# Base class
|
942
|
+
|
943
|
+
class ReferenceRoot < PositionSpecifier
|
944
|
+
|
945
|
+
def initialize
|
946
|
+
end
|
947
|
+
|
948
|
+
def to_s
|
949
|
+
return "AEM.#{self.class::Name}"
|
950
|
+
end
|
951
|
+
|
952
|
+
def _pack_self(codecs)
|
953
|
+
return self.class::Type
|
954
|
+
end
|
955
|
+
|
956
|
+
def AEM_root
|
957
|
+
return self
|
958
|
+
end
|
959
|
+
|
960
|
+
def AEM_resolve(obj)
|
961
|
+
return obj.send(self.class::Name)
|
962
|
+
end
|
963
|
+
end
|
964
|
+
|
965
|
+
|
966
|
+
###################################
|
967
|
+
# Concrete classes
|
968
|
+
|
969
|
+
class ApplicationRoot < ReferenceRoot
|
970
|
+
# Reference base; represents an application's application object. Used to construct full references.
|
971
|
+
|
972
|
+
# Syntax: app
|
973
|
+
|
974
|
+
Name = 'app'
|
975
|
+
Type = AE::AEDesc.new(KAE::TypeNull, '')
|
976
|
+
end
|
977
|
+
|
978
|
+
|
979
|
+
class CurrentContainer < ReferenceRoot
|
980
|
+
# Reference base; represents elements' container object. Used to construct by-range references.
|
981
|
+
|
982
|
+
# Syntax: con
|
983
|
+
|
984
|
+
Name = 'con'
|
985
|
+
Type = AE::AEDesc.new(KAE::TypeCurrentContainer, '')
|
986
|
+
end
|
987
|
+
|
988
|
+
|
989
|
+
class ObjectBeingExamined < ReferenceRoot
|
990
|
+
# Reference base; represents an element to be tested. Used to construct by-filter references.
|
991
|
+
|
992
|
+
# Syntax: its
|
993
|
+
|
994
|
+
Name = 'its'
|
995
|
+
Type = AE::AEDesc.new(KAE::TypeObjectBeingExamined, '')
|
996
|
+
end
|
997
|
+
|
998
|
+
|
999
|
+
###################################
|
1000
|
+
# Reference root objects; used to construct new specifiers, e.g. AEM.app.property('pnam')
|
1001
|
+
|
1002
|
+
App = ApplicationRoot.new
|
1003
|
+
Con = CurrentContainer.new
|
1004
|
+
Its = ObjectBeingExamined.new
|
1005
|
+
end
|
1006
|
+
|