# (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 argparse
import os
import os.path
import pkg_resources
import re
import sys
from pyramid.compat import input_

_bad_chars_re = re.compile('[^a-zA-Z0-9_]')


def main(argv=sys.argv, quiet=False):
    command = PCreateCommand(argv, quiet)
    try:
        return command.run()
    except KeyboardInterrupt:  # pragma: no cover
        return 1


class PCreateCommand(object):
    verbosity = 1  # required
    parser = argparse.ArgumentParser(
        description="""\
Render Pyramid scaffolding to an output directory.

Note: As of Pyramid 1.8, this command is deprecated. Use
pyramid-cookiecutter-starter instead:
https://github.com/Pylons/pyramid-cookiecutter-starter
""",
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    parser.add_argument(
        '-s',
        '--scaffold',
        dest='scaffold_name',
        action='append',
        help=(
            "Add a scaffold to the create process "
            "(multiple -s args accepted)"
        ),
    )
    parser.add_argument(
        '-t',
        '--template',
        dest='scaffold_name',
        action='append',
        help=(
            'A backwards compatibility alias for '
            '-s/--scaffold.  Add a scaffold to the '
            'create process (multiple -t args accepted)'
        ),
    )
    parser.add_argument(
        '-l',
        '--list',
        dest='list',
        action='store_true',
        help="List all available scaffold names",
    )
    parser.add_argument(
        '--list-templates',
        dest='list',
        action='store_true',
        help=(
            "A backwards compatibility alias for -l/--list. "
            "List all available scaffold names."
        ),
    )
    parser.add_argument(
        '--package-name',
        dest='package_name',
        action='store',
        help='Package name to use. The name provided is '
        'assumed to be a valid Python package name, and '
        'will not be validated. By default the package '
        'name is derived from the value of '
        'output_directory.',
    )
    parser.add_argument(
        '--simulate',
        dest='simulate',
        action='store_true',
        help='Simulate but do no work',
    )
    parser.add_argument(
        '--overwrite',
        dest='overwrite',
        action='store_true',
        help='Always overwrite',
    )
    parser.add_argument(
        '--interactive',
        dest='interactive',
        action='store_true',
        help='When a file would be overwritten, interrogate '
        '(this is the default, but you may specify it to '
        'override --overwrite)',
    )
    parser.add_argument(
        '--ignore-conflicting-name',
        dest='force_bad_name',
        action='store_true',
        default=False,
        help='Do create a project even if the chosen name '
        'is the name of an already existing / importable '
        'package.',
    )
    parser.add_argument(
        'output_directory',
        nargs='?',
        default=None,
        help='The directory where the project will be ' 'created.',
    )

    pyramid_dist = pkg_resources.get_distribution("pyramid")

    def __init__(self, argv, quiet=False):
        self.quiet = quiet
        self.args = self.parser.parse_args(argv[1:])
        if not self.args.interactive and not self.args.overwrite:
            self.args.interactive = True
        self.scaffolds = self.all_scaffolds()

    def run(self):
        if self.args.list:
            return self.show_scaffolds()
        if not self.args.scaffold_name and not self.args.output_directory:
            if not self.quiet:  # pragma: no cover
                self.parser.print_help()
                self.out('')
                self.show_scaffolds()
            return 2

        if not self.validate_input():
            return 2
        self._warn_pcreate_deprecated()

        return self.render_scaffolds()

    @property
    def output_path(self):
        return os.path.abspath(os.path.normpath(self.args.output_directory))

    @property
    def project_vars(self):
        output_dir = self.output_path
        project_name = os.path.basename(os.path.split(output_dir)[1])
        if self.args.package_name is None:
            pkg_name = _bad_chars_re.sub(
                '', project_name.lower().replace('-', '_')
            )
            safe_name = pkg_resources.safe_name(project_name)
        else:
            pkg_name = self.args.package_name
            safe_name = pkg_name
        egg_name = pkg_resources.to_filename(safe_name)

        # get pyramid package version
        pyramid_version = self.pyramid_dist.version

        # map pyramid package version of the documentation branch ##
        # if version ends with 'dev' then docs version is 'master'
        if self.pyramid_dist.version[-3:] == 'dev':
            pyramid_docs_branch = 'master'
        else:
            # if not version is not 'dev' find the version.major_version string
            # and combine it with '-branch'
            version_match = re.match(r'(\d+\.\d+)', self.pyramid_dist.version)
            if version_match is not None:
                pyramid_docs_branch = "%s-branch" % version_match.group()
            # if can not parse the version then default to 'latest'
            else:
                pyramid_docs_branch = 'latest'

        return {
            'project': project_name,
            'package': pkg_name,
            'egg': egg_name,
            'pyramid_version': pyramid_version,
            'pyramid_docs_branch': pyramid_docs_branch,
        }

    def render_scaffolds(self):
        props = self.project_vars
        output_dir = self.output_path
        for scaffold_name in self.args.scaffold_name:
            for scaffold in self.scaffolds:
                if scaffold.name == scaffold_name:
                    scaffold.run(self, output_dir, props)
        return 0

    def show_scaffolds(self):
        scaffolds = sorted(self.scaffolds, key=lambda x: x.name)
        if scaffolds:
            max_name = max([len(t.name) for t in scaffolds])
            self.out('Available scaffolds:')
            for scaffold in scaffolds:
                self.out(
                    '  %s:%s  %s'
                    % (
                        scaffold.name,
                        ' ' * (max_name - len(scaffold.name)),
                        scaffold.summary,
                    )
                )
        else:
            self.out('No scaffolds available')
        return 0

    def all_scaffolds(self):
        scaffolds = []
        eps = list(pkg_resources.iter_entry_points('pyramid.scaffold'))
        for entry in eps:
            try:
                scaffold_class = entry.load()
                scaffold = scaffold_class(entry.name)
                scaffolds.append(scaffold)
            except Exception as e:  # pragma: no cover
                self.out(
                    'Warning: could not load entry point %s (%s: %s)'
                    % (entry.name, e.__class__.__name__, e)
                )
        return scaffolds

    def out(self, msg):  # pragma: no cover
        if not self.quiet:
            print(msg)

    def validate_input(self):
        if not self.args.scaffold_name:
            self.out(
                'You must provide at least one scaffold name: '
                '-s <scaffold name>'
            )
            self.out('')
            self.show_scaffolds()
            return False
        if not self.args.output_directory:
            self.out('You must provide a project name')
            return False
        available = [x.name for x in self.scaffolds]
        diff = set(self.args.scaffold_name).difference(available)
        if diff:
            self.out('Unavailable scaffolds: %s' % ", ".join(sorted(diff)))
            return False

        pkg_name = self.project_vars['package']

        if pkg_name == 'site' and not self.args.force_bad_name:
            self.out(
                'The package name "site" has a special meaning in '
                'Python. Are you sure you want to use it as your '
                'project\'s name?'
            )
            return self.confirm_bad_name(
                'Really use "{0}"?: '.format(pkg_name)
            )

        # check if pkg_name can be imported (i.e. already exists in current
        # $PYTHON_PATH, if so - let the user confirm
        pkg_exists = True
        try:
            # use absolute imports
            __import__(pkg_name, globals(), locals(), [], 0)
        except ImportError:
            pkg_exists = False
        if not pkg_exists:
            return True

        if self.args.force_bad_name:
            return True
        self.out(
            'A package named "{0}" already exists, are you sure you want '
            'to use it as your project\'s name?'.format(pkg_name)
        )
        return self.confirm_bad_name('Really use "{0}"?: '.format(pkg_name))

    def confirm_bad_name(self, prompt):  # pragma: no cover
        answer = input_('{0} [y|N]: '.format(prompt))
        return answer.strip().lower() == 'y'

    def _warn_pcreate_deprecated(self):
        self.out(
            '''\
Note: As of Pyramid 1.8, this command is deprecated. Use a specific
cookiecutter instead:
https://github.com/pylons/?query=cookiecutter
'''
        )


if __name__ == '__main__':  # pragma: no cover
    sys.exit(main() or 0)
