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

# Utilities for generating format independent output. Using these
# will help in generation of documentation in at minimum plain/console
# and html.

import modules ;
import numbers ;
import string ;

# The current output target. Defaults to console.
output-target = console ;

# The current output type. Defaults to plain.
output-type = plain ;

# Set the target and type of output to generate. This sets both
# the destination output and the type of docs to generate to that
# output. The target can be either a file or "console" for echoing
# to the console. If the type of output is not specified it defaults
# to plain text.
#
rule output (
    target # The target file or device; file or "console".
    type ? # The type of output; "plain", or "html".
    )
{
    type ?= plain ;
    output-target = $(target) ;
    output-type = $(type) ;
}

# Generate a section with a description. The type of output can be
# controlled by the value of the 'output-type' variable. If not set
# it defaults to 'console' indicating immediate display to the console.
# Other possible values are: 'html-file'.
#
rule section (
    name # The name of the section.
    description * # A number of description lines.
    )
{
    if $(output-type) = plain
    {
        lines $(name): ;
        lines ;
        if $(description)
        {
            lines [ split-at-words " " $(description) ] : "  " ;
            lines ;
        }
    }
}

# Generate the start of a list of items. The type of output can be
# controlled by the value of the 'output-type' variable. If not set
# it defaults to 'console' indicating immediate display to the console.
# Other possible values are: 'html-file'.
#
rule list-start ( )
{
    if $(output-type) = plain
    {
    }
}

# Generate an item in a list. The type of output can be
# controlled by the value of the 'output-type' variable. If not set
# it defaults to 'console' indicating immediate display to the console.
# Other possible values are: 'html-file'.
#
rule list-item (
    item + # The item to list.
    )
{
    if $(output-type) = plain
    {
        lines [ split-at-words "*" $(item) ] : "  " ;
    }
}

# Generate the end of a list of items. The type of output can be
# controlled by the value of the 'output-type' variable. If not set
# it defaults to 'console' indicating immediate display to the console.
# Other possible values are: 'html-file'.
#
rule list-end ( )
{
    if $(output-type) = plain
    {
        lines ;
    }
}

# Split the given text into separate lines, word-wrapping to a margin.
# The default margin is 78 characters.
#
rule split-at-words (
    text + # The text to split.
    : margin ? # An optional margin, default is 78.
    )
{
    local lines = ;
    text = $(text:J=" ") ;
    margin ?= 78 ;
    local char-match-1 = ".?" ;
    local char-match = "" ;
    while $(margin) != 0
    {
        char-match = $(char-match)$(char-match-1) ;
        margin = [ numbers.decrement $(margin) ] ;
    }
    while $(text)
    {
        local s = "" ;
        local t = "" ;
        # divide s into the first X characters and the rest
        s = [ MATCH "^($(char-match))(.*)" : $(text) ] ;
        
        if $(s[2])
        {
            # split the first half at a space
            t = [ MATCH "^(.*)[\\ ]([^\\ ]*)$" : $(s[1]) ] ;
        }
        else
        {
            t = $(s) ;
        }
        
        if ! $(t[2])
        {
            t += "" ;
        }
        
        text = $(t[2])$(s[2]) ;
        lines += $(t[1]) ;
    }
    return $(lines) ;
}

# Generate a set of fixed lines. Each single item passed in is
# output on a separate line. For console this just echos each line,
# but for html this will split them with <br>.
#
rule lines (
    text * # The lines of text.
    : indent ? # Optional indentation prepended to each line after the first one.
    )
{
    text ?= "" ;
    indent ?= "" ;
    if $(output-type) = plain
    {
        text $(text[1]) $(indent)$(text[2-]) ;
    }
    else if $(output-type) = html
    {
        local indent-chars = [ string.chars $(indent) ] ;
        indent = "" ;
        for local c in $(indent-chars)
        {
            if $(c) = " " { c = &nbsp; ; }
            else if $(c) = "	" { c = &nbsp;&nbsp;&nbsp;&nbsp; ; }
            indent = $(indent)$(c) ;
        }
        text $(text[1])<br> $(indent)$(text[2-])<br> ;
    }
}

# Output text directly to the current target.
#
rule text (
    strings * # The strings of text to output.
    )
{
    if $(output-target) = console
    {
        if ! $(strings)
        {
            ECHO ;
        }
        else
        {
            while $(strings)
            {
                ECHO $(strings[1]) ;
                strings = $(strings[2-]) ;
            }
        }
    }
    else if $(output-target)
    {
        $(output-target).line ?= 0 ;
        while $(strings)
        {
            local line-v = $(output-target).line.$($(output-target).line) ;
            $(line-v) on $(output-target) = $(strings[1]) ;
            NOCARE $(line-v) ;
            NOTFILE $(line-v) ;
            text-action $(output-target) : $(line-v) ;
            strings = $(strings[2-]) ;
            $(output-target).line = [ numbers.increment $($(output-target).line) ] ;
        }
    }
}

# Outputs the text to the current targets, after word-wrapping it
rule wrapped-text ( text + )
{
    local lines = [ split-at-words $(text) ] ;
    text $(lines) ;
}


# Append single line to target file.
#
actions quietly text-action
{
    echo '$($(>))' >> $(<)
}

local rule __test__ ( )
{
    import assert ;
    
    assert.result one two three : split-at-words one two three : 5 ;
    assert.result "one two" three : split-at-words one two three : 8 ;
    assert.result "one two" three : split-at-words one two three : 9 ;
    assert.result "one two three" : split-at-words one two three ;
}
