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.
-
-
-
java:class
-
java:field
-
java:method
-
java:sources
-
java:classes
-
java:methods
-
java:fields
-
java:insert
-
-
-
string:contains
-
string:quote
-
string:equals
-
string:startsWith
-
string:endsWith
-
string:reverse
-
string:substring
-
string:between
-
string:before
-
string:after
-
string:length
-
string:chop
-
-
shell:var
-
<<kroki,Kroki>
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.
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 |
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 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 The possible situation may even be more complicated because the accidental word following 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 |
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 parameteronceAs
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 asonceAs="the Java samples from HPC"
then the collect macro will remember this name. If you try to collect anything with the sameonceAs
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 asmyprefix::
then the snippet namedmysnippet
will be stored asmyprefix::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 namedmysnippet
will be stored asmysnippet::mypostfix
.The parameter
prefix
andpostfix
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 withtag::name[]
and ends it withend::name[]
, wherename
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 optionexclude
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.
|
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
{@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
{@snip a}
will result
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
{@snip:properties testproperties.xml}
and referencing this time the property b
as
{@snip b}
will result
letter b
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:
{#xml:define pom={@include [verbatim]pom.xml}}\
{#define VERSION={pom /project/version/text()}}\
...
<version>{VERSION}</version>
...
The result is:
...
<version>2.8.2-SNAPSHOT</version>
...
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:
{@snip:xml pom=pom.xml}\
{#define VERSION={pom /project/version/text()}}\
...
<version>{VERSION}</version>
...
The result is:
...
<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.
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
{@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 aspath
) defines the location in the original XML where to insert the content. -
id
, (can be aliased asto
) 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 thexml:insert
commands, and after that the output will be the resulting XML formatted. -
ifneeded
(can be aliased asoptional
) defines whether the insertion is optional. If the location specified by thepath
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.
{@xml:define myXml=
<xml>
<FamilyName>Muster</FamilyName>
</xml>
}\
{@xml:insert (to=myXml path=/xml) <FirstName>Peter</FirstName>}
{myXml}
will result in
<?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.
<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
<?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.
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,
{@snip:define mySnippet=
It is the snippet, which is defined inside the file and not collected from an external file.
}
{@snip mySnippet}
will result
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:
{@snip:define mySnippet1= It is the snippet,
which is defined inside the file and not collected from an external file.
}
{@snip mySnippet1}
will result
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
orimport
macro.
{@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
../jamal-snippet/documentation/macros/snippy.txt
../jamal-snippet/documentation/macros/snip_define.adoc.jam
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.
You can use the snip
macro to insert one or more snippets into the output.
There are three different ways to use the macro.
-
insert a single snippet into the output with the full text of the snippet
-
insert a part of the first line of a snippet into the output
-
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
.
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.
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 |
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.
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 When the |
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.
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.
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:
-
Enclose the parts of the code documented between
snippet NAME
andend snippet
, or use complete files. -
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. -
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 onesnip: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. -
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.
-
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.
-
Update the hash code in the macro to the new value.
-
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.
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 asid
) for the name of the snippet -
file
(can be aliased asfileName
) for the file name of the snippet -
text
(can be aliased ascontains
) 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.
|
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 asid
) regular expression to match the name of the snippet -
file
(can be aliased asfileName
) regular expression to match the file name of the snippet -
text
(can be aliased ascontains
) 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 isXML
. The available formats areXML
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 astabSize
) 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 usually1
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 thesnip:check
macro. When thesnip: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).
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 asid
) regular expression to match the name of the snippet -
file
(can be aliased asfileName
) regular expression to match the file name of the snippet -
text
(can be aliased ascontains
) 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 isXML
. The available formats areXML
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.
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 asvtrimOnly
) instructs the macro to do only the vertical trimming. If this option is defined, there is no need to definetrimVertical
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
The syntax of the macro is:
{@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:
{@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
.......|.......|.......|.......|
... ... . . .. .
This macro can filter lines of its input by a range of numbers. The syntax of the macro is:
{@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.
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.
{@base64:encode This is a test}
will result in
VGhpcyBpcyBhIHRlc3Q=
and
{@base64:decode VGhpcyBpcyBhIHRlc3Q=}
will result in
This is a test
If you need a space before the text also encoded you should write
{@base64:encode (quote) " This is a test"}
resulting in
IFRoaXMgaXMgYSB0ZXN0
and again the reverse:
>>{@base64:decode (quote) "IFRoaXMgaXMgYSB0ZXN0"}<<
will result in
>> 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:
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
image::https://kroki.io/plantuml/svg/eNplTz0PgjAQ3fsrLkwwUJXEDUl0d3M3B5ykoS0N1-hg_O8WNFJ1e3fv495xr6zDEQ2MaHsmB8Va8GfZOgWbYhttHDY9dnRSXtNeq84ash40XbwQIzUebacJkiMqm8BdpCYAeVV0y0TKaiL9YDPxiMUHZJrFdQCyGYwbbEgNjhiboSX94yzraj7guVzVVQLIMM1nzyI2g5QV_KW_lbDbBTLuuWDI88B9tVg4OadGT0U4GCfnq_MTL7B2ww==[]
The macro repeat
repeats the input string n
times.
The number of repetitions is given by the parop n
, also aliased as times
.
{@repeat (n=3)A}
will result in
AAA
The parop trim
is optional, and if it is present, then the input string is trimmed before the repetition.
This macro calculates the ROT13 transformation of the input. For example
{@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
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.
The macro def
is a simple convenience macro that defines a user defined macro.
The syntax of the macro is simplified:
{@def name=value}
{name}
and it will result
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 parameteroptions
, 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.
This macro will URL encode its input. The format of the macro is
http://my.precious.com/what?{@urlencode (charset=UTF-8) query=" this is a quoted string"}
which, in this example will result
http://my.precious.com/what?query%3D%22+this+is+a+quoted+string%22
The macro has one option parameter:
-
charset
(aliascs
) - the character set to use for encoding. The default isUTF-8
.
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
{@pos.file}:{@pos.line}:{@pos.column}
will result
../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 withparent
,all
orup
-
parent
will use the location of theinclude
orimport
macro that was used to include or import the current file. This option cannot be used together withtop
,all
orup
-
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 optionsep
. This option cannot be used together withtop
,parent
orup
-
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 theformat
macro. They are the short forms forformat=%f
,format=%l
, andformat=%c
. The format is also used with the optionall
. -
up
specifies the number of steps up in the hierarchy.up=0
is the default.up=1
is the same asparent
. This option cannot be used together withtop
,parent
orall
-
sep
specifies the string used to concatenate the locations when the optionall
is used. The default value is a comma,
. This option must be used together with the optionall
.
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.
This macro returns the input sorted. The default behaviour is to sort the lines of the input alphabetically. For example
{@sort
beta
zeta
alpha}
will result the 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
, aliascollatingOrder
, aliascollator
can define the locale for the sorting. The default localeen-US.UTF-8
. Any locale string can be used installed in the Java environment and passed to the methodLocale.forLanguageTag()
. When this option is used with the aliascollator `, 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 availablejavax0.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 isn..m
wheren
is the first character position andm-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 optioncolumns
. -
numeric
will sort based on the numeric order of the keys. In this case, the keys must be numeric or else the conversion toBigDecimal
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:
{@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
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:
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.
{@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:
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.
|
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
{@numberLines
this is the first line
this is the second line
this is the third line
}
will result
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
{#numberLines start=3 step=2 format=" %03d::"
this is the first line
this is the second line
this is the third line
}
will result
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.
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:
{#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
/* 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.
{#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
// 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.
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,
{@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
this line is there
there can be more lines
You can also define the regular expressions defining the parameters skip
and endSkip
.
For example,
{#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
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
.
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:
{@replace /the apple has fallen off the tree/apple/pear/tree/bush}
will result:
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,
{#replace {@options regex}/the apple has fallen off the tree/appl(.)/p$1ar/tree/bush}
will result in the same 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.
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,
{@define replace=/^\s*\*\s+//}
{@replaceLines
* this can be a snippet content
* which was collected
* from a Java or C program comment
}
will result
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.
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}
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,
{@counter:define id=c} {c} {c} {c}
will result
1 2 3
You can define the start
, and the step
value for the counter as well as the format
.
For example,
{#counter:define id=c start=2 step=17} {c} {c} {c}
will result
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 bya, b, c . . .
for1, 2, . . .
-
$ALPHA
will be replaced byA, B, C . . .
for1, 2, . . .
-
$greek
will be replaced byα, β, γ . . .
for1, 2, . . .
-
$GREEK
will be replaced byΑ, Β, Γ . . .
for1, 2, . . .
-
$cyrillic
will be replaced byа, б, в . . .
for1, 2, . . .
-
$CYRILLIC
will be replaced byА, Б, В . . .
for1, 2, . . .
-
$roman
will be replaced byi, ii, iii . . .
for1, 2, . . .
-
$ROMAN
will be replaced byI, II, III . . .
for1, 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:
{@counter:define id=h format=$roman IIII}{h} {h} {h} {h} {h} {h} {h} {h} {h} {h} {h}
will result in
i ii iii iiii v vi vii viii ix x xi
{#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
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
{@counter:define id=c}{c} {#define second={c}}{second} {second} {c}
will result
1 2 2 3
The implemented counters provide a simplified approach for this.
{@counter:define id=c}{c} {c} {c last} {c}
will have the same 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
{@counter:define id=m}{m} {m -> secondChapter} {m} {m} is still {secondChapter}
will result
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 |
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
.
{@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.
{h} {h} {h} {h}
resulting in:
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:
{h open} {h} {h open} {h} {h open} {h} {h close} {h} {h close} {h} {h close} {h}
will result in:
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:
{@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:
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
.
{k last}
will result in:
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:
{k save=z} {z} {z format="%d.{@ident {2:%d}.{3:%d}}"}
will result in:
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.
|
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.
{@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
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 isref.jrf
. -
holder
is the name of the macro that will hold the list of macros to be saved. The default name isxrefs
.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
orcomments
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.
{@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
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.
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:
{@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
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
{@reflow width=10
0123456789|
The
long
lines
will
be broken into words.
Empty lines are paragraph limiters.
}
The output will be
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.
{@reflow width=1
0123456789|
The
long
lines
will
be broken into words.
Empty lines are paragraph limiters.
}
The output will be
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
{#reflow {@define width=1}
0123456789|
The
long
lines
will
be broken into words.
Empty lines are paragraph limiters.
}
will have the same result as:
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.
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
{@snip:line snippet_name}
The text following the snippet id is ignored, reserved for future development.
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
{@snip:file snippet_name}
The text following the snippet id is ignored, reserved for future development.
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 paropsnippet
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.
The snip:transform
macro integrates the functionality of the macros
-
kill
-
skipLines
-
range
orranges
-
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.
-
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 thatsnip:transform
will replacefoo
withbar
. You have to use the parameterreplace
as a macro option. -
There is an extra parameter named
action
(aliasactions
, plural) that lists the actions to perform.
The names for the actions are the followings:
-
kill
-
skip
-
range
orranges
-
replace
-
trim
-
reflow
-
number
-
untab
If you have a block that you want simultaneously trim and then number the lines, you have to write
{@snip:transform actions=trim,number
wuff
line
Mayak
Canoe
}
which will result
1. wuff
2. line
3. Mayak
4. Canoe
This is essentially the same as
{#numberLines
{@trimLines
wuff
line
Mayak
Canoe
}}
eventually with the same result:
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
{@snip:transform kill=A
Apple
Birnen
Birds
Sumatra
}
will delete all lines that contain the uppercase letter A
and will result:
Birnen
Birds
Sumatra
On the other hand
{@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 actionkill
-
keep
will add the actionkill
-
skip
will add the actionskip
-
lines
,range
orranges
will add the actionrange
-
replace
will add the actionreplace
-
tab
ortabSize
will add the actionuntab
-
trim
will add the actiontrim
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 Jamal, however, makes it possible to redefine built-in macros locally and globally via the |
The parameters for the snip:transform
are:
-
action
, (aliasactions
) listing the actions to perform. -
kill
, (aliaspattern
) passed tokillLines
-
keep
passed tokillLines
-
format
passed toNumberLines
-
start
passed toNumberLines
-
step
passed toNumberLines
-
width
passed toreflow
-
replace
passed toreplaceLines
-
detectNoChange
passed toreplaceLines
-
skip
passed toskipLines
-
endSkip
passed toskipLines
-
margin
passed totrimLines
-
trimVertical
passed totrimLines
-
verticalTrimOnly
passed totrimLines
-
tab
ortabSize
passed tountab
. Note that the originaluntab
parameter is not supported in thesnip:transform
macro. The reason for that is readability. Whileuntab size
may be acceptable, it is not clear what the meaning ofsize
is insnip:transform
. -
lines
(aliasrange
,ranges
) passed torange
The meaning and the interpretation of the parameters is the same as for the underlying macros and documented there.
This macro counts the lines in the content and returns the number of lines in decimal format.
{@lineCount
1
2
3}
results
3
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 thegrep
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. Specify1
in case you do not want to recurse into subdirectories. -
followSymlinks
to follow symbolic links -
countOnly
(aliascount
) 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,
{#for macroJavaFile in ({@listDir (format=$simpleName) ./src/main/java/javax0/jamal/})=
- macroJavaFile}
will result
- 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
{#for macroJavaFile in ({#listDir (format=$simpleName) ./src/main/java/javax0/jamal/
{@define maxDepth=1}})=
- macroJavaFile}
will result
- 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 literaltrue
if the file is a directory,false
otherwise. -
$isFile
will be replaced by the string literaltrue
if the file is a plain file,false
otherwise. -
$isHidden
will be replaced by the string literaltrue
if the file is hidden,false
otherwise. -
$canExecute
will be replaced by the string literaltrue
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 literaltrue
if the file can be written,false
otherwise.
For example,
{!#for (name,size) in ({#listDir ./src/main/java/javax0/jamal/
{@define format=$simpleName|$size}
})=
- name: {`@format /%,d/(int)size} bytes}
will result
- 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:
{#listDir (format=$simpleName maxDepth=1 sep=*) ./src/main/java/javax0/jamal/}
will result
jamal*snippet
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,
{#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
<?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,
{#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
<?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.
{#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:
<?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:
{#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
<?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.
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:
{#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
<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
line.<?xml version="1.0" encoding="UTF-8" standalone="no"?>
For more examples and detailed explanation of the thin XML format, see the Thin XML Format page.
This macro will return the current date formatted using Java SimpleDateFormat
.
The format string is the input of the macro.
Example
{@date yyyy-MM-dd HH:mm:ss}
will result in the output
2024-10-29 11:46:59
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:
{@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
000000000587,466
4d38e0bd5891048a}
0000587,466.0000
5.874660e+05
5.874660e+05 is 5.874660e+05
hashCode(0x4d38e0bd5891048a)=0x15a9e437
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
, orfrom
-
end
, orto
-
step
,by
, orincrement
.
Examples:
{@numbers end=5}
{@numbers start=1 end=5}
{@numbers start=1 end=5 step=2}
will result
0,1,2,3,4
1,2,3,4
1,3
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:
0x{@unicode C}\
{@unicode A}\
{@unicode F}\
{@unicode E}\
{@unicode B}\
{@unicode A}\
{@unicode B}\
{@unicode E}
will result
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.
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
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 ofmatch
, which tries to match the entire file name. For the comparison the full path or only the file name is used controlled by the paropfullPath
. -
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 paropfullPath
. -
isFile
(boolean) specifies that only files should be found. The default is to find both files and directories. -
isDir
, orisDirectory
(boolean) specifies that only directories should be found. The default is to find both files and directories. You cannot useisFile
andisDir
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 thefile
anddirectory
macros. -
dateFormat
(string) specifies the format of the date and time. This format is the same as the format of thefile
anddirectory
macros. -
relativeTo
(string) specifies the directory that is used to make the path relative. This option works the same way as in the macrosfile
anddirectory
.
Examples:
The simplest example is to find the file, where this documentation is stored:
{@file:locate find="lo.*.adoc.jam$"}
will result in
locate.adoc.jam
Finding the README.adoc file in the module directory:
{@file:locate in="../../" format=$absolutePath find="READ..\\.adoc$"}
will result in
/Users/verhasp/github/jamal/jamal-snippet/../jamal-snippet/README.adoc
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 thefile
anddirectory
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 thefile
anddirectory
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 macrosfile
anddirectory
.
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
When Jamal processes this file it will generate {@file (format=`$name`)README.adoc}.
will result
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 isyyyy-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 |
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.
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
This file is in the directory {@define directoryFormat=`$name`}{@directory ../jamal-snippet}.
will result
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 isyyyy-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 |
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.
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:
The class that implements the macro `java:class` is
{@java:class javax0.jamal.snippet.Java$ClassMacro}.
will result in the 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 callingsimpleName()
-
$name
will be replaced by the result of callingname()
-
$canonicalName
will be replaced by the result of callingcanonicalName()
-
$packageName
will be replaced by the result of callingpackageName()
-
$typeName
will be replaced by the result of callingtypeName()
For example
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
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.
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
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 bothstatic
andfinal
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:
{@define method=/javax0.jamal.snippet.Java$MethodMacro/evaluate}\
{#java:method {method}}
will result in the 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,
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
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)
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 assource
andsources
. -
class
is the location of the compiled class files. It is also aliased asclasses
. -
store
can specify the name of the macro holding the loaded classes. See details below. -
options
aliased ascompilerOptions
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.
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 macrojava:sources
. It is recommended not to use this option. -
selector
, aliased asonly
, andfilter
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 documentedhttps://github.com/verhas/refi
. -
sep
, aliased asseparator
canbe used to specify the separator string between the classes. The default value is comma, which makes the result usable in afor
macro. Do not forget to use the[evalist]
option when using in thefor
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.
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 macrojava:sources
. It is recommended not to use this option. -
selector
, aliased asonly
, andfilter
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 documentedhttps://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 asseparator
canbe used to specify the separator string between the methods). The default value is comma, which makes the result usable in afor
macro. Do not forget to use the[evalist]
option when using in thefor
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 theabstract
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 theabstract
modifier if there was such. -
abstract
will return the signature of the method with theabstract
modifier if there was such. -
interface
will return the signature of the method without thepublic
modifier, so it can be used in an interface. -
public
will return the signature of the method with thepublic
modifier, even if the original method was not public
-
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 macrojava:sources
. It is recommended not to use this option. -
selector
, aliased asonly
, andfilter
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 documentedhttps://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 asseparator
canbe used to specify the separator string between the fields). The default value is comma, which makes the result usable in afor
macro. Do not forget to use the[evalist]
option when using in thefor
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 theabstract
modifier if there was such. -
type
the type of the field.
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 asto
andinto
. -
segment
is the name of the segment where the content is to be inserted. It is also aliased asid
andat
. 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 multiplejava: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.
{@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 The suggested practice is to name the file |
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 asstring
) must be present, and it should specify the string to find in the input. -
regex
is an optional parameter. If it istrue
, 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 matcherfind()
.)
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:
{@string:quote This "is" quoted '
new line is also quoted, tabs are also and line-feed also \ becomes doubled}
will result
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
-
"
→\"
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:
{@string:equals/aaa/aaa}
{@string:equals/aaa/bbb}
will result
true
false
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:
{@string:startsWith/aaa/aa}
{@string:startsWith/aaa/ba}
will result
true
false
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:
{@string:endsWith/aaa/aa}
{@string:endsWith/aaa/ab}
will result
true
false
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.
This macro returns the reverse of the input string. For example:
{@string:reverse 0123456789abcdefgh}
will result
hgfedcba9876543210
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:
{@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
the whole string
the only the'
the
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:
-
If the
after
string is not found in the input, the macro returns an empty string. -
If the
before
string is not found in the input, the macro also returns an empty string. -
If the
after
string appears more than once in the input, the macro selects the substring starting after the first occurrence of theafter
string. -
If the
before
string appears more than once in the input, the macro selects the substring ending before the last occurrence of thebefore
string. -
If the last occurrence of the
before
string is positioned before the first occurrence of theafter
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:
{@string:between (after="/**" before="*/")/** This is a JavaDoc comment. */}
will result in
This is a JavaDoc comment.
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 likefirst
,second
,third
, or usenth
to specify any occurrence. -
n
is used in conjunction withnth
/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 withfirst
/theFirst
, orlast
/theLast
. Instead of usingfromEnd
andlast
you can usefirst
. Similarly, instead of usingfromTheEnd
andfirst
you can uselast
. -
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:
{@string:after (second="a") alabala bim bam bum}
will result in
bala bim bam bum
{@string:before (theThird="a") alabala bim bam bum}
will result in
alab
{@string:before (theSecond="A" ignoreCase fromEnd) alabala bim bam bum}
will result in
alabal
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.
Chop off a substring from the start or from the end of the input. The parameters are:
-
prefix
,pre
orstart
can specify a string that the input may start with. If the input starts with this string, it will be removed. -
postfix
,post
orend
can specify a string that the input may end with. If the input ends with this string, it will be removed. -
ignorecase
orignoreCase
tells the macro that the strings are case-insensitive.
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.
{@define A=value}
{@shell:var $A}
results in
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.
This macro returns the hash code of the macro input as a 64-character hexadecimal string.
{@hashCode abraka dabra}
will result in
9d8abd45416a62bcfb6437eb0fe978f95b8b8df30edb58dea17980a067857124
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 thehashCode
parameter in thememoize
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.
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
|
|
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.
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):
{@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:
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
(aliasstart
) the start string of the parts that will not be compared -
variation$end
(aliasend
) 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.
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.
{@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
text that happens two times in the document
text that happens two times in the document
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.
{@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
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.
|
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.
{@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
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
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.
{@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
text that happens some TIMES in the document
text that happens Some times in the document
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.
{@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
text that happens
some times in the document
text that happens Some times in the document
.{@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:
. this is trimmed only for the comparison.
. this is trimmed only for the comparison .
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.
{@variation (id=missng)text that happens <<some times>> in the document}
{@variation (id=missng ignoreSpace)text that happens in the document}
will result in:
text that happens some times in the document
text that happens in the document
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).
{@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
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.
|
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:
{@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
{search file}
and you get
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
{search directory}
and you get
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
{@define search(whatnot)=The actual whatnot is searched.
If there are multiple {@plural whatnot}, the first one is used.}
Now
{search directory}
will result in
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
{@plural child=children}{@plural child}
and it will correctly result in
children
To get a short list of the most frequently used irregular plural words, use
{@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.
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:
{@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.
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
{@define decor(x)={@rot13 x}.}
will decorate the input by ROT13 encoding. After this, the decoration macro can be used as
{@decorate (decorator=decor)abraka dabra}
resulting in
no.raka qn.bra
The macro has the following parameters between (
and )
:
-
decor$delimiters
aliased asdelimiters
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 asratios
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 adictionary
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 asdictionary
ordict
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 nameddecor$dictionary
. If this dictionary does not exist, the macro will use the default splitting position defined by thedecor$ratios
option and the postfixes. If this option is defined, the dictionary named must exist. -
decor$pfDict
aliased asending
,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 ascommon
,commons
,cm
defines the common words' dictionary.
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:
-
The name of the diagram, which will be utilized to title the generated image file.
-
The diagram type, which can be any type supported by the Kroki service in use.
-
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/). -
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.