diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d4a2c44 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +# http://editorconfig.org + +root = true + +[*] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +insert_final_newline = true +charset = utf-8 +end_of_line = lf + +[*.bat] +indent_style = tab +end_of_line = crlf + +[LICENSE] +insert_final_newline = false + +[Makefile] +indent_style = tab diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4397470 --- /dev/null +++ b/.gitignore @@ -0,0 +1,71 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +venv/ +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask instance folder +instance/ + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# IPython Notebook +.ipynb_checkpoints + +# pyenv +.python-version + diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..8d49fbc --- /dev/null +++ b/.travis.yml @@ -0,0 +1,30 @@ +# Config file for automatic testing at travis-ci.org +# This file will be regenerated if you run travis_pypi_setup.py + +language: python + +env: + - TOXENV=py34 + - TOXENV=py33 + - TOXENV=py27 + - TOXENV=py26 + - TOXENV=pypy + +# command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors +install: pip install -U tox + +# command to run tests, e.g. python setup.py test +script: tox + +# After you create the Github repo and add it to Travis, run the +# travis_pypi_setup.py script to finish PyPI deployment setup +deploy: + provider: pypi + distributions: sdist bdist_wheel + user: snark + password: + secure: PLEASE_REPLACE_ME + on: + tags: true + repo: snark/ignorance + condition: $TOXENV == py27 diff --git a/AUTHORS.rst b/AUTHORS.rst new file mode 100644 index 0000000..404087c --- /dev/null +++ b/AUTHORS.rst @@ -0,0 +1,13 @@ +======= +Credits +======= + +Development Lead +---------------- + +* Steve Cook + +Contributors +------------ + +None yet. Why not be the first? diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 0000000..c72fbdc --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,111 @@ +============ +Contributing +============ + +Contributions are welcome, and they are greatly appreciated! Every +little bit helps, and credit will always be given. + +You can contribute in many ways: + +Types of Contributions +---------------------- + +Report Bugs +~~~~~~~~~~~ + +Report bugs at https://github.com/snark/ignorance/issues. + +If you are reporting a bug, please include: + +* Your operating system name and version. +* Any details about your local setup that might be helpful in troubleshooting. +* Detailed steps to reproduce the bug. + +Fix Bugs +~~~~~~~~ + +Look through the GitHub issues for bugs. Anything tagged with "bug" +is open to whoever wants to implement it. + +Implement Features +~~~~~~~~~~~~~~~~~~ + +Look through the GitHub issues for features. Anything tagged with "feature" +is open to whoever wants to implement it. + +Write Documentation +~~~~~~~~~~~~~~~~~~~ + +Ignorance could always use more documentation, whether as part of the +official Ignorance docs, in docstrings, or even on the web in blog posts, +articles, and such. + +Submit Feedback +~~~~~~~~~~~~~~~ + +The best way to send feedback is to file an issue at https://github.com/snark/ignorance/issues. + +If you are proposing a feature: + +* Explain in detail how it would work. +* Keep the scope as narrow as possible, to make it easier to implement. +* Remember that this is a volunteer-driven project, and that contributions + are welcome :) + +Get Started! +------------ + +Ready to contribute? Here's how to set up `ignorance` for local development. + +1. Fork the `ignorance` repo on GitHub. +2. Clone your fork locally:: + + $ git clone git@github.com:your_name_here/ignorance.git + +3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: + + $ mkvirtualenv ignorance + $ cd ignorance/ + $ python setup.py develop + +4. Create a branch for local development:: + + $ git checkout -b name-of-your-bugfix-or-feature + + Now you can make your changes locally. + +5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:: + + $ flake8 ignorance tests + $ python setup.py test + $ tox + + To get flake8 and tox, just pip install them into your virtualenv. + +6. Commit your changes and push your branch to GitHub:: + + $ git add . + $ git commit -m "Your detailed description of your changes." + $ git push origin name-of-your-bugfix-or-feature + +7. Submit a pull request through the GitHub website. + +Pull Request Guidelines +----------------------- + +Before you submit a pull request, check that it meets these guidelines: + +1. The pull request should include tests. +2. If the pull request adds functionality, the docs should be updated. Put + your new functionality into a function with a docstring, and add the + feature to the list in README.rst. +3. The pull request should work for Python 2.6, 2.7, 3.3, and 3.4, and for PyPy. Check + https://travis-ci.org/snark/ignorance/pull_requests + and make sure that the tests pass for all supported Python versions. + +Tips +---- + +To run a subset of tests:: + + $ python -m unittest tests.test_ignorance diff --git a/HISTORY.rst b/HISTORY.rst new file mode 100644 index 0000000..11c3f1c --- /dev/null +++ b/HISTORY.rst @@ -0,0 +1,9 @@ +.. :changelog: + +History +------- + +0.1.0 (2015-11-03) +--------------------- + +* First release on PyPI. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4d1906b --- /dev/null +++ b/LICENSE @@ -0,0 +1,14 @@ +Copyright (c) 2015, Steve Cook +All rights reserved. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..2bb6bb1 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,11 @@ +include AUTHORS.rst +include CONTRIBUTING.rst +include HISTORY.rst +include LICENSE +include README.rst + +recursive-include tests * +recursive-exclude * __pycache__ +recursive-exclude * *.py[co] + +recursive-include docs *.rst conf.py Makefile make.bat diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..223a224 --- /dev/null +++ b/Makefile @@ -0,0 +1,84 @@ +.PHONY: clean-pyc clean-build docs clean +define BROWSER_PYSCRIPT +import os, webbrowser, sys +try: + from urllib import pathname2url +except: + from urllib.request import pathname2url + +webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) +endef +export BROWSER_PYSCRIPT +BROWSER := python -c "$$BROWSER_PYSCRIPT" + +help: + @echo "clean - remove all build, test, coverage and Python artifacts" + @echo "clean-build - remove build artifacts" + @echo "clean-pyc - remove Python file artifacts" + @echo "clean-test - remove test and coverage artifacts" + @echo "lint - check style with flake8" + @echo "test - run tests quickly with the default Python" + @echo "test-all - run tests on every Python version with tox" + @echo "coverage - check code coverage quickly with the default Python" + @echo "docs - generate Sphinx HTML documentation, including API docs" + @echo "release - package and upload a release" + @echo "dist - package" + @echo "install - install the package to the active Python's site-packages" + +clean: clean-build clean-pyc clean-test + +clean-build: + rm -fr build/ + rm -fr dist/ + rm -fr .eggs/ + find . -name '*.egg-info' -exec rm -fr {} + + find . -name '*.egg' -exec rm -f {} + + +clean-pyc: + find . -name '*.pyc' -exec rm -f {} + + find . -name '*.pyo' -exec rm -f {} + + find . -name '*~' -exec rm -f {} + + find . -name '__pycache__' -exec rm -fr {} + + +clean-test: + rm -fr .tox/ + rm -f .coverage + rm -fr htmlcov/ + +lint: + flake8 ignorance tests + +test: + python setup.py test + +test-all: + tox + +coverage: + coverage run --source ignorance setup.py test + coverage report -m + coverage html + $(BROWSER) htmlcov/index.html + +docs: + rm -f docs/ignorance.rst + rm -f docs/modules.rst + sphinx-apidoc -o docs/ ignorance + $(MAKE) -C docs clean + $(MAKE) -C docs html + $(BROWSER) docs/_build/html/index.html + +servedocs: docs + watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . + +release: clean + python setup.py sdist upload + python setup.py bdist_wheel upload + +dist: clean + python setup.py sdist + python setup.py bdist_wheel + ls -l dist + +install: clean + python setup.py install diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..f610bb1 --- /dev/null +++ b/README.rst @@ -0,0 +1,20 @@ +=============================== +Ignorance +=============================== + +.. image:: https://img.shields.io/travis/snark/ignorance.svg + :target: https://travis-ci.org/snark/ignorance + +.. image:: https://img.shields.io/pypi/v/ignorance.svg + :target: https://pypi.python.org/pypi/ignorance + + +A spec-compliant gitignore parser + +* Free software: ISC license +* Documentation: https://ignorance.readthedocs.org. + +Features +-------- + +* A .gitignore-aware wrapper around os.walk diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..625e4a0 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,177 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# 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 ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +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/ignorance.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ignorance.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/ignorance" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ignorance" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/authors.rst b/docs/authors.rst new file mode 100644 index 0000000..e122f91 --- /dev/null +++ b/docs/authors.rst @@ -0,0 +1 @@ +.. include:: ../AUTHORS.rst diff --git a/docs/conf.py b/docs/conf.py new file mode 100755 index 0000000..3a3db1a --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,275 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# ignorance documentation build configuration file, created by +# sphinx-quickstart on Tue Jul 9 22:26:36 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 +import 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('.')) + +# Get the project root dir, which is the parent dir of this +cwd = os.getcwd() +project_root = os.path.dirname(cwd) + +# Insert the project root dir as the first element in the PYTHONPATH. +# This lets us ensure that the source package is imported, and that its +# version is used. +sys.path.insert(0, project_root) + +import ignorance + +# -- 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'Ignorance' +copyright = u'2015, Steve Cook' + +# The version info for the project you're documenting, acts as replacement +# for |version| and |release|, also used in various other places throughout +# the built documents. +# +# The short X.Y version. +version = ignorance.__version__ +# The full version, including alpha/beta/rc tags. +release = ignorance.__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 = [] + +# If true, keep warnings as "system message" paragraphs in the built +# documents. +#keep_warnings = False + + +# -- 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' + +# 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 +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as +# html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the +# top of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon +# of the docs. This file should be a Windows icon file (.ico) being +# 16x16 or 32x32 pixels large. +#html_favicon = 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 = True + +# If true, "Created using Sphinx" is shown in the HTML footer. +# Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. +# Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages +# will contain a tag referring to it. The value of this option +# must be the base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'ignorancedoc' + + +# -- 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', 'ignorance.tex', + u'Ignorance Documentation', + u'Steve Cook', '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', 'ignorance', + u'Ignorance Documentation', + [u'Steve Cook'], 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', 'ignorance', + u'Ignorance Documentation', + u'Steve Cook', + 'ignorance', + 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False diff --git a/docs/contributing.rst b/docs/contributing.rst new file mode 100644 index 0000000..e582053 --- /dev/null +++ b/docs/contributing.rst @@ -0,0 +1 @@ +.. include:: ../CONTRIBUTING.rst diff --git a/docs/history.rst b/docs/history.rst new file mode 100644 index 0000000..2506499 --- /dev/null +++ b/docs/history.rst @@ -0,0 +1 @@ +.. include:: ../HISTORY.rst diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..74e93bf --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,27 @@ +.. ignorance documentation master file, created by + sphinx-quickstart on Tue Jul 9 22:26:36 2013. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Ignorance's documentation! +====================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + readme + installation + usage + contributing + authors + history + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 0000000..d1bf69b --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,12 @@ +============ +Installation +============ + +At the command line:: + + $ easy_install ignorance + +Or, if you have virtualenvwrapper installed:: + + $ mkvirtualenv ignorance + $ pip install ignorance diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..a9a0b23 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,242 @@ +@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 ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. 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. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +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\ignorance.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\ignorance.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" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %BUILDDIR%/.. + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %BUILDDIR%/.. + echo. + echo.Build finished; the PDF 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 +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/docs/readme.rst b/docs/readme.rst new file mode 100644 index 0000000..72a3355 --- /dev/null +++ b/docs/readme.rst @@ -0,0 +1 @@ +.. include:: ../README.rst diff --git a/docs/usage.rst b/docs/usage.rst new file mode 100644 index 0000000..8a873c3 --- /dev/null +++ b/docs/usage.rst @@ -0,0 +1,7 @@ +======== +Usage +======== + +To use Ignorance in a project:: + + import ignorance diff --git a/ignorance/__init__.py b/ignorance/__init__.py new file mode 100755 index 0000000..e23d174 --- /dev/null +++ b/ignorance/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +__author__ = 'Steve Cook' +__email__ = 'steve@snarkout.org' +__version__ = '0.1.0' diff --git a/ignorance/git.py b/ignorance/git.py new file mode 100644 index 0000000..00ca7fb --- /dev/null +++ b/ignorance/git.py @@ -0,0 +1,175 @@ +# -*- coding: utf-8 -*- +import functools +import itertools +import os +import re +from . import utils +try: + # scandir-powered walk is in the stdlib in python 3.5+ + from os import scandir # NOQA + from os import walk as _walk +except ImportError: + # otherwise, grab it from scandir for the performance boost + from scandir import walk as _walk + + +def strict_subpath(base_path, child_path): + if os.path.join(child_path, '') == os.path.join(base_path, ''): + return '' + else: + child_split = child_path.split(os.path.sep) + base_split = base_path.split(os.path.sep) + return os.path.join( + *child_split[len(base_split):] + ) + + +def walk(directory, onerror=None, filename='.gitignore', + ignore_completely=None): + """ + Generate the file names in a directory tree by walking the tree + top-down, while obeying the rules of .gitignore. Links will not + be followed. + """ + if ignore_completely is None: + ignore_completely = ['.git'] + + starting_directory = os.path.abspath(directory) + + # Rule list will be a dict of directory to (rule list, parent + # directory) tuples + rule_list = {} + while True: + for root, dirs, files in _walk(directory, onerror=onerror): + rules = [] + if filename in files: + with open(os.path.join(root, filename)) as ignore_file: + for line in ignore_file: + line = line.rstrip('\n') + rule = rule_from_pattern(line, os.path.abspath(root)) + if rule: + rules.append(rule) + current_dir = os.path.abspath(root) + # SBC fix this up + # We know we are either within the base_dir... + rel_path = strict_subpath(starting_directory, current_dir) + if rel_path == '': + rule_list[''] = (rules, None) + # ...or a child dir + else: + rule_list[rel_path] = (rules, os.path.dirname(rel_path)) + # Now, make a list of rules, working our way back to the + # base directory. + applicable_rules = [] + cursor = rule_list[rel_path] + while cursor: + applicable_rules.append(cursor[0]) + # Tiny hack for py2/py3 polyglot without using six + if isinstance(cursor[1], (''.__class__, u''.__class__)): + cursor = rule_list[cursor[1]] + else: + cursor = False + # Our rules are actually ordered from the base down + applicable_rules = applicable_rules[::-1] + flat_list = list( + itertools.chain.from_iterable(applicable_rules) + ) + ignore = [] + for final_override in ignore_completely: + ignore.append(final_override) + for d in dirs: + included = True + path = os.path.abspath(os.path.join(root, d)) + for rule in flat_list: + if included != rule.negation: + if rule.match(path): + included = not included + if not included: + ignore.append(d) + dirs[:] = [d for d in dirs if d not in ignore] + ignore = [] + for f in files: + included = True + path = os.path.join(root, f) + for rule in flat_list: + if rule.directory_only: + continue + if included != rule.negation: + if rule.match(os.path.abspath(path)): + included = not included + if not included: + ignore.append(f) + files[:] = [f for f in files if f not in ignore] + yield root, dirs, files + return + + +def rule_from_pattern(pattern, base_path=None, source=None): + """ + Take a .gitignore match pattern, such as "*.py[cod]" or "**/*.bak", + and return an IgnoreRule suitable for matching against files and + directories. Patterns which do not match files, such as comments + and blank lines, will return None. + + Because git allows for nested .gitignore files, a base_path value + is required for correct behavior. The base path should be absolute. + """ + if base_path and base_path != os.path.abspath(base_path): + raise ValueError('base_path must be absolute') + # Early returns follow + # Discard comments and seperators + if pattern.strip() == '' or pattern[0] == '#': + return + # Discard anything with more than two consecutive asterisks + if pattern.find('***') > -1: + return + # Discard anything with invalid double-asterisks -- they can appear + # at the start or the end, or be surrounded by slashes + for m in re.finditer(r'\*\*', pattern): + start_index = m.start() + if (start_index != 0 and start_index != len(pattern) - 2 and + (pattern[start_index - 1] != '/' or + pattern[start_index + 2] != '/')): + return + + # Store the exact pattern for our repr and string functions + orig_pattern = pattern + + if pattern[0] == '!': + negation = True + pattern = pattern[1:] + else: + negation = False + + # Special-casing '/', which doesn't match any files or directories + if pattern.strip() == '/': + return + + directory_only = pattern[-1] == '/' + # A slash is a sign that we're tied to the base_path of our rule + # set. + anchored = '/' in pattern[:-1] + relative_path = functools.partial(strict_subpath, base_path or '') + if pattern[0] == '/': + pattern = pattern[1:] + if pattern[0] == '*' and pattern[1] == '*': + pattern = pattern[2:] + anchored = False + if pattern[0] == '/': + pattern = pattern[1:] + if pattern[-1] == '/': + pattern = pattern[:-1] + regex = utils.fnmatch_pathname_to_regex( + pattern + ) + if anchored: + regex = ''.join(['^', regex]) + return utils.IgnoreRule( + pattern=orig_pattern, + regex=regex, + negation=negation, + directory_only=directory_only, + anchored=anchored, + relative_path=relative_path, + source=source + ) diff --git a/ignorance/utils.py b/ignorance/utils.py new file mode 100644 index 0000000..348f64c --- /dev/null +++ b/ignorance/utils.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +import os +import re +import collections + +whitespace_re = re.compile(r'(\\ )+$') + +IGNORE_RULE_FIELDS = [ + 'pattern', 'regex', # Basic values + 'negation', 'directory_only', 'anchored', # Behavior flags + 'relative_path', # Meaningful for gitignore-style behavior + 'source' # (file, line) tuple for reporting +] + + +class IgnoreRule(collections.namedtuple('IgnoreRule_', IGNORE_RULE_FIELDS)): + # TODO Add source to __str__ or perhaps a method + def __str__(self): + return self.pattern + + def __repr__(self): + return ''.join(['IgnoreRule(\'', self.pattern, '\')']) + + def match(self, abs_path): + matched = False + rel_path = self.relative_path(abs_path) + if re.search(self.regex, rel_path): + matched = True + return matched + + +# Frustratingly, python's fnmatch doesn't provide the FNM_PATHNAME +# option that .gitignore's behavior depends on. +def fnmatch_pathname_to_regex(pattern): + """ + Implements fnmatch style-behavior, as though with FNM_PATHNAME flagged; + the path seperator will not match shell-style '*' and '.' wildcards. + """ + i, n = 0, len(pattern) + nonsep = ''.join(['[^', os.sep, ']']) + res = [] + while i < n: + c = pattern[i] + i = i + 1 + if c == '*': + try: + if pattern[i] == '*': + i = i + 1 + res.append('.*') + if pattern[i] == '/': + i = i + 1 + res.append(''.join([os.sep, '?'])) + else: + res.append(''.join([nonsep, '*'])) + except IndexError: + res.append(''.join([nonsep, '*'])) + elif c == '?': + res.append(nonsep) + elif c == '/': + res.append(os.sep) + elif c == '[': + j = i + if j < n and pattern[j] == '!': + j = j + 1 + if j < n and pattern[j] == ']': + j = j + 1 + while j < n and pattern[j] != ']': + j = j+1 + if j >= n: + res.append('\\[') + else: + stuff = pattern[i:j].replace('\\', '\\\\') + i = j+1 + if stuff[0] == '!': + stuff = ''.join('^', stuff[1:]) + elif stuff[0] == '^': + stuff = ''.join('\\' + stuff) + res.append('[{}]'.format(stuff)) + else: + res.append(re.escape(c)) + res.append('\Z(?ms)') + return ''.join(res) diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..5ee6477 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +testpaths = tests diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e617b73 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +scandir==1.2 diff --git a/requirements_dev.txt b/requirements_dev.txt new file mode 100644 index 0000000..0f8b477 --- /dev/null +++ b/requirements_dev.txt @@ -0,0 +1,10 @@ +bumpversion==0.5.3 +wheel==0.23.0 +watchdog==0.8.3 +flake8==2.4.1 +tox==2.1.1 +coverage==4.0 +Sphinx==1.3.1 +cryptography==1.0.1 +PyYAML==3.11 +pytest==2.8.3 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..558754a --- /dev/null +++ b/setup.cfg @@ -0,0 +1,14 @@ +[bumpversion] +current_version = 0.1.0 +commit = True +tag = True + +[bumpversion:file:setup.py] + +[bumpversion:file:ignorance/__init__.py] + +[aliases] +test=pytest + +[wheel] +universal = 1 diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..862aec1 --- /dev/null +++ b/setup.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + + +try: + from setuptools import setup +except ImportError: + from distutils.core import setup + + +with open('README.rst') as readme_file: + readme = readme_file.read() + +with open('HISTORY.rst') as history_file: + history = history_file.read().replace('.. :changelog:', '') + +requirements = [ + 'scandir' +] + +test_requirements = [ + # TODO: put package test requirements here +] + +setup( + name='ignorance', + version='0.1.0', + description="A spec-compliant gitignore parser", + long_description=readme + '\n\n' + history, + author="Steve Cook", + author_email='snarkout@gmail.com', + url='https://github.com/snark/ignorance', + packages=[ + 'ignorance', + ], + package_dir={'ignorance': + 'ignorance'}, + include_package_data=True, + install_requires=requirements, + setup_requires=['pytest-runner'], + tests_require=['pytest'], + license="ISCL", + zip_safe=False, + keywords='ignorance', + classifiers=[ + 'Development Status :: 2 - Pre-Alpha', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: ISC License (ISCL)', + 'Natural Language :: English', + "Programming Language :: Python :: 2", + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + ], +) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100755 index 0000000..40a96af --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/tests/test_git_basicwalk.py b/tests/test_git_basicwalk.py new file mode 100644 index 0000000..9e24230 --- /dev/null +++ b/tests/test_git_basicwalk.py @@ -0,0 +1,9 @@ +# content of test_git_basicwalk.py +from ignorance import git + +def test_strict_subpath(): + assert git.strict_subpath('/foo/bar', '/foo/bar') == '' + assert git.strict_subpath('/foo/bar/', '/foo/bar') == '' + assert git.strict_subpath('/foo/bar', '/foo/bar/') == '' + + diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..04e2518 --- /dev/null +++ b/tox.ini @@ -0,0 +1,10 @@ +[tox] +envlist = py26, py27, py34, py35 + +[testenv] +setenv = + PYTHONPATH = {toxinidir}:{toxinidir}/ignorance +commands = python setup.py test +deps = + -r{toxinidir}/requirements.txt + -r{toxinidir}/requirements_dev.txt diff --git a/travis_pypi_setup.py b/travis_pypi_setup.py new file mode 100755 index 0000000..ae216de --- /dev/null +++ b/travis_pypi_setup.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""Update encrypted deploy password in Travis config file +""" + + +from __future__ import print_function +import base64 +import json +import os +from getpass import getpass +import yaml +from cryptography.hazmat.primitives.serialization import load_pem_public_key +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 + + +try: + from urllib import urlopen +except: + from urllib.request import urlopen + + +GITHUB_REPO = 'snark/ignorance' +TRAVIS_CONFIG_FILE = os.path.join( + os.path.dirname(os.path.abspath(__file__)), '.travis.yml') + + +def load_key(pubkey): + """Load public RSA key, with work-around for keys using + incorrect header/footer format. + + Read more about RSA encryption with cryptography: + https://cryptography.io/latest/hazmat/primitives/asymmetric/rsa/ + """ + try: + return load_pem_public_key(pubkey.encode(), default_backend()) + except ValueError: + # workaround for https://github.com/travis-ci/travis-api/issues/196 + pubkey = pubkey.replace('BEGIN RSA', 'BEGIN').replace('END RSA', 'END') + return load_pem_public_key(pubkey.encode(), default_backend()) + + +def encrypt(pubkey, password): + """Encrypt password using given RSA public key and encode it with base64. + + The encrypted password can only be decrypted by someone with the + private key (in this case, only Travis). + """ + key = load_key(pubkey) + encrypted_password = key.encrypt(password, PKCS1v15()) + return base64.b64encode(encrypted_password) + + +def fetch_public_key(repo): + """Download RSA public key Travis will use for this repo. + + Travis API docs: http://docs.travis-ci.com/api/#repository-keys + """ + keyurl = 'https://api.travis-ci.org/repos/{0}/key'.format(repo) + data = json.loads(urlopen(keyurl).read()) + if 'key' not in data: + errmsg = "Could not find public key for repo: {}.\n".format(repo) + errmsg += "Have you already added your GitHub repo to Travis?" + raise ValueError(errmsg) + return data['key'] + + +def prepend_line(filepath, line): + """Rewrite a file adding a line to its beginning. + """ + with open(filepath) as f: + lines = f.readlines() + + lines.insert(0, line) + + with open(filepath, 'w') as f: + f.writelines(lines) + + +def load_yaml_config(filepath): + with open(filepath) as f: + return yaml.load(f) + + +def save_yaml_config(filepath, config): + with open(filepath, 'w') as f: + yaml.dump(config, f, default_flow_style=False) + + +def update_travis_deploy_password(encrypted_password): + """Update the deploy section of the .travis.yml file + to use the given encrypted password. + """ + config = load_yaml_config(TRAVIS_CONFIG_FILE) + + config['deploy']['password'] = dict(secure=encrypted_password) + + save_yaml_config(TRAVIS_CONFIG_FILE, config) + + line = ('# This file was autogenerated and will overwrite' + ' each time you run travis_pypi_setup.py\n') + prepend_line(TRAVIS_CONFIG_FILE, line) + + +def main(args): + public_key = fetch_public_key(args.repo) + password = args.password or getpass('PyPI password: ') + update_travis_deploy_password(encrypt(public_key, password)) + print("Wrote encrypted password to .travis.yml -- you're ready to deploy") + + +if '__main__' == __name__: + import argparse + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--repo', default=GITHUB_REPO, + help='GitHub repo (default: %s)' % GITHUB_REPO) + parser.add_argument('--password', + help='PyPI password (will prompt if not provided)') + + args = parser.parse_args() + main(args)