microservice_precompiler 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/.gitignore +21 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE +22 -0
  4. data/README.md +94 -0
  5. data/Rakefile +37 -0
  6. data/lib/microservice_precompiler.rb +14 -0
  7. data/lib/microservice_precompiler/builder.rb +146 -0
  8. data/lib/microservice_precompiler/version.rb +3 -0
  9. data/microservice_precompiler.gemspec +28 -0
  10. data/test/dummy/config.rb +21 -0
  11. data/test/dummy/images/link_icons/doc.png +0 -0
  12. data/test/dummy/images/link_icons/email.png +0 -0
  13. data/test/dummy/images/link_icons/external.png +0 -0
  14. data/test/dummy/images/link_icons/feed.png +0 -0
  15. data/test/dummy/images/link_icons/im.png +0 -0
  16. data/test/dummy/images/link_icons/pdf.png +0 -0
  17. data/test/dummy/images/link_icons/visited.png +0 -0
  18. data/test/dummy/images/link_icons/xls.png +0 -0
  19. data/test/dummy/javascripts/lib/backbone/backbone-min.js +38 -0
  20. data/test/dummy/javascripts/lib/jquery/jquery-1.7.2.min.js +4 -0
  21. data/test/dummy/javascripts/lib/jquery/jquery-ui-1.8.19.custom.min.js +125 -0
  22. data/test/dummy/javascripts/lib/require/require.js +33 -0
  23. data/test/dummy/javascripts/lib/underscore/underscore-min.js +32 -0
  24. data/test/dummy/javascripts/sample.js.coffee +2 -0
  25. data/test/dummy/mustaches.yml.tml +8 -0
  26. data/test/dummy/sass/dependencies/link_icons.scss +13 -0
  27. data/test/dummy/sass/dependencies/sample.scss +3 -0
  28. data/test/dummy/sass/ie.scss +5 -0
  29. data/test/dummy/sass/print.scss +3 -0
  30. data/test/dummy/sass/screen.scss +10 -0
  31. data/test/dummy/templates/sample.html.mustache +1 -0
  32. data/test/dummy/templates/sample.rb +7 -0
  33. data/test/dummy/templates/sample1.rb +7 -0
  34. data/test/dummy/templates/sample_folder/sample_folder_test.html.mustache +1 -0
  35. data/test/dummy/templates/sample_folder/sample_folder_test.rb +7 -0
  36. data/test/dummy/templates/sample_with_underscore.rb +7 -0
  37. data/test/microservice_precompiler_test.rb +7 -0
  38. data/test/test_helper.rb +2 -0
  39. data/test/unit/builder_test.rb +59 -0
  40. 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
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in microservice_precompiler.gemspec
4
+ gemspec
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,3 @@
1
+ module MicroservicePrecompiler
2
+ VERSION = "0.1.0"
3
+ 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
@@ -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);