rb-appscript 0.2.0 → 0.2.1

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.
Files changed (41) hide show
  1. data/CHANGES +17 -0
  2. data/README +1 -1
  3. data/TODO +0 -4
  4. data/doc/appscript-manual/01_introduction.html +3 -2
  5. data/doc/appscript-manual/02_aboutappscripting.html +4 -4
  6. data/doc/appscript-manual/03_quicktutorial.html +14 -12
  7. data/doc/appscript-manual/04_gettinghelp.html +1 -1
  8. data/doc/appscript-manual/06_classesandenums.html +3 -3
  9. data/doc/appscript-manual/07_applicationobjects.html +17 -16
  10. data/doc/appscript-manual/08_realvsgenericreferences.html +16 -16
  11. data/doc/appscript-manual/09_referenceforms.html +15 -15
  12. data/doc/appscript-manual/10_referenceexamples.html +28 -27
  13. data/doc/appscript-manual/11_applicationcommands.html +14 -14
  14. data/doc/appscript-manual/12_commandexamples.html +14 -14
  15. data/doc/appscript-manual/13_performanceissues.html +8 -5
  16. data/doc/appscript-manual/14_problemapps.html +4 -5
  17. data/doc/mactypes-manual/index.html +27 -9
  18. data/doc/osax-manual/index.html +1 -1
  19. data/rb-appscript-0.2.0.gem +0 -0
  20. data/rb-appscript.gemspec +1 -1
  21. data/sample/AB_list_people_with_emails.rb +2 -1
  22. data/sample/Create_daily_iCal_todos.rb +4 -3
  23. data/sample/Hello_world.rb +2 -1
  24. data/sample/List_iTunes_playlist_names.rb +2 -1
  25. data/sample/Make_Mail_message.rb +2 -1
  26. data/sample/Open_file_in_TextEdit.rb +2 -1
  27. data/sample/Organize_Mail_messages.rb +3 -2
  28. data/sample/Print_folder_tree.rb +2 -1
  29. data/sample/Select_all_HTML_files.rb +3 -2
  30. data/sample/Set_iChat_status.rb +4 -3
  31. data/sample/Simple_Finder_GUI_Scripting.rb +3 -2
  32. data/sample/Stagger_Finder_windows.rb +2 -1
  33. data/sample/TextEdit_demo.rb +6 -5
  34. data/sample/iTunes_top40_to_html.rb +3 -2
  35. data/src/lib/_aem/mactypes.rb +24 -2
  36. data/src/lib/_appscript/referencerenderer.rb +7 -7
  37. data/src/lib/appscript.rb +32 -12
  38. data/src/lib/osax.rb +21 -19
  39. data/src/rbae.c +0 -12
  40. data/test/test_appscriptreference.rb +30 -30
  41. metadata +3 -2
@@ -12,8 +12,9 @@
12
12
  # incoming messages from this sender directly into this mailbox.
13
13
 
14
14
  require "appscript"
15
+ include Appscript
15
16
 
16
- mail = AS.app('Mail')
17
+ mail = app('Mail')
17
18
 
18
19
  # get the current selection in Mail and make sure it's an email message
19
20
  selection = mail.selection.get
@@ -47,7 +48,7 @@ if not mail.rules[folder_name].exists
47
48
  new_rule = mail.rules[1].after.make(:new=>:rule, :with_properties=>{
48
49
  :name=>folder_name,
49
50
  :should_move_message=>true,
50
- :move_message=>AS.app.mailboxes[folder_name],
51
+ :move_message=>app.mailboxes[folder_name],
51
52
  :stop_evaluating_rules=>true})
