rubyosa 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/AUTHORS CHANGED
@@ -5,5 +5,9 @@ Contributors:
5
5
  Aaron Patterson <aaron.patterson@gmail.com>
6
6
  James MacAulay <jmacaulay@gmail.com>
7
7
  Michael Pruett <michael@68k.org>
8
+ Michail Pishchagin <mblsha@gmail.com>
8
9
  Sebastian Delmont <sd@notso.net>
10
+ Stefan Saasen <s@juretta.com>
11
+ Terry Donoghue <donoghue@apple.com>
9
12
  Vincent Isambart <vincent.isambart@gmail.com>
13
+ Wes Rogers <wesrog@gmail.com>
data/COPYRIGHT CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2006, Apple Computer, Inc. All rights reserved.
1
+ Copyright (c) 2006-2007, Apple Inc. All rights reserved.
2
2
 
3
3
  Redistribution and use in source and binary forms, with or without
4
4
  modification, are permitted provided that the following conditions
@@ -8,7 +8,7 @@ are met:
8
8
  2. Redistributions in binary form must reproduce the above copyright
9
9
  notice, this list of conditions and the following disclaimer in the
10
10
  documentation and/or other materials provided with the distribution.
11
- 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
11
+ 3. Neither the name of Apple Inc. ("Apple") nor the names of
12
12
  its contributors may be used to endorse or promote products derived
13
13
  from this software without specific prior written permission.
14
14
 
data/README CHANGED
@@ -24,9 +24,16 @@ libxml-ruby 0.3.8 or greater
24
24
  $ ruby extconf.rb
25
25
  $ make
26
26
  $ sudo make install
27
+ $ sudo make install-extras
28
+
29
+ `make install' will only install the core of the bridge, while
30
+ `make install-extras' will install some additional tools such as the rdoc-osa
31
+ utility.
27
32
 
28
33
  == Support ==
29
34
 
35
+ A tutorial is available online at http://rubyosa.rubyforge.org/tutorial.html.
36
+
30
37
  Sample code is available in the `sample' sub-directory.
31
38
 
32
39
  The rdoc-osa tool can be used to generate API reference documentation
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
- # RDoc frontend for RubyOSA. Generate API referene documentation for the
2
+ # RDoc frontend for RubyOSA. Generate API reference documentation for the
3
3
  # given application, based on the descriptions in the sdef(5).
4
4
  #
5
- # Copyright (c) 2006, Apple Computer, Inc. All rights reserved.
5
+ # Copyright (c) 2006-2007, Apple Inc. All rights reserved.
6
6
  #
7
7
  # Redistribution and use in source and binary forms, with or without
8
8
  # modification, are permitted provided that the following conditions
@@ -12,7 +12,7 @@
12
12
  # 2. Redistributions in binary form must reproduce the above copyright
13
13
  # notice, this list of conditions and the following disclaimer in the
14
14
  # documentation and/or other materials provided with the distribution.
15
- # 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15
+ # 3. Neither the name of Apple Inc. ("Apple") nor the names of
16
16
  # its contributors may be used to endorse or promote products derived
17
17
  # from this software without specific prior written permission.
18
18
  #
@@ -30,6 +30,7 @@
30
30
 
31
31
  require 'rbosa'
32
32
  require 'tmpdir'
33
+ require 'rbconfig'
33
34
 
34
35
  def usage
35
36
  STDERR.puts <<-EOS
@@ -55,20 +56,22 @@ end
55
56
 
56
57
  usage unless ARGV.length >= 2
57
58
 
58
- msg = case ARGV.first
59
+ key = case ARGV.first
59
60
  when '--name'
60
- :app_with_name
61
+ :name
61
62
  when '--path'
62
- :app_with_path
63
+ :path
63
64
  when '--bundle_id'
64
- :app_with_bundle_id
65
+ :bundle_id
65
66
  when '--signature'
66
- :app_with_signature
67
+ :signature
67
68
  else
68
69
  usage
69
70
  end
70
71
 
71
- app = OSA.send(msg, ARGV[1])
72
+ DOC_NOT_AVAILABLE = 'Documentation not available.'
73
+
74
+ app = OSA.app(key => ARGV[1])
72
75
  mod = OSA.const_get(app.class.name.scan(/^OSA::(.+)::Application$/).to_s)
73
76
  fake_ruby_src = mod.constants.map do |const_name|
74
77
  obj = mod.const_get(const_name)
@@ -76,20 +79,37 @@ fake_ruby_src = mod.constants.map do |const_name|
76
79
  when Class
77
80
  # Class.
78
81
  methods_desc = obj.const_get('METHODS_DESCRIPTION').map do |method|
79
- args_doc, args_def = '', ''
82
+ args_doc, args_def, args_def_opt = '', '', ''
80
83
  if method.args and !method.args.empty?
81
- args_doc = method.args.map do |x|
82
- arg = x.name.dup
83
- optional = arg.sub!(/=.+$/, '') != nil
84
- " # #{arg}::\n # #{x.description}" + (optional ? ' Optional.' : '')
85
- end.join("\n")
86
- args_def = '(' + method.args.map { |x| x.name }.join(', ') + ')'
84
+ args_doc_ary, args_def_ary, args_def_opt_ary = [], [], []
85
+ method.args.each do |x|
86
+ arg = x.name
87
+ desc = x.description
88
+ desc = DOC_NOT_AVAILABLE if desc.empty?
89
+ args_doc_ary << " # #{arg}::\n # #{desc}" + (x.optional? ? ' Optional. Can be passed as a Hash key/value.' : '')
90
+ if x.optional?
91
+ args_def_ary << x.name + '=nil'
92
+ args_def_opt_ary << ':' + x.name + ' => nil'
93
+ else
94
+ args_def_ary << x.name
95
+ args_def_opt_ary << x.name
96
+ end
97
+ end
98
+ args_doc = args_doc_ary.join("\n")
99
+ args_def = '(' + args_def_ary.join(', ') + ')'
100
+ args_def_opt = '(' + args_def_opt_ary.join(', ') + ')'
87
101
  end
88
102
  if method.result
89
103
  args_doc << "\n" unless args_doc.empty?
90
- args_doc << " # Returns::\n # #{method.result.description}\n"
104
+ desc = method.result.description
105
+ desc = DOC_NOT_AVAILABLE if desc.empty?
106
+ args_doc << " # Returns::\n # #{desc}\n"
91
107
  end
92
108
  <<EOS
109
+ # call-seq:
110
+ # #{method.name + args_def}
111
+ # #{args_def_opt != args_def ? method.name + args_def_opt : ''}
112
+ #
93
113
  # #{method.description}
94
114
  #{args_doc}
95
115
  def #{method.name}#{args_def}; end
@@ -134,9 +154,18 @@ module OSA; end
134
154
  module #{mod.name}; end
135
155
  EOS
136
156
 
157
+ rdoc_flags = ''
158
+ template = File.join(Config.datadir('rubyosa'), 'rdoc_html.rb')
159
+ if File.exists?(template)
160
+ rdoc_flags << " --template '#{template}' "
161
+ end
162
+ rdoc_flags << " --title '#{app.name} RubyOSA API' "
163
+ rdoc_flags << ' --main OSA '
164
+ rdoc_flags << ARGV[2..-1].join(' ')
165
+
137
166
  path = unique_tmp_path(app.name, '.rb')
138
167
  File.open(path, 'w') { |io| io.puts fake_ruby_src }
139
- line = "rdoc #{ARGV[2..-1].join(' ')} \"#{path}\""
168
+ line = "rdoc #{rdoc_flags} \"#{path}\""
140
169
  unless system(line)
141
170
  STDERR.puts "Error when executing `#{line}' : #{$?}"
