executable 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.ruby +61 -0
  2. data/.yardopts +7 -0
  3. data/COPYING.rdoc +35 -0
  4. data/DEMO.rdoc +568 -0
  5. data/HISTORY.rdoc +55 -0
  6. data/README.rdoc +101 -51
  7. data/Schedule.reap +17 -0
  8. data/demo/00_introduction.rdoc +6 -0
  9. data/demo/01_single_command.rdoc +44 -0
  10. data/demo/02_multiple_commands.rdoc +125 -0
  11. data/demo/03_help_text.rdoc +109 -0
  12. data/demo/04_manpage.rdoc +14 -0
  13. data/demo/05_optparse_example.rdoc +152 -0
  14. data/demo/06_delegate_example.rdoc +40 -0
  15. data/demo/07_command_methods.rdoc +36 -0
  16. data/demo/08_dispatach.rdoc +29 -0
  17. data/demo/applique/ae.rb +1 -0
  18. data/demo/applique/compare.rb +4 -0
  19. data/demo/applique/exec.rb +1 -0
  20. data/demo/samples/bin/hello +31 -0
  21. data/demo/samples/man/hello.1 +22 -0
  22. data/demo/samples/man/hello.1.html +102 -0
  23. data/demo/samples/man/hello.1.ronn +19 -0
  24. data/lib/executable.rb +67 -128
  25. data/lib/executable/core_ext.rb +102 -0
  26. data/lib/executable/dispatch.rb +30 -0
  27. data/lib/executable/domain.rb +106 -0
  28. data/lib/executable/errors.rb +22 -0
  29. data/lib/executable/help.rb +430 -0
  30. data/lib/executable/parser.rb +208 -0
  31. data/lib/executable/utils.rb +41 -0
  32. data/lib/executable/version.rb +23 -0
  33. data/meta/authors +2 -0
  34. data/meta/copyrights +3 -0
  35. data/meta/created +1 -0
  36. data/meta/description +6 -0
  37. data/meta/name +1 -0
  38. data/meta/organization +1 -0
  39. data/meta/repositories +2 -0
  40. data/meta/requirements +6 -0
  41. data/meta/resources +7 -0
  42. data/meta/summary +1 -0
  43. data/meta/version +1 -0
  44. data/test/test_executable.rb +40 -19
  45. metadata +124 -68
  46. data/History.rdoc +0 -35
  47. data/NOTICE.rdoc +0 -23
  48. data/Profile +0 -30
  49. data/Version +0 -1
  50. data/meta/license/Apache2.txt +0 -177
