samovar 2.0.1 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +110 -68
- data/lib/samovar/command.rb +7 -1
- data/lib/samovar/version.rb +1 -1
- data/spec/samovar/command_spec.rb +10 -0
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dee6078a981a3ddc9c9511c8a5411159bd94c9c422000f28babc461d44f40fb5
|
4
|
+
data.tar.gz: 7478ed72f2668fe912cde8701d4ecc2ab497ebeb36f76484160a495622b4842c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3a24806b771f6d3e32f841462ec585ba22e0778d840dd51345b452914fac06f31882c007db8740daf1a01e0d3076119bcecd926bd7e6b57847289d006ede52b4
|
7
|
+
data.tar.gz: ea01b240a4b01caac62d950abd4785c2f77efa5fd9cc9cc1fa4fd6f06ea08303842481c27c8ac276142ee1dc3aeab71cd60b4a3c0420817628cdcff4a7c4961a
|
data/README.md
CHANGED
@@ -36,95 +36,141 @@ Or install it yourself as:
|
|
36
36
|
|
37
37
|
## Usage
|
38
38
|
|
39
|
-
|
39
|
+
Generally speaking, you should create `Command` classes that represent specific functions in your program. The top level command might look something like this:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
require 'samovar'
|
40
43
|
|
41
|
-
|
44
|
+
class List < Samovar::Command
|
45
|
+
self.description = "List the current directory"
|
42
46
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
end
|
47
|
+
def call
|
48
|
+
system("ls -lah")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Application < Samovar::Command
|
53
|
+
options do
|
54
|
+
option '--help', "Do you need help?"
|
52
55
|
end
|
53
56
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
application = Application.new(['-x', '-y'])
|
58
|
-
application.options[:axis] # :y
|
59
|
-
|
60
|
-
application = Application.new(['-F'])
|
61
|
-
application.options[:flag] # true
|
57
|
+
nested :command, {
|
58
|
+
'list' => List
|
59
|
+
}, default: 'list'
|
62
60
|
|
63
|
-
|
64
|
-
|
61
|
+
def call
|
62
|
+
if @options[:help]
|
63
|
+
self.print_usage
|
64
|
+
else
|
65
|
+
@command.call
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
65
69
|
|
66
|
-
|
70
|
+
Application.call # Defaults to ARGV.
|
71
|
+
```
|
67
72
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
+
### Basic Options
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
require 'samovar'
|
77
|
+
|
78
|
+
class Application < Samovar::Command
|
79
|
+
options do
|
80
|
+
option '-f/--frobulate <text>', "Frobulate the text"
|
81
|
+
option '-x | -y', "Specify either x or y axis.", key: :axis
|
82
|
+
option '-F/--yeah/--flag', "A boolean flag with several forms."
|
83
|
+
option '--things <a,b,c>', "A list of things" do |value|
|
84
|
+
value.split(/\s*,\s*/)
|
73
85
|
end
|
74
86
|
end
|
87
|
+
end
|
88
|
+
|
89
|
+
application = Application.new(['-f', 'Algebraic!'])
|
90
|
+
application.options[:frobulate] # 'Algebraic!'
|
91
|
+
|
92
|
+
application = Application.new(['-x', '-y'])
|
93
|
+
application.options[:axis] # :y
|
94
|
+
|
95
|
+
application = Application.new(['-F'])
|
96
|
+
application.options[:flag] # true
|
97
|
+
|
98
|
+
application = Application.new(['--things', 'x,y,z'])
|
99
|
+
application.options[:things] # ['x', 'y', 'z']
|
100
|
+
```
|
101
|
+
|
102
|
+
### Nested Commands
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
require 'samovar'
|
106
|
+
|
107
|
+
class Create < Samovar::Command
|
108
|
+
def invoke(parent)
|
109
|
+
puts "Creating"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class Application < Samovar::Command
|
114
|
+
nested '<command>',
|
115
|
+
'create' => Create
|
75
116
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
if @command
|
82
|
-
@command.invoke
|
83
|
-
else
|
84
|
-
print_usage(program_name)
|
85
|
-
end
|
117
|
+
def invoke(program_name: File.basename($0))
|
118
|
+
if @command
|
119
|
+
@command.invoke
|
120
|
+
else
|
121
|
+
print_usage(program_name)
|
86
122
|
end
|
87
123
|
end
|
124
|
+
end
|
88
125
|
|
89
|
-
|
126
|
+
Application.new(['create']).invoke
|
127
|
+
```
|
90
128
|
|
91
129
|
### ARGV Splits
|
92
130
|
|
93
|
-
|
131
|
+
```ruby
|
132
|
+
require 'samovar'
|
94
133
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
134
|
+
class Application < Samovar::Command
|
135
|
+
many :packages
|
136
|
+
split :argv
|
137
|
+
end
|
99
138
|
|
100
|
-
|
101
|
-
|
102
|
-
|
139
|
+
application = Application.new(['foo', 'bar', 'baz', '--', 'apples', 'oranges', 'feijoas'])
|
140
|
+
application.packages # ['foo', 'bar', 'baz']
|
141
|
+
application.argv # ['apples', 'oranges', 'feijoas']
|
142
|
+
```
|
103
143
|
|
104
144
|
### Parsing Tokens
|
105
145
|
|
106
|
-
|
146
|
+
```ruby
|
147
|
+
require 'samovar'
|
148
|
+
|
149
|
+
class Application < Samovar::Command
|
150
|
+
self.description = "Mix together your favorite things."
|
107
151
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
one :fruit, "Name one fruit"
|
112
|
-
many :cakes, "Any cakes you like"
|
113
|
-
end
|
152
|
+
one :fruit, "Name one fruit"
|
153
|
+
many :cakes, "Any cakes you like"
|
154
|
+
end
|
114
155
|
|
115
|
-
|
116
|
-
|
117
|
-
|
156
|
+
application = Application.new(['apple', 'chocolate cake', 'fruit cake'])
|
157
|
+
application.fruit # 'apple'
|
158
|
+
application.cakes # ['chocolate cake', 'fruit cake']
|
159
|
+
```
|
118
160
|
|
119
161
|
### Explicit Commands
|
120
162
|
|
121
163
|
Given a custom `Samovar::Command` subclass, you can instantiate it with options:
|
122
164
|
|
123
|
-
|
165
|
+
```ruby
|
166
|
+
application = Application['--root', path]
|
167
|
+
```
|
124
168
|
|
125
169
|
You can also duplicate an existing command instance with additions/changes:
|
126
170
|
|
127
|
-
|
171
|
+
```ruby
|
172
|
+
concurrent_application = application['--threads', 12]
|
173
|
+
```
|
128
174
|
|
129
175
|
These forms can be useful when invoking one command from another, or in unit tests.
|
130
176
|
|
@@ -138,14 +184,6 @@ These forms can be useful when invoking one command from another, or in unit tes
|
|
138
184
|
|
139
185
|
### Future Work
|
140
186
|
|
141
|
-
#### Line Wrapping
|
142
|
-
|
143
|
-
Line wrapping is done by the terminal which is a bit ugly in some cases. There is a [half-implemented elegant solution](lib/samovar/output/line_wrapper.rb).
|
144
|
-
|
145
|
-
#### Type Coercion
|
146
|
-
|
147
|
-
It might make sense to enforce constraints at parse time.. or not. For example, if an option is given like `--count <int>` we should probably parse an integer?
|
148
|
-
|
149
187
|
#### Multi-value Options
|
150
188
|
|
151
189
|
Right now, options can take a single argument, e.g. `--count <int>`. Ideally, we support a specific sub-parser defined by the option, e.g. `--count <int...>` or `--tag <section> <tags...>`. These would map to specific parsers using `Samovar::One` and `Samovar::Many` internally.
|
@@ -154,11 +192,15 @@ Right now, options can take a single argument, e.g. `--count <int>`. Ideally, we
|
|
154
192
|
|
155
193
|
Options can only be parsed at the place they are explicitly mentioned, e.g. a command with sub-commands won't parse an option added to the end of the command:
|
156
194
|
|
157
|
-
|
195
|
+
```ruby
|
196
|
+
command list --help
|
197
|
+
```
|
158
198
|
|
159
199
|
One might reasonably expect this to parse but it isn't so easy to generalize this:
|
160
200
|
|
161
|
-
|
201
|
+
```ruby
|
202
|
+
command list -- --help
|
203
|
+
```
|
162
204
|
|
163
205
|
In this case, do we show help? Some effort is required to disambiguate this. Initially, it makes sense to keep things as simple as possible. But, it might make sense for some options to be declared in a global scope, which are extracted before parsing begins. I'm not sure if this is really a good idea. It might just be better to give good error output in this case (you specified an option but it was in the wrong place).
|
164
206
|
|
data/lib/samovar/command.rb
CHANGED
@@ -31,8 +31,14 @@ require_relative 'error'
|
|
31
31
|
|
32
32
|
module Samovar
|
33
33
|
class Command
|
34
|
+
def self.call(input = ARGV)
|
35
|
+
if command = self.parse(input)
|
36
|
+
command.call
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
34
40
|
# The top level entry point for parsing ARGV.
|
35
|
-
def self.parse(input
|
41
|
+
def self.parse(input)
|
36
42
|
self.new(input)
|
37
43
|
rescue Error => error
|
38
44
|
error.command.print_usage(output: $stderr) do |formatter|
|
data/lib/samovar/version.rb
CHANGED
@@ -46,6 +46,16 @@ module Samovar::CommandSpec
|
|
46
46
|
end
|
47
47
|
|
48
48
|
RSpec.describe Samovar::Command do
|
49
|
+
it "should invoke call" do
|
50
|
+
expect(Top).to receive(:new).and_wrap_original do |original_method, *args, &block|
|
51
|
+
original_method.call(*args, &block).tap do |instance|
|
52
|
+
expect(instance).to receive(:call)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
Top.call([])
|
57
|
+
end
|
58
|
+
|
49
59
|
it "should use default value" do
|
50
60
|
top = Top[]
|
51
61
|
expect(top.options[:configuration]).to be == 'TEAPOT_CONFIGURATION'
|