PK!S<<verchew/__init__.py"""Package for verchew.""" from .script import __version__ PK!o||verchew/__main__.py#!/usr/bin/env python """Package entry point.""" from verchew.script import main if __name__ == '__main__': main() PK!)+ " "verchew/script.py#!/usr/bin/env python # -*- coding: utf-8 -*- # The MIT License (MIT) # Copyright © 2016, Jace Browning # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # # Source: https://github.com/jacebrowning/verchew # Documentation: https://verchew.readthedocs.io # Package: https://pypi.org/project/verchew from __future__ import unicode_literals import argparse import logging import os import re import sys from collections import OrderedDict from subprocess import PIPE, STDOUT, Popen try: import configparser # Python 3 except ImportError: import ConfigParser as configparser # Python 2 __version__ = '1.6.2' PY2 = sys.version_info[0] == 2 CONFIG_FILENAMES = [ 'verchew.ini', '.verchew.ini', '.verchewrc', '.verchew', ] SAMPLE_CONFIG = """ [Python] cli = python versions = Python 3.5 | Python 3.6 [Legacy Python] cli = python2 version = Python 2.7 [virtualenv] cli = virtualenv version = 15 message = Only required with Python 2. [Make] cli = make version = GNU Make optional = true """.strip() STYLE = { "~": "✔", "*": "⭑", "?": "⚠", "x": "✘", } COLOR = { "x": "\033[91m", # red "~": "\033[92m", # green "?": "\033[93m", # yellow "*": "\033[94m", # cyan None: "\033[0m", # reset } QUIET = False log = logging.getLogger(__name__) def main(): global QUIET args = parse_args() configure_logging(args.verbose) if args.quiet: QUIET = True log.debug("PWD: %s", os.getenv('PWD')) log.debug("PATH: %s", os.getenv('PATH')) path = find_config(args.root, generate=args.init) config = parse_config(path) if not check_dependencies(config) and args.exit_code: sys.exit(1) def parse_args(): parser = argparse.ArgumentParser() version = "%(prog)s v" + __version__ parser.add_argument('--version', action='version', version=version) parser.add_argument('-r', '--root', metavar='PATH', help="specify a custom project root directory") parser.add_argument('--init', action='store_true', help="generate a sample configuration file") parser.add_argument('--exit-code', action='store_true', help="return a non-zero exit code on failure") group = parser.add_mutually_exclusive_group() group.add_argument('-v', '--verbose', action='count', default=0, help="enable verbose logging") group.add_argument('-q', '--quiet', action='store_true', help="suppress all output on success") args = parser.parse_args() return args def configure_logging(count=0): if count == 0: level = logging.WARNING elif count == 1: level = logging.INFO else: level = logging.DEBUG logging.basicConfig(level=level, format="%(levelname)s: %(message)s") def find_config(root=None, filenames=None, generate=False): root = root or os.getcwd() filenames = filenames or CONFIG_FILENAMES path = None log.info("Looking for config file in: %s", root) log.debug("Filename options: %s", ", ".join(filenames)) for filename in os.listdir(root): if filename in filenames: path = os.path.join(root, filename) log.info("Found config file: %s", path) return path if generate: path = generate_config(root, filenames) return path msg = "No config file found in: {0}".format(root) raise RuntimeError(msg) def generate_config(root=None, filenames=None): root = root or os.getcwd() filenames = filenames or CONFIG_FILENAMES path = os.path.join(root, filenames[0]) log.info("Generating sample config: %s", path) with open(path, 'w') as config: config.write(SAMPLE_CONFIG + '\n') return path def parse_config(path): data = OrderedDict() log.info("Parsing config file: %s", path) config = configparser.ConfigParser() config.read(path) for section in config.sections(): data[section] = OrderedDict() for name, value in config.items(section): data[section][name] = value for name in data: versions = data[name].get('versions', data[name].pop('version', "")) data[name]['versions'] = versions data[name]['patterns'] = [v.strip() for v in versions.split('|')] return data def check_dependencies(config): success = [] for name, settings in config.items(): show("Checking for {0}...".format(name), head=True) output = get_version(settings['cli'], settings.get('cli_version_arg')) for pattern in settings['patterns']: if match_version(pattern, output): show(_("~") + " MATCHED: {0}".format(pattern)) success.append(_("~")) break else: if settings.get('optional'): show(_("?") + " EXPECTED: {0}".format(settings['versions'])) success.append(_("?")) else: if QUIET: print("Unmatched {0} version: {1}".format( name, settings['versions'], )) show(_("x") + " EXPECTED: {0}".format(settings['versions'])) success.append(_("x")) if settings.get('message'): show(_("*") + " MESSAGE: {0}".format(settings['message'])) show("Results: " + " ".join(success), head=True) return _("x") not in success def get_version(program, argument=None): if argument is None: args = [program, '--version'] elif argument: args = [program, argument] else: args = [program] show("$ {0}".format(" ".join(args))) output = call(args) show(output.splitlines()[0] if output else "") return output def match_version(pattern, output): regex = pattern.replace('.', r'\.') + r'(\b|/)' log.debug("Matching %s: %s", regex, output) match = re.match(regex, output) if match is None: match = re.match(r'.*[^\d.]' + regex, output) return bool(match) def call(args): try: process = Popen(args, stdout=PIPE, stderr=STDOUT) except OSError: log.debug("Command not found: %s", args[0]) output = "sh: command not found: {0}".format(args[0]) else: raw = process.communicate()[0] output = raw.decode('utf-8').strip() log.debug("Command output: %r", output) return output def show(text, start='', end='\n', head=False): """Python 2 and 3 compatible version of print.""" if QUIET: return if head: start = '\n' end = '\n\n' if log.getEffectiveLevel() < logging.WARNING: log.info(text) else: formatted = (start + text + end) if PY2: formatted = formatted.encode('utf-8') sys.stdout.write(formatted) sys.stdout.flush() def _(word, is_tty=None, supports_utf8=None, supports_ansi=None): """Format and colorize a word based on available encoding.""" formatted = word if is_tty is None: is_tty = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty() if supports_utf8 is None: supports_utf8 = sys.stdout.encoding == 'UTF-8' if supports_ansi is None: supports_ansi = sys.platform != 'win32' or 'ANSICON' in os.environ style_support = supports_utf8 color_support = is_tty and supports_ansi if style_support: formatted = STYLE.get(word, word) if color_support and COLOR.get(word): formatted = COLOR[word] + formatted + COLOR[None] return formatted if __name__ == '__main__': # pragma: no cover main() PK!ޙPb""verchew/tests/__init__.py"""Unit tests for the package.""" PK!e@}}verchew/tests/conftest.py"""Unit tests configuration file.""" import logging def pytest_configure(config): """Disable verbose output when running tests.""" logging.basicConfig(level=logging.DEBUG) terminal = config.pluginmanager.getplugin('terminal') base = terminal.TerminalReporter class QuietReporter(base): """A py.test reporting that only shows dots when running tests.""" def __init__(self, *args, **kwargs): base.__init__(self, *args, **kwargs) self.verbosity = 0 self.showlongtestinfo = False self.showfspath = False terminal.TerminalReporter = QuietReporter PK! verchew/tests/test_script.py# -*- coding: utf-8 -*- # pylint: disable=unused-variable,unused-argument,expression-not-assigned,singleton-comparison from __future__ import unicode_literals import pytest from expecter import expect from verchew.script import _, find_config, get_version, match_version, parse_config def write(config, text, indent=8): config.write(text.replace(' ' * indent, '')) def describe_find_config(): @pytest.fixture def config(tmpdir): tmpdir.chdir() path = tmpdir.join("foo.bar") path.write("") return path def when_missing(config): with expect.raises(RuntimeError): find_config() def when_missing_and_generate(config): path = find_config(generate=True) generated_path = str(config).replace("foo.bar", "verchew.ini") expect(path) == generated_path def when_found(config): path = find_config(filenames=["foo.bar"]) expect(path) == config def describe_parse_config(): @pytest.fixture def config(tmpdir): tmpdir.chdir() path = tmpdir.join("verchew.ini") path.write("") return path def with_no_content(config): config.write("") expect(parse_config(str(config))) == {} def with_an_empty_section(config): write(config, """ [Foobar] """) expect(parse_config(str(config))) == { 'Foobar': { 'versions': '', 'patterns': [''], }, } def with_a_filled_section(config): write(config, """ [Foobar] cli = foobar version = v1.2.3 """) expect(parse_config(str(config))) == { 'Foobar': { 'cli': 'foobar', 'versions': 'v1.2.3', 'patterns': ['v1.2.3'], }, } def with_multiple_versions(config): write(config, """ [Foobar] version = 1 versions = 2 | 3 | 4 """) expect(parse_config(str(config))) == { 'Foobar': { 'versions': '2 | 3 | 4', 'patterns': ['2', '3', '4'], }, } def describe_get_version(): def when_missing(): expect(get_version('foobar')) == "sh: command not found: foobar" def when_found(): expect(get_version('python')).contains("Python ") def with_custom_argument(): expect(get_version('python', argument='-V')).contains("Python ") def with_no_argument(): expect(get_version('pip', argument='')).contains("Usage:") def when_no_output(): expect(get_version('echo', argument='')) == "" def describe_match_version(): def when_exact_match(): expect(match_version("1.2.3", "1.2.3")) == True def when_partial_match(): expect(match_version("1.2.", "1.2.3")) == True def when_partial_match_at_boundary(): expect(match_version("1.2", "1.2.3")) == True def when_mismatch(): expect(match_version("1.", "2.0")) == False def when_mismatch_at_boundary(): expect(match_version("1.2", "1.23")) == False def when_match_with_name(): expect(match_version("1.", "Foobar 1.2.3")) == True def when_mismatch_with_name(): expect(match_version("2.", "Foobar 1.2.3")) == False def when_match_with_version(): expect(match_version("1.", "v1.2.3")) == True def when_mismatch_with_version(): expect(match_version("2.", "v1.2.3")) == False def when_match_with_dash_followed_by_path(): """Test that the output of `printenv DIRENV_DIR` can be matched.""" expect(match_version("-", "-/foo/bar")) == True def describe_format(): def default(): expect(_('~')) == "~" @pytest.mark.parametrize("is_tty,supports_utf8,supports_ansi,formatted", [ (0, 0, 0, "~"), (0, 0, 1, "~"), (0, 1, 0, "✔"), (0, 1, 1, "✔"), (1, 0, 0, "~"), (1, 0, 1, "\033[92m~\033[0m"), (1, 1, 0, "✔"), (1, 1, 1, "\033[92m✔\033[0m"), ]) def with_options(is_tty, supports_utf8, supports_ansi, formatted): options = dict(is_tty=is_tty, supports_utf8=supports_utf8, supports_ansi=supports_ansi) expect(_('~', **options)) == formatted PK!HD'/(verchew-1.6.2.dist-info/entry_points.txtN+I/N.,()*K-JH-z <..PK!t@@"verchew-1.6.2.dist-info/LICENSE.md**The MIT License (MIT)** Copyright © 2016, Jace Browning Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!H|n-WYverchew-1.6.2.dist-info/WHEEL A н#Z;/" bFF]xzwK;<*mTֻ0*Ri.4Vm0[H, JPK!H?E+ verchew-1.6.2.dist-info/METADATAWnF}߯"'Y۹qm0xEȍ]v/Y' (OK:Ë,:RlCiwٙ3CxJ"^ęV$f#_T*D(J+B% ,&`t!%O[\i0,Rڤai ZBY<<8e?jMbkRXL\B(GN*H ]M?X gJ]<7nKi-uN/^aZnO7ݝAa} [+'R`b*r]I9`cщf\pC5F}ar$ dËٶ%았)B:vkRy&ۡJsil+:nx;EAEM0OODkm8DNT5JpBu䌢ԓѝF,/84Uyga `"Bф0wf3X"\%/ RFٽ{>H-cam8kuoT.C)ˈ Z5}x]}cr11Ǣ*kyAɇ8(bw#.NAJ+ttzLGkNDRZbȡT=oIeQ-^& ׳|B0a-c]řvc+8`m T(R6RnyQ攚D լh wtuApLvo2@v}ݼg  A;RXcl35QmVKŶpi6i*E,qLE=%r:;._4+].8zϿS]QW8뉌O 0N$3{><{Եۧ)O&§C:g {/{N] ß(>;GEcFZr Yට@0`Ѷz!VSF ,DY=@iμJOj_ '8ȢPK!HAN]verchew-1.6.2.dist-info/RECORDuMc@}Ҕb  :H6 !(B5"}zz&s}Kl|$[_ѷƆhț}p/"/lL1P+mC%qhp4,71讲n6BseDlpK /O%ߠM5-d2UsA?rDefCR/d=7y8Ĩ 4: tH+bIq/i3}]S؍F478-b'{&R9^_ӍO(?ZɦDX8P5/d`+ WO6;rO(.XEO MI ymfLm8wtH.RC[pA}ح#צbN^R,#IO齅Pɽ3V~Hs|}5ֲh .ef-|$"__y͎2Uib\jO~p%)M5/3(ϛ9)!mPK!S<<verchew/__init__.pyPK!o||mverchew/__main__.pyPK!)+ " "verchew/script.pyPK!ޙPb""i#verchew/tests/__init__.pyPK!e@}}#verchew/tests/conftest.pyPK! v&verchew/tests/test_script.pyPK!HD'/(7verchew-1.6.2.dist-info/entry_points.txtPK!t@@"28verchew-1.6.2.dist-info/LICENSE.mdPK!H|n-WY<verchew-1.6.2.dist-info/WHEELPK!H?E+ D=verchew-1.6.2.dist-info/METADATAPK!HAN]Cverchew-1.6.2.dist-info/RECORDPK $E