diff options
author | JinWang An <jinwang.an@samsung.com> | 2021-12-01 16:54:37 +0900 |
---|---|---|
committer | JinWang An <jinwang.an@samsung.com> | 2021-12-01 16:54:37 +0900 |
commit | 56c3832bc03cffe24fcca71370b668a6678d0cf9 (patch) | |
tree | 7d2ccfe8d9c60dbb012bb62d7fcd6820db56dc61 /lang/python/doc | |
parent | 398648eddb70bc844e1bc5996521e87dea3188e9 (diff) | |
download | gpgme-56c3832bc03cffe24fcca71370b668a6678d0cf9.tar.gz gpgme-56c3832bc03cffe24fcca71370b668a6678d0cf9.tar.bz2 gpgme-56c3832bc03cffe24fcca71370b668a6678d0cf9.zip |
Imported Upstream version 1.12.0upstream/1.12.0
Diffstat (limited to 'lang/python/doc')
-rw-r--r-- | lang/python/doc/README | 47 | ||||
-rw-r--r-- | lang/python/doc/meta/TODO.org | 251 | ||||
-rw-r--r-- | lang/python/doc/meta/old-commits.log | 2445 | ||||
-rw-r--r-- | lang/python/doc/rst/gpgme-python-howto.rst | 2998 | ||||
-rw-r--r-- | lang/python/doc/rst/index.rst | 12 | ||||
-rw-r--r-- | lang/python/doc/rst/short-history.rst | 152 | ||||
-rw-r--r-- | lang/python/doc/src/gpgme-python-howto.org | 3043 | ||||
-rw-r--r-- | lang/python/doc/src/index.org | 25 | ||||
-rw-r--r-- | lang/python/doc/src/short-history.org | 172 | ||||
-rw-r--r-- | lang/python/doc/texinfo/gpgme-python-howto.texi | 3155 | ||||
-rw-r--r-- | lang/python/doc/texinfo/index.texi | 52 | ||||
-rw-r--r-- | lang/python/doc/texinfo/short-history.texi | 209 | ||||
-rw-r--r-- | lang/python/doc/texinfo/texinfo.tex | 8962 |
13 files changed, 21523 insertions, 0 deletions
diff --git a/lang/python/doc/README b/lang/python/doc/README new file mode 100644 index 0000000..a14e1ad --- /dev/null +++ b/lang/python/doc/README @@ -0,0 +1,47 @@ +GPGME Python Bindings Documentation +=================================== + +As the GPGME Python bindings exist in two worlds within the FOSS +universe, it's always had a little issue with regards to its +documentation and specifically to the format of it. The GnuPG +Project, like much of the rest of the GNU Project, uses Texinfo to +build its documentation. While the actual format used to write and +edit that documentation is Org mode. Largely because most, if not +all, of the GnuPG developers use GNU Emacs for much of their work. + +The Python world, however, utilises reStructuredText almost +universally. This in turn is used by Sphinx or Docutils directly to +build the documentation. + +Each has various advantages for their own ecisystems, but this part of +the GnuPG effort is aimed at both sides. So, long story short, this +documentation is provided as both Texinfo and reStructuredText files. + +This docs directory contains four main subdirectories: + + 1. meta + 2. src + 3. rst + 4. texinfo + +The Meta directory is for docs that are not intended for distribution +or are about the docs themselves. The sole exception being this RDME +file. + +The Src directory is where the original edited files are, from which +the following two formats are generated initially. Most, if not all, +of these are written in Org Mode. + +The ReST directory contains reStructuredText files ehich have been +converted to that format from the Org Mode files via Pandoc. + +The Texinfo directory contains Texinfo files which have been exported +to that format from the Org Mode files by Org Mode itself within GNU +Emacs. + +Those latter two directories should then be used by their respective +build systems to produce the various output file formats they normally +do. They should not spill out into this parent directory. +Particularly since it is quite possible, perhaps even likely, that +alternatives to both of them may be added to this parent documentation +directory at some future point. diff --git a/lang/python/doc/meta/TODO.org b/lang/python/doc/meta/TODO.org new file mode 100644 index 0000000..0be99b3 --- /dev/null +++ b/lang/python/doc/meta/TODO.org @@ -0,0 +1,251 @@ +#+TITLE: Stuff To Do +#+LATEX_COMPILER: xelatex +#+LATEX_CLASS: article +#+LATEX_CLASS_OPTIONS: [12pt] +#+LATEX_HEADER: \usepackage{xltxtra} +#+LATEX_HEADER: \usepackage[margin=1in]{geometry} +#+LATEX_HEADER: \setmainfont[Ligatures={Common}]{Latin Modern Roman} + +* Project Task List + :PROPERTIES: + :CUSTOM_ID: task-list + :END: + +** DONE Documentation default format + CLOSED: [2018-02-15 Thu 21:29] + :PROPERTIES: + :CUSTOM_ID: todo-docs-default + :END: + + Decide on a default file format for documentation. The two main + contenders being Org Mode, the default for the GnuPG Project and + reStructuredText, the default for Python projects. A third option + of DITA XML was considered due to a number of beneficial features + it provides. + + The decision was made to use Org Mode in order to fully integrate + with the rest of the GPGME and GnuPG documentation. It is possible + to produce reST versions via Pandoc and DITA XML can be reached + through converting to either Markdown or XHTML first. + + +** TODO Documentation build systems + :PROPERTIES: + :CUSTOM_ID: todo-docs-build-systems + :END: + +Though Org Mode is being used for the default documentation format, it +still needs to end up as usable by end users. So the Org Mode files +are used to produce the "source" files used by the two main contenders +for documenting the bindings: Texinfo and ReStructuredText/Docutils. + + +*** TODO Texinfo documentation + :PROPERTIES: + :CUSTOM_ID: todo-docs-build-texinfo + :END: + +Need to add all of Texinfo's ... special systems to make it do its +things. + + +*** TODO ReStructuredText documentation + :PROPERTIES: + :CUSTOM_ID: todo-docs-build-docutils + :END: + +Need to run Sphinx's quick start, add it to the requirements and tweak +the index page for the rst files to point to the HOWTO and other files. + +It might just be easier to do all that in Org Mode and convert the +lot, then the Sphinx bits can be automated. + + +** STARTED Documentation HOWTO + :PROPERTIES: + :CUSTOM_ID: todo-docs-howto + :END: + + - State "STARTED" from "TODO" [2018-03-08 Thu 13:59] \\ + Started yesterday. + Write a HOWTO style guide for the current Python bindings. + +*** DONE Start python bindings HOWTO + CLOSED: [2018-03-07 Wed 18:14] + :PROPERTIES: + :CUSTOM_ID: howto-start + :END: + + +*** STARTED Include certain specific instructions in the HOWTO + :PROPERTIES: + :CUSTOM_ID: howto-requests + :END: + + Note: moved the S/MIME bits out to their own section of the TODO + list and may be served better by separate HOWTO documentation + anyway. + + - State "STARTED" from "TODO" [2018-03-09 Fri 15:27] + Some functions can be worked out from the handful of examples + available, but many more can't and I've already begun receiving + requests for certain functions to be explained. + + +**** DONE Standard scenarios + CLOSED: [2018-03-19 Mon 12:34] + :PROPERTIES: + :CUSTOM_ID: howto-the-basics + :END: + + - State "DONE" from "STARTED" [2018-03-19 Mon 12:34] \\ + All four of those are done. + - State "STARTED" from "TODO" [2018-03-09 Fri 15:26] \\ + Began with the example code, now to add the text. + What everyone expects: encryption, decryption, signing and verifying. + + +**** STARTED Key control + :PROPERTIES: + :CUSTOM_ID: howto-key-control + :END: + + - State "STARTED" from "TODO" [2018-03-19 Mon 12:35] \\ + Generating keys and subkeys are done, but revocation is still to be done. + Generating keys, adding subkeys, revoking subkeys (and keeping + the cert key), adding and revoking UIDs, signing/certifying keys. + + +**** DONE More key control + CLOSED: [2018-03-19 Mon 12:36] + :PROPERTIES: + :CUSTOM_ID: howto-key-selection + :END: + + - State "DONE" from "TODO" [2018-03-19 Mon 12:36] \\ + Key selection, searching, matching and counting is done. + Selecting keys to encrypt to or manipulate in other ways (e.g. as + with key control or the basics). + + +** TODO Documentation SWIG + :PROPERTIES: + :CUSTOM_ID: todo-docs-swig + :END: + + Write documentation for the complete SWIG bindings demonstrating + the correspondence with GPGME itself. + + Note: it is likely that this will be more in the nature of + something to be used in conjunction with the existing GPGME + documentation which makes it easier for Python developers to use. + + +** TODO GUI examples + :PROPERTIES: + :CUSTOM_ID: todo-gui-examples + :END: + + Create some examples of using Python bindings in a GUI application + to either match or be similar to the old GTK2 examples available + with PyME. + + +** TODO Replace SWIG + :PROPERTIES: + :CUSTOM_ID: todo-replace-swig + :END: + + Selecting SWIG for this project in 2002 was understandable and + effectively the only viable option. The options available now, + however, are significantly improved and some of those would resolve + a number of existing problems with using SWIG, particularly when + running code on both POSIX compliant and Windows platforms. + + The long term goal is to replace SWIG by reimplementing the Python + bindings using a more suitable means of interfacing with the GPGME + C source code. + + +*** TODO Replacement for SWIG + :PROPERTIES: + :CUSTOM_ID: todo-replace-swig-replacement + :END: + + Decide on a replacement for SWIG. Currently CFFI is looking like + the most viable candidate, but some additional testing and checks + are yet to be completed. + + +** TODO API for an API + :PROPERTIES: + :CUSTOM_ID: todo-api-squared + :END: + + A C API like GPGME is not what most modern developers think of when + they hear the term API. Normally they think of something they can + interact with like a RESTful web API. Though RESTful is unlikely + given the nature of GPGME and the process of encryption, it may be + possible to provide a more familiar interface which can be utilised + by developers of other languages for which bindings are not + available or for which it is too difficult to create proper + bindings. + + +** TODO S/MIME + :PROPERTIES: + :CUSTOM_ID: s-mime + :END: + + Eventually add some of this, but the OpenPGP details are far more + important at the moment. + + +* Project Task Details + :PROPERTIES: + :CUSTOM_ID: detailed-tasks + :END: + +** Working examples + :PROPERTIES: + :CUSTOM_ID: working-examples + :END: + + The old GUI examples were unable to be retained since they depended + on GTK2 and Python 2's integration with GTK2. + + Current GPGME examples so far only include command line tools or + basic Python code for use with either Python 2.7 or Python 3.4 and + above. + + Future GUI examples ought to utilise available GUI modules and + libraries supported by Python 3. This may include Qt frameworks, + Tkinter, GTK3 or something else entirely. + +** Documentation + :PROPERTIES: + :CUSTOM_ID: documentation + :END: + + The legacy documentation which no longer applies to the Python + bindings has been removed. + + Current and future documentation will adhere to the GnuPG standard + of using Org Mode and not use the reStructuredText (reST) format + more commonly associated with Python documentation. The reasons + for this are that this project is best served as shipping with the + rest of GPGME and the documentation ought to match that. There are + also aspects of Org Mode's publishing features which are superior + to the defaults of reST, including the capacity to generate fully + validating strict XHTML output. + + If reST files are required at a later point for future inclusion + with other Python packages, then that format can be generated from + the .org files with Pandoc before being leveraged by either + Docutils, Sphinx or something else. + + While there are some advanced typesetting features of reST which + are not directly available to Org Mode, more often than not those + features are best implemented with either HTML and CSS, with LaTeX + to produce a PDF or via a number of XML solutions. Both reST and + Org Mode have multiple paths by which to achieve all of these. diff --git a/lang/python/doc/meta/old-commits.log b/lang/python/doc/meta/old-commits.log new file mode 100644 index 0000000..93661e3 --- /dev/null +++ b/lang/python/doc/meta/old-commits.log @@ -0,0 +1,2445 @@ +commit 2145348ec54c6027f2ea20f695de0277e2871405 +Merge: 348ba88 2036f1a +Author: Ben McGinnes <ben@adversary.org> +Date: Wed May 6 03:04:19 2015 +1000 + + Merge pull request #4 from Hasimir/master + + history + +commit 2036f1a0a670a0561993e195c458059220b36114 +Merge: dbabf0c 348ba88 +Author: Ben McGinnes <ben@adversary.org> +Date: Wed May 6 02:57:44 2015 +1000 + + Merge branch 'master' of github:adversary-org/pyme3 + +commit dbabf0cf1f2985755c2293b619011832e34faa9c +Author: Ben McGinnes <ben@adversary.org> +Date: Wed May 6 02:52:23 2015 +1000 + + Added a short history + + * A (very) brief summary of the project's history since 2002. + * Deals with why the commit log in the GPGME repo does not include the + history of PyME. + * Mentions that intact git repos will be maintained, but not where they + are (one will be on github, another will be in a user directory on + playfair.gnupg.org). + + docs/Short_History.rst | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 57 insertions(+) + +commit 348ba883424778c711c04ae9b66035ccdb36eb8c +Merge: 127d0a5 7c37a27 +Author: Ben McGinnes <ben@adversary.org> +Date: Wed May 6 02:21:34 2015 +1000 + + Merge pull request #3 from Hasimir/master + + Version release preparation + +commit 7c37a27a6845c58222d4d947c2efbe38e955b612 +Merge: f692cff 127d0a5 +Author: Ben McGinnes <ben@adversary.org> +Date: Wed May 6 02:17:14 2015 +1000 + + Merge branch 'master' of github:adversary-org/pyme3 + +commit f692cff50a89c2c61acdbd3d7dd60f5ce3cd15af +Author: Ben McGinnes <ben@adversary.org> +Date: Wed May 6 02:09:44 2015 +1000 + + TODO update + + * Removed reference to GitHub, replaced with impending new home at gnupg.org. + + docs/TODO.rst | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +commit bd5ccf9e3bfe69fa681613757577e87b72ca08ec +Author: Ben McGinnes <ben@adversary.org> +Date: Wed May 6 02:00:44 2015 +1000 + + Version bump + + * Bumped version number to 0.9.1 to keep it somewhat in line with the + existing PyME project, even though there will be some divergence at + some point (or even re-merging, depending on how many of the Python 3 + modifications can be back-ported to the Python 2 version). + * Updated the author and copyright information to reflect the two + current authors (Martin and I). + * Replaced Igor's contact details with mine. + * Replaced project home page with the GnuPG one. + + pyme/version.py | 16 +++++++++------- + 1 file changed, 9 insertions(+), 7 deletions(-) + +commit ec167512f4ca88d8f6e89e2ae831798c8283b4df +Author: Ben McGinnes <ben@adversary.org> +Date: Wed May 6 01:48:01 2015 +1000 + + README preparation. + + * Changes in preparation for impending move of code to the GnuPG git + server as a part of GPGME. + + README.rst | 14 +++++++------- + 1 file changed, 7 insertions(+), 7 deletions(-) + +commit 8a48515e884c36b5bdb24a13cb4d2e49f4ee6f17 +Author: Ben McGinnes <ben@adversary.org> +Date: Wed May 6 01:43:53 2015 +1000 + + TODO moved to docs + + * As it says. + + TODO.rst | 25 ------------------------- + docs/TODO.rst | 25 +++++++++++++++++++++++++ + 2 files changed, 25 insertions(+), 25 deletions(-) + +commit f968c777472f01f308f6e57eac1740bf5c76c205 +Author: Ben McGinnes <ben@adversary.org> +Date: Sun May 3 16:52:13 2015 +1000 + + Started another TODO file. + + TODO.rst | 25 +++++++++++++++++++++++++ + 1 file changed, 25 insertions(+) + +commit 127d0a56fa9f7ad1d4fb39d0b529b890a8d67365 +Merge: db72dea 44837f6 +Author: Ben McGinnes <ben@adversary.org> +Date: Sun May 3 14:59:44 2015 +1000 + + Merge pull request #2 from Hasimir/master + + Minor editing. + +commit 44837f6e50fc539c86aef1f75a6a3538b02029ea +Author: Ben McGinnes <ben@adversary.org> +Date: Sun May 3 14:56:55 2015 +1000 + + Minor editing. + + * Fixed another URL. + * Changed Py3 version's version number to v0.9.1-beta0. + + README.rst | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +commit db72deaae19c3513391df040bcaf66a88d9213af +Merge: db34286 48eb185 +Author: Ben McGinnes <ben@adversary.org> +Date: Sun May 3 14:26:11 2015 +1000 + + Merge pull request #1 from Hasimir/master + + Links + +commit 48eb1856cb0739cc9f0b9084da9d965e1fc7fddd +Author: Ben McGinnes <ben@adversary.org> +Date: Sun May 3 14:22:30 2015 +1000 + + Links + + * Fixed URLs for authors. + * Updated my entry to point to github location. + ** I strongly suspect the result of this work will be concurrent + projects, so preparing for that eventuality with this repo. + + README.rst | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +commit db3428659783f30b9a76204403daedf9fc4cf7cf +Author: Ben McGinnes <ben@adversary.org> +Date: Sun May 3 11:29:00 2015 +1000 + + Explicit over Implicit ... + + ... isn't just for code. + + * Removed the 2to3 working directory and its contents. + * Made the README.rst file a little more clear that this branch is for + Python 3 (set Python 3.2 as a fairly arbitrary requirement for the + moment, but will probably raise this to 3.3). + + 2to3/2to3-output-remaining.log | 60 --- + 2to3/2to3-output-setup.log | 35 -- + 2to3/2to3-output.log | 950 ----------------------------------------- + README.rst | 10 +- + 4 files changed, 7 insertions(+), 1048 deletions(-) + +commit 3edf07a4ba8a86af3a33246234d6e133074862af +Author: Ben McGinnes <ben@adversary.org> +Date: Sun May 3 11:19:41 2015 +1000 + + Added authors. + + * In alphabetical order. + * Mine will need updating once Martin and I have decided what to do + regarding the two main branches. + + README.rst | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +commit 811eb14b53e8856312d99f46b77215f7f9bd672c +Author: Ben McGinnes <ben@adversary.org> +Date: Sun May 3 10:23:00 2015 +1000 + + Docs and other things. + + * Now able to import pyme.core without error, indicates port process is + successful. + * Code is *not* compatible with the Python 2 version. + * Will need to consider making this a parallel project with the master + branch. + * Got rid of the .org TODO file. + * Changed the README to use the reST file extension since it's full of + reST anyway. + + 2to3/TODO.org | 5 ----- + README.rst | 32 ++++++++++++++++++++++++++++++++ + README.txt | 32 -------------------------------- + 3 files changed, 32 insertions(+), 37 deletions(-) + +commit 79e784bdcce1de6f7856921b5431044c62c6f015 +Author: Ben McGinnes <ben@adversary.org> +Date: Sun May 3 10:18:40 2015 +1000 + + Fixed another implicit import by making it explicit. Hopefully this is the last one. + + pyme/util.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +commit 2b52b46ccda3e7abcc50eed0745062259d698661 +Author: Ben McGinnes <ben@adversary.org> +Date: Sun May 3 10:16:01 2015 +1000 + + Fixed another implicit import by making it explicit. + + pyme/errors.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +commit 409c8fd565e21f23cd41daaeffc867e6d23a0863 +Author: Ben McGinnes <ben@adversary.org> +Date: Sun May 3 10:08:22 2015 +1000 + + Bytes vs. Unicode + + * Trying PyBytes instead of PyUnicode. + + gpgme.i | 14 +++++++------- + helpers.c | 8 ++++---- + 2 files changed, 11 insertions(+), 11 deletions(-) + +commit d8164aa2ae98bf8c807c16e2d9be12c5fbea7cfd +Author: Ben McGinnes <ben@adversary.org> +Date: Sun May 3 09:22:58 2015 +1000 + + String to Unicode + + * Replaced all instances of PyString with PyUnicode (and hoping there's + no byte data in there). + + gpgme.i | 14 +++++++------- + helpers.c | 8 ++++---- + 2 files changed, 11 insertions(+), 11 deletions(-) + +commit bd99b7865656e559b17c419c6b64b412a22c6c44 +Author: Ben McGinnes <ben@adversary.org> +Date: Sun May 3 09:17:06 2015 +1000 + + PyInt_AsLong + + * Replaced all instances of PyInt with PyLong, as per C API docs. + + gpgme.i | 4 ++-- + helpers.c | 8 ++++---- + 2 files changed, 6 insertions(+), 6 deletions(-) + +commit 3c91e2ccf8ca788b51e3308e292c6b64888fdb15 +Author: Ben McGinnes <ben@adversary.org> +Date: Sun May 3 05:59:36 2015 +1000 + + Import correction + + * Once pygpgme.py is generated and moved, it will be in the right + directory for the explicit "from . import pygpgme" to be correct. + + pyme/core.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +commit 23a49e7070812ff1ce138d8d4cc46d0b80328897 +Author: Ben McGinnes <ben@adversary.org> +Date: Sun May 3 05:38:29 2015 +1000 + + The -py3 flag. + + Makefile | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +commit b1549587d6db5e33081b9c20f75d1348a1d25938 +Author: Ben McGinnes <ben@adversary.org> +Date: Sun May 3 05:01:42 2015 +1000 + + Fixed indentation - 4. + + pyme/core.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +commit a685142ce46761ee6f5176e90717176e38e0d24f +Author: Ben McGinnes <ben@adversary.org> +Date: Sun May 3 05:00:16 2015 +1000 + + Fixed indentation - 3. + + pyme/core.py | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + +commit 488a70b490cc64eb1c47d2483cb2f4079c6767f7 +Author: Ben McGinnes <ben@adversary.org> +Date: Sun May 3 04:53:21 2015 +1000 + + Pet Peeve + + def pet_peeve(self): + peeve = print("people who don't press return after a colon!") + + FFS! + + pyme/core.py | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +commit a5d38eb47d64bb17bb609fe594dae2aca480bac9 +Author: Ben McGinnes <ben@adversary.org> +Date: Sun May 3 04:47:54 2015 +1000 + + Fixed indentation - 2. + + pyme/core.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +commit 476a207f732b8559abb1ea3c23147c0e34804730 +Author: Ben McGinnes <ben@adversary.org> +Date: Sun May 3 04:46:01 2015 +1000 + + Fixed indentation. + + pyme/core.py | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +commit 0572900eba9bcd9b0283c7d8e022e8972f06f9f8 +Author: Ben McGinnes <ben@adversary.org> +Date: Sun May 3 04:43:49 2015 +1000 + + Replaced all tabs with 4 spaces. + + pyme/core.py | 18 +++++++++--------- + 1 file changed, 9 insertions(+), 9 deletions(-) + +commit 78c0b7677e94ce1e11b8cdb833a9064527187330 +Author: Ben McGinnes <ben@adversary.org> +Date: Sun May 3 04:39:07 2015 +1000 + + SWIG flags in the wrong place. + + Makefile | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +commit dfa7f2589963494a8f89277560d8c1116604a3c8 +Author: Ben McGinnes <ben@adversary.org> +Date: Sun May 3 04:35:09 2015 +1000 + + Fixed subprocess call for swig (again). + + setup.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +commit 249bfd8c714dcda53127b99b6cc8a6c7c4a99f20 +Author: Ben McGinnes <ben@adversary.org> +Date: Sun May 3 04:32:40 2015 +1000 + + Fixed subprocess call for swig. + + setup.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +commit 6fd7e719cf4c975f466ceb39835db7007df36fb2 +Author: Ben McGinnes <ben@adversary.org> +Date: Sun May 3 03:51:48 2015 +1000 + + Linking swig to py3 + + * Changed the swig invocations to run with the -python -py3 flags explicitly. + + Makefile | 4 ++-- + setup.py | 2 +- + 2 files changed, 3 insertions(+), 3 deletions(-) + +commit 7a6b584f50ed6ddc8617a642185eea1f24ff791a +Author: Ben McGinnes <ben@adversary.org> +Date: Sat May 2 11:12:00 2015 +1000 + + String fun + + * streamlined confdata details, including decoding strom binary to string. + + setup.py | 4 +--- + 1 file changed, 1 insertion(+), 3 deletions(-) + +commit f7fd3f270592021a95a8f779bfe85ac18f4e390b +Author: Ben McGinnes <ben@adversary.org> +Date: Sat May 2 10:46:59 2015 +1000 + + Open File + + * Removed deprecated file() and replaced with open(). + + examples/PyGtkGpgKeys.py | 2 +- + examples/pygpa.py | 6 +++--- + gpgme-h-clean.py | 2 +- + 3 files changed, 5 insertions(+), 5 deletions(-) + +commit 4227d486f9558015e7e548d71085e58e1b50ec08 +Author: Ben McGinnes <ben@adversary.org> +Date: Sat May 2 10:36:15 2015 +1000 + + print() fix + + * Makefile includes a python print, changed from statement to function. + + Makefile | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +commit 406f7f2567b701502186fe0a325dc2a3491ff7f8 +Author: Ben McGinnes <ben@adversary.org> +Date: Sat May 2 10:28:42 2015 +1000 + + Updated Makefile + + * set make to use python3 instead. + * This will mean a successful port may need to be maintained seperately + from the original python2 code instead of merged, but ought to be able + to share most things. So maybe merge with separated make files or a + pre-make script to set python2 or python3 prior to building ... decide + later, after it works. + + Makefile | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +commit 90b3efa5b193d37e08dc9b4ee766ba9ebc9412af +Author: Ben McGinnes <ben@adversary.org> +Date: Sat May 2 10:15:20 2015 +1000 + + Env and a little license issue + + * Updated all the /usr/bin/env paths to point to python3. + * Also fixed the hard coded /usr/bin/python paths. + * Updated part of setup.py which gave the impression this package was + only licensed under the GPL (it's actually licensed under the LGPL as + well, essentially the same dual licensing as the GPGME library). + + examples/PyGtkGpgKeys.py | 2 +- + examples/delkey.py | 2 +- + examples/encrypt-to-all.py | 2 +- + examples/exportimport.py | 2 +- + examples/genkey.py | 2 +- + examples/inter-edit.py | 2 +- + examples/pygpa.py | 2 +- + examples/sign.py | 2 +- + examples/signverify.py | 2 +- + examples/simple.py | 2 +- + examples/t-edit.py | 2 +- + examples/testCMSgetkey.py | 2 +- + examples/verifydetails.py | 2 +- + gpgme-h-clean.py | 2 +- + setup.py | 4 ++-- + 15 files changed, 16 insertions(+), 16 deletions(-) + +commit 1a4b55dbccd2774344352e579130bf494bc5fa4b +Author: Ben McGinnes <ben@adversary.org> +Date: Sat May 2 08:50:54 2015 +1000 + + Removed extraneous files. + + * The two .bak files. + + pyme/errors.py.bak | 46 --------------------- + setup.py.bak | 116 ----------------------------------------------------- + 2 files changed, 162 deletions(-) + +commit 208879d4f2a6d0514c3f8ee2fc0da8bba42350de +Author: Ben McGinnes <ben@adversary.org> +Date: Sat May 2 08:19:37 2015 +1000 + + Added TODO.org + + * TODO list in Emacs org-mode. + * Will eventually be removed along with this entire directory when the + porting process is complete. + + 2to3/TODO.org | 5 +++++ + 1 file changed, 5 insertions(+) + +commit 1548bf201059638675c5387c6f124d4b703363a9 +Author: Ben McGinnes <ben@adversary.org> +Date: Sat May 2 07:58:40 2015 +1000 + + 2to3 conversion of remaining files + + * Ran the extended version against all the unmodified python files. + * Only pyme/errors.py required additional work. + + 2to3/2to3-output-remaining.log | 60 ++++++++++++++++++++++++++++++++++++++++++ + pyme/errors.py | 2 +- + pyme/errors.py.bak | 46 ++++++++++++++++++++++++++++++++ + 3 files changed, 107 insertions(+), 1 deletion(-) + +commit 1230650bc6bbe4c14d1284f7877aa932f3e86eb4 +Author: Ben McGinnes <ben@adversary.org> +Date: Sat May 2 07:50:39 2015 +1000 + + 2to3 conversion of setup.py + + * Ran extended 2to3 command to produce python 3 code for setup.py. + * Effectively testing for what to run against the other originally + unmodified py2 files. + + 2to3/2to3-output-setup.log | 35 ++++++++++++++ + setup.py | 7 ++- + setup.py.bak | 116 +++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 154 insertions(+), 4 deletions(-) + +commit edad44955f59aa879e95a369591717fb19eec6b7 +Author: Ben McGinnes <ben@adversary.org> +Date: Fri May 1 21:50:07 2015 +1000 + + Removing 2to3 generated .bak files. + + * Not really needed with a real VCS, but couldn't hurt to have them for + a couple of revisions. ;) + + examples/PyGtkGpgKeys.py.bak | 663 --------------- + examples/encrypt-to-all.py.bak | 65 -- + examples/exportimport.py.bak | 75 -- + examples/genkey.py.bak | 45 - + examples/inter-edit.py.bak | 57 -- + examples/pygpa.py.bak | 1457 -------------------------------- + examples/sign.py.bak | 31 - + examples/signverify.py.bak | 78 -- + examples/simple.py.bak | 52 -- + examples/t-edit.py.bak | 59 -- + examples/testCMSgetkey.py.bak | 45 - + examples/verifydetails.py.bak | 100 --- + gpgme-h-clean.py.bak | 42 - + pyme/callbacks.py.bak | 47 -- + pyme/constants/data/__init__.py.bak | 4 - + pyme/constants/keylist/__init__.py.bak | 4 - + pyme/constants/sig/__init__.py.bak | 4 - + pyme/core.py.bak | 463 ---------- + pyme/util.py.bak | 72 -- + pyme/version.py.bak | 41 - + 20 files changed, 3404 deletions(-) + +commit 1cfc3c969f885ed191610bffbbd60ac23fdd349e +Author: Ben McGinnes <ben@adversary.org> +Date: Fri May 1 21:45:50 2015 +1000 + + 2to3 conversion log + + * The output of the command to convert the code from Python 2 to 3. + * Note: this contains the list of files which were not modified and + which will or may need to be modified. + + 2to3/2to3-output.log | 950 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 950 insertions(+) + +commit 078f6cf878aa62d12704fab424198a613a24cc8c +Author: Ben McGinnes <ben@adversary.org> +Date: Fri May 1 21:36:58 2015 +1000 + + 2to3 conversion of pyme master + + * Branch from commit 459f3eca659b4949e394c4a032d9ce2053e6c721 + * Ran this: or x in `find . | egrep .py$` ; do 2to3 -w $x; done ; + * Multiple files not modified, will record elsewhere (see next commit). + + examples/PyGtkGpgKeys.py | 10 +- + examples/PyGtkGpgKeys.py.bak | 663 +++++++++++++++ + examples/encrypt-to-all.py | 12 +- + examples/encrypt-to-all.py.bak | 65 ++ + examples/exportimport.py | 20 +- + examples/exportimport.py.bak | 75 ++ + examples/genkey.py | 2 +- + examples/genkey.py.bak | 45 + + examples/inter-edit.py | 8 +- + examples/inter-edit.py.bak | 57 ++ + examples/pygpa.py | 40 +- + examples/pygpa.py.bak | 1457 ++++++++++++++++++++++++++++++++ + examples/sign.py | 2 +- + examples/sign.py.bak | 31 + + examples/signverify.py | 18 +- + examples/signverify.py.bak | 78 ++ + examples/simple.py | 8 +- + examples/simple.py.bak | 52 ++ + examples/t-edit.py | 12 +- + examples/t-edit.py.bak | 59 ++ + examples/testCMSgetkey.py | 8 +- + examples/testCMSgetkey.py.bak | 45 + + examples/verifydetails.py | 34 +- + examples/verifydetails.py.bak | 100 +++ + gpgme-h-clean.py | 2 +- + gpgme-h-clean.py.bak | 42 + + pyme/callbacks.py | 6 +- + pyme/callbacks.py.bak | 47 ++ + pyme/constants/data/__init__.py | 2 +- + pyme/constants/data/__init__.py.bak | 4 + + pyme/constants/keylist/__init__.py | 2 +- + pyme/constants/keylist/__init__.py.bak | 4 + + pyme/constants/sig/__init__.py | 2 +- + pyme/constants/sig/__init__.py.bak | 4 + + pyme/core.py | 26 +- + pyme/core.py.bak | 463 ++++++++++ + pyme/util.py | 6 +- + pyme/util.py.bak | 72 ++ + pyme/version.py | 2 +- + pyme/version.py.bak | 41 + + 40 files changed, 3515 insertions(+), 111 deletions(-) + +commit 459f3eca659b4949e394c4a032d9ce2053e6c721 +Merge: c5966ab dae7f14 +Author: Martin Albrecht <martinralbrecht@googlemail.com> +Date: Wed Jul 9 10:48:33 2014 +0100 + + Merged in jerrykan/pyme/fix_setup_26 (pull request #1) + + Provide support for using setup.py with Python v2.6 + +commit dae7f14a54e6c2bde0ad4da7308cc7fc0d0c0469 +Author: John Kristensen <john.kristensen@dpipwe.tas.gov.au> +Date: Wed Jul 9 15:54:39 2014 +1000 + + Provide support for using setup.py with Python v2.6 + + The setup.py script uses subprocess.check_output() which was introduced + in Python v2.7. The equivalent functionality can be achieved without + adding much extra code and provide support for Python v2.6. + + setup.py | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +commit c5966abec9d772b3922d32650da288fd50a217be +Author: Martin Albrecht <martinralbrecht@googlemail.com> +Date: Thu May 15 19:43:00 2014 +0100 + + README.txt in ReST, including headlines + + README.txt | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +commit 43ee8c6f34fa9b6d3975aa6ea60b3d4a741fa721 +Author: Martin Albrecht <martinralbrecht@googlemail.com> +Date: Thu May 15 19:37:15 2014 +0100 + + README.txt in ReST + + README.txt | 25 +++++++++++++------------ + 1 file changed, 13 insertions(+), 12 deletions(-) + +commit f71a369484cba8801df23ccc5842335fa496c0df +Author: Martin Albrecht <martinralbrecht@googlemail.com> +Date: Thu May 15 19:28:12 2014 +0100 + + added MANIFEST.in and README.txt (instead of .md) + + MANIFEST.in | 6 ++++++ + README.md | 27 --------------------------- + README.txt | 27 +++++++++++++++++++++++++++ + 3 files changed, 33 insertions(+), 27 deletions(-) + +commit d0d6755229f920b0bed043e9c2731de2d57c096c +Author: Martin Albrecht <martinralbrecht@googlemail.com> +Date: Tue May 13 09:52:44 2014 +0100 + + added mailing list to README + + README.md | 19 ++++++++++++++++--- + 1 file changed, 16 insertions(+), 3 deletions(-) + +commit 30ca60ddf92df684de261cb24c83c68089be0adc +Author: Martin Albrecht <martinralbrecht@googlemail.com> +Date: Sun May 11 13:34:28 2014 +0100 + + we don't need a separate out of date ChangeLog file + + ChangeLog | 802 -------------------------------------------------------------- + 1 file changed, 802 deletions(-) + +commit 8263f1a6d38fdb7f5f3dd5c7e28f83caa7528a08 +Author: Martin Albrecht <martinralbrecht@googlemail.com> +Date: Sun May 11 13:32:31 2014 +0100 + + adding README.md + + README.md | 14 ++++++++++++++ + 1 file changed, 14 insertions(+) + +commit 3fc71b47e9e14b0b984801c28d722723baa4b406 +Author: Martin Albrecht <martinralbrecht@googlemail.com> +Date: Sat May 10 15:43:06 2014 +0100 + + ValueError -> RuntimeError + + setup.py | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +commit eec432abea56296b9fa36aac0d10926a2335b739 +Merge: eea6537 d2738b3 +Author: Martin Albrecht <martinralbrecht@googlemail.com> +Date: Sat May 10 15:41:02 2014 +0100 + + Merge branch 'master' of bitbucket.org:malb/pyme + + Conflicts: + setup.py + +commit eea6537921061b4dcfc54e00a99d3fa110e71433 +Author: Martin Albrecht <martinralbrecht@googlemail.com> +Date: Sat May 10 15:39:51 2014 +0100 + + check for swig + + setup.py | 8 ++++++++ + 1 file changed, 8 insertions(+) + +commit 53867bf9715ee1b4ea873bf5e2fbb7d9740a2b4a +Author: Martin Albrecht <martinralbrecht@googlemail.com> +Date: Sat May 10 15:35:04 2014 +0100 + + more friendly error message if gpgme is missing + + setup.py | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +commit d2738b35d63b1492d69641c5466103685f2d3a30 +Author: Martin Albrecht <martinralbrecht@googlemail.com> +Date: Sat May 10 15:35:04 2014 +0100 + + more friendly error message if gpgme is missing + + setup.py | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +commit c0b01240becf8ba6cf1d4c1f64b2cb4c056f5163 +Author: Martin Albrecht <martinralbrecht@googlemail.com> +Date: Fri May 9 15:20:24 2014 +0100 + + version number should have three digits + + pyme/version.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +commit 6672bb60b9bec60d38e854016c48658b57774578 +Author: Martin Albrecht <martinralbrecht@googlemail.com> +Date: Wed May 7 15:11:08 2014 +0100 + + bump version number for upcoming release + + pyme/version.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +commit 7bd6de700f33ca5d1f27bc16ebbd401f21d2e788 +Author: Martin Albrecht <martinralbrecht@googlemail.com> +Date: Sat May 3 19:36:25 2014 +0100 + + bump version number to indicate changes + + pyme/version.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +commit 4fb6bd9b3f47c1a343242ac83b326cacd12a136e +Author: Martin Albrecht <martinralbrecht@googlemail.com> +Date: Sat May 3 19:34:07 2014 +0100 + + pyme instead of pygpgme + + setup.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +commit 9548973138d78241a45ccb82333b25f2cf36ce7d +Author: Martin Albrecht <martinralbrecht@googlemail.com> +Date: Sat May 3 19:31:10 2014 +0100 + + dirty hack to make 'python setup.py install' work + + setup.py | 7 ++++--- + 1 file changed, 4 insertions(+), 3 deletions(-) + +commit a961d7eab9db478b7e603324bc5d243bd3c84bad +Author: Martin Albrecht <martinralbrecht@googlemail.com> +Date: Sat May 3 19:05:44 2014 +0100 + + moved everything down to the toplevel directory + + COPYING | 340 ++ + COPYING.LESSER | 510 +++ + ChangeLog | 802 +++++ + INSTALL | 15 + + Makefile | 104 + + debian/README.Debian | 6 + + debian/changelog | 93 + + debian/control | 34 + + debian/copyright | 25 + + debian/docs | 2 + + debian/examples | 2 + + debian/rules | 99 + + examples/PyGtkGpgKeys.glade | 1394 ++++++++ + examples/PyGtkGpgKeys.gladep | 8 + + examples/PyGtkGpgKeys.py | 663 ++++ + examples/delkey.py | 34 + + examples/encrypt-to-all.py | 65 + + examples/exportimport.py | 75 + + examples/genkey.py | 45 + + examples/inter-edit.py | 57 + + examples/pygpa.glade | 5546 +++++++++++++++++++++++++++++++ + examples/pygpa.py | 1457 ++++++++ + examples/sign.py | 31 + + examples/signverify.py | 78 + + examples/simple.py | 52 + + examples/t-edit.py | 59 + + examples/testCMSgetkey.py | 45 + + examples/verifydetails.py | 100 + + gpgme-h-clean.py | 42 + + gpgme.i | 267 ++ + helpers.c | 154 + + helpers.h | 36 + + pyme/COPYING | 340 -- + pyme/COPYING.LESSER | 510 --- + pyme/ChangeLog | 802 ----- + pyme/INSTALL | 15 - + pyme/Makefile | 104 - + pyme/__init__.py | 137 + + pyme/callbacks.py | 47 + + pyme/constants/__init__.py | 7 + + pyme/constants/data/__init__.py | 4 + + pyme/constants/data/encoding.py | 20 + + pyme/constants/event.py | 20 + + pyme/constants/import.py | 20 + + pyme/constants/keylist/__init__.py | 4 + + pyme/constants/keylist/mode.py | 20 + + pyme/constants/md.py | 20 + + pyme/constants/pk.py | 20 + + pyme/constants/protocol.py | 20 + + pyme/constants/sig/__init__.py | 4 + + pyme/constants/sig/mode.py | 20 + + pyme/constants/sigsum.py | 20 + + pyme/constants/status.py | 20 + + pyme/constants/validity.py | 20 + + pyme/core.py | 463 +++ + pyme/debian/README.Debian | 6 - + pyme/debian/changelog | 93 - + pyme/debian/control | 34 - + pyme/debian/copyright | 25 - + pyme/debian/docs | 2 - + pyme/debian/examples | 2 - + pyme/debian/rules | 99 - + pyme/errors.py | 46 + + pyme/examples/PyGtkGpgKeys.glade | 1394 -------- + pyme/examples/PyGtkGpgKeys.gladep | 8 - + pyme/examples/PyGtkGpgKeys.py | 663 ---- + pyme/examples/delkey.py | 34 - + pyme/examples/encrypt-to-all.py | 65 - + pyme/examples/exportimport.py | 75 - + pyme/examples/genkey.py | 45 - + pyme/examples/inter-edit.py | 57 - + pyme/examples/pygpa.glade | 5546 ------------------------------- + pyme/examples/pygpa.py | 1457 -------- + pyme/examples/sign.py | 31 - + pyme/examples/signverify.py | 78 - + pyme/examples/simple.py | 52 - + pyme/examples/t-edit.py | 59 - + pyme/examples/testCMSgetkey.py | 45 - + pyme/examples/verifydetails.py | 100 - + pyme/gpgme-h-clean.py | 42 - + pyme/gpgme.i | 267 -- + pyme/helpers.c | 154 - + pyme/helpers.h | 36 - + pyme/pyme/__init__.py | 137 - + pyme/pyme/callbacks.py | 47 - + pyme/pyme/constants/__init__.py | 7 - + pyme/pyme/constants/data/__init__.py | 4 - + pyme/pyme/constants/data/encoding.py | 20 - + pyme/pyme/constants/event.py | 20 - + pyme/pyme/constants/import.py | 20 - + pyme/pyme/constants/keylist/__init__.py | 4 - + pyme/pyme/constants/keylist/mode.py | 20 - + pyme/pyme/constants/md.py | 20 - + pyme/pyme/constants/pk.py | 20 - + pyme/pyme/constants/protocol.py | 20 - + pyme/pyme/constants/sig/__init__.py | 4 - + pyme/pyme/constants/sig/mode.py | 20 - + pyme/pyme/constants/sigsum.py | 20 - + pyme/pyme/constants/status.py | 20 - + pyme/pyme/constants/validity.py | 20 - + pyme/pyme/core.py | 463 --- + pyme/pyme/errors.py | 46 - + pyme/pyme/util.py | 72 - + pyme/pyme/version.py | 41 - + pyme/setup.py | 99 - + pyme/util.py | 72 + + pyme/version.py | 41 + + setup.py | 99 + + 108 files changed, 13384 insertions(+), 13384 deletions(-) + +commit 8148cdd424c434e833ce427612ea8c89abc6e41c +Author: Martin Albrecht <martinralbrecht@googlemail.com> +Date: Sat May 3 18:58:52 2014 +0100 + + removing pyme-web + + pyme-web/Makefile | 15 - + pyme-web/default.css | 37 -- + pyme-web/doc/gpgme/ASCII-Armor.html | 57 --- + pyme-web/doc/gpgme/Advanced-Key-Editing.html | 98 ---- + pyme-web/doc/gpgme/Algorithms.html | 47 -- + pyme-web/doc/gpgme/Building-the-Source.html | 82 ---- + .../doc/gpgme/Callback-Based-Data-Buffers.html | 148 ------ + pyme-web/doc/gpgme/Cancellation.html | 67 --- + pyme-web/doc/gpgme/Concept-Index.html | 186 ------- + pyme-web/doc/gpgme/Context-Attributes.html | 52 -- + pyme-web/doc/gpgme/Contexts.html | 61 --- + pyme-web/doc/gpgme/Creating-Contexts.html | 49 -- + pyme-web/doc/gpgme/Creating-Data-Buffers.html | 47 -- + pyme-web/doc/gpgme/Creating-a-Signature.html | 143 ------ + pyme-web/doc/gpgme/Crypto-Engine.html | 79 --- + pyme-web/doc/gpgme/Crypto-Operations.html | 67 --- + .../doc/gpgme/Cryptographic-Message-Syntax.html | 42 -- + .../doc/gpgme/Data-Buffer-I_002fO-Operations.html | 104 ---- + pyme-web/doc/gpgme/Data-Buffer-Meta_002dData.html | 100 ---- + pyme-web/doc/gpgme/Decrypt-and-Verify.html | 79 --- + pyme-web/doc/gpgme/Decrypt.html | 123 ----- + pyme-web/doc/gpgme/Deleting-Keys.html | 67 --- + pyme-web/doc/gpgme/Destroying-Contexts.html | 46 -- + pyme-web/doc/gpgme/Destroying-Data-Buffers.html | 70 --- + pyme-web/doc/gpgme/Encrypt.html | 45 -- + pyme-web/doc/gpgme/Encrypting-a-Plaintext.html | 147 ------ + pyme-web/doc/gpgme/Engine-Configuration.html | 65 --- + pyme-web/doc/gpgme/Engine-Information.html | 119 ----- + pyme-web/doc/gpgme/Engine-Version-Check.html | 48 -- + pyme-web/doc/gpgme/Error-Codes.html | 133 ----- + pyme-web/doc/gpgme/Error-Handling.html | 72 --- + pyme-web/doc/gpgme/Error-Sources.html | 89 ---- + pyme-web/doc/gpgme/Error-Strings.html | 80 --- + pyme-web/doc/gpgme/Error-Values.html | 159 ------ + pyme-web/doc/gpgme/Exchanging-Data.html | 58 --- + pyme-web/doc/gpgme/Exporting-Keys.html | 101 ---- + pyme-web/doc/gpgme/Features.html | 59 --- + pyme-web/doc/gpgme/File-Based-Data-Buffers.html | 74 --- + pyme-web/doc/gpgme/Function-and-Data-Index.html | 229 --------- + pyme-web/doc/gpgme/Generating-Keys.html | 144 ------ + pyme-web/doc/gpgme/Getting-Started.html | 55 --- + pyme-web/doc/gpgme/Hash-Algorithms.html | 59 --- + pyme-web/doc/gpgme/Header.html | 53 -- + .../doc/gpgme/I_002fO-Callback-Example-GDK.html | 85 ---- + .../gpgme/I_002fO-Callback-Example-GTK_002b.html | 86 ---- + .../doc/gpgme/I_002fO-Callback-Example-Qt.html | 99 ---- + pyme-web/doc/gpgme/I_002fO-Callback-Example.html | 259 ---------- + pyme-web/doc/gpgme/I_002fO-Callback-Interface.html | 142 ------ + pyme-web/doc/gpgme/Importing-Keys.html | 171 ------- + pyme-web/doc/gpgme/Included-Certificates.html | 70 --- + pyme-web/doc/gpgme/Information-About-Keys.html | 207 -------- + .../doc/gpgme/Information-About-Trust-Items.html | 75 --- + pyme-web/doc/gpgme/Introduction.html | 53 -- + pyme-web/doc/gpgme/Key-Listing-Mode.html | 99 ---- + pyme-web/doc/gpgme/Key-Management.html | 260 ---------- + pyme-web/doc/gpgme/Key-Signatures.html | 130 ----- + .../doc/gpgme/Largefile-Support-_0028LFS_0029.html | 110 ----- + pyme-web/doc/gpgme/Library-Copying.html | 542 --------------------- + pyme-web/doc/gpgme/Library-Version-Check.html | 97 ---- + pyme-web/doc/gpgme/Listing-Keys.html | 204 -------- + pyme-web/doc/gpgme/Listing-Trust-Items.html | 88 ---- + pyme-web/doc/gpgme/Locale.html | 69 --- + pyme-web/doc/gpgme/Manipulating-Data-Buffers.html | 45 -- + pyme-web/doc/gpgme/Manipulating-Keys.html | 63 --- + pyme-web/doc/gpgme/Manipulating-Trust-Items.html | 62 --- + pyme-web/doc/gpgme/Memory-Based-Data-Buffers.html | 107 ---- + pyme-web/doc/gpgme/Multi-Threading.html | 93 ---- + pyme-web/doc/gpgme/OpenPGP.html | 44 -- + pyme-web/doc/gpgme/Overview.html | 57 --- + pyme-web/doc/gpgme/Passphrase-Callback.html | 101 ---- + pyme-web/doc/gpgme/Preparation.html | 54 -- + pyme-web/doc/gpgme/Progress-Meter-Callback.html | 80 --- + pyme-web/doc/gpgme/Protocol-Selection.html | 60 --- + pyme-web/doc/gpgme/Protocols-and-Engines.html | 82 ---- + pyme-web/doc/gpgme/Public-Key-Algorithms.html | 74 --- + .../doc/gpgme/Registering-I_002fO-Callbacks.html | 81 --- + pyme-web/doc/gpgme/Run-Control.html | 53 -- + pyme-web/doc/gpgme/Selecting-Signers.html | 64 --- + pyme-web/doc/gpgme/Sign.html | 50 -- + pyme-web/doc/gpgme/Signal-Handling.html | 61 --- + pyme-web/doc/gpgme/Signature-Notation-Data.html | 85 ---- + pyme-web/doc/gpgme/Text-Mode.html | 63 --- + pyme-web/doc/gpgme/Trust-Item-Management.html | 68 --- + pyme-web/doc/gpgme/Using-Automake.html | 74 --- + pyme-web/doc/gpgme/Using-External-Event-Loops.html | 74 --- + pyme-web/doc/gpgme/Using-Libtool.html | 44 -- + pyme-web/doc/gpgme/Verify.html | 492 ------------------- + pyme-web/doc/gpgme/Waiting-For-Completion.html | 77 --- + pyme-web/doc/gpgme/index.html | 169 ------- + pyme-web/doc/pyme/index.html | 164 ------- + pyme-web/doc/pyme/pyme.callbacks.html | 42 -- + .../doc/pyme/pyme.constants.data.encoding.html | 48 -- + pyme-web/doc/pyme/pyme.constants.data.html | 29 -- + pyme-web/doc/pyme/pyme.constants.event.html | 48 -- + pyme-web/doc/pyme/pyme.constants.html | 39 -- + pyme-web/doc/pyme/pyme.constants.import.html | 49 -- + pyme-web/doc/pyme/pyme.constants.keylist.html | 29 -- + pyme-web/doc/pyme/pyme.constants.keylist.mode.html | 49 -- + pyme-web/doc/pyme/pyme.constants.md.html | 58 --- + pyme-web/doc/pyme/pyme.constants.pk.html | 50 -- + pyme-web/doc/pyme/pyme.constants.protocol.html | 48 -- + pyme-web/doc/pyme/pyme.constants.sig.html | 29 -- + pyme-web/doc/pyme/pyme.constants.sig.mode.html | 47 -- + pyme-web/doc/pyme/pyme.constants.sigsum.html | 55 --- + pyme-web/doc/pyme/pyme.constants.status.html | 126 ----- + pyme-web/doc/pyme/pyme.constants.validity.html | 50 -- + pyme-web/doc/pyme/pyme.core.html | 277 ----------- + pyme-web/doc/pyme/pyme.errors.html | 82 ---- + pyme-web/doc/pyme/pyme.html | 164 ------- + pyme-web/doc/pyme/pyme.util.html | 81 --- + pyme-web/doc/pyme/pyme.version.html | 37 -- + pyme-web/index.html | 72 --- + 112 files changed, 10551 deletions(-) + +commit 684d95feb7e10e538a56fb1b27f1456111bacb60 +Author: Martin Albrecht <martinralbrecht@googlemail.com> +Date: Mon Jan 6 17:44:20 2014 +0100 + + fixing op_export_keys() + + the conversion of gpgme_key_t [] was restricted to gpgme_key_t [] with the + name recv, i.e. only the use-cases of encryption were covered. + + see: http://sourceforge.net/mailarchive/forum.php?forum_name=pyme-help&max_rows=25&style=nested&viewmonth=201309 + + pyme/gpgme.i | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +commit 658d23b95110d21eeb50abf4e74701a667521a88 +Author: Martin Albrecht <martinralbrecht@googlemail.com> +Date: Mon Jan 6 17:41:33 2014 +0100 + + deleting CVSROOT + + CVSROOT/checkoutlist | 13 ------------- + CVSROOT/commitinfo | 15 --------------- + CVSROOT/config | 21 --------------------- + CVSROOT/cvswrappers | 19 ------------------- + CVSROOT/editinfo | 21 --------------------- + CVSROOT/loginfo | 26 -------------------------- + CVSROOT/modules | 26 -------------------------- + CVSROOT/notify | 12 ------------ + CVSROOT/rcsinfo | 13 ------------- + CVSROOT/taginfo | 20 -------------------- + CVSROOT/verifymsg | 21 --------------------- + 11 files changed, 207 deletions(-) + +commit 576b555499c094c4786d42de9e59aa9826009b89 +Author: convert-repo <devnull@localhost> +Date: Mon Jan 6 15:22:44 2014 +0000 + + update tags + +commit 2dcf0c5b702eb5a18c66ff1e42a72eaa7427af1d +Author: belyi <devnull@localhost> +Date: Wed Nov 26 02:38:33 2008 +0000 + + Move Windows specific fix from helpers.c to helpers.h so that it works + for edit callback as well as for the passphrase one. + + pyme/helpers.c | 5 ----- + pyme/helpers.h | 5 +++++ + 2 files changed, 5 insertions(+), 5 deletions(-) + +commit 42a035f2ef62470fea7a7f8ee33a1297fa90a603 +Author: belyi <devnull@localhost> +Date: Mon Nov 24 21:44:30 2008 +0000 + + Update the way build directives are constructed on MinGW to have a bit + more robust. Update PyMe build version to 0.8.1 in version.py + + pyme/pyme/version.py | 2 +- + pyme/setup.py | 10 ++++++++-- + 2 files changed, 9 insertions(+), 3 deletions(-) + +commit 3aaa20fbcba17066c9ffd580f5209946022793a2 +Author: belyi <devnull@localhost> +Date: Mon Nov 24 06:57:11 2008 +0000 + + Update changelog + + pyme/debian/changelog | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +commit 689ff46b2550547e3883f809a6dc40c22c3e137e +Author: belyi <devnull@localhost> +Date: Mon Nov 24 06:50:41 2008 +0000 + + Fix hang problem on Windows when password is written to a filehandle. + Fix the way path is constructed on MinGW platform. + + pyme/helpers.c | 5 +++++ + pyme/setup.py | 4 ++-- + 2 files changed, 7 insertions(+), 2 deletions(-) + +commit 852a60d541d66cb56f40378182b976fd87a02c46 +Author: belyi <devnull@localhost> +Date: Sun Nov 23 04:31:31 2008 +0000 + + Add Bernard's example testCMSgetkey.py and his updates for + verifydetails.py + + pyme/examples/testCMSgetkey.py | 45 ++++++++++++++++++++++++++++++++++++++++++ + pyme/examples/verifydetails.py | 43 +++++++++++++++++++++++++++++----------- + 2 files changed, 77 insertions(+), 11 deletions(-) + +commit f080527d9184f3360f0a8ef6136b9a188d8e7d2a +Author: belyi <devnull@localhost> +Date: Thu May 29 18:29:37 2008 +0000 + + Remove debian packaging for python2.3 since it is removed from both + testing and unstable dists. + Update docs build target to have correct PYTHONPATH set. + + pyme/Makefile | 2 +- + pyme/debian/changelog | 4 +++- + pyme/debian/control | 4 ++-- + pyme/debian/rules | 2 -- + 4 files changed, 6 insertions(+), 6 deletions(-) + +commit c25d133fcbadf3c7f6e655586b4a05d6e3cf6f0b +Author: belyi <devnull@localhost> +Date: Thu Apr 3 13:37:12 2008 +0000 + + Forgot to adjust mainText margin. Doing it now. + + pyme-web/default.css | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +commit 897286a54a32336d060cd03305cdecb7905f34f1 +Author: belyi <devnull@localhost> +Date: Thu Apr 3 13:00:11 2008 +0000 + + Fix an error in default.css and make index.html "Standards Compliant". + + pyme-web/default.css | 2 +- + pyme-web/index.html | 7 ++++--- + 2 files changed, 5 insertions(+), 4 deletions(-) + +commit 4e049212bd214449cc0ba1ce06e00782783f328a +Author: belyi <devnull@localhost> +Date: Thu Apr 3 12:38:42 2008 +0000 + + Adjust spacing between links. + + pyme-web/default.css | 9 ++++++--- + 1 file changed, 6 insertions(+), 3 deletions(-) + +commit cb2bddfbd77483b1deb14f2eab0715a03dd33fcd +Author: belyi <devnull@localhost> +Date: Wed Apr 2 22:50:21 2008 +0000 + + Make style a big more IE friendly. + + pyme-web/default.css | 15 +++++++++++---- + 1 file changed, 11 insertions(+), 4 deletions(-) + +commit ad66f0a1bb01b46baac328e9fee439b35a60c232 +Author: belyi <devnull@localhost> +Date: Wed Apr 2 11:58:32 2008 +0000 + + Make GPGME documentation a bit more web friendly on the index.html page. + + pyme-web/doc/gpgme/Algorithms.html | 2 +- + pyme-web/doc/gpgme/Concept-Index.html | 2 +- + pyme-web/doc/gpgme/Contexts.html | 2 +- + pyme-web/doc/gpgme/Error-Handling.html | 2 +- + pyme-web/doc/gpgme/Exchanging-Data.html | 2 +- + pyme-web/doc/gpgme/Function-and-Data-Index.html | 2 +- + pyme-web/doc/gpgme/Introduction.html | 4 +- + pyme-web/doc/gpgme/Library-Copying.html | 2 +- + pyme-web/doc/gpgme/Preparation.html | 2 +- + pyme-web/doc/gpgme/Protocols-and-Engines.html | 2 +- + pyme-web/doc/gpgme/index.html | 229 +----------------------- + 11 files changed, 12 insertions(+), 239 deletions(-) + +commit 4f57c0ccb049d4442e7732e2d1d05dabffd2a21d +Author: belyi <devnull@localhost> +Date: Wed Apr 2 06:12:57 2008 +0000 + + Add missing core.set_locale() to set default locale for contexts. + + pyme/debian/changelog | 2 +- + pyme/pyme/core.py | 4 ++++ + 2 files changed, 5 insertions(+), 1 deletion(-) + +commit acf7ead3dea8590cf9fe86b67bb125837ad6ed4f +Author: belyi <devnull@localhost> +Date: Wed Apr 2 05:50:24 2008 +0000 + + Avoid leaks caused by keys. + Add set/get methods for engine info. + + pyme/debian/changelog | 10 ++++++++++ + pyme/pyme/core.py | 24 ++++++++++++++++++++++++ + 2 files changed, 34 insertions(+) + +commit df4a2fb518adbb6420d95ce74af212c87abff7e7 +Author: belyi <devnull@localhost> +Date: Wed Apr 2 04:04:41 2008 +0000 + + Update index.html to reflect new versions on the web. + + pyme-web/Makefile | 3 ++- + pyme-web/doc/gpgme/index.html | 4 +--- + pyme-web/index.html | 4 ++-- + 3 files changed, 5 insertions(+), 6 deletions(-) + +commit bd3ffc9bdf98d6aafde6b689c6c8215fa468612d +Author: belyi <devnull@localhost> +Date: Wed Apr 2 04:01:04 2008 +0000 + + Update PyMe documentation to match 0.8.0 version of the package. + + pyme-web/doc/pyme/index.html | 14 ++++----- + pyme-web/doc/pyme/pyme.constants.keylist.mode.html | 1 + + pyme-web/doc/pyme/pyme.constants.protocol.html | 4 ++- + pyme-web/doc/pyme/pyme.constants.status.html | 9 ++++++ + pyme-web/doc/pyme/pyme.core.html | 36 ++++++++++++++++++---- + pyme-web/doc/pyme/pyme.errors.html | 8 ++--- + pyme-web/doc/pyme/pyme.html | 14 ++++----- + pyme-web/doc/pyme/pyme.util.html | 17 ++++++++-- + pyme-web/doc/pyme/pyme.version.html | 14 ++++----- + 9 files changed, 82 insertions(+), 35 deletions(-) + +commit 6973a69a317608a0d0661590d701f4e3f3a21b32 +Author: belyi <devnull@localhost> +Date: Wed Apr 2 02:35:24 2008 +0000 + + Have a fix for Contents being put onto 'Function and Data Index' page. + + pyme-web/doc/gpgme/Concept-Index.html | 2 +- + pyme-web/doc/gpgme/Function-and-Data-Index.html | 153 +---------------------- + pyme-web/doc/gpgme/index.html | 154 +++++++++++++++++++++++- + 3 files changed, 155 insertions(+), 154 deletions(-) + +commit 086315964cbc2abad1187f306dcb9c72ac3257f3 +Author: belyi <devnull@localhost> +Date: Wed Apr 2 01:00:29 2008 +0000 + + Update GPGME documentation. It's for v1.1.6 now. + + pyme-web/doc/gpgme/ASCII-Armor.html | 57 ++ + pyme-web/doc/gpgme/Advanced-Key-Editing.html | 98 +++ + pyme-web/doc/gpgme/Algorithms.html | 47 ++ + pyme-web/doc/gpgme/Building-the-Source.html | 82 +++ + .../doc/gpgme/Callback-Based-Data-Buffers.html | 148 +++++ + pyme-web/doc/gpgme/Cancellation.html | 67 ++ + pyme-web/doc/gpgme/Concept-Index.html | 186 ++++++ + pyme-web/doc/gpgme/Context-Attributes.html | 52 ++ + pyme-web/doc/gpgme/Contexts.html | 61 ++ + pyme-web/doc/gpgme/Creating-Contexts.html | 49 ++ + pyme-web/doc/gpgme/Creating-Data-Buffers.html | 47 ++ + pyme-web/doc/gpgme/Creating-a-Signature.html | 143 +++++ + pyme-web/doc/gpgme/Crypto-Engine.html | 79 +++ + pyme-web/doc/gpgme/Crypto-Operations.html | 67 ++ + .../doc/gpgme/Cryptographic-Message-Syntax.html | 42 ++ + .../doc/gpgme/Data-Buffer-I_002fO-Operations.html | 104 ++++ + pyme-web/doc/gpgme/Data-Buffer-Meta_002dData.html | 100 +++ + pyme-web/doc/gpgme/Decrypt-and-Verify.html | 79 +++ + pyme-web/doc/gpgme/Decrypt.html | 123 ++++ + pyme-web/doc/gpgme/Deleting-Keys.html | 67 ++ + pyme-web/doc/gpgme/Destroying-Contexts.html | 46 ++ + pyme-web/doc/gpgme/Destroying-Data-Buffers.html | 70 +++ + pyme-web/doc/gpgme/Encrypt.html | 45 ++ + pyme-web/doc/gpgme/Encrypting-a-Plaintext.html | 147 +++++ + pyme-web/doc/gpgme/Engine-Configuration.html | 65 ++ + pyme-web/doc/gpgme/Engine-Information.html | 119 ++++ + pyme-web/doc/gpgme/Engine-Version-Check.html | 48 ++ + pyme-web/doc/gpgme/Error-Codes.html | 133 ++++ + pyme-web/doc/gpgme/Error-Handling.html | 72 +++ + pyme-web/doc/gpgme/Error-Sources.html | 89 +++ + pyme-web/doc/gpgme/Error-Strings.html | 80 +++ + pyme-web/doc/gpgme/Error-Values.html | 159 +++++ + pyme-web/doc/gpgme/Exchanging-Data.html | 58 ++ + pyme-web/doc/gpgme/Exporting-Keys.html | 101 +++ + pyme-web/doc/gpgme/Features.html | 59 ++ + pyme-web/doc/gpgme/File-Based-Data-Buffers.html | 74 +++ + pyme-web/doc/gpgme/Function-and-Data-Index.html | 380 ++++++++++++ + pyme-web/doc/gpgme/Generating-Keys.html | 144 +++++ + pyme-web/doc/gpgme/Getting-Started.html | 55 ++ + pyme-web/doc/gpgme/Hash-Algorithms.html | 59 ++ + pyme-web/doc/gpgme/Header.html | 53 ++ + .../doc/gpgme/I_002fO-Callback-Example-GDK.html | 85 +++ + .../gpgme/I_002fO-Callback-Example-GTK_002b.html | 86 +++ + .../doc/gpgme/I_002fO-Callback-Example-Qt.html | 99 +++ + pyme-web/doc/gpgme/I_002fO-Callback-Example.html | 259 ++++++++ + pyme-web/doc/gpgme/I_002fO-Callback-Interface.html | 142 +++++ + pyme-web/doc/gpgme/Importing-Keys.html | 171 +++++ + pyme-web/doc/gpgme/Included-Certificates.html | 70 +++ + pyme-web/doc/gpgme/Information-About-Keys.html | 207 +++++++ + .../doc/gpgme/Information-About-Trust-Items.html | 75 +++ + pyme-web/doc/gpgme/Introduction.html | 53 ++ + pyme-web/doc/gpgme/Key-Listing-Mode.html | 99 +++ + pyme-web/doc/gpgme/Key-Management.html | 260 ++++++++ + pyme-web/doc/gpgme/Key-Signatures.html | 130 ++++ + .../doc/gpgme/Largefile-Support-_0028LFS_0029.html | 110 ++++ + pyme-web/doc/gpgme/Library-Copying.html | 542 ++++++++++++++++ + pyme-web/doc/gpgme/Library-Version-Check.html | 97 +++ + pyme-web/doc/gpgme/Listing-Keys.html | 204 ++++++ + pyme-web/doc/gpgme/Listing-Trust-Items.html | 88 +++ + pyme-web/doc/gpgme/Locale.html | 69 +++ + pyme-web/doc/gpgme/Manipulating-Data-Buffers.html | 45 ++ + pyme-web/doc/gpgme/Manipulating-Keys.html | 63 ++ + pyme-web/doc/gpgme/Manipulating-Trust-Items.html | 62 ++ + pyme-web/doc/gpgme/Memory-Based-Data-Buffers.html | 107 ++++ + pyme-web/doc/gpgme/Multi-Threading.html | 93 +++ + pyme-web/doc/gpgme/OpenPGP.html | 44 ++ + pyme-web/doc/gpgme/Overview.html | 57 ++ + pyme-web/doc/gpgme/Passphrase-Callback.html | 101 +++ + pyme-web/doc/gpgme/Preparation.html | 54 ++ + pyme-web/doc/gpgme/Progress-Meter-Callback.html | 80 +++ + pyme-web/doc/gpgme/Protocol-Selection.html | 60 ++ + pyme-web/doc/gpgme/Protocols-and-Engines.html | 82 +++ + pyme-web/doc/gpgme/Public-Key-Algorithms.html | 74 +++ + .../doc/gpgme/Registering-I_002fO-Callbacks.html | 81 +++ + pyme-web/doc/gpgme/Run-Control.html | 53 ++ + pyme-web/doc/gpgme/Selecting-Signers.html | 64 ++ + pyme-web/doc/gpgme/Sign.html | 50 ++ + pyme-web/doc/gpgme/Signal-Handling.html | 61 ++ + pyme-web/doc/gpgme/Signature-Notation-Data.html | 85 +++ + pyme-web/doc/gpgme/Text-Mode.html | 63 ++ + pyme-web/doc/gpgme/Trust-Item-Management.html | 68 ++ + pyme-web/doc/gpgme/Using-Automake.html | 74 +++ + pyme-web/doc/gpgme/Using-External-Event-Loops.html | 74 +++ + pyme-web/doc/gpgme/Using-Libtool.html | 44 ++ + pyme-web/doc/gpgme/Verify.html | 492 +++++++++++++++ + pyme-web/doc/gpgme/Waiting-For-Completion.html | 77 +++ + pyme-web/doc/gpgme/gpgme.html | 251 -------- + pyme-web/doc/gpgme/gpgme_1.html | 76 --- + pyme-web/doc/gpgme/gpgme_10.html | 61 -- + pyme-web/doc/gpgme/gpgme_11.html | 130 ---- + pyme-web/doc/gpgme/gpgme_12.html | 82 --- + pyme-web/doc/gpgme/gpgme_13.html | 130 ---- + pyme-web/doc/gpgme/gpgme_14.html | 108 ---- + pyme-web/doc/gpgme/gpgme_15.html | 69 --- + pyme-web/doc/gpgme/gpgme_16.html | 169 ----- + pyme-web/doc/gpgme/gpgme_17.html | 63 -- + pyme-web/doc/gpgme/gpgme_18.html | 63 -- + pyme-web/doc/gpgme/gpgme_19.html | 66 -- + pyme-web/doc/gpgme/gpgme_2.html | 79 --- + pyme-web/doc/gpgme/gpgme_20.html | 120 ---- + pyme-web/doc/gpgme/gpgme_21.html | 102 --- + pyme-web/doc/gpgme/gpgme_22.html | 108 ---- + pyme-web/doc/gpgme/gpgme_23.html | 237 ------- + pyme-web/doc/gpgme/gpgme_24.html | 154 ----- + pyme-web/doc/gpgme/gpgme_25.html | 248 -------- + pyme-web/doc/gpgme/gpgme_26.html | 107 ---- + pyme-web/doc/gpgme/gpgme_27.html | 80 --- + pyme-web/doc/gpgme/gpgme_28.html | 67 -- + pyme-web/doc/gpgme/gpgme_29.html | 164 ----- + pyme-web/doc/gpgme/gpgme_3.html | 86 --- + pyme-web/doc/gpgme/gpgme_30.html | 106 ---- + pyme-web/doc/gpgme/gpgme_31.html | 232 ------- + pyme-web/doc/gpgme/gpgme_32.html | 85 --- + pyme-web/doc/gpgme/gpgme_33.html | 223 ------- + pyme-web/doc/gpgme/gpgme_34.html | 83 --- + pyme-web/doc/gpgme/gpgme_35.html | 70 --- + pyme-web/doc/gpgme/gpgme_36.html | 63 -- + pyme-web/doc/gpgme/gpgme_37.html | 66 -- + pyme-web/doc/gpgme/gpgme_38.html | 86 --- + pyme-web/doc/gpgme/gpgme_39.html | 79 --- + pyme-web/doc/gpgme/gpgme_4.html | 83 --- + pyme-web/doc/gpgme/gpgme_40.html | 89 --- + pyme-web/doc/gpgme/gpgme_41.html | 99 --- + pyme-web/doc/gpgme/gpgme_42.html | 144 ----- + pyme-web/doc/gpgme/gpgme_43.html | 152 ----- + pyme-web/doc/gpgme/gpgme_44.html | 112 ---- + pyme-web/doc/gpgme/gpgme_45.html | 101 --- + pyme-web/doc/gpgme/gpgme_46.html | 459 -------------- + pyme-web/doc/gpgme/gpgme_47.html | 292 --------- + pyme-web/doc/gpgme/gpgme_48.html | 363 ----------- + pyme-web/doc/gpgme/gpgme_49.html | 209 ------- + pyme-web/doc/gpgme/gpgme_5.html | 74 --- + pyme-web/doc/gpgme/gpgme_50.html | 88 --- + pyme-web/doc/gpgme/gpgme_51.html | 208 ------- + pyme-web/doc/gpgme/gpgme_52.html | 154 ----- + pyme-web/doc/gpgme/gpgme_53.html | 291 --------- + pyme-web/doc/gpgme/gpgme_54.html | 91 --- + pyme-web/doc/gpgme/gpgme_55.html | 107 ---- + pyme-web/doc/gpgme/gpgme_56.html | 140 ----- + pyme-web/doc/gpgme/gpgme_57.html | 106 ---- + pyme-web/doc/gpgme/gpgme_58.html | 89 --- + pyme-web/doc/gpgme/gpgme_59.html | 97 --- + pyme-web/doc/gpgme/gpgme_6.html | 77 --- + pyme-web/doc/gpgme/gpgme_60.html | 142 ----- + pyme-web/doc/gpgme/gpgme_61.html | 626 ------------------- + pyme-web/doc/gpgme/gpgme_62.html | 107 ---- + pyme-web/doc/gpgme/gpgme_63.html | 67 -- + pyme-web/doc/gpgme/gpgme_64.html | 95 --- + pyme-web/doc/gpgme/gpgme_65.html | 233 ------- + pyme-web/doc/gpgme/gpgme_66.html | 65 -- + pyme-web/doc/gpgme/gpgme_67.html | 220 ------- + pyme-web/doc/gpgme/gpgme_68.html | 75 --- + pyme-web/doc/gpgme/gpgme_69.html | 119 ---- + pyme-web/doc/gpgme/gpgme_7.html | 123 ---- + pyme-web/doc/gpgme/gpgme_70.html | 107 ---- + pyme-web/doc/gpgme/gpgme_71.html | 218 ------- + pyme-web/doc/gpgme/gpgme_72.html | 134 ---- + pyme-web/doc/gpgme/gpgme_73.html | 299 --------- + pyme-web/doc/gpgme/gpgme_74.html | 103 ---- + pyme-web/doc/gpgme/gpgme_75.html | 104 ---- + pyme-web/doc/gpgme/gpgme_76.html | 118 ---- + pyme-web/doc/gpgme/gpgme_77.html | 95 --- + pyme-web/doc/gpgme/gpgme_78.html | 71 --- + pyme-web/doc/gpgme/gpgme_79.html | 686 --------------------- + pyme-web/doc/gpgme/gpgme_8.html | 155 ----- + pyme-web/doc/gpgme/gpgme_80.html | 120 ---- + pyme-web/doc/gpgme/gpgme_81.html | 278 --------- + pyme-web/doc/gpgme/gpgme_82.html | 272 -------- + pyme-web/doc/gpgme/gpgme_83.html | 180 ------ + pyme-web/doc/gpgme/gpgme_84.html | 99 --- + pyme-web/doc/gpgme/gpgme_9.html | 104 ---- + pyme-web/doc/gpgme/gpgme_abt.html | 206 ------- + pyme-web/doc/gpgme/gpgme_fot.html | 53 -- + pyme-web/doc/gpgme/gpgme_ovr.html | 68 -- + pyme-web/doc/gpgme/gpgme_toc.html | 247 -------- + pyme-web/doc/gpgme/index.html | 497 ++++++++------- + 176 files changed, 9054 insertions(+), 13378 deletions(-) + +commit 163c1053dc761682f5a4231da163bdd0ff7162d7 +Author: belyi <devnull@localhost> +Date: Tue Apr 1 21:14:29 2008 +0000 + + Update Home page to be a bit more visitor friendly. + + pyme-web/Makefile | 2 +- + pyme-web/default.css | 27 ++++++++++++++++++++ + pyme-web/index.html | 70 +++++++++++++++++++++++++++++++++++----------------- + 3 files changed, 75 insertions(+), 24 deletions(-) + +commit 05db2d17d8fda0ab8c948bbdc0643dfc1466830d +Author: belyi <devnull@localhost> +Date: Sun Mar 30 21:27:38 2008 +0000 + + Add a rule to build binary distribution for Windows. + + pyme/Makefile | 16 ++++++++++++++-- + 1 file changed, 14 insertions(+), 2 deletions(-) + +commit 57acb1089f5f8c24323ee62fc0a7f492a496b9c0 +Author: belyi <devnull@localhost> +Date: Sat Mar 29 22:50:11 2008 +0000 + + Switch to using central location for python files (pycentral) + Update docs rule to fix location of the python source files. + + pyme/Makefile | 5 +++- + pyme/debian/changelog | 4 ++- + pyme/debian/control | 74 +++++------------------------------------------ + pyme/debian/dirs | 2 -- + pyme/debian/docs | 1 + + pyme/debian/postinst.ex | 48 ------------------------------ + pyme/debian/postrm.ex | 38 ------------------------ + pyme/debian/preinst.ex | 44 ---------------------------- + pyme/debian/prerm.ex | 39 ------------------------- + pyme/debian/rules | 50 ++++++-------------------------- + pyme/debian/setup.cfg-2.2 | 8 ----- + pyme/debian/setup.cfg-2.3 | 8 ----- + pyme/debian/setup.cfg-2.4 | 8 ----- + pyme/gpgme-h-clean.py | 2 +- + pyme/pyme/core.py | 2 +- + pyme/pyme/util.py | 2 +- + 16 files changed, 28 insertions(+), 307 deletions(-) + +commit 2b56fd10517cfbcffaa4ba98d8ea42f40f0d38a9 +Author: belyi <devnull@localhost> +Date: Sun Mar 23 02:01:12 2008 +0000 + + Turn SWIG's autodoc feature on. Ignore 'next' in the types which are lists now. + Use new style for class declarations. Specify None as a default value for + core.check_version() method. Update version.py for 0.8.0 version. + + pyme/examples/pygpa.py | 2 +- + pyme/gpgme.i | 5 +++++ + pyme/pyme/core.py | 2 +- + pyme/pyme/util.py | 5 +++-- + pyme/pyme/version.py | 6 +++--- + 5 files changed, 13 insertions(+), 7 deletions(-) + +commit df5e25d7ee4dc0aa0d429f9d009322dd8ac33bb8 +Author: belyi <devnull@localhost> +Date: Thu Mar 20 19:07:00 2008 +0000 + + Improve matching for DEPRECATED typedefs + + pyme/gpgme-h-clean.py | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +commit 78d8fc732848ac267ec65e9069265cd500587cdf +Author: belyi <devnull@localhost> +Date: Wed Mar 19 19:28:40 2008 +0000 + + Update API to use list when types containing 'next' field are return. + Update examples accordingly + Add verifydetails.py example + Start adding bullets for 0.8.0 version. + + pyme/Makefile | 2 +- + pyme/debian/changelog | 14 +++++++- + pyme/examples/PyGtkGpgKeys.py | 53 +++++++++++++-------------- + pyme/examples/delkey.py | 7 ++-- + pyme/examples/encrypt-to-all.py | 7 ++-- + pyme/examples/exportimport.py | 7 ++-- + pyme/examples/pygpa.py | 70 ++++++++++++++++-------------------- + pyme/examples/signverify.py | 11 +++--- + pyme/examples/verifydetails.py | 79 +++++++++++++++++++++++++++++++++++++++++ + pyme/gpgme.i | 19 +++++++++- + 10 files changed, 180 insertions(+), 89 deletions(-) + +commit 342d85b07475e7360bcd62804bf5facda039494f +Author: belyi <devnull@localhost> +Date: Mon Mar 10 01:14:16 2008 +0000 + + Change references to source files so that they point to the WebCVS browse + location. + + pyme-web/doc/pyme/index.html | 2 +- + pyme-web/doc/pyme/pyme.callbacks.html | 2 +- + pyme-web/doc/pyme/pyme.constants.data.encoding.html | 2 +- + pyme-web/doc/pyme/pyme.constants.data.html | 2 +- + pyme-web/doc/pyme/pyme.constants.event.html | 2 +- + pyme-web/doc/pyme/pyme.constants.html | 2 +- + pyme-web/doc/pyme/pyme.constants.import.html | 2 +- + pyme-web/doc/pyme/pyme.constants.keylist.html | 2 +- + pyme-web/doc/pyme/pyme.constants.keylist.mode.html | 2 +- + pyme-web/doc/pyme/pyme.constants.md.html | 2 +- + pyme-web/doc/pyme/pyme.constants.pk.html | 2 +- + pyme-web/doc/pyme/pyme.constants.protocol.html | 2 +- + pyme-web/doc/pyme/pyme.constants.sig.html | 2 +- + pyme-web/doc/pyme/pyme.constants.sig.mode.html | 2 +- + pyme-web/doc/pyme/pyme.constants.sigsum.html | 2 +- + pyme-web/doc/pyme/pyme.constants.status.html | 2 +- + pyme-web/doc/pyme/pyme.constants.validity.html | 2 +- + pyme-web/doc/pyme/pyme.core.html | 2 +- + pyme-web/doc/pyme/pyme.errors.html | 2 +- + pyme-web/doc/pyme/pyme.html | 2 +- + pyme-web/doc/pyme/pyme.util.html | 2 +- + pyme-web/doc/pyme/pyme.version.html | 2 +- + 22 files changed, 22 insertions(+), 22 deletions(-) + +commit 4139dd1d066c1a6c892d84fe45dc3e6c4aa1b803 +Author: belyi <devnull@localhost> +Date: Sat Mar 8 18:21:08 2008 +0000 + + Add core.check_version(None) to all examples since this function is used by + Gpgme to do internal initialization. Update debian/rules to use dh_pysupport + instead of deprecated dh_python. + + pyme/debian/rules | 8 +++----- + pyme/examples/PyGtkGpgKeys.py | 7 ++++++- + pyme/examples/delkey.py | 2 ++ + pyme/examples/encrypt-to-all.py | 3 +++ + pyme/examples/exportimport.py | 2 ++ + pyme/examples/genkey.py | 1 + + pyme/examples/inter-edit.py | 3 +++ + pyme/examples/pygpa.py | 5 +++++ + pyme/examples/sign.py | 2 ++ + pyme/examples/signverify.py | 2 ++ + pyme/examples/simple.py | 2 ++ + pyme/examples/t-edit.py | 3 +++ + 12 files changed, 34 insertions(+), 6 deletions(-) + +commit ae76c6176457dd38e0634cbc17d794294a3a81d2 +Author: belyi <devnull@localhost> +Date: Wed Apr 12 22:20:38 2006 +0000 + + Change name of internal package name from 'gpgme' to 'pygpgme' to avoid + conflict with gpgme.dll on Windows. + Fix build with SWIG 1.3.28. + Change version to 0.7.1 in a preparation for new release. + + pyme/Makefile | 3 +- + pyme/debian/changelog | 12 ++++ + pyme/gpgme.i | 19 +++--- + pyme/pyme/callbacks.py | 1 - + pyme/pyme/core.py | 153 +++++++++++++++++++++++++------------------------ + pyme/pyme/errors.py | 12 ++-- + pyme/pyme/util.py | 10 ++-- + pyme/pyme/version.py | 2 +- + pyme/setup.py | 4 +- + 9 files changed, 116 insertions(+), 100 deletions(-) + +commit d644383a76e9f83bc2d426628319e3c4a989dc2d +Author: belyi <devnull@localhost> +Date: Sat Dec 17 01:34:53 2005 +0000 + + Put all constants into pyme.constants package to avoid stepping on python + reserved words. + Add build rules for Mingw32 and Cygwin on Windows. Rules for Mingw under + Debian are still to come. + Fixed a small bug in pygpa.py example. + + pyme/Makefile | 11 ++++++++--- + pyme/examples/pygpa.py | 3 ++- + pyme/pyme/__init__.py | 2 +- + pyme/pyme/constants/__init__.py | 3 +++ + pyme/setup.py | 42 ++++++++++++++++++++++++++++++++++++----- + 5 files changed, 51 insertions(+), 10 deletions(-) + +commit 89eb370fcaa8adc9d219eadbaa579dde7bf06329 +Author: belyi <devnull@localhost> +Date: Mon Aug 1 03:08:32 2005 +0000 + + Imported changes provided by Joost van Baal: + Use dh_python in debian/rules and change the Section pyme belongs to from + 'libs' to 'python'. + + pyme/debian/control | 6 +++--- + pyme/debian/rules | 2 ++ + 2 files changed, 5 insertions(+), 3 deletions(-) + +commit ad76d10c2a77b45b7459c62131279e946b860891 +Author: belyi <devnull@localhost> +Date: Fri Jun 10 03:01:22 2005 +0000 + + Update 'docs' rule in Makefile to build packages first to ensure that + documentation is build for the current version of pyme and not for the + installed one. + + Added 'callbacks' into the list of visible pyme modules (__all__ var.) + + Slightly updated INSTALL file. + + pyme/INSTALL | 11 ++++++++--- + pyme/Makefile | 4 ++-- + pyme/pyme/__init__.py | 2 +- + 3 files changed, 11 insertions(+), 6 deletions(-) + +commit 2fe1a81e00721698bfa6850b3db2eb85e43d1724 +Author: belyi <devnull@localhost> +Date: Wed Jun 8 16:16:18 2005 +0000 + + Update pyme documentation to remove dead links to pyme.gpgme.html and + pyme._gpgme.html + Added reference to the installed GPGME and PyMe documentation to the head + web page. + Updated Makefile to install all *.html files and to clean *~ files in all + subdirectories + + pyme-web/Makefile | 10 ++++++---- + pyme-web/doc/pyme/index.html | 8 +++----- + pyme-web/doc/pyme/pyme.callbacks.html | 8 -------- + pyme-web/doc/pyme/pyme.core.html | 1 - + pyme-web/doc/pyme/pyme.errors.html | 8 -------- + pyme-web/doc/pyme/pyme.html | 8 +++----- + pyme-web/doc/pyme/pyme.util.html | 8 -------- + pyme-web/index.html | 9 +++++++-- + 8 files changed, 19 insertions(+), 41 deletions(-) + +commit 6aa34cce4ea0099e50b4936dfee59778157b8ca8 +Author: belyi <devnull@localhost> +Date: Wed Jun 8 15:18:20 2005 +0000 + + Added pyme and gpgme documentation. + + pyme-web/doc/gpgme/gpgme.html | 251 ++++++++ + pyme-web/doc/gpgme/gpgme_1.html | 76 +++ + pyme-web/doc/gpgme/gpgme_10.html | 61 ++ + pyme-web/doc/gpgme/gpgme_11.html | 130 ++++ + pyme-web/doc/gpgme/gpgme_12.html | 82 +++ + pyme-web/doc/gpgme/gpgme_13.html | 130 ++++ + pyme-web/doc/gpgme/gpgme_14.html | 108 ++++ + pyme-web/doc/gpgme/gpgme_15.html | 69 +++ + pyme-web/doc/gpgme/gpgme_16.html | 169 +++++ + pyme-web/doc/gpgme/gpgme_17.html | 63 ++ + pyme-web/doc/gpgme/gpgme_18.html | 63 ++ + pyme-web/doc/gpgme/gpgme_19.html | 66 ++ + pyme-web/doc/gpgme/gpgme_2.html | 79 +++ + pyme-web/doc/gpgme/gpgme_20.html | 120 ++++ + pyme-web/doc/gpgme/gpgme_21.html | 102 +++ + pyme-web/doc/gpgme/gpgme_22.html | 108 ++++ + pyme-web/doc/gpgme/gpgme_23.html | 237 +++++++ + pyme-web/doc/gpgme/gpgme_24.html | 154 +++++ + pyme-web/doc/gpgme/gpgme_25.html | 248 ++++++++ + pyme-web/doc/gpgme/gpgme_26.html | 107 ++++ + pyme-web/doc/gpgme/gpgme_27.html | 80 +++ + pyme-web/doc/gpgme/gpgme_28.html | 67 ++ + pyme-web/doc/gpgme/gpgme_29.html | 164 +++++ + pyme-web/doc/gpgme/gpgme_3.html | 86 +++ + pyme-web/doc/gpgme/gpgme_30.html | 106 ++++ + pyme-web/doc/gpgme/gpgme_31.html | 232 +++++++ + pyme-web/doc/gpgme/gpgme_32.html | 85 +++ + pyme-web/doc/gpgme/gpgme_33.html | 223 +++++++ + pyme-web/doc/gpgme/gpgme_34.html | 83 +++ + pyme-web/doc/gpgme/gpgme_35.html | 70 +++ + pyme-web/doc/gpgme/gpgme_36.html | 63 ++ + pyme-web/doc/gpgme/gpgme_37.html | 66 ++ + pyme-web/doc/gpgme/gpgme_38.html | 86 +++ + pyme-web/doc/gpgme/gpgme_39.html | 79 +++ + pyme-web/doc/gpgme/gpgme_4.html | 83 +++ + pyme-web/doc/gpgme/gpgme_40.html | 89 +++ + pyme-web/doc/gpgme/gpgme_41.html | 99 +++ + pyme-web/doc/gpgme/gpgme_42.html | 144 +++++ + pyme-web/doc/gpgme/gpgme_43.html | 152 +++++ + pyme-web/doc/gpgme/gpgme_44.html | 112 ++++ + pyme-web/doc/gpgme/gpgme_45.html | 101 +++ + pyme-web/doc/gpgme/gpgme_46.html | 459 ++++++++++++++ + pyme-web/doc/gpgme/gpgme_47.html | 292 +++++++++ + pyme-web/doc/gpgme/gpgme_48.html | 363 +++++++++++ + pyme-web/doc/gpgme/gpgme_49.html | 209 +++++++ + pyme-web/doc/gpgme/gpgme_5.html | 74 +++ + pyme-web/doc/gpgme/gpgme_50.html | 88 +++ + pyme-web/doc/gpgme/gpgme_51.html | 208 +++++++ + pyme-web/doc/gpgme/gpgme_52.html | 154 +++++ + pyme-web/doc/gpgme/gpgme_53.html | 291 +++++++++ + pyme-web/doc/gpgme/gpgme_54.html | 91 +++ + pyme-web/doc/gpgme/gpgme_55.html | 107 ++++ + pyme-web/doc/gpgme/gpgme_56.html | 140 +++++ + pyme-web/doc/gpgme/gpgme_57.html | 106 ++++ + pyme-web/doc/gpgme/gpgme_58.html | 89 +++ + pyme-web/doc/gpgme/gpgme_59.html | 97 +++ + pyme-web/doc/gpgme/gpgme_6.html | 77 +++ + pyme-web/doc/gpgme/gpgme_60.html | 142 +++++ + pyme-web/doc/gpgme/gpgme_61.html | 626 +++++++++++++++++++ + pyme-web/doc/gpgme/gpgme_62.html | 107 ++++ + pyme-web/doc/gpgme/gpgme_63.html | 67 ++ + pyme-web/doc/gpgme/gpgme_64.html | 95 +++ + pyme-web/doc/gpgme/gpgme_65.html | 233 +++++++ + pyme-web/doc/gpgme/gpgme_66.html | 65 ++ + pyme-web/doc/gpgme/gpgme_67.html | 220 +++++++ + pyme-web/doc/gpgme/gpgme_68.html | 75 +++ + pyme-web/doc/gpgme/gpgme_69.html | 119 ++++ + pyme-web/doc/gpgme/gpgme_7.html | 123 ++++ + pyme-web/doc/gpgme/gpgme_70.html | 107 ++++ + pyme-web/doc/gpgme/gpgme_71.html | 218 +++++++ + pyme-web/doc/gpgme/gpgme_72.html | 134 ++++ + pyme-web/doc/gpgme/gpgme_73.html | 299 +++++++++ + pyme-web/doc/gpgme/gpgme_74.html | 103 ++++ + pyme-web/doc/gpgme/gpgme_75.html | 104 ++++ + pyme-web/doc/gpgme/gpgme_76.html | 118 ++++ + pyme-web/doc/gpgme/gpgme_77.html | 95 +++ + pyme-web/doc/gpgme/gpgme_78.html | 71 +++ + pyme-web/doc/gpgme/gpgme_79.html | 686 +++++++++++++++++++++ + pyme-web/doc/gpgme/gpgme_8.html | 155 +++++ + pyme-web/doc/gpgme/gpgme_80.html | 120 ++++ + pyme-web/doc/gpgme/gpgme_81.html | 278 +++++++++ + pyme-web/doc/gpgme/gpgme_82.html | 272 ++++++++ + pyme-web/doc/gpgme/gpgme_83.html | 180 ++++++ + pyme-web/doc/gpgme/gpgme_84.html | 99 +++ + pyme-web/doc/gpgme/gpgme_9.html | 104 ++++ + pyme-web/doc/gpgme/gpgme_abt.html | 206 +++++++ + pyme-web/doc/gpgme/gpgme_fot.html | 53 ++ + pyme-web/doc/gpgme/gpgme_ovr.html | 68 ++ + pyme-web/doc/gpgme/gpgme_toc.html | 247 ++++++++ + pyme-web/doc/gpgme/index.html | 251 ++++++++ + pyme-web/doc/pyme/index.html | 166 +++++ + pyme-web/doc/pyme/pyme.callbacks.html | 50 ++ + .../doc/pyme/pyme.constants.data.encoding.html | 48 ++ + pyme-web/doc/pyme/pyme.constants.data.html | 29 + + pyme-web/doc/pyme/pyme.constants.event.html | 48 ++ + pyme-web/doc/pyme/pyme.constants.html | 39 ++ + pyme-web/doc/pyme/pyme.constants.import.html | 49 ++ + pyme-web/doc/pyme/pyme.constants.keylist.html | 29 + + pyme-web/doc/pyme/pyme.constants.keylist.mode.html | 48 ++ + pyme-web/doc/pyme/pyme.constants.md.html | 58 ++ + pyme-web/doc/pyme/pyme.constants.pk.html | 50 ++ + pyme-web/doc/pyme/pyme.constants.protocol.html | 46 ++ + pyme-web/doc/pyme/pyme.constants.sig.html | 29 + + pyme-web/doc/pyme/pyme.constants.sig.mode.html | 47 ++ + pyme-web/doc/pyme/pyme.constants.sigsum.html | 55 ++ + pyme-web/doc/pyme/pyme.constants.status.html | 117 ++++ + pyme-web/doc/pyme/pyme.constants.validity.html | 50 ++ + pyme-web/doc/pyme/pyme.core.html | 254 ++++++++ + pyme-web/doc/pyme/pyme.errors.html | 90 +++ + pyme-web/doc/pyme/pyme.html | 166 +++++ + pyme-web/doc/pyme/pyme.util.html | 78 +++ + pyme-web/doc/pyme/pyme.version.html | 37 ++ + pyme-web/index.html | 6 +- + 113 files changed, 14966 insertions(+), 1 deletion(-) + +commit 2d6fe54479f042644f7b0f3d2fe35877d2056144 +Author: belyi <devnull@localhost> +Date: Thu May 19 02:06:09 2005 +0000 + + Added INSTALL file. + + pyme/INSTALL | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +commit d6892fff0c3cedf41dba4c25ab8608e7f2bc039c +Author: belyi <devnull@localhost> +Date: Tue May 17 16:49:28 2005 +0000 + + Update copyright note on simple.py + + pyme/examples/simple.py | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +commit c2cd9cdf5995843aad7b200b929db2969effc9d2 +Author: belyi <devnull@localhost> +Date: Tue May 17 15:03:58 2005 +0000 + + Update simple.py to catch errors. + + pyme/examples/simple.py | 17 +++++++++++------ + 1 file changed, 11 insertions(+), 6 deletions(-) + +commit eaedae7c6a0ea993caab067efe781a59b6769c44 +Author: belyi <devnull@localhost> +Date: Tue May 17 01:18:23 2005 +0000 + + Added 'PYTHON = python' into Makefile for bug #1199122 + + pyme/Makefile | 1 + + pyme/examples/signverify.py | 1 + + 2 files changed, 2 insertions(+) + +commit 56fd244bb2636a4d58629899ea3cde1d96428198 +Author: belyi <devnull@localhost> +Date: Wed Apr 27 21:37:06 2005 +0000 + + Added pygpa example. + + pyme/debian/changelog | 3 +- + pyme/examples/pygpa.glade | 5546 +++++++++++++++++++++++++++++++++++++++++++++ + pyme/examples/pygpa.py | 1459 ++++++++++++ + 3 files changed, 7007 insertions(+), 1 deletion(-) + +commit 2d9a2a91a59ac3fee5410c953b7e0859e9e7cd35 +Author: belyi <devnull@localhost> +Date: Thu Apr 21 15:17:51 2005 +0000 + + Change version to 0.7.0 due to the change in license. + + pyme/debian/changelog | 2 +- + pyme/pyme/version.py | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +commit 94e34e38d742f145385bd235825b6ba1e30d8339 +Author: belyi <devnull@localhost> +Date: Thu Apr 21 03:53:12 2005 +0000 + + Changed license on PyMe from GPL to LGPL. + PyMe examples keep GPL license. + + pyme/COPYING.LESSER | 510 +++++++++++++++++++++++++++++++++++ + pyme/Makefile | 20 +- + pyme/debian/changelog | 4 +- + pyme/debian/copyright | 22 +- + pyme/gpgme-h-clean.py | 16 ++ + pyme/gpgme.i | 20 +- + pyme/helpers.c | 20 +- + pyme/helpers.h | 20 +- + pyme/pyme/__init__.py | 20 +- + pyme/pyme/callbacks.py | 20 +- + pyme/pyme/constants/data/encoding.py | 20 +- + pyme/pyme/constants/event.py | 20 +- + pyme/pyme/constants/import.py | 20 +- + pyme/pyme/constants/keylist/mode.py | 20 +- + pyme/pyme/constants/md.py | 20 +- + pyme/pyme/constants/pk.py | 20 +- + pyme/pyme/constants/protocol.py | 20 +- + pyme/pyme/constants/sig/mode.py | 20 +- + pyme/pyme/constants/sigsum.py | 20 +- + pyme/pyme/constants/status.py | 20 +- + pyme/pyme/constants/validity.py | 20 +- + pyme/pyme/core.py | 20 +- + pyme/pyme/errors.py | 20 +- + pyme/pyme/util.py | 20 +- + pyme/pyme/version.py | 22 +- + pyme/setup.py | 20 +- + 26 files changed, 761 insertions(+), 233 deletions(-) + +commit 0d8aa0f6335cb1506a37085095ed45173b099a02 +Author: belyi <devnull@localhost> +Date: Tue Apr 19 01:46:06 2005 +0000 + + Added __hash__ and __eq__ methods to GpgmeWrapper to allow both Context() + and Data() to be used as a dictionary key. + Changed core.wait() function to always return a tuple. On timeout now it + returns (0, None) instead of just None. Plus, return context is now a + Context() object instead of a wrapper return by underlying gpgme. + + pyme/helpers.c | 1 - + pyme/pyme/core.py | 25 +++++++++++++++---------- + pyme/pyme/util.py | 9 +++++++++ + 3 files changed, 24 insertions(+), 11 deletions(-) + +commit 63ff6d10637be1dcbcd78c939ac1ef1ac30b1024 +Author: belyi <devnull@localhost> +Date: Wed Apr 6 04:58:40 2005 +0000 + + Made hook parameter optional in passphrase_cb and progress_cb. + Allowed None for callbacks to unset ones set previously. + Removed cleanup of exception in callbacks - now just retrieve the error code. + Added prev_bad parameter in passphrase_cb since it can be used in + change password protocols. + Updated examples to follow new sets of arguments in callbacks + Updated op_edit to check if passed key is None (otherwise gpgme dumps core) + God rid of annoying warning "function declaration isn't a prototype" in + helpers.c and helpers.h by changing from () to (void) list of arguments. + + pyme/debian/changelog | 10 +++++--- + pyme/examples/signverify.py | 2 +- + pyme/examples/t-edit.py | 2 +- + pyme/gpgme.i | 18 +++++++++----- + pyme/helpers.c | 60 ++++++++++++++++++++++++++++++--------------- + pyme/helpers.h | 4 +-- + pyme/pyme/callbacks.py | 6 +++-- + pyme/pyme/core.py | 47 +++++++++++++++++++++-------------- + pyme/pyme/errors.py | 2 +- + 9 files changed, 96 insertions(+), 55 deletions(-) + +commit 8f0ab8138c7aa190936376ccbbf33bb09c64d6f1 +Author: belyi <devnull@localhost> +Date: Thu Mar 31 23:50:59 2005 +0000 + + Added exception handling in passphrase_cb and edit_cb. If GPGMEError + exception is thrown in those callbacks it will be converted into its + core representation and return as an error code to the caller. + On all other exceptions error code will be GPG_ERR_GENERAL. + + pyme/Makefile | 1 + + pyme/debian/changelog | 8 ++++++++ + pyme/gpgme.i | 20 ++++++++++++++------ + pyme/helpers.c | 51 +++++++++++++++++++++++++++++++++++++++++++++------ + pyme/helpers.h | 3 +++ + 5 files changed, 71 insertions(+), 12 deletions(-) + +commit 9903d1fb11231e7e3d920e58d1ecb674c5988b07 +Author: belyi <devnull@localhost> +Date: Thu Mar 31 05:12:15 2005 +0000 + + Remove workaround from Context.wait() method since the bug report and + patch fixing gpgme_wait's behavior is sent to GPMGE developers already. + Added errorcheck into op_edit() so that it can report an error. + + pyme/pyme/core.py | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +commit 45e8a5f4e13d3ca797ec3b0037242874a6be5562 +Author: belyi <devnull@localhost> +Date: Sat Mar 26 19:44:18 2005 +0000 + + Updated verion number to 0.6.2 in version.py + Added examples/*.glade files into documentation package. + + pyme/debian/examples | 1 + + pyme/pyme/version.py | 2 +- + 2 files changed, 2 insertions(+), 1 deletion(-) + +commit 270b87bb40e180cb6e8f1de9a0e8161525ffa4ab +Author: belyi <devnull@localhost> +Date: Sat Mar 26 19:31:14 2005 +0000 + + Updated debian/changelog regarding PyGtkGpgKeys example and a fix in errors. + + pyme/debian/changelog | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +commit ea4682009a506db91e5174ffd038fe7e4406b591 +Author: belyi <devnull@localhost> +Date: Sat Mar 26 19:25:36 2005 +0000 + + Added handling of right mouse button click. + Changed reporting a string instead of a number on key generation failure. + + pyme/examples/PyGtkGpgKeys.glade | 2 ++ + pyme/examples/PyGtkGpgKeys.py | 30 +++++++++++++++++++++++++++--- + 2 files changed, 29 insertions(+), 3 deletions(-) + +commit f65ad1a703d0098a3204fb8527a54d253e5847e7 +Author: belyi <devnull@localhost> +Date: Sat Mar 26 18:11:11 2005 +0000 + + Added another column indicating if a key has a secret part. + Automated generation of the View menu from the view field of the KeyColumn + class. + + pyme/examples/PyGtkGpgKeys.glade | 93 ++-------------------------------------- + pyme/examples/PyGtkGpgKeys.py | 74 +++++++++++++++++--------------- + 2 files changed, 44 insertions(+), 123 deletions(-) + +commit b54e83a7a7a5785502f3c7e8b95f15e23b40e65a +Author: belyi <devnull@localhost> +Date: Sat Mar 26 16:45:13 2005 +0000 + + Small change to the way gtk.TreeModel object is used. + + pyme/examples/PyGtkGpgKeys.py | 21 ++++++++++----------- + 1 file changed, 10 insertions(+), 11 deletions(-) + +commit 7078db75cef4c1fd70cf03e37172bdb4f933fd1b +Author: belyi <devnull@localhost> +Date: Fri Mar 25 23:33:06 2005 +0000 + + Use more comprehansible error reporting since gpgme_strerror_r returns None + all the time. + + pyme/pyme/errors.py | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +commit 151213f4344d9984975721440af07de09e3df61c +Author: belyi <devnull@localhost> +Date: Fri Mar 25 04:30:17 2005 +0000 + + Improved PyGtkGpgKeys example to manage owner_trust on keys. + Added another example inter-edit.py which is just a hepler to write + scripts for Context.op_edit() command. + + pyme/examples/PyGtkGpgKeys.glade | 78 ++++++++++++++++++++++++++++++++++++++++ + pyme/examples/PyGtkGpgKeys.py | 68 +++++++++++++++++++++++++++++++---- + pyme/examples/inter-edit.py | 54 ++++++++++++++++++++++++++++ + pyme/examples/t-edit.py | 18 ++++++++++ + 4 files changed, 212 insertions(+), 6 deletions(-) + +commit fc7235af217bcee5231ce7fbd7f234712d5ad3b0 +Author: belyi <devnull@localhost> +Date: Fri Mar 25 00:30:39 2005 +0000 + + Updated PyGtkGpgKeys example to include import, export and reload + functionality. Also added ability to remove number of keys simultanously. + Rearanged how KeyColumn is used to avoid unnecessary sorts and duplication + of information in different parts of the code. + + pyme/examples/PyGtkGpgKeys.glade | 86 +++++++++- + pyme/examples/PyGtkGpgKeys.py | 332 ++++++++++++++++++++++++++++----------- + 2 files changed, 325 insertions(+), 93 deletions(-) + +commit 9f65749ccb1b7cab562e19c03f4371d5f7d94912 +Author: belyi <devnull@localhost> +Date: Thu Mar 24 05:51:03 2005 +0000 + + Added example of PyGTK+ and PyMe integration. + For now it does only simple things - listing, deleting, and generating keys. + + pyme/examples/PyGtkGpgKeys.glade | 1321 +++++++++++++++++++++++++++++++++++++ + pyme/examples/PyGtkGpgKeys.gladep | 8 + + pyme/examples/PyGtkGpgKeys.py | 424 ++++++++++++ + 3 files changed, 1753 insertions(+) + +commit 59e23f32c3b46413c9ec09e23e1a385a110fb103 +Author: belyi <devnull@localhost> +Date: Thu Mar 24 05:44:58 2005 +0000 + + Added wait method Context class which handles asynchornous calls a little + bit better than the one generated by SWIG. + + pyme/debian/changelog | 7 +++++++ + pyme/gpgme.i | 1 + + pyme/pyme/core.py | 40 ++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 48 insertions(+) + +commit 4c1b5259e4985df2cba0ae4fc09f12cd94603a75 +Author: belyi <devnull@localhost> +Date: Tue Mar 22 18:29:31 2005 +0000 + + Added correct handling of Context.op_edit() method. + Added example/t-edit.py showing usage for this method. + Output of this example should match output of the tests/gpg/t-edit + from the GPGME test suite. + Remove unused static function from helpers.c + + pyme/examples/t-edit.py | 38 ++++++++++++++++++++++++++++++++++++++ + pyme/gpgme.i | 36 ++++++++++++++++++++++++++++++++++++ + pyme/helpers.c | 36 ------------------------------------ + pyme/pyme/core.py | 5 ++++- + 4 files changed, 78 insertions(+), 37 deletions(-) + +commit dc587e215283bfef2dd594f86a7b2945f74f5155 +Author: belyi <devnull@localhost> +Date: Sat Mar 19 01:43:59 2005 +0000 + + Update changelog to include note about deprecated function in 0.6.1 release + + pyme/debian/changelog | 3 ++- + pyme/examples/encrypt-to-all.py | 3 +-- + 2 files changed, 3 insertions(+), 3 deletions(-) + +commit 86de4b3ad777f980ccf7ba3462c85bbe1787d1fd +Author: belyi <devnull@localhost> +Date: Sat Mar 19 01:40:07 2005 +0000 + + Remove deprecated functions from helpers.[ch] + Use gpgme-h-clean.py to remove deprecated functions and typedefs from + the GPGME header file. This will reduce the number of unused methods. + + pyme/Makefile | 4 ++-- + pyme/gpgme-h-clean.py | 26 ++++++++++++++++++++++++++ + pyme/helpers.c | 8 -------- + pyme/helpers.h | 2 -- + 4 files changed, 28 insertions(+), 12 deletions(-) + +commit 2483efcbd0d73c628c4d7717928a766c3b58f0aa +Author: belyi <devnull@localhost> +Date: Fri Mar 18 22:15:52 2005 +0000 + + Update copyright and author values in pyme/version.py + Create rules to build distribution files - one full and one without + debian bits. + + pyme/Makefile | 28 ++++++++++++++++++++++------ + pyme/pyme/version.py | 12 ++++++------ + 2 files changed, 28 insertions(+), 12 deletions(-) + +commit 168593285380f5a7805f3dd08657d429a72d3621 +Author: belyi <devnull@localhost> +Date: Fri Mar 18 19:09:33 2005 +0000 + + Added package building for python2.4 + + Updated copyright notes to include myslef and avoid confusion who's the + maintainer. In John's own words: "I'd prefer to just step out of the picture". + Jonh's copyright notice left intact. + + pyme/Makefile | 6 +++--- + pyme/debian/changelog | 7 +++++++ + pyme/debian/control | 30 +++++++++++++++++++++++++++--- + pyme/debian/copyright | 10 ++++------ + pyme/debian/rules | 4 ++++ + pyme/debian/setup.cfg-2.4 | 8 ++++++++ + pyme/examples/genkey.py | 4 ++-- + pyme/gpgme.i | 4 ++-- + pyme/helpers.c | 4 ++-- + pyme/helpers.h | 4 ++-- + pyme/pyme/__init__.py | 4 ++-- + pyme/pyme/callbacks.py | 4 ++-- + pyme/pyme/constants/data/encoding.py | 4 ++-- + pyme/pyme/constants/event.py | 4 ++-- + pyme/pyme/constants/import.py | 4 ++-- + pyme/pyme/constants/keylist/mode.py | 4 ++-- + pyme/pyme/constants/md.py | 4 ++-- + pyme/pyme/constants/pk.py | 4 ++-- + pyme/pyme/constants/protocol.py | 4 ++-- + pyme/pyme/constants/sig/mode.py | 4 ++-- + pyme/pyme/constants/sigsum.py | 4 ++-- + pyme/pyme/constants/status.py | 4 ++-- + pyme/pyme/constants/validity.py | 4 ++-- + pyme/pyme/core.py | 4 ++-- + pyme/pyme/errors.py | 4 ++-- + pyme/pyme/util.py | 4 ++-- + pyme/pyme/version.py | 2 +- + pyme/setup.py | 3 ++- + 28 files changed, 96 insertions(+), 54 deletions(-) + +commit 6dbbb252771133724b2879ed6d767cd708196dae +Author: belyi <devnull@localhost> +Date: Fri Mar 18 18:04:35 2005 +0000 + + Remove the note about gpgme.i to be generated - it's been the primary source + for some time. + + pyme/gpgme.i | 6 ------ + 1 file changed, 6 deletions(-) + +commit 9d449fa4889c6bda6d14583c0625b8d5c4ffe759 +Author: belyi <devnull@localhost> +Date: Fri May 7 18:31:22 2004 +0000 + + Added my copyright in genkey.py since there's enough changes made. + Updated signverify to use only keys generated by genkey.py, to check + that keys added to singers are able to sign and to check that the + list of signers is not empty. The last check is necessary to prevent + signing with the key of the user running signverify.py script. + Added delkey.py script to delete keys generated by genkey.py + Added exportimport.py example for key export/import. + + pyme/examples/delkey.py | 29 +++++++++++++++++ + pyme/examples/exportimport.py | 76 +++++++++++++++++++++++++++++++++++++++++++ + pyme/examples/genkey.py | 6 ++-- + pyme/examples/signverify.py | 18 ++++++---- + 4 files changed, 119 insertions(+), 10 deletions(-) + +commit df98c8d28245ad2c14b0ab50fc8f8932853bec8b +Author: belyi <devnull@localhost> +Date: Tue May 4 17:34:15 2004 +0000 + + Added examples/signverify.py for unattended sing/verify. + Updated examples/genkey.py to work correctly. + Updated gpgme.i to allow None as a value for gpgme_data_t + + pyme/examples/genkey.py | 14 ++------- + pyme/examples/signverify.py | 72 +++++++++++++++++++++++++++++++++++++++++++++ + pyme/gpgme.i | 21 ++++++++----- + 3 files changed, 87 insertions(+), 20 deletions(-) + +commit ba45931abf530ab89ead46d7233ff1b62b629a18 +Author: belyi <devnull@localhost> +Date: Thu Apr 8 16:15:09 2004 +0000 + + Ensure that we support only python2.2 and up. :-) + Use generators in core.Context class which makes pyme.aux obsolete + Remove importing future nested_scopes since they are standart starting + with python2.2 + + pyme/pyme/__init__.py | 5 ++--- + pyme/pyme/aux.py | 56 --------------------------------------------------- + pyme/pyme/core.py | 15 +++++++++++--- + pyme/pyme/errors.py | 1 - + pyme/pyme/util.py | 2 +- + 5 files changed, 15 insertions(+), 64 deletions(-) + +commit 4e9be5a55ecffa4da7ad5c192cc892eddaaa9586 +Author: belyi <devnull@localhost> +Date: Sun Mar 21 03:53:30 2004 +0000 + + Small change to index.html + Added clean: rule to the Makefile + + pyme-web/Makefile | 3 +++ + pyme-web/index.html | 6 +++--- + 2 files changed, 6 insertions(+), 3 deletions(-) + +commit 2efb95176f4edf56ed61c9ac0c3aa09c56534df0 +Author: belyi <devnull@localhost> +Date: Sun Mar 21 03:00:32 2004 +0000 + + Added Makefile rules for pyme module installation. + + pyme/Makefile | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +commit 2b83d5d8b513029cc3e54f2fa502ccc85618104b +Author: belyi <devnull@localhost> +Date: Sun Mar 21 02:29:54 2004 +0000 + + Decorative change. + + pyme/pyme/aux.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +commit e3478015d763a036c1d806ae01433fce59712204 +Author: belyi <devnull@localhost> +Date: Sun Mar 21 02:25:55 2004 +0000 + + Added RCS Id: tags + + pyme/Makefile | 1 + + pyme/examples/encrypt-to-all.py | 3 ++- + pyme/examples/genkey.py | 3 ++- + pyme/examples/sign.py | 3 ++- + pyme/examples/simple.py | 3 ++- + pyme/gpgme.i | 1 + + pyme/helpers.c | 1 + + pyme/helpers.h | 1 + + pyme/pyme/__init__.py | 1 + + pyme/pyme/aux.py | 1 + + pyme/pyme/callbacks.py | 1 + + pyme/pyme/constants/__init__.py | 2 ++ + pyme/pyme/constants/data/__init__.py | 2 ++ + pyme/pyme/constants/data/encoding.py | 1 + + pyme/pyme/constants/event.py | 1 + + pyme/pyme/constants/import.py | 1 + + pyme/pyme/constants/keylist/__init__.py | 2 ++ + pyme/pyme/constants/keylist/mode.py | 1 + + pyme/pyme/constants/md.py | 1 + + pyme/pyme/constants/pk.py | 1 + + pyme/pyme/constants/protocol.py | 1 + + pyme/pyme/constants/sig/__init__.py | 2 ++ + pyme/pyme/constants/sig/mode.py | 1 + + pyme/pyme/constants/sigsum.py | 1 + + pyme/pyme/constants/status.py | 1 + + pyme/pyme/constants/validity.py | 1 + + pyme/pyme/core.py | 1 + + pyme/pyme/errors.py | 1 + + pyme/pyme/util.py | 1 + + pyme/pyme/version.py | 2 ++ + 30 files changed, 39 insertions(+), 4 deletions(-) + +commit b3b3712645332c5bc3e8d9d557aab21d48ff0f86 +Author: belyi <devnull@localhost> +Date: Sun Mar 21 02:07:36 2004 +0000 + + Added Id: RCS tags to all files. + + pyme-web/Makefile | 2 ++ + pyme-web/index.html | 3 ++- + 2 files changed, 4 insertions(+), 1 deletion(-) + +commit 6aea2426beaaa8c43e6f2310a37a2737c0c3a1b5 +Author: belyi <devnull@localhost> +Date: Sun Mar 21 01:50:55 2004 +0000 + + Update example on the init pyme.html page to match simple.py example. + Fix core.py to use getcode() instead of getvalue() method of the exception. + + pyme/pyme/__init__.py | 22 ++++++++++++++-------- + pyme/pyme/core.py | 4 ++-- + 2 files changed, 16 insertions(+), 10 deletions(-) + +commit dee337455ffd624d3f83e1c159c4bb2cefc692c9 +Author: belyi <devnull@localhost> +Date: Sat Mar 20 20:32:29 2004 +0000 + + Added Makefile to simplify publishing web files. + + pyme-web/Makefile | 7 +++++++ + 1 file changed, 7 insertions(+) + +commit af7129baa8260697d85c2ddb434562e8a80b62d8 +Author: belyi <devnull@localhost> +Date: Sat Mar 20 20:15:53 2004 +0000 + + Added minimum of formating and SF icon. + + pyme-web/index.html | 18 +++++++++++------- + 1 file changed, 11 insertions(+), 7 deletions(-) + +commit 2e64dcbf99cee796b51667b04d8961e390edde87 +Author: belyi <devnull@localhost> +Date: Sat Mar 20 18:30:09 2004 +0000 + + Initial revision + + pyme-web/index.html | 33 +++++++++++++++++++++++++++++++++ + 1 file changed, 33 insertions(+) + +commit 1c51644b3d0b6611422d971758e35f303d2ad5df +Author: belyi <devnull@localhost> +Date: Sat Mar 20 05:10:46 2004 +0000 + + Update examples and package information on the initial pyme doc page. + + pyme/pyme/__init__.py | 27 ++++++++++++--------------- + 1 file changed, 12 insertions(+), 15 deletions(-) + +commit b2d31b0bfbffdff5247d6db4e3c95140cc1b1f19 +Author: belyi <devnull@localhost> +Date: Sat Mar 20 04:47:42 2004 +0000 + + Deleted unnecessary files. + Updated debian/control to remove dependency on python-xml package since there's + none now. + Move example files from 'doc' into separate control file. + Update debian/rules to build documentation from *.py files and to exclude + CVS directories from the installation. + + pyme/Makefile | 26 ++----- + pyme/debian/control | 8 +-- + pyme/debian/docs | 1 - + pyme/debian/ex.package.doc-base | 22 ------ + pyme/debian/examples | 1 + + pyme/debian/manpage.1.ex | 60 ---------------- + pyme/debian/manpage.sgml.ex | 152 ---------------------------------------- + pyme/debian/rules | 12 ++-- + 8 files changed, 15 insertions(+), 267 deletions(-) + +commit 1b517dd9b82a433499b4696b06d94d756cd36e53 +Author: belyi <devnull@localhost> +Date: Sat Mar 20 02:59:15 2004 +0000 + + Remove doc/gpgme directory containing GPGME documentation since this belongs + to a different project. Need to add reference in our documentation. + + pyme/doc/gpgme/fdl.texi | 402 ------ + pyme/doc/gpgme/gpgme.texi | 3372 ------------------------------------------- + pyme/doc/gpgme/gpl.texi | 397 ----- + pyme/doc/gpgme/version.texi | 4 - + 4 files changed, 4175 deletions(-) + +commit 95d7d171da115a0fedfe2a4a7e5acc8aa408f673 +Author: belyi <devnull@localhost> +Date: Sat Mar 20 02:45:03 2004 +0000 + + Change debian/rules to generate files by swig during build and to cleanup + those files on 'clean' rule. + Plus, leave generated gpgme_wrap.c in the root directory instead of moving + it into subdirectory 'generated'. + + pyme/Makefile | 8 +++----- + pyme/debian/rules | 3 ++- + pyme/setup.py | 2 +- + 3 files changed, 6 insertions(+), 7 deletions(-) + +commit 545b3d90d445c5c78e8d72b2c1780863e02c789a +Author: belyi <devnull@localhost> +Date: Sat Mar 20 02:18:01 2004 +0000 + + Initial revision + + pyme/COPYING | 340 ++++ + pyme/ChangeLog | 802 ++++++++ + pyme/Makefile | 79 + + pyme/debian/README.Debian | 6 + + pyme/debian/changelog | 19 + + pyme/debian/control | 68 + + pyme/debian/copyright | 27 + + pyme/debian/dirs | 2 + + pyme/debian/docs | 2 + + pyme/debian/ex.package.doc-base | 22 + + pyme/debian/manpage.1.ex | 60 + + pyme/debian/manpage.sgml.ex | 152 ++ + pyme/debian/postinst.ex | 48 + + pyme/debian/postrm.ex | 38 + + pyme/debian/preinst.ex | 44 + + pyme/debian/prerm.ex | 39 + + pyme/debian/rules | 130 ++ + pyme/debian/setup.cfg-2.2 | 8 + + pyme/debian/setup.cfg-2.3 | 8 + + pyme/doc/gpgme/fdl.texi | 402 ++++ + pyme/doc/gpgme/gpgme.texi | 3372 +++++++++++++++++++++++++++++++ + pyme/doc/gpgme/gpl.texi | 397 ++++ + pyme/doc/gpgme/version.texi | 4 + + pyme/examples/encrypt-to-all.py | 63 + + pyme/examples/genkey.py | 55 + + pyme/examples/sign.py | 28 + + pyme/examples/simple.py | 44 + + pyme/gpgme.i | 191 ++ + pyme/helpers.c | 139 ++ + pyme/helpers.h | 29 + + pyme/pyme/__init__.py | 134 ++ + pyme/pyme/aux.py | 55 + + pyme/pyme/callbacks.py | 45 + + pyme/pyme/constants/__init__.py | 2 + + pyme/pyme/constants/data/__init__.py | 2 + + pyme/pyme/constants/data/encoding.py | 19 + + pyme/pyme/constants/event.py | 19 + + pyme/pyme/constants/import.py | 19 + + pyme/pyme/constants/keylist/__init__.py | 2 + + pyme/pyme/constants/keylist/mode.py | 19 + + pyme/pyme/constants/md.py | 19 + + pyme/pyme/constants/pk.py | 19 + + pyme/pyme/constants/protocol.py | 19 + + pyme/pyme/constants/sig/__init__.py | 2 + + pyme/pyme/constants/sig/mode.py | 19 + + pyme/pyme/constants/sigsum.py | 19 + + pyme/pyme/constants/status.py | 19 + + pyme/pyme/constants/validity.py | 19 + + pyme/pyme/core.py | 367 ++++ + pyme/pyme/errors.py | 46 + + pyme/pyme/util.py | 61 + + pyme/pyme/version.py | 39 + + pyme/setup.py | 60 + + 53 files changed, 7642 insertions(+) + +commit a3d5a442dc713b6c4d6fc4134db5b47e379dc41d +Author: root <devnull@localhost> +Date: Fri Mar 19 14:12:30 2004 +0000 + + initial checkin + + CVSROOT/checkoutlist | 13 +++++++++++++ + CVSROOT/commitinfo | 15 +++++++++++++++ + CVSROOT/config | 21 +++++++++++++++++++++ + CVSROOT/cvswrappers | 19 +++++++++++++++++++ + CVSROOT/editinfo | 21 +++++++++++++++++++++ + CVSROOT/loginfo | 26 ++++++++++++++++++++++++++ + CVSROOT/modules | 26 ++++++++++++++++++++++++++ + CVSROOT/notify | 12 ++++++++++++ + CVSROOT/rcsinfo | 13 +++++++++++++ + CVSROOT/taginfo | 20 ++++++++++++++++++++ + CVSROOT/verifymsg | 21 +++++++++++++++++++++ + 11 files changed, 207 insertions(+) diff --git a/lang/python/doc/rst/gpgme-python-howto.rst b/lang/python/doc/rst/gpgme-python-howto.rst new file mode 100644 index 0000000..9181491 --- /dev/null +++ b/lang/python/doc/rst/gpgme-python-howto.rst @@ -0,0 +1,2998 @@ +.. _intro: + +Introduction +============ + ++-----------------------------------+-----------------------------------+ +| Version: | 0.1.4 | ++-----------------------------------+-----------------------------------+ +| GPGME Version: | 1.12.0 | ++-----------------------------------+-----------------------------------+ +| Author: | `Ben | +| | McGinnes <https://gnupg.org/peopl | +| | e/index.html#sec-1-5>`__ | +| | <ben@gnupg.org> | ++-----------------------------------+-----------------------------------+ +| Author GPG Key: | DB4724E6FA4286C92B4E55C4321E4E237 | +| | 3590E5D | ++-----------------------------------+-----------------------------------+ +| Language: | Australian English, British | +| | English | ++-----------------------------------+-----------------------------------+ +| xml:lang: | en-AU, en-GB, en | ++-----------------------------------+-----------------------------------+ + +This document provides basic instruction in how to use the GPGME Python +bindings to programmatically leverage the GPGME library. + +.. _py2-vs-py3: + +Python 2 versus Python 3 +------------------------ + +Though the GPGME Python bindings themselves provide support for both +Python 2 and 3, the focus is unequivocally on Python 3 and specifically +from Python 3.4 and above. As a consequence all the examples and +instructions in this guide use Python 3 code. + +Much of it will work with Python 2, but much of it also deals with +Python 3 byte literals, particularly when reading and writing data. +Developers concentrating on Python 2.7, and possibly even 2.6, will need +to make the appropriate modifications to support the older string and +unicode types as opposed to bytes. + +There are multiple reasons for concentrating on Python 3; some of which +relate to the immediate integration of these bindings, some of which +relate to longer term plans for both GPGME and the python bindings and +some of which relate to the impending EOL period for Python 2.7. +Essentially, though, there is little value in tying the bindings to a +version of the language which is a dead end and the advantages offered +by Python 3 over Python 2 make handling the data types with which GPGME +deals considerably easier. + +.. _howto-python3-examples: + +Examples +-------- + +All of the examples found in this document can be found as Python 3 +scripts in the ``lang/python/examples/howto`` directory. + +Unofficial Drafts +----------------- + +In addition to shipping with each release of GPGME, there is a section +on locations to read or download `draft editions <#draft-editions>`__ of +this document from at the end of it. These are unofficial versions +produced in between major releases. + +.. _new-stuff: + +What\'s New +----------- + +The most obviously new point for those reading this guide is this +section on other new things, but that\'s hardly important. Not given all +the other things which spurred the need for adding this section and its +subsections. + +.. _new-stuff-1-12-0: + +New in GPGME 1·12·0 +~~~~~~~~~~~~~~~~~~~ + +There have been quite a number of additions to GPGME and the Python +bindings to it since the last release of GPGME with versions 1.11.0 and +1.11.1 in April, 2018. + +The bullet points of new additiions are: + +- an expanded section on `installing <#installation>`__ and + `troubleshooting <#snafu>`__ the Python bindings. +- The release of Python 3.7.0; which appears to be working just fine + with our bindings, in spite of intermittent reports of problems for + many other Python projects with that new release. +- Python 3.7 has been moved to the head of the specified python + versions list in the build process. +- In order to fix some other issues, there are certain underlying + functions which are more exposed through the + `gpg.Context() <#howto-get-context>`__, but ongoing documentation + ought to clarify that or otherwise provide the best means of using + the bindings. Some additions to ``gpg.core`` and the ``Context()``, + however, were intended (see below). +- Continuing work in identifying and confirming the cause of + oft-reported `problems installing the Python bindings on + Windows <#snafu-runtime-not-funtime>`__. +- GSOC: Google\'s Surreptitiously Ordered Conscription ... erm ... oh, + right; Google\'s Summer of Code. Though there were two hopeful + candidates this year; only one ended up involved with the GnuPG + Project directly, the other concentrated on an unrelated third party + project with closer ties to one of the GNU/Linux distributions than + to the GnuPG Project. Thus the Python bindings benefited from GSOC + participant Jacob Adams, who added the key\ :sub:`import` function; + building on prior work by Tobias Mueller. +- Several new methods functions were added to the gpg.Context(), + including: `key\ import <#howto-import-key>`__, + `key\ export <#howto-export-key>`__, + `key\ exportminimal <#howto-export-public-key>`__ and + `key\ exportsecret <#howto-export-secret-key>`__. +- Importing and exporting examples include versions integrated with + Marcel Fest\'s recently released `HKP for + Python <https://github.com/Selfnet/hkp4py>`__ module. Some + `additional notes on this module <#hkp4py>`__ are included at the end + of the HOWTO. +- Instructions for dealing with semi-walled garden implementations like + ProtonMail are also included. This is intended to make things a + little easier when communicating with users of ProtonMail\'s services + and should not be construed as an endorsement of said service. The + GnuPG Project neither favours, nor disfavours ProtonMail and the + majority of this deals with interacting with the ProtonMail + keyserver. +- Semi-formalised the location where `draft + versions <#draft-editions>`__ of this HOWTO may periodically be + accessible. This is both for the reference of others and testing the + publishing of the document itself. Renamed this file at around the + same time. +- The Texinfo documentation build configuration has been replicated + from the parent project in order to make to maintain consistency with + that project (and actually ship with each release). +- a reStructuredText (``.rst``) version is also generated for Python + developers more used to and comfortable with that format as it is the + standard Python documentation format and Python developers may wish + to use it with Sphinx. Please note that there has been no testing of + the reStructuredText version with Sphinx at all. The reST file was + generated by the simple expedient of using + `Pandoc <https://pandoc.org/>`__. +- Added a new section for `advanced or experimental + use <#advanced-use>`__. +- Began the advanced use cases with `a section <#cython>`__ on using + the module with `Cython <http://cython.org/>`__. +- Added a number of new scripts to the ``example/howto/`` directory; + some of which may be in advance of their planned sections of the + HOWTO (and some are just there because it seemed like a good idea at + the time). +- Cleaned up a lot of things under the hood. + +GPGME Concepts +============== + +.. _gpgme-c-api: + +A C API +------- + +Unlike many modern APIs with which programmers will be more familiar +with these days, the GPGME API is a C API. The API is intended for use +by C coders who would be able to access its features by including the +``gpgme.h`` header file with their own C source code and then access its +functions just as they would any other C headers. + +This is a very effective method of gaining complete access to the API +and in the most efficient manner possible. It does, however, have the +drawback that it cannot be directly used by other languages without some +means of providing an interface to those languages. This is where the +need for bindings in various languages stems. + +.. _gpgme-python-bindings: + +Python bindings +--------------- + +The Python bindings for GPGME provide a higher level means of accessing +the complete feature set of GPGME itself. It also provides a more +pythonic means of calling these API functions. + +The bindings are generated dynamically with SWIG and the copy of +``gpgme.h`` generated when GPGME is compiled. + +This means that a version of the Python bindings is fundamentally tied +to the exact same version of GPGME used to generate that copy of +``gpgme.h``. + +.. _gpgme-python-bindings-diffs: + +Difference between the Python bindings and other GnuPG Python packages +---------------------------------------------------------------------- + +There have been numerous attempts to add GnuPG support to Python over +the years. Some of the most well known are listed here, along with what +differentiates them. + +.. _diffs-python-gnupg: + +The python-gnupg package maintained by Vinay Sajip +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This is arguably the most popular means of integrating GPG with Python. +The package utilises the ``subprocess`` module to implement wrappers for +the ``gpg`` and ``gpg2`` executables normally invoked on the command +line (``gpg.exe`` and ``gpg2.exe`` on Windows). + +The popularity of this package stemmed from its ease of use and +capability in providing the most commonly required features. + +Unfortunately it has been beset by a number of security issues in the +past; most of which stemmed from using unsafe methods of accessing the +command line via the ``subprocess`` calls. While some effort has been +made over the last two to three years (as of 2018) to mitigate this, +particularly by no longer providing shell access through those +subprocess calls, the wrapper is still somewhat limited in the scope of +its GnuPG features coverage. + +The python-gnupg package is available under the MIT license. + +.. _diffs-isis-gnupg: + +The gnupg package created and maintained by Isis Lovecruft +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In 2015 Isis Lovecruft from the Tor Project forked and then +re-implemented the python-gnupg package as just gnupg. This new package +also relied on subprocess to call the ``gpg`` or ``gpg2`` binaries, but +did so somewhat more securely. + +The naming and version numbering selected for this package, however, +resulted in conflicts with the original python-gnupg and since its +functions were called in a different manner to python-gnupg, the release +of this package also resulted in a great deal of consternation when +people installed what they thought was an upgrade that subsequently +broke the code relying on it. + +The gnupg package is available under the GNU General Public License +version 3.0 (or any later version). + +.. _diffs-pyme: + +The PyME package maintained by Martin Albrecht +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This package is the origin of these bindings, though they are somewhat +different now. For details of when and how the PyME package was folded +back into GPGME itself see the `Short History <short-history.org>`__ +document. [1]_ + +The PyME package was first released in 2002 and was also the first +attempt to implement a low level binding to GPGME. In doing so it +provided access to considerably more functionality than either the +``python-gnupg`` or ``gnupg`` packages. + +The PyME package is only available for Python 2.6 and 2.7. + +Porting the PyME package to Python 3.4 in 2015 is what resulted in it +being folded into the GPGME project and the current bindings are the end +result of that effort. + +The PyME package is available under the same dual licensing as GPGME +itself: the GNU General Public License version 2.0 (or any later +version) and the GNU Lesser General Public License version 2.1 (or any +later version). + +.. _gpgme-python-install: + +GPGME Python bindings installation +================================== + +.. _do-not-use-pypi: + +No PyPI +------- + +Most third-party Python packages and modules are available and +distributed through the Python Package Installer, known as PyPI. + +Due to the nature of what these bindings are and how they work, it is +infeasible to install the GPGME Python bindings in the same way. + +This is because the bindings use SWIG to dynamically generate C bindings +against ``gpgme.h`` and ``gpgme.h`` is generated from ``gpgme.h.in`` at +compile time when GPGME is built from source. Thus to include a package +in PyPI which actually built correctly would require either statically +built libraries for every architecture bundled with it or a full +implementation of C for each architecture. + +See the additional notes regarding `CFFI and SWIG <#snafu-cffi>`__ at +the end of this section for further details. + +.. _gpgme-python-requirements: + +Requirements +------------ + +The GPGME Python bindings only have three requirements: + +#. A suitable version of Python 2 or Python 3. With Python 2 that means + CPython 2.7 and with Python 3 that means CPython 3.4 or higher. +#. `SWIG <https://www.swig.org>`__. +#. GPGME itself. Which also means that all of GPGME\'s dependencies must + be installed too. + +.. _gpgme-python-recommendations: + +Recommended Additions +~~~~~~~~~~~~~~~~~~~~~ + +Though none of the following are absolute requirements, they are all +recommended for use with the Python bindings. In some cases these +recommendations refer to which version(s) of CPython to use the bindings +with, while others refer to third party modules which provide a +significant advantage in some way. + +#. If possible, use Python 3 instead of 2. +#. Favour a more recent version of Python since even 3.4 is due to reach + EOL soon. In production systems and services, Python 3.6 should be + robust enough to be relied on. +#. If possible add the following Python modules which are not part of + the standard library: + `Requests <http://docs.python-requests.org/en/latest/index.html>`__, + `Cython <http://cython.org/>`__ and + `hkp4py <https://github.com/Selfnet/hkp4py>`__. Chances are quite + high that at least the first one and maybe two of those will already + be installed. + +Note that, as with Cython, some of the planned additions to the +`Advanced <#advanced-use>`__ section, will bring with them additional +requirements. Most of these will be fairly well known and commonly +installed ones, however, which are in many cases likely to have already +been installed on many systems or be familiar to Python programmers. + +Installation +------------ + +Installing the Python bindings is effectively achieved by compiling and +installing GPGME itself. + +Once SWIG is installed with Python and all the dependencies for GPGME +are installed you only need to confirm that the version(s) of Python you +want the bindings installed for are in your ``$PATH``. + +By default GPGME will attempt to install the bindings for the most +recent or highest version number of Python 2 and Python 3 it detects in +``$PATH``. It specifically checks for the ``python`` and ``python3`` +executables first and then checks for specific version numbers. + +For Python 2 it checks for these executables in this order: ``python``, +``python2`` and ``python2.7``. + +For Python 3 it checks for these executables in this order: ``python3``, +``python3.7``, ``python3.6``, ``python3.5`` and ``python3.4``. [2]_ + +On systems where ``python`` is actually ``python3`` and not ``python2`` +it may be possible that ``python2`` may be overlooked, but there have +been no reports of that actually occurring as yet. + +In the three months or so since the release of Python 3.7.0 there has +been extensive testing and work with these bindings with no issues +specifically relating to the new version of Python or any of the new +features of either the language or the bindings. This has also been the +case with Python 3.7.1rc1. With that in mind and given the release of +Python 3.7.1 is scheduled for around the same time as GPGME 1.12.0, the +order of preferred Python versions has been changed to move Python 3.7 +ahead of Python 3.6. + +.. _install-gpgme: + +Installing GPGME +~~~~~~~~~~~~~~~~ + +See the GPGME ``README`` file for details of how to install GPGME from +source. + +.. _snafu: + +Known Issues +------------ + +There are a few known issues with the current build process and the +Python bindings. For the most part these are easily addressed should +they be encountered. + +.. _snafu-a-swig-of-this-builds-character: + +Breaking Builds +~~~~~~~~~~~~~~~ + +Occasionally when installing GPGME with the Python bindings included it +may be observed that the ``make`` portion of that process induces a +large very number of warnings and, eventually errors which end that part +of the build process. Yet following that with ``make check`` and +``make install`` appears to work seamlessly. + +The cause of this is related to the way SWIG needs to be called to +dynamically generate the C bindings for GPGME in the first place. So the +entire process will always produce ``lang/python/python2-gpg/`` and +``lang/python/python3-gpg/`` directories. These should contain the build +output generated during compilation, including the complete bindings and +module installed into ``site-packages``. + +Occasionally the errors in the early part or some other conflict (e.g. +not installing as **root** or **su**) may result in nothing being +installed to the relevant ``site-packages`` directory and the build +directory missing a lot of expected files. Even when this occurs, the +solution is actually quite simple and will always work. + +That solution is simply to run the following commands as either the +**root** user or prepended with ``sudo -H``\ [3]_ in the +``lang/python/`` directory: + +.. code:: shell + + /path/to/pythonX.Y setup.py build + /path/to/pythonX.Y setup.py build + /path/to/pythonX.Y setup.py install + +Yes, the build command does need to be run twice. Yes, you still need to +run the potentially failing or incomplete steps during the +``configure``, ``make`` and ``make install`` steps with installing +GPGME. This is because those steps generate a lot of essential files +needed, both by and in order to create, the bindings (including both the +``setup.py`` and ``gpgme.h`` files). + +#. IMPORTANT Note + + If specifying a selected number of languages to create bindings for, + try to leave Python last. Currently the majority of the other + language bindings are also preceding Python of either version when + listed alphabetically and so that just happens by default currently. + + If Python is set to precede one of the other languages then it is + possible that the errors described here may interrupt the build + process before generating bindings for those other languages. In + these cases it may be preferable to configure all preferred language + bindings separately with alternative ``configure`` steps for GPGME + using the ``--enable-languages=$LANGUAGE`` option. + +.. _snafu-lessons-for-the-lazy: + +Reinstalling Responsibly +~~~~~~~~~~~~~~~~~~~~~~~~ + +Regardless of whether you\'re installing for one version of Python or +several, there will come a point where reinstallation is required. With +most Python module installations, the installed files go into the +relevant site-packages directory and are then forgotten about. Then the +module is upgraded, the new files are copied over the old and that\'s +the end of the matter. + +While the same is true of these bindings, there have been intermittent +issues observed on some platforms which have benefited significantly +from removing all the previous installations of the bindings before +installing the updated versions. + +Removing the previous version(s) is simply a matter of changing to the +relevant ``site-packages`` directory for the version of Python in +question and removing the ``gpg/`` directory and any accompanying +egg-info files for that module. + +In most cases this will require root or administration privileges on the +system, but the same is true of installing the module in the first +place. + +.. _snafu-the-full-monty: + +Multiple installations +~~~~~~~~~~~~~~~~~~~~~~ + +For a veriety of reasons it may be either necessary or just preferable +to install the bindings to alternative installed Python versions which +meet the requirements of these bindings. + +On POSIX systems this will generally be most simply achieved by running +the manual installation commands (build, build, install) as described in +the previous section for each Python installation the bindings need to +be installed to. + +As per the SWIG documentation: the compilers, libraries and runtime used +to build GPGME and the Python Bindings **must** match those used to +compile Python itself, including the version number(s) (at least going +by major version numbers and probably minor numbers too). + +On most POSIX systems, including OS X, this will very likely be the case +in most, if not all, cases. + +.. _snafu-runtime-not-funtime: + +Won\'t Work With Windows +~~~~~~~~~~~~~~~~~~~~~~~~ + +There are semi-regular reports of Windows users having considerable +difficulty in installing and using the Python bindings at all. Very +often, possibly even always, these reports come from Cygwin users and/or +MinGW users and/or Msys2 users. Though not all of them have been +confirmed, it appears that these reports have also come from people who +installed Python using the Windows installer files from the `Python +website <https://python.org>`__ (i.e. mostly MSI installers, sometimes +self-extracting ``.exe`` files). + +The Windows versions of Python are not built using Cygwin, MinGW or +Msys2; they\'re built using Microsoft Visual Studio. Furthermore the +version used is *considerably* more advanced than the version which +MinGW obtained a small number of files from many years ago in order to +be able to compile anything at all. Not only that, but there are changes +to the version of Visual Studio between some micro releases, though that +is is particularly the case with Python 2.7, since it has been kept +around far longer than it should have been. + +There are two theoretical solutions to this issue: + +#. Compile and install the GnuPG stack, including GPGME and the Python + bibdings using the same version of Microsoft Visual Studio used by + the Python Foundation to compile the version of Python installed. + + If there are multiple versions of Python then this will need to be + done with each different version of Visual Studio used. + +#. Compile and install Python using the same tools used by choice, such + as MinGW or Msys2. + +Do **not** use the official Windows installer for Python unless +following the first method. + +In this type of situation it may even be for the best to accept that +there are less limitations on permissive software than free software and +simply opt to use a recent version of the Community Edition of Microsoft +Visual Studio to compile and build all of it, no matter what. + +Investigations into the extent or the limitations of this issue are +ongoing. + +.. _snafu-cffi: + +CFFI is the Best™ and GPGME should use it instead of SWIG +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +There are many reasons for favouring +`CFFI <https://cffi.readthedocs.io/en/latest/overview.html>`__ and +proponents of it are quite happy to repeat these things as if all it +would take to switch from SWIG to CFFI is repeating that list as if it +were a new concept. + +The fact is that there are things which Python\'s CFFI implementation +cannot handle in the GPGME C code. Beyond that there are features of +SWIG which are simply not available with CFFI at all. SWIG generates the +bindings to Python using the ``gpgme.h`` file, but that file is not a +single version shipped with each release, it too is generated when GPGME +is compiled. + +CFFI is currently unable to adapt to such a potentially mutable +codebase. If there were some means of applying SWIG\'s dynamic code +generation to produce the Python/CFFI API modes of accessing the GPGME +libraries (or the source source code directly), but such a thing does +not exist yet either and it currently appears that work is needed in at +least one of CFFI\'s dependencies before any of this can be addressed. + +So if you\'re a massive fan of CFFI; that\'s great, but if you want this +project to switch to CFFI then rather than just insisting that it +should, I\'d suggest you volunteer to bring CFFI up to the level this +project needs. + +If you\'re actually seriously considering doing so, then I\'d suggest +taking the ``gpgme-tool.c`` file in the GPGME ``src/`` directory and +getting that to work with any of the CFFI API methods (not the ABI +methods, they\'ll work with pretty much anything). When you start +running into trouble with \"ifdefs\" then you\'ll know what sort of +things are lacking. That doesn\'t even take into account the amount of +work saved via SWIG\'s code generation techniques either. + +.. _snafu-venv: + +Virtualised Environments +~~~~~~~~~~~~~~~~~~~~~~~~ + +It is fairly common practice amongst Python developers to, as much as +possible, use packages like virtualenv to keep various things that are +to be installed from interfering with each other. Given how much of the +GPGME bindings is often at odds with the usual pythonic way of doing +things, it stands to reason that this would be called into question too. + +As it happens the answer as to whether or not the bindings can be used +with virtualenv, the answer is both yes and no. + +In general we recommend installing to the relevant path and matching +prefix of GPGME itself. Which means that when GPGME, and ideally the +rest of the GnuPG stack, is installed to a prefix like ``/usr/local`` or +``/opt/local`` then the bindings would need to be installed to the main +Python installation and not a virtualised abstraction. Attempts to +separate the two in the past have been known to cause weird and +intermittent errors ranging from minor annoyances to complete failures +in the build process. + +As a consequence we only recommend building with and installing to the +main Python installations within the same prefix as GPGME is installed +to or which are found by GPGME\'s configuration stage immediately prior +to running the make commands. Which is exactly what the compiling and +installing process of GPGME does by default. + +Once that is done, however, it appears that a copy the compiled module +may be installed into a virtualenv of the same major and minor version +matching the build. Alternatively it is possible to utilise a +``sites.pth`` file in the ``site-packages/`` directory of a viertualenv +installation, which links back to the system installations corresponding +directory in order to import anything installed system wide. This may or +may not be appropriate on a case by case basis. + +Though extensive testing of either of these options is not yet complete, +preliminary testing of them indicates that both are viable as long as +the main installation is complete. Which means that certain other +options normally restricted to virtual environments are also available, +including integration with pythonic test suites (e.g. +`pytest <https://docs.pytest.org/en/latest/index.html>`__) and other +large projects. + +That said, it is worth reiterating the warning regarding non-standard +installations. If one were to attempt to install the bindings only to a +virtual environment without somehow also including the full GnuPG stack +(or enough of it as to include GPGME) then it is highly likely that +errors would be encountered at some point and more than a little likely +that the build process itself would break. + +If a degree of separation from the main operating system is still +required in spite of these warnings, then consider other forms of +virtualisation. Either a virtual machine (e.g. +`VirtualBox <https://www.virtualbox.org/>`__), a hardware emulation +layer (e.g. `QEMU <https://www.qemu.org/>`__) or an application +container (e.g. `Docker <https://www.docker.com/why-docker>`__). + +Finally it should be noted that the limited tests conducted thus far +have been using the ``virtualenv`` command in a new directory to create +the virtual python environment. As opposed to the standard ``python3 +-m venv`` and it is possible that this will make a difference depending +on the system and version of Python in use. Another option is to run the +command ``python3 -m virtualenv /path/to/install/virtual/thingy`` +instead. + +.. _howto-fund-a-mental: + +Fundamentals +============ + +Before we can get to the fun stuff, there are a few matters regarding +GPGME\'s design which hold true whether you\'re dealing with the C code +directly or these Python bindings. + +.. _no-rest-for-the-wicked: + +No REST +------- + +The first part of which is or will be fairly blatantly obvious upon +viewing the first example, but it\'s worth reiterating anyway. That +being that this API is **not** a REST API. Nor indeed could it ever be +one. + +Most, if not all, Python programmers (and not just Python programmers) +know how easy it is to work with a RESTful API. In fact they\'ve become +so popular that many other APIs attempt to emulate REST-like behaviour +as much as they are able. Right down to the use of JSON formatted output +to facilitate the use of their API without having to retrain developers. + +This API does not do that. It would not be able to do that and also +provide access to the entire C API on which it\'s built. It does, +however, provide a very pythonic interface on top of the direct bindings +and it\'s this pythonic layer that this HOWTO deals with. + +.. _howto-get-context: + +Context +------- + +One of the reasons which prevents this API from being RESTful is that +most operations require more than one instruction to the API to perform +the task. Sure, there are certain functions which can be performed +simultaneously, particularly if the result known or strongly anticipated +(e.g. selecting and encrypting to a key known to be in the public +keybox). + +There are many more, however, which cannot be manipulated so readily: +they must be performed in a specific sequence and the result of one +operation has a direct bearing on the outcome of subsequent operations. +Not merely by generating an error either. + +When dealing with this type of persistent state on the web, full of both +the RESTful and REST-like, it\'s most commonly referred to as a session. +In GPGME, however, it is called a context and every operation type has +one. + +.. _howto-keys: + +Working with keys +================= + +.. _howto-keys-selection: + +Key selection +------------- + +Selecting keys to encrypt to or to sign with will be a common occurrence +when working with GPGMe and the means available for doing so are quite +simple. + +They do depend on utilising a Context; however once the data is recorded +in another variable, that Context does not need to be the same one which +subsequent operations are performed. + +The easiest way to select a specific key is by searching for that key\'s +key ID or fingerprint, preferably the full fingerprint without any +spaces in it. A long key ID will probably be okay, but is not advised +and short key IDs are already a problem with some being generated to +match specific patterns. It does not matter whether the pattern is upper +or lower case. + +So this is the best method: + +.. code:: python + + import gpg + + k = gpg.Context().keylist(pattern="258E88DCBD3CD44D8E7AB43F6ECB6AF0DEADBEEF") + keys = list(k) + +This is passable and very likely to be common: + +.. code:: python + + import gpg + + k = gpg.Context().keylist(pattern="0x6ECB6AF0DEADBEEF") + keys = list(k) + +And this is a really bad idea: + +.. code:: python + + import gpg + + k = gpg.Context().keylist(pattern="0xDEADBEEF") + keys = list(k) + +Alternatively it may be that the intention is to create a list of keys +which all match a particular search string. For instance all the +addresses at a particular domain, like this: + +.. code:: python + + import gpg + + ncsc = gpg.Context().keylist(pattern="ncsc.mil") + nsa = list(ncsc) + +.. _howto-keys-counting: + +Counting keys +~~~~~~~~~~~~~ + +Counting the number of keys in your public keybox (``pubring.kbx``), the +format which has superseded the old keyring format (``pubring.gpg`` and +``secring.gpg``), or the number of secret keys is a very simple task. + +.. code:: python + + import gpg + + c = gpg.Context() + seckeys = c.keylist(pattern=None, secret=True) + pubkeys = c.keylist(pattern=None, secret=False) + + seclist = list(seckeys) + secnum = len(seclist) + + publist = list(pubkeys) + pubnum = len(publist) + + print(""" + Number of secret keys: {0} + Number of public keys: {1} + """.format(secnum, pubnum)) + +NOTE: The `Cython <#cython>`__ introduction in the `Advanced and +Experimental <#advanced-use>`__ section uses this same key counting code +with Cython to demonstrate some areas where Cython can improve +performance even with the bindings. Users with large public keyrings or +keyboxes, for instance, should consider these options if they are +comfortable with using Cython. + +.. _howto-get-key: + +Get key +------- + +An alternative method of getting a single key via its fingerprint is +available directly within a Context with ``Context().get_key``. This is +the preferred method of selecting a key in order to modify it, sign or +certify it and for obtaining relevant data about a single key as a part +of other functions; when verifying a signature made by that key, for +instance. + +By default this method will select public keys, but it can select secret +keys as well. + +This first example demonstrates selecting the current key of Werner +Koch, which is due to expire at the end of 2018: + +.. code:: python + + import gpg + + fingerprint = "80615870F5BAD690333686D0F2AD85AC1E42B367" + key = gpg.Context().get_key(fingerprint) + +Whereas this example demonstrates selecting the author\'s current key +with the ``secret`` key word argument set to ``True``: + +.. code:: python + + import gpg + + fingerprint = "DB4724E6FA4286C92B4E55C4321E4E2373590E5D" + key = gpg.Context().get_key(fingerprint, secret=True) + +It is, of course, quite possible to select expired, disabled and revoked +keys with this function, but only to effectively display information +about those keys. + +It is also possible to use both unicode or string literals and byte +literals with the fingerprint when getting a key in this way. + +.. _howto-import-key: + +Importing keys +-------------- + +Importing keys is possible with the ``key_import()`` method and takes +one argument which is a bytes literal object containing either the +binary or ASCII armoured key data for one or more keys. + +The following example retrieves one or more keys from the SKS keyservers +via the web using the requests module. Since requests returns the +content as a bytes literal object, we can then use that directly to +import the resulting data into our keybox. + +.. code:: python + + import gpg + import os.path + import requests + + c = gpg.Context() + url = "https://sks-keyservers.net/pks/lookup" + pattern = input("Enter the pattern to search for key or user IDs: ") + payload = {"op": "get", "search": pattern} + + r = requests.get(url, verify=True, params=payload) + result = c.key_import(r.content) + + if result is not None and hasattr(result, "considered") is False: + print(result) + elif result is not None and hasattr(result, "considered") is True: + num_keys = len(result.imports) + new_revs = result.new_revocations + new_sigs = result.new_signatures + new_subs = result.new_sub_keys + new_uids = result.new_user_ids + new_scrt = result.secret_imported + nochange = result.unchanged + print(""" + The total number of keys considered for import was: {0} + + Number of keys revoked: {1} + Number of new signatures: {2} + Number of new subkeys: {3} + Number of new user IDs: {4} + Number of new secret keys: {5} + Number of unchanged keys: {6} + + The key IDs for all considered keys were: + """.format(num_keys, new_revs, new_sigs, new_subs, new_uids, new_scrt, + nochange)) + for i in range(num_keys): + print("{0}\n".format(result.imports[i].fpr)) + else: + pass + +NOTE: When searching for a key ID of any length or a fingerprint +(without spaces), the SKS servers require the the leading ``0x`` +indicative of hexadecimal be included. Also note that the old short key +IDs (e.g. ``0xDEADBEEF``) should no longer be used due to the relative +ease by which such key IDs can be reproduced, as demonstrated by the +Evil32 Project in 2014 (which was subsequently exploited in 2016). + +.. _import-protonmail: + +Working with ProtonMail +~~~~~~~~~~~~~~~~~~~~~~~ + +Here is a variation on the example above which checks the constrained +ProtonMail keyserver for ProtonMail public keys. + +.. code:: python + + import gpg + import requests + import sys + + print(""" + This script searches the ProtonMail key server for the specified key and + imports it. + """) + + c = gpg.Context(armor=True) + url = "https://api.protonmail.ch/pks/lookup" + ksearch = [] + + if len(sys.argv) >= 2: + keyterm = sys.argv[1] + else: + keyterm = input("Enter the key ID, UID or search string: ") + + if keyterm.count("@") == 2 and keyterm.startswith("@") is True: + ksearch.append(keyterm[1:]) + ksearch.append(keyterm[1:]) + ksearch.append(keyterm[1:]) + elif keyterm.count("@") == 1 and keyterm.startswith("@") is True: + ksearch.append("{0}@protonmail.com".format(keyterm[1:])) + ksearch.append("{0}@protonmail.ch".format(keyterm[1:])) + ksearch.append("{0}@pm.me".format(keyterm[1:])) + elif keyterm.count("@") == 0: + ksearch.append("{0}@protonmail.com".format(keyterm)) + ksearch.append("{0}@protonmail.ch".format(keyterm)) + ksearch.append("{0}@pm.me".format(keyterm)) + elif keyterm.count("@") == 2 and keyterm.startswith("@") is False: + uidlist = keyterm.split("@") + for uid in uidlist: + ksearch.append("{0}@protonmail.com".format(uid)) + ksearch.append("{0}@protonmail.ch".format(uid)) + ksearch.append("{0}@pm.me".format(uid)) + elif keyterm.count("@") > 2: + uidlist = keyterm.split("@") + for uid in uidlist: + ksearch.append("{0}@protonmail.com".format(uid)) + ksearch.append("{0}@protonmail.ch".format(uid)) + ksearch.append("{0}@pm.me".format(uid)) + else: + ksearch.append(keyterm) + + for k in ksearch: + payload = {"op": "get", "search": k} + try: + r = requests.get(url, verify=True, params=payload) + if r.ok is True: + result = c.key_import(r.content) + elif r.ok is False: + result = r.content + except Exception as e: + result = None + + if result is not None and hasattr(result, "considered") is False: + print("{0} for {1}".format(result.decode(), k)) + elif result is not None and hasattr(result, "considered") is True: + num_keys = len(result.imports) + new_revs = result.new_revocations + new_sigs = result.new_signatures + new_subs = result.new_sub_keys + new_uids = result.new_user_ids + new_scrt = result.secret_imported + nochange = result.unchanged + print(""" + The total number of keys considered for import was: {0} + + With UIDs wholely or partially matching the following string: + + {1} + + Number of keys revoked: {2} + Number of new signatures: {3} + Number of new subkeys: {4} + Number of new user IDs: {5} + Number of new secret keys: {6} + Number of unchanged keys: {7} + + The key IDs for all considered keys were: + """.format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt, + nochange)) + for i in range(num_keys): + print(result.imports[i].fpr) + print("") + elif result is None: + print(e) + +Both the above example, +`pmkey-import.py <../examples/howto/pmkey-import.py>`__, and a version +which prompts for an alternative GnuPG home directory, +`pmkey-import-alt.py <../examples/howto/pmkey-import-alt.py>`__, are +available with the other examples and are executable scripts. + +Note that while the ProtonMail servers are based on the SKS servers, +their server is related more to their API and is not feature complete by +comparison to the servers in the SKS pool. One notable difference being +that the ProtonMail server does not permit non ProtonMail users to +update their own keys, which could be a vector for attacking ProtonMail +users who may not receive a key\'s revocation if it had been +compromised. + +.. _import-hkp4py: + +Importing with HKP for Python +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Performing the same tasks with the `hkp4py +module <https://github.com/Selfnet/hkp4py>`__ (available via PyPI) is +not too much different, but does provide a number of options of benefit +to end users. Not least of which being the ability to perform some +checks on a key before importing it or not. For instance it may be the +policy of a site or project to only import keys which have not been +revoked. The hkp4py module permits such checks prior to the importing of +the keys found. + +.. code:: python + + import gpg + import hkp4py + import sys + + c = gpg.Context() + server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net") + results = [] + + if len(sys.argv) > 2: + pattern = " ".join(sys.argv[1:]) + elif len(sys.argv) == 2: + pattern = sys.argv[1] + else: + pattern = input("Enter the pattern to search for keys or user IDs: ") + + try: + keys = server.search(pattern) + print("Found {0} key(s).".format(len(keys))) + except Exception as e: + keys = [] + for logrus in pattern.split(): + if logrus.startswith("0x") is True: + key = server.search(logrus) + else: + key = server.search("0x{0}".format(logrus)) + keys.append(key[0]) + print("Found {0} key(s).".format(len(keys))) + + for key in keys: + import_result = c.key_import(key.key_blob) + results.append(import_result) + + for result in results: + if result is not None and hasattr(result, "considered") is False: + print(result) + elif result is not None and hasattr(result, "considered") is True: + num_keys = len(result.imports) + new_revs = result.new_revocations + new_sigs = result.new_signatures + new_subs = result.new_sub_keys + new_uids = result.new_user_ids + new_scrt = result.secret_imported + nochange = result.unchanged + print(""" + The total number of keys considered for import was: {0} + + Number of keys revoked: {1} + Number of new signatures: {2} + Number of new subkeys: {3} + Number of new user IDs: {4} + Number of new secret keys: {5} + Number of unchanged keys: {6} + + The key IDs for all considered keys were: + """.format(num_keys, new_revs, new_sigs, new_subs, new_uids, new_scrt, + nochange)) + for i in range(num_keys): + print(result.imports[i].fpr) + print("") + else: + pass + +Since the hkp4py module handles multiple keys just as effectively as one +(``keys`` is a list of responses per matching key), the example above is +able to do a little bit more with the returned data before anything is +actually imported. + +.. _import-protonmail-hkp4py: + +Importing from ProtonMail with HKP for Python +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Though this can provide certain benefits even when working with +ProtonMail, the scope is somewhat constrained there due to the +limitations of the ProtonMail keyserver. + +For instance, searching the SKS keyserver pool for the term \"gnupg\" +produces hundreds of results from any time the word appears in any part +of a user ID. Performing the same search on the ProtonMail keyserver +returns zero results, even though there are at least two test accounts +which include it as part of the username. + +The cause of this discrepancy is the deliberate configuration of that +server by ProtonMail to require an exact match of the full email address +of the ProtonMail user whose key is being requested. Presumably this is +intended to reduce breaches of privacy of their users as an email +address must already be known before a key for that address can be +obtained. + +#. Import from ProtonMail via HKP for Python Example no. 1 + + The following script is avalable with the rest of the examples under + the somewhat less than original name, ``pmkey-import-hkp.py``. + + .. code:: python + + import gpg + import hkp4py + import os.path + import sys + + print(""" + This script searches the ProtonMail key server for the specified key and + imports it. + + Usage: pmkey-import-hkp.py [search strings] + """) + + c = gpg.Context(armor=True) + server = hkp4py.KeyServer("hkps://api.protonmail.ch") + keyterms = [] + ksearch = [] + allkeys = [] + results = [] + paradox = [] + homeless = None + + if len(sys.argv) > 2: + keyterms = sys.argv[1:] + elif len(sys.argv) == 2: + keyterm = sys.argv[1] + keyterms.append(keyterm) + else: + key_term = input("Enter the key ID, UID or search string: ") + keyterms = key_term.split() + + for keyterm in keyterms: + if keyterm.count("@") == 2 and keyterm.startswith("@") is True: + ksearch.append(keyterm[1:]) + ksearch.append(keyterm[1:]) + ksearch.append(keyterm[1:]) + elif keyterm.count("@") == 1 and keyterm.startswith("@") is True: + ksearch.append("{0}@protonmail.com".format(keyterm[1:])) + ksearch.append("{0}@protonmail.ch".format(keyterm[1:])) + ksearch.append("{0}@pm.me".format(keyterm[1:])) + elif keyterm.count("@") == 0: + ksearch.append("{0}@protonmail.com".format(keyterm)) + ksearch.append("{0}@protonmail.ch".format(keyterm)) + ksearch.append("{0}@pm.me".format(keyterm)) + elif keyterm.count("@") == 2 and keyterm.startswith("@") is False: + uidlist = keyterm.split("@") + for uid in uidlist: + ksearch.append("{0}@protonmail.com".format(uid)) + ksearch.append("{0}@protonmail.ch".format(uid)) + ksearch.append("{0}@pm.me".format(uid)) + elif keyterm.count("@") > 2: + uidlist = keyterm.split("@") + for uid in uidlist: + ksearch.append("{0}@protonmail.com".format(uid)) + ksearch.append("{0}@protonmail.ch".format(uid)) + ksearch.append("{0}@pm.me".format(uid)) + else: + ksearch.append(keyterm) + + for k in ksearch: + print("Checking for key for: {0}".format(k)) + try: + keys = server.search(k) + if isinstance(keys, list) is True: + for key in keys: + allkeys.append(key) + try: + import_result = c.key_import(key.key_blob) + except Exception as e: + import_result = c.key_import(key.key) + else: + paradox.append(keys) + import_result = None + except Exception as e: + import_result = None + results.append(import_result) + + for result in results: + if result is not None and hasattr(result, "considered") is False: + print("{0} for {1}".format(result.decode(), k)) + elif result is not None and hasattr(result, "considered") is True: + num_keys = len(result.imports) + new_revs = result.new_revocations + new_sigs = result.new_signatures + new_subs = result.new_sub_keys + new_uids = result.new_user_ids + new_scrt = result.secret_imported + nochange = result.unchanged + print(""" + The total number of keys considered for import was: {0} + + With UIDs wholely or partially matching the following string: + + {1} + + Number of keys revoked: {2} + Number of new signatures: {3} + Number of new subkeys: {4} + Number of new user IDs: {5} + Number of new secret keys: {6} + Number of unchanged keys: {7} + + The key IDs for all considered keys were: + """.format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt, + nochange)) + for i in range(num_keys): + print(result.imports[i].fpr) + print("") + elif result is None: + pass + +#. Import from ProtonMail via HKP for Python Example no. 2 + + Like its counterpart above, this script can also be found with the + rest of the examples, by the name pmkey-import-hkp-alt.py. + + With this script a modicum of effort has been made to treat anything + passed as a ``homedir`` which either does not exist or which is not a + directory, as also being a pssible user ID to check for. It\'s not + guaranteed to pick up on all such cases, but it should cover most of + them. + + .. code:: python + + import gpg + import hkp4py + import os.path + import sys + + print(""" + This script searches the ProtonMail key server for the specified key and + imports it. Optionally enables specifying a different GnuPG home directory. + + Usage: pmkey-import-hkp.py [homedir] [search string] + or: pmkey-import-hkp.py [search string] + """) + + c = gpg.Context(armor=True) + server = hkp4py.KeyServer("hkps://api.protonmail.ch") + keyterms = [] + ksearch = [] + allkeys = [] + results = [] + paradox = [] + homeless = None + + if len(sys.argv) > 3: + homedir = sys.argv[1] + keyterms = sys.argv[2:] + elif len(sys.argv) == 3: + homedir = sys.argv[1] + keyterm = sys.argv[2] + keyterms.append(keyterm) + elif len(sys.argv) == 2: + homedir = "" + keyterm = sys.argv[1] + keyterms.append(keyterm) + else: + keyterm = input("Enter the key ID, UID or search string: ") + homedir = input("Enter the GPG configuration directory path (optional): ") + keyterms.append(keyterm) + + if len(homedir) == 0: + homedir = None + homeless = False + + if homedir is not None: + if homedir.startswith("~"): + if os.path.exists(os.path.expanduser(homedir)) is True: + if os.path.isdir(os.path.expanduser(homedir)) is True: + c.home_dir = os.path.realpath(os.path.expanduser(homedir)) + else: + homeless = True + else: + homeless = True + elif os.path.exists(os.path.realpath(homedir)) is True: + if os.path.isdir(os.path.realpath(homedir)) is True: + c.home_dir = os.path.realpath(homedir) + else: + homeless = True + else: + homeless = True + + # First check to see if the homedir really is a homedir and if not, treat it as + # a search string. + if homeless is True: + keyterms.append(homedir) + c.home_dir = None + else: + pass + + for keyterm in keyterms: + if keyterm.count("@") == 2 and keyterm.startswith("@") is True: + ksearch.append(keyterm[1:]) + ksearch.append(keyterm[1:]) + ksearch.append(keyterm[1:]) + elif keyterm.count("@") == 1 and keyterm.startswith("@") is True: + ksearch.append("{0}@protonmail.com".format(keyterm[1:])) + ksearch.append("{0}@protonmail.ch".format(keyterm[1:])) + ksearch.append("{0}@pm.me".format(keyterm[1:])) + elif keyterm.count("@") == 0: + ksearch.append("{0}@protonmail.com".format(keyterm)) + ksearch.append("{0}@protonmail.ch".format(keyterm)) + ksearch.append("{0}@pm.me".format(keyterm)) + elif keyterm.count("@") == 2 and keyterm.startswith("@") is False: + uidlist = keyterm.split("@") + for uid in uidlist: + ksearch.append("{0}@protonmail.com".format(uid)) + ksearch.append("{0}@protonmail.ch".format(uid)) + ksearch.append("{0}@pm.me".format(uid)) + elif keyterm.count("@") > 2: + uidlist = keyterm.split("@") + for uid in uidlist: + ksearch.append("{0}@protonmail.com".format(uid)) + ksearch.append("{0}@protonmail.ch".format(uid)) + ksearch.append("{0}@pm.me".format(uid)) + else: + ksearch.append(keyterm) + + for k in ksearch: + print("Checking for key for: {0}".format(k)) + try: + keys = server.search(k) + if isinstance(keys, list) is True: + for key in keys: + allkeys.append(key) + try: + import_result = c.key_import(key.key_blob) + except Exception as e: + import_result = c.key_import(key.key) + else: + paradox.append(keys) + import_result = None + except Exception as e: + import_result = None + results.append(import_result) + + for result in results: + if result is not None and hasattr(result, "considered") is False: + print("{0} for {1}".format(result.decode(), k)) + elif result is not None and hasattr(result, "considered") is True: + num_keys = len(result.imports) + new_revs = result.new_revocations + new_sigs = result.new_signatures + new_subs = result.new_sub_keys + new_uids = result.new_user_ids + new_scrt = result.secret_imported + nochange = result.unchanged + print(""" + The total number of keys considered for import was: {0} + + With UIDs wholely or partially matching the following string: + + {1} + + Number of keys revoked: {2} + Number of new signatures: {3} + Number of new subkeys: {4} + Number of new user IDs: {5} + Number of new secret keys: {6} + Number of unchanged keys: {7} + + The key IDs for all considered keys were: + """.format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt, + nochange)) + for i in range(num_keys): + print(result.imports[i].fpr) + print("") + elif result is None: + pass + +.. _howto-export-key: + +Exporting keys +-------------- + +Exporting keys remains a reasonably simple task, but has been separated +into three different functions for the OpenPGP cryptographic engine. Two +of those functions are for exporting public keys and the third is for +exporting secret keys. + +.. _howto-export-public-key: + +Exporting public keys +~~~~~~~~~~~~~~~~~~~~~ + +There are two methods of exporting public keys, both of which are very +similar to the other. The default method, ``key_export()``, will export +a public key or keys matching a specified pattern as normal. The +alternative, the ``key_export_minimal()`` method, will do the same thing +except producing a minimised output with extra signatures and third +party signatures or certifications removed. + +.. code:: python + + import gpg + import os.path + import sys + + print(""" + This script exports one or more public keys. + """) + + c = gpg.Context(armor=True) + + if len(sys.argv) >= 4: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = sys.argv[3] + elif len(sys.argv) == 3: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = input("Enter the GPG configuration directory path (optional): ") + elif len(sys.argv) == 2: + keyfile = sys.argv[1] + logrus = input("Enter the UID matching the key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") + else: + keyfile = input("Enter the path and filename to save the secret key to: ") + logrus = input("Enter the UID matching the key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") + + if homedir.startswith("~"): + if os.path.exists(os.path.expanduser(homedir)) is True: + c.home_dir = os.path.expanduser(homedir) + else: + pass + elif os.path.exists(homedir) is True: + c.home_dir = homedir + else: + pass + + try: + result = c.key_export(pattern=logrus) + except: + result = c.key_export(pattern=None) + + if result is not None: + with open(keyfile, "wb") as f: + f.write(result) + else: + pass + +It should be noted that the result will only return ``None`` when a +search pattern has been entered, but has not matched any keys. When the +search pattern itself is set to ``None`` this triggers the exporting of +the entire public keybox. + +.. code:: python + + import gpg + import os.path + import sys + + print(""" + This script exports one or more public keys in minimised form. + """) + + c = gpg.Context(armor=True) + + if len(sys.argv) >= 4: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = sys.argv[3] + elif len(sys.argv) == 3: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = input("Enter the GPG configuration directory path (optional): ") + elif len(sys.argv) == 2: + keyfile = sys.argv[1] + logrus = input("Enter the UID matching the key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") + else: + keyfile = input("Enter the path and filename to save the secret key to: ") + logrus = input("Enter the UID matching the key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") + + if homedir.startswith("~"): + if os.path.exists(os.path.expanduser(homedir)) is True: + c.home_dir = os.path.expanduser(homedir) + else: + pass + elif os.path.exists(homedir) is True: + c.home_dir = homedir + else: + pass + + try: + result = c.key_export_minimal(pattern=logrus) + except: + result = c.key_export_minimal(pattern=None) + + if result is not None: + with open(keyfile, "wb") as f: + f.write(result) + else: + pass + +.. _howto-export-secret-key: + +Exporting secret keys +~~~~~~~~~~~~~~~~~~~~~ + +Exporting secret keys is, functionally, very similar to exporting public +keys; save for the invocation of ``pinentry`` via ``gpg-agent`` in order +to securely enter the key\'s passphrase and authorise the export. + +The following example exports the secret key to a file which is then set +with the same permissions as the output files created by the command +line secret key export options. + +.. code:: python + + import gpg + import os + import os.path + import sys + + print(""" + This script exports one or more secret keys. + + The gpg-agent and pinentry are invoked to authorise the export. + """) + + c = gpg.Context(armor=True) + + if len(sys.argv) >= 4: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = sys.argv[3] + elif len(sys.argv) == 3: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = input("Enter the GPG configuration directory path (optional): ") + elif len(sys.argv) == 2: + keyfile = sys.argv[1] + logrus = input("Enter the UID matching the secret key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") + else: + keyfile = input("Enter the path and filename to save the secret key to: ") + logrus = input("Enter the UID matching the secret key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") + + if len(homedir) == 0: + homedir = None + elif homedir.startswith("~"): + userdir = os.path.expanduser(homedir) + if os.path.exists(userdir) is True: + homedir = os.path.realpath(userdir) + else: + homedir = None + else: + homedir = os.path.realpath(homedir) + + if os.path.exists(homedir) is False: + homedir = None + else: + if os.path.isdir(homedir) is False: + homedir = None + else: + pass + + if homedir is not None: + c.home_dir = homedir + else: + pass + + try: + result = c.key_export_secret(pattern=logrus) + except: + result = c.key_export_secret(pattern=None) + + if result is not None: + with open(keyfile, "wb") as f: + f.write(result) + os.chmod(keyfile, 0o600) + else: + pass + +Alternatively the approach of the following script can be used. This +longer example saves the exported secret key(s) in files in the GnuPG +home directory, in addition to setting the file permissions as only +readable and writable by the user. It also exports the secret key(s) +twice in order to output both GPG binary (``.gpg``) and ASCII armoured +(``.asc``) files. + +.. code:: python + + import gpg + import os + import os.path + import subprocess + import sys + + print(""" + This script exports one or more secret keys as both ASCII armored and binary + file formats, saved in files within the user's GPG home directory. + + The gpg-agent and pinentry are invoked to authorise the export. + """) + + if sys.platform == "win32": + gpgconfcmd = "gpgconf.exe --list-dirs homedir" + else: + gpgconfcmd = "gpgconf --list-dirs homedir" + + a = gpg.Context(armor=True) + b = gpg.Context() + c = gpg.Context() + + if len(sys.argv) >= 4: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = sys.argv[3] + elif len(sys.argv) == 3: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = input("Enter the GPG configuration directory path (optional): ") + elif len(sys.argv) == 2: + keyfile = sys.argv[1] + logrus = input("Enter the UID matching the secret key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") + else: + keyfile = input("Enter the filename to save the secret key to: ") + logrus = input("Enter the UID matching the secret key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") + + if len(homedir) == 0: + homedir = None + elif homedir.startswith("~"): + userdir = os.path.expanduser(homedir) + if os.path.exists(userdir) is True: + homedir = os.path.realpath(userdir) + else: + homedir = None + else: + homedir = os.path.realpath(homedir) + + if os.path.exists(homedir) is False: + homedir = None + else: + if os.path.isdir(homedir) is False: + homedir = None + else: + pass + + if homedir is not None: + c.home_dir = homedir + else: + pass + + if c.home_dir is not None: + if c.home_dir.endswith("/"): + gpgfile = "{0}{1}.gpg".format(c.home_dir, keyfile) + ascfile = "{0}{1}.asc".format(c.home_dir, keyfile) + else: + gpgfile = "{0}/{1}.gpg".format(c.home_dir, keyfile) + ascfile = "{0}/{1}.asc".format(c.home_dir, keyfile) + else: + if os.path.exists(os.environ["GNUPGHOME"]) is True: + hd = os.environ["GNUPGHOME"] + else: + try: + hd = subprocess.getoutput(gpgconfcmd) + except: + process = subprocess.Popen(gpgconfcmd.split(), + stdout=subprocess.PIPE) + procom = process.communicate() + if sys.version_info[0] == 2: + hd = procom[0].strip() + else: + hd = procom[0].decode().strip() + gpgfile = "{0}/{1}.gpg".format(hd, keyfile) + ascfile = "{0}/{1}.asc".format(hd, keyfile) + + try: + a_result = a.key_export_secret(pattern=logrus) + b_result = b.key_export_secret(pattern=logrus) + except: + a_result = a.key_export_secret(pattern=None) + b_result = b.key_export_secret(pattern=None) + + if a_result is not None: + with open(ascfile, "wb") as f: + f.write(a_result) + os.chmod(ascfile, 0o600) + else: + pass + + if b_result is not None: + with open(gpgfile, "wb") as f: + f.write(b_result) + os.chmod(gpgfile, 0o600) + else: + pass + +.. _howto-send-public-key: + +Sending public keys to the SKS Keyservers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +As with the previous section on importing keys, the ``hkp4py`` module +adds another option with exporting keys in order to send them to the +public keyservers. + +The following example demonstrates how this may be done. + +.. code:: python + + import gpg + import hkp4py + import os.path + import sys + + print(""" + This script sends one or more public keys to the SKS keyservers and is + essentially a slight variation on the export-key.py script. + """) + + c = gpg.Context(armor=True) + server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net") + + if len(sys.argv) > 2: + logrus = " ".join(sys.argv[1:]) + elif len(sys.argv) == 2: + logrus = sys.argv[1] + else: + logrus = input("Enter the UID matching the key(s) to send: ") + + if len(logrus) > 0: + try: + export_result = c.key_export(pattern=logrus) + except Exception as e: + print(e) + export_result = None + else: + export_result = c.key_export(pattern=None) + + if export_result is not None: + try: + try: + send_result = server.add(export_result) + except: + send_result = server.add(export_result.decode()) + if send_result is not None: + print(send_result) + else: + pass + except Exception as e: + print(e) + else: + pass + +An expanded version of this script with additional functions for +specifying an alternative homedir location is in the examples directory +as ``send-key-to-keyserver.py``. + +The ``hkp4py`` module appears to handle both string and byte literal +text data equally well, but the GPGME bindings deal primarily with byte +literal data only and so this script sends in that format first, then +tries the string literal form. + +.. _howto-the-basics: + +Basic Functions +=============== + +The most frequently called features of any cryptographic library will be +the most fundamental tasks for encryption software. In this section we +will look at how to programmatically encrypt data, decrypt it, sign it +and verify signatures. + +.. _howto-basic-encryption: + +Encryption +---------- + +Encrypting is very straight forward. In the first example below the +message, ``text``, is encrypted to a single recipient\'s key. In the +second example the message will be encrypted to multiple recipients. + +.. _howto-basic-encryption-single: + +Encrypting to one key +~~~~~~~~~~~~~~~~~~~~~ + +Once the the Context is set the main issues with encrypting data is +essentially reduced to key selection and the keyword arguments specified +in the ``gpg.Context().encrypt()`` method. + +Those keyword arguments are: ``recipients``, a list of keys encrypted to +(covered in greater detail in the following section); ``sign``, whether +or not to sign the plaintext data, see subsequent sections on signing +and verifying signatures below (defaults to ``True``); ``sink``, to +write results or partial results to a secure sink instead of returning +it (defaults to ``None``); ``passphrase``, only used when utilising +symmetric encryption (defaults to ``None``); ``always_trust``, used to +override the trust model settings for recipient keys (defaults to +``False``); ``add_encrypt_to``, utilises any preconfigured +``encrypt-to`` or ``default-key`` settings in the user\'s ``gpg.conf`` +file (defaults to ``False``); ``prepare``, prepare for encryption +(defaults to ``False``); ``expect_sign``, prepare for signing (defaults +to ``False``); ``compress``, compresses the plaintext prior to +encryption (defaults to ``True``). + +.. code:: python + + import gpg + + a_key = "0x12345678DEADBEEF" + text = b"""Some text to test with. + + Since the text in this case must be bytes, it is most likely that + the input form will be a separate file which is opened with "rb" + as this is the simplest method of obtaining the correct data format. + """ + + c = gpg.Context(armor=True) + rkey = list(c.keylist(pattern=a_key, secret=False)) + ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=False) + + with open("secret_plans.txt.asc", "wb") as afile: + afile.write(ciphertext) + +Though this is even more likely to be used like this; with the plaintext +input read from a file, the recipient keys used for encryption +regardless of key trust status and the encrypted output also encrypted +to any preconfigured keys set in the ``gpg.conf`` file: + +.. code:: python + + import gpg + + a_key = "0x12345678DEADBEEF" + + with open("secret_plans.txt", "rb") as afile: + text = afile.read() + + c = gpg.Context(armor=True) + rkey = list(c.keylist(pattern=a_key, secret=False)) + ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=True, + always_trust=True, + add_encrypt_to=True) + + with open("secret_plans.txt.asc", "wb") as afile: + afile.write(ciphertext) + +If the ``recipients`` paramater is empty then the plaintext is encrypted +symmetrically. If no ``passphrase`` is supplied as a parameter or via a +callback registered with the ``Context()`` then an out-of-band prompt +for the passphrase via pinentry will be invoked. + +.. _howto-basic-encryption-multiple: + +Encrypting to multiple keys +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Encrypting to multiple keys essentially just expands upon the key +selection process and the recipients from the previous examples. + +The following example encrypts a message (``text``) to everyone with an +email address on the ``gnupg.org`` domain, [4]_ but does *not* encrypt +to a default key or other key which is configured to normally encrypt +to. + +.. code:: python + + import gpg + + text = b"""Oh look, another test message. + + The same rules apply as with the previous example and more likely + than not, the message will actually be drawn from reading the + contents of a file or, maybe, from entering data at an input() + prompt. + + Since the text in this case must be bytes, it is most likely that + the input form will be a separate file which is opened with "rb" + as this is the simplest method of obtaining the correct data + format. + """ + + c = gpg.Context(armor=True) + rpattern = list(c.keylist(pattern="@gnupg.org", secret=False)) + logrus = [] + + for i in range(len(rpattern)): + if rpattern[i].can_encrypt == 1: + logrus.append(rpattern[i]) + + ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, + sign=False, always_trust=True) + + with open("secret_plans.txt.asc", "wb") as afile: + afile.write(ciphertext) + +All it would take to change the above example to sign the message and +also encrypt the message to any configured default keys would be to +change the ``c.encrypt`` line to this: + +.. code:: python + + ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, + always_trust=True, + add_encrypt_to=True) + +The only keyword arguments requiring modification are those for which +the default values are changing. The default value of ``sign`` is +``True``, the default of ``always_trust`` is ``False``, the default of +``add_encrypt_to`` is ``False``. + +If ``always_trust`` is not set to ``True`` and any of the recipient keys +are not trusted (e.g. not signed or locally signed) then the encryption +will raise an error. It is possible to mitigate this somewhat with +something more like this: + +.. code:: python + + import gpg + + with open("secret_plans.txt.asc", "rb") as afile: + text = afile.read() + + c = gpg.Context(armor=True) + rpattern = list(c.keylist(pattern="@gnupg.org", secret=False)) + logrus = [] + + for i in range(len(rpattern)): + if rpattern[i].can_encrypt == 1: + logrus.append(rpattern[i]) + + try: + ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, + add_encrypt_to=True) + except gpg.errors.InvalidRecipients as e: + for i in range(len(e.recipients)): + for n in range(len(logrus)): + if logrus[n].fpr == e.recipients[i].fpr: + logrus.remove(logrus[n]) + else: + pass + try: + ciphertext, result, sign_result = c.encrypt(text, + recipients=logrus, + add_encrypt_to=True) + with open("secret_plans.txt.asc", "wb") as afile: + afile.write(ciphertext) + except: + pass + +This will attempt to encrypt to all the keys searched for, then remove +invalid recipients if it fails and try again. + +.. _howto-basic-decryption: + +Decryption +---------- + +Decrypting something encrypted to a key in one\'s secret keyring is +fairly straight forward. + +In this example code, however, preconfiguring either ``gpg.Context()`` +or ``gpg.core.Context()`` as ``c`` is unnecessary because there is no +need to modify the Context prior to conducting the decryption and since +the Context is only used once, setting it to ``c`` simply adds lines for +no gain. + +.. code:: python + + import gpg + + ciphertext = input("Enter path and filename of encrypted file: ") + newfile = input("Enter path and filename of file to save decrypted data to: ") + + with open(ciphertext, "rb") as cfile: + try: + plaintext, result, verify_result = gpg.Context().decrypt(cfile) + except gpg.errors.GPGMEError as e: + plaintext = None + print(e) + + if plaintext is not None: + with open(newfile, "wb") as nfile: + nfile.write(plaintext) + else: + pass + +The data available in ``plaintext`` in this example is the decrypted +content as a byte object, the recipient key IDs and algorithms in +``result`` and the results of verifying any signatures of the data in +``verify_result``. + +.. _howto-basic-signing: + +Signing text and files +---------------------- + +The following sections demonstrate how to specify keys to sign with. + +.. _howto-basic-signing-signers: + +Signing key selection +~~~~~~~~~~~~~~~~~~~~~ + +By default GPGME and the Python bindings will use the default key +configured for the user invoking the GPGME API. If there is no default +key specified and there is more than one secret key available it may be +necessary to specify the key or keys with which to sign messages and +files. + +.. code:: python + + import gpg + + logrus = input("Enter the email address or string to match signing keys to: ") + hancock = gpg.Context().keylist(pattern=logrus, secret=True) + sig_src = list(hancock) + +The signing examples in the following sections include the explicitly +designated ``signers`` parameter in two of the five examples; once where +the resulting signature would be ASCII armoured and once where it would +not be armoured. + +While it would be possible to enter a key ID or fingerprint here to +match a specific key, it is not possible to enter two fingerprints and +match two keys since the patten expects a string, bytes or None and not +a list. A string with two fingerprints won\'t match any single key. + +.. _howto-basic-signing-normal: + +Normal or default signing messages or files +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The normal or default signing process is essentially the same as is most +often invoked when also encrypting a message or file. So when the +encryption component is not utilised, the result is to produce an +encoded and signed output which may or may not be ASCII armoured and +which may or may not also be compressed. + +By default compression will be used unless GnuPG detects that the +plaintext is already compressed. ASCII armouring will be determined +according to the value of ``gpg.Context().armor``. + +The compression algorithm is selected in much the same way as the +symmetric encryption algorithm or the hash digest algorithm is when +multiple keys are involved; from the preferences saved into the key +itself or by comparison with the preferences with all other keys +involved. + +.. code:: python + + import gpg + + text0 = """Declaration of ... something. + + """ + text = text0.encode() + + c = gpg.Context(armor=True, signers=sig_src) + signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL) + + with open("/path/to/statement.txt.asc", "w") as afile: + afile.write(signed_data.decode()) + +Though everything in this example is accurate, it is more likely that +reading the input data from another file and writing the result to a new +file will be performed more like the way it is done in the next example. +Even if the output format is ASCII armoured. + +.. code:: python + + import gpg + + with open("/path/to/statement.txt", "rb") as tfile: + text = tfile.read() + + c = gpg.Context() + signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL) + + with open("/path/to/statement.txt.sig", "wb") as afile: + afile.write(signed_data) + +.. _howto-basic-signing-detached: + +Detached signing messages and files +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Detached signatures will often be needed in programmatic uses of GPGME, +either for signing files (e.g. tarballs of code releases) or as a +component of message signing (e.g. PGP/MIME encoded email). + +.. code:: python + + import gpg + + text0 = """Declaration of ... something. + + """ + text = text0.encode() + + c = gpg.Context(armor=True) + signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH) + + with open("/path/to/statement.txt.asc", "w") as afile: + afile.write(signed_data.decode()) + +As with normal signatures, detached signatures are best handled as byte +literals, even when the output is ASCII armoured. + +.. code:: python + + import gpg + + with open("/path/to/statement.txt", "rb") as tfile: + text = tfile.read() + + c = gpg.Context(signers=sig_src) + signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH) + + with open("/path/to/statement.txt.sig", "wb") as afile: + afile.write(signed_data) + +.. _howto-basic-signing-clear: + +Clearsigning messages or text +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Though PGP/in-line messages are no longer encouraged in favour of +PGP/MIME, there is still sometimes value in utilising in-line +signatures. This is where clear-signed messages or text is of value. + +.. code:: python + + import gpg + + text0 = """Declaration of ... something. + + """ + text = text0.encode() + + c = gpg.Context() + signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR) + + with open("/path/to/statement.txt.asc", "w") as afile: + afile.write(signed_data.decode()) + +In spite of the appearance of a clear-signed message, the data handled +by GPGME in signing it must still be byte literals. + +.. code:: python + + import gpg + + with open("/path/to/statement.txt", "rb") as tfile: + text = tfile.read() + + c = gpg.Context() + signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR) + + with open("/path/to/statement.txt.asc", "wb") as afile: + afile.write(signed_data) + +.. _howto-basic-verification: + +Signature verification +---------------------- + +Essentially there are two principal methods of verification of a +signature. The first of these is for use with the normal or default +signing method and for clear-signed messages. The second is for use with +files and data with detached signatures. + +The following example is intended for use with the default signing +method where the file was not ASCII armoured: + +.. code:: python + + import gpg + import time + + filename = "statement.txt" + gpg_file = "statement.txt.gpg" + + c = gpg.Context() + + try: + data, result = c.verify(open(gpg_file)) + verified = True + except gpg.errors.BadSignatures as e: + verified = False + print(e) + + if verified is True: + for i in range(len(result.signatures)): + sign = result.signatures[i] + print("""Good signature from: + {0} + with key {1} + made at {2} + """.format(c.get_key(sign.fpr).uids[0].uid, sign.fpr, + time.ctime(sign.timestamp))) + else: + pass + +Whereas this next example, which is almost identical would work with +normal ASCII armoured files and with clear-signed files: + +.. code:: python + + import gpg + import time + + filename = "statement.txt" + asc_file = "statement.txt.asc" + + c = gpg.Context() + + try: + data, result = c.verify(open(asc_file)) + verified = True + except gpg.errors.BadSignatures as e: + verified = False + print(e) + + if verified is True: + for i in range(len(result.signatures)): + sign = result.signatures[i] + print("""Good signature from: + {0} + with key {1} + made at {2} + """.format(c.get_key(sign.fpr).uids[0].uid, sign.fpr, + time.ctime(sign.timestamp))) + else: + pass + +In both of the previous examples it is also possible to compare the +original data that was signed against the signed data in ``data`` to see +if it matches with something like this: + +.. code:: python + + with open(filename, "rb") as afile: + text = afile.read() + + if text == data: + print("Good signature.") + else: + pass + +The following two examples, however, deal with detached signatures. With +his method of verification the data that was signed does not get +returned since it is already being explicitly referenced in the first +argument of ``c.verify``. So ``data`` is ``None`` and only the +information in ``result`` is available. + +.. code:: python + + import gpg + import time + + filename = "statement.txt" + sig_file = "statement.txt.sig" + + c = gpg.Context() + + try: + data, result = c.verify(open(filename), open(sig_file)) + verified = True + except gpg.errors.BadSignatures as e: + verified = False + print(e) + + if verified is True: + for i in range(len(result.signatures)): + sign = result.signatures[i] + print("""Good signature from: + {0} + with key {1} + made at {2} + """.format(c.get_key(sign.fpr).uids[0].uid, sign.fpr, + time.ctime(sign.timestamp))) + else: + pass + +.. code:: python + + import gpg + import time + + filename = "statement.txt" + asc_file = "statement.txt.asc" + + c = gpg.Context() + + try: + data, result = c.verify(open(filename), open(asc_file)) + verified = True + except gpg.errors.BadSignatures as e: + verified = False + print(e) + + if verified is True: + for i in range(len(result.signatures)): + sign = result.signatures[i] + print("""Good signature from: + {0} + with key {1} + made at {2} + """.format(c.get_key(sign.fpr).uids[0].uid, sign.fpr, + time.ctime(sign.timestamp))) + else: + pass + +.. _key-generation: + +Creating keys and subkeys +========================= + +The one thing, aside from GnuPG itself, that GPGME depends on, of +course, is the keys themselves. So it is necessary to be able to +generate them and modify them by adding subkeys, revoking or disabling +them, sometimes deleting them and doing the same for user IDs. + +In the following examples a key will be created for the world\'s +greatest secret agent, Danger Mouse. Since Danger Mouse is a secret +agent he needs to be able to protect information to ``SECRET`` level +clearance, so his keys will be 3072-bit keys. + +The pre-configured ``gpg.conf`` file which sets cipher, digest and other +preferences contains the following configuration parameters: + +.. code:: conf + + expert + allow-freeform-uid + allow-secret-key-import + trust-model tofu+pgp + tofu-default-policy unknown + enable-large-rsa + enable-dsa2 + cert-digest-algo SHA512 + default-preference-list TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1 ZLIB BZIP2 ZIP Uncompressed + personal-cipher-preferences TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES + personal-digest-preferences SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1 + personal-compress-preferences ZLIB BZIP2 ZIP Uncompressed + +.. _keygen-primary: + +Primary key +----------- + +Generating a primary key uses the ``create_key`` method in a Context. It +contains multiple arguments and keyword arguments, including: +``userid``, ``algorithm``, ``expires_in``, ``expires``, ``sign``, +``encrypt``, ``certify``, ``authenticate``, ``passphrase`` and +``force``. The defaults for all of those except ``userid``, +``algorithm``, ``expires_in``, ``expires`` and ``passphrase`` is +``False``. The defaults for ``algorithm`` and ``passphrase`` is +``None``. The default for ``expires_in`` is ``0``. The default for +``expires`` is ``True``. There is no default for ``userid``. + +If ``passphrase`` is left as ``None`` then the key will not be generated +with a passphrase, if ``passphrase`` is set to a string then that will +be the passphrase and if ``passphrase`` is set to ``True`` then +gpg-agent will launch pinentry to prompt for a passphrase. For the sake +of convenience, these examples will keep ``passphrase`` set to ``None``. + +.. code:: python + + import gpg + + c = gpg.Context() + + c.home_dir = "~/.gnupg-dm" + userid = "Danger Mouse <dm@secret.example.net>" + + dmkey = c.create_key(userid, algorithm="rsa3072", expires_in=31536000, + sign=True, certify=True) + +One thing to note here is the use of setting the ``c.home_dir`` +parameter. This enables generating the key or keys in a different +location. In this case to keep the new key data created for this example +in a separate location rather than adding it to existing and active key +store data. As with the default directory, ``~/.gnupg``, any temporary +or separate directory needs the permissions set to only permit access by +the directory owner. On posix systems this means setting the directory +permissions to 700. + +The ``temp-homedir-config.py`` script in the HOWTO examples directory +will create an alternative homedir with these configuration options +already set and the correct directory and file permissions. + +The successful generation of the key can be confirmed via the returned +``GenkeyResult`` object, which includes the following data: + +.. code:: python + + print(""" + Fingerprint: {0} + Primary Key: {1} + Public Key: {2} + Secret Key: {3} + Sub Key: {4} + User IDs: {5} + """.format(dmkey.fpr, dmkey.primary, dmkey.pubkey, dmkey.seckey, dmkey.sub, + dmkey.uid)) + +Alternatively the information can be confirmed using the command line +program: + +.. code:: shell + + bash-4.4$ gpg --homedir ~/.gnupg-dm -K + ~/.gnupg-dm/pubring.kbx + ---------------------- + sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15] + 177B7C25DB99745EE2EE13ED026D2F19E99E63AA + uid [ultimate] Danger Mouse <dm@secret.example.net> + + bash-4.4$ + +As with generating keys manually, to preconfigure expanded preferences +for the cipher, digest and compression algorithms, the ``gpg.conf`` file +must contain those details in the home directory in which the new key is +being generated. I used a cut down version of my own ``gpg.conf`` file +in order to be able to generate this: + +.. code:: shell + + bash-4.4$ gpg --homedir ~/.gnupg-dm --edit-key 177B7C25DB99745EE2EE13ED026D2F19E99E63AA showpref quit + Secret key is available. + + sec rsa3072/026D2F19E99E63AA + created: 2018-03-15 expires: 2019-03-15 usage: SC + trust: ultimate validity: ultimate + [ultimate] (1). Danger Mouse <dm@secret.example.net> + + [ultimate] (1). Danger Mouse <dm@secret.example.net> + Cipher: TWOFISH, CAMELLIA256, AES256, CAMELLIA192, AES192, CAMELLIA128, AES, BLOWFISH, IDEA, CAST5, 3DES + Digest: SHA512, SHA384, SHA256, SHA224, RIPEMD160, SHA1 + Compression: ZLIB, BZIP2, ZIP, Uncompressed + Features: MDC, Keyserver no-modify + + bash-4.4$ + +.. _keygen-subkeys: + +Subkeys +------- + +Adding subkeys to a primary key is fairly similar to creating the +primary key with the ``create_subkey`` method. Most of the arguments are +the same, but not quite all. Instead of the ``userid`` argument there is +now a ``key`` argument for selecting which primary key to add the subkey +to. + +In the following example an encryption subkey will be added to the +primary key. Since Danger Mouse is a security conscious secret agent, +this subkey will only be valid for about six months, half the length of +the primary key. + +.. code:: python + + import gpg + + c = gpg.Context() + c.home_dir = "~/.gnupg-dm" + + key = c.get_key(dmkey.fpr, secret=True) + dmsub = c.create_subkey(key, algorithm="rsa3072", expires_in=15768000, + encrypt=True) + +As with the primary key, the results here can be checked with: + +.. code:: python + + print(""" + Fingerprint: {0} + Primary Key: {1} + Public Key: {2} + Secret Key: {3} + Sub Key: {4} + User IDs: {5} + """.format(dmsub.fpr, dmsub.primary, dmsub.pubkey, dmsub.seckey, dmsub.sub, + dmsub.uid)) + +As well as on the command line with: + +.. code:: shell + + bash-4.4$ gpg --homedir ~/.gnupg-dm -K + ~/.gnupg-dm/pubring.kbx + ---------------------- + sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15] + 177B7C25DB99745EE2EE13ED026D2F19E99E63AA + uid [ultimate] Danger Mouse <dm@secret.example.net> + ssb rsa3072 2018-03-15 [E] [expires: 2018-09-13] + + bash-4.4$ + +.. _keygen-uids: + +User IDs +-------- + +.. _keygen-uids-add: + +Adding User IDs +~~~~~~~~~~~~~~~ + +By comparison to creating primary keys and subkeys, adding a new user ID +to an existing key is much simpler. The method used to do this is +``key_add_uid`` and the only arguments it takes are for the ``key`` and +the new ``uid``. + +.. code:: python + + import gpg + + c = gpg.Context() + c.home_dir = "~/.gnupg-dm" + + dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" + key = c.get_key(dmfpr, secret=True) + uid = "Danger Mouse <danger.mouse@secret.example.net>" + + c.key_add_uid(key, uid) + +Unsurprisingly the result of this is: + +.. code:: shell + + bash-4.4$ gpg --homedir ~/.gnupg-dm -K + ~/.gnupg-dm/pubring.kbx + ---------------------- + sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15] + 177B7C25DB99745EE2EE13ED026D2F19E99E63AA + uid [ultimate] Danger Mouse <danger.mouse@secret.example.net> + uid [ultimate] Danger Mouse <dm@secret.example.net> + ssb rsa3072 2018-03-15 [E] [expires: 2018-09-13] + + bash-4.4$ + +.. _keygen-uids-revoke: + +Revokinging User IDs +~~~~~~~~~~~~~~~~~~~~ + +Revoking a user ID is a fairly similar process, except that it uses the +``key_revoke_uid`` method. + +.. code:: python + + import gpg + + c = gpg.Context() + c.home_dir = "~/.gnupg-dm" + + dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" + key = c.get_key(dmfpr, secret=True) + uid = "Danger Mouse <danger.mouse@secret.example.net>" + + c.key_revoke_uid(key, uid) + +.. _key-sign: + +Key certification +----------------- + +Since key certification is more frequently referred to as key signing, +the method used to perform this function is ``key_sign``. + +The ``key_sign`` method takes four arguments: ``key``, ``uids``, +``expires_in`` and ``local``. The default value of ``uids`` is ``None`` +and which results in all user IDs being selected. The default value of +both ``expires_in`` and ``local`` is ``False``; which results in the +signature never expiring and being able to be exported. + +The ``key`` is the key being signed rather than the key doing the +signing. To change the key doing the signing refer to the signing key +selection above for signing messages and files. + +If the ``uids`` value is not ``None`` then it must either be a string to +match a single user ID or a list of strings to match multiple user IDs. +In this case the matching of those strings must be precise and it is +case sensitive. + +To sign Danger Mouse\'s key for just the initial user ID with a +signature which will last a little over a month, do this: + +.. code:: python + + import gpg + + c = gpg.Context() + uid = "Danger Mouse <dm@secret.example.net>" + + dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" + key = c.get_key(dmfpr, secret=True) + c.key_sign(key, uids=uid, expires_in=2764800) + +.. _advanced-use: + +Advanced or Experimental Use Cases +================================== + +.. _cython: + +C plus Python plus SWIG plus Cython +----------------------------------- + +In spite of the apparent incongruence of using Python bindings to a C +interface only to generate more C from the Python; it is in fact quite +possible to use the GPGME bindings with +`Cython <http://docs.cython.org/en/latest/index.html>`__. Though in many +cases the benefits may not be obvious since the most computationally +intensive work never leaves the level of the C code with which GPGME +itself is interacting with. + +Nevertheless, there are some situations where the benefits are +demonstrable. One of the better and easier examples being the one of the +early examples in this HOWTO, the `key +counting <#howto-keys-counting>`__ code. Running that example as an +executable Python script, ``keycount.py`` (available in the +``examples/howto/`` directory), will take a noticable amount of time to +run on most systems where the public keybox or keyring contains a few +thousand public keys. + +Earlier in the evening, prior to starting this section, I ran that +script on my laptop; as I tend to do periodically and timed it using +``time`` utility, with the following results: + +.. code:: shell + + bash-4.4$ time keycount.py + + Number of secret keys: 23 + Number of public keys: 12112 + + + real 11m52.945s + user 0m0.913s + sys 0m0.752s + + bash-4.4$ + +Sometime after that I imported another key and followed it with a little +test of Cython. This test was kept fairly basic, essentially lifting the +material from the `Cython Basic +Tutorial <http://docs.cython.org/en/latest/src/tutorial/cython_tutorial.html>`__ +to demonstrate compiling Python code to C. The first step was to take +the example key counting code quoted previously, essentially from the +importing of the ``gpg`` module to the end of the script: + +.. code:: python + + import gpg + + c = gpg.Context() + seckeys = c.keylist(pattern=None, secret=True) + pubkeys = c.keylist(pattern=None, secret=False) + + seclist = list(seckeys) + secnum = len(seclist) + + publist = list(pubkeys) + pubnum = len(publist) + + print(""" + Number of secret keys: {0} + Number of public keys: {1} + + """.format(secnum, pubnum)) + +Save that into a file called ``keycount.pyx`` and then create a +``setup.py`` file which contains this: + +.. code:: python + + from distutils.core import setup + from Cython.Build import cythonize + + setup( + ext_modules = cythonize("keycount.pyx") + ) + +Compile it: + +.. code:: shell + + bash-4.4$ python setup.py build_ext --inplace + bash-4.4$ + +Then run it in a similar manner to ``keycount.py``: + +.. code:: shell + + bash-4.4$ time python3.7 -c "import keycount" + + Number of secret keys: 23 + Number of public keys: 12113 + + + real 6m47.905s + user 0m0.785s + sys 0m0.331s + + bash-4.4$ + +Cython turned ``keycount.pyx`` into an 81KB ``keycount.o`` file in the +``build/`` directory, a 24KB ``keycount.cpython-37m-darwin.so`` file to +be imported into Python 3.7 and a 113KB ``keycount.c`` generated C +source code file of nearly three thousand lines. Quite a bit bigger than +the 314 bytes of the ``keycount.pyx`` file or the full 1,452 bytes of +the full executable ``keycount.py`` example script. + +On the other hand it ran in nearly half the time; taking 6 minutes and +47.905 seconds to run. As opposed to the 11 minutes and 52.945 seconds +which the CPython script alone took. + +The ``keycount.pyx`` and ``setup.py`` files used to generate this +example have been added to the ``examples/howto/advanced/cython/`` +directory The example versions include some additional options to +annotate the existing code and to detect Cython\'s use. The latter comes +from the `Magic +Attributes <http://docs.cython.org/en/latest/src/tutorial/pure.html#magic-attributes-within-the-pxd>`__ +section of the Cython documentation. + +.. _cheats-and-hacks: + +Miscellaneous extras and work-arounds +===================================== + +Most of the things in the following sections are here simply because +there was no better place to put them, even though some are only +peripherally related to the GPGME Python bindings. Some are also +workarounds for functions not integrated with GPGME as yet. This is +especially true of the first of these, dealing with `group +lines <#group-lines>`__. + +Group lines +----------- + +There is not yet an easy way to access groups configured in the gpg.conf +file from within GPGME. As a consequence these central groupings of keys +cannot be shared amongst multiple programs, such as MUAs readily. + +The following code, however, provides a work-around for obtaining this +information in Python. + +.. code:: python + + import subprocess + import sys + + if sys.platform == "win32": + gpgconfcmd = "gpgconf.exe --list-options gpg" + else: + gpgconfcmd = "gpgconf --list-options gpg" + + try: + lines = subprocess.getoutput(gpgconfcmd).splitlines() + except: + process = subprocess.Popen(gpgconfcmd.split(), stdout=subprocess.PIPE) + procom = process.communicate() + if sys.version_info[0] == 2: + lines = procom[0].splitlines() + else: + lines = procom[0].decode().splitlines() + + for i in range(len(lines)): + if lines[i].startswith("group") is True: + line = lines[i] + else: + pass + + groups = line.split(":")[-1].replace('"', '').split(',') + + group_lines = [] + group_lists = [] + + for i in range(len(groups)): + group_lines.append(groups[i].split("=")) + group_lists.append(groups[i].split("=")) + + for i in range(len(group_lists)): + group_lists[i][1] = group_lists[i][1].split() + +The result of that code is that ``group_lines`` is a list of lists where +``group_lines[i][0]`` is the name of the group and ``group_lines[i][1]`` +is the key IDs of the group as a string. + +The ``group_lists`` result is very similar in that it is a list of +lists. The first part, ``group_lists[i][0]`` matches +``group_lines[i][0]`` as the name of the group, but +``group_lists[i][1]`` is the key IDs of the group as a string. + +A demonstration of using the ``groups.py`` module is also available in +the form of the executable ``mutt-groups.py`` script. This second script +reads all the group entries in a user\'s ``gpg.conf`` file and converts +them into crypt-hooks suitable for use with the Mutt and Neomutt mail +clients. + +.. _hkp4py: + +Keyserver access for Python +--------------------------- + +The `hkp4py <https://github.com/Selfnet/hkp4py>`__ module by Marcel Fest +was originally a port of the old +`python-hkp <https://github.com/dgladkov/python-hkp>`__ module from +Python 2 to Python 3 and updated to use the +`requests <http://docs.python-requests.org/en/latest/index.html>`__ +module instead. It has since been modified to provide support for Python +2.7 as well and is available via PyPI. + +Since it rewrites the ``hkp`` protocol prefix as ``http`` and ``hkps`` +as ``https``, the module is able to be used even with servers which do +not support the full scope of keyserver functions. [5]_ It also works +quite readily when incorporated into a `Cython <#cython>`__ generated +and compiled version of any code. + +.. _hkp4py-strings: + +Key import format +~~~~~~~~~~~~~~~~~ + +The hkp4py module returns key data via requests as string literals +(``r.text``) instead of byte literals (``r.content``). This means that +the retrurned key data must be encoded to UTF-8 when importing that key +material using a ``gpg.Context().key_import()`` method. + +For this reason an alternative method has been added to the ``search`` +function of ``hkp4py.KeyServer()`` which returns the key in the correct +format as expected by ``key_import``. When importing using this module, +it is now possible to import with this: + +.. code:: python + + for key in keys: + if key.revoked is False: + gpg.Context().key_import(key.key_blob) + else: + pass + +Without that recent addition it would have been necessary to encode the +contents of each ``hkp4py.KeyServer().search()[i].key`` in +``hkp4py.KeyServer().search()`` before trying to import it. + +An example of this is included in the `Importing +Keys <#howto-import-key>`__ section of this HOWTO and the corresponding +executable version of that example is available in the +``lang/python/examples/howto`` directory as normal; the executable +version is the ``import-keys-hkp.py`` file. + +.. _copyright-and-license: + +Copyright and Licensing +======================= + +Copyright +--------- + +Copyright © The GnuPG Project, 2018. + +Copyright (C) The GnuPG Project, 2018. + +.. _draft-editions: + +Draft Editions of this HOWTO +---------------------------- + +Draft editions of this HOWTO may be periodically available directly from +the author at any of the following URLs: + +- `GPGME Python Bindings HOWTO draft (XHTML AWS S3 + SSL) <https://files.au.adversary.org/crypto/gpgme-python-howto.html>`__ +- `GPGME Python Bindings HOWTO draft (XHTML AWS S3 no + SSL) <http://files.au.adversary.org/crypto/gpgme-python-howto.html>`__ +- `GPGME Python Bindings HOWTO draft (Texinfo file AWS S3 + SSL) <https://files.au.adversary.org/crypto/gpgme-python-howto.texi>`__ +- `GPGME Python Bindings HOWTO draft (Texinfo file AWS S3 no + SSL) <http://files.au.adversary.org/crypto/gpgme-python-howto.texi>`__ +- `GPGME Python Bindings HOWTO draft (Info file AWS S3 + SSL) <https://files.au.adversary.org/crypto/gpgme-python-howto.info>`__ +- `GPGME Python Bindings HOWTO draft (Info file AWS S3 no + SSL) <http://files.au.adversary.org/crypto/gpgme-python-howto.info>`__ +- `GPGME Python Bindings HOWTO draft (reST file AWS S3 + SSL) <https://files.au.adversary.org/crypto/gpgme-python-howto.rst>`__ +- `GPGME Python Bindings HOWTO draft (reST file AWS S3 no + SSL) <http://files.au.adversary.org/crypto/gpgme-python-howto.rst>`__ +- `GPGME Python Bindings HOWTO draft (Docbook 4.2 AWS S3 + SSL) <https://files.au.adversary.org/crypto/gpgme-python-howto.xml>`__ +- `GPGME Python Bindings HOWTO draft (Docbook 4.2 AWS S3 no + SSL) <http://files.au.adversary.org/crypto/gpgme-python-howto.xml>`__ + +All of these draft versions except for one have been generated from this +document via Emacs `Org mode <https://orgmode.org/>`__ and `GNU +Texinfo <https://www.gnu.org/software/texinfo/>`__. Though it is likely +that the specific +`file <https://files.au.adversary.org/crypto/gpgme-python-howto.org>`__ +`version <http://files.au.adversary.org/crypto/gpgme-python-howto.org>`__ +used will be on the same server with the generated output formats. + +The one exception is the reStructuredText version, which was converted +using the latest version of Pandoc from the Org mode source file using +the following command: + +.. code:: shell + + pandoc -f org -t rst+smart -o gpgme-python-howto.rst gpgme-python-howto.org + +In addition to these there is a significantly less frequently updated +version as a HTML `WebHelp +site <https://files.au.adversary.org/crypto/gpgme-python-howto/webhelp/index.html>`__ +(AWS S3 SSL); generated from DITA XML source files, which can be found +in `an alternative +branch <https://dev.gnupg.org/source/gpgme/browse/ben%252Fhowto-dita/>`__ +of the GPGME git repository. + +These draft editions are not official documents and the version of +documentation in the master branch or which ships with released versions +is the only official documentation. Nevertheless, these draft editions +may occasionally be of use by providing more accessible web versions +which are updated between releases. They are provided on the +understanding that they may contain errors or may contain content +subject to change prior to an official release. + +.. _license: + +License GPL compatible +---------------------- + +This file is free software; as a special exception the author gives +unlimited permission to copy and/or distribute it, with or without +modifications, as long as this notice is preserved. + +This file is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY, to the extent permitted by law; without even the implied +warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +Footnotes +========= + +.. [1] + ``short-history.org`` and/or ``short-history.html``. + +.. [2] + With no issues reported specific to Python 3.7, the release of Python + 3.7.1 at around the same time as GPGME 1.12.0 and the testing with + Python 3.7.1rc1, there is no reason to delay moving 3.7 ahead of 3.6 + now. Production environments with more conservative requirements will + always enforce their own policies anyway and installation to each + supported minor release is quite possible too. + +.. [3] + Yes, even if you use virtualenv with everything you do in Python. If + you want to install this module as just your user account then you + will need to manually configure, compile and install the *entire* + GnuPG stack as that user as well. This includes libraries which are + not often installed that way. It can be done and there are + circumstances under which it is worthwhile, but generally only on + POSIX systems which utilise single user mode (some even require it). + +.. [4] + You probably don\'t really want to do this. Searching the keyservers + for \"gnupg.org\" produces over 400 results, the majority of which + aren\'t actually at the gnupg.org domain, but just included a comment + regarding the project in their key somewhere. + +.. [5] + Such as with ProtonMail servers. This also means that restricted + servers which only advertise either HTTP or HTTPS end points and not + HKP or HKPS end points must still be identified as as HKP or HKPS + within the Python Code. The ``hkp4py`` module will rewrite these + appropriately when the connection is made to the server. diff --git a/lang/python/doc/rst/index.rst b/lang/python/doc/rst/index.rst new file mode 100644 index 0000000..31dc146 --- /dev/null +++ b/lang/python/doc/rst/index.rst @@ -0,0 +1,12 @@ +.. _index: + +GPGME Python Bindings +===================== + +.. _index-contents: + +Contents +-------- + +- `A short history of the project <short-history.org>`__ +- `GPGME Python Bindings HOWTO <gpgme-python-howto.org>`__ diff --git a/lang/python/doc/rst/short-history.rst b/lang/python/doc/rst/short-history.rst new file mode 100644 index 0000000..8cf604f --- /dev/null +++ b/lang/python/doc/rst/short-history.rst @@ -0,0 +1,152 @@ +Overview +======== + +The GPGME Python bindings passed through many hands and numerous phases +before, after a fifteen year journey, coming full circle to return to +the source. This is a short explanation of that journey. + +.. _in-the-begining: + +In the beginning +---------------- + +In 2002 John Goerzen released PyME; Python bindings for the GPGME module +which utilised the current release of Python of the time and SWIG. [1]_ +Shortly after creating it and ensuring it worked he stopped supporting +it, though he left his work available on his Gopher site. + +Keeping the flame alive +----------------------- + +A couple of years later the project was picked up by Igor Belyi and +actively developed and maintained by him from 2004 to 2008. Igor's +whereabouts at the time of this document's creation are unknown, but the +current authors do hope he is well. We're assuming (or hoping) that life +did what life does and made continuing untenable. + +Passing the torch +----------------- + +In 2014 Martin Albrecht wanted to patch a bug in the PyME code and +discovered the absence of Igor. Following a discussion on the PyME +mailing list he became the new maintainer for PyME, releasing version +0.9.0 in May of that year. He remains the maintainer of the original +PyME release in Python 2.6 and 2.7 (available via PyPI). + +.. _ouroboros: + +Coming full circle +------------------ + +In 2015 Ben McGinnes approached Martin about a Python 3 version, while +investigating how complex a task this would be the task ended up being +completed. A subsequent discussion with Werner Koch led to the decision +to fold the Python 3 port back into the original GPGME release in the +languages subdirectory for non-C bindings under the module name of +``pyme3``. + +In 2016 this PyME module was integrated back into the GPGME project by +Justus Winter. During the course of this work Justus adjusted the port +to restore limited support for Python 2, but not as many minor point +releases as the original PyME package supports. During the course of +this integration the package was renamed to more accurately reflect its +status as a component of GPGME. The ``pyme3`` module was renamed to +``gpg`` and adopted by the upstream GnuPG team. + +In 2017 Justus departed G10code and the GnuPG team. Following this Ben +returned to maintain of gpgme Python bindings and continue building them +from that point. + +.. _relics-past: + +Relics of the past +================== + +There are a few things, in addition to code specific factors, such as +SWIG itself, which are worth noting here. + +The Annoyances of Git +--------------------- + +As anyone who has ever worked with git knows, submodules are horrible +way to deal with pretty much anything. In the interests of avoiding +migraines, that was skipped with addition of the PyME code to GPGME. + +Instead the files were added to a subdirectory of the ``lang/`` +directory, along with a copy of the entire git log up to that point as a +separate file within the ``lang/python/docs/`` directory. [2]_ As the +log for PyME is nearly 100KB and the log for GPGME is approximately 1MB, +this would cause considerable bloat, as well as some confusion, should +the two be merged. + +Hence the unfortunate, but necessary, step to simply move the files. A +regular repository version has been maintained should it be possible to +implement this better in the future. + +The Perils of PyPI +------------------ + +The early port of the Python 2 ``pyme`` module as ``pyme3`` was never +added to PyPI while the focus remained on development and testing during +2015 and early 2016. Later in 2016, however, when Justus completed his +major integration work and subsequently renamed the module from +``pyme3`` to ``gpg``, some prior releases were also provided through +PyPI. + +Since these bindings require a matching release of the GPGME libraries +in order to function, it was determined that there was little benefit in +also providing a copy through PyPI since anyone obtaining the GPGME +source code would obtain the Python bindings source code at the same +time. Whereas there was the potential to sew confusion amongst Python +users installing the module from PyPI, only to discover that without the +relevant C files, header files or SWIG compiled binaries, the Python +module did them little good. + +There are only two files on PyPI which might turn up in a search for +this module or a sample of its content: + +#. gpg (1.8.0) - Python bindings for GPGME GnuPG cryptography library +#. pyme (0.9.0) - Python support for GPGME GnuPG cryptography library + +.. _pypi-gpgme-180: + +GPG 1·8·0 - Python bindings for GPGME GnuPG cryptography library +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This is the most recent version to reach PyPI and is the version of the +official Pyhon bindings which shipped with GPGME 1.8.0. If you have +GPGME 1.8.0 installed and *only* 1.8.0 installed, then it is probably +safe to use this copy from PyPI. + +As there have been a lot of changes since the release of GPGME 1.8.0, +the GnuPG Project recommends not using this version of the module and +instead installing the current version of GPGME along with the Python +bindings included with that package. + +.. _pypi-gpgme-90: + +PyME 0·9·0 - Python support for GPGME GnuPG cryptography library +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This is the last release of the PyME bindings maintained by Martin +Albrecht and is only compatible with Python 2, it will not work with +Python 3. This is the version of the software from which the port from +Python 2 to Python 3 code was made in 2015. + +Users of the more recent Python bindings will recognise numerous points +of similarity, but also significant differences. It is likely that the +more recent official bindings will feel "more pythonic." + +For those using Python 2, there is essentially no harm in using this +module, but it may lack a number of more recent features added to GPGME. + +Footnotes +========= + +.. [1] + In all likelihood thos would have been Python 2.2 or possibly Python + 2.3. + +.. [2] + The entire PyME git log and other preceding VCS logs are located in + the ``gpgme/lang/python/docs/old-commits.log`` file. diff --git a/lang/python/doc/src/gpgme-python-howto.org b/lang/python/doc/src/gpgme-python-howto.org new file mode 100644 index 0000000..caa8e2f --- /dev/null +++ b/lang/python/doc/src/gpgme-python-howto.org @@ -0,0 +1,3043 @@ +#+TITLE: GNU Privacy Guard (GnuPG) Made Easy Python Bindings HOWTO (English) +#+AUTHOR: Ben McGinnes +#+LATEX_COMPILER: xelatex +#+LATEX_CLASS: article +#+LATEX_CLASS_OPTIONS: [12pt] +#+LATEX_HEADER: \usepackage{xltxtra} +#+LATEX_HEADER: \usepackage[margin=1in]{geometry} +#+LATEX_HEADER: \setmainfont[Ligatures={Common}]{Times New Roman} +#+LATEX_HEADER: \author{Ben McGinnes <ben@gnupg.org>} + + +* Introduction + :PROPERTIES: + :CUSTOM_ID: intro + :END: + +| Version: | 0.1.4 | +| GPGME Version: | 1.12.0 | +| Author: | [[https://gnupg.org/people/index.html#sec-1-5][Ben McGinnes]] <ben@gnupg.org> | +| Author GPG Key: | DB4724E6FA4286C92B4E55C4321E4E2373590E5D | +| Language: | Australian English, British English | +| xml:lang: | en-AU, en-GB, en | + +This document provides basic instruction in how to use the GPGME +Python bindings to programmatically leverage the GPGME library. + + +** Python 2 versus Python 3 + :PROPERTIES: + :CUSTOM_ID: py2-vs-py3 + :END: + +Though the GPGME Python bindings themselves provide support for both +Python 2 and 3, the focus is unequivocally on Python 3 and +specifically from Python 3.4 and above. As a consequence all the +examples and instructions in this guide use Python 3 code. + +Much of it will work with Python 2, but much of it also deals with +Python 3 byte literals, particularly when reading and writing data. +Developers concentrating on Python 2.7, and possibly even 2.6, will +need to make the appropriate modifications to support the older string +and unicode types as opposed to bytes. + +There are multiple reasons for concentrating on Python 3; some of +which relate to the immediate integration of these bindings, some of +which relate to longer term plans for both GPGME and the python +bindings and some of which relate to the impending EOL period for +Python 2.7. Essentially, though, there is little value in tying the +bindings to a version of the language which is a dead end and the +advantages offered by Python 3 over Python 2 make handling the data +types with which GPGME deals considerably easier. + + +** Examples + :PROPERTIES: + :CUSTOM_ID: howto-python3-examples + :END: + +All of the examples found in this document can be found as Python 3 +scripts in the =lang/python/examples/howto= directory. + + +** Unofficial Drafts + :PROPERTIES: + :CUSTOM_ID: unofficial-drafts + :END: + +In addition to shipping with each release of GPGME, there is a section +on locations to read or download [[#draft-editions][draft editions]] of this document from +at the end of it. These are unofficial versions produced in between +major releases. + + +** What's New + :PROPERTIES: + :CUSTOM_ID: new-stuff + :END: + +The most obviously new point for those reading this guide is this +section on other new things, but that's hardly important. Not given +all the other things which spurred the need for adding this section +and its subsections. + +*** New in GPGME 1·12·0 + :PROPERTIES: + :CUSTOM_ID: new-stuff-1-12-0 + :END: + +There have been quite a number of additions to GPGME and the Python +bindings to it since the last release of GPGME with versions 1.11.0 +and 1.11.1 in April, 2018. + +The bullet points of new additiions are: + +- an expanded section on [[#installation][installing]] and [[#snafu][troubleshooting]] the Python + bindings. +- The release of Python 3.7.0; which appears to be working just fine + with our bindings, in spite of intermittent reports of problems for + many other Python projects with that new release. +- Python 3.7 has been moved to the head of the specified python + versions list in the build process. +- In order to fix some other issues, there are certain underlying + functions which are more exposed through the [[#howto-get-context][gpg.Context()]], but + ongoing documentation ought to clarify that or otherwise provide the + best means of using the bindings. Some additions to =gpg.core= and + the =Context()=, however, were intended (see below). +- Continuing work in identifying and confirming the cause of + oft-reported [[#snafu-runtime-not-funtime][problems installing the Python bindings on Windows]]. +- GSOC: Google's Surreptitiously Ordered Conscription ... erm ... oh, + right; Google's Summer of Code. Though there were two hopeful + candidates this year; only one ended up involved with the GnuPG + Project directly, the other concentrated on an unrelated third party + project with closer ties to one of the GNU/Linux distributions than + to the GnuPG Project. Thus the Python bindings benefited from GSOC + participant Jacob Adams, who added the key_import function; building + on prior work by Tobias Mueller. +- Several new methods functions were added to the gpg.Context(), + including: [[#howto-import-key][key_import]], [[#howto-export-key][key_export]], [[#howto-export-public-key][key_export_minimal]] and + [[#howto-export-secret-key][key_export_secret]]. +- Importing and exporting examples include versions integrated with + Marcel Fest's recently released [[https://github.com/Selfnet/hkp4py][HKP for Python]] module. Some + [[#hkp4py][additional notes on this module]] are included at the end of the HOWTO. +- Instructions for dealing with semi-walled garden implementations + like ProtonMail are also included. This is intended to make things + a little easier when communicating with users of ProtonMail's + services and should not be construed as an endorsement of said + service. The GnuPG Project neither favours, nor disfavours + ProtonMail and the majority of this deals with interacting with the + ProtonMail keyserver. +- Semi-formalised the location where [[#draft-editions][draft versions]] of this HOWTO may + periodically be accessible. This is both for the reference of + others and testing the publishing of the document itself. Renamed + this file at around the same time. +- The Texinfo documentation build configuration has been replicated + from the parent project in order to make to maintain consistency + with that project (and actually ship with each release). +- a reStructuredText (=.rst=) version is also generated for Python + developers more used to and comfortable with that format as it is + the standard Python documentation format and Python developers may + wish to use it with Sphinx. Please note that there has been no + testing of the reStructuredText version with Sphinx at all. The + reST file was generated by the simple expedient of using [[https://pandoc.org/][Pandoc]]. +- Added a new section for [[#advanced-use][advanced or experimental use]]. +- Began the advanced use cases with [[#cython][a section]] on using the module with + [[http://cython.org/][Cython]]. +- Added a number of new scripts to the =example/howto/= directory; + some of which may be in advance of their planned sections of the + HOWTO (and some are just there because it seemed like a good idea at + the time). +- Cleaned up a lot of things under the hood. + + +* GPGME Concepts + :PROPERTIES: + :CUSTOM_ID: gpgme-concepts + :END: + + +** A C API + :PROPERTIES: + :CUSTOM_ID: gpgme-c-api + :END: + +Unlike many modern APIs with which programmers will be more familiar +with these days, the GPGME API is a C API. The API is intended for +use by C coders who would be able to access its features by including +the =gpgme.h= header file with their own C source code and then access +its functions just as they would any other C headers. + +This is a very effective method of gaining complete access to the API +and in the most efficient manner possible. It does, however, have the +drawback that it cannot be directly used by other languages without +some means of providing an interface to those languages. This is +where the need for bindings in various languages stems. + + +** Python bindings + :PROPERTIES: + :CUSTOM_ID: gpgme-python-bindings + :END: + +The Python bindings for GPGME provide a higher level means of +accessing the complete feature set of GPGME itself. It also provides +a more pythonic means of calling these API functions. + +The bindings are generated dynamically with SWIG and the copy of +=gpgme.h= generated when GPGME is compiled. + +This means that a version of the Python bindings is fundamentally tied +to the exact same version of GPGME used to generate that copy of +=gpgme.h=. + + +** Difference between the Python bindings and other GnuPG Python packages + :PROPERTIES: + :CUSTOM_ID: gpgme-python-bindings-diffs + :END: + +There have been numerous attempts to add GnuPG support to Python over +the years. Some of the most well known are listed here, along with +what differentiates them. + + +*** The python-gnupg package maintained by Vinay Sajip + :PROPERTIES: + :CUSTOM_ID: diffs-python-gnupg + :END: + +This is arguably the most popular means of integrating GPG with +Python. The package utilises the =subprocess= module to implement +wrappers for the =gpg= and =gpg2= executables normally invoked on the +command line (=gpg.exe= and =gpg2.exe= on Windows). + +The popularity of this package stemmed from its ease of use and +capability in providing the most commonly required features. + +Unfortunately it has been beset by a number of security issues in the +past; most of which stemmed from using unsafe methods of accessing the +command line via the =subprocess= calls. While some effort has been +made over the last two to three years (as of 2018) to mitigate this, +particularly by no longer providing shell access through those +subprocess calls, the wrapper is still somewhat limited in the scope +of its GnuPG features coverage. + +The python-gnupg package is available under the MIT license. + + +*** The gnupg package created and maintained by Isis Lovecruft + :PROPERTIES: + :CUSTOM_ID: diffs-isis-gnupg + :END: + +In 2015 Isis Lovecruft from the Tor Project forked and then +re-implemented the python-gnupg package as just gnupg. This new +package also relied on subprocess to call the =gpg= or =gpg2= +binaries, but did so somewhat more securely. + +The naming and version numbering selected for this package, however, +resulted in conflicts with the original python-gnupg and since its +functions were called in a different manner to python-gnupg, the +release of this package also resulted in a great deal of consternation +when people installed what they thought was an upgrade that +subsequently broke the code relying on it. + +The gnupg package is available under the GNU General Public License +version 3.0 (or any later version). + + +*** The PyME package maintained by Martin Albrecht + :PROPERTIES: + :CUSTOM_ID: diffs-pyme + :END: + +This package is the origin of these bindings, though they are somewhat +different now. For details of when and how the PyME package was +folded back into GPGME itself see the [[file:short-history.org][Short History]] document.[fn:1] + +The PyME package was first released in 2002 and was also the first +attempt to implement a low level binding to GPGME. In doing so it +provided access to considerably more functionality than either the +=python-gnupg= or =gnupg= packages. + +The PyME package is only available for Python 2.6 and 2.7. + +Porting the PyME package to Python 3.4 in 2015 is what resulted in it +being folded into the GPGME project and the current bindings are the +end result of that effort. + +The PyME package is available under the same dual licensing as GPGME +itself: the GNU General Public License version 2.0 (or any later +version) and the GNU Lesser General Public License version 2.1 (or any +later version). + + +* GPGME Python bindings installation + :PROPERTIES: + :CUSTOM_ID: gpgme-python-install + :END: + + +** No PyPI + :PROPERTIES: + :CUSTOM_ID: do-not-use-pypi + :END: + +Most third-party Python packages and modules are available and +distributed through the Python Package Installer, known as PyPI. + +Due to the nature of what these bindings are and how they work, it is +infeasible to install the GPGME Python bindings in the same way. + +This is because the bindings use SWIG to dynamically generate C +bindings against =gpgme.h= and =gpgme.h= is generated from +=gpgme.h.in= at compile time when GPGME is built from source. Thus to +include a package in PyPI which actually built correctly would require +either statically built libraries for every architecture bundled with +it or a full implementation of C for each architecture. + +See the additional notes regarding [[#snafu-cffi][CFFI and SWIG]] at the end of this +section for further details. + + +** Requirements + :PROPERTIES: + :CUSTOM_ID: gpgme-python-requirements + :END: + +The GPGME Python bindings only have three requirements: + +1. A suitable version of Python 2 or Python 3. With Python 2 that + means CPython 2.7 and with Python 3 that means CPython 3.4 or + higher. +2. [[https://www.swig.org][SWIG]]. +3. GPGME itself. Which also means that all of GPGME's dependencies + must be installed too. + + +*** Recommended Additions + :PROPERTIES: + :CUSTOM_ID: gpgme-python-recommendations + :END: + +Though none of the following are absolute requirements, they are all +recommended for use with the Python bindings. In some cases these +recommendations refer to which version(s) of CPython to use the +bindings with, while others refer to third party modules which provide +a significant advantage in some way. + +1. If possible, use Python 3 instead of 2. +2. Favour a more recent version of Python since even 3.4 is due to + reach EOL soon. In production systems and services, Python 3.6 + should be robust enough to be relied on. +3. If possible add the following Python modules which are not part of + the standard library: [[http://docs.python-requests.org/en/latest/index.html][Requests]], [[http://cython.org/][Cython]] and [[https://github.com/Selfnet/hkp4py][hkp4py]]. Chances are + quite high that at least the first one and maybe two of those will + already be installed. + +Note that, as with Cython, some of the planned additions to the +[[#advanced-use][Advanced]] section, will bring with them additional requirements. Most +of these will be fairly well known and commonly installed ones, +however, which are in many cases likely to have already been installed +on many systems or be familiar to Python programmers. + + +** Installation + :PROPERTIES: + :CUSTOM_ID: installation + :END: + +Installing the Python bindings is effectively achieved by compiling +and installing GPGME itself. + +Once SWIG is installed with Python and all the dependencies for GPGME +are installed you only need to confirm that the version(s) of Python +you want the bindings installed for are in your =$PATH=. + +By default GPGME will attempt to install the bindings for the most +recent or highest version number of Python 2 and Python 3 it detects +in =$PATH=. It specifically checks for the =python= and =python3= +executables first and then checks for specific version numbers. + +For Python 2 it checks for these executables in this order: =python=, +=python2= and =python2.7=. + +For Python 3 it checks for these executables in this order: =python3=, + =python3.7=, =python3.6=, =python3.5= and =python3.4=.[fn:2] + +On systems where =python= is actually =python3= and not =python2= it +may be possible that =python2= may be overlooked, but there have been +no reports of that actually occurring as yet. + +In the three months or so since the release of Python 3.7.0 there has +been extensive testing and work with these bindings with no issues +specifically relating to the new version of Python or any of the new +features of either the language or the bindings. This has also been +the case with Python 3.7.1rc1. With that in mind and given the +release of Python 3.7.1 is scheduled for around the same time as GPGME +1.12.0, the order of preferred Python versions has been changed to +move Python 3.7 ahead of Python 3.6. + + +*** Installing GPGME + :PROPERTIES: + :CUSTOM_ID: install-gpgme + :END: + +See the GPGME =README= file for details of how to install GPGME from +source. + + +** Known Issues + :PROPERTIES: + :CUSTOM_ID: snafu + :END: + +There are a few known issues with the current build process and the +Python bindings. For the most part these are easily addressed should +they be encountered. + + +*** Breaking Builds + :PROPERTIES: + :CUSTOM_ID: snafu-a-swig-of-this-builds-character + :END: + +Occasionally when installing GPGME with the Python bindings included +it may be observed that the =make= portion of that process induces a +large very number of warnings and, eventually errors which end that +part of the build process. Yet following that with =make check= and +=make install= appears to work seamlessly. + +The cause of this is related to the way SWIG needs to be called to +dynamically generate the C bindings for GPGME in the first place. So +the entire process will always produce =lang/python/python2-gpg/= and +=lang/python/python3-gpg/= directories. These should contain the +build output generated during compilation, including the complete +bindings and module installed into =site-packages=. + +Occasionally the errors in the early part or some other conflict +(e.g. not installing as */root/* or */su/*) may result in nothing +being installed to the relevant =site-packages= directory and the +build directory missing a lot of expected files. Even when this +occurs, the solution is actually quite simple and will always work. + +That solution is simply to run the following commands as either the +*root* user or prepended with =sudo -H=[fn:3] in the =lang/python/= +directory: + +#+BEGIN_SRC shell + /path/to/pythonX.Y setup.py build + /path/to/pythonX.Y setup.py build + /path/to/pythonX.Y setup.py install +#+END_SRC + +Yes, the build command does need to be run twice. Yes, you still need +to run the potentially failing or incomplete steps during the +=configure=, =make= and =make install= steps with installing GPGME. +This is because those steps generate a lot of essential files needed, +both by and in order to create, the bindings (including both the +=setup.py= and =gpgme.h= files). + + +**** IMPORTANT Note + :PROPERTIES: + :CUSTOM_ID: snafu-swig-build-note + :END: + +If specifying a selected number of languages to create bindings for, +try to leave Python last. Currently the majority of the other +language bindings are also preceding Python of either version when +listed alphabetically and so that just happens by default currently. + +If Python is set to precede one of the other languages then it is +possible that the errors described here may interrupt the build +process before generating bindings for those other languages. In +these cases it may be preferable to configure all preferred language +bindings separately with alternative =configure= steps for GPGME using +the =--enable-languages=$LANGUAGE= option. + + +*** Reinstalling Responsibly + :PROPERTIES: + :CUSTOM_ID: snafu-lessons-for-the-lazy + :END: + +Regardless of whether you're installing for one version of Python or +several, there will come a point where reinstallation is required. +With most Python module installations, the installed files go into the +relevant site-packages directory and are then forgotten about. Then +the module is upgraded, the new files are copied over the old and +that's the end of the matter. + +While the same is true of these bindings, there have been intermittent +issues observed on some platforms which have benefited significantly +from removing all the previous installations of the bindings before +installing the updated versions. + +Removing the previous version(s) is simply a matter of changing to the +relevant =site-packages= directory for the version of Python in +question and removing the =gpg/= directory and any accompanying +egg-info files for that module. + +In most cases this will require root or administration privileges on +the system, but the same is true of installing the module in the first +place. + + +*** Multiple installations + :PROPERTIES: + :CUSTOM_ID: snafu-the-full-monty + :END: + +For a veriety of reasons it may be either necessary or just preferable +to install the bindings to alternative installed Python versions which +meet the requirements of these bindings. + +On POSIX systems this will generally be most simply achieved by +running the manual installation commands (build, build, install) as +described in the previous section for each Python installation the +bindings need to be installed to. + +As per the SWIG documentation: the compilers, libraries and runtime +used to build GPGME and the Python Bindings *must* match those used to +compile Python itself, including the version number(s) (at least going +by major version numbers and probably minor numbers too). + +On most POSIX systems, including OS X, this will very likely be the +case in most, if not all, cases. + + +*** Won't Work With Windows + :PROPERTIES: + :CUSTOM_ID: snafu-runtime-not-funtime + :END: + +There are semi-regular reports of Windows users having considerable +difficulty in installing and using the Python bindings at all. Very +often, possibly even always, these reports come from Cygwin users +and/or MinGW users and/or Msys2 users. Though not all of them have +been confirmed, it appears that these reports have also come from +people who installed Python using the Windows installer files from the +[[https://python.org][Python website]] (i.e. mostly MSI installers, sometimes self-extracting +=.exe= files). + +The Windows versions of Python are not built using Cygwin, MinGW or +Msys2; they're built using Microsoft Visual Studio. Furthermore the +version used is /considerably/ more advanced than the version which +MinGW obtained a small number of files from many years ago in order to +be able to compile anything at all. Not only that, but there are +changes to the version of Visual Studio between some micro releases, +though that is is particularly the case with Python 2.7, since it has +been kept around far longer than it should have been. + +There are two theoretical solutions to this issue: + + 1. Compile and install the GnuPG stack, including GPGME and the + Python bibdings using the same version of Microsoft Visual Studio + used by the Python Foundation to compile the version of Python + installed. + + If there are multiple versions of Python then this will need to be + done with each different version of Visual Studio used. + + 2. Compile and install Python using the same tools used by choice, + such as MinGW or Msys2. + +Do *not* use the official Windows installer for Python unless +following the first method. + +In this type of situation it may even be for the best to accept that +there are less limitations on permissive software than free software +and simply opt to use a recent version of the Community Edition of +Microsoft Visual Studio to compile and build all of it, no matter +what. + +Investigations into the extent or the limitations of this issue are +ongoing. + + +*** CFFI is the Best™ and GPGME should use it instead of SWIG + :PROPERTIES: + :CUSTOM_ID: snafu-cffi + :END: + +There are many reasons for favouring [[https://cffi.readthedocs.io/en/latest/overview.html][CFFI]] and proponents of it are +quite happy to repeat these things as if all it would take to switch +from SWIG to CFFI is repeating that list as if it were a new concept. + +The fact is that there are things which Python's CFFI implementation +cannot handle in the GPGME C code. Beyond that there are features of +SWIG which are simply not available with CFFI at all. SWIG generates +the bindings to Python using the =gpgme.h= file, but that file is not +a single version shipped with each release, it too is generated when +GPGME is compiled. + +CFFI is currently unable to adapt to such a potentially mutable +codebase. If there were some means of applying SWIG's dynamic code +generation to produce the Python/CFFI API modes of accessing the GPGME +libraries (or the source source code directly), but such a thing does +not exist yet either and it currently appears that work is needed in +at least one of CFFI's dependencies before any of this can be +addressed. + +So if you're a massive fan of CFFI; that's great, but if you want this +project to switch to CFFI then rather than just insisting that it +should, I'd suggest you volunteer to bring CFFI up to the level this +project needs. + +If you're actually seriously considering doing so, then I'd suggest +taking the =gpgme-tool.c= file in the GPGME =src/= directory and +getting that to work with any of the CFFI API methods (not the ABI +methods, they'll work with pretty much anything). When you start +running into trouble with "ifdefs" then you'll know what sort of +things are lacking. That doesn't even take into account the amount of +work saved via SWIG's code generation techniques either. + + +*** Virtualised Environments + :PROPERTIES: + :CUSTOM_ID: snafu-venv + :END: + +It is fairly common practice amongst Python developers to, as much as +possible, use packages like virtualenv to keep various things that are +to be installed from interfering with each other. Given how much of +the GPGME bindings is often at odds with the usual pythonic way of +doing things, it stands to reason that this would be called into +question too. + +As it happens the answer as to whether or not the bindings can be used +with virtualenv, the answer is both yes and no. + +In general we recommend installing to the relevant path and matching +prefix of GPGME itself. Which means that when GPGME, and ideally the +rest of the GnuPG stack, is installed to a prefix like =/usr/local= or +=/opt/local= then the bindings would need to be installed to the main +Python installation and not a virtualised abstraction. Attempts to +separate the two in the past have been known to cause weird and +intermittent errors ranging from minor annoyances to complete failures +in the build process. + +As a consequence we only recommend building with and installing to the +main Python installations within the same prefix as GPGME is installed +to or which are found by GPGME's configuration stage immediately prior +to running the make commands. Which is exactly what the compiling and +installing process of GPGME does by default. + +Once that is done, however, it appears that a copy the compiled module +may be installed into a virtualenv of the same major and minor version +matching the build. Alternatively it is possible to utilise a +=sites.pth= file in the =site-packages/= directory of a viertualenv +installation, which links back to the system installations +corresponding directory in order to import anything installed system +wide. This may or may not be appropriate on a case by case basis. + +Though extensive testing of either of these options is not yet +complete, preliminary testing of them indicates that both are viable +as long as the main installation is complete. Which means that +certain other options normally restricted to virtual environments are +also available, including integration with pythonic test suites +(e.g. [[https://docs.pytest.org/en/latest/index.html][pytest]]) and other large projects. + +That said, it is worth reiterating the warning regarding non-standard +installations. If one were to attempt to install the bindings only to +a virtual environment without somehow also including the full GnuPG +stack (or enough of it as to include GPGME) then it is highly likely +that errors would be encountered at some point and more than a little +likely that the build process itself would break. + +If a degree of separation from the main operating system is still +required in spite of these warnings, then consider other forms of +virtualisation. Either a virtual machine (e.g. [[https://www.virtualbox.org/][VirtualBox]]), a +hardware emulation layer (e.g. [[https://www.qemu.org/][QEMU]]) or an application container +(e.g. [[https://www.docker.com/why-docker][Docker]]). + +Finally it should be noted that the limited tests conducted thus far +have been using the =virtualenv= command in a new directory to create +the virtual python environment. As opposed to the standard =python3 +-m venv= and it is possible that this will make a difference depending +on the system and version of Python in use. Another option is to run +the command =python3 -m virtualenv /path/to/install/virtual/thingy= +instead. + + +* Fundamentals + :PROPERTIES: + :CUSTOM_ID: howto-fund-a-mental + :END: + +Before we can get to the fun stuff, there are a few matters regarding +GPGME's design which hold true whether you're dealing with the C code +directly or these Python bindings. + + +** No REST + :PROPERTIES: + :CUSTOM_ID: no-rest-for-the-wicked + :END: + +The first part of which is or will be fairly blatantly obvious upon +viewing the first example, but it's worth reiterating anyway. That +being that this API is /*not*/ a REST API. Nor indeed could it ever +be one. + +Most, if not all, Python programmers (and not just Python programmers) +know how easy it is to work with a RESTful API. In fact they've +become so popular that many other APIs attempt to emulate REST-like +behaviour as much as they are able. Right down to the use of JSON +formatted output to facilitate the use of their API without having to +retrain developers. + +This API does not do that. It would not be able to do that and also +provide access to the entire C API on which it's built. It does, +however, provide a very pythonic interface on top of the direct +bindings and it's this pythonic layer that this HOWTO deals with. + + +** Context + :PROPERTIES: + :CUSTOM_ID: howto-get-context + :END: + +One of the reasons which prevents this API from being RESTful is that +most operations require more than one instruction to the API to +perform the task. Sure, there are certain functions which can be +performed simultaneously, particularly if the result known or strongly +anticipated (e.g. selecting and encrypting to a key known to be in the +public keybox). + +There are many more, however, which cannot be manipulated so readily: +they must be performed in a specific sequence and the result of one +operation has a direct bearing on the outcome of subsequent +operations. Not merely by generating an error either. + +When dealing with this type of persistent state on the web, full of +both the RESTful and REST-like, it's most commonly referred to as a +session. In GPGME, however, it is called a context and every +operation type has one. + + +* Working with keys + :PROPERTIES: + :CUSTOM_ID: howto-keys + :END: + + +** Key selection + :PROPERTIES: + :CUSTOM_ID: howto-keys-selection + :END: + +Selecting keys to encrypt to or to sign with will be a common +occurrence when working with GPGMe and the means available for doing +so are quite simple. + +They do depend on utilising a Context; however once the data is +recorded in another variable, that Context does not need to be the +same one which subsequent operations are performed. + +The easiest way to select a specific key is by searching for that +key's key ID or fingerprint, preferably the full fingerprint without +any spaces in it. A long key ID will probably be okay, but is not +advised and short key IDs are already a problem with some being +generated to match specific patterns. It does not matter whether the +pattern is upper or lower case. + +So this is the best method: + +#+BEGIN_SRC python -i +import gpg + +k = gpg.Context().keylist(pattern="258E88DCBD3CD44D8E7AB43F6ECB6AF0DEADBEEF") +keys = list(k) +#+END_SRC + +This is passable and very likely to be common: + +#+BEGIN_SRC python -i +import gpg + +k = gpg.Context().keylist(pattern="0x6ECB6AF0DEADBEEF") +keys = list(k) +#+END_SRC + +And this is a really bad idea: + +#+BEGIN_SRC python -i +import gpg + +k = gpg.Context().keylist(pattern="0xDEADBEEF") +keys = list(k) +#+END_SRC + +Alternatively it may be that the intention is to create a list of keys +which all match a particular search string. For instance all the +addresses at a particular domain, like this: + +#+BEGIN_SRC python -i +import gpg + +ncsc = gpg.Context().keylist(pattern="ncsc.mil") +nsa = list(ncsc) +#+END_SRC + + +*** Counting keys + :PROPERTIES: + :CUSTOM_ID: howto-keys-counting + :END: + +Counting the number of keys in your public keybox (=pubring.kbx=), the +format which has superseded the old keyring format (=pubring.gpg= and +=secring.gpg=), or the number of secret keys is a very simple task. + +#+BEGIN_SRC python -i +import gpg + +c = gpg.Context() +seckeys = c.keylist(pattern=None, secret=True) +pubkeys = c.keylist(pattern=None, secret=False) + +seclist = list(seckeys) +secnum = len(seclist) + +publist = list(pubkeys) +pubnum = len(publist) + +print(""" + Number of secret keys: {0} + Number of public keys: {1} +""".format(secnum, pubnum)) +#+END_SRC + +NOTE: The [[#cython][Cython]] introduction in the [[#advanced-use][Advanced and Experimental]] +section uses this same key counting code with Cython to demonstrate +some areas where Cython can improve performance even with the +bindings. Users with large public keyrings or keyboxes, for instance, +should consider these options if they are comfortable with using +Cython. + + +** Get key + :PROPERTIES: + :CUSTOM_ID: howto-get-key + :END: + +An alternative method of getting a single key via its fingerprint is +available directly within a Context with =Context().get_key=. This is +the preferred method of selecting a key in order to modify it, sign or +certify it and for obtaining relevant data about a single key as a +part of other functions; when verifying a signature made by that key, +for instance. + +By default this method will select public keys, but it can select +secret keys as well. + +This first example demonstrates selecting the current key of Werner +Koch, which is due to expire at the end of 2018: + +#+BEGIN_SRC python -i +import gpg + +fingerprint = "80615870F5BAD690333686D0F2AD85AC1E42B367" +key = gpg.Context().get_key(fingerprint) +#+END_SRC + +Whereas this example demonstrates selecting the author's current key +with the =secret= key word argument set to =True=: + +#+BEGIN_SRC python -i +import gpg + +fingerprint = "DB4724E6FA4286C92B4E55C4321E4E2373590E5D" +key = gpg.Context().get_key(fingerprint, secret=True) +#+END_SRC + +It is, of course, quite possible to select expired, disabled and +revoked keys with this function, but only to effectively display +information about those keys. + +It is also possible to use both unicode or string literals and byte +literals with the fingerprint when getting a key in this way. + + +** Importing keys + :PROPERTIES: + :CUSTOM_ID: howto-import-key + :END: + +Importing keys is possible with the =key_import()= method and takes +one argument which is a bytes literal object containing either the +binary or ASCII armoured key data for one or more keys. + +The following example retrieves one or more keys from the SKS +keyservers via the web using the requests module. Since requests +returns the content as a bytes literal object, we can then use that +directly to import the resulting data into our keybox. + +#+BEGIN_SRC python -i +import gpg +import os.path +import requests + +c = gpg.Context() +url = "https://sks-keyservers.net/pks/lookup" +pattern = input("Enter the pattern to search for key or user IDs: ") +payload = {"op": "get", "search": pattern} + +r = requests.get(url, verify=True, params=payload) +result = c.key_import(r.content) + +if result is not None and hasattr(result, "considered") is False: + print(result) +elif result is not None and hasattr(result, "considered") is True: + num_keys = len(result.imports) + new_revs = result.new_revocations + new_sigs = result.new_signatures + new_subs = result.new_sub_keys + new_uids = result.new_user_ids + new_scrt = result.secret_imported + nochange = result.unchanged + print(""" + The total number of keys considered for import was: {0} + + Number of keys revoked: {1} + Number of new signatures: {2} + Number of new subkeys: {3} + Number of new user IDs: {4} + Number of new secret keys: {5} + Number of unchanged keys: {6} + + The key IDs for all considered keys were: +""".format(num_keys, new_revs, new_sigs, new_subs, new_uids, new_scrt, + nochange)) + for i in range(num_keys): + print("{0}\n".format(result.imports[i].fpr)) +else: + pass +#+END_SRC + +NOTE: When searching for a key ID of any length or a fingerprint +(without spaces), the SKS servers require the the leading =0x= +indicative of hexadecimal be included. Also note that the old short +key IDs (e.g. =0xDEADBEEF=) should no longer be used due to the +relative ease by which such key IDs can be reproduced, as demonstrated +by the Evil32 Project in 2014 (which was subsequently exploited in +2016). + + +*** Working with ProtonMail + :PROPERTIES: + :CUSTOM_ID: import-protonmail + :END: + +Here is a variation on the example above which checks the constrained +ProtonMail keyserver for ProtonMail public keys. + +#+BEGIN_SRC python -i +import gpg +import requests +import sys + +print(""" +This script searches the ProtonMail key server for the specified key and +imports it. +""") + +c = gpg.Context(armor=True) +url = "https://api.protonmail.ch/pks/lookup" +ksearch = [] + +if len(sys.argv) >= 2: + keyterm = sys.argv[1] +else: + keyterm = input("Enter the key ID, UID or search string: ") + +if keyterm.count("@") == 2 and keyterm.startswith("@") is True: + ksearch.append(keyterm[1:]) + ksearch.append(keyterm[1:]) + ksearch.append(keyterm[1:]) +elif keyterm.count("@") == 1 and keyterm.startswith("@") is True: + ksearch.append("{0}@protonmail.com".format(keyterm[1:])) + ksearch.append("{0}@protonmail.ch".format(keyterm[1:])) + ksearch.append("{0}@pm.me".format(keyterm[1:])) +elif keyterm.count("@") == 0: + ksearch.append("{0}@protonmail.com".format(keyterm)) + ksearch.append("{0}@protonmail.ch".format(keyterm)) + ksearch.append("{0}@pm.me".format(keyterm)) +elif keyterm.count("@") == 2 and keyterm.startswith("@") is False: + uidlist = keyterm.split("@") + for uid in uidlist: + ksearch.append("{0}@protonmail.com".format(uid)) + ksearch.append("{0}@protonmail.ch".format(uid)) + ksearch.append("{0}@pm.me".format(uid)) +elif keyterm.count("@") > 2: + uidlist = keyterm.split("@") + for uid in uidlist: + ksearch.append("{0}@protonmail.com".format(uid)) + ksearch.append("{0}@protonmail.ch".format(uid)) + ksearch.append("{0}@pm.me".format(uid)) +else: + ksearch.append(keyterm) + +for k in ksearch: + payload = {"op": "get", "search": k} + try: + r = requests.get(url, verify=True, params=payload) + if r.ok is True: + result = c.key_import(r.content) + elif r.ok is False: + result = r.content + except Exception as e: + result = None + + if result is not None and hasattr(result, "considered") is False: + print("{0} for {1}".format(result.decode(), k)) + elif result is not None and hasattr(result, "considered") is True: + num_keys = len(result.imports) + new_revs = result.new_revocations + new_sigs = result.new_signatures + new_subs = result.new_sub_keys + new_uids = result.new_user_ids + new_scrt = result.secret_imported + nochange = result.unchanged + print(""" +The total number of keys considered for import was: {0} + +With UIDs wholely or partially matching the following string: + + {1} + + Number of keys revoked: {2} + Number of new signatures: {3} + Number of new subkeys: {4} + Number of new user IDs: {5} +Number of new secret keys: {6} + Number of unchanged keys: {7} + +The key IDs for all considered keys were: +""".format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt, + nochange)) + for i in range(num_keys): + print(result.imports[i].fpr) + print("") + elif result is None: + print(e) +#+END_SRC + +Both the above example, [[../examples/howto/pmkey-import.py][pmkey-import.py]], and a version which prompts +for an alternative GnuPG home directory, [[../examples/howto/pmkey-import-alt.py][pmkey-import-alt.py]], are +available with the other examples and are executable scripts. + +Note that while the ProtonMail servers are based on the SKS servers, +their server is related more to their API and is not feature complete +by comparison to the servers in the SKS pool. One notable difference +being that the ProtonMail server does not permit non ProtonMail users +to update their own keys, which could be a vector for attacking +ProtonMail users who may not receive a key's revocation if it had been +compromised. + + +*** Importing with HKP for Python + :PROPERTIES: + :CUSTOM_ID: import-hkp4py + :END: + +Performing the same tasks with the [[https://github.com/Selfnet/hkp4py][hkp4py module]] (available via PyPI) +is not too much different, but does provide a number of options of +benefit to end users. Not least of which being the ability to perform +some checks on a key before importing it or not. For instance it may +be the policy of a site or project to only import keys which have not +been revoked. The hkp4py module permits such checks prior to the +importing of the keys found. + +#+BEGIN_SRC python -i +import gpg +import hkp4py +import sys + +c = gpg.Context() +server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net") +results = [] + +if len(sys.argv) > 2: + pattern = " ".join(sys.argv[1:]) +elif len(sys.argv) == 2: + pattern = sys.argv[1] +else: + pattern = input("Enter the pattern to search for keys or user IDs: ") + +try: + keys = server.search(pattern) + print("Found {0} key(s).".format(len(keys))) +except Exception as e: + keys = [] + for logrus in pattern.split(): + if logrus.startswith("0x") is True: + key = server.search(logrus) + else: + key = server.search("0x{0}".format(logrus)) + keys.append(key[0]) + print("Found {0} key(s).".format(len(keys))) + +for key in keys: + import_result = c.key_import(key.key_blob) + results.append(import_result) + +for result in results: + if result is not None and hasattr(result, "considered") is False: + print(result) + elif result is not None and hasattr(result, "considered") is True: + num_keys = len(result.imports) + new_revs = result.new_revocations + new_sigs = result.new_signatures + new_subs = result.new_sub_keys + new_uids = result.new_user_ids + new_scrt = result.secret_imported + nochange = result.unchanged + print(""" +The total number of keys considered for import was: {0} + + Number of keys revoked: {1} + Number of new signatures: {2} + Number of new subkeys: {3} + Number of new user IDs: {4} +Number of new secret keys: {5} + Number of unchanged keys: {6} + +The key IDs for all considered keys were: +""".format(num_keys, new_revs, new_sigs, new_subs, new_uids, new_scrt, + nochange)) + for i in range(num_keys): + print(result.imports[i].fpr) + print("") + else: + pass +#+END_SRC + +Since the hkp4py module handles multiple keys just as effectively as +one (=keys= is a list of responses per matching key), the example +above is able to do a little bit more with the returned data before +anything is actually imported. + + +*** Importing from ProtonMail with HKP for Python + :PROPERTIES: + :CUSTOM_ID: import-protonmail-hkp4py + :END: + +Though this can provide certain benefits even when working with +ProtonMail, the scope is somewhat constrained there due to the +limitations of the ProtonMail keyserver. + +For instance, searching the SKS keyserver pool for the term "gnupg" +produces hundreds of results from any time the word appears in any +part of a user ID. Performing the same search on the ProtonMail +keyserver returns zero results, even though there are at least two +test accounts which include it as part of the username. + +The cause of this discrepancy is the deliberate configuration of that +server by ProtonMail to require an exact match of the full email +address of the ProtonMail user whose key is being requested. +Presumably this is intended to reduce breaches of privacy of their +users as an email address must already be known before a key for that +address can be obtained. + + +**** Import from ProtonMail via HKP for Python Example no. 1 + :PROPERTIES: + :CUSTOM_ID: import-hkp4py-pm1 + :END: + +The following script is avalable with the rest of the examples under +the somewhat less than original name, =pmkey-import-hkp.py=. + +#+BEGIN_SRC python -i +import gpg +import hkp4py +import os.path +import sys + +print(""" +This script searches the ProtonMail key server for the specified key and +imports it. + +Usage: pmkey-import-hkp.py [search strings] +""") + +c = gpg.Context(armor=True) +server = hkp4py.KeyServer("hkps://api.protonmail.ch") +keyterms = [] +ksearch = [] +allkeys = [] +results = [] +paradox = [] +homeless = None + +if len(sys.argv) > 2: + keyterms = sys.argv[1:] +elif len(sys.argv) == 2: + keyterm = sys.argv[1] + keyterms.append(keyterm) +else: + key_term = input("Enter the key ID, UID or search string: ") + keyterms = key_term.split() + +for keyterm in keyterms: + if keyterm.count("@") == 2 and keyterm.startswith("@") is True: + ksearch.append(keyterm[1:]) + ksearch.append(keyterm[1:]) + ksearch.append(keyterm[1:]) + elif keyterm.count("@") == 1 and keyterm.startswith("@") is True: + ksearch.append("{0}@protonmail.com".format(keyterm[1:])) + ksearch.append("{0}@protonmail.ch".format(keyterm[1:])) + ksearch.append("{0}@pm.me".format(keyterm[1:])) + elif keyterm.count("@") == 0: + ksearch.append("{0}@protonmail.com".format(keyterm)) + ksearch.append("{0}@protonmail.ch".format(keyterm)) + ksearch.append("{0}@pm.me".format(keyterm)) + elif keyterm.count("@") == 2 and keyterm.startswith("@") is False: + uidlist = keyterm.split("@") + for uid in uidlist: + ksearch.append("{0}@protonmail.com".format(uid)) + ksearch.append("{0}@protonmail.ch".format(uid)) + ksearch.append("{0}@pm.me".format(uid)) + elif keyterm.count("@") > 2: + uidlist = keyterm.split("@") + for uid in uidlist: + ksearch.append("{0}@protonmail.com".format(uid)) + ksearch.append("{0}@protonmail.ch".format(uid)) + ksearch.append("{0}@pm.me".format(uid)) + else: + ksearch.append(keyterm) + +for k in ksearch: + print("Checking for key for: {0}".format(k)) + try: + keys = server.search(k) + if isinstance(keys, list) is True: + for key in keys: + allkeys.append(key) + try: + import_result = c.key_import(key.key_blob) + except Exception as e: + import_result = c.key_import(key.key) + else: + paradox.append(keys) + import_result = None + except Exception as e: + import_result = None + results.append(import_result) + +for result in results: + if result is not None and hasattr(result, "considered") is False: + print("{0} for {1}".format(result.decode(), k)) + elif result is not None and hasattr(result, "considered") is True: + num_keys = len(result.imports) + new_revs = result.new_revocations + new_sigs = result.new_signatures + new_subs = result.new_sub_keys + new_uids = result.new_user_ids + new_scrt = result.secret_imported + nochange = result.unchanged + print(""" +The total number of keys considered for import was: {0} + +With UIDs wholely or partially matching the following string: + + {1} + + Number of keys revoked: {2} + Number of new signatures: {3} + Number of new subkeys: {4} + Number of new user IDs: {5} +Number of new secret keys: {6} + Number of unchanged keys: {7} + +The key IDs for all considered keys were: +""".format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt, + nochange)) + for i in range(num_keys): + print(result.imports[i].fpr) + print("") + elif result is None: + pass +#+END_SRC + + +**** Import from ProtonMail via HKP for Python Example no. 2 + :PROPERTIES: + :CUSTOM_ID: import-hkp4py-pm2 + :END: + +Like its counterpart above, this script can also be found with the +rest of the examples, by the name pmkey-import-hkp-alt.py. + +With this script a modicum of effort has been made to treat anything +passed as a =homedir= which either does not exist or which is not a +directory, as also being a pssible user ID to check for. It's not +guaranteed to pick up on all such cases, but it should cover most of +them. + +#+BEGIN_SRC python -i +import gpg +import hkp4py +import os.path +import sys + +print(""" +This script searches the ProtonMail key server for the specified key and +imports it. Optionally enables specifying a different GnuPG home directory. + +Usage: pmkey-import-hkp.py [homedir] [search string] + or: pmkey-import-hkp.py [search string] +""") + +c = gpg.Context(armor=True) +server = hkp4py.KeyServer("hkps://api.protonmail.ch") +keyterms = [] +ksearch = [] +allkeys = [] +results = [] +paradox = [] +homeless = None + +if len(sys.argv) > 3: + homedir = sys.argv[1] + keyterms = sys.argv[2:] +elif len(sys.argv) == 3: + homedir = sys.argv[1] + keyterm = sys.argv[2] + keyterms.append(keyterm) +elif len(sys.argv) == 2: + homedir = "" + keyterm = sys.argv[1] + keyterms.append(keyterm) +else: + keyterm = input("Enter the key ID, UID or search string: ") + homedir = input("Enter the GPG configuration directory path (optional): ") + keyterms.append(keyterm) + +if len(homedir) == 0: + homedir = None + homeless = False + +if homedir is not None: + if homedir.startswith("~"): + if os.path.exists(os.path.expanduser(homedir)) is True: + if os.path.isdir(os.path.expanduser(homedir)) is True: + c.home_dir = os.path.realpath(os.path.expanduser(homedir)) + else: + homeless = True + else: + homeless = True + elif os.path.exists(os.path.realpath(homedir)) is True: + if os.path.isdir(os.path.realpath(homedir)) is True: + c.home_dir = os.path.realpath(homedir) + else: + homeless = True + else: + homeless = True + +# First check to see if the homedir really is a homedir and if not, treat it as +# a search string. +if homeless is True: + keyterms.append(homedir) + c.home_dir = None +else: + pass + +for keyterm in keyterms: + if keyterm.count("@") == 2 and keyterm.startswith("@") is True: + ksearch.append(keyterm[1:]) + ksearch.append(keyterm[1:]) + ksearch.append(keyterm[1:]) + elif keyterm.count("@") == 1 and keyterm.startswith("@") is True: + ksearch.append("{0}@protonmail.com".format(keyterm[1:])) + ksearch.append("{0}@protonmail.ch".format(keyterm[1:])) + ksearch.append("{0}@pm.me".format(keyterm[1:])) + elif keyterm.count("@") == 0: + ksearch.append("{0}@protonmail.com".format(keyterm)) + ksearch.append("{0}@protonmail.ch".format(keyterm)) + ksearch.append("{0}@pm.me".format(keyterm)) + elif keyterm.count("@") == 2 and keyterm.startswith("@") is False: + uidlist = keyterm.split("@") + for uid in uidlist: + ksearch.append("{0}@protonmail.com".format(uid)) + ksearch.append("{0}@protonmail.ch".format(uid)) + ksearch.append("{0}@pm.me".format(uid)) + elif keyterm.count("@") > 2: + uidlist = keyterm.split("@") + for uid in uidlist: + ksearch.append("{0}@protonmail.com".format(uid)) + ksearch.append("{0}@protonmail.ch".format(uid)) + ksearch.append("{0}@pm.me".format(uid)) + else: + ksearch.append(keyterm) + +for k in ksearch: + print("Checking for key for: {0}".format(k)) + try: + keys = server.search(k) + if isinstance(keys, list) is True: + for key in keys: + allkeys.append(key) + try: + import_result = c.key_import(key.key_blob) + except Exception as e: + import_result = c.key_import(key.key) + else: + paradox.append(keys) + import_result = None + except Exception as e: + import_result = None + results.append(import_result) + +for result in results: + if result is not None and hasattr(result, "considered") is False: + print("{0} for {1}".format(result.decode(), k)) + elif result is not None and hasattr(result, "considered") is True: + num_keys = len(result.imports) + new_revs = result.new_revocations + new_sigs = result.new_signatures + new_subs = result.new_sub_keys + new_uids = result.new_user_ids + new_scrt = result.secret_imported + nochange = result.unchanged + print(""" +The total number of keys considered for import was: {0} + +With UIDs wholely or partially matching the following string: + + {1} + + Number of keys revoked: {2} + Number of new signatures: {3} + Number of new subkeys: {4} + Number of new user IDs: {5} +Number of new secret keys: {6} + Number of unchanged keys: {7} + +The key IDs for all considered keys were: +""".format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt, + nochange)) + for i in range(num_keys): + print(result.imports[i].fpr) + print("") + elif result is None: + pass +#+END_SRC + + +** Exporting keys + :PROPERTIES: + :CUSTOM_ID: howto-export-key + :END: + +Exporting keys remains a reasonably simple task, but has been +separated into three different functions for the OpenPGP cryptographic +engine. Two of those functions are for exporting public keys and the +third is for exporting secret keys. + + +*** Exporting public keys + :PROPERTIES: + :CUSTOM_ID: howto-export-public-key + :END: + +There are two methods of exporting public keys, both of which are very +similar to the other. The default method, =key_export()=, will export +a public key or keys matching a specified pattern as normal. The +alternative, the =key_export_minimal()= method, will do the same thing +except producing a minimised output with extra signatures and third +party signatures or certifications removed. + +#+BEGIN_SRC python -i +import gpg +import os.path +import sys + +print(""" +This script exports one or more public keys. +""") + +c = gpg.Context(armor=True) + +if len(sys.argv) >= 4: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = sys.argv[3] +elif len(sys.argv) == 3: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = input("Enter the GPG configuration directory path (optional): ") +elif len(sys.argv) == 2: + keyfile = sys.argv[1] + logrus = input("Enter the UID matching the key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") +else: + keyfile = input("Enter the path and filename to save the secret key to: ") + logrus = input("Enter the UID matching the key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") + +if homedir.startswith("~"): + if os.path.exists(os.path.expanduser(homedir)) is True: + c.home_dir = os.path.expanduser(homedir) + else: + pass +elif os.path.exists(homedir) is True: + c.home_dir = homedir +else: + pass + +try: + result = c.key_export(pattern=logrus) +except: + result = c.key_export(pattern=None) + +if result is not None: + with open(keyfile, "wb") as f: + f.write(result) +else: + pass +#+END_SRC + +It should be noted that the result will only return =None= when a +search pattern has been entered, but has not matched any keys. When +the search pattern itself is set to =None= this triggers the exporting +of the entire public keybox. + +#+BEGIN_SRC python -i +import gpg +import os.path +import sys + +print(""" +This script exports one or more public keys in minimised form. +""") + +c = gpg.Context(armor=True) + +if len(sys.argv) >= 4: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = sys.argv[3] +elif len(sys.argv) == 3: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = input("Enter the GPG configuration directory path (optional): ") +elif len(sys.argv) == 2: + keyfile = sys.argv[1] + logrus = input("Enter the UID matching the key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") +else: + keyfile = input("Enter the path and filename to save the secret key to: ") + logrus = input("Enter the UID matching the key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") + +if homedir.startswith("~"): + if os.path.exists(os.path.expanduser(homedir)) is True: + c.home_dir = os.path.expanduser(homedir) + else: + pass +elif os.path.exists(homedir) is True: + c.home_dir = homedir +else: + pass + +try: + result = c.key_export_minimal(pattern=logrus) +except: + result = c.key_export_minimal(pattern=None) + +if result is not None: + with open(keyfile, "wb") as f: + f.write(result) +else: + pass +#+END_SRC + + +*** Exporting secret keys + :PROPERTIES: + :CUSTOM_ID: howto-export-secret-key + :END: + +Exporting secret keys is, functionally, very similar to exporting +public keys; save for the invocation of =pinentry= via =gpg-agent= in +order to securely enter the key's passphrase and authorise the export. + +The following example exports the secret key to a file which is then +set with the same permissions as the output files created by the +command line secret key export options. + +#+BEGIN_SRC python -i +import gpg +import os +import os.path +import sys + +print(""" +This script exports one or more secret keys. + +The gpg-agent and pinentry are invoked to authorise the export. +""") + +c = gpg.Context(armor=True) + +if len(sys.argv) >= 4: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = sys.argv[3] +elif len(sys.argv) == 3: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = input("Enter the GPG configuration directory path (optional): ") +elif len(sys.argv) == 2: + keyfile = sys.argv[1] + logrus = input("Enter the UID matching the secret key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") +else: + keyfile = input("Enter the path and filename to save the secret key to: ") + logrus = input("Enter the UID matching the secret key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") + +if len(homedir) == 0: + homedir = None +elif homedir.startswith("~"): + userdir = os.path.expanduser(homedir) + if os.path.exists(userdir) is True: + homedir = os.path.realpath(userdir) + else: + homedir = None +else: + homedir = os.path.realpath(homedir) + +if os.path.exists(homedir) is False: + homedir = None +else: + if os.path.isdir(homedir) is False: + homedir = None + else: + pass + +if homedir is not None: + c.home_dir = homedir +else: + pass + +try: + result = c.key_export_secret(pattern=logrus) +except: + result = c.key_export_secret(pattern=None) + +if result is not None: + with open(keyfile, "wb") as f: + f.write(result) + os.chmod(keyfile, 0o600) +else: + pass +#+END_SRC + +Alternatively the approach of the following script can be used. This +longer example saves the exported secret key(s) in files in the GnuPG +home directory, in addition to setting the file permissions as only +readable and writable by the user. It also exports the secret key(s) +twice in order to output both GPG binary (=.gpg=) and ASCII armoured +(=.asc=) files. + +#+BEGIN_SRC python -i +import gpg +import os +import os.path +import subprocess +import sys + +print(""" +This script exports one or more secret keys as both ASCII armored and binary +file formats, saved in files within the user's GPG home directory. + +The gpg-agent and pinentry are invoked to authorise the export. +""") + +if sys.platform == "win32": + gpgconfcmd = "gpgconf.exe --list-dirs homedir" +else: + gpgconfcmd = "gpgconf --list-dirs homedir" + +a = gpg.Context(armor=True) +b = gpg.Context() +c = gpg.Context() + +if len(sys.argv) >= 4: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = sys.argv[3] +elif len(sys.argv) == 3: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = input("Enter the GPG configuration directory path (optional): ") +elif len(sys.argv) == 2: + keyfile = sys.argv[1] + logrus = input("Enter the UID matching the secret key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") +else: + keyfile = input("Enter the filename to save the secret key to: ") + logrus = input("Enter the UID matching the secret key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") + +if len(homedir) == 0: + homedir = None +elif homedir.startswith("~"): + userdir = os.path.expanduser(homedir) + if os.path.exists(userdir) is True: + homedir = os.path.realpath(userdir) + else: + homedir = None +else: + homedir = os.path.realpath(homedir) + +if os.path.exists(homedir) is False: + homedir = None +else: + if os.path.isdir(homedir) is False: + homedir = None + else: + pass + +if homedir is not None: + c.home_dir = homedir +else: + pass + +if c.home_dir is not None: + if c.home_dir.endswith("/"): + gpgfile = "{0}{1}.gpg".format(c.home_dir, keyfile) + ascfile = "{0}{1}.asc".format(c.home_dir, keyfile) + else: + gpgfile = "{0}/{1}.gpg".format(c.home_dir, keyfile) + ascfile = "{0}/{1}.asc".format(c.home_dir, keyfile) +else: + if os.path.exists(os.environ["GNUPGHOME"]) is True: + hd = os.environ["GNUPGHOME"] + else: + try: + hd = subprocess.getoutput(gpgconfcmd) + except: + process = subprocess.Popen(gpgconfcmd.split(), + stdout=subprocess.PIPE) + procom = process.communicate() + if sys.version_info[0] == 2: + hd = procom[0].strip() + else: + hd = procom[0].decode().strip() + gpgfile = "{0}/{1}.gpg".format(hd, keyfile) + ascfile = "{0}/{1}.asc".format(hd, keyfile) + +try: + a_result = a.key_export_secret(pattern=logrus) + b_result = b.key_export_secret(pattern=logrus) +except: + a_result = a.key_export_secret(pattern=None) + b_result = b.key_export_secret(pattern=None) + +if a_result is not None: + with open(ascfile, "wb") as f: + f.write(a_result) + os.chmod(ascfile, 0o600) +else: + pass + +if b_result is not None: + with open(gpgfile, "wb") as f: + f.write(b_result) + os.chmod(gpgfile, 0o600) +else: + pass +#+END_SRC + + +*** Sending public keys to the SKS Keyservers + :PROPERTIES: + :CUSTOM_ID: howto-send-public-key + :END: + +As with the previous section on importing keys, the =hkp4py= module +adds another option with exporting keys in order to send them to the +public keyservers. + +The following example demonstrates how this may be done. + +#+BEGIN_SRC python -i +import gpg +import hkp4py +import os.path +import sys + +print(""" +This script sends one or more public keys to the SKS keyservers and is +essentially a slight variation on the export-key.py script. +""") + +c = gpg.Context(armor=True) +server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net") + +if len(sys.argv) > 2: + logrus = " ".join(sys.argv[1:]) +elif len(sys.argv) == 2: + logrus = sys.argv[1] +else: + logrus = input("Enter the UID matching the key(s) to send: ") + +if len(logrus) > 0: + try: + export_result = c.key_export(pattern=logrus) + except Exception as e: + print(e) + export_result = None +else: + export_result = c.key_export(pattern=None) + +if export_result is not None: + try: + try: + send_result = server.add(export_result) + except: + send_result = server.add(export_result.decode()) + if send_result is not None: + print(send_result) + else: + pass + except Exception as e: + print(e) +else: + pass +#+END_SRC + +An expanded version of this script with additional functions for +specifying an alternative homedir location is in the examples +directory as =send-key-to-keyserver.py=. + +The =hkp4py= module appears to handle both string and byte literal text +data equally well, but the GPGME bindings deal primarily with byte +literal data only and so this script sends in that format first, then +tries the string literal form. + + +* Basic Functions + :PROPERTIES: + :CUSTOM_ID: howto-the-basics + :END: + +The most frequently called features of any cryptographic library will +be the most fundamental tasks for encryption software. In this +section we will look at how to programmatically encrypt data, decrypt +it, sign it and verify signatures. + + +** Encryption + :PROPERTIES: + :CUSTOM_ID: howto-basic-encryption + :END: + +Encrypting is very straight forward. In the first example below the +message, =text=, is encrypted to a single recipient's key. In the +second example the message will be encrypted to multiple recipients. + + +*** Encrypting to one key + :PROPERTIES: + :CUSTOM_ID: howto-basic-encryption-single + :END: + +Once the the Context is set the main issues with encrypting data is +essentially reduced to key selection and the keyword arguments +specified in the =gpg.Context().encrypt()= method. + +Those keyword arguments are: =recipients=, a list of keys encrypted to +(covered in greater detail in the following section); =sign=, whether +or not to sign the plaintext data, see subsequent sections on signing +and verifying signatures below (defaults to =True=); =sink=, to write +results or partial results to a secure sink instead of returning it +(defaults to =None=); =passphrase=, only used when utilising symmetric +encryption (defaults to =None=); =always_trust=, used to override the +trust model settings for recipient keys (defaults to =False=); +=add_encrypt_to=, utilises any preconfigured =encrypt-to= or +=default-key= settings in the user's =gpg.conf= file (defaults to +=False=); =prepare=, prepare for encryption (defaults to =False=); +=expect_sign=, prepare for signing (defaults to =False=); =compress=, +compresses the plaintext prior to encryption (defaults to =True=). + +#+BEGIN_SRC python -i +import gpg + +a_key = "0x12345678DEADBEEF" +text = b"""Some text to test with. + +Since the text in this case must be bytes, it is most likely that +the input form will be a separate file which is opened with "rb" +as this is the simplest method of obtaining the correct data format. +""" + +c = gpg.Context(armor=True) +rkey = list(c.keylist(pattern=a_key, secret=False)) +ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=False) + +with open("secret_plans.txt.asc", "wb") as afile: + afile.write(ciphertext) +#+END_SRC + +Though this is even more likely to be used like this; with the +plaintext input read from a file, the recipient keys used for +encryption regardless of key trust status and the encrypted output +also encrypted to any preconfigured keys set in the =gpg.conf= file: + +#+BEGIN_SRC python -i +import gpg + +a_key = "0x12345678DEADBEEF" + +with open("secret_plans.txt", "rb") as afile: + text = afile.read() + +c = gpg.Context(armor=True) +rkey = list(c.keylist(pattern=a_key, secret=False)) +ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=True, + always_trust=True, + add_encrypt_to=True) + +with open("secret_plans.txt.asc", "wb") as afile: + afile.write(ciphertext) +#+END_SRC + +If the =recipients= paramater is empty then the plaintext is encrypted +symmetrically. If no =passphrase= is supplied as a parameter or via a +callback registered with the =Context()= then an out-of-band prompt +for the passphrase via pinentry will be invoked. + + +*** Encrypting to multiple keys + :PROPERTIES: + :CUSTOM_ID: howto-basic-encryption-multiple + :END: + +Encrypting to multiple keys essentially just expands upon the key +selection process and the recipients from the previous examples. + +The following example encrypts a message (=text=) to everyone with an +email address on the =gnupg.org= domain,[fn:4] but does /not/ encrypt +to a default key or other key which is configured to normally encrypt +to. + +#+BEGIN_SRC python -i +import gpg + +text = b"""Oh look, another test message. + +The same rules apply as with the previous example and more likely +than not, the message will actually be drawn from reading the +contents of a file or, maybe, from entering data at an input() +prompt. + +Since the text in this case must be bytes, it is most likely that +the input form will be a separate file which is opened with "rb" +as this is the simplest method of obtaining the correct data +format. +""" + +c = gpg.Context(armor=True) +rpattern = list(c.keylist(pattern="@gnupg.org", secret=False)) +logrus = [] + +for i in range(len(rpattern)): + if rpattern[i].can_encrypt == 1: + logrus.append(rpattern[i]) + +ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, + sign=False, always_trust=True) + +with open("secret_plans.txt.asc", "wb") as afile: + afile.write(ciphertext) +#+END_SRC + +All it would take to change the above example to sign the message +and also encrypt the message to any configured default keys would +be to change the =c.encrypt= line to this: + +#+BEGIN_SRC python -i +ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, + always_trust=True, + add_encrypt_to=True) +#+END_SRC + +The only keyword arguments requiring modification are those for which +the default values are changing. The default value of =sign= is +=True=, the default of =always_trust= is =False=, the default of +=add_encrypt_to= is =False=. + +If =always_trust= is not set to =True= and any of the recipient keys +are not trusted (e.g. not signed or locally signed) then the +encryption will raise an error. It is possible to mitigate this +somewhat with something more like this: + +#+BEGIN_SRC python -i +import gpg + +with open("secret_plans.txt.asc", "rb") as afile: + text = afile.read() + +c = gpg.Context(armor=True) +rpattern = list(c.keylist(pattern="@gnupg.org", secret=False)) +logrus = [] + +for i in range(len(rpattern)): + if rpattern[i].can_encrypt == 1: + logrus.append(rpattern[i]) + + try: + ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, + add_encrypt_to=True) + except gpg.errors.InvalidRecipients as e: + for i in range(len(e.recipients)): + for n in range(len(logrus)): + if logrus[n].fpr == e.recipients[i].fpr: + logrus.remove(logrus[n]) + else: + pass + try: + ciphertext, result, sign_result = c.encrypt(text, + recipients=logrus, + add_encrypt_to=True) + with open("secret_plans.txt.asc", "wb") as afile: + afile.write(ciphertext) + except: + pass +#+END_SRC + +This will attempt to encrypt to all the keys searched for, then remove +invalid recipients if it fails and try again. + + +** Decryption + :PROPERTIES: + :CUSTOM_ID: howto-basic-decryption + :END: + +Decrypting something encrypted to a key in one's secret keyring is +fairly straight forward. + +In this example code, however, preconfiguring either =gpg.Context()= +or =gpg.core.Context()= as =c= is unnecessary because there is no need +to modify the Context prior to conducting the decryption and since the +Context is only used once, setting it to =c= simply adds lines for no +gain. + +#+BEGIN_SRC python -i +import gpg + +ciphertext = input("Enter path and filename of encrypted file: ") +newfile = input("Enter path and filename of file to save decrypted data to: ") + +with open(ciphertext, "rb") as cfile: + try: + plaintext, result, verify_result = gpg.Context().decrypt(cfile) + except gpg.errors.GPGMEError as e: + plaintext = None + print(e) + +if plaintext is not None: + with open(newfile, "wb") as nfile: + nfile.write(plaintext) + else: + pass +#+END_SRC + +The data available in =plaintext= in this example is the decrypted +content as a byte object, the recipient key IDs and algorithms in +=result= and the results of verifying any signatures of the data in +=verify_result=. + + +** Signing text and files + :PROPERTIES: + :CUSTOM_ID: howto-basic-signing + :END: + +The following sections demonstrate how to specify keys to sign with. + + +*** Signing key selection + :PROPERTIES: + :CUSTOM_ID: howto-basic-signing-signers + :END: + +By default GPGME and the Python bindings will use the default key +configured for the user invoking the GPGME API. If there is no +default key specified and there is more than one secret key available +it may be necessary to specify the key or keys with which to sign +messages and files. + +#+BEGIN_SRC python -i +import gpg + +logrus = input("Enter the email address or string to match signing keys to: ") +hancock = gpg.Context().keylist(pattern=logrus, secret=True) +sig_src = list(hancock) +#+END_SRC + +The signing examples in the following sections include the explicitly +designated =signers= parameter in two of the five examples; once where +the resulting signature would be ASCII armoured and once where it +would not be armoured. + +While it would be possible to enter a key ID or fingerprint here to +match a specific key, it is not possible to enter two fingerprints and +match two keys since the patten expects a string, bytes or None and +not a list. A string with two fingerprints won't match any single +key. + + +*** Normal or default signing messages or files + :PROPERTIES: + :CUSTOM_ID: howto-basic-signing-normal + :END: + +The normal or default signing process is essentially the same as is +most often invoked when also encrypting a message or file. So when +the encryption component is not utilised, the result is to produce an +encoded and signed output which may or may not be ASCII armoured and +which may or may not also be compressed. + +By default compression will be used unless GnuPG detects that the +plaintext is already compressed. ASCII armouring will be determined +according to the value of =gpg.Context().armor=. + +The compression algorithm is selected in much the same way as the +symmetric encryption algorithm or the hash digest algorithm is when +multiple keys are involved; from the preferences saved into the key +itself or by comparison with the preferences with all other keys +involved. + +#+BEGIN_SRC python -i +import gpg + +text0 = """Declaration of ... something. + +""" +text = text0.encode() + +c = gpg.Context(armor=True, signers=sig_src) +signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL) + +with open("/path/to/statement.txt.asc", "w") as afile: + afile.write(signed_data.decode()) +#+END_SRC + +Though everything in this example is accurate, it is more likely that +reading the input data from another file and writing the result to a +new file will be performed more like the way it is done in the next +example. Even if the output format is ASCII armoured. + +#+BEGIN_SRC python -i +import gpg + +with open("/path/to/statement.txt", "rb") as tfile: + text = tfile.read() + +c = gpg.Context() +signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL) + +with open("/path/to/statement.txt.sig", "wb") as afile: + afile.write(signed_data) +#+END_SRC + + +*** Detached signing messages and files + :PROPERTIES: + :CUSTOM_ID: howto-basic-signing-detached + :END: + +Detached signatures will often be needed in programmatic uses of +GPGME, either for signing files (e.g. tarballs of code releases) or as +a component of message signing (e.g. PGP/MIME encoded email). + +#+BEGIN_SRC python -i +import gpg + +text0 = """Declaration of ... something. + +""" +text = text0.encode() + +c = gpg.Context(armor=True) +signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH) + +with open("/path/to/statement.txt.asc", "w") as afile: + afile.write(signed_data.decode()) +#+END_SRC + +As with normal signatures, detached signatures are best handled as +byte literals, even when the output is ASCII armoured. + +#+BEGIN_SRC python -i +import gpg + +with open("/path/to/statement.txt", "rb") as tfile: + text = tfile.read() + +c = gpg.Context(signers=sig_src) +signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH) + +with open("/path/to/statement.txt.sig", "wb") as afile: + afile.write(signed_data) +#+END_SRC + + +*** Clearsigning messages or text + :PROPERTIES: + :CUSTOM_ID: howto-basic-signing-clear + :END: + +Though PGP/in-line messages are no longer encouraged in favour of +PGP/MIME, there is still sometimes value in utilising in-line +signatures. This is where clear-signed messages or text is of value. + +#+BEGIN_SRC python -i +import gpg + +text0 = """Declaration of ... something. + +""" +text = text0.encode() + +c = gpg.Context() +signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR) + +with open("/path/to/statement.txt.asc", "w") as afile: + afile.write(signed_data.decode()) +#+END_SRC + +In spite of the appearance of a clear-signed message, the data handled +by GPGME in signing it must still be byte literals. + +#+BEGIN_SRC python -i +import gpg + +with open("/path/to/statement.txt", "rb") as tfile: + text = tfile.read() + +c = gpg.Context() +signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR) + +with open("/path/to/statement.txt.asc", "wb") as afile: + afile.write(signed_data) +#+END_SRC + + +** Signature verification + :PROPERTIES: + :CUSTOM_ID: howto-basic-verification + :END: + +Essentially there are two principal methods of verification of a +signature. The first of these is for use with the normal or default +signing method and for clear-signed messages. The second is for use +with files and data with detached signatures. + +The following example is intended for use with the default signing +method where the file was not ASCII armoured: + +#+BEGIN_SRC python -i +import gpg +import time + +filename = "statement.txt" +gpg_file = "statement.txt.gpg" + +c = gpg.Context() + +try: + data, result = c.verify(open(gpg_file)) + verified = True +except gpg.errors.BadSignatures as e: + verified = False + print(e) + +if verified is True: + for i in range(len(result.signatures)): + sign = result.signatures[i] + print("""Good signature from: +{0} +with key {1} +made at {2} +""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr, + time.ctime(sign.timestamp))) +else: + pass +#+END_SRC + +Whereas this next example, which is almost identical would work with +normal ASCII armoured files and with clear-signed files: + +#+BEGIN_SRC python -i +import gpg +import time + +filename = "statement.txt" +asc_file = "statement.txt.asc" + +c = gpg.Context() + +try: + data, result = c.verify(open(asc_file)) + verified = True +except gpg.errors.BadSignatures as e: + verified = False + print(e) + +if verified is True: + for i in range(len(result.signatures)): + sign = result.signatures[i] + print("""Good signature from: +{0} +with key {1} +made at {2} +""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr, + time.ctime(sign.timestamp))) +else: + pass +#+END_SRC + +In both of the previous examples it is also possible to compare the +original data that was signed against the signed data in =data= to see +if it matches with something like this: + +#+BEGIN_SRC python -i +with open(filename, "rb") as afile: + text = afile.read() + +if text == data: + print("Good signature.") +else: + pass +#+END_SRC + +The following two examples, however, deal with detached signatures. +With his method of verification the data that was signed does not get +returned since it is already being explicitly referenced in the first +argument of =c.verify=. So =data= is =None= and only the information +in =result= is available. + +#+BEGIN_SRC python -i +import gpg +import time + +filename = "statement.txt" +sig_file = "statement.txt.sig" + +c = gpg.Context() + +try: + data, result = c.verify(open(filename), open(sig_file)) + verified = True +except gpg.errors.BadSignatures as e: + verified = False + print(e) + +if verified is True: + for i in range(len(result.signatures)): + sign = result.signatures[i] + print("""Good signature from: +{0} +with key {1} +made at {2} +""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr, + time.ctime(sign.timestamp))) +else: + pass +#+END_SRC + +#+BEGIN_SRC python -i +import gpg +import time + +filename = "statement.txt" +asc_file = "statement.txt.asc" + +c = gpg.Context() + +try: + data, result = c.verify(open(filename), open(asc_file)) + verified = True +except gpg.errors.BadSignatures as e: + verified = False + print(e) + +if verified is True: + for i in range(len(result.signatures)): + sign = result.signatures[i] + print("""Good signature from: +{0} +with key {1} +made at {2} +""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr, + time.ctime(sign.timestamp))) +else: + pass +#+END_SRC + + +* Creating keys and subkeys + :PROPERTIES: + :CUSTOM_ID: key-generation + :END: + +The one thing, aside from GnuPG itself, that GPGME depends on, of +course, is the keys themselves. So it is necessary to be able to +generate them and modify them by adding subkeys, revoking or disabling +them, sometimes deleting them and doing the same for user IDs. + +In the following examples a key will be created for the world's +greatest secret agent, Danger Mouse. Since Danger Mouse is a secret +agent he needs to be able to protect information to =SECRET= level +clearance, so his keys will be 3072-bit keys. + +The pre-configured =gpg.conf= file which sets cipher, digest and other +preferences contains the following configuration parameters: + +#+BEGIN_SRC conf + expert + allow-freeform-uid + allow-secret-key-import + trust-model tofu+pgp + tofu-default-policy unknown + enable-large-rsa + enable-dsa2 + cert-digest-algo SHA512 + default-preference-list TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1 ZLIB BZIP2 ZIP Uncompressed + personal-cipher-preferences TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES + personal-digest-preferences SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1 + personal-compress-preferences ZLIB BZIP2 ZIP Uncompressed +#+END_SRC + + +** Primary key + :PROPERTIES: + :CUSTOM_ID: keygen-primary + :END: + +Generating a primary key uses the =create_key= method in a Context. +It contains multiple arguments and keyword arguments, including: +=userid=, =algorithm=, =expires_in=, =expires=, =sign=, =encrypt=, +=certify=, =authenticate=, =passphrase= and =force=. The defaults for +all of those except =userid=, =algorithm=, =expires_in=, =expires= and +=passphrase= is =False=. The defaults for =algorithm= and +=passphrase= is =None=. The default for =expires_in= is =0=. The +default for =expires= is =True=. There is no default for =userid=. + +If =passphrase= is left as =None= then the key will not be generated +with a passphrase, if =passphrase= is set to a string then that will +be the passphrase and if =passphrase= is set to =True= then gpg-agent +will launch pinentry to prompt for a passphrase. For the sake of +convenience, these examples will keep =passphrase= set to =None=. + +#+BEGIN_SRC python -i +import gpg + +c = gpg.Context() + +c.home_dir = "~/.gnupg-dm" +userid = "Danger Mouse <dm@secret.example.net>" + +dmkey = c.create_key(userid, algorithm="rsa3072", expires_in=31536000, + sign=True, certify=True) +#+END_SRC + +One thing to note here is the use of setting the =c.home_dir= +parameter. This enables generating the key or keys in a different +location. In this case to keep the new key data created for this +example in a separate location rather than adding it to existing and +active key store data. As with the default directory, =~/.gnupg=, any +temporary or separate directory needs the permissions set to only +permit access by the directory owner. On posix systems this means +setting the directory permissions to 700. + +The =temp-homedir-config.py= script in the HOWTO examples directory +will create an alternative homedir with these configuration options +already set and the correct directory and file permissions. + +The successful generation of the key can be confirmed via the returned +=GenkeyResult= object, which includes the following data: + +#+BEGIN_SRC python -i +print(""" + Fingerprint: {0} + Primary Key: {1} + Public Key: {2} + Secret Key: {3} + Sub Key: {4} +User IDs: {5} +""".format(dmkey.fpr, dmkey.primary, dmkey.pubkey, dmkey.seckey, dmkey.sub, + dmkey.uid)) +#+END_SRC + +Alternatively the information can be confirmed using the command line +program: + +#+BEGIN_SRC shell + bash-4.4$ gpg --homedir ~/.gnupg-dm -K + ~/.gnupg-dm/pubring.kbx + ---------------------- + sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15] + 177B7C25DB99745EE2EE13ED026D2F19E99E63AA + uid [ultimate] Danger Mouse <dm@secret.example.net> + + bash-4.4$ +#+END_SRC + +As with generating keys manually, to preconfigure expanded preferences +for the cipher, digest and compression algorithms, the =gpg.conf= file +must contain those details in the home directory in which the new key +is being generated. I used a cut down version of my own =gpg.conf= +file in order to be able to generate this: + +#+BEGIN_SRC shell + bash-4.4$ gpg --homedir ~/.gnupg-dm --edit-key 177B7C25DB99745EE2EE13ED026D2F19E99E63AA showpref quit + Secret key is available. + + sec rsa3072/026D2F19E99E63AA + created: 2018-03-15 expires: 2019-03-15 usage: SC + trust: ultimate validity: ultimate + [ultimate] (1). Danger Mouse <dm@secret.example.net> + + [ultimate] (1). Danger Mouse <dm@secret.example.net> + Cipher: TWOFISH, CAMELLIA256, AES256, CAMELLIA192, AES192, CAMELLIA128, AES, BLOWFISH, IDEA, CAST5, 3DES + Digest: SHA512, SHA384, SHA256, SHA224, RIPEMD160, SHA1 + Compression: ZLIB, BZIP2, ZIP, Uncompressed + Features: MDC, Keyserver no-modify + + bash-4.4$ +#+END_SRC + + +** Subkeys + :PROPERTIES: + :CUSTOM_ID: keygen-subkeys + :END: + +Adding subkeys to a primary key is fairly similar to creating the +primary key with the =create_subkey= method. Most of the arguments +are the same, but not quite all. Instead of the =userid= argument +there is now a =key= argument for selecting which primary key to add +the subkey to. + +In the following example an encryption subkey will be added to the +primary key. Since Danger Mouse is a security conscious secret agent, +this subkey will only be valid for about six months, half the length +of the primary key. + +#+BEGIN_SRC python -i +import gpg + +c = gpg.Context() +c.home_dir = "~/.gnupg-dm" + +key = c.get_key(dmkey.fpr, secret=True) +dmsub = c.create_subkey(key, algorithm="rsa3072", expires_in=15768000, + encrypt=True) +#+END_SRC + +As with the primary key, the results here can be checked with: + +#+BEGIN_SRC python -i +print(""" + Fingerprint: {0} + Primary Key: {1} + Public Key: {2} + Secret Key: {3} + Sub Key: {4} +User IDs: {5} +""".format(dmsub.fpr, dmsub.primary, dmsub.pubkey, dmsub.seckey, dmsub.sub, + dmsub.uid)) +#+END_SRC + +As well as on the command line with: + +#+BEGIN_SRC shell + bash-4.4$ gpg --homedir ~/.gnupg-dm -K + ~/.gnupg-dm/pubring.kbx + ---------------------- + sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15] + 177B7C25DB99745EE2EE13ED026D2F19E99E63AA + uid [ultimate] Danger Mouse <dm@secret.example.net> + ssb rsa3072 2018-03-15 [E] [expires: 2018-09-13] + + bash-4.4$ +#+END_SRC + + +** User IDs + :PROPERTIES: + :CUSTOM_ID: keygen-uids + :END: + + +*** Adding User IDs + :PROPERTIES: + :CUSTOM_ID: keygen-uids-add + :END: + +By comparison to creating primary keys and subkeys, adding a new user +ID to an existing key is much simpler. The method used to do this is +=key_add_uid= and the only arguments it takes are for the =key= and +the new =uid=. + +#+BEGIN_SRC python -i +import gpg + +c = gpg.Context() +c.home_dir = "~/.gnupg-dm" + +dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" +key = c.get_key(dmfpr, secret=True) +uid = "Danger Mouse <danger.mouse@secret.example.net>" + +c.key_add_uid(key, uid) +#+END_SRC + +Unsurprisingly the result of this is: + +#+BEGIN_SRC shell + bash-4.4$ gpg --homedir ~/.gnupg-dm -K + ~/.gnupg-dm/pubring.kbx + ---------------------- + sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15] + 177B7C25DB99745EE2EE13ED026D2F19E99E63AA + uid [ultimate] Danger Mouse <danger.mouse@secret.example.net> + uid [ultimate] Danger Mouse <dm@secret.example.net> + ssb rsa3072 2018-03-15 [E] [expires: 2018-09-13] + + bash-4.4$ +#+END_SRC + + +*** Revokinging User IDs + :PROPERTIES: + :CUSTOM_ID: keygen-uids-revoke + :END: + +Revoking a user ID is a fairly similar process, except that it uses +the =key_revoke_uid= method. + +#+BEGIN_SRC python -i +import gpg + +c = gpg.Context() +c.home_dir = "~/.gnupg-dm" + +dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" +key = c.get_key(dmfpr, secret=True) +uid = "Danger Mouse <danger.mouse@secret.example.net>" + +c.key_revoke_uid(key, uid) +#+END_SRC + + +** Key certification + :PROPERTIES: + :CUSTOM_ID: key-sign + :END: + +Since key certification is more frequently referred to as key signing, +the method used to perform this function is =key_sign=. + +The =key_sign= method takes four arguments: =key=, =uids=, +=expires_in= and =local=. The default value of =uids= is =None= and +which results in all user IDs being selected. The default value of +both =expires_in= and =local= is =False=; which results in the +signature never expiring and being able to be exported. + +The =key= is the key being signed rather than the key doing the +signing. To change the key doing the signing refer to the signing key +selection above for signing messages and files. + +If the =uids= value is not =None= then it must either be a string to +match a single user ID or a list of strings to match multiple user +IDs. In this case the matching of those strings must be precise and +it is case sensitive. + +To sign Danger Mouse's key for just the initial user ID with a +signature which will last a little over a month, do this: + +#+BEGIN_SRC python -i +import gpg + +c = gpg.Context() +uid = "Danger Mouse <dm@secret.example.net>" + +dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" +key = c.get_key(dmfpr, secret=True) +c.key_sign(key, uids=uid, expires_in=2764800) +#+END_SRC + + +* Advanced or Experimental Use Cases + :PROPERTIES: + :CUSTOM_ID: advanced-use + :END: + + +** C plus Python plus SWIG plus Cython + :PROPERTIES: + :CUSTOM_ID: cython + :END: + +In spite of the apparent incongruence of using Python bindings to a C +interface only to generate more C from the Python; it is in fact quite +possible to use the GPGME bindings with [[http://docs.cython.org/en/latest/index.html][Cython]]. Though in many cases +the benefits may not be obvious since the most computationally +intensive work never leaves the level of the C code with which GPGME +itself is interacting with. + +Nevertheless, there are some situations where the benefits are +demonstrable. One of the better and easier examples being the one of +the early examples in this HOWTO, the [[#howto-keys-counting][key counting]] code. Running that +example as an executable Python script, =keycount.py= (available in +the =examples/howto/= directory), will take a noticable amount of time +to run on most systems where the public keybox or keyring contains a +few thousand public keys. + +Earlier in the evening, prior to starting this section, I ran that +script on my laptop; as I tend to do periodically and timed it using +=time= utility, with the following results: + +#+BEGIN_SRC shell + bash-4.4$ time keycount.py + + Number of secret keys: 23 + Number of public keys: 12112 + + + real 11m52.945s + user 0m0.913s + sys 0m0.752s + + bash-4.4$ +#+END_SRC + +Sometime after that I imported another key and followed it with a +little test of Cython. This test was kept fairly basic, essentially +lifting the material from the [[http://docs.cython.org/en/latest/src/tutorial/cython_tutorial.html][Cython Basic Tutorial]] to demonstrate +compiling Python code to C. The first step was to take the example +key counting code quoted previously, essentially from the importing of +the =gpg= module to the end of the script: + +#+BEGIN_SRC python -i +import gpg + +c = gpg.Context() +seckeys = c.keylist(pattern=None, secret=True) +pubkeys = c.keylist(pattern=None, secret=False) + +seclist = list(seckeys) +secnum = len(seclist) + +publist = list(pubkeys) +pubnum = len(publist) + +print(""" + Number of secret keys: {0} + Number of public keys: {1} + +""".format(secnum, pubnum)) +#+END_SRC + +Save that into a file called =keycount.pyx= and then create a +=setup.py= file which contains this: + +#+BEGIN_SRC python -i +from distutils.core import setup +from Cython.Build import cythonize + +setup( + ext_modules = cythonize("keycount.pyx") +) +#+END_SRC + +Compile it: + +#+BEGIN_SRC shell + bash-4.4$ python setup.py build_ext --inplace + bash-4.4$ +#+END_SRC + +Then run it in a similar manner to =keycount.py=: + +#+BEGIN_SRC shell + bash-4.4$ time python3.7 -c "import keycount" + + Number of secret keys: 23 + Number of public keys: 12113 + + + real 6m47.905s + user 0m0.785s + sys 0m0.331s + + bash-4.4$ +#+END_SRC + +Cython turned =keycount.pyx= into an 81KB =keycount.o= file in the +=build/= directory, a 24KB =keycount.cpython-37m-darwin.so= file to be +imported into Python 3.7 and a 113KB =keycount.c= generated C source +code file of nearly three thousand lines. Quite a bit bigger than the +314 bytes of the =keycount.pyx= file or the full 1,452 bytes of the +full executable =keycount.py= example script. + +On the other hand it ran in nearly half the time; taking 6 minutes and +47.905 seconds to run. As opposed to the 11 minutes and 52.945 seconds +which the CPython script alone took. + +The =keycount.pyx= and =setup.py= files used to generate this example +have been added to the =examples/howto/advanced/cython/= directory +The example versions include some additional options to annotate the +existing code and to detect Cython's use. The latter comes from the +[[http://docs.cython.org/en/latest/src/tutorial/pure.html#magic-attributes-within-the-pxd][Magic Attributes]] section of the Cython documentation. + + +* Miscellaneous extras and work-arounds + :PROPERTIES: + :CUSTOM_ID: cheats-and-hacks + :END: + +Most of the things in the following sections are here simply because +there was no better place to put them, even though some are only +peripherally related to the GPGME Python bindings. Some are also +workarounds for functions not integrated with GPGME as yet. This is +especially true of the first of these, dealing with [[#group-lines][group lines]]. + + +** Group lines + :PROPERTIES: + :CUSTOM_ID: group-lines + :END: + +There is not yet an easy way to access groups configured in the +gpg.conf file from within GPGME. As a consequence these central +groupings of keys cannot be shared amongst multiple programs, such as +MUAs readily. + +The following code, however, provides a work-around for obtaining this +information in Python. + +#+BEGIN_SRC python -i +import subprocess +import sys + +if sys.platform == "win32": + gpgconfcmd = "gpgconf.exe --list-options gpg" +else: + gpgconfcmd = "gpgconf --list-options gpg" + +try: + lines = subprocess.getoutput(gpgconfcmd).splitlines() +except: + process = subprocess.Popen(gpgconfcmd.split(), stdout=subprocess.PIPE) + procom = process.communicate() + if sys.version_info[0] == 2: + lines = procom[0].splitlines() + else: + lines = procom[0].decode().splitlines() + +for i in range(len(lines)): + if lines[i].startswith("group") is True: + line = lines[i] + else: + pass + +groups = line.split(":")[-1].replace('"', '').split(',') + +group_lines = [] +group_lists = [] + +for i in range(len(groups)): + group_lines.append(groups[i].split("=")) + group_lists.append(groups[i].split("=")) + +for i in range(len(group_lists)): + group_lists[i][1] = group_lists[i][1].split() +#+END_SRC + +The result of that code is that =group_lines= is a list of lists where +=group_lines[i][0]= is the name of the group and =group_lines[i][1]= +is the key IDs of the group as a string. + +The =group_lists= result is very similar in that it is a list of +lists. The first part, =group_lists[i][0]= matches +=group_lines[i][0]= as the name of the group, but =group_lists[i][1]= +is the key IDs of the group as a string. + +A demonstration of using the =groups.py= module is also available in +the form of the executable =mutt-groups.py= script. This second +script reads all the group entries in a user's =gpg.conf= file and +converts them into crypt-hooks suitable for use with the Mutt and +Neomutt mail clients. + + +** Keyserver access for Python + :PROPERTIES: + :CUSTOM_ID: hkp4py + :END: + +The [[https://github.com/Selfnet/hkp4py][hkp4py]] module by Marcel Fest was originally a port of the old +[[https://github.com/dgladkov/python-hkp][python-hkp]] module from Python 2 to Python 3 and updated to use the +[[http://docs.python-requests.org/en/latest/index.html][requests]] module instead. It has since been modified to provide +support for Python 2.7 as well and is available via PyPI. + +Since it rewrites the =hkp= protocol prefix as =http= and =hkps= as +=https=, the module is able to be used even with servers which do not +support the full scope of keyserver functions.[fn:5] It also works quite +readily when incorporated into a [[#cython][Cython]] generated and compiled version +of any code. + + +*** Key import format + :PROPERTIES: + :CUSTOM_ID: hkp4py-strings + :END: + +The hkp4py module returns key data via requests as string literals +(=r.text=) instead of byte literals (=r.content=). This means that +the retrurned key data must be encoded to UTF-8 when importing that +key material using a =gpg.Context().key_import()= method. + +For this reason an alternative method has been added to the =search= +function of =hkp4py.KeyServer()= which returns the key in the correct +format as expected by =key_import=. When importing using this module, +it is now possible to import with this: + +#+BEGIN_SRC python -i +for key in keys: + if key.revoked is False: + gpg.Context().key_import(key.key_blob) + else: + pass +#+END_SRC + +Without that recent addition it would have been necessary to encode +the contents of each =hkp4py.KeyServer().search()[i].key= in +=hkp4py.KeyServer().search()= before trying to import it. + +An example of this is included in the [[#howto-import-key][Importing Keys]] section of this +HOWTO and the corresponding executable version of that example is +available in the =lang/python/examples/howto= directory as normal; the +executable version is the =import-keys-hkp.py= file. + + +* Copyright and Licensing + :PROPERTIES: + :CUSTOM_ID: copyright-and-license + :END: + + +** Copyright + :PROPERTIES: + :CUSTOM_ID: copyright + :END: + +Copyright © The GnuPG Project, 2018. + +Copyright (C) The GnuPG Project, 2018. + + +** Draft Editions of this HOWTO + :PROPERTIES: + :CUSTOM_ID: draft-editions + :END: + +Draft editions of this HOWTO may be periodically available directly +from the author at any of the following URLs: + +- [[https://files.au.adversary.org/crypto/gpgme-python-howto.html][GPGME Python Bindings HOWTO draft (XHTML AWS S3 SSL)]] +- [[http://files.au.adversary.org/crypto/gpgme-python-howto.html][GPGME Python Bindings HOWTO draft (XHTML AWS S3 no SSL)]] +- [[https://files.au.adversary.org/crypto/gpgme-python-howto.texi][GPGME Python Bindings HOWTO draft (Texinfo file AWS S3 SSL)]] +- [[http://files.au.adversary.org/crypto/gpgme-python-howto.texi][GPGME Python Bindings HOWTO draft (Texinfo file AWS S3 no SSL)]] +- [[https://files.au.adversary.org/crypto/gpgme-python-howto.info][GPGME Python Bindings HOWTO draft (Info file AWS S3 SSL)]] +- [[http://files.au.adversary.org/crypto/gpgme-python-howto.info][GPGME Python Bindings HOWTO draft (Info file AWS S3 no SSL)]] +- [[https://files.au.adversary.org/crypto/gpgme-python-howto.rst][GPGME Python Bindings HOWTO draft (reST file AWS S3 SSL)]] +- [[http://files.au.adversary.org/crypto/gpgme-python-howto.rst][GPGME Python Bindings HOWTO draft (reST file AWS S3 no SSL)]] +- [[https://files.au.adversary.org/crypto/gpgme-python-howto.xml][GPGME Python Bindings HOWTO draft (Docbook 4.2 AWS S3 SSL)]] +- [[http://files.au.adversary.org/crypto/gpgme-python-howto.xml][GPGME Python Bindings HOWTO draft (Docbook 4.2 AWS S3 no SSL)]] + +All of these draft versions except for one have been generated from +this document via Emacs [[https://orgmode.org/][Org mode]] and [[https://www.gnu.org/software/texinfo/][GNU Texinfo]]. Though it is likely +that the specific [[https://files.au.adversary.org/crypto/gpgme-python-howto.org][file]] [[http://files.au.adversary.org/crypto/gpgme-python-howto.org][version]] used will be on the same server with +the generated output formats. + +The one exception is the reStructuredText version, which was converted +using the latest version of Pandoc from the Org mode source file using +the following command: + +#+BEGIN_SRC shell + pandoc -f org -t rst+smart -o gpgme-python-howto.rst gpgme-python-howto.org +#+END_SRC + +In addition to these there is a significantly less frequently updated +version as a HTML [[https://files.au.adversary.org/crypto/gpgme-python-howto/webhelp/index.html][WebHelp site]] (AWS S3 SSL); generated from DITA XML +source files, which can be found in [[https://dev.gnupg.org/source/gpgme/browse/ben%252Fhowto-dita/][an alternative branch]] of the GPGME +git repository. + +These draft editions are not official documents and the version of +documentation in the master branch or which ships with released +versions is the only official documentation. Nevertheless, these +draft editions may occasionally be of use by providing more accessible +web versions which are updated between releases. They are provided on +the understanding that they may contain errors or may contain content +subject to change prior to an official release. + + +** License GPL compatible + :PROPERTIES: + :CUSTOM_ID: license + :END: + +This file is free software; as a special exception the author gives +unlimited permission to copy and/or distribute it, with or without +modifications, as long as this notice is preserved. + +This file is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY, to the extent permitted by law; without even the +implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +PURPOSE. + + +* Footnotes + +[fn:1] =short-history.org= and/or =short-history.html=. + +[fn:2] With no issues reported specific to Python 3.7, the release of +Python 3.7.1 at around the same time as GPGME 1.12.0 and the testing +with Python 3.7.1rc1, there is no reason to delay moving 3.7 ahead of +3.6 now. Production environments with more conservative requirements +will always enforce their own policies anyway and installation to each +supported minor release is quite possible too. + +[fn:3] Yes, even if you use virtualenv with everything you do in +Python. If you want to install this module as just your user account +then you will need to manually configure, compile and install the +/entire/ GnuPG stack as that user as well. This includes libraries +which are not often installed that way. It can be done and there are +circumstances under which it is worthwhile, but generally only on +POSIX systems which utilise single user mode (some even require it). + +[fn:4] You probably don't really want to do this. Searching the +keyservers for "gnupg.org" produces over 400 results, the majority of +which aren't actually at the gnupg.org domain, but just included a +comment regarding the project in their key somewhere. + +[fn:5] Such as with ProtonMail servers. This also means that +restricted servers which only advertise either HTTP or HTTPS end +points and not HKP or HKPS end points must still be identified as as +HKP or HKPS within the Python Code. The =hkp4py= module will rewrite +these appropriately when the connection is made to the server. diff --git a/lang/python/doc/src/index.org b/lang/python/doc/src/index.org new file mode 100644 index 0000000..701d986 --- /dev/null +++ b/lang/python/doc/src/index.org @@ -0,0 +1,25 @@ +#+TITLE: GNU Privacy Guard (GnuPG) Made Easy Python Bindings +#+AUTHOR: Ben McGinnes +#+LATEX_COMPILER: xelatex +#+LATEX_CLASS: article +#+LATEX_CLASS_OPTIONS: [12pt] +#+LATEX_HEADER: \usepackage{xltxtra} +#+LATEX_HEADER: \usepackage[margin=1in]{geometry} +#+LATEX_HEADER: \setmainfont[Ligatures={Common}]{Times New Roman} +#+LATEX_HEADER: \author{Ben McGinnes <ben@gnupg.org>} + + +* GPGME Python Bindings + :PROPERTIES: + :CUSTOM_ID: index + :END: + + +** Contents + :PROPERTIES: + :CUSTOM_ID: index-contents + :END: + + +- [[file:short-history.org][A short history of the project]] +- [[file:gpgme-python-howto.org][GPGME Python Bindings HOWTO]] diff --git a/lang/python/doc/src/short-history.org b/lang/python/doc/src/short-history.org new file mode 100644 index 0000000..587cb9f --- /dev/null +++ b/lang/python/doc/src/short-history.org @@ -0,0 +1,172 @@ +#+TITLE: A Short History of the GPGME bindings for Python +#+LATEX_COMPILER: xelatex +#+LATEX_CLASS: article +#+LATEX_CLASS_OPTIONS: [12pt] +#+LATEX_HEADER: \usepackage{xltxtra} +#+LATEX_HEADER: \usepackage[margin=1in]{geometry} +#+LATEX_HEADER: \setmainfont[Ligatures={Common}]{Latin Modern Roman} + +* Overview + :PROPERTIES: + :CUSTOM_ID: overview + :END: + +The GPGME Python bindings passed through many hands and numerous +phases before, after a fifteen year journey, coming full circle to +return to the source. This is a short explanation of that journey. + +** In the beginning + :PROPERTIES: + :CUSTOM_ID: in-the-begining + :END: + + In 2002 John Goerzen released PyME; Python bindings for the GPGME + module which utilised the current release of Python of the time and + SWIG.[fn:1] Shortly after creating it and ensuring it worked he stopped + supporting it, though he left his work available on his Gopher + site. + +** Keeping the flame alive + :PROPERTIES: + :CUSTOM_ID: keeping-the-flame-alive + :END: + + A couple of years later the project was picked up by Igor Belyi and + actively developed and maintained by him from 2004 to 2008. Igor's + whereabouts at the time of this document's creation are unknown, + but the current authors do hope he is well. We're assuming (or + hoping) that life did what life does and made continuing untenable. + +** Passing the torch + :PROPERTIES: + :CUSTOM_ID: passing-the-torch + :END: + + In 2014 Martin Albrecht wanted to patch a bug in the PyME code and + discovered the absence of Igor. Following a discussion on the PyME + mailing list he became the new maintainer for PyME, releasing + version 0.9.0 in May of that year. He remains the maintainer of + the original PyME release in Python 2.6 and 2.7 (available via + PyPI). + +** Coming full circle + :PROPERTIES: + :CUSTOM_ID: ouroboros + :END: + + In 2015 Ben McGinnes approached Martin about a Python 3 version, + while investigating how complex a task this would be the task ended + up being completed. A subsequent discussion with Werner Koch led + to the decision to fold the Python 3 port back into the original + GPGME release in the languages subdirectory for non-C bindings + under the module name of =pyme3=. + + In 2016 this PyME module was integrated back into the GPGME project + by Justus Winter. During the course of this work Justus adjusted + the port to restore limited support for Python 2, but not as many + minor point releases as the original PyME package supports. During + the course of this integration the package was renamed to more + accurately reflect its status as a component of GPGME. The =pyme3= + module was renamed to =gpg= and adopted by the upstream GnuPG team. + + In 2017 Justus departed G10code and the GnuPG team. Following this + Ben returned to maintain of gpgme Python bindings and continue + building them from that point. + +* Relics of the past + :PROPERTIES: + :CUSTOM_ID: relics-past + :END: + +There are a few things, in addition to code specific factors, such as +SWIG itself, which are worth noting here. + +** The Annoyances of Git + :PROPERTIES: + :CUSTOM_ID: the-annoyances-of-git + :END: + + As anyone who has ever worked with git knows, submodules are + horrible way to deal with pretty much anything. In the interests + of avoiding migraines, that was skipped with addition of the PyME + code to GPGME. + + Instead the files were added to a subdirectory of the =lang/= + directory, along with a copy of the entire git log up to that point + as a separate file within the =lang/python/docs/= directory.[fn:2] + As the log for PyME is nearly 100KB and the log for GPGME is + approximately 1MB, this would cause considerable bloat, as well as + some confusion, should the two be merged. + + Hence the unfortunate, but necessary, step to simply move the + files. A regular repository version has been maintained should it + be possible to implement this better in the future. + +** The Perils of PyPI + :PROPERTIES: + :CUSTOM_ID: the-perils-of-pypi + :END: + + The early port of the Python 2 =pyme= module as =pyme3= was never + added to PyPI while the focus remained on development and testing + during 2015 and early 2016. Later in 2016, however, when Justus + completed his major integration work and subsequently renamed the + module from =pyme3= to =gpg=, some prior releases were also + provided through PyPI. + + Since these bindings require a matching release of the GPGME + libraries in order to function, it was determined that there was + little benefit in also providing a copy through PyPI since anyone + obtaining the GPGME source code would obtain the Python bindings + source code at the same time. Whereas there was the potential to + sew confusion amongst Python users installing the module from PyPI, + only to discover that without the relevant C files, header files or + SWIG compiled binaries, the Python module did them little good. + + There are only two files on PyPI which might turn up in a search + for this module or a sample of its content: + + 1. gpg (1.8.0) - Python bindings for GPGME GnuPG cryptography library + 2. pyme (0.9.0) - Python support for GPGME GnuPG cryptography library + +*** GPG 1·8·0 - Python bindings for GPGME GnuPG cryptography library + :PROPERTIES: + :CUSTOM_ID: pypi-gpgme-180 + :END: + + This is the most recent version to reach PyPI and is the version + of the official Pyhon bindings which shipped with GPGME 1.8.0. If + you have GPGME 1.8.0 installed and /only/ 1.8.0 installed, then it + is probably safe to use this copy from PyPI. + + As there have been a lot of changes since the release of GPGME + 1.8.0, the GnuPG Project recommends not using this version of the + module and instead installing the current version of GPGME along + with the Python bindings included with that package. + +*** PyME 0·9·0 - Python support for GPGME GnuPG cryptography library + :PROPERTIES: + :CUSTOM_ID: pypi-gpgme-90 + :END: + + This is the last release of the PyME bindings maintained by Martin + Albrecht and is only compatible with Python 2, it will not work + with Python 3. This is the version of the software from which the + port from Python 2 to Python 3 code was made in 2015. + + Users of the more recent Python bindings will recognise numerous + points of similarity, but also significant differences. It is + likely that the more recent official bindings will feel "more + pythonic." + + For those using Python 2, there is essentially no harm in using + this module, but it may lack a number of more recent features + added to GPGME. + +* Footnotes + +[fn:1] In all likelihood thos would have been Python 2.2 or possibly +Python 2.3. + +[fn:2] The entire PyME git log and other preceding VCS logs are +located in the =gpgme/lang/python/docs/old-commits.log= file. diff --git a/lang/python/doc/texinfo/gpgme-python-howto.texi b/lang/python/doc/texinfo/gpgme-python-howto.texi new file mode 100644 index 0000000..40beb7a --- /dev/null +++ b/lang/python/doc/texinfo/gpgme-python-howto.texi @@ -0,0 +1,3155 @@ +\input texinfo @c -*- texinfo -*- +@c %**start of header +@setfilename gpgme-python-howto.info +@settitle GNU Privacy Guard (GnuPG) Made Easy Python Bindings HOWTO (English) +@documentencoding UTF-8 +@documentlanguage en +@c %**end of header + +@finalout +@titlepage +@title GNU Privacy Guard (GnuPG) Made Easy Python Bindings HOWTO (English) +@author Ben McGinnes +@end titlepage + +@contents + +@ifnottex +@node Top +@top GNU Privacy Guard (GnuPG) Made Easy Python Bindings HOWTO (English) +@end ifnottex + +@menu +* Introduction:: +* GPGME Concepts:: +* GPGME Python bindings installation:: +* Fundamentals:: +* Working with keys:: +* Basic Functions:: +* Creating keys and subkeys:: +* Advanced or Experimental Use Cases:: +* Miscellaneous extras and work-arounds:: +* Copyright and Licensing:: + +@detailmenu +--- The Detailed Node Listing --- + +Introduction + +* Python 2 versus Python 3:: +* Examples:: +* Unofficial Drafts:: +* What's New:: + +What's New + +* New in GPGME 1·12·0:: + +GPGME Concepts + +* A C API:: +* Python bindings:: +* Difference between the Python bindings and other GnuPG Python packages:: + +Difference between the Python bindings and other GnuPG Python packages + +* The python-gnupg package maintained by Vinay Sajip:: +* The gnupg package created and maintained by Isis Lovecruft:: +* The PyME package maintained by Martin Albrecht:: + +GPGME Python bindings installation + +* No PyPI:: +* Requirements:: +* Installation:: +* Known Issues:: + +Requirements + +* Recommended Additions:: + +Installation + +* Installing GPGME:: + +Known Issues + +* Breaking Builds:: +* Reinstalling Responsibly:: +* Multiple installations:: +* Won't Work With Windows:: +* CFFI is the Best™ and GPGME should use it instead of SWIG:: +* Virtualised Environments:: + +Fundamentals + +* No REST:: +* Context:: + +Working with keys + +* Key selection:: +* Get key:: +* Importing keys:: +* Exporting keys:: + +Key selection + +* Counting keys:: + +Importing keys + +* Working with ProtonMail:: +* Importing with HKP for Python:: +* Importing from ProtonMail with HKP for Python:: + +Exporting keys + +* Exporting public keys:: +* Exporting secret keys:: +* Sending public keys to the SKS Keyservers:: + +Basic Functions + +* Encryption:: +* Decryption:: +* Signing text and files:: +* Signature verification:: + +Encryption + +* Encrypting to one key:: +* Encrypting to multiple keys:: + +Signing text and files + +* Signing key selection:: +* Normal or default signing messages or files:: +* Detached signing messages and files:: +* Clearsigning messages or text:: + +Creating keys and subkeys + +* Primary key:: +* Subkeys:: +* User IDs:: +* Key certification:: + +User IDs + +* Adding User IDs:: +* Revokinging User IDs:: + +Advanced or Experimental Use Cases + +* C plus Python plus SWIG plus Cython:: + +Miscellaneous extras and work-arounds + +* Group lines:: +* Keyserver access for Python:: + +Keyserver access for Python + +* Key import format:: + +Copyright and Licensing + +* Copyright:: +* Draft Editions of this HOWTO:: +* License GPL compatible:: + +@end detailmenu +@end menu + +@node Introduction +@chapter Introduction + +@multitable {aaaaaaaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} +@item Version: +@tab 0.1.4 +@item GPGME Version: +@tab 1.12.0 +@item Author: +@tab @uref{https://gnupg.org/people/index.html#sec-1-5, Ben McGinnes} <ben@@gnupg.org> +@item Author GPG Key: +@tab DB4724E6FA4286C92B4E55C4321E4E2373590E5D +@item Language: +@tab Australian English, British English +@item xml:lang: +@tab en-AU, en-GB, en +@end multitable + +This document provides basic instruction in how to use the GPGME +Python bindings to programmatically leverage the GPGME library. + +@menu +* Python 2 versus Python 3:: +* Examples:: +* Unofficial Drafts:: +* What's New:: +@end menu + +@node Python 2 versus Python 3 +@section Python 2 versus Python 3 + +Though the GPGME Python bindings themselves provide support for both +Python 2 and 3, the focus is unequivocally on Python 3 and +specifically from Python 3.4 and above. As a consequence all the +examples and instructions in this guide use Python 3 code. + +Much of it will work with Python 2, but much of it also deals with +Python 3 byte literals, particularly when reading and writing data. +Developers concentrating on Python 2.7, and possibly even 2.6, will +need to make the appropriate modifications to support the older string +and unicode types as opposed to bytes. + +There are multiple reasons for concentrating on Python 3; some of +which relate to the immediate integration of these bindings, some of +which relate to longer term plans for both GPGME and the python +bindings and some of which relate to the impending EOL period for +Python 2.7. Essentially, though, there is little value in tying the +bindings to a version of the language which is a dead end and the +advantages offered by Python 3 over Python 2 make handling the data +types with which GPGME deals considerably easier. + +@node Examples +@section Examples + +All of the examples found in this document can be found as Python 3 +scripts in the @samp{lang/python/examples/howto} directory. + +@node Unofficial Drafts +@section Unofficial Drafts + +In addition to shipping with each release of GPGME, there is a section +on locations to read or download @ref{Draft Editions of this HOWTO, , draft editions} of this document from +at the end of it. These are unofficial versions produced in between +major releases. + +@node What's New +@section What's New + +The most obviously new point for those reading this guide is this +section on other new things, but that's hardly important. Not given +all the other things which spurred the need for adding this section +and its subsections. + +@menu +* New in GPGME 1·12·0:: +@end menu + +@node New in GPGME 1·12·0 +@subsection New in GPGME 1·12·0 + +There have been quite a number of additions to GPGME and the Python +bindings to it since the last release of GPGME with versions 1.11.0 +and 1.11.1 in April, 2018. + +The bullet points of new additiions are: + +@itemize +@item +an expanded section on @ref{Installation, , installing} and @ref{Known Issues, , troubleshooting} the Python +bindings. +@item +The release of Python 3.7.0; which appears to be working just fine +with our bindings, in spite of intermittent reports of problems for +many other Python projects with that new release. +@item +Python 3.7 has been moved to the head of the specified python +versions list in the build process. +@item +In order to fix some other issues, there are certain underlying +functions which are more exposed through the @ref{Context, , gpg.Context()}, but +ongoing documentation ought to clarify that or otherwise provide the +best means of using the bindings. Some additions to @samp{gpg.core} and +the @samp{Context()}, however, were intended (see below). +@item +Continuing work in identifying and confirming the cause of +oft-reported @ref{Won't Work With Windows, , problems installing the Python bindings on Windows}. +@item +GSOC: Google's Surreptitiously Ordered Conscription @dots{} erm @dots{} oh, +right; Google's Summer of Code. Though there were two hopeful +candidates this year; only one ended up involved with the GnuPG +Project directly, the other concentrated on an unrelated third party +project with closer ties to one of the GNU/Linux distributions than +to the GnuPG Project. Thus the Python bindings benefited from GSOC +participant Jacob Adams, who added the key@math{_import} function; building +on prior work by Tobias Mueller. +@item +Several new methods functions were added to the gpg.Context(), +including: @ref{Importing keys, , key@math{_import}}, @ref{Exporting keys, , key@math{_export}}, @ref{Exporting public keys, , key@math{_export}@math{_minimal}} and +@ref{Exporting secret keys, , key@math{_export}@math{_secret}}. +@item +Importing and exporting examples include versions integrated with +Marcel Fest's recently released @uref{https://github.com/Selfnet/hkp4py, HKP for Python} module. Some +@ref{Keyserver access for Python, , additional notes on this module} are included at the end of the HOWTO. +@item +Instructions for dealing with semi-walled garden implementations +like ProtonMail are also included. This is intended to make things +a little easier when communicating with users of ProtonMail's +services and should not be construed as an endorsement of said +service. The GnuPG Project neither favours, nor disfavours +ProtonMail and the majority of this deals with interacting with the +ProtonMail keyserver. +@item +Semi-formalised the location where @ref{Draft Editions of this HOWTO, , draft versions} of this HOWTO may +periodically be accessible. This is both for the reference of +others and testing the publishing of the document itself. Renamed +this file at around the same time. +@item +The Texinfo documentation build configuration has been replicated +from the parent project in order to make to maintain consistency +with that project (and actually ship with each release). +@item +a reStructuredText (@samp{.rst}) version is also generated for Python +developers more used to and comfortable with that format as it is +the standard Python documentation format and Python developers may +wish to use it with Sphinx. Please note that there has been no +testing of the reStructuredText version with Sphinx at all. The +reST file was generated by the simple expedient of using @uref{https://pandoc.org/, Pandoc}. +@item +Added a new section for @ref{Advanced or Experimental Use Cases, , advanced or experimental use}. +@item +Began the advanced use cases with @ref{C plus Python plus SWIG plus Cython, , a section} on using the module with +@uref{http://cython.org/, Cython}. +@item +Added a number of new scripts to the @samp{example/howto/} directory; +some of which may be in advance of their planned sections of the +HOWTO (and some are just there because it seemed like a good idea at +the time). +@item +Cleaned up a lot of things under the hood. +@end itemize + +@node GPGME Concepts +@chapter GPGME Concepts + +@menu +* A C API:: +* Python bindings:: +* Difference between the Python bindings and other GnuPG Python packages:: +@end menu + +@node A C API +@section A C API + +Unlike many modern APIs with which programmers will be more familiar +with these days, the GPGME API is a C API. The API is intended for +use by C coders who would be able to access its features by including +the @samp{gpgme.h} header file with their own C source code and then access +its functions just as they would any other C headers. + +This is a very effective method of gaining complete access to the API +and in the most efficient manner possible. It does, however, have the +drawback that it cannot be directly used by other languages without +some means of providing an interface to those languages. This is +where the need for bindings in various languages stems. + +@node Python bindings +@section Python bindings + +The Python bindings for GPGME provide a higher level means of +accessing the complete feature set of GPGME itself. It also provides +a more pythonic means of calling these API functions. + +The bindings are generated dynamically with SWIG and the copy of +@samp{gpgme.h} generated when GPGME is compiled. + +This means that a version of the Python bindings is fundamentally tied +to the exact same version of GPGME used to generate that copy of +@samp{gpgme.h}. + +@node Difference between the Python bindings and other GnuPG Python packages +@section Difference between the Python bindings and other GnuPG Python packages + +There have been numerous attempts to add GnuPG support to Python over +the years. Some of the most well known are listed here, along with +what differentiates them. + +@menu +* The python-gnupg package maintained by Vinay Sajip:: +* The gnupg package created and maintained by Isis Lovecruft:: +* The PyME package maintained by Martin Albrecht:: +@end menu + +@node The python-gnupg package maintained by Vinay Sajip +@subsection The python-gnupg package maintained by Vinay Sajip + +This is arguably the most popular means of integrating GPG with +Python. The package utilises the @samp{subprocess} module to implement +wrappers for the @samp{gpg} and @samp{gpg2} executables normally invoked on the +command line (@samp{gpg.exe} and @samp{gpg2.exe} on Windows). + +The popularity of this package stemmed from its ease of use and +capability in providing the most commonly required features. + +Unfortunately it has been beset by a number of security issues in the +past; most of which stemmed from using unsafe methods of accessing the +command line via the @samp{subprocess} calls. While some effort has been +made over the last two to three years (as of 2018) to mitigate this, +particularly by no longer providing shell access through those +subprocess calls, the wrapper is still somewhat limited in the scope +of its GnuPG features coverage. + +The python-gnupg package is available under the MIT license. + +@node The gnupg package created and maintained by Isis Lovecruft +@subsection The gnupg package created and maintained by Isis Lovecruft + +In 2015 Isis Lovecruft from the Tor Project forked and then +re-implemented the python-gnupg package as just gnupg. This new +package also relied on subprocess to call the @samp{gpg} or @samp{gpg2} +binaries, but did so somewhat more securely. + +The naming and version numbering selected for this package, however, +resulted in conflicts with the original python-gnupg and since its +functions were called in a different manner to python-gnupg, the +release of this package also resulted in a great deal of consternation +when people installed what they thought was an upgrade that +subsequently broke the code relying on it. + +The gnupg package is available under the GNU General Public License +version 3.0 (or any later version). + +@node The PyME package maintained by Martin Albrecht +@subsection The PyME package maintained by Martin Albrecht + +This package is the origin of these bindings, though they are somewhat +different now. For details of when and how the PyME package was +folded back into GPGME itself see the @uref{short-history.org, Short History} document.@footnote{@samp{short-history.org} and/or @samp{short-history.html}.} + +The PyME package was first released in 2002 and was also the first +attempt to implement a low level binding to GPGME. In doing so it +provided access to considerably more functionality than either the +@samp{python-gnupg} or @samp{gnupg} packages. + +The PyME package is only available for Python 2.6 and 2.7. + +Porting the PyME package to Python 3.4 in 2015 is what resulted in it +being folded into the GPGME project and the current bindings are the +end result of that effort. + +The PyME package is available under the same dual licensing as GPGME +itself: the GNU General Public License version 2.0 (or any later +version) and the GNU Lesser General Public License version 2.1 (or any +later version). + +@node GPGME Python bindings installation +@chapter GPGME Python bindings installation + +@menu +* No PyPI:: +* Requirements:: +* Installation:: +* Known Issues:: +@end menu + +@node No PyPI +@section No PyPI + +Most third-party Python packages and modules are available and +distributed through the Python Package Installer, known as PyPI. + +Due to the nature of what these bindings are and how they work, it is +infeasible to install the GPGME Python bindings in the same way. + +This is because the bindings use SWIG to dynamically generate C +bindings against @samp{gpgme.h} and @samp{gpgme.h} is generated from +@samp{gpgme.h.in} at compile time when GPGME is built from source. Thus to +include a package in PyPI which actually built correctly would require +either statically built libraries for every architecture bundled with +it or a full implementation of C for each architecture. + +See the additional notes regarding @ref{CFFI is the Best™ and GPGME should use it instead of SWIG, , CFFI and SWIG} at the end of this +section for further details. + +@node Requirements +@section Requirements + +The GPGME Python bindings only have three requirements: + +@enumerate +@item +A suitable version of Python 2 or Python 3. With Python 2 that +means CPython 2.7 and with Python 3 that means CPython 3.4 or +higher. +@item +@uref{https://www.swig.org, SWIG}. +@item +GPGME itself. Which also means that all of GPGME's dependencies +must be installed too. +@end enumerate + +@menu +* Recommended Additions:: +@end menu + +@node Recommended Additions +@subsection Recommended Additions + +Though none of the following are absolute requirements, they are all +recommended for use with the Python bindings. In some cases these +recommendations refer to which version(s) of CPython to use the +bindings with, while others refer to third party modules which provide +a significant advantage in some way. + +@enumerate +@item +If possible, use Python 3 instead of 2. +@item +Favour a more recent version of Python since even 3.4 is due to +reach EOL soon. In production systems and services, Python 3.6 +should be robust enough to be relied on. +@item +If possible add the following Python modules which are not part of +the standard library: @uref{http://docs.python-requests.org/en/latest/index.html, Requests}, @uref{http://cython.org/, Cython} and @uref{https://github.com/Selfnet/hkp4py, hkp4py}. Chances are +quite high that at least the first one and maybe two of those will +already be installed. +@end enumerate + +Note that, as with Cython, some of the planned additions to the +@ref{Advanced or Experimental Use Cases, , Advanced} section, will bring with them additional requirements. Most +of these will be fairly well known and commonly installed ones, +however, which are in many cases likely to have already been installed +on many systems or be familiar to Python programmers. + +@node Installation +@section Installation + +Installing the Python bindings is effectively achieved by compiling +and installing GPGME itself. + +Once SWIG is installed with Python and all the dependencies for GPGME +are installed you only need to confirm that the version(s) of Python +you want the bindings installed for are in your @samp{$PATH}. + +By default GPGME will attempt to install the bindings for the most +recent or highest version number of Python 2 and Python 3 it detects +in @samp{$PATH}. It specifically checks for the @samp{python} and @samp{python3} +executables first and then checks for specific version numbers. + +For Python 2 it checks for these executables in this order: @samp{python}, +@samp{python2} and @samp{python2.7}. + +For Python 3 it checks for these executables in this order: @samp{python3}, + @samp{python3.7}, @samp{python3.6}, @samp{python3.5} and @samp{python3.4}.@footnote{With no issues reported specific to Python 3.7, the release of +Python 3.7.1 at around the same time as GPGME 1.12.0 and the testing +with Python 3.7.1rc1, there is no reason to delay moving 3.7 ahead of +3.6 now. Production environments with more conservative requirements +will always enforce their own policies anyway and installation to each +supported minor release is quite possible too.} + +On systems where @samp{python} is actually @samp{python3} and not @samp{python2} it +may be possible that @samp{python2} may be overlooked, but there have been +no reports of that actually occurring as yet. + +In the three months or so since the release of Python 3.7.0 there has +been extensive testing and work with these bindings with no issues +specifically relating to the new version of Python or any of the new +features of either the language or the bindings. This has also been +the case with Python 3.7.1rc1. With that in mind and given the +release of Python 3.7.1 is scheduled for around the same time as GPGME +1.12.0, the order of preferred Python versions has been changed to +move Python 3.7 ahead of Python 3.6. + +@menu +* Installing GPGME:: +@end menu + +@node Installing GPGME +@subsection Installing GPGME + +See the GPGME @samp{README} file for details of how to install GPGME from +source. + +@node Known Issues +@section Known Issues + +There are a few known issues with the current build process and the +Python bindings. For the most part these are easily addressed should +they be encountered. + +@menu +* Breaking Builds:: +* Reinstalling Responsibly:: +* Multiple installations:: +* Won't Work With Windows:: +* CFFI is the Best™ and GPGME should use it instead of SWIG:: +* Virtualised Environments:: +@end menu + +@node Breaking Builds +@subsection Breaking Builds + +Occasionally when installing GPGME with the Python bindings included +it may be observed that the @samp{make} portion of that process induces a +large very number of warnings and, eventually errors which end that +part of the build process. Yet following that with @samp{make check} and +@samp{make install} appears to work seamlessly. + +The cause of this is related to the way SWIG needs to be called to +dynamically generate the C bindings for GPGME in the first place. So +the entire process will always produce @samp{lang/python/python2-gpg/} and +@samp{lang/python/python3-gpg/} directories. These should contain the +build output generated during compilation, including the complete +bindings and module installed into @samp{site-packages}. + +Occasionally the errors in the early part or some other conflict +(e.g. not installing as @strong{@emph{root}} or @strong{@emph{su}}) may result in nothing +being installed to the relevant @samp{site-packages} directory and the +build directory missing a lot of expected files. Even when this +occurs, the solution is actually quite simple and will always work. + +That solution is simply to run the following commands as either the +@strong{root} user or prepended with @samp{sudo -H}@footnote{Yes, even if you use virtualenv with everything you do in +Python. If you want to install this module as just your user account +then you will need to manually configure, compile and install the +@emph{entire} GnuPG stack as that user as well. This includes libraries +which are not often installed that way. It can be done and there are +circumstances under which it is worthwhile, but generally only on +POSIX systems which utilise single user mode (some even require it).} in the @samp{lang/python/} +directory: + +@example +/path/to/pythonX.Y setup.py build +/path/to/pythonX.Y setup.py build +/path/to/pythonX.Y setup.py install +@end example + +Yes, the build command does need to be run twice. Yes, you still need +to run the potentially failing or incomplete steps during the +@samp{configure}, @samp{make} and @samp{make install} steps with installing GPGME. +This is because those steps generate a lot of essential files needed, +both by and in order to create, the bindings (including both the +@samp{setup.py} and @samp{gpgme.h} files). + +@enumerate +@item +IMPORTANT Note + + +If specifying a selected number of languages to create bindings for, +try to leave Python last. Currently the majority of the other +language bindings are also preceding Python of either version when +listed alphabetically and so that just happens by default currently. + +If Python is set to precede one of the other languages then it is +possible that the errors described here may interrupt the build +process before generating bindings for those other languages. In +these cases it may be preferable to configure all preferred language +bindings separately with alternative @samp{configure} steps for GPGME using +the @samp{--enable-languages=$LANGUAGE} option. +@end enumerate + +@node Reinstalling Responsibly +@subsection Reinstalling Responsibly + +Regardless of whether you're installing for one version of Python or +several, there will come a point where reinstallation is required. +With most Python module installations, the installed files go into the +relevant site-packages directory and are then forgotten about. Then +the module is upgraded, the new files are copied over the old and +that's the end of the matter. + +While the same is true of these bindings, there have been intermittent +issues observed on some platforms which have benefited significantly +from removing all the previous installations of the bindings before +installing the updated versions. + +Removing the previous version(s) is simply a matter of changing to the +relevant @samp{site-packages} directory for the version of Python in +question and removing the @samp{gpg/} directory and any accompanying +egg-info files for that module. + +In most cases this will require root or administration privileges on +the system, but the same is true of installing the module in the first +place. + +@node Multiple installations +@subsection Multiple installations + +For a veriety of reasons it may be either necessary or just preferable +to install the bindings to alternative installed Python versions which +meet the requirements of these bindings. + +On POSIX systems this will generally be most simply achieved by +running the manual installation commands (build, build, install) as +described in the previous section for each Python installation the +bindings need to be installed to. + +As per the SWIG documentation: the compilers, libraries and runtime +used to build GPGME and the Python Bindings @strong{must} match those used to +compile Python itself, including the version number(s) (at least going +by major version numbers and probably minor numbers too). + +On most POSIX systems, including OS X, this will very likely be the +case in most, if not all, cases. + +@node Won't Work With Windows +@subsection Won't Work With Windows + +There are semi-regular reports of Windows users having considerable +difficulty in installing and using the Python bindings at all. Very +often, possibly even always, these reports come from Cygwin users +and/or MinGW users and/or Msys2 users. Though not all of them have +been confirmed, it appears that these reports have also come from +people who installed Python using the Windows installer files from the +@uref{https://python.org, Python website} (i.e. mostly MSI installers, sometimes self-extracting +@samp{.exe} files). + +The Windows versions of Python are not built using Cygwin, MinGW or +Msys2; they're built using Microsoft Visual Studio. Furthermore the +version used is @emph{considerably} more advanced than the version which +MinGW obtained a small number of files from many years ago in order to +be able to compile anything at all. Not only that, but there are +changes to the version of Visual Studio between some micro releases, +though that is is particularly the case with Python 2.7, since it has +been kept around far longer than it should have been. + +There are two theoretical solutions to this issue: + +@enumerate +@item +Compile and install the GnuPG stack, including GPGME and the +Python bibdings using the same version of Microsoft Visual Studio +used by the Python Foundation to compile the version of Python +installed. + +If there are multiple versions of Python then this will need to be +done with each different version of Visual Studio used. + +@item +Compile and install Python using the same tools used by choice, +such as MinGW or Msys2. +@end enumerate + +Do @strong{not} use the official Windows installer for Python unless +following the first method. + +In this type of situation it may even be for the best to accept that +there are less limitations on permissive software than free software +and simply opt to use a recent version of the Community Edition of +Microsoft Visual Studio to compile and build all of it, no matter +what. + +Investigations into the extent or the limitations of this issue are +ongoing. + +@node CFFI is the Best™ and GPGME should use it instead of SWIG +@subsection CFFI is the Best™ and GPGME should use it instead of SWIG + +There are many reasons for favouring @uref{https://cffi.readthedocs.io/en/latest/overview.html, CFFI} and proponents of it are +quite happy to repeat these things as if all it would take to switch +from SWIG to CFFI is repeating that list as if it were a new concept. + +The fact is that there are things which Python's CFFI implementation +cannot handle in the GPGME C code. Beyond that there are features of +SWIG which are simply not available with CFFI at all. SWIG generates +the bindings to Python using the @samp{gpgme.h} file, but that file is not +a single version shipped with each release, it too is generated when +GPGME is compiled. + +CFFI is currently unable to adapt to such a potentially mutable +codebase. If there were some means of applying SWIG's dynamic code +generation to produce the Python/CFFI API modes of accessing the GPGME +libraries (or the source source code directly), but such a thing does +not exist yet either and it currently appears that work is needed in +at least one of CFFI's dependencies before any of this can be +addressed. + +So if you're a massive fan of CFFI; that's great, but if you want this +project to switch to CFFI then rather than just insisting that it +should, I'd suggest you volunteer to bring CFFI up to the level this +project needs. + +If you're actually seriously considering doing so, then I'd suggest +taking the @samp{gpgme-tool.c} file in the GPGME @samp{src/} directory and +getting that to work with any of the CFFI API methods (not the ABI +methods, they'll work with pretty much anything). When you start +running into trouble with "ifdefs" then you'll know what sort of +things are lacking. That doesn't even take into account the amount of +work saved via SWIG's code generation techniques either. + +@node Virtualised Environments +@subsection Virtualised Environments + +It is fairly common practice amongst Python developers to, as much as +possible, use packages like virtualenv to keep various things that are +to be installed from interfering with each other. Given how much of +the GPGME bindings is often at odds with the usual pythonic way of +doing things, it stands to reason that this would be called into +question too. + +As it happens the answer as to whether or not the bindings can be used +with virtualenv, the answer is both yes and no. + +In general we recommend installing to the relevant path and matching +prefix of GPGME itself. Which means that when GPGME, and ideally the +rest of the GnuPG stack, is installed to a prefix like @samp{/usr/local} or +@samp{/opt/local} then the bindings would need to be installed to the main +Python installation and not a virtualised abstraction. Attempts to +separate the two in the past have been known to cause weird and +intermittent errors ranging from minor annoyances to complete failures +in the build process. + +As a consequence we only recommend building with and installing to the +main Python installations within the same prefix as GPGME is installed +to or which are found by GPGME's configuration stage immediately prior +to running the make commands. Which is exactly what the compiling and +installing process of GPGME does by default. + +Once that is done, however, it appears that a copy the compiled module +may be installed into a virtualenv of the same major and minor version +matching the build. Alternatively it is possible to utilise a +@samp{sites.pth} file in the @samp{site-packages/} directory of a viertualenv +installation, which links back to the system installations +corresponding directory in order to import anything installed system +wide. This may or may not be appropriate on a case by case basis. + +Though extensive testing of either of these options is not yet +complete, preliminary testing of them indicates that both are viable +as long as the main installation is complete. Which means that +certain other options normally restricted to virtual environments are +also available, including integration with pythonic test suites +(e.g. @uref{https://docs.pytest.org/en/latest/index.html, pytest}) and other large projects. + +That said, it is worth reiterating the warning regarding non-standard +installations. If one were to attempt to install the bindings only to +a virtual environment without somehow also including the full GnuPG +stack (or enough of it as to include GPGME) then it is highly likely +that errors would be encountered at some point and more than a little +likely that the build process itself would break. + +If a degree of separation from the main operating system is still +required in spite of these warnings, then consider other forms of +virtualisation. Either a virtual machine (e.g. @uref{https://www.virtualbox.org/, VirtualBox}), a +hardware emulation layer (e.g. @uref{https://www.qemu.org/, QEMU}) or an application container +(e.g. @uref{https://www.docker.com/why-docker, Docker}). + +Finally it should be noted that the limited tests conducted thus far +have been using the @samp{virtualenv} command in a new directory to create +the virtual python environment. As opposed to the standard @samp{python3 +-m venv} and it is possible that this will make a difference depending +on the system and version of Python in use. Another option is to run +the command @samp{python3 -m virtualenv /path/to/install/virtual/thingy} +instead. + +@node Fundamentals +@chapter Fundamentals + +Before we can get to the fun stuff, there are a few matters regarding +GPGME's design which hold true whether you're dealing with the C code +directly or these Python bindings. + +@menu +* No REST:: +* Context:: +@end menu + +@node No REST +@section No REST + +The first part of which is or will be fairly blatantly obvious upon +viewing the first example, but it's worth reiterating anyway. That +being that this API is @emph{@strong{not}} a REST API. Nor indeed could it ever +be one. + +Most, if not all, Python programmers (and not just Python programmers) +know how easy it is to work with a RESTful API. In fact they've +become so popular that many other APIs attempt to emulate REST-like +behaviour as much as they are able. Right down to the use of JSON +formatted output to facilitate the use of their API without having to +retrain developers. + +This API does not do that. It would not be able to do that and also +provide access to the entire C API on which it's built. It does, +however, provide a very pythonic interface on top of the direct +bindings and it's this pythonic layer that this HOWTO deals with. + +@node Context +@section Context + +One of the reasons which prevents this API from being RESTful is that +most operations require more than one instruction to the API to +perform the task. Sure, there are certain functions which can be +performed simultaneously, particularly if the result known or strongly +anticipated (e.g. selecting and encrypting to a key known to be in the +public keybox). + +There are many more, however, which cannot be manipulated so readily: +they must be performed in a specific sequence and the result of one +operation has a direct bearing on the outcome of subsequent +operations. Not merely by generating an error either. + +When dealing with this type of persistent state on the web, full of +both the RESTful and REST-like, it's most commonly referred to as a +session. In GPGME, however, it is called a context and every +operation type has one. + +@node Working with keys +@chapter Working with keys + +@menu +* Key selection:: +* Get key:: +* Importing keys:: +* Exporting keys:: +@end menu + +@node Key selection +@section Key selection + +Selecting keys to encrypt to or to sign with will be a common +occurrence when working with GPGMe and the means available for doing +so are quite simple. + +They do depend on utilising a Context; however once the data is +recorded in another variable, that Context does not need to be the +same one which subsequent operations are performed. + +The easiest way to select a specific key is by searching for that +key's key ID or fingerprint, preferably the full fingerprint without +any spaces in it. A long key ID will probably be okay, but is not +advised and short key IDs are already a problem with some being +generated to match specific patterns. It does not matter whether the +pattern is upper or lower case. + +So this is the best method: + +@example +import gpg + +k = gpg.Context().keylist(pattern="258E88DCBD3CD44D8E7AB43F6ECB6AF0DEADBEEF") +keys = list(k) +@end example + +This is passable and very likely to be common: + +@example +import gpg + +k = gpg.Context().keylist(pattern="0x6ECB6AF0DEADBEEF") +keys = list(k) +@end example + +And this is a really bad idea: + +@example +import gpg + +k = gpg.Context().keylist(pattern="0xDEADBEEF") +keys = list(k) +@end example + +Alternatively it may be that the intention is to create a list of keys +which all match a particular search string. For instance all the +addresses at a particular domain, like this: + +@example +import gpg + +ncsc = gpg.Context().keylist(pattern="ncsc.mil") +nsa = list(ncsc) +@end example + +@menu +* Counting keys:: +@end menu + +@node Counting keys +@subsection Counting keys + +Counting the number of keys in your public keybox (@samp{pubring.kbx}), the +format which has superseded the old keyring format (@samp{pubring.gpg} and +@samp{secring.gpg}), or the number of secret keys is a very simple task. + +@example +import gpg + +c = gpg.Context() +seckeys = c.keylist(pattern=None, secret=True) +pubkeys = c.keylist(pattern=None, secret=False) + +seclist = list(seckeys) +secnum = len(seclist) + +publist = list(pubkeys) +pubnum = len(publist) + +print(""" + Number of secret keys: @{0@} + Number of public keys: @{1@} +""".format(secnum, pubnum)) +@end example + +NOTE: The @ref{C plus Python plus SWIG plus Cython, , Cython} introduction in the @ref{Advanced or Experimental Use Cases, , Advanced and Experimental} +section uses this same key counting code with Cython to demonstrate +some areas where Cython can improve performance even with the +bindings. Users with large public keyrings or keyboxes, for instance, +should consider these options if they are comfortable with using +Cython. + +@node Get key +@section Get key + +An alternative method of getting a single key via its fingerprint is +available directly within a Context with @samp{Context().get_key}. This is +the preferred method of selecting a key in order to modify it, sign or +certify it and for obtaining relevant data about a single key as a +part of other functions; when verifying a signature made by that key, +for instance. + +By default this method will select public keys, but it can select +secret keys as well. + +This first example demonstrates selecting the current key of Werner +Koch, which is due to expire at the end of 2018: + +@example +import gpg + +fingerprint = "80615870F5BAD690333686D0F2AD85AC1E42B367" +key = gpg.Context().get_key(fingerprint) +@end example + +Whereas this example demonstrates selecting the author's current key +with the @samp{secret} key word argument set to @samp{True}: + +@example +import gpg + +fingerprint = "DB4724E6FA4286C92B4E55C4321E4E2373590E5D" +key = gpg.Context().get_key(fingerprint, secret=True) +@end example + +It is, of course, quite possible to select expired, disabled and +revoked keys with this function, but only to effectively display +information about those keys. + +It is also possible to use both unicode or string literals and byte +literals with the fingerprint when getting a key in this way. + +@node Importing keys +@section Importing keys + +Importing keys is possible with the @samp{key_import()} method and takes +one argument which is a bytes literal object containing either the +binary or ASCII armoured key data for one or more keys. + +The following example retrieves one or more keys from the SKS +keyservers via the web using the requests module. Since requests +returns the content as a bytes literal object, we can then use that +directly to import the resulting data into our keybox. + +@example +import gpg +import os.path +import requests + +c = gpg.Context() +url = "https://sks-keyservers.net/pks/lookup" +pattern = input("Enter the pattern to search for key or user IDs: ") +payload = @{"op": "get", "search": pattern@} + +r = requests.get(url, verify=True, params=payload) +result = c.key_import(r.content) + +if result is not None and hasattr(result, "considered") is False: + print(result) +elif result is not None and hasattr(result, "considered") is True: + num_keys = len(result.imports) + new_revs = result.new_revocations + new_sigs = result.new_signatures + new_subs = result.new_sub_keys + new_uids = result.new_user_ids + new_scrt = result.secret_imported + nochange = result.unchanged + print(""" + The total number of keys considered for import was: @{0@} + + Number of keys revoked: @{1@} + Number of new signatures: @{2@} + Number of new subkeys: @{3@} + Number of new user IDs: @{4@} + Number of new secret keys: @{5@} + Number of unchanged keys: @{6@} + + The key IDs for all considered keys were: +""".format(num_keys, new_revs, new_sigs, new_subs, new_uids, new_scrt, + nochange)) + for i in range(num_keys): + print("@{0@}\n".format(result.imports[i].fpr)) +else: + pass +@end example + +NOTE: When searching for a key ID of any length or a fingerprint +(without spaces), the SKS servers require the the leading @samp{0x} +indicative of hexadecimal be included. Also note that the old short +key IDs (e.g. @samp{0xDEADBEEF}) should no longer be used due to the +relative ease by which such key IDs can be reproduced, as demonstrated +by the Evil32 Project in 2014 (which was subsequently exploited in +2016). + +@menu +* Working with ProtonMail:: +* Importing with HKP for Python:: +* Importing from ProtonMail with HKP for Python:: +@end menu + +@node Working with ProtonMail +@subsection Working with ProtonMail + +Here is a variation on the example above which checks the constrained +ProtonMail keyserver for ProtonMail public keys. + +@example +import gpg +import requests +import sys + +print(""" +This script searches the ProtonMail key server for the specified key and +imports it. +""") + +c = gpg.Context(armor=True) +url = "https://api.protonmail.ch/pks/lookup" +ksearch = [] + +if len(sys.argv) >= 2: + keyterm = sys.argv[1] +else: + keyterm = input("Enter the key ID, UID or search string: ") + +if keyterm.count("@@") == 2 and keyterm.startswith("@@") is True: + ksearch.append(keyterm[1:]) + ksearch.append(keyterm[1:]) + ksearch.append(keyterm[1:]) +elif keyterm.count("@@") == 1 and keyterm.startswith("@@") is True: + ksearch.append("@{0@}@@protonmail.com".format(keyterm[1:])) + ksearch.append("@{0@}@@protonmail.ch".format(keyterm[1:])) + ksearch.append("@{0@}@@pm.me".format(keyterm[1:])) +elif keyterm.count("@@") == 0: + ksearch.append("@{0@}@@protonmail.com".format(keyterm)) + ksearch.append("@{0@}@@protonmail.ch".format(keyterm)) + ksearch.append("@{0@}@@pm.me".format(keyterm)) +elif keyterm.count("@@") == 2 and keyterm.startswith("@@") is False: + uidlist = keyterm.split("@@") + for uid in uidlist: + ksearch.append("@{0@}@@protonmail.com".format(uid)) + ksearch.append("@{0@}@@protonmail.ch".format(uid)) + ksearch.append("@{0@}@@pm.me".format(uid)) +elif keyterm.count("@@") > 2: + uidlist = keyterm.split("@@") + for uid in uidlist: + ksearch.append("@{0@}@@protonmail.com".format(uid)) + ksearch.append("@{0@}@@protonmail.ch".format(uid)) + ksearch.append("@{0@}@@pm.me".format(uid)) +else: + ksearch.append(keyterm) + +for k in ksearch: + payload = @{"op": "get", "search": k@} + try: + r = requests.get(url, verify=True, params=payload) + if r.ok is True: + result = c.key_import(r.content) + elif r.ok is False: + result = r.content + except Exception as e: + result = None + + if result is not None and hasattr(result, "considered") is False: + print("@{0@} for @{1@}".format(result.decode(), k)) + elif result is not None and hasattr(result, "considered") is True: + num_keys = len(result.imports) + new_revs = result.new_revocations + new_sigs = result.new_signatures + new_subs = result.new_sub_keys + new_uids = result.new_user_ids + new_scrt = result.secret_imported + nochange = result.unchanged + print(""" +The total number of keys considered for import was: @{0@} + +With UIDs wholely or partially matching the following string: + + @{1@} + + Number of keys revoked: @{2@} + Number of new signatures: @{3@} + Number of new subkeys: @{4@} + Number of new user IDs: @{5@} +Number of new secret keys: @{6@} + Number of unchanged keys: @{7@} + +The key IDs for all considered keys were: +""".format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt, + nochange)) + for i in range(num_keys): + print(result.imports[i].fpr) + print("") + elif result is None: + print(e) +@end example + +Both the above example, @uref{../examples/howto/pmkey-import.py, pmkey-import.py}, and a version which prompts +for an alternative GnuPG home directory, @uref{../examples/howto/pmkey-import-alt.py, pmkey-import-alt.py}, are +available with the other examples and are executable scripts. + +Note that while the ProtonMail servers are based on the SKS servers, +their server is related more to their API and is not feature complete +by comparison to the servers in the SKS pool. One notable difference +being that the ProtonMail server does not permit non ProtonMail users +to update their own keys, which could be a vector for attacking +ProtonMail users who may not receive a key's revocation if it had been +compromised. + +@node Importing with HKP for Python +@subsection Importing with HKP for Python + +Performing the same tasks with the @uref{https://github.com/Selfnet/hkp4py, hkp4py module} (available via PyPI) +is not too much different, but does provide a number of options of +benefit to end users. Not least of which being the ability to perform +some checks on a key before importing it or not. For instance it may +be the policy of a site or project to only import keys which have not +been revoked. The hkp4py module permits such checks prior to the +importing of the keys found. + +@example +import gpg +import hkp4py +import sys + +c = gpg.Context() +server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net") +results = [] + +if len(sys.argv) > 2: + pattern = " ".join(sys.argv[1:]) +elif len(sys.argv) == 2: + pattern = sys.argv[1] +else: + pattern = input("Enter the pattern to search for keys or user IDs: ") + +try: + keys = server.search(pattern) + print("Found @{0@} key(s).".format(len(keys))) +except Exception as e: + keys = [] + for logrus in pattern.split(): + if logrus.startswith("0x") is True: + key = server.search(logrus) + else: + key = server.search("0x@{0@}".format(logrus)) + keys.append(key[0]) + print("Found @{0@} key(s).".format(len(keys))) + +for key in keys: + import_result = c.key_import(key.key_blob) + results.append(import_result) + +for result in results: + if result is not None and hasattr(result, "considered") is False: + print(result) + elif result is not None and hasattr(result, "considered") is True: + num_keys = len(result.imports) + new_revs = result.new_revocations + new_sigs = result.new_signatures + new_subs = result.new_sub_keys + new_uids = result.new_user_ids + new_scrt = result.secret_imported + nochange = result.unchanged + print(""" +The total number of keys considered for import was: @{0@} + + Number of keys revoked: @{1@} + Number of new signatures: @{2@} + Number of new subkeys: @{3@} + Number of new user IDs: @{4@} +Number of new secret keys: @{5@} + Number of unchanged keys: @{6@} + +The key IDs for all considered keys were: +""".format(num_keys, new_revs, new_sigs, new_subs, new_uids, new_scrt, + nochange)) + for i in range(num_keys): + print(result.imports[i].fpr) + print("") + else: + pass +@end example + +Since the hkp4py module handles multiple keys just as effectively as +one (@samp{keys} is a list of responses per matching key), the example +above is able to do a little bit more with the returned data before +anything is actually imported. + +@node Importing from ProtonMail with HKP for Python +@subsection Importing from ProtonMail with HKP for Python + +Though this can provide certain benefits even when working with +ProtonMail, the scope is somewhat constrained there due to the +limitations of the ProtonMail keyserver. + +For instance, searching the SKS keyserver pool for the term "gnupg" +produces hundreds of results from any time the word appears in any +part of a user ID. Performing the same search on the ProtonMail +keyserver returns zero results, even though there are at least two +test accounts which include it as part of the username. + +The cause of this discrepancy is the deliberate configuration of that +server by ProtonMail to require an exact match of the full email +address of the ProtonMail user whose key is being requested. +Presumably this is intended to reduce breaches of privacy of their +users as an email address must already be known before a key for that +address can be obtained. + +@enumerate +@item +Import from ProtonMail via HKP for Python Example no. 1 + + +The following script is avalable with the rest of the examples under +the somewhat less than original name, @samp{pmkey-import-hkp.py}. + +@example +import gpg +import hkp4py +import os.path +import sys + +print(""" +This script searches the ProtonMail key server for the specified key and +imports it. + +Usage: pmkey-import-hkp.py [search strings] +""") + +c = gpg.Context(armor=True) +server = hkp4py.KeyServer("hkps://api.protonmail.ch") +keyterms = [] +ksearch = [] +allkeys = [] +results = [] +paradox = [] +homeless = None + +if len(sys.argv) > 2: + keyterms = sys.argv[1:] +elif len(sys.argv) == 2: + keyterm = sys.argv[1] + keyterms.append(keyterm) +else: + key_term = input("Enter the key ID, UID or search string: ") + keyterms = key_term.split() + +for keyterm in keyterms: + if keyterm.count("@@") == 2 and keyterm.startswith("@@") is True: + ksearch.append(keyterm[1:]) + ksearch.append(keyterm[1:]) + ksearch.append(keyterm[1:]) + elif keyterm.count("@@") == 1 and keyterm.startswith("@@") is True: + ksearch.append("@{0@}@@protonmail.com".format(keyterm[1:])) + ksearch.append("@{0@}@@protonmail.ch".format(keyterm[1:])) + ksearch.append("@{0@}@@pm.me".format(keyterm[1:])) + elif keyterm.count("@@") == 0: + ksearch.append("@{0@}@@protonmail.com".format(keyterm)) + ksearch.append("@{0@}@@protonmail.ch".format(keyterm)) + ksearch.append("@{0@}@@pm.me".format(keyterm)) + elif keyterm.count("@@") == 2 and keyterm.startswith("@@") is False: + uidlist = keyterm.split("@@") + for uid in uidlist: + ksearch.append("@{0@}@@protonmail.com".format(uid)) + ksearch.append("@{0@}@@protonmail.ch".format(uid)) + ksearch.append("@{0@}@@pm.me".format(uid)) + elif keyterm.count("@@") > 2: + uidlist = keyterm.split("@@") + for uid in uidlist: + ksearch.append("@{0@}@@protonmail.com".format(uid)) + ksearch.append("@{0@}@@protonmail.ch".format(uid)) + ksearch.append("@{0@}@@pm.me".format(uid)) + else: + ksearch.append(keyterm) + +for k in ksearch: + print("Checking for key for: @{0@}".format(k)) + try: + keys = server.search(k) + if isinstance(keys, list) is True: + for key in keys: + allkeys.append(key) + try: + import_result = c.key_import(key.key_blob) + except Exception as e: + import_result = c.key_import(key.key) + else: + paradox.append(keys) + import_result = None + except Exception as e: + import_result = None + results.append(import_result) + +for result in results: + if result is not None and hasattr(result, "considered") is False: + print("@{0@} for @{1@}".format(result.decode(), k)) + elif result is not None and hasattr(result, "considered") is True: + num_keys = len(result.imports) + new_revs = result.new_revocations + new_sigs = result.new_signatures + new_subs = result.new_sub_keys + new_uids = result.new_user_ids + new_scrt = result.secret_imported + nochange = result.unchanged + print(""" +The total number of keys considered for import was: @{0@} + +With UIDs wholely or partially matching the following string: + + @{1@} + + Number of keys revoked: @{2@} + Number of new signatures: @{3@} + Number of new subkeys: @{4@} + Number of new user IDs: @{5@} +Number of new secret keys: @{6@} + Number of unchanged keys: @{7@} + +The key IDs for all considered keys were: +""".format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt, + nochange)) + for i in range(num_keys): + print(result.imports[i].fpr) + print("") + elif result is None: + pass +@end example + +@item +Import from ProtonMail via HKP for Python Example no. 2 + + +Like its counterpart above, this script can also be found with the +rest of the examples, by the name pmkey-import-hkp-alt.py. + +With this script a modicum of effort has been made to treat anything +passed as a @samp{homedir} which either does not exist or which is not a +directory, as also being a pssible user ID to check for. It's not +guaranteed to pick up on all such cases, but it should cover most of +them. + +@example +import gpg +import hkp4py +import os.path +import sys + +print(""" +This script searches the ProtonMail key server for the specified key and +imports it. Optionally enables specifying a different GnuPG home directory. + +Usage: pmkey-import-hkp.py [homedir] [search string] + or: pmkey-import-hkp.py [search string] +""") + +c = gpg.Context(armor=True) +server = hkp4py.KeyServer("hkps://api.protonmail.ch") +keyterms = [] +ksearch = [] +allkeys = [] +results = [] +paradox = [] +homeless = None + +if len(sys.argv) > 3: + homedir = sys.argv[1] + keyterms = sys.argv[2:] +elif len(sys.argv) == 3: + homedir = sys.argv[1] + keyterm = sys.argv[2] + keyterms.append(keyterm) +elif len(sys.argv) == 2: + homedir = "" + keyterm = sys.argv[1] + keyterms.append(keyterm) +else: + keyterm = input("Enter the key ID, UID or search string: ") + homedir = input("Enter the GPG configuration directory path (optional): ") + keyterms.append(keyterm) + +if len(homedir) == 0: + homedir = None + homeless = False + +if homedir is not None: + if homedir.startswith("~"): + if os.path.exists(os.path.expanduser(homedir)) is True: + if os.path.isdir(os.path.expanduser(homedir)) is True: + c.home_dir = os.path.realpath(os.path.expanduser(homedir)) + else: + homeless = True + else: + homeless = True + elif os.path.exists(os.path.realpath(homedir)) is True: + if os.path.isdir(os.path.realpath(homedir)) is True: + c.home_dir = os.path.realpath(homedir) + else: + homeless = True + else: + homeless = True + +# First check to see if the homedir really is a homedir and if not, treat it as +# a search string. +if homeless is True: + keyterms.append(homedir) + c.home_dir = None +else: + pass + +for keyterm in keyterms: + if keyterm.count("@@") == 2 and keyterm.startswith("@@") is True: + ksearch.append(keyterm[1:]) + ksearch.append(keyterm[1:]) + ksearch.append(keyterm[1:]) + elif keyterm.count("@@") == 1 and keyterm.startswith("@@") is True: + ksearch.append("@{0@}@@protonmail.com".format(keyterm[1:])) + ksearch.append("@{0@}@@protonmail.ch".format(keyterm[1:])) + ksearch.append("@{0@}@@pm.me".format(keyterm[1:])) + elif keyterm.count("@@") == 0: + ksearch.append("@{0@}@@protonmail.com".format(keyterm)) + ksearch.append("@{0@}@@protonmail.ch".format(keyterm)) + ksearch.append("@{0@}@@pm.me".format(keyterm)) + elif keyterm.count("@@") == 2 and keyterm.startswith("@@") is False: + uidlist = keyterm.split("@@") + for uid in uidlist: + ksearch.append("@{0@}@@protonmail.com".format(uid)) + ksearch.append("@{0@}@@protonmail.ch".format(uid)) + ksearch.append("@{0@}@@pm.me".format(uid)) + elif keyterm.count("@@") > 2: + uidlist = keyterm.split("@@") + for uid in uidlist: + ksearch.append("@{0@}@@protonmail.com".format(uid)) + ksearch.append("@{0@}@@protonmail.ch".format(uid)) + ksearch.append("@{0@}@@pm.me".format(uid)) + else: + ksearch.append(keyterm) + +for k in ksearch: + print("Checking for key for: @{0@}".format(k)) + try: + keys = server.search(k) + if isinstance(keys, list) is True: + for key in keys: + allkeys.append(key) + try: + import_result = c.key_import(key.key_blob) + except Exception as e: + import_result = c.key_import(key.key) + else: + paradox.append(keys) + import_result = None + except Exception as e: + import_result = None + results.append(import_result) + +for result in results: + if result is not None and hasattr(result, "considered") is False: + print("@{0@} for @{1@}".format(result.decode(), k)) + elif result is not None and hasattr(result, "considered") is True: + num_keys = len(result.imports) + new_revs = result.new_revocations + new_sigs = result.new_signatures + new_subs = result.new_sub_keys + new_uids = result.new_user_ids + new_scrt = result.secret_imported + nochange = result.unchanged + print(""" +The total number of keys considered for import was: @{0@} + +With UIDs wholely or partially matching the following string: + + @{1@} + + Number of keys revoked: @{2@} + Number of new signatures: @{3@} + Number of new subkeys: @{4@} + Number of new user IDs: @{5@} +Number of new secret keys: @{6@} + Number of unchanged keys: @{7@} + +The key IDs for all considered keys were: +""".format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt, + nochange)) + for i in range(num_keys): + print(result.imports[i].fpr) + print("") + elif result is None: + pass +@end example +@end enumerate + +@node Exporting keys +@section Exporting keys + +Exporting keys remains a reasonably simple task, but has been +separated into three different functions for the OpenPGP cryptographic +engine. Two of those functions are for exporting public keys and the +third is for exporting secret keys. + +@menu +* Exporting public keys:: +* Exporting secret keys:: +* Sending public keys to the SKS Keyservers:: +@end menu + +@node Exporting public keys +@subsection Exporting public keys + +There are two methods of exporting public keys, both of which are very +similar to the other. The default method, @samp{key_export()}, will export +a public key or keys matching a specified pattern as normal. The +alternative, the @samp{key_export_minimal()} method, will do the same thing +except producing a minimised output with extra signatures and third +party signatures or certifications removed. + +@example +import gpg +import os.path +import sys + +print(""" +This script exports one or more public keys. +""") + +c = gpg.Context(armor=True) + +if len(sys.argv) >= 4: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = sys.argv[3] +elif len(sys.argv) == 3: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = input("Enter the GPG configuration directory path (optional): ") +elif len(sys.argv) == 2: + keyfile = sys.argv[1] + logrus = input("Enter the UID matching the key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") +else: + keyfile = input("Enter the path and filename to save the secret key to: ") + logrus = input("Enter the UID matching the key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") + +if homedir.startswith("~"): + if os.path.exists(os.path.expanduser(homedir)) is True: + c.home_dir = os.path.expanduser(homedir) + else: + pass +elif os.path.exists(homedir) is True: + c.home_dir = homedir +else: + pass + +try: + result = c.key_export(pattern=logrus) +except: + result = c.key_export(pattern=None) + +if result is not None: + with open(keyfile, "wb") as f: + f.write(result) +else: + pass +@end example + +It should be noted that the result will only return @samp{None} when a +search pattern has been entered, but has not matched any keys. When +the search pattern itself is set to @samp{None} this triggers the exporting +of the entire public keybox. + +@example +import gpg +import os.path +import sys + +print(""" +This script exports one or more public keys in minimised form. +""") + +c = gpg.Context(armor=True) + +if len(sys.argv) >= 4: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = sys.argv[3] +elif len(sys.argv) == 3: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = input("Enter the GPG configuration directory path (optional): ") +elif len(sys.argv) == 2: + keyfile = sys.argv[1] + logrus = input("Enter the UID matching the key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") +else: + keyfile = input("Enter the path and filename to save the secret key to: ") + logrus = input("Enter the UID matching the key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") + +if homedir.startswith("~"): + if os.path.exists(os.path.expanduser(homedir)) is True: + c.home_dir = os.path.expanduser(homedir) + else: + pass +elif os.path.exists(homedir) is True: + c.home_dir = homedir +else: + pass + +try: + result = c.key_export_minimal(pattern=logrus) +except: + result = c.key_export_minimal(pattern=None) + +if result is not None: + with open(keyfile, "wb") as f: + f.write(result) +else: + pass +@end example + +@node Exporting secret keys +@subsection Exporting secret keys + +Exporting secret keys is, functionally, very similar to exporting +public keys; save for the invocation of @samp{pinentry} via @samp{gpg-agent} in +order to securely enter the key's passphrase and authorise the export. + +The following example exports the secret key to a file which is then +set with the same permissions as the output files created by the +command line secret key export options. + +@example +import gpg +import os +import os.path +import sys + +print(""" +This script exports one or more secret keys. + +The gpg-agent and pinentry are invoked to authorise the export. +""") + +c = gpg.Context(armor=True) + +if len(sys.argv) >= 4: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = sys.argv[3] +elif len(sys.argv) == 3: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = input("Enter the GPG configuration directory path (optional): ") +elif len(sys.argv) == 2: + keyfile = sys.argv[1] + logrus = input("Enter the UID matching the secret key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") +else: + keyfile = input("Enter the path and filename to save the secret key to: ") + logrus = input("Enter the UID matching the secret key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") + +if len(homedir) == 0: + homedir = None +elif homedir.startswith("~"): + userdir = os.path.expanduser(homedir) + if os.path.exists(userdir) is True: + homedir = os.path.realpath(userdir) + else: + homedir = None +else: + homedir = os.path.realpath(homedir) + +if os.path.exists(homedir) is False: + homedir = None +else: + if os.path.isdir(homedir) is False: + homedir = None + else: + pass + +if homedir is not None: + c.home_dir = homedir +else: + pass + +try: + result = c.key_export_secret(pattern=logrus) +except: + result = c.key_export_secret(pattern=None) + +if result is not None: + with open(keyfile, "wb") as f: + f.write(result) + os.chmod(keyfile, 0o600) +else: + pass +@end example + +Alternatively the approach of the following script can be used. This +longer example saves the exported secret key(s) in files in the GnuPG +home directory, in addition to setting the file permissions as only +readable and writable by the user. It also exports the secret key(s) +twice in order to output both GPG binary (@samp{.gpg}) and ASCII armoured +(@samp{.asc}) files. + +@example +import gpg +import os +import os.path +import subprocess +import sys + +print(""" +This script exports one or more secret keys as both ASCII armored and binary +file formats, saved in files within the user's GPG home directory. + +The gpg-agent and pinentry are invoked to authorise the export. +""") + +if sys.platform == "win32": + gpgconfcmd = "gpgconf.exe --list-dirs homedir" +else: + gpgconfcmd = "gpgconf --list-dirs homedir" + +a = gpg.Context(armor=True) +b = gpg.Context() +c = gpg.Context() + +if len(sys.argv) >= 4: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = sys.argv[3] +elif len(sys.argv) == 3: + keyfile = sys.argv[1] + logrus = sys.argv[2] + homedir = input("Enter the GPG configuration directory path (optional): ") +elif len(sys.argv) == 2: + keyfile = sys.argv[1] + logrus = input("Enter the UID matching the secret key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") +else: + keyfile = input("Enter the filename to save the secret key to: ") + logrus = input("Enter the UID matching the secret key(s) to export: ") + homedir = input("Enter the GPG configuration directory path (optional): ") + +if len(homedir) == 0: + homedir = None +elif homedir.startswith("~"): + userdir = os.path.expanduser(homedir) + if os.path.exists(userdir) is True: + homedir = os.path.realpath(userdir) + else: + homedir = None +else: + homedir = os.path.realpath(homedir) + +if os.path.exists(homedir) is False: + homedir = None +else: + if os.path.isdir(homedir) is False: + homedir = None + else: + pass + +if homedir is not None: + c.home_dir = homedir +else: + pass + +if c.home_dir is not None: + if c.home_dir.endswith("/"): + gpgfile = "@{0@}@{1@}.gpg".format(c.home_dir, keyfile) + ascfile = "@{0@}@{1@}.asc".format(c.home_dir, keyfile) + else: + gpgfile = "@{0@}/@{1@}.gpg".format(c.home_dir, keyfile) + ascfile = "@{0@}/@{1@}.asc".format(c.home_dir, keyfile) +else: + if os.path.exists(os.environ["GNUPGHOME"]) is True: + hd = os.environ["GNUPGHOME"] + else: + try: + hd = subprocess.getoutput(gpgconfcmd) + except: + process = subprocess.Popen(gpgconfcmd.split(), + stdout=subprocess.PIPE) + procom = process.communicate() + if sys.version_info[0] == 2: + hd = procom[0].strip() + else: + hd = procom[0].decode().strip() + gpgfile = "@{0@}/@{1@}.gpg".format(hd, keyfile) + ascfile = "@{0@}/@{1@}.asc".format(hd, keyfile) + +try: + a_result = a.key_export_secret(pattern=logrus) + b_result = b.key_export_secret(pattern=logrus) +except: + a_result = a.key_export_secret(pattern=None) + b_result = b.key_export_secret(pattern=None) + +if a_result is not None: + with open(ascfile, "wb") as f: + f.write(a_result) + os.chmod(ascfile, 0o600) +else: + pass + +if b_result is not None: + with open(gpgfile, "wb") as f: + f.write(b_result) + os.chmod(gpgfile, 0o600) +else: + pass +@end example + +@node Sending public keys to the SKS Keyservers +@subsection Sending public keys to the SKS Keyservers + +As with the previous section on importing keys, the @samp{hkp4py} module +adds another option with exporting keys in order to send them to the +public keyservers. + +The following example demonstrates how this may be done. + +@example +import gpg +import hkp4py +import os.path +import sys + +print(""" +This script sends one or more public keys to the SKS keyservers and is +essentially a slight variation on the export-key.py script. +""") + +c = gpg.Context(armor=True) +server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net") + +if len(sys.argv) > 2: + logrus = " ".join(sys.argv[1:]) +elif len(sys.argv) == 2: + logrus = sys.argv[1] +else: + logrus = input("Enter the UID matching the key(s) to send: ") + +if len(logrus) > 0: + try: + export_result = c.key_export(pattern=logrus) + except Exception as e: + print(e) + export_result = None +else: + export_result = c.key_export(pattern=None) + +if export_result is not None: + try: + try: + send_result = server.add(export_result) + except: + send_result = server.add(export_result.decode()) + if send_result is not None: + print(send_result) + else: + pass + except Exception as e: + print(e) +else: + pass +@end example + +An expanded version of this script with additional functions for +specifying an alternative homedir location is in the examples +directory as @samp{send-key-to-keyserver.py}. + +The @samp{hkp4py} module appears to handle both string and byte literal text +data equally well, but the GPGME bindings deal primarily with byte +literal data only and so this script sends in that format first, then +tries the string literal form. + +@node Basic Functions +@chapter Basic Functions + +The most frequently called features of any cryptographic library will +be the most fundamental tasks for encryption software. In this +section we will look at how to programmatically encrypt data, decrypt +it, sign it and verify signatures. + +@menu +* Encryption:: +* Decryption:: +* Signing text and files:: +* Signature verification:: +@end menu + +@node Encryption +@section Encryption + +Encrypting is very straight forward. In the first example below the +message, @samp{text}, is encrypted to a single recipient's key. In the +second example the message will be encrypted to multiple recipients. + +@menu +* Encrypting to one key:: +* Encrypting to multiple keys:: +@end menu + +@node Encrypting to one key +@subsection Encrypting to one key + +Once the the Context is set the main issues with encrypting data is +essentially reduced to key selection and the keyword arguments +specified in the @samp{gpg.Context().encrypt()} method. + +Those keyword arguments are: @samp{recipients}, a list of keys encrypted to +(covered in greater detail in the following section); @samp{sign}, whether +or not to sign the plaintext data, see subsequent sections on signing +and verifying signatures below (defaults to @samp{True}); @samp{sink}, to write +results or partial results to a secure sink instead of returning it +(defaults to @samp{None}); @samp{passphrase}, only used when utilising symmetric +encryption (defaults to @samp{None}); @samp{always_trust}, used to override the +trust model settings for recipient keys (defaults to @samp{False}); +@samp{add_encrypt_to}, utilises any preconfigured @samp{encrypt-to} or +@samp{default-key} settings in the user's @samp{gpg.conf} file (defaults to +@samp{False}); @samp{prepare}, prepare for encryption (defaults to @samp{False}); +@samp{expect_sign}, prepare for signing (defaults to @samp{False}); @samp{compress}, +compresses the plaintext prior to encryption (defaults to @samp{True}). + +@example +import gpg + +a_key = "0x12345678DEADBEEF" +text = b"""Some text to test with. + +Since the text in this case must be bytes, it is most likely that +the input form will be a separate file which is opened with "rb" +as this is the simplest method of obtaining the correct data format. +""" + +c = gpg.Context(armor=True) +rkey = list(c.keylist(pattern=a_key, secret=False)) +ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=False) + +with open("secret_plans.txt.asc", "wb") as afile: + afile.write(ciphertext) +@end example + +Though this is even more likely to be used like this; with the +plaintext input read from a file, the recipient keys used for +encryption regardless of key trust status and the encrypted output +also encrypted to any preconfigured keys set in the @samp{gpg.conf} file: + +@example +import gpg + +a_key = "0x12345678DEADBEEF" + +with open("secret_plans.txt", "rb") as afile: + text = afile.read() + +c = gpg.Context(armor=True) +rkey = list(c.keylist(pattern=a_key, secret=False)) +ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=True, + always_trust=True, + add_encrypt_to=True) + +with open("secret_plans.txt.asc", "wb") as afile: + afile.write(ciphertext) +@end example + +If the @samp{recipients} paramater is empty then the plaintext is encrypted +symmetrically. If no @samp{passphrase} is supplied as a parameter or via a +callback registered with the @samp{Context()} then an out-of-band prompt +for the passphrase via pinentry will be invoked. + +@node Encrypting to multiple keys +@subsection Encrypting to multiple keys + +Encrypting to multiple keys essentially just expands upon the key +selection process and the recipients from the previous examples. + +The following example encrypts a message (@samp{text}) to everyone with an +email address on the @samp{gnupg.org} domain,@footnote{You probably don't really want to do this. Searching the +keyservers for "gnupg.org" produces over 400 results, the majority of +which aren't actually at the gnupg.org domain, but just included a +comment regarding the project in their key somewhere.} but does @emph{not} encrypt +to a default key or other key which is configured to normally encrypt +to. + +@example +import gpg + +text = b"""Oh look, another test message. + +The same rules apply as with the previous example and more likely +than not, the message will actually be drawn from reading the +contents of a file or, maybe, from entering data at an input() +prompt. + +Since the text in this case must be bytes, it is most likely that +the input form will be a separate file which is opened with "rb" +as this is the simplest method of obtaining the correct data +format. +""" + +c = gpg.Context(armor=True) +rpattern = list(c.keylist(pattern="@@gnupg.org", secret=False)) +logrus = [] + +for i in range(len(rpattern)): + if rpattern[i].can_encrypt == 1: + logrus.append(rpattern[i]) + +ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, + sign=False, always_trust=True) + +with open("secret_plans.txt.asc", "wb") as afile: + afile.write(ciphertext) +@end example + +All it would take to change the above example to sign the message +and also encrypt the message to any configured default keys would +be to change the @samp{c.encrypt} line to this: + +@example +ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, + always_trust=True, + add_encrypt_to=True) +@end example + +The only keyword arguments requiring modification are those for which +the default values are changing. The default value of @samp{sign} is +@samp{True}, the default of @samp{always_trust} is @samp{False}, the default of +@samp{add_encrypt_to} is @samp{False}. + +If @samp{always_trust} is not set to @samp{True} and any of the recipient keys +are not trusted (e.g. not signed or locally signed) then the +encryption will raise an error. It is possible to mitigate this +somewhat with something more like this: + +@example +import gpg + +with open("secret_plans.txt.asc", "rb") as afile: + text = afile.read() + +c = gpg.Context(armor=True) +rpattern = list(c.keylist(pattern="@@gnupg.org", secret=False)) +logrus = [] + +for i in range(len(rpattern)): + if rpattern[i].can_encrypt == 1: + logrus.append(rpattern[i]) + + try: + ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, + add_encrypt_to=True) + except gpg.errors.InvalidRecipients as e: + for i in range(len(e.recipients)): + for n in range(len(logrus)): + if logrus[n].fpr == e.recipients[i].fpr: + logrus.remove(logrus[n]) + else: + pass + try: + ciphertext, result, sign_result = c.encrypt(text, + recipients=logrus, + add_encrypt_to=True) + with open("secret_plans.txt.asc", "wb") as afile: + afile.write(ciphertext) + except: + pass +@end example + +This will attempt to encrypt to all the keys searched for, then remove +invalid recipients if it fails and try again. + +@node Decryption +@section Decryption + +Decrypting something encrypted to a key in one's secret keyring is +fairly straight forward. + +In this example code, however, preconfiguring either @samp{gpg.Context()} +or @samp{gpg.core.Context()} as @samp{c} is unnecessary because there is no need +to modify the Context prior to conducting the decryption and since the +Context is only used once, setting it to @samp{c} simply adds lines for no +gain. + +@example +import gpg + +ciphertext = input("Enter path and filename of encrypted file: ") +newfile = input("Enter path and filename of file to save decrypted data to: ") + +with open(ciphertext, "rb") as cfile: + try: + plaintext, result, verify_result = gpg.Context().decrypt(cfile) + except gpg.errors.GPGMEError as e: + plaintext = None + print(e) + +if plaintext is not None: + with open(newfile, "wb") as nfile: + nfile.write(plaintext) + else: + pass +@end example + +The data available in @samp{plaintext} in this example is the decrypted +content as a byte object, the recipient key IDs and algorithms in +@samp{result} and the results of verifying any signatures of the data in +@samp{verify_result}. + +@node Signing text and files +@section Signing text and files + +The following sections demonstrate how to specify keys to sign with. + +@menu +* Signing key selection:: +* Normal or default signing messages or files:: +* Detached signing messages and files:: +* Clearsigning messages or text:: +@end menu + +@node Signing key selection +@subsection Signing key selection + +By default GPGME and the Python bindings will use the default key +configured for the user invoking the GPGME API. If there is no +default key specified and there is more than one secret key available +it may be necessary to specify the key or keys with which to sign +messages and files. + +@example +import gpg + +logrus = input("Enter the email address or string to match signing keys to: ") +hancock = gpg.Context().keylist(pattern=logrus, secret=True) +sig_src = list(hancock) +@end example + +The signing examples in the following sections include the explicitly +designated @samp{signers} parameter in two of the five examples; once where +the resulting signature would be ASCII armoured and once where it +would not be armoured. + +While it would be possible to enter a key ID or fingerprint here to +match a specific key, it is not possible to enter two fingerprints and +match two keys since the patten expects a string, bytes or None and +not a list. A string with two fingerprints won't match any single +key. + +@node Normal or default signing messages or files +@subsection Normal or default signing messages or files + +The normal or default signing process is essentially the same as is +most often invoked when also encrypting a message or file. So when +the encryption component is not utilised, the result is to produce an +encoded and signed output which may or may not be ASCII armoured and +which may or may not also be compressed. + +By default compression will be used unless GnuPG detects that the +plaintext is already compressed. ASCII armouring will be determined +according to the value of @samp{gpg.Context().armor}. + +The compression algorithm is selected in much the same way as the +symmetric encryption algorithm or the hash digest algorithm is when +multiple keys are involved; from the preferences saved into the key +itself or by comparison with the preferences with all other keys +involved. + +@example +import gpg + +text0 = """Declaration of ... something. + +""" +text = text0.encode() + +c = gpg.Context(armor=True, signers=sig_src) +signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL) + +with open("/path/to/statement.txt.asc", "w") as afile: + afile.write(signed_data.decode()) +@end example + +Though everything in this example is accurate, it is more likely that +reading the input data from another file and writing the result to a +new file will be performed more like the way it is done in the next +example. Even if the output format is ASCII armoured. + +@example +import gpg + +with open("/path/to/statement.txt", "rb") as tfile: + text = tfile.read() + +c = gpg.Context() +signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL) + +with open("/path/to/statement.txt.sig", "wb") as afile: + afile.write(signed_data) +@end example + +@node Detached signing messages and files +@subsection Detached signing messages and files + +Detached signatures will often be needed in programmatic uses of +GPGME, either for signing files (e.g. tarballs of code releases) or as +a component of message signing (e.g. PGP/MIME encoded email). + +@example +import gpg + +text0 = """Declaration of ... something. + +""" +text = text0.encode() + +c = gpg.Context(armor=True) +signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH) + +with open("/path/to/statement.txt.asc", "w") as afile: + afile.write(signed_data.decode()) +@end example + +As with normal signatures, detached signatures are best handled as +byte literals, even when the output is ASCII armoured. + +@example +import gpg + +with open("/path/to/statement.txt", "rb") as tfile: + text = tfile.read() + +c = gpg.Context(signers=sig_src) +signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH) + +with open("/path/to/statement.txt.sig", "wb") as afile: + afile.write(signed_data) +@end example + +@node Clearsigning messages or text +@subsection Clearsigning messages or text + +Though PGP/in-line messages are no longer encouraged in favour of +PGP/MIME, there is still sometimes value in utilising in-line +signatures. This is where clear-signed messages or text is of value. + +@example +import gpg + +text0 = """Declaration of ... something. + +""" +text = text0.encode() + +c = gpg.Context() +signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR) + +with open("/path/to/statement.txt.asc", "w") as afile: + afile.write(signed_data.decode()) +@end example + +In spite of the appearance of a clear-signed message, the data handled +by GPGME in signing it must still be byte literals. + +@example +import gpg + +with open("/path/to/statement.txt", "rb") as tfile: + text = tfile.read() + +c = gpg.Context() +signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR) + +with open("/path/to/statement.txt.asc", "wb") as afile: + afile.write(signed_data) +@end example + +@node Signature verification +@section Signature verification + +Essentially there are two principal methods of verification of a +signature. The first of these is for use with the normal or default +signing method and for clear-signed messages. The second is for use +with files and data with detached signatures. + +The following example is intended for use with the default signing +method where the file was not ASCII armoured: + +@example +import gpg +import time + +filename = "statement.txt" +gpg_file = "statement.txt.gpg" + +c = gpg.Context() + +try: + data, result = c.verify(open(gpg_file)) + verified = True +except gpg.errors.BadSignatures as e: + verified = False + print(e) + +if verified is True: + for i in range(len(result.signatures)): + sign = result.signatures[i] + print("""Good signature from: +@{0@} +with key @{1@} +made at @{2@} +""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr, + time.ctime(sign.timestamp))) +else: + pass +@end example + +Whereas this next example, which is almost identical would work with +normal ASCII armoured files and with clear-signed files: + +@example +import gpg +import time + +filename = "statement.txt" +asc_file = "statement.txt.asc" + +c = gpg.Context() + +try: + data, result = c.verify(open(asc_file)) + verified = True +except gpg.errors.BadSignatures as e: + verified = False + print(e) + +if verified is True: + for i in range(len(result.signatures)): + sign = result.signatures[i] + print("""Good signature from: +@{0@} +with key @{1@} +made at @{2@} +""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr, + time.ctime(sign.timestamp))) +else: + pass +@end example + +In both of the previous examples it is also possible to compare the +original data that was signed against the signed data in @samp{data} to see +if it matches with something like this: + +@example +with open(filename, "rb") as afile: + text = afile.read() + +if text == data: + print("Good signature.") +else: + pass +@end example + +The following two examples, however, deal with detached signatures. +With his method of verification the data that was signed does not get +returned since it is already being explicitly referenced in the first +argument of @samp{c.verify}. So @samp{data} is @samp{None} and only the information +in @samp{result} is available. + +@example +import gpg +import time + +filename = "statement.txt" +sig_file = "statement.txt.sig" + +c = gpg.Context() + +try: + data, result = c.verify(open(filename), open(sig_file)) + verified = True +except gpg.errors.BadSignatures as e: + verified = False + print(e) + +if verified is True: + for i in range(len(result.signatures)): + sign = result.signatures[i] + print("""Good signature from: +@{0@} +with key @{1@} +made at @{2@} +""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr, + time.ctime(sign.timestamp))) +else: + pass +@end example + +@example +import gpg +import time + +filename = "statement.txt" +asc_file = "statement.txt.asc" + +c = gpg.Context() + +try: + data, result = c.verify(open(filename), open(asc_file)) + verified = True +except gpg.errors.BadSignatures as e: + verified = False + print(e) + +if verified is True: + for i in range(len(result.signatures)): + sign = result.signatures[i] + print("""Good signature from: +@{0@} +with key @{1@} +made at @{2@} +""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr, + time.ctime(sign.timestamp))) +else: + pass +@end example + +@node Creating keys and subkeys +@chapter Creating keys and subkeys + +The one thing, aside from GnuPG itself, that GPGME depends on, of +course, is the keys themselves. So it is necessary to be able to +generate them and modify them by adding subkeys, revoking or disabling +them, sometimes deleting them and doing the same for user IDs. + +In the following examples a key will be created for the world's +greatest secret agent, Danger Mouse. Since Danger Mouse is a secret +agent he needs to be able to protect information to @samp{SECRET} level +clearance, so his keys will be 3072-bit keys. + +The pre-configured @samp{gpg.conf} file which sets cipher, digest and other +preferences contains the following configuration parameters: + +@example +expert +allow-freeform-uid +allow-secret-key-import +trust-model tofu+pgp +tofu-default-policy unknown +enable-large-rsa +enable-dsa2 +cert-digest-algo SHA512 +default-preference-list TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1 ZLIB BZIP2 ZIP Uncompressed +personal-cipher-preferences TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES +personal-digest-preferences SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1 +personal-compress-preferences ZLIB BZIP2 ZIP Uncompressed +@end example + +@menu +* Primary key:: +* Subkeys:: +* User IDs:: +* Key certification:: +@end menu + +@node Primary key +@section Primary key + +Generating a primary key uses the @samp{create_key} method in a Context. +It contains multiple arguments and keyword arguments, including: +@samp{userid}, @samp{algorithm}, @samp{expires_in}, @samp{expires}, @samp{sign}, @samp{encrypt}, +@samp{certify}, @samp{authenticate}, @samp{passphrase} and @samp{force}. The defaults for +all of those except @samp{userid}, @samp{algorithm}, @samp{expires_in}, @samp{expires} and +@samp{passphrase} is @samp{False}. The defaults for @samp{algorithm} and +@samp{passphrase} is @samp{None}. The default for @samp{expires_in} is @samp{0}. The +default for @samp{expires} is @samp{True}. There is no default for @samp{userid}. + +If @samp{passphrase} is left as @samp{None} then the key will not be generated +with a passphrase, if @samp{passphrase} is set to a string then that will +be the passphrase and if @samp{passphrase} is set to @samp{True} then gpg-agent +will launch pinentry to prompt for a passphrase. For the sake of +convenience, these examples will keep @samp{passphrase} set to @samp{None}. + +@example +import gpg + +c = gpg.Context() + +c.home_dir = "~/.gnupg-dm" +userid = "Danger Mouse <dm@@secret.example.net>" + +dmkey = c.create_key(userid, algorithm="rsa3072", expires_in=31536000, + sign=True, certify=True) +@end example + +One thing to note here is the use of setting the @samp{c.home_dir} +parameter. This enables generating the key or keys in a different +location. In this case to keep the new key data created for this +example in a separate location rather than adding it to existing and +active key store data. As with the default directory, @samp{~/.gnupg}, any +temporary or separate directory needs the permissions set to only +permit access by the directory owner. On posix systems this means +setting the directory permissions to 700. + +The @samp{temp-homedir-config.py} script in the HOWTO examples directory +will create an alternative homedir with these configuration options +already set and the correct directory and file permissions. + +The successful generation of the key can be confirmed via the returned +@samp{GenkeyResult} object, which includes the following data: + +@example +print(""" + Fingerprint: @{0@} + Primary Key: @{1@} + Public Key: @{2@} + Secret Key: @{3@} + Sub Key: @{4@} +User IDs: @{5@} +""".format(dmkey.fpr, dmkey.primary, dmkey.pubkey, dmkey.seckey, dmkey.sub, + dmkey.uid)) +@end example + +Alternatively the information can be confirmed using the command line +program: + +@example +bash-4.4$ gpg --homedir ~/.gnupg-dm -K +~/.gnupg-dm/pubring.kbx +---------------------- +sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15] + 177B7C25DB99745EE2EE13ED026D2F19E99E63AA +uid [ultimate] Danger Mouse <dm@@secret.example.net> + +bash-4.4$ +@end example + +As with generating keys manually, to preconfigure expanded preferences +for the cipher, digest and compression algorithms, the @samp{gpg.conf} file +must contain those details in the home directory in which the new key +is being generated. I used a cut down version of my own @samp{gpg.conf} +file in order to be able to generate this: + +@example +bash-4.4$ gpg --homedir ~/.gnupg-dm --edit-key 177B7C25DB99745EE2EE13ED026D2F19E99E63AA showpref quit +Secret key is available. + +sec rsa3072/026D2F19E99E63AA + created: 2018-03-15 expires: 2019-03-15 usage: SC + trust: ultimate validity: ultimate +[ultimate] (1). Danger Mouse <dm@@secret.example.net> + +[ultimate] (1). Danger Mouse <dm@@secret.example.net> + Cipher: TWOFISH, CAMELLIA256, AES256, CAMELLIA192, AES192, CAMELLIA128, AES, BLOWFISH, IDEA, CAST5, 3DES + Digest: SHA512, SHA384, SHA256, SHA224, RIPEMD160, SHA1 + Compression: ZLIB, BZIP2, ZIP, Uncompressed + Features: MDC, Keyserver no-modify + +bash-4.4$ +@end example + +@node Subkeys +@section Subkeys + +Adding subkeys to a primary key is fairly similar to creating the +primary key with the @samp{create_subkey} method. Most of the arguments +are the same, but not quite all. Instead of the @samp{userid} argument +there is now a @samp{key} argument for selecting which primary key to add +the subkey to. + +In the following example an encryption subkey will be added to the +primary key. Since Danger Mouse is a security conscious secret agent, +this subkey will only be valid for about six months, half the length +of the primary key. + +@example +import gpg + +c = gpg.Context() +c.home_dir = "~/.gnupg-dm" + +key = c.get_key(dmkey.fpr, secret=True) +dmsub = c.create_subkey(key, algorithm="rsa3072", expires_in=15768000, + encrypt=True) +@end example + +As with the primary key, the results here can be checked with: + +@example +print(""" + Fingerprint: @{0@} + Primary Key: @{1@} + Public Key: @{2@} + Secret Key: @{3@} + Sub Key: @{4@} +User IDs: @{5@} +""".format(dmsub.fpr, dmsub.primary, dmsub.pubkey, dmsub.seckey, dmsub.sub, + dmsub.uid)) +@end example + +As well as on the command line with: + +@example +bash-4.4$ gpg --homedir ~/.gnupg-dm -K +~/.gnupg-dm/pubring.kbx +---------------------- +sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15] + 177B7C25DB99745EE2EE13ED026D2F19E99E63AA +uid [ultimate] Danger Mouse <dm@@secret.example.net> +ssb rsa3072 2018-03-15 [E] [expires: 2018-09-13] + +bash-4.4$ +@end example + +@node User IDs +@section User IDs + +@menu +* Adding User IDs:: +* Revokinging User IDs:: +@end menu + +@node Adding User IDs +@subsection Adding User IDs + +By comparison to creating primary keys and subkeys, adding a new user +ID to an existing key is much simpler. The method used to do this is +@samp{key_add_uid} and the only arguments it takes are for the @samp{key} and +the new @samp{uid}. + +@example +import gpg + +c = gpg.Context() +c.home_dir = "~/.gnupg-dm" + +dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" +key = c.get_key(dmfpr, secret=True) +uid = "Danger Mouse <danger.mouse@@secret.example.net>" + +c.key_add_uid(key, uid) +@end example + +Unsurprisingly the result of this is: + +@example +bash-4.4$ gpg --homedir ~/.gnupg-dm -K +~/.gnupg-dm/pubring.kbx +---------------------- +sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15] + 177B7C25DB99745EE2EE13ED026D2F19E99E63AA +uid [ultimate] Danger Mouse <danger.mouse@@secret.example.net> +uid [ultimate] Danger Mouse <dm@@secret.example.net> +ssb rsa3072 2018-03-15 [E] [expires: 2018-09-13] + +bash-4.4$ +@end example + +@node Revokinging User IDs +@subsection Revokinging User IDs + +Revoking a user ID is a fairly similar process, except that it uses +the @samp{key_revoke_uid} method. + +@example +import gpg + +c = gpg.Context() +c.home_dir = "~/.gnupg-dm" + +dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" +key = c.get_key(dmfpr, secret=True) +uid = "Danger Mouse <danger.mouse@@secret.example.net>" + +c.key_revoke_uid(key, uid) +@end example + +@node Key certification +@section Key certification + +Since key certification is more frequently referred to as key signing, +the method used to perform this function is @samp{key_sign}. + +The @samp{key_sign} method takes four arguments: @samp{key}, @samp{uids}, +@samp{expires_in} and @samp{local}. The default value of @samp{uids} is @samp{None} and +which results in all user IDs being selected. The default value of +both @samp{expires_in} and @samp{local} is @samp{False}; which results in the +signature never expiring and being able to be exported. + +The @samp{key} is the key being signed rather than the key doing the +signing. To change the key doing the signing refer to the signing key +selection above for signing messages and files. + +If the @samp{uids} value is not @samp{None} then it must either be a string to +match a single user ID or a list of strings to match multiple user +IDs. In this case the matching of those strings must be precise and +it is case sensitive. + +To sign Danger Mouse's key for just the initial user ID with a +signature which will last a little over a month, do this: + +@example +import gpg + +c = gpg.Context() +uid = "Danger Mouse <dm@@secret.example.net>" + +dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA" +key = c.get_key(dmfpr, secret=True) +c.key_sign(key, uids=uid, expires_in=2764800) +@end example + +@node Advanced or Experimental Use Cases +@chapter Advanced or Experimental Use Cases + +@menu +* C plus Python plus SWIG plus Cython:: +@end menu + +@node C plus Python plus SWIG plus Cython +@section C plus Python plus SWIG plus Cython + +In spite of the apparent incongruence of using Python bindings to a C +interface only to generate more C from the Python; it is in fact quite +possible to use the GPGME bindings with @uref{http://docs.cython.org/en/latest/index.html, Cython}. Though in many cases +the benefits may not be obvious since the most computationally +intensive work never leaves the level of the C code with which GPGME +itself is interacting with. + +Nevertheless, there are some situations where the benefits are +demonstrable. One of the better and easier examples being the one of +the early examples in this HOWTO, the @ref{Counting keys, , key counting} code. Running that +example as an executable Python script, @samp{keycount.py} (available in +the @samp{examples/howto/} directory), will take a noticable amount of time +to run on most systems where the public keybox or keyring contains a +few thousand public keys. + +Earlier in the evening, prior to starting this section, I ran that +script on my laptop; as I tend to do periodically and timed it using +@samp{time} utility, with the following results: + +@example +bash-4.4$ time keycount.py + +Number of secret keys: 23 +Number of public keys: 12112 + + +real 11m52.945s +user 0m0.913s +sys 0m0.752s + +bash-4.4$ +@end example + +Sometime after that I imported another key and followed it with a +little test of Cython. This test was kept fairly basic, essentially +lifting the material from the @uref{http://docs.cython.org/en/latest/src/tutorial/cython_tutorial.html, Cython Basic Tutorial} to demonstrate +compiling Python code to C. The first step was to take the example +key counting code quoted previously, essentially from the importing of +the @samp{gpg} module to the end of the script: + +@example +import gpg + +c = gpg.Context() +seckeys = c.keylist(pattern=None, secret=True) +pubkeys = c.keylist(pattern=None, secret=False) + +seclist = list(seckeys) +secnum = len(seclist) + +publist = list(pubkeys) +pubnum = len(publist) + +print(""" + Number of secret keys: @{0@} + Number of public keys: @{1@} + +""".format(secnum, pubnum)) +@end example + +Save that into a file called @samp{keycount.pyx} and then create a +@samp{setup.py} file which contains this: + +@example +from distutils.core import setup +from Cython.Build import cythonize + +setup( + ext_modules = cythonize("keycount.pyx") +) +@end example + +Compile it: + +@example +bash-4.4$ python setup.py build_ext --inplace +bash-4.4$ +@end example + +Then run it in a similar manner to @samp{keycount.py}: + +@example +bash-4.4$ time python3.7 -c "import keycount" + +Number of secret keys: 23 +Number of public keys: 12113 + + +real 6m47.905s +user 0m0.785s +sys 0m0.331s + +bash-4.4$ +@end example + +Cython turned @samp{keycount.pyx} into an 81KB @samp{keycount.o} file in the +@samp{build/} directory, a 24KB @samp{keycount.cpython-37m-darwin.so} file to be +imported into Python 3.7 and a 113KB @samp{keycount.c} generated C source +code file of nearly three thousand lines. Quite a bit bigger than the +314 bytes of the @samp{keycount.pyx} file or the full 1,452 bytes of the +full executable @samp{keycount.py} example script. + +On the other hand it ran in nearly half the time; taking 6 minutes and +47.905 seconds to run. As opposed to the 11 minutes and 52.945 seconds +which the CPython script alone took. + +The @samp{keycount.pyx} and @samp{setup.py} files used to generate this example +have been added to the @samp{examples/howto/advanced/cython/} directory +The example versions include some additional options to annotate the +existing code and to detect Cython's use. The latter comes from the +@uref{http://docs.cython.org/en/latest/src/tutorial/pure.html#magic-attributes-within-the-pxd, Magic Attributes} section of the Cython documentation. + +@node Miscellaneous extras and work-arounds +@chapter Miscellaneous extras and work-arounds + +Most of the things in the following sections are here simply because +there was no better place to put them, even though some are only +peripherally related to the GPGME Python bindings. Some are also +workarounds for functions not integrated with GPGME as yet. This is +especially true of the first of these, dealing with @ref{Group lines, , group lines}. + +@menu +* Group lines:: +* Keyserver access for Python:: +@end menu + +@node Group lines +@section Group lines + +There is not yet an easy way to access groups configured in the +gpg.conf file from within GPGME. As a consequence these central +groupings of keys cannot be shared amongst multiple programs, such as +MUAs readily. + +The following code, however, provides a work-around for obtaining this +information in Python. + +@example +import subprocess +import sys + +if sys.platform == "win32": + gpgconfcmd = "gpgconf.exe --list-options gpg" +else: + gpgconfcmd = "gpgconf --list-options gpg" + +try: + lines = subprocess.getoutput(gpgconfcmd).splitlines() +except: + process = subprocess.Popen(gpgconfcmd.split(), stdout=subprocess.PIPE) + procom = process.communicate() + if sys.version_info[0] == 2: + lines = procom[0].splitlines() + else: + lines = procom[0].decode().splitlines() + +for i in range(len(lines)): + if lines[i].startswith("group") is True: + line = lines[i] + else: + pass + +groups = line.split(":")[-1].replace('"', '').split(',') + +group_lines = [] +group_lists = [] + +for i in range(len(groups)): + group_lines.append(groups[i].split("=")) + group_lists.append(groups[i].split("=")) + +for i in range(len(group_lists)): + group_lists[i][1] = group_lists[i][1].split() +@end example + +The result of that code is that @samp{group_lines} is a list of lists where +@samp{group_lines[i][0]} is the name of the group and @samp{group_lines[i][1]} +is the key IDs of the group as a string. + +The @samp{group_lists} result is very similar in that it is a list of +lists. The first part, @samp{group_lists[i][0]} matches +@samp{group_lines[i][0]} as the name of the group, but @samp{group_lists[i][1]} +is the key IDs of the group as a string. + +A demonstration of using the @samp{groups.py} module is also available in +the form of the executable @samp{mutt-groups.py} script. This second +script reads all the group entries in a user's @samp{gpg.conf} file and +converts them into crypt-hooks suitable for use with the Mutt and +Neomutt mail clients. + +@node Keyserver access for Python +@section Keyserver access for Python + +The @uref{https://github.com/Selfnet/hkp4py, hkp4py} module by Marcel Fest was originally a port of the old +@uref{https://github.com/dgladkov/python-hkp, python-hkp} module from Python 2 to Python 3 and updated to use the +@uref{http://docs.python-requests.org/en/latest/index.html, requests} module instead. It has since been modified to provide +support for Python 2.7 as well and is available via PyPI. + +Since it rewrites the @samp{hkp} protocol prefix as @samp{http} and @samp{hkps} as +@samp{https}, the module is able to be used even with servers which do not +support the full scope of keyserver functions.@footnote{Such as with ProtonMail servers. This also means that +restricted servers which only advertise either HTTP or HTTPS end +points and not HKP or HKPS end points must still be identified as as +HKP or HKPS within the Python Code. The @samp{hkp4py} module will rewrite +these appropriately when the connection is made to the server.} It also works quite +readily when incorporated into a @ref{C plus Python plus SWIG plus Cython, , Cython} generated and compiled version +of any code. + +@menu +* Key import format:: +@end menu + +@node Key import format +@subsection Key import format + +The hkp4py module returns key data via requests as string literals +(@samp{r.text}) instead of byte literals (@samp{r.content}). This means that +the retrurned key data must be encoded to UTF-8 when importing that +key material using a @samp{gpg.Context().key_import()} method. + +For this reason an alternative method has been added to the @samp{search} +function of @samp{hkp4py.KeyServer()} which returns the key in the correct +format as expected by @samp{key_import}. When importing using this module, +it is now possible to import with this: + +@example +for key in keys: + if key.revoked is False: + gpg.Context().key_import(key.key_blob) + else: + pass +@end example + +Without that recent addition it would have been necessary to encode +the contents of each @samp{hkp4py.KeyServer().search()[i].key} in +@samp{hkp4py.KeyServer().search()} before trying to import it. + +An example of this is included in the @ref{Importing keys, , Importing Keys} section of this +HOWTO and the corresponding executable version of that example is +available in the @samp{lang/python/examples/howto} directory as normal; the +executable version is the @samp{import-keys-hkp.py} file. + +@node Copyright and Licensing +@chapter Copyright and Licensing + +@menu +* Copyright:: +* Draft Editions of this HOWTO:: +* License GPL compatible:: +@end menu + +@node Copyright +@section Copyright + +Copyright © The GnuPG Project, 2018. + +Copyright (C) The GnuPG Project, 2018. + +@node Draft Editions of this HOWTO +@section Draft Editions of this HOWTO + +Draft editions of this HOWTO may be periodically available directly +from the author at any of the following URLs: + +@itemize +@item +@uref{https://files.au.adversary.org/crypto/gpgme-python-howto.html, GPGME Python Bindings HOWTO draft (XHTML AWS S3 SSL)} +@item +@uref{http://files.au.adversary.org/crypto/gpgme-python-howto.html, GPGME Python Bindings HOWTO draft (XHTML AWS S3 no SSL)} +@item +@uref{https://files.au.adversary.org/crypto/gpgme-python-howto.texi, GPGME Python Bindings HOWTO draft (Texinfo file AWS S3 SSL)} +@item +@uref{http://files.au.adversary.org/crypto/gpgme-python-howto.texi, GPGME Python Bindings HOWTO draft (Texinfo file AWS S3 no SSL)} +@item +@uref{https://files.au.adversary.org/crypto/gpgme-python-howto.info, GPGME Python Bindings HOWTO draft (Info file AWS S3 SSL)} +@item +@uref{http://files.au.adversary.org/crypto/gpgme-python-howto.info, GPGME Python Bindings HOWTO draft (Info file AWS S3 no SSL)} +@item +@uref{https://files.au.adversary.org/crypto/gpgme-python-howto.rst, GPGME Python Bindings HOWTO draft (reST file AWS S3 SSL)} +@item +@uref{http://files.au.adversary.org/crypto/gpgme-python-howto.rst, GPGME Python Bindings HOWTO draft (reST file AWS S3 no SSL)} +@item +@uref{https://files.au.adversary.org/crypto/gpgme-python-howto.xml, GPGME Python Bindings HOWTO draft (Docbook 4.2 AWS S3 SSL)} +@item +@uref{http://files.au.adversary.org/crypto/gpgme-python-howto.xml, GPGME Python Bindings HOWTO draft (Docbook 4.2 AWS S3 no SSL)} +@end itemize + +All of these draft versions except for one have been generated from +this document via Emacs @uref{https://orgmode.org/, Org mode} and @uref{https://www.gnu.org/software/texinfo/, GNU Texinfo}. Though it is likely +that the specific @uref{https://files.au.adversary.org/crypto/gpgme-python-howto.org, file} @uref{http://files.au.adversary.org/crypto/gpgme-python-howto.org, version} used will be on the same server with +the generated output formats. + +The one exception is the reStructuredText version, which was converted +using the latest version of Pandoc from the Org mode source file using +the following command: + +@example +pandoc -f org -t rst+smart -o gpgme-python-howto.rst gpgme-python-howto.org +@end example + +In addition to these there is a significantly less frequently updated +version as a HTML @uref{https://files.au.adversary.org/crypto/gpgme-python-howto/webhelp/index.html, WebHelp site} (AWS S3 SSL); generated from DITA XML +source files, which can be found in @uref{https://dev.gnupg.org/source/gpgme/browse/ben%252Fhowto-dita/, an alternative branch} of the GPGME +git repository. + +These draft editions are not official documents and the version of +documentation in the master branch or which ships with released +versions is the only official documentation. Nevertheless, these +draft editions may occasionally be of use by providing more accessible +web versions which are updated between releases. They are provided on +the understanding that they may contain errors or may contain content +subject to change prior to an official release. + +@node License GPL compatible +@section License GPL compatible + +This file is free software; as a special exception the author gives +unlimited permission to copy and/or distribute it, with or without +modifications, as long as this notice is preserved. + +This file is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY, to the extent permitted by law; without even the +implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +PURPOSE. + +@bye
\ No newline at end of file diff --git a/lang/python/doc/texinfo/index.texi b/lang/python/doc/texinfo/index.texi new file mode 100644 index 0000000..4f80423 --- /dev/null +++ b/lang/python/doc/texinfo/index.texi @@ -0,0 +1,52 @@ +\input texinfo @c -*- texinfo -*- +@c %**start of header +@setfilename index.info +@settitle GNU Privacy Guard (GnuPG) Made Easy Python Bindings +@documentencoding UTF-8 +@documentlanguage en +@c %**end of header + +@finalout +@titlepage +@title GNU Privacy Guard (GnuPG) Made Easy Python Bindings +@author Ben McGinnes +@end titlepage + +@contents + +@ifnottex +@node Top +@top GNU Privacy Guard (GnuPG) Made Easy Python Bindings +@end ifnottex + +@menu +* GPGME Python Bindings:: + +@detailmenu +--- The Detailed Node Listing --- + +GPGME Python Bindings + +* Contents:: + +@end detailmenu +@end menu + +@node GPGME Python Bindings +@chapter GPGME Python Bindings + +@menu +* Contents:: +@end menu + +@node Contents +@section Contents + +@itemize +@item +@uref{short-history.org, A short history of the project} +@item +@uref{gpgme-python-howto.org, GPGME Python Bindings HOWTO} +@end itemize + +@bye
\ No newline at end of file diff --git a/lang/python/doc/texinfo/short-history.texi b/lang/python/doc/texinfo/short-history.texi new file mode 100644 index 0000000..a982f02 --- /dev/null +++ b/lang/python/doc/texinfo/short-history.texi @@ -0,0 +1,209 @@ +\input texinfo @c -*- texinfo -*- +@c %**start of header +@setfilename short-history.info +@settitle A Short History of the GPGME bindings for Python +@documentencoding UTF-8 +@documentlanguage en +@c %**end of header + +@finalout +@titlepage +@title A Short History of the GPGME bindings for Python +@author Ben McGinnes +@end titlepage + +@contents + +@ifnottex +@node Top +@top A Short History of the GPGME bindings for Python +@end ifnottex + +@menu +* Overview:: +* Relics of the past:: + +@detailmenu +--- The Detailed Node Listing --- + +Overview + +* In the beginning:: +* Keeping the flame alive:: +* Passing the torch:: +* Coming full circle:: + +Relics of the past + +* The Annoyances of Git:: +* The Perils of PyPI:: + +The Perils of PyPI + +* GPG 1·8·0 - Python bindings for GPGME GnuPG cryptography library:: +* PyME 0·9·0 - Python support for GPGME GnuPG cryptography library:: + +@end detailmenu +@end menu + +@node Overview +@chapter Overview + +The GPGME Python bindings passed through many hands and numerous +phases before, after a fifteen year journey, coming full circle to +return to the source. This is a short explanation of that journey. + +@menu +* In the beginning:: +* Keeping the flame alive:: +* Passing the torch:: +* Coming full circle:: +@end menu + +@node In the beginning +@section In the beginning + +In 2002 John Goerzen released PyME; Python bindings for the GPGME +module which utilised the current release of Python of the time and +SWIG.@footnote{In all likelihood thos would have been Python 2.2 or possibly +Python 2.3.} Shortly after creating it and ensuring it worked he stopped +supporting it, though he left his work available on his Gopher +site. + +@node Keeping the flame alive +@section Keeping the flame alive + +A couple of years later the project was picked up by Igor Belyi and +actively developed and maintained by him from 2004 to 2008. Igor's +whereabouts at the time of this document's creation are unknown, +but the current authors do hope he is well. We're assuming (or +hoping) that life did what life does and made continuing untenable. + +@node Passing the torch +@section Passing the torch + +In 2014 Martin Albrecht wanted to patch a bug in the PyME code and +discovered the absence of Igor. Following a discussion on the PyME +mailing list he became the new maintainer for PyME, releasing +version 0.9.0 in May of that year. He remains the maintainer of +the original PyME release in Python 2.6 and 2.7 (available via +PyPI). + +@node Coming full circle +@section Coming full circle + +In 2015 Ben McGinnes approached Martin about a Python 3 version, +while investigating how complex a task this would be the task ended +up being completed. A subsequent discussion with Werner Koch led +to the decision to fold the Python 3 port back into the original +GPGME release in the languages subdirectory for non-C bindings +under the module name of @samp{pyme3}. + +In 2016 this PyME module was integrated back into the GPGME project +by Justus Winter. During the course of this work Justus adjusted +the port to restore limited support for Python 2, but not as many +minor point releases as the original PyME package supports. During +the course of this integration the package was renamed to more +accurately reflect its status as a component of GPGME. The @samp{pyme3} +module was renamed to @samp{gpg} and adopted by the upstream GnuPG team. + +In 2017 Justus departed G10code and the GnuPG team. Following this +Ben returned to maintain of gpgme Python bindings and continue +building them from that point. + +@node Relics of the past +@chapter Relics of the past + +There are a few things, in addition to code specific factors, such as +SWIG itself, which are worth noting here. + +@menu +* The Annoyances of Git:: +* The Perils of PyPI:: +@end menu + +@node The Annoyances of Git +@section The Annoyances of Git + +As anyone who has ever worked with git knows, submodules are +horrible way to deal with pretty much anything. In the interests +of avoiding migraines, that was skipped with addition of the PyME +code to GPGME. + +Instead the files were added to a subdirectory of the @samp{lang/} +directory, along with a copy of the entire git log up to that point +as a separate file within the @samp{lang/python/docs/} directory.@footnote{The entire PyME git log and other preceding VCS logs are +located in the @samp{gpgme/lang/python/docs/old-commits.log} file.} +As the log for PyME is nearly 100KB and the log for GPGME is +approximately 1MB, this would cause considerable bloat, as well as +some confusion, should the two be merged. + +Hence the unfortunate, but necessary, step to simply move the +files. A regular repository version has been maintained should it +be possible to implement this better in the future. + +@node The Perils of PyPI +@section The Perils of PyPI + +The early port of the Python 2 @samp{pyme} module as @samp{pyme3} was never +added to PyPI while the focus remained on development and testing +during 2015 and early 2016. Later in 2016, however, when Justus +completed his major integration work and subsequently renamed the +module from @samp{pyme3} to @samp{gpg}, some prior releases were also +provided through PyPI. + +Since these bindings require a matching release of the GPGME +libraries in order to function, it was determined that there was +little benefit in also providing a copy through PyPI since anyone +obtaining the GPGME source code would obtain the Python bindings +source code at the same time. Whereas there was the potential to +sew confusion amongst Python users installing the module from PyPI, +only to discover that without the relevant C files, header files or +SWIG compiled binaries, the Python module did them little good. + +There are only two files on PyPI which might turn up in a search +for this module or a sample of its content: + +@enumerate +@item +gpg (1.8.0) - Python bindings for GPGME GnuPG cryptography library +@item +pyme (0.9.0) - Python support for GPGME GnuPG cryptography library +@end enumerate + +@menu +* GPG 1·8·0 - Python bindings for GPGME GnuPG cryptography library:: +* PyME 0·9·0 - Python support for GPGME GnuPG cryptography library:: +@end menu + +@node GPG 1·8·0 - Python bindings for GPGME GnuPG cryptography library +@subsection GPG 1·8·0 - Python bindings for GPGME GnuPG cryptography library + +This is the most recent version to reach PyPI and is the version +of the official Pyhon bindings which shipped with GPGME 1.8.0. If +you have GPGME 1.8.0 installed and @emph{only} 1.8.0 installed, then it +is probably safe to use this copy from PyPI. + +As there have been a lot of changes since the release of GPGME +1.8.0, the GnuPG Project recommends not using this version of the +module and instead installing the current version of GPGME along +with the Python bindings included with that package. + +@node PyME 0·9·0 - Python support for GPGME GnuPG cryptography library +@subsection PyME 0·9·0 - Python support for GPGME GnuPG cryptography library + +This is the last release of the PyME bindings maintained by Martin +Albrecht and is only compatible with Python 2, it will not work +with Python 3. This is the version of the software from which the +port from Python 2 to Python 3 code was made in 2015. + +Users of the more recent Python bindings will recognise numerous +points of similarity, but also significant differences. It is +likely that the more recent official bindings will feel "more +pythonic." + +For those using Python 2, there is essentially no harm in using +this module, but it may lack a number of more recent features +added to GPGME. + +@bye
\ No newline at end of file diff --git a/lang/python/doc/texinfo/texinfo.tex b/lang/python/doc/texinfo/texinfo.tex new file mode 100644 index 0000000..a5c849c --- /dev/null +++ b/lang/python/doc/texinfo/texinfo.tex @@ -0,0 +1,8962 @@ +% texinfo.tex -- TeX macros to handle Texinfo files. +% +% Load plain if necessary, i.e., if running under initex. +\expandafter\ifx\csname fmtname\endcsname\relax\input plain\fi +% +\def\texinfoversion{2007-12-02.17} +% +% Copyright (C) 1985, 1986, 1988, 1990, 1991, 1992, 1993, 1994, 1995, 2007, +% 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, +% 2007 Free Software Foundation, Inc. +% +% This texinfo.tex file is free software: you can redistribute it and/or +% modify it under the terms of the GNU General Public License as +% published by the Free Software Foundation, either version 3 of the +% License, or (at your option) any later version. +% +% This texinfo.tex file is distributed in the hope that it will be +% useful, but WITHOUT ANY WARRANTY; without even the implied warranty +% of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +% General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program. If not, see <https://www.gnu.org/licenses/>. +% +% As a special exception, when this file is read by TeX when processing +% a Texinfo source document, you may use the result without +% restriction. (This has been our intent since Texinfo was invented.) +% +% Please try the latest version of texinfo.tex before submitting bug +% reports; you can get the latest version from: +% https://www.gnu.org/software/texinfo/ (the Texinfo home page), or +% ftp://tug.org/tex/texinfo.tex +% (and all CTAN mirrors, see http://www.ctan.org). +% The texinfo.tex in any given distribution could well be out +% of date, so if that's what you're using, please check. +% +% Send bug reports to bug-texinfo@gnu.org. Please include including a +% complete document in each bug report with which we can reproduce the +% problem. Patches are, of course, greatly appreciated. +% +% To process a Texinfo manual with TeX, it's most reliable to use the +% texi2dvi shell script that comes with the distribution. For a simple +% manual foo.texi, however, you can get away with this: +% tex foo.texi +% texindex foo.?? +% tex foo.texi +% tex foo.texi +% dvips foo.dvi -o # or whatever; this makes foo.ps. +% The extra TeX runs get the cross-reference information correct. +% Sometimes one run after texindex suffices, and sometimes you need more +% than two; texi2dvi does it as many times as necessary. +% +% It is possible to adapt texinfo.tex for other languages, to some +% extent. You can get the existing language-specific files from the +% full Texinfo distribution. +% +% The GNU Texinfo home page is https://www.gnu.org/software/texinfo. + + +\message{Loading texinfo [version \texinfoversion]:} + +% If in a .fmt file, print the version number +% and turn on active characters that we couldn't do earlier because +% they might have appeared in the input file name. +\everyjob{\message{[Texinfo version \texinfoversion]}% + \catcode`+=\active \catcode`\_=\active} + + +\chardef\other=12 + +% We never want plain's \outer definition of \+ in Texinfo. +% For @tex, we can use \tabalign. +\let\+ = \relax + +% Save some plain tex macros whose names we will redefine. +\let\ptexb=\b +\let\ptexbullet=\bullet +\let\ptexc=\c +\let\ptexcomma=\, +\let\ptexdot=\. +\let\ptexdots=\dots +\let\ptexend=\end +\let\ptexequiv=\equiv +\let\ptexexclam=\! +\let\ptexfootnote=\footnote +\let\ptexgtr=> +\let\ptexhat=^ +\let\ptexi=\i +\let\ptexindent=\indent +\let\ptexinsert=\insert +\let\ptexlbrace=\{ +\let\ptexless=< +\let\ptexnewwrite\newwrite +\let\ptexnoindent=\noindent +\let\ptexplus=+ +\let\ptexrbrace=\} +\let\ptexslash=\/ +\let\ptexstar=\* +\let\ptext=\t + +% If this character appears in an error message or help string, it +% starts a new line in the output. +\newlinechar = `^^J + +% Use TeX 3.0's \inputlineno to get the line number, for better error +% messages, but if we're using an old version of TeX, don't do anything. +% +\ifx\inputlineno\thisisundefined + \let\linenumber = \empty % Pre-3.0. +\else + \def\linenumber{l.\the\inputlineno:\space} +\fi + +% Set up fixed words for English if not already set. +\ifx\putwordAppendix\undefined \gdef\putwordAppendix{Appendix}\fi +\ifx\putwordChapter\undefined \gdef\putwordChapter{Chapter}\fi +\ifx\putwordfile\undefined \gdef\putwordfile{file}\fi +\ifx\putwordin\undefined \gdef\putwordin{in}\fi +\ifx\putwordIndexIsEmpty\undefined \gdef\putwordIndexIsEmpty{(Index is empty)}\fi +\ifx\putwordIndexNonexistent\undefined \gdef\putwordIndexNonexistent{(Index is nonexistent)}\fi +\ifx\putwordInfo\undefined \gdef\putwordInfo{Info}\fi +\ifx\putwordInstanceVariableof\undefined \gdef\putwordInstanceVariableof{Instance Variable of}\fi +\ifx\putwordMethodon\undefined \gdef\putwordMethodon{Method on}\fi +\ifx\putwordNoTitle\undefined \gdef\putwordNoTitle{No Title}\fi +\ifx\putwordof\undefined \gdef\putwordof{of}\fi +\ifx\putwordon\undefined \gdef\putwordon{on}\fi +\ifx\putwordpage\undefined \gdef\putwordpage{page}\fi +\ifx\putwordsection\undefined \gdef\putwordsection{section}\fi +\ifx\putwordSection\undefined \gdef\putwordSection{Section}\fi +\ifx\putwordsee\undefined \gdef\putwordsee{see}\fi +\ifx\putwordSee\undefined \gdef\putwordSee{See}\fi +\ifx\putwordShortTOC\undefined \gdef\putwordShortTOC{Short Contents}\fi +\ifx\putwordTOC\undefined \gdef\putwordTOC{Table of Contents}\fi +% +\ifx\putwordMJan\undefined \gdef\putwordMJan{January}\fi +\ifx\putwordMFeb\undefined \gdef\putwordMFeb{February}\fi +\ifx\putwordMMar\undefined \gdef\putwordMMar{March}\fi +\ifx\putwordMApr\undefined \gdef\putwordMApr{April}\fi +\ifx\putwordMMay\undefined \gdef\putwordMMay{May}\fi +\ifx\putwordMJun\undefined \gdef\putwordMJun{June}\fi +\ifx\putwordMJul\undefined \gdef\putwordMJul{July}\fi +\ifx\putwordMAug\undefined \gdef\putwordMAug{August}\fi +\ifx\putwordMSep\undefined \gdef\putwordMSep{September}\fi +\ifx\putwordMOct\undefined \gdef\putwordMOct{October}\fi +\ifx\putwordMNov\undefined \gdef\putwordMNov{November}\fi +\ifx\putwordMDec\undefined \gdef\putwordMDec{December}\fi +% +\ifx\putwordDefmac\undefined \gdef\putwordDefmac{Macro}\fi +\ifx\putwordDefspec\undefined \gdef\putwordDefspec{Special Form}\fi +\ifx\putwordDefvar\undefined \gdef\putwordDefvar{Variable}\fi +\ifx\putwordDefopt\undefined \gdef\putwordDefopt{User Option}\fi +\ifx\putwordDeffunc\undefined \gdef\putwordDeffunc{Function}\fi + +% Since the category of space is not known, we have to be careful. +\chardef\spacecat = 10 +\def\spaceisspace{\catcode`\ =\spacecat} + +% sometimes characters are active, so we need control sequences. +\chardef\colonChar = `\: +\chardef\commaChar = `\, +\chardef\dashChar = `\- +\chardef\dotChar = `\. +\chardef\exclamChar= `\! +\chardef\lquoteChar= `\` +\chardef\questChar = `\? +\chardef\rquoteChar= `\' +\chardef\semiChar = `\; +\chardef\underChar = `\_ + +% Ignore a token. +% +\def\gobble#1{} + +% The following is used inside several \edef's. +\def\makecsname#1{\expandafter\noexpand\csname#1\endcsname} + +% Hyphenation fixes. +\hyphenation{ + Flor-i-da Ghost-script Ghost-view Mac-OS Post-Script + ap-pen-dix bit-map bit-maps + data-base data-bases eshell fall-ing half-way long-est man-u-script + man-u-scripts mini-buf-fer mini-buf-fers over-view par-a-digm + par-a-digms rath-er rec-tan-gu-lar ro-bot-ics se-vere-ly set-up spa-ces + spell-ing spell-ings + stand-alone strong-est time-stamp time-stamps which-ever white-space + wide-spread wrap-around +} + +% Margin to add to right of even pages, to left of odd pages. +\newdimen\bindingoffset +\newdimen\normaloffset +\newdimen\pagewidth \newdimen\pageheight + +% For a final copy, take out the rectangles +% that mark overfull boxes (in case you have decided +% that the text looks ok even though it passes the margin). +% +\def\finalout{\overfullrule=0pt} + +% @| inserts a changebar to the left of the current line. It should +% surround any changed text. This approach does *not* work if the +% change spans more than two lines of output. To handle that, we would +% have adopt a much more difficult approach (putting marks into the main +% vertical list for the beginning and end of each change). +% +\def\|{% + % \vadjust can only be used in horizontal mode. + \leavevmode + % + % Append this vertical mode material after the current line in the output. + \vadjust{% + % We want to insert a rule with the height and depth of the current + % leading; that is exactly what \strutbox is supposed to record. + \vskip-\baselineskip + % + % \vadjust-items are inserted at the left edge of the type. So + % the \llap here moves out into the left-hand margin. + \llap{% + % + % For a thicker or thinner bar, change the `1pt'. + \vrule height\baselineskip width1pt + % + % This is the space between the bar and the text. + \hskip 12pt + }% + }% +} + +% Sometimes it is convenient to have everything in the transcript file +% and nothing on the terminal. We don't just call \tracingall here, +% since that produces some useless output on the terminal. We also make +% some effort to order the tracing commands to reduce output in the log +% file; cf. trace.sty in LaTeX. +% +\def\gloggingall{\begingroup \globaldefs = 1 \loggingall \endgroup}% +\def\loggingall{% + \tracingstats2 + \tracingpages1 + \tracinglostchars2 % 2 gives us more in etex + \tracingparagraphs1 + \tracingoutput1 + \tracingmacros2 + \tracingrestores1 + \showboxbreadth\maxdimen \showboxdepth\maxdimen + \ifx\eTeXversion\undefined\else % etex gives us more logging + \tracingscantokens1 + \tracingifs1 + \tracinggroups1 + \tracingnesting2 + \tracingassigns1 + \fi + \tracingcommands3 % 3 gives us more in etex + \errorcontextlines16 +}% + +% add check for \lastpenalty to plain's definitions. If the last thing +% we did was a \nobreak, we don't want to insert more space. +% +\def\smallbreak{\ifnum\lastpenalty<10000\par\ifdim\lastskip<\smallskipamount + \removelastskip\penalty-50\smallskip\fi\fi} +\def\medbreak{\ifnum\lastpenalty<10000\par\ifdim\lastskip<\medskipamount + \removelastskip\penalty-100\medskip\fi\fi} +\def\bigbreak{\ifnum\lastpenalty<10000\par\ifdim\lastskip<\bigskipamount + \removelastskip\penalty-200\bigskip\fi\fi} + +% For @cropmarks command. +% Do @cropmarks to get crop marks. +% +\newif\ifcropmarks +\let\cropmarks = \cropmarkstrue +% +% Dimensions to add cropmarks at corners. +% Added by P. A. MacKay, 12 Nov. 1986 +% +\newdimen\outerhsize \newdimen\outervsize % set by the paper size routines +\newdimen\cornerlong \cornerlong=1pc +\newdimen\cornerthick \cornerthick=.3pt +\newdimen\topandbottommargin \topandbottommargin=.75in + +% Output a mark which sets \thischapter, \thissection and \thiscolor. +% We dump everything together because we only have one kind of mark. +% This works because we only use \botmark / \topmark, not \firstmark. +% +% A mark contains a subexpression of the \ifcase ... \fi construct. +% \get*marks macros below extract the needed part using \ifcase. +% +% Another complication is to let the user choose whether \thischapter +% (\thissection) refers to the chapter (section) in effect at the top +% of a page, or that at the bottom of a page. The solution is +% described on page 260 of The TeXbook. It involves outputting two +% marks for the sectioning macros, one before the section break, and +% one after. I won't pretend I can describe this better than DEK... +\def\domark{% + \toks0=\expandafter{\lastchapterdefs}% + \toks2=\expandafter{\lastsectiondefs}% + \toks4=\expandafter{\prevchapterdefs}% + \toks6=\expandafter{\prevsectiondefs}% + \toks8=\expandafter{\lastcolordefs}% + \mark{% + \the\toks0 \the\toks2 + \noexpand\or \the\toks4 \the\toks6 + \noexpand\else \the\toks8 + }% +} +% \topmark doesn't work for the very first chapter (after the title +% page or the contents), so we use \firstmark there -- this gets us +% the mark with the chapter defs, unless the user sneaks in, e.g., +% @setcolor (or @url, or @link, etc.) between @contents and the very +% first @chapter. +\def\gettopheadingmarks{% + \ifcase0\topmark\fi + \ifx\thischapter\empty \ifcase0\firstmark\fi \fi +} +\def\getbottomheadingmarks{\ifcase1\botmark\fi} +\def\getcolormarks{\ifcase2\topmark\fi} + +% Avoid "undefined control sequence" errors. +\def\lastchapterdefs{} +\def\lastsectiondefs{} +\def\prevchapterdefs{} +\def\prevsectiondefs{} +\def\lastcolordefs{} + +% Main output routine. +\chardef\PAGE = 255 +\output = {\onepageout{\pagecontents\PAGE}} + +\newbox\headlinebox +\newbox\footlinebox + +% \onepageout takes a vbox as an argument. Note that \pagecontents +% does insertions, but you have to call it yourself. +\def\onepageout#1{% + \ifcropmarks \hoffset=0pt \else \hoffset=\normaloffset \fi + % + \ifodd\pageno \advance\hoffset by \bindingoffset + \else \advance\hoffset by -\bindingoffset\fi + % + % Do this outside of the \shipout so @code etc. will be expanded in + % the headline as they should be, not taken literally (outputting ''code). + \ifodd\pageno \getoddheadingmarks \else \getevenheadingmarks \fi + \setbox\headlinebox = \vbox{\let\hsize=\pagewidth \makeheadline}% + \ifodd\pageno \getoddfootingmarks \else \getevenfootingmarks \fi + \setbox\footlinebox = \vbox{\let\hsize=\pagewidth \makefootline}% + % + {% + % Have to do this stuff outside the \shipout because we want it to + % take effect in \write's, yet the group defined by the \vbox ends + % before the \shipout runs. + % + \indexdummies % don't expand commands in the output. + \normalturnoffactive % \ in index entries must not stay \, e.g., if + % the page break happens to be in the middle of an example. + % We don't want .vr (or whatever) entries like this: + % \entry{{\tt \indexbackslash }acronym}{32}{\code {\acronym}} + % "\acronym" won't work when it's read back in; + % it needs to be + % {\code {{\tt \backslashcurfont }acronym} + \shipout\vbox{% + % Do this early so pdf references go to the beginning of the page. + \ifpdfmakepagedest \pdfdest name{\the\pageno} xyz\fi + % + \ifcropmarks \vbox to \outervsize\bgroup + \hsize = \outerhsize + \vskip-\topandbottommargin + \vtop to0pt{% + \line{\ewtop\hfil\ewtop}% + \nointerlineskip + \line{% + \vbox{\moveleft\cornerthick\nstop}% + \hfill + \vbox{\moveright\cornerthick\nstop}% + }% + \vss}% + \vskip\topandbottommargin + \line\bgroup + \hfil % center the page within the outer (page) hsize. + \ifodd\pageno\hskip\bindingoffset\fi + \vbox\bgroup + \fi + % + \unvbox\headlinebox + \pagebody{#1}% + \ifdim\ht\footlinebox > 0pt + % Only leave this space if the footline is nonempty. + % (We lessened \vsize for it in \oddfootingyyy.) + % The \baselineskip=24pt in plain's \makefootline has no effect. + \vskip 24pt + \unvbox\footlinebox + \fi + % + \ifcropmarks + \egroup % end of \vbox\bgroup + \hfil\egroup % end of (centering) \line\bgroup + \vskip\topandbottommargin plus1fill minus1fill + \boxmaxdepth = \cornerthick + \vbox to0pt{\vss + \line{% + \vbox{\moveleft\cornerthick\nsbot}% + \hfill + \vbox{\moveright\cornerthick\nsbot}% + }% + \nointerlineskip + \line{\ewbot\hfil\ewbot}% + }% + \egroup % \vbox from first cropmarks clause + \fi + }% end of \shipout\vbox + }% end of group with \indexdummies + \advancepageno + \ifnum\outputpenalty>-20000 \else\dosupereject\fi +} + +\newinsert\margin \dimen\margin=\maxdimen + +\def\pagebody#1{\vbox to\pageheight{\boxmaxdepth=\maxdepth #1}} +{\catcode`\@ =11 +\gdef\pagecontents#1{\ifvoid\topins\else\unvbox\topins\fi +% marginal hacks, juha@viisa.uucp (Juha Takala) +\ifvoid\margin\else % marginal info is present + \rlap{\kern\hsize\vbox to\z@{\kern1pt\box\margin \vss}}\fi +\dimen@=\dp#1\relax \unvbox#1\relax +\ifvoid\footins\else\vskip\skip\footins\footnoterule \unvbox\footins\fi +\ifr@ggedbottom \kern-\dimen@ \vfil \fi} +} + +% Here are the rules for the cropmarks. Note that they are +% offset so that the space between them is truly \outerhsize or \outervsize +% (P. A. MacKay, 12 November, 1986) +% +\def\ewtop{\vrule height\cornerthick depth0pt width\cornerlong} +\def\nstop{\vbox + {\hrule height\cornerthick depth\cornerlong width\cornerthick}} +\def\ewbot{\vrule height0pt depth\cornerthick width\cornerlong} +\def\nsbot{\vbox + {\hrule height\cornerlong depth\cornerthick width\cornerthick}} + +% Parse an argument, then pass it to #1. The argument is the rest of +% the input line (except we remove a trailing comment). #1 should be a +% macro which expects an ordinary undelimited TeX argument. +% +\def\parsearg{\parseargusing{}} +\def\parseargusing#1#2{% + \def\argtorun{#2}% + \begingroup + \obeylines + \spaceisspace + #1% + \parseargline\empty% Insert the \empty token, see \finishparsearg below. +} + +{\obeylines % + \gdef\parseargline#1^^M{% + \endgroup % End of the group started in \parsearg. + \argremovecomment #1\comment\ArgTerm% + }% +} + +% First remove any @comment, then any @c comment. +\def\argremovecomment#1\comment#2\ArgTerm{\argremovec #1\c\ArgTerm} +\def\argremovec#1\c#2\ArgTerm{\argcheckspaces#1\^^M\ArgTerm} + +% Each occurrence of `\^^M' or `<space>\^^M' is replaced by a single space. +% +% \argremovec might leave us with trailing space, e.g., +% @end itemize @c foo +% This space token undergoes the same procedure and is eventually removed +% by \finishparsearg. +% +\def\argcheckspaces#1\^^M{\argcheckspacesX#1\^^M \^^M} +\def\argcheckspacesX#1 \^^M{\argcheckspacesY#1\^^M} +\def\argcheckspacesY#1\^^M#2\^^M#3\ArgTerm{% + \def\temp{#3}% + \ifx\temp\empty + % Do not use \next, perhaps the caller of \parsearg uses it; reuse \temp: + \let\temp\finishparsearg + \else + \let\temp\argcheckspaces + \fi + % Put the space token in: + \temp#1 #3\ArgTerm +} + +% If a _delimited_ argument is enclosed in braces, they get stripped; so +% to get _exactly_ the rest of the line, we had to prevent such situation. +% We prepended an \empty token at the very beginning and we expand it now, +% just before passing the control to \argtorun. +% (Similarly, we have to think about #3 of \argcheckspacesY above: it is +% either the null string, or it ends with \^^M---thus there is no danger +% that a pair of braces would be stripped. +% +% But first, we have to remove the trailing space token. +% +\def\finishparsearg#1 \ArgTerm{\expandafter\argtorun\expandafter{#1}} + +% \parseargdef\foo{...} +% is roughly equivalent to +% \def\foo{\parsearg\Xfoo} +% \def\Xfoo#1{...} +% +% Actually, I use \csname\string\foo\endcsname, ie. \\foo, as it is my +% favourite TeX trick. --kasal, 16nov03 + +\def\parseargdef#1{% + \expandafter \doparseargdef \csname\string#1\endcsname #1% +} +\def\doparseargdef#1#2{% + \def#2{\parsearg#1}% + \def#1##1% +} + +% Several utility definitions with active space: +{ + \obeyspaces + \gdef\obeyedspace{ } + + % Make each space character in the input produce a normal interword + % space in the output. Don't allow a line break at this space, as this + % is used only in environments like @example, where each line of input + % should produce a line of output anyway. + % + \gdef\sepspaces{\obeyspaces\let =\tie} + + % If an index command is used in an @example environment, any spaces + % therein should become regular spaces in the raw index file, not the + % expansion of \tie (\leavevmode \penalty \@M \ ). + \gdef\unsepspaces{\let =\space} +} + + +\def\flushcr{\ifx\par\lisppar \def\next##1{}\else \let\next=\relax \fi \next} + +% Define the framework for environments in texinfo.tex. It's used like this: +% +% \envdef\foo{...} +% \def\Efoo{...} +% +% It's the responsibility of \envdef to insert \begingroup before the +% actual body; @end closes the group after calling \Efoo. \envdef also +% defines \thisenv, so the current environment is known; @end checks +% whether the environment name matches. The \checkenv macro can also be +% used to check whether the current environment is the one expected. +% +% Non-false conditionals (@iftex, @ifset) don't fit into this, so they +% are not treated as environments; they don't open a group. (The +% implementation of @end takes care not to call \endgroup in this +% special case.) + + +% At runtime, environments start with this: +\def\startenvironment#1{\begingroup\def\thisenv{#1}} +% initialize +\let\thisenv\empty + +% ... but they get defined via ``\envdef\foo{...}'': +\long\def\envdef#1#2{\def#1{\startenvironment#1#2}} +\def\envparseargdef#1#2{\parseargdef#1{\startenvironment#1#2}} + +% Check whether we're in the right environment: +\def\checkenv#1{% + \def\temp{#1}% + \ifx\thisenv\temp + \else + \badenverr + \fi +} + +% Environment mismatch, #1 expected: +\def\badenverr{% + \errhelp = \EMsimple + \errmessage{This command can appear only \inenvironment\temp, + not \inenvironment\thisenv}% +} +\def\inenvironment#1{% + \ifx#1\empty + out of any environment% + \else + in environment \expandafter\string#1% + \fi +} + +% @end foo executes the definition of \Efoo. +% But first, it executes a specialized version of \checkenv +% +\parseargdef\end{% + \if 1\csname iscond.#1\endcsname + \else + % The general wording of \badenverr may not be ideal, but... --kasal, 06nov03 + \expandafter\checkenv\csname#1\endcsname + \csname E#1\endcsname + \endgroup + \fi +} + +\newhelp\EMsimple{Press RETURN to continue.} + + +%% Simple single-character @ commands + +% @@ prints an @ +% Kludge this until the fonts are right (grr). +\def\@{{\tt\char64}} + +% This is turned off because it was never documented +% and you can use @w{...} around a quote to suppress ligatures. +%% Define @` and @' to be the same as ` and ' +%% but suppressing ligatures. +%\def\`{{`}} +%\def\'{{'}} + +% Used to generate quoted braces. +\def\mylbrace {{\tt\char123}} +\def\myrbrace {{\tt\char125}} +\let\{=\mylbrace +\let\}=\myrbrace +\begingroup + % Definitions to produce \{ and \} commands for indices, + % and @{ and @} for the aux/toc files. + \catcode`\{ = \other \catcode`\} = \other + \catcode`\[ = 1 \catcode`\] = 2 + \catcode`\! = 0 \catcode`\\ = \other + !gdef!lbracecmd[\{]% + !gdef!rbracecmd[\}]% + !gdef!lbraceatcmd[@{]% + !gdef!rbraceatcmd[@}]% +!endgroup + +% @comma{} to avoid , parsing problems. +\let\comma = , + +% Accents: @, @dotaccent @ringaccent @ubaraccent @udotaccent +% Others are defined by plain TeX: @` @' @" @^ @~ @= @u @v @H. +\let\, = \c +\let\dotaccent = \. +\def\ringaccent#1{{\accent23 #1}} +\let\tieaccent = \t +\let\ubaraccent = \b +\let\udotaccent = \d + +% Other special characters: @questiondown @exclamdown @ordf @ordm +% Plain TeX defines: @AA @AE @O @OE @L (plus lowercase versions) @ss. +\def\questiondown{?`} +\def\exclamdown{!`} +\def\ordf{\leavevmode\raise1ex\hbox{\selectfonts\lllsize \underbar{a}}} +\def\ordm{\leavevmode\raise1ex\hbox{\selectfonts\lllsize \underbar{o}}} + +% Dotless i and dotless j, used for accents. +\def\imacro{i} +\def\jmacro{j} +\def\dotless#1{% + \def\temp{#1}% + \ifx\temp\imacro \ptexi + \else\ifx\temp\jmacro \j + \else \errmessage{@dotless can be used only with i or j}% + \fi\fi +} + +% The \TeX{} logo, as in plain, but resetting the spacing so that a +% period following counts as ending a sentence. (Idea found in latex.) +% +\edef\TeX{\TeX \spacefactor=1000 } + +% @LaTeX{} logo. Not quite the same results as the definition in +% latex.ltx, since we use a different font for the raised A; it's most +% convenient for us to use an explicitly smaller font, rather than using +% the \scriptstyle font (since we don't reset \scriptstyle and +% \scriptscriptstyle). +% +\def\LaTeX{% + L\kern-.36em + {\setbox0=\hbox{T}% + \vbox to \ht0{\hbox{\selectfonts\lllsize A}\vss}}% + \kern-.15em + \TeX +} + +% Be sure we're in horizontal mode when doing a tie, since we make space +% equivalent to this in @example-like environments. Otherwise, a space +% at the beginning of a line will start with \penalty -- and +% since \penalty is valid in vertical mode, we'd end up putting the +% penalty on the vertical list instead of in the new paragraph. +{\catcode`@ = 11 + % Avoid using \@M directly, because that causes trouble + % if the definition is written into an index file. + \global\let\tiepenalty = \@M + \gdef\tie{\leavevmode\penalty\tiepenalty\ } +} + +% @: forces normal size whitespace following. +\def\:{\spacefactor=1000 } + +% @* forces a line break. +\def\*{\hfil\break\hbox{}\ignorespaces} + +% @/ allows a line break. +\let\/=\allowbreak + +% @. is an end-of-sentence period. +\def\.{.\spacefactor=\endofsentencespacefactor\space} + +% @! is an end-of-sentence bang. +\def\!{!\spacefactor=\endofsentencespacefactor\space} + +% @? is an end-of-sentence query. +\def\?{?\spacefactor=\endofsentencespacefactor\space} + +% @frenchspacing on|off says whether to put extra space after punctuation. +% +\def\onword{on} +\def\offword{off} +% +\parseargdef\frenchspacing{% + \def\temp{#1}% + \ifx\temp\onword \plainfrenchspacing + \else\ifx\temp\offword \plainnonfrenchspacing + \else + \errhelp = \EMsimple + \errmessage{Unknown @frenchspacing option `\temp', must be on/off}% + \fi\fi +} + +% @w prevents a word break. Without the \leavevmode, @w at the +% beginning of a paragraph, when TeX is still in vertical mode, would +% produce a whole line of output instead of starting the paragraph. +\def\w#1{\leavevmode\hbox{#1}} + +% @group ... @end group forces ... to be all on one page, by enclosing +% it in a TeX vbox. We use \vtop instead of \vbox to construct the box +% to keep its height that of a normal line. According to the rules for +% \topskip (p.114 of the TeXbook), the glue inserted is +% max (\topskip - \ht (first item), 0). If that height is large, +% therefore, no glue is inserted, and the space between the headline and +% the text is small, which looks bad. +% +% Another complication is that the group might be very large. This can +% cause the glue on the previous page to be unduly stretched, because it +% does not have much material. In this case, it's better to add an +% explicit \vfill so that the extra space is at the bottom. The +% threshold for doing this is if the group is more than \vfilllimit +% percent of a page (\vfilllimit can be changed inside of @tex). +% +\newbox\groupbox +\def\vfilllimit{0.7} +% +\envdef\group{% + \ifnum\catcode`\^^M=\active \else + \errhelp = \groupinvalidhelp + \errmessage{@group invalid in context where filling is enabled}% + \fi + \startsavinginserts + % + \setbox\groupbox = \vtop\bgroup + % Do @comment since we are called inside an environment such as + % @example, where each end-of-line in the input causes an + % end-of-line in the output. We don't want the end-of-line after + % the `@group' to put extra space in the output. Since @group + % should appear on a line by itself (according to the Texinfo + % manual), we don't worry about eating any user text. + \comment +} +% +% The \vtop produces a box with normal height and large depth; thus, TeX puts +% \baselineskip glue before it, and (when the next line of text is done) +% \lineskip glue after it. Thus, space below is not quite equal to space +% above. But it's pretty close. +\def\Egroup{% + % To get correct interline space between the last line of the group + % and the first line afterwards, we have to propagate \prevdepth. + \endgraf % Not \par, as it may have been set to \lisppar. + \global\dimen1 = \prevdepth + \egroup % End the \vtop. + % \dimen0 is the vertical size of the group's box. + \dimen0 = \ht\groupbox \advance\dimen0 by \dp\groupbox + % \dimen2 is how much space is left on the page (more or less). + \dimen2 = \pageheight \advance\dimen2 by -\pagetotal + % if the group doesn't fit on the current page, and it's a big big + % group, force a page break. + \ifdim \dimen0 > \dimen2 + \ifdim \pagetotal < \vfilllimit\pageheight + \page + \fi + \fi + \box\groupbox + \prevdepth = \dimen1 + \checkinserts +} +% +% TeX puts in an \escapechar (i.e., `@') at the beginning of the help +% message, so this ends up printing `@group can only ...'. +% +\newhelp\groupinvalidhelp{% +group can only be used in environments such as @example,^^J% +where each line of input produces a line of output.} + +% @need space-in-mils +% forces a page break if there is not space-in-mils remaining. + +\newdimen\mil \mil=0.001in + +% Old definition--didn't work. +%\parseargdef\need{\par % +%% This method tries to make TeX break the page naturally +%% if the depth of the box does not fit. +%{\baselineskip=0pt% +%\vtop to #1\mil{\vfil}\kern -#1\mil\nobreak +%\prevdepth=-1000pt +%}} + +\parseargdef\need{% + % Ensure vertical mode, so we don't make a big box in the middle of a + % paragraph. + \par + % + % If the @need value is less than one line space, it's useless. + \dimen0 = #1\mil + \dimen2 = \ht\strutbox + \advance\dimen2 by \dp\strutbox + \ifdim\dimen0 > \dimen2 + % + % Do a \strut just to make the height of this box be normal, so the + % normal leading is inserted relative to the preceding line. + % And a page break here is fine. + \vtop to #1\mil{\strut\vfil}% + % + % TeX does not even consider page breaks if a penalty added to the + % main vertical list is 10000 or more. But in order to see if the + % empty box we just added fits on the page, we must make it consider + % page breaks. On the other hand, we don't want to actually break the + % page after the empty box. So we use a penalty of 9999. + % + % There is an extremely small chance that TeX will actually break the + % page at this \penalty, if there are no other feasible breakpoints in + % sight. (If the user is using lots of big @group commands, which + % almost-but-not-quite fill up a page, TeX will have a hard time doing + % good page breaking, for example.) However, I could not construct an + % example where a page broke at this \penalty; if it happens in a real + % document, then we can reconsider our strategy. + \penalty9999 + % + % Back up by the size of the box, whether we did a page break or not. + \kern -#1\mil + % + % Do not allow a page break right after this kern. + \nobreak + \fi +} + +% @br forces paragraph break (and is undocumented). + +\let\br = \par + +% @page forces the start of a new page. +% +\def\page{\par\vfill\supereject} + +% @exdent text.... +% outputs text on separate line in roman font, starting at standard page margin + +% This records the amount of indent in the innermost environment. +% That's how much \exdent should take out. +\newskip\exdentamount + +% This defn is used inside fill environments such as @defun. +\parseargdef\exdent{\hfil\break\hbox{\kern -\exdentamount{\rm#1}}\hfil\break} + +% This defn is used inside nofill environments such as @example. +\parseargdef\nofillexdent{{\advance \leftskip by -\exdentamount + \leftline{\hskip\leftskip{\rm#1}}}} + +% @inmargin{WHICH}{TEXT} puts TEXT in the WHICH margin next to the current +% paragraph. For more general purposes, use the \margin insertion +% class. WHICH is `l' or `r'. +% +\newskip\inmarginspacing \inmarginspacing=1cm +\def\strutdepth{\dp\strutbox} +% +\def\doinmargin#1#2{\strut\vadjust{% + \nobreak + \kern-\strutdepth + \vtop to \strutdepth{% + \baselineskip=\strutdepth + \vss + % if you have multiple lines of stuff to put here, you'll need to + % make the vbox yourself of the appropriate size. + \ifx#1l% + \llap{\ignorespaces #2\hskip\inmarginspacing}% + \else + \rlap{\hskip\hsize \hskip\inmarginspacing \ignorespaces #2}% + \fi + \null + }% +}} +\def\inleftmargin{\doinmargin l} +\def\inrightmargin{\doinmargin r} +% +% @inmargin{TEXT [, RIGHT-TEXT]} +% (if RIGHT-TEXT is given, use TEXT for left page, RIGHT-TEXT for right; +% else use TEXT for both). +% +\def\inmargin#1{\parseinmargin #1,,\finish} +\def\parseinmargin#1,#2,#3\finish{% not perfect, but better than nothing. + \setbox0 = \hbox{\ignorespaces #2}% + \ifdim\wd0 > 0pt + \def\lefttext{#1}% have both texts + \def\righttext{#2}% + \else + \def\lefttext{#1}% have only one text + \def\righttext{#1}% + \fi + % + \ifodd\pageno + \def\temp{\inrightmargin\righttext}% odd page -> outside is right margin + \else + \def\temp{\inleftmargin\lefttext}% + \fi + \temp +} + +% @include file insert text of that file as input. +% +\def\include{\parseargusing\filenamecatcodes\includezzz} +\def\includezzz#1{% + \pushthisfilestack + \def\thisfile{#1}% + {% + \makevalueexpandable + \def\temp{\input #1 }% + \expandafter + }\temp + \popthisfilestack +} +\def\filenamecatcodes{% + \catcode`\\=\other + \catcode`~=\other + \catcode`^=\other + \catcode`_=\other + \catcode`|=\other + \catcode`<=\other + \catcode`>=\other + \catcode`+=\other + \catcode`-=\other +} + +\def\pushthisfilestack{% + \expandafter\pushthisfilestackX\popthisfilestack\StackTerm +} +\def\pushthisfilestackX{% + \expandafter\pushthisfilestackY\thisfile\StackTerm +} +\def\pushthisfilestackY #1\StackTerm #2\StackTerm {% + \gdef\popthisfilestack{\gdef\thisfile{#1}\gdef\popthisfilestack{#2}}% +} + +\def\popthisfilestack{\errthisfilestackempty} +\def\errthisfilestackempty{\errmessage{Internal error: + the stack of filenames is empty.}} + +\def\thisfile{} + +% @center line +% outputs that line, centered. +% +\parseargdef\center{% + \ifhmode + \let\next\centerH + \else + \let\next\centerV + \fi + \next{\hfil \ignorespaces#1\unskip \hfil}% +} +\def\centerH#1{% + {% + \hfil\break + \advance\hsize by -\leftskip + \advance\hsize by -\rightskip + \line{#1}% + \break + }% +} +\def\centerV#1{\line{\kern\leftskip #1\kern\rightskip}} + +% @sp n outputs n lines of vertical space + +\parseargdef\sp{\vskip #1\baselineskip} + +% @comment ...line which is ignored... +% @c is the same as @comment +% @ignore ... @end ignore is another way to write a comment + +\def\comment{\begingroup \catcode`\^^M=\other% +\catcode`\@=\other \catcode`\{=\other \catcode`\}=\other% +\commentxxx} +{\catcode`\^^M=\other \gdef\commentxxx#1^^M{\endgroup}} + +\let\c=\comment + +% @paragraphindent NCHARS +% We'll use ems for NCHARS, close enough. +% NCHARS can also be the word `asis' or `none'. +% We cannot feasibly implement @paragraphindent asis, though. +% +\def\asisword{asis} % no translation, these are keywords +\def\noneword{none} +% +\parseargdef\paragraphindent{% + \def\temp{#1}% + \ifx\temp\asisword + \else + \ifx\temp\noneword + \defaultparindent = 0pt + \else + \defaultparindent = #1em + \fi + \fi + \parindent = \defaultparindent +} + +% @exampleindent NCHARS +% We'll use ems for NCHARS like @paragraphindent. +% It seems @exampleindent asis isn't necessary, but +% I preserve it to make it similar to @paragraphindent. +\parseargdef\exampleindent{% + \def\temp{#1}% + \ifx\temp\asisword + \else + \ifx\temp\noneword + \lispnarrowing = 0pt + \else + \lispnarrowing = #1em + \fi + \fi +} + +% @firstparagraphindent WORD +% If WORD is `none', then suppress indentation of the first paragraph +% after a section heading. If WORD is `insert', then do indent at such +% paragraphs. +% +% The paragraph indentation is suppressed or not by calling +% \suppressfirstparagraphindent, which the sectioning commands do. +% We switch the definition of this back and forth according to WORD. +% By default, we suppress indentation. +% +\def\suppressfirstparagraphindent{\dosuppressfirstparagraphindent} +\def\insertword{insert} +% +\parseargdef\firstparagraphindent{% + \def\temp{#1}% + \ifx\temp\noneword + \let\suppressfirstparagraphindent = \dosuppressfirstparagraphindent + \else\ifx\temp\insertword + \let\suppressfirstparagraphindent = \relax + \else + \errhelp = \EMsimple + \errmessage{Unknown @firstparagraphindent option `\temp'}% + \fi\fi +} + +% Here is how we actually suppress indentation. Redefine \everypar to +% \kern backwards by \parindent, and then reset itself to empty. +% +% We also make \indent itself not actually do anything until the next +% paragraph. +% +\gdef\dosuppressfirstparagraphindent{% + \gdef\indent{% + \restorefirstparagraphindent + \indent + }% + \gdef\noindent{% + \restorefirstparagraphindent + \noindent + }% + \global\everypar = {% + \kern -\parindent + \restorefirstparagraphindent + }% +} + +\gdef\restorefirstparagraphindent{% + \global \let \indent = \ptexindent + \global \let \noindent = \ptexnoindent + \global \everypar = {}% +} + + +% @asis just yields its argument. Used with @table, for example. +% +\def\asis#1{#1} + +% @math outputs its argument in math mode. +% +% One complication: _ usually means subscripts, but it could also mean +% an actual _ character, as in @math{@var{some_variable} + 1}. So make +% _ active, and distinguish by seeing if the current family is \slfam, +% which is what @var uses. +{ + \catcode`\_ = \active + \gdef\mathunderscore{% + \catcode`\_=\active + \def_{\ifnum\fam=\slfam \_\else\sb\fi}% + } +} +% Another complication: we want \\ (and @\) to output a \ character. +% FYI, plain.tex uses \\ as a temporary control sequence (why?), but +% this is not advertised and we don't care. Texinfo does not +% otherwise define @\. +% +% The \mathchar is class=0=ordinary, family=7=ttfam, position=5C=\. +\def\mathbackslash{\ifnum\fam=\ttfam \mathchar"075C \else\backslash \fi} +% +\def\math{% + \tex + \mathunderscore + \let\\ = \mathbackslash + \mathactive + $\finishmath +} +\def\finishmath#1{#1$\endgroup} % Close the group opened by \tex. + +% Some active characters (such as <) are spaced differently in math. +% We have to reset their definitions in case the @math was an argument +% to a command which sets the catcodes (such as @item or @section). +% +{ + \catcode`^ = \active + \catcode`< = \active + \catcode`> = \active + \catcode`+ = \active + \gdef\mathactive{% + \let^ = \ptexhat + \let< = \ptexless + \let> = \ptexgtr + \let+ = \ptexplus + } +} + +% @bullet and @minus need the same treatment as @math, just above. +\def\bullet{$\ptexbullet$} +\def\minus{$-$} + +% @dots{} outputs an ellipsis using the current font. +% We do .5em per period so that it has the same spacing in the cm +% typewriter fonts as three actual period characters; on the other hand, +% in other typewriter fonts three periods are wider than 1.5em. So do +% whichever is larger. +% +\def\dots{% + \leavevmode + \setbox0=\hbox{...}% get width of three periods + \ifdim\wd0 > 1.5em + \dimen0 = \wd0 + \else + \dimen0 = 1.5em + \fi + \hbox to \dimen0{% + \hskip 0pt plus.25fil + .\hskip 0pt plus1fil + .\hskip 0pt plus1fil + .\hskip 0pt plus.5fil + }% +} + +% @enddots{} is an end-of-sentence ellipsis. +% +\def\enddots{% + \dots + \spacefactor=\endofsentencespacefactor +} + +% @comma{} is so commas can be inserted into text without messing up +% Texinfo's parsing. +% +\let\comma = , + +% @refill is a no-op. +\let\refill=\relax + +% If working on a large document in chapters, it is convenient to +% be able to disable indexing, cross-referencing, and contents, for test runs. +% This is done with @novalidate (before @setfilename). +% +\newif\iflinks \linkstrue % by default we want the aux files. +\let\novalidate = \linksfalse + +% @setfilename is done at the beginning of every texinfo file. +% So open here the files we need to have open while reading the input. +% This makes it possible to make a .fmt file for texinfo. +\def\setfilename{% + \fixbackslash % Turn off hack to swallow `\input texinfo'. + \iflinks + \tryauxfile + % Open the new aux file. TeX will close it automatically at exit. + \immediate\openout\auxfile=\jobname.aux + \fi % \openindices needs to do some work in any case. + \openindices + \let\setfilename=\comment % Ignore extra @setfilename cmds. + % + % If texinfo.cnf is present on the system, read it. + % Useful for site-wide @afourpaper, etc. + \openin 1 texinfo.cnf + \ifeof 1 \else \input texinfo.cnf \fi + \closein 1 + % + \comment % Ignore the actual filename. +} + +% Called from \setfilename. +% +\def\openindices{% + \newindex{cp}% + \newcodeindex{fn}% + \newcodeindex{vr}% + \newcodeindex{tp}% + \newcodeindex{ky}% + \newcodeindex{pg}% +} + +% @bye. +\outer\def\bye{\pagealignmacro\tracingstats=1\ptexend} + + +\message{pdf,} +% adobe `portable' document format +\newcount\tempnum +\newcount\lnkcount +\newtoks\filename +\newcount\filenamelength +\newcount\pgn +\newtoks\toksA +\newtoks\toksB +\newtoks\toksC +\newtoks\toksD +\newbox\boxA +\newcount\countA +\newif\ifpdf +\newif\ifpdfmakepagedest + +% when pdftex is run in dvi mode, \pdfoutput is defined (so \pdfoutput=1 +% can be set). So we test for \relax and 0 as well as \undefined, +% borrowed from ifpdf.sty. +\ifx\pdfoutput\undefined +\else + \ifx\pdfoutput\relax + \else + \ifcase\pdfoutput + \else + \pdftrue + \fi + \fi +\fi + +% PDF uses PostScript string constants for the names of xref targets, +% for display in the outlines, and in other places. Thus, we have to +% double any backslashes. Otherwise, a name like "\node" will be +% interpreted as a newline (\n), followed by o, d, e. Not good. +% http://www.ntg.nl/pipermail/ntg-pdftex/2004-July/000654.html +% (and related messages, the final outcome is that it is up to the TeX +% user to double the backslashes and otherwise make the string valid, so +% that's what we do). + +% double active backslashes. +% +{\catcode`\@=0 \catcode`\\=\active + @gdef@activebackslashdouble{% + @catcode`@\=@active + @let\=@doublebackslash} +} + +% To handle parens, we must adopt a different approach, since parens are +% not active characters. hyperref.dtx (which has the same problem as +% us) handles it with this amazing macro to replace tokens, with minor +% changes for Texinfo. It is included here under the GPL by permission +% from the author, Heiko Oberdiek. +% +% #1 is the tokens to replace. +% #2 is the replacement. +% #3 is the control sequence with the string. +% +\def\HyPsdSubst#1#2#3{% + \def\HyPsdReplace##1#1##2\END{% + ##1% + \ifx\\##2\\% + \else + #2% + \HyReturnAfterFi{% + \HyPsdReplace##2\END + }% + \fi + }% + \xdef#3{\expandafter\HyPsdReplace#3#1\END}% +} +\long\def\HyReturnAfterFi#1\fi{\fi#1} + +% #1 is a control sequence in which to do the replacements. +\def\backslashparens#1{% + \xdef#1{#1}% redefine it as its expansion; the definition is simply + % \lastnode when called from \setref -> \pdfmkdest. + \HyPsdSubst{(}{\realbackslash(}{#1}% + \HyPsdSubst{)}{\realbackslash)}{#1}% +} + +\newhelp\nopdfimagehelp{Texinfo supports .png, .jpg, .jpeg, and .pdf images +with PDF output, and none of those formats could be found. (.eps cannot +be supported due to the design of the PDF format; use regular TeX (DVI +output) for that.)} + +\ifpdf + % + % Color manipulation macros based on pdfcolor.tex. + \def\cmykDarkRed{0.28 1 1 0.35} + \def\cmykBlack{0 0 0 1} + % + \def\pdfsetcolor#1{\pdfliteral{#1 k}} + % Set color, and create a mark which defines \thiscolor accordingly, + % so that \makeheadline knows which color to restore. + \def\setcolor#1{% + \xdef\lastcolordefs{\gdef\noexpand\thiscolor{#1}}% + \domark + \pdfsetcolor{#1}% + } + % + \def\maincolor{\cmykBlack} + \pdfsetcolor{\maincolor} + \edef\thiscolor{\maincolor} + \def\lastcolordefs{} + % + \def\makefootline{% + \baselineskip24pt + \line{\pdfsetcolor{\maincolor}\the\footline}% + } + % + \def\makeheadline{% + \vbox to 0pt{% + \vskip-22.5pt + \line{% + \vbox to8.5pt{}% + % Extract \thiscolor definition from the marks. + \getcolormarks + % Typeset the headline with \maincolor, then restore the color. + \pdfsetcolor{\maincolor}\the\headline\pdfsetcolor{\thiscolor}% + }% + \vss + }% + \nointerlineskip + } + % + % + \pdfcatalog{/PageMode /UseOutlines} + % + % #1 is image name, #2 width (might be empty/whitespace), #3 height (ditto). + \def\dopdfimage#1#2#3{% + \def\imagewidth{#2}\setbox0 = \hbox{\ignorespaces #2}% + \def\imageheight{#3}\setbox2 = \hbox{\ignorespaces #3}% + % + % pdftex (and the PDF format) support .png, .jpg, .pdf (among + % others). Let's try in that order. + \let\pdfimgext=\empty + \begingroup + \openin 1 #1.png \ifeof 1 + \openin 1 #1.jpg \ifeof 1 + \openin 1 #1.jpeg \ifeof 1 + \openin 1 #1.JPG \ifeof 1 + \openin 1 #1.pdf \ifeof 1 + \errhelp = \nopdfimagehelp + \errmessage{Could not find image file #1 for pdf}% + \else \gdef\pdfimgext{pdf}% + \fi + \else \gdef\pdfimgext{JPG}% + \fi + \else \gdef\pdfimgext{jpeg}% + \fi + \else \gdef\pdfimgext{jpg}% + \fi + \else \gdef\pdfimgext{png}% + \fi + \closein 1 + \endgroup + % + % without \immediate, pdftex seg faults when the same image is + % included twice. (Version 3.14159-pre-1.0-unofficial-20010704.) + \ifnum\pdftexversion < 14 + \immediate\pdfimage + \else + \immediate\pdfximage + \fi + \ifdim \wd0 >0pt width \imagewidth \fi + \ifdim \wd2 >0pt height \imageheight \fi + \ifnum\pdftexversion<13 + #1.\pdfimgext + \else + {#1.\pdfimgext}% + \fi + \ifnum\pdftexversion < 14 \else + \pdfrefximage \pdflastximage + \fi} + % + \def\pdfmkdest#1{{% + % We have to set dummies so commands such as @code, and characters + % such as \, aren't expanded when present in a section title. + \indexnofonts + \turnoffactive + \activebackslashdouble + \makevalueexpandable + \def\pdfdestname{#1}% + \backslashparens\pdfdestname + \safewhatsit{\pdfdest name{\pdfdestname} xyz}% + }} + % + % used to mark target names; must be expandable. + \def\pdfmkpgn#1{#1} + % + % by default, use a color that is dark enough to print on paper as + % nearly black, but still distinguishable for online viewing. + \def\urlcolor{\cmykDarkRed} + \def\linkcolor{\cmykDarkRed} + \def\endlink{\setcolor{\maincolor}\pdfendlink} + % + % Adding outlines to PDF; macros for calculating structure of outlines + % come from Petr Olsak + \def\expnumber#1{\expandafter\ifx\csname#1\endcsname\relax 0% + \else \csname#1\endcsname \fi} + \def\advancenumber#1{\tempnum=\expnumber{#1}\relax + \advance\tempnum by 1 + \expandafter\xdef\csname#1\endcsname{\the\tempnum}} + % + % #1 is the section text, which is what will be displayed in the + % outline by the pdf viewer. #2 is the pdf expression for the number + % of subentries (or empty, for subsubsections). #3 is the node text, + % which might be empty if this toc entry had no corresponding node. + % #4 is the page number + % + \def\dopdfoutline#1#2#3#4{% + % Generate a link to the node text if that exists; else, use the + % page number. We could generate a destination for the section + % text in the case where a section has no node, but it doesn't + % seem worth the trouble, since most documents are normally structured. + \def\pdfoutlinedest{#3}% + \ifx\pdfoutlinedest\empty + \def\pdfoutlinedest{#4}% + \else + % Doubled backslashes in the name. + {\activebackslashdouble \xdef\pdfoutlinedest{#3}% + \backslashparens\pdfoutlinedest}% + \fi + % + % Also double the backslashes in the display string. + {\activebackslashdouble \xdef\pdfoutlinetext{#1}% + \backslashparens\pdfoutlinetext}% + % + \pdfoutline goto name{\pdfmkpgn{\pdfoutlinedest}}#2{\pdfoutlinetext}% + } + % + \def\pdfmakeoutlines{% + \begingroup + % Thanh's hack / proper braces in bookmarks + \edef\mylbrace{\iftrue \string{\else}\fi}\let\{=\mylbrace + \edef\myrbrace{\iffalse{\else\string}\fi}\let\}=\myrbrace + % + % Read toc silently, to get counts of subentries for \pdfoutline. + \def\numchapentry##1##2##3##4{% + \def\thischapnum{##2}% + \def\thissecnum{0}% + \def\thissubsecnum{0}% + }% + \def\numsecentry##1##2##3##4{% + \advancenumber{chap\thischapnum}% + \def\thissecnum{##2}% + \def\thissubsecnum{0}% + }% + \def\numsubsecentry##1##2##3##4{% + \advancenumber{sec\thissecnum}% + \def\thissubsecnum{##2}% + }% + \def\numsubsubsecentry##1##2##3##4{% + \advancenumber{subsec\thissubsecnum}% + }% + \def\thischapnum{0}% + \def\thissecnum{0}% + \def\thissubsecnum{0}% + % + % use \def rather than \let here because we redefine \chapentry et + % al. a second time, below. + \def\appentry{\numchapentry}% + \def\appsecentry{\numsecentry}% + \def\appsubsecentry{\numsubsecentry}% + \def\appsubsubsecentry{\numsubsubsecentry}% + \def\unnchapentry{\numchapentry}% + \def\unnsecentry{\numsecentry}% + \def\unnsubsecentry{\numsubsecentry}% + \def\unnsubsubsecentry{\numsubsubsecentry}% + \readdatafile{toc}% + % + % Read toc second time, this time actually producing the outlines. + % The `-' means take the \expnumber as the absolute number of + % subentries, which we calculated on our first read of the .toc above. + % + % We use the node names as the destinations. + \def\numchapentry##1##2##3##4{% + \dopdfoutline{##1}{count-\expnumber{chap##2}}{##3}{##4}}% + \def\numsecentry##1##2##3##4{% + \dopdfoutline{##1}{count-\expnumber{sec##2}}{##3}{##4}}% + \def\numsubsecentry##1##2##3##4{% + \dopdfoutline{##1}{count-\expnumber{subsec##2}}{##3}{##4}}% + \def\numsubsubsecentry##1##2##3##4{% count is always zero + \dopdfoutline{##1}{}{##3}{##4}}% + % + % PDF outlines are displayed using system fonts, instead of + % document fonts. Therefore we cannot use special characters, + % since the encoding is unknown. For example, the eogonek from + % Latin 2 (0xea) gets translated to a | character. Info from + % Staszek Wawrykiewicz, 19 Jan 2004 04:09:24 +0100. + % + % xx to do this right, we have to translate 8-bit characters to + % their "best" equivalent, based on the @documentencoding. Right + % now, I guess we'll just let the pdf reader have its way. + \indexnofonts + \setupdatafile + \catcode`\\=\active \otherbackslash + \input \tocreadfilename + \endgroup + } + % + \def\skipspaces#1{\def\PP{#1}\def\D{|}% + \ifx\PP\D\let\nextsp\relax + \else\let\nextsp\skipspaces + \ifx\p\space\else\addtokens{\filename}{\PP}% + \advance\filenamelength by 1 + \fi + \fi + \nextsp} + \def\getfilename#1{\filenamelength=0\expandafter\skipspaces#1|\relax} + \ifnum\pdftexversion < 14 + \let \startlink \pdfannotlink + \else + \let \startlink \pdfstartlink + \fi + % make a live url in pdf output. + \def\pdfurl#1{% + \begingroup + % it seems we really need yet another set of dummies; have not + % tried to figure out what each command should do in the context + % of @url. for now, just make @/ a no-op, that's the only one + % people have actually reported a problem with. + % + \normalturnoffactive + \def\@{@}% + \let\/=\empty + \makevalueexpandable + \leavevmode\setcolor{\urlcolor}% + \startlink attr{/Border [0 0 0]}% + user{/Subtype /Link /A << /S /URI /URI (#1) >>}% + \endgroup} + \def\pdfgettoks#1.{\setbox\boxA=\hbox{\toksA={#1.}\toksB={}\maketoks}} + \def\addtokens#1#2{\edef\addtoks{\noexpand#1={\the#1#2}}\addtoks} + \def\adn#1{\addtokens{\toksC}{#1}\global\countA=1\let\next=\maketoks} + \def\poptoks#1#2|ENDTOKS|{\let\first=#1\toksD={#1}\toksA={#2}} + \def\maketoks{% + \expandafter\poptoks\the\toksA|ENDTOKS|\relax + \ifx\first0\adn0 + \else\ifx\first1\adn1 \else\ifx\first2\adn2 \else\ifx\first3\adn3 + \else\ifx\first4\adn4 \else\ifx\first5\adn5 \else\ifx\first6\adn6 + \else\ifx\first7\adn7 \else\ifx\first8\adn8 \else\ifx\first9\adn9 + \else + \ifnum0=\countA\else\makelink\fi + \ifx\first.\let\next=\done\else + \let\next=\maketoks + \addtokens{\toksB}{\the\toksD} + \ifx\first,\addtokens{\toksB}{\space}\fi + \fi + \fi\fi\fi\fi\fi\fi\fi\fi\fi\fi + \next} + \def\makelink{\addtokens{\toksB}% + {\noexpand\pdflink{\the\toksC}}\toksC={}\global\countA=0} + \def\pdflink#1{% + \startlink attr{/Border [0 0 0]} goto name{\pdfmkpgn{#1}} + \setcolor{\linkcolor}#1\endlink} + \def\done{\edef\st{\global\noexpand\toksA={\the\toksB}}\st} +\else + \let\pdfmkdest = \gobble + \let\pdfurl = \gobble + \let\endlink = \relax + \let\setcolor = \gobble + \let\pdfsetcolor = \gobble + \let\pdfmakeoutlines = \relax +\fi % \ifx\pdfoutput + + +\message{fonts,} + +% Change the current font style to #1, remembering it in \curfontstyle. +% For now, we do not accumulate font styles: @b{@i{foo}} prints foo in +% italics, not bold italics. +% +\def\setfontstyle#1{% + \def\curfontstyle{#1}% not as a control sequence, because we are \edef'd. + \csname ten#1\endcsname % change the current font +} + +% Select #1 fonts with the current style. +% +\def\selectfonts#1{\csname #1fonts\endcsname \csname\curfontstyle\endcsname} + +\def\rm{\fam=0 \setfontstyle{rm}} +\def\it{\fam=\itfam \setfontstyle{it}} +\def\sl{\fam=\slfam \setfontstyle{sl}} +\def\bf{\fam=\bffam \setfontstyle{bf}}\def\bfstylename{bf} +\def\tt{\fam=\ttfam \setfontstyle{tt}} + +% Texinfo sort of supports the sans serif font style, which plain TeX does not. +% So we set up a \sf. +\newfam\sffam +\def\sf{\fam=\sffam \setfontstyle{sf}} +\let\li = \sf % Sometimes we call it \li, not \sf. + +% We don't need math for this font style. +\def\ttsl{\setfontstyle{ttsl}} + + +% Default leading. +\newdimen\textleading \textleading = 13.2pt + +% Set the baselineskip to #1, and the lineskip and strut size +% correspondingly. There is no deep meaning behind these magic numbers +% used as factors; they just match (closely enough) what Knuth defined. +% +\def\lineskipfactor{.08333} +\def\strutheightpercent{.70833} +\def\strutdepthpercent {.29167} +% +% can get a sort of poor man's double spacing by redefining this. +\def\baselinefactor{1} +% +\def\setleading#1{% + \dimen0 = #1\relax + \normalbaselineskip = \baselinefactor\dimen0 + \normallineskip = \lineskipfactor\normalbaselineskip + \normalbaselines + \setbox\strutbox =\hbox{% + \vrule width0pt height\strutheightpercent\baselineskip + depth \strutdepthpercent \baselineskip + }% +} + +% PDF CMaps. See also LaTeX's t1.cmap. +% +% do nothing with this by default. +\expandafter\let\csname cmapOT1\endcsname\gobble +\expandafter\let\csname cmapOT1IT\endcsname\gobble +\expandafter\let\csname cmapOT1TT\endcsname\gobble + +% if we are producing pdf, and we have \pdffontattr, then define cmaps. +% (\pdffontattr was introduced many years ago, but people still run +% older pdftex's; it's easy to conditionalize, so we do.) +\ifpdf \ifx\pdffontattr\undefined \else + \begingroup + \catcode`\^^M=\active \def^^M{^^J}% Output line endings as the ^^J char. + \catcode`\%=12 \immediate\pdfobj stream {%!PS-Adobe-3.0 Resource-CMap +%%DocumentNeededResources: ProcSet (CIDInit) +%%IncludeResource: ProcSet (CIDInit) +%%BeginResource: CMap (TeX-OT1-0) +%%Title: (TeX-OT1-0 TeX OT1 0) +%%Version: 1.000 +%%EndComments +/CIDInit /ProcSet findresource begin +12 dict begin +begincmap +/CIDSystemInfo +<< /Registry (TeX) +/Ordering (OT1) +/Supplement 0 +>> def +/CMapName /TeX-OT1-0 def +/CMapType 2 def +1 begincodespacerange +<00> <7F> +endcodespacerange +8 beginbfrange +<00> <01> <0393> +<09> <0A> <03A8> +<23> <26> <0023> +<28> <3B> <0028> +<3F> <5B> <003F> +<5D> <5E> <005D> +<61> <7A> <0061> +<7B> <7C> <2013> +endbfrange +40 beginbfchar +<02> <0398> +<03> <039B> +<04> <039E> +<05> <03A0> +<06> <03A3> +<07> <03D2> +<08> <03A6> +<0B> <00660066> +<0C> <00660069> +<0D> <0066006C> +<0E> <006600660069> +<0F> <00660066006C> +<10> <0131> +<11> <0237> +<12> <0060> +<13> <00B4> +<14> <02C7> +<15> <02D8> +<16> <00AF> +<17> <02DA> +<18> <00B8> +<19> <00DF> +<1A> <00E6> +<1B> <0153> +<1C> <00F8> +<1D> <00C6> +<1E> <0152> +<1F> <00D8> +<21> <0021> +<22> <201D> +<27> <2019> +<3C> <00A1> +<3D> <003D> +<3E> <00BF> +<5C> <201C> +<5F> <02D9> +<60> <2018> +<7D> <02DD> +<7E> <007E> +<7F> <00A8> +endbfchar +endcmap +CMapName currentdict /CMap defineresource pop +end +end +%%EndResource +%%EOF + }\endgroup + \expandafter\edef\csname cmapOT1\endcsname#1{% + \pdffontattr#1{/ToUnicode \the\pdflastobj\space 0 R}% + }% +% +% \cmapOT1IT + \begingroup + \catcode`\^^M=\active \def^^M{^^J}% Output line endings as the ^^J char. + \catcode`\%=12 \immediate\pdfobj stream {%!PS-Adobe-3.0 Resource-CMap +%%DocumentNeededResources: ProcSet (CIDInit) +%%IncludeResource: ProcSet (CIDInit) +%%BeginResource: CMap (TeX-OT1IT-0) +%%Title: (TeX-OT1IT-0 TeX OT1IT 0) +%%Version: 1.000 +%%EndComments +/CIDInit /ProcSet findresource begin +12 dict begin +begincmap +/CIDSystemInfo +<< /Registry (TeX) +/Ordering (OT1IT) +/Supplement 0 +>> def +/CMapName /TeX-OT1IT-0 def +/CMapType 2 def +1 begincodespacerange +<00> <7F> +endcodespacerange +8 beginbfrange +<00> <01> <0393> +<09> <0A> <03A8> +<25> <26> <0025> +<28> <3B> <0028> +<3F> <5B> <003F> +<5D> <5E> <005D> +<61> <7A> <0061> +<7B> <7C> <2013> +endbfrange +42 beginbfchar +<02> <0398> +<03> <039B> +<04> <039E> +<05> <03A0> +<06> <03A3> +<07> <03D2> +<08> <03A6> +<0B> <00660066> +<0C> <00660069> +<0D> <0066006C> +<0E> <006600660069> +<0F> <00660066006C> +<10> <0131> +<11> <0237> +<12> <0060> +<13> <00B4> +<14> <02C7> +<15> <02D8> +<16> <00AF> +<17> <02DA> +<18> <00B8> +<19> <00DF> +<1A> <00E6> +<1B> <0153> +<1C> <00F8> +<1D> <00C6> +<1E> <0152> +<1F> <00D8> +<21> <0021> +<22> <201D> +<23> <0023> +<24> <00A3> +<27> <2019> +<3C> <00A1> +<3D> <003D> +<3E> <00BF> +<5C> <201C> +<5F> <02D9> +<60> <2018> +<7D> <02DD> +<7E> <007E> +<7F> <00A8> +endbfchar +endcmap +CMapName currentdict /CMap defineresource pop +end +end +%%EndResource +%%EOF + }\endgroup + \expandafter\edef\csname cmapOT1IT\endcsname#1{% + \pdffontattr#1{/ToUnicode \the\pdflastobj\space 0 R}% + }% +% +% \cmapOT1TT + \begingroup + \catcode`\^^M=\active \def^^M{^^J}% Output line endings as the ^^J char. + \catcode`\%=12 \immediate\pdfobj stream {%!PS-Adobe-3.0 Resource-CMap +%%DocumentNeededResources: ProcSet (CIDInit) +%%IncludeResource: ProcSet (CIDInit) +%%BeginResource: CMap (TeX-OT1TT-0) +%%Title: (TeX-OT1TT-0 TeX OT1TT 0) +%%Version: 1.000 +%%EndComments +/CIDInit /ProcSet findresource begin +12 dict begin +begincmap +/CIDSystemInfo +<< /Registry (TeX) +/Ordering (OT1TT) +/Supplement 0 +>> def +/CMapName /TeX-OT1TT-0 def +/CMapType 2 def +1 begincodespacerange +<00> <7F> +endcodespacerange +5 beginbfrange +<00> <01> <0393> +<09> <0A> <03A8> +<21> <26> <0021> +<28> <5F> <0028> +<61> <7E> <0061> +endbfrange +32 beginbfchar +<02> <0398> +<03> <039B> +<04> <039E> +<05> <03A0> +<06> <03A3> +<07> <03D2> +<08> <03A6> +<0B> <2191> +<0C> <2193> +<0D> <0027> +<0E> <00A1> +<0F> <00BF> +<10> <0131> +<11> <0237> +<12> <0060> +<13> <00B4> +<14> <02C7> +<15> <02D8> +<16> <00AF> +<17> <02DA> +<18> <00B8> +<19> <00DF> +<1A> <00E6> +<1B> <0153> +<1C> <00F8> +<1D> <00C6> +<1E> <0152> +<1F> <00D8> +<20> <2423> +<27> <2019> +<60> <2018> +<7F> <00A8> +endbfchar +endcmap +CMapName currentdict /CMap defineresource pop +end +end +%%EndResource +%%EOF + }\endgroup + \expandafter\edef\csname cmapOT1TT\endcsname#1{% + \pdffontattr#1{/ToUnicode \the\pdflastobj\space 0 R}% + }% +\fi\fi + + +% Set the font macro #1 to the font named #2, adding on the +% specified font prefix (normally `cm'). +% #3 is the font's design size, #4 is a scale factor, #5 is the CMap +% encoding (currently only OT1, OT1IT and OT1TT are allowed, pass +% empty to omit). +\def\setfont#1#2#3#4#5{% + \font#1=\fontprefix#2#3 scaled #4 + \csname cmap#5\endcsname#1% +} +% This is what gets called when #5 of \setfont is empty. +\let\cmap\gobble +% emacs-page end of cmaps + +% Use cm as the default font prefix. +% To specify the font prefix, you must define \fontprefix +% before you read in texinfo.tex. +\ifx\fontprefix\undefined +\def\fontprefix{cm} +\fi +% Support font families that don't use the same naming scheme as CM. +\def\rmshape{r} +\def\rmbshape{bx} %where the normal face is bold +\def\bfshape{b} +\def\bxshape{bx} +\def\ttshape{tt} +\def\ttbshape{tt} +\def\ttslshape{sltt} +\def\itshape{ti} +\def\itbshape{bxti} +\def\slshape{sl} +\def\slbshape{bxsl} +\def\sfshape{ss} +\def\sfbshape{ss} +\def\scshape{csc} +\def\scbshape{csc} + +% Definitions for a main text size of 11pt. This is the default in +% Texinfo. +% +\def\definetextfontsizexi{% +% Text fonts (11.2pt, magstep1). +\def\textnominalsize{11pt} +\edef\mainmagstep{\magstephalf} +\setfont\textrm\rmshape{10}{\mainmagstep}{OT1} +\setfont\texttt\ttshape{10}{\mainmagstep}{OT1TT} +\setfont\textbf\bfshape{10}{\mainmagstep}{OT1} +\setfont\textit\itshape{10}{\mainmagstep}{OT1IT} +\setfont\textsl\slshape{10}{\mainmagstep}{OT1} +\setfont\textsf\sfshape{10}{\mainmagstep}{OT1} +\setfont\textsc\scshape{10}{\mainmagstep}{OT1} +\setfont\textttsl\ttslshape{10}{\mainmagstep}{OT1TT} +\font\texti=cmmi10 scaled \mainmagstep +\font\textsy=cmsy10 scaled \mainmagstep +\def\textecsize{1095} + +% A few fonts for @defun names and args. +\setfont\defbf\bfshape{10}{\magstep1}{OT1} +\setfont\deftt\ttshape{10}{\magstep1}{OT1TT} +\setfont\defttsl\ttslshape{10}{\magstep1}{OT1TT} +\def\df{\let\tentt=\deftt \let\tenbf = \defbf \let\tenttsl=\defttsl \bf} + +% Fonts for indices, footnotes, small examples (9pt). +\def\smallnominalsize{9pt} +\setfont\smallrm\rmshape{9}{1000}{OT1} +\setfont\smalltt\ttshape{9}{1000}{OT1TT} +\setfont\smallbf\bfshape{10}{900}{OT1} +\setfont\smallit\itshape{9}{1000}{OT1IT} +\setfont\smallsl\slshape{9}{1000}{OT1} +\setfont\smallsf\sfshape{9}{1000}{OT1} +\setfont\smallsc\scshape{10}{900}{OT1} +\setfont\smallttsl\ttslshape{10}{900}{OT1TT} +\font\smalli=cmmi9 +\font\smallsy=cmsy9 +\def\smallecsize{0900} + +% Fonts for small examples (8pt). +\def\smallernominalsize{8pt} +\setfont\smallerrm\rmshape{8}{1000}{OT1} +\setfont\smallertt\ttshape{8}{1000}{OT1TT} +\setfont\smallerbf\bfshape{10}{800}{OT1} +\setfont\smallerit\itshape{8}{1000}{OT1IT} +\setfont\smallersl\slshape{8}{1000}{OT1} +\setfont\smallersf\sfshape{8}{1000}{OT1} +\setfont\smallersc\scshape{10}{800}{OT1} +\setfont\smallerttsl\ttslshape{10}{800}{OT1TT} +\font\smalleri=cmmi8 +\font\smallersy=cmsy8 +\def\smallerecsize{0800} + +% Fonts for title page (20.4pt): +\def\titlenominalsize{20pt} +\setfont\titlerm\rmbshape{12}{\magstep3}{OT1} +\setfont\titleit\itbshape{10}{\magstep4}{OT1IT} +\setfont\titlesl\slbshape{10}{\magstep4}{OT1} +\setfont\titlett\ttbshape{12}{\magstep3}{OT1TT} +\setfont\titlettsl\ttslshape{10}{\magstep4}{OT1TT} +\setfont\titlesf\sfbshape{17}{\magstep1}{OT1} +\let\titlebf=\titlerm +\setfont\titlesc\scbshape{10}{\magstep4}{OT1} +\font\titlei=cmmi12 scaled \magstep3 +\font\titlesy=cmsy10 scaled \magstep4 +\def\authorrm{\secrm} +\def\authortt{\sectt} +\def\titleecsize{2074} + +% Chapter (and unnumbered) fonts (17.28pt). +\def\chapnominalsize{17pt} +\setfont\chaprm\rmbshape{12}{\magstep2}{OT1} +\setfont\chapit\itbshape{10}{\magstep3}{OT1IT} +\setfont\chapsl\slbshape{10}{\magstep3}{OT1} +\setfont\chaptt\ttbshape{12}{\magstep2}{OT1TT} +\setfont\chapttsl\ttslshape{10}{\magstep3}{OT1TT} +\setfont\chapsf\sfbshape{17}{1000}{OT1} +\let\chapbf=\chaprm +\setfont\chapsc\scbshape{10}{\magstep3}{OT1} +\font\chapi=cmmi12 scaled \magstep2 +\font\chapsy=cmsy10 scaled \magstep3 +\def\chapecsize{1728} + +% Section fonts (14.4pt). +\def\secnominalsize{14pt} +\setfont\secrm\rmbshape{12}{\magstep1}{OT1} +\setfont\secit\itbshape{10}{\magstep2}{OT1IT} +\setfont\secsl\slbshape{10}{\magstep2}{OT1} +\setfont\sectt\ttbshape{12}{\magstep1}{OT1TT} +\setfont\secttsl\ttslshape{10}{\magstep2}{OT1TT} +\setfont\secsf\sfbshape{12}{\magstep1}{OT1} +\let\secbf\secrm +\setfont\secsc\scbshape{10}{\magstep2}{OT1} +\font\seci=cmmi12 scaled \magstep1 +\font\secsy=cmsy10 scaled \magstep2 +\def\sececsize{1440} + +% Subsection fonts (13.15pt). +\def\ssecnominalsize{13pt} +\setfont\ssecrm\rmbshape{12}{\magstephalf}{OT1} +\setfont\ssecit\itbshape{10}{1315}{OT1IT} +\setfont\ssecsl\slbshape{10}{1315}{OT1} +\setfont\ssectt\ttbshape{12}{\magstephalf}{OT1TT} +\setfont\ssecttsl\ttslshape{10}{1315}{OT1TT} +\setfont\ssecsf\sfbshape{12}{\magstephalf}{OT1} +\let\ssecbf\ssecrm +\setfont\ssecsc\scbshape{10}{1315}{OT1} +\font\sseci=cmmi12 scaled \magstephalf +\font\ssecsy=cmsy10 scaled 1315 +\def\ssececsize{1200} + +% Reduced fonts for @acro in text (10pt). +\def\reducednominalsize{10pt} +\setfont\reducedrm\rmshape{10}{1000}{OT1} +\setfont\reducedtt\ttshape{10}{1000}{OT1TT} +\setfont\reducedbf\bfshape{10}{1000}{OT1} +\setfont\reducedit\itshape{10}{1000}{OT1IT} +\setfont\reducedsl\slshape{10}{1000}{OT1} +\setfont\reducedsf\sfshape{10}{1000}{OT1} +\setfont\reducedsc\scshape{10}{1000}{OT1} +\setfont\reducedttsl\ttslshape{10}{1000}{OT1TT} +\font\reducedi=cmmi10 +\font\reducedsy=cmsy10 +\def\reducedecsize{1000} + +% reset the current fonts +\textfonts +\rm +} % end of 11pt text font size definitions + + +% Definitions to make the main text be 10pt Computer Modern, with +% section, chapter, etc., sizes following suit. This is for the GNU +% Press printing of the Emacs 22 manual. Maybe other manuals in the +% future. Used with @smallbook, which sets the leading to 12pt. +% +\def\definetextfontsizex{% +% Text fonts (10pt). +\def\textnominalsize{10pt} +\edef\mainmagstep{1000} +\setfont\textrm\rmshape{10}{\mainmagstep}{OT1} +\setfont\texttt\ttshape{10}{\mainmagstep}{OT1TT} +\setfont\textbf\bfshape{10}{\mainmagstep}{OT1} +\setfont\textit\itshape{10}{\mainmagstep}{OT1IT} +\setfont\textsl\slshape{10}{\mainmagstep}{OT1} +\setfont\textsf\sfshape{10}{\mainmagstep}{OT1} +\setfont\textsc\scshape{10}{\mainmagstep}{OT1} +\setfont\textttsl\ttslshape{10}{\mainmagstep}{OT1TT} +\font\texti=cmmi10 scaled \mainmagstep +\font\textsy=cmsy10 scaled \mainmagstep +\def\textecsize{1000} + +% A few fonts for @defun names and args. +\setfont\defbf\bfshape{10}{\magstephalf}{OT1} +\setfont\deftt\ttshape{10}{\magstephalf}{OT1TT} +\setfont\defttsl\ttslshape{10}{\magstephalf}{OT1TT} +\def\df{\let\tentt=\deftt \let\tenbf = \defbf \let\tenttsl=\defttsl \bf} + +% Fonts for indices, footnotes, small examples (9pt). +\def\smallnominalsize{9pt} +\setfont\smallrm\rmshape{9}{1000}{OT1} +\setfont\smalltt\ttshape{9}{1000}{OT1TT} +\setfont\smallbf\bfshape{10}{900}{OT1} +\setfont\smallit\itshape{9}{1000}{OT1IT} +\setfont\smallsl\slshape{9}{1000}{OT1} +\setfont\smallsf\sfshape{9}{1000}{OT1} +\setfont\smallsc\scshape{10}{900}{OT1} +\setfont\smallttsl\ttslshape{10}{900}{OT1TT} +\font\smalli=cmmi9 +\font\smallsy=cmsy9 +\def\smallecsize{0900} + +% Fonts for small examples (8pt). +\def\smallernominalsize{8pt} +\setfont\smallerrm\rmshape{8}{1000}{OT1} +\setfont\smallertt\ttshape{8}{1000}{OT1TT} +\setfont\smallerbf\bfshape{10}{800}{OT1} +\setfont\smallerit\itshape{8}{1000}{OT1IT} +\setfont\smallersl\slshape{8}{1000}{OT1} +\setfont\smallersf\sfshape{8}{1000}{OT1} +\setfont\smallersc\scshape{10}{800}{OT1} +\setfont\smallerttsl\ttslshape{10}{800}{OT1TT} +\font\smalleri=cmmi8 +\font\smallersy=cmsy8 +\def\smallerecsize{0800} + +% Fonts for title page (20.4pt): +\def\titlenominalsize{20pt} +\setfont\titlerm\rmbshape{12}{\magstep3}{OT1} +\setfont\titleit\itbshape{10}{\magstep4}{OT1IT} +\setfont\titlesl\slbshape{10}{\magstep4}{OT1} +\setfont\titlett\ttbshape{12}{\magstep3}{OT1TT} +\setfont\titlettsl\ttslshape{10}{\magstep4}{OT1TT} +\setfont\titlesf\sfbshape{17}{\magstep1}{OT1} +\let\titlebf=\titlerm +\setfont\titlesc\scbshape{10}{\magstep4}{OT1} +\font\titlei=cmmi12 scaled \magstep3 +\font\titlesy=cmsy10 scaled \magstep4 +\def\authorrm{\secrm} +\def\authortt{\sectt} +\def\titleecsize{2074} + +% Chapter fonts (14.4pt). +\def\chapnominalsize{14pt} +\setfont\chaprm\rmbshape{12}{\magstep1}{OT1} +\setfont\chapit\itbshape{10}{\magstep2}{OT1IT} +\setfont\chapsl\slbshape{10}{\magstep2}{OT1} +\setfont\chaptt\ttbshape{12}{\magstep1}{OT1TT} +\setfont\chapttsl\ttslshape{10}{\magstep2}{OT1TT} +\setfont\chapsf\sfbshape{12}{\magstep1}{OT1} +\let\chapbf\chaprm +\setfont\chapsc\scbshape{10}{\magstep2}{OT1} +\font\chapi=cmmi12 scaled \magstep1 +\font\chapsy=cmsy10 scaled \magstep2 +\def\chapecsize{1440} + +% Section fonts (12pt). +\def\secnominalsize{12pt} +\setfont\secrm\rmbshape{12}{1000}{OT1} +\setfont\secit\itbshape{10}{\magstep1}{OT1IT} +\setfont\secsl\slbshape{10}{\magstep1}{OT1} +\setfont\sectt\ttbshape{12}{1000}{OT1TT} +\setfont\secttsl\ttslshape{10}{\magstep1}{OT1TT} +\setfont\secsf\sfbshape{12}{1000}{OT1} +\let\secbf\secrm +\setfont\secsc\scbshape{10}{\magstep1}{OT1} +\font\seci=cmmi12 +\font\secsy=cmsy10 scaled \magstep1 +\def\sececsize{1200} + +% Subsection fonts (10pt). +\def\ssecnominalsize{10pt} +\setfont\ssecrm\rmbshape{10}{1000}{OT1} +\setfont\ssecit\itbshape{10}{1000}{OT1IT} +\setfont\ssecsl\slbshape{10}{1000}{OT1} +\setfont\ssectt\ttbshape{10}{1000}{OT1TT} +\setfont\ssecttsl\ttslshape{10}{1000}{OT1TT} +\setfont\ssecsf\sfbshape{10}{1000}{OT1} +\let\ssecbf\ssecrm +\setfont\ssecsc\scbshape{10}{1000}{OT1} +\font\sseci=cmmi10 +\font\ssecsy=cmsy10 +\def\ssececsize{1000} + +% Reduced fonts for @acro in text (9pt). +\def\reducednominalsize{9pt} +\setfont\reducedrm\rmshape{9}{1000}{OT1} +\setfont\reducedtt\ttshape{9}{1000}{OT1TT} +\setfont\reducedbf\bfshape{10}{900}{OT1} +\setfont\reducedit\itshape{9}{1000}{OT1IT} +\setfont\reducedsl\slshape{9}{1000}{OT1} +\setfont\reducedsf\sfshape{9}{1000}{OT1} +\setfont\reducedsc\scshape{10}{900}{OT1} +\setfont\reducedttsl\ttslshape{10}{900}{OT1TT} +\font\reducedi=cmmi9 +\font\reducedsy=cmsy9 +\def\reducedecsize{0900} + +% reduce space between paragraphs +\divide\parskip by 2 + +% reset the current fonts +\textfonts +\rm +} % end of 10pt text font size definitions + + +% We provide the user-level command +% @fonttextsize 10 +% (or 11) to redefine the text font size. pt is assumed. +% +\def\xword{10} +\def\xiword{11} +% +\parseargdef\fonttextsize{% + \def\textsizearg{#1}% + \wlog{doing @fonttextsize \textsizearg}% + % + % Set \globaldefs so that documents can use this inside @tex, since + % makeinfo 4.8 does not support it, but we need it nonetheless. + % + \begingroup \globaldefs=1 + \ifx\textsizearg\xword \definetextfontsizex + \else \ifx\textsizearg\xiword \definetextfontsizexi + \else + \errhelp=\EMsimple + \errmessage{@fonttextsize only supports `10' or `11', not `\textsizearg'} + \fi\fi + \endgroup +} + + +% In order for the font changes to affect most math symbols and letters, +% we have to define the \textfont of the standard families. Since +% texinfo doesn't allow for producing subscripts and superscripts except +% in the main text, we don't bother to reset \scriptfont and +% \scriptscriptfont (which would also require loading a lot more fonts). +% +\def\resetmathfonts{% + \textfont0=\tenrm \textfont1=\teni \textfont2=\tensy + \textfont\itfam=\tenit \textfont\slfam=\tensl \textfont\bffam=\tenbf + \textfont\ttfam=\tentt \textfont\sffam=\tensf +} + +% The font-changing commands redefine the meanings of \tenSTYLE, instead +% of just \STYLE. We do this because \STYLE needs to also set the +% current \fam for math mode. Our \STYLE (e.g., \rm) commands hardwire +% \tenSTYLE to set the current font. +% +% Each font-changing command also sets the names \lsize (one size lower) +% and \lllsize (three sizes lower). These relative commands are used in +% the LaTeX logo and acronyms. +% +% This all needs generalizing, badly. +% +\def\textfonts{% + \let\tenrm=\textrm \let\tenit=\textit \let\tensl=\textsl + \let\tenbf=\textbf \let\tentt=\texttt \let\smallcaps=\textsc + \let\tensf=\textsf \let\teni=\texti \let\tensy=\textsy + \let\tenttsl=\textttsl + \def\curfontsize{text}% + \def\lsize{reduced}\def\lllsize{smaller}% + \resetmathfonts \setleading{\textleading}} +\def\titlefonts{% + \let\tenrm=\titlerm \let\tenit=\titleit \let\tensl=\titlesl + \let\tenbf=\titlebf \let\tentt=\titlett \let\smallcaps=\titlesc + \let\tensf=\titlesf \let\teni=\titlei \let\tensy=\titlesy + \let\tenttsl=\titlettsl + \def\curfontsize{title}% + \def\lsize{chap}\def\lllsize{subsec}% + \resetmathfonts \setleading{25pt}} +\def\titlefont#1{{\titlefonts\rm #1}} +\def\chapfonts{% + \let\tenrm=\chaprm \let\tenit=\chapit \let\tensl=\chapsl + \let\tenbf=\chapbf \let\tentt=\chaptt \let\smallcaps=\chapsc + \let\tensf=\chapsf \let\teni=\chapi \let\tensy=\chapsy + \let\tenttsl=\chapttsl + \def\curfontsize{chap}% + \def\lsize{sec}\def\lllsize{text}% + \resetmathfonts \setleading{19pt}} +\def\secfonts{% + \let\tenrm=\secrm \let\tenit=\secit \let\tensl=\secsl + \let\tenbf=\secbf \let\tentt=\sectt \let\smallcaps=\secsc + \let\tensf=\secsf \let\teni=\seci \let\tensy=\secsy + \let\tenttsl=\secttsl + \def\curfontsize{sec}% + \def\lsize{subsec}\def\lllsize{reduced}% + \resetmathfonts \setleading{16pt}} +\def\subsecfonts{% + \let\tenrm=\ssecrm \let\tenit=\ssecit \let\tensl=\ssecsl + \let\tenbf=\ssecbf \let\tentt=\ssectt \let\smallcaps=\ssecsc + \let\tensf=\ssecsf \let\teni=\sseci \let\tensy=\ssecsy + \let\tenttsl=\ssecttsl + \def\curfontsize{ssec}% + \def\lsize{text}\def\lllsize{small}% + \resetmathfonts \setleading{15pt}} +\let\subsubsecfonts = \subsecfonts +\def\reducedfonts{% + \let\tenrm=\reducedrm \let\tenit=\reducedit \let\tensl=\reducedsl + \let\tenbf=\reducedbf \let\tentt=\reducedtt \let\reducedcaps=\reducedsc + \let\tensf=\reducedsf \let\teni=\reducedi \let\tensy=\reducedsy + \let\tenttsl=\reducedttsl + \def\curfontsize{reduced}% + \def\lsize{small}\def\lllsize{smaller}% + \resetmathfonts \setleading{10.5pt}} +\def\smallfonts{% + \let\tenrm=\smallrm \let\tenit=\smallit \let\tensl=\smallsl + \let\tenbf=\smallbf \let\tentt=\smalltt \let\smallcaps=\smallsc + \let\tensf=\smallsf \let\teni=\smalli \let\tensy=\smallsy + \let\tenttsl=\smallttsl + \def\curfontsize{small}% + \def\lsize{smaller}\def\lllsize{smaller}% + \resetmathfonts \setleading{10.5pt}} +\def\smallerfonts{% + \let\tenrm=\smallerrm \let\tenit=\smallerit \let\tensl=\smallersl + \let\tenbf=\smallerbf \let\tentt=\smallertt \let\smallcaps=\smallersc + \let\tensf=\smallersf \let\teni=\smalleri \let\tensy=\smallersy + \let\tenttsl=\smallerttsl + \def\curfontsize{smaller}% + \def\lsize{smaller}\def\lllsize{smaller}% + \resetmathfonts \setleading{9.5pt}} + +% Set the fonts to use with the @small... environments. +\let\smallexamplefonts = \smallfonts + +% About \smallexamplefonts. If we use \smallfonts (9pt), @smallexample +% can fit this many characters: +% 8.5x11=86 smallbook=72 a4=90 a5=69 +% If we use \scriptfonts (8pt), then we can fit this many characters: +% 8.5x11=90+ smallbook=80 a4=90+ a5=77 +% For me, subjectively, the few extra characters that fit aren't worth +% the additional smallness of 8pt. So I'm making the default 9pt. +% +% By the way, for comparison, here's what fits with @example (10pt): +% 8.5x11=71 smallbook=60 a4=75 a5=58 +% +% I wish the USA used A4 paper. +% --karl, 24jan03. + + +% Set up the default fonts, so we can use them for creating boxes. +% +\definetextfontsizexi + +% Define these so they can be easily changed for other fonts. +\def\angleleft{$\langle$} +\def\angleright{$\rangle$} + +% Count depth in font-changes, for error checks +\newcount\fontdepth \fontdepth=0 + +% Fonts for short table of contents. +\setfont\shortcontrm\rmshape{12}{1000}{OT1} +\setfont\shortcontbf\bfshape{10}{\magstep1}{OT1} % no cmb12 +\setfont\shortcontsl\slshape{12}{1000}{OT1} +\setfont\shortconttt\ttshape{12}{1000}{OT1TT} + +%% Add scribe-like font environments, plus @l for inline lisp (usually sans +%% serif) and @ii for TeX italic + +% \smartitalic{ARG} outputs arg in italics, followed by an italic correction +% unless the following character is such as not to need one. +\def\smartitalicx{\ifx\next,\else\ifx\next-\else\ifx\next.\else + \ptexslash\fi\fi\fi} +\def\smartslanted#1{{\ifusingtt\ttsl\sl #1}\futurelet\next\smartitalicx} +\def\smartitalic#1{{\ifusingtt\ttsl\it #1}\futurelet\next\smartitalicx} + +% like \smartslanted except unconditionally uses \ttsl. +% @var is set to this for defun arguments. +\def\ttslanted#1{{\ttsl #1}\futurelet\next\smartitalicx} + +% like \smartslanted except unconditionally use \sl. We never want +% ttsl for book titles, do we? +\def\cite#1{{\sl #1}\futurelet\next\smartitalicx} + +\let\i=\smartitalic +\let\slanted=\smartslanted +\let\var=\smartslanted +\let\dfn=\smartslanted +\let\emph=\smartitalic + +% @b, explicit bold. +\def\b#1{{\bf #1}} +\let\strong=\b + +% @sansserif, explicit sans. +\def\sansserif#1{{\sf #1}} + +% We can't just use \exhyphenpenalty, because that only has effect at +% the end of a paragraph. Restore normal hyphenation at the end of the +% group within which \nohyphenation is presumably called. +% +\def\nohyphenation{\hyphenchar\font = -1 \aftergroup\restorehyphenation} +\def\restorehyphenation{\hyphenchar\font = `- } + +% Set sfcode to normal for the chars that usually have another value. +% Can't use plain's \frenchspacing because it uses the `\x notation, and +% sometimes \x has an active definition that messes things up. +% +\catcode`@=11 + \def\plainfrenchspacing{% + \sfcode\dotChar =\@m \sfcode\questChar=\@m \sfcode\exclamChar=\@m + \sfcode\colonChar=\@m \sfcode\semiChar =\@m \sfcode\commaChar =\@m + \def\endofsentencespacefactor{1000}% for @. and friends + } + \def\plainnonfrenchspacing{% + \sfcode`\.3000\sfcode`\?3000\sfcode`\!3000 + \sfcode`\:2000\sfcode`\;1500\sfcode`\,1250 + \def\endofsentencespacefactor{3000}% for @. and friends + } +\catcode`@=\other +\def\endofsentencespacefactor{3000}% default + +\def\t#1{% + {\tt \rawbackslash \plainfrenchspacing #1}% + \null +} +\def\samp#1{`\tclose{#1}'\null} +\setfont\keyrm\rmshape{8}{1000}{OT1} +\font\keysy=cmsy9 +\def\key#1{{\keyrm\textfont2=\keysy \leavevmode\hbox{% + \raise0.4pt\hbox{\angleleft}\kern-.08em\vtop{% + \vbox{\hrule\kern-0.4pt + \hbox{\raise0.4pt\hbox{\vphantom{\angleleft}}#1}}% + \kern-0.4pt\hrule}% + \kern-.06em\raise0.4pt\hbox{\angleright}}}} +\def\key #1{{\nohyphenation \uppercase{#1}}\null} +% The old definition, with no lozenge: +%\def\key #1{{\ttsl \nohyphenation \uppercase{#1}}\null} +\def\ctrl #1{{\tt \rawbackslash \hat}#1} + +% @file, @option are the same as @samp. +\let\file=\samp +\let\option=\samp + +% @code is a modification of @t, +% which makes spaces the same size as normal in the surrounding text. +\def\tclose#1{% + {% + % Change normal interword space to be same as for the current font. + \spaceskip = \fontdimen2\font + % + % Switch to typewriter. + \tt + % + % But `\ ' produces the large typewriter interword space. + \def\ {{\spaceskip = 0pt{} }}% + % + % Turn off hyphenation. + \nohyphenation + % + \rawbackslash + \plainfrenchspacing + #1% + }% + \null +} + +% We *must* turn on hyphenation at `-' and `_' in @code. +% Otherwise, it is too hard to avoid overfull hboxes +% in the Emacs manual, the Library manual, etc. + +% Unfortunately, TeX uses one parameter (\hyphenchar) to control +% both hyphenation at - and hyphenation within words. +% We must therefore turn them both off (\tclose does that) +% and arrange explicitly to hyphenate at a dash. +% -- rms. +{ + \catcode`\-=\active \catcode`\_=\active + \catcode`\'=\active \catcode`\`=\active + % + \global\def\code{\begingroup + \catcode\rquoteChar=\active \catcode\lquoteChar=\active + \let'\codequoteright \let`\codequoteleft + % + \catcode\dashChar=\active \catcode\underChar=\active + \ifallowcodebreaks + \let-\codedash + \let_\codeunder + \else + \let-\realdash + \let_\realunder + \fi + \codex + } +} + +\def\realdash{-} +\def\codedash{-\discretionary{}{}{}} +\def\codeunder{% + % this is all so @math{@code{var_name}+1} can work. In math mode, _ + % is "active" (mathcode"8000) and \normalunderscore (or \char95, etc.) + % will therefore expand the active definition of _, which is us + % (inside @code that is), therefore an endless loop. + \ifusingtt{\ifmmode + \mathchar"075F % class 0=ordinary, family 7=ttfam, pos 0x5F=_. + \else\normalunderscore \fi + \discretionary{}{}{}}% + {\_}% +} +\def\codex #1{\tclose{#1}\endgroup} + +% An additional complication: the above will allow breaks after, e.g., +% each of the four underscores in __typeof__. This is undesirable in +% some manuals, especially if they don't have long identifiers in +% general. @allowcodebreaks provides a way to control this. +% +\newif\ifallowcodebreaks \allowcodebreakstrue + +\def\keywordtrue{true} +\def\keywordfalse{false} + +\parseargdef\allowcodebreaks{% + \def\txiarg{#1}% + \ifx\txiarg\keywordtrue + \allowcodebreakstrue + \else\ifx\txiarg\keywordfalse + \allowcodebreaksfalse + \else + \errhelp = \EMsimple + \errmessage{Unknown @allowcodebreaks option `\txiarg'}% + \fi\fi +} + +% @kbd is like @code, except that if the argument is just one @key command, +% then @kbd has no effect. + +% @kbdinputstyle -- arg is `distinct' (@kbd uses slanted tty font always), +% `example' (@kbd uses ttsl only inside of @example and friends), +% or `code' (@kbd uses normal tty font always). +\parseargdef\kbdinputstyle{% + \def\txiarg{#1}% + \ifx\txiarg\worddistinct + \gdef\kbdexamplefont{\ttsl}\gdef\kbdfont{\ttsl}% + \else\ifx\txiarg\wordexample + \gdef\kbdexamplefont{\ttsl}\gdef\kbdfont{\tt}% + \else\ifx\txiarg\wordcode + \gdef\kbdexamplefont{\tt}\gdef\kbdfont{\tt}% + \else + \errhelp = \EMsimple + \errmessage{Unknown @kbdinputstyle option `\txiarg'}% + \fi\fi\fi +} +\def\worddistinct{distinct} +\def\wordexample{example} +\def\wordcode{code} + +% Default is `distinct.' +\kbdinputstyle distinct + +\def\xkey{\key} +\def\kbdfoo#1#2#3\par{\def\one{#1}\def\three{#3}\def\threex{??}% +\ifx\one\xkey\ifx\threex\three \key{#2}% +\else{\tclose{\kbdfont\look}}\fi +\else{\tclose{\kbdfont\look}}\fi} + +% For @indicateurl, @env, @command quotes seem unnecessary, so use \code. +\let\indicateurl=\code +\let\env=\code +\let\command=\code + +% @uref (abbreviation for `urlref') takes an optional (comma-separated) +% second argument specifying the text to display and an optional third +% arg as text to display instead of (rather than in addition to) the url +% itself. First (mandatory) arg is the url. Perhaps eventually put in +% a hypertex \special here. +% +\def\uref#1{\douref #1,,,\finish} +\def\douref#1,#2,#3,#4\finish{\begingroup + \unsepspaces + \pdfurl{#1}% + \setbox0 = \hbox{\ignorespaces #3}% + \ifdim\wd0 > 0pt + \unhbox0 % third arg given, show only that + \else + \setbox0 = \hbox{\ignorespaces #2}% + \ifdim\wd0 > 0pt + \ifpdf + \unhbox0 % PDF: 2nd arg given, show only it + \else + \unhbox0\ (\code{#1})% DVI: 2nd arg given, show both it and url + \fi + \else + \code{#1}% only url given, so show it + \fi + \fi + \endlink +\endgroup} + +% @url synonym for @uref, since that's how everyone uses it. +% +\let\url=\uref + +% rms does not like angle brackets --karl, 17may97. +% So now @email is just like @uref, unless we are pdf. +% +%\def\email#1{\angleleft{\tt #1}\angleright} +\ifpdf + \def\email#1{\doemail#1,,\finish} + \def\doemail#1,#2,#3\finish{\begingroup + \unsepspaces + \pdfurl{mailto:#1}% + \setbox0 = \hbox{\ignorespaces #2}% + \ifdim\wd0>0pt\unhbox0\else\code{#1}\fi + \endlink + \endgroup} +\else + \let\email=\uref +\fi + +% Check if we are currently using a typewriter font. Since all the +% Computer Modern typewriter fonts have zero interword stretch (and +% shrink), and it is reasonable to expect all typewriter fonts to have +% this property, we can check that font parameter. +% +\def\ifmonospace{\ifdim\fontdimen3\font=0pt } + +% Typeset a dimension, e.g., `in' or `pt'. The only reason for the +% argument is to make the input look right: @dmn{pt} instead of @dmn{}pt. +% +\def\dmn#1{\thinspace #1} + +\def\kbd#1{\def\look{#1}\expandafter\kbdfoo\look??\par} + +% @l was never documented to mean ``switch to the Lisp font'', +% and it is not used as such in any manual I can find. We need it for +% Polish suppressed-l. --karl, 22sep96. +%\def\l#1{{\li #1}\null} + +% Explicit font changes: @r, @sc, undocumented @ii. +\def\r#1{{\rm #1}} % roman font +\def\sc#1{{\smallcaps#1}} % smallcaps font +\def\ii#1{{\it #1}} % italic font + +% @acronym for "FBI", "NATO", and the like. +% We print this one point size smaller, since it's intended for +% all-uppercase. +% +\def\acronym#1{\doacronym #1,,\finish} +\def\doacronym#1,#2,#3\finish{% + {\selectfonts\lsize #1}% + \def\temp{#2}% + \ifx\temp\empty \else + \space ({\unsepspaces \ignorespaces \temp \unskip})% + \fi +} + +% @abbr for "Comput. J." and the like. +% No font change, but don't do end-of-sentence spacing. +% +\def\abbr#1{\doabbr #1,,\finish} +\def\doabbr#1,#2,#3\finish{% + {\plainfrenchspacing #1}% + \def\temp{#2}% + \ifx\temp\empty \else + \space ({\unsepspaces \ignorespaces \temp \unskip})% + \fi +} + +% @pounds{} is a sterling sign, which Knuth put in the CM italic font. +% +\def\pounds{{\it\$}} + +% @euro{} comes from a separate font, depending on the current style. +% We use the free feym* fonts from the eurosym package by Henrik +% Theiling, which support regular, slanted, bold and bold slanted (and +% "outlined" (blackboard board, sort of) versions, which we don't need). +% It is available from http://www.ctan.org/tex-archive/fonts/eurosym. +% +% Although only regular is the truly official Euro symbol, we ignore +% that. The Euro is designed to be slightly taller than the regular +% font height. +% +% feymr - regular +% feymo - slanted +% feybr - bold +% feybo - bold slanted +% +% There is no good (free) typewriter version, to my knowledge. +% A feymr10 euro is ~7.3pt wide, while a normal cmtt10 char is ~5.25pt wide. +% Hmm. +% +% Also doesn't work in math. Do we need to do math with euro symbols? +% Hope not. +% +% +\def\euro{{\eurofont e}} +\def\eurofont{% + % We set the font at each command, rather than predefining it in + % \textfonts and the other font-switching commands, so that + % installations which never need the symbol don't have to have the + % font installed. + % + % There is only one designed size (nominal 10pt), so we always scale + % that to the current nominal size. + % + % By the way, simply using "at 1em" works for cmr10 and the like, but + % does not work for cmbx10 and other extended/shrunken fonts. + % + \def\eurosize{\csname\curfontsize nominalsize\endcsname}% + % + \ifx\curfontstyle\bfstylename + % bold: + \font\thiseurofont = \ifusingit{feybo10}{feybr10} at \eurosize + \else + % regular: + \font\thiseurofont = \ifusingit{feymo10}{feymr10} at \eurosize + \fi + \thiseurofont +} + +% Hacks for glyphs from the EC fonts similar to \euro. We don't +% use \let for the aliases, because sometimes we redefine the original +% macro, and the alias should reflect the redefinition. +\def\guillemetleft{{\ecfont \char"13}} +\def\guillemotleft{\guillemetleft} +\def\guillemetright{{\ecfont \char"14}} +\def\guillemotright{\guillemetright} +\def\guilsinglleft{{\ecfont \char"0E}} +\def\guilsinglright{{\ecfont \char"0F}} +\def\quotedblbase{{\ecfont \char"12}} +\def\quotesinglbase{{\ecfont \char"0D}} +% +\def\ecfont{% + % We can't distinguish serif/sanserif and italic/slanted, but this + % is used for crude hacks anyway (like adding French and German + % quotes to documents typeset with CM, where we lose kerning), so + % hopefully nobody will notice/care. + \edef\ecsize{\csname\curfontsize ecsize\endcsname}% + \edef\nominalsize{\csname\curfontsize nominalsize\endcsname}% + \ifx\curfontstyle\bfstylename + % bold: + \font\thisecfont = ecb\ifusingit{i}{x}\ecsize \space at \nominalsize + \else + % regular: + \font\thisecfont = ec\ifusingit{ti}{rm}\ecsize \space at \nominalsize + \fi + \thisecfont +} + +% @registeredsymbol - R in a circle. The font for the R should really +% be smaller yet, but lllsize is the best we can do for now. +% Adapted from the plain.tex definition of \copyright. +% +\def\registeredsymbol{% + $^{{\ooalign{\hfil\raise.07ex\hbox{\selectfonts\lllsize R}% + \hfil\crcr\Orb}}% + }$% +} + +% @textdegree - the normal degrees sign. +% +\def\textdegree{$^\circ$} + +% Laurent Siebenmann reports \Orb undefined with: +% Textures 1.7.7 (preloaded format=plain 93.10.14) (68K) 16 APR 2004 02:38 +% so we'll define it if necessary. +% +\ifx\Orb\undefined +\def\Orb{\mathhexbox20D} +\fi + +% Quotes. +\chardef\quotedblleft="5C +\chardef\quotedblright=`\" +\chardef\quoteleft=`\` +\chardef\quoteright=`\' + + +\message{page headings,} + +\newskip\titlepagetopglue \titlepagetopglue = 1.5in +\newskip\titlepagebottomglue \titlepagebottomglue = 2pc + +% First the title page. Must do @settitle before @titlepage. +\newif\ifseenauthor +\newif\iffinishedtitlepage + +% Do an implicit @contents or @shortcontents after @end titlepage if the +% user says @setcontentsaftertitlepage or @setshortcontentsaftertitlepage. +% +\newif\ifsetcontentsaftertitlepage + \let\setcontentsaftertitlepage = \setcontentsaftertitlepagetrue +\newif\ifsetshortcontentsaftertitlepage + \let\setshortcontentsaftertitlepage = \setshortcontentsaftertitlepagetrue + +\parseargdef\shorttitlepage{\begingroup\hbox{}\vskip 1.5in \chaprm \centerline{#1}% + \endgroup\page\hbox{}\page} + +\envdef\titlepage{% + % Open one extra group, as we want to close it in the middle of \Etitlepage. + \begingroup + \parindent=0pt \textfonts + % Leave some space at the very top of the page. + \vglue\titlepagetopglue + % No rule at page bottom unless we print one at the top with @title. + \finishedtitlepagetrue + % + % Most title ``pages'' are actually two pages long, with space + % at the top of the second. We don't want the ragged left on the second. + \let\oldpage = \page + \def\page{% + \iffinishedtitlepage\else + \finishtitlepage + \fi + \let\page = \oldpage + \page + \null + }% +} + +\def\Etitlepage{% + \iffinishedtitlepage\else + \finishtitlepage + \fi + % It is important to do the page break before ending the group, + % because the headline and footline are only empty inside the group. + % If we use the new definition of \page, we always get a blank page + % after the title page, which we certainly don't want. + \oldpage + \endgroup + % + % Need this before the \...aftertitlepage checks so that if they are + % in effect the toc pages will come out with page numbers. + \HEADINGSon + % + % If they want short, they certainly want long too. + \ifsetshortcontentsaftertitlepage + \shortcontents + \contents + \global\let\shortcontents = \relax + \global\let\contents = \relax + \fi + % + \ifsetcontentsaftertitlepage + \contents + \global\let\contents = \relax + \global\let\shortcontents = \relax + \fi +} + +\def\finishtitlepage{% + \vskip4pt \hrule height 2pt width \hsize + \vskip\titlepagebottomglue + \finishedtitlepagetrue +} + +%%% Macros to be used within @titlepage: + +\let\subtitlerm=\tenrm +\def\subtitlefont{\subtitlerm \normalbaselineskip = 13pt \normalbaselines} + +\def\authorfont{\authorrm \normalbaselineskip = 16pt \normalbaselines + \let\tt=\authortt} + +\parseargdef\title{% + \checkenv\titlepage + \leftline{\titlefonts\rm #1} + % print a rule at the page bottom also. + \finishedtitlepagefalse + \vskip4pt \hrule height 4pt width \hsize \vskip4pt +} + +\parseargdef\subtitle{% + \checkenv\titlepage + {\subtitlefont \rightline{#1}}% +} + +% @author should come last, but may come many times. +% It can also be used inside @quotation. +% +\parseargdef\author{% + \def\temp{\quotation}% + \ifx\thisenv\temp + \def\quotationauthor{#1}% printed in \Equotation. + \else + \checkenv\titlepage + \ifseenauthor\else \vskip 0pt plus 1filll \seenauthortrue \fi + {\authorfont \leftline{#1}}% + \fi +} + + +%%% Set up page headings and footings. + +\let\thispage=\folio + +\newtoks\evenheadline % headline on even pages +\newtoks\oddheadline % headline on odd pages +\newtoks\evenfootline % footline on even pages +\newtoks\oddfootline % footline on odd pages + +% Now make TeX use those variables +\headline={{\textfonts\rm \ifodd\pageno \the\oddheadline + \else \the\evenheadline \fi}} +\footline={{\textfonts\rm \ifodd\pageno \the\oddfootline + \else \the\evenfootline \fi}\HEADINGShook} +\let\HEADINGShook=\relax + +% Commands to set those variables. +% For example, this is what @headings on does +% @evenheading @thistitle|@thispage|@thischapter +% @oddheading @thischapter|@thispage|@thistitle +% @evenfooting @thisfile|| +% @oddfooting ||@thisfile + + +\def\evenheading{\parsearg\evenheadingxxx} +\def\evenheadingxxx #1{\evenheadingyyy #1\|\|\|\|\finish} +\def\evenheadingyyy #1\|#2\|#3\|#4\finish{% +\global\evenheadline={\rlap{\centerline{#2}}\line{#1\hfil#3}}} + +\def\oddheading{\parsearg\oddheadingxxx} +\def\oddheadingxxx #1{\oddheadingyyy #1\|\|\|\|\finish} +\def\oddheadingyyy #1\|#2\|#3\|#4\finish{% +\global\oddheadline={\rlap{\centerline{#2}}\line{#1\hfil#3}}} + +\parseargdef\everyheading{\oddheadingxxx{#1}\evenheadingxxx{#1}}% + +\def\evenfooting{\parsearg\evenfootingxxx} +\def\evenfootingxxx #1{\evenfootingyyy #1\|\|\|\|\finish} +\def\evenfootingyyy #1\|#2\|#3\|#4\finish{% +\global\evenfootline={\rlap{\centerline{#2}}\line{#1\hfil#3}}} + +\def\oddfooting{\parsearg\oddfootingxxx} +\def\oddfootingxxx #1{\oddfootingyyy #1\|\|\|\|\finish} +\def\oddfootingyyy #1\|#2\|#3\|#4\finish{% + \global\oddfootline = {\rlap{\centerline{#2}}\line{#1\hfil#3}}% + % + % Leave some space for the footline. Hopefully ok to assume + % @evenfooting will not be used by itself. + \global\advance\pageheight by -12pt + \global\advance\vsize by -12pt +} + +\parseargdef\everyfooting{\oddfootingxxx{#1}\evenfootingxxx{#1}} + +% @evenheadingmarks top \thischapter <- chapter at the top of a page +% @evenheadingmarks bottom \thischapter <- chapter at the bottom of a page +% +% The same set of arguments for: +% +% @oddheadingmarks +% @evenfootingmarks +% @oddfootingmarks +% @everyheadingmarks +% @everyfootingmarks + +\def\evenheadingmarks{\headingmarks{even}{heading}} +\def\oddheadingmarks{\headingmarks{odd}{heading}} +\def\evenfootingmarks{\headingmarks{even}{footing}} +\def\oddfootingmarks{\headingmarks{odd}{footing}} +\def\everyheadingmarks#1 {\headingmarks{even}{heading}{#1} + \headingmarks{odd}{heading}{#1} } +\def\everyfootingmarks#1 {\headingmarks{even}{footing}{#1} + \headingmarks{odd}{footing}{#1} } +% #1 = even/odd, #2 = heading/footing, #3 = top/bottom. +\def\headingmarks#1#2#3 {% + \expandafter\let\expandafter\temp \csname get#3headingmarks\endcsname + \global\expandafter\let\csname get#1#2marks\endcsname \temp +} + +\everyheadingmarks bottom +\everyfootingmarks bottom + +% @headings double turns headings on for double-sided printing. +% @headings single turns headings on for single-sided printing. +% @headings off turns them off. +% @headings on same as @headings double, retained for compatibility. +% @headings after turns on double-sided headings after this page. +% @headings doubleafter turns on double-sided headings after this page. +% @headings singleafter turns on single-sided headings after this page. +% By default, they are off at the start of a document, +% and turned `on' after @end titlepage. + +\def\headings #1 {\csname HEADINGS#1\endcsname} + +\def\HEADINGSoff{% +\global\evenheadline={\hfil} \global\evenfootline={\hfil} +\global\oddheadline={\hfil} \global\oddfootline={\hfil}} +\HEADINGSoff +% When we turn headings on, set the page number to 1. +% For double-sided printing, put current file name in lower left corner, +% chapter name on inside top of right hand pages, document +% title on inside top of left hand pages, and page numbers on outside top +% edge of all pages. +\def\HEADINGSdouble{% +\global\pageno=1 +\global\evenfootline={\hfil} +\global\oddfootline={\hfil} +\global\evenheadline={\line{\folio\hfil\thistitle}} +\global\oddheadline={\line{\thischapter\hfil\folio}} +\global\let\contentsalignmacro = \chapoddpage +} +\let\contentsalignmacro = \chappager + +% For single-sided printing, chapter title goes across top left of page, +% page number on top right. +\def\HEADINGSsingle{% +\global\pageno=1 +\global\evenfootline={\hfil} +\global\oddfootline={\hfil} +\global\evenheadline={\line{\thischapter\hfil\folio}} +\global\oddheadline={\line{\thischapter\hfil\folio}} +\global\let\contentsalignmacro = \chappager +} +\def\HEADINGSon{\HEADINGSdouble} + +\def\HEADINGSafter{\let\HEADINGShook=\HEADINGSdoublex} +\let\HEADINGSdoubleafter=\HEADINGSafter +\def\HEADINGSdoublex{% +\global\evenfootline={\hfil} +\global\oddfootline={\hfil} +\global\evenheadline={\line{\folio\hfil\thistitle}} +\global\oddheadline={\line{\thischapter\hfil\folio}} +\global\let\contentsalignmacro = \chapoddpage +} + +\def\HEADINGSsingleafter{\let\HEADINGShook=\HEADINGSsinglex} +\def\HEADINGSsinglex{% +\global\evenfootline={\hfil} +\global\oddfootline={\hfil} +\global\evenheadline={\line{\thischapter\hfil\folio}} +\global\oddheadline={\line{\thischapter\hfil\folio}} +\global\let\contentsalignmacro = \chappager +} + +% Subroutines used in generating headings +% This produces Day Month Year style of output. +% Only define if not already defined, in case a txi-??.tex file has set +% up a different format (e.g., txi-cs.tex does this). +\ifx\today\undefined +\def\today{% + \number\day\space + \ifcase\month + \or\putwordMJan\or\putwordMFeb\or\putwordMMar\or\putwordMApr + \or\putwordMMay\or\putwordMJun\or\putwordMJul\or\putwordMAug + \or\putwordMSep\or\putwordMOct\or\putwordMNov\or\putwordMDec + \fi + \space\number\year} +\fi + +% @settitle line... specifies the title of the document, for headings. +% It generates no output of its own. +\def\thistitle{\putwordNoTitle} +\def\settitle{\parsearg{\gdef\thistitle}} + + +\message{tables,} +% Tables -- @table, @ftable, @vtable, @item(x). + +% default indentation of table text +\newdimen\tableindent \tableindent=.8in +% default indentation of @itemize and @enumerate text +\newdimen\itemindent \itemindent=.3in +% margin between end of table item and start of table text. +\newdimen\itemmargin \itemmargin=.1in + +% used internally for \itemindent minus \itemmargin +\newdimen\itemmax + +% Note @table, @ftable, and @vtable define @item, @itemx, etc., with +% these defs. +% They also define \itemindex +% to index the item name in whatever manner is desired (perhaps none). + +\newif\ifitemxneedsnegativevskip + +\def\itemxpar{\par\ifitemxneedsnegativevskip\nobreak\vskip-\parskip\nobreak\fi} + +\def\internalBitem{\smallbreak \parsearg\itemzzz} +\def\internalBitemx{\itemxpar \parsearg\itemzzz} + +\def\itemzzz #1{\begingroup % + \advance\hsize by -\rightskip + \advance\hsize by -\tableindent + \setbox0=\hbox{\itemindicate{#1}}% + \itemindex{#1}% + \nobreak % This prevents a break before @itemx. + % + % If the item text does not fit in the space we have, put it on a line + % by itself, and do not allow a page break either before or after that + % line. We do not start a paragraph here because then if the next + % command is, e.g., @kindex, the whatsit would get put into the + % horizontal list on a line by itself, resulting in extra blank space. + \ifdim \wd0>\itemmax + % + % Make this a paragraph so we get the \parskip glue and wrapping, + % but leave it ragged-right. + \begingroup + \advance\leftskip by-\tableindent + \advance\hsize by\tableindent + \advance\rightskip by0pt plus1fil + \leavevmode\unhbox0\par + \endgroup + % + % We're going to be starting a paragraph, but we don't want the + % \parskip glue -- logically it's part of the @item we just started. + \nobreak \vskip-\parskip + % + % Stop a page break at the \parskip glue coming up. However, if + % what follows is an environment such as @example, there will be no + % \parskip glue; then the negative vskip we just inserted would + % cause the example and the item to crash together. So we use this + % bizarre value of 10001 as a signal to \aboveenvbreak to insert + % \parskip glue after all. Section titles are handled this way also. + % + \penalty 10001 + \endgroup + \itemxneedsnegativevskipfalse + \else + % The item text fits into the space. Start a paragraph, so that the + % following text (if any) will end up on the same line. + \noindent + % Do this with kerns and \unhbox so that if there is a footnote in + % the item text, it can migrate to the main vertical list and + % eventually be printed. + \nobreak\kern-\tableindent + \dimen0 = \itemmax \advance\dimen0 by \itemmargin \advance\dimen0 by -\wd0 + \unhbox0 + \nobreak\kern\dimen0 + \endgroup + \itemxneedsnegativevskiptrue + \fi +} + +\def\item{\errmessage{@item while not in a list environment}} +\def\itemx{\errmessage{@itemx while not in a list environment}} + +% @table, @ftable, @vtable. +\envdef\table{% + \let\itemindex\gobble + \tablecheck{table}% +} +\envdef\ftable{% + \def\itemindex ##1{\doind {fn}{\code{##1}}}% + \tablecheck{ftable}% +} +\envdef\vtable{% + \def\itemindex ##1{\doind {vr}{\code{##1}}}% + \tablecheck{vtable}% +} +\def\tablecheck#1{% + \ifnum \the\catcode`\^^M=\active + \endgroup + \errmessage{This command won't work in this context; perhaps the problem is + that we are \inenvironment\thisenv}% + \def\next{\doignore{#1}}% + \else + \let\next\tablex + \fi + \next +} +\def\tablex#1{% + \def\itemindicate{#1}% + \parsearg\tabley +} +\def\tabley#1{% + {% + \makevalueexpandable + \edef\temp{\noexpand\tablez #1\space\space\space}% + \expandafter + }\temp \endtablez +} +\def\tablez #1 #2 #3 #4\endtablez{% + \aboveenvbreak + \ifnum 0#1>0 \advance \leftskip by #1\mil \fi + \ifnum 0#2>0 \tableindent=#2\mil \fi + \ifnum 0#3>0 \advance \rightskip by #3\mil \fi + \itemmax=\tableindent + \advance \itemmax by -\itemmargin + \advance \leftskip by \tableindent + \exdentamount=\tableindent + \parindent = 0pt + \parskip = \smallskipamount + \ifdim \parskip=0pt \parskip=2pt \fi + \let\item = \internalBitem + \let\itemx = \internalBitemx +} +\def\Etable{\endgraf\afterenvbreak} +\let\Eftable\Etable +\let\Evtable\Etable +\let\Eitemize\Etable +\let\Eenumerate\Etable + +% This is the counter used by @enumerate, which is really @itemize + +\newcount \itemno + +\envdef\itemize{\parsearg\doitemize} + +\def\doitemize#1{% + \aboveenvbreak + \itemmax=\itemindent + \advance\itemmax by -\itemmargin + \advance\leftskip by \itemindent + \exdentamount=\itemindent + \parindent=0pt + \parskip=\smallskipamount + \ifdim\parskip=0pt \parskip=2pt \fi + \def\itemcontents{#1}% + % @itemize with no arg is equivalent to @itemize @bullet. + \ifx\itemcontents\empty\def\itemcontents{\bullet}\fi + \let\item=\itemizeitem +} + +% Definition of @item while inside @itemize and @enumerate. +% +\def\itemizeitem{% + \advance\itemno by 1 % for enumerations + {\let\par=\endgraf \smallbreak}% reasonable place to break + {% + % If the document has an @itemize directly after a section title, a + % \nobreak will be last on the list, and \sectionheading will have + % done a \vskip-\parskip. In that case, we don't want to zero + % parskip, or the item text will crash with the heading. On the + % other hand, when there is normal text preceding the item (as there + % usually is), we do want to zero parskip, or there would be too much + % space. In that case, we won't have a \nobreak before. At least + % that's the theory. + \ifnum\lastpenalty<10000 \parskip=0in \fi + \noindent + \hbox to 0pt{\hss \itemcontents \kern\itemmargin}% + \vadjust{\penalty 1200}}% not good to break after first line of item. + \flushcr +} + +% \splitoff TOKENS\endmark defines \first to be the first token in +% TOKENS, and \rest to be the remainder. +% +\def\splitoff#1#2\endmark{\def\first{#1}\def\rest{#2}}% + +% Allow an optional argument of an uppercase letter, lowercase letter, +% or number, to specify the first label in the enumerated list. No +% argument is the same as `1'. +% +\envparseargdef\enumerate{\enumeratey #1 \endenumeratey} +\def\enumeratey #1 #2\endenumeratey{% + % If we were given no argument, pretend we were given `1'. + \def\thearg{#1}% + \ifx\thearg\empty \def\thearg{1}\fi + % + % Detect if the argument is a single token. If so, it might be a + % letter. Otherwise, the only valid thing it can be is a number. + % (We will always have one token, because of the test we just made. + % This is a good thing, since \splitoff doesn't work given nothing at + % all -- the first parameter is undelimited.) + \expandafter\splitoff\thearg\endmark + \ifx\rest\empty + % Only one token in the argument. It could still be anything. + % A ``lowercase letter'' is one whose \lccode is nonzero. + % An ``uppercase letter'' is one whose \lccode is both nonzero, and + % not equal to itself. + % Otherwise, we assume it's a number. + % + % We need the \relax at the end of the \ifnum lines to stop TeX from + % continuing to look for a <number>. + % + \ifnum\lccode\expandafter`\thearg=0\relax + \numericenumerate % a number (we hope) + \else + % It's a letter. + \ifnum\lccode\expandafter`\thearg=\expandafter`\thearg\relax + \lowercaseenumerate % lowercase letter + \else + \uppercaseenumerate % uppercase letter + \fi + \fi + \else + % Multiple tokens in the argument. We hope it's a number. + \numericenumerate + \fi +} + +% An @enumerate whose labels are integers. The starting integer is +% given in \thearg. +% +\def\numericenumerate{% + \itemno = \thearg + \startenumeration{\the\itemno}% +} + +% The starting (lowercase) letter is in \thearg. +\def\lowercaseenumerate{% + \itemno = \expandafter`\thearg + \startenumeration{% + % Be sure we're not beyond the end of the alphabet. + \ifnum\itemno=0 + \errmessage{No more lowercase letters in @enumerate; get a bigger + alphabet}% + \fi + \char\lccode\itemno + }% +} + +% The starting (uppercase) letter is in \thearg. +\def\uppercaseenumerate{% + \itemno = \expandafter`\thearg + \startenumeration{% + % Be sure we're not beyond the end of the alphabet. + \ifnum\itemno=0 + \errmessage{No more uppercase letters in @enumerate; get a bigger + alphabet} + \fi + \char\uccode\itemno + }% +} + +% Call \doitemize, adding a period to the first argument and supplying the +% common last two arguments. Also subtract one from the initial value in +% \itemno, since @item increments \itemno. +% +\def\startenumeration#1{% + \advance\itemno by -1 + \doitemize{#1.}\flushcr +} + +% @alphaenumerate and @capsenumerate are abbreviations for giving an arg +% to @enumerate. +% +\def\alphaenumerate{\enumerate{a}} +\def\capsenumerate{\enumerate{A}} +\def\Ealphaenumerate{\Eenumerate} +\def\Ecapsenumerate{\Eenumerate} + + +% @multitable macros +% Amy Hendrickson, 8/18/94, 3/6/96 +% +% @multitable ... @end multitable will make as many columns as desired. +% Contents of each column will wrap at width given in preamble. Width +% can be specified either with sample text given in a template line, +% or in percent of \hsize, the current width of text on page. + +% Table can continue over pages but will only break between lines. + +% To make preamble: +% +% Either define widths of columns in terms of percent of \hsize: +% @multitable @columnfractions .25 .3 .45 +% @item ... +% +% Numbers following @columnfractions are the percent of the total +% current hsize to be used for each column. You may use as many +% columns as desired. + + +% Or use a template: +% @multitable {Column 1 template} {Column 2 template} {Column 3 template} +% @item ... +% using the widest term desired in each column. + +% Each new table line starts with @item, each subsequent new column +% starts with @tab. Empty columns may be produced by supplying @tab's +% with nothing between them for as many times as empty columns are needed, +% ie, @tab@tab@tab will produce two empty columns. + +% @item, @tab do not need to be on their own lines, but it will not hurt +% if they are. + +% Sample multitable: + +% @multitable {Column 1 template} {Column 2 template} {Column 3 template} +% @item first col stuff @tab second col stuff @tab third col +% @item +% first col stuff +% @tab +% second col stuff +% @tab +% third col +% @item first col stuff @tab second col stuff +% @tab Many paragraphs of text may be used in any column. +% +% They will wrap at the width determined by the template. +% @item@tab@tab This will be in third column. +% @end multitable + +% Default dimensions may be reset by user. +% @multitableparskip is vertical space between paragraphs in table. +% @multitableparindent is paragraph indent in table. +% @multitablecolmargin is horizontal space to be left between columns. +% @multitablelinespace is space to leave between table items, baseline +% to baseline. +% 0pt means it depends on current normal line spacing. +% +\newskip\multitableparskip +\newskip\multitableparindent +\newdimen\multitablecolspace +\newskip\multitablelinespace +\multitableparskip=0pt +\multitableparindent=6pt +\multitablecolspace=12pt +\multitablelinespace=0pt + +% Macros used to set up halign preamble: +% +\let\endsetuptable\relax +\def\xendsetuptable{\endsetuptable} +\let\columnfractions\relax +\def\xcolumnfractions{\columnfractions} +\newif\ifsetpercent + +% #1 is the @columnfraction, usually a decimal number like .5, but might +% be just 1. We just use it, whatever it is. +% +\def\pickupwholefraction#1 {% + \global\advance\colcount by 1 + \expandafter\xdef\csname col\the\colcount\endcsname{#1\hsize}% + \setuptable +} + +\newcount\colcount +\def\setuptable#1{% + \def\firstarg{#1}% + \ifx\firstarg\xendsetuptable + \let\go = \relax + \else + \ifx\firstarg\xcolumnfractions + \global\setpercenttrue + \else + \ifsetpercent + \let\go\pickupwholefraction + \else + \global\advance\colcount by 1 + \setbox0=\hbox{#1\unskip\space}% Add a normal word space as a + % separator; typically that is always in the input, anyway. + \expandafter\xdef\csname col\the\colcount\endcsname{\the\wd0}% + \fi + \fi + \ifx\go\pickupwholefraction + % Put the argument back for the \pickupwholefraction call, so + % we'll always have a period there to be parsed. + \def\go{\pickupwholefraction#1}% + \else + \let\go = \setuptable + \fi% + \fi + \go +} + +% multitable-only commands. +% +% @headitem starts a heading row, which we typeset in bold. +% Assignments have to be global since we are inside the implicit group +% of an alignment entry. Note that \everycr resets \everytab. +\def\headitem{\checkenv\multitable \crcr \global\everytab={\bf}\the\everytab}% +% +% A \tab used to include \hskip1sp. But then the space in a template +% line is not enough. That is bad. So let's go back to just `&' until +% we encounter the problem it was intended to solve again. +% --karl, nathan@acm.org, 20apr99. +\def\tab{\checkenv\multitable &\the\everytab}% + +% @multitable ... @end multitable definitions: +% +\newtoks\everytab % insert after every tab. +% +\envdef\multitable{% + \vskip\parskip + \startsavinginserts + % + % @item within a multitable starts a normal row. + % We use \def instead of \let so that if one of the multitable entries + % contains an @itemize, we don't choke on the \item (seen as \crcr aka + % \endtemplate) expanding \doitemize. + \def\item{\crcr}% + % + \tolerance=9500 + \hbadness=9500 + \setmultitablespacing + \parskip=\multitableparskip + \parindent=\multitableparindent + \overfullrule=0pt + \global\colcount=0 + % + \everycr = {% + \noalign{% + \global\everytab={}% + \global\colcount=0 % Reset the column counter. + % Check for saved footnotes, etc. + \checkinserts + % Keeps underfull box messages off when table breaks over pages. + %\filbreak + % Maybe so, but it also creates really weird page breaks when the + % table breaks over pages. Wouldn't \vfil be better? Wait until the + % problem manifests itself, so it can be fixed for real --karl. + }% + }% + % + \parsearg\domultitable +} +\def\domultitable#1{% + % To parse everything between @multitable and @item: + \setuptable#1 \endsetuptable + % + % This preamble sets up a generic column definition, which will + % be used as many times as user calls for columns. + % \vtop will set a single line and will also let text wrap and + % continue for many paragraphs if desired. + \halign\bgroup &% + \global\advance\colcount by 1 + \multistrut + \vtop{% + % Use the current \colcount to find the correct column width: + \hsize=\expandafter\csname col\the\colcount\endcsname + % + % In order to keep entries from bumping into each other + % we will add a \leftskip of \multitablecolspace to all columns after + % the first one. + % + % If a template has been used, we will add \multitablecolspace + % to the width of each template entry. + % + % If the user has set preamble in terms of percent of \hsize we will + % use that dimension as the width of the column, and the \leftskip + % will keep entries from bumping into each other. Table will start at + % left margin and final column will justify at right margin. + % + % Make sure we don't inherit \rightskip from the outer environment. + \rightskip=0pt + \ifnum\colcount=1 + % The first column will be indented with the surrounding text. + \advance\hsize by\leftskip + \else + \ifsetpercent \else + % If user has not set preamble in terms of percent of \hsize + % we will advance \hsize by \multitablecolspace. + \advance\hsize by \multitablecolspace + \fi + % In either case we will make \leftskip=\multitablecolspace: + \leftskip=\multitablecolspace + \fi + % Ignoring space at the beginning and end avoids an occasional spurious + % blank line, when TeX decides to break the line at the space before the + % box from the multistrut, so the strut ends up on a line by itself. + % For example: + % @multitable @columnfractions .11 .89 + % @item @code{#} + % @tab Legal holiday which is valid in major parts of the whole country. + % Is automatically provided with highlighting sequences respectively + % marking characters. + \noindent\ignorespaces##\unskip\multistrut + }\cr +} +\def\Emultitable{% + \crcr + \egroup % end the \halign + \global\setpercentfalse +} + +\def\setmultitablespacing{% + \def\multistrut{\strut}% just use the standard line spacing + % + % Compute \multitablelinespace (if not defined by user) for use in + % \multitableparskip calculation. We used define \multistrut based on + % this, but (ironically) that caused the spacing to be off. + % See bug-texinfo report from Werner Lemberg, 31 Oct 2004 12:52:20 +0100. +\ifdim\multitablelinespace=0pt +\setbox0=\vbox{X}\global\multitablelinespace=\the\baselineskip +\global\advance\multitablelinespace by-\ht0 +\fi +%% Test to see if parskip is larger than space between lines of +%% table. If not, do nothing. +%% If so, set to same dimension as multitablelinespace. +\ifdim\multitableparskip>\multitablelinespace +\global\multitableparskip=\multitablelinespace +\global\advance\multitableparskip-7pt %% to keep parskip somewhat smaller + %% than skip between lines in the table. +\fi% +\ifdim\multitableparskip=0pt +\global\multitableparskip=\multitablelinespace +\global\advance\multitableparskip-7pt %% to keep parskip somewhat smaller + %% than skip between lines in the table. +\fi} + + +\message{conditionals,} + +% @iftex, @ifnotdocbook, @ifnothtml, @ifnotinfo, @ifnotplaintext, +% @ifnotxml always succeed. They currently do nothing; we don't +% attempt to check whether the conditionals are properly nested. But we +% have to remember that they are conditionals, so that @end doesn't +% attempt to close an environment group. +% +\def\makecond#1{% + \expandafter\let\csname #1\endcsname = \relax + \expandafter\let\csname iscond.#1\endcsname = 1 +} +\makecond{iftex} +\makecond{ifnotdocbook} +\makecond{ifnothtml} +\makecond{ifnotinfo} +\makecond{ifnotplaintext} +\makecond{ifnotxml} + +% Ignore @ignore, @ifhtml, @ifinfo, and the like. +% +\def\direntry{\doignore{direntry}} +\def\documentdescription{\doignore{documentdescription}} +\def\docbook{\doignore{docbook}} +\def\html{\doignore{html}} +\def\ifdocbook{\doignore{ifdocbook}} +\def\ifhtml{\doignore{ifhtml}} +\def\ifinfo{\doignore{ifinfo}} +\def\ifnottex{\doignore{ifnottex}} +\def\ifplaintext{\doignore{ifplaintext}} +\def\ifxml{\doignore{ifxml}} +\def\ignore{\doignore{ignore}} +\def\menu{\doignore{menu}} +\def\xml{\doignore{xml}} + +% Ignore text until a line `@end #1', keeping track of nested conditionals. +% +% A count to remember the depth of nesting. +\newcount\doignorecount + +\def\doignore#1{\begingroup + % Scan in ``verbatim'' mode: + \obeylines + \catcode`\@ = \other + \catcode`\{ = \other + \catcode`\} = \other + % + % Make sure that spaces turn into tokens that match what \doignoretext wants. + \spaceisspace + % + % Count number of #1's that we've seen. + \doignorecount = 0 + % + % Swallow text until we reach the matching `@end #1'. + \dodoignore{#1}% +} + +{ \catcode`_=11 % We want to use \_STOP_ which cannot appear in texinfo source. + \obeylines % + % + \gdef\dodoignore#1{% + % #1 contains the command name as a string, e.g., `ifinfo'. + % + % Define a command to find the next `@end #1'. + \long\def\doignoretext##1^^M@end #1{% + \doignoretextyyy##1^^M@#1\_STOP_}% + % + % And this command to find another #1 command, at the beginning of a + % line. (Otherwise, we would consider a line `@c @ifset', for + % example, to count as an @ifset for nesting.) + \long\def\doignoretextyyy##1^^M@#1##2\_STOP_{\doignoreyyy{##2}\_STOP_}% + % + % And now expand that command. + \doignoretext ^^M% + }% +} + +\def\doignoreyyy#1{% + \def\temp{#1}% + \ifx\temp\empty % Nothing found. + \let\next\doignoretextzzz + \else % Found a nested condition, ... + \advance\doignorecount by 1 + \let\next\doignoretextyyy % ..., look for another. + % If we're here, #1 ends with ^^M\ifinfo (for example). + \fi + \next #1% the token \_STOP_ is present just after this macro. +} + +% We have to swallow the remaining "\_STOP_". +% +\def\doignoretextzzz#1{% + \ifnum\doignorecount = 0 % We have just found the outermost @end. + \let\next\enddoignore + \else % Still inside a nested condition. + \advance\doignorecount by -1 + \let\next\doignoretext % Look for the next @end. + \fi + \next +} + +% Finish off ignored text. +{ \obeylines% + % Ignore anything after the last `@end #1'; this matters in verbatim + % environments, where otherwise the newline after an ignored conditional + % would result in a blank line in the output. + \gdef\enddoignore#1^^M{\endgroup\ignorespaces}% +} + + +% @set VAR sets the variable VAR to an empty value. +% @set VAR REST-OF-LINE sets VAR to the value REST-OF-LINE. +% +% Since we want to separate VAR from REST-OF-LINE (which might be +% empty), we can't just use \parsearg; we have to insert a space of our +% own to delimit the rest of the line, and then take it out again if we +% didn't need it. +% We rely on the fact that \parsearg sets \catcode`\ =10. +% +\parseargdef\set{\setyyy#1 \endsetyyy} +\def\setyyy#1 #2\endsetyyy{% + {% + \makevalueexpandable + \def\temp{#2}% + \edef\next{\gdef\makecsname{SET#1}}% + \ifx\temp\empty + \next{}% + \else + \setzzz#2\endsetzzz + \fi + }% +} +% Remove the trailing space \setxxx inserted. +\def\setzzz#1 \endsetzzz{\next{#1}} + +% @clear VAR clears (i.e., unsets) the variable VAR. +% +\parseargdef\clear{% + {% + \makevalueexpandable + \global\expandafter\let\csname SET#1\endcsname=\relax + }% +} + +% @value{foo} gets the text saved in variable foo. +\def\value{\begingroup\makevalueexpandable\valuexxx} +\def\valuexxx#1{\expandablevalue{#1}\endgroup} +{ + \catcode`\- = \active \catcode`\_ = \active + % + \gdef\makevalueexpandable{% + \let\value = \expandablevalue + % We don't want these characters active, ... + \catcode`\-=\other \catcode`\_=\other + % ..., but we might end up with active ones in the argument if + % we're called from @code, as @code{@value{foo-bar_}}, though. + % So \let them to their normal equivalents. + \let-\realdash \let_\normalunderscore + } +} + +% We have this subroutine so that we can handle at least some @value's +% properly in indexes (we call \makevalueexpandable in \indexdummies). +% The command has to be fully expandable (if the variable is set), since +% the result winds up in the index file. This means that if the +% variable's value contains other Texinfo commands, it's almost certain +% it will fail (although perhaps we could fix that with sufficient work +% to do a one-level expansion on the result, instead of complete). +% +\def\expandablevalue#1{% + \expandafter\ifx\csname SET#1\endcsname\relax + {[No value for ``#1'']}% + \message{Variable `#1', used in @value, is not set.}% + \else + \csname SET#1\endcsname + \fi +} + +% @ifset VAR ... @end ifset reads the `...' iff VAR has been defined +% with @set. +% +% To get special treatment of `@end ifset,' call \makeond and the redefine. +% +\makecond{ifset} +\def\ifset{\parsearg{\doifset{\let\next=\ifsetfail}}} +\def\doifset#1#2{% + {% + \makevalueexpandable + \let\next=\empty + \expandafter\ifx\csname SET#2\endcsname\relax + #1% If not set, redefine \next. + \fi + \expandafter + }\next +} +\def\ifsetfail{\doignore{ifset}} + +% @ifclear VAR ... @end ifclear reads the `...' iff VAR has never been +% defined with @set, or has been undefined with @clear. +% +% The `\else' inside the `\doifset' parameter is a trick to reuse the +% above code: if the variable is not set, do nothing, if it is set, +% then redefine \next to \ifclearfail. +% +\makecond{ifclear} +\def\ifclear{\parsearg{\doifset{\else \let\next=\ifclearfail}}} +\def\ifclearfail{\doignore{ifclear}} + +% @dircategory CATEGORY -- specify a category of the dir file +% which this file should belong to. Ignore this in TeX. +\let\dircategory=\comment + +% @defininfoenclose. +\let\definfoenclose=\comment + + +\message{indexing,} +% Index generation facilities + +% Define \newwrite to be identical to plain tex's \newwrite +% except not \outer, so it can be used within macros and \if's. +\edef\newwrite{\makecsname{ptexnewwrite}} + +% \newindex {foo} defines an index named foo. +% It automatically defines \fooindex such that +% \fooindex ...rest of line... puts an entry in the index foo. +% It also defines \fooindfile to be the number of the output channel for +% the file that accumulates this index. The file's extension is foo. +% The name of an index should be no more than 2 characters long +% for the sake of vms. +% +\def\newindex#1{% + \iflinks + \expandafter\newwrite \csname#1indfile\endcsname + \openout \csname#1indfile\endcsname \jobname.#1 % Open the file + \fi + \expandafter\xdef\csname#1index\endcsname{% % Define @#1index + \noexpand\doindex{#1}} +} + +% @defindex foo == \newindex{foo} +% +\def\defindex{\parsearg\newindex} + +% Define @defcodeindex, like @defindex except put all entries in @code. +% +\def\defcodeindex{\parsearg\newcodeindex} +% +\def\newcodeindex#1{% + \iflinks + \expandafter\newwrite \csname#1indfile\endcsname + \openout \csname#1indfile\endcsname \jobname.#1 + \fi + \expandafter\xdef\csname#1index\endcsname{% + \noexpand\docodeindex{#1}}% +} + + +% @synindex foo bar makes index foo feed into index bar. +% Do this instead of @defindex foo if you don't want it as a separate index. +% +% @syncodeindex foo bar similar, but put all entries made for index foo +% inside @code. +% +\def\synindex#1 #2 {\dosynindex\doindex{#1}{#2}} +\def\syncodeindex#1 #2 {\dosynindex\docodeindex{#1}{#2}} + +% #1 is \doindex or \docodeindex, #2 the index getting redefined (foo), +% #3 the target index (bar). +\def\dosynindex#1#2#3{% + % Only do \closeout if we haven't already done it, else we'll end up + % closing the target index. + \expandafter \ifx\csname donesynindex#2\endcsname \undefined + % The \closeout helps reduce unnecessary open files; the limit on the + % Acorn RISC OS is a mere 16 files. + \expandafter\closeout\csname#2indfile\endcsname + \expandafter\let\csname\donesynindex#2\endcsname = 1 + \fi + % redefine \fooindfile: + \expandafter\let\expandafter\temp\expandafter=\csname#3indfile\endcsname + \expandafter\let\csname#2indfile\endcsname=\temp + % redefine \fooindex: + \expandafter\xdef\csname#2index\endcsname{\noexpand#1{#3}}% +} + +% Define \doindex, the driver for all \fooindex macros. +% Argument #1 is generated by the calling \fooindex macro, +% and it is "foo", the name of the index. + +% \doindex just uses \parsearg; it calls \doind for the actual work. +% This is because \doind is more useful to call from other macros. + +% There is also \dosubind {index}{topic}{subtopic} +% which makes an entry in a two-level index such as the operation index. + +\def\doindex#1{\edef\indexname{#1}\parsearg\singleindexer} +\def\singleindexer #1{\doind{\indexname}{#1}} + +% like the previous two, but they put @code around the argument. +\def\docodeindex#1{\edef\indexname{#1}\parsearg\singlecodeindexer} +\def\singlecodeindexer #1{\doind{\indexname}{\code{#1}}} + +% Take care of Texinfo commands that can appear in an index entry. +% Since there are some commands we want to expand, and others we don't, +% we have to laboriously prevent expansion for those that we don't. +% +\def\indexdummies{% + \escapechar = `\\ % use backslash in output files. + \def\@{@}% change to @@ when we switch to @ as escape char in index files. + \def\ {\realbackslash\space }% + % + % Need these in case \tex is in effect and \{ is a \delimiter again. + % But can't use \lbracecmd and \rbracecmd because texindex assumes + % braces and backslashes are used only as delimiters. + \let\{ = \mylbrace + \let\} = \myrbrace + % + % I don't entirely understand this, but when an index entry is + % generated from a macro call, the \endinput which \scanmacro inserts + % causes processing to be prematurely terminated. This is, + % apparently, because \indexsorttmp is fully expanded, and \endinput + % is an expandable command. The redefinition below makes \endinput + % disappear altogether for that purpose -- although logging shows that + % processing continues to some further point. On the other hand, it + % seems \endinput does not hurt in the printed index arg, since that + % is still getting written without apparent harm. + % + % Sample source (mac-idx3.tex, reported by Graham Percival to + % help-texinfo, 22may06): + % @macro funindex {WORD} + % @findex xyz + % @end macro + % ... + % @funindex commtest + % + % The above is not enough to reproduce the bug, but it gives the flavor. + % + % Sample whatsit resulting: + % .@write3{\entry{xyz}{@folio }{@code {xyz@endinput }}} + % + % So: + \let\endinput = \empty + % + % Do the redefinitions. + \commondummies +} + +% For the aux and toc files, @ is the escape character. So we want to +% redefine everything using @ as the escape character (instead of +% \realbackslash, still used for index files). When everything uses @, +% this will be simpler. +% +\def\atdummies{% + \def\@{@@}% + \def\ {@ }% + \let\{ = \lbraceatcmd + \let\} = \rbraceatcmd + % + % Do the redefinitions. + \commondummies + \otherbackslash +} + +% Called from \indexdummies and \atdummies. +% +\def\commondummies{% + % + % \definedummyword defines \#1 as \string\#1\space, thus effectively + % preventing its expansion. This is used only for control% words, + % not control letters, because the \space would be incorrect for + % control characters, but is needed to separate the control word + % from whatever follows. + % + % For control letters, we have \definedummyletter, which omits the + % space. + % + % These can be used both for control words that take an argument and + % those that do not. If it is followed by {arg} in the input, then + % that will dutifully get written to the index (or wherever). + % + \def\definedummyword ##1{\def##1{\string##1\space}}% + \def\definedummyletter##1{\def##1{\string##1}}% + \let\definedummyaccent\definedummyletter + % + \commondummiesnofonts + % + \definedummyletter\_% + % + % Non-English letters. + \definedummyword\AA + \definedummyword\AE + \definedummyword\L + \definedummyword\OE + \definedummyword\O + \definedummyword\aa + \definedummyword\ae + \definedummyword\l + \definedummyword\oe + \definedummyword\o + \definedummyword\ss + \definedummyword\exclamdown + \definedummyword\questiondown + \definedummyword\ordf + \definedummyword\ordm + % + % Although these internal commands shouldn't show up, sometimes they do. + \definedummyword\bf + \definedummyword\gtr + \definedummyword\hat + \definedummyword\less + \definedummyword\sf + \definedummyword\sl + \definedummyword\tclose + \definedummyword\tt + % + \definedummyword\LaTeX + \definedummyword\TeX + % + % Assorted special characters. + \definedummyword\bullet + \definedummyword\comma + \definedummyword\copyright + \definedummyword\registeredsymbol + \definedummyword\dots + \definedummyword\enddots + \definedummyword\equiv + \definedummyword\error + \definedummyword\euro + \definedummyword\guillemetleft + \definedummyword\guillemetright + \definedummyword\guilsinglleft + \definedummyword\guilsinglright + \definedummyword\expansion + \definedummyword\minus + \definedummyword\pounds + \definedummyword\point + \definedummyword\print + \definedummyword\quotedblbase + \definedummyword\quotedblleft + \definedummyword\quotedblright + \definedummyword\quoteleft + \definedummyword\quoteright + \definedummyword\quotesinglbase + \definedummyword\result + \definedummyword\textdegree + % + % We want to disable all macros so that they are not expanded by \write. + \macrolist + % + \normalturnoffactive + % + % Handle some cases of @value -- where it does not contain any + % (non-fully-expandable) commands. + \makevalueexpandable +} + +% \commondummiesnofonts: common to \commondummies and \indexnofonts. +% +\def\commondummiesnofonts{% + % Control letters and accents. + \definedummyletter\!% + \definedummyaccent\"% + \definedummyaccent\'% + \definedummyletter\*% + \definedummyaccent\,% + \definedummyletter\.% + \definedummyletter\/% + \definedummyletter\:% + \definedummyaccent\=% + \definedummyletter\?% + \definedummyaccent\^% + \definedummyaccent\`% + \definedummyaccent\~% + \definedummyword\u + \definedummyword\v + \definedummyword\H + \definedummyword\dotaccent + \definedummyword\ringaccent + \definedummyword\tieaccent + \definedummyword\ubaraccent + \definedummyword\udotaccent + \definedummyword\dotless + % + % Texinfo font commands. + \definedummyword\b + \definedummyword\i + \definedummyword\r + \definedummyword\sc + \definedummyword\t + % + % Commands that take arguments. + \definedummyword\acronym + \definedummyword\cite + \definedummyword\code + \definedummyword\command + \definedummyword\dfn + \definedummyword\emph + \definedummyword\env + \definedummyword\file + \definedummyword\kbd + \definedummyword\key + \definedummyword\math + \definedummyword\option + \definedummyword\pxref + \definedummyword\ref + \definedummyword\samp + \definedummyword\strong + \definedummyword\tie + \definedummyword\uref + \definedummyword\url + \definedummyword\var + \definedummyword\verb + \definedummyword\w + \definedummyword\xref +} + +% \indexnofonts is used when outputting the strings to sort the index +% by, and when constructing control sequence names. It eliminates all +% control sequences and just writes whatever the best ASCII sort string +% would be for a given command (usually its argument). +% +\def\indexnofonts{% + % Accent commands should become @asis. + \def\definedummyaccent##1{\let##1\asis}% + % We can just ignore other control letters. + \def\definedummyletter##1{\let##1\empty}% + % Hopefully, all control words can become @asis. + \let\definedummyword\definedummyaccent + % + \commondummiesnofonts + % + % Don't no-op \tt, since it isn't a user-level command + % and is used in the definitions of the active chars like <, >, |, etc. + % Likewise with the other plain tex font commands. + %\let\tt=\asis + % + \def\ { }% + \def\@{@}% + % how to handle braces? + \def\_{\normalunderscore}% + % + % Non-English letters. + \def\AA{AA}% + \def\AE{AE}% + \def\L{L}% + \def\OE{OE}% + \def\O{O}% + \def\aa{aa}% + \def\ae{ae}% + \def\l{l}% + \def\oe{oe}% + \def\o{o}% + \def\ss{ss}% + \def\exclamdown{!}% + \def\questiondown{?}% + \def\ordf{a}% + \def\ordm{o}% + % + \def\LaTeX{LaTeX}% + \def\TeX{TeX}% + % + % Assorted special characters. + % (The following {} will end up in the sort string, but that's ok.) + \def\bullet{bullet}% + \def\comma{,}% + \def\copyright{copyright}% + \def\registeredsymbol{R}% + \def\dots{...}% + \def\enddots{...}% + \def\equiv{==}% + \def\error{error}% + \def\euro{euro}% + \def\guillemetleft{<<}% + \def\guillemetright{>>}% + \def\guilsinglleft{<}% + \def\guilsinglright{>}% + \def\expansion{==>}% + \def\minus{-}% + \def\pounds{pounds}% + \def\point{.}% + \def\print{-|}% + \def\quotedblbase{"}% + \def\quotedblleft{"}% + \def\quotedblright{"}% + \def\quoteleft{`}% + \def\quoteright{'}% + \def\quotesinglbase{,}% + \def\result{=>}% + \def\textdegree{degrees}% + % + % We need to get rid of all macros, leaving only the arguments (if present). + % Of course this is not nearly correct, but it is the best we can do for now. + % makeinfo does not expand macros in the argument to @deffn, which ends up + % writing an index entry, and texindex isn't prepared for an index sort entry + % that starts with \. + % + % Since macro invocations are followed by braces, we can just redefine them + % to take a single TeX argument. The case of a macro invocation that + % goes to end-of-line is not handled. + % + \macrolist +} + +\let\indexbackslash=0 %overridden during \printindex. +\let\SETmarginindex=\relax % put index entries in margin (undocumented)? + +% Most index entries go through here, but \dosubind is the general case. +% #1 is the index name, #2 is the entry text. +\def\doind#1#2{\dosubind{#1}{#2}{}} + +% Workhorse for all \fooindexes. +% #1 is name of index, #2 is stuff to put there, #3 is subentry -- +% empty if called from \doind, as we usually are (the main exception +% is with most defuns, which call us directly). +% +\def\dosubind#1#2#3{% + \iflinks + {% + % Store the main index entry text (including the third arg). + \toks0 = {#2}% + % If third arg is present, precede it with a space. + \def\thirdarg{#3}% + \ifx\thirdarg\empty \else + \toks0 = \expandafter{\the\toks0 \space #3}% + \fi + % + \edef\writeto{\csname#1indfile\endcsname}% + % + \safewhatsit\dosubindwrite + }% + \fi +} + +% Write the entry in \toks0 to the index file: +% +\def\dosubindwrite{% + % Put the index entry in the margin if desired. + \ifx\SETmarginindex\relax\else + \insert\margin{\hbox{\vrule height8pt depth3pt width0pt \the\toks0}}% + \fi + % + % Remember, we are within a group. + \indexdummies % Must do this here, since \bf, etc expand at this stage + \def\backslashcurfont{\indexbackslash}% \indexbackslash isn't defined now + % so it will be output as is; and it will print as backslash. + % + % Process the index entry with all font commands turned off, to + % get the string to sort by. + {\indexnofonts + \edef\temp{\the\toks0}% need full expansion + \xdef\indexsorttmp{\temp}% + }% + % + % Set up the complete index entry, with both the sort key and + % the original text, including any font commands. We write + % three arguments to \entry to the .?? file (four in the + % subentry case), texindex reduces to two when writing the .??s + % sorted result. + \edef\temp{% + \write\writeto{% + \string\entry{\indexsorttmp}{\noexpand\folio}{\the\toks0}}% + }% + \temp +} + +% Take care of unwanted page breaks/skips around a whatsit: +% +% If a skip is the last thing on the list now, preserve it +% by backing up by \lastskip, doing the \write, then inserting +% the skip again. Otherwise, the whatsit generated by the +% \write or \pdfdest will make \lastskip zero. The result is that +% sequences like this: +% @end defun +% @tindex whatever +% @defun ... +% will have extra space inserted, because the \medbreak in the +% start of the @defun won't see the skip inserted by the @end of +% the previous defun. +% +% But don't do any of this if we're not in vertical mode. We +% don't want to do a \vskip and prematurely end a paragraph. +% +% Avoid page breaks due to these extra skips, too. +% +% But wait, there is a catch there: +% We'll have to check whether \lastskip is zero skip. \ifdim is not +% sufficient for this purpose, as it ignores stretch and shrink parts +% of the skip. The only way seems to be to check the textual +% representation of the skip. +% +% The following is almost like \def\zeroskipmacro{0.0pt} except that +% the ``p'' and ``t'' characters have catcode \other, not 11 (letter). +% +\edef\zeroskipmacro{\expandafter\the\csname z@skip\endcsname} +% +\newskip\whatsitskip +\newcount\whatsitpenalty +% +% ..., ready, GO: +% +\def\safewhatsit#1{% +\ifhmode + #1% +\else + % \lastskip and \lastpenalty cannot both be nonzero simultaneously. + \whatsitskip = \lastskip + \edef\lastskipmacro{\the\lastskip}% + \whatsitpenalty = \lastpenalty + % + % If \lastskip is nonzero, that means the last item was a + % skip. And since a skip is discardable, that means this + % -\whatsitskip glue we're inserting is preceded by a + % non-discardable item, therefore it is not a potential + % breakpoint, therefore no \nobreak needed. + \ifx\lastskipmacro\zeroskipmacro + \else + \vskip-\whatsitskip + \fi + % + #1% + % + \ifx\lastskipmacro\zeroskipmacro + % If \lastskip was zero, perhaps the last item was a penalty, and + % perhaps it was >=10000, e.g., a \nobreak. In that case, we want + % to re-insert the same penalty (values >10000 are used for various + % signals); since we just inserted a non-discardable item, any + % following glue (such as a \parskip) would be a breakpoint. For example: + % + % @deffn deffn-whatever + % @vindex index-whatever + % Description. + % would allow a break between the index-whatever whatsit + % and the "Description." paragraph. + \ifnum\whatsitpenalty>9999 \penalty\whatsitpenalty \fi + \else + % On the other hand, if we had a nonzero \lastskip, + % this make-up glue would be preceded by a non-discardable item + % (the whatsit from the \write), so we must insert a \nobreak. + \nobreak\vskip\whatsitskip + \fi +\fi +} + +% The index entry written in the file actually looks like +% \entry {sortstring}{page}{topic} +% or +% \entry {sortstring}{page}{topic}{subtopic} +% The texindex program reads in these files and writes files +% containing these kinds of lines: +% \initial {c} +% before the first topic whose initial is c +% \entry {topic}{pagelist} +% for a topic that is used without subtopics +% \primary {topic} +% for the beginning of a topic that is used with subtopics +% \secondary {subtopic}{pagelist} +% for each subtopic. + +% Define the user-accessible indexing commands +% @findex, @vindex, @kindex, @cindex. + +\def\findex {\fnindex} +\def\kindex {\kyindex} +\def\cindex {\cpindex} +\def\vindex {\vrindex} +\def\tindex {\tpindex} +\def\pindex {\pgindex} + +\def\cindexsub {\begingroup\obeylines\cindexsub} +{\obeylines % +\gdef\cindexsub "#1" #2^^M{\endgroup % +\dosubind{cp}{#2}{#1}}} + +% Define the macros used in formatting output of the sorted index material. + +% @printindex causes a particular index (the ??s file) to get printed. +% It does not print any chapter heading (usually an @unnumbered). +% +\parseargdef\printindex{\begingroup + \dobreak \chapheadingskip{10000}% + % + \smallfonts \rm + \tolerance = 9500 + \plainfrenchspacing + \everypar = {}% don't want the \kern\-parindent from indentation suppression. + % + % See if the index file exists and is nonempty. + % Change catcode of @ here so that if the index file contains + % \initial {@} + % as its first line, TeX doesn't complain about mismatched braces + % (because it thinks @} is a control sequence). + \catcode`\@ = 11 + \openin 1 \jobname.#1s + \ifeof 1 + % \enddoublecolumns gets confused if there is no text in the index, + % and it loses the chapter title and the aux file entries for the + % index. The easiest way to prevent this problem is to make sure + % there is some text. + \putwordIndexNonexistent + \else + % + % If the index file exists but is empty, then \openin leaves \ifeof + % false. We have to make TeX try to read something from the file, so + % it can discover if there is anything in it. + \read 1 to \temp + \ifeof 1 + \putwordIndexIsEmpty + \else + % Index files are almost Texinfo source, but we use \ as the escape + % character. It would be better to use @, but that's too big a change + % to make right now. + \def\indexbackslash{\backslashcurfont}% + \catcode`\\ = 0 + \escapechar = `\\ + \begindoublecolumns + \input \jobname.#1s + \enddoublecolumns + \fi + \fi + \closein 1 +\endgroup} + +% These macros are used by the sorted index file itself. +% Change them to control the appearance of the index. + +\def\initial#1{{% + % Some minor font changes for the special characters. + \let\tentt=\sectt \let\tt=\sectt \let\sf=\sectt + % + % Remove any glue we may have, we'll be inserting our own. + \removelastskip + % + % We like breaks before the index initials, so insert a bonus. + \nobreak + \vskip 0pt plus 3\baselineskip + \penalty 0 + \vskip 0pt plus -3\baselineskip + % + % Typeset the initial. Making this add up to a whole number of + % baselineskips increases the chance of the dots lining up from column + % to column. It still won't often be perfect, because of the stretch + % we need before each entry, but it's better. + % + % No shrink because it confuses \balancecolumns. + \vskip 1.67\baselineskip plus .5\baselineskip + \leftline{\secbf #1}% + % Do our best not to break after the initial. + \nobreak + \vskip .33\baselineskip plus .1\baselineskip +}} + +% \entry typesets a paragraph consisting of the text (#1), dot leaders, and +% then page number (#2) flushed to the right margin. It is used for index +% and table of contents entries. The paragraph is indented by \leftskip. +% +% A straightforward implementation would start like this: +% \def\entry#1#2{... +% But this frozes the catcodes in the argument, and can cause problems to +% @code, which sets - active. This problem was fixed by a kludge--- +% ``-'' was active throughout whole index, but this isn't really right. +% +% The right solution is to prevent \entry from swallowing the whole text. +% --kasal, 21nov03 +\def\entry{% + \begingroup + % + % Start a new paragraph if necessary, so our assignments below can't + % affect previous text. + \par + % + % Do not fill out the last line with white space. + \parfillskip = 0in + % + % No extra space above this paragraph. + \parskip = 0in + % + % Do not prefer a separate line ending with a hyphen to fewer lines. + \finalhyphendemerits = 0 + % + % \hangindent is only relevant when the entry text and page number + % don't both fit on one line. In that case, bob suggests starting the + % dots pretty far over on the line. Unfortunately, a large + % indentation looks wrong when the entry text itself is broken across + % lines. So we use a small indentation and put up with long leaders. + % + % \hangafter is reset to 1 (which is the value we want) at the start + % of each paragraph, so we need not do anything with that. + \hangindent = 2em + % + % When the entry text needs to be broken, just fill out the first line + % with blank space. + \rightskip = 0pt plus1fil + % + % A bit of stretch before each entry for the benefit of balancing + % columns. + \vskip 0pt plus1pt + % + % Swallow the left brace of the text (first parameter): + \afterassignment\doentry + \let\temp = +} +\def\doentry{% + \bgroup % Instead of the swallowed brace. + \noindent + \aftergroup\finishentry + % And now comes the text of the entry. +} +\def\finishentry#1{% + % #1 is the page number. + % + % The following is kludged to not output a line of dots in the index if + % there are no page numbers. The next person who breaks this will be + % cursed by a Unix daemon. + \setbox\boxA = \hbox{#1}% + \ifdim\wd\boxA = 0pt + \ % + \else + % + % If we must, put the page number on a line of its own, and fill out + % this line with blank space. (The \hfil is overwhelmed with the + % fill leaders glue in \indexdotfill if the page number does fit.) + \hfil\penalty50 + \null\nobreak\indexdotfill % Have leaders before the page number. + % + % The `\ ' here is removed by the implicit \unskip that TeX does as + % part of (the primitive) \par. Without it, a spurious underfull + % \hbox ensues. + \ifpdf + \pdfgettoks#1.% + \ \the\toksA + \else + \ #1% + \fi + \fi + \par + \endgroup +} + +% Like plain.tex's \dotfill, except uses up at least 1 em. +\def\indexdotfill{\cleaders + \hbox{$\mathsurround=0pt \mkern1.5mu.\mkern1.5mu$}\hskip 1em plus 1fill} + +\def\primary #1{\line{#1\hfil}} + +\newskip\secondaryindent \secondaryindent=0.5cm +\def\secondary#1#2{{% + \parfillskip=0in + \parskip=0in + \hangindent=1in + \hangafter=1 + \noindent\hskip\secondaryindent\hbox{#1}\indexdotfill + \ifpdf + \pdfgettoks#2.\ \the\toksA % The page number ends the paragraph. + \else + #2 + \fi + \par +}} + +% Define two-column mode, which we use to typeset indexes. +% Adapted from the TeXbook, page 416, which is to say, +% the manmac.tex format used to print the TeXbook itself. +\catcode`\@=11 + +\newbox\partialpage +\newdimen\doublecolumnhsize + +\def\begindoublecolumns{\begingroup % ended by \enddoublecolumns + % Grab any single-column material above us. + \output = {% + % + % Here is a possibility not foreseen in manmac: if we accumulate a + % whole lot of material, we might end up calling this \output + % routine twice in a row (see the doublecol-lose test, which is + % essentially a couple of indexes with @setchapternewpage off). In + % that case we just ship out what is in \partialpage with the normal + % output routine. Generally, \partialpage will be empty when this + % runs and this will be a no-op. See the indexspread.tex test case. + \ifvoid\partialpage \else + \onepageout{\pagecontents\partialpage}% + \fi + % + \global\setbox\partialpage = \vbox{% + % Unvbox the main output page. + \unvbox\PAGE + \kern-\topskip \kern\baselineskip + }% + }% + \eject % run that output routine to set \partialpage + % + % Use the double-column output routine for subsequent pages. + \output = {\doublecolumnout}% + % + % Change the page size parameters. We could do this once outside this + % routine, in each of @smallbook, @afourpaper, and the default 8.5x11 + % format, but then we repeat the same computation. Repeating a couple + % of assignments once per index is clearly meaningless for the + % execution time, so we may as well do it in one place. + % + % First we halve the line length, less a little for the gutter between + % the columns. We compute the gutter based on the line length, so it + % changes automatically with the paper format. The magic constant + % below is chosen so that the gutter has the same value (well, +-<1pt) + % as it did when we hard-coded it. + % + % We put the result in a separate register, \doublecolumhsize, so we + % can restore it in \pagesofar, after \hsize itself has (potentially) + % been clobbered. + % + \doublecolumnhsize = \hsize + \advance\doublecolumnhsize by -.04154\hsize + \divide\doublecolumnhsize by 2 + \hsize = \doublecolumnhsize + % + % Double the \vsize as well. (We don't need a separate register here, + % since nobody clobbers \vsize.) + \vsize = 2\vsize +} + +% The double-column output routine for all double-column pages except +% the last. +% +\def\doublecolumnout{% + \splittopskip=\topskip \splitmaxdepth=\maxdepth + % Get the available space for the double columns -- the normal + % (undoubled) page height minus any material left over from the + % previous page. + \dimen@ = \vsize + \divide\dimen@ by 2 + \advance\dimen@ by -\ht\partialpage + % + % box0 will be the left-hand column, box2 the right. + \setbox0=\vsplit255 to\dimen@ \setbox2=\vsplit255 to\dimen@ + \onepageout\pagesofar + \unvbox255 + \penalty\outputpenalty +} +% +% Re-output the contents of the output page -- any previous material, +% followed by the two boxes we just split, in box0 and box2. +\def\pagesofar{% + \unvbox\partialpage + % + \hsize = \doublecolumnhsize + \wd0=\hsize \wd2=\hsize + \hbox to\pagewidth{\box0\hfil\box2}% +} +% +% All done with double columns. +\def\enddoublecolumns{% + % The following penalty ensures that the page builder is exercised + % _before_ we change the output routine. This is necessary in the + % following situation: + % + % The last section of the index consists only of a single entry. + % Before this section, \pagetotal is less than \pagegoal, so no + % break occurs before the last section starts. However, the last + % section, consisting of \initial and the single \entry, does not + % fit on the page and has to be broken off. Without the following + % penalty the page builder will not be exercised until \eject + % below, and by that time we'll already have changed the output + % routine to the \balancecolumns version, so the next-to-last + % double-column page will be processed with \balancecolumns, which + % is wrong: The two columns will go to the main vertical list, with + % the broken-off section in the recent contributions. As soon as + % the output routine finishes, TeX starts reconsidering the page + % break. The two columns and the broken-off section both fit on the + % page, because the two columns now take up only half of the page + % goal. When TeX sees \eject from below which follows the final + % section, it invokes the new output routine that we've set after + % \balancecolumns below; \onepageout will try to fit the two columns + % and the final section into the vbox of \pageheight (see + % \pagebody), causing an overfull box. + % + % Note that glue won't work here, because glue does not exercise the + % page builder, unlike penalties (see The TeXbook, pp. 280-281). + \penalty0 + % + \output = {% + % Split the last of the double-column material. Leave it on the + % current page, no automatic page break. + \balancecolumns + % + % If we end up splitting too much material for the current page, + % though, there will be another page break right after this \output + % invocation ends. Having called \balancecolumns once, we do not + % want to call it again. Therefore, reset \output to its normal + % definition right away. (We hope \balancecolumns will never be + % called on to balance too much material, but if it is, this makes + % the output somewhat more palatable.) + \global\output = {\onepageout{\pagecontents\PAGE}}% + }% + \eject + \endgroup % started in \begindoublecolumns + % + % \pagegoal was set to the doubled \vsize above, since we restarted + % the current page. We're now back to normal single-column + % typesetting, so reset \pagegoal to the normal \vsize (after the + % \endgroup where \vsize got restored). + \pagegoal = \vsize +} +% +% Called at the end of the double column material. +\def\balancecolumns{% + \setbox0 = \vbox{\unvbox255}% like \box255 but more efficient, see p.120. + \dimen@ = \ht0 + \advance\dimen@ by \topskip + \advance\dimen@ by-\baselineskip + \divide\dimen@ by 2 % target to split to + %debug\message{final 2-column material height=\the\ht0, target=\the\dimen@.}% + \splittopskip = \topskip + % Loop until we get a decent breakpoint. + {% + \vbadness = 10000 + \loop + \global\setbox3 = \copy0 + \global\setbox1 = \vsplit3 to \dimen@ + \ifdim\ht3>\dimen@ + \global\advance\dimen@ by 1pt + \repeat + }% + %debug\message{split to \the\dimen@, column heights: \the\ht1, \the\ht3.}% + \setbox0=\vbox to\dimen@{\unvbox1}% + \setbox2=\vbox to\dimen@{\unvbox3}% + % + \pagesofar +} +\catcode`\@ = \other + + +\message{sectioning,} +% Chapters, sections, etc. + +% \unnumberedno is an oxymoron, of course. But we count the unnumbered +% sections so that we can refer to them unambiguously in the pdf +% outlines by their "section number". We avoid collisions with chapter +% numbers by starting them at 10000. (If a document ever has 10000 +% chapters, we're in trouble anyway, I'm sure.) +\newcount\unnumberedno \unnumberedno = 10000 +\newcount\chapno +\newcount\secno \secno=0 +\newcount\subsecno \subsecno=0 +\newcount\subsubsecno \subsubsecno=0 + +% This counter is funny since it counts through charcodes of letters A, B, ... +\newcount\appendixno \appendixno = `\@ +% +% \def\appendixletter{\char\the\appendixno} +% We do the following ugly conditional instead of the above simple +% construct for the sake of pdftex, which needs the actual +% letter in the expansion, not just typeset. +% +\def\appendixletter{% + \ifnum\appendixno=`A A% + \else\ifnum\appendixno=`B B% + \else\ifnum\appendixno=`C C% + \else\ifnum\appendixno=`D D% + \else\ifnum\appendixno=`E E% + \else\ifnum\appendixno=`F F% + \else\ifnum\appendixno=`G G% + \else\ifnum\appendixno=`H H% + \else\ifnum\appendixno=`I I% + \else\ifnum\appendixno=`J J% + \else\ifnum\appendixno=`K K% + \else\ifnum\appendixno=`L L% + \else\ifnum\appendixno=`M M% + \else\ifnum\appendixno=`N N% + \else\ifnum\appendixno=`O O% + \else\ifnum\appendixno=`P P% + \else\ifnum\appendixno=`Q Q% + \else\ifnum\appendixno=`R R% + \else\ifnum\appendixno=`S S% + \else\ifnum\appendixno=`T T% + \else\ifnum\appendixno=`U U% + \else\ifnum\appendixno=`V V% + \else\ifnum\appendixno=`W W% + \else\ifnum\appendixno=`X X% + \else\ifnum\appendixno=`Y Y% + \else\ifnum\appendixno=`Z Z% + % The \the is necessary, despite appearances, because \appendixletter is + % expanded while writing the .toc file. \char\appendixno is not + % expandable, thus it is written literally, thus all appendixes come out + % with the same letter (or @) in the toc without it. + \else\char\the\appendixno + \fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi + \fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi} + +% Each @chapter defines these (using marks) as the number+name, number +% and name of the chapter. Page headings and footings can use +% these. @section does likewise. +\def\thischapter{} +\def\thischapternum{} +\def\thischaptername{} +\def\thissection{} +\def\thissectionnum{} +\def\thissectionname{} + +\newcount\absseclevel % used to calculate proper heading level +\newcount\secbase\secbase=0 % @raisesections/@lowersections modify this count + +% @raisesections: treat @section as chapter, @subsection as section, etc. +\def\raisesections{\global\advance\secbase by -1} +\let\up=\raisesections % original BFox name + +% @lowersections: treat @chapter as section, @section as subsection, etc. +\def\lowersections{\global\advance\secbase by 1} +\let\down=\lowersections % original BFox name + +% we only have subsub. +\chardef\maxseclevel = 3 +% +% A numbered section within an unnumbered changes to unnumbered too. +% To achive this, remember the "biggest" unnum. sec. we are currently in: +\chardef\unmlevel = \maxseclevel +% +% Trace whether the current chapter is an appendix or not: +% \chapheadtype is "N" or "A", unnumbered chapters are ignored. +\def\chapheadtype{N} + +% Choose a heading macro +% #1 is heading type +% #2 is heading level +% #3 is text for heading +\def\genhead#1#2#3{% + % Compute the abs. sec. level: + \absseclevel=#2 + \advance\absseclevel by \secbase + % Make sure \absseclevel doesn't fall outside the range: + \ifnum \absseclevel < 0 + \absseclevel = 0 + \else + \ifnum \absseclevel > 3 + \absseclevel = 3 + \fi + \fi + % The heading type: + \def\headtype{#1}% + \if \headtype U% + \ifnum \absseclevel < \unmlevel + \chardef\unmlevel = \absseclevel + \fi + \else + % Check for appendix sections: + \ifnum \absseclevel = 0 + \edef\chapheadtype{\headtype}% + \else + \if \headtype A\if \chapheadtype N% + \errmessage{@appendix... within a non-appendix chapter}% + \fi\fi + \fi + % Check for numbered within unnumbered: + \ifnum \absseclevel > \unmlevel + \def\headtype{U}% + \else + \chardef\unmlevel = 3 + \fi + \fi + % Now print the heading: + \if \headtype U% + \ifcase\absseclevel + \unnumberedzzz{#3}% + \or \unnumberedseczzz{#3}% + \or \unnumberedsubseczzz{#3}% + \or \unnumberedsubsubseczzz{#3}% + \fi + \else + \if \headtype A% + \ifcase\absseclevel + \appendixzzz{#3}% + \or \appendixsectionzzz{#3}% + \or \appendixsubseczzz{#3}% + \or \appendixsubsubseczzz{#3}% + \fi + \else + \ifcase\absseclevel + \chapterzzz{#3}% + \or \seczzz{#3}% + \or \numberedsubseczzz{#3}% + \or \numberedsubsubseczzz{#3}% + \fi + \fi + \fi + \suppressfirstparagraphindent +} + +% an interface: +\def\numhead{\genhead N} +\def\apphead{\genhead A} +\def\unnmhead{\genhead U} + +% @chapter, @appendix, @unnumbered. Increment top-level counter, reset +% all lower-level sectioning counters to zero. +% +% Also set \chaplevelprefix, which we prepend to @float sequence numbers +% (e.g., figures), q.v. By default (before any chapter), that is empty. +\let\chaplevelprefix = \empty +% +\outer\parseargdef\chapter{\numhead0{#1}} % normally numhead0 calls chapterzzz +\def\chapterzzz#1{% + % section resetting is \global in case the chapter is in a group, such + % as an @include file. + \global\secno=0 \global\subsecno=0 \global\subsubsecno=0 + \global\advance\chapno by 1 + % + % Used for \float. + \gdef\chaplevelprefix{\the\chapno.}% + \resetallfloatnos + % + \message{\putwordChapter\space \the\chapno}% + % + % Write the actual heading. + \chapmacro{#1}{Ynumbered}{\the\chapno}% + % + % So @section and the like are numbered underneath this chapter. + \global\let\section = \numberedsec + \global\let\subsection = \numberedsubsec + \global\let\subsubsection = \numberedsubsubsec +} + +\outer\parseargdef\appendix{\apphead0{#1}} % normally apphead0 calls appendixzzz +\def\appendixzzz#1{% + \global\secno=0 \global\subsecno=0 \global\subsubsecno=0 + \global\advance\appendixno by 1 + \gdef\chaplevelprefix{\appendixletter.}% + \resetallfloatnos + % + \def\appendixnum{\putwordAppendix\space \appendixletter}% + \message{\appendixnum}% + % + \chapmacro{#1}{Yappendix}{\appendixletter}% + % + \global\let\section = \appendixsec + \global\let\subsection = \appendixsubsec + \global\let\subsubsection = \appendixsubsubsec +} + +\outer\parseargdef\unnumbered{\unnmhead0{#1}} % normally unnmhead0 calls unnumberedzzz +\def\unnumberedzzz#1{% + \global\secno=0 \global\subsecno=0 \global\subsubsecno=0 + \global\advance\unnumberedno by 1 + % + % Since an unnumbered has no number, no prefix for figures. + \global\let\chaplevelprefix = \empty + \resetallfloatnos + % + % This used to be simply \message{#1}, but TeX fully expands the + % argument to \message. Therefore, if #1 contained @-commands, TeX + % expanded them. For example, in `@unnumbered The @cite{Book}', TeX + % expanded @cite (which turns out to cause errors because \cite is meant + % to be executed, not expanded). + % + % Anyway, we don't want the fully-expanded definition of @cite to appear + % as a result of the \message, we just want `@cite' itself. We use + % \the<toks register> to achieve this: TeX expands \the<toks> only once, + % simply yielding the contents of <toks register>. (We also do this for + % the toc entries.) + \toks0 = {#1}% + \message{(\the\toks0)}% + % + \chapmacro{#1}{Ynothing}{\the\unnumberedno}% + % + \global\let\section = \unnumberedsec + \global\let\subsection = \unnumberedsubsec + \global\let\subsubsection = \unnumberedsubsubsec +} + +% @centerchap is like @unnumbered, but the heading is centered. +\outer\parseargdef\centerchap{% + % Well, we could do the following in a group, but that would break + % an assumption that \chapmacro is called at the outermost level. + % Thus we are safer this way: --kasal, 24feb04 + \let\centerparametersmaybe = \centerparameters + \unnmhead0{#1}% + \let\centerparametersmaybe = \relax +} + +% @top is like @unnumbered. +\let\top\unnumbered + +% Sections. +\outer\parseargdef\numberedsec{\numhead1{#1}} % normally calls seczzz +\def\seczzz#1{% + \global\subsecno=0 \global\subsubsecno=0 \global\advance\secno by 1 + \sectionheading{#1}{sec}{Ynumbered}{\the\chapno.\the\secno}% +} + +\outer\parseargdef\appendixsection{\apphead1{#1}} % normally calls appendixsectionzzz +\def\appendixsectionzzz#1{% + \global\subsecno=0 \global\subsubsecno=0 \global\advance\secno by 1 + \sectionheading{#1}{sec}{Yappendix}{\appendixletter.\the\secno}% +} +\let\appendixsec\appendixsection + +\outer\parseargdef\unnumberedsec{\unnmhead1{#1}} % normally calls unnumberedseczzz +\def\unnumberedseczzz#1{% + \global\subsecno=0 \global\subsubsecno=0 \global\advance\secno by 1 + \sectionheading{#1}{sec}{Ynothing}{\the\unnumberedno.\the\secno}% +} + +% Subsections. +\outer\parseargdef\numberedsubsec{\numhead2{#1}} % normally calls numberedsubseczzz +\def\numberedsubseczzz#1{% + \global\subsubsecno=0 \global\advance\subsecno by 1 + \sectionheading{#1}{subsec}{Ynumbered}{\the\chapno.\the\secno.\the\subsecno}% +} + +\outer\parseargdef\appendixsubsec{\apphead2{#1}} % normally calls appendixsubseczzz +\def\appendixsubseczzz#1{% + \global\subsubsecno=0 \global\advance\subsecno by 1 + \sectionheading{#1}{subsec}{Yappendix}% + {\appendixletter.\the\secno.\the\subsecno}% +} + +\outer\parseargdef\unnumberedsubsec{\unnmhead2{#1}} %normally calls unnumberedsubseczzz +\def\unnumberedsubseczzz#1{% + \global\subsubsecno=0 \global\advance\subsecno by 1 + \sectionheading{#1}{subsec}{Ynothing}% + {\the\unnumberedno.\the\secno.\the\subsecno}% +} + +% Subsubsections. +\outer\parseargdef\numberedsubsubsec{\numhead3{#1}} % normally numberedsubsubseczzz +\def\numberedsubsubseczzz#1{% + \global\advance\subsubsecno by 1 + \sectionheading{#1}{subsubsec}{Ynumbered}% + {\the\chapno.\the\secno.\the\subsecno.\the\subsubsecno}% +} + +\outer\parseargdef\appendixsubsubsec{\apphead3{#1}} % normally appendixsubsubseczzz +\def\appendixsubsubseczzz#1{% + \global\advance\subsubsecno by 1 + \sectionheading{#1}{subsubsec}{Yappendix}% + {\appendixletter.\the\secno.\the\subsecno.\the\subsubsecno}% +} + +\outer\parseargdef\unnumberedsubsubsec{\unnmhead3{#1}} %normally unnumberedsubsubseczzz +\def\unnumberedsubsubseczzz#1{% + \global\advance\subsubsecno by 1 + \sectionheading{#1}{subsubsec}{Ynothing}% + {\the\unnumberedno.\the\secno.\the\subsecno.\the\subsubsecno}% +} + +% These macros control what the section commands do, according +% to what kind of chapter we are in (ordinary, appendix, or unnumbered). +% Define them by default for a numbered chapter. +\let\section = \numberedsec +\let\subsection = \numberedsubsec +\let\subsubsection = \numberedsubsubsec + +% Define @majorheading, @heading and @subheading + +% NOTE on use of \vbox for chapter headings, section headings, and such: +% 1) We use \vbox rather than the earlier \line to permit +% overlong headings to fold. +% 2) \hyphenpenalty is set to 10000 because hyphenation in a +% heading is obnoxious; this forbids it. +% 3) Likewise, headings look best if no \parindent is used, and +% if justification is not attempted. Hence \raggedright. + + +\def\majorheading{% + {\advance\chapheadingskip by 10pt \chapbreak }% + \parsearg\chapheadingzzz +} + +\def\chapheading{\chapbreak \parsearg\chapheadingzzz} +\def\chapheadingzzz#1{% + {\chapfonts \vbox{\hyphenpenalty=10000\tolerance=5000 + \parindent=0pt\raggedright + \rm #1\hfill}}% + \bigskip \par\penalty 200\relax + \suppressfirstparagraphindent +} + +% @heading, @subheading, @subsubheading. +\parseargdef\heading{\sectionheading{#1}{sec}{Yomitfromtoc}{} + \suppressfirstparagraphindent} +\parseargdef\subheading{\sectionheading{#1}{subsec}{Yomitfromtoc}{} + \suppressfirstparagraphindent} +\parseargdef\subsubheading{\sectionheading{#1}{subsubsec}{Yomitfromtoc}{} + \suppressfirstparagraphindent} + +% These macros generate a chapter, section, etc. heading only +% (including whitespace, linebreaking, etc. around it), +% given all the information in convenient, parsed form. + +%%% Args are the skip and penalty (usually negative) +\def\dobreak#1#2{\par\ifdim\lastskip<#1\removelastskip\penalty#2\vskip#1\fi} + +%%% Define plain chapter starts, and page on/off switching for it +% Parameter controlling skip before chapter headings (if needed) + +\newskip\chapheadingskip + +\def\chapbreak{\dobreak \chapheadingskip {-4000}} +\def\chappager{\par\vfill\supereject} +% Because \domark is called before \chapoddpage, the filler page will +% get the headings for the next chapter, which is wrong. But we don't +% care -- we just disable all headings on the filler page. +\def\chapoddpage{% + \chappager + \ifodd\pageno \else + \begingroup + \evenheadline={\hfil}\evenfootline={\hfil}% + \oddheadline={\hfil}\oddfootline={\hfil}% + \hbox to 0pt{}% + \chappager + \endgroup + \fi +} + +\def\setchapternewpage #1 {\csname CHAPPAG#1\endcsname} + +\def\CHAPPAGoff{% +\global\let\contentsalignmacro = \chappager +\global\let\pchapsepmacro=\chapbreak +\global\let\pagealignmacro=\chappager} + +\def\CHAPPAGon{% +\global\let\contentsalignmacro = \chappager +\global\let\pchapsepmacro=\chappager +\global\let\pagealignmacro=\chappager +\global\def\HEADINGSon{\HEADINGSsingle}} + +\def\CHAPPAGodd{% +\global\let\contentsalignmacro = \chapoddpage +\global\let\pchapsepmacro=\chapoddpage +\global\let\pagealignmacro=\chapoddpage +\global\def\HEADINGSon{\HEADINGSdouble}} + +\CHAPPAGon + +% Chapter opening. +% +% #1 is the text, #2 is the section type (Ynumbered, Ynothing, +% Yappendix, Yomitfromtoc), #3 the chapter number. +% +% To test against our argument. +\def\Ynothingkeyword{Ynothing} +\def\Yomitfromtockeyword{Yomitfromtoc} +\def\Yappendixkeyword{Yappendix} +% +\def\chapmacro#1#2#3{% + % Insert the first mark before the heading break (see notes for \domark). + \let\prevchapterdefs=\lastchapterdefs + \let\prevsectiondefs=\lastsectiondefs + \gdef\lastsectiondefs{\gdef\thissectionname{}\gdef\thissectionnum{}% + \gdef\thissection{}}% + % + \def\temptype{#2}% + \ifx\temptype\Ynothingkeyword + \gdef\lastchapterdefs{\gdef\thischaptername{#1}\gdef\thischapternum{}% + \gdef\thischapter{\thischaptername}}% + \else\ifx\temptype\Yomitfromtockeyword + \gdef\lastchapterdefs{\gdef\thischaptername{#1}\gdef\thischapternum{}% + \gdef\thischapter{}}% + \else\ifx\temptype\Yappendixkeyword + \toks0={#1}% + \xdef\lastchapterdefs{% + \gdef\noexpand\thischaptername{\the\toks0}% + \gdef\noexpand\thischapternum{\appendixletter}% + \gdef\noexpand\thischapter{\putwordAppendix{} \noexpand\thischapternum: + \noexpand\thischaptername}% + }% + \else + \toks0={#1}% + \xdef\lastchapterdefs{% + \gdef\noexpand\thischaptername{\the\toks0}% + \gdef\noexpand\thischapternum{\the\chapno}% + \gdef\noexpand\thischapter{\putwordChapter{} \noexpand\thischapternum: + \noexpand\thischaptername}% + }% + \fi\fi\fi + % + % Output the mark. Pass it through \safewhatsit, to take care of + % the preceding space. + \safewhatsit\domark + % + % Insert the chapter heading break. + \pchapsepmacro + % + % Now the second mark, after the heading break. No break points + % between here and the heading. + \let\prevchapterdefs=\lastchapterdefs + \let\prevsectiondefs=\lastsectiondefs + \domark + % + {% + \chapfonts \rm + % + % Have to define \lastsection before calling \donoderef, because the + % xref code eventually uses it. On the other hand, it has to be called + % after \pchapsepmacro, or the headline will change too soon. + \gdef\lastsection{#1}% + % + % Only insert the separating space if we have a chapter/appendix + % number, and don't print the unnumbered ``number''. + \ifx\temptype\Ynothingkeyword + \setbox0 = \hbox{}% + \def\toctype{unnchap}% + \else\ifx\temptype\Yomitfromtockeyword + \setbox0 = \hbox{}% contents like unnumbered, but no toc entry + \def\toctype{omit}% + \else\ifx\temptype\Yappendixkeyword + \setbox0 = \hbox{\putwordAppendix{} #3\enspace}% + \def\toctype{app}% + \else + \setbox0 = \hbox{#3\enspace}% + \def\toctype{numchap}% + \fi\fi\fi + % + % Write the toc entry for this chapter. Must come before the + % \donoderef, because we include the current node name in the toc + % entry, and \donoderef resets it to empty. + \writetocentry{\toctype}{#1}{#3}% + % + % For pdftex, we have to write out the node definition (aka, make + % the pdfdest) after any page break, but before the actual text has + % been typeset. If the destination for the pdf outline is after the + % text, then jumping from the outline may wind up with the text not + % being visible, for instance under high magnification. + \donoderef{#2}% + % + % Typeset the actual heading. + \nobreak % Avoid page breaks at the interline glue. + \vbox{\hyphenpenalty=10000 \tolerance=5000 \parindent=0pt \raggedright + \hangindent=\wd0 \centerparametersmaybe + \unhbox0 #1\par}% + }% + \nobreak\bigskip % no page break after a chapter title + \nobreak +} + +% @centerchap -- centered and unnumbered. +\let\centerparametersmaybe = \relax +\def\centerparameters{% + \advance\rightskip by 3\rightskip + \leftskip = \rightskip + \parfillskip = 0pt +} + + +% I don't think this chapter style is supported any more, so I'm not +% updating it with the new noderef stuff. We'll see. --karl, 11aug03. +% +\def\setchapterstyle #1 {\csname CHAPF#1\endcsname} +% +\def\unnchfopen #1{% +\chapoddpage {\chapfonts \vbox{\hyphenpenalty=10000\tolerance=5000 + \parindent=0pt\raggedright + \rm #1\hfill}}\bigskip \par\nobreak +} +\def\chfopen #1#2{\chapoddpage {\chapfonts +\vbox to 3in{\vfil \hbox to\hsize{\hfil #2} \hbox to\hsize{\hfil #1} \vfil}}% +\par\penalty 5000 % +} +\def\centerchfopen #1{% +\chapoddpage {\chapfonts \vbox{\hyphenpenalty=10000\tolerance=5000 + \parindent=0pt + \hfill {\rm #1}\hfill}}\bigskip \par\nobreak +} +\def\CHAPFopen{% + \global\let\chapmacro=\chfopen + \global\let\centerchapmacro=\centerchfopen} + + +% Section titles. These macros combine the section number parts and +% call the generic \sectionheading to do the printing. +% +\newskip\secheadingskip +\def\secheadingbreak{\dobreak \secheadingskip{-1000}} + +% Subsection titles. +\newskip\subsecheadingskip +\def\subsecheadingbreak{\dobreak \subsecheadingskip{-500}} + +% Subsubsection titles. +\def\subsubsecheadingskip{\subsecheadingskip} +\def\subsubsecheadingbreak{\subsecheadingbreak} + + +% Print any size, any type, section title. +% +% #1 is the text, #2 is the section level (sec/subsec/subsubsec), #3 is +% the section type for xrefs (Ynumbered, Ynothing, Yappendix), #4 is the +% section number. +% +\def\seckeyword{sec} +% +\def\sectionheading#1#2#3#4{% + {% + % Switch to the right set of fonts. + \csname #2fonts\endcsname \rm + % + \def\sectionlevel{#2}% + \def\temptype{#3}% + % + % Insert first mark before the heading break (see notes for \domark). + \let\prevsectiondefs=\lastsectiondefs + \ifx\temptype\Ynothingkeyword + \ifx\sectionlevel\seckeyword + \gdef\lastsectiondefs{\gdef\thissectionname{#1}\gdef\thissectionnum{}% + \gdef\thissection{\thissectionname}}% + \fi + \else\ifx\temptype\Yomitfromtockeyword + % Don't redefine \thissection. + \else\ifx\temptype\Yappendixkeyword + \ifx\sectionlevel\seckeyword + \toks0={#1}% + \xdef\lastsectiondefs{% + \gdef\noexpand\thissectionname{\the\toks0}% + \gdef\noexpand\thissectionnum{#4}% + \gdef\noexpand\thissection{\putwordSection{} \noexpand\thissectionnum: + \noexpand\thissectionname}% + }% + \fi + \else + \ifx\sectionlevel\seckeyword + \toks0={#1}% + \xdef\lastsectiondefs{% + \gdef\noexpand\thissectionname{\the\toks0}% + \gdef\noexpand\thissectionnum{#4}% + \gdef\noexpand\thissection{\putwordSection{} \noexpand\thissectionnum: + \noexpand\thissectionname}% + }% + \fi + \fi\fi\fi + % + % Output the mark. Pass it through \safewhatsit, to take care of + % the preceding space. + \safewhatsit\domark + % + % Insert space above the heading. + \csname #2headingbreak\endcsname + % + % Now the second mark, after the heading break. No break points + % between here and the heading. + \let\prevsectiondefs=\lastsectiondefs + \domark + % + % Only insert the space after the number if we have a section number. + \ifx\temptype\Ynothingkeyword + \setbox0 = \hbox{}% + \def\toctype{unn}% + \gdef\lastsection{#1}% + \else\ifx\temptype\Yomitfromtockeyword + % for @headings -- no section number, don't include in toc, + % and don't redefine \lastsection. + \setbox0 = \hbox{}% + \def\toctype{omit}% + \let\sectionlevel=\empty + \else\ifx\temptype\Yappendixkeyword + \setbox0 = \hbox{#4\enspace}% + \def\toctype{app}% + \gdef\lastsection{#1}% + \else + \setbox0 = \hbox{#4\enspace}% + \def\toctype{num}% + \gdef\lastsection{#1}% + \fi\fi\fi + % + % Write the toc entry (before \donoderef). See comments in \chapmacro. + \writetocentry{\toctype\sectionlevel}{#1}{#4}% + % + % Write the node reference (= pdf destination for pdftex). + % Again, see comments in \chapmacro. + \donoderef{#3}% + % + % Interline glue will be inserted when the vbox is completed. + % That glue will be a valid breakpoint for the page, since it'll be + % preceded by a whatsit (usually from the \donoderef, or from the + % \writetocentry if there was no node). We don't want to allow that + % break, since then the whatsits could end up on page n while the + % section is on page n+1, thus toc/etc. are wrong. Debian bug 276000. + \nobreak + % + % Output the actual section heading. + \vbox{\hyphenpenalty=10000 \tolerance=5000 \parindent=0pt \raggedright + \hangindent=\wd0 % zero if no section number + \unhbox0 #1}% + }% + % Add extra space after the heading -- half of whatever came above it. + % Don't allow stretch, though. + \kern .5 \csname #2headingskip\endcsname + % + % Do not let the kern be a potential breakpoint, as it would be if it + % was followed by glue. + \nobreak + % + % We'll almost certainly start a paragraph next, so don't let that + % glue accumulate. (Not a breakpoint because it's preceded by a + % discardable item.) + \vskip-\parskip + % + % This is purely so the last item on the list is a known \penalty > + % 10000. This is so \startdefun can avoid allowing breakpoints after + % section headings. Otherwise, it would insert a valid breakpoint between: + % + % @section sec-whatever + % @deffn def-whatever + \penalty 10001 +} + + +\message{toc,} +% Table of contents. +\newwrite\tocfile + +% Write an entry to the toc file, opening it if necessary. +% Called from @chapter, etc. +% +% Example usage: \writetocentry{sec}{Section Name}{\the\chapno.\the\secno} +% We append the current node name (if any) and page number as additional +% arguments for the \{chap,sec,...}entry macros which will eventually +% read this. The node name is used in the pdf outlines as the +% destination to jump to. +% +% We open the .toc file for writing here instead of at @setfilename (or +% any other fixed time) so that @contents can be anywhere in the document. +% But if #1 is `omit', then we don't do anything. This is used for the +% table of contents chapter openings themselves. +% +\newif\iftocfileopened +\def\omitkeyword{omit}% +% +\def\writetocentry#1#2#3{% + \edef\writetoctype{#1}% + \ifx\writetoctype\omitkeyword \else + \iftocfileopened\else + \immediate\openout\tocfile = \jobname.toc + \global\tocfileopenedtrue + \fi + % + \iflinks + {\atdummies + \edef\temp{% + \write\tocfile{@#1entry{#2}{#3}{\lastnode}{\noexpand\folio}}}% + \temp + }% + \fi + \fi + % + % Tell \shipout to create a pdf destination on each page, if we're + % writing pdf. These are used in the table of contents. We can't + % just write one on every page because the title pages are numbered + % 1 and 2 (the page numbers aren't printed), and so are the first + % two pages of the document. Thus, we'd have two destinations named + % `1', and two named `2'. + \ifpdf \global\pdfmakepagedesttrue \fi +} + + +% These characters do not print properly in the Computer Modern roman +% fonts, so we must take special care. This is more or less redundant +% with the Texinfo input format setup at the end of this file. +% +\def\activecatcodes{% + \catcode`\"=\active + \catcode`\$=\active + \catcode`\<=\active + \catcode`\>=\active + \catcode`\\=\active + \catcode`\^=\active + \catcode`\_=\active + \catcode`\|=\active + \catcode`\~=\active +} + + +% Read the toc file, which is essentially Texinfo input. +\def\readtocfile{% + \setupdatafile + \activecatcodes + \input \tocreadfilename +} + +\newskip\contentsrightmargin \contentsrightmargin=1in +\newcount\savepageno +\newcount\lastnegativepageno \lastnegativepageno = -1 + +% Prepare to read what we've written to \tocfile. +% +\def\startcontents#1{% + % If @setchapternewpage on, and @headings double, the contents should + % start on an odd page, unlike chapters. Thus, we maintain + % \contentsalignmacro in parallel with \pagealignmacro. + % From: Torbjorn Granlund <tege@matematik.su.se> + \contentsalignmacro + \immediate\closeout\tocfile + % + % Don't need to put `Contents' or `Short Contents' in the headline. + % It is abundantly clear what they are. + \chapmacro{#1}{Yomitfromtoc}{}% + % + \savepageno = \pageno + \begingroup % Set up to handle contents files properly. + \raggedbottom % Worry more about breakpoints than the bottom. + \advance\hsize by -\contentsrightmargin % Don't use the full line length. + % + % Roman numerals for page numbers. + \ifnum \pageno>0 \global\pageno = \lastnegativepageno \fi +} + +% redefined for the two-volume lispref. We always output on +% \jobname.toc even if this is redefined. +% +\def\tocreadfilename{\jobname.toc} + +% Normal (long) toc. +% +\def\contents{% + \startcontents{\putwordTOC}% + \openin 1 \tocreadfilename\space + \ifeof 1 \else + \readtocfile + \fi + \vfill \eject + \contentsalignmacro % in case @setchapternewpage odd is in effect + \ifeof 1 \else + \pdfmakeoutlines + \fi + \closein 1 + \endgroup + \lastnegativepageno = \pageno + \global\pageno = \savepageno +} + +% And just the chapters. +\def\summarycontents{% + \startcontents{\putwordShortTOC}% + % + \let\numchapentry = \shortchapentry + \let\appentry = \shortchapentry + \let\unnchapentry = \shortunnchapentry + % We want a true roman here for the page numbers. + \secfonts + \let\rm=\shortcontrm \let\bf=\shortcontbf + \let\sl=\shortcontsl \let\tt=\shortconttt + \rm + \hyphenpenalty = 10000 + \advance\baselineskip by 1pt % Open it up a little. + \def\numsecentry##1##2##3##4{} + \let\appsecentry = \numsecentry + \let\unnsecentry = \numsecentry + \let\numsubsecentry = \numsecentry + \let\appsubsecentry = \numsecentry + \let\unnsubsecentry = \numsecentry + \let\numsubsubsecentry = \numsecentry + \let\appsubsubsecentry = \numsecentry + \let\unnsubsubsecentry = \numsecentry + \openin 1 \tocreadfilename\space + \ifeof 1 \else + \readtocfile + \fi + \closein 1 + \vfill \eject + \contentsalignmacro % in case @setchapternewpage odd is in effect + \endgroup + \lastnegativepageno = \pageno + \global\pageno = \savepageno +} +\let\shortcontents = \summarycontents + +% Typeset the label for a chapter or appendix for the short contents. +% The arg is, e.g., `A' for an appendix, or `3' for a chapter. +% +\def\shortchaplabel#1{% + % This space should be enough, since a single number is .5em, and the + % widest letter (M) is 1em, at least in the Computer Modern fonts. + % But use \hss just in case. + % (This space doesn't include the extra space that gets added after + % the label; that gets put in by \shortchapentry above.) + % + % We'd like to right-justify chapter numbers, but that looks strange + % with appendix letters. And right-justifying numbers and + % left-justifying letters looks strange when there is less than 10 + % chapters. Have to read the whole toc once to know how many chapters + % there are before deciding ... + \hbox to 1em{#1\hss}% +} + +% These macros generate individual entries in the table of contents. +% The first argument is the chapter or section name. +% The last argument is the page number. +% The arguments in between are the chapter number, section number, ... + +% Chapters, in the main contents. +\def\numchapentry#1#2#3#4{\dochapentry{#2\labelspace#1}{#4}} +% +% Chapters, in the short toc. +% See comments in \dochapentry re vbox and related settings. +\def\shortchapentry#1#2#3#4{% + \tocentry{\shortchaplabel{#2}\labelspace #1}{\doshortpageno\bgroup#4\egroup}% +} + +% Appendices, in the main contents. +% Need the word Appendix, and a fixed-size box. +% +\def\appendixbox#1{% + % We use M since it's probably the widest letter. + \setbox0 = \hbox{\putwordAppendix{} M}% + \hbox to \wd0{\putwordAppendix{} #1\hss}} +% +\def\appentry#1#2#3#4{\dochapentry{\appendixbox{#2}\labelspace#1}{#4}} + +% Unnumbered chapters. +\def\unnchapentry#1#2#3#4{\dochapentry{#1}{#4}} +\def\shortunnchapentry#1#2#3#4{\tocentry{#1}{\doshortpageno\bgroup#4\egroup}} + +% Sections. +\def\numsecentry#1#2#3#4{\dosecentry{#2\labelspace#1}{#4}} +\let\appsecentry=\numsecentry +\def\unnsecentry#1#2#3#4{\dosecentry{#1}{#4}} + +% Subsections. +\def\numsubsecentry#1#2#3#4{\dosubsecentry{#2\labelspace#1}{#4}} +\let\appsubsecentry=\numsubsecentry +\def\unnsubsecentry#1#2#3#4{\dosubsecentry{#1}{#4}} + +% And subsubsections. +\def\numsubsubsecentry#1#2#3#4{\dosubsubsecentry{#2\labelspace#1}{#4}} +\let\appsubsubsecentry=\numsubsubsecentry +\def\unnsubsubsecentry#1#2#3#4{\dosubsubsecentry{#1}{#4}} + +% This parameter controls the indentation of the various levels. +% Same as \defaultparindent. +\newdimen\tocindent \tocindent = 15pt + +% Now for the actual typesetting. In all these, #1 is the text and #2 is the +% page number. +% +% If the toc has to be broken over pages, we want it to be at chapters +% if at all possible; hence the \penalty. +\def\dochapentry#1#2{% + \penalty-300 \vskip1\baselineskip plus.33\baselineskip minus.25\baselineskip + \begingroup + \chapentryfonts + \tocentry{#1}{\dopageno\bgroup#2\egroup}% + \endgroup + \nobreak\vskip .25\baselineskip plus.1\baselineskip +} + +\def\dosecentry#1#2{\begingroup + \secentryfonts \leftskip=\tocindent + \tocentry{#1}{\dopageno\bgroup#2\egroup}% +\endgroup} + +\def\dosubsecentry#1#2{\begingroup + \subsecentryfonts \leftskip=2\tocindent + \tocentry{#1}{\dopageno\bgroup#2\egroup}% +\endgroup} + +\def\dosubsubsecentry#1#2{\begingroup + \subsubsecentryfonts \leftskip=3\tocindent + \tocentry{#1}{\dopageno\bgroup#2\egroup}% +\endgroup} + +% We use the same \entry macro as for the index entries. +\let\tocentry = \entry + +% Space between chapter (or whatever) number and the title. +\def\labelspace{\hskip1em \relax} + +\def\dopageno#1{{\rm #1}} +\def\doshortpageno#1{{\rm #1}} + +\def\chapentryfonts{\secfonts \rm} +\def\secentryfonts{\textfonts} +\def\subsecentryfonts{\textfonts} +\def\subsubsecentryfonts{\textfonts} + + +\message{environments,} +% @foo ... @end foo. + +% @point{}, @result{}, @expansion{}, @print{}, @equiv{}. +% +% Since these characters are used in examples, it should be an even number of +% \tt widths. Each \tt character is 1en, so two makes it 1em. +% +\def\point{$\star$} +\def\result{\leavevmode\raise.15ex\hbox to 1em{\hfil$\Rightarrow$\hfil}} +\def\expansion{\leavevmode\raise.1ex\hbox to 1em{\hfil$\mapsto$\hfil}} +\def\print{\leavevmode\lower.1ex\hbox to 1em{\hfil$\dashv$\hfil}} +\def\equiv{\leavevmode\lower.1ex\hbox to 1em{\hfil$\ptexequiv$\hfil}} + +% The @error{} command. +% Adapted from the TeXbook's \boxit. +% +\newbox\errorbox +% +{\tentt \global\dimen0 = 3em}% Width of the box. +\dimen2 = .55pt % Thickness of rules +% The text. (`r' is open on the right, `e' somewhat less so on the left.) +\setbox0 = \hbox{\kern-.75pt \reducedsf error\kern-1.5pt} +% +\setbox\errorbox=\hbox to \dimen0{\hfil + \hsize = \dimen0 \advance\hsize by -5.8pt % Space to left+right. + \advance\hsize by -2\dimen2 % Rules. + \vbox{% + \hrule height\dimen2 + \hbox{\vrule width\dimen2 \kern3pt % Space to left of text. + \vtop{\kern2.4pt \box0 \kern2.4pt}% Space above/below. + \kern3pt\vrule width\dimen2}% Space to right. + \hrule height\dimen2} + \hfil} +% +\def\error{\leavevmode\lower.7ex\copy\errorbox} + +% @tex ... @end tex escapes into raw Tex temporarily. +% One exception: @ is still an escape character, so that @end tex works. +% But \@ or @@ will get a plain tex @ character. + +\envdef\tex{% + \catcode `\\=0 \catcode `\{=1 \catcode `\}=2 + \catcode `\$=3 \catcode `\&=4 \catcode `\#=6 + \catcode `\^=7 \catcode `\_=8 \catcode `\~=\active \let~=\tie + \catcode `\%=14 + \catcode `\+=\other + \catcode `\"=\other + \catcode `\|=\other + \catcode `\<=\other + \catcode `\>=\other + \escapechar=`\\ + % + \let\b=\ptexb + \let\bullet=\ptexbullet + \let\c=\ptexc + \let\,=\ptexcomma + \let\.=\ptexdot + \let\dots=\ptexdots + \let\equiv=\ptexequiv + \let\!=\ptexexclam + \let\i=\ptexi + \let\indent=\ptexindent + \let\noindent=\ptexnoindent + \let\{=\ptexlbrace + \let\+=\tabalign + \let\}=\ptexrbrace + \let\/=\ptexslash + \let\*=\ptexstar + \let\t=\ptext + \let\frenchspacing=\plainfrenchspacing + % + \def\endldots{\mathinner{\ldots\ldots\ldots\ldots}}% + \def\enddots{\relax\ifmmode\endldots\else$\mathsurround=0pt \endldots\,$\fi}% + \def\@{@}% +} +% There is no need to define \Etex. + +% Define @lisp ... @end lisp. +% @lisp environment forms a group so it can rebind things, +% including the definition of @end lisp (which normally is erroneous). + +% Amount to narrow the margins by for @lisp. +\newskip\lispnarrowing \lispnarrowing=0.4in + +% This is the definition that ^^M gets inside @lisp, @example, and other +% such environments. \null is better than a space, since it doesn't +% have any width. +\def\lisppar{\null\endgraf} + +% This space is always present above and below environments. +\newskip\envskipamount \envskipamount = 0pt + +% Make spacing and below environment symmetrical. We use \parskip here +% to help in doing that, since in @example-like environments \parskip +% is reset to zero; thus the \afterenvbreak inserts no space -- but the +% start of the next paragraph will insert \parskip. +% +\def\aboveenvbreak{{% + % =10000 instead of <10000 because of a special case in \itemzzz and + % \sectionheading, q.v. + \ifnum \lastpenalty=10000 \else + \advance\envskipamount by \parskip + \endgraf + \ifdim\lastskip<\envskipamount + \removelastskip + % it's not a good place to break if the last penalty was \nobreak + % or better ... + \ifnum\lastpenalty<10000 \penalty-50 \fi + \vskip\envskipamount + \fi + \fi +}} + +\let\afterenvbreak = \aboveenvbreak + +% \nonarrowing is a flag. If "set", @lisp etc don't narrow margins; it will +% also clear it, so that its embedded environments do the narrowing again. +\let\nonarrowing=\relax + +% @cartouche ... @end cartouche: draw rectangle w/rounded corners around +% environment contents. +\font\circle=lcircle10 +\newdimen\circthick +\newdimen\cartouter\newdimen\cartinner +\newskip\normbskip\newskip\normpskip\newskip\normlskip +\circthick=\fontdimen8\circle +% +\def\ctl{{\circle\char'013\hskip -6pt}}% 6pt from pl file: 1/2charwidth +\def\ctr{{\hskip 6pt\circle\char'010}} +\def\cbl{{\circle\char'012\hskip -6pt}} +\def\cbr{{\hskip 6pt\circle\char'011}} +\def\carttop{\hbox to \cartouter{\hskip\lskip + \ctl\leaders\hrule height\circthick\hfil\ctr + \hskip\rskip}} +\def\cartbot{\hbox to \cartouter{\hskip\lskip + \cbl\leaders\hrule height\circthick\hfil\cbr + \hskip\rskip}} +% +\newskip\lskip\newskip\rskip + +\envdef\cartouche{% + \ifhmode\par\fi % can't be in the midst of a paragraph. + \startsavinginserts + \lskip=\leftskip \rskip=\rightskip + \leftskip=0pt\rightskip=0pt % we want these *outside*. + \cartinner=\hsize \advance\cartinner by-\lskip + \advance\cartinner by-\rskip + \cartouter=\hsize + \advance\cartouter by 18.4pt % allow for 3pt kerns on either + % side, and for 6pt waste from + % each corner char, and rule thickness + \normbskip=\baselineskip \normpskip=\parskip \normlskip=\lineskip + % Flag to tell @lisp, etc., not to narrow margin. + \let\nonarrowing = t% + \vbox\bgroup + \baselineskip=0pt\parskip=0pt\lineskip=0pt + \carttop + \hbox\bgroup + \hskip\lskip + \vrule\kern3pt + \vbox\bgroup + \kern3pt + \hsize=\cartinner + \baselineskip=\normbskip + \lineskip=\normlskip + \parskip=\normpskip + \vskip -\parskip + \comment % For explanation, see the end of \def\group. +} +\def\Ecartouche{% + \ifhmode\par\fi + \kern3pt + \egroup + \kern3pt\vrule + \hskip\rskip + \egroup + \cartbot + \egroup + \checkinserts +} + + +% This macro is called at the beginning of all the @example variants, +% inside a group. +\def\nonfillstart{% + \aboveenvbreak + \hfuzz = 12pt % Don't be fussy + \sepspaces % Make spaces be word-separators rather than space tokens. + \let\par = \lisppar % don't ignore blank lines + \obeylines % each line of input is a line of output + \parskip = 0pt + \parindent = 0pt + \emergencystretch = 0pt % don't try to avoid overfull boxes + \ifx\nonarrowing\relax + \advance \leftskip by \lispnarrowing + \exdentamount=\lispnarrowing + \else + \let\nonarrowing = \relax + \fi + \let\exdent=\nofillexdent +} + +% If you want all examples etc. small: @set dispenvsize small. +% If you want even small examples the full size: @set dispenvsize nosmall. +% This affects the following displayed environments: +% @example, @display, @format, @lisp +% +\def\smallword{small} +\def\nosmallword{nosmall} +\let\SETdispenvsize\relax +\def\setnormaldispenv{% + \ifx\SETdispenvsize\smallword + % end paragraph for sake of leading, in case document has no blank + % line. This is redundant with what happens in \aboveenvbreak, but + % we need to do it before changing the fonts, and it's inconvenient + % to change the fonts afterward. + \ifnum \lastpenalty=10000 \else \endgraf \fi + \smallexamplefonts \rm + \fi +} +\def\setsmalldispenv{% + \ifx\SETdispenvsize\nosmallword + \else + \ifnum \lastpenalty=10000 \else \endgraf \fi + \smallexamplefonts \rm + \fi +} + +% We often define two environments, @foo and @smallfoo. +% Let's do it by one command: +\def\makedispenv #1#2{ + \expandafter\envdef\csname#1\endcsname {\setnormaldispenv #2} + \expandafter\envdef\csname small#1\endcsname {\setsmalldispenv #2} + \expandafter\let\csname E#1\endcsname \afterenvbreak + \expandafter\let\csname Esmall#1\endcsname \afterenvbreak +} + +% Define two synonyms: +\def\maketwodispenvs #1#2#3{ + \makedispenv{#1}{#3} + \makedispenv{#2}{#3} +} + +% @lisp: indented, narrowed, typewriter font; @example: same as @lisp. +% +% @smallexample and @smalllisp: use smaller fonts. +% Originally contributed by Pavel@xerox. +% +\maketwodispenvs {lisp}{example}{% + \nonfillstart + \tt\quoteexpand + \let\kbdfont = \kbdexamplefont % Allow @kbd to do something special. + \gobble % eat return +} +% @display/@smalldisplay: same as @lisp except keep current font. +% +\makedispenv {display}{% + \nonfillstart + \gobble +} + +% @format/@smallformat: same as @display except don't narrow margins. +% +\makedispenv{format}{% + \let\nonarrowing = t% + \nonfillstart + \gobble +} + +% @flushleft: same as @format, but doesn't obey \SETdispenvsize. +\envdef\flushleft{% + \let\nonarrowing = t% + \nonfillstart + \gobble +} +\let\Eflushleft = \afterenvbreak + +% @flushright. +% +\envdef\flushright{% + \let\nonarrowing = t% + \nonfillstart + \advance\leftskip by 0pt plus 1fill + \gobble +} +\let\Eflushright = \afterenvbreak + + +% @quotation does normal linebreaking (hence we can't use \nonfillstart) +% and narrows the margins. We keep \parskip nonzero in general, since +% we're doing normal filling. So, when using \aboveenvbreak and +% \afterenvbreak, temporarily make \parskip 0. +% +\envdef\quotation{% + {\parskip=0pt \aboveenvbreak}% because \aboveenvbreak inserts \parskip + \parindent=0pt + % + % @cartouche defines \nonarrowing to inhibit narrowing at next level down. + \ifx\nonarrowing\relax + \advance\leftskip by \lispnarrowing + \advance\rightskip by \lispnarrowing + \exdentamount = \lispnarrowing + \else + \let\nonarrowing = \relax + \fi + \parsearg\quotationlabel +} + +% We have retained a nonzero parskip for the environment, since we're +% doing normal filling. +% +\def\Equotation{% + \par + \ifx\quotationauthor\undefined\else + % indent a bit. + \leftline{\kern 2\leftskip \sl ---\quotationauthor}% + \fi + {\parskip=0pt \afterenvbreak}% +} + +% If we're given an argument, typeset it in bold with a colon after. +\def\quotationlabel#1{% + \def\temp{#1}% + \ifx\temp\empty \else + {\bf #1: }% + \fi +} + + +% LaTeX-like @verbatim...@end verbatim and @verb{<char>...<char>} +% If we want to allow any <char> as delimiter, +% we need the curly braces so that makeinfo sees the @verb command, eg: +% `@verbx...x' would look like the '@verbx' command. --janneke@gnu.org +% +% [Knuth]: Donald Ervin Knuth, 1996. The TeXbook. +% +% [Knuth] p.344; only we need to do the other characters Texinfo sets +% active too. Otherwise, they get lost as the first character on a +% verbatim line. +\def\dospecials{% + \do\ \do\\\do\{\do\}\do\$\do\&% + \do\#\do\^\do\^^K\do\_\do\^^A\do\%\do\~% + \do\<\do\>\do\|\do\@\do+\do\"% +} +% +% [Knuth] p. 380 +\def\uncatcodespecials{% + \def\do##1{\catcode`##1=\other}\dospecials} +% +% [Knuth] pp. 380,381,391 +% Disable Spanish ligatures ?` and !` of \tt font +\begingroup + \catcode`\`=\active\gdef`{\relax\lq} +\endgroup +% +% Setup for the @verb command. +% +% Eight spaces for a tab +\begingroup + \catcode`\^^I=\active + \gdef\tabeightspaces{\catcode`\^^I=\active\def^^I{\ \ \ \ \ \ \ \ }} +\endgroup +% +\def\setupverb{% + \tt % easiest (and conventionally used) font for verbatim + \def\par{\leavevmode\endgraf}% + \catcode`\`=\active + \tabeightspaces + % Respect line breaks, + % print special symbols as themselves, and + % make each space count + % must do in this order: + \obeylines \uncatcodespecials \sepspaces +} + +% Setup for the @verbatim environment +% +% Real tab expansion +\newdimen\tabw \setbox0=\hbox{\tt\space} \tabw=8\wd0 % tab amount +% +\def\starttabbox{\setbox0=\hbox\bgroup} + +% Allow an option to not replace quotes with a regular directed right +% quote/apostrophe (char 0x27), but instead use the undirected quote +% from cmtt (char 0x0d). The undirected quote is ugly, so don't make it +% the default, but it works for pasting with more pdf viewers (at least +% evince), the lilypond developers report. xpdf does work with the +% regular 0x27. +% +\def\codequoteright{% + \expandafter\ifx\csname SETtxicodequoteundirected\endcsname\relax + \expandafter\ifx\csname SETcodequoteundirected\endcsname\relax + '% + \else \char'15 \fi + \else \char'15 \fi +} +% +% and a similar option for the left quote char vs. a grave accent. +% Modern fonts display ASCII 0x60 as a grave accent, so some people like +% the code environments to do likewise. +% +\def\codequoteleft{% + \expandafter\ifx\csname SETtxicodequotebacktick\endcsname\relax + \expandafter\ifx\csname SETcodequotebacktick\endcsname\relax + `% + \else \char'22 \fi + \else \char'22 \fi +} +% +\begingroup + \catcode`\^^I=\active + \gdef\tabexpand{% + \catcode`\^^I=\active + \def^^I{\leavevmode\egroup + \dimen0=\wd0 % the width so far, or since the previous tab + \divide\dimen0 by\tabw + \multiply\dimen0 by\tabw % compute previous multiple of \tabw + \advance\dimen0 by\tabw % advance to next multiple of \tabw + \wd0=\dimen0 \box0 \starttabbox + }% + } + \catcode`\'=\active + \gdef\rquoteexpand{\catcode\rquoteChar=\active \def'{\codequoteright}}% + % + \catcode`\`=\active + \gdef\lquoteexpand{\catcode\lquoteChar=\active \def`{\codequoteleft}}% + % + \gdef\quoteexpand{\rquoteexpand \lquoteexpand}% +\endgroup + +% start the verbatim environment. +\def\setupverbatim{% + \let\nonarrowing = t% + \nonfillstart + % Easiest (and conventionally used) font for verbatim + \tt + \def\par{\leavevmode\egroup\box0\endgraf}% + \catcode`\`=\active + \tabexpand + \quoteexpand + % Respect line breaks, + % print special symbols as themselves, and + % make each space count + % must do in this order: + \obeylines \uncatcodespecials \sepspaces + \everypar{\starttabbox}% +} + +% Do the @verb magic: verbatim text is quoted by unique +% delimiter characters. Before first delimiter expect a +% right brace, after last delimiter expect closing brace: +% +% \def\doverb'{'<char>#1<char>'}'{#1} +% +% [Knuth] p. 382; only eat outer {} +\begingroup + \catcode`[=1\catcode`]=2\catcode`\{=\other\catcode`\}=\other + \gdef\doverb{#1[\def\next##1#1}[##1\endgroup]\next] +\endgroup +% +\def\verb{\begingroup\setupverb\doverb} +% +% +% Do the @verbatim magic: define the macro \doverbatim so that +% the (first) argument ends when '@end verbatim' is reached, ie: +% +% \def\doverbatim#1@end verbatim{#1} +% +% For Texinfo it's a lot easier than for LaTeX, +% because texinfo's \verbatim doesn't stop at '\end{verbatim}': +% we need not redefine '\', '{' and '}'. +% +% Inspired by LaTeX's verbatim command set [latex.ltx] +% +\begingroup + \catcode`\ =\active + \obeylines % + % ignore everything up to the first ^^M, that's the newline at the end + % of the @verbatim input line itself. Otherwise we get an extra blank + % line in the output. + \xdef\doverbatim#1^^M#2@end verbatim{#2\noexpand\end\gobble verbatim}% + % We really want {...\end verbatim} in the body of the macro, but + % without the active space; thus we have to use \xdef and \gobble. +\endgroup +% +\envdef\verbatim{% + \setupverbatim\doverbatim +} +\let\Everbatim = \afterenvbreak + + +% @verbatiminclude FILE - insert text of file in verbatim environment. +% +\def\verbatiminclude{\parseargusing\filenamecatcodes\doverbatiminclude} +% +\def\doverbatiminclude#1{% + {% + \makevalueexpandable + \setupverbatim + \input #1 + \afterenvbreak + }% +} + +% @copying ... @end copying. +% Save the text away for @insertcopying later. +% +% We save the uninterpreted tokens, rather than creating a box. +% Saving the text in a box would be much easier, but then all the +% typesetting commands (@smallbook, font changes, etc.) have to be done +% beforehand -- and a) we want @copying to be done first in the source +% file; b) letting users define the frontmatter in as flexible order as +% possible is very desirable. +% +\def\copying{\checkenv{}\begingroup\scanargctxt\docopying} +\def\docopying#1@end copying{\endgroup\def\copyingtext{#1}} +% +\def\insertcopying{% + \begingroup + \parindent = 0pt % paragraph indentation looks wrong on title page + \scanexp\copyingtext + \endgroup +} + + +\message{defuns,} +% @defun etc. + +\newskip\defbodyindent \defbodyindent=.4in +\newskip\defargsindent \defargsindent=50pt +\newskip\deflastargmargin \deflastargmargin=18pt +\newcount\defunpenalty + +% Start the processing of @deffn: +\def\startdefun{% + \ifnum\lastpenalty<10000 + \medbreak + \defunpenalty=10003 % Will keep this @deffn together with the + % following @def command, see below. + \else + % If there are two @def commands in a row, we'll have a \nobreak, + % which is there to keep the function description together with its + % header. But if there's nothing but headers, we need to allow a + % break somewhere. Check specifically for penalty 10002, inserted + % by \printdefunline, instead of 10000, since the sectioning + % commands also insert a nobreak penalty, and we don't want to allow + % a break between a section heading and a defun. + % + % As a minor refinement, we avoid "club" headers by signalling + % with penalty of 10003 after the very first @deffn in the + % sequence (see above), and penalty of 10002 after any following + % @def command. + \ifnum\lastpenalty=10002 \penalty2000 \else \defunpenalty=10002 \fi + % + % Similarly, after a section heading, do not allow a break. + % But do insert the glue. + \medskip % preceded by discardable penalty, so not a breakpoint + \fi + % + \parindent=0in + \advance\leftskip by \defbodyindent + \exdentamount=\defbodyindent +} + +\def\dodefunx#1{% + % First, check whether we are in the right environment: + \checkenv#1% + % + % As above, allow line break if we have multiple x headers in a row. + % It's not a great place, though. + \ifnum\lastpenalty=10002 \penalty3000 \else \defunpenalty=10002 \fi + % + % And now, it's time to reuse the body of the original defun: + \expandafter\gobbledefun#1% +} +\def\gobbledefun#1\startdefun{} + +% \printdefunline \deffnheader{text} +% +\def\printdefunline#1#2{% + \begingroup + % call \deffnheader: + #1#2 \endheader + % common ending: + \interlinepenalty = 10000 + \advance\rightskip by 0pt plus 1fil + \endgraf + \nobreak\vskip -\parskip + \penalty\defunpenalty % signal to \startdefun and \dodefunx + % Some of the @defun-type tags do not enable magic parentheses, + % rendering the following check redundant. But we don't optimize. + \checkparencounts + \endgroup +} + +\def\Edefun{\endgraf\medbreak} + +% \makedefun{deffn} creates \deffn, \deffnx and \Edeffn; +% the only thing remainnig is to define \deffnheader. +% +\def\makedefun#1{% + \expandafter\let\csname E#1\endcsname = \Edefun + \edef\temp{\noexpand\domakedefun + \makecsname{#1}\makecsname{#1x}\makecsname{#1header}}% + \temp +} + +% \domakedefun \deffn \deffnx \deffnheader +% +% Define \deffn and \deffnx, without parameters. +% \deffnheader has to be defined explicitly. +% +\def\domakedefun#1#2#3{% + \envdef#1{% + \startdefun + \parseargusing\activeparens{\printdefunline#3}% + }% + \def#2{\dodefunx#1}% + \def#3% +} + +%%% Untyped functions: + +% @deffn category name args +\makedefun{deffn}{\deffngeneral{}} + +% @deffn category class name args +\makedefun{defop}#1 {\defopon{#1\ \putwordon}} + +% \defopon {category on}class name args +\def\defopon#1#2 {\deffngeneral{\putwordon\ \code{#2}}{#1\ \code{#2}} } + +% \deffngeneral {subind}category name args +% +\def\deffngeneral#1#2 #3 #4\endheader{% + % Remember that \dosubind{fn}{foo}{} is equivalent to \doind{fn}{foo}. + \dosubind{fn}{\code{#3}}{#1}% + \defname{#2}{}{#3}\magicamp\defunargs{#4\unskip}% +} + +%%% Typed functions: + +% @deftypefn category type name args +\makedefun{deftypefn}{\deftypefngeneral{}} + +% @deftypeop category class type name args +\makedefun{deftypeop}#1 {\deftypeopon{#1\ \putwordon}} + +% \deftypeopon {category on}class type name args +\def\deftypeopon#1#2 {\deftypefngeneral{\putwordon\ \code{#2}}{#1\ \code{#2}} } + +% \deftypefngeneral {subind}category type name args +% +\def\deftypefngeneral#1#2 #3 #4 #5\endheader{% + \dosubind{fn}{\code{#4}}{#1}% + \defname{#2}{#3}{#4}\defunargs{#5\unskip}% +} + +%%% Typed variables: + +% @deftypevr category type var args +\makedefun{deftypevr}{\deftypecvgeneral{}} + +% @deftypecv category class type var args +\makedefun{deftypecv}#1 {\deftypecvof{#1\ \putwordof}} + +% \deftypecvof {category of}class type var args +\def\deftypecvof#1#2 {\deftypecvgeneral{\putwordof\ \code{#2}}{#1\ \code{#2}} } + +% \deftypecvgeneral {subind}category type var args +% +\def\deftypecvgeneral#1#2 #3 #4 #5\endheader{% + \dosubind{vr}{\code{#4}}{#1}% + \defname{#2}{#3}{#4}\defunargs{#5\unskip}% +} + +%%% Untyped variables: + +% @defvr category var args +\makedefun{defvr}#1 {\deftypevrheader{#1} {} } + +% @defcv category class var args +\makedefun{defcv}#1 {\defcvof{#1\ \putwordof}} + +% \defcvof {category of}class var args +\def\defcvof#1#2 {\deftypecvof{#1}#2 {} } + +%%% Type: +% @deftp category name args +\makedefun{deftp}#1 #2 #3\endheader{% + \doind{tp}{\code{#2}}% + \defname{#1}{}{#2}\defunargs{#3\unskip}% +} + +% Remaining @defun-like shortcuts: +\makedefun{defun}{\deffnheader{\putwordDeffunc} } +\makedefun{defmac}{\deffnheader{\putwordDefmac} } +\makedefun{defspec}{\deffnheader{\putwordDefspec} } +\makedefun{deftypefun}{\deftypefnheader{\putwordDeffunc} } +\makedefun{defvar}{\defvrheader{\putwordDefvar} } +\makedefun{defopt}{\defvrheader{\putwordDefopt} } +\makedefun{deftypevar}{\deftypevrheader{\putwordDefvar} } +\makedefun{defmethod}{\defopon\putwordMethodon} +\makedefun{deftypemethod}{\deftypeopon\putwordMethodon} +\makedefun{defivar}{\defcvof\putwordInstanceVariableof} +\makedefun{deftypeivar}{\deftypecvof\putwordInstanceVariableof} + +% \defname, which formats the name of the @def (not the args). +% #1 is the category, such as "Function". +% #2 is the return type, if any. +% #3 is the function name. +% +% We are followed by (but not passed) the arguments, if any. +% +\def\defname#1#2#3{% + % Get the values of \leftskip and \rightskip as they were outside the @def... + \advance\leftskip by -\defbodyindent + % + % How we'll format the type name. Putting it in brackets helps + % distinguish it from the body text that may end up on the next line + % just below it. + \def\temp{#1}% + \setbox0=\hbox{\kern\deflastargmargin \ifx\temp\empty\else [\rm\temp]\fi} + % + % Figure out line sizes for the paragraph shape. + % The first line needs space for \box0; but if \rightskip is nonzero, + % we need only space for the part of \box0 which exceeds it: + \dimen0=\hsize \advance\dimen0 by -\wd0 \advance\dimen0 by \rightskip + % The continuations: + \dimen2=\hsize \advance\dimen2 by -\defargsindent + % (plain.tex says that \dimen1 should be used only as global.) + \parshape 2 0in \dimen0 \defargsindent \dimen2 + % + % Put the type name to the right margin. + \noindent + \hbox to 0pt{% + \hfil\box0 \kern-\hsize + % \hsize has to be shortened this way: + \kern\leftskip + % Intentionally do not respect \rightskip, since we need the space. + }% + % + % Allow all lines to be underfull without complaint: + \tolerance=10000 \hbadness=10000 + \exdentamount=\defbodyindent + {% + % defun fonts. We use typewriter by default (used to be bold) because: + % . we're printing identifiers, they should be in tt in principle. + % . in languages with many accents, such as Czech or French, it's + % common to leave accents off identifiers. The result looks ok in + % tt, but exceedingly strange in rm. + % . we don't want -- and --- to be treated as ligatures. + % . this still does not fix the ?` and !` ligatures, but so far no + % one has made identifiers using them :). + \df \tt + \def\temp{#2}% return value type + \ifx\temp\empty\else \tclose{\temp} \fi + #3% output function name + }% + {\rm\enskip}% hskip 0.5 em of \tenrm + % + \boldbrax + % arguments will be output next, if any. +} + +% Print arguments in slanted roman (not ttsl), inconsistently with using +% tt for the name. This is because literal text is sometimes needed in +% the argument list (groff manual), and ttsl and tt are not very +% distinguishable. Prevent hyphenation at `-' chars. +% +\def\defunargs#1{% + % use sl by default (not ttsl), + % tt for the names. + \df \sl \hyphenchar\font=0 + % + % On the other hand, if an argument has two dashes (for instance), we + % want a way to get ttsl. Let's try @var for that. + \let\var=\ttslanted + #1% + \sl\hyphenchar\font=45 +} + +% We want ()&[] to print specially on the defun line. +% +\def\activeparens{% + \catcode`\(=\active \catcode`\)=\active + \catcode`\[=\active \catcode`\]=\active + \catcode`\&=\active +} + +% Make control sequences which act like normal parenthesis chars. +\let\lparen = ( \let\rparen = ) + +% Be sure that we always have a definition for `(', etc. For example, +% if the fn name has parens in it, \boldbrax will not be in effect yet, +% so TeX would otherwise complain about undefined control sequence. +{ + \activeparens + \global\let(=\lparen \global\let)=\rparen + \global\let[=\lbrack \global\let]=\rbrack + \global\let& = \& + + \gdef\boldbrax{\let(=\opnr\let)=\clnr\let[=\lbrb\let]=\rbrb} + \gdef\magicamp{\let&=\amprm} +} + +\newcount\parencount + +% If we encounter &foo, then turn on ()-hacking afterwards +\newif\ifampseen +\def\amprm#1 {\ampseentrue{\bf\ }} + +\def\parenfont{% + \ifampseen + % At the first level, print parens in roman, + % otherwise use the default font. + \ifnum \parencount=1 \rm \fi + \else + % The \sf parens (in \boldbrax) actually are a little bolder than + % the contained text. This is especially needed for [ and ] . + \sf + \fi +} +\def\infirstlevel#1{% + \ifampseen + \ifnum\parencount=1 + #1% + \fi + \fi +} +\def\bfafterword#1 {#1 \bf} + +\def\opnr{% + \global\advance\parencount by 1 + {\parenfont(}% + \infirstlevel \bfafterword +} +\def\clnr{% + {\parenfont)}% + \infirstlevel \sl + \global\advance\parencount by -1 +} + +\newcount\brackcount +\def\lbrb{% + \global\advance\brackcount by 1 + {\bf[}% +} +\def\rbrb{% + {\bf]}% + \global\advance\brackcount by -1 +} + +\def\checkparencounts{% + \ifnum\parencount=0 \else \badparencount \fi + \ifnum\brackcount=0 \else \badbrackcount \fi +} +% these should not use \errmessage; the glibc manual, at least, actually +% has such constructs (when documenting function pointers). +\def\badparencount{% + \message{Warning: unbalanced parentheses in @def...}% + \global\parencount=0 +} +\def\badbrackcount{% + \message{Warning: unbalanced square brackets in @def...}% + \global\brackcount=0 +} + + +\message{macros,} +% @macro. + +% To do this right we need a feature of e-TeX, \scantokens, +% which we arrange to emulate with a temporary file in ordinary TeX. +\ifx\eTeXversion\undefined + \newwrite\macscribble + \def\scantokens#1{% + \toks0={#1}% + \immediate\openout\macscribble=\jobname.tmp + \immediate\write\macscribble{\the\toks0}% + \immediate\closeout\macscribble + \input \jobname.tmp + } +\fi + +\def\scanmacro#1{% + \begingroup + \newlinechar`\^^M + \let\xeatspaces\eatspaces + % Undo catcode changes of \startcontents and \doprintindex + % When called from @insertcopying or (short)caption, we need active + % backslash to get it printed correctly. Previously, we had + % \catcode`\\=\other instead. We'll see whether a problem appears + % with macro expansion. --kasal, 19aug04 + \catcode`\@=0 \catcode`\\=\active \escapechar=`\@ + % ... and \example + \spaceisspace + % + % Append \endinput to make sure that TeX does not see the ending newline. + % I've verified that it is necessary both for e-TeX and for ordinary TeX + % --kasal, 29nov03 + \scantokens{#1\endinput}% + \endgroup +} + +\def\scanexp#1{% + \edef\temp{\noexpand\scanmacro{#1}}% + \temp +} + +\newcount\paramno % Count of parameters +\newtoks\macname % Macro name +\newif\ifrecursive % Is it recursive? + +% List of all defined macros in the form +% \definedummyword\macro1\definedummyword\macro2... +% Currently is also contains all @aliases; the list can be split +% if there is a need. +\def\macrolist{} + +% Add the macro to \macrolist +\def\addtomacrolist#1{\expandafter \addtomacrolistxxx \csname#1\endcsname} +\def\addtomacrolistxxx#1{% + \toks0 = \expandafter{\macrolist\definedummyword#1}% + \xdef\macrolist{\the\toks0}% +} + +% Utility routines. +% This does \let #1 = #2, with \csnames; that is, +% \let \csname#1\endcsname = \csname#2\endcsname +% (except of course we have to play expansion games). +% +\def\cslet#1#2{% + \expandafter\let + \csname#1\expandafter\endcsname + \csname#2\endcsname +} + +% Trim leading and trailing spaces off a string. +% Concepts from aro-bend problem 15 (see CTAN). +{\catcode`\@=11 +\gdef\eatspaces #1{\expandafter\trim@\expandafter{#1 }} +\gdef\trim@ #1{\trim@@ @#1 @ #1 @ @@} +\gdef\trim@@ #1@ #2@ #3@@{\trim@@@\empty #2 @} +\def\unbrace#1{#1} +\unbrace{\gdef\trim@@@ #1 } #2@{#1} +} + +% Trim a single trailing ^^M off a string. +{\catcode`\^^M=\other \catcode`\Q=3% +\gdef\eatcr #1{\eatcra #1Q^^MQ}% +\gdef\eatcra#1^^MQ{\eatcrb#1Q}% +\gdef\eatcrb#1Q#2Q{#1}% +} + +% Macro bodies are absorbed as an argument in a context where +% all characters are catcode 10, 11 or 12, except \ which is active +% (as in normal texinfo). It is necessary to change the definition of \. + +% Non-ASCII encodings make 8-bit characters active, so un-activate +% them to avoid their expansion. Must do this non-globally, to +% confine the change to the current group. + +% It's necessary to have hard CRs when the macro is executed. This is +% done by making ^^M (\endlinechar) catcode 12 when reading the macro +% body, and then making it the \newlinechar in \scanmacro. + +\def\scanctxt{% + \catcode`\"=\other + \catcode`\+=\other + \catcode`\<=\other + \catcode`\>=\other + \catcode`\@=\other + \catcode`\^=\other + \catcode`\_=\other + \catcode`\|=\other + \catcode`\~=\other + \ifx\declaredencoding\ascii \else \setnonasciicharscatcodenonglobal\other \fi +} + +\def\scanargctxt{% + \scanctxt + \catcode`\\=\other + \catcode`\^^M=\other +} + +\def\macrobodyctxt{% + \scanctxt + \catcode`\{=\other + \catcode`\}=\other + \catcode`\^^M=\other + \usembodybackslash +} + +\def\macroargctxt{% + \scanctxt + \catcode`\\=\other +} + +% \mbodybackslash is the definition of \ in @macro bodies. +% It maps \foo\ => \csname macarg.foo\endcsname => #N +% where N is the macro parameter number. +% We define \csname macarg.\endcsname to be \realbackslash, so +% \\ in macro replacement text gets you a backslash. + +{\catcode`@=0 @catcode`@\=@active + @gdef@usembodybackslash{@let\=@mbodybackslash} + @gdef@mbodybackslash#1\{@csname macarg.#1@endcsname} +} +\expandafter\def\csname macarg.\endcsname{\realbackslash} + +\def\macro{\recursivefalse\parsearg\macroxxx} +\def\rmacro{\recursivetrue\parsearg\macroxxx} + +\def\macroxxx#1{% + \getargs{#1}% now \macname is the macname and \argl the arglist + \ifx\argl\empty % no arguments + \paramno=0% + \else + \expandafter\parsemargdef \argl;% + \fi + \if1\csname ismacro.\the\macname\endcsname + \message{Warning: redefining \the\macname}% + \else + \expandafter\ifx\csname \the\macname\endcsname \relax + \else \errmessage{Macro name \the\macname\space already defined}\fi + \global\cslet{macsave.\the\macname}{\the\macname}% + \global\expandafter\let\csname ismacro.\the\macname\endcsname=1% + \addtomacrolist{\the\macname}% + \fi + \begingroup \macrobodyctxt + \ifrecursive \expandafter\parsermacbody + \else \expandafter\parsemacbody + \fi} + +\parseargdef\unmacro{% + \if1\csname ismacro.#1\endcsname + \global\cslet{#1}{macsave.#1}% + \global\expandafter\let \csname ismacro.#1\endcsname=0% + % Remove the macro name from \macrolist: + \begingroup + \expandafter\let\csname#1\endcsname \relax + \let\definedummyword\unmacrodo + \xdef\macrolist{\macrolist}% + \endgroup + \else + \errmessage{Macro #1 not defined}% + \fi +} + +% Called by \do from \dounmacro on each macro. The idea is to omit any +% macro definitions that have been changed to \relax. +% +\def\unmacrodo#1{% + \ifx #1\relax + % remove this + \else + \noexpand\definedummyword \noexpand#1% + \fi +} + +% This makes use of the obscure feature that if the last token of a +% <parameter list> is #, then the preceding argument is delimited by +% an opening brace, and that opening brace is not consumed. +\def\getargs#1{\getargsxxx#1{}} +\def\getargsxxx#1#{\getmacname #1 \relax\getmacargs} +\def\getmacname #1 #2\relax{\macname={#1}} +\def\getmacargs#1{\def\argl{#1}} + +% Parse the optional {params} list. Set up \paramno and \paramlist +% so \defmacro knows what to do. Define \macarg.blah for each blah +% in the params list, to be ##N where N is the position in that list. +% That gets used by \mbodybackslash (above). + +% We need to get `macro parameter char #' into several definitions. +% The technique used is stolen from LaTeX: let \hash be something +% unexpandable, insert that wherever you need a #, and then redefine +% it to # just before using the token list produced. +% +% The same technique is used to protect \eatspaces till just before +% the macro is used. + +\def\parsemargdef#1;{\paramno=0\def\paramlist{}% + \let\hash\relax\let\xeatspaces\relax\parsemargdefxxx#1,;,} +\def\parsemargdefxxx#1,{% + \if#1;\let\next=\relax + \else \let\next=\parsemargdefxxx + \advance\paramno by 1% + \expandafter\edef\csname macarg.\eatspaces{#1}\endcsname + {\xeatspaces{\hash\the\paramno}}% + \edef\paramlist{\paramlist\hash\the\paramno,}% + \fi\next} + +% These two commands read recursive and nonrecursive macro bodies. +% (They're different since rec and nonrec macros end differently.) + +\long\def\parsemacbody#1@end macro% +{\xdef\temp{\eatcr{#1}}\endgroup\defmacro}% +\long\def\parsermacbody#1@end rmacro% +{\xdef\temp{\eatcr{#1}}\endgroup\defmacro}% + +% This defines the macro itself. There are six cases: recursive and +% nonrecursive macros of zero, one, and many arguments. +% Much magic with \expandafter here. +% \xdef is used so that macro definitions will survive the file +% they're defined in; @include reads the file inside a group. +\def\defmacro{% + \let\hash=##% convert placeholders to macro parameter chars + \ifrecursive + \ifcase\paramno + % 0 + \expandafter\xdef\csname\the\macname\endcsname{% + \noexpand\scanmacro{\temp}}% + \or % 1 + \expandafter\xdef\csname\the\macname\endcsname{% + \bgroup\noexpand\macroargctxt + \noexpand\braceorline + \expandafter\noexpand\csname\the\macname xxx\endcsname}% + \expandafter\xdef\csname\the\macname xxx\endcsname##1{% + \egroup\noexpand\scanmacro{\temp}}% + \else % many + \expandafter\xdef\csname\the\macname\endcsname{% + \bgroup\noexpand\macroargctxt + \noexpand\csname\the\macname xx\endcsname}% + \expandafter\xdef\csname\the\macname xx\endcsname##1{% + \expandafter\noexpand\csname\the\macname xxx\endcsname ##1,}% + \expandafter\expandafter + \expandafter\xdef + \expandafter\expandafter + \csname\the\macname xxx\endcsname + \paramlist{\egroup\noexpand\scanmacro{\temp}}% + \fi + \else + \ifcase\paramno + % 0 + \expandafter\xdef\csname\the\macname\endcsname{% + \noexpand\norecurse{\the\macname}% + \noexpand\scanmacro{\temp}\egroup}% + \or % 1 + \expandafter\xdef\csname\the\macname\endcsname{% + \bgroup\noexpand\macroargctxt + \noexpand\braceorline + \expandafter\noexpand\csname\the\macname xxx\endcsname}% + \expandafter\xdef\csname\the\macname xxx\endcsname##1{% + \egroup + \noexpand\norecurse{\the\macname}% + \noexpand\scanmacro{\temp}\egroup}% + \else % many + \expandafter\xdef\csname\the\macname\endcsname{% + \bgroup\noexpand\macroargctxt + \expandafter\noexpand\csname\the\macname xx\endcsname}% + \expandafter\xdef\csname\the\macname xx\endcsname##1{% + \expandafter\noexpand\csname\the\macname xxx\endcsname ##1,}% + \expandafter\expandafter + \expandafter\xdef + \expandafter\expandafter + \csname\the\macname xxx\endcsname + \paramlist{% + \egroup + \noexpand\norecurse{\the\macname}% + \noexpand\scanmacro{\temp}\egroup}% + \fi + \fi} + +\def\norecurse#1{\bgroup\cslet{#1}{macsave.#1}} + +% \braceorline decides whether the next nonwhitespace character is a +% {. If so it reads up to the closing }, if not, it reads the whole +% line. Whatever was read is then fed to the next control sequence +% as an argument (by \parsebrace or \parsearg) +\def\braceorline#1{\let\macnamexxx=#1\futurelet\nchar\braceorlinexxx} +\def\braceorlinexxx{% + \ifx\nchar\bgroup\else + \expandafter\parsearg + \fi \macnamexxx} + + +% @alias. +% We need some trickery to remove the optional spaces around the equal +% sign. Just make them active and then expand them all to nothing. +\def\alias{\parseargusing\obeyspaces\aliasxxx} +\def\aliasxxx #1{\aliasyyy#1\relax} +\def\aliasyyy #1=#2\relax{% + {% + \expandafter\let\obeyedspace=\empty + \addtomacrolist{#1}% + \xdef\next{\global\let\makecsname{#1}=\makecsname{#2}}% + }% + \next +} + + +\message{cross references,} + +\newwrite\auxfile +\newif\ifhavexrefs % True if xref values are known. +\newif\ifwarnedxrefs % True if we warned once that they aren't known. + +% @inforef is relatively simple. +\def\inforef #1{\inforefzzz #1,,,,**} +\def\inforefzzz #1,#2,#3,#4**{\putwordSee{} \putwordInfo{} \putwordfile{} \file{\ignorespaces #3{}}, + node \samp{\ignorespaces#1{}}} + +% @node's only job in TeX is to define \lastnode, which is used in +% cross-references. The @node line might or might not have commas, and +% might or might not have spaces before the first comma, like: +% @node foo , bar , ... +% We don't want such trailing spaces in the node name. +% +\parseargdef\node{\checkenv{}\donode #1 ,\finishnodeparse} +% +% also remove a trailing comma, in case of something like this: +% @node Help-Cross, , , Cross-refs +\def\donode#1 ,#2\finishnodeparse{\dodonode #1,\finishnodeparse} +\def\dodonode#1,#2\finishnodeparse{\gdef\lastnode{#1}} + +\let\nwnode=\node +\let\lastnode=\empty + +% Write a cross-reference definition for the current node. #1 is the +% type (Ynumbered, Yappendix, Ynothing). +% +\def\donoderef#1{% + \ifx\lastnode\empty\else + \setref{\lastnode}{#1}% + \global\let\lastnode=\empty + \fi +} + +% @anchor{NAME} -- define xref target at arbitrary point. +% +\newcount\savesfregister +% +\def\savesf{\relax \ifhmode \savesfregister=\spacefactor \fi} +\def\restoresf{\relax \ifhmode \spacefactor=\savesfregister \fi} +\def\anchor#1{\savesf \setref{#1}{Ynothing}\restoresf \ignorespaces} + +% \setref{NAME}{SNT} defines a cross-reference point NAME (a node or an +% anchor), which consists of three parts: +% 1) NAME-title - the current sectioning name taken from \lastsection, +% or the anchor name. +% 2) NAME-snt - section number and type, passed as the SNT arg, or +% empty for anchors. +% 3) NAME-pg - the page number. +% +% This is called from \donoderef, \anchor, and \dofloat. In the case of +% floats, there is an additional part, which is not written here: +% 4) NAME-lof - the text as it should appear in a @listoffloats. +% +\def\setref#1#2{% + \pdfmkdest{#1}% + \iflinks + {% + \atdummies % preserve commands, but don't expand them + \edef\writexrdef##1##2{% + \write\auxfile{@xrdef{#1-% #1 of \setref, expanded by the \edef + ##1}{##2}}% these are parameters of \writexrdef + }% + \toks0 = \expandafter{\lastsection}% + \immediate \writexrdef{title}{\the\toks0 }% + \immediate \writexrdef{snt}{\csname #2\endcsname}% \Ynumbered etc. + \safewhatsit{\writexrdef{pg}{\folio}}% will be written later, during \shipout + }% + \fi +} + +% @xref, @pxref, and @ref generate cross-references. For \xrefX, #1 is +% the node name, #2 the name of the Info cross-reference, #3 the printed +% node name, #4 the name of the Info file, #5 the name of the printed +% manual. All but the node name can be omitted. +% +\def\pxref#1{\putwordsee{} \xrefX[#1,,,,,,,]} +\def\xref#1{\putwordSee{} \xrefX[#1,,,,,,,]} +\def\ref#1{\xrefX[#1,,,,,,,]} +\def\xrefX[#1,#2,#3,#4,#5,#6]{\begingroup + \unsepspaces + \def\printedmanual{\ignorespaces #5}% + \def\printedrefname{\ignorespaces #3}% + \setbox1=\hbox{\printedmanual\unskip}% + \setbox0=\hbox{\printedrefname\unskip}% + \ifdim \wd0 = 0pt + % No printed node name was explicitly given. + \expandafter\ifx\csname SETxref-automatic-section-title\endcsname\relax + % Use the node name inside the square brackets. + \def\printedrefname{\ignorespaces #1}% + \else + % Use the actual chapter/section title appear inside + % the square brackets. Use the real section title if we have it. + \ifdim \wd1 > 0pt + % It is in another manual, so we don't have it. + \def\printedrefname{\ignorespaces #1}% + \else + \ifhavexrefs + % We know the real title if we have the xref values. + \def\printedrefname{\refx{#1-title}{}}% + \else + % Otherwise just copy the Info node name. + \def\printedrefname{\ignorespaces #1}% + \fi% + \fi + \fi + \fi + % + % Make link in pdf output. + \ifpdf + \leavevmode + \getfilename{#4}% + {\indexnofonts + \turnoffactive + % See comments at \activebackslashdouble. + {\activebackslashdouble \xdef\pdfxrefdest{#1}% + \backslashparens\pdfxrefdest}% + % + \ifnum\filenamelength>0 + \startlink attr{/Border [0 0 0]}% + goto file{\the\filename.pdf} name{\pdfxrefdest}% + \else + \startlink attr{/Border [0 0 0]}% + goto name{\pdfmkpgn{\pdfxrefdest}}% + \fi + }% + \setcolor{\linkcolor}% + \fi + % + % Float references are printed completely differently: "Figure 1.2" + % instead of "[somenode], p.3". We distinguish them by the + % LABEL-title being set to a magic string. + {% + % Have to otherify everything special to allow the \csname to + % include an _ in the xref name, etc. + \indexnofonts + \turnoffactive + \expandafter\global\expandafter\let\expandafter\Xthisreftitle + \csname XR#1-title\endcsname + }% + \iffloat\Xthisreftitle + % If the user specified the print name (third arg) to the ref, + % print it instead of our usual "Figure 1.2". + \ifdim\wd0 = 0pt + \refx{#1-snt}{}% + \else + \printedrefname + \fi + % + % if the user also gave the printed manual name (fifth arg), append + % "in MANUALNAME". + \ifdim \wd1 > 0pt + \space \putwordin{} \cite{\printedmanual}% + \fi + \else + % node/anchor (non-float) references. + % + % If we use \unhbox0 and \unhbox1 to print the node names, TeX does not + % insert empty discretionaries after hyphens, which means that it will + % not find a line break at a hyphen in a node names. Since some manuals + % are best written with fairly long node names, containing hyphens, this + % is a loss. Therefore, we give the text of the node name again, so it + % is as if TeX is seeing it for the first time. + \ifdim \wd1 > 0pt + \putwordSection{} ``\printedrefname'' \putwordin{} \cite{\printedmanual}% + \else + % _ (for example) has to be the character _ for the purposes of the + % control sequence corresponding to the node, but it has to expand + % into the usual \leavevmode...\vrule stuff for purposes of + % printing. So we \turnoffactive for the \refx-snt, back on for the + % printing, back off for the \refx-pg. + {\turnoffactive + % Only output a following space if the -snt ref is nonempty; for + % @unnumbered and @anchor, it won't be. + \setbox2 = \hbox{\ignorespaces \refx{#1-snt}{}}% + \ifdim \wd2 > 0pt \refx{#1-snt}\space\fi + }% + % output the `[mynode]' via a macro so it can be overridden. + \xrefprintnodename\printedrefname + % + % But we always want a comma and a space: + ,\space + % + % output the `page 3'. + \turnoffactive \putwordpage\tie\refx{#1-pg}{}% + \fi + \fi + \endlink +\endgroup} + +% This macro is called from \xrefX for the `[nodename]' part of xref +% output. It's a separate macro only so it can be changed more easily, +% since square brackets don't work well in some documents. Particularly +% one that Bob is working on :). +% +\def\xrefprintnodename#1{[#1]} + +% Things referred to by \setref. +% +\def\Ynothing{} +\def\Yomitfromtoc{} +\def\Ynumbered{% + \ifnum\secno=0 + \putwordChapter@tie \the\chapno + \else \ifnum\subsecno=0 + \putwordSection@tie \the\chapno.\the\secno + \else \ifnum\subsubsecno=0 + \putwordSection@tie \the\chapno.\the\secno.\the\subsecno + \else + \putwordSection@tie \the\chapno.\the\secno.\the\subsecno.\the\subsubsecno + \fi\fi\fi +} +\def\Yappendix{% + \ifnum\secno=0 + \putwordAppendix@tie @char\the\appendixno{}% + \else \ifnum\subsecno=0 + \putwordSection@tie @char\the\appendixno.\the\secno + \else \ifnum\subsubsecno=0 + \putwordSection@tie @char\the\appendixno.\the\secno.\the\subsecno + \else + \putwordSection@tie + @char\the\appendixno.\the\secno.\the\subsecno.\the\subsubsecno + \fi\fi\fi +} + +% Define \refx{NAME}{SUFFIX} to reference a cross-reference string named NAME. +% If its value is nonempty, SUFFIX is output afterward. +% +\def\refx#1#2{% + {% + \indexnofonts + \otherbackslash + \expandafter\global\expandafter\let\expandafter\thisrefX + \csname XR#1\endcsname + }% + \ifx\thisrefX\relax + % If not defined, say something at least. + \angleleft un\-de\-fined\angleright + \iflinks + \ifhavexrefs + \message{\linenumber Undefined cross reference `#1'.}% + \else + \ifwarnedxrefs\else + \global\warnedxrefstrue + \message{Cross reference values unknown; you must run TeX again.}% + \fi + \fi + \fi + \else + % It's defined, so just use it. + \thisrefX + \fi + #2% Output the suffix in any case. +} + +% This is the macro invoked by entries in the aux file. Usually it's +% just a \def (we prepend XR to the control sequence name to avoid +% collisions). But if this is a float type, we have more work to do. +% +\def\xrdef#1#2{% + {% The node name might contain 8-bit characters, which in our current + % implementation are changed to commands like @'e. Don't let these + % mess up the control sequence name. + \indexnofonts + \turnoffactive + \xdef\safexrefname{#1}% + }% + % + \expandafter\gdef\csname XR\safexrefname\endcsname{#2}% remember this xref + % + % Was that xref control sequence that we just defined for a float? + \expandafter\iffloat\csname XR\safexrefname\endcsname + % it was a float, and we have the (safe) float type in \iffloattype. + \expandafter\let\expandafter\floatlist + \csname floatlist\iffloattype\endcsname + % + % Is this the first time we've seen this float type? + \expandafter\ifx\floatlist\relax + \toks0 = {\do}% yes, so just \do + \else + % had it before, so preserve previous elements in list. + \toks0 = \expandafter{\floatlist\do}% + \fi + % + % Remember this xref in the control sequence \floatlistFLOATTYPE, + % for later use in \listoffloats. + \expandafter\xdef\csname floatlist\iffloattype\endcsname{\the\toks0 + {\safexrefname}}% + \fi +} + +% Read the last existing aux file, if any. No error if none exists. +% +\def\tryauxfile{% + \openin 1 \jobname.aux + \ifeof 1 \else + \readdatafile{aux}% + \global\havexrefstrue + \fi + \closein 1 +} + +\def\setupdatafile{% + \catcode`\^^@=\other + \catcode`\^^A=\other + \catcode`\^^B=\other + \catcode`\^^C=\other + \catcode`\^^D=\other + \catcode`\^^E=\other + \catcode`\^^F=\other + \catcode`\^^G=\other + \catcode`\^^H=\other + \catcode`\^^K=\other + \catcode`\^^L=\other + \catcode`\^^N=\other + \catcode`\^^P=\other + \catcode`\^^Q=\other + \catcode`\^^R=\other + \catcode`\^^S=\other + \catcode`\^^T=\other + \catcode`\^^U=\other + \catcode`\^^V=\other + \catcode`\^^W=\other + \catcode`\^^X=\other + \catcode`\^^Z=\other + \catcode`\^^[=\other + \catcode`\^^\=\other + \catcode`\^^]=\other + \catcode`\^^^=\other + \catcode`\^^_=\other + % It was suggested to set the catcode of ^ to 7, which would allow ^^e4 etc. + % in xref tags, i.e., node names. But since ^^e4 notation isn't + % supported in the main text, it doesn't seem desirable. Furthermore, + % that is not enough: for node names that actually contain a ^ + % character, we would end up writing a line like this: 'xrdef {'hat + % b-title}{'hat b} and \xrdef does a \csname...\endcsname on the first + % argument, and \hat is not an expandable control sequence. It could + % all be worked out, but why? Either we support ^^ or we don't. + % + % The other change necessary for this was to define \auxhat: + % \def\auxhat{\def^{'hat }}% extra space so ok if followed by letter + % and then to call \auxhat in \setq. + % + \catcode`\^=\other + % + % Special characters. Should be turned off anyway, but... + \catcode`\~=\other + \catcode`\[=\other + \catcode`\]=\other + \catcode`\"=\other + \catcode`\_=\other + \catcode`\|=\other + \catcode`\<=\other + \catcode`\>=\other + \catcode`\$=\other + \catcode`\#=\other + \catcode`\&=\other + \catcode`\%=\other + \catcode`+=\other % avoid \+ for paranoia even though we've turned it off + % + % This is to support \ in node names and titles, since the \ + % characters end up in a \csname. It's easier than + % leaving it active and making its active definition an actual \ + % character. What I don't understand is why it works in the *value* + % of the xrdef. Seems like it should be a catcode12 \, and that + % should not typeset properly. But it works, so I'm moving on for + % now. --karl, 15jan04. + \catcode`\\=\other + % + % Make the characters 128-255 be printing characters. + {% + \count1=128 + \def\loop{% + \catcode\count1=\other + \advance\count1 by 1 + \ifnum \count1<256 \loop \fi + }% + }% + % + % @ is our escape character in .aux files, and we need braces. + \catcode`\{=1 + \catcode`\}=2 + \catcode`\@=0 +} + +\def\readdatafile#1{% +\begingroup + \setupdatafile + \input\jobname.#1 +\endgroup} + + +\message{insertions,} +% including footnotes. + +\newcount \footnoteno + +% The trailing space in the following definition for supereject is +% vital for proper filling; pages come out unaligned when you do a +% pagealignmacro call if that space before the closing brace is +% removed. (Generally, numeric constants should always be followed by a +% space to prevent strange expansion errors.) +\def\supereject{\par\penalty -20000\footnoteno =0 } + +% @footnotestyle is meaningful for info output only. +\let\footnotestyle=\comment + +{\catcode `\@=11 +% +% Auto-number footnotes. Otherwise like plain. +\gdef\footnote{% + \let\indent=\ptexindent + \let\noindent=\ptexnoindent + \global\advance\footnoteno by \@ne + \edef\thisfootno{$^{\the\footnoteno}$}% + % + % In case the footnote comes at the end of a sentence, preserve the + % extra spacing after we do the footnote number. + \let\@sf\empty + \ifhmode\edef\@sf{\spacefactor\the\spacefactor}\ptexslash\fi + % + % Remove inadvertent blank space before typesetting the footnote number. + \unskip + \thisfootno\@sf + \dofootnote +}% + +% Don't bother with the trickery in plain.tex to not require the +% footnote text as a parameter. Our footnotes don't need to be so general. +% +% Oh yes, they do; otherwise, @ifset (and anything else that uses +% \parseargline) fails inside footnotes because the tokens are fixed when +% the footnote is read. --karl, 16nov96. +% +\gdef\dofootnote{% + \insert\footins\bgroup + % We want to typeset this text as a normal paragraph, even if the + % footnote reference occurs in (for example) a display environment. + % So reset some parameters. + \hsize=\pagewidth + \interlinepenalty\interfootnotelinepenalty + \splittopskip\ht\strutbox % top baseline for broken footnotes + \splitmaxdepth\dp\strutbox + \floatingpenalty\@MM + \leftskip\z@skip + \rightskip\z@skip + \spaceskip\z@skip + \xspaceskip\z@skip + \parindent\defaultparindent + % + \smallfonts \rm + % + % Because we use hanging indentation in footnotes, a @noindent appears + % to exdent this text, so make it be a no-op. makeinfo does not use + % hanging indentation so @noindent can still be needed within footnote + % text after an @example or the like (not that this is good style). + \let\noindent = \relax + % + % Hang the footnote text off the number. Use \everypar in case the + % footnote extends for more than one paragraph. + \everypar = {\hang}% + \textindent{\thisfootno}% + % + % Don't crash into the line above the footnote text. Since this + % expands into a box, it must come within the paragraph, lest it + % provide a place where TeX can split the footnote. + \footstrut + \futurelet\next\fo@t +} +}%end \catcode `\@=11 + +% In case a @footnote appears in a vbox, save the footnote text and create +% the real \insert just after the vbox finished. Otherwise, the insertion +% would be lost. +% Similarly, if a @footnote appears inside an alignment, save the footnote +% text to a box and make the \insert when a row of the table is finished. +% And the same can be done for other insert classes. --kasal, 16nov03. + +% Replace the \insert primitive by a cheating macro. +% Deeper inside, just make sure that the saved insertions are not spilled +% out prematurely. +% +\def\startsavinginserts{% + \ifx \insert\ptexinsert + \let\insert\saveinsert + \else + \let\checkinserts\relax + \fi +} + +% This \insert replacement works for both \insert\footins{foo} and +% \insert\footins\bgroup foo\egroup, but it doesn't work for \insert27{foo}. +% +\def\saveinsert#1{% + \edef\next{\noexpand\savetobox \makeSAVEname#1}% + \afterassignment\next + % swallow the left brace + \let\temp = +} +\def\makeSAVEname#1{\makecsname{SAVE\expandafter\gobble\string#1}} +\def\savetobox#1{\global\setbox#1 = \vbox\bgroup \unvbox#1} + +\def\checksaveins#1{\ifvoid#1\else \placesaveins#1\fi} + +\def\placesaveins#1{% + \ptexinsert \csname\expandafter\gobblesave\string#1\endcsname + {\box#1}% +} + +% eat @SAVE -- beware, all of them have catcode \other: +{ + \def\dospecials{\do S\do A\do V\do E} \uncatcodespecials % ;-) + \gdef\gobblesave @SAVE{} +} + +% initialization: +\def\newsaveins #1{% + \edef\next{\noexpand\newsaveinsX \makeSAVEname#1}% + \next +} +\def\newsaveinsX #1{% + \csname newbox\endcsname #1% + \expandafter\def\expandafter\checkinserts\expandafter{\checkinserts + \checksaveins #1}% +} + +% initialize: +\let\checkinserts\empty +\newsaveins\footins +\newsaveins\margin + + +% @image. We use the macros from epsf.tex to support this. +% If epsf.tex is not installed and @image is used, we complain. +% +% Check for and read epsf.tex up front. If we read it only at @image +% time, we might be inside a group, and then its definitions would get +% undone and the next image would fail. +\openin 1 = epsf.tex +\ifeof 1 \else + % Do not bother showing banner with epsf.tex v2.7k (available in + % doc/epsf.tex and on ctan). + \def\epsfannounce{\toks0 = }% + \input epsf.tex +\fi +\closein 1 +% +% We will only complain once about lack of epsf.tex. +\newif\ifwarnednoepsf +\newhelp\noepsfhelp{epsf.tex must be installed for images to + work. It is also included in the Texinfo distribution, or you can get + it from ftp://tug.org/tex/epsf.tex.} +% +\def\image#1{% + \ifx\epsfbox\undefined + \ifwarnednoepsf \else + \errhelp = \noepsfhelp + \errmessage{epsf.tex not found, images will be ignored}% + \global\warnednoepsftrue + \fi + \else + \imagexxx #1,,,,,\finish + \fi +} +% +% Arguments to @image: +% #1 is (mandatory) image filename; we tack on .eps extension. +% #2 is (optional) width, #3 is (optional) height. +% #4 is (ignored optional) html alt text. +% #5 is (ignored optional) extension. +% #6 is just the usual extra ignored arg for parsing this stuff. +\newif\ifimagevmode +\def\imagexxx#1,#2,#3,#4,#5,#6\finish{\begingroup + \catcode`\^^M = 5 % in case we're inside an example + \normalturnoffactive % allow _ et al. in names + % If the image is by itself, center it. + \ifvmode + \imagevmodetrue + \nobreak\bigskip + % Usually we'll have text after the image which will insert + % \parskip glue, so insert it here too to equalize the space + % above and below. + \nobreak\vskip\parskip + \nobreak + \line\bgroup + \fi + % + % Output the image. + \ifpdf + \dopdfimage{#1}{#2}{#3}% + \else + % \epsfbox itself resets \epsf?size at each figure. + \setbox0 = \hbox{\ignorespaces #2}\ifdim\wd0 > 0pt \epsfxsize=#2\relax \fi + \setbox0 = \hbox{\ignorespaces #3}\ifdim\wd0 > 0pt \epsfysize=#3\relax \fi + \epsfbox{#1.eps}% + \fi + % + \ifimagevmode \egroup \bigbreak \fi % space after the image +\endgroup} + + +% @float FLOATTYPE,LABEL,LOC ... @end float for displayed figures, tables, +% etc. We don't actually implement floating yet, we always include the +% float "here". But it seemed the best name for the future. +% +\envparseargdef\float{\eatcommaspace\eatcommaspace\dofloat#1, , ,\finish} + +% There may be a space before second and/or third parameter; delete it. +\def\eatcommaspace#1, {#1,} + +% #1 is the optional FLOATTYPE, the text label for this float, typically +% "Figure", "Table", "Example", etc. Can't contain commas. If omitted, +% this float will not be numbered and cannot be referred to. +% +% #2 is the optional xref label. Also must be present for the float to +% be referable. +% +% #3 is the optional positioning argument; for now, it is ignored. It +% will somehow specify the positions allowed to float to (here, top, bottom). +% +% We keep a separate counter for each FLOATTYPE, which we reset at each +% chapter-level command. +\let\resetallfloatnos=\empty +% +\def\dofloat#1,#2,#3,#4\finish{% + \let\thiscaption=\empty + \let\thisshortcaption=\empty + % + % don't lose footnotes inside @float. + % + % BEWARE: when the floats start float, we have to issue warning whenever an + % insert appears inside a float which could possibly float. --kasal, 26may04 + % + \startsavinginserts + % + % We can't be used inside a paragraph. + \par + % + \vtop\bgroup + \def\floattype{#1}% + \def\floatlabel{#2}% + \def\floatloc{#3}% we do nothing with this yet. + % + \ifx\floattype\empty + \let\safefloattype=\empty + \else + {% + % the floattype might have accents or other special characters, + % but we need to use it in a control sequence name. + \indexnofonts + \turnoffactive + \xdef\safefloattype{\floattype}% + }% + \fi + % + % If label is given but no type, we handle that as the empty type. + \ifx\floatlabel\empty \else + % We want each FLOATTYPE to be numbered separately (Figure 1, + % Table 1, Figure 2, ...). (And if no label, no number.) + % + \expandafter\getfloatno\csname\safefloattype floatno\endcsname + \global\advance\floatno by 1 + % + {% + % This magic value for \lastsection is output by \setref as the + % XREFLABEL-title value. \xrefX uses it to distinguish float + % labels (which have a completely different output format) from + % node and anchor labels. And \xrdef uses it to construct the + % lists of floats. + % + \edef\lastsection{\floatmagic=\safefloattype}% + \setref{\floatlabel}{Yfloat}% + }% + \fi + % + % start with \parskip glue, I guess. + \vskip\parskip + % + % Don't suppress indentation if a float happens to start a section. + \restorefirstparagraphindent +} + +% we have these possibilities: +% @float Foo,lbl & @caption{Cap}: Foo 1.1: Cap +% @float Foo,lbl & no caption: Foo 1.1 +% @float Foo & @caption{Cap}: Foo: Cap +% @float Foo & no caption: Foo +% @float ,lbl & Caption{Cap}: 1.1: Cap +% @float ,lbl & no caption: 1.1 +% @float & @caption{Cap}: Cap +% @float & no caption: +% +\def\Efloat{% + \let\floatident = \empty + % + % In all cases, if we have a float type, it comes first. + \ifx\floattype\empty \else \def\floatident{\floattype}\fi + % + % If we have an xref label, the number comes next. + \ifx\floatlabel\empty \else + \ifx\floattype\empty \else % if also had float type, need tie first. + \appendtomacro\floatident{\tie}% + \fi + % the number. + \appendtomacro\floatident{\chaplevelprefix\the\floatno}% + \fi + % + % Start the printed caption with what we've constructed in + % \floatident, but keep it separate; we need \floatident again. + \let\captionline = \floatident + % + \ifx\thiscaption\empty \else + \ifx\floatident\empty \else + \appendtomacro\captionline{: }% had ident, so need a colon between + \fi + % + % caption text. + \appendtomacro\captionline{\scanexp\thiscaption}% + \fi + % + % If we have anything to print, print it, with space before. + % Eventually this needs to become an \insert. + \ifx\captionline\empty \else + \vskip.5\parskip + \captionline + % + % Space below caption. + \vskip\parskip + \fi + % + % If have an xref label, write the list of floats info. Do this + % after the caption, to avoid chance of it being a breakpoint. + \ifx\floatlabel\empty \else + % Write the text that goes in the lof to the aux file as + % \floatlabel-lof. Besides \floatident, we include the short + % caption if specified, else the full caption if specified, else nothing. + {% + \atdummies + % + % since we read the caption text in the macro world, where ^^M + % is turned into a normal character, we have to scan it back, so + % we don't write the literal three characters "^^M" into the aux file. + \scanexp{% + \xdef\noexpand\gtemp{% + \ifx\thisshortcaption\empty + \thiscaption + \else + \thisshortcaption + \fi + }% + }% + \immediate\write\auxfile{@xrdef{\floatlabel-lof}{\floatident + \ifx\gtemp\empty \else : \gtemp \fi}}% + }% + \fi + \egroup % end of \vtop + % + % place the captured inserts + % + % BEWARE: when the floats start floating, we have to issue warning + % whenever an insert appears inside a float which could possibly + % float. --kasal, 26may04 + % + \checkinserts +} + +% Append the tokens #2 to the definition of macro #1, not expanding either. +% +\def\appendtomacro#1#2{% + \expandafter\def\expandafter#1\expandafter{#1#2}% +} + +% @caption, @shortcaption +% +\def\caption{\docaption\thiscaption} +\def\shortcaption{\docaption\thisshortcaption} +\def\docaption{\checkenv\float \bgroup\scanargctxt\defcaption} +\def\defcaption#1#2{\egroup \def#1{#2}} + +% The parameter is the control sequence identifying the counter we are +% going to use. Create it if it doesn't exist and assign it to \floatno. +\def\getfloatno#1{% + \ifx#1\relax + % Haven't seen this figure type before. + \csname newcount\endcsname #1% + % + % Remember to reset this floatno at the next chap. + \expandafter\gdef\expandafter\resetallfloatnos + \expandafter{\resetallfloatnos #1=0 }% + \fi + \let\floatno#1% +} + +% \setref calls this to get the XREFLABEL-snt value. We want an @xref +% to the FLOATLABEL to expand to "Figure 3.1". We call \setref when we +% first read the @float command. +% +\def\Yfloat{\floattype@tie \chaplevelprefix\the\floatno}% + +% Magic string used for the XREFLABEL-title value, so \xrefX can +% distinguish floats from other xref types. +\def\floatmagic{!!float!!} + +% #1 is the control sequence we are passed; we expand into a conditional +% which is true if #1 represents a float ref. That is, the magic +% \lastsection value which we \setref above. +% +\def\iffloat#1{\expandafter\doiffloat#1==\finish} +% +% #1 is (maybe) the \floatmagic string. If so, #2 will be the +% (safe) float type for this float. We set \iffloattype to #2. +% +\def\doiffloat#1=#2=#3\finish{% + \def\temp{#1}% + \def\iffloattype{#2}% + \ifx\temp\floatmagic +} + +% @listoffloats FLOATTYPE - print a list of floats like a table of contents. +% +\parseargdef\listoffloats{% + \def\floattype{#1}% floattype + {% + % the floattype might have accents or other special characters, + % but we need to use it in a control sequence name. + \indexnofonts + \turnoffactive + \xdef\safefloattype{\floattype}% + }% + % + % \xrdef saves the floats as a \do-list in \floatlistSAFEFLOATTYPE. + \expandafter\ifx\csname floatlist\safefloattype\endcsname \relax + \ifhavexrefs + % if the user said @listoffloats foo but never @float foo. + \message{\linenumber No `\safefloattype' floats to list.}% + \fi + \else + \begingroup + \leftskip=\tocindent % indent these entries like a toc + \let\do=\listoffloatsdo + \csname floatlist\safefloattype\endcsname + \endgroup + \fi +} + +% This is called on each entry in a list of floats. We're passed the +% xref label, in the form LABEL-title, which is how we save it in the +% aux file. We strip off the -title and look up \XRLABEL-lof, which +% has the text we're supposed to typeset here. +% +% Figures without xref labels will not be included in the list (since +% they won't appear in the aux file). +% +\def\listoffloatsdo#1{\listoffloatsdoentry#1\finish} +\def\listoffloatsdoentry#1-title\finish{{% + % Can't fully expand XR#1-lof because it can contain anything. Just + % pass the control sequence. On the other hand, XR#1-pg is just the + % page number, and we want to fully expand that so we can get a link + % in pdf output. + \toksA = \expandafter{\csname XR#1-lof\endcsname}% + % + % use the same \entry macro we use to generate the TOC and index. + \edef\writeentry{\noexpand\entry{\the\toksA}{\csname XR#1-pg\endcsname}}% + \writeentry +}} + + +\message{localization,} + +% @documentlanguage is usually given very early, just after +% @setfilename. If done too late, it may not override everything +% properly. Single argument is the language (de) or locale (de_DE) +% abbreviation. It would be nice if we could set up a hyphenation file. +% +{ + \catcode`\_ = \active + \globaldefs=1 +\parseargdef\documentlanguage{\begingroup + \let_=\normalunderscore % normal _ character for filenames + \tex % read txi-??.tex file in plain TeX. + % Read the file by the name they passed if it exists. + \openin 1 txi-#1.tex + \ifeof 1 + \documentlanguagetrywithoutunderscore{#1_\finish}% + \else + \input txi-#1.tex + \fi + \closein 1 + \endgroup +\endgroup} +} +% +% If they passed de_DE, and txi-de_DE.tex doesn't exist, +% try txi-de.tex. +% +\def\documentlanguagetrywithoutunderscore#1_#2\finish{% + \openin 1 txi-#1.tex + \ifeof 1 + \errhelp = \nolanghelp + \errmessage{Cannot read language file txi-#1.tex}% + \else + \input txi-#1.tex + \fi + \closein 1 +} +% +\newhelp\nolanghelp{The given language definition file cannot be found or +is empty. Maybe you need to install it? In the current directory +should work if nowhere else does.} + +% Set the catcode of characters 128 through 255 to the specified number. +% +\def\setnonasciicharscatcode#1{% + \count255=128 + \loop\ifnum\count255<256 + \global\catcode\count255=#1\relax + \advance\count255 by 1 + \repeat +} + +\def\setnonasciicharscatcodenonglobal#1{% + \count255=128 + \loop\ifnum\count255<256 + \catcode\count255=#1\relax + \advance\count255 by 1 + \repeat +} + +% @documentencoding sets the definition of non-ASCII characters +% according to the specified encoding. +% +\parseargdef\documentencoding{% + % Encoding being declared for the document. + \def\declaredencoding{\csname #1.enc\endcsname}% + % + % Supported encodings: names converted to tokens in order to be able + % to compare them with \ifx. + \def\ascii{\csname US-ASCII.enc\endcsname}% + \def\latnine{\csname ISO-8859-15.enc\endcsname}% + \def\latone{\csname ISO-8859-1.enc\endcsname}% + \def\lattwo{\csname ISO-8859-2.enc\endcsname}% + \def\utfeight{\csname UTF-8.enc\endcsname}% + % + \ifx \declaredencoding \ascii + \asciichardefs + % + \else \ifx \declaredencoding \lattwo + \setnonasciicharscatcode\active + \lattwochardefs + % + \else \ifx \declaredencoding \latone + \setnonasciicharscatcode\active + \latonechardefs + % + \else \ifx \declaredencoding \latnine + \setnonasciicharscatcode\active + \latninechardefs + % + \else \ifx \declaredencoding \utfeight + \setnonasciicharscatcode\active + \utfeightchardefs + % + \else + \message{Unknown document encoding #1, ignoring.}% + % + \fi % utfeight + \fi % latnine + \fi % latone + \fi % lattwo + \fi % ascii +} + +% A message to be logged when using a character that isn't available +% the default font encoding (OT1). +% +\def\missingcharmsg#1{\message{Character missing in OT1 encoding: #1.}} + +% Take account of \c (plain) vs. \, (Texinfo) difference. +\def\cedilla#1{\ifx\c\ptexc\c{#1}\else\,{#1}\fi} + +% First, make active non-ASCII characters in order for them to be +% correctly categorized when TeX reads the replacement text of +% macros containing the character definitions. +\setnonasciicharscatcode\active +% +% Latin1 (ISO-8859-1) character definitions. +\def\latonechardefs{% + \gdef^^a0{~} + \gdef^^a1{\exclamdown} + \gdef^^a2{\missingcharmsg{CENT SIGN}} + \gdef^^a3{{\pounds}} + \gdef^^a4{\missingcharmsg{CURRENCY SIGN}} + \gdef^^a5{\missingcharmsg{YEN SIGN}} + \gdef^^a6{\missingcharmsg{BROKEN BAR}} + \gdef^^a7{\S} + \gdef^^a8{\"{}} + \gdef^^a9{\copyright} + \gdef^^aa{\ordf} + \gdef^^ab{\missingcharmsg{LEFT-POINTING DOUBLE ANGLE QUOTATION MARK}} + \gdef^^ac{$\lnot$} + \gdef^^ad{\-} + \gdef^^ae{\registeredsymbol} + \gdef^^af{\={}} + % + \gdef^^b0{\textdegree} + \gdef^^b1{$\pm$} + \gdef^^b2{$^2$} + \gdef^^b3{$^3$} + \gdef^^b4{\'{}} + \gdef^^b5{$\mu$} + \gdef^^b6{\P} + % + \gdef^^b7{$^.$} + \gdef^^b8{\cedilla\ } + \gdef^^b9{$^1$} + \gdef^^ba{\ordm} + % + \gdef^^bb{\missingcharmsg{RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK}} + \gdef^^bc{$1\over4$} + \gdef^^bd{$1\over2$} + \gdef^^be{$3\over4$} + \gdef^^bf{\questiondown} + % + \gdef^^c0{\`A} + \gdef^^c1{\'A} + \gdef^^c2{\^A} + \gdef^^c3{\~A} + \gdef^^c4{\"A} + \gdef^^c5{\ringaccent A} + \gdef^^c6{\AE} + \gdef^^c7{\cedilla C} + \gdef^^c8{\`E} + \gdef^^c9{\'E} + \gdef^^ca{\^E} + \gdef^^cb{\"E} + \gdef^^cc{\`I} + \gdef^^cd{\'I} + \gdef^^ce{\^I} + \gdef^^cf{\"I} + % + \gdef^^d0{\missingcharmsg{LATIN CAPITAL LETTER ETH}} + \gdef^^d1{\~N} + \gdef^^d2{\`O} + \gdef^^d3{\'O} + \gdef^^d4{\^O} + \gdef^^d5{\~O} + \gdef^^d6{\"O} + \gdef^^d7{$\times$} + \gdef^^d8{\O} + \gdef^^d9{\`U} + \gdef^^da{\'U} + \gdef^^db{\^U} + \gdef^^dc{\"U} + \gdef^^dd{\'Y} + \gdef^^de{\missingcharmsg{LATIN CAPITAL LETTER THORN}} + \gdef^^df{\ss} + % + \gdef^^e0{\`a} + \gdef^^e1{\'a} + \gdef^^e2{\^a} + \gdef^^e3{\~a} + \gdef^^e4{\"a} + \gdef^^e5{\ringaccent a} + \gdef^^e6{\ae} + \gdef^^e7{\cedilla c} + \gdef^^e8{\`e} + \gdef^^e9{\'e} + \gdef^^ea{\^e} + \gdef^^eb{\"e} + \gdef^^ec{\`{\dotless i}} + \gdef^^ed{\'{\dotless i}} + \gdef^^ee{\^{\dotless i}} + \gdef^^ef{\"{\dotless i}} + % + \gdef^^f0{\missingcharmsg{LATIN SMALL LETTER ETH}} + \gdef^^f1{\~n} + \gdef^^f2{\`o} + \gdef^^f3{\'o} + \gdef^^f4{\^o} + \gdef^^f5{\~o} + \gdef^^f6{\"o} + \gdef^^f7{$\div$} + \gdef^^f8{\o} + \gdef^^f9{\`u} + \gdef^^fa{\'u} + \gdef^^fb{\^u} + \gdef^^fc{\"u} + \gdef^^fd{\'y} + \gdef^^fe{\missingcharmsg{LATIN SMALL LETTER THORN}} + \gdef^^ff{\"y} +} + +% Latin9 (ISO-8859-15) encoding character definitions. +\def\latninechardefs{% + % Encoding is almost identical to Latin1. + \latonechardefs + % + \gdef^^a4{\euro} + \gdef^^a6{\v S} + \gdef^^a8{\v s} + \gdef^^b4{\v Z} + \gdef^^b8{\v z} + \gdef^^bc{\OE} + \gdef^^bd{\oe} + \gdef^^be{\"Y} +} + +% Latin2 (ISO-8859-2) character definitions. +\def\lattwochardefs{% + \gdef^^a0{~} + \gdef^^a1{\missingcharmsg{LATIN CAPITAL LETTER A WITH OGONEK}} + \gdef^^a2{\u{}} + \gdef^^a3{\L} + \gdef^^a4{\missingcharmsg{CURRENCY SIGN}} + \gdef^^a5{\v L} + \gdef^^a6{\'S} + \gdef^^a7{\S} + \gdef^^a8{\"{}} + \gdef^^a9{\v S} + \gdef^^aa{\cedilla S} + \gdef^^ab{\v T} + \gdef^^ac{\'Z} + \gdef^^ad{\-} + \gdef^^ae{\v Z} + \gdef^^af{\dotaccent Z} + % + \gdef^^b0{\textdegree} + \gdef^^b1{\missingcharmsg{LATIN SMALL LETTER A WITH OGONEK}} + \gdef^^b2{\missingcharmsg{OGONEK}} + \gdef^^b3{\l} + \gdef^^b4{\'{}} + \gdef^^b5{\v l} + \gdef^^b6{\'s} + \gdef^^b7{\v{}} + \gdef^^b8{\cedilla\ } + \gdef^^b9{\v s} + \gdef^^ba{\cedilla s} + \gdef^^bb{\v t} + \gdef^^bc{\'z} + \gdef^^bd{\H{}} + \gdef^^be{\v z} + \gdef^^bf{\dotaccent z} + % + \gdef^^c0{\'R} + \gdef^^c1{\'A} + \gdef^^c2{\^A} + \gdef^^c3{\u A} + \gdef^^c4{\"A} + \gdef^^c5{\'L} + \gdef^^c6{\'C} + \gdef^^c7{\cedilla C} + \gdef^^c8{\v C} + \gdef^^c9{\'E} + \gdef^^ca{\missingcharmsg{LATIN CAPITAL LETTER E WITH OGONEK}} + \gdef^^cb{\"E} + \gdef^^cc{\v E} + \gdef^^cd{\'I} + \gdef^^ce{\^I} + \gdef^^cf{\v D} + % + \gdef^^d0{\missingcharmsg{LATIN CAPITAL LETTER D WITH STROKE}} + \gdef^^d1{\'N} + \gdef^^d2{\v N} + \gdef^^d3{\'O} + \gdef^^d4{\^O} + \gdef^^d5{\H O} + \gdef^^d6{\"O} + \gdef^^d7{$\times$} + \gdef^^d8{\v R} + \gdef^^d9{\ringaccent U} + \gdef^^da{\'U} + \gdef^^db{\H U} + \gdef^^dc{\"U} + \gdef^^dd{\'Y} + \gdef^^de{\cedilla T} + \gdef^^df{\ss} + % + \gdef^^e0{\'r} + \gdef^^e1{\'a} + \gdef^^e2{\^a} + \gdef^^e3{\u a} + \gdef^^e4{\"a} + \gdef^^e5{\'l} + \gdef^^e6{\'c} + \gdef^^e7{\cedilla c} + \gdef^^e8{\v c} + \gdef^^e9{\'e} + \gdef^^ea{\missingcharmsg{LATIN SMALL LETTER E WITH OGONEK}} + \gdef^^eb{\"e} + \gdef^^ec{\v e} + \gdef^^ed{\'\i} + \gdef^^ee{\^\i} + \gdef^^ef{\v d} + % + \gdef^^f0{\missingcharmsg{LATIN SMALL LETTER D WITH STROKE}} + \gdef^^f1{\'n} + \gdef^^f2{\v n} + \gdef^^f3{\'o} + \gdef^^f4{\^o} + \gdef^^f5{\H o} + \gdef^^f6{\"o} + \gdef^^f7{$\div$} + \gdef^^f8{\v r} + \gdef^^f9{\ringaccent u} + \gdef^^fa{\'u} + \gdef^^fb{\H u} + \gdef^^fc{\"u} + \gdef^^fd{\'y} + \gdef^^fe{\cedilla t} + \gdef^^ff{\dotaccent{}} +} + +% UTF-8 character definitions. +% +% This code to support UTF-8 is based on LaTeX's utf8.def, with some +% changes for Texinfo conventions. It is included here under the GPL by +% permission from Frank Mittelbach and the LaTeX team. +% +\newcount\countUTFx +\newcount\countUTFy +\newcount\countUTFz + +\gdef\UTFviiiTwoOctets#1#2{\expandafter + \UTFviiiDefined\csname u8:#1\string #2\endcsname} +% +\gdef\UTFviiiThreeOctets#1#2#3{\expandafter + \UTFviiiDefined\csname u8:#1\string #2\string #3\endcsname} +% +\gdef\UTFviiiFourOctets#1#2#3#4{\expandafter + \UTFviiiDefined\csname u8:#1\string #2\string #3\string #4\endcsname} + +\gdef\UTFviiiDefined#1{% + \ifx #1\relax + \message{\linenumber Unicode char \string #1 not defined for Texinfo}% + \else + \expandafter #1% + \fi +} + +\begingroup + \catcode`\~13 + \catcode`\"12 + + \def\UTFviiiLoop{% + \global\catcode\countUTFx\active + \uccode`\~\countUTFx + \uppercase\expandafter{\UTFviiiTmp}% + \advance\countUTFx by 1 + \ifnum\countUTFx < \countUTFy + \expandafter\UTFviiiLoop + \fi} + + \countUTFx = "C2 + \countUTFy = "E0 + \def\UTFviiiTmp{% + \xdef~{\noexpand\UTFviiiTwoOctets\string~}} + \UTFviiiLoop + + \countUTFx = "E0 + \countUTFy = "F0 + \def\UTFviiiTmp{% + \xdef~{\noexpand\UTFviiiThreeOctets\string~}} + \UTFviiiLoop + + \countUTFx = "F0 + \countUTFy = "F4 + \def\UTFviiiTmp{% + \xdef~{\noexpand\UTFviiiFourOctets\string~}} + \UTFviiiLoop +\endgroup + +\begingroup + \catcode`\"=12 + \catcode`\<=12 + \catcode`\.=12 + \catcode`\,=12 + \catcode`\;=12 + \catcode`\!=12 + \catcode`\~=13 + + \gdef\DeclareUnicodeCharacter#1#2{% + \countUTFz = "#1\relax + \wlog{\space\space defining Unicode char U+#1 (decimal \the\countUTFz)}% + \begingroup + \parseXMLCharref + \def\UTFviiiTwoOctets##1##2{% + \csname u8:##1\string ##2\endcsname}% + \def\UTFviiiThreeOctets##1##2##3{% + \csname u8:##1\string ##2\string ##3\endcsname}% + \def\UTFviiiFourOctets##1##2##3##4{% + \csname u8:##1\string ##2\string ##3\string ##4\endcsname}% + \expandafter\expandafter\expandafter\expandafter + \expandafter\expandafter\expandafter + \gdef\UTFviiiTmp{#2}% + \endgroup} + + \gdef\parseXMLCharref{% + \ifnum\countUTFz < "A0\relax + \errhelp = \EMsimple + \errmessage{Cannot define Unicode char value < 00A0}% + \else\ifnum\countUTFz < "800\relax + \parseUTFviiiA,% + \parseUTFviiiB C\UTFviiiTwoOctets.,% + \else\ifnum\countUTFz < "10000\relax + \parseUTFviiiA;% + \parseUTFviiiA,% + \parseUTFviiiB E\UTFviiiThreeOctets.{,;}% + \else + \parseUTFviiiA;% + \parseUTFviiiA,% + \parseUTFviiiA!% + \parseUTFviiiB F\UTFviiiFourOctets.{!,;}% + \fi\fi\fi + } + + \gdef\parseUTFviiiA#1{% + \countUTFx = \countUTFz + \divide\countUTFz by 64 + \countUTFy = \countUTFz + \multiply\countUTFz by 64 + \advance\countUTFx by -\countUTFz + \advance\countUTFx by 128 + \uccode `#1\countUTFx + \countUTFz = \countUTFy} + + \gdef\parseUTFviiiB#1#2#3#4{% + \advance\countUTFz by "#10\relax + \uccode `#3\countUTFz + \uppercase{\gdef\UTFviiiTmp{#2#3#4}}} +\endgroup + +\def\utfeightchardefs{% + \DeclareUnicodeCharacter{00A0}{\tie} + \DeclareUnicodeCharacter{00A1}{\exclamdown} + \DeclareUnicodeCharacter{00A3}{\pounds} + \DeclareUnicodeCharacter{00A8}{\"{ }} + \DeclareUnicodeCharacter{00A9}{\copyright} + \DeclareUnicodeCharacter{00AA}{\ordf} + \DeclareUnicodeCharacter{00AB}{\guillemetleft} + \DeclareUnicodeCharacter{00AD}{\-} + \DeclareUnicodeCharacter{00AE}{\registeredsymbol} + \DeclareUnicodeCharacter{00AF}{\={ }} + + \DeclareUnicodeCharacter{00B0}{\ringaccent{ }} + \DeclareUnicodeCharacter{00B4}{\'{ }} + \DeclareUnicodeCharacter{00B8}{\cedilla{ }} + \DeclareUnicodeCharacter{00BA}{\ordm} + \DeclareUnicodeCharacter{00BB}{\guillemetright} + \DeclareUnicodeCharacter{00BF}{\questiondown} + + \DeclareUnicodeCharacter{00C0}{\`A} + \DeclareUnicodeCharacter{00C1}{\'A} + \DeclareUnicodeCharacter{00C2}{\^A} + \DeclareUnicodeCharacter{00C3}{\~A} + \DeclareUnicodeCharacter{00C4}{\"A} + \DeclareUnicodeCharacter{00C5}{\AA} + \DeclareUnicodeCharacter{00C6}{\AE} + \DeclareUnicodeCharacter{00C7}{\cedilla{C}} + \DeclareUnicodeCharacter{00C8}{\`E} + \DeclareUnicodeCharacter{00C9}{\'E} + \DeclareUnicodeCharacter{00CA}{\^E} + \DeclareUnicodeCharacter{00CB}{\"E} + \DeclareUnicodeCharacter{00CC}{\`I} + \DeclareUnicodeCharacter{00CD}{\'I} + \DeclareUnicodeCharacter{00CE}{\^I} + \DeclareUnicodeCharacter{00CF}{\"I} + + \DeclareUnicodeCharacter{00D1}{\~N} + \DeclareUnicodeCharacter{00D2}{\`O} + \DeclareUnicodeCharacter{00D3}{\'O} + \DeclareUnicodeCharacter{00D4}{\^O} + \DeclareUnicodeCharacter{00D5}{\~O} + \DeclareUnicodeCharacter{00D6}{\"O} + \DeclareUnicodeCharacter{00D8}{\O} + \DeclareUnicodeCharacter{00D9}{\`U} + \DeclareUnicodeCharacter{00DA}{\'U} + \DeclareUnicodeCharacter{00DB}{\^U} + \DeclareUnicodeCharacter{00DC}{\"U} + \DeclareUnicodeCharacter{00DD}{\'Y} + \DeclareUnicodeCharacter{00DF}{\ss} + + \DeclareUnicodeCharacter{00E0}{\`a} + \DeclareUnicodeCharacter{00E1}{\'a} + \DeclareUnicodeCharacter{00E2}{\^a} + \DeclareUnicodeCharacter{00E3}{\~a} + \DeclareUnicodeCharacter{00E4}{\"a} + \DeclareUnicodeCharacter{00E5}{\aa} + \DeclareUnicodeCharacter{00E6}{\ae} + \DeclareUnicodeCharacter{00E7}{\cedilla{c}} + \DeclareUnicodeCharacter{00E8}{\`e} + \DeclareUnicodeCharacter{00E9}{\'e} + \DeclareUnicodeCharacter{00EA}{\^e} + \DeclareUnicodeCharacter{00EB}{\"e} + \DeclareUnicodeCharacter{00EC}{\`{\dotless{i}}} + \DeclareUnicodeCharacter{00ED}{\'{\dotless{i}}} + \DeclareUnicodeCharacter{00EE}{\^{\dotless{i}}} + \DeclareUnicodeCharacter{00EF}{\"{\dotless{i}}} + + \DeclareUnicodeCharacter{00F1}{\~n} + \DeclareUnicodeCharacter{00F2}{\`o} + \DeclareUnicodeCharacter{00F3}{\'o} + \DeclareUnicodeCharacter{00F4}{\^o} + \DeclareUnicodeCharacter{00F5}{\~o} + \DeclareUnicodeCharacter{00F6}{\"o} + \DeclareUnicodeCharacter{00F8}{\o} + \DeclareUnicodeCharacter{00F9}{\`u} + \DeclareUnicodeCharacter{00FA}{\'u} + \DeclareUnicodeCharacter{00FB}{\^u} + \DeclareUnicodeCharacter{00FC}{\"u} + \DeclareUnicodeCharacter{00FD}{\'y} + \DeclareUnicodeCharacter{00FF}{\"y} + + \DeclareUnicodeCharacter{0100}{\=A} + \DeclareUnicodeCharacter{0101}{\=a} + \DeclareUnicodeCharacter{0102}{\u{A}} + \DeclareUnicodeCharacter{0103}{\u{a}} + \DeclareUnicodeCharacter{0106}{\'C} + \DeclareUnicodeCharacter{0107}{\'c} + \DeclareUnicodeCharacter{0108}{\^C} + \DeclareUnicodeCharacter{0109}{\^c} + \DeclareUnicodeCharacter{010A}{\dotaccent{C}} + \DeclareUnicodeCharacter{010B}{\dotaccent{c}} + \DeclareUnicodeCharacter{010C}{\v{C}} + \DeclareUnicodeCharacter{010D}{\v{c}} + \DeclareUnicodeCharacter{010E}{\v{D}} + + \DeclareUnicodeCharacter{0112}{\=E} + \DeclareUnicodeCharacter{0113}{\=e} + \DeclareUnicodeCharacter{0114}{\u{E}} + \DeclareUnicodeCharacter{0115}{\u{e}} + \DeclareUnicodeCharacter{0116}{\dotaccent{E}} + \DeclareUnicodeCharacter{0117}{\dotaccent{e}} + \DeclareUnicodeCharacter{011A}{\v{E}} + \DeclareUnicodeCharacter{011B}{\v{e}} + \DeclareUnicodeCharacter{011C}{\^G} + \DeclareUnicodeCharacter{011D}{\^g} + \DeclareUnicodeCharacter{011E}{\u{G}} + \DeclareUnicodeCharacter{011F}{\u{g}} + + \DeclareUnicodeCharacter{0120}{\dotaccent{G}} + \DeclareUnicodeCharacter{0121}{\dotaccent{g}} + \DeclareUnicodeCharacter{0124}{\^H} + \DeclareUnicodeCharacter{0125}{\^h} + \DeclareUnicodeCharacter{0128}{\~I} + \DeclareUnicodeCharacter{0129}{\~{\dotless{i}}} + \DeclareUnicodeCharacter{012A}{\=I} + \DeclareUnicodeCharacter{012B}{\={\dotless{i}}} + \DeclareUnicodeCharacter{012C}{\u{I}} + \DeclareUnicodeCharacter{012D}{\u{\dotless{i}}} + + \DeclareUnicodeCharacter{0130}{\dotaccent{I}} + \DeclareUnicodeCharacter{0131}{\dotless{i}} + \DeclareUnicodeCharacter{0132}{IJ} + \DeclareUnicodeCharacter{0133}{ij} + \DeclareUnicodeCharacter{0134}{\^J} + \DeclareUnicodeCharacter{0135}{\^{\dotless{j}}} + \DeclareUnicodeCharacter{0139}{\'L} + \DeclareUnicodeCharacter{013A}{\'l} + + \DeclareUnicodeCharacter{0141}{\L} + \DeclareUnicodeCharacter{0142}{\l} + \DeclareUnicodeCharacter{0143}{\'N} + \DeclareUnicodeCharacter{0144}{\'n} + \DeclareUnicodeCharacter{0147}{\v{N}} + \DeclareUnicodeCharacter{0148}{\v{n}} + \DeclareUnicodeCharacter{014C}{\=O} + \DeclareUnicodeCharacter{014D}{\=o} + \DeclareUnicodeCharacter{014E}{\u{O}} + \DeclareUnicodeCharacter{014F}{\u{o}} + + \DeclareUnicodeCharacter{0150}{\H{O}} + \DeclareUnicodeCharacter{0151}{\H{o}} + \DeclareUnicodeCharacter{0152}{\OE} + \DeclareUnicodeCharacter{0153}{\oe} + \DeclareUnicodeCharacter{0154}{\'R} + \DeclareUnicodeCharacter{0155}{\'r} + \DeclareUnicodeCharacter{0158}{\v{R}} + \DeclareUnicodeCharacter{0159}{\v{r}} + \DeclareUnicodeCharacter{015A}{\'S} + \DeclareUnicodeCharacter{015B}{\'s} + \DeclareUnicodeCharacter{015C}{\^S} + \DeclareUnicodeCharacter{015D}{\^s} + \DeclareUnicodeCharacter{015E}{\cedilla{S}} + \DeclareUnicodeCharacter{015F}{\cedilla{s}} + + \DeclareUnicodeCharacter{0160}{\v{S}} + \DeclareUnicodeCharacter{0161}{\v{s}} + \DeclareUnicodeCharacter{0162}{\cedilla{t}} + \DeclareUnicodeCharacter{0163}{\cedilla{T}} + \DeclareUnicodeCharacter{0164}{\v{T}} + + \DeclareUnicodeCharacter{0168}{\~U} + \DeclareUnicodeCharacter{0169}{\~u} + \DeclareUnicodeCharacter{016A}{\=U} + \DeclareUnicodeCharacter{016B}{\=u} + \DeclareUnicodeCharacter{016C}{\u{U}} + \DeclareUnicodeCharacter{016D}{\u{u}} + \DeclareUnicodeCharacter{016E}{\ringaccent{U}} + \DeclareUnicodeCharacter{016F}{\ringaccent{u}} + + \DeclareUnicodeCharacter{0170}{\H{U}} + \DeclareUnicodeCharacter{0171}{\H{u}} + \DeclareUnicodeCharacter{0174}{\^W} + \DeclareUnicodeCharacter{0175}{\^w} + \DeclareUnicodeCharacter{0176}{\^Y} + \DeclareUnicodeCharacter{0177}{\^y} + \DeclareUnicodeCharacter{0178}{\"Y} + \DeclareUnicodeCharacter{0179}{\'Z} + \DeclareUnicodeCharacter{017A}{\'z} + \DeclareUnicodeCharacter{017B}{\dotaccent{Z}} + \DeclareUnicodeCharacter{017C}{\dotaccent{z}} + \DeclareUnicodeCharacter{017D}{\v{Z}} + \DeclareUnicodeCharacter{017E}{\v{z}} + + \DeclareUnicodeCharacter{01C4}{D\v{Z}} + \DeclareUnicodeCharacter{01C5}{D\v{z}} + \DeclareUnicodeCharacter{01C6}{d\v{z}} + \DeclareUnicodeCharacter{01C7}{LJ} + \DeclareUnicodeCharacter{01C8}{Lj} + \DeclareUnicodeCharacter{01C9}{lj} + \DeclareUnicodeCharacter{01CA}{NJ} + \DeclareUnicodeCharacter{01CB}{Nj} + \DeclareUnicodeCharacter{01CC}{nj} + \DeclareUnicodeCharacter{01CD}{\v{A}} + \DeclareUnicodeCharacter{01CE}{\v{a}} + \DeclareUnicodeCharacter{01CF}{\v{I}} + + \DeclareUnicodeCharacter{01D0}{\v{\dotless{i}}} + \DeclareUnicodeCharacter{01D1}{\v{O}} + \DeclareUnicodeCharacter{01D2}{\v{o}} + \DeclareUnicodeCharacter{01D3}{\v{U}} + \DeclareUnicodeCharacter{01D4}{\v{u}} + + \DeclareUnicodeCharacter{01E2}{\={\AE}} + \DeclareUnicodeCharacter{01E3}{\={\ae}} + \DeclareUnicodeCharacter{01E6}{\v{G}} + \DeclareUnicodeCharacter{01E7}{\v{g}} + \DeclareUnicodeCharacter{01E8}{\v{K}} + \DeclareUnicodeCharacter{01E9}{\v{k}} + + \DeclareUnicodeCharacter{01F0}{\v{\dotless{j}}} + \DeclareUnicodeCharacter{01F1}{DZ} + \DeclareUnicodeCharacter{01F2}{Dz} + \DeclareUnicodeCharacter{01F3}{dz} + \DeclareUnicodeCharacter{01F4}{\'G} + \DeclareUnicodeCharacter{01F5}{\'g} + \DeclareUnicodeCharacter{01F8}{\`N} + \DeclareUnicodeCharacter{01F9}{\`n} + \DeclareUnicodeCharacter{01FC}{\'{\AE}} + \DeclareUnicodeCharacter{01FD}{\'{\ae}} + \DeclareUnicodeCharacter{01FE}{\'{\O}} + \DeclareUnicodeCharacter{01FF}{\'{\o}} + + \DeclareUnicodeCharacter{021E}{\v{H}} + \DeclareUnicodeCharacter{021F}{\v{h}} + + \DeclareUnicodeCharacter{0226}{\dotaccent{A}} + \DeclareUnicodeCharacter{0227}{\dotaccent{a}} + \DeclareUnicodeCharacter{0228}{\cedilla{E}} + \DeclareUnicodeCharacter{0229}{\cedilla{e}} + \DeclareUnicodeCharacter{022E}{\dotaccent{O}} + \DeclareUnicodeCharacter{022F}{\dotaccent{o}} + + \DeclareUnicodeCharacter{0232}{\=Y} + \DeclareUnicodeCharacter{0233}{\=y} + \DeclareUnicodeCharacter{0237}{\dotless{j}} + + \DeclareUnicodeCharacter{1E02}{\dotaccent{B}} + \DeclareUnicodeCharacter{1E03}{\dotaccent{b}} + \DeclareUnicodeCharacter{1E04}{\udotaccent{B}} + \DeclareUnicodeCharacter{1E05}{\udotaccent{b}} + \DeclareUnicodeCharacter{1E06}{\ubaraccent{B}} + \DeclareUnicodeCharacter{1E07}{\ubaraccent{b}} + \DeclareUnicodeCharacter{1E0A}{\dotaccent{D}} + \DeclareUnicodeCharacter{1E0B}{\dotaccent{d}} + \DeclareUnicodeCharacter{1E0C}{\udotaccent{D}} + \DeclareUnicodeCharacter{1E0D}{\udotaccent{d}} + \DeclareUnicodeCharacter{1E0E}{\ubaraccent{D}} + \DeclareUnicodeCharacter{1E0F}{\ubaraccent{d}} + + \DeclareUnicodeCharacter{1E1E}{\dotaccent{F}} + \DeclareUnicodeCharacter{1E1F}{\dotaccent{f}} + + \DeclareUnicodeCharacter{1E20}{\=G} + \DeclareUnicodeCharacter{1E21}{\=g} + \DeclareUnicodeCharacter{1E22}{\dotaccent{H}} + \DeclareUnicodeCharacter{1E23}{\dotaccent{h}} + \DeclareUnicodeCharacter{1E24}{\udotaccent{H}} + \DeclareUnicodeCharacter{1E25}{\udotaccent{h}} + \DeclareUnicodeCharacter{1E26}{\"H} + \DeclareUnicodeCharacter{1E27}{\"h} + + \DeclareUnicodeCharacter{1E30}{\'K} + \DeclareUnicodeCharacter{1E31}{\'k} + \DeclareUnicodeCharacter{1E32}{\udotaccent{K}} + \DeclareUnicodeCharacter{1E33}{\udotaccent{k}} + \DeclareUnicodeCharacter{1E34}{\ubaraccent{K}} + \DeclareUnicodeCharacter{1E35}{\ubaraccent{k}} + \DeclareUnicodeCharacter{1E36}{\udotaccent{L}} + \DeclareUnicodeCharacter{1E37}{\udotaccent{l}} + \DeclareUnicodeCharacter{1E3A}{\ubaraccent{L}} + \DeclareUnicodeCharacter{1E3B}{\ubaraccent{l}} + \DeclareUnicodeCharacter{1E3E}{\'M} + \DeclareUnicodeCharacter{1E3F}{\'m} + + \DeclareUnicodeCharacter{1E40}{\dotaccent{M}} + \DeclareUnicodeCharacter{1E41}{\dotaccent{m}} + \DeclareUnicodeCharacter{1E42}{\udotaccent{M}} + \DeclareUnicodeCharacter{1E43}{\udotaccent{m}} + \DeclareUnicodeCharacter{1E44}{\dotaccent{N}} + \DeclareUnicodeCharacter{1E45}{\dotaccent{n}} + \DeclareUnicodeCharacter{1E46}{\udotaccent{N}} + \DeclareUnicodeCharacter{1E47}{\udotaccent{n}} + \DeclareUnicodeCharacter{1E48}{\ubaraccent{N}} + \DeclareUnicodeCharacter{1E49}{\ubaraccent{n}} + + \DeclareUnicodeCharacter{1E54}{\'P} + \DeclareUnicodeCharacter{1E55}{\'p} + \DeclareUnicodeCharacter{1E56}{\dotaccent{P}} + \DeclareUnicodeCharacter{1E57}{\dotaccent{p}} + \DeclareUnicodeCharacter{1E58}{\dotaccent{R}} + \DeclareUnicodeCharacter{1E59}{\dotaccent{r}} + \DeclareUnicodeCharacter{1E5A}{\udotaccent{R}} + \DeclareUnicodeCharacter{1E5B}{\udotaccent{r}} + \DeclareUnicodeCharacter{1E5E}{\ubaraccent{R}} + \DeclareUnicodeCharacter{1E5F}{\ubaraccent{r}} + + \DeclareUnicodeCharacter{1E60}{\dotaccent{S}} + \DeclareUnicodeCharacter{1E61}{\dotaccent{s}} + \DeclareUnicodeCharacter{1E62}{\udotaccent{S}} + \DeclareUnicodeCharacter{1E63}{\udotaccent{s}} + \DeclareUnicodeCharacter{1E6A}{\dotaccent{T}} + \DeclareUnicodeCharacter{1E6B}{\dotaccent{t}} + \DeclareUnicodeCharacter{1E6C}{\udotaccent{T}} + \DeclareUnicodeCharacter{1E6D}{\udotaccent{t}} + \DeclareUnicodeCharacter{1E6E}{\ubaraccent{T}} + \DeclareUnicodeCharacter{1E6F}{\ubaraccent{t}} + + \DeclareUnicodeCharacter{1E7C}{\~V} + \DeclareUnicodeCharacter{1E7D}{\~v} + \DeclareUnicodeCharacter{1E7E}{\udotaccent{V}} + \DeclareUnicodeCharacter{1E7F}{\udotaccent{v}} + + \DeclareUnicodeCharacter{1E80}{\`W} + \DeclareUnicodeCharacter{1E81}{\`w} + \DeclareUnicodeCharacter{1E82}{\'W} + \DeclareUnicodeCharacter{1E83}{\'w} + \DeclareUnicodeCharacter{1E84}{\"W} + \DeclareUnicodeCharacter{1E85}{\"w} + \DeclareUnicodeCharacter{1E86}{\dotaccent{W}} + \DeclareUnicodeCharacter{1E87}{\dotaccent{w}} + \DeclareUnicodeCharacter{1E88}{\udotaccent{W}} + \DeclareUnicodeCharacter{1E89}{\udotaccent{w}} + \DeclareUnicodeCharacter{1E8A}{\dotaccent{X}} + \DeclareUnicodeCharacter{1E8B}{\dotaccent{x}} + \DeclareUnicodeCharacter{1E8C}{\"X} + \DeclareUnicodeCharacter{1E8D}{\"x} + \DeclareUnicodeCharacter{1E8E}{\dotaccent{Y}} + \DeclareUnicodeCharacter{1E8F}{\dotaccent{y}} + + \DeclareUnicodeCharacter{1E90}{\^Z} + \DeclareUnicodeCharacter{1E91}{\^z} + \DeclareUnicodeCharacter{1E92}{\udotaccent{Z}} + \DeclareUnicodeCharacter{1E93}{\udotaccent{z}} + \DeclareUnicodeCharacter{1E94}{\ubaraccent{Z}} + \DeclareUnicodeCharacter{1E95}{\ubaraccent{z}} + \DeclareUnicodeCharacter{1E96}{\ubaraccent{h}} + \DeclareUnicodeCharacter{1E97}{\"t} + \DeclareUnicodeCharacter{1E98}{\ringaccent{w}} + \DeclareUnicodeCharacter{1E99}{\ringaccent{y}} + + \DeclareUnicodeCharacter{1EA0}{\udotaccent{A}} + \DeclareUnicodeCharacter{1EA1}{\udotaccent{a}} + + \DeclareUnicodeCharacter{1EB8}{\udotaccent{E}} + \DeclareUnicodeCharacter{1EB9}{\udotaccent{e}} + \DeclareUnicodeCharacter{1EBC}{\~E} + \DeclareUnicodeCharacter{1EBD}{\~e} + + \DeclareUnicodeCharacter{1ECA}{\udotaccent{I}} + \DeclareUnicodeCharacter{1ECB}{\udotaccent{i}} + \DeclareUnicodeCharacter{1ECC}{\udotaccent{O}} + \DeclareUnicodeCharacter{1ECD}{\udotaccent{o}} + + \DeclareUnicodeCharacter{1EE4}{\udotaccent{U}} + \DeclareUnicodeCharacter{1EE5}{\udotaccent{u}} + + \DeclareUnicodeCharacter{1EF2}{\`Y} + \DeclareUnicodeCharacter{1EF3}{\`y} + \DeclareUnicodeCharacter{1EF4}{\udotaccent{Y}} + + \DeclareUnicodeCharacter{1EF8}{\~Y} + \DeclareUnicodeCharacter{1EF9}{\~y} + + \DeclareUnicodeCharacter{2013}{--} + \DeclareUnicodeCharacter{2014}{---} + \DeclareUnicodeCharacter{2018}{\quoteleft} + \DeclareUnicodeCharacter{2019}{\quoteright} + \DeclareUnicodeCharacter{201A}{\quotesinglbase} + \DeclareUnicodeCharacter{201C}{\quotedblleft} + \DeclareUnicodeCharacter{201D}{\quotedblright} + \DeclareUnicodeCharacter{201E}{\quotedblbase} + \DeclareUnicodeCharacter{2022}{\bullet} + \DeclareUnicodeCharacter{2026}{\dots} + \DeclareUnicodeCharacter{2039}{\guilsinglleft} + \DeclareUnicodeCharacter{203A}{\guilsinglright} + \DeclareUnicodeCharacter{20AC}{\euro} + + \DeclareUnicodeCharacter{2192}{\expansion} + \DeclareUnicodeCharacter{21D2}{\result} + + \DeclareUnicodeCharacter{2212}{\minus} + \DeclareUnicodeCharacter{2217}{\point} + \DeclareUnicodeCharacter{2261}{\equiv} +}% end of \utfeightchardefs + + +% US-ASCII character definitions. +\def\asciichardefs{% nothing need be done + \relax +} + +% Make non-ASCII characters printable again for compatibility with +% existing Texinfo documents that may use them, even without declaring a +% document encoding. +% +\setnonasciicharscatcode \other + + +\message{formatting,} + +\newdimen\defaultparindent \defaultparindent = 15pt + +\chapheadingskip = 15pt plus 4pt minus 2pt +\secheadingskip = 12pt plus 3pt minus 2pt +\subsecheadingskip = 9pt plus 2pt minus 2pt + +% Prevent underfull vbox error messages. +\vbadness = 10000 + +% Don't be so finicky about underfull hboxes, either. +\hbadness = 2000 + +% Following George Bush, get rid of widows and orphans. +\widowpenalty=10000 +\clubpenalty=10000 + +% Use TeX 3.0's \emergencystretch to help line breaking, but if we're +% using an old version of TeX, don't do anything. We want the amount of +% stretch added to depend on the line length, hence the dependence on +% \hsize. We call this whenever the paper size is set. +% +\def\setemergencystretch{% + \ifx\emergencystretch\thisisundefined + % Allow us to assign to \emergencystretch anyway. + \def\emergencystretch{\dimen0}% + \else + \emergencystretch = .15\hsize + \fi +} + +% Parameters in order: 1) textheight; 2) textwidth; +% 3) voffset; 4) hoffset; 5) binding offset; 6) topskip; +% 7) physical page height; 8) physical page width. +% +% We also call \setleading{\textleading}, so the caller should define +% \textleading. The caller should also set \parskip. +% +\def\internalpagesizes#1#2#3#4#5#6#7#8{% + \voffset = #3\relax + \topskip = #6\relax + \splittopskip = \topskip + % + \vsize = #1\relax + \advance\vsize by \topskip + \outervsize = \vsize + \advance\outervsize by 2\topandbottommargin + \pageheight = \vsize + % + \hsize = #2\relax + \outerhsize = \hsize + \advance\outerhsize by 0.5in + \pagewidth = \hsize + % + \normaloffset = #4\relax + \bindingoffset = #5\relax + % + \ifpdf + \pdfpageheight #7\relax + \pdfpagewidth #8\relax + % if we don't reset these, they will remain at "1 true in" of + % whatever layout pdftex was dumped with. + \pdfhorigin = 1 true in + \pdfvorigin = 1 true in + \fi + % + \setleading{\textleading} + % + \parindent = \defaultparindent + \setemergencystretch +} + +% @letterpaper (the default). +\def\letterpaper{{\globaldefs = 1 + \parskip = 3pt plus 2pt minus 1pt + \textleading = 13.2pt + % + % If page is nothing but text, make it come out even. + \internalpagesizes{607.2pt}{6in}% that's 46 lines + {\voffset}{.25in}% + {\bindingoffset}{36pt}% + {11in}{8.5in}% +}} + +% Use @smallbook to reset parameters for 7x9.25 trim size. +\def\smallbook{{\globaldefs = 1 + \parskip = 2pt plus 1pt + \textleading = 12pt + % + \internalpagesizes{7.5in}{5in}% + {-.2in}{0in}% + {\bindingoffset}{16pt}% + {9.25in}{7in}% + % + \lispnarrowing = 0.3in + \tolerance = 700 + \hfuzz = 1pt + \contentsrightmargin = 0pt + \defbodyindent = .5cm +}} + +% Use @smallerbook to reset parameters for 6x9 trim size. +% (Just testing, parameters still in flux.) +\def\smallerbook{{\globaldefs = 1 + \parskip = 1.5pt plus 1pt + \textleading = 12pt + % + \internalpagesizes{7.4in}{4.8in}% + {-.2in}{-.4in}% + {0pt}{14pt}% + {9in}{6in}% + % + \lispnarrowing = 0.25in + \tolerance = 700 + \hfuzz = 1pt + \contentsrightmargin = 0pt + \defbodyindent = .4cm +}} + +% Use @afourpaper to print on European A4 paper. +\def\afourpaper{{\globaldefs = 1 + \parskip = 3pt plus 2pt minus 1pt + \textleading = 13.2pt + % + % Double-side printing via postscript on Laserjet 4050 + % prints double-sided nicely when \bindingoffset=10mm and \hoffset=-6mm. + % To change the settings for a different printer or situation, adjust + % \normaloffset until the front-side and back-side texts align. Then + % do the same for \bindingoffset. You can set these for testing in + % your texinfo source file like this: + % @tex + % \global\normaloffset = -6mm + % \global\bindingoffset = 10mm + % @end tex + \internalpagesizes{673.2pt}{160mm}% that's 51 lines + {\voffset}{\hoffset}% + {\bindingoffset}{44pt}% + {297mm}{210mm}% + % + \tolerance = 700 + \hfuzz = 1pt + \contentsrightmargin = 0pt + \defbodyindent = 5mm +}} + +% Use @afivepaper to print on European A5 paper. +% From romildo@urano.iceb.ufop.br, 2 July 2000. +% He also recommends making @example and @lisp be small. +\def\afivepaper{{\globaldefs = 1 + \parskip = 2pt plus 1pt minus 0.1pt + \textleading = 12.5pt + % + \internalpagesizes{160mm}{120mm}% + {\voffset}{\hoffset}% + {\bindingoffset}{8pt}% + {210mm}{148mm}% + % + \lispnarrowing = 0.2in + \tolerance = 800 + \hfuzz = 1.2pt + \contentsrightmargin = 0pt + \defbodyindent = 2mm + \tableindent = 12mm +}} + +% A specific text layout, 24x15cm overall, intended for A4 paper. +\def\afourlatex{{\globaldefs = 1 + \afourpaper + \internalpagesizes{237mm}{150mm}% + {\voffset}{4.6mm}% + {\bindingoffset}{7mm}% + {297mm}{210mm}% + % + % Must explicitly reset to 0 because we call \afourpaper. + \globaldefs = 0 +}} + +% Use @afourwide to print on A4 paper in landscape format. +\def\afourwide{{\globaldefs = 1 + \afourpaper + \internalpagesizes{241mm}{165mm}% + {\voffset}{-2.95mm}% + {\bindingoffset}{7mm}% + {297mm}{210mm}% + \globaldefs = 0 +}} + +% @pagesizes TEXTHEIGHT[,TEXTWIDTH] +% Perhaps we should allow setting the margins, \topskip, \parskip, +% and/or leading, also. Or perhaps we should compute them somehow. +% +\parseargdef\pagesizes{\pagesizesyyy #1,,\finish} +\def\pagesizesyyy#1,#2,#3\finish{{% + \setbox0 = \hbox{\ignorespaces #2}\ifdim\wd0 > 0pt \hsize=#2\relax \fi + \globaldefs = 1 + % + \parskip = 3pt plus 2pt minus 1pt + \setleading{\textleading}% + % + \dimen0 = #1\relax + \advance\dimen0 by \voffset + % + \dimen2 = \hsize + \advance\dimen2 by \normaloffset + % + \internalpagesizes{#1}{\hsize}% + {\voffset}{\normaloffset}% + {\bindingoffset}{44pt}% + {\dimen0}{\dimen2}% +}} + +% Set default to letter. +% +\letterpaper + + +\message{and turning on texinfo input format.} + +% Define macros to output various characters with catcode for normal text. +\catcode`\"=\other +\catcode`\~=\other +\catcode`\^=\other +\catcode`\_=\other +\catcode`\|=\other +\catcode`\<=\other +\catcode`\>=\other +\catcode`\+=\other +\catcode`\$=\other +\def\normaldoublequote{"} +\def\normaltilde{~} +\def\normalcaret{^} +\def\normalunderscore{_} +\def\normalverticalbar{|} +\def\normalless{<} +\def\normalgreater{>} +\def\normalplus{+} +\def\normaldollar{$}%$ font-lock fix + +% This macro is used to make a character print one way in \tt +% (where it can probably be output as-is), and another way in other fonts, +% where something hairier probably needs to be done. +% +% #1 is what to print if we are indeed using \tt; #2 is what to print +% otherwise. Since all the Computer Modern typewriter fonts have zero +% interword stretch (and shrink), and it is reasonable to expect all +% typewriter fonts to have this, we can check that font parameter. +% +\def\ifusingtt#1#2{\ifdim \fontdimen3\font=0pt #1\else #2\fi} + +% Same as above, but check for italic font. Actually this also catches +% non-italic slanted fonts since it is impossible to distinguish them from +% italic fonts. But since this is only used by $ and it uses \sl anyway +% this is not a problem. +\def\ifusingit#1#2{\ifdim \fontdimen1\font>0pt #1\else #2\fi} + +% Turn off all special characters except @ +% (and those which the user can use as if they were ordinary). +% Most of these we simply print from the \tt font, but for some, we can +% use math or other variants that look better in normal text. + +\catcode`\"=\active +\def\activedoublequote{{\tt\char34}} +\let"=\activedoublequote +\catcode`\~=\active +\def~{{\tt\char126}} +\chardef\hat=`\^ +\catcode`\^=\active +\def^{{\tt \hat}} + +\catcode`\_=\active +\def_{\ifusingtt\normalunderscore\_} +\let\realunder=_ +% Subroutine for the previous macro. +\def\_{\leavevmode \kern.07em \vbox{\hrule width.3em height.1ex}\kern .07em } + +\catcode`\|=\active +\def|{{\tt\char124}} +\chardef \less=`\< +\catcode`\<=\active +\def<{{\tt \less}} +\chardef \gtr=`\> +\catcode`\>=\active +\def>{{\tt \gtr}} +\catcode`\+=\active +\def+{{\tt \char 43}} +\catcode`\$=\active +\def${\ifusingit{{\sl\$}}\normaldollar}%$ font-lock fix + +% If a .fmt file is being used, characters that might appear in a file +% name cannot be active until we have parsed the command line. +% So turn them off again, and have \everyjob (or @setfilename) turn them on. +% \otherifyactive is called near the end of this file. +\def\otherifyactive{\catcode`+=\other \catcode`\_=\other} + +% Used sometimes to turn off (effectively) the active characters even after +% parsing them. +\def\turnoffactive{% + \normalturnoffactive + \otherbackslash +} + +\catcode`\@=0 + +% \backslashcurfont outputs one backslash character in current font, +% as in \char`\\. +\global\chardef\backslashcurfont=`\\ +\global\let\rawbackslashxx=\backslashcurfont % let existing .??s files work + +% \realbackslash is an actual character `\' with catcode other, and +% \doublebackslash is two of them (for the pdf outlines). +{\catcode`\\=\other @gdef@realbackslash{\} @gdef@doublebackslash{\\}} + +% In texinfo, backslash is an active character; it prints the backslash +% in fixed width font. +\catcode`\\=\active +@def@normalbackslash{{@tt@backslashcurfont}} +% On startup, @fixbackslash assigns: +% @let \ = @normalbackslash + +% \rawbackslash defines an active \ to do \backslashcurfont. +% \otherbackslash defines an active \ to be a literal `\' character with +% catcode other. +@gdef@rawbackslash{@let\=@backslashcurfont} +@gdef@otherbackslash{@let\=@realbackslash} + +% Same as @turnoffactive except outputs \ as {\tt\char`\\} instead of +% the literal character `\'. +% +@def@normalturnoffactive{% + @let\=@normalbackslash + @let"=@normaldoublequote + @let~=@normaltilde + @let^=@normalcaret + @let_=@normalunderscore + @let|=@normalverticalbar + @let<=@normalless + @let>=@normalgreater + @let+=@normalplus + @let$=@normaldollar %$ font-lock fix + @unsepspaces +} + +% Make _ and + \other characters, temporarily. +% This is canceled by @fixbackslash. +@otherifyactive + +% If a .fmt file is being used, we don't want the `\input texinfo' to show up. +% That is what \eatinput is for; after that, the `\' should revert to printing +% a backslash. +% +@gdef@eatinput input texinfo{@fixbackslash} +@global@let\ = @eatinput + +% On the other hand, perhaps the file did not have a `\input texinfo'. Then +% the first `\' in the file would cause an error. This macro tries to fix +% that, assuming it is called before the first `\' could plausibly occur. +% Also turn back on active characters that might appear in the input +% file name, in case not using a pre-dumped format. +% +@gdef@fixbackslash{% + @ifx\@eatinput @let\ = @normalbackslash @fi + @catcode`+=@active + @catcode`@_=@active +} + +% Say @foo, not \foo, in error messages. +@escapechar = `@@ + +% These look ok in all fonts, so just make them not special. +@catcode`@& = @other +@catcode`@# = @other +@catcode`@% = @other + + +@c Local variables: +@c eval: (add-hook 'write-file-hooks 'time-stamp) +@c page-delimiter: "^\\\\message" +@c time-stamp-start: "def\\\\texinfoversion{" +@c time-stamp-format: "%:y-%02m-%02d.%02H" +@c time-stamp-end: "}" +@c End: + +@c vim:sw=2: + +@ignore + arch-tag: e1b36e32-c96e-4135-a41a-0b2efa2ea115 +@end ignore |