142
171
  exit 1
data/extconf.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2006, Apple Computer, Inc. All rights reserved.
1
+ # Copyright (c) 2006-2007, Apple Inc. All rights reserved.
2
2
  #
3
3
  # Redistribution and use in source and binary forms, with or without
4
4
  # modification, are permitted provided that the following conditions
@@ -8,7 +8,7 @@
8
8
  # 2. Redistributions in binary form must reproduce the above copyright
9
9
  # notice, this list of conditions and the following disclaimer in the
10
10
  # documentation and/or other materials provided with the distribution.
11
- # 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
11
+ # 3. Neither the name of Apple Inc. ("Apple") nor the names of
12
12
  # its contributors may be used to endorse or promote products derived
13
13
  # from this software without specific prior written permission.
14
14
  #
@@ -50,4 +50,10 @@ new_filename_prefix = 'osx_'
50
50
  end
51
51
  end
52
52
 
53
+ # Generate the Makefile
53
54
  create_makefile('osa', 'src')
55
+
56
+ # Tweak the Makefile to add an extra install task.
57
+ text = File.read('Makefile')
58
+ text << "\n\ninstall-extras: post-install.rb\n\t@$(RUBY) post-install.rb\n\n"
59
+ File.open('Makefile', 'w') { |io| io.write(text) }
@@ -0,0 +1,31 @@
1
+ # Print all the contacts your Address Book contains.
2
+ # Thanks to Stefan Saasen.
3
+
4
+ begin require 'rubygems'; rescue LoadError; end
5
+ require 'rbosa'
6
+
7
+ OSA.utf8_strings = true
8
+
9
+ def print_person(pe)
10
+ puts pe.name
11
+ unless pe.emails.size < 1
12
+ puts "\tE-Mail: " + pe.emails.map { |email|
13
+ email.value
14
+ }.join(', ')
15
+ end
16
+ formatted_addresses = pe.addresses.map { |a|
17
+ # Some malformed addresses can't be formatted and the address book
18
+ # will therefore return an application-level error, that we handle there.
19
+ ('(' + a.label + ') ' + a.formatted_address rescue nil)
20
+ }.compact.map { |a|
21
+ "\t\t" + a.gsub(/\n/, ' ').strip.squeeze(' ')
22
+ }
23
+ unless formatted_addresses.size < 1
24
+ puts "\tAddresses:\n" + formatted_addresses.join("\n")
25
+ end
26
+ end
27
+
28
+ ab = OSA.app('Address Book')
29
+ ab.people.each do |pe|
30
+ print_person pe
31
+ end
@@ -0,0 +1,14 @@
1
+ # Retrieve and show every selected message content in Mail into new TextEdit documents.
2
+
3
+ begin require 'rubygems'; rescue LoadError; end
4
+ require 'rbosa'
5
+
6
+ OSA.utf8_strings = true
7
+ textedit = OSA.app('TextEdit')
8
+ mailApp = OSA.app('Mail')
9
+ viewers = mailApp.message_viewers
10
+ viewers.each do |viewer|
11
+ viewer.selected_messages.each do |message|
12
+ textedit.make(OSA::TextEdit::Document).text = message.content
13
+ end
14
+ end
@@ -6,7 +6,7 @@ require 'rbosa'
6
6
  textedit = OSA.app('TextEdit')
7
7
 
8
8
  # Complex way.
9
- textedit.make(OSA::TextEdit::Document, nil, nil, {:ctxt => 'Hello World #1'})
9
+ textedit.make(OSA::TextEdit::Document, :with_properties => {:text => 'Hello World #1'})
10
10
 
11
11
  # Easier way.
12
12
  textedit.make(OSA::TextEdit::Document).text = 'Hello World #2'
@@ -0,0 +1,20 @@
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
+ selection = itunes.selection.get
12
+ if selection.empty?
13
+ $stderr.puts "Please select some tracks."
14
+ exit 1
15
+ end
16
+ selection.each do |track|
17
+ feed = "http://ws.audioscrobbler.com/1.0/artist/#{CGI::escape(track.artist)}/toptags.xml"
18
+ doc = Document.new(Net::HTTP.get(URI(feed)))
19
+ track.genre = doc.root[1][1].text
20
+ end
@@ -18,20 +18,20 @@ end
18
18
 
19
19
  usage unless ARGV.length == 2
20
20
 
