# -------------------------------------------------------------------------- # Portions of this code were taken from the "poignant.rb" script created # by whytheluckystiff, which generates "Why's (Poignant) Guide to Ruby". # The original script may be obtained from the poignant CVS repository, # at http://poignant.rubyforge.org. # # This script is distributed under the by-sa/1.0 Creative Commons license. # -------------------------------------------------------------------------- require 'erb' require 'fileutils' require 'yaml' require 'redcloth' require 'syntax/convertors/html' module Net ; module SSH module Manual class Manual attr_accessor :product, :meta, :chapters, :tutorials, :examples, :recent_updates def Manual.load( file_name ) File.open( file_name ) { |file| YAML.load( file ) } end end class Meta attr_accessor :copyright, :author, :email end class Product attr_accessor :name, :tagline, :version, :logo, :urls, :project end class Sidebar attr_accessor :title, :content end class Chapter attr_accessor :index, :title, :sections def initialize( index, title, sections ) @index = index @title = title section_index = 0 @sections = ( sections || [] ).collect do |section| section_index += 1 if section.respond_to? :keys Section.new( section_index, section.keys.first, section.values.first ) else section_index -= 1 Section.new( section_index, nil, section ) end end end def page_title "Chapter #{index}: #{title}" end end class Tutorial attr_accessor :index, :title, :brief, :intro, :steps, :summary def initialize( index, title, brief, intro, steps, summary ) @index = index @title = title @brief = RedCloth.new( brief ) @intro = RedCloth.new( intro ) if intro @summary = RedCloth.new( summary ) if summary @steps = steps.map { |step| RedCloth.new( step ) } end def page_title "Tutorial #{index}: #{title}" end end class Example attr_accessor :index, :title, :brief, :intro, :design, :implementation, :summary def initialize( index, title, brief, intro, design, implementation, summary ) @index = index @title = title @brief = RedCloth.new( brief ) @intro = RedCloth.new( intro ) @design = RedCloth.new( design ) @implementation = RedCloth.new( implementation ) @summary = RedCloth.new( summary ) end def page_title "Example #{index}: #{title}" end end class Section attr_accessor :index, :title, :content def initialize( index, title, content ) @index = index @title = RedCloth.new( title ).to_html.gsub( %r{}, "" ) if title @content = FigureContainer.new( content || "" ) end end class FigureContainer def initialize( content ) @content = content @html = nil end def to_html return @html if @html extract_figures convert_to_html replace_figures @html end private Figure = Struct.new( :opts, :body ) def extract_figures @figures = [] @content.gsub!( /^\[!figure( .*?)?\n(.*?)\n!\]$/m ) do body = $2.strip opts = Hash[*$1.strip.split(/,/).map{|p| p.split(/=/)}.flatten] @figures << Figure.new( opts, body ) "====#{@figures.length-1}====" end end def convert_to_html @html = ( @content.length < 1 ? "" : RedCloth.new( @content ).to_html ) end def replace_figures @html.gsub!( /

====(.*?)====<\/p>/ ) do figure = @figures[$1.to_i] lang = figure.opts["lang"] caption = figure.opts["caption"] || "Figure" caption << " [#{lang}]" if lang body = figure.body if lang convertor = Syntax::Convertors::HTML.for_syntax( lang ) body = "" + "

" + convertor.convert( body ) + "
" end if figure.opts["number"] && eval(figure.opts["number"]) line = 1 numbers = "" body.each_line { numbers << "#{line}
"; line += 1 } body = "" + "" + "
#{numbers}#{body}
" end "
\n" + "#{caption}\n" + "
#{body}
" + "
" end end end YAML.add_private_type( 'file' ) { |type_id, value| File.read( value ) rescue "" } YAML.add_private_type( 'eval' ) { |type_id, value| eval( value ) } YAML.add_domain_type( 'jamisbuck.org,2004', 'manual' ) do |taguri, val| index = 0 val['chapters'].collect! do |chapter| index += 1 Chapter.new( index, chapter.keys.first, chapter.values.first ) end index = 0 ( val['tutorials'] ||= [] ).collect! do |tutorial| index += 1 content = tutorial.values.first Tutorial.new( index, tutorial.keys.first, content['brief'], content['intro'], content['steps'], content['summary'] ) end index = 0 ( val['examples'] ||= [] ).collect! do |example| index += 1 content = example.values.first Example.new( index, example.keys.first, content['brief'], content['intro'], content['design'], content['implementation'], content['summary'] ) end YAML.object_maker( Manual, val ) end YAML.add_domain_type( 'jamisbuck.org,2004', 'meta' ) do |taguri, val| YAML.object_maker( Meta, val ) end YAML.add_domain_type( 'jamisbuck.org,2004', 'product' ) do |taguri, val| version = val["version"] if version.respond_to?( :type_id ) if version.type_id == "version" if version.value =~ %r{(.*)/(.*)} require_file, constant = $1, $2 else constant = version.value end require require_file if require_file val["version"] = eval(constant) else raise "Unexpected type: #{val.type_id}" end end YAML.object_maker( Product, val ) end YAML.add_domain_type( 'jamisbuck.org,2004', 'sidebar' ) do |taguri, val| YAML.object_maker( Sidebar, 'title' => val.keys.first, 'content'=> RedCloth.new( val.values.first ) ) end end end if __FILE__ == $0 def log_action( action ) $stderr.puts action end unless ( output_path = ARGV[0] ) $stderr.puts "Usage: #{$0} [/path/to/save/html]" exit end FileUtils.mkdir_p File.join( output_path, "stylesheets" ) log_action "Loading manual.yml..." manual = Net::SSH::Manual::Manual.load( 'manual.yml' ) # force these to be defined at the TOPLEVEL_BINDING object = nil guts = nil page = File.open( "page.erb" ) { |file| ERB.new( file.read ) } page.filename = "page.erb" template = File.open( "index.erb" ) { |file| ERB.new( file.read ) } template.filename = "index.erb" File.open( File.join( output_path, "index.html" ), "w" ) do |file| guts = template.result file << page.result end template = File.open( "chapter.erb" ) { |file| ERB.new( file.read ) } template.filename = "chapter.erb" manual.chapters.each_with_index do |object,index| log_action "Processing chapter ##{object.index}..." previous_chapter = ( index < 1 ? nil : manual.chapters[index-1] ) next_chapter = manual.chapters[index+1] File.open( File.join( output_path, "chapter-#{object.index}.html" ), "w" ) do |file| guts = template.result( binding ) file << page.result( binding ) end end template = File.open( "tutorial.erb" ) { |file| ERB.new( file.read ) } template.filename = "tutorial.erb" manual.tutorials.each do |object| log_action "Processing tutorial ##{object.index}..." File.open( File.join( output_path, "tutorial-#{object.index}.html" ), "w" ) do |file| guts = template.result file << page.result end end # template = File.open( "example.erb" ) { |file| ERB.new( file.read ) } # template.filename = "example.erb" # manual.examples.each do |object| # log_action "Processing example ##{object.index}..." # File.open( File.join( output_path, "example-#{object.index}.html" ), "w" ) do |file| # guts = template.result # file << page.result # end # end log_action "Copying style sheets..." FileUtils.cp Dir["stylesheets/*.css"], File.join( output_path, "stylesheets" ) log_action "Done!" end