@@ -0,0 +1,208 @@
1
+ module Executable
2
+
3
+ # The Parser class does all the heavy lifting for Executable.
4
+ #
5
+ class Parser
6
+
7
+ #
8
+ # @param [Executable] cli_class
9
+ # An executabe class.
10
+ #
11
+ def initialize(cli_class)
12
+ @cli_class = cli_class
13
+ end
14
+
15
+ attr :cli_class
16
+
17
+ # Parse command-line.
18
+ #
19
+ # @param argv [Array,String] command-line arguments
20
+ #
21
+ def parse(argv=ARGV)
22
+ # duplicate to make sure ARGV stay intact.
23
+ argv = argv.dup
24
+ argv = parse_shellwords(argv)
25
+
26
+ cmd, argv = parse_subcommand(argv)
27
+ cli = cmd.new
28
+ args = parse_arguments(cli, argv)
29
+
30
+ return cli, args
31
+ end
32
+
33
+ # Make sure arguments are an array. If argv is a String,
34
+ # then parse using Shellwords module.
35
+ #
36
+ # @param argv [Array,String] commmand-line arguments
37
+ def parse_shellwords(argv)
38
+ if String === argv
39
+ require 'shellwords'
40
+ argv = Shellwords.shellwords(argv)
41
+ end
42
+ argv.to_a
43
+ end
44
+
45
+ #
46
+ #
47
+ #
48
+ def parse_subcommand(argv)
49
+ cmd = cli_class
50
+ arg = argv.first
51
+
52
+ while c = cmd.subcommands[arg]
53
+ cmd = c
54
+ argv.shift
55
+ arg = argv.first
56
+ end
57
+
58
+ return cmd, argv
59
+ end
60
+
61
+ #
62
+ # Parse command line options based on given object.
63
+ #
64
+ # @param obj [Object] basis for command-line parsing
65
+ # @param argv [Array,String] command-line arguments
66
+ # @param args [Array] pre-seeded arguments to add to
67
+ #
68
+ # @return [Array] parsed arguments
69
+ #
70
+ def parse_arguments(obj, argv, args=[])
71
+ case argv
72
+ when String
73
+ require 'shellwords'
74
+ argv = Shellwords.shellwords(argv)
75
+ #else
76
+ # argv = argv.dup
77
+ end
78
+
79
+ #subc = nil
80
+ #@args = [] #opts, i = {}, 0
81
+
82
+ while argv.size > 0
83
+ case arg = argv.shift
84
+ when /=/
85
+ parse_equal(obj, arg, argv, args)
86
+ when /^--/
87
+ parse_long(obj, arg, argv, args)
88
+ when /^-/
89
+ parse_flags(obj, arg, argv, args)
90
+ else
91
+ #if Executable === obj
92
+ # if cmd_class = obj.class.subcommands[arg]
93
+ # cmd = cmd_class.new(obj)
94
+ # subc = cmd
95
+ # parse(cmd, argv, args)
96
+ # else
97
+ args << arg
98
+ # end
99
+ #end
100
+ end
101
+ end
102
+
103
+ return args
104
+ end
105
+
106
+ #
107
+ # Parse equal setting comman-line option.
108
+ #
109
+ def parse_equal(obj, opt, argv, args)
110
+ if md = /^[-]*(.*?)=(.*?)$/.match(opt)
111
+ x, v = md[1], md[2]
112
+ else
113
+ raise ArgumentError, "#{x}"
114
+ end
115
+ if obj.respond_to?("#{x}=")
116
+ v = true if v == 'true' # yes or on ?
117
+ v = false if v == 'false' # no or off ?
118
+ obj.send("#{x}=", v)
119
+ else
120
+ obj.__send__(:option_missing, x, v) # argv?
121
+ end
122
+ end
123
+
124
+ #
125
+ # Parse double-dash command-line option.
126
+ #
127
+ def parse_long(obj, opt, argv, args)
128
+ x = opt.sub(/^\-+/, '') # remove '--'
129
+ if obj.respond_to?("#{x}=")
130
+ m = obj.method("#{x}=")
131
+ if obj.respond_to?("#{x}?")
132
+ m.call(true)
133
+ else
134
+ invoke(obj, m, argv)
135
+ end
136
+ elsif obj.respond_to?("#{x}!")
137
+ invoke(obj, "#{x}!", argv)
138
+ else
139
+ # call even if private method
140
+ obj.__send__(:option_missing, x, argv)
141
+ end
142
+ end
143
+
144
+ # TODO: parse_flags needs some thought concerning character
145
+ # spliting and arguments.
146
+
147
+ #
148
+ # Parse single-dash command-line option.
149
+ #
150
+ def parse_flags(obj, opt, argv, args)
151
+ x = opt[1..-1]
152
+ c = 0
153
+ x.split(//).each do |k|
154
+ if obj.respond_to?("#{k}=")
155
+ m = obj.method("#{k}=")
156
+ if obj.respond_to?("#{x}?")
157
+ m.call(true)
158
+ else
159
+ invoke(obj, m, argv) #m.call(argv.shift)
160
+ end
161
+ elsif obj.respond_to?("#{k}!")
162
+ invoke(obj, "#{k}!", argv)
163
+ else
164
+ long = find_long_option(obj, k)
165
+ if long
166
+ if long.end_with?('=') && obj.respond_to?(long.chomp('=')+'?')
167
+ invoke(obj, long, [true])
168
+ else
169
+ invoke(obj, long, argv)
170
+ end
171
+ else
172
+ obj.__send__(:option_missing, x, argv)
173
+ end
174
+ end
175
+ end
176
+ end
177
+
178
+ #
179
+ #
180
+ # @todo Sort alphabetically?
181
+ #
182
+ def find_long_option(obj, char)
183
+ meths = obj.methods.map{ |m| m.to_s }
184
+ meths = meths.select do |m|
185
+ m.start_with?(char) and (m.end_with?('=') or m.end_with?('!'))
186
+ end
187
+ meths.first
188
+ end
189
+
190
+ #
191
+ #
192
+ def invoke(obj, meth, argv)
193
+ m = (Method === meth ? meth : obj.method(meth))
194
+ a = []
195
+ m.arity.abs.times{ a << argv.shift }
196
+ m.call(*a)
197
+ end
198
+
199
+ # Index of subcommands.
200
+ #
201
+ # @return [Hash] name mapped to subcommnd class
202
+ def subcommands
203
+ @cli_class.subcommands
204
+ end
205
+
206
+ end
207
+
208
+ end
@@ -0,0 +1,41 @@
1
+ module Executable
2
+
3
+ # Some handy-dandy CLI utility methods.
4
+ #
5
+ module Utils
6
+ extend self
7
+
8
+ # TODO: Maybe #ask chould serve all purposes depending on degfault?
9
+ # e.g. `ask?("ok?", default=>true)`, would be same as `yes?("ok?")`.
10
+
11
+ # Strings to interprest as boolean values.
12
+ BOOLEAN_MAP = {"y"=>true, "yes"=>true, "n"=>false, "no"=>false}
13
+
14
+ # Query the user for a yes/no answer, defaulting to yes.
15
+ def yes?(question, options={})
16
+ print "#{question} [Y/n] "
17
+ input = STDIN.readline.chomp.downcase
18
+ BOOLEAN_MAP[input] || true
19
+ end
20
+
21
+ # Query the user for a yes/no answer, defaulting to no.
22
+ def no?(question, options={})
23
+ print "#{question} [y/N] "
24
+ input = STDIN.readline.chomp.downcase
25
+ BOOLEAN_MAP[input] || false
26
+ end
27
+
28
+ # Query the user for an answer.
29
+ def ask(question, options={})
30
+ print "#{question} [default: #{options[:default]}] "
31
+ reply = STDIN.readline.chomp
32
+ if reply.empty?
33
+ options[:default]
34
+ else
35
+ reply
36
+ end
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,23 @@
1
+ module Exectuable
2
+
3
+ #
4
+ DIRECTORY = File.dirname(__FILE__)
5
+
6
+ #
7
+ def self.version
8
+ @package ||= (
9
+ require 'yaml'
10
+ YAML.load(File.new(DIRECTORY + '/version.yml'))
11
+ )
12
+ end
13
+
14
+ #
15
+ def self.const_missing(name)
16
+ version[name.to_s.downcase] || super(name)
17
+ end
18
+
19
+ # because Ruby 1.8~ gets in the way
20
+ remove_const(:VERSION) if const_defined?(:VERSION)
21
+
22
+ end
23
+
@@ -0,0 +1,2 @@
1
+ ---
2
+ - 7rans <transfire@gmail.com>
@@ -0,0 +1,3 @@
1
+ ---
2
+ - (c) 2008 Rubyworks (BSD-2-Clause)
3
+
@@ -0,0 +1 @@
1
+ 2008-08-08
@@ -0,0 +1,6 @@
1
+ Think of Executable as a *COM*, a Commandline Object Mapper,
2
+ in much the same way that ActiveRecord is an ORM,
3
+ an Object Relational Mapper. A class utilizing Executable
4
+ can define a complete command line tool using nothing more
5
+ than Ruby's own method definitions.
6
+
@@ -0,0 +1 @@
1
+ executable
@@ -0,0 +1 @@
1
+ rubyworks
@@ -0,0 +1,2 @@
1
+ ---
2
+ upstream: git://github.com/rubyworks/executable.git
@@ -0,0 +1,6 @@
1
+ ---
2
+ - qed (test)
3
+ - ae (test)
4
+ - detroit (build)
5
+ - simplecov (build)
6
+
@@ -0,0 +1,7 @@
1
+ ---
2
+ home: http://rubyworks.github.com/executable
3
+ code: http://github.com/rubyworks/executable
4
+ bugs: http://github.com/rubyworks/executable/issues
5
+ mail: http://groups.google.com/rubyworks-mailinglist
6
+ chat: irc://chat.us.freenode.net#rubyworks
7
+
@@ -0,0 +1 @@
1
+ Commandline Object Mapper
@@ -0,0 +1 @@
1
+ 1.2.0
@@ -1,38 +1,59 @@
1
+ $:.unshift(File.dirname(__FILE__)+'/../lib')
2
+
3
+ require 'microtest'
4
+ require 'ae'
5
+
1
6
  require 'executable'
2
7
 
3
- class TestExecutable < Test::Unit::TestCase
8
+ class ExecutableTestCase < MicroTest::TestCase
4
9
 
5
- class SampleCli
10
+ class MyCommand
6
11
  include Executable
7
12
 
8
- attr :result
13
+ attr_reader :size, :quiet, :file
9
14
 
10
15
  def initialize
11
- @result = []
16
+ @file = 'hey.txt' # default
17
+ end
18
+
19
+ def quiet=(bool)
20
+ @quiet = bool
21
+ end
22
+
23
+ def quiet?
24
+ @quiet
12
25
  end
13
26
 
14
- def output=(value)
15
- @result << "output: #{value}"
27
+ def size=(integer)
28
+ @size = integer.to_i
16
29
  end
17
30
 
18
- def jump
19
- @result << "jump"
31
+ def file=(fname)
32
+ @file = fname
33
+ end
34
+
35
+ def call(*args)
36
+ @args = args
20
37
  end
21
38
  end
22
39
 
23
- #
24
- def test_parse_without_option
25
- s = SampleCli.new
26
- s.execute!("jump")
27
- assert_equal(s.result, ["jump"])
40
+ def test_boolean_optiion
41
+ mc = MyCommand.execute('--quiet')
42
+ mc.assert.quiet?
28
43
  end
29
44
 
30
- #
31
- def test_parse_with_option
32
- s = SampleCli.new
33
- s.execute!("jump --output=home")
34
- assert_equal(s.result, ['output: home', 'jump'])
45
+ def test_integer_optiion
46
+ mc = MyCommand.execute('--size=4')
47
+ mc.size.assert == 4
35
48
  end
36
49
 
37
- end
50
+ def test_default_value
51
+ mc = MyCommand.execute('')
52
+ mc.file.assert == 'hey.txt'
53
+ end
54
+
55
+ #def usage_output
56
+ # MyCommand.help.usage.assert == "{$0} [options] [subcommand]"
57
+ #end
38
58
 
59
+ end
metadata CHANGED
@@ -1,90 +1,146 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: executable
3
- version: !ruby/object:Gem::Version
4
- hash: 19
5
- prerelease: false
6
- segments:
7
- - 1
8
- - 1
9
- - 0
10
- version: 1.1.0
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.0
5
+ prerelease:
11
6
  platform: ruby
12
- authors:
13
- - Thomas Sawyer
7
+ authors:
8
+ - 7rans
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2011-04-21 00:00:00 -04:00
19
- default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
22
- name: turn
12
+ date: 2012-02-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: qed
16
+ requirement: &16160360 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
23
  prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
24
+ version_requirements: *16160360
25
+ - !ruby/object:Gem::Dependency
26
+ name: ae
27
+ requirement: &16158800 !ruby/object:Gem::Requirement
25
28
  none: false
26
- requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- hash: 3
30
- segments:
31
- - 0
32
- version: "0"
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
33
  type: :development
34
- version_requirements: *id001
35
- description: The Executable mixin is a very quick and and easy way to make almost any class usable via a command line interface. It simply uses writer methods as option setters, and the first command line argument as the method to call, with the subsequent arguments passed to the method.
36
- email: ""
37
- executables: []
34
+ prerelease: false
35
+ version_requirements: *16158800
36
+ - !ruby/object:Gem::Dependency
37
+ name: detroit
38
+ requirement: &16156720 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *16156720
47
+ - !ruby/object:Gem::Dependency
48
+ name: simplecov
49
+ requirement: &16155820 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *16155820
58
+ description: ! 'Think of Executable as a *COM*, a Commandline Object Mapper,
38
59
 
39
- extensions: []
60
+ in much the same way that ActiveRecord is an ORM,
61
+
62
+ an Object Relational Mapper. A class utilizing Executable
63
+
64
+ can define a complete command line tool using nothing more
40
65
 
41
- extra_rdoc_files:
66
+ than Ruby''s own method definitions.'
67
+ email:
68
+ - transfire@gmail.com
69
+ executables: []
70
+ extensions: []
71
+ extra_rdoc_files:
72
+ - HISTORY.rdoc
73
+ - DEMO.rdoc
42
74
  - README.rdoc
43
- files:
75
+ - COPYING.rdoc
76
+ files:
77
+ - .ruby
78
+ - .yardopts
79
+ - demo/00_introduction.rdoc
80
+ - demo/01_single_command.rdoc
81
+ - demo/02_multiple_commands.rdoc
82
+ - demo/03_help_text.rdoc
83
+ - demo/04_manpage.rdoc
84
+ - demo/05_optparse_example.rdoc
85
+ - demo/06_delegate_example.rdoc
86
+ - demo/07_command_methods.rdoc
87
+ - demo/08_dispatach.rdoc
88
+ - demo/applique/ae.rb
89
+ - demo/applique/compare.rb
90
+ - demo/applique/exec.rb
91
+ - demo/samples/bin/hello
92
+ - demo/samples/man/hello.1
93
+ - demo/samples/man/hello.1.html
94
+ - demo/samples/man/hello.1.ronn
95
+ - lib/executable/core_ext.rb
96
+ - lib/executable/dispatch.rb
97
+ - lib/executable/domain.rb
98
+ - lib/executable/errors.rb
99
+ - lib/executable/help.rb
100
+ - lib/executable/parser.rb
101
+ - lib/executable/utils.rb
102
+ - lib/executable/version.rb
44
103
  - lib/executable.rb
45
- - meta/license/Apache2.txt
104
+ - meta/authors
105
+ - meta/copyrights
106
+ - meta/created
107
+ - meta/description
108
+ - meta/name
109
+ - meta/organization
110
+ - meta/repositories
111
+ - meta/requirements
112
+ - meta/resources
113
+ - meta/summary
114
+ - meta/version
46
115
  - test/test_executable.rb
47
- - Profile
116
+ - HISTORY.rdoc
117
+ - DEMO.rdoc
118
+ - Schedule.reap
48
119
  - README.rdoc
49
- - History.rdoc
50
- - Version
51
- - NOTICE.rdoc
52
- has_rdoc: true
120
+ - COPYING.rdoc
53
121
  homepage: http://rubyworks.github.com/executable
54
- licenses:
55
- - Apache 2.0
122
+ licenses:
123
+ - BSD-2-Clause
56
124
  post_install_message:
57
- rdoc_options:
58
- - --title
59
- - Executable API
60
- - --main
61
- - README.rdoc
62
- require_paths:
125
+ rdoc_options: []
126
+ require_paths:
63
127
  - lib
64
- required_ruby_version: !ruby/object:Gem::Requirement
128
+ required_ruby_version: !ruby/object:Gem::Requirement
65
129
  none: false
66
- requirements:
67
- - - ">="
68
- - !ruby/object:Gem::Version
69
- hash: 3
70
- segments:
71
- - 0
72
- version: "0"
73
- required_rubygems_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
135
  none: false
75
- requirements:
76
- - - ">="
77
- - !ruby/object:Gem::Version
78
- hash: 3
79
- segments:
80
- - 0
81
- version: "0"
136
+ requirements:
137
+ - - ! '>='
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
82
140
  requirements: []
83
-
84
- rubyforge_project: executable
85
- rubygems_version: 1.3.7
141
+ rubyforge_project:
142
+ rubygems_version: 1.8.11
86
143
  signing_key:
87
144
  specification_version: 3
88
- summary: Any class, a command-line interface.
89
- test_files:
90
- - test/test_executable.rb
145
+ summary: Commandline Object Mapper
146
+ test_files: []