simple_command_dispatcher 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.mb +32 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +210 -0
- data/Rakefile +18 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/core_extensions/string.rb +5 -0
- data/lib/simple_command_dispatcher.rb +93 -0
- data/lib/simple_command_dispatcher/klass_transform.rb +240 -0
- data/lib/simple_command_dispatcher/version.rb +5 -0
- data/lib/tasks/simple_command_dispatcher_sandbox.rake +232 -0
- data/simple_command_dispatcher.gemspec +48 -0
- metadata +186 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 978601fab3ba1ceb4f717b3b4d1d6fb13ea649f0
|
4
|
+
data.tar.gz: ceae85997bb18d8479845f89bf324c4d15fd4eb6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7df1d6310235fbe4ea04e82f95f7a00d40e5790ddaf8f6a4a1722cb59b642b2ba6b209231b4497ed81edfc0da9d214bb62e6bbf307b9ff415f2bfea53f7e01b6
|
7
|
+
data.tar.gz: 4272706508300aa3a2e7d0760c717e5621d4db0c73cab6736eb0a5f197c192c0b4696371bc307e8882ee52d6645691036c848a8ae761c52691bb2e9004ad8b97
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.3.1
|
data/.travis.yml
ADDED
data/CHANGELOG.mb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
### Unreleased
|
2
|
+
* Upcoming features
|
3
|
+
* whitelist for allowable namespaces
|
4
|
+
|
5
|
+
### 1.1.1
|
6
|
+
* Documentation updates
|
7
|
+
* Add example code in README.md to include clarification on command namespacing, and how to autoload command classes to avoid NameError exceptions when SimpleCommand::Dispatcher.call(...) is call due to uninitialized command constants.
|
8
|
+
|
9
|
+
### 1.1.0
|
10
|
+
* Bug fix lib/core_extensions/string.rb not included in SimpleCommand::Dispatcher, causing exception.
|
11
|
+
|
12
|
+
### 1.0.0
|
13
|
+
* Limit support to ruby >= 2.2.2
|
14
|
+
|
15
|
+
### 0.2.0
|
16
|
+
* Yanked
|
17
|
+
|
18
|
+
### 0.1.3
|
19
|
+
* Documentation updates
|
20
|
+
* Add `SimpleCommand::KlassTransform` rake task sandbox to test the below listed `SimpleCommand::KlassTransform` members; run the rake tasks for usage; these rake tasks are simply a playground to see how simple_command_dispatcher transforms parameters into output when calling SimpleCommand::Dispatcher.call(...):
|
21
|
+
* `SimpleCommand::KlassTransform#to_class_string`
|
22
|
+
* $ rake to_class_string
|
23
|
+
* `SimpleCommand::KlassTransform#to_modules_string`
|
24
|
+
* $ rake to_modules_string
|
25
|
+
* `SimpleCommand::KlassTransform#to_constantized_class_string`
|
26
|
+
* $ rake to_constantized_class_string
|
27
|
+
|
28
|
+
### 0.1.2
|
29
|
+
* Documentation updates
|
30
|
+
|
31
|
+
### 0.1.1
|
32
|
+
* Documentation updates
|
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at web.gma@gmail.com. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [http://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: http://contributor-covenant.org
|
74
|
+
[version]: http://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 gangelo
|
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,210 @@
|
|
1
|
+
[![GitHub version](https://badge.fury.io/gh/gangelo%2Fsimple_command_dispatcher.svg)](https://badge.fury.io/gh/gangelo%2Fsimple_command_dispatcher)
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/simple_command_dispatcher.svg)](https://badge.fury.io/rb/simple_command_dispatcher)
|
3
|
+
|
4
|
+
# Q. simple_command_dispatcher - what is it?
|
5
|
+
# A. It's a Ruby gem!
|
6
|
+
|
7
|
+
## Overview
|
8
|
+
__simple_command_dispatcher__ (SCD) allows you to execute __simple_command__ commands in a more dynamic way. If you are not familiar with the _simple_command_ gem, check it out [here][simple-command]. SCD was written specifically with the [rails-api][rails-api] in mind; however, you can use SDC wherever you use simple_command commands.
|
9
|
+
|
10
|
+
## Example
|
11
|
+
The below example is from a `rails-api` API that uses token-based authentication and services two mobile applications, identified as *__my_app1__* and *__my_app2__*, in this example.
|
12
|
+
|
13
|
+
This example assumes the following:
|
14
|
+
|
15
|
+
* `application_controller` is a base class, inherited by all other controllers. The `#authenticate_request` method is called for every request in order to make sure the request is authorized (`before_action :authenticate_request`).
|
16
|
+
* `request.headers` will contain the authorization token to authorize all requests (`request.headers["Authorization"]`)
|
17
|
+
* This application uses the following folder structure to manage its _simple_command_ commands:
|
18
|
+
|
19
|
+
![N|Solid](https://cldup.com/1UeyWzOLic.png)
|
20
|
+
|
21
|
+
Command classes (and the modules they reside under) are named *__according to their file name and respective location within the above folder structure__*; for example, the command class defined in the `/api/my_app1/v1/authenticate_request.rb` file would be defined in this manner:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
# /api/my_app1/v1/authenticate_request.rb
|
25
|
+
|
26
|
+
module Api
|
27
|
+
module MyApp1
|
28
|
+
module V1
|
29
|
+
class AuthenticateRequest
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
Likewise, the command class defined in the `/api/my_app2/v2/update_user.rb` file would be defined in this manner, and so on:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
# /api/my_app2/v2/update_user.rb
|
40
|
+
|
41
|
+
module Api
|
42
|
+
module MyApp2
|
43
|
+
module V2
|
44
|
+
class UpdateUser
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
The __routes used in this example__, conform to the following format: `"/api/[app_name]/[app_version]/[controller]"` where `[app_name]` = the _application name_,`[app_version]` = the _application version_, and `[controller]` = the _controller_; therefore, running `$ rake routes` for this example would output the following sample route information:
|
52
|
+
|
53
|
+
|
54
|
+
| Prefix | Verb | URI Pattern | Controller#Action |
|
55
|
+
|-------------:|:-------------|:------------------|:------------------|
|
56
|
+
| api_my_app1_v1_user_authenticate | POST | /api/my_app1/v1/user/authenticate(.:format) | api/my_app1/v1/authentication#create |
|
57
|
+
| api_my_app1_v2_user_authenticate | POST | /api/my_app1/v2/user/authenticate(.:format) | api/my_app1/v2/authentication#create |
|
58
|
+
| api_my_app2_v1_user_authenticate | POST | /api/my_app2/v1/user/authenticate(.:format) | api/my_app2/v1/authentication#create |
|
59
|
+
| api_my_app2_v2_user | PATCH | /api/my_app2/v2/users/:id(.:format) | api/my_app2/v2/users#update |
|
60
|
+
| | PUT | /api/my_app2/v2/users/:id(.:format) | api/my_app2/v2/users#update |
|
61
|
+
|
62
|
+
|
63
|
+
### Request Authentication Code Snippet
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
# /config/initializers/simple_command_dispatcher.rb
|
67
|
+
|
68
|
+
# See: http://pothibo.com/2013/07/namespace-stuff-in-your-app-folder/
|
69
|
+
|
70
|
+
=begin
|
71
|
+
# Uncomment this code if you want to namespace your commands in the following manner, for example:
|
72
|
+
#
|
73
|
+
# class Api::MyApp1::V1::AuthenticateRequest; end
|
74
|
+
#
|
75
|
+
# As opposed to this:
|
76
|
+
#
|
77
|
+
# module Api
|
78
|
+
# module MyApp1
|
79
|
+
# module V1
|
80
|
+
# class AuthenticateRequest
|
81
|
+
# end
|
82
|
+
# end
|
83
|
+
# end
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
module Helpers
|
87
|
+
def self.ensure_namespace(namespace, scope = "::")
|
88
|
+
namespace_parts = namespace.split("::")
|
89
|
+
|
90
|
+
namespace_chain = ""
|
91
|
+
|
92
|
+
namespace_parts.each { | part |
|
93
|
+
namespace_chain = (namespace_chain.empty?) ? part : "#{namespace_chain}::#{part}"
|
94
|
+
eval("module #{scope}#{namespace_chain}; end")
|
95
|
+
}
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
Helpers.ensure_namespace("Api::MyApp1::V1")
|
100
|
+
Helpers.ensure_namespace("Api::MyApp1::V2")
|
101
|
+
Helpers.ensure_namespace("Api::MyApp2::V1")
|
102
|
+
Helpers.ensure_namespace("Api::MyApp2::V2")
|
103
|
+
=end
|
104
|
+
|
105
|
+
# simple_command_dispatcher creates commands dynamically; therefore we need
|
106
|
+
# to make sure the namespaces and command classes are loaded before we construct and
|
107
|
+
# call them. The below code traverses the 'app/api' and all subfolders, and
|
108
|
+
# autoloads them so that we do not get any NameError exceptions due to
|
109
|
+
# uninitialized constants.
|
110
|
+
Rails.application.config.to_prepare do
|
111
|
+
path = Rails.root + "app/api"
|
112
|
+
ActiveSupport::Dependencies.autoload_paths -= [path.to_s]
|
113
|
+
|
114
|
+
reloader = ActiveSupport::FileUpdateChecker.new [], path.to_s => [:rb] do
|
115
|
+
ActiveSupport::DescendantsTracker.clear
|
116
|
+
ActiveSupport::Dependencies.clear
|
117
|
+
|
118
|
+
Dir[path + "**/*.rb"].each do |file|
|
119
|
+
ActiveSupport.require_or_load file
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
Rails.application.reloaders << reloader
|
124
|
+
ActionDispatch::Reloader.to_prepare { reloader.execute_if_updated }
|
125
|
+
reloader.execute
|
126
|
+
end
|
127
|
+
```
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
# /app/controllers/application_controller.rb
|
131
|
+
|
132
|
+
require 'simple_command_dispatcher'
|
133
|
+
|
134
|
+
class ApplicationController < ActionController::API
|
135
|
+
before_action :authenticate_request
|
136
|
+
attr_reader :current_user
|
137
|
+
|
138
|
+
protected
|
139
|
+
|
140
|
+
def get_command_path
|
141
|
+
# request.env['PATH_INFO'] could return any number of paths. The important
|
142
|
+
# thing (in the case of our example), is that we get the portion of the
|
143
|
+
# path that uniquely identifies the SimpleCommand we need to call; this
|
144
|
+
# would include the application, the API version and the SimpleCommand
|
145
|
+
# name itself.
|
146
|
+
command_path = request.env['PATH_INFO'] # => "/api/[app name]/v1/[action]”
|
147
|
+
command_path = command_path.split('/').slice(0,4).join('/') # => "/api/[app name]/v1/"
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def authenticate_request
|
153
|
+
# The parameters and options we are passing to the dispatcher, wind up equating
|
154
|
+
# to the following: Api::MyApp1::V1::AuthenticateRequest.call(request.headers).
|
155
|
+
# Explaination: @param command_modules (e.g. path, "/api/my_app1/v1/"), in concert with @param
|
156
|
+
# options { camelize: true }, is transformed into "Api::MyApp1::V1" and prepended to the
|
157
|
+
# @param command, which becomes "Api::MyApp1::V1::AuthenticateRequest." This string is then
|
158
|
+
# simply constantized; #call is then executed, passing the @param command_parameters
|
159
|
+
# (e.g. request.headers, which contains ["Authorization"], out authorization token).
|
160
|
+
# Consequently, the correlation between our routes and command class module structure
|
161
|
+
# was no coincidence.
|
162
|
+
command = SimpleCommand::Dispatcher.call(:AuthenticateRequest, get_command_path, { camelize: true}, request.headers)
|
163
|
+
if command.success?
|
164
|
+
@current_user = command.result
|
165
|
+
else
|
166
|
+
render json: { error: 'Not Authorized' }, status: 401
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
```
|
171
|
+
|
172
|
+
|
173
|
+
## Installation
|
174
|
+
|
175
|
+
Add this line to your application's Gemfile:
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
gem 'simple_command_dispatcher'
|
179
|
+
```
|
180
|
+
|
181
|
+
And then execute:
|
182
|
+
|
183
|
+
$ bundle
|
184
|
+
|
185
|
+
Or install it yourself as:
|
186
|
+
|
187
|
+
$ gem install simple_command_dispatcher
|
188
|
+
|
189
|
+
## Usage
|
190
|
+
|
191
|
+
See the example above.
|
192
|
+
|
193
|
+
## Development
|
194
|
+
|
195
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
196
|
+
|
197
|
+
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).
|
198
|
+
|
199
|
+
## Contributing
|
200
|
+
|
201
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/gangelo/simple_command_dispatcher. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
202
|
+
|
203
|
+
|
204
|
+
## License
|
205
|
+
|
206
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
207
|
+
|
208
|
+
[simple-command]: <https://rubygems.org/gems/simple_command>
|
209
|
+
[rails-api]: <https://rubygems.org/gems/rails-api>
|
210
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rspec/core/rake_task"
|
3
|
+
require 'yard'
|
4
|
+
|
5
|
+
# Rspec
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
|
+
task default: :spec
|
8
|
+
|
9
|
+
|
10
|
+
# Yard
|
11
|
+
YARD::Rake::YardocTask.new do |t|
|
12
|
+
t.files = ['lib/**/*.rb']
|
13
|
+
t.options = ['--no-cache', '--protected', '--private']
|
14
|
+
t.stats_options = ['--list-undoc']
|
15
|
+
end
|
16
|
+
|
17
|
+
# Load our custom rake tasks.
|
18
|
+
Gem.find_files("tasks/**/*.rake").each { | path | import path }
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "simple_command_dispatcher"
|
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,93 @@
|
|
1
|
+
require "simple_command_dispatcher/version"
|
2
|
+
require "simple_command_dispatcher/klass_transform"
|
3
|
+
require "simple_command"
|
4
|
+
require "active_support/core_ext/string/inflections"
|
5
|
+
|
6
|
+
module Kernel
|
7
|
+
def eigenclass
|
8
|
+
class << self
|
9
|
+
self
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module SimpleCommand
|
15
|
+
|
16
|
+
# Provides a way to call SimpleCommand commands in a more dymanic manner.
|
17
|
+
#
|
18
|
+
# For information about the simple_command gem, visit https://rubygems.org/gems/simple_command
|
19
|
+
#
|
20
|
+
module Dispatcher
|
21
|
+
|
22
|
+
class << self
|
23
|
+
include SimpleCommand::KlassTransform
|
24
|
+
|
25
|
+
public
|
26
|
+
|
27
|
+
# Calls a *SimpleCommand* given the command name, the modules the command belongs to and the parameters to pass to the command.
|
28
|
+
#
|
29
|
+
# @param command [Symbol, String] the name of the SimpleCommand to call.
|
30
|
+
#
|
31
|
+
# @param command_modules [Hash, Array] the ruby modules that qualify the SimpleCommand to call. When passing a Hash, the Hash
|
32
|
+
# keys serve as documentation only. For example, ['Api', 'AppName', 'V1'] and { :api :Api, app_name: :AppName, api_version: :V1 }
|
33
|
+
# will both produce 'Api::AppName::V1', this string will be prepended to the command to form the SimpleCommand to call
|
34
|
+
# (e.g. 'Api::AppName::V1::MySimpleCommand' = Api::AppName::V1::MySimpleCommand.call(*command_parameters)).
|
35
|
+
#
|
36
|
+
# @param [Hash] options the options that determine how command and command_module are transformed.
|
37
|
+
# @option options [Boolean] :camelize (false) determines whether or not both class and module names should be camelized.
|
38
|
+
# @option options [Boolean] :titleize (false) determines whether or not both class and module names should be titleized.
|
39
|
+
# @option options [Boolean] :class_titleize (false) determines whether or not class names should be titleized.
|
40
|
+
# @option options [Boolean] :class_camelized (false) determines whether or not class names should be camelized.
|
41
|
+
# @option options [Boolean] :module_titleize (false) determines whether or not module names should be titleized.
|
42
|
+
# @option options [Boolean] :module_camelized (false) determines whether or not module names should be camelized.
|
43
|
+
#
|
44
|
+
# @param command_parameters [*] the parameters to pass to the call method of the SimpleCommand. This parameter is simply
|
45
|
+
# passed through to the call method of the SimpleCommand.
|
46
|
+
#
|
47
|
+
# @return [SimpleCommand] the SimpleCommand returned as a result from calling the SimpleCommand#call method.
|
48
|
+
#
|
49
|
+
# @example
|
50
|
+
#
|
51
|
+
# # Below call equates to the following: Api::Carz4Rent::V1::Authenticate.call({ email: 'sam@gmail.com', password: 'AskM3!' })
|
52
|
+
# SimpleCommand::Dispatcher.call(:Authenticate, { api: :Api, app_name: :Carz4Rent, api_version: :V1 },
|
53
|
+
# { email: 'sam@gmail.com', password: 'AskM3!' } ) # => SimpleCommand result
|
54
|
+
#
|
55
|
+
# # Below equates to the following: Api::Carz4Rent::V2::Authenticate.call('sam@gmail.com', 'AskM3!')
|
56
|
+
# SimpleCommand::Dispatcher.call(:Authenticate, ['Api', 'Carz4Rent', 'V2'], 'sam@gmail.com', 'AskM3!') # => SimpleCommand result
|
57
|
+
#
|
58
|
+
# # Below equates to the following: Api::Auth::JazzMeUp::V1::Authenticate.call('jazz_me@gmail.com', 'JazzM3!')
|
59
|
+
# SimpleCommand::Dispatcher.call(:Authenticate, ['Api::Auth::JazzMeUp', :V1], 'jazz_me@gmail.com', 'JazzM3!') # => SimpleCommand result
|
60
|
+
#
|
61
|
+
def call(command = "", command_modules = {}, options = {}, *command_parameters)
|
62
|
+
|
63
|
+
# Create a constantized class from our command and command_modules...
|
64
|
+
simple_command_class_constant = to_constantized_class(command, command_modules, options)
|
65
|
+
|
66
|
+
# Calling is_simple_command? returns true if the class pointed to by
|
67
|
+
# simple_command_class_constant is a valid SimpleCommand class; that is,
|
68
|
+
# if it prepends module SimpleCommand::ClassMethods.
|
69
|
+
if !is_simple_command?(simple_command_class_constant)
|
70
|
+
raise ArgumentError.new('Class does not prepend module SimpleCommand.')
|
71
|
+
end
|
72
|
+
|
73
|
+
# We know we have a valid SimpleCommand; all we need to do is call #call,
|
74
|
+
# pass the command_parameter variable arguments to the call, and return the results.
|
75
|
+
simple_command_class_constant.call(*command_parameters)
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
# @!visibility public
|
81
|
+
#
|
82
|
+
# Returns true or false depending on whether or not the class constant prepends module SimpleCommand::ClassMethods.
|
83
|
+
#
|
84
|
+
# @param klass_constant [String] a class constant that will be validated to see whether or not the class prepends module SimpleCommand::ClassMethods.
|
85
|
+
#
|
86
|
+
# @return [Boolean] true if klass_constant prepends Module SimpleCommand::ClassMethods, false otherwise.
|
87
|
+
#
|
88
|
+
def is_simple_command?(klass_constant)
|
89
|
+
klass_constant.eigenclass.included_modules.include? SimpleCommand::ClassMethods
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,240 @@
|
|
1
|
+
require_relative '../core_extensions/string'
|
2
|
+
|
3
|
+
module SimpleCommand
|
4
|
+
|
5
|
+
# Handles class and module transformations.
|
6
|
+
module KlassTransform
|
7
|
+
|
8
|
+
# Returns a constantized class (as a Class constant), given the klass and klass_modules.
|
9
|
+
#
|
10
|
+
# @param klass [Symbol or String] the class name.
|
11
|
+
# @param klass_modules [Hash, Array or String] the modules klass belongs to.
|
12
|
+
# @param options [Hash] the options that determine how klass_modules is transformed.
|
13
|
+
# @option options [Boolean] :camelize (false) determines whether or not both klass and klass_modules should be camelized.
|
14
|
+
# @option options [Boolean] :titleize (false) determines whether or not both klass and klass_modules should be titleized.
|
15
|
+
# @option options [Boolean] :class_titleize (false) determines whether or not klass names should be titleized.
|
16
|
+
# @option options [Boolean] :class_camelized (false) determines whether or not klass names should be camelized.
|
17
|
+
# @option options [Boolean] :module_titleize (false) determines whether or not klass_modules names should be titleized.
|
18
|
+
# @option options [Boolean] :module_camelized (false) determines whether or not klass_modules names should be camelized.
|
19
|
+
#
|
20
|
+
# @return [Class] the class constant. Can be used to call ClassConstant.constantize.
|
21
|
+
#
|
22
|
+
# @raise [NameError] if the constantized class string cannot be constantized; that is, if it is not
|
23
|
+
# a valid class constant.
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
#
|
27
|
+
# to_constantized_class("Authenticate", "Api") # => Api::Authenticate
|
28
|
+
# to_constantized_class(:Authenticate, [:Api, :AppName, :V1]) # => Api::AppName::V1::Authenticate
|
29
|
+
# to_constantized_class(:Authenticate, { :api :Api, app_name: :AppName, api_version: :V2 }) # => Api::AppName::V2::Authenticate
|
30
|
+
# to_constantized_class("authenticate", { :api :api, app_name: :app_name, api_version: :v1 }, { class_titleize: true, module_titleize: true }) # => Api::AppName::V1::Authenticate
|
31
|
+
#
|
32
|
+
def to_constantized_class(klass, klass_modules = [], options = {})
|
33
|
+
constantized_class_string = to_constantized_class_string(klass, klass_modules, options)
|
34
|
+
|
35
|
+
begin
|
36
|
+
constantized_class_string.constantize
|
37
|
+
rescue
|
38
|
+
raise NameError.new("\"#{constantized_class_string}\" is not a valid class constant.")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns a fully-qualified constantized class (as a string), given the klass and klass_modules.
|
43
|
+
#
|
44
|
+
# @param [Symbol or String] klass the class name.
|
45
|
+
# @param [Hash, Array or String] klass_modules the modules klass belongs to.
|
46
|
+
# @param [Hash] options the options that determine how klass_modules is transformed.
|
47
|
+
# @option options [Boolean] :class_titleize (false) Determines whether or not klass should be titleized.
|
48
|
+
# @option options [Boolean] :module_titleize (false) Determines whether or not klass_modules should be titleized.
|
49
|
+
#
|
50
|
+
# @return [String] the fully qualified class, which includes module(s) and class name.
|
51
|
+
#
|
52
|
+
# @example
|
53
|
+
#
|
54
|
+
# to_constantized_class_string("Authenticate", "Api") # => "Api::Authenticate"
|
55
|
+
# to_constantized_class_string(:Authenticate, [:Api, :AppName, :V1]) # => "Api::AppName::V1::Authenticate"
|
56
|
+
# to_constantized_class_string(:Authenticate, { :api :Api, app_name: :AppName, api_version: :V2 }) # => "Api::AppName::V2::Authenticate"
|
57
|
+
# to_constantized_class_string("authenticate", { :api :api, app_name: :app_name, api_version: :v1 }, { class_titleize: true, module_titleize: true }) # => "Api::AppName::V1::Authenticate"
|
58
|
+
#
|
59
|
+
def to_constantized_class_string(klass, klass_modules = [], options = {})
|
60
|
+
options = ensure_options(options)
|
61
|
+
klass_modules = to_modules_string(klass_modules, options)
|
62
|
+
klass_string = to_class_string(klass, options)
|
63
|
+
"#{klass_modules}#{klass_string}"
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns a string of modules that can be subsequently prepended to a class, to create a constantized class.
|
67
|
+
#
|
68
|
+
# @param [Hash, Array or String] klass_modules the modules a class belongs to.
|
69
|
+
# @param [Hash] options the options that determine how klass_modules is transformed.
|
70
|
+
# @option options [Boolean] :module_titleize (false) Determines whether or not klass_modules should be titleized.
|
71
|
+
#
|
72
|
+
# @return [String] a string of modules that can be subsequently prepended to a class, to create a constantized class.
|
73
|
+
#
|
74
|
+
# @raise [ArgumentError] if the klass_modules is not of type String, Hash or Array.
|
75
|
+
#
|
76
|
+
# @example
|
77
|
+
#
|
78
|
+
# to_modules_string("Api") # => "Api::"
|
79
|
+
# to_modules_string([:Api, :AppName, :V1]) # => "Api::AppName::V1::"
|
80
|
+
# to_modules_string({ :api :Api, app_name: :AppName, api_version: :V1 }) # => "Api::AppName::V1::"
|
81
|
+
# to_modules_string({ :api :api, app_name: :app_name, api_version: :v1 }, { module_titleize: true }) # => "Api::AppName::V1::"
|
82
|
+
#
|
83
|
+
def to_modules_string(klass_modules = [], options = {})
|
84
|
+
klass_modules = validate_klass_modules(klass_modules)
|
85
|
+
|
86
|
+
options = ensure_options(options)
|
87
|
+
|
88
|
+
klass_modules_string = ''
|
89
|
+
if !klass_modules.empty?
|
90
|
+
case klass_modules
|
91
|
+
when String
|
92
|
+
klass_modules_string = klass_modules
|
93
|
+
when Array
|
94
|
+
klass_modules_string = "#{klass_modules.join('::')}"
|
95
|
+
when Hash
|
96
|
+
klass_modules_string = ''
|
97
|
+
klass_modules.to_a.each_with_index.map { | value, index |
|
98
|
+
klass_modules_string = index == 0 ? value[1].to_s : "#{klass_modules_string}::#{value[1]}"
|
99
|
+
}
|
100
|
+
else
|
101
|
+
raise ArgumentError.new('Class modules is not a String, Hash or Array.')
|
102
|
+
end
|
103
|
+
klass_modules_string = klass_modules_string.split('::').map(&:titleize).join('::') if options[:module_titleize]
|
104
|
+
klass_modules_string = camelize(klass_modules_string) if options[:module_camelize]
|
105
|
+
klass_modules_string = klass_modules_string.trim_all
|
106
|
+
klass_modules_string = "#{klass_modules_string}::" unless klass_modules_string.empty?
|
107
|
+
end
|
108
|
+
|
109
|
+
klass_modules_string
|
110
|
+
end
|
111
|
+
|
112
|
+
# Returns the klass as a string after transformations have been applied.
|
113
|
+
#
|
114
|
+
# @param [Symbol or String] klass the class name to be transformed.
|
115
|
+
# @param [Hash] options the options that determine how klass will be transformed.
|
116
|
+
# @option options [Boolean] :class_titleize (false) Determines whether or not klass should be titleized.
|
117
|
+
#
|
118
|
+
# @return [String] the transformed class as a string.
|
119
|
+
#
|
120
|
+
# @example
|
121
|
+
#
|
122
|
+
# to_class_string("MyClass") # => "MyClass"
|
123
|
+
# to_class_string("myClass", { class_titleize: true }) # => "MyClass"
|
124
|
+
# to_class_string(:MyClass) # => "MyClass"
|
125
|
+
# to_class_string(:myClass, { class_titleize: true }) # => "MyClass"
|
126
|
+
#
|
127
|
+
def to_class_string(klass, options = {})
|
128
|
+
klass = validate_klass(klass, options)
|
129
|
+
if options[:class_titleize]
|
130
|
+
klass = klass.titleize
|
131
|
+
end
|
132
|
+
if options[:class_camelize]
|
133
|
+
klass = camelize(klass)
|
134
|
+
end
|
135
|
+
klass
|
136
|
+
end
|
137
|
+
|
138
|
+
# Transforms a route into a module string
|
139
|
+
#
|
140
|
+
# @return [String] the camelized token.
|
141
|
+
#
|
142
|
+
# @example
|
143
|
+
#
|
144
|
+
# camelize("/api/app/auth/v1") # => "Api::App::Auth::V1"
|
145
|
+
# camelize("/api/app_name/auth/v1") # => "Api::AppName::Auth::V1"
|
146
|
+
#
|
147
|
+
def camelize(token)
|
148
|
+
if !token.instance_of? String
|
149
|
+
raise ArgumentError.new('Token is not a String')
|
150
|
+
end
|
151
|
+
token = token.titlecase.camelize.sub(/^[:]*/,"").trim_all unless token.empty?
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
# @!visibility public
|
157
|
+
#
|
158
|
+
# Ensures options are initialized and valid before accessing them.
|
159
|
+
#
|
160
|
+
# @param [Hash] options the options that determine how processing and transformations will be handled.
|
161
|
+
# @option options [Boolean] :camelize (false) determines whether or not both class and module names should be camelized.
|
162
|
+
# @option options [Boolean] :titleize (false) determines whether or not both class and module names should be titleized.
|
163
|
+
# @option options [Boolean] :class_titleize (false) determines whether or not class names should be titleized.
|
164
|
+
# @option options [Boolean] :module_titleize (false) determines whether or not module names should be titleized.
|
165
|
+
# @option options [Boolean] :class_camelized (false) determines whether or not class names should be camelized.
|
166
|
+
# @option options [Boolean] :module_camelized (false) determines whether or not module names should be camelized.
|
167
|
+
#
|
168
|
+
# @return [Hash] the initialized, validated options.
|
169
|
+
#
|
170
|
+
def ensure_options(options)
|
171
|
+
options = {} unless options.instance_of? Hash
|
172
|
+
options = { camelize: false, titleize: false, class_titleize: false, module_titleize: false, class_camelize: false, module_camelize: false}.merge(options)
|
173
|
+
|
174
|
+
if options[:camelize]
|
175
|
+
options[:class_camelize] = options[:module_camelize] = true
|
176
|
+
end
|
177
|
+
|
178
|
+
if options[:titleize]
|
179
|
+
options[:class_titleize] = options[:module_titleize] = true
|
180
|
+
end
|
181
|
+
|
182
|
+
options
|
183
|
+
end
|
184
|
+
|
185
|
+
# @!visibility public
|
186
|
+
#
|
187
|
+
# Validates klass and returns klass as a string after all blanks have been removed using klass.gsub(/\s+/, "").
|
188
|
+
#
|
189
|
+
# @param [Symbol or String] klass the class name to be validated. klass cannot be empty?
|
190
|
+
#
|
191
|
+
# @return [String] the validated class as a string with blanks removed.
|
192
|
+
#
|
193
|
+
# @raise [ArgumentError] if the klass is empty? or not of type String or Symbol.
|
194
|
+
#
|
195
|
+
# @example
|
196
|
+
#
|
197
|
+
# validate_klass(" My Class ") # => "MyClass"
|
198
|
+
# validate_klass(:MyClass) # => "MyClass"
|
199
|
+
#
|
200
|
+
def validate_klass(klass, options)
|
201
|
+
if !(klass.is_a?(Symbol) || klass.is_a?(String))
|
202
|
+
raise ArgumentError.new('Class is not a String or Symbol. Class must equal the name of the SimpleCommand to call in the form of a String or Symbol.')
|
203
|
+
end
|
204
|
+
|
205
|
+
klass = klass.to_s.strip
|
206
|
+
|
207
|
+
if klass.empty?
|
208
|
+
raise ArgumentError.new('Class is empty?')
|
209
|
+
end
|
210
|
+
|
211
|
+
klass
|
212
|
+
end
|
213
|
+
|
214
|
+
# @!visibility public
|
215
|
+
#
|
216
|
+
# Validates and returns klass_modules.
|
217
|
+
#
|
218
|
+
# @param [Symbol, Array or String] klass_modules the module(s) to be validated.
|
219
|
+
#
|
220
|
+
# @return [Symbol, Array or String] the validated module(s).
|
221
|
+
#
|
222
|
+
# @raise [ArgumentError] if the klass_modules is not of type String, Hash or Array.
|
223
|
+
#
|
224
|
+
# @example
|
225
|
+
#
|
226
|
+
# validate_modules(" Module ") # => " Module "
|
227
|
+
# validate_modules(:Module) # => "Module"
|
228
|
+
# validate_module("ModuleA::ModuleB") # => "ModuleA::ModuleB"
|
229
|
+
#
|
230
|
+
def validate_klass_modules(klass_modules)
|
231
|
+
return {} if klass_modules.nil? || (klass_modules.respond_to?(:empty?) && klass_modules.empty?)
|
232
|
+
|
233
|
+
if !klass_modules.instance_of?(String) && !klass_modules.instance_of?(Hash) && !klass_modules.instance_of?(Array)
|
234
|
+
raise ArgumentError.new('Class modules is not a String, Hash or Array.')
|
235
|
+
end
|
236
|
+
|
237
|
+
klass_modules
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
require 'rake'
|
2
|
+
# Tasks to play with certain klass_transform module members
|
3
|
+
|
4
|
+
|
5
|
+
desc 'Test to_class_string'
|
6
|
+
task :to_class_string do
|
7
|
+
require "colorize"
|
8
|
+
require "simple_command_dispatcher"
|
9
|
+
|
10
|
+
class Test
|
11
|
+
prepend SimpleCommand::KlassTransform
|
12
|
+
end
|
13
|
+
|
14
|
+
puts "Testing SimpleCommand::KlassTransform#to_class_String...\n".cyan
|
15
|
+
puts "Enter a blank line to exit.".cyan
|
16
|
+
loop do
|
17
|
+
puts "\nUsage: \"-c [class], -o [options]\" where class = String or Symbol and options = Hash".cyan
|
18
|
+
puts "\n\t Examples:"
|
19
|
+
print "\n\t\t-c \"my_class\" -o { class_camelize: true }".cyan
|
20
|
+
print "\n\t\t-c :MyClass -o { class_camelize: false }".cyan
|
21
|
+
|
22
|
+
print "\n=> "
|
23
|
+
|
24
|
+
input = STDIN.gets.chomp
|
25
|
+
break if input.empty?
|
26
|
+
|
27
|
+
input = clean_input(input)
|
28
|
+
|
29
|
+
klass = get_option_class(input)
|
30
|
+
if klass.nil? || klass.empty?
|
31
|
+
print "=> Error: Class not a String or Symbol\n".red
|
32
|
+
next
|
33
|
+
elsif !klass.is_a?(String) && ! klass.is_a?(Symbol)
|
34
|
+
print "=> Error: Class not a String or Symbol\n".red
|
35
|
+
next
|
36
|
+
end
|
37
|
+
|
38
|
+
options_hash = get_options_hash(input)
|
39
|
+
|
40
|
+
options_hash = { class_camelize: true }.merge(options_hash || {})
|
41
|
+
|
42
|
+
puts "\nCalling to_class_string with the following parameters: class: #{klass.class}, options: #{options_hash.class}".cyan
|
43
|
+
|
44
|
+
print "\nSuccess: => #to_class_String(#{klass}, #{options_hash}) => #{Test.new.to_class_string(klass, options_hash)}\n".green
|
45
|
+
end
|
46
|
+
|
47
|
+
print
|
48
|
+
print 'Done'.yellow
|
49
|
+
print
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
desc 'Test to_modules_string'
|
54
|
+
task :to_modules_string do
|
55
|
+
require "colorize"
|
56
|
+
require "simple_command_dispatcher"
|
57
|
+
|
58
|
+
class Test
|
59
|
+
prepend SimpleCommand::KlassTransform
|
60
|
+
end
|
61
|
+
|
62
|
+
puts "Testing SimpleCommand::KlassTransform#to_modules_string...\n".cyan
|
63
|
+
puts "Enter a blank line to exit.".cyan
|
64
|
+
loop do
|
65
|
+
puts "\nUsage: \"-m [modules], -o [options]\" where modules = Hash, Array or String and options = Hash".cyan
|
66
|
+
puts "\n\t Examples:"
|
67
|
+
print "\n\t\t-m \"Module1::Module2::Module3\" -o { modules_camelize: false }".cyan
|
68
|
+
print "\n\t\t-m [:module1, :module2, :module3] -o { modules_camelize: true }".cyan
|
69
|
+
print "\n\t\t-m {api: :api, app: :crazy_buttons, version: :v1} -o { modules_camelize: true }".cyan
|
70
|
+
|
71
|
+
print "\n=> "
|
72
|
+
|
73
|
+
input = STDIN.gets.chomp
|
74
|
+
break if input.empty?
|
75
|
+
|
76
|
+
input = clean_input(input)
|
77
|
+
|
78
|
+
modules = get_option_modules(input)
|
79
|
+
p modules.class
|
80
|
+
if modules.nil? || modules.empty?
|
81
|
+
print "=> Error: Modules not a Hash, Array or String\n".red
|
82
|
+
next
|
83
|
+
elsif !modules.is_a?(Hash) && !modules.is_a?(Array) && !modules.is_a?(String)
|
84
|
+
print "=> Error: Modules not a Hash, Array or String\n".red
|
85
|
+
next
|
86
|
+
end
|
87
|
+
|
88
|
+
options_hash = get_options_hash(input)
|
89
|
+
options_hash = { module_camelize: true }.merge(options_hash || {})
|
90
|
+
|
91
|
+
puts "\nCalling to_modules_string with the following parameters: modules: #{modules}, type: #{modules.class}, options: #{options_hash}, type: #{options_hash.class}...".cyan
|
92
|
+
|
93
|
+
puts "\nSuccess: => #to_modules_string(#{modules}, #{options_hash}) => #{Test.new.to_modules_string(modules, options_hash)}\n".green
|
94
|
+
end
|
95
|
+
|
96
|
+
print
|
97
|
+
print 'Done'.yellow
|
98
|
+
print
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
#
|
104
|
+
#
|
105
|
+
#
|
106
|
+
|
107
|
+
desc 'Test to_constantized_class_string'
|
108
|
+
task :to_constantized_class_string do
|
109
|
+
require "colorize"
|
110
|
+
require "simple_command_dispatcher"
|
111
|
+
|
112
|
+
class Test
|
113
|
+
prepend SimpleCommand::KlassTransform
|
114
|
+
end
|
115
|
+
|
116
|
+
puts "Testing SimpleCommand::KlassTransform#to_constantized_class_string...\n".cyan
|
117
|
+
puts "Enter a blank line to exit.".cyan
|
118
|
+
loop do
|
119
|
+
puts "\nUsage: \"-c [class] -m [modules], -o [options]\" where class = Symbol or String, modules = Hash, Array or String and options = Hash".cyan
|
120
|
+
puts "\n\t Examples:"
|
121
|
+
print "\n\t\t-c :MyClass -m \"Module1::Module2::Module3\" -o { modules_camelize: false }".cyan
|
122
|
+
print "\n\t\t-c \"my_class\" -m [:module1, :module2, :module3] -o { modules_camelize: true }".cyan
|
123
|
+
print "\n\t\t-c :myClass -m {api: :api, app: :crazy_buttons, version: :v1} -o { modules_camelize: true }".cyan
|
124
|
+
|
125
|
+
print "\n=> "
|
126
|
+
|
127
|
+
input = STDIN.gets.chomp
|
128
|
+
break if input.empty?
|
129
|
+
|
130
|
+
input = clean_input(input)
|
131
|
+
|
132
|
+
klass = get_option_class(input)
|
133
|
+
if klass.nil? || klass.empty?
|
134
|
+
print "=> Error: Class not a String or Symbol\n".red
|
135
|
+
next
|
136
|
+
elsif !klass.is_a?(String) && ! klass.is_a?(Symbol)
|
137
|
+
print "=> Error: Class not a String or Symbol\n".red
|
138
|
+
next
|
139
|
+
end
|
140
|
+
|
141
|
+
modules = get_option_modules(input)
|
142
|
+
if modules.nil? || modules.empty?
|
143
|
+
print "=> Error: Modules not a Hash, Array or String\n".red
|
144
|
+
next
|
145
|
+
elsif !modules.is_a?(Hash) && !modules.is_a?(Array) && !modules.is_a?(String)
|
146
|
+
print "=> Error: Modules not a Hash, Array or String\n".red
|
147
|
+
next
|
148
|
+
end
|
149
|
+
|
150
|
+
options_hash = get_options_hash(input)
|
151
|
+
options_hash = { class_camelize: true, module_camelize: true }.merge(options_hash || {})
|
152
|
+
|
153
|
+
puts "\nCalling to_constantized_class_string with the following parameters: class: #{klass}, type: #{klass.class}, modules: #{modules}, type: #{modules.class}, options: #{options_hash}, type: #{options_hash.class}...".cyan
|
154
|
+
|
155
|
+
puts "\nSuccess: => #to_constantized_class_string(#{klass}, #{modules}, #{options_hash}) => #{Test.new.to_constantized_class_string(klass, modules, options_hash)}\n".green
|
156
|
+
end
|
157
|
+
|
158
|
+
print
|
159
|
+
print 'Done'.yellow
|
160
|
+
print
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
#
|
165
|
+
# Helper methods
|
166
|
+
#
|
167
|
+
|
168
|
+
def clean_input(input)
|
169
|
+
# Place a space before every dash that is not separated by a space.
|
170
|
+
input = input.gsub(/-c/, " -c ").gsub(/-m/, " -m ").gsub(/-o/, " -o ")
|
171
|
+
input = input.gsub(/"/, " \" ").gsub(/\s+/, " ").strip
|
172
|
+
|
173
|
+
puts "=> keyboard input after clean_input => #{input}".magenta
|
174
|
+
|
175
|
+
input
|
176
|
+
end
|
177
|
+
|
178
|
+
def get_option_class(options)
|
179
|
+
if options.nil? || options.empty?
|
180
|
+
return ""
|
181
|
+
end
|
182
|
+
options_options = options[/-c\s*([:"].+?(["\s-]||$?))($|\s+|-)/m,1]
|
183
|
+
|
184
|
+
return "" if options_options.nil?
|
185
|
+
|
186
|
+
options_options = options_options.gsub(/("|')+/, "")
|
187
|
+
klass = options_options.strip.gsub(/\s+/, " ")
|
188
|
+
|
189
|
+
puts "=> class after get_option_class => #{klass}".magenta
|
190
|
+
|
191
|
+
klass
|
192
|
+
end
|
193
|
+
|
194
|
+
def get_options_hash(options)
|
195
|
+
if options.nil? || options.empty?
|
196
|
+
return {}
|
197
|
+
end
|
198
|
+
options_options = options[/-o\s*([{].+?([}\s-]$?))($|\s+|-)/m,1]
|
199
|
+
|
200
|
+
return {} if options_options.nil?
|
201
|
+
|
202
|
+
puts "=> options after get_options_hash => #{options_options}".magenta
|
203
|
+
|
204
|
+
begin
|
205
|
+
eval(options_options)
|
206
|
+
rescue
|
207
|
+
{}
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def get_option_modules(modules)
|
212
|
+
if modules.nil? || modules.empty?
|
213
|
+
return ""
|
214
|
+
end
|
215
|
+
|
216
|
+
extracted_modules = modules[/-m\s*([\[{"].+?(["}\]\s-]$?))($|\s+|-)/m,1]
|
217
|
+
|
218
|
+
return "" if extracted_modules.nil?
|
219
|
+
|
220
|
+
extracted_modules = extracted_modules.strip.gsub(/\s+/, " ")
|
221
|
+
|
222
|
+
puts "=> modules after get_option_modules => #{extracted_modules}".magenta
|
223
|
+
|
224
|
+
begin
|
225
|
+
eval(extracted_modules)
|
226
|
+
rescue
|
227
|
+
""
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
|
232
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'simple_command_dispatcher/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "simple_command_dispatcher"
|
8
|
+
spec.version = SimpleCommand::Dispatcher::VERSION
|
9
|
+
spec.authors = ["Gene M. Angelo, Jr."]
|
10
|
+
spec.email = ["public.gma@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Provides a way to dispatch simple_command ruby gem SimpleCommands (service objects) in a more dynamic manner within your service API. Ideal for rails-api.}
|
13
|
+
spec.description = %q{Within a services API (rails-api for instance), you may have a need to execute different SimpleCommands
|
14
|
+
based on one or more factors: multiple application, API version, user type, user credentials, etc. For example,
|
15
|
+
your service API may need to execute either Api::Auth::V1::AuthenticateCommand.call(...) or Api::Auth::V2::AuthenticateCommand.call(...)
|
16
|
+
based on the API version. simple_command_dispatcher allows you to make one call to execute both command dynamically.}.gsub(/\s+/,' ')
|
17
|
+
spec.homepage = "https://github.com/gangelo/simple_command_dispatcher"
|
18
|
+
spec.license = "MIT"
|
19
|
+
|
20
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
21
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
22
|
+
#if spec.respond_to?(:metadata)
|
23
|
+
# spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
|
24
|
+
#else
|
25
|
+
# raise "RubyGems 2.0 or newer is required to protect against " \
|
26
|
+
# "public gem pushes."
|
27
|
+
#end
|
28
|
+
|
29
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
30
|
+
f.match(%r{^(test|spec|features)/})
|
31
|
+
end
|
32
|
+
spec.bindir = "exe"
|
33
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
34
|
+
spec.require_paths = ["lib"]
|
35
|
+
|
36
|
+
spec.add_development_dependency "bundler", "~> 1.13"
|
37
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
38
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
39
|
+
|
40
|
+
spec.add_development_dependency "yard"
|
41
|
+
spec.add_development_dependency "rdoc"
|
42
|
+
spec.add_development_dependency "colorize"
|
43
|
+
|
44
|
+
spec.required_ruby_version = '>= 2.2.2'
|
45
|
+
spec.add_runtime_dependency 'activesupport', '~> 5.0', '>= 5.0.0.1'
|
46
|
+
|
47
|
+
spec.add_runtime_dependency 'simple_command', '>= 0.0.9'
|
48
|
+
end
|
metadata
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simple_command_dispatcher
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gene M. Angelo, Jr.
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-11-02 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: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: yard
|
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: rdoc
|
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: colorize
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: activesupport
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '5.0'
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: 5.0.0.1
|
107
|
+
type: :runtime
|
108
|
+
prerelease: false
|
109
|
+
version_requirements: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - "~>"
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '5.0'
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: 5.0.0.1
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: simple_command
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: 0.0.9
|
124
|
+
type: :runtime
|
125
|
+
prerelease: false
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: 0.0.9
|
131
|
+
description: 'Within a services API (rails-api for instance), you may have a need
|
132
|
+
to execute different SimpleCommands based on one or more factors: multiple application,
|
133
|
+
API version, user type, user credentials, etc. For example, your service API may
|
134
|
+
need to execute either Api::Auth::V1::AuthenticateCommand.call(...) or Api::Auth::V2::AuthenticateCommand.call(...)
|
135
|
+
based on the API version. simple_command_dispatcher allows you to make one call
|
136
|
+
to execute both command dynamically.'
|
137
|
+
email:
|
138
|
+
- public.gma@gmail.com
|
139
|
+
executables: []
|
140
|
+
extensions: []
|
141
|
+
extra_rdoc_files: []
|
142
|
+
files:
|
143
|
+
- ".gitignore"
|
144
|
+
- ".rspec"
|
145
|
+
- ".ruby-version"
|
146
|
+
- ".travis.yml"
|
147
|
+
- CHANGELOG.mb
|
148
|
+
- CODE_OF_CONDUCT.md
|
149
|
+
- Gemfile
|
150
|
+
- LICENSE.txt
|
151
|
+
- README.md
|
152
|
+
- Rakefile
|
153
|
+
- bin/console
|
154
|
+
- bin/setup
|
155
|
+
- lib/core_extensions/string.rb
|
156
|
+
- lib/simple_command_dispatcher.rb
|
157
|
+
- lib/simple_command_dispatcher/klass_transform.rb
|
158
|
+
- lib/simple_command_dispatcher/version.rb
|
159
|
+
- lib/tasks/simple_command_dispatcher_sandbox.rake
|
160
|
+
- simple_command_dispatcher.gemspec
|
161
|
+
homepage: https://github.com/gangelo/simple_command_dispatcher
|
162
|
+
licenses:
|
163
|
+
- MIT
|
164
|
+
metadata: {}
|
165
|
+
post_install_message:
|
166
|
+
rdoc_options: []
|
167
|
+
require_paths:
|
168
|
+
- lib
|
169
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: 2.2.2
|
174
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
175
|
+
requirements:
|
176
|
+
- - ">="
|
177
|
+
- !ruby/object:Gem::Version
|
178
|
+
version: '0'
|
179
|
+
requirements: []
|
180
|
+
rubyforge_project:
|
181
|
+
rubygems_version: 2.6.7
|
182
|
+
signing_key:
|
183
|
+
specification_version: 4
|
184
|
+
summary: Provides a way to dispatch simple_command ruby gem SimpleCommands (service
|
185
|
+
objects) in a more dynamic manner within your service API. Ideal for rails-api.
|
186
|
+
test_files: []
|