# 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 ;

# List of possible modules, but which really aren't.
#
not-modules = boost-build test ;

# 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 module-name = ;
        local help = ;
        if $(arg) = --help
        {
            help = _any_ ;
        }
        else
        {
            if [ MATCH --help-[^\\.]*(\\.) : $(arg) ]
            {
                module-name = [ MATCH --help-([^\\.]*) : $(arg) ] ;
                help = [ MATCH --help-[^\\.]*\\.(.*) : $(arg) ] ;
            }
            else
            {
                help = [ MATCH --help-(.*) : $(arg) ] ;
            }
        }
        #> ECHO module-name={$(module-name)} - help={$(help)} ;
        if ! $(module-name)
        {
            # Any help.
            if $(help) && $(help) = _any_
            {
                print-help-any ;
                did-help = true ;
                help = ;
            }
            # Only modules?
            if $(help) && $(help) = "module"
            {
                print-help-modules ;
                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)
        {
            do-scan $(module-path[1]) : print-help-module ;
            did-help = true ;
            help = ;
        }
        # Unrecognized.
        if $(help)
        {
            EXIT "Unrecognized help option '"$(arg)"'." ;
        }
    }
    #> if $(did-help)
    #> {
        #> EXIT
            #> "For more information consult the documentation at"
            #> "'http://www.boost.org'." ;
    #> }
    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) ;
    }
}

# 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.
    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).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-any ( )
{
    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-modules; Brief information on available modules. ;
    print.list-end ;
}

local rule print-help-module-section (
    module
    section
    : section-head
    section-description *
    )
{
    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-modules ( )
{
    print.section "Modules"
        "These are all the known modules. Use --help-<module> to get more"
        "detailed information."
        ;
    local modules-to-list =
        [ set.difference
            [ GLOB [ modules.peek : BOOST_BUILD_PATH ] : *.jam ]
            : $(not-modules) ] ;
    if $(modules-to-list)
    {
        print.list-start ;
        for local module-name in $(modules-to-list:B)
        {
            # Make sure we have the docs for the module.
            modules.load $(module-name) ;
            
            # 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).<rule-name> to get more information. ;
    
    # Print out the documented variables.
    print-help-module-section $(module-name) variables : "Variables"
        Use --help-$(module-name).<variable-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)
        {
            local docs = $($(module).$(rule-name).docs) ;
            print.section "'"$(module).$(rule-name)"'"
                [ extract-next-doc-item docs ] ;
            if $(docs)
            {
                print.list-start ;
                while $(docs)
                {
                    local doc-item = [ extract-next-doc-item docs ] ;
                    if $(doc-item[1]:G) = <argument>
                    {
                        print.list-item $(doc-item[1]:G=)";" $(doc-item[2-]) ;
                    }
                }
                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)
        {
            local docs = $($(module).$(variable-name).docs) ;
            print.section "'"$(module).$(variable-name)"'"
                [ extract-next-doc-item docs ] ;
            if $(docs)
            {
                print.list-start ;
                while $(docs)
                {
                    local doc-item = [ extract-next-doc-item docs ] ;
                    if $(doc-item[1]:G) = <default-value>
                    {
                        print.list-item "default value;" $(doc-item[2]) ;
                    }
                }
                print.list-end ;
            }
        }
    }
}

local rule __test__
{
}

ws = "	 " ;

local rule extract-comment ( var )
{
    local comment = ;
    local line = $($(var)[1]) ;
    while [ MATCH "^[$(ws)]*(#)" : $(line) ] && $($(var))
    {
        comment += [ MATCH "^[$(ws)]*#[$(ws)]*(.*)$" : $(line) ] ;
        $(var) = $($(var)[2-]) ;
        line = $($(var)[1]) ;
    }
    return $(comment) ;
}

local rule extract-syntax ( var )
{
    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) ;
}

rule scan-module ( target : text * )
{
    #> ECHO -**- scan-module "(" $(1)... ")" ;
    local module-name = $(.module<$(target)>.name) ;
    #> ECHO "    " module ==> $(module-name) ;
    local module-documented = ;
    local comment-block = ;
    local syntax-block = ;
    while $(text)
    {
        comment-block = [ extract-comment text ] ;
        #> ECHO "##" '$(comment-block)' ;
        syntax-block = [ extract-syntax text ] ;
        #> ECHO "<>" '$(syntax-block)' ;
        local rule-parts =
            [ MATCH "^[$(ws)]*(rule|local[$(ws)]*rule)[$(ws)]+([^$(ws)]+)[$(ws)]*(.*)" : $(syntax-block:J=" ") ] ;
        if $(rule-parts[2])
        {
            # mark as doc for rule.
            local rule-name = $(rule-parts[2]) ;
            local is-local = [ MATCH "^(local).*" : $(rule-parts[1]) ] ;
            if $(comment-block)
            {
                set-rule-doc $(rule-name) $(module-name) : $(comment-block) ; #<is-local>$(is-local) ;
            }
            # parse args of rule.
            #> ECHO [] |$(rule-parts)| ;
        }
        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 ignored = [ $(action) $(module-name) : $(.module<$(target).action<$(action)>.arg) ] ;
    }
}

local rule do-scan ( modules + : action arg ? )
{
    #> ECHO === $(modules) ;
    local targets = ;
    for local module-file in $(modules)
    {
        local module-name = $(module-path:B) ;
        .module<$(module-file)>.name = $(module-name) ;
        .module<$(module-file)>.actions += $(action) ;
        .module<$(module-file)>.action<$(action)>.arg = $(arg) ;
        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 ;
    }
    DEPENDS all : $(targets) ;
}
