Source code for skelpy.main

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""entry point to skelpy

main.py is the front-end module that parses the command line options and creates
the project-structure accordingly.
"""

from __future__ import absolute_import, print_function

import sys
import logging
import os

from skelpy.makers import settings, get_maker
from skelpy.utils.defaultsubparse import DefaultSubcommandArgParser
import skelpy.utils.helpers as helpers


def _setup_arg_parser():
    """Setup command-line option parser

    """
    parser = DefaultSubcommandArgParser()
    subparsers = parser.add_subparsers(title='sub-command')

    #: main options
    #: setting 'prog' prevents showing subparser's name in the usage output
    main_parser = subparsers.add_parser('template',
                                        prog='skelpy',
                                        description='A simple template tool for a python project.',
                                        epilog="For the 'license' sub-command, see 'skelpy license --help'.")

    main_parser.add_argument('projectName', metavar='ProjectName', nargs='?',
                             default='', help='project(directory) name to create')
    main_parser.add_argument('-F', '--format', default='basic',
                             choices=['basic', 'src'],
                             help='format of directory structure [default: %(default)s]')
    main_parser.add_argument('-T', '--test', default='pytest',
                             choices=['unittest', 'pytest'],
                             help='testing tool [default: %(default)s]')
    main_parser.add_argument('-q', '--quiet', action='store_true',
                             help='skip editing setup.cfg file [default: %(default)s]')
    main_parser.add_argument('-m', '--merge', action='store_true',
                             help='overlap project onto existing directory [default: %(default)s]')
    main_parser.add_argument('-f', '--force', action='store_true',
                             help='overwrite existing files [default: %(default)s]')
    main_parser.add_argument('-v', '--verbose', action='store_true',
                             help='show verbose messages [default: %(default)s]')

    #: license sub-command options
    lic_parser = subparsers.add_parser('license', prog='skelpy license')
    lic_parser.add_argument('license', metavar='LICENSE', nargs='?', default=None,
                            help='new license to create or change to')
    lic_parser.add_argument('-l', '--list', action='store_true',
                            help='show licenses supported by templater')
    lic_parser.add_argument('-v', '--verbose', action='store_false',
                            help='show verbose messages [default: %(default)s]')

    #: assign a front-end function to each sub-parser
    main_parser.set_defaults(func=_skel)
    lic_parser.set_defaults(func=_license)
    #: set the default sub-parser to main_parser
    parser.set_default_subparser('template')
    #: combine usage messages of main_parser and lic_parser
    main_parser.usage = parser.combine_usage([main_parser, lic_parser])
    #: replace the top-most parser's usage and help messages for main sub-parser's
    parser.format_usage = main_parser.format_usage
    parser.format_help = main_parser.format_help
    #: for easy reference to sub-parsers
    parser.main_parser = main_parser
    parser.lic_parser = lic_parser

    return parser


def _setup_logger():
    """setup the root logger

    Returns:
        none

    """
    FORMAT = '%(asctime)-s %(message)s'
    logging.basicConfig(format=FORMAT, level=logging.INFO)


def _parse_projectName(projectName):
    """parse :attr:`projectName` to get sheer projectName and projectDir

    After parsing, this method updates ``projectDir`` and ``projectName`` values
    in :attr:`settings`

    Args:
        projectName (str): project name the user input, possibly contains path

    Returns:
        tuple: (projectDir, projectName)

        .. note::
           projectDir should include the *projectName* as its the last components,
           In other words, the following statement must be True.

               ``os.path.split(projectDir)[-1] == projectName``

    """
    root = helpers.root_path()

    if not projectName:
        projectDir = os.getcwd()
        #: run in the root directory but did not provide project name
        if helpers.is_rootDir(projectDir):
            return projectDir, ''
        #: not in the root directory
        projectName = os.path.split(projectDir)[-1]
    elif os.path.abspath(projectName) == root:   # maybe too much?
        return root, ''
    else:
        #: remove trailing os.sep
        if projectName.endswith(os.sep):
            projectName, _ = os.path.split(projectName)

        #: if the path given is not absolute, abspath() returns getcwd() + path
        #: abspath() includes normpath()
        projectDir = os.path.abspath(projectName)
        projectName = os.path.split(projectDir)[-1]

    return projectDir, projectName


def _skel(opts, parser):
    """create the skeleton of a python project

    Args:
        opts (dict): arguments passed from command line, i.e, sys.argv[1:]
        |FYI, sub-command is not passed
        parser (obj): instance of :class:`DefaultSubcommandArgParser` class

    Returns:
        bool

    """
    projectDir, projectName = _parse_projectName(opts['projectName'])
    if not projectName:
        sys.stderr.write(
            "[skelpy] Invalid project name: {}\n".format(projectName))
        return False

    opts['projectDir'] = projectDir
    opts['projectName'] = projectName
    settings.update(opts)

    maker_cls = get_maker('project')
    if not maker_cls:
        return False

    maker = maker_cls(**settings)
    if not maker.generate():
        return False

    return True


def _license(opts, parser):
    """do license sub-command jobs, i.e., creating or changing a license

    Args:
        opts (dict): arguments passed from command line, i.e, sys.argv[1:]
        |FYI, sub-command is not passed
        parser (obj): instance of :class:`DefaultSubcommandArgParser` class

    Returns:
        None

    """
    #: if neither list option nor license argument is given
    if not opts.get('list') and not opts.get('license'):
        # ArgumentParser.error() terminates the process.
        # no need to call return of sys.exit()
        parser.lic_parser.error(
            "Either '-l/--list' option or 'LICENSE' argument is required.")
        return False

    maker_cls = get_maker('license_change')
    if not maker_cls:
        sys.stderr.write(
            "[skelpy] Maker module not found: 'license_change.py'\n")
        return False

    maker = maker_cls(**opts)
    return maker.generate()


[docs]def run(argv=None): """driver to run ``skelpy`` """ _setup_logger() if argv is None: argv = sys.argv[1:] try: parser = _setup_arg_parser() opts = vars(parser.parse_args(argv)) except Exception as e: sys.stderr.write("[skelpy] " + repr(e) + "\n") sys.stderr.write("For help, use --help\n") return 2 # MAIN BODY # if not opts.pop('verbose'): logging.disable(logging.CRITICAL) func = opts.pop('func') if func(opts, parser): sys.stdout.write('Successfully done.\n') return 0 else: sys.stdout.write('Failed.\n') parser.print_usage() return 1
if __name__ == "__main__": sys.exit(run())