From 067dd350c8ccb0b40ded53b50cbd003a04cb0252 Mon Sep 17 00:00:00 2001 From: Thomas Reynolds Date: Tue, 20 Nov 2018 14:08:03 -0800 Subject: [PATCH] Avoid all the Maybe nonsense (#2224) Rename dep classes Split into files Sass was still returning nil Let's make dependencies immutable Avoid IsA Fix contract --- .gitignore | 3 +- middleman-core/lib/middleman-core/builder.rb | 30 +- .../lib/middleman-core/contracts.rb | 26 ++ .../lib/middleman-core/dependencies.rb | 264 ++---------------- .../lib/middleman-core/dependencies/edge.rb | 27 ++ .../lib/middleman-core/dependencies/graph.rb | 78 ++++++ .../middleman-core/dependencies/vertices.rb | 9 + .../dependencies/vertices/file_vertex.rb | 66 +++++ .../dependencies/vertices/vertex.rb | 71 +++++ .../lib/middleman-core/file_renderer.rb | 13 +- middleman-core/lib/middleman-core/filter.rb | 8 +- .../lib/middleman-core/inline_url_filter.rb | 11 +- .../lib/middleman-core/renderers/sass.rb | 11 +- .../lib/middleman-core/sitemap/resource.rb | 16 +- .../lib/middleman-core/template_context.rb | 15 +- .../lib/middleman-core/template_renderer.rb | 24 +- 16 files changed, 373 insertions(+), 299 deletions(-) create mode 100644 middleman-core/lib/middleman-core/dependencies/edge.rb create mode 100644 middleman-core/lib/middleman-core/dependencies/graph.rb create mode 100644 middleman-core/lib/middleman-core/dependencies/vertices.rb create mode 100644 middleman-core/lib/middleman-core/dependencies/vertices/file_vertex.rb create mode 100644 middleman-core/lib/middleman-core/dependencies/vertices/vertex.rb diff --git a/.gitignore b/.gitignore index f131c690..ac8828d1 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,5 @@ Makefile .idea *.sublime-workspace /bin -/vendor/bundle/ \ No newline at end of file +/vendor/bundle/ +deps.yml \ No newline at end of file diff --git a/middleman-core/lib/middleman-core/builder.rb b/middleman-core/lib/middleman-core/builder.rb index bc9b9be8..3c51a7aa 100644 --- a/middleman-core/lib/middleman-core/builder.rb +++ b/middleman-core/lib/middleman-core/builder.rb @@ -2,7 +2,9 @@ require 'pathname' require 'fileutils' require 'tempfile' require 'parallel' -require 'middleman-core/dependencies' +require 'middleman-core/dependencies/graph' +require 'middleman-core/dependencies/edge' +require 'middleman-core/dependencies/vertices/file_vertex' require 'middleman-core/callback_manager' require 'middleman-core/contracts' @@ -81,8 +83,7 @@ module Middleman prerender_css.tap do |resources| if @track_dependencies resources.each do |r| - dependency = r[1] - @graph.add_dependency(dependency) unless dependency.nil? + @graph.add_edge(r[1]) unless r[1].nil? end end end @@ -94,8 +95,7 @@ module Middleman output_files.tap do |resources| if @track_dependencies resources.each do |r| - dependency = r[1] - @graph.add_dependency(dependency) unless dependency.nil? + @graph.add_edge(r[1]) unless r[1].nil? end end end @@ -120,7 +120,7 @@ module Middleman # Pre-request CSS to give Compass a chance to build sprites # @return [Array] List of css resources that were output. - Contract ArrayOf[[Pathname, Maybe[::Middleman::Dependencies::DependencyLink]]] + Contract ArrayOf[[Pathname, Maybe[::Middleman::Dependencies::Edge]]] def prerender_css logger.debug '== Prerendering CSS' @@ -143,7 +143,7 @@ module Middleman # Find all the files we need to output and do so. # @return [Array] List of resources that were output. - Contract ArrayOf[[Pathname, Maybe[::Middleman::Dependencies::DependencyLink]]] + Contract ArrayOf[[Pathname, Maybe[::Middleman::Dependencies::Edge]]] def output_files logger.debug '== Building files' @@ -155,7 +155,7 @@ module Middleman output_resources(resources.to_a) end - Contract OldResourceList => ArrayOf[Or[Bool, [Pathname, Maybe[::Middleman::Dependencies::DependencyLink]]]] + Contract OldResourceList => ArrayOf[Or[Bool, [Pathname, Maybe[::Middleman::Dependencies::Edge]]]] def output_resources(resources) res_count = resources.count @@ -278,7 +278,7 @@ module Middleman # Try to output a resource and capture errors. # @param [Middleman::Sitemap::Resource] resource The resource. # @return [void] - Contract IsA['Middleman::Sitemap::Resource'] => Or[Bool, [Pathname, Maybe[::Middleman::Dependencies::DependencyLink]]] + Contract IsA['Middleman::Sitemap::Resource'] => Or[Bool, [Pathname, Maybe[::Middleman::Dependencies::Edge]]] def output_resource(resource) ::Middleman::Util.instrument 'builder.output.resource', path: File.basename(resource.destination_path) do begin @@ -305,17 +305,17 @@ module Middleman end end - deps = nil + vertices = nil if resource.binary? export_file!(output_file, resource.file_descriptor[:full_path], true) else content = resource.render({}, {}) - unless resource.dependencies.empty? - deps = ::Middleman::Dependencies::DependencyLink.new( - ::Middleman::Dependencies::FileDependency.from_resource(resource), - resource.dependencies + unless resource.vertices.empty? + vertices = ::Middleman::Dependencies::Edge.new( + ::Middleman::Dependencies::FileVertex.from_resource(resource), + resource.vertices ) end @@ -326,7 +326,7 @@ module Middleman return false end - [output_file, deps] + [output_file, vertices] end end diff --git a/middleman-core/lib/middleman-core/contracts.rb b/middleman-core/lib/middleman-core/contracts.rb index 8a5c9061..d3e23199 100644 --- a/middleman-core/lib/middleman-core/contracts.rb +++ b/middleman-core/lib/middleman-core/contracts.rb @@ -19,6 +19,29 @@ if ENV['CONTRACTS'] != 'false' end VectorOf = Contracts::CollectionOf::Factory.new(::Hamster::Vector) + + class ImmutableHashOf < Contracts::CallableClass + INVALID_KEY_VALUE_PAIR = 'You should provide only one key-value pair to HashOf contract'.freeze + + def initialize(key, value) + @key = key + @value = value + end + + def valid?(hash) + return false unless hash.is_a?(::Hamster::Hash) + + keys_match = hash.keys.map { |k| Contract.valid?(k, @key) }.all? + vals_match = hash.values.map { |v| Contract.valid?(v, @value) }.all? + + [keys_match, vals_match].all? + end + + def to_s + "ImmutableHash<#{@key}, #{@value}>" + end + end + ImmutableSetOf = Contracts::CollectionOf::Factory.new(::Hamster::Set) ImmutableSortedSetOf = Contracts::CollectionOf::Factory.new(::Hamster::SortedSet) OldResourceList = Contracts::ArrayOf[IsA['Middleman::Sitemap::Resource']] @@ -110,6 +133,9 @@ else class VectorOf < Callable end + class ImmutableHashOf < Callable + end + class ImmutableSetOf < Callable end end diff --git a/middleman-core/lib/middleman-core/dependencies.rb b/middleman-core/lib/middleman-core/dependencies.rb index 57d13f07..c3816387 100644 --- a/middleman-core/lib/middleman-core/dependencies.rb +++ b/middleman-core/lib/middleman-core/dependencies.rb @@ -1,261 +1,47 @@ -require 'middleman-core/contracts' require 'set' require 'pathname' +require 'digest/sha1' require 'yaml' +require 'middleman-core/contracts' +require 'middleman-core/dependencies/graph' +require 'middleman-core/dependencies/vertices' module Middleman module Dependencies - class BaseDependency - include Contracts - - DEPENDENCY_KEY = Or[String, Symbol] - DEPENDENCY_ATTRS = HashOf[Symbol, String] - SERIALIZED_DEPENDENCY = { - key: Any, # Weird inheritance bug - type: Symbol, - attributes: DEPENDENCY_ATTRS - }.freeze - - Contract DEPENDENCY_KEY - attr_reader :key - - Contract DEPENDENCY_ATTRS - attr_reader :attributes - - Contract DEPENDENCY_KEY, DEPENDENCY_ATTRS => Any - def initialize(key, attributes) - @key = key - @attributes = attributes - end - - Contract BaseDependency => Bool - def ==(other) - key == other.key - end - - Contract Bool - def valid? - raise NotImplementedError - end - - Contract IsA['Middleman::Sitemap::Resource'] => Bool - def invalidates_resource?(_resource) - raise NotImplementedError - end - - Contract Maybe[DEPENDENCY_ATTRS] => SERIALIZED_DEPENDENCY - def serialize(attributes = {}) - { - key: @key, - type: type_id, - attributes: @attributes.merge(attributes) - } - end - - protected - - Contract Symbol - def type_id - self.class.const_get :TYPE_ID - end - - Contract Pathname, String => String - def relative_path(root, file) - Pathname(File.expand_path(file)).relative_path_from(root).to_s - end - - Contract Pathname, String => String - def full_path(root, file) - File.expand_path(file, root) - end - end - - class FileDependency < BaseDependency - include Contracts - - TYPE_ID = :file - - Contract IsA['::Middleman::Application'], String, BaseDependency::DEPENDENCY_ATTRS => FileDependency - def self.deserialize(app, key, attributes) - FileDependency.new(app.root_path, key, attributes) - end - - Contract IsA['Middleman::Sitemap::Resource'] => FileDependency - def self.from_resource(resource) - from_source_file(resource.app, resource.file_descriptor) - end - - Contract IsA['::Middleman::Application'], IsA['::Middleman::SourceFile'] => FileDependency - def self.from_source_file(app, source_file) - FileDependency.new(app.root_path, source_file[:full_path].to_s) - end - - Contract Or[String, Pathname], String, Maybe[DEPENDENCY_ATTRS] => Any - def initialize(root, key, attributes = {}) - @root = Pathname(root) - @full_path = full_path(@root, key) - super(relative_path(@root, key), attributes) - end - - Contract Bool - def valid? - @is_valid = (previous_hash.nil? || hash_file == previous_hash) if @is_valid.nil? - @is_valid - end - - Contract IsA['Middleman::Sitemap::Resource'] => Bool - def invalidates_resource?(resource) - resource.file_descriptor[:full_path].to_s == @full_path - end - - Contract BaseDependency::SERIALIZED_DEPENDENCY - def serialize - super({ - hash: valid? && !previous_hash.nil? ? previous_hash : hash_file - }) - end - - private - - Contract Maybe[String] - def previous_hash - @attributes[:hash] - end - - Contract String - def hash_file - ::Digest::SHA1.file(@full_path).hexdigest - end - end - - DEPENDENCY_CLASSES_BY_TYPE = { - FileDependency::TYPE_ID => FileDependency - }.freeze - - class DependencyLink - include Contracts - - Contract BaseDependency - attr_reader :dependency - - Contract Maybe[SetOf[BaseDependency]] - attr_accessor :depends_on - - Contract BaseDependency, Maybe[SetOf[BaseDependency]] => Any - def initialize(dependency, depends_on = nil) - @dependency = dependency - @depends_on = nil - @depends_on = Set.new(depends_on) unless depends_on.nil? - end - - Contract String - def to_s - "#<#{self.class} file=#{@dependency} depends_on=#{@depends_on}>" - end - end - - class Graph - include Contracts - - Contract HashOf[BaseDependency::DEPENDENCY_KEY, BaseDependency] - attr_reader :dependencies - - Contract HashOf[BaseDependency, SetOf[BaseDependency]] - attr_accessor :dependency_map - - def initialize(dependencies = {}) - @dependencies = dependencies - @dependency_map = {} - end - - Contract BaseDependency => BaseDependency - def known_dependency_or_new(dep) - @dependencies[dep.key] ||= dep - end - - Contract DependencyLink => Any - def add_dependency(link) - deduped_depender = known_dependency_or_new link.dependency - - @dependency_map[deduped_depender] ||= Set.new - @dependency_map[deduped_depender] << deduped_depender - - return if link.depends_on.nil? - - link.depends_on.each do |depended_on| - deduped_depended_on = known_dependency_or_new depended_on - - @dependency_map[deduped_depended_on] ||= Set.new - @dependency_map[deduped_depended_on] << deduped_depended_on - @dependency_map[deduped_depended_on] << deduped_depender - end - end - - Contract String => Bool - def exists?(file_path) - @dependency_map.key?(file_path) - end - - Contract SetOf[BaseDependency] - def invalidated - @_invalidated_cache ||= begin - invalidated_dependencies = @dependency_map.keys.select do |dependency| - # Either "Missing from known dependencies" - # Or invalided by the class - !@dependencies.key?(dependency.key) || !dependency.valid? - end - - invalidated_dependencies.reduce(Set.new) do |sum, dependency| - sum | invalidated_with_parents(dependency) - end - end - end - - Contract BaseDependency => SetOf[BaseDependency] - def invalidated_with_parents(dependency) - # TODO, recurse more? - (Set.new + (@dependency_map[dependency])) << dependency - end - - Contract IsA['::Middleman::Sitemap::Resource'] => Bool - def invalidates_resource?(resource) - invalidated.any? { |d| d.invalidates_resource?(resource) } - end - end - include Contracts + DEFAULT_FILE_PATH = 'deps.yml'.freeze + RUBY_FILES = ['**/*.rb', 'Gemfile.lock'].freeze + module_function Contract IsA['::Middleman::Application'], Graph => String def serialize(app, graph) - ruby_files = Dir['**/*.rb', 'Gemfile.lock'].reduce([]) do |sum, file| + ruby_files = Dir.glob(RUBY_FILES).reduce([]) do |sum, file| sum << { file: Pathname(File.expand_path(file)).relative_path_from(app.root_path).to_s, hash: ::Digest::SHA1.file(file).hexdigest } end - dependency_links = graph.dependency_map.reduce([]) do |sum, (dependency, depended_on_by)| + edges = graph.dependency_map.reduce([]) do |sum, (vertex, depended_on_by)| sum << { - key: dependency.key, - depended_on_by: depended_on_by.delete(dependency).to_a.map(&:key).sort + key: vertex.key, + depended_on_by: depended_on_by.delete(vertex).to_a.map(&:key).sort } end - dependencies = graph.dependency_map.reduce([]) do |sum, (dependency, _depended_on_by)| - sum << dependency.serialize + vertices = graph.dependency_map.reduce([]) do |sum, (vertex, _depended_on_by)| + sum << vertex.serialize end ::YAML.dump( ruby_files: ruby_files.sort_by { |d| d[:file] }, - dependency_links: dependency_links.sort_by { |d| d[:file] }, - dependencies: dependencies.sort_by { |d| d[:key] } + edges: edges.sort_by { |d| d[:file] }, + vertices: vertices.sort_by { |d| d[:key] } ) end - DEFAULT_FILE_PATH = 'deps.yml'.freeze - Contract IsA['::Middleman::Application'], Graph, Maybe[String] => Any def serialize_and_save(app, graph, file_path = DEFAULT_FILE_PATH) File.open(file_path, 'w') do |file| @@ -302,19 +88,19 @@ module Middleman raise InvalidatedRubyFiles, invalidated end - dependencies = data[:dependencies].each_with_object({}) do |row, sum| - dep_class = DEPENDENCY_CLASSES_BY_TYPE[row[:type]] - dep = dep_class.deserialize(app, row[:key], row[:attributes]) - sum[dep.key] = dep + vertices = data[:vertices].each_with_object({}) do |row, sum| + vertex_class = VERTICES_BY_TYPE[row[:type]] + vertex = vertex_class.deserialize(app, row[:key], row[:attributes]) + sum[vertex.key] = vertex end - graph = Graph.new(dependencies) + graph = Graph.new(vertices) - dependency_links = data[:dependency_links] - graph.dependency_map = dependency_links.each_with_object({}) do |row, sum| - dependency = graph.dependencies[row[:key]] - depended_on_by = row[:depended_on_by].map { |k| graph.dependencies[k] } - sum[dependency] = Set.new(depended_on_by) << dependency + edges = data[:edges] + graph.dependency_map = edges.each_with_object({}) do |row, sum| + vertex = graph.vertices[row[:key]] + depended_on_by = row[:depended_on_by].map { |k| graph.vertices[k] } + sum[vertex] = ::Hamster::Set.new(depended_on_by) << vertex end graph diff --git a/middleman-core/lib/middleman-core/dependencies/edge.rb b/middleman-core/lib/middleman-core/dependencies/edge.rb new file mode 100644 index 00000000..e5eed2a8 --- /dev/null +++ b/middleman-core/lib/middleman-core/dependencies/edge.rb @@ -0,0 +1,27 @@ +require 'middleman-core/contracts' +require 'middleman-core/dependencies/vertices/vertex' + +module Middleman + module Dependencies + class Edge + include Contracts + + Contract Vertex + attr_reader :vertex + + Contract ImmutableSetOf[Vertex] + attr_accessor :depends_on + + Contract Vertex, ImmutableSetOf[Vertex] => Any + def initialize(vertex, depends_on) + @vertex = vertex + @depends_on = depends_on + end + + Contract String + def to_s + "#<#{self.class} vertex=#{@vertex} edges=#{@edges}>" + end + end + end +end diff --git a/middleman-core/lib/middleman-core/dependencies/graph.rb b/middleman-core/lib/middleman-core/dependencies/graph.rb new file mode 100644 index 00000000..e9d990ab --- /dev/null +++ b/middleman-core/lib/middleman-core/dependencies/graph.rb @@ -0,0 +1,78 @@ +require 'set' +require 'middleman-core/contracts' +require 'middleman-core/dependencies/vertices/vertex' +require 'middleman-core/dependencies/edge' + +module Middleman + module Dependencies + class Graph + include Contracts + + Contract HashOf[Vertex::VERTEX_KEY, Vertex] + attr_reader :vertices + + Contract ImmutableHashOf[Vertex, ImmutableSetOf[Vertex]] + attr_accessor :dependency_map + + def initialize(vertices = {}) + @vertices = vertices + @dependency_map = ::Hamster::Hash.empty + end + + Contract Vertex => Vertex + def known_vertex_or_new(v) + @vertices[v.key] ||= v + end + + Contract Edge => Any + def add_edge(edge) + deduped_vertex = known_vertex_or_new edge.vertex + + @dependency_map.put(deduped_vertex) do |v| + (v || ::Hamster::Set.empty) << deduped_vertex + end + + return if edge.depends_on.nil? + + edge.depends_on.each do |depended_on| + deduped_depended_on = known_vertex_or_new depended_on + + @dependency_map.put(deduped_depended_on) do |v| + (v || ::Hamster::Set.empty) << deduped_depended_on << deduped_vertex + end + end + end + + Contract String => Bool + def exists?(file_path) + @dependency_map.key?(file_path) + end + + Contract ImmutableSetOf[Vertex] + def invalidated + @_invalidated_cache ||= begin + invalidated_vertices = @dependency_map.keys.select do |vertex| + # Either "Missing from known vertices" + # Or invalided by the class + !@vertices.key?(vertex.key) || !vertex.valid? + end + + invalidated_vertices.reduce(::Hamster::Set.empty) do |sum, vertex| + sum | invalidated_with_parents(vertex) + end + end + end + + Contract Vertex => ImmutableSetOf[Vertex] + def invalidated_with_parents(vertex) + # TODO, recurse more? + @dependency_map[vertex] << vertex + end + + Contract IsA['::Middleman::Sitemap::Resource'] => Bool + def invalidates_resource?(resource) + invalidated.any? { |d| d.invalidates_resource?(resource) } + end + end + end +end diff --git a/middleman-core/lib/middleman-core/dependencies/vertices.rb b/middleman-core/lib/middleman-core/dependencies/vertices.rb new file mode 100644 index 00000000..b68cf49d --- /dev/null +++ b/middleman-core/lib/middleman-core/dependencies/vertices.rb @@ -0,0 +1,9 @@ +require 'middleman-core/dependencies/vertices/file_vertex' + +module Middleman + module Dependencies + VERTICES_BY_TYPE = { + FileVertex::TYPE_ID => FileVertex + }.freeze + end +end diff --git a/middleman-core/lib/middleman-core/dependencies/vertices/file_vertex.rb b/middleman-core/lib/middleman-core/dependencies/vertices/file_vertex.rb new file mode 100644 index 00000000..2796f2ec --- /dev/null +++ b/middleman-core/lib/middleman-core/dependencies/vertices/file_vertex.rb @@ -0,0 +1,66 @@ +require 'pathname' +require 'digest/sha1' +require 'middleman-core/contracts' +require 'middleman-core/dependencies/vertices/vertex' + +module Middleman + module Dependencies + class FileVertex < Vertex + include Contracts + + TYPE_ID = :file + + Contract IsA['::Middleman::Application'], String, Vertex::VERTEX_ATTRS => FileVertex + def self.deserialize(app, key, attributes) + FileVertex.new(app.root_path, key, attributes) + end + + Contract IsA['Middleman::Sitemap::Resource'] => FileVertex + def self.from_resource(resource) + from_source_file(resource.app, resource.file_descriptor) + end + + Contract IsA['::Middleman::Application'], IsA['::Middleman::SourceFile'] => FileVertex + def self.from_source_file(app, source_file) + FileVertex.new(app.root_path, source_file[:full_path].to_s) + end + + Contract Or[String, Pathname], String, Maybe[VERTEX_ATTRS] => Any + def initialize(root, key, attributes = {}) + @root = Pathname(root) + @full_path = full_path(@root, key) + super(relative_path(@root, key), attributes) + end + + Contract Bool + def valid? + @is_valid = (previous_hash.nil? || hash_file == previous_hash) if @is_valid.nil? + @is_valid + end + + Contract IsA['Middleman::Sitemap::Resource'] => Bool + def invalidates_resource?(resource) + resource.file_descriptor[:full_path].to_s == @full_path + end + + Contract Vertex::SERIALIZED_VERTEX + def serialize + super({ + hash: valid? && !previous_hash.nil? ? previous_hash : hash_file + }) + end + + private + + Contract Maybe[String] + def previous_hash + @attributes[:hash] + end + + Contract String + def hash_file + ::Digest::SHA1.file(@full_path).hexdigest + end + end + end +end diff --git a/middleman-core/lib/middleman-core/dependencies/vertices/vertex.rb b/middleman-core/lib/middleman-core/dependencies/vertices/vertex.rb new file mode 100644 index 00000000..1c942d03 --- /dev/null +++ b/middleman-core/lib/middleman-core/dependencies/vertices/vertex.rb @@ -0,0 +1,71 @@ +require 'pathname' +require 'middleman-core/contracts' + +module Middleman + module Dependencies + class Vertex + include Contracts + + VERTEX_KEY = Or[String, Symbol] + VERTEX_ATTRS = HashOf[Symbol, String] + SERIALIZED_VERTEX = { + key: Any, # Weird inheritance bug + type: Symbol, + attributes: VERTEX_ATTRS + }.freeze + + Contract VERTEX_KEY + attr_reader :key + + Contract VERTEX_ATTRS + attr_reader :attributes + + Contract VERTEX_KEY, VERTEX_ATTRS => Any + def initialize(key, attributes) + @key = key + @attributes = attributes + end + + Contract Vertex => Bool + def ==(other) + key == other.key + end + + Contract Bool + def valid? + raise NotImplementedError + end + + Contract IsA['Middleman::Sitemap::Resource'] => Bool + def invalidates_resource?(_resource) + raise NotImplementedError + end + + Contract Maybe[VERTEX_ATTRS] => SERIALIZED_VERTEX + def serialize(attributes = {}) + { + key: @key, + type: type_id, + attributes: @attributes.merge(attributes) + } + end + + protected + + Contract Symbol + def type_id + self.class.const_get :TYPE_ID + end + + Contract Pathname, String => String + def relative_path(root, file) + Pathname(File.expand_path(file)).relative_path_from(root).to_s + end + + Contract Pathname, String => String + def full_path(root, file) + File.expand_path(file, root) + end + end + end +end diff --git a/middleman-core/lib/middleman-core/file_renderer.rb b/middleman-core/lib/middleman-core/file_renderer.rb index f3ab1a68..fe8639fa 100644 --- a/middleman-core/lib/middleman-core/file_renderer.rb +++ b/middleman-core/lib/middleman-core/file_renderer.rb @@ -1,8 +1,9 @@ require 'tilt' -require 'set' +require 'hamster' require 'active_support/core_ext/string/output_safety' require 'active_support/core_ext/module/delegation' require 'middleman-core/contracts' +require 'middleman-core/dependencies/vertices/vertex' ::Tilt.default_mapping.lazy_map.delete('html') ::Tilt.default_mapping.lazy_map.delete('csv') @@ -16,15 +17,15 @@ module Middleman @_cache ||= ::Tilt::Cache.new end - Contract Maybe[SetOf[IsA['::Middleman::Dependencies::BaseDependency']]] - attr_reader :dependencies + Contract ImmutableSetOf[::Middleman::Dependencies::Vertex] + attr_reader :vertices def_delegator :"self.class", :cache def initialize(app, path) @app = app @path = path.to_s - @dependencies = nil + @vertices = ::Hamster::Set.empty end # Render an on-disk file. Used for everything, including layouts. @@ -71,12 +72,12 @@ module Middleman # ::Tilt.new(path, 1, options) { body } # end - @dependencies = nil + @vertices = ::Hamster::Set.empty # Render using Tilt content = ::Middleman::Util.instrument 'render.tilt', path: path do template.render(context, locs, &block).tap do - @dependencies = template.dependencies if template.respond_to?(:dependencies) + @vertices = template.vertices if template.respond_to?(:vertices) end end diff --git a/middleman-core/lib/middleman-core/filter.rb b/middleman-core/lib/middleman-core/filter.rb index 99b91316..0e13f012 100644 --- a/middleman-core/lib/middleman-core/filter.rb +++ b/middleman-core/lib/middleman-core/filter.rb @@ -1,4 +1,6 @@ +require 'hamster' require 'middleman-core/contracts' +require 'middleman-core/dependencies/vertices/vertex' module Middleman class Filter @@ -31,7 +33,7 @@ module Middleman @after_filter = @options.fetch(:after_filter, nil) end - Contract String => String + Contract String => [String, ImmutableSetOf[::Middleman::Dependencies::Vertex]] def execute_filter(_body) raise NotImplementedError end @@ -45,10 +47,10 @@ module Middleman @callable = callable end - Contract String => [String, Maybe[SetOf[String]]] + Contract String => [String, ImmutableSetOf[::Middleman::Dependencies::Vertex]] def execute_filter(body) result = @callable.call(body) - result.is_a?(Array) ? result : [result, nil] + result.is_a?(Array) ? result : [result, ::Hamster::Set.empty] end end end diff --git a/middleman-core/lib/middleman-core/inline_url_filter.rb b/middleman-core/lib/middleman-core/inline_url_filter.rb index c1066825..0816bd20 100644 --- a/middleman-core/lib/middleman-core/inline_url_filter.rb +++ b/middleman-core/lib/middleman-core/inline_url_filter.rb @@ -1,6 +1,9 @@ +require 'hamster' require 'middleman-core/util' require 'middleman-core/filter' require 'middleman-core/contracts' +require 'middleman-core/dependencies/vertices/vertex' +require 'middleman-core/dependencies/vertices/file_vertex' module Middleman class InlineURLRewriter < Filter @@ -28,13 +31,13 @@ module Middleman @app.sitemap.by_destination_path(full_asset_path) || @app.sitemap.by_path(full_asset_path) end - Contract String => [String, Maybe[SetOf[String]]] + Contract String => [String, ImmutableSetOf[::Middleman::Dependencies::Vertex]] def execute_filter(body) path = "/#{@resource.destination_path}" dirpath = ::Pathname.new(File.dirname(path)) ::Middleman::Util.instrument 'inline_url_filter', path: path do - deps = Set.new + vertices = ::Hamster::Set.empty new_content = ::Middleman::Util.rewrite_paths(body, path, @options.fetch(:url_extensions), @app) do |asset_path| uri = ::Middleman::Util.parse_uri(asset_path) @@ -55,7 +58,7 @@ module Middleman if result if @options.fetch(:create_dependencies, false) - deps << ::Middleman::Dependencies::FileDependency.from_resource( + vertices <<= ::Middleman::Dependencies::FileVertex.from_resource( target_resource(asset_path, dirpath) ) end @@ -66,7 +69,7 @@ module Middleman asset_path end - [new_content, deps] + [new_content, vertices] end end end diff --git a/middleman-core/lib/middleman-core/renderers/sass.rb b/middleman-core/lib/middleman-core/renderers/sass.rb index 3ba1539f..b54e622c 100644 --- a/middleman-core/lib/middleman-core/renderers/sass.rb +++ b/middleman-core/lib/middleman-core/renderers/sass.rb @@ -1,6 +1,7 @@ -require 'set' +require 'hamster' require 'sass' require 'middleman-core/dependencies' +require 'middleman-core/dependencies/vertices/file_vertex' begin require 'sassc' @@ -76,12 +77,10 @@ module Middleman end end - def dependencies - files = @engine.dependencies.map do |d| - ::Middleman::Dependencies::FileDependency.new(@context.app.root_path, d.filename) + def vertices + @engine.dependencies.reduce(::Hamster::Set.empty) do |sum, d| + sum << ::Middleman::Dependencies::FileVertex.new(@context.app.root_path, d.filename) end - - files.empty? ? nil : Set.new(files) end # Change Sass path, for url functions, to the build folder if we're building diff --git a/middleman-core/lib/middleman-core/sitemap/resource.rb b/middleman-core/lib/middleman-core/sitemap/resource.rb index 3702f1b8..7272d8e2 100644 --- a/middleman-core/lib/middleman-core/sitemap/resource.rb +++ b/middleman-core/lib/middleman-core/sitemap/resource.rb @@ -1,10 +1,12 @@ require 'rack/mime' +require 'set' +require 'hamster' require 'middleman-core/sitemap/extensions/traversal' require 'middleman-core/file_renderer' require 'middleman-core/template_renderer' require 'middleman-core/contracts' -require 'set' require 'middleman-core/inline_url_filter' +require 'middleman-core/dependencies/vertices/vertex' module Middleman # Sitemap namespace @@ -39,8 +41,8 @@ module Middleman Contract Num attr_reader :priority - Contract Maybe[SetOf[IsA['::Middleman::Dependencies::BaseDependency']]] - attr_reader :dependencies + Contract ImmutableSetOf[::Middleman::Dependencies::Vertex] + attr_reader :vertices # Initialize resource with parent store and URL # @param [Middleman::Sitemap::Store] store @@ -54,7 +56,7 @@ module Middleman @ignored = false @filters = ::Hamster::SortedSet.empty @priority = priority - @dependencies = Set.new + @vertices = ::Hamster::Set.empty source = Pathname(source) if source&.is_a?(String) @@ -175,7 +177,7 @@ module Middleman # @return [String] Contract Hash, Hash, Maybe[Proc] => String def render(options_hash = ::Middleman::EMPTY_HASH, locs = ::Middleman::EMPTY_HASH, &_block) - @dependencies = Set.new + @vertices = ::Hamster::Set.empty body = render_without_filters(options_hash, locs) @@ -186,7 +188,7 @@ module Middleman output elsif filter.is_a?(Filter) result = filter.execute_filter(output) - @dependencies |= result[1] unless result[1].nil? + @vertices |= result[1] result[0] else output @@ -219,7 +221,7 @@ module Middleman renderer = ::Middleman::TemplateRenderer.new(@app, file_descriptor[:full_path].to_s) renderer.render(locs, opts).to_str.tap do - @dependencies |= renderer.dependencies unless renderer.dependencies.nil? + @vertices |= renderer.vertices end end diff --git a/middleman-core/lib/middleman-core/template_context.rb b/middleman-core/lib/middleman-core/template_context.rb index 43143ce0..0ff99a9a 100644 --- a/middleman-core/lib/middleman-core/template_context.rb +++ b/middleman-core/lib/middleman-core/template_context.rb @@ -1,7 +1,10 @@ require 'pathname' +require 'hamster' require 'middleman-core/file_renderer' require 'middleman-core/template_renderer' require 'middleman-core/contracts' +require 'middleman-core/dependencies/vertices/vertex' +require 'middleman-core/dependencies/vertices/file_vertex' module Middleman # The TemplateContext Class @@ -22,8 +25,8 @@ module Middleman # Required for Padrino's rendering attr_accessor :current_engine - Contract Maybe[SetOf[IsA['::Middleman::Dependencies::BaseDependency']]] - attr_reader :dependencies + Contract ImmutableSetOf[::Middleman::Dependencies::Vertex] + attr_reader :vertices # Shorthand references to global values on the app instance. def_delegators :@app, :config, :logger, :sitemap, :server?, :build?, :environment?, :environment, :data, :extensions, :root, :development?, :production? @@ -38,7 +41,7 @@ module Middleman @locs = locs @opts = options_hash - @dependencies = Set.new + @vertices = ::Hamster::Set.empty end # Return the current buffer to the caller and clear the value internally. @@ -91,7 +94,7 @@ module Middleman restore_buffer(buf_was) end - @dependencies << ::Middleman::Dependencies::FileDependency.from_source_file(@app, layout_file) + @vertices <<= ::Middleman::Dependencies::FileVertex.from_source_file(@app, layout_file) # Render the layout, with the contents of the block inside. concat_safe_content render_file(layout_file, @locs, @opts) { content } @@ -118,7 +121,7 @@ module Middleman source_path = sitemap.file_to_path(partial_file) r = sitemap.by_path(source_path) - @dependencies << ::Middleman::Dependencies::FileDependency.from_source_file(@app, partial_file) + @vertices <<= ::Middleman::Dependencies::FileVertex.from_source_file(@app, partial_file) result = if (r && !r.template?) || (Tilt[partial_file[:full_path]].nil? && partial_file[:full_path].exist?) partial_file.read @@ -218,7 +221,7 @@ module Middleman content_renderer = ::Middleman::FileRenderer.new(@app, path) content = content_renderer.render(locs, opts, context, &block) - @dependencies |= content_renderer.dependencies unless content_renderer.dependencies.nil? + @vertices |= content_renderer.vertices path = File.basename(path, File.extname(path)) rescue LocalJumpError diff --git a/middleman-core/lib/middleman-core/template_renderer.rb b/middleman-core/lib/middleman-core/template_renderer.rb index 8381b4ad..5d78ef0f 100644 --- a/middleman-core/lib/middleman-core/template_renderer.rb +++ b/middleman-core/lib/middleman-core/template_renderer.rb @@ -1,10 +1,11 @@ require 'tilt' -require 'set' +require 'hamster' require 'active_support/core_ext/string/output_safety' +require 'middleman-core/contracts' require 'middleman-core/template_context' require 'middleman-core/file_renderer' -require 'middleman-core/dependencies' -require 'middleman-core/contracts' +require 'middleman-core/dependencies/vertices/vertex' +require 'middleman-core/dependencies/vertices/file_vertex' module Middleman class TemplateRenderer @@ -100,13 +101,13 @@ module Middleman # Custom error class for handling class TemplateNotFound < RuntimeError; end - Contract Maybe[SetOf[IsA['::Middleman::Dependencies::BaseDependency']]] - attr_reader :dependencies + Contract ImmutableSetOf[::Middleman::Dependencies::Vertex] + attr_reader :vertices def initialize(app, path) @app = app @path = path - @dependencies = nil + @vertices = ::Hamster::Set.empty end # Render a template, with layout, given a path @@ -174,8 +175,8 @@ module Middleman ::Middleman::Util.instrument 'builder.output.resource.render-layout', path: File.basename(layout_file[:relative_path].to_s) do layout_renderer.render(locals, options, context) { content }.tap do - @dependencies << ::Middleman::Dependencies::FileDependency.from_source_file(@app, layout_file) - @dependencies |= layout_renderer.dependencies unless layout_renderer.dependencies.nil? + @vertices <<= ::Middleman::Dependencies::FileVertex.from_source_file(@app, layout_file) + @vertices |= layout_renderer.vertices end end else @@ -183,8 +184,7 @@ module Middleman end end - @dependencies |= context.dependencies unless context.dependencies.nil? - @dependencies = @dependencies.empty? ? nil : @dependencies + @vertices |= context.vertices # Return result content @@ -201,7 +201,7 @@ module Middleman # handles cases like `style.css.sass.erb` content = nil - @dependencies = Set.new + @vertices = ::Hamster::Set.empty while ::Middleman::Util.tilt_class(path) begin @@ -209,7 +209,7 @@ module Middleman content_renderer = ::Middleman::FileRenderer.new(@app, path) content = content_renderer.render(locs, opts, context, &block) - @dependencies |= content_renderer.dependencies unless content_renderer.dependencies.nil? + @vertices |= content_renderer.vertices path = path.sub(/\.[^.]*\z/, '') rescue LocalJumpError