diff -Nru click-0.4.43+16.04.20160203/artwork/logo.svg click-6.7/artwork/logo.svg --- click-0.4.43+16.04.20160203/artwork/logo.svg 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/artwork/logo.svg 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,75 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff -Nru click-0.4.43+16.04.20160203/autogen.sh click-6.7/autogen.sh --- click-0.4.43+16.04.20160203/autogen.sh 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/autogen.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,7 +0,0 @@ -#! /bin/sh -set -e -intltoolize --copy --force --automake -autoreconf -fi -# We want to keep po/click.pot in the source package. -sed -i '/rm .*\$(GETTEXT_PACKAGE)\.pot/s/ \$(GETTEXT_PACKAGE)\.pot//' \ - po/Makefile.in.in diff -Nru click-0.4.43+16.04.20160203/bin/click click-6.7/bin/click --- click-0.4.43+16.04.20160203/bin/click 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/bin/click 1970-01-01 00:00:00.000000000 +0000 @@ -1,110 +0,0 @@ -#! /usr/bin/python3 - -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Operations on Click packages.""" - -from __future__ import print_function - -from optparse import OptionParser -import os -import signal -import sys -from textwrap import dedent - -# Support running from the build tree. -sys.path.insert(0, os.path.join(sys.path[0], os.pardir)) - -import gi -gi.require_version('Click', '0.4') - -# There is an unfortunate name clash with -# https://pypi.python.org/pypi/click; try to detect this and take evasive -# action. -import click -if not getattr(click, "_CLICK_IS_A_PACKAGING_FORMAT_", None): - import site - wrong_click_mods = [ - mod for mod in sys.modules if mod.split(".")[0] == "click"] - for mod in wrong_click_mods: - del sys.modules[mod] - try: - user_site_index = sys.path.index(site.getusersitepackages()) - except ValueError: - print( - "Cannot start click due to a conflict with a different " - "locally-installed Python 'click' package. Remove it using " - "Python packaging tools and try again.", - file=sys.stderr) - sys.exit(1) - del sys.path[user_site_index] - -from click import commands - - -def fix_stdout(): - if sys.version >= "3": - # Force encoding to UTF-8 even in non-UTF-8 locales. - import io - sys.stdout = io.TextIOWrapper( - sys.stdout.detach(), encoding="UTF-8", line_buffering=True) - else: - # Avoid having to do .encode("UTF-8") everywhere. - import codecs - sys.stdout = codecs.EncodedFile(sys.stdout, "UTF-8") - - def null_decode(input, errors="strict"): - return input, len(input) - - sys.stdout.decode = null_decode - - -def main(): - fix_stdout() - # Python's default handling of SIGPIPE is not helpful to us. - signal.signal(signal.SIGPIPE, signal.SIG_DFL) - - parser = OptionParser(dedent("""\ - %%prog COMMAND [options] - - Commands are as follows ('%%prog COMMAND --help' for more): - - %s""") % commands.help_text()) - - parser.disable_interspersed_args() - _, args = parser.parse_args() - if not args: - parser.print_help() - return 0 - command = args[0] - args = args[1:] - - if command == "help": - if args and args[0] in commands.all_commands: - mod = commands.load_command(args[0]) - mod.run(["--help"]) - else: - parser.print_help() - return 0 - - if command not in commands.all_commands: - parser.error("unknown command: %s" % command) - mod = commands.load_command(command) - return mod.run(args) - - -if __name__ == "__main__": - sys.exit(main()) diff -Nru click-0.4.43+16.04.20160203/CHANGES click-6.7/CHANGES --- click-0.4.43+16.04.20160203/CHANGES 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/CHANGES 2017-01-06 22:40:17.000000000 +0000 @@ -0,0 +1,351 @@ +Click Changelog +=============== + +This contains all major version changes between Click releases. + +Version 6.7 +----------- + +(bugfix release; released on January 6th 2017) + +- Make `click.progressbar` work with `codecs.open` files. See #637. +- Fix bug in bash completion with nested subcommands. See #639. +- Fix test runner not saving caller env correctly. See #644. +- Fix handling of SIGPIPE. See #626 +- Deal with broken Windows environments such as Google App Engine's. See #711. + +Version 6.6 +----------- + +(bugfix release; released on April 4th 2016) + +- Fix bug in `click.Path` where it would crash when passed a `-`. See #551. + +Version 6.4 +----------- + +(bugfix release; released on March 24th 2016) + +- Fix bug in bash completion where click would discard one or more trailing + arguments. See #471. + +Version 6.3 +----------- + +(bugfix release; released on February 22 2016) + +- Fix argument checks for interpreter invoke with `-m` and `-c` + on Windows. +- Fixed a bug that cased locale detection to error out on Python 3. + + +Version 6.2 +----------- + +(bugfix release, released on November 27th 2015) + +- Correct fix for hidden progress bars. + +Version 6.1 +----------- + +(bugfix release, released on November 27th 2015) + +- Resolved an issue with invisible progress bars no longer rendering. +- Disable chain commands with subcommands as they were inherently broken. +- Fix `MissingParameter` not working without parameters passed. + +Version 6.0 +----------- + +(codename "pow pow", released on November 24th 2015) + +- Optimized the progressbar rendering to not render when it did not + actually change. +- Explicitly disallow nargs=-1 with a set default. +- The context is now closed before it's popped from the stack. +- Added support for short aliases for the false flag on toggles. +- Click will now attempt to aid you with debugging locale errors + better by listing with the help of the OS what locales are + available. +- Click used to return byte strings on Python 2 in some unit-testing + situations. This has been fixed to correctly return unicode strings + now. +- For Windows users on Python 2, Click will now handle Unicode more + correctly handle Unicode coming in from the system. This also has + the disappointing side effect that filenames will now be always + unicode by default in the `Path` type which means that this can + introduce small bugs for code not aware of this. +- Added a `type` parameter to `Path` to force a specific string type + on the value. +- For users running Python on Windows the `echo`) and `prompt` functions + now work with full unicode functionality in the Python windows console + by emulating an output stream. This also applies to getting the + virtual output and input streams via `click.get_text_stream(...)`. +- Unittests now always force a certain virtual terminal width. +- Added support for allowing dashes to indicate standard streams to the + `Path` type. +- Multi commands in chain mode no longer propagate arguments left over + from parsing to the callbacks. It's also now disallowed through an + exception when optional arguments are attached to multi commands if chain + mode is enabled. +- Relaxed restriction that disallowed chained commands to have other + chained commands as child commands. +- Arguments with positive nargs can now have defaults implemented. + Previously this configuration would often result in slightly unexpected + values be returned. + +Version 5.1 +----------- + +(bugfix release, released on 17th August 2015) + +- Fix a bug in `pass_obj` that would accidentally pass the context too. + +Version 5.0 +----------- + +(codename "tok tok", released on 16th August 2015) + +- Removed various deprecated functionality. +- Atomic files now only accept the `w` mode. +- Change the usage part of help output for very long commands to wrap + their arguments onto the next line, indented by 4 spaces. +- Fix a bug where return code and error messages were incorrect when + using ``CliRunner``. +- added `get_current_context`. +- added a `meta` dictionary to the context which is shared across the + linked list of contexts to allow click utilities to place state there. +- introduced `Context.scope`. +- The `echo` function is now threadsafe: It calls the `write` method of the + underlying object only once. +- `prompt(hide_input=True)` now prints a newline on `^C`. +- Click will now warn if users are using ``unicode_literals``. +- Click will now ignore the ``PAGER`` environment variable if it is empty or + contains only whitespace. +- The `click-contrib` GitHub organization was created. + +Version 4.1 +----------- + +(bugfix release, released on July 14th 2015) + +- Fix a bug where error messages would include a trailing `None` string. +- Fix a bug where Click would crash on docstrings with trailing newlines. +- Support streams with encoding set to `None` on Python 3 by barfing with + a better error. +- Handle ^C in less-pager properly. +- Handle return value of `None` from `sys.getfilesystemencoding` +- Fix crash when writing to unicode files with `click.echo`. +- Fix type inference with multiple options. + +Version 4.0 +----------- + +(codename "zoom zoom", released on March 31st 2015) + +- Added `color` parameters to lots of interfaces that directly or indirectly + call into echoing. This previously was always autodetection (with the + exception of the `echo_via_pager` function). Now you can forcefully + enable or disable it, overriding the auto detection of Click. +- Added an `UNPROCESSED` type which does not perform any type changes which + simplifies text handling on 2.x / 3.x in some special advanced usecases. +- Added `NoSuchOption` and `BadOptionUsage` exceptions for more generic + handling of errors. +- Added support for handling of unprocessed options which can be useful in + situations where arguments are forwarded to underlying tools. +- Added `max_content_width` parameter to the context which can be used to + change the maximum width of help output. By default Click will not format + content for more than 80 characters width. +- Added support for writing prompts to stderr. +- Fix a bug when showing the default for multiple arguments. +- Added support for custom subclasses to `option` and `argument`. +- Fix bug in ``clear()`` on Windows when colorama is installed. +- Reject ``nargs=-1`` for options properly. Options cannot be variadic. +- Fixed an issue with bash completion not working properly for commands with + non ASCII characters or dashes. +- Added a way to manually update the progressbar. +- Changed the formatting of missing arguments. Previously the internal + argument name was shown in error messages, now the metavar is shown if + passed. In case an automated metavar is selected, it's stripped of + extra formatting first. + +Version 3.3 +----------- + +(bugfix release, released on September 8th 2014) + +- Fixed an issue with error reporting on Python 3 for invalid forwarding + of commands. + +Version 3.2 +----------- + +(bugfix release, released on August 22nd 2014) + +- Added missing `err` parameter forwarding to the `secho` function. +- Fixed default parameters not being handled properly by the context + invoke method. This is a backwards incompatible change if the function + was used improperly. See :ref:`upgrade-to-3.2` for more information. +- Removed the `invoked_subcommands` attribute largely. It is not possible + to provide it to work error free due to how the parsing works so this + API has been deprecated. See :ref:`upgrade-to-3.2` for more information. +- Restored the functionality of `invoked_subcommand` which was broken as + a regression in 3.1. + +Version 3.1 +----------- + +(bugfix release, released on August 13th 2014) + +- Fixed a regression that caused contexts of subcommands to be + created before the parent command was invoked which was a + regression from earlier Click versions. + +Version 3.0 +----------- + +(codename "clonk clonk", released on August 12th 2014) + +- formatter now no longer attempts to accomodate for terminals + smaller than 50 characters. If that happens it just assumes + a minimal width. +- added a way to not swallow exceptions in the test system. +- added better support for colors with pagers and ways to + override the autodetection. +- the CLI runner's result object now has a traceback attached. +- improved automatic short help detection to work better with + dots that do not terminate sentences. +- when definining options without actual valid option strings + now, Click will give an error message instead of silently + passing. This should catch situations where users wanted to + created arguments instead of options. +- Restructured Click internally to support vendoring. +- Added support for multi command chaining. +- Added support for defaults on options with `multiple` and + options and arguments with `nargs != 1`. +- label passed to `progressbar` is no longer rendered with + whitespace stripped. +- added a way to disable the standalone mode of the `main` + method on a Click command to be able to handle errors better. +- added support for returning values from command callbacks. +- added simplifications for printing to stderr from `echo`. +- added result callbacks for groups. +- entering a context multiple times defers the cleanup until + the last exit occurs. +- added `open_file`. + +Version 2.6 +----------- + +(bugfix release, released on August 11th 2014) + +- Fixed an issue where the wrapped streams on Python 3 would be reporting + incorrect values for seekable. + +Version 2.5 +----------- + +(bugfix release, released on July 28th 2014) + +- Fixed a bug with text wrapping on Python 3. + +Version 2.4 +----------- + +(bugfix release, released on July 4th 2014) + +- Corrected a bug in the change of the help option in 2.3. + +Version 2.3 +----------- + +(bugfix release, released on July 3rd 2014) + +- Fixed an incorrectly formatted help record for count options.' +- Add support for ansi code stripping on Windows if colorama + is not available. +- restored the Click 1.0 handling of the help parameter for certain + edge cases. + +Version 2.2 +----------- + +(bugfix release, released on June 26th 2014) + +- fixed tty detection on PyPy. +- fixed an issue that progress bars were not rendered when the + context manager was entered. + +Version 2.1 +----------- + +(bugfix release, released on June 14th 2014) + +- fixed the :func:`launch` function on windows. +- improved the colorama support on windows to try hard to not + screw up the console if the application is interrupted. +- fixed windows terminals incorrectly being reported to be 80 + characters wide instead of 79 +- use colorama win32 bindings if available to get the correct + dimensions of a windows terminal. +- fixed an issue with custom function types on Python 3. +- fixed an issue with unknown options being incorrectly reported + in error messages. + +Version 2.0 +----------- + +(codename "tap tap tap", released on June 6th 2014) + +- added support for opening stdin/stdout on Windows in + binary mode correctly. +- added support for atomic writes to files by going through + a temporary file. +- introduced :exc:`BadParameter` which can be used to easily perform + custom validation with the same error messages as in the type system. +- added :func:`progressbar`; a function to show progress bars. +- added :func:`get_app_dir`; a function to calculate the home folder + for configs. +- Added transparent handling for ANSI codes into the :func:`echo` + function through `colorama`. +- Added :func:`clear` function. +- Breaking change: parameter callbacks now get the parameter object + passed as second argument. There is legacy support for old callbacks + which will warn but still execute the script. +- Added :func:`style`, :func:`unstyle` and :func:`secho` for ANSI + styles. +- Added an :func:`edit` function that invokes the default editor. +- Added an :func:`launch` function that launches browsers and applications. +- nargs of -1 for arguments can now be forced to be a single item through + the required flag. It defaults to not required. +- setting a default for arguments now implicitly makes it non required. +- changed "yN" / "Yn" to "y/N" and "Y/n" in confirmation prompts. +- added basic support for bash completion. +- added :func:`getchar` to fetch a single character from the terminal. +- errors now go to stderr as intended. +- fixed various issues with more exotic parameter formats like DOS/Windows + style arguments. +- added :func:`pause` which works similar to the Windows ``pause`` cmd + built-in but becomes an automatic noop if the application is not run + through a terminal. +- added a bit of extra information about missing choice parameters. +- changed how the help function is implemented to allow global overriding + of the help option. +- added support for token normalization to implement case insensitive handling. +- added support for providing defaults for context settings. + +Version 1.1 +----------- + +(bugfix release, released on May 23rd 2014) + +- fixed a bug that caused text files in Python 2 to not accept + native strings. + +Version 1.0 +----------- + +(no codename, released on May 21st 2014) + +- Initial release. diff -Nru click-0.4.43+16.04.20160203/click/arfile.py click-6.7/click/arfile.py --- click-0.4.43+16.04.20160203/click/arfile.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/arfile.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,102 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Basic support for writing ar archive files. - -We do things this way so that Click packages can be created with minimal -dependencies (e.g. on non-Ubuntu systems). No read support is needed, since -Click packages are always installed on systems that have dpkg. - -Some method names and general approach come from the tarfile module in -Python's standard library; details of the format come from dpkg. -""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'ArFile', - ] - -import os -import shutil -import time - - -class ArFile: - def __init__(self, name=None, mode="w", fileobj=None): - if mode != "w": - raise ValueError("only mode 'w' is supported") - self.mode = mode - self.real_mode = "wb" - - if fileobj: - if name is None and hasattr(fileobj, "name"): - name = fileobj.name - if hasattr(fileobj, "mode"): - if fileobj.mode != "wb": - raise ValueError("fileobj must be opened with mode='wb'") - self._mode = fileobj.mode - self.opened_fileobj = False - else: - fileobj = open(name, self.real_mode) - self.opened_fileobj = True - self.name = name - self.fileobj = fileobj - self.closed = False - - def close(self): - if self.opened_fileobj: - self.fileobj.close() - self.closed = True - - def _check(self): - if self.closed: - raise IOError("ArFile %s is closed" % self.name) - - def __enter__(self): - self._check() - return self - - def __exit__(self, *args): - self.close() - - def add_magic(self): - self.fileobj.write(b"!\n") - - def add_header(self, name, size): - if len(name) > 15: - raise ValueError("ar member name '%s' length too long" % name) - if size > 9999999999: - raise ValueError("ar member size %d too large" % size) - header = ("%-16s%-12u0 0 100644 %-10d`\n" % ( - name, int(time.time()), size)).encode() - assert len(header) == 60 # sizeof(struct ar_hdr) - self.fileobj.write(header) - - def add_data(self, name, data): - size = len(data) - self.add_header(name, size) - self.fileobj.write(data) - if size & 1: - self.fileobj.write(b"\n") # padding - - def add_file(self, name, path): - with open(path, "rb") as fobj: - size = os.fstat(fobj.fileno()).st_size - self.add_header(name, size) - shutil.copyfileobj(fobj, self.fileobj) - if size & 1: - self.fileobj.write(b"\n") # padding diff -Nru click-0.4.43+16.04.20160203/click/_bashcomplete.py click-6.7/click/_bashcomplete.py --- click-0.4.43+16.04.20160203/click/_bashcomplete.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/click/_bashcomplete.py 2016-12-01 12:33:48.000000000 +0000 @@ -0,0 +1,83 @@ +import os +import re +from .utils import echo +from .parser import split_arg_string +from .core import MultiCommand, Option + + +COMPLETION_SCRIPT = ''' +%(complete_func)s() { + COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \\ + COMP_CWORD=$COMP_CWORD \\ + %(autocomplete_var)s=complete $1 ) ) + return 0 +} + +complete -F %(complete_func)s -o default %(script_names)s +''' + +_invalid_ident_char_re = re.compile(r'[^a-zA-Z0-9_]') + + +def get_completion_script(prog_name, complete_var): + cf_name = _invalid_ident_char_re.sub('', prog_name.replace('-', '_')) + return (COMPLETION_SCRIPT % { + 'complete_func': '_%s_completion' % cf_name, + 'script_names': prog_name, + 'autocomplete_var': complete_var, + }).strip() + ';' + + +def resolve_ctx(cli, prog_name, args): + ctx = cli.make_context(prog_name, args, resilient_parsing=True) + while ctx.protected_args + ctx.args and isinstance(ctx.command, MultiCommand): + a = ctx.protected_args + ctx.args + cmd = ctx.command.get_command(ctx, a[0]) + if cmd is None: + return None + ctx = cmd.make_context(a[0], a[1:], parent=ctx, resilient_parsing=True) + return ctx + + +def get_choices(cli, prog_name, args, incomplete): + ctx = resolve_ctx(cli, prog_name, args) + if ctx is None: + return + + choices = [] + if incomplete and not incomplete[:1].isalnum(): + for param in ctx.command.params: + if not isinstance(param, Option): + continue + choices.extend(param.opts) + choices.extend(param.secondary_opts) + elif isinstance(ctx.command, MultiCommand): + choices.extend(ctx.command.list_commands(ctx)) + + for item in choices: + if item.startswith(incomplete): + yield item + + +def do_complete(cli, prog_name): + cwords = split_arg_string(os.environ['COMP_WORDS']) + cword = int(os.environ['COMP_CWORD']) + args = cwords[1:cword] + try: + incomplete = cwords[cword] + except IndexError: + incomplete = '' + + for item in get_choices(cli, prog_name, args, incomplete): + echo(item) + + return True + + +def bashcomplete(cli, prog_name, complete_var, complete_instr): + if complete_instr == 'source': + echo(get_completion_script(prog_name, complete_var)) + return True + elif complete_instr == 'complete': + return do_complete(cli, prog_name) + return False diff -Nru click-0.4.43+16.04.20160203/click/build.py click-6.7/click/build.py --- click-0.4.43+16.04.20160203/click/build.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/build.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,341 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Building Click packages.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'ClickBuildError', - 'ClickBuilder', - 'ClickSourceBuilder', - ] - - -import contextlib -import hashlib -import io -import json -import os -import re -import shutil -import subprocess -import sys -import tarfile -import tempfile -from textwrap import dedent - -try: - import apt_pkg - apt_pkg.init_system() -except ImportError: - # "click build" is required to work with only the Python standard library. - pass - -from click import osextras -from click.arfile import ArFile -from click.preinst import static_preinst -from click.versions import spec_version - -from click.framework import ( - validate_framework, - ClickFrameworkInvalid, -) - - -@contextlib.contextmanager -def make_temp_dir(): - temp_dir = tempfile.mkdtemp(prefix="click") - try: - os.chmod(temp_dir, 0o755) - yield temp_dir - finally: - shutil.rmtree(temp_dir) - - -class FakerootTarFile(tarfile.TarFile): - """A version of TarFile which pretends all files are owned by root:root.""" - - def gettarinfo(self, *args, **kwargs): - tarinfo = super(FakerootTarFile, self).gettarinfo(*args, **kwargs) - tarinfo.uid = tarinfo.gid = 0 - tarinfo.uname = tarinfo.gname = "root" - return tarinfo - - -class ClickBuildError(Exception): - pass - - -class ClickBuilderBase: - def __init__(self): - self.file_map = {} - # From @Dpkg::Source::Package::tar_ignore_default_pattern. - # (more in ClickSourceBuilder) - self._ignore_patterns = [ - "*.click", - ".*.sw?", - "*~", - ",,*", - ".[#~]*", - ".arch-ids", - ".arch-inventory", - ".bzr", - ".bzr-builddeb", - ".bzr.backup", - ".bzr.tags", - ".bzrignore", - ".cvsignore", - ".git", - ".gitattributes", - ".gitignore", - ".gitmodules", - ".hg", - ".hgignore", - ".hgsigs", - ".hgtags", - ".shelf", - ".svn", - "CVS", - "DEADJOE", - "RCS", - "_MTN", - "_darcs", - "{arch}", - ] - - def add_ignore_pattern(self, pattern): - self._ignore_patterns.append(pattern) - - def add_file(self, source_path, dest_path): - self.file_map[source_path] = dest_path - - def read_manifest(self, manifest_path): - with io.open(manifest_path, encoding="UTF-8") as manifest: - try: - self.manifest = json.load(manifest) - except Exception as e: - raise ClickBuildError( - "Error reading manifest from %s: %s" % (manifest_path, e)) - keys = sorted(self.manifest) - for key in keys: - if key.startswith("_"): - print( - "Ignoring reserved dynamic key '%s'." % key, - file=sys.stderr) - del self.manifest[key] - - @property - def name(self): - return self.manifest["name"] - - @property - def version(self): - return self.manifest["version"] - - @property - def epochless_version(self): - return re.sub(r"^\d+:", "", self.version) - - @property - def maintainer(self): - return self.manifest["maintainer"] - - @property - def title(self): - return self.manifest["title"] - - @property - def architecture(self): - manifest_arch = self.manifest.get("architecture", "all") - if isinstance(manifest_arch, list): - return "multi" - else: - return manifest_arch - - -class ClickBuilder(ClickBuilderBase): - - def list_files(self, root_path): - for dirpath, _, filenames in os.walk(root_path): - rel_dirpath = os.path.relpath(dirpath, root_path) - if rel_dirpath == ".": - rel_dirpath = "" - for filename in filenames: - yield os.path.join(rel_dirpath, filename) - - def _filter_dot_click(self, tarinfo): - """Filter out attempts to include .click at the top level.""" - if tarinfo.name == './.click' or tarinfo.name.startswith('./.click/'): - return None - return tarinfo - - def _pack(self, temp_dir, control_dir, data_dir, package_path): - data_tar_path = os.path.join(temp_dir, "data.tar.gz") - with contextlib.closing(FakerootTarFile.open( - name=data_tar_path, mode="w:gz", format=tarfile.GNU_FORMAT - )) as data_tar: - data_tar.add(data_dir, arcname="./", filter=self._filter_dot_click) - - control_tar_path = os.path.join(temp_dir, "control.tar.gz") - control_tar = tarfile.open( - name=control_tar_path, mode="w:gz", format=tarfile.GNU_FORMAT) - control_tar.add(control_dir, arcname="./") - control_tar.close() - - with ArFile(name=package_path, mode="w") as package: - package.add_magic() - package.add_data("debian-binary", b"2.0\n") - package.add_data( - "_click-binary", ("%s\n" % spec_version).encode("UTF-8")) - package.add_file("control.tar.gz", control_tar_path) - package.add_file("data.tar.gz", data_tar_path) - - def _validate_framework(self, framework_string): - """Apply policy checks to framework declarations.""" - try: - validate_framework( - framework_string, ignore_missing_frameworks=True) - except ClickFrameworkInvalid as e: - raise ClickBuildError(str(e)) - - def build(self, dest_dir, manifest_path="manifest.json"): - with make_temp_dir() as temp_dir: - # Prepare data area. - root_path = os.path.join(temp_dir, "data") - - for source_path, dest_path in self.file_map.items(): - if dest_path.startswith("/"): - dest_path = dest_path[1:] - real_dest_path = os.path.join(root_path, dest_path) - shutil.copytree( - source_path, real_dest_path, symlinks=True, - ignore=shutil.ignore_patterns(*self._ignore_patterns)) - - # Prepare control area. - control_dir = os.path.join(temp_dir, "DEBIAN") - osextras.ensuredir(control_dir) - - if os.path.isabs(manifest_path): - full_manifest_path = manifest_path - else: - full_manifest_path = os.path.join(root_path, manifest_path) - self.read_manifest(full_manifest_path) - if "framework" in self.manifest: - self._validate_framework(self.manifest["framework"]) - - du_output = subprocess.check_output( - ["du", "-k", "-s", "--apparent-size", "."], - cwd=temp_dir, universal_newlines=True).rstrip("\n") - match = re.match(r"^(\d+)\s+\.$", du_output) - if not match: - raise Exception("du gave unexpected output '%s'" % du_output) - installed_size = match.group(1) - self.manifest["installed-size"] = installed_size - control_path = os.path.join(control_dir, "control") - osextras.ensuredir(os.path.dirname(control_path)) - with io.open(control_path, "w", encoding="UTF-8") as control: - print(dedent("""\ - Package: %s - Version: %s - Click-Version: %s - Architecture: %s - Maintainer: %s - Installed-Size: %s - Description: %s""" % ( - self.name, self.version, spec_version, self.architecture, - self.maintainer, installed_size, self.title)), - file=control) - - # Control file names must not contain a dot, hence "manifest" - # rather than "manifest.json" in the control area. - real_manifest_path = os.path.join(control_dir, "manifest") - with io.open( - real_manifest_path, "w", encoding="UTF-8") as manifest: - print( - json.dumps( - self.manifest, ensure_ascii=False, sort_keys=True, - indent=4, separators=(",", ": ")), - file=manifest) - os.unlink(full_manifest_path) - os.chmod(real_manifest_path, 0o644) - - md5sums_path = os.path.join(control_dir, "md5sums") - with open(md5sums_path, "w") as md5sums: - for path in sorted(self.list_files(root_path)): - md5 = hashlib.md5() - p = os.path.join(root_path, path) - if not os.path.exists(p): - continue - with open(p, "rb") as f: - while True: - buf = f.read(16384) - if not buf: - break - md5.update(buf) - print("%s %s" % (md5.hexdigest(), path), file=md5sums) - - preinst_path = os.path.join(control_dir, "preinst") - with open(preinst_path, "w") as preinst: - preinst.write(static_preinst) - - # Pack everything up. - package_name = "%s_%s_%s.click" % ( - self.name, self.epochless_version, self.architecture) - package_path = os.path.join(dest_dir, package_name) - self._pack(temp_dir, control_dir, root_path, package_path) - return package_path - - -class ClickSourceBuilder(ClickBuilderBase): - - def __init__(self): - super(ClickSourceBuilder, self).__init__() - # From @Dpkg::Source::Package::tar_ignore_default_pattern. - # (more in ClickBuilderBase) - self._ignore_patterns += [ - "*.a", - ".be", - ".deps", - "*.la", - "*.o", - "*.so", - ] - - def build(self, dest_dir, manifest_path=None): - with make_temp_dir() as temp_dir: - root_path = os.path.join(temp_dir, "source") - for source_path, dest_path in self.file_map.items(): - if dest_path.startswith("/"): - dest_path = dest_path[1:] - real_dest_path = os.path.join(root_path, dest_path) - shutil.copytree( - source_path, real_dest_path, symlinks=True, - ignore=shutil.ignore_patterns(*self._ignore_patterns)) - - real_manifest_path = os.path.join(root_path, "manifest.json") - if manifest_path is not None: - shutil.copy2(manifest_path, real_manifest_path) - os.chmod(real_manifest_path, 0o644) - self.read_manifest(real_manifest_path) - - package_name = "%s_%s.tar.gz" % (self.name, self.epochless_version) - package_path = os.path.join(dest_dir, package_name) - with contextlib.closing(FakerootTarFile.open( - name=package_path, mode="w:gz", format=tarfile.GNU_FORMAT - )) as tar: - tar.add(root_path, arcname="./") - return package_path diff -Nru click-0.4.43+16.04.20160203/click/chroot.py click-6.7/click/chroot.py --- click-0.4.43+16.04.20160203/click/chroot.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/chroot.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,712 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Authors: Colin Watson , -# Brian Murray -# Michael Vogt -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Chroot management for building Click packages.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - "ClickChroot", - "ClickChrootException", - "ClickChrootAlreadyExistsException", - "ClickChrootDoesNotExistException", - ] - -try: - from urllib.error import URLError - from urllib.request import urlopen -except ImportError: - from urllib2 import URLError, urlopen -import os -import pwd -import re -import shutil -import stat -import subprocess -import sys -from textwrap import dedent -from xml.etree import ElementTree - - -framework_base = { - "ubuntu-sdk-13.10": "ubuntu-sdk-13.10", - # 14.04 - "ubuntu-sdk-14.04-html": "ubuntu-sdk-14.04", - "ubuntu-sdk-14.04-papi": "ubuntu-sdk-14.04", - "ubuntu-sdk-14.04-qml": "ubuntu-sdk-14.04", - # 14.10 - "ubuntu-sdk-14.10-html": "ubuntu-sdk-14.10", - "ubuntu-sdk-14.10-papi": "ubuntu-sdk-14.10", - "ubuntu-sdk-14.10-qml": "ubuntu-sdk-14.10", - # 15.04 - "ubuntu-sdk-15.04-html": "ubuntu-sdk-15.04", - "ubuntu-sdk-15.04-papi": "ubuntu-sdk-15.04", - "ubuntu-sdk-15.04-qml": "ubuntu-sdk-15.04", - # 15.10 - "ubuntu-sdk-15.10-html-dev1": "ubuntu-sdk-15.10-dev1", - "ubuntu-sdk-15.10-papi-dev1": "ubuntu-sdk-15.10-dev1", - "ubuntu-sdk-15.10-qml-dev1": "ubuntu-sdk-15.10-dev1", - } - - -framework_series = { - "ubuntu-sdk-13.10": "saucy", - "ubuntu-sdk-14.04": "trusty", - "ubuntu-sdk-14.10": "utopic", - "ubuntu-sdk-15.04": "vivid", - "ubuntu-sdk-15.10": "wily", - } - - -# Please keep the lists of package names sorted. -extra_packages = { - "ubuntu-sdk-13.10": [ - "libqt5opengl5-dev:{TARGET}", - "libqt5svg5-dev:{TARGET}", - "libqt5v8-5-dev:{TARGET}", - "libqt5webkit5-dev:{TARGET}", - "libqt5xmlpatterns5-dev:{TARGET}", - "qmlscene:{TARGET}", - "qt3d5-dev:{TARGET}", - "qt5-default:{TARGET}", - "qt5-qmake:{TARGET}", - "qtbase5-dev:{TARGET}", - "qtdeclarative5-dev:{TARGET}", - "qtmultimedia5-dev:{TARGET}", - "qtquick1-5-dev:{TARGET}", - "qtscript5-dev:{TARGET}", - "qtsensors5-dev:{TARGET}", - "qttools5-dev:{TARGET}", - "ubuntu-ui-toolkit-doc", - ], - "ubuntu-sdk-14.04": [ - "cmake", - "google-mock:{TARGET}", - "intltool", - "libboost1.54-dev:{TARGET}", - "libjsoncpp-dev:{TARGET}", - "libprocess-cpp-dev:{TARGET}", - "libproperties-cpp-dev:{TARGET}", - "libqt5svg5-dev:{TARGET}", - "libqt5webkit5-dev:{TARGET}", - "libqt5xmlpatterns5-dev:{TARGET}", - "libunity-scopes-dev:{TARGET}", - # bug #1316930, needed for autopilot - "python3", - "qmlscene:{TARGET}", - "qt3d5-dev:{TARGET}", - "qt5-default:{TARGET}", - "qtbase5-dev:{TARGET}", - "qtdeclarative5-dev:{TARGET}", - "qtdeclarative5-dev-tools", - "qtlocation5-dev:{TARGET}", - "qtmultimedia5-dev:{TARGET}", - "qtscript5-dev:{TARGET}", - "qtsensors5-dev:{TARGET}", - "qttools5-dev:{TARGET}", - "qttools5-dev-tools:{TARGET}", - "ubuntu-ui-toolkit-doc", - ], - "ubuntu-sdk-14.10": [ - "cmake", - "cmake-extras", - "google-mock:{TARGET}", - "intltool", - "libboost1.55-dev:{TARGET}", - "libcontent-hub-dev:{TARGET}", - "libjsoncpp-dev:{TARGET}", - "libnet-cpp-dev:{TARGET}", - "libprocess-cpp-dev:{TARGET}", - "libproperties-cpp-dev:{TARGET}", - "libqt5keychain0:{TARGET}", - "libqt5sensors5-dev:{TARGET}", - "libqt5svg5-dev:{TARGET}", - "libqt5webkit5-dev:{TARGET}", - "libqt5xmlpatterns5-dev:{TARGET}", - "libunity-scopes-dev:{TARGET}", - # bug #1316930, needed for autopilot - "python3", - "qml-module-qt-labs-settings:{TARGET}", - "qml-module-qtmultimedia:{TARGET}", - "qml-module-qtquick-layouts:{TARGET}", - "qml-module-qtsensors:{TARGET}", - "qml-module-qtwebkit:{TARGET}", - "qmlscene:{TARGET}", - "qt3d5-dev:{TARGET}", - "qt5-default:{TARGET}", - "qtdeclarative5-accounts-plugin:{TARGET}", - "qtdeclarative5-dev-tools", - "qtdeclarative5-folderlistmodel-plugin:{TARGET}", - "qtdeclarative5-localstorage-plugin:{TARGET}", - "qtdeclarative5-online-accounts-client0.1:{TARGET}", - "qtdeclarative5-particles-plugin:{TARGET}", - "qtdeclarative5-poppler1.0:{TARGET}", - "qtdeclarative5-qtlocation-plugin:{TARGET}", - "qtdeclarative5-qtorganizer-plugin:{TARGET}", - "qtdeclarative5-qtpositioning-plugin:{TARGET}", - "qtdeclarative5-u1db1.0:{TARGET}", - "qtdeclarative5-ubuntu-content0.1:{TARGET}", - "qtdeclarative5-ubuntu-download-manager0.1:{TARGET}", - "qtdeclarative5-ubuntu-mediascanner0.1:{TARGET}", - "qtdeclarative5-ubuntu-syncmonitor0.1:{TARGET}", - "qtdeclarative5-ubuntu-telephony-phonenumber0.1:{TARGET}", - "qtdeclarative5-ubuntu-ui-toolkit-plugin:{TARGET}", - "qtdeclarative5-usermetrics0.1:{TARGET}", - "qtdeclarative5-xmllistmodel-plugin:{TARGET}", - "qtlocation5-dev:{TARGET}", - "qtmultimedia5-dev:{TARGET}", - "qtscript5-dev:{TARGET}", - "qttools5-dev:{TARGET}", - "qttools5-dev-tools:{TARGET}", - "ubuntu-html5-theme:{TARGET}", - "ubuntu-ui-toolkit-doc", - ], - "ubuntu-sdk-15.04": [ - # the sdk libs - "ubuntu-sdk-libs:{TARGET}", - "ubuntu-sdk-libs-dev:{TARGET}", - # the native build tools - "ubuntu-sdk-libs-tools", - # FIXME: see - # http://pad.lv/~mvo/oxide/crossbuild-friendly/+merge/234093 - # we help the apt resolver here until the - # oxideqt-codecs/oxidec-codecs-extras is sorted - "oxideqt-codecs-extra", - ], - "ubuntu-sdk-15.10-dev1": [ - # the sdk libs - "ubuntu-sdk-libs:{TARGET}", - "ubuntu-sdk-libs-dev:{TARGET}", - # the native build tools - "ubuntu-sdk-libs-tools", - # FIXME: see - # http://pad.lv/~mvo/oxide/crossbuild-friendly/+merge/234093 - # we help the apt resolver here until the - # oxideqt-codecs/oxidec-codecs-extras is sorted - "oxideqt-codecs-extra", - ], - } - - -primary_arches = ["amd64", "i386"] - - -non_meta_re = re.compile(r'^[a-zA-Z0-9+,./:=@_-]+$') - - -GEOIP_SERVER = "http://geoip.ubuntu.com/lookup" - -overlay_ppa = "ci-train-ppa-service/stable-phone-overlay" - - -def get_geoip_country_code_prefix(): - click_no_local_mirror = os.environ.get('CLICK_NO_LOCAL_MIRROR', 'auto') - if click_no_local_mirror == '1': - return "" - try: - with urlopen(GEOIP_SERVER) as f: - xml_data = f.read() - et = ElementTree.fromstring(xml_data) - cc = et.find("CountryCode") - if not cc: - return "" - return cc.text.lower()+"." - except (ElementTree.ParseError, URLError): - pass - return "" - - -def generate_sources(series, native_arch, target_arch, - archive_mirror, ports_mirror, components): - """Generate a list of strings for apts sources.list. - Arguments: - series -- the distro series (e.g. vivid) - native_arch -- the native architecture (e.g. amd64) - target_arch -- the target architecture (e.g. armhf) - archive_mirror -- main mirror, e.g. http://archive.ubuntu.com/ubuntu - ports_mirror -- ports mirror, e.g. http://ports.ubuntu.com/ubuntu-ports - components -- the components as string, e.g. "main restricted universe" - """ - pockets = ['%s' % series] - for pocket in ['updates', 'security']: - pockets.append('%s-%s' % (series, pocket)) - sources = [] - # write binary lines - arches = [target_arch] - if native_arch != target_arch: - arches.append(native_arch) - for arch in arches: - if arch not in primary_arches: - mirror = ports_mirror - else: - mirror = archive_mirror - for pocket in pockets: - sources.append("deb [arch=%s] %s %s %s" % - (arch, mirror, pocket, components)) - # write source lines - for pocket in pockets: - sources.append("deb-src %s %s %s" % - (archive_mirror, pocket, components)) - return sources - - -def shell_escape(command): - escaped = [] - for arg in command: - if non_meta_re.match(arg): - escaped.append(arg) - else: - escaped.append("'%s'" % arg.replace("'", "'\\''")) - return " ".join(escaped) - - -def strip_dev_series_from_framework(framework): - """Remove trailing -dev[0-9]+ from a framework name""" - return re.sub(r'^(.*)-dev[0-9]+$', r'\1', framework) - - -class ClickChrootException(Exception): - """A generic issue with the chroot""" - pass - - -class ClickChrootAlreadyExistsException(ClickChrootException): - """The chroot already exists""" - pass - - -class ClickChrootDoesNotExistException(ClickChrootException): - """A chroot with that name does not exist yet""" - pass - - -class ClickChroot: - - DAEMON_POLICY = dedent("""\ - #!/bin/sh - while true; do - case "$1" in - -*) shift ;; - makedev) exit 0;; - x11-common) exit 0;; - *) exit 101;; - esac - done - """) - - def __init__(self, target_arch, framework, name=None, series=None, - session=None, chroots_dir=None): - self.target_arch = target_arch - self.framework = strip_dev_series_from_framework(framework) - if name is None: - name = "click" - self.name = name - if series is None: - series = framework_series[self.framework_base] - self.series = series - self.session = session - system_arch = subprocess.check_output( - ["dpkg", "--print-architecture"], - universal_newlines=True).strip() - self.native_arch = self._get_native_arch(system_arch, self.target_arch) - if chroots_dir is None: - chroots_dir = "/var/lib/schroot/chroots" - self.chroots_dir = chroots_dir - - if "SUDO_USER" in os.environ: - self.user = os.environ["SUDO_USER"] - elif "PKEXEC_UID" in os.environ: - self.user = pwd.getpwuid(int(os.environ["PKEXEC_UID"])).pw_name - else: - self.user = pwd.getpwuid(os.getuid()).pw_name - self.dpkg_architecture = self._dpkg_architecture() - - def _get_native_arch(self, system_arch, target_arch): - """Determine the proper native architecture for a chroot. - - Some combinations of system and target architecture do not require - cross-building, so in these cases we just create a chroot suitable - for native building. - """ - if (system_arch, target_arch) in ( - ("amd64", "i386"), - # This will only work if the system is running a 64-bit - # kernel; but there's no alternative since no i386-to-amd64 - # cross-compiler is available in the Ubuntu archive. - ("i386", "amd64"), - ): - return target_arch - else: - return system_arch - - def _dpkg_architecture(self): - dpkg_architecture = {} - command = ["dpkg-architecture", "-a%s" % self.target_arch] - env = dict(os.environ) - env["CC"] = "true" - # Force dpkg-architecture to recalculate everything rather than - # picking up values from the environment, which will be present when - # running the test suite under dpkg-buildpackage. - for key in list(env): - if key.startswith("DEB_BUILD_") or key.startswith("DEB_HOST_"): - del env[key] - lines = subprocess.check_output( - command, env=env, universal_newlines=True).splitlines() - for line in lines: - try: - key, value = line.split("=", 1) - except ValueError: - continue - dpkg_architecture[key] = value - if self.native_arch == self.target_arch: - # We may have overridden the native architecture (see - # _get_native_arch above), so we need to force DEB_BUILD_* to - # match. - for key in list(dpkg_architecture): - if key.startswith("DEB_HOST_"): - new_key = "DEB_BUILD_" + key[len("DEB_HOST_"):] - dpkg_architecture[new_key] = dpkg_architecture[key] - return dpkg_architecture - - def _generate_chroot_config(self, mount): - admin_user = "root" - users = [] - for key in ("users", "root-users", "source-root-users"): - users.append("%s=%s,%s" % (key, admin_user, self.user)) - with open(self.chroot_config, "w") as target: - target.write(dedent("""\ - [{full_name}] - description=Build chroot for click packages on {target_arch} - {users} - type=directory - profile=default - setup.fstab=click/fstab - # Not protocols or services see - # debian bug 557730 - setup.nssdatabases=sbuild/nssdatabases - union-type=overlayfs - directory={mount} - """).format(full_name=self.full_name, - target_arch=self.target_arch, - users="\n".join(users), - mount=mount)) - - def _generate_daemon_policy(self, mount): - daemon_policy = "%s/usr/sbin/policy-rc.d" % mount - with open(daemon_policy, "w") as policy: - policy.write(self.DAEMON_POLICY) - return daemon_policy - - def _generate_apt_proxy_file(self, mount, proxy): - apt_conf_d = os.path.join(mount, "etc", "apt", "apt.conf.d") - if not os.path.exists(apt_conf_d): - os.makedirs(apt_conf_d) - apt_conf_f = os.path.join(apt_conf_d, "99-click-chroot-proxy") - if proxy: - with open(apt_conf_f, "w") as f: - f.write(dedent("""\ - // proxy settings copied by click chroot - Acquire { - HTTP { - Proxy "%s"; - }; - }; - """) % proxy) - return apt_conf_f - - def _generate_finish_script(self, mount, build_pkgs): - finish_script = "%s/finish.sh" % mount - with open(finish_script, 'w') as finish: - finish.write(dedent("""\ - #!/bin/bash - set -e - # Configure target arch - dpkg --add-architecture {target_arch} - # Reload package lists - apt-get update || true - # Pull down signature requirements - apt-get -y --force-yes install gnupg ubuntu-keyring - """).format(target_arch=self.target_arch)) - if self.series == "vivid": - finish.write(dedent("""\ - apt-get -y --force-yes install software-properties-common - add-apt-repository -y ppa:{ppa} - echo "Package: *" \ - > /etc/apt/preferences.d/stable-phone-overlay.pref - echo \ - "Pin: release o=LP-PPA-{pin_ppa}" \ - >> /etc/apt/preferences.d/stable-phone-overlay.pref - echo "Pin-Priority: 1001" \ - >> /etc/apt/preferences.d/stable-phone-overlay.pref - """).format(ppa=overlay_ppa, - pin_ppa=re.sub('/', '-', overlay_ppa))) - finish.write(dedent("""\ - # Reload package lists - apt-get update || true - # Disable debconf questions - # so that automated builds won't prompt - echo set debconf/frontend Noninteractive | debconf-communicate - echo set debconf/priority critical | debconf-communicate - apt-get -y --force-yes dist-upgrade - # Install basic build tool set to match buildd - apt-get -y --force-yes install {build_pkgs} - # Set up expected /dev entries - if [ ! -r /dev/stdin ]; then - ln -s /proc/self/fd/0 /dev/stdin - fi - if [ ! -r /dev/stdout ]; then - ln -s /proc/self/fd/1 /dev/stdout - fi - if [ ! -r /dev/stderr ]; then - ln -s /proc/self/fd/2 /dev/stderr - fi - # Clean up - rm /finish.sh - apt-get clean - """).format(build_pkgs=' '.join(build_pkgs))) - return finish_script - - def _debootstrap(self, components, mount, archive_mirror, ports_mirror): - if self.native_arch in primary_arches: - mirror = archive_mirror - else: - mirror = ports_mirror - subprocess.check_call([ - "debootstrap", - "--arch", self.native_arch, - "--variant=buildd", - "--components=%s" % ','.join(components), - self.series, - mount, - mirror, - ]) - - @property - def framework_base(self): - if self.framework in framework_base: - return framework_base[self.framework] - else: - return self.framework - - @property - def full_name(self): - return "%s-%s-%s" % (self.name, self.framework_base, self.target_arch) - - @property - def full_session_name(self): - return "%s-%s" % (self.full_name, self.session) - - @property - def chroot_config(self): - return "/etc/schroot/chroot.d/%s" % self.full_name - - def exists(self): - command = ["schroot", "-c", self.full_name, "-i"] - with open("/dev/null", "w") as devnull: - return subprocess.call( - command, stdout=devnull, stderr=devnull) == 0 - - def _make_executable(self, path): - mode = stat.S_IMODE(os.stat(path).st_mode) - os.chmod(path, mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) - - def _make_cross_package(self, prefix): - if self.native_arch == self.target_arch: - return prefix - else: - target_tuple = self.dpkg_architecture["DEB_HOST_GNU_TYPE"] - return "%s-%s" % (prefix, target_tuple) - - def create(self, keep_broken_chroot_on_fail=False): - if self.exists(): - raise ClickChrootAlreadyExistsException( - "Chroot %s already exists" % self.full_name) - components = ["main", "restricted", "universe", "multiverse"] - mount = "%s/%s" % (self.chroots_dir, self.full_name) - proxy = None - if not proxy and "http_proxy" in os.environ: - proxy = os.environ["http_proxy"] - if not proxy: - proxy = subprocess.check_output( - 'unset x; eval "$(apt-config shell x Acquire::HTTP::Proxy)"; \ - echo "$x"', - shell=True, universal_newlines=True).strip() - build_pkgs = [ - # sort alphabetically - "apt-utils", - "build-essential", - "cmake", - "dpkg-cross", - "fakeroot", - "libc-dev:%s" % self.target_arch, - # build pkg names dynamically - self._make_cross_package("g++"), - self._make_cross_package("pkg-config"), - ] - for package in extra_packages.get(self.framework_base, []): - package = package.format(TARGET=self.target_arch) - build_pkgs.append(package) - os.makedirs(mount) - - country_code = get_geoip_country_code_prefix() - archive_mirror = "http://%sarchive.ubuntu.com/ubuntu" % country_code - ports_mirror = "http://%sports.ubuntu.com/ubuntu-ports" % country_code - # this doesn't work because we are running this under sudo - if 'DEBOOTSTRAP_MIRROR' in os.environ: - archive_mirror = os.environ['DEBOOTSTRAP_MIRROR'] - self._debootstrap(components, mount, archive_mirror, ports_mirror) - sources = generate_sources(self.series, self.native_arch, - self.target_arch, - archive_mirror, ports_mirror, - ' '.join(components)) - with open("%s/etc/apt/sources.list" % mount, "w") as sources_list: - for line in sources: - print(line, file=sources_list) - shutil.copy2("/etc/localtime", "%s/etc/" % mount) - shutil.copy2("/etc/timezone", "%s/etc/" % mount) - self._generate_chroot_config(mount) - daemon_policy = self._generate_daemon_policy(mount) - self._make_executable(daemon_policy) - initctl = "%s/sbin/initctl" % mount - if os.path.exists(initctl): - os.remove(initctl) - os.symlink("%s/bin/true" % mount, initctl) - self._generate_apt_proxy_file(mount, proxy) - finish_script = self._generate_finish_script(mount, build_pkgs) - self._make_executable(finish_script) - command = ["/finish.sh"] - ret_code = self.maint(*command) - if ret_code != 0 and not keep_broken_chroot_on_fail: - # cleanup on failure - self.destroy() - raise ClickChrootException( - "Failed to create chroot '{}' (exit status {})".format( - self.full_name, ret_code)) - return ret_code - - def run(self, *args): - if not self.exists(): - raise ClickChrootDoesNotExistException( - "Chroot %s does not exist" % self.full_name) - command = ["schroot", "-c"] - if self.session: - command.extend([self.full_session_name, "--run-session"]) - else: - command.append(self.full_name) - command.extend(["--", "env"]) - for key, value in self.dpkg_architecture.items(): - command.append("%s=%s" % (key, value)) - command.extend(args) - ret = subprocess.call(command) - if ret == 0: - return 0 - else: - print("Command returned %d: %s" % (ret, shell_escape(command)), - file=sys.stderr) - return ret - - def maint(self, *args): - command = ["schroot", "-u", "root", "-c"] - if self.session: - command.extend([self.full_session_name, "--run-session"]) - else: - command.append("source:%s" % self.full_name) - command.append("--") - command.extend(args) - ret = subprocess.call(command) - if ret == 0: - return 0 - else: - print("Command returned %d: %s" % (ret, shell_escape(command)), - file=sys.stderr) - return ret - - def install(self, *pkgs): - if not self.exists(): - raise ClickChrootDoesNotExistException( - "Chroot %s does not exist" % self.full_name) - ret = self.update() - if ret != 0: - return ret - command = ["apt-get", "install", "--yes"] - command.extend(pkgs) - ret = self.maint(*command) - if ret != 0: - return ret - return self.clean() - - def clean(self): - command = ["apt-get", "clean"] - return self.maint(*command) - - def update(self): - command = ["apt-get", "update", "--yes"] - return self.maint(*command) - - def upgrade(self): - if not self.exists(): - raise ClickChrootDoesNotExistException( - "Chroot %s does not exist" % self.full_name) - ret = self.update() - if ret != 0: - return ret - command = ["apt-get", "dist-upgrade", "--yes"] - ret = self.maint(*command) - if ret != 0: - return ret - return self.clean() - - def destroy(self): - # remove config - if os.path.exists(self.chroot_config): - os.remove(self.chroot_config) - # find all schroot mount points, this is actually quite complicated - mount_dir = os.path.abspath( - os.path.join(self.chroots_dir, "..", "mount")) - needle = os.path.join(mount_dir, self.full_name) - all_mounts = [] - with open("/proc/mounts") as f: - for line in f.readlines(): - mp = line.split()[1] - if mp.startswith(needle): - all_mounts.append(mp) - # reverse order is important in case of submounts - for mp in sorted(all_mounts, key=len, reverse=True): - subprocess.call(["umount", mp]) - # now remove the rest - chroot_dir = "%s/%s" % (self.chroots_dir, self.full_name) - if os.path.exists(chroot_dir): - shutil.rmtree(chroot_dir) - return 0 - - def begin_session(self): - if not self.exists(): - raise ClickChrootDoesNotExistException( - "Chroot %s does not exist" % self.full_name) - command = ["schroot", "-c", self.full_name, "--begin-session", - "--session-name", self.full_session_name] - subprocess.check_call(command) - return 0 - - def end_session(self): - if not self.exists(): - raise ClickChrootDoesNotExistException( - "Chroot %s does not exist" % self.full_name) - command = ["schroot", "-c", self.full_session_name, "--end-session"] - subprocess.check_call(command) - return 0 diff -Nru click-0.4.43+16.04.20160203/click/commands/build.py click-6.7/click/commands/build.py --- click-0.4.43+16.04.20160203/click/commands/build.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/commands/build.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,77 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Build a Click package.""" - -from __future__ import print_function - -from optparse import OptionParser -import os -import sys -import subprocess - -from gi.repository import Click -from click.build import ClickBuildError, ClickBuilder - - -def run(argv): - parser = OptionParser("%prog build [options] DIRECTORY") - parser.add_option( - "-m", "--manifest", metavar="PATH", default="manifest.json", - help="read package manifest from PATH (default: manifest.json)") - parser.add_option( - "--no-validate", action="store_false", default=True, dest="validate", - help="Don't run click-reviewers-tools check on resulting .click") - parser.add_option( - "-I", "--ignore", metavar="file-pattern", action='append', default=[], - help="Ignore the given pattern when building the package") - options, args = parser.parse_args(argv) - if len(args) < 1: - parser.error("need directory") - directory = args[0] - if not os.path.isdir(directory): - parser.error('directory "%s" does not exist' % directory) - if os.path.isdir(os.path.join(directory, options.manifest)): - options.manifest = os.path.join(options.manifest, "manifest.json") - if not os.path.exists(os.path.join(directory, options.manifest)): - parser.error( - 'directory "%s" does not contain manifest file "%s"' % - (directory, options.manifest)) - builder = ClickBuilder() - builder.add_file(directory, "./") - for ignore in options.ignore: - builder.add_ignore_pattern(ignore) - try: - path = builder.build(".", manifest_path=options.manifest) - except ClickBuildError as e: - print(e, file=sys.stderr) - return 1 - if options.validate and Click.find_on_path('click-review'): - print("Now executing: click-review %s" % path) - try: - subprocess.check_call(['click-review', path]) - except subprocess.CalledProcessError: - # qtcreator-plugin-ubuntu relies on return code 0 - # to establish if a .click package has been built - # at all. - # - # If we want to distinguish between - # - click build failed - # - click build succeeded, but validation failed - # both tools will have to learn this at the same - # time. - pass - print("Successfully built package in '%s'." % path) - return 0 diff -Nru click-0.4.43+16.04.20160203/click/commands/buildsource.py click-6.7/click/commands/buildsource.py --- click-0.4.43+16.04.20160203/click/commands/buildsource.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/commands/buildsource.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,60 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Build a Click source package.""" - -from __future__ import print_function - -from optparse import OptionParser -import os -import sys - -from click.build import ClickBuildError, ClickSourceBuilder - - -def run(argv): - parser = OptionParser("%prog buildsource [options] DIRECTORY") - parser.add_option( - "-m", "--manifest", metavar="PATH", - help="read package manifest from PATH") - parser.add_option( - "-I", "--ignore", metavar="file-pattern", action='append', - default=[], - help="Ignore the given pattern when building the package") - options, args = parser.parse_args(argv) - if len(args) < 1: - parser.error("need directory") - directory = args[0] - if not os.path.isdir(directory): - parser.error('directory "%s" does not exist' % directory) - if not options.manifest: - options.manifest = os.path.join(directory, "manifest.json") - if os.path.isdir(os.path.join(directory, options.manifest)): - options.manifest = os.path.join(options.manifest, "manifest.json") - if not os.path.exists(os.path.join(directory, options.manifest)): - parser.error( - 'directory "%s" does not contain manifest file "%s"' % - (directory, options.manifest)) - builder = ClickSourceBuilder() - builder.add_file(directory, "./") - for ignore in options.ignore: - builder.add_ignore_pattern(ignore) - try: - path = builder.build(".", manifest_path=options.manifest) - except ClickBuildError as e: - print(e, file=sys.stderr) - return 1 - print("Successfully built source package in '%s'." % path) - return 0 diff -Nru click-0.4.43+16.04.20160203/click/commands/chroot.py click-6.7/click/commands/chroot.py --- click-0.4.43+16.04.20160203/click/commands/chroot.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/commands/chroot.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,266 +0,0 @@ -#! /usr/bin/python3 - -# Copyright (C) 2013 Canonical Ltd. -# Author: Brian Murray - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Use and manage a Click chroot.""" - -from __future__ import print_function - -from argparse import ArgumentParser, REMAINDER -from contextlib import contextmanager -import os - -from click.chroot import ( - ClickChroot, - ClickChrootAlreadyExistsException, - ClickChrootDoesNotExistException, -) -from click import osextras - - -def requires_root(parser): - if os.getuid() != 0: - parser.error("must be run as root; try sudo") - - -@contextmanager -def message_on_error(exc, msg): - """ - Context Manager that prints the error message 'msg' on exception 'exc' - """ - try: - yield - except exc: - print(msg) - - -# FIXME: i18n(?) -class ErrorMessages: - EXISTS = """A chroot for that name and architecture already exists. -Please see the man-page how to use it.""" - NOT_EXISTS = """A chroot for that name and architecture does not exist. -Please use 'create' to create it.""" - - -def create(parser, args): - if not osextras.find_on_path("debootstrap"): - parser.error( - "debootstrap not installed and configured; install click-dev and " - "debootstrap") - requires_root(parser) - chroot = ClickChroot( - args.architecture, args.framework, name=args.name, series=args.series) - with message_on_error( - ClickChrootAlreadyExistsException, ErrorMessages.EXISTS): - return chroot.create(args.keep_broken_chroot) - # if we reach this point there was a error so return exit_status 1 - return 1 - - -def install(parser, args): - packages = args.packages - chroot = ClickChroot( - args.architecture, args.framework, name=args.name, - session=args.session) - with message_on_error( - ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS): - return chroot.install(*packages) - # if we reach this point there was a error so return exit_status 1 - return 1 - - -def destroy(parser, args): - requires_root(parser) - # ask for confirmation? - chroot = ClickChroot(args.architecture, args.framework, name=args.name) - with message_on_error( - ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS): - return chroot.destroy() - # if we reach this point there was a error so return exit_status 1 - return 1 - - -def execute(parser, args): - program = args.program - if not program: - program = ["/bin/bash"] - chroot = ClickChroot( - args.architecture, args.framework, name=args.name, - session=args.session) - with message_on_error( - ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS): - return chroot.run(*program) - # if we reach this point there was a error so return exit_status 1 - return 1 - - -def maint(parser, args): - program = args.program - if not program: - program = ["/bin/bash"] - chroot = ClickChroot( - args.architecture, args.framework, name=args.name, - session=args.session) - with message_on_error( - ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS): - return chroot.maint(*program) - # if we reach this point there was a error so return exit_status 1 - return 1 - - -def upgrade(parser, args): - chroot = ClickChroot( - args.architecture, args.framework, name=args.name, - session=args.session) - with message_on_error( - ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS): - return chroot.upgrade() - # if we reach this point there was a error so return exit_status 1 - return 1 - - -def begin_session(parser, args): - chroot = ClickChroot( - args.architecture, args.framework, name=args.name, - session=args.session) - with message_on_error( - ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS): - return chroot.begin_session() - # if we reach this point there was a error so return exit_status 1 - return 1 - - -def end_session(parser, args): - chroot = ClickChroot( - args.architecture, args.framework, name=args.name, - session=args.session) - with message_on_error( - ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS): - return chroot.end_session() - # if we reach this point there was a error so return exit_status 1 - return 1 - - -def exists(parser, args): - chroot = ClickChroot(args.architecture, args.framework, name=args.name) - # return shell exit codes 0 on success, 1 on failure - if chroot.exists(): - return 0 - else: - return 1 - - -def run(argv): - parser = ArgumentParser("click chroot") - subparsers = parser.add_subparsers( - description="management subcommands", - help="valid commands") - parser.add_argument( - "-a", "--architecture", required=True, - help="architecture for the chroot") - parser.add_argument( - "-f", "--framework", default="ubuntu-sdk-14.04", - help="framework for the chroot (default: ubuntu-sdk-14.04)") - parser.add_argument( - "-s", "--series", - help="series to use for a newly-created chroot (defaults to a series " - "appropriate for the framework)") - parser.add_argument( - "-n", "--name", default="click", - help=( - "name of the chroot (default: click; the framework and " - "architecture will be appended)")) - create_parser = subparsers.add_parser( - "create", - help="create a chroot of the provided architecture") - create_parser.add_argument( - "-k", "--keep-broken-chroot", default=False, action="store_true", - help="Keep the chroot even if creating it fails (default is to delete " - "it)") - create_parser.set_defaults(func=create) - destroy_parser = subparsers.add_parser( - "destroy", - help="destroy the chroot") - destroy_parser.set_defaults(func=destroy) - upgrade_parser = subparsers.add_parser( - "upgrade", - help="upgrade the chroot") - upgrade_parser.add_argument( - "-n", "--session-name", - dest='session', - help="persistent chroot session name to upgrade") - upgrade_parser.set_defaults(func=upgrade) - install_parser = subparsers.add_parser( - "install", - help="install packages in the chroot") - install_parser.add_argument( - "-n", "--session-name", - dest='session', - help="persistent chroot session name to install packages in") - install_parser.add_argument( - "packages", nargs="+", - help="packages to install") - install_parser.set_defaults(func=install) - execute_parser = subparsers.add_parser( - "run", - help="run a program in the chroot") - execute_parser.add_argument( - "-n", "--session-name", - dest='session', - help="persistent chroot session name to run a program in") - execute_parser.add_argument( - "program", nargs=REMAINDER, - help="program to run with arguments") - execute_parser.set_defaults(func=execute) - maint_parser = subparsers.add_parser( - "maint", - help="run a maintenance command in the chroot") - maint_parser.add_argument( - "-n", "--session-name", - dest='session', - help="persistent chroot session name to run a maintenance command in") - maint_parser.add_argument( - "program", nargs=REMAINDER, - help="program to run with arguments") - maint_parser.set_defaults(func=maint) - begin_parser = subparsers.add_parser( - "begin-session", - help="begin a persistent chroot session") - begin_parser.add_argument( - "session", - help="new session name") - begin_parser.set_defaults(func=begin_session) - end_parser = subparsers.add_parser( - "end-session", - help="end a persistent chroot session") - end_parser.add_argument( - "session", - help="session name to end") - end_parser.set_defaults(func=end_session) - exists_parser = subparsers.add_parser( - "exists", - help="test if the given chroot exists") - exists_parser.set_defaults(func=exists) - args = parser.parse_args(argv) - if not hasattr(args, "func"): - parser.print_help() - return 1 - if (not osextras.find_on_path("schroot") or - not os.path.exists("/etc/schroot/click/fstab")): - parser.error( - "schroot not installed and configured; install click-dev and " - "schroot") - return args.func(parser, args) diff -Nru click-0.4.43+16.04.20160203/click/commands/contents.py click-6.7/click/commands/contents.py --- click-0.4.43+16.04.20160203/click/commands/contents.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/commands/contents.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,31 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Show the file-list contents of a Click package file.""" - -from __future__ import print_function - -from optparse import OptionParser -import subprocess - - -def run(argv): - parser = OptionParser("%prog contents [options] PATH") - _, args = parser.parse_args(argv) - if len(args) < 1: - parser.error("need file name") - path = args[0] - subprocess.check_call(["dpkg-deb", "-c", path]) - return 0 diff -Nru click-0.4.43+16.04.20160203/click/commands/desktophook.py click-6.7/click/commands/desktophook.py --- click-0.4.43+16.04.20160203/click/commands/desktophook.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/commands/desktophook.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,179 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Click desktop hook. (Temporary; do not rely on this.)""" - -from __future__ import print_function - -import errno -import io -import json -from optparse import OptionParser -import os - -from gi.repository import Click - -from click import osextras - - -COMMENT = \ - '# Generated by "click desktophook"; changes here will be overwritten.' - - -def desktop_entries(directory, only_ours=False): - for entry in osextras.listdir_force(directory): - if not entry.endswith(".desktop"): - continue - path = os.path.join(directory, entry) - if only_ours: - try: - with io.open(path, encoding="UTF-8") as f: - if COMMENT not in f.read(): - continue - except Exception: - continue - yield entry - - -def split_entry(entry): - entry = entry[:-8] # strip .desktop - return entry.split("_", 2) - - -def older(source_path, target_path): - """Return True iff source_path is older than target_path. - - It's also OK for target_path to be missing. - """ - try: - source_mtime = os.stat(source_path).st_mtime - except OSError as e: - if e.errno == errno.ENOENT: - return False - try: - target_mtime = os.stat(target_path).st_mtime - except OSError as e: - if e.errno == errno.ENOENT: - return True - return source_mtime < target_mtime - - -def read_hooks_for(path, package, app_name): - try: - directory = Click.find_package_directory(path) - manifest_path = os.path.join( - directory, ".click", "info", "%s.manifest" % package) - with io.open(manifest_path, encoding="UTF-8") as manifest: - return json.load(manifest).get("hooks", {}).get(app_name, {}) - except Exception: - return {} - - -def quote_for_desktop_exec(s): - """Quote a string for Exec in a .desktop file. - - The rules are fairly awful. See: - http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html - """ - for c in s: - if c in " \t\n\"'\\><~|&;$*?#()`%": - break - else: - return s - quoted = [] - for c in s: - if c in "\"`$\\": - quoted.append("\\" + c) - elif c == "%": - quoted.append("%%") - else: - quoted.append(c) - escaped = [] - for c in "".join(quoted): - if c == "\\": - escaped.append("\\\\") - else: - escaped.append(c) - return '"%s"' % "".join(escaped) - - -# TODO: This is a very crude .desktop file mangler; we should instead -# implement proper (de)serialisation. -def write_desktop_file(target_path, source_path, profile): - Click.ensuredir(os.path.dirname(target_path)) - with io.open(source_path, encoding="UTF-8") as source, \ - io.open(target_path, "w", encoding="UTF-8") as target: - source_dir = Click.find_package_directory(source_path) - written_comment = False - seen_path = False - for line in source: - if not line.rstrip("\n") or line.startswith("#"): - # Comment - target.write(line) - elif line.startswith("["): - # Group header - target.write(line) - if not written_comment: - print(COMMENT, file=target) - elif "=" not in line: - # Who knows? - target.write(line) - else: - key, value = line.split("=", 1) - key = key.strip() - value = value.strip() - if key == "Exec": - target.write( - "%s=aa-exec-click -p %s -- %s\n" % - (key, quote_for_desktop_exec(profile), value)) - elif key == "Path": - target.write("%s=%s\n" % (key, source_dir)) - seen_path = True - elif key == "Icon": - icon_path = os.path.join(source_dir, value) - if os.path.exists(icon_path): - target.write("%s=%s\n" % (key, icon_path)) - else: - target.write("%s=%s\n" % (key, value)) - else: - target.write("%s=%s\n" % (key, value)) - if not seen_path: - target.write("Path=%s\n" % source_dir) - - -def run(argv): - parser = OptionParser("%prog desktophook [options]") - parser.parse_args(argv) - source_dir = os.path.expanduser("~/.local/share/click/hooks/desktop") - target_dir = os.path.expanduser("~/.local/share/applications") - source_entries = set(desktop_entries(source_dir)) - target_entries = set(desktop_entries(target_dir, only_ours=True)) - - for new_entry in source_entries: - package, app_name, version = split_entry(new_entry) - source_path = os.path.join(source_dir, new_entry) - target_path = os.path.join(target_dir, new_entry) - if older(source_path, target_path): - hooks = read_hooks_for(source_path, package, app_name) - if "apparmor" in hooks: - profile = "%s_%s_%s" % (package, app_name, version) - else: - profile = "unconfined" - write_desktop_file(target_path, source_path, profile) - - for remove_entry in target_entries - source_entries: - os.unlink(os.path.join(target_dir, remove_entry)) - - return 0 diff -Nru click-0.4.43+16.04.20160203/click/commands/framework.py click-6.7/click/commands/framework.py --- click-0.4.43+16.04.20160203/click/commands/framework.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/commands/framework.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,71 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""List available frameworks.""" - -from __future__ import print_function - -from argparse import ArgumentParser - -from gi.repository import Click - - -def list(parser, args): - for framework in Click.Framework.get_frameworks(): - print("%s" % framework.props.name) - return 0 - - -def info(parser, args): - framework = Click.Framework.open(args.framework_name) - for field in sorted(framework.get_fields()): - print("%s: %s" % (field, framework.get_field(field))) - - -def get_field(parser, args): - framework = Click.Framework.open(args.framework_name) - print(framework.get_field(args.field_name)) - - -def run(argv): - parser = ArgumentParser("click framework") - subparsers = parser.add_subparsers() - list_parser = subparsers.add_parser( - "list", - help="list available frameworks") - list_parser.set_defaults(func=list) - info_parser = subparsers.add_parser( - "info", - help="show info about a specific framework") - info_parser.add_argument( - "framework_name", - help="framework name with the information") - info_parser.set_defaults(func=info) - get_field_parser = subparsers.add_parser( - "get-field", - help="get a field from a given framework") - get_field_parser.add_argument( - "framework_name", - help="framework name with the information") - get_field_parser.add_argument( - "field_name", - help="the field name (e.g. base-version)") - get_field_parser.set_defaults(func=get_field) - - args = parser.parse_args(argv) - if not hasattr(args, "func"): - parser.print_help() - return 1 - return args.func(parser, args) diff -Nru click-0.4.43+16.04.20160203/click/commands/hook.py click-6.7/click/commands/hook.py --- click-0.4.43+16.04.20160203/click/commands/hook.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/commands/hook.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,94 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Install or remove a Click system hook.""" - -from __future__ import print_function - -from optparse import OptionParser -import sys -from textwrap import dedent - -from gi.repository import Click, GLib - - -per_hook_subcommands = { - "install": "install", - "remove": "remove", - } - - -def run(argv): - parser = OptionParser(dedent("""\ - %prog hook [options] SUBCOMMAND [...] - - Subcommands are as follows: - - install HOOK - remove HOOK - run-system - run-user [--user=USER]""")) - parser.add_option( - "--root", metavar="PATH", help="look for additional packages in PATH") - parser.add_option( - "--user", metavar="USER", - help=( - "run user-level hooks for USER (default: current user; only " - "applicable to run-user)")) - options, args = parser.parse_args(argv) - if len(args) < 1: - parser.error("need subcommand (install, remove, run-system, run-user)") - subcommand = args[0] - if subcommand in per_hook_subcommands: - if len(args) < 2: - parser.error("need hook name") - db = Click.DB() - db.read(db_dir=None) - if options.root is not None: - db.add(options.root) - name = args[1] - hook = Click.Hook.open(db, name) - getattr(hook, per_hook_subcommands[subcommand])(user_name=None) - elif subcommand == "run-system": - db = Click.DB() - db.read(db_dir=None) - if options.root is not None: - db.add(options.root) - try: - Click.run_system_hooks(db) - except GLib.GError as e: - if e.domain == "click_hooks_error-quark": - print(e.message, file=sys.stderr) - return 1 - else: - raise - elif subcommand == "run-user": - db = Click.DB() - db.read(db_dir=None) - if options.root is not None: - db.add(options.root) - try: - Click.run_user_hooks(db, user_name=options.user) - except GLib.GError as e: - if e.domain == "click_hooks_error-quark": - print(e.message, file=sys.stderr) - return 1 - else: - raise - else: - parser.error( - "unknown subcommand '%s' (known: install, remove, run-system," - "run-user)" % subcommand) - return 0 diff -Nru click-0.4.43+16.04.20160203/click/commands/info.py click-6.7/click/commands/info.py --- click-0.4.43+16.04.20160203/click/commands/info.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/commands/info.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,88 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Show manifest information for a Click package.""" - -from __future__ import print_function - -from contextlib import closing -import glob -import json -from optparse import OptionParser -import os -import sys - -from gi.repository import Click - -from click.install import DebFile -from click.json_helpers import json_object_to_python - - -def _load_manifest(manifest_file): - manifest = json.load(manifest_file) - keys = list(manifest) - for key in keys: - if key.startswith("_"): - del manifest[key] - return manifest - - -def get_manifest(options, arg): - if "/" not in arg: - db = Click.DB() - db.read(db_dir=None) - if options.root is not None: - db.add(options.root) - registry = Click.User.for_user(db, name=options.user) - if registry.has_package_name(arg): - return json_object_to_python(registry.get_manifest(arg)) - - try: - with closing(DebFile(filename=arg)) as package: - with package.control.get_file( - "manifest", encoding="UTF-8") as manifest_file: - return _load_manifest(manifest_file) - except Exception: - pkgdir = Click.find_package_directory(arg) - manifest_path = glob.glob( - os.path.join(pkgdir, ".click", "info", "*.manifest")) - if len(manifest_path) > 1: - raise Exception("Multiple manifest files found in '%s'" % ( - manifest_path)) - with open(manifest_path[0]) as f: - return _load_manifest(f) - - -def run(argv): - parser = OptionParser("%prog info [options] PATH") - parser.add_option( - "--root", metavar="PATH", help="look for additional packages in PATH") - parser.add_option( - "--user", metavar="USER", - help="look up PACKAGE-NAME for USER (if you have permission; " - "default: current user)") - options, args = parser.parse_args(argv) - if len(args) < 1: - parser.error("need file name") - try: - manifest = get_manifest(options, args[0]) - except Exception as e: - print(e, file=sys.stderr) - return 1 - json.dump( - manifest, sys.stdout, ensure_ascii=False, sort_keys=True, indent=4, - separators=(",", ": ")) - print() - return 0 diff -Nru click-0.4.43+16.04.20160203/click/commands/__init__.py click-6.7/click/commands/__init__.py --- click-0.4.43+16.04.20160203/click/commands/__init__.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/commands/__init__.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,55 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""click commands.""" - -import importlib - - -all_commands = ( - "build", - "buildsource", - "chroot", - "contents", - "desktophook", - "framework", - "hook", - "info", - "install", - "list", - "pkgdir", - "register", - "unregister", - "verify", - ) - - -hidden_commands = ( - "desktophook", - ) - - -def load_command(command): - return importlib.import_module("click.commands.%s" % command) - - -def help_text(): - lines = [] - for command in all_commands: - if command in hidden_commands: - continue - mod = load_command(command) - lines.append(" %-21s %s" % (command, mod.__doc__.splitlines()[0])) - return "\n".join(lines) diff -Nru click-0.4.43+16.04.20160203/click/commands/install.py click-6.7/click/commands/install.py --- click-0.4.43+16.04.20160203/click/commands/install.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/commands/install.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,70 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Install a Click package (low-level; consider pkcon instead).""" - -from __future__ import print_function - -from optparse import OptionParser -import sys -from textwrap import dedent - -from gi.repository import Click - -from click.install import ClickInstaller, ClickInstallerError - - -def run(argv): - parser = OptionParser(dedent("""\ - %prog install [options] PACKAGE-FILE - - This is a low-level tool; to install a package as an ordinary user - you should generally use "pkcon install-local PACKAGE-FILE" - instead.""")) - parser.add_option( - "--root", metavar="PATH", help="install packages underneath PATH") - parser.add_option( - "--force-missing-framework", action="store_true", default=False, - help="install despite missing system framework") - parser.add_option( - "--user", metavar="USER", help="register package for USER") - parser.add_option( - "--all-users", default=False, action="store_true", - help="register package for all users") - parser.add_option( - "--allow-unauthenticated", default=False, action="store_true", - help="allow installing packages with no signatures") - parser.add_option( - "--verbose", default=False, action="store_true", - help="be more verbose on install") - options, args = parser.parse_args(argv) - if len(args) < 1: - parser.error("need package file name") - db = Click.DB() - db.read(db_dir=None) - if options.root is not None: - db.add(options.root) - package_path = args[0] - installer = ClickInstaller( - db=db, force_missing_framework=options.force_missing_framework, - allow_unauthenticated=options.allow_unauthenticated) - try: - installer.install( - package_path, user=options.user, all_users=options.all_users, - quiet=not options.verbose) - except ClickInstallerError as e: - print("Cannot install %s: %s" % (package_path, e), file=sys.stderr) - return 1 - return 0 diff -Nru click-0.4.43+16.04.20160203/click/commands/list.py click-6.7/click/commands/list.py --- click-0.4.43+16.04.20160203/click/commands/list.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/commands/list.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,64 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""List installed Click packages.""" - -from __future__ import print_function - -import json -from optparse import OptionParser -import sys - -from gi.repository import Click - -from click.json_helpers import json_array_to_python - - -def list_packages(options): - db = Click.DB() - db.read(db_dir=None) - if options.root is not None: - db.add(options.root) - if options.all: - return json_array_to_python(db.get_manifests(all_versions=True)) - else: - registry = Click.User.for_user(db, name=options.user) - return json_array_to_python(registry.get_manifests()) - - -def run(argv): - parser = OptionParser("%prog list [options]") - parser.add_option( - "--root", metavar="PATH", help="look for additional packages in PATH") - parser.add_option( - "--all", default=False, action="store_true", - help="list all installed packages") - parser.add_option( - "--user", metavar="USER", - help="list packages registered by USER (if you have permission)") - parser.add_option( - "--manifest", default=False, action="store_true", - help="format output as a JSON array of manifests") - options, _ = parser.parse_args(argv) - json_output = list_packages(options) - if options.manifest: - json.dump( - json_output, sys.stdout, ensure_ascii=False, sort_keys=True, - indent=4, separators=(",", ": ")) - print() - else: - for manifest in json_output: - print("%s\t%s" % (manifest["name"], manifest["version"])) - return 0 diff -Nru click-0.4.43+16.04.20160203/click/commands/pkgdir.py click-6.7/click/commands/pkgdir.py --- click-0.4.43+16.04.20160203/click/commands/pkgdir.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/commands/pkgdir.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,52 +0,0 @@ -#! /usr/bin/python3 - -# Copyright (C) 2013 Canonical Ltd. - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Print the directory where a Click package is unpacked.""" - -from __future__ import print_function - -from optparse import OptionParser -import sys - -from gi.repository import Click - - -def run(argv): - parser = OptionParser("%prog pkgdir [options] {PACKAGE-NAME|PATH}") - parser.add_option( - "--root", metavar="PATH", help="look for additional packages in PATH") - parser.add_option( - "--user", metavar="USER", - help="look up PACKAGE-NAME for USER (if you have permission; " - "default: current user)") - options, args = parser.parse_args(argv) - if len(args) < 1: - parser.error("need package name") - try: - if "/" in args[0]: - print(Click.find_package_directory(args[0])) - else: - db = Click.DB() - db.read(db_dir=None) - if options.root is not None: - db.add(options.root) - package_name = args[0] - registry = Click.User.for_user(db, name=options.user) - print(registry.get_path(package_name)) - except Exception as e: - print(e, file=sys.stderr) - return 1 - return 0 diff -Nru click-0.4.43+16.04.20160203/click/commands/register.py click-6.7/click/commands/register.py --- click-0.4.43+16.04.20160203/click/commands/register.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/commands/register.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,57 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Register an installed Click package for a user.""" - -from __future__ import print_function - -from optparse import OptionParser - -from gi.repository import Click, GLib - - -def run(argv): - parser = OptionParser("%prog register [options] PACKAGE-NAME VERSION") - parser.add_option( - "--root", metavar="PATH", help="look for additional packages in PATH") - parser.add_option( - "--user", metavar="USER", - help="register package for USER (default: current user)") - parser.add_option( - "--all-users", default=False, action="store_true", - help="register package for all users") - options, args = parser.parse_args(argv) - if len(args) < 1: - parser.error("need package name") - if len(args) < 2: - parser.error("need version") - db = Click.DB() - db.read(db_dir=None) - if options.root is not None: - db.add(options.root) - package = args[0] - version = args[1] - if options.all_users: - registry = Click.User.for_all_users(db) - else: - registry = Click.User.for_user(db, name=options.user) - try: - old_version = registry.get_version(package) - except GLib.GError: - old_version = None - registry.set_version(package, version) - if old_version is not None: - db.maybe_remove(package, old_version) - return 0 diff -Nru click-0.4.43+16.04.20160203/click/commands/unregister.py click-6.7/click/commands/unregister.py --- click-0.4.43+16.04.20160203/click/commands/unregister.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/commands/unregister.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,65 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Unregister an installed Click package for a user.""" - -from __future__ import print_function - -from optparse import OptionParser -import os -import sys - -from gi.repository import Click - - -def run(argv): - parser = OptionParser("%prog unregister [options] PACKAGE-NAME [VERSION]") - parser.add_option( - "--root", metavar="PATH", help="look for additional packages in PATH") - parser.add_option( - "--user", metavar="USER", - help="unregister package for USER (default: $SUDO_USER, if known)") - parser.add_option( - "--all-users", default=False, action="store_true", - help="unregister package that was previously registered for all users") - options, args = parser.parse_args(argv) - if len(args) < 1: - parser.error("need package name") - if os.geteuid() != 0: - parser.error( - "click unregister must be started as root, since it may need to " - "remove packages from disk") - if options.user is None and "SUDO_USER" in os.environ: - options.user = os.environ["SUDO_USER"] - db = Click.DB() - db.read(db_dir=None) - if options.root is not None: - db.add(options.root) - package = args[0] - if options.all_users: - registry = Click.User.for_all_users(db) - else: - registry = Click.User.for_user(db, name=options.user) - old_version = registry.get_version(package) - if len(args) >= 2 and old_version != args[1]: - print( - "Not removing %s %s; expected version %s" % - (package, old_version, args[1]), - file=sys.stderr) - sys.exit(1) - registry.remove(package) - db.maybe_remove(package, old_version) - # TODO: remove data - return 0 diff -Nru click-0.4.43+16.04.20160203/click/commands/verify.py click-6.7/click/commands/verify.py --- click-0.4.43+16.04.20160203/click/commands/verify.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/commands/verify.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,43 +0,0 @@ -#! /usr/bin/python3 - -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Verify a Click package.""" - -from __future__ import print_function - -from optparse import OptionParser - -from click.install import ClickInstaller - - -def run(argv): - parser = OptionParser("%prog verify [options] PACKAGE-FILE") - parser.add_option( - "--force-missing-framework", action="store_true", default=False, - help="ignore missing system framework") - parser.add_option( - "--allow-unauthenticated", action="store_true", default=False, - help="allow installing packages with no sigantures") - options, args = parser.parse_args(argv) - if len(args) < 1: - parser.error("need package file name") - package_path = args[0] - installer = ClickInstaller( - db=None, force_missing_framework=options.force_missing_framework, - allow_unauthenticated=options.allow_unauthenticated) - installer.audit(package_path, slow=True) - return 0 diff -Nru click-0.4.43+16.04.20160203/click/_compat.py click-6.7/click/_compat.py --- click-0.4.43+16.04.20160203/click/_compat.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/click/_compat.py 2017-01-06 22:39:09.000000000 +0000 @@ -0,0 +1,648 @@ +import re +import io +import os +import sys +import codecs +from weakref import WeakKeyDictionary + + +PY2 = sys.version_info[0] == 2 +WIN = sys.platform.startswith('win') +DEFAULT_COLUMNS = 80 + + +_ansi_re = re.compile('\033\[((?:\d|;)*)([a-zA-Z])') + + +def get_filesystem_encoding(): + return sys.getfilesystemencoding() or sys.getdefaultencoding() + + +def _make_text_stream(stream, encoding, errors): + if encoding is None: + encoding = get_best_encoding(stream) + if errors is None: + errors = 'replace' + return _NonClosingTextIOWrapper(stream, encoding, errors, + line_buffering=True) + + +def is_ascii_encoding(encoding): + """Checks if a given encoding is ascii.""" + try: + return codecs.lookup(encoding).name == 'ascii' + except LookupError: + return False + + +def get_best_encoding(stream): + """Returns the default stream encoding if not found.""" + rv = getattr(stream, 'encoding', None) or sys.getdefaultencoding() + if is_ascii_encoding(rv): + return 'utf-8' + return rv + + +class _NonClosingTextIOWrapper(io.TextIOWrapper): + + def __init__(self, stream, encoding, errors, **extra): + self._stream = stream = _FixupStream(stream) + io.TextIOWrapper.__init__(self, stream, encoding, errors, **extra) + + # The io module is a place where the Python 3 text behavior + # was forced upon Python 2, so we need to unbreak + # it to look like Python 2. + if PY2: + def write(self, x): + if isinstance(x, str) or is_bytes(x): + try: + self.flush() + except Exception: + pass + return self.buffer.write(str(x)) + return io.TextIOWrapper.write(self, x) + + def writelines(self, lines): + for line in lines: + self.write(line) + + def __del__(self): + try: + self.detach() + except Exception: + pass + + def isatty(self): + # https://bitbucket.org/pypy/pypy/issue/1803 + return self._stream.isatty() + + +class _FixupStream(object): + """The new io interface needs more from streams than streams + traditionally implement. As such, this fix-up code is necessary in + some circumstances. + """ + + def __init__(self, stream): + self._stream = stream + + def __getattr__(self, name): + return getattr(self._stream, name) + + def read1(self, size): + f = getattr(self._stream, 'read1', None) + if f is not None: + return f(size) + # We only dispatch to readline instead of read in Python 2 as we + # do not want cause problems with the different implementation + # of line buffering. + if PY2: + return self._stream.readline(size) + return self._stream.read(size) + + def readable(self): + x = getattr(self._stream, 'readable', None) + if x is not None: + return x() + try: + self._stream.read(0) + except Exception: + return False + return True + + def writable(self): + x = getattr(self._stream, 'writable', None) + if x is not None: + return x() + try: + self._stream.write('') + except Exception: + try: + self._stream.write(b'') + except Exception: + return False + return True + + def seekable(self): + x = getattr(self._stream, 'seekable', None) + if x is not None: + return x() + try: + self._stream.seek(self._stream.tell()) + except Exception: + return False + return True + + +if PY2: + text_type = unicode + bytes = str + raw_input = raw_input + string_types = (str, unicode) + iteritems = lambda x: x.iteritems() + range_type = xrange + + def is_bytes(x): + return isinstance(x, (buffer, bytearray)) + + _identifier_re = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$') + + # For Windows, we need to force stdout/stdin/stderr to binary if it's + # fetched for that. This obviously is not the most correct way to do + # it as it changes global state. Unfortunately, there does not seem to + # be a clear better way to do it as just reopening the file in binary + # mode does not change anything. + # + # An option would be to do what Python 3 does and to open the file as + # binary only, patch it back to the system, and then use a wrapper + # stream that converts newlines. It's not quite clear what's the + # correct option here. + # + # This code also lives in _winconsole for the fallback to the console + # emulation stream. + # + # There are also Windows environments where the `msvcrt` module is not + # available (which is why we use try-catch instead of the WIN variable + # here), such as the Google App Engine development server on Windows. In + # those cases there is just nothing we can do. + try: + import msvcrt + except ImportError: + set_binary_mode = lambda x: x + else: + def set_binary_mode(f): + try: + fileno = f.fileno() + except Exception: + pass + else: + msvcrt.setmode(fileno, os.O_BINARY) + return f + + def isidentifier(x): + return _identifier_re.search(x) is not None + + def get_binary_stdin(): + return set_binary_mode(sys.stdin) + + def get_binary_stdout(): + return set_binary_mode(sys.stdout) + + def get_binary_stderr(): + return set_binary_mode(sys.stderr) + + def get_text_stdin(encoding=None, errors=None): + rv = _get_windows_console_stream(sys.stdin, encoding, errors) + if rv is not None: + return rv + return _make_text_stream(sys.stdin, encoding, errors) + + def get_text_stdout(encoding=None, errors=None): + rv = _get_windows_console_stream(sys.stdout, encoding, errors) + if rv is not None: + return rv + return _make_text_stream(sys.stdout, encoding, errors) + + def get_text_stderr(encoding=None, errors=None): + rv = _get_windows_console_stream(sys.stderr, encoding, errors) + if rv is not None: + return rv + return _make_text_stream(sys.stderr, encoding, errors) + + def filename_to_ui(value): + if isinstance(value, bytes): + value = value.decode(get_filesystem_encoding(), 'replace') + return value +else: + import io + text_type = str + raw_input = input + string_types = (str,) + range_type = range + isidentifier = lambda x: x.isidentifier() + iteritems = lambda x: iter(x.items()) + + def is_bytes(x): + return isinstance(x, (bytes, memoryview, bytearray)) + + def _is_binary_reader(stream, default=False): + try: + return isinstance(stream.read(0), bytes) + except Exception: + return default + # This happens in some cases where the stream was already + # closed. In this case, we assume the default. + + def _is_binary_writer(stream, default=False): + try: + stream.write(b'') + except Exception: + try: + stream.write('') + return False + except Exception: + pass + return default + return True + + def _find_binary_reader(stream): + # We need to figure out if the given stream is already binary. + # This can happen because the official docs recommend detaching + # the streams to get binary streams. Some code might do this, so + # we need to deal with this case explicitly. + if _is_binary_reader(stream, False): + return stream + + buf = getattr(stream, 'buffer', None) + + # Same situation here; this time we assume that the buffer is + # actually binary in case it's closed. + if buf is not None and _is_binary_reader(buf, True): + return buf + + def _find_binary_writer(stream): + # We need to figure out if the given stream is already binary. + # This can happen because the official docs recommend detatching + # the streams to get binary streams. Some code might do this, so + # we need to deal with this case explicitly. + if _is_binary_writer(stream, False): + return stream + + buf = getattr(stream, 'buffer', None) + + # Same situation here; this time we assume that the buffer is + # actually binary in case it's closed. + if buf is not None and _is_binary_writer(buf, True): + return buf + + def _stream_is_misconfigured(stream): + """A stream is misconfigured if its encoding is ASCII.""" + # If the stream does not have an encoding set, we assume it's set + # to ASCII. This appears to happen in certain unittest + # environments. It's not quite clear what the correct behavior is + # but this at least will force Click to recover somehow. + return is_ascii_encoding(getattr(stream, 'encoding', None) or 'ascii') + + def _is_compatible_text_stream(stream, encoding, errors): + stream_encoding = getattr(stream, 'encoding', None) + stream_errors = getattr(stream, 'errors', None) + + # Perfect match. + if stream_encoding == encoding and stream_errors == errors: + return True + + # Otherwise, it's only a compatible stream if we did not ask for + # an encoding. + if encoding is None: + return stream_encoding is not None + + return False + + def _force_correct_text_reader(text_reader, encoding, errors): + if _is_binary_reader(text_reader, False): + binary_reader = text_reader + else: + # If there is no target encoding set, we need to verify that the + # reader is not actually misconfigured. + if encoding is None and not _stream_is_misconfigured(text_reader): + return text_reader + + if _is_compatible_text_stream(text_reader, encoding, errors): + return text_reader + + # If the reader has no encoding, we try to find the underlying + # binary reader for it. If that fails because the environment is + # misconfigured, we silently go with the same reader because this + # is too common to happen. In that case, mojibake is better than + # exceptions. + binary_reader = _find_binary_reader(text_reader) + if binary_reader is None: + return text_reader + + # At this point, we default the errors to replace instead of strict + # because nobody handles those errors anyways and at this point + # we're so fundamentally fucked that nothing can repair it. + if errors is None: + errors = 'replace' + return _make_text_stream(binary_reader, encoding, errors) + + def _force_correct_text_writer(text_writer, encoding, errors): + if _is_binary_writer(text_writer, False): + binary_writer = text_writer + else: + # If there is no target encoding set, we need to verify that the + # writer is not actually misconfigured. + if encoding is None and not _stream_is_misconfigured(text_writer): + return text_writer + + if _is_compatible_text_stream(text_writer, encoding, errors): + return text_writer + + # If the writer has no encoding, we try to find the underlying + # binary writer for it. If that fails because the environment is + # misconfigured, we silently go with the same writer because this + # is too common to happen. In that case, mojibake is better than + # exceptions. + binary_writer = _find_binary_writer(text_writer) + if binary_writer is None: + return text_writer + + # At this point, we default the errors to replace instead of strict + # because nobody handles those errors anyways and at this point + # we're so fundamentally fucked that nothing can repair it. + if errors is None: + errors = 'replace' + return _make_text_stream(binary_writer, encoding, errors) + + def get_binary_stdin(): + reader = _find_binary_reader(sys.stdin) + if reader is None: + raise RuntimeError('Was not able to determine binary ' + 'stream for sys.stdin.') + return reader + + def get_binary_stdout(): + writer = _find_binary_writer(sys.stdout) + if writer is None: + raise RuntimeError('Was not able to determine binary ' + 'stream for sys.stdout.') + return writer + + def get_binary_stderr(): + writer = _find_binary_writer(sys.stderr) + if writer is None: + raise RuntimeError('Was not able to determine binary ' + 'stream for sys.stderr.') + return writer + + def get_text_stdin(encoding=None, errors=None): + rv = _get_windows_console_stream(sys.stdin, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_reader(sys.stdin, encoding, errors) + + def get_text_stdout(encoding=None, errors=None): + rv = _get_windows_console_stream(sys.stdout, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_writer(sys.stdout, encoding, errors) + + def get_text_stderr(encoding=None, errors=None): + rv = _get_windows_console_stream(sys.stderr, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_writer(sys.stderr, encoding, errors) + + def filename_to_ui(value): + if isinstance(value, bytes): + value = value.decode(get_filesystem_encoding(), 'replace') + else: + value = value.encode('utf-8', 'surrogateescape') \ + .decode('utf-8', 'replace') + return value + + +def get_streerror(e, default=None): + if hasattr(e, 'strerror'): + msg = e.strerror + else: + if default is not None: + msg = default + else: + msg = str(e) + if isinstance(msg, bytes): + msg = msg.decode('utf-8', 'replace') + return msg + + +def open_stream(filename, mode='r', encoding=None, errors='strict', + atomic=False): + # Standard streams first. These are simple because they don't need + # special handling for the atomic flag. It's entirely ignored. + if filename == '-': + if 'w' in mode: + if 'b' in mode: + return get_binary_stdout(), False + return get_text_stdout(encoding=encoding, errors=errors), False + if 'b' in mode: + return get_binary_stdin(), False + return get_text_stdin(encoding=encoding, errors=errors), False + + # Non-atomic writes directly go out through the regular open functions. + if not atomic: + if encoding is None: + return open(filename, mode), True + return io.open(filename, mode, encoding=encoding, errors=errors), True + + # Some usability stuff for atomic writes + if 'a' in mode: + raise ValueError( + 'Appending to an existing file is not supported, because that ' + 'would involve an expensive `copy`-operation to a temporary ' + 'file. Open the file in normal `w`-mode and copy explicitly ' + 'if that\'s what you\'re after.' + ) + if 'x' in mode: + raise ValueError('Use the `overwrite`-parameter instead.') + if 'w' not in mode: + raise ValueError('Atomic writes only make sense with `w`-mode.') + + # Atomic writes are more complicated. They work by opening a file + # as a proxy in the same folder and then using the fdopen + # functionality to wrap it in a Python file. Then we wrap it in an + # atomic file that moves the file over on close. + import tempfile + fd, tmp_filename = tempfile.mkstemp(dir=os.path.dirname(filename), + prefix='.__atomic-write') + + if encoding is not None: + f = io.open(fd, mode, encoding=encoding, errors=errors) + else: + f = os.fdopen(fd, mode) + + return _AtomicFile(f, tmp_filename, filename), True + + +# Used in a destructor call, needs extra protection from interpreter cleanup. +if hasattr(os, 'replace'): + _replace = os.replace + _can_replace = True +else: + _replace = os.rename + _can_replace = not WIN + + +class _AtomicFile(object): + + def __init__(self, f, tmp_filename, real_filename): + self._f = f + self._tmp_filename = tmp_filename + self._real_filename = real_filename + self.closed = False + + @property + def name(self): + return self._real_filename + + def close(self, delete=False): + if self.closed: + return + self._f.close() + if not _can_replace: + try: + os.remove(self._real_filename) + except OSError: + pass + _replace(self._tmp_filename, self._real_filename) + self.closed = True + + def __getattr__(self, name): + return getattr(self._f, name) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tb): + self.close(delete=exc_type is not None) + + def __repr__(self): + return repr(self._f) + + +auto_wrap_for_ansi = None +colorama = None +get_winterm_size = None + + +def strip_ansi(value): + return _ansi_re.sub('', value) + + +def should_strip_ansi(stream=None, color=None): + if color is None: + if stream is None: + stream = sys.stdin + return not isatty(stream) + return not color + + +# If we're on Windows, we provide transparent integration through +# colorama. This will make ANSI colors through the echo function +# work automatically. +if WIN: + # Windows has a smaller terminal + DEFAULT_COLUMNS = 79 + + from ._winconsole import _get_windows_console_stream + + def _get_argv_encoding(): + import locale + return locale.getpreferredencoding() + + if PY2: + def raw_input(prompt=''): + sys.stderr.flush() + if prompt: + stdout = _default_text_stdout() + stdout.write(prompt) + stdin = _default_text_stdin() + return stdin.readline().rstrip('\r\n') + + try: + import colorama + except ImportError: + pass + else: + _ansi_stream_wrappers = WeakKeyDictionary() + + def auto_wrap_for_ansi(stream, color=None): + """This function wraps a stream so that calls through colorama + are issued to the win32 console API to recolor on demand. It + also ensures to reset the colors if a write call is interrupted + to not destroy the console afterwards. + """ + try: + cached = _ansi_stream_wrappers.get(stream) + except Exception: + cached = None + if cached is not None: + return cached + strip = should_strip_ansi(stream, color) + ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip) + rv = ansi_wrapper.stream + _write = rv.write + + def _safe_write(s): + try: + return _write(s) + except: + ansi_wrapper.reset_all() + raise + + rv.write = _safe_write + try: + _ansi_stream_wrappers[stream] = rv + except Exception: + pass + return rv + + def get_winterm_size(): + win = colorama.win32.GetConsoleScreenBufferInfo( + colorama.win32.STDOUT).srWindow + return win.Right - win.Left, win.Bottom - win.Top +else: + def _get_argv_encoding(): + return getattr(sys.stdin, 'encoding', None) or get_filesystem_encoding() + + _get_windows_console_stream = lambda *x: None + + +def term_len(x): + return len(strip_ansi(x)) + + +def isatty(stream): + try: + return stream.isatty() + except Exception: + return False + + +def _make_cached_stream_func(src_func, wrapper_func): + cache = WeakKeyDictionary() + def func(): + stream = src_func() + try: + rv = cache.get(stream) + except Exception: + rv = None + if rv is not None: + return rv + rv = wrapper_func() + try: + cache[stream] = rv + except Exception: + pass + return rv + return func + + +_default_text_stdin = _make_cached_stream_func( + lambda: sys.stdin, get_text_stdin) +_default_text_stdout = _make_cached_stream_func( + lambda: sys.stdout, get_text_stdout) +_default_text_stderr = _make_cached_stream_func( + lambda: sys.stderr, get_text_stderr) + + +binary_streams = { + 'stdin': get_binary_stdin, + 'stdout': get_binary_stdout, + 'stderr': get_binary_stderr, +} + +text_streams = { + 'stdin': get_text_stdin, + 'stdout': get_text_stdout, + 'stderr': get_text_stderr, +} diff -Nru click-0.4.43+16.04.20160203/click/core.py click-6.7/click/core.py --- click-0.4.43+16.04.20160203/click/core.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/click/core.py 2017-01-06 22:32:37.000000000 +0000 @@ -0,0 +1,1744 @@ +import errno +import os +import sys +from contextlib import contextmanager +from itertools import repeat +from functools import update_wrapper + +from .types import convert_type, IntRange, BOOL +from .utils import make_str, make_default_short_help, echo, get_os_args +from .exceptions import ClickException, UsageError, BadParameter, Abort, \ + MissingParameter +from .termui import prompt, confirm +from .formatting import HelpFormatter, join_options +from .parser import OptionParser, split_opt +from .globals import push_context, pop_context + +from ._compat import PY2, isidentifier, iteritems +from ._unicodefun import _check_for_unicode_literals, _verify_python3_env + + +_missing = object() + + +SUBCOMMAND_METAVAR = 'COMMAND [ARGS]...' +SUBCOMMANDS_METAVAR = 'COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...' + + +def _bashcomplete(cmd, prog_name, complete_var=None): + """Internal handler for the bash completion support.""" + if complete_var is None: + complete_var = '_%s_COMPLETE' % (prog_name.replace('-', '_')).upper() + complete_instr = os.environ.get(complete_var) + if not complete_instr: + return + + from ._bashcomplete import bashcomplete + if bashcomplete(cmd, prog_name, complete_var, complete_instr): + sys.exit(1) + + +def _check_multicommand(base_command, cmd_name, cmd, register=False): + if not base_command.chain or not isinstance(cmd, MultiCommand): + return + if register: + hint = 'It is not possible to add multi commands as children to ' \ + 'another multi command that is in chain mode' + else: + hint = 'Found a multi command as subcommand to a multi command ' \ + 'that is in chain mode. This is not supported' + raise RuntimeError('%s. Command "%s" is set to chain and "%s" was ' + 'added as subcommand but it in itself is a ' + 'multi command. ("%s" is a %s within a chained ' + '%s named "%s"). This restriction was supposed to ' + 'be lifted in 6.0 but the fix was flawed. This ' + 'will be fixed in Click 7.0' % ( + hint, base_command.name, cmd_name, + cmd_name, cmd.__class__.__name__, + base_command.__class__.__name__, + base_command.name)) + + +def batch(iterable, batch_size): + return list(zip(*repeat(iter(iterable), batch_size))) + + +def invoke_param_callback(callback, ctx, param, value): + code = getattr(callback, '__code__', None) + args = getattr(code, 'co_argcount', 3) + + if args < 3: + # This will become a warning in Click 3.0: + from warnings import warn + warn(Warning('Invoked legacy parameter callback "%s". The new ' + 'signature for such callbacks starting with ' + 'click 2.0 is (ctx, param, value).' + % callback), stacklevel=3) + return callback(ctx, value) + return callback(ctx, param, value) + + +@contextmanager +def augment_usage_errors(ctx, param=None): + """Context manager that attaches extra information to exceptions that + fly. + """ + try: + yield + except BadParameter as e: + if e.ctx is None: + e.ctx = ctx + if param is not None and e.param is None: + e.param = param + raise + except UsageError as e: + if e.ctx is None: + e.ctx = ctx + raise + + +def iter_params_for_processing(invocation_order, declaration_order): + """Given a sequence of parameters in the order as should be considered + for processing and an iterable of parameters that exist, this returns + a list in the correct order as they should be processed. + """ + def sort_key(item): + try: + idx = invocation_order.index(item) + except ValueError: + idx = float('inf') + return (not item.is_eager, idx) + + return sorted(declaration_order, key=sort_key) + + +class Context(object): + """The context is a special internal object that holds state relevant + for the script execution at every single level. It's normally invisible + to commands unless they opt-in to getting access to it. + + The context is useful as it can pass internal objects around and can + control special execution features such as reading data from + environment variables. + + A context can be used as context manager in which case it will call + :meth:`close` on teardown. + + .. versionadded:: 2.0 + Added the `resilient_parsing`, `help_option_names`, + `token_normalize_func` parameters. + + .. versionadded:: 3.0 + Added the `allow_extra_args` and `allow_interspersed_args` + parameters. + + .. versionadded:: 4.0 + Added the `color`, `ignore_unknown_options`, and + `max_content_width` parameters. + + :param command: the command class for this context. + :param parent: the parent context. + :param info_name: the info name for this invocation. Generally this + is the most descriptive name for the script or + command. For the toplevel script it is usually + the name of the script, for commands below it it's + the name of the script. + :param obj: an arbitrary object of user data. + :param auto_envvar_prefix: the prefix to use for automatic environment + variables. If this is `None` then reading + from environment variables is disabled. This + does not affect manually set environment + variables which are always read. + :param default_map: a dictionary (like object) with default values + for parameters. + :param terminal_width: the width of the terminal. The default is + inherit from parent context. If no context + defines the terminal width then auto + detection will be applied. + :param max_content_width: the maximum width for content rendered by + Click (this currently only affects help + pages). This defaults to 80 characters if + not overridden. In other words: even if the + terminal is larger than that, Click will not + format things wider than 80 characters by + default. In addition to that, formatters might + add some safety mapping on the right. + :param resilient_parsing: if this flag is enabled then Click will + parse without any interactivity or callback + invocation. This is useful for implementing + things such as completion support. + :param allow_extra_args: if this is set to `True` then extra arguments + at the end will not raise an error and will be + kept on the context. The default is to inherit + from the command. + :param allow_interspersed_args: if this is set to `False` then options + and arguments cannot be mixed. The + default is to inherit from the command. + :param ignore_unknown_options: instructs click to ignore options it does + not know and keeps them for later + processing. + :param help_option_names: optionally a list of strings that define how + the default help parameter is named. The + default is ``['--help']``. + :param token_normalize_func: an optional function that is used to + normalize tokens (options, choices, + etc.). This for instance can be used to + implement case insensitive behavior. + :param color: controls if the terminal supports ANSI colors or not. The + default is autodetection. This is only needed if ANSI + codes are used in texts that Click prints which is by + default not the case. This for instance would affect + help output. + """ + + def __init__(self, command, parent=None, info_name=None, obj=None, + auto_envvar_prefix=None, default_map=None, + terminal_width=None, max_content_width=None, + resilient_parsing=False, allow_extra_args=None, + allow_interspersed_args=None, + ignore_unknown_options=None, help_option_names=None, + token_normalize_func=None, color=None): + #: the parent context or `None` if none exists. + self.parent = parent + #: the :class:`Command` for this context. + self.command = command + #: the descriptive information name + self.info_name = info_name + #: the parsed parameters except if the value is hidden in which + #: case it's not remembered. + self.params = {} + #: the leftover arguments. + self.args = [] + #: protected arguments. These are arguments that are prepended + #: to `args` when certain parsing scenarios are encountered but + #: must be never propagated to another arguments. This is used + #: to implement nested parsing. + self.protected_args = [] + if obj is None and parent is not None: + obj = parent.obj + #: the user object stored. + self.obj = obj + self._meta = getattr(parent, 'meta', {}) + + #: A dictionary (-like object) with defaults for parameters. + if default_map is None \ + and parent is not None \ + and parent.default_map is not None: + default_map = parent.default_map.get(info_name) + self.default_map = default_map + + #: This flag indicates if a subcommand is going to be executed. A + #: group callback can use this information to figure out if it's + #: being executed directly or because the execution flow passes + #: onwards to a subcommand. By default it's None, but it can be + #: the name of the subcommand to execute. + #: + #: If chaining is enabled this will be set to ``'*'`` in case + #: any commands are executed. It is however not possible to + #: figure out which ones. If you require this knowledge you + #: should use a :func:`resultcallback`. + self.invoked_subcommand = None + + if terminal_width is None and parent is not None: + terminal_width = parent.terminal_width + #: The width of the terminal (None is autodetection). + self.terminal_width = terminal_width + + if max_content_width is None and parent is not None: + max_content_width = parent.max_content_width + #: The maximum width of formatted content (None implies a sensible + #: default which is 80 for most things). + self.max_content_width = max_content_width + + if allow_extra_args is None: + allow_extra_args = command.allow_extra_args + #: Indicates if the context allows extra args or if it should + #: fail on parsing. + #: + #: .. versionadded:: 3.0 + self.allow_extra_args = allow_extra_args + + if allow_interspersed_args is None: + allow_interspersed_args = command.allow_interspersed_args + #: Indicates if the context allows mixing of arguments and + #: options or not. + #: + #: .. versionadded:: 3.0 + self.allow_interspersed_args = allow_interspersed_args + + if ignore_unknown_options is None: + ignore_unknown_options = command.ignore_unknown_options + #: Instructs click to ignore options that a command does not + #: understand and will store it on the context for later + #: processing. This is primarily useful for situations where you + #: want to call into external programs. Generally this pattern is + #: strongly discouraged because it's not possibly to losslessly + #: forward all arguments. + #: + #: .. versionadded:: 4.0 + self.ignore_unknown_options = ignore_unknown_options + + if help_option_names is None: + if parent is not None: + help_option_names = parent.help_option_names + else: + help_option_names = ['--help'] + + #: The names for the help options. + self.help_option_names = help_option_names + + if token_normalize_func is None and parent is not None: + token_normalize_func = parent.token_normalize_func + + #: An optional normalization function for tokens. This is + #: options, choices, commands etc. + self.token_normalize_func = token_normalize_func + + #: Indicates if resilient parsing is enabled. In that case Click + #: will do its best to not cause any failures. + self.resilient_parsing = resilient_parsing + + # If there is no envvar prefix yet, but the parent has one and + # the command on this level has a name, we can expand the envvar + # prefix automatically. + if auto_envvar_prefix is None: + if parent is not None \ + and parent.auto_envvar_prefix is not None and \ + self.info_name is not None: + auto_envvar_prefix = '%s_%s' % (parent.auto_envvar_prefix, + self.info_name.upper()) + else: + self.auto_envvar_prefix = auto_envvar_prefix.upper() + self.auto_envvar_prefix = auto_envvar_prefix + + if color is None and parent is not None: + color = parent.color + + #: Controls if styling output is wanted or not. + self.color = color + + self._close_callbacks = [] + self._depth = 0 + + def __enter__(self): + self._depth += 1 + push_context(self) + return self + + def __exit__(self, exc_type, exc_value, tb): + self._depth -= 1 + if self._depth == 0: + self.close() + pop_context() + + @contextmanager + def scope(self, cleanup=True): + """This helper method can be used with the context object to promote + it to the current thread local (see :func:`get_current_context`). + The default behavior of this is to invoke the cleanup functions which + can be disabled by setting `cleanup` to `False`. The cleanup + functions are typically used for things such as closing file handles. + + If the cleanup is intended the context object can also be directly + used as a context manager. + + Example usage:: + + with ctx.scope(): + assert get_current_context() is ctx + + This is equivalent:: + + with ctx: + assert get_current_context() is ctx + + .. versionadded:: 5.0 + + :param cleanup: controls if the cleanup functions should be run or + not. The default is to run these functions. In + some situations the context only wants to be + temporarily pushed in which case this can be disabled. + Nested pushes automatically defer the cleanup. + """ + if not cleanup: + self._depth += 1 + try: + with self as rv: + yield rv + finally: + if not cleanup: + self._depth -= 1 + + @property + def meta(self): + """This is a dictionary which is shared with all the contexts + that are nested. It exists so that click utiltiies can store some + state here if they need to. It is however the responsibility of + that code to manage this dictionary well. + + The keys are supposed to be unique dotted strings. For instance + module paths are a good choice for it. What is stored in there is + irrelevant for the operation of click. However what is important is + that code that places data here adheres to the general semantics of + the system. + + Example usage:: + + LANG_KEY = __name__ + '.lang' + + def set_language(value): + ctx = get_current_context() + ctx.meta[LANG_KEY] = value + + def get_language(): + return get_current_context().meta.get(LANG_KEY, 'en_US') + + .. versionadded:: 5.0 + """ + return self._meta + + def make_formatter(self): + """Creates the formatter for the help and usage output.""" + return HelpFormatter(width=self.terminal_width, + max_width=self.max_content_width) + + def call_on_close(self, f): + """This decorator remembers a function as callback that should be + executed when the context tears down. This is most useful to bind + resource handling to the script execution. For instance, file objects + opened by the :class:`File` type will register their close callbacks + here. + + :param f: the function to execute on teardown. + """ + self._close_callbacks.append(f) + return f + + def close(self): + """Invokes all close callbacks.""" + for cb in self._close_callbacks: + cb() + self._close_callbacks = [] + + @property + def command_path(self): + """The computed command path. This is used for the ``usage`` + information on the help page. It's automatically created by + combining the info names of the chain of contexts to the root. + """ + rv = '' + if self.info_name is not None: + rv = self.info_name + if self.parent is not None: + rv = self.parent.command_path + ' ' + rv + return rv.lstrip() + + def find_root(self): + """Finds the outermost context.""" + node = self + while node.parent is not None: + node = node.parent + return node + + def find_object(self, object_type): + """Finds the closest object of a given type.""" + node = self + while node is not None: + if isinstance(node.obj, object_type): + return node.obj + node = node.parent + + def ensure_object(self, object_type): + """Like :meth:`find_object` but sets the innermost object to a + new instance of `object_type` if it does not exist. + """ + rv = self.find_object(object_type) + if rv is None: + self.obj = rv = object_type() + return rv + + def lookup_default(self, name): + """Looks up the default for a parameter name. This by default + looks into the :attr:`default_map` if available. + """ + if self.default_map is not None: + rv = self.default_map.get(name) + if callable(rv): + rv = rv() + return rv + + def fail(self, message): + """Aborts the execution of the program with a specific error + message. + + :param message: the error message to fail with. + """ + raise UsageError(message, self) + + def abort(self): + """Aborts the script.""" + raise Abort() + + def exit(self, code=0): + """Exits the application with a given exit code.""" + sys.exit(code) + + def get_usage(self): + """Helper method to get formatted usage string for the current + context and command. + """ + return self.command.get_usage(self) + + def get_help(self): + """Helper method to get formatted help page for the current + context and command. + """ + return self.command.get_help(self) + + def invoke(*args, **kwargs): + """Invokes a command callback in exactly the way it expects. There + are two ways to invoke this method: + + 1. the first argument can be a callback and all other arguments and + keyword arguments are forwarded directly to the function. + 2. the first argument is a click command object. In that case all + arguments are forwarded as well but proper click parameters + (options and click arguments) must be keyword arguments and Click + will fill in defaults. + + Note that before Click 3.2 keyword arguments were not properly filled + in against the intention of this code and no context was created. For + more information about this change and why it was done in a bugfix + release see :ref:`upgrade-to-3.2`. + """ + self, callback = args[:2] + ctx = self + + # It's also possible to invoke another command which might or + # might not have a callback. In that case we also fill + # in defaults and make a new context for this command. + if isinstance(callback, Command): + other_cmd = callback + callback = other_cmd.callback + ctx = Context(other_cmd, info_name=other_cmd.name, parent=self) + if callback is None: + raise TypeError('The given command does not have a ' + 'callback that can be invoked.') + + for param in other_cmd.params: + if param.name not in kwargs and param.expose_value: + kwargs[param.name] = param.get_default(ctx) + + args = args[2:] + with augment_usage_errors(self): + with ctx: + return callback(*args, **kwargs) + + def forward(*args, **kwargs): + """Similar to :meth:`invoke` but fills in default keyword + arguments from the current context if the other command expects + it. This cannot invoke callbacks directly, only other commands. + """ + self, cmd = args[:2] + + # It's also possible to invoke another command which might or + # might not have a callback. + if not isinstance(cmd, Command): + raise TypeError('Callback is not a command.') + + for param in self.params: + if param not in kwargs: + kwargs[param] = self.params[param] + + return self.invoke(cmd, **kwargs) + + +class BaseCommand(object): + """The base command implements the minimal API contract of commands. + Most code will never use this as it does not implement a lot of useful + functionality but it can act as the direct subclass of alternative + parsing methods that do not depend on the Click parser. + + For instance, this can be used to bridge Click and other systems like + argparse or docopt. + + Because base commands do not implement a lot of the API that other + parts of Click take for granted, they are not supported for all + operations. For instance, they cannot be used with the decorators + usually and they have no built-in callback system. + + .. versionchanged:: 2.0 + Added the `context_settings` parameter. + + :param name: the name of the command to use unless a group overrides it. + :param context_settings: an optional dictionary with defaults that are + passed to the context object. + """ + #: the default for the :attr:`Context.allow_extra_args` flag. + allow_extra_args = False + #: the default for the :attr:`Context.allow_interspersed_args` flag. + allow_interspersed_args = True + #: the default for the :attr:`Context.ignore_unknown_options` flag. + ignore_unknown_options = False + + def __init__(self, name, context_settings=None): + #: the name the command thinks it has. Upon registering a command + #: on a :class:`Group` the group will default the command name + #: with this information. You should instead use the + #: :class:`Context`\'s :attr:`~Context.info_name` attribute. + self.name = name + if context_settings is None: + context_settings = {} + #: an optional dictionary with defaults passed to the context. + self.context_settings = context_settings + + def get_usage(self, ctx): + raise NotImplementedError('Base commands cannot get usage') + + def get_help(self, ctx): + raise NotImplementedError('Base commands cannot get help') + + def make_context(self, info_name, args, parent=None, **extra): + """This function when given an info name and arguments will kick + off the parsing and create a new :class:`Context`. It does not + invoke the actual command callback though. + + :param info_name: the info name for this invokation. Generally this + is the most descriptive name for the script or + command. For the toplevel script it's usually + the name of the script, for commands below it it's + the name of the script. + :param args: the arguments to parse as list of strings. + :param parent: the parent context if available. + :param extra: extra keyword arguments forwarded to the context + constructor. + """ + for key, value in iteritems(self.context_settings): + if key not in extra: + extra[key] = value + ctx = Context(self, info_name=info_name, parent=parent, **extra) + with ctx.scope(cleanup=False): + self.parse_args(ctx, args) + return ctx + + def parse_args(self, ctx, args): + """Given a context and a list of arguments this creates the parser + and parses the arguments, then modifies the context as necessary. + This is automatically invoked by :meth:`make_context`. + """ + raise NotImplementedError('Base commands do not know how to parse ' + 'arguments.') + + def invoke(self, ctx): + """Given a context, this invokes the command. The default + implementation is raising a not implemented error. + """ + raise NotImplementedError('Base commands are not invokable by default') + + def main(self, args=None, prog_name=None, complete_var=None, + standalone_mode=True, **extra): + """This is the way to invoke a script with all the bells and + whistles as a command line application. This will always terminate + the application after a call. If this is not wanted, ``SystemExit`` + needs to be caught. + + This method is also available by directly calling the instance of + a :class:`Command`. + + .. versionadded:: 3.0 + Added the `standalone_mode` flag to control the standalone mode. + + :param args: the arguments that should be used for parsing. If not + provided, ``sys.argv[1:]`` is used. + :param prog_name: the program name that should be used. By default + the program name is constructed by taking the file + name from ``sys.argv[0]``. + :param complete_var: the environment variable that controls the + bash completion support. The default is + ``"__COMPLETE"`` with prog name in + uppercase. + :param standalone_mode: the default behavior is to invoke the script + in standalone mode. Click will then + handle exceptions and convert them into + error messages and the function will never + return but shut down the interpreter. If + this is set to `False` they will be + propagated to the caller and the return + value of this function is the return value + of :meth:`invoke`. + :param extra: extra keyword arguments are forwarded to the context + constructor. See :class:`Context` for more information. + """ + # If we are in Python 3, we will verify that the environment is + # sane at this point of reject further execution to avoid a + # broken script. + if not PY2: + _verify_python3_env() + else: + _check_for_unicode_literals() + + if args is None: + args = get_os_args() + else: + args = list(args) + + if prog_name is None: + prog_name = make_str(os.path.basename( + sys.argv and sys.argv[0] or __file__)) + + # Hook for the Bash completion. This only activates if the Bash + # completion is actually enabled, otherwise this is quite a fast + # noop. + _bashcomplete(self, prog_name, complete_var) + + try: + try: + with self.make_context(prog_name, args, **extra) as ctx: + rv = self.invoke(ctx) + if not standalone_mode: + return rv + ctx.exit() + except (EOFError, KeyboardInterrupt): + echo(file=sys.stderr) + raise Abort() + except ClickException as e: + if not standalone_mode: + raise + e.show() + sys.exit(e.exit_code) + except IOError as e: + if e.errno == errno.EPIPE: + sys.exit(1) + else: + raise + except Abort: + if not standalone_mode: + raise + echo('Aborted!', file=sys.stderr) + sys.exit(1) + + def __call__(self, *args, **kwargs): + """Alias for :meth:`main`.""" + return self.main(*args, **kwargs) + + +class Command(BaseCommand): + """Commands are the basic building block of command line interfaces in + Click. A basic command handles command line parsing and might dispatch + more parsing to commands nested below it. + + .. versionchanged:: 2.0 + Added the `context_settings` parameter. + + :param name: the name of the command to use unless a group overrides it. + :param context_settings: an optional dictionary with defaults that are + passed to the context object. + :param callback: the callback to invoke. This is optional. + :param params: the parameters to register with this command. This can + be either :class:`Option` or :class:`Argument` objects. + :param help: the help string to use for this command. + :param epilog: like the help string but it's printed at the end of the + help page after everything else. + :param short_help: the short help to use for this command. This is + shown on the command listing of the parent command. + :param add_help_option: by default each command registers a ``--help`` + option. This can be disabled by this parameter. + """ + + def __init__(self, name, context_settings=None, callback=None, + params=None, help=None, epilog=None, short_help=None, + options_metavar='[OPTIONS]', add_help_option=True): + BaseCommand.__init__(self, name, context_settings) + #: the callback to execute when the command fires. This might be + #: `None` in which case nothing happens. + self.callback = callback + #: the list of parameters for this command in the order they + #: should show up in the help page and execute. Eager parameters + #: will automatically be handled before non eager ones. + self.params = params or [] + self.help = help + self.epilog = epilog + self.options_metavar = options_metavar + if short_help is None and help: + short_help = make_default_short_help(help) + self.short_help = short_help + self.add_help_option = add_help_option + + def get_usage(self, ctx): + formatter = ctx.make_formatter() + self.format_usage(ctx, formatter) + return formatter.getvalue().rstrip('\n') + + def get_params(self, ctx): + rv = self.params + help_option = self.get_help_option(ctx) + if help_option is not None: + rv = rv + [help_option] + return rv + + def format_usage(self, ctx, formatter): + """Writes the usage line into the formatter.""" + pieces = self.collect_usage_pieces(ctx) + formatter.write_usage(ctx.command_path, ' '.join(pieces)) + + def collect_usage_pieces(self, ctx): + """Returns all the pieces that go into the usage line and returns + it as a list of strings. + """ + rv = [self.options_metavar] + for param in self.get_params(ctx): + rv.extend(param.get_usage_pieces(ctx)) + return rv + + def get_help_option_names(self, ctx): + """Returns the names for the help option.""" + all_names = set(ctx.help_option_names) + for param in self.params: + all_names.difference_update(param.opts) + all_names.difference_update(param.secondary_opts) + return all_names + + def get_help_option(self, ctx): + """Returns the help option object.""" + help_options = self.get_help_option_names(ctx) + if not help_options or not self.add_help_option: + return + + def show_help(ctx, param, value): + if value and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + return Option(help_options, is_flag=True, + is_eager=True, expose_value=False, + callback=show_help, + help='Show this message and exit.') + + def make_parser(self, ctx): + """Creates the underlying option parser for this command.""" + parser = OptionParser(ctx) + parser.allow_interspersed_args = ctx.allow_interspersed_args + parser.ignore_unknown_options = ctx.ignore_unknown_options + for param in self.get_params(ctx): + param.add_to_parser(parser, ctx) + return parser + + def get_help(self, ctx): + """Formats the help into a string and returns it. This creates a + formatter and will call into the following formatting methods: + """ + formatter = ctx.make_formatter() + self.format_help(ctx, formatter) + return formatter.getvalue().rstrip('\n') + + def format_help(self, ctx, formatter): + """Writes the help into the formatter if it exists. + + This calls into the following methods: + + - :meth:`format_usage` + - :meth:`format_help_text` + - :meth:`format_options` + - :meth:`format_epilog` + """ + self.format_usage(ctx, formatter) + self.format_help_text(ctx, formatter) + self.format_options(ctx, formatter) + self.format_epilog(ctx, formatter) + + def format_help_text(self, ctx, formatter): + """Writes the help text to the formatter if it exists.""" + if self.help: + formatter.write_paragraph() + with formatter.indentation(): + formatter.write_text(self.help) + + def format_options(self, ctx, formatter): + """Writes all the options into the formatter if they exist.""" + opts = [] + for param in self.get_params(ctx): + rv = param.get_help_record(ctx) + if rv is not None: + opts.append(rv) + + if opts: + with formatter.section('Options'): + formatter.write_dl(opts) + + def format_epilog(self, ctx, formatter): + """Writes the epilog into the formatter if it exists.""" + if self.epilog: + formatter.write_paragraph() + with formatter.indentation(): + formatter.write_text(self.epilog) + + def parse_args(self, ctx, args): + parser = self.make_parser(ctx) + opts, args, param_order = parser.parse_args(args=args) + + for param in iter_params_for_processing( + param_order, self.get_params(ctx)): + value, args = param.handle_parse_result(ctx, opts, args) + + if args and not ctx.allow_extra_args and not ctx.resilient_parsing: + ctx.fail('Got unexpected extra argument%s (%s)' + % (len(args) != 1 and 's' or '', + ' '.join(map(make_str, args)))) + + ctx.args = args + return args + + def invoke(self, ctx): + """Given a context, this invokes the attached callback (if it exists) + in the right way. + """ + if self.callback is not None: + return ctx.invoke(self.callback, **ctx.params) + + +class MultiCommand(Command): + """A multi command is the basic implementation of a command that + dispatches to subcommands. The most common version is the + :class:`Group`. + + :param invoke_without_command: this controls how the multi command itself + is invoked. By default it's only invoked + if a subcommand is provided. + :param no_args_is_help: this controls what happens if no arguments are + provided. This option is enabled by default if + `invoke_without_command` is disabled or disabled + if it's enabled. If enabled this will add + ``--help`` as argument if no arguments are + passed. + :param subcommand_metavar: the string that is used in the documentation + to indicate the subcommand place. + :param chain: if this is set to `True` chaining of multiple subcommands + is enabled. This restricts the form of commands in that + they cannot have optional arguments but it allows + multiple commands to be chained together. + :param result_callback: the result callback to attach to this multi + command. + """ + allow_extra_args = True + allow_interspersed_args = False + + def __init__(self, name=None, invoke_without_command=False, + no_args_is_help=None, subcommand_metavar=None, + chain=False, result_callback=None, **attrs): + Command.__init__(self, name, **attrs) + if no_args_is_help is None: + no_args_is_help = not invoke_without_command + self.no_args_is_help = no_args_is_help + self.invoke_without_command = invoke_without_command + if subcommand_metavar is None: + if chain: + subcommand_metavar = SUBCOMMANDS_METAVAR + else: + subcommand_metavar = SUBCOMMAND_METAVAR + self.subcommand_metavar = subcommand_metavar + self.chain = chain + #: The result callback that is stored. This can be set or + #: overridden with the :func:`resultcallback` decorator. + self.result_callback = result_callback + + if self.chain: + for param in self.params: + if isinstance(param, Argument) and not param.required: + raise RuntimeError('Multi commands in chain mode cannot ' + 'have optional arguments.') + + def collect_usage_pieces(self, ctx): + rv = Command.collect_usage_pieces(self, ctx) + rv.append(self.subcommand_metavar) + return rv + + def format_options(self, ctx, formatter): + Command.format_options(self, ctx, formatter) + self.format_commands(ctx, formatter) + + def resultcallback(self, replace=False): + """Adds a result callback to the chain command. By default if a + result callback is already registered this will chain them but + this can be disabled with the `replace` parameter. The result + callback is invoked with the return value of the subcommand + (or the list of return values from all subcommands if chaining + is enabled) as well as the parameters as they would be passed + to the main callback. + + Example:: + + @click.group() + @click.option('-i', '--input', default=23) + def cli(input): + return 42 + + @cli.resultcallback() + def process_result(result, input): + return result + input + + .. versionadded:: 3.0 + + :param replace: if set to `True` an already existing result + callback will be removed. + """ + def decorator(f): + old_callback = self.result_callback + if old_callback is None or replace: + self.result_callback = f + return f + def function(__value, *args, **kwargs): + return f(old_callback(__value, *args, **kwargs), + *args, **kwargs) + self.result_callback = rv = update_wrapper(function, f) + return rv + return decorator + + def format_commands(self, ctx, formatter): + """Extra format methods for multi methods that adds all the commands + after the options. + """ + rows = [] + for subcommand in self.list_commands(ctx): + cmd = self.get_command(ctx, subcommand) + # What is this, the tool lied about a command. Ignore it + if cmd is None: + continue + + help = cmd.short_help or '' + rows.append((subcommand, help)) + + if rows: + with formatter.section('Commands'): + formatter.write_dl(rows) + + def parse_args(self, ctx, args): + if not args and self.no_args_is_help and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + + rest = Command.parse_args(self, ctx, args) + if self.chain: + ctx.protected_args = rest + ctx.args = [] + elif rest: + ctx.protected_args, ctx.args = rest[:1], rest[1:] + + return ctx.args + + def invoke(self, ctx): + def _process_result(value): + if self.result_callback is not None: + value = ctx.invoke(self.result_callback, value, + **ctx.params) + return value + + if not ctx.protected_args: + # If we are invoked without command the chain flag controls + # how this happens. If we are not in chain mode, the return + # value here is the return value of the command. + # If however we are in chain mode, the return value is the + # return value of the result processor invoked with an empty + # list (which means that no subcommand actually was executed). + if self.invoke_without_command: + if not self.chain: + return Command.invoke(self, ctx) + with ctx: + Command.invoke(self, ctx) + return _process_result([]) + ctx.fail('Missing command.') + + # Fetch args back out + args = ctx.protected_args + ctx.args + ctx.args = [] + ctx.protected_args = [] + + # If we're not in chain mode, we only allow the invocation of a + # single command but we also inform the current context about the + # name of the command to invoke. + if not self.chain: + # Make sure the context is entered so we do not clean up + # resources until the result processor has worked. + with ctx: + cmd_name, cmd, args = self.resolve_command(ctx, args) + ctx.invoked_subcommand = cmd_name + Command.invoke(self, ctx) + sub_ctx = cmd.make_context(cmd_name, args, parent=ctx) + with sub_ctx: + return _process_result(sub_ctx.command.invoke(sub_ctx)) + + # In chain mode we create the contexts step by step, but after the + # base command has been invoked. Because at that point we do not + # know the subcommands yet, the invoked subcommand attribute is + # set to ``*`` to inform the command that subcommands are executed + # but nothing else. + with ctx: + ctx.invoked_subcommand = args and '*' or None + Command.invoke(self, ctx) + + # Otherwise we make every single context and invoke them in a + # chain. In that case the return value to the result processor + # is the list of all invoked subcommand's results. + contexts = [] + while args: + cmd_name, cmd, args = self.resolve_command(ctx, args) + sub_ctx = cmd.make_context(cmd_name, args, parent=ctx, + allow_extra_args=True, + allow_interspersed_args=False) + contexts.append(sub_ctx) + args, sub_ctx.args = sub_ctx.args, [] + + rv = [] + for sub_ctx in contexts: + with sub_ctx: + rv.append(sub_ctx.command.invoke(sub_ctx)) + return _process_result(rv) + + def resolve_command(self, ctx, args): + cmd_name = make_str(args[0]) + original_cmd_name = cmd_name + + # Get the command + cmd = self.get_command(ctx, cmd_name) + + # If we can't find the command but there is a normalization + # function available, we try with that one. + if cmd is None and ctx.token_normalize_func is not None: + cmd_name = ctx.token_normalize_func(cmd_name) + cmd = self.get_command(ctx, cmd_name) + + # If we don't find the command we want to show an error message + # to the user that it was not provided. However, there is + # something else we should do: if the first argument looks like + # an option we want to kick off parsing again for arguments to + # resolve things like --help which now should go to the main + # place. + if cmd is None: + if split_opt(cmd_name)[0]: + self.parse_args(ctx, ctx.args) + ctx.fail('No such command "%s".' % original_cmd_name) + + return cmd_name, cmd, args[1:] + + def get_command(self, ctx, cmd_name): + """Given a context and a command name, this returns a + :class:`Command` object if it exists or returns `None`. + """ + raise NotImplementedError() + + def list_commands(self, ctx): + """Returns a list of subcommand names in the order they should + appear. + """ + return [] + + +class Group(MultiCommand): + """A group allows a command to have subcommands attached. This is the + most common way to implement nesting in Click. + + :param commands: a dictionary of commands. + """ + + def __init__(self, name=None, commands=None, **attrs): + MultiCommand.__init__(self, name, **attrs) + #: the registered subcommands by their exported names. + self.commands = commands or {} + + def add_command(self, cmd, name=None): + """Registers another :class:`Command` with this group. If the name + is not provided, the name of the command is used. + """ + name = name or cmd.name + if name is None: + raise TypeError('Command has no name.') + _check_multicommand(self, name, cmd, register=True) + self.commands[name] = cmd + + def command(self, *args, **kwargs): + """A shortcut decorator for declaring and attaching a command to + the group. This takes the same arguments as :func:`command` but + immediately registers the created command with this instance by + calling into :meth:`add_command`. + """ + def decorator(f): + cmd = command(*args, **kwargs)(f) + self.add_command(cmd) + return cmd + return decorator + + def group(self, *args, **kwargs): + """A shortcut decorator for declaring and attaching a group to + the group. This takes the same arguments as :func:`group` but + immediately registers the created command with this instance by + calling into :meth:`add_command`. + """ + def decorator(f): + cmd = group(*args, **kwargs)(f) + self.add_command(cmd) + return cmd + return decorator + + def get_command(self, ctx, cmd_name): + return self.commands.get(cmd_name) + + def list_commands(self, ctx): + return sorted(self.commands) + + +class CommandCollection(MultiCommand): + """A command collection is a multi command that merges multiple multi + commands together into one. This is a straightforward implementation + that accepts a list of different multi commands as sources and + provides all the commands for each of them. + """ + + def __init__(self, name=None, sources=None, **attrs): + MultiCommand.__init__(self, name, **attrs) + #: The list of registered multi commands. + self.sources = sources or [] + + def add_source(self, multi_cmd): + """Adds a new multi command to the chain dispatcher.""" + self.sources.append(multi_cmd) + + def get_command(self, ctx, cmd_name): + for source in self.sources: + rv = source.get_command(ctx, cmd_name) + if rv is not None: + if self.chain: + _check_multicommand(self, cmd_name, rv) + return rv + + def list_commands(self, ctx): + rv = set() + for source in self.sources: + rv.update(source.list_commands(ctx)) + return sorted(rv) + + +class Parameter(object): + """A parameter to a command comes in two versions: they are either + :class:`Option`\s or :class:`Argument`\s. Other subclasses are currently + not supported by design as some of the internals for parsing are + intentionally not finalized. + + Some settings are supported by both options and arguments. + + .. versionchanged:: 2.0 + Changed signature for parameter callback to also be passed the + parameter. In Click 2.0, the old callback format will still work, + but it will raise a warning to give you change to migrate the + code easier. + + :param param_decls: the parameter declarations for this option or + argument. This is a list of flags or argument + names. + :param type: the type that should be used. Either a :class:`ParamType` + or a Python type. The later is converted into the former + automatically if supported. + :param required: controls if this is optional or not. + :param default: the default value if omitted. This can also be a callable, + in which case it's invoked when the default is needed + without any arguments. + :param callback: a callback that should be executed after the parameter + was matched. This is called as ``fn(ctx, param, + value)`` and needs to return the value. Before Click + 2.0, the signature was ``(ctx, value)``. + :param nargs: the number of arguments to match. If not ``1`` the return + value is a tuple instead of single value. The default for + nargs is ``1`` (except if the type is a tuple, then it's + the arity of the tuple). + :param metavar: how the value is represented in the help page. + :param expose_value: if this is `True` then the value is passed onwards + to the command callback and stored on the context, + otherwise it's skipped. + :param is_eager: eager values are processed before non eager ones. This + should not be set for arguments or it will inverse the + order of processing. + :param envvar: a string or list of strings that are environment variables + that should be checked. + """ + param_type_name = 'parameter' + + def __init__(self, param_decls=None, type=None, required=False, + default=None, callback=None, nargs=None, metavar=None, + expose_value=True, is_eager=False, envvar=None): + self.name, self.opts, self.secondary_opts = \ + self._parse_decls(param_decls or (), expose_value) + + self.type = convert_type(type, default) + + # Default nargs to what the type tells us if we have that + # information available. + if nargs is None: + if self.type.is_composite: + nargs = self.type.arity + else: + nargs = 1 + + self.required = required + self.callback = callback + self.nargs = nargs + self.multiple = False + self.expose_value = expose_value + self.default = default + self.is_eager = is_eager + self.metavar = metavar + self.envvar = envvar + + @property + def human_readable_name(self): + """Returns the human readable name of this parameter. This is the + same as the name for options, but the metavar for arguments. + """ + return self.name + + def make_metavar(self): + if self.metavar is not None: + return self.metavar + metavar = self.type.get_metavar(self) + if metavar is None: + metavar = self.type.name.upper() + if self.nargs != 1: + metavar += '...' + return metavar + + def get_default(self, ctx): + """Given a context variable this calculates the default value.""" + # Otherwise go with the regular default. + if callable(self.default): + rv = self.default() + else: + rv = self.default + return self.type_cast_value(ctx, rv) + + def add_to_parser(self, parser, ctx): + pass + + def consume_value(self, ctx, opts): + value = opts.get(self.name) + if value is None: + value = ctx.lookup_default(self.name) + if value is None: + value = self.value_from_envvar(ctx) + return value + + def type_cast_value(self, ctx, value): + """Given a value this runs it properly through the type system. + This automatically handles things like `nargs` and `multiple` as + well as composite types. + """ + if self.type.is_composite: + if self.nargs <= 1: + raise TypeError('Attempted to invoke composite type ' + 'but nargs has been set to %s. This is ' + 'not supported; nargs needs to be set to ' + 'a fixed value > 1.' % self.nargs) + if self.multiple: + return tuple(self.type(x or (), self, ctx) for x in value or ()) + return self.type(value or (), self, ctx) + + def _convert(value, level): + if level == 0: + return self.type(value, self, ctx) + return tuple(_convert(x, level - 1) for x in value or ()) + return _convert(value, (self.nargs != 1) + bool(self.multiple)) + + def process_value(self, ctx, value): + """Given a value and context this runs the logic to convert the + value as necessary. + """ + # If the value we were given is None we do nothing. This way + # code that calls this can easily figure out if something was + # not provided. Otherwise it would be converted into an empty + # tuple for multiple invocations which is inconvenient. + if value is not None: + return self.type_cast_value(ctx, value) + + def value_is_missing(self, value): + if value is None: + return True + if (self.nargs != 1 or self.multiple) and value == (): + return True + return False + + def full_process_value(self, ctx, value): + value = self.process_value(ctx, value) + + if value is None: + value = self.get_default(ctx) + + if self.required and self.value_is_missing(value): + raise MissingParameter(ctx=ctx, param=self) + + return value + + def resolve_envvar_value(self, ctx): + if self.envvar is None: + return + if isinstance(self.envvar, (tuple, list)): + for envvar in self.envvar: + rv = os.environ.get(envvar) + if rv is not None: + return rv + else: + return os.environ.get(self.envvar) + + def value_from_envvar(self, ctx): + rv = self.resolve_envvar_value(ctx) + if rv is not None and self.nargs != 1: + rv = self.type.split_envvar_value(rv) + return rv + + def handle_parse_result(self, ctx, opts, args): + with augment_usage_errors(ctx, param=self): + value = self.consume_value(ctx, opts) + try: + value = self.full_process_value(ctx, value) + except Exception: + if not ctx.resilient_parsing: + raise + value = None + if self.callback is not None: + try: + value = invoke_param_callback( + self.callback, ctx, self, value) + except Exception: + if not ctx.resilient_parsing: + raise + + if self.expose_value: + ctx.params[self.name] = value + return value, args + + def get_help_record(self, ctx): + pass + + def get_usage_pieces(self, ctx): + return [] + + +class Option(Parameter): + """Options are usually optional values on the command line and + have some extra features that arguments don't have. + + All other parameters are passed onwards to the parameter constructor. + + :param show_default: controls if the default value should be shown on the + help page. Normally, defaults are not shown. + :param prompt: if set to `True` or a non empty string then the user will + be prompted for input if not set. If set to `True` the + prompt will be the option name capitalized. + :param confirmation_prompt: if set then the value will need to be confirmed + if it was prompted for. + :param hide_input: if this is `True` then the input on the prompt will be + hidden from the user. This is useful for password + input. + :param is_flag: forces this option to act as a flag. The default is + auto detection. + :param flag_value: which value should be used for this flag if it's + enabled. This is set to a boolean automatically if + the option string contains a slash to mark two options. + :param multiple: if this is set to `True` then the argument is accepted + multiple times and recorded. This is similar to ``nargs`` + in how it works but supports arbitrary number of + arguments. + :param count: this flag makes an option increment an integer. + :param allow_from_autoenv: if this is enabled then the value of this + parameter will be pulled from an environment + variable in case a prefix is defined on the + context. + :param help: the help string. + """ + param_type_name = 'option' + + def __init__(self, param_decls=None, show_default=False, + prompt=False, confirmation_prompt=False, + hide_input=False, is_flag=None, flag_value=None, + multiple=False, count=False, allow_from_autoenv=True, + type=None, help=None, **attrs): + default_is_missing = attrs.get('default', _missing) is _missing + Parameter.__init__(self, param_decls, type=type, **attrs) + + if prompt is True: + prompt_text = self.name.replace('_', ' ').capitalize() + elif prompt is False: + prompt_text = None + else: + prompt_text = prompt + self.prompt = prompt_text + self.confirmation_prompt = confirmation_prompt + self.hide_input = hide_input + + # Flags + if is_flag is None: + if flag_value is not None: + is_flag = True + else: + is_flag = bool(self.secondary_opts) + if is_flag and default_is_missing: + self.default = False + if flag_value is None: + flag_value = not self.default + self.is_flag = is_flag + self.flag_value = flag_value + if self.is_flag and isinstance(self.flag_value, bool) \ + and type is None: + self.type = BOOL + self.is_bool_flag = True + else: + self.is_bool_flag = False + + # Counting + self.count = count + if count: + if type is None: + self.type = IntRange(min=0) + if default_is_missing: + self.default = 0 + + self.multiple = multiple + self.allow_from_autoenv = allow_from_autoenv + self.help = help + self.show_default = show_default + + # Sanity check for stuff we don't support + if __debug__: + if self.nargs < 0: + raise TypeError('Options cannot have nargs < 0') + if self.prompt and self.is_flag and not self.is_bool_flag: + raise TypeError('Cannot prompt for flags that are not bools.') + if not self.is_bool_flag and self.secondary_opts: + raise TypeError('Got secondary option for non boolean flag.') + if self.is_bool_flag and self.hide_input \ + and self.prompt is not None: + raise TypeError('Hidden input does not work with boolean ' + 'flag prompts.') + if self.count: + if self.multiple: + raise TypeError('Options cannot be multiple and count ' + 'at the same time.') + elif self.is_flag: + raise TypeError('Options cannot be count and flags at ' + 'the same time.') + + def _parse_decls(self, decls, expose_value): + opts = [] + secondary_opts = [] + name = None + possible_names = [] + + for decl in decls: + if isidentifier(decl): + if name is not None: + raise TypeError('Name defined twice') + name = decl + else: + split_char = decl[:1] == '/' and ';' or '/' + if split_char in decl: + first, second = decl.split(split_char, 1) + first = first.rstrip() + if first: + possible_names.append(split_opt(first)) + opts.append(first) + second = second.lstrip() + if second: + secondary_opts.append(second.lstrip()) + else: + possible_names.append(split_opt(decl)) + opts.append(decl) + + if name is None and possible_names: + possible_names.sort(key=lambda x: len(x[0])) + name = possible_names[-1][1].replace('-', '_').lower() + if not isidentifier(name): + name = None + + if name is None: + if not expose_value: + return None, opts, secondary_opts + raise TypeError('Could not determine name for option') + + if not opts and not secondary_opts: + raise TypeError('No options defined but a name was passed (%s). ' + 'Did you mean to declare an argument instead ' + 'of an option?' % name) + + return name, opts, secondary_opts + + def add_to_parser(self, parser, ctx): + kwargs = { + 'dest': self.name, + 'nargs': self.nargs, + 'obj': self, + } + + if self.multiple: + action = 'append' + elif self.count: + action = 'count' + else: + action = 'store' + + if self.is_flag: + kwargs.pop('nargs', None) + if self.is_bool_flag and self.secondary_opts: + parser.add_option(self.opts, action=action + '_const', + const=True, **kwargs) + parser.add_option(self.secondary_opts, action=action + + '_const', const=False, **kwargs) + else: + parser.add_option(self.opts, action=action + '_const', + const=self.flag_value, + **kwargs) + else: + kwargs['action'] = action + parser.add_option(self.opts, **kwargs) + + def get_help_record(self, ctx): + any_prefix_is_slash = [] + + def _write_opts(opts): + rv, any_slashes = join_options(opts) + if any_slashes: + any_prefix_is_slash[:] = [True] + if not self.is_flag and not self.count: + rv += ' ' + self.make_metavar() + return rv + + rv = [_write_opts(self.opts)] + if self.secondary_opts: + rv.append(_write_opts(self.secondary_opts)) + + help = self.help or '' + extra = [] + if self.default is not None and self.show_default: + extra.append('default: %s' % ( + ', '.join('%s' % d for d in self.default) + if isinstance(self.default, (list, tuple)) + else self.default, )) + if self.required: + extra.append('required') + if extra: + help = '%s[%s]' % (help and help + ' ' or '', '; '.join(extra)) + + return ((any_prefix_is_slash and '; ' or ' / ').join(rv), help) + + def get_default(self, ctx): + # If we're a non boolean flag out default is more complex because + # we need to look at all flags in the same group to figure out + # if we're the the default one in which case we return the flag + # value as default. + if self.is_flag and not self.is_bool_flag: + for param in ctx.command.params: + if param.name == self.name and param.default: + return param.flag_value + return None + return Parameter.get_default(self, ctx) + + def prompt_for_value(self, ctx): + """This is an alternative flow that can be activated in the full + value processing if a value does not exist. It will prompt the + user until a valid value exists and then returns the processed + value as result. + """ + # Calculate the default before prompting anything to be stable. + default = self.get_default(ctx) + + # If this is a prompt for a flag we need to handle this + # differently. + if self.is_bool_flag: + return confirm(self.prompt, default) + + return prompt(self.prompt, default=default, + hide_input=self.hide_input, + confirmation_prompt=self.confirmation_prompt, + value_proc=lambda x: self.process_value(ctx, x)) + + def resolve_envvar_value(self, ctx): + rv = Parameter.resolve_envvar_value(self, ctx) + if rv is not None: + return rv + if self.allow_from_autoenv and \ + ctx.auto_envvar_prefix is not None: + envvar = '%s_%s' % (ctx.auto_envvar_prefix, self.name.upper()) + return os.environ.get(envvar) + + def value_from_envvar(self, ctx): + rv = self.resolve_envvar_value(ctx) + if rv is None: + return None + value_depth = (self.nargs != 1) + bool(self.multiple) + if value_depth > 0 and rv is not None: + rv = self.type.split_envvar_value(rv) + if self.multiple and self.nargs != 1: + rv = batch(rv, self.nargs) + return rv + + def full_process_value(self, ctx, value): + if value is None and self.prompt is not None \ + and not ctx.resilient_parsing: + return self.prompt_for_value(ctx) + return Parameter.full_process_value(self, ctx, value) + + +class Argument(Parameter): + """Arguments are positional parameters to a command. They generally + provide fewer features than options but can have infinite ``nargs`` + and are required by default. + + All parameters are passed onwards to the parameter constructor. + """ + param_type_name = 'argument' + + def __init__(self, param_decls, required=None, **attrs): + if required is None: + if attrs.get('default') is not None: + required = False + else: + required = attrs.get('nargs', 1) > 0 + Parameter.__init__(self, param_decls, required=required, **attrs) + if self.default is not None and self.nargs < 0: + raise TypeError('nargs=-1 in combination with a default value ' + 'is not supported.') + + @property + def human_readable_name(self): + if self.metavar is not None: + return self.metavar + return self.name.upper() + + def make_metavar(self): + if self.metavar is not None: + return self.metavar + var = self.name.upper() + if not self.required: + var = '[%s]' % var + if self.nargs != 1: + var += '...' + return var + + def _parse_decls(self, decls, expose_value): + if not decls: + if not expose_value: + return None, [], [] + raise TypeError('Could not determine name for argument') + if len(decls) == 1: + name = arg = decls[0] + name = name.replace('-', '_').lower() + elif len(decls) == 2: + name, arg = decls + else: + raise TypeError('Arguments take exactly one or two ' + 'parameter declarations, got %d' % len(decls)) + return name, [arg], [] + + def get_usage_pieces(self, ctx): + return [self.make_metavar()] + + def add_to_parser(self, parser, ctx): + parser.add_argument(dest=self.name, nargs=self.nargs, + obj=self) + + +# Circular dependency between decorators and core +from .decorators import command, group diff -Nru click-0.4.43+16.04.20160203/click/decorators.py click-6.7/click/decorators.py --- click-0.4.43+16.04.20160203/click/decorators.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/click/decorators.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,304 @@ +import sys +import inspect + +from functools import update_wrapper + +from ._compat import iteritems +from ._unicodefun import _check_for_unicode_literals +from .utils import echo +from .globals import get_current_context + + +def pass_context(f): + """Marks a callback as wanting to receive the current context + object as first argument. + """ + def new_func(*args, **kwargs): + return f(get_current_context(), *args, **kwargs) + return update_wrapper(new_func, f) + + +def pass_obj(f): + """Similar to :func:`pass_context`, but only pass the object on the + context onwards (:attr:`Context.obj`). This is useful if that object + represents the state of a nested system. + """ + def new_func(*args, **kwargs): + return f(get_current_context().obj, *args, **kwargs) + return update_wrapper(new_func, f) + + +def make_pass_decorator(object_type, ensure=False): + """Given an object type this creates a decorator that will work + similar to :func:`pass_obj` but instead of passing the object of the + current context, it will find the innermost context of type + :func:`object_type`. + + This generates a decorator that works roughly like this:: + + from functools import update_wrapper + + def decorator(f): + @pass_context + def new_func(ctx, *args, **kwargs): + obj = ctx.find_object(object_type) + return ctx.invoke(f, obj, *args, **kwargs) + return update_wrapper(new_func, f) + return decorator + + :param object_type: the type of the object to pass. + :param ensure: if set to `True`, a new object will be created and + remembered on the context if it's not there yet. + """ + def decorator(f): + def new_func(*args, **kwargs): + ctx = get_current_context() + if ensure: + obj = ctx.ensure_object(object_type) + else: + obj = ctx.find_object(object_type) + if obj is None: + raise RuntimeError('Managed to invoke callback without a ' + 'context object of type %r existing' + % object_type.__name__) + return ctx.invoke(f, obj, *args[1:], **kwargs) + return update_wrapper(new_func, f) + return decorator + + +def _make_command(f, name, attrs, cls): + if isinstance(f, Command): + raise TypeError('Attempted to convert a callback into a ' + 'command twice.') + try: + params = f.__click_params__ + params.reverse() + del f.__click_params__ + except AttributeError: + params = [] + help = attrs.get('help') + if help is None: + help = inspect.getdoc(f) + if isinstance(help, bytes): + help = help.decode('utf-8') + else: + help = inspect.cleandoc(help) + attrs['help'] = help + _check_for_unicode_literals() + return cls(name=name or f.__name__.lower(), + callback=f, params=params, **attrs) + + +def command(name=None, cls=None, **attrs): + """Creates a new :class:`Command` and uses the decorated function as + callback. This will also automatically attach all decorated + :func:`option`\s and :func:`argument`\s as parameters to the command. + + The name of the command defaults to the name of the function. If you + want to change that, you can pass the intended name as the first + argument. + + All keyword arguments are forwarded to the underlying command class. + + Once decorated the function turns into a :class:`Command` instance + that can be invoked as a command line utility or be attached to a + command :class:`Group`. + + :param name: the name of the command. This defaults to the function + name. + :param cls: the command class to instantiate. This defaults to + :class:`Command`. + """ + if cls is None: + cls = Command + def decorator(f): + cmd = _make_command(f, name, attrs, cls) + cmd.__doc__ = f.__doc__ + return cmd + return decorator + + +def group(name=None, **attrs): + """Creates a new :class:`Group` with a function as callback. This + works otherwise the same as :func:`command` just that the `cls` + parameter is set to :class:`Group`. + """ + attrs.setdefault('cls', Group) + return command(name, **attrs) + + +def _param_memo(f, param): + if isinstance(f, Command): + f.params.append(param) + else: + if not hasattr(f, '__click_params__'): + f.__click_params__ = [] + f.__click_params__.append(param) + + +def argument(*param_decls, **attrs): + """Attaches an argument to the command. All positional arguments are + passed as parameter declarations to :class:`Argument`; all keyword + arguments are forwarded unchanged (except ``cls``). + This is equivalent to creating an :class:`Argument` instance manually + and attaching it to the :attr:`Command.params` list. + + :param cls: the argument class to instantiate. This defaults to + :class:`Argument`. + """ + def decorator(f): + ArgumentClass = attrs.pop('cls', Argument) + _param_memo(f, ArgumentClass(param_decls, **attrs)) + return f + return decorator + + +def option(*param_decls, **attrs): + """Attaches an option to the command. All positional arguments are + passed as parameter declarations to :class:`Option`; all keyword + arguments are forwarded unchanged (except ``cls``). + This is equivalent to creating an :class:`Option` instance manually + and attaching it to the :attr:`Command.params` list. + + :param cls: the option class to instantiate. This defaults to + :class:`Option`. + """ + def decorator(f): + if 'help' in attrs: + attrs['help'] = inspect.cleandoc(attrs['help']) + OptionClass = attrs.pop('cls', Option) + _param_memo(f, OptionClass(param_decls, **attrs)) + return f + return decorator + + +def confirmation_option(*param_decls, **attrs): + """Shortcut for confirmation prompts that can be ignored by passing + ``--yes`` as parameter. + + This is equivalent to decorating a function with :func:`option` with + the following parameters:: + + def callback(ctx, param, value): + if not value: + ctx.abort() + + @click.command() + @click.option('--yes', is_flag=True, callback=callback, + expose_value=False, prompt='Do you want to continue?') + def dropdb(): + pass + """ + def decorator(f): + def callback(ctx, param, value): + if not value: + ctx.abort() + attrs.setdefault('is_flag', True) + attrs.setdefault('callback', callback) + attrs.setdefault('expose_value', False) + attrs.setdefault('prompt', 'Do you want to continue?') + attrs.setdefault('help', 'Confirm the action without prompting.') + return option(*(param_decls or ('--yes',)), **attrs)(f) + return decorator + + +def password_option(*param_decls, **attrs): + """Shortcut for password prompts. + + This is equivalent to decorating a function with :func:`option` with + the following parameters:: + + @click.command() + @click.option('--password', prompt=True, confirmation_prompt=True, + hide_input=True) + def changeadmin(password): + pass + """ + def decorator(f): + attrs.setdefault('prompt', True) + attrs.setdefault('confirmation_prompt', True) + attrs.setdefault('hide_input', True) + return option(*(param_decls or ('--password',)), **attrs)(f) + return decorator + + +def version_option(version=None, *param_decls, **attrs): + """Adds a ``--version`` option which immediately ends the program + printing out the version number. This is implemented as an eager + option that prints the version and exits the program in the callback. + + :param version: the version number to show. If not provided Click + attempts an auto discovery via setuptools. + :param prog_name: the name of the program (defaults to autodetection) + :param message: custom message to show instead of the default + (``'%(prog)s, version %(version)s'``) + :param others: everything else is forwarded to :func:`option`. + """ + if version is None: + module = sys._getframe(1).f_globals.get('__name__') + def decorator(f): + prog_name = attrs.pop('prog_name', None) + message = attrs.pop('message', '%(prog)s, version %(version)s') + + def callback(ctx, param, value): + if not value or ctx.resilient_parsing: + return + prog = prog_name + if prog is None: + prog = ctx.find_root().info_name + ver = version + if ver is None: + try: + import pkg_resources + except ImportError: + pass + else: + for dist in pkg_resources.working_set: + scripts = dist.get_entry_map().get('console_scripts') or {} + for script_name, entry_point in iteritems(scripts): + if entry_point.module_name == module: + ver = dist.version + break + if ver is None: + raise RuntimeError('Could not determine version') + echo(message % { + 'prog': prog, + 'version': ver, + }, color=ctx.color) + ctx.exit() + + attrs.setdefault('is_flag', True) + attrs.setdefault('expose_value', False) + attrs.setdefault('is_eager', True) + attrs.setdefault('help', 'Show the version and exit.') + attrs['callback'] = callback + return option(*(param_decls or ('--version',)), **attrs)(f) + return decorator + + +def help_option(*param_decls, **attrs): + """Adds a ``--help`` option which immediately ends the program + printing out the help page. This is usually unnecessary to add as + this is added by default to all commands unless suppressed. + + Like :func:`version_option`, this is implemented as eager option that + prints in the callback and exits. + + All arguments are forwarded to :func:`option`. + """ + def decorator(f): + def callback(ctx, param, value): + if value and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + attrs.setdefault('is_flag', True) + attrs.setdefault('expose_value', False) + attrs.setdefault('help', 'Show this message and exit.') + attrs.setdefault('is_eager', True) + attrs['callback'] = callback + return option(*(param_decls or ('--help',)), **attrs)(f) + return decorator + + +# Circular dependencies between core and decorators +from .core import Command, Group, Argument, Option diff -Nru click-0.4.43+16.04.20160203/click/exceptions.py click-6.7/click/exceptions.py --- click-0.4.43+16.04.20160203/click/exceptions.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/click/exceptions.py 2017-01-06 22:32:37.000000000 +0000 @@ -0,0 +1,201 @@ +from ._compat import PY2, filename_to_ui, get_text_stderr +from .utils import echo + + +class ClickException(Exception): + """An exception that Click can handle and show to the user.""" + + #: The exit code for this exception + exit_code = 1 + + def __init__(self, message): + if PY2: + if message is not None: + message = message.encode('utf-8') + Exception.__init__(self, message) + self.message = message + + def format_message(self): + return self.message + + def show(self, file=None): + if file is None: + file = get_text_stderr() + echo('Error: %s' % self.format_message(), file=file) + + +class UsageError(ClickException): + """An internal exception that signals a usage error. This typically + aborts any further handling. + + :param message: the error message to display. + :param ctx: optionally the context that caused this error. Click will + fill in the context automatically in some situations. + """ + exit_code = 2 + + def __init__(self, message, ctx=None): + ClickException.__init__(self, message) + self.ctx = ctx + + def show(self, file=None): + if file is None: + file = get_text_stderr() + color = None + if self.ctx is not None: + color = self.ctx.color + echo(self.ctx.get_usage() + '\n', file=file, color=color) + echo('Error: %s' % self.format_message(), file=file, color=color) + + +class BadParameter(UsageError): + """An exception that formats out a standardized error message for a + bad parameter. This is useful when thrown from a callback or type as + Click will attach contextual information to it (for instance, which + parameter it is). + + .. versionadded:: 2.0 + + :param param: the parameter object that caused this error. This can + be left out, and Click will attach this info itself + if possible. + :param param_hint: a string that shows up as parameter name. This + can be used as alternative to `param` in cases + where custom validation should happen. If it is + a string it's used as such, if it's a list then + each item is quoted and separated. + """ + + def __init__(self, message, ctx=None, param=None, + param_hint=None): + UsageError.__init__(self, message, ctx) + self.param = param + self.param_hint = param_hint + + def format_message(self): + if self.param_hint is not None: + param_hint = self.param_hint + elif self.param is not None: + param_hint = self.param.opts or [self.param.human_readable_name] + else: + return 'Invalid value: %s' % self.message + if isinstance(param_hint, (tuple, list)): + param_hint = ' / '.join('"%s"' % x for x in param_hint) + return 'Invalid value for %s: %s' % (param_hint, self.message) + + +class MissingParameter(BadParameter): + """Raised if click required an option or argument but it was not + provided when invoking the script. + + .. versionadded:: 4.0 + + :param param_type: a string that indicates the type of the parameter. + The default is to inherit the parameter type from + the given `param`. Valid values are ``'parameter'``, + ``'option'`` or ``'argument'``. + """ + + def __init__(self, message=None, ctx=None, param=None, + param_hint=None, param_type=None): + BadParameter.__init__(self, message, ctx, param, param_hint) + self.param_type = param_type + + def format_message(self): + if self.param_hint is not None: + param_hint = self.param_hint + elif self.param is not None: + param_hint = self.param.opts or [self.param.human_readable_name] + else: + param_hint = None + if isinstance(param_hint, (tuple, list)): + param_hint = ' / '.join('"%s"' % x for x in param_hint) + + param_type = self.param_type + if param_type is None and self.param is not None: + param_type = self.param.param_type_name + + msg = self.message + if self.param is not None: + msg_extra = self.param.type.get_missing_message(self.param) + if msg_extra: + if msg: + msg += '. ' + msg_extra + else: + msg = msg_extra + + return 'Missing %s%s%s%s' % ( + param_type, + param_hint and ' %s' % param_hint or '', + msg and '. ' or '.', + msg or '', + ) + + +class NoSuchOption(UsageError): + """Raised if click attempted to handle an option that does not + exist. + + .. versionadded:: 4.0 + """ + + def __init__(self, option_name, message=None, possibilities=None, + ctx=None): + if message is None: + message = 'no such option: %s' % option_name + UsageError.__init__(self, message, ctx) + self.option_name = option_name + self.possibilities = possibilities + + def format_message(self): + bits = [self.message] + if self.possibilities: + if len(self.possibilities) == 1: + bits.append('Did you mean %s?' % self.possibilities[0]) + else: + possibilities = sorted(self.possibilities) + bits.append('(Possible options: %s)' % ', '.join(possibilities)) + return ' '.join(bits) + + +class BadOptionUsage(UsageError): + """Raised if an option is generally supplied but the use of the option + was incorrect. This is for instance raised if the number of arguments + for an option is not correct. + + .. versionadded:: 4.0 + """ + + def __init__(self, message, ctx=None): + UsageError.__init__(self, message, ctx) + + +class BadArgumentUsage(UsageError): + """Raised if an argument is generally supplied but the use of the argument + was incorrect. This is for instance raised if the number of values + for an argument is not correct. + + .. versionadded:: 6.0 + """ + + def __init__(self, message, ctx=None): + UsageError.__init__(self, message, ctx) + + +class FileError(ClickException): + """Raised if a file cannot be opened.""" + + def __init__(self, filename, hint=None): + ui_filename = filename_to_ui(filename) + if hint is None: + hint = 'unknown error' + ClickException.__init__(self, hint) + self.ui_filename = ui_filename + self.filename = filename + + def format_message(self): + return 'Could not open file %s: %s' % (self.ui_filename, self.message) + + +class Abort(RuntimeError): + """An internal signalling exception that signals Click to abort.""" diff -Nru click-0.4.43+16.04.20160203/click/formatting.py click-6.7/click/formatting.py --- click-0.4.43+16.04.20160203/click/formatting.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/click/formatting.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,256 @@ +from contextlib import contextmanager +from .termui import get_terminal_size +from .parser import split_opt +from ._compat import term_len + + +# Can force a width. This is used by the test system +FORCED_WIDTH = None + + +def measure_table(rows): + widths = {} + for row in rows: + for idx, col in enumerate(row): + widths[idx] = max(widths.get(idx, 0), term_len(col)) + return tuple(y for x, y in sorted(widths.items())) + + +def iter_rows(rows, col_count): + for row in rows: + row = tuple(row) + yield row + ('',) * (col_count - len(row)) + + +def wrap_text(text, width=78, initial_indent='', subsequent_indent='', + preserve_paragraphs=False): + """A helper function that intelligently wraps text. By default, it + assumes that it operates on a single paragraph of text but if the + `preserve_paragraphs` parameter is provided it will intelligently + handle paragraphs (defined by two empty lines). + + If paragraphs are handled, a paragraph can be prefixed with an empty + line containing the ``\\b`` character (``\\x08``) to indicate that + no rewrapping should happen in that block. + + :param text: the text that should be rewrapped. + :param width: the maximum width for the text. + :param initial_indent: the initial indent that should be placed on the + first line as a string. + :param subsequent_indent: the indent string that should be placed on + each consecutive line. + :param preserve_paragraphs: if this flag is set then the wrapping will + intelligently handle paragraphs. + """ + from ._textwrap import TextWrapper + text = text.expandtabs() + wrapper = TextWrapper(width, initial_indent=initial_indent, + subsequent_indent=subsequent_indent, + replace_whitespace=False) + if not preserve_paragraphs: + return wrapper.fill(text) + + p = [] + buf = [] + indent = None + + def _flush_par(): + if not buf: + return + if buf[0].strip() == '\b': + p.append((indent or 0, True, '\n'.join(buf[1:]))) + else: + p.append((indent or 0, False, ' '.join(buf))) + del buf[:] + + for line in text.splitlines(): + if not line: + _flush_par() + indent = None + else: + if indent is None: + orig_len = term_len(line) + line = line.lstrip() + indent = orig_len - term_len(line) + buf.append(line) + _flush_par() + + rv = [] + for indent, raw, text in p: + with wrapper.extra_indent(' ' * indent): + if raw: + rv.append(wrapper.indent_only(text)) + else: + rv.append(wrapper.fill(text)) + + return '\n\n'.join(rv) + + +class HelpFormatter(object): + """This class helps with formatting text-based help pages. It's + usually just needed for very special internal cases, but it's also + exposed so that developers can write their own fancy outputs. + + At present, it always writes into memory. + + :param indent_increment: the additional increment for each level. + :param width: the width for the text. This defaults to the terminal + width clamped to a maximum of 78. + """ + + def __init__(self, indent_increment=2, width=None, max_width=None): + self.indent_increment = indent_increment + if max_width is None: + max_width = 80 + if width is None: + width = FORCED_WIDTH + if width is None: + width = max(min(get_terminal_size()[0], max_width) - 2, 50) + self.width = width + self.current_indent = 0 + self.buffer = [] + + def write(self, string): + """Writes a unicode string into the internal buffer.""" + self.buffer.append(string) + + def indent(self): + """Increases the indentation.""" + self.current_indent += self.indent_increment + + def dedent(self): + """Decreases the indentation.""" + self.current_indent -= self.indent_increment + + def write_usage(self, prog, args='', prefix='Usage: '): + """Writes a usage line into the buffer. + + :param prog: the program name. + :param args: whitespace separated list of arguments. + :param prefix: the prefix for the first line. + """ + usage_prefix = '%*s%s ' % (self.current_indent, prefix, prog) + text_width = self.width - self.current_indent + + if text_width >= (term_len(usage_prefix) + 20): + # The arguments will fit to the right of the prefix. + indent = ' ' * term_len(usage_prefix) + self.write(wrap_text(args, text_width, + initial_indent=usage_prefix, + subsequent_indent=indent)) + else: + # The prefix is too long, put the arguments on the next line. + self.write(usage_prefix) + self.write('\n') + indent = ' ' * (max(self.current_indent, term_len(prefix)) + 4) + self.write(wrap_text(args, text_width, + initial_indent=indent, + subsequent_indent=indent)) + + self.write('\n') + + def write_heading(self, heading): + """Writes a heading into the buffer.""" + self.write('%*s%s:\n' % (self.current_indent, '', heading)) + + def write_paragraph(self): + """Writes a paragraph into the buffer.""" + if self.buffer: + self.write('\n') + + def write_text(self, text): + """Writes re-indented text into the buffer. This rewraps and + preserves paragraphs. + """ + text_width = max(self.width - self.current_indent, 11) + indent = ' ' * self.current_indent + self.write(wrap_text(text, text_width, + initial_indent=indent, + subsequent_indent=indent, + preserve_paragraphs=True)) + self.write('\n') + + def write_dl(self, rows, col_max=30, col_spacing=2): + """Writes a definition list into the buffer. This is how options + and commands are usually formatted. + + :param rows: a list of two item tuples for the terms and values. + :param col_max: the maximum width of the first column. + :param col_spacing: the number of spaces between the first and + second column. + """ + rows = list(rows) + widths = measure_table(rows) + if len(widths) != 2: + raise TypeError('Expected two columns for definition list') + + first_col = min(widths[0], col_max) + col_spacing + + for first, second in iter_rows(rows, len(widths)): + self.write('%*s%s' % (self.current_indent, '', first)) + if not second: + self.write('\n') + continue + if term_len(first) <= first_col - col_spacing: + self.write(' ' * (first_col - term_len(first))) + else: + self.write('\n') + self.write(' ' * (first_col + self.current_indent)) + + text_width = max(self.width - first_col - 2, 10) + lines = iter(wrap_text(second, text_width).splitlines()) + if lines: + self.write(next(lines) + '\n') + for line in lines: + self.write('%*s%s\n' % ( + first_col + self.current_indent, '', line)) + else: + self.write('\n') + + @contextmanager + def section(self, name): + """Helpful context manager that writes a paragraph, a heading, + and the indents. + + :param name: the section name that is written as heading. + """ + self.write_paragraph() + self.write_heading(name) + self.indent() + try: + yield + finally: + self.dedent() + + @contextmanager + def indentation(self): + """A context manager that increases the indentation.""" + self.indent() + try: + yield + finally: + self.dedent() + + def getvalue(self): + """Returns the buffer contents.""" + return ''.join(self.buffer) + + +def join_options(options): + """Given a list of option strings this joins them in the most appropriate + way and returns them in the form ``(formatted_string, + any_prefix_is_slash)`` where the second item in the tuple is a flag that + indicates if any of the option prefixes was a slash. + """ + rv = [] + any_prefix_is_slash = False + for opt in options: + prefix = split_opt(opt)[0] + if prefix == '/': + any_prefix_is_slash = True + rv.append((len(prefix), opt)) + + rv.sort(key=lambda x: x[0]) + + rv = ', '.join(x[1] for x in rv) + return rv, any_prefix_is_slash diff -Nru click-0.4.43+16.04.20160203/click/framework.py click-6.7/click/framework.py --- click-0.4.43+16.04.20160203/click/framework.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/framework.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,145 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Pure python click framework handling support.""" - -import logging -import os -import re - -try: - import apt_pkg -except: - pass - -import click.paths - - -class ClickFrameworkInvalid(Exception): - pass - - -# python version of the vala parse_deb822_file() -def parse_deb822_file(filename): - data = {} - with open(filename) as f: - for line in f: - line = line.strip() - # from deb822.vala - field_re_posix = ( - r'^([^:[:space:]]+)[[:space:]]*:[[:space:]]' - '([^[:space:]].*?)[[:space:]]*$') - # python does not do posix char classes - field_re = field_re_posix.replace("[:space:]", "\s") - blank_re_posix = r'^[[:space:]]*$' - blank_re = blank_re_posix.replace("[:space:]", "\s") - if re.match(blank_re, line): - break - match = re.match(field_re, line) - if match and match.group(1) and match.group(2): - data[match.group(1).lower()] = match.group(2) - return data - - -# python version of vala get_frameworks_dir -def get_frameworks_dir(): - return click.paths.frameworks_dir - - -def get_framework_path(framework_name): - framework_path = os.path.join( - get_frameworks_dir(), framework_name+".framework") - return framework_path - - -# python version of the vala click_framework_get_base_version() -def click_framework_get_base_version(framework_name): - deb822 = parse_deb822_file(get_framework_path(framework_name)) - return deb822.get("base-version", None) - - -# python version of the vala click_framework_get_base_version() -def click_framework_get_base_name(framework_name): - deb822 = parse_deb822_file(get_framework_path(framework_name)) - return deb822.get("base-name", None) - - -# python version of the vala click_framework_has_framework -def click_framework_has_framework(framework_name): - return os.path.exists(get_framework_path(framework_name)) - - -def validate_framework(framework_string, ignore_missing_frameworks=False): - try: - apt_pkg - except NameError: - logging.warning("No apt_pkg module, skipping validate_framework") - return - - try: - parsed_framework = apt_pkg.parse_depends(framework_string) - except ValueError: - raise ClickFrameworkInvalid( - 'Could not parse framework "%s"' % framework_string) - - base_name_versions = {} - missing_frameworks = [] - for or_dep in parsed_framework: - if len(or_dep) > 1: - raise ClickFrameworkInvalid( - 'Alternative dependencies in framework "%s" not yet ' - 'allowed' % framework_string) - if or_dep[0][1] or or_dep[0][2]: - raise ClickFrameworkInvalid( - 'Version relationship in framework "%s" not yet allowed' % - framework_string) - # now verify that different base versions are not mixed - framework_name = or_dep[0][0] - if not click_framework_has_framework(framework_name): - missing_frameworks.append(framework_name) - continue - # ensure we do not use different base versions for the same base-name - framework_base_name = click_framework_get_base_name( - framework_name) - framework_base_version = click_framework_get_base_version( - framework_name) - prev = base_name_versions.get(framework_base_name, None) - if prev and prev != framework_base_version: - raise ClickFrameworkInvalid( - 'Multiple frameworks with different base versions are not ' - 'allowed. Found: {} ({} != {})'.format( - framework_base_name, - framework_base_version, - base_name_versions[framework_base_name])) - base_name_versions[framework_base_name] = framework_base_version - - if not ignore_missing_frameworks: - if len(missing_frameworks) > 1: - raise ClickFrameworkInvalid( - 'Frameworks %s not present on system (use ' - '--force-missing-framework option to override)' % - ", ".join('"%s"' % f for f in missing_frameworks)) - elif missing_frameworks: - raise ClickFrameworkInvalid( - 'Framework "%s" not present on system (use ' - '--force-missing-framework option to override)' % - missing_frameworks[0]) - else: - if len(missing_frameworks) > 1: - logging.warning("Ignoring missing frameworks %s" % ( - ", ".join('"%s"' % f for f in missing_frameworks))) - elif missing_frameworks: - logging.warning('Ignoring missing framework "%s"' % ( - missing_frameworks[0])) diff -Nru click-0.4.43+16.04.20160203/click/globals.py click-6.7/click/globals.py --- click-0.4.43+16.04.20160203/click/globals.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/click/globals.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,48 @@ +from threading import local + + +_local = local() + + +def get_current_context(silent=False): + """Returns the current click context. This can be used as a way to + access the current context object from anywhere. This is a more implicit + alternative to the :func:`pass_context` decorator. This function is + primarily useful for helpers such as :func:`echo` which might be + interested in changing it's behavior based on the current context. + + To push the current context, :meth:`Context.scope` can be used. + + .. versionadded:: 5.0 + + :param silent: is set to `True` the return value is `None` if no context + is available. The default behavior is to raise a + :exc:`RuntimeError`. + """ + try: + return getattr(_local, 'stack')[-1] + except (AttributeError, IndexError): + if not silent: + raise RuntimeError('There is no active click context.') + + +def push_context(ctx): + """Pushes a new context to the current stack.""" + _local.__dict__.setdefault('stack', []).append(ctx) + + +def pop_context(): + """Removes the top level from the stack.""" + _local.stack.pop() + + +def resolve_color_default(color=None): + """"Internal helper to get the default value of the color flag. If a + value is passed it's returned unchanged, otherwise it's looked up from + the current context. + """ + if color is not None: + return color + ctx = get_current_context(silent=True) + if ctx is not None: + return ctx.color diff -Nru click-0.4.43+16.04.20160203/click/__init__.py click-6.7/click/__init__.py --- click-0.4.43+16.04.20160203/click/__init__.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/__init__.py 2017-01-06 22:40:55.000000000 +0000 @@ -1,3 +1,98 @@ -# Marker to help resolve unfortunate name clash between this package and -# https://pypi.python.org/pypi/click. -_CLICK_IS_A_PACKAGING_FORMAT_ = 1 +# -*- coding: utf-8 -*- +""" + click + ~~~~~ + + Click is a simple Python module that wraps the stdlib's optparse to make + writing command line scripts fun. Unlike other modules, it's based around + a simple API that does not come with too much magic and is composable. + + In case optparse ever gets removed from the stdlib, it will be shipped by + this module. + + :copyright: (c) 2014 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + +# Core classes +from .core import Context, BaseCommand, Command, MultiCommand, Group, \ + CommandCollection, Parameter, Option, Argument + +# Globals +from .globals import get_current_context + +# Decorators +from .decorators import pass_context, pass_obj, make_pass_decorator, \ + command, group, argument, option, confirmation_option, \ + password_option, version_option, help_option + +# Types +from .types import ParamType, File, Path, Choice, IntRange, Tuple, \ + STRING, INT, FLOAT, BOOL, UUID, UNPROCESSED + +# Utilities +from .utils import echo, get_binary_stream, get_text_stream, open_file, \ + format_filename, get_app_dir, get_os_args + +# Terminal functions +from .termui import prompt, confirm, get_terminal_size, echo_via_pager, \ + progressbar, clear, style, unstyle, secho, edit, launch, getchar, \ + pause + +# Exceptions +from .exceptions import ClickException, UsageError, BadParameter, \ + FileError, Abort, NoSuchOption, BadOptionUsage, BadArgumentUsage, \ + MissingParameter + +# Formatting +from .formatting import HelpFormatter, wrap_text + +# Parsing +from .parser import OptionParser + + +__all__ = [ + # Core classes + 'Context', 'BaseCommand', 'Command', 'MultiCommand', 'Group', + 'CommandCollection', 'Parameter', 'Option', 'Argument', + + # Globals + 'get_current_context', + + # Decorators + 'pass_context', 'pass_obj', 'make_pass_decorator', 'command', 'group', + 'argument', 'option', 'confirmation_option', 'password_option', + 'version_option', 'help_option', + + # Types + 'ParamType', 'File', 'Path', 'Choice', 'IntRange', 'Tuple', 'STRING', + 'INT', 'FLOAT', 'BOOL', 'UUID', 'UNPROCESSED', + + # Utilities + 'echo', 'get_binary_stream', 'get_text_stream', 'open_file', + 'format_filename', 'get_app_dir', 'get_os_args', + + # Terminal functions + 'prompt', 'confirm', 'get_terminal_size', 'echo_via_pager', + 'progressbar', 'clear', 'style', 'unstyle', 'secho', 'edit', 'launch', + 'getchar', 'pause', + + # Exceptions + 'ClickException', 'UsageError', 'BadParameter', 'FileError', + 'Abort', 'NoSuchOption', 'BadOptionUsage', 'BadArgumentUsage', + 'MissingParameter', + + # Formatting + 'HelpFormatter', 'wrap_text', + + # Parsing + 'OptionParser', +] + + +# Controls if click should emit the warning about the use of unicode +# literals. +disable_unicode_literals_warning = False + + +__version__ = '6.7' diff -Nru click-0.4.43+16.04.20160203/click/install.py click-6.7/click/install.py --- click-0.4.43+16.04.20160203/click/install.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/install.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,479 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Installing Click packages.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'ClickInstaller', - 'ClickInstallerAuditError', - 'ClickInstallerError', - 'ClickInstallerPermissionDenied', - ] - - -from functools import partial -import grp -import inspect -import json -import logging -import os -import pwd -import shutil -import stat -import subprocess -import sys -import tempfile -from textwrap import dedent - -from contextlib import closing - -from debian.debfile import DebFile as _DebFile -from debian.debian_support import Version -from gi.repository import Click - -from click.paths import preload_path -from click.preinst import static_preinst_matches -from click.versions import spec_version - -from click.framework import ( - validate_framework, - ClickFrameworkInvalid, -) - - -try: - _DebFile.close - DebFile = _DebFile -except AttributeError: - # Yay! The Ubuntu 13.04 version of python-debian 0.1.21 - # debian.debfile.DebFile has a .close() method but the PyPI version of - # 0.1.21 does not. It's worse than that because DebFile.close() really - # delegates to DebPart.close() and *that's* missing in the PyPI version. - # To get that working, we have to reach inside the object and name mangle - # the attribute. - class DebFile(_DebFile): - def close(self): - self.control._DebPart__member.close() - self.data._DebPart__member.close() - - -class DebsigVerifyError(Exception): - pass - - -class DebsigVerify: - """Tiny wrapper around the debsig-verify commandline""" - # from debsig-verify-0.9/debsigs.h - DS_SUCCESS = 0 - DS_FAIL_NOSIGS = 10 - DS_FAIL_UNKNOWN_ORIGIN = 11 - DS_FAIL_NOPOLICIES = 12 - DS_FAIL_BADSIG = 13 - DS_FAIL_INTERNAL = 14 - - # should be a property, but python does not support support - # class properties easily - @classmethod - def available(cls): - return Click.find_on_path("debsig-verify") - - @classmethod - def verify(cls, path, allow_unauthenticated): - command = ["debsig-verify"] + [path] - try: - subprocess.check_output(command, universal_newlines=True) - except subprocess.CalledProcessError as e: - if (allow_unauthenticated and - e.returncode in (DebsigVerify.DS_FAIL_NOSIGS, - DebsigVerify.DS_FAIL_UNKNOWN_ORIGIN, - DebsigVerify.DS_FAIL_NOPOLICIES)): - logging.warning( - "Signature check failed, but installing anyway " - "as requested") - else: - raise DebsigVerifyError( - "Signature verification error: %s" % e.output) - return True - - -class ClickInstallerError(Exception): - pass - - -class ClickInstallerPermissionDenied(ClickInstallerError): - pass - - -class ClickInstallerAuditError(ClickInstallerError): - pass - - -class ClickInstaller: - def __init__(self, db, force_missing_framework=False, - allow_unauthenticated=False): - self.db = db - self.force_missing_framework = force_missing_framework - self.allow_unauthenticated = allow_unauthenticated - - def _preload_path(self): - if "CLICK_PACKAGE_PRELOAD" in os.environ: - return os.environ["CLICK_PACKAGE_PRELOAD"] - my_path = inspect.getsourcefile(ClickInstaller) - preload = os.path.join( - os.path.dirname(my_path), os.pardir, "preload", ".libs", - "libclickpreload.so") - if os.path.exists(preload): - return os.path.abspath(preload) - return preload_path - - def _dpkg_architecture(self): - return subprocess.check_output( - ["dpkg", "--print-architecture"], - universal_newlines=True).rstrip("\n") - - def extract(self, path, target): - command = ["dpkg-deb", "-R", path, target] - with open(path, "rb") as fd: - env = dict(os.environ) - preloads = [self._preload_path()] - if "LD_PRELOAD" in env: - preloads.append(env["LD_PRELOAD"]) - env["LD_PRELOAD"] = " ".join(preloads) - env["CLICK_BASE_DIR"] = target - env["CLICK_PACKAGE_PATH"] = path - env["CLICK_PACKAGE_FD"] = str(fd.fileno()) - env.pop("HOME", None) - kwargs = {} - if sys.version >= "3.2": - kwargs["pass_fds"] = (fd.fileno(),) - subprocess.check_call(command, env=env, **kwargs) - - def audit(self, path, slow=False, check_arch=False): - # always do the signature check first - if DebsigVerify.available(): - try: - DebsigVerify.verify(path, self.allow_unauthenticated) - except DebsigVerifyError as e: - raise ClickInstallerAuditError(str(e)) - else: - logging.warning( - "debsig-verify not available; cannot check signatures") - - # fail early if the file cannot be opened - try: - with closing(DebFile(filename=path)) as package: - pass - except Exception as e: - raise ClickInstallerError("Failed to read %s: %s" % ( - path, str(e))) - - # then perform the audit - with closing(DebFile(filename=path)) as package: - control_fields = package.control.debcontrol() - - try: - click_version = Version(control_fields["Click-Version"]) - except KeyError: - raise ClickInstallerAuditError("No Click-Version field") - if click_version > spec_version: - raise ClickInstallerAuditError( - "Click-Version: %s newer than maximum supported version " - "%s" % (click_version, spec_version)) - - for field in ( - "Pre-Depends", "Depends", "Recommends", "Suggests", "Enhances", - "Conflicts", "Breaks", - "Provides", - ): - if field in control_fields: - raise ClickInstallerAuditError( - "%s field is forbidden in Click packages" % field) - - scripts = package.control.scripts() - if ("preinst" in scripts and - static_preinst_matches(scripts["preinst"])): - scripts.pop("preinst", None) - if scripts: - raise ClickInstallerAuditError( - "Maintainer scripts are forbidden in Click packages " - "(found: %s)" % - " ".join(sorted(scripts))) - - if not package.control.has_file("manifest"): - raise ClickInstallerAuditError("Package has no manifest") - with package.control.get_file("manifest", encoding="UTF-8") as f: - manifest = json.load(f) - - try: - package_name = manifest["name"] - except KeyError: - raise ClickInstallerAuditError('No "name" entry in manifest') - # TODO: perhaps just do full name validation? - if "/" in package_name: - raise ClickInstallerAuditError( - 'Invalid character "/" in "name" entry: %s' % package_name) - if "_" in package_name: - raise ClickInstallerAuditError( - 'Invalid character "_" in "name" entry: %s' % package_name) - - try: - package_version = manifest["version"] - except KeyError: - raise ClickInstallerAuditError( - 'No "version" entry in manifest') - # TODO: perhaps just do full version validation? - if "/" in package_version: - raise ClickInstallerAuditError( - 'Invalid character "/" in "version" entry: %s' % - package_version) - if "_" in package_version: - raise ClickInstallerAuditError( - 'Invalid character "_" in "version" entry: %s' % - package_version) - - try: - framework = manifest["framework"] - except KeyError: - raise ClickInstallerAuditError( - 'No "framework" entry in manifest') - try: - validate_framework(framework, self.force_missing_framework) - except ClickFrameworkInvalid as e: - raise ClickInstallerAuditError(str(e)) - - if check_arch: - architecture = manifest.get("architecture", "all") - if architecture != "all": - dpkg_architecture = self._dpkg_architecture() - if isinstance(architecture, list): - if dpkg_architecture not in architecture: - raise ClickInstallerAuditError( - 'Package architectures "%s" not compatible ' - 'with system architecture "%s"' % - (" ".join(architecture), dpkg_architecture)) - elif architecture != dpkg_architecture: - raise ClickInstallerAuditError( - 'Package architecture "%s" not compatible ' - 'with system architecture "%s"' % - (architecture, dpkg_architecture)) - - # This isn't ideally quick, since it has to decompress the data - # part of the package, but dpkg's path filtering code assumes - # that all paths start with "./" so we must check it before - # passing the package to dpkg. - for data_name in package.data: - if data_name != "." and not data_name.startswith("./"): - raise ClickInstallerAuditError( - 'File name "%s" in package does not start with "./"' % - data_name) - - if slow: - temp_dir = tempfile.mkdtemp(prefix="click") - try: - self.extract(path, temp_dir) - command = [ - "md5sum", "-c", "--quiet", - os.path.join("DEBIAN", "md5sums"), - ] - subprocess.check_call(command, cwd=temp_dir) - finally: - shutil.rmtree(temp_dir) - - return package_name, package_version - - def _drop_privileges(self, username): - if os.geteuid() != 0: - return - pw = pwd.getpwnam(username) - os.setgroups( - [g.gr_gid for g in grp.getgrall() if username in g.gr_mem]) - # Portability note: this assumes that we have [gs]etres[gu]id, which - # is true on Linux but not necessarily elsewhere. If you need to - # support something else, there are reasonably standard alternatives - # involving other similar calls; see e.g. gnulib/lib/idpriv-drop.c. - os.setresgid(pw.pw_gid, pw.pw_gid, pw.pw_gid) - os.setresuid(pw.pw_uid, pw.pw_uid, pw.pw_uid) - assert os.getresuid() == (pw.pw_uid, pw.pw_uid, pw.pw_uid) - assert os.getresgid() == (pw.pw_gid, pw.pw_gid, pw.pw_gid) - os.umask(0o022) - - def _euid_access(self, username, path, mode): - """Like os.access, but for the effective UID.""" - # TODO: Dropping privileges and calling - # os.access(effective_ids=True) ought to work, but for some reason - # appears not to return False when it should. It seems that we need - # a subprocess to check this reliably. At least we don't have to - # exec anything. - pid = os.fork() - if pid == 0: # child - self._drop_privileges(username) - os._exit(0 if os.access(path, mode) else 1) - else: # parent - _, status = os.waitpid(pid, 0) - return status == 0 - - def _check_write_permissions(self, path): - while True: - if os.path.exists(path): - break - path = os.path.dirname(path) - if path == "/": - break - if not self._euid_access("clickpkg", path, os.W_OK): - raise ClickInstallerPermissionDenied( - 'Cannot acquire permission to write to %s; either run as root ' - 'with --user, or use "pkcon install-local" instead' % path) - - def _install_preexec(self, inst_dir): - self._drop_privileges("clickpkg") - - admin_dir = os.path.join(inst_dir, ".click") - if not os.path.exists(admin_dir): - os.makedirs(admin_dir) - with open(os.path.join(admin_dir, "available"), "w"): - pass - with open(os.path.join(admin_dir, "status"), "w"): - pass - os.mkdir(os.path.join(admin_dir, "info")) - os.mkdir(os.path.join(admin_dir, "updates")) - os.mkdir(os.path.join(admin_dir, "triggers")) - - def _unpack(self, path, user=None, all_users=False, quiet=True): - package_name, package_version = self.audit(path, check_arch=True) - - # Is this package already unpacked in an underlay (non-topmost) - # database? - if self.db.has_package_version(package_name, package_version): - overlay = self.db.get(self.db.props.size - 1) - if not overlay.has_package_version(package_name, package_version): - return package_name, package_version, None - - package_dir = os.path.join(self.db.props.overlay, package_name) - inst_dir = os.path.join(package_dir, package_version) - assert ( - os.path.dirname(os.path.dirname(inst_dir)) == - self.db.props.overlay) - - self._check_write_permissions(self.db.props.overlay) - root_click = os.path.join(self.db.props.overlay, ".click") - if not os.path.exists(root_click): - os.makedirs(root_click) - if os.geteuid() == 0: - pw = pwd.getpwnam("clickpkg") - os.chown(root_click, pw.pw_uid, pw.pw_gid) - - # TODO: sandbox so that this can only write to the unpack directory - command = [ - "dpkg", - # We normally run dpkg as non-root. - "--force-not-root", - # /sbin and /usr/sbin may not necessarily be on $PATH; we don't - # use the tools dpkg gets from there. - "--force-bad-path", - # We check the package architecture ourselves in audit(). - "--force-architecture", - "--instdir", inst_dir, - "--admindir", os.path.join(inst_dir, ".click"), - "--path-exclude", "*/.click/*", - "--log", os.path.join(root_click, "log"), - "--no-triggers", - "--install", path, - ] - with open(path, "rb") as fd: - env = dict(os.environ) - preloads = [self._preload_path()] - if "LD_PRELOAD" in env: - preloads.append(env["LD_PRELOAD"]) - env["LD_PRELOAD"] = " ".join(preloads) - env["CLICK_BASE_DIR"] = self.db.props.overlay - env["CLICK_PACKAGE_PATH"] = path - env["CLICK_PACKAGE_FD"] = str(fd.fileno()) - env.pop("HOME", None) - kwargs = {} - if sys.version >= "3.2": - kwargs["pass_fds"] = (fd.fileno(),) - if quiet: - fn = subprocess.check_output - kwargs["stderr"] = subprocess.STDOUT - else: - fn = subprocess.check_call - try: - fn(command, - preexec_fn=partial(self._install_preexec, inst_dir), - env=env, universal_newlines=True, - **kwargs) - except subprocess.CalledProcessError as e: - logging.error("%s failed with exit_code %s:\n%s" % ( - command, e.returncode, e.output)) - raise - for dirpath, dirnames, filenames in os.walk(inst_dir): - for entry in dirnames + filenames: - entry_path = os.path.join(dirpath, entry) - entry_mode = os.lstat(entry_path).st_mode - new_entry_mode = entry_mode | stat.S_IRGRP | stat.S_IROTH - if entry_mode & stat.S_IXUSR: - new_entry_mode |= stat.S_IXGRP | stat.S_IXOTH - if new_entry_mode != entry_mode: - try: - os.chmod(entry_path, new_entry_mode) - except OSError: - pass - - current_path = os.path.join(package_dir, "current") - - if os.path.islink(current_path): - old_version = os.readlink(current_path) - if "/" in old_version: - old_version = None - else: - old_version = None - Click.package_install_hooks( - self.db, package_name, old_version, package_version, - user_name=None) - - new_path = os.path.join(package_dir, "current.new") - Click.symlink_force(package_version, new_path) - if os.geteuid() == 0: - # shutil.chown would be more convenient, but it doesn't support - # follow_symlinks=False in Python 3.3. - # http://bugs.python.org/issue18108 - pw = pwd.getpwnam("clickpkg") - os.chown(new_path, pw.pw_uid, pw.pw_gid, follow_symlinks=False) - os.rename(new_path, current_path) - - return package_name, package_version, old_version - - def install(self, path, user=None, all_users=False, quiet=True): - package_name, package_version, old_version = self._unpack( - path, user=user, all_users=all_users, quiet=quiet) - - if user is not None or all_users: - if all_users: - registry = Click.User.for_all_users(self.db) - else: - registry = Click.User.for_user(self.db, name=user) - registry.set_version(package_name, package_version) - else: - print(dedent("""\ - %s %s has not been registered for any users. - It may be garbage-collected the next time the system starts. - To avoid this, use "click register". - """) % (package_name, package_version)) - - if old_version is not None: - self.db.maybe_remove(package_name, old_version) diff -Nru click-0.4.43+16.04.20160203/click/json_helpers.py click-6.7/click/json_helpers.py --- click-0.4.43+16.04.20160203/click/json_helpers.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/json_helpers.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,59 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Helper functions to turn json-glib objects into Python objects.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'ClickJsonError', - 'json_array_to_python', - 'json_node_to_python', - 'json_object_to_python', - ] - - -from gi.repository import Json - - -class ClickJsonError(Exception): - pass - - -def json_array_to_python(array): - return [json_node_to_python(element) for element in array.get_elements()] - - -def json_object_to_python(obj): - ret = {} - for name in obj.get_members(): - ret[name] = json_node_to_python(obj.get_member(name)) - return ret - - -def json_node_to_python(node): - node_type = node.get_node_type() - if node_type == Json.NodeType.ARRAY: - return json_array_to_python(node.get_array()) - elif node_type == Json.NodeType.OBJECT: - return json_object_to_python(node.get_object()) - elif node_type == Json.NodeType.NULL: - return None - elif node_type == Json.NodeType.VALUE: - return node.get_value() - else: - raise ClickJsonError( - "Unknown JSON node type \"%s\"" % node_type.value_nick) diff -Nru click-0.4.43+16.04.20160203/click/Makefile.am click-6.7/click/Makefile.am --- click-0.4.43+16.04.20160203/click/Makefile.am 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/Makefile.am 1970-01-01 00:00:00.000000000 +0000 @@ -1,13 +0,0 @@ -SUBDIRS = tests - -noinst_SCRIPTS = paths.py -CLEANFILES = $(noinst_SCRIPTS) - -do_subst = sed \ - -e 's,[@]sysconfdir[@],$(sysconfdir),g' \ - -e 's,[@]pkgdatadir[@],$(pkgdatadir),g' \ - -e 's,[@]pkglibdir[@],$(pkglibdir),g' \ - -e 's,[@]DEFAULT_ROOT[@],$(DEFAULT_ROOT),g' - -paths.py: paths.py.in Makefile - $(do_subst) < $(srcdir)/paths.py.in > $@ diff -Nru click-0.4.43+16.04.20160203/click/osextras.py click-6.7/click/osextras.py --- click-0.4.43+16.04.20160203/click/osextras.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/osextras.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,92 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Extra OS-level utility functions. - -Usually we can instead use the functions exported from -lib/click/osextras.vala via GObject Introspection. These pure-Python -versions are preserved so that they can be used from code that needs to be -maximally portable: for example, click.build is intended to be usable even -on systems that lack GObject, as long as they have a reasonably recent -version of Python. -""" - -__all__ = [ - 'ensuredir', - 'find_on_path', - 'unlink_force', - ] - - -import errno -import os - -try: - # Python 3.3 - from shutil import which - - def find_on_path(command): - # http://bugs.python.org/issue17012 - path = os.environ.get('PATH', os.pathsep) - return which(command, path=os.environ.get('PATH', path)) is not None -except ImportError: - # Python 2 - def find_on_path(command): - """Is command on the executable search path?""" - if 'PATH' not in os.environ: - return False - path = os.environ['PATH'] - for element in path.split(os.pathsep): - if not element: - continue - filename = os.path.join(element, command) - if os.path.isfile(filename) and os.access(filename, os.X_OK): - return True - return False - - -def ensuredir(directory): - if not os.path.isdir(directory): - os.makedirs(directory) - - -def listdir_force(directory): - try: - return os.listdir(directory) - except OSError as e: - if e.errno == errno.ENOENT: - return [] - raise - - -def unlink_force(path): - """Unlink path, without worrying about whether it exists.""" - try: - os.unlink(path) - except OSError as e: - if e.errno != errno.ENOENT: - raise - - -def symlink_force(source, link_name): - """Create symlink link_name -> source, even if link_name exists.""" - unlink_force(link_name) - os.symlink(source, link_name) - - -def get_umask(): - mask = os.umask(0) - os.umask(mask) - return mask diff -Nru click-0.4.43+16.04.20160203/click/parser.py click-6.7/click/parser.py --- click-0.4.43+16.04.20160203/click/parser.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/click/parser.py 2017-01-06 22:32:37.000000000 +0000 @@ -0,0 +1,426 @@ +# -*- coding: utf-8 -*- +""" + click.parser + ~~~~~~~~~~~~ + + This module started out as largely a copy paste from the stdlib's + optparse module with the features removed that we do not need from + optparse because we implement them in Click on a higher level (for + instance type handling, help formatting and a lot more). + + The plan is to remove more and more from here over time. + + The reason this is a different module and not optparse from the stdlib + is that there are differences in 2.x and 3.x about the error messages + generated and optparse in the stdlib uses gettext for no good reason + and might cause us issues. +""" +import re +from collections import deque +from .exceptions import UsageError, NoSuchOption, BadOptionUsage, \ + BadArgumentUsage + + +def _unpack_args(args, nargs_spec): + """Given an iterable of arguments and an iterable of nargs specifications, + it returns a tuple with all the unpacked arguments at the first index + and all remaining arguments as the second. + + The nargs specification is the number of arguments that should be consumed + or `-1` to indicate that this position should eat up all the remainders. + + Missing items are filled with `None`. + """ + args = deque(args) + nargs_spec = deque(nargs_spec) + rv = [] + spos = None + + def _fetch(c): + try: + if spos is None: + return c.popleft() + else: + return c.pop() + except IndexError: + return None + + while nargs_spec: + nargs = _fetch(nargs_spec) + if nargs == 1: + rv.append(_fetch(args)) + elif nargs > 1: + x = [_fetch(args) for _ in range(nargs)] + # If we're reversed, we're pulling in the arguments in reverse, + # so we need to turn them around. + if spos is not None: + x.reverse() + rv.append(tuple(x)) + elif nargs < 0: + if spos is not None: + raise TypeError('Cannot have two nargs < 0') + spos = len(rv) + rv.append(None) + + # spos is the position of the wildcard (star). If it's not `None`, + # we fill it with the remainder. + if spos is not None: + rv[spos] = tuple(args) + args = [] + rv[spos + 1:] = reversed(rv[spos + 1:]) + + return tuple(rv), list(args) + + +def _error_opt_args(nargs, opt): + if nargs == 1: + raise BadOptionUsage('%s option requires an argument' % opt) + raise BadOptionUsage('%s option requires %d arguments' % (opt, nargs)) + + +def split_opt(opt): + first = opt[:1] + if first.isalnum(): + return '', opt + if opt[1:2] == first: + return opt[:2], opt[2:] + return first, opt[1:] + + +def normalize_opt(opt, ctx): + if ctx is None or ctx.token_normalize_func is None: + return opt + prefix, opt = split_opt(opt) + return prefix + ctx.token_normalize_func(opt) + + +def split_arg_string(string): + """Given an argument string this attempts to split it into small parts.""" + rv = [] + for match in re.finditer(r"('([^'\\]*(?:\\.[^'\\]*)*)'" + r'|"([^"\\]*(?:\\.[^"\\]*)*)"' + r'|\S+)\s*', string, re.S): + arg = match.group().strip() + if arg[:1] == arg[-1:] and arg[:1] in '"\'': + arg = arg[1:-1].encode('ascii', 'backslashreplace') \ + .decode('unicode-escape') + try: + arg = type(string)(arg) + except UnicodeError: + pass + rv.append(arg) + return rv + + +class Option(object): + + def __init__(self, opts, dest, action=None, nargs=1, const=None, obj=None): + self._short_opts = [] + self._long_opts = [] + self.prefixes = set() + + for opt in opts: + prefix, value = split_opt(opt) + if not prefix: + raise ValueError('Invalid start character for option (%s)' + % opt) + self.prefixes.add(prefix[0]) + if len(prefix) == 1 and len(value) == 1: + self._short_opts.append(opt) + else: + self._long_opts.append(opt) + self.prefixes.add(prefix) + + if action is None: + action = 'store' + + self.dest = dest + self.action = action + self.nargs = nargs + self.const = const + self.obj = obj + + @property + def takes_value(self): + return self.action in ('store', 'append') + + def process(self, value, state): + if self.action == 'store': + state.opts[self.dest] = value + elif self.action == 'store_const': + state.opts[self.dest] = self.const + elif self.action == 'append': + state.opts.setdefault(self.dest, []).append(value) + elif self.action == 'append_const': + state.opts.setdefault(self.dest, []).append(self.const) + elif self.action == 'count': + state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 + else: + raise ValueError('unknown action %r' % self.action) + state.order.append(self.obj) + + +class Argument(object): + + def __init__(self, dest, nargs=1, obj=None): + self.dest = dest + self.nargs = nargs + self.obj = obj + + def process(self, value, state): + if self.nargs > 1: + holes = sum(1 for x in value if x is None) + if holes == len(value): + value = None + elif holes != 0: + raise BadArgumentUsage('argument %s takes %d values' + % (self.dest, self.nargs)) + state.opts[self.dest] = value + state.order.append(self.obj) + + +class ParsingState(object): + + def __init__(self, rargs): + self.opts = {} + self.largs = [] + self.rargs = rargs + self.order = [] + + +class OptionParser(object): + """The option parser is an internal class that is ultimately used to + parse options and arguments. It's modelled after optparse and brings + a similar but vastly simplified API. It should generally not be used + directly as the high level Click classes wrap it for you. + + It's not nearly as extensible as optparse or argparse as it does not + implement features that are implemented on a higher level (such as + types or defaults). + + :param ctx: optionally the :class:`~click.Context` where this parser + should go with. + """ + + def __init__(self, ctx=None): + #: The :class:`~click.Context` for this parser. This might be + #: `None` for some advanced use cases. + self.ctx = ctx + #: This controls how the parser deals with interspersed arguments. + #: If this is set to `False`, the parser will stop on the first + #: non-option. Click uses this to implement nested subcommands + #: safely. + self.allow_interspersed_args = True + #: This tells the parser how to deal with unknown options. By + #: default it will error out (which is sensible), but there is a + #: second mode where it will ignore it and continue processing + #: after shifting all the unknown options into the resulting args. + self.ignore_unknown_options = False + if ctx is not None: + self.allow_interspersed_args = ctx.allow_interspersed_args + self.ignore_unknown_options = ctx.ignore_unknown_options + self._short_opt = {} + self._long_opt = {} + self._opt_prefixes = set(['-', '--']) + self._args = [] + + def add_option(self, opts, dest, action=None, nargs=1, const=None, + obj=None): + """Adds a new option named `dest` to the parser. The destination + is not inferred (unlike with optparse) and needs to be explicitly + provided. Action can be any of ``store``, ``store_const``, + ``append``, ``appnd_const`` or ``count``. + + The `obj` can be used to identify the option in the order list + that is returned from the parser. + """ + if obj is None: + obj = dest + opts = [normalize_opt(opt, self.ctx) for opt in opts] + option = Option(opts, dest, action=action, nargs=nargs, + const=const, obj=obj) + self._opt_prefixes.update(option.prefixes) + for opt in option._short_opts: + self._short_opt[opt] = option + for opt in option._long_opts: + self._long_opt[opt] = option + + def add_argument(self, dest, nargs=1, obj=None): + """Adds a positional argument named `dest` to the parser. + + The `obj` can be used to identify the option in the order list + that is returned from the parser. + """ + if obj is None: + obj = dest + self._args.append(Argument(dest=dest, nargs=nargs, obj=obj)) + + def parse_args(self, args): + """Parses positional arguments and returns ``(values, args, order)`` + for the parsed options and arguments as well as the leftover + arguments if there are any. The order is a list of objects as they + appear on the command line. If arguments appear multiple times they + will be memorized multiple times as well. + """ + state = ParsingState(args) + try: + self._process_args_for_options(state) + self._process_args_for_args(state) + except UsageError: + if self.ctx is None or not self.ctx.resilient_parsing: + raise + return state.opts, state.largs, state.order + + def _process_args_for_args(self, state): + pargs, args = _unpack_args(state.largs + state.rargs, + [x.nargs for x in self._args]) + + for idx, arg in enumerate(self._args): + arg.process(pargs[idx], state) + + state.largs = args + state.rargs = [] + + def _process_args_for_options(self, state): + while state.rargs: + arg = state.rargs.pop(0) + arglen = len(arg) + # Double dashes always handled explicitly regardless of what + # prefixes are valid. + if arg == '--': + return + elif arg[:1] in self._opt_prefixes and arglen > 1: + self._process_opts(arg, state) + elif self.allow_interspersed_args: + state.largs.append(arg) + else: + state.rargs.insert(0, arg) + return + + # Say this is the original argument list: + # [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)] + # ^ + # (we are about to process arg(i)). + # + # Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of + # [arg0, ..., arg(i-1)] (any options and their arguments will have + # been removed from largs). + # + # The while loop will usually consume 1 or more arguments per pass. + # If it consumes 1 (eg. arg is an option that takes no arguments), + # then after _process_arg() is done the situation is: + # + # largs = subset of [arg0, ..., arg(i)] + # rargs = [arg(i+1), ..., arg(N-1)] + # + # If allow_interspersed_args is false, largs will always be + # *empty* -- still a subset of [arg0, ..., arg(i-1)], but + # not a very interesting subset! + + def _match_long_opt(self, opt, explicit_value, state): + if opt not in self._long_opt: + possibilities = [word for word in self._long_opt + if word.startswith(opt)] + raise NoSuchOption(opt, possibilities=possibilities) + + option = self._long_opt[opt] + if option.takes_value: + # At this point it's safe to modify rargs by injecting the + # explicit value, because no exception is raised in this + # branch. This means that the inserted value will be fully + # consumed. + if explicit_value is not None: + state.rargs.insert(0, explicit_value) + + nargs = option.nargs + if len(state.rargs) < nargs: + _error_opt_args(nargs, opt) + elif nargs == 1: + value = state.rargs.pop(0) + else: + value = tuple(state.rargs[:nargs]) + del state.rargs[:nargs] + + elif explicit_value is not None: + raise BadOptionUsage('%s option does not take a value' % opt) + + else: + value = None + + option.process(value, state) + + def _match_short_opt(self, arg, state): + stop = False + i = 1 + prefix = arg[0] + unknown_options = [] + + for ch in arg[1:]: + opt = normalize_opt(prefix + ch, self.ctx) + option = self._short_opt.get(opt) + i += 1 + + if not option: + if self.ignore_unknown_options: + unknown_options.append(ch) + continue + raise NoSuchOption(opt) + if option.takes_value: + # Any characters left in arg? Pretend they're the + # next arg, and stop consuming characters of arg. + if i < len(arg): + state.rargs.insert(0, arg[i:]) + stop = True + + nargs = option.nargs + if len(state.rargs) < nargs: + _error_opt_args(nargs, opt) + elif nargs == 1: + value = state.rargs.pop(0) + else: + value = tuple(state.rargs[:nargs]) + del state.rargs[:nargs] + + else: + value = None + + option.process(value, state) + + if stop: + break + + # If we got any unknown options we re-combinate the string of the + # remaining options and re-attach the prefix, then report that + # to the state as new larg. This way there is basic combinatorics + # that can be achieved while still ignoring unknown arguments. + if self.ignore_unknown_options and unknown_options: + state.largs.append(prefix + ''.join(unknown_options)) + + def _process_opts(self, arg, state): + explicit_value = None + # Long option handling happens in two parts. The first part is + # supporting explicitly attached values. In any case, we will try + # to long match the option first. + if '=' in arg: + long_opt, explicit_value = arg.split('=', 1) + else: + long_opt = arg + norm_long_opt = normalize_opt(long_opt, self.ctx) + + # At this point we will match the (assumed) long option through + # the long option matching code. Note that this allows options + # like "-foo" to be matched as long options. + try: + self._match_long_opt(norm_long_opt, explicit_value, state) + except NoSuchOption: + # At this point the long option matching failed, and we need + # to try with short options. However there is a special rule + # which says, that if we have a two character options prefix + # (applies to "--foo" for instance), we do not dispatch to the + # short option code and will instead raise the no option + # error. + if arg[:2] not in self._opt_prefixes: + return self._match_short_opt(arg, state) + if not self.ignore_unknown_options: + raise + state.largs.append(arg) diff -Nru click-0.4.43+16.04.20160203/click/paths.py.in click-6.7/click/paths.py.in --- click-0.4.43+16.04.20160203/click/paths.py.in 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/paths.py.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,19 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Click paths.""" - -preload_path = "@pkglibdir@/libclickpreload.so" -frameworks_dir = "@pkgdatadir@/frameworks" diff -Nru click-0.4.43+16.04.20160203/click/preinst.py click-6.7/click/preinst.py --- click-0.4.43+16.04.20160203/click/preinst.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/preinst.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,66 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Preinst for Click packages. - -In general there is a rule that Click packages may not have maintainer -scripts. However, there is one exception: a static preinst used to cause -dpkg to fail if people attempt to install Click packages directly using dpkg -rather than via "click install". This avoids accidents, since Click -packages use a different root of their filesystem tarball. -""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'static_preinst', - 'static_preinst_matches', - ] - - -_older_static_preinst = """\ -#! /bin/sh -echo "Click packages may not be installed directly using dpkg." -echo "Use click-install instead." -exit 1 -""" - - -_old_static_preinst = """\ -#! /bin/sh -echo "Click packages may not be installed directly using dpkg." -echo "Use 'click-package install' instead." -exit 1 -""" - - -static_preinst = """\ -#! /bin/sh -echo "Click packages may not be installed directly using dpkg." -echo "Use 'click install' instead." -exit 1 -""" - - -def static_preinst_matches(preinst): - for allow_preinst in ( - _older_static_preinst, - _old_static_preinst, - static_preinst, - ): - if preinst == allow_preinst.encode(): - return True - return False diff -Nru click-0.4.43+16.04.20160203/click/_termui_impl.py click-6.7/click/_termui_impl.py --- click-0.4.43+16.04.20160203/click/_termui_impl.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/click/_termui_impl.py 2017-01-06 22:32:37.000000000 +0000 @@ -0,0 +1,547 @@ +""" + click._termui_impl + ~~~~~~~~~~~~~~~~~~ + + This module contains implementations for the termui module. To keep the + import time of Click down, some infrequently used functionality is placed + in this module and only imported as needed. + + :copyright: (c) 2014 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import os +import sys +import time +import math +from ._compat import _default_text_stdout, range_type, PY2, isatty, \ + open_stream, strip_ansi, term_len, get_best_encoding, WIN +from .utils import echo +from .exceptions import ClickException + + +if os.name == 'nt': + BEFORE_BAR = '\r' + AFTER_BAR = '\n' +else: + BEFORE_BAR = '\r\033[?25l' + AFTER_BAR = '\033[?25h\n' + + +def _length_hint(obj): + """Returns the length hint of an object.""" + try: + return len(obj) + except (AttributeError, TypeError): + try: + get_hint = type(obj).__length_hint__ + except AttributeError: + return None + try: + hint = get_hint(obj) + except TypeError: + return None + if hint is NotImplemented or \ + not isinstance(hint, (int, long)) or \ + hint < 0: + return None + return hint + + +class ProgressBar(object): + + def __init__(self, iterable, length=None, fill_char='#', empty_char=' ', + bar_template='%(bar)s', info_sep=' ', show_eta=True, + show_percent=None, show_pos=False, item_show_func=None, + label=None, file=None, color=None, width=30): + self.fill_char = fill_char + self.empty_char = empty_char + self.bar_template = bar_template + self.info_sep = info_sep + self.show_eta = show_eta + self.show_percent = show_percent + self.show_pos = show_pos + self.item_show_func = item_show_func + self.label = label or '' + if file is None: + file = _default_text_stdout() + self.file = file + self.color = color + self.width = width + self.autowidth = width == 0 + + if length is None: + length = _length_hint(iterable) + if iterable is None: + if length is None: + raise TypeError('iterable or length is required') + iterable = range_type(length) + self.iter = iter(iterable) + self.length = length + self.length_known = length is not None + self.pos = 0 + self.avg = [] + self.start = self.last_eta = time.time() + self.eta_known = False + self.finished = False + self.max_width = None + self.entered = False + self.current_item = None + self.is_hidden = not isatty(self.file) + self._last_line = None + + def __enter__(self): + self.entered = True + self.render_progress() + return self + + def __exit__(self, exc_type, exc_value, tb): + self.render_finish() + + def __iter__(self): + if not self.entered: + raise RuntimeError('You need to use progress bars in a with block.') + self.render_progress() + return self + + def render_finish(self): + if self.is_hidden: + return + self.file.write(AFTER_BAR) + self.file.flush() + + @property + def pct(self): + if self.finished: + return 1.0 + return min(self.pos / (float(self.length) or 1), 1.0) + + @property + def time_per_iteration(self): + if not self.avg: + return 0.0 + return sum(self.avg) / float(len(self.avg)) + + @property + def eta(self): + if self.length_known and not self.finished: + return self.time_per_iteration * (self.length - self.pos) + return 0.0 + + def format_eta(self): + if self.eta_known: + t = self.eta + 1 + seconds = t % 60 + t /= 60 + minutes = t % 60 + t /= 60 + hours = t % 24 + t /= 24 + if t > 0: + days = t + return '%dd %02d:%02d:%02d' % (days, hours, minutes, seconds) + else: + return '%02d:%02d:%02d' % (hours, minutes, seconds) + return '' + + def format_pos(self): + pos = str(self.pos) + if self.length_known: + pos += '/%s' % self.length + return pos + + def format_pct(self): + return ('% 4d%%' % int(self.pct * 100))[1:] + + def format_progress_line(self): + show_percent = self.show_percent + + info_bits = [] + if self.length_known: + bar_length = int(self.pct * self.width) + bar = self.fill_char * bar_length + bar += self.empty_char * (self.width - bar_length) + if show_percent is None: + show_percent = not self.show_pos + else: + if self.finished: + bar = self.fill_char * self.width + else: + bar = list(self.empty_char * (self.width or 1)) + if self.time_per_iteration != 0: + bar[int((math.cos(self.pos * self.time_per_iteration) + / 2.0 + 0.5) * self.width)] = self.fill_char + bar = ''.join(bar) + + if self.show_pos: + info_bits.append(self.format_pos()) + if show_percent: + info_bits.append(self.format_pct()) + if self.show_eta and self.eta_known and not self.finished: + info_bits.append(self.format_eta()) + if self.item_show_func is not None: + item_info = self.item_show_func(self.current_item) + if item_info is not None: + info_bits.append(item_info) + + return (self.bar_template % { + 'label': self.label, + 'bar': bar, + 'info': self.info_sep.join(info_bits) + }).rstrip() + + def render_progress(self): + from .termui import get_terminal_size + nl = False + + if self.is_hidden: + buf = [self.label] + nl = True + else: + buf = [] + # Update width in case the terminal has been resized + if self.autowidth: + old_width = self.width + self.width = 0 + clutter_length = term_len(self.format_progress_line()) + new_width = max(0, get_terminal_size()[0] - clutter_length) + if new_width < old_width: + buf.append(BEFORE_BAR) + buf.append(' ' * self.max_width) + self.max_width = new_width + self.width = new_width + + clear_width = self.width + if self.max_width is not None: + clear_width = self.max_width + + buf.append(BEFORE_BAR) + line = self.format_progress_line() + line_len = term_len(line) + if self.max_width is None or self.max_width < line_len: + self.max_width = line_len + buf.append(line) + + buf.append(' ' * (clear_width - line_len)) + line = ''.join(buf) + + # Render the line only if it changed. + if line != self._last_line: + self._last_line = line + echo(line, file=self.file, color=self.color, nl=nl) + self.file.flush() + + def make_step(self, n_steps): + self.pos += n_steps + if self.length_known and self.pos >= self.length: + self.finished = True + + if (time.time() - self.last_eta) < 1.0: + return + + self.last_eta = time.time() + self.avg = self.avg[-6:] + [-(self.start - time.time()) / (self.pos)] + + self.eta_known = self.length_known + + def update(self, n_steps): + self.make_step(n_steps) + self.render_progress() + + def finish(self): + self.eta_known = 0 + self.current_item = None + self.finished = True + + def next(self): + if self.is_hidden: + return next(self.iter) + try: + rv = next(self.iter) + self.current_item = rv + except StopIteration: + self.finish() + self.render_progress() + raise StopIteration() + else: + self.update(1) + return rv + + if not PY2: + __next__ = next + del next + + +def pager(text, color=None): + """Decide what method to use for paging through text.""" + stdout = _default_text_stdout() + if not isatty(sys.stdin) or not isatty(stdout): + return _nullpager(stdout, text, color) + pager_cmd = (os.environ.get('PAGER', None) or '').strip() + if pager_cmd: + if WIN: + return _tempfilepager(text, pager_cmd, color) + return _pipepager(text, pager_cmd, color) + if os.environ.get('TERM') in ('dumb', 'emacs'): + return _nullpager(stdout, text, color) + if WIN or sys.platform.startswith('os2'): + return _tempfilepager(text, 'more <', color) + if hasattr(os, 'system') and os.system('(less) 2>/dev/null') == 0: + return _pipepager(text, 'less', color) + + import tempfile + fd, filename = tempfile.mkstemp() + os.close(fd) + try: + if hasattr(os, 'system') and os.system('more "%s"' % filename) == 0: + return _pipepager(text, 'more', color) + return _nullpager(stdout, text, color) + finally: + os.unlink(filename) + + +def _pipepager(text, cmd, color): + """Page through text by feeding it to another program. Invoking a + pager through this might support colors. + """ + import subprocess + env = dict(os.environ) + + # If we're piping to less we might support colors under the + # condition that + cmd_detail = cmd.rsplit('/', 1)[-1].split() + if color is None and cmd_detail[0] == 'less': + less_flags = os.environ.get('LESS', '') + ' '.join(cmd_detail[1:]) + if not less_flags: + env['LESS'] = '-R' + color = True + elif 'r' in less_flags or 'R' in less_flags: + color = True + + if not color: + text = strip_ansi(text) + + c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, + env=env) + encoding = get_best_encoding(c.stdin) + try: + c.stdin.write(text.encode(encoding, 'replace')) + c.stdin.close() + except (IOError, KeyboardInterrupt): + pass + + # Less doesn't respect ^C, but catches it for its own UI purposes (aborting + # search or other commands inside less). + # + # That means when the user hits ^C, the parent process (click) terminates, + # but less is still alive, paging the output and messing up the terminal. + # + # If the user wants to make the pager exit on ^C, they should set + # `LESS='-K'`. It's not our decision to make. + while True: + try: + c.wait() + except KeyboardInterrupt: + pass + else: + break + + +def _tempfilepager(text, cmd, color): + """Page through text by invoking a program on a temporary file.""" + import tempfile + filename = tempfile.mktemp() + if not color: + text = strip_ansi(text) + encoding = get_best_encoding(sys.stdout) + with open_stream(filename, 'wb')[0] as f: + f.write(text.encode(encoding)) + try: + os.system(cmd + ' "' + filename + '"') + finally: + os.unlink(filename) + + +def _nullpager(stream, text, color): + """Simply print unformatted text. This is the ultimate fallback.""" + if not color: + text = strip_ansi(text) + stream.write(text) + + +class Editor(object): + + def __init__(self, editor=None, env=None, require_save=True, + extension='.txt'): + self.editor = editor + self.env = env + self.require_save = require_save + self.extension = extension + + def get_editor(self): + if self.editor is not None: + return self.editor + for key in 'VISUAL', 'EDITOR': + rv = os.environ.get(key) + if rv: + return rv + if WIN: + return 'notepad' + for editor in 'vim', 'nano': + if os.system('which %s >/dev/null 2>&1' % editor) == 0: + return editor + return 'vi' + + def edit_file(self, filename): + import subprocess + editor = self.get_editor() + if self.env: + environ = os.environ.copy() + environ.update(self.env) + else: + environ = None + try: + c = subprocess.Popen('%s "%s"' % (editor, filename), + env=environ, shell=True) + exit_code = c.wait() + if exit_code != 0: + raise ClickException('%s: Editing failed!' % editor) + except OSError as e: + raise ClickException('%s: Editing failed: %s' % (editor, e)) + + def edit(self, text): + import tempfile + + text = text or '' + if text and not text.endswith('\n'): + text += '\n' + + fd, name = tempfile.mkstemp(prefix='editor-', suffix=self.extension) + try: + if WIN: + encoding = 'utf-8-sig' + text = text.replace('\n', '\r\n') + else: + encoding = 'utf-8' + text = text.encode(encoding) + + f = os.fdopen(fd, 'wb') + f.write(text) + f.close() + timestamp = os.path.getmtime(name) + + self.edit_file(name) + + if self.require_save \ + and os.path.getmtime(name) == timestamp: + return None + + f = open(name, 'rb') + try: + rv = f.read() + finally: + f.close() + return rv.decode('utf-8-sig').replace('\r\n', '\n') + finally: + os.unlink(name) + + +def open_url(url, wait=False, locate=False): + import subprocess + + def _unquote_file(url): + try: + import urllib + except ImportError: + import urllib + if url.startswith('file://'): + url = urllib.unquote(url[7:]) + return url + + if sys.platform == 'darwin': + args = ['open'] + if wait: + args.append('-W') + if locate: + args.append('-R') + args.append(_unquote_file(url)) + null = open('/dev/null', 'w') + try: + return subprocess.Popen(args, stderr=null).wait() + finally: + null.close() + elif WIN: + if locate: + url = _unquote_file(url) + args = 'explorer /select,"%s"' % _unquote_file( + url.replace('"', '')) + else: + args = 'start %s "" "%s"' % ( + wait and '/WAIT' or '', url.replace('"', '')) + return os.system(args) + + try: + if locate: + url = os.path.dirname(_unquote_file(url)) or '.' + else: + url = _unquote_file(url) + c = subprocess.Popen(['xdg-open', url]) + if wait: + return c.wait() + return 0 + except OSError: + if url.startswith(('http://', 'https://')) and not locate and not wait: + import webbrowser + webbrowser.open(url) + return 0 + return 1 + + +def _translate_ch_to_exc(ch): + if ch == '\x03': + raise KeyboardInterrupt() + if ch == '\x04': + raise EOFError() + + +if WIN: + import msvcrt + + def getchar(echo): + rv = msvcrt.getch() + if echo: + msvcrt.putchar(rv) + _translate_ch_to_exc(rv) + if PY2: + enc = getattr(sys.stdin, 'encoding', None) + if enc is not None: + rv = rv.decode(enc, 'replace') + else: + rv = rv.decode('cp1252', 'replace') + return rv +else: + import tty + import termios + + def getchar(echo): + if not isatty(sys.stdin): + f = open('/dev/tty') + fd = f.fileno() + else: + fd = sys.stdin.fileno() + f = None + try: + old_settings = termios.tcgetattr(fd) + try: + tty.setraw(fd) + ch = os.read(fd, 32) + if echo and isatty(sys.stdout): + sys.stdout.write(ch) + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + sys.stdout.flush() + if f is not None: + f.close() + except termios.error: + pass + _translate_ch_to_exc(ch) + return ch.decode(get_best_encoding(sys.stdin), 'replace') diff -Nru click-0.4.43+16.04.20160203/click/termui.py click-6.7/click/termui.py --- click-0.4.43+16.04.20160203/click/termui.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/click/termui.py 2017-01-06 22:32:37.000000000 +0000 @@ -0,0 +1,539 @@ +import os +import sys +import struct + +from ._compat import raw_input, text_type, string_types, \ + isatty, strip_ansi, get_winterm_size, DEFAULT_COLUMNS, WIN +from .utils import echo +from .exceptions import Abort, UsageError +from .types import convert_type +from .globals import resolve_color_default + + +# The prompt functions to use. The doc tools currently override these +# functions to customize how they work. +visible_prompt_func = raw_input + +_ansi_colors = ('black', 'red', 'green', 'yellow', 'blue', 'magenta', + 'cyan', 'white', 'reset') +_ansi_reset_all = '\033[0m' + + +def hidden_prompt_func(prompt): + import getpass + return getpass.getpass(prompt) + + +def _build_prompt(text, suffix, show_default=False, default=None): + prompt = text + if default is not None and show_default: + prompt = '%s [%s]' % (prompt, default) + return prompt + suffix + + +def prompt(text, default=None, hide_input=False, + confirmation_prompt=False, type=None, + value_proc=None, prompt_suffix=': ', + show_default=True, err=False): + """Prompts a user for input. This is a convenience function that can + be used to prompt a user for input later. + + If the user aborts the input by sending a interrupt signal, this + function will catch it and raise a :exc:`Abort` exception. + + .. versionadded:: 6.0 + Added unicode support for cmd.exe on Windows. + + .. versionadded:: 4.0 + Added the `err` parameter. + + :param text: the text to show for the prompt. + :param default: the default value to use if no input happens. If this + is not given it will prompt until it's aborted. + :param hide_input: if this is set to true then the input value will + be hidden. + :param confirmation_prompt: asks for confirmation for the value. + :param type: the type to use to check the value against. + :param value_proc: if this parameter is provided it's a function that + is invoked instead of the type conversion to + convert a value. + :param prompt_suffix: a suffix that should be added to the prompt. + :param show_default: shows or hides the default value in the prompt. + :param err: if set to true the file defaults to ``stderr`` instead of + ``stdout``, the same as with echo. + """ + result = None + + def prompt_func(text): + f = hide_input and hidden_prompt_func or visible_prompt_func + try: + # Write the prompt separately so that we get nice + # coloring through colorama on Windows + echo(text, nl=False, err=err) + return f('') + except (KeyboardInterrupt, EOFError): + # getpass doesn't print a newline if the user aborts input with ^C. + # Allegedly this behavior is inherited from getpass(3). + # A doc bug has been filed at https://bugs.python.org/issue24711 + if hide_input: + echo(None, err=err) + raise Abort() + + if value_proc is None: + value_proc = convert_type(type, default) + + prompt = _build_prompt(text, prompt_suffix, show_default, default) + + while 1: + while 1: + value = prompt_func(prompt) + if value: + break + # If a default is set and used, then the confirmation + # prompt is always skipped because that's the only thing + # that really makes sense. + elif default is not None: + return default + try: + result = value_proc(value) + except UsageError as e: + echo('Error: %s' % e.message, err=err) + continue + if not confirmation_prompt: + return result + while 1: + value2 = prompt_func('Repeat for confirmation: ') + if value2: + break + if value == value2: + return result + echo('Error: the two entered values do not match', err=err) + + +def confirm(text, default=False, abort=False, prompt_suffix=': ', + show_default=True, err=False): + """Prompts for confirmation (yes/no question). + + If the user aborts the input by sending a interrupt signal this + function will catch it and raise a :exc:`Abort` exception. + + .. versionadded:: 4.0 + Added the `err` parameter. + + :param text: the question to ask. + :param default: the default for the prompt. + :param abort: if this is set to `True` a negative answer aborts the + exception by raising :exc:`Abort`. + :param prompt_suffix: a suffix that should be added to the prompt. + :param show_default: shows or hides the default value in the prompt. + :param err: if set to true the file defaults to ``stderr`` instead of + ``stdout``, the same as with echo. + """ + prompt = _build_prompt(text, prompt_suffix, show_default, + default and 'Y/n' or 'y/N') + while 1: + try: + # Write the prompt separately so that we get nice + # coloring through colorama on Windows + echo(prompt, nl=False, err=err) + value = visible_prompt_func('').lower().strip() + except (KeyboardInterrupt, EOFError): + raise Abort() + if value in ('y', 'yes'): + rv = True + elif value in ('n', 'no'): + rv = False + elif value == '': + rv = default + else: + echo('Error: invalid input', err=err) + continue + break + if abort and not rv: + raise Abort() + return rv + + +def get_terminal_size(): + """Returns the current size of the terminal as tuple in the form + ``(width, height)`` in columns and rows. + """ + # If shutil has get_terminal_size() (Python 3.3 and later) use that + if sys.version_info >= (3, 3): + import shutil + shutil_get_terminal_size = getattr(shutil, 'get_terminal_size', None) + if shutil_get_terminal_size: + sz = shutil_get_terminal_size() + return sz.columns, sz.lines + + if get_winterm_size is not None: + return get_winterm_size() + + def ioctl_gwinsz(fd): + try: + import fcntl + import termios + cr = struct.unpack( + 'hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234')) + except Exception: + return + return cr + + cr = ioctl_gwinsz(0) or ioctl_gwinsz(1) or ioctl_gwinsz(2) + if not cr: + try: + fd = os.open(os.ctermid(), os.O_RDONLY) + try: + cr = ioctl_gwinsz(fd) + finally: + os.close(fd) + except Exception: + pass + if not cr or not cr[0] or not cr[1]: + cr = (os.environ.get('LINES', 25), + os.environ.get('COLUMNS', DEFAULT_COLUMNS)) + return int(cr[1]), int(cr[0]) + + +def echo_via_pager(text, color=None): + """This function takes a text and shows it via an environment specific + pager on stdout. + + .. versionchanged:: 3.0 + Added the `color` flag. + + :param text: the text to page. + :param color: controls if the pager supports ANSI colors or not. The + default is autodetection. + """ + color = resolve_color_default(color) + if not isinstance(text, string_types): + text = text_type(text) + from ._termui_impl import pager + return pager(text + '\n', color) + + +def progressbar(iterable=None, length=None, label=None, show_eta=True, + show_percent=None, show_pos=False, + item_show_func=None, fill_char='#', empty_char='-', + bar_template='%(label)s [%(bar)s] %(info)s', + info_sep=' ', width=36, file=None, color=None): + """This function creates an iterable context manager that can be used + to iterate over something while showing a progress bar. It will + either iterate over the `iterable` or `length` items (that are counted + up). While iteration happens, this function will print a rendered + progress bar to the given `file` (defaults to stdout) and will attempt + to calculate remaining time and more. By default, this progress bar + will not be rendered if the file is not a terminal. + + The context manager creates the progress bar. When the context + manager is entered the progress bar is already displayed. With every + iteration over the progress bar, the iterable passed to the bar is + advanced and the bar is updated. When the context manager exits, + a newline is printed and the progress bar is finalized on screen. + + No printing must happen or the progress bar will be unintentionally + destroyed. + + Example usage:: + + with progressbar(items) as bar: + for item in bar: + do_something_with(item) + + Alternatively, if no iterable is specified, one can manually update the + progress bar through the `update()` method instead of directly + iterating over the progress bar. The update method accepts the number + of steps to increment the bar with:: + + with progressbar(length=chunks.total_bytes) as bar: + for chunk in chunks: + process_chunk(chunk) + bar.update(chunks.bytes) + + .. versionadded:: 2.0 + + .. versionadded:: 4.0 + Added the `color` parameter. Added a `update` method to the + progressbar object. + + :param iterable: an iterable to iterate over. If not provided the length + is required. + :param length: the number of items to iterate over. By default the + progressbar will attempt to ask the iterator about its + length, which might or might not work. If an iterable is + also provided this parameter can be used to override the + length. If an iterable is not provided the progress bar + will iterate over a range of that length. + :param label: the label to show next to the progress bar. + :param show_eta: enables or disables the estimated time display. This is + automatically disabled if the length cannot be + determined. + :param show_percent: enables or disables the percentage display. The + default is `True` if the iterable has a length or + `False` if not. + :param show_pos: enables or disables the absolute position display. The + default is `False`. + :param item_show_func: a function called with the current item which + can return a string to show the current item + next to the progress bar. Note that the current + item can be `None`! + :param fill_char: the character to use to show the filled part of the + progress bar. + :param empty_char: the character to use to show the non-filled part of + the progress bar. + :param bar_template: the format string to use as template for the bar. + The parameters in it are ``label`` for the label, + ``bar`` for the progress bar and ``info`` for the + info section. + :param info_sep: the separator between multiple info items (eta etc.) + :param width: the width of the progress bar in characters, 0 means full + terminal width + :param file: the file to write to. If this is not a terminal then + only the label is printed. + :param color: controls if the terminal supports ANSI colors or not. The + default is autodetection. This is only needed if ANSI + codes are included anywhere in the progress bar output + which is not the case by default. + """ + from ._termui_impl import ProgressBar + color = resolve_color_default(color) + return ProgressBar(iterable=iterable, length=length, show_eta=show_eta, + show_percent=show_percent, show_pos=show_pos, + item_show_func=item_show_func, fill_char=fill_char, + empty_char=empty_char, bar_template=bar_template, + info_sep=info_sep, file=file, label=label, + width=width, color=color) + + +def clear(): + """Clears the terminal screen. This will have the effect of clearing + the whole visible space of the terminal and moving the cursor to the + top left. This does not do anything if not connected to a terminal. + + .. versionadded:: 2.0 + """ + if not isatty(sys.stdout): + return + # If we're on Windows and we don't have colorama available, then we + # clear the screen by shelling out. Otherwise we can use an escape + # sequence. + if WIN: + os.system('cls') + else: + sys.stdout.write('\033[2J\033[1;1H') + + +def style(text, fg=None, bg=None, bold=None, dim=None, underline=None, + blink=None, reverse=None, reset=True): + """Styles a text with ANSI styles and returns the new string. By + default the styling is self contained which means that at the end + of the string a reset code is issued. This can be prevented by + passing ``reset=False``. + + Examples:: + + click.echo(click.style('Hello World!', fg='green')) + click.echo(click.style('ATTENTION!', blink=True)) + click.echo(click.style('Some things', reverse=True, fg='cyan')) + + Supported color names: + + * ``black`` (might be a gray) + * ``red`` + * ``green`` + * ``yellow`` (might be an orange) + * ``blue`` + * ``magenta`` + * ``cyan`` + * ``white`` (might be light gray) + * ``reset`` (reset the color code only) + + .. versionadded:: 2.0 + + :param text: the string to style with ansi codes. + :param fg: if provided this will become the foreground color. + :param bg: if provided this will become the background color. + :param bold: if provided this will enable or disable bold mode. + :param dim: if provided this will enable or disable dim mode. This is + badly supported. + :param underline: if provided this will enable or disable underline. + :param blink: if provided this will enable or disable blinking. + :param reverse: if provided this will enable or disable inverse + rendering (foreground becomes background and the + other way round). + :param reset: by default a reset-all code is added at the end of the + string which means that styles do not carry over. This + can be disabled to compose styles. + """ + bits = [] + if fg: + try: + bits.append('\033[%dm' % (_ansi_colors.index(fg) + 30)) + except ValueError: + raise TypeError('Unknown color %r' % fg) + if bg: + try: + bits.append('\033[%dm' % (_ansi_colors.index(bg) + 40)) + except ValueError: + raise TypeError('Unknown color %r' % bg) + if bold is not None: + bits.append('\033[%dm' % (1 if bold else 22)) + if dim is not None: + bits.append('\033[%dm' % (2 if dim else 22)) + if underline is not None: + bits.append('\033[%dm' % (4 if underline else 24)) + if blink is not None: + bits.append('\033[%dm' % (5 if blink else 25)) + if reverse is not None: + bits.append('\033[%dm' % (7 if reverse else 27)) + bits.append(text) + if reset: + bits.append(_ansi_reset_all) + return ''.join(bits) + + +def unstyle(text): + """Removes ANSI styling information from a string. Usually it's not + necessary to use this function as Click's echo function will + automatically remove styling if necessary. + + .. versionadded:: 2.0 + + :param text: the text to remove style information from. + """ + return strip_ansi(text) + + +def secho(text, file=None, nl=True, err=False, color=None, **styles): + """This function combines :func:`echo` and :func:`style` into one + call. As such the following two calls are the same:: + + click.secho('Hello World!', fg='green') + click.echo(click.style('Hello World!', fg='green')) + + All keyword arguments are forwarded to the underlying functions + depending on which one they go with. + + .. versionadded:: 2.0 + """ + return echo(style(text, **styles), file=file, nl=nl, err=err, color=color) + + +def edit(text=None, editor=None, env=None, require_save=True, + extension='.txt', filename=None): + r"""Edits the given text in the defined editor. If an editor is given + (should be the full path to the executable but the regular operating + system search path is used for finding the executable) it overrides + the detected editor. Optionally, some environment variables can be + used. If the editor is closed without changes, `None` is returned. In + case a file is edited directly the return value is always `None` and + `require_save` and `extension` are ignored. + + If the editor cannot be opened a :exc:`UsageError` is raised. + + Note for Windows: to simplify cross-platform usage, the newlines are + automatically converted from POSIX to Windows and vice versa. As such, + the message here will have ``\n`` as newline markers. + + :param text: the text to edit. + :param editor: optionally the editor to use. Defaults to automatic + detection. + :param env: environment variables to forward to the editor. + :param require_save: if this is true, then not saving in the editor + will make the return value become `None`. + :param extension: the extension to tell the editor about. This defaults + to `.txt` but changing this might change syntax + highlighting. + :param filename: if provided it will edit this file instead of the + provided text contents. It will not use a temporary + file as an indirection in that case. + """ + from ._termui_impl import Editor + editor = Editor(editor=editor, env=env, require_save=require_save, + extension=extension) + if filename is None: + return editor.edit(text) + editor.edit_file(filename) + + +def launch(url, wait=False, locate=False): + """This function launches the given URL (or filename) in the default + viewer application for this file type. If this is an executable, it + might launch the executable in a new session. The return value is + the exit code of the launched application. Usually, ``0`` indicates + success. + + Examples:: + + click.launch('http://click.pocoo.org/') + click.launch('/my/downloaded/file', locate=True) + + .. versionadded:: 2.0 + + :param url: URL or filename of the thing to launch. + :param wait: waits for the program to stop. + :param locate: if this is set to `True` then instead of launching the + application associated with the URL it will attempt to + launch a file manager with the file located. This + might have weird effects if the URL does not point to + the filesystem. + """ + from ._termui_impl import open_url + return open_url(url, wait=wait, locate=locate) + + +# If this is provided, getchar() calls into this instead. This is used +# for unittesting purposes. +_getchar = None + + +def getchar(echo=False): + """Fetches a single character from the terminal and returns it. This + will always return a unicode character and under certain rare + circumstances this might return more than one character. The + situations which more than one character is returned is when for + whatever reason multiple characters end up in the terminal buffer or + standard input was not actually a terminal. + + Note that this will always read from the terminal, even if something + is piped into the standard input. + + .. versionadded:: 2.0 + + :param echo: if set to `True`, the character read will also show up on + the terminal. The default is to not show it. + """ + f = _getchar + if f is None: + from ._termui_impl import getchar as f + return f(echo) + + +def pause(info='Press any key to continue ...', err=False): + """This command stops execution and waits for the user to press any + key to continue. This is similar to the Windows batch "pause" + command. If the program is not run through a terminal, this command + will instead do nothing. + + .. versionadded:: 2.0 + + .. versionadded:: 4.0 + Added the `err` parameter. + + :param info: the info string to print before pausing. + :param err: if set to message goes to ``stderr`` instead of + ``stdout``, the same as with echo. + """ + if not isatty(sys.stdin) or not isatty(sys.stdout): + return + try: + if info: + echo(info, nl=False, err=err) + try: + getchar() + except (KeyboardInterrupt, EOFError): + pass + finally: + if info: + echo(err=err) diff -Nru click-0.4.43+16.04.20160203/click/testing.py click-6.7/click/testing.py --- click-0.4.43+16.04.20160203/click/testing.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/click/testing.py 2017-01-06 22:32:37.000000000 +0000 @@ -0,0 +1,322 @@ +import os +import sys +import shutil +import tempfile +import contextlib + +from ._compat import iteritems, PY2 + + +# If someone wants to vendor click, we want to ensure the +# correct package is discovered. Ideally we could use a +# relative import here but unfortunately Python does not +# support that. +clickpkg = sys.modules[__name__.rsplit('.', 1)[0]] + + +if PY2: + from cStringIO import StringIO +else: + import io + from ._compat import _find_binary_reader + + +class EchoingStdin(object): + + def __init__(self, input, output): + self._input = input + self._output = output + + def __getattr__(self, x): + return getattr(self._input, x) + + def _echo(self, rv): + self._output.write(rv) + return rv + + def read(self, n=-1): + return self._echo(self._input.read(n)) + + def readline(self, n=-1): + return self._echo(self._input.readline(n)) + + def readlines(self): + return [self._echo(x) for x in self._input.readlines()] + + def __iter__(self): + return iter(self._echo(x) for x in self._input) + + def __repr__(self): + return repr(self._input) + + +def make_input_stream(input, charset): + # Is already an input stream. + if hasattr(input, 'read'): + if PY2: + return input + rv = _find_binary_reader(input) + if rv is not None: + return rv + raise TypeError('Could not find binary reader for input stream.') + + if input is None: + input = b'' + elif not isinstance(input, bytes): + input = input.encode(charset) + if PY2: + return StringIO(input) + return io.BytesIO(input) + + +class Result(object): + """Holds the captured result of an invoked CLI script.""" + + def __init__(self, runner, output_bytes, exit_code, exception, + exc_info=None): + #: The runner that created the result + self.runner = runner + #: The output as bytes. + self.output_bytes = output_bytes + #: The exit code as integer. + self.exit_code = exit_code + #: The exception that happend if one did. + self.exception = exception + #: The traceback + self.exc_info = exc_info + + @property + def output(self): + """The output as unicode string.""" + return self.output_bytes.decode(self.runner.charset, 'replace') \ + .replace('\r\n', '\n') + + def __repr__(self): + return '' % ( + self.exception and repr(self.exception) or 'okay', + ) + + +class CliRunner(object): + """The CLI runner provides functionality to invoke a Click command line + script for unittesting purposes in a isolated environment. This only + works in single-threaded systems without any concurrency as it changes the + global interpreter state. + + :param charset: the character set for the input and output data. This is + UTF-8 by default and should not be changed currently as + the reporting to Click only works in Python 2 properly. + :param env: a dictionary with environment variables for overriding. + :param echo_stdin: if this is set to `True`, then reading from stdin writes + to stdout. This is useful for showing examples in + some circumstances. Note that regular prompts + will automatically echo the input. + """ + + def __init__(self, charset=None, env=None, echo_stdin=False): + if charset is None: + charset = 'utf-8' + self.charset = charset + self.env = env or {} + self.echo_stdin = echo_stdin + + def get_default_prog_name(self, cli): + """Given a command object it will return the default program name + for it. The default is the `name` attribute or ``"root"`` if not + set. + """ + return cli.name or 'root' + + def make_env(self, overrides=None): + """Returns the environment overrides for invoking a script.""" + rv = dict(self.env) + if overrides: + rv.update(overrides) + return rv + + @contextlib.contextmanager + def isolation(self, input=None, env=None, color=False): + """A context manager that sets up the isolation for invoking of a + command line tool. This sets up stdin with the given input data + and `os.environ` with the overrides from the given dictionary. + This also rebinds some internals in Click to be mocked (like the + prompt functionality). + + This is automatically done in the :meth:`invoke` method. + + .. versionadded:: 4.0 + The ``color`` parameter was added. + + :param input: the input stream to put into sys.stdin. + :param env: the environment overrides as dictionary. + :param color: whether the output should contain color codes. The + application can still override this explicitly. + """ + input = make_input_stream(input, self.charset) + + old_stdin = sys.stdin + old_stdout = sys.stdout + old_stderr = sys.stderr + old_forced_width = clickpkg.formatting.FORCED_WIDTH + clickpkg.formatting.FORCED_WIDTH = 80 + + env = self.make_env(env) + + if PY2: + sys.stdout = sys.stderr = bytes_output = StringIO() + if self.echo_stdin: + input = EchoingStdin(input, bytes_output) + else: + bytes_output = io.BytesIO() + if self.echo_stdin: + input = EchoingStdin(input, bytes_output) + input = io.TextIOWrapper(input, encoding=self.charset) + sys.stdout = sys.stderr = io.TextIOWrapper( + bytes_output, encoding=self.charset) + + sys.stdin = input + + def visible_input(prompt=None): + sys.stdout.write(prompt or '') + val = input.readline().rstrip('\r\n') + sys.stdout.write(val + '\n') + sys.stdout.flush() + return val + + def hidden_input(prompt=None): + sys.stdout.write((prompt or '') + '\n') + sys.stdout.flush() + return input.readline().rstrip('\r\n') + + def _getchar(echo): + char = sys.stdin.read(1) + if echo: + sys.stdout.write(char) + sys.stdout.flush() + return char + + default_color = color + def should_strip_ansi(stream=None, color=None): + if color is None: + return not default_color + return not color + + old_visible_prompt_func = clickpkg.termui.visible_prompt_func + old_hidden_prompt_func = clickpkg.termui.hidden_prompt_func + old__getchar_func = clickpkg.termui._getchar + old_should_strip_ansi = clickpkg.utils.should_strip_ansi + clickpkg.termui.visible_prompt_func = visible_input + clickpkg.termui.hidden_prompt_func = hidden_input + clickpkg.termui._getchar = _getchar + clickpkg.utils.should_strip_ansi = should_strip_ansi + + old_env = {} + try: + for key, value in iteritems(env): + old_env[key] = os.environ.get(key) + if value is None: + try: + del os.environ[key] + except Exception: + pass + else: + os.environ[key] = value + yield bytes_output + finally: + for key, value in iteritems(old_env): + if value is None: + try: + del os.environ[key] + except Exception: + pass + else: + os.environ[key] = value + sys.stdout = old_stdout + sys.stderr = old_stderr + sys.stdin = old_stdin + clickpkg.termui.visible_prompt_func = old_visible_prompt_func + clickpkg.termui.hidden_prompt_func = old_hidden_prompt_func + clickpkg.termui._getchar = old__getchar_func + clickpkg.utils.should_strip_ansi = old_should_strip_ansi + clickpkg.formatting.FORCED_WIDTH = old_forced_width + + def invoke(self, cli, args=None, input=None, env=None, + catch_exceptions=True, color=False, **extra): + """Invokes a command in an isolated environment. The arguments are + forwarded directly to the command line script, the `extra` keyword + arguments are passed to the :meth:`~clickpkg.Command.main` function of + the command. + + This returns a :class:`Result` object. + + .. versionadded:: 3.0 + The ``catch_exceptions`` parameter was added. + + .. versionchanged:: 3.0 + The result object now has an `exc_info` attribute with the + traceback if available. + + .. versionadded:: 4.0 + The ``color`` parameter was added. + + :param cli: the command to invoke + :param args: the arguments to invoke + :param input: the input data for `sys.stdin`. + :param env: the environment overrides. + :param catch_exceptions: Whether to catch any other exceptions than + ``SystemExit``. + :param extra: the keyword arguments to pass to :meth:`main`. + :param color: whether the output should contain color codes. The + application can still override this explicitly. + """ + exc_info = None + with self.isolation(input=input, env=env, color=color) as out: + exception = None + exit_code = 0 + + try: + cli.main(args=args or (), + prog_name=self.get_default_prog_name(cli), **extra) + except SystemExit as e: + if e.code != 0: + exception = e + + exc_info = sys.exc_info() + + exit_code = e.code + if not isinstance(exit_code, int): + sys.stdout.write(str(exit_code)) + sys.stdout.write('\n') + exit_code = 1 + except Exception as e: + if not catch_exceptions: + raise + exception = e + exit_code = -1 + exc_info = sys.exc_info() + finally: + sys.stdout.flush() + output = out.getvalue() + + return Result(runner=self, + output_bytes=output, + exit_code=exit_code, + exception=exception, + exc_info=exc_info) + + @contextlib.contextmanager + def isolated_filesystem(self): + """A context manager that creates a temporary folder and changes + the current working directory to it for isolated filesystem tests. + """ + cwd = os.getcwd() + t = tempfile.mkdtemp() + os.chdir(t) + try: + yield t + finally: + os.chdir(cwd) + try: + shutil.rmtree(t) + except (OSError, IOError): + pass diff -Nru click-0.4.43+16.04.20160203/click/tests/config.py.in click-6.7/click/tests/config.py.in --- click-0.4.43+16.04.20160203/click/tests/config.py.in 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/config.py.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,20 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -abs_top_builddir = "@abs_top_builddir@" -STAT_OFFSET_UID = @STAT_OFFSET_UID@ -STAT_OFFSET_GID = @STAT_OFFSET_GID@ -STAT64_OFFSET_UID = @STAT64_OFFSET_UID@ -STAT64_OFFSET_GID = @STAT64_OFFSET_GID@ diff -Nru click-0.4.43+16.04.20160203/click/tests/gimock.py click-6.7/click/tests/gimock.py --- click-0.4.43+16.04.20160203/click/tests/gimock.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/gimock.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,521 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Mock function support based on GObject Introspection. - -(Note to reviewers: I expect to rewrite this from scratch on my own time as -a more generalised set of Python modules for unit testing of C code, -although using similar core ideas. This is a first draft for the purpose of -getting Click's test suite to work expediently, rather than an interface I'm -prepared to commit to long-term.) - -Python is a versatile and concise language for writing tests, and GObject -Introspection (GI) makes it straightforward (often trivial) to bind native -code into Python. However, writing tests for native code quickly runs into -the problem of how to build mock functions. You might reasonably have code -that calls chown(), for instance, and want to test how it's called rather -than worrying about setting up a fakeroot-type environment where chown() -will work. The obvious solution is to use `LD_PRELOAD` wrappers, but there -are various problems to overcome in practice: - - * You can only set up a new `LD_PRELOAD` by going through the run-time - linker; you can't just set it for a single in-process test case. - * Generating the preloaded wrapper involves a fair bit of boilerplate code. - * Having to write per-test mock code in C is inconvenient, and makes it - difficult to get information back out of the mock (such as "how often was - this function called, and with what arguments?"). - -The first problem can be solved by a decorator that knows how to run -individual tests in a subprocess. This is made somewhat more inconvenient -by the fact that there is no way for a context manager's `__enter__` method -to avoid executing the context-managed block other than by throwing an -exception, which makes it hard to silently avoid executing the test case in -the parent process, but we can work around this at the cost of an extra line -of code per invocation. - -For the rest, a combination of GI itself and ctypes can help. We can use GI -to keep track of argument and return types of the mocked C functions in a -reasonably sane way, by parsing header files. We're operating in the other -direction from how GI is normally used, so PyGObject can't deal with -bridging the two calling conventions for us. ctypes can: but we still need -to be careful! We have to construct the callback functions in the child -process, ensure that we keep references to them, and inject function -pointers into the preloaded library via specially-named helper functions; -until those function pointers are set up we must make sure to call the libc -functions instead (since some of them might be called during Python -startup). - -The combination of all of this allows us to bridge C functions somewhat -transparently into Python. This lets you supply a Python function or method -as the mock replacement for a C library function, making it much simpler to -record state. - -It's still not perfect: - - * We're using GI in an upside-down kind of way, and we specifically need - GIR files rather than typelibs so that we can extract the original C - type, so some fiddling is required for each new function you want to - mock. - - * The subprocess arrangements are unavoidably slow and it's possible that - they may cause problems with some test runners. - - * Some C functions (such as `stat`) tend to have multiple underlying entry - points in the C library which must be preloaded independently. - - * You have to be careful about how your libraries are linked, because `ld - -Wl,-Bsymbolic-functions` prevents `LD_PRELOAD` working for intra-library - calls. - - * `ctypes should return composite types from callbacks - `_. The least awful approach for now - seems to be to construct the composite type in question, stash a - reference to it forever, and then return a pointer to it as a void *; we - can only get away with this because tests are by nature relatively - short-lived. - - * The ctypes module's handling of 64-bit pointers is basically just awful. - The right answer is probably to use a different callback-generation - framework entirely (maybe extending PyGObject so that we can get at the - pieces we need), but I've hacked around it for now. - - * It doesn't appear to be possible to install mock replacements for - functions that are called directly from Python code using their GI - wrappers. You can work around this by simply patching the GI wrapper - instead, using `mock.patch`. - -I think the benefits, in terms of local clarity of tests, are worth the -downsides. -""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = ['GIMockTestCase'] - - -import contextlib -import ctypes -import fcntl -from functools import partial -import os -import pickle -import shutil -import subprocess -import sys -import tempfile -from textwrap import dedent -import traceback -import unittest -try: - from unittest import mock -except ImportError: - import mock -try: - import xml.etree.cElementTree as etree -except ImportError: - import xml.etree.ElementTree as etree - -from click.tests.gimock_types import Stat, Stat64 -from click.tests import get_executable - -# Borrowed from giscanner.girparser. -CORE_NS = "http://www.gtk.org/introspection/core/1.0" -C_NS = "http://www.gtk.org/introspection/c/1.0" -GLIB_NS = "http://www.gtk.org/introspection/glib/1.0" - - -def _corens(tag): - return '{%s}%s' % (CORE_NS, tag) - - -def _glibns(tag): - return '{%s}%s' % (GLIB_NS, tag) - - -def _cns(tag): - return '{%s}%s' % (C_NS, tag) - - -# Override some c:type annotations that g-ir-scanner gets a bit wrong. -_c_type_override = { - "passwd*": "struct passwd*", - "stat*": "struct stat*", - "stat64*": "struct stat64*", - } - - -# Mapping of GI type name -> ctypes type. -_typemap = { - "GError**": ctypes.c_void_p, - "gboolean": ctypes.c_int, - "gint": ctypes.c_int, - "gint*": ctypes.POINTER(ctypes.c_int), - "gint32": ctypes.c_int32, - "gpointer": ctypes.c_void_p, - "guint": ctypes.c_uint, - "guint8**": ctypes.POINTER(ctypes.POINTER(ctypes.c_uint8)), - "guint32": ctypes.c_uint32, - "none": None, - "utf8": ctypes.c_char_p, - "utf8*": ctypes.POINTER(ctypes.c_char_p), - } - - -class GIMockTestCase(unittest.TestCase): - def setUp(self): - super(GIMockTestCase, self).setUp() - self._gimock_temp_dir = tempfile.mkdtemp(prefix="gimock") - self.addCleanup(shutil.rmtree, self._gimock_temp_dir) - self._preload_func_refs = [] - self._composite_refs = [] - self._delegate_funcs = {} - - def tearDown(self): - self._preload_func_refs = [] - self._composite_refs = [] - self._delegate_funcs = {} - - def doCleanups(self): - # we do not want to run the cleanups twice, just run it in the parent - if "GIMOCK_SUBPROCESS" not in os.environ: - return super(GIMockTestCase, self).doCleanups() - - def _gir_get_type(self, obj): - ret = {} - arrayinfo = obj.find(_corens("array")) - if arrayinfo is not None: - typeinfo = arrayinfo.find(_corens("type")) - raw_ctype = arrayinfo.get(_cns("type")) - else: - typeinfo = obj.find(_corens("type")) - raw_ctype = typeinfo.get(_cns("type")) - gi_type = typeinfo.get("name") - if obj.get("direction", "in") == "out": - gi_type += "*" - if arrayinfo is not None: - gi_type += "*" - ret["gi"] = gi_type - ret["c"] = _c_type_override.get(raw_ctype, raw_ctype) - return ret - - def _parse_gir(self, path): - # A very, very crude GIR parser. We might have used - # giscanner.girparser, but it's not importable in Python 3 at the - # moment. - tree = etree.parse(path) - root = tree.getroot() - assert root.tag == _corens("repository") - assert root.get("version") == "1.2" - ns = root.find(_corens("namespace")) - assert ns is not None - funcs = {} - for func in ns.findall(_corens("function")): - name = func.get(_cns("identifier")) - # g-ir-scanner skips identifiers starting with "__", which we - # need in order to mock stat effectively. Work around this. - name = name.replace("under_under_", "__") - headers = None - for attr in func.findall(_corens("attribute")): - if attr.get("name") == "headers": - headers = attr.get("value") - break - rv = func.find(_corens("return-value")) - assert rv is not None - params = [] - paramnode = func.find(_corens("parameters")) - if paramnode is not None: - for param in paramnode.findall(_corens("parameter")): - params.append({ - "name": param.get("name"), - "type": self._gir_get_type(param), - }) - if func.get("throws", "0") == "1": - params.append({ - "name": "error", - "type": {"gi": "GError**", "c": "GError**"}, - }) - funcs[name] = { - "name": name, - "headers": headers, - "rv": self._gir_get_type(rv), - "params": params, - } - return funcs - - def _ctypes_type(self, gi_type): - return _typemap[gi_type["gi"]] - - def make_preloads(self, preloads): - rpreloads = [] - std_headers = set([ - "dlfcn.h", - # Not strictly needed, but convenient for ad-hoc debugging. - "stdio.h", - "stdint.h", - "stdlib.h", - "string.h", - "sys/types.h", - "unistd.h", - ]) - preload_headers = set() - funcs = self._parse_gir("click/tests/preload.gir") - for name, func in preloads.items(): - info = funcs[name] - rpreloads.append([info, func]) - headers = info["headers"] - if headers is not None: - preload_headers.update(headers.split(",")) - if "GIMOCK_SUBPROCESS" in os.environ: - return None, rpreloads - preloads_dir = os.path.join(self._gimock_temp_dir, "_preloads") - os.makedirs(preloads_dir) - c_path = os.path.join(preloads_dir, "gimockpreload.c") - with open(c_path, "w") as c: - print("#define _GNU_SOURCE", file=c) - for header in sorted(std_headers | preload_headers): - print("#include <%s>" % header, file=c) - print(file=c) - for info, _ in rpreloads: - conv = {} - conv["name"] = info["name"] - argtypes = [p["type"]["c"] for p in info["params"]] - argnames = [p["name"] for p in info["params"]] - conv["ret"] = info["rv"]["c"] - conv["bareproto"] = ", ".join(argtypes) - conv["proto"] = ", ".join( - "%s %s" % pair for pair in zip(argtypes, argnames)) - conv["args"] = ", ".join(argnames) - if conv["ret"] == "gchar*": - conv["need_strdup"] = "strdup" - else: - conv["need_strdup"] = "" - # The delegation scheme used here is needed because trying - # to pass pointers back and forward through ctypes is a - # recipe for having them truncated to 32 bits at the drop of - # a hat. This approach is less obvious but much safer. - print(dedent("""\ - typedef %(ret)s preloadtype_%(name)s (%(bareproto)s); - preloadtype_%(name)s *ctypes_%(name)s = (void *) 0; - preloadtype_%(name)s *real_%(name)s = (void *) 0; - static volatile int delegate_%(name)s = 0; - - extern void _gimock_init_%(name)s (preloadtype_%(name)s *f) - { - ctypes_%(name)s = f; - if (! real_%(name)s) { - /* Retry lookup in case the symbol wasn't - * resolvable until the program under test was - * loaded. - */ - dlerror (); - real_%(name)s = dlsym (RTLD_NEXT, \"%(name)s\"); - if (dlerror ()) _exit (1); - } - } - """) % conv, file=c) - if conv["ret"] == "void": - print(dedent("""\ - void %(name)s (%(proto)s) - { - if (ctypes_%(name)s) { - delegate_%(name)s = 0; - (*ctypes_%(name)s) (%(args)s); - if (! delegate_%(name)s) - return; - } - (*real_%(name)s) (%(args)s); - } - """) % conv, file=c) - else: - print(dedent("""\ - %(ret)s %(name)s (%(proto)s) - { - if (ctypes_%(name)s) { - %(ret)s ret; - delegate_%(name)s = 0; - ret = (*ctypes_%(name)s) (%(args)s); - if (! delegate_%(name)s) - return %(need_strdup)s(ret); - } - return (*real_%(name)s) (%(args)s); - } - """) % conv, file=c) - print(dedent("""\ - extern void _gimock_delegate_%(name)s (void) - { - delegate_%(name)s = 1; - } - """) % conv, file=c) - print(dedent("""\ - static void __attribute__ ((constructor)) - gimockpreload_init (void) - { - dlerror (); - """), file=c) - for info, _ in rpreloads: - name = info["name"] - print(" real_%s = dlsym (RTLD_NEXT, \"%s\");" % - (name, name), file=c) - print(" if (dlerror ()) _exit (1);", file=c) - print("}", file=c) - if "GIMOCK_PRELOAD_DEBUG" in os.environ: - with open(c_path) as c: - print(c.read()) - # TODO: Use libtool or similar rather than hardcoding gcc invocation. - lib_path = os.path.join(preloads_dir, "libgimockpreload.so") - cflags = subprocess.check_output([ - "pkg-config", "--cflags", "glib-2.0", "gee-0.8", "json-glib-1.0"], - universal_newlines=True).rstrip("\n").split() - subprocess.check_call([ - "gcc", "-O0", "-g", "-shared", "-fPIC", "-DPIC", "-I", "lib/click", - ] + cflags + [ - "-Wl,-soname", "-Wl,libgimockpreload.so", - c_path, "-ldl", "-o", lib_path, - ]) - return lib_path, rpreloads - - # Use as: - # with self.run_in_subprocess("func", ...) as (enter, preloads): - # enter() - # # test case body; preloads["func"] will be a mock.MagicMock - # # instance - @contextlib.contextmanager - def run_in_subprocess(self, *patches): - preloads = {} - for patch in patches: - preloads[patch] = mock.MagicMock() - if preloads: - lib_path, rpreloads = self.make_preloads(preloads) - else: - lib_path, rpreloads = None, None - - class ParentProcess(Exception): - pass - - def helper(lib_path, rpreloads): - if "GIMOCK_SUBPROCESS" in os.environ: - del os.environ["LD_PRELOAD"] - preload_lib = ctypes.cdll.LoadLibrary(lib_path) - delegate_cfunctype = ctypes.CFUNCTYPE(None) - for info, func in rpreloads: - signature = [info["rv"]] + [ - p["type"] for p in info["params"]] - signature = [self._ctypes_type(t) for t in signature] - cfunctype = ctypes.CFUNCTYPE(*signature) - init = getattr( - preload_lib, "_gimock_init_%s" % info["name"]) - cfunc = cfunctype(func) - self._preload_func_refs.append(cfunc) - init(cfunc) - delegate = getattr( - preload_lib, "_gimock_delegate_%s" % info["name"]) - self._delegate_funcs[info["name"]] = delegate_cfunctype( - delegate) - return - rfd, wfd = os.pipe() - # It would be cleaner to use subprocess.Popen(pass_fds=[wfd]), but - # that isn't available in Python 2.7. - if hasattr(os, "set_inheritable"): - os.set_inheritable(wfd, True) - else: - fcntl.fcntl(rfd, fcntl.F_SETFD, fcntl.FD_CLOEXEC) - args = get_executable() + [ - "-m", "unittest", - "%s.%s.%s" % ( - self.__class__.__module__, self.__class__.__name__, - self._testMethodName)] - env = os.environ.copy() - env["GIMOCK_SUBPROCESS"] = str(wfd) - if lib_path is not None: - env["LD_PRELOAD"] = lib_path - subp = subprocess.Popen(args, close_fds=False, env=env, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - os.close(wfd) - reader = os.fdopen(rfd, "rb") - stdout, stderr = subp.communicate() - exctype = pickle.load(reader) - # "normal" exit - if exctype is not None and issubclass(exctype, SystemExit): - pass - elif exctype is not None and issubclass(exctype, AssertionError): - print(stdout, file=sys.stdout) - print(stderr, file=sys.stderr) - raise AssertionError("Subprocess failed a test!") - elif exctype is not None or subp.returncode != 0: - print(stdout, file=sys.stdout) - print(stderr, file=sys.stderr) - raise Exception("Subprocess returned an error! (%s, %s)" % ( - exctype, subp.returncode)) - reader.close() - raise ParentProcess() - - try: - yield partial(helper, lib_path, rpreloads), preloads - if "GIMOCK_SUBPROCESS" in os.environ: - wfd = int(os.environ["GIMOCK_SUBPROCESS"]) - writer = os.fdopen(wfd, "wb") - # a sys.ext will generate a SystemExit exception, so we - # push it into the pipe so that the parent knows whats going on - pickle.dump(SystemExit, writer) - writer.flush() - sys.exit(0) - except ParentProcess: - pass - except Exception as e: - if "GIMOCK_SUBPROCESS" in os.environ: - wfd = int(os.environ["GIMOCK_SUBPROCESS"]) - writer = os.fdopen(wfd, "wb") - # It would be better to use tblib to pickle the traceback so - # that we can re-raise it properly from the parent process. - # Until that's packaged and available to us, just print the - # traceback and send the exception type. - print() - traceback.print_exc() - pickle.dump(type(e), writer) - writer.flush() - os._exit(1) - else: - raise - - def make_pointer(self, composite): - # Store a reference to a composite type and return a pointer to it, - # working around http://bugs.python.org/issue5710. - self._composite_refs.append(composite) - return ctypes.addressof(composite) - - def make_string(self, s): - # As make_pointer, but for a string. - copied = ctypes.create_string_buffer(s.encode()) - self._composite_refs.append(copied) - return ctypes.addressof(copied) - - def convert_pointer(self, composite_type, address): - # Return a ctypes composite type instance at a given address. - return composite_type.from_address(address) - - def convert_stat_pointer(self, name, address): - # As convert_pointer, but for a "struct stat *" or "struct stat64 *" - # depending on the wrapped function name. - stat_type = {"__xstat": Stat, "__xstat64": Stat64} - return self.convert_pointer(stat_type[name], address) - - def delegate_to_original(self, name): - # Cause the wrapper function to delegate to the original version - # after the callback returns. (Note that the callback still needs - # to return something type-compatible with the declared result type, - # although the return value will otherwise be ignored.) - self._delegate_funcs[name]() diff -Nru click-0.4.43+16.04.20160203/click/tests/gimock_types.py click-6.7/click/tests/gimock_types.py --- click-0.4.43+16.04.20160203/click/tests/gimock_types.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/gimock_types.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,89 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""A collection of variously hacky ctypes definitions for use with gimock.""" - -import ctypes - -from click.tests.config import ( - STAT_OFFSET_GID, - STAT_OFFSET_UID, - STAT64_OFFSET_GID, - STAT64_OFFSET_UID, - ) - - -class Passwd(ctypes.Structure): - _fields_ = [ - ("pw_name", ctypes.c_char_p), - ("pw_passwd", ctypes.c_char_p), - ("pw_uid", ctypes.c_uint32), - ("pw_gid", ctypes.c_uint32), - ("pw_gecos", ctypes.c_char_p), - ("pw_dir", ctypes.c_char_p), - ("pw_shell", ctypes.c_char_p), - ] - - -# TODO: This is pretty awful. The layout of "struct stat" is complicated -# enough that we have to use offsetof() in configure to pick out the fields -# we care about. Fortunately, we only care about a couple of fields, and -# since this is an output parameter it doesn't matter if our structure is -# too short (if we cared about this then we could use AC_CHECK_SIZEOF to -# figure it out). -class Stat(ctypes.Structure): - _pack_ = 1 - _fields_ = [] - _fields_.append( - ("pad0", ctypes.c_ubyte * min(STAT_OFFSET_UID, STAT_OFFSET_GID))) - if STAT_OFFSET_UID < STAT_OFFSET_GID: - _fields_.append(("st_uid", ctypes.c_uint32)) - pad = (STAT_OFFSET_GID - STAT_OFFSET_UID - - ctypes.sizeof(ctypes.c_uint32)) - assert pad >= 0 - if pad > 0: - _fields_.append(("pad1", ctypes.c_ubyte * pad)) - _fields_.append(("st_gid", ctypes.c_uint32)) - else: - _fields_.append(("st_gid", ctypes.c_uint32)) - pad = (STAT_OFFSET_UID - STAT_OFFSET_GID - - ctypes.sizeof(ctypes.c_uint32)) - assert pad >= 0 - if pad > 0: - _fields_.append(("pad1", ctypes.c_ubyte * pad)) - _fields_.append(("st_uid", ctypes.c_uint32)) - - -class Stat64(ctypes.Structure): - _pack_ = 1 - _fields_ = [] - _fields_.append( - ("pad0", ctypes.c_ubyte * min(STAT64_OFFSET_UID, STAT64_OFFSET_GID))) - if STAT64_OFFSET_UID < STAT64_OFFSET_GID: - _fields_.append(("st_uid", ctypes.c_uint32)) - pad = (STAT64_OFFSET_GID - STAT64_OFFSET_UID - - ctypes.sizeof(ctypes.c_uint32)) - assert pad >= 0 - if pad > 0: - _fields_.append(("pad1", ctypes.c_ubyte * pad)) - _fields_.append(("st_gid", ctypes.c_uint32)) - else: - _fields_.append(("st_gid", ctypes.c_uint32)) - pad = (STAT64_OFFSET_UID - STAT64_OFFSET_GID - - ctypes.sizeof(ctypes.c_uint32)) - assert pad >= 0 - if pad > 0: - _fields_.append(("pad1", ctypes.c_ubyte * pad)) - _fields_.append(("st_uid", ctypes.c_uint32)) diff -Nru click-0.4.43+16.04.20160203/click/tests/helpers.py click-6.7/click/tests/helpers.py --- click-0.4.43+16.04.20160203/click/tests/helpers.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/helpers.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,357 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# This file contains code from Python 3.3, released under the Python license -# (http://docs.python.org/3/license.html). - -"""Testing helpers.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'TestCase', - 'mkfile', - 'touch', - ] - - -import contextlib -from functools import wraps -import json -import os -import re -import shutil -import sys -import tempfile -import unittest -try: - from unittest import mock -except ImportError: - import mock - -from gi.repository import Click, GLib - -from click.tests import gimock - - -def disable_logging(func): - """Decorator to disable logging e.g. during a test""" - @wraps(func) - def wrapper(*args, **kwargs): - import logging - logging.disable(logging.CRITICAL) - try: - return func(*args, **kwargs) - finally: - logging.disable(logging.NOTSET) - return wrapper - - -def make_installed_click(db, db_dir, package="test-1", version="1.0", - json_data={}, make_current=True, user="@all"): - """Create a fake installed click package for the given db/db_dir""" - json_data["name"] = package - json_data["version"] = version - with mkfile_utf8(os.path.join( - db_dir, package, version, ".click", "info", - "%s.manifest" % package)) as f: - json.dump(json_data, f, ensure_ascii=False) - if make_current: - os.symlink( - version, os.path.join(db_dir, package, "current")) - if user == "@all": - registry = Click.User.for_all_users(db) - else: - registry = Click.User.for_user(db, user) - registry.set_version(package, version) - return os.path.join(db_dir, package, version) - - -class TestCase(gimock.GIMockTestCase): - def setUp(self): - super(TestCase, self).setUp() - self.temp_dir = None - self.save_env = dict(os.environ) - self.maxDiff = None - - def tearDown(self): - for key in set(os.environ) - set(self.save_env): - del os.environ[key] - for key, value in os.environ.items(): - if value != self.save_env[key]: - os.environ[key] = self.save_env[key] - for key in set(self.save_env) - set(os.environ): - os.environ[key] = self.save_env[key] - - def use_temp_dir(self): - if self.temp_dir is not None: - return self.temp_dir - self.temp_dir = tempfile.mkdtemp(prefix="click") - self.addCleanup(shutil.rmtree, self.temp_dir) - return self.temp_dir - - # Monkey-patch for Python 2/3 compatibility. - if not hasattr(unittest.TestCase, 'assertCountEqual'): - assertCountEqual = unittest.TestCase.assertItemsEqual - if not hasattr(unittest.TestCase, 'assertRegex'): - assertRegex = unittest.TestCase.assertRegexpMatches - # Renamed in Python 3.2 to omit the trailing 'p'. - if not hasattr(unittest.TestCase, 'assertRaisesRegex'): - assertRaisesRegex = unittest.TestCase.assertRaisesRegexp - - def assertRaisesGError(self, domain_name, code, callableObj, - *args, **kwargs): - with self.assertRaises(GLib.GError) as cm: - callableObj(*args, **kwargs) - self.assertEqual(domain_name, cm.exception.domain) - self.assertEqual(code, cm.exception.code) - - def assertRaisesFileError(self, code, callableObj, *args, **kwargs): - self.assertRaisesGError( - "g-file-error-quark", code, callableObj, *args, **kwargs) - - def assertRaisesDatabaseError(self, code, callableObj, *args, **kwargs): - self.assertRaisesGError( - "click_database_error-quark", code, callableObj, *args, **kwargs) - - def assertRaisesFrameworkError(self, code, callableObj, *args, **kwargs): - self.assertRaisesGError( - "click_framework_error-quark", code, callableObj, *args, **kwargs) - - def assertRaisesHooksError(self, code, callableObj, *args, **kwargs): - self.assertRaisesGError( - "click_hooks_error-quark", code, callableObj, *args, **kwargs) - - def assertRaisesQueryError(self, code, callableObj, *args, **kwargs): - self.assertRaisesGError( - "click_query_error-quark", code, callableObj, *args, **kwargs) - - def assertRaisesUserError(self, code, callableObj, *args, **kwargs): - self.assertRaisesGError( - "click_user_error-quark", code, callableObj, *args, **kwargs) - - def _setup_frameworks(self, preloads, frameworks_dir=None, frameworks=[]): - frameworks_dir = self._create_mock_framework_dir(frameworks_dir) - shutil.rmtree(frameworks_dir, ignore_errors=True) - for framework in frameworks: - self._create_mock_framework_file(framework) - preloads["click_get_frameworks_dir"].side_effect = ( - lambda: self.make_string(frameworks_dir)) - - def _create_mock_framework_dir(self, frameworks_dir=None): - if frameworks_dir is None: - frameworks_dir = os.path.join(self.temp_dir, "frameworks") - patcher = mock.patch('click.paths.frameworks_dir', frameworks_dir) - patcher.start() - self.addCleanup(patcher.stop) - Click.ensuredir(frameworks_dir) - return frameworks_dir - - def _create_mock_framework_file(self, framework_name): - self.use_temp_dir() - self._create_mock_framework_dir() - r = r'(?P[a-z]+-sdk)-(?P[0-9.]+)(-[a-z0-9-]+)?' - match = re.match(r, framework_name) - if match is None: - name = "unknown" - ver = "1.0" - else: - name = match.group("name") - ver = match.group("ver") - framework_filename = os.path.join( - self.temp_dir, "frameworks", - "{0}.framework".format(framework_name)) - with open(framework_filename, "w") as f: - f.write("Base-Name: {0}\n".format(name)) - f.write("Base-Version: {0}\n".format(ver)) - - -if not hasattr(mock, "call"): - # mock 0.7.2, the version in Ubuntu 12.04 LTS, lacks mock.ANY and - # mock.call. Since it's so convenient, monkey-patch a partial backport - # (from Python 3.3 unittest.mock) into place here. - class _ANY(object): - "A helper object that compares equal to everything." - - def __eq__(self, other): - return True - - def __ne__(self, other): - return False - - def __repr__(self): - return '' - - mock.ANY = _ANY() - - class _Call(tuple): - """ - A tuple for holding the results of a call to a mock, either in the form - `(args, kwargs)` or `(name, args, kwargs)`. - - If args or kwargs are empty then a call tuple will compare equal to - a tuple without those values. This makes comparisons less verbose:: - - _Call(('name', (), {})) == ('name',) - _Call(('name', (1,), {})) == ('name', (1,)) - _Call(((), {'a': 'b'})) == ({'a': 'b'},) - - The `_Call` object provides a useful shortcut for comparing with call:: - - _Call(((1, 2), {'a': 3})) == call(1, 2, a=3) - _Call(('foo', (1, 2), {'a': 3})) == call.foo(1, 2, a=3) - - If the _Call has no name then it will match any name. - """ - def __new__(cls, value=(), name=None, parent=None, two=False, - from_kall=True): - name = '' - args = () - kwargs = {} - _len = len(value) - if _len == 3: - name, args, kwargs = value - elif _len == 2: - first, second = value - if isinstance(first, str): - name = first - if isinstance(second, tuple): - args = second - else: - kwargs = second - else: - args, kwargs = first, second - elif _len == 1: - value, = value - if isinstance(value, str): - name = value - elif isinstance(value, tuple): - args = value - else: - kwargs = value - - if two: - return tuple.__new__(cls, (args, kwargs)) - - return tuple.__new__(cls, (name, args, kwargs)) - - def __init__(self, value=(), name=None, parent=None, two=False, - from_kall=True): - self.name = name - self.parent = parent - self.from_kall = from_kall - - def __eq__(self, other): - if other is mock.ANY: - return True - try: - len_other = len(other) - except TypeError: - return False - - self_name = '' - if len(self) == 2: - self_args, self_kwargs = self - else: - self_name, self_args, self_kwargs = self - - other_name = '' - if len_other == 0: - other_args, other_kwargs = (), {} - elif len_other == 3: - other_name, other_args, other_kwargs = other - elif len_other == 1: - value, = other - if isinstance(value, tuple): - other_args = value - other_kwargs = {} - elif isinstance(value, str): - other_name = value - other_args, other_kwargs = (), {} - else: - other_args = () - other_kwargs = value - else: - # len 2 - # could be (name, args) or (name, kwargs) or (args, kwargs) - first, second = other - if isinstance(first, str): - other_name = first - if isinstance(second, tuple): - other_args, other_kwargs = second, {} - else: - other_args, other_kwargs = (), second - else: - other_args, other_kwargs = first, second - - if self_name and other_name != self_name: - return False - - # this order is important for ANY to work! - return (other_args, other_kwargs) == (self_args, self_kwargs) - - def __ne__(self, other): - return not self.__eq__(other) - - def __call__(self, *args, **kwargs): - if self.name is None: - return _Call(('', args, kwargs), name='()') - - name = self.name + '()' - return _Call((self.name, args, kwargs), name=name, parent=self) - - def __getattr__(self, attr): - if self.name is None: - return _Call(name=attr, from_kall=False) - name = '%s.%s' % (self.name, attr) - return _Call(name=name, parent=self, from_kall=False) - - mock.call = _Call(from_kall=False) - - -@contextlib.contextmanager -def mkfile(path, mode="w"): - Click.ensuredir(os.path.dirname(path)) - with open(path, mode) as f: - yield f - - -@contextlib.contextmanager -def mkfile_utf8(path, mode="w"): - Click.ensuredir(os.path.dirname(path)) - if sys.version < "3": - import codecs - with codecs.open(path, mode, "UTF-8") as f: - yield f - else: - # io.open is available from Python 2.6, but we only use it with - # Python 3 because it raises exceptions when passed bytes. - import io - with io.open(path, mode, encoding="UTF-8") as f: - yield f - - -def touch(path): - with mkfile(path, mode="a"): - pass - - -def make_file_with_content(filename, content, mode=0o644): - """Create a file with the given content and mode""" - Click.ensuredir(os.path.dirname(filename)) - with open(filename, "w") as f: - f.write(content) - os.chmod(filename, mode) diff -Nru click-0.4.43+16.04.20160203/click/tests/__init__.py click-6.7/click/tests/__init__.py --- click-0.4.43+16.04.20160203/click/tests/__init__.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/__init__.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,48 +0,0 @@ -from __future__ import print_function - -import os -import sys - -from click.tests import config - - -def _append_env_path(envname, value): - if envname in os.environ: - if value in os.environ[envname].split(":"): - return False - os.environ[envname] = "%s:%s" % (os.environ[envname], value) - else: - os.environ[envname] = value - return True - - -def get_executable(): - """Get python executable (respecting if python-coverage was used)""" - coverage_executable = sys.executable+"-coverage" - if "coverage" in sys.modules and os.path.isfile(coverage_executable): - return [coverage_executable, "run", "-p"] - return [sys.executable] - - -# Don't do any of this in interactive mode. -if not hasattr(sys, "ps1"): - _lib_click_dir = os.path.join(config.abs_top_builddir, "lib", "click") - changed = False - if _append_env_path( - "LD_LIBRARY_PATH", os.path.join(_lib_click_dir, ".libs")): - changed = True - if _append_env_path("GI_TYPELIB_PATH", _lib_click_dir): - changed = True - if changed: - coverage_executable = get_executable() - # We have to re-exec ourselves to get the dynamic loader to pick up - # the new value of LD_LIBRARY_PATH. - if "-m unittest" in sys.argv[0]: - # unittest does horrible things to sys.argv in the name of - # "usefulness", making the re-exec more painful than it needs to - # be. - os.execvp( - coverage_executable[0], coverage_executable + ["-m", "unittest"] + sys.argv[1:]) - else: - os.execvp(coverage_executable[0], coverage_executable + sys.argv) - os._exit(1) Binary files /tmp/tmp5CGTrm/S7dHEMVmjm/click-0.4.43+16.04.20160203/click/tests/integration/data/evil-keyring/pubring.gpg and /tmp/tmp5CGTrm/FrCzimMInt/click-6.7/click/tests/integration/data/evil-keyring/pubring.gpg differ Binary files /tmp/tmp5CGTrm/S7dHEMVmjm/click-0.4.43+16.04.20160203/click/tests/integration/data/evil-keyring/secring.gpg and /tmp/tmp5CGTrm/FrCzimMInt/click-6.7/click/tests/integration/data/evil-keyring/secring.gpg differ Binary files /tmp/tmp5CGTrm/S7dHEMVmjm/click-0.4.43+16.04.20160203/click/tests/integration/data/evil-keyring/trustdb.gpg and /tmp/tmp5CGTrm/FrCzimMInt/click-6.7/click/tests/integration/data/evil-keyring/trustdb.gpg differ Binary files /tmp/tmp5CGTrm/S7dHEMVmjm/click-0.4.43+16.04.20160203/click/tests/integration/data/origin-keyring/pubring.gpg and /tmp/tmp5CGTrm/FrCzimMInt/click-6.7/click/tests/integration/data/origin-keyring/pubring.gpg differ Binary files /tmp/tmp5CGTrm/S7dHEMVmjm/click-0.4.43+16.04.20160203/click/tests/integration/data/origin-keyring/secring.gpg and /tmp/tmp5CGTrm/FrCzimMInt/click-6.7/click/tests/integration/data/origin-keyring/secring.gpg differ Binary files /tmp/tmp5CGTrm/S7dHEMVmjm/click-0.4.43+16.04.20160203/click/tests/integration/data/origin-keyring/trustdb.gpg and /tmp/tmp5CGTrm/FrCzimMInt/click-6.7/click/tests/integration/data/origin-keyring/trustdb.gpg differ diff -Nru click-0.4.43+16.04.20160203/click/tests/integration/helpers.py click-6.7/click/tests/integration/helpers.py --- click-0.4.43+16.04.20160203/click/tests/integration/helpers.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/integration/helpers.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,124 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Integration tests helper for the click CLI interface.""" - -import contextlib -import glob -import json -import os -import random -import shutil -import string -import subprocess -import tempfile -import unittest - - -def require_root(): - if os.getuid() != 0: - raise unittest.SkipTest("This test needs to run as root") - - -def require_network(): - try: - if subprocess.call(["ping", "-c1", "archive.ubuntu.com"]) != 0: - raise unittest.SkipTest("Need network") - except Exception: - pass - - -@contextlib.contextmanager -def chdir(target): - curdir = os.getcwd() - os.chdir(target) - try: - yield - finally: - os.chdir(curdir) - - -def cmdline_for_user(username): - """Helper to get the click commandline for the given username""" - if username == "@all": - user = "--all-users" - else: - user = "--user=%s" % username - return user - - -class ClickTestCase(unittest.TestCase): - - @classmethod - def setUpClass(cls): - if "TEST_INTEGRATION" not in os.environ: - raise unittest.SkipTest("Skipping integration tests") - cls.click_binary = os.environ.get("CLICK_BINARY", "/usr/bin/click") - - def setUp(self): - super(ClickTestCase, self).setUp() - self.temp_dir = tempfile.mkdtemp() - - def tearDown(self): - super(ClickTestCase, self).tearDown() - # we force the cleanup before removing the tempdir so that stuff - # in temp_dir is still available - self.doCleanups() - shutil.rmtree(self.temp_dir) - - def click_install(self, path_to_click, click_name, username, - allow_unauthenticated=True): - cmd = [self.click_binary, "install", cmdline_for_user(username)] - if allow_unauthenticated: - cmd.append("--allow-unauthenticated") - cmd.append(path_to_click) - subprocess.check_call(cmd) - self.addCleanup(self.click_unregister, click_name, username) - - def click_unregister(self, click_name, username): - subprocess.check_call( - [self.click_binary, "unregister", cmdline_for_user(username), - click_name]) - - def _create_manifest(self, target, name, version, framework, hooks={}): - with open(target, "w") as f: - json.dump({ - 'name': name, - 'version': str(version), - 'maintainer': 'Foo Bar ', - 'title': 'test title', - 'framework': framework, - 'hooks': hooks, - }, f) - - def _make_click(self, name=None, version=1.0, - framework="ubuntu-sdk-13.10", hooks={}): - if name is None: - name = "com.example.%s" % "".join( - random.choice(string.ascii_lowercase) for i in range(10)) - tmpdir = tempfile.mkdtemp() - self.addCleanup(lambda: shutil.rmtree(tmpdir)) - clickdir = os.path.join(tmpdir, name) - os.makedirs(clickdir) - self._create_manifest(os.path.join(clickdir, "manifest.json"), - name, version, framework, hooks) - with open(os.path.join(clickdir, "README"), "w") as f: - f.write("hello world!") - with chdir(tmpdir), open(os.devnull, "w") as devnull: - subprocess.call( - [self.click_binary, "build", clickdir], stdout=devnull) - generated_clicks = glob.glob(os.path.join(tmpdir, "*.click")) - self.assertEqual(len(generated_clicks), 1) - return generated_clicks[0] diff -Nru click-0.4.43+16.04.20160203/click/tests/integration/test_build_core_apps.py click-6.7/click/tests/integration/test_build_core_apps.py --- click-0.4.43+16.04.20160203/click/tests/integration/test_build_core_apps.py 2016-02-03 11:17:44.000000000 +0000 +++ click-6.7/click/tests/integration/test_build_core_apps.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,131 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Integration tests for the click chroot feature.""" - -from glob import glob -import json -import os -import shutil -import subprocess - -from six import with_metaclass - -from .helpers import ( - chdir, - require_network, - require_root, - ClickTestCase, -) - -# the branches we want to testbuild -TEST_BUILD_BRANCHES = [ - "lp:camera-app", - "lp:notes-app", -] - -# command to "configure" -CORE_APP_CONFIGURE_CMD = [ - "cmake", "..", "-DCLICK_MODE=on", "-DINSTALL_TESTS=off"] - -# command to make install -CLICK_TARGET_DIR = "click-package" -CORE_APP_MAKE_CMD = [ - "make", "DESTDIR=%s" % CLICK_TARGET_DIR, "install"] - -# architectures with native- or cross-compiling support for armhf -ALLOW_ARCHITECTURES = ["amd64", "arm64", "armhf", "i386"] - - -def find_manifest(start_dir): - """Find a click manifest.json{,.in} under the given directory""" - for path, dirs, files in os.walk(start_dir): - for needle in ["manifest.json", "manifest.json.in"]: - if needle in files: - return os.path.join(path, needle) - return None - - -class AddBranchTestFunctions(type): - """Metaclass that creates one test for each branch""" - def __new__(cls, name, bases, dct): - for branch in TEST_BUILD_BRANCHES: - name = "test_build_%s" % branch.split(":")[1].replace("-", "_") - dct[name] = lambda self: self._testbuild_branch(branch) - return type.__new__(cls, name, bases, dct) - - -class TestBuildCoreApps(with_metaclass(AddBranchTestFunctions, ClickTestCase)): - - @classmethod - def setUpClass(cls): - super(TestBuildCoreApps, cls).setUpClass() - require_root() - require_network() - - def _run_in_chroot(self, cmd): - """Run the given cmd in a click chroot""" - return subprocess.check_call(self.chroot_cmd + ["run"] + cmd) - - def _set_arch_and_framework_from_manifest(self, manifest): - with open(manifest) as f: - data = json.load(f) - self.arch = data["architecture"] - self.framework = data["framework"] - - @property - def chroot_cmd(self): - return [ - self.click_binary, "chroot", "-a", self.arch, "-f", self.framework] - - def _ensure_click_chroot(self): - if subprocess.call(self.chroot_cmd + ["exists"]) != 0: - subprocess.check_call(self.chroot_cmd + ["create"]) - - def configure(self): - self._run_in_chroot(CORE_APP_CONFIGURE_CMD) - - def make(self): - self._run_in_chroot(CORE_APP_MAKE_CMD) - - def create_click(self): - subprocess.check_call( - [self.click_binary, "build", CLICK_TARGET_DIR]) - # we expect exactly one click - self.assertEqual(len(glob("*.click")), 1) - - def _testbuild_branch(self, branch): - system_arch = subprocess.check_output( - ["dpkg", "--print-architecture"], universal_newlines=True).strip() - if system_arch not in ALLOW_ARCHITECTURES: - self.skipTest("%s has no armhf build support" % system_arch) - # get and parse - branch_dir = branch[len("lp:"):] - build_dir = os.path.join(branch_dir, "build-tree") - if os.path.exists(branch_dir): - subprocess.check_call(["bzr", "pull"], cwd=branch_dir) - else: - subprocess.check_call(["bzr", "branch", branch]) - manifest = find_manifest(branch_dir) - # build it - self._set_arch_and_framework_from_manifest(manifest) - if os.path.exists(build_dir): - shutil.rmtree(build_dir) - os.makedirs(build_dir) - with chdir(build_dir): - self._ensure_click_chroot() - self.configure() - self.make() - self.create_click() diff -Nru click-0.4.43+16.04.20160203/click/tests/integration/test_build.py click-6.7/click/tests/integration/test_build.py --- click-0.4.43+16.04.20160203/click/tests/integration/test_build.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/integration/test_build.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,26 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Integration tests for the click CLI build command.""" - -import os - -from .helpers import ClickTestCase - - -class TestBuild(ClickTestCase): - def test_build(self): - path_to_click = self._make_click() - self.assertTrue(os.path.exists(path_to_click)) diff -Nru click-0.4.43+16.04.20160203/click/tests/integration/test_buildsource.py click-6.7/click/tests/integration/test_buildsource.py --- click-0.4.43+16.04.20160203/click/tests/integration/test_buildsource.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/integration/test_buildsource.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,51 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Integration tests for the click CLI buildsource command.""" - -import os -import shutil -import subprocess -import tarfile -import tempfile - -from .helpers import ( - chdir, - ClickTestCase, -) - - -class TestBuildSource(ClickTestCase): - - def test_buildsource(self): - temp_dir = tempfile.mkdtemp() - self.addCleanup(shutil.rmtree, temp_dir) - with chdir(temp_dir): - with open(os.path.join(temp_dir, "README"), "w") as f: - f.write("I'm a source package") - os.mkdir(os.path.join(temp_dir, ".git")) - os.mkdir(os.path.join(temp_dir, ".bzr")) - os.mkdir(os.path.join(temp_dir, ".normal")) - self._create_manifest(os.path.join(temp_dir, "manifest.json"), - "srcfoo", "1.2", "ubuntu-sdk-13.10") - subprocess.check_call( - [self.click_binary, "buildsource", temp_dir], - universal_newlines=True) - # ensure we have the content we expect - source_file = "srcfoo_1.2.tar.gz" - tar = tarfile.open(source_file) - self.assertEqual( - sorted(tar.getnames()), - sorted([".", "./.normal", "./manifest.json", "./README"])) diff -Nru click-0.4.43+16.04.20160203/click/tests/integration/test_chroot.py click-6.7/click/tests/integration/test_chroot.py --- click-0.4.43+16.04.20160203/click/tests/integration/test_chroot.py 2016-02-03 11:17:44.000000000 +0000 +++ click-6.7/click/tests/integration/test_chroot.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,100 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Integration tests for the click chroot feature.""" - -import subprocess -import unittest - -from .helpers import ( - require_network, - require_root, - ClickTestCase, -) - -# architectures present in 14.04 (current default framework) -ALLOW_ARCHITECTURES = [ - "amd64", "arm64", "armhf", "i386", "powerpc", "ppc64el"] - - -def skipUnlessAllowedArchitecture(): - system_arch = subprocess.check_output( - ["dpkg", "--print-architecture"], universal_newlines=True).strip() - if system_arch in ALLOW_ARCHITECTURES: - return lambda func: func - else: - return unittest.skip("%s does not exist in 14.04") - - -@skipUnlessAllowedArchitecture() -class TestChroot(ClickTestCase): - - @classmethod - def command(cls, arch, *args): - return [cls.click_binary, "chroot", "-a", arch] + list(args) - - @classmethod - def setUpClass(cls): - super(TestChroot, cls).setUpClass() - require_root() - require_network() - cls.arch = subprocess.check_output( - ["dpkg", "--print-architecture"], universal_newlines=True).strip() - subprocess.check_call(cls.command(cls.arch, "create")) - - @classmethod - def tearDownClass(cls): - subprocess.check_call(cls.command(cls.arch, "destroy")) - - def test_upgrade(self): - subprocess.check_call(self.command(self.arch, "upgrade")) - - def test_install(self): - subprocess.check_call(self.command(self.arch, "install", "apt-utils")) - - def test_run(self): - output = subprocess.check_output( - self.command(self.arch, "run", "echo", "hello world"), - universal_newlines=True) - self.assertEqual(output, "hello world\n") - - def test_maint(self): - output = subprocess.check_output( - self.command(self.arch, "maint", "id"), - universal_newlines=True) - self.assertEqual(output, "uid=0(root) gid=0(root) groups=0(root)\n") - - def test_exists_ok(self): - subprocess.check_call(self.command(self.arch, "exists")) - - def test_exists_no(self): - with self.assertRaises(subprocess.CalledProcessError): - subprocess.check_call( - self.command("arch-that-does-not-exist", "exists")) - - -class TestChrootName(TestChroot): - """Run the chroot tests again with a different --name.""" - - @classmethod - def command(cls, arch, *args): - return super(TestChrootName, cls).command( - arch, "-n", "testname", *args) - - def test_exists_different_name_fails(self): - # "click chroot exists" fails for a non-existent name. - with self.assertRaises(subprocess.CalledProcessError): - subprocess.check_call(super(TestChrootName, self).command( - self.arch, "-n", "testname2", "exists")) diff -Nru click-0.4.43+16.04.20160203/click/tests/integration/test_contents.py click-6.7/click/tests/integration/test_contents.py --- click-0.4.43+16.04.20160203/click/tests/integration/test_contents.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/integration/test_contents.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,33 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Integration tests for the click CLI contents command.""" - -import re -import subprocess - -from .helpers import ClickTestCase - - -class TestContents(ClickTestCase): - def test_contents(self): - name = "com.example.contents" - path_to_click = self._make_click(name) - output = subprocess.check_output([ - self.click_binary, "contents", path_to_click], - universal_newlines=True) - self.assertTrue(re.search( - r'-rw-r[-w]-r-- root/root\s+[0-9]+\s+[0-9-]+ [0-9:]+ ./README', - output)) diff -Nru click-0.4.43+16.04.20160203/click/tests/integration/test_frameworks.py click-6.7/click/tests/integration/test_frameworks.py --- click-0.4.43+16.04.20160203/click/tests/integration/test_frameworks.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/integration/test_frameworks.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,34 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Integration tests for the click CLI frameworks command.""" - -import os -import subprocess - -from .helpers import ClickTestCase - - -class TestFrameworks(ClickTestCase): - def setUp(self): - super(TestFrameworks, self).setUp() - if (not os.path.exists("/usr/share/click/frameworks") or - not os.listdir("/usr/share/click/frameworks")): - self.skipTest("Please install ubuntu-sdk-libs") - - def test_framework_list(self): - output = subprocess.check_output([ - self.click_binary, "framework", "list"], universal_newlines=True) - self.assertTrue("ubuntu-sdk-" in output) diff -Nru click-0.4.43+16.04.20160203/click/tests/integration/test_hook.py click-6.7/click/tests/integration/test_hook.py --- click-0.4.43+16.04.20160203/click/tests/integration/test_hook.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/integration/test_hook.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Integration tests for the click hook feature.""" - -import os -import subprocess -from textwrap import dedent - -from .helpers import ( - ClickTestCase, - require_root, -) - - -class TestHook(ClickTestCase): - - @classmethod - def setUpClass(cls): - super(TestHook, cls).setUpClass() - require_root() - - def _make_hook(self, name): - hook_fname = "/usr/share/click/hooks/%s.hook" % name - canary_fname = os.path.join(self.temp_dir, "canary.sh") - canary_log = os.path.join(self.temp_dir, "canary.log") - with open(hook_fname, "w") as f: - f.write(dedent("""\ - Pattern: ${home}/${id}.test-hook - User-Level: yes - Exec: %s - Hook-Name: %s - """ % (canary_fname, name))) - with open(canary_fname, "w") as f: - f.write(dedent("""\ - #!/bin/sh - echo "i-hook-you-up" >> %s - """ % canary_log)) - os.chmod(canary_fname, 0o755) - return hook_fname, canary_log - - def test_hook_install_user(self): - # build/install the hook - hook_name = "clicktesthook" - hook_file, hook_log = self._make_hook(hook_name) - self.addCleanup(os.unlink, hook_file) - subprocess.check_call( - [self.click_binary, "hook", "install", hook_name]) - self.addCleanup( - subprocess.check_call, [self.click_binary, "hook", "remove", - hook_name]) - # make click that uses the hook - hooks = {'app1': {hook_name: 'README'}} - click_pkg_name = "com.example.hook-1" - click_pkg = self._make_click( - click_pkg_name, framework="", hooks=hooks) - user = os.environ.get("USER", "root") - self.click_install(click_pkg, click_pkg_name, user) - # ensure we have the hook - generated_hook_file = os.path.expanduser( - "~/com.example.hook-1_app1_1.0.test-hook") - self.assertTrue(os.path.exists(generated_hook_file)) - self.assertEqual( - os.path.realpath(generated_hook_file), - "/opt/click.ubuntu.com/com.example.hook-1/1.0/README") - with open(hook_log) as f: - hook_log_content = f.read().strip() - self.assertEqual("i-hook-you-up", hook_log_content) diff -Nru click-0.4.43+16.04.20160203/click/tests/integration/test_info.py click-6.7/click/tests/integration/test_info.py --- click-0.4.43+16.04.20160203/click/tests/integration/test_info.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/integration/test_info.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,65 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Integration tests for the click CLI info command.""" - -import json -import os -import subprocess - -from .helpers import ClickTestCase - - -class TestInfo(ClickTestCase): - def test_info_from_path(self): - name = "com.example.foo" - path_to_click = self._make_click(name) - output = subprocess.check_output([ - self.click_binary, "info", path_to_click], universal_newlines=True) - self.assertEqual(name, json.loads(output)["name"]) - - def test_info_installed_click(self): - name = "com.example.foo" - user = os.environ.get("USER", "root") - path_to_click = self._make_click(name, framework="") - self.click_install(path_to_click, name, user) - output = subprocess.check_output([ - self.click_binary, "info", name], universal_newlines=True) - self.assertEqual(json.loads(output)["name"], name) - - def test_info_file_in_package(self): - name = "org.example.info" - version = "1.0" - click_pkg = self._make_click(name=name, version=version, framework="") - subprocess.check_call( - [self.click_binary, "install", "--allow-unauthenticated", - "--all-users", click_pkg]) - self.addCleanup( - subprocess.check_call, - [self.click_binary, "unregister", "--all-users", name]) - output = subprocess.check_output( - [self.click_binary, "info", - "/opt/click.ubuntu.com/%s/%s/README" % (name, version)], - universal_newlines=True) - self.assertEqual(name, json.loads(output)["name"]) - - def test_info_different_extension(self): - name = "org.example.info" - raw_path = self._make_click(name) - path = "%s.extra" % raw_path - os.rename(raw_path, path) - output = subprocess.check_output([ - self.click_binary, "info", path], universal_newlines=True) - self.assertEqual(name, json.loads(output)["name"]) diff -Nru click-0.4.43+16.04.20160203/click/tests/integration/test_install.py click-6.7/click/tests/integration/test_install.py --- click-0.4.43+16.04.20160203/click/tests/integration/test_install.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/integration/test_install.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,118 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Integration tests for the click install feature.""" - -import subprocess - -from .helpers import ( - require_root, - ClickTestCase, -) - - -def add_user(name): - subprocess.check_call(["useradd", name]) - return name - - -def del_user(name): - subprocess.check_call(["userdel", "-r", name]) - - -class TestClickInstall(ClickTestCase): - - @classmethod - def setUpClass(cls): - super(TestClickInstall, cls).setUpClass() - require_root() - cls.USER_1 = add_user("click-test-user-1") - cls.USER_2 = add_user("click-test-user-2") - - @classmethod - def tearDownClass(cls): - super(TestClickInstall, cls).tearDownClass() - del_user(cls.USER_1) - del_user(cls.USER_2) - - def test_install_for_single_user(self): - name = "foo-1" - click_pkg = self._make_click(name=name, framework="") - # install it - self.click_install(click_pkg, name, self.USER_1) - # ensure that user-1 has it - output = subprocess.check_output([ - "sudo", "-u", self.USER_1, - self.click_binary, "list"], universal_newlines=True) - self.assertEqual(output, "%s\t1.0\n" % name) - # but not user-2 - output = subprocess.check_output([ - "sudo", "-u", self.USER_2, - self.click_binary, "list"], universal_newlines=True) - self.assertEqual(output, "") - # and that we can see it with the --user option - output = subprocess.check_output( - [self.click_binary, "list", "--user=%s" % self.USER_1], - universal_newlines=True) - self.assertEqual(output, "%s\t1.0\n" % name) - - def test_install_for_single_user_and_register(self): - name = "foo-1" - click_pkg = self._make_click(name=name, framework="") - self.click_install(click_pkg, name, self.USER_1) - # not available for user2 - output = subprocess.check_output([ - "sudo", "-u", self.USER_2, - self.click_binary, "list"], universal_newlines=True) - self.assertEqual(output, "") - # register it - subprocess.check_call( - [self.click_binary, "register", "--user=%s" % self.USER_2, - name, "1.0", ]) - self.addCleanup(self.click_unregister, name, self.USER_2) - # and ensure its available for user2 - output = subprocess.check_output([ - "sudo", "-u", self.USER_2, - self.click_binary, "list"], universal_newlines=True) - self.assertEqual(output, "%s\t1.0\n" % name) - - def test_install_for_all_users(self): - name = "foo-2" - click_pkg = self._make_click(name=name, framework="") - self.click_install(click_pkg, name, "@all") - # ensure all users see it - for user in (self.USER_1, self.USER_2): - output = subprocess.check_output( - ["sudo", "-u", user, self.click_binary, "list"], - universal_newlines=True) - self.assertEqual(output, "%s\t1.0\n" % name) - - def test_pkgdir_after_install(self): - name = "foo-3" - click_pkg = self._make_click(name=name, version="1.2", framework="") - self.click_install(click_pkg, name, "@all") - # from the path - output = subprocess.check_output( - [self.click_binary, "pkgdir", - "/opt/click.ubuntu.com/%s/1.2/README" % name], - universal_newlines=True).strip() - self.assertEqual(output, "/opt/click.ubuntu.com/%s/1.2" % name) - # now test from the click package name - output = subprocess.check_output( - [self.click_binary, "pkgdir", name], - universal_newlines=True).strip() - # note that this is different from above - self.assertEqual( - output, "/opt/click.ubuntu.com/.click/users/@all/%s" % name) diff -Nru click-0.4.43+16.04.20160203/click/tests/integration/test_list.py click-6.7/click/tests/integration/test_list.py --- click-0.4.43+16.04.20160203/click/tests/integration/test_list.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/integration/test_list.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,33 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Integration tests for the click CLI list command.""" - -import os -import subprocess - -from .helpers import ClickTestCase - - -class TestList(ClickTestCase): - def test_list_simple(self): - name = "com.ubuntu.verify-ok" - path_to_click = self._make_click(name, framework="") - user = os.environ.get("USER", "root") - self.click_install(path_to_click, name, user) - output = subprocess.check_output( - [self.click_binary, "list", "--user=%s" % user], - universal_newlines=True) - self.assertIn(name, output) diff -Nru click-0.4.43+16.04.20160203/click/tests/integration/test_signatures.py click-6.7/click/tests/integration/test_signatures.py --- click-0.4.43+16.04.20160203/click/tests/integration/test_signatures.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/integration/test_signatures.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,364 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Integration tests for the click signature checking.""" - -import copy -import os -import shutil -import subprocess -import tarfile -from textwrap import dedent - -from .helpers import ( - require_root, - ClickTestCase, -) - - -def makedirs(path): - try: - os.makedirs(path) - except OSError: - pass - - -def get_keyid_from_gpghome(gpg_home): - """Return the public keyid of a given gpg home dir""" - output = subprocess.check_output( - ["gpg", "--home", gpg_home, "--list-keys", "--with-colons"], - universal_newlines=True) - for line in output.splitlines(): - if not line.startswith("pub:"): - continue - return line.split(":")[4] - raise ValueError("Cannot find public key in output: '%s'" % output) - - -class Debsigs: - """Tiny wrapper around the debsigs CLI""" - def __init__(self, gpghome, keyid): - self.keyid = keyid - self.gpghome = gpghome - self.policy = "/etc/debsig/policies/%s/generic.pol" % self.keyid - - def sign(self, filepath, signature_type="origin"): - """Sign the click at filepath""" - env = copy.copy(os.environ) - env["GNUPGHOME"] = os.path.abspath(self.gpghome) - subprocess.check_call( - ["debsigs", - "--sign=%s" % signature_type, - "--default-key=%s" % self.keyid, - filepath], env=env) - - def install_signature_policy(self): - """Install/update the system-wide signature policy""" - xmls = dedent("""\ - - - - - - - - - - - - - - """.format(keyid=self.keyid, filename="origin.pub")) - makedirs(os.path.dirname(self.policy)) - with open(self.policy, "w") as f: - f.write(xmls) - self.pubkey_path = ( - "/usr/share/debsig/keyrings/%s/origin.pub" % self.keyid) - makedirs(os.path.dirname(self.pubkey_path)) - shutil.copy( - os.path.join(self.gpghome, "pubring.gpg"), self.pubkey_path) - - def uninstall_signature_policy(self): - # FIXME: update debsig-verify so that it can work from a different - # root than "/" so that the tests do not have to use the - # system root - os.remove(self.policy) - os.remove(self.pubkey_path) - - -class ClickSignaturesTestCase(ClickTestCase): - - @classmethod - def setUpClass(cls): - super(ClickSignaturesTestCase, cls).setUpClass() - require_root() - - def assertClickNoSignatureError(self, cmd_args): - with self.assertRaises(subprocess.CalledProcessError) as cm: - output = subprocess.check_output( - [self.click_binary] + cmd_args, - stderr=subprocess.STDOUT, universal_newlines=True) - output = cm.exception.output - expected_error_message = ("debsig: Origin Signature check failed. " - "This deb might not be signed.") - self.assertIn(expected_error_message, output) - - def assertClickInvalidSignatureError(self, cmd_args): - with self.assertRaises(subprocess.CalledProcessError) as cm: - output = subprocess.check_output( - [self.click_binary] + cmd_args, - stderr=subprocess.STDOUT, universal_newlines=True) - print(output) - - output = cm.exception.output - expected_error_message = "Signature verification error: " - self.assertIn(expected_error_message, output) - - -class TestSignatureVerificationNoSignature(ClickSignaturesTestCase): - - @classmethod - def setUpClass(cls): - super(TestSignatureVerificationNoSignature, cls).setUpClass() - require_root() - - def test_debsig_verify_no_sig(self): - name = "org.example.debsig-no-sig" - path_to_click = self._make_click(name, framework="") - self.assertClickNoSignatureError(["verify", path_to_click]) - - def test_debsig_install_no_sig(self): - name = "org.example.debsig-no-sig" - path_to_click = self._make_click(name, framework="") - self.assertClickNoSignatureError(["install", path_to_click]) - - def test_debsig_install_can_install_with_sig_override(self): - name = "org.example.debsig-no-sig" - path_to_click = self._make_click(name, framework="") - user = os.environ.get("USER", "root") - subprocess.check_call( - [self.click_binary, "install", - "--allow-unauthenticated", "--user=%s" % user, - path_to_click]) - self.addCleanup( - subprocess.call, [self.click_binary, "unregister", - "--user=%s" % user, name]) - - -class TestSignatureVerification(ClickSignaturesTestCase): - - @classmethod - def setUpClass(cls): - super(TestSignatureVerification, cls).setUpClass() - require_root() - - def setUp(self): - super(TestSignatureVerification, self).setUp() - self.user = os.environ.get("USER", "root") - # the valid origin keyring - self.datadir = os.path.join(os.path.dirname(__file__), "data") - origin_keyring_dir = os.path.abspath( - os.path.join(self.datadir, "origin-keyring")) - keyid = get_keyid_from_gpghome(origin_keyring_dir) - self.debsigs = Debsigs(origin_keyring_dir, keyid) - self.debsigs.install_signature_policy() - - def tearDown(self): - self.debsigs.uninstall_signature_policy() - - def test_debsig_install_valid_signature(self): - name = "org.example.debsig-valid-sig" - path_to_click = self._make_click(name, framework="") - self.debsigs.sign(path_to_click) - subprocess.check_call( - [self.click_binary, "install", - "--user=%s" % self.user, - path_to_click]) - self.addCleanup( - subprocess.call, [self.click_binary, "unregister", - "--user=%s" % self.user, name]) - output = subprocess.check_output( - [self.click_binary, "list", "--user=%s" % self.user], - universal_newlines=True) - self.assertIn(name, output) - - def test_debsig_install_signature_not_in_keyring(self): - name = "org.example.debsig-no-keyring-sig" - path_to_click = self._make_click(name, framework="") - evil_keyring_dir = os.path.join(self.datadir, "evil-keyring") - keyid = get_keyid_from_gpghome(evil_keyring_dir) - debsig_bad = Debsigs(evil_keyring_dir, keyid) - debsig_bad.sign(path_to_click) - # and ensure its really not there - self.assertClickInvalidSignatureError(["install", path_to_click]) - output = subprocess.check_output( - [self.click_binary, "list", "--user=%s" % self.user], - universal_newlines=True) - self.assertNotIn(name, output) - - def test_debsig_install_not_a_signature(self): - name = "org.example.debsig-invalid-sig" - path_to_click = self._make_click(name, framework="") - invalid_sig = os.path.join(self.temp_dir, "_gpgorigin") - with open(invalid_sig, "w") as f: - f.write("no-valid-signature") - # add a invalid sig - subprocess.check_call(["ar", "-r", path_to_click, invalid_sig]) - self.assertClickInvalidSignatureError(["install", path_to_click]) - output = subprocess.check_output( - [self.click_binary, "list", "--user=%s" % self.user], - universal_newlines=True) - self.assertNotIn(name, output) - - def test_debsig_install_signature_altered_click(self): - def modify_ar_member(member): - subprocess.check_call( - ["ar", "-x", path_to_click, "control.tar.gz"], - cwd=self.temp_dir) - altered_member = os.path.join(self.temp_dir, member) - with open(altered_member, "ba") as f: - f.write(b"\0") - subprocess.check_call(["ar", "-r", path_to_click, altered_member]) - - # ensure that all members we care about are checked by debsig-verify - for member in ["control.tar.gz", "data.tar.gz", "debian-binary"]: - name = "org.example.debsig-altered-click" - path_to_click = self._make_click(name, framework="") - self.debsigs.sign(path_to_click) - modify_ar_member(member) - self.assertClickInvalidSignatureError(["install", path_to_click]) - output = subprocess.check_output( - [self.click_binary, "list", "--user=%s" % self.user], - universal_newlines=True) - self.assertNotIn(name, output) - - def make_nasty_data_tar(self, compression): - new_data_tar = os.path.join(self.temp_dir, "data.tar." + compression) - evilfile = os.path.join(self.temp_dir, "README.evil") - with open(evilfile, "w") as f: - f.write("I am a nasty README") - with tarfile.open(new_data_tar, "w:"+compression) as tar: - tar.add(evilfile) - return new_data_tar - - def test_debsig_install_signature_injected_data_tar(self): - name = "org.example.debsig-injected-data-click" - path_to_click = self._make_click(name, framework="") - self.debsigs.sign(path_to_click) - new_data = self.make_nasty_data_tar("bz2") - # insert before the real data.tar.gz and ensure this is caught - # NOTE: that right now this will not be caught by debsig-verify - # but later in audit() by debian.debfile.DebFile() - subprocess.check_call(["ar", - "-r", - "-b", "data.tar.gz", - path_to_click, - new_data]) - output = subprocess.check_output( - ["ar", "-t", path_to_click], universal_newlines=True) - self.assertEqual(output.splitlines(), - ["debian-binary", - "_click-binary", - "control.tar.gz", - "data.tar.bz2", - "data.tar.gz", - "_gpgorigin"]) - with self.assertRaises(subprocess.CalledProcessError): - output = subprocess.check_output( - [self.click_binary, "install", path_to_click], - stderr=subprocess.STDOUT, universal_newlines=True) - output = subprocess.check_output( - [self.click_binary, "list", "--user=%s" % self.user], - universal_newlines=True) - self.assertNotIn(name, output) - - def test_debsig_install_signature_replaced_data_tar(self): - name = "org.example.debsig-replaced-data-click" - path_to_click = self._make_click(name, framework="") - self.debsigs.sign(path_to_click) - new_data = self.make_nasty_data_tar("bz2") - # replace data.tar.gz with data.tar.bz2 and ensure this is caught - subprocess.check_call(["ar", - "-d", - path_to_click, - "data.tar.gz", - ]) - subprocess.check_call(["ar", - "-r", - path_to_click, - new_data]) - output = subprocess.check_output( - ["ar", "-t", path_to_click], universal_newlines=True) - self.assertEqual(output.splitlines(), - ["debian-binary", - "_click-binary", - "control.tar.gz", - "_gpgorigin", - "data.tar.bz2", - ]) - with self.assertRaises(subprocess.CalledProcessError) as cm: - output = subprocess.check_output( - [self.click_binary, "install", path_to_click], - stderr=subprocess.STDOUT, universal_newlines=True) - self.assertIn("Signature verification error", cm.exception.output) - output = subprocess.check_output( - [self.click_binary, "list", "--user=%s" % self.user], - universal_newlines=True) - self.assertNotIn(name, output) - - def test_debsig_install_signature_prepend_sig(self): - # this test is probably not really needed, it tries to trick - # the system by prepending a valid signature that is not - # in the keyring. But given that debsig-verify only reads - # the first packet of any given _gpg$foo signature it's - # equivalent to test_debsig_install_signature_not_in_keyring test - name = "org.example.debsig-replaced-data-prepend-sig-click" - path_to_click = self._make_click(name, framework="") - self.debsigs.sign(path_to_click) - new_data = self.make_nasty_data_tar("gz") - # replace data.tar.gz - subprocess.check_call(["ar", - "-r", - path_to_click, - new_data, - ]) - # get previous good _gpgorigin for the old data - subprocess.check_call( - ["ar", "-x", path_to_click, "_gpgorigin"], cwd=self.temp_dir) - with open(os.path.join(self.temp_dir, "_gpgorigin"), "br") as f: - good_gpg_origin = f.read() - # and append a valid signature from a non-keyring key - evil_keyring_dir = os.path.join(self.datadir, "evil-keyring") - debsig_bad = Debsigs(evil_keyring_dir, "18B38B9AC1B67A0D") - debsig_bad.sign(path_to_click) - subprocess.check_call( - ["ar", "-x", path_to_click, "_gpgorigin"], cwd=self.temp_dir) - with open(os.path.join(self.temp_dir, "_gpgorigin"), "br") as f: - evil_gpg_origin = f.read() - with open(os.path.join(self.temp_dir, "_gpgorigin"), "wb") as f: - f.write(evil_gpg_origin) - f.write(good_gpg_origin) - subprocess.check_call( - ["ar", "-r", path_to_click, "_gpgorigin"], cwd=self.temp_dir) - # now ensure that the verification fails as well - with self.assertRaises(subprocess.CalledProcessError) as cm: - output = subprocess.check_output( - [self.click_binary, "install", path_to_click], - stderr=subprocess.STDOUT, universal_newlines=True) - self.assertIn("Signature verification error", cm.exception.output) - output = subprocess.check_output( - [self.click_binary, "list", "--user=%s" % self.user], - universal_newlines=True) - self.assertNotIn(name, output) diff -Nru click-0.4.43+16.04.20160203/click/tests/integration/test_verify.py click-6.7/click/tests/integration/test_verify.py --- click-0.4.43+16.04.20160203/click/tests/integration/test_verify.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/integration/test_verify.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,72 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Integration tests for the click CLI verify command.""" - -import os -import subprocess - -from .helpers import ClickTestCase - - -class TestVerify(ClickTestCase): - def test_verify_force_missing_framework_ok(self): - name = "com.example.verify-missing-framework" - path_to_click = self._make_click(name) - output = subprocess.check_output([ - self.click_binary, "verify", - "--force-missing-framework", - "--allow-unauthenticated", - path_to_click], universal_newlines=True) - self.assertEqual(output, "") - - def test_verify_force_ok(self): - name = "com.example.verify-ok" - path_to_click = self._make_click(name, framework="") - output = subprocess.check_output([ - self.click_binary, "verify", "--allow-unauthenticated", - path_to_click], universal_newlines=True) - self.assertEqual(output, "") - - def test_verify_missing_framework(self): - name = "com.example.verify-really-missing-framework" - path_to_click = self._make_click(name, framework="missing") - with self.assertRaises(subprocess.CalledProcessError) as cm: - subprocess.check_output( - [self.click_binary, "verify", - "--allow-unauthenticated", - path_to_click], - universal_newlines=True, stderr=subprocess.STDOUT) - expected_error = ( - 'click.framework.ClickFrameworkInvalid: Framework ' - '"missing" not present on system (use ' - '--force-missing-framework option to override)') - self.assertIn(expected_error, cm.exception.output) - - def test_verify_no_click_but_invalid(self): - name = "com.example.verify-no-click" - path_to_click = os.path.join(self.temp_dir, name+".click") - with open(path_to_click, "w") as f: - f.write("something-that-is-not-a-click") - with self.assertRaises(subprocess.CalledProcessError) as cm: - subprocess.check_output( - [self.click_binary, "verify", "--allow-unauthenticated", - path_to_click], - universal_newlines=True, stderr=subprocess.STDOUT) - expected_error = ( - 'click.install.DebsigVerifyError: Signature verification error: ' - 'debsig: %s does not appear to be a deb format package' - ) % path_to_click - self.assertIn(expected_error, cm.exception.output) diff -Nru click-0.4.43+16.04.20160203/click/tests/Makefile.am click-6.7/click/tests/Makefile.am --- click-0.4.43+16.04.20160203/click/tests/Makefile.am 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/Makefile.am 1970-01-01 00:00:00.000000000 +0000 @@ -1,22 +0,0 @@ -noinst_DATA = preload.gir -CLEANFILES = $(noinst_DATA) - -preload.gir: preload.h - PKG_CONFIG_PATH=$(top_builddir)/lib/click g-ir-scanner \ - -n preload --nsversion 0 -l c \ - --pkg glib-2.0 --pkg gee-0.8 --pkg json-glib-1.0 \ - --pkg click-0.4 \ - -I$(top_builddir)/lib/click -L$(top_builddir)/lib/click \ - --accept-unprefixed --warn-all \ - --libtool "$(LIBTOOL)" \ - $< --output $@ - -noinst_SCRIPTS = test_paths.py -CLEANFILES += $(noinst_SCRIPTS) - -do_subst = sed \ - -e 's,[@]sysconfdir[@],$(sysconfdir),g' \ - -e 's,[@]pkgdatadir[@],$(pkgdatadir),g' - -test_paths.py: test_paths.py.in Makefile - $(do_subst) < $(srcdir)/test_paths.py.in > $@ diff -Nru click-0.4.43+16.04.20160203/click/tests/preload.h click-6.7/click/tests/preload.h --- click-0.4.43+16.04.20160203/click/tests/preload.h 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/preload.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,100 +0,0 @@ -#include -#include - -#include - -#include "click.h" - -/** - * chown: (attributes headers=unistd.h) - */ -extern int chown (const char *file, uid_t owner, gid_t group); - -/** - * geteuid: (attributes headers=sys/types.h,unistd.h) - */ -extern uid_t geteuid (void); - -/* Workaround for g-ir-scanner not picking up the type properly: mode_t is - * uint32_t on all glibc platforms. - */ -/** - * mkdir: (attributes headers=sys/stat.h,sys/types.h) - * @mode: (type guint32) - */ -extern int mkdir (const char *pathname, mode_t mode); - -/** - * getpwnam: (attributes headers=sys/types.h,pwd.h) - * - * Returns: (transfer none): - */ -extern struct passwd *getpwnam (const char *name); - -/** - * under_under_xstat: (attributes headers=sys/types.h,sys/stat.h,unistd.h) - */ -extern int under_under_xstat (int ver, const char *pathname, struct stat *buf); - -/** - * under_under_xstat64: (Attributes headers=sys/types.h,sys/stat.h,unistd.h) - */ -extern int under_under_xstat64 (int ver, const char *pathname, struct stat64 *buf); - -const gchar *g_get_user_name (void); - -/** - * g_spawn_sync: (attributes headers=glib.h) - * @argv: (array zero-terminated=1): - * @envp: (array zero-terminated=1): - * @flags: (type gint) - * @child_setup: (type gpointer) - * @standard_output: (out) (array zero-terminated=1) (element-type guint8): - * @standard_error: (out) (array zero-terminated=1) (element-type guint8): - * @exit_status: (out): - */ -gboolean g_spawn_sync (const gchar *working_directory, - gchar **argv, - gchar **envp, - GSpawnFlags flags, - GSpawnChildSetupFunc child_setup, - gpointer user_data, - gchar **standard_output, - gchar **standard_error, - gint *exit_status, - GError **error); - -/** - * click_find_on_path: (attributes headers=glib.h) - */ -gboolean click_find_on_path (const gchar *command); - -/** - * click_get_db_dir: (attributes headers=glib.h) - */ -gchar *click_get_db_dir (void); - -/** - * click_get_frameworks_dir: (attributes headers=glib.h) - */ -gchar *click_get_frameworks_dir (void); - -/** - * click_get_hooks_dir: (attributes headers=glib.h) - */ -gchar *click_get_hooks_dir (void); - -/** - * click_get_user_home: (attributes headers=glib.h) - */ -gchar *click_get_user_home (const gchar *user_name); - -/** - * click_package_install_hooks: (attributes headers=glib.h,click.h) - - * @db: (type gpointer) - */ -void click_package_install_hooks (ClickDB *db, const gchar *package, - const gchar *old_version, - const gchar *new_version, - const gchar *user_name, GError **error); diff -Nru click-0.4.43+16.04.20160203/click/tests/test_arfile.py click-6.7/click/tests/test_arfile.py --- click-0.4.43+16.04.20160203/click/tests/test_arfile.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/test_arfile.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,86 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Unit tests for click.arfile.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'TestArFile', - ] - - -import os -import subprocess - -from click.arfile import ArFile -from click.tests.helpers import TestCase, touch - - -class TestArFile(TestCase): - def setUp(self): - super(TestArFile, self).setUp() - self.use_temp_dir() - - def test_init_rejects_mode_r(self): - self.assertRaises(ValueError, ArFile, mode="r") - - def test_init_name(self): - path = os.path.join(self.temp_dir, "foo.a") - with ArFile(name=path, mode="w") as arfile: - self.assertEqual("w", arfile.mode) - self.assertEqual("wb", arfile.real_mode) - self.assertEqual(path, arfile.name) - self.assertEqual(path, arfile.fileobj.name) - self.assertTrue(arfile.opened_fileobj) - self.assertFalse(arfile.closed) - - def test_init_rejects_readonly_fileobj(self): - path = os.path.join(self.temp_dir, "foo.a") - touch(path) - with open(path, "rb") as fileobj: - self.assertRaises(ValueError, ArFile, fileobj=fileobj) - - def test_init_fileobj(self): - path = os.path.join(self.temp_dir, "foo.a") - with open(path, "wb") as fileobj: - arfile = ArFile(fileobj=fileobj) - self.assertEqual("w", arfile.mode) - self.assertEqual("wb", arfile.real_mode) - self.assertEqual(path, arfile.name) - self.assertEqual(fileobj, arfile.fileobj) - self.assertFalse(arfile.opened_fileobj) - self.assertFalse(arfile.closed) - - def test_writes_valid_ar_file(self): - member_path = os.path.join(self.temp_dir, "member") - with open(member_path, "wb") as member: - member.write(b"\x00\x01\x02\x03\x04\x05\x06\x07") - path = os.path.join(self.temp_dir, "foo.a") - with ArFile(name=path, mode="w") as arfile: - arfile.add_magic() - arfile.add_data("data-member", b"some data") - arfile.add_file("file-member", member_path) - extract_path = os.path.join(self.temp_dir, "extract") - os.mkdir(extract_path) - subprocess.call(["ar", "x", path], cwd=extract_path) - self.assertCountEqual( - ["data-member", "file-member"], os.listdir(extract_path)) - with open(os.path.join(extract_path, "data-member"), "rb") as member: - self.assertEqual(b"some data", member.read()) - with open(os.path.join(extract_path, "file-member"), "rb") as member: - self.assertEqual( - b"\x00\x01\x02\x03\x04\x05\x06\x07", member.read()) diff -Nru click-0.4.43+16.04.20160203/click/tests/test_build.py click-6.7/click/tests/test_build.py --- click-0.4.43+16.04.20160203/click/tests/test_build.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/test_build.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,345 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Unit tests for click.build.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'TestClickBuilder', - 'TestClickSourceBuilder', - ] - - -import json -import os -import stat -import subprocess -import tarfile -from textwrap import dedent - -from click.build import ClickBuildError, ClickBuilder, ClickSourceBuilder -from click.preinst import static_preinst -from click.tests.helpers import ( - disable_logging, - mkfile, - TestCase, - touch, -) - - -# BAW 2013-04-15: Some tests require umask 022. Use this decorator to -# temporarily tweak the process's umask. The test -- or system -- should -# probably be made more robust instead. -def umask(force_umask): - def decorator(func): - def wrapper(*args, **kws): - old_umask = os.umask(force_umask) - try: - return func(*args, **kws) - finally: - os.umask(old_umask) - return wrapper - return decorator - - -class TestClickBuilderBaseMixin: - def test_read_manifest(self): - self.use_temp_dir() - manifest_path = os.path.join(self.temp_dir, "manifest.json") - with mkfile(manifest_path) as manifest: - print(dedent("""\ - { - "name": "com.example.test", - "version": "1.0", - "maintainer": "Foo Bar ", - "title": "test title", - "framework": "ubuntu-sdk-13.10" - }"""), file=manifest) - self.builder.read_manifest(manifest_path) - self.assertEqual("com.example.test", self.builder.name) - self.assertEqual("1.0", self.builder.version) - self.assertEqual("Foo Bar ", self.builder.maintainer) - self.assertEqual("test title", self.builder.title) - self.assertEqual("all", self.builder.architecture) - - def test_add_file(self): - self.builder.add_file("/nonexistent", "target") - self.assertEqual({"/nonexistent": "target"}, self.builder.file_map) - - def test_epochless_version(self): - self.use_temp_dir() - manifest_path = os.path.join(self.temp_dir, "manifest.json") - for version, epochless_version in ( - ("1.0", "1.0"), - ("1:1.2.3", "1.2.3"), - ): - with mkfile(manifest_path) as manifest: - print(dedent("""\ - { - "name": "com.example.test", - "version": "%s", - "maintainer": "Foo Bar ", - "title": "test title", - "framework": "ubuntu-sdk-13.10" - }""") % version, file=manifest) - self.builder.read_manifest(manifest_path) - self.assertEqual(epochless_version, self.builder.epochless_version) - - def test_manifest_syntax_error(self): - self.use_temp_dir() - manifest_path = os.path.join(self.temp_dir, "manifest.json") - with mkfile(manifest_path) as manifest: - # The comma after the "name" entry is intentionally missing. - print(dedent("""\ - { - "name": "com.example.test" - "version": "1.0" - }"""), file=manifest) - self.assertRaises( - ClickBuildError, self.builder.read_manifest, manifest_path) - - -class TestClickBuilder(TestCase, TestClickBuilderBaseMixin): - def setUp(self): - super(TestClickBuilder, self).setUp() - self.builder = ClickBuilder() - - def extract_field(self, path, name): - return subprocess.check_output( - ["dpkg-deb", "-f", path, name], - universal_newlines=True).rstrip("\n") - - @disable_logging - @umask(0o22) - def test_build(self): - self.use_temp_dir() - scratch = os.path.join(self.temp_dir, "scratch") - with mkfile(os.path.join(scratch, "bin", "foo")) as f: - f.write("test /bin/foo\n") - os.symlink("foo", os.path.join(scratch, "bin", "bar")) - touch(os.path.join(scratch, ".git", "config")) - with mkfile(os.path.join(scratch, "toplevel")) as f: - f.write("test /toplevel\n") - os.symlink( - "file-does-not-exist", os.path.join(scratch, "broken-symlink")) - with mkfile(os.path.join(scratch, "manifest.json")) as f: - json.dump({ - "name": "com.example.test", - "version": "1.0", - "maintainer": "Foo Bar ", - "title": "test title", - "architecture": "all", - "framework": "ubuntu-sdk-13.10", - }, f) - # build() overrides this back to 0o644 - os.fchmod(f.fileno(), 0o600) - self.builder.add_file(scratch, "/") - path = os.path.join(self.temp_dir, "com.example.test_1.0_all.click") - self.assertEqual(path, self.builder.build(self.temp_dir)) - self.assertTrue(os.path.exists(path)) - for key, value in ( - ("Package", "com.example.test"), - ("Version", "1.0"), - ("Click-Version", "0.4"), - ("Architecture", "all"), - ("Maintainer", "Foo Bar "), - ("Description", "test title"), - ): - self.assertEqual(value, self.extract_field(path, key)) - self.assertNotEqual( - "", self.extract_field(path, "Installed-Size")) - control_path = os.path.join(self.temp_dir, "control") - subprocess.check_call(["dpkg-deb", "-e", path, control_path]) - manifest_path = os.path.join(control_path, "manifest") - self.assertEqual(0o644, stat.S_IMODE(os.stat(manifest_path).st_mode)) - with open(os.path.join(scratch, "manifest.json")) as source, \ - open(manifest_path) as target: - source_json = json.load(source) - target_json = json.load(target) - self.assertNotEqual("", target_json["installed-size"]) - del target_json["installed-size"] - self.assertEqual(source_json, target_json) - with open(os.path.join(control_path, "md5sums")) as md5sums: - self.assertRegex( - md5sums.read(), - r"^" - r"eb774c3ead632b397d6450d1df25e001 bin/bar\n" - r"eb774c3ead632b397d6450d1df25e001 bin/foo\n" - r"49327ce6306df8a87522456b14a179e0 toplevel\n" - r"$") - with open(os.path.join(control_path, "preinst")) as preinst: - self.assertEqual(static_preinst, preinst.read()) - contents = subprocess.check_output( - ["dpkg-deb", "-c", path], universal_newlines=True) - self.assertRegex(contents, r"^drwxr-xr-x root/root 0 .* \./\n") - self.assertRegex( - contents, - "\nlrwxrwxrwx root/root 0 .* \./bin/bar -> foo\n") - self.assertRegex( - contents, "\n-rw-r--r-- root/root 14 .* \./bin/foo\n") - self.assertRegex( - contents, "\n-rw-r--r-- root/root 15 .* \./toplevel\n") - extract_path = os.path.join(self.temp_dir, "extract") - subprocess.check_call(["dpkg-deb", "-x", path, extract_path]) - for rel_path in ( - os.path.join("bin", "foo"), - "toplevel", - ): - with open(os.path.join(scratch, rel_path)) as source, \ - open(os.path.join(extract_path, rel_path)) as target: - self.assertEqual(source.read(), target.read()) - self.assertTrue( - os.path.islink(os.path.join(extract_path, "bin", "bar"))) - self.assertEqual( - "foo", os.readlink(os.path.join(extract_path, "bin", "bar"))) - - def _make_scratch_dir(self, manifest_override={}): - self.use_temp_dir() - scratch = os.path.join(self.temp_dir, "scratch") - manifest = { - "name": "com.example.test", - "version": "1.0", - "maintainer": "Foo Bar ", - "title": "test title", - "architecture": "all", - "framework": "ubuntu-sdk-13.10", - } - manifest.update(manifest_override) - with mkfile(os.path.join(scratch, "manifest.json")) as f: - json.dump(manifest, f) - self.builder.add_file(scratch, "/") - return scratch - - @disable_logging - def test_build_excludes_dot_click(self): - scratch = self._make_scratch_dir() - touch(os.path.join(scratch, ".click", "evil-file")) - path = self.builder.build(self.temp_dir) - extract_path = os.path.join(self.temp_dir, "extract") - subprocess.check_call(["dpkg-deb", "-x", path, extract_path]) - self.assertEqual([], os.listdir(extract_path)) - - def test_build_ignore_pattern(self): - scratch = self._make_scratch_dir() - touch(os.path.join(scratch, "build", "foo.o")) - self.builder.add_file(scratch, "/") - self.builder.add_ignore_pattern("build") - path = self.builder.build(self.temp_dir) - extract_path = os.path.join(self.temp_dir, "extract") - subprocess.check_call(["dpkg-deb", "-x", path, extract_path]) - self.assertEqual([], os.listdir(extract_path)) - - @disable_logging - def test_build_multiple_architectures(self): - scratch = self._make_scratch_dir(manifest_override={ - "architecture": ["armhf", "i386"], - }) - path = os.path.join(self.temp_dir, "com.example.test_1.0_multi.click") - self.assertEqual(path, self.builder.build(self.temp_dir)) - self.assertTrue(os.path.exists(path)) - self.assertEqual("multi", self.extract_field(path, "Architecture")) - control_path = os.path.join(self.temp_dir, "control") - subprocess.check_call(["dpkg-deb", "-e", path, control_path]) - manifest_path = os.path.join(control_path, "manifest") - with open(os.path.join(scratch, "manifest.json")) as source, \ - open(manifest_path) as target: - source_json = json.load(source) - target_json = json.load(target) - del target_json["installed-size"] - self.assertEqual(source_json, target_json) - - @disable_logging - def test_build_multiple_frameworks(self): - scratch = self._make_scratch_dir(manifest_override={ - "framework": - "ubuntu-sdk-14.04-basic, ubuntu-sdk-14.04-webapps", - }) - path = self.builder.build(self.temp_dir) - control_path = os.path.join(self.temp_dir, "control") - subprocess.check_call(["dpkg-deb", "-e", path, control_path]) - manifest_path = os.path.join(control_path, "manifest") - with open(os.path.join(scratch, "manifest.json")) as source, \ - open(manifest_path) as target: - source_json = json.load(source) - target_json = json.load(target) - del target_json["installed-size"] - self.assertEqual(source_json, target_json) - - -class TestClickFrameworkValidation(TestCase): - def setUp(self): - super(TestClickFrameworkValidation, self).setUp() - self.builder = ClickBuilder() - for framework_name in ("ubuntu-sdk-13.10", - "ubuntu-sdk-14.04-papi", - "ubuntu-sdk-14.04-html", - "docker-sdk-1.3"): - self._create_mock_framework_file(framework_name) - - def test_validate_framework_good(self): - valid_framework_values = ( - "ubuntu-sdk-13.10", - "ubuntu-sdk-14.04-papi, ubuntu-sdk-14.04-html", - "ubuntu-sdk-13.10, docker-sdk-1.3", - ) - for framework in valid_framework_values: - self.builder._validate_framework(framework) - - def test_validate_framework_bad(self): - invalid_framework_values = ( - "ubuntu-sdk-13.10, ubuntu-sdk-14.04-papi", - "ubuntu-sdk-13.10 (>= 13.10)", - "ubuntu-sdk-13.10 | ubuntu-sdk-14.04", - ) - for framework in invalid_framework_values: - with self.assertRaises(ClickBuildError): - self.builder._validate_framework(framework) - - -class TestClickSourceBuilder(TestCase, TestClickBuilderBaseMixin): - def setUp(self): - super(TestClickSourceBuilder, self).setUp() - self.builder = ClickSourceBuilder() - - @umask(0o22) - def test_build(self): - self.use_temp_dir() - scratch = os.path.join(self.temp_dir, "scratch") - touch(os.path.join(scratch, "bin", "foo")) - touch(os.path.join(scratch, ".git", "config")) - touch(os.path.join(scratch, "foo.so")) - touch(os.path.join(scratch, "build", "meep.goah")) - with mkfile(os.path.join(scratch, "manifest.json")) as f: - json.dump({ - "name": "com.example.test", - "version": "1.0", - "maintainer": "Foo Bar ", - "title": "test title", - "architecture": "all", - "framework": "ubuntu-sdk-13.10", - }, f) - # build() overrides this back to 0o644 - os.fchmod(f.fileno(), 0o600) - self.builder.add_file(scratch, "./") - self.builder.add_ignore_pattern("build") - path = os.path.join(self.temp_dir, "com.example.test_1.0.tar.gz") - self.assertEqual(path, self.builder.build(self.temp_dir)) - self.assertTrue(os.path.exists(path)) - with tarfile.open(path, mode="r:gz") as tar: - self.assertCountEqual( - [".", "./bin", "./bin/foo", "./manifest.json"], tar.getnames()) - self.assertTrue(tar.getmember("./bin/foo").isfile()) diff -Nru click-0.4.43+16.04.20160203/click/tests/test_chroot.py click-6.7/click/tests/test_chroot.py --- click-0.4.43+16.04.20160203/click/tests/test_chroot.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/test_chroot.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,356 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Unit tests for click.chroot.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'TestClickChroot', - ] - -import os -import re -from textwrap import dedent - -from click.chroot import ( - ClickChroot, - generate_sources, - strip_dev_series_from_framework, -) -from click.tests.helpers import TestCase, mock - - -class FakeClickChroot(ClickChroot): - - def __init__(self, *args, **kwargs): - self.temp_dir = kwargs.pop("temp_dir") - super(FakeClickChroot, self).__init__(*args, **kwargs) - self._exists = False - - def exists(self): - return self._exists - - def maint(self, *args, **kwargs): - self._maint_args = args - self._maint_kwargs = kwargs - return 0 - - def _debootstrap(self, components, mount, archive_mirror, ports_mirror): - os.makedirs(os.path.join(mount, "etc", "apt")) - os.makedirs(os.path.join(mount, "usr", "sbin")) - os.makedirs(os.path.join(mount, "sbin")) - with open(os.path.join(mount, "sbin", "initctl"), "w"): - pass - self._exists = True - - @property - def chroot_config(self): - p = self.temp_dir + super(FakeClickChroot, self).chroot_config - if not os.path.exists(os.path.dirname(p)): - os.makedirs(os.path.dirname(p)) - return p - - -class TestClickChroot(TestCase): - def set_dpkg_native_architecture(self, arch): - """Fool dpkg-architecture into selecting a given native arch.""" - self.use_temp_dir() - dpkg_script_path = os.path.join(self.temp_dir, "dpkg") - with open(dpkg_script_path, "w") as dpkg_script: - print(dedent("""\ - #! /bin/sh - echo %s - """) % arch, file=dpkg_script) - os.chmod(dpkg_script_path, 0o755) - os.environ["PATH"] = "%s:%s" % (self.temp_dir, os.environ["PATH"]) - - def test_get_native_arch_amd64_to_amd64(self): - chroot = ClickChroot("amd64", "ubuntu-sdk-14.04", series="trusty") - self.assertEqual("amd64", chroot._get_native_arch("amd64", "amd64")) - - def test_get_native_arch_amd64_to_armhf(self): - chroot = ClickChroot("armhf", "ubuntu-sdk-14.04", series="trusty") - self.assertEqual("amd64", chroot._get_native_arch("amd64", "armhf")) - - def test_get_native_arch_amd64_to_i386(self): - chroot = ClickChroot("i386", "ubuntu-sdk-14.04", series="trusty") - self.assertEqual("i386", chroot._get_native_arch("amd64", "i386")) - - def test_dpkg_architecture_amd64_to_armhf(self): - self.set_dpkg_native_architecture("amd64") - chroot = ClickChroot("armhf", "ubuntu-sdk-14.04", series="trusty") - self.assertEqual("amd64", chroot.dpkg_architecture["DEB_BUILD_ARCH"]) - self.assertEqual("armhf", chroot.dpkg_architecture["DEB_HOST_ARCH"]) - - def test_dpkg_architecture_i386_to_armhf(self): - self.set_dpkg_native_architecture("i386") - chroot = ClickChroot("armhf", "ubuntu-sdk-14.04", series="trusty") - self.assertEqual("i386", chroot.dpkg_architecture["DEB_BUILD_ARCH"]) - self.assertEqual("armhf", chroot.dpkg_architecture["DEB_HOST_ARCH"]) - - def test_dpkg_architecture_amd64_to_i386(self): - self.set_dpkg_native_architecture("amd64") - chroot = ClickChroot("i386", "ubuntu-sdk-14.04", series="trusty") - self.assertEqual("i386", chroot.dpkg_architecture["DEB_BUILD_ARCH"]) - self.assertEqual("i386", chroot.dpkg_architecture["DEB_HOST_ARCH"]) - - def test_gen_sources_archive_only(self): - chroot = ClickChroot("amd64", "ubuntu-sdk-13.10", series="trusty") - chroot.native_arch = "i386" - sources = generate_sources( - chroot.series, chroot.native_arch, chroot.target_arch, - "http://archive.ubuntu.com/ubuntu", - "http://ports.ubuntu.com/ubuntu-ports", - "main") - self.assertEqual([ - 'deb [arch=amd64] http://archive.ubuntu.com/ubuntu trusty main', - 'deb [arch=amd64] http://archive.ubuntu.com/ubuntu trusty-updates main', - 'deb [arch=amd64] http://archive.ubuntu.com/ubuntu trusty-security main', - 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty main', - 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty-updates main', - 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty-security main', - 'deb-src http://archive.ubuntu.com/ubuntu trusty main', - 'deb-src http://archive.ubuntu.com/ubuntu trusty-updates main', - 'deb-src http://archive.ubuntu.com/ubuntu trusty-security main', - ], sources) - - def test_gen_sources_mixed_archive_ports(self): - chroot = ClickChroot("armhf", "ubuntu-sdk-13.10", series="trusty") - chroot.native_arch = "i386" - sources = generate_sources( - chroot.series, chroot.native_arch, chroot.target_arch, - "http://archive.ubuntu.com/ubuntu", - "http://ports.ubuntu.com/ubuntu-ports", - "main") - self.assertEqual([ - 'deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports trusty main', - 'deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports trusty-updates main', - 'deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports trusty-security main', - 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty main', - 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty-updates main', - 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty-security main', - 'deb-src http://archive.ubuntu.com/ubuntu trusty main', - 'deb-src http://archive.ubuntu.com/ubuntu trusty-updates main', - 'deb-src http://archive.ubuntu.com/ubuntu trusty-security main', - ], sources) - - def test_gen_sources_ports_only(self): - chroot = ClickChroot("armhf", "ubuntu-sdk-13.10", series="trusty") - chroot.native_arch = "armel" - sources = generate_sources( - chroot.series, chroot.native_arch, chroot.target_arch, - "http://archive.ubuntu.com/ubuntu", - "http://ports.ubuntu.com/ubuntu-ports", - "main") - self.assertEqual([ - 'deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports trusty main', - 'deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports trusty-updates main', - 'deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports trusty-security main', - 'deb [arch=armel] http://ports.ubuntu.com/ubuntu-ports trusty main', - 'deb [arch=armel] http://ports.ubuntu.com/ubuntu-ports trusty-updates main', - 'deb [arch=armel] http://ports.ubuntu.com/ubuntu-ports trusty-security main', - 'deb-src http://archive.ubuntu.com/ubuntu trusty main', - 'deb-src http://archive.ubuntu.com/ubuntu trusty-updates main', - 'deb-src http://archive.ubuntu.com/ubuntu trusty-security main', - ], sources) - - def test_gen_sources_native(self): - chroot = ClickChroot("i386", "ubuntu-sdk-14.04", series="trusty") - chroot.native_arch = "i386" - sources = generate_sources( - chroot.series, chroot.native_arch, chroot.target_arch, - "http://archive.ubuntu.com/ubuntu", - "http://ports.ubuntu.com/ubuntu-ports", - "main") - self.assertEqual([ - 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty main', - 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty-updates main', - 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty-security main', - 'deb-src http://archive.ubuntu.com/ubuntu trusty main', - 'deb-src http://archive.ubuntu.com/ubuntu trusty-updates main', - 'deb-src http://archive.ubuntu.com/ubuntu trusty-security main', - ], sources) - - def test_make_cross_package_native(self): - chroot = ClickChroot("amd64", "ubuntu-sdk-14.04", series="trusty") - chroot.native_arch = "amd64" - self.assertEqual("g++", chroot._make_cross_package("g++")) - - def test_make_cross_package_cross(self): - chroot = ClickChroot("armhf", "ubuntu-sdk-14.04", series="trusty") - chroot.native_arch = "amd64" - self.assertEqual( - "g++-arm-linux-gnueabihf", chroot._make_cross_package("g++")) - - def test_framework_base_base(self): - chroot = ClickChroot("i386", "ubuntu-sdk-14.04-papi") - self.assertEqual(chroot.framework_base, "ubuntu-sdk-14.04") - - def test_framework_base_series(self): - chroot = ClickChroot("i386", "ubuntu-sdk-14.04") - self.assertEqual(chroot.framework_base, "ubuntu-sdk-14.04") - - def test_chroot_series(self): - chroot = ClickChroot("i386", "ubuntu-sdk-14.04") - self.assertEqual(chroot.series, "trusty") - - def test_chroot_full_name(self): - chroot = ClickChroot("i386", "ubuntu-sdk-14.04") - self.assertEqual(chroot.full_name, "click-ubuntu-sdk-14.04-i386") - - def test_chroot_generate_daemon_config(self): - self.use_temp_dir() - chroot = ClickChroot("i386", "ubuntu-sdk-14.04") - os.makedirs(os.path.join(self.temp_dir, "usr", "sbin")) - daemon_policy = chroot._generate_daemon_policy(self.temp_dir) - with open(daemon_policy) as f: - self.assertEqual(f.read(), chroot.DAEMON_POLICY) - - def test_chroot_generate_finish_script(self): - self.use_temp_dir() - chroot = ClickChroot("i386", "ubuntu-sdk-14.04") - finish_script = chroot._generate_finish_script( - self.temp_dir, - ["build-pkg-1", "build-pkg-2"]) - with open(finish_script) as f: - self.assertEqual(f.read(), dedent("""\ - #!/bin/bash - set -e - # Configure target arch - dpkg --add-architecture i386 - # Reload package lists - apt-get update || true - # Pull down signature requirements - apt-get -y --force-yes install gnupg ubuntu-keyring - # Reload package lists - apt-get update || true - # Disable debconf questions - # so that automated builds won't prompt - echo set debconf/frontend Noninteractive | debconf-communicate - echo set debconf/priority critical | debconf-communicate - apt-get -y --force-yes dist-upgrade - # Install basic build tool set to match buildd - apt-get -y --force-yes install build-pkg-1 build-pkg-2 - # Set up expected /dev entries - if [ ! -r /dev/stdin ]; then - ln -s /proc/self/fd/0 /dev/stdin - fi - if [ ! -r /dev/stdout ]; then - ln -s /proc/self/fd/1 /dev/stdout - fi - if [ ! -r /dev/stderr ]; then - ln -s /proc/self/fd/2 /dev/stderr - fi - # Clean up - rm /finish.sh - apt-get clean - """)) - - def test_chroot_generate_apt_conf_d_empty(self): - self.use_temp_dir() - chroot = ClickChroot("i386", "ubuntu-sdk-14.04") - apt_conf_f = chroot._generate_apt_proxy_file(self.temp_dir, "") - self.assertFalse(os.path.exists(apt_conf_f)) - - def test_chroot_generate_apt_conf_d(self): - self.use_temp_dir() - chroot = ClickChroot("i386", "ubuntu-sdk-14.04") - apt_conf_f = chroot._generate_apt_proxy_file( - self.temp_dir, "http://proxy.example.com") - with open(apt_conf_f) as f: - self.assertEqual( - re.sub(r'\s+', ' ', f.read()), - '// proxy settings copied by click chroot ' - 'Acquire { HTTP { Proxy "http://proxy.example.com"; }; }; ') - - def test_chroot_generate_chroot_config(self): - self.use_temp_dir() - chroot = FakeClickChroot( - "i386", "ubuntu-sdk-14.04", temp_dir=self.temp_dir) - with mock.patch.object(chroot, "user", new="meep"): - chroot._generate_chroot_config(self.temp_dir) - with open(chroot.chroot_config) as f: - content = f.read() - self.assertEqual( - content, dedent("""\ - [click-ubuntu-sdk-14.04-i386] - description=Build chroot for click packages on i386 - users=root,{user} - root-users=root,{user} - source-root-users=root,{user} - type=directory - profile=default - setup.fstab=click/fstab - # Not protocols or services see - # debian bug 557730 - setup.nssdatabases=sbuild/nssdatabases - union-type=overlayfs - directory={temp_dir} - """).format(user="meep", temp_dir=self.temp_dir)) - - def test_chroot_create_mocked(self): - self.use_temp_dir() - os.environ["http_proxy"] = "http://proxy.example.com/" - target = "ubuntu-sdk-14.04" - chroot = FakeClickChroot( - "i386", target, chroots_dir=self.temp_dir, temp_dir=self.temp_dir) - with mock.patch.object(chroot, "maint") as mock_maint: - mock_maint.return_value = 0 - chroot.create() - mock_maint.assert_called_with("/finish.sh") - # ensure the following files where created inside the chroot - for in_chroot in ["etc/localtime", - "etc/timezone", - "etc/apt/sources.list", - "usr/sbin/policy-rc.d"]: - full_path = os.path.join( - self.temp_dir, chroot.full_name, in_chroot) - self.assertTrue(os.path.exists(full_path)) - # ensure the schroot/chroot.d file was created and looks valid - schroot_d = os.path.join( - self.temp_dir, "etc", "schroot", "chroot.d", chroot.full_name) - self.assertTrue(os.path.exists(schroot_d)) - - def test_chroot_maint(self): - chroot = ClickChroot("i386", "ubuntu-sdk-14.04") - with mock.patch("subprocess.call") as mock_call: - mock_call.return_value = 0 - chroot.maint("foo", "bar") - mock_call.assert_called_with([ - "schroot", "-u", "root", - "-c", "source:"+chroot.full_name, - "--", - "foo", "bar"]) - - def test_chroot_destroy(self): - self.use_temp_dir() - chroot = FakeClickChroot( - "i386", "ubuntu-sdk-14.04", - chroots_dir=self.temp_dir, temp_dir=self.temp_dir) - chroot.create() - chroot_path = os.path.join(self.temp_dir, chroot.full_name) - self.assertTrue(os.path.exists(chroot_path)) - chroot.destroy() - self.assertFalse(os.path.exists(chroot_path)) - - def test_strip_dev_series_from_framework(self): - for have, want in ( - ("ubuntu-sdk-14.10-html-dev1", "ubuntu-sdk-14.10-html"), - ("ubuntu-sdk-14.10-html", "ubuntu-sdk-14.10-html"), - ("ubuntu-sdk-14.04-dev99", "ubuntu-sdk-14.04"), - ): - self.assertEqual(strip_dev_series_from_framework(have), want) diff -Nru click-0.4.43+16.04.20160203/click/tests/test_database.py click-6.7/click/tests/test_database.py --- click-0.4.43+16.04.20160203/click/tests/test_database.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/test_database.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,936 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Unit tests for click.database.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - "TestClickDB", - "TestClickInstalledPackage", - "TestClickSingleDB", - ] - - -from functools import partial -from itertools import takewhile -import json -import os -import unittest - -from gi.repository import Click, GLib -from six import integer_types - -from click.json_helpers import json_array_to_python, json_object_to_python -from click.tests.gimock_types import Passwd -from click.tests.helpers import TestCase, mkfile, touch - - -class TestClickInstalledPackage(TestCase): - def setUp(self): - super(TestClickInstalledPackage, self).setUp() - self.foo = Click.InstalledPackage.new( - "foo", "1.0", "/path/to/foo/1.0", False) - self.foo_clone = Click.InstalledPackage.new( - "foo", "1.0", "/path/to/foo/1.0", False) - self.foo_different_version = Click.InstalledPackage.new( - "foo", "2.0", "/path/to/foo/1.0", False) - self.foo_different_path = Click.InstalledPackage.new( - "foo", "1.0", "/path/to/foo/2.0", False) - self.foo_different_writeable = Click.InstalledPackage.new( - "foo", "1.0", "/path/to/foo/1.0", True) - self.bar = Click.InstalledPackage.new( - "bar", "1.0", "/path/to/foo/1.0", False) - - def test_hash(self): - self.assertIsInstance(self.foo.hash(), integer_types) - self.assertEqual(self.foo.hash(), self.foo_clone.hash()) - self.assertNotEqual(self.foo.hash(), self.foo_different_version.hash()) - self.assertNotEqual(self.foo.hash(), self.foo_different_path.hash()) - self.assertNotEqual( - self.foo.hash(), self.foo_different_writeable.hash()) - self.assertNotEqual(self.foo.hash(), self.bar.hash()) - - # GLib doesn't allow passing an InstalledPackage as an argument here. - @unittest.expectedFailure - def test_equal_to(self): - self.assertTrue(self.foo.equal_to(self.foo_clone)) - self.assertFalse(self.foo.equal_to(self.foo_different_version)) - self.assertFalse(self.foo.equal_to(self.foo_different_path)) - self.assertFalse(self.foo.equal_to(self.foo_different_writeable)) - self.assertFalse(self.foo.equal_to(self.bar)) - - -class TestClickSingleDB(TestCase): - def setUp(self): - super(TestClickSingleDB, self).setUp() - self.use_temp_dir() - self.master_db = Click.DB() - self.master_db.add(self.temp_dir) - self.db = self.master_db.get(self.master_db.props.size - 1) - self.spawn_calls = [] - - def g_spawn_sync_side_effect(self, status_map, working_directory, argv, - envp, flags, child_setup, user_data, - standard_output, standard_error, exit_status, - error): - self.spawn_calls.append(list(takewhile(lambda x: x is not None, argv))) - if argv[0] in status_map: - exit_status[0] = status_map[argv[0]] - else: - self.delegate_to_original("g_spawn_sync") - return 0 - - def _installed_packages_tuplify(self, ip): - return [(p.props.package, p.props.version, p.props.path) for p in ip] - - def test_path(self): - path = os.path.join(self.temp_dir, "a", "1.0") - os.makedirs(path) - self.assertEqual(path, self.db.get_path("a", "1.0")) - self.assertRaisesDatabaseError( - Click.DatabaseError.DOES_NOT_EXIST, self.db.get_path, "a", "1.1") - - def test_has_package_version(self): - os.makedirs(os.path.join(self.temp_dir, "a", "1.0")) - self.assertTrue(self.db.has_package_version("a", "1.0")) - self.assertFalse(self.db.has_package_version("a", "1.1")) - - def test_packages_current(self): - os.makedirs(os.path.join(self.temp_dir, "a", "1.0")) - os.makedirs(os.path.join(self.temp_dir, "a", "1.1")) - a_current = os.path.join(self.temp_dir, "a", "current") - os.symlink("1.1", a_current) - os.makedirs(os.path.join(self.temp_dir, "b", "0.1")) - b_current = os.path.join(self.temp_dir, "b", "current") - os.symlink("0.1", b_current) - os.makedirs(os.path.join(self.temp_dir, "c", "2.0")) - self.assertEqual([ - ("a", "1.1", a_current), - ("b", "0.1", b_current), - ], self._installed_packages_tuplify( - self.db.get_packages(all_versions=False))) - - def test_packages_all(self): - os.makedirs(os.path.join(self.temp_dir, "a", "1.0")) - os.makedirs(os.path.join(self.temp_dir, "a", "1.1")) - os.symlink("1.1", os.path.join(self.temp_dir, "a", "current")) - os.makedirs(os.path.join(self.temp_dir, "b", "0.1")) - os.symlink("0.1", os.path.join(self.temp_dir, "b", "current")) - os.makedirs(os.path.join(self.temp_dir, "c", "2.0")) - self.assertEqual([ - ("a", "1.0", os.path.join(self.temp_dir, "a", "1.0")), - ("a", "1.1", os.path.join(self.temp_dir, "a", "1.1")), - ("b", "0.1", os.path.join(self.temp_dir, "b", "0.1")), - ("c", "2.0", os.path.join(self.temp_dir, "c", "2.0")), - ], self._installed_packages_tuplify( - self.db.get_packages(all_versions=True))) - - def test_packages_all_ignores_non_directory(self): - os.makedirs(os.path.join(self.temp_dir, "a", "1.0")) - touch(os.path.join(self.temp_dir, "file")) - self.assertEqual([ - ("a", "1.0", os.path.join(self.temp_dir, "a", "1.0")), - ], self._installed_packages_tuplify( - self.db.get_packages(all_versions=True))) - - def test_manifest(self): - manifest_path = os.path.join( - self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") - manifest_obj = { - "name": "a", "version": "1.0", "hooks": {"a-app": {}}, - "_should_be_removed": "", - } - with mkfile(manifest_path) as manifest: - json.dump(manifest_obj, manifest) - del manifest_obj["_should_be_removed"] - manifest_obj["_directory"] = os.path.join(self.temp_dir, "a", "1.0") - self.assertEqual( - manifest_obj, - json_object_to_python(self.db.get_manifest("a", "1.0"))) - self.assertRaisesDatabaseError( - Click.DatabaseError.DOES_NOT_EXIST, - self.db.get_manifest, "a", "1.1") - self.assertEqual( - manifest_obj, - json.loads(self.db.get_manifest_as_string("a", "1.0"))) - self.assertRaisesDatabaseError( - Click.DatabaseError.DOES_NOT_EXIST, - self.db.get_manifest_as_string, "a", "1.1") - - def test_manifest_bad(self): - manifest_path = os.path.join( - self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") - with mkfile(manifest_path) as manifest: - print("{bad syntax", file=manifest) - self.assertRaisesDatabaseError( - Click.DatabaseError.BAD_MANIFEST, self.db.get_manifest, "a", "1.0") - self.assertRaisesDatabaseError( - Click.DatabaseError.BAD_MANIFEST, - self.db.get_manifest_as_string, "a", "1.0") - manifest_path = os.path.join( - self.temp_dir, "a", "1.1", ".click", "info", "a.manifest") - with mkfile(manifest_path) as manifest: - print("[0]", file=manifest) - self.assertRaisesDatabaseError( - Click.DatabaseError.BAD_MANIFEST, self.db.get_manifest, "a", "1.1") - self.assertRaisesDatabaseError( - Click.DatabaseError.BAD_MANIFEST, - self.db.get_manifest_as_string, "a", "1.1") - - def test_app_running(self): - with self.run_in_subprocess( - "click_find_on_path", "g_spawn_sync", - ) as (enter, preloads): - enter() - preloads["click_find_on_path"].return_value = True - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 0}) - self.assertTrue(self.db.app_running("foo", "bar", "1.0")) - self.assertEqual( - [[b"ubuntu-app-pid", b"foo_bar_1.0"]], self.spawn_calls) - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 1 << 8}) - self.assertFalse(self.db.app_running("foo", "bar", "1.0")) - - def test_any_app_running_ubuntu_app_pid(self): - with self.run_in_subprocess( - "click_find_on_path", "g_spawn_sync", - ) as (enter, preloads): - enter() - manifest_path = os.path.join( - self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") - with mkfile(manifest_path) as manifest: - json.dump({"hooks": {"a-app": {}}}, manifest) - preloads["click_find_on_path"].side_effect = ( - lambda command: command == b"ubuntu-app-pid") - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 0}) - self.assertTrue(self.db.any_app_running("a", "1.0")) - self.assertEqual( - [[b"ubuntu-app-pid", b"a_a-app_1.0"]], self.spawn_calls) - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 1 << 8}) - self.assertFalse(self.db.any_app_running("a", "1.0")) - - def test_any_app_running_upstart_app_pid(self): - with self.run_in_subprocess( - "click_find_on_path", "g_spawn_sync", - ) as (enter, preloads): - enter() - manifest_path = os.path.join( - self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") - with mkfile(manifest_path) as manifest: - json.dump({"hooks": {"a-app": {}}}, manifest) - preloads["click_find_on_path"].side_effect = ( - lambda command: command == b"upstart-app-pid") - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"upstart-app-pid": 0}) - self.assertTrue(self.db.any_app_running("a", "1.0")) - self.assertEqual( - [[b"upstart-app-pid", b"a_a-app_1.0"]], self.spawn_calls) - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"upstart-app-pid": 1 << 8}) - self.assertFalse(self.db.any_app_running("a", "1.0")) - - def test_any_app_running_no_app_pid_command(self): - with self.run_in_subprocess( - "click_find_on_path", "g_spawn_sync", - ) as (enter, preloads): - enter() - manifest_path = os.path.join( - self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") - with mkfile(manifest_path) as manifest: - json.dump({"hooks": {"a-app": {}}}, manifest) - preloads["click_find_on_path"].return_value = False - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 0}) - self.assertFalse(self.db.any_app_running("a", "1.0")) - - def test_any_app_running_missing_app(self): - with self.run_in_subprocess("click_find_on_path") as (enter, preloads): - enter() - preloads["click_find_on_path"].side_effect = ( - lambda command: command == b"ubuntu-app-pid") - self.assertRaisesDatabaseError( - Click.DatabaseError.DOES_NOT_EXIST, - self.db.any_app_running, "a", "1.0") - - def test_any_app_running_bad_manifest(self): - with self.run_in_subprocess( - "click_find_on_path", "g_spawn_sync", - ) as (enter, preloads): - enter() - manifest_path = os.path.join( - self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") - with mkfile(manifest_path) as manifest: - print("{bad syntax", file=manifest) - preloads["click_find_on_path"].side_effect = ( - lambda command: command == b"ubuntu-app-pid") - self.assertFalse(self.db.any_app_running("a", "1.0")) - self.assertFalse(preloads["g_spawn_sync"].called) - - def test_any_app_running_no_hooks(self): - with self.run_in_subprocess( - "click_find_on_path", "g_spawn_sync", - ) as (enter, preloads): - enter() - manifest_path = os.path.join( - self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") - with mkfile(manifest_path) as manifest: - json.dump({}, manifest) - preloads["click_find_on_path"].side_effect = ( - lambda command: command == b"ubuntu-app-pid") - self.assertFalse(self.db.any_app_running("a", "1.0")) - self.assertFalse(preloads["g_spawn_sync"].called) - - def test_maybe_remove_registered(self): - with self.run_in_subprocess( - "click_find_on_path", "g_spawn_sync", - ) as (enter, preloads): - enter() - version_path = os.path.join(self.temp_dir, "a", "1.0") - manifest_path = os.path.join( - version_path, ".click", "info", "a.manifest") - with mkfile(manifest_path) as manifest: - json.dump({"hooks": {"a-app": {}}}, manifest) - user_path = os.path.join( - self.temp_dir, ".click", "users", "test-user", "a") - os.makedirs(os.path.dirname(user_path)) - os.symlink(version_path, user_path) - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 0}) - preloads["click_find_on_path"].return_value = True - self.db.maybe_remove("a", "1.0") - self.assertTrue(os.path.exists(version_path)) - self.assertTrue(os.path.exists(user_path)) - - def test_maybe_remove_running(self): - with self.run_in_subprocess( - "click_find_on_path", "g_spawn_sync", - ) as (enter, preloads): - enter() - version_path = os.path.join(self.temp_dir, "a", "1.0") - manifest_path = os.path.join( - version_path, ".click", "info", "a.manifest") - with mkfile(manifest_path) as manifest: - json.dump({"hooks": {"a-app": {}}}, manifest) - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 0}) - preloads["click_find_on_path"].return_value = True - self.db.maybe_remove("a", "1.0") - self.assertTrue(os.path.exists(version_path)) - - def test_maybe_remove_not_running(self): - with self.run_in_subprocess( - "click_find_on_path", "g_spawn_sync", - ) as (enter, preloads): - enter() - os.environ["TEST_QUIET"] = "1" - version_path = os.path.join(self.temp_dir, "a", "1.0") - manifest_path = os.path.join( - version_path, ".click", "info", "a.manifest") - with mkfile(manifest_path) as manifest: - json.dump({"hooks": {"a-app": {}}}, manifest) - current_path = os.path.join(self.temp_dir, "a", "current") - os.symlink("1.0", current_path) - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 1 << 8}) - preloads["click_find_on_path"].return_value = True - self.db.maybe_remove("a", "1.0") - self.assertFalse(os.path.exists(os.path.join(self.temp_dir, "a"))) - - def test_gc(self): - with self.run_in_subprocess( - "click_find_on_path", "g_spawn_sync", "getpwnam" - ) as (enter, preloads): - enter() - preloads["getpwnam"].side_effect = ( - lambda name: self.make_pointer(Passwd(pw_uid=1, pw_gid=1))) - os.environ["TEST_QUIET"] = "1" - a_path = os.path.join(self.temp_dir, "a", "1.0") - a_manifest_path = os.path.join( - a_path, ".click", "info", "a.manifest") - with mkfile(a_manifest_path) as manifest: - json.dump({"hooks": {"a-app": {}}}, manifest) - b_path = os.path.join(self.temp_dir, "b", "1.0") - b_manifest_path = os.path.join( - b_path, ".click", "info", "b.manifest") - with mkfile(b_manifest_path) as manifest: - json.dump({"hooks": {"b-app": {}}}, manifest) - c_path = os.path.join(self.temp_dir, "c", "1.0") - c_manifest_path = os.path.join( - c_path, ".click", "info", "c.manifest") - with mkfile(c_manifest_path) as manifest: - json.dump({"hooks": {"c-app": {}}}, manifest) - a_user_path = os.path.join( - self.temp_dir, ".click", "users", "test-user", "a") - os.makedirs(os.path.dirname(a_user_path)) - os.symlink(a_path, a_user_path) - b_gcinuse_path = os.path.join( - self.temp_dir, ".click", "users", "@gcinuse", "b") - os.makedirs(os.path.dirname(b_gcinuse_path)) - os.symlink(b_path, b_gcinuse_path) - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 1 << 8}) - preloads["click_find_on_path"].return_value = True - self.db.gc() - self.assertTrue(os.path.exists(a_path)) - self.assertFalse(os.path.exists(b_gcinuse_path)) - self.assertFalse(os.path.exists(b_path)) - self.assertFalse(os.path.exists(c_path)) - - def test_gc_ignores_non_directory(self): - with self.run_in_subprocess( - "getpwnam" - ) as (enter, preloads): - enter() - preloads["getpwnam"].side_effect = ( - lambda name: self.make_pointer(Passwd(pw_uid=1, pw_gid=1))) - a_path = os.path.join(self.temp_dir, "a", "1.0") - a_manifest_path = os.path.join( - a_path, ".click", "info", "a.manifest") - with mkfile(a_manifest_path) as manifest: - json.dump({"hooks": {"a-app": {}}}, manifest) - a_user_path = os.path.join( - self.temp_dir, ".click", "users", "test-user", "a") - os.makedirs(os.path.dirname(a_user_path)) - os.symlink(a_path, a_user_path) - touch(os.path.join(self.temp_dir, "file")) - self.db.gc() - self.assertTrue(os.path.exists(a_path)) - - # Test that bug #1479001 is fixed. Uses the following scenario: - # - # - Two databases: db1 and db2. - # - One package, "test-package": - # - Versions 1 and 3 installed in db1 - # - Version 2 installed in db2 - # - User has a registration in db2 for version 2, where the registration - # timestamp precedes the installation of version 3. - # - # In this case, bug #1479001 expects that the user's registration would - # be updated to 3, since it was installed after the user registered for - # 2, which implies that the user would like the update to 3. - def test_gc_fixes_old_user_registrations(self): - with self.run_in_subprocess("getpwnam") as (enter, preloads): - enter() - - # Setup the system hook - preloads["getpwnam"].side_effect = ( - lambda name: self.make_pointer(Passwd(pw_dir=b"/foo"))) - - # Setup both databases - db1 = os.path.join(self.temp_dir, "db1") - db2 = os.path.join(self.temp_dir, "db2") - db = Click.DB() - db.add(db1) - db.add(db2) - - # Prepare common manifest for the packages - manifest = {"hooks": {"test-app": {"test": "foo"}}} - - # Setup versions 1.0 and 3.0 of package in db1 - version1 = os.path.join(db1, "test-package", "1.0") - with mkfile(os.path.join(version1, ".click", "info", - "test-package.manifest")) as f: - json.dump(manifest, f) - - version3 = os.path.join(db1, "test-package", "3.0") - with mkfile(os.path.join(version3, ".click", "info", - "test-package.manifest")) as f: - json.dump(manifest, f) - - # Setup version 0.2 of package in db2 - version2 = os.path.join(db2, "test-package", "2.0") - with mkfile(os.path.join(version2, ".click", "info", - "test-package.manifest")) as f: - json.dump(manifest, f) - - # Setup the user registration for 2.0 in db2. - registrationPath = os.path.join( - db2, ".click", "users", "foo", "test-package") - os.makedirs(os.path.dirname(registrationPath)) - os.symlink(version2, registrationPath) - - # Run the garbage collection to update the registrations. - db.gc() - - # Verify that the user still has a registration for the package, - # and that it's now registered for version 3.0. - self.assertTrue(os.path.lexists(registrationPath)) - self.assertEqual(version3, os.readlink(registrationPath)) - - user_db = Click.User.for_user(db, "foo") - try: - version = user_db.get_version("test-package") - self.assertEqual("3.0", version) - except: - self.fail("No user registration for 'test-package'") - - def _make_ownership_test(self): - path = os.path.join(self.temp_dir, "a", "1.0") - touch(os.path.join(path, ".click", "info", "a.manifest")) - os.symlink("1.0", os.path.join(self.temp_dir, "a", "current")) - user_path = os.path.join( - self.temp_dir, ".click", "users", "test-user", "a") - os.makedirs(os.path.dirname(user_path)) - os.symlink(path, user_path) - touch(os.path.join(self.temp_dir, ".click", "log")) - - def _set_stat_side_effect(self, preloads, side_effect, limit): - limit = limit.encode() - preloads["__xstat"].side_effect = ( - lambda ver, path, buf: side_effect( - "__xstat", limit, ver, path, buf)) - preloads["__xstat64"].side_effect = ( - lambda ver, path, buf: side_effect( - "__xstat64", limit, ver, path, buf)) - - def test_ensure_ownership_quick_if_correct(self): - def stat_side_effect(name, limit, ver, path, buf): - st = self.convert_stat_pointer(name, buf) - if path == limit: - st.st_uid = 1 - st.st_gid = 1 - return 0 - else: - self.delegate_to_original(name) - return -1 - - with self.run_in_subprocess( - "chown", "getpwnam", "__xstat", "__xstat64", - ) as (enter, preloads): - enter() - preloads["getpwnam"].side_effect = ( - lambda name: self.make_pointer(Passwd(pw_uid=1, pw_gid=1))) - self._set_stat_side_effect( - preloads, stat_side_effect, self.db.props.root) - - self._make_ownership_test() - self.db.ensure_ownership() - self.assertFalse(preloads["chown"].called) - - def test_ensure_ownership(self): - def stat_side_effect(name, limit, ver, path, buf): - st = self.convert_stat_pointer(name, buf) - if path == limit: - st.st_uid = 2 - st.st_gid = 2 - return 0 - else: - self.delegate_to_original(name) - return -1 - - with self.run_in_subprocess( - "chown", "getpwnam", "__xstat", "__xstat64", - ) as (enter, preloads): - enter() - preloads["getpwnam"].side_effect = ( - lambda name: self.make_pointer(Passwd(pw_uid=1, pw_gid=1))) - self._set_stat_side_effect( - preloads, stat_side_effect, self.db.props.root) - - self._make_ownership_test() - self.db.ensure_ownership() - expected_paths = [ - self.temp_dir, - os.path.join(self.temp_dir, ".click"), - os.path.join(self.temp_dir, ".click", "log"), - os.path.join(self.temp_dir, ".click", "users"), - os.path.join(self.temp_dir, "a"), - os.path.join(self.temp_dir, "a", "1.0"), - os.path.join(self.temp_dir, "a", "1.0", ".click"), - os.path.join(self.temp_dir, "a", "1.0", ".click", "info"), - os.path.join( - self.temp_dir, "a", "1.0", ".click", "info", "a.manifest"), - os.path.join(self.temp_dir, "a", "current"), - ] - self.assertCountEqual( - [path.encode() for path in expected_paths], - [args[0][0] for args in preloads["chown"].call_args_list]) - self.assertCountEqual( - [(1, 1)], - set(args[0][1:] for args in preloads["chown"].call_args_list)) - - def test_ensure_ownership_missing_clickpkg_user(self): - with self.run_in_subprocess("getpwnam") as (enter, preloads): - enter() - preloads["getpwnam"].return_value = None - self.assertRaisesDatabaseError( - Click.DatabaseError.ENSURE_OWNERSHIP, self.db.ensure_ownership) - - def test_ensure_ownership_failed_chown(self): - def stat_side_effect(name, limit, ver, path, buf): - st = self.convert_stat_pointer(name, buf) - if path == limit: - st.st_uid = 2 - st.st_gid = 2 - return 0 - else: - self.delegate_to_original(name) - return -1 - - with self.run_in_subprocess( - "chown", "getpwnam", "__xstat", "__xstat64", - ) as (enter, preloads): - enter() - preloads["chown"].return_value = -1 - preloads["getpwnam"].side_effect = ( - lambda name: self.make_pointer(Passwd(pw_uid=1, pw_gid=1))) - self._set_stat_side_effect( - preloads, stat_side_effect, self.db.props.root) - - self._make_ownership_test() - self.assertRaisesDatabaseError( - Click.DatabaseError.ENSURE_OWNERSHIP, self.db.ensure_ownership) - - -class TestClickDB(TestCase): - def setUp(self): - super(TestClickDB, self).setUp() - self.use_temp_dir() - - def _installed_packages_tuplify(self, ip): - return [ - (p.props.package, p.props.version, p.props.path, p.props.writeable) - for p in ip] - - def test_read_configuration(self): - with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: - print("[Click Database]", file=a) - print("root = /a", file=a) - with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: - print("[Click Database]", file=b) - print("root = /b", file=b) - db = Click.DB() - db.read(db_dir=self.temp_dir) - db.add("/c") - self.assertEqual(3, db.props.size) - self.assertEqual( - ["/a", "/b", "/c"], - [db.get(i).props.root for i in range(db.props.size)]) - - def test_no_read(self): - with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: - print("[Click Database]", file=a) - print("root = /a", file=a) - db = Click.DB() - self.assertEqual(0, db.props.size) - - def test_no_db_conf_errors(self): - db = Click.DB() - self.assertRaisesDatabaseError( - Click.DatabaseError.INVALID, db.get, 0) - self.assertEqual(db.props.overlay, "") - self.assertRaisesDatabaseError( - Click.DatabaseError.INVALID, db.maybe_remove, "something", "1.0") - self.assertRaisesDatabaseError( - Click.DatabaseError.INVALID, db.gc) - self.assertRaisesDatabaseError( - Click.DatabaseError.INVALID, db.ensure_ownership) - - def test_read_nonexistent(self): - db = Click.DB() - db.read(db_dir=os.path.join(self.temp_dir, "nonexistent")) - self.assertEqual(0, db.props.size) - - def test_read_not_directory(self): - path = os.path.join(self.temp_dir, "file") - touch(path) - db = Click.DB() - self.assertRaisesFileError(GLib.FileError.NOTDIR, db.read, db_dir=path) - - def test_add(self): - db = Click.DB() - self.assertEqual(0, db.props.size) - db.add("/new/root") - self.assertEqual(1, db.props.size) - self.assertEqual("/new/root", db.get(0).props.root) - - def test_overlay(self): - with open(os.path.join(self.temp_dir, "00_custom.conf"), "w") as f: - print("[Click Database]", file=f) - print("root = /custom", file=f) - with open(os.path.join(self.temp_dir, "99_default.conf"), "w") as f: - print("[Click Database]", file=f) - print("root = /opt/click.ubuntu.com", file=f) - db = Click.DB() - db.read(db_dir=self.temp_dir) - self.assertEqual("/opt/click.ubuntu.com", db.props.overlay) - - def test_path(self): - with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: - print("[Click Database]", file=a) - print("root = %s" % os.path.join(self.temp_dir, "a"), file=a) - with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: - print("[Click Database]", file=b) - print("root = %s" % os.path.join(self.temp_dir, "b"), file=b) - db = Click.DB() - db.read(db_dir=self.temp_dir) - self.assertRaisesDatabaseError( - Click.DatabaseError.DOES_NOT_EXIST, db.get_path, "pkg", "1.0") - os.makedirs(os.path.join(self.temp_dir, "a", "pkg", "1.0")) - self.assertEqual( - os.path.join(self.temp_dir, "a", "pkg", "1.0"), - db.get_path("pkg", "1.0")) - self.assertRaisesDatabaseError( - Click.DatabaseError.DOES_NOT_EXIST, db.get_path, "pkg", "1.1") - os.makedirs(os.path.join(self.temp_dir, "b", "pkg", "1.0")) - # The deepest copy of the same package/version is still preferred. - self.assertEqual( - os.path.join(self.temp_dir, "a", "pkg", "1.0"), - db.get_path("pkg", "1.0")) - os.makedirs(os.path.join(self.temp_dir, "b", "pkg", "1.1")) - self.assertEqual( - os.path.join(self.temp_dir, "b", "pkg", "1.1"), - db.get_path("pkg", "1.1")) - - def test_has_package_version(self): - with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: - print("[Click Database]", file=a) - print("root = %s" % os.path.join(self.temp_dir, "a"), file=a) - with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: - print("[Click Database]", file=b) - print("root = %s" % os.path.join(self.temp_dir, "b"), file=b) - db = Click.DB() - db.read(db_dir=self.temp_dir) - self.assertFalse(db.has_package_version("pkg", "1.0")) - os.makedirs(os.path.join(self.temp_dir, "a", "pkg", "1.0")) - self.assertTrue(db.has_package_version("pkg", "1.0")) - self.assertFalse(db.has_package_version("pkg", "1.1")) - os.makedirs(os.path.join(self.temp_dir, "b", "pkg", "1.0")) - self.assertTrue(db.has_package_version("pkg", "1.0")) - os.makedirs(os.path.join(self.temp_dir, "b", "pkg", "1.1")) - self.assertTrue(db.has_package_version("pkg", "1.1")) - - def test_packages_current(self): - with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: - print("[Click Database]", file=a) - print("root = %s" % os.path.join(self.temp_dir, "a"), file=a) - with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: - print("[Click Database]", file=b) - print("root = %s" % os.path.join(self.temp_dir, "b"), file=b) - db = Click.DB() - db.read(db_dir=self.temp_dir) - self.assertEqual([], list(db.get_packages(all_versions=False))) - os.makedirs(os.path.join(self.temp_dir, "a", "pkg1", "1.0")) - os.symlink("1.0", os.path.join(self.temp_dir, "a", "pkg1", "current")) - os.makedirs(os.path.join(self.temp_dir, "b", "pkg1", "1.1")) - pkg1_current = os.path.join(self.temp_dir, "b", "pkg1", "current") - os.symlink("1.1", pkg1_current) - os.makedirs(os.path.join(self.temp_dir, "b", "pkg2", "0.1")) - pkg2_current = os.path.join(self.temp_dir, "b", "pkg2", "current") - os.symlink("0.1", pkg2_current) - self.assertEqual([ - ("pkg1", "1.1", pkg1_current, True), - ("pkg2", "0.1", pkg2_current, True), - ], self._installed_packages_tuplify( - db.get_packages(all_versions=False))) - - def test_packages_all(self): - with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: - print("[Click Database]", file=a) - print("root = %s" % os.path.join(self.temp_dir, "a"), file=a) - with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: - print("[Click Database]", file=b) - print("root = %s" % os.path.join(self.temp_dir, "b"), file=b) - db = Click.DB() - db.read(db_dir=self.temp_dir) - self.assertEqual([], list(db.get_packages(all_versions=True))) - os.makedirs(os.path.join(self.temp_dir, "a", "pkg1", "1.0")) - os.symlink("1.0", os.path.join(self.temp_dir, "a", "pkg1", "current")) - os.makedirs(os.path.join(self.temp_dir, "b", "pkg1", "1.1")) - os.symlink("1.1", os.path.join(self.temp_dir, "b", "pkg1", "current")) - os.makedirs(os.path.join(self.temp_dir, "b", "pkg2", "0.1")) - os.symlink("0.1", os.path.join(self.temp_dir, "b", "pkg2", "current")) - self.assertEqual([ - ("pkg1", "1.1", os.path.join(self.temp_dir, "b", "pkg1", "1.1"), - True), - ("pkg2", "0.1", os.path.join(self.temp_dir, "b", "pkg2", "0.1"), - True), - ("pkg1", "1.0", os.path.join(self.temp_dir, "a", "pkg1", "1.0"), - False), - ], self._installed_packages_tuplify( - db.get_packages(all_versions=True))) - - def test_manifest(self): - with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: - print("[Click Database]", file=a) - print("root = %s" % os.path.join(self.temp_dir, "a"), file=a) - with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: - print("[Click Database]", file=b) - print("root = %s" % os.path.join(self.temp_dir, "b"), file=b) - db = Click.DB() - db.read(db_dir=self.temp_dir) - self.assertRaisesDatabaseError( - Click.DatabaseError.DOES_NOT_EXIST, db.get_manifest, "pkg", "1.0") - self.assertRaisesDatabaseError( - Click.DatabaseError.DOES_NOT_EXIST, - db.get_manifest_as_string, "pkg", "1.0") - a_manifest_path = os.path.join( - self.temp_dir, "a", "pkg", "1.0", ".click", "info", "pkg.manifest") - a_manifest_obj = {"name": "pkg", "version": "1.0"} - with mkfile(a_manifest_path) as a_manifest: - json.dump(a_manifest_obj, a_manifest) - a_manifest_obj["_directory"] = os.path.join( - self.temp_dir, "a", "pkg", "1.0") - self.assertEqual( - a_manifest_obj, - json_object_to_python(db.get_manifest("pkg", "1.0"))) - self.assertEqual( - a_manifest_obj, - json.loads(db.get_manifest_as_string("pkg", "1.0"))) - self.assertRaisesDatabaseError( - Click.DatabaseError.DOES_NOT_EXIST, db.get_manifest, "pkg", "1.1") - self.assertRaisesDatabaseError( - Click.DatabaseError.DOES_NOT_EXIST, - db.get_manifest_as_string, "pkg", "1.1") - b_manifest_path = os.path.join( - self.temp_dir, "b", "pkg", "1.1", ".click", "info", "pkg.manifest") - b_manifest_obj = {"name": "pkg", "version": "1.1"} - with mkfile(b_manifest_path) as b_manifest: - json.dump(b_manifest_obj, b_manifest) - b_manifest_obj["_directory"] = os.path.join( - self.temp_dir, "b", "pkg", "1.1") - self.assertEqual( - b_manifest_obj, - json_object_to_python(db.get_manifest("pkg", "1.1"))) - self.assertEqual( - b_manifest_obj, - json.loads(db.get_manifest_as_string("pkg", "1.1"))) - - def test_manifest_bad(self): - with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: - print("[Click Database]", file=a) - print("root = %s" % os.path.join(self.temp_dir, "a"), file=a) - db = Click.DB() - db.read(db_dir=self.temp_dir) - manifest_path = os.path.join( - self.temp_dir, "a", "pkg", "1.0", ".click", "info", "pkg.manifest") - with mkfile(manifest_path) as manifest: - print("{bad syntax", file=manifest) - self.assertRaisesDatabaseError( - Click.DatabaseError.BAD_MANIFEST, db.get_manifest, "pkg", "1.0") - self.assertRaisesDatabaseError( - Click.DatabaseError.BAD_MANIFEST, - db.get_manifest_as_string, "pkg", "1.0") - manifest_path = os.path.join( - self.temp_dir, "a", "pkg", "1.1", ".click", "info", "pkg.manifest") - with mkfile(manifest_path) as manifest: - print("[0]", file=manifest) - self.assertRaisesDatabaseError( - Click.DatabaseError.BAD_MANIFEST, db.get_manifest, "pkg", "1.0") - self.assertRaisesDatabaseError( - Click.DatabaseError.BAD_MANIFEST, - db.get_manifest_as_string, "pkg", "1.0") - - def test_manifests_current(self): - with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: - print("[Click Database]", file=a) - print("root = %s" % os.path.join(self.temp_dir, "a"), file=a) - with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: - print("[Click Database]", file=b) - print("root = %s" % os.path.join(self.temp_dir, "b"), file=b) - db = Click.DB() - db.read(db_dir=self.temp_dir) - self.assertEqual( - [], json_array_to_python(db.get_manifests(all_versions=False))) - self.assertEqual( - [], json.loads(db.get_manifests_as_string(all_versions=False))) - a_pkg1_manifest_path = os.path.join( - self.temp_dir, "a", "pkg1", "1.0", - ".click", "info", "pkg1.manifest") - a_pkg1_manifest_obj = {"name": "pkg1", "version": "1.0"} - with mkfile(a_pkg1_manifest_path) as a_pkg1_manifest: - json.dump(a_pkg1_manifest_obj, a_pkg1_manifest) - os.symlink("1.0", os.path.join(self.temp_dir, "a", "pkg1", "current")) - b_pkg1_manifest_path = os.path.join( - self.temp_dir, "b", "pkg1", "1.1", - ".click", "info", "pkg1.manifest") - b_pkg1_manifest_obj = {"name": "pkg1", "version": "1.1"} - with mkfile(b_pkg1_manifest_path) as b_pkg1_manifest: - json.dump(b_pkg1_manifest_obj, b_pkg1_manifest) - os.symlink("1.1", os.path.join(self.temp_dir, "b", "pkg1", "current")) - b_pkg2_manifest_path = os.path.join( - self.temp_dir, "b", "pkg2", "0.1", - ".click", "info", "pkg2.manifest") - b_pkg2_manifest_obj = {"name": "pkg2", "version": "0.1"} - with mkfile(b_pkg2_manifest_path) as b_pkg2_manifest: - json.dump(b_pkg2_manifest_obj, b_pkg2_manifest) - os.symlink("0.1", os.path.join(self.temp_dir, "b", "pkg2", "current")) - b_pkg1_manifest_obj["_directory"] = os.path.join( - self.temp_dir, "b", "pkg1", "1.1") - b_pkg1_manifest_obj["_removable"] = 1 - b_pkg2_manifest_obj["_directory"] = os.path.join( - self.temp_dir, "b", "pkg2", "0.1") - b_pkg2_manifest_obj["_removable"] = 1 - self.assertEqual( - [b_pkg1_manifest_obj, b_pkg2_manifest_obj], - json_array_to_python(db.get_manifests(all_versions=False))) - self.assertEqual( - [b_pkg1_manifest_obj, b_pkg2_manifest_obj], - json.loads(db.get_manifests_as_string(all_versions=False))) - - def test_manifests_all(self): - with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: - print("[Click Database]", file=a) - print("root = %s" % os.path.join(self.temp_dir, "a"), file=a) - with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: - print("[Click Database]", file=b) - print("root = %s" % os.path.join(self.temp_dir, "b"), file=b) - db = Click.DB() - db.read(db_dir=self.temp_dir) - self.assertEqual( - [], json_array_to_python(db.get_manifests(all_versions=True))) - self.assertEqual( - [], json.loads(db.get_manifests_as_string(all_versions=True))) - a_pkg1_manifest_path = os.path.join( - self.temp_dir, "a", "pkg1", "1.0", - ".click", "info", "pkg1.manifest") - a_pkg1_manifest_obj = {"name": "pkg1", "version": "1.0"} - with mkfile(a_pkg1_manifest_path) as a_pkg1_manifest: - json.dump(a_pkg1_manifest_obj, a_pkg1_manifest) - os.symlink("1.0", os.path.join(self.temp_dir, "a", "pkg1", "current")) - b_pkg1_manifest_path = os.path.join( - self.temp_dir, "b", "pkg1", "1.1", - ".click", "info", "pkg1.manifest") - b_pkg1_manifest_obj = {"name": "pkg1", "version": "1.1"} - with mkfile(b_pkg1_manifest_path) as b_pkg1_manifest: - json.dump(b_pkg1_manifest_obj, b_pkg1_manifest) - os.symlink("1.1", os.path.join(self.temp_dir, "b", "pkg1", "current")) - b_pkg2_manifest_path = os.path.join( - self.temp_dir, "b", "pkg2", "0.1", - ".click", "info", "pkg2.manifest") - b_pkg2_manifest_obj = {"name": "pkg2", "version": "0.1"} - with mkfile(b_pkg2_manifest_path) as b_pkg2_manifest: - json.dump(b_pkg2_manifest_obj, b_pkg2_manifest) - os.symlink("0.1", os.path.join(self.temp_dir, "b", "pkg2", "current")) - a_pkg1_manifest_obj["_directory"] = os.path.join( - self.temp_dir, "a", "pkg1", "1.0") - a_pkg1_manifest_obj["_removable"] = 0 - b_pkg1_manifest_obj["_directory"] = os.path.join( - self.temp_dir, "b", "pkg1", "1.1") - b_pkg1_manifest_obj["_removable"] = 1 - b_pkg2_manifest_obj["_directory"] = os.path.join( - self.temp_dir, "b", "pkg2", "0.1") - b_pkg2_manifest_obj["_removable"] = 1 - self.assertEqual( - [b_pkg1_manifest_obj, b_pkg2_manifest_obj, a_pkg1_manifest_obj], - json_array_to_python(db.get_manifests(all_versions=True))) - self.assertEqual( - [b_pkg1_manifest_obj, b_pkg2_manifest_obj, a_pkg1_manifest_obj], - json.loads(db.get_manifests_as_string(all_versions=True))) diff -Nru click-0.4.43+16.04.20160203/click/tests/test_framework.py click-6.7/click/tests/test_framework.py --- click-0.4.43+16.04.20160203/click/tests/test_framework.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/test_framework.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,142 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Unit tests for click.framework.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'TestClickFramework', - ] - - -import os - -from gi.repository import Click - -from click.tests.helpers import TestCase, touch - - -class TestClickFramework(TestCase): - def setUp(self): - super(TestClickFramework, self).setUp() - self.use_temp_dir() - - def _setup_frameworks(self, preloads, frameworks_dir=None, frameworks={}): - if frameworks_dir is None: - frameworks_dir = os.path.join(self.temp_dir, "frameworks") - Click.ensuredir(frameworks_dir) - for framework_name in frameworks: - framework_path = os.path.join( - frameworks_dir, "%s.framework" % framework_name) - with open(framework_path, "w") as framework: - for key, value in frameworks[framework_name].items(): - print("%s: %s" % (key, value), file=framework) - preloads["click_get_frameworks_dir"].side_effect = ( - lambda: self.make_string(frameworks_dir)) - - def test_open(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - self._setup_frameworks(preloads, frameworks={"framework-1": {}}) - Click.Framework.open("framework-1") - self.assertRaisesFrameworkError( - Click.FrameworkError.NO_SUCH_FRAMEWORK, - Click.Framework.open, "framework-2") - - def test_has_framework(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - self._setup_frameworks(preloads, frameworks={"framework-1": {}}) - self.assertTrue(Click.Framework.has_framework("framework-1")) - self.assertFalse(Click.Framework.has_framework("framework-2")) - - def test_get_frameworks(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - self._setup_frameworks( - preloads, - frameworks={"ubuntu-sdk-13.10": {}, "ubuntu-sdk-14.04": {}, - "ubuntu-sdk-14.10": {}}) - self.assertEqual( - ["ubuntu-sdk-13.10", "ubuntu-sdk-14.04", "ubuntu-sdk-14.10"], - sorted(f.props.name for f in Click.Framework.get_frameworks())) - - def test_get_frameworks_nonexistent(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - frameworks_dir = os.path.join(self.temp_dir, "nonexistent") - preloads["click_get_frameworks_dir"].side_effect = ( - lambda: self.make_string(frameworks_dir)) - self.assertEqual([], Click.Framework.get_frameworks()) - - def test_get_frameworks_not_directory(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - path = os.path.join(self.temp_dir, "file") - touch(path) - preloads["click_get_frameworks_dir"].side_effect = ( - lambda: self.make_string(path)) - self.assertEqual([], Click.Framework.get_frameworks()) - - def test_get_frameworks_ignores_other_files(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - frameworks_dir = os.path.join(self.temp_dir, "frameworks") - Click.ensuredir(frameworks_dir) - touch(os.path.join(frameworks_dir, "file")) - preloads["click_get_frameworks_dir"].side_effect = ( - lambda: self.make_string(frameworks_dir)) - self.assertEqual([], Click.Framework.get_frameworks()) - - def test_get_frameworks_ignores_unopenable_files(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - frameworks_dir = os.path.join(self.temp_dir, "frameworks") - Click.ensuredir(frameworks_dir) - os.symlink( - "nonexistent", os.path.join(frameworks_dir, "foo.framework")) - preloads["click_get_frameworks_dir"].side_effect = ( - lambda: self.make_string(frameworks_dir)) - self.assertEqual([], Click.Framework.get_frameworks()) - - def test_fields(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - self._setup_frameworks( - preloads, - frameworks={ - "ubuntu-sdk-14.04-qml": { - "base-name": "ubuntu-sdk", "base-version": "14.04", - }}) - framework = Click.Framework.open("ubuntu-sdk-14.04-qml") - self.assertCountEqual( - ["base-name", "base-version"], framework.get_fields()) - self.assertEqual("ubuntu-sdk", framework.get_field("base-name")) - self.assertEqual("14.04", framework.get_field("base-version")) - self.assertRaisesFrameworkError( - Click.FrameworkError.MISSING_FIELD, - framework.get_field, "nonexistent") - self.assertEqual("ubuntu-sdk", framework.get_base_name()) - self.assertEqual("14.04", framework.get_base_version()) diff -Nru click-0.4.43+16.04.20160203/click/tests/test_hooks.py click-6.7/click/tests/test_hooks.py --- click-0.4.43+16.04.20160203/click/tests/test_hooks.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/test_hooks.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,1232 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Unit tests for click.hooks.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - "TestClickHookSystemLevel", - "TestClickHookUserLevel", - "TestClickPatternFormatter", - "TestPackageInstallHooks", - "TestPackageRemoveHooks", - ] - - -from functools import partial -from itertools import takewhile -import json -import os -from textwrap import dedent - -from gi.repository import Click, GLib - -from click.tests.gimock_types import Passwd -from click.tests.helpers import TestCase, mkfile, mkfile_utf8 - - -class TestClickPatternFormatter(TestCase): - def _make_variant(self, **kwargs): - # pygobject's Variant creator can't handle maybe types, so we have - # to do this by hand. - builder = GLib.VariantBuilder.new(GLib.VariantType.new("a{sms}")) - for key, value in kwargs.items(): - entry = GLib.VariantBuilder.new(GLib.VariantType.new("{sms}")) - entry.add_value(GLib.Variant.new_string(key)) - entry.add_value(GLib.Variant.new_maybe( - GLib.VariantType.new("s"), - None if value is None else GLib.Variant.new_string(value))) - builder.add_value(entry.end()) - return builder.end() - - def test_expands_provided_keys(self): - self.assertEqual( - "foo.bar", - Click.pattern_format("foo.${key}", self._make_variant(key="bar"))) - self.assertEqual( - "foo.barbaz", - Click.pattern_format( - "foo.${key1}${key2}", - self._make_variant(key1="bar", key2="baz"))) - - def test_expands_missing_keys_to_empty_string(self): - self.assertEqual( - "xy", Click.pattern_format("x${key}y", self._make_variant())) - - def test_preserves_unmatched_dollar(self): - self.assertEqual("$", Click.pattern_format("$", self._make_variant())) - self.assertEqual( - "$ {foo}", Click.pattern_format("$ {foo}", self._make_variant())) - self.assertEqual( - "x${y", - Click.pattern_format("${key}${y", self._make_variant(key="x"))) - - def test_double_dollar(self): - self.assertEqual("$", Click.pattern_format("$$", self._make_variant())) - self.assertEqual( - "${foo}", Click.pattern_format("$${foo}", self._make_variant())) - self.assertEqual( - "x$y", - Click.pattern_format("x$$${key}", self._make_variant(key="y"))) - - def test_possible_expansion(self): - self.assertEqual( - {"id": "abc"}, - Click.pattern_possible_expansion( - "x_abc_1", "x_${id}_${num}", - self._make_variant(num="1")).unpack()) - self.assertIsNone( - Click.pattern_possible_expansion( - "x_abc_1", "x_${id}_${num}", self._make_variant(num="2"))) - - -class TestClickHookBase(TestCase): - - TEST_USER = "test-user" - - def setUp(self): - super(TestClickHookBase, self).setUp() - self.use_temp_dir() - self.db = Click.DB() - self.db.add(self.temp_dir) - self.spawn_calls = [] - - def _make_installed_click(self, package="test-1", version="1.0", - json_data={}, - make_current=True, - all_users=False): - with mkfile_utf8(os.path.join( - self.temp_dir, package, version, ".click", "info", - "%s.manifest" % package)) as f: - json.dump(json_data, f, ensure_ascii=False) - if make_current: - os.symlink( - version, os.path.join(self.temp_dir, package, "current")) - if all_users: - db = Click.User.for_all_users(self.db) - else: - db = Click.User.for_user(self.db, self.TEST_USER) - db.set_version(package, version) - - def _make_hook_file(self, content, hookname="test"): - hook_file = os.path.join(self.hooks_dir, "%s.hook" % hookname) - with mkfile(hook_file) as f: - print(content, file=f) - - def _setup_hooks_dir(self, preloads, hooks_dir=None): - if hooks_dir is None: - hooks_dir = self.temp_dir - preloads["click_get_hooks_dir"].side_effect = ( - lambda: self.make_string(hooks_dir)) - self.hooks_dir = hooks_dir - - def g_spawn_sync_side_effect(self, status_map, working_directory, argv, - envp, flags, child_setup, user_data, - standard_output, standard_error, exit_status, - error): - self.spawn_calls.append(list(takewhile(lambda x: x is not None, argv))) - if argv[0] in status_map: - exit_status[0] = status_map[argv[0]] - else: - self.delegate_to_original("g_spawn_sync") - return 0 - - -class TestClickHookSystemLevel(TestClickHookBase): - def test_open(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file(dedent("""\ - Pattern: /usr/share/test/${id}.test - # Comment - Exec: test-update - User: root - """)) - hook = Click.Hook.open(self.db, "test") - self.assertCountEqual( - ["pattern", "exec", "user"], hook.get_fields()) - self.assertEqual( - "/usr/share/test/${id}.test", hook.get_field("pattern")) - self.assertEqual("test-update", hook.get_field("exec")) - self.assertRaisesHooksError( - Click.HooksError.MISSING_FIELD, hook.get_field, "nonexistent") - self.assertFalse(hook.props.is_user_level) - - def test_open_unopenable_file(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - os.symlink("nonexistent", os.path.join(self.hooks_dir, "foo.hook")) - self.assertRaisesHooksError( - Click.HooksError.NO_SUCH_HOOK, Click.Hook.open, self.db, "foo") - - def test_hook_name_absent(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file( - "Pattern: /usr/share/test/${id}.test") - hook = Click.Hook.open(self.db, "test") - self.assertEqual("test", hook.get_hook_name()) - - def test_hook_name_present(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file(dedent("""\ - Pattern: /usr/share/test/${id}.test - Hook-Name: other""")) - hook = Click.Hook.open(self.db, "test") - self.assertEqual("other", hook.get_hook_name()) - - def test_invalid_app_id(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file(dedent("""\ - Pattern: /usr/share/test/${id}.test - # Comment - Exec: test-update - User: root - """)) - hook = Click.Hook.open(self.db, "test") - self.assertRaisesHooksError( - Click.HooksError.BAD_APP_NAME, hook.get_app_id, - "package", "0.1", "app_name") - self.assertRaisesHooksError( - Click.HooksError.BAD_APP_NAME, hook.get_app_id, - "package", "0.1", "app/name") - self.assertRaisesHooksError( - Click.HooksError.BAD_APP_NAME, hook.get_pattern, - "package", "0.1", "app_name") - self.assertRaisesHooksError( - Click.HooksError.BAD_APP_NAME, hook.get_pattern, - "package", "0.1", "app/name") - - def test_short_id_invalid(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file( - "Pattern: /usr/share/test/${short-id}.test") - hook = Click.Hook.open(self.db, "test") - # It would perhaps be better if unrecognised $-expansions raised - # KeyError, but they don't right now. - self.assertEqual( - "/usr/share/test/.test", - hook.get_pattern("package", "0.1", "app-name", user_name=None)) - - def test_short_id_valid_with_single_version(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file(dedent("""\ - Pattern: /usr/share/test/${short-id}.test - Single-Version: yes""")) - hook = Click.Hook.open(self.db, "test") - self.assertEqual( - "/usr/share/test/package_app-name.test", - hook.get_pattern("package", "0.1", "app-name", user_name=None)) - - def test_run_commands(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "g_spawn_sync") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"/bin/sh": 0}) - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: - print("Exec: test-update", file=f) - print("User: root", file=f) - hook = Click.Hook.open(self.db, "test") - self.assertEqual( - "root", hook.get_run_commands_user(user_name=None)) - hook.run_commands(user_name=None) - self.assertEqual( - [[b"/bin/sh", b"-c", b"test-update"]], self.spawn_calls) - - def test_run_commands_fail(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "g_spawn_sync") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"/bin/sh": 1}) - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: - print("Exec: test-update", file=f) - print("User: root", file=f) - hook = Click.Hook.open(self.db, "test") - self.assertRaisesHooksError( - Click.HooksError.COMMAND_FAILED, hook.run_commands, - user_name=None) - - def test_install_package(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file( - "Pattern: %s/${id}.test" % self.temp_dir) - os.makedirs( - os.path.join(self.temp_dir, "org.example.package", "1.0")) - hook = Click.Hook.open(self.db, "test") - hook.install_package( - "org.example.package", "1.0", "test-app", "foo/bar", - user_name=None) - symlink_path = os.path.join( - self.temp_dir, "org.example.package_test-app_1.0.test") - target_path = os.path.join( - self.temp_dir, "org.example.package", "1.0", "foo", "bar") - self.assertTrue(os.path.islink(symlink_path)) - self.assertEqual(target_path, os.readlink(symlink_path)) - - def test_install_package_trailing_slash(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file( - "Pattern: %s/${id}/" % self.temp_dir) - os.makedirs( - os.path.join(self.temp_dir, "org.example.package", "1.0")) - hook = Click.Hook.open(self.db, "test") - hook.install_package( - "org.example.package", "1.0", "test-app", "foo", - user_name=None) - symlink_path = os.path.join( - self.temp_dir, "org.example.package_test-app_1.0") - target_path = os.path.join( - self.temp_dir, "org.example.package", "1.0", "foo") - self.assertTrue(os.path.islink(symlink_path)) - self.assertEqual(target_path, os.readlink(symlink_path)) - - def test_install_package_uses_deepest_copy(self): - # If the same version of a package is unpacked in multiple - # databases, then we make sure the link points to the deepest copy, - # even if it already points somewhere else. It is important to be - # consistent about this since system hooks may only have a single - # target for any given application ID. - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file( - "Pattern: %s/${id}.test" % self.temp_dir) - underlay = os.path.join(self.temp_dir, "underlay") - overlay = os.path.join(self.temp_dir, "overlay") - db = Click.DB() - db.add(underlay) - db.add(overlay) - os.makedirs(os.path.join(underlay, "org.example.package", "1.0")) - os.makedirs(os.path.join(overlay, "org.example.package", "1.0")) - symlink_path = os.path.join( - self.temp_dir, "org.example.package_test-app_1.0.test") - underlay_target_path = os.path.join( - underlay, "org.example.package", "1.0", "foo") - overlay_target_path = os.path.join( - overlay, "org.example.package", "1.0", "foo") - os.symlink(overlay_target_path, symlink_path) - hook = Click.Hook.open(db, "test") - hook.install_package( - "org.example.package", "1.0", "test-app", "foo", - user_name=None) - self.assertTrue(os.path.islink(symlink_path)) - self.assertEqual(underlay_target_path, os.readlink(symlink_path)) - - def test_upgrade(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file( - "Pattern: %s/${id}.test" % self.temp_dir) - symlink_path = os.path.join( - self.temp_dir, "org.example.package_test-app_1.0.test") - os.symlink("old-target", symlink_path) - os.makedirs( - os.path.join(self.temp_dir, "org.example.package", "1.0")) - hook = Click.Hook.open(self.db, "test") - hook.install_package( - "org.example.package", "1.0", "test-app", "foo/bar", - user_name=None) - target_path = os.path.join( - self.temp_dir, "org.example.package", "1.0", "foo", "bar") - self.assertTrue(os.path.islink(symlink_path)) - self.assertEqual(target_path, os.readlink(symlink_path)) - - def test_remove_package(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file( - "Pattern: %s/${id}.test" % self.temp_dir) - symlink_path = os.path.join( - self.temp_dir, "org.example.package_test-app_1.0.test") - os.symlink("old-target", symlink_path) - hook = Click.Hook.open(self.db, "test") - hook.remove_package( - "org.example.package", "1.0", "test-app", user_name=None) - self.assertFalse(os.path.exists(symlink_path)) - - def test_install(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir( - preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) - self._make_hook_file( - "Pattern: %s/${id}.new" % self.temp_dir, - hookname="new") - self._make_installed_click("test-1", "1.0", json_data={ - "maintainer": - b"Unic\xc3\xb3de ".decode( - "UTF-8"), - "hooks": {"test1-app": {"new": "target-1"}}}) - self._make_installed_click("test-2", "2.0", json_data={ - "maintainer": - b"Unic\xc3\xb3de ".decode( - "UTF-8"), - "hooks": {"test1-app": {"new": "target-2"}}, - }) - hook = Click.Hook.open(self.db, "new") - hook.install(user_name=None) - path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.new") - self.assertTrue(os.path.lexists(path_1)) - self.assertEqual( - os.path.join(self.temp_dir, "test-1", "1.0", "target-1"), - os.readlink(path_1)) - path_2 = os.path.join(self.temp_dir, "test-2_test1-app_2.0.new") - self.assertTrue(os.path.lexists(path_2)) - self.assertEqual( - os.path.join(self.temp_dir, "test-2", "2.0", "target-2"), - os.readlink(path_2)) - - def test_remove(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir( - preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) - self._make_hook_file( - "Pattern: %s/${id}.old" % self.temp_dir, - hookname="old") - self._make_installed_click("test-1", "1.0", json_data={ - "hooks": {"test1-app": {"old": "target-1"}}}) - path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.old") - os.symlink( - os.path.join(self.temp_dir, "test-1", "1.0", "target-1"), - path_1) - self._make_installed_click("test-2", "2.0", json_data={ - "hooks": {"test2-app": {"old": "target-2"}}}) - path_2 = os.path.join(self.temp_dir, "test-2_test2-app_2.0.old") - os.symlink( - os.path.join(self.temp_dir, "test-2", "2.0", "target-2"), - path_2) - hook = Click.Hook.open(self.db, "old") - hook.remove(user_name=None) - self.assertFalse(os.path.exists(path_1)) - self.assertFalse(os.path.exists(path_2)) - - def test_sync(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir( - preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) - self._make_hook_file( - "Pattern: %s/${id}.test" % self.temp_dir) - self._make_installed_click("test-1", "1.0", json_data={ - "hooks": {"test1-app": {"test": "target-1"}}}) - self._make_installed_click( - "test-2", "1.0", make_current=False, - json_data={"hooks": {"test2-app": {"test": "target-2"}}}) - self._make_installed_click("test-2", "1.1", json_data={ - "hooks": {"test2-app": {"test": "target-2"}}}) - path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.test") - os.symlink( - os.path.join(self.temp_dir, "test-1", "1.0", "target-1"), - path_1) - path_2_1_0 = os.path.join( - self.temp_dir, "test-2_test2-app_1.0.test") - path_2_1_1 = os.path.join( - self.temp_dir, "test-2_test2-app_1.1.test") - path_3 = os.path.join(self.temp_dir, "test-3_test3-app_1.0.test") - os.symlink( - os.path.join(self.temp_dir, "test-3", "1.0", "target-3"), - path_3) - hook = Click.Hook.open(self.db, "test") - hook.sync(user_name=None) - self.assertTrue(os.path.lexists(path_1)) - self.assertEqual( - os.path.join(self.temp_dir, "test-1", "1.0", "target-1"), - os.readlink(path_1)) - self.assertTrue(os.path.lexists(path_2_1_0)) - self.assertEqual( - os.path.join(self.temp_dir, "test-2", "1.0", "target-2"), - os.readlink(path_2_1_0)) - self.assertTrue(os.path.lexists(path_2_1_1)) - self.assertEqual( - os.path.join(self.temp_dir, "test-2", "1.1", "target-2"), - os.readlink(path_2_1_1)) - self.assertFalse(os.path.lexists(path_3)) - - -class TestClickHookUserLevel(TestClickHookBase): - def test_open(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file(dedent("""\ - User-Level: yes - Pattern: ${home}/.local/share/test/${id}.test - # Comment - Exec: test-update - """)) - hook = Click.Hook.open(self.db, "test") - self.assertCountEqual( - ["user-level", "pattern", "exec"], hook.get_fields()) - self.assertEqual( - "${home}/.local/share/test/${id}.test", - hook.get_field("pattern")) - self.assertEqual("test-update", hook.get_field("exec")) - self.assertRaisesHooksError( - Click.HooksError.MISSING_FIELD, hook.get_field, "nonexistent") - self.assertTrue(hook.props.is_user_level) - - def test_hook_name_absent(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file(dedent("""\ - User-Level: yes - Pattern: ${home}/.local/share/test/${id}.test""")) - hook = Click.Hook.open(self.db, "test") - self.assertEqual("test", hook.get_hook_name()) - - def test_hook_name_present(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file(dedent("""\ - User-Level: yes - Pattern: ${home}/.local/share/test/${id}.test - Hook-Name: other""")) - hook = Click.Hook.open(self.db, "test") - self.assertEqual("other", hook.get_hook_name()) - - def test_invalid_app_id(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file(dedent("""\ - User-Level: yes - Pattern: ${home}/.local/share/test/${id}.test - # Comment - Exec: test-update""")) - hook = Click.Hook.open(self.db, "test") - self.assertRaisesHooksError( - Click.HooksError.BAD_APP_NAME, hook.get_app_id, - "package", "0.1", "app_name") - self.assertRaisesHooksError( - Click.HooksError.BAD_APP_NAME, hook.get_app_id, - "package", "0.1", "app/name") - self.assertRaisesHooksError( - Click.HooksError.BAD_APP_NAME, hook.get_pattern, - "package", "0.1", "app_name") - self.assertRaisesHooksError( - Click.HooksError.BAD_APP_NAME, hook.get_pattern, - "package", "0.1", "app/name") - - def test_short_id_valid(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "getpwnam") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - preloads["getpwnam"].side_effect = ( - lambda name: self.make_pointer(Passwd(pw_dir=b"/mock"))) - self._make_hook_file(dedent("""\ - User-Level: yes - Pattern: ${home}/.local/share/test/${short-id}.test - """)) - hook = Click.Hook.open(self.db, "test") - self.assertEqual( - "/mock/.local/share/test/package_app-name.test", - hook.get_pattern( - "package", "0.1", "app-name", user_name="mock")) - - def test_run_commands(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "g_spawn_sync") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"/bin/sh": 0}) - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: - print("User-Level: yes", file=f) - print("Exec: test-update", file=f) - hook = Click.Hook.open(self.db, "test") - self.assertEqual( - self.TEST_USER, - hook.get_run_commands_user(user_name=self.TEST_USER)) - hook.run_commands(user_name=self.TEST_USER) - self.assertEqual( - [[b"/bin/sh", b"-c", b"test-update"]], self.spawn_calls) - - def test_run_commands_fail(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "g_spawn_sync") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"/bin/sh": 1}) - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: - print("User-Level: yes", file=f) - print("Exec: test-update", file=f) - hook = Click.Hook.open(self.db, "test") - self.assertRaisesHooksError( - Click.HooksError.COMMAND_FAILED, hook.run_commands, - user_name=self.TEST_USER) - - def test_install_package(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", - ) as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - preloads["click_get_user_home"].return_value = "/home/test-user" - os.makedirs(os.path.join( - self.temp_dir, "org.example.package", "1.0")) - user_db = Click.User.for_user(self.db, self.TEST_USER) - user_db.set_version("org.example.package", "1.0") - self._make_hook_file(dedent("""\ - User-Level: yes - Pattern: %s/${id}.test""") % self.temp_dir) - hook = Click.Hook.open(self.db, "test") - hook.install_package( - "org.example.package", "1.0", "test-app", "foo/bar", - user_name=self.TEST_USER) - symlink_path = os.path.join( - self.temp_dir, "org.example.package_test-app_1.0.test") - target_path = os.path.join( - self.temp_dir, ".click", "users", self.TEST_USER, - "org.example.package", "foo", "bar") - self.assertTrue(os.path.islink(symlink_path)) - self.assertEqual(target_path, os.readlink(symlink_path)) - - def test_install_package_trailing_slash(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", - ) as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - preloads["click_get_user_home"].return_value = "/home/test-user" - os.makedirs(os.path.join( - self.temp_dir, "org.example.package", "1.0")) - user_db = Click.User.for_user(self.db, self.TEST_USER) - user_db.set_version("org.example.package", "1.0") - self._make_hook_file(dedent("""\ - User-Level: yes - Pattern: %s/${id}/""") % self.temp_dir) - hook = Click.Hook.open(self.db, "test") - hook.install_package( - "org.example.package", "1.0", "test-app", "foo", - user_name=self.TEST_USER) - symlink_path = os.path.join( - self.temp_dir, "org.example.package_test-app_1.0") - target_path = os.path.join( - self.temp_dir, ".click", "users", self.TEST_USER, - "org.example.package", "foo") - self.assertTrue(os.path.islink(symlink_path)) - self.assertEqual(target_path, os.readlink(symlink_path)) - - def test_install_package_removes_previous(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", - ) as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - preloads["click_get_user_home"].return_value = "/home/test-user" - os.makedirs(os.path.join( - self.temp_dir, "org.example.package", "1.0")) - os.makedirs(os.path.join( - self.temp_dir, "org.example.package", "1.1")) - user_db = Click.User.for_user(self.db, self.TEST_USER) - user_db.set_version("org.example.package", "1.0") - self._make_hook_file(dedent("""\ - User-Level: yes - Pattern: %s/${id}.test""") % self.temp_dir) - hook = Click.Hook.open(self.db, "test") - hook.install_package( - "org.example.package", "1.0", "test-app", "foo/bar", - user_name=self.TEST_USER) - hook.install_package( - "org.example.package", "1.1", "test-app", "foo/bar", - user_name=self.TEST_USER) - old_symlink_path = os.path.join( - self.temp_dir, "org.example.package_test-app_1.0.test") - symlink_path = os.path.join( - self.temp_dir, "org.example.package_test-app_1.1.test") - self.assertFalse(os.path.islink(old_symlink_path)) - self.assertTrue(os.path.islink(symlink_path)) - target_path = os.path.join( - self.temp_dir, ".click", "users", self.TEST_USER, - "org.example.package", "foo", "bar") - self.assertEqual(target_path, os.readlink(symlink_path)) - - def test_upgrade(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", - ) as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - preloads["click_get_user_home"].return_value = "/home/test-user" - symlink_path = os.path.join( - self.temp_dir, "org.example.package_test-app_1.0.test") - os.symlink("old-target", symlink_path) - os.makedirs(os.path.join( - self.temp_dir, "org.example.package", "1.0")) - user_db = Click.User.for_user(self.db, self.TEST_USER) - user_db.set_version("org.example.package", "1.0") - self._make_hook_file(dedent("""\ - User-Level: yes - Pattern: %s/${id}.test""") % self.temp_dir) - hook = Click.Hook.open(self.db, "test") - hook.install_package( - "org.example.package", "1.0", "test-app", "foo/bar", - user_name=self.TEST_USER) - target_path = os.path.join( - self.temp_dir, ".click", "users", self.TEST_USER, - "org.example.package", "foo", "bar") - self.assertTrue(os.path.islink(symlink_path)) - self.assertEqual(target_path, os.readlink(symlink_path)) - - def test_remove_package(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", - ) as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - preloads["click_get_user_home"].return_value = "/home/test-user" - self._make_hook_file(dedent("""\ - User-Level: yes - Pattern: %s/${id}.test""") % self.temp_dir) - symlink_path = os.path.join( - self.temp_dir, "org.example.package_test-app_1.0.test") - os.symlink("old-target", symlink_path) - hook = Click.Hook.open(self.db, "test") - hook.remove_package( - "org.example.package", "1.0", "test-app", - user_name=self.TEST_USER) - self.assertFalse(os.path.exists(symlink_path)) - - def test_install(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", "getpwnam" - ) as (enter, preloads): - enter() - # Don't tell click about the hooks directory yet. - self._setup_hooks_dir(preloads) - preloads["click_get_user_home"].return_value = "/home/test-user" - preloads["getpwnam"].side_effect = ( - lambda name: self.make_pointer(Passwd(pw_uid=1, pw_gid=1))) - with mkfile(os.path.join(self.temp_dir, "hooks", "new.hook")) as f: - print("User-Level: yes", file=f) - print("Pattern: %s/${id}.new" % self.temp_dir, file=f) - self._make_installed_click("test-1", "1.0", json_data={ - "maintainer": - b"Unic\xc3\xb3de ".decode( - "UTF-8"), - "hooks": {"test1-app": {"new": "target-1"}}, - }) - self._make_installed_click("test-2", "2.0", json_data={ - "maintainer": - b"Unic\xc3\xb3de ".decode( - "UTF-8"), - "hooks": {"test1-app": {"new": "target-2"}}, - }) - # Now tell click about the hooks directory and make sure it - # catches up correctly. - self._setup_hooks_dir( - preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) - hook = Click.Hook.open(self.db, "new") - hook.install(user_name=None) - path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.new") - self.assertTrue(os.path.lexists(path_1)) - self.assertEqual( - os.path.join( - self.temp_dir, ".click", "users", self.TEST_USER, "test-1", - "target-1"), - os.readlink(path_1)) - path_2 = os.path.join(self.temp_dir, "test-2_test1-app_2.0.new") - self.assertTrue(os.path.lexists(path_2)) - self.assertEqual( - os.path.join( - self.temp_dir, ".click", "users", self.TEST_USER, "test-2", - "target-2"), - os.readlink(path_2)) - - os.unlink(path_1) - os.unlink(path_2) - hook.install(user_name="another-user") - self.assertFalse(os.path.lexists(path_1)) - self.assertFalse(os.path.lexists(path_2)) - - hook.install(user_name=self.TEST_USER) - self.assertTrue(os.path.lexists(path_1)) - self.assertEqual( - os.path.join( - self.temp_dir, ".click", "users", self.TEST_USER, "test-1", - "target-1"), - os.readlink(path_1)) - self.assertTrue(os.path.lexists(path_2)) - self.assertEqual( - os.path.join( - self.temp_dir, ".click", "users", self.TEST_USER, "test-2", - "target-2"), - os.readlink(path_2)) - - def test_remove(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", - ) as (enter, preloads): - enter() - # Don't tell click about the hooks directory yet. - self._setup_hooks_dir(preloads) - preloads["click_get_user_home"].return_value = "/home/test-user" - with mkfile(os.path.join(self.temp_dir, "hooks", "old.hook")) as f: - print("User-Level: yes", file=f) - print("Pattern: %s/${id}.old" % self.temp_dir, file=f) - user_db = Click.User.for_user(self.db, self.TEST_USER) - self._make_installed_click("test-1", "1.0", json_data={ - "hooks": {"test1-app": {"old": "target-1"}}}) - path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.old") - os.symlink( - os.path.join(user_db.get_path("test-1"), "target-1"), path_1) - self._make_installed_click("test-2", "2.0", json_data={ - "hooks": {"test2-app": {"old": "target-2"}}}) - path_2 = os.path.join(self.temp_dir, "test-2_test2-app_2.0.old") - os.symlink( - os.path.join(user_db.get_path("test-2"), "target-2"), path_2) - # Now tell click about the hooks directory and make sure it - # catches up correctly. - self._setup_hooks_dir( - preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) - hook = Click.Hook.open(self.db, "old") - hook.remove(user_name=None) - self.assertFalse(os.path.exists(path_1)) - self.assertFalse(os.path.exists(path_2)) - - def test_sync(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", - ) as (enter, preloads): - enter() - preloads["click_get_user_home"].return_value = "/home/test-user" - self._setup_hooks_dir(preloads) - with mkfile( - os.path.join(self.temp_dir, "hooks", "test.hook")) as f: - print("User-Level: yes", file=f) - print("Pattern: %s/${id}.test" % self.temp_dir, file=f) - self._make_installed_click("test-1", "1.0", json_data={ - "hooks": {"test1-app": {"test": "target-1"}}}) - self._make_installed_click("test-2", "1.1", json_data={ - "hooks": {"test2-app": {"test": "target-2"}}}) - path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.test") - os.symlink( - os.path.join( - self.temp_dir, ".click", "users", self.TEST_USER, "test-1", - "target-1"), - path_1) - path_2 = os.path.join(self.temp_dir, "test-2_test2-app_1.1.test") - path_3 = os.path.join(self.temp_dir, "test-3_test3-app_1.0.test") - os.symlink( - os.path.join( - self.temp_dir, ".click", "users", self.TEST_USER, "test-3", - "target-3"), - path_3) - self._setup_hooks_dir( - preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) - hook = Click.Hook.open(self.db, "test") - hook.sync(user_name=self.TEST_USER) - self.assertTrue(os.path.lexists(path_1)) - self.assertEqual( - os.path.join( - self.temp_dir, ".click", "users", self.TEST_USER, "test-1", - "target-1"), - os.readlink(path_1)) - self.assertTrue(os.path.lexists(path_2)) - self.assertEqual( - os.path.join( - self.temp_dir, ".click", "users", self.TEST_USER, "test-2", - "target-2"), - os.readlink(path_2)) - self.assertFalse(os.path.lexists(path_3)) - - def test_sync_without_user_db(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", - ) as (enter, preloads): - enter() - preloads["click_get_user_home"].return_value = "/home/test-user" - self._setup_hooks_dir(preloads) - with mkfile( - os.path.join(self.temp_dir, "hooks", "test.hook")) as f: - print("User-Level: yes", file=f) - print("Pattern: %s/${id}.test" % self.temp_dir, file=f) - self._make_installed_click( - "test-package", "1.0", all_users=True, json_data={ - "hooks": {"test-app": {"test": "target"}}}) - self._setup_hooks_dir( - preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) - hook = Click.Hook.open(self.db, "test") - hook.sync(user_name=self.TEST_USER) - self.assertFalse(os.path.exists(os.path.join( - self.temp_dir, ".click", "users", self.TEST_USER, - "test-package"))) - - def test_sync_uses_deepest_copy(self): - # If the same version of a package is unpacked in multiple - # databases, then we make sure the user link points to the deepest - # copy, even if it already points somewhere else. It is important - # to be consistent about this since system hooks may only have a - # single target for any given application ID, and user links must - # match system hooks so that (for example) the version of an - # application run by a user has a matching system AppArmor profile. - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", - ) as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - preloads["click_get_user_home"].return_value = "/home/test-user" - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: - print("User-Level: yes", file=f) - print("Pattern: %s/${id}.test" % self.temp_dir, file=f) - underlay = os.path.join(self.temp_dir, "underlay") - overlay = os.path.join(self.temp_dir, "overlay") - db = Click.DB() - db.add(underlay) - db.add(overlay) - underlay_unpacked = os.path.join(underlay, "test-package", "1.0") - overlay_unpacked = os.path.join(overlay, "test-package", "1.0") - os.makedirs(underlay_unpacked) - os.makedirs(overlay_unpacked) - manifest = {"hooks": {"test-app": {"test": "foo"}}} - with mkfile(os.path.join( - underlay_unpacked, ".click", "info", - "test-package.manifest")) as f: - json.dump(manifest, f) - with mkfile(os.path.join( - overlay_unpacked, ".click", "info", - "test-package.manifest")) as f: - json.dump(manifest, f) - underlay_user_link = os.path.join( - underlay, ".click", "users", "@all", "test-package") - overlay_user_link = os.path.join( - overlay, ".click", "users", self.TEST_USER, "test-package") - Click.ensuredir(os.path.dirname(underlay_user_link)) - os.symlink(underlay_unpacked, underlay_user_link) - Click.ensuredir(os.path.dirname(overlay_user_link)) - os.symlink(overlay_unpacked, overlay_user_link) - symlink_path = os.path.join( - self.temp_dir, "test-package_test-app_1.0.test") - underlay_target_path = os.path.join(underlay_user_link, "foo") - overlay_target_path = os.path.join(overlay_user_link, "foo") - os.symlink(overlay_target_path, symlink_path) - hook = Click.Hook.open(db, "test") - hook.sync(user_name=self.TEST_USER) - self.assertTrue(os.path.islink(underlay_user_link)) - self.assertEqual( - underlay_unpacked, os.readlink(underlay_user_link)) - self.assertFalse(os.path.islink(overlay_user_link)) - self.assertTrue(os.path.islink(symlink_path)) - self.assertEqual(underlay_target_path, os.readlink(symlink_path)) - - -class TestPackageInstallHooks(TestClickHookBase): - def test_removes_old_hooks(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - hooks_dir = os.path.join(self.temp_dir, "hooks") - self._setup_hooks_dir(preloads, hooks_dir=hooks_dir) - with mkfile(os.path.join(hooks_dir, "unity.hook")) as f: - print("Pattern: %s/unity/${id}.scope" % self.temp_dir, file=f) - print("Single-Version: yes", file=f) - with mkfile(os.path.join(hooks_dir, "yelp-docs.hook")) as f: - print("Pattern: %s/yelp/docs-${id}.txt" % self.temp_dir, - file=f) - print("Single-Version: yes", file=f) - print("Hook-Name: yelp", file=f) - with mkfile(os.path.join(hooks_dir, "yelp-other.hook")) as f: - print("Pattern: %s/yelp/other-${id}.txt" % self.temp_dir, - file=f) - print("Single-Version: yes", file=f) - print("Hook-Name: yelp", file=f) - os.mkdir(os.path.join(self.temp_dir, "unity")) - unity_path = os.path.join( - self.temp_dir, "unity", "test_app_1.0.scope") - os.symlink("dummy", unity_path) - os.mkdir(os.path.join(self.temp_dir, "yelp")) - yelp_docs_path = os.path.join( - self.temp_dir, "yelp", "docs-test_app_1.0.txt") - os.symlink("dummy", yelp_docs_path) - yelp_other_path = os.path.join( - self.temp_dir, "yelp", "other-test_app_1.0.txt") - os.symlink("dummy", yelp_other_path) - self._make_installed_click("test", "1.0", make_current=False, json_data={ - "hooks": {"app": {"yelp": "foo.txt", "unity": "foo.scope"}}}) - self._make_installed_click("test", "1.1", json_data={}) - Click.package_install_hooks( - self.db, "test", "1.0", "1.1", user_name=None) - self.assertFalse(os.path.lexists(unity_path)) - self.assertFalse(os.path.lexists(yelp_docs_path)) - self.assertFalse(os.path.lexists(yelp_other_path)) - - def test_installs_new_hooks(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - hooks_dir = os.path.join(self.temp_dir, "hooks") - self._setup_hooks_dir(preloads, hooks_dir=hooks_dir) - with mkfile(os.path.join(hooks_dir, "a.hook")) as f: - print("Pattern: %s/a/${id}.a" % self.temp_dir, file=f) - with mkfile(os.path.join(hooks_dir, "b-1.hook")) as f: - print("Pattern: %s/b/1-${id}.b" % self.temp_dir, file=f) - print("Hook-Name: b", file=f) - with mkfile(os.path.join(hooks_dir, "b-2.hook")) as f: - print("Pattern: %s/b/2-${id}.b" % self.temp_dir, file=f) - print("Hook-Name: b", file=f) - os.mkdir(os.path.join(self.temp_dir, "a")) - os.mkdir(os.path.join(self.temp_dir, "b")) - self._make_installed_click( - "test", "1.0", make_current=False, json_data={"hooks": {}}) - self._make_installed_click("test", "1.1", json_data={ - "hooks": {"app": {"a": "foo.a", "b": "foo.b"}}}) - Click.package_install_hooks( - self.db, "test", "1.0", "1.1", user_name=None) - self.assertTrue(os.path.lexists( - os.path.join(self.temp_dir, "a", "test_app_1.1.a"))) - self.assertTrue(os.path.lexists( - os.path.join(self.temp_dir, "b", "1-test_app_1.1.b"))) - self.assertTrue(os.path.lexists( - os.path.join(self.temp_dir, "b", "2-test_app_1.1.b"))) - - def test_upgrades_existing_hooks(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - hooks_dir = os.path.join(self.temp_dir, "hooks") - self._setup_hooks_dir(preloads, hooks_dir=hooks_dir) - with mkfile(os.path.join(hooks_dir, "a.hook")) as f: - print("Pattern: %s/a/${id}.a" % self.temp_dir, file=f) - print("Single-Version: yes", file=f) - with mkfile(os.path.join(hooks_dir, "b-1.hook")) as f: - print("Pattern: %s/b/1-${id}.b" % self.temp_dir, file=f) - print("Single-Version: yes", file=f) - print("Hook-Name: b", file=f) - with mkfile(os.path.join(hooks_dir, "b-2.hook")) as f: - print("Pattern: %s/b/2-${id}.b" % self.temp_dir, file=f) - print("Single-Version: yes", file=f) - print("Hook-Name: b", file=f) - with mkfile(os.path.join(hooks_dir, "c.hook")) as f: - print("Pattern: %s/c/${id}.c" % self.temp_dir, file=f) - print("Single-Version: yes", file=f) - os.mkdir(os.path.join(self.temp_dir, "a")) - a_path = os.path.join(self.temp_dir, "a", "test_app_1.0.a") - os.symlink("dummy", a_path) - os.mkdir(os.path.join(self.temp_dir, "b")) - b_irrelevant_path = os.path.join( - self.temp_dir, "b", "1-test_other-app_1.0.b") - os.symlink("dummy", b_irrelevant_path) - b_1_path = os.path.join(self.temp_dir, "b", "1-test_app_1.0.b") - os.symlink("dummy", b_1_path) - b_2_path = os.path.join(self.temp_dir, "b", "2-test_app_1.0.b") - os.symlink("dummy", b_2_path) - os.mkdir(os.path.join(self.temp_dir, "c")) - package_dir = os.path.join(self.temp_dir, "test") - with mkfile(os.path.join( - package_dir, "1.0", ".click", "info", - "test.manifest")) as f: - json.dump({"hooks": {"app": {"a": "foo.a", "b": "foo.b"}}}, f) - with mkfile(os.path.join( - package_dir, "1.1", ".click", "info", - "test.manifest")) as f: - json.dump( - {"hooks": { - "app": {"a": "foo.a", "b": "foo.b", "c": "foo.c"}} - }, f) - Click.package_install_hooks( - self.db, "test", "1.0", "1.1", user_name=None) - self.assertFalse(os.path.lexists(a_path)) - self.assertTrue(os.path.lexists(b_irrelevant_path)) - self.assertFalse(os.path.lexists(b_1_path)) - self.assertFalse(os.path.lexists(b_2_path)) - self.assertTrue(os.path.lexists( - os.path.join(self.temp_dir, "a", "test_app_1.1.a"))) - self.assertTrue(os.path.lexists( - os.path.join(self.temp_dir, "b", "1-test_app_1.1.b"))) - self.assertTrue(os.path.lexists( - os.path.join(self.temp_dir, "b", "2-test_app_1.1.b"))) - self.assertTrue(os.path.lexists( - os.path.join(self.temp_dir, "c", "test_app_1.1.c"))) - - -class TestPackageRemoveHooks(TestClickHookBase): - def test_removes_hooks(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - hooks_dir = os.path.join(self.temp_dir, "hooks") - self._setup_hooks_dir(preloads, hooks_dir=hooks_dir) - with mkfile(os.path.join(hooks_dir, "unity.hook")) as f: - print("Pattern: %s/unity/${id}.scope" % self.temp_dir, file=f) - with mkfile(os.path.join(hooks_dir, "yelp-docs.hook")) as f: - print("Pattern: %s/yelp/docs-${id}.txt" % self.temp_dir, - file=f) - print("Hook-Name: yelp", file=f) - with mkfile(os.path.join(hooks_dir, "yelp-other.hook")) as f: - print("Pattern: %s/yelp/other-${id}.txt" % self.temp_dir, - file=f) - print("Hook-Name: yelp", file=f) - os.mkdir(os.path.join(self.temp_dir, "unity")) - unity_path = os.path.join( - self.temp_dir, "unity", "test_app_1.0.scope") - os.symlink("dummy", unity_path) - os.mkdir(os.path.join(self.temp_dir, "yelp")) - yelp_docs_path = os.path.join( - self.temp_dir, "yelp", "docs-test_app_1.0.txt") - os.symlink("dummy", yelp_docs_path) - yelp_other_path = os.path.join( - self.temp_dir, "yelp", "other-test_app_1.0.txt") - os.symlink("dummy", yelp_other_path) - package_dir = os.path.join(self.temp_dir, "test") - with mkfile(os.path.join( - package_dir, "1.0", ".click", "info", - "test.manifest")) as f: - json.dump( - {"hooks": { - "app": {"yelp": "foo.txt", "unity": "foo.scope"}} - }, f) - Click.package_remove_hooks(self.db, "test", "1.0", user_name=None) - self.assertFalse(os.path.lexists(unity_path)) - self.assertFalse(os.path.lexists(yelp_docs_path)) - self.assertFalse(os.path.lexists(yelp_other_path)) - - -class TestPackageHooksValidateFramework(TestClickHookBase): - - def _setup_test_env(self, preloads): - preloads["click_get_user_home"].return_value = "/home/test-user" - self._setup_hooks_dir( - preloads, os.path.join(self.temp_dir, "hooks")) - self._make_hook_file(dedent("""\ - User-Level: yes - Pattern: %s/${id}.test - """) % self.temp_dir) - self.hook_symlink_path = os.path.join( - self.temp_dir, "test-1_test1-app_1.0.test") - - def test_links_are_kept_on_validate_framework(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", - "click_get_frameworks_dir", - ) as (enter, preloads): - enter() - self._setup_frameworks( - preloads, frameworks=["ubuntu-sdk-13.10"]) - self._setup_test_env(preloads) - self._make_installed_click(json_data={ - "framework": "ubuntu-sdk-13.10", - "hooks": { - "test1-app": {"test": "target-1"} - }, - }) - self.assertTrue(os.path.lexists(self.hook_symlink_path)) - # run the hooks - Click.run_user_hooks(self.db, user_name=self.TEST_USER) - self.assertTrue(os.path.lexists(self.hook_symlink_path)) - - def test_links_are_kept_multiple_frameworks(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", - "click_get_frameworks_dir", - ) as (enter, preloads): - enter() - self._setup_frameworks( - preloads, frameworks=["ubuntu-sdk-14.04", "ubuntu-sdk-13.10"]) - self._setup_test_env(preloads) - self._make_installed_click(json_data={ - "framework": "ubuntu-sdk-13.10", - "hooks": { - "test1-app": {"test": "target-1"} - }, - }) - self.assertTrue(os.path.lexists(self.hook_symlink_path)) - # run the hooks - Click.run_user_hooks(self.db, user_name=self.TEST_USER) - self.assertTrue(os.path.lexists(self.hook_symlink_path)) - - def test_links_are_removed_on_missing_framework(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", - "click_get_frameworks_dir", - ) as (enter, preloads): - enter() - self._setup_frameworks(preloads, frameworks=["missing"]) - self._setup_test_env(preloads) - self._make_installed_click(json_data={ - "framework": "ubuntu-sdk-13.10", - "hooks": { - "test1-app": {"test": "target-1"} - }, - }) - self.assertTrue(os.path.lexists(self.hook_symlink_path)) - # run the hooks - Click.run_user_hooks(self.db, user_name=self.TEST_USER) - self.assertFalse(os.path.lexists(self.hook_symlink_path)) - - def test_links_are_removed_on_missing_multiple_framework(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", - "click_get_frameworks_dir", - ) as (enter, preloads): - enter() - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - self._setup_test_env(preloads) - self._make_installed_click(json_data={ - "framework": "ubuntu-sdk-13.10, ubuntu-sdk-13.10-html", - "hooks": { - "test1-app": {"test": "target-1"} - }, - }) - self.assertTrue(os.path.lexists(self.hook_symlink_path)) - # run the hooks - Click.run_user_hooks(self.db, user_name=self.TEST_USER) - self.assertFalse(os.path.lexists(self.hook_symlink_path)) diff -Nru click-0.4.43+16.04.20160203/click/tests/test_install.py click-6.7/click/tests/test_install.py --- click-0.4.43+16.04.20160203/click/tests/test_install.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/test_install.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,771 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Unit tests for click.install.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'TestClickInstaller', - ] - - -from contextlib import ( - closing, - contextmanager, - ) -import hashlib -import json -import os -import shutil -import stat -import subprocess -import tarfile - -from unittest import skipUnless - -from debian.deb822 import Deb822 -from gi.repository import Click - -from click.arfile import ArFile -from click.build import ClickBuilder -from click.install import ( - ClickInstaller, - ClickInstallerAuditError, - ClickInstallerPermissionDenied, -) -from click.preinst import static_preinst -from click.tests.helpers import ( - disable_logging, - mkfile, - mock, - TestCase, - touch, -) -from click.versions import spec_version - - -@contextmanager -def mock_quiet_subprocess_call(): - original_call = subprocess.call - - def side_effect(*args, **kwargs): - if "TEST_VERBOSE" in os.environ: - return original_call(*args, **kwargs) - else: - with open("/dev/null", "w") as devnull: - return original_call( - *args, stdout=devnull, stderr=devnull, **kwargs) - - with mock.patch("subprocess.call") as mock_call: - mock_call.side_effect = side_effect - yield mock_call - - -class TestClickInstaller(TestCase): - def setUp(self): - super(TestClickInstaller, self).setUp() - self.use_temp_dir() - self.db = Click.DB() - self.db.add(self.temp_dir) - # mock signature checks during the tests - self.debsig_patcher = mock.patch("click.install.DebsigVerify") - self.debsig_patcher.start() - - def tearDown(self): - self.debsig_patcher.stop() - - def make_fake_package(self, control_fields=None, manifest=None, - control_scripts=None, data_files=None): - """Build a fake package with given contents.""" - control_fields = {} if control_fields is None else control_fields - control_scripts = {} if control_scripts is None else control_scripts - data_files = {} if data_files is None else data_files - - data_dir = os.path.join(self.temp_dir, "fake-package") - control_dir = os.path.join(self.temp_dir, "DEBIAN") - with mkfile(os.path.join(control_dir, "control")) as control: - for key, value in control_fields.items(): - print('%s: %s' % (key.title(), value), file=control) - print(file=control) - if manifest is not None: - with mkfile(os.path.join(control_dir, "manifest")) as f: - json.dump(manifest, f) - print(file=f) - for name, contents in control_scripts.items(): - with mkfile(os.path.join(control_dir, name)) as script: - script.write(contents) - Click.ensuredir(data_dir) - for name, path in data_files.items(): - Click.ensuredir(os.path.dirname(os.path.join(data_dir, name))) - if path is None: - touch(os.path.join(data_dir, name)) - elif os.path.isdir(path): - shutil.copytree(path, os.path.join(data_dir, name)) - else: - shutil.copy2(path, os.path.join(data_dir, name)) - package_path = '%s.click' % data_dir - ClickBuilder()._pack( - self.temp_dir, control_dir, data_dir, package_path) - return package_path - - def test_audit_no_click_version(self): - path = self.make_fake_package() - self.assertRaisesRegex( - ClickInstallerAuditError, "No Click-Version field", - ClickInstaller(self.db).audit, path) - - def test_audit_bad_click_version(self): - path = self.make_fake_package(control_fields={"Click-Version": "|"}) - self.assertRaises(ValueError, ClickInstaller(self.db).audit, path) - - def test_audit_new_click_version(self): - path = self.make_fake_package(control_fields={"Click-Version": "999"}) - self.assertRaisesRegex( - ClickInstallerAuditError, - "Click-Version: 999 newer than maximum supported version .*", - ClickInstaller(self.db).audit, path) - - def test_audit_forbids_depends(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - path = self.make_fake_package( - control_fields={ - "Click-Version": "0.2", - "Depends": "libc6", - }) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - self.assertRaisesRegex( - ClickInstallerAuditError, - "Depends field is forbidden in Click packages", - ClickInstaller(self.db).audit, path) - - def test_audit_forbids_maintscript(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - control_scripts={ - "preinst": "#! /bin/sh\n", - "postinst": "#! /bin/sh\n", - }) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - self.assertRaisesRegex( - ClickInstallerAuditError, - r"Maintainer scripts are forbidden in Click packages " - r"\(found: postinst preinst\)", - ClickInstaller(self.db).audit, path) - - def test_audit_requires_manifest(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - control_scripts={"preinst": static_preinst}) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - self.assertRaisesRegex( - ClickInstallerAuditError, "Package has no manifest", - ClickInstaller(self.db).audit, path) - - def test_audit_invalid_manifest_json(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - control_scripts={"manifest": "{", "preinst": static_preinst}) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - self.assertRaises(ValueError, ClickInstaller(self.db).audit, path) - - def test_audit_no_name(self): - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - manifest={}) - self.assertRaisesRegex( - ClickInstallerAuditError, 'No "name" entry in manifest', - ClickInstaller(self.db).audit, path) - - def test_audit_name_bad_character(self): - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - manifest={"name": "../evil"}) - self.assertRaisesRegex( - ClickInstallerAuditError, - 'Invalid character "/" in "name" entry: ../evil', - ClickInstaller(self.db).audit, path) - - def test_audit_no_version(self): - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - manifest={"name": "test-package"}) - self.assertRaisesRegex( - ClickInstallerAuditError, 'No "version" entry in manifest', - ClickInstaller(self.db).audit, path) - - def test_audit_no_framework(self): - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - manifest={"name": "test-package", "version": "1.0"}, - control_scripts={"preinst": static_preinst}) - self.assertRaisesRegex( - ClickInstallerAuditError, 'No "framework" entry in manifest', - ClickInstaller(self.db).audit, path) - - def test_audit_missing_framework(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - manifest={ - "name": "test-package", - "version": "1.0", - "framework": "missing", - }, - control_scripts={"preinst": static_preinst}) - self._setup_frameworks(preloads, frameworks=["present"]) - self.assertRaisesRegex( - ClickInstallerAuditError, - 'Framework "missing" not present on system.*', - ClickInstaller(self.db).audit, path) - - # FIXME: we really want a unit test with a valid signature too - def test_audit_no_signature(self): - if not Click.find_on_path("debsig-verify"): - self.skipTest("this test needs debsig-verify") - path = self.make_fake_package( - control_fields={"Click-Version": "0.4"}, - manifest={ - "name": "test-package", - "version": "1.0", - "framework": "", - }) - self.debsig_patcher.stop() - self.assertRaisesRegex( - ClickInstallerAuditError, "Signature verification error", - ClickInstaller(self.db).audit, path) - self.debsig_patcher.start() - - @disable_logging - def test_audit_missing_framework_force(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - manifest={ - "name": "test-package", - "version": "1.0", - "framework": "missing", - }) - self._setup_frameworks(preloads, frameworks=["present"]) - ClickInstaller(self.db, True).audit(path) - - def test_audit_passes_correct_package(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - manifest={ - "name": "test-package", - "version": "1.0", - "framework": "ubuntu-sdk-13.10", - }, - control_scripts={"preinst": static_preinst}) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - installer = ClickInstaller(self.db) - self.assertEqual(("test-package", "1.0"), installer.audit(path)) - - def test_audit_multiple_frameworks(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - path = self.make_fake_package( - control_fields={"Click-Version": "0.4"}, - manifest={ - "name": "test-package", - "version": "1.0", - "framework": - "ubuntu-sdk-14.04-basic, ubuntu-sdk-14.04-webapps", - }, - control_scripts={"preinst": static_preinst}) - installer = ClickInstaller(self.db) - self._setup_frameworks(preloads, frameworks=["dummy"]) - self.assertRaisesRegex( - ClickInstallerAuditError, - 'Frameworks "ubuntu-sdk-14.04-basic", ' - '"ubuntu-sdk-14.04-webapps" not present on system.*', - installer.audit, path) - self._setup_frameworks( - preloads, frameworks=["dummy", "ubuntu-sdk-14.04-basic"]) - self.assertRaisesRegex( - ClickInstallerAuditError, - 'Framework "ubuntu-sdk-14.04-webapps" not present on ' - 'system.*', - installer.audit, path) - self._setup_frameworks( - preloads, frameworks=[ - "dummy", "ubuntu-sdk-14.04-basic", - "ubuntu-sdk-14.04-webapps", - ]) - self.assertEqual(("test-package", "1.0"), installer.audit(path)) - - def test_audit_missing_dot_slash(self): - # Manually construct a package with data paths that do not start - # with "./", which could be used to bypass path filtering. - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - manifest={ - "name": "test-package", - "version": "1.0", - "framework": "ubuntu-sdk-13.10", - }, - control_scripts={"preinst": static_preinst}, - data_files={".click/tmp.ci/manifest": None}) - # Repack without the leading "./". - data_dir = os.path.join(self.temp_dir, "fake-package") - data_tar_path = os.path.join(self.temp_dir, "data.tar.gz") - control_tar_path = os.path.join(self.temp_dir, "control.tar.gz") - package_path = '%s.click' % data_dir - with closing(tarfile.TarFile.open( - name=data_tar_path, mode="w:gz", format=tarfile.GNU_FORMAT - )) as data_tar: - data_tar.add( - os.path.join(data_dir, ".click"), arcname=".click") - with ArFile(name=package_path, mode="w") as package: - package.add_magic() - package.add_data("debian-binary", b"2.0\n") - package.add_data( - "_click-binary", ("%s\n" % spec_version).encode("UTF-8")) - package.add_file("control.tar.gz", control_tar_path) - package.add_file("data.tar.gz", data_tar_path) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - with mock_quiet_subprocess_call(): - installer = ClickInstaller(self.db) - self.assertRaisesRegex( - ClickInstallerAuditError, - 'File name ".click" in package does not start with "./"', - installer.audit, path) - - def test_audit_broken_md5sums(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - manifest={ - "name": "test-package", - "version": "1.0", - "framework": "ubuntu-sdk-13.10", - }, - control_scripts={ - "preinst": static_preinst, - "md5sums": "%s foo" % ("0" * 32), - }, - data_files={"foo": None}) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - with mock_quiet_subprocess_call(): - installer = ClickInstaller(self.db) - self.assertRaises( - subprocess.CalledProcessError, installer.audit, - path, slow=True) - - def test_audit_matching_md5sums(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - data_path = os.path.join(self.temp_dir, "foo") - with mkfile(data_path) as data: - print("test", file=data) - with open(data_path, "rb") as data: - data_md5sum = hashlib.md5(data.read()).hexdigest() - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - manifest={ - "name": "test-package", - "version": "1.0", - "framework": "ubuntu-sdk-13.10", - }, - control_scripts={ - "preinst": static_preinst, - "md5sums": "%s foo" % data_md5sum, - }, - data_files={"foo": data_path}) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - with mock_quiet_subprocess_call(): - installer = ClickInstaller(self.db) - self.assertEqual( - ("test-package", "1.0"), installer.audit(path, slow=True)) - - def test_no_write_permission(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - manifest={ - "name": "test-package", - "version": "1.0", - "framework": "ubuntu-sdk-13.10", - }, - control_scripts={"preinst": static_preinst}) - write_mask = ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - installer = ClickInstaller(self.db) - temp_dir_mode = os.stat(self.temp_dir).st_mode - try: - os.chmod(self.temp_dir, temp_dir_mode & write_mask) - self.assertRaises( - ClickInstallerPermissionDenied, installer.install, path) - finally: - os.chmod(self.temp_dir, temp_dir_mode) - - @skipUnless( - os.path.exists(ClickInstaller(None)._preload_path()), - "preload bits not built; installing packages will fail") - @mock.patch("gi.repository.Click.package_install_hooks") - def test_install(self, mock_package_install_hooks): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - path = self.make_fake_package( - control_fields={ - "Package": "test-package", - "Version": "1.0", - "Architecture": "all", - "Maintainer": "Foo Bar ", - "Description": "test", - "Click-Version": "0.2", - }, - manifest={ - "name": "test-package", - "version": "1.0", - "framework": "ubuntu-sdk-13.10", - }, - control_scripts={"preinst": static_preinst}, - data_files={"foo": None}) - root = os.path.join(self.temp_dir, "root") - db = Click.DB() - db.add(root) - installer = ClickInstaller(db) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - with mock_quiet_subprocess_call(): - installer.install(path) - self.assertCountEqual([".click", "test-package"], os.listdir(root)) - package_dir = os.path.join(root, "test-package") - self.assertCountEqual(["1.0", "current"], os.listdir(package_dir)) - inst_dir = os.path.join(package_dir, "current") - self.assertTrue(os.path.islink(inst_dir)) - self.assertEqual("1.0", os.readlink(inst_dir)) - self.assertCountEqual([".click", "foo"], os.listdir(inst_dir)) - status_path = os.path.join(inst_dir, ".click", "status") - with open(status_path) as status_file: - # .readlines() avoids the need for a python-apt backport to - # Ubuntu 12.04 LTS. - status = list(Deb822.iter_paragraphs(status_file.readlines())) - self.assertEqual(1, len(status)) - self.assertEqual({ - "Package": "test-package", - "Status": "install ok installed", - "Version": "1.0", - "Architecture": "all", - "Maintainer": "Foo Bar ", - "Description": "test", - "Click-Version": "0.2", - }, status[0]) - mock_package_install_hooks.assert_called_once_with( - db, "test-package", None, "1.0", user_name=None) - - @skipUnless( - os.path.exists(ClickInstaller(None)._preload_path()), - "preload bits not built; installing packages will fail") - def test_sandbox(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - original_call = subprocess.check_output - - def call_side_effect(*args, **kwargs): - return original_call( - ["touch", os.path.join(self.temp_dir, "sentinel")], - **kwargs) - - path = self.make_fake_package( - control_fields={ - "Package": "test-package", - "Version": "1.0", - "Architecture": "all", - "Maintainer": "Foo Bar ", - "Description": "test", - "Click-Version": "0.2", - }, - manifest={ - "name": "test-package", - "version": "1.0", - "framework": "ubuntu-sdk-13.10", - }, - control_scripts={"preinst": static_preinst}, - data_files={"foo": None}) - root = os.path.join(self.temp_dir, "root") - db = Click.DB() - db.add(root) - installer = ClickInstaller(db) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - with mock.patch("subprocess.check_output") as mock_call: - mock_call.side_effect = call_side_effect - self.assertRaises( - subprocess.CalledProcessError, installer.install, path) - self.assertFalse( - os.path.exists(os.path.join(self.temp_dir, "sentinel"))) - - @skipUnless( - os.path.exists(ClickInstaller(None)._preload_path()), - "preload bits not built; installing packages will fail") - @mock.patch("gi.repository.Click.package_install_hooks") - def test_upgrade(self, mock_package_install_hooks): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - os.environ["TEST_QUIET"] = "1" - path = self.make_fake_package( - control_fields={ - "Package": "test-package", - "Version": "1.1", - "Architecture": "all", - "Maintainer": "Foo Bar ", - "Description": "test", - "Click-Version": "0.2", - }, - manifest={ - "name": "test-package", - "version": "1.1", - "framework": "ubuntu-sdk-13.10", - }, - control_scripts={"preinst": static_preinst}, - data_files={"foo": None}) - root = os.path.join(self.temp_dir, "root") - package_dir = os.path.join(root, "test-package") - inst_dir = os.path.join(package_dir, "current") - os.makedirs(os.path.join(package_dir, "1.0")) - os.symlink("1.0", inst_dir) - db = Click.DB() - db.add(root) - installer = ClickInstaller(db) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - with mock_quiet_subprocess_call(): - installer.install(path) - self.assertCountEqual([".click", "test-package"], os.listdir(root)) - self.assertCountEqual(["1.1", "current"], os.listdir(package_dir)) - self.assertTrue(os.path.islink(inst_dir)) - self.assertEqual("1.1", os.readlink(inst_dir)) - self.assertCountEqual([".click", "foo"], os.listdir(inst_dir)) - status_path = os.path.join(inst_dir, ".click", "status") - with open(status_path) as status_file: - # .readlines() avoids the need for a python-apt backport to - # Ubuntu 12.04 LTS. - status = list(Deb822.iter_paragraphs(status_file.readlines())) - self.assertEqual(1, len(status)) - self.assertEqual({ - "Package": "test-package", - "Status": "install ok installed", - "Version": "1.1", - "Architecture": "all", - "Maintainer": "Foo Bar ", - "Description": "test", - "Click-Version": "0.2", - }, status[0]) - mock_package_install_hooks.assert_called_once_with( - db, "test-package", "1.0", "1.1", user_name=None) - - def _get_mode(self, path): - return stat.S_IMODE(os.stat(path).st_mode) - - @skipUnless( - os.path.exists(ClickInstaller(None)._preload_path()), - "preload bits not built; installing packages will fail") - @mock.patch("gi.repository.Click.package_install_hooks") - def test_world_readable(self, mock_package_install_hooks): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - owner_only_file = os.path.join(self.temp_dir, "owner-only-file") - touch(owner_only_file) - os.chmod(owner_only_file, stat.S_IRUSR | stat.S_IWUSR) - owner_only_dir = os.path.join(self.temp_dir, "owner-only-dir") - os.mkdir(owner_only_dir, stat.S_IRWXU) - path = self.make_fake_package( - control_fields={ - "Package": "test-package", - "Version": "1.1", - "Architecture": "all", - "Maintainer": "Foo Bar ", - "Description": "test", - "Click-Version": "0.2", - }, - manifest={ - "name": "test-package", - "version": "1.1", - "framework": "ubuntu-sdk-13.10", - }, - control_scripts={"preinst": static_preinst}, - data_files={ - "world-readable-file": owner_only_file, - "world-readable-dir": owner_only_dir, - }) - root = os.path.join(self.temp_dir, "root") - db = Click.DB() - db.add(root) - installer = ClickInstaller(db) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - with mock_quiet_subprocess_call(): - installer.install(path) - inst_dir = os.path.join(root, "test-package", "current") - self.assertEqual( - stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH, - self._get_mode(os.path.join(inst_dir, "world-readable-file"))) - self.assertEqual( - stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | - stat.S_IROTH | stat.S_IXOTH, - self._get_mode(os.path.join(inst_dir, "world-readable-dir"))) - - @skipUnless( - os.path.exists(ClickInstaller(None)._preload_path()), - "preload bits not built; installing packages will fail") - @mock.patch("gi.repository.Click.package_install_hooks") - @mock.patch("click.install.ClickInstaller._dpkg_architecture") - def test_single_architecture(self, mock_dpkg_architecture, - mock_package_install_hooks): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - mock_dpkg_architecture.return_value = "armhf" - path = self.make_fake_package( - control_fields={ - "Package": "test-package", - "Version": "1.1", - "Architecture": "armhf", - "Maintainer": "Foo Bar ", - "Description": "test", - "Click-Version": "0.2", - }, - manifest={ - "name": "test-package", - "version": "1.1", - "framework": "ubuntu-sdk-13.10", - "architecture": "armhf", - }, - control_scripts={"preinst": static_preinst}) - root = os.path.join(self.temp_dir, "root") - db = Click.DB() - db.add(root) - installer = ClickInstaller(db) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - with mock_quiet_subprocess_call(): - installer.install(path) - self.assertTrue( - os.path.exists(os.path.join(root, "test-package", "current"))) - - @skipUnless( - os.path.exists(ClickInstaller(None)._preload_path()), - "preload bits not built; installing packages will fail") - @mock.patch("gi.repository.Click.package_install_hooks") - @mock.patch("click.install.ClickInstaller._dpkg_architecture") - def test_multiple_architectures(self, mock_dpkg_architecture, - mock_package_install_hooks): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - mock_dpkg_architecture.return_value = "armhf" - path = self.make_fake_package( - control_fields={ - "Package": "test-package", - "Version": "1.1", - "Architecture": "multi", - "Maintainer": "Foo Bar ", - "Description": "test", - "Click-Version": "0.2", - }, - manifest={ - "name": "test-package", - "version": "1.1", - "framework": "ubuntu-sdk-13.10", - "architecture": ["armhf", "i386"], - }, - control_scripts={"preinst": static_preinst}) - root = os.path.join(self.temp_dir, "root") - db = Click.DB() - db.add(root) - installer = ClickInstaller(db) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - with mock_quiet_subprocess_call(): - installer.install(path) - self.assertTrue( - os.path.exists(os.path.join(root, "test-package", "current"))) - - @disable_logging - def test_reinstall_preinstalled(self): - # Attempting to reinstall a preinstalled version shouldn't actually - # reinstall it in an overlay database (which would cause - # irreconcilable confusion about the correct target for system hook - # symlinks), but should instead simply update the user registration. - path = self.make_fake_package( - control_fields={ - "Package": "test-package", - "Version": "1.1", - "Architecture": "all", - "Maintainer": "Foo Bar ", - "Description": "test", - "Click-Version": "0.4", - }, - manifest={ - "name": "test-package", - "version": "1.1", - "framework": "ubuntu-sdk-13.10", - }, - control_scripts={"preinst": static_preinst}) - underlay = os.path.join(self.temp_dir, "underlay") - overlay = os.path.join(self.temp_dir, "overlay") - db = Click.DB() - db.add(underlay) - installer = ClickInstaller(db, True) - with mock_quiet_subprocess_call(): - installer.install(path, all_users=True) - underlay_unpacked = os.path.join(underlay, "test-package", "1.1") - self.assertTrue(os.path.exists(underlay_unpacked)) - all_link = os.path.join( - underlay, ".click", "users", "@all", "test-package") - self.assertTrue(os.path.islink(all_link)) - self.assertEqual(underlay_unpacked, os.readlink(all_link)) - db.add(overlay) - registry = Click.User.for_user(db, "test-user") - registry.remove("test-package") - user_link = os.path.join( - overlay, ".click", "users", "test-user", "test-package") - self.assertTrue(os.path.islink(user_link)) - self.assertEqual("@hidden", os.readlink(user_link)) - installer = ClickInstaller(db, True) - with mock_quiet_subprocess_call(): - installer.install(path, user="test-user") - overlay_unpacked = os.path.join(overlay, "test-package", "1.1") - self.assertFalse(os.path.exists(overlay_unpacked)) - self.assertEqual("1.1", registry.get_version("test-package")) diff -Nru click-0.4.43+16.04.20160203/click/tests/test_osextras.py click-6.7/click/tests/test_osextras.py --- click-0.4.43+16.04.20160203/click/tests/test_osextras.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/test_osextras.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,193 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Unit tests for click.osextras.""" - -from __future__ import print_function -__all__ = [ - 'TestOSExtrasNative', - 'TestOSExtrasPython', - ] - - -import os - -from gi.repository import Click, GLib - -from click import osextras -from click.tests.helpers import TestCase, mock, touch - - -class TestOSExtrasBaseMixin: - def test_ensuredir_previously_missing(self): - new_dir = os.path.join(self.temp_dir, "dir") - self.mod.ensuredir(new_dir) - self.assertTrue(os.path.isdir(new_dir)) - - def test_ensuredir_previously_present(self): - new_dir = os.path.join(self.temp_dir, "dir") - os.mkdir(new_dir) - self.mod.ensuredir(new_dir) - self.assertTrue(os.path.isdir(new_dir)) - - def test_find_on_path_missing_environment(self): - os.environ.pop("PATH", None) - self.assertFalse(self.mod.find_on_path("ls")) - - def test_find_on_path_present_executable(self): - bin_dir = os.path.join(self.temp_dir, "bin") - program = os.path.join(bin_dir, "program") - touch(program) - os.chmod(program, 0o755) - os.environ["PATH"] = bin_dir - self.assertTrue(self.mod.find_on_path("program")) - - def test_find_on_path_present_not_executable(self): - bin_dir = os.path.join(self.temp_dir, "bin") - touch(os.path.join(bin_dir, "program")) - os.environ["PATH"] = bin_dir - self.assertFalse(self.mod.find_on_path("program")) - - def test_find_on_path_requires_regular_file(self): - bin_dir = os.path.join(self.temp_dir, "bin") - self.mod.ensuredir(os.path.join(bin_dir, "subdir")) - os.environ["PATH"] = bin_dir - self.assertFalse(self.mod.find_on_path("subdir")) - - def test_unlink_file_present(self): - path = os.path.join(self.temp_dir, "file") - touch(path) - self.mod.unlink_force(path) - self.assertFalse(os.path.exists(path)) - - def test_unlink_file_missing(self): - path = os.path.join(self.temp_dir, "file") - self.mod.unlink_force(path) - self.assertFalse(os.path.exists(path)) - - def test_symlink_file_present(self): - path = os.path.join(self.temp_dir, "link") - touch(path) - self.mod.symlink_force("source", path) - self.assertTrue(os.path.islink(path)) - self.assertEqual("source", os.readlink(path)) - - def test_symlink_link_present(self): - path = os.path.join(self.temp_dir, "link") - os.symlink("old", path) - self.mod.symlink_force("source", path) - self.assertTrue(os.path.islink(path)) - self.assertEqual("source", os.readlink(path)) - - def test_symlink_missing(self): - path = os.path.join(self.temp_dir, "link") - self.mod.symlink_force("source", path) - self.assertTrue(os.path.islink(path)) - self.assertEqual("source", os.readlink(path)) - - def test_umask(self): - old_mask = os.umask(0o040) - try: - self.assertEqual(0o040, self.mod.get_umask()) - os.umask(0o002) - self.assertEqual(0o002, self.mod.get_umask()) - finally: - os.umask(old_mask) - - -class TestOSExtrasNative(TestCase, TestOSExtrasBaseMixin): - def setUp(self): - super(TestOSExtrasNative, self).setUp() - self.use_temp_dir() - self.mod = Click - - def test_ensuredir_error(self): - path = os.path.join(self.temp_dir, "file") - touch(path) - self.assertRaisesFileError(mock.ANY, self.mod.ensuredir, path) - - def test_dir_read_name_directory_present(self): - new_dir = os.path.join(self.temp_dir, "dir") - touch(os.path.join(new_dir, "file")) - d = Click.Dir.open(new_dir, 0) - self.assertEqual("file", d.read_name()) - self.assertIsNone(d.read_name()) - - def test_dir_read_name_directory_missing(self): - new_dir = os.path.join(self.temp_dir, "dir") - d = Click.Dir.open(new_dir, 0) - self.assertIsNone(d.read_name()) - - def test_dir_open_error(self): - not_dir = os.path.join(self.temp_dir, "file") - touch(not_dir) - self.assertRaisesFileError( - GLib.FileError.NOTDIR, Click.Dir.open, not_dir, 0) - - def test_unlink_error(self): - path = os.path.join(self.temp_dir, "dir") - os.mkdir(path) - self.assertRaisesFileError(mock.ANY, self.mod.unlink_force, path) - - def test_symlink_unlink_error(self): - path = os.path.join(self.temp_dir, "dir") - os.mkdir(path) - self.assertRaisesFileError( - mock.ANY, self.mod.symlink_force, "source", path) - - def test_symlink_error(self): - path = os.path.join(self.temp_dir, "dir", "file") - self.assertRaisesFileError( - mock.ANY, self.mod.symlink_force, "source", path) - - -class TestOSExtrasPython(TestCase, TestOSExtrasBaseMixin): - def setUp(self): - super(TestOSExtrasPython, self).setUp() - self.use_temp_dir() - self.mod = osextras - - def test_ensuredir_oserror(self): - path = os.path.join(self.temp_dir, "file") - touch(path) - self.assertRaises(OSError, self.mod.ensuredir, path) - - def test_listdir_directory_present(self): - new_dir = os.path.join(self.temp_dir, "dir") - touch(os.path.join(new_dir, "file")) - self.assertEqual(["file"], osextras.listdir_force(new_dir)) - - def test_listdir_directory_missing(self): - new_dir = os.path.join(self.temp_dir, "dir") - self.assertEqual([], osextras.listdir_force(new_dir)) - - def test_listdir_oserror(self): - not_dir = os.path.join(self.temp_dir, "file") - touch(not_dir) - self.assertRaises(OSError, osextras.listdir_force, not_dir) - - def test_unlink_oserror(self): - path = os.path.join(self.temp_dir, "dir") - os.mkdir(path) - self.assertRaises(OSError, self.mod.unlink_force, path) - - def test_symlink_unlink_oserror(self): - path = os.path.join(self.temp_dir, "dir") - os.mkdir(path) - self.assertRaises(OSError, self.mod.symlink_force, "source", path) - - def test_symlink_oserror(self): - path = os.path.join(self.temp_dir, "dir", "file") - self.assertRaises(OSError, self.mod.symlink_force, "source", path) diff -Nru click-0.4.43+16.04.20160203/click/tests/test_paths.py.in click-6.7/click/tests/test_paths.py.in --- click-0.4.43+16.04.20160203/click/tests/test_paths.py.in 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/test_paths.py.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,42 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Unit tests for click.paths. - -This is mostly just to reduce noise in the coverage report. -""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'TestClickPaths', - ] - - -from gi.repository import Click - -from click.tests.helpers import TestCase - - -class TestClickPaths(TestCase): - def test_get_hooks_dir(self): - self.assertEqual("@pkgdatadir@/hooks", Click.get_hooks_dir()) - - def test_get_db_dir(self): - self.assertEqual("@sysconfdir@/click/databases", Click.get_db_dir()) - - def test_get_frameworks_dir(self): - self.assertEqual("@pkgdatadir@/frameworks", Click.get_frameworks_dir()) diff -Nru click-0.4.43+16.04.20160203/click/tests/test_query.py click-6.7/click/tests/test_query.py --- click-0.4.43+16.04.20160203/click/tests/test_query.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/test_query.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,52 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Unit tests for click.query.""" - -from __future__ import print_function -__all__ = [ - 'TestQuery', - ] - - -import os - -from gi.repository import Click - -from click.tests.helpers import TestCase, touch - - -class TestQuery(TestCase): - def setUp(self): - super(TestQuery, self).setUp() - self.use_temp_dir() - - def test_find_package_directory_missing(self): - path = os.path.join(self.temp_dir, "nonexistent") - self.assertRaisesQueryError( - Click.QueryError.PATH, Click.find_package_directory, path) - - def test_find_package_directory(self): - info = os.path.join(self.temp_dir, ".click", "info") - path = os.path.join(self.temp_dir, "file") - Click.ensuredir(info) - touch(path) - pkgdir = Click.find_package_directory(path) - self.assertEqual(self.temp_dir, pkgdir) - - def test_find_package_directory_outside(self): - self.assertRaisesQueryError( - Click.QueryError.NO_PACKAGE_DIR, Click.find_package_directory, - "/bin") diff -Nru click-0.4.43+16.04.20160203/click/tests/test_scripts.py click-6.7/click/tests/test_scripts.py --- click-0.4.43+16.04.20160203/click/tests/test_scripts.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/test_scripts.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,49 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Test that all top-level scripts work.""" - -__metaclass__ = type -__all__ = [ - 'TestScripts', - ] - -import os -import subprocess -from unittest import skipIf - -from click.tests.helpers import TestCase - - -class TestScripts(TestCase): - @skipIf('SKIP_SLOW_TESTS' in os.environ, 'Skipping slow tests') - def test_scripts(self): - self.longMessage = True - paths = [] - for dirpath, _, filenames in os.walk("bin"): - filenames = [ - n for n in filenames - if not n.startswith(".") and not n.endswith("~")] - for filename in filenames: - paths.append(os.path.join(dirpath, filename)) - for path in paths: - subp = subprocess.Popen( - [path, "--help"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - universal_newlines=True) - err = subp.communicate()[1] - self.assertEqual("", err, "%s --help produced error output" % path) - self.assertEqual( - 0, subp.returncode, "%s --help exited non-zero" % path) diff -Nru click-0.4.43+16.04.20160203/click/tests/test_static.py click-6.7/click/tests/test_static.py --- click-0.4.43+16.04.20160203/click/tests/test_static.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/test_static.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Test compliance with various static analysis tools.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'TestStatic', - ] - - -import os -import sys -from unittest import skipIf - -from pkg_resources import resource_filename - -try: - import pep8 -except ImportError: - pep8 = None -try: - import pyflakes - import pyflakes.api - import pyflakes.reporter -except ImportError: - pyflakes = None - - -from click.tests.helpers import TestCase - - -class TestStatic(TestCase): - def all_paths(self): - paths = [] - start_dir = os.path.dirname(resource_filename('click', '__init__.py')) - for dirpath, dirnames, filenames in os.walk(start_dir): - for ignore in ('doc', ".bzr", "__pycache__"): - if ignore in dirnames: - dirnames.remove(ignore) - filenames = [ - n for n in filenames - if not n.startswith(".") and not n.endswith("~")] - if dirpath.split(os.sep)[-1] == "bin": - for filename in filenames: - paths.append(os.path.join(dirpath, filename)) - else: - for filename in filenames: - if filename.endswith(".py"): - paths.append(os.path.join(dirpath, filename)) - return paths - - @skipIf('SKIP_SLOW_TESTS' in os.environ, 'Skipping slow tests') - @skipIf(pep8 is None, 'No pep8 package available') - def test_pep8_clean(self): - # https://github.com/jcrocholl/pep8/issues/103 - pep8_style = pep8.StyleGuide(ignore='E123') - result = pep8_style.check_files(self.all_paths()) - self.assertEqual(result.total_errors, 0) - - @skipIf('SKIP_SLOW_TESTS' in os.environ, 'Skipping slow tests') - @skipIf(pyflakes is None, 'No pyflakes package available') - def test_pyflakes_clean(self): - reporter = pyflakes.reporter.Reporter(sys.stdout, sys.stderr) - warnings = pyflakes.api.checkRecursive(self.all_paths(), reporter) - self.assertEqual(0, warnings) diff -Nru click-0.4.43+16.04.20160203/click/tests/test_user.py click-6.7/click/tests/test_user.py --- click-0.4.43+16.04.20160203/click/tests/test_user.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/tests/test_user.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,591 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Unit tests for click.user.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'TestClickUser', - ] - - -import json -import os -import shutil -from textwrap import dedent - -from gi.repository import Click, GLib - -from click.json_helpers import json_array_to_python, json_object_to_python -from click.tests.gimock_types import Passwd -from click.tests.helpers import ( - TestCase, - make_installed_click, - mkfile, - make_file_with_content, - touch, -) - - -class TestClickUser(TestCase): - def setUp(self): - super(TestClickUser, self).setUp() - self.use_temp_dir() - self.db = Click.DB() - self.db.add(self.temp_dir) - - def _setUpMultiDB(self): - self.multi_db = Click.DB() - self.multi_db.add(os.path.join(self.temp_dir, "custom")) - self.multi_db.add(os.path.join(self.temp_dir, "click")) - user_dbs = [ - os.path.join( - self.multi_db.get(i).props.root, ".click", "users", "user") - for i in range(self.multi_db.props.size) - ] - a_1_0 = os.path.join(self.temp_dir, "custom", "a", "1.0") - os.makedirs(a_1_0) - with mkfile(os.path.join(a_1_0, ".click", "info", "a.manifest")) as m: - json.dump({"name": "a", "version": "1.0", - "hooks": {"a-app": {}}}, m) - b_2_0 = os.path.join(self.temp_dir, "custom", "b", "2.0") - os.makedirs(b_2_0) - with mkfile(os.path.join(b_2_0, ".click", "info", "b.manifest")) as m: - json.dump({"name": "b", "version": "2.0"}, m) - a_1_1 = os.path.join(self.temp_dir, "click", "a", "1.1") - os.makedirs(a_1_1) - with mkfile(os.path.join(a_1_1, ".click", "info", "a.manifest")) as m: - json.dump({"name": "a", "version": "1.1"}, m) - c_0_1 = os.path.join(self.temp_dir, "click", "c", "0.1") - os.makedirs(c_0_1) - with mkfile(os.path.join(c_0_1, ".click", "info", "c.manifest")) as m: - json.dump({"name": "c", "version": "0.1"}, m) - os.makedirs(user_dbs[0]) - os.symlink(a_1_0, os.path.join(user_dbs[0], "a")) - os.symlink(b_2_0, os.path.join(user_dbs[0], "b")) - os.makedirs(user_dbs[1]) - os.symlink(a_1_1, os.path.join(user_dbs[1], "a")) - os.symlink(c_0_1, os.path.join(user_dbs[1], "c")) - return user_dbs, Click.User.for_user(self.multi_db, "user") - - def test_new_no_db(self): - with self.run_in_subprocess( - "click_get_db_dir", "g_get_user_name") as (enter, preloads): - enter() - preloads["click_get_db_dir"].side_effect = ( - lambda: self.make_string(self.temp_dir)) - preloads["g_get_user_name"].side_effect = ( - lambda: self.make_string("test-user")) - db_root = os.path.join(self.temp_dir, "db") - os.makedirs(db_root) - with open(os.path.join(self.temp_dir, "db.conf"), "w") as f: - print("[Click Database]", file=f) - print("root = %s" % db_root, file=f) - registry = Click.User.for_user(None, None) - self.assertEqual( - os.path.join(db_root, ".click", "users", "test-user"), - registry.get_overlay_db()) - - def test_new_db_not_directory(self): - with self.run_in_subprocess( - "click_get_db_dir", "g_get_user_name") as (enter, preloads): - enter() - path = os.path.join(self.temp_dir, "file") - touch(path) - preloads["click_get_db_dir"].side_effect = ( - lambda: self.make_string(path)) - self.assertRaisesFileError( - GLib.FileError.NOTDIR, Click.User.for_user, None, None) - - def test_get_overlay_db(self): - self.assertEqual( - os.path.join(self.temp_dir, ".click", "users", "user"), - Click.User.for_user(self.db, "user").get_overlay_db()) - - def test_ensure_db_ownership(self): - # getpwnam results are cached properly, in a way that doesn't fail - # due to confusion with getpwnam returning a pointer to a static - # buffer. - with self.run_in_subprocess( - "chown", "geteuid", "getpwnam") as (enter, preloads): - enter() - preloads["geteuid"].return_value = 0 - getpwnam_result = Passwd() - - def getpwnam_side_effect(name): - if name == b"clickpkg": - getpwnam_result.pw_uid = 1 - getpwnam_result.pw_gid = 1 - else: - getpwnam_result.pw_uid = 2 - getpwnam_result.pw_gid = 2 - return self.make_pointer(getpwnam_result) - - preloads["getpwnam"].side_effect = getpwnam_side_effect - registry = Click.User.for_user(self.db, "user") - os.makedirs(os.path.join(self.temp_dir, "a", "1.0")) - click_dir = os.path.join(self.temp_dir, ".click") - - registry.set_version("a", "1.0") - self.assertEqual(3, preloads["chown"].call_count) - preloads["chown"].assert_any_call(click_dir.encode(), 1, 1) - preloads["chown"].assert_any_call( - os.path.join(click_dir, "users").encode(), 1, 1) - preloads["chown"].assert_any_call( - os.path.join(click_dir, "users", "user").encode(), 2, 2) - - # Try again, now that both password file entries should be - # cached. - shutil.rmtree(os.path.join(self.temp_dir, ".click")) - preloads["chown"].reset_mock() - registry.set_version("a", "1.0") - self.assertEqual(3, preloads["chown"].call_count) - preloads["chown"].assert_any_call(click_dir.encode(), 1, 1) - preloads["chown"].assert_any_call( - os.path.join(click_dir, "users").encode(), 1, 1) - preloads["chown"].assert_any_call( - os.path.join(click_dir, "users", "user").encode(), 2, 2) - - def test_ensure_db_mkdir_fails(self): - with self.run_in_subprocess("mkdir") as (enter, preloads): - enter() - preloads["mkdir"].return_value = -1 - registry = Click.User.for_user(self.db, "user") - self.assertRaisesUserError( - Click.UserError.CREATE_DB, registry.set_version, "a", "1.0") - - def test_ensure_db_chown_fails(self): - with self.run_in_subprocess( - "chown", "geteuid", "getpwnam") as (enter, preloads): - enter() - preloads["geteuid"].return_value = 0 - getpwnam_result = Passwd() - - def getpwnam_side_effect(name): - if name == b"clickpkg": - getpwnam_result.pw_uid = 1 - getpwnam_result.pw_gid = 1 - else: - getpwnam_result.pw_uid = 2 - getpwnam_result.pw_gid = 2 - return self.make_pointer(getpwnam_result) - - preloads["getpwnam"].side_effect = getpwnam_side_effect - preloads["chown"].return_value = -1 - registry = Click.User.for_user(self.db, "user") - self.assertRaisesUserError( - Click.UserError.CHOWN_DB, registry.set_version, "a", "1.0") - - def test_ensure_db_getpwnam_fails(self): - with self.run_in_subprocess( - "geteuid", "getpwnam") as (enter, preloads): - enter() - preloads["geteuid"].return_value = 0 - preloads["getpwnam"].return_value = None - registry = Click.User.for_user(self.db, "user") - self.assertRaisesUserError( - Click.UserError.GETPWNAM, registry.set_version, "a", "1.0") - - def test_get_package_names_missing(self): - db = Click.DB() - db.add(os.path.join(self.temp_dir, "nonexistent")) - registry = Click.User.for_user(db, None) - self.assertEqual([], list(registry.get_package_names())) - - def test_get_package_names(self): - registry = Click.User.for_user(self.db, "user") - os.makedirs(registry.get_overlay_db()) - os.symlink("/1.0", os.path.join(registry.get_overlay_db(), "a")) - os.symlink("/1.1", os.path.join(registry.get_overlay_db(), "b")) - self.assertCountEqual(["a", "b"], list(registry.get_package_names())) - - def test_get_package_names_multiple_root(self): - _, registry = self._setUpMultiDB() - self.assertCountEqual( - ["a", "b", "c"], list(registry.get_package_names())) - - def test_get_version_missing(self): - registry = Click.User.for_user(self.db, "user") - self.assertRaisesUserError( - Click.UserError.NO_SUCH_PACKAGE, registry.get_version, "a") - self.assertFalse(registry.has_package_name("a")) - - def test_get_version(self): - registry = Click.User.for_user(self.db, "user") - os.makedirs(registry.get_overlay_db()) - os.symlink("/1.0", os.path.join(registry.get_overlay_db(), "a")) - self.assertEqual("1.0", registry.get_version("a")) - self.assertTrue(registry.has_package_name("a")) - - def test_get_version_multiple_root(self): - _, registry = self._setUpMultiDB() - self.assertEqual("1.1", registry.get_version("a")) - self.assertEqual("2.0", registry.get_version("b")) - self.assertEqual("0.1", registry.get_version("c")) - self.assertTrue(registry.has_package_name("a")) - self.assertTrue(registry.has_package_name("b")) - self.assertTrue(registry.has_package_name("c")) - - def test_set_version_missing_target(self): - registry = Click.User.for_user(self.db, "user") - self.assertRaisesDatabaseError( - Click.DatabaseError.DOES_NOT_EXIST, - registry.set_version, "a", "1.0") - - def test_set_version_missing(self): - registry = Click.User.for_user(self.db, "user") - os.makedirs(os.path.join(self.temp_dir, "a", "1.0")) - registry.set_version("a", "1.0") - path = os.path.join(registry.get_overlay_db(), "a") - self.assertTrue(os.path.islink(path)) - self.assertEqual( - os.path.join(self.temp_dir, "a", "1.0"), os.readlink(path)) - - def test_set_version_changed(self): - registry = Click.User.for_user(self.db, "user") - os.makedirs(registry.get_overlay_db()) - path = os.path.join(registry.get_overlay_db(), "a") - os.symlink("/1.0", path) - os.makedirs(os.path.join(self.temp_dir, "a", "1.1")) - registry.set_version("a", "1.1") - self.assertTrue(os.path.islink(path)) - self.assertEqual( - os.path.join(self.temp_dir, "a", "1.1"), os.readlink(path)) - - def test_set_version_multiple_root(self): - user_dbs, registry = self._setUpMultiDB() - - os.makedirs(os.path.join(self.multi_db.get(1).props.root, "a", "1.2")) - registry.set_version("a", "1.2") - a_underlay = os.path.join(user_dbs[0], "a") - a_overlay = os.path.join(user_dbs[1], "a") - self.assertTrue(os.path.islink(a_underlay)) - self.assertEqual( - os.path.join(self.multi_db.get(0).props.root, "a", "1.0"), - os.readlink(a_underlay)) - self.assertTrue(os.path.islink(a_overlay)) - self.assertEqual( - os.path.join(self.multi_db.get(1).props.root, "a", "1.2"), - os.readlink(a_overlay)) - - os.makedirs(os.path.join(self.multi_db.get(1).props.root, "b", "2.1")) - registry.set_version("b", "2.1") - b_underlay = os.path.join(user_dbs[0], "b") - b_overlay = os.path.join(user_dbs[1], "b") - self.assertTrue(os.path.islink(b_underlay)) - self.assertEqual( - os.path.join(self.multi_db.get(0).props.root, "b", "2.0"), - os.readlink(b_underlay)) - self.assertTrue(os.path.islink(b_overlay)) - self.assertEqual( - os.path.join(self.multi_db.get(1).props.root, "b", "2.1"), - os.readlink(b_overlay)) - - os.makedirs(os.path.join(self.multi_db.get(1).props.root, "c", "0.2")) - registry.set_version("c", "0.2") - c_underlay = os.path.join(user_dbs[0], "c") - c_overlay = os.path.join(user_dbs[1], "c") - self.assertFalse(os.path.islink(c_underlay)) - self.assertTrue(os.path.islink(c_overlay)) - self.assertEqual( - os.path.join(self.multi_db.get(1).props.root, "c", "0.2"), - os.readlink(c_overlay)) - - os.makedirs(os.path.join(self.multi_db.get(1).props.root, "d", "3.0")) - registry.set_version("d", "3.0") - d_underlay = os.path.join(user_dbs[0], "d") - d_overlay = os.path.join(user_dbs[1], "d") - self.assertFalse(os.path.islink(d_underlay)) - self.assertTrue(os.path.islink(d_overlay)) - self.assertEqual( - os.path.join(self.multi_db.get(1).props.root, "d", "3.0"), - os.readlink(d_overlay)) - - def test_set_version_restore_to_underlay(self): - user_dbs, registry = self._setUpMultiDB() - a_underlay = os.path.join(user_dbs[0], "a") - a_overlay = os.path.join(user_dbs[1], "a") - - # Initial state: 1.0 in underlay, 1.1 in overlay. - self.assertTrue(os.path.islink(a_underlay)) - self.assertEqual( - os.path.join(self.multi_db.get(0).props.root, "a", "1.0"), - os.readlink(a_underlay)) - self.assertTrue(os.path.islink(a_overlay)) - self.assertEqual( - os.path.join(self.multi_db.get(1).props.root, "a", "1.1"), - os.readlink(a_overlay)) - - # Setting to 1.0 (version in underlay) removes overlay link. - registry.set_version("a", "1.0") - self.assertTrue(os.path.islink(a_underlay)) - self.assertEqual( - os.path.join(self.multi_db.get(0).props.root, "a", "1.0"), - os.readlink(a_underlay)) - self.assertFalse(os.path.islink(a_overlay)) - - def test_remove_missing(self): - registry = Click.User.for_user(self.db, "user") - self.assertRaisesUserError( - Click.UserError.NO_SUCH_PACKAGE, registry.remove, "a") - - def test_remove(self): - registry = Click.User.for_user(self.db, "user") - os.makedirs(registry.get_overlay_db()) - path = os.path.join(registry.get_overlay_db(), "a") - os.symlink("/1.0", path) - registry.remove("a") - self.assertFalse(os.path.exists(path)) - - def test_remove_multiple_root(self): - user_dbs, registry = self._setUpMultiDB() - registry.remove("a") - self.assertFalse(os.path.exists(os.path.join(user_dbs[1], "a"))) - # Exposed underlay. - self.assertEqual("1.0", registry.get_version("a")) - registry.remove("b") - # Hidden. - self.assertEqual( - "@hidden", os.readlink(os.path.join(user_dbs[1], "b"))) - self.assertFalse(registry.has_package_name("b")) - registry.remove("c") - self.assertFalse(os.path.exists(os.path.join(user_dbs[1], "c"))) - self.assertFalse(registry.has_package_name("c")) - self.assertRaisesUserError( - Click.UserError.NO_SUCH_PACKAGE, registry.remove, "d") - - def test_remove_multiple_root_creates_overlay_directory(self): - multi_db = Click.DB() - multi_db.add(os.path.join(self.temp_dir, "preinstalled")) - multi_db.add(os.path.join(self.temp_dir, "click")) - user_dbs = [ - os.path.join(multi_db.get(i).props.root, ".click", "users", "user") - for i in range(multi_db.props.size) - ] - a_1_0 = os.path.join(self.temp_dir, "preinstalled", "a", "1.0") - os.makedirs(a_1_0) - os.makedirs(user_dbs[0]) - os.symlink(a_1_0, os.path.join(user_dbs[0], "a")) - self.assertFalse(os.path.exists(user_dbs[1])) - registry = Click.User.for_user(multi_db, "user") - self.assertEqual("1.0", registry.get_version("a")) - registry.remove("a") - self.assertFalse(registry.has_package_name("a")) - self.assertEqual( - "@hidden", os.readlink(os.path.join(user_dbs[1], "a"))) - - def test_get_path(self): - registry = Click.User.for_user(self.db, "user") - os.makedirs(os.path.join(self.temp_dir, "a", "1.0")) - registry.set_version("a", "1.0") - self.assertEqual( - os.path.join(registry.get_overlay_db(), "a"), - registry.get_path("a")) - - def test_get_path_multiple_root(self): - user_dbs, registry = self._setUpMultiDB() - self.assertEqual( - os.path.join(user_dbs[1], "a"), registry.get_path("a")) - self.assertEqual( - os.path.join(user_dbs[0], "b"), registry.get_path("b")) - self.assertEqual( - os.path.join(user_dbs[1], "c"), registry.get_path("c")) - self.assertRaisesUserError( - Click.UserError.NO_SUCH_PACKAGE, registry.get_path, "d") - - def test_get_manifest(self): - registry = Click.User.for_user(self.db, "user") - manifest_path = os.path.join( - self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") - manifest_obj = {"name": "a", "version": "1.0"} - with mkfile(manifest_path) as manifest: - json.dump(manifest_obj, manifest) - manifest_obj["_directory"] = os.path.join( - registry.get_overlay_db(), "a") - manifest_obj["_removable"] = 1 - registry.set_version("a", "1.0") - self.assertEqual( - manifest_obj, json_object_to_python(registry.get_manifest("a"))) - self.assertEqual( - manifest_obj, json.loads(registry.get_manifest_as_string("a"))) - - def test_get_manifest_multiple_root(self): - user_dbs, registry = self._setUpMultiDB() - expected_a = { - "name": "a", - "version": "1.1", - "_directory": os.path.join(user_dbs[1], "a"), - "_removable": 1, - } - self.assertEqual( - expected_a, json_object_to_python(registry.get_manifest("a"))) - self.assertEqual( - expected_a, json.loads(registry.get_manifest_as_string("a"))) - expected_b = { - "name": "b", - "version": "2.0", - "_directory": os.path.join(user_dbs[0], "b"), - "_removable": 1, - } - self.assertEqual( - expected_b, json_object_to_python(registry.get_manifest("b"))) - self.assertEqual( - expected_b, json.loads(registry.get_manifest_as_string("b"))) - expected_c = { - "name": "c", - "version": "0.1", - "_directory": os.path.join(user_dbs[1], "c"), - "_removable": 1, - } - self.assertEqual( - expected_c, json_object_to_python(registry.get_manifest("c"))) - self.assertEqual( - expected_c, json.loads(registry.get_manifest_as_string("c"))) - self.assertRaisesUserError( - Click.UserError.NO_SUCH_PACKAGE, registry.get_path, "d") - - def test_get_manifests(self): - registry = Click.User.for_user(self.db, "user") - a_manifest_path = os.path.join( - self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") - a_manifest_obj = {"name": "a", "version": "1.0"} - with mkfile(a_manifest_path) as a_manifest: - json.dump(a_manifest_obj, a_manifest) - registry.set_version("a", "1.0") - b_manifest_path = os.path.join( - self.temp_dir, "b", "2.0", ".click", "info", "b.manifest") - b_manifest_obj = {"name": "b", "version": "2.0"} - with mkfile(b_manifest_path) as b_manifest: - json.dump(b_manifest_obj, b_manifest) - registry.set_version("b", "2.0") - a_manifest_obj["_directory"] = os.path.join( - registry.get_overlay_db(), "a") - a_manifest_obj["_removable"] = 1 - b_manifest_obj["_directory"] = os.path.join( - registry.get_overlay_db(), "b") - b_manifest_obj["_removable"] = 1 - self.assertEqual( - [a_manifest_obj, b_manifest_obj], - json_array_to_python(registry.get_manifests())) - self.assertEqual( - [a_manifest_obj, b_manifest_obj], - json.loads(registry.get_manifests_as_string())) - - def test_get_manifests_multiple_root(self): - user_dbs, registry = self._setUpMultiDB() - a_manifest_obj = { - "name": "a", - "version": "1.1", - "_directory": os.path.join(user_dbs[1], "a"), - "_removable": 1, - } - b_manifest_obj = { - "name": "b", - "version": "2.0", - "_directory": os.path.join(user_dbs[0], "b"), - "_removable": 1, - } - c_manifest_obj = { - "name": "c", - "version": "0.1", - "_directory": os.path.join(user_dbs[1], "c"), - "_removable": 1, - } - self.assertEqual( - [a_manifest_obj, c_manifest_obj, b_manifest_obj], - json_array_to_python(registry.get_manifests())) - self.assertEqual( - [a_manifest_obj, c_manifest_obj, b_manifest_obj], - json.loads(registry.get_manifests_as_string())) - registry.remove("b") - self.assertEqual( - "@hidden", os.readlink(os.path.join(user_dbs[1], "b"))) - self.assertEqual( - [a_manifest_obj, c_manifest_obj], - json_array_to_python(registry.get_manifests())) - self.assertEqual( - [a_manifest_obj, c_manifest_obj], - json.loads(registry.get_manifests_as_string())) - - def test_is_removable(self): - registry = Click.User.for_user(self.db, "user") - os.makedirs(os.path.join(self.temp_dir, "a", "1.0")) - registry.set_version("a", "1.0") - self.assertTrue(registry.is_removable("a")) - - def test_is_removable_multiple_root(self): - user_dbs, registry = self._setUpMultiDB() - self.assertTrue(registry.is_removable("a")) - self.assertTrue(registry.is_removable("b")) - self.assertTrue(registry.is_removable("c")) - self.assertFalse(registry.is_removable("d")) - - def test_hidden(self): - user_dbs, registry = self._setUpMultiDB() - b_overlay = os.path.join(user_dbs[1], "b") - - registry.remove("b") - self.assertFalse(registry.has_package_name("b")) - self.assertTrue(os.path.islink(b_overlay)) - self.assertEqual("@hidden", os.readlink(b_overlay)) - self.assertRaisesUserError( - Click.UserError.HIDDEN_PACKAGE, registry.get_version, "b") - self.assertRaisesUserError( - Click.UserError.HIDDEN_PACKAGE, registry.get_path, "b") - self.assertFalse(registry.is_removable("b")) - - registry.set_version("b", "2.0") - self.assertTrue(registry.has_package_name("b")) - self.assertTrue(os.path.islink(b_overlay)) - self.assertEqual( - os.path.join(self.multi_db.get(0).props.root, "b", "2.0"), - os.readlink(b_overlay)) - self.assertEqual("2.0", registry.get_version("b")) - self.assertEqual(b_overlay, registry.get_path("b")) - self.assertTrue(registry.is_removable("b")) - - -class StopAppTestCase(TestCase): - - def setUp(self): - super(StopAppTestCase, self).setUp() - self.use_temp_dir() - self.db = Click.DB() - self.db.add(self.temp_dir) - - # setup fake app_stop - fake_app_stop = os.path.join(self.temp_dir, "bin", "ubuntu-app-stop") - self.fake_app_stop_output = os.path.join( - self.temp_dir, "fake-app-stop.out") - fake_app_stop_content = dedent("""\ - #!/bin/sh - echo "$@" >> %s - """ % self.fake_app_stop_output) - make_file_with_content(fake_app_stop, fake_app_stop_content, 0o755) - # its ok to modify env here, click.helpers.TestCase will take care - # of it - os.environ["PATH"] = "%s:%s" % ( - os.path.dirname(fake_app_stop), os.environ["PATH"]) - - def test_app_stops_on_remove(self): - make_installed_click(self.db, self.temp_dir, "meep", "2.0", - {"hooks": {"a-app": {}}}) - registry = Click.User.for_user(self.db, "user") - registry.remove("meep") - # ensure that stop was called with the right app - with open(self.fake_app_stop_output) as f: - self.assertEqual("meep_a-app_2.0", f.read().strip()) diff -Nru click-0.4.43+16.04.20160203/click/_textwrap.py click-6.7/click/_textwrap.py --- click-0.4.43+16.04.20160203/click/_textwrap.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/click/_textwrap.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,38 @@ +import textwrap +from contextlib import contextmanager + + +class TextWrapper(textwrap.TextWrapper): + + def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width): + space_left = max(width - cur_len, 1) + + if self.break_long_words: + last = reversed_chunks[-1] + cut = last[:space_left] + res = last[space_left:] + cur_line.append(cut) + reversed_chunks[-1] = res + elif not cur_line: + cur_line.append(reversed_chunks.pop()) + + @contextmanager + def extra_indent(self, indent): + old_initial_indent = self.initial_indent + old_subsequent_indent = self.subsequent_indent + self.initial_indent += indent + self.subsequent_indent += indent + try: + yield + finally: + self.initial_indent = old_initial_indent + self.subsequent_indent = old_subsequent_indent + + def indent_only(self, text): + rv = [] + for idx, line in enumerate(text.splitlines()): + indent = self.initial_indent + if idx > 0: + indent = self.subsequent_indent + rv.append(indent + line) + return '\n'.join(rv) diff -Nru click-0.4.43+16.04.20160203/click/types.py click-6.7/click/types.py --- click-0.4.43+16.04.20160203/click/types.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/click/types.py 2017-01-06 22:32:37.000000000 +0000 @@ -0,0 +1,550 @@ +import os +import stat + +from ._compat import open_stream, text_type, filename_to_ui, \ + get_filesystem_encoding, get_streerror, _get_argv_encoding, PY2 +from .exceptions import BadParameter +from .utils import safecall, LazyFile + + +class ParamType(object): + """Helper for converting values through types. The following is + necessary for a valid type: + + * it needs a name + * it needs to pass through None unchanged + * it needs to convert from a string + * it needs to convert its result type through unchanged + (eg: needs to be idempotent) + * it needs to be able to deal with param and context being `None`. + This can be the case when the object is used with prompt + inputs. + """ + is_composite = False + + #: the descriptive name of this type + name = None + + #: if a list of this type is expected and the value is pulled from a + #: string environment variable, this is what splits it up. `None` + #: means any whitespace. For all parameters the general rule is that + #: whitespace splits them up. The exception are paths and files which + #: are split by ``os.path.pathsep`` by default (":" on Unix and ";" on + #: Windows). + envvar_list_splitter = None + + def __call__(self, value, param=None, ctx=None): + if value is not None: + return self.convert(value, param, ctx) + + def get_metavar(self, param): + """Returns the metavar default for this param if it provides one.""" + + def get_missing_message(self, param): + """Optionally might return extra information about a missing + parameter. + + .. versionadded:: 2.0 + """ + + def convert(self, value, param, ctx): + """Converts the value. This is not invoked for values that are + `None` (the missing value). + """ + return value + + def split_envvar_value(self, rv): + """Given a value from an environment variable this splits it up + into small chunks depending on the defined envvar list splitter. + + If the splitter is set to `None`, which means that whitespace splits, + then leading and trailing whitespace is ignored. Otherwise, leading + and trailing splitters usually lead to empty items being included. + """ + return (rv or '').split(self.envvar_list_splitter) + + def fail(self, message, param=None, ctx=None): + """Helper method to fail with an invalid value message.""" + raise BadParameter(message, ctx=ctx, param=param) + + +class CompositeParamType(ParamType): + is_composite = True + + @property + def arity(self): + raise NotImplementedError() + + +class FuncParamType(ParamType): + + def __init__(self, func): + self.name = func.__name__ + self.func = func + + def convert(self, value, param, ctx): + try: + return self.func(value) + except ValueError: + try: + value = text_type(value) + except UnicodeError: + value = str(value).decode('utf-8', 'replace') + self.fail(value, param, ctx) + + +class UnprocessedParamType(ParamType): + name = 'text' + + def convert(self, value, param, ctx): + return value + + def __repr__(self): + return 'UNPROCESSED' + + +class StringParamType(ParamType): + name = 'text' + + def convert(self, value, param, ctx): + if isinstance(value, bytes): + enc = _get_argv_encoding() + try: + value = value.decode(enc) + except UnicodeError: + fs_enc = get_filesystem_encoding() + if fs_enc != enc: + try: + value = value.decode(fs_enc) + except UnicodeError: + value = value.decode('utf-8', 'replace') + return value + return value + + def __repr__(self): + return 'STRING' + + +class Choice(ParamType): + """The choice type allows a value to be checked against a fixed set of + supported values. All of these values have to be strings. + + See :ref:`choice-opts` for an example. + """ + name = 'choice' + + def __init__(self, choices): + self.choices = choices + + def get_metavar(self, param): + return '[%s]' % '|'.join(self.choices) + + def get_missing_message(self, param): + return 'Choose from %s.' % ', '.join(self.choices) + + def convert(self, value, param, ctx): + # Exact match + if value in self.choices: + return value + + # Match through normalization + if ctx is not None and \ + ctx.token_normalize_func is not None: + value = ctx.token_normalize_func(value) + for choice in self.choices: + if ctx.token_normalize_func(choice) == value: + return choice + + self.fail('invalid choice: %s. (choose from %s)' % + (value, ', '.join(self.choices)), param, ctx) + + def __repr__(self): + return 'Choice(%r)' % list(self.choices) + + +class IntParamType(ParamType): + name = 'integer' + + def convert(self, value, param, ctx): + try: + return int(value) + except (ValueError, UnicodeError): + self.fail('%s is not a valid integer' % value, param, ctx) + + def __repr__(self): + return 'INT' + + +class IntRange(IntParamType): + """A parameter that works similar to :data:`click.INT` but restricts + the value to fit into a range. The default behavior is to fail if the + value falls outside the range, but it can also be silently clamped + between the two edges. + + See :ref:`ranges` for an example. + """ + name = 'integer range' + + def __init__(self, min=None, max=None, clamp=False): + self.min = min + self.max = max + self.clamp = clamp + + def convert(self, value, param, ctx): + rv = IntParamType.convert(self, value, param, ctx) + if self.clamp: + if self.min is not None and rv < self.min: + return self.min + if self.max is not None and rv > self.max: + return self.max + if self.min is not None and rv < self.min or \ + self.max is not None and rv > self.max: + if self.min is None: + self.fail('%s is bigger than the maximum valid value ' + '%s.' % (rv, self.max), param, ctx) + elif self.max is None: + self.fail('%s is smaller than the minimum valid value ' + '%s.' % (rv, self.min), param, ctx) + else: + self.fail('%s is not in the valid range of %s to %s.' + % (rv, self.min, self.max), param, ctx) + return rv + + def __repr__(self): + return 'IntRange(%r, %r)' % (self.min, self.max) + + +class BoolParamType(ParamType): + name = 'boolean' + + def convert(self, value, param, ctx): + if isinstance(value, bool): + return bool(value) + value = value.lower() + if value in ('true', '1', 'yes', 'y'): + return True + elif value in ('false', '0', 'no', 'n'): + return False + self.fail('%s is not a valid boolean' % value, param, ctx) + + def __repr__(self): + return 'BOOL' + + +class FloatParamType(ParamType): + name = 'float' + + def convert(self, value, param, ctx): + try: + return float(value) + except (UnicodeError, ValueError): + self.fail('%s is not a valid floating point value' % + value, param, ctx) + + def __repr__(self): + return 'FLOAT' + + +class UUIDParameterType(ParamType): + name = 'uuid' + + def convert(self, value, param, ctx): + import uuid + try: + if PY2 and isinstance(value, text_type): + value = value.encode('ascii') + return uuid.UUID(value) + except (UnicodeError, ValueError): + self.fail('%s is not a valid UUID value' % value, param, ctx) + + def __repr__(self): + return 'UUID' + + +class File(ParamType): + """Declares a parameter to be a file for reading or writing. The file + is automatically closed once the context tears down (after the command + finished working). + + Files can be opened for reading or writing. The special value ``-`` + indicates stdin or stdout depending on the mode. + + By default, the file is opened for reading text data, but it can also be + opened in binary mode or for writing. The encoding parameter can be used + to force a specific encoding. + + The `lazy` flag controls if the file should be opened immediately or + upon first IO. The default is to be non lazy for standard input and + output streams as well as files opened for reading, lazy otherwise. + + Starting with Click 2.0, files can also be opened atomically in which + case all writes go into a separate file in the same folder and upon + completion the file will be moved over to the original location. This + is useful if a file regularly read by other users is modified. + + See :ref:`file-args` for more information. + """ + name = 'filename' + envvar_list_splitter = os.path.pathsep + + def __init__(self, mode='r', encoding=None, errors='strict', lazy=None, + atomic=False): + self.mode = mode + self.encoding = encoding + self.errors = errors + self.lazy = lazy + self.atomic = atomic + + def resolve_lazy_flag(self, value): + if self.lazy is not None: + return self.lazy + if value == '-': + return False + elif 'w' in self.mode: + return True + return False + + def convert(self, value, param, ctx): + try: + if hasattr(value, 'read') or hasattr(value, 'write'): + return value + + lazy = self.resolve_lazy_flag(value) + + if lazy: + f = LazyFile(value, self.mode, self.encoding, self.errors, + atomic=self.atomic) + if ctx is not None: + ctx.call_on_close(f.close_intelligently) + return f + + f, should_close = open_stream(value, self.mode, + self.encoding, self.errors, + atomic=self.atomic) + # If a context is provided, we automatically close the file + # at the end of the context execution (or flush out). If a + # context does not exist, it's the caller's responsibility to + # properly close the file. This for instance happens when the + # type is used with prompts. + if ctx is not None: + if should_close: + ctx.call_on_close(safecall(f.close)) + else: + ctx.call_on_close(safecall(f.flush)) + return f + except (IOError, OSError) as e: + self.fail('Could not open file: %s: %s' % ( + filename_to_ui(value), + get_streerror(e), + ), param, ctx) + + +class Path(ParamType): + """The path type is similar to the :class:`File` type but it performs + different checks. First of all, instead of returning an open file + handle it returns just the filename. Secondly, it can perform various + basic checks about what the file or directory should be. + + .. versionchanged:: 6.0 + `allow_dash` was added. + + :param exists: if set to true, the file or directory needs to exist for + this value to be valid. If this is not required and a + file does indeed not exist, then all further checks are + silently skipped. + :param file_okay: controls if a file is a possible value. + :param dir_okay: controls if a directory is a possible value. + :param writable: if true, a writable check is performed. + :param readable: if true, a readable check is performed. + :param resolve_path: if this is true, then the path is fully resolved + before the value is passed onwards. This means + that it's absolute and symlinks are resolved. + :param allow_dash: If this is set to `True`, a single dash to indicate + standard streams is permitted. + :param type: optionally a string type that should be used to + represent the path. The default is `None` which + means the return value will be either bytes or + unicode depending on what makes most sense given the + input data Click deals with. + """ + envvar_list_splitter = os.path.pathsep + + def __init__(self, exists=False, file_okay=True, dir_okay=True, + writable=False, readable=True, resolve_path=False, + allow_dash=False, path_type=None): + self.exists = exists + self.file_okay = file_okay + self.dir_okay = dir_okay + self.writable = writable + self.readable = readable + self.resolve_path = resolve_path + self.allow_dash = allow_dash + self.type = path_type + + if self.file_okay and not self.dir_okay: + self.name = 'file' + self.path_type = 'File' + if self.dir_okay and not self.file_okay: + self.name = 'directory' + self.path_type = 'Directory' + else: + self.name = 'path' + self.path_type = 'Path' + + def coerce_path_result(self, rv): + if self.type is not None and not isinstance(rv, self.type): + if self.type is text_type: + rv = rv.decode(get_filesystem_encoding()) + else: + rv = rv.encode(get_filesystem_encoding()) + return rv + + def convert(self, value, param, ctx): + rv = value + + is_dash = self.file_okay and self.allow_dash and rv in (b'-', '-') + + if not is_dash: + if self.resolve_path: + rv = os.path.realpath(rv) + + try: + st = os.stat(rv) + except OSError: + if not self.exists: + return self.coerce_path_result(rv) + self.fail('%s "%s" does not exist.' % ( + self.path_type, + filename_to_ui(value) + ), param, ctx) + + if not self.file_okay and stat.S_ISREG(st.st_mode): + self.fail('%s "%s" is a file.' % ( + self.path_type, + filename_to_ui(value) + ), param, ctx) + if not self.dir_okay and stat.S_ISDIR(st.st_mode): + self.fail('%s "%s" is a directory.' % ( + self.path_type, + filename_to_ui(value) + ), param, ctx) + if self.writable and not os.access(value, os.W_OK): + self.fail('%s "%s" is not writable.' % ( + self.path_type, + filename_to_ui(value) + ), param, ctx) + if self.readable and not os.access(value, os.R_OK): + self.fail('%s "%s" is not readable.' % ( + self.path_type, + filename_to_ui(value) + ), param, ctx) + + return self.coerce_path_result(rv) + + +class Tuple(CompositeParamType): + """The default behavior of Click is to apply a type on a value directly. + This works well in most cases, except for when `nargs` is set to a fixed + count and different types should be used for different items. In this + case the :class:`Tuple` type can be used. This type can only be used + if `nargs` is set to a fixed number. + + For more information see :ref:`tuple-type`. + + This can be selected by using a Python tuple literal as a type. + + :param types: a list of types that should be used for the tuple items. + """ + + def __init__(self, types): + self.types = [convert_type(ty) for ty in types] + + @property + def name(self): + return "<" + " ".join(ty.name for ty in self.types) + ">" + + @property + def arity(self): + return len(self.types) + + def convert(self, value, param, ctx): + if len(value) != len(self.types): + raise TypeError('It would appear that nargs is set to conflict ' + 'with the composite type arity.') + return tuple(ty(x, param, ctx) for ty, x in zip(self.types, value)) + + +def convert_type(ty, default=None): + """Converts a callable or python ty into the most appropriate param + ty. + """ + guessed_type = False + if ty is None and default is not None: + if isinstance(default, tuple): + ty = tuple(map(type, default)) + else: + ty = type(default) + guessed_type = True + + if isinstance(ty, tuple): + return Tuple(ty) + if isinstance(ty, ParamType): + return ty + if ty is text_type or ty is str or ty is None: + return STRING + if ty is int: + return INT + # Booleans are only okay if not guessed. This is done because for + # flags the default value is actually a bit of a lie in that it + # indicates which of the flags is the one we want. See get_default() + # for more information. + if ty is bool and not guessed_type: + return BOOL + if ty is float: + return FLOAT + if guessed_type: + return STRING + + # Catch a common mistake + if __debug__: + try: + if issubclass(ty, ParamType): + raise AssertionError('Attempted to use an uninstantiated ' + 'parameter type (%s).' % ty) + except TypeError: + pass + return FuncParamType(ty) + + +#: A dummy parameter type that just does nothing. From a user's +#: perspective this appears to just be the same as `STRING` but internally +#: no string conversion takes place. This is necessary to achieve the +#: same bytes/unicode behavior on Python 2/3 in situations where you want +#: to not convert argument types. This is usually useful when working +#: with file paths as they can appear in bytes and unicode. +#: +#: For path related uses the :class:`Path` type is a better choice but +#: there are situations where an unprocessed type is useful which is why +#: it is is provided. +#: +#: .. versionadded:: 4.0 +UNPROCESSED = UnprocessedParamType() + +#: A unicode string parameter type which is the implicit default. This +#: can also be selected by using ``str`` as type. +STRING = StringParamType() + +#: An integer parameter. This can also be selected by using ``int`` as +#: type. +INT = IntParamType() + +#: A floating point value parameter. This can also be selected by using +#: ``float`` as type. +FLOAT = FloatParamType() + +#: A boolean parameter. This is the default for boolean flags. This can +#: also be selected by using ``bool`` as a type. +BOOL = BoolParamType() + +#: A UUID parameter. +UUID = UUIDParameterType() diff -Nru click-0.4.43+16.04.20160203/click/_unicodefun.py click-6.7/click/_unicodefun.py --- click-0.4.43+16.04.20160203/click/_unicodefun.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/click/_unicodefun.py 2017-01-06 22:32:37.000000000 +0000 @@ -0,0 +1,118 @@ +import os +import sys +import codecs + +from ._compat import PY2 + + +# If someone wants to vendor click, we want to ensure the +# correct package is discovered. Ideally we could use a +# relative import here but unfortunately Python does not +# support that. +click = sys.modules[__name__.rsplit('.', 1)[0]] + + +def _find_unicode_literals_frame(): + import __future__ + frm = sys._getframe(1) + idx = 1 + while frm is not None: + if frm.f_globals.get('__name__', '').startswith('click.'): + frm = frm.f_back + idx += 1 + elif frm.f_code.co_flags & __future__.unicode_literals.compiler_flag: + return idx + else: + break + return 0 + + +def _check_for_unicode_literals(): + if not __debug__: + return + if not PY2 or click.disable_unicode_literals_warning: + return + bad_frame = _find_unicode_literals_frame() + if bad_frame <= 0: + return + from warnings import warn + warn(Warning('Click detected the use of the unicode_literals ' + '__future__ import. This is heavily discouraged ' + 'because it can introduce subtle bugs in your ' + 'code. You should instead use explicit u"" literals ' + 'for your unicode strings. For more information see ' + 'http://click.pocoo.org/python3/'), + stacklevel=bad_frame) + + +def _verify_python3_env(): + """Ensures that the environment is good for unicode on Python 3.""" + if PY2: + return + try: + import locale + fs_enc = codecs.lookup(locale.getpreferredencoding()).name + except Exception: + fs_enc = 'ascii' + if fs_enc != 'ascii': + return + + extra = '' + if os.name == 'posix': + import subprocess + rv = subprocess.Popen(['locale', '-a'], stdout=subprocess.PIPE, + stderr=subprocess.PIPE).communicate()[0] + good_locales = set() + has_c_utf8 = False + + # Make sure we're operating on text here. + if isinstance(rv, bytes): + rv = rv.decode('ascii', 'replace') + + for line in rv.splitlines(): + locale = line.strip() + if locale.lower().endswith(('.utf-8', '.utf8')): + good_locales.add(locale) + if locale.lower() in ('c.utf8', 'c.utf-8'): + has_c_utf8 = True + + extra += '\n\n' + if not good_locales: + extra += ( + 'Additional information: on this system no suitable UTF-8\n' + 'locales were discovered. This most likely requires resolving\n' + 'by reconfiguring the locale system.' + ) + elif has_c_utf8: + extra += ( + 'This system supports the C.UTF-8 locale which is recommended.\n' + 'You might be able to resolve your issue by exporting the\n' + 'following environment variables:\n\n' + ' export LC_ALL=C.UTF-8\n' + ' export LANG=C.UTF-8' + ) + else: + extra += ( + 'This system lists a couple of UTF-8 supporting locales that\n' + 'you can pick from. The following suitable locales where\n' + 'discovered: %s' + ) % ', '.join(sorted(good_locales)) + + bad_locale = None + for locale in os.environ.get('LC_ALL'), os.environ.get('LANG'): + if locale and locale.lower().endswith(('.utf-8', '.utf8')): + bad_locale = locale + if locale is not None: + break + if bad_locale is not None: + extra += ( + '\n\nClick discovered that you exported a UTF-8 locale\n' + 'but the locale system could not pick up from it because\n' + 'it does not exist. The exported locale is "%s" but it\n' + 'is not supported' + ) % bad_locale + + raise RuntimeError('Click will abort further execution because Python 3 ' + 'was configured to use ASCII as encoding for the ' + 'environment. Consult http://click.pocoo.org/python3/' + 'for mitigation steps.' + extra) diff -Nru click-0.4.43+16.04.20160203/click/utils.py click-6.7/click/utils.py --- click-0.4.43+16.04.20160203/click/utils.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/click/utils.py 2017-01-06 22:32:37.000000000 +0000 @@ -0,0 +1,415 @@ +import os +import sys + +from .globals import resolve_color_default + +from ._compat import text_type, open_stream, get_filesystem_encoding, \ + get_streerror, string_types, PY2, binary_streams, text_streams, \ + filename_to_ui, auto_wrap_for_ansi, strip_ansi, should_strip_ansi, \ + _default_text_stdout, _default_text_stderr, is_bytes, WIN + +if not PY2: + from ._compat import _find_binary_writer +elif WIN: + from ._winconsole import _get_windows_argv, \ + _hash_py_argv, _initial_argv_hash + + +echo_native_types = string_types + (bytes, bytearray) + + +def _posixify(name): + return '-'.join(name.split()).lower() + + +def safecall(func): + """Wraps a function so that it swallows exceptions.""" + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except Exception: + pass + return wrapper + + +def make_str(value): + """Converts a value into a valid string.""" + if isinstance(value, bytes): + try: + return value.decode(get_filesystem_encoding()) + except UnicodeError: + return value.decode('utf-8', 'replace') + return text_type(value) + + +def make_default_short_help(help, max_length=45): + words = help.split() + total_length = 0 + result = [] + done = False + + for word in words: + if word[-1:] == '.': + done = True + new_length = result and 1 + len(word) or len(word) + if total_length + new_length > max_length: + result.append('...') + done = True + else: + if result: + result.append(' ') + result.append(word) + if done: + break + total_length += new_length + + return ''.join(result) + + +class LazyFile(object): + """A lazy file works like a regular file but it does not fully open + the file but it does perform some basic checks early to see if the + filename parameter does make sense. This is useful for safely opening + files for writing. + """ + + def __init__(self, filename, mode='r', encoding=None, errors='strict', + atomic=False): + self.name = filename + self.mode = mode + self.encoding = encoding + self.errors = errors + self.atomic = atomic + + if filename == '-': + self._f, self.should_close = open_stream(filename, mode, + encoding, errors) + else: + if 'r' in mode: + # Open and close the file in case we're opening it for + # reading so that we can catch at least some errors in + # some cases early. + open(filename, mode).close() + self._f = None + self.should_close = True + + def __getattr__(self, name): + return getattr(self.open(), name) + + def __repr__(self): + if self._f is not None: + return repr(self._f) + return '' % (self.name, self.mode) + + def open(self): + """Opens the file if it's not yet open. This call might fail with + a :exc:`FileError`. Not handling this error will produce an error + that Click shows. + """ + if self._f is not None: + return self._f + try: + rv, self.should_close = open_stream(self.name, self.mode, + self.encoding, + self.errors, + atomic=self.atomic) + except (IOError, OSError) as e: + from .exceptions import FileError + raise FileError(self.name, hint=get_streerror(e)) + self._f = rv + return rv + + def close(self): + """Closes the underlying file, no matter what.""" + if self._f is not None: + self._f.close() + + def close_intelligently(self): + """This function only closes the file if it was opened by the lazy + file wrapper. For instance this will never close stdin. + """ + if self.should_close: + self.close() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tb): + self.close_intelligently() + + def __iter__(self): + self.open() + return iter(self._f) + + +class KeepOpenFile(object): + + def __init__(self, file): + self._file = file + + def __getattr__(self, name): + return getattr(self._file, name) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tb): + pass + + def __repr__(self): + return repr(self._file) + + def __iter__(self): + return iter(self._file) + + +def echo(message=None, file=None, nl=True, err=False, color=None): + """Prints a message plus a newline to the given file or stdout. On + first sight, this looks like the print function, but it has improved + support for handling Unicode and binary data that does not fail no + matter how badly configured the system is. + + Primarily it means that you can print binary data as well as Unicode + data on both 2.x and 3.x to the given file in the most appropriate way + possible. This is a very carefree function as in that it will try its + best to not fail. As of Click 6.0 this includes support for unicode + output on the Windows console. + + In addition to that, if `colorama`_ is installed, the echo function will + also support clever handling of ANSI codes. Essentially it will then + do the following: + + - add transparent handling of ANSI color codes on Windows. + - hide ANSI codes automatically if the destination file is not a + terminal. + + .. _colorama: http://pypi.python.org/pypi/colorama + + .. versionchanged:: 6.0 + As of Click 6.0 the echo function will properly support unicode + output on the windows console. Not that click does not modify + the interpreter in any way which means that `sys.stdout` or the + print statement or function will still not provide unicode support. + + .. versionchanged:: 2.0 + Starting with version 2.0 of Click, the echo function will work + with colorama if it's installed. + + .. versionadded:: 3.0 + The `err` parameter was added. + + .. versionchanged:: 4.0 + Added the `color` flag. + + :param message: the message to print + :param file: the file to write to (defaults to ``stdout``) + :param err: if set to true the file defaults to ``stderr`` instead of + ``stdout``. This is faster and easier than calling + :func:`get_text_stderr` yourself. + :param nl: if set to `True` (the default) a newline is printed afterwards. + :param color: controls if the terminal supports ANSI colors or not. The + default is autodetection. + """ + if file is None: + if err: + file = _default_text_stderr() + else: + file = _default_text_stdout() + + # Convert non bytes/text into the native string type. + if message is not None and not isinstance(message, echo_native_types): + message = text_type(message) + + if nl: + message = message or u'' + if isinstance(message, text_type): + message += u'\n' + else: + message += b'\n' + + # If there is a message, and we're in Python 3, and the value looks + # like bytes, we manually need to find the binary stream and write the + # message in there. This is done separately so that most stream + # types will work as you would expect. Eg: you can write to StringIO + # for other cases. + if message and not PY2 and is_bytes(message): + binary_file = _find_binary_writer(file) + if binary_file is not None: + file.flush() + binary_file.write(message) + binary_file.flush() + return + + # ANSI-style support. If there is no message or we are dealing with + # bytes nothing is happening. If we are connected to a file we want + # to strip colors. If we are on windows we either wrap the stream + # to strip the color or we use the colorama support to translate the + # ansi codes to API calls. + if message and not is_bytes(message): + color = resolve_color_default(color) + if should_strip_ansi(file, color): + message = strip_ansi(message) + elif WIN: + if auto_wrap_for_ansi is not None: + file = auto_wrap_for_ansi(file) + elif not color: + message = strip_ansi(message) + + if message: + file.write(message) + file.flush() + + +def get_binary_stream(name): + """Returns a system stream for byte processing. This essentially + returns the stream from the sys module with the given name but it + solves some compatibility issues between different Python versions. + Primarily this function is necessary for getting binary streams on + Python 3. + + :param name: the name of the stream to open. Valid names are ``'stdin'``, + ``'stdout'`` and ``'stderr'`` + """ + opener = binary_streams.get(name) + if opener is None: + raise TypeError('Unknown standard stream %r' % name) + return opener() + + +def get_text_stream(name, encoding=None, errors='strict'): + """Returns a system stream for text processing. This usually returns + a wrapped stream around a binary stream returned from + :func:`get_binary_stream` but it also can take shortcuts on Python 3 + for already correctly configured streams. + + :param name: the name of the stream to open. Valid names are ``'stdin'``, + ``'stdout'`` and ``'stderr'`` + :param encoding: overrides the detected default encoding. + :param errors: overrides the default error mode. + """ + opener = text_streams.get(name) + if opener is None: + raise TypeError('Unknown standard stream %r' % name) + return opener(encoding, errors) + + +def open_file(filename, mode='r', encoding=None, errors='strict', + lazy=False, atomic=False): + """This is similar to how the :class:`File` works but for manual + usage. Files are opened non lazy by default. This can open regular + files as well as stdin/stdout if ``'-'`` is passed. + + If stdin/stdout is returned the stream is wrapped so that the context + manager will not close the stream accidentally. This makes it possible + to always use the function like this without having to worry to + accidentally close a standard stream:: + + with open_file(filename) as f: + ... + + .. versionadded:: 3.0 + + :param filename: the name of the file to open (or ``'-'`` for stdin/stdout). + :param mode: the mode in which to open the file. + :param encoding: the encoding to use. + :param errors: the error handling for this file. + :param lazy: can be flipped to true to open the file lazily. + :param atomic: in atomic mode writes go into a temporary file and it's + moved on close. + """ + if lazy: + return LazyFile(filename, mode, encoding, errors, atomic=atomic) + f, should_close = open_stream(filename, mode, encoding, errors, + atomic=atomic) + if not should_close: + f = KeepOpenFile(f) + return f + + +def get_os_args(): + """This returns the argument part of sys.argv in the most appropriate + form for processing. What this means is that this return value is in + a format that works for Click to process but does not necessarily + correspond well to what's actually standard for the interpreter. + + On most environments the return value is ``sys.argv[:1]`` unchanged. + However if you are on Windows and running Python 2 the return value + will actually be a list of unicode strings instead because the + default behavior on that platform otherwise will not be able to + carry all possible values that sys.argv can have. + + .. versionadded:: 6.0 + """ + # We can only extract the unicode argv if sys.argv has not been + # changed since the startup of the application. + if PY2 and WIN and _initial_argv_hash == _hash_py_argv(): + return _get_windows_argv() + return sys.argv[1:] + + +def format_filename(filename, shorten=False): + """Formats a filename for user display. The main purpose of this + function is to ensure that the filename can be displayed at all. This + will decode the filename to unicode if necessary in a way that it will + not fail. Optionally, it can shorten the filename to not include the + full path to the filename. + + :param filename: formats a filename for UI display. This will also convert + the filename into unicode without failing. + :param shorten: this optionally shortens the filename to strip of the + path that leads up to it. + """ + if shorten: + filename = os.path.basename(filename) + return filename_to_ui(filename) + + +def get_app_dir(app_name, roaming=True, force_posix=False): + r"""Returns the config folder for the application. The default behavior + is to return whatever is most appropriate for the operating system. + + To give you an idea, for an app called ``"Foo Bar"``, something like + the following folders could be returned: + + Mac OS X: + ``~/Library/Application Support/Foo Bar`` + Mac OS X (POSIX): + ``~/.foo-bar`` + Unix: + ``~/.config/foo-bar`` + Unix (POSIX): + ``~/.foo-bar`` + Win XP (roaming): + ``C:\Documents and Settings\\Local Settings\Application Data\Foo Bar`` + Win XP (not roaming): + ``C:\Documents and Settings\\Application Data\Foo Bar`` + Win 7 (roaming): + ``C:\Users\\AppData\Roaming\Foo Bar`` + Win 7 (not roaming): + ``C:\Users\\AppData\Local\Foo Bar`` + + .. versionadded:: 2.0 + + :param app_name: the application name. This should be properly capitalized + and can contain whitespace. + :param roaming: controls if the folder should be roaming or not on Windows. + Has no affect otherwise. + :param force_posix: if this is set to `True` then on any POSIX system the + folder will be stored in the home folder with a leading + dot instead of the XDG config home or darwin's + application support folder. + """ + if WIN: + key = roaming and 'APPDATA' or 'LOCALAPPDATA' + folder = os.environ.get(key) + if folder is None: + folder = os.path.expanduser('~') + return os.path.join(folder, app_name) + if force_posix: + return os.path.join(os.path.expanduser('~/.' + _posixify(app_name))) + if sys.platform == 'darwin': + return os.path.join(os.path.expanduser( + '~/Library/Application Support'), app_name) + return os.path.join( + os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')), + _posixify(app_name)) diff -Nru click-0.4.43+16.04.20160203/click/versions.py click-6.7/click/versions.py --- click-0.4.43+16.04.20160203/click/versions.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/click/versions.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,18 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Click package versioning.""" - -spec_version = "0.4" diff -Nru click-0.4.43+16.04.20160203/click/_winconsole.py click-6.7/click/_winconsole.py --- click-0.4.43+16.04.20160203/click/_winconsole.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/click/_winconsole.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,273 @@ +# -*- coding: utf-8 -*- +# This module is based on the excellent work by Adam Bartoš who +# provided a lot of what went into the implementation here in +# the discussion to issue1602 in the Python bug tracker. +# +# There are some general differences in regards to how this works +# compared to the original patches as we do not need to patch +# the entire interpreter but just work in our little world of +# echo and prmopt. + +import io +import os +import sys +import zlib +import time +import ctypes +import msvcrt +from click._compat import _NonClosingTextIOWrapper, text_type, PY2 +from ctypes import byref, POINTER, c_int, c_char, c_char_p, \ + c_void_p, py_object, c_ssize_t, c_ulong, windll, WINFUNCTYPE +try: + from ctypes import pythonapi + PyObject_GetBuffer = pythonapi.PyObject_GetBuffer + PyBuffer_Release = pythonapi.PyBuffer_Release +except ImportError: + pythonapi = None +from ctypes.wintypes import LPWSTR, LPCWSTR + + +c_ssize_p = POINTER(c_ssize_t) + +kernel32 = windll.kernel32 +GetStdHandle = kernel32.GetStdHandle +ReadConsoleW = kernel32.ReadConsoleW +WriteConsoleW = kernel32.WriteConsoleW +GetLastError = kernel32.GetLastError +GetCommandLineW = WINFUNCTYPE(LPWSTR)( + ('GetCommandLineW', windll.kernel32)) +CommandLineToArgvW = WINFUNCTYPE( + POINTER(LPWSTR), LPCWSTR, POINTER(c_int))( + ('CommandLineToArgvW', windll.shell32)) + + +STDIN_HANDLE = GetStdHandle(-10) +STDOUT_HANDLE = GetStdHandle(-11) +STDERR_HANDLE = GetStdHandle(-12) + + +PyBUF_SIMPLE = 0 +PyBUF_WRITABLE = 1 + +ERROR_SUCCESS = 0 +ERROR_NOT_ENOUGH_MEMORY = 8 +ERROR_OPERATION_ABORTED = 995 + +STDIN_FILENO = 0 +STDOUT_FILENO = 1 +STDERR_FILENO = 2 + +EOF = b'\x1a' +MAX_BYTES_WRITTEN = 32767 + + +class Py_buffer(ctypes.Structure): + _fields_ = [ + ('buf', c_void_p), + ('obj', py_object), + ('len', c_ssize_t), + ('itemsize', c_ssize_t), + ('readonly', c_int), + ('ndim', c_int), + ('format', c_char_p), + ('shape', c_ssize_p), + ('strides', c_ssize_p), + ('suboffsets', c_ssize_p), + ('internal', c_void_p) + ] + + if PY2: + _fields_.insert(-1, ('smalltable', c_ssize_t * 2)) + + +# On PyPy we cannot get buffers so our ability to operate here is +# serverly limited. +if pythonapi is None: + get_buffer = None +else: + def get_buffer(obj, writable=False): + buf = Py_buffer() + flags = PyBUF_WRITABLE if writable else PyBUF_SIMPLE + PyObject_GetBuffer(py_object(obj), byref(buf), flags) + try: + buffer_type = c_char * buf.len + return buffer_type.from_address(buf.buf) + finally: + PyBuffer_Release(byref(buf)) + + +class _WindowsConsoleRawIOBase(io.RawIOBase): + + def __init__(self, handle): + self.handle = handle + + def isatty(self): + io.RawIOBase.isatty(self) + return True + + +class _WindowsConsoleReader(_WindowsConsoleRawIOBase): + + def readable(self): + return True + + def readinto(self, b): + bytes_to_be_read = len(b) + if not bytes_to_be_read: + return 0 + elif bytes_to_be_read % 2: + raise ValueError('cannot read odd number of bytes from ' + 'UTF-16-LE encoded console') + + buffer = get_buffer(b, writable=True) + code_units_to_be_read = bytes_to_be_read // 2 + code_units_read = c_ulong() + + rv = ReadConsoleW(self.handle, buffer, code_units_to_be_read, + byref(code_units_read), None) + if GetLastError() == ERROR_OPERATION_ABORTED: + # wait for KeyboardInterrupt + time.sleep(0.1) + if not rv: + raise OSError('Windows error: %s' % GetLastError()) + + if buffer[0] == EOF: + return 0 + return 2 * code_units_read.value + + +class _WindowsConsoleWriter(_WindowsConsoleRawIOBase): + + def writable(self): + return True + + @staticmethod + def _get_error_message(errno): + if errno == ERROR_SUCCESS: + return 'ERROR_SUCCESS' + elif errno == ERROR_NOT_ENOUGH_MEMORY: + return 'ERROR_NOT_ENOUGH_MEMORY' + return 'Windows error %s' % errno + + def write(self, b): + bytes_to_be_written = len(b) + buf = get_buffer(b) + code_units_to_be_written = min(bytes_to_be_written, + MAX_BYTES_WRITTEN) // 2 + code_units_written = c_ulong() + + WriteConsoleW(self.handle, buf, code_units_to_be_written, + byref(code_units_written), None) + bytes_written = 2 * code_units_written.value + + if bytes_written == 0 and bytes_to_be_written > 0: + raise OSError(self._get_error_message(GetLastError())) + return bytes_written + + +class ConsoleStream(object): + + def __init__(self, text_stream, byte_stream): + self._text_stream = text_stream + self.buffer = byte_stream + + @property + def name(self): + return self.buffer.name + + def write(self, x): + if isinstance(x, text_type): + return self._text_stream.write(x) + try: + self.flush() + except Exception: + pass + return self.buffer.write(x) + + def writelines(self, lines): + for line in lines: + self.write(line) + + def __getattr__(self, name): + return getattr(self._text_stream, name) + + def isatty(self): + return self.buffer.isatty() + + def __repr__(self): + return '' % ( + self.name, + self.encoding, + ) + + +def _get_text_stdin(buffer_stream): + text_stream = _NonClosingTextIOWrapper( + io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)), + 'utf-16-le', 'strict', line_buffering=True) + return ConsoleStream(text_stream, buffer_stream) + + +def _get_text_stdout(buffer_stream): + text_stream = _NonClosingTextIOWrapper( + _WindowsConsoleWriter(STDOUT_HANDLE), + 'utf-16-le', 'strict', line_buffering=True) + return ConsoleStream(text_stream, buffer_stream) + + +def _get_text_stderr(buffer_stream): + text_stream = _NonClosingTextIOWrapper( + _WindowsConsoleWriter(STDERR_HANDLE), + 'utf-16-le', 'strict', line_buffering=True) + return ConsoleStream(text_stream, buffer_stream) + + +if PY2: + def _hash_py_argv(): + return zlib.crc32('\x00'.join(sys.argv[1:])) + + _initial_argv_hash = _hash_py_argv() + + def _get_windows_argv(): + argc = c_int(0) + argv_unicode = CommandLineToArgvW(GetCommandLineW(), byref(argc)) + argv = [argv_unicode[i] for i in range(0, argc.value)] + + if not hasattr(sys, 'frozen'): + argv = argv[1:] + while len(argv) > 0: + arg = argv[0] + if not arg.startswith('-') or arg == '-': + break + argv = argv[1:] + if arg.startswith(('-c', '-m')): + break + + return argv[1:] + + +_stream_factories = { + 0: _get_text_stdin, + 1: _get_text_stdout, + 2: _get_text_stderr, +} + + +def _get_windows_console_stream(f, encoding, errors): + if get_buffer is not None and \ + encoding in ('utf-16-le', None) \ + and errors in ('strict', None) and \ + hasattr(f, 'isatty') and f.isatty(): + func = _stream_factories.get(f.fileno()) + if func is not None: + if not PY2: + f = getattr(f, 'buffer') + if f is None: + return None + else: + # If we are on Python 2 we need to set the stream that we + # deal with to binary mode as otherwise the exercise if a + # bit moot. The same problems apply as for + # get_binary_stdin and friends from _compat. + msvcrt.setmode(f.fileno(), os.O_BINARY) + return func(f) diff -Nru click-0.4.43+16.04.20160203/click.egg-info/dependency_links.txt click-6.7/click.egg-info/dependency_links.txt --- click-0.4.43+16.04.20160203/click.egg-info/dependency_links.txt 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/click.egg-info/dependency_links.txt 2017-01-06 22:41:11.000000000 +0000 @@ -0,0 +1 @@ + diff -Nru click-0.4.43+16.04.20160203/click.egg-info/PKG-INFO click-6.7/click.egg-info/PKG-INFO --- click-0.4.43+16.04.20160203/click.egg-info/PKG-INFO 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/click.egg-info/PKG-INFO 2017-01-06 22:41:11.000000000 +0000 @@ -0,0 +1,13 @@ +Metadata-Version: 1.1 +Name: click +Version: 6.7 +Summary: A simple wrapper around optparse for powerful command line utilities. +Home-page: http://github.com/mitsuhiko/click +Author: Armin Ronacher +Author-email: armin.ronacher@active-4.com +License: UNKNOWN +Description: UNKNOWN +Platform: UNKNOWN +Classifier: License :: OSI Approved :: BSD License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 diff -Nru click-0.4.43+16.04.20160203/click.egg-info/SOURCES.txt click-6.7/click.egg-info/SOURCES.txt --- click-0.4.43+16.04.20160203/click.egg-info/SOURCES.txt 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/click.egg-info/SOURCES.txt 2017-01-06 22:41:11.000000000 +0000 @@ -0,0 +1,114 @@ +CHANGES +LICENSE +MANIFEST.in +Makefile +README +setup.cfg +setup.py +artwork/logo.svg +click/__init__.py +click/_bashcomplete.py +click/_compat.py +click/_termui_impl.py +click/_textwrap.py +click/_unicodefun.py +click/_winconsole.py +click/core.py +click/decorators.py +click/exceptions.py +click/formatting.py +click/globals.py +click/parser.py +click/termui.py +click/testing.py +click/types.py +click/utils.py +click.egg-info/PKG-INFO +click.egg-info/SOURCES.txt +click.egg-info/dependency_links.txt +click.egg-info/top_level.txt +docs/Makefile +docs/advanced.rst +docs/api.rst +docs/arguments.rst +docs/bashcomplete.rst +docs/changelog.rst +docs/clickdoctools.py +docs/commands.rst +docs/complex.rst +docs/conf.py +docs/contrib.rst +docs/documentation.rst +docs/exceptions.rst +docs/index.rst +docs/license.rst +docs/make.bat +docs/options.rst +docs/parameters.rst +docs/prompts.rst +docs/python3.rst +docs/quickstart.rst +docs/setuptools.rst +docs/testing.rst +docs/upgrading.rst +docs/utils.rst +docs/why.rst +docs/wincmd.rst +docs/_static/click-small.png +docs/_static/click-small@2x.png +docs/_static/click.png +docs/_static/click@2x.png +docs/_templates/sidebarintro.html +docs/_templates/sidebarlogo.html +examples/README +examples/aliases/README +examples/aliases/aliases.ini +examples/aliases/aliases.py +examples/aliases/setup.py +examples/colors/README +examples/colors/colors.py +examples/colors/setup.py +examples/complex/README +examples/complex/setup.py +examples/complex/complex/__init__.py +examples/complex/complex/cli.py +examples/complex/complex/commands/__init__.py +examples/complex/complex/commands/cmd_init.py +examples/complex/complex/commands/cmd_status.py +examples/imagepipe/.gitignore +examples/imagepipe/README +examples/imagepipe/example01.jpg +examples/imagepipe/example02.jpg +examples/imagepipe/imagepipe.py +examples/imagepipe/setup.py +examples/inout/README +examples/inout/inout.py +examples/inout/setup.py +examples/naval/README +examples/naval/naval.py +examples/naval/setup.py +examples/repo/README +examples/repo/repo.py +examples/repo/setup.py +examples/termui/README +examples/termui/setup.py +examples/termui/termui.py +examples/validation/README +examples/validation/setup.py +examples/validation/validation.py +tests/conftest.py +tests/test_arguments.py +tests/test_bashcomplete.py +tests/test_basic.py +tests/test_chain.py +tests/test_commands.py +tests/test_compat.py +tests/test_context.py +tests/test_defaults.py +tests/test_formatting.py +tests/test_imports.py +tests/test_normalization.py +tests/test_options.py +tests/test_termui.py +tests/test_testing.py +tests/test_utils.py \ No newline at end of file diff -Nru click-0.4.43+16.04.20160203/click.egg-info/top_level.txt click-6.7/click.egg-info/top_level.txt --- click-0.4.43+16.04.20160203/click.egg-info/top_level.txt 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/click.egg-info/top_level.txt 2017-01-06 22:41:11.000000000 +0000 @@ -0,0 +1 @@ +click diff -Nru click-0.4.43+16.04.20160203/conf/databases/10_core.conf click-6.7/conf/databases/10_core.conf --- click-0.4.43+16.04.20160203/conf/databases/10_core.conf 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/conf/databases/10_core.conf 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -[Click Database] -# Preinstalled Ubuntu core applications. -root = /usr/share/click/preinstalled diff -Nru click-0.4.43+16.04.20160203/conf/databases/20_custom.conf click-6.7/conf/databases/20_custom.conf --- click-0.4.43+16.04.20160203/conf/databases/20_custom.conf 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/conf/databases/20_custom.conf 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -[Click Database] -# OEM/carrier/etc. customisations. -root = /custom/click diff -Nru click-0.4.43+16.04.20160203/conf/databases/99_default.conf.in click-6.7/conf/databases/99_default.conf.in --- click-0.4.43+16.04.20160203/conf/databases/99_default.conf.in 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/conf/databases/99_default.conf.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -[Click Database] -# User-installed applications. -root = @DEFAULT_ROOT@ diff -Nru click-0.4.43+16.04.20160203/conf/databases/Makefile.am click-6.7/conf/databases/Makefile.am --- click-0.4.43+16.04.20160203/conf/databases/Makefile.am 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/conf/databases/Makefile.am 1970-01-01 00:00:00.000000000 +0000 @@ -1,6 +0,0 @@ -databasesdir = $(sysconfdir)/click/databases - -databases_DATA = \ - 10_core.conf \ - 20_custom.conf \ - 99_default.conf diff -Nru click-0.4.43+16.04.20160203/conf/Makefile.am click-6.7/conf/Makefile.am --- click-0.4.43+16.04.20160203/conf/Makefile.am 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/conf/Makefile.am 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -SUBDIRS = databases diff -Nru click-0.4.43+16.04.20160203/configure.ac click-6.7/configure.ac --- click-0.4.43+16.04.20160203/configure.ac 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/configure.ac 1970-01-01 00:00:00.000000000 +0000 @@ -1,154 +0,0 @@ -AC_INIT([click],m4_esyscmd([./get-version]),[https://launchpad.net/click]) -AC_CONFIG_SRCDIR([preload/clickpreload.c]) -AC_CONFIG_AUX_DIR([build-aux]) -AC_CONFIG_MACRO_DIR([m4]) -AM_INIT_AUTOMAKE([foreign]) -AM_CONFIG_HEADER([config.h]) -AC_USE_SYSTEM_EXTENSIONS -LT_INIT([disable-static]) - -AC_SUBST([GETTEXT_PACKAGE], [click]) -IT_PROG_INTLTOOL - -AC_PROG_CC -CFLAGS="$CFLAGS -Wall" - -AC_ARG_WITH([python-interpreters], - [AS_HELP_STRING([--with-python-interpreters], - [install for these Python interpreters (space-separated, default: python3)])], - [PYTHON_INTERPRETERS="$withval"], - [PYTHON_INTERPRETERS=python3]) -AC_SUBST([PYTHON_INTERPRETERS]) - -AC_ARG_WITH([default-root], - [AS_HELP_STRING([--with-default-root], - [set default root path for installed packages (default: /opt/click.ubuntu.com)])], - [DEFAULT_ROOT="$withval"], - [DEFAULT_ROOT=/opt/click.ubuntu.com]) -AC_SUBST([DEFAULT_ROOT]) - -click_save_LIBS="$LIBS" -AC_SEARCH_LIBS([dlopen], [dl]) -AC_SUBST([PRELOAD_LIBS], ["$LIBS"]) -LIBS="$click_save_LIBS" - -AC_CACHE_CHECK([for Perl vendor library directory], [click_cv_perl_vendorlib], - [click_cv_perl_vendorlib=`perl -MConfig -e 'print $Config{vendorlib}'`]) -AC_SUBST([perl_vendorlib], ["$click_cv_perl_vendorlib"]) - -AM_PROG_VALAC -PKG_CHECK_MODULES([LIBCLICK], [ - glib-2.0 >= 2.34 - gobject-2.0 >= 2.34 - json-glib-1.0 >= 0.10 - gee-0.8 - ]) -AC_SUBST([LIBCLICK_CFLAGS]) -AC_SUBST([LIBCLICK_LIBS]) - -# Structure characteristics needed for the Python/C integration in the test -# suite. -AC_COMPUTE_INT([STAT_OFFSET_UID], [offsetof(struct stat, st_uid)], [ - AC_INCLUDES_DEFAULT - #include - ]) -AC_SUBST([STAT_OFFSET_UID]) -AC_COMPUTE_INT([STAT_OFFSET_GID], [offsetof(struct stat, st_gid)], [ - AC_INCLUDES_DEFAULT - #include - ]) -AC_SUBST([STAT_OFFSET_GID]) -AC_COMPUTE_INT([STAT64_OFFSET_UID], [offsetof(struct stat64, st_uid)], [ - AC_INCLUDES_DEFAULT - #include - ]) -AC_SUBST([STAT64_OFFSET_UID]) -AC_COMPUTE_INT([STAT64_OFFSET_GID], [offsetof(struct stat64, st_gid)], [ - AC_INCLUDES_DEFAULT - #include - ]) -AC_SUBST([STAT64_OFFSET_GID]) - -GOBJECT_INTROSPECTION_REQUIRE([0.6.7]) -VAPIGEN_VAPIDIR=`$PKG_CONFIG --variable=vapidir vapigen` -AC_SUBST([VAPIGEN_VAPIDIR]) - -AC_ARG_ENABLE([packagekit], - AS_HELP_STRING([--disable-packagekit], [disable PackageKit plugin]), - [], [enable_packagekit=yes]) -if test "x$enable_packagekit" = xyes; then - PKG_CHECK_MODULES([PKPLUGIN], [ - gio-2.0 - glib-2.0 >= 2.34 - gobject-2.0 >= 2.28 - json-glib-1.0 >= 0.10 - packagekit-plugin >= 0.8.10 - ]) - AC_SUBST([PKPLUGIN_CFLAGS]) - AC_SUBST([PKPLUGIN_LIBS]) - AC_CACHE_CHECK([for packagekit-plugin library directory], - [click_cv_pkpluginlibdir], - [click_cv_pkpluginlibdir=`$PKG_CONFIG --variable=libdir packagekit-plugin`]) - AC_SUBST([pkpluginlibdir], ["$click_cv_pkpluginlibdir"]) -fi -AM_CONDITIONAL([PACKAGEKIT], [test "x$enable_packagekit" = xyes]) - -AC_ARG_ENABLE([systemd], - AS_HELP_STRING([--disable-systemd], [Disable systemd integration])) -AM_CONDITIONAL([INSTALL_SYSTEMD], [test "x$enable_systemd" != xno]) - -AC_ARG_WITH([systemdsystemunitdir], - AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd system unit files])) -if test "x$enable_systemd" != xno && test "x$with_systemdsystemunitdir" = x; then - AC_MSG_CHECKING([for systemd system unit directory]) - with_systemdsystemunitdir="$($PKG_CONFIG --variable=systemdsystemunitdir systemd)" - if test "x$with_systemdsystemunitdir" = x; then - AC_MSG_ERROR([no systemd system unit directory found]) - fi - AC_MSG_RESULT([$with_systemdsystemunitdir]) -fi -AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir]) - -AC_ARG_WITH([systemduserunitdir], - AS_HELP_STRING([--with-systemduserunitdir=DIR], [Directory for systemd user unit files])) -if test "x$enable_systemd" != xno && test "x$with_systemduserunitdir" = x; then - AC_MSG_CHECKING([for systemd user unit directory]) - with_systemduserunitdir="$($PKG_CONFIG --variable=systemduserunitdir systemd)" - if test "x$with_systemduserunitdir" = x; then - AC_MSG_ERROR([no systemd user unit directory found]) - fi - AC_MSG_RESULT([$with_systemduserunitdir]) -fi -AC_SUBST([systemduserunitdir], [$with_systemduserunitdir]) - -m4_include([m4/gcov.m4]) -AC_TDD_GCOV -AM_CONDITIONAL([HAVE_GCOV], [test "x$ac_cv_check_gcov" = xyes]) -AM_CONDITIONAL([HAVE_LCOV], [test "x$ac_cv_check_lcov" = xyes]) -AM_CONDITIONAL([HAVE_GCOVR], [test "x$ac_cv_check_gcovr" = xyes]) -AC_SUBST(COVERAGE_CFLAGS) -AC_SUBST(COVERAGE_LDFLAGS) - -AC_CONFIG_FILES([ - Makefile - click/Makefile - click/tests/Makefile - click/tests/config.py - conf/Makefile - conf/databases/Makefile - conf/databases/99_default.conf - debhelper/Makefile - init/Makefile - init/systemd/Makefile - init/upstart/Makefile - lib/Makefile - lib/click/Makefile - lib/click/click-0.4.pc - pk-plugin/Makefile - po/Makefile.in - preload/Makefile - schroot/Makefile - ]) -AC_CONFIG_FILES([lib/click/valac-wrapper], [chmod +x lib/click/valac-wrapper]) -AC_CONFIG_FILES([setup.py], [chmod +x setup.py]) -AC_OUTPUT diff -Nru click-0.4.43+16.04.20160203/.coveragerc click-6.7/.coveragerc --- click-0.4.43+16.04.20160203/.coveragerc 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/.coveragerc 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ -[run] -omit = /usr/lib/*, /usr/local/*, click/tests/*, tests/* diff -Nru click-0.4.43+16.04.20160203/debhelper/click.pm click-6.7/debhelper/click.pm --- click-0.4.43+16.04.20160203/debhelper/click.pm 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/debhelper/click.pm 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -#! /usr/bin/perl -# debhelper sequence file for click - -use warnings; -use strict; -use Debian::Debhelper::Dh_Lib; - -insert_after("dh_install", "dh_click"); - -1; diff -Nru click-0.4.43+16.04.20160203/debhelper/dh_click click-6.7/debhelper/dh_click --- click-0.4.43+16.04.20160203/debhelper/dh_click 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/debhelper/dh_click 1970-01-01 00:00:00.000000000 +0000 @@ -1,106 +0,0 @@ -#! /usr/bin/perl -w - -=head1 NAME - -dh_click - install system hooks for click - -=cut - -use strict; -use Debian::Debhelper::Dh_Lib; - -=head1 SYNOPSIS - -B [S>] - -=head1 DESCRIPTION - -dh_click is a debhelper program that is responsible for installing system -hooks for B. - -It also automatically generates the F and F commands -needed to interface with the Ubuntu B package. These commands are -inserted into the maintainer scripts by L. - -=head1 FILES - -=over 4 - -=item debian/I.click-hook - -Click package hook files, installed into -usr/share/click/hooks/I.hook in the package build directory. See -F for their format. - -=back - -=head1 OPTIONS - -=over 4 - -=item B<-n>, B<--noscripts> - -Do not modify F/F scripts. - -=item B<--name=>I - -Install the hook using the filename I instead of the default filename, -which is the package name. When this parameter is used, B looks -for and installs files named F, instead of the -usual F. - -=back - -=head1 EXAMPLES - -dh_click is usually called indirectly in a rules file via the dh command. - - %: - dh $@ --with click - -You must build-depend on at least debhelper (>= 7.0.8) to use this form, and -in any case you must build-depend on click-dev to use this program at all. - -It can also be called directly at any time before C, usually -in a binary-arch or binary-indep rule. - -=cut - -init(); - -# PROMISE: DH NOOP WITHOUT click-hook - -foreach my $package (@{$dh{DOPACKAGES}}) { - my $tmp=tmpdir($package); - my $click_hook=pkgfile($package,"click-hook"); - my $hookname=$package; - if (defined $dh{NAME}) { - $hookname=$dh{NAME}; - } - - if ($click_hook ne '') { - if (! -d "$tmp/usr/share/click/hooks") { - doit("install","-d","$tmp/usr/share/click/hooks"); - } - doit("install","-p","-m644",$click_hook,"$tmp/usr/share/click/hooks/$hookname.hook"); - - if (! $dh{NOSCRIPTS}) { - autoscript($package,"postinst","postinst-click","s/#HOOK#/$hookname/"); - autoscript($package,"prerm","prerm-click","s/#HOOK#/$hookname/"); - } - } -} - -=head1 SEE ALSO - -L - -This program is a part of click. - -=head1 AUTHOR - -Colin Watson - -Copyright (C) 2013 Canonical Ltd., licensed under the GNU GPL v3. - -=cut diff -Nru click-0.4.43+16.04.20160203/debhelper/Makefile.am click-6.7/debhelper/Makefile.am --- click-0.4.43+16.04.20160203/debhelper/Makefile.am 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/debhelper/Makefile.am 1970-01-01 00:00:00.000000000 +0000 @@ -1,11 +0,0 @@ -perllibdir = $(perl_vendorlib)/Debian/Debhelper/Sequence -debhelperdir = $(datadir)/debhelper/autoscripts - -bin_SCRIPTS = dh_click -man1_MANS = dh_click.1 -dist_perllib_DATA = click.pm -dist_debhelper_DATA = postinst-click prerm-click -CLEANFILES = $(man1_MANS) - -%.1: % - pod2man -c Debhelper -r $(PACKAGE_VERSION) $< $@ diff -Nru click-0.4.43+16.04.20160203/debhelper/postinst-click click-6.7/debhelper/postinst-click --- click-0.4.43+16.04.20160203/debhelper/postinst-click 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/debhelper/postinst-click 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -if [ "$1" = "configure" ] && which click >/dev/null 2>&1; then - click hook install #HOOK# -fi diff -Nru click-0.4.43+16.04.20160203/debhelper/prerm-click click-6.7/debhelper/prerm-click --- click-0.4.43+16.04.20160203/debhelper/prerm-click 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/debhelper/prerm-click 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -if which click >/dev/null 2>&1; then - click hook remove #HOOK# -fi diff -Nru click-0.4.43+16.04.20160203/debian/changelog click-6.7/debian/changelog --- click-0.4.43+16.04.20160203/debian/changelog 2016-02-12 11:10:44.000000000 +0000 +++ click-6.7/debian/changelog 2018-03-29 09:01:52.000000000 +0000 @@ -1,1124 +1,5 @@ -click (0.4.43+16.04.20160203-0ubuntu2) xenial; urgency=medium +click (6.7-1) xenial; urgency=high - * Fix python3-click-package -> python3-click Replaces version - (LP: #1544776). + * source package automatically created by stdeb 0.8.5 - -- Colin Watson Fri, 12 Feb 2016 11:10:43 +0000 - -click (0.4.43+16.04.20160203-0ubuntu1) xenial; urgency=medium - - [ Colin Watson ] - * Fix test skipping in build_core_apps integration tests. - * Skip chroot integration tests on architectures that were not present in - 14.04. - * Fix TestChroot.test_exists_no integration test to be more meaningful - rather than just testing a command syntax error. - * Rename python3-click back to its original name of python3-click-package - to get it out of the way of PyPI's "click" package, conflicting with - python3-click and python3-click-cli; this is unfortunate and - non-compliant with the Python policy, but from that point of view the - package that owns the name in the upstream packaging system ought to - win. - - [ CI Train Bot ] - * No-change rebuild. - - -- Colin Watson Wed, 03 Feb 2016 11:17:48 +0000 - -click (0.4.42+16.04.20151229-0ubuntu1) xenial; urgency=medium - - [ Colin Watson ] - * chroot: Point debootstrap at ports.ubuntu.com for non-primary - architectures. - * Skip build_core_apps integration tests on architectures that lack - support for native- or cross-compiling for armhf. - - [ CI Train Bot ] - * No-change rebuild. - - -- Colin Watson Tue, 29 Dec 2015 01:36:51 +0000 - -click (0.4.41+16.04.20151211-0ubuntu1) xenial; urgency=medium - - [ Colin Watson ] - * Fix spurious test_sync_without_user_db test failure. - * Fix test failures under Python 2. - * Forbid installing packages with data tarball members whose names do not - start with "./" (LP: #1506467). - * Take evasive action in case the conflicting "click" package has been - installed locally from PyPI (LP: #1486841). - * Drop use of apt_pkg from click.install, since it's no longer needed - there (LP: #1510015). - - [ Dimitri John Ledkov ] - * Require specific Click version, to avoid gi warnings that fail - test-suite (LP: #1522608). - * Set Vcs-* fields to the actual development branch. - - [ CI Train Bot ] - * No-change rebuild. - - -- Colin Watson Fri, 11 Dec 2015 01:31:29 +0000 - -click (0.4.40+15.10.20151006-0ubuntu1.1) wily; urgency=medium - - * SECURITY UPDATE: fix privilege escalation via crafted data.tar.gz that - can be used to install alternate security policy than what is defined - - click/install.py: Forbid installing packages with data tarball members - whose names do not start with "./". Patch thanks to Colin Watson. - - CVE-2015-XXXX - - LP: #1506467 - - -- Jamie Strandboge Thu, 15 Oct 2015 11:13:36 -0500 - -click (0.4.40+15.10.20151006-0ubuntu1) wily; urgency=medium - - [ Michael Vogt ] - * Don't follow symlinks when stat()ing files. (LP: 1496976) - - [ Zoltán Balogh ] - * Add and pin up the overlay PPA for vivid. - - [ Kyle Fazzari ] - * Garbage collect old user registrations. (LP: #1479001) - - [ CI Train Bot ] - * No-change rebuild. - - -- Kyle Fazzari Tue, 06 Oct 2015 19:44:52 +0000 - -click (0.4.39.1+15.10.20150702-0ubuntu1) wily; urgency=medium - - [ Michael Vogt ] - * lp:~mvo/click/lp1456328-15.10-devlibs: - - add ubuntu-sdk-15.10-dev1 - - remove ubuntu-core-15.04-dev1 - - -- CI Train Bot Thu, 02 Jul 2015 11:43:15 +0000 - -click (0.4.39) vivid; urgency=low - - [ Marco Trevisan (Treviño) ] - * handle "IP NOT FOUND" error (LP: #1433234) - - [ Michael Vogt ] - * switch the default click chroot framework to ubuntu-sdk-14.04 - - -- CI Train Bot Thu, 07 May 2015 14:47:11 +0000 - -click (0.4.38.5) vivid; urgency=medium - - [ Sebastien Bacher ] - * Don't try to replace initctl if it's not there (lp: #1430436) - - -- CI Train Bot Tue, 10 Mar 2015 18:52:17 +0000 - -click (0.4.38.4) vivid; urgency=low - - [ Michael Vogt ] - * lp:~mvo/click/lp1232130-kill-on-remove-2: - - When uninstalling a app, stop it if its running (LP: #1232130) - * lp:~mvo/click/dont-crash-for-empty-db: - - Do not crash when no database configuration is used and - Click.DB.{get,props.overlay,gc,ensure_ownership} are called. - * lp:~mvo/click/lp1219912-build-exclude: - - add a new --ignore option to click {build,buildsource} - (LP: #1219912) - * lp:~mvo/click/fix-multiple-framework-validation: - - fix framework validation for snappy - * run debian/packagekit-check with "sh" as something in jenkins - made it mode 0644 - - [ CI Train Bot ] - * New rebuild forced. - - -- CI Train Bot Thu, 26 Feb 2015 17:07:09 +0000 - -click (0.4.37) vivid; urgency=low - - [ Michael Vogt ] - * lp:~mvo/click/no-error-no-missing-systemctl: - - fix a spurious error message on systems without systemctl - * lp:~mvo/click/do-not-crash-in-build-on-broken-symlinks: - - do not crash when building a click package that contains broken - symlinks - * lp:~mvo/click/dpkg-less-verbose: - - do not show dpkg output on install unless --verbose is used - * lp:~mvo/click/lp1394256-run-user-hooks: - - ensures that click user hooks are run for all logged in users when - click is used with "--all-users". - * lp:~mvo/click/qt5-qmake-cross-armhf: - - add qt5-qmake-arm-linux-gnueabihf to chroot (LP: #1393698) - * lp:~mvo/click/chroot-15.04-multiarch: - - add ubuntu-sdk-libs-tools and oxide-codecs-extra to the chroot - * lp:~mvo/click/lp1394256-run-user-hooks-on-remove-too: - - Run the click remove user hooks for all logged in users. - * click/chroot.py: - - use string.format() for chroot TARGET selection - * skip 0.4.36 version and go straight to 0.4.37 (LP: #1418086) - - [ Zoltan Balogh ] - * lp:~bzoltan/click/vivid-transition_mirrors: - - use geoip to guess the most suitable country mirror when creating - the click chroot - - -- CI Train Bot Fri, 13 Feb 2015 14:46:34 +0000 - -click (0.4.35) vivid; urgency=low - - [ Michael Vogt ] - * lp:~mvo/click/add-run-shm: - - Ensure /run/shm is available in a click chroot - * lp:~mvo/click/ubuntu-core-framework: - - Add "ubuntu-core-15.04-dev1" click chroot - - -- Ubuntu daily release Fri, 14 Nov 2014 12:29:14 +0000 - -click (0.4.34.2) vivid; urgency=medium - - [ Michael Vogt ] - * Demote ubuntu-app-launch-tools from a click recommends to a suggests, - since they are not needed on server installs. - * Use dh-systemd to enable click system and user hook integration - (LP: #1379657). - * add ubuntu-sdk-15.04 based on ubuntu-sdk-libs/ubuntu-sdk-libs-dev - * click/tests/preload.h: - - replace deprecated "Attributes:" with annotations on the - identifier - * click/tests/Makefile.am: - - add --libtool to g-ir-scanner so that it uses the generated - libtool instead of the system libtool which is now part of - the libtool-bin package in vivid - - [ Colin Watson ] - * Make "click info" always try opening the input file as a package and - only try to interpret it as a file in an installed package if that - fails, rather than guessing by the input file extension. - * Allow "click chroot install" and "click chroot upgrade" to operate on - sessions. - - [ David Planella ] - * Adds internationalization tools and CMake macros required by the - new Qt Creator app and scope templates that provide internationalization - code. - - [ Pete Woods ] - * Include Canonical's cmake-extras project in the schroot. - - -- Ubuntu daily release Mon, 03 Nov 2014 12:49:25 +0000 - -click (0.4.33) utopic; urgency=medium - - [ Pete Woods ] - * Add scope-facing APIs to chroot build (LP: #1370727). - - [ Colin Watson ] - * Warn that "click install" without a registration may result in later - garbage-collection. - * Rearrange garbage-collection to remove versions of packages that have no - user registrations and are not running, rather than using the artificial - @gcinuse registration which never really worked properly. - * Run garbage-collection immediately before running system hooks on system - startup (LP: #1342858). - * Add new -n/--name option to "click chroot", defaulting to "click" - (LP: #1364327). - - [ Michael Vogt ] - * Make click destroy more robust by unmounting any mounted filesystem - inside the schroot first (LP: #1346723). - * Stop apps if necessary when uninstalling them (LP: #1232130). - * Add new "click framework {info,get-field}" subcommands. - - -- Ubuntu daily release Mon, 29 Sep 2014 14:18:41 +0000 - -click (0.4.32.1) utopic; urgency=low - - [ Michael Vogt ] - * fix autopkgtest failure found in 0.4.32 - - -- Ubuntu daily release Tue, 09 Sep 2014 10:02:00 +0000 - -click (0.4.32) utopic; urgency=medium - - [ Daniel Holbach ] - * Run click-review after a successful build of a click package. Offer - --no-validate as an option. - - [ Colin Watson ] - * Move integration tests to a location where they won't end up being - installed into inappropriate places on the system module path - (LP: #1337696). - * Use six.with_metaclass for TestBuildCoreApps, so that it doesn't make - pyflakes angry when running tests under Python 2. - - [ Michael Vogt ] - * Add more integration tests for "click {list,register,verify,info}". - * Only install trusted click packages by default. To override you - can run "pkcon --allow-untrusted" (LP: #1360582) - - -- Ubuntu daily release Mon, 08 Sep 2014 09:50:43 +0000 - -click (0.4.31.3) utopic; urgency=medium - - * Reinstate most of 0.4.31.2; instead, just always pass - --allow-unauthenticated to "click install" from the PackageKit plugin - for now, until we can sort out sideloading (see LP #1360582). - - -- Colin Watson Mon, 25 Aug 2014 12:25:21 -0700 - -click (0.4.31.2.is.0.4.30) utopic; urgency=medium - - * Temporary everting to previous version as the current one makes installing - click packages locally through pkcon install-local impossible, which is - breaking some other projects relying on this functionality. Needs to be - revisited. - - -- Łukasz 'sil2100' Zemczak Mon, 25 Aug 2014 17:34:07 +0200 - -click (0.4.31.2) utopic; urgency=medium - - [ Michael Vogt ] - * Add "click info" interface to get manifest corresponding to file in - installed package (LP: #1324853). - * Add support for click package gpg signatures (LP: #1330770). - - [ Colin Watson ] - * Ugly hack to get coverage reporting working again with gcovr 3.1. - - [ Ubuntu daily release ] - * New rebuild forced - - -- Ubuntu daily release Fri, 22 Aug 2014 17:19:06 +0000 - -click (0.4.30) utopic; urgency=medium - - [ Colin Watson ] - * Add many more unit tests to fill in some gaps in the coverage report. - - [ Michael Vogt ] - * Exclude non-existing users from User.get_user_names() - (LP: #1334611) - - [ Zoltan Balogh ] - * Add a set of APIs to the 14.10 frameworks. Add ubuntu-ui-toolkit-doc to - all frameworks. - - [ Ubuntu daily release ] - * New rebuild forced - - -- Ubuntu daily release Wed, 06 Aug 2014 23:33:22 +0000 - -click (0.4.29.1) utopic; urgency=medium - - [ Michael Vogt ] - * Trivial fix for the current autopkgtest failure. The ADT environment - does not have the USER environment set. - - -- Ubuntu daily release Fri, 04 Jul 2014 15:10:01 +0000 - -click (0.4.29) utopic; urgency=medium - - [ Michael Vogt ] - * Refactor click/chroot.py and improve tests. - * Test-build two ubuntu-system-apps in a click chroot as part of the - integration tests. - * Generalise handling of -devN frameworks in "click chroot". - * Add integration test for "click hook install". - - [ Colin Watson ] - * Various adjustments for improved PEP-8 compliance. - * Produce coverage only on request via ./configure --enable-gcov. Extend - it to cover C code as well, and produce a merged coverage.xml. - - [ Brendan Donegan ] - * Clean up integration tests - have one file per command and make some - things clearer. - - [ Dimitri John Ledkov ] - * Seed runtime target arch SDK app launcher, to enable remote debugging. - - -- Ubuntu daily release Thu, 03 Jul 2014 13:10:21 +0000 - -click (0.4.28) utopic; urgency=medium - - [ Colin Watson ] - * Revert change in 0.4.27 to raise an error if no databases are added; - this makes most of libclick unusable if click is not installed. - - -- Ubuntu daily release Mon, 23 Jun 2014 16:07:37 +0000 - -click (0.4.27) utopic; urgency=medium - - [ Michael Vogt ] - * Fix failing autopkgtests. - * Refactor the integration tests to be more modular. Add installation - integration tests based on - https://wiki.ubuntu.com/Process/Merges/TestPlan/click. - * Add integration tests for buildsource, pkgdir and framework. - - [ Dimitri John Ledkov ] - * Handle renaming of qtsensors5-dev to libqt5sensors5-dev in Qt 5.3. - - -- Ubuntu daily release Mon, 23 Jun 2014 11:31:50 +0000 - -click (0.4.26.1) utopic; urgency=medium - - [ Colin Watson ] - * Fix a pyflakes complaint. - - -- Ubuntu daily release Tue, 10 Jun 2014 18:04:12 +0000 - -click (0.4.26) utopic; urgency=medium - - [ Michael Vogt ] - * Add basic integration tests, run via autopkgtest. - * Add coverage testing. - - [ Colin Watson ] - * Fix DEB_BUILD_* environment variables when building on amd64 for i386 - (LP: #1328486). - - -- Colin Watson Tue, 10 Jun 2014 19:02:33 +0100 - -click (0.4.25) utopic; urgency=medium - - [ Ted Gould ] - * Check for ubuntu-app-launch-desktop hook (LP: #1326694). - - -- Ubuntu daily release Thu, 05 Jun 2014 18:18:50 +0000 - -click (0.4.24) utopic; urgency=medium - - [ Loïc Minier ] - * Add 14.10 framework in places where all frameworks are listed. - - [ Colin Watson ] - * Copy the pw_uid and pw_gid members from getpwnam's return value and - cache those separately, since getpwnam returns a pointer to a static - buffer so just caching its return value is not useful (LP: #1323998). - * Handle the renaming of upstart-app-launch to ubuntu-app-launch: we now - cope with both old and new names. - - -- Ubuntu daily release Wed, 04 Jun 2014 08:37:18 +0000 - -click (0.4.23.1) utopic; urgency=medium - - [ Colin Watson ] - * chroot: Force dpkg-architecture to recalculate everything rather than - picking up values from the environment, to avoid the test suite getting - confused by environment variables exported by dpkg-buildpackage. - - -- Ubuntu daily release Tue, 20 May 2014 13:15:41 +0000 - -click (0.4.23) utopic; urgency=medium - - [ Michael Vogt ] - * Show human-readable error message when a click chroot subcommand fails - because of existing or non-existing chroots (LP: #1296820). - * Selectively disable logging on some tests to avoid message spam during - the test runs. - * When running hooks, remove hook symlinks if framework requirements are - not met (LP: #1271944). - * Cleanup the chroot if "click chroot create" fails (unless - --keep-broken-chroot is used) - * Fix sources.list generation when native_arch and target_arch are on the - same archive server (part of LP #1319153). - * Add "click framework list" command to list available frameworks - (LP: #1294659). - - [ Pete Woods ] - * Add libunity-scopes-dev package to chroot (LP: #1320786). - - [ Sergio Schvezov ] - * click chroot creation depends on dpkg-architecture, so recommend - dpkg-dev. - - [ Colin Watson ] - * chroot: Handle the case where we can execute binaries for the target - architecture directly and thus don't need a cross-compiler - (LP: #1319153). - - -- Colin Watson Tue, 20 May 2014 14:10:11 +0100 - -click (0.4.22) utopic; urgency=medium - - [ Michael Vogt ] - * Update documentation for building the project. - * Add support for multiple frameworks (LP: #1318757). - - -- Ubuntu daily release Wed, 14 May 2014 06:28:34 +0000 - -click (0.4.21.1) trusty; urgency=medium - - [ Colin Watson ] - * When a hook command fails, include the command in the error message. - * Don't allow failure of a single hook to prevent other hooks being run. - * Log hook failures to stderr and exit non-zero, rather than propagating - an exception which is then logged as a click crash. - - -- Ubuntu daily release Tue, 08 Apr 2014 09:41:55 +0000 - -click (0.4.21) trusty; urgency=medium - - * Add *_as_string variants of manifest methods, for clients that already - have their own JSON parsing libraries and don't want to use JSON-GLib. - * Write to stderr and exit non-zero when chrooted commands fail, rather - than propagating an exception which is then logged as a click crash - (LP: #1298457). - * Make the get_manifests family of functions log errors about individual - manifests to stderr rather than crashing (LP: #1297519). - * Don't run user hooks until dbus has started; the content-hub hook needs - to modify gsettings. - * Don't rely on PyGObject supporting default None arguments; this was only - added in 3.11.1. - - -- Colin Watson Tue, 08 Apr 2014 10:13:37 +0100 - -click (0.4.20) trusty; urgency=medium - - [ Colin Watson ] - * Create system hook symlinks for all installed packages, not just current - versions. This avoids missing AppArmor profiles when there are - unregistered user-installed versions of packages lying around. - - -- Ubuntu daily release Mon, 24 Mar 2014 16:16:37 +0000 - -click (0.4.19) trusty; urgency=medium - - [ Colin Watson ] - * Set Click.User.ensure_db visibility back to private, since it's no - longer used by Click.Hook. (The C ABI is unaffected.) - * Add brief documentation on Click's multiple-database scheme, based on my - recent mail to ubuntu-phone. - * Fix a few potential GLib critical messages from the PackageKit plugin. - * Make libclick-0.4-dev depend on libjson-glib-dev for - . - * Add Requires.private to click-0.4.pc, so that programs built against - libclick pick up the proper CFLAGS including glib and json-glib. - * chroot: Allow creating 14.04 chroots. - * Include _directory and _removable dynamic manifest keys in "click info" - output (LP: #1293788). - * Document -f and -s options to "click chroot" in click(1). - * chroot: Fix code to make /finish.sh executable. - * chroot: Make /usr/sbin/policy-rc.d executable in the chroot, as - otherwise it has no effect. - * chroot: Run apt-get dist-upgrade on the chroot before trying to install - the basic build tool set. Fixes chroot creation for saucy. - - [ Benjamin Zeller ] - * Take pkexec env vars into account when creating a chroot. - - [ Dimitri John Ledkov ] - * Add session management to click chroot. - - -- Ubuntu daily release Tue, 18 Mar 2014 14:27:53 +0000 - -click (0.4.18.3) trusty; urgency=medium - - [ Colin Watson ] - * Take a slightly different approach to fixing "click hook run-user": only - try to update user registration symlinks if they already exist in the - overlay database. - - -- Ubuntu daily release Wed, 12 Mar 2014 12:02:47 +0000 - -click (0.4.18.2) trusty; urgency=medium - - * Make "click hook run-user" ensure that the user registration directory - exists before dropping privileges and trying to create symlinks in it - (LP: #1291192). - - -- Colin Watson Wed, 12 Mar 2014 11:59:31 +0000 - -click (0.4.18.1) trusty; urgency=medium - - [ Colin Watson ] - * If a user attempts to install a version of a package that is already - installed in an underlay database, then just register the appropriate - version for them rather than unpacking another copy. - * Make "click hook run-system" and "click hook run-user" consistently use - the bottom-most unpacked copy of a given version of a package, and - update hook symlinks and user registration symlinks if necessary. - - -- Ubuntu daily release Tue, 11 Mar 2014 17:22:10 +0000 - -click (0.4.18) trusty; urgency=medium - - * Give gir1.2-click-0.4 an exact-versioned dependency on libclick-0.4-0. - * Use is_symlink helper method in a few more places. - * Add a similar is_dir helper method. - * Ignore extraneous non-directories when walking a database root in - Click.DB.get_packages and Click.DB.gc. - * Make the PackageKit plugin tolerate the "_removable" dynamic manifest - key being changed to a boolean in the future. - * Document that users of "_removable" should tolerate it being a boolean. - * Use libclick when removing packages, listing packages, or searching - packages via the PackageKit plugin. - * Add libclick interfaces to get package manifests, both individually - (LP: #1287692) and for all installed packages (LP: #1287693). - * Override description-starts-with-package-name Lintian error for click; - this is describing the system as a whole rather than naming the package. - * Add libclick interfaces to get the list of frameworks supported by the - current system (LP: #1271633) and various properties of those frameworks - (LP: #1287694). - - -- Colin Watson Tue, 11 Mar 2014 17:18:07 +0000 - -click (0.4.17.2) trusty; urgency=medium - - [ Colin Watson ] - * Fix Click.User construction in "click pkgdir". - - -- Ubuntu daily release Thu, 06 Mar 2014 16:38:35 +0000 - -click (0.4.17.1) trusty; urgency=medium - - * gobject-introspection-1.0.pc is in libgirepository1.0-dev, not - gobject-introspection. Fix Build-Depends. - * Build-depend and depend on gir1.2-glib-2.0 and python3-gi. - * Map gboolean to ctypes.c_int, not ctypes.c_bool. gboolean and gint are - the same as far as glib is concerned, and ctypes does strange things - with its bool type in callbacks. - - -- Colin Watson Thu, 06 Mar 2014 16:09:33 +0000 - -click (0.4.17) trusty; urgency=medium - - * Use full path to click in Upstart jobs to save a $PATH lookup. - * Add systemd units to run Click system and user hooks at the appropriate - times. We probably won't be using these for a while, but it does no - harm to add them. - * Move an initial core of functionality (database, hooks, osextras, query, - user) from Python into a new "libclick" library, allowing - performance-critical clients to avoid the cost of starting a new Python - interpreter (LP: #1282311). - - -- Colin Watson Thu, 06 Mar 2014 14:35:26 +0000 - -click (0.4.16) trusty; urgency=medium - - [ Colin Watson ] - * hooks: Fix expansion of "$$" in hook patterns to conform to the - documented behaviour of expanding to the single character "$". - * Move version detection out of configure.ac into a separate get-version - script, since intltool-update has trouble with the previous approach. - * Stop using unittest2 if available; the relevant improvements were - integrated into the standard library's unittest in Python 2.7, and we no - longer support 2.6. - * user: When setting the registered version of a package to the version in - an underlay database (e.g. a preinstalled version vs. one in the - user-installed area), remove the overlay link rather than setting a new - one equal to the underlay; this was always the intended behaviour but - didn't work that way due to a typo. - * Add Python 3.4 to list of tested versions. - * Call setup.py from the top-level Makefile.am rather than from - debian/rules, to make the build system a bit more unified. - * Drop AM_GNU_GETTEXT and call intltoolize before autoreconf in - autogen.sh; this fixes a bug whereby "make" after "./configure" always - immediately needed to run aclocal. - * Build-depend on python3-pep8 so that test_pep8_clean doesn't need to be - skipped when running under Python 3. This can safely be removed for - backports to precise. - * Simplify click -> python3-click dependency given that both are - Architecture: any. - * Tighten packagekit-plugin-click -> click dependency to require a - matching version. - * Use dh_install --fail-missing to avoid future mistakes. - * Sync up substvar use with what debhelper actually generates for us: add - ${misc:Pre-Depends} to click and packagekit-plugin-click, and remove - ${python3:Depends} from click-dev. - * Reset SIGPIPE handling from Python's default of raising an exception to - the Unix default of terminating the process (LP: #1285790). - - -- Ubuntu daily release Tue, 04 Mar 2014 15:23:45 +0000 - -click (0.4.15) trusty; urgency=medium - - [ Stéphane Graber ] - * Set X-Auto-Uploader to no-rewrite-version - * Set Vcs-Bzr to the new target branch - - -- Ubuntu daily release Thu, 30 Jan 2014 16:12:17 +0000 - -click (0.4.14) trusty; urgency=low - - [ Colin Watson ] - * chroot: Print help if no subcommand given (LP: #1260669). - * chroot: Recommend debootstrap from click-dev, and explicitly check for - it in "click chroot create" (LP: #1260487). - * chroot: Check for root in "create" and "destroy" (LP: #1260671). - * hooks: Add a ${short-id} expansion to hook patterns; this is valid only - in user-level or single-version hooks, and expands to a new "short - application ID" without the version (LP: #1251635). - * hooks: Strip any trailing slashes from the end of patterns, as they - cause confusion with symlink-to-directory semantics and can never be - useful (LP: #1253855). - * install: Extend the interpretation of "framework" a little bit to allow - a Click package to declare that it requires multiple frameworks. This - will allow splitting up the SDK framework declarations into more - fine-grained elements. - * Policy version 3.9.5: no changes required. - * build: Enforce only a single framework declaration for now, by request. - - [ Zoltan Balogh ] - * Add qtmultimedia5-dev to the SDK framework list. - - [ Dimitri John Ledkov ] - * chroot: Add "cmake" to build_pkgs, as it is expected for cmake to be - available on any (Ubuntu) framework. - - -- Colin Watson Thu, 23 Jan 2014 17:30:54 +0000 - -click (0.4.13) trusty; urgency=low - - [ Robert Bruce Park ] - * Ignore click packages when building click packages. - - [ Colin Watson ] - * If "click build" or "click buildsource" is given a directory as the - value of its -m/--manifest option, interpret that as indicating the - "manifest.json" file in that directory (LP: #1251604). - * Ensure correct permissions on /opt/click.ubuntu.com at boot, since a - system image update may have changed clickpkg's UID/GID (LP: #1259253). - - -- Colin Watson Tue, 10 Dec 2013 14:33:42 +0000 - -click (0.4.12) trusty; urgency=low - - [ Colin Watson ] - * Adjust top-level "click help" entry for "install" to point to pkcon. - * Fix hook installation tests to test Unicode manifests properly. - * Read version and date from debian/changelog when building documentation. - * Declare click-dev Multi-Arch: foreign (LP: #1238796). - * Build-depend on python3:any/python3-all:any rather than - python3/python3-all. - - [ Brian Murray, Colin Watson ] - * Add chroot management support. - - -- Colin Watson Thu, 21 Nov 2013 14:46:16 +0000 - -click (0.4.11) saucy; urgency=low - - * Drop --force-missing-framework from PackageKit plugin now that - /usr/share/click/frameworks/ubuntu-sdk-13.10.framework is in - ubuntu-sdk-libs. - * Show a neater error message when a package's framework is not installed - (LP: #1236671). - * Show a neater error message when building a package whose manifest file - cannot be parsed (LP: #1236669). - * Show a neater error message when running "click install" with - insufficient privileges (LP: #1236673). - - -- Colin Watson Fri, 11 Oct 2013 12:07:06 +0100 - -click (0.4.10) saucy; urgency=low - - * When removing packages, only drop privileges after ensuring the - existence of the database directory (LP: #1233280). - - -- Colin Watson Mon, 30 Sep 2013 18:12:14 +0100 - -click (0.4.9) saucy; urgency=low - - * Explicitly build-depend on pkg-config, since it's needed even if the - PackageKit/GLib-related build-dependencies are removed for backporting. - * Remove some stray documentation references to Ubuntu 13.04. - * Ensure that the user's overlay database directory exists when - unregistering a preinstalled package (LP: #1232066). - * Support packages containing code for multiple architectures, and - document the "architecture" manifest field (LP: #1214380, #1214864). - * Correctly pass through return values of commands as the exit status of - the "click" wrapper. - * Extend "click info" to take a registered package name as an alternative - to a path to a Click package file (LP: #1232118). - * Force unpacked files to be owner-writeable (LP: #1232128). - - -- Colin Watson Mon, 30 Sep 2013 15:24:49 +0100 - -click (0.4.8) saucy; urgency=low - - * Show a proper error message if "click build" or "click buildsource" is - called on a directory that does not exist or does not contain a manifest - file, rather than crashing (LP: #1228619). - * Restore missing newlines after JSON dumps in "click info" and "click - list --manifest". - * Tidy up use of PackageKit IDs; local:click should refer to uninstalled - packages, while installed:click refers to installed packages. - * Expose application names and whether a package is removable via the - PackageKit API: the IDs of installed applications are now formed as - comma-separated key/value pairs, e.g. - "installed:click,removable=1,app_name=foo,app_name=bar" (LP: #1209329). - * Rename ClickUser.__setitem__ to ClickUser.set_version and - ClickUser.__delitem__ to ClickUser.remove; with multiple databases it - was impossible for these methods to fulfil the normal contract for - mutable mappings, since deleting an item might simply expose an item in - an underlying database. - * Allow unregistering preinstalled packages. A preinstalled package - cannot in general actually be removed from disk, but unregistering it - for a user records it as being hidden from that user's list of - registered packages. Reinstalling the same version unhides it. - * Consolidate ClickInstaller.audit_control into ClickInstaller.audit. - * Validate the shipped md5sums file in "click verify" (LP: #1217333). - - -- Colin Watson Tue, 24 Sep 2013 15:21:48 +0100 - -click (0.4.7) saucy; urgency=low - - * Run system hooks when removing a package from the file system - (LP: #1227681). - * If a hook symlink is already correct, don't unnecessarily remove and - recreate it. - * Improve "click hook install-system" and "click hook install-user" to - remove any stale symlinks they find, and to run Exec commands only once - per hook. This significantly speeds up system and session startup when - lots of applications are installed (LP: #1227604). - * Rename "click hook install-system" and "click hook install-user" to - "click hook run-system" and "click hook run-user" respectively, to - better fit their semantics. (I expect these commands only to have been - used internally by click's own Upstart jobs.) - * Filter version control metadata and editor backup files out of binary - packages in "click build" (LP: #1223640). - - -- Colin Watson Fri, 20 Sep 2013 18:07:13 +0100 - -click (0.4.6) saucy; urgency=low - - * Make sure all unpacked files and directories are group- and - world-readable, and (if owner-executable) also group- and - world-executable (LP: #1226553). - - -- Colin Watson Tue, 17 Sep 2013 13:37:06 +0100 - -click (0.4.5) saucy; urgency=low - - * Document --force-missing-framework option in the error message produced - when a package's required framework is not present. - * Make "click pkgdir" exit 1 if a directory for the given package name or - path is not found, rather than letting the exception propagate - (LP: #1225923). - * Run system hooks at boot time, in particular so that AppArmor profiles - for packages in /custom are generated and loaded (LP: #1223085). - - -- Colin Watson Mon, 16 Sep 2013 20:55:28 +0100 - -click (0.4.4) saucy; urgency=low - - * Amend "click help install" to recommend using "pkcon install-local". - * Run hooks when removing a per-user package registration. - * Adjust usage lines for "click help verify" and "click help pkgdir" to - indicate that options are allowed. - * Add a click(1) manual page. - * Use json.dump and json.load in most places rather than json.dumps and - json.loads (which unnecessarily construct strings). - * Add "click unregister", which unregisters a package for a user and - removes it from disk if it is not being used. - * Add RemovePackage support to the PackageKit plugin, mapped to "click - unregister". - * Attempt to remove the old version of a package after installing or - registering a new one. - * Remove code supporting PackageKit 0.7 API, and instead arrange to - disable the PackageKit plugin if the new API is not available, since we - don't need to build it on Ubuntu 12.04 LTS. - * Report errors from click subprocesses in PackageKit plugin - (LP: #1218483). - * Implement PackageKit search by name and by details. - * Reserve manifest keys starting with an underscore for use as dynamic - properties of installed packages. - * Add the dynamic key "_directory" to "click list --manifest" output, - showing the directory where each package is unpacked (LP: #1221760). - * Add the dynamic key "_removable" to "click list --manifest" output, - which is 1 if a package is unpacked in a location from which it can be - removed, otherwise 0. - - -- Colin Watson Mon, 09 Sep 2013 13:37:39 +0100 - -click (0.4.3) saucy; urgency=low - - * Add support for multiple installation root directories, configured in - /etc/click/databases/. Define /usr/share/click/preinstalled, - /custom/click, and /opt/click.ubuntu.com by default. - * Add --all-users option to "click install" and "click register": this - registers the installed package for a special pseudo-user "@all", making - it visible to all users. - * Add "click hook install-user", which runs all user-level hooks for all - packages for a given user. This is useful at session startup to catch - up with packages that may have been preinstalled and registered for all - users. - * Run "click hook install-user" on session startup from an Upstart user - job. - * Avoid calling "click desktophook" if - /usr/share/click/hooks/upstart-app-launch-desktop.hook exists. - * Force umask to a sane value when dropping privileges (022 for clickpkg, - current-umask | 002 for other users; LP: #1215480). - * Use aa-exec-click rather than aa-exec in .desktop files generated by - "click desktophook" (LP: #1197047). - - -- Colin Watson Wed, 04 Sep 2013 17:01:58 +0100 - -click (0.4.2) saucy; urgency=low - - * Suppress dpkg calls to lchown when not running as root (LP: #1220125). - - -- Colin Watson Tue, 03 Sep 2013 10:12:29 +0100 - -click (0.4.1) saucy; urgency=low - - [ Sergio Schvezov ] - * Compare mtimes for desktop files, not stat objects. - - -- Colin Watson Mon, 02 Sep 2013 14:54:49 +0100 - -click (0.4.0) saucy; urgency=low - - [ Colin Watson ] - * Add "installed-size" as a mandatory field in the control area's - "manifest" file; it should not be present in source manifest files, and - is generated automatically by "click build". - * Add an optional "icon" manifest key. - * Consistently call clickpreload_init from preloaded functions in case - they happen to be called before libclickpreload's constructor. - * Run dpkg with --force-bad-path so that /sbin and /usr/sbin are not - required to be on $PATH; we don't use the tools dpkg gets from there. - - [ Loïc Minier ] - * Add fopen64 wrapper (LP: #1218674). - - -- Colin Watson Fri, 30 Aug 2013 17:59:34 +0100 - -click (0.3.4) saucy; urgency=low - - * Make "click desktophook" tolerate dangling symlinks in - ~/.local/share/applications/. - - -- Colin Watson Wed, 28 Aug 2013 18:00:55 +0200 - -click (0.3.3) saucy; urgency=low - - * Recommend click-apparmor from click (suggested by Jamie Strandboge). - - -- Colin Watson Wed, 28 Aug 2013 12:17:23 +0200 - -click (0.3.2) saucy; urgency=low - - [ Jamie Strandboge ] - * Document maintainer as an optional field. - - [ Matthias Klumpp ] - * Support PackageKit 0.8 API. - - -- Colin Watson Tue, 27 Aug 2013 21:07:02 +0200 - -click (0.3.1) saucy; urgency=low - - [ Colin Watson ] - * Fix some more failures with mock 0.7.2. - * Work around the lack of a python-apt backport of - apt_pkg.TagFile(sequence, bytes=True) to precise. - - [ Jamie Strandboge ] - * Codify allowed characters for "application ID". - * Fix typos in apparmor hook example. - - -- Colin Watson Tue, 13 Aug 2013 10:10:11 +0200 - -click (0.3.0) saucy; urgency=low - - * Insert a new "_click-binary" ar member immediately after - "debian-binary"; this allows detecting the MIME type of a Click package - even when it doesn't have the extension ".click" (LP: #1205346). - * Declare the application/x-click MIME type, since the shared-mime-info - upstream would rather not take the patch there at this point - (https://bugs.freedesktop.org/show_bug.cgi?id=66689). - * Make removal of old links for single-version hooks work even when the - application ID is not a prefix of the pattern's basename. - * Add an optional Hook-Name field to hook files, thereby allowing multiple - hooks to attach to the same virtual name. - * Rename click's own "desktop" hook to "click-desktop", making use of the - new Hook-Name facility. - - -- Colin Watson Tue, 06 Aug 2013 11:08:46 +0100 - -click (0.2.10) saucy; urgency=low - - * Force click's stdout encoding to UTF-8 regardless of the locale. - * Don't encode non-ASCII characters in JSON dumps. - * Treat manifests as UTF-8. - - -- Colin Watson Tue, 30 Jul 2013 15:14:16 +0100 - -click (0.2.9) saucy; urgency=low - - * Tolerate dangling source symlinks in "click desktophook". - * Handle the case where the clickpkg user cannot read the .click file, - using some LD_PRELOAD trickery to allow passing it as a file descriptor - opened by the privileged process (LP: #1204523). - * Remove old links for single-version hooks when installing new versions - (LP: #1206115). - - -- Colin Watson Mon, 29 Jul 2013 16:56:42 +0100 - -click (0.2.8) saucy; urgency=low - - * Check in advance whether the root is writable by the clickpkg user, not - just by root, and do so in a way less vulnerable to useful exception - text being eaten by a subprocess preexec_fn (LP: #1204570). - * Actually install - /var/lib/polkit-1/localauthority/10-vendor.d/com.ubuntu.click.pkla in - the packagekit-plugin-click binary package. - - -- Colin Watson Thu, 25 Jul 2013 17:40:49 +0100 - -click (0.2.7) saucy; urgency=low - - * Fix error message when rejecting "_" from a package name or version - (LP: #1204560). - - -- Colin Watson Wed, 24 Jul 2013 16:42:59 +0100 - -click (0.2.6) saucy; urgency=low - - * Adjust written .desktop files to avoid tickling some bugs in Unity 8's - parsing. - - -- Colin Watson Wed, 24 Jul 2013 08:03:08 +0100 - -click (0.2.5) saucy; urgency=low - - * Ensure that ~/.local/share/applications exists if we need to write any - .desktop files. - - -- Colin Watson Wed, 24 Jul 2013 07:44:44 +0100 - -click (0.2.4) saucy; urgency=low - - * Mangle Icon in .desktop files to point to an absolute path within the - package unpack directory if necessary. - * Add a "--" separator between aa-exec's options and the subsidiary - command, per Jamie Strandboge. - - -- Colin Watson Tue, 23 Jul 2013 23:38:29 +0100 - -click (0.2.3) saucy; urgency=low - - * Set Path in generated .desktop files to the top-level package directory. - * Revert part of geteuid() change in 0.2.2; ClickUser._drop_privileges and - ClickUser._regain_privileges need to check the real UID, or else they - will never regain privileges. - * When running a hook, set HOME to the home directory of the user the hook - is running as. - - -- Colin Watson Tue, 23 Jul 2013 22:57:03 +0100 - -click (0.2.2) saucy; urgency=low - - * dh_click: Support --name option. - * Avoid ClickUser.__iter__ infecting its caller with dropped privileges. - * Use geteuid() rather than getuid() in several places to check whether we - need to drop or regain privileges. - * Add a user-level hook to create .desktop files in - ~/.local/share/applications/. (This should probably move to some other - package at some point.) - - -- Colin Watson Tue, 23 Jul 2013 19:36:44 +0100 - -click (0.2.1) saucy; urgency=low - - * Fix "click help list". - * Remove HOME from environment when running dpkg, so that it doesn't try - to read .dpkg.cfg from it (which may fail when dropping privileges from - root and produce a warning message). - * Refuse to install .click directories at any level, not just the top. - * Add "click pkgdir" command to print the top-level package directory from - either a package name or a path within a package; based on work by Ted - Gould, for which thanks. - - -- Colin Watson Mon, 22 Jul 2013 09:36:19 +0100 - -click (0.2.0) saucy; urgency=low - - * Revise and implement hooks specification. While many things have - changed, the previous version was never fully implemented. However, I - have incremented the default Click-Version value to 0.2 to reflect the - design work. - - The "hooks" manifest key now contains a dictionary keyed by - application name. This means manifest authors have to repeat - themselves much less in common cases. - - There is now an explicit distinction between system-level and - user-level hooks. System-level hooks may reflect multiple concurrent - versions, and require a user name. - - Hook symlinks are now named by a combination of the Click package - name, the application name, and the Click package version. - - The syntax of Pattern has changed to make it easier to extend with new - substitutions. - * Reject '_' and '/' characters in all of package name, application name, - and package version. - - -- Colin Watson Fri, 19 Jul 2013 13:11:31 +0100 - -click (0.1.7) saucy; urgency=low - - * Correct name of .pkla file (now - /var/lib/polkit-1/localauthority/10-vendor.d/com.ubuntu.click.pkla). - - -- Colin Watson Thu, 18 Jul 2013 17:00:46 +0100 - -click (0.1.6) saucy; urgency=low - - * Move defaults for frameworks and hooks directories to click.paths. - * Install /var/lib/polkit-1/localauthority/10-vendor.d/10-click.pkla to - allow the phablet user to install Click packages without being known to - logind, as a temporary workaround. - - -- Colin Watson Thu, 18 Jul 2013 16:55:08 +0100 - -click (0.1.5) saucy; urgency=low - - * Fix infinite recursion in ClickUser.click_pw. - * When all the files requested for installation are Click packages, - override org.freedesktop.packagekit.package-install* PolicyKit actions - to com.ubuntu.click.package-install, defined with a more open default - policy. (This requires some backports to PackageKit, not in the archive - yet.) - - -- Colin Watson Wed, 17 Jul 2013 15:46:48 +0100 - -click (0.1.4) saucy; urgency=low - - * Add support for per-user package registration. - * Move install log file from $root/.click.log to $root/.click/log. - * Add an autotools-based build system for our C components. - * Initial version of a PackageKit plugin, in a new packagekit-plugin-click - package; still experimental. - * Restore compatibility with Python 3.2 (LP: #1200670). - * Adjust tests to pass with mock 0.7.2 (as in Ubuntu 12.04 LTS). - * Make the default root directory a configure option. - * Add a simple "click list" command. - - -- Colin Watson Mon, 15 Jul 2013 15:55:48 +0100 - -click (0.1.3) saucy; urgency=low - - * Rename to click, per Mark Shuttleworth. - - -- Colin Watson Thu, 27 Jun 2013 15:57:25 +0100 - -click-package (0.1.2) saucy; urgency=low - - * Disable dh_sphinxdoc for builds that are not building - architecture-independent packages. - - -- Colin Watson Tue, 25 Jun 2013 18:57:47 +0100 - -click-package (0.1.1) saucy; urgency=low - - * clickpackage.tests.test_install: Set NO_PKG_MANGLE when building fake - packages, to avoid having Maintainer fields mangled on the buildds. - - -- Colin Watson Tue, 25 Jun 2013 17:32:00 +0100 - -click-package (0.1) saucy; urgency=low - - * Initial release. - - -- Colin Watson Mon, 24 Jun 2013 14:43:21 +0100 + -- Armin Ronacher Thu, 29 Mar 2018 11:01:50 +0200 diff -Nru click-0.4.43+16.04.20160203/debian/click.click-desktop.click-hook click-6.7/debian/click.click-desktop.click-hook --- click-0.4.43+16.04.20160203/debian/click.click-desktop.click-hook 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/debian/click.click-desktop.click-hook 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ -User-Level: yes -Pattern: ${home}/.local/share/click/hooks/desktop/${id}.desktop -Exec: [ -e /usr/share/click/hooks/ubuntu-app-launch-desktop.hook ] || [ -e /usr/share/click/hooks/upstart-app-launch-desktop.hook ] || click desktophook -Hook-Name: desktop diff -Nru click-0.4.43+16.04.20160203/debian/click-dev.install click-6.7/debian/click-dev.install --- click-0.4.43+16.04.20160203/debian/click-dev.install 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/debian/click-dev.install 1970-01-01 00:00:00.000000000 +0000 @@ -1,5 +0,0 @@ -etc/schroot/click -usr/bin/dh_click -usr/share/debhelper -usr/share/man/man1/dh_click.1 -usr/share/perl5 diff -Nru click-0.4.43+16.04.20160203/debian/click.dirs click-6.7/debian/click.dirs --- click-0.4.43+16.04.20160203/debian/click.dirs 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/debian/click.dirs 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -usr/share/click/frameworks diff -Nru click-0.4.43+16.04.20160203/debian/click-doc.docs click-6.7/debian/click-doc.docs --- click-0.4.43+16.04.20160203/debian/click-doc.docs 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/debian/click-doc.docs 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -doc/_build/html diff -Nru click-0.4.43+16.04.20160203/debian/click.install click-6.7/debian/click.install --- click-0.4.43+16.04.20160203/debian/click.install 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/debian/click.install 1970-01-01 00:00:00.000000000 +0000 @@ -1,7 +0,0 @@ -etc/click -etc/init -lib/systemd -usr/bin/click -usr/lib/*/click -usr/lib/systemd -usr/share/upstart diff -Nru click-0.4.43+16.04.20160203/debian/click.lintian-overrides click-6.7/debian/click.lintian-overrides --- click-0.4.43+16.04.20160203/debian/click.lintian-overrides 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/debian/click.lintian-overrides 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -click: description-starts-with-package-name diff -Nru click-0.4.43+16.04.20160203/debian/click.manpages click-6.7/debian/click.manpages --- click-0.4.43+16.04.20160203/debian/click.manpages 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/debian/click.manpages 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -doc/_build/man/click.1 diff -Nru click-0.4.43+16.04.20160203/debian/click.postinst click-6.7/debian/click.postinst --- click-0.4.43+16.04.20160203/debian/click.postinst 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/debian/click.postinst 1970-01-01 00:00:00.000000000 +0000 @@ -1,23 +0,0 @@ -#! /bin/sh -set -e - -if [ "$1" = configure ]; then - if ! getent passwd clickpkg >/dev/null; then - adduser --quiet --system --home /nonexistent --no-create-home \ - --disabled-password --shell /bin/false --group \ - clickpkg - fi - - mkdir -p -m 755 /opt/click.ubuntu.com - chown clickpkg:clickpkg /opt/click.ubuntu.com - - # dh-systemd has no support yet for user systemd units - # so we need to do this manually here - if which systemctl >/dev/null 2>&1; then - systemctl --global enable click-user-hooks.service || true - fi -fi - -#DEBHELPER# - -exit 0 diff -Nru click-0.4.43+16.04.20160203/debian/click.postrm click-6.7/debian/click.postrm --- click-0.4.43+16.04.20160203/debian/click.postrm 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/debian/click.postrm 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -#! /bin/sh -set -e - -if [ "$1" = purge ]; then - deluser --quiet --system clickpkg >/dev/null || true -fi - -#DEBHELPER# - -exit 0 diff -Nru click-0.4.43+16.04.20160203/debian/click.sharedmimeinfo click-6.7/debian/click.sharedmimeinfo --- click-0.4.43+16.04.20160203/debian/click.sharedmimeinfo 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/debian/click.sharedmimeinfo 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ - - - - Click package - - - - - - - - - - - - diff -Nru click-0.4.43+16.04.20160203/debian/compat click-6.7/debian/compat --- click-0.4.43+16.04.20160203/debian/compat 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/debian/compat 2018-03-29 09:01:50.000000000 +0000 @@ -1 +1 @@ -9 +7 diff -Nru click-0.4.43+16.04.20160203/debian/control click-6.7/debian/control --- click-0.4.43+16.04.20160203/debian/control 2016-02-12 11:09:23.000000000 +0000 +++ click-6.7/debian/control 2018-03-29 09:01:50.000000000 +0000 @@ -1,110 +1,17 @@ Source: click -Section: admin +Maintainer: Armin Ronacher +Section: python Priority: optional -Maintainer: Colin Watson -Standards-Version: 3.9.5 -Build-Depends: debhelper (>= 9~), dh-autoreconf, intltool, python3:any (>= 3.2), python3-all:any, python3-setuptools, python3-apt, python3-debian, python3-gi, python3:any (>= 3.3) | python3-mock, pep8, python3-pep8, pyflakes, python3-sphinx, pkg-config, valac, gobject-introspection (>= 0.6.7), libgirepository1.0-dev (>= 0.6.7), libglib2.0-dev (>= 2.34), gir1.2-glib-2.0, libjson-glib-dev (>= 0.10), libgee-0.8-dev, libpackagekit-glib2-dev (>= 0.7.2), python3-coverage, python3-six, dh-systemd (>= 1.3) -Vcs-Bzr: https://code.launchpad.net/~click-hackers/click/devel -Vcs-Browser: http://bazaar.launchpad.net/~click-hackers/click/devel/files -X-Python-Version: >= 2.7 -X-Python3-Version: >= 3.2 -XS-Testsuite: autopkgtest - -Package: click -Architecture: any -Pre-Depends: ${misc:Pre-Depends} -Depends: ${shlibs:Depends}, ${misc:Depends}, ${python3:Depends}, python3-click-package (= ${binary:Version}), adduser -Recommends: click-apparmor -Suggests: click-reviewers-tools (>= 0.9), ubuntu-app-launch-tools | upstart-app-launch-tools -Conflicts: click-package -Replaces: click-package -Provides: click-package -Description: Click packages - Click is a simplified packaging format that installs in a separate part of - the file system, suitable for third-party applications. - . - This package provides common files, including the main click program. - -Package: click-dev -Architecture: any -Multi-Arch: foreign -Depends: ${misc:Depends}, ${perl:Depends}, python3-click-package (= ${binary:Version}) -Recommends: debootstrap, schroot, dpkg-dev -Description: build Click packages - Click is a simplified packaging format that installs in a separate part of - the file system, suitable for third-party applications. - . - click-dev provides support for building these packages. +Build-Depends: python-setuptools (>= 0.6b3), python-all (>= 2.6.6-3), debhelper (>= 7) +Standards-Version: 3.9.1 -Package: python3-click-package -Section: python -Architecture: any -Depends: ${misc:Depends}, ${python3:Depends}, gir1.2-click-0.4 (= ${binary:Version}), gir1.2-glib-2.0, python3-apt, python3-debian, python3-gi -Conflicts: python3-click, python3-click-cli -Replaces: python3-click (<< 0.4.43) -Description: Click packages (Python 3 interface) - Click is a simplified packaging format that installs in a separate part of - the file system, suitable for third-party applications. - . - This package provides Python 3 modules used by click, which may also be - used directly. - -Package: libclick-0.4-0 -Section: libs -Architecture: any -Multi-Arch: same -Pre-Depends: ${misc:Pre-Depends} -Depends: ${shlibs:Depends}, ${misc:Depends} -Description: run-time Click package management library - Click is a simplified packaging format that installs in a separate part of - the file system, suitable for third-party applications. - . - This package provides a shared library for managing Click packages. - -Package: libclick-0.4-dev -Section: libdevel -Architecture: any -Multi-Arch: same -Pre-Depends: ${misc:Pre-Depends} -Depends: ${shlibs:Depends}, ${misc:Depends}, libclick-0.4-0 (= ${binary:Version}), libglib2.0-dev, libjson-glib-dev -Description: development files for Click package management library - Click is a simplified packaging format that installs in a separate part of - the file system, suitable for third-party applications. - . - This package provides development files needed to build programs for - managing Click packages. - -Package: gir1.2-click-0.4 -Section: introspection -Architecture: any -Depends: ${misc:Depends}, ${gir:Depends}, libclick-0.4-0 (= ${binary:Version}) -Description: GIR bindings for Click package management library - Click is a simplified packaging format that installs in a separate part of - the file system, suitable for third-party applications. - . - This package can be used by other packages using the GIRepository format to - generate dynamic bindings. -Package: click-doc -Section: doc + +Package: python-click Architecture: all -Depends: ${misc:Depends}, ${sphinxdoc:Depends} -Conflicts: click-package-doc -Replaces: click-package-doc -Provides: click-package-doc -Description: Click packages (documentation) - Click is a simplified packaging format that installs in a separate part of - the file system, suitable for third-party applications. - . - This package provides documentation for click. - -Package: packagekit-plugin-click -Architecture: any -Pre-Depends: ${misc:Pre-Depends} -Depends: ${shlibs:Depends}, ${misc:Depends}, click (= ${binary:Version}) -Description: Click packages (PackageKit plugin) - Click is a simplified packaging format that installs in a separate part of - the file system, suitable for third-party applications. - . - This package provides a PackageKit plugin adding support for Click - packages. +Depends: ${misc:Depends}, ${python:Depends} +Description: A simple wrapper around optparse for powerful command line u + + + + diff -Nru click-0.4.43+16.04.20160203/debian/copyright click-6.7/debian/copyright --- click-0.4.43+16.04.20160203/debian/copyright 2016-02-03 11:17:46.000000000 +0000 +++ click-6.7/debian/copyright 1970-01-01 00:00:00.000000000 +0000 @@ -1,82 +0,0 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: click -Upstream-Contact: -Source: https://launchpad.net/click - -Files: * -Copyright: 2013, Canonical Ltd. -License: GPL-3 - -Files: click/test/helpers.py -Copyright: 2013, Canonical Ltd. - 2007-2012 Michael Foord. -License: Python - -Files: pk-plugin/* -Copyright: 2010-2013, Matthias Klumpp - 2011, Richard Hughes - 2013, Canonical Ltd. -License: GPL-3 - -License: GPL-3 - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; version 3 of the License. - . - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - . - You should have received a copy of the GNU General Public License - along with this program. If not, see . - . - A copy of the GNU General Public License version 3 is available in - /usr/share/common-licenses/GPL-3. - -License: Python - 1. This LICENSE AGREEMENT is between the Python Software Foundation - ("PSF"), and the Individual or Organization ("Licensee") accessing and - otherwise using this software ("Python") in source or binary form and - its associated documentation. - . - 2. Subject to the terms and conditions of this License Agreement, PSF - hereby grants Licensee a nonexclusive, royalty-free, world-wide - license to reproduce, analyze, test, perform and/or display publicly, - prepare derivative works, distribute, and otherwise use Python - alone or in any derivative version, provided, however, that PSF's - License Agreement and PSF's notice of copyright, i.e., "Copyright (c) - 2001, 2002, 2003, 2004, 2005, 2006, 2007 Python Software Foundation; - All Rights Reserved" are retained in Python alone or in any derivative - version prepared by Licensee. - . - 3. In the event Licensee prepares a derivative work that is based on - or incorporates Python or any part thereof, and wants to make - the derivative work available to others as provided herein, then - Licensee hereby agrees to include in any such work a brief summary of - the changes made to Python. - . - 4. PSF is making Python available to Licensee on an "AS IS" - basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR - IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND - DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS - FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT - INFRINGE ANY THIRD PARTY RIGHTS. - . - 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON - FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS - A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, - OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - . - 6. This License Agreement will automatically terminate upon a material - breach of its terms and conditions. - . - 7. Nothing in this License Agreement shall be deemed to create any - relationship of agency, partnership, or joint venture between PSF and - Licensee. This License Agreement does not grant permission to use PSF - trademarks or trade name in a trademark sense to endorse or promote - products or services of Licensee, or any third party. - . - 8. By copying, installing or otherwise using Python, Licensee - agrees to be bound by the terms and conditions of this License - Agreement. diff -Nru click-0.4.43+16.04.20160203/debian/gir1.2-click-0.4.install click-6.7/debian/gir1.2-click-0.4.install --- click-0.4.43+16.04.20160203/debian/gir1.2-click-0.4.install 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/debian/gir1.2-click-0.4.install 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -usr/lib/*/girepository-1.0 usr/lib diff -Nru click-0.4.43+16.04.20160203/debian/libclick-0.4-0.install click-6.7/debian/libclick-0.4-0.install --- click-0.4.43+16.04.20160203/debian/libclick-0.4-0.install 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/debian/libclick-0.4-0.install 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -usr/lib/*/libclick*.so.* diff -Nru click-0.4.43+16.04.20160203/debian/libclick-0.4-0.symbols click-6.7/debian/libclick-0.4-0.symbols --- click-0.4.43+16.04.20160203/debian/libclick-0.4-0.symbols 2016-02-03 11:17:46.000000000 +0000 +++ click-6.7/debian/libclick-0.4-0.symbols 1970-01-01 00:00:00.000000000 +0000 @@ -1,110 +0,0 @@ -libclick-0.4.so.0 libclick-0.4-0 #MINVER# -* Build-Depends-Package: libclick-0.4-dev - click_database_error_quark@Base 0.4.17 - click_db_add@Base 0.4.17 - click_db_ensure_ownership@Base 0.4.17 - click_db_gc@Base 0.4.17 - click_db_get@Base 0.4.17 - click_db_get_manifest@Base 0.4.18 - click_db_get_manifest_as_string@Base 0.4.21 - click_db_get_manifests@Base 0.4.18 - click_db_get_manifests_as_string@Base 0.4.21 - click_db_get_overlay@Base 0.4.17 - click_db_get_packages@Base 0.4.17 - click_db_get_path@Base 0.4.17 - click_db_get_size@Base 0.4.17 - click_db_get_type@Base 0.4.17 - click_db_has_package_version@Base 0.4.18 - click_db_maybe_remove@Base 0.4.17 - click_db_new@Base 0.4.17 - click_db_read@Base 0.4.17 - click_dir_get_type@Base 0.4.17 - click_dir_open@Base 0.4.17 - click_dir_read_name@Base 0.4.17 - click_ensuredir@Base 0.4.17 - click_find_on_path@Base 0.4.17 - click_find_package_directory@Base 0.4.17 - click_framework_error_quark@Base 0.4.18 - click_framework_get_base_name@Base 0.4.18 - click_framework_get_base_version@Base 0.4.18 - click_framework_get_fields@Base 0.4.18 - click_framework_get_field@Base 0.4.18 - click_framework_get_frameworks@Base 0.4.18 - click_framework_get_name@Base 0.4.18 - click_framework_get_type@Base 0.4.18 - click_framework_has_framework@Base 0.4.18 - click_framework_open@Base 0.4.18 - click_get_db_dir@Base 0.4.17 - click_get_frameworks_dir@Base 0.4.18 - click_get_hooks_dir@Base 0.4.17 - click_get_umask@Base 0.4.17 - click_hook_get_app_id@Base 0.4.17 - click_hook_get_field@Base 0.4.17 - click_hook_get_fields@Base 0.4.17 - click_hook_get_hook_name@Base 0.4.17 - click_hook_get_is_single_version@Base 0.4.17 - click_hook_get_is_user_level@Base 0.4.17 - click_hook_get_pattern@Base 0.4.17 - click_hook_get_run_commands_user@Base 0.4.17 - click_hook_get_short_app_id@Base 0.4.17 - click_hook_get_type@Base 0.4.17 - click_hook_install@Base 0.4.17 - click_hook_install_package@Base 0.4.17 - click_hook_open@Base 0.4.17 - click_hook_open_all@Base 0.4.17 - click_hook_remove@Base 0.4.17 - click_hook_remove_package@Base 0.4.17 - click_hook_run_commands@Base 0.4.17 - click_hook_sync@Base 0.4.17 - click_hooks_error_quark@Base 0.4.17 - click_installed_package_get_package@Base 0.4.17 - click_installed_package_get_path@Base 0.4.17 - click_installed_package_get_type@Base 0.4.17 - click_installed_package_get_version@Base 0.4.17 - click_installed_package_get_writeable@Base 0.4.17 - click_installed_package_new@Base 0.4.17 - click_package_install_hooks@Base 0.4.17 - click_package_remove_hooks@Base 0.4.17 - click_pattern_format@Base 0.4.17 - click_pattern_possible_expansion@Base 0.4.17 - click_query_error_quark@Base 0.4.17 - click_run_system_hooks@Base 0.4.17 - click_run_user_hooks@Base 0.4.17 - click_single_db_any_app_running@Base 0.4.17 - click_single_db_app_running@Base 0.4.17 - click_single_db_ensure_ownership@Base 0.4.17 - click_single_db_gc@Base 0.4.17 - click_single_db_get_manifest@Base 0.4.18 - click_single_db_get_manifest_as_string@Base 0.4.21 - click_single_db_get_packages@Base 0.4.17 - click_single_db_get_path@Base 0.4.17 - click_single_db_get_root@Base 0.4.17 - click_single_db_get_type@Base 0.4.17 - click_single_db_has_package_version@Base 0.4.18 - click_single_db_maybe_remove@Base 0.4.17 - click_single_db_new@Base 0.4.17 - click_symlink_force@Base 0.4.17 - click_unlink_force@Base 0.4.17 - click_user_error_quark@Base 0.4.17 - click_user_get_is_gc_in_use@Base 0.4.17 - click_user_get_is_pseudo_user@Base 0.4.17 - click_user_get_manifest@Base 0.4.18 - click_user_get_manifest_as_string@Base 0.4.21 - click_user_get_manifests@Base 0.4.18 - click_user_get_manifests_as_string@Base 0.4.21 - click_user_get_overlay_db@Base 0.4.17 - click_user_get_package_names@Base 0.4.17 - click_user_get_path@Base 0.4.17 - click_user_get_type@Base 0.4.17 - click_user_get_version@Base 0.4.17 - click_user_has_package_name@Base 0.4.17 - click_user_is_removable@Base 0.4.17 - click_user_new_for_all_users@Base 0.4.17 - click_user_new_for_gc_in_use@Base 0.4.17 - click_user_new_for_user@Base 0.4.17 - click_user_remove@Base 0.4.17 - click_user_set_version@Base 0.4.17 - click_users_get_type@Base 0.4.17 - click_users_get_user@Base 0.4.17 - click_users_get_user_names@Base 0.4.17 - click_users_new@Base 0.4.17 diff -Nru click-0.4.43+16.04.20160203/debian/libclick-0.4-dev.install click-6.7/debian/libclick-0.4-dev.install --- click-0.4.43+16.04.20160203/debian/libclick-0.4-dev.install 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/debian/libclick-0.4-dev.install 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ -usr/include -usr/lib/*/libclick*.so -usr/lib/*/pkgconfig/click-*.pc -usr/share/gir-1.0 diff -Nru click-0.4.43+16.04.20160203/debian/packagekit-check click-6.7/debian/packagekit-check --- click-0.4.43+16.04.20160203/debian/packagekit-check 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/debian/packagekit-check 1970-01-01 00:00:00.000000000 +0000 @@ -1,9 +0,0 @@ -#! /bin/sh -set -e - -packagekit_version="$(dpkg-query -f '${Version}\n' -W libpackagekit-glib2-dev || echo 0)" -if dpkg --compare-versions "$packagekit_version" ge 0.8.10; then - echo yes -else - echo no -fi diff -Nru click-0.4.43+16.04.20160203/debian/packagekit-plugin-click.docs click-6.7/debian/packagekit-plugin-click.docs --- click-0.4.43+16.04.20160203/debian/packagekit-plugin-click.docs 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/debian/packagekit-plugin-click.docs 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -pk-plugin/README diff -Nru click-0.4.43+16.04.20160203/debian/packagekit-plugin-click.install click-6.7/debian/packagekit-plugin-click.install --- click-0.4.43+16.04.20160203/debian/packagekit-plugin-click.install 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/debian/packagekit-plugin-click.install 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -usr/lib/*/packagekit-plugins/*.so -usr/share/polkit-1/actions -var/lib/polkit-1/localauthority/10-vendor.d diff -Nru click-0.4.43+16.04.20160203/debian/python3-click-package.install click-6.7/debian/python3-click-package.install --- click-0.4.43+16.04.20160203/debian/python3-click-package.install 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/debian/python3-click-package.install 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -usr/lib/python3 diff -Nru click-0.4.43+16.04.20160203/debian/rules click-6.7/debian/rules --- click-0.4.43+16.04.20160203/debian/rules 2016-02-03 11:17:46.000000000 +0000 +++ click-6.7/debian/rules 2018-03-29 09:01:50.000000000 +0000 @@ -1,62 +1,31 @@ -#! /usr/bin/make -f +#!/usr/bin/make -f -PACKAGEKIT := $(shell sh debian/packagekit-check) -ifeq ($(PACKAGEKIT),yes) -EXTRA_DH_OPTIONS := -else -EXTRA_DH_OPTIONS := -Npackagekit-plugin-click -endif - -# The advantages of -Wl,-Bsymbolic-functions are of limited value here, and -# they mean that the test suite's LD_PRELOAD tricks don't work properly. -export DEB_LDFLAGS_MAINT_STRIP := -Wl,-Bsymbolic-functions +# This file was automatically generated by stdeb 0.8.5 at +# Thu, 29 Mar 2018 11:01:50 +0200 %: - dh $@ --with autoreconf,gir,python3,sphinxdoc,systemd \ - --buildsystem autoconf $(EXTRA_DH_OPTIONS) + dh $@ --with python2 --buildsystem=python_distutils -PY3REQUESTED := $(shell py3versions -r) -PY3DEFAULT := $(shell py3versions -d) -# Run setup.py with the default python3 last so that the scripts use -# #!/usr/bin/python3 and not #!/usr/bin/python3.X. -PY3 := $(filter-out $(PY3DEFAULT),$(PY3REQUESTED)) python3 - -confflags := \ - --with-python-interpreters='$(PY3)' \ - --with-systemdsystemunitdir=/lib/systemd/system \ - --with-systemduserunitdir=/usr/lib/systemd/user -ifeq ($(PACKAGEKIT),no) -confflags += --disable-packagekit -endif -override_dh_autoreconf: - dh_autoreconf -- ./autogen.sh +override_dh_auto_clean: + python setup.py clean -a + find . -name \*.pyc -exec rm {} \; + -override_dh_auto_configure: - dh_auto_configure -- $(confflags) override_dh_auto_build: - dh_auto_build - $(MAKE) -C doc html man + python setup.py build --force -override_dh_auto_clean: - dh_auto_clean - $(MAKE) -C doc clean -PYTHON_INSTALL_FLAGS := \ - --force --root=$(CURDIR)/debian/tmp \ - --no-compile -O0 --install-layout=deb override_dh_auto_install: - dh_auto_install -- PYTHON_INSTALL_FLAGS='$(PYTHON_INSTALL_FLAGS)' - rm -f debian/tmp/usr/lib/*/click/*.la + python setup.py install --force --root=debian/python-click --no-compile -O0 --install-layout=deb --prefix=/usr + + + +override_dh_python2: + dh_python2 --no-guessing-versions + -override_dh_install: - dh_install -X .la --fail-missing - DH_AUTOSCRIPTDIR=debhelper debhelper/dh_click --name=click-desktop -# Sphinx documentation is architecture-independent. -override_dh_sphinxdoc-arch: -override_dh_makeshlibs: - dh_makeshlibs -- -c4 diff -Nru click-0.4.43+16.04.20160203/debian/source/options click-6.7/debian/source/options --- click-0.4.43+16.04.20160203/debian/source/options 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/debian/source/options 2018-03-29 09:01:50.000000000 +0000 @@ -0,0 +1 @@ +extend-diff-ignore="\.egg-info$" \ No newline at end of file diff -Nru click-0.4.43+16.04.20160203/debian/tests/control click-6.7/debian/tests/control --- click-0.4.43+16.04.20160203/debian/tests/control 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/debian/tests/control 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -Tests: run-tests.sh -Depends: @, @builddeps@, iputils-ping, click-dev, schroot, debootstrap, sudo, bzr, debsigs, debsig-verify, python3-six -Restrictions: needs-root allow-stderr diff -Nru click-0.4.43+16.04.20160203/debian/tests/run-tests.sh click-6.7/debian/tests/run-tests.sh --- click-0.4.43+16.04.20160203/debian/tests/run-tests.sh 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/debian/tests/run-tests.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,12 +0,0 @@ -#!/bin/sh - -set -e - -# some files like config.py are generated from config.py.in -./autogen.sh -./configure --prefix=/usr \ - --sysconfdir=/etc \ - --with-systemdsystemunitdir=/lib/systemd/system \ - --with-systemduserunitdir=/usr/lib/systemd/user - -TEST_INTEGRATION=1 python3 -m unittest discover -vv click.tests.integration diff -Nru click-0.4.43+16.04.20160203/doc/conf.py click-6.7/doc/conf.py --- click-0.4.43+16.04.20160203/doc/conf.py 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/doc/conf.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,270 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Click Packages documentation build configuration file, created by -# sphinx-quickstart on Mon Apr 15 11:34:57 2013. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -from datetime import datetime -import io -import os -import re -import sys - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ----------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'Click Packages' -copyright = u'2013, Canonical Ltd.' - -changelog_header = re.compile(r'\w[-+0-9a-z.]* \(([^\(\) \t]+)\)') -changelog_trailer = re.compile( - r'^ \-\- .* <.*> ?((\w+\,\s*)?\d{1,2}\s+\w+\s+\d{4}\s+\d{1,2}:\d\d:\d\d)' - r'\s+[-+]\d{4}(\s+\([^\\\(\)]\))?\s*$') - -with io.open('../debian/changelog', encoding="UTF-8") as changelog: - line = changelog.readline() - match = changelog_header.match(line) - if match is None: - raise ValueError( - "Failed to parse first line of debian/changelog: '%s'" % line) - click_version = match.group(1) - for line in changelog: - match = changelog_trailer.match(line) - if match is not None: - click_datetime = datetime.strptime( - match.group(1), "%a, %d %b %Y %H:%M:%S") - break - elif changelog_header.match(line) is not None: - break - else: - raise ValueError( - "Failed to find trailer line in first entry of debian/changelog") - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = ".".join(click_version.split(".", 2)[:2]) -# The full version, including alpha/beta/rc tags. -release = click_version - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -today = click_datetime.strftime("%Y-%m-%d") -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'ClickPackagesdoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'ClickPackages.tex', u'Click Packages Documentation', - u'Colin Watson', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('manpage', 'click', u'package management tool for Ubuntu Touch', - [u'Colin Watson'], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------------ - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'Click', u'Click Packages Documentation', - u'Colin Watson', 'Click', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' diff -Nru click-0.4.43+16.04.20160203/doc/constraints.rst click-6.7/doc/constraints.rst --- click-0.4.43+16.04.20160203/doc/constraints.rst 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/doc/constraints.rst 1970-01-01 00:00:00.000000000 +0000 @@ -1,31 +0,0 @@ -================== -Design constraints -================== - -Building packages -================= - - * Building packages should not require any more than the Python standard - library. In particular, it should not require dpkg, python-debian, or any - other such Debian-specific tools. - - Rationale: We want people to be able to build Click packages easily on any - platform (or at least any platform that can manage a Python installation, - which is not too onerous a requirement). - - -Installing packages -=================== - - * For the purpose of rapid prototyping, package installation is also - implemented in Python. This may of course use Debian/Ubuntu-specific - tools, since it will always be running on an Ubuntu system. In future, it - will probably be re-implemented in C for performance. - - * Reading the system dpkg database is forbidden. This is partly to ensure - strict separation, and partly because the system dpkg database is large and - therefore slow to read. - - * Nothing should require root, although it may be acceptable to make use of - root-only facilities if available (but remembering to pay attention to - performance). diff -Nru click-0.4.43+16.04.20160203/doc/databases.rst click-6.7/doc/databases.rst --- click-0.4.43+16.04.20160203/doc/databases.rst 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/doc/databases.rst 1970-01-01 00:00:00.000000000 +0000 @@ -1,65 +0,0 @@ -========= -Databases -========= - -(This is a lightly-edited copy of a brain-dump sent by Colin Watson to the -ubuntu-phone mailing list, preserved here since it may be useful.) - -Click has multiple databases where packages may be unpacked: by default we -have the "core" database for core apps (``/usr/share/click/preinstalled/``), -the "custom" database for carrier/OEM customisations (``/custom/click/``), -and the "default" database for user-installed applications -(``/opt/click.ubuntu.com/``), although these are configurable in -``/etc/click/databases/``. Each database may have multiple unpacked -versions of any given package. - -Each database may also have user registrations, which live in -``.click/users/`` relative to the database root. Each user has a -subdirectory of that, which contains symlinks to the versions of each -package they have registered. This means that on a tablet, say, I can -install an app without it also showing up on my children's accounts; they'd -need to install it separately, although the disk space for the unpacked copy -of the app would be shared. - -There was an idea early on that we'd deal with preinstalled apps by going -round and registering them all for all active users on first boot. This -would have lots of problems for the packaging system, though. Most notably, -doing it that way makes it hard for a user to remove an app and make it -stick, because it would tend to reappear on system updates. You can -probably fudge your way around this somehow, but it gets very fiddly and -easy to get wrong. - -What we do instead is: we have an ``@all`` pseudo-user which you can -register packages for, typically in the core database (``click register ---root=/usr/share/click/preinstalled --all-users``). If a user wants to -remove a package, we do this by creating a deliberately broken symlink -pointing to ``@hidden`` in their user registration area in -``/opt/click.ubuntu.com/.click/users/$USERNAME/``. When click is asked to -list the set of packages for a given user, it walks its way down the list of -databases from top (default) to bottom (core). For each database, it checks -registrations for that user, followed by registrations for ``@all``. It -takes the first registration for any given package name that it finds. If -that registration is ``@hidden``, then it ignores the package, otherwise it -must be a link to the unpacked copy of the appropriate version of the -package. - -There are still some things that can't be done just with static files in the -image and instead have to be done at boot time and on session startup: we -have to make sure the right AppArmor profiles are loaded, do things to the -user's home directory like creating .desktop files, and that kind of thing. -We run ``click hook run-system`` at boot time and ``click hook run-user`` on -session startup, and these deal with running hooks for whatever packages are -visible in context, according to the rules above. - -The effect of all this is that we can hide a core app for a carrier by doing -this as root when preparing their custom overlay image:: - - click unregister --root=/custom/click --all-users PACKAGE-NAME - -This will create a symlink ``/custom/click/.click/users/@all/PACKAGE-NAME`` -pointing to ``@hidden``. Unless a user explicitly installs the app in -question, the effect of this will be that it's as if the app just isn't -there. It shouldn't incur any more than a negligible cost at startup -(basically just a readlink call); at the moment I think we might still -create an AppArmor profile for it, which isn't free, but that can be fixed -easily enough. diff -Nru click-0.4.43+16.04.20160203/doc/file-format.rst click-6.7/doc/file-format.rst --- click-0.4.43+16.04.20160203/doc/file-format.rst 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/doc/file-format.rst 1970-01-01 00:00:00.000000000 +0000 @@ -1,188 +0,0 @@ -======================================== -"Click" package file format, version 0.4 -======================================== - -This specification covers a packaging format intended for use by -self-contained third-party applications. It is intentionally designed to -make it easy to create such packages and for the archive of packages to be -able to scale to very large numbers, as well as to ensure that packages do -not execute any unverified code as root during installation and that -installed packages are sandboxable. - -This implementation proposal uses the existing dpkg as its core, although -that is entirely concealed from both users and application developers. The -author believes that using something based on dpkg will allow us to reuse -substantial amounts of package-management-related code elsewhere, not least -the many years of careful design and bug-fixing of dpkg itself; although -there are clearly several things we need to adjust. - -General format -============== - -The top-level binary format for Click packages is an ar archive containing -control and data tar archives, as for .deb packages: see deb(5) for full -details. - -The deb(5) format permits the insertion of underscore-prefixed ar members, -so a "_click-binary" member should be inserted immediately after -"debian-binary"; its contents should be the current version number of this -specification followed by a newline. This makes it possible to assign a -MIME type to Click packages without having to rely solely on their -extension. - -Despite the similar format, the file extension for these packages is .click, -to discourage attempts to install using dpkg directly (although it is still -possible to use dpkg to inspect these files). Click packages should not be -thought of as .deb packages, although they share tooling. Do not rely on -the file extension remaining .click; it may change in the future. - -Control area -============ - -control -------- - -Every Click package must include the following control fields: - - * Click-Version: the current version number of this specification - -The package manager must refuse to process packages where any of these -fields are missing or unparseable. It must refuse to process packages where -Click-Version compares newer than the corresponding version it implements -(according to rules equivalent to "dpkg --compare-versions"). It may refuse -to process packages whose Click-Version field has an older major number than -the version it implements (although future developers are encouraged to -maintain the maximum possible degree of compatibility with packages in the -wild). - -Several other fields are copied from the manifest, to ease interoperation -with Debian package manipulation tools. The manifest is the primary -location for these fields, and Click-aware tools must not rely on their -presence in the control file. - -All dependency relations are forbidden. Packages implicitly depend on the -entire contents of the Click system framework they declare. - -manifest --------- - -There must be a "manifest" file in the control area (typically corresponding -to "manifest.json" in source trees), which must be a dictionary represented -as UTF-8-encoded JSON. It must include the following keys: - - * name: unique name for the application - - * version: version number of the application - - * framework: the system framework(s) for which the package was built - - * installed-size: the size of the unpacked package in KiB; this should not - be set directly in the source tree, but will be generated automatically - by "click build" using "du -k -s --apparent-size" - -The package manager must refuse to process packages where any of these -fields are missing or unparseable. It must refuse to process packages where -the value of "framework" does not declare a framework implemented by the -system on which the package is being installed. - -The value of "name" identifies the application, following Debian source -package name rules; every package in the app store has a unique "name" -identifier, and the app store will reject clashes. It is the developer's -responsibility to choose a unique identifier. The recommended approach is -to follow the Java package name convention, i.e. "com.mydomain.myapp", -starting with the reverse of an Internet domain name owned by the person or -organisation developing the application; note that it is not necessary for -the application to contain any Java code in order to use this convention. - -The value of "version" provides a unique version for the application, -following Debian version numbering rules. See deb-version(5) for full -details. - -The syntax of "framework" is formally that of a Debian dependency -relationship field. Currently, only a simple name is permitted, e.g. -"framework": "ubuntu-sdk-13.10", or a list of simple names all of which must -be satisfied, e.g. "framework": "ubuntu-sdk-14.04-qml, -ubuntu-sdk-14.04-webapps"; version relationships and alternative -dependencies are not currently allowed. - -The manifest may contain arbitrary additional optional keys; new optional -keys may be defined without changing the version number of this -specification. The following are currently recognised: - - * title: short (one-line) synopsis of the application - - * description: extended description of the application; may be - multi-paragraph - - * maintainer: name and email address of maintainer of the application - - * architecture: one of the following: - - * "all", indicating a package containing no compiled code - - * a dpkg architecture name (e.g. "armhf") as a string, indicating a - package that will only run on that architecture - - * a list of dpkg architecture names, indicating a package that will run - on any of those architectures - - * hooks: see :doc:`hooks` - - * icon: icon to display in interfaces listing click packages; if the name - refers to an existing file when resolved relative to the base directory - of the package, the given file will be used; if not, the algorithm - described in the `Icon Theme Specification - `_ will be - used to locate the icon - -Keys beginning with the two characters "x-" are reserved for local -extensions: this file format will never define such keys to have any -particular meaning. - -Keys beginning with an underscore ("_") are reserved for use as dynamic -properties of installed packages. They must not appear in packages' -manifest files, and attempts to set them there will be ignored. The -following dynamic keys are currently defined: - - * _directory: the directory where a package is unpacked - - * _removable: 1 if a package is unpacked in a location from which it can be - removed, otherwise 0 (this may be changed to a proper boolean in future; - client code should be careful to permit either) - -Maintainer scripts ------------------- - -Maintainer scripts are forbidden, with one exception: see below. (If they -are permitted in future, they will at most be required to consist only of -verified debhelper-generated fragments that can be statically analysed.) -Packages in Click system frameworks are encouraged to provide file triggers -where appropriate (e.g. "interest /usr/share/facility"); these will be -processed as normal for dpkg file triggers. - -The exception to maintainer scripts being forbidden is that a Click package -may contain a preinst script with the effect of causing direct calls to dpkg -to refuse to install it. The package manager must enforce the permitted -text of this script. - - -Data area -========= - -Unlike .debs, each package installs in a self-contained directory, and the -filesystem tarball must be based at the root of that directory. The package -must not assume any particular installation directory: if it needs to know -where it is installed, it should look at argv[0] or similar. - -Within each package installation directory, the ".click" subdirectory will -be used for metadata. This directory must not be present at the top level -of package filesystem tarballs; the package manager should silently filter -it out if present. (Rationale: scanning the filesystem tarball in advance -is likely to impose a performance cost, especially for large packages.) - -The package manager should ensure that all unpacked files and directories -are group- and world-readable, and (if owner-executable) also group- and -world-executable. (Rationale: since packages are unpacked as a dedicated -user not used when running applications, and since packages cannot write to -their own unpack directories, any files that aren't world-readable are -unusable.) diff -Nru click-0.4.43+16.04.20160203/doc/hooks.rst click-6.7/doc/hooks.rst --- click-0.4.43+16.04.20160203/doc/hooks.rst 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/doc/hooks.rst 1970-01-01 00:00:00.000000000 +0000 @@ -1,226 +0,0 @@ -===== -Hooks -===== - -Rationale ---------- - -Of course, any sensible packaging format needs a hook mechanism of some -kind; just unpacking a filesystem tarball isn't going to cut it. But part -of the point of Click packages is to make packages easier to audit by -removing their ability to run code at installation time. How do we resolve -this? For most application packages, the code that needs to be run is to -integrate with some system package; for instance, a package that provides an -icon may need to update icon caches. Thus, the best way to achieve both -these goals at once is to make sure the code for this is always in the -integrated-with package. - -dpkg triggers are useful prior art for this approach. In general they get a -lot of things right. The code to process a trigger runs in the postinst, -which encourages an approach where trigger processing is a subset of full -package configuration and shares code with it. Furthermore, the express -inability to pass any user data through the trigger activation mechanism -itself ensures that triggers must operate in a "catch up" style, ensuring -that whatever data store they manage is up to date with the state of the -parts of the file system they use as input. This naturally results in a -system where the user can install integrating and integrated-with packages -in either order and get the same result, a valuable property which -developers are nevertheless unlikely to test explicitly in every case and -which must therefore be encouraged by design. - -There are two principal problems with dpkg triggers (aside from the point -that not all integrated-with packages use them, which is irrelevant because -they don't support any hypothetical future hook mechanisms either). The -first is that the inability to pass user data through trigger activation -means that there is no way to indicate where an integrating package is -installed, which matters when the hook files it provides cannot be in a -single location under /usr/ but might be under /opt/ or even in per-user -directories. The second is that processing dpkg triggers requires operating -on the system dpkg database, which is large and therefore slow. - -Let us consider an example of the sort that might in future be delivered as -a Click package, and one which is simple but not too simple. Our example -package (com.ubuntu.example) delivers an AppArmor profile and two .desktop -files. These are consumed by apparmor and desktop-integration (TBD) -respectively, and each lists the corresponding directory looking for files -to consume. - -We must assume that in the general case it will be at least inconvenient to -cause the integrated-with packages to look in multiple directories, -especially when the list of possible directories is not fixed, so we need a -way to cause files to exist in those directories. On the other hand, we -cannot unpack directly into those directories, because that takes us back to -using dpkg itself, and is incompatible with system image updates where the -root file system is read-only. What we can do with reasonable safety is -populate symlink farms. - -Specification -------------- - - * Only system packages (i.e. .debs) may declare hooks. Click packages must - be declarative in that they may not include code executed outside - AppArmor confinement, which precludes declaring hooks. - - * "System-level hooks" are those which operate on the full set of installed - package/version combinations. They may run as any (system) user. - (Example: AppArmor profile handling.) - - * "User-level hooks" are those which operate on the set of packages - registered by a given user. They run as that user, and thus would - generally be expected to keep their state in the user's home directory or - some similar user-owned file system location. (Example: desktop file - handling.) - - * System-level and user-level hooks share a namespace. - - * A Click package may contain one or more applications (the common case - will be only one). Each application has a name. - - * An "application ID" is a string unique to each application instance: it - is made up of the Click package name, the application name (must consist - only of characters for a Debian source package name, Debian version and - [A-Z]), and the Click package version joined by underscores, e.g. - ``com.ubuntu.clock_alarm_0.1``. - - * A "short application ID" is a string unique to each application, but not - necessarily to each instance of it: it is made up of the Click package - name and the application name (must consist only of characters for a Debian - source package name, Debian version and [A-Z]) joined by an underscore, - e.g. ``com.ubuntu.clock_alarm``. It is only valid in user-level hooks, - or in system-level hooks with ``Single-Version: yes``. - - * An integrated-with system package may add ``*.hook`` files to - ``/usr/share/click/hooks/``. These are standard Debian-style control - files with the following keys: - - User-Level: yes (optional) - If the ``User-Level`` key is present with the value ``yes``, the hook - is a user-level hook. - - Pattern: (required) - The value of ``Pattern`` is a string containing one or more - substitution placeholders, as follows: - - ``${id}`` - The application ID. - - ``${short-id}`` - The short application ID (user-level or single-version hooks only). - - ``${user}`` - The user name (user-level hooks only). - - ``${home}`` - The user's home directory (user-level hooks only). - - ``$$`` - The character '``$``'. - - At least one ``${id}`` or ``${short-id}`` substitution is required. - For user-level hooks, at least one of ``${user}`` and ``${home}`` must - be present. - - On install, the package manager creates the target path as a symlink to - a path provided by the Click package; on upgrade, it changes the target - path to be a symlink to the path in the new version of the Click - package; on removal, it unlinks the target path. - - The terms "install", "upgrade", and "removal" are taken to refer to the - status of the hook rather than of the package. That is, when upgrading - between two versions of a package, if the old version uses a given hook - but the new version does not, then that is a removal; if the old - version does not use a given hook but the new version does, then that - is an install; if both versions use a given hook, then that is an - upgrade. - - For system-level hooks, one target path exists for each unpacked - version, unless "``Single-Version: yes``" is used (see below). For - user-level hooks, a target path exists only for the current version - registered by each user for each package. - - Upgrades of user-level hooks may leave the symlink pointed at the same - target (since the target will itself be via a ``current`` symlink in - the user registration directory). ``Exec`` commands in hooks should - take care to check the modification timestamp of the target. - - Exec: (optional) - If the ``Exec`` key is present, its value is executed as if passed to - the shell after the above symlink is modified. A non-zero exit status - is an error; hook implementors must be careful to make commands in - ``Exec`` fields robust. Note that this command intentionally takes no - arguments, and will be run on install, upgrade, and removal; it must be - written such that it causes the system to catch up with the current - state of all installed hooks. ``Exec`` commands must be idempotent. - - Trigger: yes (optional) - It will often be valuable to execute a dpkg trigger after installing a - Click package to avoid code duplication between system and Click - package handling, although we must do so asynchronously and any errors - must not block the installation of Click packages. If "``Trigger: - yes``" is set in a ``*.hook`` file, then "``click install``" will - activate an asynchronous D-Bus service at the end of installation, - passing the names of all the changed paths resulting from Pattern key - expansions; this will activate any file triggers matching those paths, - and process all the packages that enter the triggers-pending state as a - result. - - User: (required, system-level hooks only) - System-level hooks are run as the user whose name is specified as the - value of ``User``. There is intentionally no default for this key, to - encourage hook authors to run their hooks with the least appropriate - privilege. - - Single-Version: yes (optional, system-level hooks only) - By default, system-level hooks support multiple versions of packages, - so target paths may exist at multiple versions. "``Single-Version: - yes``" causes only the current version of each package to have a target - path. - - Hook-Name: (optional) - The value of ``Hook-Name`` is the name that Click packages may use to - attach to this hook. By default, this is the base name of the - ``*.hook`` file, with the ``.hook`` extension removed. - - Multiple hooks may use the same hook-name, in which case all those - hooks will be run when installing, upgrading, or removing a Click - package that attaches to that name. - - * A Click package may attach to zero or more hooks, by including a "hooks" - entry in its manifest. If present, this must be a dictionary mapping - application names to hook sets; each hook set is itself a dictionary - mapping hook names to paths. The hook names are used to look up - ``*.hook`` files with matching hook-names (see ``Hook-Name`` above). The - paths are relative to the directory where the Click package is unpacked, - and are used as symlink targets by the package manager when creating - symlinks according to the ``Pattern`` field in ``*.hook`` files. - - * There is a dh_click program which installs the ``*.hook`` files in system - packages and adds maintainer script fragments to cause click to catch up - with any newly-provided hooks. It may be invoked using ``dh $@ --with - click``. - -Examples --------- - -:: - - /usr/share/click/hooks/apparmor.hook: - Pattern: /var/lib/apparmor/clicks/${id}.json - Exec: /usr/bin/aa-clickhook - User: root - - /usr/share/click/hooks/click-desktop.hook: - User-Level: yes - Pattern: /opt/click.ubuntu.com/.click/desktop-files/${user}_${id}.desktop - Exec: click desktophook - Hook-Name: desktop - - com.ubuntu.example/manifest.json: - "hooks": { - "example-app": { - "apparmor": "apparmor/example-app.json", - "desktop": "example-app.desktop" - } - } - -TODO: copy rather than symlink, for additional robustness? diff -Nru click-0.4.43+16.04.20160203/doc/index.rst click-6.7/doc/index.rst --- click-0.4.43+16.04.20160203/doc/index.rst 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/doc/index.rst 1970-01-01 00:00:00.000000000 +0000 @@ -1,164 +0,0 @@ -.. Click Packages documentation master file, created by - sphinx-quickstart on Mon Apr 15 11:34:57 2013. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -============== -Click packages -============== - -*Click* is the code name used to describe a packaging format for Ubuntu mobile -applications. This format specifies how individual apps are delivered to -mobile devices, how they are packed into distributable format, and how they -are installed on a mobile device by a system provided package manager. At a -minimum they assume that a system framework exists providing all the -necessary infrastructure and dependencies needed in order to install and run -such apps. - -The click packaging format is completely independent from facilities to do -full-system installations or upgrades. - - -Compatibility -============= - -Currently, this package should remain compatible with Python 2.7, 3.2, 3.3, -and 3.4; Ubuntu 12.04 LTS, Ubuntu 13.10, and Ubuntu 14.04 LTS. - -Build -===== - -If you run from a fresh bzr checkout, please ensure you have the required -build dependencies first by running:: - - $ dpkg-checkbuilddeps - -and installing anything that is missing here. - -Then run:: - - $ ./autogen.sh - $ ./configure --prefix=/usr \ - --sysconfdir=/etc \ - --with-systemdsystemunitdir=/lib/systemd/system \ - --with-systemduserunitdir=/usr/lib/systemd/user - $ make - -to build the project. - - -Dependencies ------------- - -For Ubuntu 14.04, make sure you have the *python2.7* and *python3.4* -packages installed. Unless you upgraded from a previous version of Ubuntu -and haven't removed it yet, you won't have Python 3.3 and Python 3.2 -available. Build them from source if necessary, install them say into -``/usr/local``, and make sure they are on your ``$PATH``. - -You'll need *tox* (Ubuntu package *python-tox*) installed in order to run the -full test suite. You should be able to just say:: - - $ tox - -to run the full suite. Use tox's ``-e`` option to run the tests against a -subset of Python versions. You shouldn't have to install anything manually -into the virtual environments that tox creates, but you might have to if you -don't have all the dependencies installed in your system Pythons. - -You'll need the *mock* and *python-debian* libraries. For Ubuntu 13.10, -apt-get install the following packages:: - - * python-mock - * python-debian - * python3-debian - - -Testing -======= - -After all of the above is installed, you can run ``tox`` to run the test suite -against all supported Python versions. The ``./run-tests`` scripts just does -an additional check to make sure you've got the preload shared library -built. - -To run a specific testcase, use the standard python unittest syntax like:: - - $ python3 -m unittest click.tests.test_install - -or:: - - $ python2 -m unittest click.tests.test_build.TestClickBuilder.test_build - -Test coverage -------------- - -If you have python-coverage installed, you can get a Python test coverage -report by typing: - - $ python-coverage combine - $ python-coverage report - -This works also for python3-coverage. - -To get Vala/C coverage information, install the gcovr and lcov packages and -run: - - $ ./configure --enable-gcov - $ make coverage-html - -which will generate a "coveragereport/index.html" file for you. - -The combined coverage information can be obtained via: - - $ make coverage.xml - -Integration Tests ------------------ - -There is also a set of integration tests that have additional -test dependencies that are listed in debian/test/control. - -Beware that some require to be run as root and they are designed to be -run in a safe environment (like a schroot or a autopkgtest container) -and may alter the system state (e.g adding test users). By default the -tests will run against the installed click binary, but you can also -use: - - $ LD_LIBRARY_PATH=$(pwd)/lib/click/.libs \ - PYTHONPATH=$(pwd) \ - GI_TYPELIB_PATH=$(pwd)/lib/click \ - CLICK_BINARY=$(pwd)/bin/click \ - TEST_INTEGRATION=1 \ - python3 -m unittest discover click.tests.integration - -to run against the build tree. - - -Documentation -============= - -To build the HTML version of the documentation, you'll need Sphinx (Ubuntu -package *python-sphinx*). Then do:: - - $ (cd doc && make html) - - -Contents: - -.. toctree:: - :maxdepth: 2 - - file-format.rst - constraints.rst - hooks.rst - databases.rst - todo.rst - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff -Nru click-0.4.43+16.04.20160203/doc/Makefile click-6.7/doc/Makefile --- click-0.4.43+16.04.20160203/doc/Makefile 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/doc/Makefile 1970-01-01 00:00:00.000000000 +0000 @@ -1,153 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ClickPackages.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ClickPackages.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/ClickPackages" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ClickPackages" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." diff -Nru click-0.4.43+16.04.20160203/doc/manpage.rst click-6.7/doc/manpage.rst --- click-0.4.43+16.04.20160203/doc/manpage.rst 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/doc/manpage.rst 1970-01-01 00:00:00.000000000 +0000 @@ -1,360 +0,0 @@ -===== -click -===== - -SYNOPSIS -======== - -click command [options] [arguments] - -DESCRIPTION -=========== - -*Click* is a packaging format for Ubuntu Touch applications, independent of -the packaging format used to deliver the underlying system. The *click* -program is the basic tool used to build, install, remove, and otherwise -manipulate these packages. - -*click*'s various functions are available via a number of commands, -described in detail below. - -While *click* supports per-user installation, packages are normally unpacked -as a special ``clickpkg`` user, to ensure that applications cannot modify -their own code; it is a design goal to ensure that *click* can be used to -install untrusted code which is then confined using `AppArmor -`_. As such, *click* should normally be -run as root (e.g. using ``sudo``) when installing packages; it will drop -privileges as needed. - -COMMAND OVERVIEW -================ - -:: - - click build DIRECTORY - click buildsource DIRECTORY - click chroot - click contents PATH - click framework list - click hook install HOOK - click hook remove HOOK - click hook run-system - click hook run-user - click info PATH - click install PACKAGE-FILE - click list - click pkgdir {PACKAGE-NAME|PATH} - click register PACKAGE-NAME VERSION - click unregister PACKAGE-NAME [VERSION] - click verify PACKAGE-FILE - -COMMANDS -======== - -click build DIRECTORY ---------------------- - -Build a Click package from the contents of DIRECTORY. The build directory -must contain a JSON-formatted manifest, described further in Click's -file-format documentation; by default, this is expected to be in -``manifest.json`` at the top level of the build directory. - -The resulting ``.click`` file is written to the current directory, so to -avoid confusion you should generally ensure that your working directory is -not inside the build directory when running this command. - -While it is possible to build a new version of a Click package by unpacking -and repacking an existing package, this is not normally recommended because -it requires some care to put the manifest file back in the right place. It -is best to keep your application's code in separate revision control rather -than relying on recovering it from packages. - -Options: - --m PATH, --manifest=PATH Read package manifest from PATH - (default: ``manifest.json``). --I file-pattern, --ignore=file-pattern Ignore the given shell-pattern - when building the package. - The option may be repeated multiple - times to list multiple patterns to - exclude. ---no-validate Don't run checks from click-reviewers-tools on - the resulting .click file. - -click buildsource DIRECTORY ---------------------------- - -Build a source package in ``.tar.gz`` format from the contents of DIRECTORY. -This allows you to distribute source code in the case where your package -contains compiled code (and so the Click package does not constitute its own -source). - -The resulting ``.tar.gz`` file is written to the current directory, so to -avoid confusion you should generally ensure that your working directory is -not inside the build directory when running this command. - -Options: - --m PATH, --manifest=PATH Read package manifest from PATH - (default: ``manifest.json``). --I file-pattern, --ignore=file-pattern Ignore the given shell-pattern - when building the package. - The option may be repeated multiple - times to list multiple patterns to - exclude. - -click chroot ------------- - -Manage chroot environments for cross-building Click packages. - -Options: - --a ARCH, --architecture ARCH Set the target architecture. --f FRAMEWORK, --framework FRAMEWORK Set the target framework (default: - ubuntu-sdk-13.10). --s SERIES, --series SERIES Set the target series for - newly-created chroots (default: a - series appropriate for the - framework). - This option is mainly for debugging; - use -f instead. - -Subcommands: - -begin-session SESSION - Begin a persistent chroot session. - -create - Create a chroot. - -destroy - Destroy a chroot. - -end-session SESSION - End a persistent chroot session. - -install [-n SESSION] PACKAGES - Install packages in the chroot. - -maint [-n SESSION] COMMAND ARGUMENTS - Run a maintenance command in the chroot. Unlike ``run``, this runs its - command as root inside the chroot, and its effects on the chroot will - persist after ``click chroot maint`` exits. - - If a session name is given, run the command in that session. The - session must previously have been created by ``click chroot - begin-session``. - -run [-n SESSION] COMMAND ARGUMENTS - Run a program in the chroot. - - If a session name is given, run the command in that session. The - session must previously have been created by ``click chroot - begin-session``. - -upgrade [-n SESSION] - Upgrade the chroot. - -click contents PATH -------------------- - -Display the contents of the Click package in PATH as a file listing. - -click framework list --------------------- - -Display a list of available frameworks as one framework per line. - -click hook install HOOK ------------------------ - -Install files associated with HOOK for any Click packages that attach to it. - -This is normally only called by maintainer scripts of system packages, by -way of dh_click(1). - -Options: - ---root=PATH Look for additional packages in PATH. - -click hook remove HOOK ----------------------- - -Remove files associated with HOOK for any Click packages that attach to it. - -This is normally only called by maintainer scripts of system packages, by -way of dh_click(1). - -Options: - ---root=PATH Look for additional packages in PATH. - -click hook run-system -------------------------- - -Run all system-level hooks for all installed Click packages. This is useful -when starting up from images with preinstalled packages which may not have -had their system-level hooks run properly when building the image. - -Options: - ---root=PATH Look for additional packages in PATH. - -click hook run-user ------------------------ - -Run all user-level hooks for all Click packages registered for a given user. -This is useful at session startup to catch up with packages that may have -been preinstalled and registered for all users. - -Options: - ---root=PATH Look for additional packages in PATH. ---user=USER Run user-level hooks for USER (default: current - user). - -click info {PACKAGE-NAME|PACKAGE-FILE} --------------------------------------- - -When given a package name (that is, a string containing no ``/`` -characters), display the manifest for that package, if it is registered for -the current user. - -When given a path (that is, a string containing at least one ``/`` -character, or a string containing no ``/`` characters that is not a -registered package name), attempt to treat that as a path to a file -containing a Click package and display the manifest for that package. - -Options: - ---root=PATH Look for additional packages in PATH. ---user=USER List packages registered by USER (if you have - permission). - -click install PACKAGE-FILE --------------------------- - -Install the Click package in PACKAGE-FILE. - -This is a low-level tool; to install a package as an ordinary user you -should generally use ``pkcon install-local PACKAGE-FILE`` or some -higher-level user interface instead, which take care to use the correct set -of options. (Do not use ``sudo`` when invoking ``pkcon``, as it needs to -know the calling user.) - -``click install`` may be used to preinstall a package in an image such that -it will be available to all users by default. When doing this, you should -normally install it to one of the databases defined in -``/etc/click/databases/`` other than the default of -``/opt/click.ubuntu.com``. For example: - - sudo click install --root=/custom/click --all-users foo.click - -The ``--force-missing-framework`` option is necessary while working with -development versions of SDKs which have not yet put a framework declaration -in place. - -You should always register installed packages either for a specific user or -for all users; if you do not do this then the packages may be -garbage-collected later. You can do this using the ``--user`` or -``--all-users`` options to this command, or using the ``click register`` -command. - -Options: - ---root=PATH Install packages underneath PATH. ---force-missing-framework Install despite missing system framework. ---user=USER Register package for USER. ---all-users Register package for all users. - -click list ----------- - -Display a list of installed packages, either as one package per line with -each line containing a package name and version separated by a tab (the -default), or as a JSON array of manifests. - -By default, ``click list`` shows only packages registered for the current -user. The ``--all`` option causes it to show all installed packages, -regardless of user registrations. - -Options: - ---root=PATH Look for additional packages in PATH. ---all List all installed packages. ---user=USER List packages registered by USER (if you have - permission). ---manifest Format output as a JSON array of manifests. - -click pkgdir {PACKAGE-NAME|PATH} --------------------------------- - -When given a package name (that is, a string containing no ``/`` -characters), display the directory where that package is installed, if it is -registered for the current user. - -When given a path (that is, a string containing at least one ``/`` -character), attempt to treat that as a path to a file within a Click package -and print the top-level directory where that package is installed, if one -exists. This is particularly useful in hooks that need to find the -top-level package directory based on a symbolic link to a single file within -it. - -Exits zero if and only if a directory for the given package name or path was -found. - -Options: - ---root=PATH Look for additional packages in PATH. ---user=USER List packages registered by USER (if you have - permission). - -click register PACKAGE-NAME VERSION ------------------------------------ - -Register an installed Click package for a user. This will normally cause -user-level hooks to be run for that user, which are needed for things such -as making the application's ``.desktop`` file available to the user -interface. - -Options: - ---root=PATH Look for additional packages in PATH. ---user=USER Register package for USER (default: current - user). ---all-users Register package for all users. - -click unregister PACKAGE-NAME [VERSION] ---------------------------------------- - -Unregister an installed Click package for a user, and remove it entirely if -no other users still have it registered and if it does not appear to be -running. This will normally cause user-level hooks to be run for that user, -which are needed for things such as removing the application's ``.desktop`` -file from the user interface. - -If a version is specified, then the registered version must match it in -order to be removed. - -Options: - ---root=PATH Look for additional packages in PATH. ---user=USER Unregister package for USER (default: - ``$SUDO_USER``, if known). ---all-users Unregister package that was previously - registered for all users. - -click verify PACKAGE-FILE -------------------------- - -Verify the Click package in PACKAGE-FILE. - -The ``--force-missing-framework`` option is necessary while working with -development versions of SDKs which have not yet put a framework declaration -in place. - -Options: - ---root=PATH Install packages underneath PATH. ---force-missing-framework Install despite missing system framework. diff -Nru click-0.4.43+16.04.20160203/doc/todo.rst click-6.7/doc/todo.rst --- click-0.4.43+16.04.20160203/doc/todo.rst 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/doc/todo.rst 1970-01-01 00:00:00.000000000 +0000 @@ -1,45 +0,0 @@ -===== -To do -===== - - * hook that gets notified about all installations - - * dbus interface etc. as backend for UI - - * method may not be feasible because caller may want to go away - - * but where do we send a completion/failure signal back to? - - * some way to manage shared data files - - * association with developer ID, to allow sharing of data - - * debug symbols - - * define exit statuses for "click install" - - * command to generate manifest template, like ``dh_make`` - - * check whether a package contains compiled code for an architecture not - listed in the "architecture" manifest field - -Delta updates -============= - -It would be helpful to have some kind of delta update format. - -Tools such as ``rsync`` and ``zsync`` are probably the wrong answer. -There's no particular reason to keep the .click file around as an rsync -target, particularly since the unpacked application directory is kept -pristine, and many devices won't have the kind of disk space where you want -to keep 4.2GB files around just for the sake of it. - -We could do something ad-hoc with ``xdelta`` or ``bsdiff`` or whatever. - -`debdelta `_ seems like a good possibility. -We're already using the .deb format, and debdelta is capable of doing patch -upgrades without having the old .deb around (though it will need minor -adjustments to cope with the different installation location of Click -packages). Under the hood, it uses xdelta/bsdiff/etc. and can be extended -with other backends if need be. If we used this then we could take -advantage of a good deal of existing code. diff -Nru click-0.4.43+16.04.20160203/docs/advanced.rst click-6.7/docs/advanced.rst --- click-0.4.43+16.04.20160203/docs/advanced.rst 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/advanced.rst 2017-01-06 22:32:37.000000000 +0000 @@ -0,0 +1,379 @@ +Advanced Patterns +================= + +.. currentmodule:: click + +In addition to common functionality that is implemented in the library +itself, there are countless patterns that can be implemented by extending +Click. This page should give some insight into what can be accomplished. + +.. _aliases: + +Command Aliases +--------------- + +Many tools support aliases for commands. For instance, you can configure +``git`` to accept ``git ci`` as alias for ``git commit``. Other tools +also support auto-discovery for aliases by automatically shortening them. + +Click does not support this out of the box, but it's very easy to customize +the :class:`Group` or any other :class:`MultiCommand` to provide this +functionality. + +As explained in :ref:`custom-multi-commands`, a multi command can provide +two methods: :meth:`~MultiCommand.list_commands` and +:meth:`~MultiCommand.get_command`. In this particular case, you only need +to override the latter as you generally don't want to enumerate the +aliases on the help page in order to avoid confusion. + +This following example implements a subclass of :class:`Group` that +accepts a prefix for a command. If there were a command called ``push``, +it would accept ``pus`` as an alias (so long as it was unique): + +.. click:example:: + + class AliasedGroup(click.Group): + + def get_command(self, ctx, cmd_name): + rv = click.Group.get_command(self, ctx, cmd_name) + if rv is not None: + return rv + matches = [x for x in self.list_commands(ctx) + if x.startswith(cmd_name)] + if not matches: + return None + elif len(matches) == 1: + return click.Group.get_command(self, ctx, matches[0]) + ctx.fail('Too many matches: %s' % ', '.join(sorted(matches))) + +And it can then be used like this: + +.. click:example:: + + @click.command(cls=AliasedGroup) + def cli(): + pass + + @cli.command() + def push(): + pass + + @cli.command() + def pop(): + pass + +Parameter Modifications +----------------------- + +Parameters (options and arguments) are forwarded to the command callbacks +as you have seen. One common way to prevent a parameter from being passed +to the callback is the `expose_value` argument to a parameter which hides +the parameter entirely. The way this works is that the :class:`Context` +object has a :attr:`~Context.params` attribute which is a dictionary of +all parameters. Whatever is in that dictionary is being passed to the +callbacks. + +This can be used to make up addition parameters. Generally this pattern +is not recommended but in some cases it can be useful. At the very least +it's good to know that the system works this way. + +.. click:example:: + + import urllib + + def open_url(ctx, param, value): + if value is not None: + ctx.params['fp'] = urllib.urlopen(value) + return value + + @click.command() + @click.option('--url', callback=open_url) + def cli(url, fp=None): + if fp is not None: + click.echo('%s: %s' % (url, fp.code)) + +In this case the callback returns the URL unchanged but also passes a +second ``fp`` value to the callback. What's more recommended is to pass +the information in a wrapper however: + +.. click:example:: + + import urllib + + class URL(object): + + def __init__(self, url, fp): + self.url = url + self.fp = fp + + def open_url(ctx, param, value): + if value is not None: + return URL(value, urllib.urlopen(value)) + + @click.command() + @click.option('--url', callback=open_url) + def cli(url): + if url is not None: + click.echo('%s: %s' % (url.url, url.fp.code)) + + +Token Normalization +------------------- + +.. versionadded:: 2.0 + +Starting with Click 2.0, it's possible to provide a function that is used +for normalizing tokens. Tokens are option names, choice values, or command +values. This can be used to implement case insensitive options, for +instance. + +In order to use this feature, the context needs to be passed a function that +performs the normalization of the token. For instance, you could have a +function that converts the token to lowercase: + +.. click:example:: + + CONTEXT_SETTINGS = dict(token_normalize_func=lambda x: x.lower()) + + @click.command(context_settings=CONTEXT_SETTINGS) + @click.option('--name', default='Pete') + def cli(name): + click.echo('Name: %s' % name) + +And how it works on the command line: + +.. click:run:: + + invoke(cli, prog_name='cli', args=['--NAME=Pete']) + +Invoking Other Commands +----------------------- + +Sometimes, it might be interesting to invoke one command from another +command. This is a pattern that is generally discouraged with Click, but +possible nonetheless. For this, you can use the :func:`Context.invoke` +or :func:`Context.forward` methods. + +They work similarly, but the difference is that :func:`Context.invoke` merely +invokes another command with the arguments you provide as a caller, +whereas :func:`Context.forward` fills in the arguments from the current +command. Both accept the command as the first argument and everything else +is passed onwards as you would expect. + +Example: + +.. click:example:: + + cli = click.Group() + + @cli.command() + @click.option('--count', default=1) + def test(count): + click.echo('Count: %d' % count) + + @cli.command() + @click.option('--count', default=1) + @click.pass_context + def dist(ctx, count): + ctx.forward(test) + ctx.invoke(test, count=42) + +And what it looks like: + +.. click:run:: + + invoke(cli, prog_name='cli', args=['dist']) + + +.. _callback-evaluation-order: + +Callback Evaluation Order +------------------------- + +Click works a bit differently than some other command line parsers in that +it attempts to reconcile the order of arguments as defined by the +programmer with the order of arguments as defined by the user before +invoking any callbacks. + +This is an important concept to understand when porting complex +patterns to Click from optparse or other systems. A parameter +callback invocation in optparse happens as part of the parsing step, +whereas a callback invocation in Click happens after the parsing. + +The main difference is that in optparse, callbacks are invoked with the raw +value as it happens, whereas a callback in Click is invoked after the +value has been fully converted. + +Generally, the order of invocation is driven by the order in which the user +provides the arguments to the script; if there is an option called ``--foo`` +and an option called ``--bar`` and the user calls it as ``--bar +--foo``, then the callback for ``bar`` will fire before the one for ``foo``. + +There are three exceptions to this rule which are important to know: + +Eagerness: + An option can be set to be "eager". All eager parameters are + evaluated before all non-eager parameters, but again in the order as + they were provided on the command line by the user. + + This is important for parameters that execute and exit like ``--help`` + and ``--version``. Both are eager parameters, but whatever parameter + comes first on the command line will win and exit the program. + +Repeated parameters: + If an option or argument is split up on the command line into multiple + places because it is repeated -- for instance, ``--exclude foo --include + baz --exclude bar`` -- the callback will fire based on the position of + the first option. In this case, the callback will fire for + ``exclude`` and it will be passed both options (``foo`` and + ``bar``), then the callback for ``include`` will fire with ``baz`` + only. + + Note that even if a parameter does not allow multiple versions, Click + will still accept the position of the first, but it will ignore every + value except the last. The reason for this is to allow composability + through shell aliases that set defaults. + +Missing parameters: + If a parameter is not defined on the command line, the callback will + still fire. This is different from how it works in optparse where + undefined values do not fire the callback. Missing parameters fire + their callbacks at the very end which makes it possible for them to + default to values from a parameter that came before. + +Most of the time you do not need to be concerned about any of this, +but it is important to know how it works for some advanced cases. + +.. _forwarding-unknown-options: + +Forwarding Unknown Options +-------------------------- + +In some situations it is interesting to be able to accept all unknown +options for further manual processing. Click can generally do that as of +Click 4.0, but it has some limitations that lie in the nature of the +problem. The support for this is provided through a parser flag called +``ignore_unknown_options`` which will instruct the parser to collect all +unknown options and to put them to the leftover argument instead of +triggering a parsing error. + +This can generally be activated in two different ways: + +1. It can be enabled on custom :class:`Command` subclasses by changing + the :attr:`~BaseCommand.ignore_unknown_options` attribute. +2. It can be enabled by changing the attribute of the same name on the + context class (:attr:`Context.ignore_unknown_options`). This is best + changed through the ``context_settings`` dictionary on the command. + +For most situations the easiest solution is the second. Once the behavior +is changed something needs to pick up those leftover options (which at +this point are considered arguments). For this again you have two +options: + +1. You can use :func:`pass_context` to get the context passed. This will + only work if in addition to :attr:`~Context.ignore_unknown_options` + you also set :attr:`~Context.allow_extra_args` as otherwise the + command will abort with an error that there are leftover arguments. + If you go with this solution, the extra arguments will be collected in + :attr:`Context.args`. +2. You can attach a :func:`argument` with ``nargs`` set to `-1` which + will eat up all leftover arguments. In this case it's recommeded to + set the `type` to :data:`UNPROCESSED` to avoid any string processing + on those arguments as otherwise they are forced into unicode strings + automatically which is often not what you want. + +In the end you end up with something like this: + +.. click:example:: + + import sys + from subprocess import call + + @click.command(context_settings=dict( + ignore_unknown_options=True, + )) + @click.option('-v', '--verbose', is_flag=True, help='Enables verbose mode') + @click.argument('timeit_args', nargs=-1, type=click.UNPROCESSED) + def cli(verbose, timeit_args): + """A wrapper around Python's timeit.""" + cmdline = ['python', '-mtimeit'] + list(timeit_args) + if verbose: + click.echo('Invoking: %s' % ' '.join(cmdline)) + call(cmdline) + +And what it looks like: + +.. click:run:: + + invoke(cli, prog_name='cli', args=['--help']) + println() + invoke(cli, prog_name='cli', args=['-n', '100', 'a = 1; b = 2; a * b']) + println() + invoke(cli, prog_name='cli', args=['-v', 'a = 1; b = 2; a * b']) + +As you can see the verbosity flag is handled by Click, everything else +ends up in the `timeit_args` variable for further processing which then +for instance, allows invoking a subprocess. There are a few things that +are important to know about how this ignoring of unhandled flag happens: + +* Unknown long options are generally ignored and not processed at all. + So for instance if ``--foo=bar`` or ``--foo bar`` are passed they + generally end up like that. Note that because the parser cannot know + if an option will accept an argument or not, the ``bar`` part might be + handled as an argument. +* Unknown short options might be partially handled and reassmebled if + necessary. For instance in the above example there is an option + called ``-v`` which enables verbose mode. If the command would be + ignored with ``-va`` then the ``-v`` part would be handled by Click + (as it is known) and ``-a`` would end up in the leftover parameters + for further processing. +* Depending on what you plan on doing you might have some success by + disabling interspersed arguments + (:attr:`~Context.allow_interspersed_args`) which instructs the parser + to not allow arguments and options to be mixed. Depending on your + situation this might improve your results. + +Generally though the combinated handling of options and arguments from +your own commands and commands from another application are discouraged +and if you can avoid it, you should. It's a much better idea to have +everything below a subcommand be forwarded to another application than to +handle some arguments yourself. + + +Global Context Access +--------------------- + +.. versionadded:: 5.0 + +Starting with Click 5.0 it is possible to access the current context from +anywhere within the same through through the use of the +:func:`get_current_context` function which returns it. This is primarily +useful for accessing the context bound object as well as some flags that +are stored on it to customize the runtime behavior. For instance the +:func:`echo` function does this to infer the default value of the `color` +flag. + +Example usage:: + + def get_current_command_name(): + return click.get_current_context().info_name + +It should be noted that this only works within the current thread. If you +spawn additional threads then those threads will not have the ability to +refer to the current context. If you want to give another thread the +ability to refer to this context you need to use the context within the +thread as a context manager:: + + def spawn_thread(ctx, func): + def wrapper(): + with ctx: + func() + t = threading.Thread(target=wrapper) + t.start() + return t + +Now the thread function can access the context like the main thread would +do. However if you do use this for threading you need to be very careful +as the vast majority of the context is not thread safe! You are only +allowed to read from the context, but not to perform any modifications on +it. diff -Nru click-0.4.43+16.04.20160203/docs/api.rst click-6.7/docs/api.rst --- click-0.4.43+16.04.20160203/docs/api.rst 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/api.rst 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,181 @@ +API +=== + +.. module:: click + +This part of the documentation lists the full API reference of all public +classes and functions. + +Decorators +---------- + +.. autofunction:: command + +.. autofunction:: group + +.. autofunction:: argument + +.. autofunction:: option + +.. autofunction:: password_option + +.. autofunction:: confirmation_option + +.. autofunction:: version_option + +.. autofunction:: help_option + +.. autofunction:: pass_context + +.. autofunction:: pass_obj + +.. autofunction:: make_pass_decorator + +Utilities +--------- + +.. autofunction:: echo + +.. autofunction:: echo_via_pager + +.. autofunction:: prompt + +.. autofunction:: confirm + +.. autofunction:: progressbar + +.. autofunction:: clear + +.. autofunction:: style + +.. autofunction:: unstyle + +.. autofunction:: secho + +.. autofunction:: edit + +.. autofunction:: launch + +.. autofunction:: getchar + +.. autofunction:: pause + +.. autofunction:: get_terminal_size + +.. autofunction:: get_binary_stream + +.. autofunction:: get_text_stream + +.. autofunction:: open_file + +.. autofunction:: get_app_dir + +.. autofunction:: format_filename + +Commands +-------- + +.. autoclass:: BaseCommand + :members: + +.. autoclass:: Command + :members: + +.. autoclass:: MultiCommand + :members: + +.. autoclass:: Group + :members: + +.. autoclass:: CommandCollection + :members: + +Parameters +---------- + +.. autoclass:: Parameter + :members: + +.. autoclass:: Option + +.. autoclass:: Argument + +Context +------- + +.. autoclass:: Context + :members: + +.. autofunction:: get_current_context + +Types +----- + +.. autodata:: STRING + +.. autodata:: INT + +.. autodata:: FLOAT + +.. autodata:: BOOL + +.. autodata:: UUID + +.. autodata:: UNPROCESSED + +.. autoclass:: File + +.. autoclass:: Path + +.. autoclass:: Choice + +.. autoclass:: IntRange + +.. autoclass:: Tuple + +.. autoclass:: ParamType + :members: + +Exceptions +---------- + +.. autoexception:: ClickException + +.. autoexception:: Abort + +.. autoexception:: UsageError + +.. autoexception:: BadParameter + +.. autoexception:: FileError + +.. autoexception:: NoSuchOption + +.. autoexception:: BadOptionUsage + +.. autoexception:: BadArgumentUsage + +Formatting +---------- + +.. autoclass:: HelpFormatter + :members: + +.. autofunction:: wrap_text + +Parsing +------- + +.. autoclass:: OptionParser + :members: + +Testing +------- + +.. currentmodule:: click.testing + +.. autoclass:: CliRunner + :members: + +.. autoclass:: Result + :members: diff -Nru click-0.4.43+16.04.20160203/docs/arguments.rst click-6.7/docs/arguments.rst --- click-0.4.43+16.04.20160203/docs/arguments.rst 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/arguments.rst 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,245 @@ +.. _arguments: + +Arguments +========= + +.. currentmodule:: click + +Arguments work similarly to :ref:`options ` but are positional. +They also only support a subset of the features of options due to their +syntactical nature. Click will also not attempt to document arguments for +you and wants you to document them manually in order to avoid ugly help +pages. + +Basic Arguments +--------------- + +The most basic option is a simple string argument of one value. If no +type is provided, the type of the default value is used, and if no default +value is provided, the type is assumed to be :data:`STRING`. + +Example: + +.. click:example:: + + @click.command() + @click.argument('filename') + def touch(filename): + click.echo(filename) + +And what it looks like: + +.. click:run:: + + invoke(touch, args=['foo.txt']) + +Variadic Arguments +------------------ + +The second most common version is variadic arguments where a specific (or +unlimited) number of arguments is accepted. This can be controlled with +the ``nargs`` parameter. If it is set to ``-1``, then an unlimited number +of arguments is accepted. + +The value is then passed as a tuple. Note that only one argument can be +set to ``nargs=-1``, as it will eat up all arguments. + +Example: + +.. click:example:: + + @click.command() + @click.argument('src', nargs=-1) + @click.argument('dst', nargs=1) + def copy(src, dst): + for fn in src: + click.echo('move %s to folder %s' % (fn, dst)) + +And what it looks like: + +.. click:run:: + + invoke(copy, args=['foo.txt', 'bar.txt', 'my_folder']) + +Note that this is not how you would write this application. The reason +for this is that in this particular example the arguments are defined as +strings. Filenames, however, are not strings! They might be on certain +operating systems, but not necessarily on all. For better ways to write +this, see the next sections. + +.. admonition:: Note on Non-Empty Variadic Arguments + + If you come from ``argparse``, you might be missing support for setting + ``nargs`` to ``+`` to indicate that at least one argument is required. + + This is supported by setting ``required=True``. However, this should + not be used if you can avoid it as we believe scripts should gracefully + degrade into becoming noops if a variadic argument is empty. The + reason for this is that very often, scripts are invoked with wildcard + inputs from the command line and they should not error out if the + wildcard is empty. + +.. _file-args: + +File Arguments +-------------- + +Since all the examples have already worked with filenames, it makes sense +to explain how to deal with files properly. Command line tools are more +fun if they work with files the Unix way, which is to accept ``-`` as a +special file that refers to stdin/stdout. + +Click supports this through the :class:`click.File` type which +intelligently handles files for you. It also deals with Unicode and bytes +correctly for all versions of Python so your script stays very portable. + +Example: + +.. click:example:: + + @click.command() + @click.argument('input', type=click.File('rb')) + @click.argument('output', type=click.File('wb')) + def inout(input, output): + while True: + chunk = input.read(1024) + if not chunk: + break + output.write(chunk) + +And what it does: + +.. click:run:: + + with isolated_filesystem(): + invoke(inout, args=['-', 'hello.txt'], input=['hello'], + terminate_input=True) + invoke(inout, args=['hello.txt', '-']) + +File Path Arguments +------------------- + +In the previous example, the files were opened immediately. But what if +we just want the filename? The naïve way is to use the default string +argument type. However, remember that Click is Unicode-based, so the string +will always be a Unicode value. Unfortunately, filenames can be Unicode or +bytes depending on which operating system is being used. As such, the type +is insufficient. + +Instead, you should be using the :class:`Path` type, which automatically +handles this ambiguity. Not only will it return either bytes or Unicode +depending on what makes more sense, but it will also be able to do some +basic checks for you such as existence checks. + +Example: + +.. click:example:: + + @click.command() + @click.argument('f', type=click.Path(exists=True)) + def touch(f): + click.echo(click.format_filename(f)) + +And what it does: + +.. click:run:: + + with isolated_filesystem(): + with open('hello.txt', 'w') as f: + f.write('Hello World!\n') + invoke(touch, args=['hello.txt']) + println() + invoke(touch, args=['missing.txt']) + + +File Opening Safety +------------------- + +The :class:`FileType` type has one problem it needs to deal with, and that +is to decide when to open a file. The default behavior is to be +"intelligent" about it. What this means is that it will open stdin/stdout +and files opened for reading immediately. This will give the user direct +feedback when a file cannot be opened, but it will only open files +for writing the first time an IO operation is performed by automatically +wrapping the file in a special wrapper. + +This behavior can be forced by passing ``lazy=True`` or ``lazy=False`` to +the constructor. If the file is opened lazily, it will fail its first IO +operation by raising an :exc:`FileError`. + +Since files opened for writing will typically immediately empty the file, +the lazy mode should only be disabled if the developer is absolutely sure +that this is intended behavior. + +Forcing lazy mode is also very useful to avoid resource handling +confusion. If a file is opened in lazy mode, it will receive a +``close_intelligently`` method that can help figure out if the file +needs closing or not. This is not needed for parameters, but is +necessary for manually prompting with the :func:`prompt` function as you +do not know if a stream like stdout was opened (which was already open +before) or a real file that needs closing. + +Starting with Click 2.0, it is also possible to open files in atomic mode by +passing ``atomic=True``. In atomic mode, all writes go into a separate +file in the same folder, and upon completion, the file will be moved over to +the original location. This is useful if a file regularly read by other +users is modified. + +Environment Variables +--------------------- + +Like options, arguments can also grab values from an environment variable. +Unlike options, however, this is only supported for explicitly named +environment variables. + +Example usage: + +.. click:example:: + + @click.command() + @click.argument('src', envvar='SRC', type=click.File('r')) + def echo(src): + click.echo(src.read()) + +And from the command line: + +.. click:run:: + + with isolated_filesystem(): + with open('hello.txt', 'w') as f: + f.write('Hello World!') + invoke(echo, env={'SRC': 'hello.txt'}) + +In that case, it can also be a list of different environment variables +where the first one is picked. + +Generally, this feature is not recommended because it can cause the user +a lot of confusion. + +Option-Like Arguments +--------------------- + +Sometimes, you want to process arguments that look like options. For +instance, imagine you have a file named ``-foo.txt``. If you pass this as +an argument in this manner, Click will treat it as an option. + +To solve this, Click does what any POSIX style command line script does, +and that is to accept the string ``--`` as a separator for options and +arguments. After the ``--`` marker, all further parameters are accepted as +arguments. + +Example usage: + +.. click:example:: + + @click.command() + @click.argument('files', nargs=-1, type=click.Path()) + def touch(files): + for filename in files: + click.echo(filename) + +And from the command line: + +.. click:run:: + + invoke(touch, ['--', '-foo.txt', 'bar.txt']) diff -Nru click-0.4.43+16.04.20160203/docs/bashcomplete.rst click-6.7/docs/bashcomplete.rst --- click-0.4.43+16.04.20160203/docs/bashcomplete.rst 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/bashcomplete.rst 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,69 @@ +Bash Complete +============= + +.. versionadded:: 2.0 + +As of Click 2.0, there is built-in support for Bash completion for +any Click script. There are certain restrictions on when this completion +is available, but for the most part it should just work. + +Limitations +----------- + +Bash completion is only available if a script has been installed properly, +and not executed through the ``python`` command. For information about +how to do that, see :ref:`setuptools-integration`. Also, Click currently +only supports completion for Bash. + +Currently, Bash completion is an internal feature that is not customizable. +This might be relaxed in future versions. + +What it Completes +----------------- + +Generally, the Bash completion support will complete subcommands and +parameters. Subcommands are always listed whereas parameters only if at +least a dash has been provided. Example:: + + $ repo + clone commit copy delete setuser + $ repo clone - + --deep --help --rev --shallow -r + +Activation +---------- + +In order to activate Bash completion, you need to inform Bash that +completion is available for your script, and how. Any Click application +automatically provides support for that. The general way this works is +through a magic environment variable called ``__COMPLETE``, +where ```` is your application executable name in uppercase +with dashes replaced by underscores. + +If your tool is called ``foo-bar``, then the magic variable is called +``_FOO_BAR_COMPLETE``. By exporting it with the ``source`` value it will +spit out the activation script which can be trivally activated. + +For instance, to enable Bash completion for your ``foo-bar`` script, this +is what you would need to put into your ``.bashrc``:: + + eval "$(_FOO_BAR_COMPLETE=source foo-bar)" + +From this point onwards, your script will have Bash completion enabled. + +Activation Script +----------------- + +The above activation example will always invoke your application on +startup. This might be slowing down the shell activation time +significantly if you have many applications. Alternatively, you could also +ship a file with the contents of that, which is what Git and other systems +are doing. + +This can be easily accomplished:: + + _FOO_BAR_COMPLETE=source foo-bar > foo-bar-complete.sh + +And then you would put this into your bashrc instead:: + + . /path/to/foo-bar-complete.sh diff -Nru click-0.4.43+16.04.20160203/docs/changelog.rst click-6.7/docs/changelog.rst --- click-0.4.43+16.04.20160203/docs/changelog.rst 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/changelog.rst 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,3 @@ +.. currentmodule:: click + +.. include:: ../CHANGES diff -Nru click-0.4.43+16.04.20160203/docs/clickdoctools.py click-6.7/docs/clickdoctools.py --- click-0.4.43+16.04.20160203/docs/clickdoctools.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/clickdoctools.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,275 @@ +import os +import sys +import click +import shutil +import tempfile +import contextlib +import subprocess + +try: + from StringIO import StringIO +except ImportError: + from io import StringIO + +from docutils import nodes +from docutils.statemachine import ViewList + +from sphinx.domains import Domain +from sphinx.util.compat import Directive + + +class EchoingStdin(object): + + def __init__(self, input, output): + self._input = input + self._output = output + + def __getattr__(self, x): + return getattr(self._input, x) + + def _echo(self, rv): + mark = False + if rv.endswith('\xff'): + rv = rv[:-1] + mark = True + self._output.write(rv) + if mark: + self._output.write('^D\n') + return rv + + def read(self, n=-1): + return self._echo(self._input.read(n)) + + def readline(self, n=-1): + return self._echo(self._input.readline(n)) + + def readlines(self): + return [self._echo(x) for x in self._input.readlines()] + + def __iter__(self): + return iter(self._echo(x) for x in self._input) + + +@contextlib.contextmanager +def fake_modules(): + old_call = subprocess.call + def dummy_call(*args, **kwargs): + with tempfile.TemporaryFile('wb+') as f: + kwargs['stdout'] = f + kwargs['stderr'] = f + rv = subprocess.Popen(*args, **kwargs).wait() + f.seek(0) + click.echo(f.read().decode('utf-8', 'replace').rstrip()) + return rv + subprocess.call = dummy_call + try: + yield + finally: + subprocess.call = old_call + + +@contextlib.contextmanager +def isolation(input=None, env=None): + if isinstance(input, unicode): + input = input.encode('utf-8') + input = StringIO(input or '') + output = StringIO() + sys.stdin = EchoingStdin(input, output) + sys.stdin.encoding = 'utf-8' + + def visible_input(prompt=None): + sys.stdout.write(prompt or '') + val = input.readline().rstrip('\r\n') + sys.stdout.write(val + '\n') + sys.stdout.flush() + return val + + def hidden_input(prompt=None): + sys.stdout.write((prompt or '') + '\n') + sys.stdout.flush() + return input.readline().rstrip('\r\n') + + sys.stdout = output + sys.stderr = output + old_visible_prompt_func = click.termui.visible_prompt_func + old_hidden_prompt_func = click.termui.hidden_prompt_func + click.termui.visible_prompt_func = visible_input + click.termui.hidden_prompt_func = hidden_input + + old_env = {} + try: + if env: + for key, value in env.iteritems(): + old_env[key] = os.environ.get(value) + os.environ[key] = value + yield output + finally: + for key, value in old_env.iteritems(): + if value is None: + try: + del os.environ[key] + except Exception: + pass + else: + os.environ[key] = value + sys.stdout = sys.__stdout__ + sys.stderr = sys.__stderr__ + click.termui.visible_prompt_func = old_visible_prompt_func + click.termui.hidden_prompt_func = old_hidden_prompt_func + + +@contextlib.contextmanager +def isolated_filesystem(): + cwd = os.getcwd() + t = tempfile.mkdtemp() + os.chdir(t) + try: + yield + finally: + os.chdir(cwd) + try: + shutil.rmtree(t) + except (OSError, IOError): + pass + + +class ExampleRunner(object): + + def __init__(self): + self.namespace = { + 'click': click, + '__file__': 'dummy.py', + } + + def declare(self, source): + with fake_modules(): + code = compile(source, '', 'exec') + eval(code, self.namespace) + + def run(self, source): + code = compile(source, '', 'exec') + buffer = [] + + def invoke(cmd, args=None, prog_name=None, + input=None, terminate_input=False, env=None, + **extra): + if env: + for key, value in sorted(env.items()): + if ' ' in value: + value = '"%s"' % value + buffer.append('$ export %s=%s' % (key, value)) + args = args or [] + if prog_name is None: + prog_name = cmd.name.replace('_', '-') + buffer.append(('$ %s %s' % ( + prog_name, + ' '.join(('"%s"' % x) if ' ' in x else x for x in args) + )).rstrip()) + if isinstance(input, (tuple, list)): + input = '\n'.join(input) + '\n' + if terminate_input: + input += '\xff' + with isolation(input=input, env=env) as output: + try: + cmd.main(args=args, prog_name=prog_name.split()[-1], + **extra) + except SystemExit: + pass + buffer.extend(output.getvalue().splitlines()) + + def println(text=''): + buffer.append(text) + + eval(code, self.namespace, { + 'invoke': invoke, + 'println': println, + 'isolated_filesystem': isolated_filesystem, + }) + return buffer + + def close(self): + pass + + +def parse_rst(state, content_offset, doc): + node = nodes.section() + # hack around title style bookkeeping + surrounding_title_styles = state.memo.title_styles + surrounding_section_level = state.memo.section_level + state.memo.title_styles = [] + state.memo.section_level = 0 + state.nested_parse(doc, content_offset, node, match_titles=1) + state.memo.title_styles = surrounding_title_styles + state.memo.section_level = surrounding_section_level + return node.children + + +def get_example_runner(document): + runner = getattr(document, 'click_example_runner', None) + if runner is None: + runner = document.click_example_runner = ExampleRunner() + return runner + + +class ExampleDirective(Directive): + has_content = True + required_arguments = 0 + optional_arguments = 0 + final_argument_whitespace = False + + def run(self): + doc = ViewList() + runner = get_example_runner(self.state.document) + try: + runner.declare('\n'.join(self.content)) + except: + runner.close() + raise + doc.append('.. sourcecode:: python', '') + doc.append('', '') + for line in self.content: + doc.append(' ' + line, '') + return parse_rst(self.state, self.content_offset, doc) + + +class RunExampleDirective(Directive): + has_content = True + required_arguments = 0 + optional_arguments = 0 + final_argument_whitespace = False + + def run(self): + doc = ViewList() + runner = get_example_runner(self.state.document) + try: + rv = runner.run('\n'.join(self.content)) + except: + runner.close() + raise + doc.append('.. sourcecode:: text', '') + doc.append('', '') + for line in rv: + doc.append(' ' + line, '') + return parse_rst(self.state, self.content_offset, doc) + + +class ClickDomain(Domain): + name = 'click' + label = 'Click' + directives = { + 'example': ExampleDirective, + 'run': RunExampleDirective, + } + + +def delete_example_runner_state(app, doctree): + runner = getattr(doctree, 'click_example_runner', None) + if runner is not None: + runner.close() + del doctree.click_example_runner + + +def setup(app): + app.add_domain(ClickDomain) + + app.connect('doctree-read', delete_example_runner_state) diff -Nru click-0.4.43+16.04.20160203/docs/commands.rst click-6.7/docs/commands.rst --- click-0.4.43+16.04.20160203/docs/commands.rst 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/commands.rst 2017-01-06 22:32:37.000000000 +0000 @@ -0,0 +1,558 @@ +Commands and Groups +=================== + +.. currentmodule:: click + +The most important feature of Click is the concept of arbitrarily nesting +command line utilities. This is implemented through the :class:`Command` +and :class:`Group` (actually :class:`MultiCommand`). + +Callback Invocation +------------------- + +For a regular command, the callback is executed whenever the command runs. +If the script is the only command, it will always fire (unless a parameter +callback prevents it. This for instance happens if someone passes +``--help`` to the script). + +For groups and multi commands, the situation looks different. In this case, +the callback fires whenever a subcommand fires (unless this behavior is +changed). What this means in practice is that an outer command runs +when an inner command runs: + +.. click:example:: + + @click.group() + @click.option('--debug/--no-debug', default=False) + def cli(debug): + click.echo('Debug mode is %s' % ('on' if debug else 'off')) + + @cli.command() + def sync(): + click.echo('Synching') + +Here is what this looks like: + +.. click:run:: + + invoke(cli, prog_name='tool.py') + println() + invoke(cli, prog_name='tool.py', args=['--debug', 'sync']) + +Passing Parameters +------------------ + +Click strictly separates parameters between commands and subcommands. What this +means is that options and arguments for a specific command have to be specified +*after* the command name itself, but *before* any other command names. + +This behavior is already observable with the predefined ``--help`` option. +Suppose we have a program called ``tool.py``, containing a subcommand called +``sub``. + +- ``tool.py --help`` will return the help for the whole program (listing + subcommands). + +- ``tool.py sub --help`` will return the help for the ``sub`` subcommand. + +- But ``tool.py --help sub`` will treat ``--help`` as an argument for the main + program. Click then invokes the callback for ``--help``, which prints the + help and aborts the program before click can process the subcommand. + +Nested Handling and Contexts +---------------------------- + +As you can see from the earlier example, the basic command group accepts a +debug argument which is passed to its callback, but not to the sync +command itself. The sync command only accepts its own arguments. + +This allows tools to act completely independent of each other, but how +does one command talk to a nested one? The answer to this is the +:class:`Context`. + +Each time a command is invoked, a new context is created and linked with the +parent context. Normally, you can't see these contexts, but they are +there. Contexts are passed to parameter callbacks together with the +value automatically. Commands can also ask for the context to be passed +by marking themselves with the :func:`pass_context` decorator. In that +case, the context is passed as first argument. + +The context can also carry a program specified object that can be +used for the program's purposes. What this means is that you can build a +script like this: + +.. click:example:: + + @click.group() + @click.option('--debug/--no-debug', default=False) + @click.pass_context + def cli(ctx, debug): + ctx.obj['DEBUG'] = debug + + @cli.command() + @click.pass_context + def sync(ctx): + click.echo('Debug is %s' % (ctx.obj['DEBUG'] and 'on' or 'off')) + + if __name__ == '__main__': + cli(obj={}) + +If the object is provided, each context will pass the object onwards to +its children, but at any level a context's object can be overridden. To +reach to a parent, ``context.parent`` can be used. + +In addition to that, instead of passing an object down, nothing stops the +application from modifying global state. For instance, you could just flip +a global ``DEBUG`` variable and be done with it. + +Decorating Commands +------------------- + +As you have seen in the earlier example, a decorator can change how a +command is invoked. What actually happens behind the scenes is that +callbacks are always invoked through the :meth:`Context.invoke` method +which automatically invokes a command correctly (by either passing the +context or not). + +This is very useful when you want to write custom decorators. For +instance, a common pattern would be to configure an object representing +state and then storing it on the context and then to use a custom +decorator to find the most recent object of this sort and pass it as first +argument. + +For instance, the :func:`pass_obj` decorator can be implemented like this: + +.. click:example:: + + from functools import update_wrapper + + def pass_obj(f): + @click.pass_context + def new_func(ctx, *args, **kwargs): + return ctx.invoke(f, ctx.obj, *args, **kwargs) + return update_wrapper(new_func, f) + +The :meth:`Context.invoke` command will automatically invoke the function +in the correct way, so the function will either be called with ``f(ctx, +obj)`` or ``f(obj)`` depending on whether or not it itself is decorated with +:func:`pass_context`. + +This is a very powerful concept that can be used to build very complex +nested applications; see :ref:`complex-guide` for more information. + + +Group Invocation Without Command +-------------------------------- + +By default, a group or multi command is not invoked unless a subcommand is +passed. In fact, not providing a command automatically passes ``--help`` +by default. This behavior can be changed by passing +``invoke_without_command=True`` to a group. In that case, the callback is +always invoked instead of showing the help page. The context object also +includes information about whether or not the invocation would go to a +subcommand. + +Example: + +.. click:example:: + + @click.group(invoke_without_command=True) + @click.pass_context + def cli(ctx): + if ctx.invoked_subcommand is None: + click.echo('I was invoked without subcommand') + else: + click.echo('I am about to invoke %s' % ctx.invoked_subcommand) + + @cli.command() + def sync(): + click.echo('The subcommand') + +And how it works in practice: + +.. click:run:: + + invoke(cli, prog_name='tool', args=[]) + invoke(cli, prog_name='tool', args=['sync']) + +.. _custom-multi-commands: + +Custom Multi Commands +--------------------- + +In addition to using :func:`click.group`, you can also build your own +custom multi commands. This is useful when you want to support commands +being loaded lazily from plugins. + +A custom multi command just needs to implement a list and load method: + +.. click:example:: + + import click + import os + + plugin_folder = os.path.join(os.path.dirname(__file__), 'commands') + + class MyCLI(click.MultiCommand): + + def list_commands(self, ctx): + rv = [] + for filename in os.listdir(plugin_folder): + if filename.endswith('.py'): + rv.append(filename[:-3]) + rv.sort() + return rv + + def get_command(self, ctx, name): + ns = {} + fn = os.path.join(plugin_folder, name + '.py') + with open(fn) as f: + code = compile(f.read(), fn, 'exec') + eval(code, ns, ns) + return ns['cli'] + + cli = MyCLI(help='This tool\'s subcommands are loaded from a ' + 'plugin folder dynamically.') + + if __name__ == '__main__': + cli() + +These custom classes can also be used with decorators: + +.. click:example:: + + @click.command(cls=MyCLI) + def cli(): + pass + +Merging Multi Commands +---------------------- + +In addition to implementing custom multi commands, it can also be +interesting to merge multiple together into one script. While this is +generally not as recommended as it nests one below the other, the merging +approach can be useful in some circumstances for a nicer shell experience. + +The default implementation for such a merging system is the +:class:`CommandCollection` class. It accepts a list of other multi +commands and makes the commands available on the same level. + +Example usage: + +.. click:example:: + + import click + + @click.group() + def cli1(): + pass + + @cli1.command() + def cmd1(): + """Command on cli1""" + + @click.group() + def cli2(): + pass + + @cli2.command() + def cmd2(): + """Command on cli2""" + + cli = click.CommandCollection(sources=[cli1, cli2]) + + if __name__ == '__main__': + cli() + +And what it looks like: + +.. click:run:: + + invoke(cli, prog_name='cli', args=['--help']) + +In case a command exists in more than one source, the first source wins. + + +.. _multi-command-chaining: + +Multi Command Chaining +---------------------- + +.. versionadded:: 3.0 + +Sometimes it is useful to be allowed to invoke more than one subcommand in +one go. For instance if you have installed a setuptools package before +you might be familiar with the ``setup.py sdist bdist_wheel upload`` +command chain which invokes ``dist`` before ``bdist_wheel`` before +``upload``. Starting with Click 3.0 this is very simple to implement. +All you have to do is to pass ``chain=True`` to your multicommand: + +.. click:example:: + + @click.group(chain=True) + def cli(): + pass + + + @cli.command('sdist') + def sdist(): + click.echo('sdist called') + + + @cli.command('bdist_wheel') + def bdist_wheel(): + click.echo('bdist_wheel called') + +Now you can invoke it like this: + +.. click:run:: + + invoke(cli, prog_name='setup.py', args=['sdist', 'bdist_wheel']) + +When using multi command chaining you can only have one command (the last) +use ``nargs=-1`` on an argument. It is also not possible to nest multi +commands below chained multicommands. Other than that there are no +restrictions on how they work. They can accept options and arguments as +normal. + +Another note: the :attr:`Context.invoked_subcommand` attribute is a bit +useless for multi commands as it will give ``'*'`` as value if more than +one command is invoked. This is necessary because the handling of +subcommands happens one after another so the exact subcommands that will +be handled are not yet available when the callback fires. + +.. note:: + + It is currently not possible for chain commands to be nested. This + will be fixed in future versions of Click. + + +Multi Command Pipelines +----------------------- + +.. versionadded:: 3.0 + +A very common usecase of multi command chaining is to have one command +process the result of the previous command. There are various ways in +which this can be facilitated. The most obvious way is to store a value +on the context object and process it from function to function. This +works by decorating a function with :func:`pass_context` after which the +context object is provided and a subcommand can store its data there. + +Another way to accomplish this is to setup pipelines by returning +processing functions. Think of it like this: when a subcommand gets +invoked it processes all of its parameters and comes up with a plan of +how to do its processing. At that point it then returns a processing +function and returns. + +Where do the returned functions go? The chained multicommand can register +a callback with :meth:`MultiCommand.resultcallback` that goes over all +these functions and then invoke them. + +To make this a bit more concrete consider this example: + +.. click:example:: + + @click.group(chain=True, invoke_without_command=True) + @click.option('-i', '--input', type=click.File('r')) + def cli(input): + pass + + @cli.resultcallback() + def process_pipeline(processors, input): + iterator = (x.rstrip('\r\n') for x in input) + for processor in processors: + iterator = processor(iterator) + for item in iterator: + click.echo(item) + + @cli.command('uppercase') + def make_uppercase(): + def processor(iterator): + for line in iterator: + yield line.upper() + return processor + + @cli.command('lowercase') + def make_lowercase(): + def processor(iterator): + for line in iterator: + yield line.lower() + return processor + + @cli.command('strip') + def make_strip(): + def processor(iterator): + for line in iterator: + yield line.strip() + return processor + +That's a lot in one go, so let's go through it step by step. + +1. The first thing is to make a :func:`group` that is chainable. In + addition to that we also instruct Click to invoke even if no + subcommand is defined. If this would not be done, then invoking an + empty pipeline would produce the help page instead of running the + result callbacks. +2. The next thing we do is to register a result callback on our group. + This callback will be invoked with an argument which is the list of + all return values of all subcommands and then the same keyword + parameters as our group itself. This means we can access the input + file easily there without having to use the context object. +3. In this result callback we create an iterator of all the lines in the + input file and then pass this iterator through all the returned + callbacks from all subcommands and finally we print all lines to + stdout. + +After that point we can register as many subcommands as we want and each +subcommand can return a processor function to modify the stream of lines. + +One important thing of note is that Click shuts down the context after +each callback has been run. This means that for instance file types +cannot be accessed in the `processor` functions as the files will already +be closed there. This limitation is unlikely to change because it would +make resource handling much more complicated. For such it's recommended +to not use the file type and manually open the file through +:func:`open_file`. + +For a more complex example that also improves upon handling of the +pipelines have a look at the `imagepipe multi command chaining demo +`__ in +the Click repository. It implements a pipeline based image editing tool +that has a nice internal structure for the pipelines. + + +Overriding Defaults +------------------- + +By default, the default value for a parameter is pulled from the +``default`` flag that is provided when it's defined, but that's not the +only place defaults can be loaded from. The other place is the +:attr:`Context.default_map` (a dictionary) on the context. This allows +defaults to be loaded from a configuration file to override the regular +defaults. + +This is useful if you plug in some commands from another package but +you're not satisfied with the defaults. + +The default map can be nested arbitrarily for each subcommand and +provided when the script is invoked. Alternatively, it can also be +overriden at any point by commands. For instance, a top-level command could +load the defaults from a configuration file. + +Example usage: + +.. click:example:: + + import click + + @click.group() + def cli(): + pass + + @cli.command() + @click.option('--port', default=8000) + def runserver(port): + click.echo('Serving on http://127.0.0.1:%d/' % port) + + if __name__ == '__main__': + cli(default_map={ + 'runserver': { + 'port': 5000 + } + }) + +And in action: + +.. click:run:: + + invoke(cli, prog_name='cli', args=['runserver'], default_map={ + 'runserver': { + 'port': 5000 + } + }) + +Context Defaults +---------------- + +.. versionadded:: 2.0 + +Starting with Click 2.0 you can override defaults for contexts not just +when calling your script, but also in the decorator that declares a +command. For instance given the previous example which defines a custom +``default_map`` this can also be accomplished in the decorator now. + +This example does the same as the previous example: + +.. click:example:: + + import click + + CONTEXT_SETTINGS = dict( + default_map={'runserver': {'port': 5000}} + ) + + @click.group(context_settings=CONTEXT_SETTINGS) + def cli(): + pass + + @cli.command() + @click.option('--port', default=8000) + def runserver(port): + click.echo('Serving on http://127.0.0.1:%d/' % port) + + if __name__ == '__main__': + cli() + +And again the example in action: + +.. click:run:: + + invoke(cli, prog_name='cli', args=['runserver']) + + +Command Return Values +--------------------- + +.. versionadded:: 3.0 + +One of the new introductions in Click 3.0 is the full support for return +values from command callbacks. This enables a whole range of features +that were previously hard to implement. + +In essence any command callback can now return a value. This return value +is bubbled to certain receivers. One usecase for this has already been +show in the example of :ref:`multi-command-chaining` where it has been +demonstrated that chained multi commands can have callbacks that process +all return values. + +When working with command return values in Click, this is what you need to +know: + +- The return value of a command callback is generally returned from the + :meth:`BaseCommand.invoke` method. The exception to this rule has to + do with :class:`Group`\s: + + * In a group the return value is generally the return value of the + subcommand invoked. The only exception to this rule is that the + return value is the return value of the group callback if it's + invoked without arguments and `invoke_without_command` is enabled. + * If a group is set up for chaining then the return value is a list + of all subcommands' results. + * Return values of groups can be processed through a + :attr:`MultiCommand.result_callback`. This is invoked with the + list of all return values in chain mode, or the single return + value in case of non chained commands. + +- The return value is bubbled through from the :meth:`Context.invoke` + and :meth:`Context.forward` methods. This is useful in situations + where you internally want to call into another command. + +- Click does not have any hard requirements for the return values and + does not use them itself. This allows return values to be used for + custom decorators or workflows (like in the multi command chaining + example). + +- When a Click script is invoked as command line application (through + :meth:`BaseCommand.main`) the return value is ignored unless the + `standalone_mode` is disabled in which case it's bubbled through. diff -Nru click-0.4.43+16.04.20160203/docs/complex.rst click-6.7/docs/complex.rst --- click-0.4.43+16.04.20160203/docs/complex.rst 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/complex.rst 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,219 @@ +.. _complex-guide: + +Complex Applications +==================== + +.. currentmodule:: click + +Click is designed to assist with the creation of complex and simple CLI tools +alike. However, the power of its design is the ability to arbitrarily nest +systems together. For instance, if you have ever used Django, you will +have realized that it provides a command line utility, but so does Celery. +When using Celery with Django, there are two tools that need to interact with +each other and be cross-configured. + +In a theoretical world of two separate Click command line utilities, they +could solve this problem by nesting one inside the other. For instance, the +web framework could also load the commands for the message queue framework. + +Basic Concepts +-------------- + +To understand how this works, you need to understand two concepts: contexts +and the calling convention. + +Contexts +```````` + +Whenever a Click command is executed, a :class:`Context` object is created +which holds state for this particular invocation. It remembers parsed +parameters, what command created it, which resources need to be cleaned up +at the end of the function, and so forth. It can also optionally hold an +application-defined object. + +Context objects build a linked list until they hit the top one. Each context +is linked to a parent context. This allows a command to work below +another command and store its own information there without having to be +afraid of altering up the state of the parent command. + +Because the parent data is available, however, it is possible to navigate to +it if needed. + +Most of the time, you do not see the context object, but when writing more +complex applications it comes in handy. This brings us to the next point. + +Calling Convention +`````````````````` + +When a Click command callback is executed, it's passed all the non-hidden +parameters as keyword arguments. Notably absent is the context. However, +a callback can opt into being passed to the context object by marking itself +with :func:`pass_context`. + +So how do you invoke a command callback if you don't know if it should +receive the context or not? The answer is that the context itself +provides a helper function (:meth:`Context.invoke`) which can do this for +you. It accepts the callback as first argument and then invokes the +function correctly. + +Building a Git Clone +-------------------- + +In this example, we want to build a command line tool that resembles a +version control system. Systems like Git usually provide one +over-arching command that already accepts some parameters and +configuration, and then have extra subcommands that do other things. + +The Root Command +```````````````` + +At the top level, we need a group that can hold all our commands. In this +case, we use the basic :func:`click.group` which allows us to register +other Click commands below it. + +For this command, we also want to accept some parameters that configure the +state of our tool: + +.. click:example:: + + import os + import click + + + class Repo(object): + def __init__(self, home=None, debug=False): + self.home = os.path.abspath(home or '.') + self.debug = debug + + + @click.group() + @click.option('--repo-home', envvar='REPO_HOME', default='.repo') + @click.option('--debug/--no-debug', default=False, + envvar='REPO_DEBUG') + @click.pass_context + def cli(ctx, repo_home, debug): + ctx.obj = Repo(repo_home, debug) + + +Let's understand what this does. We create a group command which can +have subcommands. When it is invoked, it will create an instance of a +``Repo`` class. This holds the state for our command line tool. In this +case, it just remembers some parameters, but at this point it could also +start loading configuration files and so on. + +This state object is then remembered by the context as :attr:`~Context.obj`. +This is a special attribute where commands are supposed to remember what +they need to pass on to their children. + +In order for this to work, we need to mark our function with +:func:`pass_context`, because otherwise, the context object would be +entirely hidden from us. + +The First Child Command +``````````````````````` + +Let's add our first child command to it, the clone command: + +.. click:example:: + + @cli.command() + @click.argument('src') + @click.argument('dest', required=False) + def clone(src, dest): + pass + +So now we have a clone command, but how do we get access to the repo? As +you can imagine, one way is to use the :func:`pass_context` function which +again will make our callback also get the context passed on which we +memorized the repo. However, there is a second version of this decorator +called :func:`pass_obj` which will just pass the stored object, (in our case +the repo): + +.. click:example:: + + @cli.command() + @click.argument('src') + @click.argument('dest', required=False) + @click.pass_obj + def clone(repo, src, dest): + pass + +Interleaved Commands +```````````````````` + +While not relevant for the particular program we want to build, there is +also quite good support for interleaving systems. Imagine for instance that +there was a super cool plugin for our version control system that needed a +lot of configuration and wanted to store its own configuration as +:attr:`~Context.obj`. If we would then attach another command below that, +we would all of a sudden get the plugin configuration instead of our repo +object. + +One obvious way to remedy this is to store a reference to the repo in the +plugin, but then a command needs to be aware that it's attached below such a +plugin. + +There is a much better system that can built by taking advantage of the linked +nature of contexts. We know that the plugin context is linked to the context +that created our repo. Because of that, we can start a search for the last +level where the object stored by the context was a repo. + +Built-in support for this is provided by the :func:`make_pass_decorator` +factory, which will create decorators for us that find objects (it +internally calls into :meth:`Context.find_object`). In our case, we +know that we want to find the closest ``Repo`` object, so let's make a +decorator for this: + +.. click:example:: + + pass_repo = click.make_pass_decorator(Repo) + +If we now use ``pass_repo`` instead of ``pass_obj``, we will always get a +repo instead of something else: + +.. click:example:: + + @cli.command() + @click.argument('src') + @click.argument('dest', required=False) + @pass_repo + def clone(repo, src, dest): + pass + +Ensuring Object Creation +```````````````````````` + +The above example only works if there was an outer command that created a +``Repo`` object and stored it in the context. For some more advanced use +cases, this might become a problem. The default behavior of +:func:`make_pass_decorator` is to call :meth:`Context.find_object` +which will find the object. If it can't find the object, it will raise an +error. The alternative behavior is to use :meth:`Context.ensure_object` +which will find the object, and if it cannot find it, will create one and +store it in the innermost context. This behavior can also be enabled for +:func:`make_pass_decorator` by passing ``ensure=True``: + +.. click:example:: + + pass_repo = click.make_pass_decorator(Repo, ensure=True) + +In this case, the innermost context gets an object created if it is +missing. This might replace objects being placed there earlier. In this +case, the command stays executable, even if the outer command does not run. +For this to work, the object type needs to have a constructor that accepts +no arguments. + +As such it runs standalone: + +.. click:example:: + + @click.command() + @pass_repo + def cp(repo): + click.echo(repo) + +As you can see: + +.. click:run:: + + invoke(cp, []) diff -Nru click-0.4.43+16.04.20160203/docs/conf.py click-6.7/docs/conf.py --- click-0.4.43+16.04.20160203/docs/conf.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/conf.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,219 @@ +# -*- coding: utf-8 -*- +# +# click documentation build configuration file, created by +# sphinx-quickstart on Mon Apr 26 19:53:01 2010. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os +import datetime + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.append(os.path.abspath('..')) +sys.path.append(os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', + 'clickdoctools'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'click' +copyright = u'%d, Armin Ronacher' % datetime.datetime.utcnow().year + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '1.0' +# The full version, including alpha/beta/rc tags. +release = '1.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +#html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +html_theme_options = { +} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = ['_themes'] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +html_title = 'click' + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +html_sidebars = { + 'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'], + '**': ['sidebarlogo.html', 'localtoc.html', 'relations.html', + 'sourcelink.html', 'searchbox.html'] +} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'clickdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'click.tex', u'click documentation', + u'Armin Ronacher', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'click', u'click documentation', + [u'Armin Ronacher'], 1) +] + +intersphinx_mapping = { + 'http://docs.python.org/dev': None +} diff -Nru click-0.4.43+16.04.20160203/docs/contrib.rst click-6.7/docs/contrib.rst --- click-0.4.43+16.04.20160203/docs/contrib.rst 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/contrib.rst 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,25 @@ +.. _contrib: + +============= +click-contrib +============= + +As the userbase of Click grows, more and more major feature requests pop up in +Click's bugtracker. As reasonable as it may be for those features to be bundled +with Click instead of being a standalone project, many of those requested +features are either highly experimental or have unproven practical use, while +potentially being a burden to maintain. + +This is why click-contrib_ exists. The GitHub organization is a collection of +possibly experimental third-party packages whose featureset does not belong +into Click, but also a playground for major features that may be added to Click +in the future. It is also meant to coordinate and concentrate effort on writing +third-party extensions for Click, and to ease the effort of searching for such +extensions. In that sense it could be described as a low-maintenance +alternative to extension repositories of other frameworks. + +Please note that the quality and stability of those packages may be different +than what you expect from Click itself. While published under a common +organization, they are still projects separate from Click. + +.. _click-contrib: https://github.com/click-contrib/ diff -Nru click-0.4.43+16.04.20160203/docs/documentation.rst click-6.7/docs/documentation.rst --- click-0.4.43+16.04.20160203/docs/documentation.rst 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/documentation.rst 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,162 @@ +Documenting Scripts +=================== + +.. currentmodule:: click + +Click makes it very easy to document your command line tools. First of +all, it automatically generates help pages for you. While these are +currently not customizable in terms of their layout, all of the text +can be changed. + +Help Texts +---------- + +Commands and options accept help arguments. In the case of commands, the +docstring of the function is automatically used if provided. + +Simple example: + +.. click:example:: + + @click.command() + @click.option('--count', default=1, help='number of greetings') + @click.argument('name') + def hello(count, name): + """This script prints hello NAME COUNT times.""" + for x in range(count): + click.echo('Hello %s!' % name) + +And what it looks like: + +.. click:run:: + + invoke(hello, args=['--help']) + +Arguments cannot be documented this way. This is to follow the general +convention of Unix tools of using arguments for only the most necessary +things and to document them in the introduction text by referring to them +by name. + +Preventing Rewrapping +--------------------- + +The default behavior of Click is to rewrap text based on the width of the +terminal. In some circumstances, this can become a problem. The main issue +is when showing code examples, where newlines are significant. + +Rewrapping can be disabled on a per-paragraph basis by adding a line with +solely the ``\b`` escape marker in it. This line will be removed from the +help text and rewrapping will be disabled. + +Example: + +.. click:example:: + + @click.command() + def cli(): + """First paragraph. + + This is a very long second paragraph and as you + can see wrapped very early in the source text + but will be rewrapped to the terminal width in + the final output. + + \b + This is + a paragraph + without rewrapping. + + And this is a paragraph + that will be rewrapped again. + """ + +And what it looks like: + +.. click:run:: + + invoke(cli, args=['--help']) + + +Meta Variables +-------------- + +Options and parameters accept a ``metavar`` argument that can change the +meta variable in the help page. The default version is the parameter name +in uppercase with underscores, but can be annotated differently if +desired. This can be customized at all levels: + +.. click:example:: + + @click.command(options_metavar='') + @click.option('--count', default=1, help='number of greetings', + metavar='') + @click.argument('name', metavar='') + def hello(count, name): + """This script prints hello times.""" + for x in range(count): + click.echo('Hello %s!' % name) + +Example: + +.. click:run:: + + invoke(hello, args=['--help']) + + +Command Short Help +------------------ + +For commands, a short help snippet is generated. By default, it's the first +sentence of the help message of the command, unless it's too long. This can +also be overridden: + +.. click:example:: + + @click.group() + def cli(): + """A simple command line tool.""" + + @cli.command('init', short_help='init the repo') + def init(): + """Initializes the repository.""" + + @cli.command('delete', short_help='delete the repo') + def delete(): + """Deletes the repository.""" + +And what it looks like: + +.. click:run:: + + invoke(cli, prog_name='repo.py') + + +Help Parameter Customization +---------------------------- + +.. versionadded:: 2.0 + +The help parameter is implemented in Click in a very special manner. +Unlike regular parameters it's automatically added by Click for any +command and it performs automatic conflict resolution. By default it's +called ``--help``, but this can be changed. If a command itself implements +a parameter with the same name, the default help parameter stops accepting +it. There is a context setting that can be used to override the names of +the help parameters called :attr:`~Context.help_option_names`. + +This example changes the default parameters to ``-h`` and ``--help`` +instead of just ``--help``: + +.. click:example:: + + CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help']) + + @click.command(context_settings=CONTEXT_SETTINGS) + def cli(): + pass + +And what it looks like: + +.. click:run:: + + invoke(cli, ['-h']) diff -Nru click-0.4.43+16.04.20160203/docs/exceptions.rst click-6.7/docs/exceptions.rst --- click-0.4.43+16.04.20160203/docs/exceptions.rst 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/exceptions.rst 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,73 @@ +Exception Handling +================== + +.. currentmodule:: click + +Click internally uses exceptions to signal various error conditions that +the user of the application might have caused. Primarily this is things +like incorrect usage. + +Where are Errors Handled? +------------------------- + +Click's main error handling is happening in :meth:`BaseCommand.main`. In +there it handles all subclasses of :exc:`ClickException` as well as the +standard :exc:`EOFError` and :exc:`KeyboardInterrupt` exceptions. The +latter are internally translated into a :exc:`Abort`. + +The logic applied is the following: + +1. If an :exc:`EOFError` or :exc:`KeyboardInterrupt` happens, reraise it + as :exc:`Abort`. +2. If an :exc:`ClickException` is raised, invoke the + :meth:`ClickException.show` method on it to display it and then exit + the program with :attr:`ClickException.exit_code`. +3. If an :exc:`Abort` exception is raised print the string ``Aborted!`` + to standard error and exit the program with exit code ``1``. +4. if it goes through well, exit the program with exit code ``0``. + +What if I don't want that? +-------------------------- + +Generally you always have the option to invoke the :meth:`invoke` method +yourself. For instance if you have a :class:`Command` you can invoke it +manually like this:: + + ctx = command.make_context('command-name', ['args', 'go', 'here']) + with ctx: + result = command.invoke(ctx) + +In this case exceptions will not be handled at all and bubbled up as you +would expect. + +Starting with Click 3.0 you can also use the :meth:`Command.main` method +but disable the standalone mode which will do two things: disable +exception handling and disable the implicit :meth:`sys.exit` at the end. + +So you can do something like this:: + + command.main(['command-name', 'args', 'go', 'here'], + standalone_mode=False) + +Which Exceptions Exist? +----------------------- + +Click has two exception bases: :exc:`ClickException` which is raised for +all exceptions that Click wants to signal to the user and :exc:`Abort` +which is used to instruct Click to abort the execution. + +A :exc:`ClickException` has a :meth:`~ClickException.show` method which +can render an error message to stderr or the given file object. If you +want to use the exception yourself for doing something check the API docs +about what else they provide. + +The following common subclasses exist: + +* :exc:`UsageError` to inform the user that something went wrong. +* :exc:`BadParameter` to inform the user that something went wrong with + a specific parameter. These are often handled internally in Click and + augmented with extra information if possible. For instance if those + are raised from a callback Click will automatically augment it with + the parameter name if possible. +* :exc:`FileError` this is an error that is raised by the + :exc:`FileType` if Click encounters issues opening the file. diff -Nru click-0.4.43+16.04.20160203/docs/index.rst click-6.7/docs/index.rst --- click-0.4.43+16.04.20160203/docs/index.rst 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/index.rst 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,100 @@ +Welcome to the Click Documentation +================================== + +Click is a Python package for creating beautiful command line interfaces +in a composable way with as little code as necessary. It's the "Command +Line Interface Creation Kit". It's highly configurable but comes with +sensible defaults out of the box. + +It aims to make the process of writing command line tools quick and fun +while also preventing any frustration caused by the inability to implement +an intended CLI API. + +Click in three points: + +- arbitrary nesting of commands +- automatic help page generation +- supports lazy loading of subcommands at runtime + +What does it look like? Here is an example of a simple Click program: + +.. click:example:: + + import click + + @click.command() + @click.option('--count', default=1, help='Number of greetings.') + @click.option('--name', prompt='Your name', + help='The person to greet.') + def hello(count, name): + """Simple program that greets NAME for a total of COUNT times.""" + for x in range(count): + click.echo('Hello %s!' % name) + + if __name__ == '__main__': + hello() + +And what it looks like when run: + +.. click:run:: + + invoke(hello, ['--count=3'], prog_name='python hello.py', input='John\n') + +It automatically generates nicely formatted help pages: + +.. click:run:: + + invoke(hello, ['--help'], prog_name='python hello.py') + +You can get the library directly from PyPI:: + + pip install click + +Documentation Contents +---------------------- + +This part of the documentation guides you through all of the library's +usage patterns. + +.. toctree:: + :maxdepth: 2 + + why + quickstart + setuptools + parameters + options + arguments + commands + prompts + documentation + complex + advanced + testing + utils + bashcomplete + exceptions + python3 + wincmd + +API Reference +------------- + +If you are looking for information on a specific function, class, or +method, this part of the documentation is for you. + +.. toctree:: + :maxdepth: 2 + + api + +Miscellaneous Pages +------------------- + +.. toctree:: + :maxdepth: 2 + + contrib + changelog + upgrading + license diff -Nru click-0.4.43+16.04.20160203/docs/license.rst click-6.7/docs/license.rst --- click-0.4.43+16.04.20160203/docs/license.rst 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/license.rst 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,13 @@ +License +======= + +Click is licensed under a three-clause BSD License. It basically means: +do whatever you want with it as long as the copyright in Click sticks +around, the conditions are not modified and the disclaimer is present. +Furthermore, you must not use the names of the authors to promote derivatives +of the software without written consent. + +License Text +------------ + +.. include:: ../LICENSE diff -Nru click-0.4.43+16.04.20160203/docs/make.bat click-6.7/docs/make.bat --- click-0.4.43+16.04.20160203/docs/make.bat 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/make.bat 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,155 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Classy.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Classy.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff -Nru click-0.4.43+16.04.20160203/docs/Makefile click-6.7/docs/Makefile --- click-0.4.43+16.04.20160203/docs/Makefile 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/Makefile 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,130 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Classy.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Classy.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/Classy" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Classy" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ + "run these through (pdf)latex." + +latexpdf: latex + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + make -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff -Nru click-0.4.43+16.04.20160203/docs/options.rst click-6.7/docs/options.rst --- click-0.4.43+16.04.20160203/docs/options.rst 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/options.rst 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,695 @@ +.. _options: + +Options +======= + +.. currentmodule:: click + +Adding options to commands can be accomplished by the :func:`option` +decorator. Since options can come in various different versions, there +are a ton of parameters to configure their behavior. Options in click are +distinct from :ref:`positional arguments `. + +Basic Value Options +------------------- + +The most basic option is a value option. These options accept one +argument which is a value. If no type is provided, the type of the default +value is used. If no default value is provided, the type is assumed to be +:data:`STRING`. By default, the name of the parameter is the first long +option defined; otherwise the first short one is used. + +.. click:example:: + + @click.command() + @click.option('--n', default=1) + def dots(n): + click.echo('.' * n) + +And on the command line: + +.. click:run:: + + invoke(dots, args=['--n=2']) + +In this case the option is of type :data:`INT` because the default value +is an integer. + +Multi Value Options +------------------- + +Sometimes, you have options that take more than one argument. For options, +only a fixed number of arguments is supported. This can be configured by +the ``nargs`` parameter. The values are then stored as a tuple. + +.. click:example:: + + @click.command() + @click.option('--pos', nargs=2, type=float) + def findme(pos): + click.echo('%s / %s' % pos) + +And on the command line: + +.. click:run:: + + invoke(findme, args=['--pos', '2.0', '3.0']) + +.. _tuple-type: + +Tuples as Multi Value Options +----------------------------- + +.. versionadded:: 4.0 + +As you can see that by using `nargs` set to a specific number each item in +the resulting tuple is of the same type. This might not be what you want. +Commonly you might want to use different types for different indexes in +the tuple. For this you can directly specify a tuple as type: + +.. click:example:: + + @click.command() + @click.option('--item', type=(unicode, int)) + def putitem(item): + click.echo('name=%s id=%d' % item) + +And on the command line: + +.. click:run:: + + invoke(putitem, args=['--item', 'peter', '1338']) + +By using a tuple literal as type, `nargs` gets automatically set to the +length of the tuple and the :class:`click.Tuple` type is automatically +used. The above example is thus equivalent to this: + +.. click:example:: + + @click.command() + @click.option('--item', nargs=2, type=click.Tuple([unicode, int])) + def putitem(item): + click.echo('name=%s id=%d' % item) + +Multiple Options +---------------- + +Similarly to ``nargs``, there is also the case of wanting to support a +parameter being provided multiple times to and have all values recorded -- +not just the last one. For instance, ``git commit -m foo -m bar`` would +record two lines for the commit message: ``foo`` and ``bar``. This can be +accomplished with the ``multiple`` flag: + +Example: + +.. click:example:: + + @click.command() + @click.option('--message', '-m', multiple=True) + def commit(message): + click.echo('\n'.join(message)) + +And on the command line: + +.. click:run:: + + invoke(commit, args=['-m', 'foo', '-m', 'bar']) + +Counting +-------- + +In some very rare circumstances, it is interesting to use the repetition +of options to count an integer up. This can be used for verbosity flags, +for instance: + +.. click:example:: + + @click.command() + @click.option('-v', '--verbose', count=True) + def log(verbose): + click.echo('Verbosity: %s' % verbose) + +And on the command line: + +.. click:run:: + + invoke(log, args=['-vvv']) + +Boolean Flags +------------- + +Boolean flags are options that can be enabled or disabled. This can be +accomplished by defining two flags in one go separated by a slash (``/``) +for enabling or disabling the option. (If a slash is in an option string, +Click automatically knows that it's a boolean flag and will pass +``is_flag=True`` implicitly.) Click always wants you to provide an enable +and disable flag so that you can change the default later. + +Example: + +.. click:example:: + + import sys + + @click.command() + @click.option('--shout/--no-shout', default=False) + def info(shout): + rv = sys.platform + if shout: + rv = rv.upper() + '!!!!111' + click.echo(rv) + +And on the command line: + +.. click:run:: + + invoke(info, args=['--shout']) + invoke(info, args=['--no-shout']) + +If you really don't want an off-switch, you can just define one and +manually inform Click that something is a flag: + +.. click:example:: + + import sys + + @click.command() + @click.option('--shout', is_flag=True) + def info(shout): + rv = sys.platform + if shout: + rv = rv.upper() + '!!!!111' + click.echo(rv) + +And on the command line: + +.. click:run:: + + invoke(info, args=['--shout']) + +Note that if a slash is contained in your option already (for instance, if +you use Windows-style parameters where ``/`` is the prefix character), you +can alternatively split the parameters through ``;`` instead: + +.. click:example:: + + @click.command() + @click.option('/debug;/no-debug') + def log(debug): + click.echo('debug=%s' % debug) + + if __name__ == '__main__': + log() + +.. versionchanged:: 6.0 + +If you want to define an alias for the second option only, then you will +need to use leading whitespace to disambiguate the format string: + +Example: + +.. click:example:: + + import sys + + @click.command() + @click.option('--shout/--no-shout', ' /-S', default=False) + def info(shout): + rv = sys.platform + if shout: + rv = rv.upper() + '!!!!111' + click.echo(rv) + +.. click:run:: + + invoke(info, args=['--help']) + +Feature Switches +---------------- + +In addition to boolean flags, there are also feature switches. These are +implemented by setting multiple options to the same parameter name and +defining a flag value. Note that by providing the ``flag_value`` parameter, +Click will implicitly set ``is_flag=True``. + +To set a default flag, assign a value of `True` to the flag that should be +the default. + +.. click:example:: + + import sys + + @click.command() + @click.option('--upper', 'transformation', flag_value='upper', + default=True) + @click.option('--lower', 'transformation', flag_value='lower') + def info(transformation): + click.echo(getattr(sys.platform, transformation)()) + +And on the command line: + +.. click:run:: + + invoke(info, args=['--upper']) + invoke(info, args=['--lower']) + invoke(info) + +.. _choice-opts: + +Choice Options +-------------- + +Sometimes, you want to have a parameter be a choice of a list of values. +In that case you can use :class:`Choice` type. It can be instantiated +with a list of valid values. + +Example: + +.. click:example:: + + @click.command() + @click.option('--hash-type', type=click.Choice(['md5', 'sha1'])) + def digest(hash_type): + click.echo(hash_type) + +What it looks like: + +.. click:run:: + + invoke(digest, args=['--hash-type=md5']) + println() + invoke(digest, args=['--hash-type=foo']) + println() + invoke(digest, args=['--help']) + +.. _option-prompting: + +Prompting +--------- + +In some cases, you want parameters that can be provided from the command line, +but if not provided, ask for user input instead. This can be implemented with +Click by defining a prompt string. + +Example: + +.. click:example:: + + @click.command() + @click.option('--name', prompt=True) + def hello(name): + click.echo('Hello %s!' % name) + +And what it looks like: + +.. click:run:: + + invoke(hello, args=['--name=John']) + invoke(hello, input=['John']) + +If you are not happy with the default prompt string, you can ask for +a different one: + +.. click:example:: + + @click.command() + @click.option('--name', prompt='Your name please') + def hello(name): + click.echo('Hello %s!' % name) + +What it looks like: + +.. click:run:: + + invoke(hello, input=['John']) + +Password Prompts +---------------- + +Click also supports hidden prompts and asking for confirmation. This is +useful for password input: + +.. click:example:: + + @click.command() + @click.option('--password', prompt=True, hide_input=True, + confirmation_prompt=True) + def encrypt(password): + click.echo('Encrypting password to %s' % password.encode('rot13')) + +What it looks like: + +.. click:run:: + + invoke(encrypt, input=['secret', 'secret']) + +Because this combination of parameters is quite common, this can also be +replaced with the :func:`password_option` decorator: + +.. click:example:: + + @click.command() + @click.password_option() + def encrypt(password): + click.echo('Encrypting password to %s' % password.encode('rot13')) + +Dynamic Defaults for Prompts +---------------------------- + +The ``auto_envvar_prefix`` and ``default_map`` options for the context +allow the program to read option values from the environment or a +configuration file. However, this overrides the prompting mechanism, so +that the user does not get the option to change the value interactively. + +If you want to let the user configure the default value, but still be +prompted if the option isn't specified on the command line, you can do so +by supplying a callable as the default value. For example, to get a default +from the environment: + +.. click:example:: + + @click.command() + @click.option('--username', prompt=True, + default=lambda: os.environ.get('USER', '')) + def hello(username): + print("Hello,", username) + +Callbacks and Eager Options +--------------------------- + +Sometimes, you want a parameter to completely change the execution flow. +For instance, this is the case when you want to have a ``--version`` +parameter that prints out the version and then exits the application. + +Note: an actual implementation of a ``--version`` parameter that is +reusable is available in Click as :func:`click.version_option`. The code +here is merely an example of how to implement such a flag. + +In such cases, you need two concepts: eager parameters and a callback. An +eager parameter is a parameter that is handled before others, and a +callback is what executes after the parameter is handled. The eagerness +is necessary so that an earlier required parameter does not produce an +error message. For instance, if ``--version`` was not eager and a +parameter ``--foo`` was required and defined before, you would need to +specify it for ``--version`` to work. For more information, see +:ref:`callback-evaluation-order`. + +A callback is a function that is invoked with two parameters: the current +:class:`Context` and the value. The context provides some useful features +such as quitting the application and gives access to other already +processed parameters. + +Here an example for a ``--version`` flag: + +.. click:example:: + + def print_version(ctx, param, value): + if not value or ctx.resilient_parsing: + return + click.echo('Version 1.0') + ctx.exit() + + @click.command() + @click.option('--version', is_flag=True, callback=print_version, + expose_value=False, is_eager=True) + def hello(): + click.echo('Hello World!') + +The `expose_value` parameter prevents the pretty pointless ``version`` +parameter from being passed to the callback. If that was not specified, a +boolean would be passed to the `hello` script. The `resilient_parsing` +flag is applied to the context if Click wants to parse the command line +without any destructive behavior that would change the execution flow. In +this case, because we would exit the program, we instead do nothing. + +What it looks like: + +.. click:run:: + + invoke(hello) + invoke(hello, args=['--version']) + +.. admonition:: Callback Signature Changes + + In Click 2.0 the signature for callbacks changed. For more + information about these changes see :ref:`upgrade-to-2.0`. + +Yes Parameters +-------------- + +For dangerous operations, it's very useful to be able to ask a user for +confirmation. This can be done by adding a boolean ``--yes`` flag and +asking for confirmation if the user did not provide it and to fail in a +callback: + +.. click:example:: + + def abort_if_false(ctx, param, value): + if not value: + ctx.abort() + + @click.command() + @click.option('--yes', is_flag=True, callback=abort_if_false, + expose_value=False, + prompt='Are you sure you want to drop the db?') + def dropdb(): + click.echo('Dropped all tables!') + +And what it looks like on the command line: + +.. click:run:: + + invoke(dropdb, input=['n']) + invoke(dropdb, args=['--yes']) + +Because this combination of parameters is quite common, this can also be +replaced with the :func:`confirmation_option` decorator: + +.. click:example:: + + @click.command() + @click.confirmation_option(prompt='Are you sure you want to drop the db?') + def dropdb(): + click.echo('Dropped all tables!') + +.. admonition:: Callback Signature Changes + + In Click 2.0 the signature for callbacks changed. For more + information about these changes see :ref:`upgrade-to-2.0`. + +Values from Environment Variables +--------------------------------- + +A very useful feature of Click is the ability to accept parameters from +environment variables in addition to regular parameters. This allows +tools to be automated much easier. For instance, you might want to pass +a configuration file with a ``--config`` parameter but also support exporting +a ``TOOL_CONFIG=hello.cfg`` key-value pair for a nicer development +experience. + +This is supported by Click in two ways. One is to automatically build +environment variables which is supported for options only. To enable this +feature, the ``auto_envvar_prefix`` parameter needs to be passed to the +script that is invoked. Each command and parameter is then added as an +uppercase underscore-separated variable. If you have a subcommand +called ``foo`` taking an option called ``bar`` and the prefix is +``MY_TOOL``, then the variable is ``MY_TOOL_FOO_BAR``. + +Example usage: + +.. click:example:: + + @click.command() + @click.option('--username') + def greet(username): + click.echo('Hello %s!' % username) + + if __name__ == '__main__': + greet(auto_envvar_prefix='GREETER') + +And from the command line: + +.. click:run:: + + invoke(greet, env={'GREETER_USERNAME': 'john'}, + auto_envvar_prefix='GREETER') + +The second option is to manually pull values in from specific environment +variables by defining the name of the environment variable on the option. + +Example usage: + +.. click:example:: + + @click.command() + @click.option('--username', envvar='USERNAME') + def greet(username): + click.echo('Hello %s!' % username) + + if __name__ == '__main__': + greet() + +And from the command line: + +.. click:run:: + + invoke(greet, env={'USERNAME': 'john'}) + +In that case it can also be a list of different environment variables +where the first one is picked. + +Multiple Values from Environment Values +--------------------------------------- + +As options can accept multiple values, pulling in such values from +environment variables (which are strings) is a bit more complex. The way +Click solves this is by leaving it up to the type to customize this +behavior. For both ``multiple`` and ``nargs`` with values other than +``1``, Click will invoke the :meth:`ParamType.split_envvar_value` method to +perform the splitting. + +The default implementation for all types is to split on whitespace. The +exceptions to this rule are the :class:`File` and :class:`Path` types +which both split according to the operating system's path splitting rules. +On Unix systems like Linux and OS X, the splitting happens for those on +every colon (``:``), and for Windows, on every semicolon (``;``). + +Example usage: + +.. click:example:: + + @click.command() + @click.option('paths', '--path', envvar='PATHS', multiple=True, + type=click.Path()) + def perform(paths): + for path in paths: + click.echo(path) + + if __name__ == '__main__': + perform() + +And from the command line: + +.. click:run:: + + import os + invoke(perform, env={'PATHS': './foo/bar%s./test' % os.path.pathsep}) + +Other Prefix Characters +----------------------- + +Click can deal with alternative prefix characters other than ``-`` for +options. This is for instance useful if you want to handle slashes as +parameters ``/`` or something similar. Note that this is strongly +discouraged in general because Click wants developers to stay close to +POSIX semantics. However in certain situations this can be useful: + +.. click:example:: + + @click.command() + @click.option('+w/-w') + def chmod(w): + click.echo('writable=%s' % w) + + if __name__ == '__main__': + chmod() + +And from the command line: + +.. click:run:: + + invoke(chmod, args=['+w']) + invoke(chmod, args=['-w']) + +Note that if you are using ``/`` as prefix character and you want to use a +boolean flag you need to separate it with ``;`` instead of ``/``: + +.. click:example:: + + @click.command() + @click.option('/debug;/no-debug') + def log(debug): + click.echo('debug=%s' % debug) + + if __name__ == '__main__': + log() + +.. _ranges: + +Range Options +------------- + +A special mention should go to the :class:`IntRange` type, which works very +similarly to the :data:`INT` type, but restricts the value to fall into a +specific range (inclusive on both edges). It has two modes: + +- the default mode (non-clamping mode) where a value that falls outside + of the range will cause an error. +- an optional clamping mode where a value that falls outside of the + range will be clamped. This means that a range of ``0-5`` would + return ``5`` for the value ``10`` or ``0`` for the value ``-1`` (for + example). + +Example: + +.. click:example:: + + @click.command() + @click.option('--count', type=click.IntRange(0, 20, clamp=True)) + @click.option('--digit', type=click.IntRange(0, 10)) + def repeat(count, digit): + click.echo(str(digit) * count) + + if __name__ == '__main__': + repeat() + +And from the command line: + +.. click:run:: + + invoke(repeat, args=['--count=1000', '--digit=5']) + invoke(repeat, args=['--count=1000', '--digit=12']) + +If you pass ``None`` for any of the edges, it means that the range is open +at that side. + +Callbacks for Validation +------------------------ + +.. versionchanged:: 2.0 + +If you want to apply custom validation logic, you can do this in the +parameter callbacks. These callbacks can both modify values as well as +raise errors if the validation does not work. + +In Click 1.0, you can only raise the :exc:`UsageError` but starting with +Click 2.0, you can also raise the :exc:`BadParameter` error, which has the +added advantage that it will automatically format the error message to +also contain the parameter name. + +Example: + +.. click:example:: + + def validate_rolls(ctx, param, value): + try: + rolls, dice = map(int, value.split('d', 2)) + return (dice, rolls) + except ValueError: + raise click.BadParameter('rolls need to be in format NdM') + + @click.command() + @click.option('--rolls', callback=validate_rolls, default='1d6') + def roll(rolls): + click.echo('Rolling a %d-sided dice %d time(s)' % rolls) + + if __name__ == '__main__': + roll() + +And what it looks like: + +.. click:run:: + + invoke(roll, args=['--rolls=42']) + println() + invoke(roll, args=['--rolls=2d12']) diff -Nru click-0.4.43+16.04.20160203/docs/parameters.rst click-6.7/docs/parameters.rst --- click-0.4.43+16.04.20160203/docs/parameters.rst 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/parameters.rst 2017-01-06 22:32:37.000000000 +0000 @@ -0,0 +1,120 @@ +Parameters +========== + +.. currentmodule:: click + +Click supports two types of parameters for scripts: options and arguments. +There is generally some confusion among authors of command line scripts of +when to use which, so here is a quick overview of the differences. As its +name indicates, an option is optional. While arguments can be optional +within reason, they are much more restricted in how optional they can be. + +To help you decide between options and arguments, the recommendation is +to use arguments exclusively for things like going to subcommands or input +filenames / URLs, and have everything else be an option instead. + +Differences +----------- + +Arguments can do less than options. The following features are only +available for options: + +* automatic prompting for missing input +* act as flags (boolean or otherwise) +* option values can be pulled from environment variables, arguments can not +* options are fully documented in the help page, arguments are not + (this is intentional as arguments might be too specific to be + automatically documented) + +On the other hand arguments, unlike options, can accept an arbitrary number +of arguments. Options can strictly ever only accept a fixed number of +arguments (defaults to 1). + +Parameter Types +--------------- + +Parameters can be of different types. Types can be implemented with +different behavior and some are supported out of the box: + +``str`` / :data:`click.STRING`: + The default parameter type which indicates unicode strings. + +``int`` / :data:`click.INT`: + A parameter that only accepts integers. + +``float`` / :data:`click.FLOAT`: + A parameter that only accepts floating point values. + +``bool`` / :data:`click.BOOL`: + A parameter that accepts boolean values. This is automatically used + for boolean flags. If used with string values ``1``, ``yes``, ``y`` + and ``true`` convert to `True` and ``0``, ``no``, ``n`` and ``false`` + convert to `False`. + +:data:`click.UUID`: + A parameter that accepts UUID values. This is not automatically + guessed but represented as :class:`uuid.UUID`. + +.. autoclass:: File + :noindex: + +.. autoclass:: Path + :noindex: + +.. autoclass:: Choice + :noindex: + +.. autoclass:: IntRange + :noindex: + +Custom parameter types can be implemented by subclassing +:class:`click.ParamType`. For simple cases, passing a Python function that +fails with a `ValueError` is also supported, though discouraged. + +Parameter Names +--------------- + +Parameters (both options and arguments) accept a number of positional +arguments which are the parameter declarations. Each string with a +single dash is added as short argument; each string starting with a double +dash as long one. If a string is added without any dashes, it becomes the +internal parameter name which is also used as variable name. + +If a parameter is not given a name without dashes, a name is generated +automatically by taking the longest argument and converting all dashes to +underscores. For an option with ``('-f', '--foo-bar')``, the parameter +name is `foo_bar`. For an option with ``('-x',)``, the parameter is `x`. +For an option with ``('-f', '--filename', 'dest')``, the parameter is +called `dest`. + +Implementing Custom Types +------------------------- + +To implement a custom type, you need to subclass the :class:`ParamType` +class. Types can be invoked with or without context and parameter object, +which is why they need to be able to deal with this. + +The following code implements an integer type that accepts hex and octal +numbers in addition to normal integers, and converts them into regular +integers:: + + import click + + class BasedIntParamType(click.ParamType): + name = 'integer' + + def convert(self, value, param, ctx): + try: + if value[:2].lower() == '0x': + return int(value[2:], 16) + elif value[:1] == '0': + return int(value, 8) + return int(value, 10) + except ValueError: + self.fail('%s is not a valid integer' % value, param, ctx) + + BASED_INT = BasedIntParamType() + +As you can see, a subclass needs to implement the :meth:`ParamType.convert` +method and optionally provide the :attr:`ParamType.name` attribute. The +latter can be used for documentation purposes. diff -Nru click-0.4.43+16.04.20160203/docs/prompts.rst click-6.7/docs/prompts.rst --- click-0.4.43+16.04.20160203/docs/prompts.rst 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/prompts.rst 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,48 @@ +User Input Prompts +================== + +.. currentmodule:: click + +Click supports prompts in two different places. The first is automated +prompts when the parameter handling happens, and the second is to ask for +prompts at a later point independently. + +This can be accomplished with the :func:`prompt` function, which asks for +valid input according to a type, or the :func:`confirm` function, which asks +for confirmation (yes/no). + +Option Prompts +-------------- + +Option prompts are integrated into the option interface. See +:ref:`option-prompting` for more information. Internally, it +automatically calls either :func:`prompt` or :func:`confirm` as necessary. + +Input Prompts +------------- + +To manually ask for user input, you can use the :func:`prompt` function. +By default, it accepts any Unicode string, but you can ask for any other +type. For instance, you can ask for a valid integer:: + + value = click.prompt('Please enter a valid integer', type=int) + +Additionally, the type will be determined automatically if a default value is +provided. For instance, the following will only accept floats:: + + value = click.prompt('Please enter a number', default=42.0) + +Confirmation Prompts +-------------------- + +To ask if a user wants to continue with an action, the :func:`confirm` +function comes in handy. By default, it returns the result of the prompt +as a boolean value:: + + if click.confirm('Do you want to continue?'): + click.echo('Well done!') + +There is also the option to make the function automatically abort the +execution of the program if it does not return ``True``:: + + click.confirm('Do you want to continue?', abort=True) diff -Nru click-0.4.43+16.04.20160203/docs/python3.rst click-6.7/docs/python3.rst --- click-0.4.43+16.04.20160203/docs/python3.rst 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/python3.rst 2017-01-06 22:32:37.000000000 +0000 @@ -0,0 +1,183 @@ +Python 3 Support +================ + +.. currentmodule:: click + +Click supports Python 3, but like all other command line utility libraries, +it suffers from the Unicode text model in Python 3. All examples in the +documentation were written so that they could run on both Python 2.x and +Python 3.3 or higher. + +At the moment, it is strongly recommended is to use Python 2 for Click +utilities unless Python 3 is a hard requirement. + +.. _python3-limitations: + +Python 3 Limitations +-------------------- + +At the moment, Click suffers from a few problems with Python 3: + +* The command line in Unix traditionally is in bytes, not Unicode. While + there are encoding hints for all of this, there are generally some + situations where this can break. The most common one is SSH + connections to machines with different locales. + + Misconfigured environments can currently cause a wide range of Unicode + problems in Python 3 due to the lack of support for roundtripping + surrogate escapes. This will not be fixed in Click itself! + + For more information see :ref:`python3-surrogates`. + +* Standard input and output in Python 3 is opened in Unicode mode by + default. Click has to reopen the stream in binary mode in certain + situations. Because there is no standardized way to do this, this + might not always work. Primarily this can become a problem when + testing command-line applications. + + This is not supported:: + + sys.stdin = io.StringIO('Input here') + sys.stdout = io.StringIO() + + Instead you need to do this:: + + input = 'Input here' + in_stream = io.BytesIO(input.encode('utf-8')) + sys.stdin = io.TextIOWrapper(in_stream, encoding='utf-8') + out_stream = io.BytesIO() + sys.stdout = io.TextIOWrapper(out_stream, encoding='utf-8') + + Remember that in that case, you need to use ``out_stream.getvalue()`` + and not ``sys.stdout.getvalue()`` if you want to access the buffer + contents as the wrapper will not forward that method. + +Python 2 and 3 Differences +-------------------------- + +Click attempts to minimize the differences between Python 2 and Python 3 +by following best practices for both languages. + +In Python 2, the following is true: + +* ``sys.stdin``, ``sys.stdout``, and ``sys.stderr`` are opened in binary + mode, but under some circumstances they support Unicode output. Click + attempts to not subvert this but provides support for forcing streams + to be Unicode-based. +* ``sys.argv`` is always byte-based. Click will pass bytes to all + input types and convert as necessary. The :class:`STRING` type + automatically will decode properly the input value into a string by + trying the most appropriate encodings. +* When dealing with files, Click will never go through the Unicode APIs + and will instead use the operating system's byte APIs to open the + files. + +In Python 3, the following is true: + +* ``sys.stdin``, ``sys.stdout`` and ``sys.stderr`` are by default + text-based. When Click needs a binary stream, it attempts to discover + the underlying binary stream. See :ref:`python3-limitations` for how + this works. +* ``sys.argv`` is always Unicode-based. This also means that the native + type for input values to the types in Click is Unicode, and not bytes. + + This causes problems if the terminal is incorrectly set and Python + does not figure out the encoding. In that case, the Unicode string + will contain error bytes encoded as surrogate escapes. +* When dealing with files, Click will always use the Unicode file system + API calls by using the operating system's reported or guessed + filesystem encoding. Surrogates are supported for filenames, so it + should be possible to open files through the :class:`File` type even + if the environment is misconfigured. + +.. _python3-surrogates: + +Python 3 Surrogate Handling +--------------------------- + +Click in Python 3 does all the Unicode handling in the standard library +and is subject to its behavior. In Python 2, Click does all the Unicode +handling itself, which means there are differences in error behavior. + +The most glaring difference is that in Python 2, Unicode will "just work", +while in Python 3, it requires extra care. The reason for this is that in +Python 3, the encoding detection is done in the interpreter, and on Linux +and certain other operating systems, its encoding handling is problematic. + +The biggest source of frustration is that Click scripts invoked by +init systems (sysvinit, upstart, systemd, etc.), deployment tools (salt, +puppet), or cron jobs (cron) will refuse to work unless a Unicode locale is +exported. + +If Click encounters such an environment it will prevent further execution +to force you to set a locale. This is done because Click cannot know +about the state of the system once it's invoked and restore the values +before Python's Unicode handling kicked in. + +If you see something like this error in Python 3:: + + Traceback (most recent call last): + ... + RuntimeError: Click will abort further execution because Python 3 was + configured to use ASCII as encoding for the environment. Either switch + to Python 2 or consult http://click.pocoo.org/python3/ for + mitigation steps. + +You are dealing with an environment where Python 3 thinks you are +restricted to ASCII data. The solution to these problems is different +depending on which locale your computer is running in. + +For instance, if you have a German Linux machine, you can fix the problem +by exporting the locale to ``de_DE.utf-8``:: + + export LC_ALL=de_DE.utf-8 + export LANG=de_DE.utf-8 + +If you are on a US machine, ``en_US.utf-8`` is the encoding of choice. On +some newer Linux systems, you could also try ``C.UTF-8`` as the locale:: + + export LC_ALL=C.UTF-8 + export LANG=C.UTF-8 + +On some systems it was reported that `UTF-8` has to be written as `UTF8` +and vice versa. To see which locales are supported you can invoke +``locale -a``:: + + locale -a + +You need to do this before you invoke your Python script. If you are +curious about the reasons for this, you can join the discussions in the +Python 3 bug tracker: + +* `ASCII is a bad filesystem default encoding + `_ +* `Use surrogateescape as default error handler + `_ +* `Python 3 raises Unicode errors in the C locale + `_ +* `LC_CTYPE=C: pydoc leaves terminal in an unusable state + `_ (this is relevant to Click + because the pager support is provided by the stdlib pydoc module) + +Unicode Literals +---------------- + +Starting with Click 5.0 there will be a warning for the use of the +``unicode_literals`` future import in Python 2. This has been done due to +the negative consequences of this import with regards to unintentionally +causing bugs due to introducing Unicode data to APIs that are incapable of +handling them. For some examples of this issue, see the discussion on +this github issue: `python-future#22 +`_. + +If you use ``unicode_literals`` in any file that defines a Click command +or that invokes a click command you will be given a warning. You are +strongly encouraged to not use ``unicode_literals`` and instead use +explicit ``u`` prefixes for your Unicode strings. + +If you do want to ignore the warning and continue using +``unicode_literals`` on your own peril, you can disable the warning as +follows:: + + import click + click.disable_unicode_literals_warning = True diff -Nru click-0.4.43+16.04.20160203/docs/quickstart.rst click-6.7/docs/quickstart.rst --- click-0.4.43+16.04.20160203/docs/quickstart.rst 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/quickstart.rst 2017-01-06 22:32:37.000000000 +0000 @@ -0,0 +1,274 @@ +Quickstart +========== + +.. currentmodule:: click + +You can get the library directly from PyPI:: + + pip install click + +The installation into a :ref:`virtualenv` is heavily recommended. + +.. _virtualenv: + +virtualenv +---------- + +Virtualenv is probably what you want to use for developing Click +applications. + +What problem does virtualenv solve? Chances are that you want to use it +for other projects besides your Click script. But the more projects you +have, the more likely it is that you will be working with different +versions of Python itself, or at least different versions of Python +libraries. Let's face it: quite often libraries break backwards +compatibility, and it's unlikely that any serious application will have +zero dependencies. So what do you do if two or more of your projects have +conflicting dependencies? + +Virtualenv to the rescue! Virtualenv enables multiple side-by-side +installations of Python, one for each project. It doesn't actually +install separate copies of Python, but it does provide a clever way to +keep different project environments isolated. Let's see how virtualenv +works. + +If you are on Mac OS X or Linux, chances are that one of the following two +commands will work for you:: + + $ sudo easy_install virtualenv + +or even better:: + + $ sudo pip install virtualenv + +One of these will probably install virtualenv on your system. Maybe it's even +in your package manager. If you use Ubuntu, try:: + + $ sudo apt-get install python-virtualenv + +If you are on Windows (or none of the above methods worked) you must install +``pip`` first. For more information about this, see `installing pip`_. +Once you have it installed, run the ``pip`` command from above, but without +the `sudo` prefix. + +.. _installing pip: http://pip.readthedocs.org/en/latest/installing.html + +Once you have virtualenv installed, just fire up a shell and create +your own environment. I usually create a project folder and a `venv` +folder within:: + + $ mkdir myproject + $ cd myproject + $ virtualenv venv + New python executable in venv/bin/python + Installing setuptools, pip............done. + +Now, whenever you want to work on a project, you only have to activate the +corresponding environment. On OS X and Linux, do the following:: + + $ . venv/bin/activate + +If you are a Windows user, the following command is for you:: + + $ venv\scripts\activate + +Either way, you should now be using your virtualenv (notice how the prompt of +your shell has changed to show the active environment). + +And if you want to go back to the real world, use the following command:: + + $ deactivate + +After doing this, the prompt of your shell should be as familar as before. + +Now, let's move on. Enter the following command to get Click activated in your +virtualenv:: + + $ pip install Click + +A few seconds later and you are good to go. + +Screencast and Examples +----------------------- + +There is a screencast available which shows the basic API of Click and +how to build simple applications with it. It also explores how to build +commands with subcommands. + +* `Building Command Line Applications with Click + `_ + +Examples of Click applications can be found in the documentation as well +as in the GitHub repository together with readme files: + +* ``inout``: `File input and output + `_ +* ``naval``: `Port of docopt naval example + `_ +* ``aliases``: `Command alias example + `_ +* ``repo``: `Git-/Mercurial-like command line interface + `_ +* ``complex``: `Complex example with plugin loading + `_ +* ``validation``: `Custom parameter validation example + `_ +* ``colors``: `Colorama ANSI color support + `_ +* ``termui``: `Terminal UI functions demo + `_ +* ``imagepipe``: `Multi command chaining demo + `_ + +Basic Concepts +-------------- + +Click is based on declaring commands through decorators. Internally, there +is a non-decorator interface for advanced use cases, but it's discouraged +for high-level usage. + +A function becomes a Click command line tool by decorating it through +:func:`click.command`. At its simplest, just decorating a function +with this decorator will make it into a callable script: + +.. click:example:: + + import click + + @click.command() + def hello(): + click.echo('Hello World!') + +What's happening is that the decorator converts the function into a +:class:`Command` which then can be invoked:: + + if __name__ == '__main__': + hello() + +And what it looks like: + +.. click:run:: + + invoke(hello, args=[], prog_name='python hello.py') + +And the corresponding help page: + +.. click:run:: + + invoke(hello, args=['--help'], prog_name='python hello.py') + +Echoing +------- + +Why does this example use :func:`echo` instead of the regular +:func:`print` function? The answer to this question is that Click +attempts to support both Python 2 and Python 3 the same way and to be very +robust even when the environment is misconfigured. Click wants to be +functional at least on a basic level even if everything is completely +broken. + +What this means is that the :func:`echo` function applies some error +correction in case the terminal is misconfigured instead of dying with an +:exc:`UnicodeError`. + +As an added benefit, starting with Click 2.0, the echo function also +has good support for ANSI colors. It will automatically strip ANSI codes +if the output stream is a file and if colorama is supported, ANSI colors +will also work on Windows. See :ref:`ansi-colors` for more information. + +If you don't need this, you can also use the `print()` construct / +function. + +Nesting Commands +---------------- + +Commands can be attached to other commands of type :class:`Group`. This +allows arbitrary nesting of scripts. As an example here is a script that +implements two commands for managing databases: + +.. click:example:: + + @click.group() + def cli(): + pass + + @click.command() + def initdb(): + click.echo('Initialized the database') + + @click.command() + def dropdb(): + click.echo('Dropped the database') + + cli.add_command(initdb) + cli.add_command(dropdb) + +As you can see, the :func:`group` decorator works like the :func:`command` +decorator, but creates a :class:`Group` object instead which can be given +multiple subcommands that can be attached with :meth:`Group.add_command`. + +For simple scripts, it's also possible to automatically attach and create a +command by using the :meth:`Group.command` decorator instead. The above +script can instead be written like this: + +.. click:example:: + + @click.group() + def cli(): + pass + + @cli.command() + def initdb(): + click.echo('Initialized the database') + + @cli.command() + def dropdb(): + click.echo('Dropped the database') + +Adding Parameters +----------------- + +To add parameters, use the :func:`option` and :func:`argument` decorators: + +.. click:example:: + + @click.command() + @click.option('--count', default=1, help='number of greetings') + @click.argument('name') + def hello(count, name): + for x in range(count): + click.echo('Hello %s!' % name) + +What it looks like: + +.. click:run:: + + invoke(hello, args=['--help'], prog_name='python hello.py') + +.. _switching-to-setuptools: + +Switching to Setuptools +----------------------- + +In the code you wrote so far there is a block at the end of the file which +looks like this: ``if __name__ == '__main__':``. This is traditionally +how a standalone Python file looks like. With Click you can continue +doing that, but there are better ways through setuptools. + +There are two main (and many more) reasons for this: + +The first one is that setuptools automatically generates executable +wrappers for Windows so your command line utilities work on Windows too. + +The second reason is that setuptools scripts work with virtualenv on Unix +without the virtualenv having to be activated. This is a very useful +concept which allows you to bundle your scripts with all requirements into +a virtualenv. + +Click is perfectly equipped to work with that and in fact the rest of the +documentation will assume that you are writing applications through +setuptools. + +I strongly recommend to have a look at the :ref:`setuptools-integration` +chapter before reading the rest as the examples assume that you will +be using setuptools. diff -Nru click-0.4.43+16.04.20160203/docs/setuptools.rst click-6.7/docs/setuptools.rst --- click-0.4.43+16.04.20160203/docs/setuptools.rst 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/setuptools.rst 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,139 @@ +.. _setuptools-integration: + +Setuptools Integration +====================== + +When writing command line utilities, it's recommended to write them as +modules that are distributed with setuptools instead of using Unix +shebangs. + +Why would you want to do that? There are a bunch of reasons: + +1. One of the problems with the traditional approach is that the first + module the Python interpreter loads has an incorrect name. This might + sound like a small issue but it has quite significant implications. + + The first module is not called by its actual name, but the + interpreter renames it to ``__main__``. While that is a perfectly + valid name it means that if another piece of code wants to import from + that module it will trigger the import a second time under its real + name and all of a sudden your code is imported twice. + +2. Not on all platforms are things that easy to execute. On Linux and OS + X you can add a comment to the beginning of the file (``#!/usr/bin/env + python``) and your script works like an executable (assuming it has + the executable bit set). This however does not work on Windows. + While on Windows you can associate interpreters with file extensions + (like having everything ending in ``.py`` execute through the Python + interpreter) you will then run into issues if you want to use the + script in a virtualenv. + + In fact running a script in a virtualenv is an issue with OS X and + Linux as well. With the traditional approach you need to have the + whole virtualenv activated so that the correct Python interpreter is + used. Not very user friendly. + +3. The main trick only works if the script is a Python module. If your + application grows too large and you want to start using a package you + will run into issues. + +Introduction +------------ + +To bundle your script with setuptools, all you need is the script in a +Python package and a ``setup.py`` file. + +Imagine this directory structure:: + + yourscript.py + setup.py + +Contents of ``yourscript.py``: + +.. click:example:: + + import click + + @click.command() + def cli(): + """Example script.""" + click.echo('Hello World!') + +Contents of ``setup.py``:: + + from setuptools import setup + + setup( + name='yourscript', + version='0.1', + py_modules=['yourscript'], + install_requires=[ + 'Click', + ], + entry_points=''' + [console_scripts] + yourscript=yourscript:cli + ''', + ) + +The magic is in the ``entry_points`` parameter. Below +``console_scripts``, each line identifies one console script. The first +part before the equals sign (``=``) is the name of the script that should +be generated, the second part is the import path followed by a colon +(``:``) with the Click command. + +That's it. + +Testing The Script +------------------ + +To test the script, you can make a new virtualenv and then install your +package:: + + $ virtualenv venv + $ . venv/bin/activate + $ pip install --editable . + +Afterwards, your command should be available: + +.. click:run:: + + invoke(cli, prog_name='yourscript') + +Scripts in Packages +------------------- + +If your script is growing and you want to switch over to your script being +contained in a Python package the changes necessary are minimal. Let's +assume your directory structure changed to this:: + + yourpackage/ + __init__.py + main.py + utils.py + scripts/ + __init__.py + yourscript.py + +In this case instead of using ``py_modules`` in your ``setup.py`` file you +can use ``packages`` and the automatic package finding support of +setuptools. In addition to that it's also recommended to include other +package data. + +These would be the modified contents of ``setup.py``:: + + from setuptools import setup, find_packages + + setup( + name='yourpackage', + version='0.1', + packages=find_packages(), + include_package_data=True, + install_requires=[ + 'Click', + ], + entry_points=''' + [console_scripts] + yourscript=yourpackage.scripts.yourscript:cli + ''', + ) Binary files /tmp/tmp5CGTrm/S7dHEMVmjm/click-0.4.43+16.04.20160203/docs/_static/click@2x.png and /tmp/tmp5CGTrm/FrCzimMInt/click-6.7/docs/_static/click@2x.png differ Binary files /tmp/tmp5CGTrm/S7dHEMVmjm/click-0.4.43+16.04.20160203/docs/_static/click.png and /tmp/tmp5CGTrm/FrCzimMInt/click-6.7/docs/_static/click.png differ Binary files /tmp/tmp5CGTrm/S7dHEMVmjm/click-0.4.43+16.04.20160203/docs/_static/click-small@2x.png and /tmp/tmp5CGTrm/FrCzimMInt/click-6.7/docs/_static/click-small@2x.png differ Binary files /tmp/tmp5CGTrm/S7dHEMVmjm/click-0.4.43+16.04.20160203/docs/_static/click-small.png and /tmp/tmp5CGTrm/FrCzimMInt/click-6.7/docs/_static/click-small.png differ diff -Nru click-0.4.43+16.04.20160203/docs/_templates/sidebarintro.html click-6.7/docs/_templates/sidebarintro.html --- click-0.4.43+16.04.20160203/docs/_templates/sidebarintro.html 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/_templates/sidebarintro.html 2017-01-06 22:32:37.000000000 +0000 @@ -0,0 +1,13 @@ +

About

+

+ Click is a Python package for creating beautiful command line interfaces in a + composable way with as little amount of code as necessary. It’s the “Command + Line Interface Creation Kit”. +

+

Useful Links

+ diff -Nru click-0.4.43+16.04.20160203/docs/_templates/sidebarlogo.html click-6.7/docs/_templates/sidebarlogo.html --- click-0.4.43+16.04.20160203/docs/_templates/sidebarlogo.html 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/_templates/sidebarlogo.html 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,3 @@ + diff -Nru click-0.4.43+16.04.20160203/docs/testing.rst click-6.7/docs/testing.rst --- click-0.4.43+16.04.20160203/docs/testing.rst 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/testing.rst 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,113 @@ +Testing Click Applications +========================== + +.. currentmodule:: click.testing + +For basic testing, Click provides the :mod:`click.testing` module which +provides test functionality that helps you invoke command line +applications and check their behavior. + +These tools should really only be used for testing as they change +the entire interpreter state for simplicity and are not in any way +thread-safe! + +Basic Testing +------------- + +The basic functionality for testing Click applications is the +:class:`CliRunner` which can invoke commands as command line scripts. The +:meth:`CliRunner.invoke` method runs the command line script in isolation +and captures the output as both bytes and binary data. + +The return value is a :class:`Result` object, which has the captured output +data, exit code, and optional exception attached. + +Example:: + + import click + from click.testing import CliRunner + + def test_hello_world(): + @click.command() + @click.argument('name') + def hello(name): + click.echo('Hello %s!' % name) + + runner = CliRunner() + result = runner.invoke(hello, ['Peter']) + assert result.exit_code == 0 + assert result.output == 'Hello Peter!\n' + +For subcommand testing, a subcommand name must be specified in the `args` parameter of :meth:`CliRunner.invoke` method. + +Example:: + + import click + from click.testing import CliRunner + + def test_sync(): + @click.group() + @click.option('--debug/--no-debug', default=False) + def cli(debug): + click.echo('Debug mode is %s' % ('on' if debug else 'off')) + + @cli.command() + def sync(): + click.echo('Syncing') + + runner = CliRunner() + result = runner.invoke(cli, ['--debug', 'sync']) + assert result.exit_code == 0 + assert 'Debug mode is on' in result.output + assert 'Syncing' in result.output + +File System Isolation +--------------------- + +For basic command line tools that want to operate with the file system, the +:meth:`CliRunner.isolated_filesystem` method comes in useful which sets up +an empty folder and changes the current working directory to. + +Example:: + + import click + from click.testing import CliRunner + + def test_cat(): + @click.command() + @click.argument('f', type=click.File()) + def cat(f): + click.echo(f.read()) + + runner = CliRunner() + with runner.isolated_filesystem(): + with open('hello.txt', 'w') as f: + f.write('Hello World!') + + result = runner.invoke(cat, ['hello.txt']) + assert result.exit_code == 0 + assert result.output == 'Hello World!\n' + +Input Streams +------------- + +The test wrapper can also be used to provide input data for the input +stream (stdin). This is very useful for testing prompts, for instance:: + + import click + from click.testing import CliRunner + + def test_prompts(): + @click.command() + @click.option('--foo', prompt=True) + def test(foo): + click.echo('foo=%s' % foo) + + runner = CliRunner() + result = runner.invoke(test, input='wau wau\n') + assert not result.exception + assert result.output == 'Foo: wau wau\nfoo=wau wau\n' + +Note that prompts will be emulated so that they write the input data to +the output stream as well. If hidden input is expected then this +obviously does not happen. diff -Nru click-0.4.43+16.04.20160203/docs/upgrading.rst click-6.7/docs/upgrading.rst --- click-0.4.43+16.04.20160203/docs/upgrading.rst 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/upgrading.rst 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,100 @@ +Upgrading To Newer Releases +=========================== + +Click attempts the highest level of backwards compatibility but sometimes +this is not entirely possible. In case we need to break backwards +compatibility this document gives you information about how to upgrade or +handle backwards compatibility properly. + +.. _upgrade-to-3.2: + +Upgrading to 3.2 +---------------- + +Click 3.2 had to perform two changes to multi commands which were +triggered by a change between Click 2 and Click 3 that had bigger +consequences than anticipated. + +Context Invokes +``````````````` + +Click 3.2 contains a fix for the :meth:`Context.invoke` function when used +with other commands. The original intention of this function was to +invoke the other command as as if it came from the command line when it +was passed a context object instead of a function. This use was only +documented in a single place in the documentation before and there was no +proper explanation for the method in the API documentation. + +The core issue is that before 3.2 this call worked against intentions:: + + ctx.invoke(other_command, 'arg1', 'arg2') + +This was never intended to work as it does not allow Click to operate on +the parameters. Given that this pattern was never documented and ill +intended the decision was made to change this behavior in a bugfix release +before it spreads by accident and developers depend on it. + +The correct invocation for the above command is the following:: + + ctx.invoke(other_command, name_of_arg1='arg1', name_of_arg2='arg2') + +This also allowed us to fix the issue that defaults were not handled +properly by this function. + +Multicommand Chaining API +````````````````````````` + +Click 3 introduced multicommand chaning. This required a change in how +Click internally dispatches. Unfortunately this change was not correctly +implemented and it appeared that it was possible to provide an API that +can inform the super command about all the subcommands that will be +invoked. + +This assumption however does not work with one of the API guarantees that +have been given in the past. As such this functionality has been removed +in 3.2 as it was already broken. Instead the accidentally broken +functionality of the :attr:`Context.invoked_subcommand` attribute was +restored. + +If you do require the know which exact commands will be invoked there are +different ways to cope with this. The first one is to let the subcommands +all return functions and then to invoke the functions in a +:meth:`Context.resultcallback`. + + +.. _upgrade-to-2.0: + +Upgrading to 2.0 +---------------- + +Click 2.0 has one breaking change which is the signature for parameter +callbacks. Before 2.0, the callback was invoked with ``(ctx, value)`` +whereas now it's ``(ctx, param, value)``. This change was necessary as it +otherwise made reusing callbacks too complicated. + +To ease the transition Click will still accept old callbacks. Starting +with Click 3.0 it will start to issue a warning to stderr to encourage you +to upgrade. + +In case you want to support both Click 1.0 and Click 2.0, you can make a +simple decorator that adjusts the signatures:: + + import click + from functools import update_wrapper + + def compatcallback(f): + # Click 1.0 does not have a version string stored, so we need to + # use getattr here to be safe. + if getattr(click, '__version__', '0.0') >= '2.0': + return f + return update_wrapper(lambda ctx, value: f(ctx, None, value), f) + +With that helper you can then write something like this:: + + @compatcallback + def callback(ctx, param, value): + return value.upper() + +Note that because Click 1.0 did not pass a parameter, the `param` argument +here would be `None`, so a compatibility callback could not use that +argument. diff -Nru click-0.4.43+16.04.20160203/docs/utils.rst click-6.7/docs/utils.rst --- click-0.4.43+16.04.20160203/docs/utils.rst 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/utils.rst 2017-01-06 22:32:37.000000000 +0000 @@ -0,0 +1,398 @@ +Utilities +========= + +.. currentmodule:: click + +Besides the functionality that Click provides to interface with argument +parsing and handling, it also provides a bunch of addon functionality that +is useful for writing command line utilities. + + +Printing to Stdout +------------------ + +The most obvious helper is the :func:`echo` function, which in many ways +works like the Python ``print`` statement or function. The main difference is +that it works the same in Python 2 and 3, it intelligently detects +misconfigured output streams, and it will never fail (except in Python 3; for +more information see :ref:`python3-limitations`). + +Example:: + + import click + + click.echo('Hello World!') + +Most importantly, it can print both Unicode and binary data, unlike the +built-in ``print`` function in Python 3, which cannot output any bytes. It +will, however, emit a trailing newline by default, which needs to be +suppressed by passing ``nl=False``:: + + click.echo(b'\xe2\x98\x83', nl=False) + +Last but not least :func:`echo` uses click's intelligent internal output +streams to stdout and stderr which support unicode output on the Windows +console. This means for as long as you are using `click.echo` you can +output unicode character (there are some limitations on the default font +with regards to which characters can be displayed). This functionality is +new in Click 6.0. + +.. versionadded:: 6.0 + +Click now emulates output streams on Windows to support unicode to the +Windows console through separate APIs. For more information see +`wincmd`_. + +.. versionadded:: 3.0 + +Starting with Click 3.0 you can also easily print to standard error by +passing ``err=True``:: + + click.echo('Hello World!', err=True) + + +.. _ansi-colors: + +ANSI Colors +----------- + +.. versionadded:: 2.0 + +Starting with Click 2.0, the :func:`echo` function gained extra +functionality to deal with ANSI colors and styles. Note that on Windows, +this functionality is only available if `colorama`_ is installed. If it +is installed, then ANSI codes are intelligently handled. + +Primarily this means that: + +- Click's :func:`echo` function will automatically strip ANSI color codes + if the stream is not connected to a terminal. +- the :func:`echo` function will transparently connect to the terminal on + Windows and translate ANSI codes to terminal API calls. This means + that colors will work on Windows the same way they do on other + operating systems. + +Note for `colorama` support: Click will automatically detect when `colorama` +is available and use it. Do *not* call ``colorama.init()``! + +To install `colorama`, run this command:: + + $ pip install colorama + +For styling a string, the :func:`style` function can be used:: + + import click + + click.echo(click.style('Hello World!', fg='green')) + click.echo(click.style('Some more text', bg='blue', fg='white')) + click.echo(click.style('ATTENTION', blink=True, bold=True)) + +The combination of :func:`echo` and :func:`style` is also available in +a single function called :func:`secho`:: + + click.secho('Hello World!', fg='green') + click.secho('Some more text', bg='blue', fg='white') + click.secho('ATTENTION', blink=True, bold=True) + + +.. _colorama: https://pypi.python.org/pypi/colorama + +Pager Support +------------- + +In some situations, you might want to show long texts on the terminal and +let a user scroll through it. This can be achieved by using the +:func:`echo_via_pager` function which works similarly to the :func:`echo` +function, but always writes to stdout and, if possible, through a pager. + +Example: + +.. click:example:: + + @click.command() + def less(): + click.echo_via_pager('\n'.join('Line %d' % idx + for idx in range(200))) + + +Screen Clearing +--------------- + +.. versionadded:: 2.0 + +To clear the terminal screen, you can use the :func:`clear` function that +is provided starting with Click 2.0. It does what the name suggests: it +clears the entire visible screen in a platform-agnostic way: + +:: + + import click + click.clear() + + +Getting Characters from Terminal +-------------------------------- + +.. versionadded:: 2.0 + +Normally, when reading input from the terminal, you would read from +standard input. However, this is buffered input and will not show up until +the line has been terminated. In certain circumstances, you might not want +to do that and instead read individual characters as they are being written. + +For this, Click provides the :func:`getchar` function which reads a single +character from the terminal buffer and returns it as a Unicode character. + +Note that this function will always read from the terminal, even if stdin +is instead a pipe. + +Example:: + + import click + + click.echo('Continue? [yn] ', nl=False) + c = click.getchar() + click.echo() + if c == 'y': + click.echo('We will go on') + elif c == 'n': + click.echo('Abort!') + else: + click.echo('Invalid input :(') + +Note that this reads raw input, which means that things like arrow keys +will show up in the platform's native escape format. The only characters +translated are ``^C`` and ``^D`` which are converted into keyboard +interrupts and end of file exceptions respectively. This is done because +otherwise, it's too easy to forget about that and to create scripts that +cannot be properly exited. + + +Waiting for Key Press +--------------------- + +.. versionadded:: 2.0 + +Sometimes, it's useful to pause until the user presses any key on the +keyboard. This is especially useful on Windows where ``cmd.exe`` will +close the window at the end of the command execution by default, instead +of waiting. + +In click, this can be accomplished with the :func:`pause` function. This +function will print a quick message to the terminal (which can be +customized) and wait for the user to press a key. In addition to that, +it will also become a NOP (no operation instruction) if the script is not +run interactively. + +Example:: + + import click + click.pause() + + +Launching Editors +----------------- + +.. versionadded:: 2.0 + +Click supports launching editors automatically through :func:`edit`. This +is very useful for asking users for multi-line input. It will +automatically open the user's defined editor or fall back to a sensible +default. If the user closes the editor without saving, the return value +will be `None` otherwise the entered text. + +Example usage:: + + import click + + def get_commit_message(): + MARKER = '# Everything below is ignored\n' + message = click.edit('\n\n' + MARKER) + if message is not None: + return message.split(MARKER, 1)[0].rstrip('\n') + +Alternatively, the function can also be used to launch editors for files by +a specific filename. In this case, the return value is always `None`. + +Example usage:: + + import click + click.edit(filename='/etc/passwd') + + +Launching Applications +---------------------- + +.. versionadded:: 2.0 + +Click supports launching applications through :func:`launch`. This can be +used to open the default application assocated with a URL or filetype. +This can be used to launch web browsers or picture viewers, for instance. +In addition to this, it can also launch the file manager and automatically +select the provided file. + +Example usage:: + + click.launch('http://click.pocoo.org/') + click.launch('/my/downloaded/file.txt', locate=True) + + +Printing Filenames +------------------ + +Because filenames might not be Unicode, formatting them can be a bit +tricky. Generally, this is easier in Python 2 than on 3, as you can just +write the bytes to stdout with the ``print`` function, but in Python 3, you +will always need to operate in Unicode. + +The way this works with click is through the :func:`format_filename` +function. It does a best-effort conversion of the filename to Unicode and +will never fail. This makes it possible to use these filenames in the +context of a full Unicode string. + +Example:: + + click.echo('Path: %s' % click.format_filename(b'foo.txt')) + + +Standard Streams +---------------- + +For command line utilities, it's very important to get access to input and +output streams reliably. Python generally provides access to these +streams through ``sys.stdout`` and friends, but unfortunately, there are +API differences between 2.x and 3.x, especially with regards to how these +streams respond to Unicode and binary data. + +Because of this, click provides the :func:`get_binary_stream` and +:func:`get_text_stream` functions, which produce consistent results with +different Python versions and for a wide variety pf terminal configurations. + +The end result is that these functions will always return a functional +stream object (except in very odd cases in Python 3; see +:ref:`python3-limitations`). + +Example:: + + import click + + stdin_text = click.get_text_stream('stdin') + stdout_binary = click.get_binary_stream('stdout') + +.. versionadded:: 6.0 + +Click now emulates output streams on Windows to support unicode to the +Windows console through separate APIs. For more information see +`wincmd`_. + + +Intelligent File Opening +------------------------ + +.. versionadded:: 3.0 + +Starting with Click 3.0 the logic for opening files from the :class:`File` +type is exposed through the :func:`open_file` function. It can +intelligently open stdin/stdout as well as any other file. + +Example:: + + import click + + stdout = click.open_file('-', 'w') + test_file = click.open_file('test.txt', 'w') + +If stdin or stdout are returned, the return value is wrapped in a special +file where the context manager will prevent the closing of the file. This +makes the handling of standard streams transparent and you can always use +it like this:: + + with click.open_file(filename, 'w') as f: + f.write('Hello World!\n') + + +Finding Application Folders +--------------------------- + +.. versionadded:: 2.0 + +Very often, you want to open a configuration file that belongs to your +application. However, different operating systems store these configuration +files in different locations depending on their standards. Click provides +a :func:`get_app_dir` function which returns the most appropriate location +for per-user config files for your application depending on the OS. + +Example usage:: + + import os + import click + import ConfigParser + + APP_NAME = 'My Application' + + def read_config(): + cfg = os.path.join(click.get_app_dir(APP_NAME), 'config.ini') + parser = ConfigParser.RawConfigParser() + parser.read([cfg]) + rv = {} + for section in parser.sections(): + for key, value in parser.items(section): + rv['%s.%s' % (section, key)] = value + return rv + + +Showing Progress Bars +--------------------- + +.. versionadded:: 2.0 + +Sometimes, you have command line scripts that need to process a lot of data, +but you want to quickly show the user some progress about how long that +will take. Click supports simple progress bar rendering for that through +the :func:`progressbar` function. + +The basic usage is very simple: the idea is that you have an iterable that +you want to operate on. For each item in the iterable it might take some +time to do processing. So say you have a loop like this:: + + for user in all_the_users_to_process: + modify_the_user(user) + +To hook this up with an automatically updating progress bar, all you need +to do is to change the code to this:: + + import click + + with click.progressbar(all_the_users_to_process) as bar: + for user in bar: + modify_the_user(user) + +Click will then automatically print a progress bar to the terminal and +calculate the remaining time for you. The calculation of remaining time +requires that the iterable has a length. If it does not have a length +but you know the length, you can explicitly provide it:: + + with click.progressbar(all_the_users_to_process, + length=number_of_users) as bar: + for user in bar: + modify_the_user(user) + +Another useful feature is to associate a label with the progress bar which +will be shown preceding the progress bar:: + + with click.progressbar(all_the_users_to_process, + label='Modifying user accounts', + length=number_of_users) as bar: + for user in bar: + modify_the_user(user) + +Sometimes, one may need to iterate over an external iterator, and advance the +progress bar irregularly. To do so, you need to specify the length (and no +iterable), and use the update method on the context return value instead of +iterating directly over it:: + + with click.progressbar(length=total_size, + label='Unzipping archive') as bar: + for archive in zip_file: + archive.extract() + bar.update(archive.size) diff -Nru click-0.4.43+16.04.20160203/docs/why.rst click-6.7/docs/why.rst --- click-0.4.43+16.04.20160203/docs/why.rst 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/why.rst 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,152 @@ +Why Click? +========== + +There are so many libraries out there for writing command line utilities; +why does Click exist? + +This question is easy to answer: because there is not a single command +line utility for Python out there which ticks the following boxes: + +* is lazily composable without restrictions +* fully follows the Unix command line conventions +* supports loading values from environment variables out of the box +* supports for prompting of custom values +* is fully nestable and composable +* works the same in Python 2 and 3 +* supports file handling out of the box +* comes with useful common helpers (getting terminal dimensions, + ANSI colors, fetching direct keyboard input, screen clearing, + finding config paths, launching apps and editors, etc.) + +There are many alternatives to Click and you can have a look at them if +you enjoy them better. The obvious ones are ``optparse`` and ``argparse`` +from the standard library. + +Click is actually implemented as a wrapper around a mild fork of +``optparse`` and does not implement any parsing itself. The reason it's +not based on ``argparse`` is that ``argparse`` does not allow proper +nesting of commands by design and has some deficiencies when it comes to +POSIX compliant argument handling. + +Click is designed to be fun to work with and at the same time not stand in +your way. It's not overly flexible either. Currently, for instance, it +does not allow you to customize the help pages too much. This is intentional +because Click is designed to allow you to nest command line utilities. The +idea is that you can have a system that works together with another system by +tacking two Click instances together and they will continue working as they +should. + +Too much customizability would break this promise. + +Click was written to support the `Flask `_ +microframework ecosystem because no tool could provide it with the +functionality it needed. + +To get an understanding of what Click is all about, I strongly recommend +looking at the :ref:`complex-guide` chapter to see what it's useful for. + +Why not Argparse? +----------------- + +Click is internally based on optparse instead of argparse. This however +is an implementation detail that a user does not have to be concerned +with. The reason however Click is not using argparse is that it has some +problematic behaviors that make handling arbitrary command line interfaces +hard: + +* argparse has built-in magic behavior to guess if something is an + argument or an option. This becomes a problem when dealing with + incomplete command lines as it's not possible to know without having a + full understanding of the command line how the parser is going to + behave. This goes against Click's ambitions of dispatching to + subparsers. +* argparse currently does not support disabling of interspersed + arguments. Without this feature it's not possible to safely implement + Click's nested parsing nature. + +Why not Docopt etc.? +-------------------- + +Docopt and many tools like it are cool in how they work, but very few of +these tools deal with nesting of commands and composability in a way like +Click. To the best of the developer's knowledge, Click is the first +Python library that aims to create a level of composability of applications +that goes beyond what the system itself supports. + +Docopt, for instance, acts by parsing your help pages and then parsing +according to those rules. The side effect of this is that docopt is quite +rigid in how it handles the command line interface. The upside of docopt +is that it gives you strong control over your help page; the downside is +that due to this it cannot rewrap your output for the current terminal +width and it makes translations hard. On top of that docopt is restricted +to basic parsing. It does not handle argument dispatching and callback +invocation or types. This means there is a lot of code that needs to be +written in addition to the basic help page to handle the parsing results. + +Most of all, however, it makes composability hard. While docopt does +support dispatching to subcommands, it for instance does not directly +support any kind of automatic subcommand enumeration based on what's +available or it does not enforce subcommands to work in a consistent way. + +This is fine, but it's different from how Click wants to work. Click aims +to support fully composable command line user interfaces by doing the +following: + +- Click does not just parse, it also dispatches to the appropriate code. +- Click has a strong concept of an invocation context that allows + subcommands to respond to data from the parent command. +- Click has strong information available for all parameters and commands + so that it can generate unified help pages for the full CLI and to + assist the user in converting the input data as necessary. +- Click has a strong understanding of what types are and can give the user + consistent error messages if something goes wrong. A subcommand + written by a different developer will not suddenly die with a + different error messsage because it's manually handled. +- Click has enough meta information available for its whole program + that it can evolve over time to improve the user experience without + forcing developers to adjust their programs. For instance, if Click + decides to change how help pages are formatted, all Click programs + will automatically benefit from this. + +The aim of Click is to make composable systems, whereas the aim of docopt +is to build the most beautiful and hand-crafted command line interfaces. +These two goals conflict with one another in subtle ways. Click +actively prevents people from implementing certain patterns in order to +achieve unified command line interfaces. You have very little input on +reformatting your help pages for instance. + + +Why Hardcoded Behaviors? +------------------------ + +The other question is why Click goes away from optparse and hardcodes +certain behaviors instead of staying configurable. There are multiple +reasons for this. The biggest one is that too much configurability makes +it hard to achieve a consistent command line experience. + +The best example for this is optparse's ``callback`` functionality for +accepting arbitrary number of arguments. Due to syntactical ambiguities +on the command line, there is no way to implement fully variadic arguments. +There are always tradeoffs that need to be made and in case of +``argparse`` these tradeoffs have been critical enough, that a system like +Click cannot even be implemented on top of it. + +In this particular case, Click attempts to stay with a handful of accepted +paradigms for building command line interfaces that can be well documented +and tested. + + +Why No Auto Correction? +----------------------- + +The question came up why Click does not auto correct parameters given that +even optparse and argparse support automatic expansion of long arguments. +The reason for this is that it's a liability for backwards compatibility. +If people start relying on automatically modified parameters and someone +adds a new parameter in the future, the script might stop working. These +kinds of problems are hard to find so Click does not attempt to be magical +about this. + +This sort of behavior however can be implemented on a higher level to +support things such as explicit aliases. For more information see +:ref:`aliases`. diff -Nru click-0.4.43+16.04.20160203/docs/wincmd.rst click-6.7/docs/wincmd.rst --- click-0.4.43+16.04.20160203/docs/wincmd.rst 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/docs/wincmd.rst 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,73 @@ +Windows Console Notes +===================== + +.. versionadded:: 6.0 + +Until Click 6.0 there are various bugs and limitations with using Click on +a Windows console. Most notably the decoding of command line arguments +was performed with the wrong encoding on Python 2 and on all versions of +Python output of unicode characters was impossible. Starting with Click +6.0 we now emulate output streams on Windows to support unicode to the +Windows console through separate APIs and we perform different decoding of +parameters. + +Here is a brief overview of how this works and what it means to you. + +Unicode Arguments +----------------- + +Click internally is generally based on the concept that any argument can +come in as either byte string or unicode string and conversion is +performed to the type expected value as late as possible. This has some +advantages as it allows us to accept the data in the most appropriate form +for the operating system and Python version. + +For instance paths are left as bytes on Python 2 unless you explicitly +tell it otherwise. + +This caused some problems on Windows where initially the wrong encoding +was used and garbage ended up in your input data. We not only fixed the +encoding part, but we also now extract unicode parameters from `sys.argv`. + +This means that on Python 2 under Windows, the arguments processed will +*most likely* be of unicode nature and not bytes. This was something that +previously did not really happen unless you explicitly passed in unicode +parameters so your custom types need to be aware of this. + +There is also another limitation with this: if `sys.argv` was modified +prior to invoking a click handler, we have to fall back to the regular +byte input in which case not all unicode values are available but only a +subset of the codepage used for parameters. + +Unicode Output and Input +------------------------ + +Unicode output and input on Windows is implemented through the concept of +a dispatching text stream. What this means is that when click first needs +a text output (or input) stream on windows it goes through a few checks to +figure out of a windows console is connected or not. If no Windows +console is present then the text output stream is returned as such and the +encoding for that stream is set to ``utf-8`` like on all platforms. + +However if a console is connected the stream will instead be emulated and +use the cmd.exe unicode APIs to output text information. In this case the +stream will also use ``utf-16-le`` as internal encoding. However there is +some hackery going on that the underlying raw IO buffer is still bypassing +the unicode APIs and byte output through an indirection is still possible. + +This hackery is used on both Python 2 and Python 3 as neither version of +Python has native support for cmd.exe with unicode characters. There are +some limitations you need to be aware of: + +* this unicode support is limited to ``click.echo``, ``click.prompt`` as + well as ``click.get_text_stream``. +* depending on if unicode values or byte strings are passed the control + flow goes completely different places internally which can have some + odd artifacts if data partially ends up being buffered. Click + attempts to protect against that by manually always flushing but if + you are mixing and matching different string types to ``stdout`` or + ``stderr`` you will need to manually flush. + +Another important thing to note is that the Windows console's default +fonts do not support a lot of characters which means that you are mostly +limited to international letters but no emojis or special characters. diff -Nru click-0.4.43+16.04.20160203/examples/aliases/aliases.ini click-6.7/examples/aliases/aliases.ini --- click-0.4.43+16.04.20160203/examples/aliases/aliases.ini 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/aliases/aliases.ini 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,2 @@ +[aliases] +ci=commit diff -Nru click-0.4.43+16.04.20160203/examples/aliases/aliases.py click-6.7/examples/aliases/aliases.py --- click-0.4.43+16.04.20160203/examples/aliases/aliases.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/aliases/aliases.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,111 @@ +import os +import click + +try: + import ConfigParser as configparser +except ImportError: + import configparser + + +class Config(object): + """The config in this example only holds aliases.""" + + def __init__(self): + self.path = os.getcwd() + self.aliases = {} + + def read_config(self, filename): + parser = configparser.RawConfigParser() + parser.read([filename]) + try: + self.aliases.update(parser.items('aliases')) + except configparser.NoSectionError: + pass + + +pass_config = click.make_pass_decorator(Config, ensure=True) + + +class AliasedGroup(click.Group): + """This subclass of a group supports looking up aliases in a config + file and with a bit of magic. + """ + + def get_command(self, ctx, cmd_name): + # Step one: bulitin commands as normal + rv = click.Group.get_command(self, ctx, cmd_name) + if rv is not None: + return rv + + # Step two: find the config object and ensure it's there. This + # will create the config object is missing. + cfg = ctx.ensure_object(Config) + + # Step three: lookup an explicit command aliase in the config + if cmd_name in cfg.aliases: + actual_cmd = cfg.aliases[cmd_name] + return click.Group.get_command(self, ctx, actual_cmd) + + # Alternative option: if we did not find an explicit alias we + # allow automatic abbreviation of the command. "status" for + # instance will match "st". We only allow that however if + # there is only one command. + matches = [x for x in self.list_commands(ctx) + if x.lower().startswith(cmd_name.lower())] + if not matches: + return None + elif len(matches) == 1: + return click.Group.get_command(self, ctx, matches[0]) + ctx.fail('Too many matches: %s' % ', '.join(sorted(matches))) + + +def read_config(ctx, param, value): + """Callback that is used whenever --config is passed. We use this to + always load the correct config. This means that the config is loaded + even if the group itself never executes so our aliases stay always + available. + """ + cfg = ctx.ensure_object(Config) + if value is None: + value = os.path.join(os.path.dirname(__file__), 'aliases.ini') + cfg.read_config(value) + return value + + +@click.command(cls=AliasedGroup) +@click.option('--config', type=click.Path(exists=True, dir_okay=False), + callback=read_config, expose_value=False, + help='The config file to use instead of the default.') +def cli(): + """An example application that supports aliases.""" + + +@cli.command() +def push(): + """Pushes changes.""" + click.echo('Push') + + +@cli.command() +def pull(): + """Pulls changes.""" + click.echo('Pull') + + +@cli.command() +def clone(): + """Clones a repository.""" + click.echo('Clone') + + +@cli.command() +def commit(): + """Commits pending changes.""" + click.echo('Commit') + + +@cli.command() +@pass_config +def status(config): + """Shows the status.""" + click.echo('Status for %s' % config.path) diff -Nru click-0.4.43+16.04.20160203/examples/aliases/README click-6.7/examples/aliases/README --- click-0.4.43+16.04.20160203/examples/aliases/README 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/aliases/README 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,17 @@ +$ aliases_ + + aliases is a fairly advanced example that shows how + to implement command aliases with Click. It uses a + subclass of the default group to customize how commands + are located. + + It supports both aliases read from a config file as well + as automatic abbreviations. + + The aliases from the config are read from the aliases.ini + file. Try `aliases st` and `aliases ci`! + +Usage: + + $ pip install --editable . + $ aliases --help diff -Nru click-0.4.43+16.04.20160203/examples/aliases/setup.py click-6.7/examples/aliases/setup.py --- click-0.4.43+16.04.20160203/examples/aliases/setup.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/aliases/setup.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,15 @@ +from setuptools import setup + +setup( + name='click-example-aliases', + version='1.0', + py_modules=['aliases'], + include_package_data=True, + install_requires=[ + 'click', + ], + entry_points=''' + [console_scripts] + aliases=aliases:cli + ''', +) diff -Nru click-0.4.43+16.04.20160203/examples/colors/colors.py click-6.7/examples/colors/colors.py --- click-0.4.43+16.04.20160203/examples/colors/colors.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/colors/colors.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,26 @@ +import click + + +all_colors = 'black', 'red', 'green', 'yellow', 'blue', 'magenta', \ + 'cyan', 'white' + + +@click.command() +def cli(): + """This script prints some colors. If colorama is installed this will + also work on Windows. It will also automatically remove all ANSI + styles if data is piped into a file. + + Give it a try! + """ + for color in all_colors: + click.echo(click.style('I am colored %s' % color, fg=color)) + for color in all_colors: + click.echo(click.style('I am colored %s and bold' % color, + fg=color, bold=True)) + for color in all_colors: + click.echo(click.style('I am reverse colored %s' % color, fg=color, + reverse=True)) + + click.echo(click.style('I am blinking', blink=True)) + click.echo(click.style('I am underlined', underline=True)) diff -Nru click-0.4.43+16.04.20160203/examples/colors/README click-6.7/examples/colors/README --- click-0.4.43+16.04.20160203/examples/colors/README 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/colors/README 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,11 @@ +$ colors_ + + colors is a simple example that shows how you can + colorize text. + + For this to work on Windows, colorama is required. + +Usage: + + $ pip install --editable . + $ colors diff -Nru click-0.4.43+16.04.20160203/examples/colors/setup.py click-6.7/examples/colors/setup.py --- click-0.4.43+16.04.20160203/examples/colors/setup.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/colors/setup.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,17 @@ +from setuptools import setup + +setup( + name='click-example-colors', + version='1.0', + py_modules=['colors'], + include_package_data=True, + install_requires=[ + 'click', + # Colorama is only required for Windows. + 'colorama', + ], + entry_points=''' + [console_scripts] + colors=colors:cli + ''', +) diff -Nru click-0.4.43+16.04.20160203/examples/complex/complex/cli.py click-6.7/examples/complex/complex/cli.py --- click-0.4.43+16.04.20160203/examples/complex/complex/cli.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/complex/complex/cli.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,65 @@ +import os +import sys +import click + + +CONTEXT_SETTINGS = dict(auto_envvar_prefix='COMPLEX') + + +class Context(object): + + def __init__(self): + self.verbose = False + self.home = os.getcwd() + + def log(self, msg, *args): + """Logs a message to stderr.""" + if args: + msg %= args + click.echo(msg, file=sys.stderr) + + def vlog(self, msg, *args): + """Logs a message to stderr only if verbose is enabled.""" + if self.verbose: + self.log(msg, *args) + + +pass_context = click.make_pass_decorator(Context, ensure=True) +cmd_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), + 'commands')) + + +class ComplexCLI(click.MultiCommand): + + def list_commands(self, ctx): + rv = [] + for filename in os.listdir(cmd_folder): + if filename.endswith('.py') and \ + filename.startswith('cmd_'): + rv.append(filename[4:-3]) + rv.sort() + return rv + + def get_command(self, ctx, name): + try: + if sys.version_info[0] == 2: + name = name.encode('ascii', 'replace') + mod = __import__('complex.commands.cmd_' + name, + None, None, ['cli']) + except ImportError: + return + return mod.cli + + +@click.command(cls=ComplexCLI, context_settings=CONTEXT_SETTINGS) +@click.option('--home', type=click.Path(exists=True, file_okay=False, + resolve_path=True), + help='Changes the folder to operate on.') +@click.option('-v', '--verbose', is_flag=True, + help='Enables verbose mode.') +@pass_context +def cli(ctx, verbose, home): + """A complex command line interface.""" + ctx.verbose = verbose + if home is not None: + ctx.home = home diff -Nru click-0.4.43+16.04.20160203/examples/complex/complex/commands/cmd_init.py click-6.7/examples/complex/complex/commands/cmd_init.py --- click-0.4.43+16.04.20160203/examples/complex/complex/commands/cmd_init.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/complex/complex/commands/cmd_init.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,13 @@ +import click +from complex.cli import pass_context + + +@click.command('init', short_help='Initializes a repo.') +@click.argument('path', required=False, type=click.Path(resolve_path=True)) +@pass_context +def cli(ctx, path): + """Initializes a repository.""" + if path is None: + path = ctx.home + ctx.log('Initialized the repository in %s', + click.format_filename(path)) diff -Nru click-0.4.43+16.04.20160203/examples/complex/complex/commands/cmd_status.py click-6.7/examples/complex/complex/commands/cmd_status.py --- click-0.4.43+16.04.20160203/examples/complex/complex/commands/cmd_status.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/complex/complex/commands/cmd_status.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,10 @@ +import click +from complex.cli import pass_context + + +@click.command('status', short_help='Shows file changes.') +@pass_context +def cli(ctx): + """Shows file changes in the current working directory.""" + ctx.log('Changed files: none') + ctx.vlog('bla bla bla, debug info') diff -Nru click-0.4.43+16.04.20160203/examples/complex/README click-6.7/examples/complex/README --- click-0.4.43+16.04.20160203/examples/complex/README 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/complex/README 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,16 @@ +$ complex_ + + complex is an example of building very complex cli + applications that load subcommands dynamically from + a plugin folder and other things. + + All the commands are implemented as plugins in the + `complex.commands` package. If a python module is + placed named "cmd_foo" it will show up as "foo" + command and the `cli` object within it will be + loaded as nested Click command. + +Usage: + + $ pip install --editable . + $ complex --help diff -Nru click-0.4.43+16.04.20160203/examples/complex/setup.py click-6.7/examples/complex/setup.py --- click-0.4.43+16.04.20160203/examples/complex/setup.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/complex/setup.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,15 @@ +from setuptools import setup + +setup( + name='click-example-complex', + version='1.0', + packages=['complex', 'complex.commands'], + include_package_data=True, + install_requires=[ + 'click', + ], + entry_points=''' + [console_scripts] + complex=complex.cli:cli + ''', +) Binary files /tmp/tmp5CGTrm/S7dHEMVmjm/click-0.4.43+16.04.20160203/examples/imagepipe/example01.jpg and /tmp/tmp5CGTrm/FrCzimMInt/click-6.7/examples/imagepipe/example01.jpg differ Binary files /tmp/tmp5CGTrm/S7dHEMVmjm/click-0.4.43+16.04.20160203/examples/imagepipe/example02.jpg and /tmp/tmp5CGTrm/FrCzimMInt/click-6.7/examples/imagepipe/example02.jpg differ diff -Nru click-0.4.43+16.04.20160203/examples/imagepipe/.gitignore click-6.7/examples/imagepipe/.gitignore --- click-0.4.43+16.04.20160203/examples/imagepipe/.gitignore 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/imagepipe/.gitignore 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1 @@ +processed-* diff -Nru click-0.4.43+16.04.20160203/examples/imagepipe/imagepipe.py click-6.7/examples/imagepipe/imagepipe.py --- click-0.4.43+16.04.20160203/examples/imagepipe/imagepipe.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/imagepipe/imagepipe.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,266 @@ +import click +from functools import update_wrapper +from PIL import Image, ImageFilter, ImageEnhance + + +@click.group(chain=True) +def cli(): + """This script processes a bunch of images through pillow in a unix + pipe. One commands feeds into the next. + + Example: + + \b + imagepipe open -i example01.jpg resize -w 128 display + imagepipe open -i example02.jpg blur save + """ + + +@cli.resultcallback() +def process_commands(processors): + """This result callback is invoked with an iterable of all the chained + subcommands. As in this example each subcommand returns a function + we can chain them together to feed one into the other, similar to how + a pipe on unix works. + """ + # Start with an empty iterable. + stream = () + + # Pipe it through all stream processors. + for processor in processors: + stream = processor(stream) + + # Evaluate the stream and throw away the items. + for _ in stream: + pass + + +def processor(f): + """Helper decorator to rewrite a function so that it returns another + function from it. + """ + def new_func(*args, **kwargs): + def processor(stream): + return f(stream, *args, **kwargs) + return processor + return update_wrapper(new_func, f) + + +def generator(f): + """Similar to the :func:`processor` but passes through old values + unchanged and does not pass through the values as parameter. + """ + @processor + def new_func(stream, *args, **kwargs): + for item in stream: + yield item + for item in f(*args, **kwargs): + yield item + return update_wrapper(new_func, f) + + +def copy_filename(new, old): + new.filename = old.filename + return new + + +@cli.command('open') +@click.option('-i', '--image', 'images', type=click.Path(), + multiple=True, help='The image file to open.') +@generator +def open_cmd(images): + """Loads one or multiple images for processing. The input parameter + can be specified multiple times to load more than one image. + """ + for image in images: + try: + click.echo('Opening "%s"' % image) + if image == '-': + img = Image.open(click.get_binary_stdin()) + img.filename = '-' + else: + img = Image.open(image) + yield img + except Exception as e: + click.echo('Could not open image "%s": %s' % (image, e), err=True) + + +@cli.command('save') +@click.option('--filename', default='processed-%04d.png', type=click.Path(), + help='The format for the filename.', + show_default=True) +@processor +def save_cmd(images, filename): + """Saves all processed images to a series of files.""" + for idx, image in enumerate(images): + try: + fn = filename % (idx + 1) + click.echo('Saving "%s" as "%s"' % (image.filename, fn)) + yield image.save(fn) + except Exception as e: + click.echo('Could not save image "%s": %s' % + (image.filename, e), err=True) + + +@cli.command('display') +@processor +def display_cmd(images): + """Opens all images in an image viewer.""" + for image in images: + click.echo('Displaying "%s"' % image.filename) + image.show() + yield image + + +@cli.command('resize') +@click.option('-w', '--width', type=int, help='The new width of the image.') +@click.option('-h', '--height', type=int, help='The new height of the image.') +@processor +def resize_cmd(images, width, height): + """Resizes an image by fitting it into the box without changing + the aspect ratio. + """ + for image in images: + w, h = (width or image.size[0], height or image.size[1]) + click.echo('Resizing "%s" to %dx%d' % (image.filename, w, h)) + image.thumbnail((w, h)) + yield image + + +@cli.command('crop') +@click.option('-b', '--border', type=int, help='Crop the image from all ' + 'sides by this amount.') +@processor +def crop_cmd(images, border): + """Crops an image from all edges.""" + for image in images: + box = [0, 0, image.size[0], image.size[1]] + + if border is not None: + for idx, val in enumerate(box): + box[idx] = max(0, val - border) + click.echo('Cropping "%s" by %dpx' % (image.filename, border)) + yield copy_filename(image.crop(box), image) + else: + yield image + + +def convert_rotation(ctx, param, value): + if value is None: + return + value = value.lower() + if value in ('90', 'r', 'right'): + return (Image.ROTATE_90, 90) + if value in ('180', '-180'): + return (Image.ROTATE_180, 180) + if value in ('-90', '270', 'l', 'left'): + return (Image.ROTATE_270, 270) + raise click.BadParameter('invalid rotation "%s"' % value) + + +def convert_flip(ctx, param, value): + if value is None: + return + value = value.lower() + if value in ('lr', 'leftright'): + return (Image.FLIP_LEFT_RIGHT, 'left to right') + if value in ('tb', 'topbottom', 'upsidedown', 'ud'): + return (Image.FLIP_LEFT_RIGHT, 'top to bottom') + raise click.BadParameter('invalid flip "%s"' % value) + + +@cli.command('transpose') +@click.option('-r', '--rotate', callback=convert_rotation, + help='Rotates the image (in degrees)') +@click.option('-f', '--flip', callback=convert_flip, + help='Flips the image [LR / TB]') +@processor +def transpose_cmd(images, rotate, flip): + """Transposes an image by either rotating or flipping it.""" + for image in images: + if rotate is not None: + mode, degrees = rotate + click.echo('Rotate "%s" by %ddeg' % (image.filename, degrees)) + image = copy_filename(image.transpose(mode), image) + if flip is not None: + mode, direction = flip + click.echo('Flip "%s" %s' % (image.filename, direction)) + image = copy_filename(image.transpose(mode), image) + yield image + + +@cli.command('blur') +@click.option('-r', '--radius', default=2, show_default=True, + help='The blur radius.') +@processor +def blur_cmd(images, radius): + """Applies gaussian blur.""" + blur = ImageFilter.GaussianBlur(radius) + for image in images: + click.echo('Blurring "%s" by %dpx' % (image.filename, radius)) + yield copy_filename(image.filter(blur), image) + + +@cli.command('smoothen') +@click.option('-i', '--iterations', default=1, show_default=True, + help='How many iterations of the smoothen filter to run.') +@processor +def smoothen_cmd(images, iterations): + """Applies a smoothening filter.""" + for image in images: + click.echo('Smoothening "%s" %d time%s' % + (image.filename, iterations, iterations != 1 and 's' or '',)) + for x in xrange(iterations): + image = copy_filename(image.filter(ImageFilter.BLUR), image) + yield image + + +@cli.command('emboss') +@processor +def emboss_cmd(images): + """Embosses an image.""" + for image in images: + click.echo('Embossing "%s"' % image.filename) + yield copy_filename(image.filter(ImageFilter.EMBOSS), image) + + +@cli.command('sharpen') +@click.option('-f', '--factor', default=2.0, + help='Sharpens the image.', show_default=True) +@processor +def sharpen_cmd(images, factor): + """Sharpens an image.""" + for image in images: + click.echo('Sharpen "%s" by %f' % (image.filename, factor)) + enhancer = ImageEnhance.Sharpness(image) + yield copy_filename(enhancer.enhance(max(1.0, factor)), image) + + +@cli.command('paste') +@click.option('-l', '--left', default=0, help='Offset from left.') +@click.option('-r', '--right', default=0, help='Offset from right.') +@processor +def paste_cmd(images, left, right): + """Pastes the second image on the first image and leaves the rest + unchanged. + """ + imageiter = iter(images) + image = next(imageiter, None) + to_paste = next(imageiter, None) + + if to_paste is None: + if image is not None: + yield image + return + + click.echo('Paste "%s" on "%s"' % + (to_paste.filename, image.filename)) + mask = None + if to_paste.mode == 'RGBA' or 'transparency' in to_paste.info: + mask = to_paste + image.paste(to_paste, (left, right), mask) + image.filename += '+' + to_paste.filename + yield image + + for image in imageiter: + yield image diff -Nru click-0.4.43+16.04.20160203/examples/imagepipe/README click-6.7/examples/imagepipe/README --- click-0.4.43+16.04.20160203/examples/imagepipe/README 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/imagepipe/README 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,13 @@ +$ imagepipe_ + + imagepipe is an example application that implements some + multi commands that chain image processing instructions + together. + + This requires pillow. + +Usage: + + $ pip install --editable . + $ imagepipe open -i example01.jpg resize -w 128 display + $ imagepipe open -i example02.jpg blur save diff -Nru click-0.4.43+16.04.20160203/examples/imagepipe/setup.py click-6.7/examples/imagepipe/setup.py --- click-0.4.43+16.04.20160203/examples/imagepipe/setup.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/imagepipe/setup.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,16 @@ +from setuptools import setup + +setup( + name='click-example-imagepipe', + version='1.0', + py_modules=['imagepipe'], + include_package_data=True, + install_requires=[ + 'click', + 'pillow', + ], + entry_points=''' + [console_scripts] + imagepipe=imagepipe:cli + ''', +) diff -Nru click-0.4.43+16.04.20160203/examples/inout/inout.py click-6.7/examples/inout/inout.py --- click-0.4.43+16.04.20160203/examples/inout/inout.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/inout/inout.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,30 @@ +import click + + +@click.command() +@click.argument('input', type=click.File('rb'), nargs=-1) +@click.argument('output', type=click.File('wb')) +def cli(input, output): + """This script works similar to the Unix `cat` command but it writes + into a specific file (which could be the standard output as denoted by + the ``-`` sign). + + \b + Copy stdin to stdout: + inout - - + + \b + Copy foo.txt and bar.txt to stdout: + inout foo.txt bar.txt - + + \b + Write stdin into the file foo.txt + inout - foo.txt + """ + for f in input: + while True: + chunk = f.read(1024) + if not chunk: + break + output.write(chunk) + output.flush() diff -Nru click-0.4.43+16.04.20160203/examples/inout/README click-6.7/examples/inout/README --- click-0.4.43+16.04.20160203/examples/inout/README 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/inout/README 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,10 @@ +$ inout_ + + inout is a simple example of an application that + can read from files and write to files but also + accept input from stdin or write to stdout. + +Usage: + + $ pip install --editable . + $ inout input_file.txt output_file.txt diff -Nru click-0.4.43+16.04.20160203/examples/inout/setup.py click-6.7/examples/inout/setup.py --- click-0.4.43+16.04.20160203/examples/inout/setup.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/inout/setup.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,15 @@ +from setuptools import setup + +setup( + name='click-example-inout', + version='0.1', + py_modules=['inout'], + include_package_data=True, + install_requires=[ + 'click', + ], + entry_points=''' + [console_scripts] + inout=inout:cli + ''', +) diff -Nru click-0.4.43+16.04.20160203/examples/naval/naval.py click-6.7/examples/naval/naval.py --- click-0.4.43+16.04.20160203/examples/naval/naval.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/naval/naval.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,70 @@ +import click + + +@click.group() +@click.version_option() +def cli(): + """Naval Fate. + + This is the docopt example adopted to Click but with some actual + commands implemented and not just the empty parsing which really + is not all that interesting. + """ + + +@cli.group() +def ship(): + """Manages ships.""" + + +@ship.command('new') +@click.argument('name') +def ship_new(name): + """Creates a new ship.""" + click.echo('Created ship %s' % name) + + +@ship.command('move') +@click.argument('ship') +@click.argument('x', type=float) +@click.argument('y', type=float) +@click.option('--speed', metavar='KN', default=10, + help='Speed in knots.') +def ship_move(ship, x, y, speed): + """Moves SHIP to the new location X,Y.""" + click.echo('Moving ship %s to %s,%s with speed %s' % (ship, x, y, speed)) + + +@ship.command('shoot') +@click.argument('ship') +@click.argument('x', type=float) +@click.argument('y', type=float) +def ship_shoot(ship, x, y): + """Makes SHIP fire to X,Y.""" + click.echo('Ship %s fires to %s,%s' % (ship, x, y)) + + +@cli.group('mine') +def mine(): + """Manages mines.""" + + +@mine.command('set') +@click.argument('x', type=float) +@click.argument('y', type=float) +@click.option('ty', '--moored', flag_value='moored', + default=True, + help='Moored (anchored) mine. Default.') +@click.option('ty', '--drifting', flag_value='drifting', + help='Drifting mine.') +def mine_set(x, y, ty): + """Sets a mine at a specific coordinate.""" + click.echo('Set %s mine at %s,%s' % (ty, x, y)) + + +@mine.command('remove') +@click.argument('x', type=float) +@click.argument('y', type=float) +def mine_remove(x, y): + """Removes a mine at a specific coordinate.""" + click.echo('Removed mine at %s,%s' % (x, y)) diff -Nru click-0.4.43+16.04.20160203/examples/naval/README click-6.7/examples/naval/README --- click-0.4.43+16.04.20160203/examples/naval/README 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/naval/README 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,14 @@ +$ naval_ + + naval is a simple example of an application that + is ported from the docopt example of the same name. + + Unlike the original this one also runs some code and + prints messages and it's command line interface was + changed slightly to make more sense with established + POSIX semantics. + +Usage: + + $ pip install --editable . + $ naval --help diff -Nru click-0.4.43+16.04.20160203/examples/naval/setup.py click-6.7/examples/naval/setup.py --- click-0.4.43+16.04.20160203/examples/naval/setup.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/naval/setup.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,15 @@ +from setuptools import setup + +setup( + name='click-example-naval', + version='2.0', + py_modules=['naval'], + include_package_data=True, + install_requires=[ + 'click', + ], + entry_points=''' + [console_scripts] + naval=naval:cli + ''', +) diff -Nru click-0.4.43+16.04.20160203/examples/README click-6.7/examples/README --- click-0.4.43+16.04.20160203/examples/README 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/README 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,12 @@ +Click Examples + + This folder contains various Click examples. Note that + all of these are not runnable by themselves but should be + installed into a virtualenv. + + This is done this way so that scripts also properly work + on Windows and in virtualenvs without accidentally executing + through the wrong interpreter. + + For more information about this see the documentation: + http://click.pocoo.org/setuptools/ diff -Nru click-0.4.43+16.04.20160203/examples/repo/README click-6.7/examples/repo/README --- click-0.4.43+16.04.20160203/examples/repo/README 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/repo/README 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,9 @@ +$ repo_ + + repo is a simple example of an application that looks + and works similar to hg or git. + +Usage: + + $ pip install --editable . + $ repo --help diff -Nru click-0.4.43+16.04.20160203/examples/repo/repo.py click-6.7/examples/repo/repo.py --- click-0.4.43+16.04.20160203/examples/repo/repo.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/repo/repo.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,151 @@ +import os +import sys +import posixpath + +import click + + +class Repo(object): + + def __init__(self, home): + self.home = home + self.config = {} + self.verbose = False + + def set_config(self, key, value): + self.config[key] = value + if self.verbose: + click.echo(' config[%s] = %s' % (key, value), file=sys.stderr) + + def __repr__(self): + return '' % self.home + + +pass_repo = click.make_pass_decorator(Repo) + + +@click.group() +@click.option('--repo-home', envvar='REPO_HOME', default='.repo', + metavar='PATH', help='Changes the repository folder location.') +@click.option('--config', nargs=2, multiple=True, + metavar='KEY VALUE', help='Overrides a config key/value pair.') +@click.option('--verbose', '-v', is_flag=True, + help='Enables verbose mode.') +@click.version_option('1.0') +@click.pass_context +def cli(ctx, repo_home, config, verbose): + """Repo is a command line tool that showcases how to build complex + command line interfaces with Click. + + This tool is supposed to look like a distributed version control + system to show how something like this can be structured. + """ + # Create a repo object and remember it as as the context object. From + # this point onwards other commands can refer to it by using the + # @pass_repo decorator. + ctx.obj = Repo(os.path.abspath(repo_home)) + ctx.obj.verbose = verbose + for key, value in config: + ctx.obj.set_config(key, value) + + +@cli.command() +@click.argument('src') +@click.argument('dest', required=False) +@click.option('--shallow/--deep', default=False, + help='Makes a checkout shallow or deep. Deep by default.') +@click.option('--rev', '-r', default='HEAD', + help='Clone a specific revision instead of HEAD.') +@pass_repo +def clone(repo, src, dest, shallow, rev): + """Clones a repository. + + This will clone the repository at SRC into the folder DEST. If DEST + is not provided this will automatically use the last path component + of SRC and create that folder. + """ + if dest is None: + dest = posixpath.split(src)[-1] or '.' + click.echo('Cloning repo %s to %s' % (src, os.path.abspath(dest))) + repo.home = dest + if shallow: + click.echo('Making shallow checkout') + click.echo('Checking out revision %s' % rev) + + +@cli.command() +@click.confirmation_option() +@pass_repo +def delete(repo): + """Deletes a repository. + + This will throw away the current repository. + """ + click.echo('Destroying repo %s' % repo.home) + click.echo('Deleted!') + + +@cli.command() +@click.option('--username', prompt=True, + help='The developer\'s shown username.') +@click.option('--email', prompt='E-Mail', + help='The developer\'s email address') +@click.password_option(help='The login password.') +@pass_repo +def setuser(repo, username, email, password): + """Sets the user credentials. + + This will override the current user config. + """ + repo.set_config('username', username) + repo.set_config('email', email) + repo.set_config('password', '*' * len(password)) + click.echo('Changed credentials.') + + +@cli.command() +@click.option('--message', '-m', multiple=True, + help='The commit message. If provided multiple times each ' + 'argument gets converted into a new line.') +@click.argument('files', nargs=-1, type=click.Path()) +@pass_repo +def commit(repo, files, message): + """Commits outstanding changes. + + Commit changes to the given files into the repository. You will need to + "repo push" to push up your changes to other repositories. + + If a list of files is omitted, all changes reported by "repo status" + will be committed. + """ + if not message: + marker = '# Files to be committed:' + hint = ['', '', marker, '#'] + for file in files: + hint.append('# U %s' % file) + message = click.edit('\n'.join(hint)) + if message is None: + click.echo('Aborted!') + return + msg = message.split(marker)[0].rstrip() + if not msg: + click.echo('Aborted! Empty commit message') + return + else: + msg = '\n'.join(message) + click.echo('Files to be committed: %s' % (files,)) + click.echo('Commit message:\n' + msg) + + +@cli.command(short_help='Copies files.') +@click.option('--force', is_flag=True, + help='forcibly copy over an existing managed file') +@click.argument('src', nargs=-1, type=click.Path()) +@click.argument('dst', type=click.Path()) +@pass_repo +def copy(repo, src, dst, force): + """Copies one or multiple files to a new location. This copies all + files from SRC to DST. + """ + for fn in src: + click.echo('Copy from %s -> %s' % (fn, dst)) diff -Nru click-0.4.43+16.04.20160203/examples/repo/setup.py click-6.7/examples/repo/setup.py --- click-0.4.43+16.04.20160203/examples/repo/setup.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/repo/setup.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,15 @@ +from setuptools import setup + +setup( + name='click-example-repo', + version='0.1', + py_modules=['repo'], + include_package_data=True, + install_requires=[ + 'click', + ], + entry_points=''' + [console_scripts] + repo=repo:cli + ''', +) diff -Nru click-0.4.43+16.04.20160203/examples/termui/README click-6.7/examples/termui/README --- click-0.4.43+16.04.20160203/examples/termui/README 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/termui/README 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,9 @@ +$ termui_ + + termui showcases the different terminal UI helpers that + Click provides. + +Usage: + + $ pip install --editable . + $ termui --help diff -Nru click-0.4.43+16.04.20160203/examples/termui/setup.py click-6.7/examples/termui/setup.py --- click-0.4.43+16.04.20160203/examples/termui/setup.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/termui/setup.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,17 @@ +from setuptools import setup + +setup( + name='click-example-termui', + version='1.0', + py_modules=['termui'], + include_package_data=True, + install_requires=[ + 'click', + # Colorama is only required for Windows. + 'colorama', + ], + entry_points=''' + [console_scripts] + termui=termui:cli + ''', +) diff -Nru click-0.4.43+16.04.20160203/examples/termui/termui.py click-6.7/examples/termui/termui.py --- click-0.4.43+16.04.20160203/examples/termui/termui.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/termui/termui.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,156 @@ +# coding: utf-8 +import click +import math +import time +import random + +try: + range_type = xrange +except NameError: + range_type = range + + +@click.group() +def cli(): + """This script showcases different terminal UI helpers in Click.""" + pass + + +@cli.command() +def colordemo(): + """Demonstrates ANSI color support.""" + for color in 'red', 'green', 'blue': + click.echo(click.style('I am colored %s' % color, fg=color)) + click.echo(click.style('I am background colored %s' % color, bg=color)) + + +@cli.command() +def pager(): + """Demonstrates using the pager.""" + lines = [] + for x in range_type(200): + lines.append('%s. Hello World!' % click.style(str(x), fg='green')) + click.echo_via_pager('\n'.join(lines)) + + +@cli.command() +@click.option('--count', default=8000, type=click.IntRange(1, 100000), + help='The number of items to process.') +def progress(count): + """Demonstrates the progress bar.""" + items = range_type(count) + + def process_slowly(item): + time.sleep(0.002 * random.random()) + + def filter(items): + for item in items: + if random.random() > 0.3: + yield item + + with click.progressbar(items, label='Processing accounts', + fill_char=click.style('#', fg='green')) as bar: + for item in bar: + process_slowly(item) + + def show_item(item): + if item is not None: + return 'Item #%d' % item + + with click.progressbar(filter(items), label='Committing transaction', + fill_char=click.style('#', fg='yellow'), + item_show_func=show_item) as bar: + for item in bar: + process_slowly(item) + + with click.progressbar(length=count, label='Counting', + bar_template='%(label)s %(bar)s | %(info)s', + fill_char=click.style(u'█', fg='cyan'), + empty_char=' ') as bar: + for item in bar: + process_slowly(item) + + with click.progressbar(length=count, width=0, show_percent=False, + show_eta=False, + fill_char=click.style('#', fg='magenta')) as bar: + for item in bar: + process_slowly(item) + + # 'Non-linear progress bar' + steps = [math.exp( x * 1. / 20) - 1 for x in range(20)] + count = int(sum(steps)) + with click.progressbar(length=count, show_percent=False, + label='Slowing progress bar', + fill_char=click.style(u'█', fg='green')) as bar: + for item in steps: + time.sleep(item) + bar.update(item) + + +@cli.command() +@click.argument('url') +def open(url): + """Opens a file or URL In the default application.""" + click.launch(url) + + +@cli.command() +@click.argument('url') +def locate(url): + """Opens a file or URL In the default application.""" + click.launch(url, locate=True) + + +@cli.command() +def edit(): + """Opens an editor with some text in it.""" + MARKER = '# Everything below is ignored\n' + message = click.edit('\n\n' + MARKER) + if message is not None: + msg = message.split(MARKER, 1)[0].rstrip('\n') + if not msg: + click.echo('Empty message!') + else: + click.echo('Message:\n' + msg) + else: + click.echo('You did not enter anything!') + + +@cli.command() +def clear(): + """Clears the entire screen.""" + click.clear() + + +@cli.command() +def pause(): + """Waits for the user to press a button.""" + click.pause() + + +@cli.command() +def menu(): + """Shows a simple menu.""" + menu = 'main' + while 1: + if menu == 'main': + click.echo('Main menu:') + click.echo(' d: debug menu') + click.echo(' q: quit') + char = click.getchar() + if char == 'd': + menu = 'debug' + elif char == 'q': + menu = 'quit' + else: + click.echo('Invalid input') + elif menu == 'debug': + click.echo('Debug menu') + click.echo(' b: back') + char = click.getchar() + if char == 'b': + menu = 'main' + else: + click.echo('Invalid input') + elif menu == 'quit': + return diff -Nru click-0.4.43+16.04.20160203/examples/validation/README click-6.7/examples/validation/README --- click-0.4.43+16.04.20160203/examples/validation/README 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/validation/README 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,12 @@ +$ validation_ + + validation is a simple example of an application that + performs custom validation of parameters in different + ways. + + This example requires Click 2.0 or higher. + +Usage: + + $ pip install --editable . + $ validation --help diff -Nru click-0.4.43+16.04.20160203/examples/validation/setup.py click-6.7/examples/validation/setup.py --- click-0.4.43+16.04.20160203/examples/validation/setup.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/validation/setup.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,15 @@ +from setuptools import setup + +setup( + name='click-example-validation', + version='1.0', + py_modules=['validation'], + include_package_data=True, + install_requires=[ + 'click', + ], + entry_points=''' + [console_scripts] + validation=validation:cli + ''', +) diff -Nru click-0.4.43+16.04.20160203/examples/validation/validation.py click-6.7/examples/validation/validation.py --- click-0.4.43+16.04.20160203/examples/validation/validation.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/examples/validation/validation.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,44 @@ +import click +try: + from urllib import parser as urlparse +except ImportError: + import urlparse + + +def validate_count(ctx, param, value): + if value < 0 or value % 2 != 0: + raise click.BadParameter('Should be a positive, even integer.') + return value + + +class URL(click.ParamType): + name = 'url' + + def convert(self, value, param, ctx): + if not isinstance(value, tuple): + value = urlparse.urlparse(value) + if value.scheme not in ('http', 'https'): + self.fail('invalid URL scheme (%s). Only HTTP URLs are ' + 'allowed' % value.scheme, param, ctx) + return value + + +@click.command() +@click.option('--count', default=2, callback=validate_count, + help='A positive even number.') +@click.option('--foo', help='A mysterious parameter.') +@click.option('--url', help='A URL', type=URL()) +@click.version_option() +def cli(count, foo, url): + """Validation. + + This example validates parameters in different ways. It does it + through callbacks, through a custom type as well as by validating + manually in the function. + """ + if foo is not None and foo != 'wat': + raise click.BadParameter('If a value is provided it needs to be the ' + 'value "wat".', param_hint=['--foo']) + click.echo('count: %s' % count) + click.echo('foo: %s' % foo) + click.echo('url: %s' % repr(url)) diff -Nru click-0.4.43+16.04.20160203/get-version click-6.7/get-version --- click-0.4.43+16.04.20160203/get-version 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/get-version 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ -#! /bin/sh -perl -e '$_ = <>; chomp; s/.*\((.*)\).*/\1/; print; exit' $@ -endif diff -Nru click-0.4.43+16.04.20160203/init/upstart/click-system-hooks.conf.in click-6.7/init/upstart/click-system-hooks.conf.in --- click-0.4.43+16.04.20160203/init/upstart/click-system-hooks.conf.in 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/init/upstart/click-system-hooks.conf.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,8 +0,0 @@ -description "Run Click system-level hooks" -author "Colin Watson " - -start on filesystem - -task - -exec @bindir@/click hook run-system diff -Nru click-0.4.43+16.04.20160203/init/upstart/click-user-hooks.conf.in click-6.7/init/upstart/click-user-hooks.conf.in --- click-0.4.43+16.04.20160203/init/upstart/click-user-hooks.conf.in 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/init/upstart/click-user-hooks.conf.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,8 +0,0 @@ -description "Run Click user-level hooks" -author "Colin Watson " - -start on starting xsession-init and started dbus - -task - -exec @bindir@/click hook run-user diff -Nru click-0.4.43+16.04.20160203/init/upstart/Makefile.am click-6.7/init/upstart/Makefile.am --- click-0.4.43+16.04.20160203/init/upstart/Makefile.am 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/init/upstart/Makefile.am 1970-01-01 00:00:00.000000000 +0000 @@ -1,12 +0,0 @@ -EXTRA_DIST = click-system-hooks.conf.in click-user-hooks.conf.in - -CLEANFILES = click-system-hooks.conf click-user-hooks.conf - -systemdir = $(sysconfdir)/init -sessionsdir = $(prefix)/share/upstart/sessions - -system_DATA = click-system-hooks.conf -sessions_DATA = click-user-hooks.conf - -%.conf: %.conf.in - sed -e "s,[@]bindir[@],$(bindir),g" $< > $@ diff -Nru click-0.4.43+16.04.20160203/lib/click/click-0.4.pc.in click-6.7/lib/click/click-0.4.pc.in --- click-0.4.43+16.04.20160203/lib/click/click-0.4.pc.in 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/lib/click/click-0.4.pc.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,28 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# -# This file is part of click. -# -# click is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation; version 3 of the License. -# -# click is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along -# with click. If not, see . - -prefix=@prefix@ -exec_prefix=@exec_prefix@ -libdir=@libdir@ -includedir=@includedir@ - -Name: @PACKAGE_NAME@ -Description: Click package manipulation library -Version: @PACKAGE_VERSION@ -URL: https://click.readthedocs.org/en/latest/ -Requires.private: glib-2.0 gobject-2.0 json-glib-1.0 -Libs: -L${libdir} -lclick-0.4 -Cflags: -I${includedir}/click-0.4 diff -Nru click-0.4.43+16.04.20160203/lib/click/click.sym click-6.7/lib/click/click.sym --- click-0.4.43+16.04.20160203/lib/click/click.sym 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/lib/click/click.sym 1970-01-01 00:00:00.000000000 +0000 @@ -1,108 +0,0 @@ -click_database_error_quark -click_db_add -click_db_ensure_ownership -click_db_gc -click_db_get -click_db_get_manifest -click_db_get_manifest_as_string -click_db_get_manifests -click_db_get_manifests_as_string -click_db_get_overlay -click_db_get_packages -click_db_get_path -click_db_get_size -click_db_get_type -click_db_has_package_version -click_db_maybe_remove -click_db_new -click_db_read -click_dir_get_type -click_dir_open -click_dir_read_name -click_ensuredir -click_find_on_path -click_find_package_directory -click_framework_error_quark -click_framework_get_base_name -click_framework_get_base_version -click_framework_get_fields -click_framework_get_field -click_framework_get_frameworks -click_framework_get_name -click_framework_get_type -click_framework_has_framework -click_framework_open -click_get_db_dir -click_get_frameworks_dir -click_get_hooks_dir -click_get_umask -click_hook_get_app_id -click_hook_get_field -click_hook_get_fields -click_hook_get_hook_name -click_hook_get_is_single_version -click_hook_get_is_user_level -click_hook_get_pattern -click_hook_get_run_commands_user -click_hook_get_short_app_id -click_hook_get_type -click_hook_install -click_hook_install_package -click_hook_open -click_hook_open_all -click_hook_remove -click_hook_remove_package -click_hook_run_commands -click_hook_sync -click_hooks_error_quark -click_installed_package_get_package -click_installed_package_get_path -click_installed_package_get_type -click_installed_package_get_version -click_installed_package_get_writeable -click_installed_package_new -click_package_install_hooks -click_package_remove_hooks -click_pattern_format -click_pattern_possible_expansion -click_query_error_quark -click_run_system_hooks -click_run_user_hooks -click_single_db_any_app_running -click_single_db_app_running -click_single_db_ensure_ownership -click_single_db_gc -click_single_db_get_manifest -click_single_db_get_manifest_as_string -click_single_db_get_packages -click_single_db_get_path -click_single_db_get_root -click_single_db_get_type -click_single_db_has_package_version -click_single_db_maybe_remove -click_single_db_new -click_symlink_force -click_unlink_force -click_user_error_quark -click_user_get_is_gc_in_use -click_user_get_is_pseudo_user -click_user_get_manifest -click_user_get_manifest_as_string -click_user_get_manifests -click_user_get_manifests_as_string -click_user_get_overlay_db -click_user_get_package_names -click_user_get_path -click_user_get_type -click_user_get_version -click_user_has_package_name -click_user_is_removable -click_user_new_for_all_users -click_user_new_for_gc_in_use -click_user_new_for_user -click_user_remove -click_user_set_version -click_users_get_type -click_users_get_user -click_users_get_user_names -click_users_new diff -Nru click-0.4.43+16.04.20160203/lib/click/database.vala click-6.7/lib/click/database.vala --- click-0.4.43+16.04.20160203/lib/click/database.vala 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/lib/click/database.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,894 +0,0 @@ -/* Copyright (C) 2013, 2014 Canonical Ltd. - * Author: Colin Watson - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/* Click databases. */ - -namespace Click { - -public errordomain DatabaseError { - /** - * A package/version does not exist. - */ - DOES_NOT_EXIST, - /** - * Failure to remove package. - */ - REMOVE, - /** - * Failure to ensure correct ownership of database files. - */ - ENSURE_OWNERSHIP, - /** - * Package manifest cannot be parsed. - */ - BAD_MANIFEST, - /** - * No database available for the given request - */ - INVALID -} - -private string? app_pid_command = null; - -private unowned string? get_app_pid_command () -{ - if (app_pid_command == null) { - if (find_on_path ("ubuntu-app-pid")) - app_pid_command = "ubuntu-app-pid"; - else if (find_on_path ("upstart-app-pid")) - app_pid_command = "upstart-app-pid"; - else - app_pid_command = ""; - } - - if (app_pid_command == "") - return null; - else - return app_pid_command; -} - -public class InstalledPackage : Object, Gee.Hashable { - public string package { get; construct; } - public string version { get; construct; } - public string path { get; construct; } - public bool writeable { get; construct; default = true; } - - public InstalledPackage (string package, string version, string path, - bool writeable = true) - { - Object (package: package, version: version, path: path, - writeable: writeable); - } - - public uint - hash () - { - return package.hash () ^ version.hash () ^ path.hash () ^ - (writeable ? 1 : 0); - } - - public bool - equal_to (InstalledPackage obj) - { - return package == obj.package && version == obj.version && - path == obj.path && writeable == obj.writeable; - } -} - -public class SingleDB : Object { - public string root { get; construct; } - public DB master_db { private get; construct; } - - public - SingleDB (string root, DB master_db) - { - Object (root: root, master_db: master_db); - } - - private bool - show_messages () - { - return Environment.get_variable ("TEST_QUIET") == null; - } - - /** - * get_path: - * @package: A package name. - * @version: A version string. - * - * Returns: The path to this version of this package. - */ - public string - get_path (string package, string version) throws DatabaseError - { - var try_path = Path.build_filename (root, package, version); - if (exists (try_path)) - return try_path; - else - throw new DatabaseError.DOES_NOT_EXIST - ("%s %s does not exist in %s", - package, version, root); - } - - /** - * has_package_version: - * @package: A package name. - * @version: A version string. - * - * Returns: True if this version of this package is unpacked in this - * database, otherwise false. - * - * Since: 0.4.18 - */ - public bool - has_package_version (string package, string version) - { - try { - get_path (package, version); - return true; - } catch (DatabaseError e) { - return false; - } - } - - /** - * get_packages: - * @all_versions: If true, return all versions, not just current ones. - * - * Returns: A list of #InstalledPackage instances corresponding to - * package versions in only this database. - */ - public List - get_packages (bool all_versions = false) throws Error - { - var ret = new List (); - - foreach (var package in Click.Dir.open (root)) { - if (package == ".click") - continue; - if (all_versions) { - var package_path = - Path.build_filename (root, package); - if (! is_dir (package_path)) - continue; - foreach (var version in Click.Dir.open - (package_path)) { - var version_path = Path.build_filename - (package_path, version); - if (is_symlink (version_path) || - ! is_dir (version_path)) - continue; - ret.prepend(new InstalledPackage - (package, version, - version_path)); - } - } else { - var current_path = Path.build_filename - (root, package, "current"); - if (! is_symlink (current_path)) - continue; - var version = FileUtils.read_link - (current_path); - if (! ("/" in version)) - ret.prepend(new InstalledPackage - (package, version, - current_path)); - } - } - - ret.reverse (); - return ret; - } - - /** - * get_manifest: - * @package: A package name. - * @version: A version string. - * - * Returns: A #Json.Object containing the manifest of this version - * of this package. The manifest may include additional dynamic - * keys (starting with an underscore) corresponding to dynamic - * properties of installed packages. - * - * Since: 0.4.18 - */ - public Json.Object - get_manifest (string package, string version) throws DatabaseError - { - /* Extract the raw manifest from the file system. */ - var path = get_path (package, version); - var manifest_path = Path.build_filename - (path, ".click", "info", @"$package.manifest"); - var parser = new Json.Parser (); - try { - parser.load_from_file (manifest_path); - } catch (Error e) { - throw new DatabaseError.BAD_MANIFEST - ("Failed to parse manifest in %s: %s", - manifest_path, e.message); - } - var node = parser.get_root (); - if (node.get_node_type () != Json.NodeType.OBJECT) - throw new DatabaseError.BAD_MANIFEST - ("Manifest in %s is not a JSON object", - manifest_path); - var manifest = node.dup_object (); - - /* Set up dynamic keys. */ - var to_remove = new List (); - foreach (var name in manifest.get_members ()) { - if (name.has_prefix ("_")) - to_remove.prepend (name); - } - foreach (var name in to_remove) - manifest.remove_member (name); - manifest.set_string_member ("_directory", path); - - return manifest; - } - - /** - * get_manifest_as_string: - * @package: A package name. - * @version: A version string. - * - * Returns: A JSON string containing the serialised manifest of this - * version of this package. The manifest may include additional - * dynamic keys (starting with an underscore) corresponding to - * dynamic properties of installed packages. - * This interface may be useful for clients with their own JSON - * parsing tools that produce representations more convenient for - * them. - * - * Since: 0.4.21 - */ - public string - get_manifest_as_string (string package, string version) - throws DatabaseError - { - var manifest = get_manifest (package, version); - var node = new Json.Node (Json.NodeType.OBJECT); - node.set_object (manifest); - var generator = new Json.Generator (); - generator.set_root (node); - return generator.to_data (null); - } - - /* - * app_running: - * @package: A package name. - * @app_name: An application name. - * @version: A version string. - * - * Returns: True if @app_name from version @version of @package is - * known to be running, otherwise false. - */ - public bool - app_running (string package, string app_name, string version) - { - string[] command = { - get_app_pid_command (), - @"$(package)_$(app_name)_$(version)" - }; - assert (command[0] != null); - try { - int exit_status; - Process.spawn_sync - (null, command, null, - SpawnFlags.SEARCH_PATH | - SpawnFlags.STDOUT_TO_DEV_NULL, - null, null, null, out exit_status); - return Process.check_exit_status (exit_status); - } catch (Error e) { - return false; - } - } - - /* - * any_app_running: - * @package: A package name. - * @version: A version string. - * - * Returns: True if any application from version @version of - * @package is known to be running, otherwise false. - */ - public bool - any_app_running (string package, string version) throws DatabaseError - { - if (get_app_pid_command () == null) - return false; - - var manifest_path = Path.build_filename - (get_path (package, version), ".click", "info", - @"$package.manifest"); - var parser = new Json.Parser (); - try { - parser.load_from_file (manifest_path); - var manifest = parser.get_root ().get_object (); - if (! manifest.has_member ("hooks")) - return false; - var hooks = manifest.get_object_member ("hooks"); - foreach (unowned string app_name in - hooks.get_members ()) { - if (app_running (package, app_name, version)) - return true; - } - } catch (Error e) { - } - return false; - } - - private void - remove_unless_running (string package, string version) throws Error - { - if (any_app_running (package, version)) - return; - - var version_path = get_path (package, version); - if (show_messages ()) - message ("Removing %s", version_path); - package_remove_hooks (master_db, package, version); - /* In Python, we used shutil.rmtree(version_path, - * ignore_errors=True), but GLib doesn't have an obvious - * equivalent. I could write a recursive version with GLib, - * but this isn't performance-critical and it isn't worth - * the hassle for now, so just call out to "rm -rf" instead. - */ - string[] argv = { "rm", "-rf", version_path }; - int exit_status; - Process.spawn_sync (null, argv, null, SpawnFlags.SEARCH_PATH, - null, null, null, out exit_status); - Process.check_exit_status (exit_status); - - var package_path = Path.build_filename (root, package); - var current_path = Path.build_filename - (package_path, "current"); - if (is_symlink (current_path) && - FileUtils.read_link (current_path) == version) { - if (FileUtils.unlink (current_path) < 0) - throw new DatabaseError.REMOVE - ("unlink %s failed: %s", - current_path, strerror (errno)); - /* TODO: Perhaps we should relink current to the - * latest remaining version. However, that requires - * version comparison, and it's not clear whether - * it's worth it given that current is mostly - * superseded by user registration. - */ - } - if (DirUtils.remove (package_path) < 0) { - if (errno != Posix.ENOTEMPTY && - errno != Posix.EEXIST) - throw new DatabaseError.REMOVE - ("rmdir %s failed: %s", - package_path, strerror (errno)); - } - } - - /** - * maybe_remove: - * @package: A package name. - * @version: A version string. - * - * Remove a package version if it is not in use. - * - * "In use" may mean registered for another user, or running. In - * either case, we do nothing. We will already have removed at - * least one registration by this point, so if no registrations are - * left but it is running, then gc will be able to come along later - * and clean things out. - */ - public void - maybe_remove (string package, string version) throws Error - { - var users_db = new Users (master_db); - foreach (var user_name in users_db.get_user_names ()) { - var user_db = users_db.get_user (user_name); - string reg_version; - try { - reg_version = user_db.get_version (package); - } catch (UserError e) { - continue; - } - if (reg_version == version) { - /* In use. */ - return; - } - } - - remove_unless_running (package, version); - } - - /** - * gc: - * - * Remove package versions that have no user registrations and that - * are not running. - * - * This is rather like maybe_remove, but is suitable for bulk use, - * since it only needs to scan the database once rather than once - * per package. - * - * For historical reasons, we don't count @gcinuse as a real user - * registration, and remove any such registrations we find. We can - * drop this once we no longer care about upgrading versions from - * before this change to something more current in a single step. - */ - public void - gc () throws Error - { - // Clean up user registrations: registrations for old packages should - // be updated to point to the newest version available. TODO: Check - // registration timestamps and compare to package timestamps before - // blindly re-registering so old versions can still be registered if - // they were done so after the new package was installed. - foreach (var package in master_db.get_packages(true)) { - var users_db = new Users (master_db); - foreach (var name in users_db.get_user_names ()) { - var user_db = users_db.get_user (name); - - try { - string registered_version = user_db.get_version (package.package); - - string[] args = { "dpkg", "--compare-versions", - registered_version, "lt", - package.version }; - int exit_status; - Process.spawn_sync (null, args, null, - SpawnFlags.SEARCH_PATH, null, null, - null, out exit_status); - - // This will throw if non-zero - Process.check_exit_status (exit_status); - - // Update the user's registered version if necessary. - user_db.set_version (package.package, package.version); - } catch { - // User was either not registered for this app or was - // registered for this version (or newer). Either way, - // skip it. - } - } - } - - var users_db = new Users (master_db); - var user_reg = new Gee.HashMultiMap (); - foreach (var user_name in users_db.get_user_names ()) { - var user_db = users_db.get_user (user_name); - foreach (var package in user_db.get_package_names ()) { - var version = user_db.get_version (package); - if (version == "current") - continue; - /* Odd multimap syntax; this should really - * be more like foo[package] += version. - */ - if (! user_db.is_gc_in_use) - user_reg[package] = version; - } - } - - var gc_in_use_user_db = new User.for_gc_in_use (master_db); - foreach (var package in Click.Dir.open (root)) { - if (package == ".click") - continue; - var package_path = Path.build_filename (root, package); - if (! is_dir (package_path)) - continue; - foreach (var version in Click.Dir.open - (package_path)) { - if (version == "current") - continue; - if (version in user_reg[package]) - /* In use. */ - continue; - if (gc_in_use_user_db.has_package_name - (package)) - gc_in_use_user_db.remove (package); - remove_unless_running (package, version); - } - } - } - - private delegate void WalkFunc (string dirpath, string[] dirnames, - string[] filenames) throws Error; - - /** - * walk: - * - * An reduced emulation of Python's os.walk. - */ - private void - walk (string top, WalkFunc func) throws Error - { - string[] dirs = {}; - string[] nondirs = {}; - foreach (var name in Click.Dir.open (top)) { - var path = Path.build_filename (top, name); - if (is_dir (path)) - dirs += name; - else - nondirs += name; - } - func (top, dirs, nondirs); - foreach (var name in dirs) { - var path = Path.build_filename (top, name); - if (! is_symlink (path)) - walk (path, func); - } - } - - private delegate void ClickpkgForeachFunc (string path) - throws DatabaseError; - - /** - * foreach_clickpkg_path: - * - * Call a delegate for each path which should be owned by clickpkg. - */ - private void - foreach_clickpkg_path (ClickpkgForeachFunc func) throws Error - { - if (exists (root)) - func (root); - foreach (var package in Click.Dir.open (root)) { - var path = Path.build_filename (root, package); - if (package == ".click") { - func (path); - var log_path = Path.build_filename - (path, "log"); - if (exists (log_path)) - func (log_path); - var users_path = Path.build_filename - (path, "users"); - if (exists (users_path)) - func (users_path); - } else { - walk (path, (dp, dns, fns) => { - func (dp); - foreach (var dn in dns) { - var dnp = Path.build_filename - (dp, dn); - if (is_symlink (dnp)) - func (dnp); - } - foreach (var fn in fns) { - var fnp = Path.build_filename - (dp, fn); - func (fnp); - } - }); - } - } - } - - /** - * ensure_ownership: - * - * Ensure correct ownership of files in the database. - * - * On a system that is upgraded by delivering a new system image - * rather than by package upgrades, it is possible for the clickpkg - * UID to change. The overlay database must then be adjusted to - * account for this. - */ - public void - ensure_ownership () throws Error - { - errno = 0; - unowned Posix.Passwd? pw = Posix.getpwnam ("clickpkg"); - if (pw == null) - throw new DatabaseError.ENSURE_OWNERSHIP - ("Cannot get password file entry for " + - "clickpkg: %s", strerror (errno)); - Posix.Stat st; - if (Posix.stat (root, out st) < 0) - return; - if (st.st_uid == pw.pw_uid && st.st_gid == pw.pw_gid) - return; - foreach_clickpkg_path ((path) => { - if (Posix.chown (path, pw.pw_uid, pw.pw_gid) < 0) - throw new DatabaseError.ENSURE_OWNERSHIP - ("Cannot set ownership of %s: %s", - path, strerror (errno)); - }); - } -} - -public class DB : Object { - private Gee.ArrayList db = new Gee.ArrayList (); - - public DB () {} - - public void - read (string? db_dir = null) throws FileError - { - string real_db_dir = (db_dir == null) ? get_db_dir () : db_dir; - - foreach (var name in Click.Dir.open (real_db_dir)) { - if (! name.has_suffix (".conf")) - continue; - var path = Path.build_filename (real_db_dir, name); - var config = new KeyFile (); - string root; - try { - config.load_from_file - (path, KeyFileFlags.NONE); - root = config.get_string - ("Click Database", "root"); - } catch (Error e) { - warning ("%s", e.message); - continue; - } - assert (root != null); - add (root); - } - } - - public int size { get { return db.size; } } - - public new SingleDB - @get (int index) throws DatabaseError - { - if (index >= db.size) - throw new DatabaseError.INVALID - ("invalid index %i for db of size %i", index, db.size); - return db.get (index); - } - - public new void - add (string root) - { - db.add (new SingleDB (root, this)); - } - - /** - * overlay: - * - * The directory where changes should be written. - */ - public string overlay - { - get { - if (db.size == 0) - return ""; - else - return db.last ().root; - } - } - - /** - * get_path: - * @package: A package name. - * @version: A version string. - * - * Returns: The path to this version of this package. - */ - public string - get_path (string package, string version) throws DatabaseError - { - foreach (var single_db in db) { - try { - return single_db.get_path (package, version); - } catch (DatabaseError e) { - } - } - throw new DatabaseError.DOES_NOT_EXIST - ("%s %s does not exist in any database", - package, version); - } - - /** - * has_package_version: - * @package: A package name. - * @version: A version string. - * - * Returns: True if this version of this package is unpacked, - * otherwise false. - */ - public bool - has_package_version (string package, string version) - { - try { - get_path (package, version); - return true; - } catch (DatabaseError e) { - return false; - } - } - - /** - * get_packages: - * @all_versions: If true, return all versions, not just current ones. - * - * Returns: A list of #InstalledPackage instances corresponding to - * package versions in all databases. - */ - public List - get_packages (bool all_versions = false) throws Error - { - var ret = new List (); - var seen = new Gee.HashSet (); - var writeable = true; - for (int i = db.size - 1; i >= 0; --i) { - var child_packages = db[i].get_packages (all_versions); - foreach (var pkg in child_packages) { - string seen_id; - if (all_versions) - seen_id = ( - pkg.package + "_" + - pkg.version); - else - seen_id = pkg.package.dup (); - - if (! (seen_id in seen)) { - ret.prepend(new InstalledPackage - (pkg.package, pkg.version, - pkg.path, writeable)); - seen.add (seen_id); - } - } - writeable = false; - } - - ret.reverse (); - return ret; - } - - /** - * get_manifest: - * @package: A package name. - * @version: A version string. - * - * Returns: A #Json.Object containing the manifest of this version - * of this package. - * - * Since: 0.4.18 - */ - public Json.Object - get_manifest (string package, string version) throws DatabaseError - { - foreach (var single_db in db) { - try { - return single_db.get_manifest - (package, version); - } catch (DatabaseError e) { - if (e is DatabaseError.BAD_MANIFEST) - throw e; - } - } - throw new DatabaseError.DOES_NOT_EXIST - ("%s %s does not exist in any database", - package, version); - } - - /** - * get_manifest_as_string: - * @package: A package name. - * @version: A version string. - * - * Returns: A JSON string containing the serialised manifest of this - * version of this package. - * This interface may be useful for clients with their own JSON - * parsing tools that produce representations more convenient for - * them. - * - * Since: 0.4.21 - */ - public string - get_manifest_as_string (string package, string version) - throws DatabaseError - { - var manifest = get_manifest (package, version); - var node = new Json.Node (Json.NodeType.OBJECT); - node.set_object (manifest); - var generator = new Json.Generator (); - generator.set_root (node); - return generator.to_data (null); - } - - /** - * get_manifests: - * @all_versions: If true, return manifests for all versions, not - * just current ones. - * - * Returns: A #Json.Array containing manifests of all packages in - * this database. The manifest may include additional dynamic keys - * (starting with an underscore) corresponding to dynamic properties - * of installed packages. - * - * Since: 0.4.18 - */ - public Json.Array - get_manifests (bool all_versions = false) throws Error - { - var ret = new Json.Array (); - foreach (var inst in get_packages (all_versions)) { - Json.Object obj; - try { - obj = get_manifest - (inst.package, inst.version); - } catch (DatabaseError e) { - warning ("%s", e.message); - continue; - } - /* This should really be a boolean, but it was - * mistakenly made an int when the "_removable" key - * was first created. We may change this in future. - */ - obj.set_int_member ("_removable", - inst.writeable ? 1 : 0); - ret.add_object_element (obj); - } - return ret; - } - - /** - * get_manifests_as_string: - * @all_versions: If true, return manifests for all versions, not - * just current ones. - * - * Returns: A JSON string containing a serialised array of manifests - * of all packages in this database. The manifest may include - * additional dynamic keys (starting with an underscore) - * corresponding to dynamic properties of installed packages. - * This interface may be useful for clients with their own JSON - * parsing tools that produce representations more convenient for - * them. - * - * Since: 0.4.21 - */ - public string - get_manifests_as_string (bool all_versions = false) throws Error - { - var manifests = get_manifests (all_versions); - var node = new Json.Node (Json.NodeType.ARRAY); - node.set_array (manifests); - var generator = new Json.Generator (); - generator.set_root (node); - return generator.to_data (null); - } - - private void - ensure_db () throws Error - { - if (db.size == 0) - throw new DatabaseError.INVALID - ("no database loaded"); - } - - public void - maybe_remove (string package, string version) throws Error - { - ensure_db(); - db.last ().maybe_remove (package, version); - } - - public void - gc () throws Error - { - ensure_db(); - db.last ().gc (); - } - - public void - ensure_ownership () throws Error - { - ensure_db(); - db.last ().ensure_ownership (); - } -} - -} diff -Nru click-0.4.43+16.04.20160203/lib/click/deb822.vala click-6.7/lib/click/deb822.vala --- click-0.4.43+16.04.20160203/lib/click/deb822.vala 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/lib/click/deb822.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,65 +0,0 @@ -/* Copyright (C) 2014 Canonical Ltd. - * Author: Colin Watson - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/* Simple deb822-like file parsing. */ - -namespace Click { - -private Regex? field_re = null; -private Regex? blank_re = null; - -/** - * parse_deb822_file: - * @path: Path to a file. - * - * A very simple deb822-like file parser. - * - * Note that this only supports a single paragraph composed only of simple - * (non-folded/multiline) fields, which is fortunately all we need in Click. - * - * Returns: A mapping of field names to values. - */ -private Gee.Map -parse_deb822_file (string path) throws Error -{ - if (field_re == null) - field_re = new Regex - ("^([^:[:space:]]+)[[:space:]]*:[[:space:]]" + - "([^[:space:]].*?)[[:space:]]*$"); - if (blank_re == null) - blank_re = new Regex ("^[[:space:]]*$"); - - var ret = new Gee.HashMap (); - var channel = new IOChannel.file (path, "r"); - string line; - while (channel.read_line (out line, null, null) == IOStatus.NORMAL && - line != null) { - MatchInfo match_info; - - if (blank_re.match (line)) - break; - - if (field_re.match (line, 0, out match_info)) { - var key = match_info.fetch (1); - var value = match_info.fetch (2); - if (key != null && value != null) - ret[key.down ()] = value; - } - } - return ret; -} - -} diff -Nru click-0.4.43+16.04.20160203/lib/click/framework.vala click-6.7/lib/click/framework.vala --- click-0.4.43+16.04.20160203/lib/click/framework.vala 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/lib/click/framework.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,154 +0,0 @@ -/* Copyright (C) 2014 Canonical Ltd. - * Author: Colin Watson - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/* Click frameworks. */ - -namespace Click { - -public errordomain FrameworkError { - /** - * Requested framework does not exist. - */ - NO_SUCH_FRAMEWORK, - /** - * Missing hook field. - */ - MISSING_FIELD -} - -public class Framework : Object { - public string name { get; construct; } - - private Gee.Map fields; - - private - Framework (string name) - { - Object (name: name); - } - - /** - * Framework.open: - * @name: The name of the framework to open. - * - * Returns: (transfer full): A newly-allocated #Click.Framework. - * - * Since: 0.4.18 - */ - public static Framework - open (string name) throws FrameworkError - { - var path = Path.build_filename - (get_frameworks_dir (), @"$name.framework"); - try { - var framework = new Framework (name); - framework.fields = parse_deb822_file (path); - return framework; - } catch (Error e) { - throw new FrameworkError.NO_SUCH_FRAMEWORK - ("No click framework '%s' installed", name); - } - } - - /** - * has_framework: - * @name: A framework name. - * - * Returns: True if a framework by this name exists, otherwise false. - * - * Since: 0.4.18 - */ - public static bool - has_framework (string name) - { - var path = Path.build_filename - (get_frameworks_dir (), @"$name.framework"); - return exists (path); - } - - /** - * get_frameworks: - * - * Returns: (element-type ClickFramework) (transfer full): A #List - * of all #Click.Framework instances installed on the system. - * - * Since: 0.4.18 - */ - public static List - get_frameworks () - { - var ret = new List (); - Click.Dir dir; - try { - dir = Click.Dir.open (get_frameworks_dir ()); - } catch (FileError e) { - return ret; - } - foreach (var entry in dir) { - if (! entry.has_suffix (".framework")) - continue; - try { - ret.prepend (open (entry[0:-10])); - } catch (Error e) { - continue; - } - } - ret.reverse (); - return ret; - } - - /** - * get_fields: - * - * Returns: A list of field names defined by this framework. - * - * Since: 0.4.18 - */ - public List - get_fields () - { - var ret = new List (); - foreach (var key in fields.keys) - ret.prepend (key); - ret.reverse (); - return ret; - } - - public string - get_field (string key) throws FrameworkError - { - string value = fields[key.down ()]; - if (value == null) - throw new FrameworkError.MISSING_FIELD - ("Framework '%s' has no field named '%s'", - name, key); - return value; - } - - public string? - get_base_name () - { - return fields["base-name"]; - } - - public string? - get_base_version () - { - return fields["base-version"]; - } -} - -} diff -Nru click-0.4.43+16.04.20160203/lib/click/hooks.vala click-6.7/lib/click/hooks.vala --- click-0.4.43+16.04.20160203/lib/click/hooks.vala 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/lib/click/hooks.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,1228 +0,0 @@ -/* Copyright (C) 2013, 2014 Canonical Ltd. - * Author: Colin Watson - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/* Click package hooks. - * - * See doc/hooks.rst for the draft specification. - */ - -namespace Click { - -public errordomain HooksError { - /** - * Requested hook does not exist. - */ - NO_SUCH_HOOK, - /** - * Missing hook field. - */ - MISSING_FIELD, - /** - * Invalid application name. - */ - BAD_APP_NAME, - /** - * Requested user does not exist. - */ - NO_SUCH_USER, - /** - * Failure to drop privileges. - */ - DROP_PRIVS, - /** - * Not yet implemented. - */ - NYI, - /** - * Hook command failed. - */ - COMMAND_FAILED, - /** - * Some hooks were not run successfully. - */ - INCOMPLETE -} - - -/* vala implementation of click.framework.validate_framework() - * - * Note that the required_frameworks string has the form - * framework1, framework2, ... - * See doc/file-format.rst for details. - */ -private bool -validate_framework (string required_frameworks) -{ - // valid framework names, cf. debian policy §5.6.1 - Regex valid_framework_re; - try { - valid_framework_re = new Regex ("^[a-z][a-z0-9.+-]+"); - } catch (RegexError e) { - error ("Could not compile regex /^[a-z][a-z0-9.+-]+/: %s", - e.message); - } - var base_version = ""; - foreach (var framework_name in required_frameworks.split (",")) - { - framework_name = framework_name.strip (); - if (!valid_framework_re.match (framework_name)) - return false; - Framework framework; - // now check the base-version - try { - framework = Framework.open (framework_name); - } catch (FrameworkError e) { - return false; - } - if (base_version == "") - base_version = framework.get_base_version (); - if (base_version != framework.get_base_version ()) - return false; - } - return true; -} - -private bool -validate_framework_for_package (DB db, string package, string? version) -{ - var manifest = read_manifest (db, package, version); - if (!manifest.has_member ("framework")) - return true; - var required_frameworks = manifest.get_string_member ("framework"); - return validate_framework (required_frameworks); -} - -private Json.Object -read_manifest (DB db, string package, string? version) -{ - if (version == null) - return new Json.Object (); - var parser = new Json.Parser (); - try { - var manifest_path = Path.build_filename - (db.get_path (package, version), ".click", "info", - @"$package.manifest"); - parser.load_from_file (manifest_path); - var manifest = parser.get_root ().get_object (); - return manifest.ref (); - } catch (Error e) { - return new Json.Object (); - } -} - -private Json.Object -read_manifest_hooks (DB db, string package, string? version) -{ - var manifest = read_manifest (db, package, version); - if (! manifest.has_member ("hooks")) - return new Json.Object (); - var hooks = manifest.get_object_member ("hooks"); - return hooks.ref (); -} - -private class PreviousEntry : Object, Gee.Hashable { - public string path { get; construct; } - public string package { get; construct; } - public string version { get; construct; } - public string app_name { get; construct; } - - public - PreviousEntry (string path, string package, string version, - string app_name) - { - Object (path: path, package: package, version: version, - app_name: app_name); - } - - public uint - hash () - { - return path.hash () ^ package.hash () ^ version.hash () ^ - app_name.hash (); - } - - public bool - equal_to (PreviousEntry obj) - { - return path == obj.path && package == obj.package && - version == obj.version && app_name == obj.app_name; - } -} - -private class UnpackedPackage : Object, Gee.Hashable { - public string package { get; construct; } - public string version { get; construct; } - public string? user_name { get; construct; } - - public - UnpackedPackage (string package, string version, - string? user_name = null) - { - Object (package: package, version: version, - user_name: user_name); - } - - public uint - hash () - { - return package.hash () ^ version.hash () ^ - (user_name != null ? user_name.hash () : 0); - } - - public bool - equal_to (UnpackedPackage obj) - { - return package == obj.package && version == obj.version && - user_name == obj.user_name; - } -} - -private class RelevantApp : Object, Gee.Hashable { - public string package { get; construct; } - public string version { get; construct; } - public string app_name { get; construct; } - public string? user_name { get; construct; } - public string relative_path { get; construct; } - - public - RelevantApp (string package, string version, string app_name, - string? user_name, string relative_path) - { - Object (package: package, version: version, app_name: app_name, - user_name: user_name, relative_path: relative_path); - } - - public uint - hash () - { - return package.hash () ^ version.hash () ^ app_name.hash () ^ - (user_name != null ? user_name.hash () : 0) ^ - relative_path.hash (); - } - - public bool - equal_to (RelevantApp obj) - { - return package == obj.package && version == obj.version && - app_name == obj.app_name && - user_name == obj.user_name && - relative_path == obj.relative_path; - } -} - -private class AppHook : Object, Gee.Hashable, - Gee.Comparable { - public string app_name { get; construct; } - public string hook_name { get; construct; } - - public - AppHook (string app_name, string hook_name) - { - Object (app_name: app_name, hook_name: hook_name); - } - - public uint - hash () - { - return app_name.hash () ^ hook_name.hash (); - } - - public bool - equal_to (AppHook obj) - { - return app_name == obj.app_name && hook_name == obj.hook_name; - } - - public int - compare_to (AppHook obj) - { - var ret = strcmp (app_name, obj.app_name); - if (ret != 0) - return ret; - return strcmp (hook_name, obj.hook_name); - } -} - -private class ParsedPattern : Object { - public bool is_expansion { get; construct; } - public string text { get; construct; } - - public - ParsedPattern (bool is_expansion, string text) - { - Object (is_expansion: is_expansion, text: text); - } -} - -private Regex? expansion_re = null; - -/** - * pattern_parse: - * @format_string: A format string. - * - * Parse @format_string into segments. - * - * Returns: A list of #ParsedPattern segments. - */ -private Gee.List -pattern_parse (string format_string) -{ - const string EXPANSION = "\\$(?:\\$|{(.*?)})"; - var ret = new Gee.ArrayList (); - MatchInfo match_info; - var last_end = 0; - - if (expansion_re == null) { - try { - expansion_re = new Regex (EXPANSION); - } catch (RegexError e) { - error ("Could not compile regex /%s/: %s", - EXPANSION, e.message); - } - } - - expansion_re.match (format_string, 0, out match_info); - while (match_info.matches ()) { - int start, end; - var fetched = match_info.fetch_pos (0, out start, out end); - assert (fetched); - string? key = null; - if (start + 2 == end && format_string[start] == '$' && - format_string[start + 1] == '$') - ++start; - else - key = match_info.fetch (1); - if (last_end < start) { - var segment = format_string.substring - (last_end, start - last_end); - ret.add (new ParsedPattern (false, segment)); - } - if (key != null) - ret.add (new ParsedPattern (true, key)); - - last_end = end; - try { - match_info.next (); - } catch (RegexError e) { - break; - } - } - if (last_end < format_string.length) - ret.add (new ParsedPattern - (false, format_string.substring (last_end))); - - return ret; -} - -/** - * pattern_format: - * @format_string: A format string. - * @args: A #GLib.Variant of type "a{sms}", binding keys to values. - * - * Apply simple $-expansions to a string. - * - * `${key}` is replaced by the value of the `key` argument; `$$` is replaced - * by `$`. Any `$` character not followed by `{...}` is preserved intact. - * - * Returns: The expanded string. - */ -public string -pattern_format (string format_string, Variant args) -{ - string[] pieces = {}; - foreach (var segment in pattern_parse (format_string)) { - if (segment.is_expansion) { - unowned string value; - if (args.lookup (segment.text, "m&s", out value)) - pieces += value; - } else - pieces += segment.text; - } - return string.joinv ("", pieces); -} - -/** - * click_pattern_possible_expansion: - * @s: A string. - * @format_string: A format string. - * @args: A #GLib.Variant of type "a{sms}", binding keys to values. - * - * Check if @s is a possible $-expansion of @format_string. - * - * Entries in @args have the effect of binding some keys to fixed values; - * unspecified keys may take any value, and will bind greedily to the - * longest possible string. - * - * Returns: If @s is a possible expansion, then this function returns a - * (possibly empty) dictionary #GLib.Variant mapping all the unspecified - * keys to their bound values. Otherwise, it returns null. - */ -public Variant? -pattern_possible_expansion (string s, string format_string, Variant args) -{ - string[] regex_pieces = {}; - string[] group_names = {}; - foreach (var segment in pattern_parse (format_string)) { - if (segment.is_expansion) { - unowned string value; - if (args.lookup (segment.text, "m&s", out value)) - regex_pieces += Regex.escape_string (value); - else { - regex_pieces += "(.*)"; - group_names += segment.text; - } - } else - regex_pieces += Regex.escape_string (segment.text); - } - var joined = string.joinv ("", regex_pieces); - Regex compiled; - try { - compiled = new Regex ("^" + joined + "$"); - } catch (RegexError e) { - return null; - } - MatchInfo match_info; - var builder = new VariantBuilder (new VariantType ("a{ss}")); - if (compiled.match (s, 0, out match_info)) { - for (int group_i = 0; group_i < group_names.length; - ++group_i) { - var match = match_info.fetch (group_i + 1); - assert (match != null); - builder.add ("{ss}", group_names[group_i], match); - } - return builder.end (); - } else - return null; -} - -public class Hook : Object { - public DB db { private get; construct; } - public string name { internal get; construct; } - - private Gee.Map fields; - - private Hook (DB db, string name) - { - Object (db: db, name: name); - } - - /** - * Hook.open: - * @db: A #Click.DB. - * @name: The name of the hook to open. - * - * Returns: (transfer full): A newly-allocated #Click.Hook. - */ - public static Hook - open (DB db, string name) throws HooksError - { - var hook_path = Path.build_filename - (get_hooks_dir (), @"$name.hook"); - try { - var hook = new Hook (db, name); - hook.fields = parse_deb822_file (hook_path); - return hook; - } catch (Error e) { - throw new HooksError.NO_SUCH_HOOK - ("No click hook '%s' installed", name); - } - } - - /** - * open_all: - * @db: A #Click.DB. - * @hook_name: (allow-none): A string to match against Hook-Name - * fields, or null. - * - * Returns: (element-type ClickHook) (transfer full): A #List of - * #Click.Hook instances whose Hook-Name fields equal the value of - * @hook_name. - */ - public static List - open_all (DB db, string? hook_name = null) throws FileError - { - var ret = new List (); - var dir = get_hooks_dir (); - foreach (var name in Click.Dir.open (dir)) { - if (! name.has_suffix (".hook")) - continue; - var path = Path.build_filename (dir, name); - try { - var hook = new Hook (db, name[0:-5]); - hook.fields = parse_deb822_file (path); - if (hook_name == null || - hook.get_hook_name () == hook_name) - ret.prepend (hook); - } catch (Error e) { - continue; - } - } - ret.reverse (); - return ret; - } - - /** - * get_fields: - * - * Returns: A list of field names defined by this hook. - */ - public List - get_fields () - { - var ret = new List (); - foreach (var key in fields.keys) - ret.prepend (key); - ret.reverse (); - return ret; - } - - public string - get_field (string key) throws HooksError - { - string value = fields[key.down ()]; - if (value == null) - throw new HooksError.MISSING_FIELD - ("Hook '%s' has no field named '%s'", - name, key); - return value; - } - - /** - * is_user_level: - * - * True if this hook is a user-level hook, otherwise false. - */ - public bool is_user_level { get { - return fields["user-level"] == "yes"; - } } - - /** - * is_single_version: - * - * True if this hook is a single-version hook, otherwise false. - */ - public bool is_single_version { get { - return is_user_level || fields["single-version"] == "yes"; - } } - - /** - * get_hook_name: - * - * Returns: This hook's Hook-Name field, or the base of its file - * name with the ".hook" extension removed if that field is missing. - */ - public string - get_hook_name () { - if (fields.has_key ("hook-name")) - return fields["hook-name"]; - else - return name; - } - - /** - * get_short_app_id: - * @package: A package name. - * @app_name: An application name. - * - * Returns: The short application ID based on @package and - * @app_name. - */ - public string - get_short_app_id (string package, string app_name) throws HooksError - { - /* TODO: Perhaps this check belongs further up the stack - * somewhere? - */ - if ("_" in app_name || "/" in app_name) - throw new HooksError.BAD_APP_NAME - ("Application name '%s' may not contain _ " + - "or / characters", app_name); - return @"$(package)_$(app_name)"; - } - - /** - * get_app_id: - * @package: A package name. - * @version: A version string. - * @app_name: An application name. - * - * Returns: The application ID based on @package, @version, and - * @app_name. - */ - public string - get_app_id (string package, string version, string app_name) - throws HooksError - { - var short_app_id = get_short_app_id (package, app_name); - return @"$(short_app_id)_$(version)"; - } - - private string? - get_user_home (string? user_name) - { - if (user_name == null) - return null; - /* TODO: caching */ - unowned Posix.Passwd? pw = Posix.getpwnam (user_name); - if (pw == null) - return null; - return pw.pw_dir; - } - - /** - * get_pattern: - * @package: A package name. - * @version: A version string. - * @app_name: An application name. - * @user_name: (allow-none): A user name, or null. - */ - public string - get_pattern (string package, string version, string app_name, - string? user_name = null) throws HooksError - { - var builder = new VariantBuilder (new VariantType ("a{sms}")); - var app_id = get_app_id (package, version, app_name); - var pattern = get_field ("pattern"); - var user_home = get_user_home (user_name); - builder.add ("{sms}", "id", app_id); - builder.add ("{sms}", "user", user_name); - builder.add ("{sms}", "home", user_home); - if (is_single_version) { - var short_app_id = get_short_app_id (package, - app_name); - builder.add ("{sms}", "short-id", short_app_id); - } - var ret = pattern_format (pattern, builder.end ()); - var len = ret.length; - while (len > 0) { - if (ret[len - 1] == Path.DIR_SEPARATOR) - --len; - else - break; - } - if (len == ret.length) - return ret; - else - return ret.substring (0, len); - } - - private void - priv_drop_failure (string name) throws HooksError - { - throw new HooksError.DROP_PRIVS - ("Cannot drop privileges (%s): %s", - name, strerror (errno)); - } - - /* This function is not async-signal-safe, but runs between fork() and - * execve(). As such, it is not safe to run hooks from a multi-threaded - * process. Do not use the GLib main loop with this! - */ - private void - drop_privileges_inner (string user_name) throws HooksError - { - if (Posix.geteuid () != 0) - return; - - errno = 0; - unowned Posix.Passwd? pw = Posix.getpwnam (user_name); - if (pw == null) - throw new HooksError.NO_SUCH_USER - ("Cannot get password file entry for user " + - "'%s': %s", user_name, strerror (errno)); - Posix.gid_t[] supp = {}; - Posix.setgrent (); - unowned PosixExtra.Group? gr; - while ((gr = PosixExtra.getgrent ()) != null) { - foreach (unowned string member in gr.gr_mem) { - if (member == user_name) { - supp += gr.gr_gid; - break; - } - } - } - Posix.endgrent (); - if (PosixExtra.setgroups (supp.length, supp) < 0) - priv_drop_failure ("setgroups"); - /* Portability note: this assumes that we have - * [gs]etres[gu]id, which is true on Linux but not - * necessarily elsewhere. If you need to support something - * else, there are reasonably standard alternatives - * involving other similar calls; see e.g. - * gnulib/lib/idpriv-drop.c. - */ - if (PosixExtra.setresgid (pw.pw_gid, pw.pw_gid, pw.pw_gid) < 0) - priv_drop_failure ("setresgid"); - if (PosixExtra.setresuid (pw.pw_uid, pw.pw_uid, pw.pw_uid) < 0) - priv_drop_failure ("setresuid"); - { - Posix.uid_t ruid, euid, suid; - Posix.gid_t rgid, egid, sgid; - assert (PosixExtra.getresuid (out ruid, out euid, - out suid) == 0 && - ruid == pw.pw_uid && euid == pw.pw_uid && - suid == pw.pw_uid); - assert (PosixExtra.getresgid (out rgid, out egid, - out sgid) == 0 && - rgid == pw.pw_gid && egid == pw.pw_gid && - sgid == pw.pw_gid); - } - Environment.set_variable ("HOME", pw.pw_dir, true); - Posix.umask (get_umask () | Posix.S_IWOTH); - } - - private void - drop_privileges (string user_name) - { - try { - drop_privileges_inner (user_name); - } catch (HooksError e) { - error ("%s", e.message); - } - } - - /** - * get_run_commands_user: - * @user_name: (allow-none): A user name, or null. - * - * Returns: The user name under which this hook will be run. - */ - public string - get_run_commands_user (string? user_name = null) throws HooksError - { - if (is_user_level) - return user_name; - return get_field ("user"); - } - - /** - * run_commands: - * @user_name: (allow-none): A user name, or null. - * - * Run any commands specified by the hook to keep itself up to date. - */ - public void - run_commands (string? user_name = null) throws Error - { - if (fields.has_key ("exec")) { - string[] argv = {"/bin/sh", "-c", fields["exec"]}; - var target_user_name = get_run_commands_user - (user_name); - SpawnChildSetupFunc drop = - () => drop_privileges (target_user_name); - int exit_status; - Process.spawn_sync (null, argv, null, - SpawnFlags.SEARCH_PATH, drop, - null, null, out exit_status); - try { - Process.check_exit_status (exit_status); - } catch (Error e) { - throw new HooksError.COMMAND_FAILED - ("Hook command '%s' failed: %s", - fields["exec"], e.message); - } - } - - if (fields["trigger"] == "yes") - throw new HooksError.NYI - ("'Trigger: yes' not yet implemented"); - } - - private List - get_previous_entries (string? user_name = null) throws Error - { - var ret = new List (); - var link_dir_path = Path.get_dirname (get_pattern - ("", "", "", user_name)); - /* TODO: This only works if the application ID only appears, at - * most, in the last component of the pattern path. - */ - foreach (var entry in Click.Dir.open (link_dir_path)) { - var path = Path.build_filename (link_dir_path, entry); - var exp_builder = new VariantBuilder - (new VariantType ("a{sms}")); - exp_builder.add ("{sms}", "user", user_name); - exp_builder.add - ("{sms}", "home", get_user_home (user_name)); - var exp = pattern_possible_expansion - (path, fields["pattern"], exp_builder.end ()); - unowned string? id = null; - if (exp != null) - exp.lookup ("id", "&s", out id); - if (id == null) - continue; - var tokens = id.split ("_", 3); - if (tokens.length < 3) - continue; - /* tokens == { package, app_name, version } */ - ret.prepend (new PreviousEntry - (path, tokens[0], tokens[2], tokens[1])); - } - ret.reverse (); - return ret; - } - - /** - * install_link: - * @package: A package name. - * @version: A version string. - * @app_name: An application name. - * @relative_path: A relative path within the unpacked package. - * @user_name: (allow-none): A user name, or null. - * @user_db: (allow-none): A #Click.User, or null. - * - * Install a hook symlink. - * - * This should be called with dropped privileges if necessary. - */ - private void - install_link (string package, string version, string app_name, - string relative_path, string? user_name = null, - User? user_db = null) throws Error - { - string path; - if (is_user_level) - path = user_db.get_path (package); - else - path = db.get_path (package, version); - var target = Path.build_filename (path, relative_path); - var link = get_pattern (package, version, app_name, user_name); - if (is_symlink (link) && FileUtils.read_link (link) == target) - return; - ensuredir (Path.get_dirname (link)); - symlink_force (target, link); - } - - /** - * install_package: - * @package: A package name. - * @version: A version string. - * @app_name: An application name. - * @relative_path: A relative path within the unpacked package. - * @user_name: (allow-none): A user name, or null. - * - * Run this hook in response to @package being installed. - */ - public void - install_package (string package, string version, string app_name, - string relative_path, string? user_name = null) - throws Error - { - if (! is_user_level) - assert (user_name == null); - - /* Remove previous versions if necessary. */ - if (is_single_version) { - var entries = get_previous_entries (user_name); - foreach (var prev in entries) { - if (prev.package == package && - prev.app_name == app_name && - prev.version != version) - unlink_force (prev.path); - } - } - - if (is_user_level) { - var user_db = new User.for_user (db, user_name); - user_db.drop_privileges (); - try { - install_link (package, version, app_name, - relative_path, user_name, - user_db); - } finally { - user_db.regain_privileges (); - } - } else - install_link (package, version, app_name, - relative_path); - run_commands (user_name); - } - - /** - * remove_package: - * @package: A package name. - * @version: A version string. - * @app_name: An application name. - * @user_name: (allow-none): A user name, or null. - * - * Run this hook in response to @package being removed. - */ - public void - remove_package (string package, string version, string app_name, - string? user_name = null) throws Error - { - unlink_force (get_pattern - (package, version, app_name, user_name)); - run_commands (user_name); - } - - private Gee.ArrayList - get_all_packages_for_user (string user_name, User user_db) throws Error - { - var ret = new Gee.ArrayList (); - foreach (var package in user_db.get_package_names ()) - ret.add (new UnpackedPackage - (package, user_db.get_version (package), - user_name)); - return ret; - } - - /** - * get_all_packages: - * @user_name: (allow-none): A user name, or null. - * - * Return a list of all unpacked packages. - * - * If running a user-level hook, this returns (package, version, - * user) for the current version of each package registered for each - * user, or only for a single user if user is not null. - * - * If running a system-level hook, this returns (package, version, - * null) for each version of each unpacked package. - * - * Returns: A list of all unpacked packages. - */ - private List - get_all_packages (string? user_name = null) throws Error - { - var ret = new Gee.ArrayList (); - if (is_user_level) { - if (user_name != null) { - var user_db = new User.for_user - (db, user_name); - ret.add_all (get_all_packages_for_user - (user_name, user_db)); - } else { - var users_db = new Users (db); - var user_names = users_db.get_user_names (); - foreach (var one_user_name in user_names) { - if (one_user_name.has_prefix ("@")) - continue; - var one_user_db = users_db.get_user - (one_user_name); - ret.add_all (get_all_packages_for_user - (one_user_name, one_user_db)); - } - } - } else { - foreach (var inst in db.get_packages (true)) - ret.add (new UnpackedPackage - (inst.package, inst.version)); - } - /* Flatten into a List to avoid introspection problems in - * case this method is ever exposed. - */ - var ret_list = new List (); - foreach (var element in ret) - ret_list.prepend (element); - ret_list.reverse (); - return ret_list; - } - - /** - * get_relevant_apps: - * @user_name: (allow-none): A user name, or null. - * - * Returns: A list of all applications relevant for this hook. - */ - private List - get_relevant_apps (string? user_name = null) throws Error - { - var ret = new List (); - var hook_name = get_hook_name (); - foreach (var unpacked in get_all_packages (user_name)) { - // if the app is not using a valid framework (anymore) - // we don't consider it relevant (anymore) - if (!validate_framework_for_package - (db, unpacked.package, unpacked.version)) - continue; - - var manifest_hooks = read_manifest_hooks - (db, unpacked.package, unpacked.version); - foreach (var app_name in manifest_hooks.get_members ()) { - var hooks = manifest_hooks.get_object_member - (app_name); - if (hooks.has_member (hook_name)) { - var relative_path = hooks.get_string_member - (hook_name); - ret.prepend (new RelevantApp - (unpacked.package, - unpacked.version, app_name, - unpacked.user_name, - relative_path)); - } - } - } - ret.reverse (); - return ret; - } - - /** - * install: - * @user_name: (allow-none): A user name, or null. - * - * Install files associated with this hook for any packages that - * attach to it. - */ - public void - install (string? user_name = null) throws Error - { - foreach (var app in get_relevant_apps (user_name)) - install_package (app.package, app.version, - app.app_name, app.relative_path, - app.user_name); - } - - /** - * remove: - * @user_name: (allow-none): A user name, or null. - * - * Remove files associated with this hook for any packages that - * attach to it. - */ - public void - remove (string? user_name = null) throws Error - { - foreach (var app in get_relevant_apps (user_name)) - remove_package (app.package, app.version, app.app_name, - app.user_name); - } - - /** - * sync: - * @user_name: (allow-none): A user name, or null. - * - * Run a hook for all installed packages (system-level if @user_name - * is null, otherwise user-level). - * - * This is useful to catch up with preinstalled packages. - */ - public void - sync (string? user_name = null) throws Error - { - if (! is_user_level) - assert (user_name == null); - - var seen = new Gee.HashSet (); - foreach (var app in get_relevant_apps (user_name)) { - unowned string package = app.package; - unowned string version = app.version; - unowned string app_name = app.app_name; - - seen.add (@"$(package)_$(app_name)_$(version)"); - if (is_user_level) { - var user_db = new User.for_user - (db, user_name); - var overlay_path = Path.build_filename - (user_db.get_overlay_db (), - package); - user_db.drop_privileges (); - try { - if (exists (overlay_path)) - user_db.raw_set_version - (package, version); - install_link (package, version, - app_name, - app.relative_path, - app.user_name, user_db); - } finally { - user_db.regain_privileges (); - } - } else - install_link (package, version, app_name, - app.relative_path); - } - - foreach (var prev in get_previous_entries (user_name)) { - unowned string package = prev.package; - unowned string version = prev.version; - unowned string app_name = prev.app_name; - if (! (@"$(package)_$(app_name)_$(version)" in seen)) - unlink_force (prev.path); - } - - run_commands (user_name); - } -} - -private Gee.TreeSet -get_app_hooks (Json.Object manifest) -{ - var items = new Gee.TreeSet (); /* sorted */ - foreach (var app_name in manifest.get_members ()) { - var hooks = manifest.get_object_member (app_name); - foreach (var hook_name in hooks.get_members ()) - items.add (new AppHook (app_name, hook_name)); - } - return items; -} - -/** - * package_install_hooks: - * @db: A #Click.DB. - * @package: A package name. - * @old_version: (allow-none): The old version of the package, or null. - * @new_version: The new version of the package. - * @user_name: (allow-none): A user name, or null. - * - * Run hooks following install of a Click package. - * - * If @user_name is null, only run system-level hooks. If @user_name is not - * null, only run user-level hooks for that user. - */ -public void -package_install_hooks (DB db, string package, string? old_version, - string new_version, string? user_name = null) - throws Error -{ - var old_manifest = read_manifest_hooks (db, package, old_version); - var new_manifest = read_manifest_hooks (db, package, new_version); - - /* Remove any targets for single-version hooks that were in the old - * manifest but not the new one. - */ - var old_app_hooks = get_app_hooks (old_manifest); - var new_app_hooks = get_app_hooks (new_manifest); - foreach (var app_hook in new_app_hooks) - old_app_hooks.remove (app_hook); - foreach (var app_hook in old_app_hooks) { - foreach (var hook in Hook.open_all (db, app_hook.hook_name)) { - if (hook.is_user_level != (user_name != null)) - continue; - if (! hook.is_single_version) - continue; - hook.remove_package (package, old_version, - app_hook.app_name, user_name); - } - } - - var new_app_names = new_manifest.get_members (); - new_app_names.sort (strcmp); - foreach (var app_name in new_app_names) { - var app_hooks = new_manifest.get_object_member (app_name); - var hook_names = app_hooks.get_members (); - hook_names.sort (strcmp); - foreach (var hook_name in hook_names) { - var relative_path = app_hooks.get_string_member - (hook_name); - foreach (var hook in Hook.open_all (db, hook_name)) { - if (hook.is_user_level != (user_name != null)) - continue; - hook.install_package (package, new_version, - app_name, relative_path, - user_name); - } - } - } -} - -/** - * package_remove_hooks: - * @db: A #Click.DB. - * @package: A package name. - * @old_version: The old version of the package. - * @user_name: (allow-none): A user name, or null. - * - * Run hooks following removal of a Click package. - * - * If @user_name is null, only run system-level hooks. If @user_name is not - * null, only run user-level hooks for that user. - */ -public void -package_remove_hooks (DB db, string package, string old_version, - string? user_name = null) throws Error -{ - var old_manifest = read_manifest_hooks (db, package, old_version); - - foreach (var app_hook in get_app_hooks (old_manifest)) { - foreach (var hook in Hook.open_all (db, app_hook.hook_name)) { - if (hook.is_user_level != (user_name != null)) - continue; - hook.remove_package (package, old_version, - app_hook.app_name, user_name); - } - } -} - -/** - * run_system_hooks: - * @db: A #Click.DB. - * - * Run system-level hooks for all installed packages. - * - * This is useful when starting up from images with preinstalled packages - * which may not have had their system-level hooks run properly when - * building the image. It is suitable for running at system startup. - */ -public void -run_system_hooks (DB db) throws Error -{ - db.gc (); - db.ensure_ownership (); - string[] failed = {}; - foreach (var hook in Hook.open_all (db)) { - if (! hook.is_user_level) { - try { - hook.sync (); - } catch (HooksError e) { - warning ("System-level hook %s failed: %s", - hook.name, e.message); - failed += hook.name; - } - } - } - if (failed.length != 0) - throw new HooksError.INCOMPLETE - ("Some system-level hooks failed: %s", - string.joinv (", ", failed)); -} - -/** - * run_user_hooks: - * @db: A #Click.DB. - * @user_name: (allow-none): A user name, or null to run hooks for the - * current user. - * - * Run user-level hooks for all installed packages. - * - * This is useful to catch up with packages that may have been preinstalled - * and registered for all users. It is suitable for running at session - * startup. - */ -public void -run_user_hooks (DB db, string? user_name = null) throws Error -{ - if (user_name == null) - user_name = Environment.get_user_name (); - string[] failed = {}; - foreach (var hook in Hook.open_all (db)) { - if (hook.is_user_level) { - try { - hook.sync (user_name); - } catch (HooksError e) { - warning ("User-level hook %s failed: %s", - hook.name, e.message); - failed += hook.name; - } - } - } - if (failed.length != 0) - throw new HooksError.INCOMPLETE - ("Some user-level hooks failed: %s", - string.joinv (", ", failed)); -} - -} diff -Nru click-0.4.43+16.04.20160203/lib/click/Makefile.am click-6.7/lib/click/Makefile.am --- click-0.4.43+16.04.20160203/lib/click/Makefile.am 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/lib/click/Makefile.am 1970-01-01 00:00:00.000000000 +0000 @@ -1,104 +0,0 @@ -AM_CPPFLAGS = \ - -I. \ - -D_GNU_SOURCE -AM_CFLAGS = \ - $(LIBCLICK_CFLAGS) \ - $(VALA_CFLAGS) \ - $(COVERAGE_CFLAGS) \ - -Wno-unused-but-set-variable \ - -Wno-unused-function \ - -Wno-unused-variable -VALAC = $(srcdir)/valac-wrapper -AM_VALAFLAGS = \ - -H click.h \ - --gir Click-0.4.gir \ - --library click-0.4 \ - --pkg posix \ - --pkg gee-0.8 \ - --pkg json-glib-1.0 \ - --target-glib 2.32 - -lib_LTLIBRARIES = libclick-0.4.la - -libclick_0_4_la_SOURCES = \ - database.vala \ - deb822.vala \ - framework.vala \ - hooks.vala \ - osextras.vala \ - paths.vala \ - posix-extra.vapi \ - query.vala \ - user.vala - -EXTRA_libclick_0_4_la_DEPENDENCIES = \ - click.sym - -HEADER_FILES = \ - click.h - -BUILT_SOURCES = paths.vala - -CLEANFILES = \ - $(BUILT_SOURCES) \ - $(HEADER_FILES) \ - libclick_0_4_la_vala.stamp \ - click.h \ - database.c \ - deb822.c \ - framework.c \ - hooks.c \ - osextras.c \ - paths.c \ - query.c \ - user.c - -do_subst = sed \ - -e 's,[@]sysconfdir[@],$(sysconfdir),g' \ - -e 's,[@]pkgdatadir[@],$(pkgdatadir),g' - -paths.vala: paths.vala.in Makefile - $(do_subst) < $(srcdir)/paths.vala.in > $@ - -includeclickdir = $(includedir)/click-0.4 -includeclick_HEADERS = \ - $(HEADER_FILES) - -libclick_0_4_la_LIBADD = $(LIBCLICK_LIBS) - -libclick_0_4_la_LDFLAGS = \ - $(COVERAGE_LDFLAGS) \ - -export-dynamic \ - -export-symbols $(srcdir)/click.sym \ - -version-info 4:0:4 - -EXTRA_DIST = click-0.4.pc.in - -pkgconfigdir = $(libdir)/pkgconfig -pkgconfig_DATA = click-0.4.pc - -INTROSPECTION_COMPILER_ARGS = \ - --includedir $(srcdir) \ - --includedir $(builddir) \ - --shared-library libclick-0.4.so.0 - -girdir = $(datadir)/gir-1.0 -gir_DATA = Click-0.4.gir - -typelibdir = $(libdir)/girepository-1.0 -typelib_DATA = Click-0.4.typelib - -# We intentionally don't install a VAPI at this point; libclick is written -# in Vala for implementation convenience, but this probably won't be -# appropriate for most of its clients. The intent is that the C API is -# canonical (with its reflections via gobject-introspection). -#vapidir = $(VAPIGEN_VAPIDIR) -#vapi_DATA = click-0.4.vapi -noinst_DATA = click-0.4.vapi - -CLEANFILES += $(gir_DATA) $(typelib_DATA) $(noinst_DATA) - -$(HEADER_FILES) $(gir_DATA) $(noinst_DATA): libclick_0_4_la_vala.stamp - -%.typelib: %.gir - $(INTROSPECTION_COMPILER) $(INTROSPECTION_COMPILER_ARGS) $< -o $@ diff -Nru click-0.4.43+16.04.20160203/lib/click/osextras.vala click-6.7/lib/click/osextras.vala --- click-0.4.43+16.04.20160203/lib/click/osextras.vala 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/lib/click/osextras.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,219 +0,0 @@ -/* Copyright (C) 2013, 2014 Canonical Ltd. - * Author: Colin Watson - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/* Extra OS-level utility functions. */ - -namespace Click { - -/** - * find_on_path: - * @command: A command name. - * - * Returns: True if the command is on the executable search path, otherwise - * false. - */ -public bool -find_on_path (string command) -{ - unowned string? path = Environment.get_variable ("PATH"); - if (path == null) - return false; - - var elements = path.split(":"); - foreach (var element in elements) { - if (element == "") - continue; - var filename = Path.build_filename (element, command); - if (FileUtils.test (filename, FileTest.IS_REGULAR) && - FileUtils.test (filename, FileTest.IS_EXECUTABLE)) - return true; - } - - return false; -} - -/** - * ensuredir: - * @directory: A path. - * - * If @directory does not already exist, create it and its parents as - * needed. - */ -public void -ensuredir (string directory) throws FileError -{ - if (is_dir (directory)) - return; - if (DirUtils.create_with_parents (directory, 0777) < 0) { - var code = FileUtils.error_from_errno (errno); - var quark = Quark.from_string ("g-file-error-quark"); - var err = new Error (quark, code, - "ensuredir %s failed: %s", - directory, strerror (errno)); - throw (FileError) err; - } -} - -/** - * unlink_force: - * @path: A path to unlink. - * - * Unlink path, without worrying about whether it exists. Errors other than - * %ENOENT will set the provided error location. - */ -public void -unlink_force (string path) throws FileError -{ - if (FileUtils.unlink (path) < 0 && errno != Posix.ENOENT) { - var code = FileUtils.error_from_errno (errno); - var quark = Quark.from_string ("g-file-error-quark"); - var err = new Error (quark, code, - "unlink %s failed: %s", - path, strerror (errno)); - throw (FileError) err; - } -} - -/** - * symlink_force: - * @target: The intended target of the symbolic link. - * @link_name: A path where the symbolic link should be created. - * - * Create a symlink link_name -> target, even if link_name exists. - */ -public void -symlink_force (string target, string link_name) throws FileError -{ - unlink_force (link_name); - /* This produces a harmless warning when compiling C code generated - * by valac 0.22.1: - * https://bugzilla.gnome.org/show_bug.cgi?id=725151 - */ - if (FileUtils.symlink (target, link_name) < 0) { - var code = FileUtils.error_from_errno (errno); - var quark = Quark.from_string ("g-file-error-quark"); - var err = new Error (quark, code, - "symlink %s -> %s failed: %s", - link_name, target, strerror (errno)); - throw (FileError) err; - } -} - -/** - * click_get_umask: - * - * Returns: The current umask. - */ -public int -get_umask () -{ - var mask = Posix.umask (0); - Posix.umask (mask); - return (int) mask; -} - -public class Dir : Object { - private SList entries; - private unowned SList cur; - - private Dir () - { - } - - /** - * open: - * @path: The path to the directory to open. - * @flags: For future use; currently must be set to 0. - * - * Like GLib.Dir.open(), but ignores %ENOENT. - */ - public static Dir? - open (string path, uint _flags = 0) throws FileError - { - Dir dir = new Dir (); - dir.entries = new SList (); - - GLib.Dir real_dir; - try { - real_dir = GLib.Dir.open (path, _flags); - string? name; - while ((name = real_dir.read_name ()) != null) - dir.entries.prepend (name); - dir.entries.sort (strcmp); - } catch (FileError e) { - if (! (e is FileError.NOENT)) - throw e; - } - - dir.cur = dir.entries; - return dir; - } - - /** - * read_name: - * - * Like GLib.Dir.read_name(), but returns entries in sorted order. - */ - public unowned string? - read_name () - { - if (cur == null) - return null; - unowned string name = cur.data; - cur = cur.next; - return name; - } - - internal class Iterator : Object { - private Dir dir; - - public Iterator (Dir dir) { - this.dir = dir; - } - - public unowned string? - next_value () - { - return dir.read_name (); - } - } - - internal Iterator - iterator () - { - return new Iterator (this); - } -} - -private bool -exists (string path) -{ - return FileUtils.test (path, FileTest.EXISTS); -} - -private bool -is_symlink (string path) -{ - return FileUtils.test (path, FileTest.IS_SYMLINK); -} - -private bool -is_dir (string path) -{ - return FileUtils.test (path, FileTest.IS_DIR); -} - -} diff -Nru click-0.4.43+16.04.20160203/lib/click/paths.vala.in click-6.7/lib/click/paths.vala.in --- click-0.4.43+16.04.20160203/lib/click/paths.vala.in 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/lib/click/paths.vala.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,58 +0,0 @@ -/* Copyright (C) 2013, 2014 Canonical Ltd. - * Author: Colin Watson - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/* Click paths. */ - -namespace Click { - -internal static const string hooks_dir = "@pkgdatadir@/hooks"; -internal static const string db_dir = "@sysconfdir@/click/databases"; -internal static const string frameworks_dir = "@pkgdatadir@/frameworks"; - -/** - * get_hooks_dir: - * - * Returns: The Click hooks directory. - */ -public string -get_hooks_dir () -{ - return hooks_dir; -} - -/** - * get_db_dir: - * - * Returns: The Click database configuration directory. - */ -public string -get_db_dir () -{ - return db_dir; -} - -/** - * get_frameworks_dir: - * - * Returns: The Click frameworks directory. - */ -public string -get_frameworks_dir () -{ - return frameworks_dir; -} - -} diff -Nru click-0.4.43+16.04.20160203/lib/click/posix-extra.vapi click-6.7/lib/click/posix-extra.vapi --- click-0.4.43+16.04.20160203/lib/click/posix-extra.vapi 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/lib/click/posix-extra.vapi 1970-01-01 00:00:00.000000000 +0000 @@ -1,48 +0,0 @@ -/* Copyright (C) 2013, 2014 Canonical Ltd. - * Author: Colin Watson - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/* Things that should be in posix.vapi, but aren't. */ - -[CCode (cprefix = "", lower_case_cprefix = "")] -namespace PosixExtra { - /* https://bugzilla.gnome.org/show_bug.cgi?id=725149 */ - [Compact] - [CCode (cname = "struct group", cheader_filename = "grp.h")] - public class Group { - public string gr_name; - public string gr_passwd; - public Posix.gid_t gr_gid; - [CCode (array_length = false, array_null_terminated = true)] - public string[] gr_mem; - } - [CCode (cheader_filename = "grp.h")] - public unowned Group? getgrent (); - - [CCode (cheader_filename = "unistd.h")] - public int getresgid (out Posix.gid_t rgid, out Posix.gid_t egid, out Posix.gid_t sgid); - [CCode (cheader_filename = "unistd.h")] - public int getresuid (out Posix.uid_t ruid, out Posix.uid_t euid, out Posix.uid_t suid); - [CCode (cheader_filename = "unistd.h")] - public int setegid (Posix.gid_t egid); - [CCode (cheader_filename = "unistd.h")] - public int seteuid (Posix.uid_t euid); - [CCode (cheader_filename = "sys/types.h,grp.h,unistd.h")] - public int setgroups (size_t size, [CCode (array_length = false)] Posix.gid_t[] list); - [CCode (cheader_filename = "unistd.h")] - public int setresgid (Posix.gid_t rgid, Posix.gid_t egid, Posix.gid_t sgid); - [CCode (cheader_filename = "unistd.h")] - public int setresuid (Posix.uid_t ruid, Posix.uid_t euid, Posix.uid_t suid); -} diff -Nru click-0.4.43+16.04.20160203/lib/click/query.vala click-6.7/lib/click/query.vala --- click-0.4.43+16.04.20160203/lib/click/query.vala 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/lib/click/query.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,58 +0,0 @@ -/* Copyright (C) 2013, 2014 Canonical Ltd. - * Author: Colin Watson - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/* Query information about installed Click packages. */ - -namespace Click { - -public errordomain QueryError { - /** - * A path could not be canonicalised. - */ - PATH, - /** - * No package directory was found. - */ - NO_PACKAGE_DIR -} - -public string -find_package_directory (string path) throws QueryError -{ - /* We require realpath (path, NULL) to be available. */ - var dir = Posix.realpath (path); - if (dir == null) - throw new QueryError.PATH - ("Failed to canonicalize %s: %s", - path, strerror (errno)); - - do { - var info_dir = Path.build_filename (dir, ".click", "info"); - if (is_dir (info_dir)) - return dir; - if (dir == ".") - break; - var new_dir = Path.get_dirname (dir); - if (new_dir == dir) - break; - dir = new_dir; - } while (dir != null); - - throw new QueryError.NO_PACKAGE_DIR - ("No package directory found for %s", path); -} - -} diff -Nru click-0.4.43+16.04.20160203/lib/click/user.vala click-6.7/lib/click/user.vala --- click-0.4.43+16.04.20160203/lib/click/user.vala 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/lib/click/user.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,939 +0,0 @@ -/* Copyright (C) 2013, 2014 Canonical Ltd. - * Author: Colin Watson - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/* Registry of user-installed Click packages. - * - * Click packages are installed into per-package/version directories, so it - * is quite feasible for more than one version of a given package to be - * installed at once, allowing per-user installations; for instance, one - * user of a tablet may be uncomfortable with granting some new permission - * to an app, but another may be just fine with it. To make this useful, we - * also need a registry of which users have which versions of each package - * installed. - * - * We might have chosen to use a proper database. However, a major goal of - * Click packages is extreme resilience; we must never get into a situation - * where some previous error in package installation or removal makes it - * hard for the user to install or remove other packages. Furthermore, the - * simpler application execution can be the better. So, instead, we use - * just about the simplest "database" format imaginable: a directory of - * symlinks per user. - */ - - -namespace Click { - - struct LogindUser { - uint32 uid; - string name; - string ObjectPath; - } - - /* the logind dbus interface */ - [DBus (name = "org.freedesktop.login1.Manager")] - interface LogindManager : Object { - public abstract LogindUser[] ListUsers () throws IOError; - } - -/* Pseudo-usernames selected to be invalid as a real username, and alluding - * to group syntaxes used in other systems. - */ -private const string ALL_USERS = "@all"; -private const string GC_IN_USE_USER = "@gcinuse"; - -/* Pseudo-versions. In this case the @ doesn't allude to group syntaxes, - * but since @ is conveniently invalid in version numbers we stick to the - * same prefix used for pseudo-usernames. - */ -private const string HIDDEN_VERSION = "@hidden"; - -public errordomain UserError { - /** - * Failure to get password file entry. - */ - GETPWNAM, - /** - * Failure to create database directory. - */ - CREATE_DB, - /** - * Failure to set ownership of database directory. - */ - CHOWN_DB, - /** - * Requested user does not exist. - */ - NO_SUCH_USER, - /** - * Failure to drop privileges. - */ - DROP_PRIVS, - /** - * Failure to regain privileges. - */ - REGAIN_PRIVS, - /** - * Requested package is hidden. - */ - HIDDEN_PACKAGE, - /** - * Requested package does not exist. - */ - NO_SUCH_PACKAGE, - /** - * Failure to rename file. - */ - RENAME -} - -private string -db_top (string root) -{ - /* This is deliberately outside any user's home directory so that it - * can safely be iterated etc. as root. - */ - return Path.build_filename (root, ".click", "users"); -} - -private string -db_for_user (string root, string user) -{ - return Path.build_filename (db_top (root), user); -} - -private void -try_create (string path) throws UserError -{ - if (DirUtils.create (path, 0777) < 0) - throw new UserError.CREATE_DB - ("Cannot create database directory %s: %s", - path, strerror (errno)); -} - -private class CachedPasswd : Object { - public Posix.uid_t uid; - public Posix.gid_t gid; - - public - CachedPasswd (Posix.uid_t uid, Posix.gid_t gid) - { - this.uid = uid; - this.gid = gid; - } -} - -private void -try_chown (string path, CachedPasswd pw) throws UserError -{ - if (Posix.chown (path, pw.uid, pw.gid) < 0) - throw new UserError.CHOWN_DB - ("Cannot set ownership of database directory %s: %s", - path, strerror (errno)); -} - -public class Users : Object { - public DB db { private get; construct; } - private CachedPasswd? click_pw; - - public Users (DB db) - { - Object (db: db); - click_pw = null; - } - - /** - * get_click_pw: - * - * Returns: The password file entry for the `clickpkg` user. - */ - private CachedPasswd - get_click_pw () throws UserError - { - if (click_pw == null) { - errno = 0; - unowned Posix.Passwd pw = Posix.getpwnam ("clickpkg"); - if (pw == null) - throw new UserError.GETPWNAM - ("Cannot get password file entry " + - "for clickpkg: %s", strerror (errno)); - click_pw = new CachedPasswd (pw.pw_uid, pw.pw_gid); - } - return click_pw; - } - - internal void - ensure_db () throws UserError - { - var create = new List (); - - /* Only modify the last database. */ - var try_path = db_top (db.overlay); - while (! exists (try_path)) { - create.prepend (try_path); - try_path = Path.get_dirname (try_path); - } - - foreach (var path in create) { - try_create (path); - if (Posix.geteuid () == 0) - try_chown (path, get_click_pw ()); - } - } - - /** - * get_user_names: - * - * Returns: A list of user names with registrations. - */ - public List - get_user_names () throws Error - { - var entries = new List (); - var seen = new Gee.HashSet (); - foreach (var single_db in db) { - var users_db = db_top (single_db.root); - foreach (var entry in Click.Dir.open (users_db)) { - if (entry in seen) - continue; - // the user is not a pseudo user and does not/no-longer exist - if (!entry.has_prefix ("@") && - Posix.getpwnam (entry) == null) - continue; - var path = Path.build_filename (users_db, - entry); - if (is_dir (path)) { - seen.add (entry.dup ()); - entries.prepend (entry.dup ()); - } - } - } - entries.reverse (); - return entries; - } - - /** - * get_user: - * @user_name: A user name. - * - * Returns: (transfer full): A new #ClickUser instance for @user. - */ - public User - get_user (string user_name) throws Error - { - foreach (var single_db in db) { - var path = db_for_user (single_db.root, user_name); - if (is_dir (path)) - /* We only require the user path to exist in - * any database; it doesn't matter which. - */ - return new User.for_user (db, user_name); - } - throw new UserError.NO_SUCH_USER( - "User %s does not exist in any database", user_name); - } -} - -public class User : Object { - public DB db { private get; construct; } - public string name { private get; construct; } - - private Users? users; - private CachedPasswd? user_pw; - private int dropped_privileges_count; - private Posix.mode_t? old_umask; - - private User (DB? db, string? name = null) throws FileError { - DB real_db; - string real_name; - if (db != null) - real_db = db; - else { - real_db = new DB (); - real_db.read (); - } - if (name != null) - real_name = name; - else - real_name = Environment.get_user_name ().dup (); - Object (db: real_db, name: real_name); - users = null; - user_pw = null; - dropped_privileges_count = 0; - old_umask = null; - } - - public User.for_user (DB? db, string? name = null) throws FileError { - this (db, name); - } - - public User.for_all_users (DB? db) throws FileError { - this (db, ALL_USERS); - } - - public User.for_gc_in_use (DB? db) throws FileError { - this (db, GC_IN_USE_USER); - } - - /** - * True if and only if this user is a pseudo-user. - */ - public bool is_pseudo_user { get { return name.has_prefix ("@"); } } - - /** - * True if and only if this user is the pseudo-user indicating that - * a registration was in use at the time of package removal. - */ - public bool is_gc_in_use { get { return name == GC_IN_USE_USER; } } - - /** - * get_user_pw: - * - * Returns: The password file entry for this user. - */ - private CachedPasswd - get_user_pw () throws UserError - { - assert (! is_pseudo_user); - - if (user_pw == null) { - errno = 0; - unowned Posix.Passwd pw = Posix.getpwnam (name); - if (pw == null) - throw new UserError.GETPWNAM - ("Cannot get password file entry for " + - "%s: %s", name, strerror (errno)); - user_pw = new CachedPasswd (pw.pw_uid, pw.pw_gid); - } - return user_pw; - } - - /** - * get_overlay_db: - * - * Returns: The path to the overlay database for this user, i.e. the - * path where new packages will be installed. - */ - public string - get_overlay_db () - { - return db_for_user (db.overlay, name); - } - - private void - ensure_db () throws UserError - { - if (users == null) - users = new Users (db); - users.ensure_db (); - var path = get_overlay_db (); - if (! exists (path)) { - try_create (path); - if (Posix.geteuid () == 0 && ! is_pseudo_user) - try_chown (path, get_user_pw ()); - } - } - - /* Note on privilege handling: - * We can normally get away without dropping privilege when reading, - * but some filesystems are strict about how much they let root work - * with user files (e.g. NFS root_squash). It is better to play it - * safe and drop privileges for any operations on the user's - * database. - */ - - private void - priv_drop_failure (string name) throws UserError - { - throw new UserError.DROP_PRIVS - ("Cannot drop privileges (%s): %s", - name, strerror (errno)); - } - - internal void - drop_privileges () throws UserError - { - if (dropped_privileges_count == 0 && - Posix.getuid () == 0 && ! is_pseudo_user) { - /* We don't bother with setgroups here; we only need - * the user/group of created filesystem nodes to be - * correct. - */ - var pw = get_user_pw (); - if (PosixExtra.setegid (pw.gid) < 0) - priv_drop_failure ("setegid"); - if (PosixExtra.seteuid (pw.uid) < 0) - priv_drop_failure ("seteuid"); - old_umask = Posix.umask (get_umask () | Posix.S_IWOTH); - } - - ++dropped_privileges_count; - } - - private void - priv_regain_failure (string name) - { - /* It is too dangerous to carry on from this point, even if - * the caller has an exception handler. - */ - error ("Cannot regain privileges (%s): %s", - name, strerror (errno)); - } - - internal void - regain_privileges () - { - --dropped_privileges_count; - - if (dropped_privileges_count == 0 && - Posix.getuid () == 0 && ! is_pseudo_user) { - if (old_umask != null) - Posix.umask (old_umask); - if (PosixExtra.seteuid (0) < 0) - priv_regain_failure ("seteuid"); - if (PosixExtra.setegid (0) < 0) - priv_regain_failure ("setegid"); - } - } - - private bool - is_valid_link (string path) - { - if (! is_symlink (path)) - return false; - - try { - var target = FileUtils.read_link (path); - return ! target.has_prefix ("@"); - } catch (FileError e) { - return false; - } - } - - private List - get_package_names_dropped () throws Error - { - var entries = new List (); - var hidden = new Gee.HashSet (); - for (int i = db.size - 1; i >= 0; --i) { - var user_db = db_for_user (db[i].root, name); - foreach (var entry in Click.Dir.open (user_db)) { - if (entries.find_custom (entry, strcmp) - != null || - entry in hidden) - continue; - var path = Path.build_filename (user_db, entry); - if (is_valid_link (path)) - entries.prepend (entry.dup ()); - else if (is_symlink (path)) - hidden.add (entry.dup ()); - } - - if (name != ALL_USERS) { - var all_users_db = db_for_user (db[i].root, - ALL_USERS); - foreach (var entry in Click.Dir.open - (all_users_db)) { - if (entries.find_custom (entry, strcmp) - != null || - entry in hidden) - continue; - var path = Path.build_filename - (all_users_db, entry); - if (is_valid_link (path)) - entries.prepend (entry.dup ()); - else if (is_symlink (path)) - hidden.add (entry.dup ()); - } - } - } - entries.reverse (); - return entries; - } - - /** - * get_package_names: - * - * Returns: (transfer full): A list of package names installed for - * this user. - */ - public List - get_package_names () throws Error - { - drop_privileges (); - try { - return get_package_names_dropped (); - } finally { - regain_privileges (); - } - } - - /** - * has_package_name: - * @package: A package name. - * - * Returns: True if this user has a version of @package registered, - * otherwise false. - */ - public bool - has_package_name (string package) - { - try { - get_version (package); - return true; - } catch (UserError e) { - return false; - } - } - - /** - * get_version: - * @package: A package name. - * - * Returns: The version of @package registered for this user. - */ - public string - get_version (string package) throws UserError - { - for (int i = db.size - 1; i >= 0; --i) { - var user_db = db_for_user (db[i].root, name); - var path = Path.build_filename (user_db, package); - drop_privileges (); - try { - if (is_valid_link (path)) { - try { - var target = - FileUtils.read_link - (path); - return Path.get_basename - (target); - } catch (FileError e) { - } - } else if (is_symlink (path)) - throw new UserError.HIDDEN_PACKAGE - ("%s is hidden for user %s", - package, name); - } finally { - regain_privileges (); - } - - var all_users_db = db_for_user (db[i].root, ALL_USERS); - path = Path.build_filename (all_users_db, package); - if (is_valid_link (path)) { - try { - var target = FileUtils.read_link - (path); - return Path.get_basename (target); - } catch (FileError e) { - } - } else if (is_symlink (path)) - throw new UserError.HIDDEN_PACKAGE - ("%s is hidden for all users", - package); - } - - throw new UserError.NO_SUCH_PACKAGE - ("%s does not exist in any database for user %s", - package, name); - } - - /** - * raw_set_version: - * @package: A package name. - * @version: A version string. - * - * Set the version of @package to @version, without running any - * hooks. Must be run with dropped privileges. - */ - internal void - raw_set_version (string package, string version) throws Error - { - assert (dropped_privileges_count > 0); - var user_db = get_overlay_db (); - var path = Path.build_filename (user_db, package); - var new_path = Path.build_filename (user_db, @".$package.new"); - var target = db.get_path (package, version); - var done = false; - if (is_valid_link (path)) { - unlink_force (path); - try { - if (get_version (package) == version) - done = true; - } catch (UserError e) { - } - } - if (done) - return; - symlink_force (target, new_path); - if (FileUtils.rename (new_path, path) < 0) - throw new UserError.RENAME - ("rename %s -> %s failed: %s", - new_path, path, strerror (errno)); - } - - /** - * set_version: - * @package: A package name. - * @version: A version string. - * - * Register version @version of @package for this user. - */ - public void - set_version (string package, string version) throws Error - { - /* Only modify the last database. */ - ensure_db (); - string? old_version = null; - try { - old_version = get_version (package); - } catch (UserError e) { - } - drop_privileges (); - try { - raw_set_version (package, version); - } finally { - regain_privileges (); - } - if (! is_pseudo_user) - package_install_hooks (db, package, - old_version, version, name); - - // run user hooks for all logged in users - if (name == ALL_USERS) - run_user_install_hooks_for_all_logged_in_users (package, old_version, version); - } - - private string[] - get_logged_in_users() - { - string[] logged_in_users = {}; - try { - LogindManager logind = Bus.get_proxy_sync ( - BusType.SYSTEM, - "org.freedesktop.login1", - "/org/freedesktop/login1"); - var users = logind.ListUsers(); - foreach (LogindUser user in users) - { - // FIXME: ideally we would read from /etc/adduser.conf - if(user.uid >= 1000 && user.uid <= 30000) - { - logged_in_users += user.name; - } - } - } catch (Error e) { - warning ("Can not connect to logind"); - } - return logged_in_users; - } - - private void - run_user_install_hooks_for_all_logged_in_users (string package, - string? old_version, - string version) throws IOError - { - foreach (string username in get_logged_in_users()) - package_install_hooks (db, package, - old_version, version, username); - } - - private void - run_user_remove_hooks_for_all_logged_in_users (string package, - string old_version) throws IOError - { - foreach (string username in get_logged_in_users()) - package_remove_hooks (db, package, - old_version, username); - } - - private string - get_dbus_session_bus_env_for_current_user() - { - string euid = "%i".printf((int)(Posix.geteuid ())); - var dbus_session_file = Path.build_filename( - "/run", "user", euid, "dbus-session"); - string session_env; - try { - FileUtils.get_contents(dbus_session_file, out session_env); - session_env = session_env.strip(); - } catch (Error e) { - warning("Can not get the dbus session to stop app (%s)", e.message); - } - return session_env; - } - - private bool - stop_single_app (string app_id) - { - // get the users dbus session when we run as root first as this - // is where ubuntu-app-stop listens - string[] envp = Environ.get(); - envp += get_dbus_session_bus_env_for_current_user(); - - string[] command = { - "ubuntu-app-stop", app_id - }; - bool res = false; - try { - int exit_status; - Process.spawn_sync - (null, command, envp, - SpawnFlags.SEARCH_PATH, - null, null, null, out exit_status); - res = Process.check_exit_status (exit_status); - } catch (Error e) { - res = false; - } - return res; - } - - private bool - stop_running_apps_for_package (string package, string version) - { - var res = true; - if (! find_on_path ("ubuntu-app-stop")) - return false; - - Json.Object manifest; - try { - manifest = get_manifest (package); - } catch (Error e) { - warning ("Can not get manifest for %s", package); - return false; - } - - if (! manifest.has_member ("hooks")) { - warning ("No hooks in manifest %s", package); - return false; - } - var hooks = manifest.get_object_member ("hooks"); - foreach (unowned string app_name in hooks.get_members ()) - res &= stop_single_app (@"$(package)_$(app_name)_$(version)"); - return res; - } - - /** - * remove: - * @package: A package name. - * - * Remove this user's registration of @package. - */ - public void - remove (string package) throws Error - { - /* Only modify the last database. */ - var user_db = get_overlay_db (); - var path = Path.build_filename (user_db, package); - string old_version; - if (is_valid_link (path)) { - var target = FileUtils.read_link (path); - old_version = Path.get_basename (target); - drop_privileges (); - try { - // stop before removing the path to the manifest - stop_running_apps_for_package (package, old_version); - unlink_force (path); - } finally { - regain_privileges (); - } - } else { - try { - old_version = get_version (package); - } catch (UserError e) { - throw new UserError.NO_SUCH_PACKAGE - ("%s does not exist in any database " + - "for user %s", package, name); - } - ensure_db (); - drop_privileges (); - try { - // stop before removing the path to the manifest - stop_running_apps_for_package (package, old_version); - symlink_force (HIDDEN_VERSION, path); - } finally { - regain_privileges (); - } - } - - if (! is_pseudo_user) - package_remove_hooks (db, package, old_version, name); - - // run user hooks for all logged in users - if (name == ALL_USERS) - run_user_remove_hooks_for_all_logged_in_users (package, old_version); - } - - /** - * get_path: - * @package: A package name. - * - * Returns: The path at which @package is registered for this user. - */ - public string - get_path (string package) throws UserError - { - for (int i = db.size - 1; i >= 0; --i) { - var user_db = db_for_user (db[i].root, name); - var path = Path.build_filename (user_db, package); - if (is_valid_link (path)) - return path; - else if (is_symlink (path)) - throw new UserError.HIDDEN_PACKAGE - ("%s is hidden for user %s", - package, name); - - var all_users_db = db_for_user (db[i].root, ALL_USERS); - path = Path.build_filename (all_users_db, package); - if (is_valid_link (path)) - return path; - else if (is_symlink (path)) - throw new UserError.HIDDEN_PACKAGE - ("%s is hidden for all users", - package); - } - - throw new UserError.NO_SUCH_PACKAGE - ("%s does not exist in any database for user %s", - package, name); - } - - /** - * get_manifest: - * @package: A package name. - * - * Returns: A #Json.Object containing a package's manifest. - * - * Since: 0.4.18 - */ - public Json.Object - get_manifest (string package) throws Error - { - var obj = db.get_manifest (package, get_version (package)); - /* Adjust _directory to point to the user registration path. */ - obj.set_string_member ("_directory", get_path (package)); - /* This should really be a boolean, but it was mistakenly - * made an int when the "_removable" key was first created. - * We may change this in future. - */ - obj.set_int_member ("_removable", - is_removable (package) ? 1 : 0); - return obj; - } - - /** - * get_manifest_as_string: - * @package: A package name. - * - * Returns: A JSON string containing a package's serialised - * manifest. - * This interface may be useful for clients with their own JSON - * parsing tools that produce representations more convenient for - * them. - * - * Since: 0.4.21 - */ - public string - get_manifest_as_string (string package) throws Error - { - var manifest = get_manifest (package); - var node = new Json.Node (Json.NodeType.OBJECT); - node.set_object (manifest); - var generator = new Json.Generator (); - generator.set_root (node); - return generator.to_data (null); - } - - /** - * get_manifests: - * - * Returns: A #Json.Array containing manifests of all packages - * registered for this user. The manifest may include additional - * dynamic keys (starting with an underscore) corresponding to - * dynamic properties of installed packages. - * - * Since: 0.4.18 - */ - public Json.Array - get_manifests () throws Error /* API-compatibility */ - { - var ret = new Json.Array (); - foreach (var package in get_package_names ()) { - try { - ret.add_object_element - (get_manifest (package)); - } catch (Error e) { - warning ("%s", e.message); - } - } - return ret; - } - - /** - * get_manifests_as_string: - * - * Returns: A JSON string containing a serialised array of manifests - * of all packages registered for this user. The manifest may - * include additional dynamic keys (starting with an underscore) - * corresponding to dynamic properties of installed packages. - * This interface may be useful for clients with their own JSON - * parsing tools that produce representations more convenient for - * them. - * - * Since: 0.4.21 - */ - public string - get_manifests_as_string () throws Error /* API-compatibility */ - { - var manifests = get_manifests (); - var node = new Json.Node (Json.NodeType.ARRAY); - node.set_array (manifests); - var generator = new Json.Generator (); - generator.set_root (node); - return generator.to_data (null); - } - - /** - * is_removable: - * @package: A package name. - * - * Returns: True if @package is removable for this user, otherwise - * False. - */ - public bool - is_removable (string package) - { - var user_db = get_overlay_db (); - var path = Path.build_filename (user_db, package); - if (exists (path)) - return true; - else if (is_symlink (path)) - /* Already hidden. */ - return false; - var all_users_db = db_for_user (db.overlay, ALL_USERS); - path = Path.build_filename (all_users_db, package); - if (is_valid_link (path)) - return true; - else if (is_symlink (path)) - /* Already hidden. */ - return false; - if (has_package_name (package)) - /* Not in overlay database, but can be hidden. */ - return true; - else - return false; - } -} - -} diff -Nru click-0.4.43+16.04.20160203/lib/click/valac-wrapper.in click-6.7/lib/click/valac-wrapper.in --- click-0.4.43+16.04.20160203/lib/click/valac-wrapper.in 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/lib/click/valac-wrapper.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,51 +0,0 @@ -#! /bin/sh -set -e - -# Wrapper for valac, working around the fact that the .gir files it -# generates are missing the shared-library attribute in the namespace tag. -# -# https://bugzilla.gnome.org/show_bug.cgi?id=642576 -# -# Passing --shared-library to g-ir-compiler isn't enough for us, because -# dh_girepository then fails to generate a shared library dependency. -# -# While we're here, work around showing up in our external header -# file. We're careful only to make use of it internally. - -VALAC="@VALAC@" - -"$VALAC" "$@" - -header= -gir= -library= - -# Keep this in sync with any options used in lib/click/Makefile.am. -C is -# emitted by automake. -eval set -- "$(getopt -o CH: -l gir:,library:,pkg:,target-glib: -- "$@")" || \ - { echo "$0: failed to parse valac options" >&2; exit 2; } -while :; do - case $1 in - -C) shift ;; - -H) header="$2"; shift 2 ;; - --pkg|--target-glib) shift 2 ;; - --gir) gir="$2"; shift 2 ;; - --library) library="$2"; shift 2 ;; - --) shift; break ;; - *) echo "$0: failed to parse valac options" >&2; exit 2 ;; - esac -done - -[ "$header" ] || { echo "$0: failed to find -H in valac options" >&2; exit 2; } -[ "$gir" ] || { echo "$0: failed to find --gir in valac options" >&2; exit 2; } -[ "$library" ] || \ - { echo "$0: failed to find --library in valac options" >&2; exit 2; } - -if egrep 'Gee|gee_' "$header"; then - echo "libgee should not be exposed in our public header file." >&2 - exit 1 -fi -sed -i '/^#include $/d' "$header" - -sed -i 's/\(\n') - doctype = self._builder._doctype - outfile.write("\n" % ( - doctype[0], - "" if doctype[1] is None else "\n PUBLIC '%s'" % doctype[1], - "" if doctype[2] is None else "\n SYSTEM '%s'" % doctype[2])) - try: - self._tree.write(outfile, encoding="unicode") - finally: - outfile.close() - - -def main(): - parser = OptionParser(usage="%prog FILE [...]") - parser.add_option( - "-o", "--output", help="output file name (default: stdout)") - options, args = parser.parse_args() - if not args: - parser.error("need at least one input file") - coverage = Coverage() - for arg in args: - coverage.merge(arg) - coverage.write(options.output) - - -if __name__ == "__main__": - main() diff -Nru click-0.4.43+16.04.20160203/PKG-INFO click-6.7/PKG-INFO --- click-0.4.43+16.04.20160203/PKG-INFO 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/PKG-INFO 2017-01-06 22:41:11.000000000 +0000 @@ -0,0 +1,13 @@ +Metadata-Version: 1.1 +Name: click +Version: 6.7 +Summary: A simple wrapper around optparse for powerful command line utilities. +Home-page: http://github.com/mitsuhiko/click +Author: Armin Ronacher +Author-email: armin.ronacher@active-4.com +License: UNKNOWN +Description: UNKNOWN +Platform: UNKNOWN +Classifier: License :: OSI Approved :: BSD License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 diff -Nru click-0.4.43+16.04.20160203/pk-plugin/com.ubuntu.click.pkla click-6.7/pk-plugin/com.ubuntu.click.pkla --- click-0.4.43+16.04.20160203/pk-plugin/com.ubuntu.click.pkla 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/pk-plugin/com.ubuntu.click.pkla 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ -[Allow installation of Click packages] -Identity=unix-user:phablet -Action=com.ubuntu.click.package-install;com.ubuntu.click.package-remove -ResultAny=yes diff -Nru click-0.4.43+16.04.20160203/pk-plugin/com.ubuntu.click.policy.in click-6.7/pk-plugin/com.ubuntu.click.policy.in --- click-0.4.43+16.04.20160203/pk-plugin/com.ubuntu.click.policy.in 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/pk-plugin/com.ubuntu.click.policy.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,31 +0,0 @@ - - - - - Click - https://launchpad.net/click - package-x-generic - - - <_description gettext-domain="click">Install package - <_message gettext-domain="click">To install software, you need to authenticate. - - auth_self - auth_self - yes - - - - - <_description gettext-domain="click">Remove package - <_message gettext-domain="click">To remove software, you need to authenticate. - - auth_self - auth_self - yes - - - - diff -Nru click-0.4.43+16.04.20160203/pk-plugin/Makefile.am click-6.7/pk-plugin/Makefile.am --- click-0.4.43+16.04.20160203/pk-plugin/Makefile.am 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/pk-plugin/Makefile.am 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -plugindir = @pkpluginlibdir@/packagekit-plugins - -plugin_LTLIBRARIES = libpk_plugin_click.la - -libpk_plugin_click_la_SOURCES = pk-plugin-click.c - -libpk_plugin_click_la_CPPFLAGS = -I$(top_builddir)/lib/click - -libpk_plugin_click_la_CFLAGS = \ - @PKPLUGIN_CFLAGS@ \ - $(COVERAGE_CFLAGS) - -libpk_plugin_click_la_LIBADD = \ - $(top_builddir)/lib/click/libclick-0.4.la \ - @PKPLUGIN_LIBS@ - -libpk_plugin_click_la_LDFLAGS = \ - $(COVERAGE_LDFLAGS) \ - -avoid-version - -polkit_policydir = $(datadir)/polkit-1/actions - -dist_polkit_policy_DATA = com.ubuntu.click.policy - -@INTLTOOL_POLICY_RULE@ - -EXTRA_DIST = com.ubuntu.click.policy.in -DISTCLEANFILES = com.ubuntu.click.policy - -polkit_localauthoritydir = $(localstatedir)/lib/polkit-1/localauthority/10-vendor.d - -dist_polkit_localauthority_DATA = com.ubuntu.click.pkla diff -Nru click-0.4.43+16.04.20160203/pk-plugin/pk-plugin-click.c click-6.7/pk-plugin/pk-plugin-click.c --- click-0.4.43+16.04.20160203/pk-plugin/pk-plugin-click.c 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/pk-plugin/pk-plugin-click.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,1000 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- - * - * Copyright (C) 2010-2013 Matthias Klumpp - * Copyright (C) 2011 Richard Hughes - * Copyright (C) 2013 Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#define _GNU_SOURCE - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#define I_KNOW_THE_PACKAGEKIT_GLIB2_API_IS_SUBJECT_TO_CHANGE -#include -#define I_KNOW_THE_PACKAGEKIT_PLUGIN_API_IS_SUBJECT_TO_CHANGE -#include - -#include "click.h" - - -struct PkPluginPrivate { - guint dummy; -}; - -#define DEFAULT_PATH \ - "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" - -/** - * click_is_click_file: - * - * Check if a given file is a Click package file. - */ -static gboolean -click_is_click_file (const gchar *filename) -{ - gboolean ret = FALSE; - GFile *file; - GFileInfo *info = NULL; - const gchar *content_type; - - file = g_file_new_for_path (filename); - info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, - G_FILE_QUERY_INFO_NONE, NULL, NULL); - if (!info) - goto out; - content_type = g_file_info_get_content_type (info); - if (strcmp (content_type, "application/x-click") == 0) - ret = TRUE; - -out: - g_clear_object (&info); - g_clear_object (&file); - return ret; -} - -static gchar ** -click_filter_click_files (PkTransaction *transaction, gchar **files) -{ - gchar **native_files = NULL; - gchar **click_files = NULL; - GPtrArray *native = NULL; - GPtrArray *click = NULL; - gint i; - gboolean ret = FALSE; - - /* Are there any Click packages at all? If not, we can bail out - * early. - */ - for (i = 0; files[i]; ++i) { - ret = click_is_click_file (files[i]); - if (ret) - break; - } - if (!ret) - goto out; - - /* Find and filter Click packages. */ - native = g_ptr_array_new_with_free_func (g_free); - click = g_ptr_array_new_with_free_func (g_free); - - for (i = 0; files[i]; ++i) { - ret = click_is_click_file (files[i]); - g_ptr_array_add (ret ? click : native, g_strdup (files[i])); - } - - native_files = pk_ptr_array_to_strv (native); - click_files = pk_ptr_array_to_strv (click); - pk_transaction_set_full_paths (transaction, native_files); - -out: - g_strfreev (native_files); - if (native) - g_ptr_array_unref (native); - if (click) - g_ptr_array_unref (click); - return click_files; -} - -static gboolean -click_pkid_data_is_click (const gchar *data) -{ - gchar **tokens; - gboolean ret; - - tokens = g_strsplit (data, ",", 2); - ret = g_strcmp0 (tokens[0], "local:click") == 0 || - g_strcmp0 (tokens[0], "installed:click") == 0; - g_strfreev (tokens); - - return ret; -} - -/** - * click_is_click_package: - * - * Check if a given PackageKit package-id is a Click package. - */ -static gboolean -click_is_click_package (const gchar *package_id) -{ - gchar **parts = NULL; - gboolean ret = FALSE; - - parts = pk_package_id_split (package_id); - if (!parts) - goto out; - ret = click_pkid_data_is_click (parts[PK_PACKAGE_ID_DATA]); - -out: - g_strfreev (parts); - return ret; -} - -static gchar ** -click_filter_click_packages (PkTransaction *transaction, gchar **package_ids) -{ - gchar **native_package_ids = NULL; - gchar **click_package_ids = NULL; - GPtrArray *native = NULL; - GPtrArray *click = NULL; - gint i; - gboolean ret = FALSE; - - /* Are there any Click packages at all? If not, we can bail out - * early. - */ - for (i = 0; package_ids[i]; ++i) { - ret = click_is_click_package (package_ids[i]); - if (ret) - break; - } - if (!ret) - goto out; - - /* Find and filter Click packages. */ - native = g_ptr_array_new_with_free_func (g_free); - click = g_ptr_array_new_with_free_func (g_free); - - for (i = 0; package_ids[i]; ++i) { - ret = click_is_click_package (package_ids[i]); - g_ptr_array_add (ret ? click : native, - g_strdup (package_ids[i])); - } - - native_package_ids = pk_ptr_array_to_strv (native); - click_package_ids = pk_ptr_array_to_strv (click); - pk_transaction_set_package_ids (transaction, native_package_ids); - -out: - g_strfreev (native_package_ids); - if (native) - g_ptr_array_unref (native); - if (click) - g_ptr_array_unref (click); - return click_package_ids; -} - -/** - * click_get_username_for_uid: - * - * Return the username corresponding to a given user ID, or NULL. The - * caller is responsible for freeing the result. - */ -static gchar * -click_get_username_for_uid (uid_t uid) -{ - struct passwd pwbuf, *pwbufp; - char *buf = NULL; - size_t buflen; - gchar *username = NULL; - - buflen = sysconf (_SC_GETPW_R_SIZE_MAX); - if (buflen == -1) - buflen = 1024; - buf = g_malloc (buflen); - for (;;) { - int ret; - - /* TODO: getpwuid_r is apparently a portability headache; - * see glib/gio/glocalfileinfo.c. But for now we only care - * about Linux. - */ - ret = getpwuid_r (uid, &pwbuf, buf, buflen, &pwbufp); - if (pwbufp) - break; - if (ret != ERANGE) - goto out; - - buflen *= 2; /* TODO: check overflow */ - buf = g_realloc (buf, buflen); - } - - username = g_strdup (pwbuf.pw_name); - -out: - g_free (buf); - return username; -} - -/** - * click_get_envp: - * - * Return the environment needed by click. This is the same as the - * environment we got, except with a reasonable PATH (PackageKit clears its - * environment by default). - */ -static gchar ** -click_get_envp (void) -{ - gchar **environ; - gchar **env_item; - guint env_len; - - environ = g_get_environ (); - env_len = 0; - for (env_item = environ; env_item && *env_item; ++env_item) { - if (strncmp (*env_item, "PATH=", sizeof ("PATH=") - 1) == 0) - return environ; - ++env_len; - } - - env_len = environ ? g_strv_length (environ) : 0; - environ = g_realloc_n (environ, env_len + 2, sizeof (*environ)); - environ[env_len] = g_strdup ("PATH=" DEFAULT_PATH); - environ[env_len + 1] = NULL; - return environ; -} - -static void -click_pk_error (PkPlugin *plugin, PkErrorEnum code, - const char *summary, const char *extra) -{ - if (pk_backend_job_get_is_error_set (plugin->job)) { - /* PK already has an error; just log this. */ - g_warning ("%s", summary); - if (extra) - g_warning ("%s", extra); - } else if (extra) - pk_backend_job_error_code - (plugin->job, code, "%s\n%s", summary, extra); - else - pk_backend_job_error_code (plugin->job, code, "%s", summary); -} - -static JsonParser * -click_get_manifest (PkPlugin *plugin, const gchar *filename) -{ - gboolean ret; - gchar **argv = NULL; - gint i; - gchar **envp = NULL; - gchar *manifest_text = NULL; - gchar *click_stderr = NULL; - gint click_status; - JsonParser *parser = NULL; - - argv = g_malloc0_n (4, sizeof (*argv)); - i = 0; - argv[i++] = g_strdup ("click"); - argv[i++] = g_strdup ("info"); - argv[i++] = g_strdup (filename); - envp = click_get_envp (); - ret = g_spawn_sync (NULL, argv, envp, G_SPAWN_SEARCH_PATH, - NULL, NULL, &manifest_text, &click_stderr, - &click_status, NULL); - if (!ret) - goto out; - if (!g_spawn_check_exit_status (click_status, NULL)) { - gchar *summary = g_strdup_printf - ("\"click info %s\" failed.", filename); - click_pk_error (plugin, PK_ERROR_ENUM_INTERNAL_ERROR, - summary, click_stderr); - g_free (summary); - goto out; - } - - parser = json_parser_new (); - if (!parser) - goto out; - json_parser_load_from_data (parser, manifest_text, -1, NULL); - -out: - g_strfreev (argv); - g_strfreev (envp); - g_free (manifest_text); - g_free (click_stderr); - - return parser; -} - -static gchar * -click_get_field_string (JsonObject *manifest, const gchar *field) -{ - JsonNode *node; - - node = json_object_get_member (manifest, field); - if (!node) - return NULL; - return json_node_dup_string (node); -} - -static JsonObject * -click_get_field_object (JsonObject *manifest, const gchar *field) -{ - JsonNode *node; - - node = json_object_get_member (manifest, field); - if (!node) - return NULL; - /* Note that this does not take a reference. */ - return json_node_get_object (node); -} - -static gboolean -click_get_field_boolean (JsonObject *manifest, const gchar *field, - gboolean def) -{ - JsonNode *node; - - node = json_object_get_member (manifest, field); - if (!node) - return def; - return json_node_get_boolean (node); -} - -static JsonArray * -click_get_list (PkPlugin *plugin, PkTransaction *transaction) -{ - gchar *username = NULL; - ClickUser *registry = NULL; - JsonArray *array = NULL; - GError *error = NULL; - - username = click_get_username_for_uid - (pk_transaction_get_uid (transaction)); - registry = click_user_new_for_user (NULL, username, &error); - if (error) { - click_pk_error (plugin, PK_ERROR_ENUM_INTERNAL_ERROR, - "Unable to read Click database.", - error->message); - goto out; - } - array = click_user_get_manifests (registry, &error); - if (error) { - click_pk_error (plugin, PK_ERROR_ENUM_INTERNAL_ERROR, - "Unable to get Click package manifests.", - error->message); - goto out; - } - -out: - if (error) - g_error_free (error); - g_clear_object (®istry); - g_free (username); - - return array; -} - -static gchar * -click_build_pkid_data (const gchar *data_prefix, JsonObject *manifest) -{ - gint n_elements = 0; - gchar **elements = NULL; - gint i; - JsonObject *hooks; - GList *hooks_members = NULL, *hooks_iter; - gchar *data = NULL; - - hooks = click_get_field_object (manifest, "hooks"); - - n_elements = 3; /* data_prefix, removable, terminator */ - if (hooks) - n_elements += json_object_get_size (hooks); - elements = g_new0 (gchar *, n_elements); - if (!elements) - goto out; - - i = 0; - elements[i++] = g_strdup (data_prefix); - /* A missing "_removable" entry in the manifest means that we just - * installed the package, so it must be removable. - */ - if (click_get_field_boolean (manifest, "_removable", TRUE)) - elements[i++] = g_strdup ("removable=1"); - else - elements[i++] = g_strdup ("removable=0"); - if (hooks) { - hooks_members = json_object_get_members (hooks); - for (hooks_iter = hooks_members; hooks_iter; - hooks_iter = hooks_iter->next) { - g_assert (i < n_elements - 1); - elements[i++] = g_strdup_printf - ("app_name=%s", (gchar *) hooks_iter->data); - } - } - elements[i] = NULL; - data = g_strjoinv (",", elements); - -out: - g_strfreev (elements); - if (hooks_members) - g_list_free (hooks_members); - return data; -} - -static gchar * -click_build_pkid (PkPlugin *plugin, JsonObject *manifest, - const gchar *data_prefix) -{ - gchar *name = NULL; - gchar *version = NULL; - gchar *architecture = NULL; - gchar *data = NULL; - gchar *pkid = NULL; - - if (!manifest) - goto out; - name = click_get_field_string (manifest, "name"); - if (!name) - goto out; - version = click_get_field_string (manifest, "version"); - if (!version) - goto out; - architecture = click_get_field_string (manifest, "architecture"); - if (!architecture) - architecture = g_strdup (""); - data = click_build_pkid_data (data_prefix, manifest); - pkid = pk_package_id_build (name, version, architecture, data); - -out: - g_free (name); - g_free (version); - g_free (architecture); - g_free (data); - return pkid; -} - -static gboolean -click_split_pkid (const gchar *package_id, gchar **name, gchar **version, - gchar **architecture) -{ - gchar **parts = NULL; - gboolean ret = FALSE; - - parts = pk_package_id_split (package_id); - if (!parts) - goto out; - if (!click_pkid_data_is_click (parts[PK_PACKAGE_ID_DATA])) - goto out; - if (name) - *name = g_strdup (parts[PK_PACKAGE_ID_NAME]); - if (version) - *version = g_strdup (parts[PK_PACKAGE_ID_VERSION]); - if (architecture) - *architecture = g_strdup (parts[PK_PACKAGE_ID_ARCH]); - ret = TRUE; - -out: - g_strfreev (parts); - return ret; -} - -static gboolean -click_install_file (PkPlugin *plugin, PkTransaction *transaction, - const gchar *filename) -{ - gboolean ret = FALSE; - gchar **argv = NULL; - gint i; - gchar *username = NULL; - gchar **envp = NULL; - gchar *click_stderr = NULL; - gint click_status; - JsonParser *parser = NULL; - JsonObject *manifest; - gchar *pkid = NULL; - - argv = g_malloc0_n (7, sizeof (*argv)); - i = 0; - argv[i++] = g_strdup ("click"); - argv[i++] = g_strdup ("install"); - if (!pk_bitfield_contain (pk_transaction_get_transaction_flags (transaction), - PK_TRANSACTION_FLAG_ENUM_ONLY_TRUSTED)) { - argv[i++] = g_strdup ("--allow-unauthenticated"); - } - username = click_get_username_for_uid - (pk_transaction_get_uid (transaction)); - if (username) - argv[i++] = g_strdup_printf ("--user=%s", username); - /* TODO: make --force-missing-framework configurable */ - argv[i++] = g_strdup (filename); - envp = click_get_envp (); - ret = g_spawn_sync (NULL, argv, envp, - G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL, - NULL, NULL, NULL, &click_stderr, &click_status, - NULL); - if (!ret) - goto out; - if (!g_spawn_check_exit_status (click_status, NULL)) { - gchar *summary = g_strdup_printf ("%s failed to install.", - filename); - click_pk_error (plugin, - PK_ERROR_ENUM_PACKAGE_FAILED_TO_INSTALL, - summary, click_stderr); - g_free (summary); - ret = FALSE; - goto out; - } - - parser = click_get_manifest (plugin, filename); - if (parser) { - manifest = json_node_get_object - (json_parser_get_root (parser)); - pkid = click_build_pkid (plugin, manifest, "installed:click"); - } - if (!pk_backend_job_get_is_error_set (plugin->job)) { - pk_backend_job_package (plugin->job, PK_INFO_ENUM_INSTALLED, - pkid, "summary goes here"); - ret = TRUE; - } - -out: - g_strfreev (argv); - g_free (username); - g_strfreev (envp); - g_free (click_stderr); - g_clear_object (&parser); - g_free (pkid); - - return ret; -} - -static void -click_install_files (PkPlugin *plugin, PkTransaction *transaction, - gchar **filenames) -{ - gboolean ret = FALSE; - gint i; - - for (i = 0; filenames[i]; ++i) { - g_debug ("Click: installing %s", filenames[i]); - ret = click_install_file (plugin, transaction, filenames[i]); - if (!ret) - break; - } -} - -static void -click_get_packages_one (JsonArray *array, guint index, JsonNode *element_node, - gpointer data) -{ - PkPlugin *plugin; - JsonObject *manifest; - const gchar *title = NULL; - gchar *pkid = NULL; - - plugin = (PkPlugin *) data; - manifest = json_node_get_object (element_node); - if (!manifest) - return; - if (json_object_has_member (manifest, "title")) - title = json_object_get_string_member (manifest, "title"); - if (!title) - title = ""; - - pkid = click_build_pkid (plugin, manifest, "installed:click"); - if (pkid) - pk_backend_job_package (plugin->job, PK_INFO_ENUM_INSTALLED, - pkid, title); - - g_free (pkid); -} - -static void -click_get_packages (PkPlugin *plugin, PkTransaction *transaction) -{ - JsonArray *array = NULL; - - array = click_get_list (plugin, transaction); - if (!array) - return; - json_array_foreach_element (array, click_get_packages_one, plugin); - json_array_unref (array); -} - -static gboolean -click_remove_package (PkPlugin *plugin, PkTransaction *transaction, - const gchar *package_id) -{ - gboolean ret = FALSE; - gchar *username = NULL; - gchar *name = NULL; - gchar *version = NULL; - ClickDB *db = NULL; - ClickUser *registry = NULL; - gchar *old_version = NULL; - GError *error = NULL; - gchar *summary = NULL; - - // PK does not set a PATH, but we need one for removal - const gchar *old_path = g_getenv("PATH"); - if(old_path == NULL) - g_setenv("PATH", DEFAULT_PATH, 0); - - username = click_get_username_for_uid - (pk_transaction_get_uid (transaction)); - if (!username) { - g_error ("Click: cannot remove packages without a username"); - goto out; - } - if (!click_split_pkid (package_id, &name, &version, NULL)) { - g_error ("Click: cannot parse package ID '%s'", package_id); - goto out; - } - db = click_db_new (); - click_db_read (db, NULL, &error); - if (error) { - summary = g_strdup_printf - ("Unable to read Click database while removing %s.", - package_id); - click_pk_error (plugin, PK_ERROR_ENUM_PACKAGE_FAILED_TO_REMOVE, - summary, error->message); - goto out; - } - registry = click_user_new_for_user (db, username, &error); - if (error) { - summary = g_strdup_printf - ("Unable to read Click database while removing %s.", - package_id); - click_pk_error (plugin, PK_ERROR_ENUM_PACKAGE_FAILED_TO_REMOVE, - summary, error->message); - goto out; - } - old_version = click_user_get_version (registry, name, &error); - if (error) { - summary = g_strdup_printf - ("Unable to get current version of Click package %s.", - name); - click_pk_error (plugin, PK_ERROR_ENUM_PACKAGE_FAILED_TO_REMOVE, - summary, error->message); - goto out; - } - if (strcmp (old_version, version) != 0) { - summary = g_strdup_printf - ("Not removing Click package %s %s; does not match " - "current version %s.", name, version, old_version); - click_pk_error (plugin, PK_ERROR_ENUM_PACKAGE_FAILED_TO_REMOVE, - summary, NULL); - goto out; - } - click_user_remove (registry, name, &error); - if (error) { - summary = g_strdup_printf ("Failed to remove %s.", package_id); - click_pk_error (plugin, PK_ERROR_ENUM_PACKAGE_FAILED_TO_REMOVE, - summary, error->message); - goto out; - } - click_db_maybe_remove (db, name, version, &error); - if (error) { - summary = g_strdup_printf ("Failed to remove %s.", package_id); - click_pk_error (plugin, PK_ERROR_ENUM_PACKAGE_FAILED_TO_REMOVE, - summary, error->message); - goto out; - } - /* TODO: remove data? */ - ret = TRUE; - -out: - if(old_path == NULL) - g_unsetenv("PATH"); - - g_free (summary); - if (error) - g_error_free (error); - g_free (old_version); - g_clear_object (®istry); - g_clear_object (&db); - g_free (version); - g_free (name); - g_free (username); - - return ret; -} - -static void -click_remove_packages (PkPlugin *plugin, PkTransaction *transaction, - gchar **package_ids) -{ - gboolean ret = FALSE; - gint i; - - for (i = 0; package_ids[i]; ++i) { - g_debug ("Click: removing %s", package_ids[i]); - ret = click_remove_package (plugin, transaction, - package_ids[i]); - if (!ret) - break; - } -} - -struct click_search_data { - PkPlugin *plugin; - gchar **values; - gboolean search_details; -}; - -static void -click_search_emit (PkPlugin *plugin, JsonObject *manifest, const gchar *title) -{ - gchar *package_id; - - package_id = click_build_pkid (plugin, manifest, "installed:click"); - if (!package_id) - return; - g_debug ("Found package: %s", package_id); - pk_backend_job_package (plugin->job, PK_INFO_ENUM_INSTALLED, - package_id, title); - - g_free (package_id); -} - -static void -click_search_one (JsonArray *array, guint index, JsonNode *element_node, - gpointer vdata) -{ - struct click_search_data *data; - JsonObject *manifest; - const gchar *name = NULL; - const gchar *title = NULL; - const gchar *description = NULL; - gchar **value; - - data = (struct click_search_data *) vdata; - manifest = json_node_get_object (element_node); - if (!manifest) - return; - name = json_object_get_string_member (manifest, "name"); - if (!name) - return; - if (data->search_details && json_object_has_member (manifest, "title")) - title = json_object_get_string_member (manifest, "title"); - if (!title) - title = ""; - if (data->search_details && - json_object_has_member (manifest, "description")) - description = json_object_get_string_member (manifest, - "description"); - if (!description) - description = ""; - - for (value = data->values; *value; ++value) { - if (strcasestr (name, *value)) { - click_search_emit (data->plugin, manifest, title); - break; - } - if (data->search_details && - (strcasestr (title, *value) || - strcasestr (description, *value))) { - click_search_emit (data->plugin, manifest, title); - break; - } - } -} - -static void -click_search (PkPlugin *plugin, PkTransaction *transaction, gchar **values, - gboolean search_details) -{ - JsonArray *array = NULL; - struct click_search_data data; - - array = click_get_list (plugin, transaction); - if (!array) - return; - data.plugin = plugin; - data.values = values; - data.search_details = search_details; - json_array_foreach_element (array, click_search_one, &data); - json_array_unref (array); -} - -static void -click_skip_native_backend (PkPlugin *plugin) -{ - if (!pk_backend_job_get_is_error_set (plugin->job)) - pk_backend_job_set_exit_code (plugin->job, - PK_EXIT_ENUM_SKIP_TRANSACTION); -} - -/** - * pk_plugin_get_description: - */ -const gchar * -pk_plugin_get_description (void) -{ - return "Support for Click packages"; -} - -/** - * pk_plugin_initialize: - */ -void -pk_plugin_initialize (PkPlugin *plugin) -{ - /* create private area */ - plugin->priv = PK_TRANSACTION_PLUGIN_GET_PRIVATE (PkPluginPrivate); - - /* tell PK we might be able to handle these */ - pk_backend_implement (plugin->backend, PK_ROLE_ENUM_INSTALL_FILES); - pk_backend_implement (plugin->backend, PK_ROLE_ENUM_GET_PACKAGES); - pk_backend_implement (plugin->backend, PK_ROLE_ENUM_REMOVE_PACKAGES); -} - -/** - * pk_plugin_transaction_content_types: - */ -void -pk_plugin_transaction_content_types (PkPlugin *plugin, - PkTransaction *transaction) -{ - pk_transaction_add_supported_content_type (transaction, - "application/x-click"); -} - -/** - * pk_plugin_transaction_started: - */ -void -pk_plugin_transaction_started (PkPlugin *plugin, PkTransaction *transaction) -{ - PkRoleEnum role; - gchar **full_paths = NULL; - gchar **package_ids = NULL; - gchar **click_data = NULL; - gchar **values; - PkBitfield flags; - gboolean simulating; - - g_debug ("Processing transaction"); - - pk_backend_job_reset (plugin->job); - pk_transaction_signals_reset (transaction, plugin->job); - pk_backend_job_set_status (plugin->job, PK_STATUS_ENUM_SETUP); - - role = pk_transaction_get_role (transaction); - - flags = pk_transaction_get_transaction_flags (transaction); - simulating = pk_bitfield_contain (flags, - PK_TRANSACTION_FLAG_ENUM_SIMULATE); - - switch (role) { - case PK_ROLE_ENUM_INSTALL_FILES: - /* TODO: Simulation needs to be smarter - backend - * needs to Simulate() with remaining packages. - */ - full_paths = pk_transaction_get_full_paths - (transaction); - click_data = click_filter_click_files (transaction, - full_paths); - if (!simulating && click_data) - click_install_files (plugin, transaction, - click_data); - - full_paths = pk_transaction_get_full_paths - (transaction); - if (g_strv_length (full_paths) == 0) - click_skip_native_backend (plugin); - break; - - case PK_ROLE_ENUM_GET_PACKAGES: - /* TODO: Handle simulation? */ - if (!simulating) - click_get_packages (plugin, transaction); - break; - - case PK_ROLE_ENUM_REMOVE_PACKAGES: - package_ids = pk_transaction_get_package_ids - (transaction); - click_data = click_filter_click_packages (transaction, - package_ids); - if (!simulating && click_data) - click_remove_packages (plugin, transaction, - click_data); - - package_ids = pk_transaction_get_package_ids - (transaction); - if (g_strv_length (package_ids) == 0) - click_skip_native_backend (plugin); - break; - - case PK_ROLE_ENUM_SEARCH_NAME: - case PK_ROLE_ENUM_SEARCH_DETAILS: - values = pk_transaction_get_values (transaction); - click_search (plugin, transaction, values, - role == PK_ROLE_ENUM_SEARCH_DETAILS); - break; - - default: - break; - } - - g_strfreev (click_data); -} - -/** - * pk_plugin_transaction_get_action: - **/ -const gchar * -pk_plugin_transaction_get_action (PkPlugin *plugin, PkTransaction *transaction, - const gchar *action_id) -{ - const gchar *install_actions[] = { - "org.freedesktop.packagekit.package-install", - "org.freedesktop.packagekit.package-install-untrusted", - NULL - }; - const gchar *remove_action = - "org.freedesktop.packagekit.package-remove"; - const gchar **install_action; - gchar **full_paths; - gchar **package_ids; - gint i; - - if (!action_id) - return NULL; - - for (install_action = install_actions; *install_action; - ++install_action) { - if (strcmp (action_id, *install_action) == 0) { - /* Use an action with weaker auth requirements if - * and only if all the packages in the list are - * Click files. - */ - full_paths = pk_transaction_get_full_paths - (transaction); - for (i = 0; full_paths[i]; ++i) { - if (!click_is_click_file (full_paths[i])) - break; - } - if (!full_paths[i]) - return "com.ubuntu.click.package-install"; - } - } - - if (strcmp (action_id, remove_action) == 0) { - /* Use an action with weaker auth requirements if and only - * if all the packages in the list are Click packages. - */ - package_ids = pk_transaction_get_package_ids - (transaction); - for (i = 0; package_ids[i]; ++i) { - if (!click_is_click_package (package_ids[i])) - break; - } - if (!package_ids[i]) - return "com.ubuntu.click.package-remove"; - } - - return action_id; -} diff -Nru click-0.4.43+16.04.20160203/pk-plugin/README click-6.7/pk-plugin/README --- click-0.4.43+16.04.20160203/pk-plugin/README 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/pk-plugin/README 1970-01-01 00:00:00.000000000 +0000 @@ -1,25 +0,0 @@ -This plugin is experimental, although it does minimally work. To make it -usable on Ubuntu Touch when connected remotely (adb/ssh) you may need to -override PolicyKit's defaults: - - $ sudo cat /etc/polkit-1/localauthority/50-local.d/10-click.pkla - [Allow installation of Click packages] - Identity=unix-user:phablet - Action=com.ubuntu.click.package-install;com.ubuntu.click.package-remove - ResultAny=yes - -(This is now installed in -/var/lib/polkit-1/localauthority/10-vendor.d/com.ubuntu.click.pkla, for the -time being.) - -Once that's done, install packagekit and packagekit-plugin-click, and you -should be able to do things like: - - $ pkcon -p install-local foo.click - -I have not done any work on figuring out how to make this work on systems -with aptdaemon. If you want to try this on a normal Ubuntu desktop system, -then for the time being I recommend creating an LXC container for the -purpose: - - $ sudo lxc-create -t ubuntu -n saucy-click -- -r saucy -a amd64 -b $USER diff -Nru click-0.4.43+16.04.20160203/po/click.pot click-6.7/po/click.pot --- click-0.4.43+16.04.20160203/po/click.pot 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/po/click.pot 1970-01-01 00:00:00.000000000 +0000 @@ -1,34 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: Colin Watson \n" -"POT-Creation-Date: 2013-09-08 02:17+0100\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=CHARSET\n" -"Content-Transfer-Encoding: 8bit\n" - -#: ../pk-plugin/com.ubuntu.click.policy.in.h:1 -msgid "Install package" -msgstr "" - -#: ../pk-plugin/com.ubuntu.click.policy.in.h:2 -msgid "To install software, you need to authenticate." -msgstr "" - -#: ../pk-plugin/com.ubuntu.click.policy.in.h:3 -msgid "Remove package" -msgstr "" - -#: ../pk-plugin/com.ubuntu.click.policy.in.h:4 -msgid "To remove software, you need to authenticate." -msgstr "" diff -Nru click-0.4.43+16.04.20160203/po/Makevars click-6.7/po/Makevars --- click-0.4.43+16.04.20160203/po/Makevars 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/po/Makevars 1970-01-01 00:00:00.000000000 +0000 @@ -1,41 +0,0 @@ -# Makefile variables for PO directory in any package using GNU gettext. - -# Usually the message domain is the same as the package name. -DOMAIN = $(PACKAGE) - -# These two variables depend on the location of this directory. -subdir = po -top_builddir = .. - -# These options get passed to xgettext. -XGETTEXT_OPTIONS = --keyword=_ --keyword=N_ - -# This is the copyright holder that gets inserted into the header of the -# $(DOMAIN).pot file. Set this to the copyright holder of the surrounding -# package. (Note that the msgstr strings, extracted from the package's -# sources, belong to the copyright holder of the package.) Translators are -# expected to transfer the copyright for their translations to this person -# or entity, or to disclaim their copyright. The empty string stands for -# the public domain; in this case the translators are expected to disclaim -# their copyright. -COPYRIGHT_HOLDER = Canonical Ltd. - -# This is the email address or URL to which the translators shall report -# bugs in the untranslated strings: -# - Strings which are not entire sentences, see the maintainer guidelines -# in the GNU gettext documentation, section 'Preparing Strings'. -# - Strings which use unclear terms or require additional context to be -# understood. -# - Strings which make invalid assumptions about notation of date, time or -# money. -# - Pluralisation problems. -# - Incorrect English spelling. -# - Incorrect formatting. -# It can be your email address, or a mailing list address where translators -# can write to without being subscribed, or the URL of a web page through -# which the translators can contact you. -MSGID_BUGS_ADDRESS = Colin Watson - -# This is the list of locale categories, beyond LC_MESSAGES, for which the -# message catalogs shall be used. It is usually empty. -EXTRA_LOCALE_CATEGORIES = diff -Nru click-0.4.43+16.04.20160203/po/POTFILES.in click-6.7/po/POTFILES.in --- click-0.4.43+16.04.20160203/po/POTFILES.in 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/po/POTFILES.in 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -pk-plugin/com.ubuntu.click.policy.in diff -Nru click-0.4.43+16.04.20160203/preload/clickpreload.c click-6.7/preload/clickpreload.c --- click-0.4.43+16.04.20160203/preload/clickpreload.c 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/preload/clickpreload.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,444 +0,0 @@ -/* Copyright (C) 2013 Canonical Ltd. - * Author: Colin Watson - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/* Stub out a few syscalls that are unhelpful when installing Click - * packages. This is roughly akin to the effect of using all of fakechroot, - * fakeroot, and eatmydata, but a few orders of magnitude simpler. - */ - -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static int (*libc_chmod) (const char *, mode_t) = (void *) 0; -static int (*libc_chown) (const char *, uid_t, gid_t) = (void *) 0; -static int (*libc_execvp) (const char *, char * const []) = (void *) 0; -static int (*libc_fchmod) (int, mode_t) = (void *) 0; -static int (*libc_fchown) (int, uid_t, gid_t) = (void *) 0; -static FILE *(*libc_fopen) (const char *, const char *) = (void *) 0; -static FILE *(*libc_fopen64) (const char *, const char *) = (void *) 0; -static struct group *(*libc_getgrnam) (const char *) = (void *) 0; -static struct passwd *(*libc_getpwnam) (const char *) = (void *) 0; -static int (*libc_lchown) (const char *, uid_t, gid_t) = (void *) 0; -static int (*libc_link) (const char *, const char *) = (void *) 0; -static int (*libc_mkdir) (const char *, mode_t) = (void *) 0; -static int (*libc_mkfifo) (const char *, mode_t) = (void *) 0; -static int (*libc_mknod) (const char *, mode_t, dev_t) = (void *) 0; -static int (*libc_open) (const char *, int, mode_t) = (void *) 0; -static int (*libc_open64) (const char *, int, mode_t) = (void *) 0; -static int (*libc_symlink) (const char *, const char *) = (void *) 0; -static int (*libc___xstat) (int, const char *, struct stat *) = (void *) 0; -static int (*libc___xstat64) (int, const char *, struct stat64 *) = (void *) 0; - -uid_t euid; -struct passwd root_pwd; -struct group root_grp; -const char *base_path; -size_t base_path_len; -const char *package_path; -int package_fd; - -#define GET_NEXT_SYMBOL(name) \ - do { \ - libc_##name = dlsym (RTLD_NEXT, #name); \ - if (dlerror ()) \ - _exit (1); \ - } while (0) - -static void __attribute__ ((constructor)) clickpreload_init (void) -{ - const char *package_fd_str; - - /* Clear any old error conditions, albeit unlikely, as per dlsym(2) */ - dlerror (); - - GET_NEXT_SYMBOL (chmod); - GET_NEXT_SYMBOL (chown); - GET_NEXT_SYMBOL (execvp); - GET_NEXT_SYMBOL (fchmod); - GET_NEXT_SYMBOL (fchown); - GET_NEXT_SYMBOL (fopen); - GET_NEXT_SYMBOL (fopen64); - GET_NEXT_SYMBOL (getgrnam); - GET_NEXT_SYMBOL (getpwnam); - GET_NEXT_SYMBOL (lchown); - GET_NEXT_SYMBOL (link); - GET_NEXT_SYMBOL (mkdir); - GET_NEXT_SYMBOL (mkfifo); - GET_NEXT_SYMBOL (mknod); - GET_NEXT_SYMBOL (open); - GET_NEXT_SYMBOL (open64); - GET_NEXT_SYMBOL (symlink); - GET_NEXT_SYMBOL (__xstat); - GET_NEXT_SYMBOL (__xstat64); - - euid = geteuid (); - /* dpkg only cares about these fields. */ - root_pwd.pw_uid = 0; - root_grp.gr_gid = 0; - - base_path = getenv ("CLICK_BASE_DIR"); - base_path_len = base_path ? strlen (base_path) : 0; - - package_path = getenv ("CLICK_PACKAGE_PATH"); - package_fd_str = getenv ("CLICK_PACKAGE_FD"); - package_fd = atoi (package_fd_str); -} - -/* dpkg calls chown/fchown/lchown to set permissions of extracted files. If - * we aren't running as root, we don't care. - */ -int chown (const char *path, uid_t owner, gid_t group) -{ - if (euid != 0) - return 0; - - if (!libc_chown) - clickpreload_init (); - return (*libc_chown) (path, owner, group); -} - -int fchown (int fd, uid_t owner, gid_t group) -{ - if (euid != 0) - return 0; - - if (!libc_fchown) - clickpreload_init (); - return (*libc_fchown) (fd, owner, group); -} - -int lchown (const char *path, uid_t owner, gid_t group) -{ - if (euid != 0) - return 0; - - if (!libc_lchown) - clickpreload_init (); - return (*libc_lchown) (path, owner, group); -} - -/* Similarly, we don't much care about passwd/group lookups when we aren't - * root. (This could be more sanely replaced by having dpkg cache those - * lookups itself.) - */ -struct passwd *getpwnam (const char *name) -{ - if (!libc_getpwnam) - clickpreload_init (); /* also needed for root_pwd */ - - if (euid != 0) - return &root_pwd; - return (*libc_getpwnam) (name); -} - -struct group *getgrnam (const char *name) -{ - if (!libc_getgrnam) - clickpreload_init (); /* also needed for root_grp */ - - if (euid != 0) - return &root_grp; - return (*libc_getgrnam) (name); -} - -/* dpkg calls chroot to run maintainer scripts when --instdir is used (which - * we use so that we can have independently-rooted filesystem tarballs). - * However, there is exactly one maintainer script ever used by Click - * packages, and that's a static preinst which doesn't touch the filesystem - * except to be executed with /bin/sh. Chrooting for this causes more - * problems than it solves. - */ -int chroot (const char *path) -{ - return 0; -} - -/* dpkg executes the static preinst. We don't want it. */ -int execvp (const char *file, char * const argv[]) -{ - if (strcmp (file, "/.click/tmp.ci/preinst") == 0) - _exit (0); - - if (!libc_execvp) - clickpreload_init (); - return (*libc_execvp) (file, argv); -} - -/* dpkg calls fsync/sync_file_range quite a lot. However, Click packages - * never correspond to essential system facilities, so it's OK to compromise - * perfect write reliability in the face of hostile filesystem - * implementations for performance. - * - * (Note that dpkg only started using fsync/sync_file_range relatively - * recently, and on many reasonable filesystem configurations using those - * functions buys us nothing; most of dpkg's reliability comes from other - * strategies, such as careful unpack and renaming into place.) - */ -int fsync (int fd) -{ - return 0; -} - -int sync_file_range(int fd, off64_t offset, off64_t nbytes, unsigned int flags) -{ - return 0; -} - -/* Sandboxing: - * - * We try to insulate against dpkg getting confused enough by malformed - * archives to write outside the instdir. This is not full confinement, and - * generally for system security it should be sufficient to run "click - * install" as a specialised user; as such we don't necessarily wrap all - * possible relevant functions here. The main purpose of this is just to - * provide a useful error message if dpkg gets confused. - */ - -static void clickpreload_assert_path_in_instdir (const char *verb, - const char *pathname) -{ - if (strncmp (pathname, base_path, base_path_len) == 0 && - (pathname[base_path_len] == '\0' || pathname[base_path_len] == '/')) - return; - - /* When building click in a chroot with pkgbinarymangler, dpkg-deb is in - * fact a wrapper shell script, and bash checks at startup whether it - * can open /dev/tty for writing. This is harmless, so allow it. - */ - if (strcmp (verb, "write-open") == 0 && strcmp (pathname, "/dev/tty") == 0) - return; - - fprintf (stderr, - "Sandbox failure: 'click install' not permitted to %s '%s'\n", - verb, pathname); - exit (1); -} - -int link (const char *oldpath, const char *newpath) -{ - if (!libc_link) - clickpreload_init (); /* also needed for base_path, base_path_len */ - - clickpreload_assert_path_in_instdir ("make hard link", newpath); - return (*libc_link) (oldpath, newpath); -} - -int mkdir (const char *pathname, mode_t mode) -{ - if (!libc_mkdir) - clickpreload_init (); /* also needed for base_path, base_path_len */ - - clickpreload_assert_path_in_instdir ("mkdir", pathname); - return (*libc_mkdir) (pathname, mode); -} - -int mkfifo (const char *pathname, mode_t mode) -{ - if (!libc_mkfifo) - clickpreload_init (); /* also needed for base_path, base_path_len */ - - clickpreload_assert_path_in_instdir ("mkfifo", pathname); - return (*libc_mkfifo) (pathname, mode); -} - -int mknod (const char *pathname, mode_t mode, dev_t dev) -{ - if (!libc_mknod) - clickpreload_init (); /* also needed for base_path, base_path_len */ - - clickpreload_assert_path_in_instdir ("mknod", pathname); - return (*libc_mknod) (pathname, mode, dev); -} - -int symlink (const char *oldpath, const char *newpath) -{ - if (!libc_symlink) - clickpreload_init (); /* also needed for base_path, base_path_len */ - - clickpreload_assert_path_in_instdir ("make symbolic link", newpath); - return (*libc_symlink) (oldpath, newpath); -} - -/* As well as write sandboxing, our versions of fopen, open, and stat also - * trap accesses to the package path and turn them into accesses to a fixed - * file descriptor instead. With some cooperation from click.install, this - * allows dpkg to read packages in paths not readable by the clickpkg user. - * - * We cannot do this entirely perfectly. In particular, we have to seek to - * the start of the file on open, but the file offset is shared among all - * duplicates of a file descriptor. Let's hope that dpkg doesn't open the - * .deb multiple times and expect to have independent file offsets ... - */ - -FILE *fopen (const char *pathname, const char *mode) -{ - int for_reading = - (strncmp (mode, "r", 1) == 0 && strncmp (mode, "r+", 2) != 0); - - if (!libc_fopen) - clickpreload_init (); /* also needed for package_path */ - - if (for_reading && package_path && strcmp (pathname, package_path) == 0) { - int dup_fd = dup (package_fd); - lseek (dup_fd, 0, SEEK_SET); /* also changes offset of package_fd */ - return fdopen (dup_fd, mode); - } - - if (!for_reading) - clickpreload_assert_path_in_instdir ("write-fdopen", pathname); - - return (*libc_fopen) (pathname, mode); -} - -FILE *fopen64 (const char *pathname, const char *mode) -{ - int for_reading = - (strncmp (mode, "r", 1) == 0 && strncmp (mode, "r+", 2) != 0); - - if (!libc_fopen64) - clickpreload_init (); /* also needed for package_path */ - - if (for_reading && package_path && strcmp (pathname, package_path) == 0) { - int dup_fd = dup (package_fd); - lseek (dup_fd, 0, SEEK_SET); /* also changes offset of package_fd */ - return fdopen (dup_fd, mode); - } - - if (!for_reading) - clickpreload_assert_path_in_instdir ("write-fdopen", pathname); - - return (*libc_fopen64) (pathname, mode); -} - -int open (const char *pathname, int flags, ...) -{ - int for_writing = ((flags & O_WRONLY) || (flags & O_RDWR)); - mode_t mode = 0; - int ret; - - if (!libc_open) - clickpreload_init (); /* also needed for package_path */ - - if (!for_writing && package_path && strcmp (pathname, package_path) == 0) { - int dup_fd = dup (package_fd); - lseek (dup_fd, 0, SEEK_SET); /* also changes offset of package_fd */ - return dup_fd; - } - - if (for_writing) - clickpreload_assert_path_in_instdir ("write-open", pathname); - - if (flags & O_CREAT) { - va_list argv; - va_start (argv, flags); - mode = va_arg (argv, mode_t); - va_end (argv); - } - - ret = (*libc_open) (pathname, flags, mode); - return ret; -} - -int open64 (const char *pathname, int flags, ...) -{ - int for_writing = ((flags & O_WRONLY) || (flags & O_RDWR)); - mode_t mode = 0; - int ret; - - if (!libc_open64) - clickpreload_init (); /* also needed for package_path */ - - if (!for_writing && package_path && strcmp (pathname, package_path) == 0) { - int dup_fd = dup (package_fd); - lseek (dup_fd, 0, SEEK_SET); /* also changes offset of package_fd */ - return dup_fd; - } - - if (for_writing) - clickpreload_assert_path_in_instdir ("write-open", pathname); - - if (flags & O_CREAT) { - va_list argv; - va_start (argv, flags); - mode = va_arg (argv, mode_t); - va_end (argv); - } - - ret = (*libc_open64) (pathname, flags, mode); - return ret; -} - -int __xstat (int ver, const char *pathname, struct stat *buf) -{ - if (!libc___xstat) - clickpreload_init (); /* also needed for package_path */ - - if (package_path && strcmp (pathname, package_path) == 0) - return __fxstat (ver, package_fd, buf); - - return (*libc___xstat) (ver, pathname, buf); -} - -int __xstat64 (int ver, const char *pathname, struct stat64 *buf) -{ - if (!libc___xstat64) - clickpreload_init (); /* also needed for package_path */ - - if (package_path && strcmp (pathname, package_path) == 0) - return __fxstat64 (ver, package_fd, buf); - - return (*libc___xstat64) (ver, pathname, buf); -} - -/* As well as write sandboxing, our versions of chmod and fchmod also - * prevent the 0200 (u+w) permission bit from being removed from unpacked - * files. dpkg normally expects to be run as root which can override DAC - * write permissions, so a mode 04xx file is not normally a problem for it, - * but it is a problem when running dpkg as non-root. Since unpacked - * packages are non-writeable from the point of view of the package's code, - * forcing u+w is safe. - */ - -int chmod (const char *path, mode_t mode) -{ - if (!libc_chmod) - clickpreload_init (); /* also needed for package_path */ - - clickpreload_assert_path_in_instdir ("chmod", path); - mode |= S_IWUSR; - return (*libc_chmod) (path, mode); -} - -int fchmod (int fd, mode_t mode) -{ - if (!libc_fchmod) - clickpreload_init (); - - mode |= S_IWUSR; - return (*libc_fchmod) (fd, mode); -} diff -Nru click-0.4.43+16.04.20160203/preload/Makefile.am click-6.7/preload/Makefile.am --- click-0.4.43+16.04.20160203/preload/Makefile.am 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/preload/Makefile.am 1970-01-01 00:00:00.000000000 +0000 @@ -1,7 +0,0 @@ -pkglib_LTLIBRARIES = libclickpreload.la - -libclickpreload_la_SOURCES = clickpreload.c - -libclickpreload_la_LIBADD = @PRELOAD_LIBS@ - -libclickpreload_la_LDFLAGS = -avoid-version diff -Nru click-0.4.43+16.04.20160203/README click-6.7/README --- click-0.4.43+16.04.20160203/README 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/README 2016-11-21 13:28:45.000000000 +0000 @@ -1 +1,20 @@ -See doc/index.rst to get started. +$ click_ + + Click is a Python package for creating beautiful command line interfaces + in a composable way with as little code as necessary. It's the "Command + Line Interface Creation Kit". It's highly configurable but comes with + sensible defaults out of the box. + + It aims to make the process of writing command line tools quick and fun + while also preventing any frustration caused by the inability to implement + an intended CLI API. + + Click in three points: + + - arbitrary nesting of commands + - automatic help page generation + - supports lazy loading of subcommands at runtime + + Read the docs at http://click.pocoo.org/ + + This library is stable and active. Feedback is always welcome! diff -Nru click-0.4.43+16.04.20160203/run-click click-6.7/run-click --- click-0.4.43+16.04.20160203/run-click 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/run-click 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ -#! /bin/sh -set -e - -LD_LIBRARY_PATH=lib/click/.libs GI_TYPELIB_PATH=lib/click bin/click "$@" diff -Nru click-0.4.43+16.04.20160203/run-tests click-6.7/run-tests --- click-0.4.43+16.04.20160203/run-tests 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/run-tests 1970-01-01 00:00:00.000000000 +0000 @@ -1,14 +0,0 @@ -#! /bin/sh - -# Depends: -# python, -# python-mock, -# python3, -# python3 (>= 3.3) | python3-mock -# python-tox - -if ! [ -r preload/.libs/libclickpreload.so ]; then - echo "W: preload bits not built; will skip some tests" >&2 -fi - -tox diff -Nru click-0.4.43+16.04.20160203/schroot/fstab click-6.7/schroot/fstab --- click-0.4.43+16.04.20160203/schroot/fstab 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/schroot/fstab 1970-01-01 00:00:00.000000000 +0000 @@ -1,12 +0,0 @@ -# fstab: static file system information for chroots. -# Note that the mount point will be prefixed by the chroot path -# (CHROOT_PATH) -# -# -/proc /proc none rw,bind 0 0 -/sys /sys none rw,bind 0 0 -/dev /dev none rw,bind 0 0 -/dev/pts /dev/pts none rw,bind 0 0 -/home /home none rw,rbind 0 0 -/tmp /tmp none rw,bind 0 0 -/run/shm /run/shm none rw,bind 0 0 diff -Nru click-0.4.43+16.04.20160203/schroot/Makefile.am click-6.7/schroot/Makefile.am --- click-0.4.43+16.04.20160203/schroot/Makefile.am 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/schroot/Makefile.am 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -schroot_clickdir = @sysconfdir@/schroot/click - -schroot_click_DATA = fstab diff -Nru click-0.4.43+16.04.20160203/setup.cfg click-6.7/setup.cfg --- click-0.4.43+16.04.20160203/setup.cfg 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/setup.cfg 2017-01-06 22:41:11.000000000 +0000 @@ -0,0 +1,8 @@ +[wheel] +universal = 1 + +[egg_info] +tag_build = +tag_date = 0 +tag_svn_revision = 0 + diff -Nru click-0.4.43+16.04.20160203/setup.py click-6.7/setup.py --- click-0.4.43+16.04.20160203/setup.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/setup.py 2017-01-06 22:32:37.000000000 +0000 @@ -0,0 +1,28 @@ +import re +import ast +from setuptools import setup + + +_version_re = re.compile(r'__version__\s+=\s+(.*)') + + +with open('click/__init__.py', 'rb') as f: + version = str(ast.literal_eval(_version_re.search( + f.read().decode('utf-8')).group(1))) + + +setup( + name='click', + author='Armin Ronacher', + author_email='armin.ronacher@active-4.com', + version=version, + url='http://github.com/mitsuhiko/click', + packages=['click'], + description='A simple wrapper around optparse for ' + 'powerful command line utilities.', + classifiers=[ + 'License :: OSI Approved :: BSD License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + ], +) diff -Nru click-0.4.43+16.04.20160203/setup.py.in click-6.7/setup.py.in --- click-0.4.43+16.04.20160203/setup.py.in 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/setup.py.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,53 +0,0 @@ -#! /usr/bin/env python3 - -import sys - -from setuptools import find_packages, setup -from setuptools.command.test import test - - -requirements = [] -def require(package, pypi_name=None): - try: - __import__(package) - except ImportError: - requirements.append(package if pypi_name is None else pypi_name) - - -require('debian', 'python-debian') -if sys.version < "3.3": - require('mock') -require('chardet') - -if "@GCOVR@": - require('coverage') - - -class test_extra(test): - def run(self): - if "@GCOVR@": - coverage_executable = "python3-coverage" - self.spawn([ - coverage_executable, "run", "-m", "unittest", - "discover", "-vv", "click.tests"]) - self.spawn([coverage_executable, "combine"]) - self.spawn([ - coverage_executable, "xml", "-o", "coverage-python.xml"]) - self.spawn([coverage_executable, "report"]) - else: - test.run(self) - - -setup( - name="click", - version="@PACKAGE_VERSION@", - description="Click package manager", - author="Colin Watson", - author_email="cjwatson@ubuntu.com", - license="GNU GPL", - packages=find_packages(), - scripts=['bin/click'], - install_requires=requirements, - cmdclass={"test": test_extra}, - test_suite="click.tests", - ) diff -Nru click-0.4.43+16.04.20160203/tests/conftest.py click-6.7/tests/conftest.py --- click-0.4.43+16.04.20160203/tests/conftest.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/tests/conftest.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,8 @@ +from click.testing import CliRunner + +import pytest + + +@pytest.fixture(scope='function') +def runner(request): + return CliRunner() diff -Nru click-0.4.43+16.04.20160203/tests/test_arguments.py click-6.7/tests/test_arguments.py --- click-0.4.43+16.04.20160203/tests/test_arguments.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/tests/test_arguments.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,289 @@ +# -*- coding: utf-8 -*- +import click +from click._compat import PY2 + + +def test_nargs_star(runner): + @click.command() + @click.argument('src', nargs=-1) + @click.argument('dst') + def copy(src, dst): + click.echo('src=%s' % '|'.join(src)) + click.echo('dst=%s' % dst) + + result = runner.invoke(copy, ['foo.txt', 'bar.txt', 'dir']) + assert not result.exception + assert result.output.splitlines() == [ + 'src=foo.txt|bar.txt', + 'dst=dir', + ] + + +def test_nargs_default(runner): + try: + @click.command() + @click.argument('src', nargs=-1, default=42) + def copy(src): + pass + except TypeError as e: + assert 'nargs=-1' in str(e) + else: + assert False + + +def test_nargs_tup(runner): + @click.command() + @click.argument('name', nargs=1) + @click.argument('point', nargs=2, type=click.INT) + def copy(name, point): + click.echo('name=%s' % name) + click.echo('point=%d/%d' % point) + + result = runner.invoke(copy, ['peter', '1', '2']) + assert not result.exception + assert result.output.splitlines() == [ + 'name=peter', + 'point=1/2', + ] + + +def test_nargs_tup_composite(runner): + variations = [ + dict(type=(str, int)), + dict(type=click.Tuple([str, int])), + dict(nargs=2, type=click.Tuple([str, int])), + dict(nargs=2, type=(str, int)), + ] + + for opts in variations: + @click.command() + @click.argument('item', **opts) + def copy(item): + click.echo('name=%s id=%d' % item) + + result = runner.invoke(copy, ['peter', '1']) + assert not result.exception + assert result.output.splitlines() == [ + 'name=peter id=1', + ] + + +def test_nargs_err(runner): + @click.command() + @click.argument('x') + def copy(x): + click.echo(x) + + result = runner.invoke(copy, ['foo']) + assert not result.exception + assert result.output == 'foo\n' + + result = runner.invoke(copy, ['foo', 'bar']) + assert result.exit_code == 2 + assert 'Got unexpected extra argument (bar)' in result.output + + +def test_file_args(runner): + @click.command() + @click.argument('input', type=click.File('rb')) + @click.argument('output', type=click.File('wb')) + def inout(input, output): + while True: + chunk = input.read(1024) + if not chunk: + break + output.write(chunk) + + with runner.isolated_filesystem(): + result = runner.invoke(inout, ['-', 'hello.txt'], input='Hey!') + assert result.output == '' + assert result.exit_code == 0 + with open('hello.txt', 'rb') as f: + assert f.read() == b'Hey!' + + result = runner.invoke(inout, ['hello.txt', '-']) + assert result.output == 'Hey!' + assert result.exit_code == 0 + + +def test_path_args(runner): + @click.command() + @click.argument('input', type=click.Path(dir_okay=False, allow_dash=True)) + def foo(input): + click.echo(input) + + result = runner.invoke(foo, ['-']) + assert result.output == '-\n' + assert result.exit_code == 0 + + +def test_file_atomics(runner): + @click.command() + @click.argument('output', type=click.File('wb', atomic=True)) + def inout(output): + output.write(b'Foo bar baz\n') + output.flush() + with open(output.name, 'rb') as f: + old_content = f.read() + assert old_content == b'OLD\n' + + with runner.isolated_filesystem(): + with open('foo.txt', 'wb') as f: + f.write(b'OLD\n') + result = runner.invoke(inout, ['foo.txt'], input='Hey!', + catch_exceptions=False) + assert result.output == '' + assert result.exit_code == 0 + with open('foo.txt', 'rb') as f: + assert f.read() == b'Foo bar baz\n' + + +def test_stdout_default(runner): + @click.command() + @click.argument('output', type=click.File('w'), default='-') + def inout(output): + output.write('Foo bar baz\n') + output.flush() + + result = runner.invoke(inout, []) + assert not result.exception + assert result.output == 'Foo bar baz\n' + + +def test_nargs_envvar(runner): + @click.command() + @click.option('--arg', nargs=2) + def cmd(arg): + click.echo('|'.join(arg)) + + result = runner.invoke(cmd, [], auto_envvar_prefix='TEST', + env={'TEST_ARG': 'foo bar'}) + assert not result.exception + assert result.output == 'foo|bar\n' + + @click.command() + @click.option('--arg', envvar='X', nargs=2) + def cmd(arg): + click.echo('|'.join(arg)) + + result = runner.invoke(cmd, [], env={'X': 'foo bar'}) + assert not result.exception + assert result.output == 'foo|bar\n' + + +def test_empty_nargs(runner): + @click.command() + @click.argument('arg', nargs=-1) + def cmd(arg): + click.echo('arg:' + '|'.join(arg)) + + result = runner.invoke(cmd, []) + assert result.exit_code == 0 + assert result.output == 'arg:\n' + + @click.command() + @click.argument('arg', nargs=-1, required=True) + def cmd2(arg): + click.echo('arg:' + '|'.join(arg)) + + result = runner.invoke(cmd2, []) + assert result.exit_code == 2 + assert 'Missing argument "arg"' in result.output + + +def test_missing_arg(runner): + @click.command() + @click.argument('arg') + def cmd(arg): + click.echo('arg:' + arg) + + result = runner.invoke(cmd, []) + assert result.exit_code == 2 + assert 'Missing argument "arg".' in result.output + + +def test_implicit_non_required(runner): + @click.command() + @click.argument('f', default='test') + def cli(f): + click.echo(f) + + result = runner.invoke(cli, []) + assert result.exit_code == 0 + assert result.output == 'test\n' + + +def test_eat_options(runner): + @click.command() + @click.option('-f') + @click.argument('files', nargs=-1) + def cmd(f, files): + for filename in files: + click.echo(filename) + click.echo(f) + + result = runner.invoke(cmd, ['--', '-foo', 'bar']) + assert result.output.splitlines() == [ + '-foo', + 'bar', + '', + ] + + result = runner.invoke(cmd, ['-f', '-x', '--', '-foo', 'bar']) + assert result.output.splitlines() == [ + '-foo', + 'bar', + '-x', + ] + + +def test_nargs_star_ordering(runner): + @click.command() + @click.argument('a', nargs=-1) + @click.argument('b') + @click.argument('c') + def cmd(a, b, c): + for arg in (a, b, c): + click.echo(arg) + + result = runner.invoke(cmd, ['a', 'b', 'c']) + assert result.output.splitlines() == [ + PY2 and "(u'a',)" or "('a',)", + 'b', + 'c', + ] + + +def test_nargs_specified_plus_star_ordering(runner): + @click.command() + @click.argument('a', nargs=-1) + @click.argument('b') + @click.argument('c', nargs=2) + def cmd(a, b, c): + for arg in (a, b, c): + click.echo(arg) + + result = runner.invoke(cmd, ['a', 'b', 'c', 'd', 'e', 'f']) + assert result.output.splitlines() == [ + PY2 and "(u'a', u'b', u'c')" or "('a', 'b', 'c')", + 'd', + PY2 and "(u'e', u'f')" or "('e', 'f')", + ] + + +def test_defaults_for_nargs(runner): + @click.command() + @click.argument('a', nargs=2, type=int, default=(1, 2)) + def cmd(a): + x, y = a + click.echo(x + y) + + result = runner.invoke(cmd, []) + assert result.output.strip() == '3' + + result = runner.invoke(cmd, ['3', '4']) + assert result.output.strip() == '7' + + result = runner.invoke(cmd, ['3']) + assert result.exception is not None + assert 'argument a takes 2 values' in result.output diff -Nru click-0.4.43+16.04.20160203/tests/test_bashcomplete.py click-6.7/tests/test_bashcomplete.py --- click-0.4.43+16.04.20160203/tests/test_bashcomplete.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/tests/test_bashcomplete.py 2016-12-01 12:33:48.000000000 +0000 @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- + +import click +from click._bashcomplete import get_choices + + +def test_single_command(): + @click.command() + @click.option('--local-opt') + def cli(local_opt): + pass + + assert list(get_choices(cli, 'lol', [], '-')) == ['--local-opt'] + assert list(get_choices(cli, 'lol', [], '')) == [] + + +def test_small_chain(): + @click.group() + @click.option('--global-opt') + def cli(global_opt): + pass + + @cli.command() + @click.option('--local-opt') + def sub(local_opt): + pass + + assert list(get_choices(cli, 'lol', [], '')) == ['sub'] + assert list(get_choices(cli, 'lol', [], '-')) == ['--global-opt'] + assert list(get_choices(cli, 'lol', ['sub'], '')) == [] + assert list(get_choices(cli, 'lol', ['sub'], '-')) == ['--local-opt'] + + +def test_long_chain(): + @click.group('cli') + @click.option('--cli-opt') + def cli(cli_opt): + pass + + @cli.group('asub') + @click.option('--asub-opt') + def asub(asub_opt): + pass + + @asub.group('bsub') + @click.option('--bsub-opt') + def bsub(bsub_opt): + pass + + @bsub.command('csub') + @click.option('--csub-opt') + def csub(csub_opt): + pass + + assert list(get_choices(cli, 'lol', [], '-')) == ['--cli-opt'] + assert list(get_choices(cli, 'lol', [], '')) == ['asub'] + assert list(get_choices(cli, 'lol', ['asub'], '-')) == ['--asub-opt'] + assert list(get_choices(cli, 'lol', ['asub'], '')) == ['bsub'] + assert list(get_choices(cli, 'lol', ['asub', 'bsub'], '-')) == ['--bsub-opt'] + assert list(get_choices(cli, 'lol', ['asub', 'bsub'], '')) == ['csub'] + assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub'], '-')) == ['--csub-opt'] + assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub'], '')) == [] diff -Nru click-0.4.43+16.04.20160203/tests/test_basic.py click-6.7/tests/test_basic.py --- click-0.4.43+16.04.20160203/tests/test_basic.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/tests/test_basic.py 2017-01-06 22:32:37.000000000 +0000 @@ -0,0 +1,399 @@ +# -*- coding: utf-8 -*- +import os +import uuid +import click + + +def test_basic_functionality(runner): + @click.command() + def cli(): + """Hello World!""" + click.echo('I EXECUTED') + + result = runner.invoke(cli, ['--help']) + assert not result.exception + assert 'Hello World!' in result.output + assert 'Show this message and exit.' in result.output + assert result.exit_code == 0 + assert 'I EXECUTED' not in result.output + + result = runner.invoke(cli, []) + assert not result.exception + assert 'I EXECUTED' in result.output + assert result.exit_code == 0 + + +def test_return_values(): + @click.command() + def cli(): + return 42 + + with cli.make_context('foo', []) as ctx: + rv = cli.invoke(ctx) + assert rv is 42 + + +def test_basic_group(runner): + @click.group() + def cli(): + """This is the root.""" + click.echo('ROOT EXECUTED') + + @cli.command() + def subcommand(): + """This is a subcommand.""" + click.echo('SUBCOMMAND EXECUTED') + + result = runner.invoke(cli, ['--help']) + assert not result.exception + assert 'COMMAND [ARGS]...' in result.output + assert 'This is the root' in result.output + assert 'This is a subcommand.' in result.output + assert result.exit_code == 0 + assert 'ROOT EXECUTED' not in result.output + + result = runner.invoke(cli, ['subcommand']) + assert not result.exception + assert result.exit_code == 0 + assert 'ROOT EXECUTED' in result.output + assert 'SUBCOMMAND EXECUTED' in result.output + + +def test_basic_option(runner): + @click.command() + @click.option('--foo', default='no value') + def cli(foo): + click.echo('FOO:[%s]' % foo) + + result = runner.invoke(cli, []) + assert not result.exception + assert 'FOO:[no value]' in result.output + + result = runner.invoke(cli, ['--foo=42']) + assert not result.exception + assert 'FOO:[42]' in result.output + + result = runner.invoke(cli, ['--foo']) + assert result.exception + assert '--foo option requires an argument' in result.output + + result = runner.invoke(cli, ['--foo=']) + assert not result.exception + assert 'FOO:[]' in result.output + + result = runner.invoke(cli, [u'--foo=\N{SNOWMAN}']) + assert not result.exception + assert u'FOO:[\N{SNOWMAN}]' in result.output + + +def test_int_option(runner): + @click.command() + @click.option('--foo', default=42) + def cli(foo): + click.echo('FOO:[%s]' % (foo * 2)) + + result = runner.invoke(cli, []) + assert not result.exception + assert 'FOO:[84]' in result.output + + result = runner.invoke(cli, ['--foo=23']) + assert not result.exception + assert 'FOO:[46]' in result.output + + result = runner.invoke(cli, ['--foo=bar']) + assert result.exception + assert 'Invalid value for "--foo": bar is not a valid integer' \ + in result.output + + +def test_uuid_option(runner): + @click.command() + @click.option('--u', default='ba122011-349f-423b-873b-9d6a79c688ab', + type=click.UUID) + def cli(u): + assert type(u) is uuid.UUID + click.echo('U:[%s]' % u) + + result = runner.invoke(cli, []) + assert not result.exception + assert 'U:[ba122011-349f-423b-873b-9d6a79c688ab]' in result.output + + result = runner.invoke(cli, ['--u=821592c1-c50e-4971-9cd6-e89dc6832f86']) + assert not result.exception + assert 'U:[821592c1-c50e-4971-9cd6-e89dc6832f86]' in result.output + + result = runner.invoke(cli, ['--u=bar']) + assert result.exception + assert 'Invalid value for "--u": bar is not a valid UUID value' \ + in result.output + + +def test_float_option(runner): + @click.command() + @click.option('--foo', default=42, type=click.FLOAT) + def cli(foo): + assert type(foo) is float + click.echo('FOO:[%s]' % foo) + + result = runner.invoke(cli, []) + assert not result.exception + assert 'FOO:[42.0]' in result.output + + result = runner.invoke(cli, ['--foo=23.5']) + assert not result.exception + assert 'FOO:[23.5]' in result.output + + result = runner.invoke(cli, ['--foo=bar']) + assert result.exception + assert 'Invalid value for "--foo": bar is not a valid float' \ + in result.output + + +def test_boolean_option(runner): + for default in True, False: + @click.command() + @click.option('--with-foo/--without-foo', default=default) + def cli(with_foo): + click.echo(with_foo) + + result = runner.invoke(cli, ['--with-foo']) + assert not result.exception + assert result.output == 'True\n' + result = runner.invoke(cli, ['--without-foo']) + assert not result.exception + assert result.output == 'False\n' + result = runner.invoke(cli, []) + assert not result.exception + assert result.output == '%s\n' % default + + for default in True, False: + @click.command() + @click.option('--flag', is_flag=True, default=default) + def cli(flag): + click.echo(flag) + + result = runner.invoke(cli, ['--flag']) + assert not result.exception + assert result.output == '%s\n' % (not default) + result = runner.invoke(cli, []) + assert not result.exception + assert result.output == '%s\n' % (default) + + +def test_file_option(runner): + @click.command() + @click.option('--file', type=click.File('w')) + def input(file): + file.write('Hello World!\n') + + @click.command() + @click.option('--file', type=click.File('r')) + def output(file): + click.echo(file.read()) + + with runner.isolated_filesystem(): + result_in = runner.invoke(input, ['--file=example.txt']) + result_out = runner.invoke(output, ['--file=example.txt']) + + assert not result_in.exception + assert result_in.output == '' + assert not result_out.exception + assert result_out.output == 'Hello World!\n\n' + + +def test_file_lazy_mode(runner): + do_io = False + + @click.command() + @click.option('--file', type=click.File('w')) + def input(file): + if do_io: + file.write('Hello World!\n') + + @click.command() + @click.option('--file', type=click.File('r')) + def output(file): + pass + + with runner.isolated_filesystem(): + os.mkdir('example.txt') + + do_io = True + result_in = runner.invoke(input, ['--file=example.txt']) + assert result_in.exit_code == 1 + + do_io = False + result_in = runner.invoke(input, ['--file=example.txt']) + assert result_in.exit_code == 0 + + result_out = runner.invoke(output, ['--file=example.txt']) + assert result_out.exception + + @click.command() + @click.option('--file', type=click.File('w', lazy=False)) + def input_non_lazy(file): + file.write('Hello World!\n') + + with runner.isolated_filesystem(): + os.mkdir('example.txt') + result_in = runner.invoke(input_non_lazy, ['--file=example.txt']) + assert result_in.exit_code == 2 + assert 'Invalid value for "--file": Could not open file: example.txt' \ + in result_in.output + + +def test_path_option(runner): + @click.command() + @click.option('-O', type=click.Path(file_okay=False, exists=True, + writable=True)) + def write_to_dir(o): + with open(os.path.join(o, 'foo.txt'), 'wb') as f: + f.write(b'meh\n') + + with runner.isolated_filesystem(): + os.mkdir('test') + + result = runner.invoke(write_to_dir, ['-O', 'test']) + assert not result.exception + + with open('test/foo.txt', 'rb') as f: + assert f.read() == b'meh\n' + + result = runner.invoke(write_to_dir, ['-O', 'test/foo.txt']) + assert 'Invalid value for "-O": Directory "test/foo.txt" is a file.' \ + in result.output + + @click.command() + @click.option('-f', type=click.Path(exists=True)) + def showtype(f): + click.echo('is_file=%s' % os.path.isfile(f)) + click.echo('is_dir=%s' % os.path.isdir(f)) + + with runner.isolated_filesystem(): + result = runner.invoke(showtype, ['-f', 'xxx']) + assert 'Error: Invalid value for "-f": Path "xxx" does not exist' \ + in result.output + + result = runner.invoke(showtype, ['-f', '.']) + assert 'is_file=False' in result.output + assert 'is_dir=True' in result.output + + @click.command() + @click.option('-f', type=click.Path()) + def exists(f): + click.echo('exists=%s' % os.path.exists(f)) + + with runner.isolated_filesystem(): + result = runner.invoke(exists, ['-f', 'xxx']) + assert 'exists=False' in result.output + + result = runner.invoke(exists, ['-f', '.']) + assert 'exists=True' in result.output + + +def test_choice_option(runner): + @click.command() + @click.option('--method', type=click.Choice(['foo', 'bar', 'baz'])) + def cli(method): + click.echo(method) + + result = runner.invoke(cli, ['--method=foo']) + assert not result.exception + assert result.output == 'foo\n' + + result = runner.invoke(cli, ['--method=meh']) + assert result.exit_code == 2 + assert 'Invalid value for "--method": invalid choice: meh. ' \ + '(choose from foo, bar, baz)' in result.output + + result = runner.invoke(cli, ['--help']) + assert '--method [foo|bar|baz]' in result.output + + +def test_int_range_option(runner): + @click.command() + @click.option('--x', type=click.IntRange(0, 5)) + def cli(x): + click.echo(x) + + result = runner.invoke(cli, ['--x=5']) + assert not result.exception + assert result.output == '5\n' + + result = runner.invoke(cli, ['--x=6']) + assert result.exit_code == 2 + assert 'Invalid value for "--x": 6 is not in the valid range of 0 to 5.\n' \ + in result.output + + @click.command() + @click.option('--x', type=click.IntRange(0, 5, clamp=True)) + def clamp(x): + click.echo(x) + + result = runner.invoke(clamp, ['--x=5']) + assert not result.exception + assert result.output == '5\n' + + result = runner.invoke(clamp, ['--x=6']) + assert not result.exception + assert result.output == '5\n' + + result = runner.invoke(clamp, ['--x=-1']) + assert not result.exception + assert result.output == '0\n' + + +def test_required_option(runner): + @click.command() + @click.option('--foo', required=True) + def cli(foo): + click.echo(foo) + + result = runner.invoke(cli, []) + assert result.exit_code == 2 + assert 'Missing option "--foo"' in result.output + + +def test_evaluation_order(runner): + called = [] + + def memo(ctx, value): + called.append(value) + return value + + @click.command() + @click.option('--missing', default='missing', + is_eager=False, callback=memo) + @click.option('--eager-flag1', flag_value='eager1', + is_eager=True, callback=memo) + @click.option('--eager-flag2', flag_value='eager2', + is_eager=True, callback=memo) + @click.option('--eager-flag3', flag_value='eager3', + is_eager=True, callback=memo) + @click.option('--normal-flag1', flag_value='normal1', + is_eager=False, callback=memo) + @click.option('--normal-flag2', flag_value='normal2', + is_eager=False, callback=memo) + @click.option('--normal-flag3', flag_value='normal3', + is_eager=False, callback=memo) + def cli(**x): + pass + + result = runner.invoke(cli, ['--eager-flag2', + '--eager-flag1', + '--normal-flag2', + '--eager-flag3', + '--normal-flag3', + '--normal-flag3', + '--normal-flag1', + '--normal-flag1']) + assert not result.exception + assert called == [ + 'eager2', + 'eager1', + 'eager3', + 'normal2', + 'normal3', + 'normal1', + 'missing', + ] diff -Nru click-0.4.43+16.04.20160203/tests/test_chain.py click-6.7/tests/test_chain.py --- click-0.4.43+16.04.20160203/tests/test_chain.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/tests/test_chain.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,252 @@ +import sys +import click +import pytest + + +def debug(): + click.echo('%s=%s' % ( + sys._getframe(1).f_code.co_name, + '|'.join(click.get_current_context().args), + )) + + +def test_basic_chaining(runner): + @click.group(chain=True) + def cli(): + pass + + @cli.command('sdist') + def sdist(): + click.echo('sdist called') + + @cli.command('bdist') + def bdist(): + click.echo('bdist called') + + result = runner.invoke(cli, ['bdist', 'sdist', 'bdist']) + assert not result.exception + assert result.output.splitlines() == [ + 'bdist called', + 'sdist called', + 'bdist called', + ] + + +def test_chaining_help(runner): + @click.group(chain=True) + def cli(): + """ROOT HELP""" + pass + + @cli.command('sdist') + def sdist(): + """SDIST HELP""" + click.echo('sdist called') + + @cli.command('bdist') + def bdist(): + """BDIST HELP""" + click.echo('bdist called') + + result = runner.invoke(cli, ['--help']) + assert not result.exception + assert 'COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...' in result.output + assert 'ROOT HELP' in result.output + + result = runner.invoke(cli, ['sdist', '--help']) + assert not result.exception + assert 'SDIST HELP' in result.output + + result = runner.invoke(cli, ['bdist', '--help']) + assert not result.exception + assert 'BDIST HELP' in result.output + + result = runner.invoke(cli, ['bdist', 'sdist', '--help']) + assert not result.exception + assert 'SDIST HELP' in result.output + + +def test_chaining_with_options(runner): + @click.group(chain=True) + def cli(): + pass + + @cli.command('sdist') + @click.option('--format') + def sdist(format): + click.echo('sdist called %s' % format) + + @cli.command('bdist') + @click.option('--format') + def bdist(format): + click.echo('bdist called %s' % format) + + result = runner.invoke(cli, ['bdist', '--format=1', 'sdist', '--format=2']) + assert not result.exception + assert result.output.splitlines() == [ + 'bdist called 1', + 'sdist called 2', + ] + + +def test_chaining_with_arguments(runner): + @click.group(chain=True) + def cli(): + pass + + @cli.command('sdist') + @click.argument('format') + def sdist(format): + click.echo('sdist called %s' % format) + + @cli.command('bdist') + @click.argument('format') + def bdist(format): + click.echo('bdist called %s' % format) + + result = runner.invoke(cli, ['bdist', '1', 'sdist', '2']) + assert not result.exception + assert result.output.splitlines() == [ + 'bdist called 1', + 'sdist called 2', + ] + + +def test_pipeline(runner): + @click.group(chain=True, invoke_without_command=True) + @click.option('-i', '--input', type=click.File('r')) + def cli(input): + pass + + @cli.resultcallback() + def process_pipeline(processors, input): + iterator = (x.rstrip('\r\n') for x in input) + for processor in processors: + iterator = processor(iterator) + for item in iterator: + click.echo(item) + + @cli.command('uppercase') + def make_uppercase(): + def processor(iterator): + for line in iterator: + yield line.upper() + return processor + + @cli.command('strip') + def make_strip(): + def processor(iterator): + for line in iterator: + yield line.strip() + return processor + + result = runner.invoke(cli, ['-i', '-'], input='foo\nbar') + assert not result.exception + assert result.output.splitlines() == [ + 'foo', + 'bar', + ] + + result = runner.invoke(cli, ['-i', '-', 'strip'], input='foo \n bar') + assert not result.exception + assert result.output.splitlines() == [ + 'foo', + 'bar', + ] + + result = runner.invoke(cli, ['-i', '-', 'strip', 'uppercase'], + input='foo \n bar') + assert not result.exception + assert result.output.splitlines() == [ + 'FOO', + 'BAR', + ] + + +def test_args_and_chain(runner): + @click.group(chain=True) + def cli(): + debug() + + @cli.command() + def a(): + debug() + + @cli.command() + def b(): + debug() + + @cli.command() + def c(): + debug() + + result = runner.invoke(cli, ['a', 'b', 'c']) + assert not result.exception + assert result.output.splitlines() == [ + 'cli=', + 'a=', + 'b=', + 'c=', + ] + + +def test_multicommand_arg_behavior(runner): + with pytest.raises(RuntimeError): + @click.group(chain=True) + @click.argument('forbidden', required=False) + def bad_cli(): + pass + + with pytest.raises(RuntimeError): + @click.group(chain=True) + @click.argument('forbidden', nargs=-1) + def bad_cli2(): + pass + + @click.group(chain=True) + @click.argument('arg') + def cli(arg): + click.echo('cli:%s' % arg) + + @cli.command() + def a(): + click.echo('a') + + result = runner.invoke(cli, ['foo', 'a']) + assert not result.exception + assert result.output.splitlines() == [ + 'cli:foo', + 'a', + ] + + +@pytest.mark.xfail +def test_multicommand_chaining(runner): + @click.group(chain=True) + def cli(): + debug() + + @cli.group() + def l1a(): + debug() + + @l1a.command() + def l2a(): + debug() + + @l1a.command() + def l2b(): + debug() + + @cli.command() + def l1b(): + debug() + + result = runner.invoke(cli, ['l1a', 'l2a', 'l1b']) + assert not result.exception + assert result.output.splitlines() == [ + 'cli=', + 'l1a=', + 'l2a=', + 'l1b=', + ] diff -Nru click-0.4.43+16.04.20160203/tests/test_commands.py click-6.7/tests/test_commands.py --- click-0.4.43+16.04.20160203/tests/test_commands.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/tests/test_commands.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,255 @@ +# -*- coding: utf-8 -*- +import re +import click + + +def test_other_command_invoke(runner): + @click.command() + @click.pass_context + def cli(ctx): + return ctx.invoke(other_cmd, arg=42) + + @click.command() + @click.argument('arg', type=click.INT) + def other_cmd(arg): + click.echo(arg) + + result = runner.invoke(cli, []) + assert not result.exception + assert result.output == '42\n' + + +def test_other_command_forward(runner): + cli = click.Group() + + @cli.command() + @click.option('--count', default=1) + def test(count): + click.echo('Count: %d' % count) + + @cli.command() + @click.option('--count', default=1) + @click.pass_context + def dist(ctx, count): + ctx.forward(test) + ctx.invoke(test, count=42) + + result = runner.invoke(cli, ['dist']) + assert not result.exception + assert result.output == 'Count: 1\nCount: 42\n' + + +def test_auto_shorthelp(runner): + @click.group() + def cli(): + pass + + @cli.command() + def short(): + """This is a short text.""" + + @cli.command() + def special_chars(): + """Login and store the token in ~/.netrc.""" + + @cli.command() + def long(): + """This is a long text that is too long to show as short help + and will be truncated instead.""" + + result = runner.invoke(cli, ['--help']) + assert re.search( + r'Commands:\n\s+' + r'long\s+This is a long text that is too long to show\.\.\.\n\s+' + r'short\s+This is a short text\.\n\s+' + r'special_chars\s+Login and store the token in ~/.netrc\.\s*', + result.output) is not None + + +def test_default_maps(runner): + @click.group() + def cli(): + pass + + @cli.command() + @click.option('--name', default='normal') + def foo(name): + click.echo(name) + + result = runner.invoke(cli, ['foo'], default_map={ + 'foo': {'name': 'changed'} + }) + + assert not result.exception + assert result.output == 'changed\n' + + +def test_group_with_args(runner): + @click.group() + @click.argument('obj') + def cli(obj): + click.echo('obj=%s' % obj) + + @cli.command() + def move(): + click.echo('move') + + result = runner.invoke(cli, []) + assert result.exit_code == 0 + assert 'Show this message and exit.' in result.output + + result = runner.invoke(cli, ['obj1']) + assert result.exit_code == 2 + assert 'Error: Missing command.' in result.output + + result = runner.invoke(cli, ['obj1', '--help']) + assert result.exit_code == 0 + assert 'Show this message and exit.' in result.output + + result = runner.invoke(cli, ['obj1', 'move']) + assert result.exit_code == 0 + assert result.output == 'obj=obj1\nmove\n' + + +def test_base_command(runner): + import optparse + + @click.group() + def cli(): + pass + + class OptParseCommand(click.BaseCommand): + + def __init__(self, name, parser, callback): + click.BaseCommand.__init__(self, name) + self.parser = parser + self.callback = callback + + def parse_args(self, ctx, args): + try: + opts, args = parser.parse_args(args) + except Exception as e: + ctx.fail(str(e)) + ctx.args = args + ctx.params = vars(opts) + + def get_usage(self, ctx): + return self.parser.get_usage() + + def get_help(self, ctx): + return self.parser.format_help() + + def invoke(self, ctx): + ctx.invoke(self.callback, ctx.args, **ctx.params) + + parser = optparse.OptionParser(usage='Usage: foo test [OPTIONS]') + parser.add_option("-f", "--file", dest="filename", + help="write report to FILE", metavar="FILE") + parser.add_option("-q", "--quiet", + action="store_false", dest="verbose", default=True, + help="don't print status messages to stdout") + + def test_callback(args, filename, verbose): + click.echo(' '.join(args)) + click.echo(filename) + click.echo(verbose) + cli.add_command(OptParseCommand('test', parser, test_callback)) + + result = runner.invoke(cli, ['test', '-f', 'test.txt', '-q', + 'whatever.txt', 'whateverelse.txt']) + assert not result.exception + assert result.output.splitlines() == [ + 'whatever.txt whateverelse.txt', + 'test.txt', + 'False', + ] + + result = runner.invoke(cli, ['test', '--help']) + assert not result.exception + assert result.output.splitlines() == [ + 'Usage: foo test [OPTIONS]', + '', + 'Options:', + ' -h, --help show this help message and exit', + ' -f FILE, --file=FILE write report to FILE', + ' -q, --quiet don\'t print status messages to stdout', + ] + + +def test_object_propagation(runner): + for chain in False, True: + @click.group(chain=chain) + @click.option('--debug/--no-debug', default=False) + @click.pass_context + def cli(ctx, debug): + if ctx.obj is None: + ctx.obj = {} + ctx.obj['DEBUG'] = debug + + @cli.command() + @click.pass_context + def sync(ctx): + click.echo('Debug is %s' % (ctx.obj['DEBUG'] and 'on' or 'off')) + + result = runner.invoke(cli, ['sync']) + assert result.exception is None + assert result.output == 'Debug is off\n' + + +def test_other_command_invoke_with_defaults(runner): + @click.command() + @click.pass_context + def cli(ctx): + return ctx.invoke(other_cmd) + + @click.command() + @click.option('--foo', type=click.INT, default=42) + @click.pass_context + def other_cmd(ctx, foo): + assert ctx.info_name == 'other_cmd' + click.echo(foo) + + result = runner.invoke(cli, []) + assert not result.exception + assert result.output == '42\n' + + +def test_invoked_subcommand(runner): + @click.group(invoke_without_command=True) + @click.pass_context + def cli(ctx): + if ctx.invoked_subcommand is None: + click.echo('no subcommand, use default') + ctx.invoke(sync) + else: + click.echo('invoke subcommand') + + @cli.command() + def sync(): + click.echo('in subcommand') + + result = runner.invoke(cli, ['sync']) + assert not result.exception + assert result.output == 'invoke subcommand\nin subcommand\n' + + result = runner.invoke(cli) + assert not result.exception + assert result.output == 'no subcommand, use default\nin subcommand\n' + + +def test_unprocessed_options(runner): + @click.command(context_settings=dict( + ignore_unknown_options=True + )) + @click.argument('args', nargs=-1, type=click.UNPROCESSED) + @click.option('--verbose', '-v', count=True) + def cli(verbose, args): + click.echo('Verbosity: %s' % verbose) + click.echo('Args: %s' % '|'.join(args)) + + result = runner.invoke(cli, ['-foo', '-vvvvx', '--muhaha', 'x', 'y', '-x']) + assert not result.exception + assert result.output.splitlines() == [ + 'Verbosity: 4', + 'Args: -foo|-x|--muhaha|x|y|-x', + ] diff -Nru click-0.4.43+16.04.20160203/tests/test_compat.py click-6.7/tests/test_compat.py --- click-0.4.43+16.04.20160203/tests/test_compat.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/tests/test_compat.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,24 @@ +import click + + +if click.__version__ >= '3.0': + def test_legacy_callbacks(runner): + def legacy_callback(ctx, value): + return value.upper() + + @click.command() + @click.option('--foo', callback=legacy_callback) + def cli(foo): + click.echo(foo) + + result = runner.invoke(cli, ['--foo', 'wat']) + assert result.exit_code == 0 + assert 'WAT' in result.output + assert 'Invoked legacy parameter callback' in result.output + + +def test_bash_func_name(): + from click._bashcomplete import get_completion_script + script = get_completion_script('foo-bar baz_blah', '_COMPLETE_VAR').strip() + assert script.startswith('_foo_barbaz_blah_completion()') + assert '_COMPLETE_VAR=complete $1' in script diff -Nru click-0.4.43+16.04.20160203/tests/test_context.py click-6.7/tests/test_context.py --- click-0.4.43+16.04.20160203/tests/test_context.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/tests/test_context.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,205 @@ +# -*- coding: utf-8 -*- +import click + + +def test_ensure_context_objects(runner): + class Foo(object): + def __init__(self): + self.title = 'default' + + pass_foo = click.make_pass_decorator(Foo, ensure=True) + + @click.group() + @pass_foo + def cli(foo): + pass + + @cli.command() + @pass_foo + def test(foo): + click.echo(foo.title) + + result = runner.invoke(cli, ['test']) + assert not result.exception + assert result.output == 'default\n' + + +def test_get_context_objects(runner): + class Foo(object): + def __init__(self): + self.title = 'default' + + pass_foo = click.make_pass_decorator(Foo, ensure=True) + + @click.group() + @click.pass_context + def cli(ctx): + ctx.obj = Foo() + ctx.obj.title = 'test' + + @cli.command() + @pass_foo + def test(foo): + click.echo(foo.title) + + result = runner.invoke(cli, ['test']) + assert not result.exception + assert result.output == 'test\n' + + +def test_get_context_objects_no_ensuring(runner): + class Foo(object): + def __init__(self): + self.title = 'default' + + pass_foo = click.make_pass_decorator(Foo) + + @click.group() + @click.pass_context + def cli(ctx): + ctx.obj = Foo() + ctx.obj.title = 'test' + + @cli.command() + @pass_foo + def test(foo): + click.echo(foo.title) + + result = runner.invoke(cli, ['test']) + assert not result.exception + assert result.output == 'test\n' + + +def test_get_context_objects_missing(runner): + class Foo(object): + pass + + pass_foo = click.make_pass_decorator(Foo) + + @click.group() + @click.pass_context + def cli(ctx): + pass + + @cli.command() + @pass_foo + def test(foo): + click.echo(foo.title) + + result = runner.invoke(cli, ['test']) + assert result.exception is not None + assert isinstance(result.exception, RuntimeError) + assert "Managed to invoke callback without a context object " \ + "of type 'Foo' existing" in str(result.exception) + + +def test_multi_enter(runner): + called = [] + + @click.command() + @click.pass_context + def cli(ctx): + def callback(): + called.append(True) + ctx.call_on_close(callback) + + with ctx: + pass + assert not called + + result = runner.invoke(cli, []) + assert result.exception is None + assert called == [True] + + +def test_global_context_object(runner): + @click.command() + @click.pass_context + def cli(ctx): + assert click.get_current_context() is ctx + ctx.obj = 'FOOBAR' + assert click.get_current_context().obj == 'FOOBAR' + + assert click.get_current_context(silent=True) is None + runner.invoke(cli, [], catch_exceptions=False) + assert click.get_current_context(silent=True) is None + + +def test_context_meta(runner): + LANG_KEY = __name__ + '.lang' + + def set_language(value): + click.get_current_context().meta[LANG_KEY] = value + + def get_language(): + return click.get_current_context().meta.get(LANG_KEY, 'en_US') + + @click.command() + @click.pass_context + def cli(ctx): + assert get_language() == 'en_US' + set_language('de_DE') + assert get_language() == 'de_DE' + + runner.invoke(cli, [], catch_exceptions=False) + + +def test_context_pushing(): + rv = [] + + @click.command() + def cli(): + pass + + ctx = click.Context(cli) + + @ctx.call_on_close + def test_callback(): + rv.append(42) + + with ctx.scope(cleanup=False): + # Internal + assert ctx._depth == 2 + + assert rv == [] + + with ctx.scope(): + # Internal + assert ctx._depth == 1 + + assert rv == [42] + + +def test_pass_obj(runner): + @click.group() + @click.pass_context + def cli(ctx): + ctx.obj = 'test' + + @cli.command() + @click.pass_obj + def test(obj): + click.echo(obj) + + result = runner.invoke(cli, ['test']) + assert not result.exception + assert result.output == 'test\n' + + +def test_close_before_pop(runner): + called = [] + + @click.command() + @click.pass_context + def cli(ctx): + ctx.obj = 'test' + @ctx.call_on_close + def foo(): + assert click.get_current_context().obj == 'test' + called.append(True) + click.echo('aha!') + + result = runner.invoke(cli, []) + assert not result.exception + assert result.output == 'aha!\n' + assert called == [True] diff -Nru click-0.4.43+16.04.20160203/tests/test_defaults.py click-6.7/tests/test_defaults.py --- click-0.4.43+16.04.20160203/tests/test_defaults.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/tests/test_defaults.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,46 @@ +import click + + +def test_basic_defaults(runner): + @click.command() + @click.option('--foo', default=42, type=click.FLOAT) + def cli(foo): + assert type(foo) is float + click.echo('FOO:[%s]' % foo) + + result = runner.invoke(cli, []) + assert not result.exception + assert 'FOO:[42.0]' in result.output + + +def test_multiple_defaults(runner): + @click.command() + @click.option('--foo', default=[23, 42], type=click.FLOAT, + multiple=True) + def cli(foo): + for item in foo: + assert type(item) is float + click.echo(item) + + result = runner.invoke(cli, []) + assert not result.exception + assert result.output.splitlines() == [ + '23.0', + '42.0', + ] + + +def test_nargs_plus_multiple(runner): + @click.command() + @click.option('--arg', default=((1, 2), (3, 4)), + nargs=2, multiple=True, type=click.INT) + def cli(arg): + for item in arg: + click.echo('<%d|%d>' % item) + + result = runner.invoke(cli, []) + assert not result.exception + assert result.output.splitlines() == [ + '<1|2>', + '<3|4>', + ] diff -Nru click-0.4.43+16.04.20160203/tests/test_formatting.py click-6.7/tests/test_formatting.py --- click-0.4.43+16.04.20160203/tests/test_formatting.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/tests/test_formatting.py 2017-01-06 22:32:37.000000000 +0000 @@ -0,0 +1,147 @@ +# -*- coding: utf-8 -*- +import click + + +def test_basic_functionality(runner): + @click.command() + def cli(): + """First paragraph. + + This is a very long second + paragraph and not correctly + wrapped but it will be rewrapped. + + \b + This is + a paragraph + without rewrapping. + + \b + 1 + 2 + 3 + + And this is a paragraph + that will be rewrapped again. + """ + + result = runner.invoke(cli, ['--help'], terminal_width=60) + assert not result.exception + assert result.output.splitlines() == [ + 'Usage: cli [OPTIONS]', + '', + ' First paragraph.', + '', + ' This is a very long second paragraph and not correctly', + ' wrapped but it will be rewrapped.', + '', + ' This is', + ' a paragraph', + ' without rewrapping.', + '', + ' 1', + ' 2', + ' 3', + '', + ' And this is a paragraph that will be rewrapped again.', + '', + 'Options:', + ' --help Show this message and exit.', + ] + + +def test_wrapping_long_options_strings(runner): + @click.group() + def cli(): + """Top level command + """ + + @cli.group() + def a_very_long(): + """Second level + """ + + @a_very_long.command() + @click.argument('first') + @click.argument('second') + @click.argument('third') + @click.argument('fourth') + @click.argument('fifth') + @click.argument('sixth') + def command(): + """A command. + """ + + # 54 is chosen as a length where the second line is one character + # longer than the maximum length. + result = runner.invoke(cli, ['a_very_long', 'command', '--help'], + terminal_width=54) + assert not result.exception + assert result.output.splitlines() == [ + 'Usage: cli a_very_long command [OPTIONS] FIRST SECOND', + ' THIRD FOURTH FIFTH', + ' SIXTH', + '', + ' A command.', + '', + 'Options:', + ' --help Show this message and exit.', + ] + + +def test_wrapping_long_command_name(runner): + @click.group() + def cli(): + """Top level command + """ + + @cli.group() + def a_very_very_very_long(): + """Second level + """ + + @a_very_very_very_long.command() + @click.argument('first') + @click.argument('second') + @click.argument('third') + @click.argument('fourth') + @click.argument('fifth') + @click.argument('sixth') + def command(): + """A command. + """ + + result = runner.invoke(cli, ['a_very_very_very_long', 'command', '--help'], + terminal_width=54) + assert not result.exception + assert result.output.splitlines() == [ + 'Usage: cli a_very_very_very_long command ', + ' [OPTIONS] FIRST SECOND THIRD FOURTH FIFTH', + ' SIXTH', + '', + ' A command.', + '', + 'Options:', + ' --help Show this message and exit.', + ] + + +def test_formatting_empty_help_lines(runner): + @click.command() + def cli(): + """Top level command + + """ + + result = runner.invoke(cli, ['--help']) + assert not result.exception + assert result.output.splitlines() == [ + 'Usage: cli [OPTIONS]', + '', + ' Top level command', + '', + '', + '', + 'Options:', + ' --help Show this message and exit.', + ] diff -Nru click-0.4.43+16.04.20160203/tests/test_imports.py click-6.7/tests/test_imports.py --- click-0.4.43+16.04.20160203/tests/test_imports.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/tests/test_imports.py 2016-12-01 12:33:48.000000000 +0000 @@ -0,0 +1,56 @@ +import sys +import json +import subprocess + +from click._compat import WIN + + +IMPORT_TEST = b'''\ +try: + import __builtin__ as builtins +except ImportError: + import builtins + +found_imports = set() +real_import = builtins.__import__ +import sys + +def tracking_import(module, locals=None, globals=None, fromlist=None, + level=0): + rv = real_import(module, locals, globals, fromlist, level) + if globals and globals['__name__'].startswith('click') and level == 0: + found_imports.add(module) + return rv +builtins.__import__ = tracking_import + +import click +rv = list(found_imports) +import json +click.echo(json.dumps(rv)) +''' + +ALLOWED_IMPORTS = set([ + 'weakref', 'os', 'struct', 'collections', 'sys', 'contextlib', + 'functools', 'stat', 're', 'codecs', 'inspect', 'itertools', 'io', + 'threading', 'colorama', 'errno' +]) + +if WIN: + ALLOWED_IMPORTS.update(['ctypes', 'ctypes.wintypes', 'msvcrt', 'time', + 'zlib']) + + +def test_light_imports(): + c = subprocess.Popen([sys.executable, '-'], stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + rv = c.communicate(IMPORT_TEST)[0] + + if sys.version_info[0] != 2: + rv = rv.decode('utf-8') + imported = json.loads(rv) + print(imported) + + for module in imported: + if module == 'click' or module.startswith('click.'): + continue + assert module in ALLOWED_IMPORTS diff -Nru click-0.4.43+16.04.20160203/tests/test_normalization.py click-6.7/tests/test_normalization.py --- click-0.4.43+16.04.20160203/tests/test_normalization.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/tests/test_normalization.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,39 @@ +import click + + +CONTEXT_SETTINGS = dict(token_normalize_func=lambda x: x.lower()) + + +def test_option_normalization(runner): + @click.command(context_settings=CONTEXT_SETTINGS) + @click.option('--foo') + @click.option('-x') + def cli(foo, x): + click.echo(foo) + click.echo(x) + + result = runner.invoke(cli, ['--FOO', '42', '-X', 23]) + assert result.output == '42\n23\n' + + +def test_choice_normalization(runner): + @click.command(context_settings=CONTEXT_SETTINGS) + @click.option('--choice', type=click.Choice(['Foo', 'Bar'])) + def cli(choice): + click.echo('Foo') + + result = runner.invoke(cli, ['--CHOICE', 'FOO']) + assert result.output == 'Foo\n' + + +def test_command_normalization(runner): + @click.group(context_settings=CONTEXT_SETTINGS) + def cli(): + pass + + @cli.command() + def foo(): + click.echo('here!') + + result = runner.invoke(cli, ['FOO']) + assert result.output == 'here!\n' diff -Nru click-0.4.43+16.04.20160203/tests/test_options.py click-6.7/tests/test_options.py --- click-0.4.43+16.04.20160203/tests/test_options.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/tests/test_options.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,337 @@ +# -*- coding: utf-8 -*- +import re +import os +import click +import pytest + +from click._compat import text_type + + +def test_prefixes(runner): + @click.command() + @click.option('++foo', is_flag=True, help='das foo') + @click.option('--bar', is_flag=True, help='das bar') + def cli(foo, bar): + click.echo('foo=%s bar=%s' % (foo, bar)) + + result = runner.invoke(cli, ['++foo', '--bar']) + assert not result.exception + assert result.output == 'foo=True bar=True\n' + + result = runner.invoke(cli, ['--help']) + assert re.search(r'\+\+foo\s+das foo', result.output) is not None + assert re.search(r'--bar\s+das bar', result.output) is not None + + +def test_invalid_option(runner): + try: + @click.command() + @click.option('foo') + def cli(foo): + pass + except TypeError as e: + assert 'No options defined but a name was passed (foo).' \ + in str(e) + else: + assert False, 'Expected a type error because of an invalid option.' + + +def test_invalid_nargs(runner): + try: + @click.command() + @click.option('--foo', nargs=-1) + def cli(foo): + pass + except TypeError as e: + assert 'Options cannot have nargs < 0' in str(e) + else: + assert False, 'Expected a type error because of an invalid option.' + + +def test_nargs_tup_composite_mult(runner): + @click.command() + @click.option('--item', type=(str, int), multiple=True) + def copy(item): + for item in item: + click.echo('name=%s id=%d' % item) + + result = runner.invoke(copy, ['--item', 'peter', '1', '--item', 'max', '2']) + assert not result.exception + assert result.output.splitlines() == [ + 'name=peter id=1', + 'name=max id=2', + ] + + +def test_counting(runner): + @click.command() + @click.option('-v', count=True, help='Verbosity', + type=click.IntRange(0, 3)) + def cli(v): + click.echo('verbosity=%d' % v) + + result = runner.invoke(cli, ['-vvv']) + assert not result.exception + assert result.output == 'verbosity=3\n' + + result = runner.invoke(cli, ['-vvvv']) + assert result.exception + assert 'Invalid value for "-v": 4 is not in the valid range of 0 to 3.' \ + in result.output + + result = runner.invoke(cli, []) + assert not result.exception + assert result.output == 'verbosity=0\n' + + result = runner.invoke(cli, ['--help']) + assert re.search('-v\s+Verbosity', result.output) is not None + + +@pytest.mark.parametrize('unknown_flag', ['--foo', '-f']) +def test_unknown_options(runner, unknown_flag): + @click.command() + def cli(): + pass + + result = runner.invoke(cli, [unknown_flag]) + assert result.exception + assert 'no such option: {0}'.format(unknown_flag) in result.output + + +def test_multiple_required(runner): + @click.command() + @click.option('-m', '--message', multiple=True, required=True) + def cli(message): + click.echo('\n'.join(message)) + + result = runner.invoke(cli, ['-m', 'foo', '-mbar']) + assert not result.exception + assert result.output == 'foo\nbar\n' + + result = runner.invoke(cli, []) + assert result.exception + assert 'Error: Missing option "-m" / "--message".' in result.output + + +def test_multiple_envvar(runner): + @click.command() + @click.option('--arg', multiple=True) + def cmd(arg): + click.echo('|'.join(arg)) + + result = runner.invoke(cmd, [], auto_envvar_prefix='TEST', + env={'TEST_ARG': 'foo bar baz'}) + assert not result.exception + assert result.output == 'foo|bar|baz\n' + + @click.command() + @click.option('--arg', multiple=True, envvar='X') + def cmd(arg): + click.echo('|'.join(arg)) + + result = runner.invoke(cmd, [], env={'X': 'foo bar baz'}) + assert not result.exception + assert result.output == 'foo|bar|baz\n' + + @click.command() + @click.option('--arg', multiple=True, type=click.Path()) + def cmd(arg): + click.echo('|'.join(arg)) + + result = runner.invoke(cmd, [], auto_envvar_prefix='TEST', + env={'TEST_ARG': 'foo%sbar' % os.path.pathsep}) + assert not result.exception + assert result.output == 'foo|bar\n' + + +def test_multiple_default_help(runner): + @click.command() + @click.option('--arg1', multiple=True, default=('foo', 'bar'), + show_default=True) + @click.option('--arg2', multiple=True, default=(1, 2), type=int, + show_default=True) + def cmd(arg, arg2): + pass + + result = runner.invoke(cmd, ['--help']) + assert not result.exception + assert 'foo, bar' in result.output + assert '1, 2' in result.output + + +def test_multiple_default_type(runner): + @click.command() + @click.option('--arg1', multiple=True, default=('foo', 'bar')) + @click.option('--arg2', multiple=True, default=(1, 'a')) + def cmd(arg1, arg2): + assert all(isinstance(e[0], text_type) for e in arg1) + assert all(isinstance(e[1], text_type) for e in arg1) + + assert all(isinstance(e[0], int) for e in arg2) + assert all(isinstance(e[1], text_type) for e in arg2) + + result = runner.invoke(cmd, '--arg1 a b --arg1 test 1 --arg2 2 ' + 'two --arg2 4 four'.split()) + assert not result.exception + + +def test_nargs_envvar(runner): + @click.command() + @click.option('--arg', nargs=2) + def cmd(arg): + click.echo('|'.join(arg)) + + result = runner.invoke(cmd, [], auto_envvar_prefix='TEST', + env={'TEST_ARG': 'foo bar'}) + assert not result.exception + assert result.output == 'foo|bar\n' + + @click.command() + @click.option('--arg', nargs=2, multiple=True) + def cmd(arg): + for item in arg: + click.echo('|'.join(item)) + + result = runner.invoke(cmd, [], auto_envvar_prefix='TEST', + env={'TEST_ARG': 'x 1 y 2'}) + assert not result.exception + assert result.output == 'x|1\ny|2\n' + + +def test_custom_validation(runner): + def validate_pos_int(ctx, value): + if value < 0: + raise click.BadParameter('Value needs to be positive') + return value + + @click.command() + @click.option('--foo', callback=validate_pos_int, default=1) + def cmd(foo): + click.echo(foo) + + result = runner.invoke(cmd, ['--foo', '-1']) + assert 'Invalid value for "--foo": Value needs to be positive' \ + in result.output + + result = runner.invoke(cmd, ['--foo', '42']) + assert result.output == '42\n' + + +def test_winstyle_options(runner): + @click.command() + @click.option('/debug;/no-debug', help='Enables or disables debug mode.') + def cmd(debug): + click.echo(debug) + + result = runner.invoke(cmd, ['/debug'], help_option_names=['/?']) + assert result.output == 'True\n' + result = runner.invoke(cmd, ['/no-debug'], help_option_names=['/?']) + assert result.output == 'False\n' + result = runner.invoke(cmd, [], help_option_names=['/?']) + assert result.output == 'False\n' + result = runner.invoke(cmd, ['/?'], help_option_names=['/?']) + assert '/debug; /no-debug Enables or disables debug mode.' in result.output + assert '/? Show this message and exit.' in result.output + + +def test_legacy_options(runner): + @click.command() + @click.option('-whatever') + def cmd(whatever): + click.echo(whatever) + + result = runner.invoke(cmd, ['-whatever', '42']) + assert result.output == '42\n' + result = runner.invoke(cmd, ['-whatever=23']) + assert result.output == '23\n' + + +def test_missing_choice(runner): + @click.command() + @click.option('--foo', type=click.Choice(['foo', 'bar']), + required=True) + def cmd(foo): + click.echo(foo) + + result = runner.invoke(cmd) + assert result.exit_code == 2 + assert 'Error: Missing option "--foo". Choose from foo, bar.' \ + in result.output + + +def test_multiline_help(runner): + @click.command() + @click.option('--foo', help=""" + hello + + i am + + multiline + """) + def cmd(foo): + click.echo(foo) + + result = runner.invoke(cmd, ['--help']) + assert result.exit_code == 0 + out = result.output.splitlines() + assert ' --foo TEXT hello' in out + assert ' i am' in out + assert ' multiline' in out + + +def test_argument_custom_class(runner): + class CustomArgument(click.Argument): + def get_default(self, ctx): + '''a dumb override of a default value for testing''' + return 'I am a default' + + @click.command() + @click.argument('testarg', cls=CustomArgument, default='you wont see me') + def cmd(testarg): + click.echo(testarg) + + result = runner.invoke(cmd) + assert 'I am a default' in result.output + assert 'you wont see me' not in result.output + + +def test_option_custom_class(runner): + class CustomOption(click.Option): + def get_help_record(self, ctx): + '''a dumb override of a help text for testing''' + return ('--help', 'I am a help text') + + @click.command() + @click.option('--testoption', cls=CustomOption, help='you wont see me') + def cmd(testoption): + click.echo(testoption) + + result = runner.invoke(cmd, ['--help']) + assert 'I am a help text' in result.output + assert 'you wont see me' not in result.output + + +def test_aliases_for_flags(runner): + @click.command() + @click.option('--warnings/--no-warnings', ' /-W', default=True) + def cli(warnings): + click.echo(warnings) + + result = runner.invoke(cli, ['--warnings']) + assert result.output == 'True\n' + result = runner.invoke(cli, ['--no-warnings']) + assert result.output == 'False\n' + result = runner.invoke(cli, ['-W']) + assert result.output == 'False\n' + + @click.command() + @click.option('--warnings/--no-warnings', '-w', default=True) + def cli_alt(warnings): + click.echo(warnings) + + result = runner.invoke(cli_alt, ['--warnings']) + assert result.output == 'True\n' + result = runner.invoke(cli_alt, ['--no-warnings']) + assert result.output == 'False\n' + result = runner.invoke(cli_alt, ['-w']) + assert result.output == 'True\n' diff -Nru click-0.4.43+16.04.20160203/tests/test_termui.py click-6.7/tests/test_termui.py --- click-0.4.43+16.04.20160203/tests/test_termui.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/tests/test_termui.py 2017-01-06 22:32:37.000000000 +0000 @@ -0,0 +1,14 @@ +import click + + +def test_progressbar_strip_regression(runner, monkeypatch): + label = ' padded line' + + @click.command() + def cli(): + with click.progressbar(tuple(range(10)), label=label) as progress: + for thing in progress: + pass + + monkeypatch.setattr(click._termui_impl, 'isatty', lambda _: True) + assert label in runner.invoke(cli, []).output diff -Nru click-0.4.43+16.04.20160203/tests/test_testing.py click-6.7/tests/test_testing.py --- click-0.4.43+16.04.20160203/tests/test_testing.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/tests/test_testing.py 2017-01-06 22:32:37.000000000 +0000 @@ -0,0 +1,204 @@ +import os +import sys + +import pytest +import click + +from click.testing import CliRunner + +from click._compat import PY2, WIN + +# Use the most reasonable io that users would use for the python version. +if PY2: + from cStringIO import StringIO as ReasonableBytesIO +else: + from io import BytesIO as ReasonableBytesIO + + +def test_runner(): + @click.command() + def test(): + i = click.get_binary_stream('stdin') + o = click.get_binary_stream('stdout') + while 1: + chunk = i.read(4096) + if not chunk: + break + o.write(chunk) + o.flush() + + runner = CliRunner() + result = runner.invoke(test, input='Hello World!\n') + assert not result.exception + assert result.output == 'Hello World!\n' + + runner = CliRunner(echo_stdin=True) + result = runner.invoke(test, input='Hello World!\n') + assert not result.exception + assert result.output == 'Hello World!\nHello World!\n' + + +def test_runner_with_stream(): + @click.command() + def test(): + i = click.get_binary_stream('stdin') + o = click.get_binary_stream('stdout') + while 1: + chunk = i.read(4096) + if not chunk: + break + o.write(chunk) + o.flush() + + runner = CliRunner() + result = runner.invoke(test, input=ReasonableBytesIO(b'Hello World!\n')) + assert not result.exception + assert result.output == 'Hello World!\n' + + runner = CliRunner(echo_stdin=True) + result = runner.invoke(test, input=ReasonableBytesIO(b'Hello World!\n')) + assert not result.exception + assert result.output == 'Hello World!\nHello World!\n' + + +def test_prompts(): + @click.command() + @click.option('--foo', prompt=True) + def test(foo): + click.echo('foo=%s' % foo) + + runner = CliRunner() + result = runner.invoke(test, input='wau wau\n') + assert not result.exception + assert result.output == 'Foo: wau wau\nfoo=wau wau\n' + + @click.command() + @click.option('--foo', prompt=True, hide_input=True) + def test(foo): + click.echo('foo=%s' % foo) + + runner = CliRunner() + result = runner.invoke(test, input='wau wau\n') + assert not result.exception + assert result.output == 'Foo: \nfoo=wau wau\n' + + +def test_getchar(): + @click.command() + def continue_it(): + click.echo(click.getchar()) + + runner = CliRunner() + result = runner.invoke(continue_it, input='y') + assert not result.exception + assert result.output == 'y\n' + + +def test_catch_exceptions(): + class CustomError(Exception): + pass + + @click.command() + def cli(): + raise CustomError(1) + + runner = CliRunner() + + result = runner.invoke(cli) + assert isinstance(result.exception, CustomError) + assert type(result.exc_info) is tuple + assert len(result.exc_info) == 3 + + with pytest.raises(CustomError): + runner.invoke(cli, catch_exceptions=False) + + CustomError = SystemExit + + result = runner.invoke(cli) + assert result.exit_code == 1 + + +@pytest.mark.skipif(WIN, reason='Test does not make sense on Windows.') +def test_with_color(): + @click.command() + def cli(): + click.secho('hello world', fg='blue') + + runner = CliRunner() + + result = runner.invoke(cli) + assert result.output == 'hello world\n' + assert not result.exception + + result = runner.invoke(cli, color=True) + assert result.output == click.style('hello world', fg='blue') + '\n' + assert not result.exception + + +def test_with_color_but_pause_not_blocking(): + @click.command() + def cli(): + click.pause() + + runner = CliRunner() + result = runner.invoke(cli, color=True) + assert not result.exception + assert result.output == '' + + +def test_exit_code_and_output_from_sys_exit(): + # See issue #362 + @click.command() + def cli_string(): + click.echo('hello world') + sys.exit('error') + + @click.command() + def cli_int(): + click.echo('hello world') + sys.exit(1) + + @click.command() + def cli_float(): + click.echo('hello world') + sys.exit(1.0) + + @click.command() + def cli_no_error(): + click.echo('hello world') + + runner = CliRunner() + + result = runner.invoke(cli_string) + assert result.exit_code == 1 + assert result.output == 'hello world\nerror\n' + + result = runner.invoke(cli_int) + assert result.exit_code == 1 + assert result.output == 'hello world\n' + + result = runner.invoke(cli_float) + assert result.exit_code == 1 + assert result.output == 'hello world\n1.0\n' + + result = runner.invoke(cli_no_error) + assert result.exit_code == 0 + assert result.output == 'hello world\n' + + +def test_env(): + @click.command() + def cli_env(): + click.echo('ENV=%s' % os.environ['TEST_CLICK_ENV']) + + runner = CliRunner() + + env_orig = dict(os.environ) + env = dict(env_orig) + assert 'TEST_CLICK_ENV' not in env + env['TEST_CLICK_ENV'] = 'some_value' + result = runner.invoke(cli_env, env=env) + assert result.exit_code == 0 + assert result.output == 'ENV=some_value\n' + + assert os.environ == env_orig diff -Nru click-0.4.43+16.04.20160203/tests/test_utils.py click-6.7/tests/test_utils.py --- click-0.4.43+16.04.20160203/tests/test_utils.py 1970-01-01 00:00:00.000000000 +0000 +++ click-6.7/tests/test_utils.py 2016-11-21 13:28:45.000000000 +0000 @@ -0,0 +1,283 @@ +import os +import sys + +import pytest + +import click +import click.utils +import click._termui_impl +from click._compat import WIN, PY2 + + +def test_echo(runner): + with runner.isolation() as out: + click.echo(u'\N{SNOWMAN}') + click.echo(b'\x44\x44') + click.echo(42, nl=False) + click.echo(b'a', nl=False) + click.echo('\x1b[31mx\x1b[39m', nl=False) + bytes = out.getvalue().replace(b'\r\n', b'\n') + assert bytes == b'\xe2\x98\x83\nDD\n42ax' + + # If we are in Python 2, we expect that writing bytes into a string io + # does not do anything crazy. In Python 3 + if sys.version_info[0] == 2: + import StringIO + sys.stdout = x = StringIO.StringIO() + try: + click.echo('\xf6') + finally: + sys.stdout = sys.__stdout__ + assert x.getvalue() == '\xf6\n' + + # And in any case, if wrapped, we expect bytes to survive. + @click.command() + def cli(): + click.echo(b'\xf6') + result = runner.invoke(cli, []) + assert result.output_bytes == b'\xf6\n' + + # Ensure we do not strip for bytes. + with runner.isolation() as out: + click.echo(bytearray(b'\x1b[31mx\x1b[39m'), nl=False) + assert out.getvalue() == b'\x1b[31mx\x1b[39m' + + +def test_echo_custom_file(): + import io + f = io.StringIO() + click.echo(u'hello', file=f) + assert f.getvalue() == u'hello\n' + + +def test_styling(): + examples = [ + ('x', dict(fg='black'), '\x1b[30mx\x1b[0m'), + ('x', dict(fg='red'), '\x1b[31mx\x1b[0m'), + ('x', dict(fg='green'), '\x1b[32mx\x1b[0m'), + ('x', dict(fg='yellow'), '\x1b[33mx\x1b[0m'), + ('x', dict(fg='blue'), '\x1b[34mx\x1b[0m'), + ('x', dict(fg='magenta'), '\x1b[35mx\x1b[0m'), + ('x', dict(fg='cyan'), '\x1b[36mx\x1b[0m'), + ('x', dict(fg='white'), '\x1b[37mx\x1b[0m'), + ('x', dict(bg='black'), '\x1b[40mx\x1b[0m'), + ('x', dict(bg='red'), '\x1b[41mx\x1b[0m'), + ('x', dict(bg='green'), '\x1b[42mx\x1b[0m'), + ('x', dict(bg='yellow'), '\x1b[43mx\x1b[0m'), + ('x', dict(bg='blue'), '\x1b[44mx\x1b[0m'), + ('x', dict(bg='magenta'), '\x1b[45mx\x1b[0m'), + ('x', dict(bg='cyan'), '\x1b[46mx\x1b[0m'), + ('x', dict(bg='white'), '\x1b[47mx\x1b[0m'), + ('foo bar', dict(blink=True), '\x1b[5mfoo bar\x1b[0m'), + ('foo bar', dict(underline=True), '\x1b[4mfoo bar\x1b[0m'), + ('foo bar', dict(bold=True), '\x1b[1mfoo bar\x1b[0m'), + ('foo bar', dict(dim=True), '\x1b[2mfoo bar\x1b[0m'), + ] + for text, styles, ref in examples: + assert click.style(text, **styles) == ref + assert click.unstyle(ref) == text + + +def test_filename_formatting(): + assert click.format_filename(b'foo.txt') == 'foo.txt' + assert click.format_filename(b'/x/foo.txt') == '/x/foo.txt' + assert click.format_filename(u'/x/foo.txt') == '/x/foo.txt' + assert click.format_filename(u'/x/foo.txt', shorten=True) == 'foo.txt' + + # filesystem encoding on windows permits this. + if not WIN: + assert click.format_filename(b'/x/foo\xff.txt', shorten=True) \ + == u'foo\ufffd.txt' + + +def test_prompts(runner): + @click.command() + def test(): + if click.confirm('Foo'): + click.echo('yes!') + else: + click.echo('no :(') + + result = runner.invoke(test, input='y\n') + assert not result.exception + assert result.output == 'Foo [y/N]: y\nyes!\n' + + result = runner.invoke(test, input='\n') + assert not result.exception + assert result.output == 'Foo [y/N]: \nno :(\n' + + result = runner.invoke(test, input='n\n') + assert not result.exception + assert result.output == 'Foo [y/N]: n\nno :(\n' + + @click.command() + def test_no(): + if click.confirm('Foo', default=True): + click.echo('yes!') + else: + click.echo('no :(') + + result = runner.invoke(test_no, input='y\n') + assert not result.exception + assert result.output == 'Foo [Y/n]: y\nyes!\n' + + result = runner.invoke(test_no, input='\n') + assert not result.exception + assert result.output == 'Foo [Y/n]: \nyes!\n' + + result = runner.invoke(test_no, input='n\n') + assert not result.exception + assert result.output == 'Foo [Y/n]: n\nno :(\n' + + +@pytest.mark.skipif(WIN, reason='Different behavior on windows.') +def test_prompts_abort(monkeypatch, capsys): + def f(_): + raise KeyboardInterrupt() + + monkeypatch.setattr('click.termui.hidden_prompt_func', f) + + try: + click.prompt('Password', hide_input=True) + except click.Abort: + click.echo('Screw you.') + + out, err = capsys.readouterr() + assert out == 'Password: \nScrew you.\n' + + +@pytest.mark.skipif(WIN, reason='Different behavior on windows.') +@pytest.mark.parametrize('cat', ['cat', 'cat ', 'cat ']) +def test_echo_via_pager(monkeypatch, capfd, cat): + monkeypatch.setitem(os.environ, 'PAGER', cat) + monkeypatch.setattr(click._termui_impl, 'isatty', lambda x: True) + click.echo_via_pager('haha') + out, err = capfd.readouterr() + assert out == 'haha\n' + + +@pytest.mark.skipif(WIN, reason='Test does not make sense on Windows.') +def test_echo_color_flag(monkeypatch, capfd): + isatty = True + monkeypatch.setattr(click._compat, 'isatty', lambda x: isatty) + + text = 'foo' + styled_text = click.style(text, fg='red') + assert styled_text == '\x1b[31mfoo\x1b[0m' + + click.echo(styled_text, color=False) + out, err = capfd.readouterr() + assert out == text + '\n' + + click.echo(styled_text, color=True) + out, err = capfd.readouterr() + assert out == styled_text + '\n' + + isatty = True + click.echo(styled_text) + out, err = capfd.readouterr() + assert out == styled_text + '\n' + + isatty = False + click.echo(styled_text) + out, err = capfd.readouterr() + assert out == text + '\n' + + +@pytest.mark.skipif(WIN, reason='Test too complex to make work windows.') +def test_echo_writing_to_standard_error(capfd, monkeypatch): + def emulate_input(text): + """Emulate keyboard input.""" + if sys.version_info[0] == 2: + from StringIO import StringIO + else: + from io import StringIO + monkeypatch.setattr(sys, 'stdin', StringIO(text)) + + click.echo('Echo to standard output') + out, err = capfd.readouterr() + assert out == 'Echo to standard output\n' + assert err == '' + + click.echo('Echo to standard error', err=True) + out, err = capfd.readouterr() + assert out == '' + assert err == 'Echo to standard error\n' + + emulate_input('asdlkj\n') + click.prompt('Prompt to stdin') + out, err = capfd.readouterr() + assert out == 'Prompt to stdin: ' + assert err == '' + + emulate_input('asdlkj\n') + click.prompt('Prompt to stderr', err=True) + out, err = capfd.readouterr() + assert out == '' + assert err == 'Prompt to stderr: ' + + emulate_input('y\n') + click.confirm('Prompt to stdin') + out, err = capfd.readouterr() + assert out == 'Prompt to stdin [y/N]: ' + assert err == '' + + emulate_input('y\n') + click.confirm('Prompt to stderr', err=True) + out, err = capfd.readouterr() + assert out == '' + assert err == 'Prompt to stderr [y/N]: ' + + monkeypatch.setattr(click.termui, 'isatty', lambda x: True) + monkeypatch.setattr(click.termui, 'getchar', lambda: ' ') + + click.pause('Pause to stdout') + out, err = capfd.readouterr() + assert out == 'Pause to stdout\n' + assert err == '' + + click.pause('Pause to stderr', err=True) + out, err = capfd.readouterr() + assert out == '' + assert err == 'Pause to stderr\n' + + +def test_open_file(runner): + with runner.isolated_filesystem(): + with open('hello.txt', 'w') as f: + f.write('Cool stuff') + + @click.command() + @click.argument('filename') + def cli(filename): + with click.open_file(filename) as f: + click.echo(f.read()) + click.echo('meep') + + result = runner.invoke(cli, ['hello.txt']) + assert result.exception is None + assert result.output == 'Cool stuff\nmeep\n' + + result = runner.invoke(cli, ['-'], input='foobar') + assert result.exception is None + assert result.output == 'foobar\nmeep\n' + + +@pytest.mark.xfail(WIN and not PY2, reason='God knows ...') +def test_iter_keepopenfile(tmpdir): + expected = list(map(str, range(10))) + p = tmpdir.mkdir('testdir').join('testfile') + p.write(os.linesep.join(expected)) + f = p.open() + for e_line, a_line in zip(expected, click.utils.KeepOpenFile(f)): + assert e_line == a_line.strip() + + +@pytest.mark.xfail(WIN and not PY2, reason='God knows ...') +def test_iter_lazyfile(tmpdir): + expected = list(map(str, range(10))) + p = tmpdir.mkdir('testdir').join('testfile') + p.write(os.linesep.join(expected)) + f = p.open() + for e_line, a_line in zip(expected, click.utils.LazyFile(f.name)): + assert e_line == a_line.strip() diff -Nru click-0.4.43+16.04.20160203/tox.ini click-6.7/tox.ini --- click-0.4.43+16.04.20160203/tox.ini 2016-02-03 11:17:38.000000000 +0000 +++ click-6.7/tox.ini 1970-01-01 00:00:00.000000000 +0000 @@ -1,6 +0,0 @@ -[tox] -envlist = py27,py32,py33,py34 - -[testenv] -commands = python -m unittest discover -vv click.tests -sitepackages = True