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.
Constant Summary
- 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)
-
- (Float) dwell
Limits how often a full refresh is allowed to run.
Instance Method Summary (collapse)
-
- (Sources) add(directory, path = nil)
Adds a new directory of source files.
-
- (String) base_js(env = {})
Determine the path_info and query_string for loading base_js.
-
- (String) deps_js(env = {})
Determine the path_info for where deps_js is located.
-
- (Rack::Response) deps_response(base, env = {})
Builds a Rack::Response to serve a dynamic deps.js.
-
- each {|path, directory| ... }
Yields path and directory for each of the added sources.
-
- (Array<String>) files_for(namespace, filenames = nil, env = {})
Calculate all required files for a namespace.
-
- (Sources) initialize(dwell = 1.0)
constructor
A new instance of Sources.
-
- invalidate(env)
Certain Script operations, such as building Templates, will need to invalidate the cache.
-
- (String) namespaces_for(filename, env = {})
Return all provided and required namespaces for a file.
-
- (String) src_for(filename, env = {})
Calculate the file server path for a filename.
Constructor Details
- (Sources) initialize(dwell = 1.0)
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
- (Float) dwell
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
- (Sources) add(directory, path = nil)
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 |
- (String) base_js(env = {})
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 |
- (String) deps_js(env = {})
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 |
- (Rack::Response) deps_response(base, env = {})
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| ... }
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 |
- (Array<String>) files_for(namespace, filenames = nil, env = {})
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)
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 |
- (String) namespaces_for(filename, env = {})
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 |
- (String) src_for(filename, env = {})
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 |