Imported from SVN by Bitbucket

This commit is contained in:
2015-03-31 20:26:20 +00:00
committed by bitbucket
commit ceb7984dec
212 changed files with 49537 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
#

View File

@@ -0,0 +1,615 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
"""
Provides the two commands for preparing an application:
``prepare-app`` and ``setup-app``
"""
import os
import sys
if sys.version_info < (2, 4):
from paste.script.util import string24 as string
else:
import string
import new
from cStringIO import StringIO
from paste.script.command import Command, BadCommand, run as run_command
import paste.script.templates
from paste.script import copydir
import pkg_resources
Cheetah = None
from ConfigParser import ConfigParser
from paste.util import import_string
from paste.deploy import appconfig
from paste.script.util import uuid
from paste.script.util import secret
class AbstractInstallCommand(Command):
default_interactive = 1
default_sysconfigs = [
(False, '/etc/paste/sysconfig.py'),
(False, '/usr/local/etc/paste/sysconfig.py'),
(True, 'paste.script.default_sysconfig'),
]
if os.environ.get('HOME'):
default_sysconfigs.insert(
0, (False, os.path.join(os.environ['HOME'], '.paste', 'config',
'sysconfig.py')))
if os.environ.get('PASTE_SYSCONFIG'):
default_sysconfigs.insert(
0, (False, os.environ['PASTE_SYSCONFIG']))
def run(self, args):
# This is overridden so we can parse sys-config before we pass
# it to optparse
self.sysconfigs = self.default_sysconfigs
new_args = []
while args:
if args[0].startswith('--no-default-sysconfig'):
self.sysconfigs = []
args.pop(0)
continue
if args[0].startswith('--sysconfig='):
self.sysconfigs.insert(
0, (True, args.pop(0)[len('--sysconfig='):]))
continue
if args[0] == '--sysconfig':
args.pop(0)
if not args:
raise BadCommand, (
"You gave --sysconfig as the last argument without "
"a value")
self.sysconfigs.insert(0, (True, args.pop(0)))
continue
new_args.append(args.pop(0))
self.load_sysconfigs()
return super(AbstractInstallCommand, self).run(new_args)
#@classmethod
def standard_parser(cls, **kw):
parser = super(AbstractInstallCommand, cls).standard_parser(**kw)
parser.add_option('--sysconfig',
action="append",
dest="sysconfigs",
help="System configuration file")
parser.add_option('--no-default-sysconfig',
action='store_true',
dest='no_default_sysconfig',
help="Don't load the default sysconfig files")
parser.add_option(
'--easy-install',
action='append',
dest='easy_install_op',
metavar='OP',
help='An option to add if invoking easy_install (like --easy-install=exclude-scripts)')
parser.add_option(
'--no-install',
action='store_true',
dest='no_install',
help="Don't try to install the package (it must already be installed)")
parser.add_option(
'-f', '--find-links',
action='append',
dest='easy_install_find_links',
metavar='URL',
help='Passed through to easy_install')
return parser
standard_parser = classmethod(standard_parser)
########################################
## Sysconfig Handling
########################################
def load_sysconfigs(self):
configs = self.sysconfigs[:]
configs.reverse()
self.sysconfig_modules = []
for index, (explicit, name) in enumerate(configs):
# @@: At some point I'd like to give the specialized
# modules some access to the values in earlier modules,
# e.g., to specialize those values or functions. That's
# why these modules are loaded backwards.
if name.endswith('.py'):
if not os.path.exists(name):
if explicit:
raise BadCommand, (
"sysconfig file %s does not exist"
% name)
else:
continue
globs = {}
execfile(name, globs)
mod = new.module('__sysconfig_%i__' % index)
for name, value in globs.items():
setattr(mod, name, value)
mod.__file__ = name
else:
try:
mod = import_string.simple_import(name)
except ImportError, e:
if explicit:
raise
else:
continue
mod.paste_command = self
self.sysconfig_modules.insert(0, mod)
# @@: I'd really prefer to clone the parser here somehow,
# not to modify it in place
parser = self.parser
self.call_sysconfig_functions('add_custom_options', parser)
def get_sysconfig_option(self, name, default=None):
"""
Return the value of the given option in the first sysconfig
module in which it is found, or ``default`` (None) if not
found in any.
"""
for mod in self.sysconfig_modules:
if hasattr(mod, name):
return getattr(mod, name)
return default
def get_sysconfig_options(self, name):
"""
Return the option value for the given name in all the
sysconfig modules in which is is found (``[]`` if none).
"""
return [getattr(mod, name) for mod in self.sysconfig_modules
if hasattr(mod, name)]
def call_sysconfig_function(self, name, *args, **kw):
"""
Call the specified function in the first sysconfig module it
is defined in. ``NameError`` if no function is found.
"""
val = self.get_sysconfig_option(name)
if val is None:
raise NameError, (
"Method %s not found in any sysconfig module" % name)
return val(*args, **kw)
def call_sysconfig_functions(self, name, *args, **kw):
"""
Call all the named functions in the sysconfig modules,
returning a list of the return values.
"""
return [method(*args, **kw) for method in
self.get_sysconfig_options(name)]
def sysconfig_install_vars(self, installer):
"""
Return the folded results of calling the
``install_variables()`` functions.
"""
result = {}
all_vars = self.call_sysconfig_functions(
'install_variables', installer)
all_vars.reverse()
for vardict in all_vars:
result.update(vardict)
return result
########################################
## Distributions
########################################
def get_distribution(self, req):
"""
This gets a distribution object, and installs the distribution
if required.
"""
try:
dist = pkg_resources.get_distribution(req)
if self.verbose:
print 'Distribution already installed:'
print ' ', dist, 'from', dist.location
return dist
except pkg_resources.DistributionNotFound:
if self.options.no_install:
print "Because --no-install was given, we won't try to install the package %s" % req
raise
options = ['-v', '-m']
for op in self.options.easy_install_op or []:
if not op.startswith('-'):
op = '--'+op
options.append(op)
for op in self.options.easy_install_find_links or []:
options.append('--find-links=%s' % op)
if self.simulate:
raise BadCommand(
"Must install %s, but in simulation mode" % req)
print "Must install %s" % req
from setuptools.command import easy_install
from setuptools import setup
setup(script_args=['-q', 'easy_install']
+ options + [req])
return pkg_resources.get_distribution(req)
def get_installer(self, distro, ep_group, ep_name):
installer_class = distro.load_entry_point(
'paste.app_install', ep_name)
installer = installer_class(
distro, ep_group, ep_name)
return installer
class MakeConfigCommand(AbstractInstallCommand):
default_verbosity = 1
max_args = None
min_args = 1
summary = "Install a package and create a fresh config file/directory"
usage = "PACKAGE_NAME [CONFIG_FILE] [VAR=VALUE]"
description = """\
Note: this is an experimental command, and it will probably change
in several ways by the next release.
make-config is part of a two-phase installation process (the
second phase is setup-app). make-config installs the package
(using easy_install) and asks it to create a bare configuration
file or directory (possibly filling in defaults from the extra
variables you give).
"""
parser = AbstractInstallCommand.standard_parser(
simulate=True, quiet=True, no_interactive=True)
parser.add_option('--info',
action="store_true",
dest="show_info",
help="Show information on the package (after installing it), but do not write a config.")
parser.add_option('--name',
action='store',
dest='ep_name',
help='The name of the application contained in the distribution (default "main")')
parser.add_option('--entry-group',
action='store',
dest='ep_group',
default='paste.app_factory',
help='The entry point group to install (i.e., the kind of application; default paste.app_factory')
parser.add_option('--edit',
action='store_true',
dest='edit',
help='Edit the configuration file after generating it (using $EDITOR)')
parser.add_option('--setup',
action='store_true',
dest='run_setup',
help='Run setup-app immediately after generating (and possibly editing) the configuration file')
def command(self):
self.requirement = self.args[0]
if '#' in self.requirement:
if self.options.ep_name is not None:
raise BadCommand(
"You may not give both --name and a requirement with "
"#name")
self.requirement, self.options.ep_name = self.requirement.split('#', 1)
if not self.options.ep_name:
self.options.ep_name = 'main'
self.distro = self.get_distribution(self.requirement)
self.installer = self.get_installer(
self.distro, self.options.ep_group, self.options.ep_name)
if self.options.show_info:
if len(self.args) > 1:
raise BadCommand(
"With --info you can only give one argument")
return self.show_info()
if len(self.args) < 2:
# See if sysconfig can give us a default filename
options = filter(None, self.call_sysconfig_functions(
'default_config_filename', self.installer))
if not options:
raise BadCommand(
"You must give a configuration filename")
self.config_file = options[0]
else:
self.config_file = self.args[1]
self.check_config_file()
self.project_name = self.distro.project_name
self.vars = self.sysconfig_install_vars(self.installer)
self.vars.update(self.parse_vars(self.args[2:]))
self.vars['project_name'] = self.project_name
self.vars['requirement'] = self.requirement
self.vars['ep_name'] = self.options.ep_name
self.vars['ep_group'] = self.options.ep_group
self.vars.setdefault('app_name', self.project_name.lower())
self.vars.setdefault('app_instance_uuid', uuid.uuid4())
self.vars.setdefault('app_instance_secret', secret.secret_string())
if self.verbose > 1:
print_vars = self.vars.items()
print_vars.sort()
print 'Variables for installation:'
for name, value in print_vars:
print ' %s: %r' % (name, value)
self.installer.write_config(self, self.config_file, self.vars)
edit_success = True
if self.options.edit:
edit_success = self.run_editor()
setup_configs = self.installer.editable_config_files(self.config_file)
# @@: We'll just assume the first file in the list is the one
# that works with setup-app...
setup_config = setup_configs[0]
if self.options.run_setup:
if not edit_success:
print 'Config-file editing was not successful.'
if self.ask('Run setup-app anyway?', default=False):
self.run_setup(setup_config)
else:
self.run_setup(setup_config)
else:
filenames = self.installer.editable_config_files(self.config_file)
assert not isinstance(filenames, basestring), (
"editable_config_files returned a string, not a list")
if not filenames and filenames is not None:
print 'No config files need editing'
else:
print 'Now you should edit the config files'
if filenames:
for fn in filenames:
print ' %s' % fn
def show_info(self):
text = self.installer.description(None)
print text
def check_config_file(self):
if self.installer.expect_config_directory is None:
return
fn = self.config_file
if self.installer.expect_config_directory:
if os.path.splitext(fn)[1]:
raise BadCommand(
"The CONFIG_FILE argument %r looks like a filename, "
"and a directory name is expected" % fn)
else:
if fn.endswith('/') or not os.path.splitext(fn):
raise BadCommand(
"The CONFIG_FILE argument %r looks like a directory "
"name and a filename is expected" % fn)
def run_setup(self, filename):
run_command(['setup-app', filename])
def run_editor(self):
filenames = self.installer.editable_config_files(self.config_file)
if filenames is None:
print 'Warning: the config file is not known (--edit ignored)'
return False
if not filenames:
print 'Warning: no config files need editing (--edit ignored)'
return True
if len(filenames) > 1:
print 'Warning: there is more than one editable config file (--edit ignored)'
return False
if not os.environ.get('EDITOR'):
print 'Error: you must set $EDITOR if using --edit'
return False
if self.verbose:
print '%s %s' % (os.environ['EDITOR'], filenames[0])
retval = os.system('$EDITOR %s' % filenames[0])
if retval:
print 'Warning: editor %s returned with error code %i' % (
os.environ['EDITOR'], retval)
return False
return True
class SetupCommand(AbstractInstallCommand):
default_verbosity = 1
max_args = 1
min_args = 1
summary = "Setup an application, given a config file"
usage = "CONFIG_FILE"
description = """\
Note: this is an experimental command, and it will probably change
in several ways by the next release.
Setup an application according to its configuration file. This is
the second part of a two-phase web application installation
process (the first phase is prepare-app). The setup process may
consist of things like creating directories and setting up
databases.
"""
parser = AbstractInstallCommand.standard_parser(
simulate=True, quiet=True, interactive=True)
parser.add_option('--name',
action='store',
dest='section_name',
default=None,
help='The name of the section to set up (default: app:main)')
def command(self):
config_spec = self.args[0]
section = self.options.section_name
if section is None:
if '#' in config_spec:
config_spec, section = config_spec.split('#', 1)
else:
section = 'main'
if not ':' in section:
plain_section = section
section = 'app:'+section
else:
plain_section = section.split(':', 1)[0]
if not config_spec.startswith('config:'):
config_spec = 'config:' + config_spec
if plain_section != 'main':
config_spec += '#' + plain_section
config_file = config_spec[len('config:'):].split('#', 1)[0]
config_file = os.path.join(os.getcwd(), config_file)
self.logging_file_config(config_file)
conf = appconfig(config_spec, relative_to=os.getcwd())
ep_name = conf.context.entry_point_name
ep_group = conf.context.protocol
dist = conf.context.distribution
if dist is None:
raise BadCommand(
"The section %r is not the application (probably a filter). You should add #section_name, where section_name is the section that configures your application" % plain_section)
installer = self.get_installer(dist, ep_group, ep_name)
installer.setup_config(
self, config_file, section, self.sysconfig_install_vars(installer))
self.call_sysconfig_functions(
'post_setup_hook', installer, config_file)
class Installer(object):
"""
Abstract base class for installers, and also a generic
installer that will run off config files in the .egg-info
directory of a distribution.
Packages that simply refer to this installer can provide a file
``*.egg-info/paste_deploy_config.ini_tmpl`` that will be
interpreted by Cheetah. They can also provide ``websetup``
modules with a ``setup_app(command, conf, vars)`` (or the
now-deprecated ``setup_config(command, filename, section, vars)``)
function, that will be called.
In the future other functions or configuration files may be
called.
"""
# If this is true, then try to detect filename-looking config_file
# values, and reject them. Conversely, if false try to detect
# directory-looking values and reject them. None means don't
# check.
expect_config_directory = False
# Set this to give a default config filename when none is
# specified:
default_config_filename = None
# Set this to true to use Cheetah to fill your templates, or false
# to not do so:
use_cheetah = True
def __init__(self, dist, ep_group, ep_name):
self.dist = dist
self.ep_group = ep_group
self.ep_name = ep_name
def description(self, config):
return 'An application'
def write_config(self, command, filename, vars):
"""
Writes the content to the filename (directory or single file).
You should use the ``command`` object, which respects things
like simulation and interactive. ``vars`` is a dictionary
of user-provided variables.
"""
command.ensure_file(filename, self.config_content(command, vars))
def editable_config_files(self, filename):
"""
Return a list of filenames; this is primarily used when the
filename is treated as a directory and several configuration
files are created. The default implementation returns the
file itself. Return None if you don't know what files should
be edited on installation.
"""
if not self.expect_config_directory:
return [filename]
else:
return None
def config_content(self, command, vars):
"""
Called by ``self.write_config``, this returns the text content
for the config file, given the provided variables.
The default implementation reads
``Package.egg-info/paste_deploy_config.ini_tmpl`` and fills it
with the variables.
"""
global Cheetah
meta_name = 'paste_deploy_config.ini_tmpl'
if not self.dist.has_metadata(meta_name):
if command.verbose:
print 'No %s found' % meta_name
return self.simple_config(vars)
return self.template_renderer(
self.dist.get_metadata(meta_name), vars, filename=meta_name)
def template_renderer(self, content, vars, filename=None):
"""
Subclasses may override this to provide different template
substitution (e.g., use a different template engine).
"""
if self.use_cheetah:
import Cheetah.Template
tmpl = Cheetah.Template.Template(content,
searchList=[vars])
return copydir.careful_sub(
tmpl, vars, filename)
else:
tmpl = string.Template(content)
return tmpl.substitute(vars)
def simple_config(self, vars):
"""
Return a very simple configuration file for this application.
"""
if self.ep_name != 'main':
ep_name = '#'+self.ep_name
else:
ep_name = ''
return ('[app:main]\n'
'use = egg:%s%s\n'
% (self.dist.project_name, ep_name))
def setup_config(self, command, filename, section, vars):
"""
Called to setup an application, given its configuration
file/directory.
The default implementation calls
``package.websetup.setup_config(command, filename, section,
vars)`` or ``package.websetup.setup_app(command, config,
vars)``
With ``setup_app`` the ``config`` object is a dictionary with
the extra attributes ``global_conf``, ``local_conf`` and
``filename``
"""
modules = [
line.strip()
for line in self.dist.get_metadata_lines('top_level.txt')
if line.strip() and not line.strip().startswith('#')]
if not modules:
print 'No modules are listed in top_level.txt'
print 'Try running python setup.py egg_info to regenerate that file'
for mod_name in modules:
mod_name = mod_name + '.websetup'
mod = import_string.try_import_module(mod_name)
if mod is None:
continue
if hasattr(mod, 'setup_app'):
if command.verbose:
print 'Running setup_app() from %s' % mod_name
self._call_setup_app(
mod.setup_app, command, filename, section, vars)
elif hasattr(mod, 'setup_config'):
if command.verbose:
print 'Running setup_config() from %s' % mod_name
mod.setup_config(command, filename, section, vars)
else:
print 'No setup_app() or setup_config() function in %s (%s)' % (
mod.__name__, mod.__file__)
def _call_setup_app(self, func, command, filename, section, vars):
filename = os.path.abspath(filename)
if ':' in section:
section = section.split(':', 1)[1]
conf = 'config:%s#%s' % (filename, section)
conf = appconfig(conf)
conf.filename = filename
func(command, conf, vars)

View File

@@ -0,0 +1,60 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
"""
A subclass of ``optparse.OptionParser`` that allows boolean long
options (like ``--verbose``) to also take arguments (like
``--verbose=true``). Arguments *must* use ``=``.
"""
import optparse
try:
_ = optparse._
except AttributeError:
from gettext import gettext as _
class BoolOptionParser(optparse.OptionParser):
def _process_long_opt(self, rargs, values):
arg = rargs.pop(0)
# Value explicitly attached to arg? Pretend it's the next
# argument.
if "=" in arg:
(opt, next_arg) = arg.split("=", 1)
rargs.insert(0, next_arg)
had_explicit_value = True
else:
opt = arg
had_explicit_value = False
opt = self._match_long_opt(opt)
option = self._long_opt[opt]
if option.takes_value():
nargs = option.nargs
if len(rargs) < nargs:
if nargs == 1:
self.error(_("%s option requires an argument") % opt)
else:
self.error(_("%s option requires %d arguments")
% (opt, nargs))
elif nargs == 1:
value = rargs.pop(0)
else:
value = tuple(rargs[0:nargs])
del rargs[0:nargs]
elif had_explicit_value:
value = rargs[0].lower().strip()
del rargs[0:1]
if value in ('true', 'yes', 'on', '1', 'y', 't'):
value = None
elif value in ('false', 'no', 'off', '0', 'n', 'f'):
# Don't process
return
else:
self.error(_('%s option takes a boolean value only (true/false)') % opt)
else:
value = None
option.process(opt, value, values, self)

View File

@@ -0,0 +1,71 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
import os
import sys
## FIXME: this should be deprecated in favor of wsgiref
def paste_run_cgi(wsgi_app, global_conf):
run_with_cgi(wsgi_app)
stdout = sys.__stdout__
# Taken from the WSGI spec:
def run_with_cgi(application):
environ = dict(os.environ.items())
environ['wsgi.input'] = sys.stdin
environ['wsgi.errors'] = sys.stderr
environ['wsgi.version'] = (1,0)
environ['wsgi.multithread'] = False
environ['wsgi.multiprocess'] = True
environ['wsgi.run_once'] = True
if environ.get('HTTPS','off') in ('on','1'):
environ['wsgi.url_scheme'] = 'https'
else:
environ['wsgi.url_scheme'] = 'http'
headers_set = []
headers_sent = []
def write(data):
if not headers_set:
raise AssertionError("write() before start_response()")
elif not headers_sent:
# Before the first output, send the stored headers
status, response_headers = headers_sent[:] = headers_set
stdout.write('Status: %s\r\n' % status)
for header in response_headers:
stdout.write('%s: %s\r\n' % header)
stdout.write('\r\n')
stdout.write(data)
stdout.flush()
def start_response(status,response_headers,exc_info=None):
if exc_info:
try:
if headers_sent:
# Re-raise original exception if headers sent
raise exc_info[0], exc_info[1], exc_info[2]
finally:
exc_info = None # avoid dangling circular ref
elif headers_set:
raise AssertionError("Headers already set!")
headers_set[:] = [status,response_headers]
return write
result = application(environ, start_response)
try:
for data in result:
if data: # don't send headers until body appears
write(data)
if not headers_sent:
write('') # send headers now if body was empty
finally:
if hasattr(result,'close'):
result.close()

View File

