lightrail 0.0.1 → 0.99.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
![Lightrail](https://github.com/lightness/lightrail/raw/master/logo.png)
|
2
|
+
============
|
3
|
+
[![Build Status](https://secure.travis-ci.org/lightness/lightrail.png?branch=master)](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
|