52
53
  new_rule.make(:new=>:rule_condition, :with_properties=>{
53
54
  :expression=>address,
@@ -3,10 +3,11 @@
3
3
  # Prints the sub-folder hierarchy of a given folder as a list of folder names indented according to depth.
4
4
 
5
5
  require "appscript"
6
+ include Appscript
6
7
 
7
8
  def print_folder_tree(folder, indent='')
8
9
  puts indent + folder.name.get
9
10
  folder.folders.get.each { |folder| print_folder_tree(folder, indent + "\t") }
10
11
  end
11
12
 
12
- print_folder_tree(AS.app('Finder').home.folders['Documents'])
13
+ print_folder_tree(app('Finder').home.folders['Documents'])
@@ -3,6 +3,7 @@
3
3
  # Selects all .htm/.html files in the top Finder window.
4
4
 
5
5
  require "appscript"
6
+ include Appscript
6
7
 
7
- w = AS.app('Finder').Finder_windows[1].target.get
8
- w.files[AS.its.name_extension.is_in(['htm', 'html'])].select
8
+ w = app('Finder').Finder_windows[1].target.get
9
+ w.files[its.name_extension.is_in(['htm', 'html'])].select
@@ -7,14 +7,15 @@
7
7
  # <http://developer.apple.com/cocoa/applescriptforapps.html>
8
8
 
9
9
  require "appscript"
10
+ include Appscript
10
11
 
11
12
  begin
12
- track_name = AS.app("iTunes").current_track.name.get
13
- rescue AS::CommandError => e
13
+ track_name = app("iTunes").current_track.name.get
14
+ rescue CommandError => e
14
15
  if e.to_i == -1728 # Can't get reference.
15
16
  track_name = 'No track selected.'
16
17
  else
17
18
  raise
18
19
  end
19
20
  end
20
- AS.app("iChat").status_message.set(track_name)
21
+ app("iChat").status_message.set(track_name)
@@ -1,14 +1,15 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'appscript'
4
+ include Appscript
4
5
 
5
6
  # Opens a new Finder smart folder that searches for 'ruby', via GUI Scripting
6
7
 
7
8
  # (Note: to use GUI Scripting, 'Enable access for assistive devices' option must
8
9
  # be enabled in the Universal Access panel of System Preferences.)
9
10
 
10
- se = AS.app('System Events')
11
+ se = app('System Events')
11
12
 
12
- AS.app('Finder').activate
13
+ app('Finder').activate
13
14
  se.keystroke('n', :using=>[:command_down, :option_down])
14
15
  se.keystroke('ruby')
@@ -5,12 +5,13 @@
5
5
  # that uses standard window class terminology.)
6
6
 
7
7
  require "appscript"
8
+ include Appscript
8
9
 
9
10
  x, y = 0, 44
10
11
  offset = 22
11
12
 
12
13
  # Get list of window references, ignoring any minimised windows
13
- window_list = AS.app('Finder').windows[AS.its.collapsed.not].get
14
+ window_list = app('Finder').windows[its.collapsed.not].get
14
15
 
15
16
  # Move windows while preserving their original sizes
16
17
  window_list.reverse.each do |window|
@@ -3,8 +3,9 @@
3
3
  # Demonstrates various references and commands in action.
4
4
 
5
5
  require "appscript"
6
+ include Appscript
6
7
 
7
- textedit = AS.app('TextEdit') # get an application object for TextEdit
8
+ textedit = app('TextEdit') # get an application object for TextEdit
8
9
 
9
10
 
10
11
  # tell application "TextEdit" to activate
@@ -102,12 +103,12 @@ p textedit.documents[1].text.paragraphs.get
102
103
 
103
104
  # tell application "TextEdit" to get every paragraph of document 1
104
105
  # where it is not "\n" -- get non-empty paragraphs
105
- p textedit.documents[1].paragraphs[AS.its.ne("\n")].get
106
+ p textedit.documents[1].paragraphs[its.ne("\n")].get
106
107
 
107
108
 
108
109
  # tell application "TextEdit" to get text of every document
109
110
  # whose text begins with "H"
110
- p textedit.documents[AS.its.text.starts_with('H')].text.get
111
+ p textedit.documents[its.text.starts_with('H')].text.get
111
112
 
112
113
 
113
114
 
@@ -117,10 +118,10 @@ p textedit.documents[AS.its.text.starts_with('H')].text.get
117
118
  # # tell application "Tex-Edit Plus" to get words (character 5)
118
119
  # # thru (paragraph 9) of document 1
119
120
  # p app('Tex-Edit Plus').documents[1] \
120
- # .words[AS.con.characters[5], AS.con.paragraphs[9]].get
121
+ # .words[con.characters[5], con.paragraphs[9]].get
121
122
  #
122
123
  #
123
124
  # # tell application "Tex-Edit Plus" to get every word of text
124
125
  # # of document 1 whose color is {0, 0, 0}
125
126
  # p app('Tex-Edit Plus').documents[1].text.paragraphs \
126
- # [AS.its.color.equals((0, 0, 0))].get
127
+ # [its.color.equals((0, 0, 0))].get
@@ -6,6 +6,7 @@
6
6
  # Requires the Amrita templating engine: http://amrita.sourceforge.jp
7
7
 
8
8
  require 'appscript'
9
+ include Appscript
9
10
  require 'osax'
10
11
  require "amrita/template"
11
12
  include Amrita
@@ -42,7 +43,7 @@ sa = OSAX.osax('StandardAdditions')
42
43
  out_file = sa.choose_file_name(:default_name=>'My iTunes Top 40.html')
43
44
 
44
45
  # Get the played count, name and album for every track in iTunes
45
- tracks = AS.app('iTunes').library_playlists[1].tracks
46
+ tracks = app('iTunes').library_playlists[1].tracks
46
47
  info = tracks.played_count.get.zip(tracks.name.get, tracks.album.get)
47
48
  # Extract the top 40 most played entries
48
49
  top40 = info.sort.reverse[0, 40]
@@ -57,7 +58,7 @@ tmpl.prettyprint = true
57
58
  File.open(out_file.to_s, 'w') { |f| tmpl.expand(f, data) }
58
59
 
59
60
  # Open file in Safari for viewing
60
- safari = AS.app('Safari')
61
+ safari = app('Safari')
61
62
  safari.activate
62
63
  safari.open(out_file)
63
64
 
@@ -13,16 +13,18 @@ module MacTypes
13
13
  class FileBase
14
14
 
15
15
  URLPrefix = 'file://localhost'
16
+ URLPatt = Regexp.new('file://(?:.*?)(/.*)', Regexp::IGNORECASE)
16
17
 
17
18
  def FileBase._path_to_url(path)
18
19
  return URLPrefix + path.gsub(/[^a-zA-Z0-9_.-\/]/) { |c| "%%%02x" % c[0] }
19
20
  end
20
21
 
21
22
  def FileBase._url_to_path(url)
22
- if url[0, URLPrefix.length] != URLPrefix
23
+ match = url.match(URLPatt)
24
+ if not match
23
25
  raise ArgumentError, "Not a file:// URL."
24
26
  end
25
- return url[URLPrefix.length, url.length].gsub(/%../) { |s| "%c" % s[1,2].hex }
27
+ return match[1].gsub(/%../) { |s| "%c" % s[1,2].hex }
26
28
  end
27
29
 
28
30
  def FileBase._coerce(desc, type, path=nil)
@@ -78,6 +80,11 @@ module MacTypes
78
80
  KAE::TypeAlias, path))
