Permalink
Browse files

Initial public commit

  • Loading branch information...
0 parents commit fb971c863fe91dd1244fa2866337e9e2ac5356bd @pauldowman pauldowman committed Nov 14, 2010
Showing with 15,103 additions and 0 deletions.
  1. +8 −0 .gitignore
  2. +1 −0 .rspec
  3. +1 −0 .rvmrc
  4. +32 −0 Gemfile
  5. +164 −0 Gemfile.lock
  6. +166 −0 README.md
  7. +10 −0 Rakefile
  8. +3 −0 app/controllers/application_controller.rb
  9. +8 −0 app/controllers/homepage_controller.rb
  10. +41 −0 app/controllers/pages_controller.rb
  11. +2 −0 app/helpers/application_helper.rb
  12. +2 −0 app/helpers/homepage_helper.rb
  13. +60 −0 app/helpers/pages_helper.rb
  14. +100 −0 app/models/page.rb
  15. +15 −0 app/views/homepage/index.html.haml
  16. +11 −0 app/views/layouts/_footer.html.haml
  17. +8 −0 app/views/layouts/_head.html.haml
  18. +9 −0 app/views/layouts/_syntax_highlighter.html.haml
  19. +8 −0 app/views/layouts/application.html.haml
  20. +9 −0 app/views/pages/index.html.haml
  21. +20 −0 app/views/pages/show.html.haml
  22. +2 −0 autotest/discover.rb
  23. +4 −0 config.ru
  24. +57 −0 config/application.rb
  25. +14 −0 config/boot.rb
  26. +10 −0 config/cucumber.yml
  27. +25 −0 config/database.yml
  28. +5 −0 config/environment.rb
  29. +30 −0 config/environments/development.rb
  30. +54 −0 config/environments/production.rb
  31. +40 −0 config/environments/test.rb
  32. +7 −0 config/initializers/backtrace_silencers.rb
  33. +10 −0 config/initializers/inflections.rb
  34. +5 −0 config/initializers/mime_types.rb
  35. +5 −0 config/locales/en.yml
  36. +84 −0 config/routes.rb
  37. +14 −0 db/schema.rb
  38. +7 −0 db/seeds.rb
  39. +2 −0 doc/README_FOR_APP
  40. BIN features/bindata/cat.jpg
  41. BIN features/bindata/cat.zip
  42. +44 −0 features/homepage.feature
  43. +64 −0 features/page_indexes.feature
  44. +156 −0 features/pages.feature
  45. +40 −0 features/step_definitions/custom_web_steps.rb
  46. +34 −0 features/step_definitions/page_steps.rb
  47. +219 −0 features/step_definitions/web_steps.rb
  48. +59 −0 features/support/env.rb
  49. +33 −0 features/support/paths.rb
  50. +3 −0 features/support/setup.rb
  51. 0 lib/tasks/.gitkeep
  52. +9 −0 lib/tasks/balisong.rake
  53. +53 −0 lib/tasks/cucumber.rake
  54. +26 −0 public/404.html
  55. +26 −0 public/422.html
  56. +26 −0 public/500.html
  57. 0 public/favicon.ico
  58. BIN public/images/rails.png
  59. +2 −0 public/javascripts/application.js
  60. +963 −0 public/javascripts/controls.js
  61. +973 −0 public/javascripts/dragdrop.js
  62. +1,128 −0 public/javascripts/effects.js
  63. +4,320 −0 public/javascripts/prototype.js
  64. +110 −0 public/javascripts/rails.js
  65. +165 −0 public/lib/syntaxhighlighter_3.0.83/LGPL-LICENSE
  66. +20 −0 public/lib/syntaxhighlighter_3.0.83/MIT-LICENSE
  67. +17 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shAutoloader.js
  68. +59 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shBrushAS3.js
  69. +75 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shBrushAppleScript.js
  70. +59 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shBrushBash.js
  71. +65 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shBrushCSharp.js
  72. +100 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shBrushColdFusion.js
  73. +97 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shBrushCpp.js
  74. +91 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shBrushCss.js
  75. +55 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shBrushDelphi.js
  76. +41 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shBrushDiff.js
  77. +52 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shBrushErlang.js
  78. +67 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shBrushGroovy.js
  79. +52 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shBrushJScript.js
  80. +57 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shBrushJava.js
  81. +58 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shBrushJavaFX.js
  82. +72 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shBrushPerl.js
  83. +88 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shBrushPhp.js
  84. +33 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shBrushPlain.js
  85. +74 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shBrushPowerShell.js
  86. +64 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shBrushPython.js
  87. +55 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shBrushRuby.js
  88. +94 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shBrushSass.js
  89. +51 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shBrushScala.js
  90. +66 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shBrushSql.js
  91. +56 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shBrushVb.js
  92. +69 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shBrushXml.js
  93. +17 −0 public/lib/syntaxhighlighter_3.0.83/scripts/shCore.js
  94. +226 −0 public/lib/syntaxhighlighter_3.0.83/styles/shCore.css
  95. +328 −0 public/lib/syntaxhighlighter_3.0.83/styles/shCoreDefault.css
  96. +331 −0 public/lib/syntaxhighlighter_3.0.83/styles/shCoreDjango.css
  97. +339 −0 public/lib/syntaxhighlighter_3.0.83/styles/shCoreEclipse.css
  98. +324 −0 public/lib/syntaxhighlighter_3.0.83/styles/shCoreEmacs.css
  99. +328 −0 public/lib/syntaxhighlighter_3.0.83/styles/shCoreFadeToGrey.css
  100. +324 −0 public/lib/syntaxhighlighter_3.0.83/styles/shCoreMDUltra.css
  101. +324 −0 public/lib/syntaxhighlighter_3.0.83/styles/shCoreMidnight.css
  102. +324 −0 public/lib/syntaxhighlighter_3.0.83/styles/shCoreRDark.css
  103. +117 −0 public/lib/syntaxhighlighter_3.0.83/styles/shThemeDefault.css
  104. +120 −0 public/lib/syntaxhighlighter_3.0.83/styles/shThemeDjango.css
  105. +128 −0 public/lib/syntaxhighlighter_3.0.83/styles/shThemeEclipse.css
  106. +113 −0 public/lib/syntaxhighlighter_3.0.83/styles/shThemeEmacs.css
  107. +117 −0 public/lib/syntaxhighlighter_3.0.83/styles/shThemeFadeToGrey.css
  108. +113 −0 public/lib/syntaxhighlighter_3.0.83/styles/shThemeMDUltra.css
  109. +113 −0 public/lib/syntaxhighlighter_3.0.83/styles/shThemeMidnight.css
  110. +113 −0 public/lib/syntaxhighlighter_3.0.83/styles/shThemeRDark.css
  111. +5 −0 public/robots.txt
  112. 0 public/stylesheets/.gitkeep
  113. +10 −0 script/cucumber
  114. +10 −0 script/rails
  115. +53 −0 spec/models/page_spec.rb
  116. +27 −0 spec/spec_helper.rb
  117. +6 −0 spec/support/setup.rb
  118. 0 vendor/plugins/.gitkeep
