# --------------------------------------------------------------------------- # # This script generates the guides. It can be invoked either directly or via the # generate_guides rake task within the railties directory. # # Guides are taken from the source directory, and the resulting HTML goes into the # output directory. Assets are stored under files, and copied to output/files as # part of the generation process. # # Options: # # :warnings # If you are writing a guide, please work always with :warnings = true. # Users can generate the guides, and thus this flag is off by default. # # Internal links (anchors) are checked. If a reference is broken levenshtein # distance is used to suggest an existing one. This is useful since IDs are # generated by Textile from headers and thus edits alter them. # # Also detects duplicated IDs. They happen if there are headers with the same # text. Please do resolve them, if any, so guides are valid XHTML. # # :force # Set to true to force the generation of all guides. # # :edge # Set to true to indicate generated guides should be marked as edge. This # inserts a badge and changes the preamble of the home page. # # :layout # Use to customize layout. Layout must be placed to the same directory with guides sources. # # --------------------------------------------------------------------------- require 'set' require 'fileutils' require 'active_support/core_ext/string/output_safety' require 'active_support/core_ext/object/blank' require 'action_controller' require 'action_view' require 'hayde/indexer' require 'hayde/helpers' require 'hayde/levenshtein' require 'hayde/file_utils' module Hayde class Generator include Hayde::Utils::Files attr_accessor :output_dir, :assets_dir, :warnings, :edge, :force, :layout filelist_attribute :sources GUIDES_RE = /\.(?:textile|html\.erb)$/ def initialize(output = nil) set_defaults(output) yield self if block_given? end def generate puts "Generating guides has been started." generate_guides puts "Copying assets ..." copy_assets puts "Generating guides has been finished." end def clean FileUtils.rm_r Dir.glob(File.join(output_dir, '*')) end private def set_defaults(output) root_dir = Rails.root if defined? Rails root_dir ||= Dir.pwd @layout = 'layout' @assets_dir = File.join(root_dir, 'guides', 'assets') @output_dir = output || File.join(root_dir, 'docs', 'guides') FileUtils.mkdir_p(@output_dir) end def generate_guides sources.each do |source| output = output_file_for(source) generate_guide(source, output) if generate?(source, output) end end def copy_assets #FileUtils.cp(Dir.glob(File.join(assets_dir, '**', '*')).reject {|file| file !~ /(css|js|png|gif)$/ }, output_dir) copy(assets_dir, output_dir, /\.$|\.svn/) end def copy(source, destination, exclude) Dir.foreach(source) do |file| next if exclude && exclude.match(file) source_file = File.join(source, file) destination_file = File.join(destination, file) puts "#{source_file} --> #{destination_file}" if File.directory?(source_file) FileUtils.mkdir(destination_file) if !File.directory?(destination_file) copy(source_file, destination_file, exclude) else FileUtils.cp(source_file, destination_file) end end end def output_file_for(source) output = File.basename(source).sub(GUIDES_RE, '.html') File.join(output_dir, output) end def generate?(source, output) force || !File.exists?(output) || File.mtime(output) < File.mtime(source) end def generate_guide(source, output) puts "Generating #{output} ..." File.open(output, 'w') do |f| view = ActionView::Base.new(File.dirname(source), :edge => edge) view.extend(Helpers) if source =~ /\.html\.erb$/ # Generate the special pages like the home. result = view.render(:layout => layout, :file => source) else body = File.read(source) body = set_header_section(body, view) body = set_index(body, view) result = view.render(:layout => layout, :text => textile(body)) warn_about_broken_links(result) if warnings end f.write result end end def set_header_section(body, view) new_body = body.gsub(/(.*?)endprologue\./m, '').strip header = $1 header =~ /h2\.(.*)/ page_title = "Framework Guides: #{$1.strip}" header = textile(header) view.content_for(:page_title) { page_title.html_safe } view.content_for(:header_section) { header.html_safe } new_body end def set_index(body, view) index = <<-INDEX
#{es}
dirty_workaround_for_notextile_(\d+)
}) do |_| code_blocks[$1.to_i] end end def warn_about_broken_links(html) anchors = extract_anchors(html) check_fragment_identifiers(html, anchors) end def extract_anchors(html) # Textile generates headers with IDs computed from titles. anchors = Set.new html.scan(/