rubyuno 0.3.0

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.
@@ -0,0 +1,302 @@
1
+ # coding: utf-8
2
+ #
3
+ # Copyright 2011 Tsutomu Uchino
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ module RubyLoader
19
+ NAME = "RubyLoader"
20
+
21
+ LOADER_DEBUG_FLAG = "#{RubyLoader::NAME}Debug"
22
+ LOADER_DEBUG_OUTPUT = "#{LOADER_DEBUG_FLAG}Output"
23
+ LOG_DEFAULT_PATH = "$(user)/temp/#{LOADER_DEBUG_FLAG}.txt"
24
+ if ENV[LOADER_DEBUG_OUTPUT]
25
+ require 'logger'
26
+ @@log = Logger.new(
27
+ case ENV.fetch(LOADER_DEBUG_OUTPUT, "STDOUT")
28
+ when "STDOUT"
29
+ STDOUT
30
+ when "STDERR"
31
+ STDERR
32
+ when "FILE"
33
+ Uno.to_system_path(Runo.get_component_context.getServiceManager.
34
+ createInstanceWithContext("com.sun.star.util.PathSubstitution",
35
+ Runo.get_component_context).
36
+ substituteVariables(LOG_DEFAULT_PATH, true))
37
+ else
38
+ ENV.fetch(LOADER_DEBUG_OUTPUT)
39
+ end
40
+ )
41
+ @@log.level = Logger.const_get(
42
+ ENV.fetch(LOADER_DEBUG_FLAG, "ERROR").intern)
43
+ else
44
+ class DummyLogger
45
+ def null_out(progname=nil, &b)
46
+ end
47
+
48
+ def out(progname=nil, &b)
49
+ if b
50
+ puts "#{progname} -- #{b()}"
51
+ else
52
+ puts b()
53
+ end
54
+ true
55
+ end
56
+
57
+ alias :log :null_out
58
+ alias :debug :null_out
59
+ alias :warn :null_out
60
+ alias :error :out
61
+ alias :fatal :out
62
+ end
63
+ @@log = DummyLogger.new
64
+ end
65
+
66
+ def self.log
67
+ return @@log
68
+ end
69
+ end
70
+
71
+ module Uno
72
+ RRuno.uno_require 'com.sun.star.lang.XServiceInfo'
73
+
74
+ # All UNO components should be include UnoComponentBase module
75
+ # to its class and define two constants for it. If your class
76
+ # include css.lang.XServiceInfo module also, they are used to provide
77
+ # service information of your component.
78
+ # IMPLE_NAME: implementation name of the component, have to be defined
79
+ # on herself.
80
+ # SERVICE_NAMES: Array of supported service names.
81
+ module UnoComponentBase
82
+ # IMPLE_NAME = ""
83
+ # SERVICE_NAMES = []
84
+ end
85
+
86
+ # Provides XServiceInfo interface from two constants.
87
+ # IMPLE_NAME is its implementation name in String.
88
+ # SERVICE_NAMES is supported service names of the component
89
+ # in Array of String.
90
+ module Com::Sun::Star::Lang::XServiceInfo
91
+ def getImplementationName
92
+ return self.class::IMPLE_NAME
93
+ end
94
+
95
+ def supportsService(name)
96
+ return self.class::SERVICE_NAMES.include? name
97
+ end
98
+
99
+ def getSupportedServiceNames
100
+ return self.class::SERVICE_NAMES
101
+ end
102
+ end
103
+ end
104
+
105
+ # Loads implementations which are written in Ruby.
106
+ module RubyLoader
107
+
108
+ Runo.uno_require 'com.sun.star.uno.Exception'
109
+ Runo.uno_require 'com.sun.star.lang.XSingleComponentFactory'
110
+ Runo.uno_require 'com.sun.star.loader.XImplementationLoader'
111
+
112
+ def self.error_to_str(e)
113
+ return %Q!#{e}\n#{e.backtrace.join("\n")}\n!
114
+ end
115
+
116
+ # Component factory class wraps your class which implements
117
+ # UNO component.
118
+ class SingleComponentFactory
119
+ include Uno::UnoBase
120
+ include Runo::Com::Sun::Star::Lang::XServiceInfo
121
+ include Runo::Com::Sun::Star::Lang::XSingleComponentFactory
122
+
123
+ def initialize(klass, imple_name, url)
124
+ @klass = klass
125
+ @imple_name = imple_name
126
+ @url = url
127
+ end
128
+
129
+ # XSingleComponentFactory
130
+ def createInstanceWithContext(ctx)
131
+ begin
132
+ load_class unless @klass
133
+ return @klass.new(ctx)
134
+ rescue => e
135
+ RubyLoader.log.error(@klass.to_s) {
136
+ "Error raised at #{self.class.name}##{__method__}\n" +
137
+ RubyLoader.error_to_str(e)}
138
+ raise
139
+ end
140
+ end
141
+
142
+ # Generaly, this method instantiate the class and
143
+ # calls css.lang.XInitialization#initialize method.
144
+ # But Ruby's initialize method is special one.
145
+ # Therefore additional arguments are passed as second one.
146
+ def createInstanceWithArgumentsAndContext(args, ctx)
147
+ begin
148
+ load_class unless @klass
149
+ return @klass.new(ctx, args)
150
+ rescue => e
151
+ RubyLoader.log.error(@klass.to_s) {
152
+ "Error raised at #{self.class.name}##{__method__}\n" +
153
+ RubyLoader.error_to_str(e)}
154
+ raise
155
+ end
156
+ end
157
+
158
+ # XServiceInfo
159
+
160
+ def getImplementationName
161
+ return @klass::IMPLE_NAME if @klass
162
+ return @imple_name
163
+ end
164
+
165
+ def supportsService(name)
166
+ return @klass::SERVICE_NAMES.include?(name)
167
+ end
168
+
169
+ def getSupportedServiceNames
170
+ return @klass::SERVICE_NAMES if @klass
171
+ return []
172
+ end
173
+
174
+ private
175
+
176
+ # load_class -> Class
177
+ # Load class from the file which implements components.
178
+ def load_class
179
+ begin
180
+ @klass = REGISTERED.get_imple(@klass, @url, @imple_name)
181
+ rescue => e
182
+ # missing file, syntax error, load error and so on
183
+ message = "#{@imple_name} was not found in #{@url}. " +
184
+ RubyLoader.error_to_str(e)
185
+ puts message
186
+ RubyLoader.log.error(@klass.to_s) {message}
187
+ ex = Runo::Com::Sun::Star::Uno::Exception.new(message, nil)
188
+ raise Uno::UnoError, ex
189
+ end
190
+ unless @klass
191
+ ex = Runo::Com::Sun::Star::Uno::Exception.new(
192
+ "#{@imple_name} was not found in #{@url}", nil)
193
+ raise Uno::UnoError, ex
194
+ end
195
+ end
196
+ end
197
+
198
+ # keeps loaded modules of UNO components
199
+ REGISTERED = {}
200
+ class << REGISTERED
201
+
202
+ EXPAND_PROTOCOL = "vnd.sun.star.expand:"
203
+
204
+ # get_imple(ctx, url, imple_name) -> Class
205
+ # Load file and get implementation as an class.
206
+ def get_imple(ctx, url, imple_name)
207
+ klass = find_imple(imple_name, url)
208
+ unless klass
209
+ mod = load_as_module(ctx, url)
210
+ register_module(url, mod)
211
+ klass = find_imple(url, imple_name)
212
+ end
213
+ return klass
214
+ end
215
+
216
+ private
217
+
218
+ # load_as_module(ctx, url) -> Module
219
+ # Loads file content into a module with eval.
220
+ def load_as_module(ctx, url)
221
+ path = get_path(ctx, url)
222
+ mod = Module.new
223
+ mod.module_eval(IO.read(path), path)
224
+ return mod
225
+ end
226
+
227
+ # register_module(url, mod) -> obj
228
+ # Register new module at url.
229
+ def register_module(url, mod)
230
+ REGISTERED[url] = mod
231
+ end
232
+
233
+ # find_imple(url, imple_name) -> Class|nil
234
+ # Find implementation from the file.
235
+ def find_imple(url, imple_name)
236
+ klass = nil
237
+ mod = REGISTERED[url]
238
+ if mod
239
+ get_component_classes(mod).each do |imple|
240
+ if imple.const_defined?(:IMPLE_NAME) &&
241
+ imple.const_defined?(:SERVICE_NAMES) &&
242
+ imple.const_get(:IMPLE_NAME) == imple_name
243
+ klass = imple
244
+ break
245
+ end
246
+ end
247
+ end
248
+ return klass
249
+ end
250
+
251
+ # get_component_classes(mod) -> [Class]
252
+ # Find all classes includes Uno::ComponentBase module from module.
253
+ def get_component_classes(mod)
254
+ klasses = []
255
+ mod.constants.each do |item|
256
+ c = mod.const_get(item)
257
+ if c.instance_of?(Module)
258
+ klasses.concat(get_component_classes(c))
259
+ elsif c.instance_of?(Class) &&
260
+ c.ancestors.include?(Uno::UnoComponentBase)
261
+ klasses << c
262
+ end
263
+ end
264
+ return klasses
265
+ end
266
+
267
+ # get_path(ctx, url) -> String
268
+ # Make expanded url.
269
+ def get_path(ctx, url)
270
+ if url.start_with?(EXPAND_PROTOCOL)
271
+ url = ctx.getValueByName(
272
+ "/singletons/com.sun.star.util.theMacroExpander").expandMacros(url)
273
+ url[EXPAND_PROTOCOL] = ""
274
+ end
275
+ return Uno.to_system_path(url)
276
+ end
277
+ end
278
+
279
+ # This class is refered from C++ loader implementation.
280
+ class RubyLoaderImpl
281
+ include Uno::UnoBase
282
+ include Runo::Com::Sun::Star::Loader::XImplementationLoader
283
+ include Runo::Com::Sun::Star::Lang::XServiceInfo
284
+
285
+ IMPLE_NAME = "mytools.loader.Ruby"
286
+ SERVICE_NAMES = ["com.sun.star.loader.Ruby"]
287
+
288
+ def initialize(ctx)
289
+ @ctx = ctx
290
+ end
291
+
292
+ # XImplementationLoader
293
+ def activate(imple_name, ignored, url, key)
294
+ return SingleComponentFactory.new(nil, imple_name, url)
295
+ end
296
+
297
+ # this method is no longer required
298
+ def writeRegistryInfo(key, ignored, url)
299
+ return true
300
+ end
301
+ end
302
+ end
@@ -0,0 +1,1025 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Copyright 2011 Tsutomu Uchino
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ #
19
+ # When you want to execute a macro written in Ruby,
20
+ # it should be placed under user_profile/Scripts/ruby directory.
21
+ module RubyScriptProvider
22
+ LANGUAGE = "Ruby"
23
+ NAME = "#{LANGUAGE}ScriptProvider"
24
+ RB_EXTENSION = ".rb"
25
+ end
26
+
27
+ module LoggerForRubyScriptProvider
28
+
29
+ PROVIDER_DEBUG_FLAG = "#{RubyScriptProvider::NAME}Debug"
30
+ PROVIDER_DEBUG_OUTPUT = "#{PROVIDER_DEBUG_FLAG}Output"
31
+ LOG_DEFAULT_PATH = "$(user)/temp/#{PROVIDER_DEBUG_OUTPUT}.txt"
32
+
33
+ require 'logger'
34
+ @@log = Logger.new(
35
+ case ENV.fetch(LOADER_DEBUG_OUTPUT, "STDOUT")
36
+ when "STDOUT"
37
+ STDOUT
38
+ when "FILE"
39
+ Uno.to_system_path(
40
+ Runo.get_component_context.getServiceManager.
41
+ createInstanceWithContext(
42
+ "com.sun.star.util.PathSubstitution",
43
+ Runo.get_component_context).
44
+ substituteVariables(LOG_DEFAULT_PATH, true))
45
+ else
46
+ ENV.fetch(LOADER_DEBUG_OUTPUT)
47
+ end
48
+ )
49
+ @@log.level = Logger.const_get(
50
+ ENV.fetch(LOADER_DEBUG_FLAG, "ERROR").intern)
51
+ end
52
+
53
+
54
+ module RubyScriptProvider
55
+ # keeps loaded scripts
56
+ module RubyScripts
57
+ @@scripts = {}
58
+
59
+ # get(name, datetime) -> RubyScript|nil
60
+ # Try to get script. If the script is updated,
61
+ # nil is returned. Otherwise found script is returned.
62
+ def self.get(name, datetime)
63
+ item = @@scripts[name]
64
+ if item
65
+ if item[:timestamp] >= datetime
66
+ return item[:module]
67
+ else
68
+ @@scripts.delete(name)
69
+ return nil
70
+ end
71
+ end
72
+ end
73
+
74
+ # add(name, datetime, capsule) -> Hash
75
+ # Register new script with update time.
76
+ def self.add(name, datetime, capsule)
77
+ @@scripts[name] = {:timestamp => datetime, :module => capsule}
78
+ end
79
+
80
+ # delete(name) -> Hash
81
+ # Delete specific script.
82
+ def self.delete(name)
83
+ @@scripts.delete(name)
84
+ end
85
+
86
+ # get_script(storage, script_context, url, uri) -> RubyScript
87
+ # Creates capsulated script.
88
+ def self.get_script(storage, script_context, url, uri)
89
+ script = nil
90
+ capsule = nil
91
+ file_name, func_name = url.to_url(uri)
92
+ if storage.exists?(file_name)
93
+ if file_name.start_with?(DOC_PROTOCOL)
94
+ t = Time.new # avoide illegal error
95
+ else
96
+ t = RubyScripts.datetime_to_time(storage.modified_time(file_name))
97
+ end
98
+ capsule = RubyScripts.get(uri, t)
99
+ unless capsule
100
+ klass = Class.new
101
+ klass.const_set(:XSCRIPTCONTEXT, script_context)
102
+ text = storage.file_content(file_name)
103
+ klass.class_eval(text, Uno.to_system_path(file_name))
104
+ capsule = klass.new
105
+ RubyScripts.add(uri, t, capsule)
106
+ end
107
+ end
108
+ unless capsule
109
+ ex = ScriptFrameworkErrorException.new(
110
+ "script not found", nil, uri, LANGUAGE, 2)
111
+ raise ex
112
+ end
113
+ if capsule.public_methods(false).include?(func_name.intern)
114
+ script = RubyScript.new(capsule, file_name, func_name)
115
+ end
116
+ return script
117
+ end
118
+
119
+ private
120
+
121
+ # datetime_to_time(dt) -> Time
122
+ # Convert css.util.DateTime to Time.
123
+ def self.datetime_to_time(dt)
124
+ return Time.local(dt.Year, dt.Month, dt.Day,
125
+ dt.Hours, dt.Minutes, dt.Seconds)
126
+ end
127
+ end
128
+
129
+ Runo.uno_require 'com.sun.star.script.provider.XScriptContext'
130
+
131
+ # class for XSCRIPTCONTEXT constant
132
+ class ScriptContext
133
+ include Uno::UnoBase
134
+ include Runo::Com::Sun::Star::Script::Provider::XScriptContext
135
+
136
+ def initialize(component_context, document, inv)
137
+ @component_context = component_context
138
+ @document = document
139
+ @inv = inv
140
+ end
141
+
142
+ # XScriptContext
143
+ def getComponentContext
144
+ return @component_context
145
+ end
146
+
147
+ def getDocument
148
+ return @document if @document
149
+ return getDesktop.getCurrentComponent
150
+ end
151
+
152
+ def getDesktop
153
+ return @component_context.getServiceManager.createInstanceWithContext(
154
+ "com.sun.star.frame.Desktop", @component_context)
155
+ end
156
+
157
+ def getInvocationContext
158
+ return @inv
159
+ end
160
+ end
161
+ end
162
+
163
+
164
+ module RubyScriptProvider
165
+
166
+ DOC_PROTOCOL = "vnd.sun.star.tdoc:"
167
+
168
+ # Macro URL.
169
+ class URL
170
+ EXPAND_PROTOCOL = "vnd.sun.star.expand:"
171
+
172
+ def initialize(ctx, context, url)
173
+ @ctx = ctx
174
+ @context = context
175
+ @url = url
176
+ @url = helper.getRootStorageURI unless url
177
+ end
178
+
179
+ attr_reader :url, :context
180
+ alias :to_s :url
181
+
182
+ def create(url)
183
+ return RubyScriptProvider::URL.new(@ctx, @context, url)
184
+ end
185
+
186
+ def helper
187
+ return create_service("com.sun.star.script.provider.ScriptURIHelper",
188
+ [LANGUAGE, @context])
189
+ end
190
+
191
+ def absolutize
192
+ return Uno.absolutize(@url, @url) rescue nil
193
+ return @url
194
+ end
195
+
196
+ def transient?
197
+ return @url.start_with?(DOC_PROTOCOL)
198
+ end
199
+
200
+ def basename(ext="")
201
+ return File.basename(@url, ext)
202
+ end
203
+
204
+ def dirname
205
+ return File.dirname(@url)
206
+ end
207
+
208
+ def to_uri(function_name=nil)
209
+ url = @url
210
+ url += "$" + function_name if function_name
211
+ return helper.getScriptURI(url)
212
+ end
213
+
214
+ def join(a, b=nil)
215
+ if b
216
+ return a + b if a.end_with?("/")
217
+ return "#{a}/#{b}"
218
+ else
219
+ return @url + a if @url.end_with?("/")
220
+ return "#{@url}/#{a}"
221
+ end
222
+ end
223
+
224
+ def expand(uri)
225
+ url = @ctx.getValueByName(
226
+ "/singletons/com.sun.star.util.theMacroExpander").expandMacros(uri)
227
+ url[EXPAND_PROTOCOL] = ""
228
+ url = Uno.absolutize(url, url) rescue nil
229
+ url += "/" if uri.end_with?("/")
230
+ return url
231
+ end
232
+
233
+ def to_url(uri)
234
+ url = helper.getStorageURI(uri)
235
+ n = url.rindex("$")
236
+ return url[0..n-1], url[n+1..-1]
237
+ end
238
+
239
+ private
240
+
241
+ # create_service(name, args=nil) -> obj
242
+ # Create instance of service.
243
+ def create_service(name, args=nil)
244
+ return @ctx.getServiceManager.createInstanceWithArgumentsAndContext(
245
+ name, args, @ctx) if args
246
+ return @ctx.getServiceManager.createInstanceWithContext(name, @ctx)
247
+ end
248
+ end
249
+
250
+ # Wrapper for file access.
251
+ class Storage
252
+ YAML_EXTENSION = '.yml'
253
+
254
+ def initialize(ctx)
255
+ @ctx = ctx
256
+ @sfa = create_service("com.sun.star.ucb.SimpleFileAccess")
257
+ end
258
+
259
+ def modified_time(url)
260
+ return @sfa.getDateTimeModified(url.to_s)
261
+ end
262
+
263
+ def exists?(url)
264
+ return @sfa.exists(url.to_s)
265
+ end
266
+
267
+ def is_dir?(url)
268
+ return @sfa.isFolder(url.to_s)
269
+ end
270
+
271
+ def is_readonly?(url)
272
+ return @sfa.isReadOnly(url.to_s)
273
+ end
274
+
275
+ def rename(url, dest_url)
276
+ @sfa.move(url, dest_url)
277
+ end
278
+
279
+ def delete(url)
280
+ @sfa.kill(url.to_s)
281
+ return true
282
+ end
283
+
284
+ def dir_contents(url)
285
+ return @sfa.getFolderContents(url.to_s, true) if exists?(url)
286
+ return []
287
+ end
288
+
289
+ def dir_create(url)
290
+ @sfa.createFolder(url.to_s) unless exists?(url)
291
+ end
292
+
293
+ def file_create(url, name)
294
+ dir_create(url)
295
+ write_text(url.join(name))
296
+ end
297
+
298
+ def file_copy(source, dest, yaml=false)
299
+ source = source.to_s
300
+ dest = dest.to_s
301
+ if @sfa.exists(source)
302
+ source_input = @sfa.openFileRead(source)
303
+ @sfa.kill(dest) if @sfa.exists(dest)
304
+ @sfa.writeFile(dest, source_input)
305
+ source_input.closeInput
306
+ if yaml
307
+ yaml_source = source[0..-File.extname(source).length-1] +
308
+ YAML_EXTENSION
309
+ if @sfa.exists(yaml_source)
310
+ yaml_dest = dest[0..-File.extname(dest).length-1] +
311
+ YAML_EXTENSION
312
+ file_copy(yaml_source, yaml_dest)
313
+ end
314
+ end
315
+ end
316
+ end
317
+
318
+ def write_text(url, text="")
319
+ file_url = url.to_s
320
+ text_output = create_service("com.sun.star.io.TextOutputStream")
321
+ if url.start_with?(DOC_PROTOCOL)
322
+ pipe = create_service("com.sun.star.io.Pipe")
323
+ text_output.setOutputStream(pipe)
324
+ text_output.writeString(text)
325
+ text_output.closeOutput
326
+ @sfa.writeFile(file_url, pipe)
327
+ pipe.closeInput
328
+ else
329
+ file_output = @sfa.openFileWrite(file_url)
330
+ text_output.setOutputStream(file_output)
331
+ text_output.writeString(text)
332
+ text_output.flush()
333
+ file_output.closeOutput
334
+ end
335
+ end
336
+
337
+ def choose_file
338
+ scripts_dir = create_service("com.sun.star.util.PathSubstitution")
339
+ .substituteVariables("$(user)/Scripts/#{LANGUAGE.downcase}", true)
340
+ picker = create_service("com.sun.star.ui.dialogs.FilePicker")
341
+ picker.appendFilter("All Files (*.*)", "*.*")
342
+ picker.appendFilter(
343
+ "#{LANGUAGE} Files (*#{RB_EXTENSION})", "*#{RB_EXTENSION}")
344
+ picker.setCurrentFilter("All Files (*.*)")
345
+ picker.setDisplayDirectory(scripts_dir)
346
+ picker.setMultiSelectionMode(false)
347
+ picker.getFiles[0] if picker.execute > 0
348
+ end
349
+
350
+ def file_content(url)
351
+ lines = []
352
+ text_input = create_service("com.sun.star.io.TextInputStream")
353
+ text_input.setInputStream(@sfa.openFileRead(url.to_s))
354
+ lines << text_input.readLine until text_input.isEOF
355
+ text_input.closeInput
356
+ return lines.join("\n")
357
+ end
358
+
359
+ def descriptions(url)
360
+ require 'yaml'
361
+ ret = nil
362
+ yaml_url = url.join(url.dirname,
363
+ url.basename(RB_EXTENSION) + YAML_EXTENSION)
364
+ if exists?(yaml_url)
365
+ text = file_content(yaml_url)
366
+ ret = YAML.load(text) rescue nil
367
+ end
368
+ ret = {} unless ret
369
+ return ret
370
+ end
371
+
372
+ # try to find public methods from sexp tree
373
+ def get_method_names(url)
374
+ require 'ripper'
375
+ text = file_content(url)
376
+ result = Ripper.sexp(text, url.to_s)
377
+ method_defs = []
378
+ if result.length == 2 && result[0] == :program
379
+ is_public = true
380
+ result[1].each do |item|
381
+ case item[0]
382
+ when :def
383
+ ident = find_item(item, :@ident)
384
+ if ident
385
+ if is_public
386
+ method_defs << ident
387
+ else
388
+ method_defs.delete(ident)
389
+ end
390
+ end
391
+ when :command
392
+ ident = find_item(item, :@ident)
393
+ case ident
394
+ when "private", "protected", "public"
395
+ block = find_item(item, :args_add_block)
396
+ items = get_symbol_literals(block)
397
+ if ident == "public"
398
+ method_defs.concat(items)
399
+ else
400
+ items.each do |i|
401
+ method_defs.delete(i)
402
+ end
403
+ end
404
+ end
405
+ when :var_ref
406
+ ident = find_item(item, :@ident)
407
+ is_public = ident == "public" if ident
408
+ end
409
+ end
410
+ method_defs.uniq!
411
+ end
412
+ return method_defs
413
+ end
414
+
415
+ private
416
+
417
+ def find_item(args, type)
418
+ found = args[1..-1].assoc(type)
419
+ return found[1] if found
420
+ end
421
+
422
+ def get_symbol_literals(args)
423
+ founds = []
424
+ args.each do |i|
425
+ if i[0] == :symbol_literal
426
+ ident = find_item(i[1], :@ident)
427
+ founds << ident if ident
428
+ end
429
+ end
430
+ return founds
431
+ end
432
+
433
+ def create_service(name)
434
+ return @ctx.getServiceManager.createInstanceWithContext(name, @ctx)
435
+ end
436
+ end
437
+
438
+ Runo.uno_require 'com.sun.star.task.XInteractionHandler'
439
+ Runo.uno_require 'com.sun.star.ucb.XProgressHandler'
440
+ Runo.uno_require 'com.sun.star.ucb.XCommandEnvironment'
441
+
442
+ class DummyInteractionHandler
443
+ include Uno::UnoBase
444
+ include Runo::Com::Sun::Star::Task::XInteractionHandler
445
+
446
+ def handle(request)
447
+ end
448
+ end
449
+
450
+ class DummyProgressHandler
451
+ include Uno::UnoBase
452
+ include Runo::Com::Sun::Star::Ucb::XProgressHandler
453
+
454
+ def push(status)
455
+ end
456
+
457
+ def update(status)
458
+ end
459
+
460
+ def pop
461
+ end
462
+ end
463
+
464
+ class CommandEnvironment
465
+ include Uno::UnoBase
466
+ include Runo::Com::Sun::Star::Ucb::XCommandEnvironment
467
+
468
+ def initialize
469
+ @interaction_handler = DummyInteractionHandler.new
470
+ @progress_handler = DummyProgressHandler.new
471
+ end
472
+
473
+ def getInteractionHandler
474
+ return @interaction_handler
475
+ end
476
+
477
+ def getProgressHandler
478
+ return @progress_handler
479
+ end
480
+ end
481
+
482
+ Property = Runo.uno_require 'com.sun.star.beans.Property'
483
+ Command = Runo.uno_require 'com.sun.star.ucb.Command'
484
+
485
+ # get_document_model(ctx, uri) -> obj
486
+ # Get document model from internal uri.
487
+ def self.get_document_model(ctx, uri)
488
+ doc = nil
489
+ smgr = ctx.getServiceManager
490
+ ucb = smgr.createInstanceWithArgumentsAndContext(
491
+ "com.sun.star.ucb.UniversalContentBroker", ["Local", "Office"], ctx)
492
+ identifier = ucb.createContentIdentifier(uri)
493
+ content = ucb.queryContent(identifier)
494
+ property = Property.new("DocumentModel", -1, Uno.get_type("void"), 1)
495
+ command = Command.new(
496
+ "getPropertyValues", -1,
497
+ Runo::Any.new("[]com.sun.star.beans.Property", [property]))
498
+ env = CommandEnvironment.new
499
+ begin
500
+ result_set = content.execute(command, 0, env)
501
+ doc = result_set.getObject(1, nil)
502
+ rescue => e
503
+ p e.to_s + "\n" + e.backtrace.join("\n") + "\n"
504
+ end
505
+ return doc
506
+ end
507
+
508
+ # get_context(ctx, doc) -> obj
509
+ # Get document context.
510
+ def self.get_context(ctx, doc)
511
+ return ctx.getServiceManager.createInstanceWithContext(
512
+ "com.sun.star.frame.TransientDocumentsDocumentContentFactory", ctx).
513
+ createDocumentContent(doc).getIdentifier.getContentIdentifier
514
+ end
515
+ end
516
+
517
+ module RubyScriptProvider
518
+
519
+ Runo.uno_require 'com.sun.star.beans.XPropertySet'
520
+ Runo.uno_require 'com.sun.star.container.XNameContainer'
521
+ Runo.uno_require 'com.sun.star.lang.XServiceInfo'
522
+ Runo.uno_require 'com.sun.star.script.browse.XBrowseNode'
523
+ Runo.uno_require 'com.sun.star.script.provider.XScriptProvider'
524
+ Runo.uno_require 'com.sun.star.script.provider.XScript'
525
+ Runo.uno_require 'com.sun.star.script.XInvocation'
526
+ ScriptFrameworkErrorException = Runo.uno_require(
527
+ "com.sun.star.script.provider.ScriptFrameworkErrorException")
528
+ RuntimeException = Runo.uno_require(
529
+ "com.sun.star.uno.RuntimeException")
530
+ BrowseNodeTypes = Runo.uno_require(
531
+ "com.sun.star.script.browse.BrowseNodeTypes")
532
+
533
+ # base node for all script nodes
534
+ class BaseNode
535
+ include Uno::UnoBase
536
+ include Runo::Com::Sun::Star::Script::Browse::XBrowseNode
537
+ include Runo::Com::Sun::Star::Script::XInvocation
538
+ include Runo::Com::Sun::Star::Beans::XPropertySet
539
+
540
+ NODE_TYPE = BrowseNodeTypes::CONTAINER
541
+
542
+ def initialize(url, storage, name)
543
+ @url = url
544
+ @storage = storage
545
+ @name = name
546
+ context = @url.context
547
+ @editable = (context == "user" || context.start_with?(DOC_PROTOCOL))
548
+ end
549
+
550
+ attr_reader :name, :editable
551
+
552
+ def update(name, url)
553
+ @name = name
554
+ @url = url
555
+ end
556
+
557
+ # XBrowseNode
558
+ alias :getName :name
559
+
560
+ def getChildNodes
561
+ return []
562
+ end
563
+
564
+ def hasChildNodes
565
+ return true
566
+ end
567
+
568
+ def getType
569
+ return self.class::NODE_TYPE
570
+ end
571
+
572
+ # XInvocation
573
+ def getIntrospection
574
+ return nil
575
+ end
576
+
577
+ def setValue(name, value)
578
+ end
579
+
580
+ def getValue(name)
581
+ return nil
582
+ end
583
+
584
+ def hasMethod(name)
585
+ return false
586
+ end
587
+
588
+ def hasProperty(name)
589
+ return false
590
+ end
591
+
592
+ def invoke(name, arg1, arg2, arg3)
593
+ return [false, [0], [nil]]
594
+ end
595
+
596
+ # XPrpertySet
597
+ def getPropertySetInfo
598
+ return nil
599
+ end
600
+
601
+ def addPropertyChangeListener(name, listener)
602
+ end
603
+
604
+ def removePropertyChangeListener(name, listener)
605
+ end
606
+
607
+ def addVetoableChangeListener(name, listener)
608
+ end
609
+
610
+ def removeVetoableChangeListener(name, listener)
611
+ end
612
+
613
+ def setPropertyValue(name, value)
614
+ end
615
+
616
+ def getPropertyValue(name)
617
+ return nil
618
+ end
619
+
620
+ def rename(name)
621
+ url = @url.join(@url.dirname, name)
622
+ @storage.rename(@url, url)
623
+ return url
624
+ end
625
+ end
626
+
627
+ #
628
+ class ScriptBrowseNode < BaseNode
629
+ NODE_TYPE = BrowseNodeTypes::SCRIPT
630
+
631
+ def initialize(url, storage, name, func_name, description)
632
+ super(url, storage, name)
633
+ @func_name = func_name.to_s
634
+ @description = description.to_s
635
+ end
636
+
637
+ def getPropertyValue(name)
638
+ case name
639
+ when "URI"
640
+ return @url.to_uri(@func_name)
641
+ when "Description"
642
+ return @description
643
+ when "Editable"
644
+ return false
645
+ end
646
+ end
647
+ end
648
+
649
+ # describes file
650
+ class FileBrowseNode < BaseNode
651
+
652
+ def initialize(url, storage, name)
653
+ super(url, storage, name)
654
+ end
655
+
656
+ def parse_entry(entry)
657
+ begin
658
+ return entry["name"], entry["description"]
659
+ rescue
660
+ return "", ""
661
+ end
662
+ end
663
+
664
+ def getChildNodes
665
+ result = []
666
+ if @storage.exists?(@url)
667
+ begin
668
+ file_name = @url.basename
669
+ descriptions = @storage.descriptions(@url)
670
+ @storage.get_method_names(@url).each do |name|
671
+ display_name, description = parse_entry(descriptions[name])
672
+ display_name = name if display_name.empty?
673
+ result << ScriptBrowseNode.new(
674
+ @url, @storage, display_name, name, description)
675
+ end
676
+ rescue => e
677
+ p e.to_s + "\n" + e.backtrace.join("\n") + "\n"
678
+ end
679
+ end
680
+ return result
681
+ end
682
+
683
+ def getPropertyValue(name)
684
+ ret = false
685
+ case name
686
+ when "Renamable", "Deletable"
687
+ ret = ! @storage.is_readonly?(@url)
688
+ when "Editable"
689
+ ret = @url.transient? && ! @storage.is_readonly?(@url)
690
+ end
691
+ return ret
692
+ end
693
+
694
+ def invoke(name, args, arg2, arg3)
695
+ value = false
696
+ begin
697
+ case name
698
+ when "Editable"
699
+ # push yaml file too if there
700
+ source = @storage.choose_file
701
+ @storage.file_copy(source, @url, true) if source
702
+ when "Renamable"
703
+ name = args[0]
704
+ name += RB_EXTENSION unless name.end_with?(RB_EXTENSION)
705
+ dest = rename(name)
706
+ update(name, dest)
707
+ value = self
708
+ when "Deletable"
709
+ value = @storage.delete(@url)
710
+ end
711
+ rescue => e
712
+ p %Q!#{e}\n#{e.backtrace.join("\n")}\n!
713
+ end
714
+ return [value, [0], [nil]]
715
+ end
716
+ end
717
+
718
+ # describes directory
719
+ class DirBrowseNode < BaseNode
720
+
721
+ def initialize(url, storage, name)
722
+ super(url, storage, name)
723
+ end
724
+
725
+ def getChildNodes
726
+ ret = []
727
+ begin
728
+ if @storage.exists?(@url)
729
+ @storage.dir_contents(@url).sort!.each do |name|
730
+ url = @url.create(name)
731
+ if @storage.is_dir?(url)
732
+ ret << DirBrowseNode.new(url, @storage, url.basename)
733
+ elsif File::extname(name) == RB_EXTENSION
734
+ ret << FileBrowseNode.new(url, @storage, url.basename(RB_EXTENSION))
735
+ end
736
+ end
737
+ end
738
+ rescue => e
739
+ p e
740
+ end
741
+ return ret
742
+ end
743
+
744
+ def getPropertyValue(name)
745
+ ret = false
746
+ case name
747
+ when "Renamable", "Deletable", "Creatable"
748
+ ret = ! @storage.is_readonly?(@url)
749
+ end
750
+ return ret
751
+ end
752
+
753
+ def invoke(name, args, arg2, arg3)
754
+ value = false
755
+ begin
756
+ case name
757
+ when "Creatable"
758
+ name = args[0]
759
+ if name.end_with?("/")
760
+ name = name[0..-2]
761
+ url = @url.create(@url.join(name))
762
+ @storage.dir_create(url)
763
+ node = DirBrowseNode.new(url, @storage, name)
764
+ else
765
+ name += RB_EXTENSION unless name.end_with?(RB_EXTENSION)
766
+ url = @url.create(@url.join(name))
767
+ @storage.file_create(@url, name)
768
+ node = FileBrowseNode.new(url, @storage, url.basename(RB_EXTENSION))
769
+ end
770
+ value = node
771
+ when "Renamable"
772
+ name = args[0]
773
+ dest = rename(name)
774
+ update(name, dest)
775
+ value = self
776
+ when "Deletable"
777
+ value = @storage.delete(@url)
778
+ end
779
+ rescue => e
780
+ p e, e.backtrace.join("\n")
781
+ end
782
+ return [value, [0], [nil]]
783
+ end
784
+ end
785
+
786
+ module PackageManager
787
+ PACKAGE_REGISTERED = "extensions-ruby.txt"
788
+
789
+ def package_manager_init(ctx, context)
790
+ @context = context
791
+ @file_path = ctx.getServiceManager.createInstanceWithContext(
792
+ "com.sun.star.util.PathSubstitution", ctx).substituteVariables(
793
+ "$(user)/Scripts/#{PACKAGE_REGISTERED}", true)
794
+ @packages = {}
795
+ load_registerd
796
+ end
797
+
798
+ def packages
799
+ packages = {}
800
+ @packages.each do |name, state|
801
+ if state
802
+ if name.find("USER")
803
+ storage = "user"
804
+ else
805
+ storage = @context
806
+ end
807
+ if storage == @context
808
+ url = @url.expand(name)
809
+ packages[url] = File::basename(url)
810
+ end
811
+ end
812
+ end
813
+ return packages
814
+ end
815
+
816
+ # XNameContainer
817
+ def hasByName(name)
818
+ return @packages.has_key?(name)
819
+ end
820
+
821
+ def insertByName(name, value)
822
+ @packages[name] = value.getName
823
+ store_registered
824
+ end
825
+
826
+ def removeByName(name)
827
+ @packages.delete(name)
828
+ store_registered
829
+ end
830
+
831
+ def replaceByName(name, value)
832
+ @packages[name] = value.getName
833
+ end
834
+
835
+ def getByName(name)
836
+ return nil
837
+ end
838
+
839
+ def getElementNames
840
+ return []
841
+ end
842
+
843
+ def getElementType
844
+ return Uno.get_type("void")
845
+ end
846
+
847
+ def hasElements
848
+ return false
849
+ end
850
+
851
+ private
852
+
853
+ def has_scripts?(url)
854
+ @storage.dir_contents(url).each do |name|
855
+ if @storage.is_dir?(name)
856
+ return true if has_scripts?(name)
857
+ elsif name.end_with(RB_EXTENSION)
858
+ return true
859
+ end
860
+ end
861
+ return false
862
+ end
863
+
864
+ def load_registerd
865
+ if File.exist?(@file_path)
866
+ @packages.clear
867
+ lines = IO.readlines(@file_path)
868
+ lines.each do |name|
869
+ name.strip!
870
+ unless name.empty?
871
+ url = @url.expand(name)
872
+ @packages[name] = has_scripts?(url)
873
+ end
874
+ end
875
+ end
876
+ end
877
+
878
+ def store_registered
879
+ if File.writable?(File::dirname(@file_path))
880
+ open(@file_path, "w") do |f|
881
+ f.write(@packages.keys.join("\n"))
882
+ end
883
+ end
884
+ end
885
+ end
886
+
887
+ # package node is read-only
888
+ class PackageBrowseNode < DirBrowseNode
889
+ def initialize(url, storage, name, manager)
890
+ super(url, storage, name)
891
+ @manager = manager
892
+ end
893
+
894
+ def getChildNodes
895
+ ret = []
896
+ @manager.packages.each do |url, name|
897
+ ret << DirBrowseNode.new(@url.create(url), @storage, name)
898
+ end
899
+ return ret
900
+ end
901
+
902
+ def getPropertyValue(name)
903
+ return false
904
+ end
905
+
906
+ def invoke(name, args, arg2, arg3)
907
+ return [false, [0], [nil]]
908
+ end
909
+ end
910
+
911
+
912
+ # Executes script.
913
+ class RubyScript
914
+ include Uno::UnoBase
915
+ include Runo::Com::Sun::Star::Script::Provider::XScript
916
+
917
+ def initialize(capsule, file_name, func_name)
918
+ @capsule = capsule
919
+ @file_name = file_name
920
+ @func_name = func_name
921
+ end
922
+
923
+ def invoke(args, param_index, out_params)
924
+ ret = nil
925
+ begin
926
+ m = @capsule.method(@func_name)
927
+ if m
928
+ ret = m.call(*args)
929
+ end
930
+ rescue Uno::UnoError => e
931
+ trace = e.backtrace
932
+ trace.unshift(trace[0].gsub(
933
+ /`[^']*'/, "\`#{e.uno_method}'")) if e.respond_to? :uno_method
934
+ trace.pop(2)
935
+ e.uno_exception.Message += "\nError during invoking method: \n" +
936
+ "Script: #{@func_name}\n" +
937
+ "File: #{@file_name}\nType: #{e.type_name}" + "\n\n" +
938
+ %Q!#{e.uno_exception}\n#{trace.join("\n")}\n!
939
+ raise
940
+ rescue => e
941
+ ex = RuntimeException.new(
942
+ %Q!\n#{e}\n#{e.backtrace.join("\n")}\n!, self)
943
+ raise Uno::UnoError, ex
944
+ end
945
+ return [ret, [0], [nil]]
946
+ end
947
+ end
948
+
949
+ # The script provider for Ruby.
950
+ class ScriptProviderImpl < BaseNode
951
+ include Uno::UnoComponentBase
952
+ include Runo::Com::Sun::Star::Script::Provider::XScriptProvider
953
+ include Runo::Com::Sun::Star::Lang::XServiceInfo
954
+ include Runo::Com::Sun::Star::Container::XNameContainer
955
+
956
+ IMPLE_NAME = "mytools.script.provider.ScriptProviderForRuby"
957
+ SERVICE_NAMES = [
958
+ "com.sun.star.script.provider.ScriptProviderForRuby",
959
+ "com.sun.star.script.provider.LanguageScriptProvider"]
960
+ NODE_TYPE = BrowseNodeTypes::ROOT
961
+
962
+ def initialize(ctx, args)
963
+ @name = RubyScriptProvider::LANGUAGE
964
+ doc = nil
965
+ inv = nil
966
+ is_package = false
967
+ context = ""
968
+
969
+ if args[0].kind_of?(String)
970
+ context = args[0]
971
+ is_package = context.end_with?(":uno_packages")
972
+ doc = RubyScriptProvider.get_document_model(ctx, context) if
973
+ context.start_with?(DOC_PROTOCOL)
974
+ else
975
+ inv = args[0]
976
+ doc = inv.ScriptContainer
977
+ context = RubyScriptProvider.get_context(ctx, doc)
978
+ end
979
+ @script_context = ScriptContext.new(ctx, doc, inv)
980
+ @url = URL.new(ctx, context, nil)
981
+ @storage = Storage.new(ctx)
982
+ if is_package
983
+ self.extend PackageManager
984
+ package_manager_init(ctx, context)
985
+ @node = PackageBrowseNode.new(@url, @storage, LANGUAGE, self)
986
+ else
987
+ @node = DirBrowseNode.new(@url, @storage, LANGUAGE)
988
+ end
989
+ end
990
+
991
+ def getChildNodes
992
+ @node.getChildNodes
993
+ end
994
+
995
+ def getScript(uri)
996
+ begin
997
+ script = RubyScripts.get_script(@storage, @script_context, @url, uri)
998
+ return script
999
+ rescue StandardError, ScriptError => e
1000
+ ex = ScriptFrameworkErrorException.new(
1001
+ %Q!\n#{e.message}\n#{e.backtrace.join("\n")}\n!,
1002
+ nil, uri, LANGUAGE, 0)
1003
+ raise Uno::UnoError, ex
1004
+ end
1005
+ end
1006
+
1007
+ def invoke(name, params, arg2, arg3)
1008
+ result = [false, [0], [nil]]
1009
+ case name
1010
+ when "Creatable"
1011
+ result = @node.invoke(name, params, arg2, arg3)
1012
+ end
1013
+ return result
1014
+ end
1015
+
1016
+ def getPropertyValue(name)
1017
+ case name
1018
+ when "Creatable"
1019
+ return @node.editable
1020
+ end
1021
+ return false
1022
+ end
1023
+ end
1024
+ end
1025
+