Merge branch 'release-1.0.0'
commit
56a0928a4f
|
@ -0,0 +1 @@
|
|||
src/_version.py export-subst
|
|
@ -35,11 +35,13 @@ nosetests.xml
|
|||
.pydevproject
|
||||
|
||||
# Temp files
|
||||
.\#*
|
||||
\#*\#
|
||||
*~
|
||||
|
||||
# tags
|
||||
TAGS
|
||||
tags
|
||||
|
||||
# notes
|
||||
*.org
|
||||
|
@ -47,3 +49,26 @@ TAGS
|
|||
# Ignore log files and directories from tests:
|
||||
keys/*
|
||||
*.log
|
||||
|
||||
# Ignore distutils record of installed files:
|
||||
installed-files.txt
|
||||
|
||||
# Ignore PyCharm files:
|
||||
.idea/*
|
||||
|
||||
# and git-tickets and tickets:
|
||||
.tickets/*
|
||||
tickets/*
|
||||
|
||||
# Ignore virtualenv folders, if they are here:
|
||||
include/*
|
||||
local/*
|
||||
|
||||
# Ignore gpg binary symlinks:
|
||||
gpg
|
||||
|
||||
# Ignore keys generated during tests:
|
||||
*generated-keys*
|
||||
*.pubring
|
||||
*.secring
|
||||
*random_seed*
|
||||
|
|
12
COPYLEFT
12
COPYLEFT
|
@ -1,12 +0,0 @@
|
|||
This file is part of python-gnupg, a Python wrapper around GnuPG.
|
||||
Copyright (C) 2013 Isis Lovecruft
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program 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 Affero General Public License for more details.
|
67
Makefile
67
Makefile
|
@ -1,18 +1,65 @@
|
|||
|
||||
clean:
|
||||
rm -f \#*\#
|
||||
rm -f ./*.pyc
|
||||
rm -f ./*.pyo
|
||||
ctags:
|
||||
ctags -R *.py
|
||||
|
||||
cleantest: clean
|
||||
mkdir -p keys
|
||||
etags:
|
||||
find . -name "*.py" -print | xargs etags
|
||||
|
||||
cleanup-src:
|
||||
cd src && \
|
||||
rm -f \#*\# && \
|
||||
rm -f ./*.pyc && \
|
||||
rm -f ./*.pyo
|
||||
|
||||
cleanup-tests:
|
||||
cd tests && \
|
||||
rm -f \#*\# && \
|
||||
rm -f ./*.pyc && \
|
||||
rm -f ./*.pyo
|
||||
mkdir -p tests/tmp
|
||||
mkdir -p tests/logs
|
||||
touch tests/placeholder.log
|
||||
mv tests/*.log tests/logs/
|
||||
rm tests/logs/placeholder.log
|
||||
touch placeholder.log
|
||||
rm -rf keys
|
||||
rm *.log
|
||||
rm tests/random_seed
|
||||
|
||||
test: cleantest
|
||||
python test_gnupg.py basic
|
||||
cleanup-tests-all: cleanup-tests
|
||||
rm -rf tests/tmp
|
||||
|
||||
cleanup-build:
|
||||
mkdir buildnot
|
||||
rm -rf build*
|
||||
|
||||
test: cleanup-src cleanup-tests
|
||||
which gpg
|
||||
gpg --version
|
||||
which gpg2
|
||||
gpg2 --version
|
||||
which gpg-agent
|
||||
which pinentry
|
||||
which python
|
||||
python --version
|
||||
which pip
|
||||
pip --version
|
||||
pip list
|
||||
python tests/test_gnupg.py parsers basic encodings genkey sign listkeys crypt keyrings import
|
||||
|
||||
install:
|
||||
python setup.py install
|
||||
python setup.py install --record installed-files.txt
|
||||
|
||||
uninstall:
|
||||
touch installed-files.txt
|
||||
cat installed-files.txt | sudo xargs rm -rf
|
||||
|
||||
cleandocs:
|
||||
sphinx-apidoc -F -A "Isis Agora Lovecruft" -H "python-gnupg" -V 0.4.0 -R 0.4.0 -o docs src/ tests/
|
||||
|
||||
docs:
|
||||
cd docs
|
||||
make clean
|
||||
make html
|
||||
|
||||
venv:
|
||||
-source /usr/shared/python/ns/virtualenvwrapper.sh && mkvirtualenv -a "$PWD" --no-site-packages --unzip-setuptools --distribute python-gnupg
|
||||
|
|
43
PKG-INFO
43
PKG-INFO
|
@ -5,24 +5,25 @@ Summary: A wrapper for the Gnu Privacy Guard (GPG or GnuPG)
|
|||
Home-page: https://www.github.com/isislovecruft/python-gnupg
|
||||
Author: Isis Lovecruft
|
||||
Author-email: isis@leap.se
|
||||
License: Copyright (C) 2013 by Isis Lovecruft. All Rights Reserved. See LICENSE for license. See COPYLEFT for "copyright".
|
||||
Download-URL: https://github.com/isislovecruft/python-gnupg.git
|
||||
Description: This module allows easy access to GnuPG's key management, encryption and signature functionality from Python programs. It is intended for use with Python 2.4 or greater.
|
||||
Platform: No particular restrictions
|
||||
Classifier: Development Status :: 3 - Alpha
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 2.4
|
||||
Classifier: Programming Language :: Python :: 2.5
|
||||
Classifier: Programming Language :: Python :: 2.6
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3.0
|
||||
Classifier: Programming Language :: Python :: 3.1
|
||||
Classifier: Programming Language :: Python :: 3.2
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Topic :: Security :: Cryptography
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
Classifier: Topic :: Utilities
|
||||
Download-URL: https://github.com/isislovecruft/python-gnupg/archives/develop.zip
|
||||
Description:: This module allows easy access to GnuPG's key management, encryption and signature functionality from Python programs. It is intended for use with Python 2.6 or greater.
|
||||
Classifier:: Development Status :: 3 - Alpha
|
||||
License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
||||
Operating System :: MacOS :: MacOS X
|
||||
Operating System :: Microsoft :: Windows :: Windows 7
|
||||
Operating System :: Microsoft :: Windows :: Windows XP
|
||||
Operating System :: POSIX :: BSD
|
||||
Operating System :: POSIX :: Linux
|
||||
Classifier:: Intended Audience :: Developers
|
||||
Classifier:: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
||||
Classifier:: Programming Language :: Python
|
||||
Classifier:: Programming Language :: Python :: 2
|
||||
Classifier:: Programming Language :: Python :: 3
|
||||
Classifier:: Programming Language :: Python :: 2.6
|
||||
Classifier:: Programming Language :: Python :: 2.7
|
||||
Classifier:: Programming Language :: Python :: 3.0
|
||||
Classifier:: Programming Language :: Python :: 3.1
|
||||
Classifier:: Programming Language :: Python :: 3.2
|
||||
Classifier:: Topic :: Security :: Cryptography
|
||||
Classifier:: Topic :: Software Development :: Libraries :: Python Modules
|
||||
Classifier:: Topic :: Utilities
|
||||
|
|
12
README
12
README
|
@ -1,12 +0,0 @@
|
|||
python-gnupg
|
||||
============
|
||||
|
||||
Fork of python-gnupg-0.3.2, patched to remove Popen([...], shell=True).
|
||||
|
||||
Installation
|
||||
------------
|
||||
To install this package from a source distribution, do the following.
|
||||
|
||||
1. Extract all the files in the distribution archive to some directory on your system.
|
||||
2. In that directory, run "python setup.py install".
|
||||
3. Optionally, run "python test_gnupg.py" to ensure that the package is working as expected.
|
|
@ -0,0 +1,63 @@
|
|||
# python-gnupg #
|
||||
================
|
||||
|
||||
Fork of python-gnupg-0.3.2, patched to remove ```Popen([...], shell=True)```.
|
||||
|
||||
### Installation ###
|
||||
|
||||
#### From this git repository ####
|
||||
To install this package from this git repository, do:
|
||||
|
||||
```
|
||||
git clone https://github.com/isislovecruft/python-gnupg.git
|
||||
cd python-gnupg
|
||||
make install
|
||||
make test
|
||||
```
|
||||
|
||||
Optionally to build the documentation after installation, do:
|
||||
```
|
||||
make docs
|
||||
```
|
||||
|
||||
To get started using python-gnupg's API, see the [online documentation](https://python-gnupg.readthedocs.org/en/latest/),
|
||||
and import the module like so:
|
||||
```
|
||||
>>> import gnupg
|
||||
```
|
||||
|
||||
The primary interface class you'll likely want to interact with is
|
||||
[```gnupg.GPG```](https://python-gnupg.readthedocs.org/en/latest/gnupg.html#gpg):
|
||||
```
|
||||
>>> gpg = gnupg.GPG(gpgbinary='/usr/bin/gpg',
|
||||
... gpghome='./keys',
|
||||
... pubring='pubring.gpg',
|
||||
... secring='secring.gpg')
|
||||
>>> batch_key_input = gpg.gen_key_input()
|
||||
>>> print batch_key_input
|
||||
Key-Type: RSA
|
||||
Name-Email: isis@wintermute
|
||||
Name-Comment: Generated by gnupg.py
|
||||
Key-Length: 4096
|
||||
Name-Real: Autogenerated Key
|
||||
%pubring /home/isis/code/python-gnupg/keys/pubring.gpg
|
||||
%secring /home/isis/code/python-gnupg/keys/secring.gpg
|
||||
%commit
|
||||
|
||||
>>> key = gpg.gen_key(batch_key_input)
|
||||
>>> print key.fingerprint
|
||||
245D8FA30F543B742053949F553C0E154F2E7A98
|
||||
|
||||
```
|
||||
|
||||
#### From PyPI ####
|
||||
Hold your horses, boy. I haven't finished development, so the packages on
|
||||
[PyPI](https://pypi.python.org) are still the old versions belonging to the
|
||||
other authors.
|
||||
|
||||
### Bug Reports & Feature Requests ###
|
||||
Our bugtracker is [here](https://leap.se/code/projects/eip_server/issue/new).
|
||||
|
||||
Please use that for bug reports and feature requests instead of github's
|
||||
tracker. We're using github for code commenting and review between
|
||||
collaborators.
|
|
@ -0,0 +1,60 @@
|
|||
-*- mode: org -*-
|
||||
|
||||
* Keyring separation :keyseparation:
|
||||
** TODO in GPG.gen_key() :keyseparation:gen_key:
|
||||
It would be nice to have an option for gen_key() [[gnupg.py:927]] to
|
||||
automatically switch before key generation to a new tempfile.mkdtemp()
|
||||
directory, with a new keyring and secring, and then to rename either the
|
||||
directory or the keyrings with the long keyid of the key which was freshly
|
||||
generated.
|
||||
|
||||
* I/O :io:
|
||||
** TODO in GPG.__make_args() :io:makeargs:
|
||||
It would be nice to make the file descriptors for communication with the GnuPG
|
||||
process configurable, and not the default, hard-coded 0=stdin 1=stdout
|
||||
2=stderr.
|
||||
|
||||
* Key editing :editkey:
|
||||
** TODO add '--edit-key' feature :editkey:
|
||||
see :compatibility:gen__key_input:
|
||||
|
||||
* Compatibility between GnuPG versions :compatibility:
|
||||
** TODO GnuPG>=2.1.0 won't allow key generation with preset passphrase
|
||||
*** TODO in GPG.gen__key_input() :compatibility:gen_key_input:
|
||||
In the docstring of GPG.gen__key_input() [[gnupg.py:1068]], for the parameter
|
||||
'passphrase', it is explained that:
|
||||
|
||||
:param str passphrase: The passphrase for the new key. The default is
|
||||
to not use any passphrase. Note that
|
||||
GnuPG>=2.1.x will not allow you to specify a
|
||||
passphrase for batch key generation -- GnuPG
|
||||
will ignore the ``passphrase`` parameter, stop,
|
||||
and ask the user for the new passphrase.
|
||||
However, we can put the command '%no-protection'
|
||||
into the batch key generation file to allow a
|
||||
passwordless key to be created, which can then
|
||||
have its passphrase set later with '--edit-key'.
|
||||
|
||||
If we add a GnuPG version detection feature (the version string is already
|
||||
obtained in GPG.___init___() [[gnupg.py:407]]), then we can automatically chain
|
||||
GPG.gen__key_input() to another new feature for '--edit-key'. This chaining
|
||||
would likely need to happen here [[gnupg.py:1146]].
|
||||
|
||||
*** TODO add '--edit-key' feature :editkey:
|
||||
This would be necessary for adding a passphrase to the key after passwordless
|
||||
generation in GnuPG>=2.1.0.
|
||||
|
||||
* Code cleanup :cleanup:
|
||||
** TODO in parsers.__sanitise() :cleanup:sanitise:
|
||||
Ughh...this is the ugliest code I think I've ever written. It works, but I
|
||||
worry that it is fragile, not to mention *I* have trouble reading it, and I
|
||||
fucking wrote the damn thing. There's probably not much that could be done to
|
||||
make it more Pythonic, because type checks and input validation are pretty much
|
||||
intrinsically non-Pythonic. But did i mention that it's ugly? I'm sure these
|
||||
functions would be pretty glad to get a shower, shave, and haircut.
|
||||
|
||||
** TODO in parsers.__is_allowed() :cleanup:is_allowed:
|
||||
There is a lot of madness dealing with stupid things like hyphens
|
||||
vs. underscores, and lists of options vs. strings. This can *definitely* be
|
||||
cleaned up.
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,153 @@
|
|||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " gettext to make PO message catalogs"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-gnupg.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-gnupg.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/python-gnupg"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-gnupg"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,386 @@
|
|||
/* custom stuff I put in FIXME where is it "supposed" to go? */
|
||||
|
||||
div.admonition-todo
|
||||
{
|
||||
border: 1px solid red;
|
||||
background-color: #Fdd;
|
||||
}
|
||||
|
||||
div.admonition-todo p.admonition-title
|
||||
{
|
||||
margin: 0;
|
||||
color: red;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
p.admonition-title
|
||||
{
|
||||
font-size: 120%;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
dl.class>dt, dl.interface>dt, dl.function>dt, dl.staticmethod>dt
|
||||
{
|
||||
font-size: 150%;
|
||||
background-color:#ddd;
|
||||
}
|
||||
|
||||
dl.method>dt
|
||||
{
|
||||
background-color: #eee;
|
||||
border-bottom: 2px solid #ddd;
|
||||
}
|
||||
|
||||
dl.method:hover
|
||||
{
|
||||
background-color:#ffd;
|
||||
}
|
||||
|
||||
/** end custom */
|
||||
|
||||
html {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
background: #FFF url(bg-page.png) top left repeat-x;
|
||||
}
|
||||
|
||||
body {
|
||||
line-height: 1.5;
|
||||
margin: auto;
|
||||
padding: 0px;
|
||||
font-family: "DejaVu Sans", Arial, Helvetica, sans-serif;
|
||||
min-width: 59em;
|
||||
max-width: 70em;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
padding: 8px;
|
||||
font-size: 11px;
|
||||
text-align: center;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* link colors and text decoration */
|
||||
|
||||
a:link {
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
color: #dc3c01;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
color: #892601;
|
||||
}
|
||||
|
||||
a:hover, a:active {
|
||||
text-decoration: underline;
|
||||
color: #ff4500;
|
||||
}
|
||||
|
||||
/* Some headers act as anchors, don't give them a hover effect */
|
||||
|
||||
h1 a:hover, a:active {
|
||||
text-decoration: none;
|
||||
color: #0c3762;
|
||||
}
|
||||
|
||||
h2 a:hover, a:active {
|
||||
text-decoration: none;
|
||||
color: #0c3762;
|
||||
}
|
||||
|
||||
h3 a:hover, a:active {
|
||||
text-decoration: none;
|
||||
color: #0c3762;
|
||||
}
|
||||
|
||||
h4 a:hover, a:active {
|
||||
text-decoration: none;
|
||||
color: #0c3762;
|
||||
}
|
||||
|
||||
a.headerlink {
|
||||
color: #a7ce38;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
a.headerlink:hover {
|
||||
color: #a7ce38;
|
||||
}
|
||||
|
||||
/* basic text elements */
|
||||
|
||||
div.content {
|
||||
margin-top: 20px;
|
||||
margin-left: 40px;
|
||||
margin-right: 40px;
|
||||
margin-bottom: 50px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* heading and navigation */
|
||||
|
||||
div.header {
|
||||
position: relative;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
height: 85px;
|
||||
/* background: #eeeeee; */
|
||||
padding: 0 40px;
|
||||
}
|
||||
div.header h1 {
|
||||
font-size: 1.6em;
|
||||
font-weight: normal;
|
||||
letter-spacing: 1px;
|
||||
color: #0c3762;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding-top: 15px;
|
||||
}
|
||||
div.header h1 a {
|
||||
font-weight: normal;
|
||||
color: #0c3762;
|
||||
}
|
||||
div.header h2 {
|
||||
font-size: 1.3em;
|
||||
font-weight: normal;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
color: #aaa;
|
||||
border: 0;
|
||||
margin-top: -3px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.header img.rightlogo {
|
||||
float: right;
|
||||
}
|
||||
|
||||
|
||||
div.title {
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
color: #0c3762;
|
||||
border-bottom: dotted thin #e0e0e0;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
div.topnav {
|
||||
/* background: #e0e0e0; */
|
||||
}
|
||||
div.topnav p {
|
||||
margin-top: 0;
|
||||
margin-left: 40px;
|
||||
margin-right: 40px;
|
||||
margin-bottom: 0px;
|
||||
text-align: right;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
div.bottomnav {
|
||||
background: #eeeeee;
|
||||
}
|
||||
div.bottomnav p {
|
||||
margin-right: 40px;
|
||||
text-align: right;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
a.uplink {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
|
||||
/* contents box */
|
||||
|
||||
table.index {
|
||||
margin: 0px 0px 30px 30px;
|
||||
padding: 1px;
|
||||
border-width: 1px;
|
||||
border-style: dotted;
|
||||
border-color: #e0e0e0;
|
||||
}
|
||||
table.index tr.heading {
|
||||
background-color: #e0e0e0;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
table.index tr.index {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
table.index td {
|
||||
padding: 5px 20px;
|
||||
}
|
||||
|
||||
table.index a:link, table.index a:visited {
|
||||
font-weight: normal;
|
||||
text-decoration: none;
|
||||
color: #dc3c01;
|
||||
}
|
||||
table.index a:hover, table.index a:active {
|
||||
text-decoration: underline;
|
||||
color: #ff4500;
|
||||
}
|
||||
|
||||
|
||||
/* Haiku User Guide styles and layout */
|
||||
|
||||
/* Rounded corner boxes */
|
||||
/* Common declarations */
|
||||
div.admonition {
|
||||
-webkit-border-radius: 10px;
|
||||
-khtml-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
border-style: dotted;
|
||||
border-width: thin;
|
||||
border-color: #dcdcdc;
|
||||
padding: 10px 15px 10px 15px;
|
||||
margin-bottom: 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
div.note {
|
||||
padding: 10px 15px 10px 80px;
|
||||
background: #e4ffde url(alert_info_32.png) 15px 15px no-repeat;
|
||||
min-height: 42px;
|
||||
}
|
||||
div.warning {
|
||||
padding: 10px 15px 10px 80px;
|
||||
background: #fffbc6 url(alert_warning_32.png) 15px 15px no-repeat;
|
||||
min-height: 42px;
|
||||
}
|
||||
div.seealso {
|
||||
background: #e4ffde;
|
||||
}
|
||||
|
||||
/* More layout and styles */
|
||||
h1 {
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
color: #0c3762;
|
||||
border-bottom: dotted thin #e0e0e0;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.2em;
|
||||
font-weight: normal;
|
||||
color: #0c3762;
|
||||
border-bottom: dotted thin #e0e0e0;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.1em;
|
||||
font-weight: normal;
|
||||
color: #0c3762;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.0em;
|
||||
font-weight: normal;
|
||||
color: #0c3762;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
p.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
ol {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 5px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
li {
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
div.content ul > li {
|
||||
-moz-background-clip:border;
|
||||
-moz-background-inline-policy:continuous;
|
||||
-moz-background-origin:padding;
|
||||
background: transparent url(bullet_orange.png) no-repeat scroll left 0.45em;
|
||||
list-style-image: none;
|
||||
list-style-type: none;
|
||||
padding: 0 0 0 1.666em;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
tt {
|
||||
background-color: #e2e2e2;
|
||||
font-size: 1.0em;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
pre {
|
||||
border-color: #0c3762;
|
||||
border-style: dotted;
|
||||
border-width: thin;
|
||||
margin: 0 0 12px 0;
|
||||
padding: 0.8em;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: 0;
|
||||
border-right: 0;
|
||||
border-left: 0;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* printer only pretty stuff */
|
||||
@media print {
|
||||
.noprint {
|
||||
display: none;
|
||||
}
|
||||
/* for acronyms we want their definitions inlined at print time */
|
||||
acronym[title]:after {
|
||||
font-size: small;
|
||||
content: " (" attr(title) ")";
|
||||
font-style: italic;
|
||||
}
|
||||
/* and not have mozilla dotted underline */
|
||||
acronym {
|
||||
border: none;
|
||||
}
|
||||
div.topnav, div.bottomnav, div.header, table.index {
|
||||
display: none;
|
||||
}
|
||||
div.content {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
html {
|
||||
background: #FFF;
|
||||
}
|
||||
}
|
||||
|
||||
.viewcode-back {
|
||||
font-family: "DejaVu Sans", Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
div.viewcode-block:target {
|
||||
background-color: #f4debf;
|
||||
border-top: 1px solid #ac9;
|
||||
border-bottom: 1px solid #ac9;
|
||||
margin: -1px -12px;
|
||||
padding: 0 12px;
|
||||
}
|
|
@ -0,0 +1,306 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# python-gnupg documentation build configuration file, created by
|
||||
# sphinx-quickstart on Fri Apr 5 22:38:47 2013.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.insert(0, os.path.abspath('./../gnupg'))
|
||||
sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- Autodoc settings ----------------------------------------------------------
|
||||
## trying to set this somewhere...
|
||||
autodoc_member_order = 'bysource'
|
||||
autodoc_default_flags = ['members', 'show-inheritance', 'undoc-members']
|
||||
autoclass_content = 'both'
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'python-gnupg'
|
||||
copyright = u'2013, Isis Agora Lovecruft'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
from gnupg import __version__
|
||||
version = __version__
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = __version__
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'default'
|
||||
html_theme = 'scrolls'
|
||||
html_theme = 'traditional'
|
||||
html_theme = 'nature'
|
||||
html_theme = 'pyramid'
|
||||
html_theme = 'agogo'
|
||||
html_theme = 'haiku'
|
||||
html_theme_options = {
|
||||
# 'stickysidebar': 'true',
|
||||
# 'rightsidebar':'true',
|
||||
'nosidebar': 'false',
|
||||
# 'full_logo': 'false'
|
||||
'sidebarwidth': '300'
|
||||
}
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
html_show_sourcelink = False
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
html_show_copyright = False
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'python-gnupgdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'python-gnupg.tex', u'python-gnupg Documentation',
|
||||
u'Isis Agora Lovecruft', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'python-gnupg', u'python-gnupg Documentation',
|
||||
[u'Isis Agora Lovecruft'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output ------------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'python-gnupg', u'python-gnupg Documentation',
|
||||
u'Isis Agora Lovecruft', 'python-gnupg', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
|
||||
# -- Options for Epub output ---------------------------------------------------
|
||||
|
||||
# Bibliographic Dublin Core info.
|
||||
epub_title = u'python-gnupg'
|
||||
epub_author = u'Isis Agora Lovecruft'
|
||||
epub_publisher = u'Isis Agora Lovecruft'
|
||||
epub_copyright = u'2013, Isis Agora Lovecruft'
|
||||
|
||||
# The language of the text. It defaults to the language option
|
||||
# or en if the language is not set.
|
||||
#epub_language = ''
|
||||
|
||||
# The scheme of the identifier. Typical schemes are ISBN or URL.
|
||||
#epub_scheme = ''
|
||||
|
||||
# The unique identifier of the text. This can be a ISBN number
|
||||
# or the project homepage.
|
||||
#epub_identifier = ''
|
||||
|
||||
# A unique identification for the text.
|
||||
#epub_uid = ''
|
||||
|
||||
# A tuple containing the cover image and cover page html template filenames.
|
||||
#epub_cover = ()
|
||||
|
||||
# HTML files that should be inserted before the pages created by sphinx.
|
||||
# The format is a list of tuples containing the path and title.
|
||||
#epub_pre_files = []
|
||||
|
||||
# HTML files shat should be inserted after the pages created by sphinx.
|
||||
# The format is a list of tuples containing the path and title.
|
||||
#epub_post_files = []
|
||||
|
||||
# A list of files that should not be packed into the epub file.
|
||||
#epub_exclude_files = []
|
||||
|
||||
# The depth of the table of contents in toc.ncx.
|
||||
#epub_tocdepth = 3
|
||||
|
||||
# Allow duplicate toc entries.
|
||||
#epub_tocdup = True
|
|
@ -0,0 +1,7 @@
|
|||
gnupg Module
|
||||
============
|
||||
|
||||
.. automodule:: gnupg
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
|
@ -0,0 +1,24 @@
|
|||
.. python-gnupg documentation master file, created by
|
||||
sphinx-quickstart on Fri Apr 5 22:38:47 2013.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
python-gnupg documentation
|
||||
==========================
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
|
||||
gnupg
|
||||
parsers
|
||||
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
@ECHO OFF
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set BUILDDIR=_build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||
set I18NSPHINXOPTS=%SPHINXOPTS% .
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "help" (
|
||||
:help
|
||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||
echo. html to make standalone HTML files
|
||||
echo. dirhtml to make HTML files named index.html in directories
|
||||
echo. singlehtml to make a single large HTML file
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and a HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. devhelp to make HTML files and a Devhelp project
|
||||
echo. epub to make an epub
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. text to make text files
|
||||
echo. man to make manual pages
|
||||
echo. texinfo to make Texinfo files
|
||||
echo. gettext to make PO message catalogs
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||
del /q /s %BUILDDIR%\*
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "singlehtml" (
|
||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\python-gnupg.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\python-gnupg.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "devhelp" (
|
||||
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "epub" (
|
||||
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "text" (
|
||||
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The text files are in %BUILDDIR%/text.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "man" (
|
||||
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "texinfo" (
|
||||
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "gettext" (
|
||||
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.The overview file is in %BUILDDIR%/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in %BUILDDIR%/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
:end
|
|
@ -0,0 +1,7 @@
|
|||
parsers Module
|
||||
==============
|
||||
|
||||
.. automodule:: parsers
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
|
@ -0,0 +1,2 @@
|
|||
Sphinx>=1.1
|
||||
psutil>=0.5.1
|
75
setup.py
75
setup.py
|
@ -1,37 +1,48 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
from distutils.core import setup
|
||||
|
||||
from gnupg import __version__ as version
|
||||
import versioneer
|
||||
versioneer.versionfile_source = 'src/_version.py'
|
||||
versioneer.versionfile_build = 'gnupg/_version.py'
|
||||
versioneer.tag_prefix = ''
|
||||
versioneer.parentdir_prefix = 'python-gnupg-'
|
||||
|
||||
__author__ = "Isis Agora Lovecruft"
|
||||
__contact__ = 'isis@leap.se'
|
||||
|
||||
setup(name = "python-gnupg",
|
||||
description="A wrapper for the Gnu Privacy Guard (GPG or GnuPG)",
|
||||
long_description = "This module allows easy access to GnuPG's key \
|
||||
description="A wrapper for the Gnu Privacy Guard (GPG or GnuPG)",
|
||||
long_description = "This module allows easy access to GnuPG's key \
|
||||
management, encryption and signature functionality from Python programs. \
|
||||
It is intended for use with Python 2.4 or greater.",
|
||||
license="""Copyright (C) 2008-2012 by Vinay Sajip. All Rights Reserved. See LICENSE for license.""",
|
||||
version=version,
|
||||
author="Vinay Sajip",
|
||||
author_email="vinay_sajip@red-dove.com",
|
||||
maintainer="Vinay Sajip",
|
||||
maintainer_email="vinay_sajip@red-dove.com",
|
||||
url="http://packages.python.org/python-gnupg/index.html",
|
||||
py_modules=["gnupg"],
|
||||
platforms="No particular restrictions",
|
||||
download_url="http://python-gnupg.googlecode.com/files/python-gnupg-%s.tar.gz" % version,
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
"Intended Audience :: Developers",
|
||||
'License :: OSI Approved :: BSD License',
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 2",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 2.4",
|
||||
"Programming Language :: Python :: 2.5",
|
||||
"Programming Language :: Python :: 2.6",
|
||||
"Programming Language :: Python :: 2.7",
|
||||
"Programming Language :: Python :: 3.0",
|
||||
"Programming Language :: Python :: 3.1",
|
||||
"Programming Language :: Python :: 3.2",
|
||||
"Operating System :: OS Independent",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules"
|
||||
]
|
||||
)
|
||||
It is intended for use with Python 2.6 or greater.",
|
||||
license="""Copyright © 2013 Isis Lovecruft, et.al. see LICENSE file.""",
|
||||
version=versioneer.get_version(),
|
||||
cmdclass=versioneer.get_cmdclass(),
|
||||
author=__author__,
|
||||
author_email=__contact__,
|
||||
maintainer=__author__,
|
||||
maintainer_email=__contact__,
|
||||
url="https://github.com/isislovecruft/python-gnupg",
|
||||
package_dir={'gnupg': 'src'},
|
||||
packages=['gnupg'],
|
||||
platforms="Linux, BSD, OSX, Windows",
|
||||
download_url="https://github.com/isislovecruft/python-gnupg/archive/develop.zip",
|
||||
classifiers=[
|
||||
'Development Status :: 4 - Alpha',
|
||||
"Intended Audience :: Developers",
|
||||
'Classifier:: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)',
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 2",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 2.6",
|
||||
"Programming Language :: Python :: 2.7",
|
||||
"Programming Language :: Python :: 3.0",
|
||||
"Programming Language :: Python :: 3.1",
|
||||
"Programming Language :: Python :: 3.2",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
'Classifier:: Topic :: Security :: Cryptography',
|
||||
'Classifier:: Topic :: Software Development :: Libraries :: Python Modules',
|
||||
'Classifier:: Topic :: Utilities',]
|
||||
)
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
import gnupg
|
||||
import copyleft
|
||||
|
||||
from gnupg import GPG
|
||||
|
||||
from ._version import get_versions
|
||||
__version__ = get_versions()['version']
|
||||
|
||||
gnupg.__version__ = __version__
|
||||
gnupg.__author__ = 'Isis Agora Lovecruft'
|
||||
gnupg.__contact__ = 'isis@leap.se'
|
||||
gnupg.__url__ = 'https://github.com/isislovecruft/python-gnupg'
|
||||
gnupg.__license__ = copyleft.disclaimer
|
||||
|
||||
__all__ = ["GPG"]
|
||||
|
||||
del gnupg
|
||||
del copyleft
|
||||
del get_versions
|
||||
del _version
|
|
@ -0,0 +1,172 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# This file is part of python-gnupg, a Python wrapper aroung GnuPG, and it was
|
||||
# taken from https://gist.github.com/vsajip/758430 on the 14th of May, 2013. It
|
||||
# has also been included in the 'logutils' Python module, see
|
||||
# https://code.google.com/p/logutils/ .
|
||||
#
|
||||
# The original copyright and license text are as follows:
|
||||
# |
|
||||
# | Copyright (C) 2010-2012 Vinay Sajip. All rights reserved.
|
||||
# | Licensed under the new BSD license.
|
||||
# |
|
||||
#
|
||||
# This file is part of python-gnupg, a Python wrapper around GnuPG.
|
||||
# Copyright © 2013 Isis Lovecruft, Andrej B.
|
||||
# © 2008-2012 Vinay Sajip
|
||||
# © 2005 Steve Traugott
|
||||
# © 2004 A.M. Kuchling
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program 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 Affero General Public License for more details.
|
||||
|
||||
|
||||
import ctypes
|
||||
import logging
|
||||
import os
|
||||
|
||||
class ColorizingStreamHandler(logging.StreamHandler):
|
||||
# color names to indices
|
||||
color_map = {
|
||||
'black': 0,
|
||||
'red': 1,
|
||||
'green': 2,
|
||||
'yellow': 3,
|
||||
'blue': 4,
|
||||
'magenta': 5,
|
||||
'cyan': 6,
|
||||
'white': 7,
|
||||
}
|
||||
|
||||
#levels to (background, foreground, bold/intense)
|
||||
if os.name == 'nt':
|
||||
level_map = {
|
||||
logging.DEBUG: (None, 'blue', True),
|
||||
logging.INFO: (None, 'green', False),
|
||||
logging.WARNING: (None, 'yellow', True),
|
||||
logging.ERROR: (None, 'red', True),
|
||||
logging.CRITICAL: ('red', 'white', True),
|
||||
}
|
||||
else:
|
||||
level_map = {
|
||||
logging.DEBUG: (None, 'blue', False),
|
||||
logging.INFO: (None, 'green', False),
|
||||
logging.WARNING: (None, 'yellow', False),
|
||||
logging.ERROR: (None, 'red', False),
|
||||
logging.CRITICAL: ('red', 'white', True),
|
||||
}
|
||||
csi = '\x1b['
|
||||
reset = '\x1b[0m'
|
||||
|
||||
@property
|
||||
def is_tty(self):
|
||||
isatty = getattr(self.stream, 'isatty', None)
|
||||
return isatty and isatty()
|
||||
|
||||
def emit(self, record):
|
||||
try:
|
||||
message = self.format(record)
|
||||
stream = self.stream
|
||||
if not self.is_tty:
|
||||
stream.write(message)
|
||||
else:
|
||||
self.output_colorized(message)
|
||||
stream.write(getattr(self, 'terminator', '\n'))
|
||||
self.flush()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
self.handleError(record)
|
||||
|
||||
if os.name != 'nt':
|
||||
def output_colorized(self, message):
|
||||
self.stream.write(message)
|
||||
else:
|
||||
import re
|
||||
ansi_esc = re.compile(r'\x1b\[((?:\d+)(?:;(?:\d+))*)m')
|
||||
|
||||
nt_color_map = {
|
||||
0: 0x00, # black
|
||||
1: 0x04, # red
|
||||
2: 0x02, # green
|
||||
3: 0x06, # yellow
|
||||
4: 0x01, # blue
|
||||
5: 0x05, # magenta
|
||||
6: 0x03, # cyan
|
||||
7: 0x07, # white
|
||||
}
|
||||
|
||||
def output_colorized(self, message):
|
||||
parts = self.ansi_esc.split(message)
|
||||
write = self.stream.write
|
||||
h = None
|
||||
fd = getattr(self.stream, 'fileno', None)
|
||||
if fd is not None:
|
||||
fd = fd()
|
||||
if fd in (1, 2): # stdout or stderr
|
||||
h = ctypes.windll.kernel32.GetStdHandle(-10 - fd)
|
||||
while parts:
|
||||
text = parts.pop(0)
|
||||
if text:
|
||||
write(text)
|
||||
if parts:
|
||||
params = parts.pop(0)
|
||||
if h is not None:
|
||||
params = [int(p) for p in params.split(';')]
|
||||
color = 0
|
||||
for p in params:
|
||||
if 40 <= p <= 47:
|
||||
color |= self.nt_color_map[p - 40] << 4
|
||||
elif 30 <= p <= 37:
|
||||
color |= self.nt_color_map[p - 30]
|
||||
elif p == 1:
|
||||
color |= 0x08 # foreground intensity on
|
||||
elif p == 0: # reset to default color
|
||||
color = 0x07
|
||||
else:
|
||||
pass # error condition ignored
|
||||
ctypes.windll.kernel32.SetConsoleTextAttribute(h, color)
|
||||
|
||||
def colorize(self, message, record):
|
||||
if record.levelno in self.level_map:
|
||||
bg, fg, bold = self.level_map[record.levelno]
|
||||
params = []
|
||||
if bg in self.color_map:
|
||||
params.append(str(self.color_map[bg] + 40))
|
||||
if fg in self.color_map:
|
||||
params.append(str(self.color_map[fg] + 30))
|
||||
if bold:
|
||||
params.append('1')
|
||||
if params:
|
||||
message = ''.join((self.csi, ';'.join(params),
|
||||
'm', message, self.reset))
|
||||
return message
|
||||
|
||||
def format(self, record):
|
||||
message = logging.StreamHandler.format(self, record)
|
||||
if self.is_tty:
|
||||
# Don't colorize any traceback
|
||||
parts = message.split('\n', 1)
|
||||
parts[0] = self.colorize(parts[0], record)
|
||||
message = '\n'.join(parts)
|
||||
return message
|
||||
|
||||
def main():
|
||||
root = logging.getLogger()
|
||||
root.setLevel(logging.DEBUG)
|
||||
root.addHandler(ColorizingStreamHandler())
|
||||
logging.debug('DEBUG')
|
||||
logging.info('INFO')
|
||||
logging.warning('WARNING')
|
||||
logging.error('ERROR')
|
||||
logging.critical('CRITICAL')
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,97 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# This file is part of python-gnupg, a Python wrapper around GnuPG.
|
||||
# Copyright © 2013 Isis Lovecruft, Andrej B.
|
||||
# © 2008-2012 Vinay Sajip
|
||||
# © 2005 Steve Traugott
|
||||
# © 2004 A.M. Kuchling
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program 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 Affero General Public License for more details.
|
||||
'''log.py
|
||||
----------
|
||||
Logging module for python-gnupg.
|
||||
'''
|
||||
|
||||
from __future__ import print_function
|
||||
from datetime import datetime
|
||||
from functools import wraps
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import os
|
||||
|
||||
import _ansistrm
|
||||
|
||||
try:
|
||||
from logging import NullHandler
|
||||
except:
|
||||
class NullHandler(logging.Handler):
|
||||
def handle(self, record):
|
||||
pass
|
||||
|
||||
|
||||
GNUPG_STATUS_LEVEL = 9
|
||||
|
||||
def status(self, message, *args, **kwargs):
|
||||
"""LogRecord for GnuPG internal status messages."""
|
||||
if self.isEnabledFor(GNUPG_STATUS_LEVEL):
|
||||
self._log(GNUPG_STATUS_LEVEL, message, args, **kwargs)
|
||||
|
||||
@wraps(logging.Logger)
|
||||
def create_logger(level=logging.NOTSET):
|
||||
"""Create a logger for python-gnupg at a specific message level.
|
||||
|
||||
:type level: int or str
|
||||
:param level: A string or an integer for the lowest level to log.
|
||||
Available levels:
|
||||
int str description
|
||||
0 NOTSET Disable all logging.
|
||||
9 GNUPG Log GnuPG's internal status messages.
|
||||
10 DEBUG Log module level debuging messages.
|
||||
20 INFO Normal user-level messages.
|
||||
30 WARN Warning messages.
|
||||
40 ERROR Error messages and tracebacks.
|
||||
50 CRITICAL Unhandled exceptions and tracebacks.
|
||||
"""
|
||||
_test = os.path.join(os.getcwd(), 'tests')
|
||||
_now = datetime.now().strftime("%Y-%m-%d_%H%M%S")
|
||||
_fn = os.path.join(_test, "%s_test_gnupg.log" % _now)
|
||||
_fmt = "%(relativeCreated)-4d L%(lineno)-4d:%(funcName)-18.18s %(levelname)-7.7s %(message)s"
|
||||
|
||||
## Add the GNUPG_STATUS_LEVEL LogRecord to all Loggers in the module:
|
||||
logging.addLevelName(GNUPG_STATUS_LEVEL, "GNUPG")
|
||||
logging.Logger.status = status
|
||||
|
||||
|
||||
if level > logging.NOTSET:
|
||||
logging.basicConfig(level=level, filename=_fn,
|
||||
filemode="a", format=_fmt)
|
||||
logging.captureWarnings(True)
|
||||
logging.logThreads = True
|
||||
|
||||
colouriser = _ansistrm.ColorizingStreamHandler
|
||||
colouriser.level_map[9] = (None, 'blue', False)
|
||||
colouriser.level_map[10] = (None, 'cyan', False)
|
||||
handler = colouriser(stream=sys.stderr)
|
||||
handler.setLevel(level)
|
||||
|
||||
formatr = logging.Formatter(_fmt)
|
||||
handler.setFormatter(formatr)
|
||||
print("Starting the logger...")
|
||||
else:
|
||||
handler = NullHandler()
|
||||
print("GnuPG logging disabled...")
|
||||
|
||||
log = logging.getLogger('gnupg')
|
||||
log.addHandler(handler)
|
||||
log.setLevel(level)
|
||||
log.info("Log opened: %s UTC" % datetime.ctime(datetime.utcnow()))
|
||||
return log
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,470 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# This file is part of python-gnupg, a Python wrapper around GnuPG.
|
||||
# Copyright © 2013 Isis Lovecruft, Andrej B.
|
||||
# © 2008-2012 Vinay Sajip
|
||||
# © 2005 Steve Traugott
|
||||
# © 2004 A.M. Kuchling
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program 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 Affero General Public License for more details.
|
||||
'''
|
||||
util.py
|
||||
----------
|
||||
Extra utilities for python-gnupg.
|
||||
'''
|
||||
|
||||
from datetime import datetime
|
||||
from socket import gethostname
|
||||
|
||||
import codecs
|
||||
import encodings
|
||||
import os
|
||||
import time
|
||||
import threading
|
||||
import random
|
||||
import string
|
||||
import sys
|
||||
|
||||
import _logger
|
||||
|
||||
try:
|
||||
from io import StringIO
|
||||
from io import BytesIO
|
||||
except ImportError:
|
||||
from cStringIO import StringIO
|
||||
|
||||
try:
|
||||
unicode
|
||||
_py3k = False
|
||||
try:
|
||||
isinstance(__name__, basestring)
|
||||
except NameError:
|
||||
msg = "Sorry, python-gnupg requires a Python version with proper"
|
||||
msg += " unicode support. Please upgrade to Python>=2.3."
|
||||
raise SystemExit(msg)
|
||||
except NameError:
|
||||
_py3k = True
|
||||
|
||||
|
||||
## Directory shortcuts:
|
||||
_here = os.getcwd()
|
||||
_test = os.path.join(os.path.join(_here, 'tests'), 'tmp') ## ./tests/tmp
|
||||
_user = os.environ.get('HOME') ## $HOME
|
||||
_ugpg = os.path.join(_user, '.gnupg') ## $HOME/.gnupg
|
||||
_conf = os.path.join(os.path.join(_user, '.config'), 'python-gnupg')
|
||||
## $HOME/.config/python-gnupg
|
||||
|
||||
## Logger is disabled by default
|
||||
log = _logger.create_logger(0)
|
||||
|
||||
|
||||
def find_encodings(enc=None, system=False):
|
||||
"""Find functions for encoding translations for a specific codec.
|
||||
|
||||
:param str enc: The codec to find translation functions for. It will be
|
||||
normalized by converting to lowercase, excluding
|
||||
everything which is not ascii, and hyphens will be
|
||||
converted to underscores.
|
||||
|
||||
:param bool system: If True, find encodings based on the system's stdin
|
||||
encoding, otherwise assume utf-8.
|
||||
|
||||
:raises: :exc:LookupError if the normalized codec, ``enc``, cannot be
|
||||
found in Python's encoding translation map.
|
||||
"""
|
||||
if not enc:
|
||||
enc = 'utf-8'
|
||||
|
||||
if system:
|
||||
if getattr(sys.stdin, 'encoding', None) is None:
|
||||
enc = sys.stdin.encoding
|
||||
log.debug("Obtained encoding from stdin: %s" % enc)
|
||||
else:
|
||||
enc = 'ascii'
|
||||
|
||||
## have to have lowercase to work, see
|
||||
## http://docs.python.org/dev/library/codecs.html#standard-encodings
|
||||
enc = enc.lower()
|
||||
codec_alias = encodings.normalize_encoding(enc)
|
||||
|
||||
codecs.register(encodings.search_function)
|
||||
coder = codecs.lookup(codec_alias)
|
||||
|
||||
return coder
|
||||
|
||||
def _copy_data(instream, outstream):
|
||||
"""Copy data from one stream to another.
|
||||
|
||||
:type instream: :class:`io.BytesIO` or :class:`io.StringIO` or file
|
||||
:param instream: A byte stream or open file to read from.
|
||||
:param file outstream: The file descriptor of a tmpfile to write to.
|
||||
"""
|
||||
sent = 0
|
||||
|
||||
#try:
|
||||
# #assert (util._is_stream(instream)
|
||||
# # or isinstance(instream, file)), "instream not stream or file"
|
||||
# assert isinstance(outstream, file), "outstream is not a file"
|
||||
#except AssertionError as ae:
|
||||
# log.exception(ae)
|
||||
# return
|
||||
|
||||
coder = find_encodings()
|
||||
|
||||
while True:
|
||||
if ((_py3k and isinstance(instream, str)) or
|
||||
(not _py3k and isinstance(instream, basestring))):
|
||||
data = instream[:1024]
|
||||
instream = instream[1024:]
|
||||
else:
|
||||
data = instream.read(1024)
|
||||
if len(data) == 0:
|
||||
break
|
||||
sent += len(data)
|
||||
log.debug("Sending chunk %d bytes:\n%s"
|
||||
% (sent, data))
|
||||
try:
|
||||
outstream.write(data)
|
||||
except UnicodeError:
|
||||
try:
|
||||
outstream.write(coder.encode(data))
|
||||
except IOError:
|
||||
log.exception("Error sending data: Broken pipe")
|
||||
break
|
||||
except IOError:
|
||||
# Can get 'broken pipe' errors even when all data was sent
|
||||
log.exception('Error sending data: Broken pipe')
|
||||
break
|
||||
try:
|
||||
outstream.close()
|
||||
except IOError as ioe:
|
||||
log.error("Unable to close outstream %s:\r\t%s" % (outstream, ioe))
|
||||
else:
|
||||
log.debug("Closed outstream: %d bytes sent." % sent)
|
||||
|
||||
def _create_if_necessary(directory):
|
||||
"""Create the specified directory, if necessary.
|
||||
|
||||
:param str directory: The directory to use.
|
||||
:rtype: bool
|
||||
:returns: True if no errors occurred and the directory was created or
|
||||
existed beforehand, False otherwise.
|
||||
"""
|
||||
|
||||
if not os.path.isabs(directory):
|
||||
log.debug("Got non-absolute path: %s" % directory)
|
||||
directory = os.path.abspath(directory)
|
||||
|
||||
if not os.path.isdir(directory):
|
||||
log.info("Creating directory: %s" % directory)
|
||||
try:
|
||||
os.makedirs(directory, 0x1C0)
|
||||
except OSError as ose:
|
||||
log.error(ose, exc_info=1)
|
||||
return False
|
||||
else:
|
||||
log.debug("Created directory.")
|
||||
return True
|
||||
|
||||
def create_uid_email(username=None, hostname=None):
|
||||
"""Create an email address suitable for a UID on a GnuPG key.
|
||||
|
||||
:param str username: The username portion of an email address. If None,
|
||||
defaults to the username of the running Python
|
||||
process.
|
||||
|
||||
:param str hostname: The FQDN portion of an email address. If None, the
|
||||
hostname is obtained from gethostname(2).
|
||||
|
||||
:rtype: str
|
||||
:returns: A string formatted as <username>@<hostname>.
|
||||
"""
|
||||
if hostname:
|
||||
hostname = hostname.replace(' ', '_')
|
||||
if not username:
|
||||
try: username = os.environ['LOGNAME']
|
||||
except KeyError: username = os.environ['USERNAME']
|
||||
|
||||
if not hostname: hostname = gethostname()
|
||||
|
||||
uid = "%s@%s" % (username.replace(' ', '_'), hostname)
|
||||
else:
|
||||
username = username.replace(' ', '_')
|
||||
if (not hostname) and (username.find('@') == 0):
|
||||
uid = "%s@%s" % (username, gethostname())
|
||||
elif hostname:
|
||||
uid = "%s@%s" % (username, hostname)
|
||||
else:
|
||||
uid = username
|
||||
|
||||
return uid
|
||||
|
||||
def _find_binary(binary=None):
|
||||
"""Find the absolute path to the GnuPG binary.
|
||||
|
||||
Also run checks that the binary is not a symlink, and check that
|
||||
our process real uid has exec permissions.
|
||||
|
||||
:param str binary: The path to the GnuPG binary.
|
||||
:raises: :exc:RuntimeError if it appears that GnuPG is not installed.
|
||||
:rtype: str
|
||||
:returns: The absolute path to the GnuPG binary to use, if no exceptions
|
||||
occur.
|
||||
"""
|
||||
gpg_binary = None
|
||||
if binary is not None:
|
||||
if not os.path.isabs(binary):
|
||||
try: binary = _which(binary)[0]
|
||||
except IndexError as ie:
|
||||
log.error(ie.message)
|
||||
if binary is None:
|
||||
try: binary = _which('gpg')[0]
|
||||
except IndexError: raise RuntimeError("GnuPG is not installed!")
|
||||
try:
|
||||
assert os.path.isabs(binary), "Path to gpg binary not absolute"
|
||||
assert not os.path.islink(binary), "Path to gpg binary is symlink"
|
||||
assert os.access(binary, os.X_OK), "Lacking +x perms for gpg binary"
|
||||
except (AssertionError, AttributeError) as ae:
|
||||
log.error(ae.message)
|
||||
else:
|
||||
return binary
|
||||
|
||||
def _has_readwrite(path):
|
||||
"""
|
||||
Determine if the real uid/gid of the executing user has read and write
|
||||
permissions for a directory or a file.
|
||||
|
||||
:param str path: The path to the directory or file to check permissions
|
||||
for.
|
||||
:rtype: bool
|
||||
:returns: True if real uid/gid has read+write permissions, False otherwise.
|
||||
"""
|
||||
return os.access(path, os.R_OK ^ os.W_OK)
|
||||
|
||||
def _is_file(input):
|
||||
"""Check that the size of the thing which is supposed to be a filename has
|
||||
size greater than zero, without following symbolic links or using
|
||||
:func:os.path.isfile.
|
||||
|
||||
:param input: An object to check.
|
||||
:rtype: bool
|
||||
:returns: True if :param:input is file-like, False otherwise.
|
||||
"""
|
||||
try:
|
||||
assert os.lstat(input).st_size > 0, "not a file: %s" % input
|
||||
except (AssertionError, TypeError, IOError, OSError) as err:
|
||||
log.error(err.message, exc_info=1)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def _is_stream(input):
|
||||
"""Check that the input is a byte stream.
|
||||
|
||||
:param input: An object provided for reading from or writing to.
|
||||
:rtype: bool
|
||||
:returns: True if :param:input is a stream, False if otherwise.
|
||||
"""
|
||||
return isinstance(input, BytesIO) or isinstance(input, StringIO)
|
||||
|
||||
def _is_list_or_tuple(instance):
|
||||
"""Check that ``instance`` is a list or tuple.
|
||||
|
||||
:param instance: The object to type check.
|
||||
:rtype: bool
|
||||
:returns: True if ``instance`` is a list or tuple, False otherwise.
|
||||
"""
|
||||
return isinstance(instance, (list, tuple,))
|
||||
|
||||
def _make_binary_stream(s, encoding):
|
||||
"""
|
||||
xxx fill me in
|
||||
"""
|
||||
try:
|
||||
if _py3k:
|
||||
if isinstance(s, str):
|
||||
s = s.encode(encoding)
|
||||
else:
|
||||
if type(s) is not str:
|
||||
s = s.encode(encoding)
|
||||
from io import BytesIO
|
||||
rv = BytesIO(s)
|
||||
except ImportError:
|
||||
rv = StringIO(s)
|
||||
return rv
|
||||
|
||||
def _make_passphrase(length=None, save=False, file=None):
|
||||
"""Create a passphrase and write it to a file that only the user can read.
|
||||
|
||||
This is not very secure, and should not be relied upon for actual key
|
||||
passphrases.
|
||||
|
||||
:param int length: The length in bytes of the string to generate.
|
||||
|
||||
:param file file: The file to save the generated passphrase in. If not
|
||||
given, defaults to 'passphrase-<the real user id>-<seconds since
|
||||
epoch>' in the top-level directory.
|
||||
"""
|
||||
if not length:
|
||||
length = 40
|
||||
|
||||
passphrase = _make_random_string(length)
|
||||
|
||||
if save:
|
||||
ruid, euid, suid = os.getresuid()
|
||||
gid = os.getgid()
|
||||
now = time.mktime(time.gmtime())
|
||||
|
||||
if not file:
|
||||
filename = str('passphrase-%s-%s' % uid, now)
|
||||
file = os.path.join(_repo, filename)
|
||||
|
||||
with open(file, 'a') as fh:
|
||||
fh.write(passphrase)
|
||||
fh.flush()
|
||||
fh.close()
|
||||
os.chmod(file, 0600)
|
||||
os.chown(file, ruid, gid)
|
||||
|
||||
log.warn("Generated passphrase saved to %s" % file)
|
||||
return passphrase
|
||||
|
||||
def _make_random_string(length):
|
||||
"""Returns a random lowercase, uppercase, alphanumerical string.
|
||||
|
||||
:param int length: The length in bytes of the string to generate.
|
||||
"""
|
||||
chars = string.ascii_lowercase + string.ascii_uppercase + string.digits
|
||||
return ''.join(random.choice(chars) for x in range(length))
|
||||
|
||||
def _next_year():
|
||||
"""Get the date of today plus one year.
|
||||
|
||||
:rtype: str
|
||||
:returns: The date of this day next year, in the format '%Y-%m-%d'.
|
||||
"""
|
||||
now = datetime.now().__str__()
|
||||
date = now.split(' ', 1)[0]
|
||||
year, month, day = date.split('-', 2)
|
||||
next_year = str(int(year)+1)
|
||||
return '-'.join((next_year, month, day))
|
||||
|
||||
def _now():
|
||||
"""Get a timestamp for right now, formatted according to ISO 8601."""
|
||||
return datetime.isoformat(datetime.now())
|
||||
|
||||
def _threaded_copy_data(instream, outstream):
|
||||
"""Copy data from one stream to another in a separate thread.
|
||||
|
||||
Wraps ``_copy_data()`` in a :class:`threading.Thread`.
|
||||
|
||||
:type instream: :class:`io.BytesIO` or :class:`io.StringIO`
|
||||
:param instream: A byte stream to read from.
|
||||
:param file outstream: The file descriptor of a tmpfile to write to.
|
||||
"""
|
||||
copy_thread = threading.Thread(target=_copy_data,
|
||||
args=(instream, outstream))
|
||||
copy_thread.setDaemon(True)
|
||||
log.debug('%r, %r, %r', copy_thread, instream, outstream)
|
||||
copy_thread.start()
|
||||
return copy_thread
|
||||
|
||||
def _utc_epoch():
|
||||
"""Get the seconds since epoch for UTC."""
|
||||
return int(time.mktime(time.gmtime()))
|
||||
|
||||
def _which(executable, flags=os.X_OK):
|
||||
"""Borrowed from Twisted's :mod:twisted.python.proutils .
|
||||
|
||||
Search PATH for executable files with the given name.
|
||||
|
||||
On newer versions of MS-Windows, the PATHEXT environment variable will be
|
||||
set to the list of file extensions for files considered executable. This
|
||||
will normally include things like ".EXE". This fuction will also find files
|
||||
with the given name ending with any of these extensions.
|
||||
|
||||
On MS-Windows the only flag that has any meaning is os.F_OK. Any other
|
||||
flags will be ignored.
|
||||
|
||||
Note: This function does not help us prevent an attacker who can already
|
||||
manipulate the environment's PATH settings from placing malicious code
|
||||
higher in the PATH. It also does happily follows links.
|
||||
|
||||
:param str name: The name for which to search.
|
||||
:param int flags: Arguments to L{os.access}.
|
||||
:rtype: list
|
||||
:returns: A list of the full paths to files found, in the order in which
|
||||
they were found.
|
||||
"""
|
||||
result = []
|
||||
exts = filter(None, os.environ.get('PATHEXT', '').split(os.pathsep))
|
||||
path = os.environ.get('PATH', None)
|
||||
if path is None:
|
||||
return []
|
||||
for p in os.environ.get('PATH', '').split(os.pathsep):
|
||||
p = os.path.join(p, executable)
|
||||
if os.access(p, flags):
|
||||
result.append(p)
|
||||
for e in exts:
|
||||
pext = p + e
|
||||
if os.access(pext, flags):
|
||||
result.append(pext)
|
||||
return result
|
||||
|
||||
def _write_passphrase(stream, passphrase, encoding):
|
||||
"""Write the passphrase from memory to the GnuPG process' stdin.
|
||||
|
||||
:type stream: file, :class:BytesIO, or :class:StringIO
|
||||
:param stream: The input file descriptor to write the password to.
|
||||
:param str passphrase: The passphrase for the secret key material.
|
||||
:param str encoding: The data encoding expected by GnuPG. Usually, this
|
||||
is ``sys.getfilesystemencoding()``.
|
||||
"""
|
||||
passphrase = '%s\n' % passphrase
|
||||
passphrase = passphrase.encode(encoding)
|
||||
stream.write(passphrase)
|
||||
log.debug("Wrote passphrase on stdin.")
|
||||
|
||||
|
||||
class InheritableProperty(object):
|
||||
"""Based on the emulation of PyProperty_Type() in Objects/descrobject.c"""
|
||||
|
||||
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
|
||||
self.fget = fget
|
||||
self.fset = fset
|
||||
self.fdel = fdel
|
||||
self.__doc__ = doc
|
||||
|
||||
def __get__(self, obj, objtype=None):
|
||||
if obj is None:
|
||||
return self
|
||||
if self.fget is None:
|
||||
raise AttributeError, "unreadable attribute"
|
||||
if self.fget.__name__ == '<lambda>' or not self.fget.__name__:
|
||||
return self.fget(obj)
|
||||
else:
|
||||
return getattr(obj, self.fget.__name__)()
|
||||
|
||||
def __set__(self, obj, value):
|
||||
if self.fset is None:
|
||||
raise AttributeError, "can't set attribute"
|
||||
if self.fset.__name__ == '<lambda>' or not self.fset.__name__:
|
||||
self.fset(obj, value)
|
||||
else:
|
||||
getattr(obj, self.fset.__name__)(value)
|
||||
|
||||
def __delete__(self, obj):
|
||||
if self.fdel is None:
|
||||
raise AttributeError, "can't delete attribute"
|
||||
if self.fdel.__name__ == '<lambda>' or not self.fdel.__name__:
|
||||
self.fdel(obj)
|
||||
else:
|
||||
getattr(obj, self.fdel.__name__)()
|
|
@ -0,0 +1,197 @@
|
|||
|
||||
IN_LONG_VERSION_PY = True
|
||||
# This file helps to compute a version number in source trees obtained from
|
||||
# git-archive tarball (such as those provided by githubs download-from-tag
|
||||
# feature). Distribution tarballs (build by setup.py sdist) and build
|
||||
# directories (produced by setup.py build) will contain a much shorter file
|
||||
# that just contains the computed version number.
|
||||
|
||||
# This file is released into the public domain. Generated by
|
||||
# versioneer-0.7+ (https://github.com/warner/python-versioneer)
|
||||
|
||||
# these strings will be replaced by git during git-archive
|
||||
git_refnames = "$Format:%d$"
|
||||
git_full = "$Format:%H$"
|
||||
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
def run_command(args, cwd=None, verbose=False):
|
||||
try:
|
||||
# remember shell=False, so use git.cmd on windows, not just git
|
||||
p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd)
|
||||
except EnvironmentError:
|
||||
e = sys.exc_info()[1]
|
||||
if verbose:
|
||||
print("unable to run %s" % args[0])
|
||||
print(e)
|
||||
return None
|
||||
stdout = p.communicate()[0].strip()
|
||||
if sys.version >= '3':
|
||||
stdout = stdout.decode()
|
||||
if p.returncode != 0:
|
||||
if verbose:
|
||||
print("unable to run %s (error)" % args[0])
|
||||
return None
|
||||
return stdout
|
||||
|
||||
|
||||
import sys
|
||||
import re
|
||||
import os.path
|
||||
|
||||
def get_expanded_variables(versionfile_source):
|
||||
# the code embedded in _version.py can just fetch the value of these
|
||||
# variables. When used from setup.py, we don't want to import
|
||||
# _version.py, so we do it with a regexp instead. This function is not
|
||||
# used from _version.py.
|
||||
variables = {}
|
||||
try:
|
||||
for line in open(versionfile_source,"r").readlines():
|
||||
if line.strip().startswith("git_refnames ="):
|
||||
mo = re.search(r'=\s*"(.*)"', line)
|
||||
if mo:
|
||||
variables["refnames"] = mo.group(1)
|
||||
if line.strip().startswith("git_full ="):
|
||||
mo = re.search(r'=\s*"(.*)"', line)
|
||||
if mo:
|
||||
variables["full"] = mo.group(1)
|
||||
except EnvironmentError:
|
||||
pass
|
||||
return variables
|
||||
|
||||
def versions_from_expanded_variables(variables, tag_prefix, verbose=False):
|
||||
refnames = variables["refnames"].strip()
|
||||
if refnames.startswith("$Format"):
|
||||
if verbose:
|
||||
print("variables are unexpanded, not using")
|
||||
return {} # unexpanded, so not in an unpacked git-archive tarball
|
||||
refs = set([r.strip() for r in refnames.strip("()").split(",")])
|
||||
for ref in list(refs):
|
||||
if not re.search(r'\d', ref):
|
||||
if verbose:
|
||||
print("discarding '%s', no digits" % ref)
|
||||
refs.discard(ref)
|
||||
# Assume all version tags have a digit. git's %d expansion
|
||||
# behaves like git log --decorate=short and strips out the
|
||||
# refs/heads/ and refs/tags/ prefixes that would let us
|
||||
# distinguish between branches and tags. By ignoring refnames
|
||||
# without digits, we filter out many common branch names like
|
||||
# "release" and "stabilization", as well as "HEAD" and "master".
|
||||
if verbose:
|
||||
print("remaining refs: %s" % ",".join(sorted(refs)))
|
||||
for ref in sorted(refs):
|
||||
# sorting will prefer e.g. "2.0" over "2.0rc1"
|
||||
if ref.startswith(tag_prefix):
|
||||
r = ref[len(tag_prefix):]
|
||||
if verbose:
|
||||
print("picking %s" % r)
|
||||
return { "version": r,
|
||||
"full": variables["full"].strip() }
|
||||
# no suitable tags, so we use the full revision id
|
||||
if verbose:
|
||||
print("no suitable tags, using full revision id")
|
||||
return { "version": variables["full"].strip(),
|
||||
"full": variables["full"].strip() }
|
||||
|
||||
def versions_from_vcs(tag_prefix, versionfile_source, verbose=False):
|
||||
# this runs 'git' from the root of the source tree. That either means
|
||||
# someone ran a setup.py command (and this code is in versioneer.py, so
|
||||
# IN_LONG_VERSION_PY=False, thus the containing directory is the root of
|
||||
# the source tree), or someone ran a project-specific entry point (and
|
||||
# this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the
|
||||
# containing directory is somewhere deeper in the source tree). This only
|
||||
# gets called if the git-archive 'subst' variables were *not* expanded,
|
||||
# and _version.py hasn't already been rewritten with a short version
|
||||
# string, meaning we're inside a checked out source tree.
|
||||
|
||||
try:
|
||||
here = os.path.abspath(__file__)
|
||||
except NameError:
|
||||
# some py2exe/bbfreeze/non-CPython implementations don't do __file__
|
||||
return {} # not always correct
|
||||
|
||||
# versionfile_source is the relative path from the top of the source tree
|
||||
# (where the .git directory might live) to this file. Invert this to find
|
||||
# the root from __file__.
|
||||
root = here
|
||||
if IN_LONG_VERSION_PY:
|
||||
for i in range(len(versionfile_source.split("/"))):
|
||||
root = os.path.dirname(root)
|
||||
else:
|
||||
root = os.path.dirname(here)
|
||||
if not os.path.exists(os.path.join(root, ".git")):
|
||||
if verbose:
|
||||
print("no .git in %s" % root)
|
||||
return {}
|
||||
|
||||
GIT = "git"
|
||||
if sys.platform == "win32":
|
||||
GIT = "git.cmd"
|
||||
stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"],
|
||||
cwd=root)
|
||||
if stdout is None:
|
||||
return {}
|
||||
if not stdout.startswith(tag_prefix):
|
||||
if verbose:
|
||||
print("tag '%s' doesn't start with prefix '%s'" % (stdout, tag_prefix))
|
||||
return {}
|
||||
tag = stdout[len(tag_prefix):]
|
||||
stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root)
|
||||
if stdout is None:
|
||||
return {}
|
||||
full = stdout.strip()
|
||||
if tag.endswith("-dirty"):
|
||||
full += "-dirty"
|
||||
return {"version": tag, "full": full}
|
||||
|
||||
|
||||
def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False):
|
||||
if IN_LONG_VERSION_PY:
|
||||
# We're running from _version.py. If it's from a source tree
|
||||
# (execute-in-place), we can work upwards to find the root of the
|
||||
# tree, and then check the parent directory for a version string. If
|
||||
# it's in an installed application, there's no hope.
|
||||
try:
|
||||
here = os.path.abspath(__file__)
|
||||
except NameError:
|
||||
# py2exe/bbfreeze/non-CPython don't have __file__
|
||||
return {} # without __file__, we have no hope
|
||||
# versionfile_source is the relative path from the top of the source
|
||||
# tree to _version.py. Invert this to find the root from __file__.
|
||||
root = here
|
||||
for i in range(len(versionfile_source.split("/"))):
|
||||
root = os.path.dirname(root)
|
||||
else:
|
||||
# we're running from versioneer.py, which means we're running from
|
||||
# the setup.py in a source tree. sys.argv[0] is setup.py in the root.
|
||||
here = os.path.abspath(sys.argv[0])
|
||||
root = os.path.dirname(here)
|
||||
|
||||
# Source tarballs conventionally unpack into a directory that includes
|
||||
# both the project name and a version string.
|
||||
dirname = os.path.basename(root)
|
||||
if not dirname.startswith(parentdir_prefix):
|
||||
if verbose:
|
||||
print("guessing rootdir is '%s', but '%s' doesn't start with prefix '%s'" %
|
||||
(root, dirname, parentdir_prefix))
|
||||
return None
|
||||
return {"version": dirname[len(parentdir_prefix):], "full": ""}
|
||||
|
||||
tag_prefix = "python-gnupg-"
|
||||
parentdir_prefix = "python-gnupg-"
|
||||
versionfile_source = "src/_version.py"
|
||||
|
||||
def get_versions(default={"version": "unknown", "full": ""}, verbose=False):
|
||||
variables = { "refnames": git_refnames, "full": git_full }
|
||||
ver = versions_from_expanded_variables(variables, tag_prefix, verbose)
|
||||
if not ver:
|
||||
ver = versions_from_vcs(tag_prefix, versionfile_source, verbose)
|
||||
if not ver:
|
||||
ver = versions_from_parentdir(parentdir_prefix, versionfile_source,
|
||||
verbose)
|
||||
if not ver:
|
||||
ver = default
|
||||
return ver
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
#-*- encoding: utf-8 -*-
|
||||
'''
|
||||
Copyright information for python-gnupg.
|
||||
'''
|
||||
|
||||
copyright = """\
|
||||
Copyright (C) 2013 Isis Lovecruft.
|
||||
See LICENSE for details."""
|
||||
|
||||
disclaimer = """\
|
||||
This file is part of python-gnupg, a Python wrapper around GnuPG.
|
||||
%s
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program 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
|
||||
Affero General Public License for more details.""" % (copyright,)
|
||||
|
||||
|
||||
txcopyright = """\
|
||||
Where stated, parts of this program were taken from Twisted, which is
|
||||
licensed as follows:
|
||||
|
||||
Twisted, the Framework of Your Internet
|
||||
Copyright (c) 2001-2013 Twisted Matrix Laboratories.
|
||||
See LICENSE for details.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."""
|
File diff suppressed because it is too large
Load Diff
547
test_gnupg.py
547
test_gnupg.py
|
@ -1,547 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
A test harness for gnupg.py.
|
||||
|
||||
Copyright © 2013 Isis Lovecruft.
|
||||
Copyright © 2008-2013 Vinay Sajip. All rights reserved.
|
||||
"""
|
||||
import doctest
|
||||
import logging
|
||||
from functools import wraps
|
||||
import io
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import gnupg
|
||||
|
||||
__author__ = "Isis Lovecruft"
|
||||
__date__ = "2013-03-02"
|
||||
|
||||
ALL_TESTS = True
|
||||
REPO_DIR = os.getcwd()
|
||||
TEST_DIR = os.path.join(REPO_DIR, 'keys')
|
||||
|
||||
tempfile.tempdir = os.path.join(REPO_DIR, 'temp')
|
||||
if not os.path.isdir(tempfile.gettempdir()):
|
||||
os.mkdir(tempfile.gettempdir())
|
||||
|
||||
@wraps(tempfile.TemporaryFile)
|
||||
def _make_tempfile(*args, **kwargs):
|
||||
return tempfile.TemporaryFile(dir=tempfile.gettempdir(),
|
||||
*args, **kwargs)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
KEYS_TO_IMPORT = """-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Version: GnuPG v1.4.9 (MingW32)
|
||||
|
||||
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
|
||||
=sqld
|
||||
-----END PGP PUBLIC KEY BLOCK-----"""
|
||||
|
||||
|
||||
def is_list_with_len(o, n):
|
||||
return isinstance(o, list) and len(o) == n
|
||||
|
||||
def compare_keys(k1, k2):
|
||||
"""Compare ASCII keys"""
|
||||
k1 = k1.split('\n')
|
||||
k2 = k2.split('\n')
|
||||
del k1[1] # remove version lines
|
||||
del k2[1]
|
||||
return k1 != k2
|
||||
|
||||
class ResultStringIO(io.StringIO):
|
||||
def __init__(self):
|
||||
super(self, io.StringIO).__init__()
|
||||
|
||||
def write(self, data):
|
||||
super(self, io.StringIO).write(unicode(data))
|
||||
|
||||
class GPGTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
hd = os.path.join(os.getcwd(), 'keys')
|
||||
if os.path.exists(hd):
|
||||
self.assertTrue(os.path.isdir(hd),
|
||||
"Not a directory: %s" % hd)
|
||||
shutil.rmtree(hd)
|
||||
self.homedir = hd
|
||||
self.gpg = gnupg.GPG(gpghome=hd, gpgbinary='gpg')
|
||||
self.pubring = os.path.join(self.homedir, 'pubring.gpg')
|
||||
self.secring = os.path.join(self.homedir, 'secring.gpg')
|
||||
|
||||
def test_environment(self):
|
||||
"""Test the environment by ensuring that setup worked"""
|
||||
hd = self.homedir
|
||||
self.assertTrue(os.path.exists(hd) and os.path.isdir(hd),
|
||||
"Not an existing directory: %s" % hd)
|
||||
|
||||
def test_gpg_binary(self):
|
||||
"""Test that 'gpg --version' does not return an error code"""
|
||||
proc = self.gpg._open_subprocess(['--version'])
|
||||
result = io.StringIO()
|
||||
self.gpg._collect_output(proc, result, stdin=proc.stdin)
|
||||
self.assertEqual(proc.returncode, 0)
|
||||
|
||||
def test_gpg_binary_version_str(self):
|
||||
"""That that 'gpg --version' returns the expected output"""
|
||||
proc = self.gpg._open_subprocess(['--version'])
|
||||
result = proc.stdout.read(1024)
|
||||
expected1 = "Supported algorithms:"
|
||||
expected2 = "Pubkey:"
|
||||
expected3 = "Cipher:"
|
||||
expected4 = "Compression:"
|
||||
logger.debug("'gpg --version' returned output:n%s" % result)
|
||||
self.assertGreater(result.find(expected1), 0)
|
||||
self.assertGreater(result.find(expected2), 0)
|
||||
self.assertGreater(result.find(expected3), 0)
|
||||
self.assertGreater(result.find(expected4), 0)
|
||||
|
||||
def test_gpg_binary_not_abs(self):
|
||||
"""Test that a non-absolute path to gpg results in a full path"""
|
||||
self.assertTrue(os.path.isabs(self.gpg.gpgbinary))
|
||||
|
||||
def test_make_args_drop_protected_options(self):
|
||||
"""Test that unsupported gpg options are dropped"""
|
||||
self.gpg.options = ['--tyrannosaurus-rex', '--stegosaurus']
|
||||
self.gpg.keyring = self.secring
|
||||
cmd = self.gpg.make_args(None, False)
|
||||
expected = ['/usr/bin/gpg',
|
||||
'--status-fd 2 --no-tty',
|
||||
'--homedir "/home/isis/code/riseup/python-gnupg/keys"',
|
||||
'--no-default-keyring --keyring "%s"' % self.secring]
|
||||
self.assertListEqual(cmd, expected)
|
||||
|
||||
def test_make_args(self):
|
||||
"""Test argument line construction"""
|
||||
not_allowed = ['--bicycle', '--zeppelin', 'train', 'flying-carpet']
|
||||
self.gpg.options = not_allowed[:-2]
|
||||
args = self.gpg.make_args(not_allowed[2:], False)
|
||||
self.assertTrue(len(args) == 4)
|
||||
for na in not_allowed:
|
||||
self.assertNotIn(na, args)
|
||||
|
||||
def test_list_keys_initial_public(self):
|
||||
"""Test that initially there are no public keys"""
|
||||
public_keys = self.gpg.list_keys()
|
||||
self.assertTrue(is_list_with_len(public_keys, 0),
|
||||
"Empty list expected...got instead: %s"
|
||||
% str(public_keys))
|
||||
|
||||
def test_list_keys_initial_secret(self):
|
||||
"""Test that initially there are no secret keys"""
|
||||
private_keys = self.gpg.list_keys(secret=True)
|
||||
self.assertTrue(is_list_with_len(private_keys, 0),
|
||||
"Empty list expected...got instead: %s"
|
||||
% str(private_keys))
|
||||
|
||||
|
||||
def test_copy_data(self):
|
||||
"""Test that _copy_data() is able to duplicate byte streams"""
|
||||
instream = io.BytesIO("This is a string of bytes mapped in memory.")
|
||||
outstream = str("And this one is just a string.")
|
||||
|
||||
|
||||
def generate_key(self, first_name, last_name, domain, passphrase=None):
|
||||
"""Generate a key"""
|
||||
|
||||
params = {'Key-Type': 'RSA',
|
||||
'Key-Length': 2048,
|
||||
'Subkey-Type': 'RSA',
|
||||
'Subkey-Length': 2048,
|
||||
'Name-Comment': 'A test user',
|
||||
'Expire-Date': 0,
|
||||
'Name-Real': '%s %s' % (first_name, last_name),
|
||||
'Name-Email': ("%s.%s@%s"
|
||||
% (first_name, last_name, domain)).lower()}
|
||||
if passphrase is None:
|
||||
passphrase = ("%s%s" % (first_name[0], last_name)).lower()
|
||||
params['Passphrase'] = passphrase
|
||||
cmd = self.gpg.gen_key_input(**params)
|
||||
return self.gpg.gen_key(cmd)
|
||||
|
||||
def do_key_generation(self):
|
||||
"""Test that key generation succeeds"""
|
||||
result = self.generate_key("Barbara", "Brown", "beta.com")
|
||||
self.assertNotEqual(None, result, "Non-null result")
|
||||
return result
|
||||
|
||||
def test_key_generation_with_invalid_key_type(self):
|
||||
"""Test that key generation handles invalid key type"""
|
||||
params = {
|
||||
'Key-Type': 'INVALID',
|
||||
'Key-Length': 1024,
|
||||
'Subkey-Type': 'ELG-E',
|
||||
'Subkey-Length': 2048,
|
||||
'Name-Comment': 'A test user',
|
||||
'Expire-Date': 0,
|
||||
'Name-Real': 'Test Name',
|
||||
'Name-Email': 'test.name@example.com',
|
||||
}
|
||||
cmd = self.gpg.gen_key_input(**params)
|
||||
result = self.gpg.gen_key(cmd)
|
||||
self.assertFalse(result.data, 'Null data result')
|
||||
self.assertEqual(None, result.fingerprint, 'Null fingerprint result')
|
||||
|
||||
def test_key_generation_with_colons(self):
|
||||
"""Test that key generation handles colons in key fields"""
|
||||
params = {
|
||||
'key_type': 'RSA',
|
||||
'name_real': 'urn:uuid:731c22c4-830f-422f-80dc-14a9fdae8c19',
|
||||
'name_comment': 'dummy comment',
|
||||
'name_email': 'test.name@example.com',
|
||||
}
|
||||
cmd = self.gpg.gen_key_input(**params)
|
||||
result = self.gpg.gen_key(cmd)
|
||||
keys = self.gpg.list_keys()
|
||||
self.assertEqual(len(keys), 1)
|
||||
key = keys[0]
|
||||
uids = key['uids']
|
||||
self.assertEqual(len(uids), 1)
|
||||
uid = uids[0]
|
||||
self.assertEqual(uid, 'urn:uuid:731c22c4-830f-422f-80dc-14a9fdae8c19 '
|
||||
'(dummy comment) <test.name@example.com>')
|
||||
|
||||
def test_key_generation_with_empty_value(self):
|
||||
"""Test that key generation handles empty values"""
|
||||
params = {
|
||||
'key_type': 'RSA',
|
||||
'key_length': 1024,
|
||||
'name_comment': ' ', # Not added, so default will appear
|
||||
}
|
||||
cmd = self.gpg.gen_key_input(**params)
|
||||
self.assertTrue('\nName-Comment: Generated by gnupg.py\n' in cmd)
|
||||
params['name_comment'] = 'A'
|
||||
cmd = self.gpg.gen_key_input(**params)
|
||||
self.assertTrue('\nName-Comment: A\n' in cmd)
|
||||
|
||||
def test_list_keys_after_generation(self):
|
||||
"""Test that after key generation, the generated key is available"""
|
||||
self.test_list_keys_initial()
|
||||
self.do_key_generation()
|
||||
public_keys = self.gpg.list_keys()
|
||||
self.assertTrue(is_list_with_len(public_keys, 1),
|
||||
"1-element list expected")
|
||||
private_keys = self.gpg.list_keys(secret=True)
|
||||
self.assertTrue(is_list_with_len(private_keys, 1),
|
||||
"1-element list expected")
|
||||
|
||||
def test_encryption_and_decryption(self):
|
||||
"""Test that encryption and decryption works"""
|
||||
logger.debug("test_encryption_and_decryption begins")
|
||||
key = self.generate_key("Andrew", "Able", "alpha.com",
|
||||
passphrase="andy")
|
||||
andrew = key.fingerprint
|
||||
key = self.generate_key("Barbara", "Brown", "beta.com")
|
||||
barbara = key.fingerprint
|
||||
gpg = self.gpg
|
||||
gpg.encoding = 'latin-1'
|
||||
if gnupg._py3k:
|
||||
data = 'Hello, André!'
|
||||
else:
|
||||
data = unicode('Hello, André', gpg.encoding)
|
||||
data = data.encode(gpg.encoding)
|
||||
edata = str(gpg.encrypt(data, barbara))
|
||||
self.assertNotEqual(data, edata, "Data must have changed")
|
||||
ddata = gpg.decrypt(edata, passphrase="bbrown")
|
||||
if data != ddata.data:
|
||||
logger.debug("was: %r", data)
|
||||
logger.debug("new: %r", ddata.data)
|
||||
self.assertEqual(data, ddata.data, "Round-trip must work")
|
||||
edata = str(gpg.encrypt(data, [andrew, barbara]))
|
||||
self.assertNotEqual(data, edata, "Data must have changed")
|
||||
ddata = gpg.decrypt(edata, passphrase="andy")
|
||||
self.assertEqual(data, ddata.data, "Round-trip must work")
|
||||
ddata = gpg.decrypt(edata, passphrase="bbrown")
|
||||
self.assertEqual(data, ddata.data, "Round-trip must work")
|
||||
logger.debug("test_encryption_and_decryption ends")
|
||||
# Test symmetric encryption
|
||||
data = "chippy was here"
|
||||
edata = str(gpg.encrypt(data, None, passphrase='bbrown', symmetric=True))
|
||||
ddata = gpg.decrypt(edata, passphrase='bbrown')
|
||||
self.assertEqual(data, str(ddata))
|
||||
|
||||
def test_public_keyring(self):
|
||||
"""Test that the public keyring is found in the gpg home directory"""
|
||||
self.gpg.keyring = self.pubring
|
||||
self.assertTrue(os.path.isfile(self.pubring))
|
||||
|
||||
def test_secret_keyring(self):
|
||||
"""Test that the secret keyring is found in the gpg home directory"""
|
||||
self.gpg.keyring = self.secring
|
||||
self.assertTrue(os.path.isfile(self.secring))
|
||||
|
||||
def test_import_and_export(self):
|
||||
"""Test that key import and export works"""
|
||||
logger.debug("test_import_and_export begins")
|
||||
self.test_list_keys_initial()
|
||||
gpg = self.gpg
|
||||
result = gpg.import_keys(KEYS_TO_IMPORT)
|
||||
self.assertEqual(result.summary(), '2 imported')
|
||||
public_keys = gpg.list_keys()
|
||||
self.assertTrue(is_list_with_len(public_keys, 2),
|
||||
"2-element list expected")
|
||||
private_keys = gpg.list_keys(secret=True)
|
||||
self.assertTrue(is_list_with_len(private_keys, 0),
|
||||
"Empty list expected")
|
||||
ascii = gpg.export_keys([k['keyid'] for k in public_keys])
|
||||
self.assertTrue(ascii.find("PGP PUBLIC KEY BLOCK") >= 0,
|
||||
"Exported key should be public")
|
||||
ascii = ascii.replace("\r", "").strip()
|
||||
match = compare_keys(ascii, KEYS_TO_IMPORT)
|
||||
if match:
|
||||
logger.debug("was: %r", KEYS_TO_IMPORT)
|
||||
logger.debug("now: %r", ascii)
|
||||
self.assertEqual(0, match, "Keys must match")
|
||||
#Generate a key so we can test exporting private keys
|
||||
key = self.do_key_generation()
|
||||
ascii = gpg.export_keys(key.fingerprint, True)
|
||||
self.assertTrue(ascii.find("PGP PRIVATE KEY BLOCK") >= 0,
|
||||
"Exported key should be private")
|
||||
logger.debug("test_import_and_export ends")
|
||||
|
||||
def test_import_only(self):
|
||||
"""Test that key import works"""
|
||||
logger.debug("test_import_only begins")
|
||||
self.test_list_keys_initial()
|
||||
self.gpg.import_keys(KEYS_TO_IMPORT)
|
||||
public_keys = self.gpg.list_keys()
|
||||
self.assertTrue(is_list_with_len(public_keys, 2),
|
||||
"2-element list expected")
|
||||
private_keys = self.gpg.list_keys(secret=True)
|
||||
self.assertTrue(is_list_with_len(private_keys, 0),
|
||||
"Empty list expected")
|
||||
ascii = self.gpg.export_keys([k['keyid'] for k in public_keys])
|
||||
self.assertTrue(ascii.find("PGP PUBLIC KEY BLOCK") >= 0,
|
||||
"Exported key should be public")
|
||||
ascii = ascii.replace("\r", "").strip()
|
||||
match = compare_keys(ascii, KEYS_TO_IMPORT)
|
||||
if match:
|
||||
logger.debug("was: %r", KEYS_TO_IMPORT)
|
||||
logger.debug("now: %r", ascii)
|
||||
self.assertEqual(0, match, "Keys must match")
|
||||
logger.debug("test_import_only ends")
|
||||
|
||||
def test_signature_verification(self):
|
||||
"""Test that signing and verification works"""
|
||||
logger.debug("test_signature_verification begins")
|
||||
key = self.generate_key("Andrew", "Able", "alpha.com")
|
||||
self.gpg.encoding = 'latin-1'
|
||||
if gnupg._py3k:
|
||||
data = 'Hello, André!'
|
||||
else:
|
||||
data = unicode('Hello, André', self.gpg.encoding)
|
||||
data = data.encode(self.gpg.encoding)
|
||||
sig = self.gpg.sign(data, keyid=key.fingerprint, passphrase='bbrown')
|
||||
self.assertFalse(sig, "Bad passphrase should fail")
|
||||
sig = self.gpg.sign(data, keyid=key.fingerprint, passphrase='aable')
|
||||
self.assertTrue(sig, "Good passphrase should succeed")
|
||||
verified = self.gpg.verify(sig.data)
|
||||
if key.fingerprint != verified.fingerprint:
|
||||
logger.debug("key: %r", key.fingerprint)
|
||||
logger.debug("ver: %r", verified.fingerprint)
|
||||
self.assertEqual(key.fingerprint, verified.fingerprint,
|
||||
"Fingerprints must match")
|
||||
self.assertEqual(verified.trust_level, verified.TRUST_ULTIMATE)
|
||||
self.assertEqual(verified.trust_text, 'TRUST_ULTIMATE')
|
||||
if not os.path.exists('random_binary_data'):
|
||||
data_file = open('random_binary_data', 'wb')
|
||||
data_file.write(os.urandom(5120 * 1024))
|
||||
data_file.close()
|
||||
data_file = open('random_binary_data', 'rb')
|
||||
sig = self.gpg.sign_file(data_file, keyid=key.fingerprint,
|
||||
passphrase='aable')
|
||||
data_file.close()
|
||||
self.assertTrue(sig, "File signing should succeed")
|
||||
try:
|
||||
file = gnupg._make_binary_stream(sig.data, self.gpg.encoding)
|
||||
verified = self.gpg.verify_file(file)
|
||||
except UnicodeDecodeError: #happens in Python 2.6
|
||||
verified = self.gpg.verify_file(io.BytesIO(sig.data))
|
||||
if key.fingerprint != verified.fingerprint:
|
||||
logger.debug("key: %r", key.fingerprint)
|
||||
logger.debug("ver: %r", verified.fingerprint)
|
||||
self.assertEqual(key.fingerprint, verified.fingerprint,
|
||||
"Fingerprints must match")
|
||||
data_file = open('random_binary_data', 'rb')
|
||||
sig = self.gpg.sign_file(data_file, keyid=key.fingerprint,
|
||||
passphrase='aable', detach=True)
|
||||
data_file.close()
|
||||
self.assertTrue(sig, "File signing should succeed")
|
||||
try:
|
||||
file = gnupg._make_binary_stream(sig.data, self.gpg.encoding)
|
||||
verified = self.gpg.verify_file(file, 'random_binary_data')
|
||||
except UnicodeDecodeError: #happens in Python 2.6
|
||||
verified = self.gpg.verify_file(io.BytesIO(sig.data))
|
||||
if key.fingerprint != verified.fingerprint:
|
||||
logger.debug("key: %r", key.fingerprint)
|
||||
logger.debug("ver: %r", verified.fingerprint)
|
||||
self.assertEqual(key.fingerprint, verified.fingerprint,
|
||||
"Fingerprints must match")
|
||||
logger.debug("test_signature_verification ends")
|
||||
|
||||
def test_deletion(self):
|
||||
"""Test that key deletion works"""
|
||||
logger.debug("test_deletion begins")
|
||||
self.gpg.import_keys(KEYS_TO_IMPORT)
|
||||
public_keys = self.gpg.list_keys()
|
||||
self.assertTrue(is_list_with_len(public_keys, 2),
|
||||
"2-element list expected, got %d" % len(public_keys))
|
||||
self.gpg.delete_keys(public_keys[0]['fingerprint'])
|
||||
public_keys = self.gpg.list_keys()
|
||||
self.assertTrue(is_list_with_len(public_keys, 1),
|
||||
"1-element list expected, got %d" % len(public_keys))
|
||||
logger.debug("test_deletion ends")
|
||||
|
||||
def test_file_encryption_and_decryption(self):
|
||||
"""Test that encryption/decryption to/from file works"""
|
||||
logger.debug("test_file_encryption_and_decryption begins")
|
||||
|
||||
encfname = _make_tempfile()
|
||||
logger.debug('Created tempfile for encrypted content: %s' % encfname)
|
||||
decfname = _make_tempfile()
|
||||
logger.debug('Created tempfile for decrypted content: f%s' % decfname)
|
||||
# On Windows, if the handles aren't closed, the files can't be deleted
|
||||
#os.close(encfno)
|
||||
#os.close(decfno)
|
||||
try:
|
||||
key = self.generate_key("Andrew", "Able", "alpha.com",
|
||||
passphrase="andy")
|
||||
andrew = key.fingerprint
|
||||
key = self.generate_key("Barbara", "Brown", "beta.com")
|
||||
barbara = key.fingerprint
|
||||
data = "Hello, world!"
|
||||
file = gnupg._make_binary_stream(data, self.gpg.encoding)
|
||||
edata = self.gpg.encrypt_file(file, barbara,
|
||||
armor=False, output=encfname)
|
||||
ddata = self.gpg.decrypt_file(efile, passphrase="bbrown",
|
||||
output=decfname)
|
||||
encfname.seek(0, 0) # can't use os.SEEK_SET in 2.4
|
||||
edata = encfname.read()
|
||||
ddata = decfname.read()
|
||||
data = data.encode(self.gpg.encoding)
|
||||
if ddata != data:
|
||||
logger.debug("was: %r", data)
|
||||
logger.debug("new: %r", ddata)
|
||||
self.assertEqual(data, ddata, "Round-trip must work")
|
||||
except Exception as exc:
|
||||
logger.warn(exc.message)
|
||||
logger.debug("test_file_encryption_and_decryption ends")
|
||||
|
||||
|
||||
TEST_GROUPS = {
|
||||
'basic' : set(['test_environment',
|
||||
'test_gpg_binary',
|
||||
'test_gpg_binary_not_abs',
|
||||
'test_gpg_binary_version_str',
|
||||
'test_list_keys_initial_public',
|
||||
'test_list_keys_initial_secret',
|
||||
'test_make_args_drop_protected_options',
|
||||
'test_make_args']),
|
||||
'sign' : set(['test_signature_verification']),
|
||||
|
||||
'crypt' : set(['test_encryption_and_decryption',
|
||||
'test_file_encryption_and_decryption']),
|
||||
'key' : set(['test_deletion',
|
||||
'test_import_and_export',
|
||||
'test_public_keyring',
|
||||
'test_secret_keyring',
|
||||
'test_list_keys_after_generation',
|
||||
'test_key_generation_with_invalid_key_type',
|
||||
'test_key_generation_with_empty_value',
|
||||
'test_key_generation_with_colons']),
|
||||
'import' : set(['test_import_only']),
|
||||
}
|
||||
|
||||
def suite(args=None):
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
if not args:
|
||||
result = unittest.TestLoader().loadTestsFromTestCase(GPGTestCase)
|
||||
want_doctests = False
|
||||
else:
|
||||
tests = set()
|
||||
want_doctests = False
|
||||
for arg in args:
|
||||
if arg in TEST_GROUPS:
|
||||
tests.update(TEST_GROUPS[arg])
|
||||
elif arg == "doc":
|
||||
want_doctests = True
|
||||
else:
|
||||
print("Ignoring unknown test group %r" % arg)
|
||||
result = unittest.TestSuite(list(map(GPGTestCase, tests)))
|
||||
if want_doctests:
|
||||
result.addTest(doctest.DocTestSuite(gnupg))
|
||||
return result
|
||||
|
||||
def init_logging():
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG, filename="test_gnupg.log",
|
||||
filemode="a",
|
||||
format="%(asctime)s %(levelname)-5s %(name)-7s %(threadName)-10s %(message)s")
|
||||
logging.captureWarnings(True)
|
||||
logging.logThreads = True
|
||||
logger.addHandler(logging.StreamHandler(stream=sys.stdout))
|
||||
#logger.addHandler(logging.RootLogger(logging.DEBUG))
|
||||
#logger.addHandler(logging.Logger("gnupg.py", level=logging.DEBUG))
|
||||
|
||||
def main():
|
||||
init_logging()
|
||||
tests = suite()
|
||||
results = unittest.TextTestRunner(verbosity=3).run(tests)
|
||||
return not results.wasSuccessful()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
|
@ -0,0 +1,87 @@
|
|||
Privacy is necessary for an open society in the electronic age. Privacy is not
|
||||
secrecy. A private matter is something one doesn't want the whole world to
|
||||
know, but a secret matter is something one doesn't want anybody to
|
||||
know. Privacy is the power to selectively reveal oneself to the world.
|
||||
|
||||
If two parties have some sort of dealings, then each has a memory of their
|
||||
interaction. Each party can speak about their own memory of this; how could
|
||||
anyone prevent it? One could pass laws against it, but the freedom of speech,
|
||||
even more than privacy, is fundamental to an open society; we seek not to
|
||||
restrict any speech at all. If many parties speak together in the same forum,
|
||||
each can speak to all the others and aggregate together knowledge about
|
||||
individuals and other parties. The power of electronic communications has
|
||||
enabled such group speech, and it will not go away merely because we might want
|
||||
it to.
|
||||
|
||||
Since we desire privacy, we must ensure that each party to a transaction have
|
||||
knowledge only of that which is directly necessary for that transaction. Since
|
||||
any information can be spoken of, we must ensure that we reveal as little as
|
||||
possible. In most cases personal identity is not salient. When I purchase a
|
||||
magazine at a store and hand cash to the clerk, there is no need to know who I
|
||||
am. When I ask my electronic mail provider to send and receive messages, my
|
||||
provider need not know to whom I am speaking or what I am saying or what others
|
||||
are saying to me; my provider only need know how to get the message there and
|
||||
how much I owe them in fees. When my identity is revealed by the underlying
|
||||
mechanism of the transaction, I have no privacy. I cannot here selectively
|
||||
reveal myself; I must always reveal myself.
|
||||
|
||||
Therefore, privacy in an open society requires anonymous transaction
|
||||
systems. Until now, cash has been the primary such system. An anonymous
|
||||
transaction system is not a secret transaction system. An anonymous system
|
||||
empowers individuals to reveal their identity when desired and only when
|
||||
desired; this is the essence of privacy.
|
||||
|
||||
Privacy in an open society also requires cryptography. If I say something, I
|
||||
want it heard only by those for whom I intend it. If the content of my speech
|
||||
is available to the world, I have no privacy. To encrypt is to indicate the
|
||||
desire for privacy, and to encrypt with weak cryptography is to indicate not
|
||||
too much desire for privacy. Furthermore, to reveal one's identity with
|
||||
assurance when the default is anonymity requires the cryptographic signature.
|
||||
|
||||
We cannot expect governments, corporations, or other large, faceless
|
||||
organizations to grant us privacy out of their beneficence. It is to their
|
||||
advantage to speak of us, and we should expect that they will speak. To try to
|
||||
prevent their speech is to fight against the realities of
|
||||
information. Information does not just want to be free, it longs to be
|
||||
free. Information expands to fill the available storage space. Information is
|
||||
Rumor's younger, stronger cousin; Information is fleeter of foot, has more
|
||||
eyes, knows more, and understands less than Rumor.
|
||||
|
||||
We must defend our own privacy if we expect to have any. We must come together
|
||||
and create systems which allow anonymous transactions to take place. People
|
||||
have been defending their own privacy for centuries with whispers, darkness,
|
||||
envelopes, closed doors, secret handshakes, and couriers. The technologies of
|
||||
the past did not allow for strong privacy, but electronic technologies do.
|
||||
|
||||
We the Cypherpunks are dedicated to building anonymous systems. We are
|
||||
defending our privacy with cryptography, with anonymous mail forwarding
|
||||
systems, with digital signatures, and with electronic money.
|
||||
|
||||
Cypherpunks write code. We know that someone has to write software to defend
|
||||
privacy, and since we can't get privacy unless we all do, we're going to write
|
||||
it. We publish our code so that our fellow Cypherpunks may practice and play
|
||||
with it. Our code is free for all to use, worldwide. We don't much care if you
|
||||
don't approve of the software we write. We know that software can't be
|
||||
destroyed and that a widely dispersed system can't be shut down.
|
||||
|
||||
Cypherpunks deplore regulations on cryptography, for encryption is
|
||||
fundamentally a private act. The act of encryption, in fact, removes
|
||||
information from the public realm. Even laws against cryptography reach only so
|
||||
far as a nation's border and the arm of its violence. Cryptography will
|
||||
ineluctably spread over the whole globe, and with it the anonymous transactions
|
||||
systems that it makes possible.
|
||||
|
||||
For privacy to be widespread it must be part of a social contract. People must
|
||||
come and together deploy these systems for the common good. Privacy only
|
||||
extends so far as the cooperation of one's fellows in society. We the
|
||||
Cypherpunks seek your questions and your concerns and hope we may engage you so
|
||||
that we do not deceive ourselves. We will not, however, be moved out of our
|
||||
course because some may disagree with our goals.
|
||||
|
||||
The Cypherpunks are actively engaged in making the networks safer for
|
||||
privacy. Let us proceed together apace.
|
||||
|
||||
Onward.
|
||||
|
||||
Eric Hughes <hughes@soda.berkeley.edu>
|
||||
9 March 1993
|
|
@ -0,0 +1,138 @@
|
|||
## 1024R primary secret key, usage C, pasphrase 'katpics'
|
||||
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||
|
||||
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
|
||||
=66aD
|
||||
-----END PGP PRIVATE KEY BLOCK-----
|
||||
|
||||
## 1024R subkey, usage ES, passphrase 'katpics'
|
||||
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||
|
||||
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
|
||||
TNnjCWTKcw==
|
||||
=AVxa
|
||||
-----END PGP PRIVATE KEY BLOCK-----
|
|
@ -0,0 +1,55 @@
|
|||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Version: GnuPG v1.4.12 (GNU/Linux)
|
||||
|
||||
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
|
||||
=sTVP
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
|
@ -0,0 +1,78 @@
|
|||
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||
Version: GnuPG v1.4.12 (GNU/Linux)
|
||||
|
||||
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
|
||||
m8XB+to=
|
||||
=GLJA
|
||||
-----END PGP PRIVATE KEY BLOCK-----
|
|
@ -0,0 +1,48 @@
|
|||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Version: GnuPG v1.4.12 (GNU/Linux)
|
||||
|
||||
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
|
||||
=bZbj
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
|
@ -0,0 +1,77 @@
|
|||
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||
Version: GnuPG v1.4.12 (GNU/Linux)
|
||||
|
||||
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
|
||||
=01Sl
|
||||
-----END PGP PRIVATE KEY BLOCK-----
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,656 @@
|
|||
#! /usr/bin/python
|
||||
|
||||
"""versioneer.py
|
||||
|
||||
(like a rocketeer, but for versions)
|
||||
|
||||
* https://github.com/warner/python-versioneer
|
||||
* Brian Warner
|
||||
* License: Public Domain
|
||||
* Version: 0.7+
|
||||
|
||||
This file helps distutils-based projects manage their version number by just
|
||||
creating version-control tags.
|
||||
|
||||
For developers who work from a VCS-generated tree (e.g. 'git clone' etc),
|
||||
each 'setup.py version', 'setup.py build', 'setup.py sdist' will compute a
|
||||
version number by asking your version-control tool about the current
|
||||
checkout. The version number will be written into a generated _version.py
|
||||
file of your choosing, where it can be included by your __init__.py
|
||||
|
||||
For users who work from a VCS-generated tarball (e.g. 'git archive'), it will
|
||||
compute a version number by looking at the name of the directory created when
|
||||
te tarball is unpacked. This conventionally includes both the name of the
|
||||
project and a version number.
|
||||
|
||||
For users who work from a tarball built by 'setup.py sdist', it will get a
|
||||
version number from a previously-generated _version.py file.
|
||||
|
||||
As a result, loading code directly from the source tree will not result in a
|
||||
real version. If you want real versions from VCS trees (where you frequently
|
||||
update from the upstream repository, or do new development), you will need to
|
||||
do a 'setup.py version' after each update, and load code from the build/
|
||||
directory.
|
||||
|
||||
You need to provide this code with a few configuration values:
|
||||
|
||||
versionfile_source:
|
||||
A project-relative pathname into which the generated version strings
|
||||
should be written. This is usually a _version.py next to your project's
|
||||
main __init__.py file. If your project uses src/myproject/__init__.py,
|
||||
this should be 'src/myproject/_version.py'. This file should be checked
|
||||
in to your VCS as usual: the copy created below by 'setup.py
|
||||
update_files' will include code that parses expanded VCS keywords in
|
||||
generated tarballs. The 'build' and 'sdist' commands will replace it with
|
||||
a copy that has just the calculated version string.
|
||||
|
||||
versionfile_build:
|
||||
Like versionfile_source, but relative to the build directory instead of
|
||||
the source directory. These will differ when your setup.py uses
|
||||
'package_dir='. If you have package_dir={'myproject': 'src/myproject'},
|
||||
then you will probably have versionfile_build='myproject/_version.py' and
|
||||
versionfile_source='src/myproject/_version.py'.
|
||||
|
||||
tag_prefix: a string, like 'PROJECTNAME-', which appears at the start of all
|
||||
VCS tags. If your tags look like 'myproject-1.2.0', then you
|
||||
should use tag_prefix='myproject-'. If you use unprefixed tags
|
||||
like '1.2.0', this should be an empty string.
|
||||
|
||||
parentdir_prefix: a string, frequently the same as tag_prefix, which
|
||||
appears at the start of all unpacked tarball filenames. If
|
||||
your tarball unpacks into 'myproject-1.2.0', this should
|
||||
be 'myproject-'.
|
||||
|
||||
To use it:
|
||||
|
||||
1: include this file in the top level of your project
|
||||
2: make the following changes to the top of your setup.py:
|
||||
import versioneer
|
||||
versioneer.versionfile_source = 'src/myproject/_version.py'
|
||||
versioneer.versionfile_build = 'myproject/_version.py'
|
||||
versioneer.tag_prefix = '' # tags are like 1.2.0
|
||||
versioneer.parentdir_prefix = 'myproject-' # dirname like 'myproject-1.2.0'
|
||||
3: add the following arguments to the setup() call in your setup.py:
|
||||
version=versioneer.get_version(),
|
||||
cmdclass=versioneer.get_cmdclass(),
|
||||
4: run 'setup.py update_files', which will create _version.py, and will
|
||||
append the following to your __init__.py:
|
||||
from _version import __version__
|
||||
5: modify your MANIFEST.in to include versioneer.py
|
||||
6: add both versioneer.py and the generated _version.py to your VCS
|
||||
"""
|
||||
|
||||
import os, sys, re
|
||||
from distutils.core import Command
|
||||
from distutils.command.sdist import sdist as _sdist
|
||||
from distutils.command.build import build as _build
|
||||
|
||||
versionfile_source = None
|
||||
versionfile_build = None
|
||||
tag_prefix = None
|
||||
parentdir_prefix = None
|
||||
|
||||
VCS = "git"
|
||||
IN_LONG_VERSION_PY = False
|
||||
|
||||
|
||||
LONG_VERSION_PY = '''
|
||||
IN_LONG_VERSION_PY = True
|
||||
# This file helps to compute a version number in source trees obtained from
|
||||
# git-archive tarball (such as those provided by githubs download-from-tag
|
||||
# feature). Distribution tarballs (build by setup.py sdist) and build
|
||||
# directories (produced by setup.py build) will contain a much shorter file
|
||||
# that just contains the computed version number.
|
||||
|
||||
# This file is released into the public domain. Generated by
|
||||
# versioneer-0.7+ (https://github.com/warner/python-versioneer)
|
||||
|
||||
# these strings will be replaced by git during git-archive
|
||||
git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s"
|
||||
git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s"
|
||||
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
def run_command(args, cwd=None, verbose=False):
|
||||
try:
|
||||
# remember shell=False, so use git.cmd on windows, not just git
|
||||
p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd)
|
||||
except EnvironmentError:
|
||||
e = sys.exc_info()[1]
|
||||
if verbose:
|
||||
print("unable to run %%s" %% args[0])
|
||||
print(e)
|
||||
return None
|
||||
stdout = p.communicate()[0].strip()
|
||||
if sys.version >= '3':
|
||||
stdout = stdout.decode()
|
||||
if p.returncode != 0:
|
||||
if verbose:
|
||||
print("unable to run %%s (error)" %% args[0])
|
||||
return None
|
||||
return stdout
|
||||
|
||||
|
||||
import sys
|
||||
import re
|
||||
import os.path
|
||||
|
||||
def get_expanded_variables(versionfile_source):
|
||||
# the code embedded in _version.py can just fetch the value of these
|
||||
# variables. When used from setup.py, we don't want to import
|
||||
# _version.py, so we do it with a regexp instead. This function is not
|
||||
# used from _version.py.
|
||||
variables = {}
|
||||
try:
|
||||
for line in open(versionfile_source,"r").readlines():
|
||||
if line.strip().startswith("git_refnames ="):
|
||||
mo = re.search(r'=\s*"(.*)"', line)
|
||||
if mo:
|
||||
variables["refnames"] = mo.group(1)
|
||||
if line.strip().startswith("git_full ="):
|
||||
mo = re.search(r'=\s*"(.*)"', line)
|
||||
if mo:
|
||||
variables["full"] = mo.group(1)
|
||||
except EnvironmentError:
|
||||
pass
|
||||
return variables
|
||||
|
||||
def versions_from_expanded_variables(variables, tag_prefix, verbose=False):
|
||||
refnames = variables["refnames"].strip()
|
||||
if refnames.startswith("$Format"):
|
||||
if verbose:
|
||||
print("variables are unexpanded, not using")
|
||||
return {} # unexpanded, so not in an unpacked git-archive tarball
|
||||
refs = set([r.strip() for r in refnames.strip("()").split(",")])
|
||||
for ref in list(refs):
|
||||
if not re.search(r'\d', ref):
|
||||
if verbose:
|
||||
print("discarding '%%s', no digits" %% ref)
|
||||
refs.discard(ref)
|
||||
# Assume all version tags have a digit. git's %%d expansion
|
||||
# behaves like git log --decorate=short and strips out the
|
||||
# refs/heads/ and refs/tags/ prefixes that would let us
|
||||
# distinguish between branches and tags. By ignoring refnames
|
||||
# without digits, we filter out many common branch names like
|
||||
# "release" and "stabilization", as well as "HEAD" and "master".
|
||||
if verbose:
|
||||
print("remaining refs: %%s" %% ",".join(sorted(refs)))
|
||||
for ref in sorted(refs):
|
||||
# sorting will prefer e.g. "2.0" over "2.0rc1"
|
||||
if ref.startswith(tag_prefix):
|
||||
r = ref[len(tag_prefix):]
|
||||
if verbose:
|
||||
print("picking %%s" %% r)
|
||||
return { "version": r,
|
||||
"full": variables["full"].strip() }
|
||||
# no suitable tags, so we use the full revision id
|
||||
if verbose:
|
||||
print("no suitable tags, using full revision id")
|
||||
return { "version": variables["full"].strip(),
|
||||
"full": variables["full"].strip() }
|
||||
|
||||
def versions_from_vcs(tag_prefix, versionfile_source, verbose=False):
|
||||
# this runs 'git' from the root of the source tree. That either means
|
||||
# someone ran a setup.py command (and this code is in versioneer.py, so
|
||||
# IN_LONG_VERSION_PY=False, thus the containing directory is the root of
|
||||
# the source tree), or someone ran a project-specific entry point (and
|
||||
# this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the
|
||||
# containing directory is somewhere deeper in the source tree). This only
|
||||
# gets called if the git-archive 'subst' variables were *not* expanded,
|
||||
# and _version.py hasn't already been rewritten with a short version
|
||||
# string, meaning we're inside a checked out source tree.
|
||||
|
||||
try:
|
||||
here = os.path.abspath(__file__)
|
||||
except NameError:
|
||||
# some py2exe/bbfreeze/non-CPython implementations don't do __file__
|
||||
return {} # not always correct
|
||||
|
||||
# versionfile_source is the relative path from the top of the source tree
|
||||
# (where the .git directory might live) to this file. Invert this to find
|
||||
# the root from __file__.
|
||||
root = here
|
||||
if IN_LONG_VERSION_PY:
|
||||
for i in range(len(versionfile_source.split("/"))):
|
||||
root = os.path.dirname(root)
|
||||
else:
|
||||
root = os.path.dirname(here)
|
||||
if not os.path.exists(os.path.join(root, ".git")):
|
||||
if verbose:
|
||||
print("no .git in %%s" %% root)
|
||||
return {}
|
||||
|
||||
GIT = "git"
|
||||
if sys.platform == "win32":
|
||||
GIT = "git.cmd"
|
||||
stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"],
|
||||
cwd=root)
|
||||
if stdout is None:
|
||||
return {}
|
||||
if not stdout.startswith(tag_prefix):
|
||||
if verbose:
|
||||
print("tag '%%s' doesn't start with prefix '%%s'" %% (stdout, tag_prefix))
|
||||
return {}
|
||||
tag = stdout[len(tag_prefix):]
|
||||
stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root)
|
||||
if stdout is None:
|
||||
return {}
|
||||
full = stdout.strip()
|
||||
if tag.endswith("-dirty"):
|
||||
full += "-dirty"
|
||||
return {"version": tag, "full": full}
|
||||
|
||||
|
||||
def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False):
|
||||
if IN_LONG_VERSION_PY:
|
||||
# We're running from _version.py. If it's from a source tree
|
||||
# (execute-in-place), we can work upwards to find the root of the
|
||||
# tree, and then check the parent directory for a version string. If
|
||||
# it's in an installed application, there's no hope.
|
||||
try:
|
||||
here = os.path.abspath(__file__)
|
||||
except NameError:
|
||||
# py2exe/bbfreeze/non-CPython don't have __file__
|
||||
return {} # without __file__, we have no hope
|
||||
# versionfile_source is the relative path from the top of the source
|
||||
# tree to _version.py. Invert this to find the root from __file__.
|
||||
root = here
|
||||
for i in range(len(versionfile_source.split("/"))):
|
||||
root = os.path.dirname(root)
|
||||
else:
|
||||
# we're running from versioneer.py, which means we're running from
|
||||
# the setup.py in a source tree. sys.argv[0] is setup.py in the root.
|
||||
here = os.path.abspath(sys.argv[0])
|
||||
root = os.path.dirname(here)
|
||||
|
||||
# Source tarballs conventionally unpack into a directory that includes
|
||||
# both the project name and a version string.
|
||||
dirname = os.path.basename(root)
|
||||
if not dirname.startswith(parentdir_prefix):
|
||||
if verbose:
|
||||
print("guessing rootdir is '%%s', but '%%s' doesn't start with prefix '%%s'" %%
|
||||
(root, dirname, parentdir_prefix))
|
||||
return None
|
||||
return {"version": dirname[len(parentdir_prefix):], "full": ""}
|
||||
|
||||
tag_prefix = "%(TAG_PREFIX)s"
|
||||
parentdir_prefix = "%(PARENTDIR_PREFIX)s"
|
||||
versionfile_source = "%(VERSIONFILE_SOURCE)s"
|
||||
|
||||
def get_versions(default={"version": "unknown", "full": ""}, verbose=False):
|
||||
variables = { "refnames": git_refnames, "full": git_full }
|
||||
ver = versions_from_expanded_variables(variables, tag_prefix, verbose)
|
||||
if not ver:
|
||||
ver = versions_from_vcs(tag_prefix, versionfile_source, verbose)
|
||||
if not ver:
|
||||
ver = versions_from_parentdir(parentdir_prefix, versionfile_source,
|
||||
verbose)
|
||||
if not ver:
|
||||
ver = default
|
||||
return ver
|
||||
|
||||
'''
|
||||
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
def run_command(args, cwd=None, verbose=False):
|
||||
try:
|
||||
# remember shell=False, so use git.cmd on windows, not just git
|
||||
p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd)
|
||||
except EnvironmentError:
|
||||
e = sys.exc_info()[1]
|
||||
if verbose:
|
||||
print("unable to run %s" % args[0])
|
||||
print(e)
|
||||
return None
|
||||
stdout = p.communicate()[0].strip()
|
||||
if sys.version >= '3':
|
||||
stdout = stdout.decode()
|
||||
if p.returncode != 0:
|
||||
if verbose:
|
||||
print("unable to run %s (error)" % args[0])
|
||||
return None
|
||||
return stdout
|
||||
|
||||
|
||||
import sys
|
||||
import re
|
||||
import os.path
|
||||
|
||||
def get_expanded_variables(versionfile_source):
|
||||
# the code embedded in _version.py can just fetch the value of these
|
||||
# variables. When used from setup.py, we don't want to import
|
||||
# _version.py, so we do it with a regexp instead. This function is not
|
||||
# used from _version.py.
|
||||
variables = {}
|
||||
try:
|
||||
for line in open(versionfile_source,"r").readlines():
|
||||
if line.strip().startswith("git_refnames ="):
|
||||
mo = re.search(r'=\s*"(.*)"', line)
|
||||
if mo:
|
||||
variables["refnames"] = mo.group(1)
|
||||
if line.strip().startswith("git_full ="):
|
||||
mo = re.search(r'=\s*"(.*)"', line)
|
||||
if mo:
|
||||
variables["full"] = mo.group(1)
|
||||
except EnvironmentError:
|
||||
pass
|
||||
return variables
|
||||
|
||||
def versions_from_expanded_variables(variables, tag_prefix, verbose=False):
|
||||
refnames = variables["refnames"].strip()
|
||||
if refnames.startswith("$Format"):
|
||||
if verbose:
|
||||
print("variables are unexpanded, not using")
|
||||
return {} # unexpanded, so not in an unpacked git-archive tarball
|
||||
refs = set([r.strip() for r in refnames.strip("()").split(",")])
|
||||
for ref in list(refs):
|
||||
if not re.search(r'\d', ref):
|
||||
if verbose:
|
||||
print("discarding '%s', no digits" % ref)
|
||||
refs.discard(ref)
|
||||
# Assume all version tags have a digit. git's %d expansion
|
||||
# behaves like git log --decorate=short and strips out the
|
||||
# refs/heads/ and refs/tags/ prefixes that would let us
|
||||
# distinguish between branches and tags. By ignoring refnames
|
||||
# without digits, we filter out many common branch names like
|
||||
# "release" and "stabilization", as well as "HEAD" and "master".
|
||||
if verbose:
|
||||
print("remaining refs: %s" % ",".join(sorted(refs)))
|
||||
for ref in sorted(refs):
|
||||
# sorting will prefer e.g. "2.0" over "2.0rc1"
|
||||
if ref.startswith(tag_prefix):
|
||||
r = ref[len(tag_prefix):]
|
||||
if verbose:
|
||||
print("picking %s" % r)
|
||||
return { "version": r,
|
||||
"full": variables["full"].strip() }
|
||||
# no suitable tags, so we use the full revision id
|
||||
if verbose:
|
||||
print("no suitable tags, using full revision id")
|
||||
return { "version": variables["full"].strip(),
|
||||
"full": variables["full"].strip() }
|
||||
|
||||
def versions_from_vcs(tag_prefix, versionfile_source, verbose=False):
|
||||
# this runs 'git' from the root of the source tree. That either means
|
||||
# someone ran a setup.py command (and this code is in versioneer.py, so
|
||||
# IN_LONG_VERSION_PY=False, thus the containing directory is the root of
|
||||
# the source tree), or someone ran a project-specific entry point (and
|
||||
# this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the
|
||||
# containing directory is somewhere deeper in the source tree). This only
|
||||
# gets called if the git-archive 'subst' variables were *not* expanded,
|
||||
# and _version.py hasn't already been rewritten with a short version
|
||||
# string, meaning we're inside a checked out source tree.
|
||||
|
||||
try:
|
||||
here = os.path.abspath(__file__)
|
||||
except NameError:
|
||||
# some py2exe/bbfreeze/non-CPython implementations don't do __file__
|
||||
return {} # not always correct
|
||||
|
||||
# versionfile_source is the relative path from the top of the source tree
|
||||
# (where the .git directory might live) to this file. Invert this to find
|
||||
# the root from __file__.
|
||||
root = here
|
||||
if IN_LONG_VERSION_PY:
|
||||
for i in range(len(versionfile_source.split("/"))):
|
||||
root = os.path.dirname(root)
|
||||
else:
|
||||
root = os.path.dirname(here)
|
||||
if not os.path.exists(os.path.join(root, ".git")):
|
||||
if verbose:
|
||||
print("no .git in %s" % root)
|
||||
return {}
|
||||
|
||||
GIT = "git"
|
||||
if sys.platform == "win32":
|
||||
GIT = "git.cmd"
|
||||
stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"],
|
||||
cwd=root)
|
||||
if stdout is None:
|
||||
return {}
|
||||
if not stdout.startswith(tag_prefix):
|
||||
if verbose:
|
||||
print("tag '%s' doesn't start with prefix '%s'" % (stdout, tag_prefix))
|
||||
return {}
|
||||
tag = stdout[len(tag_prefix):]
|
||||
stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root)
|
||||
if stdout is None:
|
||||
return {}
|
||||
full = stdout.strip()
|
||||
if tag.endswith("-dirty"):
|
||||
full += "-dirty"
|
||||
return {"version": tag, "full": full}
|
||||
|
||||
|
||||
def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False):
|
||||
if IN_LONG_VERSION_PY:
|
||||
# We're running from _version.py. If it's from a source tree
|
||||
# (execute-in-place), we can work upwards to find the root of the
|
||||
# tree, and then check the parent directory for a version string. If
|
||||
# it's in an installed application, there's no hope.
|
||||
try:
|
||||
here = os.path.abspath(__file__)
|
||||
except NameError:
|
||||
# py2exe/bbfreeze/non-CPython don't have __file__
|
||||
return {} # without __file__, we have no hope
|
||||
# versionfile_source is the relative path from the top of the source
|
||||
# tree to _version.py. Invert this to find the root from __file__.
|
||||
root = here
|
||||
for i in range(len(versionfile_source.split("/"))):
|
||||
root = os.path.dirname(root)
|
||||
else:
|
||||
# we're running from versioneer.py, which means we're running from
|
||||
# the setup.py in a source tree. sys.argv[0] is setup.py in the root.
|
||||
here = os.path.abspath(sys.argv[0])
|
||||
root = os.path.dirname(here)
|
||||
|
||||
# Source tarballs conventionally unpack into a directory that includes
|
||||
# both the project name and a version string.
|
||||
dirname = os.path.basename(root)
|
||||
if not dirname.startswith(parentdir_prefix):
|
||||
if verbose:
|
||||
print("guessing rootdir is '%s', but '%s' doesn't start with prefix '%s'" %
|
||||
(root, dirname, parentdir_prefix))
|
||||
return None
|
||||
return {"version": dirname[len(parentdir_prefix):], "full": ""}
|
||||
|
||||
import sys
|
||||
|
||||
def do_vcs_install(versionfile_source, ipy):
|
||||
GIT = "git"
|
||||
if sys.platform == "win32":
|
||||
GIT = "git.cmd"
|
||||
run_command([GIT, "add", "versioneer.py"])
|
||||
run_command([GIT, "add", versionfile_source])
|
||||
run_command([GIT, "add", ipy])
|
||||
present = False
|
||||
try:
|
||||
f = open(".gitattributes", "r")
|
||||
for line in f.readlines():
|
||||
if line.strip().startswith(versionfile_source):
|
||||
if "export-subst" in line.strip().split()[1:]:
|
||||
present = True
|
||||
f.close()
|
||||
except EnvironmentError:
|
||||
pass
|
||||
if not present:
|
||||
f = open(".gitattributes", "a+")
|
||||
f.write("%s export-subst\n" % versionfile_source)
|
||||
f.close()
|
||||
run_command([GIT, "add", ".gitattributes"])
|
||||
|
||||
|
||||
SHORT_VERSION_PY = """
|
||||
# This file was generated by 'versioneer.py' (0.7+) from
|
||||
# revision-control system data, or from the parent directory name of an
|
||||
# unpacked source archive. Distribution tarballs contain a pre-generated copy
|
||||
# of this file.
|
||||
|
||||
version_version = '%(version)s'
|
||||
version_full = '%(full)s'
|
||||
def get_versions(default={}, verbose=False):
|
||||
return {'version': version_version, 'full': version_full}
|
||||
|
||||
"""
|
||||
|
||||
DEFAULT = {"version": "unknown", "full": "unknown"}
|
||||
|
||||
def versions_from_file(filename):
|
||||
versions = {}
|
||||
try:
|
||||
f = open(filename)
|
||||
except EnvironmentError:
|
||||
return versions
|
||||
for line in f.readlines():
|
||||
mo = re.match("version_version = '([^']+)'", line)
|
||||
if mo:
|
||||
versions["version"] = mo.group(1)
|
||||
mo = re.match("version_full = '([^']+)'", line)
|
||||
if mo:
|
||||
versions["full"] = mo.group(1)
|
||||
return versions
|
||||
|
||||
def write_to_version_file(filename, versions):
|
||||
f = open(filename, "w")
|
||||
f.write(SHORT_VERSION_PY % versions)
|
||||
f.close()
|
||||
print("set %s to '%s'" % (filename, versions["version"]))
|
||||
|
||||
|
||||
def get_best_versions(versionfile, tag_prefix, parentdir_prefix,
|
||||
default=DEFAULT, verbose=False):
|
||||
# returns dict with two keys: 'version' and 'full'
|
||||
#
|
||||
# extract version from first of _version.py, 'git describe', parentdir.
|
||||
# This is meant to work for developers using a source checkout, for users
|
||||
# of a tarball created by 'setup.py sdist', and for users of a
|
||||
# tarball/zipball created by 'git archive' or github's download-from-tag
|
||||
# feature.
|
||||
|
||||
variables = get_expanded_variables(versionfile_source)
|
||||
if variables:
|
||||
ver = versions_from_expanded_variables(variables, tag_prefix)
|
||||
if ver:
|
||||
if verbose: print("got version from expanded variable %s" % ver)
|
||||
return ver
|
||||
|
||||
ver = versions_from_file(versionfile)
|
||||
if ver:
|
||||
if verbose: print("got version from file %s %s" % (versionfile, ver))
|
||||
return ver
|
||||
|
||||
ver = versions_from_vcs(tag_prefix, versionfile_source, verbose)
|
||||
if ver:
|
||||
if verbose: print("got version from git %s" % ver)
|
||||
return ver
|
||||
|
||||
ver = versions_from_parentdir(parentdir_prefix, versionfile_source, verbose)
|
||||
if ver:
|
||||
if verbose: print("got version from parentdir %s" % ver)
|
||||
return ver
|
||||
|
||||
if verbose: print("got version from default %s" % ver)
|
||||
return default
|
||||
|
||||
def get_versions(default=DEFAULT, verbose=False):
|
||||
assert versionfile_source is not None, "please set versioneer.versionfile_source"
|
||||
assert tag_prefix is not None, "please set versioneer.tag_prefix"
|
||||
assert parentdir_prefix is not None, "please set versioneer.parentdir_prefix"
|
||||
return get_best_versions(versionfile_source, tag_prefix, parentdir_prefix,
|
||||
default=default, verbose=verbose)
|
||||
def get_version(verbose=False):
|
||||
return get_versions(verbose=verbose)["version"]
|
||||
|
||||
class cmd_version(Command):
|
||||
description = "report generated version string"
|
||||
user_options = []
|
||||
boolean_options = []
|
||||
def initialize_options(self):
|
||||
pass
|
||||
def finalize_options(self):
|
||||
pass
|
||||
def run(self):
|
||||
ver = get_version(verbose=True)
|
||||
print("Version is currently: %s" % ver)
|
||||
|
||||
|
||||
class cmd_build(_build):
|
||||
def run(self):
|
||||
versions = get_versions(verbose=True)
|
||||
_build.run(self)
|
||||
# now locate _version.py in the new build/ directory and replace it
|
||||
# with an updated value
|
||||
target_versionfile = os.path.join(self.build_lib, versionfile_build)
|
||||
print("UPDATING %s" % target_versionfile)
|
||||
os.unlink(target_versionfile)
|
||||
f = open(target_versionfile, "w")
|
||||
f.write(SHORT_VERSION_PY % versions)
|
||||
f.close()
|
||||
|
||||
class cmd_sdist(_sdist):
|
||||
def run(self):
|
||||
versions = get_versions(verbose=True)
|
||||
self._versioneer_generated_versions = versions
|
||||
# unless we update this, the command will keep using the old version
|
||||
self.distribution.metadata.version = versions["version"]
|
||||
return _sdist.run(self)
|
||||
|
||||
def make_release_tree(self, base_dir, files):
|
||||
_sdist.make_release_tree(self, base_dir, files)
|
||||
# now locate _version.py in the new base_dir directory (remembering
|
||||
# that it may be a hardlink) and replace it with an updated value
|
||||
target_versionfile = os.path.join(base_dir, versionfile_source)
|
||||
print("UPDATING %s" % target_versionfile)
|
||||
os.unlink(target_versionfile)
|
||||
f = open(target_versionfile, "w")
|
||||
f.write(SHORT_VERSION_PY % self._versioneer_generated_versions)
|
||||
f.close()
|
||||
|
||||
INIT_PY_SNIPPET = """
|
||||
from ._version import get_versions
|
||||
__version__ = get_versions()['version']
|
||||
del get_versions
|
||||
"""
|
||||
|
||||
class cmd_update_files(Command):
|
||||
description = "modify __init__.py and create _version.py"
|
||||
user_options = []
|
||||
boolean_options = []
|
||||
def initialize_options(self):
|
||||
pass
|
||||
def finalize_options(self):
|
||||
pass
|
||||
def run(self):
|
||||
ipy = os.path.join(os.path.dirname(versionfile_source), "__init__.py")
|
||||
print(" creating %s" % versionfile_source)
|
||||
f = open(versionfile_source, "w")
|
||||
f.write(LONG_VERSION_PY % {"DOLLAR": "$",
|
||||
"TAG_PREFIX": tag_prefix,
|
||||
"PARENTDIR_PREFIX": parentdir_prefix,
|
||||
"VERSIONFILE_SOURCE": versionfile_source,
|
||||
})
|
||||
f.close()
|
||||
try:
|
||||
old = open(ipy, "r").read()
|
||||
except EnvironmentError:
|
||||
old = ""
|
||||
if INIT_PY_SNIPPET not in old:
|
||||
print(" appending to %s" % ipy)
|
||||
f = open(ipy, "a")
|
||||
f.write(INIT_PY_SNIPPET)
|
||||
f.close()
|
||||
else:
|
||||
print(" %s unmodified" % ipy)
|
||||
do_vcs_install(versionfile_source, ipy)
|
||||
|
||||
def get_cmdclass():
|
||||
return {'version': cmd_version,
|
||||
'update_files': cmd_update_files,
|
||||
'build': cmd_build,
|
||||
'sdist': cmd_sdist,
|
||||
}
|
Loading…
Reference in New Issue