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.
@@ -0,0 +1,30 @@
1
+ #
2
+ # Contains the OptionArray class.
3
+ #
4
+
5
+ require 'delegate'
6
+ require 'ohash'
7
+
8
+ #
9
+ # Represents an array of options and adds some
10
+ # useful methods.
11
+ #
12
+ class Caty::OptionArray < Array
13
+
14
+ include Caty::Helpers
15
+
16
+ #
17
+ # Forwards the ::grep!() to the different Options
18
+ # and collects the results into an OpenHash.
19
+ #
20
+ def grep!( args )
21
+ returning(OpenHash.new) do |hash|
22
+ self.each do |option|
23
+ grab = option.grep!(args)
24
+ hash[option.name.to_sym] = grab unless grab.nil?
25
+ end
26
+ end
27
+ end
28
+
29
+ end
30
+
@@ -0,0 +1,89 @@
1
+ #
2
+ # Contains the OptionConstructor class.
3
+ #
4
+
5
+ #
6
+ # Constructs options using a DSL, e.g:
7
+ #
8
+ # constructor.construct do
9
+ # desc('description')
10
+ # string :option, 'default'
11
+ #
12
+ # desc('desc2')
13
+ # integer :nother_one
14
+ # end
15
+ #
16
+ # Used by Caty when Caty::global_options() is
17
+ # called.
18
+ #
19
+ class Caty::OptionConstructor
20
+
21
+ class << self
22
+
23
+ #
24
+ # Returns an array of all the option types
25
+ # registered.
26
+ #
27
+ def types
28
+ @types ||= Array.new
29
+ end
30
+
31
+ #
32
+ # Registers an option type (symbol) with the
33
+ # constructor.
34
+ #
35
+ def register( type )
36
+ @types ||= Array.new
37
+ @types << type
38
+ end
39
+
40
+ end
41
+
42
+ #
43
+ # Creates a new OptionConstructor that constructs
44
+ # the given options of the given class.
45
+ #
46
+ def initialize( option_klass )
47
+ @klass = option_klass
48
+ @options = Caty::OptionArray.new
49
+ end
50
+
51
+ #
52
+ # Constructs the actual options according to the
53
+ # given block.
54
+ #
55
+ def construct( &block )
56
+ self.instance_eval(&block)
57
+ @options
58
+ end
59
+
60
+ private
61
+
62
+ #
63
+ # Emulates the type methods, e.g:
64
+ #
65
+ # string :name, 'default'
66
+ #
67
+ def method_missing( meth, *args, &block )
68
+ if self.class.types.include?(meth) and args.length.between?(1,2)
69
+ name = args[0]
70
+ default = args.length == 2 ?
71
+ args[1] : meth
72
+ option = @klass.new(name, default)
73
+ option.description, @description =
74
+ @description, nil
75
+ @options << option
76
+ else
77
+ super
78
+ end
79
+ end
80
+
81
+ #
82
+ # Allows you to describe the constructed options.
83
+ #
84
+ def desc( description )
85
+ @description = description
86
+ end
87
+
88
+ end
89
+
data/lib/caty/task.rb ADDED
@@ -0,0 +1,74 @@
1
+ #
2
+ # Contains the Task class.
3
+ #
4
+
5
+ #
6
+ # Represents a single task.
7
+ #
8
+ # A Task object is created for every public method created
9
+ # in the Caty subclass.
10
+ #
11
+ class Caty::Task
12
+
13
+ include Caty::Helpers
14
+ include Caty::HasDescription
15
+
16
+ attr_accessor :name, :usage
17
+
18
+ #
19
+ # Creates a new task with the given name for the
20
+ # given instance_method.
21
+ #
22
+ def initialize( name, instance_method, options )
23
+ @name, @instance_method, @options =
24
+ name, instance_method, options
25
+ end
26
+
27
+ #
28
+ # Tries to remove the options defined for this task
29
+ # from the args array.
30
+ #
31
+ # Returns an OpenHash containing the retrieved
32
+ # options.
33
+ #
34
+ def parse!( args )
35
+ @args = args
36
+ @options.grep!(args)
37
+ end
38
+
39
+ #
40
+ # Executes the associated instance_method by binding it
41
+ # to the given _context_.
42
+ #
43
+ def execute( context )
44
+ @instance_method.bind(context).call(*@args)
45
+ end
46
+
47
+ #
48
+ # Resolving end point.
49
+ # See Caty::Indirection#resolve() for more information.
50
+ #
51
+ def resolve( task_hash )
52
+ self
53
+ end
54
+
55
+ #
56
+ # Returns a string representation of the task and its
57
+ # options to be used by the help system.
58
+ #
59
+ def to_help
60
+ [ self.to_s , self.short_description, self.description ]
61
+ end
62
+
63
+ def to_s
64
+ returning(@name.to_s) do |output|
65
+ output << " #{@usage}" unless @usage.nil?
66
+
67
+ @options.each do |option|
68
+ output << " #{option}"
69
+ end
70
+ end
71
+ end
72
+
73
+ end
74
+
@@ -0,0 +1,75 @@
1
+ #
2
+ # Contains the TaskHash class.
3
+ #
4
+
5
+ require 'delegate'
6
+
7
+ #
8
+ # A hash that preserves the order of task insertion.
9
+ #
10
+ class Caty::TaskHash < DelegateClass(Hash)
11
+
12
+ #
13
+ # Works like Hash#initialize.
14
+ #
15
+ def initialize( *args )
16
+ @ary = Array.new
17
+ @hash = Hash.new(*args)
18
+ super(@hash)
19
+ end
20
+
21
+ #
22
+ # Works like Hash#[].
23
+ #
24
+ def []( num_or_key )
25
+ case num_or_key
26
+ when Numeric, Range then @ary[num_or_key]
27
+ else @hash[num_or_key]
28
+ end
29
+ end
30
+
31
+ #
32
+ # Works like Hash#[]=.
33
+ #
34
+ def []=( key, value )
35
+ index = @ary.find { |item| item == @hash[key] }
36
+
37
+ if @hash[key].nil? or index.nil?
38
+ @ary << value
39
+ else
40
+ @ary[index] = value
41
+ end
42
+
43
+ @hash[key] = value
44
+ end
45
+
46
+ #
47
+ # Follows indirections until the task behind
48
+ # the given task name is discovered.
49
+ #
50
+ def resolve( task_name )
51
+ task = self[task_name]
52
+
53
+ if task.nil? then nil
54
+ else task.resolve(self)
55
+ end
56
+ end
57
+
58
+ #
59
+ # Returns a copy of the task hash as an array.
60
+ # The order of the items in the array is the same as in the
61
+ # task hash.
62
+ #
63
+ def to_a
64
+ @ary.dup
65
+ end
66
+
67
+ #
68
+ # Returns a copy of the task hash as a plain hash.
69
+ #
70
+ def to_h
71
+ @hash.dup
72
+ end
73
+
74
+ end
75
+
data/test/kitty.rb ADDED
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'caty'
4
+
5
+ #
6
+ # A Kitty.
7
+ #
8
+ class Kitty < Caty
9
+
10
+ global_options do
11
+
12
+ #
13
+ # It's trained not to meow if you tell it to be quiet...
14
+ #
15
+ desc('No meowing.')
16
+ boolean :quiet, false
17
+
18
+ end
19
+
20
+ #
21
+ # It needs to eat sometimes.
22
+ #
23
+ desc('SOMETHING', cut("
24
+ Eats SOMETHING.
25
+ You can give the kitty any -amount of food.
26
+ "))
27
+ task_options :amount => 1
28
+
29
+ def eat( something )
30
+ puts "The kitty eats #{task_options.amount} #{something}!"
31
+ end
32
+
33
+ #
34
+ # By default the kitty just meows at you.
35
+ #
36
+ def meow
37
+ puts 'meow!'
38
+ end
39
+
40
+ default :meow
41
+
42
+ #
43
+ # It likes to meow a lot.
44
+ #
45
+ before do |task|
46
+ puts 'meow!' unless global_options.quiet or task == :help
47
+ end
48
+
49
+ #
50
+ # And it tells you when it's done.
51
+ #
52
+ after do |task|
53
+ puts 'purrr...' unless task == :help
54
+ end
55
+
56
+ #
57
+ # If you ask it politely, it will tell you what it can do.
58
+ #
59
+ help_task
60
+
61
+ end
62
+
63
+ #
64
+ # You could put this in a separate file and require it here
65
+ # instead, thus separating any tasks from the others and
66
+ # reducing interdependencies
67
+ #
68
+ Kitty.append do
69
+
70
+ #
71
+ # It can be sleepy...
72
+ # And doze away...
73
+ #
74
+ desc("Sleeps a bit.")
75
+ map :doze => :sleep
76
+
77
+ def sleep
78
+ puts 'The kitty sleeps...'
79
+ end
80
+
81
+ end
82
+
83
+ #
84
+ # Pushes the kitty's on/off switch ;-)
85
+ #
86
+ Kitty.start!
87
+
data/test/test_caty.rb ADDED
@@ -0,0 +1,100 @@
1
+ require 'rubygems'
2
+ require 'bacon'
3
+ require 'facon'
4
+ require 'caty'
5
+
6
+ describe 'Caty' do
7
+
8
+ DEFAULT_TESTER = mock('default')
9
+
10
+ class CatyTest < Caty
11
+
12
+ default :beer
13
+ map 'lager' => :beer
14
+
15
+ def beer arg = DEFAULT_TESTER
16
+ arg.beer
17
+ end
18
+
19
+ def energy_drink
20
+ party(42) # raises an argument error
21
+ end
22
+
23
+ protected
24
+
25
+ def punch arg
26
+ arg.punch
27
+ end
28
+
29
+ private
30
+
31
+ def wine arg
32
+ arg.wine
33
+ end
34
+
35
+ def party
36
+ end
37
+
38
+ end
39
+
40
+ def suppress_output
41
+ output = $stdout = StringIO.new
42
+ yield
43
+ return output.string
44
+ ensure
45
+ $stdout = STDOUT
46
+ end
47
+
48
+ it 'should pass arguments' do
49
+ tester = mock('beer')
50
+ tester.should.receive(:beer)
51
+ CatyTest.start!(['beer', tester])
52
+ end
53
+
54
+ it 'should handle mappings' do
55
+ tester = mock('lager')
56
+ tester.should.receive(:beer)
57
+ CatyTest.start!(['lager', tester])
58
+ end
59
+
60
+ it 'should handle default task invocation' do
61
+ DEFAULT_TESTER.should.receive(:beer)
62
+ CatyTest.start!([])
63
+ end
64
+
65
+ it 'should not invoke private or protected methods' do
66
+ suppress_output do
67
+ tester = mock('punch')
68
+ tester.should.not.receive(:punch)
69
+ CatyTest.start!(['punch', tester])
70
+
71
+ tester = mock('wine')
72
+ tester.should.not.receive(:wine)
73
+ CatyTest.start!(['wine', tester])
74
+ end.should.not.be.empty
75
+ end
76
+
77
+ it 'should catch task argument errors' do
78
+ suppress_output do
79
+ lambda {
80
+ CatyTest.start!(%w{beer and lemonade})
81
+ }.should.not.raise ArgumentError
82
+ end.should.not.be.empty
83
+ end
84
+
85
+ it 'should not catch other argument errors' do
86
+ suppress_output do
87
+ lambda {
88
+ CatyTest.start!(%w{energy_drink})
89
+ }.should.raise ArgumentError
90
+ end.should.be.empty
91
+ end
92
+
93
+ it 'should handle unknown tasks' do
94
+ suppress_output do
95
+ CatyTest.start!(%w{salad})
96
+ end.should.not.be.empty
97
+ end
98
+
99
+ end
100
+