From 0b4aeb296f3aafa59e636eb9b23e330b7f4acb13 Mon Sep 17 00:00:00 2001 From: Jelte Fennema Date: Sat, 15 Jul 2023 23:58:17 +0200 Subject: [PATCH] Run black on all files --- docs/gen_example_title.py | 8 +- docs/source/conf.py | 182 ++++++++++++++------------ pylatex/__init__.py | 59 +++++++-- pylatex/_version.py | 157 +++++++++++++--------- pylatex/base_classes/__init__.py | 11 +- pylatex/base_classes/command.py | 70 +++++----- pylatex/base_classes/containers.py | 38 +++--- pylatex/base_classes/float.py | 4 +- pylatex/base_classes/latex_object.py | 27 ++-- pylatex/basic.py | 4 +- pylatex/config.py | 1 + pylatex/document.py | 161 +++++++++++++---------- pylatex/figure.py | 58 +++++---- pylatex/frames.py | 2 +- pylatex/headfoot.py | 24 ++-- pylatex/labelref.py | 24 ++-- pylatex/lists.py | 7 +- pylatex/math.py | 30 ++--- pylatex/package.py | 4 +- pylatex/position.py | 47 +++---- pylatex/quantities.py | 40 +++--- pylatex/section.py | 12 +- pylatex/table.py | 188 ++++++++++++++++----------- pylatex/tikz.py | 157 +++++++++++----------- pylatex/utils.py | 65 ++++----- setup.py | 131 ++++++++++--------- 26 files changed, 853 insertions(+), 658 deletions(-) diff --git a/docs/gen_example_title.py b/docs/gen_example_title.py index a268f475..23e33a46 100644 --- a/docs/gen_example_title.py +++ b/docs/gen_example_title.py @@ -2,11 +2,11 @@ title = sys.argv[1] -if title.endswith('_ex'): +if title.endswith("_ex"): title = title[:-3] -title = title.replace('_', ' ') -title = title.capitalize() + ' example' +title = title.replace("_", " ") +title = title.capitalize() + " example" print(title) -print(len(title) * '=') +print(len(title) * "=") diff --git a/docs/source/conf.py b/docs/source/conf.py index be410172..35dee0b6 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -17,6 +17,7 @@ # Needed for old sphinx version to work import collections + if sys.version_info >= (3, 10): collections.Callable = collections.abc.Callable @@ -28,7 +29,7 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath('../../')) +sys.path.insert(0, os.path.abspath("../../")) from pylatex import __version__ # -- General configuration ------------------------------------------------ @@ -40,17 +41,17 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.doctest', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.mathjax', - 'sphinx.ext.ifconfig', - 'sphinx.ext.intersphinx', - 'sphinx.ext.autosummary', - 'sphinx.ext.extlinks', - 'sphinx.ext.napoleon', - 'sphinx.ext.linkcode', + "sphinx.ext.autodoc", + "sphinx.ext.doctest", + "sphinx.ext.todo", + "sphinx.ext.coverage", + "sphinx.ext.mathjax", + "sphinx.ext.ifconfig", + "sphinx.ext.intersphinx", + "sphinx.ext.autosummary", + "sphinx.ext.extlinks", + "sphinx.ext.napoleon", + "sphinx.ext.linkcode", ] napoleon_include_special_with_doc = False @@ -58,30 +59,30 @@ numpydoc_class_members_toctree = False # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'PyLaTeX' -copyright = '2015, Jelte Fennema' -author = 'Jelte Fennema' +project = "PyLaTeX" +copyright = "2015, Jelte Fennema" +author = "Jelte Fennema" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = __version__.rstrip('.dirty') +version = __version__.rstrip(".dirty") # The full version, including alpha/beta/rc tags. release = version @@ -98,9 +99,9 @@ # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' -autodoc_member_order = 'bysource' -autodoc_default_flags = ['inherited-members'] -autoclass_content = 'both' +autodoc_member_order = "bysource" +autodoc_default_flags = ["inherited-members"] +autoclass_content = "both" def auto_change_docstring(app, what, name, obj, options, lines): @@ -111,54 +112,70 @@ def auto_change_docstring(app, what, name, obj, options, lines): - Add a title to module docstrings - Merge lines that end with a '\' with the next line. """ - if what == 'module' and name.startswith('pylatex'): - lines.insert(0, len(name) * '=') + if what == "module" and name.startswith("pylatex"): + lines.insert(0, len(name) * "=") lines.insert(0, name) hits = 0 for i, line in enumerate(lines.copy()): - if line.endswith('\\'): + if line.endswith("\\"): lines[i - hits] += lines.pop(i + 1 - hits) hits += 1 -def autodoc_allow_most_inheritance(app, what, name, obj, namespace, skip, - options): - cls = namespace.split('.')[-1] +def autodoc_allow_most_inheritance(app, what, name, obj, namespace, skip, options): + cls = namespace.split(".")[-1] members = { - 'object': ['dump', 'dumps_packages', 'dump_packages', 'latex_name', - 'escape', 'generate_tex', 'packages', 'dumps_as_content', - 'end_paragraph', 'separate_paragraph', 'content_separator'], - - 'container': ['create', 'dumps', 'dumps_content', 'begin_paragraph'], - - 'userlist': ['append', 'clear', 'copy', 'count', 'extend', 'index', - 'insert', 'pop', 'remove', 'reverse', 'sort'], - 'error': ['args', 'with_traceback'], + "object": [ + "dump", + "dumps_packages", + "dump_packages", + "latex_name", + "escape", + "generate_tex", + "packages", + "dumps_as_content", + "end_paragraph", + "separate_paragraph", + "content_separator", + ], + "container": ["create", "dumps", "dumps_content", "begin_paragraph"], + "userlist": [ + "append", + "clear", + "copy", + "count", + "extend", + "index", + "insert", + "pop", + "remove", + "reverse", + "sort", + ], + "error": ["args", "with_traceback"], } - members['all'] = list(set([req for reqs in members.values() for req in - reqs])) + members["all"] = list(set([req for reqs in members.values() for req in reqs])) - if name in members['all']: + if name in members["all"]: skip = True - if cls == 'LatexObject': + if cls == "LatexObject": return False - if cls in ('Container', 'Environment') and \ - name in members['container']: + if cls in ("Container", "Environment") and name in members["container"]: return False - if cls == 'Document' and name == 'generate_tex': + if cls == "Document" and name == "generate_tex": return False - if name == 'separate_paragraph' and cls in ('SubFigure', 'Float'): + if name == "separate_paragraph" and cls in ("SubFigure", "Float"): return False # Ignore all functions of NoEscape, since it is inherited - if cls == 'NoEscape': + if cls == "NoEscape": return True return skip @@ -166,30 +183,30 @@ def autodoc_allow_most_inheritance(app, what, name, obj, namespace, skip, def setup(app): """Connect autodoc event to custom handler.""" - app.connect('autodoc-process-docstring', auto_change_docstring) - app.connect('autodoc-skip-member', autodoc_allow_most_inheritance) + app.connect("autodoc-process-docstring", auto_change_docstring) + app.connect("autodoc-skip-member", autodoc_allow_most_inheritance) def linkcode_resolve(domain, info): """A simple function to find matching source code.""" - module_name = info['module'] - fullname = info['fullname'] - attribute_name = fullname.split('.')[-1] - base_url = 'https://github.com/JelteF/PyLaTeX/' - - if '+' in version: - commit_hash = version.split('.')[-1][1:] - base_url += 'tree/%s/' % commit_hash + module_name = info["module"] + fullname = info["fullname"] + attribute_name = fullname.split(".")[-1] + base_url = "https://github.com/JelteF/PyLaTeX/" + + if "+" in version: + commit_hash = version.split(".")[-1][1:] + base_url += "tree/%s/" % commit_hash else: - base_url += 'blob/v%s/' % version + base_url += "blob/v%s/" % version - filename = module_name.replace('.', '/') + '.py' + filename = module_name.replace(".", "/") + ".py" module = sys.modules.get(module_name) # Get the actual object try: actual_object = module - for obj in fullname.split('.'): + for obj in fullname.split("."): parent = actual_object actual_object = getattr(actual_object, obj) except AttributeError: @@ -217,7 +234,7 @@ def linkcode_resolve(domain, info): else: end_line = start_line + len(source) - 1 - line_anchor = '#L%d-L%d' % (start_line, end_line) + line_anchor = "#L%d-L%d" % (start_line, end_line) return base_url + filename + line_anchor @@ -228,7 +245,7 @@ def linkcode_resolve(domain, info): # The reST default role (used for this markup: `text`) to use for all # documents. -default_role = 'py:obj' +default_role = "py:obj" # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True @@ -242,10 +259,10 @@ def linkcode_resolve(domain, info): # show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -modindex_common_prefix = ['pylatex.'] +modindex_common_prefix = ["pylatex."] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False @@ -254,11 +271,10 @@ def linkcode_resolve(domain, info): todo_include_todos = True intersphinx_mapping = { - 'python': ('https://docs.python.org/3', None), - 'matplotlib': ('http://matplotlib.org/', None), - 'numpy': ('https://docs.scipy.org/doc/numpy/', None), - 'quantities': ('https://pythonhosted.org/quantities/', - 'quantities-inv.txt'), + "python": ("https://docs.python.org/3", None), + "matplotlib": ("http://matplotlib.org/", None), + "numpy": ("https://docs.scipy.org/doc/numpy/", None), + "quantities": ("https://pythonhosted.org/quantities/", "quantities-inv.txt"), } @@ -266,7 +282,7 @@ def linkcode_resolve(domain, info): # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'sphinx_rtd_theme' +html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -291,12 +307,12 @@ def linkcode_resolve(domain, info): # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -html_favicon = '_static/realfavicongenerator.ico' +html_favicon = "_static/realfavicongenerator.ico" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied @@ -359,20 +375,17 @@ def linkcode_resolve(domain, info): # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. -htmlhelp_basename = 'PyLaTeXdoc' +htmlhelp_basename = "PyLaTeXdoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # 'preamble': '', - # Latex figure (float) alignment # 'figure_align': 'htbp', } @@ -381,8 +394,7 @@ def linkcode_resolve(domain, info): # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'PyLaTeX.tex', 'PyLaTeX Documentation', - 'Jelte Fennema', 'manual'), + (master_doc, "PyLaTeX.tex", "PyLaTeX Documentation", "Jelte Fennema", "manual"), ] # The name of an image file (relative to this directory) to place at the top of @@ -410,10 +422,7 @@ def linkcode_resolve(domain, info): # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'pylatex', 'PyLaTeX Documentation', - [author], 1) -] +man_pages = [(master_doc, "pylatex", "PyLaTeX Documentation", [author], 1)] # If true, show URL addresses after external links. # man_show_urls = False @@ -425,8 +434,15 @@ def linkcode_resolve(domain, info): # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'PyLaTeX', 'PyLaTeX Documentation', author, 'PyLaTeX', - 'One line description of project.', 'Miscellaneous'), + ( + master_doc, + "PyLaTeX", + "PyLaTeX Documentation", + author, + "PyLaTeX", + "One line description of project.", + "Miscellaneous", + ), ] # Documents to append as an appendix to all manuals. diff --git a/pylatex/__init__.py b/pylatex/__init__.py index f76706e0..2891da5c 100644 --- a/pylatex/__init__.py +++ b/pylatex/__init__.py @@ -5,18 +5,49 @@ :license: MIT, see License for more details. """ -from .basic import HugeText, NewPage, LineBreak, NewLine, HFill, LargeText, \ - MediumText, SmallText, FootnoteText, TextColor +from .basic import ( + HugeText, + NewPage, + LineBreak, + NewLine, + HFill, + LargeText, + MediumText, + SmallText, + FootnoteText, + TextColor, +) from .document import Document from .frames import MdFramed, FBox from .math import Math, VectorName, Matrix, Alignat from .package import Package from .section import Chapter, Section, Subsection, Subsubsection -from .table import Table, MultiColumn, MultiRow, Tabular, Tabu, LongTable, \ - LongTabu, Tabularx, LongTabularx, ColumnType -from .tikz import TikZ, Axis, Plot, TikZNode, TikZDraw, TikZCoordinate, \ - TikZPathList, TikZPath, TikZUserPath, TikZOptions, TikZNodeAnchor, \ - TikZScope +from .table import ( + Table, + MultiColumn, + MultiRow, + Tabular, + Tabu, + LongTable, + LongTabu, + Tabularx, + LongTabularx, + ColumnType, +) +from .tikz import ( + TikZ, + Axis, + Plot, + TikZNode, + TikZDraw, + TikZCoordinate, + TikZPathList, + TikZPath, + TikZUserPath, + TikZOptions, + TikZNodeAnchor, + TikZScope, +) from .figure import Figure, SubFigure, StandAloneGraphic from .lists import Enumerate, Itemize, Description from .quantities import Quantity @@ -24,9 +55,17 @@ from .utils import NoEscape, escape_latex from .errors import TableRowSizeError from .headfoot import PageStyle, Head, Foot, simple_page_number -from .position import Center, FlushLeft, FlushRight, MiniPage, TextBlock, \ - HorizontalSpace, VerticalSpace +from .position import ( + Center, + FlushLeft, + FlushRight, + MiniPage, + TextBlock, + HorizontalSpace, + VerticalSpace, +) from .labelref import Marker, Label, Ref, Pageref, Eqref, Autoref, Hyperref from . import _version -__version__ = _version.get_versions()['version'] + +__version__ = _version.get_versions()["version"] diff --git a/pylatex/_version.py b/pylatex/_version.py index 51858ef6..4d8c202c 100644 --- a/pylatex/_version.py +++ b/pylatex/_version.py @@ -1,4 +1,3 @@ - # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (built by setup.py sdist) and build @@ -68,12 +67,14 @@ class NotThisMethod(Exception): def register_vcs_handler(vcs: str, method: str) -> Callable: # decorator """Create decorator to mark a method as the handler of a VCS.""" + def decorate(f: Callable) -> Callable: """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f return f + return decorate @@ -100,10 +101,14 @@ def run_command( try: dispcmd = str([command] + args) # remember shell=False, so use git.cmd on windows, not just git - process = subprocess.Popen([command] + args, cwd=cwd, env=env, - stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None), **popen_kwargs) + process = subprocess.Popen( + [command] + args, + cwd=cwd, + env=env, + stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr else None), + **popen_kwargs, + ) break except OSError as e: if e.errno == errno.ENOENT: @@ -141,15 +146,21 @@ def versions_from_parentdir( for _ in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): - return {"version": dirname[len(parentdir_prefix):], - "full-revisionid": None, - "dirty": False, "error": None, "date": None} + return { + "version": dirname[len(parentdir_prefix) :], + "full-revisionid": None, + "dirty": False, + "error": None, + "date": None, + } rootdirs.append(root) root = os.path.dirname(root) # up a level if verbose: - print("Tried directories %s but none started with prefix %s" % - (str(rootdirs), parentdir_prefix)) + print( + "Tried directories %s but none started with prefix %s" + % (str(rootdirs), parentdir_prefix) + ) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") @@ -212,7 +223,7 @@ def git_versions_from_keywords( # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " - tags = {r[len(TAG):] for r in refs if r.startswith(TAG)} + tags = {r[len(TAG) :] for r in refs if r.startswith(TAG)} if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d @@ -221,7 +232,7 @@ def git_versions_from_keywords( # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". - tags = {r for r in refs if re.search(r'\d', r)} + tags = {r for r in refs if re.search(r"\d", r)} if verbose: print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: @@ -229,32 +240,36 @@ def git_versions_from_keywords( for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): - r = ref[len(tag_prefix):] + r = ref[len(tag_prefix) :] # Filter out refs that exactly match prefix or that don't start # with a number once the prefix is stripped (mostly a concern # when prefix is '') - if not re.match(r'\d', r): + if not re.match(r"\d", r): continue if verbose: print("picking %s" % r) - return {"version": r, - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": None, - "date": date} + return { + "version": r, + "full-revisionid": keywords["full"].strip(), + "dirty": False, + "error": None, + "date": date, + } # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") - return {"version": "0+unknown", - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": "no suitable tags", "date": None} + return { + "version": "0+unknown", + "full-revisionid": keywords["full"].strip(), + "dirty": False, + "error": "no suitable tags", + "date": None, + } @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs( - tag_prefix: str, - root: str, - verbose: bool, - runner: Callable = run_command + tag_prefix: str, root: str, verbose: bool, runner: Callable = run_command ) -> Dict[str, Any]: """Get version from 'git describe' in the root of the source tree. @@ -273,8 +288,7 @@ def git_pieces_from_vcs( env.pop("GIT_DIR", None) runner = functools.partial(runner, env=env) - _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, - hide_stderr=not verbose) + _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=not verbose) if rc != 0: if verbose: print("Directory %s not under git control" % root) @@ -282,10 +296,19 @@ def git_pieces_from_vcs( # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out, rc = runner(GITS, [ - "describe", "--tags", "--dirty", "--always", "--long", - "--match", f"{tag_prefix}[[:digit:]]*" - ], cwd=root) + describe_out, rc = runner( + GITS, + [ + "describe", + "--tags", + "--dirty", + "--always", + "--long", + "--match", + f"{tag_prefix}[[:digit:]]*", + ], + cwd=root, + ) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") @@ -300,8 +323,7 @@ def git_pieces_from_vcs( pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None - branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], - cwd=root) + branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], cwd=root) # --abbrev-ref was added in git-1.6.3 if rc != 0 or branch_name is None: raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") @@ -341,17 +363,16 @@ def git_pieces_from_vcs( dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if dirty: - git_describe = git_describe[:git_describe.rindex("-dirty")] + git_describe = git_describe[: git_describe.rindex("-dirty")] # now we have TAG-NUM-gHEX or HEX if "-" in git_describe: # TAG-NUM-gHEX - mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) + mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe) if not mo: # unparsable. Maybe git-describe is misbehaving? - pieces["error"] = ("unable to parse git-describe output: '%s'" - % describe_out) + pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out return pieces # tag @@ -360,10 +381,12 @@ def git_pieces_from_vcs( if verbose: fmt = "tag '%s' doesn't start with prefix '%s'" print(fmt % (full_tag, tag_prefix)) - pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" - % (full_tag, tag_prefix)) + pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % ( + full_tag, + tag_prefix, + ) return pieces - pieces["closest-tag"] = full_tag[len(tag_prefix):] + pieces["closest-tag"] = full_tag[len(tag_prefix) :] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) @@ -412,8 +435,7 @@ def render_pep440(pieces: Dict[str, Any]) -> str: rendered += ".dirty" else: # exception #1 - rendered = "0+untagged.%d.g%s" % (pieces["distance"], - pieces["short"]) + rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered @@ -442,8 +464,7 @@ def render_pep440_branch(pieces: Dict[str, Any]) -> str: rendered = "0" if pieces["branch"] != "master": rendered += ".dev0" - rendered += "+untagged.%d.g%s" % (pieces["distance"], - pieces["short"]) + rendered += "+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered @@ -604,11 +625,13 @@ def render_git_describe_long(pieces: Dict[str, Any]) -> str: def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]: """Render the given version pieces into the requested style.""" if pieces["error"]: - return {"version": "unknown", - "full-revisionid": pieces.get("long"), - "dirty": None, - "error": pieces["error"], - "date": None} + return { + "version": "unknown", + "full-revisionid": pieces.get("long"), + "dirty": None, + "error": pieces["error"], + "date": None, + } if not style or style == "default": style = "pep440" # the default @@ -632,9 +655,13 @@ def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]: else: raise ValueError("unknown style '%s'" % style) - return {"version": rendered, "full-revisionid": pieces["long"], - "dirty": pieces["dirty"], "error": None, - "date": pieces.get("date")} + return { + "version": rendered, + "full-revisionid": pieces["long"], + "dirty": pieces["dirty"], + "error": None, + "date": pieces.get("date"), + } def get_versions() -> Dict[str, Any]: @@ -648,8 +675,7 @@ def get_versions() -> Dict[str, Any]: verbose = cfg.verbose try: - return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, - verbose) + return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose) except NotThisMethod: pass @@ -658,13 +684,16 @@ def get_versions() -> Dict[str, Any]: # versionfile_source is the relative path from the top of the source # tree (where the .git directory might live) to this file. Invert # this to find the root from __file__. - for _ in cfg.versionfile_source.split('/'): + for _ in cfg.versionfile_source.split("/"): root = os.path.dirname(root) except NameError: - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, - "error": "unable to find root of source tree", - "date": None} + return { + "version": "0+unknown", + "full-revisionid": None, + "dirty": None, + "error": "unable to find root of source tree", + "date": None, + } try: pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) @@ -678,6 +707,10 @@ def get_versions() -> Dict[str, Any]: except NotThisMethod: pass - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, - "error": "unable to compute version", "date": None} + return { + "version": "0+unknown", + "full-revisionid": None, + "dirty": None, + "error": "unable to compute version", + "date": None, + } diff --git a/pylatex/base_classes/__init__.py b/pylatex/base_classes/__init__.py index 54ce7cae..65615a7e 100644 --- a/pylatex/base_classes/__init__.py +++ b/pylatex/base_classes/__init__.py @@ -7,8 +7,15 @@ from .latex_object import LatexObject from .containers import Container, Environment, ContainerCommand -from .command import CommandBase, Command, UnsafeCommand, Options, \ - SpecialOptions, Arguments, SpecialArguments +from .command import ( + CommandBase, + Command, + UnsafeCommand, + Options, + SpecialOptions, + Arguments, + SpecialArguments, +) from .float import Float # Old names of the base classes for backwards compatibility diff --git a/pylatex/base_classes/command.py b/pylatex/base_classes/command.py index f9cea00b..1e308aa5 100644 --- a/pylatex/base_classes/command.py +++ b/pylatex/base_classes/command.py @@ -23,8 +23,7 @@ class CommandBase(LatexObject): """ - def __init__(self, arguments=None, options=None, *, - extra_arguments=None): + def __init__(self, arguments=None, options=None, *, extra_arguments=None): r""" Args ---- @@ -40,17 +39,17 @@ def __init__(self, arguments=None, options=None, *, """ - self._set_parameters(arguments, 'arguments') - self._set_parameters(options, 'options') + self._set_parameters(arguments, "arguments") + self._set_parameters(options, "options") if extra_arguments is None: self.extra_arguments = None else: - self._set_parameters(extra_arguments, 'extra_arguments') + self._set_parameters(extra_arguments, "extra_arguments") super().__init__() def _set_parameters(self, parameters, argument_type): - parameter_cls = Options if argument_type == 'options' else Arguments + parameter_cls = Options if argument_type == "options" else Arguments if parameters is None: parameters = parameter_cls() @@ -70,8 +69,7 @@ def __key(self): tuple """ - return (self.latex_name, self.arguments, self.options, - self.extra_arguments) + return (self.latex_name, self.arguments, self.options, self.extra_arguments) def __eq__(self, other): """Compare two commands. @@ -117,15 +115,18 @@ def dumps(self): arguments = self.arguments.dumps() if self.extra_arguments is None: - return r'\{command}{options}{arguments}'\ - .format(command=self.latex_name, options=options, - arguments=arguments) + return r"\{command}{options}{arguments}".format( + command=self.latex_name, options=options, arguments=arguments + ) extra_arguments = self.extra_arguments.dumps() - return r'\{command}{arguments}{options}{extra_arguments}'\ - .format(command=self.latex_name, arguments=arguments, - options=options, extra_arguments=extra_arguments) + return r"\{command}{arguments}{options}{extra_arguments}".format( + command=self.latex_name, + arguments=arguments, + options=options, + extra_arguments=extra_arguments, + ) class Command(CommandBase): @@ -135,10 +136,17 @@ class Command(CommandBase): is used multiple times it is better to subclass `.CommandBase`. """ - _repr_attributes_mapping = {'command': 'latex_name'} - - def __init__(self, command=None, arguments=None, options=None, *, - extra_arguments=None, packages=None): + _repr_attributes_mapping = {"command": "latex_name"} + + def __init__( + self, + command=None, + arguments=None, + options=None, + *, + extra_arguments=None, + packages=None + ): r""" Args ---- @@ -207,7 +215,7 @@ class Parameters(LatexObject): def __repr__(self): args = [repr(a) for a in self._positional_args] args += ["%s=%r" % k_v for k_v in self._key_value_args.items()] - return self.__class__.__name__ + '(' + ', '.join(args) + ')' + return self.__class__.__name__ + "(" + ", ".join(args) + ")" def __init__(self, *args, **kwargs): r""" @@ -220,10 +228,10 @@ def __init__(self, *args, **kwargs): """ if len(args) == 1 and not isinstance(args[0], str): - if hasattr(args[0], 'items') and len(kwargs) == 0: + if hasattr(args[0], "items") and len(kwargs) == 0: kwargs = args[0] # do not just iterate over the dict keys args = () - elif hasattr(args[0], '__iter__'): + elif hasattr(args[0], "__iter__"): args = args[0] self._positional_args = list(args) @@ -281,10 +289,11 @@ def _format_contents(self, prefix, separator, suffix): params = self._list_args_kwargs() if len(params) <= 0: - return '' + return "" - string = prefix + dumps_list(params, escape=self.escape, - token=separator) + suffix + string = ( + prefix + dumps_list(params, escape=self.escape, token=separator) + suffix + ) return string @@ -298,8 +307,9 @@ def _list_args_kwargs(self): params = [] params.extend(self._positional_args) - params.extend(['{k}={v}'.format(k=k, v=v) for k, v in - self._key_value_args.items()]) + params.extend( + ["{k}={v}".format(k=k, v=v) for k, v in self._key_value_args.items()] + ) return params @@ -333,7 +343,7 @@ def dumps(self): str """ - return self._format_contents('[', ',', ']') + return self._format_contents("[", ",", "]") class SpecialOptions(Options): @@ -342,7 +352,7 @@ class SpecialOptions(Options): def dumps(self): """Represent the parameters as a string in LaTex syntax.""" - return self._format_contents('[', '][', ']') + return self._format_contents("[", "][", "]") class Arguments(Parameters): @@ -375,7 +385,7 @@ def dumps(self): str """ - return self._format_contents('{', '}{', '}') + return self._format_contents("{", "}{", "}") class SpecialArguments(Arguments): @@ -391,4 +401,4 @@ def dumps(self): str """ - return self._format_contents('{', ',', '}') + return self._format_contents("{", ",", "}") diff --git a/pylatex/base_classes/containers.py b/pylatex/base_classes/containers.py index 191485e2..38698cf0 100644 --- a/pylatex/base_classes/containers.py +++ b/pylatex/base_classes/containers.py @@ -23,7 +23,7 @@ class Container(LatexObject, UserList): """ - content_separator = '%\n' + content_separator = "%\n" def __init__(self, *, data=None): r""" @@ -48,7 +48,7 @@ def __init__(self, *, data=None): @property def _repr_attributes(self): - return super()._repr_attributes + ['real_data'] + return super()._repr_attributes + ["real_data"] def dumps_content(self, **kwargs): r"""Represent the container as a string in LaTeX syntax. @@ -65,8 +65,9 @@ def dumps_content(self, **kwargs): A LaTeX string representing the container """ - return dumps_list(self, escape=self.escape, - token=self.content_separator, **kwargs) + return dumps_list( + self, escape=self.escape, token=self.content_separator, **kwargs + ) def _propagate_packages(self): """Make sure packages get propagated.""" @@ -133,8 +134,7 @@ class Environment(Container): #: string if it has no content. omit_if_empty = False - def __init__(self, *, options=None, arguments=None, start_arguments=None, - **kwargs): + def __init__(self, *, options=None, arguments=None, start_arguments=None, **kwargs): r""" Args ---- @@ -165,9 +165,9 @@ def dumps(self): content = self.dumps_content() if not content.strip() and self.omit_if_empty: - return '' + return "" - string = '' + string = "" # Something other than None needs to be used as extra arguments, that # way the options end up behind the latex_name argument. @@ -176,14 +176,15 @@ def dumps(self): else: extra_arguments = self.arguments - begin = Command('begin', self.start_arguments, self.options, - extra_arguments=extra_arguments) + begin = Command( + "begin", self.start_arguments, self.options, extra_arguments=extra_arguments + ) begin.arguments._positional_args.insert(0, self.latex_name) string += begin.dumps() + self.content_separator string += content + self.content_separator - string += Command('end', self.latex_name).dumps() + string += Command("end", self.latex_name).dumps() return string @@ -255,18 +256,17 @@ def dumps(self): content = self.dumps_content() if not content.strip() and self.omit_if_empty: - return '' + return "" - string = '' + string = "" - start = Command(self.latex_name, arguments=self.arguments, - options=self.options) + start = Command(self.latex_name, arguments=self.arguments, options=self.options) - string += start.dumps() + '{%\n' + string += start.dumps() + "{%\n" - if content != '': - string += content + '%\n}' + if content != "": + string += content + "%\n}" else: - string += '}' + string += "}" return string diff --git a/pylatex/base_classes/float.py b/pylatex/base_classes/float.py index 1cae9acb..b632d678 100644 --- a/pylatex/base_classes/float.py +++ b/pylatex/base_classes/float.py @@ -17,7 +17,7 @@ class Float(Environment): separate_paragraph = True _repr_attributes_mapping = { - 'position': 'options', + "position": "options", } def __init__(self, *, position=None, **kwargs): @@ -44,4 +44,4 @@ def add_caption(self, caption): The text of the caption. """ - self.append(Command('caption', caption)) + self.append(Command("caption", caption)) diff --git a/pylatex/base_classes/latex_object.py b/pylatex/base_classes/latex_object.py index 8c5631ed..abf1eeb2 100644 --- a/pylatex/base_classes/latex_object.py +++ b/pylatex/base_classes/latex_object.py @@ -18,11 +18,11 @@ def __init__(cls, name, bases, d): # noqa packages = OrderedSet() for b in bases: - if hasattr(b, 'packages'): + if hasattr(b, "packages"): packages |= b.packages - if 'packages' in d: - packages |= d['packages'] + if "packages" in d: + packages |= d["packages"] cls.packages = packages @@ -39,7 +39,7 @@ class LatexObject(metaclass=_CreatePackages): """ _latex_name = None - _star_latex_name = False # latex_name + ('*' if True else '') + _star_latex_name = False # latex_name + ('*' if True else '') #: Set this to an iterable to override the list of default repr #: attributes. @@ -91,18 +91,23 @@ def __init__(self): def __repr__(self): """Create a printable representation of the object.""" - return self.__class__.__name__ + '(' + \ - ', '.join(map(repr, self._repr_values)) + ')' + return ( + self.__class__.__name__ + + "(" + + ", ".join(map(repr, self._repr_values)) + + ")" + ) @property def _repr_values(self): """Return values that are to be shown in repr string.""" + def getattr_better(obj, field): try: return getattr(obj, field) except AttributeError as e: try: - return getattr(obj, '_' + field) + return getattr(obj, "_" + field) except AttributeError: raise e @@ -127,7 +132,7 @@ def latex_name(self): It can be `None` when the class doesn't have a name. """ - star = ('*' if self._star_latex_name else '') + star = "*" if self._star_latex_name else "" if self._latex_name is not None: return self._latex_name + star return self.__class__.__name__.lower() + star @@ -165,7 +170,7 @@ def generate_tex(self, filepath): The name of the file (without .tex) """ - with open(filepath + '.tex', 'w', encoding='utf-8') as newf: + with open(filepath + ".tex", "w", encoding="utf-8") as newf: self.dump(newf) def dumps_packages(self): @@ -202,9 +207,9 @@ def dumps_as_content(self): string = self.dumps() if self.separate_paragraph or self.begin_paragraph: - string = '\n\n' + string.lstrip('\n') + string = "\n\n" + string.lstrip("\n") if self.separate_paragraph or self.end_paragraph: - string = string.rstrip('\n') + '\n\n' + string = string.rstrip("\n") + "\n\n" return string diff --git a/pylatex/basic.py b/pylatex/basic.py index e00ef708..11a34e1d 100644 --- a/pylatex/basic.py +++ b/pylatex/basic.py @@ -69,9 +69,7 @@ class FootnoteText(HugeText): class TextColor(ContainerCommand): """An environment which changes the text color of the data.""" - _repr_attributes_mapping = { - "color": "arguments" - } + _repr_attributes_mapping = {"color": "arguments"} packages = [Package("xcolor")] diff --git a/pylatex/config.py b/pylatex/config.py index eb8f04d9..e2b2ce65 100644 --- a/pylatex/config.py +++ b/pylatex/config.py @@ -107,6 +107,7 @@ class Version2(Version1): microtype = True row_height = 1.3 + #: The default configuration in the nxt major release. Currently the same as #: `Version2`. NextMajor = Version2 diff --git a/pylatex/document.py b/pylatex/document.py index 47d4c73f..6d1d83b2 100644 --- a/pylatex/document.py +++ b/pylatex/document.py @@ -10,8 +10,14 @@ import sys import subprocess import errno -from .base_classes import Environment, Command, Container, LatexObject, \ - UnsafeCommand, SpecialArguments +from .base_classes import ( + Environment, + Command, + Container, + LatexObject, + UnsafeCommand, + SpecialArguments, +) from .package import Package from .errors import CompilerError from .utils import dumps_list, rm_temp_dir, NoEscape @@ -28,11 +34,23 @@ class Document(Environment): """ - def __init__(self, default_filepath='default_filepath', *, - documentclass='article', document_options=None, fontenc='T1', - inputenc='utf8', font_size="normalsize", lmodern=True, - textcomp=True, microtype=None, page_numbers=True, indent=None, - geometry_options=None, data=None): + def __init__( + self, + default_filepath="default_filepath", + *, + documentclass="article", + document_options=None, + fontenc="T1", + inputenc="utf8", + font_size="normalsize", + lmodern=True, + textcomp=True, + microtype=None, + page_numbers=True, + indent=None, + geometry_options=None, + data=None + ): r""" Args ---- @@ -72,9 +90,9 @@ def __init__(self, default_filepath='default_filepath', *, if isinstance(documentclass, Command): self.documentclass = documentclass else: - self.documentclass = Command('documentclass', - arguments=documentclass, - options=document_options) + self.documentclass = Command( + "documentclass", arguments=documentclass, options=document_options + ) if indent is None: indent = cf.active.indent if microtype is None: @@ -90,35 +108,37 @@ def __init__(self, default_filepath='default_filepath', *, packages = [] if fontenc is not None: - packages.append(Package('fontenc', options=fontenc)) + packages.append(Package("fontenc", options=fontenc)) if inputenc is not None: - packages.append(Package('inputenc', options=inputenc)) + packages.append(Package("inputenc", options=inputenc)) if lmodern: - packages.append(Package('lmodern')) + packages.append(Package("lmodern")) if textcomp: - packages.append(Package('textcomp')) + packages.append(Package("textcomp")) if page_numbers: - packages.append(Package('lastpage')) + packages.append(Package("lastpage")) if not indent: - packages.append(Package('parskip')) + packages.append(Package("parskip")) if microtype: - packages.append(Package('microtype')) + packages.append(Package("microtype")) if geometry_options is not None: - packages.append(Package('geometry')) + packages.append(Package("geometry")) # Make sure we don't add this options command for an empty list, # because that breaks. if geometry_options: - packages.append(Command( - 'geometry', - arguments=SpecialArguments(geometry_options), - )) + packages.append( + Command( + "geometry", + arguments=SpecialArguments(geometry_options), + ) + ) super().__init__(data=data) # Usually the name is the class name, but if we create our own # document class, \begin{document} gets messed up. - self._latex_name = 'document' + self._latex_name = "document" self.packages |= packages self.variables = [] @@ -143,7 +163,7 @@ def _propagate_packages(self): super()._propagate_packages() - for item in (self.preamble): + for item in self.preamble: if isinstance(item, LatexObject): if isinstance(item, Container): item._propagate_packages() @@ -158,12 +178,12 @@ def dumps(self): str """ - head = self.documentclass.dumps() + '%\n' - head += self.dumps_packages() + '%\n' - head += dumps_list(self.variables) + '%\n' - head += dumps_list(self.preamble) + '%\n' + head = self.documentclass.dumps() + "%\n" + head += self.dumps_packages() + "%\n" + head += dumps_list(self.variables) + "%\n" + head += dumps_list(self.preamble) + "%\n" - return head + '%\n' + super().dumps() + return head + "%\n" + super().dumps() def generate_tex(self, filepath=None): """Generate a .tex file for the document. @@ -177,8 +197,16 @@ def generate_tex(self, filepath=None): super().generate_tex(self._select_filepath(filepath)) - def generate_pdf(self, filepath=None, *, clean=True, clean_tex=True, - compiler=None, compiler_args=None, silent=True): + def generate_pdf( + self, + filepath=None, + *, + clean=True, + clean_tex=True, + compiler=None, + compiler_args=None, + silent=True + ): """Generate a pdf file from the document. Args @@ -212,8 +240,7 @@ def generate_pdf(self, filepath=None, *, clean=True, clean_tex=True, filepath = self._select_filepath(filepath) if not os.path.basename(filepath): - filepath = os.path.join(os.path.abspath(filepath), - 'default_basename') + filepath = os.path.join(os.path.abspath(filepath), "default_basename") else: filepath = os.path.abspath(filepath) @@ -228,18 +255,15 @@ def generate_pdf(self, filepath=None, *, clean=True, clean_tex=True, if compiler is not None: compilers = ((compiler, []),) else: - latexmk_args = ['--pdf'] + latexmk_args = ["--pdf"] - compilers = ( - ('latexmk', latexmk_args), - ('pdflatex', []) - ) + compilers = (("latexmk", latexmk_args), ("pdflatex", [])) - main_arguments = ['--interaction=nonstopmode', filepath + '.tex'] + main_arguments = ["--interaction=nonstopmode", filepath + ".tex"] check_output_kwargs = {} if python_cwd_available: - check_output_kwargs = {'cwd': dest_dir} + check_output_kwargs = {"cwd": dest_dir} os_error = None @@ -247,9 +271,9 @@ def generate_pdf(self, filepath=None, *, clean=True, clean_tex=True, command = [compiler] + arguments + compiler_args + main_arguments try: - output = subprocess.check_output(command, - stderr=subprocess.STDOUT, - **check_output_kwargs) + output = subprocess.check_output( + command, stderr=subprocess.STDOUT, **check_output_kwargs + ) except (OSError, IOError) as e: # Use FileNotFoundError when python 2 is dropped os_error = e @@ -269,17 +293,18 @@ def generate_pdf(self, filepath=None, *, clean=True, clean_tex=True, if clean: try: # Try latexmk cleaning first - subprocess.check_output(['latexmk', '-c', filepath], - stderr=subprocess.STDOUT, - **check_output_kwargs) + subprocess.check_output( + ["latexmk", "-c", filepath], + stderr=subprocess.STDOUT, + **check_output_kwargs + ) except (OSError, IOError, subprocess.CalledProcessError): # Otherwise just remove some file extensions. - extensions = ['aux', 'log', 'out', 'fls', - 'fdb_latexmk'] + extensions = ["aux", "log", "out", "fls", "fdb_latexmk"] for ext in extensions: try: - os.remove(filepath + '.' + ext) + os.remove(filepath + "." + ext) except (OSError, IOError) as e: # Use FileNotFoundError when python 2 is dropped if e.errno != errno.ENOENT: @@ -287,7 +312,7 @@ def generate_pdf(self, filepath=None, *, clean=True, clean_tex=True, rm_temp_dir() if clean_tex: - os.remove(filepath + '.tex') # Remove generated tex file + os.remove(filepath + ".tex") # Remove generated tex file # Compilation has finished, so no further compilers have to be # tried @@ -295,11 +320,13 @@ def generate_pdf(self, filepath=None, *, clean=True, clean_tex=True, else: # Notify user that none of the compilers worked. - raise(CompilerError( - 'No LaTex compiler was found\n' - 'Either specify a LaTex compiler ' - 'or make sure you have latexmk or pdfLaTex installed.' - )) + raise ( + CompilerError( + "No LaTex compiler was found\n" + "Either specify a LaTex compiler " + "or make sure you have latexmk or pdfLaTex installed." + ) + ) if not python_cwd_available: os.chdir(cur_dir) @@ -321,9 +348,10 @@ def _select_filepath(self, filepath): if filepath is None: return self.default_filepath else: - if os.path.basename(filepath) == '': - filepath = os.path.join(filepath, os.path.basename( - self.default_filepath)) + if os.path.basename(filepath) == "": + filepath = os.path.join( + filepath, os.path.basename(self.default_filepath) + ) return filepath def change_page_style(self, style): @@ -365,9 +393,9 @@ def add_color(self, name, model, description): self.packages.append(Package("color")) self.color = True - self.preamble.append(Command("definecolor", arguments=[name, - model, - description])) + self.preamble.append( + Command("definecolor", arguments=[name, model, description]) + ) def change_length(self, parameter, value): r"""Change the length of a certain parameter to a certain value. @@ -380,8 +408,7 @@ def change_length(self, parameter, value): The value to set the parameter to """ - self.preamble.append(UnsafeCommand('setlength', - arguments=[parameter, value])) + self.preamble.append(UnsafeCommand("setlength", arguments=[parameter, value])) def set_variable(self, name, value): r"""Add a variable which can be used inside the document. @@ -407,10 +434,10 @@ def set_variable(self, name, value): break if variable_exists: - renew = Command(command="renewcommand", - arguments=[NoEscape(name_arg), value]) + renew = Command( + command="renewcommand", arguments=[NoEscape(name_arg), value] + ) self.append(renew) else: - new = Command(command="newcommand", - arguments=[NoEscape(name_arg), value]) + new = Command(command="newcommand", arguments=[NoEscape(name_arg), value]) self.variables.append(new) diff --git a/pylatex/figure.py b/pylatex/figure.py index efbdd979..2ecc525f 100644 --- a/pylatex/figure.py +++ b/pylatex/figure.py @@ -17,8 +17,13 @@ class Figure(Float): """A class that represents a Figure environment.""" - def add_image(self, filename, *, width=NoEscape(r'0.8\textwidth'), - placement=NoEscape(r'\centering')): + def add_image( + self, + filename, + *, + width=NoEscape(r"0.8\textwidth"), + placement=NoEscape(r"\centering") + ): """Add an image to the figure. Args @@ -36,15 +41,16 @@ def add_image(self, filename, *, width=NoEscape(r'0.8\textwidth'), if self.escape: width = escape_latex(width) - width = 'width=' + str(width) + width = "width=" + str(width) if placement is not None: self.append(placement) - self.append(StandAloneGraphic(image_options=width, - filename=fix_filename(filename))) + self.append( + StandAloneGraphic(image_options=width, filename=fix_filename(filename)) + ) - def _save_plot(self, *args, extension='pdf', **kwargs): + def _save_plot(self, *args, extension="pdf", **kwargs): """Save the plot. Returns @@ -55,13 +61,13 @@ def _save_plot(self, *args, extension='pdf', **kwargs): import matplotlib.pyplot as plt tmp_path = make_temp_dir() - filename = '{}.{}'.format(str(uuid.uuid4()), extension.strip('.')) + filename = "{}.{}".format(str(uuid.uuid4()), extension.strip(".")) filepath = posixpath.join(tmp_path, filename) plt.savefig(filepath, *args, **kwargs) return filepath - def add_plot(self, *args, extension='pdf', **kwargs): + def add_plot(self, *args, extension="pdf", **kwargs): """Add the current Matplotlib plot to the figure. The plot that gets added is the one that would normally be shown when @@ -82,7 +88,7 @@ def add_plot(self, *args, extension='pdf', **kwargs): add_image_kwargs = {} - for key in ('width', 'placement'): + for key in ("width", "placement"): if key in kwargs: add_image_kwargs[key] = kwargs.pop(key) @@ -94,17 +100,17 @@ def add_plot(self, *args, extension='pdf', **kwargs): class SubFigure(Figure): """A class that represents a subfigure from the subcaption package.""" - packages = [Package('subcaption')] + packages = [Package("subcaption")] #: By default a subfigure is not on its own paragraph since that looks #: weird inside another figure. separate_paragraph = False _repr_attributes_mapping = { - 'width': 'arguments', + "width": "arguments", } - def __init__(self, width=NoEscape(r'0.45\linewidth'), **kwargs): + def __init__(self, width=NoEscape(r"0.45\linewidth"), **kwargs): """ Args ---- @@ -116,8 +122,7 @@ def __init__(self, width=NoEscape(r'0.45\linewidth'), **kwargs): super().__init__(arguments=width, **kwargs) - def add_image(self, filename, *, width=NoEscape(r'\linewidth'), - placement=None): + def add_image(self, filename, *, width=NoEscape(r"\linewidth"), placement=None): """Add an image to the subfigure. Args @@ -138,16 +143,16 @@ class StandAloneGraphic(UnsafeCommand): _latex_name = "includegraphics" - packages = [Package('graphicx')] + packages = [Package("graphicx")] - _repr_attributes_mapping = { - "filename": "arguments", - "image_options": "options" - } + _repr_attributes_mapping = {"filename": "arguments", "image_options": "options"} - def __init__(self, filename, - image_options=NoEscape(r'width=0.8\textwidth'), - extra_arguments=None): + def __init__( + self, + filename, + image_options=NoEscape(r"width=0.8\textwidth"), + extra_arguments=None, + ): r""" Args ---- @@ -159,6 +164,9 @@ def __init__(self, filename, arguments = [NoEscape(filename)] - super().__init__(command=self._latex_name, arguments=arguments, - options=image_options, - extra_arguments=extra_arguments) + super().__init__( + command=self._latex_name, + arguments=arguments, + options=image_options, + extra_arguments=extra_arguments, + ) diff --git a/pylatex/frames.py b/pylatex/frames.py index 30b7d671..84a3e43e 100644 --- a/pylatex/frames.py +++ b/pylatex/frames.py @@ -13,7 +13,7 @@ class MdFramed(Environment): """A class that defines an mdframed environment.""" - packages = [Package('mdframed')] + packages = [Package("mdframed")] class FBox(ContainerCommand): diff --git a/pylatex/headfoot.py b/pylatex/headfoot.py index 2cc236b2..523aafb5 100644 --- a/pylatex/headfoot.py +++ b/pylatex/headfoot.py @@ -16,10 +16,9 @@ class PageStyle(ContainerCommand): _latex_name = "fancypagestyle" - packages = [Package('fancyhdr')] + packages = [Package("fancyhdr")] - def __init__(self, name, *, header_thickness=0, footer_thickness=0, - data=None): + def __init__(self, name, *, header_thickness=0, footer_thickness=0, data=None): r""" Args ---- @@ -59,12 +58,19 @@ def change_thickness(self, element, thickness): """ if element == "header": - self.data.append(Command("renewcommand", - arguments=[NoEscape(r"\headrulewidth"), - str(thickness) + 'pt'])) + self.data.append( + Command( + "renewcommand", + arguments=[NoEscape(r"\headrulewidth"), str(thickness) + "pt"], + ) + ) elif element == "footer": - self.data.append(Command("renewcommand", arguments=[ - NoEscape(r"\footrulewidth"), str(thickness) + 'pt'])) + self.data.append( + Command( + "renewcommand", + arguments=[NoEscape(r"\footrulewidth"), str(thickness) + "pt"], + ) + ) def simple_page_number(): @@ -76,7 +82,7 @@ def simple_page_number(): The latex string that displays the page number """ - return NoEscape(r'Page \thepage\ of \pageref{LastPage}') + return NoEscape(r"Page \thepage\ of \pageref{LastPage}") class Head(ContainerCommand): diff --git a/pylatex/labelref.py b/pylatex/labelref.py index 099c6a86..ffc79cf4 100644 --- a/pylatex/labelref.py +++ b/pylatex/labelref.py @@ -9,7 +9,7 @@ def _remove_invalid_char(s): """Remove invalid and dangerous characters from a string.""" - s = ''.join([i if ord(i) >= 32 and ord(i) < 127 else '' for i in s]) + s = "".join([i if ord(i) >= 32 and ord(i) < 127 else "" for i in s]) s = s.translate(dict.fromkeys(map(ord, "&%$#_{}~^\\\n\xA0[]\":;' "))) return s @@ -18,8 +18,8 @@ class Marker(LatexObject): """A class that represents a marker (label/ref parameter).""" _repr_attributes_override = [ - 'name', - 'prefix', + "name", + "prefix", ] def __init__(self, name, prefix="", del_invalid_char=True): @@ -59,7 +59,7 @@ class RefLabelBase(CommandBase): """A class used as base for command that take a marker only.""" _repr_attributes_mapping = { - 'marker': 'arguments', + "marker": "arguments", } def __init__(self, marker): @@ -89,37 +89,37 @@ class Pageref(RefLabelBase): class Eqref(RefLabelBase): """A class that represent a ref to a formulae.""" - packages = [Package('amsmath')] + packages = [Package("amsmath")] class Cref(RefLabelBase): """A class that represent a cref (not a Cref).""" - packages = [Package('cleveref')] + packages = [Package("cleveref")] class CrefUp(RefLabelBase): """A class that represent a Cref.""" - packages = [Package('cleveref')] - latex_name = 'Cref' + packages = [Package("cleveref")] + latex_name = "Cref" class Autoref(RefLabelBase): """A class that represent an autoref.""" - packages = [Package('hyperref')] + packages = [Package("hyperref")] class Hyperref(CommandBase): """A class that represents an hyperlink to a label.""" _repr_attributes_mapping = { - 'marker': 'options', - 'text': 'arguments', + "marker": "options", + "text": "arguments", } - packages = [Package('hyperref')] + packages = [Package("hyperref")] def __init__(self, marker, text): """ diff --git a/pylatex/lists.py b/pylatex/lists.py index 87ded8e4..32fe7ebc 100644 --- a/pylatex/lists.py +++ b/pylatex/lists.py @@ -28,7 +28,7 @@ def add_item(self, s): s: str or `~.LatexObject` The item itself. """ - self.append(Command('item')) + self.append(Command("item")) self.append(s) @@ -58,8 +58,7 @@ def __init__(self, enumeration_symbol=None, *, options=None, **kwargs): options = Options(options) else: options = Options() - options._positional_args.append(NoEscape('label=' + - enumeration_symbol)) + options._positional_args.append(NoEscape("label=" + enumeration_symbol)) super().__init__(options=options, **kwargs) @@ -81,5 +80,5 @@ def add_item(self, label, s): s: str or `~.LatexObject` The item itself. """ - self.append(Command('item', options=label)) + self.append(Command("item", options=label)) self.append(s) diff --git a/pylatex/math.py b/pylatex/math.py index ebe52696..19a1b9b2 100644 --- a/pylatex/math.py +++ b/pylatex/math.py @@ -16,7 +16,7 @@ class Alignat(Environment): #: Alignat environment cause compile errors when they do not contain items. #: This is why it is omitted fully if they are empty. omit_if_empty = True - packages = [Package('amsmath')] + packages = [Package("amsmath")] def __init__(self, aligns=2, numbering=True, escape=None): """ @@ -40,9 +40,9 @@ def __init__(self, aligns=2, numbering=True, escape=None): class Math(Container): """A class representing a math environment.""" - packages = [Package('amsmath')] + packages = [Package("amsmath")] - content_separator = ' ' + content_separator = " " def __init__(self, *, inline=False, data=None, escape=None): r""" @@ -69,15 +69,15 @@ def dumps(self): """ if self.inline: - return '$' + self.dumps_content() + '$' - return '\\[%\n' + self.dumps_content() + '%\n\\]' + return "$" + self.dumps_content() + "$" + return "\\[%\n" + self.dumps_content() + "%\n\\]" class VectorName(Command): """A class representing a named vector.""" _repr_attributes_mapping = { - 'name': 'arguments', + "name": "arguments", } def __init__(self, name): @@ -88,19 +88,19 @@ def __init__(self, name): Name of the vector """ - super().__init__('mathbf', arguments=name) + super().__init__("mathbf", arguments=name) class Matrix(Environment): """A class representing a matrix.""" - packages = [Package('amsmath')] + packages = [Package("amsmath")] _repr_attributes_mapping = { - 'alignment': 'arguments', + "alignment": "arguments", } - def __init__(self, matrix, *, mtype='p', alignment=None): + def __init__(self, matrix, *, mtype="p", alignment=None): r""" Args ---- @@ -123,10 +123,10 @@ def __init__(self, matrix, *, mtype='p', alignment=None): self.matrix = matrix - self.latex_name = mtype + 'matrix' + self.latex_name = mtype + "matrix" self._mtype = mtype if alignment is not None: - self.latex_name += '*' + self.latex_name += "*" super().__init__(arguments=alignment) @@ -140,16 +140,16 @@ def dumps_content(self): import numpy as np - string = '' + string = "" shape = self.matrix.shape for (y, x), value in np.ndenumerate(self.matrix): if x: - string += '&' + string += "&" string += str(value) if x == shape[1] - 1 and y != shape[0] - 1: - string += r'\\' + '%\n' + string += r"\\" + "%\n" super().dumps_content() diff --git a/pylatex/package.py b/pylatex/package.py index 040bed53..ccbbf30f 100644 --- a/pylatex/package.py +++ b/pylatex/package.py @@ -12,10 +12,10 @@ class Package(CommandBase): """A class that represents a package.""" - _latex_name = 'usepackage' + _latex_name = "usepackage" _repr_attributes_mapping = { - 'name': 'arguments', + "name": "arguments", } def __init__(self, name, options=None): diff --git a/pylatex/position.py b/pylatex/position.py index 396c0f15..61703e75 100644 --- a/pylatex/position.py +++ b/pylatex/position.py @@ -16,11 +16,9 @@ class HorizontalSpace(CommandBase): """Add/remove the amount of horizontal space between elements.""" - _latex_name = 'hspace' + _latex_name = "hspace" - _repr_attributes_mapping = { - "size": "arguments" - } + _repr_attributes_mapping = {"size": "arguments"} def __init__(self, size, *, star=True): """ @@ -34,7 +32,7 @@ def __init__(self, size, *, star=True): """ if star: - self.latex_name += '*' + self.latex_name += "*" super().__init__(arguments=size) @@ -42,13 +40,13 @@ def __init__(self, size, *, star=True): class VerticalSpace(HorizontalSpace): """Add the user specified amount of vertical space to the document.""" - _latex_name = 'vspace' + _latex_name = "vspace" class Center(Environment): r"""Centered environment.""" - packages = [Package('ragged2e')] + packages = [Package("ragged2e")] class FlushLeft(Center): @@ -62,19 +60,27 @@ class FlushRight(Center): class MiniPage(Environment): r"""A class that allows the creation of minipages within document pages.""" - packages = [Package('ragged2e')] + packages = [Package("ragged2e")] _repr_attributes_mapping = { "width": "arguments", "pos": "options", "height": "options", "content_pos": "options", - "align": "options" + "align": "options", } - def __init__(self, *, width=NoEscape(r'\textwidth'), pos=None, - height=None, content_pos=None, align=None, fontsize=None, - data=None): + def __init__( + self, + *, + width=NoEscape(r"\textwidth"), + pos=None, + height=None, + content_pos=None, + align=None, + fontsize=None, + data=None + ): r""" Args ---- @@ -104,8 +110,7 @@ def __init__(self, *, width=NoEscape(r'\textwidth'), pos=None, if height is not None: options.append(NoEscape(height)) - if ((content_pos is not None) and (pos is not None) and - (height is not None)): + if (content_pos is not None) and (pos is not None) and (height is not None): options.append(content_pos) options = SpecialOptions(*options) @@ -142,14 +147,11 @@ class TextBlock(Environment): Make sure to set lengths of TPHorizModule and TPVertModule """ - _repr_attributes_mapping = { - "width": "arguments" - } + _repr_attributes_mapping = {"width": "arguments"} - packages = [Package('textpos')] + packages = [Package("textpos")] - def __init__(self, width, horizontal_pos, vertical_pos, *, - indent=False, data=None): + def __init__(self, width, horizontal_pos, vertical_pos, *, indent=False, data=None): r""" Args ---- @@ -171,8 +173,7 @@ def __init__(self, width, horizontal_pos, vertical_pos, *, super().__init__(arguments=arguments) - self.append("(%s, %s)" % (str(self.horizontal_pos), - str(self.vertical_pos))) + self.append("(%s, %s)" % (str(self.horizontal_pos), str(self.vertical_pos))) if not indent: - self.append(NoEscape(r'\noindent')) + self.append(NoEscape(r"\noindent")) diff --git a/pylatex/quantities.py b/pylatex/quantities.py index d6e60c8a..4a37908f 100644 --- a/pylatex/quantities.py +++ b/pylatex/quantities.py @@ -21,31 +21,31 @@ # Translations for names used in the quantities package to ones used by SIunitx UNIT_NAME_TRANSLATIONS = { - 'Celsius': 'celsius', - 'revolutions_per_minute': 'rpm', - 'v': 'volt', + "Celsius": "celsius", + "revolutions_per_minute": "rpm", + "v": "volt", } def _dimensionality_to_siunitx(dim): import quantities as pq - string = '' + string = "" items = dim.items() for unit, power in sorted(items, key=itemgetter(1), reverse=True): if power < 0: - substring = r'\per' + substring = r"\per" power = -power elif power == 0: continue else: - substring = '' + substring = "" - prefixes = [x for x in dir(pq.prefixes) if not x.startswith('_')] + prefixes = [x for x in dir(pq.prefixes) if not x.startswith("_")] for prefix in prefixes: # Split unitname into prefix and actual name if possible if unit.name.startswith(prefix): - substring += '\\' + prefix + substring += "\\" + prefix name = unit.name[len(prefix)] break else: @@ -58,10 +58,10 @@ def _dimensionality_to_siunitx(dim): except KeyError: pass - substring += '\\' + name + substring += "\\" + name if power > 1: - substring += r'\tothe{' + str(power) + '}' + substring += r"\tothe{" + str(power) + "}" string += substring return NoEscape(string) @@ -70,8 +70,8 @@ class Quantity(Command): """A class representing quantities.""" packages = [ - Package('siunitx', options=[NoEscape('separate-uncertainty=true')]), - NoEscape('\\DeclareSIUnit\\rpm{rpm}') + Package("siunitx", options=[NoEscape("separate-uncertainty=true")]), + NoEscape("\\DeclareSIUnit\\rpm{rpm}"), ] def __init__(self, quantity, *, options=None, format_cb=None): @@ -124,19 +124,21 @@ def _format(val): return format_cb(val) if isinstance(quantity, pq.UncertainQuantity): - magnitude_str = '{} +- {}'.format( - _format(quantity.magnitude), - _format(quantity.uncertainty.magnitude)) + magnitude_str = "{} +- {}".format( + _format(quantity.magnitude), _format(quantity.uncertainty.magnitude) + ) elif isinstance(quantity, pq.Quantity): magnitude_str = _format(quantity.magnitude) if isinstance(quantity, (pq.UncertainQuantity, pq.Quantity)): unit_str = _dimensionality_to_siunitx(quantity.dimensionality) - super().__init__(command='SI', arguments=(magnitude_str, unit_str), - options=options) + super().__init__( + command="SI", arguments=(magnitude_str, unit_str), options=options + ) else: - super().__init__(command='num', arguments=_format(quantity), - options=options) + super().__init__( + command="num", arguments=_format(quantity), options=options + ) self.arguments._escape = False # dash in e.g. \num{3 +- 2} if self.options is not None: diff --git a/pylatex/section.py b/pylatex/section.py index 45745f55..b889acbf 100644 --- a/pylatex/section.py +++ b/pylatex/section.py @@ -45,8 +45,8 @@ def __init__(self, title, numbering=None, *, label=True, **kwargs): if isinstance(label, Label): self.label = label elif isinstance(label, str): - if ':' in label: - label = label.split(':', 1) + if ":" in label: + label = label.split(":", 1) self.label = Label(Marker(label[1], label[0])) else: self.label = Label(Marker(label, self.marker_prefix)) @@ -67,14 +67,14 @@ def dumps(self): """ if not self.numbering: - num = '*' + num = "*" else: - num = '' + num = "" string = Command(self.latex_name + num, self.title).dumps() if self.label is not None: - string += '%\n' + self.label.dumps() - string += '%\n' + self.dumps_content() + string += "%\n" + self.label.dumps() + string += "%\n" + self.dumps_content() return string diff --git a/pylatex/table.py b/pylatex/table.py index 255f5231..1b7a8dad 100644 --- a/pylatex/table.py +++ b/pylatex/table.py @@ -6,8 +6,14 @@ :license: MIT, see License for more details. """ -from .base_classes import LatexObject, Container, Command, UnsafeCommand, \ - Float, Environment +from .base_classes import ( + LatexObject, + Container, + Command, + UnsafeCommand, + Float, + Environment, +) from .package import Package from .errors import TableRowSizeError, TableError from .utils import dumps_list, NoEscape, _is_iterable @@ -18,7 +24,7 @@ # The letters used to count the table width -COLUMN_LETTERS = {'l', 'c', 'r', 'p', 'm', 'b', 'X'} +COLUMN_LETTERS = {"l", "c", "r", "p", "m", "b", "X"} def _get_table_width(table_spec): @@ -37,10 +43,10 @@ def _get_table_width(table_spec): """ # Remove things like {\bfseries} - cleaner_spec = re.sub(r'{[^}]*}', '', table_spec) + cleaner_spec = re.sub(r"{[^}]*}", "", table_spec) # Remove X[] in tabu environments so they dont interfere with column count - cleaner_spec = re.sub(r'X\[(.*?(.))\]', r'\2', cleaner_spec) + cleaner_spec = re.sub(r"X\[(.*?(.))\]", r"\2", cleaner_spec) spec_counter = Counter(cleaner_spec) return sum(spec_counter[l] for l in COLUMN_LETTERS) @@ -50,13 +56,22 @@ class Tabular(Environment): """A class that represents a tabular.""" _repr_attributes_mapping = { - 'table_spec': 'arguments', - 'pos': 'options', + "table_spec": "arguments", + "pos": "options", } - def __init__(self, table_spec, data=None, pos=None, *, - row_height=None, col_space=None, width=None, booktabs=None, - **kwargs): + def __init__( + self, + table_spec, + data=None, + pos=None, + *, + row_height=None, + col_space=None, + width=None, + booktabs=None, + **kwargs + ): """ Args ---- @@ -95,16 +110,15 @@ def __init__(self, table_spec, data=None, pos=None, *, self.booktabs = booktabs if self.booktabs: - self.packages.add(Package('booktabs')) - table_spec = '@{}%s@{}' % table_spec + self.packages.add(Package("booktabs")) + table_spec = "@{}%s@{}" % table_spec - self.row_height = row_height if row_height is not None else \ - cf.active.row_height + self.row_height = row_height if row_height is not None else cf.active.row_height self.col_space = col_space - super().__init__(data=data, options=pos, - arguments=NoEscape(table_spec), - **kwargs) + super().__init__( + data=data, options=pos, arguments=NoEscape(table_spec), **kwargs + ) # Parameter that determines if the xcolor package has been added. self.color = False @@ -115,16 +129,16 @@ def dumps(self): string = "" if self.row_height is not None: - row_height = Command('renewcommand', arguments=[ - NoEscape(r'\arraystretch'), - self.row_height]) - string += row_height.dumps() + '%\n' + row_height = Command( + "renewcommand", arguments=[NoEscape(r"\arraystretch"), self.row_height] + ) + string += row_height.dumps() + "%\n" if self.col_space is not None: - col_space = Command('setlength', arguments=[ - NoEscape(r'\tabcolsep'), - self.col_space]) - string += col_space.dumps() + '%\n' + col_space = Command( + "setlength", arguments=[NoEscape(r"\tabcolsep"), self.col_space] + ) + string += col_space.dumps() + "%\n" return string + super().dumps() @@ -144,19 +158,18 @@ def dumps_content(self, **kwargs): A LaTeX string representing the """ - content = '' + content = "" if self.booktabs: - content += '\\toprule%\n' + content += "\\toprule%\n" content += super().dumps_content(**kwargs) if self.booktabs: - content += '\\bottomrule%\n' + content += "\\bottomrule%\n" return NoEscape(content) - def add_hline(self, start=None, end=None, *, color=None, - cmidruleoption=None): + def add_hline(self, start=None, end=None, *, color=None, cmidruleoption=None): r"""Add a horizontal line to the table. Args @@ -172,17 +185,17 @@ def add_hline(self, start=None, end=None, *, color=None, ``\cmidrule(x){1-3}``. """ if self.booktabs: - hline = 'midrule' - cline = 'cmidrule' + hline = "midrule" + cline = "cmidrule" if cmidruleoption is not None: - cline += '(' + cmidruleoption + ')' + cline += "(" + cmidruleoption + ")" else: - hline = 'hline' - cline = 'cline' + hline = "hline" + cline = "cline" if color is not None: if not self.color: - self.packages.append(Package('xcolor', options='table')) + self.packages.append(Package("xcolor", options="table")) self.color = True color_command = Command(command="arrayrulecolor", arguments=color) self.append(color_command) @@ -195,16 +208,14 @@ def add_hline(self, start=None, end=None, *, color=None, elif end is None: end = self.width - self.append(Command(cline, - dumps_list([start, NoEscape('-'), end]))) + self.append(Command(cline, dumps_list([start, NoEscape("-"), end]))) def add_empty_row(self): """Add an empty row to the table.""" - self.append(NoEscape((self.width - 1) * '&' + r'\\')) + self.append(NoEscape((self.width - 1) * "&" + r"\\")) - def add_row(self, *cells, color=None, escape=None, mapper=None, - strict=True): + def add_row(self, *cells, color=None, escape=None, mapper=None, strict=True): """Add a row of cells to the table. Args @@ -246,28 +257,30 @@ def add_row(self, *cells, color=None, escape=None, mapper=None, cell_count += 1 if strict and cell_count != self.width: - msg = "Number of cells added to table ({}) " \ + msg = ( + "Number of cells added to table ({}) " "did not match table width ({})".format(cell_count, self.width) + ) raise TableRowSizeError(msg) if color is not None: if not self.color: - self.packages.append(Package("xcolor", options='table')) + self.packages.append(Package("xcolor", options="table")) self.color = True color_command = Command(command="rowcolor", arguments=color) self.append(color_command) - self.append(dumps_list(cells, escape=escape, token='&', - mapper=mapper) + NoEscape(r'\\')) + self.append( + dumps_list(cells, escape=escape, token="&", mapper=mapper) + NoEscape(r"\\") + ) class Tabularx(Tabular): """A class that represents a tabularx environment.""" - packages = [Package('tabularx')] + packages = [Package("tabularx")] - def __init__(self, *args, width_argument=NoEscape(r'\textwidth'), - **kwargs): + def __init__(self, *args, width_argument=NoEscape(r"\textwidth"), **kwargs): """ Args ---- @@ -283,7 +296,7 @@ class MultiColumn(Container): # TODO: Make this subclass of ContainerCommand - def __init__(self, size, *, align='c', color=None, data=None): + def __init__(self, size, *, align="c", color=None, data=None): """ Args ---- @@ -304,7 +317,7 @@ def __init__(self, size, *, align='c', color=None, data=None): # Add a cell color to the MultiColumn if color is not None: - self.packages.append(Package('xcolor', options='table')) + self.packages.append(Package("xcolor", options="table")) color_command = Command("cellcolor", arguments=color) self.append(color_command) @@ -327,9 +340,9 @@ class MultiRow(Container): # TODO: Make this subclass CommandBase and Container - packages = [Package('multirow')] + packages = [Package("multirow")] - def __init__(self, size, *, width='*', color=None, data=None): + def __init__(self, size, *, width="*", color=None, data=None): """ Args ---- @@ -350,7 +363,7 @@ def __init__(self, size, *, width='*', color=None, data=None): super().__init__(data=data) if color is not None: - self.packages.append(Package('xcolor', options='table')) + self.packages.append(Package("xcolor", options="table")) color_command = Command("cellcolor", arguments=color) self.append(color_command) @@ -375,11 +388,22 @@ class Table(Float): class Tabu(Tabular): """A class that represents a tabu (more flexible table).""" - packages = [Package('tabu')] - - def __init__(self, table_spec, data=None, pos=None, *, - row_height=None, col_space=None, width=None, booktabs=None, - spread=None, to=None, **kwargs): + packages = [Package("tabu")] + + def __init__( + self, + table_spec, + data=None, + pos=None, + *, + row_height=None, + col_space=None, + width=None, + booktabs=None, + spread=None, + to=None, + **kwargs + ): """ Args ---- @@ -415,9 +439,16 @@ def __init__(self, table_spec, data=None, pos=None, *, * https://en.wikibooks.org/wiki/LaTeX/Tables#The_tabular_environment """ - super().__init__(table_spec, data, pos, - row_height=row_height, col_space=col_space, - width=width, booktabs=booktabs, **kwargs) + super().__init__( + table_spec, + data, + pos, + row_height=row_height, + col_space=col_space, + width=width, + booktabs=booktabs, + **kwargs + ) self._preamble = "" if spread: @@ -442,8 +473,10 @@ def dumps(self): elif _s.startswith(r"\begin{tabu}"): _s = _s[:12] + self._preamble + _s[12:] else: - raise TableError("Can't apply preamble to Tabu table " - "(unexpected initial command sequence)") + raise TableError( + "Can't apply preamble to Tabu table " + "(unexpected initial command sequence)" + ) return _s @@ -451,7 +484,7 @@ def dumps(self): class LongTable(Tabular): """A class that represents a longtable (multipage table).""" - packages = [Package('longtable')] + packages = [Package("longtable")] header = False foot = False @@ -466,7 +499,7 @@ def end_table_header(self): self.header = True - self.append(Command(r'endhead')) + self.append(Command(r"endhead")) def end_table_footer(self): r"""End the table foot which will appear on every page.""" @@ -477,7 +510,7 @@ def end_table_footer(self): self.foot = True - self.append(Command('endfoot')) + self.append(Command("endfoot")) def end_table_last_footer(self): r"""End the table foot which will appear on the last page.""" @@ -488,7 +521,7 @@ def end_table_last_footer(self): self.lastFoot = True - self.append(Command('endlastfoot')) + self.append(Command("endlastfoot")) class LongTabu(LongTable, Tabu): @@ -504,9 +537,9 @@ class LongTabularx(Tabularx, LongTable): elements in that document over multiple pages as well. """ - _latex_name = 'tabularx' + _latex_name = "tabularx" - packages = [Package('ltablex')] + packages = [Package("ltablex")] class ColumnType(UnsafeCommand): @@ -517,10 +550,7 @@ class ColumnType(UnsafeCommand): questions/257128/how-does-the-newcolumntype-command-work>`_. """ - _repr_attributes_mapping = { - 'name': 'arguments', - 'parameters': 'options' - } + _repr_attributes_mapping = {"name": "arguments", "parameters": "options"} def __init__(self, name, base, modifications, *, parameters=None): """ @@ -546,13 +576,17 @@ def __init__(self, name, base, modifications, *, parameters=None): if parameters is None: # count the number of non escaped # parameters - parameters = len(re.findall(r'(?{%s\arraybackslash}%s" % (modifications, base) - super().__init__(command="newcolumntype", arguments=name, - options=parameters, extra_arguments=modified) + super().__init__( + command="newcolumntype", + arguments=name, + options=parameters, + extra_arguments=modified, + ) diff --git a/pylatex/tikz.py b/pylatex/tikz.py index 98add4be..59300fb4 100644 --- a/pylatex/tikz.py +++ b/pylatex/tikz.py @@ -26,14 +26,14 @@ def append_positional(self, option): class TikZ(Environment): """Basic TikZ container class.""" - _latex_name = 'tikzpicture' - packages = [Package('tikz')] + _latex_name = "tikzpicture" + packages = [Package("tikz")] class Axis(Environment): """PGFPlots axis container class, this contains plots.""" - packages = [Package('pgfplots'), Command('pgfplotsset', 'compat=newest')] + packages = [Package("pgfplots"), Command("pgfplotsset", "compat=newest")] def __init__(self, options=None, *, data=None): """ @@ -49,14 +49,15 @@ def __init__(self, options=None, *, data=None): class TikZScope(Environment): """TikZ Scope Environment.""" - _latex_name = 'scope' + _latex_name = "scope" class TikZCoordinate(LatexObject): """A General Purpose Coordinate Class.""" - _coordinate_str_regex = re.compile(r'(\+\+)?\(\s*(-?[0-9]+(\.[0-9]+)?)\s*' - r',\s*(-?[0-9]+(\.[0-9]+)?)\s*\)') + _coordinate_str_regex = re.compile( + r"(\+\+)?\(\s*(-?[0-9]+(\.[0-9]+)?)\s*" r",\s*(-?[0-9]+(\.[0-9]+)?)\s*\)" + ) def __init__(self, x, y, relative=False): """ @@ -75,10 +76,10 @@ def __init__(self, x, y, relative=False): def __repr__(self): if self.relative: - ret_str = '++' + ret_str = "++" else: - ret_str = '' - return ret_str + '({},{})'.format(self._x, self._y) + ret_str = "" + return ret_str + "({},{})".format(self._x, self._y) def dumps(self): """Return representation.""" @@ -92,15 +93,14 @@ def from_str(cls, coordinate): m = cls._coordinate_str_regex.match(coordinate) if m is None: - raise ValueError('invalid coordinate string') + raise ValueError("invalid coordinate string") - if m.group(1) == '++': + if m.group(1) == "++": relative = True else: relative = False - return TikZCoordinate( - float(m.group(2)), float(m.group(4)), relative=relative) + return TikZCoordinate(float(m.group(2)), float(m.group(4)), relative=relative) def __eq__(self, other): if isinstance(other, tuple): @@ -113,47 +113,47 @@ def __eq__(self, other): other_x = other._x other_y = other._y else: - raise TypeError('can only compare tuple and TiKZCoordinate types') + raise TypeError("can only compare tuple and TiKZCoordinate types") # prevent comparison between relative and non relative # by returning False - if (other_relative != self.relative): + if other_relative != self.relative: return False # return comparison result - return (other_x == self._x and other_y == self._y) + return other_x == self._x and other_y == self._y def _arith_check(self, other): if isinstance(other, tuple): other_coord = TikZCoordinate(*other) elif isinstance(other, TikZCoordinate): if other.relative is True or self.relative is True: - raise ValueError('refusing to add relative coordinates') + raise ValueError("refusing to add relative coordinates") other_coord = other else: - raise TypeError('can only add tuple or TiKZCoordinate types') + raise TypeError("can only add tuple or TiKZCoordinate types") return other_coord def __add__(self, other): other_coord = self._arith_check(other) - return TikZCoordinate(self._x + other_coord._x, - self._y + other_coord._y) + return TikZCoordinate(self._x + other_coord._x, self._y + other_coord._y) def __radd__(self, other): self.__add__(other) def __sub__(self, other): other_coord = self._arith_check(other) - return TikZCoordinate(self._x - other_coord._y, - self._y - other_coord._y) + return TikZCoordinate(self._x - other_coord._y, self._y - other_coord._y) def distance_to(self, other): """Euclidean distance between two coordinates.""" other_coord = self._arith_check(other) - return math.sqrt(math.pow(self._x - other_coord._x, 2) + - math.pow(self._y - other_coord._y, 2)) + return math.sqrt( + math.pow(self._x - other_coord._x, 2) + + math.pow(self._y - other_coord._y, 2) + ) class TikZObject(Container): @@ -188,7 +188,7 @@ def __init__(self, node_handle, anchor_name): self.anchor = anchor_name def __repr__(self): - return '({}.{})'.format(self.handle, self.anchor) + return "({}.{})".format(self.handle, self.anchor) def dumps(self): """Return a representation. Alias for consistency.""" @@ -199,7 +199,7 @@ def dumps(self): class TikZNode(TikZObject): """A class that represents a TiKZ node.""" - _possible_anchors = ['north', 'south', 'east', 'west'] + _possible_anchors = ["north", "south", "east", "west"] def __init__(self, handle=None, options=None, at=None, text=None): """ @@ -222,8 +222,8 @@ def __init__(self, handle=None, options=None, at=None, text=None): self._node_position = at else: raise TypeError( - 'at parameter must be an object of the' - 'TikzCoordinate class') + "at parameter must be an object of the" "TikzCoordinate class" + ) self._node_text = text @@ -231,20 +231,20 @@ def dumps(self): """Return string representation of the node.""" ret_str = [] - ret_str.append(Command('node', options=self.options).dumps()) + ret_str.append(Command("node", options=self.options).dumps()) if self.handle is not None: - ret_str.append('({})'.format(self.handle)) + ret_str.append("({})".format(self.handle)) if self._node_position is not None: - ret_str.append('at {}'.format(str(self._node_position))) + ret_str.append("at {}".format(str(self._node_position))) if self._node_text is not None: - ret_str.append('{{{text}}};'.format(text=self._node_text)) + ret_str.append("{{{text}}};".format(text=self._node_text)) else: - ret_str.append('{};') + ret_str.append("{};") - return ' '.join(ret_str) + return " ".join(ret_str) def get_anchor_point(self, anchor_name): """Return an anchor point of the node, if it exists.""" @@ -253,7 +253,7 @@ def get_anchor_point(self, anchor_name): return TikZNodeAnchor(self.handle, anchor_name) else: try: - anchor = int(anchor_name.split('_')[1]) + anchor = int(anchor_name.split("_")[1]) except: anchor = None @@ -303,9 +303,7 @@ def dumps(self): class TikZPathList(LatexObject): """Represents a path drawing.""" - _legal_path_types = ['--', '-|', '|-', 'to', - 'rectangle', 'circle', - 'arc', 'edge'] + _legal_path_types = ["--", "-|", "|-", "to", "rectangle", "circle", "arc", "edge"] def __init__(self, *args): """ @@ -333,10 +331,10 @@ def _parse_next_item(self, item): except (TypeError, ValueError): # not a point, do something raise TypeError( - 'First element of path list must be a node identifier' - ' or coordinate' + "First element of path list must be a node identifier" + " or coordinate" ) - elif self._last_item_type == 'point': + elif self._last_item_type == "point": # point after point is permitted, doesnt draw try: self._add_point(item) @@ -347,7 +345,7 @@ def _parse_next_item(self, item): # will raise typeerror if wrong self._add_path(item) - elif self._last_item_type == 'path': + elif self._last_item_type == "path": # only point allowed after path original_exception = None try: @@ -366,8 +364,9 @@ def _parse_next_item(self, item): # disentangle exceptions if not_a_path is False: - raise ValueError('only a point descriptor can come' - ' after a path descriptor') + raise ValueError( + "only a point descriptor can come" " after a path descriptor" + ) if original_exception is not None: raise original_exception @@ -386,12 +385,12 @@ def _add_path(self, path, parse_only=False): elif isinstance(path, TikZUserPath): _path = path else: - raise TypeError('Only string or TikZUserPath types are allowed') + raise TypeError("Only string or TikZUserPath types are allowed") # add if parse_only is False: self._arg_list.append(_path) - self._last_item_type = 'path' + self._last_item_type = "path" else: return _path @@ -406,17 +405,19 @@ def _add_point(self, point, parse_only=False): elif isinstance(point, tuple): _item = TikZCoordinate(*point) elif isinstance(point, TikZNode): - _item = '({})'.format(point.handle) + _item = "({})".format(point.handle) elif isinstance(point, TikZNodeAnchor): _item = point.dumps() else: - raise TypeError('Only str, tuple, TikZCoordinate,' - 'TikZNode or TikZNodeAnchor types are allowed,' - ' got: {}'.format(type(point))) + raise TypeError( + "Only str, tuple, TikZCoordinate," + "TikZNode or TikZNodeAnchor types are allowed," + " got: {}".format(type(point)) + ) # add, finally if parse_only is False: self._arg_list.append(_item) - self._last_item_type = 'point' + self._last_item_type = "point" else: return _item @@ -432,7 +433,7 @@ def dumps(self): elif isinstance(item, str): ret_str.append(item) - return ' '.join(ret_str) + return " ".join(ret_str) class TikZPath(TikZObject): @@ -457,8 +458,7 @@ def __init__(self, path=None, options=None): elif path is None: self.path = TikZPathList() else: - raise TypeError( - 'argument "path" can only be of types list or TikZPathList') + raise TypeError('argument "path" can only be of types list or TikZPathList') def append(self, element): """Append a path element to the current list.""" @@ -467,11 +467,11 @@ def append(self, element): def dumps(self): """Return a representation for the command.""" - ret_str = [Command('path', options=self.options).dumps()] + ret_str = [Command("path", options=self.options).dumps()] ret_str.append(self.path.dumps()) - return ' '.join(ret_str) + ';' + return " ".join(ret_str) + ";" class TikZDraw(TikZPath): @@ -490,22 +490,19 @@ def __init__(self, path=None, options=None): # append option if self.options is not None: - self.options.append_positional('draw') + self.options.append_positional("draw") else: - self.options = TikZOptions('draw') + self.options = TikZOptions("draw") class Plot(LatexObject): """A class representing a PGFPlot.""" - packages = [Package('pgfplots'), Command('pgfplotsset', 'compat=newest')] + packages = [Package("pgfplots"), Command("pgfplotsset", "compat=newest")] - def __init__(self, - name=None, - func=None, - coordinates=None, - error_bar=None, - options=None): + def __init__( + self, name=None, func=None, coordinates=None, error_bar=None, options=None + ): """ Args ---- @@ -535,30 +532,38 @@ def dumps(self): str """ - string = Command('addplot', options=self.options).dumps() + string = Command("addplot", options=self.options).dumps() if self.coordinates is not None: - string += ' coordinates {%\n' + string += " coordinates {%\n" if self.error_bar is None: for x, y in self.coordinates: # ie: "(x,y)" - string += '(' + str(x) + ',' + str(y) + ')%\n' + string += "(" + str(x) + "," + str(y) + ")%\n" else: - for (x, y), (e_x, e_y) in zip(self.coordinates, - self.error_bar): + for (x, y), (e_x, e_y) in zip(self.coordinates, self.error_bar): # ie: "(x,y) +- (e_x,e_y)" - string += '(' + str(x) + ',' + str(y) + \ - ') +- (' + str(e_x) + ',' + str(e_y) + ')%\n' - - string += '};%\n%\n' + string += ( + "(" + + str(x) + + "," + + str(y) + + ") +- (" + + str(e_x) + + "," + + str(e_y) + + ")%\n" + ) + + string += "};%\n%\n" elif self.func is not None: - string += '{' + self.func + '};%\n%\n' + string += "{" + self.func + "};%\n%\n" if self.name is not None: - string += Command('addlegendentry', self.name).dumps() + string += Command("addlegendentry", self.name).dumps() super().dumps() diff --git a/pylatex/utils.py b/pylatex/utils.py index ed517b04..b4e9deb1 100644 --- a/pylatex/utils.py +++ b/pylatex/utils.py @@ -12,28 +12,28 @@ import pylatex.base_classes _latex_special_chars = { - '&': r'\&', - '%': r'\%', - '$': r'\$', - '#': r'\#', - '_': r'\_', - '{': r'\{', - '}': r'\}', - '~': r'\textasciitilde{}', - '^': r'\^{}', - '\\': r'\textbackslash{}', - '\n': '\\newline%\n', - '-': r'{-}', - '\xA0': '~', # Non-breaking space - '[': r'{[}', - ']': r'{]}', + "&": r"\&", + "%": r"\%", + "$": r"\$", + "#": r"\#", + "_": r"\_", + "{": r"\{", + "}": r"\}", + "~": r"\textasciitilde{}", + "^": r"\^{}", + "\\": r"\textbackslash{}", + "\n": "\\newline%\n", + "-": r"{-}", + "\xA0": "~", # Non-breaking space + "[": r"{[}", + "]": r"{]}", } _tmp_path = None def _is_iterable(element): - return hasattr(element, '__iter__') and not isinstance(element, str) + return hasattr(element, "__iter__") and not isinstance(element, str) class NoEscape(str): @@ -51,7 +51,7 @@ class NoEscape(str): """ def __repr__(self): - return '%s(%s)' % (self.__class__.__name__, self) + return "%s(%s)" % (self.__class__.__name__, self) def __add__(self, right): s = super().__add__(right) @@ -92,7 +92,7 @@ def escape_latex(s): if isinstance(s, NoEscape): return s - return NoEscape(''.join(_latex_special_chars.get(c, c) for c in str(s))) + return NoEscape("".join(_latex_special_chars.get(c, c) for c in str(s))) def fix_filename(path): @@ -128,25 +128,25 @@ def fix_filename(path): '\detokenize{/etc/local/foo.bar.baz/foo~1/document.pdf}' """ - path_parts = path.split('/' if os.name == 'posix' else '\\') + path_parts = path.split("/" if os.name == "posix" else "\\") dir_parts = path_parts[:-1] filename = path_parts[-1] - file_parts = filename.split('.') + file_parts = filename.split(".") - if os.name == 'posix' and len(file_parts) > 2: - filename = '{' + '.'.join(file_parts[0:-1]) + '}.' + file_parts[-1] + if os.name == "posix" and len(file_parts) > 2: + filename = "{" + ".".join(file_parts[0:-1]) + "}." + file_parts[-1] dir_parts.append(filename) - fixed_path = '/'.join(dir_parts) + fixed_path = "/".join(dir_parts) - if '~' in fixed_path: - fixed_path = r'\detokenize{' + fixed_path + '}' + if "~" in fixed_path: + fixed_path = r"\detokenize{" + fixed_path + "}" return fixed_path -def dumps_list(l, *, escape=True, token='%\n', mapper=None, as_content=True): +def dumps_list(l, *, escape=True, token="%\n", mapper=None, as_content=True): r"""Try to generate a LaTeX string of a list that can contain anything. Args @@ -185,8 +185,9 @@ def dumps_list(l, *, escape=True, token='%\n', mapper=None, as_content=True): \$100\% True """ - strings = (_latex_item_to_string(i, escape=escape, as_content=as_content) - for i in l) + strings = ( + _latex_item_to_string(i, escape=escape, as_content=as_content) for i in l + ) if mapper is not None: if not isinstance(mapper, list): @@ -260,7 +261,7 @@ def bold(s, *, escape=True): if escape: s = escape_latex(s) - return NoEscape(r'\textbf{' + s + '}') + return NoEscape(r"\textbf{" + s + "}") def italic(s, *, escape=True): @@ -290,10 +291,10 @@ def italic(s, *, escape=True): if escape: s = escape_latex(s) - return NoEscape(r'\textit{' + s + '}') + return NoEscape(r"\textit{" + s + "}") -def verbatim(s, *, delimiter='|'): +def verbatim(s, *, delimiter="|"): r"""Make the string verbatim. Wraps the given string in a \verb LaTeX command. @@ -320,7 +321,7 @@ def verbatim(s, *, delimiter='|'): \verb!pi|pe! """ - return NoEscape(r'\verb' + delimiter + s + delimiter) + return NoEscape(r"\verb" + delimiter + s + delimiter) def make_temp_dir(): diff --git a/setup.py b/setup.py index a4da4a98..77116950 100644 --- a/setup.py +++ b/setup.py @@ -21,35 +21,34 @@ ) if sys.version_info[:2] <= (3, 5): - dependencies = ['ordered-set<4.0.0'] + dependencies = ["ordered-set<4.0.0"] else: - dependencies = ['ordered-set'] + dependencies = ["ordered-set"] extras = { - 'docs': ['sphinx', 'jinja2<3.0', 'MarkupSafe==2.0.1', 'alabaster<0.7.12'], - 'matrices': ['numpy'], - 'matplotlib': ['matplotlib'], - 'quantities': ['quantities', 'numpy'], - 'testing': ['pytest>=4.6', - 'coverage', 'pytest-cov'], - 'packaging': ['twine'], + "docs": ["sphinx", "jinja2<3.0", "MarkupSafe==2.0.1", "alabaster<0.7.12"], + "matrices": ["numpy"], + "matplotlib": ["matplotlib"], + "quantities": ["quantities", "numpy"], + "testing": ["pytest>=4.6", "coverage", "pytest-cov", "black"], + "packaging": ["twine"], } if sys.version_info[0] == 3: - source_dir = '.' + source_dir = "." if sys.version_info < (3, 4): - del extras['docs'] - extras['matplotlib'] = ['matplotlib<2.0.0'] - extras['matrices'] = ['numpy<1.12.0'] - extras['quantities'][1] = 'numpy<1.12.0' + del extras["docs"] + extras["matplotlib"] = ["matplotlib<2.0.0"] + extras["matrices"] = ["numpy<1.12.0"] + extras["quantities"][1] = "numpy<1.12.0" else: - source_dir = 'python2_source' - dependencies.append('future>=0.15.2') + source_dir = "python2_source" + dependencies.append("future>=0.15.2") PY2_CONVERTED = False -extras['all'] = list(set([req for reqs in extras.values() for req in reqs])) +extras["all"] = list(set([req for reqs in extras.values() for req in reqs])) # Automatically convert the source from Python 3 to Python 2 if we need to. @@ -67,66 +66,70 @@ def initialize_options(self): def convert_to_py2(): global PY2_CONVERTED - if source_dir == 'python2_source' and not PY2_CONVERTED: - pylatex_exists = os.path.exists(os.path.join(source_dir, 'pylatex')) + if source_dir == "python2_source" and not PY2_CONVERTED: + pylatex_exists = os.path.exists(os.path.join(source_dir, "pylatex")) - if '+' not in version and pylatex_exists: + if "+" not in version and pylatex_exists: # This is an official release, just use the pre existing existing # python2_source dir return try: # Check if 3to2 exists - subprocess.check_output(['3to2', '--help']) - subprocess.check_output(['pasteurize', '--help']) + subprocess.check_output(["3to2", "--help"]) + subprocess.check_output(["pasteurize", "--help"]) except OSError as e: if e.errno != errno.ENOENT: raise if not pylatex_exists: - raise ImportError('3to2 and future need to be installed ' - 'before installing when PyLaTeX for Python ' - '2.7 when it is not installed using one of ' - 'the pip releases.') + raise ImportError( + "3to2 and future need to be installed " + "before installing when PyLaTeX for Python " + "2.7 when it is not installed using one of " + "the pip releases." + ) else: - converter = os.path.dirname(os.path.realpath(__file__)) \ - + '/convert_to_py2.sh' + converter = ( + os.path.dirname(os.path.realpath(__file__)) + "/convert_to_py2.sh" + ) subprocess.check_call([converter]) PY2_CONVERTED = True -cmdclass['install'] = CustomInstall -cmdclass['egg_info'] = CustomEggInfo - -setup(name='PyLaTeX', - version=version, - author='Jelte Fennema', - author_email='pylatex@jeltef.nl', - description='A Python library for creating LaTeX files and snippets', - long_description=open('README.rst').read(), - package_dir={'': source_dir}, - packages=['pylatex', 'pylatex.base_classes'], - url='https://github.com/JelteF/PyLaTeX', - license='MIT', - install_requires=dependencies, - extras_require=extras, - cmdclass=cmdclass, - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Console', - 'Intended Audience :: Developers', - 'Intended Audience :: Education', - 'Intended Audience :: End Users/Desktop', - 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: MIT License', - 'Operating System :: POSIX :: Linux', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Topic :: Software Development :: Code Generators', - 'Topic :: Text Processing :: Markup :: LaTeX', - ] - ) +cmdclass["install"] = CustomInstall +cmdclass["egg_info"] = CustomEggInfo + +setup( + name="PyLaTeX", + version=version, + author="Jelte Fennema", + author_email="pylatex@jeltef.nl", + description="A Python library for creating LaTeX files and snippets", + long_description=open("README.rst").read(), + package_dir={"": source_dir}, + packages=["pylatex", "pylatex.base_classes"], + url="https://github.com/JelteF/PyLaTeX", + license="MIT", + install_requires=dependencies, + extras_require=extras, + cmdclass=cmdclass, + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Intended Audience :: Developers", + "Intended Audience :: Education", + "Intended Audience :: End Users/Desktop", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Topic :: Software Development :: Code Generators", + "Topic :: Text Processing :: Markup :: LaTeX", + ], +)