class Slinky::Manifest
Attributes
Public Class Methods
# File lib/slinky/manifest.rb, line 26 def initialize dir, config, options = {} @dir = dir @build_to = if d = options[:build_to] File.expand_path(d) else dir end @manifest_dir = ManifestDir.new dir, self, @build_to, self @devel = (options[:devel].nil?) ? true : options[:devel] @config = config @no_minify = options[:no_minify] || config.dont_minify end
Public Instance Methods
Adds a file to the manifest, updating the dependency graph
# File lib/slinky/manifest.rb, line 55 def add_all_by_path paths manifest_update paths do |path| md = find_by_path(File.dirname(path)).first mf = md.add_file(File.basename(path)) end end
# File lib/slinky/manifest.rb, line 279 def build @manifest_dir.build unless @devel @config.produce.keys.each{|product| compress_product(product) } # clean up the files that have been processed files_for_all_products.each{|mf| FileUtils.rm(mf.build_to, :force => true)} end end
# File lib/slinky/manifest.rb, line 206 def compress_product product compressor = compressor_for_product product post_processor = post_processor_for_product product s = files_for_product(product).map{|mf| f = File.open(mf.build_to.to_s, 'rb'){|f| f.read} post_processor ? (post_processor.call(mf, f)) : f }.join("\n") # Make the directory the product is in FileUtils.mkdir_p("#{@build_to}/#{Pathname.new(product).dirname}") File.open("#{@build_to}/#{product}", "w+"){|f| unless @no_minify f.write(compressor[s]) else f.write(s) end } end
Builds the directed graph representing the dependencies of all files in the manifest that contain a slinky_require declaration. The graph is represented as a list of pairs (required, by), each of which describes an edge.
@return [[ManifestFile, ManifestFile]] the graph
# File lib/slinky/manifest.rb, line 262 def dependency_graph return @dependency_graph if @dependency_graph graph = [] files(false).each{|mf| mf.dependencies.each{|d| graph << [d, mf] } } @dependency_graph = Graph.new(files(false), graph) end
# File lib/slinky/manifest.rb, line 275 def dependency_list dependency_graph.dependency_list end
Returns a list of all files contained in this manifest
@return [ManifestFile] a list of manifest files
# File lib/slinky/manifest.rb, line 42 def files include_ignores = true unless @files @files = [] files_rec @manifest_dir end if include_ignores @files else @files.reject{|f| @config.ignore.any?{|p| f.in_tree? p}} end end
# File lib/slinky/manifest.rb, line 197 def files_for_all_products return @files_for_all_products if @files_for_all_products SlinkyError.batch_errors do @files_for_all_products = @config.produce.keys.map{|product| files_for_product(product) }.flatten.uniq end end
Finds all the matching manifest files for a particular product. This does not take into account dependencies.
# File lib/slinky/manifest.rb, line 140 def files_for_product product p = @config.produce[product] if p.nil? raise NoSuchProductError.new( "Product '#{product}' has not been configured") end type = type_for_product product if type != ".js" && type != ".css" raise InvalidConfigError.new("Only .js and .css products are supported") end g = dependency_graph.transitive_closure # Topological indices for each file indices = {} dependency_list.each_with_index{|f, i| indices[f] = i} # Compute the set of excluded files excludes = Set.new((p["exclude"] || []).map{|p| find_by_pattern(p) }.flatten.uniq) SlinkyError.batch_errors do # First find the list of files that have been explictly # included/excluded p["include"].map{|f| mfs = find_by_pattern(f) .map{|mf| [mf] + g[f]} .flatten .reject{|f| f.output_path.extname != type} if mfs.empty? SlinkyError.raise FileNotFoundError, "No files matched by include #{f} in product #{product}" end mfs.flatten }.flatten.reject{|f| excludes.include?(f) # Then add all the files these require }.map{|f| # Find all of the downstream files # check that we're not excluding any required files g[f].each{|rf| if p["exclude"] && r = p["exclude"].find{|ex| rf.matches_path?(ex, true)} SlinkyError.raise DependencyError, "File #{f} requires #{rf} which is excluded by exclusion rule #{r}" end } [f] + g[f] }.flatten.uniq.sort_by{|f| # Sort by topological order indices[f] } end end
Finds the file at the given path in the manifest if one exists, otherwise nil.
@param String path the path of the file relative to the manifest
@return ManifestFile
the manifest file at that path if one exists
# File lib/slinky/manifest.rb, line 83 def find_by_path path, allow_multiple = false @manifest_dir.find_by_path path, allow_multiple end
Finds all files that match the given pattern. The match rules are similar to those for .gitignore and given by
-
If the pattern ends with a slash, it will only match directories; e.g. `foo/` would match a directory `foo/` but not a file `foo`. In a file context, matching a directory is equivalent to matching all files under that directory, recursively.
-
If the pattern does not contain a slash, slinky treats it as a relative pathname which can match files in any directory. For example, the rule `test.js` will matching `/test.js` and
`/component/test.js`.
-
If the pattern begins with a slash, it will be treated as an absolute path starting at the root of the source directory.
-
If the pattern does not begin with a slash, but does contain one or more slashes, it will be treated as a path relative to any directory. For example, `test/*.js` will match `/test/main.js`, and /component/test/component.js`, but not `main.js`.
-
A single star `*` in a pattern will match any number of characters within a single path component. For example, `/test/*.js` will match `/test/main_test.js` but not `/test/component/test.js`.
-
A double star `**` will match any number of characters including path separators. For example `/scripts/**/main.js` will match any file named `main.js` under the `/scripts` directory, including
`/scripts/main.js` and `/scripts/component/main.js`.
# File lib/slinky/manifest.rb, line 111 def find_by_pattern pattern # The strategy here is to convert the pattern into an equivalent # regex and run that against the pathnames of all the files in # the manifest. regex_str = Regexp.escape(pattern) .gsub('\*\*/', ".*") .gsub('\*\*', ".*") .gsub('\*', "[^/]*") if regex_str[0] != '/' regex_str = '.*/' + regex_str end if regex_str[-1] == '/' regex_str += '.*' end regex_str = "^#{regex_str}$" regex = Regexp.new(regex_str) files(false).reject{|f| !regex.match('/' + f.relative_source_path.to_s) && !regex.match('/' + f.relative_output_path.to_s) } end
Returns a md5 encompassing the current state of the manifest. Any change to the manifest should produce a different hash. This can be used to determine if the manifest has changed.
# File lib/slinky/manifest.rb, line 294 def md5 if @md5 @md5 else @md5 = Digest::MD5.hexdigest(files.map{|f| [f.source, f.md5]} .sort.flatten.join(":")) end end
Produces a string of HTML that includes all of the files for the given product.
# File lib/slinky/manifest.rb, line 246 def product_string product if @devel files_for_product(product).map{|f| html_for_path("/#{f.relative_output_path}") }.join("\n") else html_for_path("#{product}?#{rand(999999999)}") end end
Removes a file from the manifest
# File lib/slinky/manifest.rb, line 68 def remove_all_by_path paths manifest_update paths do |path| mf = find_by_path(path).first() if mf mf.parent.remove_file(mf) end end end
These are special cases for simplicity and backwards compatability. If no products are defined, we have two default products, one which includes are .js files in the repo and one that includes all .css files. This method produces an HTML include string for all of the .js files.
# File lib/slinky/manifest.rb, line 231 def scripts_string product_string ConfigReader::DEFAULT_SCRIPT_PRODUCT end
These are special cases for simplicity and backwards compatability. If no products are defined, we have two default products, one which includes are .js files in the repo and one that includes all .css files. This method produces an HTML include string for all of the .css files.
# File lib/slinky/manifest.rb, line 240 def styles_string product_string ConfigReader::DEFAULT_STYLE_PRODUCT end
Notifies of an update to a file in the manifest
# File lib/slinky/manifest.rb, line 63 def update_all_by_path paths manifest_update paths end
Private Instance Methods
# File lib/slinky/manifest.rb, line 311 def compressor_for_product product require 'sassc' case type_for_product(product) when ".js" # Use UglifyJS lambda{|s| Uglifier.compile(s.force_encoding("UTF-8"), mangle: false, output: {ascii_only: false})} when ".css" # Use SASS's compressed output lambda{|s| SassC::Engine.new(s, :syntax => :scss, :style => :compressed).render} end end
# File lib/slinky/manifest.rb, line 304 def files_rec md @files += md.files md.children.each do |c| files_rec c end end
# File lib/slinky/manifest.rb, line 341 def html_for_path path ext = path.split("?").first.split(".").last case ext when "css" %Q|<link rel="stylesheet" href="#{path}" />| when "js" %Q|<script type="text/javascript" src="#{path}"></script>| else raise InvalidConfigError.new("Unsupported file extension #{ext}") end end
# File lib/slinky/manifest.rb, line 334 def invalidate_cache @files = nil @dependency_graph = nil @md5 = nil @files_for_all_products = nil end
# File lib/slinky/manifest.rb, line 357 def manifest_update paths paths.each{|path| if path[0] == '/' path = Pathname.new(path).relative_path_from(Pathname.new(@dir).expand_path).to_s end yield path if block_given? } invalidate_cache files.each{|f| if f.directives.include?(:slinky_scripts) || f.directives.include?(:slinky_styles) || f.directives.include?(:slinky_product) f.invalidate f.find_directives end } end
# File lib/slinky/manifest.rb, line 324 def post_processor_for_product product case type_for_product(product) when ".css" lambda{|s, css| css.gsub(CSS_URL_MATCHER){|url| p = s.relative_output_path.dirname.to_s + "/#{$1}" "url('/#{p}')" }} end end
# File lib/slinky/manifest.rb, line 353 def type_for_product product "." + product.split(".")[-1] end