[](https://github.com/SETI/rms-pdstemplate/releases)
[](https://github.com/SETI/rms-pdstemplate/releases)
[](https://github.com/SETI/rms-pdstemplate/actions)
[](https://rms-pdstemplate.readthedocs.io/en/latest/?badge=latest)
[](https://codecov.io/gh/SETI/rms-pdstemplate)
<br />
[](https://pypi.org/project/rms-pdstemplate)
[](https://pypi.org/project/rms-pdstemplate)
[](https://pypi.org/project/rms-pdstemplate)
[](https://pypi.org/project/rms-pdstemplate)
<br />
[](https://github.com/SETI/rms-pdstemplate/commits/main/)
[](https://github.com/SETI/rms-pdstemplate/commits/main/)
[](https://github.com/SETI/rms-pdstemplate/commits/main/)
<br />
[](https://github.com/SETI/rms-pdstemplate/issues)
[](https://github.com/SETI/rms-pdstemplate/issues)
[](https://github.com/SETI/rms-pdstemplate/pulls)
[](https://github.com/SETI/rms-pdstemplate/pulls)
<br />

[](https://github.com/SETI/rms-pdstemplate/stargazers)

# Introduction
`pdstemplate` is a Python module that defines the `PdsTemplate` class, which is
used to generate PDS labels based on template files. Both PDS3 and PDS4 (xml) labels are
supported. Although specifically designed to facilitate data deliveries by PDS data
providers, the template system is generic and can be used to generate files from templates
for other purposes.
`pdstemplate` is a product of the [PDS Ring-Moon Systems Node](https://pds-rings.seti.org).
# Installation
The `pdstemplate` module is available via the `rms-pdstemplate`[](https://pypi.org/project/rms-pdstemplate)
package on PyPI and can be installed with:
```sh
pip install rms-pdstemplate
```
# Getting Started
The general procedure is as follows:
1. Create a template object by calling the
`PdsTemplate`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate)
constructor to read a template file:
from pdstemplate import PdsTemplate
template = PdsTemplate(template_file_path)
2. Create a dictionary that contains the parameter values to use inside the label.
3. Construct the label using the
`write()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.write)
method as follows:
template.write(dictionary, label_file)
This will create a new label of the given name, using the values in the given
dictionary. Once the template has been constructed, steps 2 and 3 can be repeated any
number of times.
Alternatively, you can obtain the content of a label without writing it to a file using
`generate()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.generate).
`pdstemplate` employs the RMS Node's `rms-filecache`[](https://pypi.org/project/rms-filecache)
module and its `FCPath`[](https://rms-filecache.readthedocs.io/en/latest/module.html#filecache.file_cache_path.FCPath)
class to support the handling of files at a website or in the cloud. You can refer to a
remote file by URL and the `PdsTemplate` will treat it as if it were a local file.
See filecache's [documentation](https://rms-filecache.readthedocs.io/en/latest/index.html)
for further details.
Details of the `PdsTemplate` class are available in the [module documentation](https://rms-pdstemplate.readthedocs.io/en/latest/module.html).
# Template Syntax
A template file will look generally like a label file, except for certain embedded
expressions that will be replaced when the template's
`write()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.write)
or
`generate()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.generate)
method is called.
## Substitutions
In general, everything between dollar signs "$" in the template is interpreted as a
Python expression to be evaluated. The result of this expression then replaces it
inside the label. For example, if `dictionary['INSTRUMENT_ID'] == 'ISSWA'`, then
<instrument_id>$INSTRUMENT_ID$</instrument_id>
in the template will become
<instrument_id>ISSWA</instrument_id>
in the label. The expression between "$" in the template can include indexes, function
calls, or just about any other Python expression. As another example, using the same
dictionary above,
<camera_fov>$"Narrow" if INSTRUMENT_ID == "ISSNA" else "Wide"$</camera_fov>
in the template will become
<camera_fov>Wide</camera_fov>
in the label.
An expression in the template of the form `$name=expression$`, where the `name` is a
valid Python variable name, will also also have the side-effect of defining this
variable so that it can be re-used later in the template. For example, if this appears
as an expression,
$cruise_or_saturn=('cruise' if START_TIME < 2004 else 'saturn')$
then later in the template, one can write:
<lid_reference>
urn:nasa:pds:cassini_iss_$cruise_or_saturn$:data_raw:cum-index
</lid_reference>
To embed a literal "$" inside a label, enter "$$" into the template.
## Headers
Headers provide even more sophisticaed control over the content of a label. A header
appears alone on a line of the template and begins with "$" as the first non-blank
character. It determines whether or how subsequent text of the template will appear in the
file, from here up to the next header line.
### FOR and END_FOR
You can include one or more repetitions of the same text using `FOR` and `END_FOR`
headers. The format is
$FOR(expression)
<template text>
$END_FOR
where `expression` evaluates to a Python iterable. Within the `template text`, these new
variable names are assigned:
- `VALUE` = the next value of the iterator;
- `INDEX` = the index of this iterator, starting at zero;
- `LENGTH` = the number of items in the iteration.
For example, if
dictionary["targets"] = ["Jupiter", "Io", "Europa"]
dictionary["naif_ids"] = [599, 501, 502],
then
$FOR(targets)
<target_name>$VALUE (naif_ids[INDEX])$</target_name>
$END_FOR
in the template will become
<target_name>Jupiter (599)</target_name>
<target_name>Io (501)</target_name>
<target_name>Europa (502)</target_name>
in the label.
Instead of using the names `VALUE`, `INDEX`, and `LENGTH`, you can customize the
variable names by listing up to three comma-separated names and an equal sign `=`
before the iterable expression. For example, this will produce the same results as the
example above:
$FOR(name, k=targets)
<target_name>$name (naif_ids[k])$</target_name>
$END_FOR
### IF, ELSE_IF, ELSE, and END_IF
You can also use `IF`, `ELSE_IF`, `ELSE`, and `END_IF` headers to select among
alternative blocks of text in the template:
- `IF(expression)` - Evaluate `expression` and include the next lines of the
template if it is logically True (e.g., boolean True, a nonzero number, a non-empty
list or string, etc.).
- `ELSE_IF(expression)` - Include the next lines of the template if `expression` is
logically True and every previous expression was not.
- `ELSE` - Include the next lines of the template only if all prior
expressions were logically False.
- `END_IF` - This marks the end of the set of if/else alternatives.
As with other substitutions, you can define a new variable of a specified name by
using `name=expression` inside the parentheses of `IF()` or `ELSE_IF()`.
Note that headers can be nested arbitrarily inside the template.
### ONCE
`ONCE` is a header that simply includes the content that follows it one time. However,
it is useful for its side-effect, which is that `ONCE(expression)` allows the embedded
`expression` to be evaluated without writing new text into the label. You can use this
capability to define variables internally without affecting the content of the label
produced. For example:
$ONCE(date=big_dictionary["key"]["date"])
will assign the value of the variable named `date` for subsequent use within the template.
### INCLUDE
This header will read the content of another file and insert its content into the template
here:
$INCLUDE(filename)
Using the environment variable `PDSTEMPLATE_INCLUDES`, you can define one or more
directories that will be searched for a file to be included. If multiple directories are
to be searched, they should be separated by colons. You can also specify one or more
directories to search in the
`PdsTemplate`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate)
constructor using the `includes` input parameter.
Include files are handled somewhat differently from other headers. When `INCLUDE`
references a file as a literal string rather than as an expression to evaluate, it is
processed at the time that the
`PdsTemplate`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate)
is constructed. However, if the
filename is given as an expression, it is not evaluated until the
`write()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.write)
or
`generate()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.generate)
method is called for each label.
### NOTE and END_NOTE
You can use `NOTE` and `END_NOTE` to embed any arbitrary comment block into the
template. Any text between these headers does not appear in the label:
$NOTE
Here is an extended comment about the templae
$END_NOTE
You can also use `$NOTE:` for an in-line comment. This text, and any blanks before it,
are not included in the label::
<filter>$FILTER$</filter> $NOTE: This is where we identify the filter
## Pre-defined Functions
The following pre-defined functions can be used inside any expression in the template.
- `BASENAME(filepath)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.BASENAME):
The basename of `filepath`, with leading directory path removed.
- `BOOL(value, true='true', false='false')`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.BOOL):
Return "true" if `value` evaluates to Boolean True; otherwise, return "false".
- `COUNTER(name, reset=False)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.COUNTER):
The current value of a counter identified by `name`, starting at 1. If `reset` is True, the counter is reset to 0.
- `CURRENT_TIME(date_only=False)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.CURRENT_TIME):
The current time in the local time zone as a string of the form
"yyyy-mm-ddThh:mm:sss" if `date_only=False` or "yyyy-mm-dd" if `date_only=True`.
- `CURRENT_ZULU(date_only=False)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.CURRENT_ZULU):
The current UTC time as a string of the form "yyyy-mm-ddThh:mm:sssZ" if
`date_only=False` or "yyyy-mm-dd" if `date_only=True`.
- `DATETIME(time, offset=0, digits=None)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.DATETIME):
Convert `time` as an arbitrary date/time string or TDB seconds to an ISO date
format with a trailing "Z". An optional `offset` in seconds can be applied. The
returned string contains an appropriate number of decimal digits in the seconds
field unless `digits` is specified explicitly. If `time` is "UNK", then "UNK" is
returned.
- `DATETIME_DOY(time, offset=0, digits=None)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.DATETIME_DOY):
Convert `time` as an arbitrary date/time string or TDB seconds to an ISO date
of the form "yyyy-dddThh:mm:ss[.fff]Z". An optional `offset` in seconds can be
applied. The returned string contains an appropriate number of decimal digits in
the seconds field unless `digits` is specified explicitly. If `time` is "UNK",
then "UNK" is returned.
- `DAYSECS(string)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.DAYSECS):
The number of elapsed seconds since the most recent midnight. `time` can be
a date/time string, a time string, or TDB seconds.
- `FILE_BYTES(filepath)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.FILE_BYTES):
The size in bytes of the file specified by `filepath`.
- `FILE_MD5(filepath)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.FILE_MD5):
The MD5 checksum of the file specified by `filepath`.
- `FILE_RECORDS(filepath)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.FILE_RECORDS):
The number of records in the the file specified by `filepath` if it is ASCII; 0
if the file is binary.
- `FILE_TIME(filepath)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.FILE_TIME):
The modification time in the local time zone of the file specified by `filepath`
in the form "yyyy-mm-ddThh:mm:ss".
- `FILE_ZULU(filepath)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.FILE_ZULU):
The UTC modification time of the the file specified by `filepath` in the form
"yyyy-mm-ddThh:mm:ssZ".
- `GETENV(name, default='')`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.GETENV):
The value of any environment variable.
- `LABEL_PATH()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.LABEL_PATH):
The full directory path to the label file being written.
- `LOG(level, message, filepath='', force=False)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.LOG):
Write a message to the current log.
- `NOESCAPE(text)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.NOESCAPE):
If the template is XML, evaluated expressions are "escaped" to ensure that they
are suitable for embedding in a PDS4 label. For example, ">" inside a string will
be replaced by `>`. This function prevents `text` from being escaped in the
label, allowing it to contain literal XML.
- `QUOTE_IF(text)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.QUOTE_IF):
Quote the given text if it requires quotes within a PDS3 label.
- `RAISE(exception, message)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.RAISE):
Raise an exception with the given class `exception` and the `message`.
- `REPLACE_NA(value, if_na, flag='N/A')`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.REPLACE_NA):
Return `if_na` if `value` equals "N/A" (or `flag` if specified); otherwise, return `value`.
- `REPLACE_UNK(value, if_unk)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.REPLACE_UNK):
Return `if_unk` if `value` equals "UNK"; otherwise, return `value`.
- `TEMPLATE_PATH()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.TEMPLATE_PATH):
The directory path to the template file.
- `VERSION_ID()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.VERSION_ID):
Version ID of this module using two digits, e.g., "v1.0".
- `WRAP(left, right, text, preserve_single_newlines=True)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.WRAP):
Wrap the given text to a specified indentation and width.
These functions can also be used directly by the programmer; they are static functions
of class PdsTemplate.
# Logging and Exception Handling
`pdstemplate` employs the RMS Node's `rms-pdslogger`[](https://pypi.org/project/rms-pdslogger)
module to handle logging. By default, the
logger is a `PdsLogger`[](https://rms-pdslogger.readthedocs.io/en/latest/module.html#pdslogger.PdsLogger)
object, although any `logging.Logger` object will work. See
[`pdslogger`'s documentation](https://rms-pdslogger.readthedocs.io) for further details.
You can override the default Logger using static method
`set_logger()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.utils.set_logger).
You can also set the logging level ("info", "warning", "error", etc.) using
`set_log_level()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.utils.set_log_level)
and can select among many log formatting options using
`set_log_format()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.utils.set_log_format)
Use
`get_logger()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.utils.get_logger)
to obtain the current Logger.
By default, exceptions during a call to
`write()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.write)
or
`generate()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.generate)
are handled as follows:
1. They are written to the log.
2. The expression that triggered the exception is replaced by the error text in the
label, surrounded by "[[[" and "]]]" to make it easier to find.
3. The attributes `fatal_count`, `error_count`, and `warning_count` of the
`PdsTemplate`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate)
object contain the number of messages logged by each category.
4. The exception is otherwise suppressed.
This behavior can be modified by calling method `raise_exceptions(True)` in the call to
`write()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.write)
or
`generate()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.generate);
in this case, the exception
will be raised, label generation will stop, and the label will not be written.
# Pre-processors
A pre-processor is a function that takes the text of a template file as input and returns
a new template as output. As described above, `INCLUDE` headers that contain an explicit
file name (rather than an expression to be evaluated) are handled by a pre-processor.
You may define your own functions to pre-process the content of a template. They must have
this call signature::
func(path: str | Path | FCPath, content: str, *args, **kwargs) -> str
where
- `path` is the path to the template file (used here just for error logging).
- `content` is the content of a template represented by a single string with <LF> line
terminators.
- `*args` is for any additional positional arguments to `func`.
- `**kwargs` is for any additional keyword arguments to `func`.
When you invoke the
`PdsTemplate`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate)
constructor, one of the optional inputs is
`preprocess`, which takes either a single function or a list of functions to apply after
the `INCLUDE` pre-processor. For the first of these, the `args` and `kwargs` inputs can be
provided as additional inputs to the constructor. Subsequent pre-processors cannot take
additional arguments; define them using lambda notation instead.
Note that a
`PdsTemplate`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate)
object has an attribute `content`, which contains the
full content of the template after all pre-processing has been performed. You can examine
this attribute to see the final result of all processing. Note also that when line numbers
appear in an error message, they refer to the line number of the template after
pre-processing, not before.
# `pdstemplate.pds3table`
`pds3table` is a plug-in module to automate the generation and validation of PDS3 labels
for ASCII tables. It works in concert with the
`asciitable` module, which analyzes
the content of ASCII table files. It is used by stand-alone program `tablelabel` to
validate and repair existing PDS3 labels as well as to generate new labels; if
`tablelabel` meets your needs, you can avoid any programming in Python.
To import:
import pdstemplate.pds3table
Once imported, the following pre-defined functions become available for use within a
`PdsTemplate`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate):
- `ANALYZE_PDS3_LABEL(labelpath, validate=True)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.pds3table.ANALYZE_PDS3_LABEL):
analyzes the content of a PDS3 label or template, gathering
information about the names and other properties of its `TABLE` and `COLUMN` objects. Once
it is called, the following functions become available.
- `ANALYZE_TABLE(filepath, separator=',', crlf=None, escape='')`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.asciitable.ANALYZE_TABLE)
(from `asciitable`) takes the path to an existing
ASCII table and analyzes its content, inferring details about the content and formats of
all the columns.
- `VALIDATE_PDS3_LABEL(hide_warnings=False, abort_on_error=True)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.pds3table.VALIDATE_PDS3_LABEL):
issues a warning message for any errors found in the label
or template. Optionally, it can abort the generation of the label if it encounters an
irrecoverable incompatibility with the ASCII table.
- `LABEL_VALUE(name, column=0)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.pds3table.LABEL_VALUE):
returns correct and valid PDS3 values for many of the attributes of
PDS3 TABLE and COLUMN objects, based on its analysis of the table.
- `OLD_LABEL_VALUE(name, column=0)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.pds3table.OLD_LABEL_VALUE):
returns the current (although possibly incorrect or missing)
values for many of the same PDS3 `TABLE` and `COLUMN` attributes.
For example, consider a template that contains this content:
$ONCE(ANALYZE_TABLE(LABEL_PATH().replace('.lbl', '.tab')))
$ONCE(ANALYZE_PDS3_LABEL(TEMPLATE_PATH()))
...
OBJECT = TABLE
...
ROWS = $LABEL_VALUE('ROWS')$
COLUMNS = $LABEL_VALUE('COLUMNS')$
OBJECT = COLUMN
NAME = FILE_NAME
COLUMN_NUMBER = $LABEL_VALUE("COLUMN_NUMBER", "FILE_NAME")$
DATA_TYPE = $LABEL_VALUE("DATA_TYPE", "FILE_NAME")$
START_BYTE = $LABEL_VALUE("START_BYTE", "FILE_NAME")$
BYTES = $LABEL_VALUE("BYTES", "FILE_NAME")$
FORMAT = $LABEL_VALUE("FORMAT", "FILE_NAME")$
MINIMUM_VALUE = $LABEL_VALUE("MINIMUM_VALUE", "FILE_NAME")$
MAXIMUM_VALUE = $LABEL_VALUE("MAXIMUM_VALUE", "FILE_NAME")$
DESCRIPTION = "Name of file in the directory"
END_OBJECT = COLUMN
...
The initial calls to
`ANALYZE_TABLE()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.asciitable.ANALYZE_TABLE)
and
`ANALYZE_PDS3_LABEL()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.pds3table.ANALYZE_PDS3_LABEL)
are
embedded inside a `ONCE()` directive because they return no content. The first call
analyzes the content and structure of the ASCII table, and the second analyzes the
template. The subsequent calls to
`LABEL_VALUE()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.pds3table.LABEL_VALUE)
fill in the correct values for the specified quantities.
Optionally, you could include this as the third line in the template::
$ONCE(VALIDATE_PDS3_LABEL())
This function logs a warnings and errors for any incorrect TABLE and COLUMN values
currently in the template.
This module also provides a pre-processor, which can be used to validate or repair an
exising PDS3 label. The function
`pds3_table_preprocessor`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.pds3table.pds3_table_preprocessor),
when used as the `preprocess` input to the
`PdsTemplate`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate)
constructor, transforms an
existing PDS3 label into a new template by replacing all needed `TABLE` and `COLUMN`
attributes with calls to
`LABEL_VALUE()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.pds3table.LABEL_VALUE).
The effect is that when the label is
generated, it is guaranteed to contain correct information where the earlier label might
have been incorrect. In this case, your program would look something like this:
from pdstemplate import PdsTemplate
from pdstemplate.pds3table import pds3_table_preprocessor
template = PdsTemplate(label_path, crlf=True, ...
preprocess=pds3_table_preprocessor, kwargs={...})
template.write({}, label_path, ...)
The constructor invokes
`pds3_table_preprocessor`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.pds3table.pds3_table_preprocessor)
to transform the label into a
template. You can use the `kwargs` input dictionary to provide inputs to the
pre-processor, such as adding a requirement that each column contain `FORMAT`,
`COLUMN_NUMBER`, `MINIMUM/MAXIMUM_VALUEs`, etc., and designating how warnings and errors are
to be handled.
Afterward, the call to the template's
`write()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.write)
method will
validate the label and/or write a new label, depending on its input parameters.
For example, suppose the label contains this:
PDS_VERSION_ID = PDS3
RECORD_TYPE = FIXED_LENGTH
RECORD_BYTES = 1089
FILE_RECORDS = 1711
^INDEX_TABLE = "COVIMS_0094_index.tab"
OBJECT = INDEX_TABLE
INTERCHANGE_FORMAT = ASCII
ROWS = 1711
COLUMNS = 61
ROW_BYTES = 1089
DESCRIPTION = "This Cassini VIMS image index ...."
OBJECT = COLUMN
NAME = FILE_NAME
DATA_TYPE = CHARACTER
START_BYTE = 2
BYTES = 25
DESCRIPTION = "Name of file in the directory"
END_OBJECT = COLUMN
...
You then execute this:
template = PdsTemplate(label_path, crlf=True,
preprocess=pds3_table_preprocessor,
kwargs={'numbers': True, 'formats': True})
After the call, you can look at the template's `content` attribute, which contains the
template's content after pre-processing. Its value is this:
$ONCE(ANALYZE_TABLE(LABEL_PATH().replace(".lbl",".tab").replace(".LBL",".TAB"), crlf=True))
$ONCE(VALIDATE_PDS3_LABEL(hide_warnings, abort_on_error))
PDS_VERSION_ID = PDS3
RECORD_TYPE = $LABEL_VALUE("RECORD_TYPE")$
RECORD_BYTES = $LABEL_VALUE("RECORD_BYTES")$
FILE_RECORDS = $LABEL_VALUE("FILE_RECORDS")$
OBJECT = INDEX_TABLE
INTERCHANGE_FORMAT = $LABEL_VALUE("INTERCHANGE_FORMAT")$
ROWS = $LABEL_VALUE("ROWS")$
COLUMNS = $LABEL_VALUE("COLUMNS")$
ROW_BYTES = $LABEL_VALUE("ROW_BYTES")$
DESCRIPTION = "This Cassini VIMS image index ...."
OBJECT = COLUMN
NAME = FILE_NAME
COLUMN_NUMBER = $LABEL_VALUE("COLUMN_NUMBER", 1)$
DATA_TYPE = $LABEL_VALUE("DATA_TYPE", 1)$
START_BYTE = $LABEL_VALUE("START_BYTE", 1)$
BYTES = $LABEL_VALUE("BYTES", 1)$
FORMAT = $QUOTE_IF(LABEL_VALUE("FORMAT", 1))$
DESCRIPTION = "Name of file in the directory"
END_OBJECT = COLUMN
...
The `TABLE` and `COLUMN` attributes defining table format and structure have been replaced by
calls to
`LABEL_VALUE()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.pds3table.LABEL_VALUE),
which will provide the correct value whether or not the
value in the original label was correct. Also, `COLUMN_NUMBER` and `FORMAT` have been added to
the COLUMN object because of the pre-processor inputs `numbers=True` and `formats=True`.
Another application of the preprocessor is to simplify the construction of a template for
an ASCII table. Within a template, the only required attributes of a `COLUMN` object are
`NAME` and `DESCRIPTION`. Optionally, you can also specify any special constants,
`VALID_MINIMUM/MAXIMUM` values, `OFFSET` and `SCALING_FACTOR`, and the number of `ITEMS` if the
`COLUMN` object describes more than one. All remaining information about the column, such as
`DATA_TYPE`, `START_BYTE`, `BYTES`, etc., will be filled in by the pre-processor. Inputs to the
preprocessor let you indicate whether to include `FORMATs`, `COLUMN_NUMBERs`, and the
`MINIMUM/MAXIMUM_VALUEs` attributes automatically.
# `pdstemplate.asciitable`
`asciitable` is a plug-in module to assist with the labeling of ASCII tables in PDS3 and PDS4. It
supports the `pdstemplate.pds3table` module and the `tablelabel` tool, and will also be used by
a future `pds4table` tool. To import:
import pdstemplate.asciitable
This import creates two new pds-defined functions, which can be accessed within any
template.
- `ANALYZE_TABLE(filepath, *, separator=',', crlf=None, escape='')`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.asciitable.ANALYZE_TABLE)
takes the path to an existing
ASCII table and analyzes its content, inferring details about the content and formats of
all the columns.
- `TABLE_VALUE(name, column=0)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.asciitable.TABLE_VALUE)
returns information about the content of the table for use within
the label to be generated.
For example, consider a template that contains this content:
$ONCE(ANALYZE_TABLE(LABEL_PATH().replace('.lbl', '.tab')))
...
OBJECT = TABLE
...
ROWS = $TABLE_VALUE('ROWS')$
COLUMNS = $TABLE_VALUE('COLUMNS')$
OBJECT = COLUMN
NAME = FILE_NAME
DATA_TYPE = $TABLE_VALUE("PDS3_DATA_TYPE", 1)$
START_BYTE = $TABLE_VALUE("START_BYTE", 1)$
BYTES = $TABLE_VALUE("BYTES", 1)$
FORMAT = $TABLE_VALUE("PDS3_FORMAT", 1))$
MINIMUM_VALUE = $TABLE_VALUE("MINIMUM", 1))$
MAXIMUM_VALUE = $TABLE_VALUE("MAXIMUM", 1))$
DESCRIPTION = "Name of file in the directory"
END_OBJECT = COLUMN
...
The initial call to
`ANALYZE_TABLE()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.asciitable.ANALYZE_TABLE)
is embedded inside a `ONCE` directive
because it returns no content. However, it reads the table file and assembles a database
of what it has found. The subsequent calls to it can be used for multiple labels and each
label will always contain the correct numbers of `ROWS` and `COLUMNS`. `TABLE_VALUE` can
also retrieve information about the content and format about each of the table's columns.
# tablelabel
This is a stand-alone program that can be used to validate and repair existing PDS3 labels
describing ASCII tables and can also generate new labels. Type:
tablelabel --help
for full information:
usage: tablelabel.py [-h] (--validate | --repair | --create | --save) [--template TEMPLATE]
[--numbers] [--formats] [--minmax [MINMAX ...]]
[--derived [DERIVED ...]] [--edit [EDIT ...]] [--real [REAL ...]]
[--dict [DICT ...]] [-e] [-E] [--nobackup] [--quiet] [--log] [--debug]
[--timestamps]
[path ...]
tablelabel: Validate, repair, or create a PDS3 label file for an existing ASCII table.
positional arguments:
path Path to one or more PDS3 label or ASCII table files. It is always
assumed that a label file has a ".lbl" extension and its associated
table file is in the same directory but with a ".tab" extension.
options:
-h, --help show this help message and exit
--validate, -v Validate an existing label, logging any errors or other
discrepancies as warnings messages; do not write a new label.
--repair, -r Update an existing label file only if the new label would be
different; otherwise leave it unchanged.
--create, -c Create a new label where none exists; leave existing labels alone.
--save, -s Save a new label, replacing any existing file.
--template TEMPLATE, -t TEMPLATE
An optional template file path. If specified, this template is used
to generate new label content; otherwise, an existing label is
validated or repaired.
--numbers, -n Require every column to have a COLUMN_NUMBER attribute.
--formats, -f Require every column to have a FORMAT attribute.
--minmax [MINMAX ...], -m [MINMAX ...]
One or more column names that should have the MINIMUM_VALUE and
MAXIMUM_VALUE attributes. Use "float" to include these attributes
for all floating-point columns; use "int" to include these
attributes for all integer-valued columns.
--derived [DERIVED ...]
One or more column names that should have the DERIVED_MINIMUM and
DERIVED_MAXIMUM attributes. Use "float" to include these attributes
for all floating-point columns.
--edit [EDIT ...] One or more expressions of the form "column:name=value", which will
be used to insert or replace values currently in the label.
--real [REAL ...] One or more COLUMN names that should be identified as ASCII_REAL
even if every value in the table is an integer.
--dict [DICT ...], -d [DICT ...]
One or more keyword definitions of the form "name=value", which
will be used when the label is generated. Each value must be an
integer, float, or quoted string.
-e Format values involving an exponential using lower case "e"
-E Format values involving an exponential using upper case "E"
--nobackup, -B If a file is repaired, do not save a backup of an existing file.
Otherwise, an existing file is renamed with a suffix identifying
its original creation date and time.
--quiet, -q Do not log to the terminal.
--log, -l Save a log file of warnings and steps performed. The log file will
have the same name as the label except the extension will be ".log"
instead of ".lbl".
--debug Include "debug" messages in the log.
--timestamps Include a timestamp in each log record.
Raw data
{
"_id": null,
"home_page": null,
"name": "rms-pdstemplate",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": "\"Robert S. French\" <rfrench@seti.org>",
"keywords": "pdstemplate",
"author": null,
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/40/4d/47135a0b78253d7bb26cebf7447b581c7c3f35ce5cc97a0130cf8d568e3d/rms_pdstemplate-2.1.1.tar.gz",
"platform": null,
"description": "[](https://github.com/SETI/rms-pdstemplate/releases)\n[](https://github.com/SETI/rms-pdstemplate/releases)\n[](https://github.com/SETI/rms-pdstemplate/actions)\n[](https://rms-pdstemplate.readthedocs.io/en/latest/?badge=latest)\n[](https://codecov.io/gh/SETI/rms-pdstemplate)\n<br />\n[](https://pypi.org/project/rms-pdstemplate)\n[](https://pypi.org/project/rms-pdstemplate)\n[](https://pypi.org/project/rms-pdstemplate)\n[](https://pypi.org/project/rms-pdstemplate)\n<br />\n[](https://github.com/SETI/rms-pdstemplate/commits/main/)\n[](https://github.com/SETI/rms-pdstemplate/commits/main/)\n[](https://github.com/SETI/rms-pdstemplate/commits/main/)\n<br />\n[](https://github.com/SETI/rms-pdstemplate/issues)\n[](https://github.com/SETI/rms-pdstemplate/issues)\n[](https://github.com/SETI/rms-pdstemplate/pulls)\n[](https://github.com/SETI/rms-pdstemplate/pulls)\n<br />\n\n[](https://github.com/SETI/rms-pdstemplate/stargazers)\n\n\n# Introduction\n\n`pdstemplate` is a Python module that defines the `PdsTemplate` class, which is\nused to generate PDS labels based on template files. Both PDS3 and PDS4 (xml) labels are\nsupported. Although specifically designed to facilitate data deliveries by PDS data\nproviders, the template system is generic and can be used to generate files from templates\nfor other purposes.\n\n`pdstemplate` is a product of the [PDS Ring-Moon Systems Node](https://pds-rings.seti.org).\n\n# Installation\n\nThe `pdstemplate` module is available via the `rms-pdstemplate`[](https://pypi.org/project/rms-pdstemplate)\npackage on PyPI and can be installed with:\n\n```sh\npip install rms-pdstemplate\n```\n\n# Getting Started\n\nThe general procedure is as follows:\n\n1. Create a template object by calling the\n`PdsTemplate`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate)\nconstructor to read a template file:\n\n from pdstemplate import PdsTemplate\n template = PdsTemplate(template_file_path)\n\n2. Create a dictionary that contains the parameter values to use inside the label.\n3. Construct the label using the\n`write()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.write)\nmethod as follows:\n\n template.write(dictionary, label_file)\n\nThis will create a new label of the given name, using the values in the given\ndictionary. Once the template has been constructed, steps 2 and 3 can be repeated any\nnumber of times.\n\nAlternatively, you can obtain the content of a label without writing it to a file using\n`generate()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.generate).\n\n`pdstemplate` employs the RMS Node's `rms-filecache`[](https://pypi.org/project/rms-filecache)\nmodule and its `FCPath`[](https://rms-filecache.readthedocs.io/en/latest/module.html#filecache.file_cache_path.FCPath)\nclass to support the handling of files at a website or in the cloud. You can refer to a\nremote file by URL and the `PdsTemplate` will treat it as if it were a local file.\nSee filecache's [documentation](https://rms-filecache.readthedocs.io/en/latest/index.html)\nfor further details.\n\nDetails of the `PdsTemplate` class are available in the [module documentation](https://rms-pdstemplate.readthedocs.io/en/latest/module.html).\n\n# Template Syntax\n\nA template file will look generally like a label file, except for certain embedded\nexpressions that will be replaced when the template's\n`write()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.write)\nor\n`generate()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.generate)\nmethod is called.\n\n## Substitutions\n\nIn general, everything between dollar signs \"$\" in the template is interpreted as a\nPython expression to be evaluated. The result of this expression then replaces it\ninside the label. For example, if `dictionary['INSTRUMENT_ID'] == 'ISSWA'`, then\n\n <instrument_id>$INSTRUMENT_ID$</instrument_id>\n\nin the template will become\n\n <instrument_id>ISSWA</instrument_id>\n\nin the label. The expression between \"$\" in the template can include indexes, function\ncalls, or just about any other Python expression. As another example, using the same\ndictionary above,\n\n <camera_fov>$\"Narrow\" if INSTRUMENT_ID == \"ISSNA\" else \"Wide\"$</camera_fov>\n\nin the template will become\n\n <camera_fov>Wide</camera_fov>\n\nin the label.\n\nAn expression in the template of the form `$name=expression$`, where the `name` is a\nvalid Python variable name, will also also have the side-effect of defining this\nvariable so that it can be re-used later in the template. For example, if this appears\nas an expression,\n\n $cruise_or_saturn=('cruise' if START_TIME < 2004 else 'saturn')$\n\nthen later in the template, one can write:\n\n <lid_reference>\n urn:nasa:pds:cassini_iss_$cruise_or_saturn$:data_raw:cum-index\n </lid_reference>\n\nTo embed a literal \"$\" inside a label, enter \"$$\" into the template.\n\n## Headers\n\nHeaders provide even more sophisticaed control over the content of a label. A header\nappears alone on a line of the template and begins with \"$\" as the first non-blank\ncharacter. It determines whether or how subsequent text of the template will appear in the\nfile, from here up to the next header line.\n\n### FOR and END_FOR\n\nYou can include one or more repetitions of the same text using `FOR` and `END_FOR`\nheaders. The format is\n\n $FOR(expression)\n <template text>\n $END_FOR\n\nwhere `expression` evaluates to a Python iterable. Within the `template text`, these new\nvariable names are assigned:\n\n- `VALUE` = the next value of the iterator;\n- `INDEX` = the index of this iterator, starting at zero;\n- `LENGTH` = the number of items in the iteration.\n\nFor example, if\n\n dictionary[\"targets\"] = [\"Jupiter\", \"Io\", \"Europa\"]\n dictionary[\"naif_ids\"] = [599, 501, 502],\n\nthen\n\n $FOR(targets)\n <target_name>$VALUE (naif_ids[INDEX])$</target_name>\n $END_FOR\n\nin the template will become\n\n <target_name>Jupiter (599)</target_name>\n <target_name>Io (501)</target_name>\n <target_name>Europa (502)</target_name>\n\nin the label.\n\nInstead of using the names `VALUE`, `INDEX`, and `LENGTH`, you can customize the\nvariable names by listing up to three comma-separated names and an equal sign `=`\nbefore the iterable expression. For example, this will produce the same results as the\nexample above:\n\n $FOR(name, k=targets)\n <target_name>$name (naif_ids[k])$</target_name>\n $END_FOR\n\n### IF, ELSE_IF, ELSE, and END_IF\n\nYou can also use `IF`, `ELSE_IF`, `ELSE`, and `END_IF` headers to select among\nalternative blocks of text in the template:\n\n- `IF(expression)` - Evaluate `expression` and include the next lines of the\n template if it is logically True (e.g., boolean True, a nonzero number, a non-empty\n list or string, etc.).\n- `ELSE_IF(expression)` - Include the next lines of the template if `expression` is\n logically True and every previous expression was not.\n- `ELSE` - Include the next lines of the template only if all prior\n expressions were logically False.\n- `END_IF` - This marks the end of the set of if/else alternatives.\n\nAs with other substitutions, you can define a new variable of a specified name by\nusing `name=expression` inside the parentheses of `IF()` or `ELSE_IF()`.\n\nNote that headers can be nested arbitrarily inside the template.\n\n### ONCE\n\n`ONCE` is a header that simply includes the content that follows it one time. However,\nit is useful for its side-effect, which is that `ONCE(expression)` allows the embedded\n`expression` to be evaluated without writing new text into the label. You can use this\ncapability to define variables internally without affecting the content of the label\nproduced. For example:\n\n $ONCE(date=big_dictionary[\"key\"][\"date\"])\n\nwill assign the value of the variable named `date` for subsequent use within the template.\n\n### INCLUDE\n\nThis header will read the content of another file and insert its content into the template\nhere:\n\n $INCLUDE(filename)\n\nUsing the environment variable `PDSTEMPLATE_INCLUDES`, you can define one or more\ndirectories that will be searched for a file to be included. If multiple directories are\nto be searched, they should be separated by colons. You can also specify one or more\ndirectories to search in the\n`PdsTemplate`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate)\nconstructor using the `includes` input parameter.\n\nInclude files are handled somewhat differently from other headers. When `INCLUDE`\nreferences a file as a literal string rather than as an expression to evaluate, it is\nprocessed at the time that the\n`PdsTemplate`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate)\nis constructed. However, if the\nfilename is given as an expression, it is not evaluated until the\n`write()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.write)\nor\n`generate()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.generate)\nmethod is called for each label.\n\n### NOTE and END_NOTE\n\nYou can use `NOTE` and `END_NOTE` to embed any arbitrary comment block into the\ntemplate. Any text between these headers does not appear in the label:\n\n $NOTE\n Here is an extended comment about the templae\n $END_NOTE\n\nYou can also use `$NOTE:` for an in-line comment. This text, and any blanks before it,\nare not included in the label::\n\n <filter>$FILTER$</filter> $NOTE: This is where we identify the filter\n\n## Pre-defined Functions\n\nThe following pre-defined functions can be used inside any expression in the template.\n\n\n- `BASENAME(filepath)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.BASENAME):\n The basename of `filepath`, with leading directory path removed.\n\n- `BOOL(value, true='true', false='false')`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.BOOL):\n Return \"true\" if `value` evaluates to Boolean True; otherwise, return \"false\".\n\n- `COUNTER(name, reset=False)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.COUNTER):\n The current value of a counter identified by `name`, starting at 1. If `reset` is True, the counter is reset to 0.\n\n- `CURRENT_TIME(date_only=False)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.CURRENT_TIME):\n The current time in the local time zone as a string of the form\n \"yyyy-mm-ddThh:mm:sss\" if `date_only=False` or \"yyyy-mm-dd\" if `date_only=True`.\n\n- `CURRENT_ZULU(date_only=False)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.CURRENT_ZULU):\n The current UTC time as a string of the form \"yyyy-mm-ddThh:mm:sssZ\" if\n `date_only=False` or \"yyyy-mm-dd\" if `date_only=True`.\n\n- `DATETIME(time, offset=0, digits=None)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.DATETIME):\n Convert `time` as an arbitrary date/time string or TDB seconds to an ISO date\n format with a trailing \"Z\". An optional `offset` in seconds can be applied. The\n returned string contains an appropriate number of decimal digits in the seconds\n field unless `digits` is specified explicitly. If `time` is \"UNK\", then \"UNK\" is\n returned.\n\n- `DATETIME_DOY(time, offset=0, digits=None)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.DATETIME_DOY):\n Convert `time` as an arbitrary date/time string or TDB seconds to an ISO date\n of the form \"yyyy-dddThh:mm:ss[.fff]Z\". An optional `offset` in seconds can be\n applied. The returned string contains an appropriate number of decimal digits in\n the seconds field unless `digits` is specified explicitly. If `time` is \"UNK\",\n then \"UNK\" is returned.\n\n- `DAYSECS(string)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.DAYSECS):\n The number of elapsed seconds since the most recent midnight. `time` can be\n a date/time string, a time string, or TDB seconds.\n\n- `FILE_BYTES(filepath)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.FILE_BYTES):\n The size in bytes of the file specified by `filepath`.\n\n- `FILE_MD5(filepath)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.FILE_MD5):\n The MD5 checksum of the file specified by `filepath`.\n\n- `FILE_RECORDS(filepath)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.FILE_RECORDS):\n The number of records in the the file specified by `filepath` if it is ASCII; 0\n if the file is binary.\n\n- `FILE_TIME(filepath)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.FILE_TIME):\n The modification time in the local time zone of the file specified by `filepath`\n in the form \"yyyy-mm-ddThh:mm:ss\".\n\n- `FILE_ZULU(filepath)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.FILE_ZULU):\n The UTC modification time of the the file specified by `filepath` in the form\n \"yyyy-mm-ddThh:mm:ssZ\".\n\n- `GETENV(name, default='')`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.GETENV):\n The value of any environment variable.\n\n- `LABEL_PATH()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.LABEL_PATH):\n The full directory path to the label file being written.\n\n- `LOG(level, message, filepath='', force=False)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.LOG):\n Write a message to the current log.\n\n- `NOESCAPE(text)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.NOESCAPE):\n If the template is XML, evaluated expressions are \"escaped\" to ensure that they\n are suitable for embedding in a PDS4 label. For example, \">\" inside a string will\n be replaced by `>`. This function prevents `text` from being escaped in the\n label, allowing it to contain literal XML.\n\n- `QUOTE_IF(text)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.QUOTE_IF):\n Quote the given text if it requires quotes within a PDS3 label.\n\n- `RAISE(exception, message)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.RAISE):\n Raise an exception with the given class `exception` and the `message`.\n\n- `REPLACE_NA(value, if_na, flag='N/A')`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.REPLACE_NA):\n Return `if_na` if `value` equals \"N/A\" (or `flag` if specified); otherwise, return `value`.\n\n- `REPLACE_UNK(value, if_unk)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.REPLACE_UNK):\n Return `if_unk` if `value` equals \"UNK\"; otherwise, return `value`.\n\n- `TEMPLATE_PATH()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.TEMPLATE_PATH):\n The directory path to the template file.\n\n- `VERSION_ID()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.VERSION_ID):\n Version ID of this module using two digits, e.g., \"v1.0\".\n\n- `WRAP(left, right, text, preserve_single_newlines=True)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.WRAP):\n Wrap the given text to a specified indentation and width.\n\nThese functions can also be used directly by the programmer; they are static functions\nof class PdsTemplate.\n\n# Logging and Exception Handling\n\n`pdstemplate` employs the RMS Node's `rms-pdslogger`[](https://pypi.org/project/rms-pdslogger)\nmodule to handle logging. By default, the\nlogger is a `PdsLogger`[](https://rms-pdslogger.readthedocs.io/en/latest/module.html#pdslogger.PdsLogger)\nobject, although any `logging.Logger` object will work. See\n[`pdslogger`'s documentation](https://rms-pdslogger.readthedocs.io) for further details.\n\nYou can override the default Logger using static method\n`set_logger()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.utils.set_logger).\nYou can also set the logging level (\"info\", \"warning\", \"error\", etc.) using\n`set_log_level()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.utils.set_log_level)\nand can select among many log formatting options using\n`set_log_format()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.utils.set_log_format)\nUse\n`get_logger()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.utils.get_logger)\nto obtain the current Logger.\n\nBy default, exceptions during a call to\n`write()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.write)\nor\n`generate()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.generate)\nare handled as follows:\n\n1. They are written to the log.\n2. The expression that triggered the exception is replaced by the error text in the\n label, surrounded by \"[[[\" and \"]]]\" to make it easier to find.\n3. The attributes `fatal_count`, `error_count`, and `warning_count` of the\n `PdsTemplate`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate)\n object contain the number of messages logged by each category.\n4. The exception is otherwise suppressed.\n\nThis behavior can be modified by calling method `raise_exceptions(True)` in the call to\n`write()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.write)\nor\n`generate()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.generate);\nin this case, the exception\nwill be raised, label generation will stop, and the label will not be written.\n\n# Pre-processors\n\nA pre-processor is a function that takes the text of a template file as input and returns\na new template as output. As described above, `INCLUDE` headers that contain an explicit\nfile name (rather than an expression to be evaluated) are handled by a pre-processor.\n\nYou may define your own functions to pre-process the content of a template. They must have\nthis call signature::\n\n func(path: str | Path | FCPath, content: str, *args, **kwargs) -> str\n\nwhere\n\n- `path` is the path to the template file (used here just for error logging).\n- `content` is the content of a template represented by a single string with <LF> line\n terminators.\n- `*args` is for any additional positional arguments to `func`.\n- `**kwargs` is for any additional keyword arguments to `func`.\n\nWhen you invoke the\n`PdsTemplate`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate)\nconstructor, one of the optional inputs is\n`preprocess`, which takes either a single function or a list of functions to apply after\nthe `INCLUDE` pre-processor. For the first of these, the `args` and `kwargs` inputs can be\nprovided as additional inputs to the constructor. Subsequent pre-processors cannot take\nadditional arguments; define them using lambda notation instead.\n\nNote that a\n`PdsTemplate`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate)\nobject has an attribute `content`, which contains the\nfull content of the template after all pre-processing has been performed. You can examine\nthis attribute to see the final result of all processing. Note also that when line numbers\nappear in an error message, they refer to the line number of the template after\npre-processing, not before.\n\n# `pdstemplate.pds3table`\n\n`pds3table` is a plug-in module to automate the generation and validation of PDS3 labels\nfor ASCII tables. It works in concert with the\n`asciitable` module, which analyzes\nthe content of ASCII table files. It is used by stand-alone program `tablelabel` to\nvalidate and repair existing PDS3 labels as well as to generate new labels; if\n`tablelabel` meets your needs, you can avoid any programming in Python.\n\nTo import:\n\n import pdstemplate.pds3table\n\nOnce imported, the following pre-defined functions become available for use within a\n`PdsTemplate`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate):\n\n- `ANALYZE_PDS3_LABEL(labelpath, validate=True)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.pds3table.ANALYZE_PDS3_LABEL):\n analyzes the content of a PDS3 label or template, gathering\n information about the names and other properties of its `TABLE` and `COLUMN` objects. Once\n it is called, the following functions become available.\n- `ANALYZE_TABLE(filepath, separator=',', crlf=None, escape='')`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.asciitable.ANALYZE_TABLE)\n (from `asciitable`) takes the path to an existing\n ASCII table and analyzes its content, inferring details about the content and formats of\n all the columns.\n- `VALIDATE_PDS3_LABEL(hide_warnings=False, abort_on_error=True)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.pds3table.VALIDATE_PDS3_LABEL):\n issues a warning message for any errors found in the label\n or template. Optionally, it can abort the generation of the label if it encounters an\n irrecoverable incompatibility with the ASCII table.\n- `LABEL_VALUE(name, column=0)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.pds3table.LABEL_VALUE):\n returns correct and valid PDS3 values for many of the attributes of\n PDS3 TABLE and COLUMN objects, based on its analysis of the table.\n- `OLD_LABEL_VALUE(name, column=0)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.pds3table.OLD_LABEL_VALUE):\n returns the current (although possibly incorrect or missing)\n values for many of the same PDS3 `TABLE` and `COLUMN` attributes.\n\nFor example, consider a template that contains this content:\n\n $ONCE(ANALYZE_TABLE(LABEL_PATH().replace('.lbl', '.tab')))\n $ONCE(ANALYZE_PDS3_LABEL(TEMPLATE_PATH()))\n ...\n OBJECT = TABLE\n ...\n ROWS = $LABEL_VALUE('ROWS')$\n COLUMNS = $LABEL_VALUE('COLUMNS')$\n\n OBJECT = COLUMN\n NAME = FILE_NAME\n COLUMN_NUMBER = $LABEL_VALUE(\"COLUMN_NUMBER\", \"FILE_NAME\")$\n DATA_TYPE = $LABEL_VALUE(\"DATA_TYPE\", \"FILE_NAME\")$\n START_BYTE = $LABEL_VALUE(\"START_BYTE\", \"FILE_NAME\")$\n BYTES = $LABEL_VALUE(\"BYTES\", \"FILE_NAME\")$\n FORMAT = $LABEL_VALUE(\"FORMAT\", \"FILE_NAME\")$\n MINIMUM_VALUE = $LABEL_VALUE(\"MINIMUM_VALUE\", \"FILE_NAME\")$\n MAXIMUM_VALUE = $LABEL_VALUE(\"MAXIMUM_VALUE\", \"FILE_NAME\")$\n DESCRIPTION = \"Name of file in the directory\"\n END_OBJECT = COLUMN\n ...\n\nThe initial calls to\n`ANALYZE_TABLE()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.asciitable.ANALYZE_TABLE)\nand\n`ANALYZE_PDS3_LABEL()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.pds3table.ANALYZE_PDS3_LABEL)\nare\nembedded inside a `ONCE()` directive because they return no content. The first call\nanalyzes the content and structure of the ASCII table, and the second analyzes the\ntemplate. The subsequent calls to\n`LABEL_VALUE()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.pds3table.LABEL_VALUE)\nfill in the correct values for the specified quantities.\n\nOptionally, you could include this as the third line in the template::\n\n $ONCE(VALIDATE_PDS3_LABEL())\n\nThis function logs a warnings and errors for any incorrect TABLE and COLUMN values\ncurrently in the template.\n\nThis module also provides a pre-processor, which can be used to validate or repair an\nexising PDS3 label. The function\n`pds3_table_preprocessor`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.pds3table.pds3_table_preprocessor),\nwhen used as the `preprocess` input to the\n`PdsTemplate`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate)\nconstructor, transforms an\nexisting PDS3 label into a new template by replacing all needed `TABLE` and `COLUMN`\nattributes with calls to\n`LABEL_VALUE()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.pds3table.LABEL_VALUE).\nThe effect is that when the label is\ngenerated, it is guaranteed to contain correct information where the earlier label might\nhave been incorrect. In this case, your program would look something like this:\n\n from pdstemplate import PdsTemplate\n from pdstemplate.pds3table import pds3_table_preprocessor\n\n template = PdsTemplate(label_path, crlf=True, ...\n preprocess=pds3_table_preprocessor, kwargs={...})\n template.write({}, label_path, ...)\n\nThe constructor invokes\n`pds3_table_preprocessor`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.pds3table.pds3_table_preprocessor)\nto transform the label into a\ntemplate. You can use the `kwargs` input dictionary to provide inputs to the\npre-processor, such as adding a requirement that each column contain `FORMAT`,\n`COLUMN_NUMBER`, `MINIMUM/MAXIMUM_VALUEs`, etc., and designating how warnings and errors are\nto be handled.\n\nAfterward, the call to the template's\n`write()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.PdsTemplate.write)\nmethod will\nvalidate the label and/or write a new label, depending on its input parameters.\n\nFor example, suppose the label contains this:\n\n PDS_VERSION_ID = PDS3\n RECORD_TYPE = FIXED_LENGTH\n RECORD_BYTES = 1089\n FILE_RECORDS = 1711\n ^INDEX_TABLE = \"COVIMS_0094_index.tab\"\n\n OBJECT = INDEX_TABLE\n INTERCHANGE_FORMAT = ASCII\n ROWS = 1711\n COLUMNS = 61\n ROW_BYTES = 1089\n DESCRIPTION = \"This Cassini VIMS image index ....\"\n\n OBJECT = COLUMN\n NAME = FILE_NAME\n DATA_TYPE = CHARACTER\n START_BYTE = 2\n BYTES = 25\n DESCRIPTION = \"Name of file in the directory\"\n END_OBJECT = COLUMN\n ...\n\nYou then execute this:\n\n template = PdsTemplate(label_path, crlf=True,\n preprocess=pds3_table_preprocessor,\n kwargs={'numbers': True, 'formats': True})\n\nAfter the call, you can look at the template's `content` attribute, which contains the\ntemplate's content after pre-processing. Its value is this:\n\n $ONCE(ANALYZE_TABLE(LABEL_PATH().replace(\".lbl\",\".tab\").replace(\".LBL\",\".TAB\"), crlf=True))\n $ONCE(VALIDATE_PDS3_LABEL(hide_warnings, abort_on_error))\n PDS_VERSION_ID = PDS3\n RECORD_TYPE = $LABEL_VALUE(\"RECORD_TYPE\")$\n RECORD_BYTES = $LABEL_VALUE(\"RECORD_BYTES\")$\n FILE_RECORDS = $LABEL_VALUE(\"FILE_RECORDS\")$\n\n OBJECT = INDEX_TABLE\n INTERCHANGE_FORMAT = $LABEL_VALUE(\"INTERCHANGE_FORMAT\")$\n ROWS = $LABEL_VALUE(\"ROWS\")$\n COLUMNS = $LABEL_VALUE(\"COLUMNS\")$\n ROW_BYTES = $LABEL_VALUE(\"ROW_BYTES\")$\n DESCRIPTION = \"This Cassini VIMS image index ....\"\n\n OBJECT = COLUMN\n NAME = FILE_NAME\n COLUMN_NUMBER = $LABEL_VALUE(\"COLUMN_NUMBER\", 1)$\n DATA_TYPE = $LABEL_VALUE(\"DATA_TYPE\", 1)$\n START_BYTE = $LABEL_VALUE(\"START_BYTE\", 1)$\n BYTES = $LABEL_VALUE(\"BYTES\", 1)$\n FORMAT = $QUOTE_IF(LABEL_VALUE(\"FORMAT\", 1))$\n DESCRIPTION = \"Name of file in the directory\"\n END_OBJECT = COLUMN\n ...\n\nThe `TABLE` and `COLUMN` attributes defining table format and structure have been replaced by\ncalls to\n`LABEL_VALUE()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.pds3table.LABEL_VALUE),\nwhich will provide the correct value whether or not the\nvalue in the original label was correct. Also, `COLUMN_NUMBER` and `FORMAT` have been added to\nthe COLUMN object because of the pre-processor inputs `numbers=True` and `formats=True`.\n\nAnother application of the preprocessor is to simplify the construction of a template for\nan ASCII table. Within a template, the only required attributes of a `COLUMN` object are\n`NAME` and `DESCRIPTION`. Optionally, you can also specify any special constants,\n`VALID_MINIMUM/MAXIMUM` values, `OFFSET` and `SCALING_FACTOR`, and the number of `ITEMS` if the\n`COLUMN` object describes more than one. All remaining information about the column, such as\n`DATA_TYPE`, `START_BYTE`, `BYTES`, etc., will be filled in by the pre-processor. Inputs to the\npreprocessor let you indicate whether to include `FORMATs`, `COLUMN_NUMBERs`, and the\n`MINIMUM/MAXIMUM_VALUEs` attributes automatically.\n\n# `pdstemplate.asciitable`\n\n`asciitable` is a plug-in module to assist with the labeling of ASCII tables in PDS3 and PDS4. It\nsupports the `pdstemplate.pds3table` module and the `tablelabel` tool, and will also be used by\na future `pds4table` tool. To import:\n\n import pdstemplate.asciitable\n\nThis import creates two new pds-defined functions, which can be accessed within any\ntemplate.\n\n- `ANALYZE_TABLE(filepath, *, separator=',', crlf=None, escape='')`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.asciitable.ANALYZE_TABLE)\n takes the path to an existing\n ASCII table and analyzes its content, inferring details about the content and formats of\n all the columns.\n- `TABLE_VALUE(name, column=0)`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.asciitable.TABLE_VALUE)\n returns information about the content of the table for use within\n the label to be generated.\n\nFor example, consider a template that contains this content:\n\n $ONCE(ANALYZE_TABLE(LABEL_PATH().replace('.lbl', '.tab')))\n ...\n OBJECT = TABLE\n ...\n ROWS = $TABLE_VALUE('ROWS')$\n COLUMNS = $TABLE_VALUE('COLUMNS')$\n\n OBJECT = COLUMN\n NAME = FILE_NAME\n DATA_TYPE = $TABLE_VALUE(\"PDS3_DATA_TYPE\", 1)$\n START_BYTE = $TABLE_VALUE(\"START_BYTE\", 1)$\n BYTES = $TABLE_VALUE(\"BYTES\", 1)$\n FORMAT = $TABLE_VALUE(\"PDS3_FORMAT\", 1))$\n MINIMUM_VALUE = $TABLE_VALUE(\"MINIMUM\", 1))$\n MAXIMUM_VALUE = $TABLE_VALUE(\"MAXIMUM\", 1))$\n DESCRIPTION = \"Name of file in the directory\"\n END_OBJECT = COLUMN\n ...\n\n\nThe initial call to\n`ANALYZE_TABLE()`[](https://rms-pdstemplate.readthedocs.io/en/latest/module.html#pdstemplate.asciitable.ANALYZE_TABLE)\nis embedded inside a `ONCE` directive\nbecause it returns no content. However, it reads the table file and assembles a database\nof what it has found. The subsequent calls to it can be used for multiple labels and each\nlabel will always contain the correct numbers of `ROWS` and `COLUMNS`. `TABLE_VALUE` can\nalso retrieve information about the content and format about each of the table's columns.\n\n# tablelabel\n\nThis is a stand-alone program that can be used to validate and repair existing PDS3 labels\ndescribing ASCII tables and can also generate new labels. Type:\n\n tablelabel --help\n\nfor full information:\n\n usage: tablelabel.py [-h] (--validate | --repair | --create | --save) [--template TEMPLATE]\n [--numbers] [--formats] [--minmax [MINMAX ...]]\n [--derived [DERIVED ...]] [--edit [EDIT ...]] [--real [REAL ...]]\n [--dict [DICT ...]] [-e] [-E] [--nobackup] [--quiet] [--log] [--debug]\n [--timestamps]\n [path ...]\n\n tablelabel: Validate, repair, or create a PDS3 label file for an existing ASCII table.\n\n positional arguments:\n path Path to one or more PDS3 label or ASCII table files. It is always\n assumed that a label file has a \".lbl\" extension and its associated\n table file is in the same directory but with a \".tab\" extension.\n\n options:\n -h, --help show this help message and exit\n --validate, -v Validate an existing label, logging any errors or other\n discrepancies as warnings messages; do not write a new label.\n --repair, -r Update an existing label file only if the new label would be\n different; otherwise leave it unchanged.\n --create, -c Create a new label where none exists; leave existing labels alone.\n --save, -s Save a new label, replacing any existing file.\n --template TEMPLATE, -t TEMPLATE\n An optional template file path. If specified, this template is used\n to generate new label content; otherwise, an existing label is\n validated or repaired.\n --numbers, -n Require every column to have a COLUMN_NUMBER attribute.\n --formats, -f Require every column to have a FORMAT attribute.\n --minmax [MINMAX ...], -m [MINMAX ...]\n One or more column names that should have the MINIMUM_VALUE and\n MAXIMUM_VALUE attributes. Use \"float\" to include these attributes\n for all floating-point columns; use \"int\" to include these\n attributes for all integer-valued columns.\n --derived [DERIVED ...]\n One or more column names that should have the DERIVED_MINIMUM and\n DERIVED_MAXIMUM attributes. Use \"float\" to include these attributes\n for all floating-point columns.\n --edit [EDIT ...] One or more expressions of the form \"column:name=value\", which will\n be used to insert or replace values currently in the label.\n --real [REAL ...] One or more COLUMN names that should be identified as ASCII_REAL\n even if every value in the table is an integer.\n --dict [DICT ...], -d [DICT ...]\n One or more keyword definitions of the form \"name=value\", which\n will be used when the label is generated. Each value must be an\n integer, float, or quoted string.\n -e Format values involving an exponential using lower case \"e\"\n -E Format values involving an exponential using upper case \"E\"\n --nobackup, -B If a file is repaired, do not save a backup of an existing file.\n Otherwise, an existing file is renamed with a suffix identifying\n its original creation date and time.\n --quiet, -q Do not log to the terminal.\n --log, -l Save a log file of warnings and steps performed. The log file will\n have the same name as the label except the extension will be \".log\"\n instead of \".lbl\".\n --debug Include \"debug\" messages in the log.\n --timestamps Include a timestamp in each log record.\n",
"bugtrack_url": null,
"license": "Apache-2.0",
"summary": "Class to generate PDS labels based on templates",
"version": "2.1.1",
"project_urls": {
"Documentation": "https://rms-pdstemplate.readthedocs.io/en/latest",
"Homepage": "https://github.com/SETI/rms-pdstemplate",
"Issues": "https://github.com/SETI/rms-pdstemplate/issues",
"Repository": "https://github.com/SETI/rms-pdstemplate",
"Source": "https://github.com/SETI/rms-pdstemplate"
},
"split_keywords": [
"pdstemplate"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "0a3d3ef4127ae1fa1b94b21ea7c9f3a14b6ea196004bc8e8e5f25d8b062e0163",
"md5": "e1d470100f02087dcf470fab25b63cd3",
"sha256": "d9f6103005593255b8fe6d8724677e6d9eb497ecac4761fa80200e578bb8a12f"
},
"downloads": -1,
"filename": "rms_pdstemplate-2.1.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "e1d470100f02087dcf470fab25b63cd3",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 65421,
"upload_time": "2025-01-17T18:45:24",
"upload_time_iso_8601": "2025-01-17T18:45:24.808087Z",
"url": "https://files.pythonhosted.org/packages/0a/3d/3ef4127ae1fa1b94b21ea7c9f3a14b6ea196004bc8e8e5f25d8b062e0163/rms_pdstemplate-2.1.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "404d47135a0b78253d7bb26cebf7447b581c7c3f35ce5cc97a0130cf8d568e3d",
"md5": "9dd129bfdb063cda7bab29a2c89aa8db",
"sha256": "ad42fa53558880cfde5ceeba68f7ff36bd8692d12ffb58b2e98f2a865cfe0ba3"
},
"downloads": -1,
"filename": "rms_pdstemplate-2.1.1.tar.gz",
"has_sig": false,
"md5_digest": "9dd129bfdb063cda7bab29a2c89aa8db",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 199484,
"upload_time": "2025-01-17T18:45:26",
"upload_time_iso_8601": "2025-01-17T18:45:26.382170Z",
"url": "https://files.pythonhosted.org/packages/40/4d/47135a0b78253d7bb26cebf7447b581c7c3f35ce5cc97a0130cf8d568e3d/rms_pdstemplate-2.1.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-01-17 18:45:26",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "SETI",
"github_project": "rms-pdstemplate",
"travis_ci": false,
"coveralls": true,
"github_actions": true,
"requirements": [
{
"name": "coverage",
"specs": []
},
{
"name": "flake8",
"specs": []
},
{
"name": "myst-parser",
"specs": []
},
{
"name": "pytest",
"specs": []
},
{
"name": "rms-filecache",
"specs": []
},
{
"name": "rms-julian",
"specs": []
},
{
"name": "rms-pdslogger",
"specs": [
[
">=",
"3.0.0"
]
]
},
{
"name": "sphinx",
"specs": []
},
{
"name": "sphinxcontrib-napoleon",
"specs": []
},
{
"name": "sphinx-rtd-theme",
"specs": []
}
],
"lcname": "rms-pdstemplate"
}