Add setup.py, add quickstart script.

This commit is contained in:
Georg Brandl 2008-02-09 23:09:36 +00:00
parent b2ec05e690
commit a1e379d46c
12 changed files with 532 additions and 13 deletions

223
ez_setup.py Normal file
View File

@ -0,0 +1,223 @@
#!python
"""Bootstrap setuptools installation
If you want to use setuptools in your package's setup.py, just include this
file in the same directory with it, and add this to the top of your setup.py::
from ez_setup import use_setuptools
use_setuptools()
If you want to require a specific version of setuptools, set a download
mirror, or use an alternate download directory, you can do so by supplying
the appropriate options to ``use_setuptools()``.
This file can also be run as a script to install or upgrade setuptools.
"""
import sys
DEFAULT_VERSION = "0.6c5"
DEFAULT_URL = "http://cheeseshop.python.org/packages/%s/s/setuptools/" % sys.version[:3]
md5_data = {
'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f',
'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2',
'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc',
'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167',
'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64',
'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d',
}
import sys, os
def _validate_md5(egg_name, data):
if egg_name in md5_data:
from md5 import md5
digest = md5(data).hexdigest()
if digest != md5_data[egg_name]:
print >>sys.stderr, (
"md5 validation of %s failed! (Possible download problem?)"
% egg_name
)
sys.exit(2)
return data
def use_setuptools(
version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
download_delay=15
):
"""Automatically find/download setuptools and make it available on sys.path
`version` should be a valid setuptools version number that is available
as an egg for download under the `download_base` URL (which should end with
a '/'). `to_dir` is the directory where setuptools will be downloaded, if
it is not already available. If `download_delay` is specified, it should
be the number of seconds that will be paused before initiating a download,
should one be required. If an older version of setuptools is installed,
this routine will print a message to ``sys.stderr`` and raise SystemExit in
an attempt to abort the calling script.
"""
try:
import setuptools
if setuptools.__version__ == '0.0.1':
print >>sys.stderr, (
"You have an obsolete version of setuptools installed. Please\n"
"remove it from your system entirely before rerunning this script."
)
sys.exit(2)
except ImportError:
egg = download_setuptools(version, download_base, to_dir, download_delay)
sys.path.insert(0, egg)
import setuptools; setuptools.bootstrap_install_from = egg
import pkg_resources
try:
pkg_resources.require("setuptools>="+version)
except pkg_resources.VersionConflict, e:
# XXX could we install in a subprocess here?
print >>sys.stderr, (
"The required version of setuptools (>=%s) is not available, and\n"
"can't be installed while this script is running. Please install\n"
" a more recent version first.\n\n(Currently using %r)"
) % (version, e.args[0])
sys.exit(2)
def download_setuptools(
version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
delay = 15
):
"""Download setuptools from a specified location and return its filename
`version` should be a valid setuptools version number that is available
as an egg for download under the `download_base` URL (which should end
with a '/'). `to_dir` is the directory where the egg will be downloaded.
`delay` is the number of seconds to pause before an actual download attempt.
"""
import urllib2, shutil
egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
url = download_base + egg_name
saveto = os.path.join(to_dir, egg_name)
src = dst = None
if not os.path.exists(saveto): # Avoid repeated downloads
try:
from distutils import log
if delay:
log.warn("""
---------------------------------------------------------------------------
This script requires setuptools version %s to run (even to display
help). I will attempt to download it for you (from
%s), but
you may need to enable firewall access for this script first.
I will start the download in %d seconds.
(Note: if this machine does not have network access, please obtain the file
%s
and place it in this directory before rerunning this script.)
---------------------------------------------------------------------------""",
version, download_base, delay, url
); from time import sleep; sleep(delay)
log.warn("Downloading %s", url)
src = urllib2.urlopen(url)
# Read/write all in one block, so we don't create a corrupt file
# if the download is interrupted.
data = _validate_md5(egg_name, src.read())
dst = open(saveto,"wb"); dst.write(data)
finally:
if src: src.close()
if dst: dst.close()
return os.path.realpath(saveto)
def main(argv, version=DEFAULT_VERSION):
"""Install or upgrade setuptools and EasyInstall"""
try:
import setuptools
except ImportError:
egg = None
try:
egg = download_setuptools(version, delay=0)
sys.path.insert(0,egg)
from setuptools.command.easy_install import main
return main(list(argv)+[egg]) # we're done here
finally:
if egg and os.path.exists(egg):
os.unlink(egg)
else:
if setuptools.__version__ == '0.0.1':
# tell the user to uninstall obsolete version
use_setuptools(version)
req = "setuptools>="+version
import pkg_resources
try:
pkg_resources.require(req)
except pkg_resources.VersionConflict:
try:
from setuptools.command.easy_install import main
except ImportError:
from easy_install import main
main(list(argv)+[download_setuptools(delay=0)])
sys.exit(0) # try to force an exit
else:
if argv:
from setuptools.command.easy_install import main
main(argv)
else:
print "Setuptools version",version,"or greater has been installed."
print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
def update_md5(filenames):
"""Update our built-in md5 registry"""
import re
from md5 import md5
for name in filenames:
base = os.path.basename(name)
f = open(name,'rb')
md5_data[base] = md5(f.read()).hexdigest()
f.close()
data = [" %r: %r,\n" % it for it in md5_data.items()]
data.sort()
repl = "".join(data)
import inspect
srcfile = inspect.getsourcefile(sys.modules[__name__])
f = open(srcfile, 'rb'); src = f.read(); f.close()
match = re.search("\nmd5_data = {\n([^}]+)}", src)
if not match:
print >>sys.stderr, "Internal error!"
sys.exit(2)
src = src[:match.start(1)] + repl + src[match.end(1):]
f = open(srcfile,'w')
f.write(src)
f.close()
if __name__=='__main__':
if len(sys.argv)>2 and sys.argv[1]=='--md5update':
update_md5(sys.argv[2:])
else:
main(sys.argv[1:])

