ruby_ex 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. data/AUTHORS +51 -0
  2. data/ChangeLog +1763 -0
  3. data/NEWS +3 -0
  4. data/README +1 -0
  5. data/Rakefile +8 -0
  6. data/SPEC.dyn.yml +10 -0
  7. data/SPEC.gem.yml +269 -0
  8. data/SPEC.yml +36 -0
  9. data/src/abstract.rb +253 -0
  10. data/src/abstract_node.rb +85 -0
  11. data/src/algorithms.rb +12 -0
  12. data/src/algorithms/simulated_annealing.rb +142 -0
  13. data/src/ask.rb +100 -0
  14. data/src/attributed_class.rb +303 -0
  15. data/src/cache.rb +350 -0
  16. data/src/checkout.rb +12 -0
  17. data/src/choose.rb +271 -0
  18. data/src/commands.rb +20 -0
  19. data/src/commands/command.rb +492 -0
  20. data/src/commands/datas.rb +16 -0
  21. data/src/commands/datas/composite.rb +31 -0
  22. data/src/commands/datas/data.rb +65 -0
  23. data/src/commands/datas/factory.rb +69 -0
  24. data/src/commands/datas/temp.rb +26 -0
  25. data/src/commands/factory.rb +67 -0
  26. data/src/commands/helpers.rb +81 -0
  27. data/src/commands/pipe.rb +66 -0
  28. data/src/commands/runners.rb +16 -0
  29. data/src/commands/runners/exec.rb +50 -0
  30. data/src/commands/runners/fork.rb +130 -0
  31. data/src/commands/runners/runner.rb +140 -0
  32. data/src/commands/runners/system.rb +57 -0
  33. data/src/commands/seq.rb +32 -0
  34. data/src/config_file.rb +95 -0
  35. data/src/const_regexp.rb +57 -0
  36. data/src/daemon.rb +135 -0
  37. data/src/diff.rb +665 -0
  38. data/src/dlogger.rb +62 -0
  39. data/src/drb/drb_observable.rb +95 -0
  40. data/src/drb/drb_observable_pool.rb +27 -0
  41. data/src/drb/drb_service.rb +44 -0
  42. data/src/drb/drb_undumped_attributes.rb +56 -0
  43. data/src/drb/drb_undumped_indexed_object.rb +55 -0
  44. data/src/drb/insecure_protected_methods.rb +101 -0
  45. data/src/drb_ex.rb +12 -0
  46. data/src/dumpable_proc.rb +57 -0
  47. data/src/filetype.rb +229 -0
  48. data/src/generate_id.rb +44 -0
  49. data/src/histogram.rb +222 -0
  50. data/src/hookable.rb +283 -0
  51. data/src/hooker.rb +54 -0
  52. data/src/indexed_node.rb +65 -0
  53. data/src/io_marshal.rb +99 -0
  54. data/src/ioo.rb +193 -0
  55. data/src/labeled_node.rb +62 -0
  56. data/src/logger_observer.rb +24 -0
  57. data/src/md5sum.rb +70 -0
  58. data/src/module/autoload_tree.rb +65 -0
  59. data/src/module/hierarchy.rb +334 -0
  60. data/src/module/instance_method_visibility.rb +71 -0
  61. data/src/node.rb +81 -0
  62. data/src/object_monitor.rb +143 -0
  63. data/src/object_monitor_activity.rb +34 -0
  64. data/src/observable.rb +138 -0
  65. data/src/observable_pool.rb +291 -0
  66. data/src/orderedhash.rb +252 -0
  67. data/src/pp_hierarchy.rb +30 -0
  68. data/src/random_generators.rb +29 -0
  69. data/src/random_generators/random_generator.rb +33 -0
  70. data/src/random_generators/ruby.rb +25 -0
  71. data/src/ruby_ex.rb +124 -0
  72. data/src/safe_eval.rb +346 -0
  73. data/src/sendmail.rb +214 -0
  74. data/src/service_manager.rb +122 -0
  75. data/src/shuffle.rb +30 -0
  76. data/src/spring.rb +134 -0
  77. data/src/spring_set.rb +134 -0
  78. data/src/symtbl.rb +108 -0
  79. data/src/synflow.rb +474 -0
  80. data/src/thread_mutex.rb +11 -0
  81. data/src/timeout_ex.rb +79 -0
  82. data/src/trace.rb +26 -0
  83. data/src/uri/druby.rb +78 -0
  84. data/src/uri/file.rb +63 -0
  85. data/src/uri/ftp_ex.rb +36 -0
  86. data/src/uri/http_ex.rb +41 -0
  87. data/src/uri/pgsql.rb +136 -0
  88. data/src/uri/ssh.rb +87 -0
  89. data/src/uri/svn.rb +113 -0
  90. data/src/uri_ex.rb +71 -0
  91. data/src/verbose_object.rb +70 -0
  92. data/src/yaml/basenode_ext.rb +63 -0
  93. data/src/yaml/chop_header.rb +24 -0
  94. data/src/yaml/transform.rb +450 -0
  95. data/src/yaml/yregexpath.rb +76 -0
  96. data/test/algorithms/simulated_annealing_test.rb +102 -0
  97. data/test/check-pkg-ruby_ex.yml +15 -0
  98. data/test/check-ruby_ex.yml +12 -0
  99. data/test/resources/autoload_tree/A.rb +11 -0
  100. data/test/resources/autoload_tree/B.rb +10 -0
  101. data/test/resources/autoload_tree/foo/C.rb +18 -0
  102. data/test/resources/foo.txt +6 -0
  103. data/test/sanity-suite.yml +12 -0
  104. data/test/sanity/multiple-requires.yml +20 -0
  105. data/test/sanity/single-requires.yml +24 -0
  106. data/test/test-unit-setup.rb +6 -0
  107. data/test/unit-suite.yml +14 -0
  108. metadata +269 -0
