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