clint 0.1.0

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.
Files changed (3) hide show
  1. data/lib/clint.rb +166 -0
  2. data/man/clint.7.gz +0 -0
  3. metadata +106 -0
@@ -0,0 +1,166 @@
1
+ class Clint
2
+
3
+ def initialize(options={})
4
+ reset
5
+ @strict = !options[:strict].nil?
6
+ end
7
+
8
+ def usage
9
+ if block_given?
10
+ @usage = Proc.new
11
+ else
12
+ @usage.call if @usage.respond_to? :call
13
+ end
14
+ end
15
+
16
+ def help
17
+ if block_given?
18
+ @help = Proc.new
19
+ else
20
+ usage
21
+ @help.call if @help.respond_to? :call
22
+ end
23
+ end
24
+
25
+ # Reset the list of valid options and aliases.
26
+ def reset
27
+ @options, @aliases = {}, {}
28
+ end
29
+
30
+ # Add new valid options and aliases with either classes to be constructed
31
+ # or default values (from which classes are inferred). This returns
32
+ # @options and thus works as an attr_reader with no arguments.
33
+ def options(options={})
34
+ options.each do |option, default|
35
+ option = option.to_sym
36
+ if Symbol == default.class
37
+ @aliases[option] = default
38
+ else
39
+ if Class == default.class
40
+ if default.respond_to? :new
41
+ begin
42
+ @options[option] = default.new
43
+ rescue ArgumentError
44
+ @options[option] = default.new(nil)
45
+ end
46
+ else
47
+ begin
48
+ @options[option] = default()
49
+ rescue ArgumentError
50
+ @options[option] = default(nil)
51
+ end
52
+ end
53
+ else
54
+ @options[option] = default
55
+ end
56
+ end
57
+ end
58
+ @options
59
+ end
60
+
61
+ attr_reader :aliases
62
+
63
+ # Parse arguments, saving options in @options and leaving everything else
64
+ # in @args.
65
+ def parse(args=nil)
66
+ args = @args if args.nil?
67
+ i = 0
68
+ while args.length > i do
69
+
70
+ # Skip anything not structured like an option.
71
+ case args[i]
72
+ when /^-([^-=\s])$/
73
+ when /^-([^-=\s])\s*(.+)$/
74
+ when /^--([^=\s]+)$/
75
+ when /^--([^=\s]+)(?:=|\s+)(.+)?$/
76
+ else
77
+ i += 1
78
+ next
79
+ end
80
+ option, value = $1.to_sym, $2
81
+
82
+ # Follow aliases through to a real option.
83
+ option = @aliases[option] while @aliases[option]
84
+
85
+ # Skip unknown options unless we're in strict mode.
86
+ if @options[option].nil?
87
+ if @strict
88
+ usage
89
+ exit 1
90
+ end
91
+ i += 1
92
+ next
93
+ end
94
+
95
+ # Handle boolean options.
96
+ if [TrueClass, FalseClass].include? @options[option].class
97
+ unless value.nil?
98
+ usage
99
+ exit 1
100
+ end
101
+ args.delete_at i
102
+ @options[option] = !@options[option]
103
+
104
+ # Handle options with values. The call to new below may raise
105
+ # NoMethodError but this is allowed to surface so it's noticed
106
+ # during development.
107
+ else
108
+ args.delete_at i
109
+ value = args.delete_at(i) if value.nil?
110
+ if value.nil?
111
+ usage
112
+ exit 1
113
+ end
114
+ @options[option] = @options[option].class.new(value)
115
+
116
+ end
117
+ end
118
+ @args = args
119
+ end
120
+
121
+ # Treat the first non-option argument as a subcommand in the given class.
122
+ # If a suitable class method is found, it is called with all remaining
123
+ # arguments, including @options if we can get away with it. Otherwise,
124
+ # an instance is constructed with the next non-option argument and the
125
+ # subcommand is sent to the instance with the remaining non-option
126
+ # arguments.
127
+ def subcommand(klass)
128
+ if 1 > @args.length
129
+ usage
130
+ exit 1
131
+ end
132
+ subcommand = @args.shift.to_sym
133
+ if klass.singleton_methods(false).include? subcommand.to_s
134
+ arity = klass.method(subcommand).arity
135
+ if @args.length != arity && -@args.length - 1 != arity
136
+ usage
137
+ exit 1
138
+ end
139
+ begin
140
+ klass.send subcommand, *(@args + [@options])
141
+ rescue ArgumentError
142
+ klass.send subcommand, *@args
143
+ end
144
+ exit 0
145
+ end
146
+ if 1 > @args.length
147
+ usage
148
+ exit 1
149
+ end
150
+ instance = klass.new(@args.shift)
151
+ if instance.public_methods(false).include? subcommand.to_s
152
+ arity = instance.method(subcommand).arity
153
+ if @args.length != arity && -@args.length - 1 != arity
154
+ usage
155
+ exit 1
156
+ end
157
+ begin
158
+ instance.send subcommand, *(@args + [@options])
159
+ rescue ArgumentError
160
+ instance.send subcommand, *@args
161
+ end
162
+ exit 0
163
+ end
164
+ end
165
+
166
+ end
Binary file
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: clint
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Richard Crowley
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-02-22 00:00:00 +00:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: |
22
+ clint(7) -- Ruby command line argument parser
23
+ =============================================
24
+
25
+ ## SYNOPSIS
26
+
27
+ require 'clint'
28
+ c = Clint.new
29
+ c.usage do
30
+ $stderr.puts "Usage: #{File.basename(__FILE__)} [-h|--help]"
31
+ end
32
+ c.help do
33
+ $stderr.puts " -h, --help\tshow this help message"
34
+ end
35
+ c.options :help => false, :h => :help
36
+ c.parse ARGV
37
+ if c.options[:help]
38
+ c.help
39
+ exit 1
40
+ end
41
+ c.subcommand Klass
42
+
43
+ ## DESCRIPTION
44
+
45
+ Clint is an alternative Ruby command line argument parser that's very good for programs using the subcommand pattern familiar from `git`(1), `svn`(1), `apt-get`(8), and many others. In addition, it separates option declarations from usage and help messages becuase the author feels like that's a better idea.
46
+
47
+ Clint options are declared by passing hash arguments to `Clint#options`. The hash keys should be `Symbol`s. If the value is also a `Symbol`, an alias is defined from the key to the value. If the value is a `Class`, Clint attempts to find a default value for that class. Otherwise, the value is treated as the default and the value's class will be used to construct type-accurate values from command line arguments.
48
+
49
+ `Clint#options` may be called repeatedly to declare extra options and aliases. `Clint#reset` can be used at any time to clear all declared options and aliases.
50
+
51
+ `Clint#parse` may likewise be called repeatedly. At the end of each invocation, it stores the remaining non-option arguments, meaning that arguments (for example, `ARGV`) must only be passed as a parameter to the first invocation.
52
+
53
+ `Clint#subcommand` may be called after `Clint#parse` to automatically handle the subcommand pattern as follows. The first non-option argument is taken to be the subcommand, which must exist as a singleton or instance method of the class object passed to `Clint#subcommand`. If a suitable class method is found, it is called with all remaining arguments, including a hash of the parsed options if we can get away with it. Otherwise, an instance is constructed with the next non-option argument and the instance method is called with all remaining arguments, again including a hash of the parsed options if we can get away with it.
54
+
55
+ Due to limitations in the Ruby 1.8 grammar, all methods that could act as subcommands must not declare default argument values except `options={}` if desired.
56
+
57
+ ## AUTHOR
58
+
59
+ Richard Crowley <r@rcrowley.org>
60
+
61
+ ## SEE ALSO
62
+
63
+ The standard Ruby `OptionParser` class <http://ruby-doc.org/stdlib/libdoc/optparse/rdoc/classes/OptionParser.html>.
64
+
65
+ email: r@rcrowley.org
66
+ executables: []
67
+
68
+ extensions: []
69
+
70
+ extra_rdoc_files: []
71
+
72
+ files:
73
+ - lib/clint.rb
74
+ - man/clint.7.gz
75
+ has_rdoc: true
76
+ homepage: http://github.com/rcrowley/clint.git
77
+ licenses: []
78
+
79
+ post_install_message:
80
+ rdoc_options: []
81
+
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ segments:
89
+ - 0
90
+ version: "0"
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ segments:
96
+ - 0
97
+ version: "0"
98
+ requirements: []
99
+
100
+ rubyforge_project:
101
+ rubygems_version: 1.3.6
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: command line argument parser
105
+ test_files: []
106
+