rubyuno 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+