# vim:sw=4:ts=4 # $Id: yod.rb,v 1.1 2003/03/20 13:25:38 whythluckystiff Exp $ # # YOD: Yaml Ok Documentation # require 'yaml' # # Hi. Yod is... well... something. Okay, look. # It's a personal project for building documentation. # It's also meant to be an example project. To give # a more complete example of YAML's use. # module Yod VERSION = '0.2' SECTION_GIF =< 2, :y => 0.0, :title => title, :sections => [] } end def pdf.textPara( fontName, fontSize, fontAlign, indent, ybump, textchunk, force_page = false ) if force_page @guts[:y] = 0.0 end begint = false textchunk.split( /\n/ ).each do |text| j = 0 j_p = 0 line_width = ( 6.6 - ( indent * 2.0 ) ) * ClibPDF::INCH self.setFont( fontName, "MacRomanEncoding", fontSize ) while j = text.index( /\s/, j ) or text.length.nonzero? unless j j = -1 j_p = -1 end if j < 0 or self.stringWidth( text[ 0..j-1 ] ) > line_width if @guts[:y] < 0.6 # Start a new page if begint self.endText begint = false end @guts[:page] += 1 @guts[:y] = 10.2 self.pageInit( @guts[:page], ClibPDF::PORTRAIT, ClibPDF::LETTER, ClibPDF::LETTER) self.beginText( 0 ) self.setFont( "NewCenturySchlbk-Roman", "MacRomanEncoding", 10.0) self.text(1.0, 0.2, 0.0, @guts[:title]) self.textAligned( 7.8, 0.2, 0.0, ClibPDF::TEXTPOS_MR, "Page #{@guts[:page]}" ) self.endText self.setgray(0.0) self.setlinewidth(0.2) self.moveto(1.0, 0.4) self.lineto(7.8, 0.4) self.stroke end # # Set the font # unless begint self.beginText( 0 ) self.setFont( fontName, "MacRomanEncoding", fontSize ) begint = true end line = text[ 0..j_p ] # self.text( 1.0 + indent, @guts[:y], 0.0, line ) align = ClibPDF::TEXTPOS_ML origin = 1.0 + indent if fontAlign == RIGHT align = ClibPDF::TEXTPOS_MR origin = 7.8 - indent elsif fontAlign == CENTER align = ClibPDF::TEXTPOS_MM origin = 4.4 end self.textAligned( origin, @guts[:y], 0.0, align, line ) if j_p > 0 text = text[ j_p + 2..-1 ] else text = "" end j = 0 j_p = 0 @guts[:y] -= ybump next end j_p = j - 1 j += 1 end end self.newline if begint self.endText end end def pdf.newline @guts[:y] -= 0.15 end pdf.initPara( @title ) @table_of_contents.each_with_index { |x, i| @contents[x].to_pdf( :Page, pdf, [ i + 1 ] ) } pdf.finalizeAll pdf.savePDFmemoryStreamToFile( output ) pdf.close() when :TableOfContents pdf = args.shift @table_of_contents.each_with_index { |x, i| @contents[x].to_pdf( :TableOfContents, pdf, i + 1 ) } end end def to_chm( *args ) page = args.shift case page when :MakeAll output, prefix = args Yod.multi_mkdir( output, 0755 ) # Write the INI File.open( File.join( output, "#{prefix}.hhp" ), "w" ) { |f| f.write( self.to_chm( :IniFile, prefix ) ) } # Write the bullet GIF File.open( File.join( output, "section.gif" ), "w" ) { |f| f.write( Yod::SECTION_GIF.unpack( "m*" )[0] ) } # Write the CSS File.open( File.join( output, "global.css" ), "w" ) { |f| f.write( Yod::TEXT_CSS ) } # Write the Index File.open( File.join( output, "Index.hhk" ), "w" ) { |f| f.write( self.to_chm( :Index ) ) } # Write the TOC File.open( File.join( output, "Table of Contents.hhc" ), "w" ) { |f| f.write( self.to_chm( :TableOfContents ) ) } # Write each page of the manual self.pages.each { |p| Yod.multi_mkdir( output + File::SEPARATOR + p.html_file_name.split( File::SEPARATOR )[0], 0755 ) File.open( File.join( output, p.html_file_name ), "w" ) { |f| f.write( p.to_chm( :Html ) ) } } when :IniFile prefix = args.shift page_list = self.pages.collect { |p| p.html_file_name } return < EOF } return <
    #{index_content}
