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,55 @@
1
+ = RELEASE HISTORY
2
+
3
+ == 1.2.0 / 2012-01-31
4
+
5
+ Version 1.2.0 is complete rewrite of Executable. Actually it was decided that
6
+ the old design was too simplistic in it design concept, so another library
7
+ that was in the works, called Executioner, and briefly CLI::Base, was
8
+ ported over. And with some API changes, it is now the new Executable project.
9
+ The idea of the project is generally the same, but Executable now offers
10
+ more features, such as good help output and namespace-based subcomamnds.
11
+ Of course, to accommodate all this the API had to change some over
12
+ the previous version, so be sure to read the API documentation.
13
+
14
+ Changes:
15
+
16
+ * Deprecate old implementation.
17
+ * Port Executioner project over to become new Executable project.
18
+ * Supports namespace-base subcommmands.
19
+ * Supports formatted help output in plain text and markdown.
20
+ * Supports manpage look-up and display.
21
+
22
+
23
+ == 1.1.0 / 2011-04-21
24
+
25
+ This release simplifies Executable, removing the #option_missing method
26
+ and using the standard #method_missing callback instead. Along with this
27
+ the special error class, +NoOptionError+, has been removed. This release
28
+ also fixes an issue with inconsistent arguments being passed to the callback.
29
+ Finally it renames the #execute_command method to simple #execute!.
30
+
31
+ Changes:
32
+
33
+ * Name Changes
34
+
35
+ * Renamed `#execute_command` method to `#execute!`.
36
+
37
+ * Deprecations
38
+
39
+ * Rely on #method_missing callback instead of special #option_missing method.
40
+ * The +NoOptionError+ exception class is no longer needed because of above.
41
+
42
+ * Bug Fixes
43
+
44
+ * The #method_missing callback takes the value of the option being set.
45
+
46
+
47
+ == 1.0.0 / 2011-04-15
48
+
49
+ This is the initialize release of Executable (as a stand alone project).
50
+ Executable is a mixin that can turn any class into an commandline interface.
51
+
52
+ * 1 Major Enhancement
53
+
54
+ * Birthday!
55
+
@@ -1,94 +1,144 @@
1
1
  = Executable
2
2
 
