diff --git a/NEWS.rst b/NEWS.rst new file mode 100644 index 0000000..69057dd --- /dev/null +++ b/NEWS.rst @@ -0,0 +1,700 @@ +================================================== + PythonTeX News +================================================== + + +Version History +=============== + + +v0.19 (2026/02/15) +------------------ + +* Fixed backslash escapes in docstrings and regexes for syntax compatibility + with Python 3.11+. + +* ``js`` now works as a command family for JavaScript (#197). + +* Python console commands and environments are now compatible with Python + 3.14 (#229). + +* Fixed a bug that could cause non-Windows operating systems to attempt to + invoke a nonexistent batch file when the executable associated with a + language is not found (#225). + +* Replaced doctype ``tex`` with ``texminted`` for ``Weave.jl`` (#219). + +* Fixed a bug that resulted in uncaught exceptions when ``\inputpygments`` + was used with files that do not exist (#64). + +* Fixed a bug in tracking the number of Pygments errors (#206). + +* Improved error messages (#207, #209). Improved documentation for + ``--error-exit-code`` (#208). + +* Several minor improvements to documentation. + + + +v0.18 (2021/06/06) +------------------ + +* ``\inputpygments`` now checks inputted files for modification, so that + typeset code will correctly update when the source is changed (#162). + +* Julia now uses project flag "``--project=@.``" (#157, #158). + +* Fixed bug in processing Pygments options (``pygopt``) when a key is used + without a value (#181). + +* Some error handling for Windows was incompatible with other operating + systems: replaced checks for ``WindowsError`` with checks for ``OSError`` + (#177). + +* Rust support is now compatible with document and working directory paths + that contain spaces (#167). + + + +v0.17 (2019/09/22) +------------------ + +* Pygments syntax highlighting for the Python console (``pycon`` lexer) now + uses the ``python3`` option, and the default Python lexer is now + ``python3`` (#156). + +* Added support for JavaScript (#147; thanks to Nathan Carter). + +* Updated Julia support for Julia versions 0.6 (#107), and 0.7 and 1.0 (#126, + #130). + +* There are now meaningful error messages for the Julia console when Weave.jl + is not installed or raises errors (#131). + +* ``pythontexcustomcode`` and ``\pythontexcustomc`` now set + ``pytex.context`` (#65). + +* Added support for R. The ``R`` family of commands and environments + (``\R``, ``\Rc``, ``Rcode``, ...) executes code as a script. + There is currently no utilities class or equivalent. The ``Rcon`` family + (``Rconsole``) executes code to emulate an interactive R session (#121). + +* ``fancyvrb`` settings from ``\setpythontexfv`` and console + environments now work with Julia and R consoles. + +* ``pythontexcustomcode`` now works with ``juliacon``. There are now proper + ``juliaconcode`` and ``Rconcode`` environments that execute code but + typeset nothing, to parallel ``pyconcode`` (#134). + +* Added support for Perl with the ``perl`` and ``pl`` families of commands + and environments. There is currently no utilities class or equivalent. + +* Added support for Perl 6 with the ``perlsix`` and ``psix`` families of + commands and environments (#104). There is currently no utilities class or + equivalent. + +* Updated Rust support by using ``dyn`` with traits in utilities object. + +* Under Windows, capitalization of script paths in ``stderr`` is now + preserved. + +* Fixed a bug that prevented the ``sub`` environment from working with + ``depythontex`` (#155). + +* Fixed a bug in checking mtime of dependencies to see if they have been + modified while ``pythontex`` is running. The check failed for dependencies + that do not exist or were deleted before ``pythontex`` can read them + (#136). + + + +v0.16 (2017/07/20) +------------------ + +* Added preliminary console support for Julia (#98). + +* Fixed Python console compatibility with Python 3.6 by setting the ``code`` + module's new ``exitmsg`` argument to suppress the exit message (#100). + +* Improved Rust support, including tracking of created files and + dependencies (#91). + + + +v0.15 (2016/07/21) +------------------ + +New features +~~~~~~~~~~~~ + +* The ``fvextra`` package is now required. This provides line breaking with + fine-grained control over break locations, the ability to highlight + specific lines or ranges of lines, improved handling of tabs, and several + additional features. + +* Added ``sub`` commands and environments (``\pys``, ``pysub``, ...). These + commands and environments perform string interpolation on text. Fields + delimited by ``!{...}`` are replaced by the result of evaluating and then + printing their content. This works for all families of commands and + environments, not just Python. See the documentation for details about + field delimiters and escaping. + +* Added ``rust`` and ``rs`` families of commands and environments. These + provide essentially complete support for Rust, except that + ``rstex.formatter()``, ``rstex.before()``, and ``rstex.after()`` will + likely need additional refinement (#90). + +* Added the ``sage`` family of commands and environments, which provide + support for Sage (#63). + +* Added ``bash`` family of commands and environments. This provides basic + support for bash (no utilities class or equivalent). Bash works with + Windows if it is installed. + +* Improved ``console`` compatibility under Linux with Python 3 (#70). + +* Counters for default sessions are now created automatically. This prevents + counter errors under some circumstances when working with ``\includeonly``. + +* Commands like ``\py`` can now output verbatim content under LuaTeX. + +Bugfixes +~~~~~~~~ + +* Fixed a bug that could cause an endless loop when a ``code`` command or + environment printed a ``code`` command or environment of the same family + with ``autoprint=true``. + + + +v0.14 (2014/07/17) +------------------ + +New features +~~~~~~~~~~~~ + +* All commands for working with code inline are now robust, via + ``etoolbox``'s ``\newrobustcmd``. Among other things, this allows + commands like ``\py`` to work in standard captions that have not been + redefined to avoid protection issues. +* Upgraded ``syncpdb`` to v0.2, which provides better list formatting. + +Backward-incompatible changes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* The default working directory is now the main document directory instead + of the output directory. Using the output directory was a common source + of confusion for new users and was incompatible with plans for future + development. Old documents in which the working directory was not + specified will continue to use the output directory, but PythonTeX will + print an upgrade message; new documents will use the new setting. The + output directory may be selected as the working directory manually, or + with the shorthand + "``\setpythontexworkingdir{}``". + +* Standardized version numbering by removing the "v" prefix from the stored + version numbers in Python variables and LaTeX macros. Standardized the + PythonTeX scripts by renaming ``version`` to ``__version__``. + + + +v0.13 (2014/07/14) +------------------ + +New features +~~~~~~~~~~~~ + +* Added ``--interactive`` command-line option. This runs a single + session in interactive mode, allowing user input. Among other things, + this is useful when working with debuggers. + +* Added ``--debug`` command-line option. This runs a single session + with the default debugger in interactive mode. Currently, only + standard (non-console) Python sessions are supported. The default + Python debugger is the new ``syncpdb``, which wraps ``pdb`` and + synchronizes code line numbers with document line numbers. All + ``pdb`` commands that take a line number or filename:lineno as an + argument will refer to document files and line numbers when the + argument has a percent symbol (``%``) as a prefix. For example, + ``list %50`` lists code that came from around line 50 in the + document. The ``--debug`` option will support other languages and + provide for customization in the future. + +* Added command-line option ``--jobs``, which allows the maximum number + of concurrent processes to be specified (#35). + +* Added support for GNU Octave, via the ``octave`` family of commands + and environments (#36). Parsing of Octave stderr is not ideal, though + synchronization works in most cases; this will be addressed by a + future rewrite of the stderr parser. + +* Installer now automatically works with MiKTeX, not just TeX Live. + +* The PythonTeX utilities class has a new ``open()`` method that opens + files and automatically tracks dependencies/created files. + +* When ``pythontex2.py`` and ``pythontex3.py`` are run directly, the + Python interpreter is automatically set to a reasonable default + (``py -2`` or ``py -3`` under Windows, using the Python 3.3+ wrapper; + ``python2`` or ``python3`` under other systems). + +* The installer now creates symlinks for the numbered scripts + ``pythontex*.py`` and ``depythontex*.py``. + +* Added Python version checking to all numbered scripts. + +* Under Python, the type of data passed via ``\setpythontexcontext`` may + now be set using YAML-style tags (``!!str``, ``!!int``, ``!!float``). For + example, ``{myint=!!int 123}``. + +* The ``fancyvrb`` options ``firstline`` and ``lastline`` now work with + the ``pygments`` environment and ``\inputpygments`` command. This required + some additional patching of ``fancyvrb``. + +* The ``pytx@Verbatim`` and ``pytx@SaveVerbatim`` environments are now + used for typesetting verbatim code. These are copies of the + ``fancyvrb`` environments. This prevents conflicts when literal + ``Verbatim`` and ``SaveVerbatim`` environments need to be typeset. + +* Improved ``latexmk`` compatibility (#40). Added discussion of + ``latexmk`` usage to documentation. + +* Tildes ``~`` may now be used in ``outputdir`` and ``workingdir`` to + refer to the user�s home directory, even under Windows. + +Bugfixes +~~~~~~~~ + +* Fixed a bug that prevented created files from being cleaned up when + the working directory was not the document root directory and the + full path to the files was not provided. + +* Fixed a bug that prevented the ``fvextfile`` option from working when + external files were highlighted. + + + +v0.13-beta (2014/02/06) +----------------------- + +New features +~~~~~~~~~~~~ + +* Switching to GitHub's Releases for downloads. + +* TeX information such as page dimensions may now be easily passed to the + programming-language side, using the new ``\setpythontexcontext`` command. + Contextual information is stored in the ``context`` attribute of the + utilities class, which is a dictionary (and also has attributes in Python). + +* The utilities class now has ``pt_to_in()``, ``pt_to_cm()``, and + ``pt_to_mm()`` methods for converting units of TeX points into inches, + centimeters, and millimeters. These work with integers and floats, as + well as strings that consist of numbers and optionally end in "pt". There + is also a ``pt_to_bp()`` for converting TeX points (1/72.27 inch) into big + (DTP or PostScript) points (1/72 inch). + +* Expanded Quickstart. Quickstart is now compatible with all LaTeX engines. + Quickstart now avoids ``microtype`` issues on some systems (#32). + +* Added information on citing PythonTeX (#28). + +* Utilities class has a new attribute ``id``, which is a string that joins + the command family name, session name, and session restart parameters with + underscores. This may be used in creating files that need a name that + contains a unique, session-based identifier (for example, names for + figures that are saved automatically). + +Backward-incompatible changes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* All utilities-class attributes with names of the form ``input_*`` have + been renamed with the "``input_``" removed. Among other things, this + makes it easier to access the ``context`` attribute (``pytex.context`` + vs. ``pytex.input_context``). + +* ``depythontex`` now has ``-o`` and ``--output`` command-line options for + specifying the name of the output file. If an output file is not + specified, then output is written to ``stdout``. This allows + ``depythontex`` output to be piped to another program. + +* All scripts ``*2.py`` now have shebangs with ``env python2``, and all + scripts ``*3.py`` now have shebangs with ``env python3``. This allows the + wrapper scripts (``env python`` shebang) to be used with the default + Python installation, and the numbered scripts to be used with specific + versions. Remember that except for console content, the ``--interpreter`` + option is what determines the Python version that actually executes code. + The version of Python used to launch ``pythontex.py`` merely determines + the version that manages code execution. (``--interpreter`` support for + console content is coming.) + +* Changed the template style used in the ``CodeEngine`` class. Replacement + fields are now surrounded by single curly braces (as in Python's format + string syntax), rather than double curly braces. Literal curly braces are + obtained by doubling braces. This allows the use of literal adjacent + double braces in templates, which was not possible previously. + +* The Julia template now uses the new ``in()`` function, replacing + ``contains()``. This requires Julia v0.2.0+. + +Bugfixes +~~~~~~~~ + +* Modified test for LuaTeX, so that ``\directlua`` is not ``\let`` to + ``\relax`` if it does not exist. This was causing incompatibility with + ``babel`` under pdfTeX and XeTeX (#33). + +* Added missing shebangs to ``depythontex*.py``. Handling of ``utilspath`` + is now more forgiving, so that ``pythontex_utils.py`` can be installed in + alternate locations (#23). + +* ``depythontex`` no longer leaves a blank line where + ``\usepackage{pythontex}`` was removed. + +* Console environments typeset with ``fancyvrb`` no longer end with an + unnecessary empty line. + +* Fixed bug in installer when ``kpsewhich`` was not found (#21). + + + +v0.12 (2013/08/26) +------------------ + +* Added support for the Julia language, with the ``julia`` and ``jl`` + families of commands and environments. (Note that Pygments only added + Julia support in version 1.6.) + +* Warnings and errors are now synchronized with the line numbers of files + brought in via ``\input``, ``\include``, etc. This is accomplished using + the ``currfile`` package. + +* Added package option ``gobble``. When ``gobble=auto``, all code is + dedented before being executed and/or typeset. The current + implementation is functional but basic; it will be improved and extended + in the future. + +* The document root directory is now always added to ``sys.path`` (or its + equivalent), even when it is not the working directory. (The working + directory has been added to ``sys.path`` since v0.12beta.) The document + directory is added after the working directory, so that the working + directory has precedence. + +* Fixed a bug in ``console`` commands and environments; ``sys.path`` now + contains the working and document directories, and the working directory + is now the output directory by default. This parallels the behavior of + non-``console`` commands and environments. + +* Added command-line option ``--interpreter`` that allows an interpreter to + be invoked via a specific command. This allows, for example, a specific + version of Python to be invoked. + +* Improved synchronization of stderr in cases when an error is triggered + far after its origin (for example, an error caused by a multiline string + that is lacking a closing quote/delimiter, and thus may span several + chunks of user code). + +* Modified usage of the ``shlex`` module to work around its lack of Unicode + support in Python versions prior to 2.7.3. + +* Fixed a bug from v0.12beta that prevented ``\inputpygments`` from working + when ``pygments=true``. + +* Fixed a bug with counters that caused errors when content spanning + multiple columns was created within a ``tabular`` environment. + +* Added checking for compatible Python versions in ``pythontex.py``. + +* Improved execution of ``*.bat`` and ``*.cmd`` files under Windows. The + solution from v0.12beta allowed ``*.bat`` and ``*.cmd`` to be found and + executed when the extension was not given, but did not give correct + return codes. + + +v0.12beta (2013/06/24) +---------------------- + +* Merged ``pythontex_types*.py`` into a single replacement + ``pythontex_engines.py`` compatible with both Python 2 and 3. It is + now much simpler to add support for additional languages. + +* Added support for the Ruby language as a demonstration of new + capabilities. The ``ruby`` and ``rb`` families of commands and + environments may be enabled via the new ``usefamily`` package option. + Support for additional languages is coming soon. See the new section + in the documentation on support for other languages for more + information. + +* Reimplemented treatment of Pygments content for better efficiency. + Now a Pygments process only runs if there is content to highlight. + Eliminated redundant highlighting of unmodified code. + +* Improved treatment of dependencies. If a dependency is modified + (``os.path.getmtime()``) after the current PythonTeX run starts, then + code that depends on it will be re-executed the next time PythonTeX + runs. A message is also issued to indicate that this is the case. + +* The utilities class now has ``before()`` and ``after()`` methods that + are called immediately before and after user code. These may be + redefined to customize output. For example, LaTeX commands could be + printed before and after user code; stdout could be redirected to + ``StringIO`` for further processing; or matplotlib figures could be + automatically detected, saved, and included in the document. + +* Added explanation of how to track dependencies and created files + automatically, and how to include matplotlib figures automatically, + to the documentation for the PythonTeX utilities class. + +* Created a new system for parsing and synchronizing stderr. + + - Exceptions that do not reference a line number in user code (such + as those from ``warnings.warn()`` in a module) are now traced back + to a single command or environment. Previously no synchronization + was attempted. This is accomplished by writing delimiters to + stderr before executing the code from each command/environment. + + - Exceptions that do reference a line in user code are more + efficiently synchronized with a document line number. This is + accomplished by careful record keeping as each script is + assembled. Line number synchronization no longer involves parsing + the script that was executed. + + - Improved and generalized parsing of stderr, in preparation for + supporting additional languages. Exceptions that cannot be + identified as errors or warnings are treated based on + ``Popen.returncode``. + +* Created a new system for ``console`` content. + + - There are now separate families of ``console`` commands and + environments. No Pygments or ``fancyvrb`` settings are shared with + the non-``console`` families, as was previously the case. There + is a new family of commands and environments based on ``pycon``, + including the ``\pycon`` command (inline reference to console variable), + ``pyconsole`` environment (same as the old one), ``\pyconc`` and + ``pyconcode`` (execute only), and ``\pyconv`` and ``pyconverbatim`` + (typeset only). There are equivalent families based on + ``pylabcon`` and ``sympycon``. + + - Each console session now runs in its own process and is cached + individually. Console output is now cached so that changing + Pygments settings no longer requires re-execution. + + - Unicode is now supported under Python 2. + + - The new package option ``pyconfuture`` allows automatic imports + from ``__future__`` for ``console`` families under Python 2, + paralleling the ``pyfuture`` option. + + - Any errors or warnings caused by code that is not typeset + (``code`` command and environment, startup code) are reported in + the run summary. This ensures that such code does not create + mischief. + + - ``customcode`` is now supported for ``console`` content. + +* Better support for ``latexmk`` and similar build tools. PythonTeX + creates a file of macros (``*.pytxmcr``) that is always included in a + document, and thus can be automatically detected and tracked by + ``latexmk``. This file now contains the time at which PythonTeX last + created files. When new files are created, the macro file will have a + new hash, triggering another document compile. + +* Improved the way in which the PythonTeX ``outputdir`` is added to the + graphics path. This had been done with ``\graphicspath``, but that + overwrites any graphics path previously specified by the user. Now the + ``outputdir`` is appended to any pre-existing path. + +* Added the ``depythontex`` option ``--graphicspath``. This adds the + ``outputdir`` to the graphics path of the ``depythontex`` document. + +* The installer now provides more options for installation locations. + It will now create missing directories if desired. + +* The working directory (``workingdir``) is now appended to + ``sys.path``, so that code there may be imported. + +* Under Windows, ``subprocess.Popen()`` is now invoked with + ``shell=True`` if ``shell=False`` results in a WindowsError. This + allows commands involving ``*.bat`` and ``*.cmd`` files to be + executed when the extension is not specified; otherwise, only ``*.exe`` + can be found and run. + +* The path to utils is now found in ``pythontex.py`` via + ``sys.path[0]`` rather than ``kpsewhich``. This allows the PythonTeX + scripts to be executed in an arbitrary location; they no longer must + be installed in a texmf tree where ``kpsewhich`` can find them. + +* Added ``rerun`` value ``never``. + +* At the end of each run, data and macros are only saved if modified, + improving efficiency. + +* The number of temporary files required by each process was reduced by + one. All macros for commands like ``\py`` are now returned within + stdout, rather than in their own file. + +* Fixed a bug with ``\stderrpythontex``; it was defaulting to ``verb`` + rather than ``verbatim`` mode. + + +v0.11 (2013/04/21) +------------------ + +* As the first non-beta release, this version adds several features and introduces several changes. You should read these release notes carefully, since some changes are not backwards-compatible. Changes are based on a thorough review of all current and planned features. PythonTeX's capabilities have already grown beyond what was originally intended, and a long list of features still remains to be implemented. As a result, some changes are needed to ensure consistent syntax and naming in the future. Insofar as possible, all command names and syntax will be frozen after this release. +* Added the ``pythontex.py`` and ``depythontex.py`` wrapper scripts. When run, these detect the current version of Python and import the correct PythonTeX code. It is still possible to run ``pythontex*.py`` and ``depythontex*.py`` directly, but the new wrapper scripts should be used instead for simplicity. There is now only a single ``pythontex_utils.py``, which works with both Python 2 and Python 3. +* Added the ``beta`` package option. This makes the current version behave like v0.11beta, for compatibility. This option is temporary and will probably only be retained for a few releases. +* Backward-incompatible changes (require the ``beta`` option to restore old behavior) + + - The ``pyverb`` environment has been renamed ``pyverbatim``. The old name was intended to be concise, but promoted confusion with LaTeX's ``\verb`` macro. + - For ``\printpythontex``, ``\stdoutpythontex``, and ``\stderrpythontex``, the modes ``inlineverb`` and ``v`` have been replaced by ``verb``, and the old mode ``verb`` has been replaced by ``verbatim``. This brings naming conventions in line with standard LaTeX ``\verb`` and ``verbatim``, avoiding a source of potential confusion. + - The ``\setpythontexpyglexer``, ``\setpythontexpygopt``, and ``\setpygmentspygopt`` commands now take an optional argument and a mandatory argument, rather than two mandatory arguments. This creates better uniformity among current and planned settings macros. + - The ``\setpythontexformatter`` and ``\setpygmentsformatter`` commands have been replaced by the ``\setpythontexprettyprinter`` and ``\setpygmentsprettyprinter`` commands. This anticipates possible upcoming features. It also avoids potential confusion with Pygments's formatters and the utilities class's ``formatter()`` method. + +* Deprecated (still work, but raise warnings; after a few releases, they will raise errors instead, and after that eventually be removed) + + - The ``rerun`` setting ``all`` was renamed ``always``, in preparation for upcoming features. + - The ``stderr`` option is replaced by ``makestderr``. The ``print``/``stdout`` option is replaced by ``debug``. These are intended to prevent confusion with future features. + - The ``fixlr`` option is deprecated. It was originally introduced to deal with some of SymPy's LaTeX formatting, which has since changed. + - The utilities class method ``init_sympy_latex()`` is deprecated. The ``sympy_latex()`` and ``set_sympy_latex()`` methods now automatically initialize themselves on first use. + +* Added ``autostdout`` package option and ``\setpythontexautostdout``, to complement ``autoprint``. Added ``prettyprinter`` and ``prettyprintinline`` package options to complement new settings commands. +* Added quickstart guide. +* Installer now installs gallery and quickstart files, if present. + + +v0.11beta (2013/02/17) +---------------------- + +* Commands like ``\py`` can now bring in any valid LaTeX code, including verbatim content, under the pdfTeX and XeTeX engines. Verbatim content was not allowed previously. LuaTeX cannot bring in verbatim, due to a known bug. +* Added package option ``depythontex`` and scripts ``depythontex*.py``. These allow a PythonTeX document to be converted into a pure LaTeX document, with no Python dependency. The package option creates an auxiliary file with extension ``.depytx``. The ``depythontex*.py`` scripts take this auxiliary file and the original LaTeX document, and combine the two to produce a new document that does not rely on the PythonTeX package. All PythonTeX commands and environments are replaced by their output. All Python-generated content is substituted directly into the document. By default, all typeset code is wrapped in ``\verb`` and ``verbatim``, but ``depythontex*.py`` has a ``--listing`` option that allows ``fancyvrb``, ``listings``, ``minted``, or ``pythontex`` to be used instead. +* The current PythonTeX version is now saved in the ``.pytxcode``. If this does not match the version of the PythonTeX scripts, a warning is issued. This makes it easier to determine errors due to version mismatches. +* Fixed an incompatibility with the latest release of ``xstring`` (version 1.7, 2013/01/13). +* Fixed a bug in the ``console`` environment that could cause problems when switching from Pygments highlighting to ``fancyvrb`` when using the ``fvextfile`` option. Fixed a bug introduced in the v0.10beta series that prevented the ``console`` environment from working with ``fancyvrb``. +* Fixed a bug with PythonTeX verbatim commands and environments that use Pygments. The verbatim commands and environments were incorrectly treated as if they had the attributes of executed code in the v0.10beta series. +* Fixed a bug from the v0.10beta series that sometimes prevented imports from ``__future__`` from working when there were multiple sessions. +* Fixed a bug related to hashing dependencies' mtime under Python 3. + + +v0.10beta2 (2013/01/23) +----------------------- + +* Improved ``pythontex*.py``'s handling of the name of the file being processed. A warning is no longer raised if the name is given with an extension; extensions are now processed (stripped) automatically. The filename may now contain a path to the file, so you need not run ``pythontex*.py`` from within the document's directory. +* Added command-line option ``--verbose`` for more verbose output. Currently, this prints a list of all processes that are launched. +* Fixed a bug that could crash ``pythontex*.py`` when the package option ``pygments=false``. +* Added documentation about ``autoprint`` behavior in the preamble. Summary: ``code`` commands and environments are allowed in the preamble as of v0.10beta. ``autoprint`` only applies to the body of the document, because nothing can be typeset in the preamble. Content printed in the preamble can be brought in by explicitly using ``\printpythontex``, but this should be used with great care. +* Revised ``\stdoutpythontex`` and ``\printpythontex`` so that they work in the preamble. Again, this should be used with great care if at all. +* Revised treatment of any content that custom code attempts to print. Custom code is not allowed to print to the document (see documentation). If custom code attempts to print, a warning is raised, and the printed content is included in the ``pythontex*.py`` run summary. +* One-line entries in stderr, such as those produced by Python's ``warnings.warn()``, were not previously parsed because they are of the form ``::`` rather than ``line ``. These are now parsed and synchronized with the document. They are also correctly parsed for inclusion in the document via ``\stderrpythontex``. +* If the package option ``stderrfilename`` is changed, all sessions that produced errors or warnings are now re-executed automatically, so that their stderr content is properly updated with the new filename. + + +v0.10beta (2013/01/09) +---------------------- + +* Backward-incompatible: Redid treatment of command-line options for + ``pythontex*.py``, using Python's ``argparse`` module. Run + ``pythontex*.py`` with option ``-h`` to see new command line options. +* Deprecated: ``\setpythontexcustomcode`` is deprecated in favor of the + ``\pythontexcustomc`` command and ``pythontexcustomcode`` + environment. These allow entry of pure code, unlike + ``\setpythontexcustomcode``. These also allow custom code to be + added to the beginning or end of a session, via an optional argument. + Improved treatment of errors and warnings associated with custom + code. +* The summary of errors and warnings now correctly differentiates + errors and warnings produced by user code, rather than treating all + of them as errors. By default, ``pythontex*.py`` now returns an + exit code of 1 if there were errors. +* The PythonTeX utilities class now allows external file dependencies + to be specified via ``pytex.add_dependencies()``, so that sessions + are automatically re-executed when external dependencies are + modified (modification is determined via either hash or mtime; this + is governed by the new ``hashdependencies`` option). +* The PythonTeX utilities class now allows created files to be + specified via ``pytex.add_created()``, so that created files may be + automatically cleaned up (deleted) when the code that created them + is modified (for example, name change for a saved plot). +* Added the following package options. + + - ``stdout`` (or ``print``): Allows input of stdout to be disabled. + Useful for debugging. + - ``runall``: Executes everything. Useful when code depends on + external data. + - ``rerun``: Determines when code is re-executed. Code may be set + to always run (same as ``runall`` option), or only run when it is + modified or when it produces errors or warnings. By default, + code is always re-executed if there are errors or modifications, + but not re-executed if there are warnings. + - ``hashdependencies``: Determines whether external dependencies + (data, external code files highlighted with Pygments, etc.) are + checked for modification via hashing or modification time. + Modification time is default for performance reasons. + +* Added the following new command line options. The options that are + equivalent to package options are overridden by the package options + when present. + + - ``--error-exit-code``: Determines whether an exit code of 1 is + returned if there were errors. On by default, but can be turned + off since it is undesirable when working with some editors. + - ``--runall``: Equivalent to new package option. + - ``--rerun``: Equivalent to new package option. + - ``--hashdependencies``: Equivalent to new package option. + +* Modified the ``fixlr`` option, so that it only patches commands if + they have not already been patched (avoids package conflicts). +* Added ``\setpythontexautoprint`` command for toggling autoprint + on/off within the body of the document. +* Installer now attempts to create symlinks under OS X and Linux with + TeX Live, and under OS X with MacPorts Tex Live. +* Performed compatibility testing under lualatex and xelatex + (previously, had only tested with pdflatex). Added documentation + for using these TeX engines; at most, slightly different preambles + are needed. Modified the PythonTeX gallery to support all three + engines. +* Code commands and environments may now be used in the preamble. + This, combined with the new treatment of custom code, allows + PythonTeX to be used in creating LaTeX packages. +* Added documentation for using PythonTeX in LaTeX programming. +* Fixed a bug that sometimes caused incorrect line numbers with + ``stderr`` content. Improved processing of stderr. +* Fixed a bug in automatic detection of pre-existing listings + environment. +* Improved the detection of imports from ``__future__``. Detection + should now be stricter, faster, and more accurate. + + +v0.9beta3 (2012/07/17) +---------------------- + +* Added Unicode support, which required the Python code to be split into + one set for Python 2 and another set for Python 3. This will require + any old installation to be completely removed, and a new installation + created from scratch. +* Refactoring of Python code. Documents should automatically re-execute + all code after updating to the new version. Otherwise, you should delete + the PythonTeX directory and run PythonTeX. +* Improved installation script. +* Added package options: ``pyfuture``, ``stderr``, ``upquote``, + ``pyglexer``, ``pyginline``. Renamed the ``pygextfile`` option to + ``fvextfile``. +* Added custom code and workingdir commands. +* Added the ``console`` environment and associated options. +* Rewrote ``pythontex_utils*.py``, creating a new, context-aware interface to + SymPy's LatexPrinter class. +* Content brought in via macros no longer uses labels. Rather, long defs + are used, which allows line breaks. +* Pygments highlighting is now default for PythonTeX commands and environments. + + +v0.9beta2 (2012/05/09) +---------------------- + +* Changed Python output extension to ``.stdout``. + + +v0.9beta (2012/04/27) +--------------------- + +* Initial public beta release. diff --git a/README.rst b/README.rst index b265863..2854032 100644 --- a/README.rst +++ b/README.rst @@ -2,153 +2,121 @@ PythonTeX =============================================== ------------------------------------------------ - Fast Access to Python from within LaTeX ------------------------------------------------ +------------------------------------------------------------------------------------------ + Execute Python and other code in LaTeX documents, or typeset it with syntax highlighting +------------------------------------------------------------------------------------------ -:Author: Geoffrey Poore +PythonTeX executes code in LaTeX documents and allows the output to be +included in the original document. It supports Python as well as Bash, +JavaScript, Julia, Octave, Perl, R, Raku (Perl 6), Ruby, Rust, and SageMath. +PythonTeX also provides syntax highlighting for typeset code in LaTeX +documents via the `Pygments `_ syntax highlighter. -:Version: 0.10beta2 +See ``pythontex_quickstart.pdf`` to get started, and ``pythontex_gallery.pdf`` +for examples of what is possible with PythonTeX. PythonTeX is included in TeX +Live and MiKTeX and may be installed via the package manager. See +``pythontex.pdf`` for detailed installation instructions if you want to +install the current development version, or just use the installation script +for TeX Live and MiKTeX. -:License: LPPL_ (LaTeX code) and `BSD 3-Clause`_ (Python code) +The ``depythontex`` utility creates a copy of a PythonTeX document in which +all code has been replaced by its output. This plain LaTeX document is +more suitable for journal submission, sharing, or conversion to other document +formats. See ``pythontex_gallery.html`` and the accompanying conversion +script for an example of a PythonTeX document that was converted to HTML via +``depythontex`` and `Pandoc `_. -.. _LPPL: http://www.latex-project.org/lppl.txt -.. _`BSD 3-Clause`: http://www.opensource.org/licenses/BSD-3-Clause +Example +======= + +* LaTeX document ``doc.tex``: + + .. code-block:: latex + + \documentclass{article} + + \usepackage{pythontex} + + \newcommand{\pymultiply}[2]{\py{#1*#2}} + + \begin{document} + + \begin{pycode} + print("Python says ``Hello!''") + \end{pycode} + + $8 \times 256 = \pymultiply{8}{256}$ + + \end{document} + +* Compiling under Windows: + + :: + + pdflatex -interaction=nonstopmode doc.tex + pythontex doc.tex + pdflatex -interaction=nonstopmode doc.tex + + +* Compiling under other operating systems: + + :: + + pdflatex -interaction=nonstopmode doc.tex + pythontex.py doc.tex + pdflatex -interaction=nonstopmode doc.tex -PythonTeX provides fast, user-friendly access to Python from within LaTeX. It allows Python code entered within a LaTeX document to be executed, and the results to be included within the original document. It also provides syntax highlighting for code within LaTeX documents via the Pygments package. - -See ``pythontex.pdf`` for installation instructions. See ``pythontex_gallery.pdf`` for examples of what is possible with PythonTeX. - - -Current status --------------- - -The 0.10beta series will become the first full release with only minor tweaks -by the end of January, unless major bugs are discovered. As soon as the full -release is out, PythonTeX will be submitted to CTAN. - -Upcoming development will focus on ``depythontex``. - - -Version History ---------------- - -v0.10beta2 (2013/01/23) - -* Improved ``pythontex*.py``'s handling of the name of the file being processed. A warning is no longer raised if the name is given with an extension; extensions are now processed (stripped) automatically. The filename may now contain a path to the file, so you need not run ``pythontex*.py`` from within the document's directory. -* Added command-line option ``--verbose`` for more verbose output. Currently, this prints a list of all processes that are launched. -* Fixed a bug that could crash ``pythontex*.py`` when the package option ``pygments=false``. -* Added documentation about ``autoprint`` behavior in the preamble. Summary: ``code`` commands and environments are allowed in the preamble as of v0.10beta. ``autoprint`` only applies to the body of the document, because nothing can be typeset in the preamble. Content printed in the preamble can be brought in by explicitly using ``\printpythontex``, but this should be used with great care. -* Revised ``\stdoutpythontex`` and ``\printpythontex`` so that they work in the preamble. Again, this should be used with great care if at all. -* Revised treatment of any content that custom code attempts to print. Custom code is not allowed to print to the document (see documentation). If custom code attempts to print, a warning is raised, and the printed content is included in the ``pythontex*.py`` run summary. -* One-line entries in stderr, such as those produced by Python's ``warnings.warn()``, were not previously parsed because they are of the form ``::`` rather than ``line ``. These are now parsed and synchronized with the document. They are also correctly parsed for inclusion in the document via ``\stderrpythontex``. -* If the package option ``stderrfilename`` is changed, all sessions that produced errors or warnings are now re-executed automatically, so that their stderr content is properly updated with the new filename. - - -v0.10beta (2013/01/09) - -* Backward-incompatible: Redid treatment of command-line options for - ``pythontex*.py``, using Python's ``argparse`` module. Run - ``pythontex*.py`` with option ``-h`` to see new command line options. -* Deprecated: ``\setpythontexcustomcode`` is deprecated in favor of the - ``\pythontexcustomc`` command and ``pythontexcustomcode`` - environment. These allow entry of pure code, unlike - ``\setpythontexcustomcode``. These also allow custom code to be - added to the beginning or end of a session, via an optional argument. - Improved treatment of errors and warnings associated with custom - code. -* The summary of errors and warnings now correctly differentiates - errors and warnings produced by user code, rather than treating all - of them as errors. By default, ``pythontex*.py`` now returns an - exit code of 1 if there were errors. -* The PythonTeX utilities class now allows external file dependencies - to be specified via ``pytex.add_dependencies()``, so that sessions - are automatically re-executed when external dependencies are - modified (modification is determined via either hash or mtime; this - is governed by the new ``hashdependencies`` option). -* The PythonTeX utilities class now allows created files to be - specified via ``pytex.add_created()``, so that created files may be - automatically cleaned up (deleted) when the code that created them - is modified (for example, name change for a saved plot). -* Added the following package options. - - - ``stdout`` (or ``print``): Allows input of stdout to be disabled. - Useful for debugging. - - ``runall``: Executes everything. Useful when code depends on - external data. - - ``rerun``: Determines when code is re-executed. Code may be set - to always run (same as ``runall`` option), or only run when it is - modified or when it produces errors or warnings. By default, - code is always re-executed if there are errors or modifications, - but not re-executed if there are warnings. - - ``hashdependencies``: Determines whether external dependencies - (data, external code files highlighted with Pygments, etc.) are - checked for modification via hashing or modification time. - Modification time is default for performance reasons. - -* Added the following new command line options. The options that are - equivalent to package options are overridden by the package options - when present. - - - ``--error-exit-code``: Determines whether an exit code of 1 is - returned if there were errors. On by default, but can be turned - off since it is undesirable when working with some editors. - - ``--runall``: Equivalent to new package option. - - ``--rerun``: Equivalent to new package option. - - ``--hashdependencies``: Equivalent to new package option. - -* Modified the ``fixlr`` option, so that it only patches commands if - they have not already been patched (avoids package conflicts). -* Added ``\setpythontexautoprint`` command for toggling autoprint - on/off within the body of the document. -* Installer now attempts to create symlinks under OS X and Linux with - TeX Live, and under OS X with MacPorts Tex Live. -* Performed compatibility testing under lualatex and xelatex - (previously, had only tested with pdflatex). Added documentation - for using these TeX engines; at most, slightly different preambles - are needed. Modified the PythonTeX gallery to support all three - engines. -* Code commands and environments may now be used in the preamble. - This, combined with the new treatment of custom code, allows - PythonTeX to be used in creating LaTeX packages. -* Added documentation for using PythonTeX in LaTeX programming. -* Fixed a bug that sometimes caused incorrect line numbers with - ``stderr`` content. Improved processing of stderr. -* Fixed a bug in automatic detection of pre-existing listings - environment. -* Improved the detection of imports from ``__future__``. Detection - should now be stricter, faster, and more accurate. - - -v0.9beta3 (2012/07/17) - -* Added Unicode support, which required the Python code to be split into - one set for Python 2 and another set for Python 3. This will require - any old installation to be completely removed, and a new installation - created from scratch. -* Refactoring of Python code. Documents should automatically re-execute - all code after updating to the new version. Otherwise, you should delete - the PythonTeX directory and run PythonTeX. -* Improved installation script. -* Added package options: pyfuture, stderr, upquote, pyglexer, pyginline. - Renamed the pygextfile option to fvextfile. -* Added custom code and workingdir commands. -* Added the console environment and associated options. -* Rewrote pythontex_utils*.py, creating a new, context-aware interface to - SymPy's LatexPrinter class. -* Content brought in via macros no longer uses labels. Rather, long defs - are used, which allows line breaks. -* Pygments highlighting is now default for PythonTeX commands and environments. - - -v0.9beta2 (2012/05/09) - -* Changed Python output extension to .stdout. - -v0.9beta (2012/04/27) - -* Initial public beta release. +* Output: + + :: + + Python says “Hello!” + 8 × 256 = 2048 + +Notice that there is a three-step compile process. This is what makes +possible commands like ``\pymultiply`` that use Python or other languages +internally. You may want to configure your LaTeX editor with a shortcut for +running ``pythontex`` or ``pythontex.py``, or configure your LaTeX build +system to run ``pythontex`` or ``pythontex.py``. + + +Citing PythonTeX +================ + +If you use PythonTeX in your writing and research, please consider citing it +in any resulting publications. The best and most recent paper is in +`Computational Science & Discovery `_ +(doi:10.1088/1749-4699/8/1/014010). You may also cite the paper in the +`2013 SciPy proceedings `_. + + +Development status +================== + +Since 2020, I am increasingly creating new teaching materials with Markdown +and HTML instead of PythonTeX and LaTeX. I have limited time for developing +open-source software that I do not use frequently myself. There should be +occasional releases to keep PythonTeX running, but no major changes or +significant new features are anticipated. + +I have been developing `Codebraid `_ since 2019, +partially to have a PythonTeX equivalent for Markdown but also in the hope +that it could eventually be integrated with LaTeX as a PythonTeX replacement. +As part of `minted `_ version 3, I created +new software for passing data between LaTeX and Python. I cannot make any +guarantees, but I hope that this will eventually make it possible to create a +new LaTeX package based on Codebraid, with significant PythonTeX compatibility. + + +License +======= + +LPPL_ for LaTeX code and `BSD 3-Clause`_ for Python code. + +.. _LPPL: http://www.latex-project.org/lppl.txt + +.. _`BSD 3-Clause`: http://www.opensource.org/licenses/BSD-3-Clause diff --git a/package_downloads/old_versions/pythontex_0.10beta.zip b/package_downloads/old_versions/pythontex_0.10beta.zip deleted file mode 100644 index 11d47ff..0000000 Binary files a/package_downloads/old_versions/pythontex_0.10beta.zip and /dev/null differ diff --git a/package_downloads/old_versions/pythontex_0.9beta3.zip b/package_downloads/old_versions/pythontex_0.9beta3.zip deleted file mode 100644 index b8ef46b..0000000 Binary files a/package_downloads/old_versions/pythontex_0.9beta3.zip and /dev/null differ diff --git a/package_downloads/pythontex_0.10beta2.zip b/package_downloads/pythontex_0.10beta2.zip deleted file mode 100644 index 8cb549d..0000000 Binary files a/package_downloads/pythontex_0.10beta2.zip and /dev/null differ diff --git a/pythontex/README b/pythontex/README new file mode 100644 index 0000000..14c2e82 --- /dev/null +++ b/pythontex/README @@ -0,0 +1,70 @@ +=================================================================== + The PythonTeX Package +=================================================================== + +:Author: Geoffrey Poore +:Version: 0.19 (2026/02/15) +:License: LPPL v1.3 or later (LaTeX code) and BSD 3-Clause (Python code) +:Development: https://github.com/gpoore/pythontex +:Requirements: Python 2.7 or 3.2+; Pygments + + +The PythonTeX package allows you to enter Python code within a LaTeX document, +execute the code, and access its output in the original document. It also +supports the Ruby, Julia, Octave, Sage, Bash, Rust, R, Perl, Perl 6, and +JavaScript languages. + +PythonTeX provides fast, user-friendly access to Python from within LaTeX. +Python code is only executed when it has been modified, or when it meets +user-specified criteria. Code may be divided into user-defined sessions, +which automatically run in parallel. Errors and warnings are synchronized +with the LaTeX document, so that they refer to the document's line numbers. +External dependencies can be tracked, so that code is re-executed when the +data it depends on is modified. PythonTeX also provides syntax highlighting +for code in LaTeX documents via the Pygments syntax highlighter. + +PythonTeX includes a "depythontex" utility that creates a copy of a document +in which all Python code has been replaced by its output. This can be +convenient for journal submissions, sharing documents, and converting to +other formats. + +Consult pythontex_quickstart.pdf and pythontex_gallery.pdf to get +started and for examples of what PythonTeX can do. + +Compiling a PythonTeX document involves three steps: + +1. Run latex (all Python code is saved to an auxiliary file) +2. Run pythontex.py (all code is executed) +3. Run latex again (Python output is inputted) + +See the main documentation in pythontex.pdf for detailed installation +instructions. An installation script is provided for TeX Live and MiKTeX. +Files should be installed in the directories indicated below. In summary, +all files must be installed, and symlinks or launching wrappers should be +created for pythontex.py and depythontex.py. + +* /doc/latex/pythontex/ + + - pythontex.pdf + - README + - pythontex_quickstart.tex + - pythontex_quickstart.pdf + - pythontex_gallery.tex + - pythontex_gallery.pdf + +* /scripts/pythontex/ + + - pythontex.py, pythontex2.py, and pythontex3.py + - pythontex_engines.py + - pythontex_utils.py + - depythontex.py, depythontex2.py, and depythontex3.py + - syncpdb.py + +* /source/latex/pythontex/ + + - pythontex.dtx + - pythontex.ins + +* /tex/latex/pythontex/ + + - pythontex.sty diff --git a/pythontex/README.rst b/pythontex/README.rst deleted file mode 100644 index 75f6803..0000000 --- a/pythontex/README.rst +++ /dev/null @@ -1,23 +0,0 @@ -=============================================== - PythonTeX -=============================================== - ------------------------------------------------ - Fast Access to Python from within LaTeX ------------------------------------------------ - - -:Author: Geoffrey Poore - -:Version: 0.10beta2 - -:License: LPPL_ (LaTeX code) and `BSD 3-Clause`_ (Python code) - -.. _LPPL: http://www.latex-project.org/lppl.txt - -.. _`BSD 3-Clause`: http://www.opensource.org/licenses/BSD-3-Clause - - -PythonTeX provides fast, user-friendly access to Python from within LaTeX. It allows Python code entered within a LaTeX document to be executed, and the results to be included within the original document. It also provides syntax highlighting for code within LaTeX documents via the Pygments package. - -See pythontex.pdf for installation instructions. See pythontex_gallery.pdf for examples of what is possible with PythonTeX. diff --git a/pythontex/depythontex.bat b/pythontex/depythontex.bat new file mode 100644 index 0000000..d80ddad --- /dev/null +++ b/pythontex/depythontex.bat @@ -0,0 +1,2 @@ +@echo off +depythontex.py %* diff --git a/pythontex/depythontex.py b/pythontex/depythontex.py new file mode 100644 index 0000000..f015c15 --- /dev/null +++ b/pythontex/depythontex.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +This is the depythontex wrapper script. It automatically detects the version +of Python, and then imports the correct code from depythontex2.py or +depythontex3.py. It is intended for use with the default Python installation +on your system. If you wish to use a different version of Python, you could +launch depythontex2.py or depythontex3.py directly. The version of Python +does not matter for depythontex, since no code is executed. + +Copyright (c) 2013-2026, Geoffrey M. Poore +All rights reserved. +Licensed under the BSD 3-Clause License: + http://www.opensource.org/licenses/BSD-3-Clause + +''' + +import sys +if sys.version_info[0] == 2: + import depythontex2 as depythontex +elif sys.version_info[0] == 3: + import depythontex3 as depythontex diff --git a/pythontex/depythontex2.py b/pythontex/depythontex2.py new file mode 100644 index 0000000..6e5a7f8 --- /dev/null +++ b/pythontex/depythontex2.py @@ -0,0 +1,1427 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +''' +PythonTeX depythontex script. + +This script takes a LaTeX document that uses the PythonTeX package and +creates a new document that does not depend on PythonTeX. It substitutes all +externally generated content into a copy of the original LaTeX document. +This is useful when you need a document that relies on few external packages +or custom macros (for example, for submission to a journal or conversion to +another document format). + +If you just want to share a document that uses PythonTeX, keep in mind that +the document can be modified and compiled just like a regular LaTeX document, +without needing Python or any other external tools, so long as the following +conditions are met: + + * A copy of pythontex.sty is included with the document. + * The pythontex-files- directory is included with the document. + * The PythonTeX-specific parts of the document are not modified. + +To work, this script requires that the original LaTeX document be compiled +with the package option `depythontex`. That creates an auxiliary file with +the extension .depytx that contains information about all content that needs +to be substituted. + +This script is purposely written in a simple, largely linear form to +facilitate customization. Most of the key substitutions are performed by a +few functions defined near the beginning of the script, so if you need custom +substitutions, you should begin there. By default, all typeset code is +wrapped in `\verb` commands and verbatim environments, since these have the +greatest generality. However, the command-line option --listing allows code +to be typeset with the fancyvrb, listings, minted, or PythonTeX packages +instead. + +The script automatically extracts all arguments of all commands and +environments that it replaces, so that these are available if desired for +customized substitution. Two additional pieces of information are also +available for any typeset code: the Pygments lexer (often the same as the +language) and the starting line number (if line numbering was used). + +Keep in mind that some manual adjustments may be required after a document is +depythontex'ed. While depythontex attempts to create an exact copy of the +original document, in many cases an identical copy is impossible. For +example, typeset code may have a different appearance or layout when it is +typeset with a different package. + + +Copyright (c) 2013-2026, Geoffrey M. Poore +All rights reserved. +Licensed under the BSD 3-Clause License: + http://www.opensource.org/licenses/BSD-3-Clause + +''' + + +# Imports +#// Python 2 +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals +#\\ End Python 2 +import sys +import os +#// Python 2 +if sys.version_info.major != 2: + sys.exit('This version of the PythonTeX script requires Python 2.') +#\\ End Python 2 +#// Python 3 +#if sys.version_info.major != 3: +# sys.exit('This version of the PythonTeX script requires Python 3.') +#\\ End Python 3 + +#// Python 2 +from io import open +input = raw_input +#\\ End Python 2 +import argparse +from collections import defaultdict +from re import match, sub, search +import textwrap +import codecs + + +# Script parameters +# Version +__version__ = '0.19' + + +# Functions and parameters for customizing the script output + +# Style or package for replacing code listings +# This is actually set via command-line option --listing +# It is created here simply for reference +listing = None #'verbatim', 'fancyvrb', 'listings', 'minted', 'pythontex' + +# List of things to add to the preamble +# It can be appended to via the command-line option --preamble +# It is also appended to based on the code listing style that is used +# And it could be manually edited here as well, as long as it remains a list +preamble_additions = list() + +# Lexer dict +# If you are using Pygments lexers that don't directly correspond to the +# languages used by the listings package, you can submit replacements via the +# command line option --lexer-dict, or edit this dict manually here. When +# listings is used, all lexers are checked against this dict to see if a +# substitution should be made. This approach could easily be modified to +# work with another, non-Pygments highlighting package. +lexer_dict = dict() + + +def replace_code_cmd(name, arglist, linenum, code_replacement, + code_replacement_mode, after, lexer, firstnumber): + ''' + Typeset code from a command with a command. + + It is only ever called if there is indeed code to typeset. + + Usually, code from a command is also typeset with a command. This + function primarily deals with that case. In cases where code from a + command is typeset with an environment (for example, `\inputpygments`), + this function performs some preprocessing and then uses + replace_code_env() to do the real work. This approach prevents the two + functions from unnecessarily duplicating each other, while still giving + the desired output. + + Args: + name (str): name of the command + arglist (list, of str/None): all arguments given to the original + command; the last argument is what is typeset, unless a + code_replacement is specified or other instructions are given + linenum (int): line number in the original TeX document + code_replacement (str/None): replacement for the code; usually None + for commands, because typically the code to be typeset is the + last argument passed to the command, rather than something + captured elsewhere (like the body of an environment) or something + preprocessed (like a console environment's content) + code_replacement_mode (str/None): mode in which the replacement is + to be typeset; raw/None (as TeX; generally unused for code), + verb (inline), or verbatim (environment) + after (str): text immediately following the command; usually + shouldn't be needed + lexer (str/None): Pygments lexer + Returns: + (replacement, after) (tuple, of str) + + ''' + # Get the correct replacement + if code_replacement is None: + code_replacement = arglist[-1] + + # We only consider two possible modes of typesetting, verbatim and inline + # verbatim + if code_replacement_mode == 'verbatim': + # Sometimes we must replace a command with an environment, for + # example, for `\inputpygments` + + # Make sure the introduction of an environment where a command was + # previously won't produce errors with following content; make sure + # that any following content is on a separate line + if bool(match('[ \t]*\S', after)): + after = '\n' + after + # Rather than duplicating much of replace_code_env(), just use it + return replace_code_env(name, arglist, linenum, code_replacement, + code_replacement_mode, after, lexer, firstnumber) + else: + # Usually, we're replacing a command with a command + + # Wrap the replacement in appropriate delimiters + if (listing in ('verbatim', 'fancyvrb', 'minted') or + (listing in ('listings', 'pythontex') and + ('{' in code_replacement or '}' in code_replacement))): + for delim in ('|', '/', '`', '!', '&', '#', '@', ':', '%', '~', '$', + '=', '+', '-', '^', '_', '?', ';'): + if delim not in code_replacement: + break + code_replacement = delim + code_replacement + delim + else: + code_replacement = '{' + code_replacement + '}' + # Assemble the actual replacement + if listing in ('verbatim', 'minted'): # `\mint` isn't for inline use + code_replacement = r'\verb' + code_replacement + elif listing == 'fancyvrb': + code_replacement = r'\Verb' + code_replacement + elif listing == 'listings': + if lexer is None: + code_replacement = r'\lstinline[language={}]' + code_replacement + else: + if lexer in lexer_dict: + lexer = lexer_dict[lexer] + code_replacement = r'\lstinline[language=' + lexer + ']' + code_replacement + elif listing == 'pythontex': + if lexer is None: + code_replacement = r'\pygment{text}' + code_replacement + else: + code_replacement = r'\pygment{' + lexer + '}' + code_replacement + return (code_replacement, after) + + +def replace_code_env(name, arglist, linenum, code_replacement, + code_replacement_mode, after, lexer, firstnumber): + ''' + Typeset code from an environment with an environment. + + It is only ever called if there is indeed code to typeset. + + Usually it is only used to typeset code from an environment. However, + some commands bring in code that must be typeset as an environment. In + those cases, replace_code_cmd() is called initially, and after it + performs some preprocessing, this function is called. This approach + avoids unnecessary duplication between the two functions. + + Args: + name (str): name of the environment + arglist (list, of str/None): all arguments given to the original + environment + linenum (int): line number in the original TeX document where + the environment began + code_replacement (str): replacement for the code; unlike the case of + commands, this is always not None if the function is called + code_replacement_mode (str/None): mode in which the replacement is + to be typeset; raw/None (as TeX; generally unused for code), + verb (inline), or verbatim (environment) + after (str): text immediately following the environment; usually + shouldn't be needed + lexer (str/None): Pygments lexer + firstnumber (str/None): the first number of the listing, if the listing + had numbered lines + Returns: + (replacement, after) (tuple, of str) + + ''' + # Currently, there is no need to test for code_replacement_mode, because + # this function is only ever called if the mode is 'verbatim'. That may + # change in the future, but it seems unlikely that code entered in an + # environment would end up typeset with a command. + if listing == 'verbatim': + pre = '\\begin{verbatim}' + post = '\\end{verbatim}' + elif listing == 'fancyvrb': + if firstnumber is None: + pre = '\\begin{Verbatim}' + else: + pre = '\\begin{{Verbatim}}[numbers=left,firstnumber={0}]'.format(firstnumber) + post = '\\end{Verbatim}' + elif listing == 'listings': + if lexer is None: + if firstnumber is None: + pre = '\\begin{lstlisting}[language={}]' + else: + pre = '\\begin{{lstlisting}}[language={{}},numbers=left,firstnumber={0}]'.format(firstnumber) + else: + if lexer in lexer_dict: + lexer = lexer_dict[lexer] + if firstnumber is None: + pre = '\\begin{{lstlisting}}[language={0}]'.format(lexer) + else: + pre = '\\begin{{lstlisting}}[language={0},numbers=left,firstnumber={1}]'.format(lexer, firstnumber) + post = '\\end{lstlisting}' + elif listing == 'minted': + if lexer is None: + if firstnumber is None: + pre = '\\begin{minted}{text}' + else: + pre = '\\begin{{minted}}[linenos,firstnumber={0}]{{text}}'.format(firstnumber) + else: + if firstnumber is None: + pre = '\\begin{{minted}}{{{0}}}'.format(lexer) + else: + pre = '\\begin{{minted}}[linenos,firstnumber={0}]{{{1}}}'.format(firstnumber, lexer) + post = '\\end{minted}' + elif listing == 'pythontex': + if lexer is None: + if firstnumber is None: + pre = '\\begin{pygments}{text}' + else: + pre = '\\begin{{pygments}}[numbers=left,firstnumber={0}]{{text}}'.format(firstnumber) + else: + if firstnumber is None: + pre = '\\begin{{pygments}}{{{0}}}'.format(lexer) + else: + pre = '\\begin{{pygments}}[numbers=left,firstnumber={0}]{{{1}}}'.format(firstnumber, lexer) + post = '\\end{pygments}' + code_replacement = pre + code_replacement + post + return (code_replacement, after) + + +# We will need to issue a warning every time that a substitution of printed +# content results in a forced double space. We could just do this as we go, +# but it's easier for the user to read if we just collect all the warnings +# of this type, and print them once. +forced_double_space_list = list() + + +def replace_print_cmd(name, arglist, linenum, + print_replacement, print_replacement_mode, source, + after): + ''' + Typeset printed content from a command. + + It is only ever called if there is indeed printed content to typeset. + + Args: + name (str): name of the command + arglist (list, of str/None): all arguments given to the original + command + linenum (int): line number in the original TeX document + print_replacement (str): printed content, read directly from file + into a single string + print_replacement_mode (str/None): mode in which the replacement is + to be typeset; raw/None (as TeX), inlineverb (or v) (as inline), + or verb (as environment) + source (str/None): source of the replacement content + after (str): text immediately following the command; important in + some situations, because spacing can depend on what's next + Returns: + (replacement, after) (tuple, of str) + + ''' + if print_replacement_mode == 'verb': + if print_replacement.count('\n') > 1: + print('* DePythonTeX error:') + print(' Attempt to print multiple lines of content near line ' + str(linenum)) + print(' This is not possible in inline verbatim mode') + sys.exit(1) + print_replacement = print_replacement.rstrip('\n') + for delim in ('|', '/', '`', '!', '&', '#', '@', ':', '%', '~', '$', + '=', '+', '-', '^', '_', '?', ';'): + if delim not in print_replacement: + break + print_replacement = r'\verb' + delim + print_replacement + delim + elif print_replacement_mode == 'verbatim': + if bool(match('\s*?\n', after)): + # Usually, we would end the verbatim environment with a newline. + # This is fine if there is content in `after` before the next + # newline---in fact, it's desirable, because the verbatim package + # doesn't allow for content on the same line as the end of the + # environment. But if `after` is an empty line, then adding a + # newline will throw off spacing and must be avoided + print_replacement = '\\begin{verbatim}\n' + print_replacement + '\\end{verbatim}' + else: + print_replacement = '\\begin{verbatim}\n' + print_replacement + '\\end{verbatim}\n' + else: + # When printed content from a file is included as LaTeX code, we have + # to be particularly careful to ensure that the content produces the + # same output when substituted as when brought in by `\input`. In + # particular, `\input` strips newlines from each line of content and + # adds a space at the end of each line. This space is inside the + # `\input`, so it will not merge with following spaces. So when we + # substitute the content, sometimes we need to replace the final + # newline with a space that cannot be gobbled. + # + # It gets more complicated. This final space is often not + # desirable. It can be prevented by either printing an `\endinput` + # command, to terminate the `\input`, or printing a percent + # character % in the last line of the content, which comments out the + # final newline. So we must check for `\endinput` anywhere in + # printed content, and % in the final line, and remove any content + # after them. It's also possible that the print is followed by + # an `\unskip` that eats the space, so we need to check for that too. + # + # It turns out that the same approach is needed when a command like + # `\py` brings in content ending in a newline + if (print_replacement.endswith('\\endinput\n') and + not print_replacement.endswith('\\string\\endinput\n')): + # If `\endinput` is present, everything from it on should be + # discarded, unless the `\endinput` is not actually a command + # but rather a typeset name (for example, `\string\endinput` or + # `\verb|\endinput|`). It's impossible to check for all cases in + # which `\endinput` is not a command (at least, without actually + # using LaTeX), and even checking for most of them would require + # a good bit of parsing. We assume that `\endinput`, as a + # command, will only ever occur at the immediate end of the + # printed content. Later, we issue a warning in case it appears + # anywhere else. + print_replacement = print_replacement.rsplit(r'\endinput', 1)[0] + elif (print_replacement.endswith('%\n') and + not print_replacement.endswith('\\%\n') and + not print_replacement.endswith('\\string%\n')): + # Perform an analogous check for a terminating percent characer %. + # This case would be a bit easier to parse fully, since a percent + # that comments out the last newline would have to be in the + # final line of the replacement. But it would still be + # very difficult to perform a complete check. Later, we issue a + # warning if there is reason to think that a percent character + # was active in the last line. + print_replacement = print_replacement.rsplit(r'%', 1)[0] + elif print_replacement.endswith('\n'): + # We can't just use `else` because that would catch content + # from `\py` and similar + # By default, LaTeX strips newlines and adds a space at the end + # of each line of content that is brought in by `\input`. This + # may or may not be desirable, but we replicate the effect here + # for consistency with the original document. We use `\space{}` + # because plain `\space` would gobble a following space, which + # isn't consistent with the `\input` behavior being replicated. + if bool(match(r'\\unskip\s+\S', after)): + # If there's an `\unskip`, fix the spacing and remove the + # `\unskip`. Since this is inline, the `\unskip` must + # immediately follow the command to do any good; otherwise, + # it eliminates spaces that precede it, but doesn't get into + # the `\input` content. + print_replacement = print_replacement.rstrip(' \t\n') + after = sub(r'^\\unskip\s+', '', after) + elif bool(match('\S', after)): + # If the next character is not whitespace, we can just leave + # the `\n`, and it will yield a space. + pass + elif bool(match('\s*$', after)): + # If the rest of the current line, and the next line, are + # whitespace, we will get the correct spacing without needing + # `\space{}`. We could leave `\n`, but it would be + # extraneous whitespace. + print_replacement = print_replacement[:-1] + else: + # Otherwise, we do need to insert `\space{}` + # We keep the newline at the end of printed content, in case + # it's at the end of an environment, and thus is needed to + # protect the following content + print_replacement += '\\space{}' + after = sub('^\s+', '', after) + forced_double_space_list.append((name, linenum)) + else: + if bool(match('\s+\S', after)): + # If the following line starts with whitespace, replace it + # with a newline, to protect in the event that the printed + # content ended with an end-of-environment command + after = sub('^\s+', '\n', after) + # Issue warnings, if warranted + # Warn about `\endinput` + if (r'\endinput' in print_replacement and + print_replacement.count(r'\endinput') != print_replacement.count(r'\string\endinput')): + print('* DePythonTeX warning:') + print(' "\\endinput" was present in printed content near line ' + str(linenum)) + print(' If this "\\endinput" was verbatim, you have nothing to worry about') + print(' If this "\\endinput" is to be active, it should be printed last') + print(' If you need "\\endinput" elsewhere, customize depythontex.py') + # Warn if it looks like there are active `%` that could comment + # out part of the original document. We only need to check the + # last line of printed content, because only there could + # percent characters escape from their original confines within + # `\input`, and comment out part of the document. + if print_replacement.endswith('\n'): + if print_replacement.count('\n') > 1: + last_line = print_replacement.rsplit('\n', 2)[1] + else: + last_line = print_replacement + if last_line.count('%') != last_line.count(r'\%') + last_line.count(r'\string%'): + print('* DePythonTeX warning:') + print(' One or more percent characters are on the last line of ') + print(' printed content near line ' + str(linenum)) + print(' If they are not verbatim, they could comment out the following newline') + else: + if '\n' in print_replacement: + last_line = print_replacement.rsplit('\n', 1)[1] + else: + last_line = print_replacement + if last_line.count('%') != last_line.count(r'\%') + last_line.count(r'\string%'): + print('* DePythonTeX warning:') + print(' One or more percent characters are on the last line of ') + print(' printed content near line ' + str(linenum)) + print(' If they are not verbatim, they could comment out the following text') + # Warn if there were `\unskip`'s in the output, in case they weren't + # positioned correctly + if bool(match(r'\s*\\unskip', after)): + print('* DePythonTeX warning:') + print(' "\\unskip" survived substitution near line ' + str(linenum)) + print(' If it should have adjusted the spacing of printed content') + print(' you should double-check the spacing') + return (print_replacement, after) + + +def replace_print_env(name, arglist, linenum, + print_replacement, print_replacement_mode, source, + after): + ''' + Typeset printed content from an environment. + + It is only ever called if there is indeed printed content to typeset. + + This should be similar to replace_print_cmd(). The main difference is + that the environment context typically ends with a newline, so + substitution has to be a little different to ensure that spacing after + the environment isn't modified. + + Args: + name (str): name of the environment + arglist (list, of str/None): all arguments given to the original + environment + linenum (int): line number in the original TeX document where the + environment began + print_replacement (str): printed content, read directly from file + into a single string + print_replacement_mode (str/None): mode in which the replacement is + to be typeset; raw/None (as TeX), inlineverb (or v) (as inline), + or verb (as environment) + source (str/None): source of the replacement content + after (str): text immediately following the command; important in + some situations, because spacing can depend on what's next + Returns: + (replacement, after) (tuple, of str) + + #### The inlineverb and verb modes should work, but haven't been tested + since there are currently no environments that use them; they are only + used by `\printpythontex`, which is a command. + ''' + if print_replacement_mode == 'verb': + if print_replacement.count('\n') > 1: + print('* DePythonTeX error:') + print(' Attempt to print multiple lines of content near line ' + str(linenum)) + print(' This is not possible in inline verbatim mode') + sys.exit(1) + print_replacement = print_replacement.rstrip('\n') + for delim in ('|', '/', '`', '!', '&', '#', '@', ':', '%', '~', '$', + '=', '+', '-', '^', '_', '?', ';'): + if delim not in print_replacement: + break + print_replacement = r'\verb' + delim + print_replacement + delim + if not bool(match('[ \t]+\S', after)): + # If there is text on the same line as the end of the + # environment, we're fine (this is unusual). Otherwise, + # we need to toss the newline at the end of the environment + # and gobble leading spaces. Leading spaces need to be + # gobbled because previously they were at the beginning of a + # line, where they would have been discarded. + if not bool(match('\s*$', after)): + after = sub('^\s*?\n\s*', '', after) + elif print_replacement_mode == 'verbatim': + if bool(match('\s*?\n', after)): + # Usually, we would end the verbatim environment with a newline. + # This is fine if there is content in `after` before the next + # newline---in fact, it's desirable, because the verbatim package + # doesn't allow for content on the same line as the end of the + # environment. But if `after` is an empty line, then adding a + # newline will throw off spacing and must be avoided + print_replacement = '\\begin{verbatim}\n' + print_replacement + '\\end{verbatim}' + else: + print_replacement = '\\begin{verbatim}\n' + print_replacement + '\\end{verbatim}\n' + else: + # When printed content is included as LaTeX code, we have to be + # particularly careful to ensure that the content produces the same + # output when substituted as when brought in by `\input`. In + # particular, `\input` strips newlines from each line of content and + # adds a space at the end of each line. This space is inside the + # `\input`, so it will not merge with following spaces. So when we + # substitute the content, sometimes we need to replace the final + # newline with a space that cannot be gobbled. + # + # It gets more complicated. This final space is often not + # desirable. It can be prevented by either printing an `\endinput` + # command, to terminate the `\input`, or printing a percent + # character % in the last line of the content, which comments out the + # final newline. So we must check for `\endinput` anywhere in + # printed content, and % in the final line, and remove any content + # after them. It's also possible that the print is followed by + # an `\unskip` that eats the space, so we need to check for that too. + if (print_replacement.endswith('\\endinput\n') and + not print_replacement.endswith('\\string\\endinput\n')): + # If `\endinput` is present, everything from it on should be + # discarded, unless the `\endinput` is not actually a command + # but rather a typeset name (for example, `\string\endinput` or + # `\verb|\endinput|`). It's impossible to check for all cases in + # which `\endinput` is not a command (at least, without actually + # using LaTeX), and even checking for most of them would require + # a good bit of parsing. We assume that `\endinput`, as a + # command, will only ever occur at the immediate end of the + # printed content. Later, we issue a warning in case it appears + # anywhere else. + print_replacement = print_replacement.rsplit(r'\endinput', 1)[0] + if not bool(match('[ \t]+\S', after)): + # If there is text on the same line as the end of the + # environment, we're fine (this is unusual). Otherwise, + # we need to toss the newline at the end of the environment + # and gobble leading spaces. Leading spaces need to be + # gobbled because previously they were at the beginning of a + # line, where they would have been discarded. + if not bool(match('\s*$', after)): + after = sub('^\s*?\n\s*', '', after) + elif (print_replacement.endswith('%\n') and + not print_replacement.endswith('\\%\n') and + not print_replacement.endswith('\\string%\n')): + # Perform an analogous check for a terminating percent characer %. + # This case would be a bit easier to parse fully, since a percent + # that comments out the last newline would have to be in the + # final line of the replacement. But it would still be + # very difficult to perform a complete check. Later, we issue a + # warning if there is reason to think that a percent character + # was active in the last line. + print_replacement = print_replacement.rsplit(r'%', 1)[0] + if not bool(match('[ \t]+\S', after)): + # If there is text on the same line as the end of the + # environment, we're fine (this is unusual). Otherwise, + # we need to toss the newline at the end of the environment + # and gobble leading spaces. Leading spaces need to be + # gobbled because previously they were at the beginning of a + # line, where they would have been discarded. + if not bool(match('\s*$', after)): + after = sub('^\s*?\n\s*', '', after) + else: + # By default, LaTeX strips newlines and adds a space at the end + # of each line of content that is brought in by `\input`. This + # may or may not be desirable, but we replicate the effect here + # for consistency with the original document. We use `\space{}` + # because plain `\space` would gobble a following space, which + # isn't consistent with the `\input` behavior being replicated. + if bool(match(r'\s*\\unskip\s+\S', after)): + # If there's an `\unskip`, fix the spacing and remove the + # `\unskip` + print_replacement = print_replacement.rstrip(' \t\n') + after = sub(r'^\s*\\unskip\s+', '', after) + elif bool(match('[ \t]+\S', after)): + # If the next character after the end of the environment is + # not whitespace (usually not allowed), we can just leave + # the `\n` in printed content, and it will yield a space. + # So we need do nothing. But if there is text on that line + # we need `\space{}`. + after = sub('^\s+', '\\space', after) + forced_double_space_list.append((name, linenum)) + else: + # If the line at the end of the environment is blank, + # we can just discard it and keep the newline at the end of + # the printed content; the newline gives us the needed space + after = after.split('\n', 1)[1] + # Issue warnings, if warranted + # Warn about `\endinput` + if (r'\endinput' in print_replacement and + print_replacement.count(r'\endinput') != print_replacement.count(r'\string\endinput')): + print('* DePythonTeX warning:') + print(' "\\endinput" was present in printed content near line ' + str(linenum)) + print(' If this "\\endinput" was verbatim, you have nothing to worry about') + print(' If this "\\endinput" is to be active, it should be printed last') + print(' If you need "\\endinput" elsewhere, customize depythontex.py') + # Warn if it looks like there are active `%` that could comment + # out part of the original document. We only need to check the + # last line of printed content, because only there could + # percent characters escape from their original confines within + # `\input`, and comment out part of the document. + if print_replacement.endswith('\n'): + if print_replacement.count('\n') > 1: + last_line = print_replacement.rsplit('\n', 2)[1] + else: + last_line = print_replacement + if last_line.count('%') != last_line.count(r'\%') + last_line.count(r'\string%'): + print('* DePythonTeX warning:') + print(' One or more percent characters are on the last line of ') + print(' printed content near line ' + str(linenum)) + print(' If they are not verbatim, they could comment out the following newline') + else: + if '\n' in print_replacement: + last_line = print_replacement.rsplit('\n', 1)[1] + else: + last_line = print_replacement + if last_line.count('%') != last_line.count(r'\%') + last_line.count(r'\string%'): + print('* DePythonTeX warning:') + print(' One or more percent characters are on the last line of ') + print(' printed content near line ' + str(linenum)) + print(' If they are not verbatim, they could comment out the following text') + # Warn if there were `\unskip`'s in the output, in case they weren't + # positioned correctly + if bool(match(r'\s*\\unskip', after)): + print('* DePythonTeX warning:') + print(' "\\unskip" survived substitution near line ' + str(linenum)) + print(' If it should have adjusted the spacing of printed content') + print(' you should double-check the spacing') + return (print_replacement, after) + + + + +# Deal with argv +# Parse argv +parser = argparse.ArgumentParser() +parser.add_argument('--version', action='version', + version='DePythonTeX {0}'.format(__version__)) +parser.add_argument('--encoding', default='utf-8', + help='encoding for all text files (see codecs module for encodings)') +parser.add_argument('--overwrite', default=False, action='store_true', + help='overwrite existing output, if it exists (off by default)') +parser.add_argument('--listing', default='verbatim', + choices=('verbatim', 'fancyvrb', 'listings', 'minted', 'pythontex'), + help='style or package used for typesetting code') +parser.add_argument('--lexer-dict', default=None, + help='add mappings from Pygments lexer names to the language names of other highlighting packages; should be a comma-separated list of the form ":, :, ..."') +parser.add_argument('--preamble', default=None, + help='line of commands to add to output preamble') +parser.add_argument('--graphicspath', default=False, action='store_true', + help=r'Add the outputdir to the graphics path, by modifying an existing \graphicspath command or adding one.') +parser.add_argument('-o', '--output', default=None, + help='output file') +parser.add_argument('TEXNAME', + help='LaTeX file') +args = parser.parse_args() + +# Process argv +encoding = args.encoding +listing = args.listing +if args.preamble is not None: + preamble_additions.append(args.preamble) +if args.lexer_dict is not None: + args.lexer_dict = args.lexer_dict.replace(' ', '').replace("'", "").replace('"','').strip('{}') + for entry in args.lexer_dict.split(','): + k, v = entry.split(':') + lexer_dict[k] = v +if args.listing == 'verbatim': + # In some contexts, the verbatim package might be desirable. + # But we assume that the user wants minimal packages. + # Also, the default verbatim environment doesn't allow text to follow the + # end-of-environment command. + # If the verbatim package is ever desired, simply uncomment the following: + # preamble_additions.append('\\usepackage{verbatim}') + pass +elif args.listing == 'fancyvrb': + preamble_additions.append('\\usepackage{fancyvrb}') +elif args.listing == 'listings': + preamble_additions.append('\\usepackage{listings}') +elif args.listing == 'minted': + preamble_additions.append('\\usepackage{minted}') +elif args.listing == 'pythontex': + preamble_additions.append('\\usepackage{pythontex}') + + + + +# Let the user know things have started +if args.output is not None: + print('This is DePythonTeX {0}'.format(__version__)) + sys.stdout.flush() + + + + +# Make sure we have a valid texfile +texfile_name = os.path.expanduser(os.path.normcase(args.TEXNAME)) +if not os.path.isfile(texfile_name): + resolved = False + if not texfile_name.endswith('.tex'): + for ext in ('.tex', '.ltx', '.dtx'): + if os.path.isfile(texfile_name + ext): + texfile_name = texfile_name + ext + resolved = True + break + if not resolved: + print('* DePythonTeX error:') + print(' Could not locate file "' + texfile_name + '"') + sys.exit(1) +# Make sure we have a valid outfile +if args.output is not None: + outfile_name = os.path.expanduser(os.path.normcase(args.output)) + if not args.overwrite and os.path.isfile(outfile_name): + print('* DePythonTeX warning:') + print(' Output file "' + outfile_name + '" already exists') + ans = input(' Do you want to overwrite this file? [y,n]\n ') + if ans != 'y': + sys.exit(1) +# Make sure the .depytx file exists +depytxfile_name = texfile_name.rsplit('.')[0] + '.depytx' +if not os.path.isfile(depytxfile_name): + print('* DePythonTeX error:') + print(' Could not find DePythonTeX auxiliary file "' + depytxfile_name + '"') + print(' Use package option depythontex to create it') + sys.exit(1) + + + + +# Start opening files and loading data +# Read in the LaTeX file +# We read into a list with an empty first entry, so that we don't have to +# worry about zero indexing when comparing list index to file line number +f = open(texfile_name, 'r', encoding=encoding) +tex = [''] +tex.extend(f.readlines()) +f.close() +# Load the .depytx +f = open(depytxfile_name, 'r', encoding=encoding) +depytx = f.readlines() +f.close() +# Process the .depytx by getting the settings contained in the last few lines +settings = dict() +n = len(depytx) - 1 +while depytx[n].startswith('=>DEPYTHONTEX:SETTINGS#'): + content = depytx[n].split('#', 1)[1].rsplit('#', 1)[0] + k, v = content.split('=', 1) + if v in ('true', 'True'): + v = True + elif v in ('false', 'False'): + v = False + settings[k] = v + depytx[n] = '' + n -= 1 +# Check .depytx version to make sure it is compatible +if settings['version'] != __version__: + print('* DePythonTeX warning:') + print(' Version mismatch with DePythonTeX auxiliary file') + print(' Do a complete compile cycle to update the auxiliary file') + print(' Attempting to proceed') +# Go ahead and open the outfile, even though we don't need it until the end +# This lets us change working directories for convenience without worrying +# about having to modify the outfile path +if args.output is not None: + outfile = open(outfile_name, 'w', encoding=encoding) + + + + +# Change working directory to the document directory +# Technically, we could get by without this, but that would require a lot of +# path modification. This way, we can just use all paths straight out of the +# .depytx without any modification, which is much simpler and less error-prone. +if os.path.split(texfile_name)[0] != '': + os.chdir(os.path.split(texfile_name)[0]) + + + + +# Open and process the file of macros +# Read in the macros +if os.path.isfile(os.path.expanduser(os.path.normcase(settings['macrofile']))): + f = open(os.path.expanduser(os.path.normcase(settings['macrofile'])), 'r', encoding=encoding) + macros = f.readlines() + f.close() +else: + print('* DePythonTeX error:') + print(' The macro file could not be found:') + print(' "' + settings['macrofile'] + '"') + print(' Run PythonTeX to create it') + sys.exit(1) +# Create a dict for storing macros +macrodict = defaultdict(list) +# Create variables for keeping track of whether we're inside a macro or +# environment +# These must exist before we begin processing +inside_macro = False +inside_environment = False +# Loop through the macros, and extract everything +# We just extract content; we get content wrappers later, when we process all +# substituted content +for line in macros: + if inside_macro: + # If we're in a macro, look for the end-of-macro command + if r'\endpytx@SVMCR' in line: + # If the current line contains the end-of-macro command, split + # off any content that comes before it. Also reset + # `inside_macro`. + macrodict[current_macro].append(line.rsplit(r'\endpytx@SVMCR', 1)[0]) + inside_macro = False + else: + # If the current line doesn't end the macro, we add the whole + # line to the macro dict + macrodict[current_macro].append(line) + elif inside_environment: + if line.startswith(end_environment): + # If the environment is ending, we reset inside_environment + inside_environment = False + else: + # If we're still in the environment, add the current line to the + # macro dict + macrodict[current_macro].append(line) + else: + # If we're not in a macro or environment, we need to figure out which + # we are dealing with (if either; there are blank lines in the macro + # file to increase readability). Once we've determined which one, + # we need to get its name and extract any content. + if line.startswith(r'\begin{'): + # Any \begin will indicate a use of fancyvrb to save verbatim + # content, since that is the only time an environment is used in + # the macro file. All other content is saved in a standard macro. + # We extract the name of the macro in which the verbatim content + # is saved. + current_macro = line.rsplit('{', 1)[1].rstrip('}\n') + inside_environment = True + # We assemble the end-of-environment string we will need to look + # for. We don't assume any particular name, for generality. + end_environment = r'\end{' + line.split('}', 1)[0].split('{', 1)[1] + '}' + # Code typset in an environment needs to have a leading newline, + # because the content of a normal verbatim environment keeps its + # leading newline. + macrodict[current_macro].append('\n') + elif line.startswith(r'\pytx@SVMCR{'): + # Any regular macro will use `\pytx@SVMCR` + current_macro = line.split('{', 1)[1].split('}', 1)[0] + inside_macro = True + # Any content will always be on the next line, so we don't need + # to check for it + + + + +# Do the actual processing +# Create a variable for keeping track of the current line in the LaTeX file +# Start at 1, since the first entry in the tex list is `''` +texlinenum = 1 +# Create a variable for storing the current line(s) we are processing. +# This contains all lines from immediately after the last successfully +# processed line up to and including texlinenum. We may have to process +# multiple lines at once if a macro is split over multiple lines, etc. +texcontent = tex[texlinenum] +# Create a list for storing processed content. +texout = list() +# Loop through the depytx and process +for n, depytxline in enumerate(depytx): + if depytxline.startswith('=>DEPYTHONTEX#'): + # Process info + depytxcontent = depytxline.split('#', 1)[1].rstrip('#\n') + depy_type, depy_name, depy_args, depy_typeset, depy_linenum, depy_lexer = depytxcontent.split(':') + if depy_lexer == '': + depy_lexer = None + + # Do a quick check on validity of info + # #### Eventually add 'cp' and 'pc' + if not (depy_type in ('cmd', 'env') and + all([letter in ('o', 'm', 'v', 'n', '|') for letter in depy_args]) and + ('|' not in depy_args or (depy_args.count('|') == 1 and depy_args.endswith('|'))) and + depy_typeset in ('c', 'p', 'n')): + print('* PythonTeX error:') + print(' Invalid \\Depythontex string for operation on line ' + str(depy_linenum)) + print(' The offending string was ' + depytxcontent) + sys.exit(1) + # If depy_args contains a `|` to indicate `\obeylines`, strip it and + # store in a variable. Create a bool to keep track of obeylines + # status, which governs whether we can look on the next line for + # arguments. (If obeylines is active, a newline terminates the + # argument search.) + if depy_args.endswith('|'): + obeylines = True + depy_args = depy_args.rstrip('|') + else: + obeylines = False + # Get the line number as an integer + # We don't have to adjust for zero indexing in tex + depy_linenum = int(depy_linenum) + + + # Check for information passed from LaTeX + # This will be extra listings information, or replacements to plug in + code_replacement = None + code_replacement_mode = None + print_replacement = None + print_replacement_mode = None + firstnumber = None + source = None + scan_ahead_line = n + 1 + nextdepytxline = depytx[scan_ahead_line] + while not nextdepytxline.startswith('=>DEPYTHONTEX#'): + if nextdepytxline.startswith('LISTING:'): + listingcontent = nextdepytxline.split(':', 1)[1].rstrip('\n') + if bool(match(r'firstnumber=\d+$', listingcontent)): + firstnumber = listingcontent.split('=', 1)[1] + else: + print('* DePythonTeX error:') + print(' Unknown information in listings data on line ' + str(depy_linenum)) + print(' The listings content was "' + listingcontent + '"') + sys.exit(1) + elif nextdepytxline.startswith('MACRO:'): + source = 'macro' + try: + typeset, macro = nextdepytxline.rstrip('\n').split(':', 2)[1:] + except: + print('* DePythonTeX error:') + print(' Improperly formatted macro information on line ' + str(depy_linenum)) + print(' The macro information was "' + nextdepytxline + '"') + sys.exit(1) + if macro not in macrodict: + print('* DePythonTeX error:') + print(' Could not find replacement content for macro "' + macro + '"') + print(' This is probably because the document needs to be recompiled') + sys.exit(1) + if typeset == 'c': + if depy_type == 'cmd': + code_replacement = ''.join(macrodict[macro]).strip('\n') + else: + code_replacement = ''.join(macrodict[macro]) + elif typeset == 'p': + print_replacement = ''.join(macrodict[macro]) + else: + print('* DePythonTeX error:') + print(' Improper typesetting information for macro information on line ' + str(depy_linenum)) + print(' The macro information was "' + nextdepytxline + '"') + sys.exit(1) + elif nextdepytxline.startswith('FILE:'): + source = 'file' + try: + typeset, f_name = nextdepytxline.rstrip('\n').split(':', 2)[1:] + except: + print('* DePythonTeX error:') + print(' Improperly formatted file information on line ' + str(depy_linenum)) + print(' The file information was "' + nextdepytxline + '"') + sys.exit(1) + # Files that are brought in have an optional mode that + # determines if they need special handling (for example, verbatim) + if ':mode=' in f_name: + f_name, mode = f_name.split(':mode=') + else: + mode = None + f = open(os.path.expanduser(os.path.normcase(f_name)), 'r', encoding=encoding) + replacement = f.read() + f.close() + if typeset == 'c': + code_replacement_mode = mode + if depy_type == 'cmd' and code_replacement_mode != 'verbatim': + # Usually, code from commands is typeset with commands + # and code from environments is typeset in + # environments. The except is code from commands + # that bring in external files, like `\inputpygments` + code_replacement = replacement + else: + # If we're replacing an environment of code with a + # file, then we lose the newline at the beginning + # of the environment, and need to get it back. + code_replacement = '\n' + replacement + elif typeset == 'p': + print_replacement_mode = mode + print_replacement = replacement + else: + print('* DePythonTeX error:') + print(' Improper typesetting information for file information on line ' + str(depy_linenum)) + print(' The file information was "' + nextdepytxline + '"') + sys.exit(1) + # Increment the line in depytx to check for more information + # from LaTeX + scan_ahead_line += 1 + if scan_ahead_line == len(depytx): + break + else: + nextdepytxline = depytx[scan_ahead_line] + + + # If the line we're looking for is within the range currently held by + # texcontent, do nothing. Otherwise, transfer content from tex + # to texout until we get to the line of tex that we're looking for + if depy_linenum > texlinenum: + texout.append(texcontent) + texlinenum += 1 + while texlinenum < depy_linenum: + texout.append(tex[texlinenum]) + texlinenum += 1 + texcontent = tex[texlinenum] + + + # Deal with arguments + # All arguments are parsed and stored in a list variables, even if + # they are not used, for completeness; this makes it easy to add + # functionality + # Start by splitting the current line into what comes before the + # command or environment, and what is after it + if depy_type == 'cmd': + try: + before, after = texcontent.split('\\' + depy_name, 1) + except: + print('* DePythonTeX error:') + print(' Could not find command "' + depy_name + '" on line ' + str(depy_linenum)) + sys.exit(1) + else: # depy_type == 'env': + try: + before, after = texcontent.split(r'\begin{' + depy_name + '}', 1) + except: + print('* DePythonTeX error:') + print(' Could not find environment "' + depy_name + '" on line ' + str(depy_linenum)) + sys.exit(1) + # We won't need the content from before the command or environment + # again, so we go ahead and store it + texout.append(before) + + # Parse the arguments + # Create a list for storing the recovered arguments + arglist = list() + for argindex, arg in enumerate(depy_args): + if arg == 'n': + pass + elif arg == 'o': + if after[0] == '[': + # Account for possible line breaks before end of arg + while ']' not in after: + texlinenum += 1 + after += tex[texlinenum] + optarg, after = after[1:].split(']', 1) + else: + if obeylines: + # Take into account possible whitespace before arg + if bool(match('[ \t]*\[', after)): + after = after.split('[', 1)[1] + while ']' not in after: + texlinenum += 1 + after += tex[texlinenum] + optarg, after = after.split(']', 1) + else: + optarg = None + # If this is the last arg, and it wasn't found, + # the macro should eat all whitespace following it + if argindex == len(depy_args) - 1: + after = sub('^[ \t]*', '', after) + else: + # Allow peeking ahead a line for the argument + if bool(match('\s*$', after)) and after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + # Take into account possible whitespace before arg + if bool(match('\s*\[', after)): + after = after.split('[', 1)[1] + while ']' not in after: + texlinenum += 1 + after += tex[texlinenum] + optarg, after = after.split(']', 1) + else: + optarg = None + # Account for eating whitespace afterward, if arg not found + if argindex == len(depy_args) - 1: + if bool(match('\s*$', after)) and after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + if not bool(match('\s*$', after)): + after = sub('^\s*', '', after) + arglist.append(optarg) + elif arg == 'm': + # Account for possible line breaks or spaces before arg + if after[0] == '{': + after = after[1:] + else: + if obeylines: + # Account for possible leading whitespace + if bool(match('[ \t\f\v]*\{', after)): + after = after.split('{', 1)[1] + else: + print('* DePythonTeX error:') + print(' Flawed mandatory argument for "' + depy_name + '" on line ' + str(depy_linenum)) + sys.exit(1) + else: + # Peek ahead a line if needed + if bool(match('\s*$', after)) and after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + if bool(match('\s*\{', after)): + after = after.split('{', 1)[1] + else: + print('* DePythonTeX error:') + print(' Flawed mandatory argument for "' + depy_name + '" on line ' + str(depy_linenum)) + sys.exit(1) + # Go through the argument character by character to find the + # closing brace. + # If possible, use a very simple approach + if (r'\{' not in after and r'\}' not in after and + r'\string' not in after and + after.count('{') + 1 == after.count('}')): + pos = 0 + lbraces = 1 + rbraces = 0 + while True: + if after[pos] == '{': + lbraces += 1 + elif after[pos] == '}': + rbraces += 1 + if lbraces == rbraces: + break + pos += 1 + if pos == len(after): + texlinenum += 1 + after += tex[texlinenum] + # If a simple parsing approach won't work, parse in much + # greater depth + else: + pos = 0 + lbraces = 1 + rbraces = 0 + while True: + if after[pos] == '{': + # If the current character is a brace, we count it + lbraces += 1 + if lbraces == rbraces: + break + pos += 1 + elif after[pos] == '}': + # If the current character is a brace, we count it + rbraces += 1 + if lbraces == rbraces: + break + pos += 1 + elif after[pos:].startswith(r'\string'): + # If the current position marks the beginning of `\string`, we + # resolve the `\string` command + # First, jump ahead to after `\string` + pos += 7 #+= len(r'\string') + # See if `\string` is followed by a regular macro + # If so, jump past it; otherwise, figure out if a + # single-character macro, or just a single character, is next, + # and jump past it + standard_macro = match(r'\\[a-zA-Z]+', line[pos:]) + if bool(standard_macro): + pos += standard_macro.end() + elif line[pos] == '\\': + pos += 2 + else: + pos += 1 + elif line[pos] == '\\': + # If the current position is a backslash, figure out what + # macro is used, and jump past it + # The macro must either be a standard alphabetic macro, + # or a single-character macro + standard_macro = match(r'\\[a-zA-Z]+', line[pos:]) + if bool(standard_macro): + pos += standard_macro.end() + else: + pos += 2 + else: + pos += 1 + if pos == len(after): + texlinenum += 1 + after += tex[texlinenum] + mainarg = after[:pos] + after = after[pos+1:] + arglist.append(mainarg) + elif arg == 'v': + if after[0] == '{': + # Account for the possibility of matched brace delimiters + # Not all verbatim commands allow for these + pos = 1 + lbraces = 1 + rbraces = 0 + while True: + if after[pos] == '{': + lbraces += 1 + elif after[pos] == '}': + rbraces += 1 + if lbraces == rbraces: + break + pos += 1 + if pos == len(after): + texlinenum += 1 + after += tex[texlinenum] + mainarg = after[1:pos] + after = after[pos+1:] + else: + # Deal with matched character delims + delim = after[0] + while after.count(delim) < 2: + texlinenum += 1 + after += tex[texlinenum] + mainarg, after = after[1:].split(delim, 1) + arglist.append(mainarg) + + + # Do substitution, depending on what is required + # Need a variable for processed content to be added to texout + processed = None + if depy_typeset == 'c': + if depy_type == 'cmd': + # Make sure that `after` contains two lines of content + # This is needed for some replacements that need to look ahead + if after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + processed, texcontent = replace_code_cmd(depy_name, arglist, + depy_linenum, + code_replacement, + code_replacement_mode, + after, depy_lexer, + firstnumber) + else: # depy_type == 'env' + end_environment = r'\end{' + depy_name + '}' + if code_replacement is None: + if end_environment not in after: + while True: + texlinenum += 1 + after += tex[texlinenum] + if end_environment in tex[texlinenum]: + break + code_replacement, after = after.split(end_environment, 1) + # If there's content on the line with the end-environment + # command, it should be discarded, to imitate TeX + if not code_replacement.endswith('\n'): + code_replacement = code_replacement.rsplit('\n', 1)[0] + '\n' + # Take care of `gobble` + if settings['gobble'] == 'auto': + code_replacement = textwrap.dedent(code_replacement) + else: + if end_environment not in after: + while True: + texlinenum += 1 + if end_environment in tex[texlinenum]: + after = tex[texlinenum] + break + after = after.split(end_environment, 1)[1] + # Make sure that `after` contains two lines of content + # This is needed for some replacements that need to look ahead + if after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + processed, texcontent = replace_code_env(depy_name, arglist, + depy_linenum, + code_replacement, + code_replacement_mode, + after, depy_lexer, + firstnumber) + elif depy_typeset == 'p' and print_replacement is not None: + if depy_type == 'cmd': + # Make sure that `after` contains two lines of content + # This is needed for some replacements that need to look ahead + if after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + processed, texcontent = replace_print_cmd(depy_name, arglist, + depy_linenum, + print_replacement, + print_replacement_mode, + source, + after) + else: # depy_type == 'env' + end_environment = r'\end{' + depy_name + '}' + if end_environment not in after: + while True: + texlinenum += 1 + if end_environment in tex[texlinenum]: + after = tex[texlinenum] + break + after = after.split(end_environment, 1)[1] + # Make sure that `after` contains two lines of content + # This is needed for some replacements that need to look ahead + if after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + processed, texcontent = replace_print_env(depy_name, arglist, + depy_linenum, + print_replacement, + print_replacement_mode, + source, + after) + else: # depy_typeset == 'n' or (depy_typeset == 'p' and print_replacement is None): + if depy_type == 'cmd': + texcontent = after + else: # depy_type == 'env' + end_environment = r'\end{' + depy_name + '}' + if end_environment not in after: + while True: + texlinenum += 1 + if end_environment in tex[texlinenum]: + after = tex[texlinenum] + break + after = after.split(end_environment, 1)[1] + if bool(match('\s*\n', after)): + # If the line following `after` is whitespace, it should + # be stripped, since most environments throw away + # anything after the end of the environment + after = after.split('\n')[1] + texcontent = after + # #### Once it's supported on the TeX side, need to add support for + # pc and cp + + + # Store any processed content + if processed is not None: + texout.append(processed) + + +# Transfer anything that's left in tex to texout +texout.append(texcontent) +texout.extend(tex[texlinenum+1:]) + + + + +# Replace the `\usepackage{pythontex}` +for n, line in enumerate(texout): + if '{pythontex}' in line: + startline = n + while '\\usepackage' not in texout[startline] and startline >= 0: + startline -= 1 + if startline == n: + if bool(search(r'\\usepackage(?:\[.*?\]){0,1}\{pythontex\}', line)): + texout[n] = sub(r'\\usepackage(?:\[.*?\]){0,1}\{pythontex\}', '', line) + if texout[n].isspace(): + texout[n] = '' + break + else: + content = ''.join(texout[startline:n+1]) + if bool(search(r'(?s)\\usepackage(?:\[.*?\]\s*){0,1}\{pythontex\}', content)): + replacement = sub(r'(?s)\\usepackage(?:\[.*?\]\s*){0,1}\{pythontex\}', '', content) + if replacement.isspace(): + replacement = '' + texout[startline] = replacement + for l in range(startline+1, n+1): + texout[l] = '' + break + elif line.startswith(r'\begin{document}'): + break +if preamble_additions: + texout[n] += '\n'.join(preamble_additions) + '\n' +# Take care of graphicspath +if args.graphicspath and settings['graphicx']: + for n, line in enumerate(texout): + if '\\graphicspath' in line and not bool(match('\s*%', line)): + texout[n] = line.replace('\\graphicspath{', '\\graphicspath{{' + settings['outputdir'] +'/}') + break + elif line.startswith(r'\begin{document}'): + texout[n] = '\\graphicspath{{' + settings['outputdir'] + '/}}\n' + line + break + + + + +# Print any final messages +if forced_double_space_list: + print('* DePythonTeX warning:') + print(' A trailing double space was forced with "\\space{}" for the following') + print(' This can happen when printed content is included inline') + print(' The forced double space is only an issue if it is not intentional') + for name, linenum in forced_double_space_list: + print(' "' + name + '" near line ' + str(linenum)) + + + + +# Write output +if args.output is not None: + for line in texout: + outfile.write(line) + outfile.close() +else: + if sys.version_info[0] == 2: + sys.stdout = codecs.getwriter(encoding)(sys.stdout, 'strict') + sys.stderr = codecs.getwriter(encoding)(sys.stderr, 'strict') + else: + sys.stdout = codecs.getwriter(encoding)(sys.stdout.buffer, 'strict') + sys.stderr = codecs.getwriter(encoding)(sys.stderr.buffer, 'strict') + for line in texout: + sys.stdout.write(line) diff --git a/pythontex/depythontex3.py b/pythontex/depythontex3.py new file mode 100644 index 0000000..e19f102 --- /dev/null +++ b/pythontex/depythontex3.py @@ -0,0 +1,1427 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +''' +PythonTeX depythontex script. + +This script takes a LaTeX document that uses the PythonTeX package and +creates a new document that does not depend on PythonTeX. It substitutes all +externally generated content into a copy of the original LaTeX document. +This is useful when you need a document that relies on few external packages +or custom macros (for example, for submission to a journal or conversion to +another document format). + +If you just want to share a document that uses PythonTeX, keep in mind that +the document can be modified and compiled just like a regular LaTeX document, +without needing Python or any other external tools, so long as the following +conditions are met: + + * A copy of pythontex.sty is included with the document. + * The pythontex-files- directory is included with the document. + * The PythonTeX-specific parts of the document are not modified. + +To work, this script requires that the original LaTeX document be compiled +with the package option `depythontex`. That creates an auxiliary file with +the extension .depytx that contains information about all content that needs +to be substituted. + +This script is purposely written in a simple, largely linear form to +facilitate customization. Most of the key substitutions are performed by a +few functions defined near the beginning of the script, so if you need custom +substitutions, you should begin there. By default, all typeset code is +wrapped in `\\verb` commands and verbatim environments, since these have the +greatest generality. However, the command-line option --listing allows code +to be typeset with the fancyvrb, listings, minted, or PythonTeX packages +instead. + +The script automatically extracts all arguments of all commands and +environments that it replaces, so that these are available if desired for +customized substitution. Two additional pieces of information are also +available for any typeset code: the Pygments lexer (often the same as the +language) and the starting line number (if line numbering was used). + +Keep in mind that some manual adjustments may be required after a document is +depythontex'ed. While depythontex attempts to create an exact copy of the +original document, in many cases an identical copy is impossible. For +example, typeset code may have a different appearance or layout when it is +typeset with a different package. + + +Copyright (c) 2013-2026, Geoffrey M. Poore +All rights reserved. +Licensed under the BSD 3-Clause License: + http://www.opensource.org/licenses/BSD-3-Clause + +''' + + +# Imports +#// Python 2 +#from __future__ import absolute_import +#from __future__ import division +#from __future__ import print_function +#from __future__ import unicode_literals +#\\ End Python 2 +import sys +import os +#// Python 2 +#if sys.version_info.major != 2: +# sys.exit('This version of the PythonTeX script requires Python 2.') +#\\ End Python 2 +#// Python 3 +if sys.version_info.major != 3: + sys.exit('This version of the PythonTeX script requires Python 3.') +#\\ End Python 3 + +#// Python 2 +#from io import open +#input = raw_input +#\\ End Python 2 +import argparse +from collections import defaultdict +from re import match, sub, search +import textwrap +import codecs + + +# Script parameters +# Version +__version__ = '0.19' + + +# Functions and parameters for customizing the script output + +# Style or package for replacing code listings +# This is actually set via command-line option --listing +# It is created here simply for reference +listing = None #'verbatim', 'fancyvrb', 'listings', 'minted', 'pythontex' + +# List of things to add to the preamble +# It can be appended to via the command-line option --preamble +# It is also appended to based on the code listing style that is used +# And it could be manually edited here as well, as long as it remains a list +preamble_additions = list() + +# Lexer dict +# If you are using Pygments lexers that don't directly correspond to the +# languages used by the listings package, you can submit replacements via the +# command line option --lexer-dict, or edit this dict manually here. When +# listings is used, all lexers are checked against this dict to see if a +# substitution should be made. This approach could easily be modified to +# work with another, non-Pygments highlighting package. +lexer_dict = dict() + + +def replace_code_cmd(name, arglist, linenum, code_replacement, + code_replacement_mode, after, lexer, firstnumber): + ''' + Typeset code from a command with a command. + + It is only ever called if there is indeed code to typeset. + + Usually, code from a command is also typeset with a command. This + function primarily deals with that case. In cases where code from a + command is typeset with an environment (for example, `\\inputpygments`), + this function performs some preprocessing and then uses + replace_code_env() to do the real work. This approach prevents the two + functions from unnecessarily duplicating each other, while still giving + the desired output. + + Args: + name (str): name of the command + arglist (list, of str/None): all arguments given to the original + command; the last argument is what is typeset, unless a + code_replacement is specified or other instructions are given + linenum (int): line number in the original TeX document + code_replacement (str/None): replacement for the code; usually None + for commands, because typically the code to be typeset is the + last argument passed to the command, rather than something + captured elsewhere (like the body of an environment) or something + preprocessed (like a console environment's content) + code_replacement_mode (str/None): mode in which the replacement is + to be typeset; raw/None (as TeX; generally unused for code), + verb (inline), or verbatim (environment) + after (str): text immediately following the command; usually + shouldn't be needed + lexer (str/None): Pygments lexer + Returns: + (replacement, after) (tuple, of str) + + ''' + # Get the correct replacement + if code_replacement is None: + code_replacement = arglist[-1] + + # We only consider two possible modes of typesetting, verbatim and inline + # verbatim + if code_replacement_mode == 'verbatim': + # Sometimes we must replace a command with an environment, for + # example, for `\inputpygments` + + # Make sure the introduction of an environment where a command was + # previously won't produce errors with following content; make sure + # that any following content is on a separate line + if bool(match(r'[ \t]*\S', after)): + after = '\n' + after + # Rather than duplicating much of replace_code_env(), just use it + return replace_code_env(name, arglist, linenum, code_replacement, + code_replacement_mode, after, lexer, firstnumber) + else: + # Usually, we're replacing a command with a command + + # Wrap the replacement in appropriate delimiters + if (listing in ('verbatim', 'fancyvrb', 'minted') or + (listing in ('listings', 'pythontex') and + ('{' in code_replacement or '}' in code_replacement))): + for delim in ('|', '/', '`', '!', '&', '#', '@', ':', '%', '~', '$', + '=', '+', '-', '^', '_', '?', ';'): + if delim not in code_replacement: + break + code_replacement = delim + code_replacement + delim + else: + code_replacement = '{' + code_replacement + '}' + # Assemble the actual replacement + if listing in ('verbatim', 'minted'): # `\mint` isn't for inline use + code_replacement = r'\verb' + code_replacement + elif listing == 'fancyvrb': + code_replacement = r'\Verb' + code_replacement + elif listing == 'listings': + if lexer is None: + code_replacement = r'\lstinline[language={}]' + code_replacement + else: + if lexer in lexer_dict: + lexer = lexer_dict[lexer] + code_replacement = r'\lstinline[language=' + lexer + ']' + code_replacement + elif listing == 'pythontex': + if lexer is None: + code_replacement = r'\pygment{text}' + code_replacement + else: + code_replacement = r'\pygment{' + lexer + '}' + code_replacement + return (code_replacement, after) + + +def replace_code_env(name, arglist, linenum, code_replacement, + code_replacement_mode, after, lexer, firstnumber): + ''' + Typeset code from an environment with an environment. + + It is only ever called if there is indeed code to typeset. + + Usually it is only used to typeset code from an environment. However, + some commands bring in code that must be typeset as an environment. In + those cases, replace_code_cmd() is called initially, and after it + performs some preprocessing, this function is called. This approach + avoids unnecessary duplication between the two functions. + + Args: + name (str): name of the environment + arglist (list, of str/None): all arguments given to the original + environment + linenum (int): line number in the original TeX document where + the environment began + code_replacement (str): replacement for the code; unlike the case of + commands, this is always not None if the function is called + code_replacement_mode (str/None): mode in which the replacement is + to be typeset; raw/None (as TeX; generally unused for code), + verb (inline), or verbatim (environment) + after (str): text immediately following the environment; usually + shouldn't be needed + lexer (str/None): Pygments lexer + firstnumber (str/None): the first number of the listing, if the listing + had numbered lines + Returns: + (replacement, after) (tuple, of str) + + ''' + # Currently, there is no need to test for code_replacement_mode, because + # this function is only ever called if the mode is 'verbatim'. That may + # change in the future, but it seems unlikely that code entered in an + # environment would end up typeset with a command. + if listing == 'verbatim': + pre = '\\begin{verbatim}' + post = '\\end{verbatim}' + elif listing == 'fancyvrb': + if firstnumber is None: + pre = '\\begin{Verbatim}' + else: + pre = '\\begin{{Verbatim}}[numbers=left,firstnumber={0}]'.format(firstnumber) + post = '\\end{Verbatim}' + elif listing == 'listings': + if lexer is None: + if firstnumber is None: + pre = '\\begin{lstlisting}[language={}]' + else: + pre = '\\begin{{lstlisting}}[language={{}},numbers=left,firstnumber={0}]'.format(firstnumber) + else: + if lexer in lexer_dict: + lexer = lexer_dict[lexer] + if firstnumber is None: + pre = '\\begin{{lstlisting}}[language={0}]'.format(lexer) + else: + pre = '\\begin{{lstlisting}}[language={0},numbers=left,firstnumber={1}]'.format(lexer, firstnumber) + post = '\\end{lstlisting}' + elif listing == 'minted': + if lexer is None: + if firstnumber is None: + pre = '\\begin{minted}{text}' + else: + pre = '\\begin{{minted}}[linenos,firstnumber={0}]{{text}}'.format(firstnumber) + else: + if firstnumber is None: + pre = '\\begin{{minted}}{{{0}}}'.format(lexer) + else: + pre = '\\begin{{minted}}[linenos,firstnumber={0}]{{{1}}}'.format(firstnumber, lexer) + post = '\\end{minted}' + elif listing == 'pythontex': + if lexer is None: + if firstnumber is None: + pre = '\\begin{pygments}{text}' + else: + pre = '\\begin{{pygments}}[numbers=left,firstnumber={0}]{{text}}'.format(firstnumber) + else: + if firstnumber is None: + pre = '\\begin{{pygments}}{{{0}}}'.format(lexer) + else: + pre = '\\begin{{pygments}}[numbers=left,firstnumber={0}]{{{1}}}'.format(firstnumber, lexer) + post = '\\end{pygments}' + code_replacement = pre + code_replacement + post + return (code_replacement, after) + + +# We will need to issue a warning every time that a substitution of printed +# content results in a forced double space. We could just do this as we go, +# but it's easier for the user to read if we just collect all the warnings +# of this type, and print them once. +forced_double_space_list = list() + + +def replace_print_cmd(name, arglist, linenum, + print_replacement, print_replacement_mode, source, + after): + ''' + Typeset printed content from a command. + + It is only ever called if there is indeed printed content to typeset. + + Args: + name (str): name of the command + arglist (list, of str/None): all arguments given to the original + command + linenum (int): line number in the original TeX document + print_replacement (str): printed content, read directly from file + into a single string + print_replacement_mode (str/None): mode in which the replacement is + to be typeset; raw/None (as TeX), inlineverb (or v) (as inline), + or verb (as environment) + source (str/None): source of the replacement content + after (str): text immediately following the command; important in + some situations, because spacing can depend on what's next + Returns: + (replacement, after) (tuple, of str) + + ''' + if print_replacement_mode == 'verb': + if print_replacement.count('\n') > 1: + print('* DePythonTeX error:') + print(' Attempt to print multiple lines of content near line ' + str(linenum)) + print(' This is not possible in inline verbatim mode') + sys.exit(1) + print_replacement = print_replacement.rstrip('\n') + for delim in ('|', '/', '`', '!', '&', '#', '@', ':', '%', '~', '$', + '=', '+', '-', '^', '_', '?', ';'): + if delim not in print_replacement: + break + print_replacement = r'\verb' + delim + print_replacement + delim + elif print_replacement_mode == 'verbatim': + if bool(match(r'\s*?\n', after)): + # Usually, we would end the verbatim environment with a newline. + # This is fine if there is content in `after` before the next + # newline---in fact, it's desirable, because the verbatim package + # doesn't allow for content on the same line as the end of the + # environment. But if `after` is an empty line, then adding a + # newline will throw off spacing and must be avoided + print_replacement = '\\begin{verbatim}\n' + print_replacement + '\\end{verbatim}' + else: + print_replacement = '\\begin{verbatim}\n' + print_replacement + '\\end{verbatim}\n' + else: + # When printed content from a file is included as LaTeX code, we have + # to be particularly careful to ensure that the content produces the + # same output when substituted as when brought in by `\input`. In + # particular, `\input` strips newlines from each line of content and + # adds a space at the end of each line. This space is inside the + # `\input`, so it will not merge with following spaces. So when we + # substitute the content, sometimes we need to replace the final + # newline with a space that cannot be gobbled. + # + # It gets more complicated. This final space is often not + # desirable. It can be prevented by either printing an `\endinput` + # command, to terminate the `\input`, or printing a percent + # character % in the last line of the content, which comments out the + # final newline. So we must check for `\endinput` anywhere in + # printed content, and % in the final line, and remove any content + # after them. It's also possible that the print is followed by + # an `\unskip` that eats the space, so we need to check for that too. + # + # It turns out that the same approach is needed when a command like + # `\py` brings in content ending in a newline + if (print_replacement.endswith('\\endinput\n') and + not print_replacement.endswith('\\string\\endinput\n')): + # If `\endinput` is present, everything from it on should be + # discarded, unless the `\endinput` is not actually a command + # but rather a typeset name (for example, `\string\endinput` or + # `\verb|\endinput|`). It's impossible to check for all cases in + # which `\endinput` is not a command (at least, without actually + # using LaTeX), and even checking for most of them would require + # a good bit of parsing. We assume that `\endinput`, as a + # command, will only ever occur at the immediate end of the + # printed content. Later, we issue a warning in case it appears + # anywhere else. + print_replacement = print_replacement.rsplit(r'\endinput', 1)[0] + elif (print_replacement.endswith('%\n') and + not print_replacement.endswith('\\%\n') and + not print_replacement.endswith('\\string%\n')): + # Perform an analogous check for a terminating percent characer %. + # This case would be a bit easier to parse fully, since a percent + # that comments out the last newline would have to be in the + # final line of the replacement. But it would still be + # very difficult to perform a complete check. Later, we issue a + # warning if there is reason to think that a percent character + # was active in the last line. + print_replacement = print_replacement.rsplit(r'%', 1)[0] + elif print_replacement.endswith('\n'): + # We can't just use `else` because that would catch content + # from `\py` and similar + # By default, LaTeX strips newlines and adds a space at the end + # of each line of content that is brought in by `\input`. This + # may or may not be desirable, but we replicate the effect here + # for consistency with the original document. We use `\space{}` + # because plain `\space` would gobble a following space, which + # isn't consistent with the `\input` behavior being replicated. + if bool(match(r'\\unskip\s+\S', after)): + # If there's an `\unskip`, fix the spacing and remove the + # `\unskip`. Since this is inline, the `\unskip` must + # immediately follow the command to do any good; otherwise, + # it eliminates spaces that precede it, but doesn't get into + # the `\input` content. + print_replacement = print_replacement.rstrip(' \t\n') + after = sub(r'^\\unskip\s+', '', after) + elif bool(match(r'\S', after)): + # If the next character is not whitespace, we can just leave + # the `\n`, and it will yield a space. + pass + elif bool(match(r'\s*$', after)): + # If the rest of the current line, and the next line, are + # whitespace, we will get the correct spacing without needing + # `\space{}`. We could leave `\n`, but it would be + # extraneous whitespace. + print_replacement = print_replacement[:-1] + else: + # Otherwise, we do need to insert `\space{}` + # We keep the newline at the end of printed content, in case + # it's at the end of an environment, and thus is needed to + # protect the following content + print_replacement += '\\space{}' + after = sub(r'^\s+', '', after) + forced_double_space_list.append((name, linenum)) + else: + if bool(match(r'\s+\S', after)): + # If the following line starts with whitespace, replace it + # with a newline, to protect in the event that the printed + # content ended with an end-of-environment command + after = sub(r'^\s+', '\n', after) + # Issue warnings, if warranted + # Warn about `\endinput` + if (r'\endinput' in print_replacement and + print_replacement.count(r'\endinput') != print_replacement.count(r'\string\endinput')): + print('* DePythonTeX warning:') + print(' "\\endinput" was present in printed content near line ' + str(linenum)) + print(' If this "\\endinput" was verbatim, you have nothing to worry about') + print(' If this "\\endinput" is to be active, it should be printed last') + print(' If you need "\\endinput" elsewhere, customize depythontex.py') + # Warn if it looks like there are active `%` that could comment + # out part of the original document. We only need to check the + # last line of printed content, because only there could + # percent characters escape from their original confines within + # `\input`, and comment out part of the document. + if print_replacement.endswith('\n'): + if print_replacement.count('\n') > 1: + last_line = print_replacement.rsplit('\n', 2)[1] + else: + last_line = print_replacement + if last_line.count('%') != last_line.count(r'\%') + last_line.count(r'\string%'): + print('* DePythonTeX warning:') + print(' One or more percent characters are on the last line of ') + print(' printed content near line ' + str(linenum)) + print(' If they are not verbatim, they could comment out the following newline') + else: + if '\n' in print_replacement: + last_line = print_replacement.rsplit('\n', 1)[1] + else: + last_line = print_replacement + if last_line.count('%') != last_line.count(r'\%') + last_line.count(r'\string%'): + print('* DePythonTeX warning:') + print(' One or more percent characters are on the last line of ') + print(' printed content near line ' + str(linenum)) + print(' If they are not verbatim, they could comment out the following text') + # Warn if there were `\unskip`'s in the output, in case they weren't + # positioned correctly + if bool(match(r'\s*\\unskip', after)): + print('* DePythonTeX warning:') + print(' "\\unskip" survived substitution near line ' + str(linenum)) + print(' If it should have adjusted the spacing of printed content') + print(' you should double-check the spacing') + return (print_replacement, after) + + +def replace_print_env(name, arglist, linenum, + print_replacement, print_replacement_mode, source, + after): + ''' + Typeset printed content from an environment. + + It is only ever called if there is indeed printed content to typeset. + + This should be similar to replace_print_cmd(). The main difference is + that the environment context typically ends with a newline, so + substitution has to be a little different to ensure that spacing after + the environment isn't modified. + + Args: + name (str): name of the environment + arglist (list, of str/None): all arguments given to the original + environment + linenum (int): line number in the original TeX document where the + environment began + print_replacement (str): printed content, read directly from file + into a single string + print_replacement_mode (str/None): mode in which the replacement is + to be typeset; raw/None (as TeX), inlineverb (or v) (as inline), + or verb (as environment) + source (str/None): source of the replacement content + after (str): text immediately following the command; important in + some situations, because spacing can depend on what's next + Returns: + (replacement, after) (tuple, of str) + + #### The inlineverb and verb modes should work, but haven't been tested + since there are currently no environments that use them; they are only + used by `\\printpythontex`, which is a command. + ''' + if print_replacement_mode == 'verb': + if print_replacement.count('\n') > 1: + print('* DePythonTeX error:') + print(' Attempt to print multiple lines of content near line ' + str(linenum)) + print(' This is not possible in inline verbatim mode') + sys.exit(1) + print_replacement = print_replacement.rstrip('\n') + for delim in ('|', '/', '`', '!', '&', '#', '@', ':', '%', '~', '$', + '=', '+', '-', '^', '_', '?', ';'): + if delim not in print_replacement: + break + print_replacement = r'\verb' + delim + print_replacement + delim + if not bool(match(r'[ \t]+\S', after)): + # If there is text on the same line as the end of the + # environment, we're fine (this is unusual). Otherwise, + # we need to toss the newline at the end of the environment + # and gobble leading spaces. Leading spaces need to be + # gobbled because previously they were at the beginning of a + # line, where they would have been discarded. + if not bool(match(r'\s*$', after)): + after = sub(r'^\s*?\n\s*', '', after) + elif print_replacement_mode == 'verbatim': + if bool(match(r'\s*?\n', after)): + # Usually, we would end the verbatim environment with a newline. + # This is fine if there is content in `after` before the next + # newline---in fact, it's desirable, because the verbatim package + # doesn't allow for content on the same line as the end of the + # environment. But if `after` is an empty line, then adding a + # newline will throw off spacing and must be avoided + print_replacement = '\\begin{verbatim}\n' + print_replacement + '\\end{verbatim}' + else: + print_replacement = '\\begin{verbatim}\n' + print_replacement + '\\end{verbatim}\n' + else: + # When printed content is included as LaTeX code, we have to be + # particularly careful to ensure that the content produces the same + # output when substituted as when brought in by `\input`. In + # particular, `\input` strips newlines from each line of content and + # adds a space at the end of each line. This space is inside the + # `\input`, so it will not merge with following spaces. So when we + # substitute the content, sometimes we need to replace the final + # newline with a space that cannot be gobbled. + # + # It gets more complicated. This final space is often not + # desirable. It can be prevented by either printing an `\endinput` + # command, to terminate the `\input`, or printing a percent + # character % in the last line of the content, which comments out the + # final newline. So we must check for `\endinput` anywhere in + # printed content, and % in the final line, and remove any content + # after them. It's also possible that the print is followed by + # an `\unskip` that eats the space, so we need to check for that too. + if (print_replacement.endswith('\\endinput\n') and + not print_replacement.endswith('\\string\\endinput\n')): + # If `\endinput` is present, everything from it on should be + # discarded, unless the `\endinput` is not actually a command + # but rather a typeset name (for example, `\string\endinput` or + # `\verb|\endinput|`). It's impossible to check for all cases in + # which `\endinput` is not a command (at least, without actually + # using LaTeX), and even checking for most of them would require + # a good bit of parsing. We assume that `\endinput`, as a + # command, will only ever occur at the immediate end of the + # printed content. Later, we issue a warning in case it appears + # anywhere else. + print_replacement = print_replacement.rsplit(r'\endinput', 1)[0] + if not bool(match(r'[ \t]+\S', after)): + # If there is text on the same line as the end of the + # environment, we're fine (this is unusual). Otherwise, + # we need to toss the newline at the end of the environment + # and gobble leading spaces. Leading spaces need to be + # gobbled because previously they were at the beginning of a + # line, where they would have been discarded. + if not bool(match(r'\s*$', after)): + after = sub(r'^\s*?\n\s*', '', after) + elif (print_replacement.endswith('%\n') and + not print_replacement.endswith('\\%\n') and + not print_replacement.endswith('\\string%\n')): + # Perform an analogous check for a terminating percent characer %. + # This case would be a bit easier to parse fully, since a percent + # that comments out the last newline would have to be in the + # final line of the replacement. But it would still be + # very difficult to perform a complete check. Later, we issue a + # warning if there is reason to think that a percent character + # was active in the last line. + print_replacement = print_replacement.rsplit(r'%', 1)[0] + if not bool(match(r'[ \t]+\S', after)): + # If there is text on the same line as the end of the + # environment, we're fine (this is unusual). Otherwise, + # we need to toss the newline at the end of the environment + # and gobble leading spaces. Leading spaces need to be + # gobbled because previously they were at the beginning of a + # line, where they would have been discarded. + if not bool(match(r'\s*$', after)): + after = sub(r'^\s*?\n\s*', '', after) + else: + # By default, LaTeX strips newlines and adds a space at the end + # of each line of content that is brought in by `\input`. This + # may or may not be desirable, but we replicate the effect here + # for consistency with the original document. We use `\space{}` + # because plain `\space` would gobble a following space, which + # isn't consistent with the `\input` behavior being replicated. + if bool(match(r'\s*\\unskip\s+\S', after)): + # If there's an `\unskip`, fix the spacing and remove the + # `\unskip` + print_replacement = print_replacement.rstrip(' \t\n') + after = sub(r'^\s*\\unskip\s+', '', after) + elif bool(match(r'[ \t]+\S', after)): + # If the next character after the end of the environment is + # not whitespace (usually not allowed), we can just leave + # the `\n` in printed content, and it will yield a space. + # So we need do nothing. But if there is text on that line + # we need `\space{}`. + after = sub(r'^\s+', '\\space', after) + forced_double_space_list.append((name, linenum)) + else: + # If the line at the end of the environment is blank, + # we can just discard it and keep the newline at the end of + # the printed content; the newline gives us the needed space + after = after.split('\n', 1)[1] + # Issue warnings, if warranted + # Warn about `\endinput` + if (r'\endinput' in print_replacement and + print_replacement.count(r'\endinput') != print_replacement.count(r'\string\endinput')): + print('* DePythonTeX warning:') + print(' "\\endinput" was present in printed content near line ' + str(linenum)) + print(' If this "\\endinput" was verbatim, you have nothing to worry about') + print(' If this "\\endinput" is to be active, it should be printed last') + print(' If you need "\\endinput" elsewhere, customize depythontex.py') + # Warn if it looks like there are active `%` that could comment + # out part of the original document. We only need to check the + # last line of printed content, because only there could + # percent characters escape from their original confines within + # `\input`, and comment out part of the document. + if print_replacement.endswith('\n'): + if print_replacement.count('\n') > 1: + last_line = print_replacement.rsplit('\n', 2)[1] + else: + last_line = print_replacement + if last_line.count('%') != last_line.count(r'\%') + last_line.count(r'\string%'): + print('* DePythonTeX warning:') + print(' One or more percent characters are on the last line of ') + print(' printed content near line ' + str(linenum)) + print(' If they are not verbatim, they could comment out the following newline') + else: + if '\n' in print_replacement: + last_line = print_replacement.rsplit('\n', 1)[1] + else: + last_line = print_replacement + if last_line.count('%') != last_line.count(r'\%') + last_line.count(r'\string%'): + print('* DePythonTeX warning:') + print(' One or more percent characters are on the last line of ') + print(' printed content near line ' + str(linenum)) + print(' If they are not verbatim, they could comment out the following text') + # Warn if there were `\unskip`'s in the output, in case they weren't + # positioned correctly + if bool(match(r'\s*\\unskip', after)): + print('* DePythonTeX warning:') + print(' "\\unskip" survived substitution near line ' + str(linenum)) + print(' If it should have adjusted the spacing of printed content') + print(' you should double-check the spacing') + return (print_replacement, after) + + + + +# Deal with argv +# Parse argv +parser = argparse.ArgumentParser() +parser.add_argument('--version', action='version', + version='DePythonTeX {0}'.format(__version__)) +parser.add_argument('--encoding', default='utf-8', + help='encoding for all text files (see codecs module for encodings)') +parser.add_argument('--overwrite', default=False, action='store_true', + help='overwrite existing output, if it exists (off by default)') +parser.add_argument('--listing', default='verbatim', + choices=('verbatim', 'fancyvrb', 'listings', 'minted', 'pythontex'), + help='style or package used for typesetting code') +parser.add_argument('--lexer-dict', default=None, + help='add mappings from Pygments lexer names to the language names of other highlighting packages; should be a comma-separated list of the form ":, :, ..."') +parser.add_argument('--preamble', default=None, + help='line of commands to add to output preamble') +parser.add_argument('--graphicspath', default=False, action='store_true', + help=r'Add the outputdir to the graphics path, by modifying an existing \graphicspath command or adding one.') +parser.add_argument('-o', '--output', default=None, + help='output file') +parser.add_argument('TEXNAME', + help='LaTeX file') +args = parser.parse_args() + +# Process argv +encoding = args.encoding +listing = args.listing +if args.preamble is not None: + preamble_additions.append(args.preamble) +if args.lexer_dict is not None: + args.lexer_dict = args.lexer_dict.replace(' ', '').replace("'", "").replace('"','').strip('{}') + for entry in args.lexer_dict.split(','): + k, v = entry.split(':') + lexer_dict[k] = v +if args.listing == 'verbatim': + # In some contexts, the verbatim package might be desirable. + # But we assume that the user wants minimal packages. + # Also, the default verbatim environment doesn't allow text to follow the + # end-of-environment command. + # If the verbatim package is ever desired, simply uncomment the following: + # preamble_additions.append('\\usepackage{verbatim}') + pass +elif args.listing == 'fancyvrb': + preamble_additions.append('\\usepackage{fancyvrb}') +elif args.listing == 'listings': + preamble_additions.append('\\usepackage{listings}') +elif args.listing == 'minted': + preamble_additions.append('\\usepackage{minted}') +elif args.listing == 'pythontex': + preamble_additions.append('\\usepackage{pythontex}') + + + + +# Let the user know things have started +if args.output is not None: + print('This is DePythonTeX {0}'.format(__version__)) + sys.stdout.flush() + + + + +# Make sure we have a valid texfile +texfile_name = os.path.expanduser(os.path.normcase(args.TEXNAME)) +if not os.path.isfile(texfile_name): + resolved = False + if not texfile_name.endswith('.tex'): + for ext in ('.tex', '.ltx', '.dtx'): + if os.path.isfile(texfile_name + ext): + texfile_name = texfile_name + ext + resolved = True + break + if not resolved: + print('* DePythonTeX error:') + print(' Could not locate file "' + texfile_name + '"') + sys.exit(1) +# Make sure we have a valid outfile +if args.output is not None: + outfile_name = os.path.expanduser(os.path.normcase(args.output)) + if not args.overwrite and os.path.isfile(outfile_name): + print('* DePythonTeX warning:') + print(' Output file "' + outfile_name + '" already exists') + ans = input(' Do you want to overwrite this file? [y,n]\n ') + if ans != 'y': + sys.exit(1) +# Make sure the .depytx file exists +depytxfile_name = texfile_name.rsplit('.')[0] + '.depytx' +if not os.path.isfile(depytxfile_name): + print('* DePythonTeX error:') + print(' Could not find DePythonTeX auxiliary file "' + depytxfile_name + '"') + print(' Use package option depythontex to create it') + sys.exit(1) + + + + +# Start opening files and loading data +# Read in the LaTeX file +# We read into a list with an empty first entry, so that we don't have to +# worry about zero indexing when comparing list index to file line number +f = open(texfile_name, 'r', encoding=encoding) +tex = [''] +tex.extend(f.readlines()) +f.close() +# Load the .depytx +f = open(depytxfile_name, 'r', encoding=encoding) +depytx = f.readlines() +f.close() +# Process the .depytx by getting the settings contained in the last few lines +settings = dict() +n = len(depytx) - 1 +while depytx[n].startswith('=>DEPYTHONTEX:SETTINGS#'): + content = depytx[n].split('#', 1)[1].rsplit('#', 1)[0] + k, v = content.split('=', 1) + if v in ('true', 'True'): + v = True + elif v in ('false', 'False'): + v = False + settings[k] = v + depytx[n] = '' + n -= 1 +# Check .depytx version to make sure it is compatible +if settings['version'] != __version__: + print('* DePythonTeX warning:') + print(' Version mismatch with DePythonTeX auxiliary file') + print(' Do a complete compile cycle to update the auxiliary file') + print(' Attempting to proceed') +# Go ahead and open the outfile, even though we don't need it until the end +# This lets us change working directories for convenience without worrying +# about having to modify the outfile path +if args.output is not None: + outfile = open(outfile_name, 'w', encoding=encoding) + + + + +# Change working directory to the document directory +# Technically, we could get by without this, but that would require a lot of +# path modification. This way, we can just use all paths straight out of the +# .depytx without any modification, which is much simpler and less error-prone. +if os.path.split(texfile_name)[0] != '': + os.chdir(os.path.split(texfile_name)[0]) + + + + +# Open and process the file of macros +# Read in the macros +if os.path.isfile(os.path.expanduser(os.path.normcase(settings['macrofile']))): + f = open(os.path.expanduser(os.path.normcase(settings['macrofile'])), 'r', encoding=encoding) + macros = f.readlines() + f.close() +else: + print('* DePythonTeX error:') + print(' The macro file could not be found:') + print(' "' + settings['macrofile'] + '"') + print(' Run PythonTeX to create it') + sys.exit(1) +# Create a dict for storing macros +macrodict = defaultdict(list) +# Create variables for keeping track of whether we're inside a macro or +# environment +# These must exist before we begin processing +inside_macro = False +inside_environment = False +# Loop through the macros, and extract everything +# We just extract content; we get content wrappers later, when we process all +# substituted content +for line in macros: + if inside_macro: + # If we're in a macro, look for the end-of-macro command + if r'\endpytx@SVMCR' in line: + # If the current line contains the end-of-macro command, split + # off any content that comes before it. Also reset + # `inside_macro`. + macrodict[current_macro].append(line.rsplit(r'\endpytx@SVMCR', 1)[0]) + inside_macro = False + else: + # If the current line doesn't end the macro, we add the whole + # line to the macro dict + macrodict[current_macro].append(line) + elif inside_environment: + if line.startswith(end_environment): + # If the environment is ending, we reset inside_environment + inside_environment = False + else: + # If we're still in the environment, add the current line to the + # macro dict + macrodict[current_macro].append(line) + else: + # If we're not in a macro or environment, we need to figure out which + # we are dealing with (if either; there are blank lines in the macro + # file to increase readability). Once we've determined which one, + # we need to get its name and extract any content. + if line.startswith(r'\begin{'): + # Any \begin will indicate a use of fancyvrb to save verbatim + # content, since that is the only time an environment is used in + # the macro file. All other content is saved in a standard macro. + # We extract the name of the macro in which the verbatim content + # is saved. + current_macro = line.rsplit('{', 1)[1].rstrip('}\n') + inside_environment = True + # We assemble the end-of-environment string we will need to look + # for. We don't assume any particular name, for generality. + end_environment = r'\end{' + line.split('}', 1)[0].split('{', 1)[1] + '}' + # Code typset in an environment needs to have a leading newline, + # because the content of a normal verbatim environment keeps its + # leading newline. + macrodict[current_macro].append('\n') + elif line.startswith(r'\pytx@SVMCR{'): + # Any regular macro will use `\pytx@SVMCR` + current_macro = line.split('{', 1)[1].split('}', 1)[0] + inside_macro = True + # Any content will always be on the next line, so we don't need + # to check for it + + + + +# Do the actual processing +# Create a variable for keeping track of the current line in the LaTeX file +# Start at 1, since the first entry in the tex list is `''` +texlinenum = 1 +# Create a variable for storing the current line(s) we are processing. +# This contains all lines from immediately after the last successfully +# processed line up to and including texlinenum. We may have to process +# multiple lines at once if a macro is split over multiple lines, etc. +texcontent = tex[texlinenum] +# Create a list for storing processed content. +texout = list() +# Loop through the depytx and process +for n, depytxline in enumerate(depytx): + if depytxline.startswith('=>DEPYTHONTEX#'): + # Process info + depytxcontent = depytxline.split('#', 1)[1].rstrip('#\n') + depy_type, depy_name, depy_args, depy_typeset, depy_linenum, depy_lexer = depytxcontent.split(':') + if depy_lexer == '': + depy_lexer = None + + # Do a quick check on validity of info + # #### Eventually add 'cp' and 'pc' + if not (depy_type in ('cmd', 'env') and + all([letter in ('o', 'm', 'v', 'n', '|') for letter in depy_args]) and + ('|' not in depy_args or (depy_args.count('|') == 1 and depy_args.endswith('|'))) and + depy_typeset in ('c', 'p', 'n')): + print('* PythonTeX error:') + print(' Invalid \\Depythontex string for operation on line ' + str(depy_linenum)) + print(' The offending string was ' + depytxcontent) + sys.exit(1) + # If depy_args contains a `|` to indicate `\obeylines`, strip it and + # store in a variable. Create a bool to keep track of obeylines + # status, which governs whether we can look on the next line for + # arguments. (If obeylines is active, a newline terminates the + # argument search.) + if depy_args.endswith('|'): + obeylines = True + depy_args = depy_args.rstrip('|') + else: + obeylines = False + # Get the line number as an integer + # We don't have to adjust for zero indexing in tex + depy_linenum = int(depy_linenum) + + + # Check for information passed from LaTeX + # This will be extra listings information, or replacements to plug in + code_replacement = None + code_replacement_mode = None + print_replacement = None + print_replacement_mode = None + firstnumber = None + source = None + scan_ahead_line = n + 1 + nextdepytxline = depytx[scan_ahead_line] + while not nextdepytxline.startswith('=>DEPYTHONTEX#'): + if nextdepytxline.startswith('LISTING:'): + listingcontent = nextdepytxline.split(':', 1)[1].rstrip('\n') + if bool(match(r'firstnumber=\d+$', listingcontent)): + firstnumber = listingcontent.split('=', 1)[1] + else: + print('* DePythonTeX error:') + print(' Unknown information in listings data on line ' + str(depy_linenum)) + print(' The listings content was "' + listingcontent + '"') + sys.exit(1) + elif nextdepytxline.startswith('MACRO:'): + source = 'macro' + try: + typeset, macro = nextdepytxline.rstrip('\n').split(':', 2)[1:] + except: + print('* DePythonTeX error:') + print(' Improperly formatted macro information on line ' + str(depy_linenum)) + print(' The macro information was "' + nextdepytxline + '"') + sys.exit(1) + if macro not in macrodict: + print('* DePythonTeX error:') + print(' Could not find replacement content for macro "' + macro + '"') + print(' This is probably because the document needs to be recompiled') + sys.exit(1) + if typeset == 'c': + if depy_type == 'cmd': + code_replacement = ''.join(macrodict[macro]).strip('\n') + else: + code_replacement = ''.join(macrodict[macro]) + elif typeset == 'p': + print_replacement = ''.join(macrodict[macro]) + else: + print('* DePythonTeX error:') + print(' Improper typesetting information for macro information on line ' + str(depy_linenum)) + print(' The macro information was "' + nextdepytxline + '"') + sys.exit(1) + elif nextdepytxline.startswith('FILE:'): + source = 'file' + try: + typeset, f_name = nextdepytxline.rstrip('\n').split(':', 2)[1:] + except: + print('* DePythonTeX error:') + print(' Improperly formatted file information on line ' + str(depy_linenum)) + print(' The file information was "' + nextdepytxline + '"') + sys.exit(1) + # Files that are brought in have an optional mode that + # determines if they need special handling (for example, verbatim) + if ':mode=' in f_name: + f_name, mode = f_name.split(':mode=') + else: + mode = None + f = open(os.path.expanduser(os.path.normcase(f_name)), 'r', encoding=encoding) + replacement = f.read() + f.close() + if typeset == 'c': + code_replacement_mode = mode + if depy_type == 'cmd' and code_replacement_mode != 'verbatim': + # Usually, code from commands is typeset with commands + # and code from environments is typeset in + # environments. The except is code from commands + # that bring in external files, like `\inputpygments` + code_replacement = replacement + else: + # If we're replacing an environment of code with a + # file, then we lose the newline at the beginning + # of the environment, and need to get it back. + code_replacement = '\n' + replacement + elif typeset == 'p': + print_replacement_mode = mode + print_replacement = replacement + else: + print('* DePythonTeX error:') + print(' Improper typesetting information for file information on line ' + str(depy_linenum)) + print(' The file information was "' + nextdepytxline + '"') + sys.exit(1) + # Increment the line in depytx to check for more information + # from LaTeX + scan_ahead_line += 1 + if scan_ahead_line == len(depytx): + break + else: + nextdepytxline = depytx[scan_ahead_line] + + + # If the line we're looking for is within the range currently held by + # texcontent, do nothing. Otherwise, transfer content from tex + # to texout until we get to the line of tex that we're looking for + if depy_linenum > texlinenum: + texout.append(texcontent) + texlinenum += 1 + while texlinenum < depy_linenum: + texout.append(tex[texlinenum]) + texlinenum += 1 + texcontent = tex[texlinenum] + + + # Deal with arguments + # All arguments are parsed and stored in a list variables, even if + # they are not used, for completeness; this makes it easy to add + # functionality + # Start by splitting the current line into what comes before the + # command or environment, and what is after it + if depy_type == 'cmd': + try: + before, after = texcontent.split('\\' + depy_name, 1) + except: + print('* DePythonTeX error:') + print(' Could not find command "' + depy_name + '" on line ' + str(depy_linenum)) + sys.exit(1) + else: # depy_type == 'env': + try: + before, after = texcontent.split(r'\begin{' + depy_name + '}', 1) + except: + print('* DePythonTeX error:') + print(' Could not find environment "' + depy_name + '" on line ' + str(depy_linenum)) + sys.exit(1) + # We won't need the content from before the command or environment + # again, so we go ahead and store it + texout.append(before) + + # Parse the arguments + # Create a list for storing the recovered arguments + arglist = list() + for argindex, arg in enumerate(depy_args): + if arg == 'n': + pass + elif arg == 'o': + if after[0] == '[': + # Account for possible line breaks before end of arg + while ']' not in after: + texlinenum += 1 + after += tex[texlinenum] + optarg, after = after[1:].split(']', 1) + else: + if obeylines: + # Take into account possible whitespace before arg + if bool(match(r'[ \t]*\[', after)): + after = after.split('[', 1)[1] + while ']' not in after: + texlinenum += 1 + after += tex[texlinenum] + optarg, after = after.split(']', 1) + else: + optarg = None + # If this is the last arg, and it wasn't found, + # the macro should eat all whitespace following it + if argindex == len(depy_args) - 1: + after = sub('^[ \t]*', '', after) + else: + # Allow peeking ahead a line for the argument + if bool(match(r'\s*$', after)) and after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + # Take into account possible whitespace before arg + if bool(match(r'\s*\[', after)): + after = after.split('[', 1)[1] + while ']' not in after: + texlinenum += 1 + after += tex[texlinenum] + optarg, after = after.split(']', 1) + else: + optarg = None + # Account for eating whitespace afterward, if arg not found + if argindex == len(depy_args) - 1: + if bool(match(r'\s*$', after)) and after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + if not bool(match(r'\s*$', after)): + after = sub(r'^\s*', '', after) + arglist.append(optarg) + elif arg == 'm': + # Account for possible line breaks or spaces before arg + if after[0] == '{': + after = after[1:] + else: + if obeylines: + # Account for possible leading whitespace + if bool(match(r'[ \t\f\v]*\{', after)): + after = after.split('{', 1)[1] + else: + print('* DePythonTeX error:') + print(' Flawed mandatory argument for "' + depy_name + '" on line ' + str(depy_linenum)) + sys.exit(1) + else: + # Peek ahead a line if needed + if bool(match(r'\s*$', after)) and after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + if bool(match(r'\s*\{', after)): + after = after.split('{', 1)[1] + else: + print('* DePythonTeX error:') + print(' Flawed mandatory argument for "' + depy_name + '" on line ' + str(depy_linenum)) + sys.exit(1) + # Go through the argument character by character to find the + # closing brace. + # If possible, use a very simple approach + if (r'\{' not in after and r'\}' not in after and + r'\string' not in after and + after.count('{') + 1 == after.count('}')): + pos = 0 + lbraces = 1 + rbraces = 0 + while True: + if after[pos] == '{': + lbraces += 1 + elif after[pos] == '}': + rbraces += 1 + if lbraces == rbraces: + break + pos += 1 + if pos == len(after): + texlinenum += 1 + after += tex[texlinenum] + # If a simple parsing approach won't work, parse in much + # greater depth + else: + pos = 0 + lbraces = 1 + rbraces = 0 + while True: + if after[pos] == '{': + # If the current character is a brace, we count it + lbraces += 1 + if lbraces == rbraces: + break + pos += 1 + elif after[pos] == '}': + # If the current character is a brace, we count it + rbraces += 1 + if lbraces == rbraces: + break + pos += 1 + elif after[pos:].startswith(r'\string'): + # If the current position marks the beginning of `\string`, we + # resolve the `\string` command + # First, jump ahead to after `\string` + pos += 7 #+= len(r'\string') + # See if `\string` is followed by a regular macro + # If so, jump past it; otherwise, figure out if a + # single-character macro, or just a single character, is next, + # and jump past it + standard_macro = match(r'\\[a-zA-Z]+', line[pos:]) + if bool(standard_macro): + pos += standard_macro.end() + elif line[pos] == '\\': + pos += 2 + else: + pos += 1 + elif line[pos] == '\\': + # If the current position is a backslash, figure out what + # macro is used, and jump past it + # The macro must either be a standard alphabetic macro, + # or a single-character macro + standard_macro = match(r'\\[a-zA-Z]+', line[pos:]) + if bool(standard_macro): + pos += standard_macro.end() + else: + pos += 2 + else: + pos += 1 + if pos == len(after): + texlinenum += 1 + after += tex[texlinenum] + mainarg = after[:pos] + after = after[pos+1:] + arglist.append(mainarg) + elif arg == 'v': + if after[0] == '{': + # Account for the possibility of matched brace delimiters + # Not all verbatim commands allow for these + pos = 1 + lbraces = 1 + rbraces = 0 + while True: + if after[pos] == '{': + lbraces += 1 + elif after[pos] == '}': + rbraces += 1 + if lbraces == rbraces: + break + pos += 1 + if pos == len(after): + texlinenum += 1 + after += tex[texlinenum] + mainarg = after[1:pos] + after = after[pos+1:] + else: + # Deal with matched character delims + delim = after[0] + while after.count(delim) < 2: + texlinenum += 1 + after += tex[texlinenum] + mainarg, after = after[1:].split(delim, 1) + arglist.append(mainarg) + + + # Do substitution, depending on what is required + # Need a variable for processed content to be added to texout + processed = None + if depy_typeset == 'c': + if depy_type == 'cmd': + # Make sure that `after` contains two lines of content + # This is needed for some replacements that need to look ahead + if after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + processed, texcontent = replace_code_cmd(depy_name, arglist, + depy_linenum, + code_replacement, + code_replacement_mode, + after, depy_lexer, + firstnumber) + else: # depy_type == 'env' + end_environment = r'\end{' + depy_name + '}' + if code_replacement is None: + if end_environment not in after: + while True: + texlinenum += 1 + after += tex[texlinenum] + if end_environment in tex[texlinenum]: + break + code_replacement, after = after.split(end_environment, 1) + # If there's content on the line with the end-environment + # command, it should be discarded, to imitate TeX + if not code_replacement.endswith('\n'): + code_replacement = code_replacement.rsplit('\n', 1)[0] + '\n' + # Take care of `gobble` + if settings['gobble'] == 'auto': + code_replacement = textwrap.dedent(code_replacement) + else: + if end_environment not in after: + while True: + texlinenum += 1 + if end_environment in tex[texlinenum]: + after = tex[texlinenum] + break + after = after.split(end_environment, 1)[1] + # Make sure that `after` contains two lines of content + # This is needed for some replacements that need to look ahead + if after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + processed, texcontent = replace_code_env(depy_name, arglist, + depy_linenum, + code_replacement, + code_replacement_mode, + after, depy_lexer, + firstnumber) + elif depy_typeset == 'p' and print_replacement is not None: + if depy_type == 'cmd': + # Make sure that `after` contains two lines of content + # This is needed for some replacements that need to look ahead + if after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + processed, texcontent = replace_print_cmd(depy_name, arglist, + depy_linenum, + print_replacement, + print_replacement_mode, + source, + after) + else: # depy_type == 'env' + end_environment = r'\end{' + depy_name + '}' + if end_environment not in after: + while True: + texlinenum += 1 + if end_environment in tex[texlinenum]: + after = tex[texlinenum] + break + after = after.split(end_environment, 1)[1] + # Make sure that `after` contains two lines of content + # This is needed for some replacements that need to look ahead + if after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + processed, texcontent = replace_print_env(depy_name, arglist, + depy_linenum, + print_replacement, + print_replacement_mode, + source, + after) + else: # depy_typeset == 'n' or (depy_typeset == 'p' and print_replacement is None): + if depy_type == 'cmd': + texcontent = after + else: # depy_type == 'env' + end_environment = r'\end{' + depy_name + '}' + if end_environment not in after: + while True: + texlinenum += 1 + if end_environment in tex[texlinenum]: + after = tex[texlinenum] + break + after = after.split(end_environment, 1)[1] + if bool(match(r'\s*\n', after)): + # If the line following `after` is whitespace, it should + # be stripped, since most environments throw away + # anything after the end of the environment + after = after.split('\n')[1] + texcontent = after + # #### Once it's supported on the TeX side, need to add support for + # pc and cp + + + # Store any processed content + if processed is not None: + texout.append(processed) + + +# Transfer anything that's left in tex to texout +texout.append(texcontent) +texout.extend(tex[texlinenum+1:]) + + + + +# Replace the `\usepackage{pythontex}` +for n, line in enumerate(texout): + if '{pythontex}' in line: + startline = n + while '\\usepackage' not in texout[startline] and startline >= 0: + startline -= 1 + if startline == n: + if bool(search(r'\\usepackage(?:\[.*?\]){0,1}\{pythontex\}', line)): + texout[n] = sub(r'\\usepackage(?:\[.*?\]){0,1}\{pythontex\}', '', line) + if texout[n].isspace(): + texout[n] = '' + break + else: + content = ''.join(texout[startline:n+1]) + if bool(search(r'(?s)\\usepackage(?:\[.*?\]\s*){0,1}\{pythontex\}', content)): + replacement = sub(r'(?s)\\usepackage(?:\[.*?\]\s*){0,1}\{pythontex\}', '', content) + if replacement.isspace(): + replacement = '' + texout[startline] = replacement + for l in range(startline+1, n+1): + texout[l] = '' + break + elif line.startswith(r'\begin{document}'): + break +if preamble_additions: + texout[n] += '\n'.join(preamble_additions) + '\n' +# Take care of graphicspath +if args.graphicspath and settings['graphicx']: + for n, line in enumerate(texout): + if '\\graphicspath' in line and not bool(match(r'\s*%', line)): + texout[n] = line.replace('\\graphicspath{', '\\graphicspath{{' + settings['outputdir'] +'/}') + break + elif line.startswith(r'\begin{document}'): + texout[n] = '\\graphicspath{{' + settings['outputdir'] + '/}}\n' + line + break + + + + +# Print any final messages +if forced_double_space_list: + print('* DePythonTeX warning:') + print(' A trailing double space was forced with "\\space{}" for the following') + print(' This can happen when printed content is included inline') + print(' The forced double space is only an issue if it is not intentional') + for name, linenum in forced_double_space_list: + print(' "' + name + '" near line ' + str(linenum)) + + + + +# Write output +if args.output is not None: + for line in texout: + outfile.write(line) + outfile.close() +else: + if sys.version_info[0] == 2: + sys.stdout = codecs.getwriter(encoding)(sys.stdout, 'strict') + sys.stderr = codecs.getwriter(encoding)(sys.stderr, 'strict') + else: + sys.stdout = codecs.getwriter(encoding)(sys.stdout.buffer, 'strict') + sys.stderr = codecs.getwriter(encoding)(sys.stderr.buffer, 'strict') + for line in texout: + sys.stdout.write(line) diff --git a/pythontex/pythontex.bat b/pythontex/pythontex.bat new file mode 100644 index 0000000..0dfa442 --- /dev/null +++ b/pythontex/pythontex.bat @@ -0,0 +1,2 @@ +@echo off +pythontex.py %* diff --git a/pythontex/pythontex.dtx b/pythontex/pythontex.dtx index 198a21d..be9c287 100644 --- a/pythontex/pythontex.dtx +++ b/pythontex/pythontex.dtx @@ -1,6 +1,6 @@ % \iffalse meta-comment % -% Copyright (C) 2012-2013 by Geoffrey M. Poore +% Copyright (C) 2012-2026 by Geoffrey M. Poore % --------------------------------------------------------------------------- % This work may be distributed and/or modified under the % conditions of the LaTeX Project Public License, either version 1.3 @@ -26,7 +26,7 @@ %\NeedsTeXFormat{LaTeX2e}[1999/12/01] %\ProvidesPackage{pythontex} %<*package> - [2013/01/23 Version~0.10beta2 PythonTeX] + [2026/02/15 v0.19 execute and typeset Python code and other languages] % % %<*driver> @@ -34,41 +34,172 @@ \makeatletter \usepackage[T1]{fontenc} \usepackage[utf8]{inputenc} +\usepackage{lmodern} +\usepackage{microtype} + \usepackage{textcomp} \usepackage[svgnames]{xcolor} \usepackage{environ} \usepackage{enumitem} -\usepackage[fixlr=false]{pythontex} %Need fixlr=false so can demo issue +\usepackage{fvextra} +% The typesetting for macrocode doesn't use \@noligs, which upquote modifies. +% So apply the upquote fix to \verbatim@nolig@list as well, which is in macrocode. +\begingroup +\catcode`'=\active +\catcode``=\active +\g@addto@macro\verbatim@nolig@list{% + \let'\textquotesingle + \let`\textasciigrave + \ifx\encodingdefault\upquote@OTone + \ifx\ttdefault\upquote@cmtt + \def'{\char13 }% + \def`{\char18 }% + \fi\fi} +\endgroup +\usepackage{pythontex} \usepackage{hyperref} -\hypersetup{colorlinks=true,urlcolor=Green,linkcolor=blue} +\hypersetup{ + pdftitle=The PythonTeX package, + pdfauthor=Geoffrey M. Poore, + pdfsubject={PythonTeX LaTeX package manual}, + colorlinks=true, + allcolors=ForestGreen, +} +\usepackage{cleveref} + \newcommand{\pytxtodo}[1]{} %\newcommand{\pytxtodo}[1]{\textcolor{red}{TO~DO: \scantokens{#1}}} \newcommand{\changestext}{} \NewEnviron{changelog}[2]{% - \g@addto@macro\changestext{\item[#1] (#2) \begin{itemize}}% + \g@addto@macro\changestext{\subsection*{\textbf{#1} (#2)}}% \expandafter\g@addto@macro\expandafter\changestext\expandafter{\BODY}% - \g@addto@macro\changestext{\end{itemize}}% + \g@addto@macro\changestext{}% } \newcommand{\PrintChangelog}{% \addcontentsline{toc}{section}{Version History} \section*{Version History}% - \begin{description}% \changestext - \end{description}% } -\EnableCrossrefs -\CodelineIndex + +% Create a short verbatim pipe that handles quotation marks properly +\begingroup +\catcode`\|=\active +\gdef\pipe@active@verbatim{% + \begingroup + \let\do\@makeother\dospecials + \catcode`\|=\active + \catcode`\`=\active + \catcode`\'=\active + \catcode`\<=\active + \catcode`\>=\active + \catcode`\-=\active + \catcode`\,=\active + \catcode`\ =\active + \pipe@active@verbatim@i} +\gdef\pipe@active@verbatim@i#1|{% + \endgroup + \begingroup + \def\FV@SV@pipe@active@verbatim{% + \FV@Gobble + \expandafter\FV@ProcessLine\expandafter{#1}}% + %\let\FV@BeginVBox\relax + %\let\FV@EndVBox\relax + %\def\FV@BProcessLine##1{\FancyVerbFormatLine{##1}}% + \BUseVerbatim{pipe@active@verbatim}% + \endgroup} +\AtBeginDocument{\let|\pipe@active@verbatim} +\endgroup + +\renewenvironment{verbatim}% + {\VerbatimEnvironment\begin{Verbatim}[gobble=1]}% + {\end{Verbatim}} + + +\def\MacroFont{% + \fontencoding\encodingdefault% + \fontfamily\ttdefault% + \fontseries\mddefault% + \fontshape\updefault% + \small} + +\def\PrintMacroName#1{{\strut\MacroFont\color{DarkGreen}\footnotesize\string #1\ }} + +\def\PrintDescribeMacro#1{\parbox[t]{1in}{\strut\MacroFont\textcolor{DarkGreen}{\string #1\ }}} +\let\PrintDescribeEnv\PrintDescribeMacro +%\let\PrintMacroName\PrintDescribeMacro +\let\PrintEnvName\PrintDescribeEnv + +\def\theCodelineNo{\textcolor{DarkGreen}{\sffamily\scriptsize{\arabic{CodelineNo}}}} + + +\renewenvironment{macro}{\macro@custom@arg}{} +\def\macro@custom@arg{% + \begingroup\makeatletter\macro@custom@arg@i} +\def\macro@custom@arg@i#1{% + \makeatother + \par\noindent + \ifstrempty{#1}% + {~\par}% + {\ttfamily\color{DarkGreen}\hspace*{-0.5in}% + \macro@custom@arg@split#1,\FV@Sentinel\par}% + \endgroup} +\def\macro@custom@arg@spacegobble#1{#1} +\def\macro@custom@arg@split#1,#2\FV@Sentinel{% + \expandafter\string\macro@custom@arg@spacegobble#1% + \if\relax\detokenize{#2}\relax + \expandafter\@firstoftwo + \else + \expandafter\@secondoftwo + \fi + {}{\\\hspace*{-0.5in}\macro@custom@arg@split#2\FV@Sentinel}} + +\renewenvironment{environment}{\env@custom@arg}{} +\def\env@custom@arg{% + \begingroup\makeatletter\env@custom@arg@i} +\def\env@custom@arg@i#1{% + \makeatother + \ifstrempty{#1}% + {~\par}% + {\ttfamily\color{DarkGreen}\hspace*{-0.5in}% + \env@custom@arg@split#1,\FV@Sentinel\par}% + \endgroup} +\def\env@custom@arg@spacegobble#1{#1} +\def\env@custom@arg@split#1,#2\FV@Sentinel{% + \env@custom@arg@spacegobble#1{\textrm{ \textit{(env.)}}}% + \if\relax\detokenize{#2}\relax + \expandafter\@firstoftwo + \else + \expandafter\@secondoftwo + \fi + {}{\\\hspace*{-0.5in}\env@custom@arg@split#2\FV@Sentinel}} + + +\let\orig@footnote\footnote +\renewcommand{\footnote}{% + \begingroup + \let\do\@makeother + \dospecials + \catcode`\{=1 + \catcode`\}=2 + \new@footnote} +\newcommand{\new@footnote}[1]{% + \endgroup + \orig@footnote{\scantokens{#1}}} + + +%\EnableCrossrefs +%\CodelineIndex %\RecordChanges \makeatother \begin{document} \DocInput{pythontex.dtx} %\PrintChanges - \PrintIndex + %\PrintIndex \end{document} % % \fi % -% \CheckSum{2144} +% \CheckSum{3289} % % \CharacterTable % {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z @@ -87,7 +218,241 @@ % Right brace \} Tilde \~} % % +% +% \begin{changelog}{v0.19}{2026/02/15} +% \begin{itemize} +% \item Fixed backslash escapes in docstrings and regexes for syntax compatibility with Python 3.11+. +% \item \texttt{js} now works as a command family for JavaScript (\#197). +% \item Python console commands and environments are now compatible with Python 3.14 (\#229). +% \item Fixed a bug that could cause non-Windows operating systems to attempt to invoke a nonexistent batch file when the executable associated with a language is not found (\#225). +% \item Replaced doctype \texttt{tex} with \texttt{texminted} for \texttt{Weave.jl} (\#219). +% \item Fixed a bug that resulted in uncaught exceptions when \Verb{\inputpygments} was used with files that do not exist (\#64). +% \item Fixed a bug in tracking the number of Pygments errors (\#206). +% \item Improved error messages (\#207, \#209). Improved documentation for \Verb{--error-exit-code} (\#208). +% \item Several minor improvements to documentation. +% \end{itemize} +% \end{changelog} +% +% +% \begin{changelog}{v0.18}{2021/06/06} +% \begin{itemize} +% \item \texttt{\string\inputpygments} now checks inputted files for modification, so that typeset code will correctly update when the source is changed (\#162). +% \item Julia now uses project flag ``\texttt{-{}-project=@.}'' (\#157, \#158). +% \item Fixed bug in processing Pygments options (\texttt{pygopt}) when a key is used without a value (\#181). +% \item Some error handling for Windows was incompatible with other operating systems: replaced checks for \texttt{WindowsError} with checks for \texttt{OSError} (\#177). +% \item Rust support is now compatible with document and working directory paths that contain spaces (\#167). +% \end{itemize} +% \end{changelog} +% +% +% \begin{changelog}{v0.17}{2019/09/22} +% \begin{itemize} +% \item Pygments syntax highlighting for the Python console (\texttt{pycon} lexer) now uses the \texttt{python3} option, and the default Python lexer is now \texttt{python3} (\#156). +% \item Added support for JavaScript (\#147; thanks to Nathan Carter). +% \item Updated Julia support for Julia versions 0.6 (\#107), and 0.7 and 1.0 (\#126, \#130). +% \item There are now meaningful error messages for the Julia console when Weave.jl is not installed or raises errors (\#131). +% \item \texttt{pythontexcustomcode} and \texttt{\string\pythontexcustomc} now set \texttt{pytex.context} (\#65). +% \item Added support for R. The \texttt{R} family of commands and environments (\texttt{\string\R}, \texttt{\string\Rc}, \texttt{Rcode}, ...) executes code as a script. There is currently no utilities class or equivalent. The \texttt{Rcon} family (\texttt{Rconsole}) executes code to emulate an interactive R session (\#121). +% \item \texttt{fancyvrb} settings from \texttt{\string\setpythontexfv} and console environments now work with Julia and R consoles. +% \item \texttt{pythontexcustomcode} now works with \texttt{juliacon}. There are now proper \texttt{juliaconcode} and \texttt{Rconcode} environments that execute code but typeset nothing, to parallel \texttt{pyconcode} (\#134). +% \item Added support for Perl with the \texttt{perl} and \texttt{pl} families of commands and environments. There is currently no utilities class or equivalent. +% \item Added support for Perl 6 with the \texttt{perlsix} and \texttt{psix} families of commands and environments (\#104). There is currently no utilities class or equivalent. +% \item Updated Rust support by using \texttt{dyn} with traits in utilities object. +% \item Under Windows, capitalization of script paths in \texttt{stderr} is now preserved. +% \item Fixed a bug that prevented the \texttt{sub} environment from working with \texttt{depythontex} (\#155). +% \item Fixed a bug in checking mtime of dependencies to see if they have been modified while \texttt{pythontex} is running. The check failed for dependencies that do not exist or were deleted before \texttt{pythontex} can read them (\#136). +% \end{itemize} +% \end{changelog} +% +% +% \begin{changelog}{v0.16}{2017/07/20} +% \begin{itemize} +% \item Added preliminary console support for Julia (\#98). +% \item Fixed Python console compatibility with Python 3.6 by setting the \texttt{code} module's new \texttt{exitmsg} argument to suppress the exit message (\#100). +% \item Improved Rust support, including tracking of created files and dependencies (\#91). +% \end{itemize} +% \end{changelog} +% +% +% \begin{changelog}{v0.15}{2016/07/21} +% \textbf{New features} +% \begin{itemize} +% \item The \texttt{fvextra} package is now required. This provides line breaking with fine-grained control over break locations, the ability to highlight specific lines or ranges of lines, improved handling of tabs, and several additional features. +% \item Added \texttt{sub} commands and environments (\texttt{\string\pys}, \texttt{pysub}, ...). These commands and environments perform string interpolation on text. Fields delimited by \texttt{!\{...\}} are replaced by the result of evaluating and then printing their content. This works for all families of commands and environments, not just Python. See the documentation for details about field delimiters and escaping. +% \item Added \texttt{rust} and \texttt{rs} families of commands and environments. These provide essentially complete support for Rust, except that \texttt{rstex.formatter()}, \texttt{rstex.before()}, and \texttt{rstex.after()} will likely need additional refinement (\#90). +% \item Added the \texttt{sage} family of commands and environments, which provide support for Sage (\#63). +% \item Added \texttt{bash} family of commands and environments. This provides basic support for bash (no utilities class or equivalent). Bash works with Windows if it is installed. +% \item Improved \texttt{console} compatibility under Linux with Python 3 (\#70). +% \item Counters for default sessions are now created automatically. This prevents counter errors under some circumstances when working with \texttt{\string\includeonly}. +% \item Commands like \texttt{\string\py} can now output verbatim content under LuaTeX. +% \end{itemize} +% \textbf{Bugfixes} +% \begin{itemize} +% \item Fixed a bug that could cause an endless loop when a \texttt{code} command or environment printed a \texttt{code} command or environment of the same family with \texttt{autoprint=true}. +% \end{itemize} +% \end{changelog} +% +% +% \begin{changelog}{v0.14}{2014/07/17} +% \textbf{New features} +% \begin{itemize} +% \item All commands for working with code inline are now robust, via \texttt{etoolbox}'s \texttt{\string\newrobustcmd}. Among other things, this allows commands like \texttt{\string\py} to work in standard captions that have not been redefined to avoid protection issues. +% \item Upgraded \texttt{syncpdb} to v0.2, which provides better list formatting. +% \end{itemize} +% \textbf{Backward-incompatible changes} +% \begin{itemize} +% \item The default working directory is now the main document directory instead of the output directory. Using the output directory was a common source of confusion for new users and was incompatible with plans for future development. Old documents in which the working directory was not specified will continue to use the output directory, but PythonTeX will print an upgrade message; new documents will use the new setting. The output directory may be selected as the working directory manually, or with the shorthand ``\texttt{\string\setpythontexworkingdir\{\}}''. +% \item Standardized version numbering by removing the ``v'' prefix from the stored version numbers in Python variables and LaTeX macros. Standardized the PythonTeX scripts by renaming \texttt{version} to \texttt{\_\_version\_\_}. +% \end{itemize} +% \end{changelog} +% +% \begin{changelog}{v0.13}{2014/07/14} +% \textbf{New features} +% \begin{itemize} +% \item Added \texttt{-{}-interactive} command-line option. This runs a single session in interactive mode, allowing user input. Among other things, this is useful when working with debuggers. +% \item Added \texttt{-{}-debug} command-line option. This runs a single session with the default debugger in interactive mode. Currently, only standard (non-console) Python sessions are supported. The default Python debugger is the new \texttt{syncpdb}, which wraps \texttt{pdb} and synchronizes code line numbers with document line numbers. All \texttt{pdb} commands that take a line number or filename:lineno as an argument will refer to document files and line numbers when the argument has a percent symbol (\texttt{\%}) as a prefix. For example, \texttt{list \%50} lists code that came from around line 50 in the document. The \texttt{-{}-debug} option will support other languages and provide for customization in the future. +% \item Added command-line option \texttt{-{}-jobs}, which allows the maximum number of concurrent processes to be specified (\#35). +% \item Added support for GNU Octave, via the \texttt{octave} family of commands and environments (\#36). Parsing of Octave stderr is not ideal, though synchronization works in most cases; this will be addressed by a future rewrite of the stderr parser. +% \item Installer now automatically works with MiKTeX, not just TeX Live. +% \item The PythonTeX utilities class has a new \texttt{open()} method that opens files and automatically tracks dependencies/created files. +% \item When \texttt{pythontex2.py} and \texttt{pythontex3.py} are run directly, the Python interpreter is automatically set to a reasonable default (\texttt{py -2} or \texttt{py -3} under Windows, using the Python 3.3+ wrapper; \texttt{python2} or \texttt{python3} under other systems). +% \item The installer now creates symlinks for the numbered scripts \texttt{pythontex*.py} and \texttt{depythontex*.py}. +% \item Added Python version checking to all numbered scripts. +% \item Under Python, the type of data passed via \texttt{\string\setpythontexcontext} may now be set using YAML-style tags (\texttt{!!str}, \texttt{!!int}, \texttt{!!float}). For example, \texttt{\string\setpythontexcontext\{myint=!!int 123\}}. +% \item The \texttt{fancyvrb} options \texttt{firstline} and \texttt{lastline} now work with the \texttt{pygments} environment and \texttt{\string\inputpygments} command. This required some additional patching of \texttt{fancyvrb}. +% \item The \texttt{pytx@Verbatim} and \texttt{pytx@SaveVerbatim} environments are now used for typesetting verbatim code. These are copies of the \texttt{fancyvrb} environments. This prevents conflicts when literal \texttt{Verbatim} and \texttt{SaveVerbatim} environments need to be typeset. +% \item Improved \texttt{latexmk} compatibility (\#40). Added discussion of \texttt{latexmk} usage to documentation. +% \item Tildes \texttt{\string~} may now be used in \texttt{outputdir} and \texttt{workingdir} to refer to the user's home directory, even under Windows. +% \end{itemize} +% \textbf{Bugfixes} +% \begin{itemize} +% \item Fixed a bug that prevented created files from being cleaned up when the working directory was not the document root directory and the full path to the files was not provided. +% \item Fixed a bug that prevented the \texttt{fvextfile} option from working when external files were highlighted. +% \end{itemize} +% \end{changelog} +% +% \begin{changelog}{v0.13-beta}{2014/02/06} +% \textbf{New features} +% \begin{itemize} +% \item Switching to GitHub's Releases for downloads. +% \item TeX information such as page dimensions may now be easily passed to the programming-language side, using the new \texttt{\string\setpythontexcontext} command. Contextual information is stored in the \texttt{context} attribute of the utilities class, which is a dictionary (and also has attributes in Python). +% \item The utilities class now has \texttt{pt\_to\_in()}, \texttt{pt\_to\_cm()}, and \texttt{pt\_to\_mm()} methods for converting units of TeX points into inches, centimeters, and millimeters. These work with integers and floats, as well as strings that consist of numbers and optionally end in ``pt''. There is also a \texttt{pt\_to\_bp()} for converting TeX points (1/72.27 inch) into big (DTP or PostScript) points (1/72 inch). +% \item Expanded Quickstart. Quickstart is now compatible with all LaTeX engines. Quickstart now avoids \texttt{microtype} issues on some systems (\#32). +% \item Added information on citing PythonTeX (\#28). +% \item Utilities class has a new attribute \texttt{id}, which is a string that joins the command family name, session name, and session restart parameters with underscores. This may be used in creating files that need a name that contains a unique, session-based identifier (for example, names for figures that are saved automatically). +% \end{itemize} +% \textbf{Backward-incompatible changes} +% \begin{itemize} +% \item All utilities-class attributes with names of the form \texttt{input\_*} have been renamed with the ``\texttt{input\_}'' removed. Among other things, this makes it easier to access the \texttt{context} attribute (\texttt{pytex.context} vs.\ \texttt{pytex.input\_context}). +% \item \texttt{depythontex} now has \texttt{-o} and \texttt{-{}-output} command-line options for specifying the name of the output file. If an output file is not specified, then output is written to \texttt{stdout}. This allows \texttt{depythontex} output to be piped to another program. +% \item All scripts \texttt{*2.py} now have shebangs with \texttt{env python2}, and all scripts \texttt{*3.py} now have shebangs with \texttt{env python3}. This allows the wrapper scripts (\texttt{env python} shebang) to be used with the default Python installation, and the numbered scripts to be used with specific versions. Remember that except for console content, the \texttt{-{}-interpreter} option is what determines the Python version that actually executes code. The version of Python used to launch \texttt{pythontex.py} merely determines the version that manages code execution. (\texttt{-{}-interpreter} support for console content is coming.) +% \item Changed the template style used in the \texttt{CodeEngine} class. Replacement fields are now surrounded by single curly braces (as in Python's format string syntax), rather than double curly braces. Literal curly braces are obtained by doubling braces. This allows the use of literal adjacent double braces in templates, which was not possible previously. +% \item The Julia template now uses the new \texttt{in()} function, replacing \texttt{contains()}. This requires Julia v0.2.0+. +% \end{itemize} +% \textbf{Bugfixes} +% \begin{itemize} +% \item Modified test for LuaTeX, so that \texttt{\string\directlua} is not \texttt{\string\let} to \texttt{\string\relax} if it does not exist. This was causing incompatibility with \texttt{babel} under pdfTeX and XeTeX (\#33). +% \item Added missing shebangs to \texttt{depythontex*.py}. Handling of \texttt{utilspath} is now more forgiving, so that \texttt{pythontex\_utils.py} can be installed in alternate locations (\#23). +% \item \texttt{depythontex} no longer leaves a blank line where \texttt{\string\usepackage\{pythontex\}} was removed. +% \item Console environments typeset with \texttt{fancyvrb} no longer end with an unnecessary empty line. +% \item Fixed bug in installer when \texttt{kpsewhich} was not found (\#21). +% \end{itemize} +% \end{changelog} +% +% \begin{changelog}{v0.12}{2013/08/26} +% \begin{itemize} +% \item Added support for the Julia language, with the \texttt{julia} and \texttt{jl} families of commands and environments. (Note that Pygments only added Julia support in version 1.6.) +% \item Warnings and errors are now synchronized with the line numbers of files brought in via \texttt{\string\input}, \texttt{\string\include}, etc. This is accomplished using the \texttt{currfile} package. +% \item Added package option \texttt{gobble}. When \texttt{gobble=auto}, all code is dedented before being executed and/or typeset. The current implementation is functional but basic; it will be improved and extended in the future. +% \item The document root directory is now always added to \texttt{sys.path} (or its equivalent), even when it is not the working directory. (The working directory has been added to \texttt{sys.path} since v0.12beta.) The document directory is added after the working directory, so that the working directory has precedence. +% \item Fixed a bug in \texttt{console} commands and environments; \texttt{sys.path} now contains the working and document directories, and the working directory is now the output directory by default. This parallels the behavior of non-\texttt{console} commands and environments. +% \item Added command-line option \texttt{-{}-interpreter} that allows an interpreter to be invoked via a specific command. This allows, for example, a specific version of Python to be invoked. +% \item Improved synchronization of stderr in cases when an error is triggered far after its origin (for example, an error caused by a multiline string that is lacking a closing quote/delimiter, and thus may span several chunks of user code). +% \item Modified usage of the \texttt{shlex} module to work around its lack of Unicode support in Python versions prior to 2.7.3. +% \item Fixed a bug from v0.12beta that prevented \texttt{\string\inputpygments} from working when \texttt{pygments=true}. +% \item Fixed a bug with counters that caused errors when content spanning multiple columns was created within a \texttt{tabular} environment. +% \item Added checking for compatible Python versions in \texttt{pythontex.py}. +% \item Improved execution of \texttt{*.bat} and \texttt{*.cmd} files under Windows. The solution from v0.12beta allowed \texttt{*.bat} and \texttt{*.cmd} to be found and executed when the extension was not given, but did not give correct return codes. +% \end{itemize} +% \end{changelog} +% +% \begin{changelog}{v0.12beta}{2013/06/24} +% \begin{itemize} +% \item Merged \texttt{pythontex\_types*.py} into a single replacement \texttt{pythontex\_engines.py} compatible with both Python 2 and 3. It is now much simpler to add support for additional languages. +% \item Added support for the Ruby language as a demonstration of new capabilities. The \texttt{ruby} and \texttt{rb} families of commands and environments may be enabled via the new \texttt{usefamily} package option. Support for additional languages is coming soon. See the new section in the documentation on support for other languages for more information. +% \item Reimplemented treatment of Pygments content for better efficiency. Now a Pygments process only runs if there is content to highlight. Eliminated redundant highlighting of unmodified code. +% \item Improved treatment of dependencies. If a dependency is modified (\texttt{os.path.getmtime()}) after the current PythonTeX run starts, then code that depends on it will be re-executed the next time PythonTeX runs. A message is also issued to indicate that this is the case. +% \item The utilities class now has \texttt{before()} and \texttt{after()} methods that are called immediately before and after user code. These may be redefined to customize output. For example, LaTeX commands could be printed before and after user code; stdout could be redirected to \texttt{StringIO} for further processing; or matplotlib figures could be automatically detected, saved, and included in the document. +% \item Added explanation of how to track dependencies and created files automatically, and how to include matplotlib figures automatically, to the documentation for the PythonTeX utilities class. +% \item Created a new system for parsing and synchronizing stderr. +% \begin{itemize} +% \item Exceptions that do not reference a line number in user code (such as those from \texttt{warnings.warn()} in a module) are now traced back to a single command or environment. Previously no synchronization was attempted. This is accomplished by writing delimiters to stderr before executing the code from each command/environment. +% \item Exceptions that do reference a line in user code are more efficiently synchronized with a document line number. This is accomplished by careful record keeping as each script is assembled. Line number synchronization no longer involves parsing the script that was executed. +% \item Improved and generalized parsing of stderr, in preparation for supporting additional languages. Exceptions that cannot be identified as errors or warnings are treated based on \texttt{Popen.returncode}. +% \end{itemize} +% \item Created a new system for \texttt{console} content. +% \begin{itemize} +% \item There are now separate families of \texttt{console} commands and environments. No Pygments or \texttt{fancyvrb} settings are shared with the non-\texttt{console} families, as was previously the case. There is a new family of commands and environments based on \texttt{pycon}, including the \texttt{\string\pycon} command (inline reference to console variable), \texttt{pyconsole} environment (same as the old one), \texttt{\string\pyconc} and \texttt{pyconcode} (execute only), and \texttt{\string\pyconv} and \texttt{pyconverbatim} (typeset only). There are equivalent families based on \texttt{pylabcon} and \texttt{sympycon}. +% \item Each console session now runs in its own process and is cached individually. Console output is now cached so that changing Pygments settings no longer requires re-execution. +% \item Unicode is now supported under Python 2. +% \item The new package option \texttt{pyconfuture} allows automatic imports from \texttt{\_\_future\_\_} for \texttt{console} families under Python 2, paralleling the \texttt{pyfuture} option. +% \item Any errors or warnings caused by code that is not typeset (\texttt{code} command and environment, startup code) are reported in the run summary. This ensures that such code does not create mischief. +% \item \texttt{customcode} is now supported for \texttt{console} content. +% \end{itemize} +% \item Better support for \texttt{latexmk} and similar build tools. PythonTeX creates a file of macros (\texttt{*.pytxmcr}) that is always included in a document, and thus can be automatically detected and tracked by \texttt{latexmk}. This file now contains the time at which PythonTeX last created files. When new files are created, the macro file will have a new hash, triggering another document compile. +% \item Improved the way in which the PythonTeX \texttt{outputdir} is added to the graphics path. This had been done with \texttt{\string\graphicspath}, but that overwrites any graphics path previously specified by the user. Now the \texttt{outputdir} is appended to any pre-existing path. +% \item Added the \texttt{depythontex} option \texttt{-{}-graphicspath}. This adds the \texttt{outputdir} to the graphics path of the \texttt{depythontex} document. +% \item The installer now provides more options for installation locations. It will now create missing directories if desired. +% \item The working directory (\texttt{workingdir}) is now appended to \texttt{sys.path}, so that code there may be imported. +% \item Under Windows, \texttt{subprocess.Popen()} is now invoked with \texttt{shell=True} if \texttt{shell=False} results in a WindowsError. This allows commands involving \texttt{*.bat} and \texttt{*.cmd} files to be executed when the extension is not specified; otherwise, only \texttt{*.exe} can be found and run. +% \item The path to utils is now found in \texttt{pythontex.py} via \texttt{sys.path[0]} rather than \texttt{kpsewhich}. This allows the PythonTeX scripts to be executed in an arbitrary location; they no longer must be installed in a texmf tree where \texttt{kpsewhich} can find them. +% \item Added \texttt{rerun} value \texttt{never}. +% \item At the end of each run, data and macros are only saved if modified, improving efficiency. +% \item The number of temporary files required by each process was reduced by one. All macros for commands like \texttt{\string\py} are now returned within stdout, rather than in their own file. +% \item Fixed a bug with \texttt{\string\stderrpythontex}; it was defaulting to \texttt{verb} rather than \texttt{verbatim} mode. +% \end{itemize} +% \end{changelog} +% +% \begin{changelog}{v0.11}{2013/04/21} +% \begin{itemize} +% \item As the first non-beta release, this version adds several features and introduces several changes. You should read these release notes carefully, since some changes are not backwards-compatible. Changes are based on a thorough review of all current and planned features. PythonTeX's capabilities have already grown beyond what was originally intended, and a long list of features still remains to be implemented. As a result, some changes are needed to ensure consistent syntax and naming in the future. Insofar as possible, all command names and syntax will be frozen after this release. +% \item Added the \texttt{pythontex.py} and \texttt{depythontex.py} wrapper scripts. When run, these detect the current version of Python and import the correct PythonTeX code. It is still possible to run \texttt{pythontex*.py} and \texttt{depythontex*.py} directly, but the new wrapper scripts should be used instead for simplicity. There is now only a single \texttt{pythontex\_utils.py}, which works with both Python 2 and Python 3. +% \item Added the \texttt{beta} package option. This makes the current version behave like v0.11beta, for compatibility. This option is temporary and will probably only be retained for a few releases. +% \item Backward-incompatible changes (require the \texttt{beta} option to restore old behavior) +% \begin{itemize} +% \item The \texttt{pyverb} environment has been renamed \texttt{pyverbatim}. The old name was intended to be concise, but promoted confusion with LaTeX's \texttt{\string\verb} macro. +% \item For \texttt{\string\printpythontex}, \texttt{\string\stdoutpythontex}, and \texttt{\string\stderrpythontex}, the modes \texttt{inlineverb} and \texttt{v} have been replaced by \texttt{verb}, and the old mode \texttt{verb} has been replaced by \texttt{verbatim}. This brings naming conventions in line with standard LaTeX \texttt{\string\verb} and \texttt{verbatim}, avoiding a source of potential confusion. +% \item The \texttt{\string\setpythontexpyglexer}, \texttt{\string\setpythontexpygopt}, and \texttt{\string\setpygmentspygopt} commands now take an optional argument and a mandatory argument, rather than two mandatory arguments. This creates better uniformity among current and planned settings macros. +% \item The \texttt{\string\setpythontexformatter} and \texttt{\string\setpygmentsformatter} commands have been replaced by the \texttt{\string\setpythontexprettyprinter} and \texttt{\string\setpygmentsprettyprinter} commands. This anticipates possible upcoming features. It also avoids potential confusion with Pygments's formatters and the utilities class's \texttt{formatter()} method. +% \end{itemize} +% \item Deprecated (still work, but raise warnings; after a few releases, they will raise errors instead, and after that eventually be removed) +% \begin{itemize} +% \item The \texttt{rerun} setting \texttt{all} was renamed \texttt{always}, in preparation for upcoming features. +% \item The \texttt{stderr} option is replaced by \texttt{makestderr}. The \texttt{print}/\texttt{stdout} option is replaced by \texttt{debug}. These are intended to prevent confusion with future features. +% \item The \texttt{fixlr} option is deprecated. It was originally introduced to deal with some of SymPy's LaTeX formatting, which has since changed. +% \item The utilities class method \texttt{init\_sympy\_latex()} is deprecated. The \texttt{sympy\_latex()} and \texttt{set\_sympy\_latex()} methods now automatically initialize themselves on first use. +% \end{itemize} +% \item Added \texttt{autostdout} package option and \texttt{\string\setpythontexautostdout}, to complement \texttt{autoprint}. Added |prettyprinter| and |prettyprintinline| package options to complement new settings commands. +% \item Added quickstart guide. +% \item Installer now installs gallery and quickstart files, if present. +% \end{itemize} +% \end{changelog} +% +% \begin{changelog}{v0.11beta}{2013/02/17} +% \begin{itemize} +% \item Commands like \texttt{\string\py} can now bring in any valid LaTeX code, including verbatim content, under the pdfTeX and XeTeX engines. Verbatim content was not allowed previously. LuaTeX cannot bring in verbatim, due to a known bug. +% \item Added package option \texttt{depythontex} and scripts \texttt{depythontex*.py}. These allow a PythonTeX document to be converted into a pure LaTeX document, with no Python dependency. The package option creates an auxiliary file with extension \texttt{.depytx}. The \texttt{depythontex*.py} scripts take this auxiliary file and the original LaTeX document, and combine the two to produce a new document that does not rely on the PythonTeX package. All PythonTeX commands and environments are replaced by their output. All Python-generated content is substituted directly into the document. By default, all typeset code is wrapped in \texttt{\string\verb} and \texttt{verbatim}, but \texttt{depythontex*.py} has a \texttt{-{}-listing} option that allows \texttt{fancyvrb}, \texttt{listings}, \texttt{minted}, or \texttt{pythontex} to be used instead. +% \item The current PythonTeX version is now saved in the \texttt{.pytxcode}. If this does not match the version of the PythonTeX scripts, a warning is issued. This makes it easier to determine errors due to version mismatches. +% \item Fixed an incompatibility with the latest release of \texttt{xstring} (version 1.7, 2013/01/13). +% \item Fixed a bug in the \texttt{console} environment that could cause problems when switching from Pygments highlighting to \texttt{fancyvrb} when using the \texttt{fvextfile} option. Fixed a bug introduced in the v0.10beta series that prevented the \texttt{console} environment from working with \texttt{fancyvrb}. +% \item Fixed a bug with PythonTeX verbatim commands and environments that use Pygments. The verbatim commands and environments were incorrectly treated as if they had the attributes of executed code in the v0.10beta series. +% \item Fixed a bug from the v0.10beta series that sometimes prevented imports from \texttt{\_\_future\_\_} from working when there were multiple sessions. +% \item Fixed a bug related to hashing dependencies' mtime under Python 3. +% \end{itemize} +% \end{changelog} +% % \begin{changelog}{v0.10beta2}{2013/01/23} +% \begin{itemize} % \item Improved \texttt{pythontex*.py}'s handling of the name of the file being processed. A warning is no longer raised if the name is given with an extension; extensions are now processed (stripped) automatically. The filename may now contain a path to the file, so you need not run \texttt{pythontex*.py} from within the document's directory. % \item Added command-line option \texttt{-{}-verbose} for more verbose output. Currently, this prints a list of all processes that are launched. % \item Fixed a bug that could crash \texttt{pythontex*.py} when the package option \texttt{pygments=false}. @@ -96,9 +461,11 @@ % \item Revised treatment of any content that custom code attempts to print. Custom code is not allowed to print to the document (see documentation). If custom code attempts to print, a warning is raised, and the printed content is included in the \texttt{pythontex*.py} run summary. % \item One-line entries in stderr, such as those produced by Python's \texttt{warnings.warn()}, were not previously parsed because they are of the form \texttt{::} rather than \texttt{line~}. These are now parsed and synchronized with the document. They are also correctly parsed for inclusion in the document via \texttt{\string\stderrpythontex}. % \item If the package option \texttt{stderrfilename} is changed, all sessions that produced errors or warnings are now re-executed automatically, so that their stderr content is properly updated with the new filename. +% \end{itemize} % \end{changelog} % % \begin{changelog}{v0.10beta}{2013/01/09} +% \begin{itemize} % \item Backward-incompatible: Redid treatment of command-line options for \texttt{pythontex*.py}, using Python's \texttt{argparse} module. Run \texttt{pythontex*.py} with option \texttt{-h} to see new command line options. % \item Deprecated: \texttt{\string\setpythontexcustomcode} is deprecated in favor of the \texttt{\string\pythontexcustomc} command and \texttt{pythontexcustomcode} environment. These allow entry of pure code, unlike \texttt{\string\setpythontexcustomcode}. These also allow custom code to be added to the beginning or end of a session, via an optional argument. Improved treatment of errors and warnings associated with custom code. % \item The summary of errors and warnings now correctly differentiates errors and warnings produced by user code, rather than treating all of them as errors. By default, \texttt{pythontex*.py} now returns an exit code of 1 if there were errors. @@ -127,9 +494,11 @@ % \item Fixed a bug that sometimes caused incorrect line numbers with \texttt{stderr} content. Improved processing of stderr. % \item Fixed a bug in automatic detection of pre-existing listings environment. % \item Improved the detection of imports from \texttt{\_\_future\_\_}. Detection should now be stricter, faster, and more accurate. +% \end{itemize} % \end{changelog} % % \begin{changelog}{v0.9beta3}{2012/07/17} +% \begin{itemize} % \item Added Unicode support, which required the Python code to be split into one set for Python 2 and another set for Python 3. This will require any old installation to be completely removed, and a new installation created from scratch. % \item Refactoring of Python code. Documents should automatically re-execute all code after updating to the new version. Otherwise, you should delete the PythonTeX directory and run PythonTeX. % \item Improved installation script. @@ -139,14 +508,19 @@ % \item Rewrote pythontex\_utils*.py, creating a new, context-aware interface to SymPy's LatexPrinter class. % \item Content brought in via macros no longer uses labels. Rather, long defs are used, which allows line breaks. % \item Pygments highlighting is now default for PythonTeX commands and environments +% \end{itemize} % \end{changelog} % % \begin{changelog}{v0.9beta2}{2012/05/09} +% \begin{itemize} % \item Changed Python output extension to .stdout. +% \end{itemize} % \end{changelog} % % \begin{changelog}{v0.9beta}{2012/04/27} +% \begin{itemize} % \item Initial public beta release. +% \end{itemize} % \end{changelog} % % @@ -160,27 +534,39 @@ % \providecommand*{\url}{\texttt} % \newcommand{\pytex}{Python\TeX} % \GetFileInfo{pythontex.dtx} -% \title{The \textsf{pythontex} package} -% \author{Geoffrey M.\ Poore \\ \url{gpoore@gmail.com}} +% \title{\vspace{-0.5in}The \textsf{pythontex} package} +% \author{Geoffrey M.\ Poore \\ \url{gpoore@gmail.com} \\ \href{https://github.com/gpoore/pythontex}{\texttt{github.com/gpoore/pythontex}}} % \date{\fileversion~from \filedate} % % \maketitle % +% % \begin{abstract} -% \pytex\ allows Python code entered within a \LaTeX\ document to be executed, and the output to be included within the original document. This provides access to the full power of Python from within \LaTeX, simplifying Python-\LaTeX\ workflow and making possible a range of document customization and automation. It also allows macro definitions that mix Python and \LaTeX\ code. In addition, \pytex\ provides syntax highlighting for many programming languages via the Pygments syntax highlighter. +% \pytex\ provides access to Python from within \LaTeX\ documents. It allows Python code entered within a \LaTeX\ document to be executed, and the results to be included within the original document. Python code may be adjacent to the figure or calculation it produces. The package also makes possible macro definitions that mix Python and \LaTeX\ code. In addition, \pytex\ provides syntax highlighting for many programming languages via the Pygments syntax highlighter. % -% \pytex\ is fast and user-friendly. Python code is only executed when it has been modified. When code is executed, it automatically attempts to run in parallel. If Python code produces errors, the error message line numbers are synchronized with the \LaTeX\ document line numbers, so that it is easy to find the misbehaving code. Code dependencies may be specified so that code is automatically re-executed whenever they change. +% \pytex\ is fast and user-friendly. Python code is only executed when it has been modified, or when user-specified criteria are met. When code is executed, user-defined sessions automatically run in parallel. If Python code produces errors, the error message line numbers are synchronized with the \LaTeX\ document line numbering, simplifying debugging. Dependencies may be specified so that code is automatically re-executed whenever they are modified. +% +% Because documents that use \pytex\ mix \LaTeX\ and Python code, they are less suitable than plain \LaTeX\ documents for journal submission, sharing, and conversion to other formats. \pytex\ includes a |depythontex| utility that creates a copy of a document in which all \pytex\ content is replaced by its output. +% +% While Python is the focus of \pytex, adding basic support for an additional language is usually as simple as creating a new class instance and a few templates, usually totaling less than 100 lines of code. The following languages already have built-in support: Ruby, Julia, Octave, Bash, Rust, R, Perl, Perl 6, and JavaScript. % \end{abstract} % % -% \section*{Warning} -% \pytex\ makes possible some pretty amazing things. But that power brings with it a certain risk and responsibility. Compiling a document that uses \pytex\ involves executing Python code on your computer. You should only compile \pytex\ documents from sources you trust. \pytex\ comes with NO WARRANTY.\footnote{All \LaTeX\ code is licensed under the \href{http://www.latex-project.org/lppl.txt}{\LaTeX\ Project Public License (LPPL)} and all Python code is licensed under the \href{http://www.opensource.org/licenses/BSD-3-Clause}{BSD 3-Clause License}.} The copyright holder and any additional authors will not be liable for any damages. % +% \begingroup +% \small +% +% \subsection*{\centering Development Status} +% Since 2020, I am increasingly creating new teaching materials with Markdown and HTML instead of \pytex\ and \LaTeX. I have limited time for developing open-source software that I do not use frequently myself. There should be occasional releases to keep \pytex\ running, but no major changes or significant new features are anticipated. +% +% +% +% \subsection*{\centering Warning} +% +% \pytex\ makes possible some pretty amazing things. But that power brings with it a certain risk and responsibility. Compiling a document that uses \pytex\ involves executing Python code, and potentially other programs, on your computer. You should only compile \pytex\ documents from sources you trust. \pytex\ comes with NO WARRANTY.\footnote{All \LaTeX\ code is licensed under the \href{http://www.latex-project.org/lppl.txt}{\LaTeX\ Project Public License (LPPL)} and all Python code is licensed under the \href{http://www.opensource.org/licenses/BSD-3-Clause}{BSD 3-Clause License}.} The copyright holder and any additional authors will not be liable for any damages. +% +% \endgroup % -% \section*{Package status} -% \pytex\ is currently in ``beta,'' but a full release and submission to CTAN is very close. It has been tested under Windows with \TeX\ Live and Python 2.7 and 3.2, under OS X (10.7) with MacPort's \TeX\ Live and Python 2.7, and under Linux (Ubuntu) with TeX Live and Python 2.7. -% \vfill -% % % \pagebreak % \tableofcontents @@ -189,28 +575,30 @@ % % \section{Introduction} % -% \LaTeX\ can do a lot,\footnote{\TeX\ is a Turing-complete language.} but the programming required can sometimes be painful.\footnote{As I learned in creating this package.} Also, in spite of the many packages available for \LaTeX, the libraries and packages of a general-purpose programming language are lacking. For these reasons, there have been multiple attempts to allow other languages to be used within \LaTeX.\footnote{Two additional examples not explicitly discussed here are \href{http://www.stat.uni-muenchen.de/~leisch/Sweave/}{Sweave} and \href{http://yihui.name/knitr/}{knitr}, which combine \LaTeX\ with the R language for tasks such as dynamic report generation. These are quite sophisticated. Since they are inspired by noweb (the .tex source is generated from .Rnw), passing information from \LaTeX\ to R can be non-trivial and thus the \TeX\ integration is weaker in that sense.} +% This introduction provides background and objectives for the \pytex\ package. To jump right in and get started, you may wish to consult the |pythontex_quickstart| and |pythontex_gallery| documents, as well as Sections~\ref{sec:installing-and-running} and \ref{sec:usage}, below. If you are primarily interested in using \pytex\ with a language other than Python, see Section~\ref{sec:other-languages}. +% +% \LaTeX\ can do a lot,\footnote{\TeX\ is a Turing-complete language.} but the programming required can sometimes be painful.\footnote{As I learned in creating this package.} In spite of the many packages available for \LaTeX, the libraries and packages of a general-purpose programming language are lacking. Furthermore, it can be convenient to include non-\LaTeX\ code in a document to make it more reproducible. For these reasons, there have been multiple systems that allow other languages to be used within \LaTeX\ documents.\footnote{I am not including the various web and weave dialects in my discussion, since they typically involve a web or weave document from which the .tex source is generated, and thus weaker integration with \LaTeX. Two sophisticated examples of this approach are \href{http://www.stat.uni-muenchen.de/~leisch/Sweave/}{Sweave} and \href{http://yihui.name/knitr/}{knitr}, both of which combine \LaTeX\ with the R language for tasks such as dynamic report generation.} % \begin{itemize} -% \item \href{http://www.ctan.org/tex-archive/macros/latex/contrib/perltex/}{Perl\TeX} allows the bodies of \LaTeX\ macros to be written in Perl. -% \item \href{http://www.ctan.org/tex-archive/macros/latex/contrib/sagetex/}{Sage\TeX} allows code for the Sage mathematics software to be executed from within a \LaTeX\ document. -% \item Martin R.\ Ehmsen's \href{http://web.archive.org/web/20080728170129/www.imada.sdu.dk/~ehmsen/python.sty }{|python.sty|} provides a very basic method of executing Python code from within a \LaTeX\ document. +% \item \href{http://www.ctan.org/pkg/perltex}{Perl\TeX} allows the bodies of \LaTeX\ macros to be written in Perl. +% \item \href{http://www.ctan.org/pkg/sagetex/}{Sage\TeX} allows code for the Sage mathematics software to be executed from within a \LaTeX\ document. +% \item Martin R.\ Ehmsen's \href{http://www.ctan.org/pkg/python}{|python.sty|} provides a very basic method of executing Python code from within a \LaTeX\ document. % \item \href{http://elec.otago.ac.nz/w/index.php/SympyTeX}{Sympy\TeX} allows more sophisticated Python execution, and is largely based on a subset of Sage\TeX. % \item \href{http://www.luatex.org/}{Lua\TeX} extends the pdf\TeX\ engine to provide Lua as an embedded scripting language, and as a result yields tight, low-level Lua integration. % \end{itemize} % % \pytex\ attempts to fill a perceived gap in the current integrations of \LaTeX\ with an additional language. It has a number of objectives, only some of which have been met by previous packages. % \begin{description} -% \item[Execution speed]\hfill\\ In the approaches mentioned above, all the non-\LaTeX\ code is executed at every compilation of the \LaTeX\ document (Perl\TeX, Lua\TeX, and |python.sty|), or all the non-\LaTeX\ code is executed every time it is modified (Sage\TeX\ and Sympy\TeX). However, many tasks such as plotting and data analysis take significant time to execute. We need a way to fine-tune code execution, so that independent blocks of slow code may be separated into their own sessions and are only executed when modified. If we are going to split code into multiple sessions, we might as well run these sessions in parallel, further increasing speed. A byproduct of this approach is that it now becomes much more feasible to include slower code, since we can still have fast compilations whenever the slow code isn't modified. +% \item[Execution speed]\hfill\\ In the approaches mentioned above, all the non-\LaTeX\ code is executed at every compilation of the \LaTeX\ document (Perl\TeX, Lua\TeX, and |python.sty|), or all the non-\LaTeX\ code is executed every time it is modified (Sage\TeX\ and Sympy\TeX). However, many tasks such as plotting and data analysis take a significant time to execute. We need a way to fine-tune code execution, so that independent blocks of slow code may be separated into their own sessions and are only executed when modified. If we are going to split code into multiple sessions, we might as well run these sessions in parallel, further increasing speed. A byproduct of this approach is that it now becomes much more feasible to include slower code, since we can still have fast compilations whenever the slow code isn't modified. % \item[Compiling without executing]\hfill\\ Even with all of these features to boost execution speed, there will be times when we have to run slow code. Thus, we need the execution of non-\LaTeX\ code to be separated from compiling the \LaTeX\ document. We need to be able to edit and compile a document containing unexecuted code. Unexecuted code should be invisible or be replaced by placeholders. Sage\TeX\ and Sympy\TeX\ have implemented such a separation of compiling and executing. In contrast, Lua\TeX\ and Perl\TeX\ execute all the code at each compilation---but that is appropriate given their goal of simplifying macro programming. % \item[Error messages]\hfill\\ Whenever code is saved from a \LaTeX\ document to an external file and then executed, the line numbers for any error messages will not correspond to the line numbering of the original \LaTeX\ document. At one extreme, |python.sty| doesn't attempt to deal with this issue, while at the other extreme, Sage\TeX\ uses an ingenous system of |Try|/|Except| statements on every chunk of code. We need a system that translates all error messages so that they correspond to the line numbering of the original \LaTeX\ document, with minimal overhead when there are no errors. -% \item[Syntax highlighting]\hfill\\ Once we begin using non-\LaTeX\ code, sooner or later we will likely wish to typeset some of it, which means we need syntax highlighting. A number of syntax highlighting packages currently exist for \LaTeX; perhaps the most popular are |listings| and |minted|. |listings| uses pure \LaTeX. It has not been updated since 2007, which makes it a less ideal solution in some circumstances. |minted| uses the Python-based syntax highlighter Pygments to perform highlighting. Pygments can provide superior syntax highlighting, but |minted| can be very slow because all code must be highlighted at each compilation and each instance of highlighting involves launching an external Python process. We need high-speed, user-friendly syntax highlighting via Pygments. -% \item[Printing]\hfill\\ It would be nice for the |print| statement/function,\footnote{In Python, |print| was a statement until Python 3.0, when it became a function. The function form is available via import from |\textunderscore\textunderscore future\textunderscore\textunderscore| in Python 2.6 and later.} or its equivalent, to automatically return its output within the \LaTeX\ document. For example, using |python.sty| it is possible to generate some text while in Python, open a file, save the text to it, close the file, and then |\input| the file after returning to \LaTeX. But it is much simpler to generate the text and |print| it, since the printed content is automatically included in the \LaTeX\ document. This was one of the things that |python.sty| really got right. -% \item[Pure code]\hfill\\ \LaTeX\ has a number of special characters (|# $ % & ~ _ ^ \ { }|), which complicates the entry of code in a non-\LaTeX\ language since these same characters are common in many languages. Sage\TeX\ and Sympy\TeX\ delimit all inline code with curly braces (|{}|), but this approach fails in the (somewhat unlikely) event that code needs to contain an unmatched brace. More seriously, they do not allow the percent symbol |%| (modular arithmetic and string formatting in Sage and Python) to be used within inline code. Rather, a |\percent| macro must be used instead. This means that code must (sometimes) be entered as a hybrid between \LaTeX\ and the non-\LaTeX\ language. Lua\TeX\ is somewhat similar: ``The main thing about Lua code in a TeX document is this: the code is expanded by TeX before Lua gets to it. This means that all the Lua code, even the comments, must be valid TeX!''\footnote{\url{http://wiki.contextgarden.net/Programming_in_LuaTeX}} In the case of Lua\TeX, though, there is the |luacode| package that allows for pure Lua. +% \item[Syntax highlighting]\hfill\\ Once we begin using non-\LaTeX\ code, sooner or later we will want to typeset some of it, which means we need syntax highlighting. A number of syntax highlighting packages currently exist for \LaTeX; perhaps the most popular are |listings| and |minted|. |listings| uses pure \LaTeX. It has not been updated since 2007, which makes it a less ideal solution in some circumstances. |minted| uses the Python-based syntax highlighter Pygments to perform highlighting. Pygments can provide superior syntax highlighting, but |minted| can be very slow because all code must be highlighted at each compilation and each instance of highlighting involves launching an external Python process. We need high-speed, user-friendly syntax highlighting via Pygments.\footnote{The author recently started maintaining the \texttt{minted} package. In the near future, \texttt{minted} will inherit \pytex's speed enhancements, and the two packages will become more compatible.} +% \item[Printing]\hfill\\ It would be nice for the |print| statement/function,\footnote{In Python, |print| was a statement until Python 3, when it became a function. The function form is available via import from |__future__| in Python 2.6 and later.} or its equivalent, to automatically return its output within the \LaTeX\ document. For example, using |python.sty| it is possible to generate some text while in Python, open a file, save the text to it, close the file, and then |\input| the file after returning to \LaTeX. But it is much simpler to generate the text and |print| it, since the printed content is automatically included in the \LaTeX\ document. This was one of the things that |python.sty| really got right. +% \item[Pure code]\hfill\\ \LaTeX\ has a number of special characters (|#| \texttt{\$} |%| |&| |~| |_| |^| |\| |{| |}|), which complicates the entry of non-\LaTeX\ code since these same characters are common in many languages. Sage\TeX\ and Sympy\TeX\ delimit all inline code with curly braces (|{}|), but this approach fails in the (somewhat unlikely) event that code needs to contain an unmatched brace. More seriously, they do not allow the percent symbol |%| (modular arithmetic and string formatting in Sage and Python) to be used within inline code. Rather, a |\percent| macro must be used instead. This means that code must (sometimes) be entered as a hybrid between \LaTeX\ and the non-\LaTeX\ language. Lua\TeX\ is somewhat similar: ``The main thing about Lua code in a TeX document is this: the code is expanded by TeX before Lua gets to it. This means that all the Lua code, even the comments, must be valid TeX!''\footnote{\url{http://wiki.contextgarden.net/Programming_in_LuaTeX}} In the case of Lua\TeX, though, there is the \href{http://www.ctan.org/pkg/luacode}{\texttt{luacode}} package that allows for pure Lua. % % This language hybridization is not terribly difficult to work around in the Sage\TeX\ and Sympy\TeX\ cases, and is actually a Lua\TeX\ feature in many contexts. But if we are going to create a system for general-purpose access to a non-\LaTeX\ language, we need \textbf{all} valid code to work correctly in \textbf{all} contexts, with no hybridization of any sort required. We should be able to copy and paste valid code into a \LaTeX\ document, without having to worry about hybridizing it. Among other things, this means that inline code delimiters other than \LaTeX's default curly braces |{}| must be available. % \item[Hybrid code]\hfill\\ Although we need a system that allows input of pure non-\LaTeX\ code, it would also be convenient to allow hybrid code, or code in which \LaTeX\ macros may be present and are expanded before the code is executed. This allows \LaTeX\ data to be easily passed to the non-\LaTeX\ language, facilitating a tighter integration of the two languages and the use of the non-\LaTeX\ language in macro definitions. % \item[Math and science libraries]\hfill\\ The author decided to create \pytex\ after writing a physics dissertation using \LaTeX\ and realizing how frustrating it can be to switch back and forth between a \TeX\ editor and plotting software when fine-tuning figures. We need access to a non-\LaTeX\ language like Python, MATLAB, or Mathematica that provides strong support for data analysis and visualization. To maintain broad appeal, this language should primarily involve open-source tools, should have strong cross-platform support, and should also be suitable for general-purpose programming. -% \item[Language-independent implementation]\hfill\\ It would be nice to have a system for executing non-\LaTeX\ code that depends very little on the language of the code. We should not expect to be able to escape all language dependence. But if the system is designed to be as general as possible, then it may be expanded in the future to support additional languages. +% \item[Language-independent implementation]\hfill\\ It would be nice to have a system for executing non-\LaTeX\ code that depends very little on the language of the code. We should not expect to escape all language dependence. But if the system is designed to be as general as possible, then it may be expanded in the future to support additional languages. % \end{description} % % Python was chosen as the language to fulfill these objectives for several reasons. @@ -220,65 +608,95 @@ % \item We need a language that is suitable for scripting. Lua is already available via Lua\TeX, and in any case lacks the math and science tools.\footnote{One could use \href{http://labix.org/lunatic-python}{Lunatic Python}, and some numeric packages for Lua are \href{http://numlua.luaforge.net/}{in development}.} Perl is already available via Perl\TeX, although Perl\TeX's emphasis on Perl for macro creation makes it rather unsuitable for scientific work using the \href{http://pdl.perl.org/}{Perl Data Language (PDL)} or for more general programming. Python is one logical choice for scripting. % \end{itemize} % -% Now at this point there will almost certainly be some reader, sooner or later, who wants to object, ``But what about language \textit{X}!'' Well, yes, in some respects the choice to use Python did come down to personal preference. But you should give Python a try, if you haven't already. You may also wish to consider the many interfaces that are available between Python and other languages. If you still aren't satisfied, keep in mind \pytex's ``language-independent'' implementation! Although \pytex\ is written to support Python within \LaTeX, the implementation has been specially crafted so that other languages may be supported in the future. See Section~\ref{sec:future} for more details. +% Now at this point there will almost certainly be some reader, sooner or later, who wants to object, ``But what about language \textit{X}!'' Well, yes, in some respects the choice to use Python did come down to personal preference. But you should give Python a try, if you haven't already. You may also wish to consider the many interfaces that are available between Python and other languages. If you still aren't satisfied, keep in mind \pytex's ``language-independent'' implementation! In many cases, adding support for additional languages is relatively simple (see Section~\ref{sec:other-languages}). +% % +% \section{Citing \pytex} +% \label{sec:citing} +% +% If you use \pytex\ in your writing and research, please consider citing it in any resulting publications. The best and most recent paper is in \textit{Computational Science \& Discovery}. +% \begin{itemize} +% \item ``PythonTeX: reproducible documents with LaTeX, Python, and more,'' Geoffrey M Poore. \textit{Computational Science \& Discovery} 8 (2015) 014010. Full text and Bib\TeX\ entry available at \url{http://stacks.iop.org/1749-4699/8/i=1/a=014010}. +% \item ``Reproducible Documents with PythonTeX,'' Geoffrey M.\ Poore. \textit{Proceedings of the 12th Python in Science Conference} (2013), pp.\ 73--79. Full text and Bib\TeX\ entry available at \url{http://conference.scipy.org/proceedings/scipy2013/poore.html}. +% \end{itemize} % % % \section{Installing and running} +% \label{sec:installing-and-running} +% +% \subsection{Installing \pytex} % -% \subsection{Installing \pytex} +% \pytex\ requires a \TeX\ installation. It has been tested with \href{http://www.tug.org/texlive/}{\TeX\ Live} and \href{http://miktex.org/}{MiK\TeX}, but should work with other distributions. The following \LaTeX\ packages, with their dependencies, are required: |fancyvrb|, |fvextra|, |etoolbox|, |xstring|, |pgfopts|, |newfloat| (part of the |caption| bundle), |currfile|, and |color| or |xcolor|. A current \TeX\ installation is recommended, since some features require recent versions of the packages. If you are creating and including graphics, you will also need |graphicx|. The \href{http://www.ctan.org/pkg/mdframed}{\texttt{mdframed}} package is recommended for enclosing typeset code in boxes with fancy borders and/or background colors; \href{http://www.ctan.org/pkg/tcolorbox}{\texttt{tcolorbox}} and \href{http://www.ctan.org/pkg/framed}{\texttt{framed}} are alternatives. % -% \pytex\ requires a \TeX\ installation. \href{http://www.tug.org/texlive/}{\TeX\ Live} or \href{http://miktex.org/}{MiK\TeX} are preferred. \pytex\ requires the |Kpathsea| library, which is available in both of these distributions. The following \LaTeX\ packages, with their dependencies, are also required: |fancyvrb|, |etex|, |etoolbox|, |xstring|, |pgfopts|, |newfloat|, and |color| or |xcolor|. If you are creating and importing graphics using Python, you will also need |graphicx|. The |mdframed| package is recommended for enclosing typeset code in boxes with fancy borders and/or background colors. +% \pytex\ also requires a \href{http://www.python.org/}{Python} installation. A recent Python 3 installation is recommended, but there is still support for Python 2.7. The Python package \href{http://pygments.org/}{Pygments} must be installed for syntax highlighting to function. \pytex\ has been tested with Pygments 1.4 and later, but the latest version is recommended. For scientific work, or to compile |pythontex_gallery.tex|, the following are also recommended: \href{http://numpy.scipy.org/}{NumPy}, \href{http://www.scipy.org/}{SciPy}, \href{http://matplotlib.sourceforge.net/}{matplotlib}, and \href{http://sympy.org}{SymPy}. When using \pytex\ with LyX, be aware that LyX may try to use its own version of Python; you may need to reconfigure LyX. % -% \pytex\ also requires a \href{http://www.python.org/}{Python} installation. Python 2.7 is recommended for the greatest compatibility with scientific tools. Python 3.1 and later will work as well. Earlier versions of Python 2 and 3 are not compatible, at least not without several modifications to the \pytex\ scripts. The Python package \href{http://pygments.org/}{Pygments} must be installed for syntax highlighting to function. \pytex\ has been tested with Pygments 1.4 and later, but the latest version is recommended. For scientific work, or to compile orthe \pytex\ gallery file, the following are also recommended: \href{http://numpy.scipy.org/}{NumPy}, \href{http://www.scipy.org/}{SciPy}, \href{http://matplotlib.sourceforge.net/}{matplotlib}, and \href{http://sympy.org}{SymPy}. +% \pytex\ also provides support for other languages such as Ruby, so you will need to install any additional languags you plan to use. Typically, the most recent major version of these languages is supported. % % \pytex\ consists of the following files: % \begin{itemize} % \item Installer file |pythontex.ins| % \item Documented \LaTeX\ source file |pythontex.dtx|, from which |pythontex.pdf| and |pythontex.sty| are generated -% \item Main Python scripts |pythontex2.py| and |pythontex3.py| -% \item Helper scripts |pythontex_utils2.py| and |pythontex_types2.py|, and |pythontex_utils3.py| and |pythontex_types3.py| -% \item Installation script |pythontex_install_texlive| (for \TeX\ Live) -% \item |README.rst| -% \item Optional batch files |pythontex2.bat| and |pythontex3.bat| for use in launching |pythontex*.py| under Windows +% \item Main script |pythontex.py|, which imports from |pythontex2.py| or |pythontex3.py|, based on the Python version +% \item Language definitions |pythontex_engines.py| +% \item Utilities class |pythontex_utils.py| +% \item |depythontex.py|, which imports from |depythontex2.py| or |depythontex3.py|, based on the Python version; used to remove \pytex\ dependence +% \item Synchronized Python Debugger |syncpdb.py| +% \item |README| (in rst style) +% \item |pythontex_gallery.tex| and |pythontex_gallery.pdf| +% \item |pythontex_quickstart.tex| and |pythontex_quickstart.pdf| +% \item Optional installation script |pythontex_install.py| for \TeX\ Live and MiKTeX +% \item Optional batch file |pythontex.bat| for use in launching |pythontex.py| under Windows +% \item |pythontex_2to3.py| was originally included with \pytex, but it is no longer needed. Originally, the core of \pytex\ was developed in |pythontex2.py| for Python 2.7, and then |pythontex_2to3.py| was used convert |pythontex2.py| into |pythontex3.py| for Python 3.2+. Currently, all development is in |pythontex3.py|. |pythontex2.py| is retained for backward compatibility, but in most cases it is no longer updated to maintain parity with |pythontex3.py|. Similarly, |depythontex3.py| is now developed directly, separately from |depythontex2.py|, and |depythontex2.py| typically doesn't receive updates. % \end{itemize} -% The style file |pythontex.sty| may be generated by running \LaTeX\ on |pythontex.ins|. The documentation you are reading may be generated by running \LaTeX\ on |pythontex.dtx|. Two versions of all of the Python scipts are supplied, one for Python 2 and one for Python 3.\footnote{Unfortunately, it is not possible to provide full Unicode support for both Python 2 and 3 using a single script. Currently, all code is written for Python 2, and then the Python 3 version is automatically generated via the |pythontex\_2to3.py| script. This script comments out code that is only for Python 2, and un-comments code that is only for Python 3.} +% The style file |pythontex.sty| may be generated by running \LaTeX\ on |pythontex.ins|. The documentation you are reading may be generated by running \LaTeX\ on |pythontex.dtx|. Some code is provided in two forms, one for Python 2 and one for Python 3 (names ending in |2| and |3|). Whenever this is the case, a version-independent wrapper is supplied that automatically runs the correct code based on the Python version. For example, there are two main scripts, |pythontex2.py| and |pythontex3.py|, but you can typically run |pythontex.py|, which imports the correct code based on the Python version. +% +% If you want the absolute latest version of \pytex, you should install it manually from \href{https://github.com/gpoore/pythontex}{github.com/gpoore/pythontex}. A Python installation script is provided for use with \TeX\ Live and MiKTeX. It has been tested with Windows, Linux, and OS~X, but may need manual input or slight modifications depending on your system. The installation script performs the steps described below. +% +% \textbf{For a MiKTeX installation, you may need administrator privileges; running \texttt{pythontex\_install.bat} as administrator may be simplest.} % -% Until \pytex\ is submitted to CTAN (or if you always want the absolute latest version), it must be installed manually. A Python installation script is provided for use with \TeX\ Live. It has been tested with Windows, Linux, and OS~X, but may need manual input or slight modifications depending on your system. The installation script performs the steps described below. Note that you may have to run the script with elevated privileges, and may need to run it using the user's PATH. For example, under Ubuntu Linux, you may need the following: +% \textbf{Note that for a typical \TeX\ setup under Linux, you may need to run the script with elevated privileges, and may need to run it with the user's PATH.} This can be necessary when you are using a Linux distribution that includes an outdated version of \TeX\ Live, and have installed a new version manually. \textbf{If you are installing \pytex\ on a machine with multiple versions of \TeX, make sure you install \pytex\ for the correct version.} For example, under Ubuntu Linux, you will probably need the following command if you have installed the latest version of \TeX\ Live manually: % \begin{verbatim} -% sudo env PATH=$PATH python pythontex_install_texlive.py +%sudo env PATH=$PATH python pythontex_install.py % \end{verbatim} % -% The \pytex\ files should be installed within the \TeX\ directory structure as follows. +% The installer creates the following files. It will offer to create the paths if they do not exist. If you are installing in TEXMFLOCAL, the paths will have an additional |local/| at the end. % \begin{itemize} % \item \meta{\TeX\ tree root}|/doc/latex/pythontex/| % \begin{itemize} % \item |pythontex.pdf| % \item |README| +% \item |pythontex_quickstart.tex| +% \item |pythontex_quickstart.pdf| +% \item |pythontex_gallery.tex| +% \item |pythontex_gallery.pdf| % \end{itemize} % \item \meta{\TeX\ tree root}|/scripts/pythontex/| % \begin{itemize} -% \item |pythontex2.py| and |pythontex3.py| -% \item |pythontex_types2.py| and |pythontex_types3.py| -% \item |pythontex_utils2.py| and |pythontex_utils3.py| +% \item |pythontex.py|, |pythontex2.py| and |pythontex3.py| +% \item |pythontex_engines.py| +% \item |pythontex_utils.py| +% \item |depythontex.py|, |depythontex2.py| and |depythontex3.py| +% \item |syncpdb.py| % \end{itemize} % \item \meta{\TeX\ tree root}|/source/latex/pythontex/| % \begin{itemize} % \item |pythontex.dtx| +% \item |pythontex.ins| % \end{itemize} % \item \meta{\TeX\ tree root}|/tex/latex/pythontex/| % \begin{itemize} % \item |pythontex.sty| % \end{itemize} % \end{itemize} -% After the files are installed, the system must be made aware of their existence. Run |mktexlsr| to do this. In order for |pythontex*.py| to be executable, a symlink (\TeX\ Live under Linux), launching wrapper (\TeX\ Live under Windows), or batch file (general Windows) should be created in the |bin/|\meta{system} directory. For \TeX\ Live under Windows, simply copy |bin/win32/runscript.exe| to |bin/win32/pythontex*.exe| to create the wrapper (replace the |*| with the appropriate version).\footnote{See the output of |runscript -h| under Windows for additional details.} +% After the files are installed, the system must be made aware of their existence. The installer runs |mktexlsr| to do this. In order for |pythontex.py| and |depythontex.py| to be executable, a symlink (\TeX\ Live under Linux), launching wrapper (\TeX\ Live under Windows), or batch file (general Windows) should be created in the |bin/|\meta{system} directory. The installer attempts to create a symlink or launching wrapper automatically. For \TeX\ Live under Windows, it copies |bin/win32/runscript.exe| to |bin/win32/pythontex.exe| to create the wrapper.\footnote{See the output of |runscript -h| under Windows for additional details.} % % % \subsection{Compiling documents using \pytex} +% \label{sec:installing-and-running:compiling} % -% Compiling a document with \pytex\ involves three steps: running a \LaTeX-compatible \TeX\ engine, running |pythontex*.py| (preferably via a symlink, wrapper, or batch file, as described above), and finally running the \TeX\ engine again. The first \TeX\ run saves code into an external file where \pytex\ can access it. The second \TeX\ run pulls the \pytex\ output back into the document. +% Compiling a document with \pytex\ involves three steps: running a \LaTeX-compatible \TeX\ engine (binary executable), running |pythontex.py| (preferably via a symlink, wrapper, or batch file, as described above), and finally running the \TeX\ engine again. The first \TeX\ run saves code into an external file where \pytex\ can access it. The second \TeX\ run pulls the \pytex\ output back into the document. % -% If you plan to use code that contains Unicode characters (or other characters beyond ASCII) you should make sure that your document is properly configured: +% If you plan to use code that contains non-ASCII characters such as Unicode, you should make sure that your document is properly configured: % \begin{itemize} % \item Under pdfLaTeX, your documents need |\usepackage[T1]{fontenc}| and |\usepackage[utf8]{inputenc}|, or a similar configuration. % \item Under LuaLaTeX, your documents need |\usepackage{fontspec}|, or a similar configuration. @@ -286,33 +704,69 @@ % \end{itemize} % For an example of a \pytex\ document that will correctly compile under all three engines, see the |pythontex_gallery.tex| source. % -% If you use XeLaTeX, and your source code contains tabs, you \textbf{must} invoke XeLaTeX with the |-8bit| option so that tabs will be written to file as actual tab characters rather than as the character sequence |^^I|.\footnote{See \url{http://tex.stackexchange.com/questions/58732/how-to-output-a-tabulation-into-a-file} for more on tabs with XeTeX.} +% If you use XeLaTeX, and your non-\LaTeX\ code contains tabs, you \textbf{must} invoke XeLaTeX with the |-8bit| option so that tabs will be written to file as actual tab characters rather than as the character sequence |^^I|.\footnote{See \url{http://tex.stackexchange.com/questions/58732/how-to-output-a-tabulation-into-a-file} for more on tabs with XeTeX.} % -% |pythontex*.py| requires a single command-line argument: the name of the .tex file to process. The filename can be passed with or without an extension; the script really only needs the |\jobname|, so any extension is stripped off.\footnote{Thus, \pytex\ works happily with .tex, .ltx, .dtx, and any other extension.} The filename may include the path to the file; you do not have to be in the same directory as the file to run \pytex. If you are configuring your editor to run \pytex\ automatically via a shortcut, you may want to wrap the filename in double quotes |"| to allow for space characters.\footnote{Using spaces in the names of .tex files is apparently frowned upon. But if you configure things to handle spaces whenever it doesn't take much extra work, then that's one less thing that can go wrong.} For example, under Windows with \TeX\ Live and Python 2.7 we would create the wrapper |pythontex2.exe|. Then we could run \pytex\ on a file \meta{file~name}.tex using the command |pythontex2.exe "|\meta{file~name}|"|. +% |pythontex.py| requires a single command-line argument: the name of the .tex file to process. The filename can be passed with or without an extension; the script really only needs the |\jobname|, so any extension is stripped off.\footnote{Thus, \pytex\ works happily with .tex, .ltx, .dtx, and any other extension.} The filename may include the path to the file; you do not have to be in the same directory as the file to run \pytex. If you are configuring your editor to run \pytex\ automatically via a shortcut, you may want to wrap the filename in double quotes |"| to allow for space characters.\footnote{Using spaces in the names of .tex files is apparently frowned upon. But if you configure things to handle spaces whenever it doesn't take much extra work, then that's one less thing that can go wrong.} For example, under Windows with \TeX\ Live and Python 2.7 we would create the wrapper |pythontex.exe|. Then we could run \pytex\ on a file \meta{file~name}.tex using the command |pythontex.exe "|\meta{file~name}|"|. % -% |pythontex*.py| accepts the following optional command-line arguments. Some of these options duplicate package-level options, so that settings may be configured either within the document or at the command line. In the event that the command-line and package options conflict, the package options always override the command-line options. For variations on these options that are acceptable, run |pythontex*.py -h|. +% |pythontex.py| accepts the following optional command-line arguments. Some of these options duplicate package-level options, so that settings may be configured either within the document or at the command line. In the event that the command-line and package options conflict, the package options always override the command-line options. For variations on these options that are acceptable, run |pythontex.py -h|. % \begin{itemize} -% \item |--encoding=|\meta{encoding} This sets the file encoding. Any encoding supported by Python's \href{http://docs.python.org/library/codecs.html}{codecs} module may be used. If an encoding is not specified, \pytex\ uses UTF-8. Note that the encoding \textbf{must} be used consistently; the .tex source, the \pytex\ output, and any external code files that \pytex\ highlights should all use the same encoding. If support for characters beyond ASCII is required, then additional \LaTeX\ packages are required; see the discussion of \TeX\ engines above. -% \item |--error-exit-code={true,false}| By default, |pythontex*.py| returns an exit code of 1 if there were any errors, and an exit code of 0 otherwise. This may be useful when \pytex\ is used in a scripting or command-line context, since the presence of errors may be easily detected. It is also useful with some \TeX\ editors. For example, \href{http://www.tug.org/texworks/}{TeXworks} automatically hides the output of external programs unless there are errors. +% \item |--encoding=|\meta{encoding} This sets the file encoding. Any encoding supported by Python's \href{http://docs.python.org/library/codecs.html}{\texttt{codecs}} module may be used. The encoding should match that of the \LaTeX\ document. If an encoding is not specified, \pytex\ uses UTF-8. If support for characters beyond ASCII is required, then additional \LaTeX\ packages are required; see the discussion of \TeX\ engines above. +% \item |--error-exit-code={true,false}| By default, when |pythontex.py| finishes running, it returns an exit code of 1 if there were any errors (for example, in code execution), and an exit code of 0 otherwise. This may be useful when \pytex\ is used in a scripting or command-line context, since the presence of errors may be easily detected. It is also useful with some \TeX\ editors. For example, \href{http://www.tug.org/texworks/}{TeXworks} automatically hides the output of external programs unless there are errors. % -% But in some contexts, returning a nonzero exit code can be redundant. For example, with the \href{http://winshell.de/}{WinShell} editor under Windows with TeX Live, the complete output of \pytex\ is always available in the ``Output'' view, so it is clear if errors have occurred. Having a nonzero exit code causes |runscript.exe| to return an additional, redundant error message in the ``Output'' view. In such situations, it may be desirable to disable the nonzero exit code. -% \item |--runall=[{true,false}]| This causes all code to be executed, regardless of whether it has been modified. It is useful when code has not been modified, but a dependency such as a library or external data has changed. Note that the \pytex\ utilities class also provides a mechanism for automatically re-executing code that depends on external files when those external files are modified. +% In some contexts, returning a nonzero exit code can be redundant. For example, with the \href{http://winshell.de/}{WinShell} editor under Windows with TeX Live, the complete output of \pytex\ is always available in the ``Output'' view, so it is clear if errors have occurred. Having a nonzero exit code causes |runscript.exe| to return an additional, redundant error message in the ``Output'' view. In such situations, it may be desirable to disable the nonzero exit code. +% \item |--runall=[{true,false}]| This causes \textbf{all} code to be executed, regardless of modification or |rerun| settings. It is useful when code has not been modified, but a dependency such as a library or external data has changed. Note that the \pytex\ utilities class also provides a mechanism for automatically re-executing code that depends on external files when those external files are modified. % -% There is an equivalent |runall| package option. The command-line option |--rerun=all| is also equivalent. -% \item |--rerun={modified,errors,warnings,all}| This sets the threshold for re-executing code. By default, \pytex\ will rerun code that has been modified or that produced errors on the last run. Sometimes, we may wish to have a more lenient setting (only rerun if modified) or a more stringent setting (rerun even for warnings, or just rerun everything). |modified| only executes code that has been modified (or that has modified dependencies). |errors| executes all modified code as well as all code that produced errors on the last run; this is the default. |warnings| executes all modified code, as well as all code that produced errors or warnings. |all| executes all code and is equivalent to |--runall|. +% There is an equivalent |runall| package option. The command-line option |--rerun=always| is essentially equivalent. +% \item |--rerun={never,modified,errors,warnings,always}| This sets the threshold for re-executing code. By default, \pytex\ will rerun code that has been modified or that produced errors on the last run. Sometimes, we may wish to have a more lenient setting (only rerun if modified) or a more stringent setting (rerun even for warnings, or just rerun everything). |never| never executes code; a warning is issued if there is modified code. |modified| only executes code that has been modified (or that has modified dependencies). |errors| executes all modified code as well as all code that produced errors on the last run; this is the default. |warnings| executes all modified code, as well as all code that produced errors or warnings. |always| executes all code always and is essentially equivalent to |--runall|. % % There is an equivalent |rerun| package option. -% \item |--hashdependencies=[{true,false}]| This determines whether dependencies (external files highlighted by Pygments, code dependencies specified via |pytex.add_dependencies()|, etc.) are considered to be modified based on their hash or modification time. By default, mtime is used, since it is faster. The package option |hashdependencies| is equivalent. +% \item |--hashdependencies=[{true,false}]| This determines whether dependencies (external files highlighted by Pygments, code dependencies specified via |pytex.add_dependencies()|, etc.) are checked for changes via their hashes or modification times. By default, mtime is used, since it is faster. The package option |hashdependencies| is equivalent. +% +% \item |--jobs| This sets the maximum number of concurrent processes. By default, this will be Python's |multiprocessing.cpu_count()|, which is the number of CPUs in the system. It may be useful to set a smaller value when some jobs are particularly resource intensive or themselves use subprocesses. % % \item |--verbose| This gives more verbose output, including a list of all processes that are launched. -% \end{itemize} % -% \pytex\ currently does not provide means to choose between multiple Python installations; it will use the default Python installation. Support for multiple installations is unlikely to be added, since a cross-platform solution would be required.\footnote{Python 3.3's \texttt{py} launcher for Windows may make this more feasible.} If you need to work with multiple installations, you may wish to modify |pythontex_types*.py| to create additional command and environment families that invoke different versions of Python, based on your system. +% \item |--interpreter| This allows the interpreter for a given language to be specified. The argument should be in the form +% \begin{verbatim} +%--interpreter ":, :, ..." +% \end{verbatim} +% where || is |python|, |ruby|, etc., and || is the command for invoking the desired interpreter. The argument to |--interpreter| may also be in the form of a Python dictionary. The argument need not be enclosed in quotation marks if it contains no spaces. +% +% For example, by default Python code is executed with whatever interpreter the |python| command invokes. But Python 3 could be specified using |--interpreter python:python3| (many Linux distributions) or |--interpreter "python:py -3"| (Windows, with Python 3.3 installed so that the |py| wrapper is available). +% +% \item |--interactive [::]| This is used to run a single session in interactive mode. This allows user input. Code output is written to stdout. Interactive mode is particularly useful when working with debuggers (but also see the |--debug| option). +% +% |[::]| is optional; if it is not provided, the default session is executed. For non-default sessions (or if there are multiple default sessions, due to the use of multiple families of commands), simply supplying the session name is usually sufficient (for example, |--debug session|). The full combination of |[::]| (for example, |py:session:default|) is only necessary when the session name alone would be ambiguous. +% +% Note that when a session is run in interactive mode, it will \emph{not} save printed content in a form that may be brought back into the document. You will have to run the session again in normal mode to complete document compilation. % -% \pytex\ attempts to check for a wide range of errors and return meaningful error messages. But due to the interaction of \LaTeX\ and Python code, some strange errors are possible. If you cannot make sense of errors when using \pytex, the simplest thing to try is deleting all files created by \pytex, then recompiling. By default, these files are stored in a directory called |pythontex-files-|\meta{jobname}, in the same directory as your .tex document. See Section \ref{sec:troubleshooting} for more details regarding Troubleshooting. +% Code that requires user input will cause \pytex\ to ``hang'' when \pytex\ is not running in interactive mode. This is because the code will request user input, but no input is possible given the way that the code is being executed, so the code will wait for input forever. It is inefficient constantly to add and then delete interactive code as you switch between normal and interactive modes. To avoid this, you can conditionally invoke code that requires input. In interactive mode, the temporary script that is executed is given the command-line argument |--interactive|. You can check for the presence of this argument, and only invoke interactive code if it is present. For example, under Python you could start the |pdb| debugger, only when the code is being executed in interactive mode, using commands such as the following. +%\begin{verbatim} +%import pdb +%import sys +%if '--interactive' in sys.argv[1:]: +% pdb.set_trace() +%\end{verbatim} +% +% This option is currently not compatible with Python console commands and environments. +% +% \item |--debug [::]| This is used to run a single session with the default debugger in interactive mode. Currently, only standard Python sessions are supported. (Python console commands and environments are not supported.) Support for other languages and support for customization will be added in the future. +% +% |[::]| is optional; if it is not provided, the default session is executed. For non-default sessions (or if there are multiple default sessions, due to the use of multiple families of commands), simply supplying the session name is usually sufficient (for example, |--debug session|). The full combination of |[::]| (for example, |py:session:default|) is only necessary when the session name alone would be ambiguous. +% +% Note that when a session is run in debug mode, it will \emph{not} save printed content in a form that may be brought back into the document. You will have to run the session again in normal mode to complete document compilation. +% +% The default Python debugger is |syncpdb|, the Synchronized Python Debugger. It provides a wrapper around |pdb| that is aware of the connection between the code and the \LaTeX\ document from which it was extracted. All |pdb| commands function normally. In addition, commands that take a line number or filename:lineno as an argument will also take these same values with a percent symbol |%| prefix. If the percent symbol is present, then |syncpdb| interprets the filename and line number as referring to the document, rather than to the code that is executed. It will translate the filename and line number to the corresponding code equivalents, and then pass these to the standard |pdb| internals. For example, the |pdb| command |list 50| would list the code that is being executed, centered around line 50. |syncpdb| allows the command |list %10|, which would list the code that is being executed, centered around the code that came from line 10 in the main \LaTeX\ document. (If no file name is given, then the main \LaTeX\ document is assumed.) If the code instead came from an inputed file |input.tex|, then |list %input.tex:10| could be used. Further details are provided at \href{https://github.com/gpoore/syncpdb}{github.com/gpoore/syncpdb}. +% +% The temporary script that is executed is given the command-line argument |--interactive| when run in debug mode. You can check for the presence of this argument if you wish to invoke code that requires user input conditionally. See the |--interactive| command-line option for more details. +% +% \end{itemize} +% +% \pytex\ attempts to check for a wide range of errors and return meaningful error messages. But due to the interaction of \LaTeX\ and Python code, some strange errors are possible. If you cannot make sense of errors when using \pytex, the simplest thing to try is deleting all files created by \pytex, then recompiling. By default, these files are stored in a directory called |pythontex-files-|\meta{jobname}, in the same directory as your .tex document. See Section \ref{sec:troubleshooting} for more details regarding troubleshooting. % % % \section{Usage} +% \label{sec:usage} % % \subsection{Package options} % Package options may be set in the standard manner when the package is loaded: @@ -324,50 +778,77 @@ % % Some options have a command-line equivalent. Package options override command-line options. % +% All options related to printed content are provided in two forms for convenience: one based on the word |print| and one based on |stdout|. +% +% +% \DescribeMacro{usefamily=\meta{basename}/\marg{basename1,~basename2,~...}} +% +% By default, only the |py|, |sympy|, and |pylab| families of commands and environments are defined, to prevent possible package conflicts.\footnote{For example, a \texttt{\string\ruby} command for Ruby code, and the \texttt{\string\ruby} command defined by the Ruby package in the \href{http://www.ctan.org/pkg/cjk}{CJK package}.} This option defines preconfigured families for other available languages. It takes either a single language base name, or a list of comma-separated names enclosed in curly braces. For example, the Ruby families |rb| and |ruby|, the Julia families |jl| and |julia|, and the Octave family |octave| may be enabled. For a full list of supported languages, see \Cref{sec:other-languages}. +% +% +% \DescribeMacro{gobble=none/auto default:none} +% +% This option is still under development and may change somewhat in future releases. If that occurs, equivalent functionality will be provided. +% +% This option determines how code indentation is handled. By default, indentation is left as-is; leading whitespace is significant. |auto| will dedent all code by gobbling the largest common leading whitespace, using Python's |textwrap.dedent()|.\footnote{It would be possible to do the dedent on the \LaTeX\ side, as is done manually in the \texttt{fancyvrb} and \texttt{listings} packages with the \texttt{gobble} option and is done automatically in the \texttt{lstautogobble} package. This is not done for stability and security reasons. \texttt{lstautogobble} determines the dedent by extracting the leading whitespace from the first line of code, and then applying this dedent to each subsequent line. This is adequate for \textbf{typesetting} code, since the worst-case scenario is that a subsequent line with less indentation will be typeset with the first few characters missing. Such an approach is not acceptable when the code will be \textbf{executed}, since a few missing characters could in principle cause serious damage. Doing the dedent on the Python side ensures that no characters are discarded, even if that results in an indentation error.} Keep in mind that Python's dedent will not work correctly with mixed tabs and spaces. +% +% The |gobble| option always works correctly with \textbf{executed} code. However, currently the option \textbf{only works with typeset code when Pygments is used}. The option is currently only available at the document level, but finer-grained control is planned in the future. +% +% The |gobble| option is supported by |depythontex|. +% +% +% \DescribeMacro{beta=\meta{none}/true/false default:false \meta{none}=true} +% +% This option provides compatibility with the beta releases from before the full v0.11 release, which introduced some changes in syntax and command names. This option should \textbf{only} be used with old \pytex\ documents that require it. +% +% You are encouraged to update old documents, since this compatibility option will only be provided for a few releases. +% % % \DescribeMacro{runall=\meta{none}/true/false default:false \meta{none}=true} % % This option causes all code to be executed, regardless of whether it has been modified. This option is primarily useful when code depends on external files, and needs to be re-executed when those external files are modified, even though the code itself may not have changed. Note that the \pytex\ utilities class also provides a mechanism for automatically re-executing code that depends on external files when those external files are modified. % -% A command-line equivalent |--runall| exists for |pythontex*.py|. The package option |rerun=all| is also equivelent. +% A command-line equivalent |--runall| exists for |pythontex.py|. The package option |rerun=always| is essentially equivelent. +% % -% -% \DescribeMacro{rerun=modified/errors/warnings/all default:errors} +% \DescribeMacro{rerun=never/modified/errors/warnings/always default:errors} % -% This option sets the threshold for re-executing code. By default, \pytex\ will rerun code that has been modified or that produced errors on the last run. Sometimes, we may wish to have a more lenient setting (only rerun if modified) or a more stringent setting (rerun even for warnings, or just rerun everything). |modified| only executes code that has been modified. |errors| executes all modified code as well as all code that produced errors on the last run; this is the default. |warnings| executes all modified code, as well as all code that produced errors or warnings. |all| executes all code and is equivalent to the package option |runall|. +% This option sets the threshold for re-executing code. By default, \pytex\ will rerun code that has been modified or that produced errors on the last run. Sometimes, we may wish to have a more lenient setting (only rerun if modified) or a more stringent setting (rerun even for warnings, or always rerun). |never| never executes code; a warning is issued if there is modified code. |modified| only executes code that has been modified. |errors| executes all modified code as well as all code that produced errors on the last run; this is the default. |warnings| executes all modified code, as well as all code that produced errors or warnings. |always| executes all code regardless of its condition. % -% A command-line equivalent |--rerun| exists for |pythontex*.py|. +% A command-line equivalent |--rerun| exists for |pythontex.py|. % % % \DescribeMacro{hashdependencies=\meta{none}/true/false default:false \meta{none}=true} % % When external code files are highlighted with Pygments, or external dependencies are specified via the \pytex\ utilities class, they are checked for modification via their modification time (Python's |os.path.getmtime()|). Usually, this should be sufficient---and it offers superior performance, which is important if data sets are large enough that hashing takes a noticeable amount of time. However, occasionally hashing may be necessary or desirable, so this option is provided. % -% A command-line equivalent |--hashdependencies| exists for |pythontex*.py|. +% A command-line equivalent |--hashdependencies| exists for |pythontex.py|. % % % \DescribeMacro{autoprint=\meta{none}/true/false default:true \meta{none}=true} % -% Whenever a |print| command/statement is used, the printed content will automatically be included in the document, unless the code doing the printing is being typeset.\footnote{Note that |autoprint| only works within the body of the document. The |code| command and environment can be used in the preamble, but |autoprint| is disabled there. It is usually a not a good idea to print in the preamble, because nothing can be typeset; the only thing that could be validly printed is \LaTeX\ commands that do not typeset content, such as macro definitions. Thus, it is appropriate that printed content is only brought in while in the preamble if it is explicitly requested via |\string\printpythontex|. This approach is also helpful for writing packages using \pytex, since the author does not have to worry about any \LaTeX\ commands printed by the package either not being included (if |autoprint| is relied upon, but the user turns it off) or being included twice (if |\string\printpythontex| is used and |autoprint| is enabled). Printing should only be used in the preamble with great care.} In that case, the printed content must be included using the |\printpythontex| or |\stdoutpythontex| commands. +% ~ % -% Printed content is pulled in directly from the external file in which it is saved, and is interpreted by \LaTeX\ as \LaTeX\ code. If you wish to avoid this, you should print appropriate \LaTeX\ commands with your content to ensure that it is typeset as you desire. Alternatively, you may use |\printpythontex| or |\stdoutpythontex| to bring in printed content in verbatim form, using those commands' optional |verb| and |inlineverb| (|v|) options. +% ~ % -% The |autoprint| option sets autoprint behavior for the entire document. This may be overridden within the document using the |\setpythontexautoprint| command. +% \DescribeMacro{autostdout=\meta{none}/true/false default:true \meta{none}=true} % -% \DescribeMacro{print=\meta{none}/true/false default:true \meta{none}=true} +% Whenever a |print| command/statement is used, the printed content will automatically be included in the document, unless the code doing the printing is being typeset.\footnote{Note that |autoprint| only works within the body of the document. The |code| command and environment can be used in the preamble, but |autoprint| is disabled there. It is usually a not a good idea to print in the preamble, because nothing can be typeset; the only thing that could be validly printed is \LaTeX\ commands that do not typeset content, such as macro definitions. Thus, it is appropriate that printed content is only brought in while in the preamble if it is explicitly requested via |\printpythontex|. This approach is also helpful for writing packages using \pytex, since the author does not have to worry about any \LaTeX\ commands printed by the package either not being included (if |autoprint| is relied upon, but the user turns it off) or being included twice (if |\printpythontex| is used and |autoprint| is enabled). Printing should only be used in the preamble with great care.} In that case, the printed content must be included using the |\printpythontex| or |\stdoutpythontex| commands. % -% \DescribeMacro{stdout=\meta{none}/true/false default:true \meta{none}=true} +% Printed content is pulled in directly from the external file in which it is saved, and is interpreted by \LaTeX\ as \LaTeX\ code. If you wish to avoid this, you should print appropriate \LaTeX\ commands with your content to ensure that it is typeset as you desire. Alternatively, you may use |\printpythontex| or |\stdoutpythontex| to bring in printed content in verbatim form, using those commands' optional |verb| and |verbatim| options. % -% This option determines whether printed content/content written to stdout is included in the document. Since printed content should almost \textbf{always} be included, a warning is raised when it is not. Not including printed content is useful when the printed content contains \LaTeX\ errors, and would cause document compilation to fail. When the document fails to compile, this can prevent modified Python code from being written to the code file, resulting in an inescapable loop unless printed content is disabled. +% The |autoprint| (|autostdout|) option sets autoprint behavior for the entire document. This may be overridden within the document using the |\setpythontexautoprint| command. % -% As is typical for \pytex\ settings dealing with stdout/printing, two equivalent forms are provided based on the names |print| and |stdout|. +% \DescribeMacro{debug} % -% +% This option aids in debugging invalid \LaTeX\ code that is brought in from Python. It disables the inclusion of printed content/content written to stdout. Since printed content should almost \textbf{always} be included, a warning is raised when this option is used. % -% Note that since commands like |\py| involve printing, they are also disabled if |print| or |stdout| is set to |false|. +% Not including printed content is useful when the printed content contains \LaTeX\ errors, and would cause document compilation to fail. When the document fails to compile, this can prevent modified Python code from being written to the code file, resulting in an inescapable loop unless printed content is disabled or the saved output is deleted. % +% Note that since commands like |\py| involve printing, they are also disabled. % -% \DescribeMacro{stderr=\meta{none}/true/false default:false \meta{none}=true} +% +% \DescribeMacro{makestderr=\meta{none}/true/false default:false \meta{none}=true} % % This option determines whether the stderr produced by scripts is available for input by \pytex, via the |\stderrpythontex| macro. This will not be needed in most situations. It is intended for typeseting incorrect code next to the errors that it produces. This option is not |true| by default, because additional processing is required to synchronize stderr with the document. % @@ -377,126 +858,176 @@ % \begin{quote} % | File "", line | % \end{quote} -% By default (option |full|), || is the actual name of the script that was executed. The name will be in the form \meta{family~name}|_|\meta{session}|_|\meta{group}|.|\meta{extension}. For example, an error produced by a |py| command or environment, in the session |mysession|, using the default group (that is, the default |\restartpythontexsession| treatment), would be reported in |py_mysession_default.py|. The |session| option replaces the full file name with the name of the session, |mysession.py| in this example. The |genericfile| and |genericscript| options replace the file name with || and | + + + +
+

PythonTeX Gallery

+

Geoffrey M. Poore

+
+

General Python interaction

+

We can typeset code that is passed to Python, and bring back the results.

+

This can be simple. For example, print('Python says hi!') returns the following:

+
+

Python says hi!

+
+

Or we could access the printed content verbatim (it might contain special characters):

+
+

Python says hi!

+
+

Python interaction can also be more complex. print(str(2**2**2) + r'\endinput') returns 16. In this case, the printed result includes LaTeX code, which is correctly interpreted by LaTeX to ensure that there is not an extra space after the 16. Printed output is saved to a file and brought back in via \input, and the \endinput command stops input immediately, before LaTeX gets to the end of the line and inserts a space character there, after the 16.

+

Printing works, but as the last example demonstrates, you have to be careful about spacing if you have text immediately after the printed content. In that case, it’s usually best to assemble text within a PythonTeX environment and store the text in a variable. Then you can bring in the text later, using the \py command. The \py command brings in a string representation of its argument. First we create the text.

+
mytext = '$1 + 1 = {0}$'.format(1 + 1)
+

Then we bring it in: \(1 + 1 = 2\). The \py command can even bring in verbatim content.

+

We don’t have to typeset the code we’re executing. It can be hidden. And then we can access it later: This is a message from Python.

+

It is also possible to perform variable substitution or string interpolation. The earlier result could be recreated: \(1 + 1 = 2\).

+

Pygments highlighting

+

PythonTeX supports syntax highlighting via Pygments. Any language supported by Pygments can be highlighted. Unicode is supported. Consider this snippet copied and pasted from a Python 3 interactive session. (Using random strings of Unicode for variable names is probably not a good idea, but PythonTeX will happily highlight it for you.)

+
>>> âæéöø = 123
+>>> ßçñðŠ = 456
+>>> âæéöø + ßçñðŠ
+579
+

There is also a Pygments command for inline use: \pygment.

+

Python console environment

+

PythonTeX includes an environment that emulates a Python interactive session. Commands are entered within the environment, each line is treated as input to an interactive session, and the result is typeset.

+
>>> x = 123
+>>> y = 345
+>>> z = x + y
+>>> z
+468
+>>> def f(expr):
+...     return(expr**4)
+... 
+>>> f(x)
+228886641
+>>> print('Python says hi from the console!')
+Python says hi from the console!
+

It is possible to refer to the values of console variables later on in inline contexts, using the \pycon command. For example, the value of \(z\) was 468.

+

Basic SymPy interaction

+

PythonTeX allows us to perform algebraic manipulations with SymPy and then properly typeset the results.

+

We create three variables, and define \(z\) in terms of the other two.

+
var('x, y, z')
+z = x + y
+

Now we can access what \(z\) is equal to:

+

\[z=x + y\]

+

Many things are possible, including some very nice calculus.

+
f = x**3 + cos(x)**5
+g = Integral(f, x)
+

\[\int \left(x^{3} + \cos^{5}{\left(x \right)}\right)\, dx=\frac{x^{4}}{4} + \frac{\sin^{5}{\left(x \right)}}{5} - \frac{2 \sin^{3}{\left(x \right)}}{3} + \sin{\left(x \right)}\]

+

It’s easy to use arbitrary symbols in equations.

+
phi = Symbol(r'\phi')
+h = Integral(exp(-phi**2), (phi, 0, oo))
+

\[\int\limits_{0}^{\infty} e^{- \phi^{2}}\, d\phi=\frac{\sqrt{\pi}}{2}\]

+

Plots with matplotlib

+

We can create plots with matplotlib, perfectly matching the plot fonts with the document fonts. No more searching for the code that created a figure!

+

It is possible to pass page dimensions and similar contextual information from the LaTeX side to the Python side. If you want your figures to be, for example, a particular fraction of the page width, you can pass the value of \textwidth to the Python side, and use it in creating your figures. See \setpythontexcontext in the main documentation for details.

+

You may want to use matplotlib’s PGF backend when creating plots.

+
rc('text', usetex=True)
+rc('font', family='serif')
+rc('font', size=10.0)
+rc('legend', fontsize=10.0)
+rc('font', weight='normal')
+x = linspace(0, 10)
+figure(figsize=(4, 2.5))
+plot(x, sin(x), label='$\sin(x)$')
+xlabel(r'$x\mathrm{-axis}$')
+ylabel(r'$y\mathrm{-axis}$')
+legend(loc='lower right')
+savefig('myplot.png', bbox_inches='tight')
+
+

image

+
+

Basic pylab interaction

+
from scipy.integrate import quad
+myintegral = quad(lambda x: e**-x**2, 0, inf)[0]
+

\[\int_0^\infty e^{-x^2}\,dx = 0.886226925452758\]

+

An automated derivative and integral table

+

PythonTeX allows some amazing document automation, such as this derivative and integral table. Try typing that by hand, fast!

+
from re import sub
+
+var('x')
+
+# Create a list of functions to include in the table
+funcs = ['sin(x)', 'cos(x)', 'tan(x)', 
+         'sin(x)**2', 'cos(x)**2', 'tan(x)**2', 
+         'asin(x)', 'acos(x)', 'atan(x)', 
+         'sinh(x)', 'cosh(x)', 'tanh(x)']
+
+print(r'\begin{align*}')
+
+for func in funcs:
+    # Put in some vertical space when switching to arc and hyperbolic funcs
+    if func == 'asin(x)' or func == 'sinh(x)':
+        print(r'&\\')
+    myderiv = 'Derivative(' + func + ', x)'
+    myint = 'Integral(' + func + ', x)'
+    print(latex(eval(myderiv)) + '&=' +
+            latex(eval(myderiv + '.doit()')) + r'\quad & \quad')
+    print(latex(eval(myint)) + '&=' +
+            latex(eval(myint+'.doit()')) + r'\\')
+print(r'\end{align*}')
+

\[\begin{aligned} +\frac{d}{d x} \sin{\left(x \right)}&=\cos{\left(x \right)}\quad & \quad +\int \sin{\left(x \right)}\, dx&=- \cos{\left(x \right)}\\ +\frac{d}{d x} \cos{\left(x \right)}&=- \sin{\left(x \right)}\quad & \quad +\int \cos{\left(x \right)}\, dx&=\sin{\left(x \right)}\\ +\frac{d}{d x} \tan{\left(x \right)}&=\tan^{2}{\left(x \right)} + 1\quad & \quad +\int \tan{\left(x \right)}\, dx&=- \log{\left(\cos{\left(x \right)} \right)}\\ +\frac{d}{d x} \sin^{2}{\left(x \right)}&=2 \sin{\left(x \right)} \cos{\left(x \right)}\quad & \quad +\int \sin^{2}{\left(x \right)}\, dx&=\frac{x}{2} - \frac{\sin{\left(x \right)} \cos{\left(x \right)}}{2}\\ +\frac{d}{d x} \cos^{2}{\left(x \right)}&=- 2 \sin{\left(x \right)} \cos{\left(x \right)}\quad & \quad +\int \cos^{2}{\left(x \right)}\, dx&=\frac{x}{2} + \frac{\sin{\left(x \right)} \cos{\left(x \right)}}{2}\\ +\frac{d}{d x} \tan^{2}{\left(x \right)}&=\left(2 \tan^{2}{\left(x \right)} + 2\right) \tan{\left(x \right)}\quad & \quad +\int \tan^{2}{\left(x \right)}\, dx&=- x + \frac{\sin{\left(x \right)}}{\cos{\left(x \right)}}\\ +&\\ +\frac{d}{d x} \operatorname{asin}{\left(x \right)}&=\frac{1}{\sqrt{1 - x^{2}}}\quad & \quad +\int \operatorname{asin}{\left(x \right)}\, dx&=x \operatorname{asin}{\left(x \right)} + \sqrt{1 - x^{2}}\\ +\frac{d}{d x} \operatorname{acos}{\left(x \right)}&=- \frac{1}{\sqrt{1 - x^{2}}}\quad & \quad +\int \operatorname{acos}{\left(x \right)}\, dx&=x \operatorname{acos}{\left(x \right)} - \sqrt{1 - x^{2}}\\ +\frac{d}{d x} \operatorname{atan}{\left(x \right)}&=\frac{1}{x^{2} + 1}\quad & \quad +\int \operatorname{atan}{\left(x \right)}\, dx&=x \operatorname{atan}{\left(x \right)} - \frac{\log{\left(x^{2} + 1 \right)}}{2}\\ +&\\ +\frac{d}{d x} \sinh{\left(x \right)}&=\cosh{\left(x \right)}\quad & \quad +\int \sinh{\left(x \right)}\, dx&=\cosh{\left(x \right)}\\ +\frac{d}{d x} \cosh{\left(x \right)}&=\sinh{\left(x \right)}\quad & \quad +\int \cosh{\left(x \right)}\, dx&=\sinh{\left(x \right)}\\ +\frac{d}{d x} \tanh{\left(x \right)}&=1 - \tanh^{2}{\left(x \right)}\quad & \quad +\int \tanh{\left(x \right)}\, dx&=x - \log{\left(\tanh{\left(x \right)} + 1 \right)}\\\end{aligned}\]

+

Step-by-step solutions

+

Using SymPy, it is possible to typeset step-by-step solutions. In this particular case, we also use the mdframed package to place a colored background behind our code.

+
x, y, z = symbols('x,y,z')
+f = Symbol('f(x,y,z)')
+
+# Define limits of integration
+x_llim = 0
+x_ulim = 2
+y_llim = 0
+y_ulim = 3
+z_llim = 0
+z_ulim = 4
+
+print(r'\begin{align*}')
+
+# Notice how I define f as a symbol, then later as an actual function
+left = Integral(f, (x, x_llim, x_ulim), (y, y_llim, y_ulim), (z, z_llim, z_ulim))
+f = x*y + y*sin(z) + cos(x+y)
+right = Integral(f, (x, x_llim, x_ulim), (y, y_llim, y_ulim), (z, z_llim, z_ulim))
+print(latex(left) + '&=' + latex(right) + r'\\')
+
+# For each step, I move limits from an outer integral to an inner, evaluated 
+# integral until the outer integral is no longer needed
+right = Integral(Integral(f, (z, z_llim, z_ulim)).doit(), (x, x_llim, x_ulim),
+                 (y, y_llim, y_ulim))
+print('&=' + latex(right) + r'\\')
+
+right = Integral(Integral(f, (z, z_llim, z_ulim), (y, y_llim, y_ulim)).doit(),
+                 (x, x_llim, x_ulim))
+print('&=' + latex(right) + r'\\')
+
+right = Integral(f, (z, z_llim, z_ulim), (y, y_llim, y_ulim), 
+                 (x, x_llim, x_ulim)).doit()
+print('&=' + latex(right) + r'\\')
+
+print('&=' + latex(N(right)) + r'\\')
+
+print(r'\end{align*}')
+

\[\begin{aligned} +\int\limits_{0}^{4}\int\limits_{0}^{3}\int\limits_{0}^{2} f(x,y,z)\, dx\, dy\, dz&=\int\limits_{0}^{4}\int\limits_{0}^{3}\int\limits_{0}^{2} \left(x y + y \sin{\left(z \right)} + \cos{\left(x + y \right)}\right)\, dx\, dy\, dz\\ +&=\int\limits_{0}^{3}\int\limits_{0}^{2} \left(4 x y - y \cos{\left(4 \right)} + y + 4 \cos{\left(x + y \right)}\right)\, dx\, dy\\ +&=\int\limits_{0}^{2} \left(18 x - 4 \sin{\left(x \right)} + 4 \sin{\left(x + 3 \right)} - \frac{9 \cos{\left(4 \right)}}{2} + \frac{9}{2}\right)\, dx\\ +&=4 \cos{\left(3 \right)} + 4 \cos{\left(2 \right)} - 4 \cos{\left(5 \right)} - 9 \cos{\left(4 \right)} + 41\\ +&=40.1235865133293\\\end{aligned}\]

+

Including stderr

+

PythonTeX allows code to be typset next to the stderr it produces. This requires the package option makestderr.

+
x = 123
+y = 345
+z = x + y +
+

This code causes a syntax error:

+
  File "py_errorsession_9.py", line 3
+    z = x + y +
+              ^
+SyntaxError: invalid syntax
+

The package option stderrfilename allows the file name that appears in the error message to be customized.

+
+
+
    +
  1. Since PythonTeX runs Python code (and potentially other code) on your computer, documents using PythonTeX have a greater potential for security risks than do standard LaTeX documents. You should only compile PythonTeX documents from sources you trust.↩︎

  2. +
+
+ + diff --git a/pythontex_gallery/pythontex_gallery.pdf b/pythontex_gallery/pythontex_gallery.pdf index 54fbd78..41f7c98 100644 Binary files a/pythontex_gallery/pythontex_gallery.pdf and b/pythontex_gallery/pythontex_gallery.pdf differ diff --git a/pythontex_gallery/pythontex_gallery.tex b/pythontex_gallery/pythontex_gallery.tex index f68af88..10295a0 100644 --- a/pythontex_gallery/pythontex_gallery.tex +++ b/pythontex_gallery/pythontex_gallery.tex @@ -4,19 +4,19 @@ % Detect pdftex/xetex/luatex, and load appropriate font packages. % This is inspired by the approach in the iftex package. % pdftex: -\expandafter\ifx\csname pdfmatch\endcsname\relax +\ifx\pdfmatch\undefined \else \usepackage[T1]{fontenc} \usepackage[utf8]{inputenc} \fi % xetex: -\expandafter\ifx\csname XeTeXinterchartoks\endcsname\relax +\ifx\XeTeXinterchartoks\undefined \else \usepackage{fontspec} \defaultfontfeatures{Ligatures=TeX} \fi % luatex: -\expandafter\ifx\csname directlua\endcsname\relax +\ifx\directlua\undefined \else \usepackage{fontspec} \fi @@ -26,8 +26,10 @@ \usepackage{fullpage} \usepackage{graphicx} \usepackage[svgnames]{xcolor} +\usepackage{url} +\urlstyle{same} -\usepackage[stderr]{pythontex} +\usepackage[makestderr]{pythontex} \restartpythontexsession{\thesection} @@ -55,7 +57,7 @@ \section{General Python interaction} -We can typeset code that is passed to Python, and pull in the results. +We can typeset code that is passed to Python, and bring back the results. This can be simple. For example, \pyb{print('Python says hi!')} returns the following: \begin{quote} @@ -67,9 +69,17 @@ \section{General Python interaction} \printpythontex[verb] \end{quote} -Python interaction can also be more complex. \pyb{print(str(2**2**2) + r'\endinput')} returns \printpythontex. In this case, the printed results include \LaTeX\ code, which is correctly interpreted by \LaTeX\ to ensure that there is not an extra space after the 16. Printed output is saved to a file and brought back in via \verb|\input|, and the \verb|\endinput| command prevents \LaTeX\ from treating the newline at the end of the file as justification for a space character. +Python interaction can also be more complex. \pyb{print(str(2**2**2) + r'\endinput')} returns \printpythontex. In this case, the printed result includes \LaTeX\ code, which is correctly interpreted by \LaTeX\ to ensure that there is not an extra space after the 16. Printed output is saved to a file and brought back in via \verb|\input|, and the \verb|\endinput| command stops input immediately, before \LaTeX\ gets to the end of the line and inserts a space character there, after the 16. -But we don't have to typeset the code. It can be hidden.\pyc{mystring=r'\textbf{This is a message from Python}'} And then we can access it later: \py{mystring}. +Printing works, but as the last example demonstrates, you have to be careful about spacing if you have text immediately after the printed content. In that case, it's usually best to assemble text within a \pytex\ environment and store the text in a variable. Then you can bring in the text later, using the \pygment{latex}{\py} command. The \pygment{latex}{\py} command brings in a string representation of its argument. First we create the text. +\begin{pyblock} +mytext = '$1 + 1 = {0}$'.format(1 + 1) +\end{pyblock} +Then we bring it in: \py{mytext}. The \pygment{latex}{\py} command can even bring in verbatim content. + +We don't have to typeset the code we're executing. It can be hidden.\pyc{mystring=r'\textbf{This is a message from Python}'} And then we can access it later: \py{mystring}. + +It is also possible to perform variable substitution or string interpolation. The earlier result could be recreated: \pys{$1 + 1 = !{1+1}$}. \section{Pygments highlighting} @@ -83,6 +93,8 @@ \section{Pygments highlighting} 579 \end{pygments} +There is also a Pygments command for inline use: \pygment{latex}{\pygment}. + \section{Python console environment} @@ -100,6 +112,8 @@ \section{Python console environment} print('Python says hi from the console!') \end{pyconsole} +It is possible to refer to the values of console variables later on in inline contexts, using the \pygment{latex}{\pycon} command. For example, the value of $z$ was \pycon{z}. + \section{Basic SymPy interaction} @@ -120,7 +134,7 @@ \section{Basic SymPy interaction} \begin{sympyblock} f = x**3 + cos(x)**5 -g = Integral(f,x) +g = Integral(f, x) \end{sympyblock} \[\sympy{g}=\sympy{g.doit()}\] @@ -129,7 +143,7 @@ \section{Basic SymPy interaction} \begin{sympyblock} phi = Symbol(r'\phi') -h = Integral(exp(-phi**2), (phi,0,oo)) +h = Integral(exp(-phi**2), (phi, 0, oo)) \end{sympyblock} \[\sympy{h}=\sympy{h.doit()}\] @@ -140,15 +154,19 @@ \section{Plots with matplotlib} We can create plots with matplotlib, perfectly matching the plot fonts with the document fonts. No more searching for the code that created a figure! +It is possible to pass page dimensions and similar contextual information from the \LaTeX\ side to the Python side. If you want your figures to be, for example, a particular fraction of the page width, you can pass the value of \pygment{latex}{\textwidth} to the Python side, and use it in creating your figures. See \pygment{latex}{\setpythontexcontext} in the main documentation for details. + +You may want to use matplotlib's PGF backend when creating plots. + \begin{pylabblock} rc('text', usetex=True) rc('font', family='serif') rc('font', size=10.0) rc('legend', fontsize=10.0) rc('font', weight='normal') -x = linspace(0,10) -figure(figsize=(4,2.5)) -plot(x, sin(x), label='$\sin(x)$') +x = linspace(0, 10) +figure(figsize=(4, 2.5)) +plot(x, sin(x), label=r'$\sin(x)$') xlabel(r'$x\mathrm{-axis}$') ylabel(r'$y\mathrm{-axis}$') legend(loc='lower right') @@ -156,7 +174,7 @@ \section{Plots with matplotlib} \end{pylabblock} \begin{center} -\includegraphics{myplot} +\includegraphics{myplot.pdf} \end{center} @@ -179,7 +197,7 @@ \section{An automated derivative and integral table} var('x') -#Create a list of functions to include in the table +# Create a list of functions to include in the table funcs = ['sin(x)', 'cos(x)', 'tan(x)', 'sin(x)**2', 'cos(x)**2', 'tan(x)**2', 'asin(x)', 'acos(x)', 'atan(x)', @@ -188,15 +206,15 @@ \section{An automated derivative and integral table} print(r'\begin{align*}') for func in funcs: - #Put in some vertical space when switching to arc and hyperbolic funcs - if func=='asin(x)' or func=='sinh(x)': - print(r'\vspace{0.5in}\\') + # Put in some vertical space when switching to arc and hyperbolic funcs + if func == 'asin(x)' or func == 'sinh(x)': + print(r'&\\') myderiv = 'Derivative(' + func + ', x)' myint = 'Integral(' + func + ', x)' - print(latex(eval(myderiv)) + '&=' \ - + latex(eval(myderiv+'.doit()')) + r'\quad & \quad') - print(latex(eval(myint)) + '&=' \ - + latex(eval(myint+'.doit()'))+ r'\\') + print(latex(eval(myderiv)) + '&=' + + latex(eval(myderiv + '.doit()')) + r'\quad & \quad') + print(latex(eval(myint)) + '&=' + + latex(eval(myint+'.doit()')) + r'\\') print(r'\end{align*}') \end{sympyblock} @@ -207,9 +225,9 @@ \section{Step-by-step solutions} Using SymPy, it is possible to typeset step-by-step solutions. In this particular case, we also use the \verb|mdframed| package to place a colored background behind our code. -\begin{mdframed}[linecolor=Green,innerrightmargin=30pt,innerleftmargin=30pt,leftmargin=-30pt,rightmargin=-30pt,backgroundcolor=Black!5,skipabove=10pt,skipbelow=10pt,roundcorner=5pt,frametitle={\centering Step-by-Step Integral Evaluation\linebreak}] +\begin{mdframed}[linecolor=Green,innerrightmargin=30pt,innerleftmargin=30pt,leftmargin=-30pt,rightmargin=-30pt,backgroundcolor=Black!5,skipabove=10pt,skipbelow=10pt,roundcorner=5pt,frametitle={Step-by-Step Integral Evaluation},frametitlealignment=\center,splitbottomskip=6pt,splittopskip=12pt] \begin{sympyblock}[][numbers=left] -(x, y, z) = symbols('x,y,z') +x, y, z = symbols('x,y,z') f = Symbol('f(x,y,z)') # Define limits of integration @@ -223,25 +241,26 @@ \section{Step-by-step solutions} print(r'\begin{align*}') # Notice how I define f as a symbol, then later as an actual function -left = Integral(f, (x,x_llim,x_ulim), (y,y_llim,y_ulim), (z,z_llim,z_ulim)) -f = x*y+y*sin(z) + cos(x+y) -right = Integral(f, (x,x_llim,x_ulim), (y,y_llim,y_ulim), (z,z_llim,z_ulim)) -print(latex(left) + '&=' + latex(right)+r'\\') +left = Integral(f, (x, x_llim, x_ulim), (y, y_llim, y_ulim), (z, z_llim, z_ulim)) +f = x*y + y*sin(z) + cos(x+y) +right = Integral(f, (x, x_llim, x_ulim), (y, y_llim, y_ulim), (z, z_llim, z_ulim)) +print(latex(left) + '&=' + latex(right) + r'\\') # For each step, I move limits from an outer integral to an inner, evaluated # integral until the outer integral is no longer needed -right = Integral(Integral(f,(z,z_llim,z_ulim)).doit(), (x,x_llim,x_ulim), \ - (y,y_llim,y_ulim)) -print('&=' + latex(right)+r'\\') +right = Integral(Integral(f, (z, z_llim, z_ulim)).doit(), (x, x_llim, x_ulim), + (y, y_llim, y_ulim)) +print('&=' + latex(right) + r'\\') -right = Integral(Integral(f,(z,z_llim,z_ulim),(y,y_llim,y_ulim)).doit(), \ - (x,x_llim,x_ulim)) -print('&=' + latex(right)+r'\\') +right = Integral(Integral(f, (z, z_llim, z_ulim), (y, y_llim, y_ulim)).doit(), + (x, x_llim, x_ulim)) +print('&=' + latex(right) + r'\\') -right = Integral(f,(z,z_llim,z_ulim),(y,y_llim,y_ulim),(x,x_llim,x_ulim)).doit() -print('&=' + latex(right)+r'\\') +right = Integral(f, (z, z_llim, z_ulim), (y, y_llim, y_ulim), + (x, x_llim, x_ulim)).doit() +print('&=' + latex(right) + r'\\') -print('&=' + latex(N(right))+r'\\') +print('&=' + latex(N(right)) + r'\\') print(r'\end{align*}') \end{sympyblock} @@ -252,7 +271,7 @@ \section{Step-by-step solutions} \section{Including stderr} -\pytex\ allows code to be typset next to the stderr it produces. This requires the package option \verb|stderr| +\pytex\ allows code to be typset next to the stderr it produces. This requires the package option \verb|makestderr|. \begin{pyblock}[errorsession][numbers=left] x = 123 @@ -262,9 +281,9 @@ \section{Including stderr} This code causes a syntax error: -\stderrpythontex +\stderrpythontex[verbatim][frame=single] -The file name that appears in the message can be customized using the package option \verb|stderrfilename|. +The package option \verb|stderrfilename| allows the file name that appears in the error message to be customized. diff --git a/pythontex_gallery/pythontex_gallery_2to3.py b/pythontex_gallery/pythontex_gallery_2to3.py new file mode 100644 index 0000000..3c78131 --- /dev/null +++ b/pythontex_gallery/pythontex_gallery_2to3.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Convert PythonTeX scripts from Python 2 to Python 3 + +It isn't possible to have a single PythonTeX code base, since unicode text +needs to be supported. Under Python 2, this means importing unicode_literals +from __future__, or using the unicode function or "u" prefix. Under Python 3, +all strings are automatically unicode. + +At the same time, the differences between the Python 2 and 3 versions are +usually very small, involving only a few lines of code. To keep the code base +unified, while simultaneously fully supporting both Python 2 and 3, the +following scheme was devised. The code is written for Python 2. Whenever +code is not compatible with Python 3, it is enclosed with the tags +"#// Python 2" and "#\\ End Python 2" (each on its own line, by itself). If +a Python 3 version of the code is needed, it is included between analogous +tags "#// Python 3" and "#\\ End Python 2". The Python 3 code is commented +out with "#", at the same indentation level as the Python 3 tags. + +This script creates Python 3 scripts from the original Python 2 scripts +by commenting out everything between the Python 2 tags, and uncommenting +everything between the Python 3 tags. In this way, full compatibility is +maintained with both Python 2 and 3 while keeping the code base essentially +unified. This approach also allows greater customization of version-specific +code than would be possible if automatic translation with a tool like 2to3 +was required. + +Copyright (c) 2012-2013, Geoffrey M. Poore +All rights reserved. +Licensed under the BSD 3-Clause License: + http://www.opensource.org/licenses/BSD-3-Clause + +''' + + +# Imports +from __future__ import unicode_literals +from io import open +import re + + +files_to_process = ('make_pythontex_gallery_html2.py', ) +encoding = 'utf-8' + + +def from2to3(list_of_code): + fixed = [] + in_2 = False + in_3 = False + indent = '' + + for line in list_of_code: + if r'#// Python 2' in line: + in_2 = True + indent = line.split('#', 1)[0] + elif r'#\\ End Python 2' in line: + in_2 = False + elif r'#// Python 3' in line: + in_3 = True + indent = line.split('#', 1)[0] + elif r'#\\ End Python 3' in line: + in_3 = False + elif in_2: + line = re.sub(indent, indent + '#', line, count=1) + elif in_3: + line = re.sub(indent + '#', indent, line, count=1) + fixed.append(line) + return fixed + + +for file in files_to_process: + f = open(file, 'r', encoding=encoding) + converted_code = from2to3(f.readlines()) + f.close() + f = open(re.sub('2', '3', file), 'w', encoding=encoding) + f.write(''.join(converted_code)) + f.close() + + diff --git a/pythontex_quickstart/pythontex_quickstart.pdf b/pythontex_quickstart/pythontex_quickstart.pdf new file mode 100644 index 0000000..c47904c Binary files /dev/null and b/pythontex_quickstart/pythontex_quickstart.pdf differ diff --git a/pythontex_quickstart/pythontex_quickstart.tex b/pythontex_quickstart/pythontex_quickstart.tex new file mode 100644 index 0000000..fb3c8ef --- /dev/null +++ b/pythontex_quickstart/pythontex_quickstart.tex @@ -0,0 +1,193 @@ +\documentclass[twocolumn]{article} + +% Engine-specific settings +% Detect pdftex/xetex/luatex, and load appropriate font packages. +% This is inspired by the approach in the iftex package. +% pdftex: +\ifx\pdfmatch\undefined +\else + \usepackage[T1]{fontenc} + \usepackage[utf8]{inputenc} +\fi +% xetex: +\ifx\XeTeXinterchartoks\undefined +\else + \usepackage{fontspec} + \defaultfontfeatures{Ligatures=TeX} +\fi +% luatex: +\ifx\directlua\undefined +\else + \usepackage{fontspec} +\fi +% End engine-specific settings +\usepackage{lmodern} + +\usepackage[margin=1in]{geometry} +\usepackage{microtype} + +\usepackage{pythontex} + +\usepackage[svgnames]{xcolor} + +\usepackage{hyperref} +\hypersetup{colorlinks=true,urlcolor=Green,linkcolor=blue} + +\newcommand{\pytex}{Python\TeX} +\renewcommand*{\thefootnote}{\fnsymbol{footnote}} + +\title{\vspace{-0.6in} \pytex\ Quickstart \\ {\normalsize \href{https://github.com/gpoore/pythontex}{github.com/gpoore/pythontex}}} +\author{} +\date{} + + +\begin{document} + + +\maketitle + + + + +\section*{Installing} + +\pytex\ requires Python 2.7 or 3.2+. When using \pytex\ with LyX, be aware that LyX may try to use its own version of Python; you may need to reconfigure LyX to use other Python installations. + +\pytex\ is included in TeX Live and MiKTeX. It may be installed via the package manager. + +A Python installation script is included with the package. It should be able to install the package in most situations. Depending on the configuration of your system, you may have to run the installation script with administrative priviliges. + +Detailed installation information is available in the main documentation, \texttt{pythontex.pdf}. + +\section*{Compiling} + +Compiling a document that uses \pytex\ involves three steps: run \LaTeX, run \texttt{pythontex.py}, and finally run \LaTeX\ again. You may wish to create a symlink or launching wrapper for \texttt{pythontex.py}, if one was not created during installation. \pytex\ is compatible with the pdfTeX, XeTeX, and LuaTeX engines, so you can use \texttt{latex}, \texttt{pdflatex}, \texttt{xelatex}, or \texttt{lualatex}. + +The last two compile steps are \emph{only} necessary when code needs to be executed or highlighted. Otherwise, the document may be compiled just like a normal \LaTeX\ document; all output is cached. + +\pytex\ is compatible with \texttt{latexmk}. Details for configuring \texttt{latexmk} are provided in the main documentation. + + + +\section*{Basic commands} + +\pygment{latex}{\py} returns a string representation of its argument. For example, \pygment{latex}{\py{2 + 4**2}} produces ``\py{2 + 4**2}'', and \pygment{latex}{\py{'ABC'.lower()}} produces ``\py{'ABC'.lower()}''. \pygment{latex}{\py}'s argument can be delimited by curly braces, or by a matched pair of other characters (just like \pygment{latex}{\verb}). + +\pygment{latex}{\pyc} executes code. By default, anything that is printed is automatically included in the document (see \texttt{autoprint}/\texttt{autostdout} in the main documentation). For example, \pygment{latex}{\pyc{var = 2}} \pyc{var = 2} creates a variable, and then its value may be accessed later via \pygment{latex}{\py{var}}: \py{var}. + +\pygment{latex}{\pyb} executes and typesets code. For example, \pygment{latex}{\pyb{var = 2}} typesets \pyb{var = 2} in addition to creating the variable. If anything is printed, it is not automatically included, but can be accessed via \pygment{latex}{\printpythontex} or \pygment{latex}{\stdoutpythontex}. + +\pygment{latex}{\pyv} only typesets code; nothing is executed. For example, \pygment{latex}{\pyv{var = 2}} produces \pyv{var = 2}. + +\pygment{latex}{\pys} performs variable substitution or string interpolation on code. Substitution fields are denoted by \verb|!{...}|; details about escaping are provided in the main documentation. For example, using the pre-existing variable \pyv{var}, \pygment{latex}{\pys{\verb|var = !{var}|}} yields \pys{\verb|var = !{var}|}. + + +\section*{Basic environments} + +There are \texttt{pycode}, \texttt{pyblock}, \texttt{pyverbatim}, and \texttt{pysub} environments, which are the environment equivalents of \pygment{latex}{\pyc}, \pygment{latex}{\pyb}, \pygment{latex}{\pyv}, and \pygment{latex}{\pys}. For example, +\begin{pygments}{text} +\begin{pycode} +print(r'\begin{center}') +print(r'\textit{A message from Python!}') +print(r'\end{center}') +\end{pycode} +\end{pygments} +produces +\begin{pycode} +print(r'\begin{center}') +print(r'\textit{A message from Python!}') +print(r'\end{center}') +\end{pycode} + +The \pygment{latex}{\begin} and \pygment{latex}{\end} of an environment should be on lines by themselves. Code in environments may be indented; see the \texttt{gobble} option in the main documentation for more details. + + +\section*{More commands/environments} + +All commands and environments described so far have names beginning with \texttt{py}. There are equivalent commands and environments that begin with \texttt{sympy}; these automatically include +\begin{pygments}{python} +from sympy import * +\end{pygments} +There are also equivalent commands and environments that begin with \texttt{pylab}; these automatically use matplotlib's \texttt{pylab} module via +\begin{pygments}{python} +from pylab import * +\end{pygments} +The \texttt{sympy} and \texttt{pylab} commands and environments execute code in separate sessions from the \texttt{py} commands and environments. This can make it easier to avoid namespace conflicts. + +There is also a \texttt{pyconsole} environment that emulates a Python interactive console. For example, +\begin{pygments}{text} +\begin{pyconsole} +var = 1 + 1 +var +\end{pyconsole} +\end{pygments} +yields +\begin{pyconsole} +var = 1 + 1 +var +\end{pyconsole} +Console variable values may be accessed inline via the \pygment{latex}{\pycon} command. More console information is available in the main documentation. + +\section*{Working with Python 2} +\pytex\ supports both Python 2 and 3. Under Python 2, imports from \pygment{python}{__future__} will work so long as they are the first user-entered code in a given session. \pytex\ imports most things from \pygment{python}{__future__} by default. To control what is automatically imported, see the \texttt{pyfuture} and \texttt{pyconfuture} package options in the main documentation. + + +\section*{Support for additional languages} + +\pytex\ also provides support for additional languages. Currently, Ruby, Julia, Octave, Sage, Bash, and Rust support is included. To enable commands and environments for these language, see the \texttt{usefamily} package option in the main documentation. + +Language support is provided via a template system; in most cases, a new language can be added with about 100 lines of template code---and basic support can require less than 20 lines. If you would like support for a new language, please open an issue at \href{https://github.com/gpoore/pythontex}{GitHub}. The main documentation also contains a summary of the process for adding languages. + +\section*{Macro programming} + +\pytex\ commands can be used inside other commands in macro programming. They will usually work fine, but curly braces should be used as delimiters and special \LaTeX\ characters such as \texttt{\%} and \texttt{\#} should be avoided in the Python code. These limitations can be removed by passing arguments verbatim or through catcode trickery. \pytex\ environments cannot normally be used inside \LaTeX\ commands, due to the way \LaTeX\ deals with verbatim content and catcodes. + + +\section*{Additional features} + +\pytex\ provides many additional features. The working and output directories can be specified via \pygment{latex}{\setpythontexworkingdir} and \pygment{latex}{\setpythontexoutputdir}. The user can determine when code is executed with the package option \texttt{rerun}, selecting factors such as modification and exit status. By default, all commands and environments with the same base name (\texttt{py}, \texttt{sympy}, \texttt{pylab}, etc.) run in a single session, providing continuity. Commands and environments accept an optional argument that specifies the session in which the code is executed; sessions run in parallel. \pytex\ provides a utilities class that is always imported into each session. The utilities class provides methods for tracking dependencies and automatically cleaning up created files. The utilities class also allows information such as page width to be passed from the \TeX\ side to Python/other languages. See the main documentation for additional information. + +\pytex\ also provides the \texttt{depythontex} utility, which creates a copy of a document in which all \pytex\ commands and environments have been replaced by their output. The resulting document is more suitable for journal submission, sharing, and conversion to other document formats. + +Code may be run in interactive mode on the command line via the \verb|--interactive| and \verb|--debug| options. This is primarily useful for working with interactive debuggers. + + +\section*{Customizing typesetting} + +\pytex\ typesets code using the \texttt{fancyvrb} package and the \texttt{fvextra} package that extends \texttt{fancyvrb}. There is a \pygment{latex}{\setpythontexfv} command for setting \pytex-specific \texttt{fancyvrb} and \texttt{fvextra} options. The normal \pygment{latex}{\fvset} works as well for document-wide settings. \pytex\ environments take a second optional argument that consists of \texttt{fancyvrb} and \texttt{fvextra} settings. This can be used to customize automatic line breaking or line highlighting for a single environment. + + + +\section*{Unicode support} + +\pytex\ supports Unicode under all \LaTeX\ engines. For example, consider the following example from Python: +\begin{pygments}{python} +my_string = '¥ § ß Ğ Ð Ñ Ö þ ø' +\end{pygments} +This requires some engine-specific packages. Typical packages are listed below. + +\begin{itemize} +\item pdfLaTeX: +\begin{pygments}{latex} +\usepackage[T1]{fontenc} +\usepackage[utf8]{inputenc} +\end{pygments} +\item LuaLaTeX: +\begin{pygments}{latex} +\usepackage{fontspec} +\end{pygments} +\item XeLaTeX: +\begin{pygments}{latex} +\usepackage{fontspec} +\defaultfontfeatures{Ligatures=TeX} +\end{pygments} +\end{itemize} + +If you are using Python 2, you will also need to specify that you are using Unicode. You may want +\begin{pygments}{python} +from __future__ import unicode_literals +\end{pygments} +at the beginning of your Python code. Or you can just load the \pytex\ package with the option \verb|pyfuture=all|, which will import \verb|unicode_literals| automatically. + + +\end{document} diff --git a/test/R/R_test.tex b/test/R/R_test.tex new file mode 100644 index 0000000..233c3d0 --- /dev/null +++ b/test/R/R_test.tex @@ -0,0 +1,103 @@ +\documentclass[11pt]{article} + +% Engine-specific settings +% pdftex: +\ifcsname pdfmatch\endcsname + \usepackage[T1]{fontenc} + \usepackage[utf8]{inputenc} +\fi +% xetex: +\ifcsname XeTeXinterchartoks\endcsname + \usepackage{fontspec} + \defaultfontfeatures{Ligatures=TeX} +\fi +% luatex: +\ifcsname directlua\endcsname + \usepackage{fontspec} +\fi +% End engine-specific settings + +\usepackage{lmodern} +\usepackage{amssymb,amsmath} +\usepackage{graphicx} +\usepackage{fullpage} +\usepackage[keeptemps=all, makestderr, usefamily={R}]{pythontex} + + +\begin{document} + + + +\section*{R} + +\subsection*{Commands} + +\R{2^8} + +\Rc{write(2^16, stdout())} + +\Rb{cat(2^32)} + +\printpythontex + +\Rv{cat(2^32)} + +\Rs{\LaTeX\ and then \textcolor{blue}{!{"R"}} and back to \LaTeX.} + + +\subsection*{Environments} + +Code: +\begin{Rcode} +cat("A string.", " ") +cat(2^8) +\end{Rcode} + +Block: +\begin{Rblock} +cat("A string.", " ") +cat(2^8) +\end{Rblock} + +\printpythontex + +Verbatim: +\begin{Rverbatim} +cat("A string.", " ") +cat(2^8) +\end{Rverbatim} + +Sub: +\begin{Rsub} +\LaTeX\ and then \textcolor{blue}{!{"R"}} and back to \LaTeX. +\end{Rsub} + + +\section*{R stderr} + + +\begin{Rblock}[err1][numbers=left] +# Comment +s <- "R a +\end{Rblock} + +\stderrpythontex[][breaklines, breakafter=\\/] + +\begin{Rblock}[err2][numbers=left] +1++ +\end{Rblock} + +\stderrpythontex[][breaklines, breakafter=\\/] + +\begin{Rblock}[err3][numbers=left] +# Comment +# Another comment +1 + "ab" +\end{Rblock} + +\stderrpythontex[][breaklines, breakafter=\\/] + + + +\end{document} + diff --git a/test/R/Rcon_test.tex b/test/R/Rcon_test.tex new file mode 100644 index 0000000..8e17411 --- /dev/null +++ b/test/R/Rcon_test.tex @@ -0,0 +1,79 @@ +\documentclass[11pt]{article} + +% Engine-specific settings +% pdftex: +\ifcsname pdfmatch\endcsname + \usepackage[T1]{fontenc} + \usepackage[utf8]{inputenc} +\fi +% xetex: +\ifcsname XeTeXinterchartoks\endcsname + \usepackage{fontspec} + \defaultfontfeatures{Ligatures=TeX} +\fi +% luatex: +\ifcsname directlua\endcsname + \usepackage{fontspec} +\fi +% End engine-specific settings + +\usepackage{lmodern} +\usepackage{amssymb,amsmath} +\usepackage{graphicx} +\usepackage{fullpage} +\usepackage[keeptemps=all, makestderr, usefamily={Rcon}]{pythontex} + + +\begin{document} + + +\section*{R Console} + + +\subsection*{Basics} + +Console math and printing. + +\begin{Rconsole} +1+4 +print("Some text ...") +\end{Rconsole} + +Test \pygment{r}{cat()} without a trailing newline \verb|\n|. This should produce output followed immediately on the same line by the prompt and the next command. +\begin{Rconsole} +cat("Some", "text", "...") +cat("And", "some", "more", "...") +\end{Rconsole} + +After a final \pygment{r}{cat()} without a trailing newline \verb|\n|, the next environment should start normally, with the prompt at the beginning of the line. + +\begin{Rconsole} +write("stdout", stdout()) +write("stderr", stderr()) +\end{Rconsole} + + +\subsection*{Continuity between environments} + +Set a variable. +\begin{Rconsole} +x <- 2^12 +\end{Rconsole} + +Retrieve variable value. +\begin{Rconsole} +x +\end{Rconsole} + + +\subsection*{Continue after errors} + +\begin{Rconsole} +1+"a" +3*6 +\end{Rconsole} + + + +\end{document} + diff --git a/test/bash/bash_test.tex b/test/bash/bash_test.tex new file mode 100644 index 0000000..8b9496e --- /dev/null +++ b/test/bash/bash_test.tex @@ -0,0 +1,58 @@ +\documentclass[11pt]{article} + +% Engine-specific settings +% pdftex: +\ifcsname pdfmatch\endcsname + \usepackage[T1]{fontenc} + \usepackage[utf8]{inputenc} +\fi +% xetex: +\ifcsname XeTeXinterchartoks\endcsname + \usepackage{fontspec} + \defaultfontfeatures{Ligatures=TeX} +\fi +% luatex: +\ifcsname directlua\endcsname + \usepackage{fontspec} +\fi +% End engine-specific settings + +\usepackage{lmodern} +\usepackage{amssymb,amsmath} +\usepackage{graphicx} +\usepackage{fullpage} +\usepackage[keeptemps=all, makestderr, usefamily=bash]{pythontex} + +\begin{document} + + +\section*{Bash} + +Inline: \bash{echo "Hello from bash!"} + +Code environment: +\begin{bashcode} +echo "More from bash." +\end{bashcode} + +Block environment: +\begin{bashblock} +ls -a +\end{bashblock} +Printed output: +\printpythontex[verbatim] + +Verbatim: +\begin{bashverbatim} +echo "More from bash." +\end{bashverbatim} + + +Sub environment: +\begin{bashsub} +Some text \textcolor{blue}{!{"followed by things from bash"}} and then more text. +\end{bashsub} + +Again, with command: \bashs{Some text \textcolor{blue}{!{"followed by more things from bash"}} and then more text.} + +\end{document} diff --git a/test/javascript/javascript_test.tex b/test/javascript/javascript_test.tex new file mode 100644 index 0000000..e281a08 --- /dev/null +++ b/test/javascript/javascript_test.tex @@ -0,0 +1,103 @@ +\documentclass[11pt]{article} + +% Engine-specific settings +% pdftex: +\ifcsname pdfmatch\endcsname + \usepackage[T1]{fontenc} + \usepackage[utf8]{inputenc} +\fi +% xetex: +\ifcsname XeTeXinterchartoks\endcsname + \usepackage{fontspec} + \defaultfontfeatures{Ligatures=TeX} +\fi +% luatex: +\ifcsname directlua\endcsname + \usepackage{fontspec} +\fi +% End engine-specific settings + +\usepackage{lmodern} +\usepackage{amssymb,amsmath} +\usepackage{graphicx} +\usepackage{fullpage} +\usepackage[keeptemps=all, makestderr, usefamily={javascript}]{pythontex} + + +\begin{document} + + + +\section*{JavaScript} + +\subsection*{Commands} + +\javascript{2**8} + +\javascriptc{console.log(2**16);} + +\javascriptb{console.log(2**32);} + +\printpythontex + +\javascriptv{console.log(2**32);} + +\javascripts{\LaTeX\ and then \textcolor{blue}{!{"JavaScript"}} and back to \LaTeX.} + + +\subsection*{Environments} + +Code: +\begin{javascriptcode} +console.log("A string." + " "); +console.log(2**8); +\end{javascriptcode} + +Block: +\begin{javascriptblock} +console.log("A string." + " "); +console.log(2**8); +\end{javascriptblock} + +\printpythontex + +Verbatim: +\begin{javascriptverbatim} +console.log("A string." + " "); +console.log(2**8); +\end{javascriptverbatim} + +Sub: +\begin{javascriptsub} +\LaTeX\ and then \textcolor{blue}{!{"JavaScript"}} and back to \LaTeX. +\end{javascriptsub} + + +\section*{JavaScript stderr} + + +\begin{javascriptblock}[err1][numbers=left] +// Comment +s = "javascript a +\end{javascriptblock} + +\stderrpythontex[][breaklines, breakafter=\\/] + +\begin{javascriptblock}[err2][numbers=left] +1+; +\end{javascriptblock} + +\stderrpythontex[][breaklines, breakafter=\\/] + +\begin{javascriptblock}[err3][numbers=left] +// Comment +// Another comment +"a" "b"; +\end{javascriptblock} + +\stderrpythontex[][breaklines, breakafter=\\/] + + + +\end{document} + diff --git a/test/julia/julia_test.tex b/test/julia/julia_test.tex new file mode 100644 index 0000000..612b9e5 --- /dev/null +++ b/test/julia/julia_test.tex @@ -0,0 +1,104 @@ +\documentclass[11pt]{article} + +% Engine-specific settings +% pdftex: +\ifcsname pdfmatch\endcsname + \usepackage[T1]{fontenc} + \usepackage[utf8]{inputenc} +\fi +% xetex: +\ifcsname XeTeXinterchartoks\endcsname + \usepackage{fontspec} + \defaultfontfeatures{Ligatures=TeX} +\fi +% luatex: +\ifcsname directlua\endcsname + \usepackage{fontspec} +\fi +% End engine-specific settings + +\usepackage{lmodern} +\usepackage{amssymb,amsmath} +\usepackage{graphicx} +\usepackage{fullpage} +\usepackage[keeptemps=all, makestderr, usefamily={julia}]{pythontex} + + +\begin{document} + + + +\section*{Julia} + +\subsection*{Commands} + +\julia{2^8} + +\juliac{println(2^16)} + +\juliab{println(2^32)} + +\printpythontex + +\juliav{println(2^32)} + +\julias{\LaTeX\ and then \textcolor{blue}{!{"Julia"}} and back to \LaTeX.} + + +\subsection*{Environments} + +Code: +\begin{juliacode} +println("A string.") +println(2^8) +\end{juliacode} + +Block: +\begin{juliablock} +println("A string.") +println(2^8) +\end{juliablock} + +\printpythontex + +Verbatim: +\begin{juliaverbatim} +println("A string.") +println(2^8) +\end{juliaverbatim} + +Sub: +\begin{juliasub} +\LaTeX\ and then \textcolor{blue}{!{"Julia"}} and back to \LaTeX. +\end{juliasub} + + +\section*{Julia stderr} + + +\begin{juliablock}[err1][numbers=left] +# Comment +s = """ + Julia a +\end{juliablock} + +\stderrpythontex[][breaklines, breakafter=\\/] + +\begin{juliablock}[err2][numbers=left] +1+ +\end{juliablock} + +\stderrpythontex[][breaklines, breakafter=\\/] + +\begin{juliablock}[err3][numbers=left] +# Comment +# Another comment +1 + "ab" +\end{juliablock} + +\stderrpythontex[][breaklines, breakafter=\\/] + + + +\end{document} + diff --git a/test/julia/juliacon_test.tex b/test/julia/juliacon_test.tex new file mode 100644 index 0000000..7a8138e --- /dev/null +++ b/test/julia/juliacon_test.tex @@ -0,0 +1,76 @@ +\documentclass[11pt]{article} + +% Engine-specific settings +% pdftex: +\ifcsname pdfmatch\endcsname + \usepackage[T1]{fontenc} + \usepackage[utf8]{inputenc} +\fi +% xetex: +\ifcsname XeTeXinterchartoks\endcsname + \usepackage{fontspec} + \defaultfontfeatures{Ligatures=TeX} +\fi +% luatex: +\ifcsname directlua\endcsname + \usepackage{fontspec} +\fi +% End engine-specific settings + +\usepackage{lmodern} +\usepackage{amssymb,amsmath} +\usepackage{graphicx} +\usepackage{fullpage} +\usepackage[keeptemps=all, makestderr, usefamily={juliacon,julia}]{pythontex} + + +\begin{document} + + +\section*{Julia Console} + + +\subsection*{Basics} + +Console math and printing. + +\begin{juliaconsole} +1+4 +println("Some text ...") +\end{juliaconsole} + +Test code that generates output without a trailing newline \verb|\n|. Since there is always a newline before the \verb|julia>| prompt, this should affect vertical layout, but not line breaks. +\begin{juliaconsole} +print("Some text ...") +print("And some more ...") +\end{juliaconsole} + +The next environment should start normally, with the prompt at the beginning of the line. +\begin{juliaconsole} +println("After ...") +\end{juliaconsole} + + + +\subsection*{Continuity between environments} + +Set a variable. +\begin{juliaconsole} +x = 2^12 +\end{juliaconsole} + +Retrieve variable value. +\begin{juliaconsole} +x +\end{juliaconsole} + + +\subsection*{Continue after errors} + +\begin{juliaconsole}[][breaklines] +1+"a" +3*6 +\end{juliaconsole} + + +\end{document} diff --git a/test/octave/octave_test.tex b/test/octave/octave_test.tex new file mode 100644 index 0000000..109b972 --- /dev/null +++ b/test/octave/octave_test.tex @@ -0,0 +1,102 @@ +\documentclass[11pt]{article} + +% Engine-specific settings +% pdftex: +\ifcsname pdfmatch\endcsname + \usepackage[T1]{fontenc} + \usepackage[utf8]{inputenc} +\fi +% xetex: +\ifcsname XeTeXinterchartoks\endcsname + \usepackage{fontspec} + \defaultfontfeatures{Ligatures=TeX} +\fi +% luatex: +\ifcsname directlua\endcsname + \usepackage{fontspec} +\fi +% End engine-specific settings + +\usepackage{lmodern} +\usepackage{amssymb,amsmath} +\usepackage{graphicx} +\usepackage{fullpage} +\usepackage[keeptemps=all, makestderr, usefamily={octave}]{pythontex} + +\begin{document} + + + +\section*{Octave} + +\subsection*{Commands} + +\octave{strrep(strrep(pwd(), '\', '/'), '_', '\_')} + +\octavec{disp(num2str(2^8))} + +\octaveb{disp(num2str(2^16))} + +\printpythontex + +\octavev{disp(num2str(2^32))} + + + +\subsection*{Environments} + +Code: +\begin{octavecode} +disp("Octave!") +disp(2^8) +\end{octavecode} + +Block: +\begin{octaveblock} +disp("Octave!") +disp(2^8) +\end{octaveblock} + +\printpythontex + +Verbatim: +\begin{octaveverbatim} +disp("Octave!") +disp(2^8) +\end{octaveverbatim} + +Sub: +\begin{octavesub} +In \LaTeX\ and then \textcolor{blue}{!{strcat(["Octave calculations $2^8=", num2str(2^8), "$"])}} and then back in \LaTeX. +\end{octavesub} + + + +\section*{Octave stderr} + + +\begin{octaveblock}[err1][numbers=left] +% Comment +s = "Octave a +\end{octaveblock} + +\stderrpythontex + +\begin{octaveblock}[err2][numbers=left] +1+ +\end{octaveblock} + +\stderrpythontex + +\begin{octaveblock}[err3][numbers=left] +% Comment +% Another comment +1 + qrst +\end{octaveblock} + +\stderrpythontex + + + +\end{document} + diff --git a/test/perl/perl_test.tex b/test/perl/perl_test.tex new file mode 100644 index 0000000..f89303d --- /dev/null +++ b/test/perl/perl_test.tex @@ -0,0 +1,103 @@ +\documentclass[11pt]{article} + +% Engine-specific settings +% pdftex: +\ifcsname pdfmatch\endcsname + \usepackage[T1]{fontenc} + \usepackage[utf8]{inputenc} +\fi +% xetex: +\ifcsname XeTeXinterchartoks\endcsname + \usepackage{fontspec} + \defaultfontfeatures{Ligatures=TeX} +\fi +% luatex: +\ifcsname directlua\endcsname + \usepackage{fontspec} +\fi +% End engine-specific settings + +\usepackage{lmodern} +\usepackage{amssymb,amsmath} +\usepackage{graphicx} +\usepackage{fullpage} +\usepackage[keeptemps=all, makestderr, usefamily={perl}]{pythontex} + + +\begin{document} + + + +\section*{Perl} + +\subsection*{Commands} + +\perl{2**8} + +\perlc{print 2**16;} + +\perlb{print 2**32;} + +\printpythontex + +\perlv{print 2**32;} + +\perls{\LaTeX\ and then \textcolor{blue}{!{"Perl"}} and back to \LaTeX.} + + +\subsection*{Environments} + +Code: +\begin{perlcode} +print "A string." . " "; +print 2**8; +\end{perlcode} + +Block: +\begin{perlblock} +print "A string." . " "; +print 2**8; +\end{perlblock} + +\printpythontex + +Verbatim: +\begin{perlverbatim} +print "A string." . " "; +print 2**8; +\end{perlverbatim} + +Sub: +\begin{perlsub} +\LaTeX\ and then \textcolor{blue}{!{"Perl"}} and back to \LaTeX. +\end{perlsub} + + +\section*{Perl stderr} + + +\begin{perlblock}[err1][numbers=left] +# Comment +my $s = "Perl a +\end{perlblock} + +\stderrpythontex[][breaklines, breakafter=\\/] + +\begin{perlblock}[err2][numbers=left] +1+; +\end{perlblock} + +\stderrpythontex[][breaklines, breakafter=\\/] + +\begin{perlblock}[err3][numbers=left] +# Comment +# Another comment +"a" "b"; +\end{perlblock} + +\stderrpythontex[][breaklines, breakafter=\\/] + + + +\end{document} + diff --git a/test/perl6/perl6_test.tex b/test/perl6/perl6_test.tex new file mode 100644 index 0000000..d0f1ac4 --- /dev/null +++ b/test/perl6/perl6_test.tex @@ -0,0 +1,127 @@ +\documentclass[11pt]{article} + +% Engine-specific settings +% pdftex: +\ifcsname pdfmatch\endcsname + \usepackage[T1]{fontenc} + \usepackage[utf8]{inputenc} +\fi +% xetex: +\ifcsname XeTeXinterchartoks\endcsname + \usepackage{fontspec} + \defaultfontfeatures{Ligatures=TeX} +\fi +% luatex: +\ifcsname directlua\endcsname + \usepackage{fontspec} +\fi +% End engine-specific settings + +\usepackage{lmodern} +\usepackage{amssymb,amsmath} +\usepackage{graphicx} +\usepackage{fullpage} +\usepackage[keeptemps=all, makestderr, usefamily={perlsix}]{pythontex} + + +\begin{document} + + + +\section*{Perl 6} + +\subsection*{Commands} + +\perlsix{2**8} + +\perlsixc{put 2**16;} + +\perlsixb{put 2**32;} + +\printpythontex + +\perlsixv{put 2**32;} + +\perlsixs{\LaTeX\ and then \textcolor{blue}{!{"Perl 6"}} and back to \LaTeX.} + + +\subsection*{Environments} + +Code: +\begin{perlsixcode} +put "A string. " ~ "More."; +put 2**8; +\end{perlsixcode} + +Block: +\begin{perlsixblock} +put "A string. " ~ "More."; +put 2**8; +\end{perlsixblock} + +\printpythontex + +Verbatim: +\begin{perlsixverbatim} +put "A string. " ~ "More."; +put 2**8; +\end{perlsixverbatim} + +Sub: +\begin{perlsixsub} +\LaTeX\ and then \textcolor{blue}{!{"Perl 6"}} and back to \LaTeX. +\end{perlsixsub} + + +\section*{Perl 6 stderr} + + +\begin{perlsixblock}[err1][numbers=left] +# Comment +my $s = "Perl a +\end{perlsixblock} + +\stderrpythontex[][breaklines, breakafter=\\/] + + +\begin{perlsixblock}[err2][numbers=left] +1+; +\end{perlsixblock} + +\stderrpythontex[][breaklines, breakafter=\\/] + + +\begin{perlsixblock}[err3][numbers=left] +# Comment +# Another comment +"a" "b"; +\end{perlsixblock} + +\stderrpythontex[][breaklines, breakafter=\\/] + + +\begin{perlsixblock}[err4][numbers=left] +# Comment +my $idx = "text"; +my @arr = <1, 2, 3>; +my $var = @arr[$idx]; +\end{perlsixblock} + +\stderrpythontex[][breaklines, breakafter=\\/] + + +\begin{perlsixblock}[err5][numbers=left] +sub nums {1 ... 1000} + +for nums { + say "Current element: $_"; +} +\end{perlsixblock} + +\stderrpythontex[][breaklines, breakafter=\\/] + + + + +\end{document} + diff --git a/test/ruby/ruby_test.tex b/test/ruby/ruby_test.tex new file mode 100644 index 0000000..dd4b3e8 --- /dev/null +++ b/test/ruby/ruby_test.tex @@ -0,0 +1,119 @@ +\documentclass[11pt]{article} + +% Engine-specific settings +% pdftex: +\ifcsname pdfmatch\endcsname + \usepackage[T1]{fontenc} + \usepackage[utf8]{inputenc} +\fi +% xetex: +\ifcsname XeTeXinterchartoks\endcsname + \usepackage{fontspec} + \defaultfontfeatures{Ligatures=TeX} +\fi +% luatex: +\ifcsname directlua\endcsname + \usepackage{fontspec} +\fi +% End engine-specific settings + +\usepackage{lmodern} +\usepackage{amssymb,amsmath} +\usepackage{graphicx} +\usepackage{fullpage} +\usepackage[keeptemps=all, makestderr, usefamily={ruby, rb}]{pythontex} + +\begin{document} + + +\section*{Ruby (\texttt{ruby})} + +\ruby{33*33} + +Code: +\begin{rubycode} +puts "Hello from Ruby!" +\end{rubycode} + +Block: +\begin{rubyblock} +puts "ABC" +\end{rubyblock} +Printed output: \printpythontex + +Verbatim: +\begin{rubyverbatim} +puts "Hello from Ruby! +\end{rubyverbatim} + +Sub: +\begin{rubysub} +In \LaTeX\textcolor{blue}{!{"...then Ruby..."}}and back in \LaTeX! +\end{rubysub} + + + +\section*{Ruby (\texttt{rb})} + +\rb{33*33} + +Code: +\begin{rbcode} +puts "Hello from Ruby!" +\end{rbcode} + +Block: +\begin{rbblock} +puts "ABC" +\end{rbblock} +Printed output: \printpythontex + +Verbatim: +\begin{rbverbatim} +puts "Hello from Ruby! +\end{rbverbatim} + +Sub: +\begin{rbsub} +In \LaTeX\textcolor{blue}{!{"...then Ruby..."}}and back in \LaTeX! +\end{rbsub} + + + +\subsection*{Errors} + +\setpythontexfv{numbers=left, firstnumber=last} + +\begin{rubyblock}[err1] +# Comment +s = <<-QQ Python a +\end{rubyblock} + +Should get no STDERR following this, because the sync system isn't yet sophisticated enough: \stderrpythontex + +\begin{rubyblock}[err1] +# Comment +#s = 'Python a +\end{rubyblock} + +\stderrpythontex + + +\begin{rubyblock}[err2][numbers=left] +1+ +\end{rubyblock} + +\stderrpythontex + + +\begin{rubyblock}[err3][numbers=left] +# Hmm +# Ah +1+'ab' +\end{rubyblock} + +\stderrpythontex + + +\end{document} + diff --git a/test/rust/rust_test.tex b/test/rust/rust_test.tex new file mode 100644 index 0000000..1823b4f --- /dev/null +++ b/test/rust/rust_test.tex @@ -0,0 +1,131 @@ +\documentclass[11pt]{article} + +% Engine-specific settings +% pdftex: +\ifcsname pdfmatch\endcsname + \usepackage[T1]{fontenc} + \usepackage[utf8]{inputenc} +\fi +% xetex: +\ifcsname XeTeXinterchartoks\endcsname + \usepackage{fontspec} + \defaultfontfeatures{Ligatures=TeX} +\fi +% luatex: +\ifcsname directlua\endcsname + \usepackage{fontspec} +\fi +% End engine-specific settings + +\usepackage{lmodern} +\usepackage{amssymb,amsmath} +\usepackage{graphicx} +\usepackage{fullpage} +\usepackage[keeptemps=all, makestderr, usefamily={rust, rs}]{pythontex} +\fvset{breaklines} + +\begin{document} + + +\section*{Rust (\texttt{rust})} + +Inline: \rust{format!("$3 + 5 = {}$", 3+5)}. \rusts{$4 + 6 = !{4+6}$}. + + +\begin{rustcode} +println!("Hello from Rust!"); +println!("Running command family ``{}'', session ``{}'', restart ``{}''.", rstex.family, rstex.session, rstex.restart); +\end{rustcode} + + + +\begin{rustblock} +println!("{}\\endinput", 2.0_f64.powf(8.0_f64)); +\end{rustblock} + +Printed output: \printpythontex. + +\begin{rustsub} +\color{blue} +\begin{Verbatim} +2.0_f64.powf(8.0_f64) = !{2.0_f64.powf(8.0_f64)} +\end{Verbatim} +\end{rustsub} + + +\section*{Rust (\texttt{rs})} + +Inline: \rs{format!("$3 + 5 = {}$", 3+5)}. \rss{$4 + 6 = !{4+6}$}. + + +\begin{rscode} +println!("Hello from Rust!"); +println!("Running command family ``{}'', session ``{}'', restart ``{}''.", rstex.family, rstex.session, rstex.restart); +\end{rscode} + + + +\begin{rsblock} +println!("{}\\endinput", 2.0_f64.powf(8.0_f64)); +\end{rsblock} + +Printed output: \printpythontex. + +\begin{rssub} +\color{blue} +\begin{Verbatim} +2.0_f64.powf(8.0_f64) = !{2.0_f64.powf(8.0_f64)} +\end{Verbatim} +\end{rssub} + + + +\section*{Errors} + +\begin{rustblock}[error] +println!("{}\\endinput", 2.0_f64.powf(8.0_f64); +\end{rustblock} + +\stderrpythontex + + + +\section*{File tracking} + +\begin{rustblock}[file_io] +// Open and write to two files, to check created file tracking +let mut my_file1 = io::BufWriter::new(rstex.open("my_file1.txt", open_mode::W) + .unwrap()); +writeln!(my_file1, "This is a text file.").unwrap(); +writeln!(my_file1, "Some more text.").unwrap(); +writeln!(my_file1, "3 + 2 = {}", 3 + 2).unwrap(); +drop(my_file1); + +let mut my_file2 = io::BufWriter::new(rstex.open("my_file2.txt", open_mode::W) + .unwrap()); +writeln!(my_file2, "This is another text file.").unwrap(); +writeln!(my_file2, "Some more text for another text file.").unwrap(); +writeln!(my_file2, "5 + 8 = {}", 5 + 8).unwrap(); +drop(my_file2); + + +// Open and read from LaTeX file, to check dependency tracking +let in_file = "rust_test.tex"; +println!("Reading from file \"{}\"...", in_file); +for (n, line) in io::BufReader::new(rstex.open(in_file, open_mode::R) + .unwrap()) + .lines() + .map(Result::unwrap) + .enumerate() +{ + if n < 8 { + println!("line {}: {}", n + 1, line); + } +} +\end{rustblock} + +Text written to stdout: + +\printpythontex[verbatim] + +\end{document}