21
- msg = case ARGV.first
21
+ key = case ARGV.first
22
22
  when '--name'
23
- :app_with_name
23
+ :name
24
24
  when '--path'
25
- :app_with_path
25
+ :path
26
26
  when '--bundle_id'
27
- :app_with_bundle_id
27
+ :bundle_id
28
28
  when '--signature'
29
- :app_with_signature
29
+ :signature
30
30
  else
31
31
  usage
32
32
  end
33
33
 
34
- app = OSA.send(msg, ARGV.last)
34
+ app = OSA.app(key => ARGV.last)
35
35
  doc = REXML::Document.new(app.sdef)
36
36
  doc.write(STDOUT, 0)
37
37
  puts ""
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2006, Apple Computer, Inc. All rights reserved.
1
+ # Copyright (c) 2006-2007, Apple Inc. All rights reserved.
2
2
  #
3
3
  # Redistribution and use in source and binary forms, with or without
4
4
  # modification, are permitted provided that the following conditions
@@ -8,7 +8,7 @@
8
8
  # 2. Redistributions in binary form must reproduce the above copyright
9
9
  # notice, this list of conditions and the following disclaimer in the
10
10
  # documentation and/or other materials provided with the distribution.
11
- # 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
11
+ # 3. Neither the name of Apple Inc. ("Apple") nor the names of
12
12
  # its contributors may be used to endorse or promote products derived
13
13
  # from this software without specific prior written permission.
14
14
  #
@@ -116,8 +116,8 @@ class OSA::Element
116
116
  end
117
117
 
118
118
  def self.from_rbobj(requested_type, value, enum_group_codes)
119
- obj = OSA.convert_to_osa(requested_type, value, enum_group_codes)
120
- obj.is_a?(OSA::Element) ? obj : self.__new__(*obj)
119
+ obj = OSA.convert_to_osa(requested_type, value, enum_group_codes)
120
+ obj.is_a?(OSA::Element) ? obj : self.__new__(*obj)
121
121
  end
122
122
  end
123
123
 
@@ -129,16 +129,37 @@ class OSA::ElementList
129
129
  end
130
130
 
131
131
  class OSA::ElementRecord
132
+ def self.from_hash(hash)
133
+ value = {}
134
+ hash.each do |code, val|
135
+ key = OSA.sym_to_code(code)
136
+ if key.nil?
137
+ raise ArgumentError, "invalid key `#{code}'" if code.to_s.length != 4
138
+ key = code
139
+ end
140
+ value[key] = OSA::Element.from_rbobj(nil, val, nil)
141
+ end
142
+ OSA::ElementRecord.__new__(value)
143
+ end
144
+
132
145
  def to_hash
133
146
  h = {}
134
- self.to_a.each { |key, val| h[key] = val.to_rbobj }
147
+ self.to_a.each do |code, val|
148
+ key = (OSA.code_to_sym(code) or code)
149
+ h[key] = val.to_rbobj
150
+ end
135
151
  return h
136
152
  end
137
153
  end
138
154
 
139
155
  module OSA::ObjectSpecifier
140
156
  def get
141
- @app.__send_event__('core', 'getd', [['----', self]], true).to_rbobj
157
+ new_obj = @app.__send_event__('core', 'getd', [['----', self]], true).to_rbobj
158
+ if !new_obj.is_a?(self.class) and new_obj.is_a?(OSA::Element) and self.respond_to?(:properties) and (klass = self.properties[:class])
159
+ klass.__duplicate__(new_obj)
160
+ else
161
+ new_obj
162
+ end
142
163
  end
143
164
  end
144
165
 
@@ -150,7 +171,7 @@ class OSA::ObjectSpecifierList
150
171
  end
151
172
 
152
173
  def length
153
- @length ||= @app.__send_event__(
174
+ @app.__send_event__(
154
175
  'core', 'cnte',
155
176
  [['----', @container], ['kocl', OSA::Element.__new__('type', @desired_class::CODE.to_4cc)]],
156
177
  true).to_rbobj
@@ -186,6 +207,10 @@ class OSA::ObjectSpecifierList
186
207
  and (0..other.length).all? { |i| other[i] == self[i] }
187
208
  end
188
209
 
210
+ def inspect
211
+ super.scan(/^([^ ]+)/).to_s << " desired_class=#{@desired_class}>"
212
+ end
213
+
189
214
  #######
190
215
  private
191
216
  #######
@@ -196,25 +221,87 @@ class OSA::ObjectSpecifierList
196
221
  end
197
222
  end
198
223
 
224
+ module OSA::EventDispatcher
225
+
226
+ SCRIPTING_ADDITIONS_DIR = [
227
+ '/System/Library/ScriptingAdditions',
228
+ '/Library/ScriptingAdditions'
229
+ ]
230
+ if home = ENV['HOME']
231
+ SCRIPTING_ADDITIONS_DIR << File.join(home, '/Library/ScriptingAdditions')
232
+ end
233
+
234
+ def merge(args)
235
+ args = { :name => args } if args.is_a?(String)
236
+ by_name = args[:name]
237
+ begin
238
+ name, target, sdef = OSA.__scripting_info__(args)
239
+ rescue RuntimeError => excp
240
+ # If an sdef bundle can't be find by name, let's be clever and look in the ScriptingAdditions locations.
241
+ if by_name
242
+ args = SCRIPTING_ADDITIONS_DIR.each do |dir|
243
+ path = ['.app', '.osax'].map { |e| File.join(dir, by_name + e) }.find { |p| File.exists?(p) }
244
+ if path
245
+ break { :path => path }
246
+ end
247
+ end
248
+ if args.is_a?(Hash)
249
+ by_name = nil
250
+ retry
251
+ end
252
+ end
253
+ raise excp
254
+ end
255
+ app_module_name = self.class.name.scan(/^OSA::(.+)::.+$/).flatten.first
256
+ app_module = OSA.const_get(app_module_name)
257
+ OSA.__load_sdef__(sdef, target, app_module, true, self.class)
258
+ (self.__send_event__('ascr', 'gdut', [], true) rescue nil) # Don't ask me why...
259
+ return self
260
+ end
261
+ end
262
+
199
263
  module OSA
200
264
  def self.app_with_name(name)
201
- self.__app__(*OSA.__scripting_info__(:by_name, name))
265
+ STDERR.puts "OSA.app_with_name() has been deprecated and its usage is now discouraged. Please use OSA.app('name') instead."
266
+ self.__app__(*OSA.__scripting_info__(:name => name))
202
267
  end
203
268
 
204
269
  def self.app_with_path(path)
205
- self.__app__(*OSA.__scripting_info__(:by_path, path))
270
+ STDERR.puts "OSA.app_by_path() has been deprecated and its usage is now discouraged. Please use OSA.app(:path => 'path') instead."
271
+ self.__app__(*OSA.__scripting_info__(:path => path))
206
272
  end
207
273
 
208
- def self.app_with_bundle_id(bundle_id)
209
- self.__app__(*OSA.__scripting_info__(:by_bundle_id, bundle_id))
274
+ def self.app_by_bundle_id(bundle_id)
275
+ 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."
276
+ self.__app__(*OSA.__scripting_info__(:bundle_id => bundle_id))
210
277
  end
211
278
 
212
- def self.app_with_signature(signature)
213
- self.__app__(*OSA.__scripting_info__(:by_signature, signature))
279
+ def self.app_by_signature(signature)
280
+ STDERR.puts "OSA.app_by_signature() has been deprecated and its usage is now discouraged. Please use OSA.app(:signature => 'signature') instead."
281
+ self.__app__(*OSA.__scripting_info__(:signature => signature))
214
282
  end
215
283
 
216
- def self.app(name)
217
- self.app_with_name(name)
284
+ def self.app(*args)
285
+ if args.size == 2
286
+ if args.first.is_a?(String) and args.last.is_a?(Hash)
287
+ hash = args.last
288
+ if hash.has_key?(:name)
289
+ warn "Given Hash argument already has a :name key, ignoring the first String argument `#{args.first}'"
290
+ else
291
+ hash[:name] = args.first
292
+ end
293
+ args = hash
294
+ else
295
+ raise ArgumentError, "when called with 2 arguments, the first is supposed to be a String and the second a Hash"
296
+ end
297
+ else
298
+ if args.size != 1
299
+ raise ArgumentError, "wrong number of arguments (#{args.size} for at least 1)"
300
+ end
301
+ args = args.first
302
+ end
303
+ args = { :name => args } if args.is_a?(String)
304
+ self.__app__(*OSA.__scripting_info__(args))
218
305
  end
219
306
 
220
307
  @conversions_to_ruby = {}
@@ -253,47 +340,49 @@ module OSA
253
340
  end
254
341
  end
255
342
 
256
- def self.convert_to_osa(requested_type, value, enum_group_codes=nil)
257
- if requested_type.nil?
258
- case value
259
- when OSA::Element
260
- return value
261
- when String
262
- requested_type = 'text'
263
- when Array
264
- requested_type = 'list'
265
- when Hash
266
- requested_type = 'record'
267
- when Integer
268
- requested_type = 'integer'
269
- else
270
- STDERR.puts "can't determine OSA type for #{value}" if $VERBOSE
271
- ['null', nil]
272
- end
273
- end
274
-
343
+ def self.__convert_to_osa__(requested_type, value, enum_group_codes=nil)
344
+ return value if value.is_a?(OSA::Element)
275
345
  if conversion = @conversions_to_osa[requested_type]
276
346
  args = [value, requested_type]
277
347
  conversion.call(*args[0..(conversion.arity - 1)])
278
348
  elsif enum_group_codes and enum_group_codes.include?(requested_type)
279
349
  ['enum', value.code.to_4cc]
280
350
  elsif md = /^list_of_(.+)$/.match(requested_type)
281
- ary = value.to_a.map { |x| convert_to_osa(md[1], x, enum_group_codes) }
351
+ ary = value.to_a.map do |elem|
352
+ obj = convert_to_osa(md[1], elem, enum_group_codes)
353
+ obj.is_a?(OSA::Element) ? obj : OSA::Element.__new__(*obj)
354
+ end
282
355
  ElementList.__new__(ary)
283
356
  else
284
357
  STDERR.puts "unrecognized type #{requested_type}" if $VERBOSE
285
358
  ['null', nil]
286
359
  end
287
360
  end
361
+
362
+ def self.convert_to_osa(requested_type, value, enum_group_codes=nil)
363
+ ary = __convert_to_osa__(requested_type, value, enum_group_codes)
364
+ if ary == ['null', nil]
365
+ new_type = case value
366
+ when String then 'text'
367
+ when Array then 'list'
368
+ when Hash then 'record'
369
+ when Integer then 'integer'
370
+ end
371
+ if new_type
372
+ ary = __convert_to_osa__(new_type, value, enum_group_codes)
373
+ end
374
+ end
375
+ ary
376
+ end
288
377
 
289
378
  def self.set_params(hash)
290
379
  previous_values = {}
291
380
  hash.each do |key, val|
292
- ivar_key = '@' + key.to_s
293
- previous_val = self.instance_variable_get(ivar_key)
294
- if previous_val.nil?
381
+ unless OSA.respond_to?(key)
295
382
  raise ArgumentError, "Invalid key value (no parameter named #{key} was found)"
296
383
  end
384
+ ivar_key = '@' + key.to_s
385
+ previous_val = self.instance_variable_get(ivar_key)
297
386
  previous_values[ivar_key] = previous_val;
298
387
  self.instance_variable_set(ivar_key, hash[key])
299
388
  end
@@ -310,9 +399,13 @@ module OSA
310
399
 
311
400
  class DocItem
312
401
  attr_reader :name, :description
313
- def initialize(name, description)
402
+ def initialize(name, description, optional=false)
314
403
  @name = name
315
404
  @description = description
405
+ @optional = optional
406
+ end
407
+ def optional?
408
+ @optional
316
409
  end
317
410
  end
318
411
 
@@ -328,10 +421,20 @@ module OSA
328
421
  end
329
422
  end
330
423
 
331
- def self.__app__(name, signature, sdef)
424
+ def self.__app__(name, target, sdef)
332
425
  @apps ||= {}
333
- app = @apps[signature]
426
+ app = @apps[target]
334
427
  return app if app
428
+
429
+ # Creates a module for this app, we will define the scripting interface within it.
430
+ app_module = Module.new
431
+ self.const_set(rubyfy_constant_string(name), app_module)
432
+
433
+ @apps[target] = __load_sdef__(sdef, target, app_module)
434
+ end
435
+
436
+ def self.__load_sdef__(sdef, target, app_module, merge_only=false, app_class=nil)
437
+ # Load the sdef.
335
438
  doc = if USE_LIBXML
336
439
  parser = XML::Parser.new
337
440
  parser.string = sdef
@@ -340,10 +443,6 @@ module OSA
340
443
  REXML::Document.new(sdef)
341
444
  end
342
445
 
343
- # Creates a module for this app, we will define the scripting interface within it.
344
- app_module = Module.new
345
- self.const_set(rubyfy_constant_string(name), app_module)
346
-
347
446
  # Retrieves and creates enumerations.
348
447
  enum_group_codes = {}
349
448
  doc.find('/dictionary/suite/enumeration').each do |element|
@@ -367,14 +466,15 @@ module OSA
367
466
  documentation << DocItem.new(enum_name, englishify_sentence(element['description']))
368
467
  end
369
468
 
370
- app_module.const_set(enum_module_name, enum_module)
469
+ app_module.const_set(enum_module_name, enum_module) unless app_module.const_defined?(enum_module_name)
371
470
  end
372
471
 
373
472
  # Retrieves and creates classes.
374
473
  classes = {}
375
474
  class_elements = {}
376
475
  doc.find('/dictionary/suite/class').each do |element|
377
- (class_elements[element['name']] ||= []) << element
476
+ key = (element['id'] or element['name'])
477
+ (class_elements[key] ||= []) << element
378
478
  end
379
479
  class_elements.values.flatten.each do |element|
380
480
  klass = add_class_from_xml_element(element, class_elements, classes, app_module)
@@ -396,7 +496,7 @@ module OSA
396
496
  # Add basic properties that might be missing to the Item class (if any).
397
497
  props = {}
398
498
  element.find('property').each do |x|
399
- props[x['name']] = [x['code'], x['type'], x['access'], x['description']]
499
+ props[x['name']] = [x['code'], type_of_parameter(x), x['access'], x['description']]
400
500
  end
401
501
  if klass.name[-6..-1] == '::Item'
402
502
  unless props.has_key?('id')
@@ -422,60 +522,54 @@ module OSA
422
522
  # Implicit 'get' if the property class is primitive (not defined in the sdef),
423
523
  # otherwise just return an object specifier.
424
524
  method_name = rubyfy_method(name, klass, type)
425
- method_code = if pklass.nil?
426
- <<EOC
427
- def #{method_name}
428
- @app.__send_event__('core', 'getd',
429
- [['----', Element.__new_object_specifier__('prop', @app == self ? Element.__new__('null', nil) : self,
430
- 'prop', Element.__new__('type', '#{code}'.to_4cc))]],
431
- true).to_rbobj
432
- end
433
- EOC
525
+ method_proc = if pklass.nil?
526
+ proc do
527
+ @app.__send_event__('core', 'getd',
528
+ [['----', Element.__new_object_specifier__('prop', @app == self ? Element.__new__('null', nil) : self,
529
+ 'prop', Element.__new__('type', code.to_4cc))]],
530
+ true).to_rbobj
531
+ end
434
532
  else
435
- <<EOC
436
- def #{method_name}
437
- o = #{pklass.name}.__new_object_specifier__('prop', @app == self ? Element.__new__('null', nil) : self,
438
- 'prop', Element.__new__('type', '#{code}'.to_4cc))
439
- unless OSA.lazy_events?
440
- @app.__send_event__('core', 'getd', [['----', o]], true).to_rbobj
441
- else
442
- o.instance_variable_set(:@app, @app)
443
- o.extend(OSA::ObjectSpecifier)
444
- end
445
- end
446
- EOC
533
+ proc do
534
+ o = pklass.__new_object_specifier__('prop', @app == self ? Element.__new__('null', nil) : self,
535
+ 'prop', Element.__new__('type', code.to_4cc))
536
+ unless OSA.lazy_events?
537
+ @app.__send_event__('core', 'getd', [['----', o]], true).to_rbobj
538
+ else
539
+ o.instance_variable_set(:@app, @app)
540
+ o.extend(OSA::ObjectSpecifier)
541
+ end
542
+ end
447
543
  end
