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.
- data/Rakefile +52 -0
- data/bin/ypath +5 -0
- data/bin/ytemplates +5 -0
- data/lib/VERSION +1 -0
- data/lib/ytools/basecli.rb +67 -0
- data/lib/ytools/errors.rb +7 -0
- data/lib/ytools/path/cli.rb +103 -0
- data/lib/ytools/path/examples.txt +64 -0
- data/lib/ytools/path/executor.rb +58 -0
- data/lib/ytools/path/lexer.rb +166 -0
- data/lib/ytools/path/parser.rb +135 -0
- data/lib/ytools/path/selectors.rb +132 -0
- data/lib/ytools/templates/cli.rb +108 -0
- data/lib/ytools/templates/examples.txt +52 -0
- data/lib/ytools/templates/executor.rb +28 -0
- data/lib/ytools/templates/yaml_object.rb +11 -0
- data/lib/ytools/version.rb +12 -0
- data/lib/ytools/yaml_object.rb +112 -0
- data/lib/ytools/yreader.rb +21 -0
- data/spec/helpers.rb +15 -0
- data/spec/path/executor_spec.rb +45 -0
- data/spec/path/lexer_spec.rb +127 -0
- data/spec/path/parser_spec.rb +104 -0
- data/spec/path/selectors_spec.rb +123 -0
- data/spec/path/yamls/1.yml +2 -0
- data/spec/path/yamls/2.yml +2 -0
- data/spec/path/yamls/3.yml +6 -0
- data/spec/path/yamls/4.yml +12 -0
- data/spec/path/yamls/5.yml +4 -0
- data/spec/yaml_object_spec.rb +61 -0
- metadata +118 -0
@@ -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,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
|