executable 1.1.0 → 1.2.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.
- data/.ruby +61 -0
- data/.yardopts +7 -0
- data/COPYING.rdoc +35 -0
- data/DEMO.rdoc +568 -0
- data/HISTORY.rdoc +55 -0
- data/README.rdoc +101 -51
- data/Schedule.reap +17 -0
- data/demo/00_introduction.rdoc +6 -0
- data/demo/01_single_command.rdoc +44 -0
- data/demo/02_multiple_commands.rdoc +125 -0
- data/demo/03_help_text.rdoc +109 -0
- data/demo/04_manpage.rdoc +14 -0
- data/demo/05_optparse_example.rdoc +152 -0
- data/demo/06_delegate_example.rdoc +40 -0
- data/demo/07_command_methods.rdoc +36 -0
- data/demo/08_dispatach.rdoc +29 -0
- data/demo/applique/ae.rb +1 -0
- data/demo/applique/compare.rb +4 -0
- data/demo/applique/exec.rb +1 -0
- data/demo/samples/bin/hello +31 -0
- data/demo/samples/man/hello.1 +22 -0
- data/demo/samples/man/hello.1.html +102 -0
- data/demo/samples/man/hello.1.ronn +19 -0
- data/lib/executable.rb +67 -128
- data/lib/executable/core_ext.rb +102 -0
- data/lib/executable/dispatch.rb +30 -0
- data/lib/executable/domain.rb +106 -0
- data/lib/executable/errors.rb +22 -0
- data/lib/executable/help.rb +430 -0
- data/lib/executable/parser.rb +208 -0
- data/lib/executable/utils.rb +41 -0
- data/lib/executable/version.rb +23 -0
- data/meta/authors +2 -0
- data/meta/copyrights +3 -0
- data/meta/created +1 -0
- data/meta/description +6 -0
- data/meta/name +1 -0
- data/meta/organization +1 -0
- data/meta/repositories +2 -0
- data/meta/requirements +6 -0
- data/meta/resources +7 -0
- data/meta/summary +1 -0
- data/meta/version +1 -0
- data/test/test_executable.rb +40 -19
- metadata +124 -68
- data/History.rdoc +0 -35
- data/NOTICE.rdoc +0 -23
- data/Profile +0 -30
- data/Version +0 -1
- 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
|
data/lib/executable.rb
CHANGED
@@ -1,143 +1,82 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
#
|
11
|
-
#
|
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
|
-
#
|
40
|
-
|
41
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
#
|
49
|
-
|
50
|
-
|
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
|
-
|
66
|
-
alias_method :run, :execute
|
34
|
+
public
|
67
35
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
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
|
-
|
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
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
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
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
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
|
-
|
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
|