require 'set' IGNORED_PAGES = Set['sitemap.xml'] LANGS = Set['en', 'pt', 'fr', 'eo'] # jp zh es de module Jekyll class Linter < Generator safe true priority :high def insert_id(name, document) lang = document.data['lang'] ref = document.data['ref'] id = "#{name}:#{lang}:#{ref}" if @known_ids.include? id then raise "Duplicate ID found: '#{id}'" else @known_ids.add id end end def assert_unique_ids(site) @known_ids = Set[] all_documents(site) do |collection_name, document| insert_id collection_name, document end end def slugify(s) s.ljust(100) .gsub(/[()']/, '') .gsub(/[\W]+/, ' ') .strip .gsub(/\s\s+/, '-') .downcase .gsub(' ', '-') .gsub('_', '-') end def assert(value, message) unless value raise message end value end def assert_field(document, field) f = document.data[field] raise "Undefined '#{field}' for #{document.path}" unless f f end COLLECTION_LAYOUTS = { 'page' => 'default', 'slides' => 'slides', 'articles' => 'post', 'pastebins' => 'post', 'tils' => 'post', 'podcasts' => 'post', 'screencasts' => 'post' } def assert_frontmatter_fields(name, document) title = assert_field document, 'title' lang = assert_field document, 'lang' ref = assert_field document, 'ref' layout = assert_field document, 'layout' date = document.date.strftime('%Y-%m-%d') unless layout == 'default' slug = layout == 'default' ? ref : assert_field(document, 'slug') extension = name == 'slides' ? 'slides' : 'md' unless LANGS.member? lang raise "Invalid lang '#{lang}' in #{document.path}" end if COLLECTION_LAYOUTS[name] != layout raise "Layout mismatch: expected '#{COLLECTION_LAYOUTS[name]}', got '#{layout}' for #{document.path}" end if lang == 'en' unless ['index', 'root', 'tils'].include? ref if slugify(title) != ref then raise "#{ref} isn't a slug of the title.\nref: '#{ref}'\ntitle slug: '#{slugify(title)}'" end end end unless layout == 'default' then path = "_#{name}/#{date}-#{slug}.#{extension}" unless path == document.relative_path then raise "date/filename mismatch:\ndate+slug: #{path}\nfilename: #{document.relative_path}" end if lang == 'en' then unless ref == slug then raise "ref/slug mismatch:\nref: #{ref}\nslug: #{slug}" end end end if name == 'podcasts' then flac = "resources/podcasts/#{date}-#{slug}.flac" unless File.exist? flac then raise "Missing FLAC file '#{flac}'" end end if name == 'screencasts' then webm = "resources/screencasts/#{date}-#{slug}.webm" unless File.exist? webm then raise "Missing WebM file '#{webm}'" end end end def assert_frontmatter(site) all_documents(site) do |collection_name, document| assert_frontmatter_fields collection_name, document end end @@first_build = true def assert_git_annex(site) url = site.config['url'] stdout = `git annex find --not --in web` if stdout != '' or not $?.success? then puts 'Files in Git Annex not published to "web" remote:' puts stdout puts "Add them with:\n\n" stdout.strip.split("\n").each do |file| puts "git annex addurl --file #{file} #{url}/#{file}" end msg = "\nBuild again after files above are added" if @@first_build then @@first_build = false puts msg else raise msg end end end MEDIA_EXTENSION = { 'podcasts' => 'flac', 'screencasts' => 'webm' } def assert_media_metadata(site) site.collections.each do |name, collection| if ['podcasts', 'screencasts'].include? name then collection.docs.each do |document| date = document.data['date'].strftime('%Y-%m-%d') slug = document.data['slug'] ext = MEDIA_EXTENSION[name] file = "resources/#{name}/#{date}-#{slug}.#{ext}" feed_name = site.config['t'][name]['feed']['title'][document.data['lang']] if name == 'podcasts' stdout = `metaflac --export-tags-to=- #{file}`.strip.split("\n") expected = [ "COMMENTS=#{site.config['url']}/#{file}", 'ARTIST=EuAndreh', "DATE=#{date}", "TITLE=#{document.data['title']}", "ALBUM=#{feed_name}" ] expected.each do |metadata| unless stdout.include? metadata tags = expected.join('\\n').gsub(/'/, "'\"'\"'") add_metadata_cmd = "metaflac --remove-all #{file}\nprintf '#{tags}\\n' | metaflac --import-tags-from=- #{file}" check_metadata_cmd = "metaflac --export-tags-to=- #{file}" raise "Missing metadata entry '#{metadata}' in '#{file}'.\nAdd it with:\n\n#{add_metadata_cmd}\n\nCheck with:\n #{check_metadata_cmd}" end end check_cover_cmd = "metaflac #{file} --export-picture-to=- | diff - static/favicon.png" `#{check_cover_cmd}` unless $?.success? then add_cover_cmd = "metaflac #{file} --import-picture-from=static/favicon.png" raise "Cover art from '#{file}' doesn't match 'static/favicon.png'.\nFix it with:\n\n#{add_cover_cmd}\n\nCheck with:\n #{check_cover_cmd}" end elsif name == 'screencasts' then stdout = `mediainfo #{file} | awk -F: '/^Movie name/ { print $2 }'`.strip expected = document.data['title'] + ' - ' + feed_name unless stdout == expected then escaped_title = expected.gsub(/'/, "'\"'\"'") add_metadata_cmd = "mkvpropedit '#{file}' -e info -s title='#{escaped_title}'" check_metadata_cmd = "mediainfo '#{file}' | grep 'Movie name'" raise "Missing metadata entry 'title' in '#{file}'.\nAdd it with:\n\n#{add_metadata_cmd}\n\nCheck with:\n #{check_metadata_cmd}" end end end end end end def assert_lilypond(site) site.config['musics'].each do |music| assert music['title'], "Missing 'title' in #{music}" assert music['composer'], "Missing 'composer' in #{music}" ref = assert music['ref'], "Missing 'ref' in #{music}" ly = "music/#{ref}.ly" unless File.exist? ly then raise "Missing '#{ly}' file present in _config.yml." end unless open(ly) { |f| f.include? "\\pointAndClickOff\n" } then raise "Missing '\\pointAndClickOff' in '#{ly}'" end unless open(ly) { |f| f.include? "#(ly:set-option 'embed-source-code #t)\n" } then raise "Missing '#(ly:set-option 'embed-source-code #t)' in '#{ly}'" end end end def generate(site) assert_unique_ids(site) assert_frontmatter(site) assert_media_metadata(site) assert_git_annex(site) assert_lilypond(site) end def all_documents(site) site.collections.each do |name, collection| collection.docs.each do |document| unless document.data['generated'] yield name, document end end end site.pages.each do |page| unless IGNORED_PAGES.include? page.path unless page.data['generated'] yield 'page', page end end end end end end