• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2002, 2005 Dave Abrahams
2# Copyright 2002, 2003, 2006 Rene Rivera
3# Copyright 2003 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# Documentation system, handles --help requests.
8# It defines rules that attach documentation to modules, rules, and variables.
9# Collects and generates documentation for the various parts of the build
10# system. The documentation is collected from comments integrated into the code.
11
12import modules ;
13import print ;
14import set ;
15import container ;
16import "class" ;
17import sequence ;
18import path ;
19
20
21# The type of output to generate.
22# "console" is formatted text echoed to the console (the default);
23# "text" is formatted text appended to the output file;
24# "html" is HTML output to the file.
25#
26help-output = console ;
27
28
29# The file to output documentation to when generating "text" or "html" help.
30# This is without extension as the extension is determined by the type of
31# output.
32#
33help-output-file = help ;
34
35# Whether to include local rules in help output.
36#
37.option.show-locals ?= ;
38
39# When showing documentation for a module, whether to also generate
40# automatically the detailed docs for each item in the module.
41#
42.option.detailed ?= ;
43
44# Generate debug output as the help is generated and modules are parsed.
45#
46.option.debug ?= ;
47
48# These are all the options available for enabling or disabling to control the
49# help system in various ways. Options can be enabled or disabled with
50# '--help-enable-<option>', and '--help-disable-<option>' respectively.
51#
52.option-description = Help Options ;
53
54# Enable or disable a documentation option.
55#
56local rule set-option (
57    option  # The option name.
58    : value ?  # Enabled (non-empty), or disabled (empty)
59)
60{
61    .option.$(option) = $(value) ;
62}
63
64
65# Set the type of output.
66#
67local rule set-output ( type )
68{
69    help-output = $(type) ;
70}
71
72
73# Set the output to a file.
74#
75local rule set-output-file ( file )
76{
77    help-output-file = $(file) ;
78}
79
80
81# Extracts the brief comment from a complete comment. The brief comment is the
82# first sentence.
83#
84local rule brief-comment (
85    docs *  # The comment documentation.
86)
87{
88    local d = $(docs:J=" ") ;
89    local p = [ MATCH ".*([.])$" : $(d) ] ;
90    if ! $(p) { d = $(d)"." ; }
91    d = $(d)" " ;
92    local m = [ MATCH "^([^.]+[.])(.*)" : $(d) ] ;
93    local brief = $(m[1]) ;
94    while $(m[2]) && [ MATCH "^([^ ])" : $(m[2]) ]
95    {
96        m = [ MATCH "^([^.]+[.])(.*)" : $(m[2]) ] ;
97        brief += $(m[1]) ;
98    }
99    return $(brief:J="") ;
100}
101
102
103# Specifies the documentation for the current module.
104#
105local rule set-module-doc (
106    module-name ?  # The name of the module to document.
107    : docs *  # The documentation for the module.
108)
109{
110    module-name ?= * ;
111
112    $(module-name).brief = [ brief-comment $(docs) ] ;
113    $(module-name).docs = $(docs) ;
114
115    if ! $(module-name) in $(documented-modules)
116    {
117        documented-modules += $(module-name) ;
118    }
119}
120
121
122# Specifies the documentation for the current module.
123#
124local rule set-module-copyright (
125    module-name ?  # The name of the module to document.
126    : copyright *  # The copyright for the module.
127)
128{
129    module-name ?= * ;
130
131    $(module-name).copy-brief = [ brief-comment $(copyright) ] ;
132    $(module-name).copy-docs = $(docs) ;
133
134    if ! $(module-name) in $(documented-modules)
135    {
136        documented-modules += $(module-name) ;
137    }
138}
139
140
141# Specifies the documentation for a rule in the current module. If called in the
142# global module, this documents a global rule.
143#
144local rule set-rule-doc (
145    name  # The name of the rule.
146    module-name ?  # The name of the module to document.
147    is-local ?  # Whether the rule is local to the module.
148    : docs *  # The documentation for the rule.
149)
150{
151    module-name ?= * ;
152
153    $(module-name).$(name).brief = [ brief-comment $(docs) ] ;
154    $(module-name).$(name).docs = $(docs) ;
155    $(module-name).$(name).is-local = $(is-local) ;
156
157    if ! $(name) in $($(module-name).rules)
158    {
159        $(module-name).rules += $(name) ;
160    }
161}
162
163
164# Specify a class, will turn a rule into a class.
165#
166local rule set-class-doc (
167    name  # The name of the class.
168    module-name ?  # The name of the module to document.
169    : super-name ?  # The super class name.
170)
171{
172    module-name ?= * ;
173
174    $(module-name).$(name).is-class = true ;
175    if $(super-name)
176    {
177        $(module-name).$(name).super-name = $(super-name) ;
178    }
179    $(module-name).$(name).class-rules =
180        [ MATCH "^($(name)[.].*)" : $($(module-name).rules) ] ;
181    $(module-name).$($(module-name).$(name).class-rules).is-class-rule = true ;
182
183    $(module-name).classes += $(name) ;
184    $(module-name).class-rules += $($(module-name).$(name).class-rules) ;
185    $(module-name).rules =
186        [ set.difference $($(module-name).rules) :
187            $(name) $($(module-name).$(name).class-rules) ] ;
188}
189
190
191# Set the argument call signature of a rule.
192#
193local rule set-rule-arguments-signature (
194    name  # The name of the rule.
195    module-name ?  # The name of the module to document.
196    : signature *  # The arguments signature.
197)
198{
199    module-name ?= * ;
200
201    $(module-name).$(name).signature = $(signature) ;
202}
203
204
205# Specifies the documentation for an argument of a rule.
206#
207local rule set-argument-doc (
208    name  # The name of the argument.
209    qualifier  # Argument syntax qualifier, "*", "+", etc.
210    rule-name  # The name of the rule.
211    module-name ?  # THe optional name of the module.
212    : docs *  # The documentation.
213)
214{
215    module-name ?= * ;
216
217    $(module-name).$(rule-name).args.$(name).qualifier = $(qualifier) ;
218    $(module-name).$(rule-name).args.$(name).docs = $(docs) ;
219
220    if ! $(name) in $($(module-name).$(rule-name).args)
221    {
222        $(module-name).$(rule-name).args += $(name) ;
223    }
224}
225
226
227# Specifies the documentation for a variable in the current module. If called in
228# the global module, the global variable is documented.
229#
230local rule set-variable-doc (
231    name  # The name of the variable.
232    default  # The default value.
233    initial  # The initial value.
234    module-name ?  # The name of the module to document.
235    : docs *  # The documentation for the variable.
236)
237{
238    module-name ?= * ;
239
240    $(module-name).$(name).brief = [ brief-comment $(docs) ] ;
241    $(module-name).$(name).default = $(default) ;
242    $(module-name).$(name).initial = $(initial) ;
243    $(module-name).$(name).docs = $(docs) ;
244
245    if ! $(name) in $($(module-name).variables)
246    {
247        $(module-name).variables += $(name) ;
248    }
249}
250
251
252# Generates a general description of the documentation and help system.
253#
254local rule print-help-top ( )
255{
256    print.section "General command line usage" ;
257
258    print.text "    b2 [options] [properties] [targets]
259
260  Options, properties and targets can be specified in any order.
261      " ;
262
263    print.section "Important Options" ;
264
265    print.list-start ;
266    print.list-item "--clean Remove targets instead of building" ;
267    print.list-item "-a Rebuild everything" ;
268    print.list-item "-n Don't execute the commands, only print them" ;
269    print.list-item "-d+2 Show commands as they are executed" ;
270    print.list-item "-d0 Suppress all informational messages" ;
271    print.list-item "-q Stop at first error" ;
272    print.list-item "--reconfigure Rerun all configuration checks" ;
273    print.list-item "--debug-configuration Diagnose configuration" ;
274    print.list-item "--debug-building Report which targets are built with what properties" ;
275    print.list-item "--debug-generator Diagnose generator search/execution" ;
276    print.list-end ;
277
278    print.section "Further Help"
279        The following options can be used to obtain additional documentation.
280      ;
281
282    print.list-start ;
283    print.list-item "--help-options Print more obscure command line options." ;
284    print.list-item "--help-internal B2 implementation details." ;
285    print.list-item "--help-doc-options Implementation details doc formatting." ;
286    print.list-end ;
287}
288
289
290# Generate Jam/Boost.Jam command usage information.
291#
292local rule print-help-usage ( )
293{
294    print.section "B2 Usage"
295        "b2 [ options... ] targets..."
296        ;
297    print.list-start ;
298    print.list-item -a\;
299        Build all targets, even if they are current. ;
300    print.list-item -fx\;
301        Read '"x"' as the Jamfile for building instead of searching for the
302        B2 system. ;
303    print.list-item -jx\;
304        Run up to '"x"' commands concurrently. ;
305    print.list-item -n\;
306        Do not execute build commands. Instead print out the commands as they
307        would be executed if building. ;
308    print.list-item -ox\;
309        Output the used build commands to file '"x"'. ;
310    print.list-item -q\;
311        Quit as soon as a build failure is encountered. Without this option
312        Boost.Jam will continue building as many targets as it can. ;
313    print.list-item -sx=y\;
314        Sets a Jam variable '"x"' to the value '"y"', overriding any value that
315        variable would have from the environment. ;
316    print.list-item -tx\;
317        Rebuild the target '"x"', even if it is up-to-date. ;
318    print.list-item -v\;
319        Display the version of b2. ;
320    print.list-item --x\;
321        Any option not explicitly handled by B2 remains available to
322        build scripts using the '"ARGV"' variable. ;
323    print.list-item --abbreviate-paths\;
324        Use abbreviated paths for targets. ;
325    print.list-item --hash\;
326        Shorten target paths by using an MD5 hash. ;
327    print.list-item -dconsole\;
328        Run the interactive debugger.  Cannot be used with any other option. ;
329    print.list-item -dn\;
330        Enables output of diagnostic messages. The debug level '"n"' and all
331        below it are enabled by this option. ;
332    print.list-item -d+n\;
333        Enables output of diagnostic messages. Only the output for debug level
334        '"n"' is enabled. ;
335    print.list-end ;
336    print.section "Debug Levels"
337        Each debug level shows a different set of information. Usually with
338        higher levels producing more verbose information. The following levels
339        are supported\: ;
340    print.list-start ;
341    print.list-item 0\;
342        Turn off all diagnostic output. Only errors are reported. ;
343    print.list-item 1\;
344        Show the actions taken for building targets, as they are executed. ;
345    print.list-item 2\;
346        Show "quiet" actions and display all action text, as they are executed. ;
347    print.list-item 3\;
348        Show dependency analysis, and target/source timestamps/paths. ;
349    print.list-item 4\;
350        Show arguments of shell invocations. ;
351    print.list-item 5\;
352        Show rule invocations and variable expansions. ;
353    print.list-item 6\;
354        Show directory/header file/archive scans, and attempts at binding to targets. ;
355    print.list-item 7\;
356        Show variable settings. ;
357    print.list-item 8\;
358        Show variable fetches, variable expansions, and evaluation of '"if"' expressions. ;
359    print.list-item 9\;
360        Show variable manipulation, scanner tokens, and memory usage. ;
361    print.list-item 10\;
362        Show execution times for rules. ;
363    print.list-item 11\;
364        Show parsing progress of Jamfiles. ;
365    print.list-item 12\;
366        Show graph for target dependencies. ;
367    print.list-item 13\;
368        Show changes in target status (fate). ;
369    print.list-end ;
370}
371
372# Generates description of options controlling the help system. This
373# automatically reads the options as all variables in the module given
374# with the name `module-name` of the form  ".option.*".
375#
376local rule print-help-options (
377    module-name
378)
379{
380    local options-to-list = [ MATCH "^[.]option[.](.*)" : $($(module-name).variables) ] ;
381    if $(options-to-list)
382    {
383        local option-title = $($(module-name)..option-description.initial) ;
384        if ! $(option-title) || $(option-title) = "(empty)"
385        {
386            option-title = "$(module-name) Options" ;
387        }
388        local option-description = $(option-title)
389                                   $($(module-name)..option-description.docs) ;
390        print.section $(option-description) ;
391        print.list-start ;
392        for local option in [ sequence.insertion-sort $(options-to-list) ]
393        {
394            local def = disabled ;
395            if $($(module-name)..option.$(option).default) != "(empty)"
396            {
397                def = $($(module-name)..option.$(option).default) ;
398            }
399            print.list-item $(option)\: $($(module-name)..option.$(option).docs)
400                Default is $(def). ;
401        }
402        print.list-end ;
403    }
404}
405
406
407# Generate brief documentation for all the known items in the section for a
408# module. Possible sections are: "rules", and "variables".
409#
410local rule print-help-module-section (
411    module  # The module name.
412    section  # rules or variables.
413    : section-head  # The title of the section.
414    section-description *  # The detailed description of the section.
415)
416{
417    if $($(module).$(section))
418    {
419        print.section $(section-head) $(section-description) ;
420        print.list-start ;
421        for local item in [ sequence.insertion-sort $($(module).$(section)) ]
422        {
423            local show = ;
424            if ! $($(module).$(item).is-local)
425            {
426                show = yes ;
427            }
428            if $(.option.show-locals)
429            {
430                show = yes ;
431            }
432            if $(show)
433            {
434                print.list-item $(item)\: $($(module).$(item).brief) ;
435            }
436        }
437        print.list-end ;
438    }
439}
440
441
442# Generate documentation for all possible modules. We attempt to list all known
443# modules together with a brief description of each.
444#
445local rule print-help-all (
446    ignored  # Usually the module name, but is ignored here.
447)
448{
449    print.section "Modules"
450        "These are all the known modules. Use --help <module> to get more"
451        "detailed information."
452        ;
453    if $(documented-modules)
454    {
455        print.list-start ;
456        for local module-name in [ sequence.insertion-sort $(documented-modules) ]
457        {
458            # The brief docs for each module.
459            print.list-item $(module-name)\: $($(module-name).brief) ;
460        }
461        print.list-end ;
462    }
463    # The documentation for each module when details are requested.
464    if $(documented-modules) && $(.option.detailed)
465    {
466        for local module-name in [ sequence.insertion-sort $(documented-modules) ]
467        {
468            # The brief docs for each module.
469            print-help-module $(module-name) ;
470        }
471    }
472}
473
474
475# Generate documentation for a module. Basic information about the module is
476# generated.
477#
478local rule print-help-module (
479    module-name  # The module to generate docs for.
480)
481{
482    # Print the docs.
483    print.section "Module '$(module-name)'" $($(module-name).docs) ;
484
485    # Print out the documented classes.
486    print-help-module-section $(module-name) classes : "Module '$(module-name)' classes"
487        Use --help $(module-name).<class-name> to get more information. ;
488
489    # Print out the documented rules.
490    print-help-module-section $(module-name) rules : "Module '$(module-name)' rules"
491        Use --help $(module-name).<rule-name> to get more information. ;
492
493    # Print out the documented variables.
494    print-help-module-section $(module-name) variables : "Module '$(module-name)' variables"
495        Use --help $(module-name).<variable-name> to get more information. ;
496
497    # Print out all the same information but indetailed form.
498    if $(.option.detailed)
499    {
500        print-help-classes $(module-name) ;
501        print-help-rules $(module-name) ;
502        print-help-variables $(module-name) ;
503    }
504}
505
506
507# Generate documentation for a set of rules in a module.
508#
509local rule print-help-rules (
510    module-name  # Module of the rules.
511    : name *  # Optional list of rules to describe.
512)
513{
514    name ?= $($(module-name).rules) ;
515    if [ set.intersection $(name) : $($(module-name).rules) $($(module-name).class-rules) ]
516    {
517        # Print out the given rules.
518        for local rule-name in [ sequence.insertion-sort $(name) ]
519        {
520            if $(.option.show-locals) || ! $($(module-name).$(rule-name).is-local)
521            {
522                local signature = $($(module-name).$(rule-name).signature:J=" ") ;
523                signature ?= "" ;
524                print.section "Rule '$(module-name).$(rule-name) ( $(signature) )'"
525                    $($(module-name).$(rule-name).docs) ;
526                if $($(module-name).$(rule-name).args) &&
527                    $($(module-name).$(rule-name).args.$($(module-name).$(rule-name).args).docs)
528                {
529                    print.list-start ;
530                    for local arg-name in $($(module-name).$(rule-name).args)
531                    {
532                        if $($(module-name).$(rule-name).args.$(arg-name).docs)
533                        {
534                            print.list-item $(arg-name)\: $($(module-name).$(rule-name).args.$(arg-name).docs) ;
535                        }
536                    }
537                    print.list-end ;
538                }
539            }
540        }
541    }
542}
543
544
545# Generate documentation for a set of classes in a module.
546#
547local rule print-help-classes (
548    module-name  # Module of the classes.
549    : name *  # Optional list of classes to describe.
550)
551{
552    name ?= $($(module-name).classes) ;
553    if [ set.intersection $(name) : $($(module-name).classes) ]
554    {
555        # Print out the given classes.
556        for local class-name in [ sequence.insertion-sort $(name) ]
557        {
558            if $(.option.show-locals) || ! $($(module-name).$(class-name).is-local)
559            {
560                local signature = $($(module-name).$(class-name).signature:J=" ") ;
561                signature ?= "" ;
562                print.section "Class '$(module-name).$(class-name) ( $(signature) )'"
563                    $($(module-name).$(class-name).docs)
564                   "Inherits from '"$($(module-name).$(class-name).super-name)"'." ;
565                if $($(module-name).$(class-name).args)
566                {
567                    print.list-start ;
568                    for local arg-name in $($(module-name).$(class-name).args)
569                    {
570                        print.list-item $(arg-name)\: $($(module-name).$(class-name).args.$(arg-name).docs) ;
571                    }
572                    print.list-end ;
573                }
574            }
575
576            # Print out the documented rules of the class.
577            print-help-module-section $(module-name) $(class-name).class-rules : "Class '$(module-name).$(class-name)' rules"
578                Use --help $(module-name).<rule-name> to get more information. ;
579
580            # Print out all the rules if details are requested.
581            if $(.option.detailed)
582            {
583                print-help-rules $(module-name) : $($(module-name).$(class-name).class-rules) ;
584            }
585        }
586    }
587}
588
589
590# Generate documentation for a set of variables in a module.
591#
592local rule print-help-variables (
593    module-name ?  # Module of the variables.
594    : name *  # Optional list of variables to describe.
595)
596{
597    name ?= $($(module-name).variables) ;
598    if [ set.intersection $(name) : $($(module-name).variables) ]
599    {
600        # Print out the given variables.
601        for local variable-name in [ sequence.insertion-sort $(name) ]
602        {
603            print.section "Variable '$(module-name).$(variable-name)'" $($(module-name).$(variable-name).docs) ;
604            if $($(module-name).$(variable-name).default) ||
605                $($(module-name).$(variable-name).initial)
606            {
607                print.list-start ;
608                if $($(module-name).$(variable-name).default)
609                {
610                    print.list-item "default value:" '$($(module-name).$(variable-name).default:J=" ")' ;
611                }
612                if $($(module-name).$(variable-name).initial)
613                {
614                    print.list-item "initial value:" '$($(module-name).$(variable-name).initial:J=" ")' ;
615                }
616                print.list-end ;
617            }
618        }
619    }
620}
621
622
623# Generate documentation for a project.
624#
625local rule print-help-project (
626    unused ?
627    : jamfile *  # The project Jamfile.
628)
629{
630    if $(jamfile<$(jamfile)>.docs)
631    {
632        # Print the docs.
633        print.section "Project-specific help"
634            Project has jamfile at $(jamfile) ;
635
636        print.lines $(jamfile<$(jamfile)>.docs) "" ;
637    }
638}
639
640
641# Generate documentation for a config file.
642#
643local rule print-help-config (
644    unused ?
645    : type  # The type of configuration file user or site.
646    config-file  # The configuration Jamfile.
647)
648{
649    if $(jamfile<$(config-file)>.docs)
650    {
651        # Print the docs.
652        print.section "Configuration help"
653            Configuration file at $(config-file) ;
654
655        print.lines $(jamfile<$(config-file)>.docs) "" ;
656    }
657}
658
659
660ws = "\t " ;
661
662# Extract the text from a single comment
663#
664local rule extract-one-comment (
665    var  # The name of the variable to extract from
666  : start # The initial part after the leading '#'
667)
668{
669    local m = [ MATCH ^(\\|)(.*) : $(start) ] ;
670    if $(m)
671    {
672        start = $(m[2]) ;
673        local comment ;
674        while true
675        {
676            local end = [ MATCH "(.*)(\\|#)(.*)" : $(start) ] ;
677            if $(end)
678            {
679                comment += $(end[1]) ;
680                $(var) = $(end[3]) $($(var)[2-]) ;
681                return $(comment) ;
682            }
683            else
684            {
685                comment += $(start) ;
686                $(var) = $($(var)[2-]) ;
687            }
688            start = $($(var)[1]) ;
689        }
690    }
691    else
692    {
693        $(var) = $($(var)[2-]) ;
694        if $(start) { return [ MATCH "^[$(ws)]?(.*)$" : $(start) ] ; }
695        else { return "" ; }
696    }
697}
698
699# Extract the text from a block of comments.
700#
701local rule extract-comment (
702    var  # The name of the variable to extract from.
703)
704{
705    local comment = ;
706    local line = $($(var)[1]) ;
707    local l = [ MATCH "^[$(ws)]*(#)(.*)$" : $(line) ] ;
708    while $(l[1]) && $($(var))
709    {
710        comment += [ extract-one-comment $(var) : $(l[2]) ] ;
711        line = $($(var)[1]) ;
712        l = [ MATCH "^[$(ws)]*(#)(.*)$" : $(line) ] ;
713    }
714    return $(comment) ;
715}
716
717
718# Extract s single line of Jam syntax, ignoring any comments.
719#
720local rule extract-syntax (
721    var  # The name of the variable to extract from.
722)
723{
724    local syntax = ;
725    local line = $($(var)[1]) ;
726    while ! $(syntax) && ! [ MATCH "^[$(ws)]*(#)" : $(line) ] && $($(var))
727    {
728        local m = [ MATCH "^[$(ws)]*(.*)$" : $(line) ] ;
729        if $(m)
730        {
731            syntax = $(m) ;
732        }
733        $(var) = $($(var)[2-]) ;
734        line = $($(var)[1]) ;
735    }
736    return $(syntax) ;
737}
738
739
740# Extract the next token, this is either a single Jam construct or a comment as
741# a single token.
742#
743local rule extract-token (
744    var  # The name of the variable to extract from.
745)
746{
747    local parts = ;
748    while ! $(parts)
749    {
750        parts = [ MATCH "^[$(ws)]*([^$(ws)]+)[$(ws)]*(.*)" : $($(var)[1]) ] ;
751        if ! $(parts)
752        {
753            $(var) = $($(var)[2-]) ;
754        }
755    }
756    local token = ;
757    if [ MATCH "^(#)" : $(parts[1]) ]
758    {
759        token = $(parts:J=" ") ;
760        $(var) = $($(var)[2-]) ;
761    }
762    else
763    {
764        token = $(parts[1]) ;
765        $(var) = $(parts[2-]:J=" ") $($(var)[2-]) ;
766    }
767    return $(token) ;
768}
769
770
771# Scan for a rule declaration as the next item in the variable.
772#
773local rule scan-rule (
774    syntax ?  # The first part of the text which contains the rule declaration.
775    : var  # The name of the variable to extract from.
776)
777{
778    local rule-parts =
779        [ MATCH "^[$(ws)]*(rule|local[$(ws)]*rule)[$(ws)]+([^$(ws)]+)[$(ws)]*(.*)" : $(syntax:J=" ") ] ;
780    if $(rule-parts[1])
781    {
782        # Mark as doc for rule.
783        local rule-name = $(rule-parts[2]) ;
784        if $(scope-name)
785        {
786            rule-name = $(scope-name).$(rule-name) ;
787        }
788        local is-local = [ MATCH "^(local).*" : $(rule-parts[1]) ] ;
789        if $(comment-block)
790        {
791            set-rule-doc $(rule-name) $(module-name) $(is-local) : $(comment-block) ;
792        }
793        # Parse args of rule.
794        $(var) = $(rule-parts[3-]) $($(var)) ;
795        set-rule-arguments-signature $(rule-name) $(module-name) : [ scan-rule-arguments $(var) ] ;
796        # Scan within this rules scope.
797        local scope-level = [ extract-token $(var) ] ;
798        local scope-name = $(rule-name) ;
799        while $(scope-level) && $($(var))
800        {
801            local comment-block = [ extract-comment $(var) ] ;
802            local syntax-block = [ extract-syntax $(var) ] ;
803            if [ scan-rule $(syntax-block) : $(var) ]
804            {
805            }
806            else if [ MATCH "^(\\{)" : $(syntax-block) ]
807            {
808                scope-level += "{" ;
809            }
810            else if [ MATCH "^[^\\}]*([\\}])[$(ws)]*$"  : $(syntax-block) ]
811            {
812                scope-level = $(scope-level[2-]) ;
813            }
814        }
815
816        return true ;
817    }
818}
819
820
821# Scan the arguments of a rule.
822#
823local rule scan-rule-arguments (
824    var  # The name of the variable to extract from.
825)
826{
827    local arg-syntax = ;
828    local token = [ extract-token $(var) ] ;
829    while $(token) != "(" && $(token) != "{"
830    {
831        token = [ extract-token $(var) ] ;
832    }
833    if $(token) != "{"
834    {
835        token = [ extract-token $(var) ] ;
836    }
837    local arg-signature = ;
838    while $(token) != ")" && $(token) != "{"
839    {
840        local arg-name = ;
841        local arg-qualifier = " " ;
842        local arg-doc = ;
843        if $(token) = ":"
844        {
845            arg-signature += $(token) ;
846            token = [ extract-token $(var) ] ;
847        }
848        arg-name = $(token) ;
849        arg-signature += $(token) ;
850        token = [ extract-token $(var) ] ;
851        if [ MATCH "^([\\*\\+\\?])" : $(token) ]
852        {
853            arg-qualifier = $(token) ;
854            arg-signature += $(token) ;
855            token = [ extract-token $(var) ] ;
856        }
857        if $(token) = ":"
858        {
859            arg-signature += $(token) ;
860            token = [ extract-token $(var) ] ;
861        }
862        if [ MATCH "^(#)" : $(token) ]
863        {
864            $(var) = $(token) $($(var)) ;
865            arg-doc = [ extract-comment $(var) ] ;
866            token = [ extract-token $(var) ] ;
867        }
868        set-argument-doc $(arg-name) $(arg-qualifier) $(rule-name) $(module-name) : $(arg-doc) ;
869    }
870    while $(token) != "{"
871    {
872        token = [ extract-token $(var) ] ;
873    }
874    $(var) = "{" $($(var)) ;
875    arg-signature ?= "" ;
876    return $(arg-signature) ;
877}
878
879
880# Scan for a variable declaration.
881#
882local rule scan-variable (
883    syntax ?  # The first part of the text which contains the variable declaration.
884    : var  # The name of the variable to extract from.
885)
886{
887    # [1] = name, [2] = value(s)
888    local var-parts =
889        [ MATCH "^[$(ws)]*([^$(ws)]+)[$(ws)]+([\\?\\=]*)[$(ws)]+([^\\;]*)\\;" : $(syntax) ] ;
890    if $(var-parts)
891    {
892        local value = [ MATCH "^(.*)[ ]$" : $(var-parts[3-]:J=" ") ] ;
893        local default-value = "" ;
894        local initial-valie = "" ;
895        if $(var-parts[2]) = "?="
896        {
897            default-value = $(value) ;
898            default-value ?= "(empty)" ;
899        }
900        else
901        {
902            initial-value = $(value) ;
903            initial-value ?= "(empty)" ;
904        }
905        if $(comment-block)
906        {
907            set-variable-doc $(var-parts[1]) $(default-value) $(initial-value) $(module-name) : $(comment-block) ;
908        }
909        return true ;
910    }
911}
912
913
914# Scan a class declaration.
915#
916local rule scan-class (
917    syntax ?  # The syntax text for the class declaration.
918    : var  # The name of the variable to extract from.
919)
920{
921    # [1] = class?, [2] = name, [3] = superclass
922    local class-parts =
923        [ MATCH "^[$(ws)]*([^$(ws)]+)[$(ws)]+([^$(ws)]+)[$(ws)]*:*[$(ws)]*([^$(ws);]*)" : $(syntax) ] ;
924    if $(class-parts[1]) = "class" || $(class-parts[1]) = "class.class"
925    {
926        # Scan within this class scope.
927        local scope-level = [ extract-token $(var) ] ;
928        local scope-name = $(class-parts[2]) ;
929        while $(scope-level) && $($(var))
930        {
931            local comment-block = [ extract-comment $(var) ] ;
932            local syntax-block = [ extract-syntax $(var) ] ;
933            if [ scan-rule $(syntax-block) : $(var) ]
934            {
935            }
936            else if [ MATCH "^(\\{)" : $(syntax-block) ]
937            {
938                scope-level += "{" ;
939            }
940            else if [ MATCH "^[^\\}]*([\\}])[$(ws)]*$"  : $(syntax-block) ]
941            {
942                scope-level = $(scope-level[2-]) ;
943            }
944        }
945
946        # This has to come after parsing the rules, because
947        # it looks up the rules for the class from the global list.
948        set-class-doc $(class-parts[2]) $(module-name) : $(class-parts[3]) ;
949
950        return true ;
951    }
952}
953
954
955# Scan a module file for documentation comments. This also invokes any actions
956# assigned to the module. The actions are the rules that do the actual output of
957# the documentation. This rule is invoked as the header scan rule for the module
958# file.
959#
960rule scan-module (
961    target  # The module file.
962    : text *  # The text in the file, one item per line.
963    : action *  # Rule to call to output docs for the module.
964)
965{
966    if $(.option.debug) { ECHO "HELP:" scanning module target '$(target)' ; }
967    local module-name = $(target:B) ;
968    local module-documented = ;
969    local comment-block = ;
970    local syntax-block = ;
971    # This is a hack because we can not get the line of a file if it happens to
972    # not have a new-line termination.
973    text += "}" ;
974    while $(text)
975    {
976        comment-block = [ extract-comment text ] ;
977        syntax-block = [ extract-syntax text ] ;
978        if $(.option.debug)
979        {
980            ECHO "HELP:" comment block\; '$(comment-block)' ;
981            ECHO "HELP:" syntax block\; '$(syntax-block)' ;
982        }
983        if [ scan-rule $(syntax-block) : text ] { }
984        else if [ scan-variable $(syntax-block) : text ] { }
985        else if [ scan-class $(syntax-block) : text ] { }
986        else if [ MATCH ".*([cC]opyright).*" : $(comment-block:J=" ") ]
987        {
988            # mark as the copy for the module.
989            set-module-copyright $(module-name) : $(comment-block) ;
990        }
991        else if $(action[1]) in "print-help-project" "print-help-config"
992            && ! $(jamfile<$(target)>.docs)
993        {
994            # special module docs for the project jamfile.
995            jamfile<$(target)>.docs = $(comment-block) ;
996        }
997        else if ! $(module-documented)
998        {
999            # document the module.
1000            set-module-doc $(module-name) : $(comment-block) ;
1001            module-documented = true ;
1002        }
1003    }
1004    if $(action)
1005    {
1006        $(action[1]) $(module-name) : $(action[2-]) ;
1007    }
1008}
1009
1010
1011# Import scan-module to global scope, so that it is available during header
1012# scanning phase.
1013#
1014IMPORT $(__name__) : scan-module : : doc.scan-module ;
1015
1016
1017# Read in a file using the SHELL builtin and return the individual lines as
1018# would be done for header scanning.
1019#
1020local rule read-file (
1021    file  # The file to read in.
1022)
1023{
1024    file = [ path.native [ path.root [ path.make $(file) ] [ path.pwd ] ] ] ;
1025    if ! $(.file<$(file)>.lines)
1026    {
1027        local content ;
1028        switch [ modules.peek : OS ]
1029        {
1030            case NT :
1031            content = [ SHELL "TYPE \"$(file)\"" ] ;
1032
1033            case * :
1034            content = [ SHELL "cat \"$(file)\"" ] ;
1035        }
1036        local lines ;
1037        local << = "([^\r\n]*)[\r]?[\n](.*)" ;
1038        local line+ = [ MATCH "$(<<)" : "$(content)" ] ;
1039        while $(line+)
1040        {
1041            lines += $(line+[1]) ;
1042            line+ = [ MATCH "$(<<)" : "$(line+[2])" ] ;
1043        }
1044        .file<$(file)>.lines = $(lines) ;
1045    }
1046    return $(.file<$(file)>.lines) ;
1047}
1048
1049
1050# Add a scan action to perform to generate the help documentation. The action
1051# rule is passed the name of the module as the first argument. The second
1052# argument(s) are optional and passed directly as specified here.
1053#
1054local rule do-scan (
1055    modules +  # The modules to scan and perform the action on.
1056    : action *  # The action rule, plus the secondary arguments to pass to the action rule.
1057)
1058{
1059    if $(help-output) = text
1060    {
1061        print.output $(help-output-file).txt plain ;
1062        ALWAYS $(help-output-file).txt ;
1063        DEPENDS all : $(help-output-file).txt ;
1064    }
1065    if $(help-output) = html
1066    {
1067        print.output $(help-output-file).html html ;
1068        ALWAYS $(help-output-file).html ;
1069        DEPENDS all : $(help-output-file).html ;
1070    }
1071    for local module-file in $(modules[1--2])
1072    {
1073        scan-module $(module-file) : [ read-file $(module-file) ] ;
1074    }
1075    scan-module $(modules[-1]) : [ read-file $(modules[-1]) ] : $(action) ;
1076}
1077