# Copyright 2003 Douglas Gregor # Copyright 2002, 2003, 2005 Rene Rivera # Copyright 2002, 2003, 2004, 2005 Vladimir Prus # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) # Utilities for generating format independent output. Using these # will help in generation of documentation in at minimum plain/console # and html. import modules ; import numbers ; import string ; import regex ; # The current output target. Defaults to console. output-target = console ; # The current output type. Defaults to plain. output-type = plain ; # Whitespace. .whitespace = [ string.whitespace ] ; # Set the target and type of output to generate. This sets both # the destination output and the type of docs to generate to that # output. The target can be either a file or "console" for echoing # to the console. If the type of output is not specified it defaults # to plain text. # rule output ( target # The target file or device; file or "console". type ? # The type of output; "plain", or "html". ) { type ?= plain ; if $(output-target) != $(target) { output-target = $(target) ; output-type = $(type) ; if $(output-type) = html { text "" "" "" "" "" : true : prefix ; text "" "" : : suffix ; } } } # Generate a section with a description. The type of output can be # controlled by the value of the 'output-type' variable. If not set # it defaults to 'console' indicating immediate display to the console. # Other possible values are: 'html-file'. # rule section ( name # The name of the section. description * # A number of description lines. ) { if $(output-type) = plain { lines [ split-at-words $(name): ] ; lines ; } else if $(output-type) = html { name = [ escape-html $(name) ] ; text

$(name)