@@ -0,0 +1,441 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
"""
This is a module to check the filesystem for the presence and
permissions of certain files. It can also be used to correct the
permissions (but not existance) of those files.
Currently only supports Posix systems (with Posixy permissions).
Permission stuff can probably be stubbed out later.
"""
import os
def read_perm_spec(spec):
"""
Reads a spec like 'rw-r--r--' into a octal number suitable for
chmod. That is characters in groups of three -- first group is
user, second for group, third for other (all other people). The
characters are r (read), w (write), and x (executable), though the
executable can also be s (sticky). Files in sticky directories
get the directories permission setting.
Examples::
>>> print oct(read_perm_spec('rw-r--r--'))
0644
>>> print oct(read_perm_spec('rw-rwsr--'))
02664
>>> print oct(read_perm_spec('r-xr--r--'))
0544
>>> print oct(read_perm_spec('r--------'))
0400
"""
total_mask = 0
# suid/sgid modes give this mask in user, group, other mode:
set_bits = (04000, 02000, 0)
pieces = (spec[0:3], spec[3:6], spec[6:9])
for i, (mode, set_bit) in enumerate(zip(pieces, set_bits)):
mask = 0
read, write, exe = list(mode)
if read == 'r':
mask = mask | 4
elif read != '-':
raise ValueError, (
"Character %r unexpected (should be '-' or 'r')"
% read)
if write == 'w':
mask = mask | 2
elif write != '-':
raise ValueError, (
"Character %r unexpected (should be '-' or 'w')"
% write)
if exe == 'x':
mask = mask | 1
elif exe not in ('s', '-'):
raise ValueError, (
"Character %r unexpected (should be '-', 'x', or 's')"
% exe)
if exe == 's' and i == 2:
raise ValueError, (
"The 'other' executable setting cannot be suid/sgid ('s')")
mask = mask << ((2-i)*3)
if exe == 's':
mask = mask | set_bit
total_mask = total_mask | mask
return total_mask
modes = [
(04000, 'setuid bit',
'setuid bit: make contents owned by directory owner'),
(02000, 'setgid bit',
'setgid bit: make contents inherit permissions from directory'),
(01000, 'sticky bit',
'sticky bit: append-only directory'),
(00400, 'read by owner', 'read by owner'),
(00200, 'write by owner', 'write by owner'),
(00100, 'execute by owner', 'owner can search directory'),
(00040, 'allow read by group members',
'allow read by group members',),
(00020, 'allow write by group members',
'allow write by group members'),
(00010, 'execute by group members',
'group members can search directory'),
(00004, 'read by others', 'read by others'),
(00002, 'write by others', 'write by others'),
(00001, 'execution by others', 'others can search directory'),
]
exe_bits = [0100, 0010, 0001]
exe_mask = 0111
full_mask = 07777
def mode_diff(filename, mode, **kw):
"""
Returns the differences calculated using ``calc_mode_diff``
"""
cur_mode = os.stat(filename).st_mode
return calc_mode_diff(cur_mode, mode, **kw)
def calc_mode_diff(cur_mode, mode, keep_exe=True,
not_set='not set: ',
set='set: '):
"""
Gives the difference between the actual mode of the file and the
given mode. If ``keep_exe`` is true, then if the mode doesn't
include any executable information the executable information will
simply be ignored. High bits are also always ignored (except
suid/sgid and sticky bit).
Returns a list of differences (empty list if no differences)
"""
for exe_bit in exe_bits:
if mode & exe_bit:
keep_exe = False
diffs = []
isdir = os.path.isdir(filename)
for bit, file_desc, dir_desc in modes:
if keep_exe and bit in exe_bits:
continue
if isdir:
desc = dir_desc
else:
desc = file_desc
if (mode & bit) and not (cur_mode & bit):
diffs.append(not_set + desc)
if not (mode & bit) and (cur_mode & bit):
diffs.append(set + desc)
return diffs
def calc_set_mode(cur_mode, mode, keep_exe=True):
"""
Calculates the new mode given the current node ``cur_mode`` and
the mode spec ``mode`` and if ``keep_exe`` is true then also keep
the executable bits in ``cur_mode`` if ``mode`` has no executable
bits in it. Return the new mode.
Examples::
>>> print oct(calc_set_mode(0775, 0644))
0755
>>> print oct(calc_set_mode(0775, 0744))
0744
>>> print oct(calc_set_mode(010600, 0644))
010644
>>> print oct(calc_set_mode(0775, 0644, False))
0644
"""
for exe_bit in exe_bits:
if mode & exe_bit:
keep_exe = False
# This zeros-out full_mask parts of the current mode:
keep_parts = (cur_mode | full_mask) ^ full_mask
if keep_exe:
keep_parts = keep_parts | (cur_mode & exe_mask)
new_mode = keep_parts | mode
return new_mode
def set_mode(filename, mode, **kw):
"""
Sets the mode on ``filename`` using ``calc_set_mode``
"""
cur_mode = os.stat(filename).st_mode
new_mode = calc_set_mode(cur_mode, mode, **kw)
os.chmod(filename, new_mode)
def calc_ownership_spec(spec):
"""
Calculates what a string spec means, returning (uid, username,
gid, groupname), where there can be None values meaning no
preference.
The spec is a string like ``owner:group``. It may use numbers
instead of user/group names. It may leave out ``:group``. It may
use '-' to mean any-user/any-group.
"""
import grp
import pwd
user = group = None
uid = gid = None
if ':' in spec:
user_spec, group_spec = spec.split(':', 1)
else:
user_spec, group_spec = spec, '-'
if user_spec == '-':
user_spec = '0'
if group_spec == '-':
group_spec = '0'
try:
uid = int(user_spec)
except ValueError:
uid = pwd.getpwnam(user_spec)
user = user_spec
else:
if not uid:
uid = user = None
else:
user = pwd.getpwuid(uid).pw_name
try:
gid = int(group_spec)
except ValueError:
gid = grp.getgrnam(group_spec)
group = group_spec
else:
if not gid:
gid = group = None
else:
group = grp.getgrgid(gid).gr_name
return (uid, user, gid, group)
def ownership_diff(filename, spec):
"""
Return a list of differences between the ownership of ``filename``
and the spec given.
"""
import grp
import pwd
diffs = []
uid, user, gid, group = calc_ownership_spec(spec)
st = os.stat(filename)
if uid and uid != st.st_uid:
diffs.append('owned by %s (should be %s)' %
(pwd.getpwuid(st.st_uid).pw_name, user))
if gid and gid != st.st_gid:
diffs.append('group %s (should be %s)' %
(grp.getgrgid(st.st_gid).gr_name, group))
return diffs
def set_ownership(filename, spec):
"""
Set the ownership of ``filename`` given the spec.
"""
uid, user, gid, group = calc_ownership_spec(spec)
st = os.stat(filename)
if not uid:
uid = st.st_uid
if not gid:
gid = st.st_gid
os.chmod(filename, uid, gid)
class PermissionSpec(object):
"""
Represents a set of specifications for permissions.
Typically reads from a file that looks like this::
rwxrwxrwx user:group filename
If the filename ends in /, then it expected to be a directory, and
the directory is made executable automatically, and the contents
of the directory are given the same permission (recursively). By
default the executable bit on files is left as-is, unless the
permissions specifically say it should be on in some way.
You can use 'nomodify filename' for permissions to say that any
permission is okay, and permissions should not be changed.
Use 'noexist filename' to say that a specific file should not
exist.
Use 'symlink filename symlinked_to' to assert a symlink destination
The entire file is read, and most specific rules are used for each
file (i.e., a rule for a subdirectory overrides the rule for a
superdirectory). Order does not matter.
"""
def __init__(self):
self.paths = {}
def parsefile(self, filename):
f = open(filename)
lines = f.readlines()
f.close()
self.parselines(lines, filename=filename)
commands = {}
def parselines(self, lines, filename=None):
for lineindex, line in enumerate(lines):
line = line.strip()
if not line or line.startswith('#'):
continue
parts = line.split()
command = parts[0]
if command in self.commands:
cmd = self.commands[command](*parts[1:])
else:
cmd = self.commands['*'](*parts)
self.paths[cmd.path] = cmd
def check(self):
action = _Check(self)
self.traverse(action)
def fix(self):
action = _Fixer(self)
self.traverse(action)
def traverse(self, action):
paths = self.paths_sorted()
checked = {}
for path, checker in list(paths)[::-1]:
self.check_tree(action, path, paths, checked)
for path, checker in paths:
if path not in checked:
action.noexists(path, checker)
def traverse_tree(self, action, path, paths, checked):
if path in checked:
return
self.traverse_path(action, path, paths, checked)
if os.path.isdir(path):
for fn in os.listdir(path):
fn = os.path.join(path, fn)
self.traverse_tree(action, fn, paths, checked)
def traverse_path(self, action, path, paths, checked):
checked[path] = None
for check_path, checker in paths:
if path.startswith(check_path):
action.check(check_path, checker)
if not checker.inherit:
break
def paths_sorted(self):
paths = self.paths.items()
paths.sort(lambda a, b: -cmp(len(a[0]), len(b[0])))
class _Rule(object):
class __metaclass__(type):
def __new__(meta, class_name, bases, d):
cls = type.__new__(meta, class_name, bases, d)
PermissionSpec.commands[cls.__name__] = cls
return cls
inherit = False
def noexists(self):
return ['Path %s does not exist' % path]
class _NoModify(_Rule):
name = 'nomodify'
def __init__(self, path):
self.path = path
def fix(self, path):
pass
class _NoExist(_Rule):
name = 'noexist'
def __init__(self, path):
self.path = path
def check(self, path):
return ['Path %s should not exist' % path]
def noexists(self, path):
return []
def fix(self, path):
# @@: Should delete?
pass
class _SymLink(_Rule):
name = 'symlink'
inherit = True
def __init__(self, path, dest):
self.path = path
self.dest = dest
def check(self, path):
assert path == self.path, (
"_Symlink should only be passed specific path %s (not %s)"
% (self.path, path))
try:
link = os.path.readlink(path)
except OSError:
if e.errno != 22:
raise
return ['Path %s is not a symlink (should point to %s)'
% (path, self.dest)]
if link != self.dest:
return ['Path %s should symlink to %s, not %s'
% (path, self.dest, link)]
return []
def fix(self, path):
assert path == self.path, (
"_Symlink should only be passed specific path %s (not %s)"
% (self.path, path))
if not os.path.exists(path):
os.symlink(path, self.dest)
else:
# @@: This should correct the symlink or something:
print 'Not symlinking %s' % path
class _Permission(_Rule):
name = '*'
def __init__(self, perm, owner, dir):
self.perm_spec = read_perm_spec(perm)
self.owner = owner
self.dir = dir
def check(self, path):
return mode_diff(path, self.perm_spec)
def fix(self, path):
set_mode(path, self.perm_spec)
class _Strategy(object):
def __init__(self, spec):
self.spec = spec
class _Check(_Strategy):
def noexists(self, path, checker):
checker.noexists(path)
def check(self, path, checker):
checker.check(path)
class _Fixer(_Strategy):
def noexists(self, path, checker):
pass
def check(self, path, checker):
checker.fix(path)
if __name__ == '__main__':
import doctest
doctest.testmod()

View File

@@ -0,0 +1,101 @@
"""
Entry point for CherryPy's WSGI server
"""
import paste.script.wsgiserver as wsgiserver
def cpwsgi_server(app, global_conf=None, host='127.0.0.1', port=None,
ssl_pem=None, protocol_version=None, numthreads=None,
server_name=None, max=None, request_queue_size=None,
timeout=None):
"""
Serves the specified WSGI app via CherryPyWSGIServer.
``app``
The WSGI 'application callable'; multiple WSGI applications
may be passed as (script_name, callable) pairs.
``host``
This is the ipaddress to bind to (or a hostname if your
nameserver is properly configured). This defaults to
127.0.0.1, which is not a public interface.
``port``
The port to run on, defaults to 8080 for HTTP, or 4443 for
HTTPS. This can be a string or an integer value.
``ssl_pem``
This an optional SSL certificate file (via OpenSSL) You can
generate a self-signed test PEM certificate file as follows:
$ openssl genrsa 1024 > host.key
$ chmod 400 host.key
$ openssl req -new -x509 -nodes -sha1 -days 365 \\
-key host.key > host.cert
$ cat host.cert host.key > host.pem
$ chmod 400 host.pem
``protocol_version``
The protocol used by the server, by default ``HTTP/1.1``.
``numthreads``
The number of worker threads to create.
``server_name``
The string to set for WSGI's SERVER_NAME environ entry.
``max``
The maximum number of queued requests. (defaults to -1 = no
limit).
``request_queue_size``
The 'backlog' argument to socket.listen(); specifies the
maximum number of queued connections.
``timeout``
The timeout in seconds for accepted connections.
"""
is_ssl = False
if ssl_pem:
port = port or 4443
is_ssl = True
if not port:
if ':' in host:
host, port = host.split(':', 1)
else:
port = 8080
bind_addr = (host, int(port))
kwargs = {}
for var_name in ('numthreads', 'max', 'request_queue_size', 'timeout'):
var = locals()[var_name]
if var is not None:
kwargs[var_name] = int(var)
server = wsgiserver.CherryPyWSGIServer(bind_addr, app,
server_name=server_name, **kwargs)
server.ssl_certificate = server.ssl_private_key = ssl_pem
if protocol_version:
server.protocol = protocol_version
try:
protocol = is_ssl and 'https' or 'http'
if host == '0.0.0.0':
print 'serving on 0.0.0.0:%s view at %s://127.0.0.1:%s' % \
(port, protocol, port)
else:
print "serving on %s://%s:%s" % (protocol, host, port)
server.start()
except (KeyboardInterrupt, SystemExit):
server.stop()
return server

View File

@@ -0,0 +1,818 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
import pkg_resources
import sys
import optparse
import bool_optparse
import os
import re
import textwrap
import pluginlib
import ConfigParser
import getpass
try:
import subprocess
except ImportError:
try:
from paste.script.util import subprocess24 as subprocess
except ImportError:
subprocess = None # jython
difflib = None
if sys.version_info >= (2, 6):
from logging.config import fileConfig
else:
# Use our custom fileConfig -- 2.5.1's with a custom Formatter class
# and less strict whitespace (which were incorporated into 2.6's)
from paste.script.util.logging_config import fileConfig
class BadCommand(Exception):
def __init__(self, message, exit_code=2):
self.message = message
self.exit_code = exit_code
Exception.__init__(self, message)
def _get_message(self):
"""Getter for 'message'; needed only to override deprecation
in BaseException."""
return self.__message
def _set_message(self, value):
"""Setter for 'message'; needed only to override deprecation
in BaseException."""
self.__message = value
# BaseException.message has been deprecated since Python 2.6.
# To prevent DeprecationWarning from popping up over this
# pre-existing attribute, use a new property that takes lookup
# precedence.
message = property(_get_message, _set_message)
class NoDefault(object):
pass
dist = pkg_resources.get_distribution('PasteScript')
python_version = sys.version.splitlines()[0].strip()
parser = optparse.OptionParser(add_help_option=False,
version='%s from %s (python %s)'
% (dist, dist.location, python_version),
usage='%prog [paster_options] COMMAND [command_options]')
parser.add_option(
'--plugin',
action='append',
dest='plugins',
help="Add a plugin to the list of commands (plugins are Egg specs; will also require() the Egg)")
parser.add_option(
'-h', '--help',
action='store_true',
dest='do_help',
help="Show this help message")
parser.disable_interspersed_args()
# @@: Add an option to run this in another Python interpreter
system_plugins = []
def run(args=None):
if (not args and
len(sys.argv) >= 2
and os.environ.get('_') and sys.argv[0] != os.environ['_']
and os.environ['_'] == sys.argv[1]):
# probably it's an exe execution
args = ['exe', os.environ['_']] + sys.argv[2:]
if args is None:
args = sys.argv[1:]
options, args = parser.parse_args(args)
options.base_parser = parser
system_plugins.extend(options.plugins or [])
commands = get_commands()
if options.do_help:
args = ['help'] + args
if not args:
print 'Usage: %s COMMAND' % sys.argv[0]
args = ['help']
command_name = args[0]
if command_name not in commands:
command = NotFoundCommand
else:
command = commands[command_name].load()
invoke(command, command_name, options, args[1:])
def parse_exe_file(config):
import shlex
p = ConfigParser.RawConfigParser()
p.read([config])
command_name = 'exe'
options = []
if p.has_option('exe', 'command'):
command_name = p.get('exe', 'command')
if p.has_option('exe', 'options'):
options = shlex.split(p.get('exe', 'options'))
if p.has_option('exe', 'sys.path'):
paths = shlex.split(p.get('exe', 'sys.path'))
paths = [os.path.abspath(os.path.join(os.path.dirname(config), p))
for p in paths]
for path in paths:
pkg_resources.working_set.add_entry(path)
sys.path.insert(0, path)
args = [command_name, config] + options
return args
def get_commands():
plugins = system_plugins[:]
egg_info_dir = pluginlib.find_egg_info_dir(os.getcwd())
if egg_info_dir:
plugins.append(os.path.splitext(os.path.basename(egg_info_dir))[0])
base_dir = os.path.dirname(egg_info_dir)
if base_dir not in sys.path:
sys.path.insert(0, base_dir)
pkg_resources.working_set.add_entry(base_dir)
plugins = pluginlib.resolve_plugins(plugins)
commands = pluginlib.load_commands_from_plugins(plugins)
commands.update(pluginlib.load_global_commands())
return commands
def invoke(command, command_name, options, args):
try:
runner = command(command_name)
exit_code = runner.run(args)
except BadCommand, e:
print e.message
exit_code = e.exit_code
sys.exit(exit_code)
class Command(object):
def __init__(self, name):
self.command_name = name
max_args = None
max_args_error = 'You must provide no more than %(max_args)s arguments'
min_args = None
min_args_error = 'You must provide at least %(min_args)s arguments'
required_args = None
# If this command takes a configuration file, set this to 1 or -1
# Then if invoked through #! the config file will be put into the positional
# arguments -- at the beginning with 1, at the end with -1
takes_config_file = None
# Grouped in help messages by this:
group_name = ''
required_args = ()
description = None
usage = ''
hidden = False
# This is the default verbosity level; --quiet subtracts,
# --verbose adds:
default_verbosity = 0
# This is the default interactive state:
default_interactive = 0
return_code = 0
BadCommand = BadCommand
# Must define:
# parser
# summary
# command()
def run(self, args):
self.parse_args(args)
# Setup defaults:
for name, default in [('verbose', 0),
('quiet', 0),
('interactive', False),
('overwrite', False)]:
if not hasattr(self.options, name):
setattr(self.options, name, default)
if getattr(self.options, 'simulate', False):
self.options.verbose = max(self.options.verbose, 1)
self.interactive = self.default_interactive
if getattr(self.options, 'interactive', False):
self.interactive += self.options.interactive
if getattr(self.options, 'no_interactive', False):
self.interactive = False
self.verbose = self.default_verbosity
self.verbose += self.options.verbose
self.verbose -= self.options.quiet
self.simulate = getattr(self.options, 'simulate', False)
# For #! situations:
if (os.environ.get('PASTE_CONFIG_FILE')
and self.takes_config_file is not None):
take = self.takes_config_file
filename = os.environ.get('PASTE_CONFIG_FILE')
if take == 1:
self.args.insert(0, filename)
elif take == -1:
self.args.append(filename)
else:
assert 0, (
"Value takes_config_file must be None, 1, or -1 (not %r)"
% take)
if (os.environ.get('PASTE_DEFAULT_QUIET')):
self.verbose = 0
# Validate:
if self.min_args is not None and len(self.args) < self.min_args:
raise BadCommand(
self.min_args_error % {'min_args': self.min_args,
'actual_args': len(self.args)})
if self.max_args is not None and len(self.args) > self.max_args:
raise BadCommand(
self.max_args_error % {'max_args': self.max_args,
'actual_args': len(self.args)})
for var_name, option_name in self.required_args:
if not getattr(self.options, var_name, None):
raise BadCommand(
'You must provide the option %s' % option_name)
result = self.command()
if result is None:
return self.return_code
else:
return result
def parse_args(self, args):
if self.usage:
usage = ' '+self.usage
else:
usage = ''
self.parser.usage = "%%prog [options]%s\n%s" % (
usage, self.summary)
self.parser.prog = self._prog_name()
if self.description:
desc = self.description
desc = textwrap.dedent(desc)
self.parser.description = desc
self.options, self.args = self.parser.parse_args(args)
def _prog_name(self):
return '%s %s' % (os.path.basename(sys.argv[0]), self.command_name)
########################################
## Utility methods
########################################
def here(cls):
mod = sys.modules[cls.__module__]
return os.path.dirname(mod.__file__)
here = classmethod(here)
def ask(self, prompt, safe=False, default=True):
"""
Prompt the user. Default can be true, false, ``'careful'`` or
``'none'``. If ``'none'`` then the user must enter y/n. If
``'careful'`` then the user must enter yes/no (long form).
If the interactive option is over two (``-ii``) then ``safe``
will be used as a default. This option should be the
do-nothing option.
"""
# @@: Should careful be a separate argument?
if self.options.interactive >= 2:
default = safe
if default == 'careful':
prompt += ' [yes/no]?'
elif default == 'none':
prompt += ' [y/n]?'
elif default:
prompt += ' [Y/n]? '
else:
prompt += ' [y/N]? '
while 1:
response = raw_input(prompt).strip().lower()
if not response:
if default in ('careful', 'none'):
print 'Please enter yes or no'
continue
return default
if default == 'careful':
if response in ('yes', 'no'):
return response == 'yes'
print 'Please enter "yes" or "no"'
continue
if response[0].lower() in ('y', 'n'):
return response[0].lower() == 'y'
print 'Y or N please'
def challenge(self, prompt, default=NoDefault, should_echo=True):
"""
Prompt the user for a variable.
"""
if default is not NoDefault:
prompt += ' [%r]' % default
prompt += ': '
while 1:
if should_echo:
prompt_method = raw_input
else:
prompt_method = getpass.getpass
response = prompt_method(prompt).strip()
if not response:
if default is not NoDefault:
return default
else:
continue
else:
return response
def pad(self, s, length, dir='left'):
if len(s) >= length:
return s
if dir == 'left':
return s + ' '*(length-len(s))
else:
return ' '*(length-len(s)) + s
def standard_parser(cls, verbose=True,
interactive=False,
no_interactive=False,
simulate=False,
quiet=False,
overwrite=False):
"""
Create a standard ``OptionParser`` instance.
Typically used like::
class MyCommand(Command):
parser = Command.standard_parser()
Subclasses may redefine ``standard_parser``, so use the
nearest superclass's class method.
"""
parser = bool_optparse.BoolOptionParser()
if verbose:
parser.add_option('-v', '--verbose',
action='count',
dest='verbose',
default=0)
if quiet:
parser.add_option('-q', '--quiet',
action='count',
dest='quiet',
default=0)
if no_interactive:
parser.add_option('--no-interactive',
action="count",
dest="no_interactive",
default=0)
if interactive:
parser.add_option('-i', '--interactive',
action='count',
dest='interactive',
default=0)
if simulate:
parser.add_option('-n', '--simulate',
action='store_true',
dest='simulate',
default=False)
if overwrite:
parser.add_option('-f', '--overwrite',
dest="overwrite",
action="store_true",
help="Overwrite files (warnings will be emitted for non-matching files otherwise)")
return parser
standard_parser = classmethod(standard_parser)
def shorten(self, fn, *paths):
"""
Return a shorted form of the filename (relative to the current
directory), typically for displaying in messages. If
``*paths`` are present, then use os.path.join to create the
full filename before shortening.
"""
if paths:
fn = os.path.join(fn, *paths)
if fn.startswith(os.getcwd()):
return fn[len(os.getcwd()):].lstrip(os.path.sep)
else:
return fn
def ensure_dir(self, dir, svn_add=True):
"""
Ensure that the directory exists, creating it if necessary.
Respects verbosity and simulation.
Adds directory to subversion if ``.svn/`` directory exists in
parent, and directory was created.
"""
dir = dir.rstrip(os.sep)
if not dir:
# we either reached the parent-most directory, or we got
# a relative directory
# @@: Should we make sure we resolve relative directories
# first? Though presumably the current directory always
# exists.
return
if not os.path.exists(dir):
self.ensure_dir(os.path.dirname(dir))
if self.verbose:
print 'Creating %s' % self.shorten(dir)
if not self.simulate:
os.mkdir(dir)
if (svn_add and
os.path.exists(os.path.join(os.path.dirname(dir), '.svn'))):
self.svn_command('add', dir)
else:
if self.verbose > 1:
print "Directory already exists: %s" % self.shorten(dir)
def ensure_file(self, filename, content, svn_add=True):
"""
Ensure a file named ``filename`` exists with the given
content. If ``--interactive`` has been enabled, this will ask
the user what to do if a file exists with different content.
"""
global difflib
assert content is not None, (
"You cannot pass a content of None")
self.ensure_dir(os.path.dirname(filename), svn_add=svn_add)
if not os.path.exists(filename):
if self.verbose:
print 'Creating %s' % filename
if not self.simulate:
f = open(filename, 'wb')
f.write(content)
f.close()
if svn_add and os.path.exists(os.path.join(os.path.dirname(filename), '.svn')):
self.svn_command('add', filename,
warn_returncode=True)
return
f = open(filename, 'rb')
old_content = f.read()
f.close()
if content == old_content:
if self.verbose > 1:
print 'File %s matches expected content' % filename
return
if not self.options.overwrite:
print 'Warning: file %s does not match expected content' % filename
if difflib is None:
import difflib
diff = difflib.context_diff(
content.splitlines(),
old_content.splitlines(),
'expected ' + filename,
filename)
print '\n'.join(diff)
if self.interactive:
while 1:
s = raw_input(
'Overwrite file with new content? [y/N] ').strip().lower()
if not s:
s = 'n'
if s.startswith('y'):
break
if s.startswith('n'):
return
print 'Unknown response; Y or N please'
else:
return
if self.verbose:
print 'Overwriting %s with new content' % filename
if not self.simulate:
f = open(filename, 'wb')
f.write(content)
f.close()
def insert_into_file(self, filename, marker_name, text,
indent=False):
"""
Inserts ``text`` into the file, right after the given marker.
Markers look like: ``-*- <marker_name>[:]? -*-``, and the text
will go on the immediately following line.
Raises ``ValueError`` if the marker is not found.
If ``indent`` is true, then the text will be indented at the
same level as the marker.
"""
if not text.endswith('\n'):
raise ValueError(
"The text must end with a newline: %r" % text)
if not os.path.exists(filename) and self.simulate:
# If we are doing a simulation, it's expected that some
# files won't exist...
if self.verbose:
print 'Would (if not simulating) insert text into %s' % (
self.shorten(filename))
return
f = open(filename)
lines = f.readlines()
f.close()
regex = re.compile(r'-\*-\s+%s:?\s+-\*-' % re.escape(marker_name),
re.I)
for i in range(len(lines)):
if regex.search(lines[i]):
# Found it!
if (lines[i:] and len(lines[i:]) > 1 and
''.join(lines[i+1:]).strip().startswith(text.strip())):
# Already have it!
print 'Warning: line already found in %s (not inserting' % filename
print ' %s' % lines[i]
return
if indent:
text = text.lstrip()
match = re.search(r'^[ \t]*', lines[i])
text = match.group(0) + text
lines[i+1:i+1] = [text]
break
else:
errstr = (
"Marker '-*- %s -*-' not found in %s"
% (marker_name, filename))
if 1 or self.simulate: # @@: being permissive right now
print 'Warning: %s' % errstr
else:
raise ValueError(errstr)
if self.verbose:
print 'Updating %s' % self.shorten(filename)
if not self.simulate:
f = open(filename, 'w')
f.write(''.join(lines))
f.close()
def run_command(self, cmd, *args, **kw):
"""
Runs the command, respecting verbosity and simulation.
Returns stdout, or None if simulating.
Keyword arguments:
cwd:
the current working directory to run the command in
capture_stderr:
if true, then both stdout and stderr will be returned
expect_returncode:
if true, then don't fail if the return code is not 0
force_no_simulate:
if true, run the command even if --simulate
"""
if subprocess is None:
raise RuntimeError('Environment does not support subprocess '
'module, cannot run command.')
cmd = self.quote_first_command_arg(cmd)
cwd = popdefault(kw, 'cwd', os.getcwd())
capture_stderr = popdefault(kw, 'capture_stderr', False)
expect_returncode = popdefault(kw, 'expect_returncode', False)
force = popdefault(kw, 'force_no_simulate', False)
warn_returncode = popdefault(kw, 'warn_returncode', False)
if warn_returncode:
expect_returncode = True
simulate = self.simulate
if force:
simulate = False
assert not kw, ("Arguments not expected: %s" % kw)
if capture_stderr:
stderr_pipe = subprocess.STDOUT
else:
stderr_pipe = subprocess.PIPE
try:
proc = subprocess.Popen([cmd] + list(args),
cwd=cwd,
stderr=stderr_pipe,
stdout=subprocess.PIPE)
except OSError, e:
if e.errno != 2:
# File not found
raise
raise OSError(
"The expected executable %s was not found (%s)"
% (cmd, e))
if self.verbose:
print 'Running %s %s' % (cmd, ' '.join(args))
if simulate:
return None
stdout, stderr = proc.communicate()
if proc.returncode and not expect_returncode:
if not self.verbose:
print 'Running %s %s' % (cmd, ' '.join(args))
print 'Error (exit code: %s)' % proc.returncode
if stderr:
print stderr
raise OSError("Error executing command %s" % cmd)
if self.verbose > 2:
if stderr:
print 'Command error output:'
print stderr
if stdout:
print 'Command output:'
print stdout
elif proc.returncode and warn_returncode:
print 'Warning: command failed (%s %s)' % (cmd, ' '.join(args))
print 'Exited with code %s' % proc.returncode
return stdout
def quote_first_command_arg(self, arg):
"""
There's a bug in Windows when running an executable that's
located inside a path with a space in it. This method handles
that case, or on non-Windows systems or an executable with no
spaces, it just leaves well enough alone.
"""
if (sys.platform != 'win32'
or ' ' not in arg):
# Problem does not apply:
return arg
try:
import win32api
except ImportError:
raise ValueError(
"The executable %r contains a space, and in order to "
"handle this issue you must have the win32api module "
"installed" % arg)
arg = win32api.GetShortPathName(arg)
return arg
_svn_failed = False
def svn_command(self, *args, **kw):
"""
Run an svn command, but don't raise an exception if it fails.
"""
try:
return self.run_command('svn', *args, **kw)
except OSError, e:
if not self._svn_failed:
print 'Unable to run svn command (%s); proceeding anyway' % e
self._svn_failed = True
def write_file(self, filename, content, source=None,
binary=True, svn_add=True):
"""
Like ``ensure_file``, but without the interactivity. Mostly
deprecated. (I think I forgot it existed)
"""
import warnings
warnings.warn(
"command.write_file has been replaced with "
"command.ensure_file",
DeprecationWarning, 2)
if os.path.exists(filename):
if binary:
f = open(filename, 'rb')
else:
f = open(filename, 'r')
old_content = f.read()
f.close()
if content == old_content:
if self.verbose:
print 'File %s exists with same content' % (
self.shorten(filename))
return
if (not self.simulate and self.options.interactive):
if not self.ask('Overwrite file %s?' % filename):
return
if self.verbose > 1 and source:
print 'Writing %s from %s' % (self.shorten(filename),
self.shorten(source))
elif self.verbose:
print 'Writing %s' % self.shorten(filename)
if not self.simulate:
already_existed = os.path.exists(filename)
if binary:
f = open(filename, 'wb')
else:
f = open(filename, 'w')
f.write(content)
f.close()
if (not already_existed
and svn_add
and os.path.exists(os.path.join(os.path.dirname(filename), '.svn'))):
self.svn_command('add', filename)
def parse_vars(self, args):
"""
Given variables like ``['a=b', 'c=d']`` turns it into ``{'a':
'b', 'c': 'd'}``
"""
result = {}
for arg in args:
if '=' not in arg:
raise BadCommand(
'Variable assignment %r invalid (no "=")'
% arg)
name, value = arg.split('=', 1)
result[name] = value
return result
def read_vars(self, config, section='pastescript'):
"""
Given a configuration filename, this will return a map of values.
"""
result = {}
p = ConfigParser.RawConfigParser()
p.read([config])
if p.has_section(section):
for key, value in p.items(section):
if key.endswith('__eval__'):
result[key[:-len('__eval__')]] = eval(value)
else:
result[key] = value
return result
def write_vars(self, config, vars, section='pastescript'):
"""
Given a configuration filename, this will add items in the
vars mapping to the configuration file. Will create the
configuration file if it doesn't exist.
"""
modified = False
p = ConfigParser.RawConfigParser()
if not os.path.exists(config):
f = open(config, 'w')
f.write('')
f.close()
modified = True
p.read([config])
if not p.has_section(section):
p.add_section(section)
modified = True
existing_options = p.options(section)
for key, value in vars.items():
if (key not in existing_options and
'%s__eval__' % key not in existing_options):
if not isinstance(value, str):
p.set(section, '%s__eval__' % key, repr(value))
else:
p.set(section, key, value)
modified = True
if modified:
p.write(open(config, 'w'))
def indent_block(self, text, indent=2, initial=None):
"""
Indent the block of text (each line is indented). If you give
``initial``, then that is used in lieue of ``indent`` for the
first line.
"""
if initial is None:
initial = indent
lines = text.splitlines()
first = (' '*initial) + lines[0]
rest = [(' '*indent)+l for l in lines[1:]]
return '\n'.join([first]+rest)
def logging_file_config(self, config_file):
"""
Setup logging via the logging module's fileConfig function with the
specified ``config_file``, if applicable.
ConfigParser defaults are specified for the special ``__file__``
and ``here`` variables, similar to PasteDeploy config loading.
"""
parser = ConfigParser.ConfigParser()
parser.read([config_file])
if parser.has_section('loggers'):
config_file = os.path.abspath(config_file)
fileConfig(config_file, dict(__file__=config_file,
here=os.path.dirname(config_file)))
class NotFoundCommand(Command):
def run(self, args):
#for name, value in os.environ.items():
# print '%s: %s' % (name, value)
#print sys.argv
print ('Command %r not known (you may need to run setup.py egg_info)'
% self.command_name)
commands = get_commands().items()
commands.sort()
if not commands:
print 'No commands registered.'
print 'Have you installed Paste Script?'
print '(try running python setup.py develop)'
return 2
print 'Known commands:'
longest = max([len(n) for n, c in commands])
for name, command in commands:
print ' %s %s' % (self.pad(name, length=longest),
command.load().summary)
return 2
def popdefault(dict, name, default=None):
if name not in dict:
return default
else:
v = dict[name]
del dict[name]
return v

