rubycom 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/README.md +162 -146
- data/Rakefile +12 -12
- data/lib/rubycom.rb +156 -226
- data/lib/rubycom/arg_parse.rb +252 -0
- data/lib/rubycom/command_interface.rb +97 -0
- data/lib/rubycom/completions.rb +62 -0
- data/lib/rubycom/error_handler.rb +15 -0
- data/lib/rubycom/executor.rb +23 -0
- data/lib/rubycom/helpers.rb +98 -0
- data/lib/rubycom/output_handler.rb +15 -0
- data/lib/rubycom/parameter_extract.rb +262 -0
- data/lib/rubycom/singleton_commands.rb +78 -0
- data/lib/rubycom/sources.rb +99 -0
- data/lib/rubycom/version.rb +1 -1
- data/lib/rubycom/yard_doc.rb +146 -0
- data/rubycom.gemspec +14 -16
- data/test/rubycom/arg_parse_test.rb +247 -0
- data/test/rubycom/command_interface_test.rb +293 -0
- data/test/rubycom/completions_test.rb +94 -0
- data/test/rubycom/error_handler_test.rb +72 -0
- data/test/rubycom/executor_test.rb +64 -0
- data/test/rubycom/helpers_test.rb +467 -0
- data/test/rubycom/output_handler_test.rb +76 -0
- data/test/rubycom/parameter_extract_test.rb +141 -0
- data/test/rubycom/rubycom_test.rb +290 -548
- data/test/rubycom/singleton_commands_test.rb +122 -0
- data/test/rubycom/sources_test.rb +59 -0
- data/test/rubycom/util_test_bin.rb +8 -0
- data/test/rubycom/util_test_composite.rb +23 -20
- data/test/rubycom/util_test_module.rb +142 -112
- data/test/rubycom/util_test_no_singleton.rb +2 -2
- data/test/rubycom/util_test_sub_module.rb +13 -0
- data/test/rubycom/yard_doc_test.rb +165 -0
- metadata +61 -24
- data/lib/rubycom/arguments.rb +0 -133
- data/lib/rubycom/commands.rb +0 -63
- data/lib/rubycom/documentation.rb +0 -212
- data/test/rubycom/arguments_test.rb +0 -289
- data/test/rubycom/commands_test.rb +0 -51
- data/test/rubycom/documentation_test.rb +0 -186
- data/test/rubycom/util_test_job.yaml +0 -21
- data/test/rubycom/utility_tester.rb +0 -17
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
ZThmMjdiYjc1YjlkMDUxODYxNGQyMmI2YzYzNGI0YTQwMDQ2MGI3Mg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
OTY2ZWQzMzI1NGNhOTEyYzM5YTVhZGUwOTgyMTcxMjAyM2Y3MzliYw==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ZDhlMWRhMDNiODI2Y2RmMjMxNGVhZDU4NDk4YTc3MjljMmE2OWEyNWQ5YTBj
|
10
|
+
OWZkM2JiZjE2MThjMTRjMzE0NjM2YzI5YzliMmU3NzZjZmViZWE1ODgyODMw
|
11
|
+
MTM5YTYyZjYyYjcxNzkyY2ZjYWUzZDA1NWNjNjlmYjk0MzlhYzk=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
ZWEyZThiOGJiMzU2NDYzYzlhMmFlZTM3Yjc2YjY3YTE3YzI5MTUzMGQyYmJh
|
14
|
+
Yjc3ZmE2MWE0MGJiNzY0Njg5Mzc5YzUyYmNlNDZhZDRlN2IxMmM4MTNhZGFm
|
15
|
+
ZDI0MWYzNDE1M2IzMjJhNjQzYmI4NjEwNjUzZjYxYWQ5NjhhZDc=
|
data/README.md
CHANGED
@@ -1,146 +1,162 @@
|
|
1
|
-
Rubycom
|
2
|
-
---------------
|
3
|
-
|
4
|
-
© Danny Purcell 2013 | MIT license
|
5
|
-
|
6
|
-
Makes creating command line tools as easy as
|
7
|
-
|
8
|
-
When a
|
9
|
-
match the command name to a
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
*
|
19
|
-
*
|
20
|
-
*
|
21
|
-
*
|
22
|
-
*
|
23
|
-
*
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
*
|
80
|
-
|
81
|
-
*
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
*
|
145
|
-
|
146
|
-
|
1
|
+
Rubycom
|
2
|
+
---------------
|
3
|
+
|
4
|
+
© Danny Purcell 2013 | MIT license
|
5
|
+
|
6
|
+
Makes creating command line tools as easy as including Rubycom.
|
7
|
+
|
8
|
+
When a Module which has included Rubycom is run from the terminal, Rubycom will parse ARGV for a command name,
|
9
|
+
match the command name to a method in the including module, and run the method with the given arguments.
|
10
|
+
|
11
|
+
Features
|
12
|
+
---------------
|
13
|
+
|
14
|
+
Allows the user to write a properly documented module/class and convert it to a command line tool
|
15
|
+
by simply including Rubycom at the bottom.
|
16
|
+
|
17
|
+
* Provides a Command Line Interface for any library simply by stating `include Rubycom` at the bottom.
|
18
|
+
* Public singleton methods are made accessible from the terminal. Usage documentation is pulled from method comments.
|
19
|
+
* Method parameters become required CLI arguments. Optional (defaulted) parameters become CLI options.
|
20
|
+
* Command consoles can be built up by including other modules before including Rubycom.
|
21
|
+
* Included modules become commands, their public singleton methods become sub-commands.
|
22
|
+
* Built in tab completion support for all commands.
|
23
|
+
* Users may call `./path/to/my_command.rb register_completions` then `source ~/.bash_profile` to register completions.
|
24
|
+
* Customize Rubycom's functionality by calling `Rubycom.run_command` with custom plugin modules.
|
25
|
+
* When calling run_command, functionality can be easily modified by providing a custom module for one of the following
|
26
|
+
keys `:arguments, :discover, :documentation, :source, :parameters, :executor, :output, :interface, :error` in plugins_options.
|
27
|
+
|
28
|
+
Installation
|
29
|
+
---------------
|
30
|
+
|
31
|
+
#### Install with Gem
|
32
|
+
* Available on [Rubygems](https://rubygems.org/gems/rubycom)
|
33
|
+
* Be sure one of your gem sources is `source 'https://rubygems.org'`
|
34
|
+
* Run `gem install rubycom`
|
35
|
+
|
36
|
+
#### Building locally
|
37
|
+
* Fork the repository if you wish
|
38
|
+
* Clone repository locally
|
39
|
+
* If using the main repo: `git clone https://github.com/dannypurcell/rubycom.git`
|
40
|
+
* Run `rake install` if installing for the first time
|
41
|
+
* If updating to the latest version run the following commands
|
42
|
+
* `git checkout master`
|
43
|
+
* `git pull origin master`
|
44
|
+
* If that causes any problems `git reset --hard origin/master`
|
45
|
+
* `rake upgrade`
|
46
|
+
|
47
|
+
Usage
|
48
|
+
---------------
|
49
|
+
|
50
|
+
Write your library, document them as you normally would. `include Rubycom` at the bottom.
|
51
|
+
Optionally `#!/usr/bin/env ruby` at the top.
|
52
|
+
|
53
|
+
Now any singleton methods `def self.method_name` will be available to call from the terminal.
|
54
|
+
|
55
|
+
Calling `ruby ./path/to/module.rb <command_name>` will automatically discover and run your `<command_name>` singleton method.
|
56
|
+
If no method is found by the given name, a usage print out will be given including a summary of each command available
|
57
|
+
and it's description from the corresponding method's comments.
|
58
|
+
|
59
|
+
Calling a valid command with incorrect arguments will produce a usage print out for the matched method.
|
60
|
+
Rubycom will include as much documentation on the command line as you provide in your method comments. Currently Rubycom
|
61
|
+
only handles YardDoc style comments for discovering the parameter and return documentation. All other commentary will be
|
62
|
+
included as part of the command description. In the absence of YardDoc annotations, Rubycom will generate a clean usage
|
63
|
+
text which may work for your method doc even though Rubycom is not specifically parsing it.
|
64
|
+
|
65
|
+
|
66
|
+
#####Special commands
|
67
|
+
|
68
|
+
| Command | Description | Options |
|
69
|
+
| ------- |:-----------:| -------:|
|
70
|
+
| `ruby ./path/to/module.rb help [command_name]` | Will print out usage for the module or optionally the specified command.||
|
71
|
+
| `ruby ./path/to/module.rb register_completions ` | Setup bash tab completion ||
|
72
|
+
| `ruby ./path/to/module.rb tab_complete [text]` | Print a list of possible matches for a given word ||
|
73
|
+
|
74
|
+
###Arguments
|
75
|
+
|
76
|
+
When using Rubycom's default modules:
|
77
|
+
* Arguments are automatically parsed from the command line using Rubycom's ArgParse module and converted to Ruby types
|
78
|
+
by Ruby's core Yaml module.
|
79
|
+
* Arguments will be passed to your method in order of their appearance on the command line. With smart parsing for
|
80
|
+
option arguments and flags.
|
81
|
+
* If you specify a default value for a parameter in your method, then Rubycom will look for a named option argument in
|
82
|
+
the command line which matches the parameter's name or the first letter in the parameter name if it is unique among the
|
83
|
+
other method parameters.
|
84
|
+
* Users may call out option parameters in any order using `--<param_name>=<value>`, `--<param_name> = <value>`, or
|
85
|
+
`--<param_name> <value>`
|
86
|
+
* Rubycom attempts to handle short names for optional parameters so specifying `-<p> <value>` or `-<param> <value>`
|
87
|
+
is equivalent to `--<parameter> <value>` if the characters uniquely match a parameter name in the called method.
|
88
|
+
* Any parameter which is not mentioned in the command line will receive one of the remaining, unnamed arguments in order
|
89
|
+
of appearance.
|
90
|
+
* Optional parameters which do not get overridden either by a named optional argument or an available unnamed command line
|
91
|
+
argument will be filled by their default as usual.
|
92
|
+
* If a rest parameter `*param_name` is defined in the method being called, any remaining arguments will be passed to the
|
93
|
+
rest parameter after the required and optional parameters are filled.
|
94
|
+
|
95
|
+
Raison d'etre
|
96
|
+
---------------
|
97
|
+
|
98
|
+
* Command line scripts written from scratch often include redundant ARGV parsing code, little or no testing, and slim documentation.
|
99
|
+
Development speed is important and setting up a properly documented and tested terminal interface takes a while.
|
100
|
+
* OptionParser and the like help script authors define options for a script.
|
101
|
+
They provide structure to the redundant code and slightly easier argument specification.
|
102
|
+
* Thor and the like provide a framework the script author will extend to create command line tools.
|
103
|
+
The Prescriptive approach creates consistency but requires the script author to learn the framework and conform.
|
104
|
+
|
105
|
+
While these are things do help, we are still writing redundant code and tightly coupling the functional code to the
|
106
|
+
interface which presents it. We also lack a generic command line parser which, if available, could help encourage
|
107
|
+
Rubyists to standardize command line inputs.
|
108
|
+
|
109
|
+
So, what to do?
|
110
|
+
|
111
|
+
...Ruby is interpreted...use the source.
|
112
|
+
|
113
|
+
Rather than making concessions for the presentation and tightly coupling the functional code
|
114
|
+
to the interface, it would be nice if a script author could simply write their code and attach the interface to it.
|
115
|
+
|
116
|
+
|
117
|
+
How it works
|
118
|
+
---------------
|
119
|
+
Rubycom attaches the CLI to the functional code. The author is free to write the functional code as any other.
|
120
|
+
If a library needs to be accessible from the terminal, just `include Rubycom` at the bottom of the main Module and run
|
121
|
+
the ruby file.
|
122
|
+
|
123
|
+
* Methods are made accessible from the terminal.
|
124
|
+
* ARGV is parsed for a method to run and arguments.
|
125
|
+
* Usage documentation is pulled from method comments.
|
126
|
+
* Method parameters become required CLI arguments.
|
127
|
+
* Optional (defaulted) parameters become CLI options.
|
128
|
+
* Tab completion support if the user has registered it for the file.
|
129
|
+
|
130
|
+
The result is a library which can be consumed easily from other classes/modules and which is accessible from the command line.
|
131
|
+
|
132
|
+
Customizing Rubycom
|
133
|
+
---------------
|
134
|
+
|
135
|
+
Note: The plugin_options hash is currently taking Modules and calling specific methods on them. This will change to a
|
136
|
+
Symbol => Proc mapping soon. Please log an issue on [GitHub](https://github.com/dannypurcell/rubycom/issues) if you
|
137
|
+
want this right away.
|
138
|
+
|
139
|
+
Rubycom is designed to fit several different ways of calling command line utilities and to respect many of the
|
140
|
+
strong conventions regarding command line semantics. While Rubycom's default functionality should fit many common use
|
141
|
+
cases it is also built in a modular fashion such that the core functionality can be easily adapted to fit specific
|
142
|
+
requirements or user preferences.
|
143
|
+
|
144
|
+
* Calling Rubycom via `include Rubycom` will attempt to execute the default functionality.
|
145
|
+
* Alternately, calling `Rubycom.run_command(base, args=[], plugins_options={})` directly enables the user to inject
|
146
|
+
custom modules for specific portions of the execution via the plugin_options parameter.
|
147
|
+
|
148
|
+
#####Plugin Module Contracts
|
149
|
+
|
150
|
+
| Key | Expected Inputs | Expected Outputs |
|
151
|
+
| -------------- |:---------------:|:----------------:|
|
152
|
+
| :arguments | ARGV | A data structure representing the arguments, options, and flags |
|
153
|
+
| :discover | The Module which included Rubycom and a parsed command line | A Method or Module representing the command which should be run |
|
154
|
+
| :documentation | The command to run and the :source plugin | The command matched to it's documentation |
|
155
|
+
| :source | A Module or Method object | The source code for that reference |
|
156
|
+
| :parameters | A command, a parsed command line, and the command documentation | The command parameters matched to their values for this run |
|
157
|
+
| :executor | A command to execute and the command parameters matched to their values for this run | The result of a call to the given method with the given parameters |
|
158
|
+
| :output | The command result | Some output handling action |
|
159
|
+
| :interface | A command and it's documentation | A string representing the usage text to present in a terminal |
|
160
|
+
| :error | An Error and a String representing usage text | Some error handling action |
|
161
|
+
|
162
|
+
|
data/Rakefile
CHANGED
@@ -11,11 +11,11 @@ task :clean do
|
|
11
11
|
end
|
12
12
|
|
13
13
|
task :bundle do
|
14
|
-
system(
|
14
|
+
system('bundle install')
|
15
15
|
end
|
16
16
|
|
17
17
|
Rake::TestTask.new do |t|
|
18
|
-
t.libs <<
|
18
|
+
t.libs << 'test'
|
19
19
|
t.test_files = FileList['test/*/*_test.rb']
|
20
20
|
t.verbose = true
|
21
21
|
end
|
@@ -23,10 +23,10 @@ end
|
|
23
23
|
YARD::Rake::YardocTask.new
|
24
24
|
|
25
25
|
task :package => [:clean, :bundle, :test, :yard] do
|
26
|
-
gem_specs = Dir.glob(
|
26
|
+
gem_specs = Dir.glob('**/*.gemspec')
|
27
27
|
gem_specs.each { |gem_spec|
|
28
28
|
system("gem build #{gem_spec}")
|
29
|
-
raise
|
29
|
+
raise 'Error during build phase' if $?.exitstatus != 0
|
30
30
|
}
|
31
31
|
end
|
32
32
|
|
@@ -36,12 +36,12 @@ task :install => :package do
|
|
36
36
|
end
|
37
37
|
|
38
38
|
task :upgrade => :package do
|
39
|
-
system(
|
39
|
+
system('gem uninstall rubycom -a')
|
40
40
|
load "#{File.expand_path(File.dirname(__FILE__))}/lib/rubycom/version.rb"
|
41
41
|
system("gem install #{File.expand_path(File.dirname(__FILE__))}/rubycom-#{Rubycom::VERSION}.gem")
|
42
42
|
end
|
43
43
|
|
44
|
-
task :version_set, [:version] do |
|
44
|
+
task :version_set, [:version] do |_, args|
|
45
45
|
raise "Must provide a version.\n If you called 'rake version_set 1.2.3', try 'rake version_set[1.2.3]'" if args[:version].nil? || args[:version].empty?
|
46
46
|
|
47
47
|
version_file = <<-END.gsub(/^ {4}/, '')
|
@@ -54,21 +54,21 @@ task :version_set, [:version] do |t, args|
|
|
54
54
|
file.write(version_file)
|
55
55
|
}
|
56
56
|
file_text = File.read("#{File.expand_path(File.dirname(__FILE__))}/lib/rubycom/version.rb")
|
57
|
-
raise
|
57
|
+
raise 'Could not update version file' if file_text != version_file
|
58
58
|
end
|
59
59
|
|
60
|
-
task :release, [:version] => [:version_set, :package] do |
|
61
|
-
system(
|
62
|
-
system(
|
60
|
+
task :release, [:version] => [:version_set, :package] do |_, args|
|
61
|
+
system('git clean -f')
|
62
|
+
system('git add .')
|
63
63
|
system("git commit -m\"Version to #{args[:version]}\"")
|
64
64
|
if $?.exitstatus == 0
|
65
65
|
system("git tag -a v#{args[:version]} -m\"Version #{args[:version]} Release\"")
|
66
66
|
if $?.exitstatus == 0
|
67
|
-
system(
|
67
|
+
system('git push origin master --tags')
|
68
68
|
if $?.exitstatus == 0
|
69
69
|
load "#{File.expand_path(File.dirname(__FILE__))}/lib/rubycom/version.rb"
|
70
70
|
system("gem push #{File.expand_path(File.dirname(__FILE__))}/rubycom-#{Rubycom::VERSION}.gem")
|
71
71
|
end
|
72
72
|
end
|
73
73
|
end
|
74
|
-
end
|
74
|
+
end
|
data/lib/rubycom.rb
CHANGED
@@ -1,226 +1,156 @@
|
|
1
|
-
require "#{File.dirname(__FILE__)}/rubycom/
|
2
|
-
require "#{File.dirname(__FILE__)}/rubycom/
|
3
|
-
require "#{File.dirname(__FILE__)}/rubycom/
|
4
|
-
require "#{File.dirname(__FILE__)}/rubycom/
|
5
|
-
|
6
|
-
require
|
7
|
-
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
#
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
args = Arguments.resolve(param_defs, arguments)
|
158
|
-
flatten = false
|
159
|
-
params = method.parameters.map { |arr| flatten = true if arr[0]==:rest; args[arr[1]] }
|
160
|
-
if flatten
|
161
|
-
rest_arr = params.delete_at(-1)
|
162
|
-
if rest_arr.respond_to?(:each)
|
163
|
-
rest_arr.each { |arg| params << arg }
|
164
|
-
else
|
165
|
-
params << rest_arr
|
166
|
-
end
|
167
|
-
end
|
168
|
-
(arguments.nil? || arguments.empty?) ? method.call : method.call(*params)
|
169
|
-
end
|
170
|
-
|
171
|
-
# Inserts a tab completion into the current user's .bash_profile with a command entry to register the function for
|
172
|
-
# the current running ruby file
|
173
|
-
#
|
174
|
-
# @param [Module] base the module which invoked 'include Rubycom'
|
175
|
-
# @return [String] a message indicating the result of the command
|
176
|
-
def self.register_completions(base)
|
177
|
-
completion_function = <<-END.gsub(/^ {4}/, '')
|
178
|
-
|
179
|
-
_#{base}_complete() {
|
180
|
-
COMPREPLY=()
|
181
|
-
local completions="$(ruby #{File.absolute_path($0)} tab_complete ${COMP_WORDS[*]} 2>/dev/null)"
|
182
|
-
COMPREPLY=( $(compgen -W "$completions") )
|
183
|
-
}
|
184
|
-
complete -o bashdefault -o default -o nospace -F _#{base}_complete #{$0.split('/').last}
|
185
|
-
END
|
186
|
-
|
187
|
-
already_registered = File.readlines("#{Dir.home}/.bash_profile").map { |line| line.include?("_#{base}_complete()") }.reduce(:|) rescue false
|
188
|
-
if already_registered
|
189
|
-
"Completion function for #{base} already registered."
|
190
|
-
else
|
191
|
-
File.open("#{Dir.home}/.bash_profile", 'a+') { |file|
|
192
|
-
file.write(completion_function)
|
193
|
-
}
|
194
|
-
"Registration complete, run 'source #{Dir.home}/.bash_profile' to enable auto-completion."
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
# Discovers a list of possible matches to the given arguments
|
199
|
-
# Intended for use with bash tab completion
|
200
|
-
#
|
201
|
-
# @param [Module] base the module which invoked 'include Rubycom'
|
202
|
-
# @param [Array] arguments a String Array representing the arguments to be matched
|
203
|
-
# @return [Array] a String Array including the possible matches for the given arguments
|
204
|
-
def self.tab_complete(base, arguments)
|
205
|
-
arguments = [] if arguments.nil?
|
206
|
-
args = (arguments.include?("tab_complete")) ? arguments[2..-1] : arguments
|
207
|
-
matches = ['']
|
208
|
-
if args.nil? || args.empty?
|
209
|
-
matches = Rubycom::Commands.get_top_level_commands(base).map { |sym| sym.to_s }
|
210
|
-
elsif args.length == 1
|
211
|
-
matches = Rubycom::Commands.get_top_level_commands(base).map { |sym| sym.to_s }.select { |word| !word.match(/^#{args[0]}/).nil? }
|
212
|
-
if matches.size == 1 && matches[0] == args[0]
|
213
|
-
matches = self.tab_complete(Kernel.const_get(args[0].to_sym), args[1..-1])
|
214
|
-
end
|
215
|
-
elsif args.length > 1
|
216
|
-
begin
|
217
|
-
matches = self.tab_complete(Kernel.const_get(args[0].to_sym), args[1..-1])
|
218
|
-
rescue Exception
|
219
|
-
matches = ['']
|
220
|
-
end
|
221
|
-
end unless base.nil?
|
222
|
-
matches = [''] if matches.nil? || matches.include?(args[0])
|
223
|
-
matches
|
224
|
-
end
|
225
|
-
|
226
|
-
end
|
1
|
+
require "#{File.dirname(__FILE__)}/rubycom/completions.rb"
|
2
|
+
require "#{File.dirname(__FILE__)}/rubycom/arg_parse.rb"
|
3
|
+
require "#{File.dirname(__FILE__)}/rubycom/singleton_commands.rb"
|
4
|
+
require "#{File.dirname(__FILE__)}/rubycom/sources.rb"
|
5
|
+
require "#{File.dirname(__FILE__)}/rubycom/yard_doc.rb"
|
6
|
+
require "#{File.dirname(__FILE__)}/rubycom/parameter_extract.rb"
|
7
|
+
require "#{File.dirname(__FILE__)}/rubycom/executor.rb"
|
8
|
+
require "#{File.dirname(__FILE__)}/rubycom/output_handler.rb"
|
9
|
+
require "#{File.dirname(__FILE__)}/rubycom/command_interface.rb"
|
10
|
+
require "#{File.dirname(__FILE__)}/rubycom/error_handler.rb"
|
11
|
+
|
12
|
+
require 'yaml'
|
13
|
+
|
14
|
+
# Upon inclusion in another Module, Rubycom will attempt to call a method in the including module by parsing
|
15
|
+
# ARGV for a method name and a list of arguments.
|
16
|
+
# If found Rubycom will call the method specified in ARGV with the parameters parsed from the remaining arguments
|
17
|
+
# If a Method match can not be made, Rubycom will print help instead by parsing code comments from the including
|
18
|
+
# module.
|
19
|
+
module Rubycom
|
20
|
+
|
21
|
+
# Base class for all Rubycom errors
|
22
|
+
class RubycomError < StandardError
|
23
|
+
end
|
24
|
+
# To be thrown in case of an error while parsing arguments
|
25
|
+
class ArgParseError < RubycomError;
|
26
|
+
end
|
27
|
+
# To be thrown in case of an error while executing a method
|
28
|
+
class ExecutorError < RubycomError;
|
29
|
+
end
|
30
|
+
# To be thrown in case of an error while extracting parameters
|
31
|
+
class ParameterExtractError < RubycomError;
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
# Determines whether the including module was executed by a gem binary
|
36
|
+
#
|
37
|
+
# @param [String] base_file_path the path to the including module's source file
|
38
|
+
# @return [Boolean] true|false
|
39
|
+
def self.is_executed_by_gem?(base_file_path)
|
40
|
+
Gem.loaded_specs.map { |k, s|
|
41
|
+
{k => {name: "#{s.name}-#{s.version}", executables: s.executables}}
|
42
|
+
}.reduce({}, &:merge).map { |_, s|
|
43
|
+
base_file_path.include?(s[:name]) && s[:executables].include?(File.basename(base_file_path))
|
44
|
+
}.flatten.reduce(&:|)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Detects that Rubycom was included in another module and calls Rubycom#run
|
48
|
+
#
|
49
|
+
# @param [Module] base the module which invoked 'include Rubycom'
|
50
|
+
def self.included(base)
|
51
|
+
base_file_path = caller.first.gsub(/:\d+:.+/, '')
|
52
|
+
if base.class == Module && (base_file_path == $0 || self.is_executed_by_gem?(base_file_path))
|
53
|
+
base.module_eval {
|
54
|
+
Rubycom.run(base, ARGV)
|
55
|
+
}
|
56
|
+
end
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
|
60
|
+
# Main entry point for Rubycom. Uses #run_command to discover and run commands
|
61
|
+
#
|
62
|
+
# @param [Module] base this will be used to determine available commands
|
63
|
+
# @param [Array] args a String Array representing the command to run followed by arguments to be passed
|
64
|
+
# @return [Object] the result of calling #run_command! or a String representing a default help message
|
65
|
+
def self.run(base, args=[])
|
66
|
+
begin
|
67
|
+
raise RubycomError, "base should should not be nil" if base.nil?
|
68
|
+
case args[0]
|
69
|
+
when 'register_completions'
|
70
|
+
puts Rubycom::Completions.register_completions(base)
|
71
|
+
when 'tab_complete'
|
72
|
+
puts Rubycom::Completions.tab_complete(base, args, Rubycom::SingletonCommands)
|
73
|
+
when 'help'
|
74
|
+
help_topic = args[1]
|
75
|
+
if help_topic == 'register_completions'
|
76
|
+
puts "Usage: #{base} register_completions"
|
77
|
+
elsif help_topic == 'tab_complete'
|
78
|
+
usage = "Usage: #{base} tab_complete <word>\nParameters:\n [String] word the word or partial word to find matches for"
|
79
|
+
puts usage
|
80
|
+
return usage
|
81
|
+
else
|
82
|
+
self.run_command(base, (args[1..-1] << '-h'))
|
83
|
+
$stderr.puts <<-END.gsub(/^ {12}/, '')
|
84
|
+
Default Commands:
|
85
|
+
help - prints this help page
|
86
|
+
register_completions - setup bash tab completion
|
87
|
+
tab_complete - print a list of possible matches for a given word
|
88
|
+
END
|
89
|
+
end
|
90
|
+
else
|
91
|
+
self.run_command(base, args)
|
92
|
+
end
|
93
|
+
rescue RubycomError => e
|
94
|
+
$stderr.puts e
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Calls the given process method with the given base, args, and steps.
|
99
|
+
#
|
100
|
+
# @param [Module] base the Module containing the Method or sub Module to run
|
101
|
+
# @param [Array] args a String Array representing the command to run followed by arguments to be passed
|
102
|
+
# @param [Hash] steps should have the following keys mapped to Methods or Procs which will be called by the process method
|
103
|
+
# :arguments, :discover, :documentation, :source, :parameters, :executor, :output, :interface, :error
|
104
|
+
# @param [Method|Proc] process a Method or Proc which calls the step_methods in order to parse args and run a command on base
|
105
|
+
# @return [Object] the result of calling the method selected by the :discover method using the args from the :arguments method
|
106
|
+
# matched to parameters by the :parameters method
|
107
|
+
def self.run_command(base, args=[], steps={}, process=Rubycom.public_method(:process))
|
108
|
+
process.call(base, args, steps)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Calls the given steps with the required parameters and ordering to locate and call a method on base or one of it's
|
112
|
+
# included modules. This method expresses a procedure and calls the methods in steps to execute each step in the procedure.
|
113
|
+
# If not overridden in steps, then method called for each step will be determined by the return from #step_methods.
|
114
|
+
#
|
115
|
+
# @param [Module] base the Module containing the Method or sub Module to run
|
116
|
+
# @param [Array] args a String Array representing the command to run followed by arguments to be passed
|
117
|
+
# @param [Hash] steps should have the following keys mapped to Methods or Procs which will be called by the process method
|
118
|
+
# :arguments, :discover, :documentation, :source, :parameters, :executor, :output, :interface, :error
|
119
|
+
# @return [Object] the result of calling the method selected by the :discover method using the args from the :arguments method
|
120
|
+
# matched to parameters by the :parameters method
|
121
|
+
def self.process(base, args=[], steps={})
|
122
|
+
steps = self.step_methods.merge(steps)
|
123
|
+
|
124
|
+
parsed_command_line = steps[:arguments].call(args)
|
125
|
+
command = steps[:discover].call(base, parsed_command_line)
|
126
|
+
begin
|
127
|
+
command_doc = steps[:documentation].call(command, steps[:source])
|
128
|
+
parameters = steps[:parameters].call(command, parsed_command_line, command_doc)
|
129
|
+
command_result = steps[:executor].call(command, parameters)
|
130
|
+
steps[:output].call(command_result)
|
131
|
+
rescue RubycomError => e
|
132
|
+
cli_output = steps[:interface].call(command, command_doc)
|
133
|
+
steps[:error].call(e, cli_output)
|
134
|
+
end
|
135
|
+
command_result
|
136
|
+
end
|
137
|
+
|
138
|
+
# Convenience call for use with #process when the default Rubycom functionality is required.
|
139
|
+
#
|
140
|
+
# @return [Hash] mapping :arguments, :discover, :documentation, :source, :parameters, :executor, :output, :interface, :error
|
141
|
+
# to the default methods which carry out the step referred to by the key.
|
142
|
+
def self.step_methods()
|
143
|
+
{
|
144
|
+
arguments: Rubycom::ArgParse.public_method(:parse_command_line),
|
145
|
+
discover: Rubycom::SingletonCommands.public_method(:discover_command),
|
146
|
+
documentation: Rubycom::YardDoc.public_method(:document_command),
|
147
|
+
source: Rubycom::Sources.public_method(:source_command),
|
148
|
+
parameters: Rubycom::ParameterExtract.public_method(:extract_parameters),
|
149
|
+
executor: Rubycom::Executor.public_method(:execute_command),
|
150
|
+
output: Rubycom::OutputHandler.public_method(:process_output),
|
151
|
+
interface: Rubycom::CommandInterface.public_method(:build_interface),
|
152
|
+
error: Rubycom::ErrorHandler.public_method(:handle_error)
|
153
|
+
}
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|