42
setup.py Normal file
View File

@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
import ez_setup
ez_setup.use_setuptools()
import sphinx
from setuptools import setup, Feature
setup(
name='Sphinx',
version=sphinx.__version__,
# url='',
# download_url='',
license='BSD',
author='Georg Brandl',
author_email='georg@python.org',
description='Python documentation generator',
long_description='',
zip_safe=False,
classifiers=[
'Development Status :: 2 - Pre-Alpha',
'Environment :: Console',
'Environment :: Web Environment',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Topic :: Documentation',
'Topic :: Utilities',
],
platforms='any',
packages=['sphinx'],
include_package_data=True,
scripts=['sphinx-build.py', 'sphinx-web.py', 'sphinx-quickstart.py'],
entry_points={
'console_scripts': [
'sphinx-build = sphinx:main',
'sphinx-web = sphinx.web:main',
'sphinx-quickstart = sphinx.quickstart:main'
]
},
install_requires=['Pygments>=0.8', 'docutils==0.4']
)

14
sphinx-quickstart.py Normal file
View File

@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
"""
Sphinx - Python documentation toolchain
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: 2008 by Georg Brandl.
:license: BSD.
"""
import sys
if __name__ == '__main__':
from sphinx.quickstart import main
sys.exit(main(sys.argv))

View File

