qedproject 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. data/bin/qedproject +49 -0
  2. data/lib/qedproject/helpers.rb +17 -0
  3. data/lib/qedproject/libraries/backbone.rb +9 -0
  4. data/lib/qedproject/libraries/base.rb +105 -0
  5. data/lib/qedproject/libraries/jquery.rb +9 -0
  6. data/lib/qedproject/libraries/jquerytmpl.rb +9 -0
  7. data/lib/qedproject/libraries/knockout.rb +9 -0
  8. data/lib/qedproject/libraries/skeleton.rb +21 -0
  9. data/lib/qedproject/project.rb +185 -0
  10. data/lib/qedproject/version.rb +3 -0
  11. data/lib/qedproject.rb +14 -0
  12. data/templates/Guardfile +29 -0
  13. data/templates/Rakefile +25 -0
  14. data/templates/assets.yml +10 -0
  15. data/templates/index.html +35 -0
  16. data/templates/sampleSpec.coffee +0 -0
  17. data/templates/sampleSpec.js +7 -0
  18. data/templates/suite.html +23 -0
  19. data/vendor/backbone/VERSION +1 -0
  20. data/vendor/backbone/backbone-min.js +33 -0
  21. data/vendor/backbone/json2.js +480 -0
  22. data/vendor/backbone/underscore-min.js +27 -0
  23. data/vendor/jasmine/lib/jasmine-1.0.2/jasmine-html.js +188 -0
  24. data/vendor/jasmine/lib/jasmine-1.0.2/jasmine.css +166 -0
  25. data/vendor/jasmine/lib/jasmine-1.0.2/jasmine.js +2421 -0
  26. data/vendor/jquery/VERSION +1 -0
  27. data/vendor/jquery/jquery-1.6.2.min.js +18 -0
  28. data/vendor/jquerytmpl/VERSION +1 -0
  29. data/vendor/jquerytmpl/jquery.tmpl.min.js +10 -0
  30. data/vendor/knockout/VERSION +1 -0
  31. data/vendor/knockout/knockout-1.2.1.js +76 -0
  32. data/vendor/skeleton/VERSION +1 -0
  33. data/vendor/skeleton/apple-touch-icon-114x114.png +0 -0
  34. data/vendor/skeleton/apple-touch-icon-72x72.png +0 -0
  35. data/vendor/skeleton/apple-touch-icon.png +0 -0
  36. data/vendor/skeleton/base.css +335 -0
  37. data/vendor/skeleton/favicon.ico +0 -0
  38. data/vendor/skeleton/layout.css +58 -0
  39. data/vendor/skeleton/skeleton.css +236 -0
  40. data/vendor/skeleton/tabs.js +42 -0
  41. data/vendor/skeleton/templates/index.html +94 -0
  42. metadata +219 -0
