rails_wizard 0.1.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/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
|