clive 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +2 -0
- data/.gitignore +25 -0
- data/LICENSE +20 -0
- data/README.md +150 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/lib/clive.rb +45 -0
- data/lib/clive/commands.rb +224 -0
- data/lib/clive/ext.rb +29 -0
- data/lib/clive/flags.rb +17 -0
- data/lib/clive/switches.rb +24 -0
- data/lib/clive/tokens.rb +80 -0
- data/test/bin_test +37 -0
- data/test/helper.rb +10 -0
- data/test/test_clive.rb +214 -0
- metadata +110 -0
data/.document
ADDED
data/.gitignore
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
## MAC OS
|
2
|
+
.DS_Store
|
3
|
+
|
4
|
+
## TEXTMATE
|
5
|
+
*.tmproj
|
6
|
+
tmtags
|
7
|
+
|
8
|
+
## EMACS
|
9
|
+
*~
|
10
|
+
\#*
|
11
|
+
.\#*
|
12
|
+
|
13
|
+
## VIM
|
14
|
+
*.swp
|
15
|
+
|
16
|
+
## PROJECT::GENERAL
|
17
|
+
coverage
|
18
|
+
rdoc
|
19
|
+
pkg
|
20
|
+
|
21
|
+
## PROJECT::SPECIFIC
|
22
|
+
.yardoc
|
23
|
+
doc
|
24
|
+
ideas.rb
|
25
|
+
research
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Joshua Hawxwell
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
# clive
|
2
|
+
|
3
|
+
Clive is a DSL for creating a command line interface. It is for people who, like me, love [OptionParser's](http://ruby-doc.org/stdlib/libdoc/optparse/rdoc/classes/OptionParser.html) syntax and love [GLI's](http://github.com/davetron5000/gli) commands.
|
4
|
+
|
5
|
+
## Install
|
6
|
+
|
7
|
+
Install with:
|
8
|
+
|
9
|
+
(sudo) gem install clive
|
10
|
+
|
11
|
+
|
12
|
+
## How To
|
13
|
+
|
14
|
+
A simple example to start:
|
15
|
+
|
16
|
+
require 'clive'
|
17
|
+
|
18
|
+
opts = {}
|
19
|
+
c = Clive.new do
|
20
|
+
switch(:v, :verbose, "Run verbosely") {opts[:verbose] = true}
|
21
|
+
end
|
22
|
+
c.parse(ARGV)
|
23
|
+
p opts
|
24
|
+
|
25
|
+
This creates a very simple interface which can have one switch, you can then use the long or short form to call the block.
|
26
|
+
|
27
|
+
my_file -v
|
28
|
+
#=> {:verbose => true}
|
29
|
+
my_file --verbose
|
30
|
+
#=> {:verbose => true}
|
31
|
+
|
32
|
+
|
33
|
+
### Switches
|
34
|
+
|
35
|
+
As we've seen above switches are created using #switch. You can provide as little information as you want. `switch(:v) {}` creates a switch that responds only to `-v`, or `switch(:verbose) {}` creates a switch that only responds to `--verbose`.
|
36
|
+
|
37
|
+
### Flags
|
38
|
+
|
39
|
+
Flags are like switches but also take an argument:
|
40
|
+
|
41
|
+
c = Clive.new do
|
42
|
+
flag(:p, :print, "Print the argument") do |i|
|
43
|
+
p i
|
44
|
+
end
|
45
|
+
end
|
46
|
+
c.parse(ARGV)
|
47
|
+
|
48
|
+
####
|
49
|
+
|
50
|
+
my_file --print=hello
|
51
|
+
#=> "hello"
|
52
|
+
my_file --print equalsless
|
53
|
+
#=> "equalsless"
|
54
|
+
my_file -p short
|
55
|
+
#=> "short"
|
56
|
+
|
57
|
+
The argument is then passed into the block. As you can see you can use short, long, equals, or no equals to call flags. As with switches you can call `flag(:p) {|i| ...}` which responds to `-p ...`, `flag(:print) {|i| ...}` which responds to `--print ...` or `--print=...`.
|
58
|
+
|
59
|
+
### Commands
|
60
|
+
|
61
|
+
Commands work like in git, here's an example:
|
62
|
+
|
63
|
+
opts = {}
|
64
|
+
c = Clive.new do
|
65
|
+
command(:add) do
|
66
|
+
opts[:add] = {}
|
67
|
+
flag(:r, :require, "Require a library") {|i| opts[:add][:lib] = i}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
c.parse(ARGV)
|
71
|
+
p opts
|
72
|
+
|
73
|
+
####
|
74
|
+
|
75
|
+
my_file add -r Clive
|
76
|
+
#=> {:add => {:lib => "Clive"}}
|
77
|
+
|
78
|
+
Commands make it easy to group flags, switches and even other commands. The block for the command is executed on finding the command, this allows you to put other code within the block specific for the command, as shown above.
|
79
|
+
|
80
|
+
|
81
|
+
### Arguments
|
82
|
+
|
83
|
+
Anything that isn't a command, switch or flag is taken as an argument. These are returned by #parse as an array.
|
84
|
+
|
85
|
+
opts = {}
|
86
|
+
c = Clive.new do
|
87
|
+
flag(:size) {|i| opts[:size] = i}
|
88
|
+
end
|
89
|
+
args = c.parse(ARGV)
|
90
|
+
p args
|
91
|
+
|
92
|
+
####
|
93
|
+
|
94
|
+
my_file --size big /usr/bin
|
95
|
+
#=> ["/usr/bin"]
|
96
|
+
|
97
|
+
### Putting It All Together
|
98
|
+
|
99
|
+
require 'clive'
|
100
|
+
|
101
|
+
opts = {}
|
102
|
+
c = Clive.new do
|
103
|
+
switch(:v, :verbose, "Run verbosely") {opts[:verbose] = true}
|
104
|
+
|
105
|
+
command(:add, "Add a new project")) do
|
106
|
+
opts[:add] = {}
|
107
|
+
|
108
|
+
switch(:force, "Force overwrite") {opts[:add][:force] = true}
|
109
|
+
flag(:framework, "Add framework") do |i|
|
110
|
+
opts[:add][:framework] ||= []
|
111
|
+
opts[:add][:framework] << i
|
112
|
+
end
|
113
|
+
|
114
|
+
command(:init, "Initialize the project after creating") do
|
115
|
+
switch(:m, :minimum, "Use minimum settings") {opts[:add][:min] = true}
|
116
|
+
flag(:width) {|i| opts[:add][:width] = i.to_i}
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
switch(:version, "Show version") do
|
122
|
+
puts "1.0.0"
|
123
|
+
exit
|
124
|
+
end
|
125
|
+
end
|
126
|
+
args = c.parse(ARGV)
|
127
|
+
p opts
|
128
|
+
p args
|
129
|
+
|
130
|
+
####
|
131
|
+
|
132
|
+
my_file --version
|
133
|
+
#=> 1.0.0
|
134
|
+
my_file -v add --framework=blueprint init -m -w 200 ~/Desktop/new_thing ~/Desktop/another_thing
|
135
|
+
#=> {:verbose => true, :add => {:framework => ["blueprint"], :min => true, :width => 200}}
|
136
|
+
#=> ["~/Desktop/new_thing", "~/Desktop/another_thing"]
|
137
|
+
|
138
|
+
## Note on Patches/Pull Requests
|
139
|
+
|
140
|
+
* Fork the project.
|
141
|
+
* Make your feature addition or bug fix.
|
142
|
+
* Add tests for it. This is important so I don't break it in a
|
143
|
+
future version unintentionally.
|
144
|
+
* Commit, do not mess with rakefile, version, or history.
|
145
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself so I can ignore when I pull)
|
146
|
+
* Send me a pull request. Bonus points for topic branches.
|
147
|
+
|
148
|
+
## Copyright
|
149
|
+
|
150
|
+
Copyright (c) 2010 Joshua Hawxwell. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "clive"
|
8
|
+
gem.summary = %Q{Imagine if optparse and gli had a son called clive.}
|
9
|
+
gem.description = %Q{Clive is a DSL for creating a command line interface. It is for people who, like me, love OptionParser's syntax and love GLI's commands.}
|
10
|
+
gem.email = "m@hawx.me"
|
11
|
+
gem.homepage = "http://github.com/hawx/clive"
|
12
|
+
gem.authors = ["Joshua Hawxwell"]
|
13
|
+
gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
14
|
+
gem.add_development_dependency "yard", ">= 0"
|
15
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
|
+
end
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'rake/testtask'
|
23
|
+
Rake::TestTask.new(:test) do |test|
|
24
|
+
test.libs << 'lib' << 'test'
|
25
|
+
test.pattern = 'test/**/test_*.rb'
|
26
|
+
test.verbose = true
|
27
|
+
end
|
28
|
+
|
29
|
+
begin
|
30
|
+
require 'rcov/rcovtask'
|
31
|
+
Rcov::RcovTask.new do |test|
|
32
|
+
test.libs << 'test'
|
33
|
+
test.pattern = 'test/**/test_*.rb'
|
34
|
+
test.verbose = true
|
35
|
+
end
|
36
|
+
rescue LoadError
|
37
|
+
task :rcov do
|
38
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
task :test => :check_dependencies
|
43
|
+
|
44
|
+
task :default => :test
|
45
|
+
|
46
|
+
begin
|
47
|
+
require 'yard'
|
48
|
+
YARD::Rake::YardocTask.new
|
49
|
+
rescue LoadError
|
50
|
+
task :yardoc do
|
51
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
52
|
+
end
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/clive.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require 'clive/tokens'
|
4
|
+
require 'clive/ext'
|
5
|
+
require 'clive/switches'
|
6
|
+
require 'clive/flags'
|
7
|
+
require 'clive/commands'
|
8
|
+
|
9
|
+
# Clive is a simple dsl for creating command line interfaces
|
10
|
+
#
|
11
|
+
# @example Simple Example
|
12
|
+
#
|
13
|
+
# opts = {}
|
14
|
+
# c = Clive.new do
|
15
|
+
# switch(:v, :verbose, "Run verbosely") {
|
16
|
+
# opts[:verbose] = true
|
17
|
+
# }
|
18
|
+
# end
|
19
|
+
# c.parse(ARGV)
|
20
|
+
#
|
21
|
+
class Clive
|
22
|
+
|
23
|
+
attr_accessor :base
|
24
|
+
|
25
|
+
def initialize(&block)
|
26
|
+
@base = Command.new(true, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def parse(argv)
|
30
|
+
@base.run(argv)
|
31
|
+
end
|
32
|
+
|
33
|
+
def switches
|
34
|
+
@base.switches
|
35
|
+
end
|
36
|
+
|
37
|
+
def commands
|
38
|
+
@base.commands
|
39
|
+
end
|
40
|
+
|
41
|
+
def flags
|
42
|
+
@base.flags
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
class Clive
|
2
|
+
|
3
|
+
# A string which describes the command to execute
|
4
|
+
# eg. git add
|
5
|
+
# git pull
|
6
|
+
#
|
7
|
+
class Command
|
8
|
+
|
9
|
+
attr_accessor :switches, :flags, :commands
|
10
|
+
attr_accessor :name, :desc, :block, :argv
|
11
|
+
attr_accessor :base
|
12
|
+
|
13
|
+
# Create a new Command instance
|
14
|
+
#
|
15
|
+
# @overload initialize(base, &block)
|
16
|
+
# Creates a new base Command to house everything else
|
17
|
+
# @param [Boolean] base whether the command is the base
|
18
|
+
#
|
19
|
+
# @overload initialize(name, desc, &block)
|
20
|
+
# Creates a new Command as part of the base Command
|
21
|
+
# @param [Symbol] name the name of the command
|
22
|
+
# @param [String] desc the description of the command
|
23
|
+
#
|
24
|
+
# @yield A block to run, containing switches, flags and commands
|
25
|
+
#
|
26
|
+
def initialize(*args, &block)
|
27
|
+
@argv = []
|
28
|
+
@switches = Clive::Array.new
|
29
|
+
@flags = Clive::Array.new
|
30
|
+
@commands = Clive::Array.new
|
31
|
+
|
32
|
+
if args.length == 1 && args[0] == true
|
33
|
+
@base = true
|
34
|
+
self.instance_eval(&block)
|
35
|
+
else
|
36
|
+
@base = false
|
37
|
+
args.each do |i|
|
38
|
+
case i
|
39
|
+
when Symbol
|
40
|
+
@name = i.to_s
|
41
|
+
when String
|
42
|
+
@desc = i
|
43
|
+
end
|
44
|
+
end
|
45
|
+
@block = block
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Run the block that was passed to find switches, flags, etc.
|
50
|
+
#
|
51
|
+
# This should only be called if the command has been called
|
52
|
+
# as the block could contain other actions to perform only
|
53
|
+
# when called.
|
54
|
+
#
|
55
|
+
def find
|
56
|
+
return nil if @base
|
57
|
+
self.instance_eval(&@block)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Parse the ARGV passed from the command line, and run
|
61
|
+
#
|
62
|
+
# @param [Array] argv the command line input, usually just ARGV
|
63
|
+
# @return [Array] any arguments that were present in the input but not used
|
64
|
+
#
|
65
|
+
def run(argv)
|
66
|
+
tokens = argv
|
67
|
+
tokens = tokenize(argv) if @base
|
68
|
+
|
69
|
+
r = []
|
70
|
+
tokens.each do |i|
|
71
|
+
k, v = i[0], i[1]
|
72
|
+
case k
|
73
|
+
when :command
|
74
|
+
v.run(i[2])
|
75
|
+
when :switch
|
76
|
+
v.run
|
77
|
+
when :flag
|
78
|
+
v.run(i[2])
|
79
|
+
when :argument
|
80
|
+
r << v
|
81
|
+
end
|
82
|
+
end
|
83
|
+
r
|
84
|
+
end
|
85
|
+
|
86
|
+
# Turns the command line input into a series of tokens.
|
87
|
+
# It will only raise errors if this is the base command instance.
|
88
|
+
#
|
89
|
+
# @param [Array] argv the command line input
|
90
|
+
# @return [Array] a series of tokens
|
91
|
+
#
|
92
|
+
# @example
|
93
|
+
#
|
94
|
+
# c.tokenize(["add", "-al", "--verbose"])
|
95
|
+
# #=> [[:command, #<Clive::Command>, ...args...], [:switch, "a",
|
96
|
+
# #<Clive::Switch>], [:switch, "l", #<Clive::Switch>], [:switch,
|
97
|
+
# "verbose", #<Clive::Switch>]]
|
98
|
+
#
|
99
|
+
def tokenize(argv)
|
100
|
+
tokens = []
|
101
|
+
pre = Tokens.to_tokens(argv)
|
102
|
+
command = nil
|
103
|
+
@argv = argv unless @base
|
104
|
+
|
105
|
+
pre.each do |i|
|
106
|
+
k, v = i[0], i[1]
|
107
|
+
case k
|
108
|
+
when :word
|
109
|
+
if @commands[v]
|
110
|
+
command = @commands[v]
|
111
|
+
pre -= [[:word, v]]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
if command
|
117
|
+
command.find
|
118
|
+
# tokenify the command
|
119
|
+
tokens << [:command, command, command.tokenize(Tokens.to_array(pre))]
|
120
|
+
pre = Tokens.to_tokens(command.argv)
|
121
|
+
end
|
122
|
+
|
123
|
+
pre.each do |i|
|
124
|
+
k, v = i[0], i[1]
|
125
|
+
case k
|
126
|
+
when :short, :long
|
127
|
+
if switch = @switches[v]
|
128
|
+
tokens << [:switch, switch]
|
129
|
+
pre -= [[k, v]] unless @base
|
130
|
+
elsif flag = @flags[v]
|
131
|
+
tokens << [:flag, flag]
|
132
|
+
pre -= [[k, v]] unless @base
|
133
|
+
else
|
134
|
+
raise "error, flag/switch '#{v}' does not exist" if @base
|
135
|
+
end
|
136
|
+
when :word
|
137
|
+
if tokens.last
|
138
|
+
case tokens.last[0]
|
139
|
+
when :flag
|
140
|
+
tokens.last[2] = v
|
141
|
+
else
|
142
|
+
tokens << [:argument, v] if @base
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
@argv = Tokens.to_array(pre)
|
148
|
+
|
149
|
+
tokens
|
150
|
+
end
|
151
|
+
|
152
|
+
#### CREATION HELPERS ####
|
153
|
+
|
154
|
+
# Add a new switch to +@switches+
|
155
|
+
#
|
156
|
+
# @overload switch(short, long, desc, &block)
|
157
|
+
# Creates a new switch
|
158
|
+
# @param [Symbol] short single character for short switch, eg. +:v+ => +-v+
|
159
|
+
# @param [Symbol] long longer switch to be used, eg. +:verbose+ => +--verbose+
|
160
|
+
# @param [String] desc the description for the switch
|
161
|
+
#
|
162
|
+
# @yield A block to run if the switch is triggered
|
163
|
+
#
|
164
|
+
def switch(*args, &block)
|
165
|
+
short, long, desc = nil, nil, nil
|
166
|
+
args.each do |i|
|
167
|
+
if i.is_a? String
|
168
|
+
desc = i
|
169
|
+
elsif i.length == 1
|
170
|
+
short = i.to_s
|
171
|
+
else
|
172
|
+
long = i.to_s
|
173
|
+
end
|
174
|
+
end
|
175
|
+
@switches << Switch.new(short, long, desc, &block)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Add a new command to +@commands+
|
179
|
+
#
|
180
|
+
# @overload command(name, desc, &block)
|
181
|
+
# Creates a new command
|
182
|
+
# @param [Symbol] name the name of the command, eg. +:add+ for +git add+
|
183
|
+
# @param [String] desc description of the command
|
184
|
+
#
|
185
|
+
# @yield A block to run when the command is called, can contain switches
|
186
|
+
# and flags
|
187
|
+
#
|
188
|
+
def command(*args, &block)
|
189
|
+
name, desc = nil, nil
|
190
|
+
args.each do |i|
|
191
|
+
if i.is_a? String
|
192
|
+
desc = i
|
193
|
+
else
|
194
|
+
name = i
|
195
|
+
end
|
196
|
+
end
|
197
|
+
@commands << Command.new(name, desc, &block)
|
198
|
+
end
|
199
|
+
|
200
|
+
# Add a new flag to +@flags+
|
201
|
+
#
|
202
|
+
# @overload flag(short, long, desc, &block)
|
203
|
+
# Creates a new flag
|
204
|
+
# @param [Symbol] short single character for short flag, eg. +:t+ => +-t 10+
|
205
|
+
# @param [Symbol] long longer switch to be used, eg. +:tries+ => +--tries=10+
|
206
|
+
# @param [String] desc the description for the flag
|
207
|
+
#
|
208
|
+
# @yield [String] A block to be run if switch is triggered
|
209
|
+
def flag(*args, &block)
|
210
|
+
short, long, desc = nil, nil, nil
|
211
|
+
args.each do |i|
|
212
|
+
if i.is_a? String
|
213
|
+
desc = i
|
214
|
+
elsif i.length == 1
|
215
|
+
short = i.to_s
|
216
|
+
else
|
217
|
+
long = i.to_s
|
218
|
+
end
|
219
|
+
end
|
220
|
+
@flags << Flag.new(short, long, desc, &block)
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
224
|
+
end
|
data/lib/clive/ext.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
class Clive
|
2
|
+
|
3
|
+
class Array < ::Array
|
4
|
+
|
5
|
+
# If passed a Symbol or String will get the item with that name,
|
6
|
+
# checks #long and #short if available or #name. Otherwise does
|
7
|
+
# what you expect of an Array (see Array#[])
|
8
|
+
#
|
9
|
+
# @param [Symbol, String, Integer, Range] val name or index of item to return
|
10
|
+
# @return the item that has been found
|
11
|
+
def [](val)
|
12
|
+
val = val.to_s if val.is_a? Symbol
|
13
|
+
if val.is_a? String
|
14
|
+
if self[0].respond_to?(:name)
|
15
|
+
self.find_all {|i| i.name == val}[0]
|
16
|
+
elsif self[0].respond_to?(:long)
|
17
|
+
if val.length == 1
|
18
|
+
self.find_all {|i| i.short == val}[0]
|
19
|
+
else
|
20
|
+
self.find_all {|i| i.long == val}[0]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
else
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
data/lib/clive/flags.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
class Clive
|
2
|
+
|
3
|
+
# A switch that takes an argument, with or without an equals
|
4
|
+
# eg. wget --tries=10
|
5
|
+
# wget -t 10
|
6
|
+
#
|
7
|
+
class Flag < Switch
|
8
|
+
|
9
|
+
# Runs the block that was given with an argument
|
10
|
+
#
|
11
|
+
# @param [String] arg argument to pass to the block
|
12
|
+
def run(arg)
|
13
|
+
@block.call(arg)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Clive
|
2
|
+
|
3
|
+
# A string that takes no argument, beginning with one or two dashes
|
4
|
+
# eg. ruby --version
|
5
|
+
# ruby -v
|
6
|
+
#
|
7
|
+
class Switch
|
8
|
+
attr_accessor :short, :long, :desc, :block
|
9
|
+
|
10
|
+
def initialize(short, long, desc, &block)
|
11
|
+
@short = short
|
12
|
+
@long = long
|
13
|
+
@desc = desc
|
14
|
+
@block = block
|
15
|
+
end
|
16
|
+
|
17
|
+
# Runs the block that was given
|
18
|
+
def run
|
19
|
+
@block.call
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/lib/clive/tokens.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
class Clive
|
2
|
+
|
3
|
+
# A subclass of Array to allow the creation of arrays that look
|
4
|
+
# like:
|
5
|
+
#
|
6
|
+
# [[:word, 'Value'], [:long, 'verbose'], [:short, 'r']]
|
7
|
+
#
|
8
|
+
# And converting between these and ordinary arrays.
|
9
|
+
#
|
10
|
+
class Tokens < Array
|
11
|
+
attr_accessor :tokens, :array
|
12
|
+
|
13
|
+
def self.to_tokens(tokens)
|
14
|
+
Tokens.new.to_tokens(tokens)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.to_array(arr)
|
18
|
+
Tokens.new.to_array(arr)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Turn into simple tokens that have been split up into logical parts
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
#
|
25
|
+
# a = Tokens.new
|
26
|
+
# a.to_tokens(["add", "-al", "--verbose"])
|
27
|
+
# #=> [[:word, "add"], [:short, "a"], [:short, "l"], [:long, "verbose"]]
|
28
|
+
#
|
29
|
+
def to_tokens(arr=@array)
|
30
|
+
@tokens = []
|
31
|
+
arr.each do |i|
|
32
|
+
if i[0..1] == "--"
|
33
|
+
if i.include?('=')
|
34
|
+
a, b = i[2..i.length].split('=')
|
35
|
+
@tokens << [:long, a] << [:word, b]
|
36
|
+
else
|
37
|
+
@tokens << [:long, i[2..i.length]]
|
38
|
+
end
|
39
|
+
|
40
|
+
elsif i[0] == "-"
|
41
|
+
i[1..i.length].split('').each do |j|
|
42
|
+
tokens << [:short, j]
|
43
|
+
end
|
44
|
+
|
45
|
+
else
|
46
|
+
@tokens << [:word, i]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
@tokens
|
51
|
+
end
|
52
|
+
|
53
|
+
# Turn tokens back to a normal array
|
54
|
+
#
|
55
|
+
# @example
|
56
|
+
#
|
57
|
+
# a = Tokens.new
|
58
|
+
# a.to_array([[:word, "add"], [:short, "a"], [:short, "l"],
|
59
|
+
# [:long, "verbose"]])
|
60
|
+
# #=> ["add", "-al", "--verbose"]
|
61
|
+
#
|
62
|
+
def to_array(tokens=@tokens)
|
63
|
+
@array = []
|
64
|
+
tokens.each do |i|
|
65
|
+
k, v = i[0], i[1]
|
66
|
+
case k
|
67
|
+
when :long
|
68
|
+
@array << "--#{v}"
|
69
|
+
when :short
|
70
|
+
@array << "-#{v}"
|
71
|
+
when :word
|
72
|
+
@array << v
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
@array
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
data/test/bin_test
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
require 'clive'
|
4
|
+
|
5
|
+
options = {}
|
6
|
+
c = Clive.new do
|
7
|
+
|
8
|
+
switch(:v, :verbose, "Run verbosely") {
|
9
|
+
options[:verbose] = true
|
10
|
+
}
|
11
|
+
|
12
|
+
flag(:t, :type, "Type to choose") {
|
13
|
+
p "hi"
|
14
|
+
}
|
15
|
+
|
16
|
+
flag(:long_only, "A flag with only a long") {}
|
17
|
+
|
18
|
+
command(:add, "Add something") {
|
19
|
+
|
20
|
+
p "adding stuff"
|
21
|
+
|
22
|
+
switch(:f, :full, "Use full") {
|
23
|
+
options[:full] = true
|
24
|
+
}
|
25
|
+
|
26
|
+
# Use with app-name add -e
|
27
|
+
switch(:e, :empty, "Use empty") {
|
28
|
+
options[:full] = false
|
29
|
+
}
|
30
|
+
|
31
|
+
}
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
c.parse(ARGV)
|
36
|
+
p options
|
37
|
+
|
data/test/helper.rb
ADDED
data/test/test_clive.rb
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestClive < Test::Unit::TestCase
|
4
|
+
|
5
|
+
should "create flag" do
|
6
|
+
c = Clive.new do
|
7
|
+
flag(:v) {}
|
8
|
+
end
|
9
|
+
assert_equal 1, c.flags.length
|
10
|
+
assert_instance_of Clive::Flag, c.flags[0]
|
11
|
+
end
|
12
|
+
|
13
|
+
should "create switch" do
|
14
|
+
c = Clive.new do
|
15
|
+
switch(:v) {}
|
16
|
+
end
|
17
|
+
assert_equal 1, c.switches.length
|
18
|
+
assert_instance_of Clive::Switch, c.switches[0]
|
19
|
+
end
|
20
|
+
|
21
|
+
should "create command" do
|
22
|
+
c = Clive.new do
|
23
|
+
command(:add) {}
|
24
|
+
end
|
25
|
+
assert_equal 1, c.commands.length
|
26
|
+
assert_instance_of Clive::Command, c.commands[0]
|
27
|
+
end
|
28
|
+
|
29
|
+
context "When parsing input" do
|
30
|
+
|
31
|
+
should "recognise flags with equals" do
|
32
|
+
opts = {}
|
33
|
+
c = Clive.new do
|
34
|
+
flag(:type) {|i| opts[:type] = i}
|
35
|
+
end
|
36
|
+
c.parse(["--type=big"])
|
37
|
+
r = {:type => 'big'}
|
38
|
+
assert_equal r, opts
|
39
|
+
end
|
40
|
+
|
41
|
+
should "recognise flags without equals" do
|
42
|
+
opts = {}
|
43
|
+
c = Clive.new do
|
44
|
+
flag(:type) {|i| opts[:type] = i}
|
45
|
+
end
|
46
|
+
c.parse(["--type", "big"])
|
47
|
+
r = {:type => 'big'}
|
48
|
+
assert_equal r, opts
|
49
|
+
end
|
50
|
+
|
51
|
+
should "recongise short flags" do
|
52
|
+
opts = {}
|
53
|
+
c = Clive.new do
|
54
|
+
flag(:t) {|i| opts[:type] = i}
|
55
|
+
end
|
56
|
+
c.parse(["-t", "big"])
|
57
|
+
r = {:type => 'big'}
|
58
|
+
assert_equal r, opts
|
59
|
+
end
|
60
|
+
|
61
|
+
should "recognise multiple flags" do
|
62
|
+
opts = {}
|
63
|
+
c = Clive.new do
|
64
|
+
flag(:type) {|i| opts[:type] = i}
|
65
|
+
flag(:lang) {|i| opts[:lang] = i}
|
66
|
+
flag(:e) {|i| opts[:e] = i}
|
67
|
+
end
|
68
|
+
c.parse(["--type=big", "--lang", "eng", "-e", "true"])
|
69
|
+
r = {:type => 'big', :lang => 'eng', :e => 'true'}
|
70
|
+
assert_equal r, opts
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
should "recognise switches" do
|
75
|
+
opts = {}
|
76
|
+
c = Clive.new do
|
77
|
+
switch(:v, :verbose) {opts[:verbose] = true}
|
78
|
+
end
|
79
|
+
c.parse(["--verbose"])
|
80
|
+
r = {:verbose => true}
|
81
|
+
assert_equal r, opts
|
82
|
+
end
|
83
|
+
|
84
|
+
should "recognise short switches" do
|
85
|
+
opts = {}
|
86
|
+
c = Clive.new do
|
87
|
+
switch(:v, :verbose) {opts[:verbose] = true}
|
88
|
+
end
|
89
|
+
c.parse(["-v"])
|
90
|
+
r = {:verbose => true}
|
91
|
+
assert_equal r, opts
|
92
|
+
end
|
93
|
+
|
94
|
+
should "recognise multiple switches" do
|
95
|
+
opts = {}
|
96
|
+
c = Clive.new do
|
97
|
+
switch(:v, :verbose) {opts[:verbose] = true}
|
98
|
+
switch(:r, :recursive) {opts[:recursive] = true}
|
99
|
+
end
|
100
|
+
c.parse(["--verbose", "-r"])
|
101
|
+
r = {:verbose => true, :recursive => true}
|
102
|
+
assert_equal r, opts
|
103
|
+
end
|
104
|
+
|
105
|
+
should "recognise multiple combined short switches" do
|
106
|
+
opts = {}
|
107
|
+
c = Clive.new do
|
108
|
+
switch(:v, :verbose) {opts[:verbose] = true}
|
109
|
+
switch(:r, :recursive) {opts[:recursive] = true}
|
110
|
+
end
|
111
|
+
c.parse(["-vr"])
|
112
|
+
r = {:verbose => true, :recursive => true}
|
113
|
+
assert_equal r, opts
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
should "recognise commands" do
|
118
|
+
opts = {}
|
119
|
+
c = Clive.new do
|
120
|
+
command(:add) {opts[:add] = true}
|
121
|
+
end
|
122
|
+
c.parse(["add"])
|
123
|
+
r = {:add => true}
|
124
|
+
assert_equal r, opts
|
125
|
+
end
|
126
|
+
|
127
|
+
should "recognise flags and switches within commands" do
|
128
|
+
opts = {}
|
129
|
+
c = Clive.new do
|
130
|
+
command(:add) {
|
131
|
+
opts[:add] = true
|
132
|
+
|
133
|
+
switch(:v, :verbose) {opts[:verbose] = true}
|
134
|
+
flag(:type) {|i| opts[:type] = i}
|
135
|
+
}
|
136
|
+
end
|
137
|
+
c.parse(["add", "--verbose", "--type=big"])
|
138
|
+
r = {:add => true, :verbose => true, :type => "big"}
|
139
|
+
assert_equal r, opts
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
should "parse a mixture properly" do
|
144
|
+
opts = {}
|
145
|
+
c = Clive.new do
|
146
|
+
switch(:v) {opts[:v] = true}
|
147
|
+
flag(:h) {opts[:h] = true}
|
148
|
+
command(:add) {
|
149
|
+
switch(:full) {opts[:full] = true}
|
150
|
+
}
|
151
|
+
end
|
152
|
+
c.parse(["-v", "add", "--full"])
|
153
|
+
r = {:v => true, :full => true}
|
154
|
+
assert_equal r, opts
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
should "return unused arguments" do
|
159
|
+
opts = {}
|
160
|
+
c = Clive.new do
|
161
|
+
switch(:v) {opts[:v] = true}
|
162
|
+
flag(:h) {opts[:h] = true}
|
163
|
+
command(:add) {
|
164
|
+
switch(:full) {opts[:full] = true}
|
165
|
+
}
|
166
|
+
end
|
167
|
+
result = c.parse(["-v", "add", "--full", "truearg"])
|
168
|
+
assert_equal ["truearg"], result
|
169
|
+
end
|
170
|
+
|
171
|
+
should "return multiple unused arguments" do
|
172
|
+
opts = {}
|
173
|
+
c = Clive.new do
|
174
|
+
switch(:v) {opts[:v] = true}
|
175
|
+
flag(:h) {opts[:h] = true}
|
176
|
+
command(:add) {
|
177
|
+
switch(:full) {opts[:full] = true}
|
178
|
+
}
|
179
|
+
end
|
180
|
+
result = c.parse(["-v", "onearg", "twoarg", "/usr/bin/env"])
|
181
|
+
assert_equal ["onearg", "twoarg", "/usr/bin/env"], result
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
context "When parsing ridiculous edge tests" do
|
188
|
+
|
189
|
+
should "parse this crazy guy" do
|
190
|
+
opts = {}
|
191
|
+
c = Clive.new do
|
192
|
+
switch(:v) {opts[:v] = true}
|
193
|
+
flag(:h) {opts[:h] = true}
|
194
|
+
|
195
|
+
command(:add) {
|
196
|
+
opts[:add] = {}
|
197
|
+
switch(:full) {opts[:add][:full] = true}
|
198
|
+
flag(:breed) {|i| opts[:add][:breed] = i}
|
199
|
+
|
200
|
+
command(:init) {
|
201
|
+
opts[:add][:init] = {}
|
202
|
+
switch(:base) {opts[:add][:init][:base] = true}
|
203
|
+
flag(:name) {|i| opts[:add][:init][:name] = i}
|
204
|
+
}
|
205
|
+
}
|
206
|
+
end
|
207
|
+
c.parse(["-v", "add", "--full", "init", "--base", "--name=Works"])
|
208
|
+
r = {:v => true, :add => {:full => true, :init => {:base => true, :name => 'Works'}} }
|
209
|
+
assert_equal r, opts
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: clive
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Joshua Hawxwell
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-08-14 00:00:00 +01:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: thoughtbot-shoulda
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: yard
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id002
|
49
|
+
description: Clive is a DSL for creating a command line interface. It is for people who, like me, love OptionParser's syntax and love GLI's commands.
|
50
|
+
email: m@hawx.me
|
51
|
+
executables: []
|
52
|
+
|
53
|
+
extensions: []
|
54
|
+
|
55
|
+
extra_rdoc_files:
|
56
|
+
- LICENSE
|
57
|
+
- README.md
|
58
|
+
files:
|
59
|
+
- .document
|
60
|
+
- .gitignore
|
61
|
+
- LICENSE
|
62
|
+
- README.md
|
63
|
+
- Rakefile
|
64
|
+
- VERSION
|
65
|
+
- lib/clive.rb
|
66
|
+
- lib/clive/commands.rb
|
67
|
+
- lib/clive/ext.rb
|
68
|
+
- lib/clive/flags.rb
|
69
|
+
- lib/clive/switches.rb
|
70
|
+
- lib/clive/tokens.rb
|
71
|
+
- test/bin_test
|
72
|
+
- test/helper.rb
|
73
|
+
- test/test_clive.rb
|
74
|
+
has_rdoc: true
|
75
|
+
homepage: http://github.com/hawx/clive
|
76
|
+
licenses: []
|
77
|
+
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options:
|
80
|
+
- --charset=UTF-8
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
hash: 3
|
89
|
+
segments:
|
90
|
+
- 0
|
91
|
+
version: "0"
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
hash: 3
|
98
|
+
segments:
|
99
|
+
- 0
|
100
|
+
version: "0"
|
101
|
+
requirements: []
|
102
|
+
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 1.3.7
|
105
|
+
signing_key:
|
106
|
+
specification_version: 3
|
107
|
+
summary: Imagine if optparse and gli had a son called clive.
|
108
|
+
test_files:
|
109
|
+
- test/helper.rb
|
110
|
+
- test/test_clive.rb
|