# 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 ;
}
}
# Generate an item in a list. The type of output can be controlled by the value
# of the 'output-type' variable.
#
rule list-item (
item + # The item to list.
)
{
if $(output-type) = plain
{
lines [ split-at-words "*" $(item) ] : " " " " ;
}
else if $(output-type) = html
{
text - [ escape-html $(item) ]
;
}
}
# Generate the end of a list of items. The type of output can be controlled by
# the value of the 'output-type' variable.
#
rule list-end ( )
{
if $(output-type) = plain
{
lines ;
}
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" "&<>" ;
}