losalamos.documents#
Handle Documents editing, managing and builds.
Class hierarchy#
DataSet
└── Document (base for all document types)
└── DocumentTeX (base for all TeX documents)
├── Preprint (preprint / arXiv-style)
├── Report (standard report)
├── ReportLarge (multi-chapter report)
└── Memo (short internal memo)
Template resolution#
Every new() call merges up to three template layers in priority order:
BASE_TEMPLATE ← VARIANT_TEMPLATE ← template_overlay (user)
BASE_TEMPLATEdefined onDocumentTeX; canonical TeX base sharedby all document types.
VARIANT_TEMPLATEdefined per subclass; files that differentiate thedocument type (cover, class options, etc.).
template_overlayoptional user-supplied folder for private orclient-specific files (logos, confidential covers).
Each layer wins over the one to its left. Files absent from a layer are inherited transparently from the layer below.
Functions
|
Classes
|
Base class for all document types. |
|
Base class for all TeX-based document types. |
|
|
|
- class losalamos.documents.Document(name='MyDocument', alias='Doc')[source]#
Bases:
DataSetBase class for all document types.
Handles generic file loading and the three-layer template merge used by
new(). Subclasses specialise behaviour by overridingBASE_TEMPLATEandVARIANT_TEMPLATE.- BASE_TEMPLATE = PosixPath('/home/runner/work/losalamos/losalamos/src/losalamos/data/templates/documents/txt/demo')#
- VARIANT_TEMPLATE = None#
- new(name, folder, template_overlay=None)[source]#
Create a new document folder by materializing template files into a target location.
This method creates a folder named
nameinsidefolderand populates it by merging up to three template layers in the following priority order:BASE_TEMPLATE ← VARIANT_TEMPLATE ← template_overlay
Each layer wins over the one to its left. Layers that are
Noneare skipped transparently. The caller is responsible for ensuring the target path does not already exist; if it does, aFileExistsErroris raised immediately and no files are written.Single-layer mode (only
BASE_TEMPLATE, bothVARIANT_TEMPLATEandtemplate_overlayareNone):The entire
BASE_TEMPLATEdirectory tree is copied as-is into the target folder, preserving all subdirectories and files.Multi-layer mode (one or both of
VARIANT_TEMPLATE,template_overlayare provided):All active layers are scanned recursively and merged into the target.
Files present in only one layer are copied from that layer.
Files present in multiple layers are resolved in favour of the highest-priority layer (right-most in the chain).
Merge example — three active layers
BASE_TEMPLATE/ VARIANT_TEMPLATE/ template_overlay/ ├── main.tex ├── main.tex └── cover.tex ├── preamble.tex └── cover.tex └── refs.bib # Resolution: # main.tex → VARIANT_TEMPLATE (highest priority) # cover.tex → template_overlay (highest priority) # preamble.tex → BASE_TEMPLATE (only source) # refs.bib → BASE_TEMPLATE (only source) # Result in target: folder/name/ ├── main.tex # from VARIANT_TEMPLATE ├── cover.tex # from template_overlay ├── preamble.tex # from BASE_TEMPLATE └── refs.bib # from BASE_TEMPLATE
- Parameters:
name (str) – Name of the new document folder to be created inside
folder.folder (str or pathlib.Path) – Parent directory under which the new document folder is created. Must already exist as a directory on the filesystem.
template_overlay (str, pathlib.Path, or None) – Optional user-supplied directory whose files take priority over both
BASE_TEMPLATEandVARIANT_TEMPLATE. Intended for private or client-specific files (logos, confidential covers, etc.). WhenNone(default), only the class-level templates are used. When provided, must be a valid existing directory; otherwiseNotADirectoryErroris raised before any files are written.
- Raises:
FileExistsError – If the target folder
folder/namealready exists. No files are created or modified in this case.NotADirectoryError – If
template_overlayis provided but does not point to a valid directory. Validated before any files are written.
- Returns:
Absolute path to the newly created document folder.
- Return type:
pathlib.Path
Usage example
# Single-layer: base template only (Document base class) doc = Document(name="MyDoc", alias="D") target = doc.new("my_doc", "/home/user/documents") # Two-layer: base + variant (subclass sets VARIANT_TEMPLATE) report = Report(name="AnnualReport", alias="AR") target = report.new("annual_report", "/home/user/documents") # Three-layer: base + variant + private user overlay target = report.new( "client_report", "/home/user/documents", template_overlay="/home/user/private/client_x", )
- class losalamos.documents.DocumentTeX(name='MyDocTeX', alias='TeX')[source]#
Bases:
DocumentBase class for all TeX-based document types.
Extends
Documentwith TeX-specific behaviour: main-file detection, PDF compilation vialatexmk, and document flattening. Subclasses setVARIANT_TEMPLATEto select their template layer.- BASE_TEMPLATE = PosixPath('/home/runner/work/losalamos/losalamos/src/losalamos/data/templates/documents/tex/base')#
- VARIANT_TEMPLATE = None#
- load_data(file_data)[source]#
Load data from a TeX file and detect whether it is a main document.
A file is considered a main document if it contains a
\documentclassdeclaration. Partial files (chapters, sections) intended to be\input-ted by a main file will haveis_mainset toFalse.- Parameters:
file_data (str or pathlib.Path) – Path to the
.texfile to load.- Returns:
None
- Return type:
None
- to_pdf(file_output=None, cleanup=True, command='pdf')[source]#
Compile the TeX document into a PDF using
latexmk.Only executes when
is_mainisTrue; partial files are silently skipped.The method temporarily switches the working directory to the source file’s parent folder before invoking
latexmk, ensuring that all relative paths in the document (\input,\include, figures, etc.) resolve correctly regardless of where the Python process was launched from. The original working directory is always restored, even if compilation raises an exception.A project-local
latexmkrcfile is expected at the root of the document folder. It is passed explicitly via-r latexmkrcso that glossaries and nomenclature are built correctly on all platforms without requiring a globallatexmkrcinstallation.- Parameters:
file_output (str or pathlib.Path or None) – Desired output path for the compiled PDF. When
None(default), the PDF is left next to the source.texfile.cleanup (bool) – If
True(default), removes auxiliary files produced bylatexmkafter a successful compilation (equivalent tolatexmk -c).command (str) –
latexmkengine flag controlling the PDF backend. Common values:"pdf"(pdflatex),"pdflua"(lualatex),"pdfxe"(xelatex).
- Returns:
None
- Return type:
None
- to_flat(file_output=None, compile_first=True, command='pdf')[source]#
Flatten the TeX document by recursively resolving
\input/\includedirectives and embedding the compiled bibliography.Delegates the flattening work to
make_flat(). Only executes whenis_mainisTrue; partial files returnNonesilently.- Parameters:
file_output (str or pathlib.Path or None) – Desired output path for the flattened
.texfile. WhenNone, the source file is overwritten in-place.compile_first (bool) – If
True(default), compiles the document before flattening so that the.bblfile reflects the current bibliography state. The intermediate PDF and.bblare removed after flattening.command (str) –
latexmkengine flag passed toto_pdf()whencompile_first=True.
- Returns:
The fully flattened LaTeX document as a string, or
Noneifis_mainisFalse.- Return type:
str or None
- export(folder_root, name, flatten=False, split=False)[source]#
Export the TeX document to a self-contained folder.
Creates
folder_root/name/and writes the document into it according to the chosen mode:Plain (
flatten=False,split=False): the source file is copied as-is, retaining its original filename.Flatten (
flatten=True,split=False):\input/\includedirectives are resolved recursively and the result is written as a single file named<name>.tex.Split (
split=True): implies flattening. The document is first flattened into a temporary file, then split intopreamble.texandmain.tex. The intermediate flat file is removed after splitting.
- Parameters:
folder_root (str or pathlib.Path) – Parent directory under which the export folder is created.
name (str) – Name of the export folder and, in flatten mode, stem of the output
.texfile.flatten (bool) – If
True, resolve all\input/\includedirectives into a single file before exporting. Ignored whensplit=True(flattening is always performed in that case).split (bool) – If
True, flatten the document and split the result intopreamble.texandmain.tex. Takes precedence overflatten.
- Returns:
Path to the created export folder.
- Return type:
pathlib.Path
- Raises:
RuntimeError – If the document is not a main file (i.e.
\documentclassis absent). Theis_mainattribute is re-evaluated fromfile_databefore the check so stale state does not produce a false negative.FileExistsError – If
folder_root/namealready exists.
- static split_preamble(input_tex, preamble_name='preamble', main_name='main', output_folder=None)[source]#
Split a main TeX file into a preamble fragment and a stub main file.
The preamble fragment receives everything between the
\documentclassline and\begin{document}(both exclusive). The stub main file retains\documentclass, replaces the preamble block with a single\input{<preamble_name>}line, and keeps everything from\begin{document}onward intact.Resulting structure:
% <main_name>.tex \documentclass{...} \input{preamble} \begin{document} ... \end{document} % <preamble_name>.tex \usepackage{...} \newcommand{...} ...
- Parameters:
input_tex (str or pathlib.Path) – Path to the root
.texfile to split. Must be a main document containing\documentclassand\begin{document}.preamble_name (str) – Stem for the preamble output file, without extension. Defaults to
"preamble".main_name (str) – Stem for the stub main output file, without extension. Defaults to
"main".output_folder (str or pathlib.Path or None) – Directory where both output files are written. When
None(default), the parent directory ofinput_texis used.
- Returns:
Tuple of
(preamble_path, main_path)as resolvedpathlib.Pathobjects.- Return type:
tuple[pathlib.Path, pathlib.Path]
- Raises:
ValueError – If
input_texdoes not contain a\documentclassdeclaration (i.e. is not a main TeX file), if\begin{document}is not found, or if the input file stem matches an output stem when both resolve to the same directory.
- static make_flat(input_tex, output_tex=None)[source]#
Flatten a TeX document by recursively resolving
\input/\includedirectives and embedding the compiled bibliography.This is a pure file-system operation with no dependency on any
DocumentTeXinstance. It can be used independently whenever a self-contained.texfile is needed (e.g. arXiv submission).- Parameters:
input_tex (str or pathlib.Path) – Path to the root
.texfile to flatten.output_tex (str or pathlib.Path or None) – Destination path for the flattened file. When
None(default), the result is written back over the source file in-place.
- Returns:
The fully flattened LaTeX source as a string.
- Return type:
str
- static lint_paragraphs(input_tex, output_tex=None)[source]#
Join wrapped prose paragraphs in a LaTeX file into single lines.
Reads
input_tex, joins each wrapped prose paragraph in the document body into a single source line, and writes the result tooutput_tex.This function performs no backup of its own. When
output_texisNone(or equal toinput_tex), the input file is overwritten in place; callers that want a safety copy should take one beforehand (seebackup_file()).- Parameters:
input_tex (str or pathlib.Path) – Path to the source
.texfile to read.output_tex (str or pathlib.Path or None) – Path to write the linted result to. If
None(the default),input_texis overwritten in place.
- Returns:
The path the linted
.texwas written to (equal toinput_texifoutput_texwasNone).- Return type:
pathlib.Path
- Raises:
FileNotFoundError – If
input_texdoes not exist.UnicodeDecodeError – If
input_texis not valid UTF-8.
Algorithm#
Everything up to and including
\begin{document}, and everything from\end{document}onward, is passed through unchanged. (The preamble’s\newcommandshorthand definitions are one-per-line by convention and must stay that way.)Within the document body, consecutive lines are grouped into “paragraphs”: maximal runs of lines that are not blank, not
\begin{...}/\end{...}, not comment-only, and not inside a non-reflowable environment. Each run is joined into a single line: every line is stripped of leading/trailing whitespace, joined with a single space, and any remaining runs of spaces are collapsed to one.The following end the current paragraph and are emitted unchanged on their own line, without being joined to anything: blank lines;
\begin{...}/\end{...}lines (which also push/pop an environment stack used to track non-reflowable regions, including nested ones, by scanning the whole line – e.g. apmatrixopened and closed on the same line inside anequation/alignnets to zero rather than leaking a permanently elevated non-reflowable count); a comment-only line that appears between paragraphs; and\[/\]display-math delimiters on their own line (tracked like a pseudo-environment), or inline\[ ... \]on one line.A
%comment that appears inside a paragraph – either a comment-only line with real content already buffered, or a trailing% ...after real content – does not split the paragraph. The real content (if any) is kept in the paragraph, and the comment text is deferred and appended to the end of the paragraph’s joined line once it is flushed. This avoids an editorial comment in the middle of a sentence cutting it into two “paragraphs”, the second starting mid-sentence (lower-case).\itemalways starts a new paragraph (flush before it), but its own wrapped continuation lines still join onto it – so consecutive list items each end up on their own line.The following commands always stand alone on their own output line, never merged with the paragraph before or after them, even without a blank line in the source:
\tcblower,\newpage,\clearpage,\cleardoublepage,\pagebreak,\newline,\figplaceholder,\boxfigplaceholder,\boxbiofigplaceholder, and\chapter/\section/\subsection/\subsubsection(starred or not).Non-reflowable environments (passed through verbatim, line-by-line, including any blank lines inside them):
equation,align,gather,multline,eqnarray,alignatand their starred forms;array,cases,matrix/pmatrix/bmatrix/vmatrix/Vmatrix;tabular,tabular*,tabularx,longtable;verbatim,lstlisting,minted,Verbatim; and\[ ... \]display math.
Note
Only line breaks and runs of whitespace within a joined paragraph are changed; no other characters are altered, so the rendered PDF is unaffected.
Example#
from pathlib import Path lint_paragraphs(Path("chapter07_T12.tex")) # overwrite in place lint_paragraphs("chapter07_T12.tex", "chapter07_T12_linted.tex")
- static lint_decorations_remove(input_tex, output_tex=None, document_only=False, except_chars=None)[source]#
Remove decoration comments/rulers (no letter characters after
%) from a LaTeX file.Reads
input_tex, strips every decorative comment (comment-only lines and trailing comment suffixes on prose lines whose comment text contains no ASCII letter), and writes the result tooutput_tex.This function performs no backup of its own. When
output_texisNone(or equal toinput_tex), the input file is overwritten in place; callers that want a safety copy should take one beforehand (seebackup_file()).- Parameters:
input_tex (str or pathlib.Path) – Path to the source
.texfile to read.output_tex (str or pathlib.Path or None) – Path to write the purged result to. If
None(the default),input_texis overwritten in place.document_only (bool) – If
False(the default), the purge runs over the entire file – preamble, body, and postamble alike. IfTrue, only the lines between\begin{document}and\end{document}are processed; the preamble and postamble are passed through unchanged.except_chars (list[str] or None) – Optional list of single characters. A decoration comment (one whose text after
%contains no ASCII letter) is nonetheless kept if its text contains at least one character from this list. Use this to preserve specific ruler styles, e.g.except_chars=['#']keeps% ####lines.None(the default) means no exceptions – all decoration comments are removed.
- Returns:
The path the purged
.texwas written to (equal toinput_texifoutput_texwasNone).- Return type:
pathlib.Path
- Raises:
FileNotFoundError – If
input_texdoes not exist.UnicodeDecodeError – If
input_texis not valid UTF-8.
Algorithm#
When
document_only=True, everything up to and including\begin{document}, and everything from\end{document}onward, is passed through unchanged. Whendocument_only=False(default), all lines – including the preamble – are processed uniformly.Within the processed region each line is classified as one of:
Blank – passed through unchanged.
Comment-only decoration – the entire line’s non-whitespace content is a
%whose following text (stripped) contains no ASCII letter and noexcept_charscharacter. The line is dropped; if the previous output line was blank, that blank is also removed (blank-line collapse).Comment-only kept – comment-only but preserved because the text contains a letter or an
except_charscharacter. Passed through unchanged.Prose with trailing decoration comment – the
%suffix qualifies as decoration. The suffix (and any whitespace before it) is stripped; the prose remainder is kept.Prose with trailing real comment – kept verbatim.
Prose without any comment – kept verbatim.
The
%position is found via_find_comment_pos, which correctly handles escaped percent signs (\%).Non-reflowable environments (
equation,align,tabular,verbatim, etc.) receive no special treatment here: comment lines inside math or table environments are still subject to the same rule.
Note
Only comment text is removed; no other characters are altered, so the rendered PDF is unaffected.
- static lint_decorations_add(input_tex, output_tex=None, ruler_chars=None, ruler_len=60)[source]#
Inject decoration rulers above section headings in a LaTeX file.
Reads
input_tex, inserts a comment ruler (% `` + N repetitions of a level-specific character) on the line immediately before every ``\chapter,\section,\subsection, and\subsubsectioncommand found inside the document body, then writes the result tooutput_tex.The preamble (everything up to and including
\begin{document}) and the postamble (\end{document}and everything after) are passed through unchanged.If a ruler with the correct character already exists on the line immediately before a heading, it is replaced rather than duplicated, making the function safe to call multiple times on the same file (idempotent with respect to ruler content; ruler length is always normalised to the current
ruler_len).This function performs no backup of its own. When
output_texisNone(or equal toinput_tex), the input file is overwritten in place; callers that want a safety copy should take one beforehand (seebackup_file()).- Parameters:
input_tex (str or pathlib.Path) – Path to the source
.texfile to read.output_tex (str or pathlib.Path or None) – Path to write the result to. If
None(the default),input_texis overwritten in place.ruler_chars (str or list[str] or None) – Sequence of up to four characters, one per heading level (chapter, section, subsection, subsubsection). Missing positions fall back to the defaults
('#', '*', '=', '-'). May be a string (each character is used positionally) or any sequence of single characters.Noneuses all defaults.ruler_len (int) – Number of times the ruler character is repeated after the
% `` prefix. Defaults to ``60.
- Returns:
The path the result was written to.
- Return type:
pathlib.Path
- Raises:
FileNotFoundError – If
input_texdoes not exist.UnicodeDecodeError – If
input_texis not valid UTF-8.ValueError – If
ruler_lenis less than 1.
Algorithm#
Split the file into preamble, body lines, and postamble at
\begin{document}/\end{document}. Only the body is processed.Resolve the four ruler strings from
ruler_charsandruler_len.Walk body lines. When a line matches a heading command, look at the previous output line:
If it is already a ruler whose dominant character matches the current level’s character, replace it with the freshly built ruler (normalises length on re-runs).
Otherwise insert the ruler as a new line before the heading.
Reassemble preamble + processed body + postamble and write.
Note
A “ruler line” for replacement detection is defined as a line whose stripped content matches
% <char><char>...with at least one repetition of char – the same format this function produces. Any other comment line is left untouched even if it precedes a heading.
- static lint_blank_lines(input_tex, output_tex=None)[source]#
Normalize blank-line spacing in a LaTeX file.
Reads
input_texand enforces two rules throughout the document body:Paragraph separation – any run of two or more consecutive blank lines in prose is collapsed to exactly one blank line.
Environment padding – every
\begin{...}is preceded by exactly two blank lines, and every\end{...}is followed by exactly two blank lines. The interior of every environment (everything between its\beginand\end) is left completely untouched.
Schematically, the target layout is:
<prose> <blank> <blank> \begin{equation} ... <- interior: never touched \end{equation} <blank> <blank> <prose>
Nested environments produce nested padding:
<blank> <blank> \begin{subequations} <blank> <blank> \begin{align} ... \end{align} <blank> <blank> \end{subequations} <blank> <blank>
The preamble (everything up to and including
\begin{document}) and the document environment boundary itself are exempt from padding.This function performs no backup of its own. When
output_texisNone(or equal toinput_tex), the input file is overwritten in place; callers that want a safety copy should take one beforehand.- Parameters:
input_tex (str or pathlib.Path) – Path to the source
.texfile to read.output_tex (str or pathlib.Path or None) – Path to write the result to. If
None(the default),input_texis overwritten in place.
- Returns:
(output_path, stats)wherestatsis a dict with keyscollapsed(excess blank lines removed) andpadded(blank lines inserted).- Return type:
tuple[pathlib.Path, dict]
- Raises:
FileNotFoundError – If
input_texdoes not exist.UnicodeDecodeError – If
input_texis not valid UTF-8.
Algorithm#
A single forward pass over the lines maintains:
env_depth– nesting depth of padded environments (0 = prose).pending_pad– number of blank lines still to be emitted after an\end{...}before the next non-blank content.
When a
\begin{...}is encountered (outside the preamble and outside the document environment): any trailing blank lines in the output buffer are stripped, exactly two blank lines are inserted, and then the\beginline itself is emitted.env_depthis incremented, and all subsequent lines (including blank ones) are passed through verbatim until the matching\end{...}is reached. On\end{...}:env_depthis decremented, the\endline is emitted, andpending_padis set to 2. The next non-blank line flushes those two blank lines first. Any blank lines in the source between\endand the next content are discarded (replaced by the controlled padding).
- class losalamos.documents.Essay(name='MyDocTeX', alias='TeX')[source]#
Bases:
DocumentTeX