mothership 0.0.1
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.
- data/LICENSE +30 -0
- data/Rakefile +24 -0
- data/lib/mothership/base.rb +62 -0
- data/lib/mothership/callbacks.rb +75 -0
- data/lib/mothership/command.rb +120 -0
- data/lib/mothership/errors.rb +37 -0
- data/lib/mothership/help.rb +237 -0
- data/lib/mothership/inputs.rb +58 -0
- data/lib/mothership/parser.rb +154 -0
- data/lib/mothership/pretty.rb +82 -0
- data/lib/mothership/progress.rb +112 -0
- data/lib/mothership/version.rb +3 -0
- data/lib/mothership.rb +66 -0
- data/spec/Rakefile +14 -0
- data/spec/arguments_spec.rb +164 -0
- data/spec/combination_spec.rb +105 -0
- data/spec/flags_spec.rb +123 -0
- data/spec/helpers.rb +23 -0
- metadata +115 -0
@@ -0,0 +1,154 @@
|
|
1
|
+
class Mothership
|
2
|
+
class Parser
|
3
|
+
def initialize(command)
|
4
|
+
@command = command
|
5
|
+
end
|
6
|
+
|
7
|
+
def inputs(argv)
|
8
|
+
inputs = {}
|
9
|
+
|
10
|
+
args = parse_flags(inputs, argv.dup)
|
11
|
+
|
12
|
+
parse_arguments(inputs, args)
|
13
|
+
|
14
|
+
inputs
|
15
|
+
end
|
16
|
+
|
17
|
+
def parse_flags(inputs, argv)
|
18
|
+
args = []
|
19
|
+
|
20
|
+
until argv.empty?
|
21
|
+
flag = normalize_flag(argv.shift, argv)
|
22
|
+
|
23
|
+
name = @command.flags[flag]
|
24
|
+
unless name
|
25
|
+
args << flag
|
26
|
+
next
|
27
|
+
end
|
28
|
+
|
29
|
+
input = @command.inputs[name]
|
30
|
+
|
31
|
+
case input[:type]
|
32
|
+
when :bool, :boolean
|
33
|
+
if argv.first == "false" || argv.first == "true"
|
34
|
+
inputs[name] = argv.shift == "true"
|
35
|
+
else
|
36
|
+
inputs[name] = true
|
37
|
+
end
|
38
|
+
when :float, :floating
|
39
|
+
if !argv.empty? && argv.first =~ /^[0-9]+(\.[0-9]*)?$/
|
40
|
+
inputs[name] = argv.shift.to_f
|
41
|
+
else
|
42
|
+
raise TypeMismatch.new(@command.name, name, "floating")
|
43
|
+
end
|
44
|
+
when :integer, :number, :numeric
|
45
|
+
if !argv.empty? && argv.first =~ /^[0-9]+$/
|
46
|
+
inputs[name] = argv.shift.to_i
|
47
|
+
else
|
48
|
+
raise TypeMismatch.new(@command.name, name, "numeric")
|
49
|
+
end
|
50
|
+
else
|
51
|
+
if argv.empty? || !argv.first.start_with?("-")
|
52
|
+
arg = argv.shift || ""
|
53
|
+
|
54
|
+
inputs[name] =
|
55
|
+
if input[:argument] == :splat
|
56
|
+
arg.split(",")
|
57
|
+
else
|
58
|
+
arg
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
args
|
65
|
+
end
|
66
|
+
|
67
|
+
# [FOO] [BAR] FIZZ BUZZ:
|
68
|
+
# 1 2 => :fizz => 1, :buzz => 2
|
69
|
+
# 1 2 3 => :foo => 1, :fizz => 2, :buzz => 3
|
70
|
+
# 1 2 3 4 => :foo => 1, :bar => 2, :fizz => 3, :buzz => 4
|
71
|
+
def parse_arguments(inputs, args)
|
72
|
+
total = @command.arguments.size
|
73
|
+
required = 0
|
74
|
+
optional = 0
|
75
|
+
@command.arguments.each do |arg|
|
76
|
+
case arg[:type]
|
77
|
+
when :optional
|
78
|
+
optional += 1
|
79
|
+
when :splat
|
80
|
+
break
|
81
|
+
else
|
82
|
+
required += 1
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
parse_optionals = args.size - required
|
87
|
+
|
88
|
+
@command.arguments.each do |arg|
|
89
|
+
name = arg[:name]
|
90
|
+
next if inputs.key? name
|
91
|
+
|
92
|
+
case arg[:type]
|
93
|
+
when :splat
|
94
|
+
inputs[name] = []
|
95
|
+
|
96
|
+
until args.empty?
|
97
|
+
inputs[name] << args.shift
|
98
|
+
end
|
99
|
+
|
100
|
+
when :optional
|
101
|
+
if parse_optionals > 0 && val = args.shift
|
102
|
+
inputs[name] = val
|
103
|
+
parse_optionals -= 1
|
104
|
+
end
|
105
|
+
|
106
|
+
else
|
107
|
+
if val = args.shift
|
108
|
+
inputs[name] = val
|
109
|
+
elsif !@command.inputs[name][:default]
|
110
|
+
raise MissingArgument.new(@command.name, name)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
raise ExtraArguments.new(@command.name) unless args.empty?
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
# --no-foo => --foo false
|
121
|
+
# --no-foo true => --foo false
|
122
|
+
# --no-foo false => --foo true
|
123
|
+
#
|
124
|
+
# --foo=bar => --foo bar
|
125
|
+
def normalize_flag(flag, argv)
|
126
|
+
case flag
|
127
|
+
# boolean negation
|
128
|
+
when /^--no-(.+)/
|
129
|
+
case argv.first
|
130
|
+
when "true"
|
131
|
+
argv[0] = "false"
|
132
|
+
when "false"
|
133
|
+
argv[0] = "true"
|
134
|
+
else
|
135
|
+
argv.unshift "false"
|
136
|
+
end
|
137
|
+
|
138
|
+
"--#$1"
|
139
|
+
|
140
|
+
# --foo=bar form
|
141
|
+
when /^--([^=]+)=(.+)/
|
142
|
+
argv.unshift $2
|
143
|
+
"--#$1"
|
144
|
+
|
145
|
+
# normal flag name
|
146
|
+
when /^--([^ ]+)$/
|
147
|
+
"--#$1"
|
148
|
+
|
149
|
+
else
|
150
|
+
flag
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require "rbconfig"
|
2
|
+
|
3
|
+
# Mix in to your Mothership class to enable user-toggleable colors.
|
4
|
+
#
|
5
|
+
# Redefine color_enabled? to control color enabling/disabling. Colors will be
|
6
|
+
# auto-disabled if the platform is Windows or if $stdout is not a tty.
|
7
|
+
#
|
8
|
+
# Redefine user_colors to return a hash from tags to color, e.g. from a user's
|
9
|
+
# color config file.
|
10
|
+
module Mothership::Pretty
|
11
|
+
WINDOWS = !!(RbConfig::CONFIG['host_os'] =~ /mingw|mswin32|cygwin/)
|
12
|
+
|
13
|
+
COLOR_CODES = {
|
14
|
+
:black => 0,
|
15
|
+
:red => 1,
|
16
|
+
:green => 2,
|
17
|
+
:yellow => 3,
|
18
|
+
:blue => 4,
|
19
|
+
:magenta => 5,
|
20
|
+
:cyan => 6,
|
21
|
+
:white => 7
|
22
|
+
}
|
23
|
+
|
24
|
+
DEFAULT_COLORS = {
|
25
|
+
:name => :blue,
|
26
|
+
:neutral => :blue,
|
27
|
+
:good => :green,
|
28
|
+
:bad => :red,
|
29
|
+
:error => :magenta,
|
30
|
+
:unknown => :cyan,
|
31
|
+
:warning => :yellow,
|
32
|
+
:instance => :yellow,
|
33
|
+
:number => :green,
|
34
|
+
:prompt => :blue,
|
35
|
+
:yes => :green,
|
36
|
+
:no => :red,
|
37
|
+
:dim => :black,
|
38
|
+
:default => :black
|
39
|
+
}
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# override with e.g. option(:color), or whatever toggle you use
|
44
|
+
def color_enabled?
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
# use colors?
|
49
|
+
def color?
|
50
|
+
color_enabled? && !WINDOWS && $stdout.tty?
|
51
|
+
end
|
52
|
+
|
53
|
+
# redefine to control the tag -> color settings
|
54
|
+
def user_colors
|
55
|
+
DEFAULT_COLORS
|
56
|
+
end
|
57
|
+
|
58
|
+
# colored text
|
59
|
+
#
|
60
|
+
# shouldn't use bright colors, as some color themes abuse
|
61
|
+
# the bright palette (I'm looking at you, Solarized)
|
62
|
+
def c(str, type)
|
63
|
+
return str unless color?
|
64
|
+
|
65
|
+
bright = false
|
66
|
+
color = user_colors[type]
|
67
|
+
if color =~ /bright-(.+)/
|
68
|
+
bright = true
|
69
|
+
color = $1.to_sym
|
70
|
+
end
|
71
|
+
|
72
|
+
return str unless color
|
73
|
+
|
74
|
+
"\e[#{bright ? 9 : 3}#{COLOR_CODES[color]}m#{str}\e[0m"
|
75
|
+
end
|
76
|
+
|
77
|
+
# bold text
|
78
|
+
def b(str)
|
79
|
+
return str unless color?
|
80
|
+
"\e[1m#{str}\e[0m"
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require "mothership/pretty"
|
2
|
+
|
3
|
+
module Mothership::Progress
|
4
|
+
include Mothership::Pretty
|
5
|
+
|
6
|
+
module Dots
|
7
|
+
class << self
|
8
|
+
DOT_COUNT = 3
|
9
|
+
DOT_TICK = 0.15
|
10
|
+
|
11
|
+
def start!
|
12
|
+
@dots ||=
|
13
|
+
Thread.new do
|
14
|
+
before_sync = $stdout.sync
|
15
|
+
|
16
|
+
$stdout.sync = true
|
17
|
+
|
18
|
+
printed = false
|
19
|
+
i = 1
|
20
|
+
until @stop_dots
|
21
|
+
if printed
|
22
|
+
print "\b" * DOT_COUNT
|
23
|
+
end
|
24
|
+
|
25
|
+
print ("." * i).ljust(DOT_COUNT)
|
26
|
+
printed = true
|
27
|
+
|
28
|
+
if i == DOT_COUNT
|
29
|
+
i = 0
|
30
|
+
else
|
31
|
+
i += 1
|
32
|
+
end
|
33
|
+
|
34
|
+
sleep DOT_TICK
|
35
|
+
end
|
36
|
+
|
37
|
+
if printed
|
38
|
+
print "\b" * DOT_COUNT
|
39
|
+
print " " * DOT_COUNT
|
40
|
+
print "\b" * DOT_COUNT
|
41
|
+
end
|
42
|
+
|
43
|
+
$stdout.sync = before_sync
|
44
|
+
@stop_dots = nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def stop!
|
49
|
+
return unless @dots
|
50
|
+
return if @stop_dots
|
51
|
+
@stop_dots = true
|
52
|
+
@dots.join
|
53
|
+
@dots = nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class Skipper
|
59
|
+
def initialize(&ret)
|
60
|
+
@return = ret
|
61
|
+
end
|
62
|
+
|
63
|
+
def skip(&callback)
|
64
|
+
@return.call("SKIPPED", :warning, callback)
|
65
|
+
end
|
66
|
+
|
67
|
+
def give_up(&callback)
|
68
|
+
@return.call("GAVE UP", :bad, callback)
|
69
|
+
end
|
70
|
+
|
71
|
+
def fail(&callback)
|
72
|
+
@return.call("FAILED", :error, callback)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# override to determine whether to show progress
|
77
|
+
def quiet?
|
78
|
+
false
|
79
|
+
end
|
80
|
+
|
81
|
+
def with_progress(message)
|
82
|
+
unless quiet?
|
83
|
+
print message
|
84
|
+
Dots.start!
|
85
|
+
end
|
86
|
+
|
87
|
+
skipper = Skipper.new do |status, color, callback|
|
88
|
+
unless quiet?
|
89
|
+
Dots.stop!
|
90
|
+
puts "... #{c(status, color)}"
|
91
|
+
end
|
92
|
+
|
93
|
+
return callback && callback.call
|
94
|
+
end
|
95
|
+
|
96
|
+
begin
|
97
|
+
res = yield skipper
|
98
|
+
unless quiet?
|
99
|
+
Dots.stop!
|
100
|
+
puts "... #{c("OK", :good)}"
|
101
|
+
end
|
102
|
+
res
|
103
|
+
rescue
|
104
|
+
unless quiet?
|
105
|
+
Dots.stop!
|
106
|
+
puts "... #{c("FAILED", :error)}"
|
107
|
+
end
|
108
|
+
|
109
|
+
raise
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
data/lib/mothership.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require "mothership/base"
|
2
|
+
require "mothership/callbacks"
|
3
|
+
require "mothership/command"
|
4
|
+
require "mothership/parser"
|
5
|
+
require "mothership/help"
|
6
|
+
require "mothership/errors"
|
7
|
+
|
8
|
+
class Mothership
|
9
|
+
# [Mothership::Command] global options
|
10
|
+
@@global = Command.new(self, "(global options)")
|
11
|
+
|
12
|
+
# [Mothershp::Inputs] inputs from global options
|
13
|
+
@@inputs = nil
|
14
|
+
|
15
|
+
# [Fixnum] exit status; reassign as appropriate error code (e.g. 1)
|
16
|
+
@@exit_status = 0
|
17
|
+
|
18
|
+
class << self
|
19
|
+
# define a global option
|
20
|
+
def option(name, options = {}, &default)
|
21
|
+
@@global.add_input(name, options, &default)
|
22
|
+
end
|
23
|
+
|
24
|
+
# parse argv, by taking the first arg as the command, and the rest as
|
25
|
+
# arguments and flags
|
26
|
+
#
|
27
|
+
# arguments and flags can be in any order; all flags will be parsed out
|
28
|
+
# first, and the bits left over will be treated as arguments
|
29
|
+
def start(argv)
|
30
|
+
@@inputs = Inputs.new(@@global, self, {})
|
31
|
+
|
32
|
+
name, *argv =
|
33
|
+
Parser.new(@@global).parse_flags(
|
34
|
+
@@inputs.inputs,
|
35
|
+
argv)
|
36
|
+
|
37
|
+
app = new
|
38
|
+
|
39
|
+
return app.default_action unless name
|
40
|
+
|
41
|
+
cmdname = name.gsub("-", "_").to_sym
|
42
|
+
|
43
|
+
cmd = @@commands[cmdname]
|
44
|
+
return app.unknown_command(cmdname) unless cmd
|
45
|
+
|
46
|
+
app.execute(cmd, argv)
|
47
|
+
|
48
|
+
exit @@exit_status
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# set the exit status
|
53
|
+
def exit_status(num)
|
54
|
+
@@exit_status = num
|
55
|
+
end
|
56
|
+
|
57
|
+
# get value of global option
|
58
|
+
def option(name, *args)
|
59
|
+
@@inputs[name, *args]
|
60
|
+
end
|
61
|
+
|
62
|
+
# test if an option was explicitly provided
|
63
|
+
def option_given?(name)
|
64
|
+
@@inputs.given? name
|
65
|
+
end
|
66
|
+
end
|
data/spec/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
Bundler.require(:default, :development)
|
4
|
+
|
5
|
+
require 'rake/dsl_definition'
|
6
|
+
require 'rake'
|
7
|
+
require 'rspec'
|
8
|
+
require 'rspec/core/rake_task'
|
9
|
+
|
10
|
+
RSpec::Core::RakeTask.new do |t|
|
11
|
+
t.pattern = "**/*_spec.rb"
|
12
|
+
t.rspec_opts = ["--format", "documentation", "--colour"]
|
13
|
+
end
|
14
|
+
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require "mothership"
|
2
|
+
require "./helpers"
|
3
|
+
|
4
|
+
describe Mothership::Parser do
|
5
|
+
describe "arguments" do
|
6
|
+
describe "normal" do
|
7
|
+
it "is declared as an input" do
|
8
|
+
command(:foo => { :argument => true }) do |c|
|
9
|
+
inputs(c, "bar").should == { :foo => "bar" }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
it "can be passed as a flag" do
|
14
|
+
command(:foo => { :argument => true }) do |c|
|
15
|
+
inputs(c, "--foo", "bar").should == { :foo => "bar" }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it "is parsed in the order of definition" do
|
20
|
+
command([
|
21
|
+
[:foo, { :argument => true }],
|
22
|
+
[:bar, { :argument => true }]]) do |c|
|
23
|
+
inputs(c, "fizz", "buzz").should ==
|
24
|
+
{ :foo => "fizz", :bar => "buzz" }
|
25
|
+
end
|
26
|
+
|
27
|
+
command([
|
28
|
+
[:bar, { :argument => true }],
|
29
|
+
[:foo, { :argument => true }]]) do |c|
|
30
|
+
inputs(c, "fizz", "buzz").should ==
|
31
|
+
{ :foo => "buzz", :bar => "fizz" }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "optional & required ordering" do
|
37
|
+
it "parses required arguments positioned before optionals first" do
|
38
|
+
command([
|
39
|
+
[:foo, { :argument => true }],
|
40
|
+
[:foo2, { :argument => true }],
|
41
|
+
[:bar, { :argument => :optional }],
|
42
|
+
[:bar2, { :argument => :optional }]]) do |c|
|
43
|
+
inputs(c, "a", "b").should == { :foo => "a", :foo2 => "b" }
|
44
|
+
|
45
|
+
inputs(c, "a", "b", "c").should ==
|
46
|
+
{ :foo => "a", :foo2 => "b", :bar => "c" }
|
47
|
+
|
48
|
+
inputs(c, "a", "b", "c", "d").should ==
|
49
|
+
{ :foo => "a", :foo2 => "b", :bar => "c", :bar2 => "d" }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it "parses required arguments positioned after optionals first " do
|
54
|
+
command([
|
55
|
+
[:foo, { :argument => :optional }],
|
56
|
+
[:foo2, { :argument => :optional }],
|
57
|
+
[:bar, { :argument => true }],
|
58
|
+
[:bar2, { :argument => true }]]) do |c|
|
59
|
+
inputs(c, "a", "b").should == { :bar => "a", :bar2 => "b" }
|
60
|
+
|
61
|
+
inputs(c, "a", "b", "c").should ==
|
62
|
+
{ :bar => "b", :bar2 => "c", :foo => "a" }
|
63
|
+
|
64
|
+
inputs(c, "a", "b", "c", "d").should ==
|
65
|
+
{ :bar => "c", :bar2 => "d", :foo => "a", :foo2 => "b" }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
it "parses required arguments positioned around optionals first " do
|
70
|
+
command([
|
71
|
+
[:foo, { :argument => true }],
|
72
|
+
[:foo2, { :argument => :optional }],
|
73
|
+
[:bar, { :argument => :optional }],
|
74
|
+
[:bar2, { :argument => true }]]) do |c|
|
75
|
+
inputs(c, "a", "b").should == { :foo => "a", :bar2 => "b" }
|
76
|
+
|
77
|
+
inputs(c, "a", "b", "c").should ==
|
78
|
+
{ :foo => "a", :foo2 => "b", :bar2 => "c" }
|
79
|
+
|
80
|
+
inputs(c, "a", "b", "c", "d").should ==
|
81
|
+
{ :foo => "a", :foo2 => "b", :bar => "c", :bar2 => "d" }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
it "parses required arguments positioned between optionals first " do
|
86
|
+
command([
|
87
|
+
[:foo, { :argument => :optional }],
|
88
|
+
[:foo2, { :argument => true }],
|
89
|
+
[:bar, { :argument => true }],
|
90
|
+
[:bar2, { :argument => :optional }]]) do |c|
|
91
|
+
inputs(c, "a", "b").should == { :foo2 => "a", :bar => "b" }
|
92
|
+
|
93
|
+
inputs(c, "a", "b", "c").should ==
|
94
|
+
{ :foo => "a", :foo2 => "b", :bar => "c" }
|
95
|
+
|
96
|
+
inputs(c, "a", "b", "c", "d").should ==
|
97
|
+
{ :foo => "a", :foo2 => "b", :bar => "c", :bar2 => "d" }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe "as flags" do
|
103
|
+
it "assigns as the value if given" do
|
104
|
+
command(:foo => { :argument => true }) do |c|
|
105
|
+
inputs(c, "--foo", "bar").should == { :foo => "bar" }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
it "assigns as an empty string if just name is given" do
|
110
|
+
command(:foo => { :argument => true }) do |c|
|
111
|
+
inputs(c, "--foo").should == { :foo => "" }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe "splats" do
|
117
|
+
it "is declared as an input" do
|
118
|
+
command(:foo => { :argument => :splat }) do |c|
|
119
|
+
inputs(c, "bar").should == { :foo => ["bar"] }
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
it "assigns as an empty array if no arguments given" do
|
124
|
+
command(:foo => { :argument => :splat }) do |c|
|
125
|
+
inputs(c).should == { :foo => [] }
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
it "assigns as an array if one argument given" do
|
130
|
+
command(:foo => { :argument => :splat }) do |c|
|
131
|
+
inputs(c, "foo").should == { :foo => ["foo"] }
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
it "assigns as an array if two or more arguments given" do
|
136
|
+
command(:foo => { :argument => :splat }) do |c|
|
137
|
+
inputs(c, "foo", "bar").should == { :foo => ["foo", "bar"] }
|
138
|
+
inputs(c, "foo", "bar", "baz").should ==
|
139
|
+
{ :foo => ["foo", "bar", "baz"] }
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
describe "as flags" do
|
144
|
+
it "assigns as an empty array if just name is given" do
|
145
|
+
command(:foo => { :argument => :splat }) do |c|
|
146
|
+
inputs(c, "--foo").should == { :foo => [] }
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
it "assigns as an array if one value given" do
|
151
|
+
command(:foo => { :argument => :splat }) do |c|
|
152
|
+
inputs(c, "--foo", "bar").should == { :foo => ["bar"] }
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
it "accepts comma-separated values" do
|
157
|
+
command(:foo => { :argument => :splat }) do |c|
|
158
|
+
inputs(c, "--foo", "bar,baz").should == { :foo => ["bar", "baz"] }
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require "mothership"
|
2
|
+
require "./helpers"
|
3
|
+
|
4
|
+
describe Mothership::Parser do
|
5
|
+
describe "combinations" do
|
6
|
+
describe "arguments & flags" do
|
7
|
+
it "parses flags placed after arguments" do
|
8
|
+
command(:flag => {}, :arg => { :argument => true }) do |c|
|
9
|
+
inputs(c, "foo", "--flag", "bar").should ==
|
10
|
+
{ :arg => "foo", :flag => "bar" }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it "parses flags placed before arguments" do
|
15
|
+
command(:flag => {}, :arg => { :argument => true }) do |c|
|
16
|
+
inputs(c, "--flag", "bar", "foo").should ==
|
17
|
+
{ :arg => "foo", :flag => "bar" }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it "parses flags placed between arguments" do
|
22
|
+
command([
|
23
|
+
[:flag, {}],
|
24
|
+
[:arg1, { :argument => true }],
|
25
|
+
[:arg2, { :argument => true }]]) do |c|
|
26
|
+
inputs(c, "foo", "--flag", "bar", "baz").should ==
|
27
|
+
{ :arg1 => "foo", :flag => "bar", :arg2 => "baz" }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it "skips parsing arguments that were passed as flags" do
|
32
|
+
command([
|
33
|
+
[:arg1, { :argument => true }],
|
34
|
+
[:arg2, { :argument => true }]]) do |c|
|
35
|
+
inputs(c, "baz", "--arg1", "foo").should ==
|
36
|
+
{ :arg1 => "foo", :arg2 => "baz" }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "arguments & splats" do
|
42
|
+
it "consumes the rest of the arguments" do
|
43
|
+
command([
|
44
|
+
[:foo, { :argument => :splat }],
|
45
|
+
[:bar, { :argument => true }]]) do |c|
|
46
|
+
proc {
|
47
|
+
inputs(c, "fizz", "buzz")
|
48
|
+
}.should raise_error(Mothership::MissingArgument)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it "consumes arguments after normal arguments" do
|
53
|
+
command([
|
54
|
+
[:foo, { :argument => true }],
|
55
|
+
[:bar, { :argument => :splat }]]) do |c|
|
56
|
+
inputs(c, "fizz", "buzz").should ==
|
57
|
+
{ :foo => "fizz", :bar => ["buzz"] }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it "appears empty when there are no arguments left after normal" do
|
62
|
+
command([
|
63
|
+
[:foo, { :argument => true }],
|
64
|
+
[:bar, { :argument => :splat }]]) do |c|
|
65
|
+
inputs(c, "fizz").should ==
|
66
|
+
{ :foo => "fizz", :bar => [] }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "splats & flags" do
|
72
|
+
it "parses flags placed after splats" do
|
73
|
+
command(:flag => {}, :arg => { :argument => :splat }) do |c|
|
74
|
+
inputs(c, "foo", "--flag", "bar").should ==
|
75
|
+
{ :arg => ["foo"], :flag => "bar" }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it "parses flags placed before splats" do
|
80
|
+
command(:flag => {}, :arg => { :argument => :splat }) do |c|
|
81
|
+
inputs(c, "--flag", "bar", "foo").should ==
|
82
|
+
{ :arg => ["foo"], :flag => "bar" }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it "parses flags placed between splat arguments" do
|
87
|
+
command([
|
88
|
+
[:flag, {}],
|
89
|
+
[:arg, { :argument => :splat }]]) do |c|
|
90
|
+
inputs(c, "foo", "--flag", "bar", "baz").should ==
|
91
|
+
{ :arg => ["foo", "baz"], :flag => "bar" }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
it "skips parsing splats that were passed as flags" do
|
96
|
+
command([
|
97
|
+
[:arg1, { :argument => :splat }],
|
98
|
+
[:arg2, { :argument => true }]]) do |c|
|
99
|
+
inputs(c, "baz", "--arg1", "foo").should ==
|
100
|
+
{ :arg1 => ["foo"], :arg2 => "baz" }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|