lightrail 0.0.1 → 0.99.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.
- data/.gitignore +7 -0
- data/.rspec +4 -0
- data/.travis.yml +11 -0
- data/CHANGES.md +8 -0
- data/Gemfile +3 -0
- data/LICENSE +19 -0
- data/README.md +205 -0
- data/Rakefile +5 -0
- data/bin/lightrail +2 -1
- data/lib/lightrail.rb +2 -0
- data/lib/lightrail/action_controller/metal.rb +0 -2
- data/lib/lightrail/cli.rb +16 -0
- data/lib/lightrail/commands/application.rb +26 -0
- data/lib/lightrail/generators.rb +323 -0
- data/lib/lightrail/generators/app_base.rb +281 -0
- data/lib/lightrail/generators/app_generator.rb +299 -0
- data/lib/lightrail/generators/base.rb +378 -0
- data/lib/lightrail/generators/templates/Gemfile +25 -0
- data/lib/lightrail/generators/templates/README +259 -0
- data/lib/lightrail/generators/templates/Rakefile +7 -0
- data/lib/lightrail/generators/templates/app/assets/images/rails.png +0 -0
- data/lib/lightrail/generators/templates/app/assets/javascripts/application.js.tt +17 -0
- data/lib/lightrail/generators/templates/app/assets/stylesheets/application.css +13 -0
- data/lib/lightrail/generators/templates/app/controllers/application_controller.rb +3 -0
- data/lib/lightrail/generators/templates/app/helpers/application_helper.rb +2 -0
- data/lib/lightrail/generators/templates/app/mailers/.empty_directory +0 -0
- data/lib/lightrail/generators/templates/app/models/.empty_directory +0 -0
- data/lib/lightrail/generators/templates/app/views/layouts/application.html.erb.tt +14 -0
- data/lib/lightrail/generators/templates/config.ru +4 -0
- data/lib/lightrail/generators/templates/config/application.rb +67 -0
- data/lib/lightrail/generators/templates/config/boot.rb +6 -0
- data/lib/lightrail/generators/templates/config/databases/frontbase.yml +31 -0
- data/lib/lightrail/generators/templates/config/databases/ibm_db.yml +86 -0
- data/lib/lightrail/generators/templates/config/databases/jdbc.yml +62 -0
- data/lib/lightrail/generators/templates/config/databases/jdbcmysql.yml +33 -0
- data/lib/lightrail/generators/templates/config/databases/jdbcpostgresql.yml +43 -0
- data/lib/lightrail/generators/templates/config/databases/jdbcsqlite3.yml +20 -0
- data/lib/lightrail/generators/templates/config/databases/mysql.yml +51 -0
- data/lib/lightrail/generators/templates/config/databases/oracle.yml +39 -0
- data/lib/lightrail/generators/templates/config/databases/postgresql.yml +55 -0
- data/lib/lightrail/generators/templates/config/databases/sqlite3.yml +25 -0
- data/lib/lightrail/generators/templates/config/environment.rb +5 -0
- data/lib/lightrail/generators/templates/config/environments/development.rb.tt +38 -0
- data/lib/lightrail/generators/templates/config/environments/production.rb.tt +76 -0
- data/lib/lightrail/generators/templates/config/environments/test.rb.tt +36 -0
- data/lib/lightrail/generators/templates/config/initializers/backtrace_silencers.rb +7 -0
- data/lib/lightrail/generators/templates/config/initializers/inflections.rb +15 -0
- data/lib/lightrail/generators/templates/config/initializers/mime_types.rb +5 -0
- data/lib/lightrail/generators/templates/config/initializers/secret_token.rb.tt +7 -0
- data/lib/lightrail/generators/templates/config/initializers/session_store.rb.tt +8 -0
- data/lib/lightrail/generators/templates/config/initializers/wrap_parameters.rb.tt +16 -0
- data/lib/lightrail/generators/templates/config/locales/en.yml +5 -0
- data/lib/lightrail/generators/templates/config/routes.rb +58 -0
- data/lib/lightrail/generators/templates/db/seeds.rb.tt +7 -0
- data/lib/lightrail/generators/templates/gitignore +16 -0
- data/lib/lightrail/generators/templates/public/404.html +26 -0
- data/lib/lightrail/generators/templates/public/422.html +26 -0
- data/lib/lightrail/generators/templates/public/500.html +25 -0
- data/lib/lightrail/generators/templates/public/favicon.ico +0 -0
- data/lib/lightrail/generators/templates/public/index.html +241 -0
- data/lib/lightrail/generators/templates/public/robots.txt +5 -0
- data/lib/lightrail/generators/templates/public/stylesheets/.empty_directory +0 -0
- data/lib/lightrail/generators/templates/script/rails +5 -0
- data/lib/lightrail/generators/templates/test/fixtures/.empty_directory +0 -0
- data/lib/lightrail/generators/templates/test/functional/.empty_directory +0 -0
- data/lib/lightrail/generators/templates/test/integration/.empty_directory +0 -0
- data/lib/lightrail/generators/templates/test/performance/browsing_test.rb +12 -0
- data/lib/lightrail/generators/templates/test/test_helper.rb +15 -0
- data/lib/lightrail/generators/templates/test/unit/.empty_directory +0 -0
- data/lib/lightrail/version.rb +1 -1
- data/lightrail.gemspec +23 -0
- data/logo.png +0 -0
- data/spec/lightrail/action_controller/metal_spec.rb +8 -0
- data/spec/spec_helper.rb +1 -0
- data/tasks/rspec.task +7 -0
- metadata +105 -13
- data/lib/lightrail/action_controller/param.rb +0 -12
- data/lib/lightrail/core_ext/regexp.rb +0 -7
- data/lib/lightrail/encryptor.rb +0 -62
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CHANGES.md
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2011 José Valim, Carl Lerche, and contributors
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
7
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
8
|
+
so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+

