ytools 0.1.0

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.
@@ -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