View File

@@ -0,0 +1,438 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
import os
import pkg_resources
import sys
if sys.version_info < (2, 4):
from paste.script.util import string24 as string
else:
import string
import cgi
import urllib
import re
Cheetah = None
try:
import subprocess
except ImportError:
try:
from paste.script.util import subprocess24 as subprocess
except ImportError:
subprocess = None # jython
import inspect
class SkipTemplate(Exception):
"""
Raised to indicate that the template should not be copied over.
Raise this exception during the substitution of your template
"""
def copy_dir(source, dest, vars, verbosity, simulate, indent=0,
use_cheetah=False, sub_vars=True, interactive=False,
svn_add=True, overwrite=True, template_renderer=None):
"""
Copies the ``source`` directory to the ``dest`` directory.
``vars``: A dictionary of variables to use in any substitutions.
``verbosity``: Higher numbers will show more about what is happening.
``simulate``: If true, then don't actually *do* anything.
``indent``: Indent any messages by this amount.
``sub_vars``: If true, variables in ``_tmpl`` files and ``+var+``
in filenames will be substituted.
``use_cheetah``: If true, then any templates encountered will be
substituted with Cheetah. Otherwise ``template_renderer`` or
``string.Template`` will be used for templates.
``svn_add``: If true, any files written out in directories with
``.svn/`` directories will be added (via ``svn add``).
``overwrite``: If false, then don't every overwrite anything.
``interactive``: If you are overwriting a file and interactive is
true, then ask before overwriting.
``template_renderer``: This is a function for rendering templates
(if you don't want to use Cheetah or string.Template). It should
have the signature ``template_renderer(content_as_string,
vars_as_dict, filename=filename)``.
"""
# This allows you to use a leading +dot+ in filenames which would
# otherwise be skipped because leading dots make the file hidden:
vars.setdefault('dot', '.')
vars.setdefault('plus', '+')
use_pkg_resources = isinstance(source, tuple)
if use_pkg_resources:
names = pkg_resources.resource_listdir(source[0], source[1])
else:
names = os.listdir(source)
names.sort()
pad = ' '*(indent*2)
if not os.path.exists(dest):
if verbosity >= 1:
print '%sCreating %s/' % (pad, dest)
if not simulate:
svn_makedirs(dest, svn_add=svn_add, verbosity=verbosity,
pad=pad)
elif verbosity >= 2:
print '%sDirectory %s exists' % (pad, dest)
for name in names:
if use_pkg_resources:
full = '/'.join([source[1], name])
else:
full = os.path.join(source, name)
reason = should_skip_file(name)
if reason:
if verbosity >= 2:
reason = pad + reason % {'filename': full}
print reason
continue
if sub_vars:
dest_full = os.path.join(dest, substitute_filename(name, vars))
sub_file = False
if dest_full.endswith('_tmpl'):
dest_full = dest_full[:-5]
sub_file = sub_vars
if use_pkg_resources and pkg_resources.resource_isdir(source[0], full):
if verbosity:
print '%sRecursing into %s' % (pad, os.path.basename(full))
copy_dir((source[0], full), dest_full, vars, verbosity, simulate,
indent=indent+1, use_cheetah=use_cheetah,
sub_vars=sub_vars, interactive=interactive,
svn_add=svn_add, template_renderer=template_renderer)
continue
elif not use_pkg_resources and os.path.isdir(full):
if verbosity:
print '%sRecursing into %s' % (pad, os.path.basename(full))
copy_dir(full, dest_full, vars, verbosity, simulate,
indent=indent+1, use_cheetah=use_cheetah,
sub_vars=sub_vars, interactive=interactive,
svn_add=svn_add, template_renderer=template_renderer)
continue
elif use_pkg_resources:
content = pkg_resources.resource_string(source[0], full)
else:
f = open(full, 'rb')
content = f.read()
f.close()
if sub_file:
try:
content = substitute_content(content, vars, filename=full,
use_cheetah=use_cheetah,
template_renderer=template_renderer)
except SkipTemplate:
continue
if content is None:
continue
already_exists = os.path.exists(dest_full)
if already_exists:
f = open(dest_full, 'rb')
old_content = f.read()
f.close()
if old_content == content:
if verbosity:
print '%s%s already exists (same content)' % (pad, dest_full)
continue
if interactive:
if not query_interactive(
full, dest_full, content, old_content,
simulate=simulate):
continue
elif not overwrite:
continue
if verbosity and use_pkg_resources:
print '%sCopying %s to %s' % (pad, full, dest_full)
elif verbosity:
print '%sCopying %s to %s' % (pad, os.path.basename(full), dest_full)
if not simulate:
f = open(dest_full, 'wb')
f.write(content)
f.close()
if svn_add and not already_exists:
if not os.path.exists(os.path.join(os.path.dirname(os.path.abspath(dest_full)), '.svn')):
if verbosity > 1:
print '%s.svn/ does not exist; cannot add file' % pad
else:
cmd = ['svn', 'add', dest_full]
if verbosity > 1:
print '%sRunning: %s' % (pad, ' '.join(cmd))
if not simulate:
# @@: Should
if subprocess is None:
raise RuntimeError('copydir failed, environment '
'does not support subprocess '
'module')
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
stdout, stderr = proc.communicate()
if verbosity > 1 and stdout:
print 'Script output:'
print stdout
elif svn_add and already_exists and verbosity > 1:
print '%sFile already exists (not doing svn add)' % pad
def should_skip_file(name):
"""
Checks if a file should be skipped based on its name.
If it should be skipped, returns the reason, otherwise returns
None.
"""
if name.startswith('.'):
return 'Skipping hidden file %(filename)s'
if name.endswith('~') or name.endswith('.bak'):
return 'Skipping backup file %(filename)s'
if name.endswith('.pyc') or name.endswith('.pyo'):
return 'Skipping %s file %%(filename)s' % os.path.splitext(name)[1]
if name.endswith('$py.class'):
return 'Skipping $py.class file %(filename)s'
if name in ('CVS', '_darcs'):
return 'Skipping version control directory %(filename)s'
return None
# Overridden on user's request:
all_answer = None
def query_interactive(src_fn, dest_fn, src_content, dest_content,
simulate):
global all_answer
from difflib import unified_diff, context_diff
u_diff = list(unified_diff(
dest_content.splitlines(),
src_content.splitlines(),
dest_fn, src_fn))
c_diff = list(context_diff(
dest_content.splitlines(),
src_content.splitlines(),
dest_fn, src_fn))
added = len([l for l in u_diff if l.startswith('+')
and not l.startswith('+++')])
removed = len([l for l in u_diff if l.startswith('-')
and not l.startswith('---')])
if added > removed:
msg = '; %i lines added' % (added-removed)
elif removed > added:
msg = '; %i lines removed' % (removed-added)
else:
msg = ''
print 'Replace %i bytes with %i bytes (%i/%i lines changed%s)' % (
len(dest_content), len(src_content),
removed, len(dest_content.splitlines()), msg)
prompt = 'Overwrite %s [y/n/d/B/?] ' % dest_fn
while 1:
if all_answer is None:
response = raw_input(prompt).strip().lower()
else:
response = all_answer
if not response or response[0] == 'b':
import shutil
new_dest_fn = dest_fn + '.bak'
n = 0
while os.path.exists(new_dest_fn):
n += 1
new_dest_fn = dest_fn + '.bak' + str(n)
print 'Backing up %s to %s' % (dest_fn, new_dest_fn)
if not simulate:
shutil.copyfile(dest_fn, new_dest_fn)
return True
elif response.startswith('all '):
rest = response[4:].strip()
if not rest or rest[0] not in ('y', 'n', 'b'):
print query_usage
continue
response = all_answer = rest[0]
if response[0] == 'y':
return True
elif response[0] == 'n':
return False
elif response == 'dc':
print '\n'.join(c_diff)
elif response[0] == 'd':
print '\n'.join(u_diff)
else:
print query_usage
query_usage = """\
Responses:
Y(es): Overwrite the file with the new content.
N(o): Do not overwrite the file.
D(iff): Show a unified diff of the proposed changes (dc=context diff)
B(ackup): Save the current file contents to a .bak file
(and overwrite)
Type "all Y/N/B" to use Y/N/B for answer to all future questions
"""
def svn_makedirs(dir, svn_add, verbosity, pad):
parent = os.path.dirname(os.path.abspath(dir))
if not os.path.exists(parent):
svn_makedirs(parent, svn_add, verbosity, pad)
os.mkdir(dir)
if not svn_add:
return
if not os.path.exists(os.path.join(parent, '.svn')):
if verbosity > 1:
print '%s.svn/ does not exist; cannot add directory' % pad
return
cmd = ['svn', 'add', dir]
if verbosity > 1:
print '%sRunning: %s' % (pad, ' '.join(cmd))
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
stdout, stderr = proc.communicate()
if verbosity > 1 and stdout:
print 'Script output:'
print stdout
def substitute_filename(fn, vars):
for var, value in vars.items():
fn = fn.replace('+%s+' % var, str(value))
return fn
def substitute_content(content, vars, filename='<string>',
use_cheetah=False, template_renderer=None):
global Cheetah
v = standard_vars.copy()
v.update(vars)
vars = v
if template_renderer is not None:
return template_renderer(content, vars, filename=filename)
if not use_cheetah:
tmpl = LaxTemplate(content)
try:
return tmpl.substitute(TypeMapper(v))
except Exception, e:
_add_except(e, ' in file %s' % filename)
raise
if Cheetah is None:
import Cheetah.Template
tmpl = Cheetah.Template.Template(source=content,
searchList=[vars])
return careful_sub(tmpl, vars, filename)
def careful_sub(cheetah_template, vars, filename):
"""
Substitutes the template with the variables, using the
.body() method if it exists. It assumes that the variables
were also passed in via the searchList.
"""
if not hasattr(cheetah_template, 'body'):
return sub_catcher(filename, vars, str, cheetah_template)
body = cheetah_template.body
args, varargs, varkw, defaults = inspect.getargspec(body)
call_vars = {}
for arg in args:
if arg in vars:
call_vars[arg] = vars[arg]
return sub_catcher(filename, vars, body, **call_vars)
def sub_catcher(filename, vars, func, *args, **kw):
"""
Run a substitution, returning the value. If an error occurs, show
the filename. If the error is a NameError, show the variables.
"""
try:
return func(*args, **kw)
except SkipTemplate, e:
print 'Skipping file %s' % filename
if str(e):
print str(e)
raise
except Exception, e:
print 'Error in file %s:' % filename
if isinstance(e, NameError):
items = vars.items()
items.sort()
for name, value in items:
print '%s = %r' % (name, value)
raise
def html_quote(s):
if s is None:
return ''
return cgi.escape(str(s), 1)
def url_quote(s):
if s is None:
return ''
return urllib.quote(str(s))
def test(conf, true_cond, false_cond=None):
if conf:
return true_cond
else:
return false_cond
def skip_template(condition=True, *args):
"""
Raise SkipTemplate, which causes copydir to skip the template
being processed. If you pass in a condition, only raise if that
condition is true (allows you to use this with string.Template)
If you pass any additional arguments, they will be used to
instantiate SkipTemplate (generally use like
``skip_template(license=='GPL', 'Skipping file; not using GPL')``)
"""
if condition:
raise SkipTemplate(*args)
def _add_except(exc, info):
if not hasattr(exc, 'args') or exc.args is None:
return
args = list(exc.args)
if args:
args[0] += ' ' + info
else:
args = [info]
exc.args = tuple(args)
return
standard_vars = {
'nothing': None,
'html_quote': html_quote,
'url_quote': url_quote,
'empty': '""',
'test': test,
'repr': repr,
'str': str,
'bool': bool,
'SkipTemplate': SkipTemplate,
'skip_template': skip_template,
}
class TypeMapper(dict):
def __getitem__(self, item):
options = item.split('|')
for op in options[:-1]:
try:
value = eval_with_catch(op, dict(self.items()))
break
except (NameError, KeyError):
pass
else:
value = eval(options[-1], dict(self.items()))
if value is None:
return ''
else:
return str(value)
def eval_with_catch(expr, vars):
try:
return eval(expr, vars)
except Exception, e:
_add_except(e, 'in expression %r' % expr)
raise
class LaxTemplate(string.Template):
# This change of pattern allows for anything in braces, but
# only identifiers outside of braces:
pattern = r"""
\$(?:
(?P<escaped>\$) | # Escape sequence of two delimiters
(?P<named>[_a-z][_a-z0-9]*) | # delimiter and a Python identifier
{(?P<braced>.*?)} | # delimiter and a braced identifier
(?P<invalid>) # Other ill-formed delimiter exprs
)
"""

View File

