command_builder 0.0.2 → 0.0.3

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,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: