1#
2#   tk/namespace.rb : methods to manipulate Tcl/Tk namespace
3#                           by Hidetoshi Nagai <nagai@ai.kyutech.ac.jp>
4#
5require 'tk'
6
7class TkNamespace < TkObject
8  extend Tk
9
10  TkCommandNames = [
11    'namespace'.freeze,
12  ].freeze
13
14  Tk_Namespace_ID_TBL = TkCore::INTERP.create_table
15
16  (Tk_Namespace_ID = ["ns".freeze, TkUtil.untrust("00000")]).instance_eval{
17    @mutex = Mutex.new
18    def mutex; @mutex; end
19    freeze
20  }
21
22  Tk_NsCode_RetObjID_TBL = TkCore::INTERP.create_table
23
24  TkCore::INTERP.init_ip_env{
25    Tk_Namespace_ID_TBL.mutex.synchronize{ Tk_Namespace_ID_TBL.clear }
26    Tk_NsCode_RetObjID_TBL.mutex.synchronize{ Tk_NsCode_RetObjID_TBL.clear }
27  }
28
29  def TkNamespace.id2obj(id)
30    Tk_Namespace_ID_TBL.mutex.synchronize{
31      Tk_Namespace_ID_TBL[id]? Tk_Namespace_ID_TBL[id]: id
32    }
33  end
34
35  #####################################
36
37  class Ensemble < TkObject
38    def __cget_cmd
39      ['namespace', 'ensemble', 'configure', self.path]
40    end
41    private :__cget_cmd
42
43    def __config_cmd
44      ['namespace', 'ensemble', 'configure', self.path]
45    end
46    private :__config_cmd
47
48    def __configinfo_struct
49      {:key=>0, :alias=>nil, :db_name=>nil, :db_class=>nil,
50        :default_value=>nil, :current_value=>2}
51    end
52    private :__configinfo_struct
53
54    def __boolval_optkeys
55      ['prefixes']
56    end
57    private :__boolval_optkeys
58
59    def __listval_optkeys
60      ['map', 'subcommands', 'unknown']
61    end
62    private :__listval_optkeys
63
64    def self.exist?(ensemble)
65      bool(tk_call('namespace', 'ensemble', 'exists', ensemble))
66    end
67
68    def initialize(keys = {})
69      @ensemble = @path = tk_call('namespace', 'ensemble', 'create', keys)
70    end
71
72    def cget(slot)
73      if slot == :namespace || slot == 'namespace'
74        ns = super(slot)
75        Tk_Namespace_ID_TBL.mutex.synchronize{
76          if TkNamespace::Tk_Namespace_ID_TBL.key?(ns)
77            TkNamespace::Tk_Namespace_ID_TBL[ns]
78          else
79            ns
80          end
81        }
82      else
83        super(slot)
84      end
85    end
86    def cget_strict(slot)
87      if slot == :namespace || slot == 'namespace'
88        ns = super(slot)
89        Tk_Namespace_ID_TBL.mutex.synchronize{
90          if TkNamespace::Tk_Namespace_ID_TBL.key?(ns)
91            TkNamespace::Tk_Namespace_ID_TBL[ns]
92          else
93            ns
94          end
95        }
96      else
97        super(slot)
98      end
99    end
100
101    def configinfo(slot = nil)
102      if slot
103        if slot == :namespace || slot == 'namespace'
104          val = super(slot)
105          Tk_Namespace_ID_TBL.mutex.synchronize{
106            if TkNamespace::Tk_Namespace_ID_TBL.key?(val)
107              val = TkNamespace::Tk_Namespace_ID_TBL[val]
108            end
109          }
110        else
111          val = super(slot)
112        end
113
114        if TkComm::GET_CONFIGINFO_AS_ARRAY
115          [slot.to_s, val]
116        else # ! TkComm::GET_CONFIGINFO_AS_ARRAY
117          {slot.to_s => val}
118        end
119
120      else
121        info = super()
122
123        if TkComm::GET_CONFIGINFO_AS_ARRAY
124          Tk_Namespace_ID_TBL.mutex.synchronize{
125            info.map!{|inf|
126              if inf[0] == 'namespace' &&
127                  TkNamespace::Tk_Namespace_ID_TBL.key?(inf[-1])
128                [inf[0], TkNamespace::Tk_Namespace_ID_TBL[inf[-1]]]
129              else
130                inf
131              end
132            }
133          }
134        else # ! TkComm::GET_CONFIGINFO_AS_ARRAY
135          val = info['namespace']
136          Tk_Namespace_ID_TBL.mutex.synchronize{
137            if TkNamespace::Tk_Namespace_ID_TBL.key?(val)
138              info['namespace'] = TkNamespace::Tk_Namespace_ID_TBL[val]
139            end
140          }
141        end
142
143        info
144      end
145    end
146
147    def exists?
148      bool(tk_call('namespace', 'ensemble', 'exists', @path))
149    end
150  end
151
152  #####################################
153
154  class ScopeArgs < Array
155    include Tk
156
157    # alias __tk_call             tk_call
158    # alias __tk_call_without_enc tk_call_without_enc
159    # alias __tk_call_with_enc    tk_call_with_enc
160    def tk_call(*args)
161      #super('namespace', 'eval', @namespace, *args)
162      args = args.collect{|arg| (s = _get_eval_string(arg, true))? s: ''}
163      super('namespace', 'eval', @namespace,
164            TkCore::INTERP._merge_tklist(*args))
165    end
166    def tk_call_without_enc(*args)
167      #super('namespace', 'eval', @namespace, *args)
168      args = args.collect{|arg| (s = _get_eval_string(arg, true))? s: ''}
169      super('namespace', 'eval', @namespace,
170            TkCore::INTERP._merge_tklist(*args))
171    end
172    def tk_call_with_enc(*args)
173      #super('namespace', 'eval', @namespace, *args)
174      args = args.collect{|arg| (s = _get_eval_string(arg, true))? s: ''}
175      super('namespace', 'eval', @namespace,
176            TkCore::INTERP._merge_tklist(*args))
177    end
178
179    def initialize(namespace, *args)
180      @namespace = namespace
181      super(args.size)
182      self.replace(args)
183    end
184  end
185
186  #####################################
187
188  class NsCode < TkObject
189    def initialize(scope, use_obj_id = false)
190      @scope = scope + ' '
191      @use_obj_id = use_obj_id
192    end
193    def path
194      @scope
195    end
196    def to_eval
197      @scope
198    end
199    def call(*args)
200      ret = TkCore::INTERP._eval_without_enc(@scope + array2tk_list(args))
201      if @use_obj_id
202        ret = TkNamespace::Tk_NsCode_RetObjID_TBL.delete(ret.to_i)
203      end
204      ret
205    end
206  end
207
208  #####################################
209
210  def install_cmd(cmd)
211    lst = tk_split_simplelist(super(cmd), false, false)
212    if lst[1] =~ /^::/
213      lst[1] = @fullname
214    else
215      lst.insert(1, @fullname)
216    end
217    TkCore::INTERP._merge_tklist(*lst)
218  end
219
220  alias __tk_call             tk_call
221  alias __tk_call_without_enc tk_call_without_enc
222  alias __tk_call_with_enc    tk_call_with_enc
223  def tk_call(*args)
224    #super('namespace', 'eval', @fullname, *args)
225    args = args.collect{|arg| (s = _get_eval_string(arg, true))? s: ''}
226    super('namespace', 'eval', @fullname,
227          TkCore::INTERP._merge_tklist(*args))
228  end
229  def tk_call_without_enc(*args)
230    #super('namespace', 'eval', @fullname, *args)
231    args = args.collect{|arg| (s = _get_eval_string(arg, true))? s: ''}
232    super('namespace', 'eval', @fullname,
233          TkCore::INTERP._merge_tklist(*args))
234  end
235  def tk_call_with_enc(*args)
236    #super('namespace', 'eval', @fullname, *args)
237    args = args.collect{|arg| (s = _get_eval_string(arg, true))? s: ''}
238    super('namespace', 'eval', @fullname,
239          TkCore::INTERP._merge_tklist(*args))
240  end
241  alias ns_tk_call             tk_call
242  alias ns_tk_call_without_enc tk_call_without_enc
243  alias ns_tk_call_with_enc    tk_call_with_enc
244
245  def initialize(name = nil, parent = nil)
246    unless name
247      Tk_Namespace_ID.mutex.synchronize{
248        # name = Tk_Namespace_ID.join('')
249        name = Tk_Namespace_ID.join(TkCore::INTERP._ip_id_)
250        Tk_Namespace_ID[1].succ!
251      }
252    end
253    name = __tk_call('namespace', 'current') if name == ''
254    if parent
255      if parent =~ /^::/
256        if name =~ /^::/
257          @fullname = parent + name
258        else
259          @fullname = parent +'::'+ name
260        end
261      else
262        ancestor = __tk_call('namespace', 'current')
263        ancestor = '' if ancestor == '::'
264        if name =~ /^::/
265          @fullname = ancestor + '::' + parent + name
266        else
267          @fullname = ancestor + '::'+ parent +'::'+ name
268        end
269      end
270    else # parent == nil
271      ancestor = __tk_call('namespace', 'current')
272      ancestor = '' if ancestor == '::'
273      if name =~ /^::/
274        @fullname = name
275      else
276        @fullname = ancestor + '::' + name
277      end
278    end
279    @path = @fullname
280    @parent = __tk_call('namespace', 'qualifiers', @fullname)
281    @name = __tk_call('namespace', 'tail', @fullname)
282
283    # create namespace
284    __tk_call('namespace', 'eval', @fullname, '')
285
286    Tk_Namespace_ID_TBL.mutex.synchronize{
287      Tk_Namespace_ID_TBL[@fullname] = self
288    }
289  end
290
291  def self.children(*args)
292    # args ::= [<namespace>] [<pattern>]
293    # <pattern> must be glob-style pattern
294    tk_split_simplelist(tk_call('namespace', 'children', *args)).collect{|ns|
295      # ns is fullname
296      Tk_Namespace_ID_TBL.mutex.synchronize{
297        if Tk_Namespace_ID_TBL.key?(ns)
298          Tk_Namespace_ID_TBL[ns]
299        else
300          ns
301        end
302      }
303    }
304  end
305  def children(pattern=None)
306    TkNamespace.children(@fullname, pattern)
307  end
308
309  def self.code(script = Proc.new)
310    TkNamespace.new('').code(script)
311  end
312=begin
313  def code(script = Proc.new)
314    if script.kind_of?(String)
315      cmd = proc{|*args| ScopeArgs.new(@fullname,*args).instance_eval(script)}
316    elsif script.kind_of?(Proc)
317      cmd = proc{|*args| ScopeArgs.new(@fullname,*args).instance_eval(&script)}
318    else
319      fail ArgumentError, "String or Proc is expected"
320    end
321    TkNamespace::NsCode.new(tk_call_without_enc('namespace', 'code',
322                                                _get_eval_string(cmd, false)))
323  end
324=end
325  def code(script = Proc.new)
326    if script.kind_of?(String)
327      cmd = proc{|*args|
328        ret = ScopeArgs.new(@fullname,*args).instance_eval(script)
329        id = ret.object_id
330        TkNamespace::Tk_NsCode_RetObjID_TBL[id] = ret
331        id
332      }
333    elsif script.kind_of?(Proc)
334      cmd = proc{|*args|
335        if TkCore::WITH_RUBY_VM  ### Ruby 1.9 !!!!
336          obj = ScopeArgs.new(@fullname,*args)
337          ret = obj.instance_exec(obj, &script)
338        else
339          ret = ScopeArgs.new(@fullname,*args).instance_eval(&script)
340        end
341        id = ret.object_id
342        TkNamespace::Tk_NsCode_RetObjID_TBL[id] = ret
343        id
344      }
345    else
346      fail ArgumentError, "String or Proc is expected"
347    end
348    TkNamespace::NsCode.new(tk_call_without_enc('namespace', 'code',
349                                                _get_eval_string(cmd, false)),
350                            true)
351  end
352
353  def self.current_path
354    tk_call('namespace', 'current')
355  end
356  def current_path
357    @fullname
358  end
359
360  def self.current
361    ns = self.current_path
362    Tk_Namespace_ID_TBL.mutex.synchronize{
363      if Tk_Namespace_ID_TBL.key?(ns)
364        Tk_Namespace_ID_TBL[ns]
365      else
366        ns
367      end
368    }
369  end
370  def current_namespace
371    # ns_tk_call('namespace', 'current')
372    # @fullname
373    self
374  end
375  alias current current_namespace
376
377  def self.delete(*ns_list)
378    tk_call('namespace', 'delete', *ns_list)
379    ns_list.each{|ns|
380      Tk_Namespace_ID_TBL.mutex.synchronize{
381        if ns.kind_of?(TkNamespace)
382          Tk_Namespace_ID_TBL.delete(ns.path)
383        else
384          Tk_Namespace_ID_TBL.delete(ns.to_s)
385        end
386      }
387    }
388  end
389  def delete
390    TkNamespece.delete(@fullname)
391  end
392
393  def self.ensemble_create(*keys)
394    tk_call('namespace', 'ensemble', 'create', *hash_kv(keys))
395  end
396  def self.ensemble_configure(cmd, slot, value=None)
397    if slot.kind_of?(Hash)
398      tk_call('namespace', 'ensemble', 'configure', cmd, *hash_kv(slot))
399    else
400      tk_call('namespace', 'ensemble', 'configure', cmd, '-'+slot.to_s, value)
401    end
402  end
403  def self.ensemble_configinfo(cmd, slot = nil)
404    if slot
405      tk_call('namespace', 'ensemble', 'configure', cmd, '-' + slot.to_s)
406    else
407      inf = {}
408      Hash(*tk_split_simplelist(tk_call('namespace', 'ensemble', 'configure', cmd))).each{|k, v| inf[k[1..-1]] = v}
409      inf
410    end
411  end
412  def self.ensemble_exist?(cmd)
413    bool(tk_call('namespace', 'ensemble', 'exists', cmd))
414  end
415
416  def self.eval(namespace, cmd = Proc.new, *args)
417    #tk_call('namespace', 'eval', namespace, cmd, *args)
418    TkNamespace.new(namespace).eval(cmd, *args)
419  end
420=begin
421  def eval(cmd = Proc.new, *args)
422    #TkNamespace.eval(@fullname, cmd, *args)
423    #ns_tk_call(cmd, *args)
424    code_obj = code(cmd)
425    ret = code_obj.call(*args)
426    # uninstall_cmd(TkCore::INTERP._split_tklist(code_obj.path)[-1])
427    uninstall_cmd(_fromUTF8(TkCore::INTERP._split_tklist(_toUTF8(code_obj.path))[-1]))
428    tk_tcl2ruby(ret)
429  end
430=end
431  def eval(cmd = Proc.new, *args)
432    code_obj = code(cmd)
433    ret = code_obj.call(*args)
434    uninstall_cmd(_fromUTF8(TkCore::INTERP._split_tklist(_toUTF8(code_obj.path))[-1]))
435    ret
436  end
437
438  def self.exist?(ns)
439    bool(tk_call('namespace', 'exists', ns))
440  end
441  def exist?
442    TkNamespece.exist?(@fullname)
443  end
444
445  def self.export(*patterns)
446    tk_call('namespace', 'export', *patterns)
447  end
448  def self.export_with_clear(*patterns)
449    tk_call('namespace', 'export', '-clear', *patterns)
450  end
451  def export
452    TkNamespace.export(@fullname)
453  end
454  def export_with_clear
455    TkNamespace.export_with_clear(@fullname)
456  end
457
458  def self.forget(*patterns)
459    tk_call('namespace', 'forget', *patterns)
460  end
461  def forget
462    TkNamespace.forget(@fullname)
463  end
464
465  def self.import(*patterns)
466    tk_call('namespace', 'import', *patterns)
467  end
468  def self.force_import(*patterns)
469    tk_call('namespace', 'import', '-force', *patterns)
470  end
471  def import
472    TkNamespace.import(@fullname)
473  end
474  def force_import
475    TkNamespace.force_import(@fullname)
476  end
477
478  def self.inscope(namespace, script, *args)
479    tk_call('namespace', 'inscope', namespace, script, *args)
480  end
481  def inscope(script, *args)
482    TkNamespace.inscope(@fullname, script, *args)
483  end
484
485  def self.origin(cmd)
486    tk_call('namespace', 'origin', cmd)
487  end
488
489  def self.parent(namespace=None)
490    ns = tk_call('namespace', 'parent', namespace)
491    Tk_Namespace_ID_TBL.mutex.synchronize{
492      if Tk_Namespace_ID_TBL.key?(ns)
493        Tk_Namespace_ID_TBL[ns]
494      else
495        ns
496      end
497    }
498  end
499  def parent
500    tk_call('namespace', 'parent', @fullname)
501  end
502
503  def self.get_path
504    tk_call('namespace', 'path')
505  end
506  def self.set_path(*namespace_list)
507    tk_call('namespace', 'path', array2tk_list(namespace_list))
508  end
509  def set_path
510    tk_call('namespace', 'path', @fullname)
511  end
512
513  def self.qualifiers(str)
514    tk_call('namespace', 'qualifiers', str)
515  end
516
517  def self.tail(str)
518    tk_call('namespace', 'tail', str)
519  end
520
521  def self.upvar(namespace, *var_pairs)
522    tk_call('namespace', 'upvar', namespace, *(var_pairs.flatten))
523  end
524  def upvar(*var_pairs)
525    TkNamespace.inscope(@fullname, *(var_pairs.flatten))
526  end
527
528  def self.get_unknown_handler
529    tk_tcl2ruby(tk_call('namespace', 'unknown'))
530  end
531  def self.set_unknown_handler(cmd = Proc.new)
532    tk_call('namespace', 'unknown', cmd)
533  end
534
535  def self.which(name)
536    tk_call('namespace', 'which', name)
537  end
538  def self.which_command(name)
539    tk_call('namespace', 'which', '-command', name)
540  end
541  def self.which_variable(name)
542    tk_call('namespace', 'which', '-variable', name)
543  end
544end
545
546TkNamespace::Global = TkNamespace.new('::')
547