@@ -0,0 +1,417 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
import re
import sys
import os
import pkg_resources
from command import Command, BadCommand
import copydir
import pluginlib
import fnmatch
try:
set
except NameError:
from sets import Set as set
class CreateDistroCommand(Command):
usage = 'PACKAGE_NAME [VAR=VALUE VAR2=VALUE2 ...]'
summary = "Create the file layout for a Python distribution"
short_description = summary
description = """\
Create a new project. Projects are typically Python packages,
ready for distribution. Projects are created from templates, and
represent different kinds of projects -- associated with a
particular framework for instance.
"""
parser = Command.standard_parser(
simulate=True, no_interactive=True, quiet=True, overwrite=True)
parser.add_option('-t', '--template',
dest='templates',
metavar='TEMPLATE',
action='append',
help="Add a template to the create process")
parser.add_option('-o', '--output-dir',
dest='output_dir',
metavar='DIR',
default='.',
help="Write put the directory into DIR (default current directory)")
parser.add_option('--svn-repository',
dest='svn_repository',
metavar='REPOS',
help="Create package at given repository location (this will create the standard trunk/ tags/ branches/ hierarchy)")
parser.add_option('--list-templates',
dest='list_templates',
action='store_true',
help="List all templates available")
parser.add_option('--list-variables',
dest="list_variables",
action="store_true",
help="List all variables expected by the given template (does not create a package)")
parser.add_option('--inspect-files',
dest='inspect_files',
action='store_true',
help="Show where the files in the given (already created) directory came from (useful when using multiple templates)")
parser.add_option('--config',
action='store',
dest='config',
help="Template variables file")
_bad_chars_re = re.compile('[^a-zA-Z0-9_]')
default_verbosity = 1
default_interactive = 1
def command(self):
if self.options.list_templates:
return self.list_templates()
asked_tmpls = self.options.templates or ['basic_package']
templates = []
for tmpl_name in asked_tmpls:
self.extend_templates(templates, tmpl_name)
if self.options.list_variables:
return self.list_variables(templates)
if self.verbose:
print 'Selected and implied templates:'
max_tmpl_name = max([len(tmpl_name) for tmpl_name, tmpl in templates])
for tmpl_name, tmpl in templates:
print ' %s%s %s' % (
tmpl_name, ' '*(max_tmpl_name-len(tmpl_name)),
tmpl.summary)
print
if not self.args:
if self.interactive:
dist_name = self.challenge('Enter project name')
else:
raise BadCommand('You must provide a PACKAGE_NAME')
else:
dist_name = self.args[0].lstrip(os.path.sep)
templates = [tmpl for name, tmpl in templates]
output_dir = os.path.join(self.options.output_dir, dist_name)
pkg_name = self._bad_chars_re.sub('', dist_name.lower())
vars = {'project': dist_name,
'package': pkg_name,
'egg': pluginlib.egg_name(dist_name),
}
vars.update(self.parse_vars(self.args[1:]))
if self.options.config and os.path.exists(self.options.config):
for key, value in self.read_vars(self.options.config).items():
vars.setdefault(key, value)
if self.verbose: # @@: > 1?
self.display_vars(vars)
if self.options.inspect_files:
self.inspect_files(
output_dir, templates, vars)
return
if not os.path.exists(output_dir):
# We want to avoid asking questions in copydir if the path
# doesn't exist yet
copydir.all_answer = 'y'
if self.options.svn_repository:
self.setup_svn_repository(output_dir, dist_name)
# First we want to make sure all the templates get a chance to
# set their variables, all at once, with the most specialized
# template going first (the last template is the most
# specialized)...
for template in templates[::-1]:
vars = template.check_vars(vars, self)
# Gather all the templates egg_plugins into one var
egg_plugins = set()
for template in templates:
egg_plugins.update(template.egg_plugins)
egg_plugins = list(egg_plugins)
egg_plugins.sort()
vars['egg_plugins'] = egg_plugins
for template in templates:
self.create_template(
template, output_dir, vars)
found_setup_py = False
paster_plugins_mtime = None
if os.path.exists(os.path.join(output_dir, 'setup.py')):
# Grab paster_plugins.txt's mtime; used to determine if the
# egg_info command wrote to it
try:
egg_info_dir = pluginlib.egg_info_dir(output_dir, dist_name)
except IOError:
egg_info_dir = None
if egg_info_dir is not None:
plugins_path = os.path.join(egg_info_dir, 'paster_plugins.txt')
if os.path.exists(plugins_path):
paster_plugins_mtime = os.path.getmtime(plugins_path)
self.run_command(sys.executable, 'setup.py', 'egg_info',
cwd=output_dir,
# This shouldn't be necessary, but a bug in setuptools 0.6c3 is causing a (not entirely fatal) problem that I don't want to fix right now:
expect_returncode=True)
found_setup_py = True
elif self.verbose > 1:
print 'No setup.py (cannot run egg_info)'
package_dir = vars.get('package_dir', None)
if package_dir:
output_dir = os.path.join(output_dir, package_dir)
# With no setup.py this doesn't make sense:
if found_setup_py:
# Only write paster_plugins.txt if it wasn't written by
# egg_info (the correct way). leaving us to do it is
# deprecated and you'll get warned
egg_info_dir = pluginlib.egg_info_dir(output_dir, dist_name)
plugins_path = os.path.join(egg_info_dir, 'paster_plugins.txt')
if len(egg_plugins) and (not os.path.exists(plugins_path) or \
os.path.getmtime(plugins_path) == paster_plugins_mtime):
if self.verbose:
print >> sys.stderr, \
('Manually creating paster_plugins.txt (deprecated! '
'pass a paster_plugins keyword to setup() instead)')
for plugin in egg_plugins:
if self.verbose:
print 'Adding %s to paster_plugins.txt' % plugin
if not self.simulate:
pluginlib.add_plugin(egg_info_dir, plugin)
if self.options.svn_repository:
self.add_svn_repository(vars, output_dir)
if self.options.config:
write_vars = vars.copy()
del write_vars['project']
del write_vars['package']
self.write_vars(self.options.config, write_vars)
def create_template(self, template, output_dir, vars):
if self.verbose:
print 'Creating template %s' % template.name
template.run(self, output_dir, vars)
def setup_svn_repository(self, output_dir, dist_name):
# @@: Use subprocess
svn_repos = self.options.svn_repository
svn_repos_path = os.path.join(svn_repos, dist_name).replace('\\','/')
svn_command = 'svn'
if sys.platform == 'win32':
svn_command += '.exe'
# @@: The previous method of formatting this string using \ doesn't work on Windows
cmd = '%(svn_command)s mkdir %(svn_repos_path)s' + \
' %(svn_repos_path)s/trunk %(svn_repos_path)s/tags' + \
' %(svn_repos_path)s/branches -m "New project %(dist_name)s"'
cmd = cmd % {
'svn_repos_path': svn_repos_path,
'dist_name': dist_name,
'svn_command':svn_command,
}
if self.verbose:
print "Running:"
print cmd
if not self.simulate:
os.system(cmd)
svn_repos_path_trunk = os.path.join(svn_repos_path,'trunk').replace('\\','/')
cmd = svn_command+' co "%s" "%s"' % (svn_repos_path_trunk, output_dir)
if self.verbose:
print "Running %s" % cmd
if not self.simulate:
os.system(cmd)
ignore_egg_info_files = [
'top_level.txt',
'entry_points.txt',
'requires.txt',
'PKG-INFO',
'namespace_packages.txt',
'SOURCES.txt',
'dependency_links.txt',
'not-zip-safe']
def add_svn_repository(self, vars, output_dir):
svn_repos = self.options.svn_repository
egg_info_dir = pluginlib.egg_info_dir(output_dir, vars['project'])
svn_command = 'svn'
if sys.platform == 'win32':
svn_command += '.exe'
self.run_command(svn_command, 'add', '-N', egg_info_dir)
paster_plugins_file = os.path.join(
egg_info_dir, 'paster_plugins.txt')
if os.path.exists(paster_plugins_file):
self.run_command(svn_command, 'add', paster_plugins_file)
self.run_command(svn_command, 'ps', 'svn:ignore',
'\n'.join(self.ignore_egg_info_files),
egg_info_dir)
if self.verbose:
print ("You must next run 'svn commit' to commit the "
"files to repository")
def extend_templates(self, templates, tmpl_name):
if '#' in tmpl_name:
dist_name, tmpl_name = tmpl_name.split('#', 1)
else:
dist_name, tmpl_name = None, tmpl_name
if dist_name is None:
for entry in self.all_entry_points():
if entry.name == tmpl_name:
tmpl = entry.load()(entry.name)
dist_name = entry.dist.project_name
break
else:
raise LookupError(
'Template by name %r not found' % tmpl_name)
else:
dist = pkg_resources.get_distribution(dist_name)
entry = dist.get_entry_info(
'paste.paster_create_template', tmpl_name)
tmpl = entry.load()(entry.name)
full_name = '%s#%s' % (dist_name, tmpl_name)
for item_full_name, item_tmpl in templates:
if item_full_name == full_name:
# Already loaded
return
for req_name in tmpl.required_templates:
self.extend_templates(templates, req_name)
templates.append((full_name, tmpl))
def all_entry_points(self):
if not hasattr(self, '_entry_points'):
self._entry_points = list(pkg_resources.iter_entry_points(
'paste.paster_create_template'))
return self._entry_points
def display_vars(self, vars):
vars = vars.items()
vars.sort()
print 'Variables:'
max_var = max([len(n) for n, v in vars])
for name, value in vars:
print ' %s:%s %s' % (
name, ' '*(max_var-len(name)), value)
def list_templates(self):
templates = []
for entry in self.all_entry_points():
try:
templates.append(entry.load()(entry.name))
except Exception, e:
# We will not be stopped!
print 'Warning: could not load entry point %s (%s: %s)' % (
entry.name, e.__class__.__name__, e)
max_name = max([len(t.name) for t in templates])
templates.sort(lambda a, b: cmp(a.name, b.name))
print 'Available templates:'
for template in templates:
# @@: Wrap description
print ' %s:%s %s' % (
template.name,
' '*(max_name-len(template.name)),
template.summary)
def inspect_files(self, output_dir, templates, vars):
file_sources = {}
for template in templates:
self._find_files(template, vars, file_sources)
self._show_files(output_dir, file_sources)
self._show_leftovers(output_dir, file_sources)
def _find_files(self, template, vars, file_sources):
tmpl_dir = template.template_dir()
self._find_template_files(
template, tmpl_dir, vars, file_sources)
def _find_template_files(self, template, tmpl_dir, vars,
file_sources, join=''):
full_dir = os.path.join(tmpl_dir, join)
for name in os.listdir(full_dir):
if name.startswith('.'):
continue
if os.path.isdir(os.path.join(full_dir, name)):
self._find_template_files(
template, tmpl_dir, vars, file_sources,
join=os.path.join(join, name))
continue
partial = os.path.join(join, name)
for name, value in vars.items():
partial = partial.replace('+%s+' % name, value)
if partial.endswith('_tmpl'):
partial = partial[:-5]
file_sources.setdefault(partial, []).append(template)
_ignore_filenames = ['.*', '*.pyc', '*.bak*']
_ignore_dirs = ['CVS', '_darcs', '.svn']
def _show_files(self, output_dir, file_sources, join='', indent=0):
pad = ' '*(2*indent)
full_dir = os.path.join(output_dir, join)
names = os.listdir(full_dir)
dirs = [n for n in names
if os.path.isdir(os.path.join(full_dir, n))]
fns = [n for n in names
if not os.path.isdir(os.path.join(full_dir, n))]
dirs.sort()
names.sort()
for name in names:
skip_this = False
for ext in self._ignore_filenames:
if fnmatch.fnmatch(name, ext):
if self.verbose > 1:
print '%sIgnoring %s' % (pad, name)
skip_this = True
break
if skip_this:
continue
partial = os.path.join(join, name)
if partial not in file_sources:
if self.verbose > 1:
print '%s%s (not from template)' % (pad, name)
continue
templates = file_sources.pop(partial)
print '%s%s from:' % (pad, name)
for template in templates:
print '%s %s' % (pad, template.name)
for dir in dirs:
if dir in self._ignore_dirs:
continue
print '%sRecursing into %s/' % (pad, dir)
self._show_files(
output_dir, file_sources,
join=os.path.join(join, dir),
indent=indent+1)
def _show_leftovers(self, output_dir, file_sources):
if not file_sources:
return
print
print 'These files were supposed to be generated by templates'
print 'but were not found:'
file_sources = file_sources.items()
file_sources.sort()
for partial, templates in file_sources:
print ' %s from:' % partial
for template in templates:
print ' %s' % template.name
def list_variables(self, templates):
for tmpl_name, tmpl in templates:
if not tmpl.read_vars():
if self.verbose > 1:
self._show_template_vars(
tmpl_name, tmpl, 'No variables found')
continue
self._show_template_vars(tmpl_name, tmpl)
def _show_template_vars(self, tmpl_name, tmpl, message=None):
title = '%s (from %s)' % (tmpl.name, tmpl_name)
print title
print '-'*len(title)
if message is not None:
print ' %s' % message
print
return
tmpl.print_vars(indent=2)

View File

@@ -0,0 +1,44 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
"""
This module contains default sysconfig settings.
The command object is inserted into this module as a global variable
``paste_command``, and can be used inside functions.
"""
def add_custom_options(parser):
"""
This method can modify the ``parser`` object (which is an
``optparse.OptionParser`` instance). This can be used to add new
options to the command.
"""
pass
def default_config_filename(installer):
"""
This function can return a default filename or directory for the
configuration file, if none was explicitly given.
Return None to mean no preference. The first non-None returning
value will be used.
Pay attention to ``installer.expect_config_directory`` here,
and to ``installer.default_config_filename``.
"""
return installer.default_config_filename
def install_variables(installer):
"""
Returns a dictionary of variables for use later in the process
(e.g., filling a configuration file). These are combined from all
sysconfig files.
"""
return {}
def post_setup_hook(installer, config_file):
"""
This is called at the very end of ``paster setup-app``. You
might use it to register an application globally.
"""
pass

View File

@@ -0,0 +1,269 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
import textwrap
import os
import pkg_resources
from command import Command, BadCommand
import fnmatch
import re
import traceback
from cStringIO import StringIO
import inspect
import types
class EntryPointCommand(Command):
usage = "ENTRY_POINT"
summary = "Show information about entry points"
description = """\
Shows information about one or many entry points (you can use
wildcards for entry point names). Entry points are used for Egg
plugins, and are named resources -- like an application, template
plugin, or other resource. Entry points have a [group] which
defines what kind of object they describe, and inside groups each
entry point is named.
"""
max_args = 2
parser = Command.standard_parser(verbose=False)
parser.add_option('--list', '-l',
dest='list_entry_points',
action='store_true',
help='List all the kinds of entry points on the system')
parser.add_option('--egg', '-e',
dest='show_egg',
help="Show all the entry points for the given Egg")
parser.add_option('--regex',
dest='use_regex',
action='store_true',
help="Make pattern match as regular expression, not just a wildcard pattern")
def command(self):
if self.options.list_entry_points:
return self.list_entry_points()
if self.options.show_egg:
return self.show_egg(self.options.show_egg)
if not self.args:
raise BadCommand("You must give an entry point (or --list)")
pattern = self.get_pattern(self.args[0])
groups = self.get_groups_by_pattern(pattern)
if not groups:
raise BadCommand('No group matched %s' % self.args[0])
ep_pat = None
if len(self.args) > 1:
ep_pat = self.get_pattern(self.args[1])
for group in groups:
desc = self.get_group_description(group)
print '[%s]' % group
if desc:
print self.wrap(desc)
print
by_dist = {}
self.print_entry_points_by_group(group, ep_pat)
def print_entry_points_by_group(self, group, ep_pat):
env = pkg_resources.Environment()
project_names = list(env)
project_names.sort()
for project_name in project_names:
dists = list(env[project_name])
assert dists
dist = dists[0]
entries = dist.get_entry_map(group).values()
if ep_pat:
entries = [e for e in entries
if ep_pat.search(e.name)]
if not entries:
continue
if len(dists) > 1:
print '%s (+ %i older versions)' % (
dist, len(dists)-1)
else:
print '%s' % dist
entries.sort(lambda a, b: cmp(a.name, b.name))
for entry in entries:
print self._ep_description(entry)
desc = self.get_entry_point_description(entry, group)
if desc and desc.description:
print self.wrap(desc.description, indent=4)
def show_egg(self, egg_name):
group_pat = None
if self.args:
group_pat = self.get_pattern(self.args[0])
ep_pat = None
if len(self.args) > 1:
ep_pat = self.get_pattern(self.args[1])
if egg_name.startswith('egg:'):
egg_name = egg_name[4:]
dist = pkg_resources.get_distribution(egg_name)
entry_map = dist.get_entry_map()
entry_groups = entry_map.items()
entry_groups.sort()
for group, points in entry_groups:
if group_pat and not group_pat.search(group):
continue
print '[%s]' % group
points = points.items()
points.sort()
for name, entry in points:
if ep_pat:
if not ep_pat.search(name):
continue
print self._ep_description(entry)
desc = self.get_entry_point_description(entry, group)
if desc and desc.description:
print self.wrap(desc.description, indent=2)
print
def wrap(self, text, indent=0):
text = dedent(text)
width = int(os.environ.get('COLUMNS', 70)) - indent
text = '\n'.join([line.rstrip() for line in text.splitlines()])
paras = text.split('\n\n')
new_paras = []
for para in paras:
if para.lstrip() == para:
# leading whitespace means don't rewrap
para = '\n'.join(textwrap.wrap(para, width))
new_paras.append(para)
text = '\n\n'.join(new_paras)
lines = [' '*indent + line
for line in text.splitlines()]
return '\n'.join(lines)
def _ep_description(self, ep, pad_name=None):
name = ep.name
if pad_name is not None:
name = name + ' '*(pad_name-len(name))
dest = ep.module_name
if ep.attrs:
dest = dest + ':' + '.'.join(ep.attrs)
return '%s = %s' % (name, dest)
def get_pattern(self, s):
if not s:
return None
if self.options.use_regex:
return re.compile(s)
else:
return re.compile(fnmatch.translate(s), re.I)
def list_entry_points(self):
pattern = self.get_pattern(self.args and self.args[0])
groups = self.get_groups_by_pattern(pattern)
print '%i entry point groups found:' % len(groups)
for group in groups:
desc = self.get_group_description(group)
print '[%s]' % group
if desc:
if hasattr(desc, 'description'):
desc = desc.description
print self.wrap(desc, indent=2)
def get_groups_by_pattern(self, pattern):
env = pkg_resources.Environment()
eps = {}
for project_name in env:
for dist in env[project_name]:
for name in pkg_resources.get_entry_map(dist):
if pattern and not pattern.search(name):
continue
if (not pattern
and name.startswith('paste.description.')):
continue
eps[name] = None
eps = eps.keys()
eps.sort()
return eps
def get_group_description(self, group):
for entry in pkg_resources.iter_entry_points('paste.entry_point_description'):
if entry.name == group:
ep = entry.load()
if hasattr(ep, 'description'):
return ep.description
else:
return ep
return None
def get_entry_point_description(self, ep, group):
try:
return self._safe_get_entry_point_description(ep, group)
except Exception, e:
out = StringIO()
traceback.print_exc(file=out)
return ErrorDescription(e, out.getvalue())
def _safe_get_entry_point_description(self, ep, group):
ep.dist.activate()
meta_group = 'paste.description.'+group
meta = ep.dist.get_entry_info(meta_group, ep.name)
if not meta:
generic = list(pkg_resources.iter_entry_points(
meta_group, 'generic'))
if not generic:
return super_generic(ep.load())
# @@: Error if len(generic) > 1?
obj = generic[0].load()
desc = obj(ep, group)
else:
desc = meta.load()
return desc
class EntryPointDescription(object):
def __init__(self, group):
self.group = group
# Should define:
# * description
class SuperGeneric(object):
def __init__(self, doc_object):
self.doc_object = doc_object
self.description = dedent(self.doc_object.__doc__)
try:
if isinstance(self.doc_object, (type, types.ClassType)):
func = self.doc_object.__init__.im_func
elif (hasattr(self.doc_object, '__call__')
and not isinstance(self.doc_object, types.FunctionType)):
func = self.doc_object.__call__
else:
func = self.doc_object
if hasattr(func, '__paste_sig__'):
sig = func.__paste_sig__
else:
sig = inspect.getargspec(func)
sig = inspect.formatargspec(*sig)
except TypeError:
sig = None
if sig:
if self.description:
self.description = '%s\n\n%s' % (
sig, self.description)
else:
self.description = sig
def dedent(s):
if s is None:
return s
s = s.strip('\n').strip('\r')
return textwrap.dedent(s)
def super_generic(obj):
desc = SuperGeneric(obj)
if not desc.description:
return None
return desc
class ErrorDescription(object):
def __init__(self, exc, tb):
self.exc = exc
self.tb = '\n'.join(tb)
self.description = 'Error loading: %s' % exc

View File

@@ -0,0 +1,57 @@
class MetaEntryPointDescription(object):
description = """
This is an entry point that describes other entry points.
"""
class CreateTemplateDescription(object):
description = """
Entry point for creating the file layout for a new project
from a template.
"""
class PasterCommandDescription(object):
description = """
Entry point that adds a command to the ``paster`` script
to a project that has specifically enabled the command.
"""
class GlobalPasterCommandDescription(object):
description = """
Entry point that adds a command to the ``paster`` script
globally.
"""
class AppInstallDescription(object):
description = """
This defines a runner that can install the application given a
configuration file.
"""
##################################################
## Not in Paste per se, but we'll document
## them...
class ConsoleScriptsDescription(object):
description = """
When a package is installed, any entry point listed here will be
turned into a command-line script.
"""
class DistutilsCommandsDescription(object):
description = """
This will add a new command when running
``python setup.py entry-point-name`` if the
package uses setuptools.
"""
class SetupKeywordsDescription(object):
description = """
This adds a new keyword to setup.py's setup() function, and a
validator to validate the value.
"""
class EggInfoWriters(object):
description = """
This adds a new writer that creates files in the PkgName.egg-info/
directory.
"""

View File

