#|
Copyright 2017 Rene Rivera
Distributed under the Boost Software License, Version 1.0. (See
accompanying file LICENSE.txt or copy at
https://www.bfgroup.xyz/b2/LICENSE.txt)
|#

#| tag::doc[]
= Asciidoctor

The asciidoctor tool converts the ascidoc documentation format to various
backend formats for either viewing or further processing by documentation
tools. This tool supports the baseline asciidoctor distribution (i.e. the
Ruby based tool).
|# # end::doc[]

import common ;
import feature ;
import generators ;
import toolset ;
import "class" : new ;

feature.feature asciidoctor : : implicit propagated symmetric ;

#| tag::doc[]

== Feature: `asciidoctor-attribute`

Defines arbitrary asciidoctor attributes. The value of the feature should be
specified with the CLI syntax for attributes.
For example to use as a target requirement:

```
html example : example.adoc :
    <asciidoctor-attribute>idprefix=ex ;
```

This is a `free` feature and is not `propagated`. I.e. it applies only to the
target it's specified on.

|# # end::doc[]

feature.subfeature asciidoctor : attribute : : free ;

#| tag::doc[]

== Feature: `asciidoctor-doctype`

Specifies the `doctype` to use for generating the output format. Allowed
`doctype` values are: `article`, `book`, `manpage`, and `inline`.

|# # end::doc[]

feature.subfeature asciidoctor : doctype
    : article book manpage inline
    : optional propagated ;

#| tag::doc[]

== Feature: `asciidoctor-backend`

Specifies the `backend` to use to produce output from the source asciidoc.
This feature is automatically applied to fit the build target type. For
example, when specifying an `html` target for an `asciidoc` source:

```
html example : example.adoc ;
```

The target will by default acquire the `<asciidoctor-backend>html5`
requirement. The default for each target type are:

* `html`: `<asciidoctor-backend>html5`
* `docbook`: `<asciidoctor-backend>docbook45`
* `man`: `<asciidoctor-backend>manpage`
* `pdf`: `<asciidoctor-backend>pdf`

To override the defaults you specify it as a requirement on the target:

```
docbook example : example.adoc :
    <asciidoctor-backend>docbook5 ;
```

Allowed `backend` values are: `html5`, `docbook45`, `docbook5`, `pdf`.

|# # end::doc[]

feature.subfeature asciidoctor : backend
    : html5 docbook5 docbook45 manpage pdf
    : propagated symmetric composite ;

#| tag::doc[]

== Initialization

To use the `asciidoctor` tool you need to declare it in a configuration file
with the `using` rule. The initialization takes the following arguments:

* `command`: The command, with any extra arguments, to execute.

For example you could insert the following in your `user-config.jam`:

```
using asciidoctor : "/usr/local/bin/asciidoctor" ;
```

If no `command` is given it defaults to just `asciidoctor` with assumption
that the `asciidoctor` is available in the search `PATH`.

|# # end::doc[]

rule init ( command * )
{
    if ! $(.initialized)
    {
        # Setup only if we were called via "using .. ;"
        .initialized = true ;

        # Register generators.
        for local target-type in HTML MANPAGE PDF DOCBOOK
        {
            generators.register
                [ new asciidoctor-generator asciidoctor.convert
                    : ASCIIDOC : $(target-type) ] ;
        }

        # Seriously bad kludge to prevent docbook generators from being
        # considered when we are generating html directly.
        # TODO: Design and implement a mechanism to resolve generator conflicts.
        generators.override asciidoctor.convert : boostbook.docbook-to-onehtml ;
    }
    else
    {
        if ! $(command)
        {
            return ;
        }
    }

    # The command.. Default is bare asciidoctor.
    command ?= asciidoctor ;
    # We attempt to resolve each component of the command to account for
    # script interpreter wrappers.
    ASCIIDOCTOR = ;
    for local c in $(command)
    {
        local t = [ common.find-tool $(c) ] ;
        t ?= $(c) ;
        if [ MATCH "(\ )" : $(t) ]
        {
            # Ruby handles quotes weirdly. So we only add them if absolutely
            # needed.
            ASCIIDOCTOR += \"$(t)\" ;
        }
        else
        {
            ASCIIDOCTOR += $(t) ;
        }
    }
}

class asciidoctor-generator : generator
{
    import property-set ;

    rule run ( project name ? : property-set : sources + )
    {
        # ECHO *** asciidoctor-generator.run $(project) $(name) :: [ $(property-set).raw ] :: $(sources) ;

        # We set a default backend based on the target type.
        local backend = [ $(property-set).get <asciidoctor-backend> ] ;

        # For now, we only accept a single adoc source.
        if ( ! $(sources[2]) ) && ( [ $(sources[1]).type ] = ASCIIDOC )
        {
            # If no output name is specified, guess it from sources.
            # NOTE: For some reason the "?=" conditional assign op doesn't
            # work here. It assigns the value regardless, so work around it.
            # TODO: Look into why that happens.
            if ! $(name)
            {
                name = [ generator.determine-output-name $(sources) ] ;
            }

            # Depending on the kind of target we set up the backend, and
            # and any other options.
            if ! $(backend)
            {
                switch [ $(property-set).get <main-target-type> ]
                {
                    case HTML : backend = html5 ;
                    case DOCBOOK : backend = docbook45 ;
                    case MANPAGE : backend = manpage ;
                    case PDF : backend = pdf ;
                }
            }
        }

        # We build a reduced property set so that we are not toolset dependent.
        local raw-set = <asciidoctor-backend>$(backend) ;
        for local p in [ $(property-set).raw ]
        {
            if $(p:G) in <asciidoctor-attribute> <asciidoctor-doctype>
                <flags> <location>
            {
                raw-set += $(p) ;
            }
        }
        local relevant-ps = [ $(property-set).relevant $(property-set) ] ;
        raw-set += [ $(relevant-ps).raw ] ;
        raw-set = [ feature.expand-composites $(raw-set) ] ;
        raw-set += [ $(property-set).incidental ] ;
        property-set = [ property-set.create $(raw-set) ] ;
        return [ generator.run $(project) $(name) : $(property-set) : $(sources) ] ;
    }
}

_ = " " ;
toolset.flags asciidoctor ATTRIBUTE : <asciidoctor-attribute> ;
toolset.flags asciidoctor DOCTYPE : <asciidoctor-doctype> ;
toolset.flags asciidoctor BACKEND : <asciidoctor-backend> ;
toolset.flags asciidoctor FLAGS : <flags> ;

feature.compose <asciidoctor-backend>pdf : <flags>"-r asciidoctor-pdf" ;

actions convert
{
    $(ASCIIDOCTOR) -o$(_)"$(<:D=)" -D$(_)"$(<:D)" -b$(_)"$(BACKEND)" -a$(_)"$(ATTRIBUTE)" -d$(_)"$(DOCTYPE)" $(FLAGS) "$(>)"
}
