caty 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY.markdown ADDED
@@ -0,0 +1,7 @@
1
+ 0.0.1
2
+ =====
3
+
4
+ * First release
5
+ * Support for tasks, parameters, global
6
+ and task-specific options
7
+
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
+