s0nspark-rubyosa 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/AUTHORS +16 -0
- data/COPYRIGHT +25 -0
- data/README +49 -0
- data/bin/rdoc-osa +232 -0
- data/data/rubyosa/rdoc_html.rb +696 -0
- data/extconf.rb +62 -0
- data/sample/AddressBook/inspect.rb +31 -0
- data/sample/BBEdit/unix_script.rb +19 -0
- data/sample/Finder/show_desktop.rb +10 -0
- data/sample/Mail/get_selected_mail.rb +14 -0
- data/sample/Photoshop/new_doc.rb +13 -0
- data/sample/Photoshop/new_doc_with_text.rb +34 -0
- data/sample/QuickTime/play_all.rb +30 -0
- data/sample/TextEdit/hello_world.rb +19 -0
- data/sample/iChat/image.rb +18 -0
- data/sample/iChat/uptime.rb +15 -0
- data/sample/iTunes/artwork.rb +14 -0
- data/sample/iTunes/control.rb +66 -0
- data/sample/iTunes/fade_volume.rb +23 -0
- data/sample/iTunes/inspect.rb +16 -0
- data/sample/iTunes/name_that_tune.rb +97 -0
- data/sample/iTunes/tag_genre_lastfm.rb +32 -0
- data/sample/misc/sdef.rb +37 -0
- data/src/lib/rbosa.rb +1001 -0
- data/src/lib/rbosa_properties.rb +141 -0
- data/src/rbosa.c +720 -0
- data/src/rbosa.h +55 -0
- data/src/rbosa_conv.c +97 -0
- data/src/rbosa_err.c +105 -0
- data/src/rbosa_sdef.c +441 -0
- metadata +91 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
# For each selected track in iTunes, retrieve the genre from Last.fm and accordingly tag the track.
|
2
|
+
|
3
|
+
begin require 'rubygems'; rescue LoadError; end
|
4
|
+
require 'rbosa'
|
5
|
+
require 'net/http'
|
6
|
+
require 'cgi'
|
7
|
+
require 'rexml/document'
|
8
|
+
include REXML
|
9
|
+
|
10
|
+
itunes = OSA.app('iTunes')
|
11
|
+
|
12
|
+
selection = itunes.selection.get
|
13
|
+
if selection.empty?
|
14
|
+
$stderr.puts "Please select some tracks."
|
15
|
+
exit 1
|
16
|
+
end
|
17
|
+
|
18
|
+
first = selection.first.artist
|
19
|
+
feed = "http://ws.audioscrobbler.com/1.0/artist/#{CGI::escape(first)}/toptags.xml"
|
20
|
+
doc = Document.new(Net::HTTP.get(URI(feed)))
|
21
|
+
|
22
|
+
selection.each do |track|
|
23
|
+
if doc.root.attributes['artist'] == track.artist
|
24
|
+
genre = doc.root[1][1].text.capitalize
|
25
|
+
else
|
26
|
+
puts 'Querying Last.fm again...'
|
27
|
+
feed = "http://ws.audioscrobbler.com/1.0/artist/#{CGI::escape(track.artist)}/toptags.xml"
|
28
|
+
doc = Document.new(Net::HTTP.get(URI(feed)))
|
29
|
+
genre = doc.root[1][1].text.capitalize
|
30
|
+
end
|
31
|
+
track.genre = genre
|
32
|
+
end
|
data/sample/misc/sdef.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# Print the given application's sdef(5).
|
2
|
+
|
3
|
+
begin require 'rubygems'; rescue LoadError; end
|
4
|
+
require 'rbosa'
|
5
|
+
require 'rexml/document'
|
6
|
+
|
7
|
+
def usage
|
8
|
+
STDERR.puts <<-EOS
|
9
|
+
Usage: #{$0} [--name | --path | --bundle_id | --signature] ...
|
10
|
+
Examples:
|
11
|
+
#{$0} --name iTunes
|
12
|
+
#{$0} --path /Applications/iTunes.app
|
13
|
+
#{$0} --bundle_id com.apple.iTunes
|
14
|
+
#{$0} --signature hook
|
15
|
+
EOS
|
16
|
+
exit 1
|
17
|
+
end
|
18
|
+
|
19
|
+
usage unless ARGV.length == 2
|
20
|
+
|
21
|
+
key = case ARGV.first
|
22
|
+
when '--name'
|
23
|
+
:name
|
24
|
+
when '--path'
|
25
|
+
:path
|
26
|
+
when '--bundle_id'
|
27
|
+
:bundle_id
|
28
|
+
when '--signature'
|
29
|
+
:signature
|
30
|
+
else
|
31
|
+
usage
|
32
|
+
end
|
33
|
+
|
34
|
+
app = OSA.app(key => ARGV.last)
|
35
|
+
doc = REXML::Document.new(app.sdef)
|
36
|
+
doc.write(STDOUT, 0)
|
37
|
+
puts ""
|
data/src/lib/rbosa.rb
ADDED
@@ -0,0 +1,1001 @@
|
|
1
|
+
# Copyright (c) 2006-2007, Apple Inc. All rights reserved.
|
2
|
+
#
|
3
|
+
# Redistribution and use in source and binary forms, with or without
|
4
|
+
# modification, are permitted provided that the following conditions
|
5
|
+
# are met:
|
6
|
+
# 1. Redistributions of source code must retain the above copyright
|
7
|
+
# notice, this list of conditions and the following disclaimer.
|
8
|
+
# 2. Redistributions in binary form must reproduce the above copyright
|
9
|
+
# notice, this list of conditions and the following disclaimer in the
|
10
|
+
# documentation and/or other materials provided with the distribution.
|
11
|
+
# 3. Neither the name of Apple Inc. ("Apple") nor the names of
|
12
|
+
# its contributors may be used to endorse or promote products derived
|
13
|
+
# from this software without specific prior written permission.
|
14
|
+
#
|
15
|
+
# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND
|
16
|
+
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
17
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
18
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR
|
19
|
+
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
20
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
21
|
+
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
22
|
+
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
23
|
+
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
24
|
+
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
25
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
26
|
+
|
27
|
+
$KCODE = 'u' # we will use UTF-8 strings
|
28
|
+
|
29
|
+
require 'osa'
|
30
|
+
require 'date'
|
31
|
+
require 'uri'
|
32
|
+
require 'iconv'
|
33
|
+
require 'xml'
|
34
|
+
|
35
|
+
class String
|
36
|
+
def to_4cc
|
37
|
+
OSA.__four_char_code__(Iconv.iconv('MACROMAN', 'UTF-8', self).to_s)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class OSA::Enumerator
|
42
|
+
attr_reader :code, :name, :group_code
|
43
|
+
|
44
|
+
def initialize(const, name, code, group_code)
|
45
|
+
@const, @name, @code, @group_code = const, name, code, group_code
|
46
|
+
self.class.instances[code] = self
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.enum_for_code(code)
|
50
|
+
instances[code]
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_s
|
54
|
+
@name
|
55
|
+
end
|
56
|
+
|
57
|
+
def inspect
|
58
|
+
"<#{@const}>"
|
59
|
+
end
|
60
|
+
|
61
|
+
#######
|
62
|
+
private
|
63
|
+
#######
|
64
|
+
|
65
|
+
def self.instances
|
66
|
+
(@@instances rescue @@instances = {})
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class OSA::Element
|
71
|
+
REAL_NAME = CODE = nil
|
72
|
+
def to_rbobj
|
73
|
+
unless __type__ == 'null'
|
74
|
+
val = OSA.convert_to_ruby(self)
|
75
|
+
val == 'msng' ? nil : val == nil ? self : val
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.from_rbobj(requested_type, value, enum_group_codes)
|
80
|
+
obj = OSA.convert_to_osa(requested_type, value, enum_group_codes)
|
81
|
+
obj.is_a?(OSA::Element) ? obj : self.__new__(*obj)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class OSA::ElementList
|
86
|
+
include Enumerable
|
87
|
+
def each
|
88
|
+
self.size.times { |i| yield(self[i]) }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class OSA::ElementRecord
|
93
|
+
def self.from_hash(hash)
|
94
|
+
value = {}
|
95
|
+
hash.each do |code, val|
|
96
|
+
key = OSA.sym_to_code(code)
|
97
|
+
if key.nil?
|
98
|
+
raise ArgumentError, "invalid key `#{code}'" if code.to_s.length != 4
|
99
|
+
key = code
|
100
|
+
end
|
101
|
+
value[key] = OSA::Element.from_rbobj(nil, val, nil)
|
102
|
+
end
|
103
|
+
OSA::ElementRecord.__new__(value)
|
104
|
+
end
|
105
|
+
|
106
|
+
def to_hash
|
107
|
+
h = {}
|
108
|
+
self.to_a.each do |code, val|
|
109
|
+
key = (OSA.code_to_sym(code) or code)
|
110
|
+
h[key] = val.to_rbobj
|
111
|
+
end
|
112
|
+
return h
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
module OSA::ObjectSpecifier
|
117
|
+
def get
|
118
|
+
new_obj = @app.__send_event__('core', 'getd', [['----', self]], true).to_rbobj
|
119
|
+
if !new_obj.is_a?(self.class) and new_obj.is_a?(OSA::Element) and self.respond_to?(:properties) and (klass = self.properties[:class])
|
120
|
+
klass.__duplicate__(new_obj)
|
121
|
+
else
|
122
|
+
new_obj
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
class OSA::ObjectSpecifierList
|
128
|
+
include Enumerable
|
129
|
+
|
130
|
+
def initialize(app, desired_class, container)
|
131
|
+
@app, @desired_class, @container = app, desired_class, container
|
132
|
+
end
|
133
|
+
|
134
|
+
def length
|
135
|
+
@app.__send_event__(
|
136
|
+
'core', 'cnte',
|
137
|
+
[['----', @container], ['kocl', OSA::Element.__new__('type', @desired_class::CODE.to_4cc)]],
|
138
|
+
true).to_rbobj
|
139
|
+
end
|
140
|
+
alias_method :size, :length
|
141
|
+
|
142
|
+
def empty?
|
143
|
+
length == 0
|
144
|
+
end
|
145
|
+
|
146
|
+
def [](idx)
|
147
|
+
idx += 1 # AE starts counting at 1.
|
148
|
+
o = obj_spec_with_key(OSA::Element.__new__('long', [idx].pack('l')))
|
149
|
+
o.instance_variable_set(:@app, @app)
|
150
|
+
o.extend OSA::ObjectSpecifier
|
151
|
+
end
|
152
|
+
|
153
|
+
def first
|
154
|
+
self[0]
|
155
|
+
end
|
156
|
+
|
157
|
+
def last
|
158
|
+
self[-1]
|
159
|
+
end
|
160
|
+
|
161
|
+
def each
|
162
|
+
self.length.times { |i| yield(self[i]) }
|
163
|
+
end
|
164
|
+
|
165
|
+
def get
|
166
|
+
o = obj_spec_with_key(OSA::Element.__new__('abso', 'all '.to_4cc))
|
167
|
+
o.instance_variable_set(:@app, @app)
|
168
|
+
o.extend OSA::ObjectSpecifier
|
169
|
+
o.get
|
170
|
+
end
|
171
|
+
alias_method :to_a, :get
|
172
|
+
|
173
|
+
def ==(other)
|
174
|
+
other.kind_of?(self.class) \
|
175
|
+
and other.length == self.length \
|
176
|
+
and (0..other.length).all? { |i| other[i] == self[i] }
|
177
|
+
end
|
178
|
+
|
179
|
+
def inspect
|
180
|
+
super.scan(/^([^ ]+)/).to_s << " desired_class=#{@desired_class}>"
|
181
|
+
end
|
182
|
+
|
183
|
+
def every(sym)
|
184
|
+
if @desired_class.method_defined?(sym) and code = OSA.sym_to_code(sym)
|
185
|
+
o = obj_spec_with_key(OSA::Element.__new__('abso', 'all '.to_4cc))
|
186
|
+
pklass = @app.classes[code]
|
187
|
+
if pklass.nil? or !OSA.lazy_events
|
188
|
+
@app.__send_event__('core', 'getd',
|
189
|
+
[['----', OSA::Element.__new_object_specifier__('prop', o,
|
190
|
+
'prop', OSA::Element.__new__('type', code.to_4cc))]],
|
191
|
+
true).to_rbobj
|
192
|
+
else
|
193
|
+
OSA::ObjectSpecifierList.new(@app, pklass, o)
|
194
|
+
end
|
195
|
+
else
|
196
|
+
raise ArgumentError, "desired class `#{@desired_class}' does not have a attribute named `#{sym.to_s}'"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
#######
|
201
|
+
private
|
202
|
+
#######
|
203
|
+
|
204
|
+
def obj_spec_with_key(element)
|
205
|
+
@desired_class.__new_object_specifier__(@desired_class::CODE, @container,
|
206
|
+
'indx', element)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
module OSA::EventDispatcher
|
211
|
+
|
212
|
+
SCRIPTING_ADDITIONS_DIR = [
|
213
|
+
'/System/Library/ScriptingAdditions',
|
214
|
+
'/Library/ScriptingAdditions'
|
215
|
+
]
|
216
|
+
if home = ENV['HOME']
|
217
|
+
SCRIPTING_ADDITIONS_DIR << File.join(home, '/Library/ScriptingAdditions')
|
218
|
+
end
|
219
|
+
|
220
|
+
def merge(args)
|
221
|
+
args = { :name => args } if args.is_a?(String)
|
222
|
+
by_name = args[:name]
|
223
|
+
begin
|
224
|
+
name, target, sdef = OSA.__scripting_info__(args)
|
225
|
+
rescue RuntimeError => excp
|
226
|
+
# If an sdef bundle can't be find by name, let's be clever and look in the ScriptingAdditions locations.
|
227
|
+
if by_name
|
228
|
+
args = SCRIPTING_ADDITIONS_DIR.each do |dir|
|
229
|
+
path = ['.app', '.osax'].map { |e| File.join(dir, by_name + e) }.find { |p| File.exists?(p) }
|
230
|
+
if path
|
231
|
+
break { :path => path }
|
232
|
+
end
|
233
|
+
end
|
234
|
+
if args.is_a?(Hash)
|
235
|
+
by_name = nil
|
236
|
+
retry
|
237
|
+
end
|
238
|
+
end
|
239
|
+
raise excp
|
240
|
+
end
|
241
|
+
app_module_name = self.class.name.scan(/^OSA::(.+)::.+$/).flatten.first
|
242
|
+
app_module = OSA.const_get(app_module_name)
|
243
|
+
OSA.__load_sdef__(sdef, target, app_module, true, self.class)
|
244
|
+
(self.__send_event__('ascr', 'gdut', [], true) rescue nil) # Don't ask me why...
|
245
|
+
return self
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
module OSA
|
250
|
+
def self.app_with_name(name)
|
251
|
+
STDERR.puts "OSA.app_with_name() has been deprecated and its usage is now discouraged. Please use OSA.app('name') instead."
|
252
|
+
self.__app__(*OSA.__scripting_info__(:name => name))
|
253
|
+
end
|
254
|
+
|
255
|
+
def self.app_with_path(path)
|
256
|
+
STDERR.puts "OSA.app_by_path() has been deprecated and its usage is now discouraged. Please use OSA.app(:path => 'path') instead."
|
257
|
+
self.__app__(*OSA.__scripting_info__(:path => path))
|
258
|
+
end
|
259
|
+
|
260
|
+
def self.app_by_bundle_id(bundle_id)
|
261
|
+
STDERR.puts "OSA.app_by_bundle_id() has been deprecated and its usage is now discouraged. Please use OSA.app(:bundle_id => 'bundle_id') instead."
|
262
|
+
self.__app__(*OSA.__scripting_info__(:bundle_id => bundle_id))
|
263
|
+
end
|
264
|
+
|
265
|
+
def self.app_by_signature(signature)
|
266
|
+
STDERR.puts "OSA.app_by_signature() has been deprecated and its usage is now discouraged. Please use OSA.app(:signature => 'signature') instead."
|
267
|
+
self.__app__(*OSA.__scripting_info__(:signature => signature))
|
268
|
+
end
|
269
|
+
|
270
|
+
def self.app(*args)
|
271
|
+
if args.size == 2
|
272
|
+
if args.first.is_a?(String) and args.last.is_a?(Hash)
|
273
|
+
hash = args.last
|
274
|
+
if hash.has_key?(:name)
|
275
|
+
warn "Given Hash argument already has a :name key, ignoring the first String argument `#{args.first}'"
|
276
|
+
else
|
277
|
+
hash[:name] = args.first
|
278
|
+
end
|
279
|
+
args = hash
|
280
|
+
else
|
281
|
+
raise ArgumentError, "when called with 2 arguments, the first is supposed to be a String and the second a Hash"
|
282
|
+
end
|
283
|
+
else
|
284
|
+
if args.size != 1
|
285
|
+
raise ArgumentError, "wrong number of arguments (#{args.size} for at least 1)"
|
286
|
+
end
|
287
|
+
args = args.first
|
288
|
+
end
|
289
|
+
args = { :name => args } if args.is_a?(String)
|
290
|
+
self.__app__(*OSA.__scripting_info__(args))
|
291
|
+
end
|
292
|
+
|
293
|
+
@conversions_to_ruby = {}
|
294
|
+
@conversions_to_osa = {}
|
295
|
+
|
296
|
+
def self.add_conversion(hash, types, block, max_arity, replace=false)
|
297
|
+
raise "Conversion block has to accept either #{(1..max_arity).to_a.join(', ')} arguments" unless (1..max_arity) === block.arity
|
298
|
+
types.each do |type|
|
299
|
+
next if !replace and hash.has_key?(type)
|
300
|
+
hash[type] = block
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
def self.replace_conversion_to_ruby(*types, &block)
|
305
|
+
add_conversion(@conversions_to_ruby, types, block, 3, true)
|
306
|
+
end
|
307
|
+
|
308
|
+
def self.add_conversion_to_ruby(*types, &block)
|
309
|
+
add_conversion(@conversions_to_ruby, types, block, 3)
|
310
|
+
end
|
311
|
+
|
312
|
+
def self.replace_conversion_to_osa(*types, &block)
|
313
|
+
add_conversion(@conversions_to_osa, types, block, 2, true)
|
314
|
+
end
|
315
|
+
|
316
|
+
def self.add_conversion_to_osa(*types, &block)
|
317
|
+
add_conversion(@conversions_to_osa, types, block, 2)
|
318
|
+
end
|
319
|
+
|
320
|
+
def self.convert_to_ruby(osa_object)
|
321
|
+
osa_type = osa_object.__type__
|
322
|
+
osa_data = osa_object.__data__(osa_type) if osa_type and osa_type != 'null'
|
323
|
+
if conversion = @conversions_to_ruby[osa_type]
|
324
|
+
args = [osa_data, osa_type, osa_object]
|
325
|
+
conversion.call(*args[0..(conversion.arity - 1)])
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
def self.__convert_to_osa__(requested_type, value, enum_group_codes=nil)
|
330
|
+
return value if value.is_a?(OSA::Element)
|
331
|
+
if conversion = @conversions_to_osa[requested_type]
|
332
|
+
args = [value, requested_type]
|
333
|
+
conversion.call(*args[0..(conversion.arity - 1)])
|
334
|
+
elsif enum_group_codes and enum_group_codes.include?(requested_type)
|
335
|
+
if value.is_a?(Array)
|
336
|
+
ary = value.map { |x| OSA::Element.__new__('enum', x.code.to_4cc) }
|
337
|
+
ElementList.__new__(ary)
|
338
|
+
else
|
339
|
+
['enum', value.code.to_4cc]
|
340
|
+
end
|
341
|
+
elsif md = /^list_of_(.+)$/.match(requested_type)
|
342
|
+
ary = value.to_a.map do |elem|
|
343
|
+
obj = convert_to_osa(md[1], elem, enum_group_codes)
|
344
|
+
obj.is_a?(OSA::Element) ? obj : OSA::Element.__new__(*obj)
|
345
|
+
end
|
346
|
+
ElementList.__new__(ary)
|
347
|
+
else
|
348
|
+
STDERR.puts "unrecognized type #{requested_type}" if $VERBOSE
|
349
|
+
['null', nil]
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
def self.convert_to_osa(requested_type, value, enum_group_codes=nil)
|
354
|
+
ary = __convert_to_osa__(requested_type, value, enum_group_codes)
|
355
|
+
if ary == ['null', nil]
|
356
|
+
new_type = case value
|
357
|
+
when String then 'text'
|
358
|
+
when Array then 'list'
|
359
|
+
when Hash then 'record'
|
360
|
+
when Integer then 'integer'
|
361
|
+
when Float then 'double'
|
362
|
+
end
|
363
|
+
if new_type
|
364
|
+
ary = __convert_to_osa__(new_type, value, enum_group_codes)
|
365
|
+
end
|
366
|
+
end
|
367
|
+
ary
|
368
|
+
end
|
369
|
+
|
370
|
+
def self.set_params(hash)
|
371
|
+
previous_values = {}
|
372
|
+
hash.each do |key, val|
|
373
|
+
unless OSA.respond_to?(key)
|
374
|
+
raise ArgumentError, "Invalid key value (no parameter named #{key} was found)"
|
375
|
+
end
|
376
|
+
ivar_key = '@' + key.to_s
|
377
|
+
previous_val = self.instance_variable_get(ivar_key)
|
378
|
+
previous_values[ivar_key] = previous_val;
|
379
|
+
self.instance_variable_set(ivar_key, hash[key])
|
380
|
+
end
|
381
|
+
if block_given?
|
382
|
+
yield
|
383
|
+
previous_values.each { |key, val| self.instance_variable_set(key, val) }
|
384
|
+
end
|
385
|
+
nil
|
386
|
+
end
|
387
|
+
|
388
|
+
#######
|
389
|
+
private
|
390
|
+
#######
|
391
|
+
|
392
|
+
class DocItem
|
393
|
+
attr_reader :name, :description
|
394
|
+
def initialize(name, description, optional=false)
|
395
|
+
@name = name
|
396
|
+
@description = description
|
397
|
+
@optional = optional
|
398
|
+
end
|
399
|
+
def optional?
|
400
|
+
@optional
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
class DocMethod < DocItem
|
405
|
+
attr_reader :result, :args
|
406
|
+
def initialize(name, description, result, args)
|
407
|
+
super(name, description)
|
408
|
+
@result = result
|
409
|
+
@args = args
|
410
|
+
end
|
411
|
+
def inspect
|
412
|
+
"<Method #{name} (#{description})>"
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
def self.__app__(name, target, sdef)
|
417
|
+
@apps ||= {}
|
418
|
+
app = @apps[target]
|
419
|
+
return app if app
|
420
|
+
|
421
|
+
# Creates a module for this app, we will define the scripting interface within it.
|
422
|
+
app_module = Module.new
|
423
|
+
self.const_set(rubyfy_constant_string(name), app_module)
|
424
|
+
|
425
|
+
@apps[target] = __load_sdef__(sdef, target, app_module)
|
426
|
+
end
|
427
|
+
|
428
|
+
def self.__load_sdef__(sdef, target, app_module, merge_only=false, app_class=nil)
|
429
|
+
# Load the sdef.
|
430
|
+
doc = XML::Parser.string(sdef).parse
|
431
|
+
|
432
|
+
# Retrieves and creates enumerations.
|
433
|
+
enum_group_codes = {}
|
434
|
+
doc.find('/dictionary/suite/enumeration').each do |element|
|
435
|
+
enum_group_code = element['code']
|
436
|
+
enum_module_name = rubyfy_constant_string(element['name'], true)
|
437
|
+
enum_module = Module.new
|
438
|
+
enum_group_codes[enum_group_code] = enum_module
|
439
|
+
|
440
|
+
documentation = []
|
441
|
+
enum_module.const_set(:DESCRIPTION, documentation)
|
442
|
+
|
443
|
+
element.find('enumerator').each do |element|
|
444
|
+
name = element['name']
|
445
|
+
enum_name = rubyfy_constant_string(name, true)
|
446
|
+
enum_code = element['code']
|
447
|
+
enum_const = app_module.name + '::' + enum_module_name + '::' + enum_name
|
448
|
+
|
449
|
+
enum = OSA::Enumerator.new(enum_const, name, enum_code, enum_group_code)
|
450
|
+
enum_module.const_set(enum_name, enum)
|
451
|
+
|
452
|
+
documentation << DocItem.new(enum_name, englishify_sentence(element['description']))
|
453
|
+
end
|
454
|
+
|
455
|
+
app_module.const_set(enum_module_name, enum_module) unless app_module.const_defined?(enum_module_name)
|
456
|
+
end
|
457
|
+
|
458
|
+
# Retrieves and creates classes.
|
459
|
+
classes = {}
|
460
|
+
class_elements = {}
|
461
|
+
doc.find('/dictionary/suite/class').each do |element|
|
462
|
+
key = (element['id'] or element['name'])
|
463
|
+
(class_elements[key] ||= []) << element
|
464
|
+
end
|
465
|
+
class_elements.values.flatten.each do |element|
|
466
|
+
klass = add_class_from_xml_element(element, class_elements, classes, app_module)
|
467
|
+
methods_doc = []
|
468
|
+
description = englishify_sentence(element['description'])
|
469
|
+
if klass.const_defined?(:DESCRIPTION)
|
470
|
+
klass.const_set(:DESCRIPTION, description) if klass.const_get(:DESCRIPTION).nil?
|
471
|
+
else
|
472
|
+
klass.const_set(:DESCRIPTION, description)
|
473
|
+
end
|
474
|
+
if klass.const_defined?(:METHODS_DESCRIPTION)
|
475
|
+
methods_doc = klass.const_get(:METHODS_DESCRIPTION)
|
476
|
+
else
|
477
|
+
methods_doc = []
|
478
|
+
klass.const_set(:METHODS_DESCRIPTION, methods_doc)
|
479
|
+
end
|
480
|
+
|
481
|
+
# Creates properties.
|
482
|
+
# Add basic properties that might be missing to the Item class (if any).
|
483
|
+
props = {}
|
484
|
+
element.find('property').each do |x|
|
485
|
+
props[x['name']] = [x['code'], type_of_parameter(x), x['access'], x['description']]
|
486
|
+
end
|
487
|
+
if klass.name[-6..-1] == '::Item'
|
488
|
+
unless props.has_key?('id')
|
489
|
+
props['id'] = ['ID ', 'integer', 'r', 'the unique ID of the item']
|
490
|
+
end
|
491
|
+
end
|
492
|
+
props.each do |name, pary|
|
493
|
+
code, type, access, description = pary
|
494
|
+
setter = (access == nil or access.include?('w'))
|
495
|
+
|
496
|
+
if type == 'reference'
|
497
|
+
pklass = OSA::Element
|
498
|
+
else
|
499
|
+
pklass = classes[type]
|
500
|
+
if pklass.nil?
|
501
|
+
pklass_elements = class_elements[type]
|
502
|
+
unless pklass_elements.nil?
|
503
|
+
pklass = add_class_from_xml_element(pklass_elements.first, class_elements, classes, app_module)
|
504
|
+
end
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
# Implicit 'get' if the property class is primitive (not defined in the sdef),
|
509
|
+
# otherwise just return an object specifier.
|
510
|
+
method_name = rubyfy_method(name, klass, type)
|
511
|
+
method_proc = if pklass.nil?
|
512
|
+
proc do
|
513
|
+
@app.__send_event__('core', 'getd',
|
514
|
+
[['----', Element.__new_object_specifier__('prop', @app == self ? Element.__new__('null', nil) : self,
|
515
|
+
'prop', Element.__new__('type', code.to_4cc))]],
|
516
|
+
true).to_rbobj
|
517
|
+
end
|
518
|
+
else
|
519
|
+
proc do
|
520
|
+
o = pklass.__new_object_specifier__('prop', @app == self ? Element.__new__('null', nil) : self,
|
521
|
+
'prop', Element.__new__('type', code.to_4cc))
|
522
|
+
unless OSA.lazy_events?
|
523
|
+
@app.__send_event__('core', 'getd', [['----', o]], true).to_rbobj
|
524
|
+
else
|
525
|
+
o.instance_variable_set(:@app, @app)
|
526
|
+
o.extend(OSA::ObjectSpecifier)
|
527
|
+
end
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
klass.class_eval { define_method(method_name, method_proc) }
|
532
|
+
ptypedoc = if pklass.nil?
|
533
|
+
type_doc(type, enum_group_codes, app_module)
|
534
|
+
else
|
535
|
+
"a #{pklass} object"
|
536
|
+
end
|
537
|
+
if description
|
538
|
+
description[0] = description[0].chr.downcase
|
539
|
+
description = '-- ' << description
|
540
|
+
end
|
541
|
+
methods_doc << DocMethod.new(method_name, englishify_sentence("Gets the #{name} property #{description}"), DocItem.new('result', englishify_sentence("the property value, as #{ptypedoc}")), nil)
|
542
|
+
|
543
|
+
# For the setter, always send an event.
|
544
|
+
if setter
|
545
|
+
method_name = rubyfy_method(name, klass, type, true)
|
546
|
+
method_proc = proc do |val|
|
547
|
+
@app.__send_event__('core', 'setd',
|
548
|
+
[['----', Element.__new_object_specifier__('prop', @app == self ? Element.__new__('null', nil) : self,
|
549
|
+
'prop', Element.__new__('type', code.to_4cc))],
|
550
|
+
['data', val.is_a?(OSA::Element) ? val : Element.from_rbobj(type, val, enum_group_codes.keys)]],
|
551
|
+
true)
|
552
|
+
return nil
|
553
|
+
end
|
554
|
+
klass.class_eval { define_method(method_name, method_proc) }
|
555
|
+
methods_doc << DocMethod.new(method_name, englishify_sentence("Sets the #{name} property #{description}"), nil, [DocItem.new('val', englishify_sentence("the value to be set, as #{ptypedoc}"))])
|
556
|
+
end
|
557
|
+
|
558
|
+
OSA.add_property(name.intern, code)
|
559
|
+
end
|
560
|
+
|
561
|
+
# Creates elements.
|
562
|
+
element.find('element').each do |eelement|
|
563
|
+
type = eelement['type']
|
564
|
+
|
565
|
+
eklass = classes[type]
|
566
|
+
if eklass.nil?
|
567
|
+
eklass_elements = class_elements[type]
|
568
|
+
unless eklass_elements.nil?
|
569
|
+
eklass = add_class_from_xml_element(eklass_elements.first, class_elements, classes, app_module)
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
if eklass.nil?
|
574
|
+
STDERR.puts "Cannot find class '#{type}', skipping element '#{eelement}'" if $DEBUG
|
575
|
+
next
|
576
|
+
end
|
577
|
+
|
578
|
+
method_name = rubyfy_method(eklass::PLURAL, klass)
|
579
|
+
method_proc = proc do
|
580
|
+
unless OSA.lazy_events?
|
581
|
+
@app.__send_event__('core', 'getd',
|
582
|
+
[['----', Element.__new_object_specifier__(
|
583
|
+
eklass::CODE.to_4cc, @app == self ? Element.__new__('null', nil) : self,
|
584
|
+
'indx', Element.__new__('abso', 'all '.to_4cc))]],
|
585
|
+
true).to_rbobj
|
586
|
+
else
|
587
|
+
ObjectSpecifierList.new(@app, eklass, @app == self ? Element.__new__('null', nil) : self)
|
588
|
+
end
|
589
|
+
end
|
590
|
+
klass.class_eval { define_method(method_name, method_proc) }
|
591
|
+
methods_doc << DocMethod.new(method_name, englishify_sentence("Gets the #{eklass::PLURAL} associated with this object"), DocItem.new('result', englishify_sentence("an Array of #{eklass} objects")), nil)
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
unless merge_only
|
596
|
+
# Having an 'application' class is required.
|
597
|
+
app_class = classes['application']
|
598
|
+
raise "No application class defined." if app_class.nil?
|
599
|
+
all_classes_but_app = classes.values.reject { |x| x.ancestors.include?(OSA::EventDispatcher) }
|
600
|
+
else
|
601
|
+
all_classes_but_app = classes.values
|
602
|
+
end
|
603
|
+
|
604
|
+
# Maps commands to the right classes.
|
605
|
+
doc.find('/dictionary/suite/command').each do |element|
|
606
|
+
name = element['name']
|
607
|
+
next if /NOT AVAILABLE/.match(name) # Finder's sdef (Tiger) names some commands with this 'tag'.
|
608
|
+
description = element['description']
|
609
|
+
direct_parameter = element.find_first('direct-parameter')
|
610
|
+
result = element.find_first('result')
|
611
|
+
has_result = result != nil
|
612
|
+
|
613
|
+
code = element['code']
|
614
|
+
begin
|
615
|
+
code = Iconv.iconv('MACROMAN', 'UTF-8', code).to_s
|
616
|
+
rescue Iconv::IllegalSequence
|
617
|
+
# We can't do more...
|
618
|
+
STDERR.puts "unrecognized command code encoding '#{code}', skipping..." if $DEBUG
|
619
|
+
next
|
620
|
+
end
|
621
|
+
|
622
|
+
classes_to_define = []
|
623
|
+
forget_direct_parameter = true
|
624
|
+
direct_parameter_optional = false
|
625
|
+
|
626
|
+
if direct_parameter.nil?
|
627
|
+
# No direct parameter, this is for the application class.
|
628
|
+
classes_to_define << app_class
|
629
|
+
else
|
630
|
+
# We have a direct parameter:
|
631
|
+
# - map it to the right class if it's a class defined in our scripting dictionary
|
632
|
+
# - map it to all classes if it's a 'reference' and to the application class if it's optional
|
633
|
+
# - otherwise, just map it to the application class.
|
634
|
+
type = type_of_parameter(direct_parameter)
|
635
|
+
direct_parameter_optional = parameter_optional?(direct_parameter)
|
636
|
+
|
637
|
+
if type == 'reference'
|
638
|
+
classes_to_define = all_classes_but_app
|
639
|
+
classes_to_define << app_class if direct_parameter_optional
|
640
|
+
else
|
641
|
+
klass = classes[type]
|
642
|
+
if klass.nil?
|
643
|
+
forget_direct_parameter = false
|
644
|
+
classes_to_define << app_class
|
645
|
+
else
|
646
|
+
classes_to_define << klass
|
647
|
+
end
|
648
|
+
end
|
649
|
+
end
|
650
|
+
|
651
|
+
# Reject classes which are already represented by an ancestor.
|
652
|
+
if classes_to_define.length > 1
|
653
|
+
classes_to_define.uniq!
|
654
|
+
classes_to_define.reject! do |x|
|
655
|
+
classes_to_define.any? { |y| x != y and x.ancestors.include?(y) }
|
656
|
+
end
|
657
|
+
end
|
658
|
+
|
659
|
+
params = []
|
660
|
+
params_doc = []
|
661
|
+
unless direct_parameter.nil?
|
662
|
+
pdesc = direct_parameter['description']
|
663
|
+
params << [
|
664
|
+
'direct',
|
665
|
+
'----',
|
666
|
+
direct_parameter_optional,
|
667
|
+
type_of_parameter(direct_parameter)
|
668
|
+
]
|
669
|
+
unless forget_direct_parameter
|
670
|
+
params_doc << DocItem.new('direct', englishify_sentence(pdesc))
|
671
|
+
end
|
672
|
+
end
|
673
|
+
|
674
|
+
element.find('parameter').to_a.each do |element|
|
675
|
+
poptional = parameter_optional?(element)
|
676
|
+
params << [
|
677
|
+
rubyfy_string(element['name']),
|
678
|
+
element['code'],
|
679
|
+
poptional,
|
680
|
+
type_of_parameter(element)
|
681
|
+
]
|
682
|
+
params_doc << DocItem.new(rubyfy_string(element['name'], true), englishify_sentence(element['description']), poptional)
|
683
|
+
end
|
684
|
+
|
685
|
+
method_proc = proc do |*args_ary|
|
686
|
+
args = []
|
687
|
+
min_argc = i = 0
|
688
|
+
already_has_optional_args = false # Once an argument is optional, all following arguments should be optional.
|
689
|
+
optional_hash = nil
|
690
|
+
params.each do |pname, pcode, optional, ptype|
|
691
|
+
self_direct = (pcode == '----' and forget_direct_parameter)
|
692
|
+
if already_has_optional_args or (optional and !self_direct)
|
693
|
+
already_has_optional_args = true
|
694
|
+
else
|
695
|
+
if args_ary.size < i
|
696
|
+
raise ArgumentError, "wrong number of arguments (#{args_ary.size} for #{i})"
|
697
|
+
end
|
698
|
+
end
|
699
|
+
val = if self_direct
|
700
|
+
self.is_a?(OSA::EventDispatcher) ? [] : ['----', self]
|
701
|
+
else
|
702
|
+
arg = args_ary[i]
|
703
|
+
min_argc += 1 unless already_has_optional_args
|
704
|
+
i += 1
|
705
|
+
if arg.is_a?(Hash) and already_has_optional_args and i >= args_ary.size and min_argc + 1 == i
|
706
|
+
optional_hash = arg
|
707
|
+
end
|
708
|
+
if optional_hash
|
709
|
+
arg = optional_hash.delete(pname.intern)
|
710
|
+
end
|
711
|
+
if arg.nil?
|
712
|
+
if already_has_optional_args
|
713
|
+
[]
|
714
|
+
end
|
715
|
+
else
|
716
|
+
[pcode, arg.is_a?(OSA::Element) ? arg : OSA::Element.from_rbobj(ptype, arg, enum_group_codes.keys)]
|
717
|
+
end
|
718
|
+
end
|
719
|
+
args << val
|
720
|
+
end
|
721
|
+
if args_ary.size > params.size or args_ary.size < min_argc
|
722
|
+
raise ArgumentError, "wrong number of arguments (#{args_ary.size} for #{min_argc})"
|
723
|
+
end
|
724
|
+
if optional_hash and !optional_hash.empty?
|
725
|
+
raise ArgumentError, "inappropriate optional argument(s): #{optional_hash.keys.join(', ')}"
|
726
|
+
end
|
727
|
+
wait_reply = (OSA.wait_reply != nil ? OSA.wait_reply : (has_result or @app.remote?))
|
728
|
+
ret = @app.__send_event__(code[0..3], code[4..-1], args, wait_reply)
|
729
|
+
wait_reply ? ret.to_rbobj : ret
|
730
|
+
end
|
731
|
+
|
732
|
+
unless has_result
|
733
|
+
result_type = result_doc = nil
|
734
|
+
else
|
735
|
+
result_type = type_of_parameter(result)
|
736
|
+
result_klass = classes[result_type]
|
737
|
+
result_doc = DocItem.new('result', englishify_sentence(result['description']))
|
738
|
+
end
|
739
|
+
|
740
|
+
classes_to_define.each do |klass|
|
741
|
+
method_name = rubyfy_method(name, klass, result_type)
|
742
|
+
klass.class_eval { define_method(method_name, method_proc) }
|
743
|
+
methods_doc = klass.const_get(:METHODS_DESCRIPTION)
|
744
|
+
methods_doc << DocMethod.new(method_name, englishify_sentence(description), result_doc, params_doc)
|
745
|
+
end
|
746
|
+
|
747
|
+
# Merge some additional commands, if necessary.
|
748
|
+
unless app_class.method_defined?(:activate)
|
749
|
+
app_class.class_eval do
|
750
|
+
define_method(:activate) do
|
751
|
+
__send_event__('misc', 'actv', [], true)
|
752
|
+
nil
|
753
|
+
end
|
754
|
+
end
|
755
|
+
methods_doc = app_class.const_get(:METHODS_DESCRIPTION)
|
756
|
+
methods_doc << DocMethod.new('activate', 'Activate the application.', nil, [])
|
757
|
+
end
|
758
|
+
end
|
759
|
+
|
760
|
+
unless merge_only
|
761
|
+
# Returns an application instance, that's all folks!
|
762
|
+
hash = {}
|
763
|
+
classes.each_value { |klass| hash[klass::CODE] = klass }
|
764
|
+
app_class.class_eval do
|
765
|
+
attr_reader :sdef, :classes
|
766
|
+
define_method(:remote?) { @is_remote == true }
|
767
|
+
end
|
768
|
+
is_remote = target.length > 4
|
769
|
+
app = is_remote ? app_class.__new__('aprl', target) : app_class.__new__('sign', target.to_4cc)
|
770
|
+
app.instance_variable_set(:@is_remote, is_remote)
|
771
|
+
app.instance_variable_set(:@sdef, sdef)
|
772
|
+
app.instance_variable_set(:@classes, hash)
|
773
|
+
app.extend OSA::EventDispatcher
|
774
|
+
end
|
775
|
+
end
|
776
|
+
|
777
|
+
def self.parameter_optional?(element)
|
778
|
+
element['optional'] == 'yes'
|
779
|
+
end
|
780
|
+
|
781
|
+
def self.add_class_from_xml_element(element, class_elements, repository, app_module)
|
782
|
+
real_name = element['name']
|
783
|
+
key = (element['id'] or real_name)
|
784
|
+
klass = repository[key]
|
785
|
+
if klass.nil?
|
786
|
+
code = element['code']
|
787
|
+
inherits = element['inherits']
|
788
|
+
plural = element['plural']
|
789
|
+
|
790
|
+
if real_name == inherits
|
791
|
+
# Inheriting from itself is a common idiom when adding methods
|
792
|
+
# to a class that has already been defined, probably to avoid
|
793
|
+
# mentioning the subclass name more than once.
|
794
|
+
inherits = nil
|
795
|
+
end
|
796
|
+
|
797
|
+
if inherits.nil?
|
798
|
+
klass = Class.new(OSA::Element)
|
799
|
+
else
|
800
|
+
super_elements = class_elements[inherits]
|
801
|
+
super_class = if super_elements.nil?
|
802
|
+
STDERR.puts "sdef bug: class '#{real_name}' inherits from '#{inherits}' which is not defined - fall back inheriting from OSA::Element" if $DEBUG
|
803
|
+
OSA::Element
|
804
|
+
else
|
805
|
+
add_class_from_xml_element(super_elements.first, class_elements, repository, app_module)
|
806
|
+
end
|
807
|
+
klass = Class.new(super_class)
|
808
|
+
end
|
809
|
+
|
810
|
+
klass.class_eval { include OSA::EventDispatcher } if real_name == 'application'
|
811
|
+
|
812
|
+
klass.const_set(:REAL_NAME, real_name) unless klass.const_defined?(:REAL_NAME)
|
813
|
+
klass.const_set(:PLURAL, plural == nil ? real_name + 's' : plural) unless klass.const_defined?(:PLURAL)
|
814
|
+
klass.const_set(:CODE, code) unless klass.const_defined?(:CODE)
|
815
|
+
|
816
|
+
app_module.const_set(rubyfy_constant_string(real_name), klass)
|
817
|
+
|
818
|
+
repository[key] = klass
|
819
|
+
end
|
820
|
+
|
821
|
+
return klass
|
822
|
+
end
|
823
|
+
|
824
|
+
def self.type_doc(type, enum_group_codes, app_module)
|
825
|
+
if mod = enum_group_codes[type]
|
826
|
+
mod.to_s
|
827
|
+
elsif md = /^list_of_(.+)$/.match(type)
|
828
|
+
"list of #{type_doc(md[1], enum_group_codes, app_module)}"
|
829
|
+
else
|
830
|
+
up_type = type.upcase
|
831
|
+
begin
|
832
|
+
app_module.const_get(up_type).to_s
|
833
|
+
rescue
|
834
|
+
type
|
835
|
+
end
|
836
|
+
end
|
837
|
+
end
|
838
|
+
|
839
|
+
def self.type_of_parameter(element)
|
840
|
+
type = element['type']
|
841
|
+
if type.nil?
|
842
|
+
etype = element.find_first('type')
|
843
|
+
if etype
|
844
|
+
type = etype['type']
|
845
|
+
if type.nil? and (etype2 = etype.find_first('type')) != nil
|
846
|
+
type = etype2['type']
|
847
|
+
end
|
848
|
+
type = "list_of_#{type}" if etype['list'] == 'yes'
|
849
|
+
end
|
850
|
+
end
|
851
|
+
raise "Parameter #{element} has no type." if type.nil?
|
852
|
+
return type
|
853
|
+
end
|
854
|
+
|
855
|
+
def self.escape_string(string)
|
856
|
+
string.gsub(/[\$\=\s\-\.\/]/, '_').gsub(/&/, 'and')
|
857
|
+
end
|
858
|
+
|
859
|
+
def self.rubyfy_constant_string(string, upcase=false)
|
860
|
+
string = string.gsub(/[^\w\s]/, '')
|
861
|
+
first = string[0]
|
862
|
+
if (?a..?z).include?(first)
|
863
|
+
string[0] = first.chr.upcase
|
864
|
+
elsif !(?A..?Z).include?(first)
|
865
|
+
string.insert(0, 'C')
|
866
|
+
end
|
867
|
+
escape_string(upcase ? string.upcase : string.gsub(/\s(.)/) { |s| s[1].chr.upcase })
|
868
|
+
end
|
869
|
+
|
870
|
+
RUBY_RESERVED_KEYWORDS = ['for', 'in', 'class']
|
871
|
+
def self.rubyfy_string(string, handle_ruby_reserved_keywords=false)
|
872
|
+
# Prefix with '_' parameter names to avoid possible collisions with reserved Ruby keywords (for, etc...).
|
873
|
+
if handle_ruby_reserved_keywords and RUBY_RESERVED_KEYWORDS.include?(string)
|
874
|
+
'_' + string
|
875
|
+
else
|
876
|
+
escape_string(string).downcase
|
877
|
+
end
|
878
|
+
end
|
879
|
+
|
880
|
+
def self.rubyfy_method(string, klass, return_type=nil, setter=false)
|
881
|
+
base = rubyfy_string(string)
|
882
|
+
s, i = base.dup, 1
|
883
|
+
loop do
|
884
|
+
if setter
|
885
|
+
# Suffix setters with '='.
|
886
|
+
s << '='
|
887
|
+
elsif return_type == 'boolean'
|
888
|
+
# Suffix predicates with '?'.
|
889
|
+
s << '?'
|
890
|
+
end
|
891
|
+
break unless klass.method_defined?(s)
|
892
|
+
# Suffix with an integer if the class already has a method with such a name.
|
893
|
+
i += 1
|
894
|
+
s = base + i.to_s
|
895
|
+
end
|
896
|
+
return s
|
897
|
+
end
|
898
|
+
|
899
|
+
def self.englishify_sentence(string)
|
900
|
+
return '' if string.nil? or string.empty?
|
901
|
+
string[0] = string[0].chr.upcase
|
902
|
+
string.strip!
|
903
|
+
last = string[-1].chr
|
904
|
+
string << '.' if last != '.' and last != '?' and last != '!'
|
905
|
+
return string
|
906
|
+
end
|
907
|
+
end
|
908
|
+
|
909
|
+
# String, for unicode stuff force utf8 type if specified.
|
910
|
+
OSA.add_conversion_to_ruby('TEXT') { |value, type, object| object.__data__('TEXT') }
|
911
|
+
OSA.add_conversion_to_ruby('utxt', 'utf8') { |value, type, object| object.__data__(OSA.utf8_strings ? 'utf8' : 'TEXT') }
|
912
|
+
OSA.add_conversion_to_osa('string', 'text') { |value| ['TEXT', value.to_s] }
|
913
|
+
OSA.add_conversion_to_osa('Unicode text') { |value| [OSA.utf8_strings ? 'utf8' : 'TEXT', value.to_s] }
|
914
|
+
|
915
|
+
# Signed/unsigned integer.
|
916
|
+
OSA.add_conversion_to_ruby('shor', 'long') { |value| value.unpack('l').first }
|
917
|
+
OSA.add_conversion_to_ruby('comp') { |value| value.unpack('q').first }
|
918
|
+
OSA.add_conversion_to_osa('integer', 'double integer') { |value| ['magn', [value].pack('l')] }
|
919
|
+
|
920
|
+
# Float
|
921
|
+
OSA.add_conversion_to_ruby('sing') { |value| value.unpack('f').first }
|
922
|
+
OSA.add_conversion_to_ruby('magn', 'doub') { |value| value.unpack('d').first }
|
923
|
+
OSA.add_conversion_to_osa('double') { |value| ['doub', [value].pack('d')] }
|
924
|
+
|
925
|
+
# Boolean.
|
926
|
+
OSA.add_conversion_to_ruby('bool') { |value| value.unpack('c').first != 0 }
|
927
|
+
OSA.add_conversion_to_osa('boolean') { |value| [(value ? 'true'.to_4cc : 'fals'.to_4cc), nil] }
|
928
|
+
OSA.add_conversion_to_ruby('true') { |value| true }
|
929
|
+
OSA.add_conversion_to_ruby('fals') { |value| false }
|
930
|
+
|
931
|
+
# Date.
|
932
|
+
OSA.add_conversion_to_ruby('ldt ') { |value|
|
933
|
+
Date.new(1904, 1, 1) + Date.time_to_day_fraction(0, 0, value.unpack('q').first)
|
934
|
+
}
|
935
|
+
|
936
|
+
# Array.
|
937
|
+
OSA.add_conversion_to_osa('list') do |value|
|
938
|
+
# The `list_of_XXX' types are not handled here.
|
939
|
+
if value.is_a?(Array)
|
940
|
+
elements = value.map { |x| OSA::Element.from_rbobj(nil, x, nil) }
|
941
|
+
OSA::ElementList.__new__(elements)
|
942
|
+
else
|
943
|
+
value
|
944
|
+
end
|
945
|
+
end
|
946
|
+
OSA.add_conversion_to_ruby('list') { |value, type, object|
|
947
|
+
object.is_a?(OSA::ElementList) ? object.to_a.map { |x| x.to_rbobj } : object
|
948
|
+
}
|
949
|
+
|
950
|
+
# File name.
|
951
|
+
# Let's use the 'furl' type here instead of 'alis', as we don't have a way to produce an alias for a file that does not exist yet.
|
952
|
+
OSA.add_conversion_to_osa('alias', 'file') { |value| ['furl', value.to_s] }
|
953
|
+
OSA.add_conversion_to_ruby('alis') do |value, type, object|
|
954
|
+
URI.unescape(URI.parse(object.__data__('furl')).path)
|
955
|
+
end
|
956
|
+
|
957
|
+
# Hash.
|
958
|
+
OSA.add_conversion_to_ruby('reco') { |value, type, object| object.is_a?(OSA::ElementRecord) ? object.to_hash : value }
|
959
|
+
OSA.add_conversion_to_osa('record') do |value|
|
960
|
+
if value.is_a?(Hash)
|
961
|
+
OSA::ElementRecord.from_hash(value)
|
962
|
+
else
|
963
|
+
value
|
964
|
+
end
|
965
|
+
end
|
966
|
+
|
967
|
+
# Enumerator.
|
968
|
+
OSA.add_conversion_to_ruby('enum') { |value, type, object| OSA::Enumerator.enum_for_code(object.__data__('TEXT')) or object }
|
969
|
+
|
970
|
+
# Class.
|
971
|
+
OSA.add_conversion_to_osa('type class', 'type') { |value| value.is_a?(Class) and value.ancestors.include?(OSA::Element) ? ['type', value::CODE.to_4cc] : value }
|
972
|
+
OSA.add_conversion_to_ruby('type') do |value, type, object|
|
973
|
+
if value == 'msng'
|
974
|
+
# Missing values.
|
975
|
+
value
|
976
|
+
else
|
977
|
+
hash = object.instance_variable_get(:@app).instance_variable_get(:@classes)
|
978
|
+
hash[value] or value
|
979
|
+
end
|
980
|
+
end
|
981
|
+
|
982
|
+
# QuickDraw Rectangle, aka "bounding rectangle".
|
983
|
+
OSA.add_conversion_to_ruby('qdrt') { |value| value.unpack('S4') }
|
984
|
+
OSA.add_conversion_to_osa('bounding rectangle') { |value| ['qdrt', value.pack('S4')] }
|
985
|
+
|
986
|
+
# Pictures (just return the raw data).
|
987
|
+
OSA.add_conversion_to_ruby('PICT') { |value, type, object| value[222..-1] } # Removing trailing garbage.
|
988
|
+
OSA.add_conversion_to_osa('picture') { |value| ['PICT', value.to_s] }
|
989
|
+
OSA.add_conversion_to_ruby('imaA') { |value, type, object| value }
|
990
|
+
OSA.add_conversion_to_ruby('TIFF') { |value, type, object| value }
|
991
|
+
OSA.add_conversion_to_osa('Image') { |value| ['imaA', value.to_s] }
|
992
|
+
OSA.add_conversion_to_osa('TIFF picture') { |value| ['TIFF', value.to_s] }
|
993
|
+
|
994
|
+
# RGB color.
|
995
|
+
OSA.add_conversion_to_ruby('cRGB') { |value| value.unpack('S3') }
|
996
|
+
OSA.add_conversion_to_osa('color') do |values|
|
997
|
+
ary = values.map { |i| OSA::Element.__new__('long', [i].pack('l')) }
|
998
|
+
OSA::ElementList.__new__(ary)
|
999
|
+
end
|
1000
|
+
|
1001
|
+
require 'rbosa_properties'
|