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