@ -42,7 +42,7 @@ modi:
* with filenames, write these.""" % (argv[0],)
def main(argv):
def main(argv=sys.argv):
if not sys.stdout.isatty() or sys.platform == 'win32':
# Windows' poor cmd box doesn't understand ANSI sequences
nocolor()

View File

@ -17,8 +17,10 @@ import sys
from docutils import nodes
from docutils.parsers.rst import directives, roles
import sphinx
from sphinx.config import Config
from sphinx.builder import builtin_builders
from sphinx.util.console import bold
class ExtensionError(Exception):
@ -62,6 +64,7 @@ class Application(object):
self._status = status
self._warning = warning
self._warncount = 0
# read config
self.config = Config(srcdir, 'conf.py')
@ -84,11 +87,14 @@ class Application(object):
print >>warning, 'Builder name %s not registered' % buildername
return
self.info(bold('Sphinx v%s, building %s' % (sphinx.__version__, buildername)))
builderclass = self.builderclasses[buildername]
self.builder = builderclass(self, freshenv=freshenv)
self.emit('builder-inited')
def warn(self, message):
self._warncount += 1
self._warning.write('WARNING: %s\n' % message)
def info(self, message='', nonl=False):

View File

@ -135,7 +135,10 @@ class Builder(object):
path.join(self.doctreedir, ENV_PICKLE_FILENAME))
self.info('done')
except Exception, err:
self.info('failed: %s' % err)
if type(err) is IOError and err.errno == 2:
self.info('not found')
else:
self.info('failed: %s' % err)
self.env = BuildEnvironment(self.srcdir, self.doctreedir, self.config)
else:
self.env = BuildEnvironment(self.srcdir, self.doctreedir, self.config)
@ -212,7 +215,10 @@ class Builder(object):
# finish (write style files etc.)
self.info(bold('finishing... '))
self.finish()
self.info(bold('build succeeded.'))
if self.app._warncount:
self.info(bold('build succeeded, %s warnings.' % self.app._warncount))
else:
self.info(bold('build succeeded.'))
def write(self, build_docnames, updated_docnames):
if build_docnames is None: # build_all
@ -234,8 +240,11 @@ class Builder(object):
self.env.set_warnfunc(warnings.append)
for docname in self.status_iterator(sorted(docnames),
'writing output... ', darkgreen):
doctree = self.env.get_and_resolve_doctree(docname, self)
self.write_doc(docname, doctree)
try:
doctree = self.env.get_and_resolve_doctree(docname, self)
self.write_doc(docname, doctree)
except Exception, err:
warnings.append('%s:: doctree not found!' % docname)
for warning in warnings:
if warning.strip():
self.warn(warning)
@ -460,7 +469,7 @@ class StandaloneHTMLBuilder(Builder):
def get_outdated_docs(self):
for docname in get_matching_docs(
self.srcdir, self.config.source_suffix,
exclude=set(self.config.unused_files)):
exclude=set(self.config.unused_docs)):
targetname = self.env.doc2path(docname, self.outdir, '.html')
try:
targetmtime = path.getmtime(targetname)
@ -544,7 +553,7 @@ class WebHTMLBuilder(StandaloneHTMLBuilder):
def get_outdated_docs(self):
for docname in get_matching_docs(
self.srcdir, self.config.source_suffix,
exclude=set(self.config.unused_files)):
exclude=set(self.config.unused_docs)):
targetname = self.env.doc2path(docname, self.outdir, '.fpickle')
try:
targetmtime = path.getmtime(targetname)

View File

@ -35,7 +35,7 @@ class Config(object):
# general reading options
master_doc = ('contents', True),
source_suffix = ('.rst', True),
unused_files = ([], True),
unused_docs = ([], True),
add_function_parentheses = (True, True),
add_module_names = (True, True),

View File

@ -303,7 +303,7 @@ class BuildEnvironment:
Return (added, changed, removed) sets.
"""
self.found_docs = set(get_matching_docs(self.srcdir, config.source_suffix,
exclude=set(config.unused_files)))
exclude=set(config.unused_docs)))
# clear all files no longer present
removed = set(self.all_docs) - self.found_docs
@ -368,7 +368,7 @@ class BuildEnvironment:
self.read_doc(docname, app=app)
if config.master_doc not in self.all_docs:
self.warn(None, 'no master file %s found' % self.doc2path(config.master_doc))
self.warn(None, 'master file %s not found' % self.doc2path(config.master_doc))
# --------- SINGLE FILE BUILDING -------------------------------------------

224
sphinx/quickstart.py Normal file
View File

