Skip to content

Commit

Permalink
Merge pull request #38 from jacebrowning/release/v1.6
Browse files Browse the repository at this point in the history
Release v1.6
  • Loading branch information
jacebrowning authored May 24, 2020
2 parents 82bff8c + 140c59c commit e8daafe
Show file tree
Hide file tree
Showing 16 changed files with 317 additions and 322 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
__pycache__
.ipynb_checkpoints
setup.py
pip-wheel-metadata/

# Temporary OS files
Icon*
Expand Down
1 change: 1 addition & 0 deletions .pylint.ini
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ disable=
wildcard-import,
unused-wildcard-import,
singleton-comparison,
bad-continuation,

# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
Expand Down
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
# 1.5 (unreleased)
# 1.6 (2020-05-24)

- Updated logging to use the source filename when the module is `'__main__'`.

# 1.5 (2020-03-28)

- Fixed `init()` to handle invalid `verbosity` levels and default to **DEBUG**.

Expand Down
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ RANDOM_SEED ?= $(shell date +%s)
FAILURES := .cache/v/cache/lastfailed

PYTEST_OPTIONS := --random --random-seed=$(RANDOM_SEED)
ifdef DISABLE_COVERAGE
PYTEST_OPTIONS += --no-cov --disable-warnings
ifndef DISABLE_COVERAGE
PYTEST_OPTIONS += --cov=$(PACKAGE)
endif
PYTEST_RERUN_OPTIONS := --last-failed --exitfirst

