Skip to content

Instantly share code, notes, and snippets.

@bnadlerjr
Created June 26, 2011 23:39
Show Gist options
  • Save bnadlerjr/1048100 to your computer and use it in GitHub Desktop.
Save bnadlerjr/1048100 to your computer and use it in GitHub Desktop.
Sinatra App Generator
*.swp
.DS_Store
.bundle
.sass-cache
doc
tags
tmp
= <%= name.capitalize %>
TODO: <Insert description here>
Author:: Bob Nadler, Jr. (bnadlerjr@gmail.com)
== Notes / Use
Install dependencies using Bundler:
bundle install
Run using rackup file, for example:
rackup config.ru
== Contributing
=== Issues / Roadmap
Use GitHub issues[http://github.com/bnadlerjr/<%= name %>/issues] for reporting bug and feature requests.
=== Patches / Pull Requests
* Fork the project.
* Make your feature addition or bug fix.
* Add tests for it. This is important so I don’t break it in a future version
unintentionally.
* Commit, do not mess with rakefile, version, or history (if you want to have
your own version, that is fine but bump version in a commit by itself I can
ignore when I pull).
* Send me a pull request. Bonus points for topic branches.
=== Project Layout
/doc::
Project documentation. Can be generated using rake task.
/lib::
Main project source code.
/test::
All test source code and data samples.
config.ru::
Rackup file.
Gemfile::
Dependency management with Bundler.
Rakefile::
Rake tasks for the project. Use "rake -T" to see a list of available tasks.
README.rdoc::
This file.
== License
(The MIT License)
Copyright (c) <%= Date.today.year %> Bob Nadler, Jr.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
$defaultBorderRadius: 5px;
$useIEFilters: 0; // 0 == false; 1 == true
/* Border Radius
---------------------------------------------------------------------------- */
@mixin border-radius($values: $defaultBorderRadius) {
-moz-border-radius: $values;
-webkit-border-radius: $values;
border-radius: $values;
}
@mixin border-corner-radius($radius, $vert_pos, $horiz_pos) {
$top-left: 0; $top-right: 0; $bottom-right: 0; $bottom-left: 0;
@if $vert_pos == 'top' {
@if $horiz_pos == 'left' {
$top-left: $radius;
} @else {
$top-right: $radius;
}
} @else {
@if $horiz_pos == 'left' {
$bottom-left: $radius;
} @else {
$bottom-right: $radius;
}
}
@include border-radius($top-left $top-right $bottom-right $bottom-left);
}
@mixin border-top-radius($radius: $defaultBorderRadius) {
@include border-radius($radius $radius 0 0);
}
@mixin border-top-left-radius($radius: $defaultBorderRadius) {
@include border-corner-radius($radius, 'top', 'left');
}
@mixin border-top-right-radius($radius: $defaultBorderRadius) {
@include border-corner-radius($radius, 'top', 'right');
}
@mixin border-bottom-radius($radius: $defaultBorderRadius) {
@include border-bottom-left-radius($radius);
@include border-bottom-right-radius($radius);
}
@mixin border-bottom-left-radius($radius: $defaultBorderRadius) {
@include border-corner-radius($radius, 'bottom', 'left');
}
@mixin border-bottom-right-radius($radius: $defaultBorderRadius) {
@include border-corner-radius($radius, 'bottom', 'right');
}
@mixin border-left-radius($radius: $defaultBorderRadius) {
@include border-top-left-radius($radius);
@include border-bottom-left-radius($radius);
}
@mixin border-right-radius($radius: $defaultBorderRadius) {
@include border-top-right-radius($radius);
@include border-bottom-right-radius($radius);
}
/* Box Shadow
---------------------------------------------------------------------------- */
@mixin box-shadow($values, $hex, $ie: $useIEFilters) {
-moz-box-shadow: $values $hex;
-o-box-shadow: $values $hex;
-webkit-box-shadow: $values $hex;
box-shadow: $values $hex;
@if $useIEFilters == 1 {
filter: progid:DXImageTransform.Microsoft.dropshadow(OffX=0px, OffY=0px, Color='#ffffff');
-ms-filter: "progid:DXImageTransform.Microsoft.dropshadow(OffX=0px, OffY=0px, Color='#ffffff')";
}
}
@mixin outer-glow($x: 0, $y: 0, $blur: 7px, $color: rgba(0, 0, 0, 0.4)) {
@include box-shadow($x $y $blur, $color);
}
@mixin outer-glow-over {
@include box-shadow(0 0 7px, rgba(0, 0, 0, 0.7));
}
/* Gradient
---------------------------------------------------------------------------- */
@mixin linear-gradient($from, $to, $ie: $useIEFilters) {
background-color: $to;
background-image: -moz-linear-gradient(top, $from, $to);
background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0, $from),color-stop(1, $to));
@if $useIEFilters == 1 {
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#{$from}', endColorstr='#{$to}');
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#{$from}', endColorstr='#{$to}')";
}
}
/* Reset
---------------------------------------------------------------------------- */
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, figcaption, figure,
footer, header, hgroup, menu, nav, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
outline: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
ins {
text-decoration: none;
}
del {
text-decoration: line-through;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
require File.join(File.dirname(__FILE__), 'lib/<%= name %>/server')
require 'sprockets'
map '/assets' do
env = Sprockets::Environment.new
env.append_path 'lib/<%= name %>/assets/javascripts'
env.append_path 'lib/<%= name %>/assets/stylesheets'
env.append_path 'lib/<%= name %>/assets/images'
run env
end
map '/' do
run <%= Thor::Util.camel_case(name) %>::Server
end
source 'http://rubygems.org'
gem "haml", "~> 3.1.3"
gem "sinatra", "~> 1.3.1"
gem "sprockets", "~> 2.0"
gem "thin", "~> 1.3.1"
group :development do
gem "rake", "~> 0.9.2"
gem "rdoc", "~> 3.6.1"
gem "sass", "~> 3.1.3"
end
group :test do
gem "contest", "~> 0.1.3"
gem "flay", "~> 1.4.3"
gem "flog", "~> 2.5.3"
gem "launchy", "~> 2.0.5"
gem "mocha", "~> 0.10.0", :require => false
gem "rack-test", "~> 0.6.1"
gem "reek", "~> 1.2.8"
gem "roodi", "~> 2.1.0"
gem "simplecov", "~> 0.5.4", :require => false
end
group :production do
end
#!/usr/bin/env ruby
require 'thor/group'
require 'thor/util'
require 'date'
class Hoboken < Thor::Group
desc 'Generates a new modular Sinatra app'
include Thor::Actions
argument :name
def intro
say <<-TEXT
===============================================================================
_ _
/\ /\___ | |__ ___ | | _____ _ __
/ /_/ / _ \| '_ \ / _ \| |/ / _ \ '_ \
/ __ / (_) | |_) | (_) | < __/ | | |
\/ /_/ \___/|_.__/ \___/|_|\_\___|_| |_|
Sinatra App Generator by Bob Nadler (bnadlerjr@gmail.com)
===============================================================================
TEXT
end
def self.source_root
File.dirname(__FILE__)
end
def create_tree
empty_directory name
inside name do
public_folders = ["public"]
lib_folders = ["lib", "lib/#{name}", "lib/#{name}/assets"]
asset_folders = ["lib/#{name}/assets/stylesheets", "lib/#{name}/assets/javascripts", "lib/#{name}/assets/images"]
test_folders = ["test", "test/#{name}", "test/integration"]
(public_folders + lib_folders + asset_folders + test_folders).each { |f| empty_directory(f) }
end
end
def apply_templates
templates = {
'.gitignore.tt' => ".gitignore",
'Gemfile.tt' => "Gemfile",
'README.rdoc.tt' => "README.rdoc",
'Rakefile.tt' => "Rakefile",
'config.ru.tt' => "config.ru",
'server.rb.tt' => "lib/#{name}/server.rb",
'name.rb.tt' => "lib/#{name}.rb",
'test_helper.rb.tt' => "test/test_helper.rb",
'server_test.rb.tt' => "test/#{name}/server_test.rb",
'rack_test_case.rb.tt' => "test/rack_test_case.rb",
'layout.haml.tt' => "/lib/#{name}/views/layout.haml",
'index.haml.tt' => "/lib/#{name}/views/index.haml"
}
templates.each { |k, v| template("templates/#{k}", "#{name}/#{v}") }
end
def javascripts
if yes?("Include jQuery? [y/n]")
inject_into_file "./#{name}/lib/#{name}/views/layout.haml", :after => "%link(rel='stylesheet' type='text/css' href='./assets/styles.css')/\n" do
" %script(type='text/javascript' src='./assets/app.js')\n"
end
get "http://code.jquery.com/jquery-latest.min.js", "./#{name}/lib/#{name}/assets/javascripts/jquery.min.js"
create_file "./#{name}/lib/#{name}/assets/javascripts/app.js" do
<<-JAVASCRIPT
//= require jquery.min.js
$(document).ready(function () {
});
JAVASCRIPT
end
end
end
def sass
if yes?("Include default scss files (_reset & _mixins)? [y/n]")
['_reset.scss', '_mixins.scss'].each { |f| copy_file("./templates/#{f}", "./#{name}/lib/#{name}/assets/stylesheets/#{f}") }
create_file "./#{name}/lib/#{name}/assets/stylesheets/styles.scss" do
<<-SASS
@import "./reset.scss";
@import "./mixins.scss";
/* Main
---------------------------------------------------------------------------- */
SASS
end
end
end
def rvm_support
create_file "#{name}/.rvmrc" do
version = ask('Which Ruby version?(i.e. "ree" or "1.9.2-p180")')
"rvm #{version}"
end
end
def print_directions
puts <<-TEXT
Successfully generated #{name}. Remember to "bundle install".
TEXT
end
end
Hoboken.start
%p Index template
!!! 5
%html
%head
%title <%= name.capitalize %>
%meta(name='author' content='Bob Nadler, Jr. <bnadlerjr@gmail.com>')
%link(rel='stylesheet' type='text/css' href='./assets/styles.css')/
%body
= yield
require 'rubygems'
require 'bundler'
Bundler.setup
$:.push File.dirname(__FILE__)
require 'sinatra/base'
require 'rack/test'
require 'launchy'
# Base Test Case for testing Rack apps. Provides custom assertions. Relies on
# rack-test.
class Rack::TestCase < Test::Unit::TestCase
include Rack::Test::Methods
CONTENT_TYPES = {
:json => "application/json;charset=utf-8",
:html => "text/html;charset=utf-8"
}
# Saves the last response body as a web page.
def show_me_the_page
filename = File.join('tmp', "#{Time.now.strftime('%Y-%m-%d %T')}.html")
File.open(filename, 'w') { |f| f.write(last_response.body) }
Launchy.open(filename)
end
# Asserts that the last response content type matches +content_type+.
# +content_type+ can be either +:html+ or +:json+.
def assert_content_type(content_type)
unless CONTENT_TYPES.keys.include?(content_type)
raise ArgumentError, "unrecognized content_type (#{content_type})"
end
assert_equal CONTENT_TYPES[content_type], last_response.content_type
end
# Asserts that the last response code matches +status+. If it does not, and
# +show_page+ is true, the response body is saved to a file and opened using
# the default browser. +show_page+ is +true+ by default.
def assert_response(status, show_page=true)
unless last_response.send("#{status}?")
show_me_the_page if show_page
flunk "expected last_response to be #{status} but was #{last_response.status}"
end
end
# Asserts that the last response body matches the regular expression
# +expected+.
def assert_body_includes(expected)
assert last_response.body.match(expected),
"expected last_response body to include #{expected}"
end
end
require "rake/testtask"
require "rdoc/task"
require "reek/rake/task"
require "roodi"
require "roodi_task"
require "flay_task"
require "flog_task"
DEFAULT_TASKS = %w[test flog flay]
EXTRA_RDOC_FILES = ['README.rdoc']
LIB_FILES = Dir["lib/**/*.rb"]
MAIN_RDOC = 'README.rdoc'
TEST_FILES = Dir["test/**/*_test.rb"]
TITLE = '<%= name %>'
desc "Default tasks: #{DEFAULT_TASKS.join(', ')}"
task :default => DEFAULT_TASKS
Rake::TestTask.new do |t|
t.libs << 'test'
t.test_files = TEST_FILES
end
RDoc::Task.new do |t|
t.main = MAIN_RDOC
t.rdoc_dir = 'doc'
t.rdoc_files.include(EXTRA_RDOC_FILES, LIB_FILES)
t.options << '-q'
t.title = TITLE
end
RoodiTask.new do |t|
t.patterns = %w[lib/**/*.rb]
end
FlayTask.new do |t|
t.dirs = %w[lib]
end
FlogTask.new do |t|
t.dirs = %w[lib]
end
Reek::Rake::Task.new do |t|
t.fail_on_error = true
t.reek_opts = "--quiet"
t.verbose = false
end
require File.join(File.dirname(__FILE__), '../<%= name %>')
module <%= Thor::Util.camel_case(name) %>
# The main application server.
class Server < Sinatra::Base
set :app_file, __FILE__
set :public_folder, Proc.new { File.join(root, '../public') }
get '/' do
haml :index
end
end
end
require 'test_helper'
require 'rack_test_case'
require '<%= name %>/server'
module <%= Thor::Util.camel_case(name) %>
class ServerTest < Rack::TestCase
test 'get root' do
get '/'
assert_response :ok
assert_content_type :html
assert_body_includes('Index template')
end
private
def app
<%= Thor::Util.camel_case(name) %>::Server
end
end
end
$:.push File.join(File.dirname(__FILE__), '..', 'lib')
require 'simplecov'
SimpleCov.start do
add_filter '/test/' # Don't report on test files
coverage_dir 'tmp/coverage' # Put results in /tmp folder
end
require 'test/unit'
require 'contest'
require 'mocha'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment