# Copyright (c) 2008-2012 Hongli Lai # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'nokogiri' require 'mizuho' require 'mizuho/source_highlight' require 'mizuho/id_map' module Mizuho class GenerationError < StandardError end class Generator def initialize(input, options = {}) @options = options @input_file = input @output_file = options[:output] || default_output_filename(input) @id_map_file = options[:id_map] || default_id_map_filename(input) @icons_dir = options[:icons_dir] @conf_file = options[:conf_file] @enable_topbar = options[:topbar] @commenting_system = options[:commenting_system] if @commenting_system == 'juvia' require_options(options, :juvia_url, :juvia_site_key) end end def start if @commenting_system @id_map = IdMap.new if File.exist?(@id_map_file) @id_map.load(@id_map_file) else warn "No ID map file, generating one (#{@id_map_file})..." end end self.class.run_asciidoc(@input_file, @output_file, @icons_dir, @conf_file) transform(@output_file) if @commenting_system @id_map.save(@id_map_file) stats = @id_map.stats if stats[:fuzzy] > 0 warn "Warning: #{stats[:fuzzy]} fuzzy ID(s)" end if stats[:orphaned] > 0 warn "Warning: #{stats[:orphaned]} unused ID(s)" end end end def self.run_asciidoc(input, output, icons_dir = nil, conf_file = nil) args = [ "python", ASCIIDOC, "-b", "html5", "-a", "toc", "-a", "theme=flask", "-a", "toclevels=3", "-a", "icons", "-n" ] if icons_dir args << "-a" args << "iconsdir=#{icons_dir}" end if conf_file # With the splat operator we support a string and an array of strings. [*conf_file].each do |cf| args << "-f" args << cf end end args += ["-o", output, input] if !system(*args) raise GenerationError, "Asciidoc failed." end end private def default_output_filename(input) return File.dirname(input) + "/" + File.basename(input, File.extname(input)) + ".html" end def default_id_map_filename(input) return File.dirname(input) + "/" + File.basename(input, File.extname(input)) + ".idmap.txt" end def warn(message) STDERR.puts(message) end def transform(filename) File.open(filename, 'r+') do |f| doc = Nokogiri.HTML(f) head = (doc / "head")[0] body = (doc / "body")[0] title = (doc / "title")[0].text preamble = (doc / "#preamble")[0] toctitle = (doc / "#toctitle")[0] head.add_child(stylesheet_tag) if @commenting_system headers = (doc / "#content h2, #content h3, #content h4") headers.each do |header| header['data-comment-topic'] = @id_map.associate(header.text) header.add_previous_sibling(create_comment_balloon) end end if @enable_topbar body.children.first.add_previous_sibling(topbar(title)) end body.add_child(javascript_tag) if preamble preamble.remove preamble_copy = (doc / "#header > h1")[0].add_next_sibling(preamble.to_s)[0] preamble_copy['id'] = 'preamble' end toctitle.add_previous_sibling(create_comment_balloon) f.rewind f.truncate(0) f.puts(doc.to_html) end end def stylesheet_tag content = %Q{<style type="text/css">\n} css = File.read("#{TEMPLATES_DIR}/mizuho.css") css.gsub!(/url\('(.*?)\.png'\)/) do data = File.open("#{TEMPLATES_DIR}/#{$1}.png", "rb") do |f| f.read end data = [data].pack('m') data.gsub!("\n", "") "url('data:image/png;base64,#{data}')" end content << css << "\n" if @enable_topbar content << File.read("#{TEMPLATES_DIR}/topbar.css") << "\n" end if @commenting_system == 'disqus' content << File.read("#{TEMPLATES_DIR}/disqus.css") << "\n" elsif @commenting_system == 'intensedebate' content << File.read("#{TEMPLATES_DIR}/intensedebate.css") << "\n" end content << %Q{</style>\n} return content end def topbar(title) content = render_template("topbar.html") content.gsub!(/\{TITLE\}/, title) return content end def javascript_tag content = %Q{<script>} content << File.read("#{TEMPLATES_DIR}/jquery-1.7.1.min.js") << "\n" content << File.read("#{TEMPLATES_DIR}/jquery.hashchange-1.0.0.js") << "\n" content << File.read("#{TEMPLATES_DIR}/mizuho.js") << "\n" if @enable_topbar content << File.read("#{TEMPLATES_DIR}/topbar.js") << "\n" end if @commenting_system == 'juvia' content << %Q{ var JUVIA_URL = '#{@options[:juvia_url]}'; var JUVIA_SITE_KEY = '#{@options[:juvia_site_key]}'; } content << File.read("#{TEMPLATES_DIR}/juvia.js") << "\n" end content << %Q{</script>} return content end def create_comment_balloon return %Q{<a href="javascript:void(0)" class="comments empty" title="Add a comment"><span class="count"></span></a>} end def render_template(name) content = File.read("#{TEMPLATES_DIR}/#{name}") content.gsub!(/\{INLINE_IMAGE:(.*?)\.png\}/) do data = File.open("#{TEMPLATES_DIR}/#{$1}.png", "rb") do |f| f.read end data = [data].pack('m') data.gsub!("\n", "") "data:image/png;base64,#{data}" end return content end def require_options(options, *required_keys) fail = false required_keys.each do |key| if !options.has_key?(key) fail = true argument_name = '--' + key.to_s.gsub('_', '-') STDERR.puts "You must also specify #{argument_name}!" end end exit 1 if fail end end end