├── .document ├── .gitignore ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.mdown ├── Rakefile ├── VERSION ├── bin └── snfn ├── lib ├── extensions │ └── string.rb ├── snfn.rb └── templates │ ├── Gemfile │ ├── Procfile │ ├── README.mdown │ ├── Rakefile │ ├── app.rb │ ├── config.ru │ ├── config │ ├── db.yml │ ├── initializers │ │ ├── database.rb │ │ └── redis.rb │ ├── redis.yml │ └── unicorn.rb │ ├── public │ ├── css │ │ └── scaffold.css │ ├── img │ │ └── .gitkeep │ └── js │ │ ├── app.js.tt │ │ └── lib │ │ └── modernizr-2.0.6.js │ └── views │ ├── layout.erb.tt │ └── welcome.erb ├── snfn.gemspec ├── test ├── helper.rb ├── test_extension_string.rb └── test_snfn.rb └── vendor └── cache ├── fakefs-0.3.2.gem ├── git-1.2.5.gem ├── jeweler-1.6.4.gem ├── rake-0.9.2.gem └── thor-0.14.6.gem /.document: -------------------------------------------------------------------------------- 1 | lib/**/*.rb 2 | bin/* 3 | - 4 | features/**/*.feature 5 | LICENSE.txt 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # rcov generated 2 | coverage 3 | 4 | # rdoc generated 5 | rdoc 6 | 7 | # yard generated 8 | doc 9 | .yardoc 10 | 11 | # bundler 12 | .bundle 13 | 14 | # jeweler generated 15 | pkg 16 | 17 | # bundled gems 18 | vendor/ruby 19 | 20 | .DS_Store 21 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | # Add dependencies required to use your gem here. 3 | # Example: 4 | # gem "activesupport", ">= 2.3.5" 5 | 6 | # Add dependencies to develop your gem here. 7 | # Include everything needed to run rake, tests, features, etc. 8 | group :development do 9 | # gem "shoulda", ">= 0" 10 | gem "bundler", "~> 1.0.0" 11 | gem "fakefs", "~> 0.3.2" 12 | gem "jeweler", "~> 1.6.4" 13 | # gem "rcov", ">= 0" 14 | end 15 | 16 | gem "thor", "~> 0.14.6" 17 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | fakefs (0.3.2) 5 | git (1.2.5) 6 | jeweler (1.6.4) 7 | bundler (~> 1.0) 8 | git (>= 1.2.5) 9 | rake 10 | rake (0.9.2) 11 | thor (0.14.6) 12 | 13 | PLATFORMS 14 | ruby 15 | 16 | DEPENDENCIES 17 | bundler (~> 1.0.0) 18 | fakefs (~> 0.3.2) 19 | jeweler (~> 1.6.4) 20 | thor (~> 0.14.6) 21 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Zach Pendleton 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.mdown: -------------------------------------------------------------------------------- 1 | # Snfn - A Sinatra application generator 2 | 3 | Snfn is an opinionated generator for Sinatra projects. It 4 | can be configured using the following options: 5 | 6 | * `-d` Database. Options are "postgres," "mysql," "sqlite", 7 | and "mongo." Default is "sqlite." 8 | * `--redis` Include Redis configuration options. 9 | * `--no-database` Don't include any database config options. 10 | * `--no-heroku` Don't include Heroku config options. 11 | 12 | ## Installation 13 | 14 | gem install snfn 15 | 16 | ## Example 17 | 18 | Standard Heroku app, using sqlite for local development and 19 | PostgreSQL for production. 20 | 21 | snfn my_app 22 | 23 | App without Heroku options and mysql config. 24 | 25 | snfn my_app -d mysql --no-heroku 26 | 27 | ## Architecture 28 | 29 | The template autoloads files in config/initializers and 30 | /lib. Database configuration options are stored in `config/db.yml` 31 | and are loaded via `config/initializers/database.rb`. 32 | 33 | Right now Snfn only comes out of the box with support for Unicorn, (the 34 | config file is stored at config/unicorn.rb), but changing it out for 35 | thin or mongrel is pretty trivial. 36 | 37 | ## DB Setup 38 | 39 | [Sequel](http://sequel.rubyforge.org) is used as an ORM for 40 | relational databases, and migrations are stored in db/migrate. 41 | Migrations can be run using the rake command `rake rb:migrate`. 42 | 43 | MongoMapper is used for Mongo apps, with the config options 44 | stored in the same files as relational databases (`config/db.yml` 45 | and `config/initializers/database.rb`). 46 | 47 | ### More information 48 | 49 | * [Sequel Migrations](http://sequel.rubyforge.org/rdoc/files/doc/migration_rdoc.html) 50 | * [Sequel Models](http://sequel.rubyforge.org/rdoc/classes/Sequel/Model.html) 51 | * [MongoMapper](http://mongomapper.com) 52 | 53 | If there is any interest in supporting other ORMs like DataMapper or 54 | ActiveRecord, either please let me know or implement it yourself and 55 | send a pull request. 56 | 57 | ## Set up on Heroku 58 | 59 | By default, Snfn creates an app with the proper options for deployment 60 | on Heroku. To get Snfn up and running on Heroku: 61 | 62 | 1. Install your gems with `bundle install` 63 | 2. Create a new git repository: `git init .` 64 | 3. Make your first commit: `git add .; git commit -m "initial commit"` 65 | 4. Create a new heroku app: `bundle exec heroku create --stack cedar` 66 | 5. Deploy the app: `git push heroku master` 67 | 68 | ### Heroku Add-Ons 69 | 70 | To use PostgreSQL, MongoDB, or Redis on Heroku, you'll need to add 71 | the proper Heroku add-ons. The commands to do this are, respectively: 72 | 73 | heroku addons:add shared-database # PostgreSQL 74 | heroku addons:add mongolab:starter # MongoDB 75 | heroku addons:add redistogo:nano # Redis 76 | 77 | ## Contributing to snfn 78 | 79 | * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet 80 | * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it 81 | * Fork the project 82 | * Start a feature/bugfix branch 83 | * Commit and push until you are happy with your contribution 84 | * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally. 85 | * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it. 86 | 87 | ## Copyright 88 | 89 | Copyright (c) 2011 Zach Pendleton. See LICENSE.txt for 90 | further details. 91 | 92 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'rubygems' 4 | require 'bundler' 5 | begin 6 | Bundler.setup(:default, :development) 7 | rescue Bundler::BundlerError => e 8 | $stderr.puts e.message 9 | $stderr.puts "Run `bundle install` to install missing gems" 10 | exit e.status_code 11 | end 12 | require 'rake' 13 | 14 | require 'jeweler' 15 | Jeweler::Tasks.new do |gem| 16 | # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options 17 | gem.name = "snfn" 18 | gem.homepage = "http://github.com/zachpendleton/snfn" 19 | gem.license = "MIT" 20 | gem.summary = %Q{A Heroku-friendy Sinatra app generator} 21 | gem.description = %Q{An app generator for Sinatra apps with an eye towards easy Heroku setup and deployment.} 22 | gem.email = "zachpendleton@gmail.com" 23 | gem.authors = ["Zach Pendleton"] 24 | # dependencies defined in Gemfile 25 | end 26 | Jeweler::RubygemsDotOrgTasks.new 27 | 28 | require 'rake/testtask' 29 | Rake::TestTask.new(:test) do |test| 30 | test.libs << 'lib' << 'test' 31 | test.pattern = 'test/**/test_*.rb' 32 | test.verbose = true 33 | end 34 | 35 | task :default => :test 36 | 37 | require 'rake/rdoctask' 38 | Rake::RDocTask.new do |rdoc| 39 | version = File.exist?('VERSION') ? File.read('VERSION') : "" 40 | 41 | rdoc.rdoc_dir = 'rdoc' 42 | rdoc.title = "snfn #{version}" 43 | rdoc.rdoc_files.include('README*') 44 | rdoc.rdoc_files.include('lib/**/*.rb') 45 | end 46 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.3.0 2 | -------------------------------------------------------------------------------- /bin/snfn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))) unless $:.include? File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")) 4 | require "snfn" 5 | 6 | Snfn::Generator.start 7 | -------------------------------------------------------------------------------- /lib/extensions/string.rb: -------------------------------------------------------------------------------- 1 | module Snfn 2 | module Extensions 3 | module String 4 | def camel_case 5 | return self.gsub(/^./) { |l| l.capitalize } if !match(/[_-]/) 6 | altered_self = self.downcase.capitalize 7 | altered_self.scan(/[_-][a-zA-Z]/).each do |match| 8 | altered_self.gsub!(match, match[1].upcase) 9 | end 10 | 11 | altered_self 12 | end 13 | 14 | def camel_case! 15 | self.replace camel_case 16 | end 17 | 18 | def file_name 19 | return self.gsub(/-/, "_") if !match(/[A-Z]/) 20 | altered_self = self.strip 21 | 22 | altered_self.scan(/[A-Z]/).each do |match| 23 | altered_self.gsub!(match, "_#{match.downcase}") 24 | end 25 | 26 | altered_self.sub(/^_/, "").gsub(/_{2,}+/, "_").downcase 27 | end 28 | 29 | def file_name! 30 | self.replace file_name 31 | end 32 | end 33 | end 34 | end 35 | 36 | String.send(:include, Snfn::Extensions::String) 37 | -------------------------------------------------------------------------------- /lib/snfn.rb: -------------------------------------------------------------------------------- 1 | %w{rubygems extensions/string thor/group}.each { |lib| require lib } 2 | 3 | module Snfn 4 | class Generator < Thor::Group 5 | include Thor::Actions 6 | 7 | desc "Creates a new snfn application" 8 | argument :name, :type => :string, :desc => "The name of the new application" 9 | class_option :database, :aliases => "-d", :default => "sqlite", :desc => "The type of database to use" 10 | class_option :no_heroku, :type => :boolean, :desc => "Exclude Heroku configuration" 11 | class_option :no_database, :type => :boolean, :desc => "Exclude all database configuration files" 12 | class_option :redis, :type => :boolean, :desc => "Include Redis configuration" 13 | 14 | # Creates instance variables from options passed to snfn. 15 | def setup 16 | @name = @app_path = name.file_name 17 | options.each do |key, value| 18 | instance_variable_set "@#{key.to_s}".to_sym, value 19 | end 20 | end 21 | 22 | def self.source_root 23 | File.expand_path(File.join(File.dirname(__FILE__), "..", "lib", "templates")) 24 | end 25 | 26 | # Create empty directories 27 | def create_empty_directories 28 | %w{config/initializers db/migrate lib log tmp}.each do |dir| 29 | empty_directory File.join(@app_path, dir) 30 | end 31 | end 32 | 33 | def create_public_directory 34 | %w{public/css public/js public/img}.each do |dir| 35 | directory dir, File.join(@app_path, dir) 36 | end 37 | end 38 | 39 | def create_view_directory 40 | directory "views", File.join(@app_path, "views") 41 | end 42 | 43 | def create_app 44 | template "app.rb", File.join(@app_path, "#{@name}.rb") 45 | end 46 | 47 | def create_config 48 | template "config.ru", File.join(@app_path, "config.ru") 49 | end 50 | 51 | def create_gemfile 52 | template "Gemfile", File.join(@app_path, "Gemfile") 53 | end 54 | 55 | def create_procfile 56 | copy_file("Procfile", File.join(@app_path, "Procfile")) unless @no_heroku 57 | end 58 | 59 | def create_rakefile 60 | copy_file "Rakefile", File.join(@app_path, "Rakefile") 61 | end 62 | 63 | def create_readme 64 | copy_file "README.mdown", File.join(@app_path, "README.mdown") 65 | end 66 | 67 | def create_server_config 68 | template "config/unicorn.rb", File.join(@app_path, "config/unicorn.rb") 69 | end 70 | 71 | def create_db_config 72 | template("config/db.yml", File.join(@app_path, "config/db.yml")) unless @no_database 73 | end 74 | 75 | def create_database_initializer 76 | template("config/initializers/database.rb", File.join(@app_path, "config/initializers/database.rb")) unless @no_database 77 | end 78 | 79 | def create_redis_config 80 | copy_file("config/redis.yml", File.join(@app_path, "config/redis.yml")) if @redis 81 | end 82 | 83 | def create_redis_initializer 84 | template("config/initializers/redis.rb", File.join(@app_path, "config/initializers/redis.rb")) if @redis 85 | end 86 | 87 | def create_lib_gitkeep 88 | create_file File.join(@app_path, "lib", ".gitkeep") 89 | end 90 | end 91 | end 92 | 93 | -------------------------------------------------------------------------------- /lib/templates/Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | # App Stack 4 | <% if !@no_heroku -%> 5 | gem "foreman" 6 | gem "heroku" 7 | <% end -%> 8 | gem "sinatra" 9 | gem "unicorn" 10 | 11 | <% unless @no_database -%> 12 | # Database Stack 13 | <% unless @database == 'mongo' -%> 14 | gem "sequel" 15 | <% end -%> 16 | <% if @database == "sqlite" -%> 17 | <% if !@no_heroku -%> 18 | group :development, :test do 19 | gem "sqlite3" 20 | end 21 | <% else -%> 22 | gem "sqlite3" 23 | <% end -%> 24 | <% elsif @database == "postgres" -%> 25 | gem "pg" 26 | <% elsif @database == "mysql" -%> 27 | <% if !@no_heroku -%> 28 | group :development, :test do 29 | gem "mysql2" 30 | end 31 | <% else -%> 32 | gem "mysql2" 33 | <% end -%> 34 | <% elsif @database == "mongo" -%> 35 | gem "bson_ext" 36 | gem "mongo_mapper" 37 | <% end -%> 38 | <% if !@no_heroku && @database != "postgres" && @database != "mongo" -%> 39 | group :production do 40 | gem "pg" 41 | end 42 | <% end -%> 43 | <% end -%> 44 | <% if @redis -%> 45 | # Redis 46 | gem "redis" 47 | <% end -%> 48 | 49 | -------------------------------------------------------------------------------- /lib/templates/Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb 2 | -------------------------------------------------------------------------------- /lib/templates/README.mdown: -------------------------------------------------------------------------------- 1 | # Snfn - A Sinatra application generator 2 | 3 | Snfn is an opinionated generator for Sinatra projects. It 4 | can be configured using the following options: 5 | 6 | * `-d` Database. Options are "postgres," "mysql," "sqlite", 7 | and "mongo." Default is "sqlite." 8 | * `--redis` Include Redis configuration options. 9 | * `--no-database` Don't include any database config options. 10 | * `--no-heroku` Don't include Heroku config options. 11 | 12 | ## Installation 13 | 14 | gem install snfn 15 | 16 | ## Example 17 | 18 | Standard Heroku app, using sqlite for local development and 19 | PostgreSQL for production. 20 | 21 | snfn my_app 22 | 23 | App without Heroku options and mysql config. 24 | 25 | snfn my_app -d mysql --no-heroku 26 | 27 | ## Architecture 28 | 29 | The template autoloads files in config/initializers and 30 | /lib. Database configuration options are stored in `config/db.yml` 31 | and are loaded via `config/initializers/database.rb`. 32 | 33 | Right now Snfn only comes out of the box with support for Unicorn, (the 34 | config file is stored at config/unicorn.rb), but changing it out for 35 | thin or mongrel is pretty trivial. 36 | 37 | ## DB Setup 38 | 39 | [Sequel](http://sequel.rubyforge.org) is used as an ORM for 40 | relational databases, and migrations are stored in db/migrate. 41 | Migrations can be run using the rake command `rake rb:migrate`. 42 | 43 | MongoMapper is used for Mongo apps, with the config options 44 | stored in the same files as relational databases (`config/db.yml` 45 | and `config/initializers/database.rb`). 46 | 47 | ### More information 48 | 49 | * [Sequel Migrations](http://sequel.rubyforge.org/rdoc/files/doc/migration_rdoc.html) 50 | * [Sequel Models](http://sequel.rubyforge.org/rdoc/classes/Sequel/Model.html) 51 | * [MongoMapper](http://mongomapper.com) 52 | 53 | If there is any interest in supporting other ORMs like DataMapper or 54 | ActiveRecord, either please let me know or implement it yourself and 55 | send a pull request. 56 | 57 | ## Set up on Heroku 58 | 59 | By default, Snfn creates an app with the proper options for deployment 60 | on Heroku. To get Snfn up and running on Heroku: 61 | 62 | 1. Install your gems with `bundle install` 63 | 2. Create a new git repository: `git init .` 64 | 3. Make your first commit: `git add .; git commit -m "initial commit"` 65 | 4. Create a new heroku app: `bundle exec heroku create --stack cedar` 66 | 5. Deploy the app: `git push heroku master` 67 | 68 | ### Heroku Add-Ons 69 | 70 | To use PostgreSQL, MongoDB, or Redis on Heroku, you'll need to add 71 | the proper Heroku add-ons. The commands to do this are, respectively: 72 | 73 | heroku addons:add shared-database # PostgreSQL 74 | heroku addons:add mongolab:starter # MongoDB 75 | heroku addons:add redistogo:nano # Redis 76 | 77 | -------------------------------------------------------------------------------- /lib/templates/Rakefile: -------------------------------------------------------------------------------- 1 | %w{ rubygems bundler find}.each { |lib| require lib } 2 | 3 | namespace :db do 4 | desc "Run all migrations in db/migrate" 5 | task :migrate => :connect do 6 | Sequel.extension(:migration) 7 | Sequel::Migrator.apply(DB, "db/migrate") 8 | end 9 | 10 | task :connect => :environment do 11 | require "./config/initializers/database" 12 | end 13 | end 14 | 15 | task :environment, [:env] do |cmd, args| 16 | Bundler.require 17 | ENV['RACK_ENV'] = args[:env] || "development" 18 | %w{ ./config/initializers ./lib }.each do 19 | Find.find(lib) { |f| require f unless f.match(/\/\..+$/) || File.directory?(f) } 20 | end 21 | end 22 | 23 | -------------------------------------------------------------------------------- /lib/templates/app.rb: -------------------------------------------------------------------------------- 1 | class <%= @name.camel_case %> < Sinatra::Base 2 | 3 | set :public => "public", :static => true 4 | 5 | get "/" do 6 | @version = RUBY_VERSION 7 | @environment = ENV['RACK_ENV'] 8 | 9 | erb :welcome 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/templates/config.ru: -------------------------------------------------------------------------------- 1 | # Load path and gems/bundler 2 | $LOAD_PATH << File.expand_path(File.dirname(__FILE__)) 3 | require "rubygems" 4 | require "bundler" 5 | Bundler.require 6 | 7 | # Local config 8 | require "find" 9 | %w{config/initializers lib}.each do |load_path| 10 | Find.find(load_path) { |f| require f unless f.match(/\/\..+$/) || File.directory?(f) } 11 | end 12 | 13 | # Load app 14 | require "<%= @name %>" 15 | run <%= @name.camel_case %> 16 | -------------------------------------------------------------------------------- /lib/templates/config/db.yml: -------------------------------------------------------------------------------- 1 | # Sequel Database Configuration 2 | <% if @database == "sqlite" %> 3 | development: "sqlite://db/development.sqlite3" 4 | test: "sqlite://db/test.sqlite3" 5 | production: "sqlite://db/production.sqlite3" 6 | <% elsif @database == "postgres" %> 7 | development: "postgres://<%= `whoami`.chop %>@localhost/<%= @name %>_development" 8 | test: "postgres://<%= `whoami`.chop %>@localhost/<%= @name %>_test" 9 | production: "postgres://<%= `whoami`.chop %>@localhost/<%= @name %>_production" 10 | <% elsif @database == "mysql" %> 11 | development: "mysql2://<%= `whoami`.chop %>@localhost/<%= @name %>_development" 12 | test: "mysql2://<%= `whoami`.chop %>@localhost/<%= @name %>_test" 13 | production: "mysql2://<%= `whoami`.chop %>@localhost/<%= @name %>_production" 14 | <% elsif @database == "mongo" %> 15 | development: 16 | host: localhost 17 | port: 27017 18 | database: <%= @name %>_development 19 | username: 20 | password: 21 | 22 | test: 23 | host: localhost 24 | port: 27017 25 | database: <%= @name %>_test 26 | username: 27 | password: 28 | 29 | production: 30 | host: localhost 31 | port: 27017 32 | database: <%= @name %>_production 33 | username: 34 | password: 35 | <% end %> 36 | -------------------------------------------------------------------------------- /lib/templates/config/initializers/database.rb: -------------------------------------------------------------------------------- 1 | <% unless @no_database -%> 2 | settings = YAML::load_file("config/db.yml") 3 | <% if @database != 'mongo' -%> 4 | # Sequel Configuration 5 | DB = Sequel.connect(<% if !@no_heroku %>ENV['DATABASE_URL'] || <% end %>settings[ENV['RACK_ENV']]) 6 | <% else -%> 7 | # MongoMapper Configuration 8 | <% if !@no_heroku -%> 9 | settings[ENV['RACK_ENV']]["uri"] = ENV['MONGOLAB_URI'] 10 | <% end -%> 11 | MongoMapper.config = settings 12 | MongoMapper.connect(ENV['RACK_ENV']) 13 | <% end -%> 14 | <% end -%> 15 | 16 | -------------------------------------------------------------------------------- /lib/templates/config/initializers/redis.rb: -------------------------------------------------------------------------------- 1 | # Redis Configuration 2 | if ENV['REDISTOGO_URL'] 3 | require "uri" 4 | uri = URI.parse(ENV['REDISTOGO_URL']) 5 | REDIS = Redis.new(:host => uri.host, :port => uri.port, :password => uri.password) 6 | else 7 | redis_settings = YAML::load_file("config/redis.yml") 8 | REDIS = Redis.new(redis_settings[ENV['RACK_ENV']]) 9 | end 10 | 11 | -------------------------------------------------------------------------------- /lib/templates/config/redis.yml: -------------------------------------------------------------------------------- 1 | development: 2 | host: localhost 3 | port: 6387 4 | password: 5 | 6 | test: 7 | host: localhost 8 | port: 6387 9 | password: 10 | 11 | production: 12 | host: localhost 13 | port: 6387 14 | password: 15 | -------------------------------------------------------------------------------- /lib/templates/config/unicorn.rb: -------------------------------------------------------------------------------- 1 | worker_processes 3 2 | timeout 30 3 | 4 | <% if @no_heroku -%> 5 | docroot = File.join(File.expand_path(File.dirname(__FILE__)), "..") 6 | 7 | listen "#{docroot}/tmp/sockets/unicorn.sock" 8 | pid "#{docroot}/tmp/pids/unicorn.pid" 9 | 10 | stderr_path "#{docroot}/log/stderr.log" 11 | stdout_path "#{docroot}/log/stdout.log" 12 | <% end -%> 13 | -------------------------------------------------------------------------------- /lib/templates/public/css/scaffold.css: -------------------------------------------------------------------------------- 1 | @media screen { 2 | /* --- Reset Styles --- */ 3 | * { 4 | list-style: none; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | html, body { 10 | height: 100%; 11 | width: 100%; 12 | } 13 | 14 | /* --- Welcome Page Styles --- */ 15 | body { 16 | background: #eee; 17 | color: #333; 18 | font-family: Sans-Serif; 19 | line-height: 18px; 20 | } 21 | 22 | .wrapper { 23 | background: #fff; 24 | -moz-box-shadow: 0 0 10px rgba(0,0,0,.3); 25 | -webkit-box-shadow: 0 0 10px rgba(0,0,0,.3); 26 | box-shadow: 0 0 10px rgba(0,0,0,.3); 27 | margin: 16px auto; 28 | max-width: 960px; 29 | padding: 2.25%; /* 18px / 800px */ 30 | width: 85%; 31 | } 32 | 33 | h1 { 34 | font-size: 36px; 35 | line-height: 54px; 36 | } 37 | 38 | h2 { 39 | border-bottom: 2px solid #ccc; 40 | font-size: 24px; 41 | line-height: 36px; 42 | margin-bottom: 16px; 43 | } 44 | 45 | h3 { 46 | font-size: 18px; 47 | line-height: 36px; 48 | } 49 | 50 | p { 51 | margin-bottom: 18px; 52 | } 53 | 54 | .main { 55 | overflow: hidden; 56 | } 57 | 58 | .content { 59 | float: left; 60 | width: 60%; /* 480px / 800px */ 61 | } 62 | 63 | .sidebar { 64 | background: #eee; 65 | border: 1px solid #ccc; 66 | float: right; 67 | padding: 3.75%; /* 9px / 240px */ 68 | width: 30%; /* 240px / 800px */ 69 | } 70 | 71 | .sidebar ul { 72 | font-size: 14px; 73 | } 74 | 75 | .branding { 76 | clear: both; 77 | } 78 | 79 | footer.branding { 80 | border-top: 2px solid #ccc; 81 | margin-top: 20px; 82 | padding-top: 20px; 83 | } 84 | } 85 | 86 | @media screen and (max-width: 480px) { 87 | .wrapper { 88 | -moz-box-shadow: none; 89 | -webkit-box-shadow: none; 90 | box-shadow: none; 91 | width: auto; 92 | } 93 | 94 | .content, .sidebar { 95 | float: none; 96 | width: 100%; 97 | } 98 | 99 | .sidebar { 100 | background: transparent; 101 | border: none; 102 | border-top: 2px solid #ccc; 103 | padding: 0; 104 | } 105 | 106 | h1 { 107 | font-size: 24px; 108 | line-height: 36px; 109 | } 110 | 111 | h2 { 112 | font-size: 18px; 113 | line-height: 24px; 114 | } 115 | } 116 | 117 | -------------------------------------------------------------------------------- /lib/templates/public/img/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zachpendleton/snfn/9c897b16ed342fb9becb34f40c0d3e768ca25409/lib/templates/public/img/.gitkeep -------------------------------------------------------------------------------- /lib/templates/public/js/app.js.tt: -------------------------------------------------------------------------------- 1 | /** 2 | * Application-specific JavaScript goes here. 3 | * 4 | */ 5 | 6 | (function(){ 7 | var <%= @name.camel_case %> = { 8 | init : function(){ 9 | return this; 10 | } 11 | } 12 | 13 | return <%= @name.camel_case %>.init(); 14 | })(); 15 | -------------------------------------------------------------------------------- /lib/templates/public/js/lib/modernizr-2.0.6.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Modernizr v2.0.6 3 | * http://www.modernizr.com 4 | * 5 | * Copyright (c) 2009-2011 Faruk Ates, Paul Irish, Alex Sexton 6 | * Dual-licensed under the BSD or MIT licenses: www.modernizr.com/license/ 7 | */ 8 | 9 | /* 10 | * Modernizr tests which native CSS3 and HTML5 features are available in 11 | * the current UA and makes the results available to you in two ways: 12 | * as properties on a global Modernizr object, and as classes on the 13 | * element. This information allows you to progressively enhance 14 | * your pages with a granular level of control over the experience. 15 | * 16 | * Modernizr has an optional (not included) conditional resource loader 17 | * called Modernizr.load(), based on Yepnope.js (yepnopejs.com). 18 | * To get a build that includes Modernizr.load(), as well as choosing 19 | * which tests to include, go to www.modernizr.com/download/ 20 | * 21 | * Authors Faruk Ates, Paul Irish, Alex Sexton, 22 | * Contributors Ryan Seddon, Ben Alman 23 | */ 24 | 25 | window.Modernizr = (function( window, document, undefined ) { 26 | 27 | var version = '2.0.6', 28 | 29 | Modernizr = {}, 30 | 31 | // option for enabling the HTML classes to be added 32 | enableClasses = true, 33 | 34 | docElement = document.documentElement, 35 | docHead = document.head || document.getElementsByTagName('head')[0], 36 | 37 | /** 38 | * Create our "modernizr" element that we do most feature tests on. 39 | */ 40 | mod = 'modernizr', 41 | modElem = document.createElement(mod), 42 | mStyle = modElem.style, 43 | 44 | /** 45 | * Create the input element for various Web Forms feature tests. 46 | */ 47 | inputElem = document.createElement('input'), 48 | 49 | smile = ':)', 50 | 51 | toString = Object.prototype.toString, 52 | 53 | // List of property values to set for css tests. See ticket #21 54 | prefixes = ' -webkit- -moz- -o- -ms- -khtml- '.split(' '), 55 | 56 | // Following spec is to expose vendor-specific style properties as: 57 | // elem.style.WebkitBorderRadius 58 | // and the following would be incorrect: 59 | // elem.style.webkitBorderRadius 60 | 61 | // Webkit ghosts their properties in lowercase but Opera & Moz do not. 62 | // Microsoft foregoes prefixes entirely <= IE8, but appears to 63 | // use a lowercase `ms` instead of the correct `Ms` in IE9 64 | 65 | // More here: http://github.com/Modernizr/Modernizr/issues/issue/21 66 | domPrefixes = 'Webkit Moz O ms Khtml'.split(' '), 67 | 68 | ns = {'svg': 'http://www.w3.org/2000/svg'}, 69 | 70 | tests = {}, 71 | inputs = {}, 72 | attrs = {}, 73 | 74 | classes = [], 75 | 76 | featureName, // used in testing loop 77 | 78 | 79 | // Inject element with style element and some CSS rules 80 | injectElementWithStyles = function( rule, callback, nodes, testnames ) { 81 | 82 | var style, ret, node, 83 | div = document.createElement('div'); 84 | 85 | if ( parseInt(nodes, 10) ) { 86 | // In order not to give false positives we create a node for each test 87 | // This also allows the method to scale for unspecified uses 88 | while ( nodes-- ) { 89 | node = document.createElement('div'); 90 | node.id = testnames ? testnames[nodes] : mod + (nodes + 1); 91 | div.appendChild(node); 92 | } 93 | } 94 | 95 | // '].join(''); 100 | div.id = mod; 101 | div.innerHTML += style; 102 | docElement.appendChild(div); 103 | 104 | ret = callback(div, rule); 105 | div.parentNode.removeChild(div); 106 | 107 | return !!ret; 108 | 109 | }, 110 | 111 | 112 | // adapted from matchMedia polyfill 113 | // by Scott Jehl and Paul Irish 114 | // gist.github.com/786768 115 | testMediaQuery = function( mq ) { 116 | 117 | if ( window.matchMedia ) { 118 | return matchMedia(mq).matches; 119 | } 120 | 121 | var bool; 122 | 123 | injectElementWithStyles('@media ' + mq + ' { #' + mod + ' { position: absolute; } }', function( node ) { 124 | bool = (window.getComputedStyle ? 125 | getComputedStyle(node, null) : 126 | node.currentStyle)['position'] == 'absolute'; 127 | }); 128 | 129 | return bool; 130 | 131 | }, 132 | 133 | 134 | /** 135 | * isEventSupported determines if a given element supports the given event 136 | * function from http://yura.thinkweb2.com/isEventSupported/ 137 | */ 138 | isEventSupported = (function() { 139 | 140 | var TAGNAMES = { 141 | 'select': 'input', 'change': 'input', 142 | 'submit': 'form', 'reset': 'form', 143 | 'error': 'img', 'load': 'img', 'abort': 'img' 144 | }; 145 | 146 | function isEventSupported( eventName, element ) { 147 | 148 | element = element || document.createElement(TAGNAMES[eventName] || 'div'); 149 | eventName = 'on' + eventName; 150 | 151 | // When using `setAttribute`, IE skips "unload", WebKit skips "unload" and "resize", whereas `in` "catches" those 152 | var isSupported = eventName in element; 153 | 154 | if ( !isSupported ) { 155 | // If it has no `setAttribute` (i.e. doesn't implement Node interface), try generic element 156 | if ( !element.setAttribute ) { 157 | element = document.createElement('div'); 158 | } 159 | if ( element.setAttribute && element.removeAttribute ) { 160 | element.setAttribute(eventName, ''); 161 | isSupported = is(element[eventName], 'function'); 162 | 163 | // If property was created, "remove it" (by setting value to `undefined`) 164 | if ( !is(element[eventName], undefined) ) { 165 | element[eventName] = undefined; 166 | } 167 | element.removeAttribute(eventName); 168 | } 169 | } 170 | 171 | element = null; 172 | return isSupported; 173 | } 174 | return isEventSupported; 175 | })(); 176 | 177 | // hasOwnProperty shim by kangax needed for Safari 2.0 support 178 | var _hasOwnProperty = ({}).hasOwnProperty, hasOwnProperty; 179 | if ( !is(_hasOwnProperty, undefined) && !is(_hasOwnProperty.call, undefined) ) { 180 | hasOwnProperty = function (object, property) { 181 | return _hasOwnProperty.call(object, property); 182 | }; 183 | } 184 | else { 185 | hasOwnProperty = function (object, property) { /* yes, this can give false positives/negatives, but most of the time we don't care about those */ 186 | return ((property in object) && is(object.constructor.prototype[property], undefined)); 187 | }; 188 | } 189 | 190 | /** 191 | * setCss applies given styles to the Modernizr DOM node. 192 | */ 193 | function setCss( str ) { 194 | mStyle.cssText = str; 195 | } 196 | 197 | /** 198 | * setCssAll extrapolates all vendor-specific css strings. 199 | */ 200 | function setCssAll( str1, str2 ) { 201 | return setCss(prefixes.join(str1 + ';') + ( str2 || '' )); 202 | } 203 | 204 | /** 205 | * is returns a boolean for if typeof obj is exactly type. 206 | */ 207 | function is( obj, type ) { 208 | return typeof obj === type; 209 | } 210 | 211 | /** 212 | * contains returns a boolean for if substr is found within str. 213 | */ 214 | function contains( str, substr ) { 215 | return !!~('' + str).indexOf(substr); 216 | } 217 | 218 | /** 219 | * testProps is a generic CSS / DOM property test; if a browser supports 220 | * a certain property, it won't return undefined for it. 221 | * A supported CSS property returns empty string when its not yet set. 222 | */ 223 | function testProps( props, prefixed ) { 224 | for ( var i in props ) { 225 | if ( mStyle[ props[i] ] !== undefined ) { 226 | return prefixed == 'pfx' ? props[i] : true; 227 | } 228 | } 229 | return false; 230 | } 231 | 232 | /** 233 | * testPropsAll tests a list of DOM properties we want to check against. 234 | * We specify literally ALL possible (known and/or likely) properties on 235 | * the element including the non-vendor prefixed one, for forward- 236 | * compatibility. 237 | */ 238 | function testPropsAll( prop, prefixed ) { 239 | 240 | var ucProp = prop.charAt(0).toUpperCase() + prop.substr(1), 241 | props = (prop + ' ' + domPrefixes.join(ucProp + ' ') + ucProp).split(' '); 242 | 243 | return testProps(props, prefixed); 244 | } 245 | 246 | /** 247 | * testBundle tests a list of CSS features that require element and style injection. 248 | * By bundling them together we can reduce the need to touch the DOM multiple times. 249 | */ 250 | /*>>testBundle*/ 251 | var testBundle = (function( styles, tests ) { 252 | var style = styles.join(''), 253 | len = tests.length; 254 | 255 | injectElementWithStyles(style, function( node, rule ) { 256 | var style = document.styleSheets[document.styleSheets.length - 1], 257 | // IE8 will bork if you create a custom build that excludes both fontface and generatedcontent tests. 258 | // So we check for cssRules and that there is a rule available 259 | // More here: https://github.com/Modernizr/Modernizr/issues/288 & https://github.com/Modernizr/Modernizr/issues/293 260 | cssText = style.cssRules && style.cssRules[0] ? style.cssRules[0].cssText : style.cssText || "", 261 | children = node.childNodes, hash = {}; 262 | 263 | while ( len-- ) { 264 | hash[children[len].id] = children[len]; 265 | } 266 | 267 | /*>>touch*/ Modernizr['touch'] = ('ontouchstart' in window) || hash['touch'].offsetTop === 9; /*>>touch*/ 268 | /*>>csstransforms3d*/ Modernizr['csstransforms3d'] = hash['csstransforms3d'].offsetLeft === 9; /*>>csstransforms3d*/ 269 | /*>>generatedcontent*/Modernizr['generatedcontent'] = hash['generatedcontent'].offsetHeight >= 1; /*>>generatedcontent*/ 270 | /*>>fontface*/ Modernizr['fontface'] = /src/i.test(cssText) && 271 | cssText.indexOf(rule.split(' ')[0]) === 0; /*>>fontface*/ 272 | }, len, tests); 273 | 274 | })([ 275 | // Pass in styles to be injected into document 276 | /*>>fontface*/ '@font-face {font-family:"font";src:url("https://")}' /*>>fontface*/ 277 | 278 | /*>>touch*/ ,['@media (',prefixes.join('touch-enabled),('),mod,')', 279 | '{#touch{top:9px;position:absolute}}'].join('') /*>>touch*/ 280 | 281 | /*>>csstransforms3d*/ ,['@media (',prefixes.join('transform-3d),('),mod,')', 282 | '{#csstransforms3d{left:9px;position:absolute}}'].join('')/*>>csstransforms3d*/ 283 | 284 | /*>>generatedcontent*/,['#generatedcontent:after{content:"',smile,'";visibility:hidden}'].join('') /*>>generatedcontent*/ 285 | ], 286 | [ 287 | /*>>fontface*/ 'fontface' /*>>fontface*/ 288 | /*>>touch*/ ,'touch' /*>>touch*/ 289 | /*>>csstransforms3d*/ ,'csstransforms3d' /*>>csstransforms3d*/ 290 | /*>>generatedcontent*/,'generatedcontent' /*>>generatedcontent*/ 291 | 292 | ]);/*>>testBundle*/ 293 | 294 | 295 | /** 296 | * Tests 297 | * ----- 298 | */ 299 | 300 | tests['flexbox'] = function() { 301 | /** 302 | * setPrefixedValueCSS sets the property of a specified element 303 | * adding vendor prefixes to the VALUE of the property. 304 | * @param {Element} element 305 | * @param {string} property The property name. This will not be prefixed. 306 | * @param {string} value The value of the property. This WILL be prefixed. 307 | * @param {string=} extra Additional CSS to append unmodified to the end of 308 | * the CSS string. 309 | */ 310 | function setPrefixedValueCSS( element, property, value, extra ) { 311 | property += ':'; 312 | element.style.cssText = (property + prefixes.join(value + ';' + property)).slice(0, -property.length) + (extra || ''); 313 | } 314 | 315 | /** 316 | * setPrefixedPropertyCSS sets the property of a specified element 317 | * adding vendor prefixes to the NAME of the property. 318 | * @param {Element} element 319 | * @param {string} property The property name. This WILL be prefixed. 320 | * @param {string} value The value of the property. This will not be prefixed. 321 | * @param {string=} extra Additional CSS to append unmodified to the end of 322 | * the CSS string. 323 | */ 324 | function setPrefixedPropertyCSS( element, property, value, extra ) { 325 | element.style.cssText = prefixes.join(property + ':' + value + ';') + (extra || ''); 326 | } 327 | 328 | var c = document.createElement('div'), 329 | elem = document.createElement('div'); 330 | 331 | setPrefixedValueCSS(c, 'display', 'box', 'width:42px;padding:0;'); 332 | setPrefixedPropertyCSS(elem, 'box-flex', '1', 'width:10px;'); 333 | 334 | c.appendChild(elem); 335 | docElement.appendChild(c); 336 | 337 | var ret = elem.offsetWidth === 42; 338 | 339 | c.removeChild(elem); 340 | docElement.removeChild(c); 341 | 342 | return ret; 343 | }; 344 | 345 | // On the S60 and BB Storm, getContext exists, but always returns undefined 346 | // http://github.com/Modernizr/Modernizr/issues/issue/97/ 347 | 348 | tests['canvas'] = function() { 349 | var elem = document.createElement('canvas'); 350 | return !!(elem.getContext && elem.getContext('2d')); 351 | }; 352 | 353 | tests['canvastext'] = function() { 354 | return !!(Modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function')); 355 | }; 356 | 357 | // This WebGL test may false positive. 358 | // But really it's quite impossible to know whether webgl will succeed until after you create the context. 359 | // You might have hardware that can support a 100x100 webgl canvas, but will not support a 1000x1000 webgl 360 | // canvas. So this feature inference is weak, but intentionally so. 361 | 362 | // It is known to false positive in FF4 with certain hardware and the iPad 2. 363 | 364 | tests['webgl'] = function() { 365 | return !!window.WebGLRenderingContext; 366 | }; 367 | 368 | /* 369 | * The Modernizr.touch test only indicates if the browser supports 370 | * touch events, which does not necessarily reflect a touchscreen 371 | * device, as evidenced by tablets running Windows 7 or, alas, 372 | * the Palm Pre / WebOS (touch) phones. 373 | * 374 | * Additionally, Chrome (desktop) used to lie about its support on this, 375 | * but that has since been rectified: http://crbug.com/36415 376 | * 377 | * We also test for Firefox 4 Multitouch Support. 378 | * 379 | * For more info, see: http://modernizr.github.com/Modernizr/touch.html 380 | */ 381 | 382 | tests['touch'] = function() { 383 | return Modernizr['touch']; 384 | }; 385 | 386 | /** 387 | * geolocation tests for the new Geolocation API specification. 388 | * This test is a standards compliant-only test; for more complete 389 | * testing, including a Google Gears fallback, please see: 390 | * http://code.google.com/p/geo-location-javascript/ 391 | * or view a fallback solution using google's geo API: 392 | * http://gist.github.com/366184 393 | */ 394 | tests['geolocation'] = function() { 395 | return !!navigator.geolocation; 396 | }; 397 | 398 | // Per 1.6: 399 | // This used to be Modernizr.crosswindowmessaging but the longer 400 | // name has been deprecated in favor of a shorter and property-matching one. 401 | // The old API is still available in 1.6, but as of 2.0 will throw a warning, 402 | // and in the first release thereafter disappear entirely. 403 | tests['postmessage'] = function() { 404 | return !!window.postMessage; 405 | }; 406 | 407 | // Web SQL database detection is tricky: 408 | 409 | // In chrome incognito mode, openDatabase is truthy, but using it will 410 | // throw an exception: http://crbug.com/42380 411 | // We can create a dummy database, but there is no way to delete it afterwards. 412 | 413 | // Meanwhile, Safari users can get prompted on any database creation. 414 | // If they do, any page with Modernizr will give them a prompt: 415 | // http://github.com/Modernizr/Modernizr/issues/closed#issue/113 416 | 417 | // We have chosen to allow the Chrome incognito false positive, so that Modernizr 418 | // doesn't litter the web with these test databases. As a developer, you'll have 419 | // to account for this gotcha yourself. 420 | tests['websqldatabase'] = function() { 421 | var result = !!window.openDatabase; 422 | /* if (result){ 423 | try { 424 | result = !!openDatabase( mod + "testdb", "1.0", mod + "testdb", 2e4); 425 | } catch(e) { 426 | } 427 | } */ 428 | return result; 429 | }; 430 | 431 | // Vendors had inconsistent prefixing with the experimental Indexed DB: 432 | // - Webkit's implementation is accessible through webkitIndexedDB 433 | // - Firefox shipped moz_indexedDB before FF4b9, but since then has been mozIndexedDB 434 | // For speed, we don't test the legacy (and beta-only) indexedDB 435 | tests['indexedDB'] = function() { 436 | for ( var i = -1, len = domPrefixes.length; ++i < len; ){ 437 | if ( window[domPrefixes[i].toLowerCase() + 'IndexedDB'] ){ 438 | return true; 439 | } 440 | } 441 | return !!window.indexedDB; 442 | }; 443 | 444 | // documentMode logic from YUI to filter out IE8 Compat Mode 445 | // which false positives. 446 | tests['hashchange'] = function() { 447 | return isEventSupported('hashchange', window) && (document.documentMode === undefined || document.documentMode > 7); 448 | }; 449 | 450 | // Per 1.6: 451 | // This used to be Modernizr.historymanagement but the longer 452 | // name has been deprecated in favor of a shorter and property-matching one. 453 | // The old API is still available in 1.6, but as of 2.0 will throw a warning, 454 | // and in the first release thereafter disappear entirely. 455 | tests['history'] = function() { 456 | return !!(window.history && history.pushState); 457 | }; 458 | 459 | tests['draganddrop'] = function() { 460 | return isEventSupported('dragstart') && isEventSupported('drop'); 461 | }; 462 | 463 | // Mozilla is targeting to land MozWebSocket for FF6 464 | // bugzil.la/659324 465 | tests['websockets'] = function() { 466 | for ( var i = -1, len = domPrefixes.length; ++i < len; ){ 467 | if ( window[domPrefixes[i] + 'WebSocket'] ){ 468 | return true; 469 | } 470 | } 471 | return 'WebSocket' in window; 472 | }; 473 | 474 | 475 | // http://css-tricks.com/rgba-browser-support/ 476 | tests['rgba'] = function() { 477 | // Set an rgba() color and check the returned value 478 | 479 | setCss('background-color:rgba(150,255,150,.5)'); 480 | 481 | return contains(mStyle.backgroundColor, 'rgba'); 482 | }; 483 | 484 | tests['hsla'] = function() { 485 | // Same as rgba(), in fact, browsers re-map hsla() to rgba() internally, 486 | // except IE9 who retains it as hsla 487 | 488 | setCss('background-color:hsla(120,40%,100%,.5)'); 489 | 490 | return contains(mStyle.backgroundColor, 'rgba') || contains(mStyle.backgroundColor, 'hsla'); 491 | }; 492 | 493 | tests['multiplebgs'] = function() { 494 | // Setting multiple images AND a color on the background shorthand property 495 | // and then querying the style.background property value for the number of 496 | // occurrences of "url(" is a reliable method for detecting ACTUAL support for this! 497 | 498 | setCss('background:url(https://),url(https://),red url(https://)'); 499 | 500 | // If the UA supports multiple backgrounds, there should be three occurrences 501 | // of the string "url(" in the return value for elemStyle.background 502 | 503 | return /(url\s*\(.*?){3}/.test(mStyle.background); 504 | }; 505 | 506 | 507 | // In testing support for a given CSS property, it's legit to test: 508 | // `elem.style[styleName] !== undefined` 509 | // If the property is supported it will return an empty string, 510 | // if unsupported it will return undefined. 511 | 512 | // We'll take advantage of this quick test and skip setting a style 513 | // on our modernizr element, but instead just testing undefined vs 514 | // empty string. 515 | 516 | 517 | tests['backgroundsize'] = function() { 518 | return testPropsAll('backgroundSize'); 519 | }; 520 | 521 | tests['borderimage'] = function() { 522 | return testPropsAll('borderImage'); 523 | }; 524 | 525 | 526 | // Super comprehensive table about all the unique implementations of 527 | // border-radius: http://muddledramblings.com/table-of-css3-border-radius-compliance 528 | 529 | tests['borderradius'] = function() { 530 | return testPropsAll('borderRadius'); 531 | }; 532 | 533 | // WebOS unfortunately false positives on this test. 534 | tests['boxshadow'] = function() { 535 | return testPropsAll('boxShadow'); 536 | }; 537 | 538 | // FF3.0 will false positive on this test 539 | tests['textshadow'] = function() { 540 | return document.createElement('div').style.textShadow === ''; 541 | }; 542 | 543 | 544 | tests['opacity'] = function() { 545 | // Browsers that actually have CSS Opacity implemented have done so 546 | // according to spec, which means their return values are within the 547 | // range of [0.0,1.0] - including the leading zero. 548 | 549 | setCssAll('opacity:.55'); 550 | 551 | // The non-literal . in this regex is intentional: 552 | // German Chrome returns this value as 0,55 553 | // https://github.com/Modernizr/Modernizr/issues/#issue/59/comment/516632 554 | return /^0.55$/.test(mStyle.opacity); 555 | }; 556 | 557 | 558 | tests['cssanimations'] = function() { 559 | return testPropsAll('animationName'); 560 | }; 561 | 562 | 563 | tests['csscolumns'] = function() { 564 | return testPropsAll('columnCount'); 565 | }; 566 | 567 | 568 | tests['cssgradients'] = function() { 569 | /** 570 | * For CSS Gradients syntax, please see: 571 | * http://webkit.org/blog/175/introducing-css-gradients/ 572 | * https://developer.mozilla.org/en/CSS/-moz-linear-gradient 573 | * https://developer.mozilla.org/en/CSS/-moz-radial-gradient 574 | * http://dev.w3.org/csswg/css3-images/#gradients- 575 | */ 576 | 577 | var str1 = 'background-image:', 578 | str2 = 'gradient(linear,left top,right bottom,from(#9f9),to(white));', 579 | str3 = 'linear-gradient(left top,#9f9, white);'; 580 | 581 | setCss( 582 | (str1 + prefixes.join(str2 + str1) + prefixes.join(str3 + str1)).slice(0, -str1.length) 583 | ); 584 | 585 | return contains(mStyle.backgroundImage, 'gradient'); 586 | }; 587 | 588 | 589 | tests['cssreflections'] = function() { 590 | return testPropsAll('boxReflect'); 591 | }; 592 | 593 | 594 | tests['csstransforms'] = function() { 595 | return !!testProps(['transformProperty', 'WebkitTransform', 'MozTransform', 'OTransform', 'msTransform']); 596 | }; 597 | 598 | 599 | tests['csstransforms3d'] = function() { 600 | 601 | var ret = !!testProps(['perspectiveProperty', 'WebkitPerspective', 'MozPerspective', 'OPerspective', 'msPerspective']); 602 | 603 | // Webkit’s 3D transforms are passed off to the browser's own graphics renderer. 604 | // It works fine in Safari on Leopard and Snow Leopard, but not in Chrome in 605 | // some conditions. As a result, Webkit typically recognizes the syntax but 606 | // will sometimes throw a false positive, thus we must do a more thorough check: 607 | if ( ret && 'webkitPerspective' in docElement.style ) { 608 | 609 | // Webkit allows this media query to succeed only if the feature is enabled. 610 | // `@media (transform-3d),(-o-transform-3d),(-moz-transform-3d),(-ms-transform-3d),(-webkit-transform-3d),(modernizr){ ... }` 611 | ret = Modernizr['csstransforms3d']; 612 | } 613 | return ret; 614 | }; 615 | 616 | 617 | tests['csstransitions'] = function() { 618 | return testPropsAll('transitionProperty'); 619 | }; 620 | 621 | 622 | /*>>fontface*/ 623 | // @font-face detection routine by Diego Perini 624 | // http://javascript.nwbox.com/CSSSupport/ 625 | tests['fontface'] = function() { 626 | return Modernizr['fontface']; 627 | }; 628 | /*>>fontface*/ 629 | 630 | // CSS generated content detection 631 | tests['generatedcontent'] = function() { 632 | return Modernizr['generatedcontent']; 633 | }; 634 | 635 | 636 | 637 | // These tests evaluate support of the video/audio elements, as well as 638 | // testing what types of content they support. 639 | // 640 | // We're using the Boolean constructor here, so that we can extend the value 641 | // e.g. Modernizr.video // true 642 | // Modernizr.video.ogg // 'probably' 643 | // 644 | // Codec values from : http://github.com/NielsLeenheer/html5test/blob/9106a8/index.html#L845 645 | // thx to NielsLeenheer and zcorpan 646 | 647 | // Note: in FF 3.5.1 and 3.5.0, "no" was a return value instead of empty string. 648 | // Modernizr does not normalize for that. 649 | 650 | tests['video'] = function() { 651 | var elem = document.createElement('video'), 652 | bool = false; 653 | 654 | // IE9 Running on Windows Server SKU can cause an exception to be thrown, bug #224 655 | try { 656 | if ( bool = !!elem.canPlayType ) { 657 | bool = new Boolean(bool); 658 | bool.ogg = elem.canPlayType('video/ogg; codecs="theora"'); 659 | 660 | // Workaround required for IE9, which doesn't report video support without audio codec specified. 661 | // bug 599718 @ msft connect 662 | var h264 = 'video/mp4; codecs="avc1.42E01E'; 663 | bool.h264 = elem.canPlayType(h264 + '"') || elem.canPlayType(h264 + ', mp4a.40.2"'); 664 | 665 | bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"'); 666 | } 667 | 668 | } catch(e) { } 669 | 670 | return bool; 671 | }; 672 | 673 | tests['audio'] = function() { 674 | var elem = document.createElement('audio'), 675 | bool = false; 676 | 677 | try { 678 | if ( bool = !!elem.canPlayType ) { 679 | bool = new Boolean(bool); 680 | bool.ogg = elem.canPlayType('audio/ogg; codecs="vorbis"'); 681 | bool.mp3 = elem.canPlayType('audio/mpeg;'); 682 | 683 | // Mimetypes accepted: 684 | // https://developer.mozilla.org/En/Media_formats_supported_by_the_audio_and_video_elements 685 | // http://bit.ly/iphoneoscodecs 686 | bool.wav = elem.canPlayType('audio/wav; codecs="1"'); 687 | bool.m4a = elem.canPlayType('audio/x-m4a;') || elem.canPlayType('audio/aac;'); 688 | } 689 | } catch(e) { } 690 | 691 | return bool; 692 | }; 693 | 694 | 695 | // Firefox has made these tests rather unfun. 696 | 697 | // In FF4, if disabled, window.localStorage should === null. 698 | 699 | // Normally, we could not test that directly and need to do a 700 | // `('localStorage' in window) && ` test first because otherwise Firefox will 701 | // throw http://bugzil.la/365772 if cookies are disabled 702 | 703 | // However, in Firefox 4 betas, if dom.storage.enabled == false, just mentioning 704 | // the property will throw an exception. http://bugzil.la/599479 705 | // This looks to be fixed for FF4 Final. 706 | 707 | // Because we are forced to try/catch this, we'll go aggressive. 708 | 709 | // FWIW: IE8 Compat mode supports these features completely: 710 | // http://www.quirksmode.org/dom/html5.html 711 | // But IE8 doesn't support either with local files 712 | 713 | tests['localstorage'] = function() { 714 | try { 715 | return !!localStorage.getItem; 716 | } catch(e) { 717 | return false; 718 | } 719 | }; 720 | 721 | tests['sessionstorage'] = function() { 722 | try { 723 | return !!sessionStorage.getItem; 724 | } catch(e){ 725 | return false; 726 | } 727 | }; 728 | 729 | 730 | tests['webworkers'] = function() { 731 | return !!window.Worker; 732 | }; 733 | 734 | 735 | tests['applicationcache'] = function() { 736 | return !!window.applicationCache; 737 | }; 738 | 739 | 740 | // Thanks to Erik Dahlstrom 741 | tests['svg'] = function() { 742 | return !!document.createElementNS && !!document.createElementNS(ns.svg, 'svg').createSVGRect; 743 | }; 744 | 745 | // specifically for SVG inline in HTML, not within XHTML 746 | // test page: paulirish.com/demo/inline-svg 747 | tests['inlinesvg'] = function() { 748 | var div = document.createElement('div'); 749 | div.innerHTML = ''; 750 | return (div.firstChild && div.firstChild.namespaceURI) == ns.svg; 751 | }; 752 | 753 | // Thanks to F1lt3r and lucideer, ticket #35 754 | tests['smil'] = function() { 755 | return !!document.createElementNS && /SVG/.test(toString.call(document.createElementNS(ns.svg, 'animate'))); 756 | }; 757 | 758 | tests['svgclippaths'] = function() { 759 | // Possibly returns a false positive in Safari 3.2? 760 | return !!document.createElementNS && /SVG/.test(toString.call(document.createElementNS(ns.svg, 'clipPath'))); 761 | }; 762 | 763 | // input features and input types go directly onto the ret object, bypassing the tests loop. 764 | // Hold this guy to execute in a moment. 765 | function webforms() { 766 | // Run through HTML5's new input attributes to see if the UA understands any. 767 | // We're using f which is the element created early on 768 | // Mike Taylr has created a comprehensive resource for testing these attributes 769 | // when applied to all input types: 770 | // http://miketaylr.com/code/input-type-attr.html 771 | // spec: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary 772 | 773 | // Only input placeholder is tested while textarea's placeholder is not. 774 | // Currently Safari 4 and Opera 11 have support only for the input placeholder 775 | // Both tests are available in feature-detects/forms-placeholder.js 776 | Modernizr['input'] = (function( props ) { 777 | for ( var i = 0, len = props.length; i < len; i++ ) { 778 | attrs[ props[i] ] = !!(props[i] in inputElem); 779 | } 780 | return attrs; 781 | })('autocomplete autofocus list placeholder max min multiple pattern required step'.split(' ')); 782 | 783 | // Run through HTML5's new input types to see if the UA understands any. 784 | // This is put behind the tests runloop because it doesn't return a 785 | // true/false like all the other tests; instead, it returns an object 786 | // containing each input type with its corresponding true/false value 787 | 788 | // Big thanks to @miketaylr for the html5 forms expertise. http://miketaylr.com/ 789 | Modernizr['inputtypes'] = (function(props) { 790 | 791 | for ( var i = 0, bool, inputElemType, defaultView, len = props.length; i < len; i++ ) { 792 | 793 | inputElem.setAttribute('type', inputElemType = props[i]); 794 | bool = inputElem.type !== 'text'; 795 | 796 | // We first check to see if the type we give it sticks.. 797 | // If the type does, we feed it a textual value, which shouldn't be valid. 798 | // If the value doesn't stick, we know there's input sanitization which infers a custom UI 799 | if ( bool ) { 800 | 801 | inputElem.value = smile; 802 | inputElem.style.cssText = 'position:absolute;visibility:hidden;'; 803 | 804 | if ( /^range$/.test(inputElemType) && inputElem.style.WebkitAppearance !== undefined ) { 805 | 806 | docElement.appendChild(inputElem); 807 | defaultView = document.defaultView; 808 | 809 | // Safari 2-4 allows the smiley as a value, despite making a slider 810 | bool = defaultView.getComputedStyle && 811 | defaultView.getComputedStyle(inputElem, null).WebkitAppearance !== 'textfield' && 812 | // Mobile android web browser has false positive, so must 813 | // check the height to see if the widget is actually there. 814 | (inputElem.offsetHeight !== 0); 815 | 816 | docElement.removeChild(inputElem); 817 | 818 | } else if ( /^(search|tel)$/.test(inputElemType) ){ 819 | // Spec doesnt define any special parsing or detectable UI 820 | // behaviors so we pass these through as true 821 | 822 | // Interestingly, opera fails the earlier test, so it doesn't 823 | // even make it here. 824 | 825 | } else if ( /^(url|email)$/.test(inputElemType) ) { 826 | // Real url and email support comes with prebaked validation. 827 | bool = inputElem.checkValidity && inputElem.checkValidity() === false; 828 | 829 | } else if ( /^color$/.test(inputElemType) ) { 830 | // chuck into DOM and force reflow for Opera bug in 11.00 831 | // github.com/Modernizr/Modernizr/issues#issue/159 832 | docElement.appendChild(inputElem); 833 | docElement.offsetWidth; 834 | bool = inputElem.value != smile; 835 | docElement.removeChild(inputElem); 836 | 837 | } else { 838 | // If the upgraded input compontent rejects the :) text, we got a winner 839 | bool = inputElem.value != smile; 840 | } 841 | } 842 | 843 | inputs[ props[i] ] = !!bool; 844 | } 845 | return inputs; 846 | })('search tel url email datetime date month week time datetime-local number range color'.split(' ')); 847 | } 848 | 849 | 850 | // End of test definitions 851 | // ----------------------- 852 | 853 | 854 | 855 | // Run through all tests and detect their support in the current UA. 856 | // todo: hypothetically we could be doing an array of tests and use a basic loop here. 857 | for ( var feature in tests ) { 858 | if ( hasOwnProperty(tests, feature) ) { 859 | // run the test, throw the return value into the Modernizr, 860 | // then based on that boolean, define an appropriate className 861 | // and push it into an array of classes we'll join later. 862 | featureName = feature.toLowerCase(); 863 | Modernizr[featureName] = tests[feature](); 864 | 865 | classes.push((Modernizr[featureName] ? '' : 'no-') + featureName); 866 | } 867 | } 868 | 869 | // input tests need to run. 870 | Modernizr.input || webforms(); 871 | 872 | 873 | /** 874 | * addTest allows the user to define their own feature tests 875 | * the result will be added onto the Modernizr object, 876 | * as well as an appropriate className set on the html element 877 | * 878 | * @param feature - String naming the feature 879 | * @param test - Function returning true if feature is supported, false if not 880 | */ 881 | Modernizr.addTest = function ( feature, test ) { 882 | if ( typeof feature == "object" ) { 883 | for ( var key in feature ) { 884 | if ( hasOwnProperty( feature, key ) ) { 885 | Modernizr.addTest( key, feature[ key ] ); 886 | } 887 | } 888 | } else { 889 | 890 | feature = feature.toLowerCase(); 891 | 892 | if ( Modernizr[feature] !== undefined ) { 893 | // we're going to quit if you're trying to overwrite an existing test 894 | // if we were to allow it, we'd do this: 895 | // var re = new RegExp("\\b(no-)?" + feature + "\\b"); 896 | // docElement.className = docElement.className.replace( re, '' ); 897 | // but, no rly, stuff 'em. 898 | return; 899 | } 900 | 901 | test = typeof test == "boolean" ? test : !!test(); 902 | 903 | docElement.className += ' ' + (test ? '' : 'no-') + feature; 904 | Modernizr[feature] = test; 905 | 906 | } 907 | 908 | return Modernizr; // allow chaining. 909 | }; 910 | 911 | 912 | // Reset modElem.cssText to nothing to reduce memory footprint. 913 | setCss(''); 914 | modElem = inputElem = null; 915 | 916 | //>>BEGIN IEPP 917 | // Enable HTML 5 elements for styling (and printing) in IE. 918 | if ( window.attachEvent && (function(){ var elem = document.createElement('div'); 919 | elem.innerHTML = ''; 920 | return elem.childNodes.length !== 1; })() ) { 921 | 922 | // iepp v2 by @jon_neal & afarkas : github.com/aFarkas/iepp/ 923 | (function(win, doc) { 924 | win.iepp = win.iepp || {}; 925 | var iepp = win.iepp, 926 | elems = iepp.html5elements || 'abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video', 927 | elemsArr = elems.split('|'), 928 | elemsArrLen = elemsArr.length, 929 | elemRegExp = new RegExp('(^|\\s)('+elems+')', 'gi'), 930 | tagRegExp = new RegExp('<(\/*)('+elems+')', 'gi'), 931 | filterReg = /^\s*[\{\}]\s*$/, 932 | ruleRegExp = new RegExp('(^|[^\\n]*?\\s)('+elems+')([^\\n]*)({[\\n\\w\\W]*?})', 'gi'), 933 | docFrag = doc.createDocumentFragment(), 934 | html = doc.documentElement, 935 | head = html.firstChild, 936 | bodyElem = doc.createElement('body'), 937 | styleElem = doc.createElement('style'), 938 | printMedias = /print|all/, 939 | body; 940 | function shim(doc) { 941 | var a = -1; 942 | while (++a < elemsArrLen) 943 | // Use createElement so IE allows HTML5-named elements in a document 944 | doc.createElement(elemsArr[a]); 945 | } 946 | 947 | iepp.getCSS = function(styleSheetList, mediaType) { 948 | if(styleSheetList+'' === undefined){return '';} 949 | var a = -1, 950 | len = styleSheetList.length, 951 | styleSheet, 952 | cssTextArr = []; 953 | while (++a < len) { 954 | styleSheet = styleSheetList[a]; 955 | //currently no test for disabled/alternate stylesheets 956 | if(styleSheet.disabled){continue;} 957 | mediaType = styleSheet.media || mediaType; 958 | // Get css from all non-screen stylesheets and their imports 959 | if (printMedias.test(mediaType)) cssTextArr.push(iepp.getCSS(styleSheet.imports, mediaType), styleSheet.cssText); 960 | //reset mediaType to all with every new *not imported* stylesheet 961 | mediaType = 'all'; 962 | } 963 | return cssTextArr.join(''); 964 | }; 965 | 966 | iepp.parseCSS = function(cssText) { 967 | var cssTextArr = [], 968 | rule; 969 | while ((rule = ruleRegExp.exec(cssText)) != null){ 970 | // Replace all html5 element references with iepp substitute classnames 971 | cssTextArr.push(( (filterReg.exec(rule[1]) ? '\n' : rule[1]) +rule[2]+rule[3]).replace(elemRegExp, '$1.iepp_$2')+rule[4]); 972 | } 973 | return cssTextArr.join('\n'); 974 | }; 975 | 976 | iepp.writeHTML = function() { 977 | var a = -1; 978 | body = body || doc.body; 979 | while (++a < elemsArrLen) { 980 | var nodeList = doc.getElementsByTagName(elemsArr[a]), 981 | nodeListLen = nodeList.length, 982 | b = -1; 983 | while (++b < nodeListLen) 984 | if (nodeList[b].className.indexOf('iepp_') < 0) 985 | // Append iepp substitute classnames to all html5 elements 986 | nodeList[b].className += ' iepp_'+elemsArr[a]; 987 | } 988 | docFrag.appendChild(body); 989 | html.appendChild(bodyElem); 990 | // Write iepp substitute print-safe document 991 | bodyElem.className = body.className; 992 | bodyElem.id = body.id; 993 | // Replace HTML5 elements with which is print-safe and shouldn't conflict since it isn't part of html5 994 | bodyElem.innerHTML = body.innerHTML.replace(tagRegExp, '<$1font'); 995 | }; 996 | 997 | 998 | iepp._beforePrint = function() { 999 | // Write iepp custom print CSS 1000 | styleElem.styleSheet.cssText = iepp.parseCSS(iepp.getCSS(doc.styleSheets, 'all')); 1001 | iepp.writeHTML(); 1002 | }; 1003 | 1004 | iepp.restoreHTML = function(){ 1005 | // Undo everything done in onbeforeprint 1006 | bodyElem.innerHTML = ''; 1007 | html.removeChild(bodyElem); 1008 | html.appendChild(body); 1009 | }; 1010 | 1011 | iepp._afterPrint = function(){ 1012 | // Undo everything done in onbeforeprint 1013 | iepp.restoreHTML(); 1014 | styleElem.styleSheet.cssText = ''; 1015 | }; 1016 | 1017 | 1018 | 1019 | // Shim the document and iepp fragment 1020 | shim(doc); 1021 | shim(docFrag); 1022 | 1023 | // 1024 | if(iepp.disablePP){return;} 1025 | 1026 | // Add iepp custom print style element 1027 | head.insertBefore(styleElem, head.firstChild); 1028 | styleElem.media = 'print'; 1029 | styleElem.className = 'iepp-printshim'; 1030 | win.attachEvent( 1031 | 'onbeforeprint', 1032 | iepp._beforePrint 1033 | ); 1034 | win.attachEvent( 1035 | 'onafterprint', 1036 | iepp._afterPrint 1037 | ); 1038 | })(window, document); 1039 | } 1040 | //>>END IEPP 1041 | 1042 | // Assign private properties to the return object with prefix 1043 | Modernizr._version = version; 1044 | 1045 | // expose these for the plugin API. Look in the source for how to join() them against your input 1046 | Modernizr._prefixes = prefixes; 1047 | Modernizr._domPrefixes = domPrefixes; 1048 | 1049 | // Modernizr.mq tests a given media query, live against the current state of the window 1050 | // A few important notes: 1051 | // * If a browser does not support media queries at all (eg. oldIE) the mq() will always return false 1052 | // * A max-width or orientation query will be evaluated against the current state, which may change later. 1053 | // * You must specify values. Eg. If you are testing support for the min-width media query use: 1054 | // Modernizr.mq('(min-width:0)') 1055 | // usage: 1056 | // Modernizr.mq('only screen and (max-width:768)') 1057 | Modernizr.mq = testMediaQuery; 1058 | 1059 | // Modernizr.hasEvent() detects support for a given event, with an optional element to test on 1060 | // Modernizr.hasEvent('gesturestart', elem) 1061 | Modernizr.hasEvent = isEventSupported; 1062 | 1063 | // Modernizr.testProp() investigates whether a given style property is recognized 1064 | // Note that the property names must be provided in the camelCase variant. 1065 | // Modernizr.testProp('pointerEvents') 1066 | Modernizr.testProp = function(prop){ 1067 | return testProps([prop]); 1068 | }; 1069 | 1070 | // Modernizr.testAllProps() investigates whether a given style property, 1071 | // or any of its vendor-prefixed variants, is recognized 1072 | // Note that the property names must be provided in the camelCase variant. 1073 | // Modernizr.testAllProps('boxSizing') 1074 | Modernizr.testAllProps = testPropsAll; 1075 | 1076 | 1077 | 1078 | // Modernizr.testStyles() allows you to add custom styles to the document and test an element afterwards 1079 | // Modernizr.testStyles('#modernizr { position:absolute }', function(elem, rule){ ... }) 1080 | Modernizr.testStyles = injectElementWithStyles; 1081 | 1082 | 1083 | // Modernizr.prefixed() returns the prefixed or nonprefixed property name variant of your input 1084 | // Modernizr.prefixed('boxSizing') // 'MozBoxSizing' 1085 | 1086 | // Properties must be passed as dom-style camelcase, rather than `box-sizing` hypentated style. 1087 | // Return values will also be the camelCase variant, if you need to translate that to hypenated style use: 1088 | // 1089 | // str.replace(/([A-Z])/g, function(str,m1){ return '-' + m1.toLowerCase(); }).replace(/^ms-/,'-ms-'); 1090 | 1091 | // If you're trying to ascertain which transition end event to bind to, you might do something like... 1092 | // 1093 | // var transEndEventNames = { 1094 | // 'WebkitTransition' : 'webkitTransitionEnd', 1095 | // 'MozTransition' : 'transitionend', 1096 | // 'OTransition' : 'oTransitionEnd', 1097 | // 'msTransition' : 'msTransitionEnd', // maybe? 1098 | // 'transition' : 'transitionEnd' 1099 | // }, 1100 | // transEndEventName = transEndEventNames[ Modernizr.prefixed('transition') ]; 1101 | 1102 | Modernizr.prefixed = function(prop){ 1103 | return testPropsAll(prop, 'pfx'); 1104 | }; 1105 | 1106 | 1107 | 1108 | // Remove "no-js" class from element, if it exists: 1109 | docElement.className = docElement.className.replace(/\bno-js\b/, '') 1110 | 1111 | // Add the new classes to the element. 1112 | + (enableClasses ? ' js ' + classes.join(' ') : ''); 1113 | 1114 | return Modernizr; 1115 | 1116 | })(this, this.document); 1117 | -------------------------------------------------------------------------------- /lib/templates/views/layout.erb.tt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | <%= @name.camel_case %> 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |

