command_mapper-gen 0.1.0.pre1

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