From 5550834eae424ac5cfa223b75bdb281fa8b9478f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Mon, 23 Dec 2013 20:31:42 +0100 Subject: Normalize docs to docs/ (Closes #5). Also normalize the package layout. Thanks @jdowner-gb & tleach for the report. --- .gitignore | 14 +- ChangeLog | 98 +++++- Makefile | 2 +- doc/Makefile | 153 ---------- doc/_static/.keep_dir | 0 doc/changelog.rst | 97 ------ doc/conf.py | 251 ---------------- doc/django.rst | 31 -- doc/index.rst | 1 - doc/make.bat | 190 ------------ doc/reference.rst | 546 ---------------------------------- docs/Makefile | 153 ++++++++++ docs/_static/.keep_dir | 0 docs/changelog.rst | 1 + docs/conf.py | 251 ++++++++++++++++ docs/django.rst | 31 ++ docs/index.rst | 1 + docs/make.bat | 190 ++++++++++++ docs/reference.rst | 546 ++++++++++++++++++++++++++++++++++ semantic_version/__init__.py | 10 + semantic_version/base.py | 490 ++++++++++++++++++++++++++++++ semantic_version/compat.py | 18 ++ semantic_version/django_fields.py | 100 +++++++ setup.py | 88 ++---- src/semantic_version/__init__.py | 10 - src/semantic_version/base.py | 490 ------------------------------ src/semantic_version/compat.py | 18 -- src/semantic_version/django_fields.py | 100 ------- 28 files changed, 1922 insertions(+), 1958 deletions(-) mode change 120000 => 100644 ChangeLog delete mode 100644 doc/Makefile delete mode 100644 doc/_static/.keep_dir delete mode 100644 doc/changelog.rst delete mode 100644 doc/conf.py delete mode 100644 doc/django.rst delete mode 120000 doc/index.rst delete mode 100644 doc/make.bat delete mode 100644 doc/reference.rst create mode 100644 docs/Makefile create mode 100644 docs/_static/.keep_dir create mode 120000 docs/changelog.rst create mode 100644 docs/conf.py create mode 100644 docs/django.rst create mode 120000 docs/index.rst create mode 100644 docs/make.bat create mode 100644 docs/reference.rst create mode 100644 semantic_version/__init__.py create mode 100644 semantic_version/base.py create mode 100644 semantic_version/compat.py create mode 100644 semantic_version/django_fields.py delete mode 100644 src/semantic_version/__init__.py delete mode 100644 src/semantic_version/base.py delete mode 100644 src/semantic_version/compat.py delete mode 100644 src/semantic_version/django_fields.py diff --git a/.gitignore b/.gitignore index ac7de2e..5ebc5ac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,14 @@ -*.pyc +# Temporary files .*.swp +*.pyc +*.pyo + +# Build-related files +docs/_build/ .coverage -htmlcov/ -tests/db/ +*.egg-info +*.egg +build/ dist/ -doc/_build/ +htmlcov/ MANIFEST diff --git a/ChangeLog b/ChangeLog deleted file mode 120000 index 36ab6c5..0000000 --- a/ChangeLog +++ /dev/null @@ -1 +0,0 @@ -doc/changelog.rst \ No newline at end of file diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..481d6a7 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,97 @@ +ChangeLog +========= + + +2.2.1 (2013-10-29) +------------------ + +*Bugfix:* + + * `#2 `_: Properly expose + :func:`~semantic_version.validate` as a top-level module function. + +2.2.0 (2013-03-22) +------------------ + +*Bugfix:* + + * `#1 `_: Allow partial + versions without minor or patch level + +*New:* + + * Add the :meth:`Version.coerce ` class method to + :class:`~semantic_version.Version` class for mapping arbitrary version strings to + semver. + * Add the :func:`~semantic_version.validate` method to validate a version + string against the SemVer rules. + * Full Python3 support + +2.1.2 (2012-05-22) +------------------ + +*Bugfix:* + + * Properly validate :class:`~semantic_version.django_fields.VersionField` and + :class:`~semantic_version.django_fields.SpecField`. + +2.1.1 (2012-05-22) +------------------ + +*New:* + + * Add introspection rules for south + +2.1.0 (2012-05-22) +------------------ + +*New:* + + * Add :func:`semantic_version.Spec.filter` (filter a list of :class:`~semantic_version.Version`) + * Add :func:`semantic_version.Spec.select` (select the highest + :class:`~semantic_version.Version` from a list) + * Update :func:`semantic_version.Version.__repr__` + +2.0.0 (2012-05-22) +------------------ + +*Backwards incompatible changes:* + + * Removed "loose" specification support + * Cleanup :class:`~semantic_version.Spec` to be more intuitive. + * Merge Spec and SpecList into :class:`~semantic_version.Spec`. + * Remove :class:`~semantic_version.django_fields.SpecListField` + +1.2.0 (2012-05-18) +------------------ + +*New:* + + * Allow split specifications when instantiating a + :class:`~semantic_version.SpecList`:: + + >>> SpecList('>=0.1.1', '!=0.1.3') == SpecList('>=0.1.1,!=0.1.3') + True + +1.1.0 (2012-05-18) +------------------ + +*New:* + + * Improved "loose" specification support (``>~``, ``<~``, ``!~``) + * Introduced "not equal" specifications (``!=``, ``!~``) + * :class:`~semantic_version.SpecList` class combining many :class:`~semantic_version.Spec` + * Add :class:`~semantic_version.django_fields.SpecListField` to store a :class:`~semantic_version.SpecList`. + +1.0.0 (2012-05-17) +------------------ + +First public release. + +*New:* + + * :class:`~semantic_version.Version` and :class:`~semantic_version.Spec` classes + * Related django fields: :class:`~semantic_version.django_fields.VersionField` + and :class:`~semantic_version.django_fields.SpecField` + +.. vim:et:ts=4:sw=4:tw=79:ft=rst: diff --git a/Makefile b/Makefile index 9ccf1e7..22e50d8 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ coverage: coverage html "--include=$(PACKAGE_DIR)/*.py,tests/*.py" doc: - $(MAKE) -C doc html + $(MAKE) -C docs html .PHONY: all default clean coverage doc test diff --git a/doc/Makefile b/doc/Makefile deleted file mode 100644 index 57270ef..0000000 --- a/doc/Makefile +++ /dev/null @@ -1,153 +0,0 @@ -# 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 ' 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 " 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-semanticversion.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-semanticversion.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-semanticversion" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-semanticversion" - @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." diff --git a/doc/_static/.keep_dir b/doc/_static/.keep_dir deleted file mode 100644 index e69de29..0000000 diff --git a/doc/changelog.rst b/doc/changelog.rst deleted file mode 100644 index 481d6a7..0000000 --- a/doc/changelog.rst +++ /dev/null @@ -1,97 +0,0 @@ -ChangeLog -========= - - -2.2.1 (2013-10-29) ------------------- - -*Bugfix:* - - * `#2 `_: Properly expose - :func:`~semantic_version.validate` as a top-level module function. - -2.2.0 (2013-03-22) ------------------- - -*Bugfix:* - - * `#1 `_: Allow partial - versions without minor or patch level - -*New:* - - * Add the :meth:`Version.coerce ` class method to - :class:`~semantic_version.Version` class for mapping arbitrary version strings to - semver. - * Add the :func:`~semantic_version.validate` method to validate a version - string against the SemVer rules. - * Full Python3 support - -2.1.2 (2012-05-22) ------------------- - -*Bugfix:* - - * Properly validate :class:`~semantic_version.django_fields.VersionField` and - :class:`~semantic_version.django_fields.SpecField`. - -2.1.1 (2012-05-22) ------------------- - -*New:* - - * Add introspection rules for south - -2.1.0 (2012-05-22) ------------------- - -*New:* - - * Add :func:`semantic_version.Spec.filter` (filter a list of :class:`~semantic_version.Version`) - * Add :func:`semantic_version.Spec.select` (select the highest - :class:`~semantic_version.Version` from a list) - * Update :func:`semantic_version.Version.__repr__` - -2.0.0 (2012-05-22) ------------------- - -*Backwards incompatible changes:* - - * Removed "loose" specification support - * Cleanup :class:`~semantic_version.Spec` to be more intuitive. - * Merge Spec and SpecList into :class:`~semantic_version.Spec`. - * Remove :class:`~semantic_version.django_fields.SpecListField` - -1.2.0 (2012-05-18) ------------------- - -*New:* - - * Allow split specifications when instantiating a - :class:`~semantic_version.SpecList`:: - - >>> SpecList('>=0.1.1', '!=0.1.3') == SpecList('>=0.1.1,!=0.1.3') - True - -1.1.0 (2012-05-18) ------------------- - -*New:* - - * Improved "loose" specification support (``>~``, ``<~``, ``!~``) - * Introduced "not equal" specifications (``!=``, ``!~``) - * :class:`~semantic_version.SpecList` class combining many :class:`~semantic_version.Spec` - * Add :class:`~semantic_version.django_fields.SpecListField` to store a :class:`~semantic_version.SpecList`. - -1.0.0 (2012-05-17) ------------------- - -First public release. - -*New:* - - * :class:`~semantic_version.Version` and :class:`~semantic_version.Spec` classes - * Related django fields: :class:`~semantic_version.django_fields.VersionField` - and :class:`~semantic_version.django_fields.SpecField` - -.. vim:et:ts=4:sw=4:tw=79:ft=rst: diff --git a/doc/conf.py b/doc/conf.py deleted file mode 100644 index 611f3c0..0000000 --- a/doc/conf.py +++ /dev/null @@ -1,251 +0,0 @@ -# -*- coding: utf-8 -*- -# -# python-semanticversion documentation build configuration file, created by -# sphinx-quickstart on Wed May 16 10:41:34 2012. -# -# 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('.')) - -# -- 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 = [] - -# 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-semanticversion' -copyright = u'2012-2013, Raphaël Barrois' - -# 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. -# -root_dir = os.path.abspath(os.path.dirname(__file__)) -def get_version(): - import re - version_re = re.compile(r"^__version__ = '([\w_.-]+)'$") - with open(os.path.join(root_dir, os.pardir, 'src', 'semantic_version', '__init__.py')) as f: - for line in f: - match = version_re.match(line[:-1]) - if match: - return match.groups()[0] - return '0.0.0' - -release = get_version() -version = '.'.join(release.split('.')[:2]) - -# 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' - -# 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 = 'python-semanticversiondoc' - - -# -- 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-semanticversion.tex', u'python-semanticversion Documentation', - u'Raphaël Barrois', '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-semanticversion', u'python-semanticversion Documentation', - [u'Raphaël Barrois'], 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-semanticversion', u'python-semanticversion Documentation', - u'Raphaël Barrois', 'python-semanticversion', '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' diff --git a/doc/django.rst b/doc/django.rst deleted file mode 100644 index a43c3ed..0000000 --- a/doc/django.rst +++ /dev/null @@ -1,31 +0,0 @@ -Interaction with Django -======================= - -.. module:: semantic_version.django_fields - -The ``python-semanticversion`` package provides two custom fields for Django: - -- :class:`VersionField`: stores a :class:`semantic_version.Version` object -- :class:`SpecField`: stores a :class:`semantic_version.Spec` object - -Those fields are :class:`django.db.models.CharField` subclasses, -with their :attr:`~django.db.models.CharField.max_length` defaulting to 200. - - -.. class:: VersionField - - Stores a :class:`semantic_version.Version` as its string representation. - - .. attribute:: partial - - Boolean; whether :attr:`~semantic_version.Version.partial` versions are allowed. - - .. attribute:: coerce - - Boolean; whether passed in values should be coerced into a semver string - before storing. - - -.. class:: SpecField - - Stores a :class:`semantic_version.Spec` as its comma-separated string representation. diff --git a/doc/index.rst b/doc/index.rst deleted file mode 120000 index 89a0106..0000000 --- a/doc/index.rst +++ /dev/null @@ -1 +0,0 @@ -../README.rst \ No newline at end of file diff --git a/doc/make.bat b/doc/make.bat deleted file mode 100644 index 5327a34..0000000 --- a/doc/make.bat +++ /dev/null @@ -1,190 +0,0 @@ -@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. 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-semanticversion.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\python-semanticversion.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 diff --git a/doc/reference.rst b/doc/reference.rst deleted file mode 100644 index 5b8ca3e..0000000 --- a/doc/reference.rst +++ /dev/null @@ -1,546 +0,0 @@ -Reference -========= - - -.. module:: semantic_version - - -Module-level functions ----------------------- - -.. function:: compare(v1, v2) - - Compare two version strings, and return a result similar to that of :func:`cmp`:: - - >>> compare('0.1.1', '0.1.2') - -1 - >>> compare('0.1.1', '0.1.1') - 0 - >>> compare('0.1.1', '0.1.1-alpha') - 1 - - :param str v1: The first version to compare - :param str v2: The second version to compare - :raises: :exc:`ValueError`, if any version string is invalid - :rtype: ``int``, -1 / 0 / 1 as for a :func:`cmp` comparison - - - -.. function:: match(spec, version) - - Check whether a version string matches a specification string:: - - >>> match('>=0.1.1', '0.1.2') - True - >>> match('>=0.1.1', '0.1.1-alpha') - False - >>> match('~0.1.1', '0.1.1-alpha') - True - - :param str spec: The specification to use, as a string - :param str version: The version string to test against the spec - :raises: :exc:`ValueError`, if the ``spec`` or the ``version`` is invalid - :rtype: ``bool`` - - -.. function:: validate(version) - - Check whether a version string complies with the `SemVer`_ rules. - - .. code-block:: pycon - - >>> semantic_version.validate('1.1.1') - True - >>> semantic_version.validate('1.2.3a4') - False - - :param str version: The version string to validate - :rtype: ``bool`` - - -Representing a version (the Version class) ------------------------------------------- - -.. class:: Version(version_string[, partial=False]) - - Object representation of a `SemVer`_-compliant version. - - Constructed from a textual version string:: - - >>> Version('1.1.1') - Version('1.1.1') - >>> str(Version('1.1.1')) - '1.1.1' - - - .. rubric:: Attributes - - - .. attribute:: partial - - ``bool``, whether this is a 'partial' or a complete version number. - Partial version number may lack :attr:`minor` or :attr:`patch` version numbers. - - .. attribute:: major - - ``int``, the major version number - - .. attribute:: minor - - ``int``, the minor version number. - - May be ``None`` for a :attr:`partial` version number in a ```` format. - - .. attribute:: patch - - ``int``, the patch version number. - - May be ``None`` for a :attr:`partial` version number in a ```` or ``.`` format. - - .. attribute:: prerelease - - ``tuple`` of ``strings``, the prerelease component. - - It contains the various dot-separated identifiers in the prerelease component. - - May be ``None`` for a :attr:`partial` version number in a ````, ``.`` or ``..`` format. - - .. attribute:: build - - ``tuple`` of ``strings``, the build component. - - It contains the various dot-separated identifiers in the build component. - - May be ``None`` for a :attr:`partial` version number in a ````, ``.``, - ``..`` or ``..-`` format. - - - .. rubric:: Methods - - - .. method:: __iter__(self) - - Iterates over the version components (:attr:`major`, :attr:`minor`, - :attr:`patch`, :attr:`prerelease`, :attr:`build`):: - - >>> list(Version('0.1.1')) - [0, 1, 1, [], []] - - .. note:: This may pose some subtle bugs when iterating over a single version - while expecting an iterable of versions -- similar to:: - - >>> list('abc') - ['a', 'b', 'c'] - >>> list(('abc',)) - ['abc'] - - - .. method:: __cmp__(self, other) - - Provides comparison methods with other :class:`Version` objects. - - The rules are: - - - For non-:attr:`partial` versions, compare using the `SemVer`_ scheme - - If any compared object is :attr:`partial`: - - Begin comparison using the `SemVer`_ scheme - - If a component (:attr:`minor`, :attr:`patch`, :attr:`prerelease` or :attr:`build`) - was absent from the :attr:`partial` :class:`Version` -- represented with :obj:`None` - --, consider both versions equal. - - For instance, ``Version('1.0', partial=True)`` means "any version beginning in ``1.0``". - - ``Version('1.0.1-alpha', partial=True)`` means "The ``1.0.1-alpha`` version or any - ulterior build of that same version": ``1.0.1-alpha+build3`` matches, ``1.0.1-alpha.2`` doesn't. - - Examples:: - - >>> Version('1.0', partial=True) == Version('1.0.1') - True - >>> Version('1.0.1-rc1.1') == Version('1.0.1-rc1', partial=True) - False - >>> Version('1.0.1-rc1+build345') == Version('1.0.1-rc1') - False - >>> Version('1.0.1-rc1+build345') == Version('1.0.1-rc1', partial=True) - True - - - .. method:: __str__(self) - - Returns the standard text representation of the version:: - - >>> v = Version('0.1.1-rc2+build4.4') - >>> v - Version('0.1.1-rc2+build4.4') - >>> str(v) - '0.1.1-rc2+build4.4' - - - .. method:: __hash__(self) - - Provides a hash based solely on the components. - - Allows using a :class:`Version` as a dictionary key. - - .. note:: A fully qualified :attr:`partial` :class:`Version` - - (up to the :attr:`build` component) will hash the same as the - equally qualified, non-:attr:`partial` :class:`Version`:: - - >>> hash(Version('1.0.1+build4')) == hash(Version('1.0.1+build4', partial=True)) - True - - - .. rubric:: Class methods - - - .. classmethod:: parse(cls, version_string[, partial=False]) - - Parse a version string into a ``(major, minor, patch, prerelease, build)`` tuple. - - :param str version_string: The version string to parse - :param bool partial: Whether this should be considered a :attr:`partial` version - :raises: :exc:`ValueError`, if the :attr:`version_string` is invalid. - :rtype: (major, minor, patch, prerelease, build) - - .. classmethod:: coerce(cls, version_string[, partial=False]) - - Try to convert an arbitrary version string into a :class:`Version` instance. - - Rules are: - - - If no minor or patch component, and :attr:`partial` is :obj:`False`, - replace them with zeroes - - Any character outside of ``a-zA-Z0-9.+-`` is replaced with a ``-`` - - If more than 3 dot-separated numerical components, everything from the - fourth component belongs to the :attr:`build` part - - Any extra ``+`` in the :attr:`build` part will be replaced with dots - - Examples: - - .. code-block:: pycon - - >>> Version.coerce('02') - Version('2.0.0') - >>> Version.coerce('1.2.3.4') - Version('1.2.3+4') - >>> Version.coerce('1.2.3.4beta2') - Version('1.2.3+4beta2') - >>> Version.coerce('1.2.3.4.5_6/7+8+9+10') - Version('1.2.3+4.5-6-7.8.9.10') - - :param str version_string: The version string to coerce - :param bool partial: Whether to allow generating a :attr:`partial` version - :raises: :exc:`ValueError`, if the :attr:`version_string` is invalid. - :rtype: :class:`Version` - - -Version specifications (the Spec class) ---------------------------------------- - - -Version specifications describe a 'range' of accepted versions: -older than, equal, similar to, … - -The main issue with representing version specifications is that the usual syntax -does not map well onto `SemVer`_ precedence rules: - -* A specification of ``<1.3.4`` is not expected to allow ``1.3.4-rc2``, but strict `SemVer`_ comparisons allow it ; -* Converting the previous specification to ``<=1.3.3`` in order to avoid ``1.3.4`` - prereleases has the issue of excluding ``1.3.3+build3`` ; -* It may be necessary to exclude either all variations on a patch-level release - (``!=1.3.3``) or specifically one build-level release (``1.3.3-build.434``). - - -In order to have version specification behave naturally, the rules are the following: - -* If no pre-release number was included in the specification, pre-release numbers - are ignored when deciding whether a version satisfies a specification. -* If no build number was included in the specification, build numbers are ignored - when deciding whether a version satisfies a specification. - -This means that:: - - >>> Version('1.1.1-rc1') in Spec('<1.1.1') - False - >>> Version('1.1.1-rc1') in Spec('<1.1.1-rc4') - True - >>> Version('1.1.1-rc1+build4') in Spec('<=1.1.1-rc1') - True - >>> Version('1.1.1-rc1+build4') in Spec('<=1.1.1-rc1+build2') - False - -In order to force matches to *strictly* compare version numbers, these additional -rules apply: - -* Setting a pre-release separator without a pre-release identifier (``<=1.1.1-``) - forces match to take into account pre-release version:: - - >>> Version('1.1.1-rc1') in Spec('<1.1.1') - False - >>> Version('1.1.1-rc1') in Spec('<1.1.1-') - True - -* Setting a build separator without a build identifier (``>1.1.1+``) forces - satisfaction tests to include both prerelease and build identifiers:: - - >>> Version('1.1.1+build2') in Spec('>1.1.1') - False - >>> Version('1.1.1+build2') in Spec('>1.1.1+') - True - -.. class:: Spec(spec_string[, spec_string[, ...]]) - - Stores a list of :class:`SpecItem` and matches any :class:`Version` against all - contained :class:`specs `. - - It is build from a comma-separated list of version specifications:: - - >>> Spec('>=1.0.0,<1.2.0,!=1.1.4') - = Version('1.0.0', partial=True)>, - , - - )> - - Version specifications may also be passed in separated arguments:: - - >>> Spec('>=1.0.0', '<1.2.0', '!=1.1.4,!=1.1.13') - = Version('1.0.0', partial=True)>, - , - , - , - )> - - - .. rubric:: Attributes - - - .. attribute:: specs - - Tuple of :class:`SpecItem`, the included specifications. - - - .. rubric:: Methods - - - .. method:: match(self, version) - - Test whether a given :class:`Version` matches all included :class:`SpecItem`:: - - >>> Spec('>=1.1.0,<1.1.2').match(Version('1.1.1')) - True - - :param version: The version to test against the specs - :type version: :class:`Version` - :rtype: ``bool`` - - - .. method:: filter(self, versions) - - Extract all compatible :class:`versions ` from an iterable of - :class:`Version` objects. - - :param versions: The versions to filter - :type versions: iterable of :class:`Version` - :yield: :class:`Version` - - - .. method:: select(self, versions) - - Select the highest compatible version from an iterable of :class:`Version` - objects. - - .. sourcecode:: pycon - - >>> s = Spec('>=0.1.0') - >>> s.select([]) - None - >>> s.select([Version('0.1.0'), Version('0.1.3'), Version('0.1.1')]) - Version('0.1.3') - - :param versions: The versions to filter - :type versions: iterable of :class:`Version` - :rtype: The highest compatible :class:`Version` if at least one of the - given versions is compatible; :class:`None` otherwise. - - - .. method:: __contains__(self, version) - - Alias of the :func:`match` method; - allows the use of the ``version in speclist`` syntax:: - - >>> Version('1.1.1-alpha') in Spec('>=1.1.0,<1.1.1') - True - - - .. method:: __str__(self) - - Converting a :class:`Spec` returns the initial description string:: - - >>> str(Spec('>=0.1.1,!=0.1.2')) - '>=0.1.1,!=0.1.2' - - .. method:: __iter__(self) - - Returns an iterator over the contained specs:: - - >>> for spec in Spec('>=0.1.1,!=0.1.2'): - ... print spec - >=0.1.1 - !=0.1.2 - - .. method:: __hash__(self) - - Provides a hash based solely on the hash of contained specs. - - Allows using a :class:`Spec` as a dictionary key. - - - .. rubric:: Class methods - - - .. classmethod:: parse(self, specs_string) - - Retrieve a ``(*specs)`` tuple from a string. - - :param str requirement_string: The textual description of the specifications - :raises: :exc:`ValueError`: if the ``requirement_string`` is invalid. - :rtype: ``(*spec)`` tuple - - -.. class:: SpecItem(spec_string) - - .. note:: This class belong to the private python-semanticversion API. - - Stores a version specification, defined from a string:: - - >>> SpecItem('>=0.1.1') - = Version('0.1.1', partial=True)> - - This allows to test :class:`Version` objects against the :class:`SpecItem`:: - - >>> SpecItem('>=0.1.1').match(Version('0.1.1-rc1')) # pre-release satisfy conditions - True - >>> Version('0.1.1+build2') in SpecItem('>=0.1.1') # build version satisfy specifications - True - >>> - >>> # Use the '-' marker to include the pre-release component in checks - >>> SpecItem('>=0.1.1-').match(Version('0.1.1-rc1') - False - >>> - >>> # Use the '+' marker to include the build identifier in checks - >>> SpecItem('<=0.1.1-alpha+').match(Version('0.1.1-alpha+build1')) - False - - - .. rubric:: Attributes - - - .. attribute:: kind - - One of :data:`KIND_LT`, :data:`KIND_LTE`, :data:`KIND_EQUAL`, :data:`KIND_GTE`, - :data:`KIND_GT` and :data:`KIND_NEQ`. - - .. attribute:: spec - - :class:`Version` in the :class:`SpecItem` description. - - It is alway a :attr:`~Version.partial` :class:`Version`. - - - .. rubric:: Class methods - - - .. classmethod:: parse(cls, requirement_string) - - Retrieve a ``(kind, version)`` tuple from a string. - - :param str requirement_string: The textual description of the specification - :raises: :exc:`ValueError`: if the ``requirement_string`` is invalid. - :rtype: (``kind``, ``version``) tuple - - - .. rubric:: Methods - - - .. method:: match(self, version) - - Test whether a given :class:`Version` matches this :class:`SpecItem`:: - - >>> SpecItem('>=0.1.1').match(Version('0.1.1-alpha')) - True - >>> SpecItem('>=0.1.1-').match(Version('0.1.1-alpha')) - False - - :param version: The version to test against the spec - :type version: :class:`Version` - :rtype: ``bool`` - - - .. method:: __str__(self) - - Converting a :class:`SpecItem` to a string returns the initial description string:: - - >>> str(SpecItem('>=0.1.1')) - '>=0.1.1' - - - .. method:: __hash__(self) - - Provides a hash based solely on the current kind and the specified version. - - Allows using a :class:`SpecItem` as a dictionary key. - - - .. rubric:: Class attributes - - - .. data:: KIND_LT - - The kind of 'Less than' specifications:: - - >>> Version('1.0.0-alpha') in Spec('<1.0.0') - False - - .. data:: KIND_LTE - - The kind of 'Less or equal to' specifications:: - - >>> Version('1.0.0-alpha1+build999') in Spec('<=1.0.0-alpha1') - True - - .. data:: KIND_EQUAL - - The kind of 'equal to' specifications:: - - >>> Version('1.0.0+build3.3') in Spec('==1.0.0') - True - - .. data:: KIND_GTE - - The kind of 'Greater or equal to' specifications:: - - >>> Version('1.0.0') in Spec('>=1.0.0') - True - - .. data:: KIND_GT - - The kind of 'Greater than' specifications:: - - >>> Version('1.0.0+build667') in Spec('>1.0.1') - False - - .. data:: KIND_NEQ - - The kind of 'Not equal to' specifications:: - - >>> Version('1.0.1') in Spec('!=1.0.1') - False - - The kind of 'Almost equal to' specifications - - - -.. _SemVer: http://semver.org/ diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..57270ef --- /dev/null +++ b/docs/Makefile @@ -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 ' 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 " 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-semanticversion.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-semanticversion.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-semanticversion" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-semanticversion" + @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." diff --git a/docs/_static/.keep_dir b/docs/_static/.keep_dir new file mode 100644 index 0000000..e69de29 diff --git a/docs/changelog.rst b/docs/changelog.rst new file mode 120000 index 0000000..22ec9b8 --- /dev/null +++ b/docs/changelog.rst @@ -0,0 +1 @@ +../ChangeLog \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..b8272ea --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,251 @@ +# -*- coding: utf-8 -*- +# +# python-semanticversion documentation build configuration file, created by +# sphinx-quickstart on Wed May 16 10:41:34 2012. +# +# 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('.')) + +# -- 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 = [] + +# 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-semanticversion' +copyright = u'2012-2013, Raphaël Barrois' + +# 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. +# +root_dir = os.path.abspath(os.path.dirname(__file__)) +def get_version(): + import re + version_re = re.compile(r"^__version__ = '([\w_.-]+)'$") + with open(os.path.join(root_dir, os.pardir, 'semantic_version', '__init__.py')) as f: + for line in f: + match = version_re.match(line[:-1]) + if match: + return match.groups()[0] + return '0.0.0' + +release = get_version() +version = '.'.join(release.split('.')[:2]) + +# 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' + +# 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 = 'python-semanticversiondoc' + + +# -- 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-semanticversion.tex', u'python-semanticversion Documentation', + u'Raphaël Barrois', '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-semanticversion', u'python-semanticversion Documentation', + [u'Raphaël Barrois'], 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-semanticversion', u'python-semanticversion Documentation', + u'Raphaël Barrois', 'python-semanticversion', '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' diff --git a/docs/django.rst b/docs/django.rst new file mode 100644 index 0000000..a43c3ed --- /dev/null +++ b/docs/django.rst @@ -0,0 +1,31 @@ +Interaction with Django +======================= + +.. module:: semantic_version.django_fields + +The ``python-semanticversion`` package provides two custom fields for Django: + +- :class:`VersionField`: stores a :class:`semantic_version.Version` object +- :class:`SpecField`: stores a :class:`semantic_version.Spec` object + +Those fields are :class:`django.db.models.CharField` subclasses, +with their :attr:`~django.db.models.CharField.max_length` defaulting to 200. + + +.. class:: VersionField + + Stores a :class:`semantic_version.Version` as its string representation. + + .. attribute:: partial + + Boolean; whether :attr:`~semantic_version.Version.partial` versions are allowed. + + .. attribute:: coerce + + Boolean; whether passed in values should be coerced into a semver string + before storing. + + +.. class:: SpecField + + Stores a :class:`semantic_version.Spec` as its comma-separated string representation. diff --git a/docs/index.rst b/docs/index.rst new file mode 120000 index 0000000..89a0106 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1 @@ +../README.rst \ No newline at end of file diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..5327a34 --- /dev/null +++ b/docs/make.bat @@ -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 ^` 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. 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-semanticversion.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\python-semanticversion.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 diff --git a/docs/reference.rst b/docs/reference.rst new file mode 100644 index 0000000..5b8ca3e --- /dev/null +++ b/docs/reference.rst @@ -0,0 +1,546 @@ +Reference +========= + + +.. module:: semantic_version + + +Module-level functions +---------------------- + +.. function:: compare(v1, v2) + + Compare two version strings, and return a result similar to that of :func:`cmp`:: + + >>> compare('0.1.1', '0.1.2') + -1 + >>> compare('0.1.1', '0.1.1') + 0 + >>> compare('0.1.1', '0.1.1-alpha') + 1 + + :param str v1: The first version to compare + :param str v2: The second version to compare + :raises: :exc:`ValueError`, if any version string is invalid + :rtype: ``int``, -1 / 0 / 1 as for a :func:`cmp` comparison + + + +.. function:: match(spec, version) + + Check whether a version string matches a specification string:: + + >>> match('>=0.1.1', '0.1.2') + True + >>> match('>=0.1.1', '0.1.1-alpha') + False + >>> match('~0.1.1', '0.1.1-alpha') + True + + :param str spec: The specification to use, as a string + :param str version: The version string to test against the spec + :raises: :exc:`ValueError`, if the ``spec`` or the ``version`` is invalid + :rtype: ``bool`` + + +.. function:: validate(version) + + Check whether a version string complies with the `SemVer`_ rules. + + .. code-block:: pycon + + >>> semantic_version.validate('1.1.1') + True + >>> semantic_version.validate('1.2.3a4') + False + + :param str version: The version string to validate + :rtype: ``bool`` + + +Representing a version (the Version class) +------------------------------------------ + +.. class:: Version(version_string[, partial=False]) + + Object representation of a `SemVer`_-compliant version. + + Constructed from a textual version string:: + + >>> Version('1.1.1') + Version('1.1.1') + >>> str(Version('1.1.1')) + '1.1.1' + + + .. rubric:: Attributes + + + .. attribute:: partial + + ``bool``, whether this is a 'partial' or a complete version number. + Partial version number may lack :attr:`minor` or :attr:`patch` version numbers. + + .. attribute:: major + + ``int``, the major version number + + .. attribute:: minor + + ``int``, the minor version number. + + May be ``None`` for a :attr:`partial` version number in a ```` format. + + .. attribute:: patch + + ``int``, the patch version number. + + May be ``None`` for a :attr:`partial` version number in a ```` or ``.`` format. + + .. attribute:: prerelease + + ``tuple`` of ``strings``, the prerelease component. + + It contains the various dot-separated identifiers in the prerelease component. + + May be ``None`` for a :attr:`partial` version number in a ````, ``.`` or ``..`` format. + + .. attribute:: build + + ``tuple`` of ``strings``, the build component. + + It contains the various dot-separated identifiers in the build component. + + May be ``None`` for a :attr:`partial` version number in a ````, ``.``, + ``..`` or ``..-`` format. + + + .. rubric:: Methods + + + .. method:: __iter__(self) + + Iterates over the version components (:attr:`major`, :attr:`minor`, + :attr:`patch`, :attr:`prerelease`, :attr:`build`):: + + >>> list(Version('0.1.1')) + [0, 1, 1, [], []] + + .. note:: This may pose some subtle bugs when iterating over a single version + while expecting an iterable of versions -- similar to:: + + >>> list('abc') + ['a', 'b', 'c'] + >>> list(('abc',)) + ['abc'] + + + .. method:: __cmp__(self, other) + + Provides comparison methods with other :class:`Version` objects. + + The rules are: + + - For non-:attr:`partial` versions, compare using the `SemVer`_ scheme + - If any compared object is :attr:`partial`: + - Begin comparison using the `SemVer`_ scheme + - If a component (:attr:`minor`, :attr:`patch`, :attr:`prerelease` or :attr:`build`) + was absent from the :attr:`partial` :class:`Version` -- represented with :obj:`None` + --, consider both versions equal. + + For instance, ``Version('1.0', partial=True)`` means "any version beginning in ``1.0``". + + ``Version('1.0.1-alpha', partial=True)`` means "The ``1.0.1-alpha`` version or any + ulterior build of that same version": ``1.0.1-alpha+build3`` matches, ``1.0.1-alpha.2`` doesn't. + + Examples:: + + >>> Version('1.0', partial=True) == Version('1.0.1') + True + >>> Version('1.0.1-rc1.1') == Version('1.0.1-rc1', partial=True) + False + >>> Version('1.0.1-rc1+build345') == Version('1.0.1-rc1') + False + >>> Version('1.0.1-rc1+build345') == Version('1.0.1-rc1', partial=True) + True + + + .. method:: __str__(self) + + Returns the standard text representation of the version:: + + >>> v = Version('0.1.1-rc2+build4.4') + >>> v + Version('0.1.1-rc2+build4.4') + >>> str(v) + '0.1.1-rc2+build4.4' + + + .. method:: __hash__(self) + + Provides a hash based solely on the components. + + Allows using a :class:`Version` as a dictionary key. + + .. note:: A fully qualified :attr:`partial` :class:`Version` + + (up to the :attr:`build` component) will hash the same as the + equally qualified, non-:attr:`partial` :class:`Version`:: + + >>> hash(Version('1.0.1+build4')) == hash(Version('1.0.1+build4', partial=True)) + True + + + .. rubric:: Class methods + + + .. classmethod:: parse(cls, version_string[, partial=False]) + + Parse a version string into a ``(major, minor, patch, prerelease, build)`` tuple. + + :param str version_string: The version string to parse + :param bool partial: Whether this should be considered a :attr:`partial` version + :raises: :exc:`ValueError`, if the :attr:`version_string` is invalid. + :rtype: (major, minor, patch, prerelease, build) + + .. classmethod:: coerce(cls, version_string[, partial=False]) + + Try to convert an arbitrary version string into a :class:`Version` instance. + + Rules are: + + - If no minor or patch component, and :attr:`partial` is :obj:`False`, + replace them with zeroes + - Any character outside of ``a-zA-Z0-9.+-`` is replaced with a ``-`` + - If more than 3 dot-separated numerical components, everything from the + fourth component belongs to the :attr:`build` part + - Any extra ``+`` in the :attr:`build` part will be replaced with dots + + Examples: + + .. code-block:: pycon + + >>> Version.coerce('02') + Version('2.0.0') + >>> Version.coerce('1.2.3.4') + Version('1.2.3+4') + >>> Version.coerce('1.2.3.4beta2') + Version('1.2.3+4beta2') + >>> Version.coerce('1.2.3.4.5_6/7+8+9+10') + Version('1.2.3+4.5-6-7.8.9.10') + + :param str version_string: The version string to coerce + :param bool partial: Whether to allow generating a :attr:`partial` version + :raises: :exc:`ValueError`, if the :attr:`version_string` is invalid. + :rtype: :class:`Version` + + +Version specifications (the Spec class) +--------------------------------------- + + +Version specifications describe a 'range' of accepted versions: +older than, equal, similar to, … + +The main issue with representing version specifications is that the usual syntax +does not map well onto `SemVer`_ precedence rules: + +* A specification of ``<1.3.4`` is not expected to allow ``1.3.4-rc2``, but strict `SemVer`_ comparisons allow it ; +* Converting the previous specification to ``<=1.3.3`` in order to avoid ``1.3.4`` + prereleases has the issue of excluding ``1.3.3+build3`` ; +* It may be necessary to exclude either all variations on a patch-level release + (``!=1.3.3``) or specifically one build-level release (``1.3.3-build.434``). + + +In order to have version specification behave naturally, the rules are the following: + +* If no pre-release number was included in the specification, pre-release numbers + are ignored when deciding whether a version satisfies a specification. +* If no build number was included in the specification, build numbers are ignored + when deciding whether a version satisfies a specification. + +This means that:: + + >>> Version('1.1.1-rc1') in Spec('<1.1.1') + False + >>> Version('1.1.1-rc1') in Spec('<1.1.1-rc4') + True + >>> Version('1.1.1-rc1+build4') in Spec('<=1.1.1-rc1') + True + >>> Version('1.1.1-rc1+build4') in Spec('<=1.1.1-rc1+build2') + False + +In order to force matches to *strictly* compare version numbers, these additional +rules apply: + +* Setting a pre-release separator without a pre-release identifier (``<=1.1.1-``) + forces match to take into account pre-release version:: + + >>> Version('1.1.1-rc1') in Spec('<1.1.1') + False + >>> Version('1.1.1-rc1') in Spec('<1.1.1-') + True + +* Setting a build separator without a build identifier (``>1.1.1+``) forces + satisfaction tests to include both prerelease and build identifiers:: + + >>> Version('1.1.1+build2') in Spec('>1.1.1') + False + >>> Version('1.1.1+build2') in Spec('>1.1.1+') + True + +.. class:: Spec(spec_string[, spec_string[, ...]]) + + Stores a list of :class:`SpecItem` and matches any :class:`Version` against all + contained :class:`specs `. + + It is build from a comma-separated list of version specifications:: + + >>> Spec('>=1.0.0,<1.2.0,!=1.1.4') + = Version('1.0.0', partial=True)>, + , + + )> + + Version specifications may also be passed in separated arguments:: + + >>> Spec('>=1.0.0', '<1.2.0', '!=1.1.4,!=1.1.13') + = Version('1.0.0', partial=True)>, + , + , + , + )> + + + .. rubric:: Attributes + + + .. attribute:: specs + + Tuple of :class:`SpecItem`, the included specifications. + + + .. rubric:: Methods + + + .. method:: match(self, version) + + Test whether a given :class:`Version` matches all included :class:`SpecItem`:: + + >>> Spec('>=1.1.0,<1.1.2').match(Version('1.1.1')) + True + + :param version: The version to test against the specs + :type version: :class:`Version` + :rtype: ``bool`` + + + .. method:: filter(self, versions) + + Extract all compatible :class:`versions ` from an iterable of + :class:`Version` objects. + + :param versions: The versions to filter + :type versions: iterable of :class:`Version` + :yield: :class:`Version` + + + .. method:: select(self, versions) + + Select the highest compatible version from an iterable of :class:`Version` + objects. + + .. sourcecode:: pycon + + >>> s = Spec('>=0.1.0') + >>> s.select([]) + None + >>> s.select([Version('0.1.0'), Version('0.1.3'), Version('0.1.1')]) + Version('0.1.3') + + :param versions: The versions to filter + :type versions: iterable of :class:`Version` + :rtype: The highest compatible :class:`Version` if at least one of the + given versions is compatible; :class:`None` otherwise. + + + .. method:: __contains__(self, version) + + Alias of the :func:`match` method; + allows the use of the ``version in speclist`` syntax:: + + >>> Version('1.1.1-alpha') in Spec('>=1.1.0,<1.1.1') + True + + + .. method:: __str__(self) + + Converting a :class:`Spec` returns the initial description string:: + + >>> str(Spec('>=0.1.1,!=0.1.2')) + '>=0.1.1,!=0.1.2' + + .. method:: __iter__(self) + + Returns an iterator over the contained specs:: + + >>> for spec in Spec('>=0.1.1,!=0.1.2'): + ... print spec + >=0.1.1 + !=0.1.2 + + .. method:: __hash__(self) + + Provides a hash based solely on the hash of contained specs. + + Allows using a :class:`Spec` as a dictionary key. + + + .. rubric:: Class methods + + + .. classmethod:: parse(self, specs_string) + + Retrieve a ``(*specs)`` tuple from a string. + + :param str requirement_string: The textual description of the specifications + :raises: :exc:`ValueError`: if the ``requirement_string`` is invalid. + :rtype: ``(*spec)`` tuple + + +.. class:: SpecItem(spec_string) + + .. note:: This class belong to the private python-semanticversion API. + + Stores a version specification, defined from a string:: + + >>> SpecItem('>=0.1.1') + = Version('0.1.1', partial=True)> + + This allows to test :class:`Version` objects against the :class:`SpecItem`:: + + >>> SpecItem('>=0.1.1').match(Version('0.1.1-rc1')) # pre-release satisfy conditions + True + >>> Version('0.1.1+build2') in SpecItem('>=0.1.1') # build version satisfy specifications + True + >>> + >>> # Use the '-' marker to include the pre-release component in checks + >>> SpecItem('>=0.1.1-').match(Version('0.1.1-rc1') + False + >>> + >>> # Use the '+' marker to include the build identifier in checks + >>> SpecItem('<=0.1.1-alpha+').match(Version('0.1.1-alpha+build1')) + False + + + .. rubric:: Attributes + + + .. attribute:: kind + + One of :data:`KIND_LT`, :data:`KIND_LTE`, :data:`KIND_EQUAL`, :data:`KIND_GTE`, + :data:`KIND_GT` and :data:`KIND_NEQ`. + + .. attribute:: spec + + :class:`Version` in the :class:`SpecItem` description. + + It is alway a :attr:`~Version.partial` :class:`Version`. + + + .. rubric:: Class methods + + + .. classmethod:: parse(cls, requirement_string) + + Retrieve a ``(kind, version)`` tuple from a string. + + :param str requirement_string: The textual description of the specification + :raises: :exc:`ValueError`: if the ``requirement_string`` is invalid. + :rtype: (``kind``, ``version``) tuple + + + .. rubric:: Methods + + + .. method:: match(self, version) + + Test whether a given :class:`Version` matches this :class:`SpecItem`:: + + >>> SpecItem('>=0.1.1').match(Version('0.1.1-alpha')) + True + >>> SpecItem('>=0.1.1-').match(Version('0.1.1-alpha')) + False + + :param version: The version to test against the spec + :type version: :class:`Version` + :rtype: ``bool`` + + + .. method:: __str__(self) + + Converting a :class:`SpecItem` to a string returns the initial description string:: + + >>> str(SpecItem('>=0.1.1')) + '>=0.1.1' + + + .. method:: __hash__(self) + + Provides a hash based solely on the current kind and the specified version. + + Allows using a :class:`SpecItem` as a dictionary key. + + + .. rubric:: Class attributes + + + .. data:: KIND_LT + + The kind of 'Less than' specifications:: + + >>> Version('1.0.0-alpha') in Spec('<1.0.0') + False + + .. data:: KIND_LTE + + The kind of 'Less or equal to' specifications:: + + >>> Version('1.0.0-alpha1+build999') in Spec('<=1.0.0-alpha1') + True + + .. data:: KIND_EQUAL + + The kind of 'equal to' specifications:: + + >>> Version('1.0.0+build3.3') in Spec('==1.0.0') + True + + .. data:: KIND_GTE + + The kind of 'Greater or equal to' specifications:: + + >>> Version('1.0.0') in Spec('>=1.0.0') + True + + .. data:: KIND_GT + + The kind of 'Greater than' specifications:: + + >>> Version('1.0.0+build667') in Spec('>1.0.1') + False + + .. data:: KIND_NEQ + + The kind of 'Not equal to' specifications:: + + >>> Version('1.0.1') in Spec('!=1.0.1') + False + + The kind of 'Almost equal to' specifications + + + +.. _SemVer: http://semver.org/ diff --git a/semantic_version/__init__.py b/semantic_version/__init__.py new file mode 100644 index 0000000..6ced92a --- /dev/null +++ b/semantic_version/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2012-2013 Raphaël Barrois +# This code is distributed under the two-clause BSD License. + + +__author__ = "Raphaël Barrois " +__version__ = '2.2.1' + + +from .base import compare, match, validate, Spec, SpecItem, Version diff --git a/semantic_version/base.py b/semantic_version/base.py new file mode 100644 index 0000000..f5153b2 --- /dev/null +++ b/semantic_version/base.py @@ -0,0 +1,490 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2012-2013 Raphaël Barrois +# This code is distributed under the two-clause BSD License. + +from __future__ import unicode_literals + +import functools +import re + + +from .compat import base_cmp + +def _to_int(value): + try: + return int(value), True + except ValueError: + return value, False + + +def identifier_cmp(a, b): + """Compare two identifier (for pre-release/build components).""" + + a_cmp, a_is_int = _to_int(a) + b_cmp, b_is_int = _to_int(b) + + if a_is_int and b_is_int: + # Numeric identifiers are compared as integers + return base_cmp(a_cmp, b_cmp) + elif a_is_int: + # Numeric identifiers have lower precedence + return -1 + elif b_is_int: + return 1 + else: + # Non-numeric identifers are compared lexicographically + return base_cmp(a_cmp, b_cmp) + + +def identifier_list_cmp(a, b): + """Compare two identifier list (pre-release/build components). + + The rule is: + - Identifiers are paired between lists + - They are compared from left to right + - If all first identifiers match, the longest list is greater. + + >>> identifier_list_cmp(['1', '2'], ['1', '2']) + 0 + >>> identifier_list_cmp(['1', '2a'], ['1', '2b']) + -1 + >>> identifier_list_cmp(['1'], ['1', '2']) + -1 + """ + identifier_pairs = zip(a, b) + for id_a, id_b in identifier_pairs: + cmp_res = identifier_cmp(id_a, id_b) + if cmp_res != 0: + return cmp_res + # alpha1.3 < alpha1.3.1 + return base_cmp(len(a), len(b)) + + +class Version(object): + + version_re = re.compile('^(\d+)\.(\d+)\.(\d+)(?:-([0-9a-zA-Z.-]+))?(?:\+([0-9a-zA-Z.-]+))?$') + partial_version_re = re.compile('^(\d+)(?:\.(\d+)(?:\.(\d+))?)?(?:-([0-9a-zA-Z.-]*))?(?:\+([0-9a-zA-Z.-]*))?$') + + def __init__(self, version_string, partial=False): + major, minor, patch, prerelease, build = self.parse(version_string, partial) + + self.major = major + self.minor = minor + self.patch = patch + self.prerelease = prerelease + self.build = build + + self.partial = partial + + @classmethod + def _coerce(cls, value, allow_none=False): + if value is None and allow_none: + return value + return int(value) + + @classmethod + def coerce(cls, version_string, partial=False): + """Coerce an arbitrary version string into a semver-compatible one. + + The rule is: + - If not enough components, fill minor/patch with zeroes; unless + partial=True + - If more than 3 dot-separated components, extra components are "build" + data. If some "build" data already appeared, append it to the + extra components + + Examples: + >>> Version.coerce('0.1') + Version(0, 1, 0) + >>> Version.coerce('0.1.2.3') + Version(0, 1, 2, (), ('3',)) + >>> Version.coerce('0.1.2.3+4') + Version(0, 1, 2, (), ('3', '4')) + >>> Version.coerce('0.1+2-3+4_5') + Version(0, 1, 0, (), ('2-3', '4-5')) + """ + base_re = re.compile(r'^\d+(?:\.\d+(?:\.\d+)?)?') + + match = base_re.match(version_string) + if not match: + raise ValueError("Version string lacks a numerical component: %r" + % version_string) + + version = version_string[:match.end()] + if not partial: + # We need a not-partial version. + while version.count('.') < 2: + version += '.0' + + if match.end() == len(version_string): + return Version(version, partial=partial) + + rest = version_string[match.end():] + + # Cleanup the 'rest' + rest = re.sub(r'[^a-zA-Z0-9+.-]', '-', rest) + + if rest[0] == '+': + # A 'build' component + prerelease = '' + build = rest[1:] + elif rest[0] == '.': + # An extra version component, probably 'build' + prerelease = '' + build = rest[1:] + elif rest[0] == '-': + rest = rest[1:] + if '+' in rest: + prerelease, build = rest.split('+', 1) + else: + prerelease, build = rest, '' + elif '+' in rest: + prerelease, build = rest.split('+', 1) + else: + prerelease, build = rest, '' + + build = build.replace('+', '.') + + if prerelease: + version = '%s-%s' % (version, prerelease) + if build: + version = '%s+%s' % (version, build) + + return cls(version, partial=partial) + + @classmethod + def parse(cls, version_string, partial=False, coerce=False): + """Parse a version string into a Version() object. + + Args: + version_string (str), the version string to parse + partial (bool), whether to accept incomplete input + coerce (bool), whether to try to map the passed in string into a + valid Version. + """ + if not version_string: + raise ValueError('Invalid empty version string: %r' % version_string) + + if partial: + version_re = cls.partial_version_re + else: + version_re = cls.version_re + + match = version_re.match(version_string) + if not match: + raise ValueError('Invalid version string: %r' % version_string) + + major, minor, patch, prerelease, build = match.groups() + + major = int(major) + minor = cls._coerce(minor, partial) + patch = cls._coerce(patch, partial) + + if prerelease is None: + if partial and (build is None): + # No build info, strip here + return (major, minor, patch, None, None) + else: + prerelease = () + elif prerelease == '': + prerelease = () + else: + prerelease = tuple(prerelease.split('.')) + + if build is None: + if partial: + build = None + else: + build = () + elif build == '': + build = () + else: + build = tuple(build.split('.')) + + return (major, minor, patch, prerelease, build) + + def __iter__(self): + return iter((self.major, self.minor, self.patch, self.prerelease, self.build)) + + def __str__(self): + version = '%d' % self.major + if self.minor is not None: + version = '%s.%d' % (version, self.minor) + if self.patch is not None: + version = '%s.%d' % (version, self.patch) + + if self.prerelease or (self.partial and self.prerelease == () and self.build is None): + version = '%s-%s' % (version, '.'.join(self.prerelease)) + if self.build or (self.partial and self.build == ()): + version = '%s+%s' % (version, '.'.join(self.build)) + return version + + def __repr__(self): + return 'Version(%r%s)' % ( + str(self), + ', partial=True' if self.partial else '', + ) + + @classmethod + def _comparison_functions(cls, partial=False): + """Retrieve comparison methods to apply on version components. + + This is a private API. + + Args: + partial (bool): whether to provide 'partial' or 'strict' matching. + + Returns: + 5-tuple of cmp-like functions. + """ + + def prerelease_cmp(a, b): + """Compare prerelease components. + + Special rule: a version without prerelease component has higher + precedence than one with a prerelease component. + """ + if a and b: + return identifier_list_cmp(a, b) + elif a: + # Versions with prerelease field have lower precedence + return -1 + elif b: + return 1 + else: + return 0 + + def build_cmp(a, b): + """Compare build components. + + Special rule: a version without build component has lower + precedence than one with a build component. + """ + if a and b: + return identifier_list_cmp(a, b) + elif a: + # Versions with build field have higher precedence + return 1 + elif b: + return -1 + else: + return 0 + + def make_optional(orig_cmp_fun): + """Convert a cmp-like function to consider 'None == *'.""" + @functools.wraps(orig_cmp_fun) + def alt_cmp_fun(a, b): + if a is None or b is None: + return 0 + return orig_cmp_fun(a, b) + + return alt_cmp_fun + + if partial: + return [ + base_cmp, # Major is still mandatory + make_optional(base_cmp), + make_optional(base_cmp), + make_optional(prerelease_cmp), + make_optional(build_cmp), + ] + else: + return [ + base_cmp, + base_cmp, + base_cmp, + prerelease_cmp, + build_cmp, + ] + + def __cmp__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + + field_pairs = zip(self, other) + comparison_functions = self._comparison_functions(partial=self.partial or other.partial) + comparisons = zip(comparison_functions, self, other) + + for cmp_fun, self_field, other_field in comparisons: + cmp_res = cmp_fun(self_field, other_field) + if cmp_res != 0: + return cmp_res + + return 0 + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + + return self.__cmp__(other) == 0 + + def __hash__(self): + return hash((self.major, self.minor, self.patch, self.prerelease, self.build)) + + def __ne__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + + return self.__cmp__(other) != 0 + + def __lt__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + + return self.__cmp__(other) < 0 + + def __le__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + + return self.__cmp__(other) <= 0 + + def __gt__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + + return self.__cmp__(other) > 0 + + def __ge__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + + return self.__cmp__(other) >= 0 + + +class SpecItem(object): + """A requirement specification.""" + + KIND_LT = '<' + KIND_LTE = '<=' + KIND_EQUAL = '==' + KIND_GTE = '>=' + KIND_GT = '>' + KIND_NEQ = '!=' + + STRICT_KINDS = ( + KIND_LT, + KIND_LTE, + KIND_EQUAL, + KIND_GTE, + KIND_GT, + KIND_NEQ, + ) + + re_spec = re.compile(r'^(<|<=|==|>=|>|!=)(\d.*)$') + + def __init__(self, requirement_string): + kind, spec = self.parse(requirement_string) + self.kind = kind + self.spec = spec + + @classmethod + def parse(cls, requirement_string): + if not requirement_string: + raise ValueError("Invalid empty requirement specification: %r" % requirement_string) + + match = cls.re_spec.match(requirement_string) + if not match: + raise ValueError("Invalid requirement specification: %r" % requirement_string) + + kind, version = match.groups() + spec = Version(version, partial=True) + return (kind, spec) + + def match(self, version): + if self.kind == self.KIND_LT: + return version < self.spec + elif self.kind == self.KIND_LTE: + return version <= self.spec + elif self.kind == self.KIND_EQUAL: + return version == self.spec + elif self.kind == self.KIND_GTE: + return version >= self.spec + elif self.kind == self.KIND_GT: + return version > self.spec + elif self.kind == self.KIND_NEQ: + return version != self.spec + else: # pragma: no cover + raise ValueError('Unexpected match kind: %r' % self.kind) + + def __str__(self): + return '%s%s' % (self.kind, self.spec) + + def __repr__(self): + return '' % (self.kind, self.spec) + + def __eq__(self, other): + if not isinstance(other, SpecItem): + return NotImplemented + return self.kind == other.kind and self.spec == other.spec + + def __hash__(self): + return hash((self.kind, self.spec)) + + +class Spec(object): + def __init__(self, *specs_strings): + subspecs = [self.parse(spec) for spec in specs_strings] + self.specs = sum(subspecs, ()) + + @classmethod + def parse(self, specs_string): + spec_texts = specs_string.split(',') + return tuple(SpecItem(spec_text) for spec_text in spec_texts) + + def match(self, version): + """Check whether a Version satisfies the Spec.""" + return all(spec.match(version) for spec in self.specs) + + def filter(self, versions): + """Filter an iterable of versions satisfying the Spec.""" + for version in versions: + if self.match(version): + yield version + + def select(self, versions): + """Select the best compatible version among an iterable of options.""" + options = list(self.filter(versions)) + if options: + return max(options) + return None + + def __contains__(self, version): + if isinstance(version, Version): + return self.match(version) + return False + + def __iter__(self): + return iter(self.specs) + + def __str__(self): + return ','.join(str(spec) for spec in self.specs) + + def __repr__(self): + return '' % (self.specs,) + + def __eq__(self, other): + if not isinstance(other, Spec): + return NotImplemented + + return set(self.specs) == set(other.specs) + + def __hash__(self): + return hash(self.specs) + + +def compare(v1, v2): + return base_cmp(Version(v1), Version(v2)) + + +def match(spec, version): + return Spec(spec).match(Version(version)) + + +def validate(version_string): + """Validates a version string againt the SemVer specification.""" + try: + Version.parse(version_string) + return True + except ValueError: + return False diff --git a/semantic_version/compat.py b/semantic_version/compat.py new file mode 100644 index 0000000..51102fc --- /dev/null +++ b/semantic_version/compat.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2012-2013 Raphaël Barrois +# This code is distributed under the two-clause BSD License. + +import sys + +is_python2 = (sys.version_info[0] == 2) + +if is_python2: # pragma: no cover + base_cmp = cmp +else: # pragma: no cover + def base_cmp(x, y): + if x < y: + return -1 + elif x > y: + return 1 + else: + return 0 diff --git a/semantic_version/django_fields.py b/semantic_version/django_fields.py new file mode 100644 index 0000000..6a70129 --- /dev/null +++ b/semantic_version/django_fields.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2012-2013 Raphaël Barrois +# This code is distributed under the two-clause BSD License. + +from __future__ import unicode_literals + +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from . import base + + +class BaseSemVerField(models.CharField): + __metaclass__ = models.SubfieldBase + + def __init__(self, *args, **kwargs): + kwargs.setdefault('max_length', 200) + super(BaseSemVerField, self).__init__(*args, **kwargs) + + def get_prep_value(self, obj): + return str(obj) + + def get_db_prep_value(self, value, connection, prepared=False): + if not prepared: + value = self.get_prep_value(value) + return value + + def value_to_string(self, obj): + value = self.to_python(self._get_val_from_obj(obj)) + return str(value) + + def run_validators(self, value): + return super(BaseSemVerField, self).run_validators(str(value)) + + +# Py2 and Py3-compatible metaclass +SemVerField = models.SubfieldBase( + str('SemVerField'), (BaseSemVerField, models.CharField), {}) + + +class VersionField(SemVerField): + default_error_messages = { + 'invalid': _("Enter a valid version number in X.Y.Z format."), + } + description = _("Version") + + def __init__(self, *args, **kwargs): + self.partial = kwargs.pop('partial', False) + self.coerce = kwargs.pop('coerce', False) + super(VersionField, self).__init__(*args, **kwargs) + + def to_python(self, value): + """Converts any value to a base.Version field.""" + if value is None or value == '': + return value + if isinstance(value, base.Version): + return value + if self.coerce: + return base.Version.coerce(value, partial=self.partial) + else: + return base.Version(value, partial=self.partial) + + +class SpecField(SemVerField): + default_error_messages = { + 'invalid': _("Enter a valid version number spec list in ==X.Y.Z,>=A.B.C format."), + } + description = _("Version specification list") + + def to_python(self, value): + """Converts any value to a base.Spec field.""" + if value is None or value == '': + return value + if isinstance(value, base.Spec): + return value + return base.Spec(value) + + +def add_south_rules(): + from south.modelsinspector import add_introspection_rules + + add_introspection_rules([ + ( + (VersionField,), + [], + { + 'partial': ('partial', {'default': False}), + 'coerce': ('coerce', {'default': False}), + }, + ), + ], ["semantic_version\.django_fields"]) + + +try: # pragma: no cover + import south +except ImportError: # pragma: no cover + south = None + +if south: # pragma: no cover + add_south_rules() diff --git a/setup.py b/setup.py index d24cdd0..8d2f9e6 100755 --- a/setup.py +++ b/setup.py @@ -2,90 +2,49 @@ # -*- coding: utf-8 -*- # Copyright (c) 2012-2013 Raphaël Barrois -from distutils.core import setup -from distutils import cmd import os import re import sys +from setuptools import setup + root_dir = os.path.abspath(os.path.dirname(__file__)) -def get_version(): - version_re = re.compile(r"^__version__ = '([\w_.-]+)'$") - with open(os.path.join(root_dir, 'src', 'semantic_version', '__init__.py')) as f: +def get_version(package_name): + version_re = re.compile(r"^__version__ = [\"']([\w_.-]+)[\"']$") + package_components = package_name.split('.') + path_components = package_components + ['__init__.py'] + with open(os.path.join(root_dir, *path_components)) as f: for line in f: match = version_re.match(line[:-1]) if match: return match.groups()[0] - return '0.0.0' - - -class test(cmd.Command): - """Run the tests for this package.""" - command_name = 'test' - description = 'run the tests associated with the package' - - user_options = [ - ('test-suite=', None, "A test suite to run (defaults to 'tests')"), - ] - - DEFAULT_TEST_SUITE = 'tests' - - def initialize_options(self): - self.test_runner = None - self.test_suite = None - - def finalize_options(self): - self.ensure_string('test_suite', self.DEFAULT_TEST_SUITE) + return '0.1.0' - def run(self): - """Run the test suite.""" - try: - import unittest2 as unittest - except ImportError: - import unittest - if self.verbose: - verbosity=1 - else: - verbosity=0 - - ex_path = sys.path - sys.path.insert(0, os.path.join(root_dir, 'src')) - loader = unittest.defaultTestLoader - suite = unittest.TestSuite() - - if self.test_suite == self.DEFAULT_TEST_SUITE: - for test_module in loader.discover('.'): - suite.addTest(test_module) - else: - suite.addTest(loader.loadTestsFromName(self.test_suite)) - - result = unittest.TextTestRunner(verbosity=verbosity).run(suite) - sys.path = ex_path - - if not result.wasSuccessful(): - sys.exit(1) +PACKAGE = 'semantic_version' setup( - name="semantic_version", - version=get_version(), + name=PACKAGE, + version=get_version(PACKAGE), author="Raphaël Barrois", author_email="raphael.barrois+semver@polytechnique.org", - description=("A library implementing the 'SemVer' scheme."), - license="BSD", + description="A library implementing the 'SemVer' scheme.", + license='BSD', keywords=['semantic version', 'versioning', 'version'], - url="http://github.com/rbarrois/python-semanticversion", - download_url="http://pypi.python.org/pypi/semantic_version/", - package_dir={'': 'src'}, + url='https://github.com/rbarrois/python-semanticversion', + download_url='http://pypi.python.org/pypi/semantic_version/', packages=['semantic_version'], + setup_requires=[ + 'setuptools>=0.8', + ], classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "License :: OSI Approved :: BSD License", - "Topic :: Software Development :: Libraries :: Python Modules", + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Topic :: Software Development :: Libraries :: Python Modules', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', @@ -96,6 +55,5 @@ setup( 'Programming Language :: Python :: 3.3', 'Topic :: Software Development :: Libraries :: Python Modules' ], - cmdclass={'test': test}, + test_suite='tests', ) - diff --git a/src/semantic_version/__init__.py b/src/semantic_version/__init__.py deleted file mode 100644 index 6ced92a..0000000 --- a/src/semantic_version/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2012-2013 Raphaël Barrois -# This code is distributed under the two-clause BSD License. - - -__author__ = "Raphaël Barrois " -__version__ = '2.2.1' - - -from .base import compare, match, validate, Spec, SpecItem, Version diff --git a/src/semantic_version/base.py b/src/semantic_version/base.py deleted file mode 100644 index f5153b2..0000000 --- a/src/semantic_version/base.py +++ /dev/null @@ -1,490 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2012-2013 Raphaël Barrois -# This code is distributed under the two-clause BSD License. - -from __future__ import unicode_literals - -import functools -import re - - -from .compat import base_cmp - -def _to_int(value): - try: - return int(value), True - except ValueError: - return value, False - - -def identifier_cmp(a, b): - """Compare two identifier (for pre-release/build components).""" - - a_cmp, a_is_int = _to_int(a) - b_cmp, b_is_int = _to_int(b) - - if a_is_int and b_is_int: - # Numeric identifiers are compared as integers - return base_cmp(a_cmp, b_cmp) - elif a_is_int: - # Numeric identifiers have lower precedence - return -1 - elif b_is_int: - return 1 - else: - # Non-numeric identifers are compared lexicographically - return base_cmp(a_cmp, b_cmp) - - -def identifier_list_cmp(a, b): - """Compare two identifier list (pre-release/build components). - - The rule is: - - Identifiers are paired between lists - - They are compared from left to right - - If all first identifiers match, the longest list is greater. - - >>> identifier_list_cmp(['1', '2'], ['1', '2']) - 0 - >>> identifier_list_cmp(['1', '2a'], ['1', '2b']) - -1 - >>> identifier_list_cmp(['1'], ['1', '2']) - -1 - """ - identifier_pairs = zip(a, b) - for id_a, id_b in identifier_pairs: - cmp_res = identifier_cmp(id_a, id_b) - if cmp_res != 0: - return cmp_res - # alpha1.3 < alpha1.3.1 - return base_cmp(len(a), len(b)) - - -class Version(object): - - version_re = re.compile('^(\d+)\.(\d+)\.(\d+)(?:-([0-9a-zA-Z.-]+))?(?:\+([0-9a-zA-Z.-]+))?$') - partial_version_re = re.compile('^(\d+)(?:\.(\d+)(?:\.(\d+))?)?(?:-([0-9a-zA-Z.-]*))?(?:\+([0-9a-zA-Z.-]*))?$') - - def __init__(self, version_string, partial=False): - major, minor, patch, prerelease, build = self.parse(version_string, partial) - - self.major = major - self.minor = minor - self.patch = patch - self.prerelease = prerelease - self.build = build - - self.partial = partial - - @classmethod - def _coerce(cls, value, allow_none=False): - if value is None and allow_none: - return value - return int(value) - - @classmethod - def coerce(cls, version_string, partial=False): - """Coerce an arbitrary version string into a semver-compatible one. - - The rule is: - - If not enough components, fill minor/patch with zeroes; unless - partial=True - - If more than 3 dot-separated components, extra components are "build" - data. If some "build" data already appeared, append it to the - extra components - - Examples: - >>> Version.coerce('0.1') - Version(0, 1, 0) - >>> Version.coerce('0.1.2.3') - Version(0, 1, 2, (), ('3',)) - >>> Version.coerce('0.1.2.3+4') - Version(0, 1, 2, (), ('3', '4')) - >>> Version.coerce('0.1+2-3+4_5') - Version(0, 1, 0, (), ('2-3', '4-5')) - """ - base_re = re.compile(r'^\d+(?:\.\d+(?:\.\d+)?)?') - - match = base_re.match(version_string) - if not match: - raise ValueError("Version string lacks a numerical component: %r" - % version_string) - - version = version_string[:match.end()] - if not partial: - # We need a not-partial version. - while version.count('.') < 2: - version += '.0' - - if match.end() == len(version_string): - return Version(version, partial=partial) - - rest = version_string[match.end():] - - # Cleanup the 'rest' - rest = re.sub(r'[^a-zA-Z0-9+.-]', '-', rest) - - if rest[0] == '+': - # A 'build' component - prerelease = '' - build = rest[1:] - elif rest[0] == '.': - # An extra version component, probably 'build' - prerelease = '' - build = rest[1:] - elif rest[0] == '-': - rest = rest[1:] - if '+' in rest: - prerelease, build = rest.split('+', 1) - else: - prerelease, build = rest, '' - elif '+' in rest: - prerelease, build = rest.split('+', 1) - else: - prerelease, build = rest, '' - - build = build.replace('+', '.') - - if prerelease: - version = '%s-%s' % (version, prerelease) - if build: - version = '%s+%s' % (version, build) - - return cls(version, partial=partial) - - @classmethod - def parse(cls, version_string, partial=False, coerce=False): - """Parse a version string into a Version() object. - - Args: - version_string (str), the version string to parse - partial (bool), whether to accept incomplete input - coerce (bool), whether to try to map the passed in string into a - valid Version. - """ - if not version_string: - raise ValueError('Invalid empty version string: %r' % version_string) - - if partial: - version_re = cls.partial_version_re - else: - version_re = cls.version_re - - match = version_re.match(version_string) - if not match: - raise ValueError('Invalid version string: %r' % version_string) - - major, minor, patch, prerelease, build = match.groups() - - major = int(major) - minor = cls._coerce(minor, partial) - patch = cls._coerce(patch, partial) - - if prerelease is None: - if partial and (build is None): - # No build info, strip here - return (major, minor, patch, None, None) - else: - prerelease = () - elif prerelease == '': - prerelease = () - else: - prerelease = tuple(prerelease.split('.')) - - if build is None: - if partial: - build = None - else: - build = () - elif build == '': - build = () - else: - build = tuple(build.split('.')) - - return (major, minor, patch, prerelease, build) - - def __iter__(self): - return iter((self.major, self.minor, self.patch, self.prerelease, self.build)) - - def __str__(self): - version = '%d' % self.major - if self.minor is not None: - version = '%s.%d' % (version, self.minor) - if self.patch is not None: - version = '%s.%d' % (version, self.patch) - - if self.prerelease or (self.partial and self.prerelease == () and self.build is None): - version = '%s-%s' % (version, '.'.join(self.prerelease)) - if self.build or (self.partial and self.build == ()): - version = '%s+%s' % (version, '.'.join(self.build)) - return version - - def __repr__(self): - return 'Version(%r%s)' % ( - str(self), - ', partial=True' if self.partial else '', - ) - - @classmethod - def _comparison_functions(cls, partial=False): - """Retrieve comparison methods to apply on version components. - - This is a private API. - - Args: - partial (bool): whether to provide 'partial' or 'strict' matching. - - Returns: - 5-tuple of cmp-like functions. - """ - - def prerelease_cmp(a, b): - """Compare prerelease components. - - Special rule: a version without prerelease component has higher - precedence than one with a prerelease component. - """ - if a and b: - return identifier_list_cmp(a, b) - elif a: - # Versions with prerelease field have lower precedence - return -1 - elif b: - return 1 - else: - return 0 - - def build_cmp(a, b): - """Compare build components. - - Special rule: a version without build component has lower - precedence than one with a build component. - """ - if a and b: - return identifier_list_cmp(a, b) - elif a: - # Versions with build field have higher precedence - return 1 - elif b: - return -1 - else: - return 0 - - def make_optional(orig_cmp_fun): - """Convert a cmp-like function to consider 'None == *'.""" - @functools.wraps(orig_cmp_fun) - def alt_cmp_fun(a, b): - if a is None or b is None: - return 0 - return orig_cmp_fun(a, b) - - return alt_cmp_fun - - if partial: - return [ - base_cmp, # Major is still mandatory - make_optional(base_cmp), - make_optional(base_cmp), - make_optional(prerelease_cmp), - make_optional(build_cmp), - ] - else: - return [ - base_cmp, - base_cmp, - base_cmp, - prerelease_cmp, - build_cmp, - ] - - def __cmp__(self, other): - if not isinstance(other, self.__class__): - return NotImplemented - - field_pairs = zip(self, other) - comparison_functions = self._comparison_functions(partial=self.partial or other.partial) - comparisons = zip(comparison_functions, self, other) - - for cmp_fun, self_field, other_field in comparisons: - cmp_res = cmp_fun(self_field, other_field) - if cmp_res != 0: - return cmp_res - - return 0 - - def __eq__(self, other): - if not isinstance(other, self.__class__): - return NotImplemented - - return self.__cmp__(other) == 0 - - def __hash__(self): - return hash((self.major, self.minor, self.patch, self.prerelease, self.build)) - - def __ne__(self, other): - if not isinstance(other, self.__class__): - return NotImplemented - - return self.__cmp__(other) != 0 - - def __lt__(self, other): - if not isinstance(other, self.__class__): - return NotImplemented - - return self.__cmp__(other) < 0 - - def __le__(self, other): - if not isinstance(other, self.__class__): - return NotImplemented - - return self.__cmp__(other) <= 0 - - def __gt__(self, other): - if not isinstance(other, self.__class__): - return NotImplemented - - return self.__cmp__(other) > 0 - - def __ge__(self, other): - if not isinstance(other, self.__class__): - return NotImplemented - - return self.__cmp__(other) >= 0 - - -class SpecItem(object): - """A requirement specification.""" - - KIND_LT = '<' - KIND_LTE = '<=' - KIND_EQUAL = '==' - KIND_GTE = '>=' - KIND_GT = '>' - KIND_NEQ = '!=' - - STRICT_KINDS = ( - KIND_LT, - KIND_LTE, - KIND_EQUAL, - KIND_GTE, - KIND_GT, - KIND_NEQ, - ) - - re_spec = re.compile(r'^(<|<=|==|>=|>|!=)(\d.*)$') - - def __init__(self, requirement_string): - kind, spec = self.parse(requirement_string) - self.kind = kind - self.spec = spec - - @classmethod - def parse(cls, requirement_string): - if not requirement_string: - raise ValueError("Invalid empty requirement specification: %r" % requirement_string) - - match = cls.re_spec.match(requirement_string) - if not match: - raise ValueError("Invalid requirement specification: %r" % requirement_string) - - kind, version = match.groups() - spec = Version(version, partial=True) - return (kind, spec) - - def match(self, version): - if self.kind == self.KIND_LT: - return version < self.spec - elif self.kind == self.KIND_LTE: - return version <= self.spec - elif self.kind == self.KIND_EQUAL: - return version == self.spec - elif self.kind == self.KIND_GTE: - return version >= self.spec - elif self.kind == self.KIND_GT: - return version > self.spec - elif self.kind == self.KIND_NEQ: - return version != self.spec - else: # pragma: no cover - raise ValueError('Unexpected match kind: %r' % self.kind) - - def __str__(self): - return '%s%s' % (self.kind, self.spec) - - def __repr__(self): - return '' % (self.kind, self.spec) - - def __eq__(self, other): - if not isinstance(other, SpecItem): - return NotImplemented - return self.kind == other.kind and self.spec == other.spec - - def __hash__(self): - return hash((self.kind, self.spec)) - - -class Spec(object): - def __init__(self, *specs_strings): - subspecs = [self.parse(spec) for spec in specs_strings] - self.specs = sum(subspecs, ()) - - @classmethod - def parse(self, specs_string): - spec_texts = specs_string.split(',') - return tuple(SpecItem(spec_text) for spec_text in spec_texts) - - def match(self, version): - """Check whether a Version satisfies the Spec.""" - return all(spec.match(version) for spec in self.specs) - - def filter(self, versions): - """Filter an iterable of versions satisfying the Spec.""" - for version in versions: - if self.match(version): - yield version - - def select(self, versions): - """Select the best compatible version among an iterable of options.""" - options = list(self.filter(versions)) - if options: - return max(options) - return None - - def __contains__(self, version): - if isinstance(version, Version): - return self.match(version) - return False - - def __iter__(self): - return iter(self.specs) - - def __str__(self): - return ','.join(str(spec) for spec in self.specs) - - def __repr__(self): - return '' % (self.specs,) - - def __eq__(self, other): - if not isinstance(other, Spec): - return NotImplemented - - return set(self.specs) == set(other.specs) - - def __hash__(self): - return hash(self.specs) - - -def compare(v1, v2): - return base_cmp(Version(v1), Version(v2)) - - -def match(spec, version): - return Spec(spec).match(Version(version)) - - -def validate(version_string): - """Validates a version string againt the SemVer specification.""" - try: - Version.parse(version_string) - return True - except ValueError: - return False diff --git a/src/semantic_version/compat.py b/src/semantic_version/compat.py deleted file mode 100644 index 51102fc..0000000 --- a/src/semantic_version/compat.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2012-2013 Raphaël Barrois -# This code is distributed under the two-clause BSD License. - -import sys - -is_python2 = (sys.version_info[0] == 2) - -if is_python2: # pragma: no cover - base_cmp = cmp -else: # pragma: no cover - def base_cmp(x, y): - if x < y: - return -1 - elif x > y: - return 1 - else: - return 0 diff --git a/src/semantic_version/django_fields.py b/src/semantic_version/django_fields.py deleted file mode 100644 index 6a70129..0000000 --- a/src/semantic_version/django_fields.py +++ /dev/null @@ -1,100 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2012-2013 Raphaël Barrois -# This code is distributed under the two-clause BSD License. - -from __future__ import unicode_literals - -from django.db import models -from django.utils.translation import ugettext_lazy as _ - -from . import base - - -class BaseSemVerField(models.CharField): - __metaclass__ = models.SubfieldBase - - def __init__(self, *args, **kwargs): - kwargs.setdefault('max_length', 200) - super(BaseSemVerField, self).__init__(*args, **kwargs) - - def get_prep_value(self, obj): - return str(obj) - - def get_db_prep_value(self, value, connection, prepared=False): - if not prepared: - value = self.get_prep_value(value) - return value - - def value_to_string(self, obj): - value = self.to_python(self._get_val_from_obj(obj)) - return str(value) - - def run_validators(self, value): - return super(BaseSemVerField, self).run_validators(str(value)) - - -# Py2 and Py3-compatible metaclass -SemVerField = models.SubfieldBase( - str('SemVerField'), (BaseSemVerField, models.CharField), {}) - - -class VersionField(SemVerField): - default_error_messages = { - 'invalid': _("Enter a valid version number in X.Y.Z format."), - } - description = _("Version") - - def __init__(self, *args, **kwargs): - self.partial = kwargs.pop('partial', False) - self.coerce = kwargs.pop('coerce', False) - super(VersionField, self).__init__(*args, **kwargs) - - def to_python(self, value): - """Converts any value to a base.Version field.""" - if value is None or value == '': - return value - if isinstance(value, base.Version): - return value - if self.coerce: - return base.Version.coerce(value, partial=self.partial) - else: - return base.Version(value, partial=self.partial) - - -class SpecField(SemVerField): - default_error_messages = { - 'invalid': _("Enter a valid version number spec list in ==X.Y.Z,>=A.B.C format."), - } - description = _("Version specification list") - - def to_python(self, value): - """Converts any value to a base.Spec field.""" - if value is None or value == '': - return value - if isinstance(value, base.Spec): - return value - return base.Spec(value) - - -def add_south_rules(): - from south.modelsinspector import add_introspection_rules - - add_introspection_rules([ - ( - (VersionField,), - [], - { - 'partial': ('partial', {'default': False}), - 'coerce': ('coerce', {'default': False}), - }, - ), - ], ["semantic_version\.django_fields"]) - - -try: # pragma: no cover - import south -except ImportError: # pragma: no cover - south = None - -if south: # pragma: no cover - add_south_rules() -- cgit v1.2.1