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,101 @@
1
+ #
2
+ # Contains all the option converters.
3
+ #
4
+
5
+ #
6
+ # Base class for all option converters.
7
+ # Offers some metaprogramming.
8
+ #
9
+ class Caty::Converter
10
+
11
+ include Caty::Helpers
12
+
13
+ class << self
14
+
15
+ attr_accessor :allowed_defaults
16
+
17
+ #
18
+ # Registers a converter subclass for a type (symbol)
19
+ # and some default value classes
20
+ #
21
+ def type( type, *allowed_defaults )
22
+ @@types ||= Hash.new
23
+ @@types[type] = self
24
+ Caty::OptionConstructor.register(type)
25
+ @allowed_defaults = allowed_defaults
26
+ end
27
+
28
+ #
29
+ # Returns a hash containing
30
+ # :type => Class
31
+ # for all converter subclasses
32
+ #
33
+ def types
34
+ @@types ||= Hash.new
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+
41
+ #
42
+ # Converter for boolean values:
43
+ # %w(true false 1 0) << nil
44
+ #
45
+ class Caty::BooleanConverter < Caty::Converter
46
+
47
+ type(:boolean, TrueClass, FalseClass)
48
+
49
+ def convert( value )
50
+ case value
51
+ when 'true', '1', nil then true
52
+ when 'false', '0' then false
53
+ else
54
+ e = Caty::OptionArgumentError.new
55
+ e.expected = '0, 1, true, false or no argument'
56
+ raise e
57
+ end
58
+ end
59
+
60
+ end
61
+
62
+ #
63
+ # Converter for string values:
64
+ # /.*/
65
+ #
66
+ class Caty::StringConverter < Caty::Converter
67
+
68
+ type(:string, String)
69
+
70
+ def convert( value )
71
+ case value
72
+ when nil
73
+ e = Caty::OptionArgumentError.new
74
+ e.expected = 'a string'
75
+ raise e
76
+ else value
77
+ end
78
+ end
79
+
80
+ end
81
+
82
+ #
83
+ # Converter for integer values:
84
+ # /^[+-]?[0-9]+$/
85
+ #
86
+ class Caty::IntegerConverter < Caty::Converter
87
+
88
+ type(:integer, Fixnum)
89
+
90
+ def convert( value )
91
+ case value
92
+ when %r{^[+-]?[0-9]+$} then value.to_i
93
+ else
94
+ e = Caty::OptionArgumentError.new
95
+ e.expected = 'an integer'
96
+ raise e
97
+ end
98
+ end
99
+
100
+ end
101
+
@@ -0,0 +1,17 @@
1
+ #
2
+ # Contains error classes used by Caty.
3
+ #
4
+
5
+ class Caty::NoSuchTaskError < ArgumentError
6
+ end
7
+
8
+ class Caty::OptionArgumentError < ArgumentError
9
+
10
+ attr_accessor :expected, :option
11
+
12
+ def message
13
+ "Bad argument to option `#{self.option}'. Expected #{self.expected}."
14
+ end
15
+
16
+ end
17
+
@@ -0,0 +1,40 @@
1
+ #
2
+ # Contains the GlobalOption class.
3
+ #
4
+
5
+ #
6
+ # Represents a single global option.
7
+ #
8
+ # A GlobalOption object is created for every option
9
+ # specified via Caty::global_options().
10
+ # The sum of all global options parsed is accessible
11
+ # via the Caty#global_options() method.
12
+ #
13
+ class Caty::GlobalOption < Caty::Option
14
+
15
+ include Caty::HasDescription
16
+
17
+ #
18
+ # Returns a string representation to be used by
19
+ # the help system.
20
+ #
21
+ def to_help
22
+ [ self.to_s, self.short_description, self.description ]
23
+ end
24
+
25
+ def to_s( ljust = 0 )
26
+ returning('') do |str|
27
+ str << "#{self.prefix}#{@name}=#{
28
+ @default.nil? ? '' : @default.inspect
29
+ }".ljust(ljust)
30
+ end
31
+ end
32
+
33
+ protected
34
+
35
+ def prefix
36
+ '--'
37
+ end
38
+
39
+ end
40
+
@@ -0,0 +1,26 @@
1
+ #
2
+ # Contains the HasDescription module.
3
+ #
4
+
5
+ #
6
+ # Mixin for everything that can has description ;-)
7
+ #
8
+ module Caty::HasDescription
9
+
10
+ attr_accessor :description
11
+
12
+ #
13
+ # If a description was set for this object,
14
+ # this will return the first line of that desciption.
15
+ # Else nil will be returned.
16
+ #
17
+ def short_description
18
+ if self.description.nil?
19
+ nil
20
+ else
21
+ self.description[%r{^[^\n\r]*}]
22
+ end
23
+ end
24
+
25
+ end
26
+
@@ -0,0 +1,107 @@
1
+ #
2
+ # Contains the objects related to the help feature.
3
+ #
4
+
5
+ #
6
+ # Will be mixed into the Caty class.
7
+ #
8
+ module Caty::HelpSystem
9
+
10
+ protected
11
+
12
+ #
13
+ # Interface for Caty's help system.
14
+ # Displays either a general help page for all tasks
15
+ # and options known to Caty in case no argument was given;
16
+ # or a page descriping a certain task/option.
17
+ #
18
+ def help( task_or_option = nil )
19
+ if task_or_option.nil?
20
+ help_overview
21
+ else
22
+ token_help(task_or_option.to_sym)
23
+ end
24
+ end
25
+
26
+ #
27
+ # Applies the standard help task
28
+ #
29
+ def help_task
30
+ self.class_eval do
31
+ desc('[TOKEN]', cut("
32
+ Provides help about the program.
33
+ A TOKEN is the name of a task or an option, e.g. 'help' for
34
+ the help task or 'version' for the --version option.
35
+ If a TOKEN is given, help for that specific TOKEN is displayed.
36
+ Otherwise, an overview of all the tasks and options is shown.
37
+ "))
38
+ def help( task_or_option = nil )
39
+ self.class.help(task_or_option)
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ #
47
+ # Displays the auto-generated help for all tasks,
48
+ # options and global options known to Caty.
49
+ #
50
+ def help_overview
51
+ task_descs = @tasks.to_a.reject { |task| task.is_a?(Caty::Indirection) }.map(&:to_help)
52
+ goption_descs = @global_options.map(&:to_help)
53
+ column_width = (task_descs + goption_descs).map(&:first).map(&:length).max
54
+
55
+ $stdout.puts 'Commands'
56
+ $stdout.puts '========'
57
+ $stdout.puts
58
+
59
+ task_descs.each do |desc|
60
+ $stdout.puts "#{desc[0].ljust(column_width)} # #{desc[1]}"
61
+ end
62
+
63
+ $stdout.puts
64
+ $stdout.puts 'Global Options'
65
+ $stdout.puts '=============='
66
+ $stdout.puts
67
+
68
+ goption_descs.each do |desc|
69
+ $stdout.puts "#{desc[0].ljust(column_width)} # #{desc[1]}"
70
+ end
71
+ end
72
+
73
+ #
74
+ # Displays the auto-generated help for a certain task/option.
75
+ #
76
+ def token_help( token )
77
+ goptions = @global_options
78
+ tasks = @tasks.to_a
79
+ item = @tasks.resolve(token) || goptions.find { |item| item.name == token }
80
+
81
+ if item.nil?
82
+ $stdout.puts "Sorry, but I don't know `#{token}'"
83
+ else
84
+ aliases = @tasks.find_all do |_,task|
85
+ task.is_a?(Caty::Indirection) and task.target == item.name
86
+ end.map(&:first).join(', ')
87
+
88
+ help_text = item.to_help
89
+ $stdout.puts help_text[0]
90
+ $stdout.puts
91
+ $stdout.puts help_text[2]
92
+
93
+ unless aliases.empty?
94
+ $stdout.puts
95
+ $stdout.puts "Aliases: #{aliases}"
96
+ end
97
+ end
98
+ end
99
+
100
+ #
101
+ # Returns the tasks as an array, with all indirections and removed.
102
+ #
103
+ def taskarray
104
+ end
105
+
106
+ end
107
+
@@ -0,0 +1,19 @@
1
+ #
2
+ # Contains the Helpers module that will be
3
+ # mixed into a number of classes.
4
+ #
5
+
6
+ module Caty::Helpers
7
+
8
+ private
9
+
10
+ #
11
+ # The K combinator.
12
+ #
13
+ def returning( value )
14
+ yield value
15
+ value
16
+ end
17
+
18
+ end
19
+
@@ -0,0 +1,26 @@
1
+ #
2
+ # Contains the Indirection class.
3
+ #
4
+
5
+ #
6
+ # Used to map things to other tasks and resolve
7
+ # those mappings at runtime.
8
+ #
9
+ Caty::Indirection = Struct.new(:name, :target) do
10
+
11
+ #
12
+ # Follows the indirection until it reaches
13
+ # an end point and returns that.
14
+ #
15
+ def resolve( task_hash )
16
+ next_item = task_hash[self.target]
17
+
18
+ if next_item.is_a?(Caty::Indirection)
19
+ next_item.resolve(task_hash)
20
+ else
21
+ next_item
22
+ end
23
+ end
24
+
25
+ end
26
+
@@ -0,0 +1,100 @@
1
+ #
2
+ # Contains the Option class.
3
+ #
4
+
5
+ #
6
+ # Represents a single option.
7
+ #
8
+ # An Option object is created for every option
9
+ # specified via Caty#task_options().
10
+ # The sum of all options parsed is accessible
11
+ # via the Caty#task_options() method.
12
+ #
13
+ class Caty::Option
14
+
15
+ include Caty::Helpers
16
+
17
+ attr_reader :name
18
+
19
+ #
20
+ # Creates a new option with the given name and default value.
21
+ #
22
+ # default may be one of the following:
23
+ # - :boolean
24
+ # nil is the default, any given argument will be coerced
25
+ # into a boolean value.
26
+ # - :string
27
+ # nil is the default, any given argument will be coerced
28
+ # into a string value.
29
+ # - :integer
30
+ # nil is the default, any given argument will be coerced
31
+ # into a integer value.
32
+ # - an Integer, String or Boolean value
33
+ # the passed value is the default value, any given argument
34
+ # will be coerced into the given type.
35
+ # - any other value will be treated as if a String had been
36
+ # given.
37
+ #
38
+ # If the deduced argument type is boolean, not giving an argument
39
+ # on the command line is interpreted as giving 'true' as the argument.
40
+ # For all the other types, a MissingOptionArgumentError is thrown.
41
+ #
42
+ def initialize( name, default )
43
+ @name = name
44
+ @converter = nil
45
+
46
+ Caty::Converter.types.each do |type,converter|
47
+ if default == type
48
+ @converter = converter.new
49
+ @default = nil
50
+ break
51
+ elsif converter.allowed_defaults.any? { |klass| default.is_a?(klass) }
52
+ @converter = converter.new
53
+ @default = default
54
+ break
55
+ end
56
+ end
57
+
58
+ raise(
59
+ ArgumentError,
60
+ 'Only boolean, string and integer values or :boolean, :integer, :string allowed.'
61
+ ) if @converter.nil?
62
+ end
63
+
64
+ #
65
+ # Tries to remove the option from the args.
66
+ # Returns the value grepped for this option.
67
+ #
68
+ def grep!( args )
69
+ rex = %r{^#{self.prefix}#{@name}(?:=(.+))?$}
70
+ index = args.index { |arg| rex =~ arg }
71
+
72
+ if index.nil?
73
+ @default
74
+ else
75
+ match = rex.match(args.delete_at(index))
76
+ @converter.convert(match[1])
77
+ end
78
+ rescue Caty::OptionArgumentError => e
79
+ e.option = @name
80
+ raise e
81
+ end
82
+
83
+ def to_s
84
+ "[#{self.prefix}#{@name}=#{
85
+ @default.nil? ? '' : @default.inspect
86
+ }]"
87
+ end
88
+
89
+ protected
90
+
91
+ #
92
+ # Defines the prefix of this option.
93
+ # May be overwritten by subclasses.
94
+ #
95
+ def prefix
96
+ '-'
97
+ end
98
+
99
+ end
100
+