ruby_fire_cli 1.0.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 +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +12 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +238 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/console_runner.gemspec +44 -0
- data/exe/rcli +7 -0
- data/lib/command_line_parser.rb +92 -0
- data/lib/file_parser.rb +114 -0
- data/lib/method_parser.rb +162 -0
- data/lib/ruby_fire_cli.rb +73 -0
- data/lib/ruby_fire_cli/version.rb +3 -0
- data/lib/ruby_fire_cli_error.rb +11 -0
- metadata +191 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 411d5d7472a24d6087de5f0df31c701a676814b6
|
4
|
+
data.tar.gz: 3fcd91eab2ef0d8069dbb7adf8f99149e93113ee
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a4eeaf81c60544e5d850c23670df17663ab052b361acfd7208cd355bce55b242edc3f02ebb6e18d6babf6abae8abe47760a6dee84ac0a6853cbaf7df95fd0362
|
7
|
+
data.tar.gz: ef41d662bf9b68498e688a62f8e4e3e40de25e504ff17eb4963e0fa841ab10d82fc9598e22cd99479f398a3b098497b2a01b668a5d9596597691a0c2734d9d44
|
data/.coveralls.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
repo_token: Zt5KZXhBj3awn9OS5Hy6pDuVEGSrWWSjT
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Yury Karpovich
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,238 @@
|
|
1
|
+
# RubyFIreCLI
|
2
|
+
[![Gem Version][GV img]][Gem Version]
|
3
|
+
[![Build Status][BS img]][Build Status]
|
4
|
+
[![Code Climate][CC img]][Code Climate]
|
5
|
+
[![Coverage Status][CS img]][Coverage Status]
|
6
|
+
|
7
|
+
This gem automatically generating command line interfaces (CLIs) from Ruby object. No special code modifications required!.
|
8
|
+
`ruby_fire_cli` is a smart mix of [YARD](http://yardoc.org/) and [Trollop](http://manageiq.github.io/trollop/) gems.
|
9
|
+
It's an analog of [Python Fire](https://github.com/google/python-fire) in Ruby language.
|
10
|
+
> 1. it parses [YARD](http://yardoc.org/) annotations of classes and methods to 'understand' your code
|
11
|
+
> 2. it generates friendly unix-like help menu for your tool (using [Trollop](http://manageiq.github.io/trollop/) gem)
|
12
|
+
> 3. it parses command-line input and run your Ruby code in a proper way
|
13
|
+
|
14
|
+
Just 4 simple steps to make your code runnable from terminal:
|
15
|
+
1. Just add `@runnable` tag in
|
16
|
+
|
17
|
+
One thing you need to do is to add an [YARD](http://yardoc.org/) tag annotation `@runnable`.
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
`ruby_fire_cli` extends [YARD](http://yardoc.org/) with a new tag: `@runnable`. You need to set this tag in a Class and Method annotation. After that it will be possible to call this method from command-line.
|
21
|
+
Usage instructions are as simple as one, two, three:
|
22
|
+
1. Add `@runnable` tag
|
23
|
+
2. Now you can run your tool from terminal by `rcli /path/to/class.rb_file` command
|
24
|
+
3. PROFIT!
|
25
|
+
|
26
|
+
### Example
|
27
|
+
1. Install `ruby_fire_cli` gem
|
28
|
+
2. Put some code to `/home/user/project/my_class.rb`
|
29
|
+
```ruby
|
30
|
+
# @runnable
|
31
|
+
class MyClass
|
32
|
+
|
33
|
+
# @runnable
|
34
|
+
def say_hello
|
35
|
+
puts 'Hello!'
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
```
|
40
|
+
3. Run terminal command to run `say_hello` method
|
41
|
+
```bash
|
42
|
+
rcli /home/user/project/my_class.rb say_hello
|
43
|
+
|
44
|
+
-> Hello!
|
45
|
+
```
|
46
|
+
|
47
|
+
Read FAQ for more examples.
|
48
|
+
|
49
|
+
## Installation
|
50
|
+
|
51
|
+
Add this line to your application's Gemfile:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
gem 'ruby_fire_cli'
|
55
|
+
```
|
56
|
+
|
57
|
+
And then execute:
|
58
|
+
|
59
|
+
$ bundle
|
60
|
+
|
61
|
+
Or install it yourself as:
|
62
|
+
|
63
|
+
$ gem install ruby_fire_cli
|
64
|
+
|
65
|
+
## FAQ
|
66
|
+
#### **Can I add documentation for my tool and customize help page content?**
|
67
|
+
Yes. Any text placed after `@runnable` tag will be displayed on the help page. You can add any additional information about how to use your tool there.
|
68
|
+
> **Tip**: You can use multi-line text as well
|
69
|
+
|
70
|
+
##### Example:
|
71
|
+
```ruby
|
72
|
+
# @runnable This tool can talk to you. Run it when you are lonely.
|
73
|
+
# Written in Ruby.
|
74
|
+
class MyClass
|
75
|
+
|
76
|
+
def initialize
|
77
|
+
@hello_msg = 'Hello!'
|
78
|
+
@bye_msg = 'Good Bye!'
|
79
|
+
end
|
80
|
+
|
81
|
+
# @runnable Say 'Hello' to you.
|
82
|
+
# @param [String] name Your name
|
83
|
+
def say_hello(name)
|
84
|
+
puts @hello_msg + ', ' + name
|
85
|
+
end
|
86
|
+
|
87
|
+
# @runnable Say 'Good Bye' to you.
|
88
|
+
def say_bye
|
89
|
+
puts @bye_msg
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
```bash
|
96
|
+
~> rcli /projects/example/my_class.rb --help
|
97
|
+
Options:
|
98
|
+
--debug Run in debug mode.
|
99
|
+
|
100
|
+
This tool can talk to you. Run it when you are lonely.
|
101
|
+
Written in Ruby.
|
102
|
+
|
103
|
+
Available actions:
|
104
|
+
- say_hello
|
105
|
+
Say 'Hello' to you.
|
106
|
+
- say_bye
|
107
|
+
Say 'Good Bye' to you.
|
108
|
+
```
|
109
|
+
|
110
|
+
```bash
|
111
|
+
~> rcli /projects/example/my_class.rb say_hello -h
|
112
|
+
Options:
|
113
|
+
-d, --debug Run in debug mode.
|
114
|
+
-h, --help Show this message
|
115
|
+
--name=<s> (Ruby class: String) Your name
|
116
|
+
|
117
|
+
Say 'Hello' to you.
|
118
|
+
|
119
|
+
```
|
120
|
+
#### **Can I send parameters to my methods?**
|
121
|
+
Yes, `ruby_fire_cli` parses YARD annotation (`@param` and `@option` tags) and check the list of parameters for your method.
|
122
|
+
|
123
|
+
> *Restriction*: You can use Hash parameters as well (for storing options). But you cannot use the same name for parameter and for option.
|
124
|
+
>
|
125
|
+
> For example, `def limit(number, options = {number: 5})...` - `number` name is not allowed. You should use another parameter name.
|
126
|
+
|
127
|
+
##### Example:
|
128
|
+
```ruby
|
129
|
+
# @runnable This tool can talk to you. Run it when you are lonely.
|
130
|
+
# Written in Ruby.
|
131
|
+
class MyClass
|
132
|
+
|
133
|
+
def initialize
|
134
|
+
@hello_msg = 'Hello'
|
135
|
+
@bye_msg = 'Good Bye'
|
136
|
+
end
|
137
|
+
|
138
|
+
# @runnable Say 'Hello' to you.
|
139
|
+
# @param [String] name Your name
|
140
|
+
# @param [Hash] options options
|
141
|
+
# @option options [Boolean] :second_meet Have you met before?
|
142
|
+
# @option options [String] :prefix Your custom prefix
|
143
|
+
def say_hello(name, options = {})
|
144
|
+
second_meet = nil
|
145
|
+
second_meet = 'Nice to see you again!' if options['second_meet']
|
146
|
+
prefix = options['prefix']
|
147
|
+
message = @hello_msg + ', '
|
148
|
+
message += "#{prefix} " if prefix
|
149
|
+
message += "#{name}. "
|
150
|
+
message += second_meet if second_meet
|
151
|
+
puts message
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
```
|
156
|
+
```bash
|
157
|
+
~> rcli /projects/example/my_class.rb say_hello -h
|
158
|
+
Options:
|
159
|
+
-d, --debug Run in debug mode.
|
160
|
+
-h, --help Show this message
|
161
|
+
--name=<s> (Ruby class: String) Your name
|
162
|
+
--second-meet (Ruby class: Boolean) Have you met before?
|
163
|
+
--prefix=<s> (Ruby class: String) Your custom prefix
|
164
|
+
|
165
|
+
Say 'Hello' to you.
|
166
|
+
```
|
167
|
+
```bash
|
168
|
+
~> rcli /projects/example/my_class.rb say_hello -n John --second-meet --prefix Mr.
|
169
|
+
Hello, Mr. John. Nice to see you again!
|
170
|
+
```
|
171
|
+
|
172
|
+
#### **Can I use optional parameters?**
|
173
|
+
Yes. All parameters with `@option` YARD tag are optional.
|
174
|
+
|
175
|
+
`--second-meet` and `--prefix` parameters are optional in the following example:
|
176
|
+
```ruby
|
177
|
+
# @runnable Say 'Hello' to you.
|
178
|
+
# @param [String] name Your name
|
179
|
+
# @param [Hash] options options
|
180
|
+
# @option options [Boolean] :second_meet Have you met before?
|
181
|
+
# @option options [String] :prefix Your custom prefix
|
182
|
+
```
|
183
|
+
Another approach is to use *default values* for parameters.
|
184
|
+
Parameter `--name` in the following example is optional because it has the default value `Chuck`.
|
185
|
+
```ruby
|
186
|
+
# @runnable Say 'Hello' to you.
|
187
|
+
# @param [String] name Your name
|
188
|
+
# @param [Hash] options options
|
189
|
+
# @option options [Boolean] :second_meet Have you met before?
|
190
|
+
# @option options [String] :prefix Your custom prefix
|
191
|
+
def say_hello(name = 'Chuck', options = {})
|
192
|
+
second_meet = nil
|
193
|
+
second_meet = 'Nice to see you again!' if options['second_meet']
|
194
|
+
prefix = options['prefix']
|
195
|
+
message = @hello_msg + ', '
|
196
|
+
message += "#{prefix} " if prefix
|
197
|
+
message += "#{name}. "
|
198
|
+
message += second_meet if second_meet
|
199
|
+
puts message
|
200
|
+
end
|
201
|
+
```
|
202
|
+
```bash
|
203
|
+
-> rcli /projects/example/my_class.rb say_hello
|
204
|
+
Hello, Chuck.
|
205
|
+
```
|
206
|
+
#### **Is it works only for class methods?**
|
207
|
+
`ruby_fire_cli` works with both methods - **class** and **instance** methods. It's clear how it works with **class** method - method is called without any preconditions.
|
208
|
+
**Instance** method will be called in accordance with following logic:
|
209
|
+
1. call `:initialize` method
|
210
|
+
2. call specified action method
|
211
|
+
|
212
|
+
#### **My `require` code doesn't work well. How can I fix it?**
|
213
|
+
Use `require_relative ` method instead.
|
214
|
+
|
215
|
+
## Development
|
216
|
+
|
217
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
218
|
+
|
219
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
220
|
+
|
221
|
+
## Contributing
|
222
|
+
|
223
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/yuri-karpovich/ruby_fire_cli.
|
224
|
+
|
225
|
+
## License
|
226
|
+
|
227
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
228
|
+
|
229
|
+
[Gem Version]: https://rubygems.org/gems/ruby_fire_cli
|
230
|
+
[Build Status]: https://travis-ci.org/yuri-karpovich/ruby_fire_cli
|
231
|
+
[travis pull requests]: https://travis-ci.org/yuri-karpovich/ruby_fire_cli/pull_requests
|
232
|
+
[Code Climate]: https://codeclimate.com/github/yuri-karpovich/ruby_fire_cli
|
233
|
+
[Coverage Status]: https://coveralls.io/github/yuri-karpovich/ruby_fire_cli
|
234
|
+
|
235
|
+
[GV img]: https://badge.fury.io/rb/ruby_fire_cli.svg
|
236
|
+
[BS img]: https://travis-ci.org/yuri-karpovich/ruby_fire_cli.svg?branch=master
|
237
|
+
[CC img]: https://codeclimate.com/github/yuri-karpovich/ruby_fire_cli.png
|
238
|
+
[CS img]: https://coveralls.io/repos/github/yuri-karpovich/ruby_fire_cli/badge.svg?branch=master
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'ruby_fire_cli'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require 'irb'
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'ruby_fire_cli/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'ruby_fire_cli'
|
9
|
+
spec.version = RubyFireCLI::VERSION
|
10
|
+
spec.authors = ['Yury Karpovich']
|
11
|
+
spec.email = %w(spoonest@gmail.com yuri.karpovich@gmail.com)
|
12
|
+
|
13
|
+
spec.summary = 'Command-line runner for ruby code.'
|
14
|
+
spec.description = 'This gem provides you an ability to run any Ruby method ' \
|
15
|
+
'from command-line (no any code modifications required!!!)'
|
16
|
+
spec.homepage = 'https://github.com/yuri-karpovich/ruby_fire_cli'
|
17
|
+
spec.license = "MIT"
|
18
|
+
|
19
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
20
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
21
|
+
if spec.respond_to?(:metadata)
|
22
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
23
|
+
else
|
24
|
+
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
25
|
+
'public gem pushes.'
|
26
|
+
end
|
27
|
+
|
28
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
29
|
+
f.match(%r{^(test|spec|features)/})
|
30
|
+
end
|
31
|
+
spec.bindir = 'exe'
|
32
|
+
spec.executables = ['rcli']
|
33
|
+
spec.require_paths = ['lib']
|
34
|
+
|
35
|
+
spec.add_development_dependency 'bundler', '~> 1.13'
|
36
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
37
|
+
spec.add_development_dependency 'minitest', '~> 5.0'
|
38
|
+
spec.add_development_dependency 'pry', '~> 0'
|
39
|
+
spec.add_development_dependency 'simplecov', '~> 0'
|
40
|
+
spec.add_development_dependency 'coveralls', '~> 0.8'
|
41
|
+
spec.add_dependency 'trollop', '~> 2.1'
|
42
|
+
spec.add_dependency 'yard', '~> 0.9'
|
43
|
+
spec.add_dependency 'colorize', '~> 0.8'
|
44
|
+
end
|
data/exe/rcli
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'trollop'
|
2
|
+
require 'method_parser'
|
3
|
+
require 'colorize'
|
4
|
+
|
5
|
+
# Parses command line and configure #Trollop
|
6
|
+
class CommandLineParser
|
7
|
+
attr_reader :method, :initialize_method
|
8
|
+
@debug = false
|
9
|
+
|
10
|
+
# Generate tool help menu.
|
11
|
+
# IMPORTANT! Should be executed before ARGV.shift
|
12
|
+
def initialize(file_parser)
|
13
|
+
@file_parser = file_parser
|
14
|
+
@sub_commands = @file_parser.runnable_methods.map { |m| m.name.to_s }
|
15
|
+
@sub_commands_text = @file_parser.runnable_methods.map do |m|
|
16
|
+
[
|
17
|
+
m.name.to_s,
|
18
|
+
FileParser.select_runnable_tags(m).map(&:text).join("\n")
|
19
|
+
]
|
20
|
+
end.to_h
|
21
|
+
@parser = Trollop::Parser.new
|
22
|
+
@parser.opt(:debug, 'Run in debug mode.', type: :flag)
|
23
|
+
@parser.stop_on @sub_commands
|
24
|
+
@initialize_method = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.debug?
|
28
|
+
@debug
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.debug=(value)
|
32
|
+
return if @debug
|
33
|
+
ENV['CR_DEBUG'] = 'true'
|
34
|
+
@debug = value
|
35
|
+
end
|
36
|
+
|
37
|
+
def tool_banner
|
38
|
+
result = FileParser.select_runnable_tags(@file_parser.clazz).map(&:text).join("\n")
|
39
|
+
result += "\n\nAvailable actions:\n"
|
40
|
+
result += @sub_commands_text.map do |c, text|
|
41
|
+
t = "\t- #{c}"
|
42
|
+
t += "\n\t\t#{text}" if text != ''
|
43
|
+
t
|
44
|
+
end.join("\n")
|
45
|
+
result
|
46
|
+
end
|
47
|
+
|
48
|
+
def maybe_help(banner, action_name = nil)
|
49
|
+
action = action_name
|
50
|
+
scope = ARGV
|
51
|
+
if action_name
|
52
|
+
action_index = ARGV.index(action)
|
53
|
+
scope = ARGV[0..action_index] if action_index
|
54
|
+
end
|
55
|
+
return unless scope.any? { |a| %w(-h --help).include? a }
|
56
|
+
@parser.banner("\n" + banner)
|
57
|
+
Trollop::with_standard_exception_handling(@parser) { raise Trollop::HelpNeeded }
|
58
|
+
end
|
59
|
+
|
60
|
+
def raise_on_action_absence(sub_commands)
|
61
|
+
return if ARGV.any? { |a| sub_commands.include? a }
|
62
|
+
raise RubyFireCLIError, "You must provide one of available actions: #{sub_commands.join ', '}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def run(action)
|
66
|
+
maybe_help(tool_banner, action ? action.name.to_s : nil)
|
67
|
+
raise RubyFireCLIError, 'Cannot find any @runnable action' unless action
|
68
|
+
raise_on_action_absence @sub_commands
|
69
|
+
@initialize_method ||= MethodParser.new(@file_parser.initialize_method) if @file_parser.initialize_method
|
70
|
+
@method = MethodParser.new action
|
71
|
+
[@initialize_method, @method].each do |method|
|
72
|
+
next unless method
|
73
|
+
method.trollop_opts.each { |a| @parser.opt(*a) }
|
74
|
+
maybe_help(method.text, action.name.to_s)
|
75
|
+
cmd_opts = @parser.parse ARGV
|
76
|
+
given_attrs = cmd_opts.keys.select { |k| k.to_s.include? '_given' }.map { |k| k.to_s.gsub('_given', '').to_sym }
|
77
|
+
method.cmd_opts = cmd_opts.select { |k, _| given_attrs.include? k }
|
78
|
+
method.default_values.each do |k, v|
|
79
|
+
param_name = k.to_sym
|
80
|
+
next if method.option_tags.map(&:name).include?(param_name.to_s)
|
81
|
+
method.cmd_opts[param_name] ||= v
|
82
|
+
end
|
83
|
+
method.required_parameters.each do |required_param|
|
84
|
+
next if method.options_group? required_param
|
85
|
+
next if method.cmd_opts[required_param.to_sym]
|
86
|
+
raise RubyFireCLIError, "You must specify required parameter: #{required_param}"
|
87
|
+
end
|
88
|
+
ARGV.shift
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
data/lib/file_parser.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'yard'
|
2
|
+
require 'ruby_fire_cli_error'
|
3
|
+
|
4
|
+
# Parses code in a file
|
5
|
+
class FileParser
|
6
|
+
RUNNABLE_TAG = :runnable
|
7
|
+
|
8
|
+
attr_reader :clazz,
|
9
|
+
:runnable_methods,
|
10
|
+
:initialize_method,
|
11
|
+
:run_method
|
12
|
+
|
13
|
+
# Parse file with #YARD::CLI::Stats
|
14
|
+
#
|
15
|
+
# @param [String] file_path path to the file to be parsed
|
16
|
+
def initialize(file_path)
|
17
|
+
raise RubyFireCLIError "Cannot find file #{file_path}" unless File.exist?(file_path)
|
18
|
+
code = YARD::CLI::Stats.new
|
19
|
+
code.run(file_path)
|
20
|
+
@all_objects = code.all_objects
|
21
|
+
runnable_classes = list_classes(:runnable)
|
22
|
+
raise RubyFireCLIError, 'At least one runnable Class should be specified in file' if runnable_classes.count != 1
|
23
|
+
@clazz = runnable_classes.first
|
24
|
+
all_methods = list_methods(:all, clazz)
|
25
|
+
@runnable_methods = list_methods(:runnable, clazz)
|
26
|
+
@initialize_method = all_methods.find { |m| m.name == :initialize }
|
27
|
+
@run_method = all_methods.find { |m| m.name == :run }
|
28
|
+
end
|
29
|
+
|
30
|
+
# Select all @runnable tags from of specified object
|
31
|
+
#
|
32
|
+
# @param [YARD::CodeObjects::MethodObject, YARD::CodeObjects::ClassObject] yard_object YARD object
|
33
|
+
# @return [Array(YARD::Tags::Tag)]
|
34
|
+
def self.select_runnable_tags(yard_object)
|
35
|
+
yard_object.tags.select { |t| t.tag_name == RUNNABLE_TAG.to_s }
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# List of methods
|
41
|
+
# @param [Symbol] scope :all - list all methods, :runnable - list only runnable methods
|
42
|
+
# @param [YARD::CodeObjects::ClassObject, nil] clazz list methods of specified class only
|
43
|
+
# @return [Array(YARD::CodeObjects::MethodObject)]
|
44
|
+
def list_methods(scope = :all, clazz = nil)
|
45
|
+
all_methods = @all_objects.select { |o| o.type == :method }
|
46
|
+
all_class_methods = []
|
47
|
+
all_class_methods = clazz.children.select { |m| m.class == YARD::CodeObjects::MethodObject } if clazz
|
48
|
+
|
49
|
+
case scope
|
50
|
+
when :all
|
51
|
+
if clazz
|
52
|
+
all_class_methods
|
53
|
+
else
|
54
|
+
all_methods
|
55
|
+
end
|
56
|
+
when RUNNABLE_TAG
|
57
|
+
if clazz
|
58
|
+
all_class_methods.select { |m| m.has_tag? RUNNABLE_TAG }
|
59
|
+
else
|
60
|
+
all_methods.select { |m| m.has_tag? RUNNABLE_TAG }
|
61
|
+
end
|
62
|
+
else
|
63
|
+
raise ":scope can be :all or #{RUNNABLE_TAG}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# List classes
|
68
|
+
#
|
69
|
+
# @param [Symbol] scope :all - list all classes, :runnable - list only runnable classes
|
70
|
+
# @return [Array(YARD::CodeObjects::ClassObject)]
|
71
|
+
def list_classes(scope= :all)
|
72
|
+
all_classes = @all_objects.select { |o| o.type == :class }
|
73
|
+
case scope
|
74
|
+
when :all
|
75
|
+
all_classes
|
76
|
+
when RUNNABLE_TAG
|
77
|
+
all_classes.select { |m| m.has_tag? RUNNABLE_TAG }
|
78
|
+
else
|
79
|
+
raise ":scope can be :all or #{RUNNABLE_TAG}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Select all @option tags from of specified object
|
84
|
+
#
|
85
|
+
# @param [YARD::CodeObjects::MethodObject, YARD::CodeObjects::ClassObject] yard_object YARD object
|
86
|
+
# @return [Array(YARD::Tags::Tag)]
|
87
|
+
def self.select_option_tags(yard_object)
|
88
|
+
yard_object.tags.select { |t| t.tag_name == 'option' }
|
89
|
+
end
|
90
|
+
|
91
|
+
# Select all @param tags from of specified object
|
92
|
+
#
|
93
|
+
# @param [YARD::CodeObjects::MethodObject, YARD::CodeObjects::ClassObject] yard_object YARD object
|
94
|
+
# @return [Array(YARD::Tags::Tag)]
|
95
|
+
def self.select_param_tags(yard_object)
|
96
|
+
yard_object.tags.select { |t| t.tag_name == 'param' }
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
# YARD::Tags::Library.define_tag "Run in Console", :runnable, :with_types_and_name
|
102
|
+
YARD::Tags::Library.define_tag 'Console Tool Description', FileParser::RUNNABLE_TAG
|
103
|
+
|
104
|
+
module YARD
|
105
|
+
module CLI
|
106
|
+
class Stats < Yardoc
|
107
|
+
def print_statistics;
|
108
|
+
end
|
109
|
+
|
110
|
+
def print_undocumented_objects;
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
# Parses method code
|
2
|
+
class MethodParser
|
3
|
+
attr_reader :method,
|
4
|
+
:name,
|
5
|
+
:text,
|
6
|
+
:parameters,
|
7
|
+
:param_tags, # All method parameters tags
|
8
|
+
:option_tags, # Only options tags
|
9
|
+
:trollop_opts,
|
10
|
+
:default_values,
|
11
|
+
:required_parameters
|
12
|
+
|
13
|
+
attr_accessor :cmd_opts
|
14
|
+
|
15
|
+
TYPES_MAPPINGS = {
|
16
|
+
'String' => :string,
|
17
|
+
'Integer' => :int,
|
18
|
+
'Fixnum' => :int,
|
19
|
+
'Float' => :float,
|
20
|
+
'Boolean' => :boolean,
|
21
|
+
'Array(String)' => :strings,
|
22
|
+
'Array(Integer)' => :ints,
|
23
|
+
'Array(Fixnum)' => :ints,
|
24
|
+
'Array(Float)' => :floats,
|
25
|
+
'Array(Boolean)' => :booleans
|
26
|
+
}.freeze
|
27
|
+
|
28
|
+
# @param [YARD::CodeObjects::MethodObject] method YARD method object to be parsed
|
29
|
+
def initialize(method)
|
30
|
+
@method = method
|
31
|
+
@name = @method.name
|
32
|
+
@text = FileParser.select_runnable_tags(@method).map(&:text).join("\n")
|
33
|
+
@parameters = @method.parameters
|
34
|
+
@default_values = default_params
|
35
|
+
@param_tags = FileParser.select_param_tags @method
|
36
|
+
@option_tags = FileParser.select_option_tags @method
|
37
|
+
@required_parameters = @param_tags.select { |t| t.tag_name == 'param' }.map(&:name)
|
38
|
+
@cmd_opts = nil
|
39
|
+
same_params = param_tags_names & option_tags_names
|
40
|
+
unless same_params.count.zero?
|
41
|
+
raise(
|
42
|
+
RubyFireCLIError,
|
43
|
+
"You have the same name for @param and @option attribute(s): #{same_params.join(', ')}.
|
44
|
+
Use different names to `ruby_fire_cli` be able to run #{@name} method."
|
45
|
+
)
|
46
|
+
end
|
47
|
+
@trollop_opts = prepare_opts_for_trollop
|
48
|
+
end
|
49
|
+
|
50
|
+
def prepare_opts_for_trollop
|
51
|
+
result = []
|
52
|
+
param_tags.each do |tag|
|
53
|
+
tag_name = tag.name
|
54
|
+
tag_text = tag.text
|
55
|
+
tag_type = tag.type
|
56
|
+
if tag_type == "Hash"
|
57
|
+
options = option_tags.select { |t| t.name == tag.name }
|
58
|
+
if options.count > 0
|
59
|
+
options.each do |option|
|
60
|
+
option_name = option.pair.name.delete(':')
|
61
|
+
option_text = option.pair.text
|
62
|
+
option_type = option.pair.type
|
63
|
+
result << [
|
64
|
+
option_name.to_sym,
|
65
|
+
"(Ruby class: #{option_type}) " + option_text.to_s,
|
66
|
+
type: parse_type(option_type)
|
67
|
+
]
|
68
|
+
end
|
69
|
+
else
|
70
|
+
result << [
|
71
|
+
tag_name.to_sym,
|
72
|
+
"(Ruby class: #{tag_type}) " + tag_text.to_s,
|
73
|
+
type: parse_type(tag_type)
|
74
|
+
]
|
75
|
+
end
|
76
|
+
else
|
77
|
+
result << [
|
78
|
+
tag_name.to_sym,
|
79
|
+
"(Ruby class: #{tag_type}) " + tag_text.to_s,
|
80
|
+
type: parse_type(tag_type)
|
81
|
+
]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
result
|
85
|
+
end
|
86
|
+
|
87
|
+
def params_array
|
88
|
+
options_groups = {}
|
89
|
+
get_params = {}
|
90
|
+
@parameters.map(&:first).map.with_index { |p, i| [i, p] }.to_h.each do |index, name|
|
91
|
+
if options_group?(name)
|
92
|
+
options_groups[index] = name
|
93
|
+
get_params[index] = option_as_hash(name)
|
94
|
+
else
|
95
|
+
get_params[index] = @cmd_opts[name.to_sym]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
get_params = get_params.to_a.sort_by { |a| a[0] }.reverse
|
99
|
+
stop_delete = false
|
100
|
+
get_params.delete_if do |a|
|
101
|
+
index = a[0]
|
102
|
+
value = a[1]
|
103
|
+
result = value.nil?
|
104
|
+
result = value == {} if options_groups[index]
|
105
|
+
stop_delete = true unless result
|
106
|
+
next if stop_delete
|
107
|
+
end
|
108
|
+
get_params.sort_by { |a| a[0] }.map { |a| a[1] }
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
# @return [Array(String)] Names of parameters
|
113
|
+
def param_tags_names
|
114
|
+
param_tags.map(&:name)
|
115
|
+
end
|
116
|
+
|
117
|
+
# @return [Array(String)] Names of options
|
118
|
+
def option_tags_names
|
119
|
+
option_tags.map { |t| t.pair.name.delete(':') }
|
120
|
+
end
|
121
|
+
|
122
|
+
# Check if the name is an option
|
123
|
+
#
|
124
|
+
# @param [String] param_name name of parameter to be verified
|
125
|
+
# @return [Boolean] true if current parameter name is an option key
|
126
|
+
def options_group?(param_name)
|
127
|
+
option_tags.any? { |t| t.name == param_name }
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def parse_type(yard_type)
|
134
|
+
result = TYPES_MAPPINGS[yard_type]
|
135
|
+
raise RubyFireCLIError, "Unsupported YARD type: #{yard_type}" unless result
|
136
|
+
result
|
137
|
+
end
|
138
|
+
|
139
|
+
# @return [Hash] default values for parameters
|
140
|
+
def default_params
|
141
|
+
@parameters.to_a.map do |array|
|
142
|
+
array.map do |a|
|
143
|
+
if a
|
144
|
+
['"', "'"].include?(a[0]) && ['"', "'"].include?(a[-1]) ? a[1..-2] : a
|
145
|
+
else
|
146
|
+
a
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end.to_h
|
150
|
+
end
|
151
|
+
|
152
|
+
# @return [Hash] options parameter as Hash
|
153
|
+
def option_as_hash(options_group_name)
|
154
|
+
result = {}
|
155
|
+
option_tags.select { |t| t.name == options_group_name }.each do |t|
|
156
|
+
option_name = t.pair.name.delete(':')
|
157
|
+
option_value = @cmd_opts[option_name.to_sym]
|
158
|
+
result[option_name] = option_value if option_value
|
159
|
+
end
|
160
|
+
result
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'file_parser'
|
2
|
+
require 'command_line_parser'
|
3
|
+
require 'ruby_fire_cli/version'
|
4
|
+
|
5
|
+
# ruby_fire_cli logic is here
|
6
|
+
module RubyFireCLI
|
7
|
+
CommandLineParser.debug = ARGV.any? { |a| %w(-d --debug).include? a }
|
8
|
+
SEPARATOR = '==================================='.freeze
|
9
|
+
begin
|
10
|
+
start_time = Time.now
|
11
|
+
success_status = true
|
12
|
+
puts "#{SEPARATOR}\nStart Time: #{start_time}\n#{SEPARATOR}".blue
|
13
|
+
file_from_arg = ARGV.shift
|
14
|
+
raise RubyFireCLIError, 'Specify file to be executed' unless file_from_arg
|
15
|
+
file_path = File.realpath file_from_arg
|
16
|
+
file_parser = FileParser.new(file_path)
|
17
|
+
next_arguments = ARGV.dup
|
18
|
+
%w(-d --debug -h --help).each { |a| next_arguments.delete a }
|
19
|
+
cmd = next_arguments[0]
|
20
|
+
actions = file_parser.runnable_methods.select { |m| m.name.to_s == cmd }
|
21
|
+
if actions.count > 1
|
22
|
+
raise(
|
23
|
+
RubyFireCLIError,
|
24
|
+
"Class and Instance methods have the same name (#{cmd}). Actions names should be unique"
|
25
|
+
)
|
26
|
+
end
|
27
|
+
action = actions.first
|
28
|
+
action ||= file_parser.run_method
|
29
|
+
c_line_parser = CommandLineParser.new(file_parser)
|
30
|
+
c_line_parser.run(action)
|
31
|
+
|
32
|
+
debug_message = SEPARATOR
|
33
|
+
if c_line_parser.initialize_method
|
34
|
+
debug_message += "\ninitialize method execution\n"
|
35
|
+
debug_message += c_line_parser.initialize_method.cmd_opts.map { |k, v| " #{k} = #{v}" }.join("\n")
|
36
|
+
end
|
37
|
+
debug_message += "\n#{action.name} method execution\n"
|
38
|
+
debug_message += c_line_parser.method.cmd_opts.map { |k, v| " #{k} = #{v}" }.join("\n")
|
39
|
+
debug_message += "\nRemaining arguments: #{ARGV.inspect}" if ARGV != []
|
40
|
+
debug_message += "\n#{SEPARATOR}"
|
41
|
+
puts debug_message if CommandLineParser.debug?
|
42
|
+
|
43
|
+
require file_path
|
44
|
+
class_full_name = file_parser.clazz.title
|
45
|
+
raise RubyFireCLIError, "#{class_full_name} is not defined" unless Module.const_defined?(class_full_name)
|
46
|
+
klass_obj = Module.const_get(class_full_name)
|
47
|
+
method_type = action.scope
|
48
|
+
method_params = c_line_parser.method.params_array
|
49
|
+
|
50
|
+
case method_type
|
51
|
+
when :class
|
52
|
+
klass_obj.send(action.name, *method_params)
|
53
|
+
when :instance
|
54
|
+
init_method = c_line_parser.initialize_method
|
55
|
+
init_params = []
|
56
|
+
init_params = init_method.params_array if init_method
|
57
|
+
obj = klass_obj.new(*init_params)
|
58
|
+
obj.send(action.name, *method_params)
|
59
|
+
else
|
60
|
+
raise RubyFireCLIError, "Unknown method type: #{method_type}"
|
61
|
+
end
|
62
|
+
rescue => e
|
63
|
+
success_status = false
|
64
|
+
raise e
|
65
|
+
ensure
|
66
|
+
finish_time = Time.now
|
67
|
+
status = success_status ? 'Success'.green : 'Error'.red
|
68
|
+
puts "\n#{SEPARATOR}".blue
|
69
|
+
puts 'Execution status: '.blue + status
|
70
|
+
puts "Finish Time: #{finish_time} (Duration: #{((finish_time - start_time) / 60).round(2) } minutes)
|
71
|
+
#{SEPARATOR}\n".blue
|
72
|
+
end
|
73
|
+
end
|
metadata
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby_fire_cli
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Yury Karpovich
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-11-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.13'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.13'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: simplecov
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: coveralls
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.8'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.8'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: trollop
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2.1'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '2.1'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: yard
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.9'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.9'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: colorize
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0.8'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0.8'
|
139
|
+
description: This gem provides you an ability to run any Ruby method from command-line
|
140
|
+
(no any code modifications required!!!)
|
141
|
+
email:
|
142
|
+
- spoonest@gmail.com
|
143
|
+
- yuri.karpovich@gmail.com
|
144
|
+
executables:
|
145
|
+
- rcli
|
146
|
+
extensions: []
|
147
|
+
extra_rdoc_files: []
|
148
|
+
files:
|
149
|
+
- ".coveralls.yml"
|
150
|
+
- ".gitignore"
|
151
|
+
- ".travis.yml"
|
152
|
+
- Gemfile
|
153
|
+
- LICENSE.txt
|
154
|
+
- README.md
|
155
|
+
- Rakefile
|
156
|
+
- bin/console
|
157
|
+
- bin/setup
|
158
|
+
- console_runner.gemspec
|
159
|
+
- exe/rcli
|
160
|
+
- lib/command_line_parser.rb
|
161
|
+
- lib/file_parser.rb
|
162
|
+
- lib/method_parser.rb
|
163
|
+
- lib/ruby_fire_cli.rb
|
164
|
+
- lib/ruby_fire_cli/version.rb
|
165
|
+
- lib/ruby_fire_cli_error.rb
|
166
|
+
homepage: https://github.com/yuri-karpovich/ruby_fire_cli
|
167
|
+
licenses:
|
168
|
+
- MIT
|
169
|
+
metadata:
|
170
|
+
allowed_push_host: https://rubygems.org
|
171
|
+
post_install_message:
|
172
|
+
rdoc_options: []
|
173
|
+
require_paths:
|
174
|
+
- lib
|
175
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - ">="
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '0'
|
180
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
181
|
+
requirements:
|
182
|
+
- - ">="
|
183
|
+
- !ruby/object:Gem::Version
|
184
|
+
version: '0'
|
185
|
+
requirements: []
|
186
|
+
rubyforge_project:
|
187
|
+
rubygems_version: 2.4.5.1
|
188
|
+
signing_key:
|
189
|
+
specification_version: 4
|
190
|
+
summary: Command-line runner for ruby code.
|
191
|
+
test_files: []
|