@@ -0,0 +1,107 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
import re
import os
import sys
import shlex
import pkg_resources
import command
class ExeCommand(command.Command):
parser = command.Command.standard_parser(verbose=False)
summary = "Run #! executable files"
description = """\
Use this at the top of files like:
#!/usr/bin/env /path/to/paster exe subcommand <command options>
The rest of the file will be used as a config file for the given
command, if it wants a config file.
You can also include an [exe] section in the file, which looks
like:
[exe]
command = serve
log_file = /path/to/log
add = /path/to/other/config.ini
Which translates to:
paster serve --log-file=/path/to/log /path/to/other/config.ini
"""
hidden = True
_exe_section_re = re.compile(r'^\s*\[\s*exe\s*\]\s*$')
_section_re = re.compile(r'^\s*\[')
def run(self, argv):
if argv and argv[0] in ('-h', '--help'):
print self.description
return
if os.environ.get('REQUEST_METHOD'):
# We're probably in a CGI environment
sys.stdout = sys.stderr
os.environ['PASTE_DEFAULT_QUIET'] = 'true'
# Maybe import cgitb or something?
if '_' not in os.environ:
print "Warning: this command is intended to be run with a #! like:"
print " #!/usr/bin/env paster exe"
print "It only works with /usr/bin/env, and only as a #! line."
# Should I actually shlex.split the args?
filename = argv[-1]
args = argv[:-1]
extra_args = []
else:
filename = os.environ['_']
extra_args = argv[:]
args = []
while extra_args:
if extra_args[0] == filename:
extra_args.pop(0)
break
args.append(extra_args.pop(0))
vars = {'here': os.path.dirname(filename),
'__file__': filename}
f = open(filename)
lines = f.readlines()
f.close()
options = {}
lineno = 1
while lines:
if self._exe_section_re.search(lines[0]):
lines.pop(0)
break
lines.pop(0)
lineno += 1
pre_options = []
options = args
for line in lines:
lineno += 1
line = line.strip()
if not line or line.startswith('#'):
continue
if self._section_re.search(line):
break
if '=' not in line:
raise command.BadCommand('Missing = in %s at %s: %r'
% (filename, lineno, line))
name, value = line.split('=', 1)
name = name.strip()
value = value.strip()
if name == 'require':
pkg_resources.require(value)
elif name == 'command' or name == 'add':
options.extend(shlex.split(value))
elif name == 'plugin':
options[:0] = ['--plugin', value]
else:
value = value % vars
options.append('--%s=%s' % (name.replace('_', '-'), value))
os.environ['PASTE_CONFIG_FILE'] = filename
options.extend(extra_args)
command.run(options)

View File

@@ -0,0 +1,362 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
import os
import glob
import pkg_resources
from paste.script import pluginlib, copydir
from paste.script.command import BadCommand
difflib = None
try:
import subprocess
except ImportError:
from paste.script.util import subprocess24 as subprocess
class FileOp(object):
"""
Enhance the ease of file copying/processing from a package into a target
project
"""
def __init__(self, simulate=False,
verbose=True,
interactive=True,
source_dir=None,
template_vars=None):
"""
Initialize our File operation helper object
source_dir
Should refer to the directory within the package
that contains the templates to be used for the other copy
operations. It is assumed that packages will keep all their
templates under a hierarchy starting here.
This should be an absolute path passed in, for example::
FileOp(source_dir=os.path.dirname(__file__) + '/templates')
"""
self.simulate = simulate
self.verbose = verbose
self.interactive = interactive
if template_vars is None:
template_vars = {}
self.template_vars = template_vars
self.source_dir = source_dir
self.use_pkg_resources = isinstance(source_dir, tuple)
def copy_file(self, template, dest, filename=None, add_py=True, package=True,
template_renderer=None):
"""
Copy a file from the source location to somewhere in the
destination.
template
The filename underneath self.source_dir to copy/process
dest
The destination directory in the project relative to where
this command is being run
filename
What to name the file in the target project, use the same name
as the template if not provided
add_py
Add a .py extension to all files copied
package
Whether or not this file is part of a Python package, and any
directories created should contain a __init__.py file as well.
template_renderer
An optional template renderer
"""
if not filename:
filename = template.split('/')[0]
if filename.endswith('_tmpl'):
filename = filename[:-5]
base_package, cdir = self.find_dir(dest, package)
self.template_vars['base_package'] = base_package
content = self.load_content(base_package, cdir, filename, template,
template_renderer=template_renderer)
if add_py:
# @@: Why is it a default to add a .py extension?
filename = '%s.py' % filename
dest = os.path.join(cdir, filename)
self.ensure_file(dest, content, package)
def copy_dir(self, template_dir, dest, destname=None, package=True):
"""
Copy a directory recursively, processing any files within it
that need to be processed (end in _tmpl).
template_dir
Directory under self.source_dir to copy/process
dest
Destination directory into which this directory will be copied
to.
destname
Use this name instead of the original template_dir name for
creating the directory
package
This directory will be a Python package and needs to have a
__init__.py file.
"""
# @@: This should actually be implemented
raise NotImplementedError
def load_content(self, base_package, base, name, template,
template_renderer=None):
blank = os.path.join(base, name + '.py')
read_content = True
if not os.path.exists(blank):
if self.use_pkg_resources:
fullpath = '/'.join([self.source_dir[1], template])
content = pkg_resources.resource_string(
self.source_dir[0], fullpath)
read_content = False
blank = fullpath
else:
blank = os.path.join(self.source_dir,
template)
if read_content:
f = open(blank, 'r')
content = f.read()
f.close()
if blank.endswith('_tmpl'):
content = copydir.substitute_content(
content, self.template_vars, filename=blank,
template_renderer=template_renderer)
return content
def find_dir(self, dirname, package=False):
egg_info = pluginlib.find_egg_info_dir(os.getcwd())
# @@: Should give error about egg_info when top_level.txt missing
f = open(os.path.join(egg_info, 'top_level.txt'))
packages = [l.strip() for l in f.readlines()
if l.strip() and not l.strip().startswith('#')]
f.close()
if not len(packages):
raise BadCommand("No top level dir found for %s" % dirname)
# @@: This doesn't support deeper servlet directories,
# or packages not kept at the top level.
base = os.path.dirname(egg_info)
possible = []
for pkg in packages:
d = os.path.join(base, pkg, dirname)
if os.path.exists(d):
possible.append((pkg, d))
if not possible:
self.ensure_dir(os.path.join(base, packages[0], dirname),
package=package)
return self.find_dir(dirname)
if len(possible) > 1:
raise BadCommand(
"Multiple %s dirs found (%s)" % (dirname, possible))
return possible[0]
def parse_path_name_args(self, name):
"""
Given the name, assume that the first argument is a path/filename
combination. Return the name and dir of this. If the name ends with
'.py' that will be erased.
Examples:
comments -> comments, ''
admin/comments -> comments, 'admin'
h/ab/fred -> fred, 'h/ab'
"""
if name.endswith('.py'):
# Erase extensions
name = name[:-3]
if '.' in name:
# Turn into directory name:
name = name.replace('.', os.path.sep)
if '/' != os.path.sep:
name = name.replace('/', os.path.sep)
parts = name.split(os.path.sep)
name = parts[-1]
if not parts[:-1]:
dir = ''
elif len(parts[:-1]) == 1:
dir = parts[0]
else:
dir = os.path.join(*parts[:-1])
return name, dir
def ensure_dir(self, dir, svn_add=True, package=False):
"""
Ensure that the directory exists, creating it if necessary.
Respects verbosity and simulation.
Adds directory to subversion if ``.svn/`` directory exists in
parent, and directory was created.
package
If package is True, any directories created will contain a
__init__.py file.
"""
dir = dir.rstrip(os.sep)
if not dir:
# we either reached the parent-most directory, or we got
# a relative directory
# @@: Should we make sure we resolve relative directories
# first? Though presumably the current directory always
# exists.
return
if not os.path.exists(dir):
self.ensure_dir(os.path.dirname(dir), svn_add=svn_add, package=package)
if self.verbose:
print 'Creating %s' % self.shorten(dir)
if not self.simulate:
os.mkdir(dir)
if (svn_add and
os.path.exists(os.path.join(os.path.dirname(dir), '.svn'))):
self.svn_command('add', dir)
if package:
initfile = os.path.join(dir, '__init__.py')
f = open(initfile, 'wb')
f.write("#\n")
f.close()
print 'Creating %s' % self.shorten(initfile)
if (svn_add and
os.path.exists(os.path.join(os.path.dirname(dir), '.svn'))):
self.svn_command('add', initfile)
else:
if self.verbose > 1:
print "Directory already exists: %s" % self.shorten(dir)
def ensure_file(self, filename, content, svn_add=True, package=False):
"""
Ensure a file named ``filename`` exists with the given
content. If ``--interactive`` has been enabled, this will ask
the user what to do if a file exists with different content.
"""
global difflib
self.ensure_dir(os.path.dirname(filename), svn_add=svn_add, package=package)
if not os.path.exists(filename):
if self.verbose:
print 'Creating %s' % filename
if not self.simulate:
f = open(filename, 'wb')
f.write(content)
f.close()
if svn_add and os.path.exists(os.path.join(os.path.dirname(filename), '.svn')):
self.svn_command('add', filename)
return
f = open(filename, 'rb')
old_content = f.read()
f.close()
if content == old_content:
if self.verbose > 1:
print 'File %s matches expected content' % filename
return
if self.interactive:
print 'Warning: file %s does not match expected content' % filename
if difflib is None:
import difflib
diff = difflib.context_diff(
content.splitlines(),
old_content.splitlines(),
'expected ' + filename,
filename)
print '\n'.join(diff)
if self.interactive:
while 1:
s = raw_input(
'Overwrite file with new content? [y/N] ').strip().lower()
if not s:
s = 'n'
if s.startswith('y'):
break
if s.startswith('n'):
return
print 'Unknown response; Y or N please'
else:
return
if self.verbose:
print 'Overwriting %s with new content' % filename
if not self.simulate:
f = open(filename, 'wb')
f.write(content)
f.close()
def shorten(self, fn, *paths):
"""
Return a shorted form of the filename (relative to the current
directory), typically for displaying in messages. If
``*paths`` are present, then use os.path.join to create the
full filename before shortening.
"""
if paths:
fn = os.path.join(fn, *paths)
if fn.startswith(os.getcwd()):
return fn[len(os.getcwd()):].lstrip(os.path.sep)
else:
return fn
_svn_failed = False
def svn_command(self, *args, **kw):
"""
Run an svn command, but don't raise an exception if it fails.
"""
try:
return self.run_command('svn', *args, **kw)
except OSError, e:
if not self._svn_failed:
print 'Unable to run svn command (%s); proceeding anyway' % e
self._svn_failed = True
def run_command(self, cmd, *args, **kw):
"""
Runs the command, respecting verbosity and simulation.
Returns stdout, or None if simulating.
"""
cwd = popdefault(kw, 'cwd', os.getcwd())
capture_stderr = popdefault(kw, 'capture_stderr', False)
expect_returncode = popdefault(kw, 'expect_returncode', False)
assert not kw, ("Arguments not expected: %s" % kw)
if capture_stderr:
stderr_pipe = subprocess.STDOUT
else:
stderr_pipe = subprocess.PIPE
try:
proc = subprocess.Popen([cmd] + list(args),
cwd=cwd,
stderr=stderr_pipe,
stdout=subprocess.PIPE)
except OSError, e:
if e.errno != 2:
# File not found
raise
raise OSError(
"The expected executable %s was not found (%s)"
% (cmd, e))
if self.verbose:
print 'Running %s %s' % (cmd, ' '.join(args))
if self.simulate:
return None
stdout, stderr = proc.communicate()
if proc.returncode and not expect_returncode:
if not self.verbose:
print 'Running %s %s' % (cmd, ' '.join(args))
print 'Error (exit code: %s)' % proc.returncode
if stderr:
print stderr
raise OSError("Error executing command %s" % cmd)
if self.verbose > 2:
if stderr:
print 'Command error output:'
print stderr
if stdout:
print 'Command output:'
print stdout
return stdout
def popdefault(dict, name, default=None):
if name not in dict:
return default
else:
v = dict[name]
del dict[name]
return v

View File

@@ -0,0 +1,119 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
from paste.deploy.converters import aslist, asbool
from paste.script.serve import ensure_port_cleanup
import warnings
def warn(name, stacklevel=3):
# Deprecated 2007-12-17
warnings.warn(
'The egg:PasteScript#flup_%s entry point is deprecated; please use egg:Flup#%s instead'
% (name, name),
DeprecationWarning, stacklevel=stacklevel)
def run_ajp_thread(wsgi_app, global_conf,
scriptName='', host='localhost', port='8009',
allowedServers='127.0.0.1'):
import flup.server.ajp
warn('ajp_thread')
addr = (host, int(port))
ensure_port_cleanup([addr])
s = flup.server.ajp.WSGIServer(
wsgi_app,
scriptName=scriptName,
bindAddress=addr,
allowedServers=aslist(allowedServers),
)
s.run()
def run_ajp_fork(wsgi_app, global_conf,
scriptName='', host='localhost', port='8009',
allowedServers='127.0.0.1'):
import flup.server.ajp_fork
warn('ajp_fork')
addr = (host, int(port))
ensure_port_cleanup([addr])
s = flup.server.ajp_fork.WSGIServer(
wsgi_app,
scriptName=scriptName,
bindAddress=addr,
allowedServers=aslist(allowedServers),
)
s.run()
def run_fcgi_thread(wsgi_app, global_conf,
host=None, port=None,
socket=None, umask=None,
multiplexed=False):
import flup.server.fcgi
warn('fcgi_thread')
if socket:
assert host is None and port is None
sock = socket
elif host:
assert host is not None and port is not None
sock = (host, int(port))
ensure_port_cleanup([sock])
else:
sock = None
if umask is not None:
umask = int(umask)
s = flup.server.fcgi.WSGIServer(
wsgi_app,
bindAddress=sock, umask=umask,
multiplexed=asbool(multiplexed))
s.run()
def run_fcgi_fork(wsgi_app, global_conf,
host=None, port=None,
socket=None, umask=None,
multiplexed=False):
import flup.server.fcgi_fork
warn('fcgi_fork')
if socket:
assert host is None and port is None
sock = socket
elif host:
assert host is not None and port is not None
sock = (host, int(port))
ensure_port_cleanup([sock])
else:
sock = None
if umask is not None:
umask = int(umask)
s = flup.server.fcgi_fork.WSGIServer(
wsgi_app,
bindAddress=sock, umask=umask,
multiplexed=asbool(multiplexed))
s.run()
def run_scgi_thread(wsgi_app, global_conf,
scriptName='', host='localhost', port='4000',
allowedServers='127.0.0.1'):
import flup.server.scgi
warn('scgi_thread')
addr = (host, int(port))
ensure_port_cleanup([addr])
s = flup.server.scgi.WSGIServer(
wsgi_app,
scriptName=scriptName,
bindAddress=addr,
allowedServers=aslist(allowedServers),
)
s.run()
def run_scgi_fork(wsgi_app, global_conf,
scriptName='', host='localhost', port='4000',
allowedServers='127.0.0.1'):
import flup.server.scgi_fork
warn('scgi_fork')
addr = (host, int(port))
ensure_port_cleanup([addr])
s = flup.server.scgi_fork.WSGIServer(
wsgi_app,
scriptName=scriptName,
bindAddress=addr,
allowedServers=aslist(allowedServers),
)
s.run()

View File

@@ -0,0 +1,168 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
import os
import py_compile
import marshal
import inspect
import re
from command import Command
import pluginlib
class GrepCommand(Command):
summary = 'Search project for symbol'
usage = 'SYMBOL'
max_args = 1
min_args = 1
bad_names = ['.svn', 'CVS', '_darcs']
parser = Command.standard_parser()
parser.add_option(
'-x', '--exclude-module',
metavar="module.name",
dest="exclude_modules",
action="append",
help="Don't search the given module")
parser.add_option(
'-t', '--add-type',
metavar=".ext",
dest="add_types",
action="append",
help="Search the given type of files")
def command(self):
self.exclude_modules = self.options.exclude_modules or []
self.add_types = self.options.add_types or []
self.symbol = self.args[0]
self.basedir = os.path.dirname(
pluginlib.find_egg_info_dir(os.getcwd()))
if self.verbose:
print "Searching in %s" % self.basedir
self.total_files = 0
self.search_dir(self.basedir)
if self.verbose > 1:
print "Searched %i files" % self.total_files
def search_dir(self, dir):
names = os.listdir(dir)
names.sort()
dirs = []
for name in names:
full = os.path.join(dir, name)
if name in self.bad_names:
continue
if os.path.isdir(full):
# Breadth-first; we'll do this later...
dirs.append(full)
continue
for t in self.add_types:
if name.lower().endswith(t.lower()):
self.search_text(full)
if not name.endswith('.py'):
continue
self.search_file(full)
for dir in dirs:
self.search_dir(dir)
def search_file(self, filename):
self.total_files += 1
if not filename.endswith('.py'):
self.search_text(filename)
return
pyc = filename[:-2]+'pyc'
if not os.path.exists(pyc):
py_compile.compile(filename)
if not os.path.exists(pyc):
# Invalid syntax...
self.search_text(filename, as_module=True)
return
f = open(pyc, 'rb')
# .pyc Header:
f.read(8)
code = marshal.load(f)
f.close()
self.search_code(code, filename, [])
def search_code(self, code, filename, path):
if code.co_name != "?":
path = path + [code.co_name]
else:
path = path
sym = self.symbol
if sym in code.co_varnames:
self.found(code, filename, path)
elif sym in code.co_names:
self.found(code, filename, path)
for const in code.co_consts:
if const == sym:
self.found(code, filename, path)
if inspect.iscode(const):
if not const.co_filename == filename:
continue
self.search_code(const, filename, path)
def search_text(self, filename, as_module=False):
f = open(filename, 'rb')
lineno = 0
any = False
for line in f:
lineno += 1
if line.find(self.symbol) != -1:
if not any:
any = True
if as_module:
print '%s (unloadable)' % self.module_name(filename)
else:
print self.relative_name(filename)
print ' %3i %s' % (lineno, line)
if not self.verbose:
break
f.close()
def found(self, code, filename, path):
print self.display(filename, path)
self.find_occurance(code)
def find_occurance(self, code):
f = open(code.co_filename, 'rb')
lineno = 0
for index, line in zip(xrange(code.co_firstlineno), f):
lineno += 1
pass
lines = []
first_indent = None
for line in f:
lineno += 1
if line.find(self.symbol) != -1:
this_indent = len(re.match(r'^[ \t]*', line).group(0))
if first_indent is None:
first_indent = this_indent
else:
if this_indent < first_indent:
break
print ' %3i %s' % (lineno, line[first_indent:].rstrip())
if not self.verbose:
break
def module_name(self, filename):
assert filename, startswith(self.basedir)
mod = filename[len(self.basedir):].strip('/').strip(os.path.sep)
mod = os.path.splitext(mod)[0]
mod = mod.replace(os.path.sep, '.').replace('/', '.')
return mod
def relative_name(self, filename):
assert filename, startswith(self.basedir)
name = filename[len(self.basedir):].strip('/').strip(os.path.sep)
return name
def display(self, filename, path):
parts = '.'.join(path)
if parts:
parts = ':' + parts
return self.module_name(filename) + parts

View File

@@ -0,0 +1,60 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
from command import Command, get_commands
from command import parser as base_parser
class HelpCommand(Command):
summary = "Display help"
usage = '[COMMAND]'
max_args = 1
parser = Command.standard_parser()
def command(self):
if not self.args:
self.generic_help()
return
name = self.args[0]
commands = get_commands()
if name not in commands:
print 'No such command: %s' % name
self.generic_help()
return
command = commands[name].load()
runner = command(name)
runner.run(['-h'])
def generic_help(self):
base_parser.print_help()
print
commands_grouped = {}
commands = get_commands()
longest = max([len(n) for n in commands.keys()])
for name, command in commands.items():
try:
command = command.load()
except Exception, e:
print 'Cannot load command %s: %s' % (name, e)
continue
if getattr(command, 'hidden', False):
continue
commands_grouped.setdefault(
command.group_name, []).append((name, command))
commands_grouped = commands_grouped.items()
commands_grouped.sort()
print 'Commands:'
for group, commands in commands_grouped:
if group:
print group + ':'
commands.sort()
for name, command in commands:
print ' %s %s' % (self.pad(name, length=longest),
command.summary)
#if command.description:
# print self.indent_block(command.description, 4)
print

View File

@@ -0,0 +1,49 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
class IAppInstall(object):
"""
The interface for objects in the entry point group
``paste.app_install``
"""
def __init__(distribution, entry_group, entry_name):
"""
An object representing a specific application (the
distribution is a pkg_resource.Distribution object), for the
given entry point name in the given group. Right now the only
group used for this is ``'paste.app_factory'``.
"""
def description(sys_config):
"""
Return a text description of the application and its
configuration. ``sys_config`` is a dictionary representing
the system configuration, and can be used for giving more
explicit defaults if the application preparation uses the
system configuration. It may be None, in which case the
description should be more abstract.
Applications are free to ignore ``sys_config``.
"""
def write_config(command, filename, sys_config):
"""
Write a fresh config file to ``filename``. ``command`` is a
``paste.script.command.Command`` object, and should be used
for the actual operations. It handles things like simulation
and verbosity.
``sys_config`` is (if given) a dictionary of system-wide
configuration options.
"""
def setup_config(command, config_filename,
config_section, sys_config):
"""
Set up the application, using ``command`` (to ensure simulate,
etc). The application is described by the configuration file
``config_filename``. ``sys_config`` is the system
configuration (though probably the values from it should have
already been encorporated into the configuration file).
"""

View File

@@ -0,0 +1,3 @@
[egg_info]
tag_build = dev
tag_svn_revision = true

View File

@@ -0,0 +1,26 @@
from setuptools import setup, find_packages
import sys, os
version = {{repr(version or "0.0")}}
setup(name={{repr(project)}},
version=version,
description="{{description or ''}}",
long_description="""\
{{long_description or ''}}""",
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
keywords={{repr(keywords or '')}},
author={{repr(author or '')}},
author_email={{repr(author_email or '')}},
url={{repr(url or '')}},
license={{repr(license_name or '')}},
packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
include_package_data=True,
zip_safe={{repr(bool(zip_safe or False))}},
install_requires=[
# -*- Extra requirements: -*-
],
entry_points="""
# -*- Entry points: -*-
""",
)

View File