79
81
  end
80
82
 
83
+ def Alias.url(url)
84
+ # Make Alias object from file URL. Note: only the path portion of the URL is used; the domain will always be localhost.
85
+ return Alias.path(_url_to_path(url))
86
+ end
87
+
81
88
  def Alias.desc(desc)
82
89
  # Make Alias object from CarbonX.AE.AEDesc of typeAlias. Note: descriptor type is not checked; clients are responsible for passing the correct type as other types will cause unexpected problems/errors.
83
90
  return new(desc)
@@ -90,6 +97,11 @@ module MacTypes
90
97
  return @desc
91
98
  end
92
99
 
100
+ def url
101
+ # Get as URL string.
102
+ return desc.coerce(KAE::TypeFileURL).data
103
+ end
104
+
93
105
  def path
94
106
  # Get as POSIX path.
95
107
  return FileBase._url_to_path(FileBase._coerce(@desc, KAE::TypeFileURL).data)
@@ -131,6 +143,11 @@ module MacTypes
131
143
  return new(path, nil)
132
144
  end
133
145
 
146
+ def FileURL.url(url)
147
+ # Make FileURL object from file URL. Note: only the path portion of the URL is used; the domain will always be localhost.
148
+ return FileURL.path(_url_to_path(url))
149
+ end
150
+
134
151
  def FileURL.desc(desc)
