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.
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