1 #
2 # tk/optiondb.rb : treat option database
3 #
4 require 'tk'
5
6 module TkOptionDB
7 include Tk
8 extend Tk
9
10 TkCommandNames = ['option'.freeze].freeze
11 (CmdClassID = ['CMD_CLASS'.freeze, '00000'.taint]).instance_eval{
12 @mutex = Mutex.new
13 def mutex; @mutex; end
14 freeze
15 }
16
17 module Priority
18 WidgetDefault = 20
19 StartupFile = 40
20 UserDefault = 60
21 Interactive = 80
22 end
23
24 def add(pat, value, pri=None)
25 # if $SAFE >= 4
26 # fail SecurityError, "can't call 'TkOptionDB.add' at $SAFE >= 4"
27 # end
28 tk_call('option', 'add', pat, value, pri)
29 end
30 def clear
31 # if $SAFE >= 4
32 # fail SecurityError, "can't call 'TkOptionDB.crear' at $SAFE >= 4"
33 # end
34 tk_call_without_enc('option', 'clear')
35 end
36 def get(win, name, klass)
37 tk_call('option', 'get', win ,name, klass)
38 end
39 def readfile(file, pri=None)
40 tk_call('option', 'readfile', file, pri)
41 end
42 alias read_file readfile
43 module_function :add, :clear, :get, :readfile, :read_file
44
45 def read_entries(file, f_enc=nil)
46 if TkCore::INTERP.safe?
47 fail SecurityError,
48 "can't call 'TkOptionDB.read_entries' on a safe interpreter"
49 end
50
51 i_enc = ((Tk.encoding)? Tk.encoding : Tk.encoding_system)
52
53 unless f_enc
54 f_enc = i_enc
55 end
56
57 ent = []
58 cline = ''
59 open(file, 'r') {|f|
60 while line = f.gets
61 #cline += line.chomp!
62 cline.concat(line.chomp!)
63 case cline
64 when /\\$/ # continue
65 cline.chop!
66 next
67 when /^\s*(!|#)/ # coment
68 cline = ''
69 next
70 when /^([^:]+):(.*)$/
71 pat = $1.strip
72 val = $2.lstrip
73 p "ResourceDB: #{[pat, val].inspect}" if $DEBUG
74 pat = TkCore::INTERP._toUTF8(pat, f_enc)
75 pat = TkCore::INTERP._fromUTF8(pat, i_enc)
76 val = TkCore::INTERP._toUTF8(val, f_enc)
77 val = TkCore::INTERP._fromUTF8(val, i_enc)
78 ent << [pat, val]
79 cline = ''
80 else # unknown --> ignore
81 cline = ''
82 next
83 end
84 end
85 }
86 ent
87 end
88 module_function :read_entries
89
90 def read_with_encoding(file, f_enc=nil, pri=None)
91 # try to read the file as an OptionDB file
92 read_entries(file, f_enc).each{|pat, val|
93 add(pat, val, pri)
94 }
95
96 =begin
97 i_enc = Tk.encoding()
98
99 unless f_enc
100 f_enc = i_enc
101 end
102
103 cline = ''
104 open(file, 'r') {|f|
105 while line = f.gets
106 cline += line.chomp!
107 case cline
108 when /\\$/ # continue
109 cline.chop!
110 next
111 when /^\s*!/ # coment
112 cline = ''
113 next
114 when /^([^:]+):\s(.*)$/
115 pat = $1
116 val = $2
117 p "ResourceDB: #{[pat, val].inspect}" if $DEBUG
118 pat = TkCore::INTERP._toUTF8(pat, f_enc)
119 pat = TkCore::INTERP._fromUTF8(pat, i_enc)
120 val = TkCore::INTERP._toUTF8(val, f_enc)
121 val = TkCore::INTERP._fromUTF8(val, i_enc)
122 add(pat, val, pri)
123 cline = ''
124 else # unknown --> ignore
125 cline = ''
126 next
127 end
128 end
129 }
130 =end
131 end
132 module_function :read_with_encoding
133
134 # support procs on the resource database
135 @@resource_proc_class = Class.new
136
137 @@resource_proc_class.const_set(:CARRIER, '.'.freeze)
138
139 @@resource_proc_class.instance_variable_set('@method_tbl',
140 TkCore::INTERP.create_table)
141 @@resource_proc_class.instance_variable_set('@add_method', false)
142 @@resource_proc_class.instance_variable_set('@safe_mode', 4)
143
144 class << @@resource_proc_class
145 private :new
146
147 =begin
148 CARRIER = '.'.freeze
149 METHOD_TBL = TkCore::INTERP.create_table
150 ADD_METHOD = false
151 SAFE_MODE = 4
152 =end
153
154 =begin
155 def __closed_block_check__(str)
156 depth = 0
157 str.scan(/[{}]/){|x|
158 if x == "{"
159 depth += 1
160 elsif x == "}"
161 depth -= 1
162 end
163 if depth <= 0 && !($' =~ /\A\s*\Z/)
164 fail RuntimeError, "bad string for procedure : #{str.inspect}"
165 end
166 }
167 str
168 end
169 private :__closed_block_check__
170 =end
171
172 def __check_proc_string__(str)
173 # If you want to check the proc_string, do it in this method.
174 # Please define this in the block given to 'new_proc_class' method.
175 str
176 end
177
178 def method_missing(id, *args)
179 #res_proc, proc_str = self::METHOD_TBL[id]
180 res_proc, proc_str = @method_tbl[id]
181
182 proc_source = TkOptionDB.get(self::CARRIER, id.id2name, '').strip
183 res_proc = nil if proc_str != proc_source # resource is changed
184
185 # unless res_proc.kind_of?(Proc)
186 unless TkComm._callback_entry?(res_proc)
187 #if id == :new || !(self::METHOD_TBL.has_key?(id) || self::ADD_METHOD)
188 if id == :new || !(@method_tbl.has_key?(id) || @add_method)
189 raise NoMethodError,
190 "not support resource-proc '#{id.id2name}' for #{self.name}"
191 end
192 proc_str = proc_source
193 proc_str = '{' + proc_str + '}' unless /\A\{.*\}\Z/ =~ proc_str
194 #proc_str = __closed_block_check__(proc_str)
195 proc_str = __check_proc_string__(proc_str)
196 res_proc = proc{
197 begin
198 #eval("$SAFE = #{self::SAFE_MODE};\nProc.new" + proc_str)
199 eval("$SAFE = #{@safe_mode};\nProc.new" + proc_str)
200 rescue SyntaxError=>err
201 raise SyntaxError,
202 TkCore::INTERP._toUTF8(err.message.gsub(/\(eval\):\d:/,
203 "(#{id.id2name}):"))
204 end
205 }.call
206 #self::METHOD_TBL[id] = [res_proc, proc_source]
207 @method_tbl[id] = [res_proc, proc_source]
208 end
209 res_proc.call(*args)
210 end
211
212 private :__check_proc_string__, :method_missing
213 end
214 @@resource_proc_class.freeze
215
216 =begin
217 def __create_new_class(klass, func, safe = 4, add = false, parent = nil)
218 klass = klass.to_s if klass.kind_of? Symbol
219 unless (?A..?Z) === klass[0]
220 fail ArgumentError, "bad string '#{klass}' for class name"
221 end
222 unless func.kind_of? Array
223 fail ArgumentError, "method-list must be Array"
224 end
225 func_str = func.join(' ')
226 if parent == nil
227 install_win(parent)
228 elsif parent <= @@resource_proc_class
229 install_win(parent::CARRIER)
230 else
231 fail ArgumentError, "parent must be Resource-Proc class"
232 end
233 carrier = Tk.tk_call_without_enc('frame', @path, '-class', klass)
234
235 body = <<-"EOD"
236 class #{klass} < TkOptionDB.module_eval('@@resource_proc_class')
237 CARRIER = '#{carrier}'.freeze
238 METHOD_TBL = TkCore::INTERP.create_table
239 ADD_METHOD = #{add}
240 SAFE_MODE = #{safe}
241 %w(#{func_str}).each{|f| METHOD_TBL[f.intern] = nil }
242 end
243 EOD
244
245 if parent.kind_of?(Class) && parent <= @@resource_proc_class
246 parent.class_eval(body)
247 eval(parent.name + '::' + klass)
248 else
249 eval(body)
250 eval('TkOptionDB::' + klass)
251 end
252 end
253 =end
254 def __create_new_class(klass, func, safe = 4, add = false, parent = nil)
255 if klass.kind_of?(TkWindow)
256 carrier = klass.path
257 CmdClassID.mutex.synchronize{
258 klass = CmdClassID.join(TkCore::INTERP._ip_id_)
259 CmdClassID[1].succ!
260 }
261 parent = nil # ignore parent
262 else
263 klass = klass.to_s if klass.kind_of?(Symbol)
264 unless (?A..?Z) === klass[0]
265 fail ArgumentError, "bad string '#{klass}' for class name"
266 end
267 if parent == nil
268 install_win(nil)
269 elsif parent.kind_of?(TkWindow)
270 install_win(parent.path)
271 elsif parent <= @@resource_proc_class
272 install_win(parent::CARRIER)
273 else
274 fail ArgumentError, "parent must be Resource-Proc class"
275 end
276 carrier = Tk.tk_call_without_enc('frame', @path, '-class', klass)
277 end
278
279 unless func.kind_of?(Array)
280 fail ArgumentError, "method-list must be Array"
281 end
282 func_str = func.join(' ')
283
284 if parent.kind_of?(Class) && parent <= @@resource_proc_class
285 cmd_klass = Class.new(parent)
286 else
287 cmd_klass = Class.new(TkOptionDB.module_eval('@@resource_proc_class'))
288 end
289 cmd_klass.const_set(:CARRIER, carrier.dup.freeze)
290
291 cmd_klass.instance_variable_set('@method_tbl', TkCore::INTERP.create_table)
292 cmd_klass.instance_variable_set('@add_method', add)
293 cmd_klass.instance_variable_set('@safe_mode', safe)
294 func.each{|f|
295 cmd_klass.instance_variable_get('@method_tbl')[f.to_s.intern] = nil
296 }
297 =begin
298 cmd_klass.const_set(:METHOD_TBL, TkCore::INTERP.create_table)
299 cmd_klass.const_set(:ADD_METHOD, add)
300 cmd_klass.const_set(:SAFE_MODE, safe)
301 func.each{|f| cmd_klass::METHOD_TBL[f.to_s.intern] = nil }
302 =end
303
304 cmd_klass
305 end
306 module_function :__create_new_class
307 private_class_method :__create_new_class
308
309 def __remove_methods_of_proc_class(klass)
310 # for security, make these methods invalid
311 class << klass
312 def __null_method(*args); nil; end
313 [ :class_eval, :name, :superclass, :clone, :dup, :autoload, :autoload?,
314 :ancestors, :const_defined?, :const_get, :const_set, :const_missing,
315 :class_variables, :constants, :included_modules, :instance_methods,
316 :method_defined?, :module_eval, :private_instance_methods,
317 :protected_instance_methods, :public_instance_methods,
318 :singleton_methods, :remove_const, :remove_method, :undef_method,
319 :to_s, :inspect, :display, :method, :methods, :respond_to?,
320 :instance_variable_get, :instance_variable_set, :instance_method,
321 :instance_eval, :instance_exec, :instance_variables, :kind_of?, :is_a?,
322 :private_methods, :protected_methods, :public_methods ].each{|m|
323 alias_method(m, :__null_method)
324 }
325 end
326 end
327 module_function :__remove_methods_of_proc_class
328 private_class_method :__remove_methods_of_proc_class
329
330 RAND_BASE_CNT = [0]
331 RAND_BASE_HEAD = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
332 RAND_BASE_CHAR = RAND_BASE_HEAD + 'abcdefghijklmnopqrstuvwxyz0123456789_'
333 def __get_random_basename
334 name = '%s%03d' % [RAND_BASE_HEAD[rand(RAND_BASE_HEAD.size),1],
335 RAND_BASE_CNT[0]]
336 len = RAND_BASE_CHAR.size
337 (6+rand(10)).times{
338 name << RAND_BASE_CHAR[rand(len),1]
339 }
340 RAND_BASE_CNT[0] = RAND_BASE_CNT[0] + 1
341 name
342 end
343 module_function :__get_random_basename
344 private_class_method :__get_random_basename
345
346 # define new proc class :
347 # If you want to modify the new class or create a new subclass,
348 # you must do such operation in the block parameter.
349 # Because the created class is flozen after evaluating the block.
350 def new_proc_class(klass, func, safe = 4, add = false, parent = nil, &b)
351 new_klass = __create_new_class(klass, func, safe, add, parent)
352 new_klass.class_eval(&b) if block_given?
353 __remove_methods_of_proc_class(new_klass)
354 new_klass.freeze
355 new_klass
356 end
357 module_function :new_proc_class
358
359 def eval_under_random_base(parent = nil, &b)
360 new_klass = __create_new_class(__get_random_basename(),
361 [], 4, false, parent)
362 ret = new_klass.class_eval(&b) if block_given?
363 __remove_methods_of_proc_class(new_klass)
364 new_klass.freeze
365 ret
366 end
367 module_function :eval_under_random_base
368
369 def new_proc_class_random(klass, func, safe = 4, add = false, &b)
370 eval_under_random_base(){
371 TkOptionDB.new_proc_class(klass, func, safe, add, self, &b)
372 }
373 end
374 module_function :new_proc_class_random
375 end
376 TkOption = TkOptionDB
377 TkResourceDB = TkOptionDB