pax_global_header00006660000000000000000000000064145142521650014517gustar00rootroot0000000000000052 comment=73765878cdfc33999d33633b10791fd7872d3203 PyLaTeX-1.4.2/000077500000000000000000000000001451425216500130115ustar00rootroot00000000000000PyLaTeX-1.4.2/.codecov.yml000066400000000000000000000007611451425216500152400ustar00rootroot00000000000000coverage: status: project: default: target: auto treshold: 1 base: auto patch: default: target: 90 treshold: null base: auto comment: layout: "reach, diff, flags, files" behavior: default require_changes: false # if true: only post the comment if coverage changes require_base: no # [yes :: must have a base report to post] require_head: yes # [yes :: must have a head report to post] branches: null PyLaTeX-1.4.2/.gitattributes000066400000000000000000000000411451425216500156770ustar00rootroot00000000000000pylatex/_version.py export-subst PyLaTeX-1.4.2/.github/000077500000000000000000000000001451425216500143515ustar00rootroot00000000000000PyLaTeX-1.4.2/.github/workflows/000077500000000000000000000000001451425216500164065ustar00rootroot00000000000000PyLaTeX-1.4.2/.github/workflows/ci.yml000066400000000000000000000021451451425216500175260ustar00rootroot00000000000000name: Python package on: [push] jobs: build: runs-on: ubuntu-latest strategy: fail-fast: false matrix: python-version: - "3.7" - "3.8" - "3.9" - "3.10" - "3.11" - "3.12" steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} # Disable cache so that issues with new dependencies are found more easily # cache: 'pip' # cache-dependency-path: | # dev_requirements.txt # setup.py - name: Install dependencies run: | sudo apt-get update sudo apt-get install texlive-latex-extra texlive-pictures texlive-science texlive-fonts-recommended lmodern ghostscript python -m pip install --upgrade pip pip install -r dev_requirements.txt --upgrade sudo sed '/pattern=".*PDF.*"/d' -i /etc/ImageMagick*/policy.xml - name: Run tests run: | ./testall.sh PyLaTeX-1.4.2/.gitignore000066400000000000000000000011141451425216500147760ustar00rootroot00000000000000*.py[cod] # C extensions *.so # Packages *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 __pycache__ MANIFEST # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox nosetests.xml # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject # Testfiles *.pdf *.tex # Python 2 distribution files python2_source # Venv venv* .venv* # Ignore generated doc files docs/source/examples docs/source/pylatex docs/source/_static/examples docs/gh-pages coverage.xml # Folders .idea/ .virtualenv/ .coverage.* PyLaTeX-1.4.2/.travis.yml000066400000000000000000000016571451425216500151330ustar00rootroot00000000000000sudo: required dist: xenial cache: pip language: python python: - '2.7' - '3.4' - '3.5' - '3.6' - '3.7' - '3.8' # Travis doesn't support 3.9 yet # - '3.9' # allow failures - 'nightly' - 'pypy2.7-6.0' - 'pypy3.5-6.0' matrix: fast_finish: true allow_failures: - python: 'nightly' - python: 'pypy2.7-6.0' - python: 'pypy3.5-6.0' addons: apt: packages: - texlive-latex-extra - texlive-pictures - texlive-science - texlive-fonts-recommended - lmodern - ghostscript - pgf install: - cat /etc/ImageMagick*/policy.xml - sudo sed '/pattern=".*PDF.*"/d' -i /etc/ImageMagick*/policy.xml - cat /etc/ImageMagick*/policy.xml - pip install 3to2 future - pip install codecov - 'if [ "$TRAVIS_PYTHON_VERSION" == 3.4 ]; then pip install -e .[all] --upgrade; else pip install -r dev_requirements.txt --upgrade; fi' script: ./testall.sh after_script: codecov PyLaTeX-1.4.2/CONTRIBUTING.md000066400000000000000000000001211451425216500152340ustar00rootroot00000000000000See the documentation: https://jeltef.github.io/PyLaTeX/latest/contributing.html PyLaTeX-1.4.2/LICENSE000066400000000000000000000020701451425216500140150ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2014 Jelte Fennema Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PyLaTeX-1.4.2/MANIFEST.in000066400000000000000000000001601451425216500145440ustar00rootroot00000000000000include *.md recursive-include pylatex *.py recursive-include python2_source/pylatex *.py include versioneer.py PyLaTeX-1.4.2/README.rst000066400000000000000000000042021451425216500144760ustar00rootroot00000000000000PyLaTeX |Actions| |License| |PyPi| |Stable Docs| |Latest Docs| ============================================================== PyLaTeX is a Python library for creating and compiling LaTeX files or snippets. The goal of this library is being an easy, but extensible interface between Python and LaTeX. Installation ------------ Simply install using ``pip``:: pip install pylatex And then install a relevant LaTeX processor and other dependencies. Examples: Ubuntu ~~~~~~~ sudo apt-get install texlive-pictures texlive-science \ texlive-latex-extra latexmk Documentation ------------- There are two versions of the documentation: - The one generated for the `last stable release `__. - The one based on the `latest git version `__. Contributing ------------ Read the `How to contribute `__ page for tips and rules when you want to contribute. Examples -------- The documentation contains a lot of examples that show the functionality. To give an impression of what can be generated see this picture: .. figure:: https://raw.github.com/JelteF/PyLaTeX/master/docs/source/_static/screenshot.png :alt: Generated PDF by PyLaTeX Copyright and License --------------------- Copyright 2014 Jelte Fennema, under `the MIT license `__ .. |Actions| image:: https://github.com/JelteF/PyLaTeX/actions/workflows/ci.yml/badge.svg :target: https://github.com/JelteF/PyLaTeX/actions/workflows/ci.yml .. |License| image:: https://img.shields.io/github/license/jeltef/pylatex.svg :target: https://github.com/JelteF/PyLaTeX/blob/master/LICENSE .. |PyPi| image:: https://img.shields.io/pypi/v/pylatex.svg :target: https://pypi.python.org/pypi/PyLaTeX .. |Latest Docs| image:: https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat :target: https://jeltef.github.io/PyLaTeX/latest/ .. |Stable Docs| image:: https://img.shields.io/badge/docs-stable-brightgreen.svg?style=flat :target: https://jeltef.github.io/PyLaTeX/current/ PyLaTeX-1.4.2/Vagrantfile000066400000000000000000000063121451425216500152000ustar00rootroot00000000000000# -*- mode: ruby -*- # vi: set ft=ruby : # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| # All Vagrant configuration is done here. The most common configuration # options are documented and commented below. For a complete reference, # please see the online documentation at vagrantup.com. # Every Vagrant virtual environment requires a box to build off of. config.vm.box = "ubuntu/trusty64" # Disable automatic box update checking. If you disable this, then # boxes will only be checked for updates when the user runs # `vagrant box outdated`. This is not recommended. # config.vm.box_check_update = false # Create a forwarded port mapping which allows access to a specific port # within the machine from a port on the host machine. In the example below, # accessing "localhost:8080" will access port 80 on the guest machine. # config.vm.network "forwarded_port", guest: 80, host: 8080 # Create a private network, which allows host-only access to the machine # using a specific IP. # config.vm.network "private_network", ip: "192.168.33.10" # Create a public network, which generally matched to bridged network. # Bridged networks make the machine appear as another physical device on # your network. # config.vm.network "public_network" # If true, then any SSH connections made will enable agent forwarding. # Default value: false # config.ssh.forward_agent = true # Share an additional folder to the guest VM. The first argument is # the path on the host to the actual folder. The second argument is # the path on the guest to mount the folder. And the optional third # argument is a set of non-required options. # config.vm.synced_folder "../data", "/vagrant_data" # Provider-specific configuration so you can fine-tune various # backing providers for Vagrant. These expose provider-specific options. # Example for VirtualBox: # # config.vm.provider "virtualbox" do |vb| # # Don't boot with headless mode # vb.gui = true # # # Use VBoxManage to customize the VM. For example to change memory: # vb.customize ["modifyvm", :id, "--memory", "1024"] # end # # View the documentation for the provider you're using for more # information on available options. # Provisioning $script = <<-SCRIPT echo Starting provisioning... sudo apt-get update sudo apt-get install -y build-essential sudo apt-get install -y texlive # texlive-science contains the SIunitx LaTeX package which is needed for quantities.py sudo apt-get install -y texlive-science sudo apt-get install -y imagemagick sudo apt-get install -y python3-pip sudo apt-get build-dep -y python-matplotlib pip3 install -e /vagrant[all] echo 'You can now cd into /vagrant and execute "./testall.sh -p python3" to run PyLaTeX tests.' | sudo tee --append /etc/update-motd.d/100-pylatex-info echo Provisioning finished. echo Running unit tests and generate docs... cd /vagrant ./testall.sh -p python3 -c echo Finished tests. SCRIPT config.vm.provision "shell", inline: $script end PyLaTeX-1.4.2/broken_examples/000077500000000000000000000000001451425216500161675ustar00rootroot00000000000000PyLaTeX-1.4.2/broken_examples/complex_report.py000066400000000000000000000115441451425216500216100ustar00rootroot00000000000000#!/usr/bin/python """ This example shows the functionality of the PyLaTeX library. It creates a sample report with 2 tables, one containing images and the other containing data. It also creates a complex header with an image. .. :copyright: (c) 2016 by Vladimir Gorovikov :license: MIT, see License for more details. """ # begin-doc-include import os from pylatex import ( Document, Foot, Head, LargeText, LineBreak, LongTabu, MediumText, MiniPage, MultiColumn, NewPage, PageStyle, StandAloneGraphic, Tabu, Tabularx, TextColor, simple_page_number, ) from pylatex.utils import NoEscape, bold def generate_unique(): geometry_options = { "head": "40pt", "margin": "0.5in", "bottom": "0.6in", "includeheadfoot": True, } doc = Document(geometry_options=geometry_options) # Generating first page style first_page = PageStyle("firstpage") # Header image with first_page.create(Head("L")) as header_left: with header_left.create( MiniPage(width=NoEscape(r"0.49\textwidth"), pos="c") ) as logo_wrapper: logo_file = os.path.join(os.path.dirname(__file__), "sample-logo.png") logo_wrapper.append( StandAloneGraphic(image_options="width=120px", filename=logo_file) ) # Add document title with first_page.create(Head("R")) as right_header: with right_header.create( MiniPage(width=NoEscape(r"0.49\textwidth"), pos="c", align="r") ) as title_wrapper: title_wrapper.append(LargeText(bold("Bank Account Statement"))) title_wrapper.append(LineBreak()) title_wrapper.append(MediumText(bold("Date"))) # Add footer with first_page.create(Foot("C")) as footer: message = "Important message please read" with footer.create( Tabularx("X X X X", width_argument=NoEscape(r"\textwidth")) ) as footer_table: footer_table.add_row( [MultiColumn(4, align="l", data=TextColor("blue", message))] ) footer_table.add_hline(color="blue") footer_table.add_empty_row() branch_address = MiniPage(width=NoEscape(r"0.25\textwidth"), pos="t") branch_address.append("960 - 22nd street east") branch_address.append("\n") branch_address.append("Saskatoon, SK") document_details = MiniPage( width=NoEscape(r"0.25\textwidth"), pos="t", align="r" ) document_details.append("1000") document_details.append(LineBreak()) document_details.append(simple_page_number()) footer_table.add_row( [branch_address, branch_address, branch_address, document_details] ) doc.preamble.append(first_page) # End first page style # Add customer information with doc.create(Tabu("X[l] X[r]")) as first_page_table: customer = MiniPage(width=NoEscape(r"0.49\textwidth"), pos="h") customer.append("Verna Volcano") customer.append("\n") customer.append("For some Person") customer.append("\n") customer.append("Address1") customer.append("\n") customer.append("Address2") customer.append("\n") customer.append("Address3") # Add branch information branch = MiniPage(width=NoEscape(r"0.49\textwidth"), pos="t!", align="r") branch.append("Branch no.") branch.append(LineBreak()) branch.append(bold("1181...")) branch.append(LineBreak()) branch.append(bold("TIB Cheque")) first_page_table.add_row([customer, branch]) first_page_table.add_empty_row() doc.change_document_style("firstpage") doc.add_color(name="lightgray", model="gray", description="0.80") # Add statement table with doc.create( LongTabu("X[l] X[2l] X[r] X[r] X[r]", row_height=1.5) ) as data_table: data_table.add_row( ["date", "description", "debits($)", "credits($)", "balance($)"], mapper=bold, color="lightgray", ) data_table.add_empty_row() data_table.add_hline() row = ["2016-JUN-01", "Test", "$100", "$1000", "-$900"] for i in range(30): if (i % 2) == 0: data_table.add_row(row, color="lightgray") else: data_table.add_row(row) doc.append(NewPage()) # Add cheque images with doc.create(LongTabu("X[c] X[c]")) as cheque_table: cheque_file = os.path.join(os.path.dirname(__file__), "chequeexample.png") cheque = StandAloneGraphic(cheque_file, image_options="width=200px") for i in range(0, 20): cheque_table.add_row([cheque, cheque]) doc.generate_pdf("complex_report", clean_tex=False) generate_unique() PyLaTeX-1.4.2/broken_examples/longtabu.py000066400000000000000000000024761451425216500203650ustar00rootroot00000000000000#!/usr/bin/python """ This example shows the functionality of the MiniPage element. It creates a sample page filled with labels using the MiniPage element. .. :copyright: (c) 2016 by Vladimir Gorovikov :license: MIT, see License for more details. """ # begin-doc-include from pylatex import Document, HFill, LongTabu from pylatex.utils import bold def genenerate_longtabu(): geometry_options = { "landscape": True, "margin": "0.5in", "headheight": "20pt", "headsep": "10pt", "includeheadfoot": True, } doc = Document(page_numbers=True, geometry_options=geometry_options) # Generate data table with doc.create(LongTabu("X[r] X[r] X[r] X[r] X[r] X[r]")) as data_table: header_row1 = ["Prov", "Num", "CurBal", "IntPay", "Total", "IntR"] data_table.add_row(header_row1, mapper=[bold]) data_table.add_hline() data_table.add_empty_row() data_table.end_table_header() data_table.add_row(["Prov", "Num", "CurBal", "IntPay", "Total", "IntR"]) row = ["PA", "9", "$100", "%10", "$1000", "Test"] for i in range(50): data_table.add_row(row) doc.append(bold("Grand Total:")) doc.append(HFill()) doc.append(bold("Total")) doc.generate_pdf("longtabu", clean_tex=False) genenerate_longtabu() PyLaTeX-1.4.2/broken_examples/tabus.py000066400000000000000000000037571451425216500176730ustar00rootroot00000000000000#!/usr/bin/python """ This example shows the functionality of the Tabu and LongTabu element. .. :copyright: (c) 2016 by Vladimir Gorovikov and Scott Wallace :license: MIT, see License for more details. """ # begin-doc-include from random import randint from pylatex import Center, Document, LongTabu, Tabu from pylatex.utils import bold def genenerate_tabus(): geometry_options = { "landscape": True, "margin": "1.5in", "headheight": "20pt", "headsep": "10pt", "includeheadfoot": True, } doc = Document(page_numbers=True, geometry_options=geometry_options) # Generate data table with 'tight' columns fmt = "X[r] X[r] X[r] X[r] X[r] X[r]" with doc.create(LongTabu(fmt, spread="0pt")) as data_table: header_row1 = ["Prov", "Num", "CurBal", "IntPay", "Total", "IntR"] data_table.add_row(header_row1, mapper=[bold]) data_table.add_hline() data_table.add_empty_row() data_table.end_table_header() data_table.add_row(["Prov", "Num", "CurBal", "IntPay", "Total", "IntR"]) row = ["PA", "9", "$100", "%10", "$1000", "Test"] for i in range(40): data_table.add_row(row) with doc.create(Center()) as centered: with centered.create(Tabu("X[r] X[r]", spread="1in")) as data_table: header_row1 = ["X", "Y"] data_table.add_row(header_row1, mapper=[bold]) data_table.add_hline() row = [randint(0, 1000), randint(0, 1000)] for i in range(4): data_table.add_row(row) with doc.create(Center()) as centered: with centered.create(Tabu("X[r] X[r]", to="4in")) as data_table: header_row1 = ["X", "Y"] data_table.add_row(header_row1, mapper=[bold]) data_table.add_hline() row = [randint(0, 1000), randint(0, 1000)] for i in range(4): data_table.add_row(row) doc.generate_pdf("tabus", clean_tex=False) genenerate_tabus() PyLaTeX-1.4.2/dev_requirements.txt000066400000000000000000000002561451425216500171360ustar00rootroot00000000000000-e .[all] -e git+https://github.com/JelteF/sphinx.git@better-autodoc-skip-member#egg=sphinx -e git+https://github.com/JelteF/sphinx_rtd_theme.git@master#egg=sphinx-rtd-theme PyLaTeX-1.4.2/docs/000077500000000000000000000000001451425216500137415ustar00rootroot00000000000000PyLaTeX-1.4.2/docs/Makefile000066400000000000000000000164461451425216500154140ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source -n # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/doctrees rm -rf $(BUILDDIR)/html/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html -n @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PyLaTeX.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PyLaTeX.qhc" applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." @echo "N.B. You won't be able to view it unless you put it in" \ "~/Library/Documentation/Help or install it in your application" \ "bundle." devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/PyLaTeX" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PyLaTeX" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." PyLaTeX-1.4.2/docs/build/000077500000000000000000000000001451425216500150405ustar00rootroot00000000000000PyLaTeX-1.4.2/docs/build/.keep000066400000000000000000000000001451425216500157530ustar00rootroot00000000000000PyLaTeX-1.4.2/docs/create_doc_files.sh000077500000000000000000000040271451425216500175550ustar00rootroot00000000000000#!/usr/bin/env bash # Optional named arguments: # -p COMMAND: the python command that should be used, e.g. -p python3 set -e # Default values python="python" # Check if a command line argument was provided as an input argument. while getopts "p:" opt; do case $opt in p) python=$OPTARG ;; \?) echo "Invalid option: -$OPTARG" >&2 exit 1 ;; :) echo "Option -$OPTARG requires an argument." >&2 exit 1 ;; esac done ARGS='--separate --force --no-headings --no-toc' echo Cleaning pylatex and examples rm -rf source/pylatex/* rm -rf source/examples/* rm -rf source/_static/examples/* sphinx-apidoc -o source/pylatex/ ../pylatex/ $ARGS echo Removing file source/pylatex/pylatex.rst rm source/pylatex/pylatex.rst echo Removing file source/pylatex/pylatex.base_classes.rst rm source/pylatex/pylatex.base_classes.rst for f in ../examples/*.py; do name=`echo $f | cut -d'/' -f3 | cut -d'.' -f1` rst=source/examples/${name}.rst $python gen_example_title.py "$name" > $rst echo Creating file ${rst} echo .. automodule:: examples.$name >> $rst echo >> $rst echo The code >> $rst echo -------- >> $rst echo ".. literalinclude:: /../$f" >> $rst echo " :start-after: begin-doc-include" >> $rst echo >> $rst echo The generated files >> $rst echo ------------------- >> $rst # Compiling examples to png cd source/_static/examples $python ../../../$f > /dev/null rst=../../../$rst for pdf in ${name}*.pdf; do convert "$pdf" -colorspace RGB "${pdf}.png" echo ".. literalinclude:: /_static/examples/${pdf%.pdf}.tex" >> $rst echo " :language: latex" >> $rst echo " :linenos:" >> $rst echo " :caption: ${pdf%.pdf}.tex" >> $rst echo >> $rst echo "$pdf" >> $rst echo >> $rst for figure in ${pdf}*.png; do echo ".. figure:: /_static/examples/${figure}" >> $rst done done rm -f *.pdf *.aux *.log *.fls *.fdb_latexmk cd ../../.. done PyLaTeX-1.4.2/docs/create_gh-pages_dir.sh000077500000000000000000000002671451425216500201610ustar00rootroot00000000000000#!/bin/bash git clone --branch=gh-pages `git config --get remote.origin.url` gh-pages rm -rf build/html ln -sf ../gh-pages/latest build/html cd gh-pages/ git submodule update --init PyLaTeX-1.4.2/docs/gen_example_title.py000066400000000000000000000003011451425216500177720ustar00rootroot00000000000000import sys title = sys.argv[1] if title.endswith("_ex"): title = title[:-3] title = title.replace("_", " ") title = title.capitalize() + " example" print(title) print(len(title) * "=") PyLaTeX-1.4.2/docs/source/000077500000000000000000000000001451425216500152415ustar00rootroot00000000000000PyLaTeX-1.4.2/docs/source/_static/000077500000000000000000000000001451425216500166675ustar00rootroot00000000000000PyLaTeX-1.4.2/docs/source/_static/.keep000066400000000000000000000000001451425216500176020ustar00rootroot00000000000000PyLaTeX-1.4.2/docs/source/_static/examples/000077500000000000000000000000001451425216500205055ustar00rootroot00000000000000PyLaTeX-1.4.2/docs/source/_static/examples/.keep000066400000000000000000000000001451425216500214200ustar00rootroot00000000000000PyLaTeX-1.4.2/docs/source/_static/favicons/000077500000000000000000000000001451425216500204775ustar00rootroot00000000000000PyLaTeX-1.4.2/docs/source/_static/favicons/android-chrome-144x144.png000066400000000000000000000050731451425216500250340ustar00rootroot00000000000000PNG  IHDR-dgAMA a cHRMz&u0`:pQ<bKGD̿tIME  -HmH CIDATx{PSWOK!@)b`Q oA UW(neZ_U`cu` L[R"ve!H$w{nB{oroL;;{ &` bb h bb h 74&zB*բ%fQckW?@OVߡPG:zQRPML9\l#02",x.MZˁ°ȅQQAsH_‚~qmP, Mjw `35Ǝ8{sTr>=n-_c xQgckLЏ3`ˌjȏƒ?A[ P-aeknb>7+UKvj<,sSG,o:}\g)!TtҠyo*'>6ՕsvK\;'ߕdg=F~8Dޝdْ,q\-m]A6BJcT. _%j2{/UZ*#VH,.89  m*hL8ǝ4w@C/(=fRv0΃x-'$ŻB ] %lF<+|?"~ǩrm WfIO=64"*̝M+ Bİ3Uy|\b :h\$Jc`1qL:E)Q}9#u ;g$7#mKy9Yڪ+/?nj 7\.e6~&ZqXsHP}GD)ēg5"!aQ#YU>P:"j /   XYYkm~;+ C-ow[" ŏ&ZڻzĽ}}W,R/}+LS\MEbhIP4u*{UTW '.ױẁy5$Sb aYSʕL=h~ .֠AHYxLwTL7ѐ7B "=0=1b ? S H iL k:mQp.͘)3lhlQD;駉5? $ e c=q@1@ 1@ 4%@y/ 0@H&K 2~Jn%ߓ!A%ؐw=Sl_63m@q7%lٺ-qmݒicܫT> aLP`SlL:lKJa na 7۠A%tEXtdate:create2015-11-18T02:12:45+01:00)%tEXtdate:modify2015-11-18T02:12:45+01:00t/IENDB`PyLaTeX-1.4.2/docs/source/_static/favicons/android-chrome-192x192.png000066400000000000000000000065641451425216500250500ustar00rootroot00000000000000PNG  IHDRw3gAMA a cHRMz&u0`:pQ<bKGD̿tIME  -HmH |IDATxyXGk[@K[x`P9Մxوo/Ȁ]Q7㵉W^FŌFA"prLo73]3ttO}~^z @v@ov@ov@ov@ov@ov@ov:R/W( BNR\P)*=/g +#M2s߲8;6tƼ7DIؾmk$~Ku O7sxIVTQdތIcbr":6Vh٤hkR1@8:nS-.hkP/>>62|fFVDKpX6eHkKJ$#O pz%vd~3m͒Y>-ttD݆N[8aPD!ȖxSSD Q#\#lD.NwU5W?j8W(5S#IJ1W)%V"@;M- QxK%<,*HG弗, N RN[:D+h7KR "p`p1RM+Icy:V!6%ALB5-*ҙTNR+oC\pv7(T!u܍VgXȿAfkR E(hpx7"͔Ia <]n z#`4I:4xR8'yX~-D$Z)[^yN7)D8;@H8#b k;5¥twL9@0UW:W"`~ Ї yp:ۢ/ST߀AX gakNT> `pIePiDuE[&y@rNvhDu xr'ldx=fڨQcQZ DhYk!t;YJ㷷&?w+jY rxs!̷'tf#5Kpi[ [4ENcy-ӦjB{2`ځ37=våb8HkTMG>6/@Qd*Alhnn>QTgu4)#UZK[-N`!@YSr{ԡC_FpA~;ߙw"&Ж䤸ޭT +xB3*x]= 0؜c O:bF='DD1lZPk3-}*<,iiN؅ hLK wѰ((CJtշ'/k/iWӡ 3&;Bƨ]7fD$l'ݒp;n_@[`E9ؓ  [Nzs5n+Tȫ` 0ISD-s!%V}mϥD\f jѧڋP C̖r[ S'z/8Wlc4eHm4 ,[r_w_Az0`VtGܒocrw6-EO3@8_!rX4k8bFz==x?1m `IYD@nt/_` ?VAcaFaoS ycEbqFOem*(\Q?&:V~B7M:s P= ďrt%ۋh"w;N8:=iÛ(֔L&V/g` y8B/n9w8lE179T#7Μ[:89"}pV6:!~06{`o5o D.=9,?AOe7¥7hZhյ;I2 gӦk 28(00owWƇʠVm;yx0ý7IiJK}~~Q'wiꡕ6O2rzQLV^^QYYUUUYQQ^.+-yY,!+po{F#I"QF⒲r]YY 1P_S.).LO|-|hgX%܃\)sM|$np74Q6~نmJX=k%Iw&߈_ pU3)   ?|M< 6@<܍5=!G╯k*SDFZ ]uTjʏP5ޢQˮI{P֥ÏX"үwI="qvAiHa^VZ1;Goooooi)/D%tEXtdate:create2015-11-18T02:12:45+01:00)%tEXtdate:modify2015-11-18T02:12:45+01:00t/IENDB`PyLaTeX-1.4.2/docs/source/_static/favicons/android-chrome-36x36.png000066400000000000000000000012701451425216500246670ustar00rootroot00000000000000PNG  IHDR$$kDgAMA a cHRMz&u0`:pQ<bKGD̿tIME  -HmHIDAT8cO`UDnݻ{֭_a>~o>}x;X͝mE@jP2jpsuw$s\Y ganz(b e*_SM10谘?ΫE}+b 9Z\?5R<;H3ޠTq׷(y~;*zۻ/ZYQyg$ߚ@jW>6I(" T25^9 聴'HKSU=>9H!YG:7s}oʖcrs1t+Smwovٽ PѯtO|?e@E޽@MOU4/4\%覲Y,$2_j$蓩g)w*&!fOߟ߄܄POf,%tEXtdate:create2015-11-18T02:12:45+01:00)%tEXtdate:modify2015-11-18T02:12:45+01:00t/IENDB`PyLaTeX-1.4.2/docs/source/_static/favicons/android-chrome-48x48.png000066400000000000000000000016021451425216500246740ustar00rootroot00000000000000PNG  IHDR00ri[gAMA a cHRMz&u0`:pQ<bKGD̿tIME  -HmHIDATHcO"`0?s`?a iK#4,JKMKI\ĴP ?/NdZ{=<8:EAh .s|Ba)0㌢$WMc fTƼC2 &/>2<nǡa>$++0T)q\2@),B;O.SC =d l7=K3S 1uVQeL Zw?lLw )T EEi1_+?|3mSdA7Ϟ7w,pk!(mԱ \[6~gIن?)91Hs'kf4Ke)xN[wݷg; /\p|D%tEXtdate:create2015-11-18T02:12:45+01:00)%tEXtdate:modify2015-11-18T02:12:45+01:00t/IENDB`PyLaTeX-1.4.2/docs/source/_static/favicons/android-chrome-72x72.png000066400000000000000000000024261451425216500246730ustar00rootroot00000000000000PNG  IHDRHHpgAMA a cHRMz&u0`:pQ<bKGD̿tIME  -HmHIDATX{LSW/t3ȣC).nئ [YX2c /a!B S3@FDPjcJ_촴.sC !?=J-k(m٣ ӯRjw,M4ը{\ p34"ch#e*1I|LDuq8xy dhi1 S~Fc4J]><2?c.G[Tr)p̑to }?P#e kNH|?E@]KdxFQ #G̈́4xuL./G&| E6Q ڸM[FxAWV>DJcjLyqJ:A=ȵ/jA^sL61G?'-c4['&U9ؘͯn:i7rA{Ld?qA_Y{6.4uXze6p\䛸ذD"ђR\kO YšIt HGn37jg h 3bl>91Ncfe"Gҁ/"ucR/JgږQ\h.>Z\ 'd}0;gwۺ/-7%~#;ݓwnKqy^w g*"eĶ0\~'Ddaƅb22 3rEΔ z%ޞ&s{?/ .7M ,˟2 F&*.+/+9(+lte[5 XrqҲ'fC%e奇d%cK':;t\ M֐=>C\ǁ<-YE|]<޴Gbvq|g_(]/ݼ!oU5'O:Q]U0z^5(F%tEXtdate:create2015-11-18T02:12:45+01:00)%tEXtdate:modify2015-11-18T02:12:45+01:00t/IENDB`PyLaTeX-1.4.2/docs/source/_static/favicons/android-chrome-96x96.png000066400000000000000000000033351451425216500247070ustar00rootroot00000000000000PNG  IHDR``(gAMA a cHRMz&u0`:pQ<bKGD̿tIME  -HmHIDATh{PTUϲ*2 "j*b D@)_ch:M> PG|,R,")BE Ceߏpw b8Msw9y}"bq8pg P?;[EӬX^碂S'IN>[e/E"ʨ4:X,9y{T,vvBN\;淢+`NxmB69tx29G际IJ/h\w49:aϷ:{YY2Cև5Qm3}X& }Wbr}˹这&QV%# 'JJbaR At5aERVS=a$t-+&8'n bh`*u/w&>`P(Z`uZ:EFjF(mCn0\13nĄ b"X}6%:v^|b{;(`ݺ67q{Dp;?(T*H‚FfX 5NH ~#m-6ykKS}5 gRTeULUjl.ۆ=!niln]L#58U..gЂnIdD="c0KQw$pcN-KL_ЛvxrIx*!b1zC1dLD  Xsڭ;G{ztSyxe{s؟oO6 ; `e%tEXtdate:create2015-11-18T02:12:45+01:00)%tEXtdate:modify2015-11-18T02:12:45+01:00t/IENDB`PyLaTeX-1.4.2/docs/source/_static/favicons/apple-touch-icon-114x114.png000066400000000000000000000036741451425216500253070ustar00rootroot00000000000000PNG  IHDRrrڡgAMA a cHRMz&u0`:pQ<bKGD̿tIME  *IDAThkPW&$< Ŷ∵:hQujbeQjUyZ[;m}*(*&۽7f ==={}.GH#y$9E2ר fLQ&L$JRD,^ 28 mFV$&C3hy|eLx[J<~֭ 1˃\ l[+~sWnj4euk:.p&uvO FaWjm$V~hJ*'wH*0 REՖsK5i\Zh H <N5BHD6ᄻ6lX"Ϣ I=PA쮶e 'TG+ C_%CHAOhiY2TU!"lGG$r6*NKfG$t%}{޾1bb?>% nB1NH(ՋD:dZVT[:.H,\cU5? "Ȍ"=NlSRSo `nZ9meBvmlUlRjZƆR')WF)i Y*x0nE\F,l^}lTw{ +Bx`zP? 9?Qll5-19!5tApV3F9}qk,rWك$n}^:$W?2^xwTTA%(t`a⦿pkv5N"lrVЬU>@ |lzyX-լj֫Wl"ɇ!snكQb?DNk2:Zd̿@6su[-ԗ- ,\onn-reG;aQZ6~w*G_-};BQEPzԺ@/Fm, oL8>?s[,tnțe'!'.ﳶenW`^ Q4C8lyi}t]%rA`V"ly<MG/Ms1["7R!j[*V$(Sqe(ՠGtpSJFAiq*y(֬~퐿T&&+k#DU .bC ?$m9^83\rRD^d>UW|ӧHkh}m/OE©M\GoC$n[)'^ \TE Jͯv}5D^9#oH#yYB{WCd{#,C֮KSʦEZF  P-{{+R(QKTGU z{=(^fQ0v{Q%{|8X2حЦ )'`Ll `ow\3Ja_8M1̸a!#L{Y87|E[8VA-j̔܄m*INF~ Yq7B[#`Ϝusʠgp%DfdWJzm8s9O#7ȹ p+mpW9q1 Un źBǁZj>ŕ>Ű:olnFd·9$ Z IOvA_ F.Zq}qZGxa zynHMT@Bl,,n?=p$j^t0.6;=rr(ryԜ%ڝ"UdJ:@Yg=jR釢yԚuƙgޚR`6vm3&~Yt&sp;|Edi2{ψހj2Vv߸ʛEh\7jӬڴ#&&W(ca_"'YSH]dLC>+Vk6]5O|3X- #Z/Vh;R!Y"r\Ǚք Ɣ* Wt=Ρ/W^Awn sSoIaVK&:gڇ=~2 .OI_쨫$/NB7{ =n-_c xQgckLЏ3`ˌjȏƒ?A[ P-aeknb>7+UKvj<,sSG,o:}\g)!TtҠyo*'>6ՕsvK\;'ߕdg=F~8Dޝdْ,q\-m]A6BJcT. _%j2{/UZ*#VH,.89  m*hL8ǝ4w@C/(=fRv0΃x-'$ŻB ] %lF<+|?"~ǩrm WfIO=64"*̝M+ Bİ3Uy|\b :h\$Jc`1qL:E)Q}9#u ;g$7#mKy9Yڪ+/?nj 7\.e6~&ZqXsHP}GD)ēg5"!aQ#YU>P:"j /   XYYkm~;+ C-ow[" ŏ&ZڻzĽ}}W,R/}+LS\MEbhIP4u*{UTW '.ױẁy5$Sb aYSʕL=h~ .֠AHYxLwTL7ѐ7B "=0=1b ? S H iL k:mQp.͘)3lhlQD;駉5? $ e c=q@1@ 1@ 4%@y/ 0@H&K 2~Jn%ߓ!A%ؐw=Sl_63m@q7%lٺ-qmݒicܫT> aLP`SlL:lKJa na 7۠A%tEXtdate:create2015-11-18T02:12:42+01:00y%tEXtdate:modify2015-11-18T02:12:42+01:00=IENDB`PyLaTeX-1.4.2/docs/source/_static/favicons/apple-touch-icon-152x152.png000066400000000000000000000053641451425216500253110ustar00rootroot00000000000000PNG  IHDR=gAMA a cHRMz&u0`:pQ<bKGD̿tIME  * IDATxyXT Ȱoð F4E BbC*TFlRSjm(~Ijj6K0ANdeS ( :3yo͛1NsϹw=`f*I0` ƀ1` ƀ1`) ƀ0` fn€1` ƀ1` GB eqX"gL*M,bZ&&c22S eCWeR˘+3snCd$<[4Eiq9^ZafJN[1Dv ikF}eC8}@6 I/, WYZZ%9!i['զ%/YwbݰCbA2_!JMTlVs3&OV_}B锅x6\Tw EJP[?T=ULm#|]j^ȬОItf{@k 80*R 8' Cr !؃vHTMQ=,qX['Kx}Ժ}I ~u?DCLTD%>ZetC rB ۅ3Is1/{4͢64 >VHgf1Ge-ZHM ۄSl':F/zVIC`ԾUQvj/C l?y1YneW&M n&{-2bu+@`8{_:gہ<Ĩ%\Lf׽shQ;."3JB=: =7+%Obbawi=tJaoE_>G .4 nCx'`lxGa?,FO ]kM.da\vmk@O+scmTͦ ~Pbs)*tL!-1 Ls|. . ;29} e~ە2;vIŤ^DŽ"y 4ռ.0vh<U<",R'T9K&*%XW@ ك0כjI{jS? (Éi[#ʿ$oy˚Ku0Auϡ`=)`k?$lX}p=Xl>R[ CSAt|*g&ȼ+{QE a"U$\6X fę,~EdN P0EnGX 8 :Y+@0I>bu*iY!Ț~6\44vXs@ n0ꊄIS#?8Pp#V`? hsol`5QW3\JE aJM\5IggAQ9LmOl@>/-a(o%*[B@(zɗbn$xC')GwU?9T{$B[ܱ"Zt˵\@2nT|'/*H (. :Iςz[?N}3WfѪXCZGz ٶmj#_>KeWd*Qa6 xɱ~N5ycc0M/nAOi z$ĺ7w~r`EVY7{e#0 pAV+|bk%lKOfػyz>>>>|>6B L}O%\FJ8{'WGEN#)ڻkxBBg+ljnimkoookmiinUڇ-ԇşw@ l;}76s{\V[KSͺ z hR7![Xu%%QJ/7@ם/G&ۆv^4 RmLE{"!^E?v0 b{`{_&f(v,10L{$I+I Ī90^1aPwpKMe,XUed`X̩h1y F1=Xc0c0܆`f61"# L&J¾Wt`^EP,JMVLM uP),OH/d`;>MHLIMKZ$j?M&KOT&?pRD<916lI037a0܄/c.X<%tEXtdate:create2015-11-18T02:12:42+01:00y%tEXtdate:modify2015-11-18T02:12:42+01:00=IENDB`PyLaTeX-1.4.2/docs/source/_static/favicons/apple-touch-icon-180x180.png000066400000000000000000000063261451425216500253120ustar00rootroot00000000000000PNG  IHDRYgAMA a cHRMz&u0`:pQ<bKGD̿tIME  * IDATxy\W_W8"EA+zUVEZ[*Ѫt=WŊU@Ż xr%@ II2D~e~͛wo=ZA#h4AA hu@4F: A#hFZA#h"2y"a5̜7%e"q5.UbHTQZRY(W[XR+UUJ79/3^'Ztu4>hoLa3t1{(qc}}5|`wsq6r߱|GyܿO/FE=j1|\e*F7{ԇpà-U$柿V?v$s*^D9oz*/S}+g@öb&Xr?rXҨ}V#F:ǁ}FA/Qψ:RG0 Z^tuq5@hTV⅃R]B)N T|PgzZl+D\:Ǎo5cBw&u(CկRV.Xj&ԙԎf>vqfpaKea|`tP9;,2Tth(Ws V]ƈ;ٱe kv50~#;{Uf3Ŕ3j 8Vmx4!E=CmAc&;~C`vJeRj.q>P!@@)4 O.oQ9l,YNyd3c,৶PVŐK'œ$GYӨ!;X8 T{RO*ʧKvXi`=DWOF`t8x1z.=:M>O.i2(D-cWb9j2cؿ`[XUX@׮Փ0x63VOBSq-^2k%7R%=@SaưDEF ]}/Dp1ڈSHkd'4A,+ߌҖ:od(f})qC`Xzh J8FVĸ\&* 3h,CUK0J3Ci.Y*jY0)6*ΆlK3{N_Nl%BOޞ=,t}m4⃲ZD3% M-n]}|Y@51wYs,ZtٲK> Z0ƔC^f.yyN2ӀAK.^Ʉў&ſ`1F闛z䫧&phM H&#m-xy$:B V/# ᫀԽOn(I0~{/)!ʱ^Rc4Naڦ1 E@<{(@WSk(& ALO*]Z஬ &swhIZnZ 󔼊°V=d|-YqInW(Ygݔ+|%{vbqF X8yfD^W0\J4gȭeEeg .>M,T'пpg%ϷX/1Y9'O-`&Y@l[;̏]v9vN#dhToFB)t:0H^\WGtDà .Kr_֝WjCC sU#_<7F\x(`)z1~H)ȅ[}_|uk,7SaLpPB?% ?-U@O0 fsڌ6 bC`[~c}+_Ju;nb|*$4fl;KV+>%<4QwO9Ʀ&AÜ&P1*Nd3DN-U[we^;3._便k.YoT2.ɸ O7} ʗe umuEΚ<~7`G{L"Q/32_N0Ֆq3GG]p/9%ajZԇ)oݸxx.}RrxD՛q&&.[=޽stleQg? OMIN{b}I2$qiv|6쑬S pN~3ۦf|26utxu)bUX< r.jY3еq$sUw]Aci9e#a~Bc 5skYSo{">u`bCc73q#1k q%^ kmا}/ȳ`p{;'OjY"2%?O:'{{G!l;mBc׉qe(Ml/:FA#h4F={:K5;~|_ 'mNdG;nް>دn} Q놀Vt_q: -N:s6 Ϟ<}2pXt'FZA#h4AA'D%tEXtdate:create2015-11-18T02:12:42+01:00y%tEXtdate:modify2015-11-18T02:12:42+01:00=IENDB`PyLaTeX-1.4.2/docs/source/_static/favicons/apple-touch-icon-57x57.png000066400000000000000000000020361451425216500251520ustar00rootroot00000000000000PNG  IHDR99sYgAMA a cHRMz&u0`:pQ<bKGD̿tIME  *&IDATHYLA9cEPBO<(J-"@1AE!jDG0AA_b-M ĀA(v-;n[Efߙ/gv7!!]NߵttfFۤ}=2 !MG6,)PW׭\>?Lݼ|ڞ1|n Ʃ_ A.+RV<:/sZu@bwp7$M~)CWѲB&Z#`H d ~j$'!|LjQF(+k7[8T7}Y {^ |}u$O[J0$Ⱥ̧t:F!dfb mȽyyyټUJ`%&ǵǜe&(j5$Z,&Dh&~nKN <.Lgc8#S Wؐ'?)!C@Qw$>* RO7@J%Ta_b[i\O[,CiHk qƦD2_|, S"+-͐]sl2nՏE>@D;]`KNwÅ+x[?9Pm)Ks1,.ei#8c1L^[`PYZ HBƥ9TJR*4 p<%lݷ'A` Y]gHmKE@됨"'%G r/)ygЬU X}O&(;dnT,׭}C/iygS%tEXtdate:create2015-11-18T02:12:42+01:00y%tEXtdate:modify2015-11-18T02:12:42+01:00=IENDB`PyLaTeX-1.4.2/docs/source/_static/favicons/apple-touch-icon-60x60.png000066400000000000000000000020761451425216500251420ustar00rootroot00000000000000PNG  IHDR<<gAMA a cHRMz&u0`:pQ<bKGD̿tIME  *FIDATHkHSaw941RDM(S^2t`SQQ7RL% X$*"~L)]0iy vuvmͭ?{9l‡|> ^ҧU Mƚir[ h++2tᤤp}nj`vr65-~UQ8ضl ]D( |l5Rd&دZgmT'oں @H%y݂ib iQ/phPddW[׌vaKl9~gav jcQ*@:)kFp5xL|#PF";KRע5X_,G ;1҃W\9hKI=t5;;;+?oh)pY1#Qh-/AS{NaPUg ;1_0OE;.)?oJϬ'Nd,>,# l(-sL$E yc3 g}(+?A@1-A,.TțJ+ @"5P/@+R!C]{]KU4> }V3#BrN[oMiqխ#ʽ.)>){$'%Z1Cf7]`1M ?dFE4+kb1E+xa&y+½%tEXtdate:create2015-11-18T02:12:42+01:00y%tEXtdate:modify2015-11-18T02:12:42+01:00=IENDB`PyLaTeX-1.4.2/docs/source/_static/favicons/apple-touch-icon-72x72.png000066400000000000000000000024261451425216500251470ustar00rootroot00000000000000PNG  IHDRHHpgAMA a cHRMz&u0`:pQ<bKGD̿tIME  *IDATX{LSW/t3ȣC).nئ [YX2c /a!B S3@FDPjcJ_촴.sC !?=J-k(m٣ ӯRjw,M4ը{\ p34"ch#e*1I|LDuq8xy dhi1 S~Fc4J]><2?c.G[Tr)p̑to }?P#e kNH|?E@]KdxFQ #G̈́4xuL./G&| E6Q ڸM[FxAWV>DJcjLyqJ:A=ȵ/jA^sL61G?'-c4['&U9ؘͯn:i7rA{Ld?qA_Y{6.4uXze6p\䛸ذD"ђR\kO YšIt HGn37jg h 3bl>91Ncfe"Gҁ/"ucR/JgږQ\h.>Z\ 'd}0;gwۺ/-7%~#;ݓwnKqy^w g*"eĶ0\~'Ddaƅb22 3rEΔ z%ޞ&s{?/ .7M ,˟2 F&*.+/+9(+lte[5 XrqҲ'fC%e奇d%cK':;t\ M֐=>C\ǁ<-YE|]<޴Gbvq|g_(]/ݼ!oU5'O:Q]U0z^5(F%tEXtdate:create2015-11-18T02:12:42+01:00y%tEXtdate:modify2015-11-18T02:12:42+01:00=IENDB`PyLaTeX-1.4.2/docs/source/_static/favicons/apple-touch-icon-76x76.png000066400000000000000000000025641451425216500251620ustar00rootroot00000000000000PNG  IHDRLLgAMA a cHRMz&u0`:pQ<bKGD̿tIME  *|IDATXPe 3T(0Iו֕eZ)w)W몫L:Eî.nE^ZyBhIN!mn߷l}{}>i",0solt[ll6eh! F/Jҥ41'xӗ?B(XQJ1W }w V(&+{'}|@I}YiLvgzemb̽iSFq ?dw&f_fq4nc՚򍶧UwVxFjP8|6oD} xGzo`ף"!oY?߯˷nLtLɆfgEopn,M#V|RNJBcFLZ D|JBw+4w)?.^$= 8/8};Kuuv«i]3emiТM1зiYN ݄9|"耵aBB¨H̆cO#[kCO Fq"^?+7m#PaDY}ãQ>]8nfh -s岜|(qkiƅE}rXq FKQp- `}czꪞ9so׽Wߪuw JF1LNA[UbRncgIP al]۔?Z*xܶ]l]BsvDe:ɧN<ɜ%K MRRTmg`_-ð*Z$5COf Hr;m[GZM42%n!HY n[a飄7 ܶ h9un[ӡTֵѳܶ3 }v 0m+:+ ;ǔR=n[q&t|rۄ΁'C8m:m#:&t|aOhvOhvOhvOhvOhvOhvOhvOhvOhvOhvOhvOh(414g]BRJQ YR˿( ) Jl FQi$ @вG%D!J쟡ۙwY NO0T+GJ ! %}ZEnD!B#5\-qJ Oa|bDr 1l,^ gnM2kRRvPP@SGDw(#si:9VŌr3sUu4%Buԧ6*4)&H FIQvGMT7xZV=ʩ:B&vYWbucQdЍ[.67®ۖq 1^Hyme'_ 1d0(#@'hE|16&o7Q~fM3D^DeFLbQhOU[2)J퓱yΤTȬB'}b o=)Qt<'Rb͕uc2UAIhNL?gq33U)V%L6w/5[54LO,G{ BQ$Niɺ7;p #Ti3}ʨa12¥GH6)0pjtUm*-"0&C\u6߅x;gUJ3K0_f|E&t)R09IK~33M5 a 05ׅy\ލ}rV`kK񤉜ϱ 2E*e簾*%Jk skk(t7JXY&5Slø-u. ]{SN i>^7l>e9P@(taa+5+uDUJ)̥YЊ6Bu%'Pj>jFHnfЧӍeo>fP PBcޯqxM0 4ϙGp9RBGtO%7 S5!G5386 L(tN{<|NO$}[+@9sMu(>d';t\0 0O3 Wsm5(~Tqnj)VSme&U^f8Ǯ-Ы4üghƹ͇=lϾCfjJ2*+3&wna~ulqV{ıZP4Uma:c[ksIYئPks<3OL!&Eh@kuEFNwWqz'^ B0ED q+7M&laYIٺ3<HhV8g-+xM#"r1Wyp[kARNX͏I>sWW͌R)ߴI5 B?3FT~9Ϡbm7p e BOQ%m2a#?gXխ[I'y'9\[=sm{R~܍\cs|dtDem|]tj'@nwXnK]Zx 0S~yz=ss#gB7h0Ĉ%[ĈŗE6QO٤u4&A6@-4,jnxLgj549{;)"G>|ė,"i}}E!A(nmYLbp SϭF j` 3ڌS$&Rrq O3e͹TgI^@#vkR(bfRQuXdGΚ =A9LttδG#(M{PLw{1:l.tҾdnccx!JZv5^q?-1||Ћ.v٬0CЎfV? 'KX] UG^$vl鮳D6?51R~Z9\iL1"HPMY^9WcQMV:&HXf_c-+[?t讗 ]-CA.ZVINyJh=3 BQ-Aiʼ_ Uʲ-t􁥜VUJ}bGWiO-S ҹ@ UJ .C ~2wfrfOLYi:DD6њRgpAv{e 뛫i>䅌lt\h?J[|m%HWYfD1]n9ScHٜ.]+_Da#g{u}G\k:$J9HclJY%4Ə,"/!V嬌z1i> V&ɵ{!ֲ0\1Fw"@7f0R Wke[jqFl4!ikACB۽'fX;b"+ˤr)etW D"2T$*J2,7vt͋jgp1iU-#5UU1|2,"p0~qfd--/iLJ;m+-?5| M)A( }7:4xӾoHM0ODhDaq&,\TS(n.:aok 1bĈh\nep*W<\+]LB V|@?Yh 0a*,ex8W5h<("@9S6aa?PLD0住'oT%s:SeW[:8򍼘{<9Z>SN#/V:1Ͱך-x \ @&k|7x o'BÙ:!ϞWEvLxc!F5cy$4\ xV꩚X탼S4^m̧xWL : +9m3{p|,%1 ̐}k< ~ȳB,N!ùny(t)Stggm њkڙIM^ dURNf)/)PɪyT eM vOhvOhvOhvOhvOhvOhvOhh7Bwu S +yTZ27_7&ekKE=YƏqQq2'mf9bDhb/!FݕA2 W>P.-d\Fly64X%l;_D Jv)dɈLO,n]5_H-E0m#:>VmB!)yἇ*Bn ݶp;sN(;ym;:8+ X-azNf;YO8na飄%~ZUUJ5G錌zXyk-m\,S'Pdž7uNVh2apvT*]] Rxz"i q7˕7%tEXtdate:create2015-11-18T02:12:42+01:00y%tEXtdate:modify2015-11-18T02:12:42+01:00=IENDB`PyLaTeX-1.4.2/docs/source/_static/favicons/apple-touch-icon.png000066400000000000000000000063261451425216500243630ustar00rootroot00000000000000PNG  IHDRYgAMA a cHRMz&u0`:pQ<bKGD̿tIME  * IDATxy\W_W8"EA+zUVEZ[*Ѫt=WŊU@Ż xr%@ II2D~e~͛wo=ZA#h4AA hu@4F: A#hFZA#h"2y"a5̜7%e"q5.UbHTQZRY(W[XR+UUJ79/3^'Ztu4>hoLa3t1{(qc}}5|`wsq6r߱|GyܿO/FE=j1|\e*F7{ԇpà-U$柿V?v$s*^D9oz*/S}+g@öb&Xr?rXҨ}V#F:ǁ}FA/Qψ:RG0 Z^tuq5@hTV⅃R]B)N T|PgzZl+D\:Ǎo5cBw&u(CկRV.Xj&ԙԎf>vqfpaKea|`tP9;,2Tth(Ws V]ƈ;ٱe kv50~#;{Uf3Ŕ3j 8Vmx4!E=CmAc&;~C`vJeRj.q>P!@@)4 O.oQ9l,YNyd3c,৶PVŐK'œ$GYӨ!;X8 T{RO*ʧKvXi`=DWOF`t8x1z.=:M>O.i2(D-cWb9j2cؿ`[XUX@׮Փ0x63VOBSq-^2k%7R%=@SaưDEF ]}/Dp1ڈSHkd'4A,+ߌҖ:od(f})qC`Xzh J8FVĸ\&* 3h,CUK0J3Ci.Y*jY0)6*ΆlK3{N_Nl%BOޞ=,t}m4⃲ZD3% M-n]}|Y@51wYs,ZtٲK> Z0ƔC^f.yyN2ӀAK.^Ʉў&ſ`1F闛z䫧&phM H&#m-xy$:B V/# ᫀԽOn(I0~{/)!ʱ^Rc4Naڦ1 E@<{(@WSk(& ALO*]Z஬ &swhIZnZ 󔼊°V=d|-YqInW(Ygݔ+|%{vbqF X8yfD^W0\J4gȭeEeg .>M,T'пpg%ϷX/1Y9'O-`&Y@l[;̏]v9vN#dhToFB)t:0H^\WGtDà .Kr_֝WjCC sU#_<7F\x(`)z1~H)ȅ[}_|uk,7SaLpPB?% ?-U@O0 fsڌ6 bC`[~c}+_Ju;nb|*$4fl;KV+>%<4QwO9Ʀ&AÜ&P1*Nd3DN-U[we^;3._便k.YoT2.ɸ O7} ʗe umuEΚ<~7`G{L"Q/32_N0Ֆq3GG]p/9%ajZԇ)oݸxx.}RrxD՛q&&.[=޽stleQg? OMIN{b}I2$qiv|6쑬S pN~3ۦf|26utxu)bUX< r.jY3еq$sUw]Aci9e#a~Bc 5skYSo{">u`bCc73q#1k q%^ kmا}/ȳ`p{;'OjY"2%?O:'{{G!l;mBc׉qe(Ml/:FA#h4F={:K5;~|_ 'mNdG;nް>دn} Q놀Vt_q: -N:s6 Ϟ<}2pXt'FZA#h4AA'D%tEXtdate:create2015-11-18T02:12:42+01:00y%tEXtdate:modify2015-11-18T02:12:42+01:00=IENDB`PyLaTeX-1.4.2/docs/source/_static/favicons/browserconfig.xml000066400000000000000000000010071451425216500240700ustar00rootroot00000000000000 #da532c PyLaTeX-1.4.2/docs/source/_static/favicons/favicon-16x16.png000066400000000000000000000007601451425216500234200ustar00rootroot00000000000000PNG  IHDR7gAMA a cHRMz&u0`:pQ<bKGD̿ pHYs33tIME  #IDAT(ϵѱ.C߽u ,݉,.;x%X$ I:! nz'|'!nheu%%"Lؓh[rBY5a'J#֜I14l˥ \4(\OSjjV{MJU@Cōc5ea]/:=0h,m(red`l-xӳo9m%tEXtdate:create2015-11-18T02:10:22+01:00\%tEXtdate:modify2015-11-18T02:10:22+01:00IIENDB`PyLaTeX-1.4.2/docs/source/_static/favicons/favicon-194x194.png000066400000000000000000000102271451425216500235750ustar00rootroot00000000000000PNG  IHDRպgAMA a cHRMz&u0`:pQ<bKGD̿ pHYs33tIME  #IDATxyTՕVM4MXl[P\\!Q?qd42! f?\B!&n1hAc :$:  ,mwKm7z꽪uQ}S{=<0 `0 `0 `0 `0 `0 `0 `0 `0 `0 pxChV0!9@!Mufg%LaĈCŇ~*-%lf]&B("Q?~`7[iF;9. v7-rJ,+UQGv^JfvNvinxVZÿ9<;.!>Na -j>^:-[\ʠ:gYjϓ|4-\BWڥw3.ҺYlѩf!= s9Ī:B,lђք4\KZb,cLѥ16sdu}D E>1Hk6Ki%.8޳(!̼dyJ>/- *tF#joIzʱIKAtn%̒5)_&Eautd!%t+a*,.ۖC!e R*ѭ-` -cOۘ$7Fv}VH6+7q2+He=gѬb f@A]x*QmFV =x B1*VU\QY>kf~?oq.嗬r,^q'0s*[&IL:ϻ0Ɋn%4(k54֢mI +v0UyӝEH[>kfAI DX̫.w(>P wڦ^?PI2B;=rA&t+al\ .ZB)3%p^D Rѭ yZq?.zEw]+p\rΗK4{E_ͳ 3ἂ@ %K^%,G}%%1Zs/Q\YTZ ̙bi *ٿq33:YKػL,az=.cK(}Jh3о.X aB|rK xZ F-usU[]jRѡS .i+LfN*`?'p[گ9 K XJ~JPI,g.*~sNi8mkti8RE5u&l45~2y KXY5㯼[jQs/]< yX)# blDrOwxإiS~f2SebKRBJlq"t>>@QDO NUZpr ׳Pe|Bi!/iI9/f{ pbvZā2Bu=GnTLR9PrxD=q{ȭ JZ-NQM:jiE ^y,x:2>I}MD,jz15B3t&F(12ɇWOjYӵYH#]tM!%L! +-5sƶcYI%W5\b3wq3Qnv` ϳd|]HJR.m)4"KǞ+-J>f)RMs!7M:')vM ִOm1QڎFN9{KM-ׯw4W1irc7)K 9DXÜy%V>mѣ!;ՂSy1\l{tٝ+2-TY0IkQˋzFdOl,G5xU_`)bO"g࿈!s}jȒ8N߅5­:,<"6*N&)AFr)O1^1`D6m j&Bp< b'?w*T3#dNf=f)hQ섗:IL,&s/+xh!L((UvJN!*_SG-eo6iQP p=?j>cI땁gF7nxIwVy3G\@tʷE,#  UÊn b\ϩa.6qi 8nB"D$Qb)q~TPI n QQ˅TAWJd_TIUt 9G8N9x,˰ 鰛U 6V8 GU~JUu78 6ܮx/V~ZPԞAO.ݜv\tؽ~ɡ@3+g sîס! _E =N:bt--2ho'f v/0ͩ{hTnTc@\b/84&DS˟&vxxhr122KS5@TlÆwRv(1Go10X* l9. U:Ê^ÎNKB~AP6KJ`%F eQB`P%F eQB`P%F eQB`P%F eQB`P% u4+A >Bn[6C5L c8b:e*6 V@!#@B{@BC@Tf]{oI6}$>of9:nNP͜T{%Yz=R}!($/9Q'ҩA;]eN :pׅBIcVK9_tԜ#:2}g0τ](5[XM¨ڦzA5*1gF2mZP>Um̖0z$9lQFlzRFkU&>3uv+Jb|vب> -nڮɴOMIxmbϸbƊ\mF\o$ܓSIz1jKF]Y4g&0f2]K{=Re\İ`1 \@ g ~ W F%tEXtdate:create2015-11-18T02:10:22+01:00\%tEXtdate:modify2015-11-18T02:10:22+01:00IIENDB`PyLaTeX-1.4.2/docs/source/_static/favicons/favicon-96x96.png000066400000000000000000000040771451425216500234450ustar00rootroot00000000000000PNG  IHDR``HgAMA a cHRMz&u0`:pQ<bKGD̿ pHYs33tIME  #2IDATxilTUi[@6˪" Z֠$HT\$*`DDA!#(AE[ "h TJ-mg^ng3YZMo>{sT\JpSK4֏#H`Œ ~ଲ$^Xanb Z?~<#IJYH, (n<ƄYՍ/AA|~8nhd;S6N1TJH@.cᤀF+߂vР,b>8YpL@L} x .,c^=z% 樴 >s\=6O Ѓrin^! KoFTKBq̢}WӸ/~Kp'2/@RݥlƩr+ R`k(` g'B怴k8)O=٦f'\Ri8#ύT3ٵ,6 X$1%_B-_j0nؕߩQ57?&a;A")w-Y},XӋB^ArJ_/ tKX" HNt-h8GNΧlT&\P p`=@3UlR44Ƈ 9ZCA 2cN}kjow) .(cU'573! 'YرaŊ ;Y8H.,\aRLPL[wrxWf4Xy]1Kfx  +\m|*su`K0WsqWS8q2a $S~3UԪ`pWtb%rWuRVB-Il jdcbKqX"'R+kЊR% Ը A53"ΠO.3ɮBC/-<rrXAS*#!w+upLIj~L pvw9|G%ZkjNf%R-0ڞQ +NYw#33%ċ*NCFrRzFOۆɜU 0,7ycJ?qRV'g_J5DWjq#{a}z6.y#Ԓi#Q!˹6n٪y>mW]e^!_=j1"REr5$3QFП|NPFiana,ya- P{j5I }$S+b"CV\cؙ(< 77"!`x4Ե *Gb"‚q0yS*sʘrC1aFC۱ꏆ!@Ш4i&We,ϨaBeISo\.88e+cP~gO'0J܉4qGW7J,d\lVok] LPy 1`a)\SCI:yLQ=_@'cj ]! `?/&Lr%MT^N2>eslxw4$<;CLdg8CcM=$ReAL;';':Y]%tEXtdate:create2015-11-18T02:10:22+01:00\%tEXtdate:modify2015-11-18T02:10:22+01:00IIENDB`PyLaTeX-1.4.2/docs/source/_static/favicons/favicon.ico000066400000000000000000000353561451425216500226340ustar00rootroot00000000000000 h6  00 %F(  !% d Owt!L.-<35EN: 1Q>;c *. *L>d17U OEO  t !SZ% N ]%H+rFB@8x*h0( @ 7658(Tq9)8m ,a@'o|PaoxdEm6 "F^>VZCo*8oBXe4cJ`N|8|"KS;?91O<@_lFh  ?4s[P[V~P2 apZ^|U?yp Hu+443444,#0+++!-,2ߟ߿?(0` $%LFBACHH;w | r s s r zI{uD9!/K   # d#` 0 0 u8 Vc'$Z!r Y fHY:E}r?Y<d'F5~Y]waOL?YdhdabfePmcdkPxnBY+ yY~.@Y"@qmFYo2KY! NYjGkGlYmA0 VY 1$Z3SiF/<R456CwP:kkDb; ZXT$`|?~?~?||?ø~?>?~~~?<PyLaTeX-1.4.2/docs/source/_static/favicons/manifest.json000066400000000000000000000016771451425216500232130ustar00rootroot00000000000000{ "name": "PyLaTex", "icons": [ { "src": "\/PyLaTeX\/current\/_images\/favicons\/android-chrome-36x36.png", "sizes": "36x36", "type": "image\/png", "density": "0.75" }, { "src": "\/PyLaTeX\/current\/_images\/favicons\/android-chrome-48x48.png", "sizes": "48x48", "type": "image\/png", "density": "1.0" }, { "src": "\/PyLaTeX\/current\/_images\/favicons\/android-chrome-72x72.png", "sizes": "72x72", "type": "image\/png", "density": "1.5" }, { "src": "\/PyLaTeX\/current\/_images\/favicons\/android-chrome-96x96.png", "sizes": "96x96", "type": "image\/png", "density": "2.0" }, { "src": "\/PyLaTeX\/current\/_images\/favicons\/android-chrome-144x144.png", "sizes": "144x144", "type": "image\/png", "density": "3.0" }, { "src": "\/PyLaTeX\/current\/_images\/favicons\/android-chrome-192x192.png", "sizes": "192x192", "type": "image\/png", "density": "4.0" } ] } PyLaTeX-1.4.2/docs/source/_static/favicons/mstile-144x144.png000066400000000000000000000062111451425216500234310ustar00rootroot00000000000000PNG  IHDRMO*3gAMA a cHRMz&u0`:pQ<bKGD̿tIME  +} IDATxyTǿo\A6 *!*K(*Yȑ# XMtH(_rEժ"J~_PS@F*@!5i6s}Dr85U!}.OPNl}I!"GWPAU[HaO5ȃ>[-Z;sRѮc޲$i~ ѐ%KS5Ku˧"WXjPZ |*Q( 9Z䄳Շ&jOTqGNZ>ԂNClM4At! }oըPg:Q]2I-5fwg$w:fP˒C(W \HqUR|.EiKq 0!D1 Lr}x"^b9{**A/Tj4oJ-GʝG7eIGˠ $1\^<3w> ]vaa͢VK Xƨ$ojN ">& yFQ#RZԪp<ȩ#-)էj*" +"'Vh(rΑ_g-%1C< q&-D[L-5TSEUTSM 5lf+l'e}͙4vc PE55Բi# 7P3|M[YAU>)](5C}jTϡjVhVHuBܫ:"_gz_'˧jLiWH@ CXaj/hE2]0e% @W}wf3Uc ˀ%wp-!ޅ<]vDQx172!vS C֧PC ^ ĄejR]7Pʼniه K^&!aV+Wf$ے &o.eIفR ȊL;F`>{?l`|/HbJB=L#6.L ldc4c`ofm %`Tr$~n!B.f˒qegko`!&jK9߰oPŸ Kst2#Wzajz=UNNYef0}}%oÌC Dh0EY,Nx}S2b^2, qK^vL?:ðϝ-J,2f_3O8ί YX?rT X%SmZQ wE Wvy.e7 ú ItLtNΔzTSmx3hkլ*6FQl%mw0+ݔ1}SBOIW~fT6XM +HmYώmnԾڒt9st_03gf~sVHuiXEQ+j?%+U1漍} *>jHSR"**:5.6)gv3:ŕPsw;:H/j?)A4\űmָ)ժa8*W_w^C<-:LL#| 9y,j{G^U3W$h{.DĐ3F݃ER=dHt{PӉzƞ 6sr웶5әL@0D$яkR-Nw"p3UrCO[+%FI,zq09a#f#﮴Jx*(gN&,3rp!/csusDr@y;;3V4X`J( V4X`J( V4X`JCo.]{NK'_?zN wO#~8-!ZS)+۬趱:_*֮١zUhYgr}~9 '=mo;(I #N,iXPAEbIr8,ԉ՚ X,bX,bXEijƵ%tEXtdate:create2015-11-18T02:12:43+01:00%tEXtdate:modify2015-11-18T02:12:43+01:006IENDB`PyLaTeX-1.4.2/docs/source/_static/favicons/mstile-150x150.png000066400000000000000000000062251451425216500234300ustar00rootroot00000000000000PNG  IHDRҸ1.gAMA a cHRMz&u0`:pQ<bKGD̿tIME  ,?] IDATxi\Ue$IH4$$T5",%((B$q) ,-q5@VIXU@ES.!$A lL3C&=ggnfKw==}{i0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333XwP[rbQLFȑ@!`wE$H /G*)7dȒ $H$E\:T \@$V<̓AG>;KY2$I1<ȹ~r=]dȑG)$E4iYwdx$G<ڧYv-I͌# \\sWnћie 2?}+Ȓa}$ &)F=<%+9Zx91_\QG͑Jy#'r*JgX]<ŋk/3y; AoK/ݢ舸aԚwێplvl:QyqXDrCA;RW6[ͮE/оq`%bu"x{UV.cm .(kQ/s!5Dryg}Erm0":9eV hy}%9v#]M,*n:tbq h(QQ@e ڵ(Wx/+ףQtda2:zfn|NGcקQ+/ygע,䶸?t%9^n2z˂|Gc@i,ŕѮ?t"9GU7v-18F fjM¶LТ5+1-mq~P2~A$CoVޡg qk"]|Тvw ̡Ci_/Fk묰Hȟ>B6Ho9kԂ'qU_WjV8"A6d'Z, j'KK[3Y-qwP7E+Y͆^!"M+m[XmzUt$7?虸~}1rvֱ,g)+k6JK ԗ pO;֯@N2kt\`3!s5i@%TrUIr\{km}ZnTsܭψx}¼hKrj܍p?(sNEy򈈈 Ҥi[%'NȐ#Gu?=1eaz7  pl1|[KqJC[QZ TPBqcI=C\jݒuA1h ܈rP^'^L(9)&?9b c9FEҋyp)qwKTZu.ӛ2FJ֢Cwk(=Z.V6MҒ 21k ՞hn*Iy ='IZ}2E{ITf!aMI[:D+u7he.P^tTْI]^HtZ}0jJLa-cjzu*\d5ԷFjJM:(-Yҽ[fD=$wO%|w-FHulɩVe3S2K~˧tUwk #+3bU1QIF*x8h}NYUj7J "@cN%v7}=YAid*jO#(s@:Rթ,iՒ넪 n>wߍxnWUb~[?ߵ]n{'Zu:$mv7Mղ ߇wE? ՝j^ J- YezH[nTZq ZV3EJ(4N BLMPJ( K/t=$J(њ# 6tJjեڪPF7p)dMr:f&1ҿb+1;x_NmJnĹg:C;QG>%C]:@SIG5*N I BWjԢ?8I}f4|$57ϡSޮZu<$H3i R9+gt7Rݞ3}%AoC^8rIP㇍ݟۯ퓃e j/~]c=qm~&XtAM+z=B888*Y}%l[ZԾ1>߫%\yW0# ,=ݑJCGj]RV+=۴t*%#A@+4f6GJ]6Iv-b=mmjNOjNtfcexJ:Q۴N+\k_>ϔ=M?Mtex.wZPF=D@tWoR<P7o3xRzwrS;m]5խq}.hOmheZBi<wLsyqBs5M]TP^yY"EJ(jW~=W>eSNY ZN"?ePKRTtN-nK=F鵚)J]I*hvҋU[At*Fk:K[4?^W>鮸@MBב}o1FJQ~'x }yQWqz#|[Yr_{ I ?kO2׶xBgx[ZSX~)xuPve}>̫lo+l|*L^=fjP}.3Ao;Y[ I;9Г}79K<5OY> J.e׏]5Gi &aۿ* w94yݡS. 5F9]wVؒlWuARL.I+(gk1ŏپi6 "?q[A5aJ{@Kʞ w]W:YMۻ|zMqS>=v_؛k1¿(;qVң};v_nۛ:+gD~uEm=/?61Qq(ﯺTA܂ztđN8}'I'ӅtrKNytk˞HPƷxw8dǟoFkf.FRVҒۘ:'8zt.2}$"y.4d}N zbqJi&UXQef(5v`#3u{q`䖪|;}E.;_9#8 ]pLޚ{=APſ`UV9YVA{ގPH)բQzj+N]Xzw軄WաZRN-HWYeSN "iVkUUVy)JEmjV=觃}~K.xgOC?l^[`8&H./;6y e 91ߟ.oqd-H<<Q^zWȸ`H'or/CGyq\^ UŲ9??a !Vut?WJ.eQYK{ww0\0@CH.)H.)H.)H.)H.)H.SP7 *K)]zP? *;FK'iV.L)uXӛOhP*Gk\ܽ0dxWxw;xt}(nn?才GDZ4^QP֫GY{"%RZ#C"9dW!H-ҾPR)E^`0r5%%tEXtdate:create2015-11-18T02:12:44+01:00^'%tEXtdate:modify2015-11-18T02:12:44+01:00kIENDB`PyLaTeX-1.4.2/docs/source/_static/favicons/mstile-310x310.png000066400000000000000000000153301451425216500234210ustar00rootroot00000000000000PNG  IHDR..2CKTgAMA a cHRMz&u0`:pQ<bKGD̿tIME  ,?]IDATxy\eVUw Ya}Et9 #:388GeQ ( ʢ( (M"Zo*ԭ[SMnw+Lt#ֶgWV9ih}PHźOr# (J)ԪV8bQVzU^9啗eY*b)VfsYJ-7d.tSJ+ - Z8著S*Uac`H ogV5I@VEKQVb8\beQˠm"!)IFdJdRԴRQ5|ұEpCO6hf:Eto_uI7&6KzB {ڪXPOxQZJi(LD7k?ZmxݳZeV',T)?cԧކ@n USiO9eZzJwTreK&MSҶV"N]{lC>M+@iզL[k7=i1C4^?z=V)wxo_rwtށ[A_]2W}KşY+k>xM^c'Ǒ_u)}וH7G}һgΕW]S׬;j=vWɏ*Ɣ?B_ٞt%Me9q߰s~G{r-ȻGe};!}{JKk;&SCe1gXkMot݁M^^K׺x~52MDžR}ͧ^gp<͗Jox[|ǧ%ʟ? .%x #]y:gW \$/e=|_ /7zj~XkjѠ# I Z|QQ?]Sځlc$ƈEvoͥ95zK )ا%]O\:/f7?Ɍ0-Z Ԩ"Ԓ7[SZuS>˽az4T~kA6~8ehzN?L~@50*3Tf@룽27U{j#7~]* 64r۾:IC)jZΣjrZԡ/N44>tKH#e\Rͩ~/C|8rl8^)-zk哤Y]7"6I_pz\IRNWꞤ2,m'&]h%I8O{teOR.[wmhI zm=qp%GXҧI4XY\Ƴ\m Y]+859.6 gE/֮GB795*{}ZܥgI\ƳrPG/^b5bcF"Ku~Bh,U&ti`.RKi~ԩ\͒$)z` xV.5ZtM7Fn;R%gq>_~_'\Ƴr3?yI"n-tVCur ^󃦪.SQO5[gw}CQ[oC<.@߭oI2~ .kI#UjL [4ͿfXŨDwe~vੑ?O{ 4ELlnR䢴npjm m)ǁ&rF|I{!ԝAdpi6/(=w$]f)40|-K5[ RfgvxU!Matϊ)qot+Pr)lu3L54G|A`IJwO|)E Zm]t,,ݼ#yVtϸd.{N-PaN,sjd+a4$gKqwMa+Zr pn~dzϤk_U=3uKL{/_{^u]NvWdx[A .n1%tGQ)!2uU^\i\pOI{dxy;%]>?8f~?K,_@2F ՁWpq)!/}~DY?O eT$2G.e.B:ɯk1?)]횪ٚT7A?']Zk Jnh82M!.8_~7cf Zl_/+SyH 4hz>=A_osTJGtnJ(;K|_[eG*;ֈKe<ۿ)4XU>&2"TaڳEaot\*U^mTSouced?m]zo{u{ \ƒ~IkjMcݭ:Uܥk V"׆l?,CFcx j_-=BS+#wT7b~:eGG.cf(tjԦjD9KԫjmkiiRa?7ԭe*9%|m*I%S~tt-)tgC.cDPW&'- tv Ĭou(Z9reK@ھL=H/G}jUMz[/[/[T lRS糼-qwol{Η`yp_ xZA?ވ5KsqGҾĵ/LV"7|\Yw^>\7+kW|XuL79Y3PCt~7Vʒ/V-74CדLd(:(]߽6oޫUe©&n>~62׫6oUžpDN/#@ӈ-Bq~1k+?Sjxdp1s>L+Y'KYvH ZjϓEGsgGDSheV9|f5a{x?F3/ 4XK/w#ZV k*jA~>woŻ"dk4wyZMOxJ#nsF,ʟvOdxyjϡD!NjV#5aY!SeRe2w {_Jo +ڇ'`Y_} H\\ %{s}}uz^^x-_q;;ޫ9;4M҆Қ)7OJ[RVT$e2(,#E͠[yzzOݬԱ[ҚVeԦRoU;iڈ Tw>-ZT;4C4Kh?*\zCzB˴N9S^*MKc(K |KR(VMUZZ}u?i=ӂQCtKZ?UZ.Ӊn}6fж-uvPCjru>*]\Uu WiR+Z}Uԫ^eUP^g(0S԰wFuhAZztΌltOXpmfxnMhQUi3fָiMmxz5Z'Q?ANWGD,^btFU|^ 51;K:q#8)|oRwo8=T[ӿ|⭞t45o_\{8rt4=XvUE=GqS}Kdp)'|hCqI^(#Y~6ZwԞ# [Yp/v.f" <\wq"+;|GL%eC~e0&%v)7o"C˓ T0j>ܫJ^*a,cQ4Y;$]4e :N%7:s.1)w-%[}:tY1q=`BURFij6\4gJ-ԓZUZ^WAVkFp|.S +PJ)Z է fkЪi5! Ԧ']%4;:tĂ#!U~rŇ+ <ʄV]Փts<]sU.B1bh =7ԤRFjU^^LV`xG Y%tEXtdate:create2015-11-18T02:12:44+01:00^'%tEXtdate:modify2015-11-18T02:12:44+01:00kIENDB`PyLaTeX-1.4.2/docs/source/_static/favicons/mstile-70x70.png000066400000000000000000000042341451425216500232700ustar00rootroot00000000000000PNG  IHDRi7@gAMA a cHRMz&u0`:pQ<bKGD̿tIME  +}IDATxkpTg7P&0RcҖ)e*CL2Dq:hkՊ0LҢRE DB06!''&lD}ڜ>y/'`0 `0 `0 `0 `0 rgTI^|FY'SV,cDjUHQdɒO~idp,uFEQ%_X%hmuעhTa*Ojtw hT>R/jȷOS4S>ۃIXN<TvlPJWg&Iڧ%jf[*QX2mB*PfyY۵NՖdYj-B%czE;u\jZJ%&<׋H~Nzރfoa".d.(az 01JW}"a@Z6g*gy˶+b]Zp m5uҠ=Vo;|upQE"]|MOCպ'5 Ts<ȸ!`:.cܹo3C(S+ҀzP =d}i p]f:UZޑXׯ=y=Mk2Mv3:zF.# M#{x)@- j9^R|ȴ\cS`]9`ų7=cJj#V}12Us8q2ƙv{d=13F;#l۞&byV`fߣ(v ޱ=7A*u0?vm Gxi9muHf6S9uiaQZB~$CAFG' V&l4uORK=d}Dж: Z}6Vo1rEF5ήվfpBYZ>uq <יEI~:H9=O#KLg 3;Ff'R}AE Jԭψ^VUT!:v,zu<ú:,mf-BVkkչW }}I6uMh&Eu*,a2  ړ"D&H< qU|Q0Qs(  kH@R b$JnZv1tZ S;>T$L"VZm֖kޕ$jWX1EeO*Uz~aWQH~;c,#L\.'|*?4ױK胎E ?:C(b$9ʧsf-|w_\v[4rKIQu<^HPC-ZKCW'l:CO#xu^cA1̿%ο:Ov*O;yw3oBJ(l]Zk0 s9}C!լR$iגO h4(E18`0 `0 `0 %uJ%tEXtdate:create2015-11-18T02:12:43+01:00%tEXtdate:modify2015-11-18T02:12:43+01:006IENDB`PyLaTeX-1.4.2/docs/source/_static/favicons/safari-pinned-tab.svg000066400000000000000000000052311451425216500245050ustar00rootroot00000000000000 Created by potrace 1.11, written by Peter Selinger 2001-2013 PyLaTeX-1.4.2/docs/source/_static/realfavicongenerator.ico000077700000000000000000000000001451425216500303002favicons/favicon-32x32.pngustar00rootroot00000000000000PyLaTeX-1.4.2/docs/source/_static/screenshot.png000066400000000000000000003200421451425216500215530ustar00rootroot00000000000000PNG  IHDRfsBITO IDATxg@0IB "R EQOTîg牅"˝zbAAQQBotI!m޾yC5>}ڵkի/^1b,kWTT (&vލݴi˗RRRKfGx {C=.ڬoBꍍ_: H5d{ڵK 8p!`0FHvɓ/ST})^{|Eenğ(*[nJ&Zl־<9%44ٹ5*P1bĈvzY[SRR:R]]ݣG+V,ZHbtĉ4hСC#<<355UCCf:488Wpvvfgos~#G1-Ydڵ СCϟ?ڵرco߮۩>}:nY_=vXbbbnnn=,--W^RΌu7o<~ƍD+k޼y:wL߿Ν;$''w۷˖-uϝ;'9 WZ=._93iKr\;!!AE?/y +WWWSNtccc|qÇ뇆k!wj h4ŋʕ+/D" D"Qbbb^k֭[c|r\tU2w?۷7n`>|ر#B觟~=Q__{կ_?__̼<777F۶m iTJ93͞t1c!>ܱc Nj1c?633ëwQX̾Ǭmnn>n8@E"tdR}}}ЪUsBd*x/RcEE.8q cC?Ǐ]VXwo*22H]|fN8_0;w,wOK߾}9^TT$7tuu 9{7&* e\RPPЖJ%!1#roMrss$Beee#JJJ\Dܼycǎ[l!_ə5kF?frv =x𠎎f >x!++VNeeeRcfYYYxZ^jUVW\qwwG:t4*uf=-=z3/^(// oB̙3!uӔ|ztPG\j|ͭ#^xhѢI&yKUQ">mG68}Ds={6yFѿ3g6 P\XXX]]5,P'Meff8qB hbb'xmP]kܹWWWǫ{m/J9du*//x%ATVV庺f߉Mw|/nm޼\.700pUUUH;&$$xxxڵkܹ\.ѣw#.]4 b9889sFj}蘘oS+V' pttܹs'lkk+ `T*E بd+z{{3flݺ/lCCD|!tHm6-899-]ٳƆ z ***Bw[E"KE>|1bĚ5k𲗗H$j6* BY7447n >{L~SM[gDc|>^}4...]RRRHv% _~M/_|P(6m;^ݻeȭ\Y? A,]tݺuR5A6m={6>O0\+͗/_uFFMNjqww2dBhÆ <==!JrW^ūIՕ\=zAhrw!5 /B~ȔrxU$t2kѳgOYY… x9..!$>CjTM5M򏽲[nN|'O7% iѹsb0.]"7YYFFF/^,++#"33BɃ22274֣G`rҥK;wxxŋ֠ 333Y}V]]===?>33Sj0RP(iiiAǏo;%Rkfb#F1bͿӧ:KPΝѿ .֭FI;˗iiixgݻw_!CrssqaYYـ444*BR}hڵǎcXxPЪDTcǎݶm… [ڸaKP\n~ME?~A 4m޼ygH~g͛ $g̘ABeee:thiG\Ӯ/]djjrM&zĉBjj*F58Nhݛ&D"\IS/,\hQpʕ+Fff[ظqEڷo-92m ؽ{;w zfÇ{׮]mmmϝ;f===9M5`R?xMI#s˖-ϟ?s΂ =z4a„/u466%O>0`^p8wƫQQQ={411!GFFnڴIVk^>}It1c]lllIRߡo3_z%QOѺv1bBlvii).WHII/$e:G?rÇLA;޽{AXXXO<3fP(BXUTTWGkx… %o^p!8pe˖[|PH'-~zB!>Fb?i4700Sq:u$'r@l2qԩt"Mj]aKbllǏ#d͌$ۛ\ի\$ȟ oߪ!p!ghhhffKؐWBĉ!4f̘1}t__>} ۦ4Hb #ƅ FPWWwƍw:u1>iҤ7np8˗/kkkGEEdddp8mmm䜜~9;;?{,;;;33sԩ'NLNNeee[_<ضm[III|||rr2pd`ffoݻwѣYYY'NģΝ366NJJbXiiiO<)**}q7n~EcccHHH "'*  E[[ˋ`x<|(5;;;GDDX[[[ZZiiiGjSu&'lYQfffIIرcGݻk׮EFFr[n%$$|KKK9˗/_|uV|?EUUU}111qժUxdb{nȑt:]__ÇgNOOOOO_f Nر{8Nn~ᇦ :̙3_zԩSe}]ݽuuucccw?r?իW߾}[GG'$$ٙF5m?ڵ믿ro?OJz OT޾}pZlAh} yyyxU(fee5ÃE fGE-H$JDYYBDaaV@PTT$5\ݻwx|Rƅ œo|o(& ǎ/pBtt4})Sy&)),)**x+`Ti{mPMM g0עELMMt\bEoƷ?BJ k* 6J k* 6J k* 6J k* 6J k* 6J k* 6g횚ƶpaYA~~>AJT$lذ<{lǎl6[݁JuN:մŋ-4iқ7oQQQSұ|yk׮wvv>p:x AAjk׮~ǒ%KpɨQۗ7k,@IB!%lÛn  H(""""l$_0p'''@a #^aҤI#̙3@6y<ޘ1c[`M :㦛B޽kѱ e377uvv&*:x`&o)T}v޽~Wt &&&!>/^.ǫW2Aiii5Fn  iYD]rEj @| BDDė ۷\.\w^DD"K(JyyH$a 8q"^SJkhkAuϞ=eem /_,--'jhhF fEEE}}=_ IDATbꮪ*>>^"B\n\\\~~>YKJJBH$&D"QͶFӐHR}Ɋӎi!!!/_pӳ-_9O,--w% ի ;t@Vٳ˗/ϟ?`Ѷx944t„ RmxR ͛7gϞMP.\*H$ھ}7o cbblmm&yÇs˗(Y-DFFt1!!#,,l׮]srG{x*]ti@@rpp8s挬#nnnׯڸq#ù{СC;u˃7ĉgϞ>|L4I\;::?~̙3Vj߾}ZZN:͞=!tN:㏯^R<$f>|8$$dΝ#bckΟ?/>|Ag[j>?yDDZn"##cbbB;v0i$qӧӥKYfxBZW~]x^0aBNNx#999QkҥKTfR2aiiٳgO_/ڏ?622xbYYALzz:B(55܅jjj^ziRi jffFfI F憗thr~Ն{J{zz^p/!  w%B$͞=Ɔ;.X`xry 8w"X 5k9Mu&4%6oެmooOĜ?~ԨQcƌ!p8۶mվ۷o񲯯jUUU3g\~N:5h )ZJM[򠠠$Y,?KDBg[_YR^QQ;tرcMd2ϟ?A :SNEׯ~FF9^߻wo:aaa'OjP(0aΝ;9) :77G@ ܲeܹ`GM0#$jD"\H# OֿC}(~Gq#FҾ}]ffffffO=jhho]vmݺun{n]]]|uu1d/ _TTTFFFӨ[nlڞMϛ7(<<,|>|&ۛԫW/}˖-#7 |k׮]#ι O@*ڞ={l\|;w"##utt ܂8ӧO⒟[^VVoIakggWQQq155%K<|p۶m%%%?3B[n7n~EcccHHH r;v̘1#;;;,,ɓgϞݱc`̝;EiHݺu?̒cʉ"P( `0x<>Գ-?|p…O>Oΐ!CN<XYYg*nnn~ ]ɓ߹s͛FFF7o,--5kٔP(}zQQQpp/7C .2$=kf\|6 Ҋ6m~L&OG׏3F_ghh>k,vݺuÇ_bE u0B@̼yf= /^;vԨQ@k@UJ k* 6J k* 6J k* 6J k* 6J k* 6J k* 6J k* 6J k*Q( JR{ꥮ.grr]v&&& C $''ld2ZgnEP(_[S| m^߷Y:::Мԯ1225{mmmcǎvK-,,t:Bή o\7ۗ|6m֦MOw-߼y7m4Ђ l5~2;;Ç#LfUUjG?>7o\2wܦ˖-k}VոW/*++1NZM~ vUUՔ)SۗիȷfXz5?{  ((_meGR[ٲƕվ}I7ydЪUZ5ΦĦT]K?}j>k޺unih;addy<^ ׯ_1cQf B??ִo}_T*՞]M!qvvFIl"IvZ.֬Y׺0k@еj;iMC^N ۷ԩS߿Q{{{ /^ׯ{>PPYYٳgO~ Wغukaa!B(44t!33[n5mɓQQQT*u֭?YBƛm_"ήf+""r877w1 QRRsӧ &X,5~xGh ˗/_zJh޽WD>>>++]v#RSSX,{Ϟ=_VV6a„^V?~\.TjA~S-777GD5ݻw x >H s}Aw$F"\.A@\<33?4iRffffff^^ق+Bn˖-86d2o"ׯǍ%"fժU")>mkk3gի#ϟ3gBh< !dbb"^Fd$#F ={'}石g#a|]~\|_l?}H$4hBhƌYfeYY; !ԣGPKۇ޽DG!qV' qZxG!qoȱk.Tol"^gҥoniS B@1?~ΆAaSSS|ԸK7ܹsuu5.2e Bcǎ999<~ӋUd+Bb̘1(E$>Sߡov\!DPpv{q'-JKKKHH ޚ!rsslxs璫:uBZ׾D*qCCCC9uuuG6%^XSS#>3f Îrppx}ZZJ?qBFaÆQԢK.wgjjڽ{w:WVVfeeVx |l ,Xe˖'NxzzVWW'%%YXX&&&r@x&&&Wʁ(B]]}Ϟ=N`wn|>X]]q6'/ Bx555<7499Y_~xBVTѣ?B%%% @|Y[]]߾}޽{Ϟ=+W [tuPPPdddK{hWց(?vǏ322\}h4ڞ={>MH~D"IQWW'*[PV !&LYGrE묬L f}}{M}HY*++6o|[[[F(0])**JIQ+| 555^^^CᅮjkkO>qʕ68###<$??_jjr. ^ 4xM0fM/'ҔgΜٱcDž *ݻwΜ9;Y]YYZx1Yݴ.B⦿?YRRRnݺ`0V^믿v,Yhڵ_ן:u*BH\ QSSofiiik>44TbӅ _A6DiYbݻw!Gڲ2Jggg l Æ ÿcbbpIIIIJJ ^NLL$9!޽{׿eH" ȼS__`OhJqא!C7l@^x.]JIIO trr^yCCƍT'zN:շoГ'OӧOۇuA67E&۷d2ZZZzzz:::-cǎN:۷m&I555544444ȭ7o&WWX`0455utttuu֨#G)S,\pҥ...~j<QCC&88 r&`0tuu}}}ttt4.p6[s r444hiittt222wmgggoo?w\+++777rfM666ݻwӧO:u3!{۴iS>}Ǝ;}tKKɓ'㭯^"[j׮ݟI+ӦM_?|@Vh6Ȗ~ E!_r j<CmQ)!`D >6.rrOx:Y٦6@U3@@UYT dmP%@@UYT dmP%@@UYT dmP%@@UYT dmP%@@UYT dmL/_TVEEE/^(((PVRսzM{@)QWW_1Loo={hjjj% c˖-Ν:uB޽{֭[˽D"QΝo߾Brʛ7oh4Bhԩ3gtttTn/(% /^x"^e2/..6/]fBT*K. ?B?qF:tH (#6PH$<6">~ʕMiz PM6͙3!$jkk=zS9 M'r |U@@BA 66))IjO6cƌܔPvv#MLL@ B3"""B ~~~R7ӧOL0gϞ/^lllqrS6Bh޽{}ffӧO!ekWcc/>~ثWaÆn7oٳ@ k*w#@@UYT dmP%@@UYT dmP%@@UYT dmP%@@UYT dmP%@@UYT dmP%@@UYT dmP%@@UYT dmP%@@UYT dmP%@@Uzקlclllee%H$JLLp8l6[]]}ܸq3N f03f022| 99L&޷~ IDAT]P(!6>+BCC[ ;}#G<===zT__N4ghjjjiiL&dzzzͶlnnϒ\.!+++Bݻghhhkk{%CCrIuuNG5ȦMw&!4A~~7oMZ`\p_~Wvss3f BcǎgΜiE2Y[[}z޽WVVֽ{fWwk*QXX1b=zD222dmR$M<ÇVRSa@Mi622JMMw)//Wӧ1 uӧO޽{vX`A;xrEW˨T뗎BR{*,,455EW:u*BN]Q__?sLЮ]k޽O#vu\re|E"lQtzPP.)//accccff&~ڿL%AHdccj*봴4f{Ѷ9s899ݹsv!׭[S  Sgff>!4i$|DyyyGlr]6ɔ^~_DW{vvvv:tP\\LDQQFKJJڴiS^BYH$VVVH=zۇ޽x5ǎ+^XQQABdaӬΛ7O|_|/%?rҥK B;wթ,v˖-K.}vz%JSSS|Vh>}Bd"'OĖ5gmMMMq.صkW3700UBӧO'K 6)|Yȳg^zuܹQQQ @E%$$fff\r)j``0`Y,BH}9ן8q! FR.]$2449tȑ#?~\WW닇G#Ν;ֹsgF:uB֜ <Ow㔀 kjjG%333Bnv>ߤ۾}޽{"'''00011 fСB"9ѣG8 `qqq!<-Kzz:~-^`0듓P={\]]4?vޭMLL$B}3)5'Dޕ >|C+M}h"___{{{\lٲ\_D~~ʕ+–.]dmm٢Fw⿙&L;nz/N?Yk?~rG֜u7lsQF={ŋZ/J{^''G)6(( TVVZYYm޼… 4hɌZА!CdU4hx]Od8p ^/r WSS5tп;))W\i%| qrrje-.]$%%qm7o\t ^_ef3gvq…?sϞ=Ϟ=re˖͙3ήCoJ|ѕŋٲRMMM||eOA%^F%BSSsrvdݺux`^_I-z Eq\Z\\W^cӦMNNNr̔Y,ֽ{BAAAdKKK8qbmmG||||||n޼ԣGeu &('''߸q/ B777@4g9:uo߾'O$o>}o߾%KȏۻK.W\ӍB 7nR'NիWȝȄX__KM=JPY𩩩|>ݻwo}-bnnmjj:dȐC9rر+W~`DSSS]]kjj2L---===EW۱cGNG74 FRFFx COO]v:::8_5rH:>eʔ .]ӯ_?,Xrw5uԅ ZZZ2熆---MMM{Mr?0m4++_4D6mԧOcN>rM;l׮ݬYccݻwϝ;M|uttL&yx7. %L&SGG6(rBx}|p>ڮ28Bld֮|= {}BBBII&Ͽw^nn&']~]H$zޯ_ӧOo@r1b nHiӦk׮}CjܹsAAAhЦ0p7--ۃ߿hh%KzꠠN:!\\\okQtP^^ BH$2IѾ6 hZ{=y$YzT:twekkuVFZAJJʠAlЖtq\u~~3g֭[gll&"##_r [>xŋlƵ}my2l̙s]v--O6-<<|߾}}􉊊bX777>TgMDoM$o Hڑd۶mG(VVVo }Ǔ%B͛Uп 7hXp\!tڵϟ߿Ϟ=i6lٲaÆW-~dɓ'oe-A QSSS[[xk#322vڕ`aa1`OOOL:RM~~|gH{:uϯ[n'\\\h} ͝;w.$$$--6흝tRZZZ\\|ۗJ!!!A?Rt…|ԩ[o^[[:88\tmY4$$1 /WXaii`'OݻuBGLL SY;+++00XWWܾ}{ٲe4h 8{,mbӧO bYիWܹශdq ݻwo_~ݾ}>mcY;::޽{L5<6on``6̙C۹sO?`6-RRRV^-#""jjj***]\\iV&=~o9qDAAX,vqq=z4##۷o7oeq@L>xŋi!sƍӧO{zz:thȐ![˫ə;w)Sd2_vv%K7.۷?^~= fƺ|M5ҥKFFF!q-ZH&o!w޵Nf͚bۼy m6"(>>Ϗhpȷvvv999[ܹ3Bٙ,D>8I(gKKKЖ#k (M k@6 lf 6t}z?311Q h.:S]FF^}„ ~~~_|E``?ܜ[YY-[eggϘ1?dGɓ...QQQ۷O gϞ'̝;Κ5k@-2СC]tGqŋRi 6 VZuĉ͛7NMMwÇN ޽Kv!%%5yb&!]]ݔ z#yU3Gzl޼9$$Ǐz!A%O>ep4JY{նX,pBٚ[pz02kgee/ڳgBܹsdIYYYKe=z ۠ PϞ=9cgR|c…Cwvv%WFUVV2u#$A]]Ϟ=!c6_>|>|8Լy ?|Ѩ(z B{={={Z4S]]}}|Umm-BY<00PWW ^|ٮ];(ڷnB̙3 |ӧO4ߟz}i.$R)BHܣvE"""JJJlwWKKKKK 5|r||g;:uGE0r!xd|VUtྶ/&hf>)?\.&&#Aĉkkk_~=k,ߓu|hkΝ~J,ZOjjjYY7|H߿駟._mbbҮ];WW˗SG,ח/_]prrB9996msNiiN.]&N8k,v&L-B_k׮޽ɓ'UUUM_SSsѳgϦD" unݺ: l٢-$WOHHXr%>PWWwE333yҸ\P(tww?sL~~>B֭[k֬yΝ{ ^qŊ_}ٜ}jaaŋYf3m믿ʇ;qŏH#G-tԩ*Sp6!ǏΞ=G111͛7csssmm툈?~<22߫W+WQQQajj1cL&#"''gРA![[Bv>O$ ϟq???z!4f̘tLV]]Bի7o"##G[9rŋ{ĉg2NqL˗ 2$_vgϞd+W{d@AAAő֭Åcǎ_`-~EuR3k:88Ʌ :vϞ= &HpL&@YZZ߿SܹsB{zxiii{d!yiܸq)..Ƌ/H\]]Bbb3%?}ŋؗUmmFO:յk׺:*tttxbK.??M5EvV>kc}X\\.sKܨtO+׬Y --__x"/_ɓ!TXX( qapppii)Bhڴi9}UUUD`k!!!dB͛& 8::z۶m?z(>i$|!uV+zb~~| ]bsw{$ djCHgUZ@[N־x={/\70x<2+Y/::L~ IDATpttaSSSi}Сϟasee%ǁM<==k\255u͚5 /~G͛7srrP۷߿+Ν;"Ryӝ;w&Oz9;;;MĤi666ws~mS|x5Ss }ȋ4~9qV۷oWرc5:G"ttt\=^q'hkkzzzAjkks\mm:kUTT;88={vv޽.J999BJ˪\rvv2dÇBj/VXѳgO.ۭ[7KKKK.)X  >bknsεvssرq}idm`8BlY6 dm`&M k@6 lY6 dm`&M k@6 lY6 dm`&:-f<~庸Ջ!JKK333[:@p8-E˃qioܸJ=H}ǏZ[[ꪺ7ndff٩bѣGN~˗/_]vVVV}Yee#aD"Uc5M'O\YY9tPM֪(65.p [nȐ!! C)]Q"F)j ^:f̘ϟ1gΜ[ʏ_~nnnxHnFS߲[WW:|޽{;X(1/njx$ADld=ZZZSR_ʕ+wc  =}4.)**OMLL]+u!iӦ/2//>~ϟ?KBG]x1/>|C'cvIiMM A޽A!陓CDzz'І ^npH$Ó (5:> \t h`` L&;w.ǟ={ɖe2ĉb+6C&jP_kӧ#ȣ#GDYXXdff}$&&(N0A~x6Ν;wػw/Bk׮8e lllȝD___qLMM###mZ__?&&,ѣB(((,9vBΎa˖-8NM(L񸅄VATWW־{͛Tt8$+YdYÿdfdS9Sc^Zӧ_x`@+G믿ܹcffECW dyyyɗ{xxhii?~oM׮]uuu>|FT~ !$ƍ!4dr23^z3gpTdBH__a sAAYٳǏ1"qkwO>ݻs5jƍqUPٳ!4x]V+.dpH,//|}:t֭z@  3f @4+W,]tڵ7oM4wEaaeee9>Zoǫz|~p\a4\GLJJ¡!e#u]q/ܹ~=z(133ɓO<\"qŋ0aB`` (j@k}uP SDRe/IOOoƍ+V{<oʕ6llgv֭)SYfMe#Fׯ'^|Icc+o|7@{O~355DL&Sܸڴ)ə3gקO>|OOӧOƪqko߾ϟS V#08J$U[*(/_sԩWlٲE[ѠU|GGG)'ZD탂BQ?rLNMMMuu|XI/''zϿs())qss~zpppxx sŘ lÆ 2lR믿~U۷o|QbeyyÇwؑRQQ/p?z#Gѭ[#G̙3g֬YǏر_ƍ]W_ ILLLE5q-$-ZÇK!I|||II Bhԩd7oD`G|3gm6v1cƔ+^P۷o'_HD֐PJ[ - /^L~x<ނ &MT?` +Қ:HF'B #a;sm$dҤI;(_lB|69K]KKȑ#d5-?~SUPW^!YxXuu5?w\r;6p@B]tUUU=zP:+V@ fA࿻>Lu9eBq`mÆ NTWW;vؘ!Bt(jPq &4:ի!---HDL& urrBC,35J ǁF?_|tzx!O~D"ӪU&hgmkԼyk~:u"lllڷoo```hhhbbL_iii5""U`ժU=z:t1c\\\|}}}}}}CCCccM6ɯA۶mٳg߾}G6iҤoߒjkkmֽ{ݻ3fԨQAAA8Y`bx޼y^^^gΜQ<EEE|>@__ቺP__ڵk׮]322%|>?%% ;wx{{9rӧO;trppPa8<>ٳ N>rqGVqC-Ν;{zz 6,66¢a&bΝӧO9rѣG-[F~nܸAsHF?ߤ$rpݼy3==rÆ SL pss[n]+[pVDTMMMUUB̬555555M],KJJ:vSjjjJKKMMMɛ)t+++B|>!dii 64|J$KK۷%k֬Ay\=greeem~4(o!!! P(433۸q##[i8V A۷|{mSSSDBe)Tߩ| Tǵ5H*n۶m޼ydL&ddBC噘 D"Qzz!C4Hooݻwoee%ͭ4:V TTTwЁ,111)--}ҥO񱲫@ 5ŋ999 ,/=y$YzEg[u׮][n &RRR TyUVV47X)Tj``@k!KK&}WceW߁ kk޽{s\gΜYn1-FFFΘ1ܹsW\Ӫ-Z={$ 55VMBRƃIiUXwښ7}tud2̙3ΝvZF6:mڴ}'**fknnn|>_qy2VHR_L&!pRiw>V_ZZZ&MRP'44mϞ=M݈H$SZsرǏW{[Tƪ|W,~dQiw+Y[#8wySu"##%ɶm8P(s^ }Ǔ%B͛jիEEEիihh8dȐϋD"W:V }'ߣ@K &}WceK߁ kk_|9lذ*\vdx/jo.33sٲe!!!d )Yh_ԩ޽{g͚հСCE"э7ؐұj\.wJ2V5FF^ ttt.H$VVV>GGG[{ҥK/]5k8;;?}fMfmm'LѰT*533޽++ ^z5|???>ooo?}Edʔ)۷o?3tP(^T4);cU{d`߁8DiR}O>m޻w///CWW9ĔR_XBΧ~4Qzk+;C=wͼ].m1119hѣG=X*Oiw>Vw@f>Ye eeeWi=cF4 ky$ IDAT3,;;[&uܹiYYY뷪R5xrM;=|Q_V5VOS[; 6Êݸ).M~ x=>VCR{T &w@dm!eF 6lذcБ#G̙#inWmDO;})eo GeDsT &5.V\y#FL<; 6]O>?0 88}6m]MI~ԿTƊzH4G@iR[n]b߹sϘ1/bWMqI;v,v̘14Ѧŋ(>A]6*cE=$#]*FDDDDDSL|2> k3 CQy4ѦTeS:VfT2L&%''%.۹; 6Ô~)>B[%hS k3@kSﻮnnnɓ'ɒT׫W/cXMM R/GhDmA]6cE9sT>zh'~S\.|̙u7gAkYaۨ`#DjPuMXQG믿wL6s̹s]5oAkYaChGhS67Pұ ԃ;m}OJJھ}/***'O50CCCPUU:-JHH ={vbb{N:Esh3##c׮]\.ȑ#iii-j_~*cE=$#̙KվKұcߺu,tttAl; 6è|;quGGǘЦHmvСC/?ǣX_ $#j4wmmm|& mm4ѦJI0k+FgT w@\0mlS_PTڭdT w@dm1"xC)oUcj4 k3VmlfxYZX>ϯ" L<977ŋYzXnnnzz:ƊzHjiW^+** (((,,}jjᙇwiii}w yzzܹ|{M.+lNmq={o#""9x BŋjoEXQI~qqqD#N>Hkz6m4|Э[ׯ_t ())ѣѱt4I>{>~]sX5C4* kqm`&M k@6 lY6 dm`&M k@6 lY6 dm`&M k@6 lY6 dm`1M-544ܹ;KiX.o޼7vXsssMd'OD"P7l0 īAӧBP$tD0ATD7n~uG__6K۷o+PjjjUU;^DDNNNqq˧Nx*H$uuuCppop-[ȗ_|y̙=zXx \g:vxCnnn_/敗{{{gddƶtD4p [nȐ!! Ciz]! e2YLLLvB'Nd ?3 ayII խ#333uuuRRRgw5rssc&f7k,oK@S^r޽d8k믚[WmfmlŊx.\)ڸqcY[*1!h"#G  ܹsӦMb6lꬍ?8lcjkknjSSSp8k֬ԩBhÆ ,tՄ޽{ͼiyR?ht?SVVvw!LMMq+Vq@SR)BH( B\z&XW޾}_tڵ LA id2… 111MQLnQ )So>///OO:!mmmBfe\iiB3gΔ_TWWw˗/x<@gƌZZZx?񪫫KJJu|rzyyqرcRRBŋӦM .\@;vl͚5*&&O>![[[!Phoo-Pnnرc Bn«lذa۶mT[pܲO?taaa/^ptt~zZZڌ3Aeemۺuu>mݺUOOOp'N2dȑ#G˝/^WzjLwf[WUuuuwRRRbccӡCJVz_|1~x\2y!C_wE111Ǒ͛Zv-駟7k``0p@kLС=׮];{{{{{.]<<,ꫜ]]ݴ44::', )GB'O|@ y;q~~~W\$K.4BG~N#plmm >}ZoQΝ>KOOoƍ+Vx<ʕ+7l bHзn 2e .5k?ܺ wuu{nAAAJJ;???8 ə3gקO>|OOӧO-[رcwݹsAݻ7eʔK6Z4Jsz`-|ԩSW^MOOٲeƍ)nV{GGGiJ#>|C +#Z,ҀAII׃ MPO'+6;w!u@^^uJJ A/^=:~8=V#ݻ۷/U>cǎ_~(}Guȑ#GHҊ̎;Q\+(J122Br鵠@E}رcY6@p ajjzYcbbON}-[_rRB23>y7} ]v|\XWWGfooK.I$/_:99)[gϞo޼r\.n_}Uڂî^z1y5<`T*?y<B*11qĉ*5 (8jԨ֝7o|SN[lQc]^~mhhhhhHN0ر֭[WVVVom۶ٳo߾Gvss4i۷o;wx{{9rӧOUBd;;wtuu>}ȑ#=l27nby\י3g0|>Ϟ=/766/o^FFFϟW02OO>%%%A>p8(Y rFL'!ֶC kSYJ@6 lY6ݿ?00p=RP޽{T@@9OOv{ժU yyyWq5}mrqq122RZO>h`_[gѣĭ[.ĴiAZ: r(^}mE~_υO=7b@5UW5k{{iيD"Cmm-B\TYYY^^X`ɍ.ŠRiJKKkjjhUݤ,ػ&;mVn(-PC(SD@DDw,*r EE]W) * `-7J)wxlZîɜLg<ΊOEEyjU^~,Bʕknt8q7iii?=z~G=_|z길ܹE 0sLNׯ_ 6/K,Kc_>---%%d2I_oܹsv~޽{O4ɋۂK޻{  p߅3.{_>3oACCɓ: A:udbT1c 6ln:z„ f͒ݼysnxvf-[,%%h4]v>k&4&&*OV v{}IQBٳ/߲KL[ZTtvX)@Ν^^QhhZnҤ(hׯ_NN'hڴŋk׮>|<?yf}믿>a{k׮[lyG啶o~ӦM^R$4Tg7PB۴sM6Bw$f,,4=LV5_h:u֕GĤ@FFFiii˖-sGKТaÆjժpUֵ]zqx0˰ ^CsO=[SBt;[7d׋iR@J)g h`uCƍ煸& jՂojj :]Қ5B,R@L4ݺHzܹs7n:uE… Xe~ҥ 8qB?!#Y۽=Y᭷~XٻW=1$ظQl4,M^6xg4%IOH0JKK'JKKym۶>駟ʣ>ۏ9M駟2K;v@~~>$%%M8>\h:wn\[lُ?ؼyѣGo…[liРa^|Y,; IDATf}7aaa Zzuiiܹs{믿^|gLL _dZ`ANNСC\Vy晄I&M:`0̝;ŋC QT/BzzҥKwڕ4f̘^z[afjf} &kâE^*T$b46lJtxMTSNEGGGGGjys7nx0k#jn$̜ ^*`5=k{KWP\ B~YSк5mBYzBfì]!Ta֮d8t\ܳ!f ~w v`i!_+&9w 8+GطA ζ{ݻÿ*XBޅYb4 ;w*KNNnܸq !¬]aݻcg!-Zؽ{7)ׅwUQ־qơCnݺU7oLOOwͨrzڮ W,G7nܹsGs/nvIrssCmOv/++;~iPr3B󬝟?mڴUV8qb#GtcsYx1]vܸqQ:FR9*G׏;?/((x׬Y3f/111֭Fƍ?cN2e…gϞkMKKڵkllǗ,Y?Ξ='(--?uUVȩ|nIIɱc pI>ČP%qӧO϶txQVE9}8ۧN*O3{'N7oǏ+R\\R'W> /_mڴY]m 3ڑ[mkKjQFm߾ڵk\QC fI܏*w tU*ׯ_1cu#Ov ;w>q^D̕|ORJ;&K.w})z}zz / ;rHRRQ]vu3c׮]1"w x\7zRڞ3G~pSN!{'~iXvfΜxdwL7sΞ=jT׮]ɻ+P<{;clΜ9[l[n}wyuݑwldPP גy3gFDDM|zQg rpgdd4lpԨQ_}o{e/SN-X`ݺu[eW#諸ڔgyf3fpb4Se2܌r?o3֭['O֬Y3}U9$uȧYYY(ӧOZ#T (-ZSNoPPP #""܌r?096nܸҗݻr34ު&XbܹO=}ѝ;wVZtÇ˅ B/^\PPh!XtF?~aaalle˂.\xedw^߾}?|riiҥKG5a„qjjѯZ>}Mn!骒:|A ??nׯ~(ፑ;v~F0*iii^Ъ3`8sLii))%%%'Ou>''_㭵_ T ~]v1Um;w޺uk֬Y7|4o\9Mlll.]5G^j]r?o ZV~ 88UVN]vڵ-[ׄuŌPpBJr+V,X`͚5pm۶)GÞ={O3UX/^}=U! >mɛL&NjCM2ExԨQ .#u3ܱJ^嵑[NS" BVk5;?} r0_$|)S׌̌6m]ݏ*wJn޽{SRR7Y3 Phj[mۦݻ0އCv{w8Su}HⷃBt3ܱ@i"eʢE z PMkGo u,; .[#?`0`@ݐeK u`B5?FG& \1L{ , |@VajBDDS!@ ME< !ldΝW9u%oSUuޓ!T50kWRT̜IHcH*ꅦAނզ~cA܄}*^0llX9X߁ ?^ТM}n;P5Y;z?UXGA81b; /pnK#>oװ=lXBfm>{/J B¬5+y ?;PYk4ƍA fmo>#!T=aJzlcAUO~}HHk;pBfm/>!T ZL>>wbX_ Kie!TY3e%IoOR ;vee !T`?+haPZl|JE#BYk=I9_㥒!5`2IiqAUWAyhBfmINWi4 ;"$gX7CEU+!,(6}W !TM`HغUP}QCwU6׼94oC0ނ,whmmj w`9s`b(z~=>!t0kVR[>w8Y̓Kh+b>!t0k\B<5t_|pB vU=/(<-J$ì]Q#މ g}kB(0a0}:=y\%ǵoBD#Clkܹsz5+.G`F^R B0k܁ v.-ٺ_!fm310J7 úuAgkvBfmKJaoiVTt2MөcB,Tuɺ!0kW`X\{!t0TZB(a־W4 ͞>BY)DY^qXjsP̲+ Q'k߼y3==Ν;b}IES_)B^T6clΜ9/k׎7`0;(Պ, pJr}+ԭ zAu+vFɪC޴iSzzۭ[ӧתUk޼y|99`2«Bf&L+V@hܳ?#BRz/B>Pʕ+}Q!C֬Y#ICDvj{}$޲eO®]Wph' ! cbb!yyyGcThw5Pn]h<3K_gv>~Ma 0Q"TS|ʒ$ICt:={Ay믅t6aaM""#"Z||…~ƍƍ+8fm^﷘<۶ ɓQ0p DE_ 'ݾ}60oCN%O? <=s&Ԯ n>kM)pzƍ7n[ ռ93Z)Xa᥷jҩz#HWR\ 7&$LIӀz׹p… $>kGFF@AAAANZ1꼼M^|"#'ahHH!|N0w\V׵ׯ: dzvÆ IJBkCBil֬S*a֖eY99> `_W $'ÿ/л7jsB u@Y;66K.K"z}PPPrrIIЪJU uFݻ˗Cqp(|u̴ЦKe.Q  ̘v٬HCwh_9%O` rs߀+f)/#kY`Iߗ.Aj*x3KsÙ3֥cGbŊAog͚5vXSVz{*7c\|p%48e""PRpeoZA C),XUtZV(eKCBPiJWy3BC:12RKsH۷XI5tưO#cvu%R*^M)e}XFRHI3ht2&I1o,!A "t%e1oҤVlΜ3ö`L3%%}V:{*TP"/",𯖖$iرm۶}=dɒ7FGG;NIH%*ڜ_|(6twؕGB  4Bp2$LkޜLRxl|VGP1T\PNxQSQ9~ D_AIIп?-*`޽Ir2-.> 5>=hҮ-(XhtfSFi˕{w^0 BXHH°aPTJ7o\Ҭ=[5;{G*UWHJJjӦ 8PvT\ @5U#1$>!_VvvZOе:]dj!44Vj[n%b.anH6ky BU^c,`/Vdw#tO_?Vq?/*.foESRLVf2[ŝxaz֪I%TP֢vN~(KHZ)4'aX˖SHHYҷߚ>ڶ5t:cZƍYY),!;ƚ7t:cxxip0= 144T V(?Ϯ^e@QԳiAރ04(WΩt:iFZ_=Ԭ݇ěΟss1z~l݇]Z u F>/>xWw@Qԫg?^P9ӧ <:0+ m\hj_~ahQQ!FAۧ*] _%~0ZhCC=+8#؇yK@`ֶb 剻Ҋ/]ڏ7 )|2 v*h4 ͫkU*`Fރ}Hj*=T@n6Cxv1J23)A?za +)f/0|Gsl7aPPh˖؉ڨ F@e%9Xe_T+%@~>l ˖wAqEBBݥ$ޤ`RBژgy^z V PV[KGkQ0y2y!Jwg u0d4 "l%w= vVn NaKib;%%k^}oq"ff';CM&eAii~FF$A,h`fLUþ%ޅYHA۶=ZHH,>ꠠ:L'_~ڵBa[l»Cˆ0bLp:׮7Dl`[x11k$'CZ0n6 Kw0Q4PiZ+Q#fmׇテ^ "|h,);;l>ȑ=C oa!.) mI`!/XN@[ F^1xش FEEyv#v~ !%)E/!8!,!dz} 5IaF# uDt B!H0k#P B6BPPP4fm*B9t)6ȏ3:>Vwlfìƌ`\XvO?߁+$y`4 ._6APVt͛Ů]I4xO6>-,RVv\~׮]8;񠚆ӳ뢢 4_u~ӦM1cFTMKK܌5GE'wa(vFsTzT߶%IJMMMMMoZQo~5ljW\裏o fI܏*w,B7f @p0 |xPM۬M);vL .]RקC"##9f}QAn`0cET|jK6l 9qDppp-ʒ$ICt:={(3dB`h~Nr8q1|~62((Hk޼y̙3#""&݌r?O!nذF] U_R3ό?~ƌcyZYdr3Y3hv,Ugݔ IDATwn&۷믿n7pѢE:uz"##@_GDD~ƊnB;n?H߾0f \;$TT&kThכL& ca_>XO 6t3N#7oƍ7n&ݻ"U]n=wL\frυ .\QuTύܹs[f͚~7<@͕vEye^ JNNjFi0s̩&ݻ߸>k2jU =?N';w]ƈ׵322.\RV\bŊ Y&>>?m6>ѣ#ϸgϞÇӌnF;!\ޫ\YY>d2խ[nIII|CL"O`P5Y!9֯~ߑj 6B^5f lУ KG0k#Uдia/`PuY!o3&?7 0k#mÇ?EE (I?x 1O߿0k#eL|#Afm|@'R 쵍6B1fLkaF7u ~<8PuY!_<=[^Ȼ0k#3cƌ`5;T`Fg.ǁP_4޿Qj6B>T]|Y@PY!Rשu";T}3B  v^* 6B>l`U+X!A@Y! fm $B(`F@Y! Cȇ:lk#+;vD Q˿_u%60k#+M5!Nm5cINwd(a!ΞVٽoB 8Vl,̟O=XPu $B(`F@Y! fm $=rp]-=_r.r!H0k#P B6B!H0k#P !ՠBBV\5+k#P B!H0k#P BvQJ/_,-++zܼy3==Ν;䞃u ;|fZnݨQA clܹÆ [vzjFiA^m cu!<<}ȑ# yH w\m]e>e˖&M9sf޽ 66v͚5̛ުUSnذaٲeu 9s&ԓ+ŋ?k_~Qu3</rW_=ވԪk׮0qD7ܺuCjh<$_un\yK;wN0aݻwY7ߜ={IT*?fޒ7h`<ݏ57LLL3fuܺu<{nNoҫ8s]i/**i"##~~*}Hl]br:ϭj*))C }sdNOOk5k(--ڵ6mZYY)S 8pذa=А!C-[G<:i… SO=,ŧO~{< yݴiw_pјޭ[7ߴiS۷/xx y>6//}*7_=9꘵Ǐ=z#G:uIHwr<&Mںu+cP oTaa4hFaeeelO.Ou<^z%[b4hd2@ ͛7lذ+W_~ɎvuԩSaaa6m*P%T8kSJο]cȐ! 4OMMsNE ={ 8uTכBzs)))XEge(6+7e֖$imڴʲlvyMH^ y6#@JJ蕖R[|94i҄lا~ 6n6?+266ܹsCGdG"᫯`ر~'k׮믟x}EGG{8ŋlrI&;e˖G%#=/(((裏g{n#C=|xr!y|wy'\kD7h $$ċB7n|ڵ~4oXe ^Dmxx8Ɏh4s}WyiӦ͙3]D>U{=z'|2j(>g}w\r߽{}N@͛:t(;;;--rW'2pt 6DDDx+k3Ko+ۤ'}Ç/Ydƌ  ,cG}jykw᎚:ujϞ=7nܸcǎ3gΔ曢UN8~S^ߴi]$''Gv/r^JKK4ҽU֔)S… 6lp5cl۶mvSĉ׮];^ C}ܝ'^z &lذaÆUVy8cHU8wtk /zXYe;w|;w^x!9cvq=쳏?xJJJ:u7:thllȑ#!n"c'MgNNNiӦ]t4oV-%v$I'Np7<<k׮Pջv>{7= ?|:uCʝiHUǏeqkϞ=%%%'O>rf=Ɏ~/(؂'M4brWٱcGBHv{=1F)MKK{Ah֬Y;tЮ]T^dI\\\vƎ#[_T$`0L0k׮7oPRRo7o޼Cԩӈ#._>ÇkڐаhBBBZRRvԩrCo޼ O222B,'??/RޤI;n޼YCٺ~;!!!!!aȐ! 2e žl~Ç[1oxZEQ$ԥKϯ9B9Y! VHB(`F@Y! fm $B(`F@Y! fm $B(`F@Y! fm $B(`F@Y! fm $B(`F@Y! fm $B(`F@Y! fm $B(`F@Y! fm $B(`F@Y! fm $*P}YVf&o nq2vnHb39cB1n)օ1,SLr B1a#, ev ,鸽&/۲b>@Qn1֘,0T]F!`Y5o-j‰rW)H\Kt:SϏ߰QyS{fm?SF!@n[25[Ƃ`bANe!@+>#D c̋tY̳s Bdm&gmkVocR9D<*ά<³G)fgfsrҶfXwrgs$q]8\Z _ F)bvy̼:Zne:fYWNǬ0kW!smYr!ͤb.9YZ_Ě .v8Yf-+!|zew29QfLk&M w˒Mi,uX0Od]%1_52ylj\Mq8;j,@ f+hfnWvr y*(wXpf:b mnQ\O, ryi2ΰi>+<(%0EִKj slJ̶EosBeK!A((`֮6EP ojS? 5̕xʶ SmMEZw`i\_[26K@ DU(dsvsDNʣ, _ܳQf=%,?8K8ok9٬hPs6[_&8uFƘ\1繘wI2↳IOm)AE(m`;5b!anhc)[(۹L1-*'f=@Rv;!Qy-u9;m`sD%U퀥YFPe˗Kɿ͙ "sXNYjo9 Q z(]vHע $A*3|IJX-Di۰vbH"vUf rUb^Hv(F)7u qN'#Y\K57 X jE)#(U RZҩbI v)9Gmυ *hSv^"p0Qo-3Es/5v۷\m Ħ&#ȯa@ѦvPq8P8_DŽ-c>lAؼ΍;a9bI/a2jj<6fĚ387Yc6oZOR*hPF o$9X<);epMVe#3N0sBE[z%PkOFj;ؖJ%GBr]v`m$a=D cSx+,/@hS$5`ZYHE,46-Ķ'$DQ$#6l[* 0ڬPDJrg;۵۾k;bicukYrg^7Mbu Xtu:YO>2f'08Z~b[E 0$*Tr]PDJ?f[V0 IDAT`ȄZ-dTam33 0E| kTv4$6&JV(%eMDmĒ_CrkWU3?m`9+R #.cE1c rOnەrO?s|Ŏe@+*((eK“($I$1Pd^9ZIK( &6S^uìG>,̼?,STA>(yj%~,[j{B@qwF ''!kZ6 |S#!Bfm DA @Qj[aX5O5~aS{῾-C-qv+ 0ffLPA*5d2R1ߕ/bB@EQ0X+M8hi0[ZEm0²jCww檽]oɖxI% Dqo۪u~@۟NhF `SL`@s՜P>[ZG򓚊N,IѼb!a\3tI6Y9k;I P"LF$*{(fR (ITA3 AnIk3!$cL`]Hz729]3eb`/T*dPȲ '&jDPA0*O"WAF-x!$$D(Q*cxUZS11"ARW Q(e,c 52(`RjJRA`$jLd$@DA~#$(P`:BL-_Dd-CWމmd,(*)2\\p'㘹 @at}N%j(kn@ Hhn#[. V.2|FCsMeŜJ*0Xf* ?mE:l0Z Tjh4Lrw9<$|c ~V0HQ*I`iіW?!߃RJ(*JVUPJ),'r֦2FDAV4jF10Ih2IFfi3F0yddUe}) q8319odopyC(ba}$a9BdmR^2_)Z1R`2 aruL(A za8c@y\ Mfyr1AD 1DDKR y홀I "F%"儡*$I$QR* Z֨5*?I$QJ)P%I!jJRi4 ZT&$J(!!#Wx ?VZav\-zaK1gjֿVG}vY<(0a֮DDQ`2Kb@`,DXnݧh]wl"|3y0 cDT@Dd/ (6#IYAbL rr)YDAD` DM1Ġ J[֔1DKܿ1$FJ j:HěیR*$JPb>}OR DDJQ:8HVF`L2dI$JdeP"`OR 2R1xD˩U',,u1fv#{f 9&qqZ"tJfm# BQ%2ޛ6G$WO3 nRM3#dOU8pc>yȪj+Ld [p5Hke̊JA\ckʗ$WMh'}Ӯ "u]UxBB`&f&ښ6UVaU"R07ǫ ` ['v3&~~Vv 9hRDTDLa^rݮyTgRC@EAP{kKt; PDz00LaYzowVC0%d[smEDl[Zmt~^cQ* kSUQ#1"r8ޅrD$1;tӗq_hp^/vC_o޻Fն:g݉ K$I`n*2w_<}AD%\hMt_qaMQsPpMVP!*,uNV~UuYz- j[z_7т,T[fJ J׾.{?5.clNt7}YC]F)3'l1Z82Ë5]\DAZ*"MX82ưpm}iڄt:_$w+w `~壼H4-|$Ϟ({U &&P֘!cے&"YH%"T i/P'T) 7 z1FJIIL*.HQJ[oʦKkm6cu]׵֚j[Z[ޛjk*ZSbvp[k${#@X@mDP&K_}MȌ(ϭ@F"XTֆQݒ|:'dDxH]D0Z[>=>Dmv2@Ą[u뾭eWFvï,Q]L@cIҚ "NTOb%#T(6^^_xyOt`e"]*R,ݛ.}Yzォ 1˱v1`a]벴t{kik]D>yoŹGu!]{J̥nPgېQ85D5K[& dg"A7l*9,+ ~1L.[ a47FœHi.z_־zx":93  R/Qdטּ)Q[sW=`7KpϙXu˶mAUi"ś)s_,K*zd~R֚Gڮql"Mڂ=C%JtmE 7O$gg#AA,$X`J$"{(64azue;J>n>yʤJ RYUpXwwǻe[%It\f&L6/|Oܷ%_7ˍ'hJ~I|޻޴ujCTd5U&*tkCPܐET"f;xϵ_Τ7]@,BdL"hZebǺ12<"É`-&-jNΜ,R#%vDi+G[˞ObdޮI@l& 8(6S^t#Y(vKsF]C֫ĢMj)ª$0[Fp\e[jM/r~鋛}M]:sbgz_Td흉{)ܝiV~%"@HJDtP%Һ̠ɩ JqԻfٶ"hӥuJkz\ᰮKkk-{o&W#iN{ͭo3 6"Rf)%QUOD"yeΞK(miBF 4e2 (i XQiBy"ο_ L"ڠsm,:?] ~j_ǭw.%Le`|F>WUL}YLu{"5"Ҿ,˪DC PޚLPr#!0FfZ{,7(:]KSU$ ,Mjo !.U^z-Do.6 yY' l{3 K2ݍfV@4#gvk>&V#1,-5+ch 1;Q]Nb6%j}dVĜ!QV=2=h37aVt#_Ki^#ER禁}ioz_w/^LDYe]Wa1De].Z^Nq4%#"_B &@NґgC qS--qeB9ޭaM[^HK $1LLD[Ү^AiE((ח18Ӯ/Eݘ\+Z$'R_HQb/Ko1PPBjkyiQ|FV6?@S>5DĬZu}ӧfU`7 eD&EEDr6|Վ_2`k~yGHz_l۲,"Ĝ6{k‚0( -` dDrJŽ,7=&Z{n<8~g:E%HƍBS+*(Y+;^0Tf$7^ 38h*EX0m' ю̘W 40GC<I3E*m?/:ボ&9eOW,žny}oxAb{;Oc]G{FԋH8^HpKmD"e •@]Zʚ>\U6f6y$ȓ| 樿sƾ;{U: EZLL5AW66`zYȑ4Ϋkb"W*@z1@)t}mi(e[$Y'7\bQ)L76zls&|_ `2Z=## 4Dy/\Ct"RbX`qw%%A'$T=kFqм=(p Ay K +"ݼrnp3W}[Qt҄Y ۰V[ NN ỺF3@h!D1 &*-־&DafDf@ Ϝ=_OIضMG[DD2ύKmuP*2k&l;CD% XSo7. *dNyɑK%䉈"L&T<G) IDATWY!dSp_ԕa3%# ThQ5jcΧ|b'jZKm u#Hw/^LݘE[6kNkTmپ* 2ʢʢ!L 7e}mMfQy[[DJ,~szi윂C3AD#;#>SS6OLH (Nu8MIP)ADF'BR=, d1ʅ#VsDDx00)#2l-`ZN`aZFDvrޮע7W_o?kZY6@FD!tW,}z3NaRI^&3wmKoKoJfd%%rf̅ kt[.p2/p$yҧ,fxt" Ea2cK4FfJ)G5gDS1_ 0+&LIn#< Ϣ=Wxlötz>OױJS9[}yokji{{Ul<hC=;4eg>LـR3];D@(~0QM##֣^e ]/FY "e),fn^h7BUm9T$ˢ0"3EրPD%=Ɣu$j`<F6S3ۯ$̤0GD{$@7vҸP$ ɕr?Y$EHL7w1FE OB\G̪5Mx#=ҬSCvmu/t:?G|T@d7KErۖס_|B .=޼^_aw/^*ኇ1'O@BIdfI;If@$Dɜ ,Y8qU ZULQF!fnE^5ggfyb(XHYbrLHV7Ɍ1\pJ8`q6e*4.!݇m^.vlcA̢SH DZ-ͣUh'7ؖr:cuвm#l󱍱qݶ|:=_.n!sߴULܸ}_V}Zk")IHN չc"@# O*t(*:+L,e"gX_&23fZF(M)$J pDeGH*#AIJD2EYd5tw7QmxzvŐmr8.q=ܯQ[+;•Pe?Y,bوbR~3L3I_ObkXA+0۷}xan{U܆EMةc{ VWT*+<@̈́Le#T.wUD ȨbrՋpQ,ܩDWe1hD5kO%zD)"]o"` 3 ~BBf !jNɤTŢEu]ml\/v>]ΏOr:mյk??hk >|8M%E&v} C01fĝmLHa1&۠| V¾5bmoZl>9ʢ!I Jy\v ͈ h,<_:ZuEDRJ0BQqmu]VUItN A ލ @>.2]@6gH 0MKa$SNXwG 0`/6^muz~.r]Om^Ƕyƈr,bpv*h|u]zk:ae]΢`>kc1<‡خpu=<<<1*Ϩf`~K_^_u7TW9s>^އ޻Fm6̍R̘Xөz*LwqGQ: LrtoLL9FEHUaU ",#NP73wc,dŅ|U[#bm]ilWV}pO"~$BD$LLlLNfLk0M'YuNO??=|~y;?9kp2#AeU;lmfp ?~|~>~88/a=i[mpϺVtzn0n @oE,_o^毱_#[qP]?"ٜ8 ~' vԃYU tW2eݶ۸;r9cSK)l g8O]s"/jfl27_]OtC=}j7A&#s1"~ %_x_yw/^j6XmF 3l۶ wr~$J#*֜@ 2]%NZӥ-Nb.7QS-x<k2@Z/[ bZLHoBLN g tܻHF<Ղ{sב#+Y~.`av|v=۶T*(2`feY2]3v'w^Mk mcۮ,Ҿ.OO?Bem6̘OfץwQӥ;[ _i# /0>w/"c߻W]]WxyQ̆ann. pd 3+7%XkQĜJə,lY´z,*̪S?Me˺*KDwwD:8\jQ ڈrQ),ML3DYͺG)<9m݇e^|c 'smLrLQE ,m#ܾ[aYӶm6Ɛ&c\/bXnRvP͆h;?4UN t5z>"3Ӕ#_{JkgsfPjp[e]VTOkD L&9;+Qw(g! j8),mY5&*֚6-뺬SI3 |t,"'q9nB 3[Ti{#k9V7QUDP`xre0i1G y\qҶ3=cp(Ɠ@D? r۶kkx4I@3Sz~˟OKצ هqJd欙7y |$ejN NJk'T ' fn ιȩOAO6hq3X%#$cR9=8Xt@k/"ʢjʾ\zWGa֗DUs@kwk#49@ҧT"4,*e:<$,AJ$tϗɶ ҅XY75e"ǻ~ٶa6u=OOOK-݆=>>ض1>~{x.DNӁFVvZ|z޶ww< gB~LJO~d85Dn~8aN,ff#dD܀M20 ~bϼ {+{ԧ^_Ow/^"خ>a A"b۶eYC@"NDo堟;a`!m3R9*0g  EfQdfNXhwãvq!a&nZn!LTU ,ZkMӦa$+ "Dʓ΢H^½wm,%3dq9q^GMϗ|9o6CLz>hǦr>mIJ?~a>|\ӏOO~~4ƘoW0nTmm{z~1Tz׷a{ND0(򝚝|9Gp("og{*k0gFxrm,3<PCTF(_3.~mY?7ӏ?)wu1:?| \Ne9OOO&La1F-h`qÞ_[^}j_o07y?Hz_g@f2j_4-35A+`&DBKL""hD4lxEL(#t$d8ύ݉y#Ո˪}齭v9_ړ.ae9UxH&T m2iSeZڟ.Htdf:(bH9&e ]?=sCEwե-gͮvm]wLwem}//"Yz#^<>~0O?Z_ӧ"|<l7Gy'݌{k<ׇސC8c9ř5{⥪/ H%1i"u3:0kKeQ& pf0܀ &BJ172Qp,6"REE(h`üiCfpY7.udVGRH: y,PՈ+:yr˺#Y$cnֻLB 3#/"h7Il潯}=qw Owww_xz|(L!j?%~v&rww|z6+cX8c?# 9|eƠ #^qbnI{⥪": F2ܝId&=I<‰k%ǰ9XZՈ`f@YWF2F`Y16f9)D#nոEDE}\U6f}5<Kᶹs(0q@9~l bfp0 7Ds7# w?u1eo>~a-߹wk=?<<>~O%',S@3o?|tzt>YR]KOO 7ٯZ6 />8F^̷>3z_8FD1l'НclxL6Mm۶ 1+1Hk=D[""TMPb6Lu.> ID6e.a4Jf e!HEJct)JӸ2"HG¤LG0SA(+Z*g"E$3+Eav>oÖpvId?b]֏w??}zO?^.uppu9""O#aò??2Ǧz4ldhNRz2$({ҫOޯH_*_>N?݂l{⥭/m)ZHE*3>h'OR aQLGI2_ IDATB83 21m3P8"r<==e){J$eYU]7 wY2 mHg@*D7$KzGD^U{I H]%wrC=}wuL?<̬RTj@ufGdTEfьD͍ߥTKn/Sc\\^=ڬ6D9'ggWGK&GIHÃvyU˔"#.A鳋gt:Zo $ēONN={3Vq=>LcKdfEqLӨf7l=8$-`uNG(A ozqݼmJon$W^úCW^Brt `,f뇈,$0nݢ8x-ZN֒$11RJA~ PJqp~_c1V0Z+")ume1E073S0-cB. 췃P#τY[niՆKfFpmz~vc'O^>{=zY:>iuz^_~Y/\,Ҳ_\{U4}!w>Z2 ͷϞ~hYo֛0 3 J8Z-WU)yݜ7 selׇ4w={GgKkXwʋI$ rB Rjp8ƴuR"o~X;̞d8dZg8H]i)ܻw~~㯟}'O..0t}>|㍳{2i{'\rb)DZu>r==bxɓO&M!{җOB擓{/L؆IH~ۛb]XaѢt'SRz-|~#жC{z}juگ̼.cKr%B"Ц|!D"1I@f ^B3 1bu}!ХfELS;:9]]lv[Gq|'UJ]8w}JOznEqzv]Σǫ(^s ifWoXa=oE045/|_!/Op؅wjB@U KQSy؄4$n)7kS"nQnS3,bZɄݦ!~=;;w_.%Ȭ< 9\|'GϞ>IZ}g_|vH=xͷ<|^/Z?vwO쐊C$L˔'9g4f-_3na4zO~[ 9~_G@ۯmݡ+/ B~cUQ)039@S ; 1:8ѵk#22A`CREbH>Nc_,ԧ(A$66rurr<'YFM ޽13113 16(w2hA Zͽ"ҝq.7VGG125* ! 4`1su`d x 5 x~>çϞO~|;K Κ#7SBu4K[oR/u}~0LHO>x^k v3bđ82s"(a}y1UuC ,}00~]ӏ[*Fpp,f6jy;H,"ff R@J)PkawvDDȍZ\yn0"#1;AC(ZwnVjL8&a!tbZkiMk/E-E g5lh@͠/ZI!0W!ľCBSijʤ 1Y_߭?7OߋLdFPM@ը*`XT-KARXg_6뫫~43$d"jXt:57zx3ҢC9a)A-`!rK']PwʫX8(E!y3v !iW@Ft$ Cp\j$DOR%0$b-V~ۭ!FA4 ;!Q3ZA$8 01&"8Τh,APTjSz,uB)E$&B!'?_ӷO}E:xrv= ~DcVKt???bL[G?4Ӵ\]0% bZ,VӮ[Rnjfsy!Ղ}b R]NM>c!\ڑ%/p^ܦsYwEϭ+Q )Rǁ tc޺c ĬTFFqܥn/r5a2re۪w|w:j" IjD&l6&Zr)""u6-e\Z;0!qjfXBiZIXBJ@d޶@"Sp+?0_͐adt@:Mh&Bs2S)>V.ҊSB@ ܫZ#VК?/'?೏?\qr"(pv믞>{_ͷW+ Y(D Y!#K,>8GeWnXMN\\]ÔKUڨꇬƽbg""!k¥tYa,ϫKL<1oN'-ZS;+߶CW^g-CB,oU!T$03+Trg55U3`4ӨPK܂8N)t] %  JQsg6`l ]ls@h>4{UwԜ1ǒ+TQܤľO8W$Ә_}ɯux99!Tj.; \IXR FwfRZ.v{q~~qgWnȹY3nb 61Bt$$B&D 1Ea6i8Tժ$-5*7p㾅J:tEuگZ ŘGD\}ZX ՝j)!Y$v],,#31 qpGs7WUws–p*k3?x=,Z Ws-ZVRjT< Z(MS.>8Ng1gR /v}bun}90=Wpw+~wx,?9{ `j0NNNW."d@,h`խ<0/eR-抍BȀH-T uj)eٖ~C~Wo)YG @>g`]rؿmuگ1bLM, [NjCs2ڲ A 6'w)02Et%+5@ d8wv̥67Vԭsf# [^/ˇo1mlY]uگrΪ EY@VsF$wW@ @ԏJLu78zn -Vj<ꗫ.8u #̹&fd΄`jVLlV]T  }D") ]M 2xxJf UKXKU<5GWwA"[1R8D9On'i؏yRwuC((s>~Ips5 CUra4 xpiT jF@c)OcUs7{mܔĈކN3gM}Dk7э?dkۀo?}gSwkawwDN]d"@U]yUcFDsn"*5+K.7vJ;}H#2#՚+! !2BYn DRI ĈnVռ֒kɪ`fjU]K a؏8es6b.1n>1cIbY$B C֜0{)5HPYL J0Nv"qꖩ_ TqYdeBPov1Pt!g\t7kk_fM`1/~[Am-e uq-Qa>y\8~׬PW-e&bF!F"H̬yJ8Y,_`$L KTVqS.!qZkJ,""3e7fJnޢr m&N$HgjFjhLZga!4[@5\":~(ݏ0R͑pmಬ+R5$ŘRz#`Dx_RON:7^]SM"PN`w]WrrH$1֥nO!!-~IժVD\.pyyY.8RŔyDvyng? m7}xfT__|g}m- m }ܯeݡ/ZN98$s S̍HBP〘9"qK#kv"f 4 kZ233Xf 5v6# !q#Q@vA[ D%לf[v9iB\$ZUutVgx/VrX.bbX,z:Z={6S5%Yy#G }ƚm:뫒{O-cׅZFzռ7D@B1OV m7VkM1C!Y&@0iֽqB3NR|[|/^lo7}!/wWwʫO{ 4q5#qDa0mu$WS)TMs-R&fDj3fͦZWRޅeK"][ٴeN$&$$vjnzT C즼ۏ s!$NNNN.I+Ff$!b! AA[q8Ҵ+2;\Lknaj@b61uoC1Ejc&vq$q$ԁ\ ՕZ IDATv:J1՚U5i^ƈyKV]}Ń7Z/a9kUwA}(vZ&qMlsTi`sf!*3KD c5bH(FRAD Q`a QB HD"p֒+: 3 jZi fsa:A]U y(Iz$NcfB2^>{vBbm7m?3ZIyuܪ cFZգ*bf} ֒ D!D+5A0M5 no}3@o{םlz _y vȄAԊTeSs!&F"aC֔, 1&_"H!cDJ-dLU Iqln@1+v&&bi 殪 ~OSƩiV`xrWˣnbR'_}4雏~{S~{u;Q~1OSfv ՜T"Zœ2~3֋~Z7ZKZYNc!ôی3;2lNմԺnEH ]Z!S}$yw9o7X}5ϿףPWBMhzݮ,gk6 ,]J}0#:33 wS$Ffhn i)6.0c 4Aݝ ܐ\SG1a 4& 9Zq쏎bJB-W}?nJ fvO~O>sNt],Ӿc.1B9A!PfwAb׬~ɇ@X G2p-5??z(uQrb-M7 nTIABD YBn lϭ;6u;aݖx+rF<(y^K?nU"b[{drWAݡ+b/ QUj u`c<AD掉M 寷ȩ\TuvɼUM jpg%$]Eu} h#`c8@CQpPR "5|QG#Sྭ=UaLCž%//͟}?8K=VqC9`_*,V_.VG, )uk(Dd5Ό1p|y엿Y[z7N12f_}}O{+,맏}T20GiLPt`#K~2TvSJ;zJqF`9hBO~ݒ7 __sToMxuگ c 9ZD1Sߥr85洂rz fLvr; "uWGTV.D]u}ץCDn-٦QԴV%JL >3 ԙgLc Cb!bqaO[WSeo܋1=z{q*>rƛo/YѣZ4;#HhZNDXObݻw_vΛhqVك{'蓏?IOOV;Z:Z_|~g?[o>Z-?\B|筷쇫O>[~{=w/Rjs325J{g?;;[?OWoܿ>?䣿}xrvwb!8"+qgC՜ 23BRñXV+J0o+sjs)EMLժ52iJɵy/:Oͼ72f=w]*u;~8sMH$bHA8@f 10RY{!7pȥHa09 :@di`Hܠk55ժ6)*ĘRhmZVgj1 PjiDUՉL+<6?'O.z_vӛo}~l٥yt}E{;o <cVB4U^Diǿ/4M^O?:== Q]\\7ȏ}OݞOy3uC8;>Y{)SqE+nsb`,ĀӜe/]}/J>e$? _.>8~/?}VL,/uJ@oGTl^^-Q$qc~ٯ|ARTͬb*eS& !Їne3j 3"墹\aojj*LbF/3Pn̨ц[wKr.^aYˣ\jT"@pW ljZ]S ]ߕŌjubFȄfVk&G!d ^TKjP殆,_fD,.0 8`J!" !8 280R. #?}8=0nE,V~gϟ>| ݻ#,^)֒ j6>oO?b;>O/?5Bk9:>>񓏞?~__naUr7mHIB$Ô؁>u IUnAdf""$>CfBFG07VK)לs1\Uܾum-p]uگѸZki$DSq?ԒcSS(ժƈ5s5V!(PCrp10pp: 6pp/% afdLaKc x6w|uy!tL1ߴ%"yIwE mOLT̔X HR\ʼnŢKя~<}GO1 rEETE"CDa XjQ+AA5 wPSUrΥ( \C59^l VHZ 3YBQ$2B!pt5˹N朧}X0l8N-o(HbY]uگdLf6~k)!M恳;3(]ZeEo?F1 8kGhKJގSLVZTID KRjSK)xGb5"?`uS0W㔋J qʻaDbI!/S`@DīhMZ*L]JJLČXcu#X6uO_|V FB@.(afnxB0WS/ E+zuu(ucBb[;zm"q`af '$@MS9LSRb uy_kk^ w @׼P׸Y?)y&ttSki%ZihLjURZ0XDb D$3oZL-4f]m']ץ k68DgEQ<́ ɲ]ʹ25')Ta& O{o/vaG-ϾZE?wbwW-ӤhRЩNSJ: N]PXn6._|U&5{ɶ:,VD}NfVUw;CΐԈ"ؤ`ClX-@' I=8p澺*w?m?c9?=7_}#Z"2uHwKEb eA`U],!!GANI.ҵC( (< f鱓pW3:Gm< ||>zڟr}s0'c&M*u2jp 'f0֪ PPQzp$ BO$ -ZhzZPxs>Ù1(<22-WBǰ̥͜`$2թL2U d1{4o#2»{wnߓ~xNʲBVJ͈ov۷Gy|!<\YZmRc]o߽/w~_M9{50 H!R0xixeOa kc +x|߿{kkafc^P6,<zڟ)m0;Pf-:z ?NID[p)в:}5H"$EX޷ۓEs]VfI$ $O K88`@]B a*Ȝ(xb% 򏹑lc(/(RF)ypwQ-bx;"BY8"omx'}x T l[ۇy^Zh{["ޯnH$1Q$Nݢ : |D۰[nS}om{۬2<<-4>,sAsF NHc q,Lac6Zh>͹?tɏƿub:pڵ*l fn,E,"ǘaZN3D2_<ֽEH( SC8ZLDA#:8ǰGֲVd&3DA}X. Ua GNAބ"F9> 0Ժ,4|3>lF\="坼9(<3"/wxz|H+ܿ}_{aR$3Pxڲniy%0j@kvn}5InCeaF[Y&#8:5 )1S'>ffc{}e3~KԈO+k}ڵ?yS]>f)ZG${zX1p#7d&9o=yai/EZ"e( CDX(n7!\hYy9,E'%=32u9K Rcl c=XVk'}3BU.Uk_kчZ[qۇu(;!߇# R U93 IDAT#̧[p7ojy}SΈZ#2)clXq2%s2d"2DtJ2(m}ذ9kaiqX+_a׋GKkޜ)_DD A:El256:'%*I\j=bEUH'vRT 40QϰxQdVf Lg Y,LN!90D>>0_30^rR-;iBg濵k`2e=*|$ic:z%(^ -f X {kQULs0%Z"03CU+R"#<A@3DTF Ff hL x ՂލsXom?Z}v:-{汍|GPSKl'aq{0x{wcFP6J]?}mi=5 $*Z 2ͅaoDs@#YU H!Hp&# $"8#22<''bnk#GB( cRH|p8Wߴ?ḧ́Lk#,ZXJѢޚ_ZI&J-uUp3RHS:6Ptso$5s1 )@ :RX;Eذ1lCZp) 03hfeRm $m,K8"${Ķ<- 93)2{?lNxxXf=%cw;Ryǎ(}^Ӿm"|Y3)h'g]8hRAYkjJ<=]L d2h: %QߧN |DG><sI9Q䏣3#S`hK+oJxzڟe:ʾ=caODB*Z-SYybq()!A6|t >D6Rp'D RXTEtkE0 '{x̵ ѡuYu.\Dk`M0(c$"K] [˺ KD0gf%Yg om]:,zƲxLH37ʨ1; >̒} r ΰ1z۷ֶnP @1qIA,A`$dP&#e%3d143{o{k{,Z+PI%eX|!N)-NDHykDAb$eFfdAlM=\xHr_JuuQUf0C~#d%nArMLL\Wa֥Dqߙp,TR<Y2Ik=R7FLuY=̄ȑ"IH?߶i ѻED?>IPzH)'{'۶WkbZRV*ݼm|OB/vvݶoz+ݞ?lmH!Btw"̌1Qh{a'D<,wL$ c ġ9@6}}P%D#y"c2 DMb@ >"<@ڵ?z+њm;%硞X\Xe: pQm><|Mj[ϑ$*ZTTu)K A 19p1,#zL @%:{'1fcDp3Űւp QIwwnA)n]o"KQs$>I aQRΗehC~DǾ_3%|}ݞݗ`}Q-v[orfB_?ܞvzu%DV»6e|4$5/ CD#(=݆6)$3!Sr$ LaN,ї:"@3A) a'hv9k8$= zbR+䳬׮ *elcXΘ)h<>)ȜJjc/Gh2y:Ia ukE \hRDRje'2BޓDI)67&PUEAiZA0-"V/uʋL"E=I="7M%=OKFSEU#8ooPֶ>軻/o"s]׻_|;˅UGlx~~zv{ѲRORb9mk7Sn}rfn Aӧ_d8{e?HHL "6˔3}(PbI֗(J_<䪔ĠÝ#ɏk ƌ9HIAʯ]׮#)rnU[ÌuY1e6n[X󼞈Z(3}9ǰr9r:-*/OUOEkQe3s(u9fkH>ݑYKB9h}>eYWꩨĈk5OVdQ$z{22UTil%#U 0S?=ۯ<|9ཽ{ "ݖ7OOK-d,ZNH(

}c,aH@U}ocR4'B&kLkHc J`P^#]JUL}ݽSFUW!̬D`+%?nA C{tB I:kڵ?y ϰL{QJ{ sۙK4e5H 1 a0STML)䏌1X"|X2}\Y?ެ;"lק{Pj)`l+'t:{7˽B$uә==n&Z*POe=sY! D$ ψR *y#$(HI ͞-0LT 12>sŒr){ I"sfcڟ0M†熒Y=H$63׮+3##2'@S>}f:M"|>I{纬ûJYRt1v `R,t\ %d& ! ĘB&UH!"kdTDŽ "TL9sM髞.dRTtZF#9D<cp6=D4,`N Uyk Q3()Ue]ھfFhFKmMLOg$ s7%Kr9i '&)2+1OyIш0}o &CQ ce/ˆUT@a^Y6 ̬ b>2pֶ8l,˛w,%#[Z/~'"- #’Rt>}W˩"q殥׶=)r&YpII&C<@`@ I~?zڟX"qd 9 ]cD gpww.K]Vf2v5yWkeB9aK8'rs7CA""GK~/'IU…A nq>Os)> =͆?^oS,@ ,{}]\Nwe=>~e}ݞƛwzh]1z׷QkUmt"hݮEaDrh,Z p(K/+0079CpDP̘ČcL@ E@G0/d S8O9nqpFɄ|gX]h-}d1QBCPEEA#<|,_&RJ,eNHQ)@D,˹, JDD dnMo/?_{{9("̔Sf0˲R!e0JQ΀5mZsxoμnAukmYz^m7OJ-`?QTJeӥmWf0?0s"[dX"TN YB4C( L$s00˹To'RDqsuMDGhBP&aG>LPKP첏ٻ8^s׮k!UV?hŃ߷̘, Qd(O͹ ΢oa=9;"Ժz ~UDj8_ sAQQAڷ+xswɻ7_}xpw^WU9Q33c&;MwhפmzjQ }ѭamzS%20!D޷.e0x&ZJ9SgHa":T"aVld"FCET"Ii6-;<DTM~ i~5-ti"3&?? \*:at)"(qD$!a+BkJtL7hC.",|)())yXa-EKa2\ɢ,*Sn[OtSe^hYNwԹ&e*H n?'_{;/Ӳ.XDL@KO woer. <c:O~޶̲ԥ."q>Ha 7 Ќຮ˝ouѲT1Ҽh`0f-gR2IJaNt;IDH_GvB1$: 0;%E:Xz H挭ZT@FdD$yoqd$c%: ݼgT]W*K'2< "s@*R!(%}40Ie%JmO "HpQr)"\.r^ߐq>}񓟼{,e!s_[nvXUTe)[74a3߶iۯ[{H ZZ)L()lQg G:)$sҝf/7uO;ly(#@36ozk-<7"3XD{~w- OZORu9)Z2iwJֲl};,}>=Ru}=m/Zj뽞N:“`mmtѷv[R>Az381|8e[UE.5 đDA@r2c ~ 2<"<=huz4p($'$J{1;3I`x QkftjnbsȌH~dL1ݶ&*4-!ZEDSFQ-ZDifykGWR>Zg1C#FIJ,hm,+ XEkdR&9 t L3n}t4mM R>R4rH()(CAH!  l8PF4ї/P HR9*t1#1M\x?zڟU^ *z*0L2W[*ly:h(.w`m3Z۞ooy[J["̥ t #)U [A``t_3Qp77mٛ BE{{D.,RdYS EDZD0D8[O0BsQ) Dz!2ȉDkka((l>s]}_4Ps N2@ExXZaQ]ET>ITT{\c rZO|E%sr\JeY n>cv=)11aa/7cڇvRhFp r(ChZܵVsS.o{Xw7kdh5FjstfrևG.\BE"r~6e&{aIZ@$L9@@ԨKHn>%\I(Bq8О+MJIDATÝ"d|LJd,PD^ܾy)D IE&^?^'/sw7:N &%{=|XM7IϢ<0O$ (H4(tY8sol"֮u2Ӻ2))󛻳 t:K]IG Zf_o۶6G&r>-fi]l6;`*as2M(*6Rz RRX`-]PM[JH 8 (CB CB^ޝ~_!d0ۿ?^s9箳=B "~vvK@D`c Q ~o+-UP!e r "D&*r{ TIbZcELmh xS {C.VhQdp*{UA%V(QC΅P#Z4iPk3$D%2RLkDȜL<4PC*:3!2l 3c*lw@=U0\@@@`*3dl "1F?p+3~d} },@(B1DUU\ 1)-T Q$hhNejk< 1%GW aЌf`ٳڍ¦EaAcQbTE%HqzP5Fe z=(v1s!q" [c",˒1@I}(| cHVb]&M>becC*Z:Arьh)d DA eh&dBԨc k .ՈA !cpMl,1#Fc S"P%N"l^髍1jq%oPUM)Xf C?p[$H:$cRU\ )+) ~^AGEH(Ƙtv E q;p8qU@aۜjf0B Q35&.U y-+AV;g֌v*,N^;QtP–1ElavS&ޗe7u:';';}(W4eEJԣW+e/ ";20T!x뾫:cGDDH!H! N !6)vr$ ₹q:#q8r *2NCBJB`%RB0DfdNK)3 =TɒRDjBPT\"p&6z`f;/C@A'NA@PD01D%6I];"ѻڲ1$ڇ"i/Zc`fшea sZٳ̞9{f( t;=e[ڲ( u@(lUEi SF)T]ޫvw윘NBI$Ll1F@ F%!]> 1b6b{14EDVպ PR4@ιA퀘ml" "H*"(a l-ąZ5FJ~ RrI 5F)6H Away E")8u((QC iY UQU_ ?h^Ť i_$`6%YktD%@%$UT&",07 xX\;O!xX;/0VT@b-n[Fä!W;'vAmҖElU)J@P|JjHF] 1jMLn |x0U2qJMH@$%9kP"4U N{!YTj軺u\zC2EjEd%\pY`1 URM46FSB!@#Z[afJ_OUPa ehlD@ "Ja]b&UUICf1@)`$4" cR?(,  $ѫ6 ]HZDFUjM$2DfcsgϜ?gVQu''QUHC޻֌FL@DXI΅ "`s: W"bd*}#zHIWbp(K9Dq]] A$6DXvur]T5D e{$$V1:q5JHeRZ(4ԠIEEnͲ0L"A(Ha5D A$j@QBHdRLHa BE)+ (ա%)e"%"k 3~T 5Fũ8AgX31r*=H-cB*š b Q\0$Fª(g[LJ *qPl4en̛3YbФ`93f4eurwIA)/?"&w`sdS%A]"2!b@1{;s-{E y?pT$~vB*|ѩ!ޅb>%u(`i e"XǺ+)/fbB(rFyЀ#hixv* kcRw5rj@bÆ#1 8[1( ႘SN@!R  A .&?nTSދR)7(LAllϞ+!C1U$#زYT j 0hpu-QҰDTj&?ACpՍlWUiM٬a3fUƔę ٢PΤJ4̥XfVx_w^?}u/y QSCmY~?wEdG!Q~vbs1)Ej *7l4aCV B~~t=a"F0HM[TeA %VѬf4 R TEQ`|ܫ zAd$&f4ưI/@S1L2ؔF2HUX$@1xcL"q 9d}ЙGUsb j 9'TTjb*,gU>F0BJ$"DY5i]ꙭٳJks>h4YeDk!FU^sO>[F, 6&(vNLv}ETA.HНN(*cC PE(h․f 0>:H Ktl,%Jfv>*A4,DP[1EU5FUwRJ(RMP,*lQ̝ٞ5] 6B1ư10aE&N !HJJR<1FCQb>e A&VÄDcX* TX ĎȠ\QӚ'acP ӓ 44 ٬=d}Й3g#0v;"D `li@4eQ62hbp[/ hIA5fvy!Tٳg[**RxT%"Hb$D"YjB]w''z;c)+TpuL>Fe wN磤y9[RkLQTUeCB--Z5[A*A;W6Fz\XvzTvQ_kMUpipFl4G 6,d Vb(,6Kk8s5\5 !L$QEU2)r\@$D}$IEG1Ĉ D0L.(""0U q G1SliJ>b bQ$@SXfk KO:dJ SN1#k3Lp(8UcUZ ~0HT[2! "RA]QINU aQb-jfaP:l41)')23 !Y`kX_(녨d+@3jN(aB$ P%JөC`3FHƚFAd0%Gpj,%k}UF|>A':(3Z`)vvz;&;TXS05JjTeC/VEum)q2ZMR>(3zgIux0HբD*BsPT8@s. ~$”Ə{D BK42" .iJ#HMȈ :0@2 @lDfP}y+dƈt^ō7xhUNv/˯yk/Mv77pw:%[aŊC%wSO=rmwށuKQ_}Cr#[H kE;88g̘n,X7iٲeGy_ŋO<]wu '<|YfuN8j(٘-oy˺d]bo]N;3m-㾳$K䞙G,xǖ-[;׿?ODK,Yd ⡖xRޱ?O?Ϝ3gNq?^ԗ`|w +W'< Ƹm۶~?neU;7M:Bxg{^۷ܹ3z7oc~ o߾f͚5kرcZ]:vŊ7p o}Onٲe[lٱcǞ{ !lݺkضm[9P` "ow]jq:.\xwuN9y]wu}o~zK/o}?y?xONNs9W]uվInݺc=vι7xym߾}/[Gs=|+-Zt9ұ7򕯄.KGgz.\׾D~_̟?ժU't?;<_W<_~O=yO{/Xbʕ瞋/鮻m?)pE}Xr'>eg1֬gs} 6L?c~ݏ~_|'sґm۶+"nڴ .ԧ>/YUw^pN3|p2ZW#-X^>.9#ȧ?M7tI'_urrĞy3 <.[.]:[ۃN.]BPպo`.ё.謳cXھ꫏;QiWUUU7pB>$裏uqSO=uwujFoɟ7`tXkGZ[U:ꨑMo׷Q}{ȩzY_yuZ{ Fo|%[Gc\pnÃ,h]w֭[UuÆ =} U-rz.^xٲe#^ S4Oͯk6mUlbY|yjz)ݧ*a)boo|x≴u%KLwED^^W^z)-Z4gΜ}{\V^ |+G˲?ޛCk;|׻ޕGSO}{{9,\Νi{#G9f]-`#S\H8~7t$5kB#쪪.xP{>s>$wUUO|g}׾9s|;Y|oLTEQgptEFD{#8"mZEQ|gvg|&̦M͛l6w9B+?[nyN崽@]$Q1{#HUg>s-\K,y/'d|`Yfyrmw}o~_WG?3:e~1N8x衇BAbʕF#y\blٲ%sk⬳:c.|왤FS :,__h~_g?{뭷~ȹ?.^ 6bM69N:$xG>]s5s4[71/'Yks~4L#K.y_',^x˖-ig?"&賦_r1|GU5H.ڵk>oF#6m>vG oЇ>}mt)rϭ~{sإcΝ;o޼z nz~^s5׼5 #,>8S`1ވXjh{ٲeoO=T Ӭ7ׯgf>p?_{֭[/3 c̽{Ez"jժիW[kو{gҥ7n|Gg͚u.^sկ~/O֦K-Zlٲ;lڵ6lι{g9裏v}hnݚK^ubbBD?m۶SoQ_`t֯__MιONN?Je5\~+bk{TLf\v7gu/~k׎<]w݇?C(U&X~UUiQ^hѡ+ɌYkg28-$L&3Ndd2DڙL&3Ndd2DڙL&3Ndd2DڙL&3Ndd2DڙL&3Nu*nIENDB`PyLaTeX-1.4.2/docs/source/api.rst000066400000000000000000000006241451425216500165460ustar00rootroot00000000000000API reference ============= This section shows all of classes and functions this library exposes. The most important thing to remember when looking at the documentation, is that this library uses subclassing extensively. That is why you should always look at the parent classes if it seems like the class you are looking at is missing methods. .. toctree:: :maxdepth: 1 :glob: pylatex/* PyLaTeX-1.4.2/docs/source/changelog.rst000066400000000000000000000435131451425216500177300ustar00rootroot00000000000000Change Log ========== All notable changes to this project will be documented on this page. This project adheres to `Semantic Versioning `_. .. highlight:: bash Unreleased_ - `docs <../latest/>`__ ----------------------------------- See these docs for changes that have not yet been released and are only present in the development version. This version might not be stable, but to install it use:: pip install git+https://github.com/JelteF/PyLaTeX.git 1.4.2_ - `docs <../v1.4.2/>`__ - 2023-10-19 ------------------------------------------- Added ~~~~~ - Add `.Chapter` in ``__init__.py`` Fixed ~~~~~ - Fix installation on Python 3.12 Cleanup ~~~~~~~ - Update tooling (use black and isort and remove custom flake8 stuff) 1.4.1_ - `docs <../v1.4.1/>`__ - 2020-10-18 ------------------------------------------- Fixed ~~~~~ - Fixes filename generation with dots in the final filename on Windows. - Fixes regression in 1.4.0 where empty ``geometry_options`` would throw an error. 1.4.0_ - `docs <../v1.4.0/>`__ - 2020-09-16 ------------------------------------------- Added ~~~~~ - Add ``Fragment`` class which is a ``Container`` without any LaTeX code surrounding its content. Fixed ~~~~~ - Escape newlines in ``ContainerCommand`` - Fix bug where the geometry options were not applied in some cases 1.3.4_ - `docs <../v1.3.4/>`__ - 2020-07-29 ------------------------------------------- Fixed ~~~~~ - Use known working versions for Python 3.5 and lower of ordered-set dependency 1.3.3_ - `docs <../v1.3.3/>`__ - 2020-06-20 ------------------------------------------- Fixed ~~~~~ - The 'at' parameter for TikZNode should now work. - Use a different temporary directory per user. 1.3.2_ - `docs <../v1.3.2/>`__ - 2020-05-16 ------------------------------------------- Fixed ~~~~~ - On python 3.6+ support multhreaded use of PyLaTeX, by not calling ``os.chdir`` 1.3.1_ - `docs <../v1.3.1/>`__ - 2019-09-26 ------------------------------------------- Fixed ~~~~~ - Make labels/sections with weird characters work 1.3.0_ - `docs <../v1.3.0/>`__ - 2017-05-19 ------------------------------------------- Added ~~~~~ - Longtables now have end_foot() and end_last_foot() functions. - Added TikZ basic drawing functions for nodes and paths, with minimal coordinate support. - More section levels `.Part`, `.Chapter`, `.Paragraph`, `.Subparagraph`. - Add label and cross reference support. Changed ~~~~~~~ - More descriptive error when no compatible LaTeX compiler was found. Fixed ~~~~~ - ``latex_name`` is now fixed for the `.Document` class. This way you can safely subclass it. - Uncertain quantity objects work again. 1.2.1_ - `docs <../v1.2.1/>`__ - 2017-05-19 ------------------------------------------- Fixed ~~~~~ - Filenames with a ``~`` (tilde) in them now also work as figure paths. This caused issues when using temp directories on Windows. 1.2.0_ - `docs <../v1.2.0/>`__ - 2017-05-06 ------------------------------------------- Added ~~~~~ - Escape flag to `.Math` container - ``_star_latex_name`` attribute of `.LatexObject` to append a star - `.Alignat` math environment - `.Figure.add_plot` method looks for extension in kwargs - `.Tabu` and `.LongTabu` environments learn 'spread' and 'to' syntax to control their width. Fixed ~~~~~ - Escape ``[`` and ``]`` (left and right bracket). - Allow mappers of `~.dumps_list` to return a `~.LatexObject`. - Section numbering default behaviour fixed - Setter method for `~.LatexObject.escape` property added 1.1.1_ - `docs <../v1.1.1/>`__ - 2016-12-10 ------------------------------------------- Changed ~~~~~~~ - Installs from git now get installed as a special version based on the commit. This is done by using versioneer. - Releases can be done with much less manual work for the maintainer in the future. Fixed ~~~~~ - Install now works on python 3.6+ - Pypi installs will not fail anymore for python 2.7, when ``3to2`` and ``future`` were installed. 1.1.0_ - `docs <../v1.1.0/>`__ - 2016-12-09 ------------------------------------------- Changed ~~~~~~~ - Allow overriding of the default numbering of `.Section` class. - `.Parameters` now unpacks a dict as keyword arguments when passed a single dictionary as argument. - Escape generated ``\n`` characters by PyLaTeX by placing a ``%`` sign in front of them. - For better readability let `~.escape_latex` change a newline to ``\\%\n`` instead of simply ``\\``. - `.Document` packages now get propagated from the preamble elements as well. - Changed `.Figure.add_image` to add a `.StandAloneGraphic` - `.Tabular.add_row` now accepts a list of mappers - `.Tabular.add_row` now accepts cells as arguments, so they don't have to be wrapped in a `list` or `tuple` anymore. - Changed from using ``$$ ... $$`` for displaymath to using ``\[ ... \]``. Added ~~~~~ - Add the ``textcomp`` package by default. This way some special glyphs, like the Euro (€) Symbol can be used in the source. - `.Quantity` got a new ``options`` keyword argument and learned to handle uncertain quantities. - Added `.PageStyle` class to support the creation of various page styles. In addition to this class `.Head` and `.Foot` were added for creating unique headers and footers within the page styles. A `.simple_page_number` function was also added for easy displaying of a simple page number. - Added a new type of container `.ContainerCommand` for supporting commands with data. - Added new options to the `.Document` constructor: ``geometry_options`` (a list of options for the geometry package), ``document_options`` (a list of options to place in the document class), ``indent`` (an option to select whether the documents elements are indented), ``page_numbers`` (an option to choose whether to use page numbers or not), ``font_size`` (the font size to set at the beggining of the document). - Added several new methods to the `.Document`: ``change_page_style``, ``change_document_style``, ``add_color``, ``change_length``, ``set_variable``. - Added a new `.position` package with the following classes: `.Center` (an environment with centered content), `.FlushLeft` (an environment with left aligned content), `.FlushRight` (an environment with right aligned content), `.MiniPage` (a portion of the document with a certain width and height), `.TextBlock` (a portion of the document for which the position can be selected using x,y coordinates), `.VerticalSpace` and `.HorizontalSpace` (add space of a certain size by using vspace and hspace) - Added `.StandAloneGraphic` to support the creation of images outside of figure environments. - Added the ability to change the ``row_height`` of a table within the `.Tabular` constructor. - Added a new type of table `.Tabularx`. - Added the option to select a color when adding an hline or adding a row to any `~.Tabular` environment. - Added the ability to add your own column types through the `.ColumnType` class. - Added the ability to end the header of a `.LongTable` which repeats on every consecutive page. - Added the ability to choose the enumeration symbol in a list using the ``enumeration_symbol`` keyword argument of `.Enumerate`. - Added a `pylatex.basic` module with the following commands: `.NewLine`, `.NewPage`, `.LineBreak`, `.HFill`. - Added several environments to `pylatex.basic`: `.HugeText`, `.LargeText`, `.MediumText`, `.SmallText`, `.FootnoteText`, `.TextColor`. - `.Tabular` can now have a width specified to override the calculated width based on the ``table_spec`` argument. - Default configuration for certain options can be overwritten with the new `pylatex.config` module. - Add support for booktabs tables, which look nicer than normal tables. - Add support for the microtype package. Fixed ~~~~~ - Setting the ``lmodern`` keyword argument of `.Document` to false will not cause invalid LaTeX code anymore. - `.Quantity` now correctly splits prefix and unit into seperate commands. - `.Quantity` can now handle Celsius. - `.Package` instances now actually get deduplicated. 1.0.0_ - `docs <../v1.0.0/>`__ - 2015-11-25 ------------------------------------------- This realease brings some great changes. The whole package has been refactored and actual documentation has been added. Because of this, things have been moved an renamed. One of the most notable changes is that all normal text is now escaped by default. Changed ~~~~~~~ - The base_classes submodule has been split into multiple sub-submodules. - The old baseclasses have been renamed as well. They now have easier names that better show their purpose. - The command and parameters submodules have been merged into one command submodule in the base_classes submodule. - The numpy classes have been moved to the math submodule. - For all of the previous changes the old submodules and names should still work during the transition period, but they will be removed before the final release. - The ``Plt`` class has been merged with the `.Figure` class. Its `~.Figure.add_plot` method also doesn't take a plt argument anymore. The plt module is now imported when the `~.Figure.add_plot` method is used. This also allows for adding plots in the `.SubFigure` class. - Compiling is more secure now and it doesn't show output unless an error occurs or explicitly specified. - The internal method ``propegate_packages`` has been spelled correctly and made "internal" by adding an underscore in front of the name, resulting in ``_propagate_packages`` - The default allignment of a multicolumn is not ``c`` instead of ``|c|``, since vertical lines in tables are ugly most of the time. - Make the list method of `.Parameters` a private method. - Make the ``get_table_width`` function private. - Make ``width`` and ``placement`` keyword only arguments for the `~.Figure.add_plot` method. - The old ``Table`` class is renamed to `.Tabular`. A new `.Table` class has been created that represents the ``table`` LaTeX environment, which can be used to create a floating table. - Fixed a bug in the `.Document` class, that lead to an error if a filepath without basename was provided. - Fixed the testall.sh script such that sphinx and nosetests get called with the correct python version. - The graphics submodule has been renamed to figure. - The pgfplots submodule has been renamed to tikz. - Rename the ``seperate_paragraph`` keyword argument to the correctly spelled ``separate_paragraph``. - The ``container_name`` attribute has been changed to `~.LatexObject.latex_name` so it can be used more than containers. By default it is still the lowercase version of the classname. To change the default for a class you should set ``_latex_name`` - Made ``Document.select_filepath`` private. - `.Container` now has a `~.Container.dumps_content` method, which dumps it content instead of a dumps method. This allows to override just that method when subclassing `.Environment` so you can do dump in some special inside the environment, while still keeping the ``\begin`` and ``\end`` stuff provided by `.Environment`. - When subclassing a class and special LaTeX packages are needed, you now have to specify the packages class attribute instead of passing packages along with the ``__init__`` method. - Content of subclasses of `.Container` is now automatically escaped. Content of `.Arguments` or `.Options` is not escaped by default. - Made `~.LatexObject.separate_paragraph`, `~.LatexObject.begin_paragraph` and `~.LatexObject.end_paragraph` class attributes instead of instance attributes. - The default of the ``filepath`` argument for the `.Document.generate_pdf` and `.Document.generate_tex` have been changed to `None`. The response to the default is not changed, so this is a fairly invisible change. - Moved `~.LatexObject.separate_paragraph`, `~.LatexObject.begin_paragraph` and `~.LatexObject.end_paragraph` attributes to `.LatexObject`. - Use ``latexmk`` to compile to pdf when available, otherwise fallback to ``pdflatex``. - Change the order of arguments of the `.Axis` constructor. - Tables like `.Tabular` now raise an exception when rows with wrong size are added - Made lots of keyword arguments keyword only arguments. This was needed to make it easy to keep the API the same in the future. - Removed the submodules ``pylatex.parameters``, ``pylatex.command`` and ``pylatex.numpy``. The content of the first two was moved to ``pylatex.base_classes.command`` and the content of the last one was moved to ``pylatex.math``. Removed ~~~~~~~ - The add ``add_multicolumn`` and ``add_multirow`` methods on tabular classes are removed in favor of the much more robust and easier to use `.MultiRow` and `.MultiColumn` classes. - Removed unused ``name`` argument of the `.Matrix` class. - Removed base keyword argument of the `.Package` class. `.Command` should be used when changing of the base is needed. - Removed the ``title``, ``author``, ``date`` and ``maketitle`` arguments from the `.Document` constructor. They were from a time when it was not possible to change the preamble, which is now very easy. They are not so commonly used that they should be part of the main `.Document` object. - Removed useless list class constructor arguments for list_spec and pos. These were probably copied from the `.Tabular` class. Added ~~~~~ - Lots of documentation!!!!! - A float environment base class. - An unfinished Quantity class that can be used in conjunction with the quantitities package. https://pythonhosted.org/quantities/ - Allow supplying a mapper function to dumps\_list and the add\_row method for tabular like objects. - An ``extra_arguments`` argument to `.Command`. See docs for description. - Add `.CommandBase`, which can be easily subclassed for a command that is used more than once. - Add `.NoEscape` string class, which can be used to make sure a raw LaTeX string is not escaped. - A ``__repr__`` method, so printing LaTeX objects gives more useful information now. 0.8.0_ - 2015-05-23 ------------------- Added ~~~~~ - List classes (enumerate, itemize, description) - Arguments for plt.savefig - SubFigure class for use with subcaption package - Command line argument for ./testall.sh to supply a custom python command - The generate_tex method is now usable in every class, this makes making snippets even easier. - MultiColumn and MultiRow classes for generalized table layouts. Changed ~~~~~~~ - BaseLaTeXNamedContainer now uses the name of the class as the default container_name - The ``Table`` object is going to be deprecated in favor of the better named `.Tabular` object. This will take a couple of releases. - Allow the data keyword argument of containers to be a single item instead of a list. If this is the case it will be wrapped in a list on initialization. Fixed ~~~~~ - Propagate packages recursively add packages of sub containers - Make cleanup of files Windows compatible - Filenames can be paths (``foo/bar/my_pdf``). - Replace ``filename`` by ``filepath`` in the names of the arguments. - Matplotlib support now uses the tmpfile module, this fixes permission issues with the badly previously badly located tmp directory. - The temp directory is only removed in generate_pdf when cleaning is enabled 0.7.1_ - 2015-03-21 ------------------- Added ~~~~~ - Contributing guidelines. Changed ~~~~~~~ - The non keyword argument for filename is now called path instead of filename to show it can also be used with paths. - Travis now checks for Flake8 errors. Fixed ~~~~~ - Fix a bug in Plt and one in fix_filename that caused an error when using them with some filenames (dots in directories and a file without an extension) 0.7.0_ - 2015-03-17 ------------------- Added ~~~~~ - Matplotlib support - Quite a bit of basic docstrings Changed ~~~~~~~ - Filenames should now be specified to the `~.Document.generate_pdf`/`~.Document.generate_tex` methods of document. If this is not done the ``default_filename`` attribute will be used. Fixed ~~~~~ - Fix a lot of bugs in the `.escape_latex` function 0.6.1_ - 2015-01-11 ------------------- Added ~~~~~ - Travis tests Fixed ~~~~~ - Bug in VectorName 0.6_ - 2015-01-07 ----------------- Added ~~~~~ - Figure class - Command and Parameter classes - ``with`` statement support 0.5_ - 2014-06-02 ----------------- Added ~~~~~ - Python 2.7 support 0.4.2_ - 2014-03-18 ------------------- Added ~~~~~ - More table types 0.4.1_ - 2014-01-29 ------------------- Added ~~~~~ - Partial experimental support for multicol/multirow Fixed ~~~~~ - Fix package delegation with duplicate packages .. _Unreleased: https://github.com/JelteF/PyLaTeX/compare/v1.4.2...HEAD .. _1.4.2: https://github.com/JelteF/PyLaTeX/compare/v1.4.1...1.4.2 .. _1.4.1: https://github.com/JelteF/PyLaTeX/compare/v1.4.0...1.4.1 .. _1.4.0: https://github.com/JelteF/PyLaTeX/compare/v1.3.4...1.4.0 .. _1.3.4: https://github.com/JelteF/PyLaTeX/compare/v1.3.3...1.3.4 .. _1.3.3: https://github.com/JelteF/PyLaTeX/compare/v1.3.2...1.3.3 .. _1.3.2: https://github.com/JelteF/PyLaTeX/compare/v1.3.1...1.3.2 .. _1.3.1: https://github.com/JelteF/PyLaTeX/compare/v1.3.0...1.3.1 .. _1.3.0: https://github.com/JelteF/PyLaTeX/compare/v1.2.1...1.3.0 .. _1.2.1: https://github.com/JelteF/PyLaTeX/compare/v1.2.0...v1.2.1 .. _1.2.0: https://github.com/JelteF/PyLaTeX/compare/v1.1.1...v1.2.0 .. _1.1.1: https://github.com/JelteF/PyLaTeX/compare/v1.1.0...v1.1.1 .. _1.1.0: https://github.com/JelteF/PyLaTeX/compare/v1.0.0...v1.1.0 .. _1.0.0: https://github.com/JelteF/PyLaTeX/compare/v0.8.0...v1.0.0 .. _0.8.0: https://github.com/JelteF/PyLaTeX/compare/v0.7.1...v0.8.0 .. _0.7.1: https://github.com/JelteF/PyLaTeX/compare/v0.7.0...v0.7.1 .. _0.7.0: https://github.com/JelteF/PyLaTeX/compare/v0.6.1...v0.7.0 .. _0.6.1: https://github.com/JelteF/PyLaTeX/compare/v0.6...v0.6.1 .. _0.6: https://github.com/JelteF/PyLaTeX/compare/v0.5...v0.6 .. _0.5: https://github.com/JelteF/PyLaTeX/compare/v0.4.2...v0.5 .. _0.4.2: https://github.com/JelteF/PyLaTeX/compare/v0.4.1...v0.4.2 .. _0.4.1: https://github.com/JelteF/PyLaTeX/compare/68ddef6bc43a5dff42105c3a38068d87d99d049f...v0.4.1 PyLaTeX-1.4.2/docs/source/conf.py000066400000000000000000000337621451425216500165530ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # PyLaTeX documentation build configuration file, created by # sphinx-quickstart on Thu Jun 18 15:35:21 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # Needed for old sphinx version to work import collections import sys if sys.version_info >= (3, 10): collections.Callable = collections.abc.Callable import inspect import os import sphinx_rtd_theme # 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("../../")) from pylatex import __version__ # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # 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", ] napoleon_include_special_with_doc = False numpydoc_show_inherited_class_members = False numpydoc_class_members_toctree = False # Add any paths that contain templates here, relative to this directory. 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" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = "index" # General information about the project. 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") # The full version, including alpha/beta/rc tags. release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # 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" def auto_change_docstring(app, what, name, obj, options, lines): r"""Make some automatic changes to docstrings. Things this function does are: - 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) * "=") lines.insert(0, name) hits = 0 for i, line in enumerate(lines.copy()): 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] 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"], } members["all"] = list(set([req for reqs in members.values() for req in reqs])) if name in members["all"]: skip = True if cls == "LatexObject": return False if cls in ("Container", "Environment") and name in members["container"]: return False if cls == "Document" and name == "generate_tex": return False if name == "separate_paragraph" and cls in ("SubFigure", "Float"): return False # Ignore all functions of NoEscape, since it is inherited if cls == "NoEscape": return True return 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) 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 else: base_url += "blob/v%s/" % version filename = module_name.replace(".", "/") + ".py" module = sys.modules.get(module_name) # Get the actual object try: actual_object = module for obj in fullname.split("."): parent = actual_object actual_object = getattr(actual_object, obj) except AttributeError: return None # Fix property methods by using their getter method if isinstance(actual_object, property): actual_object = actual_object.fget # Try to get the linenumber of the object try: source, start_line = inspect.getsourcelines(actual_object) except TypeError: # If it can not be found, try to find it anyway in the parents its # source code parent_source, parent_start_line = inspect.getsourcelines(parent) for i, line in enumerate(parent_source): if line.strip().startswith(attribute_name): start_line = parent_start_line + i end_line = start_line break else: return None else: end_line = start_line + len(source) - 1 line_anchor = "#L%d-L%d" % (start_line, end_line) return base_url + filename + line_anchor # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. default_role = "py:obj" # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = False # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. modindex_common_prefix = ["pylatex."] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. 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"), } # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. 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 # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # 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" # 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"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr' # html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # Now only 'ja' uses this config value # html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. 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', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (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 # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # 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)] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (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", ), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False PyLaTeX-1.4.2/docs/source/contributing.rst000066400000000000000000000105051451425216500205030ustar00rootroot00000000000000How to contribute ================= .. highlight:: bash First of all, if anything is incorrect or something is missing on this page (or any other for that matter), please send in a pull request. It is important that setting up the development environment is as painless as possible. Setting up the development environment -------------------------------------- Unfortunately there are quite some steps involved in setting up a development environment. If you don't want to do this and know how Vagrant works, see the bottom of this section on how to use that instead. OS specific dependencies ~~~~~~~~~~~~~~~~~~~~~~~~ Some dependencies are OS specific. Ofcourse you need to have LaTeX installed, but that also comes in some different packages on most systems. For Ubuntu and other Debian based systems:: sudo apt-get install python3 python3-dev virtualenv \ texlive-pictures texlive-science texlive-latex-extra \ imagemagick Getting the source code ~~~~~~~~~~~~~~~~~~~~~~~ You need your own fork of the `Github repository `_ by using the Github fork button. You will then need to clone your version of the repo using the normal way, something like this:: git clone git@github.com:YourUserName/pylatex cd pylatex Make your own branch for your specific feature or fix (don't do this just on master):: git checkout -b your-nice-feature Python environment setup ~~~~~~~~~~~~~~~~~~~~~~~~ This method will use a virtual environment, this is the easiest way to get all the dependencies. 1. Create a virtualenv by running:: virtualenv venv -p python3 2. Activate it by running (you should do this whenever you start working on your changes):: . venv/bin/activate 3. Install all the development dependencies inside the virtual environment by running:: pip install -r dev_requirements.txt Vagrant support ~~~~~~~~~~~~~~~ This might be an easier way to obtain a development environment, but the script is not very well maintained and might not work anymore. If everything goes as planned Vagrant will launch and configure a small virtual machine with all necessary tools for you, so that you can start working with PyLaTeX right away. With Vagrant already installed, you can start the virtual machine with ``$ vagrant up`` and then use ``$ vagrant ssh`` to ssh into it. Your source files will be located under ``/vagrant``. To run all unit tests and build the documentation run ``$ ./testall.sh -p python3 -c`` from that directory. You can download or read more about Vagrant on https://www.vagrantup.com/. Some tips before starting ------------------------- 1. Look at the code that is already there when creating something new, for instance the classes for tables. 2. To learn how to squash commits, read this `blog `_. Ignore the word of caution, since that only applies to main repositories on which people base their own work. You can do this when you have a couple of commits that could be merged together. This mostly happens when you have commits that fix a typo or bug you made in a pull request and you fix that in a new commit. Some rules ---------- There are two things that are needed for every pull request: 1. Run the ``testall.sh`` script before making a pull request to check if you didn't break anything. 2. Follow the **PEP8** style guide and make sure it passes pyflakes (this is also tested with the ``testall.sh`` script). These are also tested for by Travis, but please test them yourself as well. Depending on your type of changes some other things are needed as well. 1. If you add new arguments, function or classes, add them to ``tests/args.py`` without forgetting to name the arguments. That way it is easy to see when the external API is changed in the future. 2. Change docstrings when necessary. For instance when adding new arguments or changing behaviour. 3. If you fix something, add a **test** so it won't break again. 4. If your change is user facing, add it to the **changelog** so it will be mentioned in the next release. Its location is at ``docs/source/changelog.rst``. 5. If you add something new, show it off with an **example**. If you don't do this, I will probably still merge your pull request, but it is always nice to have examples of features. PyLaTeX-1.4.2/docs/source/examples.rst000066400000000000000000000003571451425216500176160ustar00rootroot00000000000000Examples ======== The examples below show some of the different uses of PyLaTeX. They show how to use some of the modules. For all the uses and modules please see the :doc:`api`. .. toctree:: :maxdepth: 1 :glob: examples/* PyLaTeX-1.4.2/docs/source/examples/000077500000000000000000000000001451425216500170575ustar00rootroot00000000000000PyLaTeX-1.4.2/docs/source/examples/.keep000066400000000000000000000000001451425216500177720ustar00rootroot00000000000000PyLaTeX-1.4.2/docs/source/faq.rst000066400000000000000000000010531451425216500165410ustar00rootroot00000000000000Frequently Asked Questions ========================== .. highlight:: bash How do I... ----------- ... "Rerun LaTeX" Sometimes the compiler will not be able to complete the document in one pass. In this case it will instruct you to "Rerun LaTeX" via the log output In order to deal with this, you need to make sure that `latexmk `_ is installed. Pylatex will detect and use it automatically. For Ubuntu and other Debian based systems:: sudo apt-get install latexmk PyLaTeX-1.4.2/docs/source/index.rst000066400000000000000000000052571451425216500171130ustar00rootroot00000000000000.. PyLaTeX documentation master file, created by sphinx-quickstart on Thu Jun 18 15:35:21 2015. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. PyLaTeX ======= PyLaTeX is a Python library for creating and compiling LaTeX files. The goal of this library is to be an easy, but extensible interface between Python and LaTeX. PyLaTeX has two quite different usages: generating full pdfs and generating LaTeX snippets. Generating full pdfs is mostly useful when all the text that pdf should contain is generated by python, for instance exporting the data from a database. Snippets are useful when some text still needs to be written by hand, but some stuff can be automatically generated, for instance writing a report with a couple of matplotlib plots. Installation ------------ .. highlight:: bash PyLaTeX works on Python 2.7 and 3.3+ and it is simply installed using pip: :: pip install pylatex Some of the features require other libraries as well. This is mostly the case when converting a datatype of that library to LaTeX. For instance, generating LaTeX matrices requires Numpy. The dependencies for these extra features can simply be installed like this: :: pip install pylatex[matrices] The features that require additional libraries are: - matrices - matplotlib - quantities Code ---- To see the some code in action, please take a look at the :doc:`examples/full`, which generates the pdf below. To understand how the code works, please look at the :doc:`usage`. .. image:: /_static/screenshot.png Support ------- This library is being developed in and for Python 3. Because of a conversion script the current version also works in Python 2.7. For future versions, no such promise will be made. Python 3 features that are useful but incompatible with Python 2 will be used. If you find a bug for Python 2 and it is fixable without ugly hacks feel free to send a pull request. This library is developed for Linux. I have no intention to write fixes or test for platform specific bugs with every update, especially since I have no other operating systems to test it on. Pull requests that fix those issues are always welcome though. Issues have been fixed for Windows and it seems that compiling to pdf is currently working. Contributing ------------ Read the :doc:`contributing` page for tips and rules when you want to contribute. To just see the source code, you should go to the `Github repository `_. .. toctree:: :maxdepth: 1 :glob: :hidden: usage examples api faq changelog contributing Indices ------- * :ref:`genindex` * :ref:`modindex` PyLaTeX-1.4.2/docs/source/pylatex/000077500000000000000000000000001451425216500167275ustar00rootroot00000000000000PyLaTeX-1.4.2/docs/source/pylatex/.keep000066400000000000000000000000001451425216500176420ustar00rootroot00000000000000PyLaTeX-1.4.2/docs/source/quantities-inv.txt000066400000000000000000000001731451425216500207630ustar00rootroot00000000000000# Sphinx inventory version 1 # Project: quantities # Version: 0.10.0 quantities.quantity.Quantity class user/tutorial.html PyLaTeX-1.4.2/docs/source/usage.rst000066400000000000000000000104601451425216500171000ustar00rootroot00000000000000Library usage ============= Understanding PyLaTeX --------------------- PyLaTeX is structured around two main tasks: Generating LaTeX code, and compiling LaTeX documents. The package is flexible, and can either work with your pre-existing code or generate new code with its system of classes. In turn, LaTeX code can be used to generate a document, or can be exported as-is. The Classes ----------- PyLaTeX uses a set of classes to turn LaTeX document generation into a set of pythonic components. For example, a `~.Document` might be comprised of `~.Section` objects, which in turn might have `~.List` objects, `~.Figure` objects, or custom `~.Command` objects. Classes can be part of a single document, or can act as pieces on their own. With the `~.LatexObject.dumps` method, most classes can return their LaTeX-formatted code, and with the `~.LatexObject.generate_tex` method, this code can be written to a file. Containers / Documents ~~~~~~~~~~~~~~~~~~~~~~ A `~.Container` is an object that groups other LaTeX classes. Containers function like lists; they can be indexed and appended to. One of the most important container classes is the `~.Document` class. Documents create a full LaTeX document that can create a PDF file with `~.generate_pdf` . Unless you are only generating LaTeX snippets, you will likely want to enclose your code inside a Document. Additionally, a number of `~pylatex.section` containers are available, which correspond to the standard ``\section`` commands of LaTeX. As with documents, these can be appended to. A `~.Section` can further include a `~.Subsection` or a `~.Subsubsection` object. Tables, Images, Math, etc. ~~~~~~~~~~~~~~~~~~~~~~~~~~ PyLaTeX has a number of classes that are useful in generating difficult-to-format LaTeX code. See the API documentation and code examples for information on a specific environment. Commands, Options, and Arguments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Although PyLaTeX has implemented many useful commands, it is easy to create a custom command with the `~.Command` class. Commands can be supplied with ``{}`` arguments or ``[]`` options, with either a single option as a string, or multiple options in a list. Additionally, Options and Arguments can be placed in an `~.Options` object or a `~.Arguments` object. Formatting Strings ~~~~~~~~~~~~~~~~~~ A number of functions are available in `~.utils` that are helpful in formatting text. For example, the functions `~.bold` and `~.italic` exist to format text appropriately. Extending PyLaTeX ----------------- Because of all the base classes supplied by PyLaTeX, it is very easy to extend its support in LaTeX features. Just pick one of the existing (base) classes that fits best and extend that with the needed functionality. All LaTeX objects come from `~.LatexObject` , but it is probably more useful to extend one of the other base subclasses, like `~.Environment` or `~.CommandBase`. Consult the API documentation to see the variety of base classes available for use. Plain LaTeX Strings ------------------- Although PyLaTeX contains classes and functions to make generating LaTeX formatted text easy, at its core it is a nice wrapper around string manipulations. This is why all of them also accept raw LaTeX strings. That way you can just use regular LaTeX strings when something is not supported directly by the library. Unescaping Strings ~~~~~~~~~~~~~~~~~~ Using regular LaTeX strings may not be as simple as is seems though, because by default almost all strings are escaped. This is done for security reasons and to make sure valid LaTeX code will be generated at all times. However, there are cases where raw LaTeX strings should just be used directly in the document. This is why the `~.NoEscape` string type exists. This is just a subclass of `str`, but it will not be escaped. One important thing to note about this class is that appending a `~.NoEscape` type string to a regular string results in a regular string, since one type has to be chosen and the most conservative approach is taken. Another way to make sure strings are not escaped is by setting the `~.LatexObject.escape` attribute to `False` on the class that is the container of the string. Keep in mind though that any strings that are added to that object will not be escaped when doing this. So, only use this method for objects that don't contain possibly unsafe strings. PyLaTeX-1.4.2/examples/000077500000000000000000000000001451425216500146275ustar00rootroot00000000000000PyLaTeX-1.4.2/examples/basic.py000066400000000000000000000027641451425216500162730ustar00rootroot00000000000000#!/usr/bin/python """ This example shows basic document generation functionality. .. :copyright: (c) 2014 by Jelte Fennema. :license: MIT, see License for more details. """ # begin-doc-include from pylatex import Command, Document, Section, Subsection from pylatex.utils import NoEscape, italic def fill_document(doc): """Add a section, a subsection and some text to the document. :param doc: the document :type doc: :class:`pylatex.document.Document` instance """ with doc.create(Section("A section")): doc.append("Some regular text and some ") doc.append(italic("italic text. ")) with doc.create(Subsection("A subsection")): doc.append("Also some crazy characters: $&#{}") if __name__ == "__main__": # Basic document doc = Document("basic") fill_document(doc) doc.generate_pdf(clean_tex=False) doc.generate_tex() # Document with `\maketitle` command activated doc = Document() doc.preamble.append(Command("title", "Awesome Title")) doc.preamble.append(Command("author", "Anonymous author")) doc.preamble.append(Command("date", NoEscape(r"\today"))) doc.append(NoEscape(r"\maketitle")) fill_document(doc) doc.generate_pdf("basic_maketitle", clean_tex=False) # Add stuff to the document with doc.create(Section("A second section")): doc.append("Some text.") doc.generate_pdf("basic_maketitle2", clean_tex=False) tex = doc.dumps() # The document as string in LaTeX syntax PyLaTeX-1.4.2/examples/basic_inheritance.py000066400000000000000000000025541451425216500206410ustar00rootroot00000000000000#!/usr/bin/python """ This example shows basic document generation functionality by inheritance. .. :copyright: (c) 2017 by Matthias Brandt. :license: MIT, see License for more details. """ # begin-doc-include from pylatex import Command, Document, Section, Subsection from pylatex.utils import NoEscape, italic class MyDocument(Document): def __init__(self): super().__init__() self.preamble.append(Command("title", "Awesome Title")) self.preamble.append(Command("author", "Anonymous author")) self.preamble.append(Command("date", NoEscape(r"\today"))) self.append(NoEscape(r"\maketitle")) def fill_document(self): """Add a section, a subsection and some text to the document.""" with self.create(Section("A section")): self.append("Some regular text and some ") self.append(italic("italic text. ")) with self.create(Subsection("A subsection")): self.append("Also some crazy characters: $&#{}") if __name__ == "__main__": # Document doc = MyDocument() # Call function to add text doc.fill_document() # Add stuff to the document with doc.create(Section("A second section")): doc.append("Some text.") doc.generate_pdf("basic_inheritance", clean_tex=False) tex = doc.dumps() # The document as string in LaTeX syntax PyLaTeX-1.4.2/examples/chequeexample.png000066400000000000000000000271461451425216500201750ustar00rootroot00000000000000PNG  IHDR0FٌsRGBgAMA a pHYsod-IDATx^aFQ+x2O&ɕa}nС/^T~Z7zŭ~K};ڮ4\i7տ]i߯ [muz..s.+N.+N7/9+5=/G^F_?׷sJÕ~_w]/hwizw^k}l^9;_ыN7+g]_\jWS/t"vn]i0gz1Ý|ӯ0_M=;ե.ܯgkV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~ځ__?k_DǗS;Ͽ?׿U˩EW\/]83RA␇Gw(W #a6'dnj~Z8Nď{2H|hvAxjj~y~mx$>G! ԉmhr~/$FtG< q!Hub5Ǻ=pt_4H\?p\8 ԉqA $:ځ˃/$$%Hub5>5ޟw.<nGv?svc$.7z7o}oyqo6~Qx*#px#A}}PWoOyW}Χv ?t~G엗=s͝;?M}ڸ#䇾|jjrs#GN߼f7$us1>ͧ/8{?]ub5Ǹ9\~ř{};79s)^vD >oyշ_~jjqs3'ڏ{է/ț.H|` on~^~jjqs@ _y煟7?3x۞;7q7?#o{A/8ơA]|'o|x={ix࡞ ]Mjjqs|3Wh_?ga#n{>/  ~{XM1_r&/H~šNL< o8 \U'?p8~O|?~|gw$":ځ]ioO_vA{>O<îggo>/\Mjju}ͣǧvםh<O?yExk}ī_XjjvsgǗ棂ěG{~ny9Aͷy/v:ځ9ܿ{|  n_9̹IAo|>K|_~vx}('ڏ<П=pqdn {='ct>5?ͣGϓ_~?3}]O.>LT?VSS;ӽw!ϼ\?z oٿQ{c~|/}7 Nvc:I> H$qj <ĩNvc:I> H$qj <ĩ`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;%?OߝgK~蟾;{p{p{p{p{p{p{p{p{p{p{p{p{p{p{p{p{p{p{p{p{p{p{p{p{p{p{p{Pkp{p{Pkp8A8 N'H$  pp8A8 N'H$  pp8A8 N'H$  pp8A8 N'H$  pp8A8 N'H$  pp8A8 N'H$  pp8A8 N'H$  pp8A8 N'H$  pp8A8 N'H$  pp8A8 N'H$  pp8A8 N'H$  pp8A8 N'H$  pp8A8 N'H$  pp8A8 N'H$  pp8A8 N'H+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?Lk凩`0vV~S;ZajX+?LKU`İIENDB`PyLaTeX-1.4.2/examples/config.py000066400000000000000000000043351451425216500164530ustar00rootroot00000000000000#!/usr/bin/python """ This example shows basic document generation functionality. .. :copyright: (c) 2016 by Jelte Fennema. :license: MIT, see License for more details. """ # begin-doc-include import pylatex.config as cf from pylatex import Document, NoEscape lorem = """ Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Phasellus facilisis tortor vel imperdiet vestibulum. Vivamus et mollis risus. Proin ut enim eu leo volutpat tristique. Vivamus quam enim, efficitur quis turpis ac, condimentum tincidunt tellus. Praesent non tellus in quam tempor dignissim. Sed feugiat ante id mauris vehicula, quis elementum nunc molestie. Pellentesque a vulputate nisi, ut vulputate ex. Morbi erat eros, aliquam in justo sed, placerat tempor mauris. In vitae velit eu lorem dapibus consequat. Integer posuere ornare laoreet. Donec pellentesque libero id tempor aliquam. Maecenas a diam at metus varius rutrum vel in nisl. Maecenas a est lorem. Vivamus tristique nec eros ac hendrerit. Vivamus imperdiet justo id lobortis luctus. Sed facilisis ipsum ut tellus pellentesque tincidunt. Mauris libero lectus, maximus at mattis ut, venenatis eget diam. Fusce in leo at erat varius laoreet. Mauris non ipsum pretium, convallis purus vel, pulvinar leo. Aliquam lacinia lorem dapibus tortor imperdiet, quis consequat diam mollis. Praesent accumsan ultrices diam a eleifend. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse accumsan orci ut sodales ullamcorper. Integer bibendum elementum convallis. Praesent accumsan at leo eget ullamcorper. Maecenas eget tempor enim. Quisque et nisl eros. """ def main(): cf.active = cf.Version1() doc = Document(data=NoEscape(lorem)) doc.generate_pdf("config1_with_indent", clean_tex=False) cf.active = cf.Version1(indent=False) doc = Document(data=NoEscape(lorem)) doc.generate_pdf("config2_without_indent", clean_tex=False) with cf.Version1().use(): doc = Document(data=NoEscape(lorem)) doc.generate_pdf("config3_with_indent_again", clean_tex=False) doc = Document(data=NoEscape(lorem)) doc.generate_pdf("config4_without_indent_again", clean_tex=False) if __name__ == "__main__": main() PyLaTeX-1.4.2/examples/environment_ex.py000066400000000000000000000027651451425216500202530ustar00rootroot00000000000000#!/usr/bin/python """ Wrapping existing LaTeX environments with the Environment class. .. :copyright: (c) 2014-2016 by Jelte Fennema, Scott Wallace :license: MIT, see License for more details. """ # begin-doc-include from pylatex import Document, Section from pylatex.base_classes import Environment from pylatex.package import Package from pylatex.utils import NoEscape class AllTT(Environment): """A class to wrap LaTeX's alltt environment.""" packages = [Package("alltt")] escape = False content_separator = "\n" # Create a new document doc = Document() with doc.create(Section("Wrapping Latex Environments")): doc.append( NoEscape( r""" The following is a demonstration of a custom \LaTeX{} command with a couple of parameters. """ ) ) # Put some data inside the AllTT environment with doc.create(AllTT()): verbatim = ( "This is verbatim, alltt, text.\n\n\n" "Setting \\underline{escape} to \\underline{False} " "ensures that text in the environment is not\n" "subject to escaping...\n\n\n" "Setting \\underline{content_separator} " "ensures that line endings are broken in\n" "the latex just as they are in the input text.\n" "alltt supports math: \\(x^2=10\\)" ) doc.append(verbatim) doc.append("This is back to normal text...") # Generate pdf doc.generate_pdf("environment_ex", clean_tex=False) PyLaTeX-1.4.2/examples/full.py000077500000000000000000000060101451425216500161430ustar00rootroot00000000000000#!/usr/bin/python """ This example demonstrates several features of PyLaTeX. It includes plain equations, tables, equations using numpy objects, tikz plots, and figures. .. :copyright: (c) 2014 by Jelte Fennema. :license: MIT, see License for more details. """ # begin-doc-include import os import numpy as np from pylatex import ( Alignat, Axis, Document, Figure, Math, Matrix, Plot, Section, Subsection, Tabular, TikZ, ) from pylatex.utils import italic if __name__ == "__main__": image_filename = os.path.join(os.path.dirname(__file__), "kitten.jpg") geometry_options = {"tmargin": "1cm", "lmargin": "10cm"} doc = Document(geometry_options=geometry_options) with doc.create(Section("The simple stuff")): doc.append("Some regular text and some") doc.append(italic("italic text. ")) doc.append("\nAlso some crazy characters: $&#{}") with doc.create(Subsection("Math that is incorrect")): doc.append(Math(data=["2*3", "=", 9])) with doc.create(Subsection("Table of something")): with doc.create(Tabular("rc|cl")) as table: table.add_hline() table.add_row((1, 2, 3, 4)) table.add_hline(1, 2) table.add_empty_row() table.add_row((4, 5, 6, 7)) a = np.array([[100, 10, 20]]).T M = np.matrix([[2, 3, 4], [0, 0, 1], [0, 0, 2]]) with doc.create(Section("The fancy stuff")): with doc.create(Subsection("Correct matrix equations")): doc.append(Math(data=[Matrix(M), Matrix(a), "=", Matrix(M * a)])) with doc.create(Subsection("Alignat math environment")): with doc.create(Alignat(numbering=False, escape=False)) as agn: agn.append(r"\frac{a}{b} &= 0 \\") agn.extend([Matrix(M), Matrix(a), "&=", Matrix(M * a)]) with doc.create(Subsection("Beautiful graphs")): with doc.create(TikZ()): plot_options = "height=4cm, width=6cm, grid=major" with doc.create(Axis(options=plot_options)) as plot: plot.append(Plot(name="model", func="-x^5 - 242")) coordinates = [ (-4.77778, 2027.60977), (-3.55556, 347.84069), (-2.33333, 22.58953), (-1.11111, -493.50066), (0.11111, 46.66082), (1.33333, -205.56286), (2.55556, -341.40638), (3.77778, -1169.24780), (5.00000, -3269.56775), ] plot.append(Plot(name="estimate", coordinates=coordinates)) with doc.create(Subsection("Cute kitten pictures")): with doc.create(Figure(position="h!")) as kitten_pic: kitten_pic.add_image(image_filename, width="120px") kitten_pic.add_caption("Look it's on its back") doc.generate_pdf("full", clean_tex=False) PyLaTeX-1.4.2/examples/header.py000066400000000000000000000031431451425216500164320ustar00rootroot00000000000000#!/usr/bin/python """ This example shows the functionality of the PageHeader object. It creates a sample page with the different types of headers and footers. .. :copyright: (c) 2016 by Vladimir Gorovikov :license: MIT, see License for more details. """ # begin-doc-include from pylatex import ( Document, Foot, Head, LargeText, LineBreak, MediumText, MiniPage, PageStyle, simple_page_number, ) from pylatex.utils import bold def generate_header(): geometry_options = {"margin": "0.7in"} doc = Document(geometry_options=geometry_options) # Add document header header = PageStyle("header") # Create left header with header.create(Head("L")): header.append("Page date: ") header.append(LineBreak()) header.append("R3") # Create center header with header.create(Head("C")): header.append("Company") # Create right header with header.create(Head("R")): header.append(simple_page_number()) # Create left footer with header.create(Foot("L")): header.append("Left Footer") # Create center footer with header.create(Foot("C")): header.append("Center Footer") # Create right footer with header.create(Foot("R")): header.append("Right Footer") doc.preamble.append(header) doc.change_document_style("header") # Add Heading with doc.create(MiniPage(align="c")): doc.append(LargeText(bold("Title"))) doc.append(LineBreak()) doc.append(MediumText(bold("As at:"))) doc.generate_pdf("header", clean_tex=False) generate_header() PyLaTeX-1.4.2/examples/kitten.jpg000066400000000000000000001177331451425216500166430ustar00rootroot00000000000000JFIFqA8O\=+R[߼Lm8$qߖ*Gly}9k۵xQwWm4ϥ}KNN;R9 9:$ d9wyΘ63Ϡ'R_M6fn::}?S޳Cێ:r"}0}=\TzXӡw_ҰxY$Gothݭ/>?>E`gv֨dsW>'}q>߅Z@==~}kA]_&i%ܽ;V|;1?9?C`c?cלJϧᚔwx楤՚вB׏o f۟lȥrEǯL26?ę=y'9N+9Y;$NϸӯҨ8}C>;!}}orzg?ϵtF+oCն"{q~UXb_`dǰִ=J^OӨ֔EmyuSѥ͆zz>njT}qZ+ OϧoC1?OL=W3쓷O=~zi"W8 cߟN+2Wg'z++n# GtzԌ'{J ;~A_/ԪIn>}6 :ϯj=;q UV?󮘫ׯ=]T[ׯ_\@q׃9N18c5<v}hj? lc\p4ݧ88r?_wL:qvݾK4$| }:#':1gþ?Ch.Jkwo+ۿBZ]ww'Ulz7 K63r85/Ц%K^yB?' x^/zhzwmC)VRnVi+Y>_?"GJiIIN\/("f?P1נǯGW>/sr>`$g' [ZIwh*a 8kNTv̗޾iti$_78`3{{ɪ_2IKUѱ1dm/={p1Xdr;uӮEv)FVOo0^[Z~_3>+z[Nqge'oO}4"|ױq뫜!8]mӯV:$W39Dg{0I})LeRKklbA*|0=I?\[-mgRxzDQ=٠yf獶yң7)i_]V).7M5-?1=>|@`3JIdN@9>çzl|=5汦L-WKv~vpy) ӳ_z 4Ӻ7t[D2Yi\Dյ2˓vZ~|$9+(}I&,^GsԳs 5n`<)5KLڜJ,Yۿ{;Key(Y6z`~wqPwkվtVZ;6Z+-:Duvx" F3x8ይN+HQw]ʑE4M! 呂TV 1>wUᾶ%' 2:Oҽß k{Qӧ[J,Z&!%r{qаfNUݿǥZNO/m+8`[aR^K9{q.bbVmQ<8#z{5?D46#*`xq ٭2n:Ld Hw؊Ae}t}n۾ԸmJZliWY~?"} ^ׯ L6\$zgϞIsx|xDV+]q3o¿Vi K(6"NSBzs^][Ka9"Mj^Ub"n8{?ĈBsqzr3?ڿe6=vo ,fJ6>YWq qh$w~WVm䁬0 ۩#5?%@B6`A''qYKybd:^ռ7{>ZKmqHOFG9S ZExJ2iէ^J'MX<Zs#]AGӱ\)C^ȑDhU av8*?'#=x?Os^b5gm:w_0JKe3T!~\Lt~^?/L~2=?qZ^2J/u2?}OLg}C/0rHkv5]_QСT 8yܵ\N%ewnD.kF쥫OmYuv[TVA\gm(6 g=n۞-ڬ?ppWiaQzպܲkm;k{빂eemodyB m8^8~{φ6HE [S2klp8>`)= k^#g"nHa{p{`bN~\wZm ;C. A@AO9$ts;v&ZMuW&T0s8VK:ꭵmρ~- 7dA'~w[[ѧmgݢDG ^e1ǭ~h>7EYʡ'Gv8=H<,< }dPZ%YUX9 =<¼҂y^unD,W3k{8KxIaK9,-pqk/~P $ }~~VyAXJbrA8+:gw Lts:Vu]dOZ;:4>nIUIdWmvς #@[;;*0$ f 0+p@'#?Ry$ҢxX5T0v"1<ڳҼ5mI‚|6VAqPGiWK̄e$gABx(}9um7I_ݷfmq=4%K-ߗ~m:m&*3zH䝱)H=yU?H5}%ؿvDl:,Νp <:|bڦ #p@'u KàcӾ u 13Zq;.)+-zyQsM\﷞M_mYkZia>l{Hhr<2Aw ϦxƏb-XH3w<czhӢEXЂFp?Ihڅʈ]s@kTZ9+3lUPnх*oA03SOEgz0kqFH$ri2Ȓ?, H(|u%m7K$)l8XG=х|*5j,Bi}ʥV~n}$J3k%(Ǧ\mҾNÖH#&El\FH`x^]oGwt1 zwE""6ti1.(8qɯcsIz6@a\^CyCI_]vziS'z7}jsX}6̓) .d0$gK⇂[QԲ=k"u9S̍ehv_9a+߉vTqn8@I>G=,J*$M}7yf;/SI>g䖪]&s(hC!*wml9##sZڍSL!$wF1=;M;hw2*B$O4C'ATN: [zi \1 w<08VJ)A.f]m,M|ty4]=>.?hF6#\8rH cz[ZcnPgYyUYZH?X L˶[K " qOoR{Q;P5VZ5%퓮 uog JN.nDr~t^!J mY޻_gs#2?O|z~&tw.鍛6H'ڦUux=lC+IFBz秢izU՜vt$22 WdcpA=(-=IzM\U'7yI{ܻ=wz$I[a.ndLBj7&Mf$EyS 8\d.m.a%J#d֭L- %4R K/Ωc JM q3Sh=]L&*˚ϴ~[D.gO:8?}o=:v}].ɟ\ivvIEp Oa=Mc ܅J3xoQ5~sC)oA?AnRITb3瓃>cZ\]&=iwu,x #1s6gwZtХ-nh1H?,pLVU,4II6Wҳ﫶=,i T(ϖRnz^ޖTơ5`AfH=>1GOH,m sO[~*ʼ*H;y#y<_\87׍",QBCKMpcq8|s >ML8Hֿmm|Q:^E-sľ!|KHmo0IQ㓐0zӟ3뺷 ͕K*h\#GmNpE*$s=>O^&xyg}O-5C%$ Zs79SMJZ\rVE{7m\+ihjiեώ8軰:3z瑓q0}j ;{c=>}RgY5c8O}ϯ|q vC>99۱??zJ qOO^8A8@?֪n`>kX-6hsNWz4ג~;om_ ' xR gK$n0<`8 pӵGV$g˰F~08']_gkIoi|uI,j,r:|^mwVJl[ݢ63!k̤uU&Vks٩.uIuPSVt>k{nD,/ӂ Fx8q?2Zx[mr r1$u8㱯t}jK&;uk[t_^8  k௉t$LO"bpp?1x4a$ʚvzo~hoQ9Ee{5n{Y~~,Լ3>dqI8Vn sӑjdaXǎCN@}kJyᾸbGA(lc =u]j6Kl G<6x3\u0Wd(gdS}v맍I(NvMz+kBYHb9]OFUQq0O5nLqCh0c`7bGbMEy_n#Adq9 Aud%ꑰ $He#9e2qXӄ$i8鵵u)>xJfm.gXl5IH(ke 6;^s}=ƋzdƑT+$DepAVU !#֫er6;aq˽kUĶ,Lθ)!Vq8Wr6˭ݯ^*UJwkm}SNe .RVqS"(ldVm[[2ܤPmŵ]HnySw$x+ᏈKtQүKyI 5rc[[UmZ=ϳ[jӚdQj/DBGci$c>47 `հIb!$O:HNzG<` |?`%_b墷s#IH {cѣTNxKVVݜJzIhީݭ6)YJ9n'`J[8g ;y8gq~xjץ t3+>V7 Pyg(l4! ?.N?099W4oFO}7Z߯K}jiRn1oM[[O푡C{آۼ]> +mwkjdC"RxǦ=5{WcֺmD9IHmo 15wzDn&Df.3N/$S.!'7 ƢMUtM뮽R}Hr┪⹩6+_^e+rm}4~rPGaӃo(_*k/_6]QUG|mRk6:78uOݖѓ+lcnPkw_ٴ[[(B՞}Fp)Ӡ61HɟIrH|iY_WfV?윾jʊRM+$]5ZNC{+GojĴB`YI fQ?]^ٖt6t+ŹJJh OWkI66;Hnis,1َbWPЂΏ]x"? ZZkWM>{=]7RnV-Zx-$AӬ/#D8vyۙ蝭Vxݶ^,wкv dY+m0`<ʲjJ.;g(}ZE6wt%BI ij$RjFkFlR%˯\6v!r]ɻ_wO/$tiE'잊w{;B#;'t=?_d`gn>p? 98:)]= 5n_Tv'`ޜ:~c?>};/'ϯC qǮ=*7ӉgɌW,/DNv;篧u秷8cU吒Onz{)y']Q뭺ob7emmOmxdw:tbt8XP\ tzך/ Xjة Ě9ӑXAJȹ _E"[(GuE&9];E+lHbˋtZ-uɵY^PЃ=Q_  5:~ͻxUz-}n󵴺>N?IwI}37V)iWw4}|ȖC7㓹`FWOtkO<]Il9Ĝg1N:W .![캲is(DRkxvR wfp >*ԌSq"E,dm'rlQ Knh;TH&VI6w}XF#vjouugKSuί`{m3A^wr;hd~Lw, 9۾&u-Yj}c h+( $ F9=0+/?.hQ\Yl~z}އD1qjv뵏}5FvnLLFm8~3[Hԙ@drF;Os_~4)7_;k7$ja#iD VFZ&S 8E~Ğ.iVda<1,VؠˏiB߃ΜqѤoFVnUůM+6wVw.k~:RZKt"$/I2r! 1WWB_]b- H>dpJraͬi XWr Șݹ9mtJѯ1 X*VNHۃ!F8yIv~RIJ1=lrO:Kܒ骲JeiYF$aD^|E:E+LY7`'6na[̱T)%%,jU;.Z9[wj/ 9m.^#Ϋe$i0R>opXfj @I4--c#ȸC$ec#H43iPVWG~n[]*JJE^[}VRk%nomӯV ӬVG3y NA'fh<)57ëu8\AulF2R4 v 8 &}Q;:+{{kYK;Ɛ@|2t{hQ*Fqw[-lo0 xHeԼ?f.UVt;#7GOk/&Ï|'|;"G[Y`_>yiHfH.T+Ͽk #|n>.{X٥uOM G?3t5"gSi_m%֫jNMPk -D5ރOҖD_kFWH >^s_RúL*ӳiߚ\:ּiewʬpoeB\Q攓WoXivҼew+mmke6o} %Vna+kz#5tPεK7ϊ.`׾2^[%},;cUmUnMƻWצ~' Y;/,Dl$X\I ܪ"6Y8M&&yQ_ jܔZ'eijorݤ+.g}[I+%y74]: Xh6z|CI(fgyfb 3qu;P^I%~| 3Mí|E{GT}"U[ۦO4AxW3fgH⯉ qK#KTx7ܮw[ I/|=K力1}^ x֏a\k .F..U6D&u@<FNRN.ir/iLU"S[דD赩w7$>X-dY5h統K&_{K9uPEk0f٠tɮ˾cUm."bnr65£Ozyw1y@`b~P5}cK@SCIjZʄԵRE7`B[wTӌ jڣ 7a3meIaKkyVA[4]W;]'F۫%k)0${zU6'_T˳u'鎙;׎}8:瑑nG~94}}=zirO't_޺﷙ts듟y>jׂN828=>q>ʴvZi{vc _u+gl;Ӆ$dⶽSuݦoUwNӴ_ #G#3Z^Ϙݢ1$DpXRxH&xGgw/ƚ6%[ Qlml{I >BG4O9YzCcri6Q,gK狋}1C?>4|im zbV)WVvpXnd{M(rǾ)%"Sl6ጏVgjR&)$%ETVWGf024*MFQ,VXY%n-b+̶(j"/Q2 [\j k1 *U{xz׃#|?u k:QmyO v#Cs*4BFf۱EIZJr|KDnOēR*pOEIs5~chNVݵ??౿R fg]xB]Qơa6 6dv1 Ak3>zoi PGYRx#+ OCqb1b2,&*^4]nZߍ:Vo[–7\{*W2=ķ%Ky"|XG^txZqq>oAi+iљ'@o "GgAsn|?0КP+\mrԓvYc*ҫ .eԮF{_~o [^A~%7Gj"uiZl-s/Rxuwk{o7Ƿƫ_ciFt>1KݥRUk]ֺ-WgCkI<u<+iv#`VKse"MI skODŽ%RVP&4e#9&DDӽd\ &[4Z+کʚq\׼&f]>Y9:QrO^jN*+Ż٤Zս.0|AI|#?"[MFOߧoe-GF%})e|?-kK}Z/&^Im1u]BS&7ošG'[_-Rqm}zD aҵ1%E$bN@;?I$75 Y$XVi Oq{$-H#VFXԌ54tJ/GW̰uTKEy&ܵWvMifWXw8m+LziׂiI@)=ee;ku{X׮RmRX˴#t" rX~~ǟ#EWSOfl9kÖ̰i,k:px~'+ $ e e(J1wI4[r}͹I_M ҝ:J2[%Mٻ6}oydIۦ1lg87 "܀ǚJH,Ne#vFOo\l,Ke*$Ъ$/|'B+9Yї .c*JHo|G7VEuqDg1[1M"ɱ\4w$apQNMR>g~mtzZϣkfzU^T)J0m;ҳV&i=0\!!gwa"(@fcWjd:zM ݝJ`{Iw10$swZ[٩܋T1Q$;iQ梬BHKLq ~Mn`D[ؐU y$0( 쮚V5-mMonuZGj6{mtӧVǯ ?l9 wCq1Q+C$ bSeX0irIb fre!T<".%y/\t Qcˑ|$m w2T-m"IUc69#ND{`P7ckm>Yt] ]+FRridi]bM'fke]_I[a2Ѡ2F<9oc}TmJ@#_FpAs@"ᕘ3(VچK,?*q4)˙QKW,׉|q ic@,H,mx+"N{d$tcQ8n^ 9J/u.7wvI?3NIN)GWMZ+-/%OuFXZ:'w $ 52kK00n6_W+{;"YXڼ:7r k$Ae6x|K9'GŚwï n}GZn5Dzƻnλ*Gb9Q`>iA,{tMZ#)nݬ i_EřarR8**J2횷*tcͯ*&3wWf0K;>GVrhŦJR_̣IY%vAŶ"[{g ֯`yFeK1IrYo(QDVޥ|@/)Z 6k*ZjW7u}847|ܐQI^X[yq\q|1UxZ<9]>]#Sy ʊYclymؿe/ ~6|e1_Vg-#T`hcKI"I4J+qfU*|>3.5JFS/e8-9>ꔠm($ۅJ>7^*J*6QvRJeekO-1;{K\O ח0r/$$?iQgi!xl1b)Ntdml`Z֖珆'ŸkvKcxm5 [c)<?'F<GC=FoLc8'SN@m_^iZg$hSXHo"kyD[]1ge|莮to. )8sfY #IAq}s*o2ia_g&S|NNNݫ;]'Vt}$kirIj? _\[@LUU6Q?]+;;~It:ޫid P&mIU%b3²0ꓦ_k۬n+w3.J }&ľoDmᑃD t,@HJ䲝h󽣄g9̣%/;J=$a (ŽK[;|?#l3U5LVFjvO]/ejSЭGs_vmu~=4=XWWR$G9cmbw/|Wtd[Ɨw;6]xBrwHaɨ\<*811ˉYNҪI'9M6i8}B@b"(J1`|rĕjPUC0rI+ծ,oݒk줫C޵ycvWz4%?sX[̆E͢@26m: 6O!`s-u8va+C4[j#=8><Q`&v 1#Ǭ|>q\LhV. ~i%6ӽHΕתF6Rw/{v+ɫV ]8R\~f׮ސVP0 yR͆Vkq70kTUr$JuPO bC\~i[x[e~GCm$pRYTPD݃)Z5; |47~<{m7DV>!JmGe4 g\æj:&K}j?'};7?~*|Jƴ}^.-RVRhR)^2F|z6 &9F-BkT'II'~GeN b}A/!.]Cj]-/fnV[BsyrGqy*yB(~|1īxD-♧~pӲH٭mci!y. /$ğf~xF>#I[m#;Kqے%]7צ٬&JӒ]:F_|7l#2m8Ad6D !$%V6dEbnn0Wt,]i+UsJ,k 6i1N5W7KS=IPt)+4ɩT'y(CNh%. ˰XzuX,UNtjҜhb}rMK(WRk{Z\Í3?k߱{wgZ݌:۸pN@4$2#-N![EthCǁ"=<Q1IU'R|smy=ΔWqsrpo9,F]Ũ*+× (֍(t*$./$ז~m?SsX 0ݶ]C 4YN& ~>&Z$͙u@~pyFڸr VǹlOL)`fc%u(aB7FɗheMjVO50Or6H}3៎'5sR֥IFu]G$~Vk6k?Md S~'.++0g$c5yg\YNx { E-ޝ 0 RG9!q9>oJ-mb!`=Ȩ?kN^ 7}GmwugF2Q^nj7nϢ~{onGg;9@H h\v''SG:2LξV썄nqG\sҿ$-o,Ǘ4`dr3yſM4WdR6v& Бty12RtT=2߫^2N2rNQZysxsPOԢhBqJ#:XY>:c_;4 2K`Q'_n`˜8=q_3:Q{]i}n tku V,;GBqLc9=q>*Nr:^:zRo9y<9_zOOd0'3*-n=3I=?B[_Nv?#w>xek3 H%"F9$x5k2ۓ,4h(R[ &sBl$2H+7#H nʉTusVHey OCk eʸ{ &,=E{xQdsO7pĒY }[9[#(:7IFo,JJSqZEF]k$9EbueztYCMaYwFvuBW'oY/ AwoywrI,ӲGyQ@?'ߋuioZYFH(X܍ЃLb2:>8=͜sv^"!]+X( TGPX.\ƝiΜS%M6ܔKow{kvu*S$SSQii}5k]mmǾ2:嶤ԭu>|.W>[$U†?f|"iͽ!+C!@Ω +rT*`>Vɯa8{WW#̚Y E$"=Pʨ2]vuRB\_f1z͹d6x+ȗU:P$ғj/{Yh=9ЧrRlN;]'}Uj>-MɥD a!"܇ eV=3F< 85a64հԡcrDq@, _8@u!H5{yf0f׊,U&ED @bHBp@9dqk$Q!U|TBHz(}L-'Vu*-+Z{Z\:ͱucʝ:tj>˭?Ep(1Kpʠql3Rp^eQԀ{t Eu20C$[̗W]#Ch]DmwpSV u!@&IK&i#JRc]ill^iJFvʟW ЫB4dt)96Z=bs:'9*zԗ'vݡni^-2?21iUCF$v9;H|ƞۤJБ! k+3;w,M %k*5ɌnU \eؠknP$$i ( \6.nb.@fb_tMlokzh`UFmd#ǧ vN~Ҽw'uOKNH%u[NRc$(on&}-ѐ!I''E@2Eim!,rehcnVTʜe(IT朢ծu3j AVUՄj%AI8N-Y[xgy?xsJ-4M6Ct.G -7Mf+BR4c&P,/|<2?Ů^p&֠e[M=-r<ciIiX[+p>D~"|jS/xyq$zhӨBτOo?kyh%xI%m?YMݤ\};ʨp~DFVqiյ:mQr jN״+UJI'7'uj5$J1JJ >(h"5; Z}յwO[D^Ojڧ1 :F߀_G'Zm-wv֌8C;xg>r~3~y χuyFK]ue,eKg$ZP11~OV46gcY.dX>{TU3hn$)0;y&'N0op.??~~[khF![=I?\}Fkj4*(=Xg#nO@=F;Wj{7iN7~tݶO>cSJ۪V>KBAD qק_ǏVv~6r![2OLnbԾ!| }'v1qן;_>4×$xdtdFoKiI%+]_-pi*1*)~>(!|U-~cY ǎ˙2>W|-ujڢG.%) IdWl`uSGc~|xׁu*YN2t55﫽Lngxwjڴ-}-ks;~8#WGPog!9XcO^: 'Cc0gqbm+wzoc8[۳VRm<{ѐ:^/­EN2:o*)8#ҟF+{kCG^O{'O4;M_J&wΪ ʰ'|֏opxvdq&EH##C" ) aVɂGRdI[D+6ۅ;x(˻2i1_ nd W?K*T]X]Ż|0rQ-kinl*ba[NN4]ZMi&[]{};W{xnng2hpuʪƜS, aimqsSK%ʫ\*qBY2/[Q9-ܭ[;9C "BH(@k|UWZIDa# ~GBZI&_GhB-JijqwݭuԋI?gk+=7M>ڵsk~%t%^l>nRh]n./,JU4ߕW_yi+% Y۹pWaU[ȧuX.Rӣn!q4r-H*F@$ľ x+KL^ե;' 1 ]#ܥFpuf^{5wm۵R7l{4ӵny{OIkԌm'1Dι$28Cj"FE>Wbq u0G%oځ-//_HP̏zF#8 Q}n珸<U||ae5 hR8=uUk"kx\Mrcp0V۔쑔n.#7g/)s{k0xGXc-/ xUIγ(U5V>WF22r@#26aT eY#Ose,IGwno^9"Igo1 gg6BI<,bfi)!G%楨124ń*X8\vJ5jRjQNMoʾֻZ׺wౘ* iJOOu}Jھ^XFKZ\qmNMIn?8ӥx $?,uZf-Kmq, .iM=םĦ~ aKMgòfr ihbRȑ~ciXx&<>Nh<26ڶpVk>I4iX1#}@J:5h5YsO KrTAd2dxYB44O$4lӥ' ZH)5U3tlNVi#խQEҌR3pNNϒ'}_{)&ӳ#^5l-bɻW8 kͻ/ j!M_ZQeш;dmc.g3-5m5ĶKpd6c7fthHY_#Nd_$miHl/nO-f6Kkt-=ɔ6XkxcRNPKsmRJ3Ei^1U0HӣJrnNn*˕+6m-VG_-oVvr 7P  $uKO-9@%! PG* ⡧xܷmeaD=!ɑC1%݇x5ħh% #vsT VZN2JQIj$}' jN:uaQ^SwVrM=ۯEWY%ÖH6ͬlH 6j)^C4Vm;Aى@G {Aw_W<_k4~I6l#GO C^6ӣc t$g8_4k=:2>H Q,HJ/$c{|\L˕*m)rKKmUO!U8Fr/ok** F}x9㎀]Eje29gv1YHcq8 c<VYTYHu~lzv=3ϟ>7mbӜ5צdx+'2jcjvz/Zs#٠rO?;R1\~vŎ:6>P:R&mZ=z(lr+>*q*F3_Ͻ`3Rm?IZiܺ+?`aik,͵ `(ţY: *mR{q(cj:%괖 {{Uz#@`%.Q]dc"3 |wc\9e-C,6E1b眀I!NMvn|<Ȓ1%$y%]Ì-3~KI6ӗ6^%!#dyt.ᄋNT]?p)R`FH;:9G"!_11Wf#"4T9]+绛^Gi bw)8rr+SKHeK"V=#2v'8 @a +Sn3mK)lwz=vۯsI8qM^N;hֺh~x{j뗒;{2mcXhfE`V`ĝY˨U;MxtKwiI#ǫ%&C $yaI>][cy!&cabdf+m7Fk>*LzΛg!307EYn-Ph*[!*14Rƌԓwm7GMtѣVuՔ[dD/-yZ$fmxudIY 6UeL%vcT;/ %:}ޡ ϦfM\3;kkYcJ\JUտ$WHQ"'}Icw!m:SȖ.#ʎ/u0i1fxveZ{;9;Fkт#M.KLq+,KrcLG?XҒ[mĺnj5h #+ojZٻ %\VwUY|?=K6]&O[f2I$qxWV(V%fwÿ}[{6!tQ;2J}HheqvQQ^ c*J*JeNE][lgp:Qa9F|JI'Y]hi$ֹo/5#ӭlb1On 7w%*d+ijg.x/'#zdŬ5BVU KvMN VP|SjlAv"k^h+j f6Ӱ&&m=;T%z ^\Mj$۸-HZ#m 8. LmXW+iիi_ro{j[{Is8:wZhi_[Ek^":jz5M_C7g-/0KIu+&9_+HPڵJ⧇<+_jCki5nyBʊk HI]F>_h֒, X!fjLcVX-vsiDi첦&?.X V̩_R DP(L "2Idn'5S#M]]Y˥Z9%m|4XyTR)4뤬ݛ[O[E3jo^m)iZXg3Dm {1 (`ƚt&KnbX< -^@BYY#2XYM{gI̷[}u u!뻈ٮ@`]+VmYM5@K;rYI/!&eo܃Jrz}cD{vGgR\-IyjU.);Kms M-#^yr)>)5YjM,I\a"tReJL{F7WE $>JZjVi+TIJ72cVZ^mjMŵӘZ˪k/ep974lId`6c:MgDogJ"l#yı'aR~eu 1mkd5(g`,m!VA* 1ԃa f(79*ףt0K)?z8Ʈ"ʓN+DwKM/_]^niko6]wS6Hon[7>ÈQ\ ψ⧌|?"Iwu;c IXȍF שP[+SKw09`a G\xQuݺ];*-ʓZ5O1$ʁh:iyQ"R;IJ="wnֲh c];&z%-kh֮^è2ZHC#9`|pzpOWUs (n<WiӒu ¼ɌK`W' 78 c8e RЧzASKm$wSfiZϙ(m˭=O][(%NWq Hl r2G04J70Q#ۊ,@.v%O<4H@NDdchrIj&a42) :mcqs@#H%7(IN ]u2^+Jnjkb Z+OodѲa$dR:c's`yӫA ߁׷\xVړF (2:y 9*!F !PbBA\d@+99jۄ]nVVMV5-[_K*i}5j]\NlLLFqXَ#?9 ԳGi3Q`U*'#y-/cTpq7gi5jӵ֛[rȚ{^k-˥#(=qӱq Up02x9*s=I=иq"4^aaƄp$E258$pؙoXHp[xۜDo;J:^7RMlݝi"|%kGn[.xͅ@SfH*5hpfuuϽzRy[r;O3\X03A$`kS]4pDymM2 `I9VrU Pvzg};>>VK[iv{%IMo\$n"n]J`-t3$tONM"; ymٮFO$qLg T EOxѧJ! tX;jrI;E+v?$%Vu+NS«Rқ\Wsz6<#x-m][S;XdZE X[$>c-%݋{qeFtvF<:i[h!hz2.á2WAYdӦVMVLoqx=K3Q_C;̱58s81biI{mZѳ2-Xt%)JrNͿ{I\EagV+;Mѵ)0% 'yE%)eH#gi:;;ح]Ihᕑ!8!t(qR9F$S$yI{_k۟)a*P\"RMrK7kW_u+7Q4MWSOuoi8,$f7yJ nIk>&@~">]=\Wo4f IE.A}%IrP ) V׊g( 5 QRUw(ϚM]C+έ)WR$iE~u>xT_St&MV-rC%1Oqo+g[:$@ifkfʯ\Z<\[Mysh,c+63JF6HtV:; 4EAB1䂋ku{[,tҴ?gQ[K,rm-o6̊tE_1QU*hqdRt(Ҕ7tT9Tm}b8w. u[VfUȔM.-ߕ]99{k+߇5~}_ZڪVxn#pCfR1\n%3G GO۸`2SjX^UAks:1BZO,, (2[[V*'-Szso|8V3+'V.O^[٤RvJz'3u5xJhp$!$ɴCD߁ga4$bm1nc@n2RIDF*Vh'T0G֕*ssT+9&\tOT+U)FHJJ1$ԡ+6V/}C2|@tU!,D7DcyK`$!9WxmIZ쳄[0d+2Q_-q}ZTSNe6IF1{;R}OdtH0(R{wZi%?e mJ9ex_lUXڬJ%҄‡d`v3^OOt[M"tu[F& :ȥ* +Fo2]jqRqu'kI)49^/?URG%ߕߕ]KC"E)2\Y5Ov|Hpc%Eyt-%m",Ӣ\Mʌy秊[!DF((Z ;E$߽&5?y}ӄR4ڽײwӮ[]>H'K{mY#(^R5bRTwwvpXkogqyƷmZ))In3r"ȍqp2E|kTZ[M4:SU/vVzo9. =kWF>wA ]૒VU=ɷ1_HhE,蘩VR4=%8RiKb{1e(+4K7,n}OMUh\;)i$T!G-^\;%Ԗo'D#+!9VeJ(Rj4ҿfFrt+&~;/. F]. %{x`#{u].Ftr$ Tɖl k3DE%fN`3)SMhūif}:k6\Pzo<<{V}Ǐ]3V@QeY#."?'۞8K$~y-ZE-m5z%&⬕{'PyLaTeX-1.4.2/examples/lists.py000066400000000000000000000040601451425216500163370ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- """ This example shows list functionality. .. :copyright: (c) 2014 by Jelte Fennema. :license: MIT, see License for more details. """ # begin-doc-include # Test for list structures in PyLaTeX. # More info @ http://en.wikibooks.org/wiki/LaTeX/List_Structures from pylatex import ( Command, Description, Document, Enumerate, Itemize, NoEscape, Section, ) if __name__ == "__main__": doc = Document() # create a bulleted "itemize" list like the below: # \begin{itemize} # \item The first item # \item The second item # \item The third etc \ldots # \end{itemize} with doc.create(Section('"Itemize" list')): with doc.create(Itemize()) as itemize: itemize.add_item("the first item") itemize.add_item("the second item") itemize.add_item("the third etc") # you can append to existing items itemize.append(Command("ldots")) # create a numbered "enumerate" list like the below: # \begin{enumerate}[label=\alph*),start=20] # \item The first item # \item The second item # \item The third etc \ldots # \end{enumerate} with doc.create(Section('"Enumerate" list')): with doc.create( Enumerate(enumeration_symbol=r"\alph*)", options={"start": 20}) ) as enum: enum.add_item("the first item") enum.add_item("the second item") enum.add_item(NoEscape("the third etc \\ldots")) # create a labelled "description" list like the below: # \begin{description} # \item[First] The first item # \item[Second] The second item # \item[Third] The third etc \ldots # \end{description} with doc.create(Section('"Description" list')): with doc.create(Description()) as desc: desc.add_item("First", "The first item") desc.add_item("Second", "The second item") desc.add_item("Third", NoEscape("The third etc \\ldots")) doc.generate_pdf("lists", clean_tex=False) PyLaTeX-1.4.2/examples/longtable.py000066400000000000000000000024461451425216500171560ustar00rootroot00000000000000#!/usr/bin/python """ This example shows the functionality of the longtable element. It creates a sample multi-page spanning table .. :copyright: (c) 2017 by Jarrah Gosbell :license: MIT, see License for more details. """ # begin-doc-include from pylatex import Document, LongTable, MultiColumn def genenerate_longtabu(): geometry_options = {"margin": "2.54cm", "includeheadfoot": True} doc = Document(page_numbers=True, geometry_options=geometry_options) # Generate data table with doc.create(LongTable("l l l")) as data_table: data_table.add_hline() data_table.add_row(["header 1", "header 2", "header 3"]) data_table.add_hline() data_table.end_table_header() data_table.add_hline() data_table.add_row((MultiColumn(3, align="r", data="Continued on Next Page"),)) data_table.add_hline() data_table.end_table_footer() data_table.add_hline() data_table.add_row( (MultiColumn(3, align="r", data="Not Continued on Next Page"),) ) data_table.add_hline() data_table.end_table_last_footer() row = ["Content1", "9", "Longer String"] for i in range(150): data_table.add_row(row) doc.generate_pdf("longtable", clean_tex=False) genenerate_longtabu() PyLaTeX-1.4.2/examples/matplotlib_ex.py000077500000000000000000000022431451425216500200500ustar00rootroot00000000000000#!/usr/bin/python """ This example shows matplotlib functionality. .. :copyright: (c) 2014 by Jelte Fennema. :license: MIT, see License for more details. """ # begin-doc-include import matplotlib from pylatex import Document, Figure, NoEscape, Section matplotlib.use("Agg") # Not to use X server. For TravisCI. import matplotlib.pyplot as plt # noqa def main(fname, width, *args, **kwargs): geometry_options = {"right": "2cm", "left": "2cm"} doc = Document(fname, geometry_options=geometry_options) doc.append("Introduction.") with doc.create(Section("I am a section")): doc.append("Take a look at this beautiful plot:") with doc.create(Figure(position="htbp")) as plot: plot.add_plot(width=NoEscape(width), *args, **kwargs) plot.add_caption("I am a caption.") doc.append("Created using matplotlib.") doc.append("Conclusion.") doc.generate_pdf(clean_tex=False) if __name__ == "__main__": x = [0, 1, 2, 3, 4, 5, 6] y = [15, 2, 7, 1, 5, 6, 9] plt.plot(x, y) main("matplotlib_ex-dpi", r"1\textwidth", dpi=300) main("matplotlib_ex-facecolor", r"0.5\textwidth", facecolor="b") PyLaTeX-1.4.2/examples/minipage.py000066400000000000000000000017701451425216500167770ustar00rootroot00000000000000#!/usr/bin/python """ This example shows the functionality of the MiniPage element. It creates a sample page filled with labels using the MiniPage element. .. :copyright: (c) 2016 by Vladimir Gorovikov :license: MIT, see License for more details. """ # begin-doc-include from pylatex import Document, LineBreak, MiniPage, VerticalSpace def generate_labels(): geometry_options = {"margin": "0.5in"} doc = Document(geometry_options=geometry_options) doc.change_document_style("empty") for i in range(10): with doc.create(MiniPage(width=r"0.5\textwidth")): doc.append("Vladimir Gorovikov") doc.append("\n") doc.append("Company Name") doc.append("\n") doc.append("Somewhere, City") doc.append("\n") doc.append("Country") if (i % 2) == 1: doc.append(VerticalSpace("20pt")) doc.append(LineBreak()) doc.generate_pdf("minipage", clean_tex=False) generate_labels() PyLaTeX-1.4.2/examples/multirow.py000077500000000000000000000040121451425216500170630ustar00rootroot00000000000000#!/usr/bin/python """ This example shows how multirow and multicolumns can be used. .. :copyright: (c) 2014 by Jelte Fennema. :license: MIT, see License for more details. """ # begin-doc-include from pylatex import Document, MultiColumn, MultiRow, Section, Subsection, Tabular doc = Document("multirow") section = Section("Multirow Test") test1 = Subsection("MultiColumn") test2 = Subsection("MultiRow") test3 = Subsection("MultiColumn and MultiRow") test4 = Subsection("Vext01") table1 = Tabular("|c|c|c|c|") table1.add_hline() table1.add_row((MultiColumn(4, align="|c|", data="Multicolumn"),)) table1.add_hline() table1.add_row((1, 2, 3, 4)) table1.add_hline() table1.add_row((5, 6, 7, 8)) table1.add_hline() row_cells = ("9", MultiColumn(3, align="|c|", data="Multicolumn not on left")) table1.add_row(row_cells) table1.add_hline() table2 = Tabular("|c|c|c|") table2.add_hline() table2.add_row((MultiRow(3, data="Multirow"), 1, 2)) table2.add_hline(2, 3) table2.add_row(("", 3, 4)) table2.add_hline(2, 3) table2.add_row(("", 5, 6)) table2.add_hline() table2.add_row((MultiRow(3, data="Multirow2"), "", "")) table2.add_empty_row() table2.add_empty_row() table2.add_hline() table3 = Tabular("|c|c|c|") table3.add_hline() table3.add_row( (MultiColumn(2, align="|c|", data=MultiRow(2, data="multi-col-row")), "X") ) table3.add_row((MultiColumn(2, align="|c|", data=""), "X")) table3.add_hline() table3.add_row(("X", "X", "X")) table3.add_hline() table4 = Tabular("|c|c|c|") table4.add_hline() col1_cell = MultiRow(4, data="span-4") col2_cell = MultiRow(2, data="span-2") table4.add_row((col1_cell, col2_cell, "3a")) table4.add_hline(start=3) table4.add_row(("", "", "3b")) table4.add_hline(start=2) table4.add_row(("", col2_cell, "3c")) table4.add_hline(start=3) table4.add_row(("", "", "3d")) table4.add_hline() test1.append(table1) test2.append(table2) test3.append(table3) test4.append(table4) section.append(test1) section.append(test2) section.append(test3) section.append(test4) doc.append(section) doc.generate_pdf(clean_tex=False) PyLaTeX-1.4.2/examples/numpy_ex.py000077500000000000000000000020301451425216500170430ustar00rootroot00000000000000#!/usr/bin/python """ This example shows numpy functionality. .. :copyright: (c) 2014 by Jelte Fennema. :license: MIT, see License for more details. """ # begin-doc-include import numpy as np from pylatex import Document, Math, Matrix, Section, Subsection, VectorName if __name__ == "__main__": a = np.array([[100, 10, 20]]).T doc = Document() section = Section("Numpy tests") subsection = Subsection("Array") vec = Matrix(a) vec_name = VectorName("a") math = Math(data=[vec_name, "=", vec]) subsection.append(math) section.append(subsection) subsection = Subsection("Matrix") M = np.matrix([[2, 3, 4], [0, 0, 1], [0, 0, 2]]) matrix = Matrix(M, mtype="b") math = Math(data=["M=", matrix]) subsection.append(math) section.append(subsection) subsection = Subsection("Product") math = Math(data=["M", vec_name, "=", Matrix(M * a)]) subsection.append(math) section.append(subsection) doc.append(section) doc.generate_pdf("numpy_ex", clean_tex=False) PyLaTeX-1.4.2/examples/own_commands_ex.py000066400000000000000000000053751451425216500203730ustar00rootroot00000000000000#!/usr/bin/python """ How to represent your own LaTeX commands and environments in PyLaTeX. .. :copyright: (c) 2014 by Jelte Fennema. :license: MIT, see License for more details. """ # begin-doc-include from pylatex import Document, Section, UnsafeCommand from pylatex.base_classes import Arguments, CommandBase, Environment from pylatex.package import Package from pylatex.utils import NoEscape class ExampleEnvironment(Environment): """ A class representing a custom LaTeX environment. This class represents a custom LaTeX environment named ``exampleEnvironment``. """ _latex_name = "exampleEnvironment" packages = [Package("mdframed")] class ExampleCommand(CommandBase): """ A class representing a custom LaTeX command. This class represents a custom LaTeX command named ``exampleCommand``. """ _latex_name = "exampleCommand" packages = [Package("color")] # Create a new document doc = Document() with doc.create(Section("Custom commands")): doc.append( NoEscape( r""" The following is a demonstration of a custom \LaTeX{} command with a couple of parameters. """ ) ) # Define the new command new_comm = UnsafeCommand( "newcommand", "\exampleCommand", options=3, extra_arguments=r"\color{#1} #2 #3 \color{black}", ) doc.append(new_comm) # Use our newly created command with different arguments doc.append(ExampleCommand(arguments=Arguments("blue", "Hello", "World!"))) doc.append(ExampleCommand(arguments=Arguments("green", "Hello", "World!"))) doc.append(ExampleCommand(arguments=Arguments("red", "Hello", "World!"))) with doc.create(Section("Custom environments")): doc.append( NoEscape( r""" The following is a demonstration of a custom \LaTeX{} environment using the mdframed package. """ ) ) # Define a style for our box mdf_style_definition = UnsafeCommand( "mdfdefinestyle", arguments=[ "my_style", ("linecolor=#1," "linewidth=#2," "leftmargin=1cm," "leftmargin=1cm"), ], ) # Define the new environment using the style definition above new_env = UnsafeCommand( "newenvironment", "exampleEnvironment", options=2, extra_arguments=[ mdf_style_definition.dumps() + r"\begin{mdframed}[style=my_style]", r"\end{mdframed}", ], ) doc.append(new_env) # Usage of the newly created environment with doc.create(ExampleEnvironment(arguments=Arguments("red", 3))) as environment: environment.append("This is the actual content") # Generate pdf doc.generate_pdf("own_commands_ex", clean_tex=False) PyLaTeX-1.4.2/examples/quantities_ex.py000066400000000000000000000032121451425216500200610ustar00rootroot00000000000000#!/usr/bin/python """ This example shows quantities functionality. .. :copyright: (c) 2014 by Jelte Fennema. :license: MIT, see License for more details. """ # begin-doc-include import quantities as pq from pylatex import Document, Math, Quantity, Section, Subsection if __name__ == "__main__": doc = Document() section = Section("Quantity tests") subsection = Subsection("Scalars with units") G = pq.constants.Newtonian_constant_of_gravitation moon_earth_distance = 384400 * pq.km moon_mass = 7.34767309e22 * pq.kg earth_mass = 5.972e24 * pq.kg moon_earth_force = G * moon_mass * earth_mass / moon_earth_distance**2 q1 = Quantity( moon_earth_force.rescale(pq.newton), options={"round-precision": 4, "round-mode": "figures"}, ) math = Math(data=["F=", q1]) subsection.append(math) section.append(subsection) subsection = Subsection("Scalars without units") world_population = 7400219037 N = Quantity( world_population, options={"round-precision": 2, "round-mode": "figures"}, format_cb="{0:23.17e}".format, ) subsection.append(Math(data=["N=", N])) section.append(subsection) subsection = Subsection("Scalars with uncertainties") width = pq.UncertainQuantity(7.0, pq.meter, 0.4) length = pq.UncertainQuantity(6.0, pq.meter, 0.3) area = Quantity( width * length, options="separate-uncertainty", format_cb=lambda x: "{0:.1f}".format(float(x)), ) subsection.append(Math(data=["A=", area])) section.append(subsection) doc.append(section) doc.generate_pdf("quantities_ex", clean_tex=False) PyLaTeX-1.4.2/examples/sample-logo.png000066400000000000000000000144321451425216500175600ustar00rootroot00000000000000PNG  IHDRfqtEXtSoftwareAdobe ImageReadyqe<#iTXtXML:com.adobe.xmp NIDATxkdE]Vh/[d{QaQ|\1MQ?j5H$C'~0>b(Y3#P3c\A$h(\zn=NLߪ:ߪSU M ˲͔U=E5j,vo!.JokÁGUf@LdYh-{?lahT] L4πij6Lw&*$';9-oGb&vGiI<21Q3M&Rr5!YbyՄ,2Ҏ(8(mEЖʻ9,;]|^鉕vJYL,f7T},P9Ka(ST94 -Եhp:3.b$eՀkig16hb"c3Y4O[LP;$R\(Lu&A6݆kPX eN 1N.ݥ & ƙ}rlZq|S 3Rq.qܣm:@U7x7PWg@4ق; 1oj]LT.3Uu&ƠKqjfl.2Jmk!ӳ' :َE.,!&嶈au=u?lY>ߜC+OWLђ6%in33*r4Rw_DuYׂBgp3#j]98 OKLlf'v̲ ^͟ݗp%M~O,0 sj~eeKB`z yyw |]Yv1oٗK>[?! (8T8 {mztiХݪm13uY8Ll$ޣ[t3KV5|*9Sq8 ,`]2]ؘ0Y@:iqui'`P 11n?F @JL1AnW]cmZN唚rG^PM"tcz̕gyc*N\aWEzHJO߽ߑrM=f'J <.s-x_!&Ngx'?& NX{pE} omѤ>uè3Ȳ센T'1I4r>Cf[Q]3bOJ|liD En6QqdwJ{ylK"{6@Zw+b:A8"ToNC\o&!( dXl[a|W(-$Ρxq8:&bb93D"f!NB~07}1QȈu x(b8I|IyALϞ]ێ4 Kgn gVbQ;eabcQ$."#}7&1GbB@Z :lrU#-߭Ƿf%Ź %d8q#40LnrFeRކ ˆK3FZ~9ags(Fi1N^'7S&ӟyU)/u`-֜.ԁe 1q{ NqeĬc y[ܞi*\uU.vo[wPԥ̈́ m-hd_7Ynq\6~Nsu&b L[nR",M`Ps?.rZ!$8/o`Vtӽ#2 ݵe`m0II@Nú}T4"VbnbffJ%ލS%u_`Ţq~cZɺd%r^l&zcST鮠6oWoy2}JJg&tcf;V36/8k`uf<\ue͢,D 8Nt}51f&bVbfD-vQybf8E:Ld+`|mu]>݊Nͷ,&*-;J[ՠ3`qc9bb*DA2'Qef' Aij97Ǒ늍rOz]baQm. ϥv.q8 'aߵb}-g>:A]YxfmsLjI2Nb 7\M}mԾ%DIL1!o/}%'r'`3+a,mek~7NE=n%P@_lZQ+wX cYF=):}v_'vTZ-.+ m`Ą2'\8=gir Nb9I;NB }d|vR1!7yd~,e|5r2 &IT|_G-Al'4最ucc)8 iY pZn(1=V)̑cDlϮUWl;3A!gCL8_1ыGj8/DAàADj'>X"իZ1[68I<®4HEL4ƖlaGYbL~1bԕE9 %1m ]rs`'q?WBbn2YA#OcT]2NΧOsK7EQ^hrRDU<& h1-l3 Dict[str, str]: """Get the keywords needed to look up the version information.""" # these strings will be replaced by git during git-archive. # setup.py/versioneer.py will grep for the variable names, so they must # each be defined on a line of their own. _version.py will just call # get_keywords(). git_refnames = " (HEAD -> master, tag: v1.4.2, refs/pull/191/head)" git_full = "73765878cdfc33999d33633b10791fd7872d3203" git_date = "2023-10-19 18:08:21 +0200" keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} return keywords class VersioneerConfig: """Container for Versioneer configuration parameters.""" VCS: str style: str tag_prefix: str parentdir_prefix: str versionfile_source: str verbose: bool def get_config() -> VersioneerConfig: """Create, populate and return the VersioneerConfig() object.""" # these strings are filled in when 'setup.py versioneer' creates # _version.py cfg = VersioneerConfig() cfg.VCS = "git" cfg.style = "pep440" cfg.tag_prefix = "v" cfg.parentdir_prefix = "PyLaTeX-" cfg.versionfile_source = "pylatex/_version.py" cfg.verbose = False return cfg class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" LONG_VERSION_PY: Dict[str, str] = {} HANDLERS: Dict[str, Dict[str, Callable]] = {} 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 def run_command( commands: List[str], args: List[str], cwd: Optional[str] = None, verbose: bool = False, hide_stderr: bool = False, env: Optional[Dict[str, str]] = None, ) -> Tuple[Optional[str], Optional[int]]: """Call the given command(s).""" assert isinstance(commands, list) process = None popen_kwargs: Dict[str, Any] = {} if sys.platform == "win32": # This hides the console window if pythonw.exe is used startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW popen_kwargs["startupinfo"] = startupinfo for command in commands: 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, ) break except OSError as e: if e.errno == errno.ENOENT: continue if verbose: print("unable to run %s" % dispcmd) print(e) return None, None else: if verbose: print("unable to find command, tried %s" % (commands,)) return None, None stdout = process.communicate()[0].strip().decode() if process.returncode != 0: if verbose: print("unable to run %s (error)" % dispcmd) print("stdout was %s" % stdout) return None, process.returncode return stdout, process.returncode def versions_from_parentdir( parentdir_prefix: str, root: str, verbose: bool, ) -> Dict[str, Any]: """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both the project name and a version string. We will also support searching up two directory levels for an appropriately named parent directory """ rootdirs = [] 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, } 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) ) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs: str) -> Dict[str, str]: """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. keywords: Dict[str, str] = {} try: with open(versionfile_abs, "r") as fobj: for line in fobj: if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["full"] = mo.group(1) if line.strip().startswith("git_date ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["date"] = mo.group(1) except OSError: pass return keywords @register_vcs_handler("git", "keywords") def git_versions_from_keywords( keywords: Dict[str, str], tag_prefix: str, verbose: bool, ) -> Dict[str, Any]: """Get version information from git keywords.""" if "refnames" not in keywords: raise NotThisMethod("Short version file found") date = keywords.get("date") if date is not None: # Use only the last line. Previous lines may contain GPG signature # information. date = date.splitlines()[-1] # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because # it's been around since git-1.5.3, and it's too difficult to # discover which version we're using, or to work around using an # older one. date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") refs = {r.strip() for r in refnames.strip("()").split(",")} # 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)} 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 # expansion behaves like git log --decorate=short and strips out the # refs/heads/ and refs/tags/ prefixes that would let us distinguish # 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)} if verbose: print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: print("likely tags: %s" % ",".join(sorted(tags))) 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) :] # 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): continue if verbose: print("picking %s" % r) 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, } @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs( 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. This only gets called if the git-archive 'subst' keywords were *not* expanded, and _version.py hasn't already been rewritten with a short version string, meaning we're inside a checked out source tree. """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] # GIT_DIR can interfere with correct operation of Versioneer. # It may be intended to be passed to the Versioneer-versioned project, # but that should not change where we get our version from. env = os.environ.copy() env.pop("GIT_DIR", None) runner = functools.partial(runner, env=env) _, 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) raise NotThisMethod("'git rev-parse --git-dir' returned error") # 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, ) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() pieces: Dict[str, Any] = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None 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") branch_name = branch_name.strip() if branch_name == "HEAD": # If we aren't exactly on a branch, pick a branch which represents # the current commit. If all else fails, we are on a branchless # commit. branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) # --contains was added in git-1.5.4 if rc != 0 or branches is None: raise NotThisMethod("'git branch --contains' returned error") branches = branches.split("\n") # Remove the first line if we're running detached if "(" in branches[0]: branches.pop(0) # Strip off the leading "* " from the list of branches. branches = [branch[2:] for branch in branches] if "master" in branches: branch_name = "master" elif not branches: branch_name = None else: # Pick the first branch that is returned. Good or bad. branch_name = branches[0] pieces["branch"] = branch_name # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out # look for -dirty suffix dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if 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) if not mo: # unparsable. Maybe git-describe is misbehaving? pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out return pieces # tag full_tag = mo.group(1) if not full_tag.startswith(tag_prefix): 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, ) return pieces pieces["closest-tag"] = full_tag[len(tag_prefix) :] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) # commit: short hex revision ID pieces["short"] = mo.group(3) else: # HEX: no tags pieces["closest-tag"] = None out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root) pieces["distance"] = len(out.split()) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() # Use only the last line. Previous lines may contain GPG signature # information. date = date.splitlines()[-1] pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces def plus_or_dot(pieces: Dict[str, Any]) -> str: """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces: Dict[str, Any]) -> str: """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty Exceptions: 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += plus_or_dot(pieces) rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_branch(pieces: Dict[str, Any]) -> str: """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . The ".dev0" means not master branch. Note that .dev0 sorts backwards (a feature branch will appear "older" than the master branch). Exceptions: 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: if pieces["branch"] != "master": rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0" if pieces["branch"] != "master": rendered += ".dev0" rendered += "+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]: """Split pep440 version string at the post-release segment. Returns the release segments before the post-release and the post-release version number (or -1 if no post-release segment is present). """ vc = str.split(ver, ".post") return vc[0], int(vc[1] or 0) if len(vc) == 2 else None def render_pep440_pre(pieces: Dict[str, Any]) -> str: """TAG[.postN.devDISTANCE] -- No -dirty. Exceptions: 1: no tags. 0.post0.devDISTANCE """ if pieces["closest-tag"]: if pieces["distance"]: # update the post release segment tag_version, post_version = pep440_split_post(pieces["closest-tag"]) rendered = tag_version if post_version is not None: rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"]) else: rendered += ".post0.dev%d" % (pieces["distance"]) else: # no commits, use the tag as the version rendered = pieces["closest-tag"] else: # exception #1 rendered = "0.post0.dev%d" % pieces["distance"] return rendered def render_pep440_post(pieces: Dict[str, Any]) -> str: """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards (a dirty tree will appear "older" than the corresponding clean one), but you shouldn't be releasing software with -dirty anyways. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%s" % pieces["short"] else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += "+g%s" % pieces["short"] return rendered def render_pep440_post_branch(pieces: Dict[str, Any]) -> str: """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . The ".dev0" means not master branch. Exceptions: 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["branch"] != "master": rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%s" % pieces["short"] if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["branch"] != "master": rendered += ".dev0" rendered += "+g%s" % pieces["short"] if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_old(pieces: Dict[str, Any]) -> str: """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" return rendered def render_git_describe(pieces: Dict[str, Any]) -> str: """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render_git_describe_long(pieces: Dict[str, Any]) -> str: """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. The distance/hash is unconditional. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered 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, } if not style or style == "default": style = "pep440" # the default if style == "pep440": rendered = render_pep440(pieces) elif style == "pep440-branch": rendered = render_pep440_branch(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) elif style == "pep440-post-branch": rendered = render_pep440_post_branch(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": rendered = render_git_describe(pieces) elif style == "git-describe-long": rendered = render_git_describe_long(pieces) else: raise ValueError("unknown style '%s'" % style) return { "version": rendered, "full-revisionid": pieces["long"], "dirty": pieces["dirty"], "error": None, "date": pieces.get("date"), } def get_versions() -> Dict[str, Any]: """Get version information or return default if unable to do so.""" # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have # __file__, we can work backwards from there to the root. Some # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which # case we can only use expanded keywords. cfg = get_config() verbose = cfg.verbose try: return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose) except NotThisMethod: pass try: root = os.path.realpath(__file__) # 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("/"): 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, } try: pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) return render(pieces, cfg.style) except NotThisMethod: pass try: if cfg.parentdir_prefix: return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) except NotThisMethod: pass return { "version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to compute version", "date": None, } PyLaTeX-1.4.2/pylatex/base_classes/000077500000000000000000000000001451425216500171265ustar00rootroot00000000000000PyLaTeX-1.4.2/pylatex/base_classes/__init__.py000066400000000000000000000011401451425216500212330ustar00rootroot00000000000000""" Baseclasses that can be used to create classes representing LaTeX objects. .. :copyright: (c) 2014 by Jelte Fennema. :license: MIT, see License for more details. """ from .command import ( Arguments, Command, CommandBase, Options, SpecialArguments, SpecialOptions, UnsafeCommand, ) from .containers import Container, ContainerCommand, Environment from .float import Float from .latex_object import LatexObject # Old names of the base classes for backwards compatibility BaseLaTeXClass = LatexObject BaseLaTeXContainer = Container BaseLaTeXNamedContainer = Environment PyLaTeX-1.4.2/pylatex/base_classes/command.py000066400000000000000000000254351451425216500211270ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module implements a class that implements a latex command. This can be used directly or it can be inherited to make an easier interface to it. .. :copyright: (c) 2014 by Jelte Fennema. :license: MIT, see License for more details. """ from reprlib import recursive_repr from ..utils import dumps_list from .latex_object import LatexObject class CommandBase(LatexObject): """A class that represents a LaTeX command. The name of this class (when lowercased) will be the name of this command. To supply a different name set the ``_latex_name`` attribute. """ def __init__(self, arguments=None, options=None, *, extra_arguments=None): r""" Args ---- arguments: None, str, list or `~.Arguments` The main arguments of the command. options: None, str, list or `~.Options` Options of the command. These are placed in front of the arguments. extra_arguments: None, str, list or `~.Arguments` Extra arguments for the command. When these are supplied the options will be placed before them instead of before the normal arguments. This allows for a way of having one or more arguments before the 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") super().__init__() def _set_parameters(self, parameters, argument_type): parameter_cls = Options if argument_type == "options" else Arguments if parameters is None: parameters = parameter_cls() elif not isinstance(parameters, parameter_cls): parameters = parameter_cls(parameters) # Pass on escaping to generated parameters parameters._default_escape = self._default_escape setattr(self, argument_type, parameters) def __key(self): """Return a hashable key, representing the command. Returns ------- tuple """ return (self.latex_name, self.arguments, self.options, self.extra_arguments) def __eq__(self, other): """Compare two commands. Args ---- other: `~.CommandBase` instance The command to compare this command to Returns ------- bool: If the two instances are equal """ if isinstance(other, CommandBase): return self.__key() == other.__key() return False def __hash__(self): """Calculate the hash of a command. Returns ------- int: The hash of the command """ return hash(self.__key()) def dumps(self): """Represent the command as a string in LaTeX syntax. Returns ------- str The LaTeX formatted command """ options = self.options.dumps() arguments = self.arguments.dumps() if self.extra_arguments is None: 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, ) class Command(CommandBase): """A class that represents a LaTeX command. This class is meant for one-off commands. When a command of the same type 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 ): r""" Args ---- command: str Name of the command arguments: None, str, list or `~.Arguments` The main arguments of the command. options: None, str, list or `~.Options` Options of the command. These are placed in front of the arguments. extra_arguments: None, str, list or `~.Arguments` Extra arguments for the command. When these are supplied the options will be placed before them instead of before the normal arguments. This allows for a way of having one or more arguments before the options. packages: list of `~.Package` instances A list of the packages that this command requires Examples -------- >>> Command('documentclass', >>> options=Options('12pt', 'a4paper', 'twoside'), >>> arguments='article').dumps() '\\documentclass[12pt,a4paper,twoside]{article}' >>> Command('com') '\\com' >>> Command('com', 'first') '\\com{first}' >>> Command('com', 'first', 'option') '\\com[option]{first}' >>> Command('com', 'first', 'option', 'second') '\\com{first}[option]{second}' """ self.latex_name = command super().__init__(arguments, options, extra_arguments=extra_arguments) if packages is not None: self.packages |= packages class UnsafeCommand(Command): """An unsafe version of the `Command` class. This class is meant for one-off commands that should not escape their arguments and options. Use this command with care and only use this when the arguments are hardcoded. When an unsafe command of the same type is used multiple times it is better to subclass `.CommandBase` and set the ``_default_escape`` attribute to false. """ _default_escape = False class Parameters(LatexObject): """The base class used by `~Options` and `~Arguments`. This class should probably never be used on its own and inhereting from it is only useful if a class like `~Options` or `~Arguments` is needed again. """ @recursive_repr() 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) + ")" def __init__(self, *args, **kwargs): r""" Args ---- \*args: Positional parameters \*\*kwargs: Keyword parameters """ if len(args) == 1 and not isinstance(args[0], str): 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__"): args = args[0] self._positional_args = list(args) self._key_value_args = dict(kwargs) super().__init__() def __key(self): """Generate a unique hashable key representing the parameter object. Returns ------- tuple """ return tuple(self._list_args_kwargs()) def __eq__(self, other): """Compare two parameters. Returns ------- bool """ return type(self) == type(other) and self.__key() == other.__key() def __hash__(self): """Generate a hash of the parameters. Returns ------- int """ return hash(self.__key()) def _format_contents(self, prefix, separator, suffix): """Format the parameters. The formatting is done using the three arguments suplied to this function. Arguments --------- prefix: str separator: str suffix: str Returns ------- str """ params = self._list_args_kwargs() if len(params) <= 0: return "" string = ( prefix + dumps_list(params, escape=self.escape, token=separator) + suffix ) return string def _list_args_kwargs(self): """Make a list of strings representing al parameters. Returns ------- list """ params = [] params.extend(self._positional_args) params.extend( ["{k}={v}".format(k=k, v=v) for k, v in self._key_value_args.items()] ) return params class Options(Parameters): """A class implementing LaTex options for a command. It supports normal positional parameters, as well as key-value pairs. Options are the part of a command located between the square brackets (``[]``). The positional parameters will be outputted in order and will appear before the key-value-pairs. The key value-pairs won't be outputted in the order in which they were entered Examples -------- >>> args = Options('a', 'b', 'c').dumps() '[a,b,c]' >>> Options('clip', width=50, height='25em', trim='1 2 3 4').dumps() '[clip,trim=1 2 3 4,width=50,height=25em]' """ def dumps(self): """Represent the parameters as a string in LaTeX syntax. This is to be appended to a command. Returns ------- str """ return self._format_contents("[", ",", "]") class SpecialOptions(Options): r"""A class that sepparates the options with '][' instead of ','.""" def dumps(self): """Represent the parameters as a string in LaTex syntax.""" return self._format_contents("[", "][", "]") class Arguments(Parameters): """A class implementing LaTex arguments for a command. It supports normal positional parameters, as well as key-value pairs. Arguments are the part of a command located between the curly braces (``{}``). The positional parameters will be outputted in order and will appear before the key-value-pairs. The key value-pairs won't be outputted in the order in which they were entered Examples -------- >>> args = Arguments('a', 'b', 'c').dumps() '{a}{b}{c}' >>> args = Arguments('clip', width=50, height='25em').dumps() >>> args.dumps() '{clip}{width=50}{height=25em}' """ def dumps(self): """Represent the parameters as a string in LaTeX syntax. This is to be appended to a command. Returns ------- str """ return self._format_contents("{", "}{", "}") class SpecialArguments(Arguments): """A class that separates arguments with ',' instead of '}{'.""" def dumps(self): """Represent the parameters as a string in LaTeX syntax. This is to be appended to a command. Returns ------- str """ return self._format_contents("{", ",", "}") PyLaTeX-1.4.2/pylatex/base_classes/containers.py000066400000000000000000000165571451425216500216630ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module implements LaTeX base classes that can be subclassed. .. :copyright: (c) 2014 by Jelte Fennema. :license: MIT, see License for more details. """ from collections import UserList from contextlib import contextmanager from pylatex.utils import dumps_list from .command import Arguments, Command from .latex_object import LatexObject class Container(LatexObject, UserList): """A base class that groups multiple LaTeX classes. This class should be subclassed when a LaTeX class has content that is of variable length. It subclasses UserList, so it holds a list of elements that can simply be accessed by using normal list functionality, like indexing or appending. """ content_separator = "%\n" def __init__(self, *, data=None): r""" Args ---- data: list, `~.LatexObject` or something that can be converted to a \ string The content with which the container is initialized """ if data is None: data = [] elif not isinstance(data, list): # If the data is not already a list make it a list, otherwise list # operations will not work data = [data] self.data = data self.real_data = data # Always the data of this instance super().__init__() @property def _repr_attributes(self): return super()._repr_attributes + ["real_data"] def dumps_content(self, **kwargs): r"""Represent the container as a string in LaTeX syntax. Args ---- \*\*kwargs: Arguments that can be passed to `~.dumps_list` Returns ------- string: A LaTeX string representing the container """ return dumps_list( self, escape=self.escape, token=self.content_separator, **kwargs ) def _propagate_packages(self): """Make sure packages get propagated.""" for item in self.data: if isinstance(item, LatexObject): if isinstance(item, Container): item._propagate_packages() for p in item.packages: self.packages.add(p) def dumps_packages(self): r"""Represent the packages needed as a string in LaTeX syntax. Returns ------- string: A LaTeX string representing the packages of the container """ self._propagate_packages() return super().dumps_packages() @contextmanager def create(self, child): """Add a LaTeX object to current container, context-manager style. Args ---- child: `~.Container` An object to be added to the current container """ prev_data = self.data self.data = child.data # This way append works appends to the child yield child # allows with ... as to be used as well self.data = prev_data self.append(child) class Environment(Container): r"""A base class for LaTeX environments. This class implements the basics of a LaTeX environment. A LaTeX environment looks like this: .. code-block:: latex \begin{environment_name} Some content that is in the environment \end{environment_name} The text that is used in the place of environment_name is by default the name of the class in lowercase. However, this default can be overridden in 2 ways: 1. setting the _latex_name class variable when declaring the class 2. setting the _latex_name attribute when initialising object """ #: Set to true if this full container should be equivalent to an empty #: string if it has no content. omit_if_empty = False def __init__(self, *, options=None, arguments=None, start_arguments=None, **kwargs): r""" Args ---- options: str or list or `~.Options` Options to be added to the ``\begin`` command arguments: str or list or `~.Arguments` Arguments to be added to the ``\begin`` command start_arguments: str or list or `~.Arguments` Arguments to be added before the options """ self.options = options self.arguments = arguments self.start_arguments = start_arguments super().__init__(**kwargs) def dumps(self): """Represent the environment as a string in LaTeX syntax. Returns ------- str A LaTeX string representing the environment. """ content = self.dumps_content() if not content.strip() and self.omit_if_empty: return "" string = "" # Something other than None needs to be used as extra arguments, that # way the options end up behind the latex_name argument. if self.arguments is None: extra_arguments = Arguments() else: extra_arguments = self.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() return string class Fragment(Container): r"""A LaTeX fragment container class for fragmented document construction. This only provides logical wrapping of the items. The final document will look the same as if all items would not have been part of a container. A common usecase of this is to generate a .tex snippet containing more than one LaTeX item item without any extra container around it. This snippet can then be included in another ``.tex`` file using ``\input{snippet.tex}`` """ def __init__(self, **kwargs): """ Args ---- """ super().__init__(**kwargs) def dumps(self): """Represent the fragment as a string in LaTeX syntax. Returns ------- str """ return self.dumps_content() class ContainerCommand(Container): r"""A base class for a container command (A command which contains data). Container command example: .. code-block:: latex \CommandName[options]{arguments}{ data } """ omit_if_empty = False def __init__(self, arguments=None, options=None, *, data=None, **kwargs): r""" Args ---- arguments: str or `list` The arguments for the container command options: str, list or `~.Options` The options for the preamble command data: str or `~.LatexObject` The data to place inside the preamble command """ self.arguments = arguments self.options = options super().__init__(data=data, **kwargs) def dumps(self): r"""Convert the container to a string in latex syntax.""" content = self.dumps_content() if not content.strip() and self.omit_if_empty: return "" string = "" start = Command(self.latex_name, arguments=self.arguments, options=self.options) string += start.dumps() + "{%\n" if content != "": string += content + "%\n}" else: string += "}" return string PyLaTeX-1.4.2/pylatex/base_classes/float.py000066400000000000000000000022561451425216500206120ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module implements the classes that deal with floating environments. .. :copyright: (c) 2014 by Jelte Fennema. :license: MIT, see License for more details. """ from . import Command, Environment class Float(Environment): """A class that represents a floating environment.""" #: By default floats are positioned inside a separate paragraph. #: Setting this to option to `False` will change that. separate_paragraph = True _repr_attributes_mapping = { "position": "options", } def __init__(self, *, position=None, **kwargs): """ Args ---- position: str Define the positioning of a floating environment, for instance ``'h'``. See the references for more information. References ---------- * https://www.sharelatex.com/learn/Positioning_of_Figures """ super().__init__(options=position, **kwargs) def add_caption(self, caption): """Add a caption to the float. Args ---- caption: str The text of the caption. """ self.append(Command("caption", caption)) PyLaTeX-1.4.2/pylatex/base_classes/latex_object.py000066400000000000000000000142321451425216500221450ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module implements the base LaTeX object. .. :copyright: (c) 2014 by Jelte Fennema. :license: MIT, see License for more details. """ from abc import ABCMeta, abstractmethod from inspect import getfullargspec from reprlib import recursive_repr from ordered_set import OrderedSet from ..utils import dumps_list class _CreatePackages(ABCMeta): def __init__(cls, name, bases, d): # noqa packages = OrderedSet() for b in bases: if hasattr(b, "packages"): packages |= b.packages if "packages" in d: packages |= d["packages"] cls.packages = packages super().__init__(name, bases, d) class LatexObject(metaclass=_CreatePackages): """The class that every other LaTeX class is a subclass of. This class implements the main methods that every LaTeX object needs. For conversion to LaTeX formatted strings it implements the dumps, dump and generate_tex methods. It also provides the methods that can be used to represent the packages required by the LatexObject. """ _latex_name = None _star_latex_name = False # latex_name + ('*' if True else '') #: Set this to an iterable to override the list of default repr #: attributes. _repr_attributes_override = None #: Set this to a dict to change some of the default repr attributes to #: other attributes. The key is the old one, the value the new one. _repr_attributes_mapping = None #: Set on a class to make instances default to a certain kind of escaping _default_escape = True #: Only set this directly by changing the cls.escape _escape = None @property def escape(self): """Determine whether or not to escape content of this class. This defaults to `True` for most classes. """ if self._escape is not None: return self._escape if self._default_escape is not None: return self._default_escape return True @escape.setter def escape(self, value): """Escape flag setter - to be used at object level.""" self._escape = value #: Start a new paragraph before this environment. begin_paragraph = False #: Start a new paragraph after this environment. end_paragraph = False #: Same as enabling `begin_paragraph` and `end_paragraph`, so #: effectively placing this element in its own paragraph. separate_paragraph = False def __init__(self): # TODO: only create a copy of packages when it will # Create a copy of the packages attribute, so changing it in an # instance will not change the class default. self.packages = self.packages.copy() @recursive_repr() def __repr__(self): """Create a printable representation of the object.""" 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) except AttributeError: raise e return (getattr_better(self, attr) for attr in self._repr_attributes) @property def _repr_attributes(self): """Return attributes that should be part of the repr string.""" if self._repr_attributes_override is None: # Default to init arguments attrs = getfullargspec(self.__init__).args[1:] mapping = self._repr_attributes_mapping if mapping: attrs = [mapping[a] if a in mapping else a for a in attrs] return attrs return self._repr_attributes_override @property def latex_name(self): """Return the name of the class used in LaTeX. It can be `None` when the class doesn't have a name. """ 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 @latex_name.setter def latex_name(self, value): self._latex_name = value @abstractmethod def dumps(self): """Represent the class as a string in LaTeX syntax. This method should be implemented by any class that subclasses this class. """ def dump(self, file_w): """Write the LaTeX representation of the class to a file. Args ---- file_w: io.TextIOBase The file object in which to save the data """ file_w.write(self.dumps()) def generate_tex(self, filepath): """Generate a .tex file. Args ---- filepath: str The name of the file (without .tex) """ with open(filepath + ".tex", "w", encoding="utf-8") as newf: self.dump(newf) def dumps_packages(self): """Represent the packages needed as a string in LaTeX syntax. Returns ------- list """ return dumps_list(self.packages) def dump_packages(self, file_w): """Write the LaTeX representation of the packages to a file. Args ---- file_w: io.TextIOBase The file object in which to save the data """ file_w.write(self.dumps_packages()) def dumps_as_content(self): """Create a string representation of the object as content. This is currently only used to add new lines before and after the output of the dumps function. These can be added or removed by changing the `begin_paragraph`, `end_paragraph` and `separate_paragraph` attributes of the class. """ string = self.dumps() if self.separate_paragraph or self.begin_paragraph: string = "\n\n" + string.lstrip("\n") if self.separate_paragraph or self.end_paragraph: string = string.rstrip("\n") + "\n\n" return string PyLaTeX-1.4.2/pylatex/basic.py000066400000000000000000000036741451425216500161440ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module implements several classes that represent basic latex commands. .. :copyright: (c) 2014 by Jelte Fennema. :license: MIT, see License for more details. """ from .base_classes import CommandBase, ContainerCommand, Environment from .package import Package class NewPage(CommandBase): """A command that adds a new page to the document.""" class LineBreak(NewPage): """A command that adds a line break to the document.""" class NewLine(NewPage): """A command that adds a new line to the document.""" class HFill(NewPage): """A command that fills the current line in the document.""" class HugeText(Environment): """An environment which makes the text size 'Huge'.""" _latex_name = "Huge" def __init__(self, data=None): """ Args ---- data : str or `~.LatexObject` The string or LatexObject to be formatted. """ super().__init__(data=data) class LargeText(HugeText): """An environment which makes the text size 'Large'.""" _latex_name = "Large" class MediumText(HugeText): """An environment which makes the text size 'large'.""" _latex_name = "large" class SmallText(HugeText): """An environment which makes the text size 'small'.""" _latex_name = "small" class FootnoteText(HugeText): """An environment which makes the text size 'footnotesize'.""" _latex_name = "footnotesize" class TextColor(ContainerCommand): """An environment which changes the text color of the data.""" _repr_attributes_mapping = {"color": "arguments"} packages = [Package("xcolor")] def __init__(self, color, data): """ Args ---- color: str The color to set for the data inside of the environment. data: str or `~.LatexObject` The string or LatexObject to be formatted. """ super().__init__(arguments=color, data=data) PyLaTeX-1.4.2/pylatex/config.py000066400000000000000000000052151451425216500163210ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module implements the ability to use of different configurations. The current active configuration is `pylatex.config.active`. This variable can simply be changed to another configuration and that will be used. It is also possible to use the `~.Version1.use` method to do this temporarily in a specific context. .. :copyright: (c) 2016 by Jelte Fennema. :license: MIT, see License for more details. """ from contextlib import contextmanager class Version1: """The config used to get the behaviour of v1.x.y of the library. The default attributes are:: indent = True booktabs = False microtype = False row_height = None """ indent = True booktabs = False microtype = False row_height = None def __init__(self, **kwargs): """ Args ---- kwargs: Key value pairs of the default attributes that should be overridden """ for k, v in kwargs.items(): setattr(self, k, v) @contextmanager def use(self): """Use the config temporarily in specific context. A simple usage example:: with Version1(indent=False).use(): # Do stuff where indent should be False ... """ global active prev = active active = self yield active = prev @contextmanager def change(self, **kwargs): """Override some attributes of the config in a specific context. A simple usage example:: with pylatex.config.active.change(indent=False): # Do stuff where indent should be False ... Args ---- kwargs: Key value pairs of the default attributes that should be overridden """ old_attrs = {} for k, v in kwargs.items(): old_attrs[k] = getattr(self, k, v) setattr(self, k, v) yield self # allows with ... as ... for k, v in old_attrs.items(): setattr(self, k, v) #: The default configuration in this release. Currently the same as `Version1` Default = Version1 class Version2(Version1): """The config used to get the behaviour of v2.x.y of the library. The default attributes are:: indent = False booktabs = True microtype = True row_height = 1.3 """ indent = False booktabs = True microtype = True row_height = 1.3 #: The default configuration in the nxt major release. Currently the same as #: `Version2`. NextMajor = Version2 #: The current active configuration active = Default() PyLaTeX-1.4.2/pylatex/document.py000066400000000000000000000331451451425216500166750ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module implements the class that deals with the full document. .. :copyright: (c) 2014 by Jelte Fennema. :license: MIT, see License for more details. """ import errno import os import subprocess import sys import pylatex.config as cf from .base_classes import ( Command, Container, Environment, LatexObject, SpecialArguments, UnsafeCommand, ) from .errors import CompilerError from .package import Package from .utils import NoEscape, dumps_list, rm_temp_dir class Document(Environment): r""" A class that contains a full LaTeX document. If needed, you can append stuff to the preamble or the packages. For instance, if you need to use ``\maketitle`` you can add the title, author and date commands to the preamble to make it work. """ 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 ---- default_filepath: str The default path to save files. documentclass: str or `~.Command` The LaTeX class of the document. document_options: str or `list` The options to supply to the documentclass fontenc: str The option for the fontenc package. If it is `None`, the fontenc package will not be loaded at all. inputenc: str The option for the inputenc package. If it is `None`, the inputenc package will not be loaded at all. font_size: str The font size to declare as normalsize lmodern: bool Use the Latin Modern font. This is a font that contains more glyphs than the standard LaTeX font. textcomp: bool Adds even more glyphs, for instance the Euro (€) sign. page_numbers: bool Adds the ability to add the last page to the document. indent: bool Determines whether or not the document requires indentation. If it is `None` it will use the value from the active config. Which is `True` by default. geometry_options: dict The options to supply to the geometry package data: list Initial content of the document. """ self.default_filepath = default_filepath if isinstance(documentclass, Command): self.documentclass = documentclass else: self.documentclass = Command( "documentclass", arguments=documentclass, options=document_options ) if indent is None: indent = cf.active.indent if microtype is None: microtype = cf.active.microtype # These variables are used by the __repr__ method self._fontenc = fontenc self._inputenc = inputenc self._lmodern = lmodern self._indent = indent self._microtype = microtype packages = [] if fontenc is not None: packages.append(Package("fontenc", options=fontenc)) if inputenc is not None: packages.append(Package("inputenc", options=inputenc)) if lmodern: packages.append(Package("lmodern")) if textcomp: packages.append(Package("textcomp")) if page_numbers: packages.append(Package("lastpage")) if not indent: packages.append(Package("parskip")) if microtype: packages.append(Package("microtype")) if geometry_options is not None: 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), ) ) 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.packages |= packages self.variables = [] self.preamble = [] if not page_numbers: self.change_document_style("empty") # No colors have been added to the document yet self.color = False self.meta_data = False self.append(Command(command=font_size)) def _propagate_packages(self): r"""Propogate packages. Make sure that all the packages included in the previous containers are part of the full list of packages. """ super()._propagate_packages() for item in self.preamble: if isinstance(item, LatexObject): if isinstance(item, Container): item._propagate_packages() for p in item.packages: self.packages.add(p) def dumps(self): """Represent the document as a string in LaTeX syntax. Returns ------- str """ 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() def generate_tex(self, filepath=None): """Generate a .tex file for the document. Args ---- filepath: str The name of the file (without .tex), if this is not supplied the default filepath attribute is used as the path. """ 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 ): """Generate a pdf file from the document. Args ---- filepath: str The name of the file (without .pdf), if it is `None` the ``default_filepath`` attribute will be used. clean: bool Whether non-pdf files created that are created during compilation should be removed. clean_tex: bool Also remove the generated tex file. compiler: `str` or `None` The name of the LaTeX compiler to use. If it is None, PyLaTeX will choose a fitting one on its own. Starting with ``latexmk`` and then ``pdflatex``. compiler_args: `list` or `None` Extra arguments that should be passed to the LaTeX compiler. If this is None it defaults to an empty list. silent: bool Whether to hide compiler output """ if compiler_args is None: compiler_args = [] # In case of newer python with the use of the cwd parameter # one can avoid to physically change the directory # to the destination folder python_cwd_available = sys.version_info >= (3, 6) filepath = self._select_filepath(filepath) if not os.path.basename(filepath): filepath = os.path.join(os.path.abspath(filepath), "default_basename") else: filepath = os.path.abspath(filepath) cur_dir = os.getcwd() dest_dir = os.path.dirname(filepath) if not python_cwd_available: os.chdir(dest_dir) self.generate_tex(filepath) if compiler is not None: compilers = ((compiler, []),) else: latexmk_args = ["--pdf"] compilers = (("latexmk", latexmk_args), ("pdflatex", [])) main_arguments = ["--interaction=nonstopmode", filepath + ".tex"] check_output_kwargs = {} if python_cwd_available: check_output_kwargs = {"cwd": dest_dir} os_error = None for compiler, arguments in compilers: command = [compiler] + arguments + compiler_args + main_arguments try: 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 if os_error.errno == errno.ENOENT: # If compiler does not exist, try next in the list continue raise except subprocess.CalledProcessError as e: # For all other errors print the output and raise the error print(e.output.decode()) raise else: if not silent: print(output.decode()) if clean: try: # Try latexmk cleaning first 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"] for ext in extensions: try: os.remove(filepath + "." + ext) except (OSError, IOError) as e: # Use FileNotFoundError when python 2 is dropped if e.errno != errno.ENOENT: raise rm_temp_dir() if clean_tex: os.remove(filepath + ".tex") # Remove generated tex file # Compilation has finished, so no further compilers have to be # tried break 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." ) ) if not python_cwd_available: os.chdir(cur_dir) def _select_filepath(self, filepath): """Make a choice between ``filepath`` and ``self.default_filepath``. Args ---- filepath: str the filepath to be compared with ``self.default_filepath`` Returns ------- str The selected 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) ) return filepath def change_page_style(self, style): r"""Alternate page styles of the current page. Args ---- style: str value to set for the page style of the current page """ self.append(Command("thispagestyle", arguments=style)) def change_document_style(self, style): r"""Alternate page style for the entire document. Args ---- style: str value to set for the document style """ self.append(Command("pagestyle", arguments=style)) def add_color(self, name, model, description): r"""Add a color that can be used throughout the document. Args ---- name: str Name to set for the color model: str The color model to use when defining the color description: str The values to use to define the color """ if self.color is False: self.packages.append(Package("color")) self.color = True 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. Args ---- parameter: str The name of the parameter to change the length for value: str The value to set the parameter to """ 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. Variables are defined before the preamble. If a variable with that name has already been set, the new value will override it for future uses. This is done by appending ``\renewcommand`` to the document. Args ---- name: str The name to set for the variable value: str The value to set for the variable """ name_arg = "\\" + name variable_exists = False for variable in self.variables: if name_arg == variable.arguments._positional_args[0]: variable_exists = True break if variable_exists: renew = Command( command="renewcommand", arguments=[NoEscape(name_arg), value] ) self.append(renew) else: new = Command(command="newcommand", arguments=[NoEscape(name_arg), value]) self.variables.append(new) PyLaTeX-1.4.2/pylatex/errors.py000066400000000000000000000010141451425216500163610ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module implements Error classes. .. :copyright: (c) 2015 by Rene Beckmann. :license: MIT, see License for more details. """ class PyLaTeXError(Exception): """A Base class for all PyLaTeX Exceptions.""" class CompilerError(PyLaTeXError): """A Base class for all PyLaTeX compiler related Exceptions.""" class TableError(PyLaTeXError): """A Base class for all errors concerning tables.""" class TableRowSizeError(TableError): """Error for wrong table row size.""" PyLaTeX-1.4.2/pylatex/figure.py000066400000000000000000000111121451425216500163260ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module implements the class that deals with graphics. .. :copyright: (c) 2014 by Jelte Fennema. :license: MIT, see License for more details. """ import posixpath import uuid from .base_classes import Float, UnsafeCommand from .package import Package from .utils import NoEscape, escape_latex, fix_filename, make_temp_dir 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") ): """Add an image to the figure. Args ---- filename: str Filename of the image. width: str The width of the image placement: str Placement of the figure, `None` is also accepted. """ if width is not None: if self.escape: width = escape_latex(width) width = "width=" + str(width) if placement is not None: self.append(placement) self.append( StandAloneGraphic(image_options=width, filename=fix_filename(filename)) ) def _save_plot(self, *args, extension="pdf", **kwargs): """Save the plot. Returns ------- str The basename with which the plot has been saved. """ import matplotlib.pyplot as plt tmp_path = make_temp_dir() 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): """Add the current Matplotlib plot to the figure. The plot that gets added is the one that would normally be shown when using ``plt.show()``. Args ---- args: Arguments passed to plt.savefig for displaying the plot. extension : str extension of image file indicating figure file type kwargs: Keyword arguments passed to plt.savefig for displaying the plot. In case these contain ``width`` or ``placement``, they will be used for the same purpose as in the add_image command. Namely the width and placement of the generated plot in the LaTeX document. """ add_image_kwargs = {} for key in ("width", "placement"): if key in kwargs: add_image_kwargs[key] = kwargs.pop(key) filename = self._save_plot(*args, extension=extension, **kwargs) self.add_image(filename, **add_image_kwargs) class SubFigure(Figure): """A class that represents a subfigure from the subcaption package.""" 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", } def __init__(self, width=NoEscape(r"0.45\linewidth"), **kwargs): """ Args ---- width: str Width of the subfigure itself. It needs a width because it is inside another figure. """ super().__init__(arguments=width, **kwargs) def add_image(self, filename, *, width=NoEscape(r"\linewidth"), placement=None): """Add an image to the subfigure. Args ---- filename: str Filename of the image. width: str Width of the image in LaTeX terms. placement: str Placement of the figure, `None` is also accepted. """ super().add_image(filename, width=width, placement=placement) class StandAloneGraphic(UnsafeCommand): r"""A class representing a stand alone image.""" _latex_name = "includegraphics" packages = [Package("graphicx")] _repr_attributes_mapping = {"filename": "arguments", "image_options": "options"} def __init__( self, filename, image_options=NoEscape(r"width=0.8\textwidth"), extra_arguments=None, ): r""" Args ---- filename: str The path to the image file image_options: str or `list` Specifies the options for the image (ie. height, width) """ arguments = [NoEscape(filename)] super().__init__( command=self._latex_name, arguments=arguments, options=image_options, extra_arguments=extra_arguments, ) PyLaTeX-1.4.2/pylatex/frames.py000066400000000000000000000007521451425216500163320ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module implements the classes that deal with adding frames. .. :copyright: (c) 2014 by Jelte Fennema. :license: MIT, see License for more details. """ from .base_classes import ContainerCommand, Environment from .package import Package class MdFramed(Environment): """A class that defines an mdframed environment.""" packages = [Package("mdframed")] class FBox(ContainerCommand): """A class that defines an fbox ContainerCommand.""" PyLaTeX-1.4.2/pylatex/headfoot.py000066400000000000000000000056151451425216500166510ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module implements the classes that deal with creating headers and footers. .. :copyright: (c) 2014 by Jelte Fennema. :license: MIT, see License for more details. """ from .base_classes import Command, ContainerCommand from .package import Package from .utils import NoEscape class PageStyle(ContainerCommand): r"""Allows the creation of new page styles.""" _latex_name = "fancypagestyle" packages = [Package("fancyhdr")] def __init__(self, name, *, header_thickness=0, footer_thickness=0, data=None): r""" Args ---- name: str The name of the page style header_thickness: float Value to set for the line under the header footer_thickness: float Value to set for the line over the footer data: str or `~.LatexObject` The data to place inside the PageStyle """ self.name = name super().__init__(data=data, arguments=self.name) self.change_thickness(element="header", thickness=header_thickness) self.change_thickness(element="footer", thickness=footer_thickness) # Clear the current header and footer self.append(Head()) self.append(Foot()) def change_thickness(self, element, thickness): r"""Change line thickness. Changes the thickness of the line under/over the header/footer to the specified thickness. Args ---- element: str the name of the element to change thickness for: header, footer thickness: float the thickness to set the line to """ if element == "header": 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"], ) ) def simple_page_number(): """Get a string containing commands to display the page number. Returns ------- str The latex string that displays the page number """ return NoEscape(r"Page \thepage\ of \pageref{LastPage}") class Head(ContainerCommand): r"""Allows the creation of headers.""" _latex_name = "fancyhead" def __init__(self, position=None, *, data=None): r""" Args ---- position: str the headers position: L, C, R data: str or `~.LatexObject` The data to place inside the Head element """ self.position = position super().__init__(data=data, options=position) class Foot(Head): r"""Allows the creation of footers.""" _latex_name = "fancyfoot" PyLaTeX-1.4.2/pylatex/labelref.py000066400000000000000000000060751451425216500166350ustar00rootroot00000000000000# -*- coding: utf-8 -*- """This module implements the label command and reference.""" from .base_classes import CommandBase, LatexObject from .package import Package 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 = s.translate(dict.fromkeys(map(ord, "&%$#_{}~^\\\n\xA0[]\":;' "))) return s class Marker(LatexObject): """A class that represents a marker (label/ref parameter).""" _repr_attributes_override = [ "name", "prefix", ] def __init__(self, name, prefix="", del_invalid_char=True): """ Args ---- name: str Name of the marker. prefix: str Prefix to add before the name (prefix:name). del_invalid_char: bool If True invalid and dangerous characters will be removed from the marker """ if del_invalid_char: prefix = _remove_invalid_char(prefix) name = _remove_invalid_char(name) self.prefix = prefix self.name = name def __str__(self): return ((self.prefix + ":") if self.prefix != "" else "") + self.name def dumps(self): """Represent the Marker as a string in LaTeX syntax. Returns ------- str """ return str(self) class RefLabelBase(CommandBase): """A class used as base for command that take a marker only.""" _repr_attributes_mapping = { "marker": "arguments", } def __init__(self, marker): """ Args ---- marker: Marker The marker to use with the label/ref. """ self.marker = marker super().__init__(arguments=(str(marker))) class Label(RefLabelBase): """A class that represents a label.""" class Ref(RefLabelBase): """A class that represents a reference.""" class Pageref(RefLabelBase): """A class that represents a page reference.""" class Eqref(RefLabelBase): """A class that represent a ref to a formulae.""" packages = [Package("amsmath")] class Cref(RefLabelBase): """A class that represent a cref (not a Cref).""" packages = [Package("cleveref")] class CrefUp(RefLabelBase): """A class that represent a Cref.""" packages = [Package("cleveref")] latex_name = "Cref" class Autoref(RefLabelBase): """A class that represent an autoref.""" packages = [Package("hyperref")] class Hyperref(CommandBase): """A class that represents an hyperlink to a label.""" _repr_attributes_mapping = { "marker": "options", "text": "arguments", } packages = [Package("hyperref")] def __init__(self, marker, text): """ Args ---- marker: Marker The marker to use with the label/ref. text: str The text that will be shown as a link to the label of the same marker. """ self.marker = marker super().__init__(options=(str(marker)), arguments=text) PyLaTeX-1.4.2/pylatex/lists.py000066400000000000000000000046071451425216500162160ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module implements the classes that deal with LaTeX lists. These lists are specifically enumerate, itemize and description. .. :copyright: (c) 2015 by Sean McLemon. :license: MIT, see License for more details. """ from pylatex.utils import NoEscape from .base_classes import Command, Environment, Options from .package import Package class List(Environment): """A base class that represents a list.""" #: List environments cause compile errors when they do not contain items. #: This is why they are omitted fully if they are empty. omit_if_empty = True def add_item(self, s): """Add an item to the list. Args ---- s: str or `~.LatexObject` The item itself. """ self.append(Command("item")) self.append(s) class Enumerate(List): """A class that represents an enumerate list.""" def __init__(self, enumeration_symbol=None, *, options=None, **kwargs): r""" Args ---- enumeration_symbol: str The enumeration symbol to use, see the `enumitem `_ documentation to see what can be used here. This argument is not escaped as it usually should usually contain commands, so do not use user input here. options: str or list or `.Options` Custom options to be added to the enumerate list. These options are merged with the options created by ``enumeration_symbol``. """ self._enumeration_symbol = enumeration_symbol if enumeration_symbol is not None: self.packages.add(Package("enumitem")) if options is not None: options = Options(options) else: options = Options() options._positional_args.append(NoEscape("label=" + enumeration_symbol)) super().__init__(options=options, **kwargs) class Itemize(List): """A class that represents an itemize list.""" class Description(List): """A class that represents a description list.""" def add_item(self, label, s): """Add an item to the list. Args ---- label: str Description of the item. s: str or `~.LatexObject` The item itself. """ self.append(Command("item", options=label)) self.append(s) PyLaTeX-1.4.2/pylatex/math.py000066400000000000000000000075011451425216500160050ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module implements the classes that deal with math. .. :copyright: (c) 2014 by Jelte Fennema. :license: MIT, see License for more details. """ from .base_classes import Command, Container, Environment from .package import Package class Alignat(Environment): """Class that represents a aligned equation 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")] def __init__(self, aligns=2, numbering=True, escape=None): """ Parameters ---------- aligns : int number of alignments numbering : bool Whether to number equations escape : bool if True, will escape strings """ self.aligns = aligns self.numbering = numbering self.escape = escape if not numbering: self._star_latex_name = True super().__init__(start_arguments=[str(int(aligns))]) class Math(Container): """A class representing a math environment.""" packages = [Package("amsmath")] content_separator = " " def __init__(self, *, inline=False, data=None, escape=None): r""" Args ---- data: list Content of the math container. inline: bool If the math should be displayed inline or not. escape : bool if True, will escape strings """ self.inline = inline self.escape = escape super().__init__(data=data) def dumps(self): """Return a LaTeX formatted string representing the object. Returns ------- str """ if self.inline: return "$" + self.dumps_content() + "$" return "\\[%\n" + self.dumps_content() + "%\n\\]" class VectorName(Command): """A class representing a named vector.""" _repr_attributes_mapping = { "name": "arguments", } def __init__(self, name): """ Args ---- name: str Name of the vector """ super().__init__("mathbf", arguments=name) class Matrix(Environment): """A class representing a matrix.""" packages = [Package("amsmath")] _repr_attributes_mapping = { "alignment": "arguments", } def __init__(self, matrix, *, mtype="p", alignment=None): r""" Args ---- matrix: `numpy.ndarray` instance The matrix to display mtype: str What kind of brackets are used around the matrix. The different options and their corresponding brackets are: p = ( ), b = [ ], B = { }, v = \| \|, V = \|\| \|\| alignment: str How to align the content of the cells in the matrix. This is ``c`` by default. References ---------- * https://en.wikibooks.org/wiki/LaTeX/Mathematics#Matrices_and_arrays """ import numpy # noqa, Sanity check if numpy is installed self.matrix = matrix self.latex_name = mtype + "matrix" self._mtype = mtype if alignment is not None: self.latex_name += "*" super().__init__(arguments=alignment) def dumps_content(self): """Return a string representing the matrix in LaTeX syntax. Returns ------- str """ import numpy as np string = "" shape = self.matrix.shape for (y, x), value in np.ndenumerate(self.matrix): if x: string += "&" string += str(value) if x == shape[1] - 1 and y != shape[0] - 1: string += r"\\" + "%\n" super().dumps_content() return string PyLaTeX-1.4.2/pylatex/package.py000066400000000000000000000012571451425216500164510ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module implements the class that deals with packages. .. :copyright: (c) 2014 by Jelte Fennema. :license: MIT, see License for more details. """ from .base_classes import CommandBase class Package(CommandBase): """A class that represents a package.""" _latex_name = "usepackage" _repr_attributes_mapping = { "name": "arguments", } def __init__(self, name, options=None): """ Args ---- name: str Name of the package. options: `str`, `list` or `~.Options` Options of the package. """ super().__init__(arguments=name, options=options) PyLaTeX-1.4.2/pylatex/position.py000066400000000000000000000113321451425216500167150ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module implements the classes that deal with positioning. Positions various elements on the page. .. :copyright: (c) 2014 by Jelte Fennema. :license: MIT, see License for more details. """ from .base_classes import Command, CommandBase, Environment, SpecialOptions from .package import Package from .utils import NoEscape class HorizontalSpace(CommandBase): """Add/remove the amount of horizontal space between elements.""" _latex_name = "hspace" _repr_attributes_mapping = {"size": "arguments"} def __init__(self, size, *, star=True): """ Args ---- size: str The amount of space to add star: bool Use the star variant of the command. Enabling this makes sure the space is also added where page breaking takes place. """ if star: self.latex_name += "*" super().__init__(arguments=size) class VerticalSpace(HorizontalSpace): """Add the user specified amount of vertical space to the document.""" _latex_name = "vspace" class Center(Environment): r"""Centered environment.""" packages = [Package("ragged2e")] class FlushLeft(Center): r"""Left-aligned environment.""" class FlushRight(Center): r"""Right-aligned environment.""" class MiniPage(Environment): r"""A class that allows the creation of minipages within document pages.""" packages = [Package("ragged2e")] _repr_attributes_mapping = { "width": "arguments", "pos": "options", "height": "options", "content_pos": "options", "align": "options", } def __init__( self, *, width=NoEscape(r"\textwidth"), pos=None, height=None, content_pos=None, align=None, fontsize=None, data=None ): r""" Args ---- width: str width of the minipage pos: str The vertical alignment of the minipage relative to the baseline (center(c), top(t), bottom(b)) height: str height of the minipage content_pos: str The position of the content inside the minipage (center(c), bottom(b), top(t), spread(s)) align: str alignment of the minibox fontsize: str The font size of the minipage data: str or `~.LatexObject` The data to place inside the MiniPage element """ options = [] if pos is not None: options.append(pos) 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): options.append(content_pos) options = SpecialOptions(*options) arguments = [NoEscape(str(width))] extra_data = [] if align is not None: if align == "l": extra_data.append(Command(command="flushleft")) elif align == "c": extra_data.append(Command(command="centering")) elif align == "r": extra_data.append(Command(command="flushright")) if fontsize is not None: extra_data.append(Command(command=fontsize)) if data is not None: if isinstance(data, list): data = extra_data + data else: data = extra_data + [data] else: data = extra_data super().__init__(arguments=arguments, options=options, data=data) class TextBlock(Environment): r"""A class that represents a textblock environment. Make sure to set lengths of TPHorizModule and TPVertModule """ _repr_attributes_mapping = {"width": "arguments"} packages = [Package("textpos")] def __init__(self, width, horizontal_pos, vertical_pos, *, indent=False, data=None): r""" Args ---- width: float Width of the text block in the units specified by TPHorizModule horizontal_pos: float Horizontal position in units specified by the TPHorizModule indent: bool Determines whether the text block has an indent before it vertical_pos: float Vertical position in units specified by the TPVertModule data: str or `~.LatexObject` The data to place inside the TextBlock element """ arguments = width self.horizontal_pos = horizontal_pos self.vertical_pos = vertical_pos super().__init__(arguments=arguments) self.append("(%s, %s)" % (str(self.horizontal_pos), str(self.vertical_pos))) if not indent: self.append(NoEscape(r"\noindent")) PyLaTeX-1.4.2/pylatex/quantities.py000066400000000000000000000112351451425216500172410ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module implements classes that deal with quantities. It converts the objects from the quantities package to latex strings that display them using the SIunitx package. Not all units work because of name differences between quantities and SIunitx. If you find one that doesn't work please create a pull request that adds it to the ``UNIT_NAME_TRANSLATIONS`` dictionary. .. :copyright: (c) 2015 by Björn Dahlgren. :license: MIT, see License for more details. """ from operator import itemgetter from .base_classes import Command from .package import Package from .utils import NoEscape, escape_latex # Translations for names used in the quantities package to ones used by SIunitx UNIT_NAME_TRANSLATIONS = { "Celsius": "celsius", "revolutions_per_minute": "rpm", "v": "volt", } def _dimensionality_to_siunitx(dim): import quantities as pq string = "" items = dim.items() for unit, power in sorted(items, key=itemgetter(1), reverse=True): if power < 0: substring = r"\per" power = -power elif power == 0: continue else: substring = "" 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 name = unit.name[len(prefix)] break else: # Otherwise simply use the full name name = unit.name try: # Check if the name is different in SIunitx name = UNIT_NAME_TRANSLATIONS[name] except KeyError: pass substring += "\\" + name if power > 1: substring += r"\tothe{" + str(power) + "}" string += substring return NoEscape(string) class Quantity(Command): """A class representing quantities.""" packages = [ Package("siunitx", options=[NoEscape("separate-uncertainty=true")]), NoEscape("\\DeclareSIUnit\\rpm{rpm}"), ] def __init__(self, quantity, *, options=None, format_cb=None): r""" Args ---- quantity: `quantities.quantity.Quantity` The quantity that should be displayed options: None, str, list or `~.Options` Options of the command. These are placed in front of the arguments. format_cb: callable A function which formats the number in the quantity. By default this uses `numpy.array_str`. Examples -------- >>> import quantities as pq >>> speed = 3.14159265 * pq.meter / pq.second >>> Quantity(speed, options={'round-precision': 3, ... 'round-mode': 'figures'}).dumps() '\\SI[round-mode=figures,round-precision=3]{3.14159265}{\meter\per\second}' Uncertainties are also handled: >>> length = pq.UncertainQuantity(16.0, pq.meter, 0.3) >>> width = pq.UncertainQuantity(16.0, pq.meter, 0.4) >>> Quantity(length*width).dumps() '\\SI{256.0 +- 0.5}{\meter\tothe{2}} Ordinary numbers are also supported: >>> Avogadro_constant = 6.022140857e23 >>> Quantity(Avogadro_constant, options={'round-precision': 3}).dumps() '\\num[round-precision=3]{6.022e23}' """ import numpy as np import quantities as pq self.quantity = quantity self._format_cb = format_cb def _format(val): if format_cb is None: try: return np.array_str(val) except AttributeError: return escape_latex(val) # Python float and int else: return format_cb(val) if isinstance(quantity, pq.UncertainQuantity): 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 ) else: 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: self.options._escape = False # siunitx uses dashes in kwargs PyLaTeX-1.4.2/pylatex/section.py000066400000000000000000000053011451425216500165140ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module implements the section type classes. .. :copyright: (c) 2014 by Jelte Fennema. :license: MIT, see License for more details. """ from .base_classes import Command, Container from .labelref import Label, Marker class Section(Container): """A class that represents a section.""" #: A section should normally start in its own paragraph end_paragraph = True #: Default prefix to use with Marker marker_prefix = "sec" #: Number the sections when the section element is compatible, #: by changing the `~.Section` class default all #: subclasses will also have the new default. numbering = True def __init__(self, title, numbering=None, *, label=True, **kwargs): """ Args ---- title: str The section title. numbering: bool Add a number before the section title. label: Label or bool or str Can set a label manually or use a boolean to set preference between automatic or no label """ self.title = title if numbering is not None: self.numbering = numbering if isinstance(label, Label): self.label = label elif isinstance(label, str): if ":" in label: label = label.split(":", 1) self.label = Label(Marker(label[1], label[0])) else: self.label = Label(Marker(label, self.marker_prefix)) elif label: self.label = Label(Marker(title, self.marker_prefix)) else: self.label = None super().__init__(**kwargs) def dumps(self): """Represent the section as a string in LaTeX syntax. Returns ------- str """ if not self.numbering: num = "*" else: 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() return string class Part(Section): """A class that represents a part.""" marker_prefix = "part" class Chapter(Section): """A class that represents a chapter.""" marker_prefix = "chap" class Subsection(Section): """A class that represents a subsection.""" marker_prefix = "subsec" class Subsubsection(Section): """A class that represents a subsubsection.""" marker_prefix = "ssubsec" class Paragraph(Section): """A class that represents a paragraph.""" marker_prefix = "para" class Subparagraph(Section): """A class that represents a subparagraph.""" marker_prefix = "subpara" PyLaTeX-1.4.2/pylatex/table.py000066400000000000000000000422271451425216500161470ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module implements the class that deals with tables. .. :copyright: (c) 2014 by Jelte Fennema. :license: MIT, see License for more details. """ import re from collections import Counter import pylatex.config as cf from .base_classes import ( Command, Container, Environment, Float, LatexObject, UnsafeCommand, ) from .errors import TableError, TableRowSizeError from .package import Package from .utils import NoEscape, _is_iterable, dumps_list # The letters used to count the table width COLUMN_LETTERS = {"l", "c", "r", "p", "m", "b", "X"} def _get_table_width(table_spec): """Calculate the width of a table based on its spec. Args ---- table_spec: str The LaTeX column specification for a table. Returns ------- int The width of a table which uses the specification supplied. """ # Remove things like {\bfseries} 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) spec_counter = Counter(cleaner_spec) return sum(spec_counter[l] for l in COLUMN_LETTERS) class Tabular(Environment): """A class that represents a tabular.""" _repr_attributes_mapping = { "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 ): """ Args ---- table_spec: str A string that represents how many columns a table should have and if it should contain vertical lines and where. pos: list row_height: float Specifies the heights of the rows in relation to the default row height col_space: str Specifies the spacing between table columns booktabs: bool Enable or disable booktabs style tables. These tables generally look nicer than regular tables. If this is `None` it will use the value of the ``booktabs`` attribte from the `~.active` configuration. This attribute is `False` by default. width: int The amount of columns that the table has. If this is `None` it is calculated based on the ``table_spec``, but this is only works for simple specs. In cases where this calculation is wrong override the width using this argument. References ---------- * https://en.wikibooks.org/wiki/LaTeX/Tables#The_tabular_environment """ if width is None: self.width = _get_table_width(table_spec) else: self.width = width if booktabs is None: booktabs = cf.active.booktabs self.booktabs = booktabs if self.booktabs: 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.col_space = col_space super().__init__( data=data, options=pos, arguments=NoEscape(table_spec), **kwargs ) # Parameter that determines if the xcolor package has been added. self.color = False def dumps(self): r"""Turn the Latex Object into a string in Latex format.""" string = "" if self.row_height is not None: 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" return string + super().dumps() def dumps_content(self, **kwargs): r"""Represent the content of the tabular in LaTeX syntax. This adds the top and bottomrule when using a booktabs style tabular. Args ---- \*\*kwargs: Arguments that can be passed to `~.dumps_list` Returns ------- string: A LaTeX string representing the """ content = "" if self.booktabs: content += "\\toprule%\n" content += super().dumps_content(**kwargs) if self.booktabs: content += "\\bottomrule%\n" return NoEscape(content) def add_hline(self, start=None, end=None, *, color=None, cmidruleoption=None): r"""Add a horizontal line to the table. Args ---- start: int At what cell the line should begin end: int At what cell the line should end color: str The hline color. cmidruleoption: str The option to be used for the booktabs cmidrule, i.e. the ``x`` in ``\cmidrule(x){1-3}``. """ if self.booktabs: hline = "midrule" cline = "cmidrule" if cmidruleoption is not None: cline += "(" + cmidruleoption + ")" else: hline = "hline" cline = "cline" if color is not None: if not self.color: self.packages.append(Package("xcolor", options="table")) self.color = True color_command = Command(command="arrayrulecolor", arguments=color) self.append(color_command) if start is None and end is None: self.append(Command(hline)) else: if start is None: start = 1 elif end is None: end = self.width 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"\\")) def add_row(self, *cells, color=None, escape=None, mapper=None, strict=True): """Add a row of cells to the table. Args ---- cells: iterable, such as a `list` or `tuple` There's two ways to use this method. The first method is to pass the content of each cell as a separate argument. The second method is to pass a single argument that is an iterable that contains each contents. color: str The name of the color used to highlight the row mapper: callable or `list` A function or a list of functions that should be called on all entries of the list after converting them to a string, for instance bold strict: bool Check for correct count of cells in row or not. """ if len(cells) == 1 and _is_iterable(cells): cells = cells[0] if escape is None: escape = self.escape # Propagate packages used in cells for c in cells: if isinstance(c, LatexObject): for p in c.packages: self.packages.add(p) # Count cell contents cell_count = 0 for c in cells: if isinstance(c, MultiColumn): cell_count += c.size else: cell_count += 1 if strict and cell_count != self.width: 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.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"\\") ) class Tabularx(Tabular): """A class that represents a tabularx environment.""" packages = [Package("tabularx")] def __init__(self, *args, width_argument=NoEscape(r"\textwidth"), **kwargs): """ Args ---- width_argument: The width of the table. By default the table is as wide as the text. """ super().__init__(*args, start_arguments=width_argument, **kwargs) class MultiColumn(Container): """A class that represents a multicolumn inside of a table.""" # TODO: Make this subclass of ContainerCommand def __init__(self, size, *, align="c", color=None, data=None): """ Args ---- size: int The amount of columns that this cell should fill. align: str How to align the content of the cell. data: str, list or `~.LatexObject` The content of the cell. color: str The color for the MultiColumn """ self.size = size self.align = align super().__init__(data=data) # Add a cell color to the MultiColumn if color is not None: self.packages.append(Package("xcolor", options="table")) color_command = Command("cellcolor", arguments=color) self.append(color_command) def dumps(self): """Represent the multicolumn as a string in LaTeX syntax. Returns ------- str """ args = [self.size, self.align, self.dumps_content()] string = Command(self.latex_name, args).dumps() return string class MultiRow(Container): """A class that represents a multirow in a table.""" # TODO: Make this subclass CommandBase and Container packages = [Package("multirow")] def __init__(self, size, *, width="*", color=None, data=None): """ Args ---- size: int The amount of rows that this cell should fill. width: str Width of the cell. The default is ``*``, which means the content's natural width. data: str, list or `~.LatexObject` The content of the cell. color: str The color for the MultiRow """ self.size = size self.width = width super().__init__(data=data) if color is not None: self.packages.append(Package("xcolor", options="table")) color_command = Command("cellcolor", arguments=color) self.append(color_command) def dumps(self): """Represent the multirow as a string in LaTeX syntax. Returns ------- str """ args = [self.size, self.width, self.dumps_content()] string = Command(self.latex_name, args).dumps() return string class Table(Float): """A class that represents a 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 ): """ Args ---- table_spec: str A string that represents how many columns a table should have and if it should contain vertical lines and where. pos: list row_height: float Specifies the heights of the rows in relation to the default row height col_space: str Specifies the spacing between table columns booktabs: bool Enable or disable booktabs style tables. These tables generally look nicer than regular tables. If this is `None` it will use the value of the ``booktabs`` attribte from the `~.active` configuration. This attribute is `False` by default. spread: str Specifies the Tabu table should add a given amount of 'padding' to the width of the table. This should be a latex dimension; for example: "0 pt" or "1in" to: str Specifies the Tabu table should extend to a given width. This should be a latex dimension; for example '4in' width: int The amount of columns that the table has. If this is `None` it is calculated based on the ``table_spec``, but this is only works for simple specs. In cases where this calculation is wrong override the width using this argument. References ---------- * 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 ) self._preamble = "" if spread: self._preamble = "spread " + spread elif to: self._preamble = "to " + to def dumps(self): """Turn the tabu object into a string in Latex format.""" _s = super().dumps() # Tabu tables support a unusual syntax: # \begin{tabu} spread 0pt {} # # Since this syntax isn't common, it doesn't make # sense to support it in the baseclass (e.g., Environment) # rather, here we fix the LaTeX string post-hoc if self._preamble: if _s.startswith(r"\begin{longtabu}"): _s = _s[:16] + self._preamble + _s[16:] 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)" ) return _s class LongTable(Tabular): """A class that represents a longtable (multipage table).""" packages = [Package("longtable")] header = False foot = False lastFoot = False # noqa, casing is needed for backwards compatibility def end_table_header(self): r"""End the table header which will appear on every page.""" if self.header: msg = "Table already has a header" raise TableError(msg) self.header = True self.append(Command(r"endhead")) def end_table_footer(self): r"""End the table foot which will appear on every page.""" if self.foot: msg = "Table already has a foot" raise TableError(msg) self.foot = True self.append(Command("endfoot")) def end_table_last_footer(self): r"""End the table foot which will appear on the last page.""" if self.lastFoot: msg = "Table already has a last foot" raise TableError(msg) self.lastFoot = True self.append(Command("endlastfoot")) class LongTabu(LongTable, Tabu): """A class that represents a longtabu (more flexible multipage table).""" class LongTabularx(Tabularx, LongTable): """A class that represents a long version of the tabularx environment. This uses the ``ltablex`` package. This package modifies the ``tabularx`` environment so that it can be spread over multiple pages. This has the sideeffect that using this class in a document spreads all `Tabularx` elements in that document over multiple pages as well. """ _latex_name = "tabularx" packages = [Package("ltablex")] class ColumnType(UnsafeCommand): r"""A class representing a new column type. It uses the ``\newcolumntype`` command, for a thorough explanation see `this StackExchange question `_. """ _repr_attributes_mapping = {"name": "arguments", "parameters": "options"} def __init__(self, name, base, modifications, *, parameters=None): """ Args ---- name: str The name of the new column type (a single letter) base: str The name of the column type that the new one is based on (a single letter) modifications: str The modifications to be made to the base column type parameters: int The number of # parameters inside the modifications string, if this is `None` this is calculated automatically. """ # repr vars self._base = base self._modifications = modifications COLUMN_LETTERS.add(name) 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, ) PyLaTeX-1.4.2/pylatex/tikz.py000066400000000000000000000373351451425216500160450ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module implements the classes used to show plots. .. :copyright: (c) 2014 by Jelte Fennema. :license: MIT, see License for more details. """ import math import re from .base_classes import Command, Container, Environment, LatexObject, Options from .package import Package class TikZOptions(Options): """Options class, do not escape.""" escape = False def append_positional(self, option): """Add a new positional option.""" self._positional_args.append(option) class TikZ(Environment): """Basic TikZ container class.""" _latex_name = "tikzpicture" packages = [Package("tikz")] class Axis(Environment): """PGFPlots axis container class, this contains plots.""" packages = [Package("pgfplots"), Command("pgfplotsset", "compat=newest")] def __init__(self, options=None, *, data=None): """ Args ---- options: str, list or `~.Options` Options to format the axis environment. """ super().__init__(options=options, data=data) class TikZScope(Environment): """TikZ Scope Environment.""" _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*\)" ) def __init__(self, x, y, relative=False): """ Args ---- x: float or int X coordinate y: float or int Y coordinate relative: bool Coordinate is relative or absolute """ self._x = float(x) self._y = float(y) self.relative = relative def __repr__(self): if self.relative: ret_str = "++" else: ret_str = "" return ret_str + "({},{})".format(self._x, self._y) def dumps(self): """Return representation.""" return self.__repr__() @classmethod def from_str(cls, coordinate): """Build a TikZCoordinate object from a string.""" m = cls._coordinate_str_regex.match(coordinate) if m is None: raise ValueError("invalid coordinate string") if m.group(1) == "++": relative = True else: relative = False return TikZCoordinate(float(m.group(2)), float(m.group(4)), relative=relative) def __eq__(self, other): if isinstance(other, tuple): # if comparing to a tuple, assume it to be an absolute coordinate. other_relative = False other_x = float(other[0]) other_y = float(other[1]) elif isinstance(other, TikZCoordinate): other_relative = other.relative other_x = other._x other_y = other._y else: raise TypeError("can only compare tuple and TiKZCoordinate types") # prevent comparison between relative and non relative # by returning False if other_relative != self.relative: return False # return comparison result 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") other_coord = other else: 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) 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) 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) ) class TikZObject(Container): """Abstract Class that most TikZ Objects inherits from.""" def __init__(self, options=None): """ Args ---- options: list Options pertaining to the object """ super(TikZObject, self).__init__() self.options = options class TikZNodeAnchor(LatexObject): """Representation of a node's anchor point.""" def __init__(self, node_handle, anchor_name): """ Args ---- node_handle: str Node's identifier anchor_name: str Name of the anchor """ self.handle = node_handle self.anchor = anchor_name def __repr__(self): return "({}.{})".format(self.handle, self.anchor) def dumps(self): """Return a representation. Alias for consistency.""" return self.__repr__() class TikZNode(TikZObject): """A class that represents a TiKZ node.""" _possible_anchors = ["north", "south", "east", "west"] def __init__(self, handle=None, options=None, at=None, text=None): """ Args ---- handle: str Node identifier options: list List of options at: TikZCoordinate Coordinate where node is placed text: str Body text of the node """ super(TikZNode, self).__init__(options=options) self.handle = handle if isinstance(at, (TikZCoordinate, type(None))): self._node_position = at else: raise TypeError( "at parameter must be an object of the" "TikzCoordinate class" ) self._node_text = text def dumps(self): """Return string representation of the node.""" ret_str = [] ret_str.append(Command("node", options=self.options).dumps()) if self.handle is not None: ret_str.append("({})".format(self.handle)) if self._node_position is not None: 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)) else: ret_str.append("{};") return " ".join(ret_str) def get_anchor_point(self, anchor_name): """Return an anchor point of the node, if it exists.""" if anchor_name in self._possible_anchors: return TikZNodeAnchor(self.handle, anchor_name) else: try: anchor = int(anchor_name.split("_")[1]) except: anchor = None if anchor is not None: return TikZNodeAnchor(self.handle, str(anchor)) raise ValueError('Invalid anchor name: "{}"'.format(anchor_name)) def __getattr__(self, attr_name): try: point = self.get_anchor_point(attr_name) return point except ValueError: pass # raise AttributeError( # 'Invalid attribute requested: "{}"'.format(attr_name)) class TikZUserPath(LatexObject): """Represents a possible TikZ path.""" def __init__(self, path_type, options=None): """ Args ---- path_type: str Type of path used options: Options List of options to add """ super(TikZUserPath, self).__init__() self.path_type = path_type self.options = options def dumps(self): """Return path command representation.""" ret_str = self.path_type if self.options is not None: ret_str += self.options.dumps() return ret_str class TikZPathList(LatexObject): """Represents a path drawing.""" _legal_path_types = ["--", "-|", "|-", "to", "rectangle", "circle", "arc", "edge"] def __init__(self, *args): """ Args ---- args: list A list of path elements """ self._last_item_type = None self._arg_list = [] # parse list and verify legality self._parse_arg_list(args) def append(self, item): """Add a new element to the current path.""" self._parse_next_item(item) def _parse_next_item(self, item): # assume first item is a point if self._last_item_type is None: try: self._add_point(item) except (TypeError, ValueError): # not a point, do something raise TypeError( "First element of path list must be a node identifier" " or coordinate" ) elif self._last_item_type == "point": # point after point is permitted, doesnt draw try: self._add_point(item) return except (ValueError, TypeError): # not a point, try path pass # will raise typeerror if wrong self._add_path(item) elif self._last_item_type == "path": # only point allowed after path original_exception = None try: self._add_point(item) return except (TypeError, ValueError) as ex: # check if trying to insert path after path try: self._add_path(item, parse_only=True) not_a_path = False original_exception = ex except (TypeError, ValueError) as ex: # not a path either! not_a_path = True original_exception = ex # disentangle exceptions if not_a_path is False: raise ValueError( "only a point descriptor can come" " after a path descriptor" ) if original_exception is not None: raise original_exception def _parse_arg_list(self, args): for item in args: self._parse_next_item(item) def _add_path(self, path, parse_only=False): if isinstance(path, str): if path in self._legal_path_types: _path = TikZUserPath(path) else: raise ValueError('Illegal user path type: "{}"'.format(path)) elif isinstance(path, TikZUserPath): _path = path else: 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" else: return _path def _add_point(self, point, parse_only=False): if isinstance(point, str): try: _item = TikZCoordinate.from_str(point) except ValueError: raise ValueError('Illegal point string: "{}"'.format(point)) elif isinstance(point, TikZCoordinate): _item = point elif isinstance(point, tuple): _item = TikZCoordinate(*point) elif isinstance(point, TikZNode): _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)) ) # add, finally if parse_only is False: self._arg_list.append(_item) self._last_item_type = "point" else: return _item def dumps(self): """Return representation of the path command.""" ret_str = [] for item in self._arg_list: if isinstance(item, TikZUserPath): ret_str.append(item.dumps()) elif isinstance(item, TikZCoordinate): ret_str.append(item.dumps()) elif isinstance(item, str): ret_str.append(item) return " ".join(ret_str) class TikZPath(TikZObject): r"""The TikZ \path command.""" def __init__(self, path=None, options=None): """ Args ---- path: TikZPathList A list of the nodes, path types in the path options: TikZOptions A list of options for the command """ super(TikZPath, self).__init__(options=options) if isinstance(path, TikZPathList): self.path = path elif isinstance(path, list): self.path = TikZPathList(*path) elif path is None: self.path = TikZPathList() else: raise TypeError('argument "path" can only be of types list or TikZPathList') def append(self, element): """Append a path element to the current list.""" self.path.append(element) def dumps(self): """Return a representation for the command.""" ret_str = [Command("path", options=self.options).dumps()] ret_str.append(self.path.dumps()) return " ".join(ret_str) + ";" class TikZDraw(TikZPath): """A draw command is just a path command with the draw option.""" def __init__(self, path=None, options=None): """ Args ---- path: TikZPathList A list of the nodes, path types in the path options: TikZOptions A list of options for the command """ super(TikZDraw, self).__init__(path=path, options=options) # append option if self.options is not None: self.options.append_positional("draw") else: self.options = TikZOptions("draw") class Plot(LatexObject): """A class representing a PGFPlot.""" packages = [Package("pgfplots"), Command("pgfplotsset", "compat=newest")] def __init__( self, name=None, func=None, coordinates=None, error_bar=None, options=None ): """ Args ---- name: str Name of the plot. func: str A function that should be plotted. coordinates: list A list of exact coordinates tat should be plotted. options: str, list or `~.Options` """ self.name = name self.func = func self.coordinates = coordinates self.error_bar = error_bar self.options = options super().__init__() def dumps(self): """Represent the plot as a string in LaTeX syntax. Returns ------- str """ string = Command("addplot", options=self.options).dumps() if self.coordinates is not None: string += " coordinates {%\n" if self.error_bar is None: for x, y in self.coordinates: # ie: "(x,y)" string += "(" + str(x) + "," + str(y) + ")%\n" else: 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" elif self.func is not None: string += "{" + self.func + "};%\n%\n" if self.name is not None: string += Command("addlegendentry", self.name).dumps() super().dumps() return string PyLaTeX-1.4.2/pylatex/utils.py000066400000000000000000000205331451425216500162140ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module implements some simple utility functions. .. :copyright: (c) 2014 by Jelte Fennema. :license: MIT, see License for more details. """ import os.path import shutil import tempfile 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"{]}", } _tmp_path = None def _is_iterable(element): return hasattr(element, "__iter__") and not isinstance(element, str) class NoEscape(str): """ A simple string class that is not escaped. When a `.NoEscape` string is added to another `.NoEscape` string it will produce a `.NoEscape` string. If it is added to normal string it will produce a normal string. Args ---- string: str The content of the `NoEscape` string. """ def __repr__(self): return "%s(%s)" % (self.__class__.__name__, self) def __add__(self, right): s = super().__add__(right) if isinstance(right, NoEscape): return NoEscape(s) return s def escape_latex(s): r"""Escape characters that are special in latex. Args ---- s : `str`, `NoEscape` or anything that can be converted to string The string to be escaped. If this is not a string, it will be converted to a string using `str`. If it is a `NoEscape` string, it will pass through unchanged. Returns ------- NoEscape The string, with special characters in latex escaped. Examples -------- >>> escape_latex("Total cost: $30,000") 'Total cost: \$30,000' >>> escape_latex("Issue #5 occurs in 30% of all cases") 'Issue \#5 occurs in 30\% of all cases' >>> print(escape_latex("Total cost: $30,000")) References ---------- * http://tex.stackexchange.com/a/34586/43228 * http://stackoverflow.com/a/16264094/2570866 """ if isinstance(s, NoEscape): return s return NoEscape("".join(_latex_special_chars.get(c, c) for c in str(s))) def fix_filename(path): r"""Fix filenames for use in LaTeX. Latex has problems if there are one or more points in the filename, thus 'abc.def.jpg' will be changed to '{abc.def}.jpg' Windows gets angry about the curly braces that resolve the above issue on linux Latex distributions. MikTeX however, has no qualms about multiple dots in the filename so the behavior is different for posix vs nt when the length of file_parts is greater than two. Args ---- filename : str The file name to be changed. Returns ------- str The new filename. Examples -------- >>> fix_filename("foo.bar.pdf") '{foo.bar}.pdf' >>> fix_filename("/etc/local/foo.bar.pdf") '/etc/local/{foo.bar}.pdf' >>> fix_filename("/etc/local/foo.bar.baz/document.pdf") '/etc/local/foo.bar.baz/document.pdf' >>> fix_filename("/etc/local/foo.bar.baz/foo~1/document.pdf") '\detokenize{/etc/local/foo.bar.baz/foo~1/document.pdf}' """ path_parts = path.split("/" if os.name == "posix" else "\\") dir_parts = path_parts[:-1] filename = path_parts[-1] file_parts = filename.split(".") 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) 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): r"""Try to generate a LaTeX string of a list that can contain anything. Args ---- l : list A list of objects to be converted into a single string. escape : bool Whether to escape special LaTeX characters in converted text. token : str The token (default is a newline) to separate objects in the list. mapper: callable or `list` A function, class or a list of functions/classes that should be called on all entries of the list after converting them to a string, for instance `~.bold` or `~.MediumText`. as_content: bool Indicates whether the items in the list should be dumped using `~.LatexObject.dumps_as_content` Returns ------- NoEscape A single LaTeX string. Examples -------- >>> dumps_list([r"\textbf{Test}", r"\nth{4}"]) '\\textbf{Test}%\n\\nth{4}' >>> print(dumps_list([r"\textbf{Test}", r"\nth{4}"])) \textbf{Test} \nth{4} >>> print(pylatex.utils.dumps_list(["There are", 4, "lights!"])) There are 4 lights! >>> print(dumps_list(["$100%", "True"], escape=True)) \$100\% True """ 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): mapper = [mapper] for m in mapper: strings = [m(s) for s in strings] strings = [_latex_item_to_string(s) for s in strings] return NoEscape(token.join(strings)) def _latex_item_to_string(item, *, escape=False, as_content=False): """Use the render method when possible, otherwise uses str. Args ---- item: object An object that needs to be converted to a string escape: bool Flag that indicates if escaping is needed as_content: bool Indicates whether the item should be dumped using `~.LatexObject.dumps_as_content` Returns ------- NoEscape Latex """ if isinstance(item, pylatex.base_classes.LatexObject): if as_content: return item.dumps_as_content() else: return item.dumps() elif not isinstance(item, str): item = str(item) if escape: item = escape_latex(item) return item def bold(s, *, escape=True): r"""Make a string appear bold in LaTeX formatting. bold() wraps a given string in the LaTeX command \textbf{}. Args ---- s : str The string to be formatted. escape: bool If true the bold text will be escaped Returns ------- NoEscape The formatted string. Examples -------- >>> bold("hello") '\\textbf{hello}' >>> print(bold("hello")) \textbf{hello} """ if escape: s = escape_latex(s) return NoEscape(r"\textbf{" + s + "}") def italic(s, *, escape=True): r"""Make a string appear italicized in LaTeX formatting. italic() wraps a given string in the LaTeX command \textit{}. Args ---- s : str The string to be formatted. escape: bool If true the italic text will be escaped Returns ------- NoEscape The formatted string. Examples -------- >>> italic("hello") '\\textit{hello}' >>> print(italic("hello")) \textit{hello} """ if escape: s = escape_latex(s) return NoEscape(r"\textit{" + s + "}") def verbatim(s, *, delimiter="|"): r"""Make the string verbatim. Wraps the given string in a \verb LaTeX command. Args ---- s : str The string to be formatted. delimiter : str How to designate the verbatim text (default is a pipe | ) Returns ------- NoEscape The formatted string. Examples -------- >>> verbatim(r"\renewcommand{}") '\\verb|\\renewcommand{}|' >>> print(verbatim(r"\renewcommand{}")) \verb|\renewcommand{}| >>> print(verbatim('pi|pe', '!')) \verb!pi|pe! """ return NoEscape(r"\verb" + delimiter + s + delimiter) def make_temp_dir(): """Create a temporary directory if it doesn't exist. Returns ------- str The absolute filepath to the created temporary directory. Examples -------- >>> make_temp_dir() '/var/folders/g9/ct5f3_r52c37rbls5_9nc_qc0000gn/T/pylatex' """ global _tmp_path if not _tmp_path: _tmp_path = tempfile.mkdtemp(prefix="pylatex-tmp.") return _tmp_path def rm_temp_dir(): """Remove the temporary directory specified in ``_tmp_path``.""" global _tmp_path if _tmp_path: shutil.rmtree(_tmp_path) _tmp_path = None PyLaTeX-1.4.2/pyproject.toml000066400000000000000000000004531451425216500157270ustar00rootroot00000000000000[tool.isort] profile = 'black' skip = ['.bzr', '.direnv', '.eggs', '.git', '.hg', '.mypy_cache', '.nox', '.pants.d', '.svn', '.tox', '.venv', '__pypackages__', '_build', 'buck-out', 'build', 'dist', 'node_modules', 'venv', 'versioneer.py', 'src'] [tool.black] extend-exclude = 'versioneer\.py|src' PyLaTeX-1.4.2/release.sh000077500000000000000000000027211451425216500147720ustar00rootroot00000000000000#!/bin/bash set -e if [ "$#" -ne 1 ]; then echo ERROR: Please supply the version number exit 1 fi if [[ "$1" != v* ]]; then echo ERROR: The version number should start with a v exit 1 fi if [[ -n $(git status --porcelain) ]]; then echo "ERROR: repo is dirty, please commit everything" exit 1 fi if ! grep "$1" docs/source/changelog.rst > /dev/null; then echo "ERROR: You forgot to update the changelog" exit 1 fi ./testall.sh set -x git tag "$1" -a -m '' cd docs/gh-pages git pull git submodule update --init cd .. ./create_doc_files.sh make clean make html cd gh-pages git add -A git commit -m "Updating docs to version $1" while true; do read -rp "Going to irreversibly release stuff now as $1. Are you sure y/n?" yn case $yn in [Yy]* ) break;; [Nn]* ) exit;; * ) echo "Please answer yes or no.";; esac done git push git submodule add --force ../PyLaTeX.git "version_submodules/$1" cd version_submodules/"$1" git checkout gh-pages git pull cd ../../ ln -s "version_submodules/$1/latest/" "$1" rm current ln -s "$1" current git add -A git commit -m "Updated symlinks for version $1" while true; do read -rp "Going to irreversibly release stuff now as $1. Are you sure y/n?" yn case $yn in [Yy]* ) break;; [Nn]* ) exit;; * ) echo "Please answer yes or no.";; esac done git push cd ../.. git push git push --tags rm -rf dist python setup.py sdist twine upload dist/* PyLaTeX-1.4.2/setup.cfg000066400000000000000000000022041451425216500146300ustar00rootroot00000000000000[metadata] description-file = README.rst [versioneer] VCS = git style = pep440 versionfile_source = pylatex/_version.py versionfile_build = pylatex/_version.py tag_prefix = v parentdir_prefix = PyLaTeX- # Docstrings are not needed for magic methods. # A blank line after a function doc string should always be okay. # Class docstrings should not be preceded by an empty line. # Whitespace around arithmetic operators is not manditory, sometimes no # whitespace makes precedence clearer. [flake8] ignore = D105,D202,D203,D413,E226,D205,D400,D414 putty-ignore = # Stuff is exported in the __init__ files so ignore unused imports */__init__.py : +F401 # __init__ methods should describe arguments only /def __init__/ : +D205,D400,D401 # Empty __init__ doesn't need argument description /def __init__\(self\)/ : +D102,D107 # No docstrings are needed in tests */tests/*.py : +D1 tests/*.py : +D1 # Examples only need module docstrings */examples/*.py : +D101,D102,D103 examples/*.py : +D101,D102,D103 # These generated files were causing some errors pylatex/_version.py : +N versioneer.py : +N PyLaTeX-1.4.2/setup.py000066400000000000000000000103331451425216500145230ustar00rootroot00000000000000try: from setuptools import setup from setuptools.command.egg_info import egg_info from setuptools.command.install import install except ImportError: from distutils.core import setup import errno import os import subprocess import sys import versioneer cmdclass = versioneer.get_cmdclass() version = versioneer.get_version() if sys.version_info[:2] <= (2, 6): raise RuntimeError( "You're using Python <= 2.6, but this package requires either Python " "2.7, or 3.3 or above, so you can't use it unless you upgrade your " "Python version." ) if sys.version_info[:2] <= (3, 5): dependencies = ["ordered-set<4.0.0"] else: 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", "black", "isort"], "packaging": ["twine"], } if sys.version_info[0] == 3: 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" else: 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])) # Automatically convert the source from Python 3 to Python 2 if we need to. class CustomInstall(install): def run(self): convert_to_py2() install.run(self) class CustomEggInfo(egg_info): def initialize_options(self): convert_to_py2() egg_info.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 "+" 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"]) 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." ) else: 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", ], ) PyLaTeX-1.4.2/testall.sh000077500000000000000000000051251451425216500150230ustar00rootroot00000000000000#!/usr/bin/env bash # This script executes all the examples and tests # run as: testall.sh [-p COMMAND] [clean] # Optional positional arguments # -c: cleans up the latex files generated # Optional named arguments: # -p COMMAND: the python command that should be used, e.g. ./testall.sh -p python3 # # Default values python="python" # Check if a command line argument was provided as an input argument. while getopts ":p:cdh" opt; do case $opt in p) python=$OPTARG ;; c) clean=TRUE ;; d) nodoc=TRUE ;; h) echo This runs all the tests and examples and checks for pep8 compliance echo echo Options: echo ' -c cleans up the latex and pdf files generated' echo ' -p COMMAND the python command that should be used to run the tests' echo " -d don't execute the doc tests, they can take long" exit 0 ;; \?) echo "Invalid option: -$OPTARG" >&2 exit 1 ;; :) echo "Option -$OPTARG requires an argument." >&2 exit 1 ;; esac done # Run the examples and tests python_version=$($python --version |& sed 's|Python \(.\).*|\1|g' | head -n 1) # Run the examples and tests python_version_long=$($python --version |& sed 's|Python \(.*\)|\1|g' | head -n 1) if [ "$python_version" = '3' ]; then # Check code guidelines echo -e '\e[32mChecking for code style errors \e[0m' if ! black --check .; then exit 1 fi if ! isort --check .; then exit 1 fi fi if [ "$python_version" = '2' ]; then main_folder=python2_source cd $main_folder else main_folder=. fi echo -e '\e[32mTesting tests directory\e[0m' if ! $python "$(command -v pytest)" --cov=pylatex tests/*; then exit 1 fi mv .coverage{,.tests} if [ "$python_version" = '2' ]; then cd .. fi count=0 for f in "$main_folder"/examples/*.py; do echo -e '\e[32mTesting '"$f"'\e[0m' if ! $python "$(command -v coverage)" run "$f"; then exit 1 fi ((count ++)) mv .coverage .coverage.example$count done coverage combine if [ "$clean" = 'TRUE' ]; then rm -- *.pdf *.log *.aux *.tex *.fls *.fdb_latexmk > /dev/null fi if [[ "$nodoc" != 'TRUE' && "$python_version" == "3" && "$python_version_long" != 3.3.* && "$python_version_long" != 3.4.* ]]; then echo -e '\e[32mChecking for errors in docs and docstrings\e[0m' cd docs set -e ./create_doc_files.sh -p "$python" make clean set +e if ! $python "$(command -v sphinx-build)" -b html -d build/doctrees/ source build/html -nW; then exit 1 fi fi PyLaTeX-1.4.2/tests/000077500000000000000000000000001451425216500141535ustar00rootroot00000000000000PyLaTeX-1.4.2/tests/test_args.py000077500000000000000000000264671451425216500165420ustar00rootroot00000000000000#!/usr/bin/python """ Test to check when arguments of functions get changed. This test calls functions with all available arguments to check whether they still exist. An error from this file means that the public API has been changed. """ import matplotlib import numpy as np import quantities as pq from pylatex import ( Axis, Center, ColumnType, Command, Description, Document, Enumerate, FBox, Figure, FlushLeft, FlushRight, Foot, FootnoteText, Head, HFill, HorizontalSpace, HugeText, Hyperref, Itemize, LargeText, LineBreak, LongTable, Marker, Math, Matrix, MdFramed, MediumText, MiniPage, MultiColumn, MultiRow, NewLine, NewPage, Package, PageStyle, Plot, Quantity, Section, SmallText, StandAloneGraphic, SubFigure, TableRowSizeError, Tabu, Tabular, Tabularx, TextBlock, TextColor, TikZ, TikZCoordinate, TikZDraw, TikZNode, TikZNodeAnchor, TikZOptions, TikZPath, TikZPathList, TikZScope, TikZUserPath, VectorName, VerticalSpace, ) from pylatex.utils import ( NoEscape, bold, dumps_list, escape_latex, fix_filename, italic, verbatim, ) matplotlib.use("Agg") # Not to use X server. For TravisCI. import matplotlib.pyplot as pyplot # noqa def test_document(): geometry_options = { "includeheadfoot": True, "headheight": "12pt", "headsep": "10pt", "landscape": True, } doc = Document( default_filepath="default_filepath", documentclass="article", fontenc="T1", inputenc="utf8", lmodern=True, data=None, page_numbers=True, indent=False, document_options=["a4paper", "12pt"], geometry_options=geometry_options, ) repr(doc) doc.append("Some text.") doc.change_page_style(style="empty") doc.change_document_style(style="plain") doc.add_color(name="lightgray", model="gray", description="0.6") doc.add_color(name="abitless", model="gray", description="0.8") doc.set_variable(name="myVar", value="1234") doc.set_variable(name="myVar", value="1234") doc.change_length(parameter=r"\headheight", value="0.5in") doc.generate_tex(filepath="") doc.generate_pdf(filepath="", clean=True) def test_section(): sec = Section(title="", numbering=True, data=None) repr(sec) def test_hyperref(): hr = Hyperref(Marker("marker", "prefix"), "text") repr(hr) def test_math(): math = Math(data=None, inline=False) repr(math) vec = VectorName(name="") repr(vec) # Numpy m = np.matrix([[2, 3, 4], [0, 0, 1], [0, 0, 2]]) matrix = Matrix(matrix=m, mtype="p", alignment=None) repr(matrix) def test_table(): # Tabular t = Tabular(table_spec="|c|c|", data=None, pos=None, width=2) t.add_hline(start=None, end=None) t.add_row((1, 2), escape=False, strict=True, mapper=[bold]) t.add_row(1, 2, escape=False, strict=True, mapper=[bold]) # MultiColumn/MultiRow. t.add_row((MultiColumn(size=2, align="|c|", data="MultiColumn"),), strict=True) # One multiRow-cell in that table would not be proper LaTeX, # so strict is set to False t.add_row((MultiRow(size=2, width="*", data="MultiRow"),), strict=False) repr(t) # TabularX tabularx = Tabularx(table_spec="X X X", width_argument=NoEscape(r"\textwidth")) tabularx.add_row(["test1", "test2", "test3"]) # Long Table longtable = LongTable(table_spec="c c c") longtable.add_row(["test", "test2", "test3"]) longtable.end_table_header() # Colored Tabu coloredtable = Tabu(table_spec="X[c] X[c]") coloredtable.add_row(["test", "test2"], color="gray", mapper=bold) # Colored Tabu with 'spread' coloredtable = Tabu(table_spec="X[c] X[c]", spread="1in") coloredtable.add_row(["test", "test2"], color="gray", mapper=bold) # Colored Tabu with 'to' coloredtable = Tabu(table_spec="X[c] X[c]", to="5in") coloredtable.add_row(["test", "test2"], color="gray", mapper=bold) # Colored Tabularx coloredtable = Tabularx(table_spec="X[c] X[c]") coloredtable.add_row(["test", "test2"], color="gray", mapper=bold) # Column column = ColumnType("R", "X", r"\raggedleft", parameters=2) repr(column) def test_command(): c = Command(command="documentclass", arguments=None, options=None, packages=None) repr(c) def test_graphics(): f = Figure(data=None, position=None) f.add_image(filename="", width=r"0.8\textwidth", placement=r"\centering") f.add_caption(caption="") repr(f) # Subfigure s = SubFigure(data=None, position=None, width=r"0.45\linewidth") s.add_image(filename="", width="r\linewidth", placement=None) s.add_caption(caption="") repr(s) # Matplotlib plot = Figure(data=None, position=None) x = [0, 1, 2, 3, 4, 5, 6] y = [15, 2, 7, 1, 5, 6, 9] pyplot.plot(x, y) plot.add_plot(width=r"0.8\textwidth", placement=r"\centering") plot.add_caption(caption="I am a caption.") repr(plot) # StandAloneGraphic stand_alone_graphic = StandAloneGraphic( filename="", image_options=r"width=0.8\textwidth" ) repr(stand_alone_graphic) def test_quantities(): # Quantities Quantity(quantity=1 * pq.kg) q = Quantity(quantity=1 * pq.kg, format_cb=lambda x: str(int(x))) repr(q) def test_package(): # Package p = Package(name="", options=None) repr(p) def test_tikz(): # PGFPlots t = TikZ(data=None) repr(t) a = Axis(data=None, options=None) repr(a) p = Plot(name=None, func=None, coordinates=None, error_bar=None, options=None) repr(p) opt = TikZOptions(None) repr(opt) scope = TikZScope(data=None) repr(scope) c = TikZCoordinate.from_str("(0,0)") c = TikZCoordinate(x=0, y=0, relative=False) d = c + (0, 1) e = c - (0, 1) f = (0, 1) + c c.distance_to(d) repr(c) repr(d) repr(e) repr(f) bool(c == (1, 1)) bool(c == TikZCoordinate(1, 1)) bool(TikZCoordinate(1, 1, relative=True) == (1, 1)) bool(TikZCoordinate(1, 1, relative=False) == (1, 1)) bool(TikZCoordinate(1, 1, relative=True) == TikZCoordinate(1, 1, relative=False)) # test expected to fail try: g = TikZCoordinate(0, 1, relative=True) + TikZCoordinate(1, 0, relative=False) repr(g) raise Exception except ValueError: pass a = TikZNodeAnchor(node_handle=None, anchor_name=None) repr(a) n = TikZNode(handle=None, options=None, at=None, text=None) repr(n) p = n.get_anchor_point("north") repr(p) p = n.get_anchor_point("_180") repr(p) p = n.west repr(p) up = TikZUserPath(path_type="edge", options=TikZOptions("bend right")) repr(up) pl = TikZPathList("(0, 1)", "--", "(2, 0)") pl.append((0.5, 0)) repr(pl) # generate a failure, illegal start try: pl = TikZPathList("--", "(0, 1)") raise Exception except TypeError: pass # fail with illegal path type try: pl = TikZPathList("(0, 1)", "illegal", "(0, 2)") raise Exception except ValueError: pass # fail with path after path try: pl = TikZPathList("(0, 1)", "--", "--") raise Exception except ValueError: pass # other type of failure: illegal identifier after path try: pl = TikZPathList("(0, 1)", "--", "illegal") raise Exception except (ValueError, TypeError): pass pt = TikZPath(path=None, options=TikZOptions("->")) pt.append(TikZCoordinate(0, 1, relative=True)) repr(pt) pt = TikZPath(path=[n.west, "edge", TikZCoordinate(0, 1, relative=True)]) repr(pt) pt = TikZPath(path=pl, options=None) repr(pt) dr = TikZDraw(path=None, options=None) repr(dr) def test_lists(): # Lists itemize = Itemize() itemize.add_item(s="item") itemize.append("append") repr(itemize) enum = Enumerate(enumeration_symbol=r"\alph*)", options={"start": 172}) enum.add_item(s="item") enum.add_item(s="item2") enum.append("append") repr(enum) desc = Description() desc.add_item(label="label", s="item") desc.append("append") repr(desc) def test_headfoot(): # Page styles, headers and footers page_style = PageStyle("NewStyle") page_style.change_thickness("header", "1pt") page_style.change_thickness("footer", "1pt") header = Head("C") header.append("append") footer = Foot("C") footer.append("append") page_style.append(header) page_style.append(footer) repr(header) repr(footer) repr(page_style) def test_position(): repr(HorizontalSpace(size="20pt", star=False)) repr(VerticalSpace(size="20pt", star=True)) # Test alignment environments center = Center() center.append("append") repr(center) right = FlushRight() right.append("append") repr(right) left = FlushLeft() left.append("append") repr(left) minipage = MiniPage( width=r"\textwidth", height="10pt", pos="t", align="r", content_pos="t", fontsize="Large", ) minipage.append("append") repr(minipage) textblock = TextBlock( width="200", horizontal_pos="200", vertical_pos="200", indent=True ) textblock.append("append") textblock.dumps() repr(textblock) def test_frames(): # Tests the framed environments md_framed = MdFramed() md_framed.append("Framed text") repr(md_framed) f_box = FBox() f_box.append("Fboxed text") repr(f_box) def test_basic(): # Tests the basic commands and environments # Basic commands new_page = NewPage() repr(new_page) new_line = NewLine() repr(new_line) line_break = LineBreak() repr(line_break) h_fill = HFill() repr(h_fill) # Basic environments huge = HugeText("Huge") huge.append("Huge 2") repr(huge) large = LargeText("Large") large.append("Large 2") repr(large) medium = MediumText("Medium") medium.append("Medium 2") repr(medium) small = SmallText("Small") small.append("Small 2") repr(small) footnote = FootnoteText("Footnote") footnote.append("Footnote 2") repr(footnote) text_color = TextColor("green", "GreenText") text_color.append("More Green Text") repr(text_color) def test_utils(): # Utils escape_latex(s="") fix_filename(path="") dumps_list(l=[], escape=False, token="\n") bold(s="") italic(s="") verbatim(s="", delimiter="|") def test_errors(): # Errors # TableRowSizeError # General test try: raise TableRowSizeError except TableRowSizeError: pass # Positive test, expected to raise Error t = Tabular(table_spec="|c|c|", data=None, pos=None) # TODO: this does not actually check if the error is raised try: # Wrong number of cells in table should raise an exception t.add_row((1, 2, 3), escape=False, strict=True) except TableRowSizeError: pass # Negative test, should not raise try: # Wrong number with strict=False should not raise an exception t.add_row((1, 2, 3), escape=False, strict=False) except TableRowSizeError: raise PyLaTeX-1.4.2/tests/test_config.py000066400000000000000000000015401451425216500170310ustar00rootroot00000000000000#!/usr/bin/env python """ Test to check if configuration changes have effect. .. :copyright: (c) 2016 by Jelte Fennema. :license: MIT, see License for more details. """ import pylatex.config as cf from pylatex import Document def test(): assert type(cf.active) == cf.Default cf.active = cf.Version1() assert cf.active.indent assert Document()._indent cf.active = cf.Version1(indent=False) assert not cf.active.indent assert not Document()._indent with cf.Version1().use(): assert cf.active.indent assert Document()._indent assert not cf.active.indent assert not Document()._indent with cf.active.change(indent=True): assert cf.active.indent assert Document()._indent assert not cf.active.indent assert not Document()._indent if __name__ == "__main__": test() PyLaTeX-1.4.2/tests/test_environment.py000066400000000000000000000010011451425216500201200ustar00rootroot00000000000000#!/usr/bin/python """Test to validate that Environments uphold contract of base classes.""" from pylatex.base_classes import Environment def test_alltt(): class AllTT(Environment): escape = False content_separator = "\n" alltt = AllTT() alltt.append("This is alltt content\nIn two lines") s = alltt.dumps() assert s.startswith("\\begin{alltt}\nThis is"), "Unexpected start of environment" assert s.endswith("two lines\n\\end{alltt}"), "Unexpected end of environment" PyLaTeX-1.4.2/tests/test_forced_dumps_implementation.py000066400000000000000000000004071451425216500233440ustar00rootroot00000000000000from pytest import raises from pylatex.base_classes import LatexObject class BadObject(LatexObject): pass def test_latex_object(): with raises(TypeError): LatexObject() def test_bad_object(): with raises(TypeError): BadObject() PyLaTeX-1.4.2/tests/test_inheritance.py000066400000000000000000000005121451425216500200530ustar00rootroot00000000000000import unittest from pylatex import Document class TestInheritance(unittest.TestCase): def test_latex_name(self): class MyDoc(Document): def __init__(self): super().__init__() doc = Document() my_doc = MyDoc() self.assertEqual(my_doc.latex_name, doc.latex_name) PyLaTeX-1.4.2/tests/test_jobname.py000077500000000000000000000014111451425216500171770ustar00rootroot00000000000000#!/usr/bin/env python import os import shutil from pylatex import Document def test(): doc = Document("jobname_test", data=["Jobname test"]) doc.generate_pdf() assert os.path.isfile("jobname_test.pdf") os.remove("jobname_test.pdf") folder = "tmp_jobname" os.makedirs(folder) path = os.path.join(folder, "jobname_test_dir") doc = Document(path, data=["Jobname test dir"]) doc.generate_pdf() assert os.path.isfile(path + ".pdf") shutil.rmtree(folder) folder = "tmp_jobname2" os.makedirs(folder) path = os.path.join(folder, "jobname_test_dir2") doc = Document(path, data=["Jobname test dir"]) doc.generate_pdf(os.path.join(folder, "")) assert os.path.isfile(path + ".pdf") shutil.rmtree(folder) PyLaTeX-1.4.2/tests/test_no_fontenc.py000066400000000000000000000006331451425216500177160ustar00rootroot00000000000000# -*- coding: utf-8 -*- r"""A test to make sure the document compiles with fontenc set to `None`.""" from pylatex import Document from pylatex.base_classes import Arguments doc = Document("no_fontenc", fontenc=None) doc.append("test text") # Make sure fontenc isn't used assert not any([p.arguments == Arguments("fontenc") for p in doc.packages]) doc.generate_pdf(clean=True, clean_tex=False, silent=False) PyLaTeX-1.4.2/tests/test_no_inputenc.py000066400000000000000000000006401451425216500201050ustar00rootroot00000000000000# -*- coding: utf-8 -*- r"""A test to make sure the document compiles with inputenc set to `None`.""" from pylatex import Document from pylatex.base_classes import Arguments doc = Document("no_inputenc", inputenc=None) doc.append("test text") # Make sure inputenc isn't used assert not any([p.arguments == Arguments("inputenc") for p in doc.packages]) doc.generate_pdf(clean=True, clean_tex=False, silent=False) PyLaTeX-1.4.2/tests/test_no_list_as_data.py000066400000000000000000000006511451425216500207110ustar00rootroot00000000000000from pylatex import Command, Document, Section, Subsection def test(): doc = Document() Subsection("Only a single string", data="Some words") sec1 = Section("Only contains one subsection", data="Subsection") sec2 = Section("Only a single italic command", data=Command("textit", "Hey")) sec2.append("something else that is not italic") doc.append(sec1) doc.append(sec2) doc.generate_pdf() PyLaTeX-1.4.2/tests/test_no_lmodern.py000066400000000000000000000004061451425216500177200ustar00rootroot00000000000000# -*- coding: utf-8 -*- r"""A test to make sure the document compiles with lmodern set to `False`.""" from pylatex import Document doc = Document("no_lmodern", lmodern=False) doc.append("test text") doc.generate_pdf(clean=True, clean_tex=False, silent=False) PyLaTeX-1.4.2/tests/test_pictures.py000066400000000000000000000007061451425216500174250ustar00rootroot00000000000000#!/usr/bin/env python import os from pylatex import Document, Section from pylatex.figure import Figure def test(): doc = Document() section = Section("Multirow Test") figure = Figure() image_filename = os.path.join(os.path.dirname(__file__), "../examples/kitten.jpg") figure.add_image(image_filename) figure.add_caption("Whoooo an imagage of a pdf") section.append(figure) doc.append(section) doc.generate_pdf() PyLaTeX-1.4.2/tests/test_quantities.py000066400000000000000000000020261451425216500177520ustar00rootroot00000000000000# -*- coding: utf-8 -*- import quantities as pq from pylatex.quantities import Quantity, _dimensionality_to_siunitx def test_quantity(): v = 1 * pq.m / pq.s q1 = Quantity(v) assert q1.dumps() == r"\SI{1.0}{\meter\per\second}" q2 = Quantity(v, format_cb=lambda x: str(int(x))) assert q2.dumps() == r"\SI{1}{\meter\per\second}" q3 = Quantity(v, options={"zero-decimal-to-integer": "true"}) ref = r"\SI[zero-decimal-to-integer=true]{1.0}{\meter\per\second}" assert q3.dumps() == ref def test_quantity_float(): q1 = Quantity(42.0) assert q1.dumps() == r"\num{42.0}" def test_quantity_uncertain(): t = pq.UncertainQuantity(7.0, pq.second, 1.0) q1 = Quantity(t) assert q1.dumps() == r"\SI{7.0 +- 1.0}{\second}" def test_dimensionality_to_siunitx(): assert ( _dimensionality_to_siunitx((pq.volt / pq.kelvin).dimensionality) == r"\volt\per\Kelvin" ) if __name__ == "__main__": test_quantity() test_quantity_uncertain() test_dimensionality_to_siunitx() PyLaTeX-1.4.2/tests/test_utils_dumps_list.py000066400000000000000000000005101451425216500211630ustar00rootroot00000000000000#!/usr/bin/env python from pylatex.basic import MediumText from pylatex.utils import dumps_list def test_mapper(): assert ( dumps_list(["Test", "text"], mapper=MediumText) == """\\begin{large}% Test% \\end{large}% \\begin{large}% text% \\end{large}""" ) if __name__ == "__main__": test_mapper() PyLaTeX-1.4.2/tests/test_utils_escape_latex.py000066400000000000000000000012201451425216500214340ustar00rootroot00000000000000#!/usr/bin/env python from pylatex import Document, Section from pylatex.utils import escape_latex def test(): doc = Document("utils_escape_latex") section = Section("Escape LaTeX characters test") text = escape_latex( """\ & (ampersand) % (percent) $ (dollar) # (number) _ (underscore) { (left curly brace) } (right curly brace) ~ (tilde) ^ (caret) \\ (backslash) --- (three minuses) a\xA0a (non breaking space) [ (left bracket) ] (right bracket) """ ) section.append(text) doc.append(section) doc.generate_pdf() if __name__ == "__main__": test() PyLaTeX-1.4.2/tests/test_utils_fix_filename.py000066400000000000000000000025321451425216500214340ustar00rootroot00000000000000#!/usr/bin/env python import os from pylatex.utils import fix_filename def test_no_dots(): fname = "aaa" assert fix_filename(fname) == fname def test_one_dot(): fname = "aa.a" assert fix_filename(fname) == fname def test_two_dots(): fname = "aa.a.a" original_os_name = os.name try: os.name = "posix" assert fix_filename(fname) == "{aa.a}.a" os.name = "nt" assert fix_filename(fname) == "aa.a.a" finally: os.name = original_os_name def test_three_dots(): fname = "abc.def.fgh.ijk" assert fix_filename(fname) == "{abc.def.fgh}.ijk" def test_path_and_three_dots(): fname = "/auu/bcd/abc.def.fgh.ijk" assert fix_filename(fname) == "/auu/bcd/{abc.def.fgh}.ijk" def test_dots_in_path_none_in_filename(): fname = "/au.u/b.c.d/abc" assert fix_filename(fname) == "/au.u/b.c.d/abc" def test_dots_in_path_one_in_filename(): fname = "/au.u/b.c.d/abc.def" assert fix_filename(fname) == "/au.u/b.c.d/abc.def" def test_dots_in_path_and_multiple_in_filename(): fname = "/au.u/b.c.d/abc.def.fgh.ijk" assert fix_filename(fname) == "/au.u/b.c.d/{abc.def.fgh}.ijk" def test_tilde_in_filename(): fname = "/etc/local/foo.bar.baz/foo~1/document.pdf" assert ( fix_filename(fname) == "\detokenize{/etc/local/foo.bar.baz/foo~1/document.pdf}" ) PyLaTeX-1.4.2/tests/test_utils_latex_item_to_string.py000066400000000000000000000011671451425216500232340ustar00rootroot00000000000000#!/usr/bin/env python from pylatex.base_classes import LatexObject from pylatex.utils import _latex_item_to_string TEST_STR = "hello" def test_string(): name = "abc" assert _latex_item_to_string(name) == name def test_user_latex_object(): class TestLatexObject(LatexObject): def dumps(self): return TEST_STR assert _latex_item_to_string(TestLatexObject()) == TEST_STR def test_foreign_object(): class ForeignObject: def dumps(self): return 15 def __str__(self): return TEST_STR assert _latex_item_to_string(ForeignObject()) == TEST_STR PyLaTeX-1.4.2/versioneer.py000066400000000000000000002512251451425216500155530ustar00rootroot00000000000000 # Version: 0.29 """The Versioneer - like a rocketeer, but for versions. The Versioneer ============== * like a rocketeer, but for versions! * https://github.com/python-versioneer/python-versioneer * Brian Warner * License: Public Domain (Unlicense) * Compatible with: Python 3.7, 3.8, 3.9, 3.10, 3.11 and pypy3 * [![Latest Version][pypi-image]][pypi-url] * [![Build Status][travis-image]][travis-url] This is a tool for managing a recorded version number in setuptools-based python projects. The goal is to remove the tedious and error-prone "update the embedded version string" step from your release process. Making a new release should be as easy as recording a new tag in your version-control system, and maybe making new tarballs. ## Quick Install Versioneer provides two installation modes. The "classic" vendored mode installs a copy of versioneer into your repository. The experimental build-time dependency mode is intended to allow you to skip this step and simplify the process of upgrading. ### Vendored mode * `pip install versioneer` to somewhere in your $PATH * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is available, so you can also use `conda install -c conda-forge versioneer` * add a `[tool.versioneer]` section to your `pyproject.toml` or a `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md)) * Note that you will need to add `tomli; python_version < "3.11"` to your build-time dependencies if you use `pyproject.toml` * run `versioneer install --vendor` in your source tree, commit the results * verify version information with `python setup.py version` ### Build-time dependency mode * `pip install versioneer` to somewhere in your $PATH * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is available, so you can also use `conda install -c conda-forge versioneer` * add a `[tool.versioneer]` section to your `pyproject.toml` or a `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md)) * add `versioneer` (with `[toml]` extra, if configuring in `pyproject.toml`) to the `requires` key of the `build-system` table in `pyproject.toml`: ```toml [build-system] requires = ["setuptools", "versioneer[toml]"] build-backend = "setuptools.build_meta" ``` * run `versioneer install --no-vendor` in your source tree, commit the results * verify version information with `python setup.py version` ## Version Identifiers Source trees come from a variety of places: * a version-control system checkout (mostly used by developers) * a nightly tarball, produced by build automation * a snapshot tarball, produced by a web-based VCS browser, like github's "tarball from tag" feature * a release tarball, produced by "setup.py sdist", distributed through PyPI Within each source tree, the version identifier (either a string or a number, this tool is format-agnostic) can come from a variety of places: * ask the VCS tool itself, e.g. "git describe" (for checkouts), which knows about recent "tags" and an absolute revision-id * the name of the directory into which the tarball was unpacked * an expanded VCS keyword ($Id$, etc) * a `_version.py` created by some earlier build step For released software, the version identifier is closely related to a VCS tag. Some projects use tag names that include more than just the version string (e.g. "myproject-1.2" instead of just "1.2"), in which case the tool needs to strip the tag prefix to extract the version identifier. For unreleased software (between tags), the version identifier should provide enough information to help developers recreate the same tree, while also giving them an idea of roughly how old the tree is (after version 1.2, before version 1.3). Many VCS systems can report a description that captures this, for example `git describe --tags --dirty --always` reports things like "0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the 0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has uncommitted changes). The version identifier is used for multiple purposes: * to allow the module to self-identify its version: `myproject.__version__` * to choose a name and prefix for a 'setup.py sdist' tarball ## Theory of Operation Versioneer works by adding a special `_version.py` file into your source tree, where your `__init__.py` can import it. This `_version.py` knows how to dynamically ask the VCS tool for version information at import time. `_version.py` also contains `$Revision$` markers, and the installation process marks `_version.py` to have this marker rewritten with a tag name during the `git archive` command. As a result, generated tarballs will contain enough information to get the proper version. To allow `setup.py` to compute a version too, a `versioneer.py` is added to the top level of your source tree, next to `setup.py` and the `setup.cfg` that configures it. This overrides several distutils/setuptools commands to compute the version when invoked, and changes `setup.py build` and `setup.py sdist` to replace `_version.py` with a small static file that contains just the generated version data. ## Installation See [INSTALL.md](./INSTALL.md) for detailed installation instructions. ## Version-String Flavors Code which uses Versioneer can learn about its version string at runtime by importing `_version` from your main `__init__.py` file and running the `get_versions()` function. From the "outside" (e.g. in `setup.py`), you can import the top-level `versioneer.py` and run `get_versions()`. Both functions return a dictionary with different flavors of version information: * `['version']`: A condensed version string, rendered using the selected style. This is the most commonly used value for the project's version string. The default "pep440" style yields strings like `0.11`, `0.11+2.g1076c97`, or `0.11+2.g1076c97.dirty`. See the "Styles" section below for alternative styles. * `['full-revisionid']`: detailed revision identifier. For Git, this is the full SHA1 commit id, e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac". * `['date']`: Date and time of the latest `HEAD` commit. For Git, it is the commit date in ISO 8601 format. This will be None if the date is not available. * `['dirty']`: a boolean, True if the tree has uncommitted changes. Note that this is only accurate if run in a VCS checkout, otherwise it is likely to be False or None * `['error']`: if the version string could not be computed, this will be set to a string describing the problem, otherwise it will be None. It may be useful to throw an exception in setup.py if this is set, to avoid e.g. creating tarballs with a version string of "unknown". Some variants are more useful than others. Including `full-revisionid` in a bug report should allow developers to reconstruct the exact code being tested (or indicate the presence of local changes that should be shared with the developers). `version` is suitable for display in an "about" box or a CLI `--version` output: it can be easily compared against release notes and lists of bugs fixed in various releases. The installer adds the following text to your `__init__.py` to place a basic version in `YOURPROJECT.__version__`: from ._version import get_versions __version__ = get_versions()['version'] del get_versions ## Styles The setup.cfg `style=` configuration controls how the VCS information is rendered into a version string. The default style, "pep440", produces a PEP440-compliant string, equal to the un-prefixed tag name for actual releases, and containing an additional "local version" section with more detail for in-between builds. For Git, this is TAG[+DISTANCE.gHEX[.dirty]] , using information from `git describe --tags --dirty --always`. For example "0.11+2.g1076c97.dirty" indicates that the tree is like the "1076c97" commit but has uncommitted changes (".dirty"), and that this commit is two revisions ("+2") beyond the "0.11" tag. For released software (exactly equal to a known tag), the identifier will only contain the stripped tag, e.g. "0.11". Other styles are available. See [details.md](details.md) in the Versioneer source tree for descriptions. ## Debugging Versioneer tries to avoid fatal errors: if something goes wrong, it will tend to return a version of "0+unknown". To investigate the problem, run `setup.py version`, which will run the version-lookup code in a verbose mode, and will display the full contents of `get_versions()` (including the `error` string, which may help identify what went wrong). ## Known Limitations Some situations are known to cause problems for Versioneer. This details the most significant ones. More can be found on Github [issues page](https://github.com/python-versioneer/python-versioneer/issues). ### Subprojects Versioneer has limited support for source trees in which `setup.py` is not in the root directory (e.g. `setup.py` and `.git/` are *not* siblings). The are two common reasons why `setup.py` might not be in the root: * Source trees which contain multiple subprojects, such as [Buildbot](https://github.com/buildbot/buildbot), which contains both "master" and "slave" subprojects, each with their own `setup.py`, `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI distributions (and upload multiple independently-installable tarballs). * Source trees whose main purpose is to contain a C library, but which also provide bindings to Python (and perhaps other languages) in subdirectories. Versioneer will look for `.git` in parent directories, and most operations should get the right version string. However `pip` and `setuptools` have bugs and implementation details which frequently cause `pip install .` from a subproject directory to fail to find a correct version string (so it usually defaults to `0+unknown`). `pip install --editable .` should work correctly. `setup.py install` might work too. Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in some later version. [Bug #38](https://github.com/python-versioneer/python-versioneer/issues/38) is tracking this issue. The discussion in [PR #61](https://github.com/python-versioneer/python-versioneer/pull/61) describes the issue from the Versioneer side in more detail. [pip PR#3176](https://github.com/pypa/pip/pull/3176) and [pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve pip to let Versioneer work correctly. Versioneer-0.16 and earlier only looked for a `.git` directory next to the `setup.cfg`, so subprojects were completely unsupported with those releases. ### Editable installs with setuptools <= 18.5 `setup.py develop` and `pip install --editable .` allow you to install a project into a virtualenv once, then continue editing the source code (and test) without re-installing after every change. "Entry-point scripts" (`setup(entry_points={"console_scripts": ..})`) are a convenient way to specify executable scripts that should be installed along with the python package. These both work as expected when using modern setuptools. When using setuptools-18.5 or earlier, however, certain operations will cause `pkg_resources.DistributionNotFound` errors when running the entrypoint script, which must be resolved by re-installing the package. This happens when the install happens with one version, then the egg_info data is regenerated while a different version is checked out. Many setup.py commands cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into a different virtualenv), so this can be surprising. [Bug #83](https://github.com/python-versioneer/python-versioneer/issues/83) describes this one, but upgrading to a newer version of setuptools should probably resolve it. ## Updating Versioneer To upgrade your project to a new release of Versioneer, do the following: * install the new Versioneer (`pip install -U versioneer` or equivalent) * edit `setup.cfg` and `pyproject.toml`, if necessary, to include any new configuration settings indicated by the release notes. See [UPGRADING](./UPGRADING.md) for details. * re-run `versioneer install --[no-]vendor` in your source tree, to replace `SRC/_version.py` * commit any changed files ## Future Directions This tool is designed to make it easily extended to other version-control systems: all VCS-specific components are in separate directories like src/git/ . The top-level `versioneer.py` script is assembled from these components by running make-versioneer.py . In the future, make-versioneer.py will take a VCS name as an argument, and will construct a version of `versioneer.py` that is specific to the given VCS. It might also take the configuration arguments that are currently provided manually during installation by editing setup.py . Alternatively, it might go the other direction and include code from all supported VCS systems, reducing the number of intermediate scripts. ## Similar projects * [setuptools_scm](https://github.com/pypa/setuptools_scm/) - a non-vendored build-time dependency * [minver](https://github.com/jbweston/miniver) - a lightweight reimplementation of versioneer * [versioningit](https://github.com/jwodder/versioningit) - a PEP 518-based setuptools plugin ## License To make Versioneer easier to embed, all its code is dedicated to the public domain. The `_version.py` that it creates is also in the public domain. Specifically, both are released under the "Unlicense", as described in https://unlicense.org/. [pypi-image]: https://img.shields.io/pypi/v/versioneer.svg [pypi-url]: https://pypi.python.org/pypi/versioneer/ [travis-image]: https://img.shields.io/travis/com/python-versioneer/python-versioneer.svg [travis-url]: https://travis-ci.com/github/python-versioneer/python-versioneer """ # pylint:disable=invalid-name,import-outside-toplevel,missing-function-docstring # pylint:disable=missing-class-docstring,too-many-branches,too-many-statements # pylint:disable=raise-missing-from,too-many-lines,too-many-locals,import-error # pylint:disable=too-few-public-methods,redefined-outer-name,consider-using-with # pylint:disable=attribute-defined-outside-init,too-many-arguments import configparser import errno import json import os import re import subprocess import sys from pathlib import Path from typing import Any, Callable, cast, Dict, List, Optional, Tuple, Union from typing import NoReturn import functools have_tomllib = True if sys.version_info >= (3, 11): import tomllib else: try: import tomli as tomllib except ImportError: have_tomllib = False class VersioneerConfig: """Container for Versioneer configuration parameters.""" VCS: str style: str tag_prefix: str versionfile_source: str versionfile_build: Optional[str] parentdir_prefix: Optional[str] verbose: Optional[bool] def get_root() -> str: """Get the project root directory. We require that all commands are run from the project root, i.e. the directory that contains setup.py, setup.cfg, and versioneer.py . """ root = os.path.realpath(os.path.abspath(os.getcwd())) setup_py = os.path.join(root, "setup.py") pyproject_toml = os.path.join(root, "pyproject.toml") versioneer_py = os.path.join(root, "versioneer.py") if not ( os.path.exists(setup_py) or os.path.exists(pyproject_toml) or os.path.exists(versioneer_py) ): # allow 'python path/to/setup.py COMMAND' root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0]))) setup_py = os.path.join(root, "setup.py") pyproject_toml = os.path.join(root, "pyproject.toml") versioneer_py = os.path.join(root, "versioneer.py") if not ( os.path.exists(setup_py) or os.path.exists(pyproject_toml) or os.path.exists(versioneer_py) ): err = ("Versioneer was unable to run the project root directory. " "Versioneer requires setup.py to be executed from " "its immediate directory (like 'python setup.py COMMAND'), " "or in a way that lets it use sys.argv[0] to find the root " "(like 'python path/to/setup.py COMMAND').") raise VersioneerBadRootError(err) try: # Certain runtime workflows (setup.py install/develop in a setuptools # tree) execute all dependencies in a single python process, so # "versioneer" may be imported multiple times, and python's shared # module-import table will cache the first one. So we can't use # os.path.dirname(__file__), as that will find whichever # versioneer.py was first imported, even in later projects. my_path = os.path.realpath(os.path.abspath(__file__)) me_dir = os.path.normcase(os.path.splitext(my_path)[0]) vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0]) if me_dir != vsr_dir and "VERSIONEER_PEP518" not in globals(): print("Warning: build in %s is using versioneer.py from %s" % (os.path.dirname(my_path), versioneer_py)) except NameError: pass return root def get_config_from_root(root: str) -> VersioneerConfig: """Read the project setup.cfg file to determine Versioneer config.""" # This might raise OSError (if setup.cfg is missing), or # configparser.NoSectionError (if it lacks a [versioneer] section), or # configparser.NoOptionError (if it lacks "VCS="). See the docstring at # the top of versioneer.py for instructions on writing your setup.cfg . root_pth = Path(root) pyproject_toml = root_pth / "pyproject.toml" setup_cfg = root_pth / "setup.cfg" section: Union[Dict[str, Any], configparser.SectionProxy, None] = None if pyproject_toml.exists() and have_tomllib: try: with open(pyproject_toml, 'rb') as fobj: pp = tomllib.load(fobj) section = pp['tool']['versioneer'] except (tomllib.TOMLDecodeError, KeyError) as e: print(f"Failed to load config from {pyproject_toml}: {e}") print("Try to load it from setup.cfg") if not section: parser = configparser.ConfigParser() with open(setup_cfg) as cfg_file: parser.read_file(cfg_file) parser.get("versioneer", "VCS") # raise error if missing section = parser["versioneer"] # `cast`` really shouldn't be used, but its simplest for the # common VersioneerConfig users at the moment. We verify against # `None` values elsewhere where it matters cfg = VersioneerConfig() cfg.VCS = section['VCS'] cfg.style = section.get("style", "") cfg.versionfile_source = cast(str, section.get("versionfile_source")) cfg.versionfile_build = section.get("versionfile_build") cfg.tag_prefix = cast(str, section.get("tag_prefix")) if cfg.tag_prefix in ("''", '""', None): cfg.tag_prefix = "" cfg.parentdir_prefix = section.get("parentdir_prefix") if isinstance(section, configparser.SectionProxy): # Make sure configparser translates to bool cfg.verbose = section.getboolean("verbose") else: cfg.verbose = section.get("verbose") return cfg class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" # these dictionaries contain VCS-specific tools LONG_VERSION_PY: Dict[str, str] = {} HANDLERS: Dict[str, Dict[str, Callable]] = {} 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].""" HANDLERS.setdefault(vcs, {})[method] = f return f return decorate def run_command( commands: List[str], args: List[str], cwd: Optional[str] = None, verbose: bool = False, hide_stderr: bool = False, env: Optional[Dict[str, str]] = None, ) -> Tuple[Optional[str], Optional[int]]: """Call the given command(s).""" assert isinstance(commands, list) process = None popen_kwargs: Dict[str, Any] = {} if sys.platform == "win32": # This hides the console window if pythonw.exe is used startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW popen_kwargs["startupinfo"] = startupinfo for command in commands: 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) break except OSError as e: if e.errno == errno.ENOENT: continue if verbose: print("unable to run %s" % dispcmd) print(e) return None, None else: if verbose: print("unable to find command, tried %s" % (commands,)) return None, None stdout = process.communicate()[0].strip().decode() if process.returncode != 0: if verbose: print("unable to run %s (error)" % dispcmd) print("stdout was %s" % stdout) return None, process.returncode return stdout, process.returncode LONG_VERSION_PY['git'] = r''' # 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 # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. # This file is released into the public domain. # Generated by versioneer-0.29 # https://github.com/python-versioneer/python-versioneer """Git implementation of _version.py.""" import errno import os import re import subprocess import sys from typing import Any, Callable, Dict, List, Optional, Tuple import functools def get_keywords() -> Dict[str, str]: """Get the keywords needed to look up the version information.""" # these strings will be replaced by git during git-archive. # setup.py/versioneer.py will grep for the variable names, so they must # each be defined on a line of their own. _version.py will just call # get_keywords(). git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s" git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s" git_date = "%(DOLLAR)sFormat:%%ci%(DOLLAR)s" keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} return keywords class VersioneerConfig: """Container for Versioneer configuration parameters.""" VCS: str style: str tag_prefix: str parentdir_prefix: str versionfile_source: str verbose: bool def get_config() -> VersioneerConfig: """Create, populate and return the VersioneerConfig() object.""" # these strings are filled in when 'setup.py versioneer' creates # _version.py cfg = VersioneerConfig() cfg.VCS = "git" cfg.style = "%(STYLE)s" cfg.tag_prefix = "%(TAG_PREFIX)s" cfg.parentdir_prefix = "%(PARENTDIR_PREFIX)s" cfg.versionfile_source = "%(VERSIONFILE_SOURCE)s" cfg.verbose = False return cfg class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" LONG_VERSION_PY: Dict[str, str] = {} HANDLERS: Dict[str, Dict[str, Callable]] = {} 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 def run_command( commands: List[str], args: List[str], cwd: Optional[str] = None, verbose: bool = False, hide_stderr: bool = False, env: Optional[Dict[str, str]] = None, ) -> Tuple[Optional[str], Optional[int]]: """Call the given command(s).""" assert isinstance(commands, list) process = None popen_kwargs: Dict[str, Any] = {} if sys.platform == "win32": # This hides the console window if pythonw.exe is used startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW popen_kwargs["startupinfo"] = startupinfo for command in commands: 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) break except OSError as e: if e.errno == errno.ENOENT: continue if verbose: print("unable to run %%s" %% dispcmd) print(e) return None, None else: if verbose: print("unable to find command, tried %%s" %% (commands,)) return None, None stdout = process.communicate()[0].strip().decode() if process.returncode != 0: if verbose: print("unable to run %%s (error)" %% dispcmd) print("stdout was %%s" %% stdout) return None, process.returncode return stdout, process.returncode def versions_from_parentdir( parentdir_prefix: str, root: str, verbose: bool, ) -> Dict[str, Any]: """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both the project name and a version string. We will also support searching up two directory levels for an appropriately named parent directory """ rootdirs = [] 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} 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)) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs: str) -> Dict[str, str]: """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. keywords: Dict[str, str] = {} try: with open(versionfile_abs, "r") as fobj: for line in fobj: if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["full"] = mo.group(1) if line.strip().startswith("git_date ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["date"] = mo.group(1) except OSError: pass return keywords @register_vcs_handler("git", "keywords") def git_versions_from_keywords( keywords: Dict[str, str], tag_prefix: str, verbose: bool, ) -> Dict[str, Any]: """Get version information from git keywords.""" if "refnames" not in keywords: raise NotThisMethod("Short version file found") date = keywords.get("date") if date is not None: # Use only the last line. Previous lines may contain GPG signature # information. date = date.splitlines()[-1] # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because # it's been around since git-1.5.3, and it's too difficult to # discover which version we're using, or to work around using an # older one. date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") refs = {r.strip() for r in refnames.strip("()").split(",")} # 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)} 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 # expansion behaves like git log --decorate=short and strips out the # refs/heads/ and refs/tags/ prefixes that would let us distinguish # 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)} if verbose: print("discarding '%%s', no digits" %% ",".join(refs - tags)) if verbose: print("likely tags: %%s" %% ",".join(sorted(tags))) 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):] # 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): continue if verbose: print("picking %%s" %% r) 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} @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs( 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. This only gets called if the git-archive 'subst' keywords were *not* expanded, and _version.py hasn't already been rewritten with a short version string, meaning we're inside a checked out source tree. """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] # GIT_DIR can interfere with correct operation of Versioneer. # It may be intended to be passed to the Versioneer-versioned project, # but that should not change where we get our version from. env = os.environ.copy() env.pop("GIT_DIR", None) runner = functools.partial(runner, env=env) _, 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) raise NotThisMethod("'git rev-parse --git-dir' returned error") # 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) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() pieces: Dict[str, Any] = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None 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") branch_name = branch_name.strip() if branch_name == "HEAD": # If we aren't exactly on a branch, pick a branch which represents # the current commit. If all else fails, we are on a branchless # commit. branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) # --contains was added in git-1.5.4 if rc != 0 or branches is None: raise NotThisMethod("'git branch --contains' returned error") branches = branches.split("\n") # Remove the first line if we're running detached if "(" in branches[0]: branches.pop(0) # Strip off the leading "* " from the list of branches. branches = [branch[2:] for branch in branches] if "master" in branches: branch_name = "master" elif not branches: branch_name = None else: # Pick the first branch that is returned. Good or bad. branch_name = branches[0] pieces["branch"] = branch_name # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out # look for -dirty suffix dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if 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) if not mo: # unparsable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%%s'" %% describe_out) return pieces # tag full_tag = mo.group(1) if not full_tag.startswith(tag_prefix): 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)) return pieces pieces["closest-tag"] = full_tag[len(tag_prefix):] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) # commit: short hex revision ID pieces["short"] = mo.group(3) else: # HEX: no tags pieces["closest-tag"] = None out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root) pieces["distance"] = len(out.split()) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() date = runner(GITS, ["show", "-s", "--format=%%ci", "HEAD"], cwd=root)[0].strip() # Use only the last line. Previous lines may contain GPG signature # information. date = date.splitlines()[-1] pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces def plus_or_dot(pieces: Dict[str, Any]) -> str: """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces: Dict[str, Any]) -> str: """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty Exceptions: 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += plus_or_dot(pieces) rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0+untagged.%%d.g%%s" %% (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_branch(pieces: Dict[str, Any]) -> str: """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . The ".dev0" means not master branch. Note that .dev0 sorts backwards (a feature branch will appear "older" than the master branch). Exceptions: 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: if pieces["branch"] != "master": rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0" if pieces["branch"] != "master": rendered += ".dev0" rendered += "+untagged.%%d.g%%s" %% (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]: """Split pep440 version string at the post-release segment. Returns the release segments before the post-release and the post-release version number (or -1 if no post-release segment is present). """ vc = str.split(ver, ".post") return vc[0], int(vc[1] or 0) if len(vc) == 2 else None def render_pep440_pre(pieces: Dict[str, Any]) -> str: """TAG[.postN.devDISTANCE] -- No -dirty. Exceptions: 1: no tags. 0.post0.devDISTANCE """ if pieces["closest-tag"]: if pieces["distance"]: # update the post release segment tag_version, post_version = pep440_split_post(pieces["closest-tag"]) rendered = tag_version if post_version is not None: rendered += ".post%%d.dev%%d" %% (post_version + 1, pieces["distance"]) else: rendered += ".post0.dev%%d" %% (pieces["distance"]) else: # no commits, use the tag as the version rendered = pieces["closest-tag"] else: # exception #1 rendered = "0.post0.dev%%d" %% pieces["distance"] return rendered def render_pep440_post(pieces: Dict[str, Any]) -> str: """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards (a dirty tree will appear "older" than the corresponding clean one), but you shouldn't be releasing software with -dirty anyways. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%%s" %% pieces["short"] else: # exception #1 rendered = "0.post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += "+g%%s" %% pieces["short"] return rendered def render_pep440_post_branch(pieces: Dict[str, Any]) -> str: """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . The ".dev0" means not master branch. Exceptions: 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%%d" %% pieces["distance"] if pieces["branch"] != "master": rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%%s" %% pieces["short"] if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0.post%%d" %% pieces["distance"] if pieces["branch"] != "master": rendered += ".dev0" rendered += "+g%%s" %% pieces["short"] if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_old(pieces: Dict[str, Any]) -> str: """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" else: # exception #1 rendered = "0.post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" return rendered def render_git_describe(pieces: Dict[str, Any]) -> str: """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render_git_describe_long(pieces: Dict[str, Any]) -> str: """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. The distance/hash is unconditional. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered 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} if not style or style == "default": style = "pep440" # the default if style == "pep440": rendered = render_pep440(pieces) elif style == "pep440-branch": rendered = render_pep440_branch(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) elif style == "pep440-post-branch": rendered = render_pep440_post_branch(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": rendered = render_git_describe(pieces) elif style == "git-describe-long": rendered = render_git_describe_long(pieces) else: raise ValueError("unknown style '%%s'" %% style) return {"version": rendered, "full-revisionid": pieces["long"], "dirty": pieces["dirty"], "error": None, "date": pieces.get("date")} def get_versions() -> Dict[str, Any]: """Get version information or return default if unable to do so.""" # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have # __file__, we can work backwards from there to the root. Some # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which # case we can only use expanded keywords. cfg = get_config() verbose = cfg.verbose try: return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose) except NotThisMethod: pass try: root = os.path.realpath(__file__) # 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('/'): 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} try: pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) return render(pieces, cfg.style) except NotThisMethod: pass try: if cfg.parentdir_prefix: return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) except NotThisMethod: pass return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to compute version", "date": None} ''' @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs: str) -> Dict[str, str]: """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. keywords: Dict[str, str] = {} try: with open(versionfile_abs, "r") as fobj: for line in fobj: if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["full"] = mo.group(1) if line.strip().startswith("git_date ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["date"] = mo.group(1) except OSError: pass return keywords @register_vcs_handler("git", "keywords") def git_versions_from_keywords( keywords: Dict[str, str], tag_prefix: str, verbose: bool, ) -> Dict[str, Any]: """Get version information from git keywords.""" if "refnames" not in keywords: raise NotThisMethod("Short version file found") date = keywords.get("date") if date is not None: # Use only the last line. Previous lines may contain GPG signature # information. date = date.splitlines()[-1] # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because # it's been around since git-1.5.3, and it's too difficult to # discover which version we're using, or to work around using an # older one. date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") refs = {r.strip() for r in refnames.strip("()").split(",")} # 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)} 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 # expansion behaves like git log --decorate=short and strips out the # refs/heads/ and refs/tags/ prefixes that would let us distinguish # 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)} if verbose: print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: print("likely tags: %s" % ",".join(sorted(tags))) 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):] # 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): continue if verbose: print("picking %s" % r) 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} @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs( 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. This only gets called if the git-archive 'subst' keywords were *not* expanded, and _version.py hasn't already been rewritten with a short version string, meaning we're inside a checked out source tree. """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] # GIT_DIR can interfere with correct operation of Versioneer. # It may be intended to be passed to the Versioneer-versioned project, # but that should not change where we get our version from. env = os.environ.copy() env.pop("GIT_DIR", None) runner = functools.partial(runner, env=env) _, 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) raise NotThisMethod("'git rev-parse --git-dir' returned error") # 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) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() pieces: Dict[str, Any] = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None 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") branch_name = branch_name.strip() if branch_name == "HEAD": # If we aren't exactly on a branch, pick a branch which represents # the current commit. If all else fails, we are on a branchless # commit. branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) # --contains was added in git-1.5.4 if rc != 0 or branches is None: raise NotThisMethod("'git branch --contains' returned error") branches = branches.split("\n") # Remove the first line if we're running detached if "(" in branches[0]: branches.pop(0) # Strip off the leading "* " from the list of branches. branches = [branch[2:] for branch in branches] if "master" in branches: branch_name = "master" elif not branches: branch_name = None else: # Pick the first branch that is returned. Good or bad. branch_name = branches[0] pieces["branch"] = branch_name # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out # look for -dirty suffix dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if 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) if not mo: # unparsable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%s'" % describe_out) return pieces # tag full_tag = mo.group(1) if not full_tag.startswith(tag_prefix): 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)) return pieces pieces["closest-tag"] = full_tag[len(tag_prefix):] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) # commit: short hex revision ID pieces["short"] = mo.group(3) else: # HEX: no tags pieces["closest-tag"] = None out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root) pieces["distance"] = len(out.split()) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() # Use only the last line. Previous lines may contain GPG signature # information. date = date.splitlines()[-1] pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces def do_vcs_install(versionfile_source: str, ipy: Optional[str]) -> None: """Git-specific installation logic for Versioneer. For Git, this means creating/changing .gitattributes to mark _version.py for export-subst keyword substitution. """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] files = [versionfile_source] if ipy: files.append(ipy) if "VERSIONEER_PEP518" not in globals(): try: my_path = __file__ if my_path.endswith((".pyc", ".pyo")): my_path = os.path.splitext(my_path)[0] + ".py" versioneer_file = os.path.relpath(my_path) except NameError: versioneer_file = "versioneer.py" files.append(versioneer_file) present = False try: with open(".gitattributes", "r") as fobj: for line in fobj: if line.strip().startswith(versionfile_source): if "export-subst" in line.strip().split()[1:]: present = True break except OSError: pass if not present: with open(".gitattributes", "a+") as fobj: fobj.write(f"{versionfile_source} export-subst\n") files.append(".gitattributes") run_command(GITS, ["add", "--"] + files) def versions_from_parentdir( parentdir_prefix: str, root: str, verbose: bool, ) -> Dict[str, Any]: """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both the project name and a version string. We will also support searching up two directory levels for an appropriately named parent directory """ rootdirs = [] 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} 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)) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") SHORT_VERSION_PY = """ # This file was generated by 'versioneer.py' (0.29) from # revision-control system data, or from the parent directory name of an # unpacked source archive. Distribution tarballs contain a pre-generated copy # of this file. import json version_json = ''' %s ''' # END VERSION_JSON def get_versions(): return json.loads(version_json) """ def versions_from_file(filename: str) -> Dict[str, Any]: """Try to determine the version from _version.py if present.""" try: with open(filename) as f: contents = f.read() except OSError: raise NotThisMethod("unable to read _version.py") mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON", contents, re.M | re.S) if not mo: mo = re.search(r"version_json = '''\r\n(.*)''' # END VERSION_JSON", contents, re.M | re.S) if not mo: raise NotThisMethod("no version_json in _version.py") return json.loads(mo.group(1)) def write_to_version_file(filename: str, versions: Dict[str, Any]) -> None: """Write the given version number to the given _version.py file.""" contents = json.dumps(versions, sort_keys=True, indent=1, separators=(",", ": ")) with open(filename, "w") as f: f.write(SHORT_VERSION_PY % contents) print("set %s to '%s'" % (filename, versions["version"])) def plus_or_dot(pieces: Dict[str, Any]) -> str: """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces: Dict[str, Any]) -> str: """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty Exceptions: 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += plus_or_dot(pieces) rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_branch(pieces: Dict[str, Any]) -> str: """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . The ".dev0" means not master branch. Note that .dev0 sorts backwards (a feature branch will appear "older" than the master branch). Exceptions: 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: if pieces["branch"] != "master": rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0" if pieces["branch"] != "master": rendered += ".dev0" rendered += "+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]: """Split pep440 version string at the post-release segment. Returns the release segments before the post-release and the post-release version number (or -1 if no post-release segment is present). """ vc = str.split(ver, ".post") return vc[0], int(vc[1] or 0) if len(vc) == 2 else None def render_pep440_pre(pieces: Dict[str, Any]) -> str: """TAG[.postN.devDISTANCE] -- No -dirty. Exceptions: 1: no tags. 0.post0.devDISTANCE """ if pieces["closest-tag"]: if pieces["distance"]: # update the post release segment tag_version, post_version = pep440_split_post(pieces["closest-tag"]) rendered = tag_version if post_version is not None: rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"]) else: rendered += ".post0.dev%d" % (pieces["distance"]) else: # no commits, use the tag as the version rendered = pieces["closest-tag"] else: # exception #1 rendered = "0.post0.dev%d" % pieces["distance"] return rendered def render_pep440_post(pieces: Dict[str, Any]) -> str: """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards (a dirty tree will appear "older" than the corresponding clean one), but you shouldn't be releasing software with -dirty anyways. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%s" % pieces["short"] else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += "+g%s" % pieces["short"] return rendered def render_pep440_post_branch(pieces: Dict[str, Any]) -> str: """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . The ".dev0" means not master branch. Exceptions: 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["branch"] != "master": rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%s" % pieces["short"] if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["branch"] != "master": rendered += ".dev0" rendered += "+g%s" % pieces["short"] if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_old(pieces: Dict[str, Any]) -> str: """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" return rendered def render_git_describe(pieces: Dict[str, Any]) -> str: """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render_git_describe_long(pieces: Dict[str, Any]) -> str: """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. The distance/hash is unconditional. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered 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} if not style or style == "default": style = "pep440" # the default if style == "pep440": rendered = render_pep440(pieces) elif style == "pep440-branch": rendered = render_pep440_branch(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) elif style == "pep440-post-branch": rendered = render_pep440_post_branch(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": rendered = render_git_describe(pieces) elif style == "git-describe-long": rendered = render_git_describe_long(pieces) else: raise ValueError("unknown style '%s'" % style) return {"version": rendered, "full-revisionid": pieces["long"], "dirty": pieces["dirty"], "error": None, "date": pieces.get("date")} class VersioneerBadRootError(Exception): """The project root directory is unknown or missing key files.""" def get_versions(verbose: bool = False) -> Dict[str, Any]: """Get the project version from whatever source is available. Returns dict with two keys: 'version' and 'full'. """ if "versioneer" in sys.modules: # see the discussion in cmdclass.py:get_cmdclass() del sys.modules["versioneer"] root = get_root() cfg = get_config_from_root(root) assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg" handlers = HANDLERS.get(cfg.VCS) assert handlers, "unrecognized VCS '%s'" % cfg.VCS verbose = verbose or bool(cfg.verbose) # `bool()` used to avoid `None` assert cfg.versionfile_source is not None, \ "please set versioneer.versionfile_source" assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix" versionfile_abs = os.path.join(root, cfg.versionfile_source) # extract version from first of: _version.py, VCS command (e.g. 'git # describe'), parentdir. This is meant to work for developers using a # source checkout, for users of a tarball created by 'setup.py sdist', # and for users of a tarball/zipball created by 'git archive' or github's # download-from-tag feature or the equivalent in other VCSes. get_keywords_f = handlers.get("get_keywords") from_keywords_f = handlers.get("keywords") if get_keywords_f and from_keywords_f: try: keywords = get_keywords_f(versionfile_abs) ver = from_keywords_f(keywords, cfg.tag_prefix, verbose) if verbose: print("got version from expanded keyword %s" % ver) return ver except NotThisMethod: pass try: ver = versions_from_file(versionfile_abs) if verbose: print("got version from file %s %s" % (versionfile_abs, ver)) return ver except NotThisMethod: pass from_vcs_f = handlers.get("pieces_from_vcs") if from_vcs_f: try: pieces = from_vcs_f(cfg.tag_prefix, root, verbose) ver = render(pieces, cfg.style) if verbose: print("got version from VCS %s" % ver) return ver except NotThisMethod: pass try: if cfg.parentdir_prefix: ver = versions_from_parentdir(cfg.parentdir_prefix, root, verbose) if verbose: print("got version from parentdir %s" % ver) return ver except NotThisMethod: pass if verbose: print("unable to compute version") return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to compute version", "date": None} def get_version() -> str: """Get the short version string for this project.""" return get_versions()["version"] def get_cmdclass(cmdclass: Optional[Dict[str, Any]] = None): """Get the custom setuptools subclasses used by Versioneer. If the package uses a different cmdclass (e.g. one from numpy), it should be provide as an argument. """ if "versioneer" in sys.modules: del sys.modules["versioneer"] # this fixes the "python setup.py develop" case (also 'install' and # 'easy_install .'), in which subdependencies of the main project are # built (using setup.py bdist_egg) in the same python process. Assume # a main project A and a dependency B, which use different versions # of Versioneer. A's setup.py imports A's Versioneer, leaving it in # sys.modules by the time B's setup.py is executed, causing B to run # with the wrong versioneer. Setuptools wraps the sub-dep builds in a # sandbox that restores sys.modules to it's pre-build state, so the # parent is protected against the child's "import versioneer". By # removing ourselves from sys.modules here, before the child build # happens, we protect the child from the parent's versioneer too. # Also see https://github.com/python-versioneer/python-versioneer/issues/52 cmds = {} if cmdclass is None else cmdclass.copy() # we add "version" to setuptools from setuptools import Command class cmd_version(Command): description = "report generated version string" user_options: List[Tuple[str, str, str]] = [] boolean_options: List[str] = [] def initialize_options(self) -> None: pass def finalize_options(self) -> None: pass def run(self) -> None: vers = get_versions(verbose=True) print("Version: %s" % vers["version"]) print(" full-revisionid: %s" % vers.get("full-revisionid")) print(" dirty: %s" % vers.get("dirty")) print(" date: %s" % vers.get("date")) if vers["error"]: print(" error: %s" % vers["error"]) cmds["version"] = cmd_version # we override "build_py" in setuptools # # most invocation pathways end up running build_py: # distutils/build -> build_py # distutils/install -> distutils/build ->.. # setuptools/bdist_wheel -> distutils/install ->.. # setuptools/bdist_egg -> distutils/install_lib -> build_py # setuptools/install -> bdist_egg ->.. # setuptools/develop -> ? # pip install: # copies source tree to a tempdir before running egg_info/etc # if .git isn't copied too, 'git describe' will fail # then does setup.py bdist_wheel, or sometimes setup.py install # setup.py egg_info -> ? # pip install -e . and setuptool/editable_wheel will invoke build_py # but the build_py command is not expected to copy any files. # we override different "build_py" commands for both environments if 'build_py' in cmds: _build_py: Any = cmds['build_py'] else: from setuptools.command.build_py import build_py as _build_py class cmd_build_py(_build_py): def run(self) -> None: root = get_root() cfg = get_config_from_root(root) versions = get_versions() _build_py.run(self) if getattr(self, "editable_mode", False): # During editable installs `.py` and data files are # not copied to build_lib return # now locate _version.py in the new build/ directory and replace # it with an updated value if cfg.versionfile_build: target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build) print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) cmds["build_py"] = cmd_build_py if 'build_ext' in cmds: _build_ext: Any = cmds['build_ext'] else: from setuptools.command.build_ext import build_ext as _build_ext class cmd_build_ext(_build_ext): def run(self) -> None: root = get_root() cfg = get_config_from_root(root) versions = get_versions() _build_ext.run(self) if self.inplace: # build_ext --inplace will only build extensions in # build/lib<..> dir with no _version.py to write to. # As in place builds will already have a _version.py # in the module dir, we do not need to write one. return # now locate _version.py in the new build/ directory and replace # it with an updated value if not cfg.versionfile_build: return target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build) if not os.path.exists(target_versionfile): print(f"Warning: {target_versionfile} does not exist, skipping " "version update. This can happen if you are running build_ext " "without first running build_py.") return print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) cmds["build_ext"] = cmd_build_ext if "cx_Freeze" in sys.modules: # cx_freeze enabled? from cx_Freeze.dist import build_exe as _build_exe # type: ignore # nczeczulin reports that py2exe won't like the pep440-style string # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g. # setup(console=[{ # "version": versioneer.get_version().split("+", 1)[0], # FILEVERSION # "product_version": versioneer.get_version(), # ... class cmd_build_exe(_build_exe): def run(self) -> None: root = get_root() cfg = get_config_from_root(root) versions = get_versions() target_versionfile = cfg.versionfile_source print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) _build_exe.run(self) os.unlink(target_versionfile) with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] f.write(LONG % {"DOLLAR": "$", "STYLE": cfg.style, "TAG_PREFIX": cfg.tag_prefix, "PARENTDIR_PREFIX": cfg.parentdir_prefix, "VERSIONFILE_SOURCE": cfg.versionfile_source, }) cmds["build_exe"] = cmd_build_exe del cmds["build_py"] if 'py2exe' in sys.modules: # py2exe enabled? try: from py2exe.setuptools_buildexe import py2exe as _py2exe # type: ignore except ImportError: from py2exe.distutils_buildexe import py2exe as _py2exe # type: ignore class cmd_py2exe(_py2exe): def run(self) -> None: root = get_root() cfg = get_config_from_root(root) versions = get_versions() target_versionfile = cfg.versionfile_source print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) _py2exe.run(self) os.unlink(target_versionfile) with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] f.write(LONG % {"DOLLAR": "$", "STYLE": cfg.style, "TAG_PREFIX": cfg.tag_prefix, "PARENTDIR_PREFIX": cfg.parentdir_prefix, "VERSIONFILE_SOURCE": cfg.versionfile_source, }) cmds["py2exe"] = cmd_py2exe # sdist farms its file list building out to egg_info if 'egg_info' in cmds: _egg_info: Any = cmds['egg_info'] else: from setuptools.command.egg_info import egg_info as _egg_info class cmd_egg_info(_egg_info): def find_sources(self) -> None: # egg_info.find_sources builds the manifest list and writes it # in one shot super().find_sources() # Modify the filelist and normalize it root = get_root() cfg = get_config_from_root(root) self.filelist.append('versioneer.py') if cfg.versionfile_source: # There are rare cases where versionfile_source might not be # included by default, so we must be explicit self.filelist.append(cfg.versionfile_source) self.filelist.sort() self.filelist.remove_duplicates() # The write method is hidden in the manifest_maker instance that # generated the filelist and was thrown away # We will instead replicate their final normalization (to unicode, # and POSIX-style paths) from setuptools import unicode_utils normalized = [unicode_utils.filesys_decode(f).replace(os.sep, '/') for f in self.filelist.files] manifest_filename = os.path.join(self.egg_info, 'SOURCES.txt') with open(manifest_filename, 'w') as fobj: fobj.write('\n'.join(normalized)) cmds['egg_info'] = cmd_egg_info # we override different "sdist" commands for both environments if 'sdist' in cmds: _sdist: Any = cmds['sdist'] else: from setuptools.command.sdist import sdist as _sdist class cmd_sdist(_sdist): def run(self) -> None: versions = get_versions() self._versioneer_generated_versions = versions # unless we update this, the command will keep using the old # version self.distribution.metadata.version = versions["version"] return _sdist.run(self) def make_release_tree(self, base_dir: str, files: List[str]) -> None: root = get_root() cfg = get_config_from_root(root) _sdist.make_release_tree(self, base_dir, files) # now locate _version.py in the new base_dir directory # (remembering that it may be a hardlink) and replace it with an # updated value target_versionfile = os.path.join(base_dir, cfg.versionfile_source) print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, self._versioneer_generated_versions) cmds["sdist"] = cmd_sdist return cmds CONFIG_ERROR = """ setup.cfg is missing the necessary Versioneer configuration. You need a section like: [versioneer] VCS = git style = pep440 versionfile_source = src/myproject/_version.py versionfile_build = myproject/_version.py tag_prefix = parentdir_prefix = myproject- You will also need to edit your setup.py to use the results: import versioneer setup(version=versioneer.get_version(), cmdclass=versioneer.get_cmdclass(), ...) Please read the docstring in ./versioneer.py for configuration instructions, edit setup.cfg, and re-run the installer or 'python versioneer.py setup'. """ SAMPLE_CONFIG = """ # See the docstring in versioneer.py for instructions. Note that you must # re-run 'versioneer.py setup' after changing this section, and commit the # resulting files. [versioneer] #VCS = git #style = pep440 #versionfile_source = #versionfile_build = #tag_prefix = #parentdir_prefix = """ OLD_SNIPPET = """ from ._version import get_versions __version__ = get_versions()['version'] del get_versions """ INIT_PY_SNIPPET = """ from . import {0} __version__ = {0}.get_versions()['version'] """ def do_setup() -> int: """Do main VCS-independent setup function for installing Versioneer.""" root = get_root() try: cfg = get_config_from_root(root) except (OSError, configparser.NoSectionError, configparser.NoOptionError) as e: if isinstance(e, (OSError, configparser.NoSectionError)): print("Adding sample versioneer config to setup.cfg", file=sys.stderr) with open(os.path.join(root, "setup.cfg"), "a") as f: f.write(SAMPLE_CONFIG) print(CONFIG_ERROR, file=sys.stderr) return 1 print(" creating %s" % cfg.versionfile_source) with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] f.write(LONG % {"DOLLAR": "$", "STYLE": cfg.style, "TAG_PREFIX": cfg.tag_prefix, "PARENTDIR_PREFIX": cfg.parentdir_prefix, "VERSIONFILE_SOURCE": cfg.versionfile_source, }) ipy = os.path.join(os.path.dirname(cfg.versionfile_source), "__init__.py") maybe_ipy: Optional[str] = ipy if os.path.exists(ipy): try: with open(ipy, "r") as f: old = f.read() except OSError: old = "" module = os.path.splitext(os.path.basename(cfg.versionfile_source))[0] snippet = INIT_PY_SNIPPET.format(module) if OLD_SNIPPET in old: print(" replacing boilerplate in %s" % ipy) with open(ipy, "w") as f: f.write(old.replace(OLD_SNIPPET, snippet)) elif snippet not in old: print(" appending to %s" % ipy) with open(ipy, "a") as f: f.write(snippet) else: print(" %s unmodified" % ipy) else: print(" %s doesn't exist, ok" % ipy) maybe_ipy = None # Make VCS-specific changes. For git, this means creating/changing # .gitattributes to mark _version.py for export-subst keyword # substitution. do_vcs_install(cfg.versionfile_source, maybe_ipy) return 0 def scan_setup_py() -> int: """Validate the contents of setup.py against Versioneer's expectations.""" found = set() setters = False errors = 0 with open("setup.py", "r") as f: for line in f.readlines(): if "import versioneer" in line: found.add("import") if "versioneer.get_cmdclass()" in line: found.add("cmdclass") if "versioneer.get_version()" in line: found.add("get_version") if "versioneer.VCS" in line: setters = True if "versioneer.versionfile_source" in line: setters = True if len(found) != 3: print("") print("Your setup.py appears to be missing some important items") print("(but I might be wrong). Please make sure it has something") print("roughly like the following:") print("") print(" import versioneer") print(" setup( version=versioneer.get_version(),") print(" cmdclass=versioneer.get_cmdclass(), ...)") print("") errors += 1 if setters: print("You should remove lines like 'versioneer.VCS = ' and") print("'versioneer.versionfile_source = ' . This configuration") print("now lives in setup.cfg, and should be removed from setup.py") print("") errors += 1 return errors def setup_command() -> NoReturn: """Set up Versioneer and exit with appropriate error code.""" errors = do_setup() errors += scan_setup_py() sys.exit(1 if errors else 0) if __name__ == "__main__": cmd = sys.argv[1] if cmd == "setup": setup_command()