require 'set' require 'fileutils' require 'active_support/core_ext/string/output_safety' require 'action_controller' require 'action_view' require 'rails_guides/indexer' require 'rails_guides/helpers' require 'rails_guides/levenshtein' module RailsGuides class Generator attr_reader :guides_dir, :source_dir, :output_dir GUIDES_RE = /\.(?:textile|html\.erb)$/ def initialize(output=nil) initialize_dirs(output) create_output_dir_if_needed end def generate generate_guides copy_assets end private def initialize_dirs(output) @guides_dir = File.join(File.dirname(__FILE__), '..') @source_dir = File.join(@guides_dir, "source") @output_dir = output || File.join(@guides_dir, "output") end def create_output_dir_if_needed FileUtils.mkdir_p(output_dir) end def generate_guides guides_to_generate.each do |guide| output_file = output_file_for(guide) generate_guide(guide, output_file) if generate?(guide, output_file) end end def guides_to_generate guides = Dir.entries(source_dir).grep(GUIDES_RE) ENV.key?("ONLY") ? select_only(guides) : guides end def select_only(guides) prefixes = ENV["ONLY"].split(",").map(&:strip) guides.select do |guide| prefixes.any? {|p| guide.start_with?(p)} end end def copy_assets FileUtils.cp_r(File.join(guides_dir, 'images'), File.join(output_dir, 'images')) FileUtils.cp_r(File.join(guides_dir, 'files'), File.join(output_dir, 'files')) end def output_file_for(guide) guide.sub(GUIDES_RE, '.html') end def generate?(source_file, output_file) fin = File.join(source_dir, source_file) fout = File.join(output_dir, output_file) ENV['ALL'] == '1' || !File.exists?(fout) || File.mtime(fout) < File.mtime(fin) end def generate_guide(guide, output_file) puts "Generating #{output_file}" File.open(File.join(output_dir, output_file), 'w') do |f| view = ActionView::Base.new(source_dir) view.extend(Helpers) if guide =~ /\.html\.erb$/ # Generate the erb pages with textile formatting - e.g. index/authors result = view.render(:layout => 'layout', :file => guide) else body = File.read(File.join(source_dir, guide)) 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 ENV.key?("WARN_BROKEN_LINKS") end result = insert_edge_badge(result) if ENV.key?('INSERT_EDGE_BADGE') f.write result end end def set_header_section(body, view) new_body = body.gsub(/(.*?)endprologue\./m, '').strip header = $1 header =~ /h2\.(.*)/ page_title = $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(/