command_mapper-gen 0.1.0.pre1

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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ruby.yml +27 -0
  3. data/.gitignore +10 -0
  4. data/.rspec +1 -0
  5. data/.yardopts +1 -0
  6. data/ChangeLog.md +20 -0
  7. data/Gemfile +17 -0
  8. data/LICENSE.txt +20 -0
  9. data/README.md +145 -0
  10. data/Rakefile +15 -0
  11. data/bin/command_mapper-gen +7 -0
  12. data/commnad_mapper-gen.gemspec +61 -0
  13. data/examples/grep.rb +63 -0
  14. data/gemspec.yml +26 -0
  15. data/lib/command_mapper/gen/arg.rb +43 -0
  16. data/lib/command_mapper/gen/argument.rb +53 -0
  17. data/lib/command_mapper/gen/cli.rb +233 -0
  18. data/lib/command_mapper/gen/command.rb +202 -0
  19. data/lib/command_mapper/gen/exceptions.rb +9 -0
  20. data/lib/command_mapper/gen/option.rb +66 -0
  21. data/lib/command_mapper/gen/option_value.rb +23 -0
  22. data/lib/command_mapper/gen/parsers/common.rb +49 -0
  23. data/lib/command_mapper/gen/parsers/help.rb +351 -0
  24. data/lib/command_mapper/gen/parsers/man.rb +80 -0
  25. data/lib/command_mapper/gen/parsers/options.rb +127 -0
  26. data/lib/command_mapper/gen/parsers/usage.rb +141 -0
  27. data/lib/command_mapper/gen/parsers.rb +2 -0
  28. data/lib/command_mapper/gen/task.rb +90 -0
  29. data/lib/command_mapper/gen/types/enum.rb +30 -0
  30. data/lib/command_mapper/gen/types/key_value.rb +34 -0
  31. data/lib/command_mapper/gen/types/list.rb +34 -0
  32. data/lib/command_mapper/gen/types/map.rb +36 -0
  33. data/lib/command_mapper/gen/types/num.rb +18 -0
  34. data/lib/command_mapper/gen/types/str.rb +48 -0
  35. data/lib/command_mapper/gen/types.rb +6 -0
  36. data/lib/command_mapper/gen/version.rb +6 -0
  37. data/lib/command_mapper/gen.rb +2 -0
  38. data/spec/argument_spec.rb +92 -0
  39. data/spec/cli_spec.rb +269 -0
  40. data/spec/command_spec.rb +316 -0
  41. data/spec/option_spec.rb +85 -0
  42. data/spec/option_value_spec.rb +20 -0
  43. data/spec/parsers/common_spec.rb +616 -0
  44. data/spec/parsers/help_spec.rb +612 -0
  45. data/spec/parsers/man_spec.rb +158 -0
  46. data/spec/parsers/options_spec.rb +802 -0
  47. data/spec/parsers/usage_spec.rb +1175 -0
  48. data/spec/spec_helper.rb +6 -0
  49. data/spec/task_spec.rb +69 -0
  50. data/spec/types/enum_spec.rb +45 -0
  51. data/spec/types/key_value_spec.rb +36 -0
  52. data/spec/types/list_spec.rb +36 -0
  53. data/spec/types/map_spec.rb +48 -0
  54. data/spec/types/str_spec.rb +70 -0
  55. metadata +133 -0