448
544
 
449
- klass.class_eval(method_code)
545
+ klass.class_eval { define_method(method_name, method_proc) }
450
546
  ptypedoc = if pklass.nil?
451
- if mod = enum_group_codes[type]
452
- "a #{mod} enumeration"
453
- else
454
- type
455
- end
547
+ type_doc(type, enum_group_codes, app_module)
456
548
  else
457
549
  "a #{pklass} object"
458
550
  end
459
- description[0] = description[0].chr.downcase if description
460
- methods_doc << DocMethod.new(method_name, englishify_sentence("Gets the #{name} property -- #{description}"), DocItem.new('result', englishify_sentence("the property value, as #{ptypedoc}")), nil)
551
+ if description
552
+ description[0] = description[0].chr.downcase
553
+ description = '-- ' << description
554
+ end
555
+ methods_doc << DocMethod.new(method_name, englishify_sentence("Gets the #{name} property #{description}"), DocItem.new('result', englishify_sentence("the property value, as #{ptypedoc}")), nil)
461
556
 
462
557
  # For the setter, always send an event.
463
558
  if setter
464
559
  method_name = rubyfy_method(name, klass, type, true)
465
- method_code = <<EOC
466
- def #{method_name}(val)
467
- @app.__send_event__('core', 'setd',
468
- [['----', Element.__new_object_specifier__('prop', @app == self ? Element.__new__('null', nil) : self,
469
- 'prop', Element.__new__('type', '#{code}'.to_4cc))],
470
- ['data', #{new_element_code(type, 'val', enum_group_codes)}]],
471
- true)
472
- return nil
473
- end
474
- EOC
475
-
476
- klass.class_eval(method_code)
477
- 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}"))])
560
+ method_proc = proc do |val|
561
+ @app.__send_event__('core', 'setd',
562
+ [['----', Element.__new_object_specifier__('prop', @app == self ? Element.__new__('null', nil) : self,
563
+ 'prop', Element.__new__('type', code.to_4cc))],
564
+ ['data', val.is_a?(OSA::Element) ? val : Element.from_rbobj(type, val, enum_group_codes.keys)]],
565
+ true)
566
+ return nil
567
+ end
568
+ klass.class_eval { define_method(method_name, method_proc) }
569
+ 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}"))])
478
570
  end