EOF when :TableOfContents return <
    #{@table_of_contents.collect { |x| @contents[x].to_chm( :TableOfContents ) }}
EOF end end def to_html( *args ) page = args.shift case page when :MakeAll output = args.shift Yod.multi_mkdir( output, 0755 ) # Write the bullet GIF File.open( File.join( output, "section.gif" ), "w" ) { |f| f.write( Yod::SECTION_GIF.unpack( "m*" )[0] ) } # Write the CSS File.open( File.join( output, "global.css" ), "w" ) { |f| f.write( Yod::TEXT_CSS ) } # Write the Index frame File.open( File.join( output, "index.html" ), "w" ) { |f| f.write( self.to_html( :Frame ) ) } # Write the TOC File.open( File.join( output, "contents.html" ), "w" ) { |f| f.write( self.to_html( :TableOfContents ) ) } # Write each page of the manual self.pages.each { |p| Yod.multi_mkdir( output + File::SEPARATOR + p.html_file_name.split( File::SEPARATOR )[0], 0755 ) File.open( File.join( output, p.html_file_name ), "w" ) { |f| f.write( p.to_html( :Html ) ) } } when :Frame prefix = args.shift page_list = self.pages.collect { |p| p.html_file_name } return < #{@title} EOF when :TableOfContents return <
CONTENTS

#{@table_of_contents.collect { |x| @contents[x].to_html( :TableOfContents ) }}

EOF end end end class DocElement; attr_accessor :title, :elements; def pages; self; end; end class Group < DocElement def initialize( data ) @elements = [] data.each { |ele| name, ele = ele.to_a[0] if ele.is_a?( Yod::DocElement ) ele.title = name @elements << ele else raise Yod::Error, "Invalid node of type #{ele.class} in Group: " + ele.inspect end } end def pages self.elements.collect { |e| e.pages } end def index index_all = {} self.elements.each { |e| index_all.update( e.index ) if e.respond_to? :index } index_all end def to_man( *args ) page = args.shift case page when :Page depth = args.shift if depth.zero? str = ".SH #{self.title}\n" else str = ".Sh #{self.title}\n" end self.elements.each { |e| str += e.to_man( :Page, depth + 1 ) } str end end def to_pdf( *args ) page = args.shift case page when :TableOfContents pdf, idx = args pdf.textCRLFshow( "#{idx}. #{self.title}" ) self.elements.each_with_index { |e, i| e.to_pdf( :TableOfContents, pdf, "#{idx}.#{i+1}" ) } when :Page pdf, idx = args size = 22.0 - ( idx.length * 2.0 ) if size < 14.0 size = 14.0 end pdf.newline pdf.textPara( "NewCenturySchlbk-Roman", size, LEFT, 0.0, 0.4, "#{idx.join('.')}. #{self.title}", idx.length == 1 ) self.elements.each_with_index { |e, i| e.to_pdf( :Page, pdf, [ idx ] + [ i + 1 ] ) } end end def to_chm( page ) case page when :TableOfContents return <
    #{self.elements.collect { |e| e.to_chm( :TableOfContents ) }}
EOF end end def to_html( page ) case page when :TableOfContents return < #{self.title}
    #{self.elements.collect { |e| e.to_html( :TableOfContents ) }}
