rb-scpt 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGES +497 -0
- data/doc/aem-manual/01_introduction.html +60 -0
- data/doc/aem-manual/02_apioverview.html +107 -0
- data/doc/aem-manual/03_packingandunpackingdata.html +135 -0
- data/doc/aem-manual/04_references.html +409 -0
- data/doc/aem-manual/05_targetingapplications.html +164 -0
- data/doc/aem-manual/06_buildingandsendingevents.html +229 -0
- data/doc/aem-manual/07_findapp.html +63 -0
- data/doc/aem-manual/08_examples.html +94 -0
- data/doc/aem-manual/aemreferenceinheritance.gif +0 -0
- data/doc/aem-manual/index.html +56 -0
- data/doc/appscript-manual/01_introduction.html +94 -0
- data/doc/appscript-manual/02_aboutappscripting.html +247 -0
- data/doc/appscript-manual/03_quicktutorial.html +167 -0
- data/doc/appscript-manual/04_gettinghelp.html +188 -0
- data/doc/appscript-manual/05_keywordconversion.html +106 -0
- data/doc/appscript-manual/06_classesandenums.html +192 -0
- data/doc/appscript-manual/07_applicationobjects.html +211 -0
- data/doc/appscript-manual/08_realvsgenericreferences.html +96 -0
- data/doc/appscript-manual/09_referenceforms.html +241 -0
- data/doc/appscript-manual/10_referenceexamples.html +154 -0
- data/doc/appscript-manual/11_applicationcommands.html +245 -0
- data/doc/appscript-manual/12_commandexamples.html +138 -0
- data/doc/appscript-manual/13_performanceissues.html +142 -0
- data/doc/appscript-manual/14_notes.html +80 -0
- data/doc/appscript-manual/application_architecture.gif +0 -0
- data/doc/appscript-manual/application_architecture2.gif +0 -0
- data/doc/appscript-manual/finder_to_textedit_event.gif +0 -0
- data/doc/appscript-manual/index.html +62 -0
- data/doc/appscript-manual/relationships_example.gif +0 -0
- data/doc/appscript-manual/ruby_to_itunes_event.gif +0 -0
- data/doc/full.css +106 -0
- data/doc/index.html +45 -0
- data/doc/mactypes-manual/01_introduction.html +54 -0
- data/doc/mactypes-manual/02_aliasclass.html +124 -0
- data/doc/mactypes-manual/03_fileurlclass.html +126 -0
- data/doc/mactypes-manual/04_unitsclass.html +100 -0
- data/doc/mactypes-manual/index.html +53 -0
- data/doc/osax-manual/01_introduction.html +67 -0
- data/doc/osax-manual/02_interface.html +147 -0
- data/doc/osax-manual/03_examples.html +73 -0
- data/doc/osax-manual/04_notes.html +61 -0
- data/doc/osax-manual/index.html +53 -0
- data/doc/rb-appscript-logo.png +0 -0
- data/extconf.rb +65 -0
- data/rb-scpt.gemspec +14 -0
- data/sample/AB_export_vcard.rb +31 -0
- data/sample/AB_list_people_with_emails.rb +13 -0
- data/sample/Add_iCal_event.rb +21 -0
- data/sample/Create_daily_iCal_todos.rb +75 -0
- data/sample/Export_Address_Book_phone_numbers.rb +59 -0
- data/sample/Hello_world.rb +21 -0
- data/sample/List_iTunes_playlist_names.rb +11 -0
- data/sample/Make_Mail_message.rb +33 -0
- data/sample/Open_file_in_TextEdit.rb +13 -0
- data/sample/Organize_Mail_messages.rb +61 -0
- data/sample/Print_folder_tree.rb +16 -0
- data/sample/Select_all_HTML_files.rb +14 -0
- data/sample/Set_iChat_status.rb +24 -0
- data/sample/Simple_Finder_GUI_Scripting.rb +18 -0
- data/sample/Stagger_Finder_windows.rb +25 -0
- data/sample/TextEdit_demo.rb +130 -0
- data/sample/iTunes_top40_to_html.rb +71 -0
- data/src/SendThreadSafe.c +380 -0
- data/src/SendThreadSafe.h +139 -0
- data/src/lib/_aem/aemreference.rb +1022 -0
- data/src/lib/_aem/codecs.rb +662 -0
- data/src/lib/_aem/connect.rb +205 -0
- data/src/lib/_aem/encodingsupport.rb +77 -0
- data/src/lib/_aem/findapp.rb +85 -0
- data/src/lib/_aem/mactypes.rb +251 -0
- data/src/lib/_aem/send.rb +279 -0
- data/src/lib/_aem/typewrappers.rb +59 -0
- data/src/lib/_appscript/defaultterminology.rb +277 -0
- data/src/lib/_appscript/referencerenderer.rb +245 -0
- data/src/lib/_appscript/reservedkeywords.rb +116 -0
- data/src/lib/_appscript/safeobject.rb +249 -0
- data/src/lib/_appscript/terminology.rb +471 -0
- data/src/lib/aem.rb +253 -0
- data/src/lib/appscript.rb +1075 -0
- data/src/lib/kae.rb +1489 -0
- data/src/lib/osax.rb +659 -0
- data/src/rbae.c +979 -0
- data/test/README +3 -0
- data/test/test_aemreference.rb +118 -0
- data/test/test_appscriptcommands.rb +152 -0
- data/test/test_appscriptreference.rb +106 -0
- data/test/test_codecs.rb +186 -0
- data/test/test_findapp.rb +26 -0
- data/test/test_mactypes.rb +79 -0
- data/test/test_osax.rb +54 -0
- data/test/testall.sh +10 -0
- metadata +145 -0
@@ -0,0 +1,205 @@
|
|
1
|
+
#
|
2
|
+
# rb-appscript
|
3
|
+
#
|
4
|
+
# connect -- launch applications and create AEAddressDescs
|
5
|
+
#
|
6
|
+
|
7
|
+
module Connect
|
8
|
+
# Creates Apple event descriptor records of typeProcessSerialNumber, typeKernelProcessID and typeApplicationURL, used to specify the target application in Send::Event constructor.
|
9
|
+
|
10
|
+
require "ae"
|
11
|
+
require "kae"
|
12
|
+
require "_aem/codecs"
|
13
|
+
require "_aem/send"
|
14
|
+
require "_aem/encodingsupport"
|
15
|
+
|
16
|
+
@@encoding_support = AEMEncodingSupport.encoding_support
|
17
|
+
|
18
|
+
LSLaunchDefaults = 0x00000001
|
19
|
+
LSLaunchAndPrint = 0x00000002
|
20
|
+
LSLaunchReserved2 = 0x00000004
|
21
|
+
LSLaunchReserved3 = 0x00000008
|
22
|
+
LSLaunchReserved4 = 0x00000010
|
23
|
+
LSLaunchReserved5 = 0x00000020
|
24
|
+
LSLaunchAndDisplayErrors = 0x00000040
|
25
|
+
LSLaunchInhibitBGOnly = 0x00000080
|
26
|
+
LSLaunchDontAddToRecents = 0x00000100
|
27
|
+
LSLaunchDontSwitch = 0x00000200
|
28
|
+
LSLaunchNoParams = 0x00000800
|
29
|
+
LSLaunchAsync = 0x00010000
|
30
|
+
LSLaunchStartClassic = 0x00020000
|
31
|
+
LSLaunchInClassic = 0x00040000
|
32
|
+
LSLaunchNewInstance = 0x00080000
|
33
|
+
LSLaunchAndHide = 0x00100000
|
34
|
+
LSLaunchAndHideOthers = 0x00200000
|
35
|
+
LSLaunchHasUntrustedContents = 0x00400000
|
36
|
+
|
37
|
+
KNoProcess = 0
|
38
|
+
KCurrentProcess = 2
|
39
|
+
|
40
|
+
def Connect.make_address_desc(psn)
|
41
|
+
return AE::AEDesc.new(KAE::TypeProcessSerialNumber, psn.pack('LL'))
|
42
|
+
end
|
43
|
+
|
44
|
+
NullAddress = make_address_desc([0,KNoProcess])
|
45
|
+
LaunchEvent = Send::Event.new(Connect::NullAddress, 'ascrnoop').AEM_event
|
46
|
+
RunEvent = Send::Event.new(Connect::NullAddress, 'aevtoapp').AEM_event
|
47
|
+
|
48
|
+
#######
|
49
|
+
# public
|
50
|
+
|
51
|
+
class CantLaunchApplicationError < RuntimeError
|
52
|
+
|
53
|
+
# Taken from <http://developer.apple.com/documentation/Carbon/Reference/LaunchServicesReference>:
|
54
|
+
LSErrors = {
|
55
|
+
-10660 => "The application cannot be run because it is inside a Trash folder.",
|
56
|
+
-10810 => "An unknown error has occurred.",
|
57
|
+
-10811 => "The item to be registered is not an application.",
|
58
|
+
-10813 => "Data of the desired type is not available (for example, there is no kind string).",
|
59
|
+
-10814 => "No application in the Launch Services database matches the input criteria.",
|
60
|
+
-10817 => "Data is structured improperly (for example, an item's information property list is malformed).",
|
61
|
+
-10818 => "A launch of the application is already in progress.",
|
62
|
+
-10822 => "There is a problem communicating with the server process that maintains the Launch Services database.",
|
63
|
+
-10823 => "The filename extension to be hidden cannot be hidden.",
|
64
|
+
-10825 => "The application to be launched cannot run on the current Mac OS version.",
|
65
|
+
-10826 => "The user does not have permission to launch the application (on a managed network).",
|
66
|
+
-10827 => "The executable file is missing or has an unusable format.",
|
67
|
+
-10828 => "The Classic emulation environment was required but is not available.",
|
68
|
+
-10829 => "The application to be launched cannot run simultaneously in two different user sessions.",
|
69
|
+
}
|
70
|
+
|
71
|
+
def initialize(error_number)
|
72
|
+
@error_number = error_number
|
73
|
+
super("#{ LSErrors.fetch(@error_number, 'OS error') } (#{ @error_number })")
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_i
|
77
|
+
return @error_number
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
|
83
|
+
def Connect.launch_application(path, event)
|
84
|
+
path = @@encoding_support.to_utf8_string(path)
|
85
|
+
begin
|
86
|
+
return AE.launch_application(path, event,
|
87
|
+
LSLaunchNoParams | LSLaunchStartClassic | LSLaunchDontSwitch)
|
88
|
+
rescue AE::MacOSError => err
|
89
|
+
raise CantLaunchApplicationError, err.to_i
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def Connect.launch_app_with_launch_event(path)
|
94
|
+
# Send a 'launch' event to an application. If application is not already running, it will be launched in background first.
|
95
|
+
path = @@encoding_support.to_utf8_string(path)
|
96
|
+
begin
|
97
|
+
# If app is already running, calling AE.launch_application will send a 'reopen' event, so need to check for this first:
|
98
|
+
psn = AE.psn_for_application_path(path)
|
99
|
+
rescue AE::MacOSError => err
|
100
|
+
if err.to_i == -600 # Application isn't running, so launch it and send it a 'launch' event
|
101
|
+
sleep(1)
|
102
|
+
launch_application(path, LaunchEvent)
|
103
|
+
else
|
104
|
+
raise
|
105
|
+
end
|
106
|
+
else # App is already running, so send it a 'launch' event
|
107
|
+
Send::Event.new(make_address_desc(psn), 'ascrnoop').send(60, KAE::KAENoReply)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
##
|
112
|
+
|
113
|
+
def Connect.process_exists_for_path?(path)
|
114
|
+
path = @@encoding_support.to_utf8_string(path)
|
115
|
+
# Does a local process launched from the specified application file exist?
|
116
|
+
# Note: if path is invalid, an AE::MacOSError is raised.
|
117
|
+
begin
|
118
|
+
AE.psn_for_application_path(path)
|
119
|
+
return true
|
120
|
+
rescue AE::MacOSError => err
|
121
|
+
if err.to_i == -600
|
122
|
+
return false
|
123
|
+
else
|
124
|
+
raise
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def Connect.process_exists_for_pid?(pid)
|
130
|
+
# Is there a local application process with the given unix process id?
|
131
|
+
begin
|
132
|
+
AE.psn_for_process_id(pid)
|
133
|
+
return true
|
134
|
+
rescue AE::MacOSError => err
|
135
|
+
if err.to_i == -600
|
136
|
+
return false
|
137
|
+
else
|
138
|
+
raise
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def Connect.process_exists_for_url?(url)
|
144
|
+
# Does an application process specified by the given eppc:// URL exist?
|
145
|
+
# Note: this will send a 'launch' Apple event to the target application.
|
146
|
+
raise ArgumentError, "Invalid url: #{url}" if not url.include?(':') # workaround: process will crash if no colon in URL (OS bug)
|
147
|
+
return process_exists_for_desc?(AE::AEDesc.new(KAE::TypeApplicationURL, url))
|
148
|
+
end
|
149
|
+
|
150
|
+
def Connect.process_exists_for_desc?(desc)
|
151
|
+
# Does an application process specified by the given AEAddressDesc exist?
|
152
|
+
# Returns false if process doesn't exist OR remote Apple events aren't allowed.
|
153
|
+
# Note: this will send a 'launch' Apple event to the target application.
|
154
|
+
begin
|
155
|
+
# This will usually raise error -1708 if process is running, and various errors
|
156
|
+
# if the process doesn't exist/can't be reached. If app is running but busy,
|
157
|
+
# AESendMessage may return a timeout error (this should be -1712, but
|
158
|
+
# -609 is often returned instead for some reason).
|
159
|
+
Send::Event.new(desc, 'ascrnoop').send
|
160
|
+
rescue Send::EventError => err
|
161
|
+
return (not [-600, -905].include?(err.to_i)) # not running/no network access
|
162
|
+
end
|
163
|
+
return true
|
164
|
+
end
|
165
|
+
|
166
|
+
##
|
167
|
+
|
168
|
+
CurrentApp = make_address_desc([0, KCurrentProcess])
|
169
|
+
|
170
|
+
def Connect.local_app(path)
|
171
|
+
# Make an AEAddressDesc identifying a local application. (Application will be launched if not already running.)
|
172
|
+
# path : string -- full path to application, e.g. '/Applications/TextEdit.app'
|
173
|
+
# Result : AEAddressDesc
|
174
|
+
#
|
175
|
+
# Always creates AEAddressDesc by process serial number; that way there's no confusion if multiple versions of the same app are running.
|
176
|
+
path = @@encoding_support.to_utf8_string(path)
|
177
|
+
begin
|
178
|
+
psn = AE.psn_for_application_path(path)
|
179
|
+
rescue AE::MacOSError => err
|
180
|
+
if err.to_i == -600 # Application isn't running, so launch it in background and send it a standard 'run' event.
|
181
|
+
sleep(1)
|
182
|
+
psn = launch_application(path, RunEvent)
|
183
|
+
else
|
184
|
+
raise
|
185
|
+
end
|
186
|
+
end
|
187
|
+
return make_address_desc(psn)
|
188
|
+
end
|
189
|
+
|
190
|
+
def Connect.local_app_by_pid(pid)
|
191
|
+
# Make an AEAddressDesc identifying a running application by Unix process id.
|
192
|
+
# pid : integer -- unsigned 32-bit integer
|
193
|
+
# Result : AEAddressDesc
|
194
|
+
return AE::AEDesc.new(KAE::TypeKernelProcessID, [pid].pack('L'))
|
195
|
+
end
|
196
|
+
|
197
|
+
def Connect.remote_app(url)
|
198
|
+
url = @@encoding_support.to_utf8_string(url)
|
199
|
+
# Make an AEAddressDesc identifying a running application on another machine.
|
200
|
+
# url : string -- URL for remote application, e.g. 'eppc://user:password@0.0.0.1/TextEdit'
|
201
|
+
# Result : AEAddressDesc
|
202
|
+
raise ArgumentError, "Invalid url: #{url}" if not url.include?(':') # workaround: process will crash if no colon in URL (OS bug)
|
203
|
+
return AE::AEDesc.new(KAE::TypeApplicationURL, url)
|
204
|
+
end
|
205
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
#
|
2
|
+
# rb-appscript
|
3
|
+
#
|
4
|
+
# encodingsupport -- support string encodings in Ruby 1.9+
|
5
|
+
#
|
6
|
+
|
7
|
+
|
8
|
+
module AEMEncodingSupport
|
9
|
+
|
10
|
+
module EnableStringEncodings
|
11
|
+
# AE extension methods consume and return Strings containing UTF-8 encoded data, ignoring
|
12
|
+
# any attached encoding information. This module provides wrappers for AE interactions that
|
13
|
+
# take care of any encoding issues in Ruby 1.9+.
|
14
|
+
|
15
|
+
def EnableStringEncodings.to_utf8_string(s)
|
16
|
+
# Call before passing a string to an AE method that expects it to contain UTF-8 encoded data.
|
17
|
+
return s if [Encoding::ASCII_8BIT, Encoding::UTF_8].include?(s.encoding)
|
18
|
+
return s.encode('UTF-8')
|
19
|
+
end
|
20
|
+
|
21
|
+
def EnableStringEncodings.pack_string(s, as_type)
|
22
|
+
begin
|
23
|
+
return AE::AEDesc.new(KAE::TypeUTF8Text, EnableStringEncodings.to_utf8_string(s)).coerce(as_type)
|
24
|
+
rescue AE::MacOSError => e
|
25
|
+
if e.to_i == -1700 # couldn't coerce to TypeUnicodeText
|
26
|
+
raise TypeError, "Not valid UTF8 data or couldn't coerce to type %{as_type}: #{s.inspect}"
|
27
|
+
else
|
28
|
+
raise
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def EnableStringEncodings.unpack_string(desc)
|
34
|
+
# String instances returned by AE methods contain UTF-8 data and ASCII-8BIT encoding,
|
35
|
+
# so change the encoding to match the data
|
36
|
+
return desc.coerce(KAE::TypeUTF8Text).data.force_encoding('UTF-8')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
module DisableStringEncodings
|
42
|
+
# Support for Ruby 1.8 Strings, which do not contain encoding information. User is responsible
|
43
|
+
# for ensuring String instances passed to AE APIs contain UTF-8 encoded data; String instances
|
44
|
+
# returned by unpack_string will always contain contain UTF-8 encoded data.
|
45
|
+
|
46
|
+
def DisableStringEncodings.to_utf8_string(s)
|
47
|
+
return s
|
48
|
+
end
|
49
|
+
|
50
|
+
def DisableStringEncodings.pack_string(s, as_type)
|
51
|
+
begin
|
52
|
+
# Note: while the BOM is optional in typeUnicodeText, it's not included by AS
|
53
|
+
# and some apps, e.g. iTunes 7, will handle it incorrectly, so it's omitted here.)
|
54
|
+
return AE::AEDesc.new(KAE::TypeUTF8Text, s).coerce(as_type)
|
55
|
+
rescue AE::MacOSError => e
|
56
|
+
if e.to_i == -1700 # couldn't coerce to TypeUnicodeText
|
57
|
+
raise TypeError, "Not valid UTF8 data or couldn't coerce to type %{as_type}: #{s.inspect}"
|
58
|
+
else
|
59
|
+
raise
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def DisableStringEncodings.unpack_string(desc)
|
65
|
+
return desc.coerce(KAE::TypeUTF8Text).data
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
def AEMEncodingSupport.encoding_support
|
72
|
+
# get the appropriate module for the Ruby version used
|
73
|
+
version, sub_version = RUBY_VERSION.split('.').collect {|n| n.to_i} [0, 2]
|
74
|
+
return (version >= 1 and sub_version >= 9) ? AEMEncodingSupport::EnableStringEncodings : AEMEncodingSupport::DisableStringEncodings
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
#
|
2
|
+
# rb-appscript
|
3
|
+
#
|
4
|
+
# findapp -- locate an application by name, bundle ID or creator code
|
5
|
+
#
|
6
|
+
|
7
|
+
module FindApp
|
8
|
+
# Support module for obtaining the full path to a local application given its name, bundle id or creator type. If application isn't found, an ApplicationNotFoundError exception is raised.
|
9
|
+
|
10
|
+
require "ae"
|
11
|
+
|
12
|
+
class ApplicationNotFoundError < RuntimeError
|
13
|
+
|
14
|
+
attr_reader :creator_type, :bundle_id, :application_name
|
15
|
+
|
16
|
+
def initialize(creator, id, name)
|
17
|
+
@creator_type, @bundle_id, @application_name = creator, id, name
|
18
|
+
super()
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
#######
|
23
|
+
|
24
|
+
def FindApp._find_app(creator, id, name)
|
25
|
+
begin
|
26
|
+
return AE.find_application(creator, id, name)
|
27
|
+
rescue AE::MacOSError => err
|
28
|
+
if err.to_i == -10814
|
29
|
+
ident = [creator, id, name].compact.to_s.inspect
|
30
|
+
raise ApplicationNotFoundError.new(creator, id, name), "Application #{ident} not found."
|
31
|
+
else
|
32
|
+
raise
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
#######
|
38
|
+
|
39
|
+
def FindApp.by_name(name)
|
40
|
+
# Find the application with the given name and return its full path.
|
41
|
+
#
|
42
|
+
# Absolute paths are also accepted. An '.app' suffix is optional.
|
43
|
+
#
|
44
|
+
# Examples:
|
45
|
+
# FindApp.by_name('TextEdit')
|
46
|
+
# FindApp.by_name('Finder.app')
|
47
|
+
#
|
48
|
+
if name[0, 1] != '/' # application name only, not its full path
|
49
|
+
begin
|
50
|
+
new_name = _find_app(nil, nil, name)
|
51
|
+
rescue ApplicationNotFoundError
|
52
|
+
if ('----' + name)[-4, 4].downcase == '.app'
|
53
|
+
raise ApplicationNotFoundError.new(nil, nil, name), "Application #{name.inspect} not found."
|
54
|
+
end
|
55
|
+
new_name = _find_app(nil, nil, name + '.app')
|
56
|
+
end
|
57
|
+
name = new_name
|
58
|
+
end
|
59
|
+
if not FileTest.exist?(name) and name[-4, 4].downcase != '.app' and not FileTest.exist?(name+ '.app')
|
60
|
+
name += '.app'
|
61
|
+
end
|
62
|
+
if not FileTest.exist?(name)
|
63
|
+
raise ApplicationNotFoundError.new(nil, nil, name), "Application #{name.inspect} not found."
|
64
|
+
end
|
65
|
+
return name
|
66
|
+
end
|
67
|
+
|
68
|
+
def FindApp.by_id(id)
|
69
|
+
# Find the application with the given bundle id and return its full path.
|
70
|
+
#
|
71
|
+
# Examples:
|
72
|
+
# FindApp.by_id('com.apple.textedit')
|
73
|
+
#
|
74
|
+
return _find_app(nil, id, nil)
|
75
|
+
end
|
76
|
+
|
77
|
+
def FindApp.by_creator(creator)
|
78
|
+
# Find the application with the given creator type and return its full path.
|
79
|
+
#
|
80
|
+
# Examples:
|
81
|
+
# FindApp.by_creator('ttxt')
|
82
|
+
#
|
83
|
+
return _find_app(creator, nil, nil)
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,251 @@
|
|
1
|
+
#
|
2
|
+
# rb-appscript
|
3
|
+
#
|
4
|
+
# mactypes -- Ruby classes representing Alias, FileURL and unit type AEDescs
|
5
|
+
#
|
6
|
+
|
7
|
+
module MacTypes
|
8
|
+
# Defines wrapper classes for Mac OS datatypes that don't have a suitable Ruby equivalent.
|
9
|
+
#
|
10
|
+
# Note: all path strings are/must be valid UTF8.
|
11
|
+
|
12
|
+
require "ae"
|
13
|
+
require "kae"
|
14
|
+
|
15
|
+
KCFURLPOSIXPathStyle = 0
|
16
|
+
KCFURLHFSPathStyle = 1
|
17
|
+
KCFURLWindowsPathStyle = 2
|
18
|
+
|
19
|
+
class FileBase
|
20
|
+
|
21
|
+
def FileBase._coerce(desc, type, path=nil)
|
22
|
+
begin
|
23
|
+
return desc.coerce(type)
|
24
|
+
rescue AE::MacOSError => e
|
25
|
+
if [-35, -43, -120, -1700].include?(e.to_i) # disk/file/folder not found, or coercion error
|
26
|
+
if path != nil
|
27
|
+
raise FileNotFoundError, "File #{path.inspect} not found."
|
28
|
+
else
|
29
|
+
raise FileNotFoundError, "File not found."
|
30
|
+
end
|
31
|
+
else
|
32
|
+
raise
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def ==(val)
|
38
|
+
return (self.equal?(val) or (self.class == val.class and self.url == val.url))
|
39
|
+
end
|
40
|
+
|
41
|
+
alias_method :eql?, :==
|
42
|
+
|
43
|
+
def hash
|
44
|
+
return [desc.type, desc.data].hash
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# public
|
49
|
+
|
50
|
+
class Alias < FileBase
|
51
|
+
# Wraps AEDescs of typeAlias. Alias objects keep track of filesystem objects as they're moved around the disk or renamed.
|
52
|
+
#
|
53
|
+
# Since Ruby doesn't already bridge the Mac OS's Alias Manager, simplest solution is to always store data internally as an AEDesc of typeAlias, and convert this to other forms on demand (e.g. when casting to string).
|
54
|
+
|
55
|
+
private_class_method :new
|
56
|
+
|
57
|
+
def initialize(desc)
|
58
|
+
@desc = desc
|
59
|
+
end
|
60
|
+
|
61
|
+
# Constructors
|
62
|
+
|
63
|
+
def Alias.path(path)
|
64
|
+
# Make Alias object from POSIX path.
|
65
|
+
return new(FileBase._coerce(
|
66
|
+
AE::AEDesc.new(KAE::TypeFileURL, AE.convert_path_to_url(path, KCFURLPOSIXPathStyle)),
|
67
|
+
KAE::TypeAlias, path))
|
68
|
+
end
|
69
|
+
|
70
|
+
def Alias.hfs_path(path)
|
71
|
+
# Make Alias object from HFS path.
|
72
|
+
return new(FileBase._coerce(
|
73
|
+
AE::AEDesc.new(KAE::TypeFileURL, AE.convert_path_to_url(path, KCFURLHFSPathStyle)),
|
74
|
+
KAE::TypeAlias, path))
|
75
|
+
end
|
76
|
+
|
77
|
+
def Alias.url(url)
|
78
|
+
# Make Alias object from file URL. Note: only the path portion of the URL is used; the domain will always be localhost.
|
79
|
+
return Alias.path(AE.convert_url_to_path(url, KCFURLPOSIXPathStyle))
|
80
|
+
end
|
81
|
+
|
82
|
+
def Alias.desc(desc)
|
83
|
+
# 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.
|
84
|
+
return new(desc)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Methods
|
88
|
+
|
89
|
+
attr_reader :desc # Return AEDesc of typeAlias. If clients want a different type, they can subsequently call this AEDesc's coerce method.
|
90
|
+
|
91
|
+
def url
|
92
|
+
# Get as URL string.
|
93
|
+
return desc.coerce(KAE::TypeFileURL).data
|
94
|
+
end
|
95
|
+
|
96
|
+
def path
|
97
|
+
# Get as POSIX path.
|
98
|
+
return AE.convert_url_to_path(FileBase._coerce(@desc, KAE::TypeFileURL).data, KCFURLPOSIXPathStyle)
|
99
|
+
end
|
100
|
+
|
101
|
+
def hfs_path
|
102
|
+
# Get as HFS path.
|
103
|
+
return AE.convert_url_to_path(FileBase._coerce(@desc, KAE::TypeFileURL).data, KCFURLHFSPathStyle)
|
104
|
+
end
|
105
|
+
|
106
|
+
alias_method :to_s, :path
|
107
|
+
|
108
|
+
def inspect
|
109
|
+
return "MacTypes::Alias.path(#{to_s.inspect})"
|
110
|
+
end
|
111
|
+
|
112
|
+
def to_alias
|
113
|
+
# Get as MacTypes::Alias.
|
114
|
+
return self
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_file_url
|
118
|
+
# Get as MacTypes::FileURL; note that the resulting FileURL object will always pack as an AEDesc of typeFileURL.
|
119
|
+
return MacTypes::FileURL.desc(FileBase._coerce(@desc, KAE::TypeFileURL))
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
##
|
124
|
+
|
125
|
+
class FileURL < FileBase
|
126
|
+
# Wraps AEDescs of typeFSRef/typeFSS/typeFileURL to save user from having to deal with them directly. FileURL objects refer to specific locations on the filesystem which may or may not already exist.
|
127
|
+
|
128
|
+
private_class_method :new
|
129
|
+
|
130
|
+
def initialize(path, desc)
|
131
|
+
@path = path
|
132
|
+
@desc = desc
|
133
|
+
end
|
134
|
+
|
135
|
+
# Constructors
|
136
|
+
|
137
|
+
def FileURL.path(path)
|
138
|
+
# Make FileURL object from POSIX path.
|
139
|
+
return new(path, nil)
|
140
|
+
end
|
141
|
+
|
142
|
+
def FileURL.hfs_path(path)
|
143
|
+
# Make FileURL object from HFS path.
|
144
|
+
return new(AE.convert_url_to_path(AE.convert_path_to_url(path, KCFURLHFSPathStyle), KCFURLPOSIXPathStyle), nil)
|
145
|
+
end
|
146
|
+
|
147
|
+
def FileURL.url(url)
|
148
|
+
# Make FileURL object from file URL. Note: only the path portion of the URL is used; the domain will always be localhost.
|
149
|
+
return FileURL.path(AE.convert_url_to_path(url, KCFURLPOSIXPathStyle))
|
150
|
+
end
|
151
|
+
|
152
|
+
def FileURL.desc(desc)
|
153
|
+
# 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.
|
154
|
+
return new(nil, desc)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Methods
|
158
|
+
|
159
|
+
def desc
|
160
|
+
# Get as AEDesc. If constructed from Ruby, descriptor's type is always typeFileURL; if returned by aem, its type may be typeFSS, typeFSRef or typeFileURL.
|
161
|
+
if not @desc
|
162
|
+
@desc = AE::AEDesc.new(KAE::TypeFileURL, AE.convert_path_to_url(@path, KCFURLPOSIXPathStyle))
|
163
|
+
end
|
164
|
+
return @desc
|
165
|
+
end
|
166
|
+
|
167
|
+
def url
|
168
|
+
# Get as URL string.
|
169
|
+
return desc.coerce(KAE::TypeFileURL).data
|
170
|
+
end
|
171
|
+
|
172
|
+
def path
|
173
|
+
# Get as POSIX path.
|
174
|
+
if not @path
|
175
|
+
@path = AE.convert_url_to_path(FileBase._coerce(@desc, KAE::TypeFileURL).data, KCFURLPOSIXPathStyle)
|
176
|
+
end
|
177
|
+
return @path
|
178
|
+
end
|
179
|
+
|
180
|
+
def hfs_path
|
181
|
+
return AE.convert_url_to_path(AE.convert_path_to_url(path, KCFURLPOSIXPathStyle), KCFURLHFSPathStyle)
|
182
|
+
end
|
183
|
+
|
184
|
+
alias_method :to_s, :path
|
185
|
+
|
186
|
+
def inspect
|
187
|
+
return "MacTypes::FileURL.path(#{to_s.inspect})"
|
188
|
+
end
|
189
|
+
|
190
|
+
def to_alias
|
191
|
+
# Get as MacTypes::Alias.
|
192
|
+
return MacTypes::Alias.desc(FileBase._coerce(desc, KAE::TypeAlias, to_s))
|
193
|
+
end
|
194
|
+
|
195
|
+
def to_file_url
|
196
|
+
# Get as MacTypes::FileURL; note that the resulting FileURL object will always pack as an AEDesc of typeFileURL.
|
197
|
+
return MacTypes::FileURL.desc(FileBase._coerce(desc, KAE::TypeFileURL, to_s))
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
##
|
202
|
+
|
203
|
+
class FileNotFoundError < RuntimeError
|
204
|
+
# Raised when an operation that only works for an existing filesystem object/location is performed on an Alias/FileURL object that identifies a non-existent object/location.
|
205
|
+
end
|
206
|
+
|
207
|
+
#######
|
208
|
+
|
209
|
+
class Units
|
210
|
+
# Represents a measurement; e.g. 3 inches, 98.5 degrees Fahrenheit.
|
211
|
+
#
|
212
|
+
# The AEM defines a standard set of unit types; some applications may define additional types for their own use. This wrapper stores the raw unit type and value data; aem/appscript Codecs objects will convert this to/from an AEDesc, or raise an error if the unit type is unrecognised.
|
213
|
+
|
214
|
+
attr_reader :value, :type
|
215
|
+
|
216
|
+
def initialize(value, type)
|
217
|
+
@value = value
|
218
|
+
@type = type
|
219
|
+
end
|
220
|
+
|
221
|
+
def ==(val)
|
222
|
+
return (self.equal?(val) or (
|
223
|
+
self.class == val.class and
|
224
|
+
@value == val.value and @type == val.type))
|
225
|
+
end
|
226
|
+
|
227
|
+
alias_method :eql?, :==
|
228
|
+
|
229
|
+
def hash
|
230
|
+
return [@value, @type].hash
|
231
|
+
end
|
232
|
+
|
233
|
+
def to_i
|
234
|
+
return @value.to_i
|
235
|
+
end
|
236
|
+
|
237
|
+
def to_f
|
238
|
+
return @value.to_f
|
239
|
+
end
|
240
|
+
|
241
|
+
def to_s
|
242
|
+
return "#{@value.inspect} #{@type.tr('_', ' ')}"
|
243
|
+
end
|
244
|
+
|
245
|
+
def inspect
|
246
|
+
return "MacTypes::Units.new(#{@value.inspect}, #{@type.inspect})"
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
251
|
+
|