571
+
572
+ OSA.add_property(name.intern, code)
479
573
  end
480
574
 
481
575
  # Creates elements.
@@ -496,39 +590,49 @@ EOC
496
590
  end
497
591
 
498
592
  method_name = rubyfy_method(eklass::PLURAL, klass)
499
- method_code = <<EOC
500
- def #{method_name}
501
- unless OSA.lazy_events?
502
- @app.__send_event__('core', 'getd',
503
- [['----', Element.__new_object_specifier__(
504
- '#{eklass::CODE}'.to_4cc, @app == self ? Element.__new__('null', nil) : self,
505
- 'indx', Element.__new__('abso', 'all '.to_4cc))]],
506
- true).to_rbobj
507
- else
508
- ObjectSpecifierList.new(@app, #{eklass}, @app == self ? Element.__new__('null', nil) : self)
509
- end
510
- end
511
- EOC
512
-
513
- klass.class_eval(method_code)
593
+ method_proc = proc do
594
+ unless OSA.lazy_events?
595
+ @app.__send_event__('core', 'getd',
596
+ [['----', Element.__new_object_specifier__(
597
+ eklass::CODE.to_4cc, @app == self ? Element.__new__('null', nil) : self,
598
+ 'indx', Element.__new__('abso', 'all '.to_4cc))]],
599
+ true).to_rbobj
600
+ else
601
+ ObjectSpecifierList.new(@app, eklass, @app == self ? Element.__new__('null', nil) : self)
602
+ end
603
+ end
604
+ klass.class_eval { define_method(method_name, method_proc) }
514
605
  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)
515
606
  end
516
607
  end
517
608
 
518
- # Having an 'application' class is required.
519
- app_class = classes['application']
520
- raise "No application class defined." if app_class.nil?
609
+ unless merge_only
610
+ # Having an 'application' class is required.
611
+ app_class = classes['application']
612
+ raise "No application class defined." if app_class.nil?
613
+ all_classes_but_app = classes.values.reject { |x| x.ancestors.include?(OSA::EventDispatcher) }
614
+ else
615
+ all_classes_but_app = classes.values
616
+ end
521
617
 
522
618
  # Maps commands to the right classes.
523
- all_classes_but_app = classes.values.reject { |x| x.ancestors.include?(OSA::EventDispatcher) }
524
619
  doc.find('/dictionary/suite/command').each do |element|
525
620
  name = element['name']
526
621
  next if /NOT AVAILABLE/.match(name) # Finder's sdef (Tiger) names some commands with this 'tag'.
527
622
  description = element['description']
528
- code = element['code']
529
623
  direct_parameter = element.find_first('direct-parameter')
530
624
  result = element.find_first('result')
625
+ has_result = result != nil
531
626
 
627
+ code = element['code']
628
+ begin
629
+ code = Iconv.iconv('MACROMAN', 'UTF-8', code).to_s
630
+ rescue Iconv::IllegalSequence
631
+ # We can't do more...
632
+ STDERR.puts "unrecognized command code encoding '#{code}', skipping..." if $DEBUG
633
+ next
634
+ end
635
+
532
636
  classes_to_define = []
533
637
  forget_direct_parameter = true
534
638
  direct_parameter_optional = false
@@ -567,53 +671,79 @@ EOC
567
671
  end
568
672
 
569
673
  params = []
674
+ params_doc = []
570
675
  unless direct_parameter.nil?
571
- params << ['direct',
572
- '----',
573
- direct_parameter['description'],
574
- direct_parameter_optional,
575
- type_of_parameter(direct_parameter)]
676
+ pdesc = direct_parameter['description']
677
+ params << [
678
+ 'direct',
679
+ '----',
680
+ direct_parameter_optional,
681
+ type_of_parameter(direct_parameter)
682
+ ]
683
+ unless forget_direct_parameter
684
+ params_doc << DocItem.new('direct', englishify_sentence(pdesc))
685
+ end
576
686
  end
577
687
 