@@ -0,0 +1,135 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
import os
import pkg_resources
def add_plugin(egg_info_dir, plugin_name):
"""
Add the plugin to the given distribution (or spec), in
.egg-info/paster_plugins.txt
"""
fn = os.path.join(egg_info_dir, 'paster_plugins.txt')
if not os.path.exists(fn):
lines = []
else:
f = open(fn)
lines = [l.strip() for l in f.readlines() if l.strip()]
f.close()
if plugin_name in lines:
# Nothing to do
return
lines.append(plugin_name)
if not os.path.exists(os.path.dirname(fn)):
os.makedirs(os.path.dirname(fn))
f = open(fn, 'w')
for line in lines:
f.write(line)
f.write('\n')
f.close()
def remove_plugin(egg_info_dir, plugin_name):
"""
Remove the plugin to the given distribution (or spec), in
.egg-info/paster_plugins.txt. Raises ValueError if the
plugin is not in the file.
"""
fn = os.path.join(egg_info_dir, 'paster_plugins.txt')
if not os.path.exists(fn):
raise ValueError(
"Cannot remove plugin from %s; file does not exist"
% fn)
f = open(fn)
lines = [l.strip() for l in f.readlines() if l.strip()]
f.close()
for line in lines:
# What about version specs?
if line.lower() == plugin_name.lower():
break
else:
raise ValueError(
"Plugin %s not found in file %s (from: %s)"
% (plugin_name, fn, lines))
lines.remove(line)
print 'writing', lines
f = open(fn, 'w')
for line in lines:
f.write(line)
f.write('\n')
f.close()
def find_egg_info_dir(dir):
while 1:
try:
filenames = os.listdir(dir)
except OSError:
# Probably permission denied or something
return None
for fn in filenames:
if (fn.endswith('.egg-info')
and os.path.isdir(os.path.join(dir, fn))):
return os.path.join(dir, fn)
parent = os.path.dirname(dir)
if parent == dir:
# Top-most directory
return None
dir = parent
def resolve_plugins(plugin_list):
found = []
while plugin_list:
plugin = plugin_list.pop()
try:
pkg_resources.require(plugin)
except pkg_resources.DistributionNotFound, e:
msg = '%sNot Found%s: %s (did you run python setup.py develop?)'
if str(e) != plugin:
e.args = (msg % (str(e) + ': ', ' for', plugin)),
else:
e.args = (msg % ('', '', plugin)),
raise
found.append(plugin)
dist = get_distro(plugin)
if dist.has_metadata('paster_plugins.txt'):
data = dist.get_metadata('paster_plugins.txt')
for add_plugin in parse_lines(data):
if add_plugin not in found:
plugin_list.append(add_plugin)
return map(get_distro, found)
def get_distro(spec):
return pkg_resources.get_distribution(spec)
def load_commands_from_plugins(plugins):
commands = {}
for plugin in plugins:
commands.update(pkg_resources.get_entry_map(
plugin, group='paste.paster_command'))
return commands
def parse_lines(data):
result = []
for line in data.splitlines():
line = line.strip()
if line and not line.startswith('#'):
result.append(line)
return result
def load_global_commands():
commands = {}
for p in pkg_resources.iter_entry_points('paste.global_paster_command'):
commands[p.name] = p
return commands
def egg_name(dist_name):
return pkg_resources.to_filename(pkg_resources.safe_name(dist_name))
def egg_info_dir(base_dir, dist_name):
all = []
for dir_extension in ['.'] + os.listdir(base_dir):
full = os.path.join(base_dir, dir_extension,
egg_name(dist_name)+'.egg-info')
all.append(full)
if os.path.exists(full):
return full
raise IOError("No egg-info directory found (looked in %s)"
% ', '.join(all))

View File

@@ -0,0 +1,189 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
import os
import re
import sys
import urlparse
import urllib
from command import Command, BadCommand
from paste.deploy import loadapp, loadserver
from paste.wsgilib import raw_interactive
class RequestCommand(Command):
min_args = 2
usage = 'CONFIG_FILE URL [OPTIONS/ARGUMENTS]'
takes_config_file = 1
summary = "Run a request for the described application"
description = """\
This command makes an artifical request to a web application that
uses a paste.deploy configuration file for the server and
application.
Use 'paster request config.ini /url' to request /url. Use
'paster post config.ini /url < data' to do a POST with the given
request body.
If the URL is relative (doesn't begin with /) it is interpreted as
relative to /.command/. The variable environ['paste.command_request']
will be set to True in the request, so your application can distinguish
these calls from normal requests.
Note that you can pass options besides the options listed here; any unknown
options will be passed to the application in environ['QUERY_STRING'].
"""
parser = Command.standard_parser(quiet=True)
parser.add_option('-n', '--app-name',
dest='app_name',
metavar='NAME',
help="Load the named application (default main)")
parser.add_option('--config-var',
dest='config_vars',
metavar='NAME:VALUE',
action='append',
help="Variable to make available in the config for %()s substitution "
"(you can use this option multiple times)")
parser.add_option('--header',
dest='headers',
metavar='NAME:VALUE',
action='append',
help="Header to add to request (you can use this option multiple times)")
parser.add_option('--display-headers',
dest='display_headers',
action='store_true',
help='Display headers before the response body')
ARG_OPTIONS = ['-n', '--app-name', '--config-var', '--header']
OTHER_OPTIONS = ['--display-headers']
## FIXME: some kind of verbosity?
## FIXME: allow other methods than POST and GET?
_scheme_re = re.compile(r'^[a-z][a-z]+:', re.I)
def command(self):
vars = {}
app_spec = self.args[0]
url = self.args[1]
url = urlparse.urljoin('/.command/', url)
if self.options.config_vars:
for item in self.option.config_vars:
if ':' not in item:
raise BadCommand(
"Bad option, should be name:value : --config-var=%s" % item)
name, value = item.split(':', 1)
vars[name] = value
headers = {}
if self.options.headers:
for item in self.options.headers:
if ':' not in item:
raise BadCommand(
"Bad option, should be name:value : --header=%s" % item)
name, value = item.split(':', 1)
headers[name] = value.strip()
if not self._scheme_re.search(app_spec):
app_spec = 'config:'+app_spec
if self.options.app_name:
if '#' in app_spec:
app_spec = app_spec.split('#', 1)[0]
app_spec = app_spec + '#' + options.app_name
app = loadapp(app_spec, relative_to=os.getcwd(), global_conf=vars)
if self.command_name.lower() == 'post':
request_method = 'POST'
else:
request_method = 'GET'
qs = []
for item in self.args[2:]:
if '=' in item:
item = urllib.quote(item.split('=', 1)[0]) + '=' + urllib.quote(item.split('=', 1)[1])
else:
item = urllib.quote(item)
qs.append(item)
qs = '&'.join(qs)
environ = {
'REQUEST_METHOD': request_method,
## FIXME: shouldn't be static (an option?):
'CONTENT_TYPE': 'text/plain',
'wsgi.run_once': True,
'wsgi.multithread': False,
'wsgi.multiprocess': False,
'wsgi.errors': sys.stderr,
'QUERY_STRING': qs,
'HTTP_ACCEPT': 'text/plain;q=1.0, */*;q=0.1',
'paste.command_request': True,
}
if request_method == 'POST':
environ['wsgi.input'] = sys.stdin
environ['CONTENT_LENGTH'] = '-1'
for name, value in headers.items():
if name.lower() == 'content-type':
name = 'CONTENT_TYPE'
else:
name = 'HTTP_'+name.upper().replace('-', '_')
environ[name] = value
status, headers, output, errors = raw_interactive(app, url, **environ)
assert not errors, "errors should be printed directly to sys.stderr"
if self.options.display_headers:
for name, value in headers:
sys.stdout.write('%s: %s\n' % (name, value))
sys.stdout.write('\n')
sys.stdout.write(output)
sys.stdout.flush()
status_int = int(status.split()[0])
if status_int != 200:
return status_int
def parse_args(self, args):
if args == ['-h']:
Command.parse_args(self, args)
return
# These are the arguments parsed normally:
normal_args = []
# And these are arguments passed to the URL:
extra_args = []
# This keeps track of whether we have the two required positional arguments:
pos_args = 0
while args:
start = args[0]
if not start.startswith('-'):
if pos_args < 2:
pos_args += 1
normal_args.append(start)
args.pop(0)
continue
else:
normal_args.append(start)
args.pop(0)
continue
else:
found = False
for option in self.ARG_OPTIONS:
if start == option:
normal_args.append(start)
args.pop(0)
if not args:
raise BadCommand(
"Option %s takes an argument" % option)
normal_args.append(args.pop(0))
found = True
break
elif start.startswith(option+'='):
normal_args.append(start)
args.pop(0)
found = True
break
if found:
continue
if start in self.OTHER_OPTIONS:
normal_args.append(start)
args.pop(0)
continue
extra_args.append(start)
args.pop(0)
Command.parse_args(self, normal_args)
# Add the extra arguments back in:
self.args = self.args + extra_args

View File

@@ -0,0 +1,650 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
# @@: This should be moved to paste.deploy
# For discussion of daemonizing:
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731
# Code taken also from QP:
# http://www.mems-exchange.org/software/qp/
# From lib/site.py
import re
import os
import errno
import sys
import time
try:
import subprocess
except ImportError:
from paste.util import subprocess24 as subprocess
from command import Command, BadCommand
from paste.deploy import loadapp, loadserver
import threading
import atexit
import logging
import ConfigParser
MAXFD = 1024
jython = sys.platform.startswith('java')
class DaemonizeException(Exception):
pass
class ServeCommand(Command):
min_args = 0
usage = 'CONFIG_FILE [start|stop|restart|status] [var=value]'
takes_config_file = 1
summary = "Serve the described application"
description = """\
This command serves a web application that uses a paste.deploy
configuration file for the server and application.
If start/stop/restart is given, then --daemon is implied, and it will
start (normal operation), stop (--stop-daemon), or do both.
You can also include variable assignments like 'http_port=8080'
and then use %(http_port)s in your config files.
"""
# used by subclasses that configure apps and servers differently
requires_config_file = True
parser = Command.standard_parser(quiet=True)
parser.add_option('-n', '--app-name',
dest='app_name',
metavar='NAME',
help="Load the named application (default main)")
parser.add_option('-s', '--server',
dest='server',
metavar='SERVER_TYPE',
help="Use the named server.")
parser.add_option('--server-name',
dest='server_name',
metavar='SECTION_NAME',
help="Use the named server as defined in the configuration file (default: main)")
if hasattr(os, 'fork'):
parser.add_option('--daemon',
dest="daemon",
action="store_true",
help="Run in daemon (background) mode")
parser.add_option('--pid-file',
dest='pid_file',
metavar='FILENAME',
help="Save PID to file (default to paster.pid if running in daemon mode)")
parser.add_option('--log-file',
dest='log_file',
metavar='LOG_FILE',
help="Save output to the given log file (redirects stdout)")
parser.add_option('--reload',
dest='reload',
action='store_true',
help="Use auto-restart file monitor")
parser.add_option('--reload-interval',
dest='reload_interval',
default=1,
help="Seconds between checking files (low number can cause significant CPU usage)")
parser.add_option('--monitor-restart',
dest='monitor_restart',
action='store_true',
help="Auto-restart server if it dies")
parser.add_option('--status',
action='store_true',
dest='show_status',
help="Show the status of the (presumably daemonized) server")
if hasattr(os, 'setuid'):
# I don't think these are available on Windows
parser.add_option('--user',
dest='set_user',
metavar="USERNAME",
help="Set the user (usually only possible when run as root)")
parser.add_option('--group',
dest='set_group',
metavar="GROUP",
help="Set the group (usually only possible when run as root)")
parser.add_option('--stop-daemon',
dest='stop_daemon',
action='store_true',
help='Stop a daemonized server (given a PID file, or default paster.pid file)')
if jython:
parser.add_option('--disable-jython-reloader',
action='store_true',
dest='disable_jython_reloader',
help="Disable the Jython reloader")
_scheme_re = re.compile(r'^[a-z][a-z]+:', re.I)
default_verbosity = 1
_reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN'
_monitor_environ_key = 'PASTE_MONITOR_SHOULD_RUN'
possible_subcommands = ('start', 'stop', 'restart', 'status')
def command(self):
if self.options.stop_daemon:
return self.stop_daemon()
if not hasattr(self.options, 'set_user'):
# Windows case:
self.options.set_user = self.options.set_group = None
# @@: Is this the right stage to set the user at?
self.change_user_group(
self.options.set_user, self.options.set_group)
if self.requires_config_file:
if not self.args:
raise BadCommand('You must give a config file')
app_spec = self.args[0]
if (len(self.args) > 1
and self.args[1] in self.possible_subcommands):
cmd = self.args[1]
restvars = self.args[2:]
else:
cmd = None
restvars = self.args[1:]
else:
app_spec = ""
if (self.args
and self.args[0] in self.possible_subcommands):
cmd = self.args[0]
restvars = self.args[1:]
else:
cmd = None
restvars = self.args[:]
if (getattr(self.options, 'daemon', False)
and getattr(self.options, 'reload', False)):
raise BadCommand('The --daemon and --reload options may not be used together')
jython_monitor = False
if self.options.reload:
if jython and not self.options.disable_jython_reloader:
# JythonMonitor raises the special SystemRestart
# exception that'll cause the Jython interpreter to
# reload in the existing Java process (avoiding
# subprocess startup time)
try:
from paste.reloader import JythonMonitor
except ImportError:
pass
else:
jython_monitor = JythonMonitor(poll_interval=int(
self.options.reload_interval))
if self.requires_config_file:
jython_monitor.watch_file(self.args[0])
if not jython_monitor:
if os.environ.get(self._reloader_environ_key):
from paste import reloader
if self.verbose > 1:
print 'Running reloading file monitor'
reloader.install(int(self.options.reload_interval))
if self.requires_config_file:
reloader.watch_file(self.args[0])
else:
return self.restart_with_reloader()
if cmd not in (None, 'start', 'stop', 'restart', 'status'):
raise BadCommand(
'Error: must give start|stop|restart (not %s)' % cmd)
if cmd == 'status' or self.options.show_status:
return self.show_status()
if cmd == 'restart' or cmd == 'stop':
result = self.stop_daemon()
if result:
if cmd == 'restart':
print "Could not stop daemon; aborting"
else:
print "Could not stop daemon"
return result
if cmd == 'stop':
return result
self.options.daemon = True
if cmd == 'start':
self.options.daemon = True
app_name = self.options.app_name
vars = self.parse_vars(restvars)
if not self._scheme_re.search(app_spec):
app_spec = 'config:' + app_spec
server_name = self.options.server_name
if self.options.server:
server_spec = 'egg:PasteScript'
assert server_name is None
server_name = self.options.server
else:
server_spec = app_spec
base = os.getcwd()
if getattr(self.options, 'daemon', False):
if not self.options.pid_file:
self.options.pid_file = 'paster.pid'
if not self.options.log_file:
self.options.log_file = 'paster.log'
# Ensure the log file is writeable
if self.options.log_file:
try:
writeable_log_file = open(self.options.log_file, 'a')
except IOError, ioe:
msg = 'Error: Unable to write to log file: %s' % ioe
raise BadCommand(msg)
writeable_log_file.close()
# Ensure the pid file is writeable
if self.options.pid_file:
try:
writeable_pid_file = open(self.options.pid_file, 'a')
except IOError, ioe:
msg = 'Error: Unable to write to pid file: %s' % ioe
raise BadCommand(msg)
writeable_pid_file.close()
if getattr(self.options, 'daemon', False):
try:
self.daemonize()
except DaemonizeException, ex:
if self.verbose > 0:
print str(ex)
return
if (self.options.monitor_restart
and not os.environ.get(self._monitor_environ_key)):
return self.restart_with_monitor()
if self.options.pid_file:
self.record_pid(self.options.pid_file)
if self.options.log_file:
stdout_log = LazyWriter(self.options.log_file, 'a')
sys.stdout = stdout_log
sys.stderr = stdout_log
logging.basicConfig(stream=stdout_log)
log_fn = app_spec
if log_fn.startswith('config:'):
log_fn = app_spec[len('config:'):]
elif log_fn.startswith('egg:'):
log_fn = None
if log_fn:
log_fn = os.path.join(base, log_fn)
self.logging_file_config(log_fn)
server = self.loadserver(server_spec, name=server_name,
relative_to=base, global_conf=vars)
app = self.loadapp(app_spec, name=app_name,
relative_to=base, global_conf=vars)
if self.verbose > 0:
if hasattr(os, 'getpid'):
msg = 'Starting server in PID %i.' % os.getpid()
else:
msg = 'Starting server.'
print msg
def serve():
try:
server(app)
except (SystemExit, KeyboardInterrupt), e:
if self.verbose > 1:
raise
if str(e):
msg = ' '+str(e)
else:
msg = ''
print 'Exiting%s (-v to see traceback)' % msg
if jython_monitor:
# JythonMonitor has to be ran from the main thread
threading.Thread(target=serve).start()
print 'Starting Jython file monitor'
jython_monitor.periodic_reload()
else:
serve()
def loadserver(self, server_spec, name, relative_to, **kw):
return loadserver(
server_spec, name=name,
relative_to=relative_to, **kw)
def loadapp(self, app_spec, name, relative_to, **kw):
return loadapp(
app_spec, name=name, relative_to=relative_to,
**kw)
def daemonize(self):
pid = live_pidfile(self.options.pid_file)
if pid:
raise DaemonizeException(
"Daemon is already running (PID: %s from PID file %s)"
% (pid, self.options.pid_file))
if self.verbose > 0:
print 'Entering daemon mode'
pid = os.fork()
if pid:
# The forked process also has a handle on resources, so we
# *don't* want proper termination of the process, we just
# want to exit quick (which os._exit() does)
os._exit(0)
# Make this the session leader
os.setsid()
# Fork again for good measure!
pid = os.fork()
if pid:
os._exit(0)
# @@: Should we set the umask and cwd now?
import resource # Resource usage information.
maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
if (maxfd == resource.RLIM_INFINITY):
maxfd = MAXFD
# Iterate through and close all file descriptors.
for fd in range(0, maxfd):
try:
os.close(fd)
except OSError: # ERROR, fd wasn't open to begin with (ignored)
pass
if (hasattr(os, "devnull")):
REDIRECT_TO = os.devnull
else:
REDIRECT_TO = "/dev/null"
os.open(REDIRECT_TO, os.O_RDWR) # standard input (0)
# Duplicate standard input to standard output and standard error.
os.dup2(0, 1) # standard output (1)
os.dup2(0, 2) # standard error (2)
def record_pid(self, pid_file):
pid = os.getpid()
if self.verbose > 1:
print 'Writing PID %s to %s' % (pid, pid_file)
f = open(pid_file, 'w')
f.write(str(pid))
f.close()
atexit.register(_remove_pid_file, pid, pid_file, self.verbose)
def stop_daemon(self):
pid_file = self.options.pid_file or 'paster.pid'
if not os.path.exists(pid_file):
print 'No PID file exists in %s' % pid_file
return 1
pid = read_pidfile(pid_file)
if not pid:
print "Not a valid PID file in %s" % pid_file
return 1
pid = live_pidfile(pid_file)
if not pid:
print "PID in %s is not valid (deleting)" % pid_file
try:
os.unlink(pid_file)
except (OSError, IOError), e:
print "Could not delete: %s" % e
return 2
return 1
for j in range(10):
if not live_pidfile(pid_file):
break
import signal
os.kill(pid, signal.SIGTERM)
time.sleep(1)
else:
print "failed to kill web process %s" % pid
return 3
if os.path.exists(pid_file):
os.unlink(pid_file)
return 0
def show_status(self):
pid_file = self.options.pid_file or 'paster.pid'
if not os.path.exists(pid_file):
print 'No PID file %s' % pid_file
return 1
pid = read_pidfile(pid_file)
if not pid:
print 'No PID in file %s' % pid_file
return 1
pid = live_pidfile(pid_file)
if not pid:
print 'PID %s in %s is not running' % (pid, pid_file)
return 1
print 'Server running in PID %s' % pid
return 0
def restart_with_reloader(self):
self.restart_with_monitor(reloader=True)
def restart_with_monitor(self, reloader=False):
if self.verbose > 0:
if reloader:
print 'Starting subprocess with file monitor'
else:
print 'Starting subprocess with monitor parent'
while 1:
args = [self.quote_first_command_arg(sys.executable)] + sys.argv
new_environ = os.environ.copy()
if reloader:
new_environ[self._reloader_environ_key] = 'true'
else:
new_environ[self._monitor_environ_key] = 'true'
proc = None
try:
try:
_turn_sigterm_into_systemexit()
proc = subprocess.Popen(args, env=new_environ)
exit_code = proc.wait()
proc = None
except KeyboardInterrupt:
print '^C caught in monitor process'
if self.verbose > 1:
raise
return 1
finally:
if (proc is not None
and hasattr(os, 'kill')):
import signal
try:
os.kill(proc.pid, signal.SIGTERM)
except (OSError, IOError):
pass
if reloader:
# Reloader always exits with code 3; but if we are
# a monitor, any exit code will restart
if exit_code != 3:
return exit_code
if self.verbose > 0:
print '-'*20, 'Restarting', '-'*20
def change_user_group(self, user, group):
if not user and not group:
return
import pwd, grp
uid = gid = None
if group:
try:
gid = int(group)
group = grp.getgrgid(gid).gr_name
except ValueError:
import grp
try:
entry = grp.getgrnam(group)
except KeyError:
raise BadCommand(
"Bad group: %r; no such group exists" % group)
gid = entry.gr_gid
try:
uid = int(user)
user = pwd.getpwuid(uid).pw_name
except ValueError:
try:
entry = pwd.getpwnam(user)
except KeyError:
raise BadCommand(
"Bad username: %r; no such user exists" % user)
if not gid:
gid = entry.pw_gid
uid = entry.pw_uid
if self.verbose > 0:
print 'Changing user to %s:%s (%s:%s)' % (
user, group or '(unknown)', uid, gid)
if gid:
os.setgid(gid)
if uid:
os.setuid(uid)
class LazyWriter(object):
"""
File-like object that opens a file lazily when it is first written
to.
"""
def __init__(self, filename, mode='w'):
self.filename = filename
self.fileobj = None
self.lock = threading.Lock()
self.mode = mode
def open(self):
if self.fileobj is None:
self.lock.acquire()
try:
if self.fileobj is None:
self.fileobj = open(self.filename, self.mode)
finally:
self.lock.release()
return self.fileobj
def write(self, text):
fileobj = self.open()
fileobj.write(text)
fileobj.flush()
def writelines(self, text):
fileobj = self.open()
fileobj.writelines(text)
fileobj.flush()
def flush(self):
self.open().flush()
def live_pidfile(pidfile):
"""(pidfile:str) -> int | None
Returns an int found in the named file, if there is one,
and if there is a running process with that process id.
Return None if no such process exists.
"""
pid = read_pidfile(pidfile)
if pid:
try:
os.kill(int(pid), 0)
return pid
except OSError, e:
if e.errno == errno.EPERM:
return pid
return None
def read_pidfile(filename):
if os.path.exists(filename):
try:
f = open(filename)
content = f.read()
f.close()
return int(content.strip())
except (ValueError, IOError):
return None
else:
return None
def _remove_pid_file(written_pid, filename, verbosity):
current_pid = os.getpid()
if written_pid != current_pid:
# A forked process must be exiting, not the process that
# wrote the PID file
return
if not os.path.exists(filename):
return
f = open(filename)
content = f.read().strip()
f.close()
try:
pid_in_file = int(content)
except ValueError:
pass
else:
if pid_in_file != current_pid:
print "PID file %s contains %s, not expected PID %s" % (
filename, pid_in_file, current_pid)
return
if verbosity > 0:
print "Removing PID file %s" % filename
try:
os.unlink(filename)
return
except OSError, e:
# Record, but don't give traceback
print "Cannot remove PID file: %s" % e
# well, at least lets not leave the invalid PID around...
try:
f = open(filename, 'w')
f.write('')
f.close()
except OSError, e:
print 'Stale PID left in file: %s (%e)' % (filename, e)
else:
print 'Stale PID removed'
def ensure_port_cleanup(bound_addresses, maxtries=30, sleeptime=2):
"""
This makes sure any open ports are closed.
Does this by connecting to them until they give connection
refused. Servers should call like::
import paste.script
ensure_port_cleanup([80, 443])
"""
atexit.register(_cleanup_ports, bound_addresses, maxtries=maxtries,
sleeptime=sleeptime)
def _cleanup_ports(bound_addresses, maxtries=30, sleeptime=2):
# Wait for the server to bind to the port.
import socket
import errno
for bound_address in bound_addresses:
for attempt in range(maxtries):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
sock.connect(bound_address)
except socket.error, e:
if e.args[0] != errno.ECONNREFUSED:
raise
break
else:
time.sleep(sleeptime)
else:
raise SystemExit('Timeout waiting for port.')
sock.close()
def _turn_sigterm_into_systemexit():
"""
Attempts to turn a SIGTERM exception into a SystemExit exception.
"""
try:
import signal
except ImportError:
return
def handle_term(signo, frame):
raise SystemExit
signal.signal(signal.SIGTERM, handle_term)

