ruby_fire_cli 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1 @@
1
+ repo_token: Zt5KZXhBj3awn9OS5Hy6pDuVEGSrWWSjT
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /vendor/
11
+ /.idea/
12
+ *.gem
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.2.5
5
+ before_install: gem install bundler -v 1.13.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ruby_fire_cli.gemspec
4
+ gemspec
@@ -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.
@@ -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
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
@@ -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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../test/simple_cov' if ENV['rcliNER_SCOV'].to_s == 'true'
4
+
5
+ lib = File.expand_path('../../lib', __FILE__)
6
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
7
+ require 'ruby_fire_cli'
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module RubyFireCLI
2
+ VERSION = '1.0.0'.freeze
3
+ end
@@ -0,0 +1,11 @@
1
+ class RubyFireCLIError < StandardError
2
+
3
+ def backtrace
4
+ if CommandLineParser.debug?
5
+ super
6
+ else
7
+ @object
8
+ end
9
+ end
10
+
11
+ 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: []