caty 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.markdown +7 -0
- data/LICENSE.txt +22 -0
- data/README.markdown +92 -0
- data/Rakefile +33 -0
- data/VERSION +1 -0
- data/caty.gemspec +73 -0
- data/lib/caty.rb +341 -0
- data/lib/caty/converters.rb +101 -0
- data/lib/caty/errors.rb +17 -0
- data/lib/caty/global_option.rb +40 -0
- data/lib/caty/has_description.rb +26 -0
- data/lib/caty/help_system.rb +107 -0
- data/lib/caty/helpers.rb +19 -0
- data/lib/caty/indirection.rb +26 -0
- data/lib/caty/option.rb +100 -0
- data/lib/caty/option_array.rb +30 -0
- data/lib/caty/option_constructor.rb +89 -0
- data/lib/caty/task.rb +74 -0
- data/lib/caty/task_hash.rb +75 -0
- data/test/kitty.rb +87 -0
- data/test/test_caty.rb +100 -0
- data/test/test_option.rb +81 -0
- data/test/test_option_array.rb +25 -0
- data/test/test_structure.rb +72 -0
- data/test/test_task.rb +28 -0
- metadata +94 -0
data/HISTORY.markdown
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
/*---- DON'T PANIC License 1.1 -----------
|
2
|
+
|
3
|
+
Don't panic, this piece of software is
|
4
|
+
free, i.e. you can do with it whatever
|
5
|
+
you like, including, but not limited to:
|
6
|
+
|
7
|
+
* using it
|
8
|
+
* copying it
|
9
|
+
* (re)distributing it
|
10
|
+
* burning/burying/shredding it
|
11
|
+
* eating it
|
12
|
+
* using it to obtain world domination
|
13
|
+
* and ignoring it
|
14
|
+
|
15
|
+
Under the sole condition that you
|
16
|
+
|
17
|
+
* CONSIDER buying the author a strong
|
18
|
+
brownian motion producer, say a nice
|
19
|
+
hot cup of tea, should you ever meet
|
20
|
+
him in person.
|
21
|
+
|
22
|
+
----------------------------------------*/
|
data/README.markdown
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# Caty #
|
2
|
+
|
3
|
+
* http://github.com/karottenreibe/caty
|
4
|
+
|
5
|
+
|
6
|
+
## Description ##
|
7
|
+
|
8
|
+
Caty is a command line parser that operates on the same
|
9
|
+
principle as Yehuda Katz's Thor (http://github.com/wycats/thor/tree/master).
|
10
|
+
It maps the arguments given by the user to instance methods of a class
|
11
|
+
and can automatically parse options.
|
12
|
+
|
13
|
+
|
14
|
+
## Features ##
|
15
|
+
|
16
|
+
* Define tasks that get mapped directly to instance methods of a class
|
17
|
+
* Define global (e.g. --beer) and task-specific (e.g. -rootbeer) options
|
18
|
+
* Have String, Integer and Boolean options
|
19
|
+
* Have options with default values
|
20
|
+
|
21
|
+
|
22
|
+
## Synopsis ##
|
23
|
+
|
24
|
+
require 'rubygems'
|
25
|
+
require 'caty'
|
26
|
+
|
27
|
+
class Kitty < Caty
|
28
|
+
|
29
|
+
global_options do
|
30
|
+
boolean :quiet, false
|
31
|
+
end
|
32
|
+
|
33
|
+
desc('WHAT', cut("
|
34
|
+
Kitty's hungry!
|
35
|
+
You can specify any amount of food to feed the kitty.
|
36
|
+
"))
|
37
|
+
|
38
|
+
task_options :amount => 1
|
39
|
+
|
40
|
+
def eat( something )
|
41
|
+
unless global_options.quiet
|
42
|
+
puts "I'm eating #{something}. I'm #{task_options.speed}!"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
help_task
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
Kitty.start!
|
51
|
+
|
52
|
+
|
53
|
+
#
|
54
|
+
# now you can do:
|
55
|
+
# $> kitty eat fish -amount=5 --quiet
|
56
|
+
# $> kitty help
|
57
|
+
# ...
|
58
|
+
#
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
## Install ##
|
63
|
+
|
64
|
+
* sudo gem install karottenreibe-caty --source http://gems.github.com
|
65
|
+
* sudo gem install caty
|
66
|
+
|
67
|
+
|
68
|
+
## License ##
|
69
|
+
|
70
|
+
/*---- DON'T PANIC License 1.1 -----------
|
71
|
+
|
72
|
+
Don't panic, this piece of software is
|
73
|
+
free, i.e. you can do with it whatever
|
74
|
+
you like, including, but not limited to:
|
75
|
+
|
76
|
+
* using it
|
77
|
+
* copying it
|
78
|
+
* (re)distributing it
|
79
|
+
* burning/burying/shredding it
|
80
|
+
* eating it
|
81
|
+
* using it to obtain world domination
|
82
|
+
* and ignoring it
|
83
|
+
|
84
|
+
Under the sole condition that you
|
85
|
+
|
86
|
+
* CONSIDER buying the author a strong
|
87
|
+
brownian motion producer, say a nice
|
88
|
+
hot cup of tea, should you ever meet
|
89
|
+
him in person.
|
90
|
+
|
91
|
+
----------------------------------------*/
|
92
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'jeweler'
|
2
|
+
|
3
|
+
task :release do
|
4
|
+
sh "vim HISTORY.markdown"
|
5
|
+
sh "vim README.markdown"
|
6
|
+
sh "git commit -a -m 'prerelease adjustments'; true"
|
7
|
+
end
|
8
|
+
|
9
|
+
Jeweler::Tasks.new do |gem|
|
10
|
+
gem.name = 'caty'
|
11
|
+
gem.summary = gem.description =
|
12
|
+
'Caty is a command line parser that maps arguments to instance methods'
|
13
|
+
gem.email = 'karottenreibe@gmail.com'
|
14
|
+
gem.homepage = 'http://github.com/karottenreibe/caty'
|
15
|
+
gem.authors = ['Fabian Streitel']
|
16
|
+
gem.rubyforge_project = 'k-gems'
|
17
|
+
gem.add_dependency('ohash')
|
18
|
+
end
|
19
|
+
|
20
|
+
Jeweler::RubyforgeTasks.new
|
21
|
+
|
22
|
+
require 'rake/rdoctask'
|
23
|
+
Rake::RDocTask.new do |rdoc|
|
24
|
+
rdoc.rdoc_dir = 'rdoc'
|
25
|
+
rdoc.title = 'Caty'
|
26
|
+
rdoc.rdoc_files.include('lib/*.rb')
|
27
|
+
rdoc.rdoc_files.include('lib/*/*.rb')
|
28
|
+
end
|
29
|
+
|
30
|
+
task :test do
|
31
|
+
sh 'bacon -Ilib test/test_*.rb'
|
32
|
+
end
|
33
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/caty.gemspec
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{caty}
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Fabian Streitel"]
|
12
|
+
s.date = %q{2009-09-08}
|
13
|
+
s.description = %q{Caty is a command line parser that maps arguments to instance methods}
|
14
|
+
s.email = %q{karottenreibe@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.markdown"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
"HISTORY.markdown",
|
21
|
+
"LICENSE.txt",
|
22
|
+
"README.markdown",
|
23
|
+
"Rakefile",
|
24
|
+
"VERSION",
|
25
|
+
"caty.gemspec",
|
26
|
+
"lib/caty.rb",
|
27
|
+
"lib/caty/converters.rb",
|
28
|
+
"lib/caty/errors.rb",
|
29
|
+
"lib/caty/global_option.rb",
|
30
|
+
"lib/caty/has_description.rb",
|
31
|
+
"lib/caty/help_system.rb",
|
32
|
+
"lib/caty/helpers.rb",
|
33
|
+
"lib/caty/indirection.rb",
|
34
|
+
"lib/caty/option.rb",
|
35
|
+
"lib/caty/option_array.rb",
|
36
|
+
"lib/caty/option_constructor.rb",
|
37
|
+
"lib/caty/task.rb",
|
38
|
+
"lib/caty/task_hash.rb",
|
39
|
+
"test/kitty.rb",
|
40
|
+
"test/test_caty.rb",
|
41
|
+
"test/test_option.rb",
|
42
|
+
"test/test_option_array.rb",
|
43
|
+
"test/test_structure.rb",
|
44
|
+
"test/test_task.rb"
|
45
|
+
]
|
46
|
+
s.homepage = %q{http://github.com/karottenreibe/caty}
|
47
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
48
|
+
s.require_paths = ["lib"]
|
49
|
+
s.rubyforge_project = %q{k-gems}
|
50
|
+
s.rubygems_version = %q{1.3.4}
|
51
|
+
s.summary = %q{Caty is a command line parser that maps arguments to instance methods}
|
52
|
+
s.test_files = [
|
53
|
+
"test/kitty.rb",
|
54
|
+
"test/test_caty.rb",
|
55
|
+
"test/test_option.rb",
|
56
|
+
"test/test_option_array.rb",
|
57
|
+
"test/test_structure.rb",
|
58
|
+
"test/test_task.rb"
|
59
|
+
]
|
60
|
+
|
61
|
+
if s.respond_to? :specification_version then
|
62
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
63
|
+
s.specification_version = 3
|
64
|
+
|
65
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
66
|
+
s.add_runtime_dependency(%q<ohash>, [">= 0"])
|
67
|
+
else
|
68
|
+
s.add_dependency(%q<ohash>, [">= 0"])
|
69
|
+
end
|
70
|
+
else
|
71
|
+
s.add_dependency(%q<ohash>, [">= 0"])
|
72
|
+
end
|
73
|
+
end
|
data/lib/caty.rb
ADDED
@@ -0,0 +1,341 @@
|
|
1
|
+
#
|
2
|
+
# Caty is a command line parser that supports tasks as well as
|
3
|
+
# options and maps user input directly to instance methods.
|
4
|
+
#
|
5
|
+
# To find out more about caty, file bug reports and other stuff,
|
6
|
+
# go to http://karottenreibe.github.com/caty.
|
7
|
+
#
|
8
|
+
# Inspired by Thor (http://www.yehudakatz.com/2008/05/12/by-thors-hammer)
|
9
|
+
#
|
10
|
+
# Kudos to Yehuda Katz.
|
11
|
+
#
|
12
|
+
|
13
|
+
#
|
14
|
+
# Handles command line parsing.
|
15
|
+
#
|
16
|
+
# Subclass this and add public methods.
|
17
|
+
# These methods will become tasks, which will be
|
18
|
+
# callable via
|
19
|
+
#
|
20
|
+
# app method_name
|
21
|
+
#
|
22
|
+
# Use the #global_options() and #task_options()
|
23
|
+
# methods to add global or task-specific options.
|
24
|
+
#
|
25
|
+
# Use the ::map() method to create aliases for
|
26
|
+
# tasks.
|
27
|
+
#
|
28
|
+
# Use the ::start!() method to start parsing.
|
29
|
+
#
|
30
|
+
# Use ::default() to set a default task.
|
31
|
+
#
|
32
|
+
# Use ::before() and ::after() to define Rails-style
|
33
|
+
# before and after filters.
|
34
|
+
#
|
35
|
+
class Caty
|
36
|
+
|
37
|
+
#
|
38
|
+
# Returns the options for the called task as
|
39
|
+
# an OpenHash.
|
40
|
+
#
|
41
|
+
attr_accessor :task_options
|
42
|
+
|
43
|
+
#
|
44
|
+
# Returns the global options for this invocation as
|
45
|
+
# an OpenHash.
|
46
|
+
#
|
47
|
+
attr_accessor :global_options
|
48
|
+
|
49
|
+
class << self
|
50
|
+
|
51
|
+
#
|
52
|
+
# Starts command line parsing.
|
53
|
+
#
|
54
|
+
# Subclass.start!( arguments_array )
|
55
|
+
# Subclass.start!
|
56
|
+
#
|
57
|
+
# Returns true on success, false when an ArgumentError
|
58
|
+
# was detected.
|
59
|
+
#
|
60
|
+
def start!( args = ARGV )
|
61
|
+
initialize_instance
|
62
|
+
|
63
|
+
begin
|
64
|
+
caty = self.new
|
65
|
+
caty.global_options = @global_options.grep!(args)
|
66
|
+
|
67
|
+
task_name = args.delete_at(0) || @default
|
68
|
+
raise Caty::NoSuchTaskError, "You need to provide a task" if task_name.nil?
|
69
|
+
|
70
|
+
task = @tasks.resolve(task_name.to_sym)
|
71
|
+
|
72
|
+
if task.nil?
|
73
|
+
raise Caty::NoSuchTaskError, "There is no task named `#{task_name}'"
|
74
|
+
else
|
75
|
+
caty.task_options = task.parse!(args)
|
76
|
+
|
77
|
+
caty.instance_exec(task_name.to_sym, &@before) unless @before.nil?
|
78
|
+
task.execute(caty)
|
79
|
+
caty.instance_exec(task_name.to_sym, &@after) unless @after.nil?
|
80
|
+
end
|
81
|
+
|
82
|
+
return true
|
83
|
+
|
84
|
+
rescue Caty::NoSuchTaskError, Caty::OptionArgumentError => e
|
85
|
+
$stdout.puts e.message
|
86
|
+
return false
|
87
|
+
|
88
|
+
rescue ArgumentError => e
|
89
|
+
# verify that this is actually the task throwing the error
|
90
|
+
if is_task_argument_error(e.backtrace, task_name)
|
91
|
+
$stdout.puts "Bad arguments for task #{task.name}."
|
92
|
+
$stdout.puts "Usage: #{task.to_s}"
|
93
|
+
return false
|
94
|
+
else
|
95
|
+
raise
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
# Simply class_evals the given block on
|
102
|
+
# your Caty subtype, thus allowing you to
|
103
|
+
# add new tasks in different source files.
|
104
|
+
#
|
105
|
+
# class X < Caty
|
106
|
+
# end
|
107
|
+
#
|
108
|
+
# X.append do
|
109
|
+
# def bar
|
110
|
+
# puts 'bar task'
|
111
|
+
# end
|
112
|
+
# end
|
113
|
+
#
|
114
|
+
# X.start!(%w{bar})
|
115
|
+
#
|
116
|
+
def append( &block )
|
117
|
+
self.class_eval(&block)
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# Methods to be used by the inheriting class.
|
122
|
+
#
|
123
|
+
protected
|
124
|
+
|
125
|
+
#
|
126
|
+
# Can be used to cut off whitespace in front of
|
127
|
+
# #desc() descriptions, so it can be written more
|
128
|
+
# nicely in the source.
|
129
|
+
#
|
130
|
+
# Describing this method accurately is terribly complicated,
|
131
|
+
# so here's an example:
|
132
|
+
#
|
133
|
+
# cut("
|
134
|
+
# first
|
135
|
+
# second
|
136
|
+
# third
|
137
|
+
# ")
|
138
|
+
#
|
139
|
+
# produces the string
|
140
|
+
#
|
141
|
+
# "first\n second\nthird"
|
142
|
+
#
|
143
|
+
# Notice the preserved whitespace in front of
|
144
|
+
# 'second'!
|
145
|
+
#
|
146
|
+
def cut( description )
|
147
|
+
description.sub!(%r{\A\n+}, '')
|
148
|
+
description =~ %r{\A([ \t]*)}
|
149
|
+
|
150
|
+
unless $1.nil?
|
151
|
+
space = $1
|
152
|
+
description.gsub!(%r{^#{space}}, '')
|
153
|
+
end
|
154
|
+
|
155
|
+
description.gsub(%r{\n\Z}, '')
|
156
|
+
end
|
157
|
+
|
158
|
+
#
|
159
|
+
# Adds options for the task defined next.
|
160
|
+
#
|
161
|
+
# task_options :option_name => default, :option2 ...
|
162
|
+
#
|
163
|
+
def task_options( options_hash )
|
164
|
+
initialize_instance
|
165
|
+
options_hash.each do |name,default|
|
166
|
+
@task_options << Caty::Option.new(name.to_sym, default)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
#
|
171
|
+
# Adds global options.
|
172
|
+
#
|
173
|
+
# The first option will be decorated with the latest description
|
174
|
+
# defined via #desc().
|
175
|
+
#
|
176
|
+
# global_options do
|
177
|
+
# desc('description')
|
178
|
+
# string 'option_name' => 'default'
|
179
|
+
#
|
180
|
+
# desc('desc2')
|
181
|
+
# integer 'option2'
|
182
|
+
# end
|
183
|
+
#
|
184
|
+
def global_options( &block )
|
185
|
+
initialize_instance
|
186
|
+
constructor = Caty::OptionConstructor.new(Caty::GlobalOption)
|
187
|
+
@global_options.concat(constructor.construct(&block))
|
188
|
+
end
|
189
|
+
|
190
|
+
#
|
191
|
+
# Creates aliases for tasks.
|
192
|
+
#
|
193
|
+
# map :alias => :task_name
|
194
|
+
# map %w{alias1 alias2} => :task_name
|
195
|
+
#
|
196
|
+
def map( mapping_hash )
|
197
|
+
initialize_instance
|
198
|
+
|
199
|
+
mapping_hash.each do |mappings,target|
|
200
|
+
mappings = [mappings] unless mappings.is_a?(Array)
|
201
|
+
|
202
|
+
mappings.each do |mapping|
|
203
|
+
@tasks[mapping.to_sym] = Caty::Indirection.new(mapping.to_sym, target.to_sym)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
#
|
209
|
+
# Defines a block of code that will be executed right
|
210
|
+
# before any task is called.
|
211
|
+
# _self_ will point to the Caty instance and the name of
|
212
|
+
# the task (Symbol) will be given as the only argument.
|
213
|
+
#
|
214
|
+
# before do |task|
|
215
|
+
# puts task
|
216
|
+
# end
|
217
|
+
#
|
218
|
+
def before( &block )
|
219
|
+
@before = block
|
220
|
+
end
|
221
|
+
|
222
|
+
#
|
223
|
+
# Defines a default task.
|
224
|
+
#
|
225
|
+
# default :task
|
226
|
+
#
|
227
|
+
def default( task )
|
228
|
+
@default = task.to_sym
|
229
|
+
end
|
230
|
+
|
231
|
+
#
|
232
|
+
# Defines a block of code that will be executed right
|
233
|
+
# after any task is called.
|
234
|
+
# _self_ will point to the Caty instance and the name of
|
235
|
+
# the task (Symbol) will be given as the only argument.
|
236
|
+
#
|
237
|
+
# NOTE: this code will not be executed if an error occured
|
238
|
+
# during task execution.
|
239
|
+
#
|
240
|
+
# after do |task|
|
241
|
+
# puts task
|
242
|
+
# end
|
243
|
+
#
|
244
|
+
def after( &block )
|
245
|
+
@after = block
|
246
|
+
end
|
247
|
+
|
248
|
+
#
|
249
|
+
# Decorates the next definition with a description and
|
250
|
+
# optionally a usage string.
|
251
|
+
#
|
252
|
+
# desc 'usage info', 'long description'
|
253
|
+
# desc 'long description'
|
254
|
+
#
|
255
|
+
def desc( usage, description = nil )
|
256
|
+
if description.nil?
|
257
|
+
@description = usage
|
258
|
+
else
|
259
|
+
@description, @usage = description, usage
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
#
|
264
|
+
# Methods to be used by Caty itself.
|
265
|
+
#
|
266
|
+
private
|
267
|
+
|
268
|
+
#
|
269
|
+
# Metaprogramming.
|
270
|
+
#
|
271
|
+
# See Module#method_added.
|
272
|
+
# Creates a new task, if the method that was added, was
|
273
|
+
# public.
|
274
|
+
#
|
275
|
+
def method_added( meth )
|
276
|
+
initialize_instance
|
277
|
+
name = meth.to_s
|
278
|
+
|
279
|
+
# only add public methods as tasks
|
280
|
+
if self.public_instance_methods.include?(name)
|
281
|
+
method = self.instance_method(name)
|
282
|
+
task = @tasks[meth] || Caty::Task.new(meth, method, @task_options)
|
283
|
+
task.description = @description
|
284
|
+
task.usage = @usage
|
285
|
+
|
286
|
+
@tasks[meth] = task
|
287
|
+
reset_decorators
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
#
|
292
|
+
# Tests whether the given backtrace is from
|
293
|
+
# an argument error in task invocation.
|
294
|
+
#
|
295
|
+
def is_task_argument_error( backtrace, task_name )
|
296
|
+
backtrace[0].end_with?("in `#{task_name}'") and # inside the task method
|
297
|
+
backtrace[1].end_with?("in `call'") and # in Task#call
|
298
|
+
backtrace[2].end_with?("in `execute'") and # in Task#execute
|
299
|
+
backtrace[3].end_with?("in `start!'") # in Caty::start!
|
300
|
+
end
|
301
|
+
|
302
|
+
#
|
303
|
+
# Resets the decorators applied via #desc() and #task_options().
|
304
|
+
#
|
305
|
+
def reset_decorators
|
306
|
+
@task_options = OptionArray.new
|
307
|
+
@description = nil
|
308
|
+
@usage = nil
|
309
|
+
end
|
310
|
+
|
311
|
+
#
|
312
|
+
# Initializes the tasks', options' and global options'
|
313
|
+
# storage instance variables.
|
314
|
+
#
|
315
|
+
def initialize_instance
|
316
|
+
@task_options ||= Caty::OptionArray.new
|
317
|
+
@global_options ||= Caty::OptionArray.new
|
318
|
+
@tasks ||= Caty::TaskHash.new
|
319
|
+
end
|
320
|
+
|
321
|
+
end
|
322
|
+
|
323
|
+
end
|
324
|
+
|
325
|
+
require 'caty/help_system'
|
326
|
+
Caty.extend(Caty::HelpSystem)
|
327
|
+
|
328
|
+
require 'caty/helpers'
|
329
|
+
require 'caty/errors'
|
330
|
+
require 'caty/has_description'
|
331
|
+
|
332
|
+
require 'caty/task_hash'
|
333
|
+
require 'caty/task'
|
334
|
+
require 'caty/indirection'
|
335
|
+
|
336
|
+
require 'caty/option_array'
|
337
|
+
require 'caty/option_constructor'
|
338
|
+
require 'caty/converters'
|
339
|
+
require 'caty/option'
|
340
|
+
require 'caty/global_option'
|
341
|
+
|