command_builder 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,88 @@
1
+ require 'erb'
2
+ require_relative 'command_definition'
3
+ require_relative 'command_code_names'
4
+ require_relative 'ruby_code_writer'
5
+ require_relative 'node_code_generator'
6
+
7
+ module CommandBuilder
8
+ module CodeGenerator
9
+ class Generator
10
+
11
+ def initialize(options={})
12
+ @definitions_dir = 'command_definitions'
13
+ @command_builders_dir = options[:command_builders_dir] || 'lib/command_builder'
14
+ end
15
+
16
+ def execute
17
+ pattern = File.join @definitions_dir, '*.txt'
18
+ Dir[pattern].each { |f| process_definition f }
19
+ end
20
+
21
+ private
22
+
23
+ def process_definition(definition_file)
24
+ puts "Processing: #{File.basename definition_file}"
25
+ d = CommandDefinition.new File.new(definition_file)
26
+ d.versions.each { |v| process_version d.command, v }
27
+ end
28
+
29
+ def process_version(command, version)
30
+ generate_command_module command, version
31
+ generate_version_module command, version
32
+ end
33
+
34
+ def generate_command_module(command, version)
35
+ code_names = CommandCodeNames.new command, version
36
+
37
+ model = {
38
+ module_name: code_names.command_module_name,
39
+ factory_method_name: code_names.command_factory_method_name,
40
+ command_name: command.command_name
41
+ }
42
+
43
+ template_file = File.expand_path(File.dirname(__FILE__) + '/templates/command_module.erb')
44
+ result_file = File.join @command_builders_dir, "#{code_names.command_factory_method_name}.rb"
45
+ sub_template model, template_file, result_file
46
+ end
47
+
48
+ def generate_version_module(command, version)
49
+ code_names = CommandCodeNames.new command, version
50
+
51
+ model = {
52
+ command_factory_method_signature: code_names.command_factory_method_signature,
53
+ version_factory_method_signature: code_names.version_factory_method_signature,
54
+ create_method_signature: code_names.create_method_signature,
55
+ create_method_call: code_names.create_method_call,
56
+ command_initializer_call: code_names.command_initializer_call,
57
+ command_module_name: code_names.command_module_name,
58
+ version_module_name: code_names.version_module_name,
59
+ version: version,
60
+ command_class: generate_command_class(command),
61
+ factory_method_name: code_names.command_factory_method_name,
62
+ command_factory_method_call: code_names.command_factory_method_call,
63
+ command_name: command.command_name
64
+ }
65
+
66
+ template_file = File.expand_path(File.dirname(__FILE__) + '/templates/version_module.erb')
67
+ result_file = File.join @command_builders_dir, "#{code_names.version_factory_method_name}.rb"
68
+ sub_template model, template_file, result_file
69
+ end
70
+
71
+ def sub_template(model, template_file, result_file)
72
+ template = ERB.new File.read(template_file)
73
+ result = template.result(binding)
74
+ File.open(result_file, 'w') { |f| f.write result }
75
+ end
76
+
77
+ def generate_command_class(command)
78
+ stream = StringIO.new
79
+ writer = RubyCodeWriter.new stream
80
+ writer.indent = 3
81
+ node_code_generator = NodeCodeGenerator.new command, command, writer
82
+ node_code_generator.render
83
+ stream.string
84
+ end
85
+
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,52 @@
1
+ require 'yaml'
2
+ require_relative 'string_extensions'
3
+ require_relative 'node'
4
+
5
+ module CommandBuilder
6
+ module CodeGenerator
7
+ class Command < Node
8
+
9
+ attr_reader :command_name
10
+
11
+ def initialize(command_def)
12
+ hash = Command.command_hash command_def
13
+ command_text = hash.keys[0]
14
+ command_name = Command.command_name command_text
15
+ node_def = command_text[command_name.length, command_text.length]
16
+ super node_def
17
+ Command.process_array hash.values[0], self
18
+ @command_name = command_name
19
+ end
20
+
21
+ def node_name
22
+ @node_name ||= node_alias || @command_name
23
+ end
24
+
25
+ private
26
+
27
+ def Command.command_name(command_text)
28
+ command_text[/^([-\.\w]+)/, 1]
29
+ end
30
+
31
+ def Command.command_hash(command_text)
32
+ yaml = command_text.split(String::NEW_LINE).map { |l| l.gsub(/^( *)(\S.+)$/, '\1- "\2":') }.join String::NEW_LINE
33
+ array = YAML.load yaml
34
+ array[0]
35
+ end
36
+
37
+ def Command.process_array(array, parent_node=nil)
38
+ array ||= []
39
+ array.each { |hash| Command.process_hash hash, parent_node }
40
+ end
41
+
42
+ def Command.process_hash(hash, parent_node)
43
+ hash.each_pair do |node_text, child_nodes|
44
+ node = Node.new " #{node_text}"
45
+ parent_node.child_nodes << node
46
+ process_array child_nodes, node
47
+ end
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,32 @@
1
+ module CommandBuilder
2
+ module CodeGenerator
3
+ class CommandArgument
4
+
5
+ def initialize(arg_text, required=true)
6
+ @arg_text = arg_text
7
+ @required = required
8
+ end
9
+
10
+ def arg_name
11
+ @arg_name ||= @arg_text[/<(\w+)/, 1]
12
+ end
13
+
14
+ def key_value_separator
15
+ @key_value_separator ||= @arg_text[/(\W)\W>/, 1]
16
+ end
17
+
18
+ def delimiter
19
+ @delimiter ||= @arg_text[/(\W)>/, 1]
20
+ end
21
+
22
+ def required?
23
+ @required
24
+ end
25
+
26
+ def optional?
27
+ !required?
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,80 @@
1
+ require_relative 'string_extensions'
2
+
3
+ module CommandBuilder
4
+ module CodeGenerator
5
+ class CommandCodeNames
6
+
7
+ def initialize(command, version=nil)
8
+ @command = command
9
+ @compact_version = version.to_s.gsub('.', '')
10
+ end
11
+
12
+ def command_module_name
13
+ @command.node_name.camelcase
14
+ end
15
+
16
+ def version_module_name
17
+ result = @compact_version =~ /^\d/ ? "V#{@compact_version}" : @compact_version
18
+ result.camelcase
19
+ end
20
+
21
+ def command_factory_method_name
22
+ @command.node_name.downcase.snakecase
23
+ end
24
+
25
+ def command_factory_method_signature
26
+ format_method command_factory_method_name, factory_method_args
27
+ end
28
+
29
+ def version_factory_method_name
30
+ "#{command_factory_method_name}_#{@compact_version.downcase.snakecase}"
31
+ end
32
+
33
+ def version_factory_method_signature
34
+ format_method version_factory_method_name, factory_method_args
35
+ end
36
+
37
+ def create_method_signature
38
+ format_method 'self.create', factory_method_args
39
+ end
40
+
41
+ def create_method_call
42
+ format_method "FluentCommandBuilder::#{command_module_name}::#{version_module_name}.create", initializer_values
43
+ end
44
+
45
+ def command_factory_method_call
46
+ format_method command_factory_method_name, initializer_values
47
+ end
48
+
49
+ def command_initializer_call
50
+ format_method "#{class_name}.new", %w(b) + initializer_values
51
+ end
52
+
53
+ private
54
+
55
+ def format_method(method_name, method_args)
56
+ method_args = method_args.join ', '
57
+ result = method_name
58
+ result << "(#{method_args})" unless method_args.empty?
59
+ result
60
+ end
61
+
62
+ def factory_method_args
63
+ @command.args.map do |arg|
64
+ arg_name = arg.arg_name.snakecase
65
+ arg_name << '=nil' if arg.optional?
66
+ arg_name
67
+ end
68
+ end
69
+
70
+ def initializer_values
71
+ @command.args.map { |arg| arg.arg_name.snakecase }
72
+ end
73
+
74
+ def class_name
75
+ @command.node_name.camelcase
76
+ end
77
+
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,26 @@
1
+ require_relative 'command'
2
+ require_relative 'version'
3
+
4
+ module CommandBuilder
5
+ module CodeGenerator
6
+ class CommandDefinition
7
+
8
+ attr_reader :versions, :command
9
+
10
+ def initialize(stream)
11
+ s = stream.read
12
+ @versions = parse_versions s.first_line
13
+ @command = Command.new s.strip_first_line
14
+ end
15
+
16
+ private
17
+
18
+ def parse_versions(line)
19
+ versions = line.split_and_strip ','
20
+ versions.each { |v| Version.new v }
21
+ versions
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,37 @@
1
+ require_relative 'command_argument'
2
+
3
+ module CommandBuilder
4
+ module CodeGenerator
5
+ class Fragment
6
+
7
+ def initialize(fragment_def)
8
+ @fragment_def = fragment_def
9
+ end
10
+
11
+ def fragment_text
12
+ @fragment_text ||= optional? ? @fragment_def[1, @fragment_def.length - 2] : @fragment_def
13
+ end
14
+
15
+ def required?
16
+ !optional?
17
+ end
18
+
19
+ def optional?
20
+ @optional ||= @fragment_def.start_with?('[') && @fragment_def.end_with?(']')
21
+ end
22
+
23
+ def args
24
+ @args ||= fragment_text.scan(/<.+?>/).flatten.map { |m| CommandArgument.new m, required? }
25
+ end
26
+
27
+ def arg_names
28
+ @arg_names ||= args.map { |a| a.arg_name }
29
+ end
30
+
31
+ def has_args?
32
+ @has_args ||= !args.empty?
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,67 @@
1
+ require_relative 'fragment'
2
+
3
+ module CommandBuilder
4
+ module CodeGenerator
5
+ class Node
6
+
7
+ def initialize(node_def)
8
+ @node_def = node_def
9
+ end
10
+
11
+ def child_nodes
12
+ @child_nodes ||= []
13
+ end
14
+
15
+ def branch?
16
+ !child_nodes.empty?
17
+ end
18
+
19
+ def leaf?
20
+ child_nodes.empty?
21
+ end
22
+
23
+ def node_name
24
+ @node_name ||= node_alias || (starts_with_arg? ? first_arg_name : words_preceding_args)
25
+ end
26
+
27
+ def fragments
28
+ @fragments ||= @node_def.gsub(/ \(.+?\)/, '').gsub(']', ']|').gsub('[', '|[').split('|').compact.map { |f| Fragment.new f }
29
+ end
30
+
31
+ def args
32
+ required_args + optional_args
33
+ end
34
+
35
+ protected
36
+
37
+ def required_args
38
+ select_args { |a| a.required? }
39
+ end
40
+
41
+ def optional_args
42
+ select_args { |a| a.optional? }
43
+ end
44
+
45
+ def starts_with_arg?
46
+ words_preceding_args.empty?
47
+ end
48
+
49
+ def first_arg_name
50
+ @node_def[/<(.+?)>/, 1]
51
+ end
52
+
53
+ def words_preceding_args
54
+ @node_def.gsub(/<.*/, '').gsub(/\W/, ' ').strip
55
+ end
56
+
57
+ def node_alias
58
+ @node_def[/\((.+?)\)/, 1]
59
+ end
60
+
61
+ def select_args
62
+ fragments.map { |f| f.args.map { |a| a if yield a } }.flatten.compact
63
+ end
64
+
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,101 @@
1
+ require_relative 'string_extensions'
2
+ require_relative 'node'
3
+ require_relative 'node_code_names'
4
+
5
+ module CommandBuilder
6
+ module CodeGenerator
7
+ class NodeCodeGenerator
8
+
9
+ def initialize(node, command, writer)
10
+ @node = node
11
+ @command = command
12
+ @writer = writer
13
+ end
14
+
15
+ def render
16
+ render_branch_node if @node.branch?
17
+ render_leaf_node if @node.leaf?
18
+ end
19
+
20
+ def render_method
21
+ @writer.write_method node_code_names.method_name, node_code_names.method_args do
22
+ if @node.branch?
23
+ render_branch_node_method_body
24
+ else
25
+ render_leaf_node_method_body
26
+ @writer.write_line 'yield @b if block_given?'
27
+ @writer.write_line 'self'
28
+ end
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def render_branch_node
35
+ @writer.write_class node_code_names.class_name, 'CommandBase' do
36
+ render_constructor
37
+ @node.child_nodes.each do |n|
38
+ generator = NodeCodeGenerator.new n, @command, @writer
39
+ generator.render_method
40
+ end
41
+ end
42
+ render_child_nodes
43
+ end
44
+
45
+ def render_leaf_node
46
+ render_child_nodes
47
+ end
48
+
49
+ def render_child_nodes
50
+ @node.child_nodes.each do |n|
51
+ generator = NodeCodeGenerator.new n, @command, @writer
52
+ generator.render
53
+ end
54
+ end
55
+
56
+ def render_constructor
57
+ @writer.write_method 'initialize', 'underlying_builder', node_code_names.method_args do
58
+ @writer.write_line 'super underlying_builder'
59
+ render_leaf_node_method_body
60
+ end
61
+ end
62
+
63
+ def render_branch_node_method_body
64
+ args = node_code_names.initializer_values
65
+ @writer.write_line "#{node_code_names.class_name}.new #{args.join ', '}"
66
+ end
67
+
68
+ def render_leaf_node_method_body
69
+ @node.fragments.each { |f| write_append_statement f }
70
+ end
71
+
72
+ def write_append_statement(fragment)
73
+ return if fragment.fragment_text.empty?
74
+ unless_condition = fragment.arg_names.map { |a| "#{a.snakecase}.nil?" }.join ' or '
75
+ statement = "@b.append #{append_arg fragment}"
76
+ statement << " unless #{unless_condition}" if fragment.optional? and fragment.has_args?
77
+ @writer.write_line statement
78
+ end
79
+
80
+ def append_arg(fragment)
81
+ value = fragment.fragment_text.gsub(/<.+?>/) do |m|
82
+ arg = CommandArgument.new m
83
+ is_password = arg.arg_name.downcase.include? 'password'
84
+
85
+ if is_password
86
+ "\#{@b.format_password #{arg.arg_name.snakecase}}"
87
+ else
88
+ format_args = [arg.arg_name.snakecase, [arg.delimiter, arg.key_value_separator].compact.map { |v| "'#{v}'" }].flatten
89
+ "\#{@b.format #{format_args.join ', '}}"
90
+ end
91
+ end
92
+ value.include?('#{') ? %Q["#{value}"] : "'#{value}'"
93
+ end
94
+
95
+ def node_code_names
96
+ @node_code_names ||= NodeCodeNames.new @node
97
+ end
98
+
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,33 @@
1
+ require_relative 'string_extensions'
2
+
3
+ module CommandBuilder
4
+ module CodeGenerator
5
+ class NodeCodeNames
6
+
7
+ def initialize(node)
8
+ @node = node
9
+ end
10
+
11
+ def class_name
12
+ @node.node_name.camelcase
13
+ end
14
+
15
+ def method_name
16
+ @node.node_name.snakecase
17
+ end
18
+
19
+ def method_args
20
+ @node.args.map do |arg|
21
+ arg_name = arg.arg_name.snakecase
22
+ arg_name << '=nil' if arg.optional?
23
+ arg_name
24
+ end
25
+ end
26
+
27
+ def initializer_values
28
+ %w(@b) + @node.args.map { |arg| arg.arg_name.snakecase }
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,62 @@
1
+ module CommandBuilder
2
+ module CodeGenerator
3
+ class RubyCodeWriter
4
+
5
+ INDENT_SPACES = 2
6
+
7
+ attr_accessor :indent
8
+
9
+ def initialize(stream)
10
+ @stream = stream
11
+ @indent = 0
12
+ end
13
+
14
+ def indent
15
+ @indent += 1
16
+ if block_given?
17
+ yield
18
+ dedent
19
+ end
20
+ end
21
+
22
+ def dedent
23
+ @indent -= 1
24
+ end
25
+
26
+ def write(code)
27
+ @stream.print code
28
+ end
29
+
30
+ def write_line(line='')
31
+ @stream.puts line.rjust(line.length + @indent * INDENT_SPACES, ' ')
32
+ end
33
+
34
+ def write_block(line)
35
+ write_line line
36
+ indent
37
+ yield
38
+ dedent
39
+ write_line 'end'
40
+ line
41
+ end
42
+
43
+ def write_module(module_name)
44
+ write_block("module #{module_name}") { yield }
45
+ end
46
+
47
+ def write_class(class_name, base_class_name=nil)
48
+ line = "class #{class_name}"
49
+ line << " < #{base_class_name}" if base_class_name
50
+ write_block(line) { yield }
51
+ end
52
+
53
+ def write_method(method_name, *args)
54
+ args = [args].flatten
55
+ line = "def #{method_name}"
56
+ line << "(#{args.join ', '})" unless args.empty?
57
+ write_block(line) { yield }
58
+ end
59
+
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,34 @@
1
+ class String
2
+
3
+ NEW_LINE = "\n"
4
+
5
+ def camelcase
6
+ words.join
7
+ end
8
+
9
+ def snakecase
10
+ words.join('_').downcase
11
+ end
12
+
13
+ def first_line
14
+ match(/^.*$/)[0]
15
+ end
16
+
17
+ def strip_first_line
18
+ first_line_index = self.index NEW_LINE
19
+ return '' unless first_line_index
20
+ self[first_line_index+1..self.length-1]
21
+ end
22
+
23
+ def split_and_strip(pattern)
24
+ split(pattern).map { |s| s.strip }
25
+ end
26
+
27
+ private
28
+
29
+ def words
30
+ result = self =~ /^[A-Z]+$/ ? downcase : self
31
+ result.gsub(/[\W_]/, ' ').gsub(/[A-Z*]/, ' \0').split(' ').map { |s| s.capitalize }
32
+ end
33
+
34
+ end
@@ -0,0 +1,47 @@
1
+ module CommandBuilder
2
+ module CodeGenerator
3
+ class Version
4
+
5
+ VERSION_REGEX = '(?:\d+\.)+(?:\d+)'
6
+ DELIMITER = '.'
7
+
8
+ attr_accessor :version
9
+
10
+ def initialize(version)
11
+ raise "#{version} is not a valid version." unless Version.is_valid? version
12
+ @version = version
13
+ end
14
+
15
+ def compact
16
+ first 2, ''
17
+ end
18
+
19
+ def first(count, delimiter=DELIMITER)
20
+ to_a.first(count).join(delimiter)
21
+ end
22
+
23
+ def to_s
24
+ @version
25
+ end
26
+
27
+ def to_a
28
+ @version.split DELIMITER
29
+ end
30
+
31
+ def self.match(value)
32
+ exp = Regexp.new VERSION_REGEX
33
+ version = value.scan(exp)[0]
34
+ return unless version
35
+ Version.new(version)
36
+ end
37
+
38
+ private
39
+
40
+ def self.is_valid?(version)
41
+ exp = Regexp.new "^#{VERSION_REGEX}$"
42
+ version.scan(exp)[0] != nil
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1 @@
1
+ require_relative 'code_generator/code_generator'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: command_builder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -16,7 +16,20 @@ email: matthew@matthewriley.name
16
16
  executables: []
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
- files: []
19
+ files:
20
+ - lib/command_builder/code_generator/code_generator.rb
21
+ - lib/command_builder/code_generator/command.rb
22
+ - lib/command_builder/code_generator/command_argument.rb
23
+ - lib/command_builder/code_generator/command_code_names.rb
24
+ - lib/command_builder/code_generator/command_definition.rb
25
+ - lib/command_builder/code_generator/fragment.rb
26
+ - lib/command_builder/code_generator/node.rb
27
+ - lib/command_builder/code_generator/node_code_generator.rb
28
+ - lib/command_builder/code_generator/node_code_names.rb
29
+ - lib/command_builder/code_generator/ruby_code_writer.rb
30
+ - lib/command_builder/code_generator/string_extensions.rb
31
+ - lib/command_builder/code_generator/version.rb
32
+ - lib/command_builder/code_generator.rb
20
33
  homepage:
21
34
  licenses: []
22
35
  post_install_message: