rails_wizard 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +95 -0
- data/bin/rails_wizard +7 -0
- data/lib/rails_wizard.rb +10 -0
- data/lib/rails_wizard/command.rb +78 -0
- data/lib/rails_wizard/config.rb +86 -0
- data/lib/rails_wizard/recipe.rb +106 -0
- data/lib/rails_wizard/recipes.rb +38 -0
- data/lib/rails_wizard/template.rb +57 -0
- data/recipes/activerecord.rb +37 -0
- data/recipes/capybara.rb +34 -0
- data/recipes/cucumber.rb +16 -0
- data/recipes/devise.rb +23 -0
- data/recipes/git.rb +15 -0
- data/recipes/haml.rb +11 -0
- data/recipes/heroku.rb +58 -0
- data/recipes/hoptoad.rb +31 -0
- data/recipes/jammit.rb +43 -0
- data/recipes/jquery.rb +23 -0
- data/recipes/less.rb +12 -0
- data/recipes/mongo_mapper.rb +18 -0
- data/recipes/mongohq.rb +59 -0
- data/recipes/mongoid.rb +18 -0
- data/recipes/mootools.rb +23 -0
- data/recipes/omniauth.rb +15 -0
- data/recipes/prototype.rb +11 -0
- data/recipes/redis.rb +11 -0
- data/recipes/rightjs.rb +17 -0
- data/recipes/rspec.rb +21 -0
- data/recipes/sass.rb +13 -0
- data/recipes/sequel.rb +13 -0
- data/recipes/slim.rb +11 -0
- data/recipes/test_unit.rb +11 -0
- data/spec/rails_wizard/config_spec.rb +99 -0
- data/spec/rails_wizard/recipe_spec.rb +103 -0
- data/spec/rails_wizard/recipes/sanity_spec.rb +30 -0
- data/spec/rails_wizard/recipes_spec.rb +24 -0
- data/spec/rails_wizard/template_spec.rb +48 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/support/rails_directory.rb +17 -0
- data/spec/support/template_runner.rb +28 -0
- data/templates/helpers.erb +44 -0
- data/templates/layout.erb +42 -0
- data/templates/recipe.erb +9 -0
- data/version.rb +3 -0
- metadata +139 -0
data/README.markdown
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# RailsWizard Gem
|
2
|
+
|
3
|
+
The RailsWizard gem is both the official repository of recipes for
|
4
|
+
[RailsWizard][1] as well as a stand-alone tool to generate rails
|
5
|
+
templates from the command line. The website and the gem are kept in
|
6
|
+
version sync, so any recipes released to the gem will be simultaneously
|
7
|
+
available on the web builder.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Installation is simple:
|
12
|
+
|
13
|
+
gem install rails_wizard
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
The primary usage of the `rails_wizard` gem is to utilize its
|
18
|
+
interactive terminal command to build a Rails template. To get started,
|
19
|
+
you can simply run the command thusly:
|
20
|
+
|
21
|
+
rails_wizard new APP_NAME
|
22
|
+
|
23
|
+
Where `APP_NAME` is the directory in which you wish to create the app
|
24
|
+
(it mirrors the Rails creation syntax). You will then be guided through
|
25
|
+
the recipe selection process and subsequently the Rails app generator
|
26
|
+
will automatically run with the template and all appropriate command
|
27
|
+
line options included.
|
28
|
+
|
29
|
+
### Specifying Recipes
|
30
|
+
|
31
|
+
If you wish to skip the interactive recipe selector, you may provide
|
32
|
+
instead a list of recipes with the `-r` option:
|
33
|
+
|
34
|
+
rails_wizard new APP_NAME -r jquery mongo_mapper sass
|
35
|
+
|
36
|
+
This will automatically generate a Rails template with the provided
|
37
|
+
recipes and begin the app generator.
|
38
|
+
|
39
|
+
### Listing Recipes
|
40
|
+
|
41
|
+
You can also print out a simple list of recipes:
|
42
|
+
|
43
|
+
rails_wizard list
|
44
|
+
|
45
|
+
Or print out a list of recipes for a specific category:
|
46
|
+
|
47
|
+
rails_wizard list persistence
|
48
|
+
|
49
|
+
# RailsWizard Recipes
|
50
|
+
|
51
|
+
Previously stored in MongoDB, the RailsWizard recipe collection
|
52
|
+
now live in this GitHub repository to make them fork-friendly and
|
53
|
+
available for use with the command-line tool. You can see all of
|
54
|
+
the recipes in the [recipes directory][2].
|
55
|
+
|
56
|
+
If you're looking for the web app source code, it now lives at
|
57
|
+
[rails_wizard.web][3].
|
58
|
+
|
59
|
+
## Submitting a Recipe
|
60
|
+
|
61
|
+
Submitting a recipe is actually a very straightforward process. Recipes
|
62
|
+
are made of up **template code** and **YAML back-matter** stored in a
|
63
|
+
ruby file. The `__END__` parsing convention is used so that each recipe
|
64
|
+
is actually a valid, parseable Ruby file. The structure of a recipe
|
65
|
+
looks something like this:
|
66
|
+
|
67
|
+
gem 'supergem'
|
68
|
+
|
69
|
+
after_bundler do
|
70
|
+
generate "supergem:install"
|
71
|
+
end
|
72
|
+
|
73
|
+
__END__
|
74
|
+
|
75
|
+
category: templating
|
76
|
+
name: SuperGem
|
77
|
+
description: Installs SuperGem which is useful for things
|
78
|
+
author: mbleigh
|
79
|
+
|
80
|
+
It's really that simple. The gem has RSpec tests that automatically
|
81
|
+
validate each recipe in the repository, so you should run `rake spec`
|
82
|
+
as a basic sanity check before submitting a pull request. Note that
|
83
|
+
these don't verify that your recipe code itself works, just that
|
84
|
+
RailsWizard could properly parse and understand your recipe file.
|
85
|
+
|
86
|
+
For more information on all available options for authoring recipes,
|
87
|
+
please see the
|
88
|
+
|
89
|
+
## License
|
90
|
+
|
91
|
+
RailsWizard and its recipes are distributed under the MIT License.
|
92
|
+
|
93
|
+
[1]:http://railswizard.org/
|
94
|
+
[2]:https://github.com/intridea/rails_wizard/tree/master/recipes
|
95
|
+
[3]:https://github.com/intridea/rails_wizard.web
|
data/bin/rails_wizard
ADDED
data/lib/rails_wizard.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'rails_wizard/recipes'
|
2
|
+
require 'rails_wizard/recipe'
|
3
|
+
require 'rails_wizard/config'
|
4
|
+
require 'rails_wizard/template'
|
5
|
+
|
6
|
+
Dir[File.dirname(__FILE__) + '/../recipes/*.rb'].each do |path|
|
7
|
+
key = File.basename(path, '.rb')
|
8
|
+
recipe = RailsWizard::Recipe.generate(key, File.open(path))
|
9
|
+
RailsWizard::Recipes.add(recipe)
|
10
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'rails_wizard'
|
2
|
+
require 'thor'
|
3
|
+
|
4
|
+
module RailsWizard
|
5
|
+
class Command < Thor
|
6
|
+
include Thor::Actions
|
7
|
+
desc "new APP_NAME", "create a new Rails app"
|
8
|
+
method_option :recipes, :type => :array, :aliases => "-r"
|
9
|
+
def new(name)
|
10
|
+
if options[:recipes]
|
11
|
+
run_template(name, options[:recipes])
|
12
|
+
else
|
13
|
+
@recipes = []
|
14
|
+
|
15
|
+
while recipe = ask("#{print_recipes}#{bold}Which recipe would you like to add? #{clear}#{yellow}(blank to finish)#{clear}")
|
16
|
+
if recipe == ''
|
17
|
+
run_template(name, @recipes)
|
18
|
+
elsif RailsWizard::Recipes.list.include?(recipe)
|
19
|
+
@recipes << recipe
|
20
|
+
puts
|
21
|
+
puts "> #{green}Added '#{recipe}' to template.#{clear}"
|
22
|
+
else
|
23
|
+
puts
|
24
|
+
puts "> #{red}Invalid recipe, please try again.#{clear}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "list [CATEGORY]", "list available recipes (optionally by category)"
|
31
|
+
def list(category = nil)
|
32
|
+
if category
|
33
|
+
recipes = RailsWizard::Recipes.for(category).map{|r| RailsWizard::Recipe.from_mongo(r) }
|
34
|
+
else
|
35
|
+
recipes = RailsWizard::Recipes.list_classes
|
36
|
+
end
|
37
|
+
|
38
|
+
recipes.each do |recipe|
|
39
|
+
puts recipe.key.ljust(15) + "# #{recipe.description}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
no_tasks do
|
44
|
+
def cyan; "\033[36m" end
|
45
|
+
def clear; "\033[0m" end
|
46
|
+
def bold; "\033[1m" end
|
47
|
+
def red; "\033[31m" end
|
48
|
+
def green; "\033[32m" end
|
49
|
+
def yellow; "\033[33m" end
|
50
|
+
|
51
|
+
def print_recipes
|
52
|
+
puts
|
53
|
+
puts
|
54
|
+
puts
|
55
|
+
if @recipes && @recipes.any?
|
56
|
+
puts "#{green}#{bold}Your Recipes:#{clear} " + @recipes.join(", ")
|
57
|
+
puts
|
58
|
+
end
|
59
|
+
puts "#{bold}#{cyan}Available Recipes:#{clear} " + RailsWizard::Recipes.list.join(', ')
|
60
|
+
puts
|
61
|
+
end
|
62
|
+
|
63
|
+
def run_template(name, recipes)
|
64
|
+
puts
|
65
|
+
puts
|
66
|
+
puts "#{bold}Generating and Running Template..."
|
67
|
+
puts
|
68
|
+
file = Tempfile.new('template')
|
69
|
+
template = RailsWizard::Template.new(recipes)
|
70
|
+
file.write template.compile
|
71
|
+
file.close
|
72
|
+
system "rails new #{name} -m #{file.path} #{template.args.join(' ')}"
|
73
|
+
ensure
|
74
|
+
file.unlink
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'active_support/ordered_hash'
|
2
|
+
|
3
|
+
module RailsWizard
|
4
|
+
class Config
|
5
|
+
attr_reader :questions
|
6
|
+
|
7
|
+
def initialize(schema)
|
8
|
+
@questions = ActiveSupport::OrderedHash.new
|
9
|
+
schema.each do |hash|
|
10
|
+
key = hash.keys.first
|
11
|
+
details = hash.values.first
|
12
|
+
|
13
|
+
kind = details['type']
|
14
|
+
raise ArgumentError, "Unrecognized question type, must be one of #{QUESTION_TYPES.keys.join(', ')}" unless QUESTION_TYPES.keys.include?(kind)
|
15
|
+
@questions[key] = QUESTION_TYPES[kind].new(details)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def compile(values = {})
|
20
|
+
result = []
|
21
|
+
result << "config = #{values.inspect}"
|
22
|
+
@questions.each_pair do |key, question|
|
23
|
+
result << "config['#{key}'] = #{question.compile} unless config.key?('#{key}')"
|
24
|
+
end
|
25
|
+
result.join("\n")
|
26
|
+
end
|
27
|
+
|
28
|
+
class Prompt
|
29
|
+
attr_reader :prompt, :details
|
30
|
+
def initialize(details)
|
31
|
+
@details = details
|
32
|
+
@prompt = details['prompt']
|
33
|
+
end
|
34
|
+
|
35
|
+
def compile
|
36
|
+
"#{question} if #{conditions}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def question
|
40
|
+
"ask_wizard(#{prompt.inspect})"
|
41
|
+
end
|
42
|
+
|
43
|
+
def conditions
|
44
|
+
[config_conditions, recipe_conditions].join(' && ')
|
45
|
+
end
|
46
|
+
|
47
|
+
def config_conditions
|
48
|
+
if details['if']
|
49
|
+
"config['#{details['if']}']"
|
50
|
+
elsif details['unless']
|
51
|
+
"!config['#{details['unless']}']"
|
52
|
+
else
|
53
|
+
'true'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def recipe_conditions
|
58
|
+
if details['if_recipe']
|
59
|
+
"recipe?('#{details['if_recipe']}')"
|
60
|
+
elsif details['unless_recipe']
|
61
|
+
"!recipe?('#{details['unless_recipe']}')"
|
62
|
+
else
|
63
|
+
'true'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class TrueFalse < Prompt
|
69
|
+
def question
|
70
|
+
"yes_wizard?(#{prompt.inspect})"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class MultipleChoice < Prompt
|
75
|
+
def question
|
76
|
+
"multiple_choice(#{prompt.inspect}, #{@details['choices'].inspect})"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
QUESTION_TYPES = {
|
81
|
+
'boolean' => TrueFalse,
|
82
|
+
'string' => Prompt,
|
83
|
+
'multiple_choice' => MultipleChoice
|
84
|
+
}
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'rails_wizard/config'
|
2
|
+
|
3
|
+
require 'active_support/inflector'
|
4
|
+
require 'yaml'
|
5
|
+
require 'erb'
|
6
|
+
|
7
|
+
module RailsWizard
|
8
|
+
class Recipe
|
9
|
+
extend Comparable
|
10
|
+
|
11
|
+
def self.<=>(another)
|
12
|
+
return -1 if another.run_after.include?(self.key) || self.run_before.include?(another.key)
|
13
|
+
return 1 if another.run_before.include?(self.key) || self.run_after.include?(another.key)
|
14
|
+
0
|
15
|
+
end
|
16
|
+
|
17
|
+
ATTRIBUTES = %w(key args category name description template config exclusive tags run_before run_after requires)
|
18
|
+
DEFAULT_ATTRIBUTES = {
|
19
|
+
:category => 'other',
|
20
|
+
:args => [],
|
21
|
+
:tags => [],
|
22
|
+
:run_after => [],
|
23
|
+
:run_before => [],
|
24
|
+
:requires => []
|
25
|
+
}
|
26
|
+
|
27
|
+
def self.generate(key, template_or_file, attributes = {})
|
28
|
+
if template_or_file.respond_to?(:read)
|
29
|
+
file = template_or_file.read
|
30
|
+
parts = file.split(/^__END__$/)
|
31
|
+
raise ArgumentError, "The recipe file must have YAML matter after an __END__" unless parts.size == 2
|
32
|
+
template = parts.first.strip
|
33
|
+
attributes = YAML.load(parts.last).inject({}) do |h,(k,v)|
|
34
|
+
h[k.to_sym] = v
|
35
|
+
h
|
36
|
+
end.merge!(attributes)
|
37
|
+
else
|
38
|
+
template = template_or_file
|
39
|
+
end
|
40
|
+
|
41
|
+
recipe_class = Class.new(RailsWizard::Recipe)
|
42
|
+
recipe_class.attributes = attributes
|
43
|
+
recipe_class.template = template
|
44
|
+
recipe_class.key = key
|
45
|
+
|
46
|
+
recipe_class
|
47
|
+
end
|
48
|
+
|
49
|
+
ATTRIBUTES.each do |setter|
|
50
|
+
class_eval <<-RUBY
|
51
|
+
def self.#{setter}
|
52
|
+
attributes[:#{setter}]
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.#{setter}=(val)
|
56
|
+
attributes[:#{setter}] = val
|
57
|
+
end
|
58
|
+
|
59
|
+
def #{setter}
|
60
|
+
self.class.#{setter}
|
61
|
+
end
|
62
|
+
RUBY
|
63
|
+
end
|
64
|
+
|
65
|
+
# The attributes hash containing any set values for
|
66
|
+
def self.attributes
|
67
|
+
@attributes ||= DEFAULT_ATTRIBUTES.dup
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.attributes=(hash)
|
71
|
+
attributes.merge! hash
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.config
|
75
|
+
return nil unless attributes[:config]
|
76
|
+
RailsWizard::Config.new(attributes[:config])
|
77
|
+
end
|
78
|
+
|
79
|
+
def attributes
|
80
|
+
self.class.attributes
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.compile
|
84
|
+
"# >#{"[ #{name} ]".center(75,'-')}<\n\n# #{description}\nsay_recipe '#{name}'\n\n#{template}\n"
|
85
|
+
end
|
86
|
+
def compile; self.class.compile end
|
87
|
+
|
88
|
+
def self.to_mongo(value)
|
89
|
+
case value
|
90
|
+
when Class
|
91
|
+
value.key
|
92
|
+
when String
|
93
|
+
value
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.from_mongo(key)
|
98
|
+
return key if key.respond_to?(:superclass) && key.superclass == RailsWizard::Recipe
|
99
|
+
RailsWizard::Recipes[key]
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.get_binding
|
103
|
+
binding
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module RailsWizard
|
2
|
+
module Recipes
|
3
|
+
@@categories = {}
|
4
|
+
@@list = {}
|
5
|
+
|
6
|
+
def self.add(recipe)
|
7
|
+
RailsWizard::Recipes.const_set ActiveSupport::Inflector.camelize(recipe.key), recipe
|
8
|
+
@@list[recipe.key] = recipe
|
9
|
+
(@@categories[recipe.category.to_s] ||= []) << recipe.key
|
10
|
+
@@categories[recipe.category.to_s].uniq!
|
11
|
+
recipe
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.[](key)
|
15
|
+
@@list[key.to_s]
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.list
|
19
|
+
@@list.keys.sort
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.list_classes
|
23
|
+
@@list.values.sort_by{|c| c.key}
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.categories
|
27
|
+
@@categories.keys.sort
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.for(category)
|
31
|
+
(@@categories[category.to_s] || []).sort
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.remove_from_category(category, recipe)
|
35
|
+
(@@categories[category.to_s] ||= []).delete(recipe.key)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module RailsWizard
|
2
|
+
class Template
|
3
|
+
attr_reader :recipes
|
4
|
+
|
5
|
+
def initialize(recipes)
|
6
|
+
@recipes = recipes.map{|r| RailsWizard::Recipe.from_mongo(r)}
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.template_root
|
10
|
+
File.dirname(__FILE__) + '/../../templates'
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.render(template_name, binding = nil)
|
14
|
+
erb = ERB.new(File.open(template_root + '/' + template_name + '.erb').read)
|
15
|
+
erb.result(binding)
|
16
|
+
end
|
17
|
+
def render(template_name, binding = nil); self.class.render(template_name, binding) end
|
18
|
+
|
19
|
+
|
20
|
+
def resolve_recipes
|
21
|
+
@resolve_recipes ||= recipes_with_dependencies.sort
|
22
|
+
end
|
23
|
+
|
24
|
+
def recipe_classes
|
25
|
+
@recipe_classes ||= recipes.map{|r| RailsWizard::Recipe.from_mongo(r)}
|
26
|
+
end
|
27
|
+
|
28
|
+
def recipes_with_dependencies
|
29
|
+
@recipes_with_dependencies ||= recipe_classes
|
30
|
+
|
31
|
+
added_more = false
|
32
|
+
for recipe in recipe_classes
|
33
|
+
recipe.requires.each do |requirement|
|
34
|
+
requirement = RailsWizard::Recipe.from_mongo(requirement)
|
35
|
+
count = @recipes_with_dependencies.size
|
36
|
+
(@recipes_with_dependencies << requirement).uniq!
|
37
|
+
unless @recipes_with_dependencies.size == count
|
38
|
+
added_more = true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
added_more ? recipes_with_dependencies : @recipes_with_dependencies
|
44
|
+
end
|
45
|
+
|
46
|
+
def compile
|
47
|
+
render 'layout', binding
|
48
|
+
end
|
49
|
+
|
50
|
+
def args
|
51
|
+
recipes.map(&:args).uniq
|
52
|
+
end
|
53
|
+
|
54
|
+
def custom_code?; false end
|
55
|
+
def custom_code; nil end
|
56
|
+
end
|
57
|
+
end
|