Class: Closure::Sources
- Inherits:
-
Object
- Object
- Closure::Sources
- Includes:
- Enumerable
- Defined in:
- lib/closure/sources.rb
Overview
This class is responsible for scanning source files and managing dependencies.
Defined Under Namespace
Classes: BaseJsNotFoundError, MultipleBaseJsError
Constant Summary collapse
- GOOG_REGEX_STRING =
Using regular expressions may seem clunky, but the Python scripts did it this way and I’ve not see it fail in practice.
'^\s*goog\.%s\s*\(\s*[\'"]([^\)]+)[\'"]\s*\)'
- PROVIDE_REGEX =
Regexp.new(GOOG_REGEX_STRING % 'provide')
- REQUIRE_REGEX =
Regexp.new(GOOG_REGEX_STRING % 'require')
- BASE_JS_REGEX =
Google Closure Library base.js is the file with no provides, no requires, and defines goog a particular way.
/^var goog = goog \|\| \{\};/
- ENV_FLAG =
Flag env so that refresh is never run more than once per request
'closure.sources_fresh'
Instance Attribute Summary collapse
-
#dwell ⇒ Float
Limits how often a full refresh is allowed to run.
Instance Method Summary collapse
-
#add(directory, path = nil) ⇒ Sources
Adds a new directory of source files.
-
#base_js(env = {}) ⇒ String
Determine the path_info and query_string for loading base_js.
-
#deps_js(env = {}) ⇒ String
Determine the path_info for where deps_js is located.
-
#deps_response(base, env = {}) ⇒ Rack::Response
Builds a Rack::Response to serve a dynamic deps.js.
-
#each {|path, directory| ... } ⇒ Object
Yields path and directory for each of the added sources.
-
#files_for(namespace, filenames = nil, env = {}) ⇒ Array<String>
Calculate all required files for a namespace.
-
#initialize(dwell = 1.0) ⇒ Sources
constructor
A new instance of Sources.
-
#invalidate(env) ⇒ Object
Certain Script operations, such as building Templates, will need to invalidate the cache.
-
#namespaces_for(filename, env = {}) ⇒ String
Return all provided and required namespaces for a file.
-
#src_for(filename, env = {}) ⇒ String
Calculate the file server path for a filename.
Constructor Details
#initialize(dwell = 1.0) ⇒ Sources
Returns a new instance of Sources.
48 49 50 51 52 53 54 55 |
# File 'lib/closure/sources.rb', line 48 def initialize(dwell = 1.0) @dwell = dwell @files = {} @sources = [] @semaphore = Mutex.new @last_been_run = nil reset_all_computed_instance_vars end |
Instance Attribute Details
#dwell ⇒ Float
Limits how often a full refresh is allowed to run. Blocked threads can trigger unneeded refreshes in rare scenarios. Also sent to browser in cache-control for frames performance. Caching, lazy loading, and flagging (of env) make up the remaining techniques for good performance.
64 65 66 |
# File 'lib/closure/sources.rb', line 64 def dwell @dwell end |
Instance Method Details
#add(directory, path = nil) ⇒ Sources
Adds a new directory of source files.
71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/closure/sources.rb', line 71 def add(directory, path=nil) raise "immutable once used" if @last_been_run if path raise "path must start with /" unless path =~ %r{^/} path = '' if path == '/' raise "path must not end with /" if path =~ %r{/$} raise "path already exists" if @sources.find{|s|s[0]==path} end raise "directory already exists" if @sources.find{|s|s[1]==directory} @sources << [File.(directory), path] @sources.sort! {|a,b| (b[1]||'') <=> (a[1]||'')} self end |
#base_js(env = {}) ⇒ String
Determine the path_info and query_string for loading base_js.
95 96 97 98 99 100 101 102 103 104 |
# File 'lib/closure/sources.rb', line 95 def base_js(env={}) if (goog = @goog) and @last_been_run return "#{goog[:base_js]}?#{goog[:base_js_mtime].to_i}" end @semaphore.synchronize do refresh(env) raise BaseJsNotFoundError unless @goog @goog[:base_js] end end |
#deps_js(env = {}) ⇒ String
Determine the path_info for where deps_js is located.
109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/closure/sources.rb', line 109 def deps_js(env={}) # Because Server uses this on every call, it's best to never lock. # We grab a local goog so we don't need the lock if everything looks good. # This works because #refresh creates new @goog hashes instead of modifying. if (goog = @goog) and @last_been_run return goog[:deps_js] end @semaphore.synchronize do refresh(env) raise BaseJsNotFoundError unless @goog @goog[:deps_js] end end |
#deps_response(base, env = {}) ⇒ Rack::Response
Builds a Rack::Response to serve a dynamic deps.js
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/closure/sources.rb', line 126 def deps_response(base, env={}) @semaphore.synchronize do refresh(env) base = Pathname.new(base) unless @deps[base] response = @deps[base] ||= Rack::Response.new response.write "// Dynamic Deps by Closure Script\n" @files.sort{|a,b|(a[1][:path]||'')<=>(b[1][:path]||'')}.each do |filename, dep| if dep[:path] path = Pathname.new(dep[:path]).relative_path_from(base) path = "#{path}?#{dep[:mtime].to_i}" response.write "goog.addDependency(#{path.dump}, #{dep[:provide].inspect}, #{dep[:require].inspect});\n" end end response.headers['Content-Type'] = 'application/javascript' response.headers['Cache-Control'] = "max-age=#{[1,@dwell.floor].max}, private, must-revalidate" response.headers['Last-Modified'] = Time.now.httpdate end mod_since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']) rescue nil if mod_since == Time.httpdate(@deps[base].headers['Last-Modified']) Rack::Response.new [], 304 # Not Modified else @deps[base] end end end |
#each {|path, directory| ... } ⇒ Object
Yields path and directory for each of the added sources.
88 89 90 |
# File 'lib/closure/sources.rb', line 88 def each @sources.each { |directory, path| yield directory, path } end |
#files_for(namespace, filenames = nil, env = {}) ⇒ Array<String>
Calculate all required files for a namespace.
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/closure/sources.rb', line 157 def files_for(namespace, filenames=nil, env={}) ns = nil @semaphore.synchronize do refresh(env) # Pivot the deps to a namespace hash # @ns is cleared when any requires or provides changes unless @ns @ns = {} @files.each do |filename, dep| dep[:provide].each do |provide| if @ns[provide] @ns = nil raise "Namespace #{provide.dump} provided more than once." end @ns[provide] = { :filename => filename, :require => dep[:require] } end end end ns = @ns if !filenames or filenames.empty? raise BaseJsNotFoundError unless @goog filenames ||= [] filenames << @goog[:base_filename] end end # Since @ns is only unset, not modified, by another thread, we # can work with a local reference. This has been finely tuned and # runs fast, but it's still nice to release any other threads early. calcdeps(ns, namespace, filenames) end |
#invalidate(env) ⇒ Object
Certain Script operations, such as building Templates, will need to invalidate the cache.
222 223 224 225 |
# File 'lib/closure/sources.rb', line 222 def invalidate(env) env.delete ENV_FLAG @last_been_run = Time.at 0 end |
#namespaces_for(filename, env = {}) ⇒ String
Return all provided and required namespaces for a file.
210 211 212 213 214 215 216 217 |
# File 'lib/closure/sources.rb', line 210 def namespaces_for(filename, env={}) @semaphore.synchronize do refresh(env) file = @files[filename] raise "#{filename.dump} not found" unless file file[:provide] + file[:require] end end |
#src_for(filename, env = {}) ⇒ String
Calculate the file server path for a filename
195 196 197 198 199 200 201 202 203 204 |
# File 'lib/closure/sources.rb', line 195 def src_for(filename, env={}) @semaphore.synchronize do refresh(env) file = @files[filename] unless file and file.has_key? :path raise "#{filename.dump} is not available from file server" end "#{file[:path]}?#{file[:mtime].to_i}" end end |