rubycom 0.3.2 → 0.4.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.
- 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
|