EOF end end end class Page < DocElement def initialize( data ) @elements = [] data.each { |ele| if ele.is_a?( Yod::PageElement ) @elements << ele else raise Yod::Error, "Invalid node of type #{ele.class} in Page: " + ele.inspect end } end def html_file_name "page" + File::SEPARATOR + @title.downcase.gsub( /[^A-Za-z0-9]/, '_' ) + ".htm" end def index index_all = { @title => self.html_file_name } end def to_man( *args ) page = args.shift case page when :Page depth = args.shift if depth.zero? str = ".SH #{self.title}\n" else str = ".Sh #{self.title}\n" end self.elements.collect { |e| str += e.to_man } str end end def to_pdf( *args ) page = args.shift case page when :TableOfContents pdf, idx = args pdf.textCRLFshow( "#{idx}. #{self.title}" ) when :Page pdf, idx = args size = 22.0 - ( idx.length * 2.0 ) if size < 14.0 size = 14.0 end pdf.newline pdf.textPara( "NewCenturySchlbk-Roman", size, LEFT, 0.0, 0.4, "#{idx.join('.')}. #{self.title}", idx.length == 1 ) self.elements.collect { |e| e.to_pdf( pdf ) } end end def to_chm( page ) case page when :TableOfContents return < EOF when :Html return self.to_html( :Html ) end end def to_html( page ) case page when :TableOfContents return < #{@title} EOF when :Html return < #{@title}
#{@title}
#{self.elements.collect { |e| e.to_html( :Html ) }} EOF end end end class CodePage < Page def initialize( data ) super( data ) ctr = 0 @elements.each { |e| e.text['no'] = ( ctr += 1 ) if Yod::Code === e } end end class ClassDef < Group attr_accessor :const_name def title=( t ) @title = t @const_name = t @elements.each { |e| e.add_const_namespace( self.const_namespace ) } end def add_const_namespace( ns ) self.const_name = ns + self.const_name self.elements.each { |e| e.add_const_namespace( ns ) } end def const_namespace "#{@title}#" end def title "#{@const_name} Class" end end class ModuleDef < ClassDef def const_namespace "#{@title}::" end def title "#{@const_name} Module" end end class Method < DocElement attr_accessor :brief, :since, :arguments, :block, :details, :returns, :const_name def initialize( data ) if Hash === data [:brief, :since, :arguments, :block, :returns, :details].each { |a| self.send( "#{a.id2name}=", data[a.id2name] ) } else raise Yod::Error, "ClassMethod must be a Hash." end end def add_const_namespace( ns ) self.const_name = ns + self.const_name end def html_file_name "class" + File::SEPARATOR + self.title.downcase.gsub( /\W+/, '_' ) + ".htm" end def title=( t ) @title = t @const_name = t end def title "#{@const_name} Method" end def index index_all = { self.title => self.html_file_name } end def method_example case self.const_name when /^(.+)#(\w+)$/ meth = $2 classObj = $1.gsub( /[A-Z]+/ ) { |s| s[0..0] + s[1..-1].downcase }.gsub( /::/, '' ) "a#{classObj}.#{meth}" else self.const_name end end def to_man( *args ) page = args.shift case page when :Page depth = args.shift if depth.zero? str = ".SH #{self.title}\n" else str = ".Sh #{self.title}\n" end str end end def to_pdf( *args ) page = args.shift case page when :TableOfContents pdf, idx = args pdf.textCRLFshow( "#{idx}. #{self.title}" ) when :Page pdf, idx = args txt = {} if Array === @arguments txt['args'] = "\n" + @arguments.collect { |p| if Array === p['type'] " (" + p['type'].join( " or " ) + ") #{p['name']}" else " (#{p['type']}) #{p['name']}" end }.join( ",\n" ) + "\n" end pdf.newline pdf.textPara( "NewCenturySchlbk-Roman", 16.0, LEFT, 0.0, 0.4, "#{idx.join('.')}. #{self.title}" ) pdf.textPara( "NewCenturySchlbk-Roman", 10.0, LEFT, 0.0, 0.2, @brief ) pdf.textPara( "Courier-Bold", 12.0, LEFT, 0.3, 0.2, "#{self.method_example}(#{txt['args']})" ) pdf.newline pdf.textPara( "NewCenturySchlbk-Roman", 14.0, LEFT, 0.0, 0.2, "Parameters" ) if Array === @arguments @arguments.each { |p| pdf.textPara( "NewCenturySchlbk-Italic", 10.0, LEFT, 0.2, 0.1, p['name'] ) pdf.textPara( "NewCenturySchlbk-Roman", 10.0, LEFT, 0.4, 0.2, p['brief'] ) } else pdf.textPara( "NewCenturySchlbk-Roman", 10.0, LEFT, 0.2, 0.2, "None" ) end if Array === @block pdf.newline pdf.textPara( "NewCenturySchlbk-Roman", 14.0, LEFT, 0.0, 0.2, "Block Parameters" ) @block.collect { |p| pdf.textPara( "NewCenturySchlbk-Italic", 10.0, LEFT, 0.2, 0.1, p['name'] ) pdf.textPara( "NewCenturySchlbk-Roman", 10.0, LEFT, 0.4, 0.2, p['brief'] ) } end pdf.newline pdf.textPara( "NewCenturySchlbk-Roman", 14.0, LEFT, 0.0, 0.2, "Returns" ) pdf.textPara( "NewCenturySchlbk-Roman", 10.0, LEFT, 0.4, 0.2, @returns || "None" ) if Array === @details pdf.newline pdf.textPara( "NewCenturySchlbk-Roman", 14.0, LEFT, 0.0, 0.2, "Details" ) @details.each { |e| e.to_pdf( pdf ) } end end end def to_chm( page ) case page when :TableOfContents return < EOF when :Html return self.to_html( :Html ) end end def to_html( page ) case page when :TableOfContents return < #{self.title} EOF when :Html ht = {} if Array === @details ht['remarks'] = <
Details
#{@details.collect { |e| e.to_html( :Html ) }}