8 .gitignore
@@ -0,0 +1,8 @@
+.DS_Store
+.bundle
+TODO.txt
+balisong-content
+capybara-*.html
+db/*.sqlite3
+log/*.log
+tmp/**/*
1 .rspec
@@ -0,0 +1 @@
+--colour
1 .rvmrc
@@ -0,0 +1 @@
+rvm use 1.9.2@balisong
32 Gemfile
@@ -0,0 +1,32 @@
+# Edit this Gemfile to bundle your application's dependencies.
+source :rubygems
+
+
+gem 'rails', '>=3.0.0'
+
+gem 'gitmodel', :path => '~/dev/gitmodel'
+gem 'haml'
+gem 'mime-types', :require => 'mime/types'
+gem 'rdiscount'
+gem 'RedCloth'
+
+group :development, :test do
+ gem 'ZenTest'
+ gem 'autotest'
+ gem 'autotest-fsevent' if RUBY_PLATFORM.downcase.include?("darwin") # OS X only
+ gem 'autotest-rails'
+ gem 'capybara'
+ gem 'cucumber-rails'
+ gem 'launchy'
+ gem 'rspec'
+ gem 'rspec-rails'
+end
+
+## Bundle edge rails:
+# gem 'rails', :git => 'git://github.com/rails/rails.git'
+
+# ActiveRecord requires a database adapter. By default,
+# Rails has selected sqlite3.
+#gem 'sqlite3-ruby', :require => 'sqlite3'
+
+
164 Gemfile.lock
@@ -0,0 +1,164 @@
+PATH
+ remote: ~/dev/gitmodel
+ specs:
+ gitmodel (0.0.1)
+ activemodel (>= 3.0.1)
+ activesupport (>= 3.0.1)
+ grit (>= 2.3.0)
+ lockfile (>= 1.4.3)
+
+GEM
+ remote: http://rubygems.org/
+ specs:
+ RedCloth (4.2.3)
+ ZenTest (4.4.0)
+ abstract (1.0.0)
+ actionmailer (3.0.1)
+ actionpack (= 3.0.1)
+ mail (~> 2.2.5)
+ actionpack (3.0.1)
+ activemodel (= 3.0.1)
+ activesupport (= 3.0.1)
+ builder (~> 2.1.2)
+ erubis (~> 2.6.6)
+ i18n (~> 0.4.1)
+ rack (~> 1.2.1)
+ rack-mount (~> 0.6.12)
+ rack-test (~> 0.5.4)
+ tzinfo (~> 0.3.23)
+ activemodel (3.0.1)
+ activesupport (= 3.0.1)
+ builder (~> 2.1.2)
+ i18n (~> 0.4.1)
+ activerecord (3.0.1)
+ activemodel (= 3.0.1)
+ activesupport (= 3.0.1)
+ arel (~> 1.0.0)
+ tzinfo (~> 0.3.23)
+ activeresource (3.0.1)
+ activemodel (= 3.0.1)
+ activesupport (= 3.0.1)
+ activesupport (3.0.1)
+ arel (1.0.1)
+ activesupport (~> 3.0.0)
+ autotest (4.4.2)
+ autotest-fsevent (0.2.3)
+ sys-uname
+ autotest-rails (4.1.0)
+ ZenTest
+ builder (2.1.2)
+ capybara (0.4.0)
+ celerity (>= 0.7.9)
+ culerity (>= 0.2.4)
+ mime-types (>= 1.16)
+ nokogiri (>= 1.3.3)
+ rack (>= 1.0.0)
+ rack-test (>= 0.5.4)
+ selenium-webdriver (>= 0.0.27)
+ xpath (~> 0.1.2)
+ celerity (0.8.2)
+ childprocess (0.1.3)
+ ffi (~> 0.6.3)
+ configuration (1.1.0)
+ cucumber (0.9.3)
+ builder (~> 2.1.2)
+ diff-lcs (~> 1.1.2)
+ gherkin (~> 2.2.9)
+ json (~> 1.4.6)
+ term-ansicolor (~> 1.0.5)
+ cucumber-rails (0.3.2)
+ cucumber (>= 0.8.0)
+ culerity (0.2.12)
+ diff-lcs (1.1.2)
+ erubis (2.6.6)
+ abstract (>= 1.0.0)
+ ffi (0.6.3)
+ rake (>= 0.8.7)
+ gherkin (2.2.9)
+ json (~> 1.4.6)
+ term-ansicolor (~> 1.0.5)
+ grit (2.3.0)
+ diff-lcs (~> 1.1)
+ mime-types (~> 1.15)
+ haml (3.0.23)
+ i18n (0.4.2)
+ json (1.4.6)
+ json_pure (1.4.6)
+ launchy (0.3.7)
+ configuration (>= 0.0.5)
+ rake (>= 0.8.1)
+ lockfile (1.4.3)
+ mail (2.2.9)
+ activesupport (>= 2.3.6)
+ i18n (~> 0.4.1)
+ mime-types (~> 1.16)
+ treetop (~> 1.4.8)
+ mime-types (1.16)
+ nokogiri (1.4.3.1)
+ polyglot (0.3.1)
+ rack (1.2.1)
+ rack-mount (0.6.13)
+ rack (>= 1.0.0)
+ rack-test (0.5.6)
+ rack (>= 1.0)
+ rails (3.0.1)
+ actionmailer (= 3.0.1)
+ actionpack (= 3.0.1)
+ activerecord (= 3.0.1)
+ activeresource (= 3.0.1)
+ activesupport (= 3.0.1)
+ bundler (~> 1.0.0)
+ railties (= 3.0.1)
+ railties (3.0.1)
+ actionpack (= 3.0.1)
+ activesupport (= 3.0.1)
+ rake (>= 0.8.4)
+ thor (~> 0.14.0)
+ rake (0.8.7)
+ rdiscount (1.6.5)
+ rspec (2.0.1)
+ rspec-core (~> 2.0.1)
+ rspec-expectations (~> 2.0.1)
+ rspec-mocks (~> 2.0.1)
+ rspec-core (2.0.1)
+ rspec-expectations (2.0.1)
+ diff-lcs (>= 1.1.2)
+ rspec-mocks (2.0.1)
+ rspec-core (~> 2.0.1)
+ rspec-expectations (~> 2.0.1)
+ rspec-rails (2.0.1)
+ rspec (~> 2.0.0)
+ rubyzip (0.9.4)
+ selenium-webdriver (0.0.29)
+ childprocess (>= 0.0.7)
+ ffi (~> 0.6.3)
+ json_pure
+ rubyzip
+ sys-uname (0.8.4)
+ term-ansicolor (1.0.5)
+ thor (0.14.3)
+ treetop (1.4.8)
+ polyglot (>= 0.3.1)
+ tzinfo (0.3.23)
+ xpath (0.1.2)
+ nokogiri (~> 1.3)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ RedCloth
+ ZenTest
+ autotest
+ autotest-fsevent
+ autotest-rails
+ capybara
+ cucumber-rails
+ gitmodel!
+ haml
+ launchy
+ mime-types
+ rails (>= 3.0.0)
+ rdiscount
+ rspec
+ rspec-rails
166 README.md
@@ -0,0 +1,166 @@
+Balisong: A blogging app for coders
+-----------------------------------
+
+_https://github.com/pauldowman/balisong_
+
+Balisong is a blog-aware website for coders, written in Ruby.
+
+Balisong believes that:
+
+* A coder should have a website that they hack on and customize.
+* The best way to edit website content is with your favorite text editor.
+* A static site isn't enough. A lot can be done with JavaScript but sometimes you need some
+ server-side functionality.
+* Content should be separate from the app and should be deployed/updated
+ separately.
+* It must be super simple to write content, deploy, and hack on.
+
+
+Features
+--------
+
+* A post or page is simply a directory of files. There's a main file (think of
+ it as index.html, but it can be Markdown, Textile, HTML, whatever), plus
+ included partials, image files, source code files, and downloadable files of
+ any type.
+ * Text can be written as Markdown, Textile, HTML, plain text or any other
+ common markup format.
+ * Posts/pages can consist of multiple parts: text, code blocks, and images.
+ Parts can be separate files and can be included by each other.
+ * Code blocks can be separate source code files so that they can be tested,
+ edited with syntax highlighting in your favorite editor, and so that you
+ don't have to copy & paste.
+ * Code is rendered using syntax highlighting
+ * Any partial (i.e. "include" file) can be rendered as code, so you can
+ have an HTML partial that gets rendered either as HTML or as source code
+ (or both).
+* All data is stored using Git (including dynamic data such as comments if/when
+ that gets implemented). (It uses
+ [GitModel](https://github.com/pauldowman/gitmodel))
+ * Content can be edited locally and deployed to the production serve using
+ Git without redeploying the app.
+ * Content can also be pushed to a public repository like GitHub where people
+ can follow it for updates and fork it to contribute articles.
+ * Backing up the site's data is just "git pull" (no need to bother with
+ dumping a database).
+ * The site's data can be rolled back to any previous version, and all history
+ is kept forever (stored efficiently by Git).
+* Easy to hack on and customize
+ * Balisong is a Rails 3 app using Haml and Sass
+ * Good test coverage with Cucumber and Rspec
+
+
+Customization
+------------
+
+It's a standard Rails app, but you'll want to customize it. The best way to do that is to fork this repo on GitHub. Please do send pull requests for general changes.
+
+Obviously you'll want to customize the look, which is done by editing the files
+under /app/views and the files under /public.
+
+There are also some important configuration options that need changing, such as
+the location of the git repo that contains the posts and pages. Search for
+CHANGEME comments in the code. (grep -r CHANGEME).
+
+
+Getting started
+---------------
+
+1. Deploy the app somewhere
+2. Create a new Git repo for the site content by running `RAILS_ENV=production
+ rake balisong:db:create` on the server
+3. Edit your content locally and push it to the repo on the server (TODO
+ instructions) OR: edit the content on the server in the Git repo that you
+ created in step 1 above, and commit it
+4. Profit!
+
+
+Format of pages and posts
+-------------------------
+
+The content database (i.e. the Git repo where the page content is stored) has a directory named "pages" at it's root, and inside that it has a directory for each page or post.
+
+So each page or post is a directory inside CONTENT_ROOT/pages. The directory must contain two files:
+1. A file for your article text named "main.ext" where "ext" is one of the
+ supported file types ("md", "markdown", "textile", "html", etc.) (TODO list
+ supported formats)
+2. A file named attributes.json for the article metadata such as title and categories
+
+__The metadata file__
+
+The attributes file is in JSON format, it's content should look something like
+this:
+
+ {
+ "title": "My favorite bourbons",
+ "categories": [ "Alcohol", "Delicious things", "Bourbon", "Whiskey" ]
+ }
+
+__The content files__
+
+The main content file ("main.md" or "main.textile", etc.) can include other content files. The syntax looks like this:
+
+ {{ filename | formatter }}
+
+The `formatter` argument can often be omitted if it can be guessed by the
+filename extension, for example the following are equivalent, and will include
+the file named knobcreek.markdown, rendering it as HTML:
+
+ {{ knobcreek.markdown | markdown}} {{ knobcreek.markdown}}
+
+But it can also be used to render a file in a different format, for example as
+source code. For example, to render the file named knobcreek.html as source
+code with syntax highlighting you would do:
+
+ {{ knobcreek.html | code(html)}}
+
+Note that you could also have the following which would simply include the file
+as HTML to be rendered normally by the browser:
+
+ {{ knobcreek.html}}
+
+Some more examples:
+
+Ruby code:
+
+{{whiskey.rb | code(ruby)}}
+
+Plain text files can be rendered in <pre> blocks:
+
+{{makersmark.txt | text}}
+
+Image files are rendered as images inline in the HTML (i.e. as ≶img> tags)
+
+{{bulleit.jpg | image}}
+
+Any type of file can be rendered as a download link, and when clicked the
+browser will prompt to download the file instead of displaying it:
+
+{{archive.zip | download}}
+{{bulleit.jpg | download}}
+
+The included content files can, of course, include other files. (Recursive includes are detected by the app hanging forever or crashing or something, I haven't tried it. Definitely not recommended.)
+
+
+__Pages vs. Posts__
+Blog "posts" are simply pages that have a date as part of the directory name in
+the format `YYYY-MM-DD-post-id-string.`
+
+"Posts" have a URL that includes the date, e.g.
+http://yourblog.com/2010/11/27/my-favorite-bourbons while normal pages have a
+url that is simply the page name, e.g.
+http://yourblog.com/my-favorite-bourbons.
+
+Posts (as opposed to Pages) show up in the "Recent posts" list.
+
+
+To do
+-----
+
+* Documentation
+* A simple but nice-looking default design
+ * HTML 5 boilerplate? https://github.com/himmel/html5-boilerplate
+* Atom feed
+* Comment support
+* Search for TODO in the code and Cucumber features tagged with @wip
+
10 Rakefile
@@ -0,0 +1,10 @@
+# Add your own tasks in files placed in lib/tasks ending in .rake,
+# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
+
+require File.expand_path('../config/application', __FILE__)
+
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+Balisong::Application.load_tasks
3 app/controllers/application_controller.rb
@@ -0,0 +1,3 @@
+class ApplicationController < ActionController::Base
+ protect_from_forgery
+end
8 app/controllers/homepage_controller.rb
@@ -0,0 +1,8 @@
+class HomepageController < ApplicationController
+ def index
+ # TODO only show posts with date
+ @recent_posts = Page.find_all.sort {|a,b| (b.date || Date.new) <=> (a.date || Date.new) }
+
+ @categories = Page.all_categories
+ end
+end
41 app/controllers/pages_controller.rb
@@ -0,0 +1,41 @@
+class PagesController < ApplicationController
+
+ def show
+ # convert '/' characters in id to '-'
+ id = Page.de_urlify(params[:id])
+
+ # TODO render 404 page if Page not found
+ @page = Page.find(id)
+ if params[:part]
+ # Request is for a part, it will be sent unformatted
+ if params[:download]
+ disposition = 'attachment'
+ mime_type = 'application/octet-stream'
+ else
+ disposition = 'inline'
+ mime_type = MIME::Types.type_for(params[:part]).first.content_type
+ end
+ # TODO render 404 page if Page doesn't have a blob with that name
+ send_data @page.blobs[params[:part]], :disposition => disposition, :type => mime_type
+ else
+ # Request is for the main page, it will be formatted
+ @formatter = params[:formatter] || Page.type(@page.main_part)
+ # Uses default render
+ end
+ end
+
+ def index
+ category = params[:category]
+ date_range = params[:date_range]
+
+ if category
+ @title = category
+ @pages = Page.in_category(category)
+ elsif date_range
+ @title = "All posts in \"#{date_range}\""
+ @pages = Page.in_date_range(date_range)
+ end
+
+ @pages = @pages.sort {|a,b| (b.date || Date.new) <=> (a.date || Date.new) }
+ end
+end
2 app/helpers/application_helper.rb
@@ -0,0 +1,2 @@
+module ApplicationHelper
+end
2 app/helpers/homepage_helper.rb
@@ -0,0 +1,2 @@
+module HomepageHelper
+end
60 app/helpers/pages_helper.rb
@@ -0,0 +1,60 @@
+module PagesHelper
+
+ # Render a part as HTML. Name is the filename of the part, and formatter is
+ # the name of the formatter that will be used to render the part as HTML,
+ # something like "code" or "html" or "code(ruby)".
+ # If formatter is nil it will be guessed based on the file extension.
+ def render_part(page, part_name, formatter)
+ if formatter.nil?
+ formatter = Page.type(part_name)
+ formatter_arg = nil
+ elsif formatter =~ /(.*)\(([^\)]+)\)?/
+ formatter = $1
+ formatter_arg = $2
+ else
+ formatter = formatter
+ formatter_arg = nil
+ end
+
+ Rails.logger.info "Rendering part: #{page}/#{part_name} with formatter #{formatter} (with arg: #{formatter_arg})"
+
+ raw_data = page.blobs[part_name]
+ raise "Can't find part '#{part_name}'" unless raw_data
+
+ # these should be extracted somewhere to be extensible
+ case formatter
+ when "markdown"
+ out = RDiscount.new(raw_data).to_html
+ when "textile"
+ out = RedCloth.new(raw_data).to_html
+ when "html"
+ out = raw_data
+ when "text"
+ out = "<pre>#{html_escape(raw_data)}</pre>"
+ when "code"
+ lang = formatter_arg || Page.type(part_name)
+ out = "<pre class='brush: #{lang}'>#{html_escape(raw_data)}</pre>"
+ when "image"
+ # TODO use url_for here
+ out = "<img src='/#{page.id}/#{part_name}'>"
+ skip_sub_render = true
+ when "download"
+ # TODO use url_for here
+ out = link_to(part_name, "/#{page.id}/#{part_name}?download=true")
+ skip_sub_render = true
+ else
+ raise "unknown formatter: #{formatter}"
+ end
+
+ # Render sub-parts recursively
+ unless skip_sub_render
+ # match {{ file.md }} or {{file.rb | code(ruby)}}, etc.
+ regex = /\{\{\s*([\w\.-]+)\s*\|?\s*([\w\.-]+\(?[\w\.-]*\)?)?\s*\}\}/
+ out = out.gsub(regex) {|match| render_part(page, $1, $2) }
+ end
+
+ return out
+ end
+
+end
+
100 app/models/page.rb
@@ -0,0 +1,100 @@
+# This is the model class that represents all pages, including blog posts.
+# It is not an ActiveRecord model, it stores it's data in files.
+class Page
+ include GitModel::Persistable
+
+ attribute :title
+ attribute :categories, :default => []
+ attribute :allow_comments, :default => true
+ attribute :include_in_site_menu, :default => false
+ attribute :include_in_feed, :default => true
+
+ def date
+ if id =~ /(\d{4}-\d{2}-\d{2})-.+/
+ return Date.parse($1)
+ else
+ return nil
+ end
+ end
+
+ # Determine the name of the main part
+ def main_part
+ # TODO iterate over blobs here instead of using filesystem
+ maybes = blobs.keys.keep_if{|k| k =~ /^main/}
+
+ return maybes.any? ? File.basename(maybes.first) : nil
+ end
+
+ # return all Page objects that have the given category in the categories array
+ def self.in_category(category)
+ results = []
+ find_all.each do |o|
+ results << o if o.categories.include?(category)
+ end
+ return results
+ end
+
+ # return all Page objects that have a date that matches the given date range.
+ # A date range is a string that matches 'YYYY/MM/DD' or 'YYYY/MM' or just
+ # 'YYYY'
+ def self.in_date_range(date_range)
+ year, month, day = date_range.split('/').map{|el| el.to_i}
+
+ results = []
+ find_all.each do |o|
+ if o.date && year == o.date.year && (month.nil? || month == o.date.month) && (day.nil? || day == o.date.day)
+ results << o
+ end
+ end
+ return results
+ end
+
+ def self.all_categories
+ categories = []
+ find_all.each {|page| categories += page.categories}
+ return categories.uniq.sort
+ end
+
+ # convert an id to the format with slashes that's used in url's
+ def self.urlify(id)
+ if id =~ /(\d{4})-(\d{2})-(\d{2})-(.+)/
+ return "#{$1}/#{$2}/#{$3}/#{$4}"
+ else
+ return id
+ end
+ end
+
+ # convert an id from the format with slashes that's used in url's
+ def self.de_urlify(id)
+ if id =~ /(\d{4})\/(\d{2})\/(\d{2})\/(.+)/
+ return "#{$1}-#{$2}-#{$3}-#{$4}"
+ else
+ return id
+ end
+ end
+
+ # Determine the type of a part based on the name, returning a normalized
+ # canonical type name in lower case.
+ # Examples:
+ # "foo.md" -> "markdown"
+ # "bar.htm -> "html"
+ # "baz.HTML -> "html"
+ def self.type(name)
+ return nil unless name && File.extname(name) =~ /\.(.*)/
+
+ type = $1.downcase
+
+ # return the canonical type name if it's an abbreviation:
+ case type
+ when "md"
+ type = "markdown"
+ when "htm"
+ type = "html"
+ when "rb"
+ type = "ruby"
+ end
+
+ return type
+ end
+
+end
15 app/views/homepage/index.html.haml
@@ -0,0 +1,15 @@
+%h1
+ Main p
+
+Some recent posts:
+%ul#recent_posts
+ - @recent_posts.each do |p|
+ %li
+ = link_to p.title, '/' + Page.urlify(p.id)
+
+All categories:
+%ul#all_categories
+ - @categories.each do |cat|
+ %li
+ = link_to cat, '/category/' + cat
+
11 app/views/layouts/_footer.html.haml
@@ -0,0 +1,11 @@
+%hr
+= link_to 'Home', root_url
+
+/ TODO remove this obtrusive JS
+%script{:type => "text/javascript"}
+ SyntaxHighlighter.autoloader(
+ 'js jscript javascript /lib/syntaxhighlighter_3.0.83/scripts/shBrushJScript.js',
+ 'ruby /lib/syntaxhighlighter_3.0.83/scripts/shBrushRuby.js',
+ 'xml xhtml xslt html xhtml /lib/syntaxhighlighter_3.0.83/scripts/shBrushXml.js'
+ );
+ SyntaxHighlighter.all();
8 app/views/layouts/_head.html.haml
@@ -0,0 +1,8 @@
+%head
+ %title
+ -if @title.blank?
+ Balisong
+ - else
+ = @title
+
+ = render :partial => '/layouts/syntax_highlighter'
9 app/views/layouts/_syntax_highlighter.html.haml
@@ -0,0 +1,9 @@
+/
+ Includes for SyntaxHighlighter: http://alexgorbatchev.com/SyntaxHighlighter
+ TODO include all brushes and compress JS
+- dir = '/lib/syntaxhighlighter_3.0.83'
+= javascript_include_tag "#{dir}/scripts/shCore.js"
+= javascript_include_tag "#{dir}/scripts/shAutoloader.js"
+= stylesheet_link_tag "#{dir}/styles/shCore.css"
+= stylesheet_link_tag "#{dir}/styles/shThemeDefault.css"
+/ Obtrusive JS is in footer
8 app/views/layouts/application.html.haml
@@ -0,0 +1,8 @@
+!!!
+%html
+ = render :partial => 'layouts/head'
+
+ %body
+ = yield
+
+ = render :partial => 'layouts/footer'
9 app/views/pages/index.html.haml
@@ -0,0 +1,9 @@
+%h2
+ = @title
+%ul#pages
+ - @pages.each do |page|
+ %li
+ = page.date
+ →
+ = link_to page.title, '/' + Page.urlify(page.id)
+
20 app/views/pages/show.html.haml
@@ -0,0 +1,20 @@
+- @title = @page.title
+
+- if @page.categories && @page.categories.any?
+ Categories:
+ %ul#categories
+ - @page.categories.sort.each do |c|
+ %li
+ = link_to c, "/category/#{c}"
+
+- if @page.date
+ #date
+ Posted at:
+ = @page.date
+
+%h1
+ = @title
+
+#page
+ ~ raw render_part(@page, @page.main_part, @formatter)
+
2 autotest/discover.rb
@@ -0,0 +1,2 @@
+Autotest.add_discovery { "rails" }
+Autotest.add_discovery { "rspec2" }
4 config.ru
@@ -0,0 +1,4 @@
+# This file is used by Rack-based servers to start the application.
+
+require ::File.expand_path('../config/environment', __FILE__)
+run Balisong::Application
57 config/application.rb
@@ -0,0 +1,57 @@
+require File.expand_path('../boot', __FILE__)
+
+#require 'rails/all'
+require "action_controller/railtie"
+require "action_mailer/railtie"
+require "active_resource/railtie"
+require "rails/test_unit/railtie"
+
+
+# If you have a Gemfile, require the gems listed there, including any gems
+# you've limited to :test, :development, or :production.
+Bundler.require(:default, Rails.env) if defined?(Bundler)
+
+# Any explicit requires should go here
+require 'pp'
+
+module Balisong
+ class Application < Rails::Application
+
+ # CHANGEME: Customize these
+ config.secret_token = 'e5368f35c26f28526bce69313f29709cf35936a4a869e983860210404913c4cd3fb93e3de715a31f93c038fcf8e9d2e680581f65c2d19ad0dc4fd89e8f55ab8a'
+ config.session_store :cookie_store, :key => '_balisong_session'
+
+ # Settings in config/environments/* take precedence over those specified here.
+ # Application configuration should go into files in config/initializers
+ # -- all .rb files in that directory are automatically loaded.
+
+ # Custom directories with classes and modules you want to be autoloadable.
+ # config.autoload_paths += %W(#{config.root}/extras)
+
+ # Only load the plugins named here, in the order given (default is alphabetical).
+ # :all can be used as a placeholder for all plugins not explicitly named.
+ # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
+
+ # Activate observers that should always be running.
+ # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
+
+ # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
+ # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
+ # config.time_zone = 'Central Time (US & Canada)'
+
+ # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
+ # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
+ # config.i18n.default_locale = :de
+
+ # JavaScript files you want as :defaults (application.js is always included).
+ # config.action_view.javascript_expansions[:defaults] = %w(jquery rails)
+
+ # Configure the default encoding used in templates for Ruby 1.9.
+ config.encoding = "utf-8"
+
+ config.generators do |g|
+ g.test_framework :rspec
+ end
+
+ end
+end
14 config/boot.rb
@@ -0,0 +1,14 @@
+require 'rubygems'
+
+# Set up gems listed in the Gemfile.
+gemfile = File.expand_path('../../Gemfile', __FILE__)
+begin
+ ENV['BUNDLE_GEMFILE'] = gemfile
+ require 'bundler'
+ Bundler.setup
+rescue Bundler::GemNotFound => e
+ STDERR.puts e.message
+ STDERR.puts "Try running `bundle install`."
+ exit!
+end if File.exist?(gemfile)
+
10 config/cucumber.yml
@@ -0,0 +1,10 @@
+<%
+rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : ""
+rerun_opts = rerun.to_s.strip.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} --color #{rerun}"
+std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} --strict --tags ~@wip --color"
+%>
+default: <%= std_opts %> features
+wip: --tags @wip:3 --wip features
+rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags ~@wip
+#autotest:
+#autotest-all:
25 config/database.yml
@@ -0,0 +1,25 @@
+# SQLite version 3.x
+# gem install sqlite3-ruby (not necessary on OS X Leopard)
+development:
+ adapter: sqlite3
+ database: db/development.sqlite3
+ pool: 5
+ timeout: 5000
+
+# Warning: The database defined as "test" will be erased and
+# re-generated from your development database when you run "rake".
+# Do not set this db to the same as development or production.
+test: &test
+ adapter: sqlite3
+ database: db/test.sqlite3
+ pool: 5
+ timeout: 5000
+
+production:
+ adapter: sqlite3
+ database: db/production.sqlite3
+ pool: 5
+ timeout: 5000
+
+cucumber:
+ <<: *test
5 config/environment.rb
@@ -0,0 +1,5 @@
+# Load the rails application
+require File.expand_path('../application', __FILE__)
+
+# Initialize the rails application
+Balisong::Application.initialize!
30 config/environments/development.rb
@@ -0,0 +1,30 @@
+Balisong::Application.configure do
+ # Settings specified here will take precedence over those in config/environment.rb
+
+ # In the development environment your application's code is reloaded on
+ # every request. This slows down response time but is perfect for development
+ # since you don't have to restart the webserver when you make code changes.
+ config.cache_classes = false
+
+ # Log error messages when you accidentally call methods on nil.
+ config.whiny_nils = true
+
+ # Show full error reports and disable caching
+ config.consider_all_requests_local = true
+ config.action_view.debug_rjs = true
+ config.action_controller.perform_caching = false
+
+ # Don't care if the mailer can't send
+ config.action_mailer.raise_delivery_errors = false
+
+ # Print deprecation notices to the Rails logger
+ config.active_support.deprecation = :log
+
+ # Only use best-standards-support built into browsers
+ config.action_dispatch.best_standards_support = :builtin
+
+ # CHANGEME: Customize these
+ GitModel.db_root = File.join(Rails.root, "balisong-content")
+ GitModel.git_user_name = 'Balisong'
+ GitModel.git_user_email = nil
+end
54 config/environments/production.rb
@@ -0,0 +1,54 @@
+Balisong::Application.configure do
+ # Settings specified here will take precedence over those in config/environment.rb
+
+ # The production environment is meant for finished, "live" apps.
+ # Code is not reloaded between requests
+ config.cache_classes = true
+
+ # Full error reports are disabled and caching is turned on
+ config.consider_all_requests_local = false
+ config.action_controller.perform_caching = true
+
+ # Specifies the header that your server uses for sending files
+ config.action_dispatch.x_sendfile_header = "X-Sendfile"
+
+ # For nginx:
+ # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'
+
+ # If you have no front-end server that supports something like X-Sendfile,
+ # just comment this out and Rails will serve the files
+
+ # See everything in the log (default is :info)
+ # config.log_level = :debug
+
+ # Use a different logger for distributed setups
+ # config.logger = SyslogLogger.new
+
+ # Use a different cache store in production
+ # config.cache_store = :mem_cache_store
+
+ # Disable Rails's static asset server
+ # In production, Apache or nginx will already do this
+ config.serve_static_assets = false
+
+ # Enable serving of images, stylesheets, and javascripts from an asset server
+ # config.action_controller.asset_host = "http://assets.example.com"
+
+ # Disable delivery errors, bad email addresses will be ignored
+ # config.action_mailer.raise_delivery_errors = false
+
+ # Enable threaded mode
+ # config.threadsafe!
+
+ # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
+ # the I18n.default_locale when a translation can not be found)
+ config.i18n.fallbacks = true
+
+ # Send deprecation notices to registered listeners
+ config.active_support.deprecation = :notify
+
+ # CHANGEME: Customize these
+ GitModel.db_root = "/usr/local/balisong-content"
+ GitModel.git_user_name = 'Balisong'
+ GitModel.git_user_email = nil
+end
40 config/environments/test.rb
@@ -0,0 +1,40 @@
+Balisong::Application.configure do
+ # Settings specified here will take precedence over those in config/environment.rb
+
+ # The test environment is used exclusively to run your application's
+ # test suite. You never need to work with it otherwise. Remember that
+ # your test database is "scratch space" for the test suite and is wiped
+ # and recreated between test runs. Don't rely on the data there!
+ config.cache_classes = true
+
+ # Log error messages when you accidentally call methods on nil.
+ config.whiny_nils = true
+
+ # Show full error reports and disable caching
+ config.consider_all_requests_local = true
+ config.action_controller.perform_caching = false
+
+ # Raise exceptions instead of rendering exception templates
+ config.action_dispatch.show_exceptions = false
+
+ # Disable request forgery protection in test environment
+ config.action_controller.allow_forgery_protection = false
+
+ # Tell Action Mailer not to deliver emails to the real world.
+ # The :test delivery method accumulates sent emails in the
+ # ActionMailer::Base.deliveries array.
+ config.action_mailer.delivery_method = :test
+
+ # Use SQL instead of Active Record's schema dumper when creating the test database.
+ # This is necessary if your schema can't be completely dumped by the schema dumper,
+ # like if you have constraints or database-specific column types
+ # config.active_record.schema_format = :sql
+
+ # Print deprecation notices to the stderr
+ config.active_support.deprecation = :stderr
+
+ # CHANGEME: Customize these
+ GitModel.db_root = "/tmp/balisong-content"
+ GitModel.git_user_name = 'Balisong'
+ GitModel.git_user_email = nil
+end
7 config/initializers/backtrace_silencers.rb
@@ -0,0 +1,7 @@
+# Be sure to restart your server when you modify this file.
+
+# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
+# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
+
+# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
+# Rails.backtrace_cleaner.remove_silencers!
10 config/initializers/inflections.rb
@@ -0,0 +1,10 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new inflection rules using the following format
+# (all these examples are active by default):
+# ActiveSupport::Inflector.inflections do |inflect|
+# inflect.plural /^(ox)$/i, '\1en'
+# inflect.singular /^(ox)en/i, '\1'
+# inflect.irregular 'person', 'people'
+# inflect.uncountable %w( fish sheep )
+# end
5 config/initializers/mime_types.rb
@@ -0,0 +1,5 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new mime types for use in respond_to blocks:
+# Mime::Type.register "text/richtext", :rtf
+# Mime::Type.register_alias "text/html", :iphone
5 config/locales/en.yml
@@ -0,0 +1,5 @@
+# Sample localization file for English. Add more files in this directory for other locales.
+# See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
+
+en:
+ hello: "Hello world"
84 config/routes.rb
@@ -0,0 +1,84 @@
+Balisong::Application.routes.draw do
+
+ root :to => "homepage#index"
+
+ # PagesController#index with a category arg
+ get 'category/:category',
+ :to => 'pages#index',
+ :constraints => {
+ :category => /[\w%]+/
+ }
+
+ # PagesController#index with a date arg
+ get ':date_range',
+ :to => 'pages#index',
+ :constraints => {
+ :date_range => /\d{4}(\/\d{2})?(\/\d{2})?/
+ }
+
+ # PagesController#show with an id and optionally a part name
+ get ':id(/:part)',
+ :to => 'pages#show',
+ :constraints => {
+ :id => /(\d{4}\/\d{2}\/\d{2}\/)?[\w-]+/,
+ :part => /[\w\.-]+/
+ }
+
+
+ # The priority is based upon order of creation:
+ # first created -> highest priority.
+
+ # Sample of regular route:
+ # match 'products/:id' => 'catalog#view'
+ # Keep in mind you can assign values other than :controller and :action
+
+ # Sample of named route:
+ # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
+ # This route can be invoked with purchase_url(:id => product.id)
+
+ # Sample resource route (maps HTTP verbs to controller actions automatically):
+ # resources :products
+
+ # Sample resource route with options:
+ # resources :products do
+ # member do
+ # get :short
+ # post :toggle
+ # end
+ #
+ # collection do
+ # get :sold
+ # end
+ # end
+
+ # Sample resource route with sub-resources:
+ # resources :products do
+ # resources :comments, :sales
+ # resource :seller
+ # end
+
+ # Sample resource route with more complex sub-resources
+ # resources :products do
+ # resources :comments
+ # resources :sales do
+ # get :recent, :on => :collection
+ # end
+ # end
+
+ # Sample resource route within a namespace:
+ # namespace :admin do
+ # # Directs /admin/products/* to Admin::ProductsController
+ # # (app/controllers/admin/products_controller.rb)
+ # resources :products
+ # end
+
+ # You can have the root of your site routed with "root"
+ # just remember to delete public/index.html.
+ # root :to => "welcome#index"
+
+ # See how all your routes lay out with "rake routes"
+
+ # This is a legacy wild controller route that's not recommended for RESTful applications.
+ # Note: This route will make all actions in every controller accessible via GET requests.
+ # match ':controller(/:action(/:id(.:format)))'
+end
14 db/schema.rb
@@ -0,0 +1,14 @@
+# This file is auto-generated from the current state of the database. Instead of editing this file,
+# please use the migrations feature of Active Record to incrementally modify your database, and
+# then regenerate this schema definition.
+#
+# Note that this schema.rb definition is the authoritative source for your database schema. If you need
+# to create the application database on another system, you should be using db:schema:load, not running
+# all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations
+# you'll amass, the slower it'll run and the greater likelihood for issues).
+#
+# It's strongly recommended to check this file into your version control system.
+
+ActiveRecord::Schema.define(:version => 0) do
+
+end
7 db/seeds.rb
@@ -0,0 +1,7 @@
+# This file should contain all the record creation needed to seed the database with its default values.
+# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
+#
+# Examples:
+#
+# cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])
+# Mayor.create(:name => 'Daley', :city => cities.first)
2 doc/README_FOR_APP
@@ -0,0 +1,2 @@
+Use this README file to introduce your application and point to useful places in the API for learning more.
+Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries.
BIN features/bindata/cat.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN features/bindata/cat.zip
Binary file not shown.
44 features/homepage.feature
@@ -0,0 +1,44 @@
+Feature: Home page
+ In order to easily find and discover the site's content
+ as a visitor
+ I see a homepage with an overview of what's available on the site.
+
+ Scenario: Display list of recent posts ordered with most recent first.
+ Given the following pages:
+ | title | body | id | categories |
+ | Whiskey | A blog post about Whiskey | 2010-08-01-whiskey | |
+ | Scotch | Something about Scotch | 2010-09-01-scotch | |
+ | Rye | A blog post about Rye | 2010-10-01-rye | |
+ When I go to the homepage
+ Then I should see the following list of links with css id "recent_posts":
+ | Rye | /2010/10/01/rye |
+ | Scotch | /2010/09/01/scotch |
+ | Whiskey | /2010/08/01/whiskey |
+
+ Scenario: Don't display pages without dates in list of recent posts.
+ Given the following pages:
+ | title | body | id | categories |
+ | Whiskey | A blog post about Whiskey | 2010-08-01-whiskey | |
+ | Scotch | Something about Scotch | 2010-09-01-scotch | |
+ | Rye | A page (no date in id) | rye | |
+ When I go to the homepage
+ Then I should see the following list of links with css id "recent_posts":
+ | Scotch | /2010/09/01/scotch |
+ | Whiskey | /2010/08/01/whiskey |
+ | Rye | /rye |
+
+ Scenario: Display a list of all categories on home page.
+ Given the following pages:
+ | title | body | id | categories |
+ | Whiskey | A blog post about Whiskey | 2010-08-01-whiskey | Whiskey, Alcohol |
+ | Scotch | Something about Scotch | 2010-09-01-scotch | Scotch, Alcohol |
+ | Rye | A blog post about Rye | 2010-10-01-rye | Rye, Alcohol, Malt |
+ When I go to the homepage
+ Then I should see the following list of links with css id "all_categories":
+ | Alcohol | /category/Alcohol |
+ | Malt | /category/Malt |
+ | Rye | /category/Rye |
+ | Scotch | /category/Scotch |
+ | Whiskey | /category/Whiskey |
+
+
64 features/page_indexes.feature
@@ -0,0 +1,64 @@
+Feature: Page indexes
+ In order to easily find and discover the site's content
+ as a visitor
+ I can see lists of the pages and posts by category and date
+
+ Scenario: List all pages in a category
+ Given the following pages:
+ | title | body | id | categories |
+ | Bourbon | A blog post about Bourbon | 2010-08-01-bourbon| Bourbon, Alcohol |
+ | Scotch | A blog post about Scotch | 2010-10-01-scotch | Scotch, Bourbon, Rye, Bourbon rye, Alcohol |
+ | Rye | Something about Rye | rye | Rye, Alcohol |
+ When I go to the path "/category/Rye"
+ Then I should see the following list of links with css id "pages":
+ | Scotch | /2010/10/01/scotch |
+ | Rye | /rye |
+
+ When I go to the path "/category/Bourbon"
+ Then I should see the following list of links with css id "pages":
+ | Scotch | /2010/10/01/scotch |
+ | Bourbon | /2010/08/01/bourbon |
+
+ When I go to the path "/category/Scotch"
+ Then I should see the following list of links with css id "pages":
+ | Scotch | /2010/10/01/scotch |
+
+ When I go to the path "/category/Bourbon%20rye"
+ Then I should see the following list of links with css id "pages":
+ | Scotch | /2010/10/01/scotch |
+
+ When I go to the path "/category/Alcohol"
+ Then I should see the following list of links with css id "pages":
+ | Scotch | /2010/10/01/scotch |
+ | Bourbon | /2010/08/01/bourbon |
+ | Rye | /rye |
+
+
+ Scenario: List all posts in a date range
+ Given the following pages:
+ | title | body | id |
+ | Bourbon | A blog post about Bourbon | 2010-08-01-bourbon |
+ | Rye | A blog post about Rye | 2010-08-11-rye |
+ | Malt | A blog post about Malt | 2009-10-01-malt |
+ | Scotch | A blog post about Scotch | 2010-10-01-scotch |
+ | Rye | Something about Rye | rye |
+ When I go to the path "/2010"
+ Then I should see the following list of links with css id "pages":
+ | Scotch | /2010/10/01/scotch |
+ | Rye | /2010/08/11/rye |
+ | Bourbon | /2010/08/01/bourbon |
+
+ When I go to the path "/2010/08"
+ Then I should see the following list of links with css id "pages":
+ | Rye | /2010/08/11/rye |
+ | Bourbon | /2010/08/01/bourbon |
+
+ When I go to the path "/2010/08/11"
+ Then I should see the following list of links with css id "pages":
+ | Rye | /2010/08/11/rye |
+
+ When I go to the path "/2009/10/01"
+ Then I should see the following list of links with css id "pages":
+ | Malt | /2009/10/01/malt |
+
+
156 features/pages.feature
@@ -0,0 +1,156 @@
+Feature: Pages
+ In order to browse the site's content
+ as a visitor
+ I can load each page of content
+
+
+ Scenario: Basic page without date in the id
+ Given the following pages:
+ | title | body | id | categories |
+ | Whiskey | A page with no date | whiskey | alcohol, bourbon, scotch, whiskey |
+ When I go to the path "/whiskey"
+ Then I should see "A page with no date"
+ And The title should be "Whiskey"
+ And I should see the following list of links with css id "categories":
+ | alcohol | /category/alcohol |
+ | bourbon | /category/bourbon |
+ | scotch | /category/scotch |
+ | whiskey | /category/whiskey |
+
+
+ Scenario: Basic page with date in the id
+ Given the following pages:
+ | title | body | id | categories |
+ | Whiskey | A page with no date | 2010-02-01-whiskey | alcohol, bourbon, scotch, whiskey |
+ When I go to the path "/2010/02/01/whiskey"
+ Then I should see "A page with no date"
+ And The title should be "Whiskey"
+ And I should see the following list of links with css id "categories":
+ | alcohol | /category/alcohol |
+ | bourbon | /category/bourbon |
+ | scotch | /category/scotch |
+ | whiskey | /category/whiskey |
+
+
+ @wip
+ Scenario: Missing page
+ Given the following pages:
+ | title | body | id |
+ | Bourbon | A page. | bourbon |
+ When I go to the path "/scotch"
+ Then the response should be "not found"
+
+
+ Scenario: Page with multiple parts
+ Given the following pages:
+ | title | id |
+ | Bourbon | bourbon |
+ And the page with id "bourbon" has part "main.md" with content:
+ """
+ This is a post about *bourbon*.
+ It includes a mardown part:
+ {{part1.md | markdown}}
+ And another markdown part with no explicit formatter:
+ {{part2.md}}
+ """
+ And the page with id "bourbon" has part "part1.md" with content "This is part 1."
+ And the page with id "bourbon" has part "part2.md" with content "This is part 2."
+ When I go to the path "/bourbon"
+ Then I should see "This is part 1"
+ And I should see "This is part 2"
+
+
+ Scenario: Page with HTML rendered as source code
+ Given the following pages:
+ | title | id |
+ | HTML Test | htmltest |
+ And the page with id "htmltest" has part "main.md" with content:
+ """
+ Some HTML rendered:
+ {{knobcreek.html | html}}
+ And the same HTML as code source:
+ {{knobcreek.html | code(html)}}
+ """
+ And the page with id "htmltest" has part "knobcreek.html" with content "<h2>So delicious</h2>"
+ When I go to the path "/htmltest"
+ And I should see "So delicious" within "h2"
+ And I should see "<h2>So delicious</h2>" within "pre"
+
+
+ Scenario: Page with ruby rendered as source code
+ Given the following pages:
+ | title | id |
+ | Ruby Test | rubytest |
+ And the page with id "rubytest" has part "main.md" with content:
+ """
+ Here is some sample Ruby code:
+ {{sample.rb | code(ruby)}}
+ """
+ And the page with id "rubytest" has part "sample.rb" with content:
+ """
+ def wtf(x)
+ if x < 17 && x > 5
+ puts "yay, the < and > were escaped properly"
+ end
+ end
+ """
+ When I go to the path "/rubytest"
+ Then I should see "def wtf" within "pre"
+ Then I should see "yay, the < and > were escaped properly" within "pre"
+
+
+ Scenario: Page with text file part
+ Given the following pages:
+ | title | id |
+ | Text Test | texttest |
+ And the page with id "texttest" has part "main.md" with content:
+ """
+ Here is some plain text:
+ {{something.txt | text}}
+
+ """
+ And the page with id "texttest" has part "something.txt" with content "This is a text file."
+ When I go to the path "/texttest"
+ Then I should see "This is a text file" within "pre"
+
+
+ Scenario: Page with image
+ Given the following pages:
+ | title | id |
+ | Page with image | image1 |
+ And the page with id "image1" has part "main.md" with content:
+ """
+ A picture of a cat:
+ {{cat.jpg | image}}
+ """
+ And the page with id "image1" has part "cat.jpg" with content from file "cat.jpg"
+ When I go to the path "/image1"
+ Then I should see the image "/image1/cat.jpg"
+
+ When I go to the path "/image1/cat.jpg"
+ Then the response should match the content of the file "cat.jpg"
+ And the browser should be told to display the response as a JPEG image
+
+ When I go to the path "/image1/cat.jpg?download=true"
+ Then the response should match the content of the file "cat.jpg"
+ And the browser should be told to save the response as a file
+
+
+ Scenario: Page with file to download
+ Given the following pages:
+ | title | id |
+ | Page with zip file | download1 |
+ And the page with id "download1" has part "main.md" with content:
+ """
+ A file to download:
+ {{cat.zip | download}}
+ """
+ And the page with id "download1" has part "cat.zip" with content from file "cat.zip"
+ When I go to the path "/download1"
+ Then I should see a link to "/download1/cat.zip?download=true" with text "cat.zip"
+
+ When I go to the path "/download1/cat.zip?download=true"
+ Then the response should match the content of the file "cat.zip"
+ And the browser should be told to save the response as a file
+
+
40 features/step_definitions/custom_web_steps.rb
@@ -0,0 +1,40 @@
+When /^(?:|I )go to the path "(.+)"$/ do |path|
+ visit path
+end
+
+Given /^(?:|I )am on the path "(.+)"$/ do |page_name|
+ visit path_to(page_name)
+end
+
+Then /^The title should be "(.+)"$/ do |title|
+ page.should have_xpath('//head/title', :text => title)
+end
+
+Then /^I should see the image "(.+)"$/ do |image_path|
+ page.should have_xpath(".//img[@src='#{image_path}']")
+end
+
+Then /^I should see a link to "(.+)" with text "(.+)"$/ do |url, text|
+ page.should have_xpath(".//a[@href='#{url}']", :text => text)
+end
+
+Then /^the response should match the content of the file "(.+)"$/ do |filename|
+ full_path = File.join(Rails.root, "features", "bindata", filename)
+ expected = open(full_path, "rb") {|io| io.read }
+ source.should == expected
+end
+
+Then /^the browser should be told to display the response as a JPEG image$/ do
+ page.response_headers["Content-Type"].should == "image/jpeg"
+ page.response_headers["Content-Disposition"].should == "inline"
+end
+
+Then /^the browser should be told to save the response as a file$/ do
+ page.response_headers["Content-Type"].should == "application/octet-stream"
+ page.response_headers["Content-Disposition"].should == "attachment"
+end
+
+Then /^I should see the following list of links with css id "(.+)":$/ do |id, table|
+ table.diff!(tableish("ul##{id} li a", lambda{|el| [el, el.attribute('href')]}))
+end
+
34 features/step_definitions/page_steps.rb
@@ -0,0 +1,34 @@
+Given /the following pages:$/ do |pages|
+ # convert comma-separated categories into arrays
+ pages.hashes.map{|p| p[:categories] = p.delete('categories').split(/,\s*/) if p['categories'] }
+
+ # convert the body part into blobs['main.md']
+ pages.hashes.map{|p| p[:blobs] = {'main.md' => p.delete('body')} if p['body'] }
+
+ Page.create!(pages.hashes)
+ Then %{there should be #{pages.hashes.length} pages}
+end
+
+And /the page with id "(.+)" has part "(.+)" with content "(.+)"$/ do | id, part_name, content |
+ p = Page.find(id)
+ p.blobs[part_name] = content
+ p.save!
+end
+
+And /the page with id "(.+)" has part "(.+)" with content:$/ do | id, part_name, content |
+ p = Page.find(id)
+ p.blobs[part_name] = content
+ p.save!
+end
+
+And /the page with id "(.+)" has part "(.+)" with content from file "(.+)"$/ do | id, part_name, filename |
+ full_path = File.join(Rails.root, "features", "bindata", filename)
+ p = Page.find(id)
+ p.blobs[part_name] = open(full_path, "rb") {|io| io.read }
+ p.save!
+end
+
+Then /^there should be (.+) pages$/ do |n|
+ Page.find_all.count.should == n.to_i
+end
+
219 features/step_definitions/web_steps.rb
@@ -0,0 +1,219 @@
+# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
+# It is recommended to regenerate this file in the future when you upgrade to a
+# newer version of cucumber-rails. Consider adding your own code to a new file
+# instead of editing this one. Cucumber will automatically load all features/**/*.rb
+# files.
+
+
+require 'uri'
+require 'cgi'
+require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "paths"))
+
+module WithinHelpers
+ def with_scope(locator)
+ locator ? within(locator) { yield } : yield
+ end
+end
+World(WithinHelpers)
+
+Given /^(?:|I )am on ([^"]+)$/ do |page_name|
+ visit path_to(page_name)
+end
+
+When /^(?:|I )go to ([^"]+)$/ do |page_name|
+ visit path_to(page_name)
+end
+
+When /^(?:|I )press "([^"]*)"(?: within "([^"]*)")?$/ do |button, selector|
+ with_scope(selector) do
+ click_button(button)
+ end
+end
+
+When /^(?:|I )follow "([^"]*)"(?: within "([^"]*)")?$/ do |link, selector|
+ with_scope(selector) do
+ click_link(link)
+ end
+end
+
+When /^(?:|I )fill in "([^"]*)" with "([^"]*)"(?: within "([^"]*)")?$/ do |field, value, selector|
+ with_scope(selector) do
+ fill_in(field, :with => value)
+ end
+end
+
+When /^(?:|I )fill in "([^"]*)" for "([^"]*)"(?: within "([^"]*)")?$/ do |value, field, selector|
+ with_scope(selector) do
+ fill_in(field, :with => value)
+ end
+end
+
+# Use this to fill in an entire form with data from a table. Example:
+#
+# When I fill in the following:
+# | Account Number | 5002 |
+# | Expiry date | 2009-11-01 |
+# | Note | Nice guy |
+# | Wants Email? | |
+#
+# TODO: Add support for checkbox, select og option
+# based on naming conventions.
+#
+When /^(?:|I )fill in the following(?: within "([^"]*)")?:$/ do |selector, fields|
+ with_scope(selector) do
+ fields.rows_hash.each do |name, value|
+ When %{I fill in "#{name}" with "#{value}"}
+ end
+ end
+end
+
+When /^(?:|I )select "([^"]*)" from "([^"]*)"(?: within "([^"]*)")?$/ do |value, field, selector|
+ with_scope(selector) do
+ select(value, :from => field)
+ end
+end
+
+When /^(?:|I )check "([^"]*)"(?: within "([^"]*)")?$/ do |field, selector|
+ with_scope(selector) do
+ check(field)
+ end
+end
+
+When /^(?:|I )uncheck "([^"]*)"(?: within "([^"]*)")?$/ do |field, selector|
+ with_scope(selector) do
+ uncheck(field)
+ end
+end
+
+When /^(?:|I )choose "([^"]*)"(?: within "([^"]*)")?$/ do |field, selector|
+ with_scope(selector) do
+ choose(field)
+ end
+end
+
+When /^(?:|I )attach the file "([^"]*)" to "([^"]*)"(?: within "([^"]*)")?$/ do |path, field, selector|
+ with_scope(selector) do
+ attach_file(field, path)
+ end
+end
+
+Then /^(?:|I )should see JSON:$/ do |expected_json|
+ require 'json'
+ expected = JSON.pretty_generate(JSON.parse(expected_json))
+ actual = JSON.pretty_generate(JSON.parse(response.body))
+ expected.should == actual
+end
+
+Then /^(?:|I )should see "([^"]*)"(?: within "([^"]*)")?$/ do |text, selector|
+ with_scope(selector) do
+ if page.respond_to? :should
+ page.should have_content(text)
+ else
+ assert page.has_content?(text)
+ end
+ end
+end
+
+Then /^(?:|I )should see \/([^\/]*)\/(?: within "([^"]*)")?$/ do |regexp, selector|
+ regexp = Regexp.new(regexp)
+ with_scope(selector) do
+ if page.respond_to? :should
+ page.should have_xpath('//*', :text => regexp)
+ else
+ assert page.has_xpath?('//*', :text => regexp)
+ end
+ end
+end
+
+Then /^(?:|I )should not see "([^"]*)"(?: within "([^"]*)")?$/ do |text, selector|
+ with_scope(selector) do
+ if page.respond_to? :should
+ page.should have_no_content(text)
+ else
+ assert page.has_no_content?(text)
+ end
+ end
+end
+
+Then /^(?:|I )should not see \/([^\/]*)\/(?: within "([^"]*)")?$/ do |regexp, selector|
+ regexp = Regexp.new(regexp)
+ with_scope(selector) do
+ if page.respond_to? :should
+ page.should have_no_xpath('//*', :text => regexp)
+ else
+ assert page.has_no_xpath?('//*', :text => regexp)
+ end
+ end
+end
+
+Then /^the "([^"]*)" field(?: within "([^"]*)")? should contain "([^"]*)"$/ do |field, selector, value|
+ with_scope(selector) do
+ field = find_field(field)
+ field_value = (field.tag_name == 'textarea') ? field.text : field.value
+ if field_value.respond_to? :should
+ field_value.should =~ /#{value}/
+ else
+ assert_match(/#{value}/, field_value)
+ end
+ end
+end
+
+Then /^the "([^"]*)" field(?: within "([^"]*)")? should not contain "([^"]*)"$/ do |field, selector, value|
+ with_scope(selector) do
+ field = find_field(field)
+ field_value = (field.tag_name == 'textarea') ? field.text : field.value
+ if field_value.respond_to? :should_not
+ field_value.should_not =~ /#{value}/
+ else
+ assert_no_match(/#{value}/, field_value)
+ end
+ end
+end
+
+Then /^the "([^"]*)" checkbox(?: within "([^"]*)")? should be checked$/ do |label, selector|
+ with_scope(selector) do
+ field_checked = find_field(label)['checked']
+ if field_checked.respond_to? :should
+ field_checked.should be_true
+ else
+ assert field_checked
+ end
+ end
+end
+
+Then /^the "([^"]*)" checkbox(?: within "([^"]*)")? should not be checked$/ do |label, selector|
+ with_scope(selector) do
+ field_checked = find_field(label)['checked']
+ if field_checked.respond_to? :should
+ field_checked.should be_false
+ else
+ assert !field_checked
+ end
+ end
+end
+
+Then /^(?:|I )should be on (.+)$/ do |page_name|
+ current_path = URI.parse(current_url).path
+ if current_path.respond_to? :should
+ current_path.should == path_to(page_name)
+ else
+ assert_equal path_to(page_name), current_path
+ end
+end
+
+Then /^(?:|I )should have the following query string:$/ do |expected_pairs|
+ query = URI.parse(current_url).query
+ actual_params = query ? CGI.parse(query) : {}
+ expected_params = {}
+ expected_pairs.rows_hash.each_pair{|k,v| expected_params[k] = v.split(',')}
+
+ if actual_params.respond_to? :should
+ actual_params.should == expected_params
+ else
+ assert_equal expected_params, actual_params
+ end
+end
+
+Then /^show me the page$/ do
+ save_and_open_page
+end
59 features/support/env.rb
@@ -0,0 +1,59 @@
+# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
+# It is recommended to regenerate this file in the future when you upgrade to a
+# newer version of cucumber-rails. Consider adding your own code to a new file
+# instead of editing this one. Cucumber will automatically load all features/**/*.rb
+# files.
+
+ENV["RAILS_ENV"] ||= "test"
+require File.expand_path(File.dirname(__FILE__) + '/../../config/environment')
+
+require 'cucumber/formatter/unicode' # Remove this line if you don't want Cucumber Unicode support
+require 'cucumber/rails/rspec'
+require 'cucumber/rails/world'
+require 'cucumber/rails/active_record'
+require 'cucumber/web/tableish'
+
+require 'capybara/rails'
+require 'capybara/cucumber'
+require 'capybara/session'
+require 'cucumber/rails/capybara_javascript_emulation' # Lets you click links with onclick javascript handlers without using @culerity or @javascript
+# Capybara defaults to XPath selectors rather than Webrat's default of CSS3. In
+# order to ease the transition to Capybara we set the default here. If you'd
+# prefer to use XPath just remove this line and adjust any selectors in your
+# steps to use the XPath syntax.
+Capybara.default_selector = :css
+
+# If you set this to false, any error raised from within your app will bubble
+# up to your step definition and out to cucumber unless you catch it somewhere
+# on the way. You can make Rails rescue errors and render error pages on a
+# per-scenario basis by tagging a scenario or feature with the @allow-rescue tag.
+#
+# If you set this to true, Rails will rescue all errors and render error
+# pages, more or less in the same way your application would behave in the
+# default production environment. It's not recommended to do this for all
+# of your scenarios, as this makes it hard to discover errors in your application.
+ActionController::Base.allow_rescue = false
+
+# If you set this to true, each scenario will run in a database transaction.
+# You can still turn off transactions on a per-scenario basis, simply tagging
+# a feature or scenario with the @no-txn tag. If you are using Capybara,
+# tagging with @culerity or @javascript will also turn transactions off.
+#
+# If you set this to false, transactions will be off for all scenarios,
+# regardless of whether you use @no-txn or not.
+#
+# Beware that turning transactions off will leave data in your database
+# after each scenario, which can lead to hard-to-debug failures in
+# subsequent scenarios. If you do this, we recommend you create a Before
+# block that will explicitly put your database in a known state.
+#Cucumber::Rails::World.use_transactional_fixtures = true
+# How to clean your database when transactions are turned off. See
+# http://github.com/bmabey/database_cleaner for more info.
+if defined?(ActiveRecord::Base)
+ begin
+ require 'database_cleaner'
+ DatabaseCleaner.strategy = :truncation
+ rescue LoadError => ignore_if_database_cleaner_not_present
+ end
+end
+
33 features/support/paths.rb
@@ -0,0 +1,33 @@
+module NavigationHelpers
+ # Maps a name to a path. Used by the
+ #
+ # When /^I go to (.+)$/ do |page_name|
+ #
+ # step definition in web_steps.rb
+ #
+ def path_to(page_name)
+ case page_name
+
+ when /the home\s?page/
+ '/'
+
+ # Add more mappings here.
+ # Here is an example that pulls values out of the Regexp:
+ #
+ # when /^(.*)'s profile page$/i
+ # user_profile_path(User.find_by_login($1))
+
+ else
+ begin
+ page_name =~ /the (.*) page/
+ path_components = $1.split(/\s+/)
+ self.send(path_components.push('path').join('_').to_sym)
+ rescue Object => e
+ raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
+ "Now, go and add a mapping in #{__FILE__}"
+ end
+ end
+ end
+end
+
+World(NavigationHelpers)
3 features/support/setup.rb
@@ -0,0 +1,3 @@
+Before do
+ GitModel.recreate_db!
+end
0 lib/tasks/.gitkeep
No changes.
9 lib/tasks/balisong.rake
@@ -0,0 +1,9 @@
+namespace :balisong do
+ namespace :db do
+ desc "Create content database (location is configurable in config/environments/*)"
+ task :create => :environment do
+ puts "Creating content database in #{GitModel.db_root}..."
+ GitModel.create_db!
+ end
+ end
+end
53 lib/tasks/cucumber.rake
@@ -0,0 +1,53 @@
+# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
+# It is recommended to regenerate this file in the future when you upgrade to a
+# newer version of cucumber-rails. Consider adding your own code to a new file
+# instead of editing this one. Cucumber will automatically load all features/**/*.rb
+# files.
+
+
+unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks
+
+vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first
+$LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil?
+
+begin
+ require 'cucumber/rake/task'
+
+ namespace :cucumber do
+ Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t|
+ t.binary = vendored_cucumber_bin # If nil, the gem's binary is used.
+ t.fork = true # You may get faster startup if you set this to false
+ t.profile = 'default'
+ end
+
+ Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t|
+ t.binary = vendored_cucumber_bin
+ t.fork = true # You may get faster startup if you set this to false
+ t.profile = 'wip'
+ end
+
+ Cucumber::Rake::Task.new({:rerun => 'db:test:prepare'}, 'Record failing features and run only them if any exist') do |t|
+ t.binary = vendored_cucumber_bin
+ t.fork = true # You may get faster startup if you set this to false
+ t.profile = 'rerun'
+ end
+
+ desc 'Run all features'
+ task :all => [:ok, :wip]
+ end
+ desc 'Alias for cucumber:ok'
+ task :cucumber => 'cucumber:ok'
+
+ task :default => :cucumber
+
+ task :features => :cucumber do
+ STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***"
+ end
+rescue LoadError
+ desc 'cucumber rake task not available (cucumber not installed)'
+ task :cucumber do
+ abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin'
+ end
+end
+
+end
26 public/404.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>The page you were looking for doesn't exist (404)</title>
+ <style type="text/css">
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
+ div.dialog {
+ width: 25em;
+ padding: 0 4em;
+ margin: 4em auto 0 auto;
+ border: 1px solid #ccc;
+ border-right-color: #999;
+ border-bottom-color: #999;
+ }
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
+ </style>
+</head>
+
+<body>
+ <!-- This file lives in public/404.html -->
+ <div class="dialog">
+ <h1>The page you were looking for doesn't exist.</h1>
+ <p>You may have mistyped the address or the page may have moved.</p>
+ </div>
+</body>
+</html>
26 public/422.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>The change you wanted was rejected (422)</title>
+ <style type="text/css">
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
+ div.dialog {
+ width: 25em;
+ padding: 0 4em;
+ margin: 4em auto 0 auto;
+ border: 1px solid #ccc;
+ border-right-color: #999;
+ border-bottom-color: #999;
+ }
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
+ </style>
+</head>
+
+<body>
+ <!-- This file lives in public/422.html -->
+ <div class="dialog">
+ <h1>The change you wanted was rejected.</h1>
+ <p>Maybe you tried to change something you didn't have access to.</p>
+ </div>
+</body>
+</html>
26 public/500.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>We're sorry, but something went wrong (500)</title>
+ <style type="text/css">
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
+ div.dialog {
+ width: 25em;
+ padding: 0 4em;
+ margin: 4em auto 0 auto;
+ border: 1px solid #ccc;
+ border-right-color: #999;
+ border-bottom-color: #999;
+ }
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
+ </style>
+</head>
+
+<body>
+ <!-- This file lives in public/500.html -->
+ <div class="dialog">
+ <h1>We're sorry, but something went wrong.</h1>
+ <p>We've been notified about this issue and we'll take a look at it shortly.</p>
+ </div>
+</body>
+</html>
0 public/favicon.ico
No changes.
BIN public/images/rails.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 public/javascripts/application.js
@@ -0,0 +1,2 @@
+// Place your application-specific JavaScript functions and classes here
+// This file is automatically included by javascript_include_tag :defaults
963 public/javascripts/controls.js
@@ -0,0 +1,963 @@
+// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// (c) 2005-2008 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
+// (c) 2005-2008 Jon Tirsen (http://www.tirsen.com)
+// Contributors:
+// Richard Livsey
+// Rahul Bhargava
+// Rob Wills
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+// Autocompleter.Base handles all the autocompletion functionality
+// that's independent of the data source for autocompletion. This
+// includes drawing the autocompletion menu, observing keyboard
+// and mouse events, and similar.
+//
+// Specific autocompleters need to provide, at the very least,
+// a getUpdatedChoices function that will be invoked every time
+// the text inside the monitored textbox changes. This method
+// should get the text for which to provide autocompletion by
+// invoking this.getToken(), NOT by directly accessing
+// this.element.value. This is to allow incremental tokenized
+// autocompletion. Specific auto-completion logic (AJAX, etc)
+// belongs in getUpdatedChoices.
+//
+// Tokenized incremental autocompletion is enabled automatically
+// when an autocompleter is instantiated with the 'tokens' option
+// in the options parameter, e.g.:
+// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
+// will incrementally autocomplete with a comma as the token.
+// Additionally, ',' in the above example can be replaced with
+// a token array, e.g. { tokens: [',', '\n'] } which
+// enables autocompletion on multiple tokens. This is most
+// useful when one of the tokens is \n (a newline), as it
+// allows smart autocompletion after linebreaks.
+
+if(typeof Effect == 'undefined')
+ throw("controls.js requires including script.aculo.us' effects.js library");
+
+var Autocompleter = { };
+Autocompleter.Base = Class.create({
+ baseInitialize: function(element, update, options) {
+ element = $(element);
+ this.element = element;
+ this.update = $(update);
+ this.hasFocus = false;
+ this.changed = false;
+ this.active = false;
+ this.index = 0;
+ this.entryCount = 0;
+ this.oldElementValue = this.element.value;
+
+ if(this.setOptions)
+ this.setOptions(options);
+ else
+ this.options = options || { };
+
+ this.options.paramName = this.options.paramName || this.element.name;
+ this.options.tokens = this.options.tokens || [];
+ this.options.frequency = this.options.frequency || 0.4;
+ this.options.minChars = this.options.minChars || 1;
+ this.options.onShow = this.options.onShow ||
+ function(element, update){
+ if(!update.style.position || update.style.position=='absolute') {
+ update.style.position = 'absolute';
+ Position.clone(element, update, {
+ setHeight: false,
+ offsetTop: element.offsetHeight
+ });
+ }
+ Effect.Appear(update,{duration:0.15});
+ };
+ this.options.onHide = this.options.onHide ||
+ function(element, update){ new Effect.Fade(update,{duration:0.15}) };
+
+ if(typeof(this.options.tokens) == 'string')
+ this.options.tokens = new Array(this.options.tokens);
+ // Force carriage returns as token delimiters anyway
+ if (!this.options.tokens.include('\n'))
+ this.options.tokens.push('\n');
+
+ this.observer = null;
+
+ this.element.setAttribute('autocomplete','off');
+
+ Element.hide(this.update);
+
+ Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
+ Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
+ },
+
+ show: function() {
+ if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
+ if(!this.iefix &&
+ (Prototype.Browser.IE) &&
+ (Element.getStyle(this.update, 'position')=='absolute')) {
+ new Insertion.After(this.update,
+ '<iframe id="' + this.update.id + '_iefix" '+
+ 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
+ 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
+ this.iefix = $(this.update.id+'_iefix');
+ }
+ if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
+ },
+
+ fixIEOverlapping: function() {