data/src/uri/ssh.rb ADDED
@@ -0,0 +1,87 @@
1
+ # Copyright:: Copyright (c) 2005 Nicolas Pouillard. All rights reserved.
2
+ # Author:: Nicolas Pouillard <ertai@lrde.epita.fr>.
3
+ # License:: Gnu General Public License.
4
+ # Revision:: $Id$
5
+
6
+ require 'uri_ex'
7
+
8
+ module URI
9
+
10
+ class Ssh < Generic
11
+
12
+ SCHEME = 'ssh'.freeze
13
+ DEFAULT_HOST = 'localhost'.freeze
14
+ DEFAULT_PORT = 22
15
+ DEFAULT_QUERY = ''.freeze
16
+ SCP = 'scp'.to_cmd.freeze
17
+
18
+ COMPONENT = [
19
+ :scheme,
20
+ :userinfo,
21
+ :host,
22
+ :path,
23
+ :query
24
+ ].freeze
25
+
26
+ def self.build ( args )
27
+ tmp = Util::make_components_hash(self, args)
28
+ return super(tmp)
29
+ end
30
+
31
+ def mk_opts
32
+ opts = []
33
+ opts << '-q'
34
+ opts << '-P' << @port if @port != DEFAULT_PORT
35
+ return opts if @query.nil?
36
+ @query.split(/,/).map do |x|
37
+ k, v = x.split(/=/)
38
+ if k.size == 1
39
+ opts << "-#{k}"
40
+ else
41
+ opts << "--#{k}"
42
+ end
43
+ opts << v unless v.nil?
44
+ end
45
+ opts
46
+ end
47
+
48
+ def mk_arg
49
+ "#@user@#@host:#{@path.gsub(/^\//, '')}"
50
+ end
51
+
52
+ def checkout
53
+ out = TempPath.new('checkout', pathname.basename.to_s)
54
+ SCP[mk_opts, mk_arg, out].run(self.runner)
55
+ end
56
+
57
+ def save
58
+ checkout.save
59
+ end
60
+
61
+ def commit ( aPath )
62
+ SCP[mk_opts, aPath, mk_arg].run(self.runner)
63
+ end
64
+
65
+ end # class Ssh
66
+
67
+ @@schemes[Ssh::SCHEME.upcase] = Ssh
68
+
69
+ test_section __FILE__ do
70
+ class SshTest < Test::Unit::TestCase
71
+ def test_basic
72
+ assert_nothing_raised { @uri = URI.parse('ssh://foo@bar') }
73
+ assert_equal(['-q'], @uri.mk_opts)
74
+ assert_nothing_raised { @uri = URI.parse('ssh://foo@bar:42') }
75
+ assert_equal(['-q', '-P', 42], @uri.mk_opts)
76
+ end
77
+ def test_with_query
78
+ assert_nothing_raised do
79
+ @uri = URI.parse('ssh://foo@bar/qux?a=b,c,d=e,f,ghi,jkl=mno')
80
+ end
81
+ ref = ['-q', '-a', 'b', '-c', '-d', 'e', '-f', '--ghi', '--jkl', 'mno']
82
+ assert_equal(ref, @uri.mk_opts)
83
+ end
84
+ end # class SshTest
85
+ end
86
+
87
+ end # module URI
data/src/uri/svn.rb ADDED
@@ -0,0 +1,113 @@
1
+ # Copyright: Copyright (c) 2005 Nicolas Pouillard. All rights reserved.
2
+ # Author: Nicolas Pouillard <ertai@lrde.epita.fr>.
3
+ # License: Gnu General Public License.
4
+
5
+ # $LastChangedBy: ertai $
6
+ # $Id: svn.rb 237 2005-05-17 19:01:52Z ertai $
7
+
8
+ require 'uri_ex'
9
+ require 'abstract'
10
+
11
+ module URI
12
+
13
+ class Svn < Generic
14
+
15
+ COMPONENT = [
16
+ :scheme,
17
+ :host,
18
+ :path
19
+ ].freeze
20
+ SVN = 'svn'.to_cmd.freeze
21
+ SVNADMIN = 'svnadmin'.to_cmd.freeze
22
+
23
+
24
+ def self.build ( args )
25
+ tmp = Util::make_components_hash(self, args)
26
+ return super(tmp)
27
+ end
28
+
29
+ def checkout
30
+ # klass = SvnSchemes.guess(scheme)
31
+ # klass.new
32
+ tmp = TempPath.new('svn-checkout')
33
+ tmp.mkpath
34
+ target = to_s.sub(/^svn\./, '')
35
+ tmp += pathname.basename
36
+ SVN['checkout', target, tmp].run(self.runner)
37
+ end
38
+
39
+ def save
40
+ raise SaveError unless scheme.downcase == 'svn.file'
41
+ repos = pathname
42
+ raise SaveError unless (repos + 'format').exist?
43
+ out = TempPath.new('save', "#{repos.basename}.svndump")
44
+ cmd = SVNADMIN['dump', repos, '--incremental', '--quiet'] > out
45
+ cmd.run(self.runner)
46
+ end
47
+
48
+ end # class Svn
49
+
50
+ module SvnSchemes
51
+
52
+ def self.guess ( aScheme )
53
+ init
54
+ @@svn_schemes[aScheme]
55
+ end
56
+
57
+ def self.init
58
+ return if defined? @@svn_schemes
59
+ @@svn_schemes = {}
60
+ constants.each do |const|
61
+ klass = const_get(const)
62
+ next if klass.abstract?
63
+ @@svn_schemes[klass.scheme] = klass
64
+ end
65
+ @@svn_schemes.freeze
66
+ end
67
+
68
+ def self.svn_schemes
69
+ init
70
+ @@svn_schemes
71
+ end
72
+
73
+ class AbstractSvnScheme
74
+ include Abstract
75
+
76
+ def self.scheme
77
+ const_get(:SCHEME)
78
+ end
79
+
80
+ end
81
+
82
+ class SvnScheme < AbstractSvnScheme
83
+ include Concrete
84
+ SCHEME = 'SVN'
85
+ end
86
+
87
+ class SvnSshScheme < AbstractSvnScheme
88
+ include Concrete
89
+ SCHEME = 'SVN+SSH'
90
+ end
91
+
92
+ class SvnHttpScheme < AbstractSvnScheme
93
+ include Concrete
94
+ SCHEME = 'SVN.HTTP'
95
+ end
96
+
97
+ class SvnHttpsScheme < AbstractSvnScheme
98
+ include Concrete
99
+ SCHEME = 'SVN.HTTPS'
100
+ end
101
+
102
+ class SvnFileScheme < AbstractSvnScheme
103
+ include Concrete
104
+ SCHEME = 'SVN.FILE'
105
+ end
106
+
107
+ end
108
+
109
+ SvnSchemes.svn_schemes.each_key do |name|
110
+ @@schemes[name] = Svn
111
+ end
112
+
113
+ end # module URI
data/src/uri_ex.rb ADDED
@@ -0,0 +1,71 @@
1
+ # Copyright:: Copyright (c) 2005 Nicolas Pouillard. All rights reserved.
2
+ # Author:: Nicolas Pouillard <ertai@lrde.epita.fr>.
3
+ # License:: Gnu General Public License.
4
+ # Revision:: $Id: uri_ex.rb 266 2005-06-01 14:27:18Z ertai $
5
+
6
+ require 'uri'
7
+ require 'ruby_ex'
8
+ require 'commands'
9
+
10
+
11
+ module URI
12
+
13
+ class CheckoutError < Exception
14
+ end
15
+
16
+ class CommitError < Exception
17
+ end
18
+
19
+ class SaveError < Exception
20
+ end
21
+
22
+ class Generic
23
+
24
+ #
25
+ # To make all uris command verbose you can change or modify the global
26
+ # command runner: URI::Generic.runner.make_verbose!
27
+ #
28
+ class_inheritable_accessor :runner
29
+
30
+ self.runner = Commands::Runners::System.new.raise_on_failures
31
+ self.runner.command_data_factory.command_data_class = Commands::Datas::Temp
32
+
33
+ def pathname
34
+ Pathname.new(path)
35
+ end
36
+
37
+ def pathname= ( path )
38
+ self.path = path.to_s
39
+ end
40
+
41
+ def to_yaml_type
42
+ '!uri'
43
+ end
44
+
45
+ def to_yaml ( opts={} )
46
+ "!uri #{to_s}"
47
+ end
48
+
49
+ def checkout
50
+ raise CheckoutError, "Can't checkout a #{self.class}"
51
+ end
52
+
53
+ def commit
54
+ raise CommitError, "Can't commit a #{self.class}"
55
+ end
56
+
57
+ def save
58
+ raise SaveError, "Can't save a #{self.class}"
59
+ end
60
+
61
+ def add_query ( arg )
62
+ if query.nil?
63
+ self.query = arg
64
+ else
65
+ self.query += ',' + arg
66
+ end
67
+ end
68
+
69
+ end # module Generic
70
+
71
+ end # module URI
@@ -0,0 +1,70 @@
1
+ # Author:: Nicolas Pouillard <ertai@lrde.epita.fr>.
2
+ # Copyright:: Copyright (c) 2005 Nicolas Pouillard. All rights reserved.
3
+ # License:: GNU General Public License (GPL).
4
+ # Revision:: $Id: verbose_object.rb 266 2005-06-01 14:27:18Z ertai $
5
+
6
+ require 'ruby_ex'
7
+
8
+ module Verbosify
9
+
10
+ SKIP_METHODS = %w[ instance_eval ]
11
+
12
+ def self.extend_object ( anObject )
13
+ anObject.methods.each do |meth|
14
+ next if meth =~ /__.*__/
15
+ next if SKIP_METHODS.include? meth
16
+ anObject.instance_eval %Q{
17
+ def #{meth} ( *a, &b )
18
+ __log__(:#{meth}, *a, &b)
19
+ super
20
+ end
21
+ }
22
+ end
23
+ anObject.instance_eval do
24
+ def verbose_object?
25
+ true
26
+ end
27
+ def __log__ ( *a )
28
+ STDERR.puts "LOG: #{a.inspect}"
29
+ end
30
+ end
31
+ end
32
+
33
+ end # module Verbosify
34
+
35
+
36
+ class VerboseObject
37
+
38
+ def initialize ( anObject )
39
+ @obj = anObject
40
+ end
41
+
42
+ instance_methods.each do |meth|
43
+ next if meth =~ /__.*__/
44
+ module_eval %Q{
45
+ def #{meth} ( *a, &b )
46
+ method_missing(:#{meth}, *a, &b)
47
+ end
48
+ }
49
+ end
50
+
51
+ def verbose_object?
52
+ true
53
+ end
54
+
55
+ def __log__ ( *a )
56
+ STDERR.puts "LOG: #{a.inspect}"
57
+ end
58
+
59
+ def method_missing ( *a, &b )
60
+ __log__(*a)
61
+ @obj.__send__(*a, &b)
62
+ end
63
+
64
+ end # class VerboseObject
65
+
66
+ class Object
67
+ def verbose_object?
68
+ false
69
+ end
70
+ end # class Object
@@ -0,0 +1,63 @@
1
+ # Copyright: Copyright (c) 2004 Nicolas Pouillard. All rights reserved.
2
+ # Author: Nicolas Pouillard <ertai@lrde.epita.fr>.
3
+ # License: Gnu General Public License.
4
+
5
+ # $LastChangedBy: ertai $
6
+ # $Id: basenode_ext.rb 266 2005-06-01 14:27:18Z ertai $
7
+
8
+ require 'ruby_ex'
9
+ require 'yaml'
10
+ require 'yaml/basenode'
11
+
12
+ module YAML
13
+
14
+ module BaseNode
15
+
16
+ def ordered_children_with_index
17
+ arr = children_with_index
18
+ arr.sort! { |a,b| b[0].object_id <=> a[0].object_id } unless arr.nil?
19
+ arr
20
+ end
21
+
22
+ def ordered_children_indexes
23
+ arr = ordered_children_with_index
24
+ arr.map! { |x| x[1] } unless arr.nil?
25
+ arr
26
+ end
27
+
28
+ def ordered_children
29
+ arr = children
30
+ arr.map! { |x| x[0] } unless arr.nil?
31
+ arr
32
+ end
33
+
34
+ end # module BaseNode
35
+
36
+ end # module YAML
37
+
38
+
39
+ test_section __FILE__ do
40
+
41
+ class BaseNodeExtTest < Test::Unit::TestCase
42
+
43
+ def basenode_checker ( my, ref )
44
+ node = YAML::parse(my)
45
+ assert_equal(node.ordered_children_indexes, ref)
46
+ end
47
+
48
+ def test_basenode1
49
+ basenode_checker('--- { d: 4, a: 1, b: 2, c: 3 }', ["d", "a", "b", "c"])
50
+ end
51
+
52
+ def test_basenode2
53
+ basenode_checker('--- { a: 1, d: 42, b: 2, c: 3 }', ["a", "d", "b", "c"])
54
+ end
55
+
56
+ def test_basenode3
57
+ basenode_checker(
58
+ '--- { a: :"1", d: "foo", b: [2,3], c: {} }', ["a", "d", "b", "c"])
59
+ end
60
+
61
+ end # class BaseNodeExtTest
62
+
63
+ end
@@ -0,0 +1,24 @@
1
+ # Author:: Nicolas Pouillard <ertai@lrde.epita.fr>.
2
+ # Copyright:: Copyright (c) 2004 LRDE. All rights reserved.
3
+ # License:: GNU General Public License (GPL).
4
+ # Revision:: $Id: header 98 2004-09-29 12:07:43Z ertai $
5
+
6
+ require 'ruby_ex'
7
+ require 'yaml'
8
+
9
+ module YAML
10
+
11
+ def self.chop_header ( io )
12
+ aStr = io.gets
13
+ unless aStr =~ /^---/
14
+ io.rewind
15
+ raise Exception, "First line is not valid: `#{aLine}'"
16
+ end
17
+ io.each do |aLine|
18
+ break if aLine =~ /^---/
19
+ aStr += aLine
20
+ end
21
+ YAML::load(aStr)
22
+ end
23
+
24
+ end # module YAML
@@ -0,0 +1,450 @@
1
+ # Author:: Marco Tessari <marco.tessari@epita.fr>.
2
+ # Copyright:: Copyright (c) 2004 TTK Team. All rights reserved.
3
+ # License:: Gnu General Public License.
4
+
5
+ # $LastChangedBy: ertai $
6
+ # $Id: transform.rb 266 2005-06-01 14:27:18Z ertai $
7
+
8
+ # TODO: Raised exception must be completed with a descritpion
9
+ # TODO: Fix bug on Array see test_higher_level_on_array
10
+ # TODO: Comment code.
11
+ # TODO: Write more tests.
12
+ # TODO: Make it stream.
13
+ # TODO: Add the possibility to write '//' to match all node with deep n.
14
+ # TODO: Add the && and ||.
15
+ # TODO: Optimize, skip if no more node to match.
16
+ # TODO: separate dot implemantation.
17
+
18
+ require 'ruby_ex'
19
+ require 'yaml/yregexpath'
20
+
21
+ module YAML
22
+
23
+ class Transformer
24
+ private
25
+
26
+ # FIXME: Implement all ruby types.
27
+ class ::Object # :nodoc:
28
+ def yaml_doc_traverse ( activated )
29
+ raise ArgumentError, "can't traverse class #{self.class}"
30
+ end
31
+ end
32
+
33
+ class ::Hash # :nodoc:
34
+ # FIXME: Are we sure we want to traverse like that? (by stage)
35
+ def yaml_doc_traverse ( activated )
36
+ new_activated = []
37
+ sons = []
38
+ self.each do |key, value|
39
+ activated.each do |n|
40
+ new_activated += n.match(key, value)
41
+ sons << value unless value.is_a?(String)
42
+ end
43
+ end
44
+ activated += new_activated
45
+ sons.yaml_doc_traverse(activated)
46
+ end
47
+ end
48
+
49
+ class ::Array # :nodoc:
50
+ def yaml_doc_traverse ( activated )
51
+ self.each { |e| e.yaml_doc_traverse(activated) }
52
+ end
53
+ end
54
+
55
+ class Node
56
+ attr_reader :name, :values
57
+
58
+ Struct.new( 'Value', :regexp, :remember, :associated )
59
+
60
+ def initialize ( name )
61
+ raise ArgumentError unless name.is_a?(Regexp)
62
+ @name = name
63
+ @values = []
64
+ @uid = @@cpt
65
+ @@cpt += 1
66
+ # Save a node to handle the '#' feature.
67
+ @saved_node_key = nil
68
+ @saved_node_value = nil
69
+ end
70
+ @@cpt = 0
71
+
72
+ # Return the associated nodes of a value
73
+ # Add the value if not present.
74
+ def get_associated ( value, remember=false )
75
+ val = @values.find { |e| e.regexp == value and e.remember == remember }
76
+ if val.nil?
77
+ val = Struct::Value.new(value, remember, [])
78
+ @values << val
79
+ end
80
+ return val.associated
81
+ end
82
+
83
+ # Dottify a node.
84
+ def dottify ( stream )
85
+ # Declare node
86
+ stream << "#{unique_name} [shape=box, color=blue, label=#{self}]\n"
87
+ # Print node links with values
88
+ stream << "#{unique_name} -> { rank=same; "
89
+ @values.each_index { |i| stream << "value#{@uid}_#{i}; " }
90
+ stream << "}\n"
91
+ # Declare each values and links
92
+ @values.each_with_index do |val, i|
93
+ stream << "value#{@uid}_#{i} [shape=box, color=yellow, label=\"/"
94
+ stream << val.regexp.source << '/'
95
+ stream << ' #' if val.remember
96
+ stream << "\"]\n"
97
+ stream << "value#{@uid}_#{i} -> { rank=same; "
98
+ val.associated.each { |match| stream << "#{match.unique_name}; " }
99
+ stream << "} \n"
100
+ val.associated.each { |match| match.dottify(stream) }
101
+ end
102
+ end
103
+
104
+ def match ( key, value )
105
+ return [] unless key =~ @name
106
+ activated = []
107
+ @values.each do |val|
108
+ if ! value.is_a?(String) or value =~ val.regexp
109
+ save_node_set(key, value) if val.remember
110
+ val.associated.each do |n|
111
+ if n.is_a?(Match)
112
+ # There must be a remembered node in the path.
113
+ p self unless has_saved_node?
114
+ n[@saved_node_key, @saved_node_value]
115
+ else
116
+ n.save_node_set(@saved_node_key,
117
+ @saved_node_value) if has_saved_node?
118
+ activated << n
119
+ end
120
+ end
121
+ save_node_reset if val.remember
122
+ end
123
+ end
124
+ save_node_reset
125
+ return activated
126
+ end
127
+
128
+ def save_node_set ( key, value )
129
+ raise RuntimeError unless @saved_node_key.nil?
130
+ @saved_node_key = key
131
+ raise RuntimeError unless @saved_node_value.nil?
132
+ @saved_node_value = value
133
+ end
134
+
135
+ def save_node_reset
136
+ @saved_node_key = nil
137
+ @saved_node_value = nil
138
+ end
139
+
140
+ def has_saved_node?
141
+ return ! (@saved_node_key.nil? and @saved_node_value.nil?)
142
+ end
143
+
144
+ def unique_name
145
+ "node#{@uid}"
146
+ end
147
+
148
+ # Human readable name.
149
+ def to_s
150
+ "\"/#{@name.source}/\""
151
+ end
152
+ end
153
+
154
+ class Root
155
+ attr_reader :children
156
+
157
+ def initialize
158
+ @name = 'root'
159
+ @children = []
160
+ end
161
+
162
+ def dottify ( stream )
163
+ stream << "#{self} [shape=circle, color=red, label=#{self}]\n"
164
+ stream << "#{self} -> { rank=same; "
165
+ @children.each { |e| stream << "#{e.unique_name} " }
166
+ stream << "}\n"
167
+ @children.each { |e| e.dottify(stream) }
168
+ end
169
+
170
+ def to_s
171
+ 'root'
172
+ end
173
+
174
+ alias unique_name to_s
175
+ end
176
+
177
+ class Match
178
+
179
+ def initialize ( name=nil, &block )
180
+ @block = block
181
+ @name = name
182
+ @uid = @@cpt
183
+ @@cpt += 1
184
+ end
185
+ @@cpt = 0
186
+
187
+ def dottify ( stream )
188
+ stream << "match#{@uid} [shape=circle, color=green, label=#{self}]\n"
189
+ end
190
+
191
+ def call ( key, value )
192
+ @block[key, value]
193
+ end
194
+
195
+ alias [] call
196
+
197
+ def unique_name
198
+ "match#{@uid}"
199
+ end
200
+
201
+ def to_s
202
+ @name.nil? ? unique_name : "\"#{@name}\""
203
+ end
204
+ end
205
+
206
+ public
207
+ # Constructor.
208
+ def initialize
209
+ @rootgraph = Root.new
210
+ @graph = []
211
+ @activated = []
212
+ end
213
+
214
+ # Register the given block and compile the path.
215
+ def add ( path, name=nil, &block)
216
+ raise ArgumentError unless (path.kind_of?(YRegexPath))
217
+ raise ArgumentError if (block.nil?)
218
+ current_nodes = path.root ? @rootgraph.children : @graph
219
+ add_segment(path.segments, path.wanted_node_index,
220
+ current_nodes, name, &block)
221
+ end
222
+ alias register add
223
+
224
+ def add_segment ( seg, remember_index, current_nodes, name, &block )
225
+ if (seg.empty?)
226
+ current_nodes << Match.new(name, &block)
227
+ return
228
+ end
229
+ updated = false
230
+ searched_name, searched_value = seg.shift
231
+ searched_value = /.*/ if searched_value.nil?
232
+ current_nodes.collect! do |n|
233
+ if (n.is_a?(Node) and n.name == searched_name)
234
+ updated = true
235
+ update_node(n, searched_value, seg, remember_index, name, &block)
236
+ else
237
+ n
238
+ end
239
+ end
240
+ unless (updated)
241
+ current_nodes << update_node(Node.new(searched_name), searched_value,
242
+ seg, remember_index, name, &block)
243
+ end
244
+ end
245
+ private :add_segment
246
+
247
+ def update_node ( node, value, segment, remember_index, name, &block )
248
+ sons = node.get_associated(value, remember_index == 1)
249
+ add_segment(segment, remember_index - 1, sons, name, &block)
250
+ return node
251
+ end
252
+
253
+ # Print graph in a file in doty format
254
+ def dottify ( filename )
255
+ File.open(filename, 'w') do |f|
256
+ f << <<EOF
257
+ /* Compiled rules graph visualisation by YSLT */
258
+ digraph "Compiled rules" {
259
+ node [style=filled]
260
+ edge [style=solid, arrowtail=none]
261
+ rankdir=LR
262
+ EOF
263
+ @rootgraph.dottify(f)
264
+ @graph.each do |n|
265
+ n.dottify(f)
266
+ end
267
+ f << "}\n"
268
+ end
269
+ end
270
+
271
+ # Reset position to root.
272
+ def reset
273
+ @activated = @rootgraph.children + @graph
274
+ end
275
+
276
+ # Clear compiled rules.
277
+ def clear
278
+ initialize
279
+ end
280
+
281
+ # Traverse an entire YAML tree.
282
+ def traverse ( tree )
283
+ reset
284
+ tree.yaml_doc_traverse(@activated)
285
+ end
286
+
287
+ # Work like XLST.
288
+ # Traverse all tree and return a list of element that match the path.
289
+ def self.traverse ( path, tree, &block )
290
+ p = self.new
291
+ p.add(path, &block)
292
+ p.traverse(tree)
293
+ end
294
+
295
+ end # class Transformer
296
+
297
+ end # module YAML
298
+
299
+ test_section __FILE__ do
300
+
301
+ class Transformer < Test::Unit::TestCase
302
+
303
+ def test_compiled
304
+ # Initialization.
305
+ engine = nil
306
+ str = ''
307
+ assert_nothing_raised { engine = YAML::Transformer.new }
308
+
309
+ # Build.
310
+ assert_nothing_raised do
311
+ engine.add(YAML::YRegexPath.new('/name')) do |key, value|
312
+ assert_instance_of(String, key)
313
+ str += "#{value} is"
314
+ end
315
+ end
316
+ assert_nothing_raised do
317
+ engine.add(YAML::YRegexPath.new('/name=Akli/boss')) do |key, value|
318
+ assert_instance_of(String, key)
319
+ str += ' a boss'
320
+ end
321
+ end
322
+ assert_nothing_raised do
323
+ engine.add(YAML::YRegexPath.new('/jobs/professor=EPITA/class')) do |k,v|
324
+ assert_instance_of(String, k)
325
+ str += e
326
+ end
327
+ end
328
+ assert_nothing_raised do
329
+ engine.add(YAML::YRegexPath.new('toto#titi'), 'unused') do
330
+ str += 'Why am I HERE???'
331
+ end
332
+ end
333
+
334
+ # Display.
335
+ TempPath.new do |dot_file|
336
+ assert_nothing_raised { engine.dottify(dot_file) }
337
+ `dot #{dot_file} 2> /dev/null`
338
+ # assert_equal(0, $?, 'Cannot display the graph')
339
+ end
340
+
341
+ # Traverse.
342
+ tree = {
343
+ 'name' => 'Akli A.',
344
+ 'tel' => '06932359',
345
+ 'address' => '23 boulevard voltaire',
346
+ 'jobs' => [
347
+ {
348
+ 'professor' => 'EPITA',
349
+ 'boss' => 'MindSuite',
350
+ 'god' => 'world',
351
+ }
352
+ ]
353
+ }
354
+ assert_nothing_raised { engine.traverse(tree) }
355
+ assert_equal(str, 'Akli A. is a boss a boss a boss')
356
+ end
357
+
358
+ def test_unique
359
+ names = []
360
+ tree = {
361
+ 'users' => [
362
+ {
363
+ 'name' => 'Sea',
364
+ 'tel' => '111'
365
+ },
366
+ {
367
+ 'name' => 'Sex',
368
+ 'tel' => '222'
369
+ },
370
+ {
371
+ 'name' => 'Sun',
372
+ 'tel' => '333'
373
+ }
374
+ ]
375
+ }
376
+ YAML::Transformer.traverse(YAML::YRegexPath.new('name=Se'), tree) do
377
+ |k,v| names << v
378
+ end
379
+ assert_equal(['Sea', 'Sex'], names)
380
+ end
381
+
382
+ def test_higher_level_on_hash
383
+ email = ''
384
+ tree = {
385
+ 'Name' => 'Foo',
386
+ 'FirstName' => 'Bar',
387
+ 'Emails' => {
388
+ 'home' => 'foo312@coldmail.com',
389
+ 'work' => 'bfoo@staff.com',
390
+ 'edu' => 'bar.foo@school.edu'
391
+ }
392
+ }
393
+ path = YAML::YRegexPath.new('Emails#.*=.*\.edu')
394
+ YAML::Transformer.traverse(path, tree) do |key, value|
395
+ assert_instance_of(Hash, value)
396
+ email += "#{value['home']}"
397
+ end
398
+ assert_equal('foo312@coldmail.com', email)
399
+ end
400
+
401
+ # def test_higher_level_on_array
402
+ # users = []
403
+ # tree = {
404
+ # 'users' => [
405
+ # {
406
+ # 'name' => 'Sea',
407
+ # 'tel' => '111'
408
+ # },
409
+ # {
410
+ # 'name' => 'Sex',
411
+ # 'tel' => '222'
412
+ # },
413
+ # {
414
+ # 'name' => 'Sun',
415
+ # 'tel' => '333'
416
+ # }
417
+ # ]
418
+ # }
419
+ # YAML::Transformer.traverse(YAML::YRegexPath.new('users#name=Se'), tree) do
420
+ # |key, value|
421
+ # assert_instance_of(Array, value)
422
+ # value.each do |e|
423
+ # users << "#{e['name']} (#{e['tel']})"
424
+ # end
425
+ # end
426
+ # p users
427
+ # #assert_equal(['Sea (111)', 'Sex (222)'], users)
428
+ # end
429
+
430
+ def test_errors
431
+ engine = nil
432
+ assert_nothing_raised { engine = YAML::Transformer.new }
433
+
434
+ # Not an YRegexPath gived.
435
+ assert_raise(ArgumentError) do
436
+ engine.add('/name') { |key, value| str += "error" }
437
+ end
438
+
439
+ # # Block with only one parameter.
440
+ # assert_raise(ArgumentError) do
441
+ # engine.add(YAML::YRegexPath.new('/name')) { |key| str += "error" }
442
+ # end
443
+
444
+ # No block ginven.
445
+ assert_raise(ArgumentError) do
446
+ engine.add(YAML::YRegexPath.new('/name'))
447
+ end
448
+ end
449
+ end
450
+ end