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,135 @@
1
+ require 'ytools/errors'
2
+ require 'ytools/path/lexer'
3
+ require 'ytools/path/selectors'
4
+
5
+ module YTools::Path
6
+
7
+ =begin
8
+ Yaml Path Spec:
9
+ ---------------
10
+
11
+ yaml_path:
12
+ child_selector (child_selector)* <-- '/' path_node is a root
13
+ | path_node (child_selector)*
14
+ ;
15
+
16
+ child_selector:
17
+ '/' path_node
18
+ | '//' path_node
19
+ ;
20
+
21
+ path_node:
22
+ PATH_PART index?
23
+ | NUMBER index?
24
+ ;
25
+
26
+ index:
27
+ LBRACE NUMBER RBRACE
28
+ ;
29
+
30
+ =end
31
+
32
+ class ParseError < Exception
33
+ attr_reader :token
34
+
35
+ def initialize(message, token)
36
+ super(message)
37
+ @token = token
38
+ end
39
+ end
40
+
41
+ class Parser
42
+
43
+ def initialize(path)
44
+ @lexer = Lexer.new(path)
45
+ @path = path
46
+ end
47
+
48
+ def parse!
49
+ selector = nil
50
+
51
+ if @lexer.has_next?
52
+ selector = root_selector
53
+ end
54
+
55
+ while @lexer.has_next?
56
+ selector.chain(child_selector)
57
+ end
58
+
59
+ selector
60
+ end
61
+
62
+ private
63
+ def root_selector
64
+ if @lexer.peek.type == :path_separator
65
+ subselector = child_selector
66
+ if subselector.is_a?(ChildSelector)
67
+ RootSelector.new.chain(subselector)
68
+ else
69
+ subselector
70
+ end
71
+ else
72
+ path_node(false)
73
+ end
74
+ end
75
+
76
+ def child_selector
77
+ slash = @lexer.next
78
+
79
+ if slash.nil? || slash.type != :path_separator
80
+ raise ParseError.new("Missing a path separator '/'", slash)
81
+ end
82
+
83
+ if @lexer.peek.nil?
84
+ raise ParseError.new("Unfinished child path expression", slash)
85
+ end
86
+
87
+ if @lexer.peek.type == :path_separator
88
+ @lexer.next # Pop off the separator
89
+ path_node(true)
90
+ else
91
+ path_node(false)
92
+ end
93
+ end
94
+
95
+ def path_node(descendant=nil)
96
+ node = @lexer.next
97
+
98
+ if node.type != :number && node.type != :path_part
99
+ raise ParseError.new("Unexpected path node type, number or string only", node)
100
+ end
101
+
102
+ sel = if descendant
103
+ DescendantSelector.new(node.value)
104
+ else
105
+ ChildSelector.new(node.value)
106
+ end
107
+
108
+ if !@lexer.peek.nil? && @lexer.peek.type == :lbrace
109
+ sel.chain(index_selector)
110
+ end
111
+
112
+ sel
113
+ end
114
+
115
+ def index_selector
116
+ lbrace = @lexer.next
117
+
118
+ number = @lexer.next
119
+ if number.nil?
120
+ raise ParseError.new("Missing number in unclosed index selector", lbrace)
121
+ elsif number.type != :number
122
+ raise ParseError.new("Only numbers are allowed in indices", lbrace)
123
+ end
124
+
125
+ rbrace = @lexer.next
126
+ if rbrace.nil?
127
+ raise ParseError.new("Unclosed ']' for index selector", lbrace)
128
+ elsif rbrace.type != :rbrace
129
+ raise ParseError.new("Missing ']' character for index", rbrace)
130
+ end
131
+
132
+ IndexSelector.new(number.value)
133
+ end
134
+ end # Parser
135
+ end
@@ -0,0 +1,132 @@
1
+ require 'ytools/errors'
2
+ require 'ytools/yaml_object'
3
+
4
+ module YTools::Path
5
+
6
+ class Selector
7
+ attr_reader :subselector
8
+ #alias :chain :<<
9
+
10
+ def chain(selector)
11
+ if !chained?
12
+ @subselector = selector
13
+ else
14
+ @subselector.chain(selector)
15
+ end
16
+ self
17
+ end
18
+
19
+ def chained?
20
+ !@subselector.nil?
21
+ end
22
+ end
23
+
24
+ class ChildSelector < Selector
25
+ attr_reader :child
26
+
27
+ def initialize(child)
28
+ @child = child
29
+ end
30
+
31
+ def select(yaml)
32
+ if yaml.yhash.has_key?(child)
33
+ value = yaml[child]
34
+ if chained?
35
+ subselector.select(value)
36
+ else
37
+ value
38
+ end
39
+ else
40
+ nil
41
+ end
42
+ end
43
+ end
44
+
45
+ class IndexSelector < Selector
46
+ attr_reader :index
47
+
48
+ def initialize(index)
49
+ @index = index
50
+ end
51
+
52
+ def select(array)
53
+ if array.respond_to?(:[])
54
+ value = array[index]
55
+ if chained?
56
+ subselector.select(value)
57
+ else
58
+ value
59
+ end
60
+ else
61
+ nil
62
+ end
63
+ end
64
+ end
65
+
66
+ class RootSelector < Selector
67
+ def select(yaml)
68
+ if yaml.is_a?(YTools::YamlObject)
69
+ value = yaml.yroot
70
+ if chained?
71
+ subselector.select(value)
72
+ else
73
+ value
74
+ end
75
+ else
76
+ nil
77
+ end
78
+ end
79
+ end
80
+
81
+ class DescendantSelector < Selector
82
+ attr_reader :match
83
+
84
+ def initialize(match)
85
+ @match = match
86
+ end
87
+
88
+ def select(yaml)
89
+ inner_select(yaml, [])
90
+ end
91
+
92
+ private
93
+ def inner_select(yaml, results)
94
+ yaml.yhash.each do |key, value|
95
+ if key == match
96
+ key_select(value, results)
97
+ end
98
+ value_select(value, results)
99
+ end
100
+
101
+ if results.length == 1
102
+ results[0]
103
+ else
104
+ results
105
+ end
106
+ end
107
+
108
+ def key_select(value, results)
109
+ # Filter through the chain, if present
110
+ if chained?
111
+ nvalue = subselector.select(value)
112
+ if !nvalue.nil?
113
+ results << nvalue
114
+ end
115
+ else
116
+ results << value
117
+ end
118
+ end
119
+
120
+ def value_select(value, results)
121
+ if value.is_a?(YTools::YamlObject)
122
+ inner_select(value, results)
123
+ elsif value.is_a?(Array)
124
+ value.each do |v|
125
+ if v.is_a?(YTools::YamlObject)
126
+ inner_select(v, results)
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,108 @@
1
+ require 'optparse'
2
+ require 'ytools/basecli'
3
+ require 'ytools/errors'
4
+ require 'ytools/templates/executor'
5
+
6
+ module YTools::Templates
7
+ class CLI < YTools::BaseCLI
8
+ protected
9
+ def parse(args)
10
+ OptionParser.new do |opts|
11
+ opts.banner = "Usage: #{File.basename($0)} [OPTIONS] YAML_FILES"
12
+ opts.separator <<EOF
13
+ Description:
14
+ This tool uses an ERB template file and a set of YAML files to
15
+ generate a merged file. For convenience, all of the keys in
16
+ hashes in regular YAML can work like methods in the ERB templates.
17
+ Thus, the YAML "{ 'a' : {'b' : 3 } }" could be used in an
18
+ ERB template with "<%= a.b %>" instead of the more verbose hash
19
+ syntax. Indeed, the root hash values can only be accessed by
20
+ those method attributes, because the root YAML context object
21
+ is simply assumed.
22
+
23
+ It accepts multiple yaml files, and will merge their contents in the
24
+ order in which they are given. Thus, files listed later, if their
25
+ keys conflict with ones listed earlier, override the earlier listed
26
+ values. If you pass in files that don't exist, no error will be
27
+ raised unless the '--strict' flag is passed.
28
+
29
+ Check out the '--examples' flag for more details.
30
+
31
+ Options:
32
+ EOF
33
+ opts.on('-t', '--template ERB',
34
+ "The ERB template file to use for generation") do |t|
35
+ options[:template] = t
36
+ end
37
+ opts.on('-o', '--output FILE',
38
+ "Write the generated output to a file instead",
39
+ "of STDOUT") do |o|
40
+ options[:output] = o
41
+ end
42
+ opts.on('-s', '--strict',
43
+ "Checks to make sure all of the YAML files",
44
+ "exist before proceeding.") do |s|
45
+ options[:strict] = true
46
+ end
47
+ opts.separator ""
48
+
49
+ opts.on('-e', '--examples',
50
+ "Show some examples on how to use the",
51
+ "path syntax.") do
52
+ print_examples(File.dirname(__FILE__))
53
+ end
54
+ opts.on('-v', '--version',
55
+ "Show the version information") do |v|
56
+ print_version
57
+ end
58
+ opts.on('-d', '--debug',
59
+ "Prints out the merged yaml as a",
60
+ "ruby object to STDERR.") do |d|
61
+ options[:debug] = true
62
+ end
63
+ opts.on('-h', '--help',
64
+ "Show this help message.") do
65
+ puts opts
66
+ exit 0
67
+ end
68
+ end.parse!(args)
69
+ end
70
+
71
+ def validate(args)
72
+ if options[:template].nil?
73
+ raise YTools::ConfigurationError.new("No template file was indicated")
74
+ end
75
+
76
+ if !File.exists?(options[:template])
77
+ raise YTools::ConfigurationError.new("Unable to locate the template file: #{options[:template]}")
78
+ end
79
+
80
+ if options[:output] &&
81
+ !File.exists?(File.dirname(options[:output]))
82
+ raise YTools::ConfigurationError.new("The output directory doesn't exist: #{option[:output]}")
83
+ end
84
+
85
+ if args.length == 0
86
+ raise YTools::ConfigurationError.new("No YAML files were given")
87
+ end
88
+
89
+ if options[:strict]
90
+ args.each do |file|
91
+ if !File.exists?(file)
92
+ raise YTools::ConfigurationError.new("Unable to locate YAML file: #{file}")
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ def execute(args)
99
+ executor = Executor.new(options[:template], args)
100
+
101
+ if options[:debug]
102
+ STDERR.puts executor.yaml_object.to_s
103
+ end
104
+
105
+ executor.write!(options[:output])
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,52 @@
1
+ Assuming a YAML file like the following:
2
+
3
+ ---
4
+ this:
5
+ is:
6
+ - a
7
+ - very
8
+ - simple
9
+ yaml: file
10
+ but: It's
11
+ pretty:
12
+ easy: 42
13
+ to-read: 'right?'
14
+ ---
15
+
16
+ You can then create a template to access these values:
17
+
18
+ ---
19
+ You can write out <%= this.is[0] %> simple template
20
+ <%= this.yaml %> that uses the YAML as its bound object.
21
+ It's fairly straight forward, <%= this.pretty['to-read']%>
22
+ ---
23
+
24
+ It would output:
25
+
26
+ ---
27
+ You can write out a simple template
28
+ file that uses the YAML as its bound object.
29
+ It's fairly straight forward, right?
30
+ ---
31
+
32
+ You can, of course use all of the syntax available
33
+ in a regular ERB template so you could do something
34
+ like:
35
+
36
+ ---
37
+ <% 0.upto(this.pretty.easy) do |i| %>
38
+ <% if i == 42 %>
39
+ The secret of the universe is <%= i %>!
40
+ <%= this.but %> <%= this.is[2] %> <%= this.is[3] %>
41
+ <%= this.pretty['to-read'] %>
42
+ <% end %>
43
+ <% end %>
44
+ ---
45
+
46
+ That would output:
47
+
48
+ ---
49
+ The secret of the universe is 42!
50
+ It's very simple
51
+ right?
52
+ ---
@@ -0,0 +1,28 @@
1
+ require 'erb'
2
+ require 'ytools/yaml_object'
3
+ require 'ytools/templates/yaml_object'
4
+
5
+ module YTools::Templates
6
+ class Executor
7
+ attr_reader :template, :yaml_object
8
+
9
+ def initialize(template, files)
10
+ @template = template
11
+ @yaml_object = YTools::YamlObject.from_files(files)
12
+ end
13
+
14
+ def write!(outfile)
15
+ tcontents = nil
16
+ File.open(template, 'r') { |f| tcontents = f.read}
17
+
18
+ generator = ERB.new(tcontents)
19
+ output = generator.result(yaml_object.erb_binding)
20
+
21
+ if outfile
22
+ File.open(outfile, 'w') {|f| f.write(output)}
23
+ else
24
+ puts output
25
+ end
26
+ end
27
+ end
28
+ end