import dataclasses
import pathlib
import matplotlib.figure
import astropy.units as u
import uuid
import pylatex
from pylatex import (
Command,
NoEscape,
Package,
Marker,
Label,
Ref,
)
from . import _formatting
__all__ = [
"Command",
"text_width_inches",
"column_width_inches",
"textwidth",
"columnwidth",
"Title",
"Affiliation",
"Author",
"Acronym",
"Variable",
"Abstract",
"Section",
"Subsection",
"Subsubsection",
"FigureStar",
"Fig",
"LeftFig",
"RightFig",
"Gridline",
"Document",
"NoEscape",
"Package",
"Marker",
"Label",
"Figure",
"Bibliography",
]
text_width_inches = 513.11743 / 72
column_width_inches = 242.26653 / 72
textwidth = pylatex.Command("textwidth")
columnwidth = pylatex.Command("columnwidth")
[docs]
@dataclasses.dataclass
class Title(pylatex.base_classes.LatexObject):
name: str
[docs]
def dumps(self) -> str:
return pylatex.Command("title", self.name).dumps()
[docs]
@dataclasses.dataclass
class Affiliation(pylatex.base_classes.LatexObject):
"""Organization that an author is associated with"""
name: str
"""human-readable name of the organization"""
[docs]
def dumps(self) -> str:
return pylatex.Command("affiliation", self.name).dumps()
[docs]
@dataclasses.dataclass
class Author(pylatex.base_classes.LatexObject):
"""One of the authors of this article"""
name: str
"""Name of the author"""
affiliation: Affiliation
"""The organization affiliated with the author"""
orcid: None | str = None
"""The optional ORCID of the author."""
email: None | str = None
"""
The optional email address of the author.
If this is not :obj:`None`, this author is assumed to be the corresponding
author.
"""
[docs]
def dumps(self) -> str:
author = pylatex.Command(
command="author",
arguments=self.name,
options=NoEscape(self.orcid) if self.orcid is not None else None,
).dumps()
affilation = self.affiliation.dumps()
result = f"{author}\n{affilation}"
if self.email is not None:
corresponding_author = pylatex.Command(
command="correspondingauthor",
arguments=self.name,
).dumps()
email = pylatex.Command(
command="email",
arguments=self.email,
).dumps()
result += f"\n{corresponding_author}\n{email}"
return result
[docs]
@dataclasses.dataclass
class Acronym(pylatex.base_classes.LatexObject):
acronym: str
name_full: str
name_short: None | str = None
plural: bool = False
short: bool = False
def __post_init__(self):
self.packages.append(pylatex.Package("acronym"))
[docs]
def dumps(self):
name_short = self.name_short
if name_short is None:
name_short = self.acronym
command = pylatex.Command(
command="newacro",
arguments=[
self.acronym,
],
options=[name_short],
extra_arguments=[
pylatex.NoEscape(self.name_full),
],
).dumps()
command += pylatex.Command(
command="newcommand",
arguments=[
pylatex.NoEscape(rf"\{self.acronym}"),
pylatex.NoEscape(rf"\ac{{{self.acronym}}}"),
],
).dumps()
if self.plural:
command += pylatex.Command(
command="newcommand",
arguments=[
pylatex.NoEscape(rf"\{self.acronym}s"),
pylatex.NoEscape(rf"\acp{{{self.acronym}}}"),
],
).dumps()
if self.short:
command += pylatex.Command(
command="newcommand",
arguments=[
pylatex.NoEscape(rf"\{self.acronym}Short"),
pylatex.NoEscape(rf"\acs{{{self.acronym}}}"),
],
).dumps()
return command
[docs]
@dataclasses.dataclass
class Variable(pylatex.base_classes.LatexObject):
"""
A wrapper around the ``\\newcommand`` LaTeX command.
"""
name: str
"""The name of the variable."""
value: float | u.Quantity
"""The value of the variable."""
@property
def _name(self) -> str:
return NoEscape(f"\\{self.name}")
@property
def _value(self) -> str:
v = self.value
if isinstance(v, u.Quantity):
v = f"{v:latex_inline}"
v = rf"\ensuremath{{{v[1:~0]}}}"
else:
v = str(v)
return NoEscape(v)
[docs]
def dumps(self) -> str:
return Command(
command="newcommand",
arguments=[self._name, self._value],
).dumps()
[docs]
class Abstract(pylatex.base_classes.Environment):
def __init__(
self,
*,
options: None | str | list[str] = None,
arguments: None | str | list[str] = None,
start_arguments: None | str | list[str] = None,
**kwargs,
):
super().__init__(
options=options,
arguments=arguments,
start_arguments=start_arguments,
**kwargs,
)
self.escape = False
[docs]
class Section(pylatex.Section):
def __init__(
self,
title: None | str = None,
numbering: None | bool = None,
*,
label: pylatex.Label | bool | str = True,
**kwargs,
):
super().__init__(
title=title,
numbering=numbering,
label=label,
**kwargs,
)
self.escape = False
def __format__(self, format_spec):
return pylatex.Ref(self.label.marker).dumps()
[docs]
class Subsection(
Section,
pylatex.Subsection,
):
pass
[docs]
class Subsubsection(
Subsection,
pylatex.Subsubsection,
):
pass
[docs]
class Fig(pylatex.base_classes.CommandBase):
r"""
An AASTeX 6+ `\fig command <https://journals.aas.org/aastex-v6-3-author-guide/#new_figure_features>`_
"""
def __init__(
self,
file: pathlib.Path,
width: str,
caption: str,
):
super().__init__(
arguments=[
NoEscape(str(file.resolve())),
width,
caption,
]
)
[docs]
class LeftFig(Fig):
pass
[docs]
class RightFig(Fig):
pass
[docs]
class Gridline(pylatex.base_classes.CommandBase):
r"""
An AASTeX 6+ `\gridline command <https://journals.aas.org/aastex-v6-3-author-guide/#new_figure_features>`_
"""
def __init__(
self,
figures: list[Fig],
):
super().__init__(
arguments=figures,
)
[docs]
class Document(pylatex.Document):
def __init__(
self,
default_filepath: str | pathlib.Path = "default_filepath",
documentclass: str = "aastex631",
document_options: None | str | list[str] = None,
fontenc: str = "T1",
inputenc: str = "utf8",
font_size: str = "normalsize",
lmodern: bool = True,
textcomp: bool = True,
microtype: None = None,
page_numbers: bool = True,
indent: None | bool = None,
geometry_options: None | dict = None,
data: None | list = None,
):
if document_options is None:
document_options = ["twocolumn"]
super().__init__(
default_filepath=str(default_filepath),
documentclass=documentclass,
document_options=document_options,
fontenc=fontenc,
inputenc=inputenc,
font_size=font_size,
lmodern=lmodern,
textcomp=textcomp,
microtype=microtype,
page_numbers=page_numbers,
indent=indent,
geometry_options=geometry_options,
data=data,
)
self.escape = False
self.preamble.append(
NoEscape(
"\\usepackage{savesym}\n"
"\\savesymbol{tablenum}\n"
"\\usepackage{siunitx}\n"
"\\restoresymbol{SIX}{tablenum}\n"
)
)
self.preamble.append(pylatex.Command("bibliographystyle", "aasjournal"))
[docs]
def set_variable_quantity(
self,
name: str,
value: u.Quantity,
scientific_notation: None | bool = None,
digits_after_decimal: int = 3,
) -> None:
"""
Similar to :meth:`set_variable`, but allows for ``value`` to be an
instance of :class:`astropy.units.Quantity`.
Parameters
----------
name
The name to set for the variable
value
The value to set for the variable
scientific_notation
Flag controlling whether to use scientific notation.
If :obj:`None`, scientific notation is used if ``np.all(values.abs() < .1)``
digits_after_decimal
Number of digits to include after the decimal
"""
self.set_variable(
name=name,
value=pylatex.NoEscape(
_formatting.format_quantity(
a=value,
scientific_notation=scientific_notation,
digits_after_decimal=digits_after_decimal,
)
),
)
[docs]
class Bibliography(pylatex.base_classes.CommandBase):
def __init__(
self,
sources: str,
):
super().__init__(
arguments=sources,
)