# 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 ; import "class" ; import scanner ; import path ; # The current output target. Defaults to console. output-target = console ; # The current output type. Defaults to plain. Other possible values are "html". 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. # 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. # 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) ; if $(overwrite) && $(output-target) != console { check-for-update $(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) "$(<)" } rule get-scanner ( ) { if ! $(.scanner) { .scanner = [ class.new print-scanner ] ; } return $(.scanner) ; } # The following code to update print targets when their contents # change is a horrible hack. It basically creates a target which # binds to this file (print.jam) and installs a scanner on it # which reads the target and compares its contents to the new # contents that we're writing. # rule check-for-update ( target ) { local scanner = [ get-scanner ] ; local file = [ path.native [ modules.binding $(__name__) ] ] ; local g = [ MATCH <(.*)> : $(target:G) ] ; local dependency-target = $(__file__:G=$(g:E=)-$(target:G=)-$(scanner)) ; DEPENDS $(target) : $(dependency-target) ; SEARCH on $(dependency-target) = $(file:D) ; ISFILE $(dependency-target) ; NOUPDATE $(dependency-target) ; base on $(dependency-target) = $(target) ; scanner.install $(scanner) : $(dependency-target) none ; return $(dependency-target) ; } class print-scanner : scanner { import path ; import os ; rule pattern ( ) { return "(One match...)" ; } rule process ( target : matches * : binding ) { local base = [ on $(target) return $(base) ] ; local nl = [ on $(base) return $(nl) ] ; local text-content = [ on $(base) return $(text-content) ] ; local dir = [ on $(base) return $(LOCATE) ] ; if $(dir) { dir = [ path.make $(dir) ] ; } local file = [ path.native [ path.join $(dir) $(base:G=) ] ] ; local actual-content ; if [ os.name ] = NT { actual-content = [ SHELL "type \"$(file)\" 2>nul" ] ; } else { actual-content = [ SHELL "cat \"$(file)\" 2>/dev/null" ] ; } if $(text-content:J=$(nl)) != $(actual-content) { ALWAYS $(base) ; } } } 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" "&<>" ; }