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

Raise when meta-file cannot be unambiguously associated #1370

Merged
merged 1 commit into from Oct 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions nanoc/lib/nanoc/base/errors.rb
Expand Up @@ -236,6 +236,12 @@ def initialize(filter_name)
end
end

class AmbiguousMetadataAssociation < Generic
def initialize(content_filenames, meta_filename)
super("There are multiple content files (#{content_filenames.join(', ')}) that could match the file containing metadata (#{meta_filename}).")
end
end

class InternalInconsistency < Generic
end
end
Expand Down
62 changes: 42 additions & 20 deletions nanoc/lib/nanoc/data_sources/filesystem.rb
Expand Up @@ -176,30 +176,49 @@ def load_objects(dir_name, klass)

return [] if dir_name.nil?

all_split_files_in(dir_name).each do |base_filename, (meta_ext, content_exts)|
content_exts.each do |content_ext|
meta_filename = filename_for(base_filename, meta_ext)
content_filename = filename_for(base_filename, content_ext)

proto_doc = read_proto_document(content_filename, meta_filename, klass)

content = content_for(proto_doc, content_filename)
attributes = attributes_for(proto_doc, content_filename, meta_filename)
identifier = identifier_for(content_filename, meta_filename, dir_name)

res << klass.new(
content,
attributes,
identifier,
content_checksum_data: content_checksum_data_for(proto_doc),
attributes_checksum_data: attributes_checksum_data_for(proto_doc, content_filename, meta_filename),
)
end
each_content_meta_pair_in(dir_name) do |content_filename, meta_filename|
proto_doc = read_proto_document(content_filename, meta_filename, klass)

content = content_for(proto_doc, content_filename)
attributes = attributes_for(proto_doc, content_filename, meta_filename)
identifier = identifier_for(content_filename, meta_filename, dir_name)

res << klass.new(
content,
attributes,
identifier,
content_checksum_data: content_checksum_data_for(proto_doc),
attributes_checksum_data: attributes_checksum_data_for(proto_doc, content_filename, meta_filename),
)
end

res
end

# Enumerates each pair of content file and meta file. If there is ambiguity, it will raise an error.
def each_content_meta_pair_in(dir_name)
all_split_files_in(dir_name).each do |base_filename, (meta_ext, content_exts)|
meta_filename = filename_for(base_filename, meta_ext)
content_filenames = content_exts.map { |e| filename_for(base_filename, e) }

have_possible_ambiguity = meta_filename && content_filenames.size > 1
if have_possible_ambiguity && content_filenames.count { |fn| !parser.frontmatter?(fn) } != 1
raise Nanoc::Int::Errors::AmbiguousMetadataAssociation.new(content_filenames, meta_filename)
end

content_filenames.each do |content_filename|
real_meta_filename =
if have_possible_ambiguity && parser.frontmatter?(content_filename)
nil
else
meta_filename
end

yield(content_filename, real_meta_filename)
end
end
end

def content_checksum_data_for(proto_doc)
data = proto_doc.content_checksum_data
data ? Digest::SHA1.digest(data) : nil
Expand Down Expand Up @@ -379,8 +398,11 @@ def allow_periods_in_identifiers?
end
end

def parser
@parser ||= Parser.new(config: @config)
end

def parse(content_filename, meta_filename)
parser = Parser.new(config: @config)
parser.call(content_filename, meta_filename)
end
end
Expand Down
5 changes: 5 additions & 0 deletions nanoc/lib/nanoc/data_sources/filesystem/parser.rb
Expand Up @@ -70,6 +70,11 @@ def parse_metadata(data, filename)
meta
end

def frontmatter?(filename)
data = Tools.read_file(filename, config: @config)
/\A#{SEPARATOR}\s*$/.match?(data)
end

def verify_meta(meta, filename)
return if meta.is_a?(Hash)

Expand Down
131 changes: 131 additions & 0 deletions nanoc/spec/nanoc/data_sources/filesystem_spec.rb
Expand Up @@ -110,6 +110,137 @@
expect(subject[0].content_checksum_data).to be_nil
end
end

context 'two content files (no inline metadata) with one meta file' do
let(:params) { { identifier_type: 'full' } }

before do
FileUtils.mkdir_p('foo')
File.write('foo/a.txt', 'hi')
File.write('foo/a.md', 'ho')
File.write('foo/a.yaml', 'title: Aaah')
end