135
152
  # Make FileURL object from AEDesc of typeFSS, typeFSRef, typeFileURL. Note: descriptor type is not checked; clients are responsible for passing the correct type as other types will cause unexpected problems/errors.
136
153
  return new(nil, desc)
@@ -146,6 +163,11 @@ module MacTypes
146
163
  return @desc
147
164
  end
148
165
 
166
+ def url
167
+ # Get as URL string.
168
+ return desc.coerce(KAE::TypeFileURL).data
169
+ end
170
+
149
171
  def path
150
172
  # Get as POSIX path.
151
173
  if not @path
@@ -13,7 +13,7 @@ class ReferenceRenderer
13
13
 
14
14
  def initialize(app_data)
15
15
  @_app_data = app_data
16
- @result = "AS"
16
+ @result = ""
17
17
  end
18
18
 
19
19
  def _format(val)
@@ -73,24 +73,24 @@ class ReferenceRenderer
73
73
 
74
74
  def app
75
75
  if @_app_data.path
76
- @result += ".app(#{@_app_data.path.inspect})"
76
+ @result = "app(#{@_app_data.path.inspect})"
77
77
  elsif @_app_data.pid
78
- @result += ".app.by_pid(#{@_app_data.pid.inspect})"
78
+ @result = "app.by_pid(#{@_app_data.pid.inspect})"
79
79
  elsif @_app_data.url
80
- @result += ".app.by_url(#{@_app_data.url.inspect})"
80
+ @result = "app.by_url(#{@_app_data.url.inspect})"
81
81
  else
82
- @result += ".app.current"
82
+ @result = "app.current"
83
83
  end
84
84
  return self
85
85
  end
86
86
 
87
87
  def con
88
- @result += ".con"
88
+ @result = "con"
89
89
  return self
90
90
  end
91
91
 
92
92
  def its
93
- @result += ".its"
93
+ @result = "its"
94
94
  return self
95
95
  end
96
96
 
data/src/lib/appscript.rb CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  require "_aem/mactypes"
6
6
 
7
- module AS
7
+ module Appscript
8
8
  # The following methods and classes are of interest to end users:
9
9
  # app, con, its, CommandError, ApplicationNotFoundError
10
10
  # Other classes are only of interest to implementors who need to hook in their own code.
@@ -182,7 +182,7 @@ module AS
182
182
  end
183
183
 
184
184
  def unpack_contains_comp_descriptor(op1, op2)
185
- if op1.is_a?(AS::Reference) and op1.AS_aem_reference.AEM_root == AEMReference::Its
185
+ if op1.is_a?(Appscript::Reference) and op1.AS_aem_reference.AEM_root == AEMReference::Its
186
186
  return op1.contains(op2)
187
187
  else
188
188
  return super
@@ -219,7 +219,7 @@ module AS
219
219
  end
220
220
 
221
221
  def to_s
222
- s= 'AS.' + @_call[0]
222
+ s= @_call[0]
223
223
  @_call[1, @_call.length].each do |name, args| if name == :[]
224
224
  if args.length == 1
225
225
  s += "[#{args[0]}]"
@@ -272,7 +272,7 @@ module AS
272
272
  if selector == nil
273
273
  selector = value_if_none
274
274
  end
275
- if selector.is_a?(AS::GenericReference)
275
+ if selector.is_a?(Appscript::GenericReference)
276
276
  return selector.AS_resolve(@AS_app_data).AS_aem_reference
277
277
  elsif selector.is_a?(Reference)
278
278
  return selector.AS_aem_reference
@@ -400,7 +400,7 @@ module AS
400
400
  end
401
401
  end
402
402
  # rescue => e
403
- # raise AS::CommandError.new(self, name, args, e)
403
+ # raise Appscript::CommandError.new(self, name, args, e)
404
404
  # end
405
405
  # build and send the Apple event, returning its result, if any
406
406
  begin
@@ -541,7 +541,7 @@ module AS
541
541
  self._resolve_range_boundary(end_range_selector, -1))