data/bin/qedproject ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+ require 'qedproject'
3
+
4
+ require 'optparse'
5
+
6
+ banner = "QEDProject #{QEDProject::VERSION} (C) 2011 Brian P. Hogan\n"
7
+ banner << "-" * banner.length
8
+ puts banner
9
+
10
+ options = {}
11
+ LIBRARIES = QEDProject::Libraries::Base.libs.keys
12
+ OptionParser.new do |opts|
13
+ opts.banner = "Usage: qedproject <projectname> [options]"
14
+
15
+
16
+ opts.on('-s', '--sass', "Use SASS") do
17
+ options[:sass] = true
18
+ end
19
+
20
+ opts.on('-c', '--coffee', "Use Coffeescript") do
21
+ options[:coffeescript] = true
22
+ end
23
+
24
+ opts.on('-a', '--assets', "Use Jammit to manage assets") do
25
+ options[:jammit] = true
26
+ end
27
+
28
+ opts.on('-t', '--testing', "Set up testing with Jasmine") do
29
+ options[:testing] = true
30
+ end
31
+
32
+ opts.on("-l", "--libs [LIBRARIES]", "comma-separated list of libraries to include.\nLibraries are: #{LIBRARIES.join(",")}") do |libs|
33
+ options[:libs] = libs.split(",").collect{ |f| f.to_sym }
34
+ end
35
+ end.parse!
36
+ options[:verbose] = true
37
+ project = ARGV[0]
38
+
39
+ if project.nil?
40
+ puts "Specify a project. Use qedproject -h for options."
41
+ else
42
+ begin
43
+ QEDProject::Project.create(project, options)
44
+ rescue QEDProject::BadLibraryError => e
45
+ puts e.message
46
+ puts "QEDProject supports #{LIBRARIES.join(",")}"
47
+ end
48
+ end
49
+
@@ -0,0 +1,17 @@
1
+ module QEDProject
2
+ module Helpers
3
+ # Reads a template from the file system,
4
+ # evaluates it with ERB
5
+ # places it in the output folder specified.
6
+ # Takes the binding context as the last parameter
7
+ # so that ERb has access to instance variables, etc.
8
+ # This works similar to how Rails and Sinatra templates
9
+ # work.
10
+ def render_template_to_file(template, file, context)
11
+ t = File.read(File.join(self.template_root, template))
12
+ File.open(file, "w") do |f|
13
+ f << ERB.new(t, nil, "-").result(context)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,9 @@
1
+ module QEDProject
2
+ module Libraries
3
+ class Backbone < QEDProject::Libraries::Base
4
+ library :backbone
5
+ set_js_files ["json2.js", "underscore-min.js", "backbone-min.js"]
6
+ end
7
+ end
8
+ end
9
+
@@ -0,0 +1,105 @@
1
+ module QEDProject
2
+ module Libraries
3
+
4
+ class Base
5
+ include QEDProject::Helpers
6
+
7
+ attr_accessor :template_root, :lib_root, :project
8
+ class << self
9
+
10
+ attr_accessor :libs
11
+
12
+ # Support for DSL, for setting the library name
13
+ # when creating the adapter.
14
+ # Adds the library and class to QEDProject::Libraries::Base.libs hash
15
+ # and also creates a getter method on the adapter instance
16
+ def library(name)
17
+ QEDProject::Libraries::Base.libs ||= {}
18
+ QEDProject::Libraries::Base.libs[name] = self
19
+ class_eval do
20
+ define_method :library do
21
+ name
22
+ end
23
+ end
24
+ end
25
+
26
+ def set_js_files(files)
27
+ # define class method
28
+ m = class << self; self; end
29
+ m.send :define_method, :js_files do
30
+ files
31
+ end
32
+
33
+ # define instance method
34
+ class_eval do
35
+ define_method :js_files do
36
+ files
37
+ end
38
+ end
39
+ end
40
+
41
+ # convenience method for the DSL for setting CSS files
42
+ def set_css_files(files)
43
+ # define class method to get the data out
44
+ m = class << self; self; end
45
+ m.send :define_method, :css_files do
46
+ files
47
+ end
48
+
49
+ class_eval do
50
+ define_method :css_files do
51
+ files
52
+ end
53
+ end
54
+ end
55
+
56
+
57
+ # convenience method for the DSL for setting image files.
58
+ def set_image_files(files)
59
+
60
+ m = class << self; self; end
61
+ m.send :define_method, :image_files do
62
+ files
63
+ end
64
+
65
+ class_eval do
66
+ define_method :image_files do
67
+ files
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ def initialize(project)
74
+ @project = project
75
+ @lib_root = File.join(@project.vendor_root, self.library.to_s)
76
+ @template_root = File.join(@lib_root, "templates")
77
+ end
78
+
79
+ def generate!
80
+ self.copy_js if self.respond_to?(:js_files) && self.js_files.any?
81
+ self.copy_css if self.respond_to?(:css_files) && self.css_files.any?
82
+ self.copy_images if self.respond_to?(:image_files) && self.image_files.any?
83
+ end
84
+
85
+ def copy_images
86
+ self.image_files.each do |lib|
87
+ ::FileUtils.cp_r File.join(self.lib_root, lib), File.join(self.project.path, self.project.images_path), :verbose => self.project.verbose
88
+ end
89
+ end
90
+
91
+ def copy_css
92
+ self.css_files.each do |lib|
93
+ ::FileUtils.cp_r File.join(self.lib_root, lib), File.join(self.project.path, self.project.css_path), :verbose => self.project.verbose
94
+ end
95
+ end
96
+
97
+ def copy_js
98
+ self.js_files.each do |lib|
99
+ ::FileUtils.cp_r File.join(self.lib_root, lib), File.join(self.project.path, self.project.js_path), :verbose => self.project.verbose
100
+ end
101
+ end
102
+
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,9 @@
1
+ module QEDProject
2
+ module Libraries
3
+ class JQuery < QEDProject::Libraries::Base
4
+ library :jquery
5
+ set_js_files ["jquery-1.6.2.min.js"]
6
+ end
7
+ end
8
+ end
9
+
@@ -0,0 +1,9 @@
1
+ module QEDProject
2
+ module Libraries
3
+ class JQuerytmpl < QEDProject::Libraries::Base
4
+ library :jquerytmpl
5
+ set_js_files ["jquery.tmpl.min.js"]
6
+ end
7
+ end
8
+ end
9
+
@@ -0,0 +1,9 @@
1
+ module QEDProject
2
+ module Libraries
3
+ class Knockout < QEDProject::Libraries::Base
4
+ library :knockout
5
+ set_js_files ["knockout-1.2.1.js"]
6
+ end
7
+ end
8
+ end
9
+
@@ -0,0 +1,21 @@
1
+ module QEDProject
2
+ module Libraries
3
+ class Skeleton < QEDProject::Libraries::Base
4
+ library :skeleton
5
+ set_js_files ["tabs.js"]
6
+ set_css_files ["base.css", "skeleton.css", "layout.css" ]
7
+ set_image_files [
8
+ "apple-touch-icon-114x114.png",
9
+ "apple-touch-icon-72x72.png",
10
+ "apple-touch-icon.png",
11
+ "favicon.ico"
12
+ ]
13
+
14
+ def generate!
15
+ super
16
+ render_template_to_file "index.html", File.join(self.project.path, "public", "index.html"), binding
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,185 @@
1
+ require 'fileutils'
2
+ require 'erb'
3
+ module QEDProject
4
+
5
+ class Project
6
+
7
+ include QEDProject::Helpers
8
+
9
+ attr_accessor :path, :libs, :coffeescript, :sass, :jammit,
10
+ :js_path, :css_path, :images_path, :verbose, :testing
11
+ # convenience method to create a new project.
12
+ # Simply call create with the project path and the options.
13
+ def self.create(project_path, options= {})
14
+ self.new(project_path, options).generate
15
+ end
16
+
17
+ def template_root
18
+ File.expand_path("../../../templates", __FILE__)
19
+ end
20
+
21
+ def vendor_root
22
+ File.expand_path("../../../vendor", __FILE__)
23
+ end
24
+
25
+ # Creates a new Project instance.
26
+ # Options:
27
+ # :libs : A list of js libraries to include, Can be :backbone, :jquery, :knockout
28
+ # :jammit: boolean, should Jammit be included?
29
+ # :coffeescript : Should CoffeeScript support be included?
30
+ # :sass Should sass support be included?
31
+ def initialize(project_path, options = {})
32
+ self.path = project_path
33
+ self.process_options(options)
34
+ self.set_paths
35
+ end
36
+
37
+
38
+ # Convenience method for showing all of the JavaScript files that
39
+ # the project should contain. Good for creating asset lists
40
+ # and loading files in rendered templates
41
+ def js_assets
42
+ libs.inject([]) do |original, lib|
43
+ if QEDProject::Libraries::Base.libs[lib].respond_to?(:js_files)
44
+ original += QEDProject::Libraries::Base.libs[lib].js_files
45
+ else
46
+ original
47
+ end
48
+ end
49
+ end
50
+
51
+ # Convenience method for showing all of the css files that
52
+ # the project should contain. Good for creating asset lists
53
+ # and loading files in rendered templates
54
+ def css_assets
55
+ libs.inject([]) do |original, lib|
56
+ if QEDProject::Libraries::Base.libs[lib].respond_to?(:css_files)
57
+ original += QEDProject::Libraries::Base.libs[lib].css_files
58
+ else
59
+ original
60
+ end
61
+ end
62
+ end
63
+
64
+ # Sets instance methods with values from the options hash.
65
+ def process_options(options)
66
+ self.libs = options[:libs] || []
67
+
68
+ libs.each do |lib|
69
+ raise QEDProject::BadLibraryError, "#{lib} is not a valid library" unless QEDProject::Libraries::Base.libs.include? lib
70
+ end
71
+
72
+ self.jammit = options[:jammit]
73
+ self.sass = options[:sass]
74
+ self.coffeescript = options[:coffeescript]
75
+ self.verbose = options[:verbose]
76
+ self.testing = options[:testing]
77
+ end
78
+
79
+ # Set up the basic paths we'll use throughout
80
+ def set_paths
81
+ self.images_path = File.join("public", "images")
82
+ self.js_path = self.jammit ? "javascripts" : File.join("public", "javascripts")
83
+ self.css_path = self.jammit ? "stylesheets" : File.join("public", "stylesheets")
84
+ end
85
+
86
+ # We need a guardfile if the user is using jammit, sass, or coffeescript.
87
+ def needs_guardfile?
88
+ self.jammit || self.sass || self.coffeescript
89
+ end
90
+
91
+ # Only jammit needs a config folder for now.
92
+ def needs_config_folder?
93
+ self.jammit
94
+ end
95
+
96
+ # Start the project generation.
97
+ # Create the folder
98
+ # Get all of the libraries
99
+ # create an assets file if needed
100
+ # add the testing folder if needed
101
+ # create a guardfile if needed
102
+ def generate
103
+ self.create_project_skeleton
104
+ self.create_index
105
+ self.get_libraries if self.libs
106
+ self.create_asset_file if self.jammit
107
+ self.add_testing if self.testing
108
+ self.create_guardfile if self.needs_guardfile?
109
+ self.create_rakefile
110
+ end
111
+
112
+ # includes the Jasmine BDD framework for javascript testing
113
+ def add_testing
114
+ FileUtils.mkdir_p File.join(self.path, "spec"), :verbose => self.verbose
115
+ FileUtils.cp_r File.join(self.vendor_root, "jasmine", "lib"),
116
+ File.join(self.path, "spec", "lib"), :verbose => self.verbose
117
+
118
+
119
+ render_template_to_file "suite.html", File.join(self.path, "spec", "suite.html"), binding
120
+ if self.coffeescript
121
+ render_template_to_file "sampleSpec.coffee", File.join(self.path, "spec", "sampleSpec.coffee"), binding
122
+ else
123
+ render_template_to_file "sampleSpec.js", File.join(self.path, "spec", "sampleSpec.js"), binding
124
+ end
125
+
126
+ end
127
+
128
+ # Set up the main project skeleton
129
+ # project
130
+ # public/
131
+ # assets/ * only with assets
132
+ # images/
133
+ # javascripts/ * only without assets
134
+ # stylesheets/ * only without assets
135
+ # config/
136
+ # assets.yml * optional
137
+ # coffeescripts/ * optional
138
+ # sass/ * optional
139
+ # spec/ * optional
140
+ # Guardfile * optional
141
+ def create_project_skeleton
142
+ ::FileUtils.mkdir_p( self.path, :verbose => self.verbose)
143
+ ::FileUtils.mkdir_p( File.join(self.path, "public"), :verbose => self.verbose)
144
+ ::FileUtils.mkdir_p( File.join(self.path, self.images_path), :verbose => self.verbose)
145
+ ::FileUtils.mkdir_p( File.join(self.path, "tmp"), :verbose => self.verbose)
146
+ ::FileUtils.mkdir_p( File.join(self.path, self.js_path), :verbose => self.verbose)
147
+ ::FileUtils.mkdir_p( File.join(self.path, self.css_path), :verbose => self.verbose)
148
+ ::FileUtils.mkdir_p( File.join(self.path, "config"), :verbose => self.verbose) if self.needs_config_folder?
149
+ ::FileUtils.mkdir_p( File.join(self.path, "coffeescripts"), :verbose => self.verbose) if self.coffeescript
150
+ ::FileUtils.mkdir_p( File.join(self.path, "sass"), :verbose => self.verbose) if self.sass
151
+ end
152
+
153
+ # Loop through the libraries the user added
154
+ # and run their generate methods
155
+ # which pulls in the additional optional files.
156
+ def get_libraries
157
+ libs.each do |lib|
158
+ library = QEDProject::Libraries::Base.libs[lib]
159
+ l = library.new(self)
160
+ l.generate!
161
+ end
162
+ end
163
+
164
+ def create_asset_file
165
+ render_template_to_file("assets.yml", File.join(self.path, "config", "assets.yml"), binding)
166
+ puts "Created #{File.join(self.path, 'config', 'assets.yml')}" if self.verbose
167
+ end
168
+
169
+ def create_guardfile
170
+ render_template_to_file "Guardfile", File.join(self.path, "Guardfile"), binding
171
+ puts "Created #{ File.join(self.path, "Guardfile")}" if self.verbose
172
+ end
173
+
174
+ def create_index
175
+ @project = self
176
+ render_template_to_file "index.html", File.join(self.path, "public", "index.html"), binding
177
+ puts "Created #{ File.join(self.path, "public", "index.html")}" if self.verbose
178
+ end
179
+
180
+ def create_rakefile
181
+ render_template_to_file "Rakefile", File.join(self.path, "Rakefile"), binding
182
+ puts "Created #{ File.join(self.path, "Rakefile")}" if self.verbose
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,3 @@
1
+ module QEDProject
2
+ VERSION = "0.0.4"
3
+ end
data/lib/qedproject.rb ADDED
@@ -0,0 +1,14 @@
1
+ require "qedproject/version"
2
+ require "qedproject/helpers"
3
+ require "qedproject/libraries/base"
4
+ require "qedproject/libraries/jquery"
5
+ require "qedproject/libraries/backbone"
6
+ require "qedproject/libraries/knockout"
7
+ require "qedproject/libraries/jquerytmpl"
8
+ require "qedproject/libraries/skeleton"
9
+ require "qedproject/project"
10
+
11
+ module QEDProject
12
+ class BadLibraryError < RuntimeError
13
+ end
14
+ end
@@ -0,0 +1,29 @@
1
+ <% if self.sass -%>
2
+ <% if self.jammit -%>
3
+ guard "sass", :input => "sass", :output => "tmp"
4
+ <% else -%>
5
+ guard "sass", :input => "sass", :output => "public/stylesheets"
6
+ <% end -%>
7
+ <% end -%>
8
+ <% if self.coffeescript -%>
9
+ <% if self.jammit -%>
10
+ guard "coffeescript", :input => "coffeescripts", :output => "tmp"
11
+ <% else -%>
12
+ guard "coffeescript", :input => "coffeescripts", :output => "public/javascripts"
13
+ <% end -%>
14
+ <% if self.testing -%>
15
+ guard "coffeescript", :input => "spec", :output => "spec"
16
+ <% end -%>
17
+ <% end -%>
18
+ <% if self.jammit -%>
19
+ guard "jammit" do
20
+ watch(/^javascripts\\/(.*)\.js/)
21
+ watch(/^stylesheets\\/(.*)\.css/)
22
+ <% if self.coffeescript -%>
23
+ watch(/^tmp\\/(.*)\\.js/)
24
+ <% end -%>
25
+ <% if self.sass -%>
26
+ watch(/^tmp\\/(.*)\\.css/)
27
+ <% end -%>
28
+ end
29
+ <% end -%>
@@ -0,0 +1,25 @@
1
+ SERVER = "yourhost.com"
2
+ USERNAME = "yourusername"
3
+ REMOTE_FOLDER = "/home/#{USERNAME}/yourhost.com"
4
+
5
+ require 'net/scp'
6
+
7
+ <% if self.needs_guardfile? -%>
8
+ desc "compile CSS, js files"
9
+ task :compile do
10
+ require 'guard'
11
+ Guard.setup
12
+ Guard::Dsl.evaluate_guardfile
13
+ Guard::guards.each{|guard| guard.run_all}
14
+ end
15
+ <% end -%>
16
+ desc "Deploy web site"
17
+ <% if self.needs_guardfile? -%>
18
+ task :deploy => :compile do
19
+ <% else -%>
20
+ task :deploy do
21
+ <% end -%>
22
+ Net::SCP.start(SERVER, USERNAME, {:port => PORT} ) do |scp|
23
+ scp.upload! "public", REMOTE_FOLDER, :recursive => true
24
+ end
25
+ end
@@ -0,0 +1,10 @@
1
+ stylesheets:
2
+ style.css:
3
+ <% self.css_assets.each do |lib| -%>
4
+ - <%=File.join(self.css_path, lib) %>
5
+ <% end -%>
6
+ javascripts:
7
+ app.js:
8
+ <% self.js_assets.each do |lib| -%>
9
+ - <%=File.join(self.js_path, lib) %>
10
+ <% end -%>
@@ -0,0 +1,35 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Project</title>
5
+ <% if @project.jammit %>
6
+ <link rel="stylesheet" href="assets/app.css">
7
+ <% else -%>
8
+ <link rel="stylesheet" href="stylesheets/style.css">
9
+ <% end -%>
10
+
11
+ </head>
12
+ <body>
13
+
14
+ <h1>My Project</h1>
15
+
16
+ <p>
17
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
18
+ eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
19
+ minim veniam, quis nostrud exercitation ullamco laboris nisi ut
20
+ aliquip ex ea commodo consequat. Duis aute irure dolor in
21
+ reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
22
+ pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
23
+ culpa qui officia deserunt mollit anim id est laborum.
24
+ </p>
25
+
26
+
27
+ <% if @project.jammit %>
28
+ <script src="assets/app.js"></script>
29
+ <% else -%>
30
+ <% @project.js_assets.each do |js| -%>
31
+ <script src="javascripts/<%=js %>"></script>
32
+ <% end -%>
33
+ <% end -%>
34
+ </body>
35
+ </html>
File without changes
@@ -0,0 +1,7 @@
1
+ (function() {
2
+ describe("A sample spec", function() {
3
+ return it("performs addition", function() {
4
+ return (expect(1 + 1)).toEqual(3);
5
+ });
6
+ });
7
+ }).call(this);
@@ -0,0 +1,23 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Jasmine Test Runner</title>
5
+ <link rel="stylesheet" type="text/css" href="lib/jasmine-1.0.2/jasmine.css">
6
+ <script type="text/javascript" src="lib/jasmine-1.0.2/jasmine.js"></script>
7
+ <script type="text/javascript" src="lib/jasmine-1.0.2/jasmine-html.js"></script>
8
+
9
+ <!-- include source files here... -->
10
+
11
+ <!-- include spec files here... -->
12
+ <script type="text/javascript" src="sampleSpec.js"></script>
13
+
14
+ </head>
15
+ <body>
16
+
17
+ <script type="text/javascript">
18
+ jasmine.getEnv().addReporter(new jasmine.TrivialReporter());
19
+ jasmine.getEnv().execute();
20
+ </script>
21
+
22
+ </body>
23
+ </html>
@@ -0,0 +1 @@
1
+ 0.5.3
@@ -0,0 +1,33 @@
1
+ // Backbone.js 0.5.3
2
+ // (c) 2010 Jeremy Ashkenas, DocumentCloud Inc.
3
+ // Backbone may be freely distributed under the MIT license.
4
+ // For all details and documentation:
5
+ // http://documentcloud.github.com/backbone
6
+ (function(){var h=this,p=h.Backbone,e;e=typeof exports!=="undefined"?exports:h.Backbone={};e.VERSION="0.5.3";var f=h._;if(!f&&typeof require!=="undefined")f=require("underscore")._;var g=h.jQuery||h.Zepto;e.noConflict=function(){h.Backbone=p;return this};e.emulateHTTP=!1;e.emulateJSON=!1;e.Events={bind:function(a,b,c){var d=this._callbacks||(this._callbacks={});(d[a]||(d[a]=[])).push([b,c]);return this},unbind:function(a,b){var c;if(a){if(c=this._callbacks)if(b){c=c[a];if(!c)return this;for(var d=
7
+ 0,e=c.length;d<e;d++)if(c[d]&&b===c[d][0]){c[d]=null;break}}else c[a]=[]}else this._callbacks={};return this},trigger:function(a){var b,c,d,e,f=2;if(!(c=this._callbacks))return this;for(;f--;)if(b=f?a:"all",b=c[b])for(var g=0,h=b.length;g<h;g++)(d=b[g])?(e=f?Array.prototype.slice.call(arguments,1):arguments,d[0].apply(d[1]||this,e)):(b.splice(g,1),g--,h--);return this}};e.Model=function(a,b){var c;a||(a={});if(c=this.defaults)f.isFunction(c)&&(c=c.call(this)),a=f.extend({},c,a);this.attributes={};
8
+ this._escapedAttributes={};this.cid=f.uniqueId("c");this.set(a,{silent:!0});this._changed=!1;this._previousAttributes=f.clone(this.attributes);if(b&&b.collection)this.collection=b.collection;this.initialize(a,b)};f.extend(e.Model.prototype,e.Events,{_previousAttributes:null,_changed:!1,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.attributes[a];
9
+ return this._escapedAttributes[a]=(b==null?"":""+b).replace(/&(?!\w+;|#\d+;|#x[\da-f]+;)/gi,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;").replace(/\//g,"&#x2F;")},has:function(a){return this.attributes[a]!=null},set:function(a,b){b||(b={});if(!a)return this;if(a.attributes)a=a.attributes;var c=this.attributes,d=this._escapedAttributes;if(!b.silent&&this.validate&&!this._performValidation(a,b))return!1;if(this.idAttribute in a)this.id=a[this.idAttribute];
10
+ var e=this._changing;this._changing=!0;for(var g in a){var h=a[g];if(!f.isEqual(c[g],h))c[g]=h,delete d[g],this._changed=!0,b.silent||this.trigger("change:"+g,this,h,b)}!e&&!b.silent&&this._changed&&this.change(b);this._changing=!1;return this},unset:function(a,b){if(!(a in this.attributes))return this;b||(b={});var c={};c[a]=void 0;if(!b.silent&&this.validate&&!this._performValidation(c,b))return!1;delete this.attributes[a];delete this._escapedAttributes[a];a==this.idAttribute&&delete this.id;this._changed=
11
+ !0;b.silent||(this.trigger("change:"+a,this,void 0,b),this.change(b));return this},clear:function(a){a||(a={});var b,c=this.attributes,d={};for(b in c)d[b]=void 0;if(!a.silent&&this.validate&&!this._performValidation(d,a))return!1;this.attributes={};this._escapedAttributes={};this._changed=!0;if(!a.silent){for(b in c)this.trigger("change:"+b,this,void 0,a);this.change(a)}return this},fetch:function(a){a||(a={});var b=this,c=a.success;a.success=function(d,e,f){if(!b.set(b.parse(d,f),a))return!1;c&&
12
+ c(b,d)};a.error=i(a.error,b,a);return(this.sync||e.sync).call(this,"read",this,a)},save:function(a,b){b||(b={});if(a&&!this.set(a,b))return!1;var c=this,d=b.success;b.success=function(a,e,f){if(!c.set(c.parse(a,f),b))return!1;d&&d(c,a,f)};b.error=i(b.error,c,b);var f=this.isNew()?"create":"update";return(this.sync||e.sync).call(this,f,this,b)},destroy:function(a){a||(a={});if(this.isNew())return this.trigger("destroy",this,this.collection,a);var b=this,c=a.success;a.success=function(d){b.trigger("destroy",
13
+ b,b.collection,a);c&&c(b,d)};a.error=i(a.error,b,a);return(this.sync||e.sync).call(this,"delete",this,a)},url:function(){var a=k(this.collection)||this.urlRoot||l();if(this.isNew())return a;return a+(a.charAt(a.length-1)=="/"?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this)},isNew:function(){return this.id==null},change:function(a){this.trigger("change",this,a);this._previousAttributes=f.clone(this.attributes);this._changed=!1},hasChanged:function(a){if(a)return this._previousAttributes[a]!=
14
+ this.attributes[a];return this._changed},changedAttributes:function(a){a||(a=this.attributes);var b=this._previousAttributes,c=!1,d;for(d in a)f.isEqual(b[d],a[d])||(c=c||{},c[d]=a[d]);return c},previous:function(a){if(!a||!this._previousAttributes)return null;return this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},_performValidation:function(a,b){var c=this.validate(a);if(c)return b.error?b.error(this,c,b):this.trigger("error",this,c,b),!1;return!0}});
15
+ e.Collection=function(a,b){b||(b={});if(b.comparator)this.comparator=b.comparator;f.bindAll(this,"_onModelEvent","_removeReference");this._reset();a&&this.reset(a,{silent:!0});this.initialize.apply(this,arguments)};f.extend(e.Collection.prototype,e.Events,{model:e.Model,initialize:function(){},toJSON:function(){return this.map(function(a){return a.toJSON()})},add:function(a,b){if(f.isArray(a))for(var c=0,d=a.length;c<d;c++)this._add(a[c],b);else this._add(a,b);return this},remove:function(a,b){if(f.isArray(a))for(var c=
16
+ 0,d=a.length;c<d;c++)this._remove(a[c],b);else this._remove(a,b);return this},get:function(a){if(a==null)return null;return this._byId[a.id!=null?a.id:a]},getByCid:function(a){return a&&this._byCid[a.cid||a]},at:function(a){return this.models[a]},sort:function(a){a||(a={});if(!this.comparator)throw Error("Cannot sort a set without a comparator");this.models=this.sortBy(this.comparator);a.silent||this.trigger("reset",this,a);return this},pluck:function(a){return f.map(this.models,function(b){return b.get(a)})},
17
+ reset:function(a,b){a||(a=[]);b||(b={});this.each(this._removeReference);this._reset();this.add(a,{silent:!0});b.silent||this.trigger("reset",this,b);return this},fetch:function(a){a||(a={});var b=this,c=a.success;a.success=function(d,f,e){b[a.add?"add":"reset"](b.parse(d,e),a);c&&c(b,d)};a.error=i(a.error,b,a);return(this.sync||e.sync).call(this,"read",this,a)},create:function(a,b){var c=this;b||(b={});a=this._prepareModel(a,b);if(!a)return!1;var d=b.success;b.success=function(a,e,f){c.add(a,b);
18
+ d&&d(a,e,f)};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={};this._byCid={}},_prepareModel:function(a,b){if(a instanceof e.Model){if(!a.collection)a.collection=this}else{var c=a;a=new this.model(c,{collection:this});a.validate&&!a._performValidation(c,b)&&(a=!1)}return a},_add:function(a,b){b||(b={});a=this._prepareModel(a,b);if(!a)return!1;var c=this.getByCid(a);if(c)throw Error(["Can't add the same model to a set twice",
19
+ c.id]);this._byId[a.id]=a;this._byCid[a.cid]=a;this.models.splice(b.at!=null?b.at:this.comparator?this.sortedIndex(a,this.comparator):this.length,0,a);a.bind("all",this._onModelEvent);this.length++;b.silent||a.trigger("add",a,this,b);return a},_remove:function(a,b){b||(b={});a=this.getByCid(a)||this.get(a);if(!a)return null;delete this._byId[a.id];delete this._byCid[a.cid];this.models.splice(this.indexOf(a),1);this.length--;b.silent||a.trigger("remove",a,this,b);this._removeReference(a);return a},
20
+ _removeReference:function(a){this==a.collection&&delete a.collection;a.unbind("all",this._onModelEvent)},_onModelEvent:function(a,b,c,d){(a=="add"||a=="remove")&&c!=this||(a=="destroy"&&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,arguments))}});f.each(["forEach","each","map","reduce","reduceRight","find","detect","filter","select","reject","every","all","some","any","include","contains","invoke","max",
21
+ "min","sortBy","sortedIndex","toArray","size","first","rest","last","without","indexOf","lastIndexOf","isEmpty","groupBy"],function(a){e.Collection.prototype[a]=function(){return f[a].apply(f,[this.models].concat(f.toArray(arguments)))}});e.Router=function(a){a||(a={});if(a.routes)this.routes=a.routes;this._bindRoutes();this.initialize.apply(this,arguments)};var q=/:([\w\d]+)/g,r=/\*([\w\d]+)/g,s=/[-[\]{}()+?.,\\^$|#\s]/g;f.extend(e.Router.prototype,e.Events,{initialize:function(){},route:function(a,
22
+ b,c){e.history||(e.history=new e.History);f.isRegExp(a)||(a=this._routeToRegExp(a));e.history.route(a,f.bind(function(d){d=this._extractParameters(a,d);c.apply(this,d);this.trigger.apply(this,["route:"+b].concat(d))},this))},navigate:function(a,b){e.history.navigate(a,b)},_bindRoutes:function(){if(this.routes){var a=[],b;for(b in this.routes)a.unshift([b,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(s,"\\$&").replace(q,
23
+ "([^/]*)").replace(r,"(.*?)");return RegExp("^"+a+"$")},_extractParameters:function(a,b){return a.exec(b).slice(1)}});e.History=function(){this.handlers=[];f.bindAll(this,"checkUrl")};var j=/^#*/,t=/msie [\w.]+/,m=!1;f.extend(e.History.prototype,{interval:50,getFragment:function(a,b){if(a==null)if(this._hasPushState||b){a=window.location.pathname;var c=window.location.search;c&&(a+=c);a.indexOf(this.options.root)==0&&(a=a.substr(this.options.root.length))}else a=window.location.hash;return decodeURIComponent(a.replace(j,
24
+ ""))},start:function(a){if(m)throw Error("Backbone.history has already been started");this.options=f.extend({},{root:"/"},this.options,a);this._wantsPushState=!!this.options.pushState;this._hasPushState=!(!this.options.pushState||!window.history||!window.history.pushState);a=this.getFragment();var b=document.documentMode;if(b=t.exec(navigator.userAgent.toLowerCase())&&(!b||b<=7))this.iframe=g('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow,this.navigate(a);
25
+ this._hasPushState?g(window).bind("popstate",this.checkUrl):"onhashchange"in window&&!b?g(window).bind("hashchange",this.checkUrl):setInterval(this.checkUrl,this.interval);this.fragment=a;m=!0;a=window.location;b=a.pathname==this.options.root;if(this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,!0),window.location.replace(this.options.root+"#"+this.fragment),!0;else if(this._wantsPushState&&this._hasPushState&&b&&a.hash)this.fragment=a.hash.replace(j,""),window.history.replaceState({},
26
+ document.title,a.protocol+"//"+a.host+this.options.root+this.fragment);if(!this.options.silent)return this.loadUrl()},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.iframe.location.hash));if(a==this.fragment||a==decodeURIComponent(this.fragment))return!1;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(window.location.hash)},loadUrl:function(a){var b=this.fragment=this.getFragment(a);
27
+ return f.any(this.handlers,function(a){if(a.route.test(b))return a.callback(b),!0})},navigate:function(a,b){var c=(a||"").replace(j,"");if(!(this.fragment==c||this.fragment==decodeURIComponent(c))){if(this._hasPushState){var d=window.location;c.indexOf(this.options.root)!=0&&(c=this.options.root+c);this.fragment=c;window.history.pushState({},document.title,d.protocol+"//"+d.host+c)}else if(window.location.hash=this.fragment=c,this.iframe&&c!=this.getFragment(this.iframe.location.hash))this.iframe.document.open().close(),
28
+ this.iframe.location.hash=c;b&&this.loadUrl(a)}}});e.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.delegateEvents();this.initialize.apply(this,arguments)};var u=/^(\S+)\s*(.*)$/,n=["model","collection","el","id","attributes","className","tagName"];f.extend(e.View.prototype,e.Events,{tagName:"div",$:function(a){return g(a,this.el)},initialize:function(){},render:function(){return this},remove:function(){g(this.el).remove();return this},make:function(a,
29
+ b,c){a=document.createElement(a);b&&g(a).attr(b);c&&g(a).html(c);return a},delegateEvents:function(a){if(a||(a=this.events))for(var b in f.isFunction(a)&&(a=a.call(this)),g(this.el).unbind(".delegateEvents"+this.cid),a){var c=this[a[b]];if(!c)throw Error('Event "'+a[b]+'" does not exist');var d=b.match(u),e=d[1];d=d[2];c=f.bind(c,this);e+=".delegateEvents"+this.cid;d===""?g(this.el).bind(e,c):g(this.el).delegate(d,e,c)}},_configure:function(a){this.options&&(a=f.extend({},this.options,a));for(var b=
30
+ 0,c=n.length;b<c;b++){var d=n[b];a[d]&&(this[d]=a[d])}this.options=a},_ensureElement:function(){if(this.el){if(f.isString(this.el))this.el=g(this.el).get(0)}else{var a=this.attributes||{};if(this.id)a.id=this.id;if(this.className)a["class"]=this.className;this.el=this.make(this.tagName,a)}}});e.Model.extend=e.Collection.extend=e.Router.extend=e.View.extend=function(a,b){var c=v(this,a,b);c.extend=this.extend;return c};var w={create:"POST",update:"PUT","delete":"DELETE",read:"GET"};e.sync=function(a,
31
+ b,c){var d=w[a];c=f.extend({type:d,dataType:"json"},c);if(!c.url)c.url=k(b)||l();if(!c.data&&b&&(a=="create"||a=="update"))c.contentType="application/json",c.data=JSON.stringify(b.toJSON());if(e.emulateJSON)c.contentType="application/x-www-form-urlencoded",c.data=c.data?{model:c.data}:{};if(e.emulateHTTP&&(d==="PUT"||d==="DELETE")){if(e.emulateJSON)c.data._method=d;c.type="POST";c.beforeSend=function(a){a.setRequestHeader("X-HTTP-Method-Override",d)}}if(c.type!=="GET"&&!e.emulateJSON)c.processData=
32
+ !1;return g.ajax(c)};var o=function(){},v=function(a,b,c){var d;d=b&&b.hasOwnProperty("constructor")?b.constructor:function(){return a.apply(this,arguments)};f.extend(d,a);o.prototype=a.prototype;d.prototype=new o;b&&f.extend(d.prototype,b);c&&f.extend(d,c);d.prototype.constructor=d;d.__super__=a.prototype;return d},k=function(a){if(!a||!a.url)return null;return f.isFunction(a.url)?a.url():a.url},l=function(){throw Error('A "url" property or function must be specified');},i=function(a,b,c){return function(d){a?
33
+ a(b,d,c):b.trigger("error",b,d,c)}}}).call(this);