View File

@@ -0,0 +1,278 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
import sys
import os
import inspect
import copydir
import command
from paste.util.template import paste_script_template_renderer
class Template(object):
# Subclasses must define:
# _template_dir (or template_dir())
# summary
# Variables this template uses (mostly for documentation now)
# a list of instances of var()
vars = []
# Eggs that should be added as plugins:
egg_plugins = []
# Templates that must be applied first:
required_templates = []
# Use Cheetah for substituting templates:
use_cheetah = False
# If true, then read all the templates to find the variables:
read_vars_from_templates = False
# You can also give this function/method to use something other
# than Cheetah or string.Template. The function should be of the
# signature template_renderer(content, vars, filename=filename).
# Careful you don't turn this into a method by putting a function
# here (without staticmethod)!
template_renderer = None
def __init__(self, name):
self.name = name
self._read_vars = None
def module_dir(self):
"""Returns the module directory of this template."""
mod = sys.modules[self.__class__.__module__]
return os.path.dirname(mod.__file__)
def template_dir(self):
assert self._template_dir is not None, (
"Template %r didn't set _template_dir" % self)
if isinstance( self._template_dir, tuple):
return self._template_dir
else:
return os.path.join(self.module_dir(), self._template_dir)
def run(self, command, output_dir, vars):
self.pre(command, output_dir, vars)
self.write_files(command, output_dir, vars)
self.post(command, output_dir, vars)
def check_vars(self, vars, cmd):
expect_vars = self.read_vars(cmd)
if not expect_vars:
# Assume that variables aren't defined
return vars
converted_vars = {}
unused_vars = vars.copy()
errors = []
for var in expect_vars:
if var.name not in unused_vars:
if cmd.interactive:
prompt = 'Enter %s' % var.full_description()
response = cmd.challenge(prompt, var.default, var.should_echo)
converted_vars[var.name] = response
elif var.default is command.NoDefault:
errors.append('Required variable missing: %s'
% var.full_description())
else:
converted_vars[var.name] = var.default
else:
converted_vars[var.name] = unused_vars.pop(var.name)
if errors:
raise command.BadCommand(
'Errors in variables:\n%s' % '\n'.join(errors))
converted_vars.update(unused_vars)
vars.update(converted_vars)
return converted_vars
def read_vars(self, command=None):
if self._read_vars is not None:
return self._read_vars
assert (not self.read_vars_from_templates
or self.use_cheetah), (
"You can only read variables from templates if using Cheetah")
if not self.read_vars_from_templates:
self._read_vars = self.vars
return self.vars
vars = self.vars[:]
var_names = [var.name for var in self.vars]
read_vars = find_args_in_dir(
self.template_dir(),
verbose=command and command.verbose > 1).items()
read_vars.sort()
for var_name, var in read_vars:
if var_name not in var_names:
vars.append(var)
self._read_vars = vars
return vars
def write_files(self, command, output_dir, vars):
template_dir = self.template_dir()
if not os.path.exists(output_dir):
print "Creating directory %s" % output_dir
if not command.simulate:
# Don't let copydir create this top-level directory,
# since copydir will svn add it sometimes:
os.makedirs(output_dir)
copydir.copy_dir(template_dir, output_dir,
vars,
verbosity=command.verbose,
simulate=command.options.simulate,
interactive=command.interactive,
overwrite=command.options.overwrite,
indent=1,
use_cheetah=self.use_cheetah,
template_renderer=self.template_renderer)
def print_vars(self, indent=0):
vars = self.read_vars()
var.print_vars(vars)
def pre(self, command, output_dir, vars):
"""
Called before template is applied.
"""
pass
def post(self, command, output_dir, vars):
"""
Called after template is applied.
"""
pass
NoDefault = command.NoDefault
class var(object):
def __init__(self, name, description,
default='', should_echo=True):
self.name = name
self.description = description
self.default = default
self.should_echo = should_echo
def __repr__(self):
return '<%s %s default=%r should_echo=%s>' % (
self.__class__.__name__,
self.name, self.default, self.should_echo)
def full_description(self):
if self.description:
return '%s (%s)' % (self.name, self.description)
else:
return self.name
def print_vars(cls, vars, indent=0):
max_name = max([len(v.name) for v in vars])
for var in vars:
if var.description:
print '%s%s%s %s' % (
' '*indent,
var.name,
' '*(max_name-len(var.name)),
var.description)
else:
print ' %s' % var.name
if var.default is not command.NoDefault:
print ' default: %r' % var.default
if var.should_echo is True:
print ' should_echo: %s' % var.should_echo
print
print_vars = classmethod(print_vars)
class BasicPackage(Template):
_template_dir = 'paster-templates/basic_package'
summary = "A basic setuptools-enabled package"
vars = [
var('version', 'Version (like 0.1)'),
var('description', 'One-line description of the package'),
var('long_description', 'Multi-line description (in reST)'),
var('keywords', 'Space-separated keywords/tags'),
var('author', 'Author name'),
var('author_email', 'Author email'),
var('url', 'URL of homepage'),
var('license_name', 'License name'),
var('zip_safe', 'True/False: if the package can be distributed as a .zip file', default=False),
]
template_renderer = staticmethod(paste_script_template_renderer)
_skip_variables = ['VFN', 'currentTime', 'self', 'VFFSL', 'dummyTrans',
'getmtime', 'trans']
def find_args_in_template(template):
if isinstance(template, (str, unicode)):
# Treat as filename:
import Cheetah.Template
template = Cheetah.Template.Template(file=template)
if not hasattr(template, 'body'):
# Don't know...
return None
method = template.body
args, varargs, varkw, defaults = inspect.getargspec(method)
defaults=list(defaults or [])
vars = []
while args:
if len(args) == len(defaults):
default = defaults.pop(0)
else:
default = command.NoDefault
arg = args.pop(0)
if arg in _skip_variables:
continue
# @@: No way to get description yet
vars.append(
var(arg, description=None,
default=default))
return vars
def find_args_in_dir(dir, verbose=False):
all_vars = {}
for fn in os.listdir(dir):
if fn.startswith('.') or fn == 'CVS' or fn == '_darcs':
continue
full = os.path.join(dir, fn)
if os.path.isdir(full):
inner_vars = find_args_in_dir(full)
elif full.endswith('_tmpl'):
inner_vars = {}
found = find_args_in_template(full)
if found is None:
# Couldn't read variables
if verbose:
print 'Template %s has no parseable variables' % full
continue
for var in found:
inner_vars[var.name] = var
else:
# Not a template, don't read it
continue
if verbose:
print 'Found variable(s) %s in Template %s' % (
', '.join(inner_vars.keys()), full)
for var_name, var in inner_vars.items():
# Easy case:
if var_name not in all_vars:
all_vars[var_name] = var
continue
# Emit warnings if the variables don't match well:
cur_var = all_vars[var_name]
if not cur_var.description:
cur_var.description = var.description
elif (cur_var.description and var.description
and var.description != cur_var.description):
print >> sys.stderr, (
"Variable descriptions do not match: %s: %s and %s"
% (var_name, cur_var.description, var.description))
if (cur_var.default is not command.NoDefault
and var.default is not command.NoDefault
and cur_var.default != var.default):
print >> sys.stderr, (
"Variable defaults do not match: %s: %r and %r"
% (var_name, cur_var.default, var.default))
return all_vars

View File

@@ -0,0 +1,101 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
import cgi
import os
html_page_template = '''
<html>
<head>
<title>Test Application</title>
</head>
<body>
<h1>Test Application: Working!</h1>
<table border="1">
%(environ)s
</table>
<p>
Note: to see an error report, append <code>?error=true</code>
to the URL
</p>
</body>
</html>
'''
html_row_template = '''
<tr>
<td><b>%(key)s</b></td>
<td><tt>%(value_literal)s</b></td>
</tr>
'''
text_page_template = '%(environ)s'
text_row_template = '%(key)s: %(value_repr)s\n'
def make_literal(value):
value = cgi.escape(value, 1)
value = value.replace('\n\r', '\n')
value = value.replace('\r', '\n')
value = value.replace('\n', '<br>\n')
return value
class TestApplication(object):
"""
A test WSGI application, that prints out all the environmental
variables, and if you add ``?error=t`` to the URL it will
deliberately throw an exception.
"""
def __init__(self, global_conf=None, text=False):
self.global_conf = global_conf
self.text = text
def __call__(self, environ, start_response):
if environ.get('QUERY_STRING', '').find('error=') >= 0:
assert 0, "Here is your error report, ordered and delivered"
if self.text:
page_template = text_page_template
row_template = text_row_template
content_type = 'text/plain; charset=utf8'
else:
page_template = html_page_template
row_template = html_row_template
content_type = 'text/html; charset=utf8'
keys = environ.keys()
keys.sort()
rows = []
for key in keys:
data = {'key': key}
value = environ[key]
data['value'] = value
try:
value = repr(value)
except Exception, e:
value = 'Cannot use repr(): %s' % e
data['value_repr'] = value
data['value_literal'] = make_literal(value)
row = row_template % data
rows.append(row)
rows = ''.join(rows)
page = page_template % {'environ': rows}
if isinstance(page, unicode):
page = page.encode('utf8')
headers = [('Content-type', content_type)]
start_response('200 OK', headers)
return [page]
def make_test_application(global_conf, text=False, lint=False):
from paste.deploy.converters import asbool
text = asbool(text)
lint = asbool(lint)
app = TestApplication(global_conf=global_conf, text=text)
if lint:
from paste.lint import middleware
app = middleware(app)
return app
make_test_application.__doc__ = TestApplication.__doc__

View File

@@ -0,0 +1,20 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
# @@: THIS IS INCOMPLETE!
def run_twisted(wsgi_app, global_conf,
host='127.0.0.1', port='8080'):
host = host or None
import twisted.web2.wsgi
import twisted.web2.log
import twisted.web2.channel
import twisted.web2.server
import twisted.internet.reactor
wsgi_resource = twisted.web2.wsgi.WSGIResource(wsgi_app)
resource = twisted.web2.log.LogWrapperResource(wsgi_resource)
twisted.web2.log.DefaultCommonAccessLoggingObserver().start()
site = twisted.web2.server.Site(resource)
factory = twisted.web2.channel.HTTPFactory(site)
# --- start reactor for listen port
twisted.internet.reactor.listenTCP(int(port), factory, interface=host)
twisted.internet.reactor.run()

View File

@@ -0,0 +1,3 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
#

View File

