├── .travis.yml ├── INSTALL.txt ├── README.md ├── archive ├── matrix2latexMatlab-1.0.0.tar.gz └── matrix2latexPython-1.0.0.tar.gz ├── archiver.py ├── copying.txt ├── doc ├── Makefile ├── compatibleTable.tex ├── doc.pdf ├── doc.tex ├── facT.tex ├── facV.tex ├── factorial.py ├── features.tex ├── niceTable.tex ├── table_ohm.tex ├── table_ohm2.tex ├── test.tex ├── tex │ ├── introduction.tex │ ├── matlab.tex │ ├── python.tex │ └── texPreamble.tex └── tmp.tex ├── doc_sphinx ├── Makefile ├── conf.py ├── index.txt └── sphinxext │ ├── apigen.py │ ├── docscrape.py │ ├── docscrape_sphinx.py │ ├── inheritance_diagram.py │ ├── ipython_console_highlighting.py │ └── numpydoc.py ├── matrix2latex ├── IOString.py ├── __init__.py ├── error.py ├── fixEngineeringNotation.py ├── matrix2latex.py ├── pagination.py └── render.py ├── setup.py ├── simpleExample.png ├── src_matlab ├── fixEngineeringNotation.m └── matrix2latex.m └── test ├── README.txt ├── test.m ├── test.py ├── test.tex ├── testVersionCompatibility.py ├── test_syntaxError.py └── test_util.py /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.4" 5 | - "3.5" 6 | - "3.9" 7 | - "nightly" 8 | - "pypy" 9 | - "pypy3" 10 | # command to install dependencies 11 | install: "pip install ." 12 | # command to run tests 13 | script: ./test/test.py 14 | notifications: 15 | email: false -------------------------------------------------------------------------------- /INSTALL.txt: -------------------------------------------------------------------------------- 1 | == Python == 2 | To use the code place the folder matrix2latex/ in our \verb!PYTHONPATH!, 3 | this can be done with the usual 4 | > sudo python setup.py install 5 | Alternativly, your current working directory is always on your \verb!PYTHONPATH!. 6 | 7 | == Matlab == 8 | Place the matlab files in our MATLABPATH containing at least: 9 | matrix2latex.m and fixEngineeringNotation.m 10 | Your current working directory is always on your MATLABPATH. 11 | 12 | See the following article from mathworks 13 | \url{http://www.mathworks.se/help/techdoc/ref/path.html}. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/TheChymera/matrix2latex.svg?branch=master)](https://travis-ci.org/TheChymera/matrix2latex) 2 | Matrix2LaTeX 3 | ============ 4 | Takes a Python or MATLAB matrix and outputs a LaTeX table, a LaTeX matrix, or a complete LaTeX document (and optionally calls `latex` for compilation). 5 | A number of configuration options are available. 6 | The default table output is geared towards the standard recommended by IEEE, and uses the latex package booktabs. 7 | Check out the [documentation](https://github.com/TheChymera/matrix2latex/raw/master/doc/doc.pdf "doc.pdf") for more example output and usage. 8 | 9 | Installation 10 | ------------ 11 | From source: 12 | ```bash 13 | pip install git+https://github.com/TheChymera/matrix2latex.git 14 | ``` 15 | Note that on some systems you may have to replace `pip` by `pip3` to use Python 3 for the installation. 16 | Furthermore, if you only wish to install the package for the current user, you should supply the `--user` flag to the above command. 17 | 18 | Example 19 | ------- 20 | The following python code: 21 | ```python 22 | from matrix2latex import matrix2latex 23 | m = [[1, 1], [2, 4], [3, 9]] # python nested list 24 | t = matrix2latex(m) 25 | print(t) 26 | ``` 27 | or equivalent matlab code: 28 | ```matlab 29 | m = [1, 1; 2, 4; 3, 9]; 30 | filename = '' % do not write to file 31 | t = matrix2latex(m, filename) 32 | ``` 33 | produces 34 | ```latex 35 | \begin{table}[ht] 36 | \begin{center} 37 | \begin{tabular}{cc} 38 | \toprule 39 | $1$ & $1$\\ 40 | $2$ & $4$\\ 41 | $3$ & $9$\\ 42 | \bottomrule 43 | \end{tabular} 44 | \end{center} 45 | \end{table} 46 | ``` 47 | 48 | ![alt text](https://github.com/TheChymera/matrix2latex/raw/master/simpleExample.png "Example table, latex output.") 49 | 50 | Various options are available to change the latex environment (e.g. to a matrix) or to provide 51 | header, footer, caption, label, format and/or alignment. Please see the [documentation](https://github.com/TheChymera/matrix2latex/raw/master/doc/doc.pdf "doc.pdf") for details. 52 | 53 | History 54 | ------- 55 | Inspired by the work of koehler@in.tum.de who has written 56 | a similar package only for matlab 57 | http://www.mathworks.com/matlabcentral/fileexchange/4894-matrix2latex 58 | 59 | This project was moved from https://code.google.com/p/matrix2latex/ 60 | after code.google.com was discontinued. 61 | 62 | Future goals 63 | ------------ 64 | Feel free to contribute to any of the following improvements 65 | or open an [issue](https://github.com/TheChymera/matrix2latex/issues) if you want something done. 66 | 67 | * Clean up the code (object oriented?) 68 | * Make the matlab version identical to the python version 69 | * Add support for more advanced tables. Highlights and multirow. 70 | * Command line interface (say, read in a csv, take arguments on the command line) 71 | * Additional languages (R/perl/julia?) 72 | 73 | Author 74 | ------ 75 | Øystein Bjørndal 76 | -------------------------------------------------------------------------------- /archive/matrix2latexMatlab-1.0.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheChymera/matrix2latex/cd682ff87972f7182613a653495ca88fd75c33d6/archive/matrix2latexMatlab-1.0.0.tar.gz -------------------------------------------------------------------------------- /archive/matrix2latexPython-1.0.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheChymera/matrix2latex/cd682ff87972f7182613a653495ca88fd75c33d6/archive/matrix2latexPython-1.0.0.tar.gz -------------------------------------------------------------------------------- /archiver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Utility script for creating a versioned .tar.gz of the documenentation and 3 | the code for either matlab or python. 4 | ''' 5 | import os 6 | 7 | def callSystem(command): 8 | print('>' + command) 9 | ret = os.system(command) 10 | if ret != 0: 11 | print('Command failed, exiting') 12 | exit(1) 13 | 14 | version = '1.0.0' 15 | 16 | # Ensure tests pass 17 | callSystem('cd test;nosetests-2.7 test.py') 18 | callSystem('cd test;python testVersionCompatibility.py') 19 | # Re-create doc if neccesary 20 | callSystem('cd doc;latexmk -pdf doc.tex') 21 | 22 | include_common = ' doc README.md ' 23 | 24 | python_name = 'archive/matrix2latexPython' 25 | include_files = 'matrix2latex setup.py' + include_common 26 | callSystem('git archive --format=tar --prefix={name}{v}/ HEAD {include} | gzip > {name}-{v}.tar.gz'.format(name=python_name, 27 | v=version, 28 | include=include_files)) 29 | 30 | matlab_name = 'archive/matrix2latexMatlab' 31 | include_files = 'src_matlab' + include_common 32 | callSystem('git archive --format=tar --prefix={name}{v}/ HEAD {include} | gzip > {name}-{v}.tar.gz'.format(name=matlab_name, 33 | v=version, 34 | include=include_files)) 35 | 36 | callSystem('git add {p_name}-{v}.tar.gz {m_name}-{v}.tar.gz'.format(p_name=python_name, 37 | m_name=matlab_name, 38 | v=version)) 39 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | 2 | compilePDF: 3 | latexmk -pdf -pvc doc.tex 4 | 5 | compileDVI: 6 | $(MAKE) clean 7 | latexmk -pvc doc.tex 8 | 9 | compilePreview: 10 | latexmk -pdf doc.tex 11 | osascript -e "tell application \"Preview\" to activate" 12 | 13 | compile: 14 | $(MAKE) clean 15 | rm -f doc.pdf doc.dvi # make sure they are recreated 16 | latexmk -pdf doc.tex # recreate pdf 17 | latexmk doc.tex # recreate dvi 18 | $(MAKE) clean 19 | 20 | clean: 21 | latexmk -c doc.tex 22 | rm -r pythontex-files-doc/ 23 | rm -f *.aux *.log *~ *.out *.bbl 24 | -------------------------------------------------------------------------------- /doc/compatibleTable.tex: -------------------------------------------------------------------------------- 1 | \begin{table}[htp] 2 | \begin{center} 3 | \caption{Does 'python test.py' return 0?} 4 | \label{tab:compatibleTable} 5 | \begin{tabular}{rc} 6 | \toprule 7 | {} & {Compatible}\\ 8 | \midrule 9 | {python2.6} & True\\ 10 | {python2.7} & True\\ 11 | {python3.7} & True\\ 12 | \bottomrule 13 | \end{tabular} 14 | \end{center} 15 | \end{table} -------------------------------------------------------------------------------- /doc/doc.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheChymera/matrix2latex/cd682ff87972f7182613a653495ca88fd75c33d6/doc/doc.pdf -------------------------------------------------------------------------------- /doc/doc.tex: -------------------------------------------------------------------------------- 1 | % This file is part of matrix2latex. 2 | 3 | % matrix2latex is free software: you can redistribute it and/or modify 4 | % it under the terms of the GNU General Public License as published by 5 | % the Free Software Foundation, either version 3 of the License, or 6 | % (at your option) any later version. 7 | 8 | % matrix2latex is distributed in the hope that it will be useful, 9 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | % GNU General Public License for more details. 12 | 13 | % You should have received a copy of the GNU General Public License 14 | % along with matrix2latex. If not, see . 15 | 16 | \input{tex/texPreamble} 17 | \title{Documentation for matrix2latex} 18 | 19 | \begin{document} 20 | \maketitle 21 | This is the documentation for the matrix2latex package, development can now be found here: 22 | \url{https://github.com/TheChymera/matrix2latex} 23 | 24 | This package provides easy translation of vector/matrices in python or matlab into latex table/matrix code. 25 | The implementation is written in both matlab and python, resulting in some discrepancies in features and usage. 26 | After the introduction we will discuss some 27 | matlab specific usage in chapter \ref{sec:matlab} before showing 28 | all options using the python syntax. Currently the python version is the best maintained. 29 | \chapter{Introduction/features} 30 | \label{sec:introduction} 31 | \input{tex/introduction} 32 | 33 | \chapter{Matlab specific} 34 | \label{sec:matlab} 35 | \input{tex/matlab} 36 | 37 | \chapter{General Usage} 38 | \label{sec:python} 39 | \input{tex/python} 40 | 41 | % %\bibliographystyle{IEEEtran} 42 | % %\bibliography{doc} 43 | \end{document} 44 | -------------------------------------------------------------------------------- /doc/facT.tex: -------------------------------------------------------------------------------- 1 | \begin{table}[htp] 2 | \begin{center} 3 | \caption{Comparing execution time for 4 | the different factorial implementations} 5 | \label{tab:facT} 6 | \begin{tabular}{rccc} 7 | \toprule 8 | {$n$} & {Built-in [$s$]} & {Recursive [$s$]} & {Sequential [$s$]}\\ 9 | \midrule 10 | {0} & $0.235$ & $0.396$ & $0.396$\\ 11 | {1} & $0.260$ & $0.439$ & $1.213$\\ 12 | {2} & $0.256$ & $0.880$ & $1.358$\\ 13 | {3} & $0.311$ & $1.341$ & $1.447$\\ 14 | {4} & $0.364$ & $1.810$ & $1.610$\\ 15 | {5} & $0.386$ & $2.136$ & $1.717$\\ 16 | {6} & $0.435$ & $2.527$ & $1.818$\\ 17 | {7} & $0.475$ & $2.971$ & $1.930$\\ 18 | {8} & $0.480$ & $3.337$ & $1.987$\\ 19 | {9} & $0.522$ & $3.742$ & $2.178$\\ 20 | \bottomrule 21 | \end{tabular} 22 | \end{center} 23 | \end{table} -------------------------------------------------------------------------------- /doc/facV.tex: -------------------------------------------------------------------------------- 1 | \begin{table}[htp] 2 | \begin{center} 3 | \caption{Vertifying that the different factorial 4 | implementations gives the same results} 5 | \label{tab:facV} 6 | \begin{tabular}{rrrr} 7 | \toprule 8 | {$n$} & {Built-in} & {Recursive} & {Sequential}\\ 9 | \midrule 10 | {0} & $1$ & $1$ & $1$\\ 11 | {1} & $1$ & $1$ & $1$\\ 12 | {2} & $2$ & $2$ & $2$\\ 13 | {3} & $6$ & $6$ & $6$\\ 14 | {4} & $24$ & $24$ & $24$\\ 15 | {5} & $120$ & $120$ & $120$\\ 16 | {6} & $720$ & $720$ & $720$\\ 17 | {7} & $5040$ & $5040$ & $5040$\\ 18 | {8} & $40320$ & $40320$ & $40320$\\ 19 | {9} & $362880$ & $362880$ & $362880$\\ 20 | \bottomrule 21 | \end{tabular} 22 | \end{center} 23 | \end{table} -------------------------------------------------------------------------------- /doc/factorial.py: -------------------------------------------------------------------------------- 1 | # built in factorial 2 | from math import factorial as factorialMath 3 | 4 | # recursive 5 | def factorialRecursive(n): 6 | if n == 0: 7 | return 1 8 | elif n == 1: 9 | return n 10 | else: 11 | return n*factorialRecursive(n-1) 12 | 13 | # sequential 14 | def factorialSequential(n): 15 | if n == 0: 16 | return 1 17 | res = 1 18 | for k in xrange(2, n+1): 19 | res *= k 20 | return res 21 | 22 | if __name__ == '__main__': 23 | from matrix2latex import matrix2latex 24 | 25 | N = range(0, 10) 26 | table = list() 27 | for func in (factorialMath, 28 | factorialRecursive, 29 | factorialSequential): 30 | row = list() 31 | for n in N: 32 | res = func(n) # call func 33 | row.append(res)# append result to row 34 | table.append(row) # append row to table 35 | 36 | # row labels 37 | rl = ['$n$', 'Built-in', 'Recursive', 'Sequential'] 38 | caption = '''Vertifying that the different factorial 39 | implementations gives the same results''' 40 | matrix2latex(table, 'facV', caption=caption, 41 | headerColumn=N, headerRow=rl, 42 | alignment='r', transpose=True) 43 | 44 | import timeit 45 | table = list() 46 | for func in ('factorialMath', 47 | 'factorialRecursive', 48 | 'factorialSequential'): 49 | row = list() 50 | for n in N: 51 | statement = 'factorial.{func}({n})'.format(func=func, 52 | n=n) 53 | setup = 'import factorial' 54 | # measure time 55 | res = timeit.repeat(statement, setup) 56 | row.append(min(res)) # append result 57 | table.append(row) # append row to table 58 | 59 | rl = ['$n$', 'Built-in [$s$]', 60 | 'Recursive [$s$]', 'Sequential [$s$]'] 61 | caption = '''Comparing execution time for 62 | the different factorial implementations''' 63 | matrix2latex(table, 'facT', caption=caption, 64 | headerColumn=N, headerRow=rl, 65 | transpose=True, format='$%.3f$') 66 | -------------------------------------------------------------------------------- /doc/features.tex: -------------------------------------------------------------------------------- 1 | \begin{table}[ht] 2 | \begin{center} 3 | \caption{What feature is currently available in which language?} 4 | \label{tab:features} 5 | \begin{tabular}{rcc} 6 | \toprule 7 | Feature & Python & Matlab\\ 8 | \midrule 9 | environment & True & True\\ 10 | headerRow & True & True\\ 11 | multiline headerRow & True & False\\ 12 | headerColumn & True & True\\ 13 | multiline headerColumn & False & False\\ 14 | transpose & True & True\\ 15 | caption & True & True\\ 16 | label & True & True\\ 17 | format & True & True\\ 18 | formatColumn & True & True\\ 19 | alignment & True & True\\ 20 | \bottomrule 21 | \end{tabular} 22 | \end{center} 23 | \end{table} -------------------------------------------------------------------------------- /doc/niceTable.tex: -------------------------------------------------------------------------------- 1 | \begin{table}[htp] 2 | \begin{center} 3 | \caption{Nice table!} 4 | \label{tab:niceTable} 5 | \begin{tabular}{cc} 6 | \toprule 7 | {$x$} & {$x^2$}\\ 8 | \midrule 9 | $1$ & $1$\\ 10 | $2$ & $4$\\ 11 | $3$ & $9$\\ 12 | \bottomrule 13 | \end{tabular} 14 | \end{center} 15 | \end{table} -------------------------------------------------------------------------------- /doc/table_ohm.tex: -------------------------------------------------------------------------------- 1 | \begin{table}[htp] 2 | \begin{center} 3 | \caption{Current through $50 \Omega$ resistor} 4 | \label{tab:table_ohm} 5 | \begin{tabular}{cc} 6 | \toprule 7 | {$V$} & {$I=V/R$}\\ 8 | \midrule 9 | $1 V$ & $0.02 A$\\ 10 | $2 V$ & $0.04 A$\\ 11 | $3 V$ & $0.06 A$\\ 12 | \bottomrule 13 | \end{tabular} 14 | \end{center} 15 | \end{table} -------------------------------------------------------------------------------- /doc/table_ohm2.tex: -------------------------------------------------------------------------------- 1 | \begin{table}[htp] 2 | \begin{center} 3 | \caption{Current through $50 \Omega$ resistor} 4 | \label{tab:table_ohm2} 5 | \begin{tabular}{cc} 6 | \toprule 7 | {$V$ [V]} & {$I=V/R$ [A]}\\ 8 | \midrule 9 | $1$ & $0.02$\\ 10 | $2$ & $0.04$\\ 11 | $3$ & $0.06$\\ 12 | \bottomrule 13 | \end{tabular} 14 | \end{center} 15 | \end{table} -------------------------------------------------------------------------------- /doc/test.tex: -------------------------------------------------------------------------------- 1 | \begin{table}[ht] 2 | \begin{center} 3 | \label{tab:test} 4 | \begin{tabular}{lcr} 5 | \toprule 6 | $1.00$ & $3.00$\\ 7 | $2.00$ & $4.00$\\ 8 | $4.00$ & $6.00$\\ 9 | \bottomrule 10 | \end{tabular} 11 | \end{center} 12 | \end{table} 13 | -------------------------------------------------------------------------------- /doc/tex/introduction.tex: -------------------------------------------------------------------------------- 1 | Takes a python or matlab matrix or nested list and converts to a LaTeX table or matrix. 2 | Author: ob@cakebox.net, inspired by the work of koehler@in.tum.de who has written 3 | a similar package only for matlab 4 | \url{http://www.mathworks.com/matlabcentral/fileexchange/4894-matrix2latex} 5 | 6 | This software is published under the GNU GPL, by the free software 7 | foundation. See: 8 | \url{http://www.gnu.org/licenses/licenses.html#GPL} 9 | 10 | \section{TODO} 11 | \begin{itemize} 12 | \item Improve test suite. 13 | \item Improve error handling. 14 | \item Update matlab code 15 | \end{itemize} 16 | 17 | \section{Compatibility} 18 | Table \ref{tab:features} reflect the current features and whether it is available 19 | in the matlab or python version. 20 | \input{features} 21 | These features are discussed in more detail in chapter \ref{sec:python}. 22 | 23 | \subsection{Python} 24 | The code is written without any dependencies and should be compatible with most python versions. 25 | Table \ref{tab:compatibleTable} reflects the python versions currently installed 26 | on my system\footnote{Mac OS X 10.10, python installed through macport.} 27 | and if the testsuit for matrix2latex passes. If you find a python version where 28 | it doesn't work let me know. 29 | \input{compatibleTable} 30 | \subsection{Matlab} 31 | For the moment it is only tested with matlab R2009b. 32 | 33 | \section{Installation} 34 | The following packages and definitions are recommended in the latex preamble 35 | \begin{pygments}{latex} 36 | % scientific notation, 1\e{9} will print as 1x10^9 37 | \providecommand{\e}[1]{\ensuremath{\times 10^{#1}}} 38 | \usepackage{amsmath} % needed for pmatrix 39 | \usepackage{booktabs} % Fancy tables 40 | \usepackage{caption} % Fixes spacing between caption and table 41 | ... 42 | \begin{document} 43 | ... 44 | \end{pygments} 45 | \subsection{Python} 46 | To use the code place the folder matrix2latex/ in our \verb!PYTHONPATH!, 47 | this can be done with the usual 48 | \begin{verbatim} 49 | sudo python setup.py install 50 | \end{verbatim} 51 | Alternativly, your current working directory is always on your \verb!PYTHONPATH!. 52 | 53 | % Hint: on unix systems do: \\ 54 | % \verb!echo $PYTHONPATH! \\ 55 | % to see a list of locations. Other users: ask google about \verb!PYTHONPATH! 56 | % for your operation system. 57 | -------------------------------------------------------------------------------- /doc/tex/matlab.tex: -------------------------------------------------------------------------------- 1 | \section{Installation} 2 | Place the matlab files in our \verb!MATLABPATH!. 3 | Your current working directory is always on your \verb!MATLABPATH!. 4 | 5 | See the following article from mathworks 6 | \url{http://www.mathworks.se/help/techdoc/ref/path.html}. 7 | 8 | \section{Usage} 9 | As matlab does not support keywords, something similar to the environment must be used. 10 | Most of the examples in this document are given in python code only. The matlab version should be identical 11 | in behavior but has slightly different syntax. 12 | 13 | \begin{pygments}{matlab} 14 | matrix2latex(matrix, filename, varargin) 15 | \end{pygments} 16 | 17 | \begin{pygments}{matlab} 18 | % Filename must be suppiled but filename == '' will not write to file. 19 | % filename = 'foo.tex' and filename = 'foo' will write to 'foo.tex' 20 | m = [1, 1; 2, 4; 3, 9]; 21 | t = matrix2latex(m, ''); 22 | % keyword arguments are given like this 23 | % instead of transpose = True 24 | t = matrix2latex(m, '', 'transpose', true); 25 | % environment is a keyword argument 26 | t = matrix2latex(m, '', 'environment', {'align*', 'pmatrix'}); 27 | % if you want to pass in strings you must use cell array 28 | m = {'a', 'b', '1'; '1', '2', '3'} 29 | t = matrix2latex(m, '', 'format', '%s'); 30 | \end{pygments} -------------------------------------------------------------------------------- /doc/tex/python.tex: -------------------------------------------------------------------------------- 1 | \section{Usage} 2 | The python version should be called like this 3 | \begin{pycode} 4 | from matrix2latex import * 5 | matrix=[] 6 | filename=None 7 | environment = list() 8 | keywords = dict() 9 | \end{pycode} 10 | \begin{pygments}{python} 11 | matrix2latex(matrix, filename, *environment, **keywords) 12 | \end{pygments} 13 | 14 | 15 | \subsection{matrix} 16 | \begin{itemize} 17 | \item[Python:] A numpy matrix, a pandas DataFrame/Series or a (nested) list. 18 | Note: these are all converted to a nested list internally for compatibility. 19 | \item[Matlab:] Matrix or cell array. 20 | \end{itemize} 21 | \subsection{Filename} 22 | File to place output, extension .tex is added automatically. File can be included in a LaTeX 23 | document by \verb!\input{filename}!. Output will always be returned in a string. If filename is None 24 | not a string or empty string it is ignored. 25 | 26 | \subsection{*environments} 27 | Use 28 | \pygment{python}{matrix2latex(m, None, "align*", "pmatrix", ...)} for matrix. 29 | This will give 30 | \begin{align*} 31 | \begin{pmatrix} 32 | 1 & 2 \\ 33 | 3 & 4 34 | \end{pmatrix} 35 | \end{align*} 36 | Use 37 | \pygment{python}{matrix2latex(m, "test", "table", "center", "tabular" ...)} for table. 38 | Table is default so given no arguments: table, center and tabular will be used. 39 | The above command is then equivalent to \\ 40 | \pygment{python}{matrix2latex(m, "test", ...)} 41 | 42 | The above commands looks a bit differently in matlab, since we must specify 43 | that we want to change the environment. 44 | \begin{pygments}{matlab} 45 | matrix2latex(m, None, 'environment', {'align*', 'pmatrix'}, ...) 46 | matrix2latex(m, 'test', 'environment', {'table', 'center', 'tabular'} ...) 47 | matrix2latex(m, 'test', ...) 48 | \end{pygments} 49 | 50 | \subsubsection{Example} 51 | \begin{pyblock} 52 | from matrix2latex import matrix2latex 53 | m = [[1, 1], [2, 4], [3, 9]] # python nested list 54 | t = matrix2latex(m) 55 | print(t) 56 | \end{pyblock} 57 | \begin{pygments}{tex} 58 | \begin{center} 59 | \begin{tabular}{cc} 60 | \toprule 61 | $1$ & $1$\\ 62 | $2$ & $4$\\ 63 | $3$ & $9$\\ 64 | \bottomrule 65 | \end{tabular} 66 | \end{center} 67 | \end{table} 68 | \end{pygments} 69 | \py{t} 70 | 71 | \subsection{**keywords} 72 | \subsubsection{headerRow} 73 | A row at the top used to label the columns. 74 | Must be a list of strings. 75 | Using the same example from above we can add row labels 76 | \begin{pyblock} 77 | hr = ['$x$', '$x^2$'] 78 | t = matrix2latex(m, headerRow=hr) 79 | \end{pyblock} 80 | 81 | Which in matlab looks like this (note that cell array must be used for 82 | declaring the header row) 83 | \begin{pygments}{matlab} 84 | hr = {'$x$', '$x^2$'}; 85 | t = matrix2latex(m, 'headerRow', hr); 86 | \end{pygments} 87 | \py{t} 88 | 89 | The python version currently supports header rows spanning multiple columns (using \pygment{latex}{\multicolumn}), this 90 | is done by repeating the header info. It also supports multiple rows 91 | by using a nested list. 92 | \begin{pycode}[multi] 93 | from matrix2latex import matrix2latex 94 | \end{pycode} 95 | \begin{pyblock}[multi] 96 | hr = [['Item', 'Item', ''], 97 | ['Animal', 'Description', 'Price (\$)']] 98 | t = [['Gnat', 'per gram', 13.65], 99 | ['', 'each', 0.01], 100 | ['Gnu', 'stuffed', 92.50], 101 | ['Emu', 'stuffed', 33.33], 102 | ['Armadillo', 'frozen', 8.99]] 103 | t = matrix2latex(t, headerRow = hr, alignment='@{}llr@{}') 104 | \end{pyblock} 105 | \py[multi]{t} 106 | To avoid this behavior ensure each consecutive item is unique, for instance: 107 | \pygment{python}{['Item', 'Item ', '']} 108 | (note the space after the second item). 109 | 110 | \subsubsection{headerColumn} 111 | A column used to label the rows. 112 | Must be a list of strings 113 | \subsubsection{transpose} 114 | Flips the table around in case you messed up. Equivalent to 115 | matrix2latex(m.H, ...) 116 | if m is a numpy matrix. 117 | Note that \pygment{python}{headerColumn} is used in the example. 118 | \begin{pyblock} 119 | t = matrix2latex(m, headerColumn=hr, transpose=True) 120 | \end{pyblock} 121 | \py{t} 122 | 123 | \subsubsection{caption} 124 | Use to define a caption for your table. 125 | Inserts \pygment{python}{\caption} after \pygment{python}{\begin{center}}, 126 | note that without the center environment the caption is currently ignored. 127 | Always use informative captions! 128 | \begin{pyblock} 129 | t = matrix2latex(m, headerRow=hr, 130 | caption='Nice table!') 131 | \end{pyblock} 132 | \py{t} 133 | 134 | \subsubsection{label} 135 | Used to insert \verb!\label{tab:...}! after \verb!\end{tabular}! 136 | Default is filename without extension. 137 | 138 | We can use \pygment{python}{label='niceTable'} but if we save it to file 139 | the default label is the filename, so: 140 | \begin{pyblock} 141 | matrix2latex(m, 'niceTable', headerRow=hr, 142 | caption='Nice table!') 143 | \end{pyblock} 144 | can be referenced by \verb!\ref{tab:niceTable}!. Table \ref{tab:niceTable} 145 | was included in latex by \verb!\input{niceTable}!. 146 | \input{niceTable} 147 | 148 | \subsubsection{format} 149 | Printf syntax format, e.g. \pygment{python}{$%.2f$}. Default is \pygment{python}{$%g$}. 150 | This format is then used for all the elements in the table. 151 | Using numpy creating tables for common mathematical expressions are easy: 152 | \begin{pyblock} 153 | import numpy as np 154 | m = np.zeros((3, 2)) 155 | m[:, 0] = np.arange(1, 3+1) 156 | m[:, 1] = 1./m[:, 0] 157 | 158 | hr = ['$x$', '$1/x$'] 159 | t = matrix2latex(m, headerRow=hr, 160 | format='%.2f') 161 | \end{pyblock} 162 | \py{t} 163 | 164 | \subsubsection{formatColumn} 165 | A list of printf-syntax formats, e.g. \pygment{python}{['$%.2f$', '$%g$']} 166 | Must be of same length as the number of columns. 167 | Format i is then used for column i. 168 | This is useful if some of your data should be printed with more significant figures 169 | than other parts, for instance in the example above $x$ are integers and using 170 | $d$ is more appropriate. 171 | \begin{pyblock} 172 | fc = ['$%d$', '$%.2f$'] 173 | t = matrix2latex(m, headerRow=hr, formatColumn=fc) 174 | \end{pyblock} 175 | \py{t} 176 | 177 | You could use the format option to add units to you numbers, so for instance 178 | visualizing ohms law 179 | \begin{pyblock} 180 | m2 = np.zeros((3, 2)) 181 | R = 50. # ohm 182 | m2[:, 0] = np.arange(1, 3+1) 183 | m2[:, 1] = m[:, 0]/R 184 | c = r'Current through $%g \Omega$ resistor' % R 185 | hr = ['$V$', '$I=V/R$'] 186 | t = matrix2latex(m2, 'table_ohm', headerRow=hr, 187 | formatColumn=['$%d V$', '$%.2f A$'], 188 | caption=c) 189 | \end{pyblock} 190 | \input{table_ohm} 191 | 192 | This is however not the recommend way to give units, since they should 193 | be given in the header, see tabel \ref{tab:table_ohm2}. 194 | \begin{pyblock} 195 | hr = ['$V$ [V]', '$I=V/R$ [A]'] 196 | t = matrix2latex(m2, 'table_ohm2', headerRow=hr, 197 | formatColumn=['$%d$', '$%.2f$'], 198 | caption=c) 199 | \end{pyblock} 200 | \input{table_ohm2} 201 | 202 | \subsubsection{alignment} 203 | Used as an option when tabular is given as enviroment. 204 | \verb!\begin{tabular}{alignment}! 205 | A latex alignment like c, l or r. 206 | Can be given either as one per column e.g. ``ccc''. 207 | Or if only a single character is given e.g. ``c'', 208 | it will produce the correct amount depending on the number of columns. 209 | Default is ``c'', if you use \pygment{python}{headerColumn} it will always use 210 | ``r'' as the alignment for that column. 211 | \begin{pyblock} 212 | t = matrix2latex(m, alignment='rl') 213 | \end{pyblock} 214 | \py{t} 215 | 216 | But what if I want vertical rules in my table? Well, this package is built 217 | on top of booktabs for publication ready tables and the booktabs documentation clearly 218 | states ``Never, ever use vertical rule''. But as long as you are not publishing your table, 219 | you could simply use 220 | \begin{pyblock} 221 | t = matrix2latex(m, alignment='|r||c|') 222 | \end{pyblock} 223 | \py{t} 224 | 225 | There is some error checking on the alignment but not much, it simply counts the number 226 | of c, l and r in the alignment. All other characters are ignored. 227 | 228 | \subsubsection{position} 229 | Used for the table environment to specify the optional parameter ``position specifier'' 230 | Default is \pygment{python}{'[' + 'htp' + ']'} 231 | 232 | If you want to place your table manually, do not use the table environment. 233 | 234 | \subsubsection{General considerations} 235 | Note that many of these options only has an effect when typesetting a table, 236 | if the correct environment is not given the arguments are simply ignored. 237 | To give an example of a very useless function call 238 | \begin{pyblock} 239 | t1 = matrix2latex(m, None, "align*", "pmatrix", 240 | alignment='rc', 241 | caption='hello world', 242 | label='test') 243 | # produces the exact same thing as 244 | t2 = matrix2latex(m, None, "align*", "pmatrix") 245 | assert t1 == t2 246 | \end{pyblock} 247 | \py{t1} 248 | The scary thing is that \pygment{python}{headerColumn} actually works when creating a matrix, 249 | it just looks a bit wierd. 250 | 251 | The options presented by this program represents what I need when creating a table, 252 | if you need a more sophisticated table you must either change the python code 253 | (feel free to submit a patch), manually adjust the output afterwards 254 | or adjust the input (remember that the input can be a nested list of strings). 255 | \url{http://en.wikibooks.org/wiki/LaTeX/Tables} gives an excellent overview 256 | of some advanced table techniques. 257 | 258 | The booktabs.pdf documentation is an excellent guide to proper table creation, 259 | matrix2latex does not incorporate all the features of this package (yet). 260 | 261 | \subsection{Compatibility with other packages} 262 | Bellow support for the python package Pandas and the latex package siunitx will be outlined. 263 | \subsubsection{Pandas support (a python package)} 264 | Some preliminary support for Pandas\footnote{\url{http://pandas.pydata.org/}} 265 | is available, pandas series is shown in table \ref{tab:pd_series}, while 266 | a pandas DataFrame is displayed in table \ref{tab:pd_data_frame}. 267 | \begin{pycode}[pandas] 268 | from matrix2latex import matrix2latex 269 | \end{pycode} 270 | \begin{pyblock}[pandas] 271 | import pandas as pd 272 | s = pd.Series([2, 4, 2, 42, 5], 273 | index=['a', 'b', 'c', 'd', 'e']) 274 | t = matrix2latex(s, caption='Pandas Series', 275 | label='pd_series') 276 | 277 | m = pd.DataFrame([[1, 1], [2, 4], [3, 9]], 278 | index=['item1', 'item2', 'item3'], 279 | columns=['a', 'b']) 280 | t += matrix2latex(m, caption='Pandas Dataframe', 281 | label='pd_data_frame') 282 | \end{pyblock} 283 | \py[pandas]{t} 284 | 285 | Matrix2latex automatically detects and uses the \pygment{python}{index} 286 | and \pygment{python}{columns} attributes for 287 | \pygment{python}{headerColumn} and 288 | \pygment{python}{headerRow} respectively. You can override this behavior by passing 289 | \pygment{python}{headerColumn=None, headerRow=None}. 290 | 291 | \subsubsection{siunitx (a latex package)} 292 | \pygment{python}{siunitx} provides support for physical quantities with units 293 | and introduces a new column type called 294 | \pygment{latex}{S} which is supported by matrix2latex. 295 | Note that the dollar signs must be suppressed, so the 296 | \pygment{python}{format} is changed from the default 297 | \pygment{python}{'$%.2g$'} 298 | to 299 | \pygment{python}{'%.2g'}. 300 | \begin{pycode}[siunitx] 301 | from matrix2latex import matrix2latex 302 | \end{pycode} 303 | \begin{pyblock}[siunitx] 304 | import numpy as np 305 | R1 = 50 # ohm 306 | R2 = 377 # ohm 307 | V = np.array([1, 1.2, 100]) # V 308 | I1 = V/R1 # A 309 | I2 = V/R2 310 | caption = r"""Calculated currents through a 311 | $\SI{%g}{\ohm}$ and $\SI{%g}{\ohm}$ 312 | resistor.""" % (R1, R2) 313 | headerRow = [['$V$', r'$V/\SI{%g}{\ohm}$' % R1, r'$V/\SI{%g}{\ohm}$' % R2], 314 | [r'\si{V}', r'\si{A}', r'\si{A}']] 315 | t = matrix2latex([V, I1, I2], caption=caption, transpose=True, 316 | alignment='S', format='%.2g', headerRow=headerRow) 317 | \end{pyblock} 318 | \py[siunitx]{t} 319 | %Both caption and label will do nothing if tabular environment is not used. 320 | % \begin{pycode} 321 | % import sys 322 | % sys.path.append('../') 323 | % \end{pycode} 324 | % \begin{pyblock} 325 | % from matrix2latex import matrix2latex 326 | % from numpy import matrix 327 | % m = matrix("1 2 4;3 4 6") # numpy matrix or 328 | % m = [[1, 2, 4], [3, 4, 6]] # python nested list 329 | % matrix2latex(m, "test", "table", "center", "tabular", format="$%.2f$", alignment="lcr") 330 | % # or since table, center and tabular is default: 331 | % t = matrix2latex(m, format="$%.2f$", alignment="lcr") 332 | % # produces: 333 | % \end{pyblock} 334 | % \py{t} 335 | 336 | \section{Usage examples} 337 | The usefulness of a programming interface to create \LaTeX{} 338 | tables becomes apparent when the data is dynamically created by python. 339 | This can be either because you want flexibility with respect to the tables size 340 | or because the table content is somehow created by python. 341 | 342 | One day you decide to compare different implementations of the 343 | factorial functions, you start by writing the following file 344 | as \verb!factorial.py! 345 | \begin{pyblock}[factorial] 346 | # built in factorial 347 | from math import factorial as factorialMath 348 | 349 | # recursive 350 | def factorialRecursive(n): 351 | if n == 0: 352 | return 1 353 | elif n == 1: 354 | return n 355 | else: 356 | return n*factorialRecursive(n-1) 357 | 358 | # sequential 359 | def factorialSequential(n): 360 | if n == 0: 361 | return 1 362 | res = 1 363 | for k in xrange(2, n+1): 364 | res *= k 365 | return res 366 | \end{pyblock} 367 | 368 | The first thing to do is to verify that the three implementations actually give 369 | the same results, for this we simply loop over the different functions and try for 370 | different values of $n$. The result is shown in table \ref{tab:facV}. 371 | \begin{pyblock}[factorial] 372 | from matrix2latex import matrix2latex 373 | N = range(0, 10) 374 | table = list() 375 | for func in (factorialMath, 376 | factorialRecursive, 377 | factorialSequential): 378 | row = list() 379 | for n in N: 380 | res = func(n) # call func 381 | row.append(res)# append result to row 382 | table.append(row) # append row to table 383 | 384 | # row labels 385 | rl = ['$n$', 'Built-in', 'Recursive', 'Sequential'] 386 | caption = '''Vertifying that the different factorial 387 | implementations gives the same results''' 388 | matrix2latex(table, 'facV', caption=caption, 389 | headerColumn=N, headerRow=rl, 390 | alignment='r', transpose=True) 391 | \end{pyblock} 392 | \input{facV} 393 | 394 | What we really wanted to do was to 395 | compare the speed of the different implementations. To do this 396 | we use the python package timeit, shown bellow. The speed comparision is given in table \ref{tab:facT}. 397 | \begin{pyblock}[factorial] 398 | import timeit 399 | table = list() 400 | for func in ('factorialMath', 401 | 'factorialRecursive', 402 | 'factorialSequential'): 403 | row = list() 404 | for n in N: 405 | statement = 'factorial.{func}({n})'.format(func=func, 406 | n=n) 407 | setup = 'import factorial' 408 | # measure time 409 | res = timeit.repeat(statement, setup) 410 | row.append(min(res)) # append result 411 | table.append(row) # append row to table 412 | 413 | rl = ['$n$', 'Built-in [$s$]', 414 | 'Recursive [$s$]', 'Sequential [$s$]'] 415 | caption = '''Comparing execution time for 416 | the different factorial implementations''' 417 | matrix2latex(table, 'facT', caption=caption, 418 | headerColumn=N, headerRow=rl, 419 | transpose=True, format='$%.3f$') 420 | \end{pyblock} 421 | \input{facT} 422 | 423 | As an additional example, see \verb!test_compatibility.py! to see how table \ref{tab:compatibleTable} 424 | was created. 425 | -------------------------------------------------------------------------------- /doc/tex/texPreamble.tex: -------------------------------------------------------------------------------- 1 | % This file is part of matrix2latex. 2 | 3 | % matrix2latex is free software: you can redistribute it and/or modify 4 | % it under the terms of the GNU General Public License as published by 5 | % the Free Software Foundation, either version 3 of the License, or 6 | % (at your option) any later version. 7 | 8 | % matrix2latex is distributed in the hope that it will be useful, 9 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | % GNU General Public License for more details. 12 | 13 | % You should have received a copy of the GNU General Public License 14 | % along with matrix2latex. If not, see . 15 | 16 | \documentclass[12pt, a4paper, reqno]{report} 17 | %\documentclass[a4paper, english]{IEEEtran} 18 | \usepackage[english]{babel} 19 | \usepackage[utf8]{inputenc} 20 | 21 | %\usepackage[cmex10]{amsmath} % Recommended by IEEEtran 22 | %\interdisplaylinepenalty=2500 % Recommended by IEEEtran, long math 23 | \usepackage{amsmath} 24 | \usepackage{amsfonts, amssymb, amsthm} 25 | \allowdisplaybreaks[1] % displaybreak in math is allowed but avoided 26 | 27 | \usepackage{graphicx} % include figures 28 | \usepackage{hyperref} % links 29 | \usepackage{siunitx} 30 | \usepackage{booktabs} % Fancy tables 31 | \usepackage{caption} % Fixes spacing between caption and table 32 | 33 | \usepackage{pythontex} 34 | \setpythontexworkingdir{.} 35 | 36 | \usepackage{listings} % include code 37 | \lstset{language = python, 38 | showspaces = false, 39 | showtabs = false, 40 | showstringspaces = false, 41 | mathescape = true} 42 | 43 | 44 | % Useful macros 45 | \providecommand{\fixme}[1]{\textbf{{\textsc{FIXME:} #1 }}} 46 | 47 | % Math 48 | \providecommand{\e}[1]{\ensuremath{\times 10^{#1}}} % scientific notation 49 | 50 | \author{Øystein Bjørndal} 51 | -------------------------------------------------------------------------------- /doc/tmp.tex: -------------------------------------------------------------------------------- 1 | \begin{table}[ht] 2 | \begin{center} 3 | \label{tab:tmp} 4 | \begin{tabular}{ccc} 5 | \toprule 6 | $1$ & $2$ & $3$\\ 7 | $4$ & $5$ & $6$\\ 8 | \bottomrule 9 | \end{tabular} 10 | \end{center} 11 | \end{table} -------------------------------------------------------------------------------- /doc_sphinx/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(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/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/matrix2latex.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/matrix2latex.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/matrix2latex" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/matrix2latex" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /doc_sphinx/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # matrix2latex documentation build configuration file, created by 4 | # sphinx-quickstart on Sun Feb 9 14:50:03 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | #sys.path.insert(0, os.path.abspath('.')) 22 | sys.path.insert(0, os.path.abspath('../matrix2latex')) 23 | # -- General configuration ------------------------------------------------ 24 | 25 | # If your documentation needs a minimal Sphinx version, state it here. 26 | #needs_sphinx = '1.0' 27 | 28 | # Add any Sphinx extension module names here, as strings. They can be 29 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 30 | # ones. 31 | extensions = [ 32 | 'sphinx.ext.autodoc', 33 | 'sphinx.ext.doctest', 34 | 'sphinx.ext.coverage', 35 | 'sphinx.ext.pngmath', 36 | # 'sphinx.ext.mathjax', 37 | 'sphinx.ext.ifconfig', 38 | ] 39 | 40 | # Add any paths that contain templates here, relative to this directory. 41 | templates_path = ['_templates'] 42 | 43 | # The suffix of source filenames. 44 | source_suffix = '.txt' 45 | 46 | # The encoding of source files. 47 | #source_encoding = 'utf-8-sig' 48 | 49 | # The master toctree document. 50 | master_doc = 'index' 51 | 52 | # General information about the project. 53 | project = u'matrix2latex' 54 | copyright = u'2014, obtitus@gmail.com' 55 | 56 | # The version info for the project you're documenting, acts as replacement for 57 | # |version| and |release|, also used in various other places throughout the 58 | # built documents. 59 | # 60 | # The short X.Y version. 61 | version = '1.0' 62 | # The full version, including alpha/beta/rc tags. 63 | release = '1.0.0' 64 | 65 | # The language for content autogenerated by Sphinx. Refer to documentation 66 | # for a list of supported languages. 67 | #language = None 68 | 69 | # There are two options for replacing |today|: either, you set today to some 70 | # non-false value, then it is used: 71 | #today = '' 72 | # Else, today_fmt is used as the format for a strftime call. 73 | #today_fmt = '%B %d, %Y' 74 | 75 | # List of patterns, relative to source directory, that match files and 76 | # directories to ignore when looking for source files. 77 | exclude_patterns = ['_build'] 78 | 79 | # The reST default role (used for this markup: `text`) to use for all 80 | # documents. 81 | #default_role = None 82 | 83 | # If true, '()' will be appended to :func: etc. cross-reference text. 84 | #add_function_parentheses = True 85 | 86 | # If true, the current module name will be prepended to all description 87 | # unit titles (such as .. function::). 88 | #add_module_names = True 89 | 90 | # If true, sectionauthor and moduleauthor directives will be shown in the 91 | # output. They are ignored by default. 92 | #show_authors = False 93 | 94 | # The name of the Pygments (syntax highlighting) style to use. 95 | pygments_style = 'sphinx' 96 | 97 | # A list of ignored prefixes for module index sorting. 98 | #modindex_common_prefix = [] 99 | 100 | # If true, keep warnings as "system message" paragraphs in the built documents. 101 | #keep_warnings = False 102 | 103 | 104 | # -- Options for HTML output ---------------------------------------------- 105 | 106 | # The theme to use for HTML and HTML Help pages. See the documentation for 107 | # a list of builtin themes. 108 | html_theme = 'default' 109 | 110 | # Theme options are theme-specific and customize the look and feel of a theme 111 | # further. For a list of options available for each theme, see the 112 | # documentation. 113 | #html_theme_options = {} 114 | 115 | # Add any paths that contain custom themes here, relative to this directory. 116 | #html_theme_path = [] 117 | 118 | # The name for this set of Sphinx documents. If None, it defaults to 119 | # " v documentation". 120 | #html_title = None 121 | 122 | # A shorter title for the navigation bar. Default is the same as html_title. 123 | #html_short_title = None 124 | 125 | # The name of an image file (relative to this directory) to place at the top 126 | # of the sidebar. 127 | #html_logo = None 128 | 129 | # The name of an image file (within the static path) to use as favicon of the 130 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 131 | # pixels large. 132 | #html_favicon = None 133 | 134 | # Add any paths that contain custom static files (such as style sheets) here, 135 | # relative to this directory. They are copied after the builtin static files, 136 | # so a file named "default.css" will overwrite the builtin "default.css". 137 | html_static_path = ['_static'] 138 | 139 | # Add any extra paths that contain custom files (such as robots.txt or 140 | # .htaccess) here, relative to this directory. These files are copied 141 | # directly to the root of the documentation. 142 | #html_extra_path = [] 143 | 144 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 145 | # using the given strftime format. 146 | #html_last_updated_fmt = '%b %d, %Y' 147 | 148 | # If true, SmartyPants will be used to convert quotes and dashes to 149 | # typographically correct entities. 150 | #html_use_smartypants = True 151 | 152 | # Custom sidebar templates, maps document names to template names. 153 | #html_sidebars = {} 154 | 155 | # Additional templates that should be rendered to pages, maps page names to 156 | # template names. 157 | #html_additional_pages = {} 158 | 159 | # If false, no module index is generated. 160 | #html_domain_indices = True 161 | 162 | # If false, no index is generated. 163 | #html_use_index = True 164 | 165 | # If true, the index is split into individual pages for each letter. 166 | #html_split_index = False 167 | 168 | # If true, links to the reST sources are added to the pages. 169 | #html_show_sourcelink = True 170 | 171 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 172 | #html_show_sphinx = True 173 | 174 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 175 | #html_show_copyright = True 176 | 177 | # If true, an OpenSearch description file will be output, and all pages will 178 | # contain a tag referring to it. The value of this option must be the 179 | # base URL from which the finished HTML is served. 180 | #html_use_opensearch = '' 181 | 182 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 183 | #html_file_suffix = None 184 | 185 | # Output file base name for HTML help builder. 186 | htmlhelp_basename = 'matrix2latexdoc' 187 | 188 | 189 | # -- Options for LaTeX output --------------------------------------------- 190 | 191 | latex_elements = { 192 | # The paper size ('letterpaper' or 'a4paper'). 193 | #'papersize': 'letterpaper', 194 | 195 | # The font size ('10pt', '11pt' or '12pt'). 196 | #'pointsize': '10pt', 197 | 198 | # Additional stuff for the LaTeX preamble. 199 | #'preamble': '', 200 | } 201 | 202 | # Grouping the document tree into LaTeX files. List of tuples 203 | # (source start file, target name, title, 204 | # author, documentclass [howto, manual, or own class]). 205 | latex_documents = [ 206 | ('index', 'matrix2latex.tex', u'matrix2latex Documentation', 207 | u'obtitus@gmail.com', 'manual'), 208 | ] 209 | 210 | # The name of an image file (relative to this directory) to place at the top of 211 | # the title page. 212 | #latex_logo = None 213 | 214 | # For "manual" documents, if this is true, then toplevel headings are parts, 215 | # not chapters. 216 | #latex_use_parts = False 217 | 218 | # If true, show page references after internal links. 219 | #latex_show_pagerefs = False 220 | 221 | # If true, show URL addresses after external links. 222 | #latex_show_urls = False 223 | 224 | # Documents to append as an appendix to all manuals. 225 | #latex_appendices = [] 226 | 227 | # If false, no module index is generated. 228 | #latex_domain_indices = True 229 | 230 | 231 | # -- Options for manual page output --------------------------------------- 232 | 233 | # One entry per manual page. List of tuples 234 | # (source start file, name, description, authors, manual section). 235 | man_pages = [ 236 | ('index', 'matrix2latex', u'matrix2latex Documentation', 237 | [u'obtitus@gmail.com'], 1) 238 | ] 239 | 240 | # If true, show URL addresses after external links. 241 | #man_show_urls = False 242 | 243 | 244 | # -- Options for Texinfo output ------------------------------------------- 245 | 246 | # Grouping the document tree into Texinfo files. List of tuples 247 | # (source start file, target name, title, author, 248 | # dir menu entry, description, category) 249 | texinfo_documents = [ 250 | ('index', 'matrix2latex', u'matrix2latex Documentation', 251 | u'obtitus@gmail.com', 'matrix2latex', 'One line description of project.', 252 | 'Miscellaneous'), 253 | ] 254 | 255 | # Documents to append as an appendix to all manuals. 256 | #texinfo_appendices = [] 257 | 258 | # If false, no module index is generated. 259 | #texinfo_domain_indices = True 260 | 261 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 262 | #texinfo_show_urls = 'footnote' 263 | 264 | # If true, do not generate a @detailmenu in the "Top" node's menu. 265 | #texinfo_no_detailmenu = False 266 | -------------------------------------------------------------------------------- /doc_sphinx/index.txt: -------------------------------------------------------------------------------- 1 | .. matrix2latex documentation master file, created by 2 | sphinx-quickstart on Sun Feb 9 14:50:03 2014. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to matrix2latex's documentation! 7 | ======================================== 8 | 9 | Python function syntax 10 | ====================== 11 | 12 | .. toctree:: 13 | :maxdepth: 2 14 | 15 | 16 | .. autofunction:: matrix2latex.matrix2latex 17 | .. autofunction:: render.matrix2image 18 | 19 | Indices and tables 20 | ================== 21 | 22 | * :ref:`genindex` 23 | * :ref:`modindex` 24 | * :ref:`search` 25 | 26 | -------------------------------------------------------------------------------- /doc_sphinx/sphinxext/apigen.py: -------------------------------------------------------------------------------- 1 | """Attempt to generate templates for module reference with Sphinx 2 | 3 | XXX - we exclude extension modules 4 | 5 | To include extension modules, first identify them as valid in the 6 | ``_uri2path`` method, then handle them in the ``_parse_module`` script. 7 | 8 | We get functions and classes by parsing the text of .py files. 9 | Alternatively we could import the modules for discovery, and we'd have 10 | to do that for extension modules. This would involve changing the 11 | ``_parse_module`` method to work via import and introspection, and 12 | might involve changing ``discover_modules`` (which determines which 13 | files are modules, and therefore which module URIs will be passed to 14 | ``_parse_module``). 15 | 16 | NOTE: this is a modified version of a script originally shipped with the 17 | PyMVPA project, which we've adapted for NIPY use. PyMVPA is an MIT-licensed 18 | project.""" 19 | 20 | # Stdlib imports 21 | import os 22 | import re 23 | 24 | # Functions and classes 25 | class ApiDocWriter(object): 26 | ''' Class for automatic detection and parsing of API docs 27 | to Sphinx-parsable reST format''' 28 | 29 | # only separating first two levels 30 | rst_section_levels = ['*', '=', '-', '~', '^'] 31 | 32 | def __init__(self, 33 | package_name, 34 | rst_extension='.rst', 35 | package_skip_patterns=None, 36 | module_skip_patterns=None, 37 | ): 38 | ''' Initialize package for parsing 39 | 40 | Parameters 41 | ---------- 42 | package_name : string 43 | Name of the top-level package. *package_name* must be the 44 | name of an importable package 45 | rst_extension : string, optional 46 | Extension for reST files, default '.rst' 47 | package_skip_patterns : None or sequence of {strings, regexps} 48 | Sequence of strings giving URIs of packages to be excluded 49 | Operates on the package path, starting at (including) the 50 | first dot in the package path, after *package_name* - so, 51 | if *package_name* is ``sphinx``, then ``sphinx.util`` will 52 | result in ``.util`` being passed for earching by these 53 | regexps. If is None, gives default. Default is: 54 | ['\.tests$'] 55 | module_skip_patterns : None or sequence 56 | Sequence of strings giving URIs of modules to be excluded 57 | Operates on the module name including preceding URI path, 58 | back to the first dot after *package_name*. For example 59 | ``sphinx.util.console`` results in the string to search of 60 | ``.util.console`` 61 | If is None, gives default. Default is: 62 | ['\.setup$', '\._'] 63 | ''' 64 | if package_skip_patterns is None: 65 | package_skip_patterns = ['\\.tests$'] 66 | if module_skip_patterns is None: 67 | module_skip_patterns = ['\\.setup$', '\\._'] 68 | self.package_name = package_name 69 | self.rst_extension = rst_extension 70 | self.package_skip_patterns = package_skip_patterns 71 | self.module_skip_patterns = module_skip_patterns 72 | 73 | def get_package_name(self): 74 | return self._package_name 75 | 76 | def set_package_name(self, package_name): 77 | ''' Set package_name 78 | 79 | >>> docwriter = ApiDocWriter('sphinx') 80 | >>> import sphinx 81 | >>> docwriter.root_path == sphinx.__path__[0] 82 | True 83 | >>> docwriter.package_name = 'docutils' 84 | >>> import docutils 85 | >>> docwriter.root_path == docutils.__path__[0] 86 | True 87 | ''' 88 | # It's also possible to imagine caching the module parsing here 89 | self._package_name = package_name 90 | self.root_module = __import__(package_name) 91 | self.root_path = self.root_module.__path__[0] 92 | self.written_modules = None 93 | 94 | package_name = property(get_package_name, set_package_name, None, 95 | 'get/set package_name') 96 | 97 | def _get_object_name(self, line): 98 | ''' Get second token in line 99 | >>> docwriter = ApiDocWriter('sphinx') 100 | >>> docwriter._get_object_name(" def func(): ") 101 | 'func' 102 | >>> docwriter._get_object_name(" class Klass(object): ") 103 | 'Klass' 104 | >>> docwriter._get_object_name(" class Klass: ") 105 | 'Klass' 106 | ''' 107 | name = line.split()[1].split('(')[0].strip() 108 | # in case we have classes which are not derived from object 109 | # ie. old style classes 110 | return name.rstrip(':') 111 | 112 | def _uri2path(self, uri): 113 | ''' Convert uri to absolute filepath 114 | 115 | Parameters 116 | ---------- 117 | uri : string 118 | URI of python module to return path for 119 | 120 | Returns 121 | ------- 122 | path : None or string 123 | Returns None if there is no valid path for this URI 124 | Otherwise returns absolute file system path for URI 125 | 126 | Examples 127 | -------- 128 | >>> docwriter = ApiDocWriter('sphinx') 129 | >>> import sphinx 130 | >>> modpath = sphinx.__path__[0] 131 | >>> res = docwriter._uri2path('sphinx.builder') 132 | >>> res == os.path.join(modpath, 'builder.py') 133 | True 134 | >>> res = docwriter._uri2path('sphinx') 135 | >>> res == os.path.join(modpath, '__init__.py') 136 | True 137 | >>> docwriter._uri2path('sphinx.does_not_exist') 138 | 139 | ''' 140 | if uri == self.package_name: 141 | return os.path.join(self.root_path, '__init__.py') 142 | path = uri.replace('.', os.path.sep) 143 | path = path.replace(self.package_name + os.path.sep, '') 144 | path = os.path.join(self.root_path, path) 145 | # XXX maybe check for extensions as well? 146 | if os.path.exists(path + '.py'): # file 147 | path += '.py' 148 | elif os.path.exists(os.path.join(path, '__init__.py')): 149 | path = os.path.join(path, '__init__.py') 150 | else: 151 | return None 152 | return path 153 | 154 | def _path2uri(self, dirpath): 155 | ''' Convert directory path to uri ''' 156 | relpath = dirpath.replace(self.root_path, self.package_name) 157 | if relpath.startswith(os.path.sep): 158 | relpath = relpath[1:] 159 | return relpath.replace(os.path.sep, '.') 160 | 161 | def _parse_module(self, uri): 162 | ''' Parse module defined in *uri* ''' 163 | filename = self._uri2path(uri) 164 | if filename is None: 165 | # nothing that we could handle here. 166 | return ([],[]) 167 | f = open(filename, 'rt') 168 | functions, classes = self._parse_lines(f) 169 | f.close() 170 | return functions, classes 171 | 172 | def _parse_lines(self, linesource): 173 | ''' Parse lines of text for functions and classes ''' 174 | functions = [] 175 | classes = [] 176 | for line in linesource: 177 | if line.startswith('def ') and line.count('('): 178 | # exclude private stuff 179 | name = self._get_object_name(line) 180 | if not name.startswith('_'): 181 | functions.append(name) 182 | elif line.startswith('class '): 183 | # exclude private stuff 184 | name = self._get_object_name(line) 185 | if not name.startswith('_'): 186 | classes.append(name) 187 | else: 188 | pass 189 | functions.sort() 190 | classes.sort() 191 | return functions, classes 192 | 193 | def generate_api_doc(self, uri): 194 | '''Make autodoc documentation template string for a module 195 | 196 | Parameters 197 | ---------- 198 | uri : string 199 | python location of module - e.g 'sphinx.builder' 200 | 201 | Returns 202 | ------- 203 | S : string 204 | Contents of API doc 205 | ''' 206 | # get the names of all classes and functions 207 | functions, classes = self._parse_module(uri) 208 | if not len(functions) and not len(classes): 209 | print('WARNING: Empty -',uri) # dbg 210 | return '' 211 | 212 | # Make a shorter version of the uri that omits the package name for 213 | # titles 214 | uri_short = re.sub(r'^%s\.' % self.package_name,'',uri) 215 | 216 | ad = '.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n' 217 | 218 | chap_title = uri_short 219 | ad += (chap_title+'\n'+ self.rst_section_levels[1] * len(chap_title) 220 | + '\n\n') 221 | 222 | # Set the chapter title to read 'module' for all modules except for the 223 | # main packages 224 | if '.' in uri: 225 | title = 'Module: :mod:`' + uri_short + '`' 226 | else: 227 | title = ':mod:`' + uri_short + '`' 228 | ad += title + '\n' + self.rst_section_levels[2] * len(title) 229 | 230 | if len(classes): 231 | ad += '\nInheritance diagram for ``%s``:\n\n' % uri 232 | ad += '.. inheritance-diagram:: %s \n' % uri 233 | ad += ' :parts: 3\n' 234 | 235 | ad += '\n.. automodule:: ' + uri + '\n' 236 | ad += '\n.. currentmodule:: ' + uri + '\n' 237 | multi_class = len(classes) > 1 238 | multi_fx = len(functions) > 1 239 | if multi_class: 240 | ad += '\n' + 'Classes' + '\n' + \ 241 | self.rst_section_levels[2] * 7 + '\n' 242 | elif len(classes) and multi_fx: 243 | ad += '\n' + 'Class' + '\n' + \ 244 | self.rst_section_levels[2] * 5 + '\n' 245 | for c in classes: 246 | ad += '\n:class:`' + c + '`\n' \ 247 | + self.rst_section_levels[multi_class + 2 ] * \ 248 | (len(c)+9) + '\n\n' 249 | ad += '\n.. autoclass:: ' + c + '\n' 250 | # must NOT exclude from index to keep cross-refs working 251 | ad += ' :members:\n' \ 252 | ' :undoc-members:\n' \ 253 | ' :show-inheritance:\n' \ 254 | ' :inherited-members:\n' \ 255 | '\n' \ 256 | ' .. automethod:: __init__\n' 257 | if multi_fx: 258 | ad += '\n' + 'Functions' + '\n' + \ 259 | self.rst_section_levels[2] * 9 + '\n\n' 260 | elif len(functions) and multi_class: 261 | ad += '\n' + 'Function' + '\n' + \ 262 | self.rst_section_levels[2] * 8 + '\n\n' 263 | for f in functions: 264 | # must NOT exclude from index to keep cross-refs working 265 | ad += '\n.. autofunction:: ' + uri + '.' + f + '\n\n' 266 | return ad 267 | 268 | def _survives_exclude(self, matchstr, match_type): 269 | ''' Returns True if *matchstr* does not match patterns 270 | 271 | ``self.package_name`` removed from front of string if present 272 | 273 | Examples 274 | -------- 275 | >>> dw = ApiDocWriter('sphinx') 276 | >>> dw._survives_exclude('sphinx.okpkg', 'package') 277 | True 278 | >>> dw.package_skip_patterns.append('^\\.badpkg$') 279 | >>> dw._survives_exclude('sphinx.badpkg', 'package') 280 | False 281 | >>> dw._survives_exclude('sphinx.badpkg', 'module') 282 | True 283 | >>> dw._survives_exclude('sphinx.badmod', 'module') 284 | True 285 | >>> dw.module_skip_patterns.append('^\\.badmod$') 286 | >>> dw._survives_exclude('sphinx.badmod', 'module') 287 | False 288 | ''' 289 | if match_type == 'module': 290 | patterns = self.module_skip_patterns 291 | elif match_type == 'package': 292 | patterns = self.package_skip_patterns 293 | else: 294 | raise ValueError('Cannot interpret match type "%s"' 295 | % match_type) 296 | # Match to URI without package name 297 | L = len(self.package_name) 298 | if matchstr[:L] == self.package_name: 299 | matchstr = matchstr[L:] 300 | for pat in patterns: 301 | try: 302 | pat.search 303 | except AttributeError: 304 | pat = re.compile(pat) 305 | if pat.search(matchstr): 306 | return False 307 | return True 308 | 309 | def discover_modules(self): 310 | ''' Return module sequence discovered from ``self.package_name`` 311 | 312 | 313 | Parameters 314 | ---------- 315 | None 316 | 317 | Returns 318 | ------- 319 | mods : sequence 320 | Sequence of module names within ``self.package_name`` 321 | 322 | Examples 323 | -------- 324 | >>> dw = ApiDocWriter('sphinx') 325 | >>> mods = dw.discover_modules() 326 | >>> 'sphinx.util' in mods 327 | True 328 | >>> dw.package_skip_patterns.append('\.util$') 329 | >>> 'sphinx.util' in dw.discover_modules() 330 | False 331 | >>> 332 | ''' 333 | modules = [self.package_name] 334 | # raw directory parsing 335 | for dirpath, dirnames, filenames in os.walk(self.root_path): 336 | # Check directory names for packages 337 | root_uri = self._path2uri(os.path.join(self.root_path, 338 | dirpath)) 339 | for dirname in dirnames[:]: # copy list - we modify inplace 340 | package_uri = '.'.join((root_uri, dirname)) 341 | if (self._uri2path(package_uri) and 342 | self._survives_exclude(package_uri, 'package')): 343 | modules.append(package_uri) 344 | else: 345 | dirnames.remove(dirname) 346 | # Check filenames for modules 347 | for filename in filenames: 348 | module_name = filename[:-3] 349 | module_uri = '.'.join((root_uri, module_name)) 350 | if (self._uri2path(module_uri) and 351 | self._survives_exclude(module_uri, 'module')): 352 | modules.append(module_uri) 353 | return sorted(modules) 354 | 355 | def write_modules_api(self, modules,outdir): 356 | # write the list 357 | written_modules = [] 358 | for m in modules: 359 | api_str = self.generate_api_doc(m) 360 | if not api_str: 361 | continue 362 | # write out to file 363 | outfile = os.path.join(outdir, 364 | m + self.rst_extension) 365 | fileobj = open(outfile, 'wt') 366 | fileobj.write(api_str) 367 | fileobj.close() 368 | written_modules.append(m) 369 | self.written_modules = written_modules 370 | 371 | def write_api_docs(self, outdir): 372 | """Generate API reST files. 373 | 374 | Parameters 375 | ---------- 376 | outdir : string 377 | Directory name in which to store files 378 | We create automatic filenames for each module 379 | 380 | Returns 381 | ------- 382 | None 383 | 384 | Notes 385 | ----- 386 | Sets self.written_modules to list of written modules 387 | """ 388 | if not os.path.exists(outdir): 389 | os.mkdir(outdir) 390 | # compose list of modules 391 | modules = self.discover_modules() 392 | self.write_modules_api(modules,outdir) 393 | 394 | def write_index(self, outdir, froot='gen', relative_to=None): 395 | """Make a reST API index file from written files 396 | 397 | Parameters 398 | ---------- 399 | path : string 400 | Filename to write index to 401 | outdir : string 402 | Directory to which to write generated index file 403 | froot : string, optional 404 | root (filename without extension) of filename to write to 405 | Defaults to 'gen'. We add ``self.rst_extension``. 406 | relative_to : string 407 | path to which written filenames are relative. This 408 | component of the written file path will be removed from 409 | outdir, in the generated index. Default is None, meaning, 410 | leave path as it is. 411 | """ 412 | if self.written_modules is None: 413 | raise ValueError('No modules written') 414 | # Get full filename path 415 | path = os.path.join(outdir, froot+self.rst_extension) 416 | # Path written into index is relative to rootpath 417 | if relative_to is not None: 418 | relpath = outdir.replace(relative_to + os.path.sep, '') 419 | else: 420 | relpath = outdir 421 | idx = open(path,'wt') 422 | w = idx.write 423 | w('.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n') 424 | w('.. toctree::\n\n') 425 | for f in self.written_modules: 426 | w(' %s\n' % os.path.join(relpath,f)) 427 | idx.close() 428 | -------------------------------------------------------------------------------- /doc_sphinx/sphinxext/docscrape.py: -------------------------------------------------------------------------------- 1 | """Extract reference documentation from the NumPy source tree. 2 | 3 | """ 4 | 5 | import inspect 6 | import textwrap 7 | import re 8 | import pydoc 9 | from StringIO import StringIO 10 | from warnings import warn 11 | 4 12 | class Reader(object): 13 | """A line-based string reader. 14 | 15 | """ 16 | def __init__(self, data): 17 | """ 18 | Parameters 19 | ---------- 20 | data : str 21 | String with lines separated by '\n'. 22 | 23 | """ 24 | if isinstance(data,list): 25 | self._str = data 26 | else: 27 | self._str = data.split('\n') # store string as list of lines 28 | 29 | self.reset() 30 | 31 | def __getitem__(self, n): 32 | return self._str[n] 33 | 34 | def reset(self): 35 | self._l = 0 # current line nr 36 | 37 | def read(self): 38 | if not self.eof(): 39 | out = self[self._l] 40 | self._l += 1 41 | return out 42 | else: 43 | return '' 44 | 45 | def seek_next_non_empty_line(self): 46 | for l in self[self._l:]: 47 | if l.strip(): 48 | break 49 | else: 50 | self._l += 1 51 | 52 | def eof(self): 53 | return self._l >= len(self._str) 54 | 55 | def read_to_condition(self, condition_func): 56 | start = self._l 57 | for line in self[start:]: 58 | if condition_func(line): 59 | return self[start:self._l] 60 | self._l += 1 61 | if self.eof(): 62 | return self[start:self._l+1] 63 | return [] 64 | 65 | def read_to_next_empty_line(self): 66 | self.seek_next_non_empty_line() 67 | def is_empty(line): 68 | return not line.strip() 69 | return self.read_to_condition(is_empty) 70 | 71 | def read_to_next_unindented_line(self): 72 | def is_unindented(line): 73 | return (line.strip() and (len(line.lstrip()) == len(line))) 74 | return self.read_to_condition(is_unindented) 75 | 76 | def peek(self,n=0): 77 | if self._l + n < len(self._str): 78 | return self[self._l + n] 79 | else: 80 | return '' 81 | 82 | def is_empty(self): 83 | return not ''.join(self._str).strip() 84 | 85 | 86 | class NumpyDocString(object): 87 | def __init__(self,docstring): 88 | docstring = textwrap.dedent(docstring).split('\n') 89 | 90 | self._doc = Reader(docstring) 91 | self._parsed_data = { 92 | 'Signature': '', 93 | 'Summary': [''], 94 | 'Extended Summary': [], 95 | 'Parameters': [], 96 | 'Returns': [], 97 | 'Raises': [], 98 | 'Warns': [], 99 | 'Other Parameters': [], 100 | 'Attributes': [], 101 | 'Methods': [], 102 | 'See Also': [], 103 | 'Notes': [], 104 | 'Warnings': [], 105 | 'References': '', 106 | 'Examples': '', 107 | 'index': {} 108 | } 109 | 110 | self._parse() 111 | 112 | def __getitem__(self,key): 113 | return self._parsed_data[key] 114 | 115 | def __setitem__(self,key,val): 116 | if not self._parsed_data.has_key(key): 117 | warn("Unknown section %s" % key) 118 | else: 119 | self._parsed_data[key] = val 120 | 121 | def _is_at_section(self): 122 | self._doc.seek_next_non_empty_line() 123 | 124 | if self._doc.eof(): 125 | return False 126 | 127 | l1 = self._doc.peek().strip() # e.g. Parameters 128 | 129 | if l1.startswith('.. index::'): 130 | return True 131 | 132 | l2 = self._doc.peek(1).strip() # ---------- or ========== 133 | return l2.startswith('-'*len(l1)) or l2.startswith('='*len(l1)) 134 | 135 | def _strip(self,doc): 136 | i = 0 137 | j = 0 138 | for i,line in enumerate(doc): 139 | if line.strip(): break 140 | 141 | for j,line in enumerate(doc[::-1]): 142 | if line.strip(): break 143 | 144 | return doc[i:len(doc)-j] 145 | 146 | def _read_to_next_section(self): 147 | section = self._doc.read_to_next_empty_line() 148 | 149 | while not self._is_at_section() and not self._doc.eof(): 150 | if not self._doc.peek(-1).strip(): # previous line was empty 151 | section += [''] 152 | 153 | section += self._doc.read_to_next_empty_line() 154 | 155 | return section 156 | 157 | def _read_sections(self): 158 | while not self._doc.eof(): 159 | data = self._read_to_next_section() 160 | name = data[0].strip() 161 | 162 | if name.startswith('..'): # index section 163 | yield name, data[1:] 164 | elif len(data) < 2: 165 | yield StopIteration 166 | else: 167 | yield name, self._strip(data[2:]) 168 | 169 | def _parse_param_list(self,content): 170 | r = Reader(content) 171 | params = [] 172 | while not r.eof(): 173 | header = r.read().strip() 174 | if ' : ' in header: 175 | arg_name, arg_type = header.split(' : ')[:2] 176 | else: 177 | arg_name, arg_type = header, '' 178 | 179 | desc = r.read_to_next_unindented_line() 180 | desc = dedent_lines(desc) 181 | 182 | params.append((arg_name,arg_type,desc)) 183 | 184 | return params 185 | 186 | 187 | _name_rgx = re.compile(r"^\s*(:(?P\w+):`(?P[a-zA-Z0-9_.-]+)`|" 188 | r" (?P[a-zA-Z0-9_.-]+))\s*", re.X) 189 | def _parse_see_also(self, content): 190 | """ 191 | func_name : Descriptive text 192 | continued text 193 | another_func_name : Descriptive text 194 | func_name1, func_name2, :meth:`func_name`, func_name3 195 | 196 | """ 197 | items = [] 198 | 199 | def parse_item_name(text): 200 | """Match ':role:`name`' or 'name'""" 201 | m = self._name_rgx.match(text) 202 | if m: 203 | g = m.groups() 204 | if g[1] is None: 205 | return g[3], None 206 | else: 207 | return g[2], g[1] 208 | raise ValueError("%s is not a item name" % text) 209 | 210 | def push_item(name, rest): 211 | if not name: 212 | return 213 | name, role = parse_item_name(name) 214 | items.append((name, list(rest), role)) 215 | del rest[:] 216 | 217 | current_func = None 218 | rest = [] 219 | 220 | for line in content: 221 | if not line.strip(): continue 222 | 223 | m = self._name_rgx.match(line) 224 | if m and line[m.end():].strip().startswith(':'): 225 | push_item(current_func, rest) 226 | current_func, line = line[:m.end()], line[m.end():] 227 | rest = [line.split(':', 1)[1].strip()] 228 | if not rest[0]: 229 | rest = [] 230 | elif not line.startswith(' '): 231 | push_item(current_func, rest) 232 | current_func = None 233 | if ',' in line: 234 | for func in line.split(','): 235 | push_item(func, []) 236 | elif line.strip(): 237 | current_func = line 238 | elif current_func is not None: 239 | rest.append(line.strip()) 240 | push_item(current_func, rest) 241 | return items 242 | 243 | def _parse_index(self, section, content): 244 | """ 245 | .. index: default 246 | :refguide: something, else, and more 247 | 248 | """ 249 | def strip_each_in(lst): 250 | return [s.strip() for s in lst] 251 | 252 | out = {} 253 | section = section.split('::') 254 | if len(section) > 1: 255 | out['default'] = strip_each_in(section[1].split(','))[0] 256 | for line in content: 257 | line = line.split(':') 258 | if len(line) > 2: 259 | out[line[1]] = strip_each_in(line[2].split(',')) 260 | return out 261 | 262 | def _parse_summary(self): 263 | """Grab signature (if given) and summary""" 264 | if self._is_at_section(): 265 | return 266 | 267 | summary = self._doc.read_to_next_empty_line() 268 | summary_str = " ".join([s.strip() for s in summary]).strip() 269 | if re.compile('^([\w., ]+=)?\s*[\w\.]+\(.*\)$').match(summary_str): 270 | self['Signature'] = summary_str 271 | if not self._is_at_section(): 272 | self['Summary'] = self._doc.read_to_next_empty_line() 273 | else: 274 | self['Summary'] = summary 275 | 276 | if not self._is_at_section(): 277 | self['Extended Summary'] = self._read_to_next_section() 278 | 279 | def _parse(self): 280 | self._doc.reset() 281 | self._parse_summary() 282 | 283 | for (section,content) in self._read_sections(): 284 | if not section.startswith('..'): 285 | section = ' '.join([s.capitalize() for s in section.split(' ')]) 286 | if section in ('Parameters', 'Attributes', 'Methods', 287 | 'Returns', 'Raises', 'Warns'): 288 | self[section] = self._parse_param_list(content) 289 | elif section.startswith('.. index::'): 290 | self['index'] = self._parse_index(section, content) 291 | elif section == 'See Also': 292 | self['See Also'] = self._parse_see_also(content) 293 | else: 294 | self[section] = content 295 | 296 | # string conversion routines 297 | 298 | def _str_header(self, name, symbol='-'): 299 | return [name, len(name)*symbol] 300 | 301 | def _str_indent(self, doc, indent=4): 302 | out = [] 303 | for line in doc: 304 | out += [' '*indent + line] 305 | return out 306 | 307 | def _str_signature(self): 308 | if self['Signature']: 309 | return [self['Signature'].replace('*','\*')] + [''] 310 | else: 311 | return [''] 312 | 313 | def _str_summary(self): 314 | if self['Summary']: 315 | return self['Summary'] + [''] 316 | else: 317 | return [] 318 | 319 | def _str_extended_summary(self): 320 | if self['Extended Summary']: 321 | return self['Extended Summary'] + [''] 322 | else: 323 | return [] 324 | 325 | def _str_param_list(self, name): 326 | out = [] 327 | if self[name]: 328 | out += self._str_header(name) 329 | for param,param_type,desc in self[name]: 330 | out += ['%s : %s' % (param, param_type)] 331 | out += self._str_indent(desc) 332 | out += [''] 333 | return out 334 | 335 | def _str_section(self, name): 336 | out = [] 337 | if self[name]: 338 | out += self._str_header(name) 339 | out += self[name] 340 | out += [''] 341 | return out 342 | 343 | def _str_see_also(self, func_role): 344 | if not self['See Also']: return [] 345 | out = [] 346 | out += self._str_header("See Also") 347 | last_had_desc = True 348 | for func, desc, role in self['See Also']: 349 | if role: 350 | link = ':%s:`%s`' % (role, func) 351 | elif func_role: 352 | link = ':%s:`%s`' % (func_role, func) 353 | else: 354 | link = "`%s`_" % func 355 | if desc or last_had_desc: 356 | out += [''] 357 | out += [link] 358 | else: 359 | out[-1] += ", %s" % link 360 | if desc: 361 | out += self._str_indent([' '.join(desc)]) 362 | last_had_desc = True 363 | else: 364 | last_had_desc = False 365 | out += [''] 366 | return out 367 | 368 | def _str_index(self): 369 | idx = self['index'] 370 | out = [] 371 | out += ['.. index:: %s' % idx.get('default','')] 372 | for section, references in idx.iteritems(): 373 | if section == 'default': 374 | continue 375 | out += [' :%s: %s' % (section, ', '.join(references))] 376 | return out 377 | 378 | def __str__(self, func_role=''): 379 | out = [] 380 | out += self._str_signature() 381 | out += self._str_summary() 382 | out += self._str_extended_summary() 383 | for param_list in ('Parameters','Returns','Raises'): 384 | out += self._str_param_list(param_list) 385 | out += self._str_section('Warnings') 386 | out += self._str_see_also(func_role) 387 | for s in ('Notes','References','Examples'): 388 | out += self._str_section(s) 389 | out += self._str_index() 390 | return '\n'.join(out) 391 | 392 | 393 | def indent(str,indent=4): 394 | indent_str = ' '*indent 395 | if str is None: 396 | return indent_str 397 | lines = str.split('\n') 398 | return '\n'.join(indent_str + l for l in lines) 399 | 400 | def dedent_lines(lines): 401 | """Deindent a list of lines maximally""" 402 | return textwrap.dedent("\n".join(lines)).split("\n") 403 | 404 | def header(text, style='-'): 405 | return text + '\n' + style*len(text) + '\n' 406 | 407 | 408 | class FunctionDoc(NumpyDocString): 409 | def __init__(self, func, role='func', doc=None): 410 | self._f = func 411 | self._role = role # e.g. "func" or "meth" 412 | if doc is None: 413 | doc = inspect.getdoc(func) or '' 414 | try: 415 | NumpyDocString.__init__(self, doc) 416 | except ValueError, e: 417 | print('*'*78) 418 | print("ERROR: '%s' while parsing `%s`" % (e, self._f)) 419 | print('*'*78) 420 | 421 | if not self['Signature']: 422 | func, func_name = self.get_func() 423 | try: 424 | # try to read signature 425 | argspec = inspect.getargspec(func) 426 | argspec = inspect.formatargspec(*argspec) 427 | argspec = argspec.replace('*','\*') 428 | signature = '%s%s' % (func_name, argspec) 429 | except TypeError, e: 430 | signature = '%s()' % func_name 431 | self['Signature'] = signature 432 | 433 | def get_func(self): 434 | func_name = getattr(self._f, '__name__', self.__class__.__name__) 435 | if inspect.isclass(self._f): 436 | func = getattr(self._f, '__call__', self._f.__init__) 437 | else: 438 | func = self._f 439 | return func, func_name 440 | 441 | def __str__(self): 442 | out = '' 443 | 444 | func, func_name = self.get_func() 445 | signature = self['Signature'].replace('*', '\*') 446 | 447 | roles = {'func': 'function', 448 | 'meth': 'method'} 449 | 450 | if self._role: 451 | if not roles.has_key(self._role): 452 | print("Warning: invalid role %s" % self._role) 453 | out += '.. %s:: %s\n \n\n' % (roles.get(self._role,''), 454 | func_name) 455 | 456 | out += super(FunctionDoc, self).__str__(func_role=self._role) 457 | return out 458 | 459 | 460 | class ClassDoc(NumpyDocString): 461 | def __init__(self,cls,modulename='',func_doc=FunctionDoc,doc=None): 462 | if not inspect.isclass(cls): 463 | raise ValueError("Initialise using a class. Got %r" % cls) 464 | self._cls = cls 465 | 466 | if modulename and not modulename.endswith('.'): 467 | modulename += '.' 468 | self._mod = modulename 469 | self._name = cls.__name__ 470 | self._func_doc = func_doc 471 | 472 | if doc is None: 473 | doc = pydoc.getdoc(cls) 474 | 475 | NumpyDocString.__init__(self, doc) 476 | 477 | @property 478 | def methods(self): 479 | return [name for name,func in inspect.getmembers(self._cls) 480 | if not name.startswith('_') and callable(func)] 481 | 482 | def __str__(self): 483 | out = '' 484 | out += super(ClassDoc, self).__str__() 485 | out += "\n\n" 486 | 487 | #for m in self.methods: 488 | # print("Parsing `%s`" % m) 489 | # out += str(self._func_doc(getattr(self._cls,m), 'meth')) + '\n\n' 490 | # out += '.. index::\n single: %s; %s\n\n' % (self._name, m) 491 | 492 | return out 493 | 494 | 495 | -------------------------------------------------------------------------------- /doc_sphinx/sphinxext/docscrape_sphinx.py: -------------------------------------------------------------------------------- 1 | import re, inspect, textwrap, pydoc 2 | from docscrape import NumpyDocString, FunctionDoc, ClassDoc 3 | 4 | class SphinxDocString(NumpyDocString): 5 | # string conversion routines 6 | def _str_header(self, name, symbol='`'): 7 | return ['.. rubric:: ' + name, ''] 8 | 9 | def _str_field_list(self, name): 10 | return [':' + name + ':'] 11 | 12 | def _str_indent(self, doc, indent=4): 13 | out = [] 14 | for line in doc: 15 | out += [' '*indent + line] 16 | return out 17 | 18 | def _str_signature(self): 19 | return [''] 20 | if self['Signature']: 21 | return ['``%s``' % self['Signature']] + [''] 22 | else: 23 | return [''] 24 | 25 | def _str_summary(self): 26 | return self['Summary'] + [''] 27 | 28 | def _str_extended_summary(self): 29 | return self['Extended Summary'] + [''] 30 | 31 | def _str_param_list(self, name): 32 | out = [] 33 | if self[name]: 34 | out += self._str_field_list(name) 35 | out += [''] 36 | for param,param_type,desc in self[name]: 37 | out += self._str_indent(['**%s** : %s' % (param.strip(), 38 | param_type)]) 39 | out += [''] 40 | out += self._str_indent(desc,8) 41 | out += [''] 42 | return out 43 | 44 | def _str_section(self, name): 45 | out = [] 46 | if self[name]: 47 | out += self._str_header(name) 48 | out += [''] 49 | content = textwrap.dedent("\n".join(self[name])).split("\n") 50 | out += content 51 | out += [''] 52 | return out 53 | 54 | def _str_see_also(self, func_role): 55 | out = [] 56 | if self['See Also']: 57 | see_also = super(SphinxDocString, self)._str_see_also(func_role) 58 | out = ['.. seealso::', ''] 59 | out += self._str_indent(see_also[2:]) 60 | return out 61 | 62 | def _str_warnings(self): 63 | out = [] 64 | if self['Warnings']: 65 | out = ['.. warning::', ''] 66 | out += self._str_indent(self['Warnings']) 67 | return out 68 | 69 | def _str_index(self): 70 | idx = self['index'] 71 | out = [] 72 | if len(idx) == 0: 73 | return out 74 | 75 | out += ['.. index:: %s' % idx.get('default','')] 76 | for section, references in idx.iteritems(): 77 | if section == 'default': 78 | continue 79 | elif section == 'refguide': 80 | out += [' single: %s' % (', '.join(references))] 81 | else: 82 | out += [' %s: %s' % (section, ','.join(references))] 83 | return out 84 | 85 | def _str_references(self): 86 | out = [] 87 | if self['References']: 88 | out += self._str_header('References') 89 | if isinstance(self['References'], str): 90 | self['References'] = [self['References']] 91 | out.extend(self['References']) 92 | out += [''] 93 | return out 94 | 95 | def __str__(self, indent=0, func_role="obj"): 96 | out = [] 97 | out += self._str_signature() 98 | out += self._str_index() + [''] 99 | out += self._str_summary() 100 | out += self._str_extended_summary() 101 | for param_list in ('Parameters', 'Attributes', 'Methods', 102 | 'Returns','Raises'): 103 | out += self._str_param_list(param_list) 104 | out += self._str_warnings() 105 | out += self._str_see_also(func_role) 106 | out += self._str_section('Notes') 107 | out += self._str_references() 108 | out += self._str_section('Examples') 109 | out = self._str_indent(out,indent) 110 | return '\n'.join(out) 111 | 112 | class SphinxFunctionDoc(SphinxDocString, FunctionDoc): 113 | pass 114 | 115 | class SphinxClassDoc(SphinxDocString, ClassDoc): 116 | pass 117 | 118 | def get_doc_object(obj, what=None, doc=None): 119 | if what is None: 120 | if inspect.isclass(obj): 121 | what = 'class' 122 | elif inspect.ismodule(obj): 123 | what = 'module' 124 | elif callable(obj): 125 | what = 'function' 126 | else: 127 | what = 'object' 128 | if what == 'class': 129 | return SphinxClassDoc(obj, '', func_doc=SphinxFunctionDoc, doc=doc) 130 | elif what in ('function', 'method'): 131 | return SphinxFunctionDoc(obj, '', doc=doc) 132 | else: 133 | if doc is None: 134 | doc = pydoc.getdoc(obj) 135 | return SphinxDocString(doc) 136 | 137 | -------------------------------------------------------------------------------- /doc_sphinx/sphinxext/inheritance_diagram.py: -------------------------------------------------------------------------------- 1 | """ 2 | Defines a docutils directive for inserting inheritance diagrams. 3 | 4 | Provide the directive with one or more classes or modules (separated 5 | by whitespace). For modules, all of the classes in that module will 6 | be used. 7 | 8 | Example:: 9 | 10 | Given the following classes: 11 | 12 | class A: pass 13 | class B(A): pass 14 | class C(A): pass 15 | class D(B, C): pass 16 | class E(B): pass 17 | 18 | .. inheritance-diagram: D E 19 | 20 | Produces a graph like the following: 21 | 22 | A 23 | / \ 24 | B C 25 | / \ / 26 | E D 27 | 28 | The graph is inserted as a PNG+image map into HTML and a PDF in 29 | LaTeX. 30 | """ 31 | 32 | import inspect 33 | import os 34 | import re 35 | import subprocess 36 | try: 37 | from hashlib import md5 38 | except ImportError: 39 | from md5 import md5 40 | 41 | from docutils.nodes import Body, Element 42 | from docutils.parsers.rst import directives 43 | from sphinx.roles import xfileref_role 44 | 45 | def my_import(name): 46 | """Module importer - taken from the python documentation. 47 | 48 | This function allows importing names with dots in them.""" 49 | 50 | mod = __import__(name) 51 | components = name.split('.') 52 | for comp in components[1:]: 53 | mod = getattr(mod, comp) 54 | return mod 55 | 56 | class DotException(Exception): 57 | pass 58 | 59 | class InheritanceGraph(object): 60 | """ 61 | Given a list of classes, determines the set of classes that 62 | they inherit from all the way to the root "object", and then 63 | is able to generate a graphviz dot graph from them. 64 | """ 65 | def __init__(self, class_names, show_builtins=False): 66 | """ 67 | *class_names* is a list of child classes to show bases from. 68 | 69 | If *show_builtins* is True, then Python builtins will be shown 70 | in the graph. 71 | """ 72 | self.class_names = class_names 73 | self.classes = self._import_classes(class_names) 74 | self.all_classes = self._all_classes(self.classes) 75 | if len(self.all_classes) == 0: 76 | raise ValueError("No classes found for inheritance diagram") 77 | self.show_builtins = show_builtins 78 | 79 | py_sig_re = re.compile(r'''^([\w.]*\.)? # class names 80 | (\w+) \s* $ # optionally arguments 81 | ''', re.VERBOSE) 82 | 83 | def _import_class_or_module(self, name): 84 | """ 85 | Import a class using its fully-qualified *name*. 86 | """ 87 | try: 88 | path, base = self.py_sig_re.match(name).groups() 89 | except: 90 | raise ValueError( 91 | "Invalid class or module '%s' specified for inheritance diagram" % name) 92 | fullname = (path or '') + base 93 | path = (path and path.rstrip('.')) 94 | if not path: 95 | path = base 96 | try: 97 | module = __import__(path, None, None, []) 98 | # We must do an import of the fully qualified name. Otherwise if a 99 | # subpackage 'a.b' is requested where 'import a' does NOT provide 100 | # 'a.b' automatically, then 'a.b' will not be found below. This 101 | # second call will force the equivalent of 'import a.b' to happen 102 | # after the top-level import above. 103 | my_import(fullname) 104 | 105 | except ImportError: 106 | raise ValueError( 107 | "Could not import class or module '%s' specified for inheritance diagram" % name) 108 | 109 | try: 110 | todoc = module 111 | for comp in fullname.split('.')[1:]: 112 | todoc = getattr(todoc, comp) 113 | except AttributeError: 114 | raise ValueError( 115 | "Could not find class or module '%s' specified for inheritance diagram" % name) 116 | 117 | # If a class, just return it 118 | if inspect.isclass(todoc): 119 | return [todoc] 120 | elif inspect.ismodule(todoc): 121 | classes = [] 122 | for cls in todoc.__dict__.values(): 123 | if inspect.isclass(cls) and cls.__module__ == todoc.__name__: 124 | classes.append(cls) 125 | return classes 126 | raise ValueError( 127 | "'%s' does not resolve to a class or module" % name) 128 | 129 | def _import_classes(self, class_names): 130 | """ 131 | Import a list of classes. 132 | """ 133 | classes = [] 134 | for name in class_names: 135 | classes.extend(self._import_class_or_module(name)) 136 | return classes 137 | 138 | def _all_classes(self, classes): 139 | """ 140 | Return a list of all classes that are ancestors of *classes*. 141 | """ 142 | all_classes = {} 143 | 144 | def recurse(cls): 145 | all_classes[cls] = None 146 | for c in cls.__bases__: 147 | if c not in all_classes: 148 | recurse(c) 149 | 150 | for cls in classes: 151 | recurse(cls) 152 | 153 | return all_classes.keys() 154 | 155 | def class_name(self, cls, parts=0): 156 | """ 157 | Given a class object, return a fully-qualified name. This 158 | works for things I've tested in matplotlib so far, but may not 159 | be completely general. 160 | """ 161 | module = cls.__module__ 162 | if module == '__builtin__': 163 | fullname = cls.__name__ 164 | else: 165 | fullname = "%s.%s" % (module, cls.__name__) 166 | if parts == 0: 167 | return fullname 168 | name_parts = fullname.split('.') 169 | return '.'.join(name_parts[-parts:]) 170 | 171 | def get_all_class_names(self): 172 | """ 173 | Get all of the class names involved in the graph. 174 | """ 175 | return [self.class_name(x) for x in self.all_classes] 176 | 177 | # These are the default options for graphviz 178 | default_graph_options = { 179 | "rankdir": "LR", 180 | "size": '"8.0, 12.0"' 181 | } 182 | default_node_options = { 183 | "shape": "box", 184 | "fontsize": 10, 185 | "height": 0.25, 186 | "fontname": "Vera Sans, DejaVu Sans, Liberation Sans, Arial, Helvetica, sans", 187 | "style": '"setlinewidth(0.5)"' 188 | } 189 | default_edge_options = { 190 | "arrowsize": 0.5, 191 | "style": '"setlinewidth(0.5)"' 192 | } 193 | 194 | def _format_node_options(self, options): 195 | return ','.join(["%s=%s" % x for x in options.items()]) 196 | def _format_graph_options(self, options): 197 | return ''.join(["%s=%s;\n" % x for x in options.items()]) 198 | 199 | def generate_dot(self, fd, name, parts=0, urls={}, 200 | graph_options={}, node_options={}, 201 | edge_options={}): 202 | """ 203 | Generate a graphviz dot graph from the classes that 204 | were passed in to __init__. 205 | 206 | *fd* is a Python file-like object to write to. 207 | 208 | *name* is the name of the graph 209 | 210 | *urls* is a dictionary mapping class names to http urls 211 | 212 | *graph_options*, *node_options*, *edge_options* are 213 | dictionaries containing key/value pairs to pass on as graphviz 214 | properties. 215 | """ 216 | g_options = self.default_graph_options.copy() 217 | g_options.update(graph_options) 218 | n_options = self.default_node_options.copy() 219 | n_options.update(node_options) 220 | e_options = self.default_edge_options.copy() 221 | e_options.update(edge_options) 222 | 223 | fd.write('digraph %s {\n' % name) 224 | fd.write(self._format_graph_options(g_options)) 225 | 226 | for cls in self.all_classes: 227 | if not self.show_builtins and cls in __builtins__.values(): 228 | continue 229 | 230 | name = self.class_name(cls, parts) 231 | 232 | # Write the node 233 | this_node_options = n_options.copy() 234 | url = urls.get(self.class_name(cls)) 235 | if url is not None: 236 | this_node_options['URL'] = '"%s"' % url 237 | fd.write(' "%s" [%s];\n' % 238 | (name, self._format_node_options(this_node_options))) 239 | 240 | # Write the edges 241 | for base in cls.__bases__: 242 | if not self.show_builtins and base in __builtins__.values(): 243 | continue 244 | 245 | base_name = self.class_name(base, parts) 246 | fd.write(' "%s" -> "%s" [%s];\n' % 247 | (base_name, name, 248 | self._format_node_options(e_options))) 249 | fd.write('}\n') 250 | 251 | def run_dot(self, args, name, parts=0, urls={}, 252 | graph_options={}, node_options={}, edge_options={}): 253 | """ 254 | Run graphviz 'dot' over this graph, returning whatever 'dot' 255 | writes to stdout. 256 | 257 | *args* will be passed along as commandline arguments. 258 | 259 | *name* is the name of the graph 260 | 261 | *urls* is a dictionary mapping class names to http urls 262 | 263 | Raises DotException for any of the many os and 264 | installation-related errors that may occur. 265 | """ 266 | try: 267 | dot = subprocess.Popen(['dot'] + list(args), 268 | stdin=subprocess.PIPE, stdout=subprocess.PIPE, 269 | close_fds=True) 270 | except OSError: 271 | raise DotException("Could not execute 'dot'. Are you sure you have 'graphviz' installed?") 272 | except ValueError: 273 | raise DotException("'dot' called with invalid arguments") 274 | except: 275 | raise DotException("Unexpected error calling 'dot'") 276 | 277 | self.generate_dot(dot.stdin, name, parts, urls, graph_options, 278 | node_options, edge_options) 279 | dot.stdin.close() 280 | result = dot.stdout.read() 281 | returncode = dot.wait() 282 | if returncode != 0: 283 | raise DotException("'dot' returned the errorcode %d" % returncode) 284 | return result 285 | 286 | class inheritance_diagram(Body, Element): 287 | """ 288 | A docutils node to use as a placeholder for the inheritance 289 | diagram. 290 | """ 291 | pass 292 | 293 | def inheritance_diagram_directive(name, arguments, options, content, lineno, 294 | content_offset, block_text, state, 295 | state_machine): 296 | """ 297 | Run when the inheritance_diagram directive is first encountered. 298 | """ 299 | node = inheritance_diagram() 300 | 301 | class_names = arguments 302 | 303 | # Create a graph starting with the list of classes 304 | graph = InheritanceGraph(class_names) 305 | 306 | # Create xref nodes for each target of the graph's image map and 307 | # add them to the doc tree so that Sphinx can resolve the 308 | # references to real URLs later. These nodes will eventually be 309 | # removed from the doctree after we're done with them. 310 | for name in graph.get_all_class_names(): 311 | refnodes, x = xfileref_role( 312 | 'class', ':class:`%s`' % name, name, 0, state) 313 | node.extend(refnodes) 314 | # Store the graph object so we can use it to generate the 315 | # dot file later 316 | node['graph'] = graph 317 | # Store the original content for use as a hash 318 | node['parts'] = options.get('parts', 0) 319 | node['content'] = " ".join(class_names) 320 | return [node] 321 | 322 | def get_graph_hash(node): 323 | return md5(node['content'] + str(node['parts'])).hexdigest()[-10:] 324 | 325 | def html_output_graph(self, node): 326 | """ 327 | Output the graph for HTML. This will insert a PNG with clickable 328 | image map. 329 | """ 330 | graph = node['graph'] 331 | parts = node['parts'] 332 | 333 | graph_hash = get_graph_hash(node) 334 | name = "inheritance%s" % graph_hash 335 | path = '_images' 336 | dest_path = os.path.join(setup.app.builder.outdir, path) 337 | if not os.path.exists(dest_path): 338 | os.makedirs(dest_path) 339 | png_path = os.path.join(dest_path, name + ".png") 340 | path = setup.app.builder.imgpath 341 | 342 | # Create a mapping from fully-qualified class names to URLs. 343 | urls = {} 344 | for child in node: 345 | if child.get('refuri') is not None: 346 | urls[child['reftitle']] = child.get('refuri') 347 | elif child.get('refid') is not None: 348 | urls[child['reftitle']] = '#' + child.get('refid') 349 | 350 | # These arguments to dot will save a PNG file to disk and write 351 | # an HTML image map to stdout. 352 | image_map = graph.run_dot(['-Tpng', '-o%s' % png_path, '-Tcmapx'], 353 | name, parts, urls) 354 | return ('%s' % 355 | (path, name, name, image_map)) 356 | 357 | def latex_output_graph(self, node): 358 | """ 359 | Output the graph for LaTeX. This will insert a PDF. 360 | """ 361 | graph = node['graph'] 362 | parts = node['parts'] 363 | 364 | graph_hash = get_graph_hash(node) 365 | name = "inheritance%s" % graph_hash 366 | dest_path = os.path.abspath(os.path.join(setup.app.builder.outdir, '_images')) 367 | if not os.path.exists(dest_path): 368 | os.makedirs(dest_path) 369 | pdf_path = os.path.abspath(os.path.join(dest_path, name + ".pdf")) 370 | 371 | graph.run_dot(['-Tpdf', '-o%s' % pdf_path], 372 | name, parts, graph_options={'size': '"6.0,6.0"'}) 373 | return '\n\\includegraphics{%s}\n\n' % pdf_path 374 | 375 | def visit_inheritance_diagram(inner_func): 376 | """ 377 | This is just a wrapper around html/latex_output_graph to make it 378 | easier to handle errors and insert warnings. 379 | """ 380 | def visitor(self, node): 381 | try: 382 | content = inner_func(self, node) 383 | except DotException, e: 384 | # Insert the exception as a warning in the document 385 | warning = self.document.reporter.warning(str(e), line=node.line) 386 | warning.parent = node 387 | node.children = [warning] 388 | else: 389 | source = self.document.attributes['source'] 390 | self.body.append(content) 391 | node.children = [] 392 | return visitor 393 | 394 | def do_nothing(self, node): 395 | pass 396 | 397 | def setup(app): 398 | setup.app = app 399 | setup.confdir = app.confdir 400 | 401 | app.add_node( 402 | inheritance_diagram, 403 | latex=(visit_inheritance_diagram(latex_output_graph), do_nothing), 404 | html=(visit_inheritance_diagram(html_output_graph), do_nothing)) 405 | app.add_directive( 406 | 'inheritance-diagram', inheritance_diagram_directive, 407 | False, (1, 100, 0), parts = directives.nonnegative_int) 408 | -------------------------------------------------------------------------------- /doc_sphinx/sphinxext/ipython_console_highlighting.py: -------------------------------------------------------------------------------- 1 | """reST directive for syntax-highlighting ipython interactive sessions. 2 | 3 | XXX - See what improvements can be made based on the new (as of Sept 2009) 4 | 'pycon' lexer for the python console. At the very least it will give better 5 | highlighted tracebacks. 6 | """ 7 | 8 | #----------------------------------------------------------------------------- 9 | # Needed modules 10 | 11 | # Standard library 12 | import re 13 | 14 | # Third party 15 | from pygments.lexer import Lexer, do_insertions 16 | from pygments.lexers.agile import (PythonConsoleLexer, PythonLexer, 17 | PythonTracebackLexer) 18 | from pygments.token import Comment, Generic 19 | 20 | from sphinx import highlighting 21 | 22 | #----------------------------------------------------------------------------- 23 | # Global constants 24 | line_re = re.compile('.*?\n') 25 | 26 | #----------------------------------------------------------------------------- 27 | # Code begins - classes and functions 28 | 29 | class IPythonConsoleLexer(Lexer): 30 | """ 31 | For IPython console output or doctests, such as: 32 | 33 | .. sourcecode:: ipython 34 | 35 | In [1]: a = 'foo' 36 | 37 | In [2]: a 38 | Out[2]: 'foo' 39 | 40 | In [3]: print(a) 41 | foo 42 | 43 | In [4]: 1 / 0 44 | 45 | Notes: 46 | 47 | - Tracebacks are not currently supported. 48 | 49 | - It assumes the default IPython prompts, not customized ones. 50 | """ 51 | 52 | name = 'IPython console session' 53 | aliases = ['ipython'] 54 | mimetypes = ['text/x-ipython-console'] 55 | input_prompt = re.compile("(In \[[0-9]+\]: )|( \.\.\.+:)") 56 | output_prompt = re.compile("(Out\[[0-9]+\]: )|( \.\.\.+:)") 57 | continue_prompt = re.compile(" \.\.\.+:") 58 | tb_start = re.compile("\-+") 59 | 60 | def get_tokens_unprocessed(self, text): 61 | pylexer = PythonLexer(**self.options) 62 | tblexer = PythonTracebackLexer(**self.options) 63 | 64 | curcode = '' 65 | insertions = [] 66 | for match in line_re.finditer(text): 67 | line = match.group() 68 | input_prompt = self.input_prompt.match(line) 69 | continue_prompt = self.continue_prompt.match(line.rstrip()) 70 | output_prompt = self.output_prompt.match(line) 71 | if line.startswith("#"): 72 | insertions.append((len(curcode), 73 | [(0, Comment, line)])) 74 | elif input_prompt is not None: 75 | insertions.append((len(curcode), 76 | [(0, Generic.Prompt, input_prompt.group())])) 77 | curcode += line[input_prompt.end():] 78 | elif continue_prompt is not None: 79 | insertions.append((len(curcode), 80 | [(0, Generic.Prompt, continue_prompt.group())])) 81 | curcode += line[continue_prompt.end():] 82 | elif output_prompt is not None: 83 | # Use the 'error' token for output. We should probably make 84 | # our own token, but error is typicaly in a bright color like 85 | # red, so it works fine for our output prompts. 86 | insertions.append((len(curcode), 87 | [(0, Generic.Error, output_prompt.group())])) 88 | curcode += line[output_prompt.end():] 89 | else: 90 | if curcode: 91 | for item in do_insertions(insertions, 92 | pylexer.get_tokens_unprocessed(curcode)): 93 | yield item 94 | curcode = '' 95 | insertions = [] 96 | yield match.start(), Generic.Output, line 97 | if curcode: 98 | for item in do_insertions(insertions, 99 | pylexer.get_tokens_unprocessed(curcode)): 100 | yield item 101 | 102 | 103 | def setup(app): 104 | """Setup as a sphinx extension.""" 105 | 106 | # This is only a lexer, so adding it below to pygments appears sufficient. 107 | # But if somebody knows that the right API usage should be to do that via 108 | # sphinx, by all means fix it here. At least having this setup.py 109 | # suppresses the sphinx warning we'd get without it. 110 | pass 111 | 112 | #----------------------------------------------------------------------------- 113 | # Register the extension as a valid pygments lexer 114 | highlighting.lexers['ipython'] = IPythonConsoleLexer() 115 | -------------------------------------------------------------------------------- /doc_sphinx/sphinxext/numpydoc.py: -------------------------------------------------------------------------------- 1 | """ 2 | ======== 3 | numpydoc 4 | ======== 5 | 6 | Sphinx extension that handles docstrings in the Numpy standard format. [1] 7 | 8 | It will: 9 | 10 | - Convert Parameters etc. sections to field lists. 11 | - Convert See Also section to a See also entry. 12 | - Renumber references. 13 | - Extract the signature from the docstring, if it can't be determined otherwise. 14 | 15 | .. [1] http://projects.scipy.org/scipy/numpy/wiki/CodingStyleGuidelines#docstring-standard 16 | 17 | """ 18 | 19 | import os, re, pydoc 20 | from docscrape_sphinx import get_doc_object, SphinxDocString 21 | import inspect 22 | 23 | def mangle_docstrings(app, what, name, obj, options, lines, 24 | reference_offset=[0]): 25 | if what == 'module': 26 | # Strip top title 27 | title_re = re.compile(r'^\s*[#*=]{4,}\n[a-z0-9 -]+\n[#*=]{4,}\s*', 28 | re.I|re.S) 29 | lines[:] = title_re.sub('', "\n".join(lines)).split("\n") 30 | else: 31 | doc = get_doc_object(obj, what, "\n".join(lines)) 32 | lines[:] = str(doc).split("\n") 33 | 34 | if app.config.numpydoc_edit_link and hasattr(obj, '__name__') and \ 35 | obj.__name__: 36 | if hasattr(obj, '__module__'): 37 | v = dict(full_name="%s.%s" % (obj.__module__, obj.__name__)) 38 | else: 39 | v = dict(full_name=obj.__name__) 40 | lines += ['', '.. htmlonly::', ''] 41 | lines += [' %s' % x for x in 42 | (app.config.numpydoc_edit_link % v).split("\n")] 43 | 44 | # replace reference numbers so that there are no duplicates 45 | references = [] 46 | for l in lines: 47 | l = l.strip() 48 | if l.startswith('.. ['): 49 | try: 50 | references.append(int(l[len('.. ['):l.index(']')])) 51 | except ValueError: 52 | print("WARNING: invalid reference in %s docstring" % name) 53 | 54 | # Start renaming from the biggest number, otherwise we may 55 | # overwrite references. 56 | references.sort() 57 | if references: 58 | for i, line in enumerate(lines): 59 | for r in references: 60 | new_r = reference_offset[0] + r 61 | lines[i] = lines[i].replace('[%d]_' % r, 62 | '[%d]_' % new_r) 63 | lines[i] = lines[i].replace('.. [%d]' % r, 64 | '.. [%d]' % new_r) 65 | 66 | reference_offset[0] += len(references) 67 | 68 | def mangle_signature(app, what, name, obj, options, sig, retann): 69 | # Do not try to inspect classes that don't define `__init__` 70 | if (inspect.isclass(obj) and 71 | 'initializes x; see ' in pydoc.getdoc(obj.__init__)): 72 | return '', '' 73 | 74 | if not (callable(obj) or hasattr(obj, '__argspec_is_invalid_')): return 75 | if not hasattr(obj, '__doc__'): return 76 | 77 | doc = SphinxDocString(pydoc.getdoc(obj)) 78 | if doc['Signature']: 79 | sig = re.sub("^[^(]*", "", doc['Signature']) 80 | return sig, '' 81 | 82 | def initialize(app): 83 | try: 84 | app.connect('autodoc-process-signature', mangle_signature) 85 | except: 86 | monkeypatch_sphinx_ext_autodoc() 87 | 88 | def setup(app, get_doc_object_=get_doc_object): 89 | global get_doc_object 90 | get_doc_object = get_doc_object_ 91 | 92 | app.connect('autodoc-process-docstring', mangle_docstrings) 93 | app.connect('builder-inited', initialize) 94 | app.add_config_value('numpydoc_edit_link', None, True) 95 | 96 | #------------------------------------------------------------------------------ 97 | # Monkeypatch sphinx.ext.autodoc to accept argspecless autodocs (Sphinx < 0.5) 98 | #------------------------------------------------------------------------------ 99 | 100 | def monkeypatch_sphinx_ext_autodoc(): 101 | global _original_format_signature 102 | import sphinx.ext.autodoc 103 | 104 | if sphinx.ext.autodoc.format_signature is our_format_signature: 105 | return 106 | 107 | print("[numpydoc] Monkeypatching sphinx.ext.autodoc ...") 108 | _original_format_signature = sphinx.ext.autodoc.format_signature 109 | sphinx.ext.autodoc.format_signature = our_format_signature 110 | 111 | def our_format_signature(what, obj): 112 | r = mangle_signature(None, what, None, obj, None, None, None) 113 | if r is not None: 114 | return r[0] 115 | else: 116 | return _original_format_signature(what, obj) 117 | -------------------------------------------------------------------------------- /matrix2latex/IOString.py: -------------------------------------------------------------------------------- 1 | # I needed a string object that had the write function 2 | 3 | class IOString: # todo subclass str? 4 | # For a file like object, writes to the file while keeping 5 | # a local buffer. 6 | def __init__(self, fileObject=None): 7 | self.f = fileObject 8 | self.s = "" 9 | 10 | def write(self, s): 11 | try: 12 | self.f.write(s) 13 | except AttributeError: 14 | pass 15 | self.s += s 16 | 17 | def __str__(self): 18 | return self.s 19 | 20 | def close(self): 21 | try: 22 | self.f.close() 23 | except AttributeError: 24 | pass 25 | -------------------------------------------------------------------------------- /matrix2latex/__init__.py: -------------------------------------------------------------------------------- 1 | """This file is part of matrix2latex. 2 | 3 | matrix2latex is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 3 of the License, or 6 | (at your option) any later version. 7 | 8 | matrix2latex is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | 13 | You should have received a copy of the GNU General Public License 14 | along with matrix2latex. If not, see . 15 | """ 16 | 17 | __all__ = ['matrix2latex'] 18 | 19 | try: 20 | from matrix2latex import matrix2latex 21 | except ImportError: 22 | # Really ugly hack to please python3 import mechanisms 23 | import sys, os 24 | SCRIPT_DIR = os.path.dirname(os.path.realpath(os.path.expanduser(__file__))) 25 | 26 | sys.path.insert(0, SCRIPT_DIR) 27 | from matrix2latex import matrix2latex 28 | matrix2latex = matrix2latex.matrix2latex 29 | del sys.path[0] # NOTE: ensure that matrix2latex does not change sys.path 30 | -------------------------------------------------------------------------------- /matrix2latex/error.py: -------------------------------------------------------------------------------- 1 | # Error handling for matrix2latex.py, 2 | # todo: don't yell at errors, fix them! 3 | # To clean the code, error handling is moved to small functions 4 | def assertStr(value, key): 5 | pass 6 | # assert isinstance(value, str), \ 7 | # "expected %s to be a str, got %s" % (key, type(value)) 8 | 9 | def assertKeyFormat(value): 10 | assertStr(value, "format") 11 | assert r"%" in value, \ 12 | "expected a format str, got %s" % value 13 | assert value.count("%") == 1,\ 14 | "expected a single format, got %s" % value 15 | 16 | def assertKeyAlignment(value, n): 17 | return n 18 | assertStr(value, "alignment") 19 | assert ("c" in value or "l" in value or "r" in value), \ 20 | "expected legal alignment c, l or r, got %s" % value 21 | counter = dict() 22 | counter['c'] = 0 23 | counter['l'] = 0 24 | counter['r'] = 0 25 | for v in value: 26 | if v in counter: 27 | counter[v] += 1 28 | else: 29 | counter[v] = 1 30 | length = counter['c'] + counter['l'] + counter['r'] 31 | return length 32 | # assert length == n,\ 33 | # "Error: %g of %g alignments given '%s'\n" % (length, n, value) 34 | 35 | def assertListString(value, key): 36 | pass 37 | # assert isinstance(value, list),\ 38 | # "Expected %s to be a list, got %s" % (key, type(value)) 39 | # for e in value: 40 | # assertStr(e, "%s element" % key) 41 | -------------------------------------------------------------------------------- /matrix2latex/fixEngineeringNotation.py: -------------------------------------------------------------------------------- 1 | """This file is part of matrix2latex. 2 | 3 | matrix2latex is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 3 of the License, or 6 | (at your option) any later version. 7 | 8 | matrix2latex is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | 13 | You should have received a copy of the GNU General Public License 14 | along with matrix2latex. If not, see . 15 | """ 16 | 17 | import re 18 | 19 | def fix(s, table=False): 20 | """ 21 | input: (string) s 22 | output: (string) s 23 | takes any number in s and replaces the format 24 | '8e-08' with '8\e{-08}' 25 | """ 26 | i = re.search('e[-+]\d\d', s) 27 | while i != None: 28 | before = s[0:i.start()] 29 | number = s[i.start()+1:i.start()+4] 30 | after = s[i.end():] 31 | if table: 32 | num = "%(#)+03d" % {'#': int(number)} 33 | else: 34 | num = "%(#)3d" % {'#': int(number)} 35 | 36 | s = '%s\\e{%s}%s' % (before, num, after) 37 | i = re.search('e[-+]\d\d', s) 38 | return s 39 | -------------------------------------------------------------------------------- /matrix2latex/matrix2latex.py: -------------------------------------------------------------------------------- 1 | """This file is part of matrix2latex. 2 | 3 | matrix2latex is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 3 of the License, or 6 | (at your option) any later version. 7 | 8 | matrix2latex is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | 13 | You should have received a copy of the GNU General Public License 14 | along with matrix2latex. If not, see . 15 | """ 16 | import sys 17 | import os.path 18 | import warnings 19 | import math 20 | import re 21 | def isnan(e): 22 | try: 23 | return math.isnan(e) 24 | except (TypeError, AttributeError): 25 | return e == float("nan") 26 | 27 | from fixEngineeringNotation import fix 28 | from error import * # error handling 29 | from IOString import IOString 30 | # Definitions 31 | # Matrix environments where alignment can be utilized. CHECK: Note alignment[0] used! 32 | matrix_alignment = ["pmatrix*","bmatrix*","Bmatrix*","vmatrix*","Vmatrix*"] # Needs mathtools package 33 | # Table environments where alignment can be utilized 34 | table_alignment = ["tabular", "longtable"] 35 | 36 | def matrix2latex(matr, filename=None, *environments, **keywords): 37 | r''' 38 | Takes a python matrix or nested list and converts to a LaTeX table or matrix. 39 | Author: ob@cakebox.net, inspired by the work of koehler@in.tum.de who has written 40 | a `similar package for 41 | matlab `_ 42 | 43 | The following packages and definitions are recommended in the latex preamble 44 | 45 | .. code-block:: latex 46 | 47 | \providecommand{\e}[1]{\ensuremath{\times 10^{#1}}} % scientific notation, 1\e{9} will print as 1x10^9 48 | \usepackage{amsmath} % needed for pmatrix 49 | \usepackage{booktabs} % Fancy tables 50 | ... 51 | \begin{document} 52 | ... 53 | 54 | :param list matr: The numpy matrix/array or a nested list to convert. 55 | 56 | :param str filename: File to place output, extension .tex is added automatically. File can be included in a LaTeX 57 | document by ``\input{filename}``. If filename is None 58 | or not a string it is ignored. 59 | 60 | :arg environments: A list specifing the begin and end block. 61 | Example: ``matrix2latex(m, None, "align*", "pmatrix")`` gives the matrix 62 | 63 | .. code-block:: latex 64 | 65 | \begin{align*} 66 | \begin{pmatrix} 67 | 1 & 2 \\ 68 | 3 & 4 69 | \end{pmatrix} 70 | \end{align*} 71 | 72 | The default is generating a table using the ``table``, ``center`` and ``tabular`` 73 | environment, hence 74 | ``matrix2latex(m, "test", "table", "center", "tabular" ...)`` 75 | can be written as 76 | ``matrix2latex(m, "test", ...)`` 77 | 78 | :key headerRow: 79 | A row at the top used to label the columns. 80 | Must be a list of strings. Can be a nested list for multiple headings. 81 | If two or more items are repeated, a multicolumn is inserted, so: 82 | ``headerRow=['a', 'a']`` 83 | will produces ``\multicolumn{2}{c}{Item}`` with an appropriate cmidrule beneath. 84 | To avoid this behavior ensure each consecutive item is unique, for instance: 85 | ``headerRow=['a', 'a ']`` 86 | will produces the expected ``a & a`` (note the space after the second ``a``). 87 | 88 | :key headerColumn: 89 | A column used to label the rows. 90 | Must be a list of strings 91 | 92 | :key transpose: 93 | Flips the table around in case you messed up. Equivalent to 94 | ``matrix2latex(m.H, ...)`` 95 | if m is a numpy matrix. 96 | 97 | :key caption: 98 | Use to define a caption for your table. 99 | Inserts ``\caption`` after ``\begin{center}``, 100 | note that without the center environment the caption is currently ignored. 101 | 102 | :key label: 103 | Used to insert ``\label{tab:...}`` after ``\end{tabular}`` 104 | Default is filename without extension. 105 | 106 | :key format: 107 | Printf syntax format, e.g. ``$%.2f$``. Default is ``$%g$``. 108 | This format is then used for all the elements in the table. 109 | 110 | :key formatColumn: 111 | A list of printf-syntax formats, e.g. ``[$%.2f$, $%g$]`` 112 | Must be of same length as the number of columns. 113 | Format i is then used for column i. 114 | This is useful if some of your data should be printed with more significant figures 115 | than other parts. 116 | 117 | :key alignment: 118 | Used as an option when tabular is given as enviroment. 119 | ``\begin{tabular}{alignment}`` 120 | A latex alignment like ``c``, ``l`` or ``r``. 121 | Can be given either as one per column e.g. ``"ccc"``. 122 | Or if only a single character is given e.g. ``"c"``, 123 | it will produce the correct amount depending on the number of columns. 124 | Default is ``"r"``. 125 | 126 | :key position: 127 | Used for the table environment to specify the optional parameter "position specifier" 128 | Default is ``'[' + 'htp' + ']'`` 129 | If you want to place your table manually, do not use the table environment. 130 | 131 | Note that many of these options only has an effect when typesetting a table, 132 | if the correct environment is not given the arguments are simply ignored. 133 | 134 | :return str table: 135 | Returns the latex formated output as a string. 136 | ''' 137 | headerRow = None 138 | headerColumn = None 139 | 140 | # 141 | # Convert to list 142 | # 143 | # If pandas 144 | try: 145 | headerColumn = list(matr.index) 146 | except (AttributeError, TypeError): 147 | pass 148 | try: 149 | headerRow = [list(matr.columns)] 150 | except (AttributeError, TypeError): 151 | pass 152 | try: 153 | matr = matr.to_records(index=False) 154 | except AttributeError: 155 | pass 156 | # If numpy (vops: must be placed below pandas check) 157 | try: 158 | matr = matr.tolist() 159 | except AttributeError: 160 | pass # lets hope it looks like a list 161 | 162 | # 163 | # Define matrix-size 164 | # 165 | m = len(matr) 166 | try: 167 | n = len(matr[0]) # may raise TypeError 168 | for row in matr: 169 | n = max(n, len(row)) # keep max length 170 | except TypeError: # no length in this dimension (vector...) 171 | # convert [1, 2] to [[1], [2]] 172 | newMatr = list() 173 | [newMatr.append([matr[ix]]) for ix in range(m)] 174 | matr = newMatr 175 | m = len(matr) 176 | n = len(matr[0]) 177 | except IndexError: 178 | m = 0 179 | n = 0 180 | #assert m > 0 and n > 0, "Expected positive matrix dimensions, got %g by %g matrix" % (m, n) 181 | # Bug with transpose: 182 | # # If header and/or column labels are longer use those lengths 183 | # try: 184 | # m = max(m, len(keywords['headerColumn'])) # keep max length 185 | # except KeyError: 186 | # pass 187 | # try: 188 | # n = max(n, len(keywords['headerRow'])) # keep max length 189 | # except KeyError: 190 | # pass 191 | 192 | # 193 | # Default values 194 | # 195 | 196 | # Keywords 197 | formatNumber = "$%g$" 198 | formatColumn = None 199 | if n != 0: 200 | alignment = "c"*n # cccc 201 | else: 202 | alignment = "c" 203 | 204 | caption = None 205 | label = None 206 | position = "htp" # position specifier for floating table environment 207 | 208 | # 209 | # Conflicts 210 | # 211 | if "format" in keywords and "formatColumn" in keywords: 212 | warnings.warn('Specifying both format and formatColumn is not supported, using formatColumn') 213 | del keywords["format"] 214 | 215 | # 216 | # User-defined values 217 | # 218 | for key in keywords: 219 | value = keywords[key] 220 | if key == "format": 221 | assertKeyFormat(value) 222 | formatNumber = value 223 | formatColumn = None # never let both formatColumn and formatNumber to be defined 224 | elif key == "formatColumn": 225 | formatColumn = value 226 | formatNumber = None 227 | elif key == "alignment": 228 | if len(value) == 1: 229 | alignment = value*n # rrrr 230 | else: 231 | alignment = value 232 | assertKeyAlignment(alignment, n) 233 | elif key == "headerRow": 234 | if value == None: 235 | headerRow = None 236 | else: 237 | if not(type(value[0]) == list): 238 | value = [value] # just one header 239 | #assertListString(value, "headerRow") # todo: update 240 | headerRow = list(value) 241 | elif key == "headerColumn": 242 | if value == None: 243 | headerColumn = None 244 | else: 245 | assertListString(value, "headerColumn") 246 | headerColumn = list(value) 247 | elif key == "caption": 248 | assertStr(value, "caption") 249 | caption = value 250 | elif key == "label": 251 | assertStr(value, "label") 252 | if value.startswith('tab:'): 253 | label = value[len('tab:'):] # this will be added later in the code, avoids 'tab:tab:' as label 254 | else: 255 | label = value 256 | elif key == "filename": 257 | assertStr(value, "filename") 258 | filename = value 259 | elif key == "position": 260 | assertStr(value, "position") 261 | position = value 262 | elif key == "environments": 263 | environments = value 264 | elif key == "transpose": 265 | newMatr = list(zip(*matr)) 266 | # for j in range(0, n): 267 | # row = list() 268 | # for i in range(0, m): 269 | # row.append(matr[i][j]) 270 | # newMatr.append(row) 271 | copyKeywords = dict(keywords) # can't del original since we are inside for loop. 272 | del copyKeywords['transpose'] 273 | # Recursion! 274 | return matrix2latex(newMatr, filename, *environments, **copyKeywords) 275 | else: 276 | raise ValueError("Error: key not recognized '%s'" % key) 277 | 278 | if headerColumn != None: 279 | alignment = "r" + alignment 280 | 281 | # Environments 282 | if environments is None: # environments=None passed, do not add any environments. 283 | environments = [] 284 | elif len(environments) == 0: # no environment give, assume table 285 | environments = ("table", "center", "tabular") 286 | 287 | if formatColumn == None: 288 | formatColumn = list() 289 | for j in range(0, n): 290 | formatColumn.append(formatNumber) 291 | 292 | if headerColumn != None and headerRow != None and len(headerRow[0]) == n: 293 | for i in range(len(headerRow)): 294 | headerRow[i].insert(0, "") 295 | 296 | # 297 | # Set outputFile 298 | # 299 | f = None 300 | if isinstance(filename, str) and filename != '': 301 | if not filename.endswith('.tex'): # assure propper file extension 302 | filename += '.tex' 303 | f = open(filename, 'w') 304 | if label == None: 305 | label = os.path.basename(filename) # get basename 306 | label = label[:-len(".tex")] # remove extension 307 | 308 | f = IOString(f) 309 | # 310 | # Begin block 311 | # 312 | for ixEnv in range(0, len(environments)): 313 | f.write("\t"*ixEnv) 314 | f.write(r"\begin{%s}" % environments[ixEnv]) 315 | # special environments: 316 | if environments[ixEnv] == "table": 317 | f.write("[" + position + "]") 318 | elif environments[ixEnv] == "center": 319 | if caption != None: 320 | f.write("\n"+"\t"*ixEnv) 321 | f.write(r"\caption{%s}" % fix(caption)) 322 | if label != None: 323 | f.write("\n"+"\t"*ixEnv) 324 | f.write(r"\label{tab:%s}" % label) 325 | elif environments[ixEnv] in table_alignment: 326 | f.write("{" + alignment + "}\n") 327 | f.write("\t"*ixEnv) 328 | f.write(r"\toprule") 329 | elif environments[ixEnv] in matrix_alignment: 330 | f.write("[" + alignment[0] + "]\n") #These environment you can add 331 | # newline 332 | f.write("\n") 333 | tabs = len(environments) # number of \t to use 334 | 335 | # 336 | # Table block 337 | # 338 | 339 | # Row labels 340 | if headerRow != None: 341 | for row in range(len(headerRow)): # for each header 342 | i = 0 343 | start, end = list(), list() # of cmidrule 344 | f.write("\t"*tabs) 345 | while i < len(headerRow[row]): # for each element (skipping repeating ones) 346 | j = 1 347 | # check for legal index then check if current element is equal to next (repeating) 348 | repeating = i+j < len(headerRow[row]) and headerRow[row][i] == headerRow[row][i + j] 349 | if repeating: 350 | while repeating: # figure out how long it repeats (j) 351 | j += 1 352 | repeating = i+j < len(headerRow[row]) and headerRow[row][i] == headerRow[row][i + j] 353 | f.write(r'\multicolumn{%d}{c}{%s}' % (j, headerRow[row][i])) # multicol heading 354 | start.append(i);end.append(j+i) 355 | i += j # skip ahed 356 | else: 357 | f.write('{%s}' % headerRow[row][i]) # normal heading 358 | i += 1 359 | if i < len(headerRow[row]): # if not last element 360 | f.write(' & ') 361 | 362 | f.write(r'\\') 363 | for s, e in zip(start, end): 364 | f.write(r'\cmidrule(r){%d-%d}' % (s+1, e)) 365 | f.write('\n') 366 | if len(start) == 0: # do not use if cmidrule is used on last header 367 | f.write('\t'*tabs) 368 | f.write('\\midrule\n') 369 | 370 | # Values 371 | for i in range(0, m): 372 | f.write("\t"*tabs) 373 | for j in range(0, n): 374 | 375 | if j == 0: # first row 376 | if headerColumn != None: 377 | try: 378 | f.write("{%s} & " % headerColumn[i]) 379 | except IndexError: 380 | f.write('&') 381 | 382 | try: # get current element 383 | if '%s' not in formatColumn[j]: 384 | try: 385 | e = float(matr[i][j]) # current element 386 | except ValueError: # can't convert to float, use string 387 | formatColumn[j] = '%s' 388 | e = matr[i][j] 389 | except TypeError: # raised for None 390 | e = None 391 | else: 392 | e = matr[i][j] 393 | except IndexError: 394 | e = None 395 | 396 | if e == None or isnan(e):#e == float('NaN'): 397 | f.write("{-}") 398 | elif e == float('inf'): 399 | f.write(r"$\infty$") 400 | elif e == float('-inf'): 401 | f.write(r"$-\infty$") 402 | else: 403 | fcj = formatColumn[j] 404 | 405 | formated = fcj % e 406 | formated = fix(formated, table=True) # fix 1e+2 407 | f.write('%s' % formated) 408 | if j != n-1: # not last row 409 | f.write(" & ") 410 | else: # last row 411 | f.write(r"\\") 412 | f.write("\n") 413 | 414 | # 415 | # End block 416 | # 417 | for ixEnv in range(0, len(environments)): 418 | ixEnv = len(environments)-1 - ixEnv # reverse order 419 | # special environments: 420 | if environments[ixEnv] == "center": 421 | pass 422 | elif environments[ixEnv] == "tabular": 423 | f.write("\t"*ixEnv) 424 | f.write(r"\bottomrule"+"\n") 425 | f.write("\t"*ixEnv) 426 | f.write(r"\end{%s}" % environments[ixEnv]) 427 | if ixEnv != 0: 428 | f.write("\n") 429 | 430 | f.close() 431 | return f.__str__() 432 | 433 | if __name__ == '__main__': 434 | # m = matrix('1 2 4;3 4 6') 435 | # m = matrix('1 2 4;2 2 1;2 1 2') 436 | m = [[1, 2, 3], [3, 4, 5]] 437 | print(matrix2latex(m)) 438 | print(matrix2latex(m, 'tmp.tex')) 439 | print(matrix2latex(m, None, "table", "center", "tabular", format="$%.2f$", alignment='lcr')) 440 | cl = ["a", "b", "c"] 441 | rl = ['d', 'e', 'f', 'g'] 442 | print(matrix2latex(m, None, format="$%.2g$", alignment='lcr', 443 | headerColumn=cl,caption="test", label="2", headerRow=rl)) 444 | print(matrix2latex(m, None, "align*", "pmatrix", format="%g", alignment='c')) 445 | print(matrix2latex(m, None, headerColumn=cl, caption="Hello", label="la")) 446 | print(matrix2latex([['a', 'b', '1'], ['1', '2', '3']], format='%s')) 447 | 448 | m = [[1,None,None], [2,2,1], [2,1,2]] 449 | print(matrix2latex(m, transpose=True)) 450 | 451 | # TODO: 452 | # m = [[1], [2,2,1], [2,1,2]] 453 | # print(matrix2latex(m, transpose=True)) 454 | -------------------------------------------------------------------------------- /matrix2latex/pagination.py: -------------------------------------------------------------------------------- 1 | """This file is part of matrix2latex. 2 | matrix2latex is free software: you can redistribute it and/or modify 3 | it under the terms of the GNU General Public License as published by 4 | the Free Software Foundation, either version 3 of the License, or 5 | (at your option) any later version. 6 | matrix2latex is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | GNU General Public License for more details. 10 | You should have received a copy of the GNU General Public License 11 | along with matrix2latex. If not, see . 12 | """ 13 | 14 | import os 15 | from matrix2latex import matrix2latex 16 | from subprocess import call 17 | 18 | def simple(matrix, headerRow=None, headerColumn=None, Filename=None, font_size=None, clean_latex=True): 19 | """A simple pagination function, that creates a minimal LaTeX document code for an input matrix, 20 | compiles it, and removes the LaTeX traces. 21 | 22 | Arguments: 23 | 24 | matrix 25 | A numpy matrix or a nested list 26 | 27 | Filename 28 | File to place output, extension .tex is added automatically. File can be included in a LaTeX 29 | document by \input{filename}. Output will always be returned in a string. If filename is None 30 | or not a string it is ignored. 31 | 32 | headerRow 33 | A row at the top used to label the columns. 34 | Must be a list of strings. Can be a nested list for multiple headings. 35 | If two or more items are repeated, a multicolumn is inserted, so: 36 | headerRow=['a', 'a'] 37 | will produces "\multicolumn{2}{c}{Item}" with an appropriate cmidrule beneath. 38 | To avoid this behavior ensure each consecutive item is unique, for instance: 39 | headerRow=['a', 'a '] 40 | will produces the expected "a & a". 41 | 42 | headerColumn 43 | A column used to label the rows. 44 | Must be a list of strings 45 | 46 | font_size 47 | Specify the global (document and table) font size. 48 | Accepted values are integers from 1 to 10 - these are mapped on the available LaTeX font sizes 49 | https://en.wikibooks.org/wiki/LaTeX/Fonts 50 | 51 | clean_latex 52 | Used to optionally turn off the delete phase for LaTeX traces 53 | Must be bool 54 | """ 55 | 56 | latex_font_sizes = { 57 | 1: "\\tiny", 58 | 2: "\\scriptsize", 59 | 3: "\\footnotesize", 60 | 4: "\\small", 61 | 5: "\\normalsize", 62 | 6: "\\large", 63 | 7: "\\Large", 64 | 8: "\\LARGE", 65 | 9: "\\huge", 66 | 10: "\\Huge" 67 | } 68 | 69 | if not Filename: 70 | Filename = "_temp" 71 | 72 | table = matrix2latex(matrix, headerRow=headerRow, headerColumn=headerColumn, environments=['tabular']) 73 | 74 | #determine document font size 75 | if font_size: 76 | document_fontsize = latex_font_sizes[font_size]+"\n" 77 | else: 78 | document_fontsize = "" 79 | 80 | #add header elements (with the prepend operator "+"y in reverse order) 81 | tex = "\\sbox\mt{%\n" + table 82 | tex = document_fontsize + tex 83 | tex = "\\begin{document}\n" + tex 84 | tex = "\\pagenumbering{gobble}\n" + tex 85 | tex = "\\newsavebox\mt\n" + tex 86 | tex = "\\usepackage{booktabs}\n" + tex 87 | tex = "\\usepackage{geometry}\n\\geometry{a4paper,total={210mm,297mm},left=15mm,right=15mm,top=15mm,bottom=15mm}\n" + tex 88 | tex = "\\documentclass{article}\n" + tex 89 | 90 | #add footer elements 91 | tex = tex + "%\n}\n" 92 | tex = tex + \ 93 | "\\makeatletter\n" + \ 94 | "\\ifdim\\wd\\mt>\\textwidth\n" + \ 95 | "\\setlength\\@tempdima {\\paperheight}%\n" + \ 96 | "\\setlength\\paperheight {\\paperwidth}%\n" + \ 97 | "\\setlength\\paperwidth {\\@tempdima}%\n" + \ 98 | "\\setlength\\pdfpageheight{\\paperheight}%\n" + \ 99 | "\\setlength\\pdfpagewidth{\\paperwidth}%\n" + \ 100 | "\\setlength{\\textwidth}{\\paperwidth}%\n" + \ 101 | "\\addtolength{\\textwidth}{-3cm}%\n" + \ 102 | "\\setlength{\\hsize}{\\textwidth}%\n" + \ 103 | "\\fi\n" + \ 104 | "\\makeatother\n" + \ 105 | "\\begin{table}[htp]\\setlength{\\hsize}{\\textwidth}%\n" + \ 106 | "\\centering\n" + \ 107 | "\\usebox\\mt\n" + \ 108 | "\\end{table}\n" + \ 109 | "\\end{document}\n" 110 | 111 | file_ = open(Filename+".tex", 'w') 112 | file_.write(tex) 113 | file_.close() 114 | call(["pdflatex", Filename+".tex"]) 115 | 116 | if clean_latex: 117 | all_files = os.listdir(".") 118 | latex_files = [one_file for one_file in all_files if Filename in one_file] 119 | non_pdf_latex_files = [latex_file for latex_file in latex_files if ".pdf" not in latex_file] 120 | for non_pdf_latex_file in non_pdf_latex_files: 121 | os.remove(non_pdf_latex_file) 122 | -------------------------------------------------------------------------------- /matrix2latex/render.py: -------------------------------------------------------------------------------- 1 | """This file is part of matrix2latex. 2 | 3 | matrix2latex is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 3 of the License, or 6 | (at your option) any later version. 7 | 8 | matrix2latex is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | 13 | You should have received a copy of the GNU General Public License 14 | along with matrix2latex. If not, see . 15 | """ 16 | import os 17 | import shutil 18 | import warnings 19 | import tempfile 20 | import subprocess 21 | from matrix2latex import matrix2latex 22 | 23 | _latex_documentclass = r'\documentclass[varwidth=true, border=2pt, convert=true]{standalone}' 24 | _latex_preamble = r"""\providecommand{\e}[1]{\ensuremath{\times 10^{#1}}} 25 | \usepackage{amsmath} 26 | \usepackage{booktabs} 27 | """ 28 | _latex_template = r"""%s 29 | %s 30 | \begin{document} 31 | \pagenumbering{gobble} 32 | %s 33 | \end{document} 34 | """ 35 | def matrix2image(matr, filename=None, *args, **kwargs): 36 | r''' 37 | A wrapper around ``matrix2latex(*args, **kwargs)`` that creates a minimal LaTeX document code, 38 | compiles it, an removes the LaTeX traces. 39 | 40 | A number of options are available to support non-standard latex compilers (``pdflatex``, ``tex_options`` and ``output_format``), 41 | temporary file handling (``clean_latex`` and ``working_dir``) and 42 | customizing the latex template code (``latex_preamble``, ``latex_documentclass`` and ``latex_template``), 43 | These all have (hopefully) sensible default values, if you find something particularly lacking, open an issue! 44 | 45 | :param list matr: 46 | The numpy matrix/array or a nested list to convert. 47 | :param str filename: 48 | Base-filename for the output image, defaults to 'rendered' 49 | :key clean_latex=True: 50 | Remove traces of the latex compilation. Use clean_latex=False and working_dir='tmp' to debug. 51 | :key working_dir=None: 52 | A temporary directory where the document is compiled, WARNING: will be removed as long as 53 | clean_latex is True, do not specify an existing directory. Defaults to tempfile.mkdtemp(). 54 | :key latex_preamble: 55 | Defaults to ``render._latex_preamble``. To include additional preamble commands, use 56 | 57 | ``latex_preamble = render._latex_preamble + r'\usepackage{my_package}'`` 58 | :key latex_documentclass: 59 | Defaults to ``render._latex_documentclass``, for a4 page, call with ``latex_documentclass='\documentclass[a4paper]{article}'``. 60 | :key latex_template: 61 | The latex wrapper code, defaults to ``render._latex_template``, use at your own risk. 62 | :key tex='pdflatex': 63 | The tex renderer. Assumed to produce a '.pdf', if not, remember to also specify an appropriate output_format. 64 | If empty string or None, the document is not compiled. 65 | :key tex_options=['-interaction=nonstopmode', '-shell-escape']: 66 | Options passed to tex renderer 67 | :key output_format='.pdf': 68 | By default it is assumed ``tex='pdflatex'`` produces a '.pdf' and a '.png', 69 | by default the '.pdf' is used, but you may also want to use ``output_format='.png'`` for the png image. 70 | :\*args: 71 | Additional arguments are passed to matrix2latex 72 | :\**kwargs: 73 | Additional keyword-arguments are passed to matrix2latex 74 | :returns working_dir, latex: 75 | A tuple of the working_dir and the latex document as a string. 76 | The working_dir is None if the directory has been succesfully cleaned/removed. 77 | 78 | :raises IOError: if the expected output file was not created. 79 | :raises IOError: if removing files/directories in working_dir fails, this _will_ happend if working_dir suddenly contains folders. 80 | 81 | :raises subprocess.CalledProcessError: if the call to tex indicates a failure. 82 | 83 | :raises UserWarning: if working_dir is an existing directory and clean_latex=True. 84 | ''' 85 | 86 | # Options 87 | if filename is None: 88 | filename = 'rendered' 89 | if filename.endswith(('.pdf', '.tex', '.png')): 90 | filename = filename[:-4] 91 | 92 | clean_latex = kwargs.pop('clean_latex', True) 93 | working_dir = kwargs.pop('working_dir', None) 94 | # in the case of working_dir=existing directory and clean_latex=True, keep a list of files that should not be cleaned 95 | existing_files = [] 96 | if working_dir is None: 97 | working_dir = tempfile.mkdtemp(prefix='matrix2image') 98 | elif not os.path.exists(working_dir): 99 | os.makedirs(working_dir) 100 | else: 101 | if clean_latex: 102 | warnings.warn('The working directory already exists and clean_latex is True, I will try not to delete any of the files currently in working_dir=%s, but I make no promises.' % working_dir) 103 | # Note: there is a razy condition here, any files generated after this point will be deleted, 104 | # but at least we are not deleting family photos... 105 | # An alternative would be to clean only files known to be generated by pdflatex, 106 | # but odd tex compilers and packages can create odd files. 107 | existing_files = os.listdir(working_dir) 108 | 109 | latex_template = kwargs.pop('latex_template', _latex_template) 110 | latex_preamble = kwargs.pop('latex_preamble', _latex_preamble) 111 | latex_documentclass = kwargs.pop('latex_documentclass', _latex_documentclass) 112 | tex = kwargs.pop('tex', 'pdflatex') 113 | tex_options = kwargs.pop('tex_options', ['-interaction=nonstopmode', '-shell-escape']) 114 | output_format = kwargs.pop('output_format', '.pdf') 115 | 116 | # filenames 117 | tex_filename = os.path.basename(filename) + '.tex' 118 | tex_filename_full = os.path.join(working_dir, tex_filename) 119 | output_filename_final = filename + output_format 120 | output_filename_tmp = os.path.join(working_dir, os.path.basename(filename) + output_format) 121 | 122 | # call, do not write to file but get the latex-table as a string 123 | table = matrix2latex(matr, None, *args, **kwargs) 124 | 125 | # latex document 126 | latex = latex_template % (latex_documentclass, latex_preamble, table) 127 | with open(tex_filename_full, 'w') as f: 128 | f.write(latex) 129 | 130 | # compile document 131 | if tex is not None and tex != '': # I am sure there is a sexy way to write this test 132 | cmd = [tex] 133 | cmd.extend(tex_options) 134 | cmd.append(tex_filename) 135 | subprocess.check_call(cmd, cwd=working_dir) 136 | 137 | # we should now have a output_filename in the working directory 138 | if not(os.path.exists(output_filename_tmp)): 139 | raise IOError('Expected %s to exist after calling %s' % (output_filename_tmp, cmd)) 140 | 141 | print(output_filename_tmp, output_filename_final) 142 | shutil.copyfile(output_filename_tmp, output_filename_final) 143 | 144 | if clean_latex: 145 | # only remove related files, then check if empty, then remove 146 | for p in os.listdir(working_dir): 147 | if p not in existing_files: 148 | try: 149 | os.remove(os.path.join(working_dir, p)) # raises OSError if p is a directory, which is unexpected. 150 | except OSError as e: 151 | raise OSError('Trouble removing file/directory:"%s", not cleaning. %s' % (p,e)) 152 | 153 | # if empty: 154 | if len(os.listdir(working_dir)) == 0: 155 | shutil.rmtree(working_dir) 156 | working_dir = None # do not return path to non-existing directory 157 | else: 158 | warnings.warn('working_dir not empty, not cleaning %s' % working_dir) 159 | 160 | return working_dir, latex 161 | 162 | if __name__ == '__main__': 163 | # m = [[1, 2, 3], [3, 4, 5]] 164 | # cl = ["a", "b", "c"] 165 | # rl = ['d', 'e', 'f', 'g'] 166 | 167 | # matrix2image(m, 'rendered', 'tabular', format="$%.2g$", alignment='lcr', 168 | # headerColumn=cl, caption="test", label="2", headerRow=rl) 169 | 170 | m = [[1, 1], [2, 4], [3, 9]] 171 | matrix2image(m, 'simpleExample', environments=['table', 'huge', 'center', 'tabular'], caption='hello', 172 | clean_latex=True, working_dir='tmp') 173 | 174 | # matrix2image(m, 'simpleExample_a4', environments=['table', 'huge', 'center', 'tabular'], caption='hello', 175 | # clean_latex=False, working_dir='tmp', latex_documentclass='\documentclass[a4paper]{article}' 176 | # ) 177 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | setup(name='matrix2latex', 4 | version='1.9', 5 | description='Takes a python matrix or nested list and converts to a LaTeX table or matrix.', 6 | long_description=open('README.md').read(), 7 | author='obtitus', 8 | author_email='obtitus@gmail.com', 9 | url='https://code.google.com/p/matrix2latex/', 10 | packages=['matrix2latex'], 11 | ) 12 | -------------------------------------------------------------------------------- /simpleExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheChymera/matrix2latex/cd682ff87972f7182613a653495ca88fd75c33d6/simpleExample.png -------------------------------------------------------------------------------- /src_matlab/fixEngineeringNotation.m: -------------------------------------------------------------------------------- 1 | function str=fixEngineeringNotation(str) 2 | reg = regexp(str, 'e[-+]\d\d'); 3 | while ~isempty(reg) 4 | i = reg(1); 5 | str = sprintf('%s\\e{%+03d}%s', str(1:i-1), str2num(str(i+1:i+3)), str(i+4:end)); 6 | reg = regexp(str, 'e-\d\d'); 7 | end 8 | end -------------------------------------------------------------------------------- /src_matlab/matrix2latex.m: -------------------------------------------------------------------------------- 1 | function table = matrix2latex(matrix, filename, varargin) 2 | %This file is part of matrix2latex. 3 | % 4 | %matrix2latex is free software: you can redistribute it and/or modify 5 | %it under the terms of the GNU General Public License as published by 6 | %the Free Software Foundation, either version 3 of the License, or 7 | %(at your option) any later version. 8 | % 9 | %matrix2latex is distributed in the hope that it will be useful, 10 | %but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | %MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | %GNU General Public License for more details. 13 | % 14 | %You should have received a copy of the GNU General Public License 15 | %along with matrix2latex. If not, see . 16 | % 17 | % A detailed pdf version of this documentation is available as doc.pdf 18 | %Takes a python matrix or nested list and converts to a LaTeX table or matrix. 19 | %Author: ob@cakebox.net, inspired by the work of koehler@in.tum.de who has written 20 | %a similar package for matlab 21 | %\url{http://www.mathworks.com/matlabcentral/fileexchange/4894-matrix2latex} 22 | % 23 | %The following packages and definitions are recommended in the latex preamble 24 | %% scientific notation, 1\e{9} will print as 1x10^9 25 | %\providecommand{\e}[1]{\ensuremath{\times 10^{#1}}} 26 | %\usepackage{amsmath} % needed for pmatrix 27 | %\usepackage{booktabs} % Fancy tables 28 | %... 29 | %\begin{document} 30 | %... 31 | % \input{table} 32 | % ... 33 | %\end{document} 34 | % 35 | %Arguments: 36 | % 37 | %matrix 38 | % A matrix or a cell array 39 | % 40 | %Filename 41 | % File to place output, extension .tex is added automatically. File can be included in a LaTeX 42 | % document by \input{filename}. Output will always be returned in a string. If filename is None, 43 | % empty string or not a string it is ignored. 44 | % 45 | % 46 | %**keywords 47 | %%environments 48 | % Use 49 | %matrix2latex(m, '', 'environmnet' {"align*", "pmatrix"}, ...) for matrix. 50 | % This will give 51 | % \begin{align*} 52 | % \begin{pmatrix} 53 | % 1 & 2 \\ 54 | % 3 & 4 55 | % \end{pmatrix} 56 | % \end{align*} 57 | % Use 58 | %matrix2latex(m, 'test', 'environemnt', {"table", "center", "tabular"} ...) for table. 59 | % Table is default so given no arguments: table, center and tabular will be used. 60 | % The above command is then equivalent to \\ 61 | %matrix2latex(m, 'test', ...) 62 | % 63 | %%headerRow 64 | % A row at the top used to label the columns. 65 | % Must be a list of strings. 66 | % 67 | %%headerColumn 68 | % A column used to label the rows. 69 | % Must be a list of strings 70 | % 71 | %%transpose 72 | %Flips the table around in case you messed up. Equivalent to 73 | %matrix2latex(m', ...) 74 | %if m is a matrix. 75 | % 76 | %caption 77 | % Use to define a caption for your table. 78 | % Inserts \caption after \begin{center}, 79 | % note that without the center environment the caption is currently ignored. 80 | % 81 | %label 82 | %Used to insert \verb!\label{tab:...}! after \verb!\end{tabular}! 83 | %Default is filename without extension. 84 | % 85 | % 86 | %%format 87 | %Printf syntax format, e.g. $%.2f$. Default is $%g$. 88 | % This format is then used for all the elements in the table. 89 | % 90 | %%formatColumn 91 | %A list of printf-syntax formats, e.g. {$%.2f$, $%g$} 92 | %Must be of same length as the number of columns. 93 | %Format i is then used for column i. 94 | %This is useful if some of your data should be printed with more significant figures 95 | %than other parts 96 | % 97 | %%alignment 98 | %Used as an option when tabular is given as enviroment. 99 | %\begin{tabular}{alignment} 100 | %A latex alignment like c, l or r. 101 | %Can be given either as one per column e.g. "ccc". 102 | %Or if only a single character is given e.g. "c", 103 | %it will produce the correct amount depending on the number of columns. 104 | %Default is "r". 105 | % 106 | %Note that many of these options only has an effect when typesetting a table, 107 | %if the correct environment is not given the arguments are simply ignored. 108 | % 109 | if (rem(nargin,2) == 1 || nargin < 2) 110 | error('%s: Incorrect number of arguments', mfilename); 111 | end 112 | 113 | table = ''; 114 | width = size(matrix, 2); 115 | height = size(matrix, 1); 116 | 117 | headerColumn = []; 118 | headerRow = []; 119 | if width ~= 0 120 | alignment = repmat('c', 1, width); 121 | else 122 | alignment = 'c'; 123 | end 124 | 125 | format = '$%g$'; 126 | textsize = []; 127 | caption = []; 128 | label = []; 129 | environment = {'table', 'center', 'tabular'}; 130 | 131 | for j=1:2:(nargin-2) 132 | pname = varargin{j}; 133 | pval = varargin{j+1}; 134 | if strcmpi(pname, 'headerColumn') 135 | headerColumn = pval; 136 | if isnumeric(headerColumn) 137 | headerColumn = cellstr(num2str(headerColumn(:))); 138 | end 139 | alignment = ['r', alignment]; 140 | elseif strcmpi(pname, 'headerRow') 141 | headerRow = pval; 142 | if isnumeric(headerRow) 143 | headerRow = cellstr(num2str(headerRow(:))); 144 | end 145 | elseif strcmpi(pname, 'alignment') 146 | %okAlignment = {'l', 'c', 'r'}; 147 | %if ~isempty(strmatch(pval, okAlignment, 'exact')) 148 | if length(pval) == 1 149 | alignment = repmat(pval, 1, width); 150 | else 151 | alignment = pval; 152 | end 153 | elseif strcmpi(pname, 'format') 154 | format = lower(pval); 155 | elseif strcmpi(pname, 'formatColumns') 156 | if size(pval) ~= size(matrix, 2) 157 | error('%s: Format columns has wrong length %d', mfilename, size(pval)) 158 | else 159 | format = pval; 160 | end 161 | elseif strcmpi(pname, 'size') 162 | okSize = {'tiny', 'scriptsize', 'footnotesize', 'small', 'normalsize', 'large', 'Large', ... 163 | 'LARGE', 'huge', 'Huge'}; 164 | if ~isempty(strmatch(pval, okSize, 'exact')) 165 | textsize = pval; 166 | else 167 | warning('%s: Unknown size %s', mfilename, pval) 168 | end 169 | elseif strcmpi(pname, 'caption') 170 | caption = pval; 171 | elseif strcmpi(pname, 'label') 172 | label = ['tab:', pval]; 173 | elseif strcmpi(pname, 'transpose') 174 | if pval 175 | matrix = matrix'; 176 | varargin{j+1} = false; % set transpose to false 177 | table = matrix2latex(matrix, filename, varargin{:}); 178 | return; 179 | end 180 | elseif strcmpi(pname, 'environment') 181 | environment = pval; 182 | else 183 | error('%s: unknown parameter name %s', mfilename, pname) 184 | end 185 | end 186 | 187 | if filename ~= 1 188 | if (length(filename) < 4) || ~strcmp(filename(end-3:end), '.tex') 189 | filename = [filename, '.tex']; 190 | end 191 | %fid = fopen(filename, 'w'); 192 | if isempty(label) 193 | label = ['tab:', filename(1:end-4)]; 194 | end 195 | %else 196 | %fid = 1; % fprintf will print to standard output 197 | end 198 | 199 | %if isempty(matrix) 200 | % return; 201 | %end 202 | 203 | if isnumeric(matrix) 204 | matrix = num2cell(matrix); 205 | for h=1:height 206 | for w=1:width 207 | if isnan(matrix{h, w}) 208 | matrix{h, w} = '{-}'; 209 | elseif matrix{h, w} == inf 210 | matrix{h, w} = '$\infty$'; 211 | elseif matrix{h, w} == -inf 212 | matrix{h, w} = '$-\infty$'; 213 | elseif(~isempty(format)) 214 | if iscellstr(format) % if formatColumns 215 | matrix{h, w} = num2str(matrix{h, w}, format{w}); 216 | end 217 | matrix{h, w} = num2str(matrix{h, w}, format); 218 | else 219 | matrix{h, w} = num2str(matrix{h, w}); 220 | end 221 | matrix{h, w} = fixEngineeringNotation(matrix{h, w}); 222 | end 223 | end 224 | end 225 | 226 | if(~isempty(textsize)) 227 | table = [table, sprintf('\\begin{%s}\n', textsize)]; 228 | end 229 | 230 | for ix = 1:length(environment) 231 | e = environment{ix}; 232 | table = [table, sprintf(repmat('\t',1,ix-1))]; 233 | if strcmpi(e, 'table') 234 | table = [table, sprintf('\\begin{%s}[htp]\n', e)]; 235 | elseif strcmpi(e, 'tabular') 236 | table = [table, sprintf('\\begin{%s}{', e)]; 237 | table = [table, sprintf('%s}\n', alignment)]; 238 | 239 | table = [table, sprintf(repmat('\t',1,ix-1))]; 240 | table = [table, sprintf('\\toprule\n')]; 241 | elseif strcmpi(e, 'center') 242 | table = [table, sprintf('\\begin{%s}\n', e)]; 243 | if ~isempty(caption) 244 | table = [table, sprintf('\t\\caption{%s}\n', caption)]; 245 | end 246 | if ~isempty(label) 247 | table = [table, sprintf('\t\\label{%s}\n', label)]; 248 | end 249 | else 250 | table = [table, sprintf('\\begin{%s}\n', e)]; 251 | end 252 | end 253 | 254 | if(~isempty(headerRow)) 255 | table = [table, sprintf('\t\t\t')]; 256 | if ~isempty(headerColumn) && ~isempty(headerRow) && ... 257 | length(headerRow) == width 258 | table = [table, sprintf('{} & ')]; 259 | end 260 | for w=1:length(headerRow)-1 261 | table = [table, sprintf('{%s} & ', headerRow{w})]; 262 | %\textbf{%s}&', headerRow{w})]; 263 | end 264 | if width ~= length(headerRow) 265 | table = [table, sprintf('{%s}\\\\\n', ... 266 | headerRow{width+1})]; 267 | else 268 | table = [table, sprintf('{%s}\\\\\n', ... 269 | headerRow{width})]; 270 | end 271 | table = [table, sprintf('\t\t\t\\midrule\n')]; 272 | end 273 | 274 | for h=1:height 275 | table = [table, sprintf(repmat('\t',1,height))]; 276 | if(~isempty(headerColumn)) 277 | table = [table, sprintf('{%s} & ', headerColumn{h})]; 278 | end 279 | for w=1:width-1 280 | table = [table, sprintf('%s & ', matrix{h, w})]; 281 | end 282 | table = [table, sprintf('%s\\\\\n', matrix{h, width})]; 283 | end 284 | 285 | for ix = length(environment):-1:1 286 | e = environment{ix}; 287 | table = [table, sprintf(repmat('\t',1,ix-1))]; 288 | if strcmpi(e, 'tabular') 289 | table = [table, sprintf('\\bottomrule\n')]; 290 | table = [table, sprintf(repmat('\t',1,ix-1))]; 291 | end 292 | table = [table, sprintf('\\end{%s}\n', e)]; 293 | end 294 | 295 | if(~isempty(textsize)) 296 | table = [table, sprintf('\\end{%s}', textsize)]; 297 | end 298 | 299 | if ~isempty(filename) % if we should write to file 300 | fid = fopen(filename, 'w'); 301 | fwrite(fid, table); 302 | fclose(fid); 303 | end -------------------------------------------------------------------------------- /test/README.txt: -------------------------------------------------------------------------------- 1 | This folder contains simple test scripts for the different languages, the result is compared to the test.tex file. 2 | -------------------------------------------------------------------------------- /test/test.m: -------------------------------------------------------------------------------- 1 | %This file is part of matrix2latex. 2 | % 3 | %matrix2latex is free software: you can redistribute it and/or modify 4 | %it under the terms of the GNU General Public License as published by 5 | %the Free Software Foundation, either version 3 of the License, or 6 | %(at your option) any later version. 7 | % 8 | %matrix2latex is distributed in the hope that it will be useful, 9 | %but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | %MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | %GNU General Public License for more details. 12 | % 13 | %You should have received a copy of the GNU General Public License 14 | %along with matrix2latex. If not, see . 15 | 16 | % tests for matrix2latex.m 17 | function test() 18 | path(path, '../src_matlab') 19 | m = [1, 2, 3; 4, 5, 6]; 20 | 21 | function assertLine(output, b, lineNr) 22 | output = textscan(output, '%s', 'delimiter', '\n'); 23 | a = output{1}{lineNr}; 24 | a = strtrim(a); 25 | b = strtrim(b); 26 | if ~strcmp(a, b) 27 | error('Invalid! "%s" ~= "%s"', a, b) 28 | end 29 | end 30 | function assertEqual(output, name) 31 | fid = fopen('test.tex'); 32 | tline = fgetl(fid); 33 | found = false; 34 | answer = ''; 35 | output = textscan(output, '%s', 'delimiter', '\n'); 36 | output = output{1}; 37 | ix = 1; 38 | while ischar(tline) 39 | if strcmp(tline, ['%%%', name]) 40 | found = true; 41 | elseif regexpi(tline, '%%%', 'start') 42 | found = false; 43 | elseif found 44 | answer = [answer, tline]; % append 45 | a = strtrim(output{ix}); 46 | b = strtrim(tline); 47 | if ~strcmp(a, b) 48 | output 49 | answer 50 | error('Invalid! "%s" ~= "%s"', a, b) 51 | end 52 | ix = ix + 1; 53 | end 54 | tline = fgetl(fid); % prepare next loop 55 | end 56 | fclose(fid); 57 | end 58 | 59 | function test_simple() 60 | t = matrix2latex(m, ''); 61 | assertEqual(t, 'simple'); 62 | end 63 | test_simple() 64 | 65 | function test_transpose1() 66 | t = matrix2latex(m, '', 'transpose', true); 67 | assertEqual(t, 'transpose1'); 68 | end 69 | test_transpose1() 70 | 71 | function test_transpose2() 72 | hr = {'a', 'b'}; 73 | t = matrix2latex(m, '', 'transpose', true, 'headerRow', hr); 74 | assertEqual(t, 'transpose2'); 75 | end 76 | test_transpose2() 77 | 78 | function test_file() 79 | matrix2latex(m, 'tmp.tex'); 80 | fid = fopen('tmp.tex'); 81 | content = fread(fid, '*char'); 82 | fclose(fid); 83 | assertEqual(content, 'file'); 84 | end 85 | test_file() 86 | 87 | function test_environment1() 88 | t = matrix2latex(m, '', 'environment', {'table', 'center', 'tabular'}); 89 | assertEqual(t, 'environment1'); 90 | end 91 | test_environment1() 92 | 93 | function test_environment2() 94 | t = matrix2latex(m, '', 'environment', {'foo', 'bar'}); 95 | assertEqual(t, 'environment2'); 96 | end 97 | test_environment2() 98 | 99 | function test_labels1() 100 | cl = {'a', 'b'}; 101 | rl = {'c', 'd', 'e'}; 102 | t = matrix2latex(m, '', 'headerRow', rl, 'headerColumn', cl); 103 | assertEqual(t, 'labels1'); 104 | end 105 | test_labels1() 106 | 107 | function test_labels2() 108 | % only difference from above test is 'names', note how above function 109 | % handles having too few rowLabels 110 | cl = {'a', 'b'}; 111 | rl = {'names', 'c', 'd', 'e'}; 112 | t = matrix2latex(m, '', 'headerRow', rl, 'headerColumn', cl); 113 | assertEqual(t, 'labels2'); 114 | end 115 | test_labels2() 116 | 117 | % Not possible in matlab 118 | %function test_labels3() 119 | % % pass in environment as dictionary 120 | % e = dict() 121 | % e['columnLabels'] = ['a', 'b'] 122 | % e['rowLabels'] = ['names', 'c', 'd', 'e'] 123 | % t = matrix2latex(m, None, **e) 124 | % assertEqual(t, 'labels3') 125 | %test_labels3() 126 | 127 | function test_labels4() 128 | t = matrix2latex(m, '', 'caption', 'Hello', 'label', 'la'); 129 | assertEqual(t, 'labels4'); 130 | end 131 | test_labels4() 132 | 133 | function test_alignment1() 134 | t = matrix2latex(m, '', 'alignment', 'r'); 135 | assertLine(t, '\begin{tabular}{rrr}', 3); 136 | end 137 | test_alignment1() 138 | 139 | function test_alignment2() 140 | cl = {'a', 'b'}; 141 | rl = {'names', 'c', 'd', 'e'}; 142 | t = matrix2latex(m, '', 'alignment', 'r', 'headerColumn', cl, 'headerRow', rl); 143 | assertLine(t, '\begin{tabular}{rrrr}', 3); 144 | end 145 | test_alignment2() 146 | 147 | function test_alignment2b() 148 | rl = {'a', 'b'}; 149 | cl = {'names', 'c', 'd', 'e'}; 150 | t = matrix2latex(m, '', 'alignment', 'r', 'headerColumn', cl, 'headerRow', ... 151 | rl, 'transpose', true); 152 | assertLine(t, '\begin{tabular}{rrr}', 3); 153 | end 154 | test_alignment2b() 155 | 156 | function test_alignment3() 157 | t = matrix2latex(m, '', 'alignment', 'rcl'); 158 | assertLine(t, '\begin{tabular}{rcl}', 3); 159 | end 160 | test_alignment3() 161 | 162 | function test_alignment4() 163 | t = matrix2latex(m, '', 'alignment', 'rcl', 'headerColumn', {'a', 'b'}); 164 | assertLine(t, '\begin{tabular}{rrcl}', 3); 165 | end 166 | test_alignment4() 167 | 168 | function test_alignment5() 169 | t = matrix2latex(m, '', 'alignment', 'r|c|l', 'headerColumn', {'a', 'b'}); 170 | assertLine(t, '\begin{tabular}{rr|c|l}', 3); 171 | end 172 | test_alignment5() 173 | 174 | function test_alignment_withoutTable() 175 | t = matrix2latex(m, '', 'environment', {'align*', 'pmatrix'}, ... 176 | 'format', '$%.2f$', 'alignment', 'c'); 177 | assertEqual(t, 'alignment_withoutTable'); 178 | end 179 | test_alignment_withoutTable() 180 | 181 | % numpy, not an issue 182 | %function test_numpy() 183 | % try: 184 | % import numpy as np 185 | % for a in (np.matrix, np.array): 186 | % t = matrix2latex(a(m), None, 'align*', 'pmatrix') 187 | % assertEqual(t, 'numpy') 188 | % % Systems without numpy raises import error, 189 | % % pypy raises attribute since matrix is not implemented, this is ok. 190 | % except (ImportError, AttributeError): 191 | % pass 192 | 193 | function test_string() 194 | t = matrix2latex({'a', 'b', '1'; '1', '2', '3'}, '', 'format', '%s'); 195 | assertEqual(t, 'string'); 196 | end 197 | test_string() 198 | 199 | function test_none() 200 | m = [1,nan,nan; 2,2,1; 2,1,2]; 201 | t = matrix2latex(m, ''); 202 | assertEqual(t, 'none'); 203 | 204 | t3 = matrix2latex(m, '', 'format', '$%d$'); 205 | assertEqual(t3, 'none'); 206 | end 207 | test_none() 208 | 209 | % numpy, not an issue 210 | %function test_infty1() 211 | % try: 212 | % import numpy as np 213 | % m = [[1,np.inf,float('inf')], [2,2,float('-inf')], [-np.inf,1,2]] 214 | % t = matrix2latex(m) 215 | % assertEqual(t, 'infty1') 216 | % except (ImportError, AttributeError): 217 | % pass 218 | 219 | function test_infty2() 220 | m = [1,inf, inf;, 2,2, -inf; -inf,1,2]; 221 | t = matrix2latex(m, ''); 222 | assertEqual(t, 'infty1'); 223 | end 224 | test_infty2() 225 | 226 | function test_multicolumn() 227 | hr = {{'Item', 'Item', 'Item', 'Item', 'Price', 'Price', 'test', '', 'Money', 'Money', 'Money'}, 228 | {'Animal', 'Description', '(\$)'}} 229 | t = matrix2latex(m, '', 'headerRow', hr) 230 | assertLine(t, '\multicolumn{4}{c}{Item} & \multicolumn{2}{c}{Price} & test & & \multicolumn{3}{c}{Money}\\\cmidrule(r){1-4}\cmidrule(r){5-6}\cmidrule(r){9-11}', 5) 231 | end 232 | %test_multicolumn() 233 | 234 | function test_empty() 235 | t = matrix2latex([], ''); 236 | assertEqual(t, 'empty'); 237 | end 238 | test_empty() 239 | 240 | function test_nicefloat() 241 | t = matrix2latex([123456e-10; 1e-15;12345e5], ''); 242 | assertEqual(t, 'nicefloat'); 243 | end 244 | test_nicefloat(); 245 | 246 | function test_nicefloat_4g() 247 | t = matrix2latex([123456e-10; 1e-15; 12345e5], '', 'format', '$%.4g$'); 248 | assertEqual(t, 'nicefloat_4g'); 249 | end 250 | test_nicefloat_4g(); 251 | 252 | function test_non_rectangular() 253 | t = matrix2latex([1, 2; 254 | 1, 2, 3; 255 | 5]); % not legal matlab, no need 256 | % to test/support. 257 | assertEqual(t, 'nicefloat_4g'); 258 | end 259 | %test_non_rectangular(); 260 | 261 | % end of file: 262 | end -------------------------------------------------------------------------------- /test/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """This file is part of matrix2latex. 3 | 4 | matrix2latex is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | matrix2latex is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with matrix2latex. If not, see . 16 | """ 17 | 18 | # tests for matrix2latex.py 19 | import os 20 | import sys 21 | 22 | sys.path.insert(0, '../') 23 | from matrix2latex import matrix2latex 24 | 25 | try: 26 | from test_syntaxError import * 27 | except SyntaxError: 28 | pass 29 | 30 | from test_util import * 31 | 32 | m = [[1, 2, 3], [4, 5, 6]] 33 | 34 | 35 | def test_simple(): 36 | t = matrix2latex(m) 37 | assertEqual(t, "simple") 38 | 39 | def test_transpose1(): 40 | t = matrix2latex(m, transpose=True) 41 | assertEqual(t, "transpose1") 42 | 43 | def test_transpose2(): 44 | cl = ["a", "b"] 45 | t = matrix2latex(m, transpose=True, headerRow=cl) 46 | assertEqual(t, "transpose2") 47 | 48 | def test_file(): 49 | matrix2latex(m, 'tmp.tex') 50 | f = open('tmp.tex') 51 | content = f.read() 52 | f.close() 53 | assertEqual(content, "file") 54 | 55 | def test_environment1(): 56 | t = matrix2latex(m, None, "table", "center", "tabular") 57 | assertEqual(t, "environment1") 58 | 59 | def test_environment2(): 60 | t = matrix2latex(m, None, "foo", "bar") 61 | assertEqual(t, "environment2") 62 | 63 | def test_labels1(): 64 | cl = ["a", "b"] 65 | rl = ["c", "d", "e"] 66 | t = matrix2latex(m, None, headerColumn=cl, headerRow=rl) 67 | assertEqual(t, "labels1") 68 | 69 | def test_labels2(): 70 | # only difference from above test is names, note how above function 71 | # handles having too few headerRow 72 | cl = ["a", "b"] 73 | rl = ["names", "c", "d", "e"] 74 | t = matrix2latex(m, None, headerColumn=cl, headerRow=rl) 75 | assertEqual(t, "labels2") 76 | 77 | def test_labels3(): 78 | # pass in environment as dictionary 79 | e = dict() 80 | e['headerColumn'] = ["a", "b"] 81 | e['headerRow'] = ["names", "c", "d", "e"] 82 | t = matrix2latex(m, None, **e) 83 | assertEqual(t, "labels3") 84 | 85 | def test_labels4(): 86 | t = matrix2latex(m, None, caption="Hello", label="la") 87 | assertEqual(t, "labels4") 88 | 89 | def test_alignment1(): 90 | t = matrix2latex(m, alignment='r') 91 | t = t.split('\n')[2].strip() 92 | assert t == r"\begin{tabular}{rrr}", t 93 | 94 | def test_alignment2(): 95 | cl = ["a", "b"] 96 | rl = ["names", "c", "d", "e"] 97 | t = matrix2latex(m, alignment='r', headerColumn=cl, headerRow = rl) 98 | t = t.split('\n')[2].strip() 99 | assert t == r"\begin{tabular}{rrrr}", t 100 | 101 | def test_alignment2b(): 102 | rl = ["a", "b"] 103 | cl = ["names", "c", "d", "e"] 104 | t = matrix2latex(m, alignment='r', headerColumn=cl, headerRow = rl, transpose=True) 105 | t = t.split('\n')[2].strip() 106 | assert t == r"\begin{tabular}{rrr}", t 107 | 108 | def test_alignment3(): 109 | t = matrix2latex(m, alignment='rcl') 110 | t = t.split('\n')[2].strip() 111 | assert t == r"\begin{tabular}{rcl}", t 112 | 113 | def test_alignment4(): 114 | t = matrix2latex(m, alignment='rcl', headerColumn=["a", "b"]) 115 | t = t.split('\n')[2].strip() # pick out only third line 116 | assert t == r"\begin{tabular}{rrcl}", t 117 | 118 | def test_alignment5(): 119 | t = matrix2latex(m, alignment='r|c|l', headerColumn=["a", "b"]) 120 | t = t.split('\n')[2].strip() # pick out only third line 121 | assert t == r"\begin{tabular}{rr|c|l}", t 122 | 123 | def test_alignment_withoutTable(): 124 | t = matrix2latex(m, None, "align*", "pmatrix", format="$%.2f$", alignment='c') 125 | assertEqual(t, "alignment_withoutTable") 126 | 127 | def test_numpy(): 128 | try: 129 | import numpy as np 130 | for a in (np.matrix, np.array): 131 | t = matrix2latex(a(m), None, "align*", "pmatrix") 132 | assertEqual(t, "numpy") 133 | # Systems without numpy raises import error, 134 | # pypy raises attribute since matrix is not implemented, this is ok. 135 | except (ImportError, AttributeError): 136 | pass 137 | 138 | def test_string(): 139 | t = matrix2latex([['a', 'b', '1'], ['1', '2', '3']], format='%s') 140 | assertEqual(t, "string") 141 | 142 | def test_none(): 143 | m = [[1,None,None], [2,2,1], [2,1,2]] 144 | t = matrix2latex(m) 145 | assertEqual(t, "none") 146 | 147 | m2 = [[1,float('NaN'),float('NaN')], [2,2,1], [2,1,2]] 148 | t2 = matrix2latex(m) 149 | assertEqual(t2, "none") 150 | 151 | t3 = matrix2latex(m, format='$%d$') 152 | assertEqual(t3, "none") 153 | 154 | def test_infty1(): 155 | try: 156 | import numpy as np 157 | m = [[1,np.inf,float('inf')], [2,2,float('-inf')], [-np.inf,1,2]] 158 | t = matrix2latex(m) 159 | assertEqual(t, "infty1") 160 | except (ImportError, AttributeError): 161 | pass 162 | 163 | def test_infty2(): 164 | # same as above but without numpy 165 | inf = float('inf') 166 | m = [[1,inf,float('inf')], [2,2,float('-inf')], [-inf,1,2]] 167 | t = matrix2latex(m) 168 | assertEqual(t, "infty1") 169 | 170 | def test_multicolumn(): 171 | hr = [['Item', 'Item', 'Item', 'Item', 'Price', 'Price', 'test', '', 'Money', 'Money', 'Money'], 172 | ['Animal', 'Description', '(\$)']] 173 | t = matrix2latex(m, headerRow=hr) 174 | t = t.split('\n')[4].strip() # pick out only third line 175 | assert t == r"\multicolumn{4}{c}{Item} & \multicolumn{2}{c}{Price} & {test} & {} & \multicolumn{3}{c}{Money}\\\cmidrule(r){1-4}\cmidrule(r){5-6}\cmidrule(r){9-11}", t 176 | 177 | def test_empty(): 178 | t = matrix2latex([]) 179 | assertEqual(t, 'empty') 180 | 181 | def test_nicefloat(): 182 | t = matrix2latex([123456e-10, 1e-15, 12345e5]) 183 | assertEqual(t, 'nicefloat') 184 | 185 | def test_nicefloat_4g(): 186 | t = matrix2latex([123456e-10, 1e-15, 12345e5], format='$%.4g$') 187 | assertEqual(t, 'nicefloat_4g') 188 | 189 | def test_non_rectangular(): 190 | """Test a nested list with 'missing' elements""" 191 | t = matrix2latex([[1,2], 192 | [1, 2, 3], 193 | [5]]) 194 | assertEqual(t, 'non_rectangular') 195 | 196 | 197 | def test_pandas_dataframe(): 198 | try: 199 | import pandas as pd 200 | import numpy as np 201 | m = [[1, 1], [2, 4], [3, 9]] # python nested list 202 | m = pd.DataFrame(m) 203 | #m = pd.DataFrame.from_csv('http://chymera.eu/data/test/r_data.csv', parse_dates=False, index_col=False) 204 | # print('PANDAS\n', m) 205 | # print('PANDAS\n', m.to_records()) 206 | t = matrix2latex(m) 207 | assertEqual(t, "pandas_dataframe") 208 | except ImportError: 209 | pass 210 | 211 | def test_pandas_series(): 212 | try: 213 | import pandas as pd 214 | import numpy as np 215 | s = pd.Series([2, 4, 2, 42, 5], index=['a', 'b', 'c', 'd', 'e']) 216 | # print('PANDAS\n', s) 217 | # print('PANDAS\n', s.to_dict(), s.tolist(), hasattr(s, 'to_dict')) 218 | t = matrix2latex(s) 219 | # print('pandas Series', t) 220 | t2 = matrix2latex(pd.DataFrame(s)) 221 | # print('pandas DataFrame', t2) 222 | assertEqual(t, "pandas_series") 223 | assertEqual(t2, "pandas_series_dataFrame") 224 | except ImportError: 225 | pass 226 | 227 | def test_pandas_columns(): 228 | try: 229 | import pandas as pd 230 | import numpy as np 231 | d = {'one' : pd.Series([1., 2., 3.], index=['a', 'b', 'c']), 232 | 'two' : pd.Series([1., 2., 3., 4.], index=['a', 'b', 'c', 'd'])} 233 | df = pd.DataFrame(d) 234 | t = matrix2latex(df) 235 | # print('pandas', t, df.to_records()) 236 | assertEqual(t, "pandas_columns") 237 | 238 | t = matrix2latex(df, headerRow=None, headerColumn=None) 239 | assertEqual(t, "pandas_columns_noHeaders") 240 | except ImportError: 241 | pass 242 | 243 | # Pandas Panel now depricated, remove test 244 | # def test_pandas_Panel(): 245 | # try: 246 | # import pandas as pd 247 | # import numpy as np 248 | # panel = pd.Panel(np.random.randn(3, 5, 4), items=['one', 'two', 'three'], 249 | # major_axis=pd.date_range('1/1/2000', periods=5), 250 | # minor_axis=['a', 'b', 'c', 'd']) 251 | # frame = panel.to_frame() 252 | # # TODO: Needs support for multiple headerColumns 253 | # #t = matrix2latex(frame) 254 | # #print(t) 255 | # #assert False 256 | # except ImportError: 257 | # pass 258 | 259 | if __name__ == '__main__': 260 | import test 261 | for d in sorted(test.__dict__): 262 | if 'test_' in d: 263 | print('RUNNING', d) 264 | eval(d+'()') 265 | -------------------------------------------------------------------------------- /test/test.tex: -------------------------------------------------------------------------------- 1 | %%%simple 2 | \begin{table}[htp] 3 | \begin{center} 4 | \begin{tabular}{ccc} 5 | \toprule 6 | $1$ & $2$ & $3$\\ 7 | $4$ & $5$ & $6$\\ 8 | \bottomrule 9 | \end{tabular} 10 | \end{center} 11 | \end{table} 12 | %%%transpose1 13 | \begin{table}[htp] 14 | \begin{center} 15 | \begin{tabular}{cc} 16 | \toprule 17 | $1$ & $4$\\ 18 | $2$ & $5$\\ 19 | $3$ & $6$\\ 20 | \bottomrule 21 | \end{tabular} 22 | \end{center} 23 | \end{table} 24 | %%%transpose2 25 | \begin{table}[htp] 26 | \begin{center} 27 | \begin{tabular}{cc} 28 | \toprule 29 | {a} & {b}\\ 30 | \midrule 31 | $1$ & $4$\\ 32 | $2$ & $5$\\ 33 | $3$ & $6$\\ 34 | \bottomrule 35 | \end{tabular} 36 | \end{center} 37 | \end{table} 38 | %%%file 39 | \begin{table}[htp] 40 | \begin{center} 41 | \label{tab:tmp} 42 | \begin{tabular}{ccc} 43 | \toprule 44 | $1$ & $2$ & $3$\\ 45 | $4$ & $5$ & $6$\\ 46 | \bottomrule 47 | \end{tabular} 48 | \end{center} 49 | \end{table} 50 | %%%environment1 51 | \begin{table}[htp] 52 | \begin{center} 53 | \begin{tabular}{ccc} 54 | \toprule 55 | $1$ & $2$ & $3$\\ 56 | $4$ & $5$ & $6$\\ 57 | \bottomrule 58 | \end{tabular} 59 | \end{center} 60 | \end{table} 61 | %%%environment2 62 | \begin{foo} 63 | \begin{bar} 64 | $1$ & $2$ & $3$\\ 65 | $4$ & $5$ & $6$\\ 66 | \end{bar} 67 | \end{foo} 68 | %%%labels1 69 | \begin{table}[htp] 70 | \begin{center} 71 | \begin{tabular}{rccc} 72 | \toprule 73 | {} & {c} & {d} & {e}\\ 74 | \midrule 75 | {a} & $1$ & $2$ & $3$\\ 76 | {b} & $4$ & $5$ & $6$\\ 77 | \bottomrule 78 | \end{tabular} 79 | \end{center} 80 | \end{table} 81 | %%%labels2 82 | \begin{table}[htp] 83 | \begin{center} 84 | \begin{tabular}{rccc} 85 | \toprule 86 | {names} & {c} & {d} & {e}\\ 87 | \midrule 88 | {a} & $1$ & $2$ & $3$\\ 89 | {b} & $4$ & $5$ & $6$\\ 90 | \bottomrule 91 | \end{tabular} 92 | \end{center} 93 | \end{table} 94 | %%%labels3 95 | \begin{table}[htp] 96 | \begin{center} 97 | \begin{tabular}{rccc} 98 | \toprule 99 | {names} & {c} & {d} & {e}\\ 100 | \midrule 101 | {a} & $1$ & $2$ & $3$\\ 102 | {b} & $4$ & $5$ & $6$\\ 103 | \bottomrule 104 | \end{tabular} 105 | \end{center} 106 | \end{table} 107 | %%%labels4 108 | \begin{table}[htp] 109 | \begin{center} 110 | \caption{Hello} 111 | \label{tab:la} 112 | \begin{tabular}{ccc} 113 | \toprule 114 | $1$ & $2$ & $3$\\ 115 | $4$ & $5$ & $6$\\ 116 | \bottomrule 117 | \end{tabular} 118 | \end{center} 119 | \end{table} 120 | %%%alignment_withoutTable 121 | \begin{align*} 122 | \begin{pmatrix} 123 | $1.00$ & $2.00$ & $3.00$\\ 124 | $4.00$ & $5.00$ & $6.00$\\ 125 | \end{pmatrix} 126 | \end{align*} 127 | %%%numpy 128 | \begin{align*} 129 | \begin{pmatrix} 130 | $1$ & $2$ & $3$\\ 131 | $4$ & $5$ & $6$\\ 132 | \end{pmatrix} 133 | \end{align*} 134 | %%%string 135 | \begin{table}[htp] 136 | \begin{center} 137 | \begin{tabular}{ccc} 138 | \toprule 139 | a & b & 1\\ 140 | 1 & 2 & 3\\ 141 | \bottomrule 142 | \end{tabular} 143 | \end{center} 144 | \end{table} 145 | %%%none 146 | \begin{table}[htp] 147 | \begin{center} 148 | \begin{tabular}{ccc} 149 | \toprule 150 | $1$ & {-} & {-}\\ 151 | $2$ & $2$ & $1$\\ 152 | $2$ & $1$ & $2$\\ 153 | \bottomrule 154 | \end{tabular} 155 | \end{center} 156 | \end{table} 157 | %%%infty1 158 | \begin{table}[htp] 159 | \begin{center} 160 | \begin{tabular}{ccc} 161 | \toprule 162 | $1$ & $\infty$ & $\infty$\\ 163 | $2$ & $2$ & $-\infty$\\ 164 | $-\infty$ & $1$ & $2$\\ 165 | \bottomrule 166 | \end{tabular} 167 | \end{center} 168 | \end{table} 169 | %%%empty 170 | \begin{table}[htp] 171 | \begin{center} 172 | \begin{tabular}{c} 173 | \toprule 174 | \bottomrule 175 | \end{tabular} 176 | \end{center} 177 | \end{table} 178 | %%%nicefloat 179 | \begin{table}[htp] 180 | \begin{center} 181 | \begin{tabular}{c} 182 | \toprule 183 | $1.23456\e{-05}$\\ 184 | $1\e{-15}$\\ 185 | $1.2345\e{+09}$\\ 186 | \bottomrule 187 | \end{tabular} 188 | \end{center} 189 | \end{table} 190 | %%%nicefloat_4g 191 | \begin{table}[htp] 192 | \begin{center} 193 | \begin{tabular}{c} 194 | \toprule 195 | $1.235\e{-05}$\\ 196 | $1\e{-15}$\\ 197 | $1.234\e{+09}$\\ 198 | \bottomrule 199 | \end{tabular} 200 | \end{center} 201 | \end{table} 202 | %%%non_rectangular 203 | \begin{table}[htp] 204 | \begin{center} 205 | \begin{tabular}{ccc} 206 | \toprule 207 | $1$ & $2$ & {-}\\ 208 | $1$ & $2$ & $3$\\ 209 | $5$ & {-} & {-}\\ 210 | \bottomrule 211 | \end{tabular} 212 | \end{center} 213 | \end{table} 214 | %%%format_formatColumn_Warning 215 | \begin{table}[htp] 216 | \begin{center} 217 | \begin{tabular}{cc} 218 | \toprule 219 | 1\e{+15} & 1.23456\e{+15}\\ 220 | \bottomrule 221 | \end{tabular} 222 | \end{center} 223 | \end{table} 224 | %%%pandas_dataframe 225 | \begin{table}[htp] 226 | \begin{center} 227 | \begin{tabular}{rcc} 228 | \toprule 229 | {} & {0} & {1}\\ 230 | \midrule 231 | {0} & $1$ & $1$\\ 232 | {1} & $2$ & $4$\\ 233 | {2} & $3$ & $9$\\ 234 | \bottomrule 235 | \end{tabular} 236 | \end{center} 237 | \end{table} 238 | %%%pandas_columns 239 | \begin{table}[htp] 240 | \begin{center} 241 | \begin{tabular}{rcc} 242 | \toprule 243 | {} & {one} & {two}\\ 244 | \midrule 245 | {a} & $1$ & $1$\\ 246 | {b} & $2$ & $2$\\ 247 | {c} & $3$ & $3$\\ 248 | {d} & {-} & $4$\\ 249 | \bottomrule 250 | \end{tabular} 251 | \end{center} 252 | \end{table} 253 | %%%pandas_columns_noHeaders 254 | \begin{table}[htp] 255 | \begin{center} 256 | \begin{tabular}{cc} 257 | \toprule 258 | $1$ & $1$\\ 259 | $2$ & $2$\\ 260 | $3$ & $3$\\ 261 | {-} & $4$\\ 262 | \bottomrule 263 | \end{tabular} 264 | \end{center} 265 | \end{table} 266 | %%%pandas_series 267 | \begin{table}[htp] 268 | \begin{center} 269 | \begin{tabular}{rc} 270 | \toprule 271 | {a} & $2$\\ 272 | {b} & $4$\\ 273 | {c} & $2$\\ 274 | {d} & $42$\\ 275 | {e} & $5$\\ 276 | \bottomrule 277 | \end{tabular} 278 | \end{center} 279 | \end{table} 280 | %%%pandas_series_dataFrame 281 | \begin{table}[htp] 282 | \begin{center} 283 | \begin{tabular}{rc} 284 | \toprule 285 | {} & {0}\\ 286 | \midrule 287 | {a} & $2$\\ 288 | {b} & $4$\\ 289 | {c} & $2$\\ 290 | {d} & $42$\\ 291 | {e} & $5$\\ 292 | \bottomrule 293 | \end{tabular} 294 | \end{center} 295 | \end{table} -------------------------------------------------------------------------------- /test/testVersionCompatibility.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Tries to run testsuit with python version 3 | # python. 4 | # where major and minor varies between 2-3 and 0-10. 5 | # (expand this if you have say python1.11 or python4.0) 6 | # Now also looks for pypy implementations. 7 | """This file is part of matrix2latex. 8 | 9 | matrix2latex is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | matrix2latex is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with matrix2latex. If not, see . 21 | """ 22 | import sys 23 | sys.path.insert(0, '../') 24 | 25 | import os 26 | from subprocess import call 27 | from matrix2latex import matrix2latex 28 | 29 | table = list() 30 | pythonVersions = list() 31 | 32 | def test(python, table, pythonVersions): 33 | ret = call([python, "test.py"], stdout=open(os.devnull, "w")) 34 | 35 | print(python) 36 | print(ret) 37 | if ret == 0: table.append(["True"]) 38 | else: table.append(["False"]) 39 | pythonVersions.append(python) 40 | 41 | 42 | for major in range(2, 3+1): 43 | for minor in range(0, 10): 44 | python = 'python%d.%d' % (major, minor) 45 | 46 | if call(python + " -c ''", shell=True, stderr=open(os.devnull, "w")) == 0: 47 | test(python, table, pythonVersions) 48 | 49 | if call("pypy-c" + " -c 'pass'", shell=True, stderr=open(os.devnull, "w")) == 0: 50 | test("pypy-c", table, pythonVersions) 51 | 52 | c = "Does 'python test.py' return 0?" 53 | compatibleTable = matrix2latex(table, '../doc/compatibleTable', 54 | headerColumn=pythonVersions, headerRow=['Compatible'], 55 | caption=c) 56 | print(compatibleTable) 57 | -------------------------------------------------------------------------------- /test/test_syntaxError.py: -------------------------------------------------------------------------------- 1 | """This file is a hack: files using the 'with' syntax will raise a syntax error on pre 2.4, so 2 | these are placed here.""" 3 | from __future__ import with_statement 4 | import warnings 5 | from test_util import * 6 | 7 | from matrix2latex import matrix2latex 8 | def test_format_formatColumn_Warning(): 9 | # Test for warning: http://stackoverflow.com/a/3892301/1942837 10 | with warnings.catch_warnings(record=True) as w: 11 | warnings.simplefilter("always") # Cause all warnings to always be triggered. 12 | # specify both format and formatColumn 13 | t = matrix2latex([[123456e10, 123456e10]], 14 | format='%g', formatColumn=['%.1g', '%g']) 15 | assert len(w) == 1 16 | assert issubclass(w[-1].category, Warning) 17 | assertEqual(t, 'format_formatColumn_Warning') 18 | -------------------------------------------------------------------------------- /test/test_util.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | SCRIPT_DIR = os.path.dirname(os.path.realpath(os.path.expanduser(__file__))) 4 | 5 | f = open(os.path.join(SCRIPT_DIR, 'test.tex')) 6 | answers = dict() 7 | for line in f: 8 | if line.startswith('%%%'): 9 | name = line[3:-1] # ignore %%% and \n 10 | answers[name] = '' 11 | else: 12 | answers[name] += line 13 | f.close() 14 | 15 | 16 | def loopTwoLists(x, y): 17 | for ix in range(max([len(x), len(y)])): 18 | try: a = x[ix].strip() 19 | except: a = '' 20 | try: b = y[ix].strip() 21 | except: b = '' 22 | yield a, b 23 | 24 | def assertEqual(x, name): 25 | # assert each line is equal, ignoring leading and trailing spaces 26 | print(x) 27 | y = answers[name] 28 | x = x.split('\n') 29 | y = y.split('\n') 30 | correct = True 31 | for a, b in loopTwoLists(x, y): 32 | if a != b: 33 | correct = False # found 1 or more error 34 | 35 | if not(correct): 36 | for a, b in loopTwoLists(x, y): 37 | print(a,b) 38 | raise AssertionError 39 | --------------------------------------------------------------------------------