@ -0,0 +1,224 @@
# -*- coding: utf-8 -*-
"""
sphinx.quickstart
~~~~~~~~~~~~~~~~~
Quickly setup documentation source to work with Sphinx.
:copyright: 2008 by Georg Brandl.
:license: BSD.
"""
import sys, os, time
from os import path
from sphinx.util.console import darkgreen, purple, bold, red, nocolor
QUICKSTART_CONF = '''\
# -*- coding: utf-8 -*-
#
# %(project)s documentation build configuration file, created by
# sphinx-quickstart.py on %(now)s.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# The contents of this file are pickled, so don't put values in the namespace
# that aren't pickleable (module imports are okay, they're removed automatically).
#
# All configuration values have a default value; values that are commented out
# show the default value as assigned to them.
import sys
# If your extensions are in another directory, add it here.
#sys.path.append('some/directory')
# General configuration
# ---------------------
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.addons.*') or your custom ones.
#extensions = []
# Add any paths that contain templates here, relative to this directory.
#templates_path = []
# The suffix of source filenames.
source_suffix = '%(suffix)s'
# The master toctree document.
master_doc = '%(master)s'
# General substitutions.
project = %(project)r
copyright = '%(year)s, %(author)s'
# The default replacements for |version| and |release|, also used in various
# other places throughout the built documents.
#
# The short X.Y version.
version = '%(version)s'
# The full version, including alpha/beta/rc tags.
release = '%(release)s'
# 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 documents that shouldn't be included in the build.
#unused_docs = []
# 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
# Options for HTML output
# -----------------------
# 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
# Content template for the index page, filename relative to this file.
#html_index = ''
# Custom sidebar templates, maps page names to filenames relative to this file.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# filenames relative to this file.
#html_additional_pages = {}
# If true, the reST sources are included in the HTML build as _sources/<name>.
#html_copy_source = True
# Output file base name for HTML help builder.
htmlhelp_basename = '%(project)sdoc'
# Options for LaTeX output
# ------------------------
# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, document class [howto/manual]).
#latex_documents = []
# Additional stuff for the LaTeX preamble.
#latex_preamble = '
# Documents to append as an appendix to all manuals.
#latex_appendices = []
'''
def is_path(x):
"""Please enter an existing path name."""
return path.isdir(x)
def nonempty(x):
"""Please enter some text."""
return len(x)
def suffix(x):
"""Please enter a file suffix, e.g. '.rst' or '.txt'."""
return x[0:1] == '.' and len(x) > 1
def do_prompt(d, key, text, default=None, validator=nonempty):
while True:
if default:
prompt = purple('> %s [%s]: ' % (text, default))
else:
prompt = purple('> ' + text + ': ')
x = raw_input(prompt)
if default and not x:
x = default
if validator and not validator(x):
print red(" * " + validator.__doc__)
continue
break
d[key] = x
def inner_main(args):
d = {}
if os.name == 'nt' or not sys.stdout.isatty():
nocolor()
print bold('Welcome to the Sphinx quickstart utility.')
print '''
Please enter values for the following settings (just press Enter to
accept a default value, if one is given in brackets).'''
print '''
This tool will create "src" and "build" folders in this path, which
must be an existing directory.'''
do_prompt(d, 'path', 'Root path for the documentation', '.', is_path)
print '''
The project name will occur in several places in the built documentation.'''
do_prompt(d, 'project', 'Project name')
do_prompt(d, 'author', 'Author name(s)')
print '''
Sphinx has the notion of a "version" and a "release" for the
software. Each version can have multiple releases. For example, for
Python the version is something like 2.5 or 3.0, while the release is
something like 2.5.1 or 3.0a1. If you don't need this dual structure,
just set both to the same value.'''
do_prompt(d, 'version', 'Project version')
do_prompt(d, 'release', 'Project release', d['version'])
print '''
The file name suffix for source files. Commonly, this is either ".txt"
or ".rst". Only files with this suffix are considered documents.'''
do_prompt(d, 'suffix', 'Source file suffix', '.rst', suffix)
print '''
One document is special in that it is considered the top node of the
"contents tree", that is, it is the root of the hierarchical structure
of the documents. Normally, this is "index", but if your "index"
document is a custom template, you can also set this to another filename.'''
do_prompt(d, 'master', 'Name of your master document (without suffix)', 'index')
d['year'] = time.strftime('%Y')
d['now'] = time.asctime()
os.mkdir(path.join(d['path'], 'src'))
os.mkdir(path.join(d['path'], 'build'))
f = open(path.join(d['path'], 'src', 'conf.py'), 'w')
f.write(QUICKSTART_CONF % d)
f.close()
masterfile = path.join(d['path'], 'src', d['master'] + d['suffix'])
print
print bold('Finished: An initial directory structure has been created.')
print '''
You should now create your master file %s and other documentation
sources. Use the sphinx-build.py script to build the docs.
''' % (masterfile)
def main(argv=sys.argv):
try:
return inner_main(argv)
except (KeyboardInterrupt, EOFError):
print
print '[Interrupted.]'
return

View File

@ -63,10 +63,11 @@ def get_matching_docs(dirname, suffix, exclude=()):
for sfile in files:
if not fnmatch.fnmatch(sfile, pattern):
continue
qualified_name = path.join(root[dirlen:], sfile)
qualified_name = path.join(root[dirlen:], sfile[:-len(suffix)])
qualified_name = qualified_name.replace(os.path.sep, SEP)
if qualified_name in exclude:
continue
yield qualified_name[:-len(suffix)].replace(os.path.sep, SEP)
yield qualified_name
def shorten_result(text='', keywords=[], maxlen=240, fuzz=60):

View File

@ -23,7 +23,7 @@ except ImportError:
DebuggedApplication = lambda x, y: x
def main(argv):
def main(argv=sys.argv):
opts, args = getopt.getopt(argv[1:], "dhf:")
opts = dict(opts)
if len(args) != 1 or '-h' in opts: