caty 0.0.1

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/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
+