# # = generate.rb, for the ruby-journal project. # # Documents are written in Textile and converted to HTML with RedCloth. # # However, RedCloth only generates HTML _fragments_. We need to generate HTML _documents_, # with <tt><head></tt> information (especially CSS), etc. # # The simple function +generate_document+ does this. # require 'rubygems' require 'redcloth' require 'extensions/io' require 'celsoft.com/template' # # Takes the Textile file from +path+ (relative to ~/Projects/dev-utils) and generates an HTML # file in the appropriate place, which is the <tt>build/www</tt> directory, and with an +html+ # instead of +textile+ extension. # # The HTML document will contain an inline stylesheet. # # The first "h1." tag in the document is used as the title of the HTML page. # # Special lines !header and !toc are expanded. # def generate_document(input_path, output_dir) output_path = File.join(output_dir, "#{File.basename(input_path)}".sub(/\.textile/, ".html")) raw_text = File.read(input_path) #trace {'input_path'} #trace { 'raw_text.length' } input_text = process_text(raw_text) red_cloth_text = RedCloth.new(input_text) red_cloth_text.fold_lines = true html_fragment = red_cloth_text.to_html html = html_wrap(html_fragment) File.write(output_path, html) #File.write(output_path + '.tmp', input_text) end # # Expand any meta-markers like !header and !toc. # def process_text(text) require 'shellwords' text.gsub(/^!(.*)\s*$/) { command, *args = Shellwords.shellwords($1.strip) case command when 'header' then header(*args) when 'toc' _toc = toc(text) _toc else raise "Unknown command embedded in textile input: #{command}" end } end CSS_FILE = 'etc/doc/textile.css' # # Wraps the given HTML fragment in head, body, etc., to produce a proper document. # # It points to a hardcoded stylesheet. # def html_wrap(fragment) require 'extensions/string' title = fragment.scan(%r{<h1>(.*?)</h1>}).first || [] title = title.first || "(no title)" return %{ <html> <head> <title>#{title}</title> <style type="text/css"> #{File.read(CSS_FILE).tabto(6)} </style> </head> <body> #{fragment} </body> </html> }.tabto(0) end def header(*args) require_arg = args.shift or raise ArgumentError require_text = "%(small-title)<code>require '#{require_arg}'</code>%" if args.shift == 'main' main_page_link = '%(mylink)"main page":index.html%' else main_page_link = '' end %!table{width:100%}.\n| #{require_text} |>. #{main_page_link} |\n\n! end TOC_TEMPLATE = Template::Document.new TOC_TEMPLATE.load %! <table class="toc_table"> <tr> <td> <span class="header">Links</span><br/> <ul id="links"> ${each links} <li><a href="${var href}">${var name}</a></li> ${end} </ul> </td> <td> <span class="header">Contents</span><br/> <ul id="contents"> ${each contents} <li><a href="#\${var anchor}">${var heading}</a> ${if subheadings} <ul> ${each subheadings} <li><a href="#\${var anchor}">${var heading}</a></li> ${end} </ul> </li> ${end} ${end} </ul> </td> </tr> </table> ! Heading = Struct.new(:level, :heading, :anchor) # Returns the HTML for a table containing links (from links.dat) and the contents # of this page, derived from the given text. h2 and h3 markup elements are taken # to be first- and second-level headings in the text. The text itself is modified # so that those headings have anchors on them. def toc(text) # Form links: [ { 'name' => 'Synopsis', 'href' => 'Synopsis.html' }, ... ] links = links().map { |name, href| Hash[ 'name', name, 'href', href ] } # Form contents: [ # { 'heading' => 'Introduction', 'anchor' => 'Introduction', 'subheadings' => [ { ... } ] }, # ... # ] regex = /^h([234])\.\s+(.*?)$/ headings = text.grep(regex).map { |line| line =~ regex level, heading = $1.to_i - 1, $2.strip anchor = heading.gsub(/[^\s\w]/, '') #Hash[ 'level', level, 'heading', heading, 'anchor', anchor ] Heading.new(level, heading, anchor) } contents = [] headings.each do |h| case h.level when 1 contents << Hash['heading', h.heading, 'anchor', h.anchor] when 2 (contents.last['subheadings'] ||= []) << Hash['heading', h.heading, 'anchor', h.anchor] end end # Place anchors into document. This changes the input text. text.gsub!(regex) { match = Regexp.last_match heading = $2.strip anchor = heading.gsub(/[^\s\w]/, '') match[0].chomp + %{ <a name="#{anchor}"/>\n} } # Run it through the template. TOC_TEMPLATE.data = { 'links' => links, 'contents' => contents } TOC_TEMPLATE.output end # Return [ [name, href], [name, href], ... ]. def links @links ||= begin links_file = Pathname.new(__FILE__).dirname + 'links.dat' File.readlines(links_file).grep(/^\w/).map { |line| line =~ %r{^(.*?)/(.*)$} or raise "Bad data in links.dat." [$1.strip, $2.strip] } end end