# Copyright (C) 2002, Rene Rivera. Permission to copy, use, modify, sell and # distribute this software is granted provided this copyright notice appears in # all copies. This software is provided "as is" without express or implied # warranty, and with no claim as to its suitability for any purpose. # Documentation system, handles --help requests. # It defines rules that attach documentation to modules, rules, and variables. # Collects and generates documentation for the various parts of the build system. # The documentation is collected from comments integrated into the code. import modules ; import print ; import set ; import container ; import class ; # List of possible modules, but which really aren't. # not-modules = boost-build bootstrap site-config test user-config ; # The type of output to genetare. # "console" is formated text echoed to the console (the default); # "text" is formated text appended to a help.txt file; # "html" is HTLM output to a file. # help-output = console ; # Handle --help options, displaying or generating instructions and # documentation. If this does generate any help it exits after doing # so, to prevent any build actions from occuring. # rule help ( ) { local args = [ modules.peek : ARGV ] ; local did-help = ; for local arg in $(args[2-]) { local help = ; local sub-help = ; if $(arg) = --help { help = _top_ ; } else { if [ MATCH --help-[^\\.]*(\\.) : $(arg) ] { help = [ MATCH --help-([^\\.]*) : $(arg) ] ; sub-help = [ MATCH --help-[^\\.]*\\.(.*) : $(arg) ] ; } else { help = [ MATCH --help-(.*) : $(arg) ] ; } } if $(help) = output { help-output = $(sub-help) ; did-help = true ; help = ; } if ! $(sub-help) { # Any help. if $(help) && $(help) = _top_ { print-help-top ; did-help = true ; help = ; } # All modules? if $(help) && $(help) = "all" { local path-to-modules = [ modules.peek : BOOST_BUILD_PATH ] ; path-to-modules ?= . ; local modules-to-list = [ set.difference [ GLOB $(path-to-modules) : *\\.jam ] : [ GLOB $(path-to-modules) : $(not-modules)\\.jam ] ] ; do-scan $(modules-to-list[1--2]) ; do-scan $(modules-to-list[-1]) : print-help-all ; did-help = true ; help = ; } } # Is it a module? local path-to-modules = [ modules.peek : BOOST_BUILD_PATH ] ; path-to-modules ?= . ; local module-path = [ GLOB $(path-to-modules) : $(help)\\.jam ] ; if $(help) && $(module-path) && $(sub-help) { do-scan $(module-path[1]) : print-help-rules $(sub-help) ; do-scan $(module-path[1]) : print-help-variables $(sub-help) ; did-help = true ; help = ; } if $(help) && $(module-path) { do-scan $(module-path[1]) : print-help-module ; did-help = true ; help = ; } # Unrecognized. if $(help) { EXIT "Unrecognized help option '"$(arg)"'." ; } } return $(did-help) ; } # Extracts the brief comment from a complete comment. The brief # comment is the first sentence. # local rule brief-comment ( docs * # The comment documentation. ) { local d = $(docs:J=" ") ; local p = [ MATCH ".*([.])$" : $(d) ] ; if ! $(p) { d = $(d)"." ; } d = $(d)" " ; local m = [ MATCH "^([^.]+[.])(.*)" : $(d) ] ; local brief = $(m[1]) ; while $(m[2]) && [ MATCH "^([^ ])" : $(m[2]) ] { m = [ MATCH "^([^.]+[.])(.*)" : $(m[2]) ] ; brief += $(m[1]) ; } return $(brief:J="") ; } # Specifies the documentation for the current module. # local rule set-module-doc ( module-name ? # The name of the module to document. : docs * # The documentation for the module. ) { module-name ?= * ; $(module-name).brief = [ brief-comment $(docs) ] ; $(module-name).docs = $(docs) ; if ! $(module-name) in $(documented-modules) { documented-modules += $(module-name) ; } } # Specifies the documentation for the current module. # local rule set-module-copyright ( module-name ? # The name of the module to document. : copyright * # The copyright for the module. ) { module-name ?= * ; $(module-name).copy-brief = [ brief-comment $(copyright) ] ; $(module-name).copy-docs = $(docs) ; if ! $(module-name) in $(documented-modules) { documented-modules += $(module-name) ; } } # Specifies the documentation for a rule in the current module. # If called in the global module, this documents a global rule. # local rule set-rule-doc ( name # The name of the rule. module-name ? # The name of the module to document. : docs * # The documentation for the rule. ) { module-name ?= * ; $(module-name).$(name).brief = [ brief-comment $(docs) ] ; $(module-name).$(name).docs = $(docs) ; if ! $(name) in $($(module-name).rules) { $(module-name).rules += $(name) ; } } # Set the argument call signature of a rule. # local rule set-rule-arguments-signature ( name # The name of the rule. module-name ? # The name of the module to document. : signature * # The arguments signature. ) { module-name ?= * ; $(module-name).$(name).signature = $(signature) ; } # Specifies the documentation for an argument of a rule. # local rule set-argument-doc ( name # The name of the argument. qualifier # Argument syntax qualifier, "*", "+", etc. rule-name # The name of the rule. module-name ? # THe optional name of the module. : docs * # The documentation. ) { module-name ?= * ; $(module-name).$(rule-name).$(name).qualifier = $(qualifier) ; $(module-name).$(rule-name).$(name).docs = $(docs) ; if ! $(name) in $($(module-name).$(rule-name).args) { $(module-name).$(rule-name).args += $(name) ; } } # Specifies the documentation for a variable in the current module. # If called in the global module, the global variable is documented. # local rule set-variable-doc ( name # The name of the variable. default # The default initial value. module-name ? # The name of the module to document. : docs * # The documentation for the variable. ) { module-name ?= * ; $(module-name).$(name).brief = [ brief-comment $(docs) ] ; $(module-name).$(name).default = $(default) ; $(module-name).$(name).docs = $(docs) ; if ! $(name) in $($(module-name).variables) { $(module-name).variables += $(name) ; } } # Generates a general description of the documentation and help system. # local rule print-help-top ( ) { print.section "Available Help" These are the available options for obtaining documentation. Some options have additional instructions on how to get more detailed information. ; print.list-start ; print.list-item --help; This help message. ; print.list-item --help-all; Brief information on available modules. ; print.list-end ; } # Generate brief documentation for all the known items in the section # for a module. Possible sections are: "rules", and "variables". # local rule print-help-module-section ( module # The module name. section # rules or variables. : section-head # The title of the section. section-description * # The detailed description of the section. ) { if $($(module).$(section)) { print.section $(section-head) $(section-description) ; print.list-start ; for local item in $($(module).$(section)) { print.list-item '$(item)'; $($(module).$(item).brief) ; } print.list-end ; } } # Generate documentation for possible modules. We attempt to list all known # modules, and a brief description of each. # local rule print-help-all ( ignored # Usually the module name, but is ignored here. ) { print.section "Modules" "These are all the known modules. Use --help- to get more" "detailed information." ; if $(documented-modules) { print.list-start ; for local module-name in $(documented-modules) { # The brief docs for each module. print.list-item "'"$(module-name)"';" $($(module-name).brief) ; } print.list-end ; } } # Generate documentation for a module. Basic information about # the module is generated. # local rule print-help-module ( module-name # The module to generate docs for. ) { # Print the docs. print.section '$(module-name)' $($(module-name).docs) ; # Print out the documented rules. print-help-module-section $(module-name) rules : "Rules" Use --help-$(module-name). to get more information. ; # Print out the documented variables. print-help-module-section $(module-name) variables : "Variables" Use --help-$(module-name). to get more information. ; } # Extract the next document item from the given variable. The variable # is changed to no longer contain that next item which is returned as # the result. The next item is considered anything up to, but not # including, the next gristed element. # local rule extract-next-doc-item ( var # The name of the variable to extract from. ) { local doc-item = $($(var)[1]) ; $(var) = $($(var)[2-]) ; while $($(var)) && ! $($(var)[1]:G) { doc-item += $($(var)[1]) ; $(var) = $($(var)[2-]) ; } return $(doc-item) ; } # Generate documentation for a set of rules in a module. # local rule print-help-rules ( module-name # Module of the rules. : name * # Optional list of rules to describe. ) { name ?= $($(module-name).rules) ; if [ set.intersection $(name) : $($(module-name).rules) ] { # Print out the given rules. for local rule-name in $(name) { print.section "'"$(module-name).$(rule-name)"'" $($(module-name).$(rule-name).docs) Call with "(" $($(module-name).$(rule-name).signature) ")" ; if $($(module-name).$(rule-name).args) { print.list-start ; for local arg-name in $($(module-name).$(rule-name).args) { print.list-item $(arg-name)";" $($(module-name).$(rule-name).$(arg-name).docs) ; } print.list-end ; } } } } # Generate documentation for a set of variables in a module. # local rule print-help-variables ( module-name ? # Module of the variables. : name * # Optional list of variables to describe. ) { name ?= $($(module-name).variables) ; if [ set.intersection $(name) : $($(module-name).variables) ] { # Print out the given variables. for local variable-name in $(name) { print.section "'"$(module-name).$(variable-name)"'" $($(module-name).$(variable-name).docs) ; if $($(module-name).$(variable-name).default) { print.list-start ; print.list-item "default value;" $($(module-name).$(variable-name).default) ; print.list-end ; } } } } local rule __test__ { } ws = " " ; # Extract the text from a block of comments. # local rule extract-comment ( var # The name of the variable to extract from. ) { local comment = ; local line = $($(var)[1]) ; while [ MATCH "^[$(ws)]*(#)" : $(line) ] && $($(var)) { comment += [ MATCH "^[$(ws)]*#[$(ws)]*(.*)$" : $(line) ] ; $(var) = $($(var)[2-]) ; line = $($(var)[1]) ; } return $(comment) ; } # Extract s single line of Jam syntax, ignoring any comments. # local rule extract-syntax ( var # The name of the variable to extract from. ) { local syntax = ; local line = $($(var)[1]) ; while ! $(syntax) && ! [ MATCH "^[$(ws)]*(#)" : $(line) ] && $($(var)) { local m = [ MATCH "^[$(ws)]*(.*)$" : $(line) ] ; if $(m) && ! $(m) = "" { syntax = $(m) ; } $(var) = $($(var)[2-]) ; line = $($(var)[1]) ; } return $(syntax) ; } # Extract the next token, this is either a single Jam construct # or a comment as a single token. # local rule extract-token ( var # The name of the variable to extract from. ) { local parts = ; while ! $(parts) { parts = [ MATCH "^[$(ws)]*([^$(ws)]+)[$(ws)]*(.*)" : $($(var)[1]) ] ; if ! $(parts) { $(var) = $($(var)[2-]) ; } } local token = ; if [ MATCH "^(#)" : $(parts[1]) ] { token = $(parts:J=" ") ; $(var) = $($(var)[2-]) ; } else { token = $(parts[1]) ; $(var) = $(parts[2-]:J=" ") $($(var)[2-]) ; } return $(token) ; } # Scan for a rule declaration as the next item in the variable. # local rule scan-rule ( syntax ? # The first part of the text which contains the rule declaration. : var # The name of the variable to extract from. ) { local rule-parts = [ MATCH "^[$(ws)]*(rule|local[$(ws)]*rule)[$(ws)]+([^$(ws)]+)[$(ws)]*(.*)" : $(syntax:J=" ") ] ; if $(rule-parts[1]) { # mark as doc for rule. local rule-name = $(rule-parts[2]) ; if $(scope-name) { rule-name = $(scope-name).$(rule-name) ; } local is-local = [ MATCH "^(local).*" : $(rule-parts[1]) ] ; if $(comment-block) { set-rule-doc $(rule-name) $(module-name) : $(comment-block) ; } # parse args of rule. $(var) = $(rule-parts[3-]) $($(var)) ; set-rule-arguments-signature $(rule-name) $(module-name) : [ scan-rule-arguments $(var) ] ; # scan within this rules scope. local scope-level = [ extract-token $(var) ] ; local scope-name = $(rule-name) ; while $(scope-level) { local comment-block = [ extract-comment $(var) ] ; local syntax-block = [ extract-syntax $(var) ] ; if [ scan-rule $(syntax-block) : $(var) ] { } else if [ MATCH "^(\\{)" : $(syntax-block) ] { scope-level += "{" ; } else if [ MATCH "^[^\\}]*([\\}])$" : $(syntax-block) ] { scope-level = $(scope-level[2-]) ; } #> else if $(syntax-block) #> { #> $(var) = $(syntax-block) $($(var)) ; #> local token = [ extract-token $(var) ] ; #> if $(token) = "{" #> { #> scope-level += "{" ; #> } #> else if $(token) = "}" #> { #> scope-level = $(scope-level[2-]) ; #> } #> } } return true ; } } # Scan the arguments of a rule. # local rule scan-rule-arguments ( var # The name of the variable to extract from. ) { local arg-syntax = ; local token = [ extract-token $(var) ] ; while $(token) != "(" && $(token) != "{" { token = [ extract-token $(var) ] ; } if $(token) != "{" { token = [ extract-token $(var) ] ; } local arg-signature = ; while $(token) != ")" && $(token) != "{" { local arg-name = ; local arg-qualifier = " " ; local arg-doc = ; if $(token) = ":" { arg-signature += $(token) ; token = [ extract-token $(var) ] ; } arg-name = $(token) ; arg-signature += $(token) ; token = [ extract-token $(var) ] ; if [ MATCH "^([\\*\\+\\?])" : $(token) ] { arg-qualifier = $(token) ; arg-signature += $(token) ; token = [ extract-token $(var) ] ; } if $(token) = ":" { arg-signature += $(token) ; token = [ extract-token $(var) ] ; } if [ MATCH "^(#)" : $(token) ] { $(var) = $(token) $($(var)) ; arg-doc = [ extract-comment $(var) ] ; token = [ extract-token $(var) ] ; } set-argument-doc $(arg-name) $(arg-qualifier) $(rule-name) $(module-name) : $(arg-doc) ; } while $(token) != "{" { token = [ extract-token $(var) ] ; } $(var) = "{" $($(var)) ; return $(arg-signature) ; } # Scan for a variable declaration. local rule scan-variable ( syntax ? # The first part of the text which contains the variable declaration. : var # The name of the variable to extract from. ) { # [1] = name, [2] = value(s) local var-parts = [ MATCH "^[$(ws)]*([^$(ws)]+)[$(ws)]+\\=[$(ws)]+([^\\;]*)\\;" : $(syntax) ] ; if $(var-parts) { set-variable-doc $(var-parts[1]) "$(var-parts[2-])" $(module-name) : $(comment-block) ; return true ; } } # Scan a module file for documentation comments. This also # invokes any actions assigned to the module. The actions # are the rules that do the actual output of the documentation. # This rue is invoked as the header scan rule for the module file. # rule scan-module ( target # The module file. : text * # The text in the file, one item per line. ) { #> ECHO -!- $(target) ; local module-name = $(.module<$(target)>.name) ; local module-documented = ; local comment-block = ; local syntax-block = ; # This is a hack because we can't get the line of a file if it # happens to not have a new-line termination. text += "}" ; while $(text) { comment-block = [ extract-comment text ] ; #> ECHO "##" '$(comment-block)' ; syntax-block = [ extract-syntax text ] ; #> ECHO "<>" '$(syntax-block)' ; if [ scan-rule $(syntax-block) : text ] { } else if [ scan-variable $(syntax-block) : text ] { } else if [ MATCH .*([cC]opyright).* : $(comment-block:J=" ") ] { # mark as the copy for the module. set-module-copyright $(module-name) : $(comment-block) ; } else if ! $(module-documented) { # document the module. set-module-doc $(module-name) : $(comment-block) ; module-documented = true ; } } #> print.output $(module-name).txt ; for local action in $(.module<$(target)>.actions) { local action-rule = [ $(action).front ] ; $(action).pop-front ; local action-args = [ $(action).get ] ; local ignored = [ $(action-rule) $(module-name) : $(action-args) ] ; } } # Add a scan action to perform to generate the help documentation. # The action rule is passed the name of the module as the first argument. # The second argument(s) are optional and passed directly as specified # here. # local rule do-scan ( modules + # The modules to scan and perform the action on. : action * # The action rule, plus the secondary arguments to pass to the action rule. ) { local targets = ; for local module-file in $(modules) { local module-name = $(module-file:B) ; .module<$(module-file)>.name = $(module-name) ; if $(action) { .module<$(module-file)>.actions += [ class.new vector $(action) ] ; } HDRSCAN on $(module-file) = "^(.*).$" ; HDRRULE on $(module-file) = doc.scan-module ; NOTFILE $(module-name).scan ; ALWAYS $(module-name).scan ; INCLUDES $(module-name).scan : $(module-file) ; targets += $(module-name).scan ; #> ALWAYS $(module-name).txt ; #> DEPENDS $(module-name).txt : $(module-name).scan ; #> targets += $(module-name).txt ; } if $(help-output) = console { DEPENDS all : $(targets) ; } if $(help-output) = text { print.output help.txt ; ALWAYS help.txt ; DEPENDS help.txt : $(targets) ; DEPENDS all : help.txt ; } }