rails_wizard 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/README.markdown +95 -0
  2. data/bin/rails_wizard +7 -0
  3. data/lib/rails_wizard.rb +10 -0
  4. data/lib/rails_wizard/command.rb +78 -0
  5. data/lib/rails_wizard/config.rb +86 -0
  6. data/lib/rails_wizard/recipe.rb +106 -0
  7. data/lib/rails_wizard/recipes.rb +38 -0
  8. data/lib/rails_wizard/template.rb +57 -0
  9. data/recipes/activerecord.rb +37 -0
  10. data/recipes/capybara.rb +34 -0
  11. data/recipes/cucumber.rb +16 -0
  12. data/recipes/devise.rb +23 -0
  13. data/recipes/git.rb +15 -0
  14. data/recipes/haml.rb +11 -0
  15. data/recipes/heroku.rb +58 -0
  16. data/recipes/hoptoad.rb +31 -0
  17. data/recipes/jammit.rb +43 -0
  18. data/recipes/jquery.rb +23 -0
  19. data/recipes/less.rb +12 -0
  20. data/recipes/mongo_mapper.rb +18 -0
  21. data/recipes/mongohq.rb +59 -0
  22. data/recipes/mongoid.rb +18 -0
  23. data/recipes/mootools.rb +23 -0
  24. data/recipes/omniauth.rb +15 -0
  25. data/recipes/prototype.rb +11 -0
  26. data/recipes/redis.rb +11 -0
  27. data/recipes/rightjs.rb +17 -0
  28. data/recipes/rspec.rb +21 -0
  29. data/recipes/sass.rb +13 -0
  30. data/recipes/sequel.rb +13 -0
  31. data/recipes/slim.rb +11 -0
  32. data/recipes/test_unit.rb +11 -0
  33. data/spec/rails_wizard/config_spec.rb +99 -0
  34. data/spec/rails_wizard/recipe_spec.rb +103 -0
  35. data/spec/rails_wizard/recipes/sanity_spec.rb +30 -0
  36. data/spec/rails_wizard/recipes_spec.rb +24 -0
  37. data/spec/rails_wizard/template_spec.rb +48 -0
  38. data/spec/spec_helper.rb +11 -0
  39. data/spec/support/rails_directory.rb +17 -0
  40. data/spec/support/template_runner.rb +28 -0
  41. data/templates/helpers.erb +44 -0
  42. data/templates/layout.erb +42 -0
  43. data/templates/recipe.erb +9 -0
  44. data/version.rb +3 -0
  45. 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
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ $:.push File.dirname(__FILE__) + '/../lib'
3
+
4
+ require 'rubygems'
5
+ require 'rails_wizard/command'
6
+
7
+ RailsWizard::Command.start
@@ -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