executable 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|