Skip to content

Latest commit

 

History

History
6007 lines (4473 loc) · 188 KB

README.adoc

File metadata and controls

6007 lines (4473 loc) · 188 KB

Jamal Snippet Handling Macros

Snippet handling macros help you to include information from program source code into the documentation automatically. This macro package also includes some XML handling, string, and text formatting macros, which are usually used together with snippets.

When you document an application, there is a lot of information that changes by changing the source code. These changes have to be followed in the documentation. Some changes need manual editing. Some changes can be automated. When the program functionality changes, the changed documentation has to describe the new functionality. We can hardly automate this change of documentation. When some literal parameter also used in the document changes, then Jamal can automatically update the documentation. A typical example is the version number of the application. The documentation many times may refer to the latest version.

For example, if you want to use the latest version of Jamal Snippet macro as a dependency in a pom.xml file then you have to have the lines:

<dependency>
    <groupId>com.javax0.jamal</groupId>
    <artifactId>jamal-snippet</artifactId>
    <version>2.8.2-SNAPSHOT</version>
</dependency>

You see in the README.adoc file the actual version number converted from the README.adoc.jam. The original file, however, contains

{%@snip:xml pom=pom.xml%}\
{%#define VERSION={%pom /project/version/text()%}%}
<dependency>
    <groupId>com.javax0.jamal</groupId>
    <artifactId>jamal-snippet</artifactId>
    <version>{%VERSION%}</version>
</dependency>

The Jamal Snippet macros help you automate to insert actual, and transformed values, text fragments from

  • any source code

  • XML file

  • properties files.

These small text pieces are called snippets.

When the .jam file gets processed, the snippets will automatically contain the source code’s actual text. This way, it is easier to keep your documentation up to date.

Table of contents

  1. Usage

  2. Macros

    1. snip:collect

    2. snip:properties

    3. snip:xml

    4. xml:define

    5. xml:insert

    6. snip:define

    7. snip:clear

    8. snip

    9. snip:check

    10. snip:list

    11. snip:save

    12. snip:load

    13. trimLines

    14. untab

    15. range

    16. base64

    17. repeat

    18. rot13

    19. def

    20. urlencode

    21. pos

    22. sort

    23. numberLines

    24. killLines

    25. skipLines

    26. replace

    27. replaceLines

    28. counter:define

    29. references

    30. reflow

    31. snip:line

    32. snip:file

    33. snip:eval

    34. snip:transform

    35. lineCount

    36. listDir

    37. xmlFormat

    38. thinXml

    39. date

    40. format

    41. numbers

    42. unicode

    43. case:lower, case:upper, …​

    44. file:locate

    45. dev:root

    46. file

    47. directory

    48. Java Macros

      1. java:class

      2. java:field

      3. java:method

      4. java:sources

      5. java:classes

      6. java:methods

      7. java:fields

      8. java:insert

    49. String Macros

      1. string:contains

      2. string:quote

      3. string:equals

      4. string:startsWith

      5. string:endsWith

      6. string

      7. string:reverse

      8. string:substring

      9. string:between

      10. string:before

      11. string:after

      12. string:length

      13. string:chop

    50. shell:var

    51. hashCode

    52. memoize

    53. download

    54. dictionary

    55. decorate

    56. snip:update

    57. variation

    58. plural

  3. <<kroki,Kroki>

1.Usage

If you are using Jamal programmatically or from Maven then the dependency you have to add to your project is:

<dependency>
    <groupId>com.javax0.jamal</groupId>
    <artifactId>jamal-snippet</artifactId>
    <version>2.8.2-SNAPSHOT</version>
</dependency>

The macro classes are listed in the provides directive in the module-info.java; therefore, they are available for the Jamal processor when the JAR file is on the path (class or module). There is no need to invoke the use command to get access to these macros.

2.Macros

I. snip:collect

Since 1.7.4 option onceAs

This macro collects text snippets from files.

The first line following the macro identifier until the end of the line may contain parameters. These parameters are parsed using the Standard Parameter Parsing as defined in PAROPS.

The syntax of the macro is

{@snip:collect from="fileName" ...options}

fileName can be the name of a text file, or it can be a directory. If the fileName is a text file, then the macro will collect the single file’s snippets. If the fileName is a directory, then the macro will collect the snippets from the files in that directory and from directories beneath recursively.

The file name is relative to the Jamal source, which contains the snip:collect macro. The file name can start with res: or https://. In these cases, the content of the file will be loaded from a resource or through the net respectively.

A snippet in a file is a series of lines that happen between

snippet name

and

end snippet

lines.

A special snippet containing only a single line can be specified preceding it with a line

snipline name

In this case there is no need for end snippet line. This way

// snippet MY_CONSTANT_SNIPPET_NAME
  public static final MY_CONSTANT = "Hello World";
// end snippet

is the same as

// snipline MY_CONSTANT_SNIPPET_NAME
  public static final MY_CONSTANT = "Hello World";
Note

The only difference is that the first version will contain a line with a trailing \n at the end of the line. The snipline version does not contain the trailing \n.

A snipline can also have a trailing filter=regex after the name of the snippet. The rest of the line after the filter= is a regular expression that will be used to filter the line. (Note that there is no space before or after the = character.) The regular expression should have exactly one capturing group, and it must match the next line. The capturing group will be used as snippet content.

For example

// snipline MY_CONSTANT_SNIPPET_NAME filter="(.*)"
  public static final MY_CONSTANT = "Hello World";

will store Hello World as the content of the snippet MY_CONSTANT_SNIPPET_NAME. Unfortunately, it is easy to misread the line above and to think that the capture group will match the whole line. However, the filter on the snipline is not a macro parameter. It is processed in a different way. The whole part following the filter= is part of the regular expression, including the " characters in the above example.

The possibility of filter= following the name of the snippet is a complimentary feature. Cutting off a part of the line using regular expression can also be done where the snip macro references the snippet. This complimentary feature exists to keep the filtering regular expression close to the line from which you want to cut a part off.

There can be extra characters before or after the snippet name and/or the end snippet strings. The only requirement is that the regular expression snippet\s+([a-zA-Z0-9_$]+) can be found in the starting line and end\s+snippet in the ending line.

Note

The definition and matching criteria of the start and the end of the snippet are very liberal. The reason for that is to ease in recognizing these in different files. The regular expressions will find snippet start, and snippet ends in Java files, in XML, in Perl or Python code. Essentially, you should not have a problem signaling the start, and the end of the snippet in any program code that allows you to write some form of a comment.

The disadvantage of this liberal definition is that sometimes it finds lines that accidentally contain the word snippet. If you look at the source code in the file ../../src/main/java/javax0/jamal/snippet/TrimLines.java you can see examples. The comment mentions snippets, and there is a word eligible to be an identifier after snippet, and the parsing thinks that this is a snippet start. Eventually, there is no end snippet on the lines following it, which is an error the snippet collection process recognizes. (Up to 1.7.2. Later versions use this file as a snippet source; thus, it has 'end snippet'.) Still, you do not receive an error message.

That is because the collection process only recognizes this error but does not throw an exception. The exception is thrown only when you want to use the snip macro for an unterminated snippet.

The possible situation may even be more complicated because the accidental word following snippet in a comment may also be used in other files as a snippet identifier. The collector, sooner or later, will find that definition, and it will assume that the one with the error was just an accidental comment and replaces the old with the correct, error-free snippet. It is still okay when the snippet collection finds these two snippets in the opposite order. If there is already a correct, error-free snippet collected and the collection finds an erroneous one of the same name, it ignores that.

This way, the collection and the use of the snippets ignores the accidental snippet definitions, but at the same time, it can detect the malformed snippets.

If you look at the ../../src/main/java/javax0/jamal/snippet/TrimLines.java in version 1.7.3 or later, you can see that there is a // snippet line in the code. Because there is also an accidental snippet line before it, the collection would not find this line. Because of the previous snippet line, the real // snippet line becomes part of the previous snippet. The // snippet line is preceded by an // end snippet line to avoid this. Such a line out of a snippet is ignored, and in this case, it closes the accidental snippet.

As you can see, the regular expression contains a collection group, which Jamal uses as the name of the snippet. For example, the code

// snippet sample
public class Sample implements Macro {

    @Override
    public String evaluate(Input in, Processor processor) {
        return in.toString()
            .replaceAll("^\\n+", "")
            .replaceAll("\\n+$", "");
    }
}
// snippet end

defines a snippet that is named sample. The snippets can be used later using the snip macro.

The output of the collect macro is an empty string.

The macro behaviour can be altered using options.

These options are parsed using the Standard Parameter Parsing as defined in PAROPS.

  • include can define a regular expression. Only those files will be collected that match partially the regular expression.

  • exclude can define a regular expression. Only those files will be collected that do not match partially the regular expression. For example, the test file

    {#snip:collect from="." exclude=2 include=SnippetSource-\d\.txt}
    First snippet {@snip first_snippet}
    2. snippet {@snip second_snippet}
    
    Next file
    {@try!
    First snippet {@snip second_file_first$snippet}
    Second snippet {@snip seconda_snippet_uniconde}
    }
    and this is the end

    excludes any file that contains the character 2 in its name.

  • start can define a regular expression. The lines that match the regular expression will signal the start of a snippet.

  • liner can define a regular expression. The lines that match the regular expression will signal the start of a one-liner snippet.

  • lineFilter can define a regular expression. The pattern will be used against any 'snipline' lines, to find the regular expression that will be used to filter the content of the line

  • stop can define a regular expression. The lines that match the regular expression will signal the end of a snippet.

  • scanDepth can limit the directory traversing to a certain depth.

  • from can specify the start directory for the traversing.

  • onceAs You can use the parameter onceAs to avoid repeated snippet collections. Your collect macro may be in an included file, or the Jamal source structure is complex. At a certain point, it may happen that Jamal already collected the snippets you need. Collecting it again would be erroneous. When snippets are collected, you cannot redefine a snippet. If you define a parameter as onceAs="the Java samples from HPC" then the collect macro will remember this name. If you try to collect anything with the same onceAs parameter, the collection will ignore it. It was already collected.

  • prefix You can define a prefix, which is prepended to the snippet names. The snippets will be stored with this prefix, and the macros should use these prefixed names to reference the snippets. For example, if you define the prefix as myprefix:: then the snippet named mysnippet will be stored as myprefix::mysnippet.

  • postfix You can define a postfix, which is appended to the snippet names. The snippets will be stored with this postfix, and the macros should use these postfixed names to reference the snippets. For example, if you define the postfix as ::mypostfix then the snippet named mysnippet will be stored as mysnippet::mypostfix.

    The parameter prefix and postfix can be used together. The use case is when you collect snippets from different sources where the names may collide.

  • java Collect snippets from the Java sources based on the Java syntax without any special tag.

  • javaSnippetCollectors You can define a comma-separated list of Java snippet collectors.

  • asciidoc Using this parameter, the macro will collect snippets using the ASCIIDOC tag syntax. This syntax starts a snippet with tag::name[] and ends it with end::name[], where name is the name of the snippet. Using these start and stop delimiters, the snippets can also be nested arbitrarily, and they can also overlap.

  • ignoreErrors Using this parameter, the macro will ignore IOExceptions. An IOException typically occurs when a file is binary and by accident it contains an invalid UTF-8 sequence. Use this option only as a last resort. Better do not mix binary files with ASCII files. Even if there are binary files from where you collect snippets from ASCII files, use the option exclude to exclude the binaries.

If the parameter start or liner are defined, the value will be used as a snippet start matching regular expression. They must have one collection group.

Note
We introduced this option to the snip:collect macro along with the Jamal doclet implementation. When the individual documentation parts are processed in the same processor, the processing order is not guaranteed. To refer to some snippets, you have to collect them. To do that, you have to have the snip:collect in every JavaDoc, presumably using an imported file. That collect macro should name the collection to avoid redefinition error.

II. snip:properties

This macro will load properties from a "properties" file or an "XML" file. The names of the properties will become the names of the snippets and the values of the snippets.

For example, the sample

Jamal source
{@snip:properties testproperties.properties}

will load the content of the file javax0/jamal/snippet/testproperties.properties, which is

a=letter a
b=    letter b
c = letter c

and thus using the snip macro, like

Jamal source
{@snip a}

will result

output
letter a

If the file extension is .xml, the properties will be loaded as XML format properties. For example, the same properties file in XML format looks like the following:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
    <comment>Application Configuration</comment>
    <entry key="a">letter a</entry>
    <entry key="b">letter b</entry>
    <entry key="c">letter c</entry>
</properties>

Loading it using the macro

Jamal source
{@snip:properties testproperties.xml}

and referencing this time the property b as

Jamal source
{@snip b}

will result

output
letter b

III. xml:define

This macro scans its input as an XML and assigns the parsed data to a "user-defined" macro. The syntax of the command is:

{@xml:define macroName=xmlcontent}

The defined macroName macro can be used as an ordinary user-defined macro that accepts one, optional argument. This user-defined macro evaluates in a particular way. It uses the argument as an XPath expression and returns the value from the XML document that matches the argument. If the XPath expression is missing, then the whole XML content is converted to formatted text and returned.

A typical example is to retrieve some build parameters from the pom.xml file. You can read the content of the pom.xml file and reference the version of the project using the following lines:

Jamal source
{#xml:define pom={@include [verbatim]pom.xml}}\
{#define VERSION={pom /project/version/text()}}\
...
<version>{VERSION}</version>
...

The result is:

output
...
<version>2.8.2-SNAPSHOT</version>
...

IV. snip:xml

This macro loads an XML file and assigns it to a "user-defined" macro. The syntax of the command is

{@snip:xml macroName=xml_file.xml}

The defined macroName macro can be used as an ordinary user-defined macro that accepts one, optional argument. This user-defined macro evaluates in a particular way. It uses the argument as an XPath expression and returns the value from the XML document that matches the argument. If the XPath expression is missing, then the whole XML content is converted to formatted text and returned.

For example, this document contains the following macros at the start:

Jamal source
{@snip:xml pom=pom.xml}\
{#define VERSION={pom /project/version/text()}}\
...
<version>{VERSION}</version>
...

The result is:

output
...
<version>2.8.2-SNAPSHOT</version>
...

which is the current version of the project as read from the pom.xml file.

Note that you can have the same result using the xml:define macro and including the content of the XML file verbatim. When the XML content is in a file calling this macro is a bit more efficient.

V. xml:insert

This macro can modify an XML-formatted user defined macro inserting content into the XML document. XML-formatted user defined macros can be created using the xml:define and snippet:xml macros.

This macro can also be used to insert an XML fragment into the XML document, which is the output of the whole processing. This latter use is for the case when the Jamal file processed creates an XML file. The syntax of the command is

Jamal source
{@xml:insert (options) xml_content_to_insert}

The options define the name of the xml formatted user defined macro the content should be inserted into as well as the xPath that defines the location of the insertion.

  • xpath (can be aliased as path) defines the location in the original XML where to insert the content.

  • id, (can be aliased as to) defines the name of the XML user defined macro which will be modified. If this option is missing, the insertion will happen when the whole document processing is already finished. In that case, the target XML is the one, which is the result of the Jamal processing. This is usable when the Jamal processing creates an XML as a result. The insertions are done in the order of the xml:insert commands, and after that the output will be the resulting XML formatted.

  • ifneeded (can be aliased as optional) defines whether the insertion is optional. If the location specified by the path already contains a tag with the given name, then the XML will not be modified. Without this option, a new child is appended having the name that may already be there.

  • tabsize can specify the formatting tab size. This makes only sense if the insertion happens to the final XML content of the processing.

The following example shows how to insert a new child into the XML document.

Jamal source
{@xml:define myXml=
<xml>
  <FamilyName>Muster</FamilyName>
</xml>
}\
{@xml:insert (to=myXml path=/xml) <FirstName>Peter</FirstName>}
{myXml}

will result in

output
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xml>
    <FamilyName>Muster</FamilyName>
    <FirstName>Peter</FirstName>
</xml>

The XML content is defined using the macro xml:define. Later the content of this XML is modified using the macro xml:insert. The content of the macro is converted to text and gets into the output when the name of the macro is used without the an Xpath argument.

The following example demonstrates how the result of the processing can be modified.

Jamal source
<project>
{@xml:insert (path=/project ifneeded)<dependencies></dependencies>}
{@xml:insert (path=/project ifneeded)<plugins></plugins>}
{@xml:insert (path=/project ifneeded tabsize=2)<pluginManagement></pluginManagement>}
{@xml:insert (path=/project ifneeded)<dependencyManagement></dependencyManagement>}

{@xml:insert (path=/project/dependencies)
<dependency>
<groupId>com.javax0.jamal</groupId>
<artifactId>jamal-snippet</artifactId>
<version>2.8.2-SNAPSHOT</version>
</dependency>}
<plugins><plugin>
<artifactId>my-imaginary</artifactId>
<groupId>plugin</groupId>
<version>r65.1204-2021</version>
</plugin>
</plugins>

</project>

will result

output
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project>
  <plugins>
    <plugin>
      <artifactId>my-imaginary</artifactId>
      <groupId>plugin</groupId>
      <version>r65.1204-2021</version>
    </plugin>
  </plugins>
  <dependencies>
    <dependency>
      <groupId>com.javax0.jamal</groupId>
      <artifactId>jamal-snippet</artifactId>
      <version>2.8.2-SNAPSHOT</version>
    </dependency>
  </dependencies>
  <pluginManagement/>
  <dependencyManagement/>
</project>

The insert macros in this example do not specify any id. This means that all the modification is done to the XML, which is the whole document. Also, the modification happens at the end of the processing.

The first four insertions are optional in the sense that they will modify the output if there is no such tag in the XML. They make not much sense in a simple XML file, like this, but in a larger XML, where the different parts come from different includes it may make sense. Such insertions ensure that these parts are inserted if they are needed by other insertions. The first four lines could be in a separate file and included using the include macro to support pom structure.

For example, the fifth insertion can only be executed successfully because the first one is there. Without this there would be no /project/dependencies location in the XML file. At the same time the second optional insertion for the plugins is ignored, because there is an explicit plugins tag in the content. The fifth insertion adds a dependency to the dependencies tag.

VI. snip:define

You can use this macro to define a snippet. Snippets are usually collected from project files, but it is also possible to define them via the macro snip:define. For example,

Jamal source
{@snip:define mySnippet=
It is the snippet, which is defined inside the file and not collected from an external file.
}
{@snip mySnippet}

will result

output
It is the snippet, which is defined inside the file and not collected from an external file.

Snippets typically contain several lines, and the leading spaces may be important. To accommodate this when you define a snippet using this macro, spaces following the = sign up to, and including the new line will be skipped. As you can see in the example above, there is no new line before the sentence It is the…​. It is recommended to have a new line after the = character and start the snippet content on the next line. If you start the snippet right after the = character then the spaces between the = and the first non-space character will be skipped. For example:

Jamal source
{@snip:define mySnippet1=     It is the snippet,
which is defined inside the file and not collected from an external file.
}
{@snip mySnippet1}

will result

output
It is the snippet,
which is defined inside the file and not collected from an external file.

As you can see there are no spaces before the characters It is the…​.

A snippet always holds the reference to the file and line number where it was defined. You may want to have a different location from where the snip:define macro is called. For example, you create a new snippet from an already existing one replacing some parts of it. It is logical to inherit the location of the snippet to the transformed one.

To do that, you can either specify the file name and the line number using parops, or you can name an already existing snippet to inherit the location from. The file name and the line number are used

  • to report error if there is any, and

  • to calculate the file name in the case the snippet gets evaluated and contains an include or import macro.

Jamal source
{@snip:define (file="./snippy.txt" line=6379) mySnippetA=abra kadabra badir kebi}
{@snip:define mySnippetB=badir kebi, alias Don Sakan}
{@snip:file mySnippetA}
{@snip:file mySnippetB}

will result in

output
../jamal-snippet/documentation/macros/snippy.txt
../jamal-snippet/documentation/macros/snip_define.adoc.jam

VII. snip:clear

This macro deletes all collected snippets from memory.

Snippets are stored in a central structure, which is global for the whole processed file. Snippets cannot be overwritten. If a snippet is collected from a file, which was already collected, the action will raise an error. The same happens when you try to define a snippet for a name that already exists using the snip:define macro.

Using this macro this central store can be deleted removing all the snippets from this central store.

The result of the macro is an empty string.

VIII. snip

You can use the snip macro to insert one or more snippets into the output. There are three different ways to use the macro.

  1. insert a single snippet into the output with the full text of the snippet

  2. insert a part of the first line of a snippet into the output

  3. insert multiple snippets into the output.

In all of these cases the option hash or hashCode can be used to perform a consistency check. It works exactly the same way as in the macro snip:check. The possibility to have this option eliminates the need to use the snip and the snip:check macros together. Note, however, that it is not possible to check against the number of the lines in the snippet. You can check only the hash code.

You can also use any of the options of the macro snip:transform. The macro snip will automatically apply the transformations calling the macro snip:transform.

Use one snippet

The syntax for the first case is

{@snip snipped_id comment}

The result of the macro is the content of the snippet named snipped_id. The macro reads the snippet identifier from the input, and it purposefully ignores the rest when used without the option poly. The reason to have the rest of the input as the comment is to allow the Jamal file users to insert a description of the snippet. You can manually copy the content of the snippet there, which helps the navigation in the source code, but at the same time, it may not be a problem if the copy gets outdated. You can use the macro snip_update to update the snippet’s content in the Jamal source file. Since that macro modifies the file you edit, you must use it with care.

Since the introduction of the Asciidoctor preprocessor and the WYSIWYG editing possibility of Asciidoc and Markdown files the importance of this feature has decreased.

Partial snippet

The second case uses only part of the snippet. (starting from Jamal version 1.7.2)

{@snip snipped_id /regular expression/}

If the "comment" following the snippet id starts with the / character, then the result of the macro is the first line of the snippet. This line should match the regular expression, or an error is reported. Also, the regular expression must be error-free and must have a match group. The result of the macro will be the part of the first line of the snippet that matches the regular expression match group. The typical use is when there is a constant defined in the code, and you want to reference the value of the constant. In this case, you can add

// snipline snippet_id
...

line before defining the constant, and add a regular expression with one capturing group. For example, you can have

// snipline defaultShellName
public static final String DEFAULT_RUBY_SHELL_NAME = ":rubyShell";

and the Jamal code

{#define defaultShellName={@snip defaultShellName /"(.*)"/}}\

to gauge out the string :rubyShell from the source code.

Note

It is also possible to specify the regular expression in the source code. As it is documented in the section collect, the snipLine declaration may end with a filter=regular expression option.

When you check the snippet consistency using the option hash or hashCode the hash code of the whole snippet is calculated. If there is any change in the snippet, even outside the first line, the hash code will be different and the snippet inclusion will purposefully fail.

Multiple Snippets

The third use is to insert multiple snippets into the output. (starting from Jamal 1.11.0) In this case, the syntax is the following:

{@snip (poly) regular expression}

Note that there is no / around the regular expression.

The option poly has to be between ( and ) characters as usual in non-core built-in macros.

In this case, the macro collects and concatenates all snippets with regular expression matching names, and the value of the macro is the resulting text. The snippets are sorted by their names before concatenation regardless of their appearance order in the file or files.

Note

This ordering is different from what you can achieve using AsciiDoc style snippet collecting. When you do AsciiDoc style snippet collecting, you use the tag:: name [] and end:: name [] syntax to start and stop snippets. A snippet can be started and stopped using the same name many times in the same file. Following the style of AsciiDoc snippets, the collection process collects all those fragments into a single snippet. The ordering is the same as the appearance order of the snippet fragments in the file. The collection process does the concatenation.

When the snip macro is used with the option poly, the snippets are concatenated during the use. The snippets may be collected from different files. The order of the different snippets coming from separate files is not defined. The snippets are sorted by their name before concatenation to guarantee a definite order. It is recommended to name these snippets with a name and a number, like my_snippet_1000, my_snippet_2000, etc. That way, the regular expression can be my_snippet_\d{4}, and in case the ordering needs to be changed slightly, there is room to insert a new snippet between two already existing ones. You may remember this technique from 40 years ago when BASIC program lines had to be numbered.

When used with the option poly, it is impossible to use any comment following the regular expression. The whole text after the option till the macro closing string is interpreted as part of the regular expression.

When you check the snippet consistency using the option hash or hashCode the hash code of the whole text containing the snippets concatenated is calculated. If there is

  • any change in any of the snippets, or there is

  • a new snippet included into the set,

  • a snippet was deleted or renamed and is not included into the set any more,

  • or in case the ordering of the snippets change,

  • for any reason the concatenated set of snippets changes,

then snippet inclusion will purposefully fail.

Snippet Transformation

You can surround the snippet using the macro snip:transform transforming the output. This is, however, such a usual case that the macro will automatically invoke the transformation. To do this the macro snip will accept all the options of the macro snip:transform and will pass them to that.

IX. snip:check

You can use this macro to enforce consistency between the documented system and the documentation. Using this macro will nudge the maintainer to check the relevant parts of the document when the documented code changes. The macro itself will not update the documentation. It will warn with an error if some part of the documentation needs update due to changed application code. That way, the document may remain up-to-date, and you will not forget to update it.

To use the macro, you should first select some part of the code. This part can be one or more snippets and whole source files. You specify file names using the file or files option of the macro. Snippets are specified using the id or ids option. You can define one or more files and snippets at the same time. The values are comma-separated.

{@snip:check hash=2a4ddeab580ad1fe8c95a id=snippet1,snippet2
    file=src/main/java/javax0/jamal/snippet/SnipCheck.java,src/test/java/javax0/jamal/snippet/TestSnipCheck.java%}

The macro snip:check calculates a hashcode of the snippet. The hashcode can also be specified in the macro option hash or hashCode.

(These hex code above is made up, TestSnipChek.java does not even exist.)

If this hash code is the same as the one calculated, the programmer did not change the code in the snippet. If the codes are different, then the macro will error. It means that the snippet or file has changed, and the documentation has to follow the change. When the documentation is updated, you should also update the hash code.

Nothing will stop you from updating the hash code without updating the documentation, though. It takes discipline to keep the documentation up-to-date. This macro only helps not to forget some parts.

The hash code calculated contains 64 hexadecimal characters. You may notice that the examples above contain less. The macro accepts if only a few characters of the hash code are present. However, you have to specify at least six characters to ensure consistency. You have one to the ten million chance to change the snippet and get the same hash using six characters. It is reasonably safe, but you can go safer listing more characters.

It is a considerable practice to include some instruction into the error message helping the maintainer. You can do that using the option message. The string of this option will be part of the error message. For example, this document includes some lines similar to the following ones.

{@snip:check hash=72415fa846e6f
             file=src/main/java/javax0/jamal/snippet/SnipCheck.java
             message="Review the whole chapter of 'snip:check'"
}\

When you create the documentation of some code, you should follow the steps:

  1. Enclose the parts of the code documented between snippet NAME and end snippet, or use complete files.

  2. Insert the {@snip:check hash="" id=NAME} macro into the parts of the documentation where the pieces are documented. Do not be afraid to copy and paste the macro. In this case, copy-pasting is allowed, as the aim of this macro is to increase redundancy.

  3. Run the macro processing. It will eventually result in an error. The error message will include the hash code. Copy at least six characters to the macro, like {@snip:check hash=af6ed3 id=NAME}. If you use more than one snip:check in your documents, using different parts of the hash code is recommended. It will later help navigation when you search for the specific part of the document.

  4. Rerun the macro processing.

When you update the documentation, and you get an error like

javax0.jamal.api.BadSyntaxAt: The file(src/main/java/javax0/jamal/snippet/SnipCheck.java)
hash is 'fa58557b.9735f98d.31c87ea5.074bd7f5.064ec63f.ec447a7e.58b8f969.958e5d4f' does not contain 'fa58557b9735f98k'.
'Review the whole chapter of 'snip:check'' at ../jamal-snippet/README.adoc.jam/435:14

then you have to do the following steps.

  1. Look at the documentation around where the macro is. The wrong hash code included in the error message will help you. Use text search in the files looking for the hex code. It should be reasonably unique. Update the documentation to follow the change of the code part.

  2. Update the hash code in the macro to the new value.

  3. Rerun the macro processing.

Note

The error message contains the 64 character hex code as eight times eight characters dot-separated. It helps you select a part of the code when you copy the new code into the documentation after the update. It also helps you select different parts if you want to have more than one reference to the hash code. The dots are not part of the code and are printed only for convenience and ignored when comparing. You can use as many or as few dots in the hash code as you like.

The hash code is displayed using lower case hexadecimal characters, but you can use upper case or mixed case characters. Before the check, the dots are removed, and the characters are converted to a lower case.

In some rare cases, you do not want to check all the modifications of the file. You specify the option lines to check the number of lines in the snippet or file has not changed. To do that, you can

{@snip:check lines=22 id=snippet1,snippet2}

and the macro will not error so long as long the snippet or the file contains exactly 22 lines.

You can specify both the lines and the hash together, but it does not make much sense. The macro will check the hash value first. If the hash value fails, the macro results an error. If the hash value is correct, the file or snippet is identical, meaning it has the same number of lines as before.

You can ask the macro snip:check to ignore its task defining the JAMAL_SNIPPET_CHECK system property to false. It is handy when your documentation has many snip:check control, and the compilation of the documentation is don in the tests. This would require the update of the hash code in the documentation whenever you make any small changes in the source code. To avoid this you can

mvn clean verify -DJAMAL_SNIPPET_CHECK=false

which will compile your code on the development machine fine. When you are finished with the adhoc changing, however, you have to update the documentation to reflect the changes in the code.

There is another way to force processing without error. The macro snip:check has the option snipCheckWarningOnly (aliased as warning, warningOnly). The name snipCheckWarningOnly is supposed to be used globally in the Jamal file via the options macro. On the macro you will probably use warning or the more expressive warningOnly. With this option the macro will not cause bad syntax error in case the check fails. It will only log into the log file a warning message.

Note
Even when you use the snipCheckWarningOnly option, the macro will cause bad syntax when the hash code is too short, or there are some other errors. It is only to supress hash code or line mismatch.

Using the option warning the error message will be sent to the log as a warning. If you use the option snipCheckError (aliased as error) then the error message will be sent to the log as an error. In this case, the error will not be suppressed. You cannot use warning and error together.

X. snip:list

This macro lists the defined snippets.

The first line following the macro identifier until the end of the line may contain parameters. These parameters are parsed using the Standard Parameter Parsing as defined in PAROPS.

The list is represented as comma-delimited, which contains the names of the snippets.

There are four parameters the macro handles; all are optional:

  • name (can be aliased as id) for the name of the snippet

  • file (can be aliased as fileName) for the file name of the snippet

  • text (can be aliased as contains) for the content of the snippet

  • listSeparator can redefine the list separator. The default is the comma.

The first three parameters are interpreted as regular expressions. If any missing or empty string, then the parameter is not taken into account. If any of them is specified, only those snippets will be listed that match the expression.

The listing will filter the snippets to include only those into the list that

  • the name of the snippet matches the parameter name, and

  • the file name from which we harvested the snippet matches the parameter file, and

  • at least one line of the snippet matches the parameter text.

The matching means that the regular expression should match part of the text. If you want to match the full name, file name, or content line, you have to use a ^ …​ $ format regular expression.

If all the parameters are missing, then the macro will list all the snippet names.

Note
The versions 2.2.0 and later do not include the snippets in the list which are errored. These are snippets, which are not closed. These are usually not snippets, but their start is recognized as a snippet start. You can have the word snippet in your code followed by something that may look like a snippet start. This is not an error in Jamal, only if you try to use any of these snippets.

XI. snip:save

This macro saves all the collected snippets to a file.

The first line following the macro identifier until the end of the line may contain parameters. These parameters are parsed using the Standard Parameter Parsing as defined in PAROPS.

The file name must be specified by the parameter output. The general syntax of the macro is

{@snip:save options}

The usable options are the following:

  • name (can be aliased as id) regular expression to match the name of the snippet

  • file (can be aliased as fileName) regular expression to match the file name of the snippet

  • text (can be aliased as contains) regular expression to match the content of the snippet

These parameters are interpreted as regular expressions. If there is any missing or empty string, the parameter is not considered. If any of them is specified, the macro will save only those snippets that match the expression.

  • output should specify the name of the output file. It is a mandatory parameter.

  • format can specify the format of the output file. The default value is XML. The available formats are XML and nothing else. This parameter is present for future compatibility and to provide readability if the command has to specify that the output is XML.

  • tab (can be aliased as tabSize) can specify the number of spaces to use for indentation. The default value is 4.

The saved XML file will use the namespace https://snippets.jamal.javax0.com/v1/snippets. The top level element is snippets.

The snippets tag has the following attributes:

  • ts is the time stamp when the snippets were saved.

  • dateTime the date and time when the snippets were saved.

The timestamp and the date/time values reflect the same value. Programs reading the XML can use the timestamp value. The human-readable date-time value is for the human reader. Currently, none of these values are used by Jamal.

The elements below the snippets tag are snippet tags. Every snippet tag has the following attributes:

  • id id the identifier of the snippet. When this XML is loaded, snip:load will use this identifier to load the snippet.

  • file the file the snippet was defined in. When this XML has loaded, this file name will be restored so that the snippet will look like one loaded from this file.

  • line is the line number in the file where the snippet starts. When this XML is loaded, this line number will be restored so that the snippet will look like one loaded from this line from the specified file.

  • column is the column where Jamal initially loaded the snippet from. This value is usually 1 since snippets are multi-lined strings and do not contain fractional lines. The snippet will look like one loaded from this column from the specified line when this XML is loaded.

  • hash contains the hash value of the snippet. This value is calculated precisely as the hash value in the snip:check macro. When the snip:load macro reads the XML file, it compares the hash value calculated from the snippet’s text with the hash value. It is an error if the hash values do not match. In that case, the snippet loading terminates, and no more snippets are loaded from the XML file. The snippets already loaded will stay put. Note, however, that you are allowed to delete the hash attribute from the XML file using an editor of your choice. In that case, no check is performed.

The text of the snippet is the content of the snippet tag. It is saved as a CDATA section(s).

XII. snip:load

This macro can load the snippets from a file, which was saved by snip:save.

The first line following the macro identifier until the end of the line may contain parameters. These parameters are parsed using the Standard Parameter Parsing as defined in PAROPS.

The file’s name has to be specified by the parameter input. The general syntax of the macro is

{@snip:load options}

The usable options are the following:

  • name (can be aliased as id) regular expression to match the name of the snippet

  • file (can be aliased as fileName) regular expression to match the file name of the snippet

  • text (can be aliased as contains) regular expression to match the content of the snippet

These parameters are interpreted as regular expressions. If there is any missing or empty string, the parameter is not considered. If any of them is specified, the macro will load only those snippets that match the expression.

  • input should specify the name of the input file. It is a mandatory parameter.

  • format can specify the format of the input file. The default value is XML. The available formats are XML and nothing else. This parameter is present for future compatibility and to provide readability if the command has to specify that the input is XML formatted.

The XML file format has to be the same as the one used by the snip:save macro. It has to use the same namespace and tags. The content of snippets must be in a CDATA section(s).

The ts, and dateTime attributes of the tag snippets may be missing and are ignored during reading. The attributes of the snippet tags are mandatory except the hash attribute. If the hash attribute is present, the macro will check its value against the content of the snippet. It guarantees the snippet integrity. If the tag is missing, the check is skipped.

Any tag can have any extra attributes. Extra attributes are ignored. If you want to change something in the XML file and edit some snippet code temporarily, you can rename the tag hash to _hash, for example.

XIII. trimLines, trim

This macro can cut off the unneeded spaces from the start and end of the lines.

The first line following the macro identifier until the end of the line may contain parameters. These parameters are parsed using the Standard Parameter Parsing as defined in PAROPS.

When you include a code fragment in the documentation as a snippet, the lines may have extra spaces at the start. It is the case when the fragment comes from a code part somewhere in the middle of a tabulated structure. This macro can remove the extra spaces from the start of the line keeping the relative tabulation of the lines. The code formatting remains the same as the source code, but the macro will align the code sample to the left.

The syntax of the macro is:

{@trimLines ...

  possible
    multiple lines
}

For example:

{@trimLines
  k
   a
   b
 c
}

will result

 k
  a
  b
c

The lines to be trimmed should start on the line following the name of the macro. The characters following the macro name to the end of the line are parsed for options. Options currently are:

  • margin can specify the minimum number of spaces that appear in front of every line. You can even insert extra spaces in front of the lines while keeping the tabulation using this option.

  • trimVertical is a boolean parameter to remove the new line character from the sample’s start and end. It eliminates the leading and trailing empty lines.

  • verticalTrimOnly (can be aliased as vtrimOnly) instructs the macro to do only the vertical trimming. If this option is defined, there is no need to define trimVertical also.

The macro can also delete the empty lines from the start and the end of its input if the option trimVertical is set. For example

{#trimLines
{@options trimVertical}



  k
   a
   b
  c


}

will result

k
 a
 b
c

XIV. untab

The syntax of the macro is:

Jamal source
{@untab tabSize=8
multiple line of
    text with \t tab characters
}

This macro replaces the tab characters in the input with spaces. Each tab will be replaced with one or more spaces so that the alignment of the lines is preserved. The tab stop is 8 by default, but it can be set to any value using the options tabSize. This option has two aliases tab and size, which you can also use as macro parameters. However, only tabSize is recognized as a macro if defined outside or inside the untab. With that you can set the tab stop globally for all the snippets:

Jamal source
{@define :tabSize=8}
...

{@untab ... tabSize is defined as a global macro

  possible
    multiple lines
}

For example:

{@untab tabSize=8
.......|.......|.......|.......|
...\t... .   .\t.. \t.
}

will be converted to

.......|.......|.......|.......|
...     ... .   .       ..      .

XV. range

This macro can filter lines of its input by a range of numbers. The syntax of the macro is:

Jamal source
{@range lines=1..3,5..7;..1,0

lines
}

The macro has an alias ranges that you are free to use in case the plural form is more readable. The option lines cannot be used in singular, but it can also be aliased as range or ranges.

The format and the meaning of the option lines is exactly the same as the option of the same name in the core import macro.

The individual ranges can be separated by , or ;. The ranges are specified as a range of numbers separated by ... A one line range can be specified by a single number. The range start line number can be larger than the end line number. In that case that lines appear in reversed order from the start to the end. The lines are included in the order as the ranges specify. Using this option you can rearrange the order of the lines. When you want to specify a range lasting to the end of the lines you can write 13..inf or 5..infinity.

+ This option cannot be defined as macro.

XVI. base64 encode and decode

There are two macros that can encode and decode the input using the Base64 algorithm. The encoding macro is called base64. It also has an alias base64:encode.

The decoding macro is named base64:decode.

By default, the macros trim the input removing the spaces from the start and from the end. If you need to encode a string that contains spaces you can use the option quote. In this case, the string has to be quoted. Any character can be used as quoting character except space and the same character should be used at the start and at the end of the string. The option quote can be used with the decoding macro as well to provide coherent use, although there is not much use of the option in this case.

Jamal source
{@base64:encode This is a test}

will result in

output
VGhpcyBpcyBhIHRlc3Q=

and

Jamal source
{@base64:decode VGhpcyBpcyBhIHRlc3Q=}

will result in

output
This is a test

If you need a space before the text also encoded you should write

Jamal source
{@base64:encode (quote) " This is a test"}

resulting in

output
IFRoaXMgaXMgYSB0ZXN0

and again the reverse:

Jamal source
>>{@base64:decode (quote) "IFRoaXMgaXMgYSB0ZXN0"}<<

will result in

output
>> This is a test<<

As the >> shows that the space is also decoded.

Both then encoding and the decoding macro can have the option url. In this case, URL safe encoding is used. The encoding macro will also handle the option compress. Using this option, the string is first compressed before encoding.

Note
There is no option to decode the compressed string. This option was developed to support Kroki service GET requests.

With these options, you can encode the following string:

Jamal source
image::https://kroki.io/plantuml/svg/{@base64 (compress url)
skinparam ranksep 20
skinparam dpi 125
skinparam packageTitleAlignment left

rectangle "Main" {
(main.view)
(singleton)
}
rectangle "Base" {
(base.component)
(component)
(model)
}
rectangle "<b>main.ts</b>" as main_ts

(component) ..> (base.component)
main_ts ==> (main.view)
(main.view) --> (component)
(main.view) ...> (singleton)
(singleton) ---> (model)
}[]

which will result in

output
image::https://kroki.io/plantuml/svg/eNplTz0PgjAQ3fsrLkwwUJXEDUl0d3M3B5ykoS0N1-hg_O8WNFJ1e3fv495xr6zDEQ2MaHsmB8Va8GfZOgWbYhttHDY9dnRSXtNeq84ash40XbwQIzUebacJkiMqm8BdpCYAeVV0y0TKaiL9YDPxiMUHZJrFdQCyGYwbbEgNjhiboSX94yzraj7guVzVVQLIMM1nzyI2g5QV_KW_lbDbBTLuuWDI88B9tVg4OadGT0U4GCfnq_MTL7B2ww==[]
eNplTz0PgjAQ3fsrLkwwUJXEDUl0d3M3B5ykoS0N1 hg O8WNFJ1e3fv495xr6zDEQ2MaHsmB8Va8GfZOgWbYhttHDY9dnRSXtNeq84ash40XbwQIzUebacJkiMqm8BdpCYAeVV0y0TKaiL9YDPxiMUHZJrFdQCyGYwbbEgNjhiboSX94yzraj7guVzVVQLIMM1nzyI2g5QV KW lbDbBTLuuWDI88B9tVg4OadGT0U4GCfnq MTL7B2ww==
Figure 1. output

XVII. repeat

The macro repeat repeats the input string n times. The number of repetitions is given by the parop n, also aliased as times.

Jamal source
{@repeat (n=3)A}

will result in

output
AAA

The parop trim is optional, and if it is present, then the input string is trimmed before the repetition.

XVIII. rot13

This macro calculates the ROT13 transformation of the input. For example

Jamal source
{@rot13 ROT13 is a simple letter substitution cipher
that replaces a letter with the 13th letter after it in the alphabet.
ROT13 is a special case of the Caesar cipher which
was developed in ancient Rome.}

will result in

output
EBG13 vf n fvzcyr yrggre fhofgvghgvba pvcure
gung ercynprf n yrggre jvgu gur 13gu yrggre nsgre vg va gur nycunorg.
EBG13 vf n fcrpvny pnfr bs gur Pnrfne pvcure juvpu
jnf qrirybcrq va napvrag Ebzr.

Rot13 encoding is not a strong encryption algorithm. It can easily decrypt by the reader visiting the site http://rot13.com/ The main use is to provide some kind of obfuscation for the text that requires some effort to decipher. For example, your document asks a question to the reader, and you also give the answer in ROT13. The reader will not be able to read the answer, only when they put effort deciphering.

I usually use this macro to write a "Do not edit this file, it is generated" already rot13 encoded in the .jam file. This way the source file you edit will contain an unreadable rot13 encoded text, but the generated file contains the readable warning.

XIX. def

The macro def is a simple convenience macro that defines a user defined macro. The syntax of the macro is simplified:

Jamal source
{@def name=value}
{name}

and it will result

output
value
value
Note
The value appears twice. The macro @def also returns the value.

This macro can be used when there is a repeated text in the document. You can use this macro at the text’s first occurrence, and use the macro in consequent places. It is a convenience macro, which gives a name to a text.

In principle, you can get the same result using the core define macro. The differences are:

  • The def macro is shorter to write.

  • The def macro returns the value, there is no need to use the macro immediately after the definition.

  • The def macro does not have parameter options, and as such

    • it cannot throw an error or skip the definition if the macro is already defined, it will redefine the macro, and

    • it cannot define a macro to be pure, verbatim etc.

  • The def macro does not have parameters, thus the value is always a plain text.

  • You cannot define global macros using def and therefore the name must not contain the : character.

XX. urlencode

This macro will URL encode its input. The format of the macro is

Jamal source
http://my.precious.com/what?{@urlencode (charset=UTF-8) query=" this is a quoted string"}

which, in this example will result

output
http://my.precious.com/what?query%3D%22+this+is+a+quoted+string%22

The macro has one option parameter:

  • charset (alias cs) - the character set to use for encoding. The default is UTF-8.

XXI. pos

This macro will result the

  • file name,

  • line number, or

  • column number

of the actual location. The simple way of using the macro is, for example

Jamal source
{@pos.file}:{@pos.line}:{@pos.column}

will result

output
../jamal-snippet/README.adoc.jam:1012:30

The version introduced after 1.12.4 also handles options between ( and ):

  • top will instruct the macro to use the location no of the top level. It is the same as the current file if there were no imports or includes. This option cannot be used together with parent, all or up

  • parent will use the location of the include or import macro that was used to include or import the current file. This option cannot be used together with top, all or up

  • all list all the locations in the hierarchy from the current to the top level. The locations will be separated by a comma , or by the string specified in the option sep. This option cannot be used together with top, parent or up

  • format specifies the format of the location. The format can be any string, and the formatting escape sequences %f, %l and %l are placeholders for the name of the file, line and column. The default is %f:%l:%c. This is also changed when the .file, .line or .column ending is used in the macro. These cannot be used together with the format macro. They are the short forms for format=%f, format=%l, and format=%c. The format is also used with the option all.

  • up specifies the number of steps up in the hierarchy. up=0 is the default. up=1 is the same as parent. This option cannot be used together with top, parent or all

  • sep specifies the string used to concatenate the locations when the option all is used. The default value is a comma ,. This option must be used together with the option all.

The actual file name and line number may not be the one where the macro is in the file. It is where the macro pos is evaluated. This behaviour may result that the line number or column is not accurate when the macro is not evaluated from the top level context. It is also known that Jamal increases the line numbers in some cases extensively during maco evaluation. This is a known bug resulting in the line number larger than the actual.

XXII. sort

This macro returns the input sorted. The default behaviour is to sort the lines of the input alphabetically. For example

Jamal source
{@sort
beta
zeta
alpha}

will result the output

output
alpha
beta
zeta

To have more flexibility you can use parameters on the first line of the input to specify collating order, record separator and so on.

The options are:

  • separator specifies the separator regular expression, that separates the individual records. The default value if \n, which means the lines are the records.

  • join is the string to use to join the records together after the sorting was done. The default value is the \n string (not pattern); that means the records will be individual lines in the output.

  • locale, alias collatingOrder, alias collator can define the locale for the sorting. The default locale en-US.UTF-8. Any locale string can be used installed in the Java environment and passed to the method Locale.forLanguageTag(). When this option is used with the alias collator `, the value of the option has to be the fully qualified name of a class extending the `java.text.Collator abstract class. The class will be instantiated and used to sort the records. Using this option this way makes it possible to use special purpose collator, like the readily available javax0.jamal.snippet.SemVerCollator. This collator will sort the records treating the keys as software version numbers that follow the semantic versioning standard.

  • columns can specify the part of the textual record to be used as a sorting key. The format of the parameter is n..m where n is the first character position and m-1 is the last character position to be used. The values can run from 1 to the maximum number of characters. If you specify column values that run out of the line length, then the macro will result in an error.

  • pattern can specify a regular expression pattern to define the part of the line as a sort key. The expression may contain matching groups. In that case, the strings matching the parts between the parentheses are appended from left to right and used as a key. This option must not be used together with the option columns.

  • numeric will sort based on the numeric order of the keys. In this case, the keys must be numeric or else the conversion to BigDecimal before the sort will fail.

  • reverse do the sorting in reverse order.

The input is treated as a list of textual records separated by strings. The separator can be defined as a regular expression. The default value is \n, which means that the individual lines will be the records. An example, different from the default record separator:

Jamal source
{@sort separator=### join=### pattern="key=(.*)" numeric
key=1
this
is one record
###
key=03
This is the second
record, multiple lines
###
key=2
This gets into the middle
}

will result

output
key=1
this
is one record
###
key=2
This gets into the middle
###
key=03
This is the second
record, multiple lines

This sample uses a pattern to select the key, a non-default joining string, and it also specifies that the sorting has to be numeric.

The next example specifies the sorting when the collating order is specified. Here the collating order is Hungarian. .Jamal source

{@sort locale=HU
Cukor
Csiga
Császár
Czucor
Abrak
}

It will result the following order:

output
Abrak
Cukor
Czucor
Császár
Csiga

Note that the special character á is properly used as it follows the letter a in the Hungarian collation order. Also, the cs is a compound sound following the letter c in Hungarian and that way Czucor comes before Császár or Csiga, whatever they mean.

The next example uses semantic versioning.

Jamal source
{@sort collator=semver
1.0.0-alpha
1.0.0-alpha.beta
1.0.0-beta
1.0.0-beta.2
1.0.0-alpha.1
1.0.0-beta.11
1.0.0-rc.1
1.0.0
}

It will result the following ordering:

output
1.0.0-alpha
1.0.0-alpha.1
1.0.0-alpha.beta
1.0.0-beta
1.0.0-beta.2
1.0.0-beta.11
1.0.0-rc.1
1.0.0
Note
The sample above does not specify the fully qualified class name of the collator. The macro sort implements a little shortcut for the semantic versioning collator provided in the Jamal source code. If you write semver (case insensitive) instead of the fully qualified domain name, it will automatically use the javax0.jamal.snippet.SemVerCollator class as collator.

XXIII. numberLines

This macro can put numbers in front of the lines, sequentially numbering them.

The first line following the macro identifier until the end of the line may contain parameters. These parameters are parsed using the Standard Parameter Parsing as defined in PAROPS.

The syntax of the macro is

{@numberLines options
     ..
     ..
     ..
}

By default, the numbering of the lines starts with one, and every line gets the next number. For example

Jamal source
{@numberLines
this is the first line
this is the second line
this is the third line
}

will result

output
1. this is the first line
2. this is the second line
3. this is the third line

The macro will insert the number with a . (dot) after the number and space.

The parameters start, step, and format can define different start values, step values, and formats for the numbers. For example

Jamal source
{#numberLines start=3 step=2 format=" %03d::"
this is the first line
this is the second line
this is the third line
}

will result

output
003::this is the first line
005::this is the second line
007::this is the third line

The standard Java method String::format will format the number using the formatting string. Any illegal formatting will result in an error.

XXIV. killLines

This macro deletes, or keeps the selected lines from its input.

The first line following the macro identifier until the end of the line may contain parameters. These parameters are parsed using the Standard Parameter Parsing as defined in PAROPS.

The format of the macro is

{@killLines parameters

 ...

}

or

{@filterLines parameters

 ...

}
Note
We recommend using the filterLines and pattern aliases when you want to keep the lines matching the pattern and delete the other lines. In other cases the filterLines and kill or killLines and kill or pattern aliases can be used. The killLines and keep aliases together are not recommended.

The default behavior of the macro is to delete the empty lines. In that case, it removes all lines that contain only spaces or nothing at all.

The parameter macro pattern may define a regular expression to select the lines.

For example:

Jamal source
{#killLines pattern=^\s*//
/* this stays */
// this is some C code that we want to list without the
// single line comments

#define VERSION 1.0 //this line also stays put
int j = 15;
}

creates the output

output
/* this stays */

#define VERSION 1.0 //this line also stays put
int j = 15;

If the option keep is used then the lines that match the pattern are kept and the other lines are deleted.

Jamal source
{#killLines pattern=^\s*// keep
/* this stays */
// this is some C code that we want to list without the
// single line comments

#define VERSION 1.0 //this line also stays put
int j = 15;
}

creates the output

output
// this is some C code that we want to list without the
// single line comments

In this case only the comment lines remained that start with // at the start of the line.

XXV. skipLines

You can use this macro to skip lines from the snippet.

The first line following the macro identifier until the end of the line may contain parameters. These parameters are parsed using the Standard Parameter Parsing as defined in PAROPS.

It is similar to killLines but this macro deletes ranges of lines instead of individual lines. The macro uses two regular expressions, named skip and endSkip. When a line matches the line skip, the line and the following lines are deleted from the output until a matching endSkip. The macro also deletes the lines that match the regular expressions.

For example,

Jamal source
{@skipLines
this line is there
skip this line and all other lines until a line contains 'end skip' <- this one does not count
this line is skipped
this line is skipped again
there can be anything before 'end     skip' as the regular expression uses find() and not match()
there can be more lines
}

will result

output
this line is there
there can be more lines

You can also define the regular expressions defining the parameters skip and endSkip. For example,

Jamal source
{#skipLines {@define skip=jump\s+starts?\s+here}{@define endSkip=land\s+here}
this line is there
jump start here
this line is skipped
this line is skipped again
land                 here
there can be more lines
}

will result

output
this line is there
there can be more lines

It is not an error if there is no line matching the endSkip. In that case, the macro will remove all lines starting with a string matching the skip from the output. There can be multiple skip and endSkip lines. The skip and endSkip lines cannot be nested. When there is a match for a skip, any further skip is ignored until an endSkip.

XXVI. replace

The macro replace replaces strings with other strings in its input. The macro scans the input using the Standard Built-In Macro Argument Splitting.

It uses the first argument as the input, and then every following argument pairs as search and replace strings. For example:

Jamal source
{@replace /the apple has fallen off the tree/apple/pear/tree/bush}

will result:

output
the pear has fallen off the bush

If the parop regex is true, then the search string is treated as regular expressions, and the replace string may also contain replacement parts. For example,

Jamal source
{#replace {@options regex}/the apple has fallen off the tree/appl(.)/p$1ar/tree/bush}

will result in the same output

output
the pear has fallen off the bush

but this time, the replace used regular expression substitution.

The macro also supports the parop detectNoChange to error when some of the search strings are not found in the input. This parop can also be defined as an option. In that case you can switch it off using the option (detectNoChange=false) in the macro.

XXVII. replaceLines

This macro replaces strings in the input.

The first line following the macro identifier until the end of the line may contain parameters. These parameters are parsed using the Standard Parameter Parsing as defined in PAROPS.

It works similarly to the macro replace. The difference is that the replaceLine

  • always works with regular expressions, and

  • it works on the individual lines of the input in a loop.

The difference is significant when you want to match something line by line at the start or the end of the line. For example,

Jamal source
{@define replace=/^\s*\*\s+//}
{@replaceLines
* this can be a snippet content
* which was collected
* from a Java or C program comment
}

will result

output
this can be a snippet content
which was collected
from a Java or C program comment

The searched regular expressions and the replacement strings have to be defined in the parameter replace. This parameter can be defined inside the replaceLines macro. The macro scans the value of the parameter replace using the Standard Built-In Macro Argument Splitting.

It is usually an error when no lines are replaced in a snippet. Use the parameter`detectNoChange` to detect this. If this boolean parameter is true, the macro will error if it changes no line.

XXVIII. counter:define

This macro defines a counter. The counter can be used like a parameterless user-defined macro that returns the actual formatted value of the counter each time. The actual value of the counter is modified after each use. The format of the macro is

{@counter:define id=identifier}

Flat Counter

In this section, we will discuss the simpler form of the counter. This counter is a simple integer counter that is increased each time it is used. The hierarchical counter introduced in the release 2.4.0 is discussed in the next section.

The value of the counter starts with 1 by default and is increased by 1 each time the macro is used. For example,

Jamal source
{@counter:define id=c} {c} {c} {c}

will result

output
1 2 3

You can define the start, and the step value for the counter as well as the format. For example,

Jamal source
{#counter:define id=c start=2 step=17} {c} {c} {c}

will result

output
2 19 36

The format can contain the usual String.format format string. In addition to that, it can also contain one of the $latin, $LATIN, $greek, $GREEK, $cyrillic, $CYRILLIC, $roman, $ROMAN literals.

Note
You can also use the $alpha, $ALPHA placeholders instead of $latin, $LATIN. This is provided for backward compatibility with versions where the Greek and Cyrillic alphabets were not available.
  • $alpha will be replaced by a, b, c . . . for 1, 2, . . .

  • $ALPHA will be replaced by A, B, C . . . for 1, 2, . . .

  • $greek will be replaced by α, β, γ . . . for 1, 2, . . .

  • $GREEK will be replaced by Α, Β, Γ . . . for 1, 2, . . .

  • $cyrillic will be replaced by а, б, в . . . for 1, 2, . . .

  • $CYRILLIC will be replaced by А, Б, В . . . for 1, 2, . . .

  • $roman will be replaced by i, ii, iii . . . for 1, 2, . . .

  • $ROMAN will be replaced by I, II, III . . . for 1, 2, . . . counter values.

It is an error

  • if either $alpha or $ALPHA is used in the format, and the value is zero, negative, or larger than 26,

  • if either $greek or $GREEK is used in the format, and the value is zero, negative, or larger than 24,

  • if either $cyrillic or $CYRILLIC is used in the format, and the value is zero, negative, or larger than 32, or

  • if either $roman or $ROMAN is used in the format, and the value is zero, negative, or larger than 3999.

In some applications the number 4 is depicted as IIII instead of IV when using roman numerals. You can see such use typically on some clock faces. If you want to use this notation you can use the option IIII (all capital). The use of this option makes only sense when you also specify the $roman or $ROMAN in the format. The macro does not check this.

Examples:

Jamal source
{@counter:define id=h format=$roman IIII}{h} {h} {h} {h} {h} {h} {h} {h} {h} {h} {h}

will result in

output
i ii iii iiii v vi vii viii ix x xi
Jamal source
{#counter:define id=cFormatted{@define format=%03d.}}{cFormatted} {cFormatted} {cFormatted}
{#counter:define id=aFormatted{@define format=$alpha.}}{aFormatted} {aFormatted} {aFormatted}
{#counter:define id=AFormatted{@define format=$ALPHA.}}{AFormatted} {AFormatted} {AFormatted}
{#counter:define id=rFormatted{@define format=$ROMAN.}{@define start=3213}}{rFormatted} {rFormatted} {rFormatted}
{#counter:define id=RFormatted{@define format=$ROMAN.}{@define start=3213}}{RFormatted} {RFormatted} {RFormatted}

The output will be

output
001. 002. 003.
a. b. c.
A. B. C.
MMMCCXIII. MMMCCXIV. MMMCCXV.
MMMCCXIII. MMMCCXIV. MMMCCXV.

Sometimes you want to use the current value of the counter multiple times. It is possible to define a macro using the counter and then use the macro referring to the value. For example, the following code

Jamal source
{@counter:define id=c}{c} {#define second={c}}{second} {second} {c}

will result

output
1 2 2 3

The implemented counters provide a simplified approach for this.

Jamal source
{@counter:define id=c}{c} {c} {c last} {c}

will have the same output:

output
1 2 2 3

In this case we used the word last as an argument to the counter macro c, which instructs the macro to return the last value without an increment.

Sometimes you want to refer to the value of the counter much later when the counter was already increased multiple times. In that case you can still use the define as used above, but the macro counter also gives a shortcut to do that. If you write

Jamal source
{@counter:define id=m}{m} {m -> secondChapter} {m} {m} is still {secondChapter}

will result

output
1 2 3 4 is still 2

The user defined macro m, which is a counter interprets the argument and create a new user defined macro named secondChapter. The value of this user defined macro will be the same as the actual value of the counter.

Note

The - character before the > is a macro parameter separator, and the macro itself checks only the > character. It means that you can use .>, +> or /> or any other non-alphanumeric first character in front of the >. It is recommended to use the - or = for readability reasons.

Hierarchical counter

A hierarchical counter counts numbers in a hierarchical way. This is the usual way to number chapters and sections in a document. The advantage of this counter over using the built-in formatting of the markup you use is that you can have several independent counters in the same document.

To define a hierarchical counter you use the same counter:define macro with the key hierarchical.

Jamal source
{@counter:define id=h hierarchical}

The use of the counter is the similar to the flat counter. You simply have to use the name of the counter like a build in macro.

Jamal source
{h} {h} {h} {h}

resulting in:

output
1 2 3 4

The hierarchical nature comes to life when you open and close a hierarchy level. Opening a new level is done simply writing the word open as an argument to the counter macro. Similarly, closing a level is done by writing the word close.

Continuing the previous example:

Jamal source
{h open} {h} {h open} {h} {h open} {h} {h close} {h} {h close} {h} {h close} {h}

will result in:

output
4.1  4.1.1  4.1.1.1  4.1.2  4.2  5

Opening and closing are not the only difference and possibility of the hierarchical counter. A hierarchical counter can have a different format for each level in a single format string. The format string can contain all the formatting elements that the flat counter can use. However, in addition to that, it can also contain parts between { and }.

The syntax of these parts is {<level>:<format>}. The <level> is a number starting with 1. When the actual level of the counter is smaller than the <level> the format is ignored. When the actual level is equal or larger than the <level> the format is used. The <format> is the format string that is used for the counter. The format can also contain the placeholder $title or $TITLE that will be replaced by the title of the level as defined in the argument title (see later).

The format can be defined using the macro define, inherited from the counter:define or as an argument to the counter macro in this order. When using the macro define the name of the user defined macro is the name of the counter and appending $format to it.

To demonstrate all these possibilities, we use the following example:

Jamal source
{@counter:define id=k hierarchical format="%d{2:.$greek}{3:.$roman}"}\
format inherited: {k}{k open}
format inherited: {k}{k open}
format inherited: {k}
format inherited: {k}
format defined as parameter: {k format="%d{@ident {2:.%02d}{3:.%03d}} $title" title="This is the title"}
format defined in k$format: {@define [verbatim]k$format=$ROMAN{2:.$roman}{3:.$roman}}{k}

which will result in:

output
format inherited: 1
format inherited: 1.α
format inherited: 1.α.i
format inherited: 1.α.ii
format defined as parameter: 1.01.003 This is the title
format defined in k$format: I.i.iv
Note
When the format string is defined as a user defined macro, it is better a verbatim macro to avoid the macro interpretation of the parts between the { and } characters. When the macro start and stop strings are not { and } it is not so crucial.

The hierarchical counter can also have a title. The title is defined using the parameter title and can be used in the format string using the placeholder $title or $TITLE.

Similarly to flat counter, you can also refer to the last value of the counter using the argument last.

Jamal source
{k last}

will result in:

output
I.i.iv

You may want to refer to a counter value other than the last one. In this case, you can use a define macro to store the formatted value of the counter, or you can "save" the counter. Using the parameter save (also aliased as saveAs) with a string argument you clone a frozen version of the counter. For example:

Jamal source
{k save=z} {z} {z format="%d.{@ident {2:%d}.{3:%d}}"}

will result in:

output
I.i.v 1.α.v 1.1.5

The frozen version will retain its value and will not be incremented. On the other hand, it can be formatted.

Note
Counters flat or hierarchical are technically user defined macros, but they will not be saved or loaded by the macro references. If you need a forward reference to a counter, you should define a user defined macro via the macro define and use that to reference the counter.

XXIX. references

From time to time, there may be a need to have a reference to a part of the text that is defined later. Macros, by their nature, cannot be used before they are defined. You cannot reference a section or chapter number before the section or chapter is defined.

Although the macros could provide ways to overcome this limitation, these may be a subpar solution. For example, you could define the chapter titles and numbering at the start of the document and then reference these macros at the place where the chapters start. The problem with this approach is that it separates the content from the document part that it belongs to.

The ref and references macros give a different solution to this problem. They help save the values of selected macros to an external file. Rendering the document the second time the file is read back and the macros are defined.

Typically, you use the macro references at the start of the file to define the reference file. After that you can use the ref (or reference full name, but singular) to add specific macros to the set to be saved.

Jamal source
{@references file=sample.jrf}
{@ref a}
This is defined later, but can be used here: {a}
{@define a=The value of a}

will result in

output
This is defined later, but can be used here: The value of a

The macro references can have three parameters:

  • file can specify the file name to be used to save the references. The default file name is ref.jrf.

  • holder is the name of the macro that will hold the list of macros to be saved. The default name is xrefs.

    The holder is a special user defined macro that keeps track of the macros to be saved. At the end of the processing, the holder macro is used to know which macros are to be saved. The actual value of this holder macro is not interesting. The only important thing is that it is a user defined macro and as such, you should not use it for anything else.

    You can use this macro as an ordinary user defined macro if you are curious. It will return the names of the macros that are saved comma separated in alphabetic order.

  • comment or comments instructs the macro to save some comments into the reference file. These comments are ignored when the macros are loaded. To see these comments, use this parop and open the generated file in a text editor close to your heart.

You can separate the references of different documents even in the same directory. The other way around, you can also save multiple sets of references from a single document.

When using the macro ref you can specify the name of the holder macro. Following it by a > sign, you can specify the name of the holder macro, where you want the reference saved.

Jamal source
{@references file=sample.jrf holder="otherRefs"}
{@ref a > otherRefs}
This is defined later, but can be used here: {a}
{@define a=The value of a}

will result in

output
This is defined later, but can be used here: The value of a

If you do not specify the holder macro, the default holder macro, xrefs is used.

When using these macros, the rendering has to run twice. The macros loaded by reference will always have the value that they had at the end of the previous execution. It is your responsibility to write macros that are stable, having values independent of the previous execution. The macro also signals an error if the macros change between processing.

When the rendering runs the first time, or the reference file was deleted, the macros are not defined. To avoid errors the ? can be used in front of the macro names defined later. This will result in an empty string when the macro is used.

Another possibility is to use the ref macro before the first use of the referenced macro. This macro will define the macro with the literal text UNDEFINED if the macro is not defined.

XXX. reflow

This macro reflows the content.

The first line following the macro identifier until the end of the line may contain parameters. These parameters are parsed using the Standard Parameter Parsing as defined in PAROPS.

The default behavior is to remove all single new-line characters replacing them with spaces. That way, the lines will extend without wrapping around, and double newlines will separate the paragraphs.

For example:

Jamal source
{@reflow
The
short
lines
will
be put into a single line.

Empty lines are paragraph limiters.




Multiple empty lines are
converted to one.}

The output will be

output
The short lines will be put into a single line.

Empty lines are paragraph limiters.

Multiple empty lines are converted to one.

If the parameter width specifies a positive integer number, the macro will use it to limit the length of the lines. For example

Jamal source
{@reflow width=10
0123456789|
The
long
lines
will
be broken into words.

Empty lines are paragraph limiters.
}

The output will be

output
0123456789|
The long
lines will
be broken
into words.

Empty
lines are
paragraph
limiters.

The lines are collected and broken so that none of the lines is longer than 10. In some cases, limiting is not possible. When the width is too small but still positive, some words may be longer than the given width.

For example, setting the width to 1, reflow will cut the lines into words, but it will not break the individual words.

Jamal source
{@reflow width=1
0123456789|
The
long
lines
will
be broken into words.

Empty lines are paragraph limiters.
}

The output will be

output
0123456789|
The
long
lines
will
be
broken
into
words.

Empty
lines
are
paragraph
limiters.

The width parameter can be a macro option as well as a macro. For example

Jamal source
{#reflow {@define width=1}
0123456789|
The
long
lines
will
be broken into words.

Empty lines are paragraph limiters.
}

will have the same result as:

output
0123456789|
The
long
lines
will
be
broken
into
words.

Empty
lines
are
paragraph
limiters.

Setting the width to any non-positive value will remove the limit from the line length. You may use this to override a globally set width macro.

XXXI. snip:line

The macro snip:line results in the starting line number of the snippet in the file where the snippet is defined. For example, if the snippet hubbaba was collected from a file with the snippet hubbaba on line 5, the macro will return 6. The returned number counts the lines at the start of the file with one, and it is the line, which is the first line of the snippet following the snippet signaling line.

In this document, we use different snippets collected from the Java files of the snippet library. One of the snippets is named collect_options. It contains the lines that list the options for implementing the macro snip:collect.

The snippet collect_options is defined in the file Collect.java at the line 45.

You can find the previous sentence in the README.adoc.jam file as: .Jamal source

The snippet `collect_options` is defined
in the file {%#file (format=`$simpleName`) {%@snip:file collect_options%}%}
at the line {%@snip:line collect_options%}.

You can use this macro to refer to a source code line or if you want to include some source code into your documentation with the actual positions as line numbering. The syntax of the macro is

Jamal source
{@snip:line snippet_name}

The text following the snippet id is ignored, reserved for future development.

XXXII. snip:file

The macro snip:file returns the file’s name where the snippet is defined. The returned file name is a full absolute path. If you want to display only the name or the directory, you can use the macro file to format the output. It is recommended to use this macro together with the macro snip:line described above.

You can use this macro to refer to a source file. The syntax of the macro is

Jamal source
{@snip:file snippet_name}

The text following the snippet id is ignored, reserved for future development.

XXXIII. snip:eval

This macro will evaluate the content of a snippet as Jamal macro. The functionality is very close to the core macro eval but

  • it evaluates ONLY using Jamal

  • takes into account the position of the snippet.

When a snippet is evaluated using the core eval macro the text of the snippet is evaluated. It means that the evaluation has no idea where the text was coming from. If the text contains an include or import macro with a relative file name then the file name will be interpreter as relative to the Jamal file and not relative to the file where the snippet came from. This way, when you write the code of the sippet, you have to know where it will be evaluated.

To avoid this, the macro snip:eval should be used. This macro has three parops to specify the snippet location. These are:

  • snippet can specify the snippet to be evaluated. When this parameter is specified, the other parameters must not be.

  • file can specify the file name of the tex to be evaluated. It must be specified if the parop snippet is not.

  • line can specify the line number of the text to be evaluated. The default value is 1.

When the input of the macro (the text after the parameters, specified between ( and ) characters) is empty, then the macro will use the text of the snippet text. If the input contains text then that text will be evaluated and the snippet parameter will only be used for the location.

You may want to use the snippet to specify the location and a text when you want to evaluate the snippet after some transformation. For example, the snippet is part of some JavaDoc, and you want to remove the leading * characters from the lines. In that case yur snip:eval can reference the snippet by the name for the file and line number location, but then the actual text is the result of the transformation.

XXXIV. snip:transform

The snip:transform macro integrates the functionality of the macros

  • kill

  • skipLines

  • range or ranges

  • replaceLines

  • trimLines

  • reflow

  • numberLines

  • untab

It can kill/keep lines, skip, replace, trim, lines, select line ranges; it can reflow the lines, replace tabs with spaces, and it can number the lines.

The first line following the macro identifier until the end of the line may contain parameters. These parameters are parsed using the Standard Parameter Parsing as defined in PAROPS.

The macro implementation itself is calling the underlying other macros, so the functionality what and how it does the above actions are identical. The purpose of the macro is to provide a shorthand for the common use case of the other macros used together.

The macro is configured with parameters in a similar way as the underlying macros. The parameters are the same as in those macros, and they are used by the underlying macros the same way. There are two differences, however.

  1. The snip:transform macro does not use any user defined macro or option as a parameter. You cannot, for example, {@define replace=/foo/bar/} and hope that snip:transform will replace foo with bar. You have to use the parameter replace as a macro option.

  2. There is an extra parameter named action (alias actions, plural) that lists the actions to perform.

The names for the actions are the followings:

  • kill

  • skip

  • range or ranges

  • replace

  • trim

  • reflow

  • number

  • untab

If you have a block that you want simultaneously trim and then number the lines, you have to write

Jamal source
{@snip:transform actions=trim,number
         wuff
        line
     Mayak
            Canoe
}

which will result

output
1.     wuff
2.    line
3. Mayak
4.        Canoe

This is essentially the same as

Jamal source
{#numberLines
{@trimLines
         wuff
        line
     Mayak
            Canoe
}}

eventually with the same result:

output
1.     wuff
2.    line
3. Mayak
4.        Canoe

The snip:transform takes all the actions in the order as they are specified and invokes the macros implementing them passing the parameters. It is not possible to invoke one action more than once. Any syntax allowing the separation of the parameters of one execution from another would be complex and probably hard to read.

Some actions have parameters that are the same as the name of the action. These are skip, range, kill, trim and replace. When one of these parameters is specified there may not be a need to specify the action separately. It is evident that

Jamal source
{@snip:transform kill=A
Apple
Birnen
Birds
Sumatra
}

will delete all lines that contain the uppercase letter A and will result:

output
Birnen
Birds
Sumatra

On the other hand

Jamal source
{@snip:transform pattern=A
 ...
}

does not work, even though the parameter pattern is the alias of kill.

The following parameters will add the action implicitly to the list:

  • kill will add the action kill

  • keep will add the action kill

  • skip will add the action skip

  • lines, range or ranges will add the action range

  • replace will add the action replace

  • tab or tabSize will add the action untab

  • trim will add the action trim

If an action is already in the list (they are present in the parameter actions) they are not added again. Also, their position remains as specified in the parameter actions. When added, it is in the order as they are listed here in the documentation. The actual order of the parameters in the macro is irrelevant. When implicitly added kill will always precede skip and replace. Likewise skip will precede replace.

A parameter other than these belonging to an action not listed in the actions parameter is an error. You cannot, for example, specify width without adding reflow to the actions as it is not readable what the meaning of width is without reflow.

You will list the action in the actions parameter if you want to use it in a different place/order than the one it would get added implicitly. For example, you want to kill a few lines and also number the lines. If you do not list the action kill then it will be executed after the numbering. When the lines are deleted, the numbering will have gaps. It may be your intention, but usually it is not.

Note

The macro snip:transform is just a wrapper around the underlying macros. It first looks up the macros by the name and then invokes them according to the parameters. It is usually not interesting for the user of the macro. The underlying macros are implemented in the snippet package, just like snip:transform itself.

Jamal, however, makes it possible to redefine built-in macros locally and globally via the use macro. If any of the underlying macros are redefined when the snip:transform is invoked, then the actual macro will be invoked. It also implies that the developers should implement these macros as compatible as the macro of the same name in the snippet package. They have to implement the BlockConverter interface, and their convertTextBlock() method should accept the same parameters as their snippet counterpart.

The parameters for the snip:transform are:

  • action, (alias actions) listing the actions to perform.

  • kill, (alias pattern) passed to killLines

  • keep passed to killLines

  • format passed to NumberLines

  • start passed to NumberLines

  • step passed to NumberLines

  • width passed to reflow

  • replace passed to replaceLines

  • detectNoChange passed to replaceLines

  • skip passed to skipLines

  • endSkip passed to skipLines

  • margin passed to trimLines

  • trimVertical passed to trimLines

  • verticalTrimOnly passed to trimLines

  • tab or tabSize passed to untab. Note that the original untab parameter is not supported in the snip:transform macro. The reason for that is readability. While untab size may be acceptable, it is not clear what the meaning of size is in snip:transform.

  • lines (alias range, ranges) passed to range

The meaning and the interpretation of the parameters is the same as for the underlying macros and documented there.

XXXV. lineCount

This macro counts the lines in the content and returns the number of lines in decimal format.

Jamal source
{@lineCount
1
2
3}

results

output
3

XXXVI. listDir

This macro lists the files in a directory and then returns the list of the formatted files. The format of the macro is:

{@listDir (options) directory}

The parameter directory can be absolute or relative to the currently processed document. The options are

  • format specifying the format of the individual files

  • separator to specify the separator. The default is , (comma).

  • grep to specify a regular expression to filter the files based on their content. Only the files that contain a string that matches the grep pattern will be listed.

  • pattern to specify a regular expression to filter the files based on their name

  • maxDepth is the maximum depth of recursion into subdirectories. Specify 1 in case you do not want to recurse into subdirectories.

  • followSymlinks to follow symbolic links

  • countOnly (alias count) returns the number of the files as a string instead of the list of the file names.

The returned names of the files and directories are comma separated by default. This makes the use of the macro a good candidate to provide the list elements for a for loop. For example,

Jamal source
{#for macroJavaFile in ({@listDir (format=$simpleName) ./src/main/java/javax0/jamal/})=
- macroJavaFile}

will result

output
- jamal
- snippet
- JavaMatcherBuilderMacros.java
- Snip.java
- ReplaceUtil.java
- SnipSave.java
- DevRoot.java
- Sort.java
- Decorate.java
- Case.java
- NumberLines.java
- RangeMacro.java
- Untab.java
- Plural.java
- DateMacro.java
- Format.java
- tools
- ModifiersBuilder.java
- ReflectionTools.java
- Pluralizer.java
- MethodTool.java
- Def.java
- Update.java
- Eval.java
- SnipTransform.java
- Base64.java
- FilesMacro.java
- Collect.java
- SnipFile.java
- CounterFormatter.java
- Repeat.java
- HashCode.java
- TrimLines.java
- JavaSourceInsert.java
- ThinXml.java
- Decorator.java
- Memoize.java
- LineCount.java
- Replace.java
- SnipLine.java
- SnipCheckFailed.java
- SemVerCollator.java
- CounterHierarchical.java
- SnipXml.java
- ThinXmlMacro.java
- BlockConverter.java
- UrlEncode.java
- Reference.java
- Numbers.java
- Unicode.java
- Counter.java
- CompileJavaMacros.java
- StringMacros.java
- JavaSourceTemplate.java
- AbstractXmlDefine.java
- Dictionary.java
- Clear.java
- KillLines.java
- SnipLoad.java
- Rot13.java
- SnippetStore.java
- Download.java
- ListDir.java
- XmlFormat.java
- SnippetXmlReader.java
- XmlInsert.java
- SnipProperties.java
- Xml.java
- CounterMacro.java
- Variation.java
- SnipList.java
- ShellVar.java
- Locate.java
- XmlDocument.java
- References.java
- Java.java
- ReplaceLines.java
- SkipLines.java
- SnipCheck.java
- IdempotencyFailed.java
- Reflow.java
- Pos.java
- Snippet.java

The macro for is used with the # character, so the macro listDir is evaluated before executing the for. The listing of the files is recursive and is unlimited by default. The parameter maxDepth can limit the recursion. The same listing limited to 1 depth (non-recursive) is the following

Jamal source
{#for macroJavaFile in ({#listDir (format=$simpleName) ./src/main/java/javax0/jamal/
{@define maxDepth=1}})=
- macroJavaFile}

will result

output
- jamal
- snippet

The default formatting for the list of the files is the name of the file. The parameter format can define other formats. This format can contain placeholder, and these will be replaced with actual parameters of the files. When used in a multivariable for loop, then the format usually has the format

$placeholdes1|placeholder2| ... |placeholder3

This is because the | character is the default separator for the different values in a for macro loop.

The possible placeholders are:

  • $size will be replaced by the size of the file.

  • $time will be replaced by the modification time of the file.

  • $absolutePath will be replaced by the absolute path of the file.

  • $name will be replaced by the name of the file.

  • $simpleName will be replaced by the simple name of the file.

  • $isDirectory will be replaced by the string literal true if the file is a directory, false otherwise.

  • $isFile will be replaced by the string literal true if the file is a plain file, false otherwise.

  • $isHidden will be replaced by the string literal true if the file is hidden, false otherwise.

  • $canExecute will be replaced by the string literal true if the file can be executed, false otherwise.

  • $canRead will be replaced by the TIFT can be read, false otherwise.

  • $canWrite will be replaced by the string literal true if the file can be written, false otherwise.

For example,

Jamal source
{!#for (name,size) in ({#listDir ./src/main/java/javax0/jamal/
{@define format=$simpleName|$size}
})=
- name: {`@format /%,d/(int)size} bytes}

will result

output
- jamal: 96 bytes
- snippet: 2,528 bytes
- JavaMatcherBuilderMacros.java: 20,378 bytes
- Snip.java: 4,597 bytes
- ReplaceUtil.java: 1,033 bytes
- SnipSave.java: 4,691 bytes
- DevRoot.java: 2,280 bytes
- Sort.java: 8,606 bytes
- Decorate.java: 12,250 bytes
- Case.java: 2,269 bytes
- NumberLines.java: 2,853 bytes
- RangeMacro.java: 1,191 bytes
- Untab.java: 2,389 bytes
- Plural.java: 2,277 bytes
- DateMacro.java: 556 bytes
- Format.java: 930 bytes
- tools: 192 bytes
- ModifiersBuilder.java: 1,740 bytes
- ReflectionTools.java: 26,140 bytes
- Pluralizer.java: 1,561 bytes
- MethodTool.java: 7,761 bytes
- Def.java: 952 bytes
- Update.java: 979 bytes
- Eval.java: 2,009 bytes
- SnipTransform.java: 17,071 bytes
- Base64.java: 3,162 bytes
- FilesMacro.java: 11,636 bytes
- Collect.java: 23,894 bytes
- SnipFile.java: 680 bytes
- CounterFormatter.java: 4,599 bytes
- Repeat.java: 1,120 bytes
- HashCode.java: 462 bytes
- TrimLines.java: 5,345 bytes
- JavaSourceInsert.java: 8,984 bytes
- ThinXml.java: 8,792 bytes
- Decorator.java: 9,094 bytes
- Memoize.java: 8,760 bytes
- LineCount.java: 552 bytes
- Replace.java: 3,212 bytes
- SnipLine.java: 691 bytes
- SnipCheckFailed.java: 775 bytes
- SemVerCollator.java: 9,016 bytes
- CounterHierarchical.java: 6,611 bytes
- SnipXml.java: 537 bytes
- ThinXmlMacro.java: 603 bytes
- BlockConverter.java: 1,912 bytes
- UrlEncode.java: 855 bytes
- Reference.java: 1,373 bytes
- Numbers.java: 1,785 bytes
- Unicode.java: 2,114 bytes
- Counter.java: 2,166 bytes
- CompileJavaMacros.java: 23,483 bytes
- StringMacros.java: 15,620 bytes
- JavaSourceTemplate.java: 7,443 bytes
- AbstractXmlDefine.java: 1,484 bytes
- Dictionary.java: 1,527 bytes
- Clear.java: 399 bytes
- KillLines.java: 1,711 bytes
- SnipLoad.java: 3,482 bytes
- Rot13.java: 855 bytes
- SnippetStore.java: 11,428 bytes
- Download.java: 2,122 bytes
- ListDir.java: 5,423 bytes
- XmlFormat.java: 3,927 bytes
- SnippetXmlReader.java: 6,515 bytes
- XmlInsert.java: 6,162 bytes
- SnipProperties.java: 1,485 bytes
- Xml.java: 539 bytes
- CounterMacro.java: 3,885 bytes
- Variation.java: 6,518 bytes
- SnipList.java: 1,228 bytes
- ShellVar.java: 5,290 bytes
- Locate.java: 5,824 bytes
- XmlDocument.java: 6,076 bytes
- References.java: 9,702 bytes
- Java.java: 11,623 bytes
- ReplaceLines.java: 3,479 bytes
- SkipLines.java: 2,801 bytes
- SnipCheck.java: 7,556 bytes
- IdempotencyFailed.java: 1,816 bytes
- Reflow.java: 2,094 bytes
- Pos.java: 5,016 bytes
- Snippet.java: 1,988 bytes

If the option followSymlinks is used, like in

{@options followSymlinks}

then the recursive collection process for collecting the files will follow symlinks.

The separator character between the formatted items is a comma by default. The option separator or its alias sep can modify it. For example the example:

Jamal source
{#listDir (format=$simpleName maxDepth=1 sep=*) ./src/main/java/javax0/jamal/}

will result

output
jamal*snippet

XXXVII. xmlFormat

The macro xmlFormat interprets the input as an XML document if there is any, resulting in the formatted document. If the input is empty or contains only spaces, it registers a post-processor that runs after the Jamal processing and formats the final output as XML. For example,

Jamal source
{#xmlFormat
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion><name>jamal snippet</name><packaging>jar</packaging>
<groupId>com.javax0.jamal</groupId><artifactId>jamal-snippet</artifactId><version>2.8.2-SNAPSHOT</version>
</project>
}

will result

output
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <name>jamal snippet</name>
    <packaging>jar</packaging>
    <groupId>com.javax0.jamal</groupId>
    <artifactId>jamal-snippet</artifactId>
    <version>2.8.2-SNAPSHOT</version>
</project>

The default tabulation size is four. You can alter it by defining the parameter tabsize. For example,

Jamal source
{#xmlFormat
<?xml version="1.0" encoding="UTF-8" standalone="no"?>{@define tabsize=0}
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion><name>jamal snippet</name><packaging>jar</packaging>
<groupId>com.javax0.jamal</groupId><artifactId>jamal-snippet</artifactId><version>2.8.2-SNAPSHOT</version>
</project>
}

will result

output
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<name>jamal snippet</name>
<packaging>jar</packaging>
<groupId>com.javax0.jamal</groupId>
<artifactId>jamal-snippet</artifactId>
<version>2.8.2-SNAPSHOT</version>
</project>

As you can see, there is no tabulation in this case.

There is another use of the macro xmlFormat. If you do not include any XML or anything else into the macro as input, the macro will treat this as a command to format the whole output. It registers itself into the processor, and when the processing is finished, this registered call-back starts. At that point, it will format the output of the processing. That way, you can easily format a whole processed file.

The previous example that we used before can also be formulated this way.

Jamal source
{#xmlFormat}
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion><name>jamal snippet</name><packaging>jar</packaging>
<groupId>com.javax0.jamal</groupId><artifactId>jamal-snippet</artifactId><version>2.8.2-SNAPSHOT</version>
</project>

Note that the macro invocation {#xmlFormat} in this case can be anywhere in the input. The formatting will take place postponed when the processing is finished. It will result in the same output as before:

output
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <name>jamal snippet</name>
    <packaging>jar</packaging>
    <groupId>com.javax0.jamal</groupId>
    <artifactId>jamal-snippet</artifactId>
    <version>2.8.2-SNAPSHOT</version>
</project>

The macro can also convert thin XML to normal XML. Thin XML is the same as normal XML, but the tags start without the < character, and the closing tag is omitted. The actual place of the closing tag is determined by the position of the opening tag. Whenever a tag line (containing a > character after a tag name) or a text line start on a column smaller than the tag tabulation the tag is closed.

To use the thin XML format the parameter thin must be specified.

For example:

Jamal source
{#xmlFormat (thin)
project>
  modelVersion>4.0.0
  name>jamal snippet
  packaging>jar
  groupId>com.javax0.jamal
  artifactId>jamal-snippet
  version>2.8.2-SNAPSHOT
  parent>
    groupId>com.javax0.jamal
    artifactId>jamal-parent
    version>1.10.3-SNAPSHOT
    relativePath>../jamal-parent
}

will result

output
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project>
    <modelVersion>4.0.0</modelVersion>
    <name>jamal snippet</name>
    <packaging>jar</packaging>
    <groupId>com.javax0.jamal</groupId>
    <artifactId>jamal-snippet</artifactId>
    <version>2.8.2-SNAPSHOT</version>
    <parent>
        <groupId>com.javax0.jamal</groupId>
        <artifactId>jamal-parent</artifactId>
        <version>1.10.3-SNAPSHOT</version>
        <relativePath>../jamal-parent</relativePath>
    </parent>
</project>

For more examples and detailed explanation of the thin XML format, see the Thin XML Format page.

XXXVIII. thinXml

This macro converts thin XML to normal XML. Although the macro xmlFormat can also do the conversion it works only for full XML files and not for XML fragments. If the output is not a full XML then the macro xmlFormat may fail. This macro does only the conversion and does not format the XML or check in any way that the output os correct. It is useful when you want to convert a thin XML fraction to normal XML.

For example:

Jamal source
{#thinXml
  modelVersion>4.0.0
  name>jamal snippet
  packaging>jar
  groupId>com.javax0.jamal
  artifactId>jamal-snippet
  version>2.8.2-SNAPSHOT
  parent>
    groupId>com.javax0.jamal
    artifactId>jamal-parent
    version>1.10.3-SNAPSHOT
    relativePath>../jamal-parent
}

will result

output
<modelVersion>4.0.0</modelVersion>
<name>jamal snippet</name>
<packaging>jar</packaging>
<groupId>com.javax0.jamal</groupId>
<artifactId>jamal-snippet</artifactId>
<version>2.8.2-SNAPSHOT</version>
<parent>
    <groupId>com.javax0.jamal</groupId>
    <artifactId>jamal-parent</artifactId>
    <version>1.10.3-SNAPSHOT</version>
    <relativePath>../jamal-parent</relativePath>
</parent>

As you can see in this example the output is not a full XML file. It is not formatted also and there is no leading <?xml version="1.0" encoding="UTF-8" standalone="no"?> line.

For more examples and detailed explanation of the thin XML format, see the Thin XML Format page.

XXXIX. date

This macro will return the current date formatted using Java SimpleDateFormat. The format string is the input of the macro.

Example

Jamal source
{@date yyyy-MM-dd HH:mm:ss}

will result in the output

output
2024-10-29 11:46:59

XL. format

You can use the macro format to format the arguments. The macro scans the input using the Standard Built-In Macro Argument Splitting.

The first argument will be interpreted as the format string. The rest of the arguments will be used as the values for the formatting. By the nature of Jamal, all these arguments are strings. Since the parameters to the underlying String::format method are not only strings, they can be converted. If any of the parameters starts with a (xxx) string, then the string will be converted to the type`xxx` before passing to String::format as an argument. This format is similar to the cast syntax of Java and C.

The xxx can be

  • int, the conversion will call Integer::parseInt.

  • long, the conversion will call Long::parseLong.

  • double, the conversion will call Double::parseDouble.

  • float, the conversion will call Float::parseFloat.

  • boolean, the conversion will call Boolean::parseBoolean.

  • short, the conversion will call Short::parseShort.

  • byte, the conversion will call Byte::parseByte.

  • char, the conversion will fetch the first character of the parameter.

Examples:

Jamal source
{@define LONG=5564444443455587466}
{@format /%,016d/(int)  587466}
{#format /%x/(long){LONG}}}
{@format /%,016.4f/(double)587466}
{@format /%e/(double)587466}
{@format /%e is %s/(double)587466/5.874660e+05}
{#format /hashCode(0x%x)=0x%h/(long){LONG}/(long){LONG}}

will result in the output

output
000000000587,466
4d38e0bd5891048a}
0000587,466.0000
5.874660e+05
5.874660e+05 is 5.874660e+05
hashCode(0x4d38e0bd5891048a)=0x15a9e437

XLI. numbers

The macro numbers can create a comma separated list of numbers. The numbers can be specified with a start, end and step value. The default values are start=0, and step=1. The end value is mandatory.

The first number is the start value inclusive, and the counting ends with the end value exclusive. You can specify the values with the option keys

  • start, or from

  • end, or to

  • step, by, or increment.

Examples:

Jamal source
{@numbers end=5}
{@numbers start=1 end=5}
{@numbers start=1 end=5 step=2}

will result

output
0,1,2,3,4
1,2,3,4
1,3

XLII. unicode

This macro can insert unicode characters into the output. The unicode character can be specified by its code point. The code point can be specified in decimal or hexadecimal. The code point can be specified with the prefix &#x or &#. The prefix &#x is the default.

Examples:

Jamal source
0x{@unicode &#x43}\
{@unicode &#x41}\
{@unicode &#x46}\
{@unicode &#x45}\
{@unicode &#x42}\
{@unicode &#x41}\
{@unicode &#x42}\
{@unicode &#x45}

will result

output
0xCAFEBABE

The macro is also supported by a resource file named tab.jim. You can import this file as {@import res:tab.jim}. The constants defined in this file are:

This file defines unicode control characters as macros and some extra character also as macros. Originally this file contained only the macro 'tab' and the content was a TAB character. Most editors replace TAB characters and sometimes you need the tab character, for example when you generate a Makefile.

In the original version this file was edited using vim to preserve the tab and then the macro could be used in the Jamal source wherever the TAB was needed. With the introduction of the macro 'unicode' the maintenance of this file became simpler, and it also includes a lot of other control and other characters. The name of the file, however, remained tab.jim for history and compatibility reasons.

NUL SOH start of header, STX start of text, ETX end of text, EOT end of transmission, ENQ enquiry, ACK acknowledge, BEL bell, BS backspace, HT horizontal tab, LF line feed, VT vertical tab, FF form feed, CR carriage return, SO shift out, SI shift in, DLE data link escape, DC1 device control 1, DC2 device control 2, DC3 device control 3, DC4 device control 4, NAK negative acknowledge, SYN synchronous idle, ETB end of transmission block, CAN cancel, EM end of medium, SUB substitute, ESC escape, FS file separator, GS group separator, RS record separator, US unit separator, DEL delete,

SP space, NBSP non breaking space, ZWSP zero width space,

BULLET(•)

TM(™) TRADEMARK(™) COPYRIGHT(©) REGISTERED(®) DEGREE(°) PLUSMINUS(±) MULTIPLY(×) DIVIDE(÷) APPROX(≈) NEQ(≠) LEQ(≤) GEQ(≥) INFINITY(∞) NOT(¬) AND(∧) OR(∨) IMPLIES(⇒) EQUIV(⇔) FORALL(∀) EXISTS(∃) PARTIAL(∂) EMPTYSET(∅) NABLA(∇) IN(∈) NOTIN(∉) SUBSET(⊂) SUBSETEQ(⊆) SUPSET(⊃) SUPSETEQ(⊇) UNION(∪) INTERSECTION(∩) PROD(∏) SUM(∑) INTEGRAL(∫) SQRT(√) PERP(⊥) MID(∣) ANGLE(∠) DIAMOND(♦) LOZENGE(◊) CLUB(♣) DIAMOND(♦) HEART(ϖ)

These are symbolic names for some non-printable characters and for some commonly used symbols.

XLIII. case:lower, case:upper, …​

There are a few character-case changing macros implemented in the snippet package. These are:

  • case:lower changes the characters in the input to lower case letters

  • case:upper changes the characters in the input to upper case letters

  • case:cap changes the first character in the input to upper case letter

  • case:decap changes the first character in the input to lower case letter

XLIV. file:locate

This macro finds a file in a directory tree.

To find a file or directory, the macro has the following parops:

  • in (string) specifies the directory where the search for the file starts. This can be an absolute file name, or relative to the file that contains the macro.

  • depth (number) is the maximum depth of directories to search. The default value is infinity.

  • find (string) can specify the regular expression pattern. The pattern should match part of the file name to find a file. This is the opposite of match, which tries to match the entire file name. For the comparison the full path or only the file name is used controlled by the parop fullPath.

  • match (string) can specify the regular expression pattern. The pattern should match the whole file name to find a file. For the comparison the full path or only the file name is used controlled by the parop fullPath.

  • isFile (boolean) specifies that only files should be found. The default is to find both files and directories.

  • isDir, or isDirectory (boolean) specifies that only directories should be found. The default is to find both files and directories. You cannot use isFile and isDir at the same time.

  • fullPath (boolean) specifies that the full path has to be compared with the pattern. The default is to compare only the file name.

  • format (string) specifies the format of the result. This format is the same as the format of the file and directory macros.

  • dateFormat (string) specifies the format of the date and time. This format is the same as the format of the file and directory macros.

  • relativeTo (string) specifies the directory that is used to make the path relative. This option works the same way as in the macros file and directory.

Examples:

The simplest example is to find the file, where this documentation is stored:

Jamal source
{@file:locate find="lo.*.adoc.jam$"}

will result in

output
locate.adoc.jam

Finding the README.adoc file in the module directory:

Jamal source
{@file:locate in="../../" format=$absolutePath find="READ..\\.adoc$"}

will result in

output
/Users/verhasp/github/jamal/jamal-snippet/../jamal-snippet/README.adoc

XLV. dev:root

This macro finds the root directory of a development project.

To locate the root directory of a development project, the macro attempts to identify specific files or directories typically found only in the root directory of such projects. These include files or directories such as .git and .mvn. The search initiates from the directory of the Jamal source file and proceeds upwards. If none of these files can be located, indicating that the project’s root directory cannot be determined, a syntax error is signaled. The value of the macro is the absolute path to the project root directory.

To find the development root, the macro has the following parops:

  • placeholders can list the files that may work as a placeholder. Use this option if your project has something in the root directory, which is not in the default list. If this option is used, the default file names are not used, only those listed here. The option is multivalued, and each value can contain multiple file names comma separated.

    The default list is the following:

    • ROOT.dir

    • .git

    • .mvn

    • package.json

    • requirements.txt

    • Pipfile

    • Gemfile

    • Cargo.toml

    • CMakeLists.txt

    • .sln

    • go.mod

    • .travis.yml

    • .gitlab-ci.yml

    • azure-pipelines.yml

    As you can see, the list is quite extensive. It is recommended to place a ROOT.dir file in your root directory as a last resort and use this option if even that is not a good solution for your needs.

  • format (string) specifies the format of the result. This format is the same as the format of the file and directory macros. The default value is $absolutePath.

  • dateFormat (string) specifies the format of the date and time. This format is the same as the format of the file and directory macros. This is only needed if you want to have the date and time of the root directory as the macro result.

  • relativeTo (string) specifies the directory that is used to make the path relative. This option works the same way as in the macros file and directory.

XLVI. file

You can use this macro to include the name of a file in the documentation. First, it seems counterintuitive to have a macro for that. You can type in the name of the file, and it will appear in the output.

The real added values of the macro are that

  • it checks that the file exists, and

  • it can format the name of the file.

The macro will error if the file does not exist or is not a file. It helps the maintenance of the documentation. If the file gets renamed, moved, or deleted, the document will not compile until you follow the change.

The macro can also format the name of the file. It uses the value of the user-defined macro fileFormat for the purpose. If this macro is defined outside the file macro, then the file names will be formatted using the same format. For example, you can write

Jamal source
When Jamal processes this file it will generate {@file (format=`$name`)README.adoc}.

will result

output
When Jamal processes this file it will generate `README.adoc`.

The macro file has the following parameters:

  • format can define the format of the name of the file. In the format you can use placeholders starting with $. For the list of the placeholders see the documentation below.

  • root can specify the directory where the file is. This parameter is prepended in front of the file name. The default value is an empty string.

  • dateFormat can specify the format to display the date and time when the format uses one of the time placeholders. The default value is yyyy-MM-dd HH:mm:ss.

  • relativeTo can specify a file or directory to which the output should be relative to when the placeholder $relativePath is used in the format. The default value is the directory where the top level file is.

Note

The formatting $relativePath is useful when you have a Jamal file which is a document in its own, and it is also included in another document. Having a link in the file should be relative to the document on the top level, including directly or through other files the one containing the link. The placeholder $relativePath will generate a relative file name, which is relative to the actual top level document.

In the format you can use the following placeholders:

  • $name gives the name of the file as was specified on the macro

  • $absolutePath the absolute path to the file

  • $relativePath the relative path to the file

  • $parent the parent directory where the file is

  • $simpleName the name of the file without the path

  • $canonicalPath the canonical path

  • $bareNaked the file name without the extensions

  • $naked1 the file name without the last extension

  • $naked2 the file name without the last 2 extensions

  • $naked3 the file name without the last 3 extensions

  • $naked4 the file name without the last 4 extensions

  • $naked5 the file name without the last 5 extensions

  • $extensions the file name extensions

  • $extension1 the file name last extension

  • $extension2 the file name last 2 extensions

  • $extension3 the file name last 3 extensions

  • $extension4 the file name last 4 extensions

  • $extension5 the file name last 5 extensions

  • $time the time of the last modification

  • $ctime the creation time

  • $atime the last access time

When the placeholder $time, $ctime, or $atime is used the time will be formatted as yyyy-MM-dd HH:mm:ss by default. This format can also be changed by defining the macro/option dateFormat.

The macro also supports the option root. This can be used to define the root directory for the files' location. The usual use is to define root as a global macro and all file macros should use file names relative to that following the definition. This may save some time when you are referring to many files in a deep directory structure relative to the document file.

XLVII. directory

You can use this macro to include the name of a directory in the documentation. First, it seems counterintuitive to have a macro for that. You can type in the name of the directory, and it will appear in the output.

The real added values of the macro are that

  • it checks that the directory exists, and

  • it can format the name of the directory.

The macro will error if the directory does not exist or is not a directory. It helps the maintenance of the documentation. If the directory gets renamed, moved, or deleted, the document will not compile until you follow the change.

The macro can also format the name of the directory. It uses the value of the user-defined macro directoryFormat for the purpose. If this macro is defined outside the directory macro, then the directory names will be formatted using the same format. For example, you can write

Jamal source
This file is in the directory {@define directoryFormat=`$name`}{@directory ../jamal-snippet}.

will result

output
This file is in the directory `../jamal-snippet`.

The macro directory has the following parameters:

  • format can define the format of the name of the directory. In the format you can use placeholders starting with $. For the list of the placeholders see the documentation below.

  • root can specify the directory where the directory is. This parameter is prepended in front of the directory name. The default value is an empty string.

  • dateFormat can specify the format to display the date and time when the format uses one of the time placeholders. The default value is yyyy-MM-dd HH:mm:ss.

  • relativeTo can specify a file or directory to which the output should be relative to when the placeholder $relativePath is used in the format. The default value is the directory where the top level file is.

Note

The formatting $relativePath is useful when you have a Jamal file which is a document in its own, and it is also included in another document. Having a link in the file should be relative to the document on the top level, including directly or through other files the one containing the link. The placeholder $relativePath will generate a relative file name, which is relative to the actual top level document.

In the format you can use the following placeholders:

  • $name gives the name of the directory as was specified on the macro

  • $absolutePath the absolute path to the directory

  • $relativePath the relative path to the directory

  • $parent the parent directory where the directory is

  • $simpleName the name of the directory without the path

  • $canonicalPath the canonical path

  • $bareNaked the directory name without the extensions

  • $naked1 the directory name without the last extension

  • $naked2 the directory name without the last 2 extensions

  • $naked3 the directory name without the last 3 extensions

  • $naked4 the directory name without the last 4 extensions

  • $naked5 the directory name without the last 5 extensions

  • $extensions the directory name extensions

  • $extension1 the directory name last extension

  • $extension2 the directory name last 2 extensions

  • $extension3 the directory name last 3 extensions

  • $extension4 the directory name last 4 extensions

  • $extension5 the directory name last 5 extensions

  • $time the time of the last modification

  • $ctime the creation time

  • $atime the last access time

When the placeholder $time, $ctime, or $atime is used the time will be formatted as yyyy-MM-dd HH:mm:ss by default. This format can also be changed by defining the macro/option dateFormat.

The macro also supports the option root. This can be used to define the root directory for the directorys' location. The usual use is to define root as a global macro and all directory macros should use file names relative to that following the definition. This may save some time when you are referring to many files in a deep directory structure relative to the document file.

XLVIII. Java Macros

java:class

The macro java:class checks that the parameter is a valid Java class and found on the classpath. It is an error if the class is not on the classpath. This macro is useful when you document Java source code and run the Jamal conversion from a unit test. In this case, the macro will see the test and main classes. It can check that the class mentioned in the documentation is still there; it was not deleted or renamed.

When you run the conversion outside the code of the documented program, the class is not on the classpath. You can use the java:sources macro to include the source code as well as the compiled byte codes in the documentation to overcome this limitation. It is typically the case when you use IntelliJ and Asciidoctor plugin.

Note that the Java compiler may not be available in the JRE executing the IntelliJ editor. In this case, the macro will find the class if you also specify the compiled classes using the parameter classes to the macro java:sources.

The output of the macro is the class formatted. The formatting is the simple name of the class by default. The formatting can be defined by the parameter classFormat. For example:

Jamal source
The class that implements the macro `java:class` is
{@java:class javax0.jamal.snippet.Java$ClassMacro}.

will result in the output

output
The class that implements the macro `java:class` is
ClassMacro.

The format string can be any string with $ prefixed placeholders. The placeholders that the macro handles are:

  • $simpleName will be replaced by the result of calling simpleName()

  • $name will be replaced by the result of calling name()

  • $canonicalName will be replaced by the result of calling canonicalName()

  • $packageName will be replaced by the result of calling packageName()

  • $typeName will be replaced by the result of calling typeName()

For example

Jamal source
The class that implements the macro `java:class` is
{@define classFormat=$canonicalName}\
{@java:class javax0.jamal.snippet.Java$ClassMacro} with the canonical name, and
{@define classFormat=$name}\
{@java:class javax0.jamal.snippet.Java$ClassMacro} with the "normal" name.
It is in the package {#java:class javax0.jamal.snippet.Java$ClassMacro {@define classFormat=$packageName}}
{@java:class javax0.jamal.snippet.Java$ClassMacro} is still the "normal" name,
format defined inside the macro is local.
{@java:class (format="$simpleName") javax0.jamal.snippet.Java$ClassMacro} is the simple name.

will result in the output

output
The class that implements the macro `java:class` is
javax0.jamal.snippet.Java.ClassMacro with the canonical name, and
javax0.jamal.snippet.Java$ClassMacro with the "normal" name.
It is in the package javax0.jamal.snippet
javax0.jamal.snippet.Java$ClassMacro is still the "normal" name,
format defined inside the macro is local.
ClassMacro is the simple name.

It is not recommended to overuse the format string. Do not include verbatim text into the format string. Choose a format string you want to refer to the classes and use it globally in the document.

java:field

The macro java:field checks that the parameter is a valid Java field and found on the classpath. It is an error if the class is not on the classpath. This macro is useful when you document Java source code and run the Jamal conversion from a unit test. In this case, the macro will see the test and main classes. It can check that the field mentioned in the documentation is still there; it was not deleted or renamed.

When you run the conversion outside the code of the documented program, the field is not on the classpath. You can use the java:sources macro to include the source code as well as the compiled byte codes in the documentation to overcome this limitation. It is typically the case when you use IntelliJ and Asciidoctor plugin.

Note that the Java compiler may not be available in the JRE executing the IntelliJ editor. In this case, the macro will find the class if you also specify the compiled classes using the parameter classes to the macro java:sources.

In addition to that the value of the value of the field can also be used in the formatting in case the field is both static and final. .Jamal source

{@define field=javax0.jamal.api.SpecialCharacters#PRE_EVALUATE}
The field {#java:field (format="$name") {field}}
defined in the class {#java:field (format="$className") {field}}
is both `static` and `final` and has the value {#java:field (format="$value") {field}}

will result in the output

output
The field PRE_EVALUATE
defined in the class javax0.jamal.api.SpecialCharacters
is both `static` and `final` and has the value #

The format string can be any string with $ prefixed placeholders. The placeholders that the macro handles are:

  • $name will be replaced by the name of the field

  • $classSimpleName will be replaced by the simple name of the fields' defining class

  • $className will be replaced by the name of the of the fields' defining class

  • $classCanonicalName will be replaced by the canonical name of the fields' defining class

  • $classTypeName will be replaced by the type name of the fields' defining class

  • $packageName will be replaced by the package where the method is

  • $typeClass will be replaced by the type of the field

  • $modifiers will be replaced by the modifier list of the method

  • $value will be replaced by the value of the field in case the field is both static and final

java:method

The macro java:method checks that the parameter is a valid Java method and found on the classpath. It is an error if the class is not on the classpath. This macro is useful when you document Java source code and run the Jamal conversion from a unit test. In this case, the macro will see the test and main classes. It can check that the method mentioned in the documentation is still there; it was not deleted or renamed.

When you run the conversion outside the code of the documented program, the method is not on the classpath. You can use the java:sources macro to include the source code as well as the compiled byte codes in the documentation to overcome this limitation. It is typically the case when you use IntelliJ and Asciidoctor plugin.

Note that the Java compiler may not be available in the JRE executing the IntelliJ editor. In this case, the macro will find the class if you also specify the compiled classes using the parameter classes to the macro java:sources.

The output of the macro is the method formatted. The formatting is the name of the method by default. The formatting can be defined by the parameter methodFormat. For example:

Jamal source
{@define method=/javax0.jamal.snippet.Java$MethodMacro/evaluate}\
{#java:method {method}}

will result in the output

output
evaluate

The macro can have two arguments, using the Standard Built-In Macro Argument Splitting or one, specifying the full name of the method. The method’s full name is the class’s full name, and the method name separated by either a # character or by :: characters.

The format string can be any string with $ prefixed placeholders. The placeholders that the macro handles are:

  • $classSimpleName will be replaced by the simple name of the method’s defining class

  • $className will be replaced by the name of the of the method’s defining class

  • $classCanonicalName will be replaced by the canonical name of the method’s defining class

  • $classTypeName will be replaced by the type name of the method’s defining class

  • $packageName will be replaced by the package where the method is

  • $name will be replaced by the name of the method

  • $typeClass will be replaced by the return type of the method

  • $exceptions will be replaced by the comma separated values of the exception types the method throws

  • $parameterTypes will be replaced by the comma separated parameter types

  • $parameterCount will be replaced by the number of the parameters in decimal format

  • $modifiers will be replaced by the modifier list of the method

These formats can be used in your macros directly or using the macros defined in the jim file res:snippet.jim. For example,

Jamal source
The class that implements the macro `java:method` is '{#java:method {method}{@define methodFormat=$name}}()',
but it is simpler to import the jim file included in the snippet library

            {@import res:snippet.jim}\

and use the user defined macros, like the following:

{java:method:modifiers |{method}}\
{java:method:classSimpleName |{method}}\
::{java:method:name |{method}}({java:method:parameterTypes:simpleName |{method}})

will result in the output

output
The class that implements the macro `java:method` is 'evaluate()',
but it is simpler to import the jim file included in the snippet library


and use the user defined macros, like the following:

publicMethodMacro::evaluate(Input,Processor)

java:sources

This macro will read Java sources, compile them and make them available for the other macros. Think of this macro as a location declaration of the Java files documented. You can specify the location of the source code as well as the location of the compiled class files. To do that, the macro has four parameters:

  • src is the location of the source files. It is also aliased as source and sources.

  • class is the location of the compiled class files. It is also aliased as classes.

  • store can specify the name of the macro holding the loaded classes. See details below.

  • options aliased as compilerOptions can specify the options passed to the Java compiler. You can use this parameter to pass options like -cp or -classpath to the compiler.

Since the macro does not use its input, the parameters can be specified in the macro call without enclosing them between parentheses.

The macro will first try to compile the source files. It will try to load the classes from the class files if the compilation fails for any reason.

Jamal may be running with a JDK that provides a Java compiler instance for Java. This is the case when Jamal is executed from the command line, from a Maven extension, etc. Executing Jamal inside IntelliJ as an Asciidoctor plugin preprocessor may not provide a Java compiler. The compilation may also fail when there are external dependencies needed for the compilation.

If there is no source location defined or there is no compiler available, then the macro will load the already compiled classes. In other cases, the macro will read the source files, compile them and load the classes for the macros to use.

Note
Compiling the source code may need significant resource. You may consider omitting the src parameter if you use the macros only for class, method and field names formatting. On the other hand, when you use the macros for code generation, you may stick to the source code. (See also the java:insert macro.) In those situations, the compiled code, before the code generation, may be out of date or even unavailable when Jamal is executed as part of the compilation.

When the classes are loaded form the compiled class files, it may happen that not all classes are loaded. For example, if a class extends a library class or interface, not provided as source code, then class loading will fail for that class. The other classes will, however, still be loaded, and the macros will be able to use them.

This macro has a third option named store. When the classes are loaded they are stored in a user defined macro named loadedClasses. You can load different sets of class files providing a different name in this parameter. This parameter can also be used in the macros

  • java:classes

  • java:methods

  • java:fields

but not in

  • java:class

  • java:method

  • java:field

The plural versions are used for code generation. The singular versions are used for formatting and will always use the classes in the executing JVM or those loaded to the default macro class store.

java:classes

This macro will return the list of selected classes. The classes have to be loaded first using the java:sources macro. The macro has three parameters:

  • store can specify an alternative user defined macro where the classes are stored. Use this option specifying the same name as in the macro java:sources. It is recommended not to use this option.

  • selector, aliased as only, and filter should be used to filter the classes. The value of this parameter is a selector expression. This selector expression is interpreted by the tool REFI as documented https://github.com/verhas/refi.

  • sep, aliased as separator canbe used to specify the separator string between the classes. The default value is comma, which makes the result usable in a for macro. Do not forget to use the [evalist] option when using in the for macro the result of this macro.

Since the macro does not use its input, the parameters can be specified in the macro call without enclosing them between parentheses.

The returned list will contain the names of the classes.

java:methods

This macro will return a list of selected methods. The macro has four parameters:

  • store can specify an alternative user defined macro where the classes are stored. Use this option specifying the same name as in the macro java:sources. It is recommended not to use this option.

  • selector, aliased as only, and filter should be used to filter the methods). The value of this parameter is a selector expression. This selector expression is interpreted by the tool REFI as documented https://github.com/verhas/refi.

  • class can be used to specify a selector for the classes where the methods are to be selected from.

  • sep, aliased as separator canbe used to specify the separator string between the methods). The default value is comma, which makes the result usable in a for macro. Do not forget to use the [evalist] option when using in the for macro the result of this macro.

Since the macro does not use its input, the parameters can be specified in the macro call without enclosing them between parentheses.

The methods will be stored in user-defined macros. The returned string is the name of the macro where the methods are stored. These macros can be evaluated. The macro will interpret the first argument as a formatting command and will return

  • class will result the name of the class the method is declared in

  • name will result the name of the class. This is also the case when there is no parameter defined.

  • modifiers will return the modifiers of the method.

  • modifiers-abstract will return the modifiers of the method, minus the abstract modifier if there was such.

  • exceptions will return the list of the exceptions the method throws.

  • args will return the list of the arguments of the method.

  • call will return the method call with the arguments.

  • callWith will return the method call with the arguments provided in the macro.

  • type the return type of the method.

  • signature the signature of the method. This formatting also interprets the following parameters as:

    • concrete will return the signature of the method without the abstract modifier if there was such.

    • abstract will return the signature of the method with the abstract modifier if there was such.

    • interface will return the signature of the method without the public modifier, so it can be used in an interface.

    • public will return the signature of the method with the public modifier, even if the original method was not public

java:fields

This macro will return a list of selected fields. The macro has four parameters:

  • store can specify an alternative user defined macro where the classes are stored. Use this option specifying the same name as in the macro java:sources. It is recommended not to use this option.

  • selector, aliased as only, and filter should be used to filter the fields). The value of this parameter is a selector expression. This selector expression is interpreted by the tool REFI as documented https://github.com/verhas/refi.

  • class can be used to specify a selector for the classes where the fields are to be selected from.

  • sep, aliased as separator canbe used to specify the separator string between the fields). The default value is comma, which makes the result usable in a for macro. Do not forget to use the [evalist] option when using in the for macro the result of this macro.

Since the macro does not use its input, the parameters can be specified in the macro call without enclosing them between parentheses.

The fields will be stored in user-defined macros. The returned string is the name of the macro where the fields are stored. These macros can be evaluated. The macro will interpret the first argument as a formatting command and will return

  • class the class name the field is declared in.

  • name the name of the field. This is also the case when there is no parameter defined.

  • modifiers the modifiers of the field.

  • modifiers-abstract the modifiers of the field, minus the abstract modifier if there was such.

  • type the type of the field.

java:insert

The macro java:insert can be used to insert the content of the macro into a Java source file into a segment. The macro has three parameters:

  • file Is the name of the file where the segment is to be inserted. It is also aliased as to and into.

  • segment is the name of the segment where the content is to be inserted. It is also aliased as id and at. This parameter is optional and is not needed if there is only one segment in the file.

  • check, checkUpdate, update, updateOnly will do an extra step before updating the file. It will do a lexical analysis on the original file and on the new version and compare the two. If they are the same on the Java lexical level ignoring differences in comments and white-space, then the file is not updated.

    This is useful not to ruin the already generated code when it was formatted and does not change other than that. It can also be used when the build process runs in properly configured a CI/CD environment where the build process cannot write the source files. In that case, the developer builds should update the source code, and the CI/CD process can see the already updated code.

    The downside is that the comparison needs time and some memory.

  • failOnUpdate, failUpdate, updateError will result an error if the file is updated. Note that the processing will not stop, only the macro will result in an error at the end of the Jamal file processing. If there are multiple java:insert macros in the file, then all of them will be processed the error will only happen at the end of the processing. This option may be useful in a build process when the code generation runs after the compilation. If the code was updated, then the compilation should fail. The code, however, was already updated and the compilation started again will be successful.

  • wholeFile is a boolean option. If it is present then no segment should be, and the whole file will be updated, all the lines. In this case it is not an error if the file does not exist yet.

The parameters are not enclosed between any characters. They follow the macro name directly until the end of the line.

Jamal source
{@java:insert update to="Sample.java" id="segment"}

The segment in the source file starts with a line

//<editor-fold id="segment"...>

and it ends with

//</editor-fold>

If there are lines between these two lines, they are deleted and replaced by the input of the macro.

If the macro input is empty, then the file writing is deferred until the end of the processing. In this case, the content of the whole output is going to be used.

Note

When the macro writes its' input into the file, you will most probably want to use # as macro character. That way, the input will be first processed.

The suggested practice is to name the file XYZ.java.jam for code to be inserted into XYZ.java. Do not forget to add {%@comment nosave%} to the beginning of the file. This will prevent the Asciidoctor version of Jamal overwriting the Java file when editing the macros.

XLIX. String Macros

string:contains

This macro returns the string true if the input contains a text given as a parameter and the text false otherwise. The macro handles two parameters:

  • text (can be aliased as string) must be present, and it should specify the string to find in the input.

  • regex is an optional parameter. If it is true, then the text will be interpreted as a regular expression. The macro will check if a match is found inside the input. (It calls the Java regular expression matcher find().)

string:quote

This macro returns the input of the macro quoted. You can use the string inside another string that way. This macro is useful when the output is used as some programming language source. Example:

Jamal source
{@string:quote This "is" quoted '
 new line is also quoted, tabs are also and line-feed also \ becomes doubled}

will result

output
This \"is\" quoted '\n new line is also quoted, tabs are also and line-feed also \\ becomes doubled

The actual conversions performed are:

  • \\\

  • tab → \t

  • back space → \b

  • new line → \n

  • line feed → \r

  • form feed → \f

  • "\"

string:equals

This macro returns the string literal true or false comparing the two arguments. It returns true if the two arguments are equal and false otherwise. If the option ignoreCase is used, then the comparison is made ignoring the character casing.

Example:

Jamal source
{@string:equals/aaa/aaa}
{@string:equals/aaa/bbb}

will result

output
true
false

string:startsWith

This macro returns the string literal true or false comparing the two arguments. It returns true if the first argument starts with the second argument and false otherwise.

Example:

Jamal source
{@string:startsWith/aaa/aa}
{@string:startsWith/aaa/ba}

will result

output
true
false

string:endsWith

This macro returns the string literal true or false comparing the two arguments. It returns true if the first argument ends with the second argument and false otherwise.

Example:

Jamal source
{@string:endsWith/aaa/aa}
{@string:endsWith/aaa/ab}

will result

output
true
false

string

This macro can perform the operations startsWith, endsWith, equals, and contains. The macro has three parameters:

  • the string the operation is performed on,

  • the operation to perform,

  • the string to compare to.

The operation has to be given by the names startsWith, endsWith, equals, equalsTo or contains. The name of the operation is case-insensitive.

The macro also handles the parop ignoreCase. When using this alternative format of the comparison, then all operations use this parameter.

string:reverse

This macro returns the reverse of the input string. For example:

Jamal source
{@string:reverse 0123456789abcdefgh}

will result

output
hgfedcba9876543210

string:substring

This macro returns a substring of the input. The parameters are

  • begin specifying the beginning of the substring (default is the start of the string), and

  • end specifying the end of the substring (default is the end of the string).

If any of the parameters is a negative number, then the macro calculates the position from the end of the string.

Examples:

Jamal source
{@string:substring the whole string}
{@string:substring (begin=0 end=3)the first three character} only the'
{@string:substring (begin=1 end=-1)ythea}

will result

output
the whole string
the only the'
the

string:between

This macro extracts a substring from the input, confined between two specified strings. The parameters are:

  • after: Specifies the string after which the substring should begin. By default, the substring starts at the beginning of the input string if this parameter is not provided.

  • before: Specifies the string before which the substring should end. By default, the substring extends to the end of the input string if this parameter is not provided.

The behavior of the macro under different conditions is as follows:

  1. If the after string is not found in the input, the macro returns an empty string.

  2. If the before string is not found in the input, the macro also returns an empty string.

  3. If the after string appears more than once in the input, the macro selects the substring starting after the first occurrence of the after string.

  4. If the before string appears more than once in the input, the macro selects the substring ending before the last occurrence of the before string.

  5. If the last occurrence of the before string is positioned before the first occurrence of the after string in the input, the macro returns an empty string.

This macro is useful for parsing strings where specific delimiters define the boundaries of the desired content. For example, the macro can be used to extract the content of a JavaDoc comment:

Jamal source
{@string:between (after="/**" before="*/")/** This is a JavaDoc comment. */}

will result in

output
This is a JavaDoc comment.

string:before, string:after

These macros extract a portion of the input string, starting before or after a specified occurrence of a given substring. They are highly configurable, allowing for precise control over which occurrence to consider and how to handle case sensitivity. The two macros are implemented as a single macro, which alters the behaviors depending on the alas (string:after or string:before) used to invoke it. The behaviour can be controlled using paops.

Parameter options (parops):

  • first/theFirst, second/theSecond, third/theThird, last/theLast, nth/theNth specifies the searched string and which occurrence of the substring to consider. You can indicate a specific occurrence like first, second, third, or use nth to specify any occurrence.

  • n is used in conjunction with nth/theNth to specify the nth occurrence of the substring.

  • fromEnd/fromTheEnd When set, the search for the specified occurrence of the substring starts from the end of the string. Cannot be used together with first/theFirst, or last/theLast. Instead of using fromEnd and last you can use first. Similarly, instead of using fromTheEnd and first you can use last.

  • ignorecase/ignoreCase: When set, the search is case-insensitive.

When the substring is not found, or there are less number of occurences than required, the resutn value is empty string.

Examples:

Jamal source
{@string:after (second="a") alabala bim bam bum}

will result in

output
bala bim bam bum
Jamal source
{@string:before (theThird="a") alabala bim bam bum}

will result in

output
alab
Jamal source
{@string:before (theSecond="A" ignoreCase fromEnd) alabala bim bam bum}

will result in

output
alabal

string:length

This macro returns the length of the input. The parameters are:

  • trim tells the macro that the input has to be trimmed before calculating the length

  • left tells the macro that the trimming has to be applied to the start (left) of the input

  • right tells the macro that the trimming has to be applied to the end (right) of the input

You can use left and right together with trim. It will have the same effect as using trim alone. Using either left or right without trim is an error.

string:chop

Chop off a substring from the start or from the end of the input. The parameters are:

  • prefix, pre or start can specify a string that the input may start with. If the input starts with this string, it will be removed.

  • postfix, post or end can specify a string that the input may end with. If the input ends with this string, it will be removed.

  • ignorecase or ignoreCase tells the macro that the strings are case-insensitive.

L. shell:var

This macro is a convenience tool replacing $variable and ${variable} sequences with the values of the variables. The values of the variables can be provided and looked up from

  • macro parameters in the string of the value of the parameter variables,

  • macros defined,

  • Java system variables,

  • environment variables,

in this order.

Jamal source
{@define A=value}
{@shell:var $A}

results in

output
value

The variable replacement is performed recursively. If there is a variable reference in the result, then it will be replaced again. Also, when the reference is ${string} formatted the string is first processed for variables. This processing is done 100 levels deep. If the macro detects a loop, then it will stop the processing and results in a BadSyntax exception.

Although $ is a valid character in a macro name, you cannot reference a macro that has a $ in its name. Also, the macro must not require any parameter.

LI. hashCode

This macro returns the hash code of the macro input as a 64-character hexadecimal string.

Jamal source
{@hashCode abraka dabra}

will result in

output
9d8abd45416a62bcfb6437eb0fe978f95b8b8df30edb58dea17980a067857124

LII. memoize

This macro allows for the evaluation of input just once and subsequent reevaluation only if there has been a change. It proves beneficial in executing macros that produce external resources based on the document’s text, preventing unnecessary repetition of resource generation if the text remains unchanged. It is particularly useful, for instance, in memoizing PlantUML or other image generation processes. By utilizing this macro, images are generated solely when the text that impacts the generation process is modified.

The macro has three parameters:

  • The file parameter represents the name of the file that is generated. This parameter can be specified multiple times, allowing for multiple files. The macro neither reads nor writes this file. Instead, it checks for the file’s existence. If the file does not exist, the macro will then evaluate its input, which is expected to generate the file in some manner. However, the actual generation of the file is beyond the scope of this macro.

    When no file is specified, the macro assumes that the result already exists. This is applicable in scenarios where certain calculations do not result in file generation and the outcome is stored elsewhere. Under such circumstances, checking for a file is deemed unnecessary.

  • The hashCode parameter allows for the specification of the hash value or the hash code of the text that influences the generation. If not explicitly defined, this value is automatically calculated based on the macro’s input. The hashCode macro can be utilized within the document for this calculation. Generally, manually inserting a hash value into the document is unnecessary. Instead, it’s more typical to calculate it based on text that differs from the macro’s direct input.

    For instance, consider a scenario where you’re generating a PlantUML diagram that also incorporates additional files during its creation process. The text from these included files won’t be a part of the macro’s direct input. Nevertheless, you’d want the diagram generation to occur even if only the included text files undergo modifications. In such situations, you can utilize the hashCode macro to compute the hash code of the verbatim text from the included files. Subsequently, this computed hash code can be employed as the hashCode parameter in the memoize macro, ensuring the diagram is regenerated when the included files change, even if the main input to the macro remains the same.

  • The hashFile parameter denotes the name of the file that stores the hash value. Before the macro processes its input, it compares the current hash value with the one stored in the hashFile. If they match, indicating no changes, the macro does not reevaluate the input. Conversely, if the hash values differ, or in situations where the hashFile does not exist, the macro proceeds to create the file and reevaluates the input.

The macro’s return value is the result of the evaluated input when reevaluation occurs, and it returns an empty string if no evaluation is performed. This feature is particularly beneficial during interactive editing, as it provides a clear indication of whether the macro was executed. If you don’t require this functionality, you can opt to use macros that don’t produce any output.

LIII. download

This macro downloads a resource from a URL and saves it to a file.

The format of the macro is .Jamal source

{@download (file="file name") URL}

There is one parameter:

  • file should specify the file where to save the content of the downloaded file.

The URL is given after the option.

Note
  1. There is a similar functionality macro in the jamal-io module. That macro is called io:copy. This macro cannot append to a file, will not create the directory if it does not exist and you cannot control the use of cache. This macro will not use the Jamal download cache.

  2. Technically, the URL can specify any file name that you could include. You can specify the usual maven:, res:, etc. prefixes in addition to https:.

  3. This macro is intended to be used together with the macro memoize. For this reason it does not use the cache mechanism of Jamal.

LIV. snip:update

This macro was used to automatically copy the content of the snippets into the snip macros. It meant to update the edited source document. It was a temporary woraround till the time when the WYSIWYG editor was not available in IntelliJ. Since the introduction of the WYSIWYG editing in IntelliJ, this feature is outdated.

To keep backward compatibility the .jim resource file contains the line:

{@use global comment as snip:update}

This renders the current, no operation functionality of the macro.

LV. variation

This macro helps to keep variations of repeated texts in the document consistent.

Repeated texts are text fragments that appear in the document at multiple places repeated exactly as they are or with minimal variations. The macro helps you to create these texts separating the variable part from the constant part. If there is any discrepancy between the constant part of the text fragments, the macro will report an error.

The macro is used in the following way (example):

Jamal source
{@variation text that happens <<many>> times in the document}
...
{@variation text that happens <<multiple>> times in the document}

and it will result in the following text:

output
text that happens many times in the document
...
text that happens multiple times in the document

In this case the variable parts are enclosed between << and >> strings. These are the parts that may change. When the macro is used the second and further subsequent times, these parts are ignored during the comparison. If the constant parts, which are outside of these special parentheses, differ, the macro will report an error.

When you fix a typo or change in any way the constant part of the text, you will not forget to update the other occurrences.

The functionality of the macro is configurable and flexible. The opening and closing strings are << and >> by default, but you can change them. You can also change the way the macro compares the constant parts. It can ignore the letter casing and also treat different multiple spaces as one.

The parops the macro accepts between ( and ) characters are the following:

  • id the identifier of the segment. If you use only one variation, the default value for this parop is $vari and you can omit the parameter.

  • ignoreCase ignore the case of the characters during the comparison.

  • ignoreSpace treat repeated spaces as one space during the comparison. Also, treat new line and other white-space characters as ordinary spaces for the comparison.

  • trim remove the training and leading spaces from the input before the comparison

  • variation$start (alias start) the start string of the parts that will not be compared

  • variation$end (alias end) the end of the string parts that will not be compared

The parops are all "aliases", in other words you cannot globally define them with the exceptions of variation$start and variation$end. If you define any of these as user-defined macros then the value of the macro will be used as the value of the parop for subsequent calls.

In the following, we will list some examples.

Simple constant text

This example shows a text used two times in the document. This one does not have variable parts, the whole text is contant and has to appear the second time exactly the same.

Jamal source
{@variation (id=doubled_text)text that happens two times in the document}
{@variation (id=doubled_text)text that happens two times in the document}

results in

output
text that happens two times in the document
text that happens two times in the document

Simple variable text

This example has some variable part in the text. When the macro is used, the separator strings are removed. That way the output will not contain the << and >> characters.

Jamal source
{@variation (id=somany_text)text that happens <<some>> times in the document}
{@variation (id=somany_text)text that happens <<any>> times in the document}

results in

output
text that happens some times in the document
text that happens any times in the document
Note
It is an error to have a start (<<) or end (>>) string in the text. If you need to have any of them as part of the constant or variable part of the text, you should define alternatives using the parops start and end as demonstrated in the next example.

Alternative start and end strings

This example shows that you can define alternative start and end strings. If it is defined in the first occurrence of the text in the document, it will be used for all subsequent occurrences. Even in this case, start and end strings can be redefined in the subsequent calls, but those definitions are local. Also, redefinition of these strings does not affect the variation macros with other identifiers.

Jamal source
{@variation (id=alter start=< end=>)text that happens <some> times in the document}
{@variation (id=alter)text that happens <any> times in the document}
{@variation (id=alter start=' end=')text that happens 'multiple' times in the document}
{@variation (id=alter)text that happens <nonce> times in the document}
{@variation (id=somany_text)text that happens <<some>> times in the document}
{@variation (id=somany_text)text that happens <<any>> times in the document}

results in

output
text that happens some times in the document
text that happens any times in the document
text that happens multiple times in the document
text that happens nonce times in the document
text that happens some times in the document
text that happens any times in the document

Ignoring letter casing

This example shows how to use the ignoreCase parop. When the comparison is done, the original text and the subsequent texts are compared ignoring the letter casing. The actual output is not affected by this option. Using this option, you can use upper case or lower case letters without enclosing those characters into variable parts.

This option is not inherited from the first occurrence of the text in the document. It has to be applied in all subsequent uses that may differ from the first one in letter casing.

Using this option in the first occurrence of the text is optional, but has no effect.

Jamal source
{@variation (id=abrakaDabra)text that happens <<some>> TIMES in the document}
{@variation (id=abrakaDabra ignoreCase)text that happens <<Some>> times in the document}

will result in

output
text that happens some TIMES in the document
text that happens Some times in the document

Ignoring multiple spaces

This example demonstrates that the macro can ignore multiple spaces in the text. Using the parop ignoreSpace you can define that multiple spaces in the text are treated as one. Different white-space characters, like new-line, line-feed and so on are treated as ordinary space characters. The actual output is not affected by this option. All the different space characters that you apply to the text will get into the output.

This option is not inherited from the first occurrence of the text in the document. It has to be applied in all subsequent uses that may differ from the first one in spacing.

Using this option in the first occurrence of the text is optional, but has no effect.

Jamal source
{@variation (id=ignore_spc)text that happens
<<some>> times in         the document}
{@variation (id=ignore_spc ignoreSpace)text that happens <<Some>> times in the document}

will result in

output
text that happens
some times in         the document
text that happens Some times in the document

Ignoring spaces at the beginning and end of the text

Jamal source
.{@variation (id=trimmed trim)      this is trimmed only for the comparison}.
.{@variation (id=trimmed trim)  this is trimmed only for the comparison      }.

will result in the following text:

output
.      this is trimmed only for the comparison.
.  this is trimmed only for the comparison      .

Variable part is missing

When some variable part becomes an empty string, you can omit that part. There is no need to write <<>> in the text.

You should be careful paying attention to multiple spaces as in the example below.

Jamal source
{@variation (id=missng)text that happens <<some times>> in the document}
{@variation (id=missng ignoreSpace)text that happens in the document}

will result in:

output
text that happens some times in the document
text that happens in the document

Defining global start and end strings

The start and end string by default is << and >>. It is possible to define global start and end strings that will be used in all subsequent calls of the macro. Note, however, that the macros that have already been defined will not be affected by the change. In the example below the first call (1) to variation uses the default. After this we set the user defined macros variation$start and variation$end to [ and ] respectively. This will change any subsequent calls of variation, which create a new text. On line (2) we still use the default, even though the user defined macros are already defined. The reason is that this inherits the default from the first call. This inheritance is stronger than the user defined macros.

On line (3) we define a new variation text, and this time the start and end strings are [ and ]. So are they on line (4).

Jamal source
{@variation (id=wuff) Here the <<variable>> part uses the default} (1)
{@define variation$start=[}\
{@define variation$end=]}\
{@variation (id=wuff) Here the <<changing>> part uses the default} (2)
{@variation (id=quackk) Here the [variable] part uses the newly defined} (3)
{@variation (id=quackk) Here the [changling] part uses the newly defined} (4)
{@variation (id=quackk start=' end=') Here the 'chang ling wong' part uses the newly defined} (5)
{@variation (id=quackk variation$start=' variation$end=') Here the [chang ling wong] part uses the newly defined} (6)

will result in

output
Here the variable part uses the default (1)
Here the changing part uses the default (2)
Here the variable part uses the newly defined (3)
Here the changling part uses the newly defined (4)
Here the chang ling wong part uses the newly defined (5)
Here the chang ling wong part uses the newly defined (6)
Note
You can define the start and end string using the parops start and end as demonstrated in the example on line (5). To do this, you must use the start and end aliases and not the variation$start and variation$end macro names. As demonstrated on line (6), you can also use the variation$start and variation$end macro names, but they have no effect. The reason is that the code puts the inherited strings in front of the strings defined by these names.

LVI. plural

This macro helps to get the plural form of an English word.

Having the plural form of a word is usually not a problem. In most cases, you just type the plural form. You may, however, need to get the plural form of a word dynamically.

For example, you want to create a macro to generate a sentence like this:

Jamal source
{@define search(whatnot)=The actual whatnot is searched.
If there are multiple whatnots, the first one is used.}

You start using your macro and call it as

Jamal source
{search file}

and you get

output
The actual file is searched.
If there are multiple files, the first one is used.

This is great so far. The next place you also want to use it as

Jamal source
{search directory}

and you get

output
The actual directory is searched.
If there are multiple directorys, the first one is used.

This is not what you want. You want "directories" instead of "directorys". To solve this issue the macro plural comes to rescue. Define the macro as

Jamal source
{@define search(whatnot)=The actual whatnot is searched.
If there are multiple {@plural whatnot}, the first one is used.}

Now

Jamal source
{search directory}

will result in

output
The actual directory is searched.
If there are multiple directories, the first one is used.

The macro can be used to get the plural form of a word and also to define the plural format of a word. The latter is useful when the plural form of a word is not regular. In this case, for example, you can use it as

Jamal source
{@plural child=children}{@plural child}

and it will correctly result in

output
children

To get a short list of the most frequently used irregular plural words, use

Jamal source
{@import res:plurals.jim}

The resource file is part of the snippet package.

The algorithm implemented applies special rules when the word ending is y. This applies to words where 'y' is preceded by a consonant, (not a vowel). In English, such words are pluralized by replacing 'y' with 'ies'. The method first checks if the word ends with 'y' and if the preceding character is not a vowel (a, e, i, o, u). If the original word ends with an uppercase 'Y', it replaces 'Y' with 'IES'. If it ends with a lowercase 'y', it replaces 'y' with 'ies'.

When the word ending is 's', 'sh', 'ch', 'x', or 'z' they are pluralized by adding 'es'. The method checks for these endings in both lowercase and uppercase forms. It then adds 'ES', 'Es', 'eS', or 'es' to the word, depending on the original case of the ending.

In other cases the method simply adds 's' or 'S' to the word.

LVII. dictionary

This macro can be used to define a dictionary. Dictionaries are used by the decorator macro, but other macros can also use them. A dictionary is a set of words associated with a position inside the word. The macro input consists of the words separated by new lines. Each word can have a position specified after the word separated by a non-letter character. For example, the dictionary:

Jamal source
{@dictionary
  a.braka
  da.bra
  bubabo.
  .sholano
  molano
}

will define the word abraka with the position 1, the word dabra with the position 2, the word bubabo with the position 6, the word sholano with the position 0 and the word molano with the position 6. The position is denoted using a dot in this example, but you can use any non-letter character.

The macro has one parameter id or name that specifies the name of the dictionary. The default name is decor$dictionary. This is also the default name of the dictionary used by the decorate macro.

LVIII. decorate

This macro can be used to decorate a text. The decoration happens for each word separately. You can use this to make the text more appealing, or less readable used for obfuscation.

The decoration first splits the input into words. The words are separated by non-letter characters. The non-words and words, which are delimited by special characters (see below), are not decorated.

The decoration of the words is done in two steps. The first step splits the word into parts. The second step decorates the parts individually.

Decoration is done by user-defined macros. These macros should have exactly one argument. For example, the macro

Jamal source
{@define decor(x)={@rot13 x}.}

will decorate the input by ROT13 encoding. After this, the decoration macro can be used as

Jamal source
{@decorate (decorator=decor)abraka dabra}

resulting in

output
no.raka qn.bra

The macro has the following parameters between ( and ):

  • decor$delimiters aliased as delimiters defines the delimiter strings that surrounding a word make them not decorated. The default value is ":~:~:_:_:[:]". It means that any word delimited by ~, _ oe [ and ] will not be decorated. The first character of the string is used to separate the strings that follow. Note that the default value uses single characters, but you can use any string as a delimiter.

  • decorator defines one or more macros used as decorators. You can repeat this option multiple times. When a word is split into multiple parts, the first part will be decorated using the first decorator, the second part using the second, and so on. The default decorator puts a space after the decorated part. The words will be split into no more parts than the number of decorators plus one. In this case, the last part of the word will not be decorated.

  • repeat is a boolean parameter. If it is present on the macro splitting will be repeated as many times as needed. Without this option, the words will be split into two parts at most, or to as many parts as the number of decorators.

  • decor$ratios aliased as ratios defines the number of characters and the ratio of the characters used to split the word. The default value is "- 0 1 1 2 0.4". The first character is either '-' or '+' signaling if common words should be decorated or not. Common words can be defined in a dictionary or are defined as "the", "be", "to", "of", "and", "a", "an", "it", "at", "on", "he", "she", "but", "is", "my" .

    The following integers define the number of characters used to split the word if the word is 1, 2, 3 and so on characters.

    The last number defines the ratio of the characters used when the word is longer than the last number position. In the example above, one character words will not be split, two and three character words will be split after the first character, four character words will be split after the second character. Any word longer than four characters will be split at 40%.

    The last number will be rounded to two decimals.

  • decor$dictionary aliased as dictionary or dict defines the name of the dictionary used to define words with predefined splitting position. If this option is not present the macro will try to use the dictionary named decor$dictionary. If this dictionary does not exist, the macro will use the default splitting position defined by the decor$ratios option and the postfixes. If this option is defined, the dictionary named must exist.

  • decor$pfDict aliased as ending, endings, pf defines the postfix dictionary. When a word ends with a word present in the postfix dictionary, the word will be split earlier or at last at the start of the postfix. If the option is not present a default set of postfixes will be used: "ition", "ement", "ssion", "ional", "ction", "ation", "sion", "ning", "ther", "ight", "tion", "ture", "ding", "tive", "ally", "rate", "ting", "ance", "ence", "ment", "lity", "ical", "able", "onal", "tly", "ose", "ast", "ess", "use", "est", "ion", "ice", "ist", "hip", "tic", "der", "ate", "her", "ect", "nal", "ite", "ral", "ter", "all", "ver", "ide", "ity", "ght", "ely", "ain", "ous", "cal", "nce", "ial", "are", "low", "tor", "and", "ear", "ian", "ive", "eat", "ere", "ble", "end", "ire", "ine", "ual", "ing", "ore", "ant", "one", "ure", "ary", "ent", "ase", "lly", "ise", "age", "ish" .

  • decor$cmDict aliased as common, commons, cm defines the common words' dictionary.

3.Kroki

In this section, we will describe how to utilize the snippet handling macros in conjunction with the Kroki service.

The Kroki service is a free online platform capable of rendering diagrams from a text. Users can utilize the public service or to install the application on-premises. Although we will focus on using the public service in this section, the process for an on-premises installation is very similar.

The service’s documentation and the types of diagrams it supports are accessible at [https://kroki.io/](https://kroki.io/).

Kroki provides direct support for incorporating generated images into AsciiDoc and Markdown documents. Within this module, there is a resource file named kroki.jim. You can integrate this file into your document by using {@import res:kroki.jim} and employ the macro defined within.

The kroki macro requires four parameters:

  1. The name of the diagram, which will be utilized to title the generated image file.

  2. The diagram type, which can be any type supported by the Kroki service in use.

  3. The file type, options include png, svg, jpeg, etc. This parameter will not only be relayed to the Kroki service but also used as the file name extension. The various file types that are supported are documented at [https://kroki.io/](https://kroki.io/).

  4. The diagram text.

The macro encodes the diagram text, transmits it to the Kroki service, downloads the generated image, and embeds it into the document. If the including file’s name ends with md.jam, the macro will employ Markdown syntax for the image. Otherwise, it will use AsciiDoc syntax.

Should you require a different syntax, the kroki:download macro is available. This macro accepts the same arguments and returns the file name of the downloaded image.

These macros generate image files named according to the provided name and append the file type as an extensions. Additionally, they create a file with the same name and an extra .hash extension. The macro will only call the web-based service if the image file does not exist or if the diagram text has changed since the last execution. It may be beneficial to include these files in your source repository to minimize unnecessary network traffic on the CI/CD server.