578
- params.concat(element.find('parameter').to_a.map do |element|
579
- [rubyfy_string(element['name'], true),
580
- element['code'],
581
- element['description'],
582
- parameter_optional?(element),
583
- type_of_parameter(element)]
584
- end)
585
-
586
- p_dec, p_def, p_args_doc = [], [], []
587
- already_has_optional_args = false # Once an argument is optional, all following arguments should be optional.
588
- params.each do |pname, pcode, pdesc, optional, ptype|
589
- decl = pname
590
- self_direct = (pcode == '----' and forget_direct_parameter)
591
- defi = if self_direct
592
- forget_direct_parameter ? "(self.is_a?(OSA::EventDispatcher) ? [] : ['----', self])" : "['----', self]"
593
- else
594
- "['#{pcode}', #{new_element_code(ptype, pname, enum_group_codes)}]"
688
+ element.find('parameter').to_a.each do |element|
689
+ poptional = parameter_optional?(element)
690
+ params << [
691
+ rubyfy_string(element['name']),
692
+ element['code'],
693
+ poptional,
694
+ type_of_parameter(element)
695
+ ]
696
+ params_doc << DocItem.new(rubyfy_string(element['name'], true), englishify_sentence(element['description']), poptional)
697
+ end
698
+
699
+ method_proc = proc do |*args_ary|
700
+ args = []
701
+ min_argc = i = 0
702
+ already_has_optional_args = false # Once an argument is optional, all following arguments should be optional.
703
+ optional_hash = nil
704
+ params.each do |pname, pcode, optional, ptype|
705
+ self_direct = (pcode == '----' and forget_direct_parameter)
706
+ if already_has_optional_args or (optional and !self_direct)
707
+ already_has_optional_args = true
708
+ else
709
+ if args_ary.size < i
710
+ raise ArgumentError, "wrong number of arguments (#{args_ary.size} for #{i})"
711
+ end
712
+ end
713
+ val = if self_direct
714
+ self.is_a?(OSA::EventDispatcher) ? [] : ['----', self]
715
+ else
716
+ arg = args_ary[i]
717
+ min_argc += 1 unless already_has_optional_args
718
+ i += 1
719
+ if arg.is_a?(Hash) and already_has_optional_args and i >= args_ary.size and min_argc + 1 == i
720
+ optional_hash = arg
721
+ end
722
+ if optional_hash
723
+ arg = optional_hash.delete(pname.intern)
724
+ end
725
+ if arg.nil?
726
+ if already_has_optional_args
727
+ []
728
+ end
729
+ else
730
+ [pcode, arg.is_a?(OSA::Element) ? arg : OSA::Element.from_rbobj(ptype, arg, enum_group_codes.keys)]
731
+ end
732
+ end
733
+ args << val
595
734
  end
596
- if already_has_optional_args or (optional and !self_direct)
597
- decl += '=nil'
598
- defi = "(#{pname} == nil ? [] : #{defi})"
599
- already_has_optional_args = true
600
- end
601
- unless self_direct
602
- p_dec << decl
603
- p_args_doc << DocItem.new(decl, englishify_sentence(pdesc))
735
+ if args_ary.size > params.size or args_ary.size < min_argc
736
+ raise ArgumentError, "wrong number of arguments (#{args_ary.size} for #{min_argc})"
737
+ end
738
+ if optional_hash and !optional_hash.empty?
739
+ raise ArgumentError, "inappropriate optional argument(s): #{optional_hash.keys.join(', ')}"
604
740
  end
605
- p_def << defi
741
+ wait_reply = (OSA.wait_reply != nil ? OSA.wait_reply : (has_result or @app.remote?))
742
+ ret = @app.__send_event__(code[0..3], code[4..-1], args, wait_reply)
743
+ wait_reply ? ret.to_rbobj : ret
606
744
  end
607
745
 