542
542
  elsif selector.is_a?(String)
543
543
  new_ref = @AS_aem_reference.by_name(selector)
544
- elsif selector.is_a?(AS::GenericReference)
544
+ elsif selector.is_a?(Appscript::GenericReference)
545
545
  new_ref = @AS_aem_reference.by_filter(
546
546
  selector.AS_resolve(@AS_app_data).AS_aem_reference)
547
547
  else
@@ -771,9 +771,9 @@ module AS
771
771
 
772
772
  #######
773
773
 
774
- AS_App = AS::GenericApplication.new(Application)
775
- AS_Con = AS::GenericReference.new(['con'])
776
- AS_Its = AS::GenericReference.new(['its'])
774
+ AS_App = Appscript::GenericApplication.new(Application)
775
+ AS_Con = Appscript::GenericReference.new(['con'])
776
+ AS_Its = Appscript::GenericReference.new(['its'])
777
777
 
778
778
 
779
779
  ######################################################################
@@ -781,7 +781,7 @@ module AS
781
781
  ######################################################################
782
782
  # public (note: Application & GenericApplication classes may also be accessed if subclassing Application class is required)
783
783
 
784
- def AS.app(*args)
784
+ def Appscript.app(*args)
785
785
  if args == []
786
786
  return AS_App
787
787
  else
@@ -789,11 +789,29 @@ module AS
789
789
  end
790
790
  end
791
791
 
792
- def AS.con
792
+ def Appscript.con
793
793
  return AS_Con
794
794
  end
795
795
 
796
- def AS.its
796
+ def Appscript.its
797
+ return AS_Its
798
+ end
799
+
800
+ # also define app, con, its as instance methods so that clients can 'include Appscript'
801
+
802
+ def app(*args)
803
+ if args == []
804
+ return AS_App
805
+ else
806
+ return AS_App.by_name(*args)
807
+ end
808
+ end
809
+
810
+ def con
811
+ return AS_Con
812
+ end
813
+
814
+ def its
797
815
  return AS_Its
798
816
  end
799
817
 
@@ -828,3 +846,5 @@ module AS
828
846
 
829
847
  ApplicationNotFoundError = FindApp::ApplicationNotFoundError
830
848
  end
849
+
850
+ AS = Appscript # backwards compatibility # TO DO: remove in 0.3.0
data/src/lib/osax.rb CHANGED
@@ -20,22 +20,22 @@ module OSAX
20
20
  OSAXCache = {}
21
21
  OSAXNames = []
22
22
 
23
- se = AS.app('System Events')
23
+ se = Appscript.app('System Events')
24
24
  [se.system_domain, se.local_domain, se.user_domain].each do |domain|
25
25
  osaxen = domain.scripting_additions_folder.files[
26
- AS.its.file_type.eq('osax').or(AS.its.name_extension.eq('osax'))]
26
+ Appscript.its.file_type.eq('osax').or(Appscript.its.name_extension.eq('osax'))]
27
27
  osaxen.name.get.zip(osaxen.POSIX_path.get).each do |name, path|
28
28
  name = name.sub(/(?i)\.osax$/, '') # remove name extension, if any
29
29
  OSAXNames.push(name)
30
30
  OSAXCache[name.downcase] = [path, nil]
31
31
  end
32
32
  end
33
- OSAXNames.sort.uniq
33
+ OSAXNames.sort!.uniq!
34
34
 
35
35
  #######
36
36
  # modified AppData class
37
37
 
38
- class OSAXData < AS::AppData
38
+ class OSAXData < Appscript::AppData
39
39
 
40
40
  def initialize(name, pid, url, terms)
41
41
  super(AEM::Application, name, pid, url, terms)
@@ -59,7 +59,7 @@ module OSAX
59
59
  end
60
60
  end
61
61
  @type_by_code, @type_by_name, @reference_by_code, @reference_by_name = @_terms
62
- extend(AS::AppDataAccessors)
62
+ extend(Appscript::AppDataAccessors)
63
63
  end
64
64
 
65
65
  end
@@ -85,19 +85,18 @@ module OSAX
85
85
  end
86
86
 
87
87
 
