• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2003 Douglas Gregor
2# Copyright 2002, 2003, 2005 Rene Rivera
3# Copyright 2002, 2003, 2004, 2005 Vladimir Prus
4# Distributed under the Boost Software License, Version 1.0.
5# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
6
7# Utilities for generating format independent output. Using these
8# will help in generation of documentation in at minimum plain/console
9# and html.
10
11import modules ;
12import numbers ;
13import string ;
14import regex ;
15import "class" ;
16import scanner ;
17import path ;
18import os ;
19
20# The current output target. Defaults to console.
21output-target = console ;
22
23# The current output type. Defaults to plain. Other possible values are "html".
24output-type = plain ;
25
26# Whitespace.
27.whitespace = [ string.whitespace ] ;
28
29# Redirect
30.redirect-out = ">" ;
31.redirect-append = ">>" ;
32if [ os.name ] = VMS
33{
34    .redirect-out = "| TYPE SYS$INPUT /OUT=" ;
35    .redirect-append = "| APPEND/NEW SYS$INPUT " ;
36}
37
38# Set the target and type of output to generate. This sets both the destination
39# output and the type of docs to generate to that output. The target can be
40# either a file or "console" for echoing to the console. If the type of output
41# is not specified it defaults to plain text.
42#
43rule output (
44    target  # The target file or device; file or "console".
45    type ?  # The type of output; "plain" or "html".
46)
47{
48    type ?= plain ;
49    if $(output-target) != $(target)
50    {
51        output-target = $(target) ;
52        output-type = $(type) ;
53        if $(output-type) = html
54        {
55            text
56                "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">"
57                "<html>"
58                "<head>"
59                "</head>"
60                "<body link=\"#0000ff\" vlink=\"#800080\">"
61                : true
62                : prefix ;
63            text
64                "</body>"
65                "</html>"
66                :
67                : suffix ;
68        }
69    }
70}
71
72
73# Generate a section with a description. The type of output can be controlled by
74# the value of the 'output-type' variable.
75#
76rule section (
77    name  # The name of the section.
78    description *  # A number of description lines.
79)
80{
81    if $(output-type) = plain
82    {
83        lines [ split-at-words "$(name):" ] ;
84        lines ;
85    }
86    else if $(output-type) = html
87    {
88        name = [ escape-html $(name) ] ;
89        text <h3>$(name)</h3> <p> ;
90    }
91    local pre = ;
92    while $(description)
93    {
94        local paragraph = ;
95        while $(description) && [ string.is-whitespace $(description[1]) ] { description = $(description[2-]) ; }
96        if $(pre)
97        {
98            while $(description) && (
99                $(pre) = " $(description[1])" ||
100                ( $(pre) < [ string.chars [ MATCH "^([$(.whitespace)]*)" : " $(description[1])" ] ] )
101                )
102                { paragraph += $(description[1]) ; description = $(description[2-]) ; }
103            while [ string.is-whitespace $(paragraph[-1]) ] { paragraph = $(paragraph[1--2]) ; }
104            pre = ;
105            if $(output-type) = plain
106            {
107                lines $(paragraph) "" : "  " "  " ;
108            }
109            else if $(output-type) = html
110            {
111                text <blockquote> ;
112                lines $(paragraph) ;
113                text </blockquote> ;
114            }
115        }
116        else
117        {
118            while $(description) && ! [ string.is-whitespace $(description[1]) ]
119                { paragraph += $(description[1]) ; description = $(description[2-]) ; }
120            if $(paragraph[1]) = "::" && ! $(paragraph[2])
121            {
122                pre = " " ;
123            }
124            if $(paragraph[1]) = "::"
125            {
126                if $(output-type) = plain
127                {
128                    lines $(paragraph[2-]) "" : "  " "  " ;
129                    lines ;
130                }
131                else if $(output-type) = html
132                {
133                    text <blockquote> ;
134                    lines $(paragraph[2-]) ;
135                    text </blockquote> ;
136                }
137            }
138            else
139            {
140                local p = [ MATCH "(.*)(::)$" : $(paragraph[-1]) ] ;
141                local pws = [ MATCH "([ 	]*)$" : $(p[1]) ] ;
142                p = [ MATCH "(.*)($(pws))($(p[2]))$" :  $(paragraph[-1]) ] ;
143                if $(p[3]) = "::"
144                {
145                    pre = [ string.chars [ MATCH "^([$(.whitespace)]*)" : " $(p[1])" ] ] ;
146                    if ! $(p[2]) || $(p[2]) = "" { paragraph = $(paragraph[1--2]) "$(p[1]):" ; }
147                    else { paragraph = $(paragraph[1--2]) $(p[1]) ; }
148                    if $(output-type) = plain
149                    {
150                        lines [ split-at-words " " $(paragraph) ] : "  " "  " ;
151                        lines ;
152                    }
153                    else if $(output-type) = html
154                    {
155                        text </p> <p> [ escape-html $(paragraph) ] ;
156                    }
157                }
158                else
159                {
160                    if $(output-type) = plain
161                    {
162                        lines [ split-at-words " " $(paragraph) ] : "  " "  " ;
163                        lines ;
164                    }
165                    else if $(output-type) = html
166                    {
167                        text </p> <p> [ escape-html $(paragraph) ] ;
168                    }
169                }
170            }
171        }
172    }
173    if $(output-type) = html
174    {
175        text </p> ;
176    }
177}
178
179
180# Generate the start of a list of items. The type of output can be controlled by
181# the value of the 'output-type' variable.
182#
183rule list-start ( )
184{
185    if $(output-type) = plain
186    {
187    }
188    else if $(output-type) = html
189    {
190        text <ul> ;
191    }
192}
193
194
195# Generate an item in a list. The type of output can be controlled by the value
196# of the 'output-type' variable.
197#
198rule list-item (
199    item +  # The item to list.
200)
201{
202    if $(output-type) = plain
203    {
204        lines [ split-at-words "*" $(item) ] : "    " "  " ;
205    }
206    else if $(output-type) = html
207    {
208        text <li> [ escape-html $(item) ] </li> ;
209    }
210}
211
212
213# Generate the end of a list of items. The type of output can be controlled by
214# the value of the 'output-type' variable.
215#
216rule list-end ( )
217{
218    if $(output-type) = plain
219    {
220        lines ;
221    }
222    else if $(output-type) = html
223    {
224        text </ul> ;
225    }
226}
227
228
229# Split the given text into separate lines, word-wrapping to a margin. The
230# default margin is 78 characters.
231#
232rule split-at-words (
233    text +  # The text to split.
234    : margin ?  # An optional margin, default is 78.
235)
236{
237    local lines = ;
238    text = [ string.words $(text:J=" ") ] ;
239    text = $(text:J=" ") ;
240    margin ?= 78 ;
241    local char-match-1 = ".?" ;
242    local char-match = "" ;
243    while $(margin) != 0
244    {
245        char-match = $(char-match)$(char-match-1) ;
246        margin = [ numbers.decrement $(margin) ] ;
247    }
248    while $(text)
249    {
250        local s = "" ;
251        local t = "" ;
252        # divide s into the first X characters and the rest
253        s = [ MATCH "^($(char-match))(.*)" : $(text) ] ;
254
255        if $(s[2])
256        {
257            # split the first half at a space
258            t = [ MATCH "^(.*)[\\ ]([^\\ ]*)$" : $(s[1]) ] ;
259        }
260        else
261        {
262            t = $(s) ;
263        }
264
265        if ! $(t[2])
266        {
267            t += "" ;
268        }
269
270        text = $(t[2])$(s[2]) ;
271        lines += $(t[1]) ;
272    }
273    return $(lines) ;
274}
275
276
277# Generate a set of fixed lines. Each single item passed in is output on a
278# separate line. For console this just echos each line, but for html this will
279# split them with <br>.
280#
281rule lines (
282    text *      # The lines of text.
283    : indent ?  # Optional indentation prepended to each line after the first.
284    outdent ?   # Optional indentation to prepend to the first line.
285)
286{
287    text ?= "" ;
288    indent ?= "" ;
289    outdent ?= "" ;
290    if $(output-type) = plain
291    {
292        text $(outdent)$(text[1]) $(indent)$(text[2-]) ;
293    }
294    else if $(output-type) = html
295    {
296        local indent-chars = [ string.chars $(indent) ] ;
297        indent = "" ;
298        for local c in $(indent-chars)
299        {
300            if $(c) = " " { c = "&nbsp;" ; }
301            else if $(c) = "	" { c = "&nbsp;&nbsp;&nbsp;&nbsp;" ; }
302            indent = $(indent)$(c) ;
303        }
304        local html-text = [ escape-html $(text) : "&nbsp;" ] ;
305        text $(html-text[1])<br> $(indent)$(html-text[2-])<br> ;
306    }
307}
308
309
310# Output text directly to the current target. When doing output to a file, one
311# can indicate if the text should be output to "prefix" it, as the "body"
312# (default), or "suffix" of the file. This is independent of the actual
313# execution order of the text rule. This rule invokes a singular action, one
314# action only once, which does the build of the file. Therefore actions on the
315# target outside of this rule will happen entirely before and/or after all
316# output using this rule.
317#
318rule text (
319    strings *               # The strings of text to output.
320    : overwrite ?           # True to overwrite the output (if it is a file).
321    : prefix-body-suffix ?  # Indication to output prefix, body, or suffix (for
322                            # a file).
323)
324{
325    prefix-body-suffix ?= body ;
326    if $(output-target) = console
327    {
328        if ! $(strings)
329        {
330            ECHO ;
331        }
332        else
333        {
334            for local s in $(strings)
335            {
336                ECHO $(s) ;
337            }
338        }
339    }
340    if ! $($(output-target).did-action)
341    {
342        $(output-target).did-action = yes ;
343        $(output-target).text-prefix = ;
344        $(output-target).text-body = ;
345        $(output-target).text-suffix = ;
346
347        nl on $(output-target) = "
348" ;
349        text-redirect on $(output-target) = $(.redirect-append) ;
350        if $(overwrite)
351        {
352            text-redirect on $(output-target) = $(.redirect-out) ;
353        }
354        text-content on $(output-target) = ;
355
356        text-action $(output-target) ;
357
358        if $(overwrite) && $(output-target) != console
359        {
360            check-for-update $(output-target) ;
361        }
362    }
363    $(output-target).text-$(prefix-body-suffix) += $(strings) ;
364    text-content on $(output-target) =
365        $($(output-target).text-prefix)
366        $($(output-target).text-body)
367        $($(output-target).text-suffix) ;
368}
369
370
371# Outputs the text to the current targets, after word-wrapping it.
372#
373rule wrapped-text ( text + )
374{
375    local lines = [ split-at-words $(text) ] ;
376    text $(lines) ;
377}
378
379
380# Escapes text into html/xml printable equivalents. Does not know about tags and
381# therefore tags fed into this will also be escaped. Currently escapes space,
382# "<", ">", and "&".
383#
384rule escape-html (
385    text +  # The text to escape.
386    : space ?  # What to replace spaces with, defaults to " ".
387)
388{
389    local html-text = ;
390    while $(text)
391    {
392        local html = $(text[1]) ;
393        text = $(text[2-]) ;
394        html = [ regex.replace $(html) "&" "&amp;" ] ;
395        html = [ regex.replace $(html) "<" "&lt;" ] ;
396        html = [ regex.replace $(html) ">" "&gt;" ] ;
397        if $(space)
398        {
399            html = [ regex.replace $(html) " " "$(space)" ] ;
400        }
401        html-text += $(html) ;
402    }
403    return $(html-text) ;
404}
405
406
407# Outputs the text strings collected by the text rule to the output file.
408#
409actions quietly text-action
410{
411    @($(STDOUT):E=$(text-content:J=$(nl))) $(text-redirect) "$(<)"
412}
413
414if [ os.name ] = VMS
415{
416    actions quietly text-action
417    {
418        @($(STDOUT):E=$(text-content:J=$(nl))) $(text-redirect) $(<:W)
419    }
420}
421
422rule get-scanner ( )
423{
424    if ! $(.scanner)
425    {
426        .scanner = [ class.new print-scanner ] ;
427    }
428    return $(.scanner) ;
429}
430
431
432# The following code to update print targets when their contents change is a
433# horrible hack. It basically creates a target which binds to this file
434# (print.jam) and installs a scanner on it which reads the target and compares
435# its contents to the new contents that we are writing.
436#
437rule check-for-update ( target )
438{
439    local scanner = [ get-scanner ] ;
440    local file = [ path.native [ modules.binding $(__name__) ] ] ;
441    local g = [ MATCH <(.*)> : $(target:G) ] ;
442    local dependency-target = $(__file__:G=$(g:E=)-$(target:G=)-$(scanner)) ;
443    DEPENDS $(target) : $(dependency-target) ;
444    SEARCH on $(dependency-target) = $(file:D) ;
445    ISFILE $(dependency-target) ;
446    NOUPDATE $(dependency-target) ;
447    base on $(dependency-target) = $(target) ;
448    scanner.install $(scanner) : $(dependency-target) ;
449    return $(dependency-target) ;
450}
451
452
453class print-scanner : scanner
454{
455    import path ;
456    import os ;
457
458    rule pattern ( )
459    {
460        return "(One match...)" ;
461    }
462
463    rule process ( target : matches * : binding )
464    {
465        local base = [ on $(target) return $(base) ] ;
466        local nl = [ on $(base) return $(nl) ] ;
467        local text-content = [ on $(base) return $(text-content) ] ;
468        local dir = [ on $(base) return $(LOCATE) ] ;
469        if $(dir)
470        {
471            dir = [ path.make $(dir) ] ;
472        }
473        local file = [ path.native [ path.join $(dir) $(base:G=) ] ] ;
474        local actual-content ;
475        if [ os.name ] = NT
476        {
477            actual-content = [ SHELL "type \"$(file)\" 2>nul" ] ;
478        }
479        else if [ os.name ] = VMS
480        {
481            actual-content = [ SHELL "PIPE TYPE $(file:W) 2>NL:" ] ;
482        }
483        else
484        {
485            actual-content = [ SHELL "cat \"$(file)\" 2>/dev/null" ] ;
486        }
487        if $(text-content:J=$(nl)) != $(actual-content)
488        {
489            ALWAYS $(base) ;
490        }
491    }
492}
493
494
495rule __test__ ( )
496{
497    import assert ;
498
499    assert.result one two three   : split-at-words one two three : 5 ;
500    assert.result "one two" three : split-at-words one two three : 8 ;
501    assert.result "one two" three : split-at-words one two three : 9 ;
502    assert.result "one two three" : split-at-words one two three ;
503
504    # VP, 2004-12-03 The following test fails for some reason, so commenting it
505    # out.
506    #assert.result "one&nbsp;two&nbsp;three" "&amp;&lt;&gt;" :
507    #    escape-html "one two three" "&<>" ;
508}
509