Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use fine-grained attr/content dependencies #1017

Merged
merged 10 commits into from Dec 18, 2016
25 changes: 23 additions & 2 deletions lib/nanoc/base/compilation/outdatedness_checker.rb
Expand Up @@ -160,8 +160,8 @@ def outdated_due_to_dependencies?(obj, processed = Hamster::Set.new)
return false if processed.include?(obj)

# Calculate
is_outdated = dependency_store.objects_causing_outdatedness_of(obj).any? do |other|
other.nil? || basic_outdated?(other) || outdated_due_to_dependencies?(other, processed.merge([obj]))
is_outdated = dependency_store.dependencies_causing_outdatedness_of(obj).any? do |dep|
dependency_causes_outdatedness?(dep) || outdated_due_to_dependencies?(dep.from, processed.merge([obj]))
end

# Cache
Expand All @@ -171,6 +171,27 @@ def outdated_due_to_dependencies?(obj, processed = Hamster::Set.new)
is_outdated
end

contract Nanoc::Int::DependencyStore::Dependency => C::Bool
def dependency_causes_outdatedness?(dependency)
return true if dependency.from.nil?

reason = basic_outdatedness_reason_for(dependency.from)
return false if reason.nil?

valid_reasons = valid_reasons_for_dep_outdatedness(dependency)
valid_reasons.include?(reason) || valid_reasons.include?(:all)
end

contract Nanoc::Int::DependencyStore::Dependency => Set
def valid_reasons_for_dep_outdatedness(dep)
Set.new.tap do |s|
s << Nanoc::Int::OutdatednessReasons::AttributesModified if dep.attributes?
s << Nanoc::Int::OutdatednessReasons::ContentModified if dep.raw_content?
s << :all if dep.compiled_content?
s << :all if dep.path?
end
end

contract C::Or[Nanoc::Int::Item, Nanoc::Int::ItemRep, Nanoc::Int::Layout] => C::Bool
# @param [Nanoc::Int::ItemRep, Nanoc::Int::Layout] obj The layout or item
# representation to check the rule memory for
Expand Down
2 changes: 1 addition & 1 deletion lib/nanoc/base/entities/document.rb
Expand Up @@ -5,7 +5,7 @@ class Document
include Nanoc::Int::ContractsSupport

# @return [Nanoc::Int::Content]
attr_reader :content
attr_accessor :content

# @return [Hash]
def attributes
Expand Down
18 changes: 15 additions & 3 deletions lib/nanoc/base/repos/dependency_store.rb
Expand Up @@ -5,10 +5,10 @@ class DependencyStore < ::Nanoc::Int::Store
class Dependency
include Nanoc::Int::ContractsSupport

contract C::None => C::Or[Nanoc::Int::Item, Nanoc::Int::Layout]
contract C::None => C::Maybe[C::Or[Nanoc::Int::Item, Nanoc::Int::Layout]]
attr_reader :from

contract C::None => C::Or[Nanoc::Int::Item, Nanoc::Int::Layout]
contract C::None => C::Maybe[C::Or[Nanoc::Int::Item, Nanoc::Int::Layout]]
attr_reader :to

contract C::Maybe[C::Or[Nanoc::Int::Item, Nanoc::Int::Layout]], C::Maybe[C::Or[Nanoc::Int::Item, Nanoc::Int::Layout]], C::KeywordArgs[raw_content: C::Optional[C::Bool], attributes: C::Optional[C::Bool], compiled_content: C::Optional[C::Bool], path: C::Optional[C::Bool]] => C::Any
Expand All @@ -22,6 +22,17 @@ def initialize(from, to, raw_content:, attributes:, compiled_content:, path:)
@path = path
end

contract C::None => String
def inspect
s = "Dependency(#{@from.inspect} -> #{@to.inspect}, "
s << (raw_content? ? 'r' : '_')
s << (attributes? ? 'a' : '_')
s << (compiled_content? ? 'c' : '_')
s << (path? ? 'p' : '_')
s << ')'
s
end

contract C::None => C::Bool
def raw_content?
@raw_content
Expand Down Expand Up @@ -179,10 +190,11 @@ def data=(new_data)

# Record dependency from all items on new items
new_objects = (@objects - previous_objects)
new_props = { raw_content: true, attributes: true, compiled_content: true, path: true }
new_objects.each do |new_obj|
@objects.each do |obj|
next unless obj.is_a?(Nanoc::Int::Item)
@graph.add_edge(new_obj, obj)
@graph.add_edge(new_obj, obj, props: new_props)
end
end
end
Expand Down
146 changes: 140 additions & 6 deletions spec/nanoc/base/services/outdatedness_checker_spec.rb
Expand Up @@ -15,9 +15,7 @@
let(:dependency_store) { double(:dependency_store) }

