ytools 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,11 @@
1
+ require 'erb'
2
+
3
+ module YTools
4
+ class YamlObject
5
+
6
+ # ERB bindings
7
+ def erb_binding
8
+ binding
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ require 'scanf'
2
+
3
+ module YTools
4
+ class Version
5
+ CURRENT = File.read(File.dirname(__FILE__) + '/../VERSION')
6
+ MAJOR, MINOR, TINY = CURRENT.scanf('%d.%d.%d')
7
+
8
+ def self.to_s
9
+ CURRENT
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,112 @@
1
+ require 'yaml'
2
+
3
+ module YTools
4
+ class YamlObject
5
+ def self.from_files(files)
6
+ yo = YTools::YamlObject.new
7
+ files.each do |file|
8
+ if File.exists?(file)
9
+ contents = nil
10
+ File.open(file, 'r') { |f| contents = f.read}
11
+ yo.merge(YAML::load(contents))
12
+ end
13
+ end
14
+ yo
15
+ end
16
+
17
+ attr_reader :ypath, :yroot, :yhash
18
+
19
+ def initialize(hash=nil, root=nil, ypath=nil)
20
+ if hash.nil?
21
+ @yhash = {}
22
+ else
23
+ @yhash = hash.dup
24
+ end
25
+
26
+ @ypath = ypath || '/'
27
+ @methods = {}
28
+ @yroot = root || self
29
+
30
+ merge(@yhash)
31
+ end
32
+
33
+ def merge(hash)
34
+ hash.each do |key, value|
35
+ add_entry(key, value)
36
+ end
37
+ end
38
+
39
+ def method_missing(sym, *args, &block)
40
+ method_name = sym.to_s
41
+ hash_key = @methods[method_name]
42
+
43
+ if hash_key.nil?
44
+ raise YTools::PathError.new("Unable to locate attribute '#{relative_ypath}/#{method_name}'")
45
+ else
46
+ @yhash[hash_key]
47
+ end
48
+ end
49
+
50
+ def [](key)
51
+ value = @yhash[key]
52
+ if value.nil?
53
+ raise YTools::PathError.new("Unable to locate key \"#{relative_ypath}@['#{key}']\"")
54
+ end
55
+ value
56
+ end
57
+
58
+ def []=(key, value)
59
+ add_entry(key, value)
60
+ end
61
+
62
+ def to_s
63
+ @yhash.to_s
64
+ end
65
+
66
+ private
67
+ def relative_ypath
68
+ if ypath == '/'
69
+ ''
70
+ else
71
+ ypath
72
+ end
73
+ end
74
+
75
+ def add_entry(key, value)
76
+ method_name = key.to_s.gsub(/-/, '_').gsub(/\./, '_')
77
+ child_path = if method_name =~ /(@|\/|[|])/
78
+ "|#{key}|"
79
+ elsif key =~ /(\.|-)/
80
+ key
81
+ else
82
+ method_name
83
+ end
84
+ hash_key = @methods[method_name]
85
+
86
+ if hash_key.nil? # The item hasn't been defined yet
87
+ value = hashify(value, "#{relative_ypath}/#{child_path}")
88
+ @methods[method_name] = key
89
+ @yhash[key] = value
90
+ else # The item is already defined
91
+ original_value = @yhash[hash_key]
92
+
93
+ if original_value.is_a?(YamlObject) && value.is_a?(Hash)
94
+ original_value.merge(value)
95
+ elsif !value.is_nil?
96
+ @yhash[key] = hashify(value, "#{relative_ypath}/#{child_path}")
97
+ end
98
+ end
99
+ end
100
+
101
+ def hashify(obj, hash_path)
102
+ if obj.is_a?(Hash)
103
+ obj = YamlObject.new(obj, yroot, hash_path)
104
+ elsif obj.is_a?(Array)
105
+ 0.upto(obj.length - 1).each do |i|
106
+ obj[i] = hashify(obj[i], "#{hash_path}[#{i}]")
107
+ end
108
+ end
109
+ obj
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,21 @@
1
+ require 'yaml'
2
+
3
+ module YTools
4
+
5
+ class YReader
6
+ def read(yaml_files)
7
+ root = {}
8
+
9
+ yaml_files.each do |yaml_file|
10
+ if File.exists?(yaml_file)
11
+ yaml = YAML::load(File.read(yaml_file))
12
+ if !yaml.is_a?(Hash)
13
+ raise PathError.new("The yaml file wasn't a hash!")
14
+ end
15
+
16
+
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
data/spec/helpers.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 'ytools/yaml_object'
2
+
3
+ def attempting(&block)
4
+ lambda &block
5
+ end
6
+
7
+ def attempting_to(&block)
8
+ lambda &block
9
+ end
10
+
11
+ module YamlObjectHelper
12
+ def yo(hash)
13
+ YTools::YamlObject.new(hash, nil)
14
+ end
15
+ end
@@ -0,0 +1,45 @@
1
+ require 'helpers'
2
+ require 'ytools/errors'
3
+ require 'ytools/path/executor'
4
+
5
+ module YTools::Path
6
+
7
+ module ExecutorHelper
8
+ def process(path, *files)
9
+ real_files = []
10
+ files.each do |file|
11
+ real_files << File.join(File.dirname(__FILE__), 'yamls', file)
12
+ end
13
+
14
+ Executor.new(path, real_files).process!
15
+ end
16
+ end
17
+
18
+ describe "Executor" do
19
+ include ExecutorHelper
20
+
21
+ it "should be able to find a simple string in a yaml file" do
22
+ process('/a/b', '1.yml').should eql("this")
23
+ end
24
+
25
+ it "should be able to find a numebr in the yaml file" do
26
+ process('/a/b', '2.yml').should eql('42')
27
+ end
28
+
29
+ it "should be able to find an indexed element" do
30
+ process('/a/b[2]', '3.yml').should eql('blah')
31
+ end
32
+
33
+ it "should be able to find descendant selectors" do
34
+ process('/a//c[2]', '4.yml').should eql('2')
35
+ end
36
+
37
+ it "should be able to print all the elements in a list" do
38
+ process('/a//c', '4.yml').should eql("0\n1\n2\n3")
39
+ end
40
+
41
+ it "should be able to pull out the keys for a hash" do
42
+ process('/a', '5.yml').should eql("b\nc\nd")
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,127 @@
1
+ require 'ytools/path/lexer'
2
+
3
+ module YTools::Path
4
+
5
+ module PathHelper
6
+ def lexer(path)
7
+ Lexer.new(path)
8
+ end
9
+
10
+ def check_token(path, type, count=nil)
11
+ count ||= 1
12
+ lex = lexer(path)
13
+
14
+ if count == 0
15
+ return lex.next # '/'
16
+ end
17
+
18
+ tok = nil
19
+ 0.upto(count) do
20
+ tok = lex.next
21
+ end
22
+
23
+ tok.type.should eql(type)
24
+ tok
25
+ end
26
+
27
+ def check_path(path, part, count=nil)
28
+ tok = check_token(path, :path_part, count)
29
+ tok.path.should eql(part)
30
+ end
31
+ end
32
+
33
+ describe "Token struct" do
34
+ it "should have a 'value' method that returns the underlying implementation string/number" do
35
+ Token.new("/42", 1, 2, :number).value.should eql(42)
36
+ end
37
+ end
38
+
39
+ describe "Lexer" do
40
+ include PathHelper
41
+
42
+ it "should be able to parse a simple path separator" do
43
+ tok = check_token('/', :path_separator, 0)
44
+ tok.offset.should eql(0)
45
+ tok.length.should eql(1)
46
+ end
47
+
48
+ it "should be able to parse a '[' character" do
49
+ check_token('/[', :lbrace)
50
+ end
51
+
52
+ it "should be able to parse a ']' character" do
53
+ check_token('/]', :rbrace)
54
+ end
55
+
56
+ it "should correctly parse a number" do
57
+ tok = check_token('/42', :number)
58
+ tok.value.should eql(42)
59
+ end
60
+
61
+ it "should know the position of the number" do
62
+ tok = check_token('/42', :number)
63
+ tok.length.should eql(2)
64
+ tok.offset.should eql(1)
65
+ end
66
+
67
+ it "should always return nil when examining past the end of the line." do
68
+ lex = lexer('/')
69
+ lex.next.should_not be(nil)
70
+ lex.next.should be(nil)
71
+ end
72
+
73
+ it "should return nil on indexing past the end of the path." do
74
+ lex = lexer('/this is a path')
75
+ lex[50].should be(nil)
76
+ end
77
+
78
+ it "should be able to find a basic path part" do
79
+ check_path('/path_token', 'path_token')
80
+ end
81
+
82
+ it "should be able to find a path using bars" do
83
+ check_path('/|complex/path/parts|', 'complex/path/parts')
84
+ end
85
+
86
+ it "should fail when a barred path isn't closed" do
87
+ attempting_to { check_path('/|noend bar', nil) }.should raise_error(YTools::PathError, /bar/)
88
+ end
89
+
90
+ it "should handle the basic backslash escape of special characters" do
91
+ check_path("/bath\\|here", 'bath|here')
92
+ end
93
+
94
+ it "should fail when a backslash character is unescaped" do
95
+ attempting_to { check_path("/back\\slash", nil) }.should raise_error(YTools::PathError, /backslash/)
96
+ end
97
+
98
+ it "should fail when the backslash character is last" do
99
+ attempting_to { check_path("/back\\", nil) }.should raise_error(YTools::PathError, /Last/)
100
+ end
101
+
102
+ it "should succeed to parse multiple paths according to slashes" do
103
+ check_path('/this/goes', 'goes', 3)
104
+ end
105
+
106
+ it "should be able to parse numbers within and at the end of path parts" do
107
+ check_path('/to2be42', 'to2be42')
108
+ end
109
+
110
+ it "should handle negative numbers" do
111
+ tok = check_token('/-42', :number)
112
+ tok.offset.should eql(0)
113
+ tok.value.should eql(-42)
114
+ end
115
+
116
+ it "should be able to peek into the token stream" do
117
+ lex = lexer('/this/that/other')
118
+ lex.peek.type.should eql(:path_separator)
119
+ lex.peek(1).value.should eql("this")
120
+ lex.peek(2).type.should eql(:path_separator)
121
+
122
+ lex.next.type.should eql(:path_separator)
123
+ lex.next.value.should eql("this")
124
+ lex.next.type.should eql(:path_separator)
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,104 @@
1
+ require 'helpers'
2
+ require 'ytools/errors'
3
+ require 'ytools/path/lexer'
4
+ require 'ytools/path/parser'
5
+
6
+ module YTools::Path
7
+
8
+ module PathParserHelper
9
+ # Call private methods on the parser
10
+ def pm(path, method=nil)
11
+ method ||= :parse!
12
+ parser = Parser.new(path)
13
+ parser.send(method)
14
+ end
15
+ end
16
+
17
+ describe "Path Parser" do
18
+ include PathParserHelper
19
+
20
+ describe "while doing index operations" do
21
+ it "should fail when the number is nil" do
22
+ attempting { pm("[", :index_selector) }.should raise_error(ParseError, /Missing number/)
23
+ end
24
+
25
+ it "should fail when the index isn't a number" do
26
+ attempting { pm("[not_a_number]", :index_selector) }.should raise_error(ParseError, /Only numbers/)
27
+ end
28
+
29
+ it "should fail when the RBRACE isn't present" do
30
+ attempting { pm("[1", :index_selector) }.should raise_error(ParseError, /Unclosed/)
31
+ end
32
+
33
+ it "should fail when the final index token isn't an RBRACE" do
34
+ attempting { pm("[1/", :index_selector) }.should raise_error(ParseError, /Missing '\]'/)
35
+ end
36
+
37
+ it "should be able to pull an index selector" do
38
+ pm("[23]", :index_selector).index.should eql(23)
39
+ end
40
+ end
41
+
42
+ describe "while doing path node operations" do
43
+ it "should be able to pull the node name" do
44
+ pm("node", :path_node).child.should eql('node')
45
+ end
46
+
47
+ it "should be able to chain an index selector" do
48
+ pm("node[1]", :path_node).subselector.index.should eql(1)
49
+ end
50
+ end
51
+
52
+ describe "while doing child selector operations" do
53
+ it "should be able to find descendent selectors" do
54
+ pm("//descendant", :child_selector).should be_a(DescendantSelector)
55
+ end
56
+
57
+ it "should be able to find simple child selectors" do
58
+ pm("/child", :child_selector).should be_a(ChildSelector)
59
+ end
60
+
61
+ it "should fail when the path doesn't begin with a '/'" do
62
+ attempting_to { pm("child", :child_selector) }.should raise_error(ParseError, /Missing/)
63
+ end
64
+
65
+ it "should fail when the slash doesn't have anything after it" do
66
+ attempting_to { pm("/", :child_selector) }.should raise_error(ParseError, /Unfinished/)
67
+ end
68
+ end
69
+
70
+ describe "while doing root operations" do
71
+ it "should be able to set the root selector correctly" do
72
+ pm("/a", :root_selector).should be_a(RootSelector)
73
+ end
74
+
75
+ it "should not set the root selector ond descendant paths" do
76
+ pm("//a", :root_selector).should be_a(DescendantSelector)
77
+ end
78
+
79
+ it "should not set the root selector on a basic path" do
80
+ pm("a", :root_selector).should be_a(ChildSelector)
81
+ end
82
+
83
+ it "should correctly identify the child selector" do
84
+ pm("/a[1]", :root_selector).subselector.child.should eql('a')
85
+ pm("/a[1]", :root_selector).subselector.subselector.index.should eql(1)
86
+ end
87
+ end
88
+
89
+ describe "while doing a full parse of a line" do
90
+ it "should be able to pull out all of the selectors" do
91
+ sel = pm("/a/b[2]//c")
92
+ sel.should be_a(RootSelector)
93
+ sel.subselector.should be_a(ChildSelector)
94
+ sel.subselector.subselector.should be_a(ChildSelector)
95
+ sel.subselector.subselector.subselector.should be_a(IndexSelector)
96
+ sel.subselector.subselector.subselector.subselector.should be_a(DescendantSelector)
97
+ end
98
+
99
+ it "should return nil on an empty string" do
100
+ pm("").should be(nil)
101
+ end
102
+ end
103
+ end
104
+ end