lightrail 0.0.1 → 0.99.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/.gitignore +7 -0
  2. data/.rspec +4 -0
  3. data/.travis.yml +11 -0
  4. data/CHANGES.md +8 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE +19 -0
  7. data/README.md +205 -0
  8. data/Rakefile +5 -0
  9. data/bin/lightrail +2 -1
  10. data/lib/lightrail.rb +2 -0
  11. data/lib/lightrail/action_controller/metal.rb +0 -2
  12. data/lib/lightrail/cli.rb +16 -0
  13. data/lib/lightrail/commands/application.rb +26 -0
  14. data/lib/lightrail/generators.rb +323 -0
  15. data/lib/lightrail/generators/app_base.rb +281 -0
  16. data/lib/lightrail/generators/app_generator.rb +299 -0
  17. data/lib/lightrail/generators/base.rb +378 -0
  18. data/lib/lightrail/generators/templates/Gemfile +25 -0
  19. data/lib/lightrail/generators/templates/README +259 -0
  20. data/lib/lightrail/generators/templates/Rakefile +7 -0
  21. data/lib/lightrail/generators/templates/app/assets/images/rails.png +0 -0
  22. data/lib/lightrail/generators/templates/app/assets/javascripts/application.js.tt +17 -0
  23. data/lib/lightrail/generators/templates/app/assets/stylesheets/application.css +13 -0
  24. data/lib/lightrail/generators/templates/app/controllers/application_controller.rb +3 -0
  25. data/lib/lightrail/generators/templates/app/helpers/application_helper.rb +2 -0
  26. data/lib/lightrail/generators/templates/app/mailers/.empty_directory +0 -0
  27. data/lib/lightrail/generators/templates/app/models/.empty_directory +0 -0
  28. data/lib/lightrail/generators/templates/app/views/layouts/application.html.erb.tt +14 -0
  29. data/lib/lightrail/generators/templates/config.ru +4 -0
  30. data/lib/lightrail/generators/templates/config/application.rb +67 -0
  31. data/lib/lightrail/generators/templates/config/boot.rb +6 -0
  32. data/lib/lightrail/generators/templates/config/databases/frontbase.yml +31 -0
  33. data/lib/lightrail/generators/templates/config/databases/ibm_db.yml +86 -0
  34. data/lib/lightrail/generators/templates/config/databases/jdbc.yml +62 -0
  35. data/lib/lightrail/generators/templates/config/databases/jdbcmysql.yml +33 -0
  36. data/lib/lightrail/generators/templates/config/databases/jdbcpostgresql.yml +43 -0
  37. data/lib/lightrail/generators/templates/config/databases/jdbcsqlite3.yml +20 -0
  38. data/lib/lightrail/generators/templates/config/databases/mysql.yml +51 -0
  39. data/lib/lightrail/generators/templates/config/databases/oracle.yml +39 -0
  40. data/lib/lightrail/generators/templates/config/databases/postgresql.yml +55 -0
  41. data/lib/lightrail/generators/templates/config/databases/sqlite3.yml +25 -0
  42. data/lib/lightrail/generators/templates/config/environment.rb +5 -0
  43. data/lib/lightrail/generators/templates/config/environments/development.rb.tt +38 -0
  44. data/lib/lightrail/generators/templates/config/environments/production.rb.tt +76 -0
  45. data/lib/lightrail/generators/templates/config/environments/test.rb.tt +36 -0
  46. data/lib/lightrail/generators/templates/config/initializers/backtrace_silencers.rb +7 -0
  47. data/lib/lightrail/generators/templates/config/initializers/inflections.rb +15 -0
  48. data/lib/lightrail/generators/templates/config/initializers/mime_types.rb +5 -0
  49. data/lib/lightrail/generators/templates/config/initializers/secret_token.rb.tt +7 -0
  50. data/lib/lightrail/generators/templates/config/initializers/session_store.rb.tt +8 -0
  51. data/lib/lightrail/generators/templates/config/initializers/wrap_parameters.rb.tt +16 -0
  52. data/lib/lightrail/generators/templates/config/locales/en.yml +5 -0
  53. data/lib/lightrail/generators/templates/config/routes.rb +58 -0
  54. data/lib/lightrail/generators/templates/db/seeds.rb.tt +7 -0
  55. data/lib/lightrail/generators/templates/gitignore +16 -0
  56. data/lib/lightrail/generators/templates/public/404.html +26 -0
  57. data/lib/lightrail/generators/templates/public/422.html +26 -0
  58. data/lib/lightrail/generators/templates/public/500.html +25 -0
  59. data/lib/lightrail/generators/templates/public/favicon.ico +0 -0
  60. data/lib/lightrail/generators/templates/public/index.html +241 -0
  61. data/lib/lightrail/generators/templates/public/robots.txt +5 -0
  62. data/lib/lightrail/generators/templates/public/stylesheets/.empty_directory +0 -0
  63. data/lib/lightrail/generators/templates/script/rails +5 -0
  64. data/lib/lightrail/generators/templates/test/fixtures/.empty_directory +0 -0
  65. data/lib/lightrail/generators/templates/test/functional/.empty_directory +0 -0
  66. data/lib/lightrail/generators/templates/test/integration/.empty_directory +0 -0
  67. data/lib/lightrail/generators/templates/test/performance/browsing_test.rb +12 -0
  68. data/lib/lightrail/generators/templates/test/test_helper.rb +15 -0
  69. data/lib/lightrail/generators/templates/test/unit/.empty_directory +0 -0
  70. data/lib/lightrail/version.rb +1 -1
  71. data/lightrail.gemspec +23 -0
  72. data/logo.png +0 -0
  73. data/spec/lightrail/action_controller/metal_spec.rb +8 -0
  74. data/spec/spec_helper.rb +1 -0
  75. data/tasks/rspec.task +7 -0
  76. metadata +105 -13
  77. data/lib/lightrail/action_controller/param.rb +0 -12
  78. data/lib/lightrail/core_ext/regexp.rb +0 -7
  79. data/lib/lightrail/encryptor.rb +0 -62
@@ -0,0 +1,7 @@
1
+ coverage/
2
+ rdoc/
3
+ doc/
4
+ pkg/
5
+ .yardoc
6
+ .bundle
7
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --color
2
+ --format documentation
3
+ --backtrace
4
+ --default_path spec
@@ -0,0 +1,11 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - ree
6
+ - ruby-head
7
+ - jruby-18mode
8
+ - jruby-19mode
9
+ - jruby-head
10
+ - rbx-18mode
11
+ - rbx-19mode
@@ -0,0 +1,8 @@
1
+ 0.99.0
2
+ ------
3
+ * Not quite 1.0-pre quality yet, but better!
4
+ * Import Rails generators, generate lightrail apps
5
+
6
+ 0.0.1
7
+ -----
8
+ * Stealth release
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
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.
@@ -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.
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ Dir["tasks/**/*.task"].each { |task| load task }
4
+
5
+ task :default => :spec
@@ -1 +1,2 @@
1
- require "rails/cli"
1
+ #!/usr/bin/env ruby
2
+ require "lightrail/cli"
@@ -0,0 +1,2 @@
1
+ require 'lightrail/version'
2
+ require 'lightrail/railtie'
@@ -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