let(:rule_memory_store) do
Nanoc::Int::RuleMemoryStore.new.tap do |store|
store[item_rep] = old_memory_for_item_rep.serialize
end
Nanoc::Int::RuleMemoryStore.new
end

let(:old_memory_for_item_rep) do
Expand All @@ -31,15 +29,16 @@
let(:action_provider) { double(:action_provider) }

let(:reps) do
Nanoc::Int::ItemRepRepo.new.tap do |repo|
repo << item_rep
end
Nanoc::Int::ItemRepRepo.new
end

let(:item_rep) { Nanoc::Int::ItemRep.new(item, :default) }
let(:item) { Nanoc::Int::Item.new('stuff', {}, '/foo.md') }

before do
reps << item_rep
rule_memory_store[item_rep] = old_memory_for_item_rep.serialize

allow(action_provider).to receive(:memory_for).with(item_rep).and_return(new_memory_for_item_rep)
end

Expand Down Expand Up @@ -87,6 +86,141 @@
end
end

describe '#outdated_due_to_dependencies?' do
subject { outdatedness_checker.send(:outdated_due_to_dependencies?, item) }

let(:dependency_store) do
Nanoc::Int::DependencyStore.new(objects)
end

let(:checksum_store) do
Nanoc::Int::ChecksumStore.new
end

let(:other_item) { Nanoc::Int::Item.new('other stuff', {}, '/other.md') }
let(:other_item_rep) { Nanoc::Int::ItemRep.new(other_item, :default) }

let(:config) { Nanoc::Int::Configuration.new }

let(:objects) { [item, other_item] }

let(:old_memory_for_other_item_rep) do
Nanoc::Int::RuleMemory.new(other_item_rep).tap do |mem|
mem.add_filter(:erb, {})
end
end

let(:new_memory_for_other_item_rep) { old_memory_for_other_item_rep }

before do
reps << other_item_rep
rule_memory_store[other_item_rep] = old_memory_for_other_item_rep.serialize
checksum_store.add(other_item)
checksum_store.add(config)

allow(action_provider).to receive(:memory_for).with(other_item_rep).and_return(new_memory_for_other_item_rep)
allow(site).to receive(:code_snippets).and_return([])
allow(site).to receive(:config).and_return(config)
end

context 'only attribute dependency' do
before do
dependency_store.record_dependency(item, other_item, attributes: true)
end

context 'attribute changed' do
before { other_item.attributes[:title] = 'omg new title' }
it { is_expected.to be }
end

context 'raw content changed' do
before { other_item.content = Nanoc::Int::TextualContent.new('omg new content') }
it { is_expected.not_to be }
end
end

context 'only raw content dependency' do
before do
dependency_store.record_dependency(item, other_item, raw_content: true)
end

context 'attribute changed' do
before { other_item.attributes[:title] = 'omg new title' }
it { is_expected.not_to be }
end

context 'raw content changed' do
before { other_item.content = Nanoc::Int::TextualContent.new('omg new content') }
it { is_expected.to be }
end
end

context 'attribute + raw content dependency' do
before do
dependency_store.record_dependency(item, other_item, attributes: true, raw_content: true)
end

context 'attribute changed' do
before { other_item.attributes[:title] = 'omg new title' }
it { is_expected.to be }
end

context 'raw content changed' do
before { other_item.content = Nanoc::Int::TextualContent.new('omg new content') }
it { is_expected.to be }
end

context 'attribute + raw content changed' do
before { other_item.attributes[:title] = 'omg new title' }
before { other_item.content = Nanoc::Int::TextualContent.new('omg new content') }
it { is_expected.to be }
end

context 'rules changed' do
let(:new_memory_for_other_item_rep) do
Nanoc::Int::RuleMemory.new(other_item_rep).tap do |mem|
mem.add_filter(:erb, {})
mem.add_filter(:donkey, {})
end
end

it { is_expected.not_to be }
end
end

context 'attribute + other dependency' do
before do
dependency_store.record_dependency(item, other_item, attributes: true, path: true)
end

context 'attribute changed' do
before { other_item.attributes[:title] = 'omg new title' }
it { is_expected.to be }
end

context 'raw content changed' do
before { other_item.content = Nanoc::Int::TextualContent.new('omg new content') }
it { is_expected.to be }
end
end

context 'other dependency' do
before do
dependency_store.record_dependency(item, other_item, path: true)
end

context 'attribute changed' do
before { other_item.attributes[:title] = 'omg new title' }
it { is_expected.to be }
end

context 'raw content changed' do
before { other_item.content = Nanoc::Int::TextualContent.new('omg new content') }
it { is_expected.to be }
end
end
end

describe '#{content,attributes}_checksums_identical?' do
subject do
[
Expand Down