3
+ {Website}[http://rubyworks.github.com/executable] |
4
+ {Source Code}[http://github.com/rubyworks/executable] |
5
+ {Report Issue}[http://github.com/rubyworks/executable/features] |
6
+ {#rubyworks}[irc://irc.freenode.org/rubyworks]
7
+
8
+ {<img src="http://travis-ci.org/rubyworks/executable.png"/>}[http://travis-ci.org/rubyworks/executable]
9
+
10
+
3
11
  == DESCRIPTION
4
12
 
5
- The Executable mixin is a very quick and and easy
6
- way to make almost any class usable via a command
7
- line interface. It simply uses writer methods as
8
- option setters, and the first command line argument
9
- as the method to call, with the subsequent arguments
10
- passed to the method.
13
+ Executable is to the commandline, what ActiveRecord is the database.
14
+ You can think of Executable as a *COM*, a Command-line Object Mapper,
15
+ just as ActiveRecord is an ORM (Object Relational Mapper). Any class
16
+ mixing in Executable or subclassing Executable::Command can define
17
+ a complete command line tool using nothing more than Ruby's standard
18
+ syntax. No special DSL is required.
11
19
 
12
20
 
13
21
  == FEATURES
14
22
 
15
- * Super easy to use, just mixin.
23
+ * Easy to use, just mixin or subclass.
24
+ * Define #call to control the command procedure.
16
25
  * Public writers become options.
17
- * Public methods become subcommands.
26
+ * Namespace children become subcommands.
27
+ * Or easily dispatch subcommands to public methods.
28
+ * Generate help in plain text or markdown.
18
29
 
19
30
 
20
- == RESOURCES
31
+ == LIMITATIONS
21
32
 
22
- * http://rubyworks.github.com/executable
23
- * http://github.com/rubyworks/executable
33
+ * Ruby 1.9+ only.
34
+ * Help doesn't handle aliases well (yet).
24
35
 
25
36
 
26
37
  == RELEASE NOTES
27
38
 
28
- Please see HISTORY file.
39
+ Please see HISTORY.rdoc file.
29
40
 
30
41
 
31
42
  == SYNOPSIS
32
43
 
33
- Simply mixin Executable, then call #execute_command.
44
+ CLIs can be built by using a Executable as a mixin, or by subclassing
45
+ `Executable::Command`. Methods seemlessly handle command-line options.
46
+ Writer methods (those ending in '=') correspond to options and query
47
+ methods (those ending in '?') modify them to be boolean switches.
48
+
49
+ For example, here is a simple "Hello, World!" commandline tool.
50
+
51
+ require 'executable'
52
+
53
+ class HelloCommand
54
+ include Executable
55
+
56
+ # Say it in uppercase?
57
+ def load=(bool)
58
+ @loud = bool
59
+ end
60
+
61
+ #
62
+ def loud?
63
+ @loud
64
+ end
65
+
66
+ # Show this message.
67
+ def help?
68
+ cli.show_help
69
+ exit
70
+ end
71
+ alias :h? :help?
72
+
73
+ # Say hello.
74
+ def call(name)
75
+ name = name || 'World'
76
+ str = "Hello, #{name}!"
77
+ str = str.upcase if loud?
78
+ puts str
79
+ end
80
+ end
34
81
 
35
- class X
36
- include Executable
82
+ To make the command available on the command line, add an executable
83
+ to your project passing ARGV to the #execute or #run methods.
37
84
 
38
- attr_accessor :quiet
85
+ #!usr/bin/env ruby
86
+ require 'hello.rb'
87
+ HelloCommand.run
39
88
 
40
- attr_accessor :size
89
+ If we named this file `hello`, set its execute flag and made it available
90
+ on our systems $PATH, then:
41
91
 
42
- def bread(*args)
43
- ["bread", quiet, size, *args]
44
- end
92
+ $ hello
93
+ Hello, World!
45
94
 
46
- def butter(*args)
47
- ["butter", quiet, size, *args]
48
- end
49
- end
95
+ $ hello John
96
+ Hello, John!
50
97
 
51
- x = X.new
98
+ $ hello --loud John
99
+ HELLO, JOHN!
52
100
 
53
- x.execute!("butter yum")
54
- => ["butter", nil, nil, "yum"]
101
+ Executable can also generate help text for commands.
55
102
 
56
- x.execute!("bread --quiet --size=big")
57
- => ["bread", true, "big"]
103
+ $ hello --help
104
+ USAGE: hello [options]
58
105
 
106
+ Say hello.
59
107
 
60
- Notice that Executable requires an equal-sign (<code>=</code>) be used
61
- when specifying values for non-boolean attributes.
108
+ --loud Say it in uppercase?
109
+ --help Show this message
62
110
 
63
- To make the command available on the command line, add an executable
64
- to your project passing ARGV to the #execute_command method.
111
+ If you look back at the class definition you can see it's pulling
112
+ comments from the source to provide descriptions. It pulls the
113
+ description the command itself from the `#call` method.
65
114
 
66
- #!usr/bin/env ruby
67
- require 'x'
68
- x = X.new
69
- x.execute_command(ARGV)
115
+ Basic help like this is fine for personal tools, but for public facing
116
+ production applications it is desirable to utilize manpages. To this end,
117
+ Executable provides Markdown formatted help as well. We can access this,
118
+ for example, via `HelloCommand.help.markdown`. The idea with this is that
119
+ we can save the output to `man/hello.ronn` or copy it the top of our `bin/`
120
+ file, edit it to perfection and then use tools such a {ronn}[https://github.com/rtomayko/ronn],
121
+ {binman}[https://github.com/sunaku/binman] or {md2man}[https://github.com/sunaku/md2man]
122
+ to generate the manpages. What's particularly cool about Executable,
123
+ is that once we have a manpage in the standard `man/` location in our project,
124
+ the `#show_help` method will use it instead of the plain text.
70
125
 
126
+ For a more detail example see {QED}[demo.html]
127
+ and {API}[http://rubydoc.info/gems/executable/frames] documentation.
71
128
 
72
- == INSTALL
73
129
 
74
- $ gem install executable
130
+ == INSTALLATION
75
131
 
132
+ Install with RubyGems in the usual fashion.
76
133
 
77
- == LEGAL
134
+ $ gem install executable
78
135
 
79
- (Apache 2.0)
80
136
 
81
- Copyright (c) 2009 Thomas Sawyer
137
+ == LEGAL
82
138
 
83
- Licensed under the Apache License, Version 2.0 (the "License");
84
- you may not use this program except in compliance with the License.
85
- You may obtain a copy of the License at
139
+ Copyright (c) 2008 Rubyworks
86
140
 
87
- http://www.apache.org/licenses/LICENSE-2.0
141
+ Distributable in accordance with the *BSD-2-Clause* license.
88
142
 
89
- Unless required by applicable law or agreed to in writing, software
90
- distributed under the License is distributed on an "AS IS" BASIS,
91
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
92
- See the License for the specific language governing permissions and
93
- limitations under the License.
143
+ See COPYING.rdoc for licensing details.
94
144
 
@@ -0,0 +1,17 @@
1
+ task :default => [:test]
2
+
3
+ desc "run unit tests (needs rubytest)"
4
+ task :test do
5
+ sh "rubytest -Ilib test/*.rb"
6
+ end
7
+
8
+ desc "render README.rdoc to web/readme.html (need malt)"
9
+ task :readme do
10
+ sh "malt README.rdoc > web/readme.html"
11
+ end
12
+
13
+ # if `README.rdoc` changes generate `web/readme.html`.
14
+ file 'README.rdoc' do
15
+ sh "malt README.rdoc > web/readme.html"
16
+ end
17
+
@@ -0,0 +1,6 @@
1
+ = Executable
2
+
3
+ Require Executable library.
4
+
5
+ require 'executable'
6
+
@@ -0,0 +1,44 @@
1
+ == No Subcommmands
2
+
3
+ This example demonstrates using Executable::Command to create a simple command line
4
+ interface without subcommands. (Note the Executable mixin could be used just
5
+ as well).
6
+
7
+ class NoSubCommandCLI < Executable::Command
8
+
9
+ attr :result
10
+
11
+ def o?
12
+ @o
13
+ end
14
+
15
+ def o=(flag)
16
+ @o = flag
17
+ end
18
+
19
+ def call
20
+ if o?
21
+ @result = "with"
22
+ else
23
+ @result = "without"
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ Execute the CLI on an example command line.
30
+
31
+ cli = NoSubCommandCLI.run('')
32
+ cli.result.assert == 'without'
33
+
34
+ Execute the CLI on an example command line.
35
+
36
+ cli = NoSubCommandCLI.run('-o')
37
+ cli.result.assert == 'with'
38
+
39
+ There are two important things to notices heres. Frist, that #main is being
40
+ called in each case. It is the method called with no other subcommands are
41
+ defined. And second, the fact the a `o?` method is defined to compliment the
42
+ `o=` writer, informs Executable that `-o` is an option _flag_, not taking
43
+ any parameters.
44
+
@@ -0,0 +1,125 @@
1
+ == Multiple Subcommmands
2
+
3
+ Setup an example CLI subclass.
4
+
5
+ class MyCLI < Executable::Command
6
+ attr :result
7
+
8
+ def initialize
9
+ @result = []
10
+ end
11
+
12
+ def g=(value)
13
+ @result << "g" if value
14
+ end
15
+
16
+ def g?
17
+ @result.include?("g")
18
+ end
19
+
20
+ #
21
+ class C1 < self
22
+ def call
23
+ @result << "c1"
24
+ end
25
+
26
+ def o1=(value)
27
+ @result << "c1_o1 #{value}"
28
+ end
29
+
30
+ def o2=(value)
31
+ @result << "c1_o2 #{value}"
32
+ end
33
+ end
34
+
35
+ #
36
+ class C2 < Executable::Command
37
+ attr :result
38
+
39
+ def initialize
40
+ @result = []
41
+ end
42
+
43
+ def call
44
+ @result << "c2"
45
+ end
46
+
47
+ def o1=(value)
48
+ @result << "c2_o1 #{value}"
49
+ end
50
+
51
+ def o2=(value)
52
+ @result << "c2_o2" if value
53
+ end
54
+
55
+ def o2?
56
+ @result.include?("c2_o2")
57
+ end
58
+ end
59
+
60
+ end
61
+
62
+ Instantiate and run the class on an example command line.
63
+
64
+ Just a command.
65
+
66
+ cli = MyCLI.run('c1')
67
+ cli.result.assert == ['c1']
68
+
69
+ Command with global option.
70
+
71
+ cli = MyCLI.run('c1 -g')
72
+ cli.result.assert == ['g', 'c1']
73
+
74
+ Command with an option.
75
+
76
+ cli = MyCLI.run('c1 --o1 A')
77
+ cli.result.assert == ['c1_o1 A', 'c1']
78
+
79
+ Command with two options.
80
+
81
+ cli = MyCLI.run('c1 --o1 A --o2 B')
82
+ cli.result.assert == ['c1_o1 A', 'c1_o2 B', 'c1']
83
+
84
+ Try out the second command.
85
+
86
+ cli = MyCLI.run('c2')
87
+ cli.result.assert == ['c2']
88
+
89
+ Seoncd command with an option.
90
+
91
+ cli = MyCLI.run('c2 --o1 A')
92
+ cli.result.assert == ['c2_o1 A', 'c2']
93
+
94
+ Second command with two options.
95
+
96
+ cli = MyCLI.run('c2 --o1 A --o2')
97
+ cli.result.assert == ['c2_o1 A', 'c2_o2', 'c2']
98
+
99
+ Since C1#main takes not arguments, if we try to issue a command
100
+ that will have left over arguments, then an ArgumentError will be raised.
101
+
102
+ expect ArgumentError do
103
+ cli = MyCLI.run('c1 a')
104
+ end
105
+
106
+ How about a non-existenct subcommand.
107
+
108
+ expect NotImplementedError do
109
+ cli = MyCLI.run('q')
110
+ cli.result.assert == ['q']
111
+ end
112
+
113
+ How about an option only.
114
+
115
+ expect NotImplementedError do
116
+ cli = MyCLI.run('-g')
117
+ cli.result.assert == ['-g']
118
+ end
119
+
120
+ How about a non-existant options.
121
+
122
+ expect Executable::NoOptionError do
123
+ MyCLI.run('c1 --foo')
124
+ end
125
+