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 = " " ; } 301 else if $(c) = " " { c = " " ; } 302 indent = $(indent)$(c) ; 303 } 304 local html-text = [ escape-html $(text) : " " ] ; 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) "&" "&" ] ; 395 html = [ regex.replace $(html) "<" "<" ] ; 396 html = [ regex.replace $(html) ">" ">" ] ; 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 two three" "&<>" : 507 # escape-html "one two three" "&<>" ; 508} 509