def parse_changes_file(filename, directory=None):
    """
    Parse a .changes file and return a dput.changes.Change instance with
    parsed changes file data. The optional directory argument refers to the
    base directory where the referred files from the changes file are expected
    to be located.
    """
    _c = Changes(filename=filename)
    _c.set_directory(directory)
    return(_c) CONFIG_LOCATIONS = {
    "/usr/share/dput-ng/": 30,
    "/etc/dput.d/": 20,
    os.path.expanduser("~/.dput.d"): 10,
    "skel/": 100
}
DPUT_CONFIG_LOCATIONS = {
    "/etc/dput.cf": 15,
    os.path.expanduser("~/.dput.cf"): 5
}
SCHEMA_DIRS = [
    "/usr/share/dput-ng/schemas",
    "skel/schemas"
]
def _write_upload_log(logfile, full_log):
def _enable_debugging(level):
def mangle_sys():
def maybe_print_traceback(debug_level, stack):
def get_local_username(): def run_pre_hooks(changes, profile):
    for name, hook in get_hooks(profile):
        if 'pre' in hook and hook['pre']:
            run_hook(name, hook, changes, profile)
        if 'pre' not in hook and 'post' not in hook:
            logger.warning("Hook: %s has no pre/post ordering. Assuming "
                           "pre.")
            run_hook(name, hook, changes, profile) def run_post_hooks(changes, profile):
    for name, hook in get_hooks(profile):
        if 'post' in hook and hook['post']:
            run_hook(name, hook, changes, profile) def run_command(command, env=None):
    """
    Run a synchronized command. The argument must be a list of arguments.
    Returns a triple (stdout, stderr, exit_status)

    If there was a problem to start the supplied command, (None, None, -1) is
    returned
    """
    if not isinstance(command, list):
        command = shlex.split(command)
    try:
        pipe = subprocess.Popen(command,
                                env=env,
                                shell=False,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)
    except OSError as e:
        logger.error("Could not execute %s: %s" % (" ".join(command), e))
        return (None, None, -1)
    (output, stderr) = pipe.communicate()
    output = output.decode('utf-8', errors='replace')
    stderr = stderr.decode('utf-8', errors='replace')
    #if pipe.returncode != 0:
    #   error("Command %s returned failure: %s" % (" ".join(command), stderr))
    return (output, stderr, pipe.returncode) def get_obj(cls, checker_method):  # checker_method is a bad name.
    """
    Get an object by plugin def (``checker_method``) in class ``cls`` (such
    as ``hooks``).
    """
    logger.trace("Attempting to resolve %s %s" % (cls, checker_method))
    try:
        config = load_config(cls, checker_method)
        validate_object('plugin', config, "%s/%s" % (cls, checker_method))

        if config is None or config == {}:
            raise NoSuchConfigError("No such config")
    except NoSuchConfigError:
        logger.debug("failed to resolve config %s" % (checker_method))
        return None
    path = config['path']
    logger.trace("loading %s %s" % (cls, path))
    try:
        return load_obj(path)
    except ImportError as e:
        logger.warning("failed to resolve path %s: %s" % (path, e))
        return None def make_delayed_upload(conf, delayed_days):
    """
    DELAYED uploads to ftp-master eventually means to use another incoming
    directory instead of the default. This is easy enough to be implemented

    Mangles the supplied configuration object
    """
    incoming_directory = os.path.join(
        conf['incoming'],
        "DELAYED",
        "%d-day" % (delayed_days)
    )
    logger.debug("overriding upload directory to %s" % (incoming_directory))
    conf['incoming'] = incoming_directory def force_passive_ftp_upload(conf):
    """
    Force FTP to use passive mode.

    Mangles the supplied configuration object
    """
    logger.debug("overriding configuration to force FTP passive mode")
    conf['passive_ftp'] = True class DputConfigurationError(DputError):
    """
    Errors in the parsing or retrieving of configuration files should raise
    an instance of this, or a subclass thereof.
    """
    pass class DputError(Exception):
    """
    Most basic dput error. All other Exceptions must inherit from this
    when it is sensable to do so.
    """
    pass class UploadException(DputError):
    """
    Thrown when there's an error uploading, or creating an uploader. Usually
    thrown by a subclass of the :class:`dput.uploader.AbstractUploader`
    """
    pass
