climate 0.1.0
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/README +0 -0
- data/bin/climate +14 -0
- data/lib/climate/argument.rb +30 -0
- data/lib/climate/command.rb +154 -0
- data/lib/climate/errors.rb +45 -0
- data/lib/climate/help.rb +148 -0
- data/lib/climate/option.rb +61 -0
- data/lib/climate/parser.rb +95 -0
- data/lib/climate/script.rb +43 -0
- data/lib/climate/version.rb +3 -0
- data/lib/climate.rb +40 -0
- metadata +117 -0
data/README
ADDED
File without changes
|
data/bin/climate
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'climate'
|
4
|
+
scriptfile = ARGV.shift or begin
|
5
|
+
$stderr.puts("Path to ruby script expected")
|
6
|
+
$stderr.puts("usage: climate <script> <arguments...>")
|
7
|
+
exit 1
|
8
|
+
end
|
9
|
+
|
10
|
+
# if climate is used to invoke a script, we can not use $PROGRAM_NAME to
|
11
|
+
# identify the script, so we remember it from here
|
12
|
+
$CLIMATE_PROGRAM_NAME = scriptfile
|
13
|
+
|
14
|
+
load scriptfile
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Climate
|
2
|
+
class Argument
|
3
|
+
|
4
|
+
attr_reader :name
|
5
|
+
attr_reader :description
|
6
|
+
|
7
|
+
def initialize(name, description, options={})
|
8
|
+
@name = name
|
9
|
+
@description = description
|
10
|
+
@required = options.fetch(:required, true)
|
11
|
+
end
|
12
|
+
|
13
|
+
def required? ; @required ; end
|
14
|
+
def optional? ; ! required? ; end
|
15
|
+
|
16
|
+
def usage
|
17
|
+
string = "<#{name}>"
|
18
|
+
|
19
|
+
if optional?
|
20
|
+
"[#{string}]"
|
21
|
+
else
|
22
|
+
string
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def formatted
|
27
|
+
required?? name.to_s.upcase : "[#{name.to_s.upcase}]"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
module Climate
|
2
|
+
|
3
|
+
#
|
4
|
+
# A {Command} is a unit of work, intended to be invoked from the command line. It should be
|
5
|
+
# extended to either do something itself by implementing run, or just be
|
6
|
+
# there as a conduit for subcommands to do their work.
|
7
|
+
#
|
8
|
+
# See {ParsingMethods} for details on how to specify options and arguments
|
9
|
+
#
|
10
|
+
class Command
|
11
|
+
|
12
|
+
class << self
|
13
|
+
include ParsingMethods
|
14
|
+
|
15
|
+
# Create an instance of this command class and run it against the given
|
16
|
+
# arguments
|
17
|
+
# @param [Array<String>] arguments A list of arguments, ARGV style
|
18
|
+
# @param [Hash] options see {#initialize}
|
19
|
+
def run(arguments, options={})
|
20
|
+
begin
|
21
|
+
instance = new(arguments, options)
|
22
|
+
rescue Trollop::HelpNeeded
|
23
|
+
raise HelpNeeded.new(self)
|
24
|
+
end
|
25
|
+
|
26
|
+
if subcommands.empty?
|
27
|
+
instance.run
|
28
|
+
else
|
29
|
+
find_and_run_subcommand(instance, options)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def ancestors(exclude_self=false)
|
34
|
+
our_list = exclude_self ? [] : [self]
|
35
|
+
parent.nil?? our_list : parent.ancestors + our_list
|
36
|
+
end
|
37
|
+
|
38
|
+
# Supply a name for this command, or return the existing name
|
39
|
+
def name(name=nil)
|
40
|
+
@name = name if name
|
41
|
+
@name
|
42
|
+
end
|
43
|
+
|
44
|
+
# Register this class as being a subcommand of another {Command} class
|
45
|
+
# @param [Command] parent_class The parent we hang off of
|
46
|
+
def subcommand_of(parent_class)
|
47
|
+
raise DefinitionError, 'can not set subcommand before name' unless @name
|
48
|
+
parent_class.add_subcommand(self)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Set the description for this command
|
52
|
+
# @param [String] string Description/Banner/Help text
|
53
|
+
def description(string=nil)
|
54
|
+
if string
|
55
|
+
@description = string
|
56
|
+
else
|
57
|
+
@description
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Set the parent of this command
|
62
|
+
attr_accessor :parent
|
63
|
+
|
64
|
+
def add_subcommand(subcommand)
|
65
|
+
if cli_arguments.empty?
|
66
|
+
subcommands << subcommand
|
67
|
+
subcommand.parent = self
|
68
|
+
stop_on(subcommands.map(&:name))
|
69
|
+
else
|
70
|
+
raise DefinitionError, 'can not mix subcommands with arguments'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def arg(*args)
|
75
|
+
if subcommands.empty?
|
76
|
+
super(*args)
|
77
|
+
else
|
78
|
+
raise DefinitionError, 'can not mix subcommands with arguments'
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def has_subcommands? ; not subcommands.empty? ; end
|
83
|
+
def subcommands ; @subcommands ||= [] ; end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def find_and_run_subcommand(parent, options)
|
88
|
+
command_name, *arguments = parent.leftovers
|
89
|
+
|
90
|
+
if command_name.nil?
|
91
|
+
raise MissingArgumentError.new(parent, "command #{parent.class.name}" +
|
92
|
+
" expects a subcommand as an argument")
|
93
|
+
end
|
94
|
+
|
95
|
+
found = subcommands.find {|c| c.name == command_name }
|
96
|
+
|
97
|
+
if found
|
98
|
+
found.run(arguments, options.merge(:parent => parent))
|
99
|
+
else
|
100
|
+
raise UnknownCommandError.new(parent, command_name)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
# Create an instance of this command to be run. You'll probably want to use
|
107
|
+
# {Command.run}
|
108
|
+
# @param [Array<String>] arguments ARGV style arguments to be parsed
|
109
|
+
# @option options [Command] :parent The parent command, made available as {#parent}
|
110
|
+
# @option options [IO] :stdout stream to use as stdout, defaulting to `$stdout`
|
111
|
+
# @option options [IO] :stderr stream to use as stderr, defaulting to `$stderr`
|
112
|
+
# @option options [IO] :stdin stream to use as stdin, defaulting to `$stdin`
|
113
|
+
def initialize(arguments, options={})
|
114
|
+
@parent = options[:parent]
|
115
|
+
|
116
|
+
@stdout = options[:stdout] || $stdout
|
117
|
+
@stderr = options[:stderr] || $stderr
|
118
|
+
@stdin = options[:stdin] || $stdin
|
119
|
+
|
120
|
+
@arguments, @options, @leftovers = self.class.parse(arguments)
|
121
|
+
end
|
122
|
+
|
123
|
+
# @return [Hash]
|
124
|
+
# Options that were parsed from the command line
|
125
|
+
attr_accessor :options
|
126
|
+
|
127
|
+
# @return [Hash]
|
128
|
+
# Arguments that were given on the command line
|
129
|
+
attr_accessor :arguments
|
130
|
+
|
131
|
+
# @return [Array]
|
132
|
+
# Unparsed arguments, usually for subcommands
|
133
|
+
attr_accessor :leftovers
|
134
|
+
|
135
|
+
# @return [Command]
|
136
|
+
# The parent command, or nil if this is not a subcommand
|
137
|
+
attr_accessor :parent
|
138
|
+
|
139
|
+
# @return [IO]
|
140
|
+
# a possibly redirected stream
|
141
|
+
attr_accessor :stdout, :stderr, :stdin
|
142
|
+
|
143
|
+
def ancestor(ancestor_class, include_self=true)
|
144
|
+
if include_self && self.class == ancestor_class
|
145
|
+
self
|
146
|
+
elsif parent.nil?
|
147
|
+
raise "no ancestor exists: #{ancestor_class}"
|
148
|
+
nil
|
149
|
+
else
|
150
|
+
parent.ancestor(ancestor_class)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Climate
|
2
|
+
class Error < ::StandardError ; end
|
3
|
+
|
4
|
+
# Raised when {Command} class methods are used incorrectly
|
5
|
+
class DefinitionError < Error ; end
|
6
|
+
|
7
|
+
# Raised by an instance of a {Command} when something went wrong during
|
8
|
+
# execution
|
9
|
+
class CommandError < Error
|
10
|
+
|
11
|
+
# The command that raised the error
|
12
|
+
attr_reader :command_class
|
13
|
+
|
14
|
+
def initialize(command_or_class, msg=nil)
|
15
|
+
@command_class = command_or_class.is_a?(Command) ? command_or_class.class :
|
16
|
+
command_or_class
|
17
|
+
super(msg)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Command instances can raise this error to exit
|
22
|
+
class ExitException < CommandError
|
23
|
+
|
24
|
+
attr_reader :exit_code
|
25
|
+
# some libraries (popen, process?) refer to this as exitcode without a _
|
26
|
+
alias :exitcode :exit_code
|
27
|
+
|
28
|
+
def initialize(command_class, msg, exit_code=1)
|
29
|
+
@exit_code = exit_code
|
30
|
+
super(command_class, msg)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class HelpNeeded < CommandError ; end
|
35
|
+
|
36
|
+
# Raised when a {Command} is run with too many arguments
|
37
|
+
class UnexpectedArgumentError < CommandError ; end
|
38
|
+
|
39
|
+
# Raised when a {Command} is run with insufficient arguments
|
40
|
+
class MissingArgumentError < CommandError ; end
|
41
|
+
|
42
|
+
# Raised when a parent {Command} is asked to run a sub command it does not
|
43
|
+
# know about
|
44
|
+
class UnknownCommandError < CommandError ; end
|
45
|
+
end
|
data/lib/climate/help.rb
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
module Climate
|
2
|
+
class Help
|
3
|
+
|
4
|
+
attr_reader :command_class
|
5
|
+
|
6
|
+
def initialize(command_class, options={})
|
7
|
+
@command_class = command_class
|
8
|
+
@indent = 0
|
9
|
+
@output = options[:output] || $stdout
|
10
|
+
end
|
11
|
+
|
12
|
+
def print
|
13
|
+
print_usage
|
14
|
+
print_description
|
15
|
+
print_options if command_class.has_options? || command_class.has_arguments?
|
16
|
+
print_subcommands if command_class.has_subcommands?
|
17
|
+
end
|
18
|
+
|
19
|
+
def print_usage
|
20
|
+
ancestor_list = command_class.ancestors.map(&:name).join(' ')
|
21
|
+
opts_usage = command_class.cli_options.map {|opt| opt.usage }.join(' ')
|
22
|
+
args_usage =
|
23
|
+
if command_class.has_subcommands?
|
24
|
+
"<subcommand> [<arguments>]"
|
25
|
+
else
|
26
|
+
command_class.cli_arguments.map {|arg| arg.usage }.join(' ')
|
27
|
+
end
|
28
|
+
puts("usage: #{ancestor_list} #{opts_usage} #{args_usage}")
|
29
|
+
end
|
30
|
+
|
31
|
+
def print_description
|
32
|
+
newline
|
33
|
+
puts "Description"
|
34
|
+
indent do
|
35
|
+
puts(command_class.description || '')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def print_subcommands
|
40
|
+
newline
|
41
|
+
puts "Available subcommands:"
|
42
|
+
indent do
|
43
|
+
command_class.subcommands.each do |subcommand_class|
|
44
|
+
puts "#{subcommand_class.name}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def print_options
|
50
|
+
newline
|
51
|
+
puts "Options"
|
52
|
+
indent do
|
53
|
+
|
54
|
+
if command_class.has_subcommands?
|
55
|
+
puts "<subcommand>"
|
56
|
+
indent do
|
57
|
+
puts "Name of subcommand to execute"
|
58
|
+
end
|
59
|
+
newline
|
60
|
+
puts "<arguments>"
|
61
|
+
indent do
|
62
|
+
puts "Arguments for subcommand"
|
63
|
+
end
|
64
|
+
newline
|
65
|
+
end
|
66
|
+
|
67
|
+
command_class.cli_arguments.each do |argument|
|
68
|
+
puts "<#{argument.name}>"
|
69
|
+
indent do
|
70
|
+
puts argument.description
|
71
|
+
end
|
72
|
+
newline
|
73
|
+
end
|
74
|
+
|
75
|
+
command_class.cli_options.each do |option|
|
76
|
+
puts "#{option.usage(:with_long => true, :hide_optional => true, :separator => ', ')}"
|
77
|
+
indent do
|
78
|
+
puts option.description
|
79
|
+
end
|
80
|
+
newline
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def indent(&block)
|
86
|
+
@indent += 1
|
87
|
+
yield if block_given?
|
88
|
+
unindent if block_given?
|
89
|
+
end
|
90
|
+
|
91
|
+
def unindent
|
92
|
+
@indent -= 1
|
93
|
+
end
|
94
|
+
|
95
|
+
def spaces
|
96
|
+
@indent * 4
|
97
|
+
end
|
98
|
+
|
99
|
+
def newline
|
100
|
+
@output.puts("\n")
|
101
|
+
end
|
102
|
+
|
103
|
+
def puts(string='')
|
104
|
+
string.split("\n").each do |line|
|
105
|
+
@output.puts((' ' * spaces) + line)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
# stolen from trollop
|
112
|
+
def width #:nodoc:
|
113
|
+
@width ||= if $stdout.tty?
|
114
|
+
begin
|
115
|
+
require 'curses'
|
116
|
+
Curses::init_screen
|
117
|
+
x = Curses::cols
|
118
|
+
Curses::close_screen
|
119
|
+
x
|
120
|
+
rescue Exception
|
121
|
+
80
|
122
|
+
end
|
123
|
+
else
|
124
|
+
80
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def wrap(string)
|
129
|
+
|
130
|
+
string.split("\n\n").map { |para|
|
131
|
+
|
132
|
+
words = para.split(/[\n ]/)
|
133
|
+
words[1..-1].inject([words.first]) { |m, v|
|
134
|
+
new_last_line = m.last + " " + v
|
135
|
+
|
136
|
+
if new_last_line.length <= (width - spaces)
|
137
|
+
m[0...-1] + [new_last_line]
|
138
|
+
else
|
139
|
+
m + [v]
|
140
|
+
end
|
141
|
+
}.join("\n")
|
142
|
+
|
143
|
+
}.join("\n\n")
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Climate
|
2
|
+
class Option
|
3
|
+
|
4
|
+
attr_reader :name
|
5
|
+
attr_reader :description
|
6
|
+
attr_reader :options
|
7
|
+
|
8
|
+
def initialize(name, description, options={})
|
9
|
+
@name = name
|
10
|
+
@description = description
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
def type ; spec[:type] ; end
|
15
|
+
def long ; spec[:long] ; end
|
16
|
+
def short ; spec[:short] ; end
|
17
|
+
def default ; spec[:default] ; end
|
18
|
+
|
19
|
+
def optional?
|
20
|
+
spec.has_key?(:default)
|
21
|
+
end
|
22
|
+
|
23
|
+
def required? ; ! optional? ; end
|
24
|
+
|
25
|
+
def parser
|
26
|
+
@parser ||= Trollop::Parser.new.tap {|p| add_to(p) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def spec
|
30
|
+
@specs ||= parser.specs[@name]
|
31
|
+
end
|
32
|
+
|
33
|
+
def usage(options={})
|
34
|
+
help =
|
35
|
+
if type == :flag
|
36
|
+
"-#{short}"
|
37
|
+
else
|
38
|
+
"-#{short}<#{type}>"
|
39
|
+
end
|
40
|
+
|
41
|
+
if options[:with_long]
|
42
|
+
help = help + options.fetch(:separator, '|') +
|
43
|
+
if type == :flag
|
44
|
+
"--#{long}"
|
45
|
+
else
|
46
|
+
"--#{long}=<#{type}>"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
if optional? && !options.fetch(:hide_optional, false)
|
51
|
+
"[#{help}]"
|
52
|
+
else
|
53
|
+
help
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def add_to(parser)
|
58
|
+
parser.opt(@name, @description, @options)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Climate
|
2
|
+
|
3
|
+
module ParsingMethods
|
4
|
+
|
5
|
+
def arg(*args)
|
6
|
+
raise DefinitionError, "can not define a required argument after an " +
|
7
|
+
"optional one" if cli_arguments.any?(&:optional?)
|
8
|
+
|
9
|
+
cli_arguments << Argument.new(*args)
|
10
|
+
end
|
11
|
+
|
12
|
+
def opt(*args)
|
13
|
+
cli_options << Option.new(*args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def stop_on(args)
|
17
|
+
@stop_on = args
|
18
|
+
end
|
19
|
+
|
20
|
+
def trollop_parser
|
21
|
+
parser = Trollop::Parser.new
|
22
|
+
|
23
|
+
parser.stop_on @stop_on
|
24
|
+
|
25
|
+
if cli_arguments.size > 0
|
26
|
+
parser.banner ""
|
27
|
+
max_length = cli_arguments.map { |h| h.name.to_s.length }.max
|
28
|
+
cli_arguments.each do |argument|
|
29
|
+
parser.banner(" " + argument.name.to_s.rjust(max_length) + " - #{argument.description}")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
parser.banner ""
|
34
|
+
cli_options.each do |option|
|
35
|
+
option.add_to(parser)
|
36
|
+
end
|
37
|
+
parser
|
38
|
+
end
|
39
|
+
|
40
|
+
def help_banner(out=$stdout)
|
41
|
+
trollop_parser.educate(out)
|
42
|
+
end
|
43
|
+
|
44
|
+
def check_arguments(args, command=self)
|
45
|
+
|
46
|
+
if args.size > cli_arguments.size
|
47
|
+
raise UnexpectedArgumentError.new(command, "#{args.size} for #{cli_arguments.size}")
|
48
|
+
end
|
49
|
+
|
50
|
+
cli_arguments.zip(args).map do |argument, arg_value|
|
51
|
+
if argument.required? && (arg_value.nil? || arg_value.empty?)
|
52
|
+
raise MissingArgumentError.new(command, argument.name)
|
53
|
+
end
|
54
|
+
|
55
|
+
# no arg given is different to an empty arg
|
56
|
+
if arg_value.nil?
|
57
|
+
{}
|
58
|
+
else
|
59
|
+
{argument.name => arg_value}
|
60
|
+
end
|
61
|
+
end.inject {|a,b| a.merge(b) } || {}
|
62
|
+
end
|
63
|
+
|
64
|
+
def parse(arguments)
|
65
|
+
parser = self.trollop_parser
|
66
|
+
options = parser.parse(arguments)
|
67
|
+
|
68
|
+
# it would get weird if we allowed arguments alongside options, so
|
69
|
+
# lets keep it one or t'other
|
70
|
+
arguments, leftovers =
|
71
|
+
if @stop_on
|
72
|
+
[[], parser.leftovers]
|
73
|
+
else
|
74
|
+
[self.check_arguments(parser.leftovers), []]
|
75
|
+
end
|
76
|
+
|
77
|
+
[arguments, options, leftovers]
|
78
|
+
end
|
79
|
+
|
80
|
+
def cli_options ; @cli_options ||= [] ; end
|
81
|
+
def cli_arguments ; @cli_arguments ||= [] ; end
|
82
|
+
|
83
|
+
def has_options? ; not cli_options.empty? ; end
|
84
|
+
def has_arguments? ; not cli_arguments.empty? ; end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
class Parser
|
89
|
+
include ParsingMethods
|
90
|
+
|
91
|
+
def initialize(&block)
|
92
|
+
instance_eval(&block) if block_given?
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Climate
|
2
|
+
|
3
|
+
# Module that you can extend any object with to turn it in to a climate
|
4
|
+
# script. See the readme for intended usage, but follows the same pattern
|
5
|
+
# as the command, with the exception that it does not allow subcommands
|
6
|
+
module Script
|
7
|
+
|
8
|
+
def self.extended(othermodule)
|
9
|
+
if @included.nil?
|
10
|
+
@included = true
|
11
|
+
at_exit do
|
12
|
+
Climate.with_standard_exception_handling do
|
13
|
+
othermodule.send(:parse_argv)
|
14
|
+
othermodule.send(:run)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
include ParsingMethods
|
21
|
+
# Set the description for this script
|
22
|
+
# @param [String] string Description/Banner/Help text
|
23
|
+
def description(string=nil)
|
24
|
+
if string.nil?
|
25
|
+
@description
|
26
|
+
else
|
27
|
+
@description = string
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def parse_argv
|
32
|
+
@arguments, @options, @leftovers = self.parse(ARGV)
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :arguments, :options, :leftovers
|
36
|
+
|
37
|
+
def ancestors ; [self] ; end
|
38
|
+
def name ; File.basename($CLIMATE_PROGRAM_NAME || $PROGRAM_NAME) ; end
|
39
|
+
|
40
|
+
def has_subcommands? ; false ; end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
data/lib/climate.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'trollop'
|
2
|
+
|
3
|
+
module Climate
|
4
|
+
def self.with_standard_exception_handling(&block)
|
5
|
+
begin
|
6
|
+
yield
|
7
|
+
rescue ExitException => e
|
8
|
+
$stderr.puts(e.message)
|
9
|
+
exit(e.exitcode)
|
10
|
+
rescue HelpNeeded => e
|
11
|
+
print_usage(e.command_class)
|
12
|
+
rescue UnknownCommandError => e
|
13
|
+
$stderr.puts("Unknown command: #{e.message}")
|
14
|
+
print_usage(e.command_class)
|
15
|
+
rescue MissingArgumentError => e
|
16
|
+
$stderr.puts("Missing argument: #{e.message}")
|
17
|
+
print_usage(e.command_class)
|
18
|
+
rescue => e
|
19
|
+
$stderr.puts("Unexpected error: #{e.message}")
|
20
|
+
$stderr.puts(e.backtrace)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.print_usage(command_class, options={})
|
25
|
+
help = Help.new(command_class)
|
26
|
+
|
27
|
+
help.print
|
28
|
+
end
|
29
|
+
|
30
|
+
def run(&block)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
require 'climate/errors'
|
35
|
+
require 'climate/argument'
|
36
|
+
require 'climate/option'
|
37
|
+
require 'climate/parser'
|
38
|
+
require 'climate/command'
|
39
|
+
require 'climate/help'
|
40
|
+
require 'climate/script'
|
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: climate
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Nick Griffiths
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-07-15 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: trollop
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: minitest
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: yard
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
60
|
+
type: :development
|
61
|
+
version_requirements: *id003
|
62
|
+
description: Library, not a framework, for building command line interfaces to your ruby application
|
63
|
+
email:
|
64
|
+
- nicobrevin@gmail.com
|
65
|
+
executables:
|
66
|
+
- climate
|
67
|
+
extensions: []
|
68
|
+
|
69
|
+
extra_rdoc_files: []
|
70
|
+
|
71
|
+
files:
|
72
|
+
- lib/climate/version.rb
|
73
|
+
- lib/climate/argument.rb
|
74
|
+
- lib/climate/command.rb
|
75
|
+
- lib/climate/option.rb
|
76
|
+
- lib/climate/parser.rb
|
77
|
+
- lib/climate/errors.rb
|
78
|
+
- lib/climate/script.rb
|
79
|
+
- lib/climate/help.rb
|
80
|
+
- lib/climate.rb
|
81
|
+
- README
|
82
|
+
- bin/climate
|
83
|
+
homepage:
|
84
|
+
licenses: []
|
85
|
+
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
hash: 3
|
97
|
+
segments:
|
98
|
+
- 0
|
99
|
+
version: "0"
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
hash: 3
|
106
|
+
segments:
|
107
|
+
- 0
|
108
|
+
version: "0"
|
109
|
+
requirements: []
|
110
|
+
|
111
|
+
rubyforge_project:
|
112
|
+
rubygems_version: 1.8.10
|
113
|
+
signing_key:
|
114
|
+
specification_version: 3
|
115
|
+
summary: Library for building command line interfaces
|
116
|
+
test_files: []
|
117
|
+
|