608
- code = Iconv.iconv('MACROMAN', 'UTF-8', code).to_s
609
-
610
- method_code = <<EOC
611
- def %METHOD_NAME%(#{p_dec.join(', ')})
612
- @app.__send_event__('#{code[0..3]}', '#{code[4..-1]}', [#{p_def.join(', ')}], #{result != nil})#{result != nil ? '.to_rbobj' : ''}
613
- end
614
- EOC
615
-
616
- if result.nil?
746
+ unless has_result
617
747
  result_type = result_doc = nil
618
748
  else
619
749
  result_type = type_of_parameter(result)
@@ -623,22 +753,27 @@ EOC
623
753
 
624
754
  classes_to_define.each do |klass|
625
755
  method_name = rubyfy_method(name, klass, result_type)
626
- code = method_code.sub(/%METHOD_NAME%/, method_name)
627
- klass.class_eval(code)
756
+ klass.class_eval { define_method(method_name, method_proc) }
628
757
  methods_doc = klass.const_get(:METHODS_DESCRIPTION)
629
- methods_doc << DocMethod.new(method_name, englishify_sentence(description), result_doc, p_args_doc)
758
+ methods_doc << DocMethod.new(method_name, englishify_sentence(description), result_doc, params_doc)
630
759
  end
631
760
  end
632
761
 
633
- # Returns an application instance, that's all folks!
634
- hash = {}
635
- classes.each_value { |klass| hash[klass::CODE] = klass }
636
- app = app_class.__new__('sign', signature.to_4cc)
637
- app.instance_variable_set(:@sdef, sdef)
638
- app.instance_variable_set(:@classes, hash)
639
- app.instance_eval 'def sdef; @sdef; end'
640
- app.extend OSA::EventDispatcher
641
- @apps[signature] = app
762
+ unless merge_only
763
+ # Returns an application instance, that's all folks!
764
+ hash = {}
765
+ classes.each_value { |klass| hash[klass::CODE] = klass }
766
+ app_class.class_eval do
767
+ attr_reader :sdef
768
+ define_method(:remote?) { @is_remote == true }
769
+ end
770
+ is_remote = target.length > 4
771
+ app = is_remote ? app_class.__new__('aprl', target) : app_class.__new__('sign', target.to_4cc)
772
+ app.instance_variable_set(:@is_remote, is_remote)
773
+ app.instance_variable_set(:@sdef, sdef)
774
+ app.instance_variable_set(:@classes, hash)
775
+ app.extend OSA::EventDispatcher
776
+ end
642
777
  end
643
778
 
644
779
  def self.parameter_optional?(element)
@@ -647,7 +782,8 @@ EOC
647
782
 
648
783
  def self.add_class_from_xml_element(element, class_elements, repository, app_module)
649
784
  real_name = element['name']
650
- klass = repository[real_name]
785
+ key = (element['id'] or real_name)
786
+ klass = repository[key]
651
787
  if klass.nil?
652
788
  code = element['code']
653
789
  inherits = element['inherits']
@@ -674,7 +810,7 @@ EOC
674
810
  end
675
811
  end
676
812
 
677
- klass.class_eval 'include OSA::EventDispatcher' if real_name == 'application'
813
+ klass.class_eval { include OSA::EventDispatcher } if real_name == 'application'
678
814
 
679
815
  klass.const_set(:REAL_NAME, real_name) unless klass.const_defined?(:REAL_NAME)
680
816
  klass.const_set(:PLURAL, plural == nil ? real_name + 's' : plural) unless klass.const_defined?(:PLURAL)
@@ -682,30 +818,45 @@ EOC
682
818
 
683
819
  app_module.const_set(rubyfy_constant_string(real_name), klass)
684
820
 
685
- repository[real_name] = klass
821
+ repository[key] = klass
686
822
  end
687
823
 
688
824
  return klass
689
825
  end
690
826
 
827
+ def self.type_doc(type, enum_group_codes, app_module)
828
+ if mod = enum_group_codes[type]
829
+ mod.to_s
830
+ elsif md = /^list_of_(.+)$/.match(type)
831
+ "list of #{type_doc(md[1], enum_group_codes, app_module)}"
832
+ else
833
+ up_type = type.upcase
834
+ begin
835
+ app_module.const_get(up_type).to_s
836
+ rescue
837
+ type
838
+ end
839
+ end
840
+ end
841
+
691
842
  def self.type_of_parameter(element)
692
843
  type = element['type']
693
844
  if type.nil?
694
845
  etype = element.find_first('type')
695
- if etype.nil? or (type = etype['type']).nil?
696
- raise "Parameter #{element} has no type."
846
+ if etype
847
+ type = etype['type']
848
+ if type.nil? and (etype2 = etype.find_first('type')) != nil
849
+ type = etype2['type']
850
+ end
851
+ type = "list_of_#{type}" if etype['list'] == 'yes'
697
852
  end
698
- type = "list_of_#{type}" if etype['list'] == 'yes'
699
853
  end
854
+ raise "Parameter #{element} has no type." if type.nil?
700
855
  return type
701
856
  end
702
857
 
703
- def self.new_element_code(type, varname, enum_group_codes)
704
- "#{varname}.is_a?(OSA::Element) ? #{varname} : Element.from_rbobj('#{type}', #{varname}, #{enum_group_codes.keys.inspect})"
705
- end
706
-
707
858
  def self.escape_string(string)
708
- string.gsub(/[\s\-\.\/]/, '_').gsub(/&/, 'and')
859
+ string.gsub(/[\$\=\s\-\.\/]/, '_').gsub(/&/, 'and')
709
860
  end
710
861
 
711
862
  def self.rubyfy_constant_string(string, upcase=false)
@@ -751,6 +902,7 @@ EOC
751
902
  def self.englishify_sentence(string)
752
903
  return '' if string.nil?
753
904
  string[0] = string[0].chr.upcase
905
+ string.strip!
754
906
  last = string[-1].chr
755
907
  string << '.' if last != '.' and last != '?' and last != '!'
756
908
  return string
@@ -766,7 +918,7 @@ OSA.add_conversion_to_osa('Unicode text') { |value| [OSA.utf8_strings ? 'utf8' :
766
918
  # Signed/unsigned integer.
767
919
  OSA.add_conversion_to_ruby('shor', 'long') { |value| value.unpack('l').first }
768
920
  OSA.add_conversion_to_ruby('comp') { |value| value.unpack('q').first }
769
- OSA.add_conversion_to_ruby('magn') { |value| value.unpack('d').first }
921
+ OSA.add_conversion_to_ruby('magn', 'doub') { |value| value.unpack('d').first }
770
922
  OSA.add_conversion_to_osa('integer', 'double integer') { |value| ['magn', [value].pack('l')] }
771
923
 
772
924
  # Float
@@ -791,14 +943,15 @@ OSA.add_conversion_to_ruby('list') { |value, type, object|
791
943
  # File name.
792
944
  # 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.
793
945
  OSA.add_conversion_to_osa('alias', 'file') { |value| ['furl', value.to_s] }
794
- OSA.add_conversion_to_ruby('alis') { |value, type, object| URI.parse(object.__data__('furl')).path }
946
+ OSA.add_conversion_to_ruby('alis') do |value, type, object|
947
+ URI.unescape(URI.parse(object.__data__('furl')).path)
948
+ end
795
949
 
796
950
  # Hash.
797
951
  OSA.add_conversion_to_ruby('reco') { |value, type, object| object.is_a?(OSA::ElementRecord) ? object.to_hash : value }
798
952
  OSA.add_conversion_to_osa('record') do |value|
799
953
  if value.is_a?(Hash)
800
- value.each { |key, val| value[key] = OSA::Element.from_rbobj(nil, val, nil) }
801
- OSA::ElementRecord.__new__(value)
954
+ OSA::ElementRecord.from_hash(value)
802
955
  else
803
956
  value
804
957
  end
@@ -808,7 +961,7 @@ end
808
961
  OSA.add_conversion_to_ruby('enum') { |value, type, object| OSA::Enumerator.enum_for_code(object.__data__('TEXT')) or object }
809
962
 
810
963
  # Class.
811
- OSA.add_conversion_to_osa('type class') { |value| value.is_a?(Class) and value.ancestors.include?(OSA::Element) ? ['type', value::CODE.to_4cc] : value }
964
+ 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 }
812
965
  OSA.add_conversion_to_ruby('type') do |value, type, object|
813
966
  if value == 'msng'
814
967
  # Missing values.
@@ -827,5 +980,8 @@ OSA.add_conversion_to_osa('bounding rectangle') { |value| ['qdrt', value.pack('S
827
980
  OSA.add_conversion_to_ruby('PICT') { |value, type, object| value[222..-1] } # Removing trailing garbage.
828
981
  OSA.add_conversion_to_osa('picture') { |value| ['PICT', value.to_s] }
829
982
  OSA.add_conversion_to_ruby('imaA') { |value, type, object| value }
983
+ OSA.add_conversion_to_ruby('TIFF') { |value, type, object| value }
830
984
  OSA.add_conversion_to_osa('Image') { |value| ['imaA', value.to_s] }
831
985
  OSA.add_conversion_to_osa('TIFF picture') { |value| ['TIFF', value.to_s] }
986
+
987
+ require 'rbosa_properties'