| #!/usr/bin/env python |
| # coding: utf-8 |
| |
| """ |
| This script supports publishing Pystache to PyPI. |
| |
| This docstring contains instructions to Pystache maintainers on how |
| to release a new version of Pystache. |
| |
| (1) Prepare the release. |
| |
| Make sure the code is finalized and merged to master. Bump the version |
| number in setup.py, update the release date in the HISTORY file, etc. |
| |
| Generate the reStructuredText long_description using-- |
| |
| $ python setup.py prep |
| |
| and be sure this new version is checked in. You must have pandoc installed |
| to do this step: |
| |
| http://johnmacfarlane.net/pandoc/ |
| |
| It helps to review this auto-generated file on GitHub prior to uploading |
| because the long description will be sent to PyPI and appear there after |
| publishing. PyPI attempts to convert this string to HTML before displaying |
| it on the PyPI project page. If PyPI finds any issues, it will render it |
| instead as plain-text, which we do not want. |
| |
| To check in advance that PyPI will accept and parse the reST file as HTML, |
| you can use the rst2html program installed by the docutils package |
| (http://docutils.sourceforge.net/). To install docutils: |
| |
| $ pip install docutils |
| |
| To check the file, run the following command and confirm that it reports |
| no warnings: |
| |
| $ python setup.py --long-description | rst2html.py -v --no-raw > out.html |
| |
| See here for more information: |
| |
| http://docs.python.org/distutils/uploading.html#pypi-package-display |
| |
| (2) Push to PyPI. To release a new version of Pystache to PyPI-- |
| |
| http://pypi.python.org/pypi/pystache |
| |
| create a PyPI user account if you do not already have one. The user account |
| will need permissions to push to PyPI. A current "Package Index Owner" of |
| Pystache can grant you those permissions. |
| |
| When you have permissions, run the following: |
| |
| python setup.py publish |
| |
| If you get an error like the following-- |
| |
| Upload failed (401): You must be identified to edit package information |
| |
| then add a file called .pyirc to your home directory with the following |
| contents: |
| |
| [server-login] |
| username: <PyPI username> |
| password: <PyPI password> |
| |
| as described here, for example: |
| |
| http://docs.python.org/release/2.5.2/dist/pypirc.html |
| |
| (3) Tag the release on GitHub. Here are some commands for tagging. |
| |
| List current tags: |
| |
| git tag -l -n3 |
| |
| Create an annotated tag: |
| |
| git tag -a -m "Version 0.5.1" "v0.5.1" |
| |
| Push a tag to GitHub: |
| |
| git push --tags defunkt v0.5.1 |
| |
| """ |
| |
| import os |
| import shutil |
| import sys |
| |
| |
| py_version = sys.version_info |
| |
| # distutils does not seem to support the following setup() arguments. |
| # It displays a UserWarning when setup() is passed those options: |
| # |
| # * entry_points |
| # * install_requires |
| # |
| # distribute works with Python 2.3.5 and above: |
| # |
| # http://packages.python.org/distribute/setuptools.html#building-and-distributing-packages-with-distribute |
| # |
| if py_version < (2, 3, 5): |
| # TODO: this might not work yet. |
| import distutils as dist |
| from distutils import core |
| setup = core.setup |
| else: |
| import setuptools as dist |
| setup = dist.setup |
| |
| |
| VERSION = '0.5.4' # Also change in pystache/__init__.py. |
| |
| FILE_ENCODING = 'utf-8' |
| |
| README_PATH = 'README.md' |
| HISTORY_PATH = 'HISTORY.md' |
| LICENSE_PATH = 'LICENSE' |
| |
| RST_DESCRIPTION_PATH = 'setup_description.rst' |
| |
| TEMP_EXTENSION = '.temp' |
| |
| PREP_COMMAND = 'prep' |
| |
| CLASSIFIERS = ( |
| 'Development Status :: 4 - Beta', |
| 'License :: OSI Approved :: MIT License', |
| 'Programming Language :: Python', |
| 'Programming Language :: Python :: 2', |
| 'Programming Language :: Python :: 2.4', |
| 'Programming Language :: Python :: 2.5', |
| 'Programming Language :: Python :: 2.6', |
| 'Programming Language :: Python :: 2.7', |
| 'Programming Language :: Python :: 3', |
| 'Programming Language :: Python :: 3.1', |
| 'Programming Language :: Python :: 3.2', |
| 'Programming Language :: Python :: 3.3', |
| 'Programming Language :: Python :: Implementation :: PyPy', |
| ) |
| |
| # Comments in reST begin with two dots. |
| RST_LONG_DESCRIPTION_INTRO = """\ |
| .. Do not edit this file. This file is auto-generated for PyPI by setup.py |
| .. using pandoc, so edits should go in the source files rather than here. |
| """ |
| |
| |
| def read(path): |
| """ |
| Read and return the contents of a text file as a unicode string. |
| |
| """ |
| # This function implementation was chosen to be compatible across Python 2/3. |
| f = open(path, 'rb') |
| # We avoid use of the with keyword for Python 2.4 support. |
| try: |
| b = f.read() |
| finally: |
| f.close() |
| |
| return b.decode(FILE_ENCODING) |
| |
| |
| def write(u, path): |
| """ |
| Write a unicode string to a file (as utf-8). |
| |
| """ |
| print("writing to: %s" % path) |
| # This function implementation was chosen to be compatible across Python 2/3. |
| f = open(path, "wb") |
| try: |
| b = u.encode(FILE_ENCODING) |
| f.write(b) |
| finally: |
| f.close() |
| |
| |
| def make_temp_path(path, new_ext=None): |
| """ |
| Arguments: |
| |
| new_ext: the new file extension, including the leading dot. |
| Defaults to preserving the existing file extension. |
| |
| """ |
| root, ext = os.path.splitext(path) |
| if new_ext is None: |
| new_ext = ext |
| temp_path = root + TEMP_EXTENSION + new_ext |
| return temp_path |
| |
| |
| def strip_html_comments(text): |
| """Strip HTML comments from a unicode string.""" |
| lines = text.splitlines(True) # preserve line endings. |
| |
| # Remove HTML comments (which we only allow to take a special form). |
| new_lines = filter(lambda line: not line.startswith("<!--"), lines) |
| |
| return "".join(new_lines) |
| |
| |
| # We write the converted file to a temp file to simplify debugging and |
| # to avoid removing a valid pre-existing file on failure. |
| def convert_md_to_rst(md_path, rst_temp_path): |
| """ |
| Convert the contents of a file from Markdown to reStructuredText. |
| |
| Returns the converted text as a Unicode string. |
| |
| Arguments: |
| |
| md_path: a path to a UTF-8 encoded Markdown file to convert. |
| |
| rst_temp_path: a temporary path to which to write the converted contents. |
| |
| """ |
| # Pandoc uses the UTF-8 character encoding for both input and output. |
| command = "pandoc --write=rst --output=%s %s" % (rst_temp_path, md_path) |
| print("converting with pandoc: %s to %s\n-->%s" % (md_path, rst_temp_path, |
| command)) |
| |
| if os.path.exists(rst_temp_path): |
| os.remove(rst_temp_path) |
| |
| os.system(command) |
| |
| if not os.path.exists(rst_temp_path): |
| s = ("Error running: %s\n" |
| " Did you install pandoc per the %s docstring?" % (command, |
| __file__)) |
| sys.exit(s) |
| |
| return read(rst_temp_path) |
| |
| |
| # The long_description needs to be formatted as reStructuredText. |
| # See the following for more information: |
| # |
| # http://docs.python.org/distutils/setupscript.html#additional-meta-data |
| # http://docs.python.org/distutils/uploading.html#pypi-package-display |
| # |
| def make_long_description(): |
| """ |
| Generate the reST long_description for setup() from source files. |
| |
| Returns the generated long_description as a unicode string. |
| |
| """ |
| readme_path = README_PATH |
| |
| # Remove our HTML comments because PyPI does not allow it. |
| # See the setup.py docstring for more info on this. |
| readme_md = strip_html_comments(read(readme_path)) |
| history_md = strip_html_comments(read(HISTORY_PATH)) |
| license_md = """\ |
| License |
| ======= |
| |
| """ + read(LICENSE_PATH) |
| |
| sections = [readme_md, history_md, license_md] |
| md_description = '\n\n'.join(sections) |
| |
| # Write the combined Markdown file to a temp path. |
| md_ext = os.path.splitext(readme_path)[1] |
| md_description_path = make_temp_path(RST_DESCRIPTION_PATH, new_ext=md_ext) |
| write(md_description, md_description_path) |
| |
| rst_temp_path = make_temp_path(RST_DESCRIPTION_PATH) |
| long_description = convert_md_to_rst(md_path=md_description_path, |
| rst_temp_path=rst_temp_path) |
| |
| return "\n".join([RST_LONG_DESCRIPTION_INTRO, long_description]) |
| |
| |
| def prep(): |
| """Update the reST long_description file.""" |
| long_description = make_long_description() |
| write(long_description, RST_DESCRIPTION_PATH) |
| |
| |
| def publish(): |
| """Publish this package to PyPI (aka "the Cheeseshop").""" |
| long_description = make_long_description() |
| |
| if long_description != read(RST_DESCRIPTION_PATH): |
| print("""\ |
| Description file not up-to-date: %s |
| Run the following command and commit the changes-- |
| |
| python setup.py %s |
| """ % (RST_DESCRIPTION_PATH, PREP_COMMAND)) |
| sys.exit() |
| |
| print("Description up-to-date: %s" % RST_DESCRIPTION_PATH) |
| |
| answer = raw_input("Are you sure you want to publish to PyPI (yes/no)?") |
| |
| if answer != "yes": |
| exit("Aborted: nothing published") |
| |
| os.system('python setup.py sdist upload') |
| |
| |
| # We use the package simplejson for older Python versions since Python |
| # does not contain the module json before 2.6: |
| # |
| # http://docs.python.org/library/json.html |
| # |
| # Moreover, simplejson stopped officially support for Python 2.4 in version 2.1.0: |
| # |
| # https://github.com/simplejson/simplejson/blob/master/CHANGES.txt |
| # |
| requires = [] |
| if py_version < (2, 5): |
| requires.append('simplejson<2.1') |
| elif py_version < (2, 6): |
| requires.append('simplejson') |
| |
| INSTALL_REQUIRES = requires |
| |
| # TODO: decide whether to use find_packages() instead. I'm not sure that |
| # find_packages() is available with distutils, for example. |
| PACKAGES = [ |
| 'pystache', |
| 'pystache.commands', |
| # The following packages are only for testing. |
| 'pystache.tests', |
| 'pystache.tests.data', |
| 'pystache.tests.data.locator', |
| 'pystache.tests.examples', |
| ] |
| |
| |
| # The purpose of this function is to follow the guidance suggested here: |
| # |
| # http://packages.python.org/distribute/python3.html#note-on-compatibility-with-setuptools |
| # |
| # The guidance is for better compatibility when using setuptools (e.g. with |
| # earlier versions of Python 2) instead of Distribute, because of new |
| # keyword arguments to setup() that setuptools may not recognize. |
| def get_extra_args(): |
| """ |
| Return a dictionary of extra args to pass to setup(). |
| |
| """ |
| extra = {} |
| # TODO: it might be more correct to check whether we are using |
| # Distribute instead of setuptools, since use_2to3 doesn't take |
| # effect when using Python 2, even when using Distribute. |
| if py_version >= (3, ): |
| # Causes 2to3 to be run during the build step. |
| extra['use_2to3'] = True |
| |
| return extra |
| |
| |
| def main(sys_argv): |
| |
| # TODO: use the logging module instead of printing. |
| # TODO: include the following in a verbose mode. |
| sys.stderr.write("pystache: using: version %s of %s\n" % (repr(dist.__version__), repr(dist))) |
| |
| command = sys_argv[-1] |
| |
| if command == 'publish': |
| publish() |
| sys.exit() |
| elif command == PREP_COMMAND: |
| prep() |
| sys.exit() |
| |
| long_description = read(RST_DESCRIPTION_PATH) |
| template_files = ['*.mustache', '*.txt'] |
| extra_args = get_extra_args() |
| |
| setup(name='pystache', |
| version=VERSION, |
| license='MIT', |
| description='Mustache for Python', |
| long_description=long_description, |
| author='Chris Wanstrath', |
| author_email='chris@ozmm.org', |
| maintainer='Chris Jerdonek', |
| maintainer_email='chris.jerdonek@gmail.com', |
| url='http://github.com/defunkt/pystache', |
| install_requires=INSTALL_REQUIRES, |
| packages=PACKAGES, |
| package_data = { |
| # Include template files so tests can be run. |
| 'pystache.tests.data': template_files, |
| 'pystache.tests.data.locator': template_files, |
| 'pystache.tests.examples': template_files, |
| }, |
| entry_points = { |
| 'console_scripts': [ |
| 'pystache=pystache.commands.render:main', |
| 'pystache-test=pystache.commands.test:main', |
| ], |
| }, |
| classifiers = CLASSIFIERS, |
| **extra_args |
| ) |
| |
| |
| if __name__=='__main__': |
| main(sys.argv) |