|
2
|
+
============
|
3
|
+
[](http://travis-ci.org/lightness/lightrail)
|
4
|
+
|
5
|
+
Lightrail is a minimalist Rails 3 stack for apps that serve primarily APIs,
|
6
|
+
with a particular focus on JSON APIs. If [Sinatra][sinatra] doesn't give
|
7
|
+
you enough, but [Rails][rails] is still too much, Lightrail is for you.
|
8
|
+
|
9
|
+
[sinatra]: http://www.sinatrarb.com/
|
10
|
+
[rails]: http://rubyonrails.org/
|
11
|
+
|
12
|
+
Join the mailing list by sending a message to: lightrail@librelist.com
|
13
|
+
|
14
|
+
Getting Started
|
15
|
+
---------------
|
16
|
+
|
17
|
+
Install the lightrail gem:
|
18
|
+
|
19
|
+
`gem install lightrail`
|
20
|
+
|
21
|
+
Like Rails, installing the lightrail gem will install a command line utility
|
22
|
+
called 'lightrail'. This command is in fact identical to the 'rails' command,
|
23
|
+
but tweaked for Lightrail defaults instead of Rails defaults.
|
24
|
+
|
25
|
+
You can use 'lightrail' to create a new application skeleton just like Rails:
|
26
|
+
|
27
|
+
`lightrail new myapp`
|
28
|
+
|
29
|
+
The skeleton application that Lightrail generates is identical to a standard
|
30
|
+
Rails application, with only these changes:
|
31
|
+
|
32
|
+
* Gemfile pulls in lightrail instead of rails
|
33
|
+
* application.rb pulls in lightrail instead of rails
|
34
|
+
* ApplicationController descends from Lightrail::ActionController::Metal
|
35
|
+
instead of ActionController::Base. ActionView is not used or installed.
|
36
|
+
|
37
|
+
Once you've created your application, run:
|
38
|
+
|
39
|
+
`lightrail server`
|
40
|
+
|
41
|
+
to launch a web server in the development environment (just like Rails!)
|
42
|
+
|
43
|
+
You can convert an existing Rails 3 application to a Lightrail application
|
44
|
+
by retrofitting the changes mentioned above.
|
45
|
+
|
46
|
+
Lightrail::ActionController::Metal
|
47
|
+
----------------------------------
|
48
|
+
|
49
|
+
A lightweight `ActionController::Base` replacement designed for when APIs are your main concern.
|
50
|
+
It removes several irrelevant modules and also provides following additional behaviors:
|
51
|
+
|
52
|
+
* `halt` stops rendering at any point using Ruby's throw/catch mechanism.
|
53
|
+
Any option passed to `halt` is forwarded to the `render` method
|
54
|
+
|
55
|
+
* `render :errors` is a renderer extension that allows you to easily render an error as JSON.
|
56
|
+
It is simply a convenience method for `render json: errors, status: 422`.
|
57
|
+
With the `halt` mechanism above you'll see this common pattern: `halt errors: { request: "invalid" }`.
|
58
|
+
|
59
|
+
|
60
|
+
Lightrail::Wrapper
|
61
|
+
------------------
|
62
|
+
|
63
|
+
Wrappers are Lightrail's view replacement, and handle JSON serialization of your models.
|
64
|
+
Instead of having a monster `#to_json` method in your model, you can factor that into a
|
65
|
+
wrapper instead, and wrappers will automatically take care of many additional JSON
|
66
|
+
serialization concerns for you.
|
67
|
+
|
68
|
+
**Creating A Wrapper**
|
69
|
+
|
70
|
+
Each model needs to have a wrapper in order to be rendered as JSON.
|
71
|
+
Instead of using several options (like `:only`, `:method`, and friends) it expects you to explicitly define the hash to returned through the `view` method.
|
72
|
+
Here is an example:
|
73
|
+
|
74
|
+
``` ruby
|
75
|
+
class AccountWrapper < Lightrail::Wrapper::Model
|
76
|
+
has_one :credit_card
|
77
|
+
has_one :subscription
|
78
|
+
|
79
|
+
def view
|
80
|
+
attributes = [:id, :name, :user_id]
|
81
|
+
|
82
|
+
if owner?
|
83
|
+
attributes.concat [:billing_address, :billing_country]
|
84
|
+
end
|
85
|
+
|
86
|
+
# Shortcut for account.attributes.slice()
|
87
|
+
hash = account.slice(*attributes)
|
88
|
+
hash[:owner] = owner?
|
89
|
+
hash
|
90
|
+
end
|
91
|
+
|
92
|
+
# Whenever an association method is defined explicitly
|
93
|
+
# it is given higher preference. That said, whenever
|
94
|
+
# including a credit_card, it will invoke this method
|
95
|
+
# instead of calling account.credit_card directly.
|
96
|
+
def credit_card
|
97
|
+
account.credit_card if owner?
|
98
|
+
end
|
99
|
+
|
100
|
+
protected
|
101
|
+
|
102
|
+
def owner?
|
103
|
+
account.owners.include? scope
|
104
|
+
end
|
105
|
+
end
|
106
|
+
```
|
107
|
+
|
108
|
+
A wrapper is initialized with two arguments:
|
109
|
+
the `resource` which is the `account` in this case and a `scope`.
|
110
|
+
In most cases the scope is the `current_user`.
|
111
|
+
The idea of having a scope inside the wrapper is to be able to properly handle permissions when exposing a resource.
|
112
|
+
In the example above you can notice that a `credit_card` is only exposed if the user actually owns the account being showed.
|
113
|
+
Billing information is also hidden except when the user is an `owner?`.
|
114
|
+
|
115
|
+
Another convenience is that the wrapper can automatically handle associations.
|
116
|
+
Associations, when exposed are not nested exposed but rather flat in the JSON here is an example:
|
117
|
+
|
118
|
+
|
119
|
+
``` json
|
120
|
+
{
|
121
|
+
"account": {
|
122
|
+
"id": 1,
|
123
|
+
"name": "Main",
|
124
|
+
"user_id": null,
|
125
|
+
"credit_card_id": 1
|
126
|
+
},
|
127
|
+
|
128
|
+
"credit_cards": {
|
129
|
+
"id": 1,
|
130
|
+
"last_4": "3232"
|
131
|
+
}
|
132
|
+
}
|
133
|
+
```
|
134
|
+
|
135
|
+
In order to render a wrapper with its associations you can use the `render` method and pass the associations explicitly:
|
136
|
+
|
137
|
+
``` ruby
|
138
|
+
AccountWrapper.new(@account, current_user).render include: [:credit_card]
|
139
|
+
```
|
140
|
+
|
141
|
+
Although most of the times this will be done automatically by the controller.
|
142
|
+
|
143
|
+
**Using The Wrapper In The Controller**
|
144
|
+
|
145
|
+
`Lightrail::Wrapper::Controller` provides several facilities to use wrappers from the controller:
|
146
|
+
|
147
|
+
* `#json(resources)` is the main method.
|
148
|
+
Given a resource (or an array of resources) it will find the proper wrapper and render it.
|
149
|
+
Any include given at `params[:include]` will be validated and passed to the underlying wrapper.
|
150
|
+
Consider the following action:
|
151
|
+
|
152
|
+
``` ruby
|
153
|
+
def last
|
154
|
+
json Account.last
|
155
|
+
end
|
156
|
+
```
|
157
|
+
|
158
|
+
When accessed as `/accounts/last` it won't return any credit card or subscription resource in the JSON, unless it is given explicitly as `/accounts/last?include=credit_cards,subscriptions` (in plural).
|
159
|
+
|
160
|
+
In order for the `json` method to work, a `wrapper_scope` needs to be defined.
|
161
|
+
You can usually define it in your `ApplicationController` as follow:
|
162
|
+
|
163
|
+
``` ruby
|
164
|
+
def wrapper_scope
|
165
|
+
current_user
|
166
|
+
end
|
167
|
+
```
|
168
|
+
|
169
|
+
* `errors(resource)` is a method that makes pair with `json(resource)`.
|
170
|
+
It basically receives a resource and render its errors.
|
171
|
+
For instance, `errors(account)` will return `:errors => { :account => account.errors }`;
|
172
|
+
|
173
|
+
* `wrap_array(resources)` as the `json` method accepts extra associations to be included through `params[:include]` we need to be careful to not do `N+1` db queries.
|
174
|
+
This can be fixed by using the `wrap_array` method that will automatically wrap the given array and preload all associations.
|
175
|
+
For instance, you want will to do this in your `index` actions:
|
176
|
+
|
177
|
+
``` ruby
|
178
|
+
def index
|
179
|
+
json wrap_array(current_user.accounts.active.all)
|
180
|
+
end
|
181
|
+
```
|
182
|
+
|
183
|
+
**Active Record Extensions**
|
184
|
+
|
185
|
+
`Lightrail::Wrapper` provides one Active Record extension method called `#slice()`.
|
186
|
+
In order to understand what it does, it is easier to look at the source:
|
187
|
+
|
188
|
+
``` ruby
|
189
|
+
def slice(*keys)
|
190
|
+
keys.map! { |key| key.to_s }
|
191
|
+
attributes.slice(*keys)
|
192
|
+
end
|
193
|
+
```
|
194
|
+
|
195
|
+
This method was used in the example showed above.
|
196
|
+
|
197
|
+
|
198
|
+
config.lightrail.*
|
199
|
+
------------------
|
200
|
+
|
201
|
+
Lightrail adds a `config.lightrail` namespace to your application with two main methods:
|
202
|
+
|
203
|
+
* `remove_session_middlewares!` removes `ActionDispatch::Cookies`,
|
204
|
+
`ActionDispatch::Session::CookieStore` and `ActionDispatch::Flash` middlewares.
|
205
|
+
* `remove_browser_middlewares!` removes the `ActionDispatch::BestStandardsSupport` middleware.
|
data/Rakefile
ADDED
data/bin/lightrail
CHANGED
@@ -1 +1,2 @@
|
|
1
|
-
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require "lightrail/cli"
|
data/lib/lightrail.rb
ADDED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'lightrail/action_controller/haltable'
|
2
|
-
require 'lightrail/action_controller/param'
|
3
2
|
|
4
3
|
module Lightrail
|
5
4
|
module ActionController
|
@@ -36,7 +35,6 @@ module Lightrail
|
|
36
35
|
include ::ActionController::Instrumentation
|
37
36
|
|
38
37
|
include Haltable
|
39
|
-
include Param
|
40
38
|
end
|
41
39
|
end
|
42
40
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
require 'rails/script_rails_loader'
|
3
|
+
|
4
|
+
# If we are inside a Rails application this method performs an exec and thus
|
5
|
+
# the rest of this script is not run.
|
6
|
+
Rails::ScriptRailsLoader.exec_script_rails!
|
7
|
+
|
8
|
+
require 'rails/ruby_version_check'
|
9
|
+
Signal.trap("INT") { puts; exit(1) }
|
10
|
+
|
11
|
+
if ARGV.first == 'plugin'
|
12
|
+
ARGV.shift
|
13
|
+
require 'rails/commands/plugin_new'
|
14
|
+
else
|
15
|
+
require 'lightrail/commands/application'
|
16
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'lightrail/version'
|
2
|
+
|
3
|
+
if ['--version', '-v'].include?(ARGV.first)
|
4
|
+
puts "Lightrail #{Lightrail::VERSION}"
|
5
|
+
exit(0)
|
6
|
+
end
|
7
|
+
|
8
|
+
if ARGV.first != "new"
|
9
|
+
ARGV[0] = "--help"
|
10
|
+
else
|
11
|
+
ARGV.shift
|
12
|
+
railsrc = File.join(File.expand_path("~"), ".railsrc")
|
13
|
+
if File.exist?(railsrc)
|
14
|
+
extra_args_string = File.open(railsrc).read
|
15
|
+
extra_args = extra_args_string.split(/\n+/).map {|l| l.split}.flatten
|
16
|
+
puts "Using #{extra_args.join(" ")} from #{railsrc}"
|
17
|
+
ARGV << extra_args
|
18
|
+
ARGV.flatten!
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'rubygems' if ARGV.include?("--dev")
|
23
|
+
require 'lightrail/generators'
|
24
|
+
require 'lightrail/generators/app_generator'
|
25
|
+
|
26
|
+
Lightrail::Generators::AppGenerator.start
|
@@ -0,0 +1,323 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/core_ext/object/blank'
|
3
|
+
require 'active_support/core_ext/kernel/singleton_class'
|
4
|
+
require 'active_support/core_ext/array/extract_options'
|
5
|
+
require 'active_support/core_ext/hash/deep_merge'
|
6
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
7
|
+
require 'active_support/core_ext/string/inflections'
|
8
|
+
|
9
|
+
require 'lightrail/generators/base'
|
10
|
+
|
11
|
+
module Lightrail
|
12
|
+
module Generators
|
13
|
+
autoload :Actions, 'rails/generators/actions'
|
14
|
+
autoload :ActiveModel, 'rails/generators/active_model'
|
15
|
+
autoload :Migration, 'rails/generators/migration'
|
16
|
+
autoload :NamedBase, 'rails/generators/named_base'
|
17
|
+
autoload :ResourceHelpers, 'rails/generators/resource_helpers'
|
18
|
+
autoload :TestCase, 'rails/generators/test_case'
|
19
|
+
|
20
|
+
mattr_accessor :namespace
|
21
|
+
|
22
|
+
DEFAULT_ALIASES = {
|
23
|
+
:rails => {
|
24
|
+
:actions => '-a',
|
25
|
+
:orm => '-o',
|
26
|
+
:javascripts => '-j',
|
27
|
+
:javascript_engine => '-je',
|
28
|
+
:resource_controller => '-c',
|
29
|
+
:scaffold_controller => '-c',
|
30
|
+
:stylesheets => '-y',
|
31
|
+
:stylesheet_engine => '-se',
|
32
|
+
:template_engine => '-e',
|
33
|
+
:test_framework => '-t'
|
34
|
+
},
|
35
|
+
|
36
|
+
:test_unit => {
|
37
|
+
:fixture_replacement => '-r',
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
DEFAULT_OPTIONS = {
|
42
|
+
:rails => {
|
43
|
+
:assets => true,
|
44
|
+
:force_plural => false,
|
45
|
+
:helper => true,
|
46
|
+
:integration_tool => nil,
|
47
|
+
:javascripts => true,
|
48
|
+
:javascript_engine => :js,
|
49
|
+
:orm => false,
|
50
|
+
:performance_tool => nil,
|
51
|
+
:resource_controller => :controller,
|
52
|
+
:scaffold_controller => :scaffold_controller,
|
53
|
+
:stylesheets => true,
|
54
|
+
:stylesheet_engine => :css,
|
55
|
+
:test_framework => false,
|
56
|
+
:template_engine => :erb
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
def self.configure!(config) #:nodoc:
|
61
|
+
no_color! unless config.colorize_logging
|
62
|
+
aliases.deep_merge! config.aliases
|
63
|
+
options.deep_merge! config.options
|
64
|
+
fallbacks.merge! config.fallbacks
|
65
|
+
templates_path.concat config.templates
|
66
|
+
templates_path.uniq!
|
67
|
+
hide_namespaces(*config.hidden_namespaces)
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.templates_path
|
71
|
+
@templates_path ||= []
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.aliases #:nodoc:
|
75
|
+
@aliases ||= DEFAULT_ALIASES.dup
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.options #:nodoc:
|
79
|
+
@options ||= DEFAULT_OPTIONS.dup
|
80
|
+
end
|
81
|
+
|
82
|
+
# Hold configured generators fallbacks. If a plugin developer wants a
|
83
|
+
# generator group to fallback to another group in case of missing generators,
|
84
|
+
# they can add a fallback.
|
85
|
+
#
|
86
|
+
# For example, shoulda is considered a test_framework and is an extension
|
87
|
+
# of test_unit. However, most part of shoulda generators are similar to
|
88
|
+
# test_unit ones.
|
89
|
+
#
|
90
|
+
# Shoulda then can tell generators to search for test_unit generators when
|
91
|
+
# some of them are not available by adding a fallback:
|
92
|
+
#
|
93
|
+
# Rails::Generators.fallbacks[:shoulda] = :test_unit
|
94
|
+
#
|
95
|
+
def self.fallbacks
|
96
|
+
@fallbacks ||= {}
|
97
|
+
end
|
98
|
+
|
99
|
+
# Remove the color from output.
|
100
|
+
def self.no_color!
|
101
|
+
Thor::Base.shell = Thor::Shell::Basic
|
102
|
+
end
|
103
|
+
|
104
|
+
# Track all generators subclasses.
|
105
|
+
def self.subclasses
|
106
|
+
@subclasses ||= []
|
107
|
+
end
|
108
|
+
|
109
|
+
# Rails finds namespaces similar to thor, it only adds one rule:
|
110
|
+
#
|
111
|
+
# Generators names must end with "_generator.rb". This is required because Rails
|
112
|
+
# looks in load paths and loads the generator just before it's going to be used.
|
113
|
+
#
|
114
|
+
# ==== Examples
|
115
|
+
#
|
116
|
+
# find_by_namespace :webrat, :rails, :integration
|
117
|
+
#
|
118
|
+
# Will search for the following generators:
|
119
|
+
#
|
120
|
+
# "rails:webrat", "webrat:integration", "webrat"
|
121
|
+
#
|
122
|
+
# Notice that "rails:generators:webrat" could be loaded as well, what
|
123
|
+
# Rails looks for is the first and last parts of the namespace.
|
124
|
+
#
|
125
|
+
def self.find_by_namespace(name, base=nil, context=nil) #:nodoc:
|
126
|
+
lookups = []
|
127
|
+
lookups << "#{base}:#{name}" if base
|
128
|
+
lookups << "#{name}:#{context}" if context
|
129
|
+
|
130
|
+
unless base || context
|
131
|
+
unless name.to_s.include?(?:)
|
132
|
+
lookups << "#{name}:#{name}"
|
133
|
+
lookups << "rails:#{name}"
|
134
|
+
end
|
135
|
+
lookups << "#{name}"
|
136
|
+
end
|
137
|
+
|
138
|
+
lookup(lookups)
|
139
|
+
|
140
|
+
namespaces = Hash[subclasses.map { |klass| [klass.namespace, klass] }]
|
141
|
+
|
142
|
+
lookups.each do |namespace|
|
143
|
+
klass = namespaces[namespace]
|
144
|
+
return klass if klass
|
145
|
+
end
|
146
|
+
|
147
|
+
invoke_fallbacks_for(name, base) || invoke_fallbacks_for(context, name)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Receives a namespace, arguments and the behavior to invoke the generator.
|
151
|
+
# It's used as the default entry point for generate, destroy and update
|
152
|
+
# commands.
|
153
|
+
def self.invoke(namespace, args=ARGV, config={})
|
154
|
+
names = namespace.to_s.split(':')
|
155
|
+
if klass = find_by_namespace(names.pop, names.any? && names.join(':'))
|
156
|
+
args << "--help" if args.empty? && klass.arguments.any? { |a| a.required? }
|
157
|
+
klass.start(args, config)
|
158
|
+
else
|
159
|
+
puts "Could not find generator #{namespace}."
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def self.hidden_namespaces
|
164
|
+
@hidden_namespaces ||= begin
|
165
|
+
orm = options[:rails][:orm]
|
166
|
+
test = options[:rails][:test_framework]
|
167
|
+
template = options[:rails][:template_engine]
|
168
|
+
css = options[:rails][:stylesheet_engine]
|
169
|
+
|
170
|
+
[
|
171
|
+
"rails",
|
172
|
+
"#{orm}:migration",
|
173
|
+
"#{orm}:model",
|
174
|
+
"#{orm}:observer",
|
175
|
+
"#{orm}:session_migration",
|
176
|
+
"#{test}:controller",
|
177
|
+
"#{test}:helper",
|
178
|
+
"#{test}:integration",
|
179
|
+
"#{test}:mailer",
|
180
|
+
"#{test}:model",
|
181
|
+
"#{test}:observer",
|
182
|
+
"#{test}:scaffold",
|
183
|
+
"#{test}:view",
|
184
|
+
"#{test}:performance",
|
185
|
+
"#{template}:controller",
|
186
|
+
"#{template}:scaffold",
|
187
|
+
"#{template}:mailer",
|
188
|
+
"#{css}:scaffold",
|
189
|
+
"#{css}:assets",
|
190
|
+
"css:assets",
|
191
|
+
"css:scaffold"
|
192
|
+
]
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
class << self
|
197
|
+
def hide_namespaces(*namespaces)
|
198
|
+
hidden_namespaces.concat(namespaces)
|
199
|
+
end
|
200
|
+
alias hide_namespace hide_namespaces
|
201
|
+
end
|
202
|
+
|
203
|
+
# Show help message with available generators.
|
204
|
+
def self.help(command = 'generate')
|
205
|
+
lookup!
|
206
|
+
|
207
|
+
namespaces = subclasses.map{ |k| k.namespace }
|
208
|
+
namespaces.sort!
|
209
|
+
|
210
|
+
groups = Hash.new { |h,k| h[k] = [] }
|
211
|
+
namespaces.each do |namespace|
|
212
|
+
base = namespace.split(':').first
|
213
|
+
groups[base] << namespace
|
214
|
+
end
|
215
|
+
|
216
|
+
puts "Usage: lightrail #{command} GENERATOR [args] [options]"
|
217
|
+
puts
|
218
|
+
puts "General options:"
|
219
|
+
puts " -h, [--help] # Print generator's options and usage"
|
220
|
+
puts " -p, [--pretend] # Run but do not make any changes"
|
221
|
+
puts " -f, [--force] # Overwrite files that already exist"
|
222
|
+
puts " -s, [--skip] # Skip files that already exist"
|
223
|
+
puts " -q, [--quiet] # Suppress status output"
|
224
|
+
puts
|
225
|
+
puts "Please choose a generator below."
|
226
|
+
puts
|
227
|
+
|
228
|
+
# Print Rails defaults first.
|
229
|
+
rails = groups.delete("rails")
|
230
|
+
rails.map! { |n| n.sub(/^rails:/, '') }
|
231
|
+
rails.delete("app")
|
232
|
+
rails.delete("plugin_new")
|
233
|
+
print_list("rails", rails)
|
234
|
+
|
235
|
+
hidden_namespaces.each {|n| groups.delete(n.to_s) }
|
236
|
+
|
237
|
+
groups.sort.each { |b, n| print_list(b, n) }
|
238
|
+
end
|
239
|
+
|
240
|
+
protected
|
241
|
+
|
242
|
+
# Prints a list of generators.
|
243
|
+
def self.print_list(base, namespaces) #:nodoc:
|
244
|
+
namespaces = namespaces.reject do |n|
|
245
|
+
hidden_namespaces.include?(n)
|
246
|
+
end
|
247
|
+
|
248
|
+
return if namespaces.empty?
|
249
|
+
puts "#{base.camelize}:"
|
250
|
+
|
251
|
+
namespaces.each do |namespace|
|
252
|
+
puts(" #{namespace}")
|
253
|
+
end
|
254
|
+
|
255
|
+
puts
|
256
|
+
end
|
257
|
+
|
258
|
+
# Try fallbacks for the given base.
|
259
|
+
def self.invoke_fallbacks_for(name, base) #:nodoc:
|
260
|
+
return nil unless base && fallbacks[base.to_sym]
|
261
|
+
invoked_fallbacks = []
|
262
|
+
|
263
|
+
Array(fallbacks[base.to_sym]).each do |fallback|
|
264
|
+
next if invoked_fallbacks.include?(fallback)
|
265
|
+
invoked_fallbacks << fallback
|
266
|
+
|
267
|
+
klass = find_by_namespace(name, fallback)
|
268
|
+
return klass if klass
|
269
|
+
end
|
270
|
+
|
271
|
+
nil
|
272
|
+
end
|
273
|
+
|
274
|
+
# Receives namespaces in an array and tries to find matching generators
|
275
|
+
# in the load path.
|
276
|
+
def self.lookup(namespaces) #:nodoc:
|
277
|
+
paths = namespaces_to_paths(namespaces)
|
278
|
+
|
279
|
+
paths.each do |raw_path|
|
280
|
+
["rails/generators", "generators"].each do |base|
|
281
|
+
path = "#{base}/#{raw_path}_generator"
|
282
|
+
|
283
|
+
begin
|
284
|
+
require path
|
285
|
+
return
|
286
|
+
rescue LoadError => e
|
287
|
+
raise unless e.message =~ /#{Regexp.escape(path)}$/
|
288
|
+
rescue Exception => e
|
289
|
+
warn "[WARNING] Could not load generator #{path.inspect}. Error: #{e.message}.\n#{e.backtrace.join("\n")}"
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# This will try to load any generator in the load path to show in help.
|
296
|
+
def self.lookup! #:nodoc:
|
297
|
+
$LOAD_PATH.each do |base|
|
298
|
+
Dir[File.join(base, "{rails/generators,generators}", "**", "*_generator.rb")].each do |path|
|
299
|
+
begin
|
300
|
+
path = path.sub("#{base}/", "")
|
301
|
+
require path
|
302
|
+
rescue Exception
|
303
|
+
# No problem
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
# Convert namespaces to paths by replacing ":" for "/" and adding
|
310
|
+
# an extra lookup. For example, "rails:model" should be searched
|
311
|
+
# in both: "rails/model/model_generator" and "rails/model_generator".
|
312
|
+
def self.namespaces_to_paths(namespaces) #:nodoc:
|
313
|
+
paths = []
|
314
|
+
namespaces.each do |namespace|
|
315
|
+
pieces = namespace.split(":")
|
316
|
+
paths << pieces.dup.push(pieces.last).join("/")
|
317
|
+
paths << pieces.join("/")
|
318
|
+
end
|
319
|
+
paths.uniq!
|
320
|
+
paths
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|