import os
import abc
import sys
import tempfile
import shutil
import dput.profile
from contextlib import contextmanager
from dput.changes import parse_changes_file
from dput.core import logger, _write_upload_log
from dput.hook import run_pre_hooks, run_post_hooks
from dput.util import (run_command, get_obj)
from dput.overrides import (make_delayed_upload, force_passive_ftp_upload)
from dput.exceptions import (DputConfigurationError, DputError,
                             UploadException)
    if not simulate or simulate >= 2:
        obj.initialize()
    obj._pre_hook()
    try:
        yield obj
    finally:
        if not simulate:
            obj._post_hook()
        if not simulate or simulate >= 2:
            obj.shutdown()


def determine_logfile(changes, conf, args):
    """
    Figure out what logfile to write to. This is mostly an internal
    implementation. Returns the file to log to, given a changes and
    profile.
    """
    # dak requires '<package>_<version>_<[a-zA-Z0-9+-]+>.changes'

    # XXX: Correct --force behavior
    logfile = changes.get_changes_file()  # XXX: Check for existing one
    xtns = [".changes", ".dud"]

    for xtn in xtns:
        if logfile.endswith(xtn):
            logfile = "%s.%s.upload" % (logfile[:-len(xtn)], conf['name'])
            break
    else:
        raise UploadException("File %s does not look like a .changes file" % (
            changes.get_filename()
        ))

    if (
        os.access(logfile, os.R_OK) and
        os.stat(logfile).st_size > 0 and
        not args.force
    ):

        raise UploadException("""Package %s was already uploaded to %s
If you want to upload nonetheless, use --force or remove %s""" % (
            changes.get_package_name(),
            conf['name'],
            logfile
        ))

    logger.debug("Writing log to %s" % (logfile))
    return logfile


def should_write_logfile(args):
    return not args.simulate and not args.check_only and not args.no_upload_log


def check_modules(profile):
    if 'hooks' in profile:
        for hook in profile['hooks']:
            obj = get_obj('hooks', hook)
            if obj is None:
                raise DputConfigurationError(
                    "Error: no such hook '%s'" % (
                        hook
                    )
                )


class DputNamespace(dict):
    def __getattr__(self, key):
        return self[key]

    def __setattr__(self, key, val):
        self[key] = val


def invoke_dput_simple(changes, host, **kwargs):
    changes = parse_changes_file(changes, os.path.dirname(changes))
    # XXX: Abspath???
    config = {
        "host": host,
        "debug": False,
        "config": None,
        "force": False,
        "simulate": False,
        "check_only": None,
        "no_upload_log": None,
        "full_upload_log": None,
        "delayed": None,
        "passive": None,
    }
    config.update(kwargs)
    config = DputNamespace(config)

    return invoke_dput(changes, config)


def invoke_dput(changes, args):
    """
    .. warning::
       This method may change names. Please use it via :func:`dput.upload`.
       also, please don't depend on args, that's likely to change shortly.

    Given a changes file ``changes``, and arguments to dput ``args``,
    upload a package to the archive that makes sense.

    """
    profile = dput.profile.load_profile(args.host)
    check_modules(profile)

    fqdn = None
    if "fqdn" in profile:
        fqdn = profile['fqdn']
    else:
        fqdn = profile['name']

    logfile = determine_logfile(changes, profile, args)
    tmp_logfile = tempfile.NamedTemporaryFile()
    if should_write_logfile(args):
        full_upload_log = profile["full_upload_log"]
        if args.full_upload_log:
            full_upload_log = args.full_upload_log

        _write_upload_log(tmp_logfile.name, full_upload_log)