@@ -0,0 +1,354 @@
# Copyright 2001-2005 by Vinay Sajip. All Rights Reserved.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose and without fee is hereby granted,
# provided that the above copyright notice appear in all copies and that
# both that copyright notice and this permission notice appear in
# supporting documentation, and that the name of Vinay Sajip
# not be used in advertising or publicity pertaining to distribution
# of the software without specific, written prior permission.
# VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
# VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""
Configuration functions for the logging package for Python. The core package
is based on PEP 282 and comments thereto in comp.lang.python, and influenced
by Apache's log4j system.
Should work under Python versions >= 1.5.2, except that source line
information is not available unless 'sys._getframe()' is.
Copyright (C) 2001-2004 Vinay Sajip. All Rights Reserved.
To use, simply 'import logging' and log away!
"""
import sys, logging, logging.handlers, string, socket, struct, os, traceback, types
try:
import thread
import threading
except ImportError:
thread = None
from SocketServer import ThreadingTCPServer, StreamRequestHandler
DEFAULT_LOGGING_CONFIG_PORT = 9030
if sys.platform == "win32":
RESET_ERROR = 10054 #WSAECONNRESET
else:
RESET_ERROR = 104 #ECONNRESET
#
# The following code implements a socket listener for on-the-fly
# reconfiguration of logging.
#
# _listener holds the server object doing the listening
_listener = None
def fileConfig(fname, defaults=None):
"""
Read the logging configuration from a ConfigParser-format file.
This can be called several times from an application, allowing an end user
the ability to select from various pre-canned configurations (if the
developer provides a mechanism to present the choices and load the chosen
configuration).
In versions of ConfigParser which have the readfp method [typically
shipped in 2.x versions of Python], you can pass in a file-like object
rather than a filename, in which case the file-like object will be read
using readfp.
"""
import ConfigParser
cp = ConfigParser.ConfigParser(defaults)
if hasattr(cp, 'readfp') and hasattr(fname, 'readline'):
cp.readfp(fname)
else:
cp.read(fname)
formatters = _create_formatters(cp)
# critical section
logging._acquireLock()
try:
logging._handlers.clear()
if hasattr(logging, '_handlerList'):
del logging._handlerList[:]
# Handlers add themselves to logging._handlers
handlers = _install_handlers(cp, formatters)
_install_loggers(cp, handlers)
finally:
logging._releaseLock()
def _resolve(name):
"""Resolve a dotted name to a global object."""
name = string.split(name, '.')
used = name.pop(0)
found = __import__(used)
for n in name:
used = used + '.' + n
try:
found = getattr(found, n)
except AttributeError:
__import__(used)
found = getattr(found, n)
return found
def _create_formatters(cp):
"""Create and return formatters"""
flist = cp.get("formatters", "keys")
if not len(flist):
return {}
flist = string.split(flist, ",")
formatters = {}
for form in flist:
form = string.strip(form)
sectname = "formatter_%s" % form
opts = cp.options(sectname)
if "format" in opts:
fs = cp.get(sectname, "format", 1)
else:
fs = None
if "datefmt" in opts:
dfs = cp.get(sectname, "datefmt", 1)
else:
dfs = None
c = logging.Formatter
if "class" in opts:
class_name = cp.get(sectname, "class")
if class_name:
c = _resolve(class_name)
f = c(fs, dfs)
formatters[form] = f
return formatters
def _install_handlers(cp, formatters):
"""Install and return handlers"""
hlist = cp.get("handlers", "keys")
if not len(hlist):
return {}
hlist = string.split(hlist, ",")
handlers = {}
fixups = [] #for inter-handler references
for hand in hlist:
hand = string.strip(hand)
sectname = "handler_%s" % hand
klass = cp.get(sectname, "class")
opts = cp.options(sectname)
if "formatter" in opts:
fmt = cp.get(sectname, "formatter")
else:
fmt = ""
try:
klass = eval(klass, vars(logging))
except (AttributeError, NameError):
klass = _resolve(klass)
args = cp.get(sectname, "args")
args = eval(args, vars(logging))
h = apply(klass, args)
if "level" in opts:
level = cp.get(sectname, "level")
h.setLevel(logging._levelNames[level])
if len(fmt):
h.setFormatter(formatters[fmt])
#temporary hack for FileHandler and MemoryHandler.
if klass == logging.handlers.MemoryHandler:
if "target" in opts:
target = cp.get(sectname,"target")
else:
target = ""
if len(target): #the target handler may not be loaded yet, so keep for later...
fixups.append((h, target))
handlers[hand] = h
#now all handlers are loaded, fixup inter-handler references...
for h, t in fixups:
h.setTarget(handlers[t])
return handlers
def _install_loggers(cp, handlers):
"""Create and install loggers"""
# configure the root first
llist = cp.get("loggers", "keys")
llist = string.split(llist, ",")
llist = map(lambda x: string.strip(x), llist)
llist.remove("root")
sectname = "logger_root"
root = logging.root
log = root
opts = cp.options(sectname)
if "level" in opts:
level = cp.get(sectname, "level")
log.setLevel(logging._levelNames[level])
for h in root.handlers[:]:
root.removeHandler(h)
hlist = cp.get(sectname, "handlers")
if len(hlist):
hlist = string.split(hlist, ",")
for hand in hlist:
log.addHandler(handlers[string.strip(hand)])
#and now the others...
#we don't want to lose the existing loggers,
#since other threads may have pointers to them.
#existing is set to contain all existing loggers,
#and as we go through the new configuration we
#remove any which are configured. At the end,
#what's left in existing is the set of loggers
#which were in the previous configuration but
#which are not in the new configuration.
existing = root.manager.loggerDict.keys()
#now set up the new ones...
for log in llist:
sectname = "logger_%s" % log
qn = cp.get(sectname, "qualname")
opts = cp.options(sectname)
if "propagate" in opts:
propagate = cp.getint(sectname, "propagate")
else:
propagate = 1
logger = logging.getLogger(qn)
if qn in existing:
existing.remove(qn)
if "level" in opts:
level = cp.get(sectname, "level")
logger.setLevel(logging._levelNames[level])
for h in logger.handlers[:]:
logger.removeHandler(h)
logger.propagate = propagate
logger.disabled = 0
hlist = cp.get(sectname, "handlers")
if len(hlist):
hlist = string.split(hlist, ",")
for hand in hlist:
logger.addHandler(handlers[string.strip(hand)])
#Disable any old loggers. There's no point deleting
#them as other threads may continue to hold references
#and by disabling them, you stop them doing any logging.
for log in existing:
root.manager.loggerDict[log].disabled = 1
def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
"""
Start up a socket server on the specified port, and listen for new
configurations.
These will be sent as a file suitable for processing by fileConfig().
Returns a Thread object on which you can call start() to start the server,
and which you can join() when appropriate. To stop the server, call
stopListening().
"""
if not thread:
raise NotImplementedError, "listen() needs threading to work"
class ConfigStreamHandler(StreamRequestHandler):
"""
Handler for a logging configuration request.
It expects a completely new logging configuration and uses fileConfig
to install it.
"""
def handle(self):
"""
Handle a request.
Each request is expected to be a 4-byte length, packed using
struct.pack(">L", n), followed by the config file.
Uses fileConfig() to do the grunt work.
"""
import tempfile
try:
conn = self.connection
chunk = conn.recv(4)
if len(chunk) == 4:
slen = struct.unpack(">L", chunk)[0]
chunk = self.connection.recv(slen)
while len(chunk) < slen:
chunk = chunk + conn.recv(slen - len(chunk))
#Apply new configuration. We'd like to be able to
#create a StringIO and pass that in, but unfortunately
#1.5.2 ConfigParser does not support reading file
#objects, only actual files. So we create a temporary
#file and remove it later.
file = tempfile.mktemp(".ini")
f = open(file, "w")
f.write(chunk)
f.close()
try:
fileConfig(file)
except (KeyboardInterrupt, SystemExit):
raise
except:
traceback.print_exc()
os.remove(file)
except socket.error, e:
if type(e.args) != types.TupleType:
raise
else:
errcode = e.args[0]
if errcode != RESET_ERROR:
raise
class ConfigSocketReceiver(ThreadingTCPServer):
"""
A simple TCP socket-based logging config receiver.
"""
allow_reuse_address = 1
def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
handler=None):
ThreadingTCPServer.__init__(self, (host, port), handler)
logging._acquireLock()
self.abort = 0
logging._releaseLock()
self.timeout = 1
def serve_until_stopped(self):
import select
abort = 0
while not abort:
rd, wr, ex = select.select([self.socket.fileno()],
[], [],
self.timeout)
if rd:
self.handle_request()
logging._acquireLock()
abort = self.abort
logging._releaseLock()
def serve(rcvr, hdlr, port):
server = rcvr(port=port, handler=hdlr)
global _listener
logging._acquireLock()
_listener = server
logging._releaseLock()
server.serve_until_stopped()
return threading.Thread(target=serve,
args=(ConfigSocketReceiver,
ConfigStreamHandler, port))
def stopListening():
"""
Stop the listening server which was created with a call to listen().
"""
global _listener
if _listener:
logging._acquireLock()
_listener.abort = 1
_listener = None
logging._releaseLock()

View File

@@ -0,0 +1,32 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
"""
Create random secrets.
"""
import os
import random
def random_bytes(length):
"""
Return a string of the given length. Uses ``os.urandom`` if it
can, or just pseudo-random numbers otherwise.
"""
try:
return os.urandom(length)
except AttributeError:
return ''.join([
chr(random.randrange(256)) for i in xrange(length)])
def secret_string(length=25):
"""
Returns a random string of the given length. The string
is a base64-encoded version of a set of random bytes, truncated
to the given length (and without any newlines).
"""
s = random_bytes(length).encode('base64')
for badchar in '\n\r=':
s = s.replace(badchar, '')
# We're wasting some characters here. But random characters are
# cheap ;)
return s[:length]

View File

@@ -0,0 +1,533 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
"""A collection of string operations (most are no longer used).
Warning: most of the code you see here isn't normally used nowadays.
Beginning with Python 1.6, many of these functions are implemented as
methods on the standard string object. They used to be implemented by
a built-in module called strop, but strop is now obsolete itself.
Public module variables:
whitespace -- a string containing all characters considered whitespace
lowercase -- a string containing all characters considered lowercase letters
uppercase -- a string containing all characters considered uppercase letters
letters -- a string containing all characters considered letters
digits -- a string containing all characters considered decimal digits
hexdigits -- a string containing all characters considered hexadecimal digits
octdigits -- a string containing all characters considered octal digits
punctuation -- a string containing all characters considered punctuation
printable -- a string containing all characters considered printable
"""
# Some strings for ctype-style character classification
whitespace = ' \t\n\r\v\f'
lowercase = 'abcdefghijklmnopqrstuvwxyz'
uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
letters = lowercase + uppercase
ascii_lowercase = lowercase
ascii_uppercase = uppercase
ascii_letters = ascii_lowercase + ascii_uppercase
digits = '0123456789'
hexdigits = digits + 'abcdef' + 'ABCDEF'
octdigits = '01234567'
punctuation = """!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""
printable = digits + letters + punctuation + whitespace
# Case conversion helpers
# Use str to convert Unicode literal in case of -U
# Note that Cookie.py bogusly uses _idmap :(
l = map(chr, xrange(256))
_idmap = str('').join(l)
del l
# Functions which aren't available as string methods.
# Capitalize the words in a string, e.g. " aBc dEf " -> "Abc Def".
# See also regsub.capwords().
def capwords(s, sep=None):
"""capwords(s, [sep]) -> string
Split the argument into words using split, capitalize each
word using capitalize, and join the capitalized words using
join. Note that this replaces runs of whitespace characters by
a single space.
"""
return (sep or ' ').join([x.capitalize() for x in s.split(sep)])
# Construct a translation string
_idmapL = None
def maketrans(fromstr, tostr):
"""maketrans(frm, to) -> string
Return a translation table (a string of 256 bytes long)
suitable for use in string.translate. The strings frm and to
must be of the same length.
"""
if len(fromstr) != len(tostr):
raise ValueError, "maketrans arguments must have same length"
global _idmapL
if not _idmapL:
_idmapL = map(None, _idmap)
L = _idmapL[:]
fromstr = map(ord, fromstr)
for i in range(len(fromstr)):
L[fromstr[i]] = tostr[i]
return ''.join(L)
####################################################################
import re as _re
class _multimap:
"""Helper class for combining multiple mappings.
Used by .{safe_,}substitute() to combine the mapping and keyword
arguments.
"""
def __init__(self, primary, secondary):
self._primary = primary
self._secondary = secondary
def __getitem__(self, key):
try:
return self._primary[key]
except KeyError:
return self._secondary[key]
class _TemplateMetaclass(type):
pattern = r"""
%(delim)s(?:
(?P<escaped>%(delim)s) | # Escape sequence of two delimiters
(?P<named>%(id)s) | # delimiter and a Python identifier
{(?P<braced>%(id)s)} | # delimiter and a braced identifier
(?P<invalid>) # Other ill-formed delimiter exprs
)
"""
def __init__(cls, name, bases, dct):
super(_TemplateMetaclass, cls).__init__(name, bases, dct)
if 'pattern' in dct:
pattern = cls.pattern
else:
pattern = _TemplateMetaclass.pattern % {
'delim' : _re.escape(cls.delimiter),
'id' : cls.idpattern,
}
cls.pattern = _re.compile(pattern, _re.IGNORECASE | _re.VERBOSE)
class Template:
"""A string class for supporting $-substitutions."""
__metaclass__ = _TemplateMetaclass
delimiter = '$'
idpattern = r'[_a-z][_a-z0-9]*'
def __init__(self, template):
self.template = template
# Search for $$, $identifier, ${identifier}, and any bare $'s
def _invalid(self, mo):
i = mo.start('invalid')
lines = self.template[:i].splitlines(True)
if not lines:
colno = 1
lineno = 1
else:
colno = i - len(''.join(lines[:-1]))
lineno = len(lines)
raise ValueError('Invalid placeholder in string: line %d, col %d' %
(lineno, colno))
def substitute(self, *args, **kws):
if len(args) > 1:
raise TypeError('Too many positional arguments')
if not args:
mapping = kws
elif kws:
mapping = _multimap(kws, args[0])
else:
mapping = args[0]
# Helper function for .sub()
def convert(mo):
# Check the most common path first.
named = mo.group('named') or mo.group('braced')
if named is not None:
val = mapping[named]
# We use this idiom instead of str() because the latter will
# fail if val is a Unicode containing non-ASCII characters.
return '%s' % val
if mo.group('escaped') is not None:
return self.delimiter
if mo.group('invalid') is not None:
self._invalid(mo)
raise ValueError('Unrecognized named group in pattern',
self.pattern)
return self.pattern.sub(convert, self.template)
def safe_substitute(self, *args, **kws):
if len(args) > 1:
raise TypeError('Too many positional arguments')
if not args:
mapping = kws
elif kws:
mapping = _multimap(kws, args[0])
else:
mapping = args[0]
# Helper function for .sub()
def convert(mo):
named = mo.group('named')
if named is not None:
try:
# We use this idiom instead of str() because the latter
# will fail if val is a Unicode containing non-ASCII
return '%s' % mapping[named]
except KeyError:
return self.delimiter + named
braced = mo.group('braced')
if braced is not None:
try:
return '%s' % mapping[braced]
except KeyError:
return self.delimiter + '{' + braced + '}'
if mo.group('escaped') is not None:
return self.delimiter
if mo.group('invalid') is not None:
return self.delimiter
raise ValueError('Unrecognized named group in pattern',
self.pattern)
return self.pattern.sub(convert, self.template)
####################################################################
# NOTE: Everything below here is deprecated. Use string methods instead.
# This stuff will go away in Python 3.0.
# Backward compatible names for exceptions
index_error = ValueError
atoi_error = ValueError
atof_error = ValueError
atol_error = ValueError
# convert UPPER CASE letters to lower case
def lower(s):
"""lower(s) -> string
Return a copy of the string s converted to lowercase.
"""
return s.lower()
# Convert lower case letters to UPPER CASE
def upper(s):
"""upper(s) -> string
Return a copy of the string s converted to uppercase.
"""
return s.upper()
# Swap lower case letters and UPPER CASE
def swapcase(s):
"""swapcase(s) -> string
Return a copy of the string s with upper case characters
converted to lowercase and vice versa.
"""
return s.swapcase()
# Strip leading and trailing tabs and spaces
def strip(s, chars=None):
"""strip(s [,chars]) -> string
Return a copy of the string s with leading and trailing
whitespace removed.
If chars is given and not None, remove characters in chars instead.
If chars is unicode, S will be converted to unicode before stripping.
"""
return s.strip(chars)
# Strip leading tabs and spaces
def lstrip(s, chars=None):
"""lstrip(s [,chars]) -> string
Return a copy of the string s with leading whitespace removed.
If chars is given and not None, remove characters in chars instead.
"""
return s.lstrip(chars)
# Strip trailing tabs and spaces
def rstrip(s, chars=None):
"""rstrip(s [,chars]) -> string
Return a copy of the string s with trailing whitespace removed.
If chars is given and not None, remove characters in chars instead.
"""
return s.rstrip(chars)
# Split a string into a list of space/tab-separated words
def split(s, sep=None, maxsplit=-1):
"""split(s [,sep [,maxsplit]]) -> list of strings
Return a list of the words in the string s, using sep as the
delimiter string. If maxsplit is given, splits at no more than
maxsplit places (resulting in at most maxsplit+1 words). If sep
is not specified or is None, any whitespace string is a separator.
(split and splitfields are synonymous)
"""
return s.split(sep, maxsplit)
splitfields = split
# Split a string into a list of space/tab-separated words
def rsplit(s, sep=None, maxsplit=-1):
"""rsplit(s [,sep [,maxsplit]]) -> list of strings
Return a list of the words in the string s, using sep as the
delimiter string, starting at the end of the string and working
to the front. If maxsplit is given, at most maxsplit splits are
done. If sep is not specified or is None, any whitespace string
is a separator.
"""
return s.rsplit(sep, maxsplit)
# Join fields with optional separator
def join(words, sep = ' '):
"""join(list [,sep]) -> string
Return a string composed of the words in list, with
intervening occurrences of sep. The default separator is a
single space.
(joinfields and join are synonymous)
"""
return sep.join(words)
joinfields = join
# Find substring, raise exception if not found
def index(s, *args):
"""index(s, sub [,start [,end]]) -> int
Like find but raises ValueError when the substring is not found.
"""
return s.index(*args)
# Find last substring, raise exception if not found
def rindex(s, *args):
"""rindex(s, sub [,start [,end]]) -> int
Like rfind but raises ValueError when the substring is not found.
"""
return s.rindex(*args)
# Count non-overlapping occurrences of substring
def count(s, *args):
"""count(s, sub[, start[,end]]) -> int
Return the number of occurrences of substring sub in string
s[start:end]. Optional arguments start and end are
interpreted as in slice notation.
"""
return s.count(*args)
# Find substring, return -1 if not found
def find(s, *args):
"""find(s, sub [,start [,end]]) -> in
Return the lowest index in s where substring sub is found,
such that sub is contained within s[start,end]. Optional
arguments start and end are interpreted as in slice notation.
Return -1 on failure.
"""
return s.find(*args)
# Find last substring, return -1 if not found
def rfind(s, *args):
"""rfind(s, sub [,start [,end]]) -> int
Return the highest index in s where substring sub is found,
such that sub is contained within s[start,end]. Optional
arguments start and end are interpreted as in slice notation.
Return -1 on failure.
"""
return s.rfind(*args)
# for a bit of speed
_float = float
_int = int
_long = long
# Convert string to float
def atof(s):
"""atof(s) -> float
Return the floating point number represented by the string s.
"""
return _float(s)
# Convert string to integer
def atoi(s , base=10):
"""atoi(s [,base]) -> int
Return the integer represented by the string s in the given
base, which defaults to 10. The string s must consist of one
or more digits, possibly preceded by a sign. If base is 0, it
is chosen from the leading characters of s, 0 for octal, 0x or
0X for hexadecimal. If base is 16, a preceding 0x or 0X is
accepted.
"""
return _int(s, base)
# Convert string to long integer
def atol(s, base=10):
"""atol(s [,base]) -> long
Return the long integer represented by the string s in the
given base, which defaults to 10. The string s must consist
of one or more digits, possibly preceded by a sign. If base
is 0, it is chosen from the leading characters of s, 0 for
octal, 0x or 0X for hexadecimal. If base is 16, a preceding
0x or 0X is accepted. A trailing L or l is not accepted,
unless base is 0.
"""
return _long(s, base)
# Left-justify a string
def ljust(s, width, *args):
"""ljust(s, width[, fillchar]) -> string
Return a left-justified version of s, in a field of the
specified width, padded with spaces as needed. The string is
never truncated. If specified the fillchar is used instead of spaces.
"""
return s.ljust(width, *args)
# Right-justify a string
def rjust(s, width, *args):
"""rjust(s, width[, fillchar]) -> string
Return a right-justified version of s, in a field of the
specified width, padded with spaces as needed. The string is
never truncated. If specified the fillchar is used instead of spaces.
"""
return s.rjust(width, *args)
# Center a string
def center(s, width, *args):
"""center(s, width[, fillchar]) -> string
Return a center version of s, in a field of the specified
width. padded with spaces as needed. The string is never
truncated. If specified the fillchar is used instead of spaces.
"""
return s.center(width, *args)
# Zero-fill a number, e.g., (12, 3) --> '012' and (-3, 3) --> '-03'
# Decadent feature: the argument may be a string or a number
# (Use of this is deprecated; it should be a string as with ljust c.s.)
def zfill(x, width):
"""zfill(x, width) -> string
Pad a numeric string x with zeros on the left, to fill a field
of the specified width. The string x is never truncated.
"""
if not isinstance(x, basestring):
x = repr(x)
return x.zfill(width)
# Expand tabs in a string.
# Doesn't take non-printing chars into account, but does understand \n.
def expandtabs(s, tabsize=8):
"""expandtabs(s [,tabsize]) -> string
Return a copy of the string s with all tab characters replaced
by the appropriate number of spaces, depending on the current
column, and the tabsize (default 8).
"""
return s.expandtabs(tabsize)
# Character translation through look-up table.
def translate(s, table, deletions=""):
"""translate(s,table [,deletions]) -> string
Return a copy of the string s, where all characters occurring
in the optional argument deletions are removed, and the
remaining characters have been mapped through the given
translation table, which must be a string of length 256. The
deletions argument is not allowed for Unicode strings.
"""
if deletions:
return s.translate(table, deletions)
else:
# Add s[:0] so that if s is Unicode and table is an 8-bit string,
# table is converted to Unicode. This means that table *cannot*
# be a dictionary -- for that feature, use u.translate() directly.
return s.translate(table + s[:0])
# Capitalize a string, e.g. "aBc dEf" -> "Abc def".
def capitalize(s):
"""capitalize(s) -> string
Return a copy of the string s with only its first character
capitalized.
"""
return s.capitalize()
# Substring replacement (global)
def replace(s, old, new, maxsplit=-1):
"""replace (str, old, new[, maxsplit]) -> string
Return a copy of string str with all occurrences of substring
old replaced by new. If the optional argument maxsplit is
given, only the first maxsplit occurrences are replaced.
"""
return s.replace(old, new, maxsplit)
# Try importing optional built-in module "strop" -- if it exists,
# it redefines some string operations that are 100-1000 times faster.
# It also defines values for whitespace, lowercase and uppercase
# that match <ctype.h>'s definitions.
try:
from strop import maketrans, lowercase, uppercase, whitespace
letters = lowercase + uppercase
except ImportError:
pass # Use the original versions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,240 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
"""UUID (universally unique identifiers) as specified in RFC 4122.
This module provides the UUID class and the functions uuid1(), uuid3(),
uuid4(), uuid5() for generating version 1, 3, 4, and 5 UUIDs respectively.
This module works with Python 2.3 or higher."""
__author__ = 'Ka-Ping Yee <ping@zesty.ca>'
__date__ = '$Date: 2005/11/30 11:51:58 $'.split()[1].replace('/', '-')
__version__ = '$Revision: 1.10 $'
RESERVED_NCS, RFC_4122, RESERVED_MICROSOFT, RESERVED_FUTURE = [
'reserved for NCS compatibility', 'specified in RFC 4122',
'reserved for Microsoft compatibility', 'reserved for future definition']
class UUID(object):
"""Instances of the UUID class represent UUIDs as specified in RFC 4122.
Converting a UUID to a string using str() produces a string in the form
"{12345678-1234-1234-1234-123456789abc}". The UUID constructor accepts
a similar string (braces and hyphens optional), or six integer arguments
(with 32-bit, 16-bit, 16-bit, 8-bit, 8-bit, and 48-bit values
respectively). UUID objects have the following attributes:
bytes gets or sets the UUID as a 16-byte string
urn gets the UUID as a URN as specified in RFC 4122
variant gets or sets the UUID variant as one of the constants
RESERVED_NCS, RFC_4122, RESERVED_MICROSOFT, RESERVED_FUTURE
version gets or sets the UUID version number (1 through 5)
"""
def __init__(self, *args):
"""Create a UUID either from a string representation in hexadecimal
or from six integers (32-bit time_low, 16-bit time_mid, 16-bit
time_hi_ver, 8-bit clock_hi_res, 8-bit clock_low, 48-bit node)."""
if len(args) == 1:
digits = args[0].replace('urn:', '').replace('uuid:', '')
digits = digits.replace('{', '').replace('}', '').replace('-', '')
assert len(digits) == 32, ValueError('badly formed UUID string')
time_low = int(digits[:8], 16)
time_mid = int(digits[8:12], 16)
time_hi_ver = int(digits[12:16], 16)
clock_hi_res = int(digits[16:18], 16)
clock_low = int(digits[18:20], 16)
node = int(digits[20:32], 16)
else:
(time_low, time_mid, time_hi_ver,
clock_hi_res, clock_low, node) = args
assert 0 <= time_low < 0x100000000, ValueError('time_low out of range')
assert 0 <= time_mid < 1<<16, ValueError('time_mid out of range')
assert 0 <= time_hi_ver < 1<<16, ValueError('time_hi_ver out of range')
assert 0 <= clock_hi_res < 1<<8, ValueError('clock_hi_res out of range')
assert 0 <= clock_low < 1<<8, ValueError('clock_low out of range')
assert 0 <= node < 0x1000000000000, ValueError('node out of range')
self.time_low = time_low
self.time_mid = time_mid
self.time_hi_ver = time_hi_ver
self.clock_hi_res = clock_hi_res
self.clock_low = clock_low
self.node = node
def __cmp__(self, other):
return cmp(self.bytes, getattr(other, 'bytes', other))
def __str__(self):
return '{%08x-%04x-%04x-%02x%02x-%012x}' % (
self.time_low, self.time_mid, self.time_hi_ver,
self.clock_hi_res, self.clock_low, self.node)
def __repr__(self):
return 'UUID(%r)' % str(self)
def get_bytes(self):
def byte(n):
return chr(n & 0xff)
return (byte(self.time_low >> 24) + byte(self.time_low >> 16) +
byte(self.time_low >> 8) + byte(self.time_low) +
byte(self.time_mid >> 8) + byte(self.time_mid) +
byte(self.time_hi_ver >> 8) + byte(self.time_hi_ver) +
byte(self.clock_hi_res) + byte(self.clock_low) +
byte(self.node >> 40) + byte(self.node >> 32) +
byte(self.node >> 24) + byte(self.node >> 16) +
byte(self.node >> 8) + byte(self.node))
def set_bytes(self, bytes):
values = map(ord, bytes)
self.time_low = ((values[0] << 24) + (values[1] << 16) +
(values[2] << 8) + values[3])
self.time_mid = (values[4] << 8) + values[5]
self.time_hi_ver = (values[6] << 8) + values[7]
self.clock_hi_res = values[8]
self.clock_low = values[9]
self.node = ((values[10] << 40) + (values[11] << 32) +
(values[12] << 24) + (values[13] << 16) +
(values[14] << 8) + values[15])
bytes = property(get_bytes, set_bytes)
def get_urn(self):
return 'urn:uuid:%08x-%04x-%04x-%02x%02x-%012x' % (
self.time_low, self.time_mid, self.time_hi_ver,
self.clock_hi_res, self.clock_low, self.node)
urn = property(get_urn)
def get_variant(self):
if not self.clock_hi_res & 0x80:
return RESERVED_NCS
elif not self.clock_hi_res & 0x40:
return RFC_4122
elif not self.clock_hi_res & 0x20:
return RESERVED_MICROSOFT
else:
return RESERVED_FUTURE
def set_variant(self, variant):
if variant == RESERVED_NCS:
self.clock_hi_res &= 0x7f
elif variant == RFC_4122:
self.clock_hi_res &= 0x3f
self.clock_hi_res |= 0x80
elif variant == RESERVED_MICROSOFT:
self.clock_hi_res &= 0x1f
self.clock_hi_res |= 0xc0
elif variant == RESERVED_FUTURE:
self.clock_hi_res &= 0x1f
self.clock_hi_res |= 0xe0
else:
raise ValueError('illegal variant identifier')
variant = property(get_variant, set_variant)
def get_version(self):
return self.time_hi_ver >> 12
def set_version(self, version):
assert 1 <= version <= 5, ValueError('illegal version number')
self.time_hi_ver &= 0x0fff
self.time_hi_ver |= (version << 12)
version = property(get_version, set_version)
def unixgetaddr(program):
"""Get the hardware address on a Unix machine."""
from os import popen
for line in popen(program):
words = line.lower().split()
if 'hwaddr' in words:
addr = words[words.index('hwaddr') + 1]
return int(addr.replace(':', ''), 16)
if 'ether' in words:
addr = words[words.index('ether') + 1]
return int(addr.replace(':', ''), 16)
def wingetaddr(program):
"""Get the hardware address on a Windows machine."""
from os import popen
for line in popen(program + ' /all'):
if line.strip().lower().startswith('physical address'):
addr = line.split(':')[-1].strip()
return int(addr.replace('-', ''), 16)
def getaddr():
"""Get the hardware address as a 48-bit integer."""
from os.path import join, isfile
for dir in ['/sbin', '/usr/sbin', r'c:\windows',
r'c:\windows\system', r'c:\windows\system32']:
if isfile(join(dir, 'ifconfig')):
return unixgetaddr(join(dir, 'ifconfig'))
if isfile(join(dir, 'ipconfig.exe')):
return wingetaddr(join(dir, 'ipconfig.exe'))
def uuid1():
"""Generate a UUID based on the time and hardware address."""
from time import time
from random import randrange
nanoseconds = int(time() * 1e9)
# 0x01b21dd213814000 is the number of 100-ns intervals between the
# UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00.
timestamp = int(nanoseconds/100) + 0x01b21dd213814000
clock = randrange(1<<16) # don't use stable storage
time_low = timestamp & (0x100000000 - 1)
time_mid = (timestamp >> 32) & 0xffff
time_hi_ver = (timestamp >> 48) & 0x0fff
clock_low = clock & 0xff
clock_hi_res = (clock >> 8) & 0x3f
node = getaddr()
uuid = UUID(time_low, time_mid, time_hi_ver, clock_low, clock_hi_res, node)
uuid.variant = RFC_4122
uuid.version = 1
return uuid
def uuid3(namespace, name):
"""Generate a UUID from the MD5 hash of a namespace UUID and a name."""
try:
from hashlib import md5
except ImportError:
from md5 import md5
uuid = UUID(0, 0, 0, 0, 0, 0)
uuid.bytes = md5(namespace.bytes + name).digest()[:16]
uuid.variant = RFC_4122
uuid.version = 3
return uuid
def uuid4():
"""Generate a random UUID."""
try:
from os import urandom
except:
from random import randrange
uuid = UUID(randrange(1<<32L), randrange(1<<16), randrange(1<<16),
randrange(1<<8), randrange(1<<8), randrange(1<<48L))
else:
uuid = UUID(0, 0, 0, 0, 0, 0)
uuid.bytes = urandom(16)
uuid.variant = RFC_4122
uuid.version = 4
return uuid
def uuid5(namespace, name):
"""Generate a UUID from the SHA-1 hash of a namespace UUID and a name."""
try:
from hashlib import sha1
except ImportError:
from sha import sha as sha1
uuid = UUID(0, 0, 0, 0, 0, 0)
uuid.bytes = sha1(namespace.bytes + name).digest()[:16]
uuid.variant = RFC_4122
uuid.version = 5
return uuid
NAMESPACE_DNS = UUID('{6ba7b810-9dad-11d1-80b4-00c04fd430c8}')
NAMESPACE_URL = UUID('{6ba7b811-9dad-11d1-80b4-00c04fd430c8}')
NAMESPACE_OID = UUID('{6ba7b812-9dad-11d1-80b4-00c04fd430c8}')
NAMESPACE_X500 = UUID('{6ba7b814-9dad-11d1-80b4-00c04fd430c8}')

View File

@@ -0,0 +1,19 @@
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
from paste.script.serve import ensure_port_cleanup
from paste.translogger import TransLogger
def run_server(wsgi_app, global_conf, host='localhost',
port=8080):
from wsgiutils import wsgiServer
import logging
logged_app = TransLogger(wsgi_app)
port = int(port)
# For some reason this is problematic on this server:
ensure_port_cleanup([(host, port)], maxtries=2, sleeptime=0.5)
app_map = {'': logged_app}
server = wsgiServer.WSGIServer((host, port), app_map)
logged_app.logger.info('Starting HTTP server on http://%s:%s',
host, port)
server.serve_forever()