88
- class ScriptingAddition < AS::Reference
88
+ class ScriptingAddition < Appscript::Reference
89
89
  # Represents a single scripting addition.
90
90
 
91
- def initialize(name)
91
+ def initialize(name, osax_data=nil)
92
92
  # name: string -- a scripting addition's name, e.g. "StandardAdditions";
93
93
  # basically its filename minus the '.osax' suffix
94
94
  #
95
95
  # Note that name is case-insensitive and an '.osax' suffix is ignored if given.
96
96
  @_osax_name = name
97
- if name.is_a?(OSAXData)
98
- osax_data = name
99
- else
100
- path, terms = OSAXCache[name.downcase.sub(/(?i)\.osax$/, '')]
97
+ if not osax_data
98
+ osax_name = name.downcase.sub(/(?i)\.osax$/, '')
99
+ path, terms = OSAXCache[osax_name]
101
100
  if not path
102
101
  raise ArgumentError, "Scripting addition not found: #{name.inspect}"
103
102
  end
@@ -105,7 +104,7 @@ module OSAX
105
104
  @_terms = terms
106
105
  else
107
106
  desc = AE.get_app_terminology(path).coerce(KAE::TypeAEList)
108
- @_terms = OSAXCache[name.downcase][1] = \
107
+ @_terms = OSAXCache[osax_name][1] = \
109
108
  Terminology.tables_for_aetes(DefaultCodecs.unpack(desc))
110
109
  end
111
110
  osax_data = OSAXData.new(nil, nil, nil, @_terms)
@@ -124,7 +123,7 @@ module OSAX
124
123
  def method_missing(name, *args)
125
124
  begin
126
125
  super
127
- rescue AS::CommandError => e
126
+ rescue Appscript::CommandError => e
128
127
  if e.to_i == -1713 # 'No user interaction allowed' error (e.g. user tried to send a 'display dialog' command to a non-GUI ruby process), so convert the target process to a full GUI process and try again
129
128
  AE.transform_process_to_foreground_application
130
129
  activate
@@ -140,31 +139,34 @@ module OSAX
140
139
 
141
140
  def by_name(name)
142
141
  # name : string -- name or full path to application
143
- return ScriptingAddition.new(OSAXData.new(FindApp.by_name(name), nil, nil, @_terms))
142
+ return ScriptingAddition.new(@_osax_name,
143
+ OSAXData.new(FindApp.by_name(name), nil, nil, @_terms))
144
144
  end
145
145
 
146
146
  def by_id(id)
147
147
  # id : string -- bundle id of application
148
- return ScriptingAddition.new(OSAXData.new(FindApp.by_id(id), nil, nil, @_terms))
148
+ return ScriptingAddition.new(@_osax_name,
149
+ OSAXData.new(FindApp.by_id(id), nil, nil, @_terms))
149
150
  end
150
151
 
151
152
  def by_creator(creator)
152
153
  # creator : string -- four-character creator code of application
153
- return ScriptingAddition.new(OSAXData.new(FindApp.by_creator(creator), nil, nil, @_terms))
154
+ return ScriptingAddition.new(@_osax_name,
155
+ OSAXData.new(FindApp.by_creator(creator), nil, nil, @_terms))
154
156
  end
155
157
 
156
158
  def by_pid(pid)
157
159
  # pid : integer -- Unix process id
158
- return ScriptingAddition.new(OSAXData.new(nil, pid, nil, @_terms))
160
+ return ScriptingAddition.new(@_osax_name, OSAXData.new(nil, pid, nil, @_terms))
159
161
  end
160
162
 
161
163
  def by_url(url)
162
164
  # url : string -- eppc URL of application
163
- return ScriptingAddition.new(OSAXData.new(nil, nil, url, @_terms))
165
+ return ScriptingAddition.new(@_osax_name, OSAXData.new(nil, nil, url, @_terms))
164
166
  end
165
167
 
166
168
  def current
167
- return ScriptingAddition.new(OSAXData.new(nil, nil, nil, @_terms))
169
+ return ScriptingAddition.new(@_osax_name, OSAXData.new(nil, nil, nil, @_terms))
168
170
  end
169
171
  end
170
172