<%= @name.camel_case %>

24 |
25 | 26 |
27 | <%%= yield %> 28 |
29 | 30 |
31 | © <%%= Time.now.year %>, Me. 32 |
33 |
34 | 35 | 36 | 37 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /lib/templates/views/welcome.erb: -------------------------------------------------------------------------------- 1 |

Sinatra Template Default Page

2 | 3 |
4 |

Welcome to the Sinatra Template! If you're seeing this page, then everything is working 5 | as expected. To get started, delete this file (views/welcome.erb) and begin adding 6 | your own actions to app.rb. For more information, see the README.

7 |
8 | 9 | 18 | -------------------------------------------------------------------------------- /snfn.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' 4 | # -*- encoding: utf-8 -*- 5 | 6 | Gem::Specification.new do |s| 7 | s.name = %q{snfn} 8 | s.version = "0.3.0" 9 | 10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 11 | s.authors = [%q{Zach Pendleton}] 12 | s.date = %q{2011-08-24} 13 | s.description = %q{An app generator for Sinatra apps with an eye towards easy Heroku setup and deployment.} 14 | s.email = %q{zachpendleton@gmail.com} 15 | s.executables = [%q{snfn}] 16 | s.extra_rdoc_files = [ 17 | "LICENSE.txt", 18 | "README.mdown" 19 | ] 20 | s.files = [ 21 | ".document", 22 | "Gemfile", 23 | "Gemfile.lock", 24 | "LICENSE.txt", 25 | "README.mdown", 26 | "Rakefile", 27 | "VERSION", 28 | "bin/snfn", 29 | "lib/extensions/string.rb", 30 | "lib/snfn.rb", 31 | "lib/templates/Gemfile", 32 | "lib/templates/Procfile", 33 | "lib/templates/README.mdown", 34 | "lib/templates/Rakefile", 35 | "lib/templates/app.rb", 36 | "lib/templates/config.ru", 37 | "lib/templates/config/db.yml", 38 | "lib/templates/config/initializers/database.rb", 39 | "lib/templates/config/initializers/redis.rb", 40 | "lib/templates/config/redis.yml", 41 | "lib/templates/config/unicorn.rb", 42 | "lib/templates/public/css/scaffold.css", 43 | "lib/templates/public/img/.gitkeep", 44 | "lib/templates/public/js/app.js.tt", 45 | "lib/templates/public/js/lib/modernizr-2.0.6.js", 46 | "lib/templates/views/layout.erb.tt", 47 | "lib/templates/views/welcome.erb", 48 | "snfn.gemspec", 49 | "test/helper.rb", 50 | "test/test_extension_string.rb", 51 | "test/test_snfn.rb", 52 | "vendor/cache/fakefs-0.3.2.gem", 53 | "vendor/cache/git-1.2.5.gem", 54 | "vendor/cache/jeweler-1.6.4.gem", 55 | "vendor/cache/rake-0.9.2.gem", 56 | "vendor/cache/thor-0.14.6.gem" 57 | ] 58 | s.homepage = %q{http://github.com/zachpendleton/snfn} 59 | s.licenses = [%q{MIT}] 60 | s.require_paths = [%q{lib}] 61 | s.rubygems_version = %q{1.8.7} 62 | s.summary = %q{A Heroku-friendy Sinatra app generator} 63 | 64 | if s.respond_to? :specification_version then 65 | s.specification_version = 3 66 | 67 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 68 | s.add_runtime_dependency(%q, ["~> 0.14.6"]) 69 | s.add_development_dependency(%q, ["~> 1.0.0"]) 70 | s.add_development_dependency(%q, ["~> 0.3.2"]) 71 | s.add_development_dependency(%q, ["~> 1.6.4"]) 72 | else 73 | s.add_dependency(%q, ["~> 0.14.6"]) 74 | s.add_dependency(%q, ["~> 1.0.0"]) 75 | s.add_dependency(%q, ["~> 0.3.2"]) 76 | s.add_dependency(%q, ["~> 1.6.4"]) 77 | end 78 | else 79 | s.add_dependency(%q, ["~> 0.14.6"]) 80 | s.add_dependency(%q, ["~> 1.0.0"]) 81 | s.add_dependency(%q, ["~> 0.3.2"]) 82 | s.add_dependency(%q, ["~> 1.6.4"]) 83 | end 84 | end 85 | 86 | -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler' 3 | begin 4 | Bundler.setup(:default, :development) 5 | rescue Bundler::BundlerError => e 6 | $stderr.puts e.message 7 | $stderr.puts "Run `bundle install` to install missing gems" 8 | exit e.status_code 9 | end 10 | require 'test/unit' 11 | 12 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 13 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 14 | require 'snfn' 15 | require 'extensions/string' 16 | 17 | class Test::Unit::TestCase 18 | end 19 | -------------------------------------------------------------------------------- /test/test_extension_string.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | class TestExtensionString < Test::Unit::TestCase 4 | def test_should_ignore_an_already_camel_cased_string 5 | assert_equal "MyApp", "MyApp".camel_case 6 | end 7 | 8 | def test_should_capitalize_an_all_lower_case_string 9 | assert_equal "Myapp", "myapp".camel_case 10 | end 11 | 12 | def test_should_camel_case_a_lower_case_string_with_underscores 13 | assert_equal "MyApp", "my_app".camel_case 14 | end 15 | 16 | def test_should_camel_case_a_lower_case_string_with_hyphens 17 | assert_equal "MyApp", "my-app".camel_case 18 | end 19 | 20 | def test_should_camel_case_an_uppercase_string_with_underscores 21 | assert_equal "MyApp", "MY_APP".camel_case 22 | end 23 | 24 | def test_should_camel_case_an_uppercase_string_with_hyphens 25 | assert_equal "MyApp", "MY-APP".camel_case 26 | end 27 | 28 | def test_should_camel_case_a_string_with_a_hyphen_preceding_a_capital_letter 29 | assert_equal "MyApp", "my_App".camel_case 30 | end 31 | 32 | def test_should_underscore_a_camel_cased_string 33 | assert_equal "my_app", "MyApp".file_name 34 | end 35 | 36 | def test_should_underscore_a_hypenated_string 37 | assert_equal "my_app", "my-app".file_name 38 | end 39 | 40 | def test_should_ignore_an_already_underscored_string 41 | assert_equal "my_app", "my_app".file_name 42 | end 43 | 44 | def test_should_underscore_a_string_with_a_hyphen_preceding_a_capital_letter 45 | assert_equal "my_app", "my_App".file_name 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /test/test_snfn.rb: -------------------------------------------------------------------------------- 1 | require 'fakefs' 2 | require 'helper' 3 | 4 | class TestSnfn < Test::Unit::TestCase 5 | end 6 | -------------------------------------------------------------------------------- /vendor/cache/fakefs-0.3.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zachpendleton/snfn/9c897b16ed342fb9becb34f40c0d3e768ca25409/vendor/cache/fakefs-0.3.2.gem -------------------------------------------------------------------------------- /vendor/cache/git-1.2.5.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zachpendleton/snfn/9c897b16ed342fb9becb34f40c0d3e768ca25409/vendor/cache/git-1.2.5.gem -------------------------------------------------------------------------------- /vendor/cache/jeweler-1.6.4.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zachpendleton/snfn/9c897b16ed342fb9becb34f40c0d3e768ca25409/vendor/cache/jeweler-1.6.4.gem -------------------------------------------------------------------------------- /vendor/cache/rake-0.9.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zachpendleton/snfn/9c897b16ed342fb9becb34f40c0d3e768ca25409/vendor/cache/rake-0.9.2.gem -------------------------------------------------------------------------------- /vendor/cache/thor-0.14.6.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zachpendleton/snfn/9c897b16ed342fb9becb34f40c0d3e768ca25409/vendor/cache/thor-0.14.6.gem --------------------------------------------------------------------------------