EOF end if Array === @arguments ht['args'] = "\n" + @arguments.collect { |p| if Array === p['type'] " (" + p['type'].join( " or " ) + ") #{p['name']}" else " #{p['type']} #{p['name']}" end }.join( ",\n" ) + "\n" ht['args2'] = @arguments.collect { |p| <#{p['name']}
#{Yod.escapeHTML( p['brief'] )}
EOF }.join( "\n" ) else ht['args2'] = "
None
" end if Array === @block ht['blockvars'] = @block.collect { |p| <#{p['name']}
#{Yod.escapeHTML( p['brief'] )}
EOF }.join( "\n" ) ht['blockvars'] = <
Block Parameters
#{ht['blockvars']}
EOF end return < #{self.title}
  #{self.title}
#{@title}
#{Yod.escapeHTML( @brief )}

#{self.method_example}(#{ht['args']})

Parameters
#{ht['args2']}
#{ht['blockvars']}
Return Values
#{@returns || "None"}

#{ht['remarks']} EOF end end end class PageElement attr_accessor :text def initialize( text ) @text = text end end class Paragraph < PageElement def to_man "#{text}\n.LP\n" end def to_pdf( pdf ) pdf.textPara( "NewCenturySchlbk-Roman", 10.0, LEFT, 0.0, 0.2, "#{text}" ) end def to_html( page ) case page when :Html return < #{Yod.escapeHTML( text )}

EOF end end end class Code < PageElement def to_man "#{text['code']}\n.LP\n" end def to_pdf( pdf ) pdf.textPara( "CPDF-Monospace", 12.0, LEFT, 0.3, 0.2, text['code'] ) pdf.textPara( "NewCenturySchlbk-Italic", 10.0, CENTER, 0.3, 0.2, "Ex. #{text['no']}: #{Yod.escapeHTML( text['name'] )}" ) end def to_html( page ) case page when :Html return <#{Yod.escapeHTML( text['code'] )}
Ex. #{text['no']}: #{Yod.escapeHTML( text['name'] )}
EOF end end end class Quote < PageElement def to_man ".IP\n#{text}\n" end def to_pdf( pdf ) pdf.textPara( "NewCenturySchlbk-Italic", 10.0, LEFT, 0.3, 0.2, "#{text}" ) end def to_html( page ) case page when :Html return < #{Yod.escapeHTML( text )} EOF end end end class Title < PageElement def to_man ".Sh #{text}\n" end def to_pdf( pdf ) pdf.newline pdf.textPara( "NewCenturySchlbk-Roman", 14.0, LEFT, 0.0, 0.2, "#{text}" ) end def to_html( page ) case page when :Html return <#{Yod.escapeHTML( text )} EOF end end end YAML.add_domain_type( "yaml4r.sf.net,2003", /^yod\// ) { |type, val| type =~ /^yod\/(\w+)(?::?(\w+))?/ if Yod.const_defined?( $1 ) if $2 Yod.const_get( $1 ).new( $2, val ) else Yod.const_get( $1 ).new( val ) end else raise Yod::Error, "Invalid type #{type} not available in Yod module." end } def Yod.load( io ) YAML::load( io ) end # based on code by WATANABE Tetsuya def Yod.multi_mkdir( mpath, mask ) path = '' mpath.split( File::SEPARATOR ).each do |f| path.concat( f ) Dir.mkdir( path, mask ) unless path == '' || File.exist?( path ) path.concat( File::SEPARATOR ) end end def Yod.escapeHTML( string ) string.to_s.gsub(/&/n, '&').gsub(/\"/n, '"').gsub(/>/n, '>').gsub(/