├── spec
├── dummy
│ ├── log
│ │ └── .gitkeep
│ ├── app
│ │ ├── mailers
│ │ │ └── .gitkeep
│ │ ├── models
│ │ │ ├── .gitkeep
│ │ │ └── user.rb
│ │ ├── helpers
│ │ │ └── application_helper.rb
│ │ ├── assets
│ │ │ ├── javascripts
│ │ │ │ ├── views
│ │ │ │ │ └── users
│ │ │ │ │ │ ├── dummy.jst.jade
│ │ │ │ │ │ └── index.jade
│ │ │ │ ├── mixins
│ │ │ │ │ ├── users_mixins.jade
│ │ │ │ │ └── application_mixins.jade
│ │ │ │ ├── sample.jst.jade
│ │ │ │ ├── includes
│ │ │ │ │ └── util.js
│ │ │ │ └── application.js
│ │ │ └── stylesheets
│ │ │ │ └── application.css
│ │ ├── views
│ │ │ ├── users
│ │ │ │ └── show.jade
│ │ │ └── layouts
│ │ │ │ └── application.html.erb
│ │ └── controllers
│ │ │ ├── application_controller.rb
│ │ │ └── users_controller.rb
│ ├── lib
│ │ └── assets
│ │ │ └── .gitkeep
│ ├── public
│ │ ├── favicon.ico
│ │ ├── 500.html
│ │ ├── 422.html
│ │ └── 404.html
│ ├── config
│ │ ├── database.yml
│ │ ├── environment.rb
│ │ ├── locales
│ │ │ └── en.yml
│ │ ├── initializers
│ │ │ ├── mime_types.rb
│ │ │ ├── jader.rb
│ │ │ ├── wrap_parameters.rb
│ │ │ ├── backtrace_silencers.rb
│ │ │ ├── session_store.rb
│ │ │ ├── secret_token.rb
│ │ │ └── inflections.rb
│ │ ├── boot.rb
│ │ ├── environments
│ │ │ ├── development.rb
│ │ │ ├── test.rb
│ │ │ └── production.rb
│ │ ├── routes.rb
│ │ └── application.rb
│ ├── config.ru
│ ├── db
│ │ ├── migrate
│ │ │ └── 20120816043248_user.rb
│ │ └── schema.rb
│ ├── Rakefile
│ ├── script
│ │ └── rails
│ └── README.rdoc
├── support
│ ├── global.rb
│ └── matchers.rb
├── source_spec.rb
├── compiler_spec.rb
├── spec_helper.rb
├── template_spec.rb
└── integration
│ └── renderer_spec.rb
├── lib
├── jader
│ ├── version.rb
│ ├── source.rb
│ ├── template.rb
│ ├── engine.rb
│ ├── configuration.rb
│ ├── renderer.rb
│ ├── serialize.rb
│ └── compiler.rb
└── jader.rb
├── Rakefile
├── .gitignore
├── Gemfile
├── LICENSE
├── jader.gemspec
├── vendor
└── assets
│ └── javascripts
│ └── jade
│ ├── runtime.js
│ └── jade.js
└── README.md
/spec/dummy/log/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/app/mailers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/lib/assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/public/favicon.ico:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/jader/version.rb:
--------------------------------------------------------------------------------
1 | module Jader
2 | VERSION = "0.0.10"
3 | end
4 |
--------------------------------------------------------------------------------
/spec/dummy/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/javascripts/views/users/dummy.jst.jade:
--------------------------------------------------------------------------------
1 | h1
2 | mixin upcase(phrase)
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'bundler/gem_tasks'
2 | require 'rspec/core/rake_task'
3 |
4 | RSpec::Core::RakeTask.new('spec')
--------------------------------------------------------------------------------
/spec/dummy/app/assets/javascripts/mixins/users_mixins.jade:
--------------------------------------------------------------------------------
1 | mixin upcase(str)
2 | - cap = str.toUpperCase()
3 | strong= cap
--------------------------------------------------------------------------------
/spec/dummy/app/assets/javascripts/sample.jst.jade:
--------------------------------------------------------------------------------
1 | !!!5
2 | head
3 | title Hello, #{name} :)
4 | body
5 | | Yap, it works
6 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/javascripts/mixins/application_mixins.jade:
--------------------------------------------------------------------------------
1 | mixin downcase(str)
2 | - low = str.toLowerCase()
3 | strong= low
--------------------------------------------------------------------------------
/spec/support/global.rb:
--------------------------------------------------------------------------------
1 | def assets
2 | Rails.application.assets
3 | end
4 |
5 | def asset_for(name)
6 | assets[name]
7 | end
8 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/user.rb:
--------------------------------------------------------------------------------
1 | class User < ActiveRecord::Base
2 | include Jader::Serialize
3 | jade_serializable :name, :email
4 | end
--------------------------------------------------------------------------------
/spec/dummy/app/views/users/show.jade:
--------------------------------------------------------------------------------
1 | h1 My name is #{user.name}
2 | h2
3 | mixin upcase(user.email)
4 | p
5 | mixin downcase(user.name)
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | .bundle
3 | Gemfile.lock
4 | pkg/*
5 | *.log
6 | .rvmrc
7 | *.sqlite
8 | spec/dummy/tmp/
9 | doc/
10 | .yardoc
11 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | protect_from_forgery
3 | end
4 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 |
3 | # Specify your gem's dependencies in jade.gemspec
4 | gemspec
5 |
6 | group :test do
7 | gem 'sqlite3'
8 | end
9 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/javascripts/views/users/index.jade:
--------------------------------------------------------------------------------
1 | h1#topHeading Hello All Users
2 | h2.secondHeading!= Util.ISODate()
3 | - each user in users
4 | p= user.name
--------------------------------------------------------------------------------
/spec/dummy/config/database.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: sqlite3
3 | database: db/development.sqlite
4 |
5 | test:
6 | adapter: sqlite3
7 | database: db/test.sqlite
--------------------------------------------------------------------------------
/spec/dummy/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require ::File.expand_path('../config/environment', __FILE__)
4 | run Dummy::Application
5 |
--------------------------------------------------------------------------------
/spec/dummy/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the rails application
2 | require File.expand_path('../application', __FILE__)
3 |
4 | # Initialize the rails application
5 | Dummy::Application.initialize!
6 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/users_controller.rb:
--------------------------------------------------------------------------------
1 | class UsersController < ApplicationController
2 |
3 | def index
4 | @users = User.where('name != "Jim"')
5 | end
6 |
7 | def show
8 | @user = User.find params[:id]
9 | end
10 | end
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20120816043248_user.rb:
--------------------------------------------------------------------------------
1 | class User < ActiveRecord::Migration
2 | def up
3 | create_table :users do |t|
4 | t.string :name
5 | t.string :email
6 | end
7 | end
8 |
9 | def down
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/spec/dummy/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Sample localization file for English. Add more files in this directory for other locales.
2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
3 |
4 | en:
5 | hello: "Hello world"
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 | # Mime::Type.register_alias "text/html", :iphone
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/boot.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | gemfile = File.expand_path('../../../../Gemfile', __FILE__)
3 |
4 | if File.exist?(gemfile)
5 | ENV['BUNDLE_GEMFILE'] = gemfile
6 | require 'bundler'
7 | Bundler.setup
8 | end
9 |
10 | $:.unshift File.expand_path('../../../../lib', __FILE__)
--------------------------------------------------------------------------------
/spec/dummy/Rakefile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env rake
2 | # Add your own tasks in files placed in lib/tasks ending in .rake,
3 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
4 |
5 | require File.expand_path('../config/application', __FILE__)
6 |
7 | Dummy::Application.load_tasks
8 |
--------------------------------------------------------------------------------
/spec/dummy/script/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3 |
4 | APP_PATH = File.expand_path('../../config/application', __FILE__)
5 | require File.expand_path('../../config/boot', __FILE__)
6 | require 'rails/commands'
7 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Dummy
5 | <%= stylesheet_link_tag "application", :media => "all" %>
6 | <%= javascript_include_tag "application" %>
7 | <%= csrf_meta_tags %>
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/jader.rb:
--------------------------------------------------------------------------------
1 | Jader.configure do |config|
2 | config.mixins_path = Rails.root.join('app','assets','javascripts','mixins')
3 | config.views_path = Rails.root.join('app','assets','javascripts','views')
4 | config.includes << IO.read(Rails.root.join('app','assets','javascripts','includes','util.js'))
5 | config.prepend_view_path = true
6 | end
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 | #
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json]
9 | end
10 |
11 |
--------------------------------------------------------------------------------
/lib/jader.rb:
--------------------------------------------------------------------------------
1 | require 'jader/source'
2 | require 'jader/compiler'
3 | require 'jader/template'
4 | require 'jader/renderer'
5 | require 'jader/configuration'
6 | require 'jader/serialize'
7 | require 'jader/engine' if defined?(::Rails)
8 |
9 | ActionView::Template.register_template_handler :jade, Jader::Renderer
10 | ActionView::Template.register_template_handler 'jst.jade', Jader::Renderer
11 |
12 | Jader.configure do |config|; end
--------------------------------------------------------------------------------
/spec/dummy/app/assets/javascripts/includes/util.js:
--------------------------------------------------------------------------------
1 | // This is a mock Util object
2 | // Used for testing server-side inclusion of Javascripts
3 | ;(function(global){
4 |
5 | var Util = {
6 | ISODate: function(){
7 | var d = new Date()
8 | return d.toISOString()
9 | }
10 | };
11 | // attach to global object to make it available inside jade templates with node.js
12 | global.Util = Util;
13 |
14 | })(this);
--------------------------------------------------------------------------------
/spec/support/matchers.rb:
--------------------------------------------------------------------------------
1 | RSpec::Matchers.define :serve do |asset_name|
2 | match do |sprockets|
3 | !!sprockets[asset_name]
4 | end
5 |
6 | failure_message do |sprockets|
7 | "expected #{asset_name} to be served, but it wasn't"
8 | end
9 |
10 | failure_message_when_negated do |sprockets|
11 | "expected #{asset_name} NOT to be served, but it was"
12 | end
13 |
14 | description do
15 | "serve #{asset_name}"
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Dummy::Application.config.session_store :cookie_store, key: '_dummy_session'
4 |
5 | # Use the database for sessions instead of the cookie-based default,
6 | # which shouldn't be used to store highly confidential information
7 | # (create the session table with "rails generate session_migration")
8 | # Dummy::Application.config.session_store :active_record_store
9 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/secret_token.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 | # Make sure the secret is at least 30 characters and all random,
6 | # no regular words or you'll be exposed to dictionary attacks.
7 | Dummy::Application.config.secret_token = '968b4807e6e4a7e64e3dfaf80293c76ac102f443bd0d80c70499451098c256b22e00d843c3ac74e19b2d6bab7d3c438191433bc250091c7644a563c36038ae94'
8 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/javascripts/application.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into including all the files listed below.
2 | // Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
3 | // be included in the compiled file accessible from http://example.com/assets/application.js
4 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
5 | // the compiled file.
6 | //
7 | //= require 'jade/runtime'
8 | //= require ./sample
9 | //= require views/users/dummy
10 |
--------------------------------------------------------------------------------
/spec/source_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Jader::Source do
4 | it "should contain jade asset" do
5 | File.exist?(Jader::Source::jade_path).should == true
6 | end
7 |
8 | it "should contain runtime asset" do
9 | File.exist?(Jader::Source::runtime_path).should == true
10 | end
11 |
12 | it "should be able to read jade source" do
13 | IO.read(Jader::Source::jade_path).should_not be_empty
14 | end
15 |
16 | it "should be able to read runtime source" do
17 | IO.read(Jader::Source::runtime_path).should_not be_empty
18 | end
19 | end
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format
4 | # (all these examples are active by default):
5 | # ActiveSupport::Inflector.inflections do |inflect|
6 | # inflect.plural /^(ox)$/i, '\1en'
7 | # inflect.singular /^(ox)en/i, '\1'
8 | # inflect.irregular 'person', 'people'
9 | # inflect.uncountable %w( fish sheep )
10 | # end
11 | #
12 | # These inflection rules are supported but not enabled by default:
13 | # ActiveSupport::Inflector.inflections do |inflect|
14 | # inflect.acronym 'RESTful'
15 | # end
16 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the top of the
9 | * compiled file, but it's generally better to create a new file per style scope.
10 | *
11 | *= require_self
12 | *= require_tree .
13 | */
14 |
--------------------------------------------------------------------------------
/lib/jader/source.rb:
--------------------------------------------------------------------------------
1 | module Jader
2 | # Jade template engine Javascript source code
3 | module Source
4 |
5 | # Jade source code
6 | def self.jade
7 | IO.read jade_path
8 | end
9 |
10 | # Jade runtime source code
11 | def self.runtime
12 | IO.read runtime_path
13 | end
14 |
15 | # Jade source code path
16 | def self.jade_path
17 | File.expand_path("../../../vendor/assets/javascripts/jade/jade.js", __FILE__)
18 | end
19 |
20 | # Jade runtime source code path
21 | def self.runtime_path
22 | File.expand_path("../../../vendor/assets/javascripts/jade/runtime.js", __FILE__)
23 | end
24 | end
25 | end
--------------------------------------------------------------------------------
/spec/dummy/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
We're sorry, but something went wrong.
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/spec/compiler_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Jader::Compiler do
4 | before :each do
5 | @compiler = Jader::Compiler.new
6 | end
7 |
8 | it "should respect default options" do
9 | @compiler.options.should == {
10 | :client => true,
11 | :compileDebug => false
12 | }
13 | end
14 |
15 | it "should define Jade.JS compiler version" do
16 | @compiler.jade_version.should == "0.27.6"
17 | end
18 |
19 | it "should compile small thing" do
20 | @compiler.compile('test').should include ""
21 | end
22 |
23 | it "should depend on options" do
24 | @compiler.compile('test').should_not include "lineno: 1"
25 | @compiler.options[:compileDebug] = true
26 | @compiler.compile('test').should include "lineno: 1"
27 | end
28 | end
--------------------------------------------------------------------------------
/spec/dummy/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
The change you wanted was rejected.
23 |
Maybe you tried to change something you didn't have access to.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | ENV["RAILS_ENV"] = "test"
2 |
3 | require 'rubygems'
4 | require 'bundler/setup'
5 | require 'pry'
6 |
7 | require File.expand_path("../dummy/config/environment.rb", __FILE__)
8 | require "rails/test_help"
9 | require "rspec/rails"
10 | require 'rspec/active_model/mocks'
11 |
12 | require 'jader'
13 |
14 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
15 |
16 | Rails.backtrace_cleaner.remove_silencers!
17 |
18 | RSpec.configure do |config|
19 | require 'rspec/mocks'
20 | require 'rspec/expectations'
21 | config.include RSpec::Matchers
22 |
23 | # Enable old-style rspec API
24 | config.mock_with :rspec do |c|
25 | c.syntax = :should
26 | end
27 | config.expect_with :rspec do |c|
28 | c.syntax = :should
29 | end
30 | config.infer_spec_type_from_file_location!
31 | end
--------------------------------------------------------------------------------
/spec/dummy/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
The page you were looking for doesn't exist.
23 |
You may have mistyped the address or the page may have moved.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/lib/jader/template.rb:
--------------------------------------------------------------------------------
1 | require 'tilt/template'
2 |
3 | module Jader
4 | # Jader Tilt template for use with JST
5 | class Template < Tilt::Template
6 | self.default_mime_type = 'application/javascript'
7 |
8 | def initializer
9 | super
10 | end
11 |
12 | # Ensure ExecJS is available when engine is initialized
13 | def self.engine_initialized?
14 | defined? ::ExecJS
15 | end
16 |
17 | # Require 'execjs' when initializing engine
18 | def initialize_engine
19 | require_template_library 'execjs'
20 | end
21 |
22 | def prepare
23 | end
24 |
25 | # Evaluate the template. Compiles the template for JST
26 | # @return [String] JST-compliant compiled version of the Jade template being rendered
27 | def evaluate(scope, locals, &block)
28 | Jader::Compiler.new(:filename => file).compile(data, file)
29 | end
30 |
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/lib/jader/engine.rb:
--------------------------------------------------------------------------------
1 | require 'sprockets'
2 | require 'sprockets/engines'
3 |
4 | module Jader
5 | class Engine < Rails::Engine
6 | initializer "jade.configure_rails_initialization", :before => 'sprockets.environment', :group => :all do |app|
7 | sprockets_env = app.assets || Sprockets
8 | sprockets_env.register_engine '.jade', ::Jader::Template
9 | end
10 |
11 | initializer 'jader.prepend_views_path', :after => :add_view_paths do |app|
12 | next if Jader::configuration.nil? or Jader::configuration.views_path.nil?
13 | app.config.paths['app/views'].unshift(Jader::configuration.views_path)
14 | if Jader::configuration.prepend_view_path
15 | ActionController::Base.class_eval do
16 | before_filter do |controller|
17 | prepend_view_path Jader::configuration.views_path
18 | end
19 | end
20 | end
21 | end
22 |
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/spec/dummy/db/schema.rb:
--------------------------------------------------------------------------------
1 | # encoding: UTF-8
2 | # This file is auto-generated from the current state of the database. Instead
3 | # of editing this file, please use the migrations feature of Active Record to
4 | # incrementally modify your database, and then regenerate this schema definition.
5 | #
6 | # Note that this schema.rb definition is the authoritative source for your
7 | # database schema. If you need to create the application database on another
8 | # system, you should be using db:schema:load, not running all the migrations
9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10 | # you'll amass, the slower it'll run and the greater likelihood for issues).
11 | #
12 | # It's strongly recommended to check this file into your version control system.
13 |
14 | ActiveRecord::Schema.define(:version => 20120816043248) do
15 |
16 | create_table "users", :force => true do |t|
17 | t.string "name"
18 | t.string "email"
19 | end
20 |
21 | end
22 |
--------------------------------------------------------------------------------
/lib/jader/configuration.rb:
--------------------------------------------------------------------------------
1 | module Jader
2 | class << self
3 | attr_accessor :configuration
4 | end
5 |
6 | # Configure Jader
7 | # @yield [config] Jader::Configuration instance
8 | # @example
9 | # Jader.configure do |config|
10 | # config.mixins_path = Rails.root.join('app','assets','javascripts','mixins')
11 | # config.views_path = Rails.root.join('app','assets','javascripts','views')
12 | # config.includes << IO.read Rails.root.join('app','assets','javascripts','util.js')
13 | # end
14 | def self.configure
15 | self.configuration ||= Configuration.new
16 | yield(configuration)
17 | end
18 |
19 | # Jader configuration class
20 | class Configuration
21 | attr_accessor :mixins_path, :views_path, :includes, :prepend_view_path
22 |
23 | # Initialize Jader::Configuration class with default values
24 | def initialize
25 | @mixins_path = nil
26 | @views_path = nil
27 | @includes = []
28 | @prepend_view_path = false
29 | end
30 | end
31 | end
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2012 Round Lake, inc.,
4 | Peter Zotov .
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
--------------------------------------------------------------------------------
/spec/dummy/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Log error messages when you accidentally call methods on nil.
10 | config.whiny_nils = true
11 |
12 | # Show full error reports and disable caching
13 | config.consider_all_requests_local = true
14 | config.action_controller.perform_caching = false
15 |
16 | # Don't care if the mailer can't send
17 | config.action_mailer.raise_delivery_errors = false
18 |
19 | # Print deprecation notices to the Rails logger
20 | config.active_support.deprecation = :log
21 |
22 | # Only use best-standards-support built into browsers
23 | config.action_dispatch.best_standards_support = :builtin
24 |
25 |
26 | # Do not compress assets
27 | config.assets.compress = false
28 |
29 | # Expands the lines which load the assets
30 | config.assets.debug = true
31 | end
32 |
--------------------------------------------------------------------------------
/spec/template_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'fileutils'
3 | describe Jader::Compiler do
4 |
5 | before :all do
6 | d = Rails.root.join('tmp','cache','assets')
7 | FileUtils.rm_r d if Dir.exists? d
8 | end
9 |
10 | def template(source, file)
11 | Jader::Template.new(file){source}
12 | end
13 |
14 | it 'should have default mime type' do
15 | Jader::Template.default_mime_type.should == 'application/javascript'
16 | end
17 |
18 | it 'should be served' do
19 | assets.should serve 'sample.js'
20 | asset_for('sample.js').body.should include "Yap, it works"
21 | end
22 |
23 | it 'should work fine with JST' do
24 | context = ExecJS.compile %{
25 | #{asset_for('application.js').to_s}
26 | html = JST['sample']({name: 'Yorik'})
27 | }
28 | context.eval('html').should == "Hello, Yorik :)Yap, it works"
29 | end
30 |
31 | it 'should use mixins in JST' do
32 | phrase = 'Hi There'
33 | context = ExecJS.compile %{
34 | #{asset_for('application.js').to_s}
35 | html = JST['views/users/dummy']({phrase: '#{phrase}'})
36 | }
37 | context.eval('html').should include(phrase.upcase)
38 | end
39 |
40 | end
41 |
--------------------------------------------------------------------------------
/jader.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | $:.push File.expand_path("../lib", __FILE__)
3 | require "jader/version"
4 |
5 | Gem::Specification.new do |s|
6 | s.name = "jader"
7 | s.version = Jader::VERSION
8 | s.authors = ["Zohar Arad", "Boris Staal"]
9 | s.email = ["zohar@zohararad.com" "boris@roundlake.ru"]
10 | s.homepage = "https://github.com/zohararad/jader"
11 | s.summary = %q{JST and Rails views compiler for Jade templates}
12 | s.description = %q{Share your Jade views between client and server, eliminate code duplication and make your single-page app SEO friendly}
13 |
14 | s.rubyforge_project = "jader"
15 |
16 | s.files = `git ls-files`.split("\n")
17 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19 | s.require_paths = ["lib"]
20 |
21 | s.add_dependency 'tilt'
22 | s.add_dependency 'sprockets'
23 | s.add_dependency 'execjs'
24 | s.add_development_dependency 'rspec', '~> 3.1'
25 | s.add_development_dependency 'rspec-rails', '~> 3.1'
26 | s.add_development_dependency 'rspec-activemodel-mocks', '~> 1.0'
27 | s.add_development_dependency 'rails', '~> 3.2'
28 | s.add_development_dependency 'pry'
29 | s.add_development_dependency 'yard'
30 | end
31 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Configure static asset server for tests with Cache-Control for performance
11 | config.serve_static_assets = true
12 | config.static_cache_control = "public, max-age=3600"
13 |
14 | # Log error messages when you accidentally call methods on nil
15 | config.whiny_nils = true
16 |
17 | # Show full error reports and disable caching
18 | config.consider_all_requests_local = true
19 | config.action_controller.perform_caching = false
20 |
21 | # Raise exceptions instead of rendering exception templates
22 | config.action_dispatch.show_exceptions = false
23 |
24 | # Disable request forgery protection in test environment
25 | config.action_controller.allow_forgery_protection = false
26 |
27 | # Tell Action Mailer not to deliver emails to the real world.
28 | # The :test delivery method accumulates sent emails in the
29 | # ActionMailer::Base.deliveries array.
30 | config.action_mailer.delivery_method = :test
31 |
32 |
33 | # Print deprecation notices to the stderr
34 | config.active_support.deprecation = :stderr
35 | end
36 |
--------------------------------------------------------------------------------
/spec/integration/renderer_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Jader::Renderer do
4 |
5 | context 'users/index' do
6 |
7 | before do
8 | User.all.each do |user|
9 | user.destroy
10 | end
11 | @joe = User.create(:name => 'Joe', :email => 'joe@gmail.com')
12 | @mike = User.create(:name => 'Mike', :email => 'mike@gmail.com')
13 | end
14 |
15 | before :each do
16 | get '/users'
17 | end
18 |
19 | it 'render users index page' do
20 | response.should render_template(:index)
21 | end
22 |
23 | it 'renders the template HTML' do
24 | response.body.should include 'Hello All Users
'
25 | end
26 |
27 | it 'renders users' do
28 | response.body.should include @mike.name
29 | response.body.should include @joe.name
30 | end
31 |
32 | it 'uses a function available from server-side includes' do
33 | response.body.should =~ /\d{4}\-\d{2}\-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z/
34 | end
35 | end
36 |
37 | context 'users/show' do
38 |
39 | before :each do
40 | @user = stub_model(User, :name => 'Sam', :email => 'sam@gmail.com')
41 | User.should_receive(:find).and_return(@user)
42 | end
43 |
44 | it 'renders instance variables' do
45 | get user_path(@user)
46 | response.body.should include 'My name is %s' % @user.name
47 | end
48 |
49 | it 'renders HTML generated by application mixin' do
50 | get user_path(@user)
51 | response.body.should include @user.name.downcase
52 | end
53 |
54 | it 'renders HTML generated by users mixin' do
55 | get user_path(@user)
56 | response.body.should include @user.email.upcase
57 | end
58 | end
59 | end
--------------------------------------------------------------------------------
/lib/jader/renderer.rb:
--------------------------------------------------------------------------------
1 | module Jader
2 | # Server side Jade templates renderer
3 | module Renderer
4 |
5 | # Convert Jade template to HTML output for rendering as a Rails view
6 | # @param [String] template_text Jade template text to convert
7 | # @param [String] controller_name name of Rails controller rendering the view
8 | # @param [Hash] vars controller instance variables passed to the template
9 | # @return [String] HTML output of evaluated template
10 | # @see Jader::Compiler#render
11 | def self.convert_template(template_text, controller_name, vars = {})
12 | compiler = Jader::Compiler.new :client => true
13 | compiler.render(template_text, controller_name, vars)
14 | end
15 |
16 | # Prepare controller instance variables for the template and execute template conversion.
17 | # Called as an ActionView::Template registered template
18 | # @param [ActionView::Template] template currently rendered ActionView::Template instance
19 | # @see Jader::Renderer#convert_template
20 | def self.call(template)
21 | template.source.gsub!(/\#\{([^\}]+)\}/,"\\\#{\\1}") # escape Jade's #{somevariable} syntax
22 | %{
23 | template_source = %{#{template.source}}
24 | controller_name = controller.controller_name
25 | variable_names = controller.instance_variable_names
26 | variable_names -= %w[@template]
27 | if controller.respond_to?(:protected_instance_variables)
28 | variable_names -= controller.protected_instance_variables
29 | end
30 |
31 | variables = {}
32 | variable_names.each do |name|
33 | next if name.include? '@_'
34 | variables[name.sub(/^@/, "")] = controller.instance_variable_get(name)
35 | end
36 | Jader::Renderer.convert_template(template_source, controller_name, variables.merge(local_assigns))
37 | }
38 | end
39 |
40 | end
41 | end
--------------------------------------------------------------------------------
/spec/dummy/config/routes.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.routes.draw do
2 |
3 | resources :users, :only => [:index, :show]
4 | # The priority is based upon order of creation:
5 | # first created -> highest priority.
6 |
7 | # Sample of regular route:
8 | # match 'products/:id' => 'catalog#view'
9 | # Keep in mind you can assign values other than :controller and :action
10 |
11 | # Sample of named route:
12 | # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
13 | # This route can be invoked with purchase_url(:id => product.id)
14 |
15 | # Sample resource route (maps HTTP verbs to controller actions automatically):
16 | # resources :products
17 |
18 | # Sample resource route with options:
19 | # resources :products do
20 | # member do
21 | # get 'short'
22 | # post 'toggle'
23 | # end
24 | #
25 | # collection do
26 | # get 'sold'
27 | # end
28 | # end
29 |
30 | # Sample resource route with sub-resources:
31 | # resources :products do
32 | # resources :comments, :sales
33 | # resource :seller
34 | # end
35 |
36 | # Sample resource route with more complex sub-resources
37 | # resources :products do
38 | # resources :comments
39 | # resources :sales do
40 | # get 'recent', :on => :collection
41 | # end
42 | # end
43 |
44 | # Sample resource route within a namespace:
45 | # namespace :admin do
46 | # # Directs /admin/products/* to Admin::ProductsController
47 | # # (app/controllers/admin/products_controller.rb)
48 | # resources :products
49 | # end
50 |
51 | # You can have the root of your site routed with "root"
52 | # just remember to delete public/index.html.
53 | # root :to => 'welcome#index'
54 |
55 | # See how all your routes lay out with "rake routes"
56 |
57 | # This is a legacy wild controller route that's not recommended for RESTful applications.
58 | # Note: This route will make all actions in every controller accessible via GET requests.
59 | # match ':controller(/:action(/:id))(.:format)'
60 | end
61 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb
3 |
4 | # Code is not reloaded between requests
5 | config.cache_classes = true
6 |
7 | # Full error reports are disabled and caching is turned on
8 | config.consider_all_requests_local = false
9 | config.action_controller.perform_caching = true
10 |
11 | # Disable Rails's static asset server (Apache or nginx will already do this)
12 | config.serve_static_assets = false
13 |
14 | # Compress JavaScripts and CSS
15 | config.assets.compress = true
16 |
17 | # Don't fallback to assets pipeline if a precompiled asset is missed
18 | config.assets.compile = false
19 |
20 | # Generate digests for assets URLs
21 | config.assets.digest = true
22 |
23 | # Defaults to nil and saved in location specified by config.assets.prefix
24 | # config.assets.manifest = YOUR_PATH
25 |
26 | # Specifies the header that your server uses for sending files
27 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
28 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
29 |
30 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
31 | # config.force_ssl = true
32 |
33 | # See everything in the log (default is :info)
34 | # config.log_level = :debug
35 |
36 | # Prepend all log lines with the following tags
37 | # config.log_tags = [ :subdomain, :uuid ]
38 |
39 | # Use a different logger for distributed setups
40 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
41 |
42 | # Use a different cache store in production
43 | # config.cache_store = :mem_cache_store
44 |
45 | # Enable serving of images, stylesheets, and JavaScripts from an asset server
46 | # config.action_controller.asset_host = "http://assets.example.com"
47 |
48 | # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
49 | # config.assets.precompile += %w( search.js )
50 |
51 | # Disable delivery errors, bad email addresses will be ignored
52 | # config.action_mailer.raise_delivery_errors = false
53 |
54 | # Enable threaded mode
55 | # config.threadsafe!
56 |
57 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
58 | # the I18n.default_locale when a translation can not be found)
59 | config.i18n.fallbacks = true
60 |
61 | # Send deprecation notices to registered listeners
62 | config.active_support.deprecation = :notify
63 |
64 | end
65 |
--------------------------------------------------------------------------------
/spec/dummy/config/application.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../boot', __FILE__)
2 |
3 | # Pick the frameworks you want:
4 | require "active_record/railtie"
5 | require "action_controller/railtie"
6 | require "action_mailer/railtie"
7 | require "active_resource/railtie"
8 | require "sprockets/railtie"
9 | # require "rails/test_unit/railtie"
10 |
11 | Bundler.require
12 | require "jader"
13 |
14 | module Dummy
15 | class Application < Rails::Application
16 | # Settings in config/environments/* take precedence over those specified here.
17 | # Application configuration should go into files in config/initializers
18 | # -- all .rb files in that directory are automatically loaded.
19 |
20 | # Custom directories with classes and modules you want to be autoloadable.
21 | # config.autoload_paths += %W(#{config.root}/extras)
22 |
23 | # Only load the plugins named here, in the order given (default is alphabetical).
24 | # :all can be used as a placeholder for all plugins not explicitly named.
25 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
26 |
27 | # Activate observers that should always be running.
28 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
29 |
30 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
31 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
32 | # config.time_zone = 'Central Time (US & Canada)'
33 |
34 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
35 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
36 | # config.i18n.default_locale = :de
37 |
38 | # Configure the default encoding used in templates for Ruby 1.9.
39 | config.encoding = "utf-8"
40 |
41 | # Configure sensitive parameters which will be filtered from the log file.
42 | config.filter_parameters += [:password]
43 |
44 | # Enable escaping HTML in JSON.
45 | config.active_support.escape_html_entities_in_json = true
46 |
47 | # Use SQL instead of Active Record's schema dumper when creating the database.
48 | # This is necessary if your schema can't be completely dumped by the schema dumper,
49 | # like if you have constraints or database-specific column types
50 | # config.active_record.schema_format = :sql
51 |
52 | # Enforce whitelist mode for mass assignment.
53 | # This will create an empty whitelist of attributes available for mass-assignment for all models
54 | # in your app. As such, your models will need to explicitly whitelist or blacklist accessible
55 | # parameters by using an attr_accessible or attr_protected declaration.
56 | # config.active_record.whitelist_attributes = true
57 |
58 | # Enable the asset pipeline
59 | config.assets.enabled = true
60 |
61 | # Version of your assets, change this if you want to expire all your assets
62 | config.assets.version = '1.0'
63 | end
64 | end
65 |
66 |
--------------------------------------------------------------------------------
/lib/jader/serialize.rb:
--------------------------------------------------------------------------------
1 | module Jader
2 |
3 | module Serialize
4 |
5 | module ClassMethods
6 |
7 | # Enable serialization on ActiveModel classes
8 | # @param [Array] args model attribute names to serialize
9 | # @param [Hash] args options serializing mode
10 | # @option args [Boolean] :merge should serialized attributes be merged with `self.attributes`
11 | # @example
12 | # class User < ActiveRecord::Base
13 | # include Jader::Serialize
14 | # jade_serializable :name, :email, :merge => false
15 | # end
16 | def jade_serializable(*args)
17 | serialize = {
18 | :attrs => [],
19 | :merge => true
20 | }
21 | args.each do |arg|
22 | if arg.is_a? Symbol
23 | serialize[:attrs] << arg
24 | elsif arg.is_a? Hash
25 | serialize[:merge] = arg[:merge] if arg.include?(:merge)
26 | end
27 | end
28 | class_variable_set(:@@serialize, serialize)
29 | end
30 |
31 | end
32 |
33 | #nodoc
34 | def self.included(base)
35 | base.extend ClassMethods
36 | end
37 |
38 | # Serialize instance attributes to a Hash based on serializable attributes defined on Model class.
39 | # @return [Hash] hash of model instance attributes
40 | def to_jade
41 | h = {:model => self.class.name.downcase}
42 | self.jade_attributes.each do |attr|
43 | h[attr] = self.send(attr)
44 |
45 | ans = h[attr].class.ancestors
46 | if h[attr].class.respond_to?(:jade_serializable) || ans.include?(Enumerable) || ans.include?(ActiveModel::Validations)
47 | h[attr] = h[attr].to_jade
48 | else
49 | end
50 | end
51 | h
52 | end
53 |
54 | # List of Model attributes that should be serialized when called `to_jade` on Model instance
55 | # @return [Array] list of serializable attributes
56 | def jade_attributes
57 | s = self.class.class_variable_get(:@@serialize)
58 | if s[:merge]
59 | attrs = s[:attrs] + self.attributes.keys
60 | else
61 | attrs = s[:attrs]
62 | end
63 | attrs.collect{|attr| attr.to_sym}.uniq
64 | end
65 | end
66 |
67 | end
68 |
69 | class Object
70 | # Serialize Object to Jade format. Invoke `self.to_jade` if instance responds to `to_jade`
71 | def to_jade
72 | if self.respond_to? :to_a
73 | self.to_a.to_jade
74 | else
75 | nil
76 | end
77 | end
78 | end
79 |
80 | #nodoc
81 | [FalseClass, TrueClass, Numeric, String].each do |cls|
82 | cls.class_eval do
83 | def to_jade
84 | self
85 | end
86 | end
87 | end
88 |
89 | class Array
90 |
91 | # Serialize Array to Jade format. Invoke `to_jade` on array members
92 | def to_jade
93 | map {|a| a.respond_to?(:to_jade) ? a.to_jade : a }
94 | end
95 | end
96 |
97 | class Hash
98 |
99 | # Serialize Hash to Jade format. Invoke `to_jade` on members
100 | def to_jade
101 | res = {}
102 | each_pair do |key, value|
103 | res[key] = (value.respond_to?(:to_jade) ? value.to_jade : value)
104 | end
105 | res
106 | end
107 | end
--------------------------------------------------------------------------------
/lib/jader/compiler.rb:
--------------------------------------------------------------------------------
1 | require 'execjs'
2 |
3 | module Jader
4 | class Compiler
5 | attr_accessor :options
6 |
7 | # Initialize Jader template compiler. @see https://github.com/visionmedia/jade#options
8 | # @param [Hash] options Jade compiler options
9 | # @option options [Boolean] :client run Jade compiler in client / server mode (default `true`)
10 | # @option options [Boolean] :compileDebug run Jade compiler in debug mode(default `false`)
11 | def initialize(options={})
12 | @options = {
13 | :client => true,
14 | :compileDebug => false
15 | }.merge options
16 | end
17 |
18 | # Jade template engine Javascript source code used to compile templates in ExecJS
19 | # @return [String] Jade source code
20 | def source
21 | @source ||= %{
22 | var window = {};
23 | #{Jader::Source::jade}
24 | var jade = window.jade;
25 | }
26 | end
27 |
28 | # ExecJS context with Jade code compiled
29 | # @return [ExecJS::Context] compiled Jade source code context
30 | def context
31 | @context ||= ExecJS.compile source
32 | end
33 |
34 | # ExecJS context for the server-side rendering with Jade code compiled
35 | # @return [ExecJS::Context] compiled Jade source code context for the server-side rendering
36 | def server_context
37 | @server_context ||= ExecJS.compile([
38 | source,
39 | Jader.configuration.includes.join("\n")
40 | ].join(" "))
41 | end
42 |
43 | # Jade Javascript engine version
44 | # @return [String] version of Jade javascript engine installed in `vendor/assets/javascripts`
45 | def jade_version
46 | context.eval("jade.version")
47 | end
48 |
49 | # Compile a Jade template for client-side use with JST
50 | # @param [String, File] template Jade template file or text to compile
51 | # @param [String] file_name name of template file used to resolve mixins inclusion
52 | # @return [String] Jade template compiled into Javascript and wrapped inside an anonymous function for JST
53 | def compile(template, file_name = '')
54 | template = template.read if template.respond_to?(:read)
55 | file_name.match(/views\/([^\/]+)\//)
56 | controller_name = $1 || nil
57 | context.eval("jade.compile(#{combo(controller_name, template)},#{@options.to_json}).toString()").to_s.sub('function anonymous','function')
58 | end
59 |
60 | # Compile and evaluate a Jade template for server-side rendering
61 | # @param [String] template Jade template text to render
62 | # @param [String] controller_name name of Rails controller that's rendering the template
63 | # @param [Hash] vars controller instance variables passed to the template
64 | # @return [String] HTML output of compiled Jade template
65 | def render(template, controller_name, vars = {})
66 | server_context.exec("return jade.compile(#{combo(controller_name, template)})(#{vars.to_jade.to_json})")
67 | end
68 |
69 | # Jade template mixins for a given controller
70 | # @param [String] controller_name name of Rails controller rendering a Jade template
71 | # @return [Array] array of Jade mixins to use with a Jade template rendered by a Rails controller
72 | def template_mixins(controller_name)
73 | mixins = []
74 | unless Jader.configuration.mixins_path.nil?
75 | Dir["#{Jader.configuration.mixins_path}/*.jade"].each do |f|
76 | base_name = File.basename(f)
77 | if base_name == '%s_mixins.jade' % controller_name || base_name == 'application_mixins.jade'
78 | mixins << IO.read(f)
79 | end
80 | end
81 | end
82 | mixins
83 | end
84 |
85 | def combo(controller_name, template)
86 | (template_mixins(controller_name) << template).join("\n").to_json
87 | end
88 |
89 | end
90 | end
--------------------------------------------------------------------------------
/vendor/assets/javascripts/jade/runtime.js:
--------------------------------------------------------------------------------
1 |
2 | jade = (function(exports){
3 | /*!
4 | * Jade - runtime
5 | * Copyright(c) 2010 TJ Holowaychuk
6 | * MIT Licensed
7 | */
8 |
9 | /**
10 | * Lame Array.isArray() polyfill for now.
11 | */
12 |
13 | if (!Array.isArray) {
14 | Array.isArray = function(arr){
15 | return '[object Array]' == Object.prototype.toString.call(arr);
16 | };
17 | }
18 |
19 | /**
20 | * Lame Object.keys() polyfill for now.
21 | */
22 |
23 | if (!Object.keys) {
24 | Object.keys = function(obj){
25 | var arr = [];
26 | for (var key in obj) {
27 | if (obj.hasOwnProperty(key)) {
28 | arr.push(key);
29 | }
30 | }
31 | return arr;
32 | }
33 | }
34 |
35 | /**
36 | * Merge two attribute objects giving precedence
37 | * to values in object `b`. Classes are special-cased
38 | * allowing for arrays and merging/joining appropriately
39 | * resulting in a string.
40 | *
41 | * @param {Object} a
42 | * @param {Object} b
43 | * @return {Object} a
44 | * @api private
45 | */
46 |
47 | exports.merge = function merge(a, b) {
48 | var ac = a['class'];
49 | var bc = b['class'];
50 |
51 | if (ac || bc) {
52 | ac = ac || [];
53 | bc = bc || [];
54 | if (!Array.isArray(ac)) ac = [ac];
55 | if (!Array.isArray(bc)) bc = [bc];
56 | ac = ac.filter(nulls);
57 | bc = bc.filter(nulls);
58 | a['class'] = ac.concat(bc).join(' ');
59 | }
60 |
61 | for (var key in b) {
62 | if (key != 'class') {
63 | a[key] = b[key];
64 | }
65 | }
66 |
67 | return a;
68 | };
69 |
70 | /**
71 | * Filter null `val`s.
72 | *
73 | * @param {Mixed} val
74 | * @return {Mixed}
75 | * @api private
76 | */
77 |
78 | function nulls(val) {
79 | return val != null;
80 | }
81 |
82 | /**
83 | * Render the given attributes object.
84 | *
85 | * @param {Object} obj
86 | * @param {Object} escaped
87 | * @return {String}
88 | * @api private
89 | */
90 |
91 | exports.attrs = function attrs(obj, escaped){
92 | var buf = []
93 | , terse = obj.terse;
94 |
95 | delete obj.terse;
96 | var keys = Object.keys(obj)
97 | , len = keys.length;
98 |
99 | if (len) {
100 | buf.push('');
101 | for (var i = 0; i < len; ++i) {
102 | var key = keys[i]
103 | , val = obj[key];
104 |
105 | if ('boolean' == typeof val || null == val) {
106 | if (val) {
107 | terse
108 | ? buf.push(key)
109 | : buf.push(key + '="' + key + '"');
110 | }
111 | } else if (0 == key.indexOf('data') && 'string' != typeof val) {
112 | buf.push(key + "='" + JSON.stringify(val) + "'");
113 | } else if ('class' == key && Array.isArray(val)) {
114 | buf.push(key + '="' + exports.escape(val.join(' ')) + '"');
115 | } else if (escaped && escaped[key]) {
116 | buf.push(key + '="' + exports.escape(val) + '"');
117 | } else {
118 | buf.push(key + '="' + val + '"');
119 | }
120 | }
121 | }
122 |
123 | return buf.join(' ');
124 | };
125 |
126 | /**
127 | * Escape the given string of `html`.
128 | *
129 | * @param {String} html
130 | * @return {String}
131 | * @api private
132 | */
133 |
134 | exports.escape = function escape(html){
135 | return String(html)
136 | .replace(/&(?!(\w+|\#\d+);)/g, '&')
137 | .replace(//g, '>')
139 | .replace(/"/g, '"');
140 | };
141 |
142 | /**
143 | * Re-throw the given `err` in context to the
144 | * the jade in `filename` at the given `lineno`.
145 | *
146 | * @param {Error} err
147 | * @param {String} filename
148 | * @param {String} lineno
149 | * @api private
150 | */
151 |
152 | exports.rethrow = function rethrow(err, filename, lineno){
153 | if (!filename) throw err;
154 |
155 | var context = 3
156 | , str = require('fs').readFileSync(filename, 'utf8')
157 | , lines = str.split('\n')
158 | , start = Math.max(lineno - context, 0)
159 | , end = Math.min(lines.length, lineno + context);
160 |
161 | // Error context
162 | var context = lines.slice(start, end).map(function(line, i){
163 | var curr = i + start + 1;
164 | return (curr == lineno ? ' > ' : ' ')
165 | + curr
166 | + '| '
167 | + line;
168 | }).join('\n');
169 |
170 | // Alter exception message
171 | err.path = filename;
172 | err.message = (filename || 'Jade') + ':' + lineno
173 | + '\n' + context + '\n\n' + err.message;
174 | throw err;
175 | };
176 |
177 | return exports;
178 |
179 | })({});
180 |
--------------------------------------------------------------------------------
/spec/dummy/README.rdoc:
--------------------------------------------------------------------------------
1 | == Welcome to Rails
2 |
3 | Rails is a web-application framework that includes everything needed to create
4 | database-backed web applications according to the Model-View-Control pattern.
5 |
6 | This pattern splits the view (also called the presentation) into "dumb"
7 | templates that are primarily responsible for inserting pre-built data in between
8 | HTML tags. The model contains the "smart" domain objects (such as Account,
9 | Product, Person, Post) that holds all the business logic and knows how to
10 | persist themselves to a database. The controller handles the incoming requests
11 | (such as Save New Account, Update Product, Show Post) by manipulating the model
12 | and directing data to the view.
13 |
14 | In Rails, the model is handled by what's called an object-relational mapping
15 | layer entitled Active Record. This layer allows you to present the data from
16 | database rows as objects and embellish these data objects with business logic
17 | methods. You can read more about Active Record in
18 | link:files/vendor/rails/activerecord/README.html.
19 |
20 | The controller and view are handled by the Action Pack, which handles both
21 | layers by its two parts: Action View and Action Controller. These two layers
22 | are bundled in a single package due to their heavy interdependence. This is
23 | unlike the relationship between the Active Record and Action Pack that is much
24 | more separate. Each of these packages can be used independently outside of
25 | Rails. You can read more about Action Pack in
26 | link:files/vendor/rails/actionpack/README.html.
27 |
28 |
29 | == Getting Started
30 |
31 | 1. At the command prompt, create a new Rails application:
32 | rails new myapp (where myapp is the application name)
33 |
34 | 2. Change directory to myapp and start the web server:
35 | cd myapp; rails server (run with --help for options)
36 |
37 | 3. Go to http://localhost:3000/ and you'll see:
38 | "Welcome aboard: You're riding Ruby on Rails!"
39 |
40 | 4. Follow the guidelines to start developing your application. You can find
41 | the following resources handy:
42 |
43 | * The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html
44 | * Ruby on Rails Tutorial Book: http://www.railstutorial.org/
45 |
46 |
47 | == Debugging Rails
48 |
49 | Sometimes your application goes wrong. Fortunately there are a lot of tools that
50 | will help you debug it and get it back on the rails.
51 |
52 | First area to check is the application log files. Have "tail -f" commands
53 | running on the server.log and development.log. Rails will automatically display
54 | debugging and runtime information to these files. Debugging info will also be
55 | shown in the browser on requests from 127.0.0.1.
56 |
57 | You can also log your own messages directly into the log file from your code
58 | using the Ruby logger class from inside your controllers. Example:
59 |
60 | class WeblogController < ActionController::Base
61 | def destroy
62 | @weblog = Weblog.find(params[:id])
63 | @weblog.destroy
64 | logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!")
65 | end
66 | end
67 |
68 | The result will be a message in your log file along the lines of:
69 |
70 | Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1!
71 |
72 | More information on how to use the logger is at http://www.ruby-doc.org/core/
73 |
74 | Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are
75 | several books available online as well:
76 |
77 | * Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe)
78 | * Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide)
79 |
80 | These two books will bring you up to speed on the Ruby language and also on
81 | programming in general.
82 |
83 |
84 | == Debugger
85 |
86 | Debugger support is available through the debugger command when you start your
87 | Mongrel or WEBrick server with --debugger. This means that you can break out of
88 | execution at any point in the code, investigate and change the model, and then,
89 | resume execution! You need to install ruby-debug to run the server in debugging
90 | mode. With gems, use sudo gem install ruby-debug. Example:
91 |
92 | class WeblogController < ActionController::Base
93 | def index
94 | @posts = Post.all
95 | debugger
96 | end
97 | end
98 |
99 | So the controller will accept the action, run the first line, then present you
100 | with a IRB prompt in the server window. Here you can do things like:
101 |
102 | >> @posts.inspect
103 | => "[#nil, "body"=>nil, "id"=>"1"}>,
105 | #"Rails", "body"=>"Only ten..", "id"=>"2"}>]"
107 | >> @posts.first.title = "hello from a debugger"
108 | => "hello from a debugger"
109 |
110 | ...and even better, you can examine how your runtime objects actually work:
111 |
112 | >> f = @posts.first
113 | => #nil, "body"=>nil, "id"=>"1"}>
114 | >> f.
115 | Display all 152 possibilities? (y or n)
116 |
117 | Finally, when you're ready to resume execution, you can enter "cont".
118 |
119 |
120 | == Console
121 |
122 | The console is a Ruby shell, which allows you to interact with your
123 | application's domain model. Here you'll have all parts of the application
124 | configured, just like it is when the application is running. You can inspect
125 | domain models, change values, and save to the database. Starting the script
126 | without arguments will launch it in the development environment.
127 |
128 | To start the console, run rails console from the application
129 | directory.
130 |
131 | Options:
132 |
133 | * Passing the -s, --sandbox argument will rollback any modifications
134 | made to the database.
135 | * Passing an environment name as an argument will load the corresponding
136 | environment. Example: rails console production.
137 |
138 | To reload your controllers and models after launching the console run
139 | reload!
140 |
141 | More information about irb can be found at:
142 | link:http://www.rubycentral.org/pickaxe/irb.html
143 |
144 |
145 | == dbconsole
146 |
147 | You can go to the command line of your database directly through rails
148 | dbconsole. You would be connected to the database with the credentials
149 | defined in database.yml. Starting the script without arguments will connect you
150 | to the development database. Passing an argument will connect you to a different
151 | database, like rails dbconsole production. Currently works for MySQL,
152 | PostgreSQL and SQLite 3.
153 |
154 | == Description of Contents
155 |
156 | The default directory structure of a generated Ruby on Rails application:
157 |
158 | |-- app
159 | | |-- assets
160 | | |-- images
161 | | |-- javascripts
162 | | `-- stylesheets
163 | | |-- controllers
164 | | |-- helpers
165 | | |-- mailers
166 | | |-- models
167 | | `-- views
168 | | `-- layouts
169 | |-- config
170 | | |-- environments
171 | | |-- initializers
172 | | `-- locales
173 | |-- db
174 | |-- doc
175 | |-- lib
176 | | `-- tasks
177 | |-- log
178 | |-- public
179 | |-- script
180 | |-- test
181 | | |-- fixtures
182 | | |-- functional
183 | | |-- integration
184 | | |-- performance
185 | | `-- unit
186 | |-- tmp
187 | | |-- cache
188 | | |-- pids
189 | | |-- sessions
190 | | `-- sockets
191 | `-- vendor
192 | |-- assets
193 | `-- stylesheets
194 | `-- plugins
195 |
196 | app
197 | Holds all the code that's specific to this particular application.
198 |
199 | app/assets
200 | Contains subdirectories for images, stylesheets, and JavaScript files.
201 |
202 | app/controllers
203 | Holds controllers that should be named like weblogs_controller.rb for
204 | automated URL mapping. All controllers should descend from
205 | ApplicationController which itself descends from ActionController::Base.
206 |
207 | app/models
208 | Holds models that should be named like post.rb. Models descend from
209 | ActiveRecord::Base by default.
210 |
211 | app/views
212 | Holds the template files for the view that should be named like
213 | weblogs/index.html.erb for the WeblogsController#index action. All views use
214 | eRuby syntax by default.
215 |
216 | app/views/layouts
217 | Holds the template files for layouts to be used with views. This models the
218 | common header/footer method of wrapping views. In your views, define a layout
219 | using the layout :default and create a file named default.html.erb.
220 | Inside default.html.erb, call <% yield %> to render the view using this
221 | layout.
222 |
223 | app/helpers
224 | Holds view helpers that should be named like weblogs_helper.rb. These are
225 | generated for you automatically when using generators for controllers.
226 | Helpers can be used to wrap functionality for your views into methods.
227 |
228 | config
229 | Configuration files for the Rails environment, the routing map, the database,
230 | and other dependencies.
231 |
232 | db
233 | Contains the database schema in schema.rb. db/migrate contains all the
234 | sequence of Migrations for your schema.
235 |
236 | doc
237 | This directory is where your application documentation will be stored when
238 | generated using rake doc:app
239 |
240 | lib
241 | Application specific libraries. Basically, any kind of custom code that
242 | doesn't belong under controllers, models, or helpers. This directory is in
243 | the load path.
244 |
245 | public
246 | The directory available for the web server. Also contains the dispatchers and the
247 | default HTML files. This should be set as the DOCUMENT_ROOT of your web
248 | server.
249 |
250 | script
251 | Helper scripts for automation and generation.
252 |
253 | test
254 | Unit and functional tests along with fixtures. When using the rails generate
255 | command, template test files will be generated for you and placed in this
256 | directory.
257 |
258 | vendor
259 | External libraries that the application depends on. Also includes the plugins
260 | subdirectory. If the app has frozen rails, those gems also go here, under
261 | vendor/rails/. This directory is in the load path.
262 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Jader - Share your Jade templates between client and server
2 |
3 | Jade is a very popular templating engine for Node.js. This gem gives
4 | you ability to easily use Jade templates for both server and client side in your Rails project.
5 |
6 | On the client-side your templates should be used with
7 | [Sprockets' JST engine](https://github.com/sstephenson/sprockets#javascript-templating-with-ejs-and-eco).
8 | On the server side, you can render your Jade templates as Rails views (similar to how you'd render ERB or HAML templates).
9 |
10 | ## Writing your templates
11 |
12 | Lets assume you have a users controller `app/controllers/users_controller.rb` which in turn renders a list of users.
13 | We'd like to share that view between the client and the server.
14 |
15 | ### Server-Side code
16 |
17 | ```ruby
18 | class UsersController < ApplicationController
19 |
20 | def index
21 | @users = User.all
22 | respond_to do |format|
23 | format.html
24 | end
25 | end
26 |
27 | end
28 | ```
29 |
30 | To share our template between client and server, we need to place it under `app/assets/javascripts` for Sprockets' JST engine.
31 |
32 | Lets create a views directory for our shared templates `app/assets/javascripts/views` and add our template there, following Rails' naming convensions.
33 |
34 | The full template path should look like this: `app/assets/javascripts/views/users/index.jst.jade`
35 |
36 | ### Template code
37 |
38 | The most significant differences between using standard server-side Ruby-based engines like ERB or HAML and using Jader are:
39 |
40 | * No access to server-side view helpers (such as url_for)
41 | * No ruby-style instance variable like `@users`
42 | * Template code is Javascript, not Ruby and has to follow Jade's syntax rather than embedded Ruby syntax
43 | * No partials or includes
44 |
45 | Our template code should look like this:
46 |
47 | ```jade
48 | ul.users
49 | each user in users
50 | li.user= user.name
51 | ```
52 |
53 | Note that rendering this template server-side, will be done inside your application's layout. You can write your views layout file in ERB / HAML
54 | and the call to `=yield` will render your Jade template above.
55 |
56 | ### Sharing template code
57 |
58 | Since Rails doesn't expect server-side templates to live under `app/assets` we need to add our client-side views path to Rails views lookup path.
59 |
60 | Assuming we have an initializer `app/config/initializers/jader.rb` we can add our client-side views directory like this:
61 |
62 | ```ruby
63 | Jader.configure do |config|
64 | # make your client-side views directory discoverable to Rails
65 | config.views_path = Rails.root.join('app','assets','javascripts','views')
66 | end
67 | ```
68 |
69 | Then, to add a `before_filter` to `ApplicationController::Base` that prepends the provided path to `ActionView::Context` add the following config:
70 |
71 | ```ruby
72 | Jader.configure do |config|
73 | ...
74 | # prepend provided views_path
75 | config.prepend_view_path = true
76 | end
77 | ```
78 |
79 | ### Client-side code
80 |
81 | To render the same template from the client, we need to fetch our users list from the server and then call our JST template with that list.
82 |
83 | First, lets change our controller to return a JSON formatted list of users when called from the client:
84 |
85 | ```ruby
86 | class UsersController < ApplicationController
87 |
88 | def index
89 | @users = User.all
90 | respond_to do |format|
91 | format.html
92 | format.json {
93 | render :json => @users.to_jade
94 | }
95 | end
96 | end
97 |
98 | end
99 | ```
100 |
101 | Note the call to `to_jade` on the `@users` collection. This ensures our users are properly serialized for use inside our template.
102 | See the [Serialization](https://github.com/zohararad/jader#serialization) section below for more details.
103 |
104 | In our `application.js` file lets write the following:
105 |
106 | ```javascript
107 | //= require jade/runtime
108 | //= require views/users/index
109 |
110 | $.getJSON('/users', function(users){
111 | $('body').html(JST['views/users/index']({users:users}));
112 | });
113 | ```
114 |
115 | ## Serialization
116 |
117 | To help Jader access Ruby and Rails variables inside the template, we need to employ some sort of JSON serializing before passing
118 | these variables to the template. On the server-side, this happens automagically before the template is rendered.
119 |
120 | Internally, Jader will try to call the `to_jade` method on each instance variable that's passed to the template. Ruby's Hash, Array and Object classes have been extended
121 | to support this functionality. Arrays and Hashes will attempt to call the `to_jade` method on their members when `to_jade` is invoked on their instances. For
122 | other collection-like variables, the `to_jade` method will only be invoked if they respond to a `to_a` method. This allows ActiveModel / ActiveRecord instance variables to
123 | automatically serialize their members before rendering.
124 |
125 | ### Serializing models
126 |
127 | Jader does not assume your Rails models should be serialized by default. Instead, it expects you to enable serializing on desired models explicitly.
128 |
129 | To enable this behaviour, consider the following example:
130 |
131 | ```ruby
132 | class User < ActiveRecord::Base
133 |
134 | include Jader::Serialize
135 |
136 | jade_serializable :name, :email, :favorites, :merge => false
137 |
138 | end
139 | ```
140 |
141 | The call to `include Jader::Serialize` mixes Jader::Serializer capabilities into our model class.
142 |
143 | We can then tell the serializer which attributes we'd like to serialize, and how we'd like the serialization to work.
144 |
145 | By default, calling `jade_serializable` with no arguments will serialize all your model attributes. Lets look at two examples:
146 |
147 | Consider the following code:
148 |
149 | ```ruby
150 | # define our model
151 | class User < ActiveRecord::Base
152 |
153 | include Jader::Serialize
154 |
155 | jade_serializable
156 |
157 | end
158 |
159 | # access in controller
160 | class UsersController < ApplicationController
161 |
162 | def index
163 | @users = User.all
164 | @users.to_jade # => all available user attributes (users table columns) will be serialized
165 | end
166 |
167 | def active
168 | @users = User.where('active = 1').select('name, email')
169 | @users.to_jade # => only name and email attributes are serialized
170 | end
171 | end
172 | ```
173 |
174 | For better control over which attributes are serialized, and when serializing model relationships, we can tell the serializer
175 | which attributes should always be serialized, and whether we'd like these attributes to be merged with the default attributes or not.
176 |
177 | Consider the following code:
178 |
179 |
180 | ```ruby
181 | # define our models
182 |
183 | class Favorite < ActiveRecord::Base
184 |
185 | include Jader::Serialize
186 |
187 | jade_serializable
188 |
189 | belongs_to :user
190 |
191 | end
192 |
193 | class User < ActiveRecord::Base
194 |
195 | include Jader::Serialize
196 |
197 | jade_serializable :favorites, :merge => true
198 |
199 | has_many :favorites
200 | end
201 |
202 | # access in controller
203 | class UsersController < ApplicationController
204 |
205 | def active
206 | @users = User.where('active = 1').select('name, email')
207 | @users.to_jade # => only name, email and favorites attributes are serialized
208 | end
209 | end
210 | ```
211 |
212 | In the above, we defined serialization for the `User` model to include the `:favorites` attribute, which is available because of the `has_many` relationship
213 | to the `Favorite` model. Additionally, we specified that serialization should merge model default attributes with the specified attributes, by setting `:merge => true` .
214 |
215 | This will result in merging `self.attributes` and `self.favorites` on any instance of the `User` model when calling the `to_jade` method on it.
216 |
217 | To only serialize the specified attributes, call `jade_serializable` with `:merge => false` .
218 |
219 | Invokation format for `jade_serializable` is:
220 |
221 | ```ruby
222 | jade_serializable :attr1, :attr2, :attr3 ...., :merge => true/false
223 | ```
224 |
225 | By default, `jade_serializable` will operate with `:merge => true` and merge instnace attributes with specified attributes.
226 |
227 | ## Mixins
228 |
229 | Jade has built in support for template mixins. Jader allows you to define and share mixins inside your templates.
230 |
231 | Following Rails helpers conventions, your Jade mixins can be defined either as application-level mixins or as controller-level mixins.
232 |
233 | Assuming we have a users controller, we can add mixins by creating a mixins folder under `app/assets/javascripts` and adding:
234 |
235 | * `app/assets/javascripts/helpers/application_mixins.jade`
236 | * `app/assets/javascripts/helpers/users_mixins.jade`
237 |
238 | Mixins that are defined inside `application_mixins.jade` will be available in all templates. This is a perfect place for example to add a pagination mixin.
239 |
240 | Controller-level mixins are defined inside `CONTROLLER_NAME_mixins.jade` and are available only inside the relevant controller. Since the
241 | client-side has no notion of which controller is currently being rendered, we need to strictly follow Rails naming conventions as follows:
242 |
243 | * Client-side views subdirectories are named after their controller. For example, for `UsersController` we will have `app/assets/javascripts/views/users`
244 | * Client-side Jade mixins files are resolved based on view name. `mixins/users_mixins.jade` will be available for views inside `views/users` folder
245 |
246 | To enable Jader's mixins capabilities we need to configure Jader and tell it where to look for mixins files:
247 |
248 | ```ruby
249 | Jader.configure do |config|
250 | config.mixins_path = Rails.root.join('app','assets','javascripts','mixins')
251 | end
252 | ```
253 |
254 | ### Notes on performance
255 |
256 | On the client-side, Jader will add the mixins code into your Jade JST template. This can potentially increase your client-side file size dramatically. Since application-level
257 | mixins are included in each and every template, please be sure to keep them to a bare minimum.
258 |
259 | ## Javascript inclusion
260 |
261 | Often we'll have additional Javascript that is included client-side and adds more functionality to our client-side application. Two such examples could be
262 | using `I18n.js` for internationalization, or `Date.js` for better date handling.
263 |
264 | Since our server-side templates cannot access this code, we need to figure out a way to share arbitrary Javascript from our client in the server.
265 |
266 | This is where inclusions come in.
267 |
268 | We can tell Jader to add arbitrary pieces of raw Javascript to the server-side rendering context before evaluating our template like so:
269 |
270 | ```ruby
271 | Jader.configure do |config|
272 | config.includes << IO.read(Rails.root.join('app','assets','javascripts','includes','util.js'))
273 | end
274 | ```
275 |
276 | `Jader.configuration.includes` is an array that accepts raw Javascript strings that are, in turn, passed to the server-side template evaluation context.
277 |
278 | To give a more pragmatic example of using Jader inclusions, lets try using `I18n.js` on both server and client.
279 |
280 | For the sake of this example, we assume `gem 'i18n-js'` is installed in our application.
281 |
282 | Our `application.js` file will then include:
283 |
284 | ``` javascript
285 | //= require i18n
286 | //= require i18n/translations
287 | ```
288 |
289 | The first require is made available via `i18n-js` vendorized assets while the second require is a translations file inside our `app/assets/javascripts/i18n` folder.
290 |
291 | To enable I18n support when rendering templates on the server, we configure Jader as follows:
292 |
293 | ```ruby
294 | Jader.configure do |config|
295 | # wait for assets to be ready
296 | Rails.application.config.after_initialize do
297 | # inject assets source into Jader's includes array
298 | config.includes << Rails.application.assets['i18n'].source
299 | config.includes << Rails.application.assets['i18n/translations'].source
300 | config.includes << "I18n.defaultLocale = 'en'; I18n.locale = 'en';"
301 | end
302 | end
303 | ```
304 |
305 | > If you want to use node.js runtime, you'll have to append your javasript modules into the global `this` object (e.g. `this.Util = Util`) to make them visible inside the templates. (See: `/spec/dummy/app/assets/javascripts/includes/util.js`)
306 |
307 | ## Configuration
308 |
309 | Its recommended to configure Jader inside a Rails initializer so that configuration is defined at boot time.
310 |
311 | Assuming we have an initializer `app/config/initializers/jader.rb` it should include:
312 |
313 | ```ruby
314 | Jader.configure do |config|
315 | config.mixins_path = Rails.root.join('app','assets','javascripts','mixins')
316 | # make your client-side views directory discoverable to Rails
317 | config.views_path = Rails.root.join('app','assets','javascripts','views')
318 | # Use some javascript from a file that's not available in the asset pipeline
319 | config.includes << IO.read(Rails.root.join('app','assets','javascripts','includes','util.js'))
320 | # In Rails 3 applications, prepend views_path using a controller before filter.
321 | # This is set to false by default
322 | config.prepend_view_path = true
323 | # wait for assets to be ready
324 | Rails.application.config.after_initialize do
325 | # include javascripts available only from asset pipeline
326 | config.includes << Rails.application.assets['util'].source
327 | end
328 | end
329 | ```
330 |
331 | ## Asset Pipeline
332 |
333 | In case your Rails asset pipeline is configured **not** to load the entire Rails environment when calling `rake assets:precompile`, you should include Jader's configuration initalizer in your Rakefile.
334 |
335 | Simply add `require File.expand_path('../config/initializers/jader', __FILE__)` before `require File.expand_path('../config/application', __FILE__)` in your Rakefile, and ensure Jader is properly configured when your assets are precompiled
336 |
337 | ## Kudos
338 |
339 | Jader is built upon the wonderful work of [Boris Staal](https://github.com/roundlake/jade/) and draws heavily from:
340 |
341 | - [ice](https://github.com/ludicast/ice)
342 | - [ruby-haml-js](https://github.com/dnagir/ruby-haml-js)
343 | - [tilt-jade](https://github.com/therabidbanana/tilt-jade)
344 |
345 | Boris Staal's [Jade](https://github.com/roundlake/jade/) Rubygem was developed as a successor to tilt-jade to improve following:
346 |
347 | * Add debugging capabilities (slightly different build technique)
348 | * Support exact Jade.JS lib without modifications
349 | * Do not hold 3 copies of Jade.JS internally
350 | * Be well-covered with RSpec
351 |
352 | ## Credits
353 |
354 |
355 |
356 | * Boris Staal ([@_inossidabile](http://twitter.com/#!/_inossidabile))
357 |
358 | # License
359 |
360 | Copyright (c) 2012 Zohar Arad
361 |
362 | 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:
363 |
364 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
365 |
366 | 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.
367 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/jade/jade.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | // CommonJS require()
4 |
5 | function require(p){
6 | var path = require.resolve(p)
7 | , mod = require.modules[path];
8 | if (!mod) throw new Error('failed to require "' + p + '"');
9 | if (!mod.exports) {
10 | mod.exports = {};
11 | mod.call(mod.exports, mod, mod.exports, require.relative(path));
12 | }
13 | return mod.exports;
14 | }
15 |
16 | require.modules = {};
17 |
18 | require.resolve = function (path){
19 | var orig = path
20 | , reg = path + '.js'
21 | , index = path + '/index.js';
22 | return require.modules[reg] && reg
23 | || require.modules[index] && index
24 | || orig;
25 | };
26 |
27 | require.register = function (path, fn){
28 | require.modules[path] = fn;
29 | };
30 |
31 | require.relative = function (parent) {
32 | return function(p){
33 | if ('.' != p.charAt(0)) return require(p);
34 |
35 | var path = parent.split('/')
36 | , segs = p.split('/');
37 | path.pop();
38 |
39 | for (var i = 0; i < segs.length; i++) {
40 | var seg = segs[i];
41 | if ('..' == seg) path.pop();
42 | else if ('.' != seg) path.push(seg);
43 | }
44 |
45 | return require(path.join('/'));
46 | };
47 | };
48 |
49 |
50 | require.register("compiler.js", function(module, exports, require){
51 |
52 | /*!
53 | * Jade - Compiler
54 | * Copyright(c) 2010 TJ Holowaychuk
55 | * MIT Licensed
56 | */
57 |
58 | /**
59 | * Module dependencies.
60 | */
61 |
62 | var nodes = require('./nodes')
63 | , filters = require('./filters')
64 | , doctypes = require('./doctypes')
65 | , selfClosing = require('./self-closing')
66 | , runtime = require('./runtime')
67 | , utils = require('./utils');
68 |
69 |
70 | if (!Object.keys) {
71 | Object.keys = function(obj){
72 | var arr = [];
73 | for (var key in obj) {
74 | if (obj.hasOwnProperty(key)) {
75 | arr.push(key);
76 | }
77 | }
78 | return arr;
79 | }
80 | }
81 |
82 | if (!String.prototype.trimLeft) {
83 | String.prototype.trimLeft = function(){
84 | return this.replace(/^\s+/, '');
85 | }
86 | }
87 |
88 |
89 |
90 | /**
91 | * Initialize `Compiler` with the given `node`.
92 | *
93 | * @param {Node} node
94 | * @param {Object} options
95 | * @api public
96 | */
97 |
98 | var Compiler = module.exports = function Compiler(node, options) {
99 | this.options = options = options || {};
100 | this.node = node;
101 | this.hasCompiledDoctype = false;
102 | this.hasCompiledTag = false;
103 | this.pp = options.pretty || false;
104 | this.debug = false !== options.compileDebug;
105 | this.indents = 0;
106 | this.parentIndents = 0;
107 | if (options.doctype) this.setDoctype(options.doctype);
108 | };
109 |
110 | /**
111 | * Compiler prototype.
112 | */
113 |
114 | Compiler.prototype = {
115 |
116 | /**
117 | * Compile parse tree to JavaScript.
118 | *
119 | * @api public
120 | */
121 |
122 | compile: function(){
123 | this.buf = ['var interp;'];
124 | if (this.pp) this.buf.push("var __indent = [];");
125 | this.lastBufferedIdx = -1;
126 | this.visit(this.node);
127 | return this.buf.join('\n');
128 | },
129 |
130 | /**
131 | * Sets the default doctype `name`. Sets terse mode to `true` when
132 | * html 5 is used, causing self-closing tags to end with ">" vs "/>",
133 | * and boolean attributes are not mirrored.
134 | *
135 | * @param {string} name
136 | * @api public
137 | */
138 |
139 | setDoctype: function(name){
140 | name = (name && name.toLowerCase()) || 'default';
141 | this.doctype = doctypes[name] || '';
142 | this.terse = this.doctype.toLowerCase() == '';
143 | this.xml = 0 == this.doctype.indexOf(' 1 && !escape && block.nodes[0].isText && block.nodes[1].isText)
294 | this.prettyIndent(1, true);
295 |
296 | for (var i = 0; i < len; ++i) {
297 | // Pretty print text
298 | if (pp && i > 0 && !escape && block.nodes[i].isText && block.nodes[i-1].isText)
299 | this.prettyIndent(1, false);
300 |
301 | this.visit(block.nodes[i]);
302 | // Multiple text nodes are separated by newlines
303 | if (block.nodes[i+1] && block.nodes[i].isText && block.nodes[i+1].isText)
304 | this.buffer('\\n');
305 | }
306 | },
307 |
308 | /**
309 | * Visit `doctype`. Sets terse mode to `true` when html 5
310 | * is used, causing self-closing tags to end with ">" vs "/>",
311 | * and boolean attributes are not mirrored.
312 | *
313 | * @param {Doctype} doctype
314 | * @api public
315 | */
316 |
317 | visitDoctype: function(doctype){
318 | if (doctype && (doctype.val || !this.doctype)) {
319 | this.setDoctype(doctype.val || 'default');
320 | }
321 |
322 | if (this.doctype) this.buffer(this.doctype);
323 | this.hasCompiledDoctype = true;
324 | },
325 |
326 | /**
327 | * Visit `mixin`, generating a function that
328 | * may be called within the template.
329 | *
330 | * @param {Mixin} mixin
331 | * @api public
332 | */
333 |
334 | visitMixin: function(mixin){
335 | var name = mixin.name.replace(/-/g, '_') + '_mixin'
336 | , args = mixin.args || ''
337 | , block = mixin.block
338 | , attrs = mixin.attrs
339 | , pp = this.pp;
340 |
341 | if (mixin.call) {
342 | if (pp) this.buf.push("__indent.push('" + Array(this.indents + 1).join(' ') + "');")
343 | if (block || attrs.length) {
344 |
345 | this.buf.push(name + '.call({');
346 |
347 | if (block) {
348 | this.buf.push('block: function(){');
349 |
350 | // Render block with no indents, dynamically added when rendered
351 | this.parentIndents++;
352 | var _indents = this.indents;
353 | this.indents = 0;
354 | this.visit(mixin.block);
355 | this.indents = _indents;
356 | this.parentIndents--;
357 |
358 | if (attrs.length) {
359 | this.buf.push('},');
360 | } else {
361 | this.buf.push('}');
362 | }
363 | }
364 |
365 | if (attrs.length) {
366 | var val = this.attrs(attrs);
367 | if (val.inherits) {
368 | this.buf.push('attributes: merge({' + val.buf
369 | + '}, attributes), escaped: merge(' + val.escaped + ', escaped, true)');
370 | } else {
371 | this.buf.push('attributes: {' + val.buf + '}, escaped: ' + val.escaped);
372 | }
373 | }
374 |
375 | if (args) {
376 | this.buf.push('}, ' + args + ');');
377 | } else {
378 | this.buf.push('});');
379 | }
380 |
381 | } else {
382 | this.buf.push(name + '(' + args + ');');
383 | }
384 | if (pp) this.buf.push("__indent.pop();")
385 | } else {
386 | this.buf.push('var ' + name + ' = function(' + args + '){');
387 | this.buf.push('var block = this.block, attributes = this.attributes || {}, escaped = this.escaped || {};');
388 | this.parentIndents++;
389 | this.visit(block);
390 | this.parentIndents--;
391 | this.buf.push('};');
392 | }
393 | },
394 |
395 | /**
396 | * Visit `tag` buffering tag markup, generating
397 | * attributes, visiting the `tag`'s code and block.
398 | *
399 | * @param {Tag} tag
400 | * @api public
401 | */
402 |
403 | visitTag: function(tag){
404 | this.indents++;
405 | var name = tag.name
406 | , pp = this.pp;
407 |
408 | if (tag.buffer) name = "' + (" + name + ") + '";
409 |
410 | if (!this.hasCompiledTag) {
411 | if (!this.hasCompiledDoctype && 'html' == name) {
412 | this.visitDoctype();
413 | }
414 | this.hasCompiledTag = true;
415 | }
416 |
417 | // pretty print
418 | if (pp && !tag.isInline())
419 | this.prettyIndent(0, true);
420 |
421 | if ((~selfClosing.indexOf(name) || tag.selfClosing) && !this.xml) {
422 | this.buffer('<' + name);
423 | this.visitAttributes(tag.attrs);
424 | this.terse
425 | ? this.buffer('>')
426 | : this.buffer('/>');
427 | } else {
428 | // Optimize attributes buffering
429 | if (tag.attrs.length) {
430 | this.buffer('<' + name);
431 | if (tag.attrs.length) this.visitAttributes(tag.attrs);
432 | this.buffer('>');
433 | } else {
434 | this.buffer('<' + name + '>');
435 | }
436 | if (tag.code) this.visitCode(tag.code);
437 | this.escape = 'pre' == tag.name;
438 | this.visit(tag.block);
439 |
440 | // pretty print
441 | if (pp && !tag.isInline() && 'pre' != tag.name && !tag.canInline())
442 | this.prettyIndent(0, true);
443 |
444 | this.buffer('' + name + '>');
445 | }
446 | this.indents--;
447 | },
448 |
449 | /**
450 | * Visit `filter`, throwing when the filter does not exist.
451 | *
452 | * @param {Filter} filter
453 | * @api public
454 | */
455 |
456 | visitFilter: function(filter){
457 | var fn = filters[filter.name];
458 |
459 | // unknown filter
460 | if (!fn) {
461 | if (filter.isASTFilter) {
462 | throw new Error('unknown ast filter "' + filter.name + ':"');
463 | } else {
464 | throw new Error('unknown filter ":' + filter.name + '"');
465 | }
466 | }
467 |
468 | if (filter.isASTFilter) {
469 | this.buf.push(fn(filter.block, this, filter.attrs));
470 | } else {
471 | var text = filter.block.nodes.map(function(node){ return node.val }).join('\n');
472 | filter.attrs = filter.attrs || {};
473 | filter.attrs.filename = this.options.filename;
474 | this.buffer(utils.text(fn(text, filter.attrs)));
475 | }
476 | },
477 |
478 | /**
479 | * Visit `text` node.
480 | *
481 | * @param {Text} text
482 | * @api public
483 | */
484 |
485 | visitText: function(text){
486 | text = utils.text(text.val.replace(/\\/g, '_SLASH_'));
487 | if (this.escape) text = escape(text);
488 | text = text.replace(/_SLASH_/g, '\\\\');
489 | this.buffer(text);
490 | },
491 |
492 | /**
493 | * Visit a `comment`, only buffering when the buffer flag is set.
494 | *
495 | * @param {Comment} comment
496 | * @api public
497 | */
498 |
499 | visitComment: function(comment){
500 | if (!comment.buffer) return;
501 | if (this.pp) this.prettyIndent(1, true);
502 | this.buffer('');
503 | },
504 |
505 | /**
506 | * Visit a `BlockComment`.
507 | *
508 | * @param {Comment} comment
509 | * @api public
510 | */
511 |
512 | visitBlockComment: function(comment){
513 | if (!comment.buffer) return;
514 | if (0 == comment.val.trim().indexOf('if')) {
515 | this.buffer('');
518 | } else {
519 | this.buffer('');
522 | }
523 | },
524 |
525 | /**
526 | * Visit `code`, respecting buffer / escape flags.
527 | * If the code is followed by a block, wrap it in
528 | * a self-calling function.
529 | *
530 | * @param {Code} code
531 | * @api public
532 | */
533 |
534 | visitCode: function(code){
535 | // Wrap code blocks with {}.
536 | // we only wrap unbuffered code blocks ATM
537 | // since they are usually flow control
538 |
539 | // Buffer code
540 | if (code.buffer) {
541 | var val = code.val.trimLeft();
542 | this.buf.push('var __val__ = ' + val);
543 | val = 'null == __val__ ? "" : __val__';
544 | if (code.escape) val = 'escape(' + val + ')';
545 | this.buf.push("buf.push(" + val + ");");
546 | } else {
547 | this.buf.push(code.val);
548 | }
549 |
550 | // Block support
551 | if (code.block) {
552 | if (!code.buffer) this.buf.push('{');
553 | this.visit(code.block);
554 | if (!code.buffer) this.buf.push('}');
555 | }
556 | },
557 |
558 | /**
559 | * Visit `each` block.
560 | *
561 | * @param {Each} each
562 | * @api public
563 | */
564 |
565 | visitEach: function(each){
566 | this.buf.push(''
567 | + '// iterate ' + each.obj + '\n'
568 | + ';(function(){\n'
569 | + ' if (\'number\' == typeof ' + each.obj + '.length) {\n');
570 |
571 | if (each.alternative) {
572 | this.buf.push(' if (' + each.obj + '.length) {');
573 | }
574 |
575 | this.buf.push(''
576 | + ' for (var ' + each.key + ' = 0, $$l = ' + each.obj + '.length; ' + each.key + ' < $$l; ' + each.key + '++) {\n'
577 | + ' var ' + each.val + ' = ' + each.obj + '[' + each.key + '];\n');
578 |
579 | this.visit(each.block);
580 |
581 | this.buf.push(' }\n');
582 |
583 | if (each.alternative) {
584 | this.buf.push(' } else {');
585 | this.visit(each.alternative);
586 | this.buf.push(' }');
587 | }
588 |
589 | this.buf.push(''
590 | + ' } else {\n'
591 | + ' var $$l = 0;\n'
592 | + ' for (var ' + each.key + ' in ' + each.obj + ') {\n'
593 | + ' $$l++;'
594 | + ' if (' + each.obj + '.hasOwnProperty(' + each.key + ')){'
595 | + ' var ' + each.val + ' = ' + each.obj + '[' + each.key + '];\n');
596 |
597 | this.visit(each.block);
598 |
599 | this.buf.push(' }\n');
600 |
601 | this.buf.push(' }\n');
602 | if (each.alternative) {
603 | this.buf.push(' if ($$l === 0) {');
604 | this.visit(each.alternative);
605 | this.buf.push(' }');
606 | }
607 | this.buf.push(' }\n}).call(this);\n');
608 | },
609 |
610 | /**
611 | * Visit `attrs`.
612 | *
613 | * @param {Array} attrs
614 | * @api public
615 | */
616 |
617 | visitAttributes: function(attrs){
618 | var val = this.attrs(attrs);
619 | if (val.inherits) {
620 | this.buf.push("buf.push(attrs(merge({ " + val.buf +
621 | " }, attributes), merge(" + val.escaped + ", escaped, true)));");
622 | } else if (val.constant) {
623 | eval('var buf={' + val.buf + '};');
624 | this.buffer(runtime.attrs(buf, JSON.parse(val.escaped)), true);
625 | } else {
626 | this.buf.push("buf.push(attrs({ " + val.buf + " }, " + val.escaped + "));");
627 | }
628 | },
629 |
630 | /**
631 | * Compile attributes.
632 | */
633 |
634 | attrs: function(attrs){
635 | var buf = []
636 | , classes = []
637 | , escaped = {}
638 | , constant = attrs.every(function(attr){ return isConstant(attr.val) })
639 | , inherits = false;
640 |
641 | if (this.terse) buf.push('terse: true');
642 |
643 | attrs.forEach(function(attr){
644 | if (attr.name == 'attributes') return inherits = true;
645 | escaped[attr.name] = attr.escaped;
646 | if (attr.name == 'class') {
647 | classes.push('(' + attr.val + ')');
648 | } else {
649 | var pair = "'" + attr.name + "':(" + attr.val + ')';
650 | buf.push(pair);
651 | }
652 | });
653 |
654 | if (classes.length) {
655 | classes = classes.join(" + ' ' + ");
656 | buf.push("class: " + classes);
657 | }
658 |
659 | return {
660 | buf: buf.join(', ').replace('class:', '"class":'),
661 | escaped: JSON.stringify(escaped),
662 | inherits: inherits,
663 | constant: constant
664 | };
665 | }
666 | };
667 |
668 | /**
669 | * Check if expression can be evaluated to a constant
670 | *
671 | * @param {String} expression
672 | * @return {Boolean}
673 | * @api private
674 | */
675 |
676 | function isConstant(val){
677 | // Check strings/literals
678 | if (/^ *("([^"\\]*(\\.[^"\\]*)*)"|'([^'\\]*(\\.[^'\\]*)*)'|true|false|null|undefined) *$/i.test(val))
679 | return true;
680 |
681 | // Check numbers
682 | if (!isNaN(Number(val)))
683 | return true;
684 |
685 | // Check arrays
686 | var matches;
687 | if (matches = /^ *\[(.*)\] *$/.exec(val))
688 | return matches[1].split(',').every(isConstant);
689 |
690 | return false;
691 | }
692 |
693 | /**
694 | * Escape the given string of `html`.
695 | *
696 | * @param {String} html
697 | * @return {String}
698 | * @api private
699 | */
700 |
701 | function escape(html){
702 | return String(html)
703 | .replace(/&(?!\w+;)/g, '&')
704 | .replace(//g, '>')
706 | .replace(/"/g, '"');
707 | }
708 |
709 | }); // module: compiler.js
710 |
711 | require.register("doctypes.js", function(module, exports, require){
712 |
713 | /*!
714 | * Jade - doctypes
715 | * Copyright(c) 2010 TJ Holowaychuk
716 | * MIT Licensed
717 | */
718 |
719 | module.exports = {
720 | '5': ''
721 | , 'default': ''
722 | , 'xml': ''
723 | , 'transitional': ''
724 | , 'strict': ''
725 | , 'frameset': ''
726 | , '1.1': ''
727 | , 'basic': ''
728 | , 'mobile': ''
729 | };
730 | }); // module: doctypes.js
731 |
732 | require.register("filters.js", function(module, exports, require){
733 |
734 | /*!
735 | * Jade - filters
736 | * Copyright(c) 2010 TJ Holowaychuk
737 | * MIT Licensed
738 | */
739 |
740 | module.exports = {
741 |
742 | /**
743 | * Wrap text with CDATA block.
744 | */
745 |
746 | cdata: function(str){
747 | return '';
748 | },
749 |
750 | /**
751 | * Transform sass to css, wrapped in style tags.
752 | */
753 |
754 | sass: function(str){
755 | str = str.replace(/\\n/g, '\n');
756 | var sass = require('sass').render(str).replace(/\n/g, '\\n');
757 | return '';
758 | },
759 |
760 | /**
761 | * Transform stylus to css, wrapped in style tags.
762 | */
763 |
764 | stylus: function(str, options){
765 | var ret;
766 | str = str.replace(/\\n/g, '\n');
767 | var stylus = require('stylus');
768 | stylus(str, options).render(function(err, css){
769 | if (err) throw err;
770 | ret = css.replace(/\n/g, '\\n');
771 | });
772 | return '';
773 | },
774 |
775 | /**
776 | * Transform less to css, wrapped in style tags.
777 | */
778 |
779 | less: function(str){
780 | var ret;
781 | str = str.replace(/\\n/g, '\n');
782 | require('less').render(str, function(err, css){
783 | if (err) throw err;
784 | ret = '';
785 | });
786 | return ret;
787 | },
788 |
789 | /**
790 | * Transform markdown to html.
791 | */
792 |
793 | markdown: function(str){
794 | var md;
795 |
796 | // support markdown / discount
797 | try {
798 | md = require('markdown');
799 | } catch (err){
800 | try {
801 | md = require('discount');
802 | } catch (err) {
803 | try {
804 | md = require('markdown-js');
805 | } catch (err) {
806 | try {
807 | md = require('marked');
808 | } catch (err) {
809 | throw new
810 | Error('Cannot find markdown library, install markdown, discount, or marked.');
811 | }
812 | }
813 | }
814 | }
815 |
816 | str = str.replace(/\\n/g, '\n');
817 | return md.parse(str).replace(/\n/g, '\\n').replace(/'/g,''');
818 | },
819 |
820 | /**
821 | * Transform coffeescript to javascript.
822 | */
823 |
824 | coffeescript: function(str){
825 | var js = require('coffee-script').compile(str).replace(/\\/g, '\\\\').replace(/\n/g, '\\n');
826 | return '';
827 | }
828 | };
829 |
830 | }); // module: filters.js
831 |
832 | require.register("inline-tags.js", function(module, exports, require){
833 |
834 | /*!
835 | * Jade - inline tags
836 | * Copyright(c) 2010 TJ Holowaychuk
837 | * MIT Licensed
838 | */
839 |
840 | module.exports = [
841 | 'a'
842 | , 'abbr'
843 | , 'acronym'
844 | , 'b'
845 | , 'br'
846 | , 'code'
847 | , 'em'
848 | , 'font'
849 | , 'i'
850 | , 'img'
851 | , 'ins'
852 | , 'kbd'
853 | , 'map'
854 | , 'samp'
855 | , 'small'
856 | , 'span'
857 | , 'strong'
858 | , 'sub'
859 | , 'sup'
860 | ];
861 | }); // module: inline-tags.js
862 |
863 | require.register("jade.js", function(module, exports, require){
864 | /*!
865 | * Jade
866 | * Copyright(c) 2010 TJ Holowaychuk
867 | * MIT Licensed
868 | */
869 |
870 | /**
871 | * Module dependencies.
872 | */
873 |
874 | var Parser = require('./parser')
875 | , Lexer = require('./lexer')
876 | , Compiler = require('./compiler')
877 | , runtime = require('./runtime')
878 |
879 | /**
880 | * Library version.
881 | */
882 |
883 | exports.version = '0.27.6';
884 |
885 | /**
886 | * Expose self closing tags.
887 | */
888 |
889 | exports.selfClosing = require('./self-closing');
890 |
891 | /**
892 | * Default supported doctypes.
893 | */
894 |
895 | exports.doctypes = require('./doctypes');
896 |
897 | /**
898 | * Text filters.
899 | */
900 |
901 | exports.filters = require('./filters');
902 |
903 | /**
904 | * Utilities.
905 | */
906 |
907 | exports.utils = require('./utils');
908 |
909 | /**
910 | * Expose `Compiler`.
911 | */
912 |
913 | exports.Compiler = Compiler;
914 |
915 | /**
916 | * Expose `Parser`.
917 | */
918 |
919 | exports.Parser = Parser;
920 |
921 | /**
922 | * Expose `Lexer`.
923 | */
924 |
925 | exports.Lexer = Lexer;
926 |
927 | /**
928 | * Nodes.
929 | */
930 |
931 | exports.nodes = require('./nodes');
932 |
933 | /**
934 | * Jade runtime helpers.
935 | */
936 |
937 | exports.runtime = runtime;
938 |
939 | /**
940 | * Template function cache.
941 | */
942 |
943 | exports.cache = {};
944 |
945 | /**
946 | * Parse the given `str` of jade and return a function body.
947 | *
948 | * @param {String} str
949 | * @param {Object} options
950 | * @return {String}
951 | * @api private
952 | */
953 |
954 | function parse(str, options){
955 | try {
956 | // Parse
957 | var parser = new Parser(str, options.filename, options);
958 |
959 | // Compile
960 | var compiler = new (options.compiler || Compiler)(parser.parse(), options)
961 | , js = compiler.compile();
962 |
963 | // Debug compiler
964 | if (options.debug) {
965 | console.error('\nCompiled Function:\n\n\033[90m%s\033[0m', js.replace(/^/gm, ' '));
966 | }
967 |
968 | return ''
969 | + 'var buf = [];\n'
970 | + (options.self
971 | ? 'var self = locals || {};\n' + js
972 | : 'with (locals || {}) {\n' + js + '\n}\n')
973 | + 'return buf.join("");';
974 | } catch (err) {
975 | parser = parser.context();
976 | runtime.rethrow(err, parser.filename, parser.lexer.lineno);
977 | }
978 | }
979 |
980 | /**
981 | * Strip any UTF-8 BOM off of the start of `str`, if it exists.
982 | *
983 | * @param {String} str
984 | * @return {String}
985 | * @api private
986 | */
987 |
988 | function stripBOM(str){
989 | return 0xFEFF == str.charCodeAt(0)
990 | ? str.substring(1)
991 | : str;
992 | }
993 |
994 | /**
995 | * Compile a `Function` representation of the given jade `str`.
996 | *
997 | * Options:
998 | *
999 | * - `compileDebug` when `false` debugging code is stripped from the compiled template
1000 | * - `client` when `true` the helper functions `escape()` etc will reference `jade.escape()`
1001 | * for use with the Jade client-side runtime.js
1002 | *
1003 | * @param {String} str
1004 | * @param {Options} options
1005 | * @return {Function}
1006 | * @api public
1007 | */
1008 |
1009 | exports.compile = function(str, options){
1010 | var options = options || {}
1011 | , client = options.client
1012 | , filename = options.filename
1013 | ? JSON.stringify(options.filename)
1014 | : 'undefined'
1015 | , fn;
1016 |
1017 | str = stripBOM(String(str));
1018 |
1019 | if (options.compileDebug !== false) {
1020 | fn = [
1021 | 'var __jade = [{ lineno: 1, filename: ' + filename + ' }];'
1022 | , 'try {'
1023 | , parse(str, options)
1024 | , '} catch (err) {'
1025 | , ' rethrow(err, __jade[0].filename, __jade[0].lineno);'
1026 | , '}'
1027 | ].join('\n');
1028 | } else {
1029 | fn = parse(str, options);
1030 | }
1031 |
1032 | if (client) {
1033 | fn = 'attrs = attrs || jade.attrs; escape = escape || jade.escape; rethrow = rethrow || jade.rethrow; merge = merge || jade.merge;\n' + fn;
1034 | }
1035 |
1036 | fn = new Function('locals, attrs, escape, rethrow, merge', fn);
1037 |
1038 | if (client) return fn;
1039 |
1040 | return function(locals){
1041 | return fn(locals, runtime.attrs, runtime.escape, runtime.rethrow, runtime.merge);
1042 | };
1043 | };
1044 |
1045 | /**
1046 | * Render the given `str` of jade and invoke
1047 | * the callback `fn(err, str)`.
1048 | *
1049 | * Options:
1050 | *
1051 | * - `cache` enable template caching
1052 | * - `filename` filename required for `include` / `extends` and caching
1053 | *
1054 | * @param {String} str
1055 | * @param {Object|Function} options or fn
1056 | * @param {Function} fn
1057 | * @api public
1058 | */
1059 |
1060 | exports.render = function(str, options, fn){
1061 | // swap args
1062 | if ('function' == typeof options) {
1063 | fn = options, options = {};
1064 | }
1065 |
1066 | // cache requires .filename
1067 | if (options.cache && !options.filename) {
1068 | return fn(new Error('the "filename" option is required for caching'));
1069 | }
1070 |
1071 | try {
1072 | var path = options.filename;
1073 | var tmpl = options.cache
1074 | ? exports.cache[path] || (exports.cache[path] = exports.compile(str, options))
1075 | : exports.compile(str, options);
1076 | fn(null, tmpl(options));
1077 | } catch (err) {
1078 | fn(err);
1079 | }
1080 | };
1081 |
1082 | /**
1083 | * Render a Jade file at the given `path` and callback `fn(err, str)`.
1084 | *
1085 | * @param {String} path
1086 | * @param {Object|Function} options or callback
1087 | * @param {Function} fn
1088 | * @api public
1089 | */
1090 |
1091 | exports.renderFile = function(path, options, fn){
1092 | var key = path + ':string';
1093 |
1094 | if ('function' == typeof options) {
1095 | fn = options, options = {};
1096 | }
1097 |
1098 | try {
1099 | options.filename = path;
1100 | var str = options.cache
1101 | ? exports.cache[key] || (exports.cache[key] = fs.readFileSync(path, 'utf8'))
1102 | : fs.readFileSync(path, 'utf8');
1103 | exports.render(str, options, fn);
1104 | } catch (err) {
1105 | fn(err);
1106 | }
1107 | };
1108 |
1109 | /**
1110 | * Express support.
1111 | */
1112 |
1113 | exports.__express = exports.renderFile;
1114 |
1115 | }); // module: jade.js
1116 |
1117 | require.register("lexer.js", function(module, exports, require){
1118 | /*!
1119 | * Jade - Lexer
1120 | * Copyright(c) 2010 TJ Holowaychuk
1121 | * MIT Licensed
1122 | */
1123 |
1124 | var utils = require('./utils');
1125 |
1126 | /**
1127 | * Initialize `Lexer` with the given `str`.
1128 | *
1129 | * Options:
1130 | *
1131 | * - `colons` allow colons for attr delimiters
1132 | *
1133 | * @param {String} str
1134 | * @param {Object} options
1135 | * @api private
1136 | */
1137 |
1138 | var Lexer = module.exports = function Lexer(str, options) {
1139 | options = options || {};
1140 | this.input = str.replace(/\r\n|\r/g, '\n');
1141 | this.colons = options.colons;
1142 | this.deferredTokens = [];
1143 | this.lastIndents = 0;
1144 | this.lineno = 1;
1145 | this.stash = [];
1146 | this.indentStack = [];
1147 | this.indentRe = null;
1148 | this.pipeless = false;
1149 | };
1150 |
1151 | /**
1152 | * Lexer prototype.
1153 | */
1154 |
1155 | Lexer.prototype = {
1156 |
1157 | /**
1158 | * Construct a token with the given `type` and `val`.
1159 | *
1160 | * @param {String} type
1161 | * @param {String} val
1162 | * @return {Object}
1163 | * @api private
1164 | */
1165 |
1166 | tok: function(type, val){
1167 | return {
1168 | type: type
1169 | , line: this.lineno
1170 | , val: val
1171 | }
1172 | },
1173 |
1174 | /**
1175 | * Consume the given `len` of input.
1176 | *
1177 | * @param {Number} len
1178 | * @api private
1179 | */
1180 |
1181 | consume: function(len){
1182 | this.input = this.input.substr(len);
1183 | },
1184 |
1185 | /**
1186 | * Scan for `type` with the given `regexp`.
1187 | *
1188 | * @param {String} type
1189 | * @param {RegExp} regexp
1190 | * @return {Object}
1191 | * @api private
1192 | */
1193 |
1194 | scan: function(regexp, type){
1195 | var captures;
1196 | if (captures = regexp.exec(this.input)) {
1197 | this.consume(captures[0].length);
1198 | return this.tok(type, captures[1]);
1199 | }
1200 | },
1201 |
1202 | /**
1203 | * Defer the given `tok`.
1204 | *
1205 | * @param {Object} tok
1206 | * @api private
1207 | */
1208 |
1209 | defer: function(tok){
1210 | this.deferredTokens.push(tok);
1211 | },
1212 |
1213 | /**
1214 | * Lookahead `n` tokens.
1215 | *
1216 | * @param {Number} n
1217 | * @return {Object}
1218 | * @api private
1219 | */
1220 |
1221 | lookahead: function(n){
1222 | var fetch = n - this.stash.length;
1223 | while (fetch-- > 0) this.stash.push(this.next());
1224 | return this.stash[--n];
1225 | },
1226 |
1227 | /**
1228 | * Return the indexOf `start` / `end` delimiters.
1229 | *
1230 | * @param {String} start
1231 | * @param {String} end
1232 | * @return {Number}
1233 | * @api private
1234 | */
1235 |
1236 | indexOfDelimiters: function(start, end){
1237 | var str = this.input
1238 | , nstart = 0
1239 | , nend = 0
1240 | , pos = 0;
1241 | for (var i = 0, len = str.length; i < len; ++i) {
1242 | if (start == str.charAt(i)) {
1243 | ++nstart;
1244 | } else if (end == str.charAt(i)) {
1245 | if (++nend == nstart) {
1246 | pos = i;
1247 | break;
1248 | }
1249 | }
1250 | }
1251 | return pos;
1252 | },
1253 |
1254 | /**
1255 | * Stashed token.
1256 | */
1257 |
1258 | stashed: function() {
1259 | return this.stash.length
1260 | && this.stash.shift();
1261 | },
1262 |
1263 | /**
1264 | * Deferred token.
1265 | */
1266 |
1267 | deferred: function() {
1268 | return this.deferredTokens.length
1269 | && this.deferredTokens.shift();
1270 | },
1271 |
1272 | /**
1273 | * end-of-source.
1274 | */
1275 |
1276 | eos: function() {
1277 | if (this.input.length) return;
1278 | if (this.indentStack.length) {
1279 | this.indentStack.shift();
1280 | return this.tok('outdent');
1281 | } else {
1282 | return this.tok('eos');
1283 | }
1284 | },
1285 |
1286 | /**
1287 | * Blank line.
1288 | */
1289 |
1290 | blank: function() {
1291 | var captures;
1292 | if (captures = /^\n *\n/.exec(this.input)) {
1293 | this.consume(captures[0].length - 1);
1294 |
1295 | ++this.lineno;
1296 | if (this.pipeless) return this.tok('text', '');
1297 | return this.next();
1298 | }
1299 | },
1300 |
1301 | /**
1302 | * Comment.
1303 | */
1304 |
1305 | comment: function() {
1306 | var captures;
1307 | if (captures = /^ *\/\/(-)?([^\n]*)/.exec(this.input)) {
1308 | this.consume(captures[0].length);
1309 | var tok = this.tok('comment', captures[2]);
1310 | tok.buffer = '-' != captures[1];
1311 | return tok;
1312 | }
1313 | },
1314 |
1315 | /**
1316 | * Interpolated tag.
1317 | */
1318 |
1319 | interpolation: function() {
1320 | var captures;
1321 | if (captures = /^#\{(.*?)\}/.exec(this.input)) {
1322 | this.consume(captures[0].length);
1323 | return this.tok('interpolation', captures[1]);
1324 | }
1325 | },
1326 |
1327 | /**
1328 | * Tag.
1329 | */
1330 |
1331 | tag: function() {
1332 | var captures;
1333 | if (captures = /^(\w[-:\w]*)(\/?)/.exec(this.input)) {
1334 | this.consume(captures[0].length);
1335 | var tok, name = captures[1];
1336 | if (':' == name[name.length - 1]) {
1337 | name = name.slice(0, -1);
1338 | tok = this.tok('tag', name);
1339 | this.defer(this.tok(':'));
1340 | while (' ' == this.input[0]) this.input = this.input.substr(1);
1341 | } else {
1342 | tok = this.tok('tag', name);
1343 | }
1344 | tok.selfClosing = !! captures[2];
1345 | return tok;
1346 | }
1347 | },
1348 |
1349 | /**
1350 | * Filter.
1351 | */
1352 |
1353 | filter: function() {
1354 | return this.scan(/^:(\w+)/, 'filter');
1355 | },
1356 |
1357 | /**
1358 | * Doctype.
1359 | */
1360 |
1361 | doctype: function() {
1362 | return this.scan(/^(?:!!!|doctype) *([^\n]+)?/, 'doctype');
1363 | },
1364 |
1365 | /**
1366 | * Id.
1367 | */
1368 |
1369 | id: function() {
1370 | return this.scan(/^#([\w-]+)/, 'id');
1371 | },
1372 |
1373 | /**
1374 | * Class.
1375 | */
1376 |
1377 | className: function() {
1378 | return this.scan(/^\.([\w-]+)/, 'class');
1379 | },
1380 |
1381 | /**
1382 | * Text.
1383 | */
1384 |
1385 | text: function() {
1386 | return this.scan(/^(?:\| ?| ?)?([^\n]+)/, 'text');
1387 | },
1388 |
1389 | /**
1390 | * Extends.
1391 | */
1392 |
1393 | "extends": function() {
1394 | return this.scan(/^extends? +([^\n]+)/, 'extends');
1395 | },
1396 |
1397 | /**
1398 | * Block prepend.
1399 | */
1400 |
1401 | prepend: function() {
1402 | var captures;
1403 | if (captures = /^prepend +([^\n]+)/.exec(this.input)) {
1404 | this.consume(captures[0].length);
1405 | var mode = 'prepend'
1406 | , name = captures[1]
1407 | , tok = this.tok('block', name);
1408 | tok.mode = mode;
1409 | return tok;
1410 | }
1411 | },
1412 |
1413 | /**
1414 | * Block append.
1415 | */
1416 |
1417 | append: function() {
1418 | var captures;
1419 | if (captures = /^append +([^\n]+)/.exec(this.input)) {
1420 | this.consume(captures[0].length);
1421 | var mode = 'append'
1422 | , name = captures[1]
1423 | , tok = this.tok('block', name);
1424 | tok.mode = mode;
1425 | return tok;
1426 | }
1427 | },
1428 |
1429 | /**
1430 | * Block.
1431 | */
1432 |
1433 | block: function() {
1434 | var captures;
1435 | if (captures = /^block\b *(?:(prepend|append) +)?([^\n]*)/.exec(this.input)) {
1436 | this.consume(captures[0].length);
1437 | var mode = captures[1] || 'replace'
1438 | , name = captures[2]
1439 | , tok = this.tok('block', name);
1440 |
1441 | tok.mode = mode;
1442 | return tok;
1443 | }
1444 | },
1445 |
1446 | /**
1447 | * Yield.
1448 | */
1449 |
1450 | yield: function() {
1451 | return this.scan(/^yield */, 'yield');
1452 | },
1453 |
1454 | /**
1455 | * Include.
1456 | */
1457 |
1458 | include: function() {
1459 | return this.scan(/^include +([^\n]+)/, 'include');
1460 | },
1461 |
1462 | /**
1463 | * Case.
1464 | */
1465 |
1466 | "case": function() {
1467 | return this.scan(/^case +([^\n]+)/, 'case');
1468 | },
1469 |
1470 | /**
1471 | * When.
1472 | */
1473 |
1474 | when: function() {
1475 | return this.scan(/^when +([^:\n]+)/, 'when');
1476 | },
1477 |
1478 | /**
1479 | * Default.
1480 | */
1481 |
1482 | "default": function() {
1483 | return this.scan(/^default */, 'default');
1484 | },
1485 |
1486 | /**
1487 | * Assignment.
1488 | */
1489 |
1490 | assignment: function() {
1491 | var captures;
1492 | if (captures = /^(\w+) += *([^;\n]+)( *;? *)/.exec(this.input)) {
1493 | this.consume(captures[0].length);
1494 | var name = captures[1]
1495 | , val = captures[2];
1496 | return this.tok('code', 'var ' + name + ' = (' + val + ');');
1497 | }
1498 | },
1499 |
1500 | /**
1501 | * Call mixin.
1502 | */
1503 |
1504 | call: function(){
1505 | var captures;
1506 | if (captures = /^\+([-\w]+)/.exec(this.input)) {
1507 | this.consume(captures[0].length);
1508 | var tok = this.tok('call', captures[1]);
1509 |
1510 | // Check for args (not attributes)
1511 | if (captures = /^ *\((.*?)\)/.exec(this.input)) {
1512 | if (!/^ *[-\w]+ *=/.test(captures[1])) {
1513 | this.consume(captures[0].length);
1514 | tok.args = captures[1];
1515 | }
1516 | }
1517 |
1518 | return tok;
1519 | }
1520 | },
1521 |
1522 | /**
1523 | * Mixin.
1524 | */
1525 |
1526 | mixin: function(){
1527 | var captures;
1528 | if (captures = /^mixin +([-\w]+)(?: *\((.*)\))?/.exec(this.input)) {
1529 | this.consume(captures[0].length);
1530 | var tok = this.tok('mixin', captures[1]);
1531 | tok.args = captures[2];
1532 | return tok;
1533 | }
1534 | },
1535 |
1536 | /**
1537 | * Conditional.
1538 | */
1539 |
1540 | conditional: function() {
1541 | var captures;
1542 | if (captures = /^(if|unless|else if|else)\b([^\n]*)/.exec(this.input)) {
1543 | this.consume(captures[0].length);
1544 | var type = captures[1]
1545 | , js = captures[2];
1546 |
1547 | switch (type) {
1548 | case 'if': js = 'if (' + js + ')'; break;
1549 | case 'unless': js = 'if (!(' + js + '))'; break;
1550 | case 'else if': js = 'else if (' + js + ')'; break;
1551 | case 'else': js = 'else'; break;
1552 | }
1553 |
1554 | return this.tok('code', js);
1555 | }
1556 | },
1557 |
1558 | /**
1559 | * While.
1560 | */
1561 |
1562 | "while": function() {
1563 | var captures;
1564 | if (captures = /^while +([^\n]+)/.exec(this.input)) {
1565 | this.consume(captures[0].length);
1566 | return this.tok('code', 'while (' + captures[1] + ')');
1567 | }
1568 | },
1569 |
1570 | /**
1571 | * Each.
1572 | */
1573 |
1574 | each: function() {
1575 | var captures;
1576 | if (captures = /^(?:- *)?(?:each|for) +(\w+)(?: *, *(\w+))? * in *([^\n]+)/.exec(this.input)) {
1577 | this.consume(captures[0].length);
1578 | var tok = this.tok('each', captures[1]);
1579 | tok.key = captures[2] || '$index';
1580 | tok.code = captures[3];
1581 | return tok;
1582 | }
1583 | },
1584 |
1585 | /**
1586 | * Code.
1587 | */
1588 |
1589 | code: function() {
1590 | var captures;
1591 | if (captures = /^(!?=|-)([^\n]+)/.exec(this.input)) {
1592 | this.consume(captures[0].length);
1593 | var flags = captures[1];
1594 | captures[1] = captures[2];
1595 | var tok = this.tok('code', captures[1]);
1596 | tok.escape = flags.charAt(0) === '=';
1597 | tok.buffer = flags.charAt(0) === '=' || flags.charAt(1) === '=';
1598 | return tok;
1599 | }
1600 | },
1601 |
1602 | /**
1603 | * Attributes.
1604 | */
1605 |
1606 | attrs: function() {
1607 | if ('(' == this.input.charAt(0)) {
1608 | var index = this.indexOfDelimiters('(', ')')
1609 | , str = this.input.substr(1, index-1)
1610 | , tok = this.tok('attrs')
1611 | , len = str.length
1612 | , colons = this.colons
1613 | , states = ['key']
1614 | , escapedAttr
1615 | , key = ''
1616 | , val = ''
1617 | , quote
1618 | , c
1619 | , p;
1620 |
1621 | function state(){
1622 | return states[states.length - 1];
1623 | }
1624 |
1625 | function interpolate(attr) {
1626 | return attr.replace(/(\\)?#\{([^}]+)\}/g, function(_, escape, expr){
1627 | return escape
1628 | ? _
1629 | : quote + " + (" + expr + ") + " + quote;
1630 | });
1631 | }
1632 |
1633 | this.consume(index + 1);
1634 | tok.attrs = {};
1635 | tok.escaped = {};
1636 |
1637 | function parse(c) {
1638 | var real = c;
1639 | // TODO: remove when people fix ":"
1640 | if (colons && ':' == c) c = '=';
1641 | switch (c) {
1642 | case ',':
1643 | case '\n':
1644 | switch (state()) {
1645 | case 'expr':
1646 | case 'array':
1647 | case 'string':
1648 | case 'object':
1649 | val += c;
1650 | break;
1651 | default:
1652 | states.push('key');
1653 | val = val.trim();
1654 | key = key.trim();
1655 | if ('' == key) return;
1656 | key = key.replace(/^['"]|['"]$/g, '').replace('!', '');
1657 | tok.escaped[key] = escapedAttr;
1658 | tok.attrs[key] = '' == val
1659 | ? true
1660 | : interpolate(val);
1661 | key = val = '';
1662 | }
1663 | break;
1664 | case '=':
1665 | switch (state()) {
1666 | case 'key char':
1667 | key += real;
1668 | break;
1669 | case 'val':
1670 | case 'expr':
1671 | case 'array':
1672 | case 'string':
1673 | case 'object':
1674 | val += real;
1675 | break;
1676 | default:
1677 | escapedAttr = '!' != p;
1678 | states.push('val');
1679 | }
1680 | break;
1681 | case '(':
1682 | if ('val' == state()
1683 | || 'expr' == state()) states.push('expr');
1684 | val += c;
1685 | break;
1686 | case ')':
1687 | if ('expr' == state()
1688 | || 'val' == state()) states.pop();
1689 | val += c;
1690 | break;
1691 | case '{':
1692 | if ('val' == state()) states.push('object');
1693 | val += c;
1694 | break;
1695 | case '}':
1696 | if ('object' == state()) states.pop();
1697 | val += c;
1698 | break;
1699 | case '[':
1700 | if ('val' == state()) states.push('array');
1701 | val += c;
1702 | break;
1703 | case ']':
1704 | if ('array' == state()) states.pop();
1705 | val += c;
1706 | break;
1707 | case '"':
1708 | case "'":
1709 | switch (state()) {
1710 | case 'key':
1711 | states.push('key char');
1712 | break;
1713 | case 'key char':
1714 | states.pop();
1715 | break;
1716 | case 'string':
1717 | if (c == quote) states.pop();
1718 | val += c;
1719 | break;
1720 | default:
1721 | states.push('string');
1722 | val += c;
1723 | quote = c;
1724 | }
1725 | break;
1726 | case '':
1727 | break;
1728 | default:
1729 | switch (state()) {
1730 | case 'key':
1731 | case 'key char':
1732 | key += c;
1733 | break;
1734 | default:
1735 | val += c;
1736 | }
1737 | }
1738 | p = c;
1739 | }
1740 |
1741 | for (var i = 0; i < len; ++i) {
1742 | parse(str.charAt(i));
1743 | }
1744 |
1745 | parse(',');
1746 |
1747 | if ('/' == this.input.charAt(0)) {
1748 | this.consume(1);
1749 | tok.selfClosing = true;
1750 | }
1751 |
1752 | return tok;
1753 | }
1754 | },
1755 |
1756 | /**
1757 | * Indent | Outdent | Newline.
1758 | */
1759 |
1760 | indent: function() {
1761 | var captures, re;
1762 |
1763 | // established regexp
1764 | if (this.indentRe) {
1765 | captures = this.indentRe.exec(this.input);
1766 | // determine regexp
1767 | } else {
1768 | // tabs
1769 | re = /^\n(\t*) */;
1770 | captures = re.exec(this.input);
1771 |
1772 | // spaces
1773 | if (captures && !captures[1].length) {
1774 | re = /^\n( *)/;
1775 | captures = re.exec(this.input);
1776 | }
1777 |
1778 | // established
1779 | if (captures && captures[1].length) this.indentRe = re;
1780 | }
1781 |
1782 | if (captures) {
1783 | var tok
1784 | , indents = captures[1].length;
1785 |
1786 | ++this.lineno;
1787 | this.consume(indents + 1);
1788 |
1789 | if (' ' == this.input[0] || '\t' == this.input[0]) {
1790 | throw new Error('Invalid indentation, you can use tabs or spaces but not both');
1791 | }
1792 |
1793 | // blank line
1794 | if ('\n' == this.input[0]) return this.tok('newline');
1795 |
1796 | // outdent
1797 | if (this.indentStack.length && indents < this.indentStack[0]) {
1798 | while (this.indentStack.length && this.indentStack[0] > indents) {
1799 | this.stash.push(this.tok('outdent'));
1800 | this.indentStack.shift();
1801 | }
1802 | tok = this.stash.pop();
1803 | // indent
1804 | } else if (indents && indents != this.indentStack[0]) {
1805 | this.indentStack.unshift(indents);
1806 | tok = this.tok('indent', indents);
1807 | // newline
1808 | } else {
1809 | tok = this.tok('newline');
1810 | }
1811 |
1812 | return tok;
1813 | }
1814 | },
1815 |
1816 | /**
1817 | * Pipe-less text consumed only when
1818 | * pipeless is true;
1819 | */
1820 |
1821 | pipelessText: function() {
1822 | if (this.pipeless) {
1823 | if ('\n' == this.input[0]) return;
1824 | var i = this.input.indexOf('\n');
1825 | if (-1 == i) i = this.input.length;
1826 | var str = this.input.substr(0, i);
1827 | this.consume(str.length);
1828 | return this.tok('text', str);
1829 | }
1830 | },
1831 |
1832 | /**
1833 | * ':'
1834 | */
1835 |
1836 | colon: function() {
1837 | return this.scan(/^: */, ':');
1838 | },
1839 |
1840 | /**
1841 | * Return the next token object, or those
1842 | * previously stashed by lookahead.
1843 | *
1844 | * @return {Object}
1845 | * @api private
1846 | */
1847 |
1848 | advance: function(){
1849 | return this.stashed()
1850 | || this.next();
1851 | },
1852 |
1853 | /**
1854 | * Return the next token object.
1855 | *
1856 | * @return {Object}
1857 | * @api private
1858 | */
1859 |
1860 | next: function() {
1861 | return this.deferred()
1862 | || this.blank()
1863 | || this.eos()
1864 | || this.pipelessText()
1865 | || this.yield()
1866 | || this.doctype()
1867 | || this.interpolation()
1868 | || this["case"]()
1869 | || this.when()
1870 | || this["default"]()
1871 | || this["extends"]()
1872 | || this.append()
1873 | || this.prepend()
1874 | || this.block()
1875 | || this.include()
1876 | || this.mixin()
1877 | || this.call()
1878 | || this.conditional()
1879 | || this.each()
1880 | || this["while"]()
1881 | || this.assignment()
1882 | || this.tag()
1883 | || this.filter()
1884 | || this.code()
1885 | || this.id()
1886 | || this.className()
1887 | || this.attrs()
1888 | || this.indent()
1889 | || this.comment()
1890 | || this.colon()
1891 | || this.text();
1892 | }
1893 | };
1894 |
1895 | }); // module: lexer.js
1896 |
1897 | require.register("nodes/attrs.js", function(module, exports, require){
1898 |
1899 | /*!
1900 | * Jade - nodes - Attrs
1901 | * Copyright(c) 2010 TJ Holowaychuk
1902 | * MIT Licensed
1903 | */
1904 |
1905 | /**
1906 | * Module dependencies.
1907 | */
1908 |
1909 | var Node = require('./node'),
1910 | Block = require('./block');
1911 |
1912 | /**
1913 | * Initialize a `Attrs` node.
1914 | *
1915 | * @api public
1916 | */
1917 |
1918 | var Attrs = module.exports = function Attrs() {
1919 | this.attrs = [];
1920 | };
1921 |
1922 | /**
1923 | * Inherit from `Node`.
1924 | */
1925 |
1926 | Attrs.prototype = new Node;
1927 | Attrs.prototype.constructor = Attrs;
1928 |
1929 |
1930 | /**
1931 | * Set attribute `name` to `val`, keep in mind these become
1932 | * part of a raw js object literal, so to quote a value you must
1933 | * '"quote me"', otherwise or example 'user.name' is literal JavaScript.
1934 | *
1935 | * @param {String} name
1936 | * @param {String} val
1937 | * @param {Boolean} escaped
1938 | * @return {Tag} for chaining
1939 | * @api public
1940 | */
1941 |
1942 | Attrs.prototype.setAttribute = function(name, val, escaped){
1943 | this.attrs.push({ name: name, val: val, escaped: escaped });
1944 | return this;
1945 | };
1946 |
1947 | /**
1948 | * Remove attribute `name` when present.
1949 | *
1950 | * @param {String} name
1951 | * @api public
1952 | */
1953 |
1954 | Attrs.prototype.removeAttribute = function(name){
1955 | for (var i = 0, len = this.attrs.length; i < len; ++i) {
1956 | if (this.attrs[i] && this.attrs[i].name == name) {
1957 | delete this.attrs[i];
1958 | }
1959 | }
1960 | };
1961 |
1962 | /**
1963 | * Get attribute value by `name`.
1964 | *
1965 | * @param {String} name
1966 | * @return {String}
1967 | * @api public
1968 | */
1969 |
1970 | Attrs.prototype.getAttribute = function(name){
1971 | for (var i = 0, len = this.attrs.length; i < len; ++i) {
1972 | if (this.attrs[i] && this.attrs[i].name == name) {
1973 | return this.attrs[i].val;
1974 | }
1975 | }
1976 | };
1977 |
1978 | }); // module: nodes/attrs.js
1979 |
1980 | require.register("nodes/block-comment.js", function(module, exports, require){
1981 |
1982 | /*!
1983 | * Jade - nodes - BlockComment
1984 | * Copyright(c) 2010 TJ Holowaychuk
1985 | * MIT Licensed
1986 | */
1987 |
1988 | /**
1989 | * Module dependencies.
1990 | */
1991 |
1992 | var Node = require('./node');
1993 |
1994 | /**
1995 | * Initialize a `BlockComment` with the given `block`.
1996 | *
1997 | * @param {String} val
1998 | * @param {Block} block
1999 | * @param {Boolean} buffer
2000 | * @api public
2001 | */
2002 |
2003 | var BlockComment = module.exports = function BlockComment(val, block, buffer) {
2004 | this.block = block;
2005 | this.val = val;
2006 | this.buffer = buffer;
2007 | };
2008 |
2009 | /**
2010 | * Inherit from `Node`.
2011 | */
2012 |
2013 | BlockComment.prototype = new Node;
2014 | BlockComment.prototype.constructor = BlockComment;
2015 |
2016 | }); // module: nodes/block-comment.js
2017 |
2018 | require.register("nodes/block.js", function(module, exports, require){
2019 |
2020 | /*!
2021 | * Jade - nodes - Block
2022 | * Copyright(c) 2010 TJ Holowaychuk
2023 | * MIT Licensed
2024 | */
2025 |
2026 | /**
2027 | * Module dependencies.
2028 | */
2029 |
2030 | var Node = require('./node');
2031 |
2032 | /**
2033 | * Initialize a new `Block` with an optional `node`.
2034 | *
2035 | * @param {Node} node
2036 | * @api public
2037 | */
2038 |
2039 | var Block = module.exports = function Block(node){
2040 | this.nodes = [];
2041 | if (node) this.push(node);
2042 | };
2043 |
2044 | /**
2045 | * Inherit from `Node`.
2046 | */
2047 |
2048 | Block.prototype = new Node;
2049 | Block.prototype.constructor = Block;
2050 |
2051 |
2052 | /**
2053 | * Block flag.
2054 | */
2055 |
2056 | Block.prototype.isBlock = true;
2057 |
2058 | /**
2059 | * Replace the nodes in `other` with the nodes
2060 | * in `this` block.
2061 | *
2062 | * @param {Block} other
2063 | * @api private
2064 | */
2065 |
2066 | Block.prototype.replace = function(other){
2067 | other.nodes = this.nodes;
2068 | };
2069 |
2070 | /**
2071 | * Pust the given `node`.
2072 | *
2073 | * @param {Node} node
2074 | * @return {Number}
2075 | * @api public
2076 | */
2077 |
2078 | Block.prototype.push = function(node){
2079 | return this.nodes.push(node);
2080 | };
2081 |
2082 | /**
2083 | * Check if this block is empty.
2084 | *
2085 | * @return {Boolean}
2086 | * @api public
2087 | */
2088 |
2089 | Block.prototype.isEmpty = function(){
2090 | return 0 == this.nodes.length;
2091 | };
2092 |
2093 | /**
2094 | * Unshift the given `node`.
2095 | *
2096 | * @param {Node} node
2097 | * @return {Number}
2098 | * @api public
2099 | */
2100 |
2101 | Block.prototype.unshift = function(node){
2102 | return this.nodes.unshift(node);
2103 | };
2104 |
2105 | /**
2106 | * Return the "last" block, or the first `yield` node.
2107 | *
2108 | * @return {Block}
2109 | * @api private
2110 | */
2111 |
2112 | Block.prototype.includeBlock = function(){
2113 | var ret = this
2114 | , node;
2115 |
2116 | for (var i = 0, len = this.nodes.length; i < len; ++i) {
2117 | node = this.nodes[i];
2118 | if (node.yield) return node;
2119 | else if (node.textOnly) continue;
2120 | else if (node.includeBlock) ret = node.includeBlock();
2121 | else if (node.block && !node.block.isEmpty()) ret = node.block.includeBlock();
2122 | if (ret.yield) return ret;
2123 | }
2124 |
2125 | return ret;
2126 | };
2127 |
2128 | /**
2129 | * Return a clone of this block.
2130 | *
2131 | * @return {Block}
2132 | * @api private
2133 | */
2134 |
2135 | Block.prototype.clone = function(){
2136 | var clone = new Block;
2137 | for (var i = 0, len = this.nodes.length; i < len; ++i) {
2138 | clone.push(this.nodes[i].clone());
2139 | }
2140 | return clone;
2141 | };
2142 |
2143 |
2144 | }); // module: nodes/block.js
2145 |
2146 | require.register("nodes/case.js", function(module, exports, require){
2147 |
2148 | /*!
2149 | * Jade - nodes - Case
2150 | * Copyright(c) 2010 TJ Holowaychuk
2151 | * MIT Licensed
2152 | */
2153 |
2154 | /**
2155 | * Module dependencies.
2156 | */
2157 |
2158 | var Node = require('./node');
2159 |
2160 | /**
2161 | * Initialize a new `Case` with `expr`.
2162 | *
2163 | * @param {String} expr
2164 | * @api public
2165 | */
2166 |
2167 | var Case = exports = module.exports = function Case(expr, block){
2168 | this.expr = expr;
2169 | this.block = block;
2170 | };
2171 |
2172 | /**
2173 | * Inherit from `Node`.
2174 | */
2175 |
2176 | Case.prototype = new Node;
2177 | Case.prototype.constructor = Case;
2178 |
2179 |
2180 | var When = exports.When = function When(expr, block){
2181 | this.expr = expr;
2182 | this.block = block;
2183 | this.debug = false;
2184 | };
2185 |
2186 | /**
2187 | * Inherit from `Node`.
2188 | */
2189 |
2190 | When.prototype = new Node;
2191 | When.prototype.constructor = When;
2192 |
2193 |
2194 |
2195 | }); // module: nodes/case.js
2196 |
2197 | require.register("nodes/code.js", function(module, exports, require){
2198 |
2199 | /*!
2200 | * Jade - nodes - Code
2201 | * Copyright(c) 2010 TJ Holowaychuk
2202 | * MIT Licensed
2203 | */
2204 |
2205 | /**
2206 | * Module dependencies.
2207 | */
2208 |
2209 | var Node = require('./node');
2210 |
2211 | /**
2212 | * Initialize a `Code` node with the given code `val`.
2213 | * Code may also be optionally buffered and escaped.
2214 | *
2215 | * @param {String} val
2216 | * @param {Boolean} buffer
2217 | * @param {Boolean} escape
2218 | * @api public
2219 | */
2220 |
2221 | var Code = module.exports = function Code(val, buffer, escape) {
2222 | this.val = val;
2223 | this.buffer = buffer;
2224 | this.escape = escape;
2225 | if (val.match(/^ *else/)) this.debug = false;
2226 | };
2227 |
2228 | /**
2229 | * Inherit from `Node`.
2230 | */
2231 |
2232 | Code.prototype = new Node;
2233 | Code.prototype.constructor = Code;
2234 |
2235 | }); // module: nodes/code.js
2236 |
2237 | require.register("nodes/comment.js", function(module, exports, require){
2238 |
2239 | /*!
2240 | * Jade - nodes - Comment
2241 | * Copyright(c) 2010 TJ Holowaychuk
2242 | * MIT Licensed
2243 | */
2244 |
2245 | /**
2246 | * Module dependencies.
2247 | */
2248 |
2249 | var Node = require('./node');
2250 |
2251 | /**
2252 | * Initialize a `Comment` with the given `val`, optionally `buffer`,
2253 | * otherwise the comment may render in the output.
2254 | *
2255 | * @param {String} val
2256 | * @param {Boolean} buffer
2257 | * @api public
2258 | */
2259 |
2260 | var Comment = module.exports = function Comment(val, buffer) {
2261 | this.val = val;
2262 | this.buffer = buffer;
2263 | };
2264 |
2265 | /**
2266 | * Inherit from `Node`.
2267 | */
2268 |
2269 | Comment.prototype = new Node;
2270 | Comment.prototype.constructor = Comment;
2271 |
2272 | }); // module: nodes/comment.js
2273 |
2274 | require.register("nodes/doctype.js", function(module, exports, require){
2275 |
2276 | /*!
2277 | * Jade - nodes - Doctype
2278 | * Copyright(c) 2010 TJ Holowaychuk
2279 | * MIT Licensed
2280 | */
2281 |
2282 | /**
2283 | * Module dependencies.
2284 | */
2285 |
2286 | var Node = require('./node');
2287 |
2288 | /**
2289 | * Initialize a `Doctype` with the given `val`.
2290 | *
2291 | * @param {String} val
2292 | * @api public
2293 | */
2294 |
2295 | var Doctype = module.exports = function Doctype(val) {
2296 | this.val = val;
2297 | };
2298 |
2299 | /**
2300 | * Inherit from `Node`.
2301 | */
2302 |
2303 | Doctype.prototype = new Node;
2304 | Doctype.prototype.constructor = Doctype;
2305 |
2306 | }); // module: nodes/doctype.js
2307 |
2308 | require.register("nodes/each.js", function(module, exports, require){
2309 |
2310 | /*!
2311 | * Jade - nodes - Each
2312 | * Copyright(c) 2010 TJ Holowaychuk
2313 | * MIT Licensed
2314 | */
2315 |
2316 | /**
2317 | * Module dependencies.
2318 | */
2319 |
2320 | var Node = require('./node');
2321 |
2322 | /**
2323 | * Initialize an `Each` node, representing iteration
2324 | *
2325 | * @param {String} obj
2326 | * @param {String} val
2327 | * @param {String} key
2328 | * @param {Block} block
2329 | * @api public
2330 | */
2331 |
2332 | var Each = module.exports = function Each(obj, val, key, block) {
2333 | this.obj = obj;
2334 | this.val = val;
2335 | this.key = key;
2336 | this.block = block;
2337 | };
2338 |
2339 | /**
2340 | * Inherit from `Node`.
2341 | */
2342 |
2343 | Each.prototype = new Node;
2344 | Each.prototype.constructor = Each;
2345 |
2346 | }); // module: nodes/each.js
2347 |
2348 | require.register("nodes/filter.js", function(module, exports, require){
2349 |
2350 | /*!
2351 | * Jade - nodes - Filter
2352 | * Copyright(c) 2010 TJ Holowaychuk
2353 | * MIT Licensed
2354 | */
2355 |
2356 | /**
2357 | * Module dependencies.
2358 | */
2359 |
2360 | var Node = require('./node')
2361 | , Block = require('./block');
2362 |
2363 | /**
2364 | * Initialize a `Filter` node with the given
2365 | * filter `name` and `block`.
2366 | *
2367 | * @param {String} name
2368 | * @param {Block|Node} block
2369 | * @api public
2370 | */
2371 |
2372 | var Filter = module.exports = function Filter(name, block, attrs) {
2373 | this.name = name;
2374 | this.block = block;
2375 | this.attrs = attrs;
2376 | this.isASTFilter = !block.nodes.every(function(node){ return node.isText });
2377 | };
2378 |
2379 | /**
2380 | * Inherit from `Node`.
2381 | */
2382 |
2383 | Filter.prototype = new Node;
2384 | Filter.prototype.constructor = Filter;
2385 |
2386 | }); // module: nodes/filter.js
2387 |
2388 | require.register("nodes/index.js", function(module, exports, require){
2389 |
2390 | /*!
2391 | * Jade - nodes
2392 | * Copyright(c) 2010 TJ Holowaychuk
2393 | * MIT Licensed
2394 | */
2395 |
2396 | exports.Node = require('./node');
2397 | exports.Tag = require('./tag');
2398 | exports.Code = require('./code');
2399 | exports.Each = require('./each');
2400 | exports.Case = require('./case');
2401 | exports.Text = require('./text');
2402 | exports.Block = require('./block');
2403 | exports.Mixin = require('./mixin');
2404 | exports.Filter = require('./filter');
2405 | exports.Comment = require('./comment');
2406 | exports.Literal = require('./literal');
2407 | exports.BlockComment = require('./block-comment');
2408 | exports.Doctype = require('./doctype');
2409 |
2410 | }); // module: nodes/index.js
2411 |
2412 | require.register("nodes/literal.js", function(module, exports, require){
2413 |
2414 | /*!
2415 | * Jade - nodes - Literal
2416 | * Copyright(c) 2010 TJ Holowaychuk
2417 | * MIT Licensed
2418 | */
2419 |
2420 | /**
2421 | * Module dependencies.
2422 | */
2423 |
2424 | var Node = require('./node');
2425 |
2426 | /**
2427 | * Initialize a `Literal` node with the given `str.
2428 | *
2429 | * @param {String} str
2430 | * @api public
2431 | */
2432 |
2433 | var Literal = module.exports = function Literal(str) {
2434 | this.str = str
2435 | .replace(/\\/g, "\\\\")
2436 | .replace(/\n|\r\n/g, "\\n")
2437 | .replace(/'/g, "\\'");
2438 | };
2439 |
2440 | /**
2441 | * Inherit from `Node`.
2442 | */
2443 |
2444 | Literal.prototype = new Node;
2445 | Literal.prototype.constructor = Literal;
2446 |
2447 |
2448 | }); // module: nodes/literal.js
2449 |
2450 | require.register("nodes/mixin.js", function(module, exports, require){
2451 |
2452 | /*!
2453 | * Jade - nodes - Mixin
2454 | * Copyright(c) 2010 TJ Holowaychuk
2455 | * MIT Licensed
2456 | */
2457 |
2458 | /**
2459 | * Module dependencies.
2460 | */
2461 |
2462 | var Attrs = require('./attrs');
2463 |
2464 | /**
2465 | * Initialize a new `Mixin` with `name` and `block`.
2466 | *
2467 | * @param {String} name
2468 | * @param {String} args
2469 | * @param {Block} block
2470 | * @api public
2471 | */
2472 |
2473 | var Mixin = module.exports = function Mixin(name, args, block, call){
2474 | this.name = name;
2475 | this.args = args;
2476 | this.block = block;
2477 | this.attrs = [];
2478 | this.call = call;
2479 | };
2480 |
2481 | /**
2482 | * Inherit from `Attrs`.
2483 | */
2484 |
2485 | Mixin.prototype = new Attrs;
2486 | Mixin.prototype.constructor = Mixin;
2487 |
2488 |
2489 |
2490 | }); // module: nodes/mixin.js
2491 |
2492 | require.register("nodes/node.js", function(module, exports, require){
2493 |
2494 | /*!
2495 | * Jade - nodes - Node
2496 | * Copyright(c) 2010 TJ Holowaychuk
2497 | * MIT Licensed
2498 | */
2499 |
2500 | /**
2501 | * Initialize a `Node`.
2502 | *
2503 | * @api public
2504 | */
2505 |
2506 | var Node = module.exports = function Node(){};
2507 |
2508 | /**
2509 | * Clone this node (return itself)
2510 | *
2511 | * @return {Node}
2512 | * @api private
2513 | */
2514 |
2515 | Node.prototype.clone = function(){
2516 | return this;
2517 | };
2518 |
2519 | }); // module: nodes/node.js
2520 |
2521 | require.register("nodes/tag.js", function(module, exports, require){
2522 |
2523 | /*!
2524 | * Jade - nodes - Tag
2525 | * Copyright(c) 2010 TJ Holowaychuk
2526 | * MIT Licensed
2527 | */
2528 |
2529 | /**
2530 | * Module dependencies.
2531 | */
2532 |
2533 | var Attrs = require('./attrs'),
2534 | Block = require('./block'),
2535 | inlineTags = require('../inline-tags');
2536 |
2537 | /**
2538 | * Initialize a `Tag` node with the given tag `name` and optional `block`.
2539 | *
2540 | * @param {String} name
2541 | * @param {Block} block
2542 | * @api public
2543 | */
2544 |
2545 | var Tag = module.exports = function Tag(name, block) {
2546 | this.name = name;
2547 | this.attrs = [];
2548 | this.block = block || new Block;
2549 | };
2550 |
2551 | /**
2552 | * Inherit from `Attrs`.
2553 | */
2554 |
2555 | Tag.prototype = new Attrs;
2556 | Tag.prototype.constructor = Tag;
2557 |
2558 |
2559 | /**
2560 | * Clone this tag.
2561 | *
2562 | * @return {Tag}
2563 | * @api private
2564 | */
2565 |
2566 | Tag.prototype.clone = function(){
2567 | var clone = new Tag(this.name, this.block.clone());
2568 | clone.line = this.line;
2569 | clone.attrs = this.attrs;
2570 | clone.textOnly = this.textOnly;
2571 | return clone;
2572 | };
2573 |
2574 | /**
2575 | * Check if this tag is an inline tag.
2576 | *
2577 | * @return {Boolean}
2578 | * @api private
2579 | */
2580 |
2581 | Tag.prototype.isInline = function(){
2582 | return ~inlineTags.indexOf(this.name);
2583 | };
2584 |
2585 | /**
2586 | * Check if this tag's contents can be inlined. Used for pretty printing.
2587 | *
2588 | * @return {Boolean}
2589 | * @api private
2590 | */
2591 |
2592 | Tag.prototype.canInline = function(){
2593 | var nodes = this.block.nodes;
2594 |
2595 | function isInline(node){
2596 | // Recurse if the node is a block
2597 | if (node.isBlock) return node.nodes.every(isInline);
2598 | return node.isText || (node.isInline && node.isInline());
2599 | }
2600 |
2601 | // Empty tag
2602 | if (!nodes.length) return true;
2603 |
2604 | // Text-only or inline-only tag
2605 | if (1 == nodes.length) return isInline(nodes[0]);
2606 |
2607 | // Multi-line inline-only tag
2608 | if (this.block.nodes.every(isInline)) {
2609 | for (var i = 1, len = nodes.length; i < len; ++i) {
2610 | if (nodes[i-1].isText && nodes[i].isText)
2611 | return false;
2612 | }
2613 | return true;
2614 | }
2615 |
2616 | // Mixed tag
2617 | return false;
2618 | };
2619 | }); // module: nodes/tag.js
2620 |
2621 | require.register("nodes/text.js", function(module, exports, require){
2622 |
2623 | /*!
2624 | * Jade - nodes - Text
2625 | * Copyright(c) 2010 TJ Holowaychuk
2626 | * MIT Licensed
2627 | */
2628 |
2629 | /**
2630 | * Module dependencies.
2631 | */
2632 |
2633 | var Node = require('./node');
2634 |
2635 | /**
2636 | * Initialize a `Text` node with optional `line`.
2637 | *
2638 | * @param {String} line
2639 | * @api public
2640 | */
2641 |
2642 | var Text = module.exports = function Text(line) {
2643 | this.val = '';
2644 | if ('string' == typeof line) this.val = line;
2645 | };
2646 |
2647 | /**
2648 | * Inherit from `Node`.
2649 | */
2650 |
2651 | Text.prototype = new Node;
2652 | Text.prototype.constructor = Text;
2653 |
2654 |
2655 | /**
2656 | * Flag as text.
2657 | */
2658 |
2659 | Text.prototype.isText = true;
2660 | }); // module: nodes/text.js
2661 |
2662 | require.register("parser.js", function(module, exports, require){
2663 |
2664 | /*!
2665 | * Jade - Parser
2666 | * Copyright(c) 2010 TJ Holowaychuk
2667 | * MIT Licensed
2668 | */
2669 |
2670 | /**
2671 | * Module dependencies.
2672 | */
2673 |
2674 | var Lexer = require('./lexer')
2675 | , nodes = require('./nodes')
2676 | , utils = require('./utils');
2677 |
2678 | /**
2679 | * Initialize `Parser` with the given input `str` and `filename`.
2680 | *
2681 | * @param {String} str
2682 | * @param {String} filename
2683 | * @param {Object} options
2684 | * @api public
2685 | */
2686 |
2687 | var Parser = exports = module.exports = function Parser(str, filename, options){
2688 | this.input = str;
2689 | this.lexer = new Lexer(str, options);
2690 | this.filename = filename;
2691 | this.blocks = {};
2692 | this.mixins = {};
2693 | this.options = options;
2694 | this.contexts = [this];
2695 | };
2696 |
2697 | /**
2698 | * Tags that may not contain tags.
2699 | */
2700 |
2701 | var textOnly = exports.textOnly = ['script', 'style'];
2702 |
2703 | /**
2704 | * Parser prototype.
2705 | */
2706 |
2707 | Parser.prototype = {
2708 |
2709 | /**
2710 | * Push `parser` onto the context stack,
2711 | * or pop and return a `Parser`.
2712 | */
2713 |
2714 | context: function(parser){
2715 | if (parser) {
2716 | this.contexts.push(parser);
2717 | } else {
2718 | return this.contexts.pop();
2719 | }
2720 | },
2721 |
2722 | /**
2723 | * Return the next token object.
2724 | *
2725 | * @return {Object}
2726 | * @api private
2727 | */
2728 |
2729 | advance: function(){
2730 | return this.lexer.advance();
2731 | },
2732 |
2733 | /**
2734 | * Skip `n` tokens.
2735 | *
2736 | * @param {Number} n
2737 | * @api private
2738 | */
2739 |
2740 | skip: function(n){
2741 | while (n--) this.advance();
2742 | },
2743 |
2744 | /**
2745 | * Single token lookahead.
2746 | *
2747 | * @return {Object}
2748 | * @api private
2749 | */
2750 |
2751 | peek: function() {
2752 | return this.lookahead(1);
2753 | },
2754 |
2755 | /**
2756 | * Return lexer lineno.
2757 | *
2758 | * @return {Number}
2759 | * @api private
2760 | */
2761 |
2762 | line: function() {
2763 | return this.lexer.lineno;
2764 | },
2765 |
2766 | /**
2767 | * `n` token lookahead.
2768 | *
2769 | * @param {Number} n
2770 | * @return {Object}
2771 | * @api private
2772 | */
2773 |
2774 | lookahead: function(n){
2775 | return this.lexer.lookahead(n);
2776 | },
2777 |
2778 | /**
2779 | * Parse input returning a string of js for evaluation.
2780 | *
2781 | * @return {String}
2782 | * @api public
2783 | */
2784 |
2785 | parse: function(){
2786 | var block = new nodes.Block, parser;
2787 | block.line = this.line();
2788 |
2789 | while ('eos' != this.peek().type) {
2790 | if ('newline' == this.peek().type) {
2791 | this.advance();
2792 | } else {
2793 | block.push(this.parseExpr());
2794 | }
2795 | }
2796 |
2797 | if (parser = this.extending) {
2798 | this.context(parser);
2799 | var ast = parser.parse();
2800 | this.context();
2801 | // hoist mixins
2802 | for (var name in this.mixins)
2803 | ast.unshift(this.mixins[name]);
2804 | return ast;
2805 | }
2806 |
2807 | return block;
2808 | },
2809 |
2810 | /**
2811 | * Expect the given type, or throw an exception.
2812 | *
2813 | * @param {String} type
2814 | * @api private
2815 | */
2816 |
2817 | expect: function(type){
2818 | if (this.peek().type === type) {
2819 | return this.advance();
2820 | } else {
2821 | throw new Error('expected "' + type + '", but got "' + this.peek().type + '"');
2822 | }
2823 | },
2824 |
2825 | /**
2826 | * Accept the given `type`.
2827 | *
2828 | * @param {String} type
2829 | * @api private
2830 | */
2831 |
2832 | accept: function(type){
2833 | if (this.peek().type === type) {
2834 | return this.advance();
2835 | }
2836 | },
2837 |
2838 | /**
2839 | * tag
2840 | * | doctype
2841 | * | mixin
2842 | * | include
2843 | * | filter
2844 | * | comment
2845 | * | text
2846 | * | each
2847 | * | code
2848 | * | yield
2849 | * | id
2850 | * | class
2851 | * | interpolation
2852 | */
2853 |
2854 | parseExpr: function(){
2855 | switch (this.peek().type) {
2856 | case 'tag':
2857 | return this.parseTag();
2858 | case 'mixin':
2859 | return this.parseMixin();
2860 | case 'block':
2861 | return this.parseBlock();
2862 | case 'case':
2863 | return this.parseCase();
2864 | case 'when':
2865 | return this.parseWhen();
2866 | case 'default':
2867 | return this.parseDefault();
2868 | case 'extends':
2869 | return this.parseExtends();
2870 | case 'include':
2871 | return this.parseInclude();
2872 | case 'doctype':
2873 | return this.parseDoctype();
2874 | case 'filter':
2875 | return this.parseFilter();
2876 | case 'comment':
2877 | return this.parseComment();
2878 | case 'text':
2879 | return this.parseText();
2880 | case 'each':
2881 | return this.parseEach();
2882 | case 'code':
2883 | return this.parseCode();
2884 | case 'call':
2885 | return this.parseCall();
2886 | case 'interpolation':
2887 | return this.parseInterpolation();
2888 | case 'yield':
2889 | this.advance();
2890 | var block = new nodes.Block;
2891 | block.yield = true;
2892 | return block;
2893 | case 'id':
2894 | case 'class':
2895 | var tok = this.advance();
2896 | this.lexer.defer(this.lexer.tok('tag', 'div'));
2897 | this.lexer.defer(tok);
2898 | return this.parseExpr();
2899 | default:
2900 | throw new Error('unexpected token "' + this.peek().type + '"');
2901 | }
2902 | },
2903 |
2904 | /**
2905 | * Text
2906 | */
2907 |
2908 | parseText: function(){
2909 | var tok = this.expect('text')
2910 | , node = new nodes.Text(tok.val);
2911 | node.line = this.line();
2912 | return node;
2913 | },
2914 |
2915 | /**
2916 | * ':' expr
2917 | * | block
2918 | */
2919 |
2920 | parseBlockExpansion: function(){
2921 | if (':' == this.peek().type) {
2922 | this.advance();
2923 | return new nodes.Block(this.parseExpr());
2924 | } else {
2925 | return this.block();
2926 | }
2927 | },
2928 |
2929 | /**
2930 | * case
2931 | */
2932 |
2933 | parseCase: function(){
2934 | var val = this.expect('case').val
2935 | , node = new nodes.Case(val);
2936 | node.line = this.line();
2937 | node.block = this.block();
2938 | return node;
2939 | },
2940 |
2941 | /**
2942 | * when
2943 | */
2944 |
2945 | parseWhen: function(){
2946 | var val = this.expect('when').val
2947 | return new nodes.Case.When(val, this.parseBlockExpansion());
2948 | },
2949 |
2950 | /**
2951 | * default
2952 | */
2953 |
2954 | parseDefault: function(){
2955 | this.expect('default');
2956 | return new nodes.Case.When('default', this.parseBlockExpansion());
2957 | },
2958 |
2959 | /**
2960 | * code
2961 | */
2962 |
2963 | parseCode: function(){
2964 | var tok = this.expect('code')
2965 | , node = new nodes.Code(tok.val, tok.buffer, tok.escape)
2966 | , block
2967 | , i = 1;
2968 | node.line = this.line();
2969 | while (this.lookahead(i) && 'newline' == this.lookahead(i).type) ++i;
2970 | block = 'indent' == this.lookahead(i).type;
2971 | if (block) {
2972 | this.skip(i-1);
2973 | node.block = this.block();
2974 | }
2975 | return node;
2976 | },
2977 |
2978 | /**
2979 | * comment
2980 | */
2981 |
2982 | parseComment: function(){
2983 | var tok = this.expect('comment')
2984 | , node;
2985 |
2986 | if ('indent' == this.peek().type) {
2987 | node = new nodes.BlockComment(tok.val, this.block(), tok.buffer);
2988 | } else {
2989 | node = new nodes.Comment(tok.val, tok.buffer);
2990 | }
2991 |
2992 | node.line = this.line();
2993 | return node;
2994 | },
2995 |
2996 | /**
2997 | * doctype
2998 | */
2999 |
3000 | parseDoctype: function(){
3001 | var tok = this.expect('doctype')
3002 | , node = new nodes.Doctype(tok.val);
3003 | node.line = this.line();
3004 | return node;
3005 | },
3006 |
3007 | /**
3008 | * filter attrs? text-block
3009 | */
3010 |
3011 | parseFilter: function(){
3012 | var block
3013 | , tok = this.expect('filter')
3014 | , attrs = this.accept('attrs');
3015 |
3016 | this.lexer.pipeless = true;
3017 | block = this.parseTextBlock();
3018 | this.lexer.pipeless = false;
3019 |
3020 | var node = new nodes.Filter(tok.val, block, attrs && attrs.attrs);
3021 | node.line = this.line();
3022 | return node;
3023 | },
3024 |
3025 | /**
3026 | * tag ':' attrs? block
3027 | */
3028 |
3029 | parseASTFilter: function(){
3030 | var block
3031 | , tok = this.expect('tag')
3032 | , attrs = this.accept('attrs');
3033 |
3034 | this.expect(':');
3035 | block = this.block();
3036 |
3037 | var node = new nodes.Filter(tok.val, block, attrs && attrs.attrs);
3038 | node.line = this.line();
3039 | return node;
3040 | },
3041 |
3042 | /**
3043 | * each block
3044 | */
3045 |
3046 | parseEach: function(){
3047 | var tok = this.expect('each')
3048 | , node = new nodes.Each(tok.code, tok.val, tok.key);
3049 | node.line = this.line();
3050 | node.block = this.block();
3051 | if (this.peek().type == 'code' && this.peek().val == 'else') {
3052 | this.advance();
3053 | node.alternative = this.block();
3054 | }
3055 | return node;
3056 | },
3057 |
3058 | /**
3059 | * 'extends' name
3060 | */
3061 |
3062 | parseExtends: function(){
3063 | var path = require('path')
3064 | , fs = require('fs')
3065 | , dirname = path.dirname
3066 | , basename = path.basename
3067 | , join = path.join;
3068 |
3069 | if (!this.filename)
3070 | throw new Error('the "filename" option is required to extend templates');
3071 |
3072 | var path = this.expect('extends').val.trim()
3073 | , dir = dirname(this.filename);
3074 |
3075 | var path = join(dir, path + '.jade')
3076 | , str = fs.readFileSync(path, 'utf8')
3077 | , parser = new Parser(str, path, this.options);
3078 |
3079 | parser.blocks = this.blocks;
3080 | parser.contexts = this.contexts;
3081 | this.extending = parser;
3082 |
3083 | // TODO: null node
3084 | return new nodes.Literal('');
3085 | },
3086 |
3087 | /**
3088 | * 'block' name block
3089 | */
3090 |
3091 | parseBlock: function(){
3092 | var block = this.expect('block')
3093 | , mode = block.mode
3094 | , name = block.val.trim();
3095 |
3096 | block = 'indent' == this.peek().type
3097 | ? this.block()
3098 | : new nodes.Block(new nodes.Literal(''));
3099 |
3100 | var prev = this.blocks[name];
3101 |
3102 | if (prev) {
3103 | switch (prev.mode) {
3104 | case 'append':
3105 | block.nodes = block.nodes.concat(prev.nodes);
3106 | prev = block;
3107 | break;
3108 | case 'prepend':
3109 | block.nodes = prev.nodes.concat(block.nodes);
3110 | prev = block;
3111 | break;
3112 | }
3113 | }
3114 |
3115 | block.mode = mode;
3116 | return this.blocks[name] = prev || block;
3117 | },
3118 |
3119 | /**
3120 | * include block?
3121 | */
3122 |
3123 | parseInclude: function(){
3124 | var path = require('path')
3125 | , fs = require('fs')
3126 | , dirname = path.dirname
3127 | , basename = path.basename
3128 | , join = path.join;
3129 |
3130 | var path = this.expect('include').val.trim()
3131 | , dir = dirname(this.filename);
3132 |
3133 | if (!this.filename)
3134 | throw new Error('the "filename" option is required to use includes');
3135 |
3136 | // no extension
3137 | if (!~basename(path).indexOf('.')) {
3138 | path += '.jade';
3139 | }
3140 |
3141 | // non-jade
3142 | if ('.jade' != path.substr(-5)) {
3143 | var path = join(dir, path)
3144 | , str = fs.readFileSync(path, 'utf8');
3145 | return new nodes.Literal(str);
3146 | }
3147 |
3148 | var path = join(dir, path)
3149 | , str = fs.readFileSync(path, 'utf8')
3150 | , parser = new Parser(str, path, this.options);
3151 | parser.blocks = utils.merge({}, this.blocks);
3152 | parser.mixins = this.mixins;
3153 |
3154 | this.context(parser);
3155 | var ast = parser.parse();
3156 | this.context();
3157 | ast.filename = path;
3158 |
3159 | if ('indent' == this.peek().type) {
3160 | ast.includeBlock().push(this.block());
3161 | }
3162 |
3163 | return ast;
3164 | },
3165 |
3166 | /**
3167 | * call ident block
3168 | */
3169 |
3170 | parseCall: function(){
3171 | var tok = this.expect('call')
3172 | , name = tok.val
3173 | , args = tok.args
3174 | , mixin = new nodes.Mixin(name, args, new nodes.Block, true);
3175 |
3176 | this.tag(mixin);
3177 | if (mixin.block.isEmpty()) mixin.block = null;
3178 | return mixin;
3179 | },
3180 |
3181 | /**
3182 | * mixin block
3183 | */
3184 |
3185 | parseMixin: function(){
3186 | var tok = this.expect('mixin')
3187 | , name = tok.val
3188 | , args = tok.args
3189 | , mixin;
3190 |
3191 | // definition
3192 | if ('indent' == this.peek().type) {
3193 | mixin = new nodes.Mixin(name, args, this.block(), false);
3194 | this.mixins[name] = mixin;
3195 | return mixin;
3196 | // call
3197 | } else {
3198 | return new nodes.Mixin(name, args, null, true);
3199 | }
3200 | },
3201 |
3202 | /**
3203 | * indent (text | newline)* outdent
3204 | */
3205 |
3206 | parseTextBlock: function(){
3207 | var block = new nodes.Block;
3208 | block.line = this.line();
3209 | var spaces = this.expect('indent').val;
3210 | if (null == this._spaces) this._spaces = spaces;
3211 | var indent = Array(spaces - this._spaces + 1).join(' ');
3212 | while ('outdent' != this.peek().type) {
3213 | switch (this.peek().type) {
3214 | case 'newline':
3215 | this.advance();
3216 | break;
3217 | case 'indent':
3218 | this.parseTextBlock().nodes.forEach(function(node){
3219 | block.push(node);
3220 | });
3221 | break;
3222 | default:
3223 | var text = new nodes.Text(indent + this.advance().val);
3224 | text.line = this.line();
3225 | block.push(text);
3226 | }
3227 | }
3228 |
3229 | if (spaces == this._spaces) this._spaces = null;
3230 | this.expect('outdent');
3231 | return block;
3232 | },
3233 |
3234 | /**
3235 | * indent expr* outdent
3236 | */
3237 |
3238 | block: function(){
3239 | var block = new nodes.Block;
3240 | block.line = this.line();
3241 | this.expect('indent');
3242 | while ('outdent' != this.peek().type) {
3243 | if ('newline' == this.peek().type) {
3244 | this.advance();
3245 | } else {
3246 | block.push(this.parseExpr());
3247 | }
3248 | }
3249 | this.expect('outdent');
3250 | return block;
3251 | },
3252 |
3253 | /**
3254 | * interpolation (attrs | class | id)* (text | code | ':')? newline* block?
3255 | */
3256 |
3257 | parseInterpolation: function(){
3258 | var tok = this.advance();
3259 | var tag = new nodes.Tag(tok.val);
3260 | tag.buffer = true;
3261 | return this.tag(tag);
3262 | },
3263 |
3264 | /**
3265 | * tag (attrs | class | id)* (text | code | ':')? newline* block?
3266 | */
3267 |
3268 | parseTag: function(){
3269 | // ast-filter look-ahead
3270 | var i = 2;
3271 | if ('attrs' == this.lookahead(i).type) ++i;
3272 | if (':' == this.lookahead(i).type) {
3273 | if ('indent' == this.lookahead(++i).type) {
3274 | return this.parseASTFilter();
3275 | }
3276 | }
3277 |
3278 | var tok = this.advance()
3279 | , tag = new nodes.Tag(tok.val);
3280 |
3281 | tag.selfClosing = tok.selfClosing;
3282 |
3283 | return this.tag(tag);
3284 | },
3285 |
3286 | /**
3287 | * Parse tag.
3288 | */
3289 |
3290 | tag: function(tag){
3291 | var dot;
3292 |
3293 | tag.line = this.line();
3294 |
3295 | // (attrs | class | id)*
3296 | out:
3297 | while (true) {
3298 | switch (this.peek().type) {
3299 | case 'id':
3300 | case 'class':
3301 | var tok = this.advance();
3302 | tag.setAttribute(tok.type, "'" + tok.val + "'");
3303 | continue;
3304 | case 'attrs':
3305 | var tok = this.advance()
3306 | , obj = tok.attrs
3307 | , escaped = tok.escaped
3308 | , names = Object.keys(obj);
3309 |
3310 | if (tok.selfClosing) tag.selfClosing = true;
3311 |
3312 | for (var i = 0, len = names.length; i < len; ++i) {
3313 | var name = names[i]
3314 | , val = obj[name];
3315 | tag.setAttribute(name, val, escaped[name]);
3316 | }
3317 | continue;
3318 | default:
3319 | break out;
3320 | }
3321 | }
3322 |
3323 | // check immediate '.'
3324 | if ('.' == this.peek().val) {
3325 | dot = tag.textOnly = true;
3326 | this.advance();
3327 | }
3328 |
3329 | // (text | code | ':')?
3330 | switch (this.peek().type) {
3331 | case 'text':
3332 | tag.block.push(this.parseText());
3333 | break;
3334 | case 'code':
3335 | tag.code = this.parseCode();
3336 | break;
3337 | case ':':
3338 | this.advance();
3339 | tag.block = new nodes.Block;
3340 | tag.block.push(this.parseExpr());
3341 | break;
3342 | }
3343 |
3344 | // newline*
3345 | while ('newline' == this.peek().type) this.advance();
3346 |
3347 | tag.textOnly = tag.textOnly || ~textOnly.indexOf(tag.name);
3348 |
3349 | // script special-case
3350 | if ('script' == tag.name) {
3351 | var type = tag.getAttribute('type');
3352 | if (!dot && type && 'text/javascript' != type.replace(/^['"]|['"]$/g, '')) {
3353 | tag.textOnly = false;
3354 | }
3355 | }
3356 |
3357 | // block?
3358 | if ('indent' == this.peek().type) {
3359 | if (tag.textOnly) {
3360 | this.lexer.pipeless = true;
3361 | tag.block = this.parseTextBlock();
3362 | this.lexer.pipeless = false;
3363 | } else {
3364 | var block = this.block();
3365 | if (tag.block) {
3366 | for (var i = 0, len = block.nodes.length; i < len; ++i) {
3367 | tag.block.push(block.nodes[i]);
3368 | }
3369 | } else {
3370 | tag.block = block;
3371 | }
3372 | }
3373 | }
3374 |
3375 | return tag;
3376 | }
3377 | };
3378 |
3379 | }); // module: parser.js
3380 |
3381 | require.register("runtime.js", function(module, exports, require){
3382 |
3383 | /*!
3384 | * Jade - runtime
3385 | * Copyright(c) 2010 TJ Holowaychuk
3386 | * MIT Licensed
3387 | */
3388 |
3389 | /**
3390 | * Lame Array.isArray() polyfill for now.
3391 | */
3392 |
3393 | if (!Array.isArray) {
3394 | Array.isArray = function(arr){
3395 | return '[object Array]' == Object.prototype.toString.call(arr);
3396 | };
3397 | }
3398 |
3399 | /**
3400 | * Lame Object.keys() polyfill for now.
3401 | */
3402 |
3403 | if (!Object.keys) {
3404 | Object.keys = function(obj){
3405 | var arr = [];
3406 | for (var key in obj) {
3407 | if (obj.hasOwnProperty(key)) {
3408 | arr.push(key);
3409 | }
3410 | }
3411 | return arr;
3412 | }
3413 | }
3414 |
3415 | /**
3416 | * Merge two attribute objects giving precedence
3417 | * to values in object `b`. Classes are special-cased
3418 | * allowing for arrays and merging/joining appropriately
3419 | * resulting in a string.
3420 | *
3421 | * @param {Object} a
3422 | * @param {Object} b
3423 | * @return {Object} a
3424 | * @api private
3425 | */
3426 |
3427 | exports.merge = function merge(a, b) {
3428 | var ac = a['class'];
3429 | var bc = b['class'];
3430 |
3431 | if (ac || bc) {
3432 | ac = ac || [];
3433 | bc = bc || [];
3434 | if (!Array.isArray(ac)) ac = [ac];
3435 | if (!Array.isArray(bc)) bc = [bc];
3436 | ac = ac.filter(nulls);
3437 | bc = bc.filter(nulls);
3438 | a['class'] = ac.concat(bc).join(' ');
3439 | }
3440 |
3441 | for (var key in b) {
3442 | if (key != 'class') {
3443 | a[key] = b[key];
3444 | }
3445 | }
3446 |
3447 | return a;
3448 | };
3449 |
3450 | /**
3451 | * Filter null `val`s.
3452 | *
3453 | * @param {Mixed} val
3454 | * @return {Mixed}
3455 | * @api private
3456 | */
3457 |
3458 | function nulls(val) {
3459 | return val != null;
3460 | }
3461 |
3462 | /**
3463 | * Render the given attributes object.
3464 | *
3465 | * @param {Object} obj
3466 | * @param {Object} escaped
3467 | * @return {String}
3468 | * @api private
3469 | */
3470 |
3471 | exports.attrs = function attrs(obj, escaped){
3472 | var buf = []
3473 | , terse = obj.terse;
3474 |
3475 | delete obj.terse;
3476 | var keys = Object.keys(obj)
3477 | , len = keys.length;
3478 |
3479 | if (len) {
3480 | buf.push('');
3481 | for (var i = 0; i < len; ++i) {
3482 | var key = keys[i]
3483 | , val = obj[key];
3484 |
3485 | if ('boolean' == typeof val || null == val) {
3486 | if (val) {
3487 | terse
3488 | ? buf.push(key)
3489 | : buf.push(key + '="' + key + '"');
3490 | }
3491 | } else if (0 == key.indexOf('data') && 'string' != typeof val) {
3492 | buf.push(key + "='" + JSON.stringify(val) + "'");
3493 | } else if ('class' == key && Array.isArray(val)) {
3494 | buf.push(key + '="' + exports.escape(val.join(' ')) + '"');
3495 | } else if (escaped && escaped[key]) {
3496 | buf.push(key + '="' + exports.escape(val) + '"');
3497 | } else {
3498 | buf.push(key + '="' + val + '"');
3499 | }
3500 | }
3501 | }
3502 |
3503 | return buf.join(' ');
3504 | };
3505 |
3506 | /**
3507 | * Escape the given string of `html`.
3508 | *
3509 | * @param {String} html
3510 | * @return {String}
3511 | * @api private
3512 | */
3513 |
3514 | exports.escape = function escape(html){
3515 | return String(html)
3516 | .replace(/&(?!(\w+|\#\d+);)/g, '&')
3517 | .replace(//g, '>')
3519 | .replace(/"/g, '"');
3520 | };
3521 |
3522 | /**
3523 | * Re-throw the given `err` in context to the
3524 | * the jade in `filename` at the given `lineno`.
3525 | *
3526 | * @param {Error} err
3527 | * @param {String} filename
3528 | * @param {String} lineno
3529 | * @api private
3530 | */
3531 |
3532 | exports.rethrow = function rethrow(err, filename, lineno){
3533 | if (!filename) throw err;
3534 |
3535 | var context = 3
3536 | , str = require('fs').readFileSync(filename, 'utf8')
3537 | , lines = str.split('\n')
3538 | , start = Math.max(lineno - context, 0)
3539 | , end = Math.min(lines.length, lineno + context);
3540 |
3541 | // Error context
3542 | var context = lines.slice(start, end).map(function(line, i){
3543 | var curr = i + start + 1;
3544 | return (curr == lineno ? ' > ' : ' ')
3545 | + curr
3546 | + '| '
3547 | + line;
3548 | }).join('\n');
3549 |
3550 | // Alter exception message
3551 | err.path = filename;
3552 | err.message = (filename || 'Jade') + ':' + lineno
3553 | + '\n' + context + '\n\n' + err.message;
3554 | throw err;
3555 | };
3556 |
3557 | }); // module: runtime.js
3558 |
3559 | require.register("self-closing.js", function(module, exports, require){
3560 |
3561 | /*!
3562 | * Jade - self closing tags
3563 | * Copyright(c) 2010 TJ Holowaychuk
3564 | * MIT Licensed
3565 | */
3566 |
3567 | module.exports = [
3568 | 'meta'
3569 | , 'img'
3570 | , 'link'
3571 | , 'input'
3572 | , 'source'
3573 | , 'area'
3574 | , 'base'
3575 | , 'col'
3576 | , 'br'
3577 | , 'hr'
3578 | ];
3579 | }); // module: self-closing.js
3580 |
3581 | require.register("utils.js", function(module, exports, require){
3582 |
3583 | /*!
3584 | * Jade - utils
3585 | * Copyright(c) 2010 TJ Holowaychuk
3586 | * MIT Licensed
3587 | */
3588 |
3589 | /**
3590 | * Convert interpolation in the given string to JavaScript.
3591 | *
3592 | * @param {String} str
3593 | * @return {String}
3594 | * @api private
3595 | */
3596 |
3597 | var interpolate = exports.interpolate = function(str){
3598 | return str.replace(/(_SLASH_)?([#!]){(.*?)}/g, function(str, escape, flag, code){
3599 | code = code
3600 | .replace(/\\'/g, "'")
3601 | .replace(/_SLASH_/g, '\\');
3602 |
3603 | return escape
3604 | ? str.slice(7)
3605 | : "' + "
3606 | + ('!' == flag ? '' : 'escape')
3607 | + "((interp = " + code
3608 | + ") == null ? '' : interp) + '";
3609 | });
3610 | };
3611 |
3612 | /**
3613 | * Escape single quotes in `str`.
3614 | *
3615 | * @param {String} str
3616 | * @return {String}
3617 | * @api private
3618 | */
3619 |
3620 | var escape = exports.escape = function(str) {
3621 | return str.replace(/'/g, "\\'");
3622 | };
3623 |
3624 | /**
3625 | * Interpolate, and escape the given `str`.
3626 | *
3627 | * @param {String} str
3628 | * @return {String}
3629 | * @api private
3630 | */
3631 |
3632 | exports.text = function(str){
3633 | return interpolate(escape(str));
3634 | };
3635 |
3636 | /**
3637 | * Merge `b` into `a`.
3638 | *
3639 | * @param {Object} a
3640 | * @param {Object} b
3641 | * @return {Object}
3642 | * @api public
3643 | */
3644 |
3645 | exports.merge = function(a, b) {
3646 | for (var key in b) a[key] = b[key];
3647 | return a;
3648 | };
3649 |
3650 |
3651 | }); // module: utils.js
3652 |
3653 | window.jade = require("jade");
3654 | })();
3655 |
--------------------------------------------------------------------------------