0001# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
0002# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
0003import sys
0004import os
0005import inspect
0006import copydir
0007import command
0008
0009from paste.util.template import paste_script_template_renderer
0010
0011class Template(object):
0012
0013    # Subclasses must define:
0014    # _template_dir (or template_dir())
0015    # summary
0016
0017    # Variables this template uses (mostly for documentation now)
0018    # a list of instances of var()
0019    vars = []
0020
0021    # Eggs that should be added as plugins:
0022    egg_plugins = []
0023
0024    # Templates that must be applied first:
0025    required_templates = []
0026
0027    # Use Cheetah for substituting templates:
0028    use_cheetah = False
0029    # If true, then read all the templates to find the variables:
0030    read_vars_from_templates = False
0031
0032    # You can also give this function/method to use something other
0033    # than Cheetah or string.Template.  The function should be of the
0034    # signature template_renderer(content, vars, filename=filename).
0035    # Careful you don't turn this into a method by putting a function
0036    # here (without staticmethod)!
0037    template_renderer = None
0038
0039    def __init__(self, name):
0040        self.name = name
0041        self._read_vars = None
0042
0043    def module_dir(self):
0044        """
0045        Returns the module directory of this template.
0046        """
0047        mod = sys.modules[self.__class__.__module__]
0048        return os.path.dirname(mod.__file__)
0049
0050    def template_dir(self):
0051        assert self._template_dir is not None, (
0052            "Template %r didn't set _template_dir" % self)
0053        return os.path.join(self.module_dir(), self._template_dir)
0054
0055    def run(self, command, output_dir, vars):
0056        self.pre(command, output_dir, vars)
0057        self.write_files(command, output_dir, vars)
0058        self.post(command, output_dir, vars)
0059
0060    def check_vars(self, vars, cmd):
0061        expect_vars = self.read_vars(cmd)
0062        if not expect_vars:
0063            # Assume that variables aren't defined
0064            return vars
0065        converted_vars = {}
0066        unused_vars = vars.copy()
0067        errors = []
0068        for var in expect_vars:
0069            if var.name not in unused_vars:
0070                if cmd.interactive:
0071                    prompt = 'Enter %s' % var.full_description()
0072                    response = cmd.challenge(prompt, var.default, var.should_echo)
0073                    converted_vars[var.name] = response
0074                elif var.default is command.NoDefault:
0075                    errors.append('Required variable missing: %s'
0076                                  % var.full_description())
0077                else:
0078                    converted_vars[var.name] = var.default
0079            else:
0080                converted_vars[var.name] = unused_vars.pop(var.name)
0081        if errors:
0082            raise command.BadCommand(
0083                'Errors in variables:\n%s' % '\n'.join(errors))
0084        converted_vars.update(unused_vars)
0085        vars.update(converted_vars)
0086        return converted_vars
0087
0088    def read_vars(self, command=None):
0089        if self._read_vars is not None:
0090            return self._read_vars
0091        assert (not self.read_vars_from_templates
0092                or self.use_cheetah), (
0093            "You can only read variables from templates if using Cheetah")
0094        if not self.read_vars_from_templates:
0095            self._read_vars = self.vars
0096            return self.vars
0097
0098        vars = self.vars[:]
0099        var_names = [var.name for var in self.vars]
0100        read_vars = find_args_in_dir(
0101            self.template_dir(),
0102            verbose=command and command.verbose > 1).items()
0103        read_vars.sort()
0104        for var_name, var in read_vars:
0105            if var_name not in var_names:
0106                vars.append(var)
0107        self._read_vars = vars
0108        return vars
0109
0110    def write_files(self, command, output_dir, vars):
0111        template_dir = self.template_dir()
0112        if not os.path.exists(output_dir):
0113            print "Creating directory %s" % output_dir
0114            if not command.simulate:
0115                # Don't let copydir create this top-level directory,
0116                # since copydir will svn add it sometimes:
0117                os.makedirs(output_dir)
0118        copydir.copy_dir(template_dir, output_dir,
0119                         vars,
0120                         verbosity=command.verbose,
0121                         simulate=command.options.simulate,
0122                         interactive=command.interactive,
0123                         overwrite=command.options.overwrite,
0124                         indent=1,
0125                         use_cheetah=self.use_cheetah,
0126                         template_renderer=self.template_renderer)
0127
0128    def print_vars(self, indent=0):
0129        vars = self.read_vars()
0130        var.print_vars(vars)
0131
0132    def pre(self, command, output_dir, vars):
0133        """
0134        Called before template is applied.
0135        """
0136        pass
0137
0138    def post(self, command, output_dir, vars):
0139        """
0140        Called after template is applied.
0141        """
0142        pass
0143
0144NoDefault = command.NoDefault
0145
0146class var(object):
0147
0148    def __init__(self, name, description,
0149                 default='', should_echo=True):
0150        self.name = name
0151        self.description = description
0152        self.default = default
0153        self.should_echo = should_echo
0154
0155    def __repr__(self):
0156        return '<%s %s default=%r should_echo=%s>' % (
0157            self.__class__.__name__,
0158            self.name, self.default, self.should_echo)
0159
0160    def full_description(self):
0161        if self.description:
0162            return '%s (%s)' % (self.name, self.description)
0163        else:
0164            return self.name
0165
0166    def print_vars(cls, vars, indent=0):
0167        max_name = max([len(v.name) for v in vars])
0168        for var in vars:
0169            if var.description:
0170                print '%s%s%s  %s' % (
0171                    ' '*indent,
0172                    var.name,
0173                    ' '*(max_name-len(var.name)),
0174                    var.description)
0175            else:
0176                print '  %s' % var.name
0177            if var.default is not command.NoDefault:
0178                print '      default: %r' % var.default
0179            if var.should_echo is True:
0180                print '      should_echo: %s' % var.should_echo
0181        print
0182
0183    print_vars = classmethod(print_vars)
0184
0185class BasicPackage(Template):
0186
0187    _template_dir = 'templates/basic_package'
0188    summary = "A basic setuptools-enabled package"
0189    vars = [
0190        var('version', 'Version (like 0.1)'),
0191        var('description', 'One-line description of the package'),
0192        var('long_description', 'Multi-line description (in reST)'),
0193        var('keywords', 'Space-separated keywords/tags'),
0194        var('author', 'Author name'),
0195        var('author_email', 'Author email'),
0196        var('url', 'URL of homepage'),
0197        var('license_name', 'License name'),
0198        var('zip_safe', 'True/False: if the package can be distributed as a .zip file', default=False),
0199        ]
0200
0201    template_renderer = staticmethod(paste_script_template_renderer)
0202
0203_skip_variables = ['VFN', 'currentTime', 'self', 'VFFSL', 'dummyTrans',
0204                   'getmtime', 'trans']
0205
0206def find_args_in_template(template):
0207    if isinstance(template, (str, unicode)):
0208        # Treat as filename:
0209        import Cheetah.Template
0210        template = Cheetah.Template.Template(file=template)
0211    if not hasattr(template, 'body'):
0212        # Don't know...
0213        return None
0214    method = template.body
0215    args, varargs, varkw, defaults = inspect.getargspec(method)
0216    defaults=list(defaults or [])
0217    vars = []
0218    while args:
0219        if len(args) == len(defaults):
0220            default = defaults.pop(0)
0221        else:
0222            default = command.NoDefault
0223        arg = args.pop(0)
0224        if arg in _skip_variables:
0225            continue
0226        # @@: No way to get description yet
0227        vars.append(
0228            var(arg, description=None,
0229                default=default))
0230    return vars
0231
0232def find_args_in_dir(dir, verbose=False):
0233    all_vars = {}
0234    for fn in os.listdir(dir):
0235        if fn.startswith('.') or fn == 'CVS' or fn == '_darcs':
0236            continue
0237        full = os.path.join(dir, fn)
0238        if os.path.isdir(full):
0239            inner_vars = find_args_in_dir(full)
0240        elif full.endswith('_tmpl'):
0241            inner_vars = {}
0242            found = find_args_in_template(full)
0243            if found is None:
0244                # Couldn't read variables
0245                if verbose:
0246                    print 'Template %s has no parseable variables' % full
0247                continue
0248            for var in found:
0249                inner_vars[var.name] = var
0250        else:
0251            # Not a template, don't read it
0252            continue
0253        if verbose:
0254            print 'Found variable(s) %s in Template %s' % (
0255                ', '.join(inner_vars.keys()), full)
0256        for var_name, var in inner_vars.items():
0257            # Easy case:
0258            if var_name not in all_vars:
0259                all_vars[var_name] = var
0260                continue
0261            # Emit warnings if the variables don't match well:
0262            cur_var = all_vars[var_name]
0263            if not cur_var.description:
0264                cur_var.description = var.description
0265            elif (cur_var.description and var.description
0266                  and var.description != cur_var.description):
0267                print >> sys.stderr, (
0268                    "Variable descriptions do not match: %s: %s and %s"
0269                    % (var_name, cur_var.description, var.description))
0270            if (cur_var.default is not command.NoDefault
0271                and var.default is not command.NoDefault
0272                and cur_var.default != var.default):
0273                print >> sys.stderr, (
0274                    "Variable defaults do not match: %s: %r and %r"
0275                    % (var_name, cur_var.default, var.default))
0276    return all_vars