it 'errors' do
expect { subject }
.to raise_error(
Nanoc::Int::Errors::AmbiguousMetadataAssociation,
'There are multiple content files (foo/a.txt, foo/a.md) that could match the file containing metadata (foo/a.yaml).',
)
end
end

context 'two content files (one has inline metadata) with one meta file' do
let(:params) { { identifier_type: 'full' } }

before do
FileUtils.mkdir_p('foo')
File.write('foo/a.txt', "---\ntitle: Hi\n---\n\nhi")
File.write('foo/a.md', 'ho')
File.write('foo/a.yaml', 'title: Aaah')
end

it 'assigns metadata to the file that doesn’t have any yet' do
expect(subject.size).to eq(2)

items = subject.sort_by { |i| i.identifier.to_s }

expect(items[0].content).to be_a(Nanoc::Int::TextualContent)
expect(items[0].identifier).to eq(Nanoc::Identifier.new('/a.md', type: :full))
expect(items[0].attributes[:title]).to eq('Aaah')

expect(items[1].content).to be_a(Nanoc::Int::TextualContent)
expect(items[1].identifier).to eq(Nanoc::Identifier.new('/a.txt', type: :full))
expect(items[1].attributes[:title]).to eq('Hi')
end
end

context 'two content files (both have inline metadata) with one meta file' do
let(:params) { { identifier_type: 'full' } }

before do
FileUtils.mkdir_p('foo')
File.write('foo/a.txt', "---\ntitle: Hi\n---\n\nhi")
File.write('foo/a.md', "---\ntitle: Ho\n---\n\nho")
File.write('foo/a.yaml', 'title: Aaah')
end

it 'errors' do
expect { subject }
.to raise_error(
Nanoc::Int::Errors::AmbiguousMetadataAssociation,
'There are multiple content files (foo/a.txt, foo/a.md) that could match the file containing metadata (foo/a.yaml).',
)
end
end

context 'two content files (both have inline metadata) with no meta file' do
let(:params) { { identifier_type: 'full' } }

before do
FileUtils.mkdir_p('foo')
File.write('foo/a.txt', "---\ntitle: Hi\n---\n\nhi")
File.write('foo/a.md', "---\ntitle: Ho\n---\n\nho")
end

it 'uses inline metadata' do
expect(subject.size).to eq(2)

items = subject.sort_by { |i| i.identifier.to_s }

expect(items[0].content).to be_a(Nanoc::Int::TextualContent)
expect(items[0].identifier).to eq(Nanoc::Identifier.new('/a.md', type: :full))
expect(items[0].attributes[:title]).to eq('Ho')

expect(items[1].content).to be_a(Nanoc::Int::TextualContent)
expect(items[1].identifier).to eq(Nanoc::Identifier.new('/a.txt', type: :full))
expect(items[1].attributes[:title]).to eq('Hi')
end
end

context 'two content files (neither have inline metadata) with no meta file' do
let(:params) { { identifier_type: 'full' } }

before do
FileUtils.mkdir_p('foo')
File.write('foo/a.txt', 'hi')
File.write('foo/a.md', 'ho')
end

it 'uses no metadata' do
expect(subject.size).to eq(2)

items = subject.sort_by { |i| i.identifier.to_s }

expect(items[0].content).to be_a(Nanoc::Int::TextualContent)
expect(items[0].identifier).to eq(Nanoc::Identifier.new('/a.md', type: :full))
expect(items[0].attributes[:title]).to be_nil

expect(items[1].content).to be_a(Nanoc::Int::TextualContent)
expect(items[1].identifier).to eq(Nanoc::Identifier.new('/a.txt', type: :full))
expect(items[1].attributes[:title]).to be_nil
end
end

context 'one content file (with inline metadata) and a meta file' do
let(:params) { { identifier_type: 'full' } }

before do
FileUtils.mkdir_p('foo')
File.write('foo/a.txt', "---\ntitle: Hi\n---\n\nhi")
File.write('foo/a.yaml', 'author: Denis')
end

it 'uses only metadata from meta file' do
expect(subject.size).to eq(1)

expect(subject[0].content).to be_a(Nanoc::Int::TextualContent)
expect(subject[0].content.string).to eq("---\ntitle: Hi\n---\n\nhi")
expect(subject[0].identifier).to eq(Nanoc::Identifier.new('/a.txt', type: :full))
expect(subject[0].attributes[:title]).to be_nil
expect(subject[0].attributes[:author]).to eq('Denis')
end
end
end
end

Expand Down