ruby_ex 0.1.1
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.
- data/AUTHORS +51 -0
- data/ChangeLog +1763 -0
- data/NEWS +3 -0
- data/README +1 -0
- data/Rakefile +8 -0
- data/SPEC.dyn.yml +10 -0
- data/SPEC.gem.yml +269 -0
- data/SPEC.yml +36 -0
- data/src/abstract.rb +253 -0
- data/src/abstract_node.rb +85 -0
- data/src/algorithms.rb +12 -0
- data/src/algorithms/simulated_annealing.rb +142 -0
- data/src/ask.rb +100 -0
- data/src/attributed_class.rb +303 -0
- data/src/cache.rb +350 -0
- data/src/checkout.rb +12 -0
- data/src/choose.rb +271 -0
- data/src/commands.rb +20 -0
- data/src/commands/command.rb +492 -0
- data/src/commands/datas.rb +16 -0
- data/src/commands/datas/composite.rb +31 -0
- data/src/commands/datas/data.rb +65 -0
- data/src/commands/datas/factory.rb +69 -0
- data/src/commands/datas/temp.rb +26 -0
- data/src/commands/factory.rb +67 -0
- data/src/commands/helpers.rb +81 -0
- data/src/commands/pipe.rb +66 -0
- data/src/commands/runners.rb +16 -0
- data/src/commands/runners/exec.rb +50 -0
- data/src/commands/runners/fork.rb +130 -0
- data/src/commands/runners/runner.rb +140 -0
- data/src/commands/runners/system.rb +57 -0
- data/src/commands/seq.rb +32 -0
- data/src/config_file.rb +95 -0
- data/src/const_regexp.rb +57 -0
- data/src/daemon.rb +135 -0
- data/src/diff.rb +665 -0
- data/src/dlogger.rb +62 -0
- data/src/drb/drb_observable.rb +95 -0
- data/src/drb/drb_observable_pool.rb +27 -0
- data/src/drb/drb_service.rb +44 -0
- data/src/drb/drb_undumped_attributes.rb +56 -0
- data/src/drb/drb_undumped_indexed_object.rb +55 -0
- data/src/drb/insecure_protected_methods.rb +101 -0
- data/src/drb_ex.rb +12 -0
- data/src/dumpable_proc.rb +57 -0
- data/src/filetype.rb +229 -0
- data/src/generate_id.rb +44 -0
- data/src/histogram.rb +222 -0
- data/src/hookable.rb +283 -0
- data/src/hooker.rb +54 -0
- data/src/indexed_node.rb +65 -0
- data/src/io_marshal.rb +99 -0
- data/src/ioo.rb +193 -0
- data/src/labeled_node.rb +62 -0
- data/src/logger_observer.rb +24 -0
- data/src/md5sum.rb +70 -0
- data/src/module/autoload_tree.rb +65 -0
- data/src/module/hierarchy.rb +334 -0
- data/src/module/instance_method_visibility.rb +71 -0
- data/src/node.rb +81 -0
- data/src/object_monitor.rb +143 -0
- data/src/object_monitor_activity.rb +34 -0
- data/src/observable.rb +138 -0
- data/src/observable_pool.rb +291 -0
- data/src/orderedhash.rb +252 -0
- data/src/pp_hierarchy.rb +30 -0
- data/src/random_generators.rb +29 -0
- data/src/random_generators/random_generator.rb +33 -0
- data/src/random_generators/ruby.rb +25 -0
- data/src/ruby_ex.rb +124 -0
- data/src/safe_eval.rb +346 -0
- data/src/sendmail.rb +214 -0
- data/src/service_manager.rb +122 -0
- data/src/shuffle.rb +30 -0
- data/src/spring.rb +134 -0
- data/src/spring_set.rb +134 -0
- data/src/symtbl.rb +108 -0
- data/src/synflow.rb +474 -0
- data/src/thread_mutex.rb +11 -0
- data/src/timeout_ex.rb +79 -0
- data/src/trace.rb +26 -0
- data/src/uri/druby.rb +78 -0
- data/src/uri/file.rb +63 -0
- data/src/uri/ftp_ex.rb +36 -0
- data/src/uri/http_ex.rb +41 -0
- data/src/uri/pgsql.rb +136 -0
- data/src/uri/ssh.rb +87 -0
- data/src/uri/svn.rb +113 -0
- data/src/uri_ex.rb +71 -0
- data/src/verbose_object.rb +70 -0
- data/src/yaml/basenode_ext.rb +63 -0
- data/src/yaml/chop_header.rb +24 -0
- data/src/yaml/transform.rb +450 -0
- data/src/yaml/yregexpath.rb +76 -0
- data/test/algorithms/simulated_annealing_test.rb +102 -0
- data/test/check-pkg-ruby_ex.yml +15 -0
- data/test/check-ruby_ex.yml +12 -0
- data/test/resources/autoload_tree/A.rb +11 -0
- data/test/resources/autoload_tree/B.rb +10 -0
- data/test/resources/autoload_tree/foo/C.rb +18 -0
- data/test/resources/foo.txt +6 -0
- data/test/sanity-suite.yml +12 -0
- data/test/sanity/multiple-requires.yml +20 -0
- data/test/sanity/single-requires.yml +24 -0
- data/test/test-unit-setup.rb +6 -0
- data/test/unit-suite.yml +14 -0
- 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
|