cp 0.0.1.pre1 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +55 -0
- data/cp.gemspec +1 -2
- data/examples/net.rb +12 -9
- data/lib/cp.rb +23 -9
- data/lib/cp/app.rb +18 -4
- data/lib/cp/command.rb +38 -7
- data/lib/cp/errors.rb +4 -0
- data/lib/cp/has/commands.rb +34 -0
- data/lib/cp/has/options.rb +17 -0
- data/lib/cp/option.rb +22 -2
- data/lib/cp/options_struct.rb +7 -0
- data/lib/cp/runners/cmd_parse.rb +32 -11
- data/lib/cp/version.rb +1 -1
- data/spec/app_spec.rb +29 -16
- data/spec/command_spec.rb +10 -4
- data/spec/cp_spec.rb +1 -1
- data/spec/has/commands_spec.rb +10 -0
- data/spec/has/options_spec.rb +10 -0
- data/spec/option_spec.rb +43 -15
- data/spec/options_struct_spec.rb +7 -0
- data/spec/runners/cmd_parse_spec.rb +6 -38
- data/spec/support/lib/test_app_runner.rb +16 -0
- data/spec/support/shared_examples/commands_examples.rb +52 -0
- data/spec/support/shared_examples/options_examples.rb +23 -0
- data/spec/support/shared_examples/runner_examples.rb +173 -0
- data/spec/support/spec_helper.rb +38 -12
- metadata +25 -23
- data/lib/cp/commands.rb +0 -15
- data/lib/cp/options.rb +0 -15
- data/spec/options_spec.rb +0 -10
- data/spec/support/shared_helper.rb +0 -47
data/README.md
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
CP
|
2
|
+
==
|
3
|
+
|
4
|
+
Write complex command line scripts simply.
|
5
|
+
|
6
|
+
CP provides an API to write command line scripts with nested subcommands
|
7
|
+
and switches.
|
8
|
+
|
9
|
+
Rather than reinventing the wheel, CP calls on the time tested [CmdParse][] gem
|
10
|
+
to do the heavy lifting, wrapping it in an updated API and adding a handful of
|
11
|
+
new features.
|
12
|
+
|
13
|
+
Roadmap
|
14
|
+
-------
|
15
|
+
|
16
|
+
CP aims to provide:
|
17
|
+
|
18
|
+
* Automatic help generation
|
19
|
+
* Any number of nested subcommands
|
20
|
+
* Global and command-specific switches
|
21
|
+
|
22
|
+
Credit
|
23
|
+
------
|
24
|
+
|
25
|
+
CP's API was inspired by the Commander gem.
|
26
|
+
|
27
|
+
I decided I didn't personally like parts
|
28
|
+
of the Commander API. I forked Commander and began adding features, but found
|
29
|
+
it difficult to extend, and started work on CP instead.
|
30
|
+
|
31
|
+
[CmdParse]: http://cmdparse.rubyforge.org/
|
32
|
+
|
33
|
+
License
|
34
|
+
-------
|
35
|
+
|
36
|
+
(The MIT License)
|
37
|
+
|
38
|
+
Copyright © 2011 Rick Fletcher <fletch@pobox.com>
|
39
|
+
|
40
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
41
|
+
this software and associated documentation files (the "Software"), to deal in
|
42
|
+
the Software without restriction, including without limitation the rights to
|
43
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
44
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
45
|
+
subject to the following conditions:
|
46
|
+
|
47
|
+
The above copyright notice and this permission notice shall be included in all
|
48
|
+
copies or substantial portions of the Software.
|
49
|
+
|
50
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
51
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
52
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
53
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
54
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
55
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/cp.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |s|
|
|
8
8
|
s.platform = Gem::Platform::RUBY
|
9
9
|
s.authors = ["Rick Fletcher"]
|
10
10
|
s.email = ["fletch@pobox.com"]
|
11
|
-
s.homepage = ""
|
11
|
+
s.homepage = "https://github.com/baseballdb/cp"
|
12
12
|
s.summary = %q{An alternative API for CmdParse}
|
13
13
|
s.description = %q{CP provides a less verbose API for the CmdParse gem.}
|
14
14
|
|
@@ -18,5 +18,4 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.require_paths = ["lib"]
|
19
19
|
|
20
20
|
s.add_runtime_dependency(%q<cmdparse>, [">= 2.0.2"])
|
21
|
-
s.add_runtime_dependency(%q<cmdparse>, [">= 2.0.2"])
|
22
21
|
end
|
data/examples/net.rb
CHANGED
@@ -1,13 +1,18 @@
|
|
1
1
|
##
|
2
2
|
# A port of the CmdParse tutorial app, net.rb
|
3
|
+
#
|
4
|
+
# This app doesn't really do much of anything. It's meant as an example of
|
5
|
+
# the difference in API from CmdParse to CP.
|
6
|
+
#
|
3
7
|
# original: http://cmdparse.rubyforge.org/tutorial.html
|
4
8
|
#
|
5
9
|
|
6
10
|
$: << File.join( ".", File.dirname( __FILE__ ), "..", "lib" )
|
7
11
|
|
12
|
+
require "yaml"
|
8
13
|
require "cp"
|
9
14
|
|
10
|
-
|
15
|
+
extend CP
|
11
16
|
|
12
17
|
$ipaddrs = []
|
13
18
|
$verbose = false
|
@@ -16,18 +21,18 @@ app :name, "net"
|
|
16
21
|
app :version, "0.1.1"
|
17
22
|
|
18
23
|
# global options
|
19
|
-
option
|
24
|
+
option "--verbose", "Be verbose when outputting info", lambda { |v| $verbose = true }
|
20
25
|
|
21
26
|
# add the top-level "ipaddr" command
|
22
27
|
command :ipaddr do |c|
|
23
|
-
|
28
|
+
c.default = true
|
24
29
|
c.summary = "Manage IP addresses"
|
25
30
|
|
26
31
|
# add the "add" subcommand
|
27
32
|
c.command( :add ) do |s|
|
28
33
|
s.summary = "Add an IP address"
|
29
34
|
|
30
|
-
s.execute do
|
35
|
+
s.execute do |opts, args|
|
31
36
|
puts "Adding IP addresses: #{args.join(', ')}" if $verbose
|
32
37
|
$ipaddrs += args
|
33
38
|
end
|
@@ -39,7 +44,7 @@ command :ipaddr do |c|
|
|
39
44
|
|
40
45
|
s.option( "-a", "--all", "Delete all IP addresses" ){ $deleteAll = true }
|
41
46
|
|
42
|
-
s.execute do
|
47
|
+
s.execute do |opts, args|
|
43
48
|
if $deleteAll
|
44
49
|
$ipaddrs = []
|
45
50
|
else
|
@@ -51,7 +56,7 @@ command :ipaddr do |c|
|
|
51
56
|
|
52
57
|
# add the "list" subcommand
|
53
58
|
c.command :list do |s|
|
54
|
-
|
59
|
+
s.default = true
|
55
60
|
s.summary = "List all IP addresses"
|
56
61
|
|
57
62
|
s.execute do |*args|
|
@@ -65,7 +70,7 @@ command :ipaddr do |c|
|
|
65
70
|
s.summary = "Show network statistics"
|
66
71
|
s.description = "This command shows very useful network statistics - eye catching!!!"
|
67
72
|
|
68
|
-
s.execute do
|
73
|
+
s.execute do |opts, args|
|
69
74
|
puts "Showing network statistics" if $verbose
|
70
75
|
puts
|
71
76
|
puts "Yeah, I will do something now..."
|
@@ -77,5 +82,3 @@ command :ipaddr do |c|
|
|
77
82
|
end
|
78
83
|
end
|
79
84
|
end
|
80
|
-
|
81
|
-
run
|
data/lib/cp.rb
CHANGED
@@ -1,22 +1,36 @@
|
|
1
1
|
require "rubygems"
|
2
2
|
require "cmdparse"
|
3
|
+
require "optparse"
|
3
4
|
|
4
|
-
require "cp/
|
5
|
-
require "cp/
|
6
|
-
|
5
|
+
require "cp/has/commands"
|
6
|
+
require "cp/has/options"
|
7
|
+
|
8
|
+
require "cp/app"
|
7
9
|
require "cp/command"
|
8
|
-
require "cp/
|
10
|
+
require "cp/errors"
|
9
11
|
require "cp/option"
|
10
|
-
require "cp/
|
12
|
+
require "cp/options_struct"
|
13
|
+
require "cp/runners/cmd_parse"
|
14
|
+
require "cp/version"
|
11
15
|
|
12
16
|
module CP
|
13
|
-
def self.included(
|
14
|
-
|
17
|
+
def self.included( klass )
|
18
|
+
klass.extend( self )
|
19
|
+
klass.class_eval do
|
20
|
+
def self.run
|
21
|
+
CP::App.run
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.extended( instance )
|
27
|
+
is_main = instance.class === Object && instance.inspect === "main"
|
28
|
+
at_exit { CP::App.run } if is_main
|
15
29
|
end
|
16
30
|
|
17
|
-
[:app, :command, :option
|
31
|
+
[:app, :command, :option].each do |method|
|
18
32
|
define_method( method ) { |*args, &block|
|
19
|
-
App.instance.send( method, *args, &block )
|
33
|
+
CP::App.instance.send( method, *args, &block )
|
20
34
|
}
|
21
35
|
end
|
22
36
|
end
|
data/lib/cp/app.rb
CHANGED
@@ -4,12 +4,13 @@ module CP
|
|
4
4
|
class App
|
5
5
|
include ::Singleton
|
6
6
|
|
7
|
-
include CP::Commands
|
8
|
-
include CP::Options
|
7
|
+
include CP::Has::Commands
|
8
|
+
include CP::Has::Options
|
9
9
|
|
10
10
|
attr_accessor :name, :version, :runner
|
11
11
|
|
12
12
|
def initialize
|
13
|
+
self.name = File.basename( $0 )
|
13
14
|
self.runner = CP::Runners::CmdParse
|
14
15
|
end
|
15
16
|
|
@@ -21,8 +22,21 @@ module CP
|
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
24
|
-
def
|
25
|
-
self.
|
25
|
+
def fatal( msg )
|
26
|
+
self.error( msg )
|
27
|
+
exit 1
|
28
|
+
end
|
29
|
+
|
30
|
+
def error( msg )
|
31
|
+
$stderr.puts "#{self.name}: #{msg}. See `#{self.name} --help`."
|
32
|
+
end
|
33
|
+
|
34
|
+
def run( *args )
|
35
|
+
CP::App.instance.runner.new( CP::App.instance ).run( *args )
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.run( *args )
|
39
|
+
CP::App.instance.run( *args )
|
26
40
|
end
|
27
41
|
end
|
28
42
|
end
|
data/lib/cp/command.rb
CHANGED
@@ -1,23 +1,54 @@
|
|
1
1
|
module CP
|
2
2
|
class Command
|
3
|
-
include CP::Commands
|
4
|
-
include CP::Options
|
3
|
+
include CP::Has::Commands
|
4
|
+
include CP::Has::Options
|
5
5
|
|
6
|
-
attr_reader :block, :name
|
7
|
-
attr_accessor :description, :summary
|
6
|
+
attr_reader :block, :name, :parent
|
7
|
+
attr_accessor :default, :description, :summary
|
8
8
|
|
9
|
-
def initialize( name )
|
9
|
+
def initialize( name, parent=nil )
|
10
10
|
unless name.respond_to?( :to_sym )
|
11
|
-
raise ArgumentError.new( "
|
11
|
+
raise ArgumentError.new( "name must be a Symbol (or respond to .to_sym)")
|
12
12
|
end
|
13
13
|
|
14
|
+
@parent = parent
|
14
15
|
@name = name.to_sym
|
15
16
|
|
16
17
|
yield self if block_given?
|
17
18
|
end
|
18
19
|
|
19
20
|
def execute( &block )
|
20
|
-
@block =
|
21
|
+
@block = lambda { |args|
|
22
|
+
begin
|
23
|
+
opts = gather_options
|
24
|
+
rescue CP::MissingOptionError => e
|
25
|
+
CP::App.instance.fatal( "'#{e}' is required" )
|
26
|
+
end
|
27
|
+
|
28
|
+
block.call( args, opts )
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def gather_options
|
35
|
+
options = []
|
36
|
+
|
37
|
+
current = self
|
38
|
+
begin
|
39
|
+
options += current.options
|
40
|
+
current = current.respond_to?( :parent ) ? current.parent : nil
|
41
|
+
end while current && current.respond_to?( :options )
|
42
|
+
|
43
|
+
option_names = options.map { |o|
|
44
|
+
raise CP::MissingOptionError.new( o.switches.last ) if o.required? && o.value.nil?
|
45
|
+
o.name
|
46
|
+
}.sort.uniq
|
47
|
+
|
48
|
+
struct = CP::OptionsStruct.new( *option_names )
|
49
|
+
|
50
|
+
options.reverse.each { |o| struct[o.name] = o.value }
|
51
|
+
struct
|
21
52
|
end
|
22
53
|
end
|
23
54
|
end
|
data/lib/cp/errors.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
module CP
|
2
|
+
module Has
|
3
|
+
module Commands
|
4
|
+
def commands
|
5
|
+
@commands ||= []
|
6
|
+
@commands
|
7
|
+
end
|
8
|
+
|
9
|
+
def command( name )
|
10
|
+
cmd = name.to_s.split( " " ).inject( self ) do |parent, name|
|
11
|
+
subcommand = parent.commands.find { |c| c.name === name.to_sym }
|
12
|
+
|
13
|
+
if subcommand.nil?
|
14
|
+
subcommand = CP::Command.new( name, parent )
|
15
|
+
parent.commands << subcommand
|
16
|
+
end
|
17
|
+
|
18
|
+
subcommand
|
19
|
+
end
|
20
|
+
|
21
|
+
yield cmd if block_given?
|
22
|
+
|
23
|
+
if cmd.default
|
24
|
+
default_cmd = commands.find{ |c| c.default }
|
25
|
+
if default_cmd
|
26
|
+
raise CP::CommandError.new( "only one default command is allowed: #{default_cmd.name}, #{cmd.name}" )
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
cmd
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/cp/option.rb
CHANGED
@@ -2,11 +2,12 @@ require "optparse"
|
|
2
2
|
|
3
3
|
module CP
|
4
4
|
class Option
|
5
|
-
attr_accessor :allowed, :
|
6
|
-
attr_reader :arg, :short, :long
|
5
|
+
attr_accessor :allowed, :default, :description, :type
|
6
|
+
attr_reader :arg, :block, :short, :long
|
7
7
|
|
8
8
|
# see: OptionsParser#make_switch
|
9
9
|
def initialize( *args )
|
10
|
+
@required = false
|
10
11
|
parsed = parse_args( args )
|
11
12
|
|
12
13
|
yield self if block_given?
|
@@ -19,6 +20,13 @@ module CP
|
|
19
20
|
end
|
20
21
|
end
|
21
22
|
|
23
|
+
def block=( block=nil )
|
24
|
+
@block = lambda { |value|
|
25
|
+
@value = value
|
26
|
+
block.call( *( block.arity === 0 ? [] : [value] ) ) if block
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
22
30
|
def long=( val )
|
23
31
|
set_switch( :long, val )
|
24
32
|
end
|
@@ -32,6 +40,14 @@ module CP
|
|
32
40
|
name.gsub( "-", "_" ).to_sym
|
33
41
|
end
|
34
42
|
|
43
|
+
def required=( val )
|
44
|
+
@required = !!val
|
45
|
+
end
|
46
|
+
|
47
|
+
def required?
|
48
|
+
@required
|
49
|
+
end
|
50
|
+
|
35
51
|
def short=( val )
|
36
52
|
set_switch( :short, val )
|
37
53
|
end
|
@@ -51,6 +67,10 @@ module CP
|
|
51
67
|
args.find_all { |a| !a.nil? }
|
52
68
|
end
|
53
69
|
|
70
|
+
def value
|
71
|
+
@value.nil? ? self.default : @value
|
72
|
+
end
|
73
|
+
|
54
74
|
private
|
55
75
|
|
56
76
|
def parse_args( args )
|
data/lib/cp/runners/cmd_parse.rb
CHANGED
@@ -4,21 +4,42 @@ require "cmdparse"
|
|
4
4
|
module CP
|
5
5
|
module Runners
|
6
6
|
class CmdParse
|
7
|
-
attr_reader :runner
|
8
|
-
|
9
7
|
def initialize( app )
|
10
8
|
@app = app
|
11
9
|
end
|
12
10
|
|
13
|
-
def run
|
14
|
-
|
15
|
-
|
16
|
-
|
11
|
+
def run( *args )
|
12
|
+
args = args.empty? ? args : [args]
|
13
|
+
command_name = ''
|
14
|
+
|
15
|
+
begin
|
16
|
+
runner.parse( *args ) { |l, c| command_name << "#{c} " if l > 0 }
|
17
|
+
|
18
|
+
rescue ::CmdParse::InvalidCommandError => e
|
19
|
+
command_name << e.message.gsub( /^.*: /, '' )
|
20
|
+
CP::App.instance.fatal( "'#{command_name}' is not a #{CP::App.instance.name} command." )
|
17
21
|
|
18
|
-
|
19
|
-
|
22
|
+
rescue ::CmdParse::InvalidOptionError,
|
23
|
+
::OptionParser::InvalidOption => e
|
24
|
+
switch = e.message.gsub( /^.*: /, '' )
|
25
|
+
CP::App.instance.fatal( "'#{switch}' is not a valid option." )
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def runner
|
30
|
+
runner = ::CmdParse::CommandParser.new
|
31
|
+
runner.program_name = @app.name
|
32
|
+
runner.program_version = @app.version ? @app.version.split( "." ) : nil
|
33
|
+
|
34
|
+
add_options( runner, @app.options )
|
35
|
+
add_commands( runner, @app.commands )
|
36
|
+
|
37
|
+
unless @app.commands.find { |c| c.name === :help }
|
38
|
+
has_default_command = @app.commands.find { |c| c.default }
|
39
|
+
runner.add_command( ::CmdParse::HelpCommand.new, !has_default_command )
|
40
|
+
end
|
20
41
|
|
21
|
-
|
42
|
+
runner
|
22
43
|
end
|
23
44
|
|
24
45
|
private
|
@@ -40,13 +61,13 @@ module CP
|
|
40
61
|
cmd.short_desc = c.summary
|
41
62
|
|
42
63
|
cmd.set_execution_block do |args|
|
43
|
-
c.block.call( args )
|
64
|
+
c.block.call( args )
|
44
65
|
end
|
45
66
|
|
46
67
|
add_options( cmd, c.options )
|
47
68
|
add_commands( cmd, c.commands )
|
48
69
|
|
49
|
-
target.add_command( cmd )
|
70
|
+
target.add_command( cmd, c.default )
|
50
71
|
end
|
51
72
|
end
|
52
73
|
end
|