microservice_precompiler 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +21 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +94 -0
- data/Rakefile +37 -0
- data/lib/microservice_precompiler.rb +14 -0
- data/lib/microservice_precompiler/builder.rb +146 -0
- data/lib/microservice_precompiler/version.rb +3 -0
- data/microservice_precompiler.gemspec +28 -0
- data/test/dummy/config.rb +21 -0
- data/test/dummy/images/link_icons/doc.png +0 -0
- data/test/dummy/images/link_icons/email.png +0 -0
- data/test/dummy/images/link_icons/external.png +0 -0
- data/test/dummy/images/link_icons/feed.png +0 -0
- data/test/dummy/images/link_icons/im.png +0 -0
- data/test/dummy/images/link_icons/pdf.png +0 -0
- data/test/dummy/images/link_icons/visited.png +0 -0
- data/test/dummy/images/link_icons/xls.png +0 -0
- data/test/dummy/javascripts/lib/backbone/backbone-min.js +38 -0
- data/test/dummy/javascripts/lib/jquery/jquery-1.7.2.min.js +4 -0
- data/test/dummy/javascripts/lib/jquery/jquery-ui-1.8.19.custom.min.js +125 -0
- data/test/dummy/javascripts/lib/require/require.js +33 -0
- data/test/dummy/javascripts/lib/underscore/underscore-min.js +32 -0
- data/test/dummy/javascripts/sample.js.coffee +2 -0
- data/test/dummy/mustaches.yml.tml +8 -0
- data/test/dummy/sass/dependencies/link_icons.scss +13 -0
- data/test/dummy/sass/dependencies/sample.scss +3 -0
- data/test/dummy/sass/ie.scss +5 -0
- data/test/dummy/sass/print.scss +3 -0
- data/test/dummy/sass/screen.scss +10 -0
- data/test/dummy/templates/sample.html.mustache +1 -0
- data/test/dummy/templates/sample.rb +7 -0
- data/test/dummy/templates/sample1.rb +7 -0
- data/test/dummy/templates/sample_folder/sample_folder_test.html.mustache +1 -0
- data/test/dummy/templates/sample_folder/sample_folder_test.rb +7 -0
- data/test/dummy/templates/sample_with_underscore.rb +7 -0
- data/test/microservice_precompiler_test.rb +7 -0
- data/test/test_helper.rb +2 -0
- data/test/unit/builder_test.rb +59 -0
- metadata +231 -0
data/.gitignore
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
.rvmrc
|
19
|
+
test/dummy/.sass-cache
|
20
|
+
test/dummy/stylesheets
|
21
|
+
test/dummy/dist
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 barnabyalter
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# MicroservicePrecompiler
|
2
|
+
|
3
|
+
The microservice precompiler uses a handful of technologies to compile Javascripts and Stylesheets and create HTML pages from templates into a distribution folder ready for deployment. The microservices used are CoffeeScript, SASS and Mustache, compiling and minifying (where possible) into Javascript, CSS and HTML, respectively.
|
4
|
+
|
5
|
+
The SASS is compiled into CSS via Compass; the CoffeeScript is translated to Javascript via Sprockets; both CSS and JS have their dependency trees included in-file by Sprockets; CSS and JS are then minified and compressed via YUICompressor and Uglifier, respectively.
|
6
|
+
|
7
|
+
The gem requires that your project root be a Compass project and expects that you have a folder structure matching the following in the root of your project:
|
8
|
+
|
9
|
+
/javascripts/
|
10
|
+
/sass/
|
11
|
+
/templates/
|
12
|
+
mustaches.yml
|
13
|
+
|
14
|
+
Where javascripts contains your Coffee, sass contains your SASS, templates contains a folder structure matching your mustaches.yml file for building out pages from mustache templates.
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
Add this line to your application's Gemfile:
|
19
|
+
|
20
|
+
gem 'microservice_precompiler'
|
21
|
+
|
22
|
+
And then execute:
|
23
|
+
|
24
|
+
$ bundle
|
25
|
+
|
26
|
+
Or install it yourself as:
|
27
|
+
|
28
|
+
$ gem install microservice_precompiler
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
To build all assets and templates into the distribution (./dist by default) folder, you can run the following from your application or rake:
|
33
|
+
|
34
|
+
require 'microservice_precompiler'
|
35
|
+
precompiler = MicroservicePrecompiler::Builder.new
|
36
|
+
precompiler.compile
|
37
|
+
|
38
|
+
Or with initialize options:
|
39
|
+
|
40
|
+
require 'microservice_precompiler'
|
41
|
+
precompiler = MicroservicePrecompiler::Builder.new
|
42
|
+
precompiler.project_root = "."
|
43
|
+
precompiler.build_path = "dist"
|
44
|
+
precompiler.mustaches_config = "mustaches.yml"
|
45
|
+
precompiler.compile
|
46
|
+
|
47
|
+
This runs all the precompiling options. Each can also be invoked individually:
|
48
|
+
|
49
|
+
# Clears the dist folder and sass cache files
|
50
|
+
precompiler.cleanup
|
51
|
+
# Runs the Compass build, which sends SASS to cachs
|
52
|
+
precompiler.compass_build
|
53
|
+
# Runs the Sprockets build which compiles CoffeeScript, minifies assets and moves to dist folder
|
54
|
+
precompiler.sprockets_build
|
55
|
+
# Runs the Mustache template build, which creates output files with the same syntax from the mustaches config yaml
|
56
|
+
precompiler.mustache_build
|
57
|
+
|
58
|
+
### SASS and CoffeeScript
|
59
|
+
|
60
|
+
These two parts are pretty simple. They are contained within their relevant directories and are written in their respective technologies. Because Sprockets is used to compile them the folders may contain subfolders which are automatically included in-line if the following line is present in a top-level file (where dependencies is the name of the subfolder you wish to include):
|
61
|
+
|
62
|
+
/*
|
63
|
+
*=require_tree ./dependencies
|
64
|
+
*/
|
65
|
+
|
66
|
+
### Mustache templating
|
67
|
+
|
68
|
+
The mustache builder requires you to provide a template file and a logic file from which to build the template. You can create a complex folder structure in the templates directory as long as it's logic is matched in the mustaches.yml config file. An example of the mustaches.yml follows:
|
69
|
+
|
70
|
+
templates:
|
71
|
+
- Sample:
|
72
|
+
- Sample
|
73
|
+
- Sample1
|
74
|
+
- SampleFolder:
|
75
|
+
- Sample2:
|
76
|
+
- Sample2
|
77
|
+
- SampleFolder:
|
78
|
+
- Sample2
|
79
|
+
- sample_with_underscore
|
80
|
+
|
81
|
+
* The case of the files is preserved from the mustaches config yaml. However, files like the above 'sample_with_underscore' are assumed to contain a class SampleWithUnderscore since this is the standard ruby practice.
|
82
|
+
* The top level is the directory you are building from. Multiple directories will work as well.
|
83
|
+
* The top level directory contains an array of template names, each of which is a hash of logic files to use the template.
|
84
|
+
** For example, Sample and Sample1 are two logic files which implement a mustache template called Sample.
|
85
|
+
* If one of the logic files is itself a hash it is assumed to be a directory. The mustache builder will treat this the same as the top level directory, reading the array of template file hashes with their array of logic files.
|
86
|
+
** In the above example, SampleFolder is a folder which contains a mustache template called Sample2 and a logic file Sample2 which implements the template Sample2. The following SampleFolder with only Sample2 beneath is does the same thing.
|
87
|
+
|
88
|
+
## Contributing
|
89
|
+
|
90
|
+
1. Fork it
|
91
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
92
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
93
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
94
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'bundler/setup'
|
6
|
+
rescue LoadError
|
7
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
8
|
+
end
|
9
|
+
|
10
|
+
begin
|
11
|
+
require 'rdoc/task'
|
12
|
+
rescue LoadError
|
13
|
+
require 'rdoc/rdoc'
|
14
|
+
require 'rake/rdoctask'
|
15
|
+
RDoc::Task = Rake::RDocTask
|
16
|
+
end
|
17
|
+
|
18
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
19
|
+
rdoc.rdoc_dir = 'rdoc'
|
20
|
+
rdoc.title = 'Exlibris::Primo'
|
21
|
+
rdoc.options << '--line-numbers'
|
22
|
+
rdoc.rdoc_files.include('README.rdoc')
|
23
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
24
|
+
end
|
25
|
+
|
26
|
+
Bundler::GemHelper.install_tasks
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
|
30
|
+
Rake::TestTask.new(:test) do |t|
|
31
|
+
t.libs << 'lib'
|
32
|
+
t.libs << 'test'
|
33
|
+
t.pattern = 'test/**/*_test.rb'
|
34
|
+
t.verbose = false
|
35
|
+
end
|
36
|
+
|
37
|
+
task :default => :test
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'compass'
|
2
|
+
require 'compass/exec'
|
3
|
+
require 'sprockets'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'uglifier'
|
6
|
+
require 'yui/compressor'
|
7
|
+
|
8
|
+
CLASS_PATH = File.dirname(__FILE__) + "/microservice_precompiler/"
|
9
|
+
[
|
10
|
+
'version',
|
11
|
+
'builder'
|
12
|
+
].each do |library|
|
13
|
+
require CLASS_PATH + library
|
14
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module MicroservicePrecompiler
|
2
|
+
class Builder
|
3
|
+
attr_accessor :project_root, :build_path, :mustaches_config
|
4
|
+
|
5
|
+
def initialize project_root = ".", build_path = "dist", mustaches_config = "mustaches.yml.tml"
|
6
|
+
@project_root = project_root
|
7
|
+
@build_path = File.join(@project_root, build_path)
|
8
|
+
@mustaches_config = mustaches_config
|
9
|
+
end
|
10
|
+
|
11
|
+
def compile
|
12
|
+
self.cleanup
|
13
|
+
self.compass_build
|
14
|
+
self.sprockets_build
|
15
|
+
self.mustache_build
|
16
|
+
end
|
17
|
+
|
18
|
+
def cleanup sprocket_assets = [:javascripts, :stylesheets]
|
19
|
+
FileUtils.rm_r @build_path if File.exists?(@build_path)
|
20
|
+
Compass::Exec::SubCommandUI.new(["clean", @project_root]).run!
|
21
|
+
# Don't initialize Compass assets, the config will take care of it
|
22
|
+
sprocket_assets.each do |asset|
|
23
|
+
FileUtils.mkdir_p File.join(@build_path, asset.to_s)
|
24
|
+
end
|
25
|
+
mustaches_config_file = File.join(@project_root, @mustaches_config)
|
26
|
+
if File.exists?(mustaches_config_file)
|
27
|
+
mustaches_config = YAML.load_file(mustaches_config_file)
|
28
|
+
if mustaches_config
|
29
|
+
mustaches_config.each_key do |dir|
|
30
|
+
FileUtils.mkdir_p File.join(@build_path, dir.to_s)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def compass_build
|
37
|
+
Compass::Exec::SubCommandUI.new(["compile", @project_root, "-s", "compact"]).run!
|
38
|
+
end
|
39
|
+
alias_method :compass, :compass_build
|
40
|
+
|
41
|
+
def sprockets_build sprocket_assets = [:javascripts, :stylesheets]
|
42
|
+
sprocket_assets.each do |asset_type|
|
43
|
+
load_path = File.join(@project_root, asset_type.to_s)
|
44
|
+
next unless File.exists?(load_path)
|
45
|
+
sprockets_env.append_path load_path
|
46
|
+
Dir.new(load_path).each do |filename|
|
47
|
+
file = File.join(load_path, filename)
|
48
|
+
if File.exists?(file) and File.file?(file)
|
49
|
+
asset = sprockets_env[filename]
|
50
|
+
attributes = sprockets_env.attributes_for(asset.pathname)
|
51
|
+
name, format = attributes.logical_path, attributes.format_extension
|
52
|
+
asset = minify(asset, format)
|
53
|
+
build_file = File.join(@build_path, asset_type.to_s, name)
|
54
|
+
File.open(build_file, 'w') do |f|
|
55
|
+
f.write(asset)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
alias_method :sprockets, :sprockets_build
|
62
|
+
|
63
|
+
def mustache_build
|
64
|
+
mustaches_config_file = "#{@project_root}/#{@mustaches_config}"
|
65
|
+
if File.exists?(mustaches_config_file)
|
66
|
+
# Load up file as a hash
|
67
|
+
mustaches_config = YAML.load_file(mustaches_config_file)
|
68
|
+
if mustaches_config.is_a? Hash
|
69
|
+
mustache_build_folder_structure(mustaches_config)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
alias_method :mustache, :mustache_build
|
74
|
+
|
75
|
+
private
|
76
|
+
def mustache_build_folder_structure mustaches_config, parent = ""
|
77
|
+
# Loop through each directory matched to a set of mustache classes/subclasses
|
78
|
+
mustaches_config.each do |dir, mustaches|
|
79
|
+
dir = (parent.eql? "") ? "#{dir}" : "#{parent}/#{dir}"
|
80
|
+
|
81
|
+
mustaches.each do |mustache|
|
82
|
+
# Get the name of the template class
|
83
|
+
template_class = (mustache.is_a? Hash) ? mustache.keys.first : mustache
|
84
|
+
# Get the name of the template file
|
85
|
+
template_file = camelcase_to_underscore(template_class)
|
86
|
+
# If the template class is an array of other classes, then these inherit from it
|
87
|
+
if mustache[template_class].is_a? Array
|
88
|
+
mustache[template_class].each do |logic_file|
|
89
|
+
if logic_file.is_a? Hash
|
90
|
+
# If the logic file is an array, then treat it like a folder and recurs
|
91
|
+
mustache_build_folder_structure(logic_file, dir)
|
92
|
+
else
|
93
|
+
mustache_template_build(dir, template_file, logic_file)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
else
|
97
|
+
mustache_template_build(dir, template_file, template_class)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def mustache_template_build dir, template_file, logic_file
|
104
|
+
logic_class_name = underscore_to_camelcase(logic_file)
|
105
|
+
output_file = logic_file #Output file should match the syntax of the mustaches config
|
106
|
+
logic_file = camelcase_to_underscore(logic_file)
|
107
|
+
# Require logic file, used to generate content from template
|
108
|
+
require File.join(@project_root, dir, logic_file)
|
109
|
+
# Create relevant directory path
|
110
|
+
FileUtils.mkdir_p File.join(@build_path, dir.to_s)
|
111
|
+
# Instantiate class from required file
|
112
|
+
mustache = Kernel.const_get(logic_class_name).new
|
113
|
+
# Set the template file
|
114
|
+
mustache.template_file = File.join(@project_root, dir, template_file) + ".html.mustache"
|
115
|
+
# Get the name of the file we will write to after it's template is processed
|
116
|
+
build_file = File.join(@build_path, dir, "#{output_file}.html")
|
117
|
+
File.open(build_file, 'w') do |f|
|
118
|
+
f.write(mustache.render)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def camelcase_to_underscore camelcase_string
|
123
|
+
camelcase_string.gsub(/(.)([A-Z])/,'\1_\2').downcase
|
124
|
+
end
|
125
|
+
|
126
|
+
def underscore_to_camelcase underscore_string
|
127
|
+
underscore_string = underscore_string.gsub(/(_)/,' ').split(' ').each { |word| word.capitalize! }.join("") unless underscore_string.match(/_/).nil?
|
128
|
+
underscore_string = underscore_string if underscore_string.match(/_/).nil?
|
129
|
+
return underscore_string
|
130
|
+
end
|
131
|
+
|
132
|
+
def sprockets_env
|
133
|
+
# Initialize sprockets environment
|
134
|
+
@sprockets_env ||= Sprockets::Environment.new(@project_root) { |env| env.logger = Logger.new(STDOUT) }
|
135
|
+
end
|
136
|
+
|
137
|
+
def minify(asset, format)
|
138
|
+
asset = asset.to_s
|
139
|
+
# Minify JS
|
140
|
+
return Uglifier.compile(asset) if format.eql?(".js")
|
141
|
+
# Minify CSS
|
142
|
+
return YUI::CssCompressor.new.compress(asset) if format.eql?(".css")
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/microservice_precompiler/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["barnabyalter"]
|
6
|
+
gem.email = ["barnaby.alter@nyu.edu"]
|
7
|
+
gem.description = %q{The microservice precompiler uses a handful of technologies to compile Javascripts and Stylesheets and create HTML pages from templates into a distribution folder ready for deployment. The microservices used are CoffeeScript, SASS and Mustache, compiling and minifying (where possible) into Javascript, CSS and HTML, respectively.}
|
8
|
+
gem.summary = %q{The microservice precompiler uses a handful of technologies to compile Javascripts and Stylesheets and create HTML pages from templates into a distribution folder ready for deployment.}
|
9
|
+
gem.homepage = "https://github.com/barnabyalter/microservice-precompiler"
|
10
|
+
|
11
|
+
|
12
|
+
gem.files = `git ls-files`.split($\)
|
13
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
14
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
15
|
+
gem.name = "microservice_precompiler"
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
gem.version = MicroservicePrecompiler::VERSION
|
18
|
+
|
19
|
+
#gem.add_development_dependency "rspec", "~> 2.6"
|
20
|
+
gem.add_development_dependency "rake"
|
21
|
+
gem.add_dependency "compass", "~> 0.12.1"
|
22
|
+
gem.add_dependency "sprockets", "~> 2.4.0"
|
23
|
+
gem.add_dependency "uglifier", "~> 1.2.4"
|
24
|
+
gem.add_dependency "mustache", "~> 0.99.4"
|
25
|
+
gem.add_dependency "yui-compressor", "~> 0.9.6"
|
26
|
+
gem.add_dependency "coffee-script", "~> 2.2.0"
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Require any additional compass plugins here.
|
2
|
+
css_dir = "stylesheets"
|
3
|
+
sass_dir = "sass"
|
4
|
+
images_dir = "images"
|
5
|
+
javascripts_dir = "javascripts"
|
6
|
+
|
7
|
+
# You can select your preferred output style here (can be overridden via the command line):
|
8
|
+
# output_style = :expanded or :nested or :compact or :compressed
|
9
|
+
|
10
|
+
# To enable relative paths to assets via compass helper functions. Uncomment:
|
11
|
+
# relative_assets = true
|
12
|
+
|
13
|
+
# To disable debugging comments that display the original location of your selectors. Uncomment:
|
14
|
+
# line_comments = false
|
15
|
+
|
16
|
+
|
17
|
+
# If you prefer the indented syntax, you might want to regenerate this
|
18
|
+
# project again passing --syntax sass, or you can uncomment this:
|
19
|
+
# preferred_syntax = :sass
|
20
|
+
# and then run:
|
21
|
+
# sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,38 @@
|
|
1
|
+
// Backbone.js 0.9.2
|
2
|
+
|
3
|
+
// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
|
4
|
+
// Backbone may be freely distributed under the MIT license.
|
5
|
+
// For all details and documentation:
|
6
|
+
// http://backbonejs.org
|
7
|
+
(function(){var l=this,y=l.Backbone,z=Array.prototype.slice,A=Array.prototype.splice,g;g="undefined"!==typeof exports?exports:l.Backbone={};g.VERSION="0.9.2";var f=l._;!f&&"undefined"!==typeof require&&(f=require("underscore"));var i=l.jQuery||l.Zepto||l.ender;g.setDomLibrary=function(a){i=a};g.noConflict=function(){l.Backbone=y;return this};g.emulateHTTP=!1;g.emulateJSON=!1;var p=/\s+/,k=g.Events={on:function(a,b,c){var d,e,f,g,j;if(!b)return this;a=a.split(p);for(d=this._callbacks||(this._callbacks=
|
8
|
+
{});e=a.shift();)f=(j=d[e])?j.tail:{},f.next=g={},f.context=c,f.callback=b,d[e]={tail:g,next:j?j.next:f};return this},off:function(a,b,c){var d,e,h,g,j,q;if(e=this._callbacks){if(!a&&!b&&!c)return delete this._callbacks,this;for(a=a?a.split(p):f.keys(e);d=a.shift();)if(h=e[d],delete e[d],h&&(b||c))for(g=h.tail;(h=h.next)!==g;)if(j=h.callback,q=h.context,b&&j!==b||c&&q!==c)this.on(d,j,q);return this}},trigger:function(a){var b,c,d,e,f,g;if(!(d=this._callbacks))return this;f=d.all;a=a.split(p);for(g=
|
9
|
+
z.call(arguments,1);b=a.shift();){if(c=d[b])for(e=c.tail;(c=c.next)!==e;)c.callback.apply(c.context||this,g);if(c=f){e=c.tail;for(b=[b].concat(g);(c=c.next)!==e;)c.callback.apply(c.context||this,b)}}return this}};k.bind=k.on;k.unbind=k.off;var o=g.Model=function(a,b){var c;a||(a={});b&&b.parse&&(a=this.parse(a));if(c=n(this,"defaults"))a=f.extend({},c,a);b&&b.collection&&(this.collection=b.collection);this.attributes={};this._escapedAttributes={};this.cid=f.uniqueId("c");this.changed={};this._silent=
|
10
|
+
{};this._pending={};this.set(a,{silent:!0});this.changed={};this._silent={};this._pending={};this._previousAttributes=f.clone(this.attributes);this.initialize.apply(this,arguments)};f.extend(o.prototype,k,{changed:null,_silent:null,_pending:null,idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},get:function(a){return this.attributes[a]},escape:function(a){var b;if(b=this._escapedAttributes[a])return b;b=this.get(a);return this._escapedAttributes[a]=f.escape(null==
|
11
|
+
b?"":""+b)},has:function(a){return null!=this.get(a)},set:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c||(c={});if(!d)return this;d instanceof o&&(d=d.attributes);if(c.unset)for(e in d)d[e]=void 0;if(!this._validate(d,c))return!1;this.idAttribute in d&&(this.id=d[this.idAttribute]);var b=c.changes={},h=this.attributes,g=this._escapedAttributes,j=this._previousAttributes||{};for(e in d){a=d[e];if(!f.isEqual(h[e],a)||c.unset&&f.has(h,e))delete g[e],(c.silent?this._silent:
|
12
|
+
b)[e]=!0;c.unset?delete h[e]:h[e]=a;!f.isEqual(j[e],a)||f.has(h,e)!=f.has(j,e)?(this.changed[e]=a,c.silent||(this._pending[e]=!0)):(delete this.changed[e],delete this._pending[e])}c.silent||this.change(c);return this},unset:function(a,b){(b||(b={})).unset=!0;return this.set(a,null,b)},clear:function(a){(a||(a={})).unset=!0;return this.set(f.clone(this.attributes),a)},fetch:function(a){var a=a?f.clone(a):{},b=this,c=a.success;a.success=function(d,e,f){if(!b.set(b.parse(d,f),a))return!1;c&&c(b,d)};
|
13
|
+
a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},save:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c=c?f.clone(c):{};if(c.wait){if(!this._validate(d,c))return!1;e=f.clone(this.attributes)}a=f.extend({},c,{silent:!0});if(d&&!this.set(d,c.wait?a:c))return!1;var h=this,i=c.success;c.success=function(a,b,e){b=h.parse(a,e);if(c.wait){delete c.wait;b=f.extend(d||{},b)}if(!h.set(b,c))return false;i?i(h,a):h.trigger("sync",h,a,c)};c.error=g.wrapError(c.error,
|
14
|
+
h,c);b=this.isNew()?"create":"update";b=(this.sync||g.sync).call(this,b,this,c);c.wait&&this.set(e,a);return b},destroy:function(a){var a=a?f.clone(a):{},b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};if(this.isNew())return d(),!1;a.success=function(e){a.wait&&d();c?c(b,e):b.trigger("sync",b,e,a)};a.error=g.wrapError(a.error,b,a);var e=(this.sync||g.sync).call(this,"delete",this,a);a.wait||d();return e},url:function(){var a=n(this,"urlRoot")||n(this.collection,"url")||t();
|
15
|
+
return this.isNew()?a:a+("/"==a.charAt(a.length-1)?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return null==this.id},change:function(a){a||(a={});var b=this._changing;this._changing=!0;for(var c in this._silent)this._pending[c]=!0;var d=f.extend({},a.changes,this._silent);this._silent={};for(c in d)this.trigger("change:"+c,this,this.get(c),a);if(b)return this;for(;!f.isEmpty(this._pending);){this._pending=
|
16
|
+
{};this.trigger("change",this,a);for(c in this.changed)!this._pending[c]&&!this._silent[c]&&delete this.changed[c];this._previousAttributes=f.clone(this.attributes)}this._changing=!1;return this},hasChanged:function(a){return!arguments.length?!f.isEmpty(this.changed):f.has(this.changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this.changed):!1;var b,c=!1,d=this._previousAttributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return!arguments.length||
|
17
|
+
!this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},isValid:function(){return!this.validate(this.attributes)},_validate:function(a,b){if(b.silent||!this.validate)return!0;var a=f.extend({},this.attributes,a),c=this.validate(a,b);if(!c)return!0;b&&b.error?b.error(this,c,b):this.trigger("error",this,c,b);return!1}});var r=g.Collection=function(a,b){b||(b={});b.model&&(this.model=b.model);b.comparator&&(this.comparator=b.comparator);
|
18
|
+
this._reset();this.initialize.apply(this,arguments);a&&this.reset(a,{silent:!0,parse:b.parse})};f.extend(r.prototype,k,{model:o,initialize:function(){},toJSON:function(a){return this.map(function(b){return b.toJSON(a)})},add:function(a,b){var c,d,e,g,i,j={},k={},l=[];b||(b={});a=f.isArray(a)?a.slice():[a];c=0;for(d=a.length;c<d;c++){if(!(e=a[c]=this._prepareModel(a[c],b)))throw Error("Can't add an invalid model to a collection");g=e.cid;i=e.id;j[g]||this._byCid[g]||null!=i&&(k[i]||this._byId[i])?
|
19
|
+
l.push(c):j[g]=k[i]=e}for(c=l.length;c--;)a.splice(l[c],1);c=0;for(d=a.length;c<d;c++)(e=a[c]).on("all",this._onModelEvent,this),this._byCid[e.cid]=e,null!=e.id&&(this._byId[e.id]=e);this.length+=d;A.apply(this.models,[null!=b.at?b.at:this.models.length,0].concat(a));this.comparator&&this.sort({silent:!0});if(b.silent)return this;c=0;for(d=this.models.length;c<d;c++)if(j[(e=this.models[c]).cid])b.index=c,e.trigger("add",e,this,b);return this},remove:function(a,b){var c,d,e,g;b||(b={});a=f.isArray(a)?
|
20
|
+
a.slice():[a];c=0;for(d=a.length;c<d;c++)if(g=this.getByCid(a[c])||this.get(a[c]))delete this._byId[g.id],delete this._byCid[g.cid],e=this.indexOf(g),this.models.splice(e,1),this.length--,b.silent||(b.index=e,g.trigger("remove",g,this,b)),this._removeReference(g);return this},push:function(a,b){a=this._prepareModel(a,b);this.add(a,b);return a},pop:function(a){var b=this.at(this.length-1);this.remove(b,a);return b},unshift:function(a,b){a=this._prepareModel(a,b);this.add(a,f.extend({at:0},b));return a},
|
21
|
+
shift:function(a){var b=this.at(0);this.remove(b,a);return b},get:function(a){return null==a?void 0:this._byId[null!=a.id?a.id:a]},getByCid:function(a){return a&&this._byCid[a.cid||a]},at:function(a){return this.models[a]},where:function(a){return f.isEmpty(a)?[]:this.filter(function(b){for(var c in a)if(a[c]!==b.get(c))return!1;return!0})},sort:function(a){a||(a={});if(!this.comparator)throw Error("Cannot sort a set without a comparator");var b=f.bind(this.comparator,this);1==this.comparator.length?
|
22
|
+
this.models=this.sortBy(b):this.models.sort(b);a.silent||this.trigger("reset",this,a);return this},pluck:function(a){return f.map(this.models,function(b){return b.get(a)})},reset:function(a,b){a||(a=[]);b||(b={});for(var c=0,d=this.models.length;c<d;c++)this._removeReference(this.models[c]);this._reset();this.add(a,f.extend({silent:!0},b));b.silent||this.trigger("reset",this,b);return this},fetch:function(a){a=a?f.clone(a):{};void 0===a.parse&&(a.parse=!0);var b=this,c=a.success;a.success=function(d,
|
23
|
+
e,f){b[a.add?"add":"reset"](b.parse(d,f),a);c&&c(b,d)};a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},create:function(a,b){var c=this,b=b?f.clone(b):{},a=this._prepareModel(a,b);if(!a)return!1;b.wait||c.add(a,b);var d=b.success;b.success=function(e,f){b.wait&&c.add(e,b);d?d(e,f):e.trigger("sync",a,f,b)};a.save(null,b);return a},parse:function(a){return a},chain:function(){return f(this.models).chain()},_reset:function(){this.length=0;this.models=[];this._byId=
|
24
|
+
{};this._byCid={}},_prepareModel:function(a,b){b||(b={});a instanceof o?a.collection||(a.collection=this):(b.collection=this,a=new this.model(a,b),a._validate(a.attributes,b)||(a=!1));return a},_removeReference:function(a){this==a.collection&&delete a.collection;a.off("all",this._onModelEvent,this)},_onModelEvent:function(a,b,c,d){("add"==a||"remove"==a)&&c!=this||("destroy"==a&&this.remove(b,d),b&&a==="change:"+b.idAttribute&&(delete this._byId[b.previous(b.idAttribute)],this._byId[b.id]=b),this.trigger.apply(this,
|
25
|
+
arguments))}});f.each("forEach,each,map,reduce,reduceRight,find,detect,filter,select,reject,every,all,some,any,include,contains,invoke,max,min,sortBy,sortedIndex,toArray,size,first,initial,rest,last,without,indexOf,shuffle,lastIndexOf,isEmpty,groupBy".split(","),function(a){r.prototype[a]=function(){return f[a].apply(f,[this.models].concat(f.toArray(arguments)))}});var u=g.Router=function(a){a||(a={});a.routes&&(this.routes=a.routes);this._bindRoutes();this.initialize.apply(this,arguments)},B=/:\w+/g,
|
26
|
+
C=/\*\w+/g,D=/[-[\]{}()+?.,\\^$|#\s]/g;f.extend(u.prototype,k,{initialize:function(){},route:function(a,b,c){g.history||(g.history=new m);f.isRegExp(a)||(a=this._routeToRegExp(a));c||(c=this[b]);g.history.route(a,f.bind(function(d){d=this._extractParameters(a,d);c&&c.apply(this,d);this.trigger.apply(this,["route:"+b].concat(d));g.history.trigger("route",this,b,d)},this));return this},navigate:function(a,b){g.history.navigate(a,b)},_bindRoutes:function(){if(this.routes){var a=[],b;for(b in this.routes)a.unshift([b,
|
27
|
+
this.routes[b]]);b=0;for(var c=a.length;b<c;b++)this.route(a[b][0],a[b][1],this[a[b][1]])}},_routeToRegExp:function(a){a=a.replace(D,"\\$&").replace(B,"([^/]+)").replace(C,"(.*?)");return RegExp("^"+a+"$")},_extractParameters:function(a,b){return a.exec(b).slice(1)}});var m=g.History=function(){this.handlers=[];f.bindAll(this,"checkUrl")},s=/^[#\/]/,E=/msie [\w.]+/;m.started=!1;f.extend(m.prototype,k,{interval:50,getHash:function(a){return(a=(a?a.location:window.location).href.match(/#(.*)$/))?a[1]:
|
28
|
+
""},getFragment:function(a,b){if(null==a)if(this._hasPushState||b){var a=window.location.pathname,c=window.location.search;c&&(a+=c)}else a=this.getHash();a.indexOf(this.options.root)||(a=a.substr(this.options.root.length));return a.replace(s,"")},start:function(a){if(m.started)throw Error("Backbone.history has already been started");m.started=!0;this.options=f.extend({},{root:"/"},this.options,a);this._wantsHashChange=!1!==this.options.hashChange;this._wantsPushState=!!this.options.pushState;this._hasPushState=
|
29
|
+
!(!this.options.pushState||!window.history||!window.history.pushState);var a=this.getFragment(),b=document.documentMode;if(b=E.exec(navigator.userAgent.toLowerCase())&&(!b||7>=b))this.iframe=i('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow,this.navigate(a);this._hasPushState?i(window).bind("popstate",this.checkUrl):this._wantsHashChange&&"onhashchange"in window&&!b?i(window).bind("hashchange",this.checkUrl):this._wantsHashChange&&(this._checkUrlInterval=setInterval(this.checkUrl,
|
30
|
+
this.interval));this.fragment=a;a=window.location;b=a.pathname==this.options.root;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,!0),window.location.replace(this.options.root+"#"+this.fragment),!0;this._wantsPushState&&this._hasPushState&&b&&a.hash&&(this.fragment=this.getHash().replace(s,""),window.history.replaceState({},document.title,a.protocol+"//"+a.host+this.options.root+this.fragment));if(!this.options.silent)return this.loadUrl()},
|
31
|
+
stop:function(){i(window).unbind("popstate",this.checkUrl).unbind("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);m.started=!1},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a==this.fragment&&this.iframe&&(a=this.getFragment(this.getHash(this.iframe)));if(a==this.fragment)return!1;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(this.getHash())},loadUrl:function(a){var b=this.fragment=this.getFragment(a);return f.any(this.handlers,
|
32
|
+
function(a){if(a.route.test(b))return a.callback(b),!0})},navigate:function(a,b){if(!m.started)return!1;if(!b||!0===b)b={trigger:b};var c=(a||"").replace(s,"");this.fragment!=c&&(this._hasPushState?(0!=c.indexOf(this.options.root)&&(c=this.options.root+c),this.fragment=c,window.history[b.replace?"replaceState":"pushState"]({},document.title,c)):this._wantsHashChange?(this.fragment=c,this._updateHash(window.location,c,b.replace),this.iframe&&c!=this.getFragment(this.getHash(this.iframe))&&(b.replace||
|
33
|
+
this.iframe.document.open().close(),this._updateHash(this.iframe.location,c,b.replace))):window.location.assign(this.options.root+a),b.trigger&&this.loadUrl(a))},_updateHash:function(a,b,c){c?a.replace(a.toString().replace(/(javascript:|#).*$/,"")+"#"+b):a.hash=b}});var v=g.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()},F=/^(\S+)\s*(.*)$/,w="model,collection,el,id,attributes,className,tagName".split(",");
|
34
|
+
f.extend(v.prototype,k,{tagName:"div",$:function(a){return this.$el.find(a)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();return this},make:function(a,b,c){a=document.createElement(a);b&&i(a).attr(b);c&&i(a).html(c);return a},setElement:function(a,b){this.$el&&this.undelegateEvents();this.$el=a instanceof i?a:i(a);this.el=this.$el[0];!1!==b&&this.delegateEvents();return this},delegateEvents:function(a){if(a||(a=n(this,"events"))){this.undelegateEvents();
|
35
|
+
for(var b in a){var c=a[b];f.isFunction(c)||(c=this[a[b]]);if(!c)throw Error('Method "'+a[b]+'" does not exist');var d=b.match(F),e=d[1],d=d[2],c=f.bind(c,this),e=e+(".delegateEvents"+this.cid);""===d?this.$el.bind(e,c):this.$el.delegate(d,e,c)}}},undelegateEvents:function(){this.$el.unbind(".delegateEvents"+this.cid)},_configure:function(a){this.options&&(a=f.extend({},this.options,a));for(var b=0,c=w.length;b<c;b++){var d=w[b];a[d]&&(this[d]=a[d])}this.options=a},_ensureElement:function(){if(this.el)this.setElement(this.el,
|
36
|
+
!1);else{var a=n(this,"attributes")||{};this.id&&(a.id=this.id);this.className&&(a["class"]=this.className);this.setElement(this.make(this.tagName,a),!1)}}});o.extend=r.extend=u.extend=v.extend=function(a,b){var c=G(this,a,b);c.extend=this.extend;return c};var H={create:"POST",update:"PUT","delete":"DELETE",read:"GET"};g.sync=function(a,b,c){var d=H[a];c||(c={});var e={type:d,dataType:"json"};c.url||(e.url=n(b,"url")||t());if(!c.data&&b&&("create"==a||"update"==a))e.contentType="application/json",
|
37
|
+
e.data=JSON.stringify(b.toJSON());g.emulateJSON&&(e.contentType="application/x-www-form-urlencoded",e.data=e.data?{model:e.data}:{});if(g.emulateHTTP&&("PUT"===d||"DELETE"===d))g.emulateJSON&&(e.data._method=d),e.type="POST",e.beforeSend=function(a){a.setRequestHeader("X-HTTP-Method-Override",d)};"GET"!==e.type&&!g.emulateJSON&&(e.processData=!1);return i.ajax(f.extend(e,c))};g.wrapError=function(a,b,c){return function(d,e){e=d===b?e:d;a?a(b,e,c):b.trigger("error",b,e,c)}};var x=function(){},G=function(a,
|
38
|
+
b,c){var d;d=b&&b.hasOwnProperty("constructor")?b.constructor:function(){a.apply(this,arguments)};f.extend(d,a);x.prototype=a.prototype;d.prototype=new x;b&&f.extend(d.prototype,b);c&&f.extend(d,c);d.prototype.constructor=d;d.__super__=a.prototype;return d},n=function(a,b){return!a||!a[b]?null:f.isFunction(a[b])?a[b]():a[b]},t=function(){throw Error('A "url" property or function must be specified');}}).call(this);
|