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,102 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv='content-type' value='text/html;charset=utf8'>
5
+ <meta name='generator' value='Ronn/v0.7.3 (http://github.com/rtomayko/ronn/tree/0.7.3)'>
6
+ <title>hellocmd(1) - Say hello.</title>
7
+ <style type='text/css' media='all'>
8
+ /* style: man */
9
+ body#manpage {margin:0}
10
+ .mp {max-width:100ex;padding:0 9ex 1ex 4ex}
11
+ .mp p,.mp pre,.mp ul,.mp ol,.mp dl {margin:0 0 20px 0}
12
+ .mp h2 {margin:10px 0 0 0}
13
+ .mp > p,.mp > pre,.mp > ul,.mp > ol,.mp > dl {margin-left:8ex}
14
+ .mp h3 {margin:0 0 0 4ex}
15
+ .mp dt {margin:0;clear:left}
16
+ .mp dt.flush {float:left;width:8ex}
17
+ .mp dd {margin:0 0 0 9ex}
18
+ .mp h1,.mp h2,.mp h3,.mp h4 {clear:left}
19
+ .mp pre {margin-bottom:20px}
20
+ .mp pre+h2,.mp pre+h3 {margin-top:22px}
21
+ .mp h2+pre,.mp h3+pre {margin-top:5px}
22
+ .mp img {display:block;margin:auto}
23
+ .mp h1.man-title {display:none}
24
+ .mp,.mp code,.mp pre,.mp tt,.mp kbd,.mp samp,.mp h3,.mp h4 {font-family:monospace;font-size:14px;line-height:1.42857142857143}
25
+ .mp h2 {font-size:16px;line-height:1.25}
26
+ .mp h1 {font-size:20px;line-height:2}
27
+ .mp {text-align:justify;background:#fff}
28
+ .mp,.mp code,.mp pre,.mp pre code,.mp tt,.mp kbd,.mp samp {color:#131211}
29
+ .mp h1,.mp h2,.mp h3,.mp h4 {color:#030201}
30
+ .mp u {text-decoration:underline}
31
+ .mp code,.mp strong,.mp b {font-weight:bold;color:#131211}
32
+ .mp em,.mp var {font-style:italic;color:#232221;text-decoration:none}
33
+ .mp a,.mp a:link,.mp a:hover,.mp a code,.mp a pre,.mp a tt,.mp a kbd,.mp a samp {color:#0000ff}
34
+ .mp b.man-ref {font-weight:normal;color:#434241}
35
+ .mp pre {padding:0 4ex}
36
+ .mp pre code {font-weight:normal;color:#434241}
37
+ .mp h2+pre,h3+pre {padding-left:0}
38
+ ol.man-decor,ol.man-decor li {margin:3px 0 10px 0;padding:0;float:left;width:33%;list-style-type:none;text-transform:uppercase;color:#999;letter-spacing:1px}
39
+ ol.man-decor {width:100%}
40
+ ol.man-decor li.tl {text-align:left}
41
+ ol.man-decor li.tc {text-align:center;letter-spacing:4px}
42
+ ol.man-decor li.tr {text-align:right;float:right}
43
+ </style>
44
+ </head>
45
+ <!--
46
+ The following styles are deprecated and will be removed at some point:
47
+ div#man, div#man ol.man, div#man ol.head, div#man ol.man.
48
+
49
+ The .man-page, .man-decor, .man-head, .man-foot, .man-title, and
50
+ .man-navigation should be used instead.
51
+ -->
52
+ <body id='manpage'>
53
+ <div class='mp' id='man'>
54
+
55
+ <div class='man-navigation' style='display:none'>
56
+ <a href="#NAME">NAME</a>
57
+ <a href="#SYNOPSIS">SYNOPSIS</a>
58
+ <a href="#DESCRIPTION">DESCRIPTION</a>
59
+ <a href="#OPTIONS">OPTIONS</a>
60
+ <a href="#COPYRIGHT">COPYRIGHT</a>
61
+ </div>
62
+
63
+ <ol class='man-decor man-head man head'>
64
+ <li class='tl'>hellocmd(1)</li>
65
+ <li class='tc'></li>
66
+ <li class='tr'>hellocmd(1)</li>
67
+ </ol>
68
+
69
+ <h2 id="NAME">NAME</h2>
70
+ <p class="man-name">
71
+ <code>hellocmd</code> - <span class="man-whatis">Say hello.</span>
72
+ </p>
73
+
74
+ <h2 id="SYNOPSIS">SYNOPSIS</h2>
75
+
76
+ <p><code>hellocmd</code> [options...] [subcommand]</p>
77
+
78
+ <h2 id="DESCRIPTION">DESCRIPTION</h2>
79
+
80
+ <p>Say hello.</p>
81
+
82
+ <h2 id="OPTIONS">OPTIONS</h2>
83
+
84
+ <dl>
85
+ <dt><code>--load=BOOL</code></dt><dd>Say it in uppercase?</dd>
86
+ </dl>
87
+
88
+
89
+ <h2 id="COPYRIGHT">COPYRIGHT</h2>
90
+
91
+ <p>Copyright (c) 2012</p>
92
+
93
+
94
+ <ol class='man-decor man-foot man foot'>
95
+ <li class='tl'></li>
96
+ <li class='tc'>January 2012</li>
97
+ <li class='tr'>hellocmd(1)</li>
98
+ </ol>
99
+
100
+ </div>
101
+ </body>
102
+ </html>
@@ -0,0 +1,19 @@
1
+ hellocmd(1) - Say hello.
2
+ ========================
3
+
4
+ ## SYNOPSIS
5
+
6
+ `hellocmd` [options...] [subcommand]
7
+
8
+ ## DESCRIPTION
9
+
10
+ Say hello.
11
+
12
+ ## OPTIONS
13
+
14
+ * `--load=BOOL`:
15
+ Say it in uppercase?
16
+
17
+ ## COPYRIGHT
18
+
19
+ Copyright (c) 2012
@@ -1,143 +1,82 @@
1
- # = Executable Mixin
2
- #
3
- # The Executable mixin is a very quick and and easy way to make almost
4
- # any class usable via a command line interface. It simply uses writer
5
- # methods as option setters, and the first command line argument as a
6
- # method to call with the subsequent arguments passed to the method.
7
- #
8
- # The only limitation of this approach is that non-boolean options must
9
- # be specified with `key=value` notation.
10
- #
11
- # class Example
12
- # include Executable
13
- #
14
- # attr_accessor :quiet
15
- #
16
- # def bread(*args)
17
- # ["bread", quiet, *args]
18
- # end
19
- #
20
- # def butter(*args)
21
- # ["butter", quiet, *args]
22
- # end
23
- # end
24
- #
25
- # ex = Example.new
26
- #
27
- # ex.execute!("butter yum")
28
- # => ["butter", nil, "yum"]
29
- #
30
- # ex.execute!("bread --quiet")
31
- # => ["butter", true]
32
- #
33
- # Executable also provides #option_missing, which you can overriden to provide
34
- # suitable results when a given command line option has no corresponding
35
- # writer method.
1
+ # encoding: utf-8
2
+
3
+ require 'executable/errors'
4
+ require 'executable/parser'
5
+ require 'executable/help'
6
+ require 'executable/utils'
7
+ require 'executable/domain'
8
+ require 'executable/dispatch'
9
+
10
+ # Executable is a mixin for creating robust, inheritable and
11
+ # reusable command line interfaces.
36
12
  #
37
13
  module Executable
38
14
 
39
- # Used the #excute! method to invoke the command.
40
- def execute!(argv=ARGV)
41
- Executable.execute(self, argv)
15
+ #
16
+ # When Exectuable is included into a class, the class is
17
+ # also extended by `Executable::Doamin`.
18
+ #
19
+ def self.included(base)
20
+ base.extend Domain
42
21
  end
43
22
 
44
- ## When no attribute write exists for an option that has been given on
45
- ## the command line #option_missing is called. Override #option_missing
46
- ## to handle these cases, if needed. Otherwise a NoMethodArgument will be
47
- ## raised. This callback method receives the name and value of the option.
48
- #def option_missing(opt, arg)
49
- # raise NoMethodError, "undefined option `#{opt}=' for #{self}"
50
- #end
51
-
52
- class << self
53
-
54
- # Process the arguments as an exectuable against the given object.
55
- def execute(obj, argv=ARGV)
56
- args = parse(obj, argv)
57
- subcmd = args.first
58
- if subcmd && !obj.respond_to?("#{subcmd}=")
59
- obj.send(*args)
60
- else
61
- obj.method_missing(*args)
62
- end
23
+ #
24
+ # Default initializer, simply takes a hash of settings
25
+ # to set attributes via writer methods. Not existnt
26
+ # attributes are simply ignored.
27
+ #
28
+ def initialize(settings={})
29
+ settings.each do |k,v|
30
+ __send__("#{k}=", v) if respond_to?("#{k}=")
63
31
  end
32
+ end
64
33
 
65
- # The original name for #execute.
66
- alias_method :run, :execute
34
+ public
67
35
 
68
- # Parse command line with respect to +obj+.
69
- def parse(obj, argv)
70
- case argv
71
- when String
72
- require 'shellwords'
73
- argv = Shellwords.shellwords(argv)
74
- else
75
- argv = argv.dup
76
- end
36
+ #
37
+ # Command invocation abstract method.
38
+ #
39
+ def call(*args)
40
+ #puts cli.show_help # TODO: show help instead of error ?
41
+ raise NotImplementedError
42
+ end
77
43
 
78
- argv = argv.dup
79
- args, opts, i = [], {}, 0
80
- while argv.size > 0
81
- case opt = argv.shift
82
- when /=/
83
- parse_equal(obj, opt, argv)
84
- when /^--/
85
- parse_option(obj, opt, argv)
86
- when /^-/
87
- parse_flags(obj, opt, argv)
88
- else
89
- args << opt
90
- end
91
- end
92
- return args
93
- end
44
+ #
45
+ # Convert Executable to Proc object.
46
+ #
47
+ def to_proc
48
+ lambda { |*args| call(*args) }
49
+ end
94
50
 
95
- # Parse a setting option.
96
- def parse_equal(obj, opt, argv)
97
- if md = /^[-]*(.*?)=(.*?)$/.match(opt)
98
- x, v = md[1], md[2]
99
- else
100
- raise ArgumentError, "#{x}"
101
- end
102
- # TODO: to_b if 'true' or 'false' ?
103
- #if obj.respond_to?("#{x}=")
104
- obj.send("#{x}=", v)
105
- #else
106
- # obj.option_missing(x, v)
107
- #end
108
- end
51
+ alias_method :inspect, :to_s
109
52
 
110
- # Parse a named boolean option.
111
- def parse_option(obj, opt, argv)
112
- x = opt.sub(/^--/, '')
113
- #if obj.respond_to?("#{x}=")
114
- obj.send("#{x}=", true)
115
- #else
116
- # obj.option_missing(x, true)
117
- #end
118
- end
53
+ #
54
+ # Output command line help.
55
+ #
56
+ def to_s
57
+ self.class.help.to_s # usage ?
58
+ end
119
59
 
120
- # Parse flags. Each character of a flag set is treated as a separate option.
121
- # For example:
122
- #
123
- # $ foo -abc
124
- #
125
- # Would be parsed the same as:
126
- #
127
- # $ foo -a -b -c
128
- #
129
- def parse_flags(obj, opt, args)
130
- x = opt.sub(/^-/, '')
131
- #c = 0
132
- x.split(//).each do |k|
133
- #if obj.respond_to?("#{k}=")
134
- obj.send("#{k}=", true)
135
- #else
136
- # obj.option_missing(x, true)
137
- #end
138
- end
139
- end
60
+ #
61
+ # Access to underlying Help instance.
62
+ #
63
+ def cli
64
+ self.class.cli
65
+ end
140
66
 
141
- end #class << self
67
+ #
68
+ # Override option_missing if needed. This receives the name of the option
69
+ # and the remaining arguments list. It must consume any arguments it uses
70
+ # from the begining of the list (i.e. in-place manipulation).
71
+ #
72
+ def option_missing(opt, argv)
73
+ raise NoOptionError, opt
74
+ end
75
+
76
+ # Base class alteranative ot using the mixin.
77
+ #
78
+ class Command
79
+ include Executable
80
+ end
142
81
 
143
82
  end
@@ -0,0 +1,102 @@
1
+ class UnboundMethod
2
+ if !method_defined?(:source_location)
3
+ if Proc.method_defined? :__file__ # /ree/
4
+ def source_location
5
+ [__file__, __line__] rescue nil
6
+ end
7
+ elsif defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /jruby/
8
+ require 'java'
9
+ def source_location
10
+ to_java.source_location(Thread.current.to_java.getContext())
11
+ end
12
+ end
13
+ end
14
+
15
+ #
16
+ def comment
17
+ Source.get_above_comment(*source_location)
18
+ end
19
+
20
+ # Source lookup.
21
+ #
22
+ module Source
23
+ extend self
24
+
25
+ # Read and cache file.
26
+ #
27
+ # @param file [String] filename, should be full path
28
+ #
29
+ # @return [Array] file content in array of lines
30
+ def read(file)
31
+ @read ||= {}
32
+ @read[file] ||= File.readlines(file)
33
+ end
34
+
35
+ # Get comment from file searching up from given line number.
36
+ #
37
+ # @param file [String] filename, should be full path
38
+ # @param line [Integer] line number in file
39
+ #
40
+ def get_above_comment(file, line)
41
+ get_above_comment_lines(file, line).join("\n").strip
42
+ end
43
+
44
+ # Get comment from file searching up from given line number.
45
+ #
46
+ # @param file [String] filename, should be full path
47
+ # @param line [Integer] line number in file
48
+ #
49
+ def get_above_comment_lines(file, line)
50
+ text = read(file)
51
+ index = line - 1
52
+ while index >= 0 && text[index] !~ /^\s*\#/
53
+ return [] if text[index] =~ /^\s*end/
54
+ index -= 1
55
+ end
56
+ rindex = index
57
+ while text[index] =~ /^\s*\#/
58
+ index -= 1
59
+ end
60
+ result = text[index..rindex]
61
+ result = result.map{ |s| s.strip }
62
+ result = result.reject{ |s| s[0,1] != '#' }
63
+ result = result.map{ |s| s.sub(/^#/,'').strip }
64
+ #result = result.reject{ |s| s == "" }
65
+ result
66
+ end
67
+
68
+ # Get comment from file searching down from given line number.
69
+ #
70
+ # @param file [String] filename, should be full path
71
+ # @param line [Integer] line number in file
72
+ #
73
+ def get_following_comment(file, line)
74
+ get_following_comment_lines(file, line).join("\n").strip
75
+ end
76
+
77
+ # Get comment from file searching down from given line number.
78
+ #
79
+ # @param file [String] filename, should be full path
80
+ # @param line [Integer] line number in file
81
+ #
82
+ def get_following_comment_lines(file, line)
83
+ text = read(file)
84
+ index = line || 0
85
+ while text[index] !~ /^\s*\#/
86
+ return nil if text[index] =~ /^\s*(class|module)/
87
+ index += 1
88
+ end
89
+ rindex = index
90
+ while text[rindex] =~ /^\s*\#/
91
+ rindex += 1
92
+ end
93
+ result = text[index..(rindex-2)]
94
+ result = result.map{ |s| s.strip }
95
+ result = result.reject{ |s| s[0,1] != '#' }
96
+ result = result.map{ |s| s.sub(/^#/,'').strip }
97
+ result.join("\n").strip
98
+ end
99
+
100
+ end
101
+
102
+ end
@@ -0,0 +1,30 @@
1
+ module Executable
2
+
3
+ # Variation of Executable which provides basic compatibility with
4
+ # previous versions of Executable. It provides a call method that
5
+ # automatically dispatches to publich methods.
6
+ #
7
+ # Among other uses, Dispatch can be useful for dropping into any class
8
+ # as a quick and dirty way to work with it on the command line.
9
+ #
10
+ # @since 1.2.0
11
+ module Dispatch
12
+ Executable.__send__(:append_features, self)
13
+
14
+ #
15
+ # When Dispatchable is included into a class, the class is
16
+ # also extended by `Executable::Domain`.
17
+ #
18
+ def self.included(base)
19
+ base.extend Domain
20
+ end
21
+
22
+ def call(name, *args)
23
+ public_method(name).call(*args)
24
+ end
25
+ end
26
+
27
+ # Dispatch is Legacy.
28
+ Legacy = Dispatch
29
+
30
+ end