Expand Down Expand Up @@ -135,9 +135,9 @@ $(MKDOCS_INDEX): docs/requirements.txt mkdocs.yml docs/*.md
@ cd docs/about && ln -sf ../../LICENSE.md license.md
poetry run mkdocs build --clean --strict

# Workaround: https://github.com/rtfd/readthedocs.org/issues/5090
docs/requirements.txt: poetry.lock
@ poetry run pip freeze -qqq | grep mkdocs > $@
@ poetry run pip freeze -qqq | grep Pygments >> $@

.PHONY: uml
uml: install docs/*.png
Expand Down
135 changes: 89 additions & 46 deletions bin/verchew
Original file line number Diff line number Diff line change
Expand Up @@ -38,27 +38,28 @@ from collections import OrderedDict
from subprocess import PIPE, STDOUT, Popen


try:
import configparser # Python 3
except ImportError:
import ConfigParser as configparser # Python 2
PY2 = sys.version_info[0] == 2

__version__ = '1.6.2'
if PY2:
import ConfigParser as configparser
from urllib import urlretrieve
else:
import configparser
from urllib.request import urlretrieve

PY2 = sys.version_info[0] == 2
__version__ = '3.1'

SCRIPT_URL = (
"https://raw.githubusercontent.com/jacebrowning/verchew/master/verchew/script.py"
)

CONFIG_FILENAMES = [
'verchew.ini',
'.verchew.ini',
'.verchewrc',
'.verchew',
]
CONFIG_FILENAMES = ['verchew.ini', '.verchew.ini', '.verchewrc', '.verchew']

SAMPLE_CONFIG = """
[Python]
cli = python
versions = Python 3.5 | Python 3.6
version = Python 3.5 || Python 3.6
[Legacy Python]
Expand All @@ -81,16 +82,16 @@ optional = true

STYLE = {
"~": "✔",
"*": "⭑",
"?": "⚠",
"?": "▴",
"x": "✘",
"#": "䷉",
}

COLOR = {
"x": "\033[91m", # red
"~": "\033[92m", # green
"?": "\033[93m", # yellow
"*": "\033[94m", # cyan
"x": "\033[91m", # red
"#": "\033[96m", # cyan
None: "\033[0m", # reset
}

Expand All @@ -110,6 +111,10 @@ def main():
log.debug("PWD: %s", os.getenv('PWD'))
log.debug("PATH: %s", os.getenv('PATH'))

if args.vendor:
vendor_script(args.vendor)
sys.exit(0)

path = find_config(args.root, generate=args.init)
config = parse_config(path)

Expand All @@ -118,22 +123,37 @@ def main():


def parse_args():
parser = argparse.ArgumentParser()
parser = argparse.ArgumentParser(description="System dependency version checker.",)

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")
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(
'--exit-code',
action='store_true',
help="return a non-zero exit code on failure",
)

group_logging = parser.add_mutually_exclusive_group()
group_logging.add_argument(
'-v', '--verbose', action='count', default=0, help="enable verbose logging"
)
group_logging.add_argument(
'-q', '--quiet', action='store_true', help="suppress all output on success"
)

group_commands = parser.add_argument_group('commands')
group_commands.add_argument(
'--init', action='store_true', help="generate a sample configuration file"
)

group_commands.add_argument(
'--vendor', metavar='PATH', help="download the program for offline use"
)

args = parser.parse_args()

Expand All @@ -151,6 +171,20 @@ def configure_logging(count=0):
logging.basicConfig(level=level, format="%(levelname)s: %(message)s")


def vendor_script(path):
root = os.path.abspath(os.path.join(path, os.pardir))
if not os.path.isdir(root):
log.info("Creating directory %s", root)
os.makedirs(root)

log.info("Downloading %s to %s", SCRIPT_URL, path)
urlretrieve(SCRIPT_URL, path)

log.debug("Making %s executable", path)
mode = os.stat(path).st_mode
os.chmod(path, mode | 0o111)


def find_config(root=None, filenames=None, generate=False):
root = root or os.getcwd()
filenames = filenames or CONFIG_FILENAMES
Expand Down Expand Up @@ -186,7 +220,7 @@ def generate_config(root=None, filenames=None):


def parse_config(path):
data = OrderedDict()
data = OrderedDict() # type: ignore

log.info("Parsing config file: %s", path)
config = configparser.ConfigParser()
Expand All @@ -198,9 +232,9 @@ def parse_config(path):
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('|')]
version = data[name].get('version') or ""
data[name]['version'] = version
data[name]['patterns'] = [v.strip() for v in version.split('||')]

return data

Expand All @@ -214,23 +248,28 @@ def check_dependencies(config):

for pattern in settings['patterns']:
if match_version(pattern, output):
show(_("~") + " MATCHED: {0}".format(pattern))
show(_("~") + " MATCHED: {0}".format(pattern or "<anything>"))
success.append(_("~"))
break
else:
if settings.get('optional'):
show(_("?") + " EXPECTED: {0}".format(settings['versions']))
show(_("?") + " EXPECTED (OPTIONAL): {0}".format(settings['version']))
success.append(_("?"))
else:
if QUIET:
print("Unmatched {0} version: {1}".format(
name,
settings['versions'],
))
show(_("x") + " EXPECTED: {0}".format(settings['versions']))
if "not found" in output:
actual = "Not found"
else:
actual = output.split('\n')[0].strip('.')
expected = settings['version'] or "<anything>"
print("{0}: {1}, EXPECTED: {2}".format(name, actual, expected))
show(
_("x")
+ " EXPECTED: {0}".format(settings['version'] or "<anything>")
)
success.append(_("x"))
if settings.get('message'):
show(_("*") + " MESSAGE: {0}".format(settings['message']))
show(_("#") + " MESSAGE: {0}".format(settings['message']))

show("Results: " + " ".join(success), head=True)

Expand All @@ -247,12 +286,16 @@ def get_version(program, argument=None):

show("$ {0}".format(" ".join(args)))
output = call(args)
show(output.splitlines()[0] if output else "<nothing>")
lines = output.splitlines()
show(lines[0] if lines else "<nothing>")

return output


def match_version(pattern, output):
if "not found" in output.split('\n')[0]:
return False

regex = pattern.replace('.', r'\.') + r'(\b|/)'

log.debug("Matching %s: %s", regex, output)
Expand Down Expand Up @@ -289,7 +332,7 @@ def show(text, start='', end='\n', head=False):
if log.getEffectiveLevel() < logging.WARNING:
log.info(text)
else:
formatted = (start + text + end)
formatted = start + text + end
if PY2:
formatted = formatted.encode('utf-8')
sys.stdout.write(formatted)
Expand All @@ -303,7 +346,7 @@ def _(word, is_tty=None, supports_utf8=None, supports_ansi=None):
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'
supports_utf8 = str(sys.stdout.encoding).lower() == 'utf-8'
if supports_ansi is None:
supports_ansi = sys.platform != 'win32' or 'ANSICON' in os.environ

Expand Down
3 changes: 2 additions & 1 deletion docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
mkdocs==1.1
mkdocs==1.0.4
Pygments==2.6.1
15 changes: 9 additions & 6 deletions log/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import warnings
from importlib import reload

from . import filters, state
from . import filters, settings, state


__all__ = ['reset', 'init', 'silence']
Expand All @@ -20,6 +20,8 @@
def reset():
logging.shutdown()
reload(logging)
state.initialized = False
state.silenced.clear()


def init(*, debug=False, verbosity=None, **kwargs):
Expand All @@ -37,15 +39,15 @@ def init(*, debug=False, verbosity=None, **kwargs):

custom_format = kwargs.get('format')
if debug:
state.default_level = logging.DEBUG
settings.DEFAULT_LEVEL = logging.DEBUG
elif verbosity is not None:
try:
state.default_level = VERBOSITY_TO_LEVEL[verbosity]
settings.DEFAULT_LEVEL = VERBOSITY_TO_LEVEL[verbosity]
except KeyError:
state.default_level = logging.DEBUG
settings.DEFAULT_LEVEL = logging.DEBUG

kwargs['level'] = kwargs.get('level', state.default_level)
kwargs['format'] = kwargs.get('format', state.default_format)
kwargs['level'] = kwargs.get('level', settings.DEFAULT_LEVEL)
kwargs['format'] = kwargs.get('format', settings.DEFAULT_FORMAT)
logging.basicConfig(**kwargs)

if custom_format:
Expand Down Expand Up @@ -74,3 +76,4 @@ def silence(*names, allow_info=False, allow_warning=False, allow_error=False):

for name in names:
logging.getLogger(name).setLevel(level)
state.silenced.add(name)
5 changes: 5 additions & 0 deletions log/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import logging


DEFAULT_LEVEL = logging.INFO
DEFAULT_FORMAT = "%(levelname)s: %(name)s: %(message)s"
6 changes: 2 additions & 4 deletions log/state.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import logging
from typing import Set


default_level = logging.INFO
default_format = "%(levelname)s: %(name)s: %(message)s"

initialized = False
silenced: Set[str] = set()
9 changes: 0 additions & 9 deletions log/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,10 @@

import logging

import pytest

import log


def pytest_configure(config):
"""Disable verbose output when running tests."""
logging.basicConfig(level=logging.DEBUG)

terminal = config.pluginmanager.getplugin('terminal')
terminal.TerminalReporter.showfspath = False


@pytest.fixture(autouse=True)
def reset_state():
log.state.default_level = None # type: ignore
Loading

0 comments on commit e8daafe

Please sign in to comment.