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