; } local pre = ; while $(description) { local paragraph = ; while $(description) && [ string.is-whitespace $(description[1]) ] { description = $(description[2-]) ; } if $(pre) { while $(description) && ( $(pre) = " $(description[1])" || ( $(pre) < [ string.chars [ MATCH "^([$(.whitespace)]*)" : " $(description[1])" ] ] ) ) { paragraph += $(description[1]) ; description = $(description[2-]) ; } while [ string.is-whitespace $(paragraph[-1]) ] { paragraph = $(paragraph[1--2]) ; } pre = ; if $(output-type) = plain { lines $(paragraph) "" : " " " " ; } else if $(output-type) = html { text

; lines $(paragraph) ; text
; } } else { while $(description) && ! [ string.is-whitespace $(description[1]) ] { paragraph += $(description[1]) ; description = $(description[2-]) ; } if $(paragraph[1]) = :: && ! $(paragraph[2]) { pre = " " ; } if $(paragraph[1]) = :: { if $(output-type) = plain { lines $(paragraph[2-]) "" : " " " " ; lines ; } else if $(output-type) = html { text
; lines $(paragraph[2-]) ; text
; } } else { local p = [ MATCH "(.*)(::)$" : $(paragraph[-1]) ] ; local pws = [ MATCH "([ ]*)$" : $(p[1]) ] ; p = [ MATCH "(.*)($(pws))($(p[2]))$" : $(paragraph[-1]) ] ; if $(p[3]) = :: { pre = [ string.chars [ MATCH "^([$(.whitespace)]*)" : " $(p[1])" ] ] ; if ! $(p[2]) || $(p[2]) = "" { paragraph = $(paragraph[1--2]) $(p[1]): ; } else { paragraph = $(paragraph[1--2]) $(p[1]) ; } if $(output-type) = plain { lines [ split-at-words " " $(paragraph) ] : " " " " ; lines ; } else if $(output-type) = html { text

[ escape-html $(paragraph) ] ; } } else { if $(output-type) = plain { lines [ split-at-words " " $(paragraph) ] : " " " " ; lines ; } else if $(output-type) = html { text

[ escape-html $(paragraph) ] ; } } } } } if $(output-type) = html { text

; } } # Generate the start of a list of items. The type of output can be # controlled by the value of the 'output-type' variable. If not set # it defaults to 'console' indicating immediate display to the console. # Other possible values are: 'html-file'. # rule list-start ( ) { if $(output-type) = plain { } else if $(output-type) = html { text ; } } # Split the given text into separate lines, word-wrapping to a margin. # The default margin is 78 characters. # rule split-at-words ( text + # The text to split. : margin ? # An optional margin, default is 78. ) { local lines = ; text = [ string.words $(text:J=" ") ] ; text = $(text:J=" ") ; margin ?= 78 ; local char-match-1 = ".?" ; local char-match = "" ; while $(margin) != 0 { char-match = $(char-match)$(char-match-1) ; margin = [ numbers.decrement $(margin) ] ; } while $(text) { local s = "" ; local t = "" ; # divide s into the first X characters and the rest s = [ MATCH "^($(char-match))(.*)" : $(text) ] ; if $(s[2]) { # split the first half at a space t = [ MATCH "^(.*)[\\ ]([^\\ ]*)$" : $(s[1]) ] ; } else { t = $(s) ; } if ! $(t[2]) { t += "" ; } text = $(t[2])$(s[2]) ; lines += $(t[1]) ; } return $(lines) ; } # Generate a set of fixed lines. Each single item passed in is # output on a separate line. For console this just echos each line, # but for html this will split them with
. # rule lines ( text * # The lines of text. : indent ? # Optional indentation prepended to each line after the first one. outdent ? # Optional indentation to prepend to the first line. ) { text ?= "" ; indent ?= "" ; outdent ?= "" ; if $(output-type) = plain { text $(outdent)$(text[1]) $(indent)$(text[2-]) ; } else if $(output-type) = html { local indent-chars = [ string.chars $(indent) ] ; indent = "" ; for local c in $(indent-chars) { if $(c) = " " { c =   ; } else if $(c) = " " { c =      ; } indent = $(indent)$(c) ; } local html-text = [ escape-html $(text) :   ] ; text $(html-text[1])
$(indent)$(html-text[2-])
; } } # Output text directly to the current target. When doing output # to a file, one can indicate if the text should be output to # "prefix" it, as the "body" (default), or "suffix" of the file. This is # independant of the actual execution order of the text rule. This rule # invokes a singular action, one action only once, which does the # build of the file. Therefore actions on the target outside of this # rule will happen entirely before and/or after all output using this rule. # rule text ( strings * # The strings of text to output. : overwrite ? # true to overwrite the output (if it is a file) : prefix-body-suffix ? # Indication to output prefix, body, or suffix (for a file). ) { prefix-body-suffix ?= body ; if $(output-target) = console { if ! $(strings) { ECHO ; } else { for local s in $(strings) { ECHO $(s) ; } } } if ! $($(output-target).did-action) { $(output-target).did-action = yes ; $(output-target).text-prefix = ; $(output-target).text-body = ; $(output-target).text-suffix = ; nl on $(output-target) = " " ; text-redirect on $(output-target) = ">>" ; if $(overwrite) { text-redirect on $(output-target) = ">" ; } text-content on $(output-target) = ; text-action $(output-target) ; } $(output-target).text-$(prefix-body-suffix) += $(strings) ; text-content on $(output-target) = $($(output-target).text-prefix) $($(output-target).text-body) $($(output-target).text-suffix) ; } # Outputs the text to the current targets, after word-wrapping it. rule wrapped-text ( text + ) { local lines = [ split-at-words $(text) ] ; text $(lines) ; } # Escapes text into html/xml printable equivalents. # Does not know about tags and therefore tags fed into # this will also be escaped. Currently escapes space, "<", ">", and "&". # rule escape-html ( text + # The text to escape. : space ? # What to replace spaces with, defaults to " ". ) { local html-text = ; while $(text) { local html = $(text[1]) ; text = $(text[2-]) ; html = [ regex.replace $(html) "&" "&" ] ; html = [ regex.replace $(html) "<" "<" ] ; html = [ regex.replace $(html) ">" ">" ] ; if $(space) { html = [ regex.replace $(html) " " "$(space)" ] ; } html-text += $(html) ; } return $(html-text) ; } # Outputs the text strings collected by the text rule to the output # file. # actions quietly text-action { @($(STDOUT):E=$(text-content:J=$(nl))) $(text-redirect) "$(<)" } local rule __test__ ( ) { import assert ; assert.result one two three : split-at-words one two three : 5 ; assert.result "one two" three : split-at-words one two three : 8 ; assert.result "one two" three : split-at-words one two three : 9 ; assert.result "one two three" : split-at-words one two three ; # VP, 2004-12-03 The following test fails for some reason, # so commenting it out. #assert.result "one two three" "&<>" : # escape-html "one two three" "&<>" ; }