ytools 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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