@@ -0,0 +1,90 @@
1
+ require 'rake/tasklib'
2
+
3
+ module CommandMapper
4
+ module Gen
5
+ #
6
+ # Defines a `command_mapper:gen` task which automatically generates a
7
+ # command class file.
8
+ #
9
+ # require 'command_mapper/gen/task'
10
+ # CommandMapper::Gen::Task.new('grep','lib/path/to/grep.rb')
11
+ #
12
+ # $ rake command_mapper:gen
13
+ #
14
+ class Task < Rake::TaskLib
15
+
16
+ # The command name or path to the command.
17
+ #
18
+ # @return [String]
19
+ attr_accessor :command_name
20
+
21
+ # The output file path.
22
+ #
23
+ # @return [String]
24
+ attr_accessor :output
25
+
26
+ # The parser to invoke.
27
+ #
28
+ # @return [:help, :man, nil]
29
+ attr_accessor :parser
30
+
31
+ #
32
+ # Initializes the task.
33
+ #
34
+ # @param [String] command_name
35
+ # The command name or path to the command.
36
+ #
37
+ # @param [String] output
38
+ # The output file path.
39
+ #
40
+ # @param [:help, :man, nil] parser
41
+ # The optional parser to target.
42
+ #
43
+ # @yield [task]
44
+ # If a block is given, it will be yielded to before the rake task has
45
+ # been defined.
46
+ #
47
+ # @yieldparam [Task] task
48
+ # The newly created task.
49
+ #
50
+ def initialize(command_name,output, parser: nil)
51
+ @command_name = command_name
52
+ @output = output
53
+
54
+ @parser = parser
55
+
56
+ yield self if block_given?
57
+ define
58
+ end
59
+
60
+ #
61
+ # Defines the `command_mapper:gen` task and output file's task.
62
+ #
63
+ def define
64
+ output_dir = File.dirname(@output)
65
+
66
+ directory(output_dir)
67
+ file(@output => output_dir) do
68
+ generate
69
+ end
70
+
71
+ desc "Generates the #{@output} file"
72
+ task 'command_mapper:gen' => @output
73
+ end
74
+
75
+ #
76
+ # Generates the {#output} file.
77
+ #
78
+ def generate
79
+ args = ["--output", @output]
80
+
81
+ if @parser
82
+ args << '--parser' << @parser.to_s
83
+ end
84
+
85
+ sh "command_mapper-gen", *args, @command_name
86
+ end
87
+
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,30 @@
1
+ module CommandMapper
2
+ module Gen
3
+ module Types
4
+ class Enum
5
+
6
+ # @return [Array<Symbol>]
7
+ attr_reader :values
8
+
9
+ #
10
+ # Initializes the enum type.
11
+ #
12
+ # @param [Array<Symbol>] values
13
+ #
14
+ def initialize(values)
15
+ @values = values
16
+ end
17
+
18
+ #
19
+ # Converts the map type to Ruby source code.
20
+ #
21
+ # @return [String]
22
+ #
23
+ def to_ruby
24
+ "Enum[#{@values.map(&:inspect).join(', ')}]"
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,34 @@
1
+ module CommandMapper
2
+ module Gen
3
+ module Types
4
+ class KeyValue
5
+
6
+ # @return [String]
7
+ attr_reader :separator
8
+
9
+ #
10
+ # Initializes the key-value type.
11
+ #
12
+ # @param [String] separator
13
+ # The separator character.
14
+ #
15
+ def initialize(separator: '=')
16
+ @separator = separator
17
+ end
18
+
19
+ #
20
+ # Converts the key-value type to Ruby source code.
21
+ #
22
+ # @return [String]
23
+ #
24
+ def to_ruby
25
+ ruby = "KeyValue.new("
26
+ ruby << "separator: #{@separator.inspect}" unless @separator == '='
27
+ ruby << ")"
28
+ ruby
29
+ end
30
+
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,34 @@
1
+ module CommandMapper
2
+ module Gen
3
+ module Types
4
+ class List
5
+
6
+ # @return [String]
7
+ attr_reader :separator
8
+
9
+ #
10
+ # Initializes the list type.
11
+ #
12
+ # @param [String] separator
13
+ # The separator character.
14
+ #
15
+ def initialize(separator: ',')
16
+ @separator = separator
17
+ end
18
+
19
+ #
20
+ # Converts the list type to Ruby source code.
21
+ #
22
+ # @return [String]
23
+ #
24
+ def to_ruby
25
+ ruby = "List.new("
26
+ ruby << "separator: #{@separator.inspect}" unless @separator == ','
27
+ ruby << ")"
28
+ ruby
29
+ end
30
+
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,36 @@
1
+ module CommandMapper
2
+ module Gen
3
+ module Types
4
+ class Map
5
+
6
+ # @return [Hash{Object => String}]
7
+ attr_reader :map
8
+
9
+ #
10
+ # Initializes the map type.
11
+ #
12
+ # @param [Hash{Object => String}] map
13
+ #
14
+ def initialize(map)
15
+ @map = map
16
+ end
17
+
18
+ #
19
+ # Converts the map type to Ruby source code.
20
+ #
21
+ # @return [String]
22
+ #
23
+ def to_ruby
24
+ pairs = []
25
+
26
+ @map.each do |value,string|
27
+ pairs << "#{value.inspect} => #{string.inspect}"
28
+ end
29
+
30
+ return "Map.new(#{pairs.join(', ')})"
31
+ end
32
+
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,18 @@
1
+ module CommandMapper
2
+ module Gen
3
+ module Types
4
+ class Num
5
+
6
+ #
7
+ # Converts the num type to Ruby source code.
8
+ #
9
+ # @return [String]
10
+ #
11
+ def to_ruby
12
+ "Num.new"
13
+ end
14
+
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,48 @@
1
+ module CommandMapper
2
+ module Gen
3
+ module Types
4
+ class Str
5
+
6
+ # The `allow_empty` keyword.
7
+ #
8
+ # @return [Boolean, nil]
9
+ attr_reader :allow_empty
10
+
11
+ # The `allow_blank` keyword.
12
+ #
13
+ # @return [Boolean, nil]
14
+ attr_reader :allow_blank
15
+
16
+ #
17
+ # Initializes the value type.
18
+ #
19
+ # @param [Boolean, nil] allow_empty
20
+ # The `allow_empty` keyword value.
21
+ #
22
+ # @param [Boolean, nil] allow_blank
23
+ # The `allow_blank` keyword value.
24
+ #
25
+ def initialize(allow_empty: nil, allow_blank: nil)
26
+ @allow_empty = allow_empty
27
+ @allow_blank = allow_blank
28
+ end
29
+
30
+ #
31
+ # Converts the value type to Ruby source code.
32
+ #
33
+ # @return [String, nil]
34
+ #
35
+ def to_ruby
36
+ unless (@allow_empty.nil? && @allow_blank.nil?)
37
+ keywords = []
38
+
39
+ keywords << "allow_empty: #{@allow_empty.inspect}" unless @allow_empty.nil?
40
+ keywords << "allow_blank: #{@allow_blank.inspect}" unless @allow_blank.nil?
41
+ keywords.join(', ')
42
+ end
43
+ end
44
+
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,6 @@
1
+ require 'command_mapper/gen/types/str'
2
+ require 'command_mapper/gen/types/num'
3
+ require 'command_mapper/gen/types/list'
4
+ require 'command_mapper/gen/types/key_value'
5
+ require 'command_mapper/gen/types/map'
6
+ require 'command_mapper/gen/types/enum'
@@ -0,0 +1,6 @@
1
+ module CommandMapper
2
+ module Gen
3
+ # Version of command_mapper-gen
4
+ VERSION = '0.1.0.pre1'
5
+ end
6
+ end
@@ -0,0 +1,2 @@
1
+ require 'command_mapper/gen/command'
2
+ require 'command_mapper/gen/parsers'
@@ -0,0 +1,92 @@
1
+ require 'spec_helper'
2
+ require 'command_mapper/gen/argument'
3
+ require 'command_mapper/gen/types/str'
4
+ require 'command_mapper/gen/types/num'
5
+
6
+ describe CommandMapper::Gen::Argument do
7
+ let(:name) { :arg1 }
8
+
9
+ subject { described_class.new(name) }
10
+
11
+ describe "#initialize" do
12
+ it "must set #name" do
13
+ expect(subject.name).to eq(name)
14
+ end
15
+
16
+ it "must default #type to nil" do
17
+ expect(subject.type).to be(nil)
18
+ end
19
+
20
+ it "must default #repeats to nil" do
21
+ expect(subject.repeats).to be(nil)
22
+ end
23
+
24
+ context "when given the value: keyword argument" do
25
+ context "and it's a Types object" do
26
+ let(:type) { Types::Num.new }
27
+
28
+ subject { described_class.new(name, type: type) }
29
+
30
+ it "must set #type" do
31
+ expect(subject.type).to be(type)
32
+ end
33
+ end
34
+ end
35
+
36
+ context "when given the repeats: keyword argument" do
37
+ let(:repeats) { true }
38
+
39
+ subject { described_class.new(name, repeats: repeats) }
40
+
41
+ it "must set #repeats" do
42
+ expect(subject.repeats).to eq(repeats)
43
+ end
44
+ end
45
+ end
46
+
47
+ describe "#to_ruby" do
48
+ it "must output 'argument \#{name}'" do
49
+ expect(subject.to_ruby).to eq("argument #{name.inspect}")
50
+ end
51
+
52
+ context "when #required is true" do
53
+ let(:required) { true}
54
+
55
+ subject { described_class.new(name, required: required) }
56
+
57
+ it "must not append 'required: true'" do
58
+ expect(subject.to_ruby).to eq("argument #{name.inspect}")
59
+ end
60
+ end
61
+
62
+ context "when #required is false" do
63
+ let(:required) { false }
64
+
65
+ subject { described_class.new(name, required: required) }
66
+
67
+ it "must append 'required: false'" do
68
+ expect(subject.to_ruby).to eq("argument #{name.inspect}, required: #{required.inspect}")
69
+ end
70
+ end
71
+
72
+ context "when #type is not nil" do
73
+ let(:type) { Types::Num.new }
74
+
75
+ subject { described_class.new(name, type: type) }
76
+
77
+ it "must append 'type: ...' and call the #type's #to_ruby method" do
78
+ expect(subject.to_ruby).to eq("argument #{name.inspect}, type: #{type.to_ruby}")
79
+ end
80
+ end
81
+
82
+ context "when #repeats is true" do
83
+ let(:repeats) { true }
84
+
85
+ subject { described_class.new(name, repeats: repeats) }
86
+
87
+ it "must append 'repeats: true'" do
88
+ expect(subject.to_ruby).to eq("argument #{name.inspect}, repeats: #{repeats.inspect}")
89
+ end
90
+ end
91
+ end
92
+ end
data/spec/cli_spec.rb ADDED
@@ -0,0 +1,269 @@
1
+ require 'spec_helper'
2
+ require 'command_mapper/gen/cli'
3
+
4
+ describe CommandMapper::Gen::CLI do
5
+ describe "#initialize" do
6
+ it "must default #output to nil" do
7
+ expect(subject.output).to be(nil)
8
+ end
9
+
10
+ it "must default #parsers to [Parsers::Help, Parsers::Man]" do
11
+ expect(subject.parsers).to eq([Parsers::Help, Parsers::Man])
12
+ end
13
+
14
+ it "must default #command to nil" do
15
+ expect(subject.command).to be(nil)
16
+ end
17
+
18
+ it "must initialize #option_parser" do
19
+ expect(subject.option_parser).to be_kind_of(OptionParser)
20
+ end
21
+
22
+ it "must set #debug to false" do
23
+ expect(subject.debug).to be(false)
24
+ end
25
+ end
26
+
27
+ describe ".run" do
28
+ subject { described_class }
29
+
30
+ context "when Interrupt is raised" do
31
+ before do
32
+ expect_any_instance_of(described_class).to receive(:run).and_raise(Interrupt)
33
+ end
34
+
35
+ it "must exit with 130" do
36
+ expect(subject.run([])).to eq(130)
37
+ end
38
+ end
39
+
40
+ context "when Errno::EPIPE is raised" do
41
+ before do
42
+ expect_any_instance_of(described_class).to receive(:run).and_raise(Errno::EPIPE)
43
+ end
44
+
45
+ it "must exit with 0" do
46
+ expect(subject.run([])).to eq(0)
47
+ end
48
+ end
49
+ end
50
+
51
+ describe "#run" do
52
+ let(:command_name) { 'yes' }
53
+
54
+ let(:expected_output) do
55
+ [
56
+ "require 'command_mapper/command'",
57
+ "",
58
+ "#",
59
+ "# Represents the `#{command_name}` command",
60
+ "#",
61
+ "class #{command_name.capitalize} < CommandMapper::Command",
62
+ "",
63
+ " command \"#{command_name}\" do",
64
+ " option \"--help\"",
65
+ " option \"--version\"",
66
+ "",
67
+ " argument :string, required: false, repeats: true",
68
+ " end",
69
+ "",
70
+ "end",
71
+ ].join($/) + $/
72
+ end
73
+
74
+ context "when given a COMMAND_NAME" do
75
+ let(:argv) { [command_name] }
76
+
77
+ it "must parse the command's --help and/or man page and print ruby code" do
78
+ expect {
79
+ subject.run(argv)
80
+ }.to output(expected_output).to_stdout
81
+ end
82
+
83
+ context "but the command isn't installed" do
84
+ before do
85
+ allow(CommandMapper::Gen::Parsers::Help).to receive(:`).with("#{command_name} --help 2>&1").and_raise(Errno::ENOENT.new(command_name))
86
+ end
87
+
88
+ it "must print an error and exit with -1" do
89
+ expect {
90
+ expect(subject.run(argv)).to eq(-1)
91
+ }.to output("#{described_class::PROGRAM_NAME}: command #{command_name.inspect} is not installed#{$/}").to_stderr
92
+ end
93
+ end
94
+ end
95
+
96
+ context "when an invalid option is given" do
97
+ let(:option) { "--foo" }
98
+ let(:argv) { [option] }
99
+
100
+ it "must print an error message and return -1" do
101
+ expect {
102
+ expect(subject.run(argv)).to eq(-1)
103
+ }.to output("#{described_class::PROGRAM_NAME}: invalid option: #{option}#{$/}").to_stderr
104
+ end
105
+ end
106
+
107
+ context "when the command's --help and -h output are empty" do
108
+ let(:argv) { %w[true] }
109
+
110
+ it "must print an error message and return -2" do
111
+ expect {
112
+ expect(subject.run(argv)).to eq(-2)
113
+ }.to output("#{described_class::PROGRAM_NAME}: no options or arguments detected#{$/}").to_stderr
114
+ end
115
+ end
116
+
117
+ context "when no arguments are given" do
118
+ let(:argv) { [] }
119
+
120
+ it "must print an error message and return -1" do
121
+ expect {
122
+ expect(subject.run(argv)).to eq(-1)
123
+ }.to output("#{described_class::PROGRAM_NAME}: expects a COMMAND_NAME#{$/}").to_stderr
124
+ end
125
+ end
126
+ end
127
+
128
+ describe "#option_parser" do
129
+ context "when given -o FILE" do
130
+ let(:file) { "/path/to/file.rb" }
131
+ let(:argv) { ['-o', file] }
132
+
133
+ before { subject.option_parser.parse(argv) }
134
+
135
+ it "must set #output to the FILE" do
136
+ expect(subject.output).to eq(file)
137
+ end
138
+ end
139
+
140
+ context "when given --output FILE" do
141
+ let(:file) { "/path/to/file.rb" }
142
+ let(:argv) { ['--output', file] }
143
+
144
+ before { subject.option_parser.parse(argv) }
145
+
146
+ it "must set #output to the FILE" do
147
+ expect(subject.output).to eq(file)
148
+ end
149
+ end
150
+
151
+ context "when givne -p help" do
152
+ let(:argv) { ['-p', "help"] }
153
+
154
+ before { subject.option_parser.parse(argv) }
155
+
156
+ it "must set #parsers to [Parser::Help]" do
157
+ expect(subject.parsers).to eq([Parsers::Help])
158
+ end
159
+ end
160
+
161
+ context "when givne -p man" do
162
+ let(:argv) { ['-p', "man"] }
163
+
164
+ before { subject.option_parser.parse(argv) }
165
+
166
+ it "must set #parsers to [Parser::Man]" do
167
+ expect(subject.parsers).to eq([Parsers::Man])
168
+ end
169
+ end
170
+
171
+ context "when givne --parser help" do
172
+ let(:argv) { ['--parser', "help"] }
173
+
174
+ before { subject.option_parser.parse(argv) }
175
+
176
+ it "must set #parsers to [Parser::Help]" do
177
+ expect(subject.parsers).to eq([Parsers::Help])
178
+ end
179
+ end
180
+
181
+ context "when givne --parser man" do
182
+ let(:argv) { ['--parser', "man"] }
183
+
184
+ before { subject.option_parser.parse(argv) }
185
+
186
+ it "must set #parsers to [Parser::Man]" do
187
+ expect(subject.parsers).to eq([Parsers::Man])
188
+ end
189
+ end
190
+
191
+ context "when given -d" do
192
+ let(:argv) { ['-d'] }
193
+
194
+ before { subject.option_parser.parse(argv) }
195
+
196
+ it "must set #debug to true" do
197
+ expect(subject.debug).to be(true)
198
+ end
199
+ end
200
+
201
+ context "when given --debug" do
202
+ let(:argv) { ['--debug'] }
203
+
204
+ before { subject.option_parser.parse(argv) }
205
+
206
+ it "must set #debug to true" do
207
+ expect(subject.debug).to be(true)
208
+ end
209
+ end
210
+
211
+ context "when given -V" do
212
+ let(:argv) { ["-v"] }
213
+
214
+ it "must print the command name and version, then exit" do
215
+ expect(subject).to receive(:exit)
216
+
217
+ expect {
218
+ subject.option_parser.parse(argv)
219
+ }.to output("#{described_class::PROGRAM_NAME} #{VERSION}#{$/}").to_stdout
220
+ end
221
+ end
222
+
223
+ context "when given --version" do
224
+ let(:argv) { ["--version"] }
225
+
226
+ it "must print the command name and version, then exit" do
227
+ expect(subject).to receive(:exit)
228
+
229
+ expect {
230
+ subject.option_parser.parse(argv)
231
+ }.to output("#{described_class::PROGRAM_NAME} #{VERSION}#{$/}").to_stdout
232
+ end
233
+ end
234
+
235
+ context "when given -h" do
236
+ let(:argv) { ["-h"] }
237
+
238
+ it "must print help text and then exit" do
239
+ expect(subject).to receive(:exit)
240
+
241
+ expect {
242
+ subject.option_parser.parse(argv)
243
+ }.to output("#{subject.option_parser}").to_stdout
244
+ end
245
+ end
246
+
247
+ context "when given --help" do
248
+ let(:argv) { ["-h"] }
249
+
250
+ it "must print help text and then exit" do
251
+ expect(subject).to receive(:exit)
252
+
253
+ expect {
254
+ subject.option_parser.parse(argv)
255
+ }.to output("#{subject.option_parser}").to_stdout
256
+ end
257
+ end
258
+ end
259
+
260
+ describe "#print_error" do
261
+ let(:message) { "error!" }
262
+
263
+ it "must print the command name and the message to stderr" do
264
+ expect {
265
+ subject.print_error(message)
266
+ }.to output("#{described_class::PROGRAM_NAME}: #{message}#{$/}").to_stderr
267
+ end
268
+ end
269
+ end