Colors and tty (#53)
This commit is contained in:
parent
e63f4962fa
commit
5c7f2b7154
|
@ -14,14 +14,14 @@ jobs:
|
|||
matrix:
|
||||
os: [ubuntu-latest, macos-latest] # , windows-latest] # see https://github.com/tox-dev/tox/issues/1570
|
||||
tox:
|
||||
- env: py36
|
||||
python-version: '3.6'
|
||||
- env: py37
|
||||
python-version: '3.7'
|
||||
- env: py38
|
||||
python-version: '3.8'
|
||||
- env: py39
|
||||
python-version: '3.9'
|
||||
- env: py310
|
||||
python-version: '3.10'
|
||||
include:
|
||||
- tox:
|
||||
env: flake8,mypy,black,pylint
|
||||
|
|
135
pogrep.py
135
pogrep.py
|
@ -4,12 +4,11 @@
|
|||
__version__ = "0.1.2"
|
||||
|
||||
import argparse
|
||||
import curses
|
||||
import glob
|
||||
import os
|
||||
import sys
|
||||
from textwrap import fill
|
||||
from typing import Sequence, NamedTuple, List, Tuple
|
||||
from typing import Sequence, NamedTuple, List, Tuple, Optional
|
||||
from shutil import get_terminal_size
|
||||
|
||||
import regex
|
||||
|
@ -17,25 +16,54 @@ import polib
|
|||
from tabulate import tabulate
|
||||
|
||||
|
||||
def get_colors():
|
||||
"""Just returns the CSI codes for red, green, magenta, and reset color."""
|
||||
class GrepColors:
|
||||
"""Hightlights various components of matched text"""
|
||||
|
||||
def __init__(self):
|
||||
self.colors = { # Default values from grep source code
|
||||
"mt": "01;31", # both ms/mc
|
||||
"ms": "01;31", # selected matched text - default: bold red
|
||||
"mc": "01;31", # context matched text - default: bold red
|
||||
"fn": "35", # filename - default: magenta
|
||||
"ln": "32", # line number - default: green
|
||||
"bn": "32", # byte(sic) offset - default: green
|
||||
"se": "36", # separator - default: cyan
|
||||
"sl": "", # selected lines - default: color pair
|
||||
"cx": "", # context lines - default: color pair
|
||||
# "rv": None, # -v reverses sl / cx
|
||||
# "ne": None, # no EL on SGR
|
||||
}
|
||||
|
||||
NO_COLOR = "\33[m\33[K"
|
||||
|
||||
def start(self, sgr_chain):
|
||||
"""Select graphic rendition to highlight the output"""
|
||||
return "\33[" + self.colors[sgr_chain] + "m\33[K"
|
||||
|
||||
def get_from_env_variables(self, grep_envvar):
|
||||
"""Get color values from GREP_COLOR and GREP_COLORS"""
|
||||
# old variable, less priority
|
||||
try:
|
||||
curses.setupterm()
|
||||
fg_color = curses.tigetstr("setaf") or curses.tigetstr("setf") or ""
|
||||
red = str(curses.tparm(fg_color, 1), "ascii")
|
||||
green = str(curses.tparm(fg_color, 2), "ascii")
|
||||
magenta = str(curses.tparm(fg_color, 5), "ascii")
|
||||
no_color = str(curses.tigetstr("sgr0"), "ascii")
|
||||
except curses.error:
|
||||
red, green, magenta = "", "", ""
|
||||
no_color = ""
|
||||
return red, green, magenta, no_color
|
||||
gc_from_env = os.environ["GREP_COLOR"]
|
||||
for k in ("mt", "ms", "mc"):
|
||||
self.colors[k] = gc_from_env
|
||||
except KeyError:
|
||||
pass
|
||||
# new variable, high priority
|
||||
last_value = ""
|
||||
try:
|
||||
for entry in reversed(grep_envvar.split(":")):
|
||||
key, value = entry.split("=")
|
||||
if value:
|
||||
self.colors[key] = value
|
||||
last_value = value
|
||||
else:
|
||||
self.colors[key] = last_value
|
||||
except (ValueError, KeyError):
|
||||
return
|
||||
|
||||
|
||||
RED, GREEN, MAGENTA, NO_COLOR = get_colors()
|
||||
|
||||
|
||||
def colorize(text, pattern, prefixes=()):
|
||||
def colorize(text, pattern, grep_colors, prefixes=()):
|
||||
"""Add CSI color codes to make pattern red in text.
|
||||
|
||||
Optionally also highlight prefixes (as (line, file) tuples) in
|
||||
|
@ -45,15 +73,28 @@ def colorize(text, pattern, prefixes=()):
|
|||
file.py:30:foo bar baz, with the following colors:
|
||||
| M ||G| |R|
|
||||
"""
|
||||
result = regex.sub(pattern, RED + r"\g<0>" + NO_COLOR, text)
|
||||
result = regex.sub(
|
||||
pattern, grep_colors.start("ms") + r"\g<0>" + grep_colors.NO_COLOR, text
|
||||
)
|
||||
for pnum, pfile in prefixes:
|
||||
prefix = " " + pfile + pnum
|
||||
prefix_colored = regex.escape(
|
||||
regex.sub(pattern, RED + r"\g<0>" + NO_COLOR, prefix)
|
||||
regex.sub(
|
||||
pattern,
|
||||
grep_colors.start("ms") + r"\g<0>" + grep_colors.NO_COLOR,
|
||||
prefix,
|
||||
)
|
||||
if regex.escape(RED) in prefix_colored:
|
||||
)
|
||||
if regex.escape(grep_colors.start("ms")) in prefix_colored:
|
||||
prefix = prefix_colored
|
||||
prefix_replace = " " + MAGENTA + pfile + GREEN + pnum + NO_COLOR
|
||||
prefix_replace = (
|
||||
" "
|
||||
+ grep_colors.start("fn")
|
||||
+ pfile
|
||||
+ grep_colors.start("ln")
|
||||
+ pnum
|
||||
+ grep_colors.NO_COLOR
|
||||
)
|
||||
result = regex.sub(prefix, prefix_replace, result, count=1)
|
||||
return result
|
||||
|
||||
|
@ -62,7 +103,7 @@ class Match(NamedTuple):
|
|||
"""Represents a string found in a po file."""
|
||||
|
||||
file: str
|
||||
line: int
|
||||
line: Optional[int] # types-polib states that linenum may be None
|
||||
msgid: str
|
||||
msgstr: str
|
||||
|
||||
|
@ -80,7 +121,7 @@ def find_in_po(
|
|||
try:
|
||||
pofile = polib.pofile(filename)
|
||||
except OSError:
|
||||
errors.append("{} doesn't seem to be a .po file".format(filename))
|
||||
errors.append(f"{filename} doesn't seem to be a .po file")
|
||||
continue
|
||||
for entry in pofile:
|
||||
if entry.msgstr and (
|
||||
|
@ -98,12 +139,16 @@ def display_results(
|
|||
pattern: str,
|
||||
line_number: bool,
|
||||
files_with_matches: bool,
|
||||
grep_colors: GrepColors,
|
||||
):
|
||||
"""Display matches as a colorfull table."""
|
||||
files = {match.file for match in matches}
|
||||
if files_with_matches: # Just print filenames
|
||||
for file in files:
|
||||
print(MAGENTA + file + NO_COLOR)
|
||||
if grep_colors:
|
||||
print(grep_colors.start("fn") + file + grep_colors.NO_COLOR)
|
||||
else:
|
||||
print(file)
|
||||
return
|
||||
prefixes = []
|
||||
table = []
|
||||
|
@ -124,7 +169,14 @@ def display_results(
|
|||
fill(match.msgstr, width=(term_width - 7) // 2),
|
||||
]
|
||||
)
|
||||
print(colorize(tabulate(table, tablefmt="fancy_grid"), pattern, prefixes))
|
||||
if grep_colors:
|
||||
print(
|
||||
colorize(
|
||||
tabulate(table, tablefmt="fancy_grid"), pattern, grep_colors, prefixes
|
||||
)
|
||||
)
|
||||
else:
|
||||
print(tabulate(table, tablefmt="fancy_grid"))
|
||||
|
||||
|
||||
def process_path(path: Sequence[str], recursive: bool) -> List[str]:
|
||||
|
@ -145,13 +197,11 @@ def process_path(path: Sequence[str], recursive: bool) -> List[str]:
|
|||
if recursive:
|
||||
files.extend(glob.glob(elt + os.sep + "**/*.po", recursive=True))
|
||||
else:
|
||||
print(
|
||||
"{}: {}: Is a directory".format(sys.argv[0], elt), file=sys.stderr
|
||||
)
|
||||
print(f"{sys.argv[0]}: {elt}: Is a directory", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(
|
||||
"{}: {}: No such file or directory".format(sys.argv[0], elt),
|
||||
f"{sys.argv[0]}: {elt}: No such file or directory",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
@ -229,6 +279,18 @@ def parse_args() -> argparse.Namespace:
|
|||
"matches GLOB. "
|
||||
"Ignore any redundant trailing slashes in GLOB.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--color",
|
||||
"--colour",
|
||||
choices=["never", "always", "auto"],
|
||||
default="auto",
|
||||
help="Surround the matched (non-empty) strings, matching lines, "
|
||||
"context lines, file names, line numbers, byte offsets, and separators "
|
||||
"(for fields and groups of context lines) with escape sequences to "
|
||||
"display them in color on the terminal. The colors are defined by the "
|
||||
"environment variable GREP_COLORS. The deprecated environment variable "
|
||||
"GREP_COLOR is still supported, but its setting does not have priority.",
|
||||
)
|
||||
parser.add_argument("pattern")
|
||||
parser.add_argument("path", nargs="*")
|
||||
return parser.parse_args()
|
||||
|
@ -237,6 +299,18 @@ def parse_args() -> argparse.Namespace:
|
|||
def main():
|
||||
"""Command line entry point."""
|
||||
args = parse_args()
|
||||
if args.color == "auto":
|
||||
args.color = sys.stdout.isatty()
|
||||
else:
|
||||
args.color = args.color != "never"
|
||||
if args.color:
|
||||
grep_colors = GrepColors()
|
||||
try:
|
||||
grep_colors.get_from_env_variables(grep_envvar=os.environ["GREP_COLORS"])
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
grep_colors = None
|
||||
if args.fixed_strings:
|
||||
args.pattern = regex.escape(args.pattern)
|
||||
if args.word_regexp:
|
||||
|
@ -255,6 +329,7 @@ def main():
|
|||
args.pattern,
|
||||
args.line_number,
|
||||
args.files_with_matches,
|
||||
grep_colors,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -1,12 +1,4 @@
|
|||
-r requirements.txt
|
||||
black
|
||||
flake8
|
||||
isort
|
||||
mypy
|
||||
pip-tools
|
||||
polib
|
||||
pylint
|
||||
pytest
|
||||
regex
|
||||
tabulate
|
||||
tox
|
||||
|
|
|
@ -4,109 +4,65 @@
|
|||
#
|
||||
# pip-compile requirements-dev.in
|
||||
#
|
||||
appdirs==1.4.4
|
||||
# via
|
||||
# black
|
||||
# virtualenv
|
||||
astroid==2.5.1
|
||||
# via pylint
|
||||
attrs==20.3.0
|
||||
attrs==22.1.0
|
||||
# via pytest
|
||||
black==20.8b1
|
||||
# via -r requirements-dev.in
|
||||
click==7.1.2
|
||||
# via
|
||||
# black
|
||||
# pip-tools
|
||||
distlib==0.3.1
|
||||
build==0.8.0
|
||||
# via pip-tools
|
||||
click==8.1.3
|
||||
# via pip-tools
|
||||
distlib==0.3.6
|
||||
# via virtualenv
|
||||
filelock==3.0.12
|
||||
filelock==3.8.0
|
||||
# via
|
||||
# tox
|
||||
# virtualenv
|
||||
flake8==3.8.4
|
||||
# via -r requirements-dev.in
|
||||
iniconfig==1.1.1
|
||||
# via pytest
|
||||
isort==5.7.0
|
||||
packaging==21.3
|
||||
# via
|
||||
# -r requirements-dev.in
|
||||
# pylint
|
||||
lazy-object-proxy==1.5.2
|
||||
# via astroid
|
||||
mccabe==0.6.1
|
||||
# via
|
||||
# flake8
|
||||
# pylint
|
||||
mypy-extensions==0.4.3
|
||||
# via
|
||||
# black
|
||||
# mypy
|
||||
mypy==0.812
|
||||
# build
|
||||
# pytest
|
||||
# tox
|
||||
pep517==0.13.0
|
||||
# via build
|
||||
pip-tools==6.8.0
|
||||
# via -r requirements-dev.in
|
||||
packaging==20.9
|
||||
platformdirs==2.5.2
|
||||
# via virtualenv
|
||||
pluggy==1.0.0
|
||||
# via
|
||||
# pytest
|
||||
# tox
|
||||
pathspec==0.8.1
|
||||
# via black
|
||||
pip-tools==5.5.0
|
||||
# via -r requirements-dev.in
|
||||
pluggy==0.13.1
|
||||
polib==1.1.1
|
||||
# via -r requirements.txt
|
||||
py==1.11.0
|
||||
# via
|
||||
# pytest
|
||||
# tox
|
||||
polib==1.1.0
|
||||
# via
|
||||
# -r requirements-dev.in
|
||||
# -r requirements.txt
|
||||
py==1.10.0
|
||||
# via
|
||||
# pytest
|
||||
# tox
|
||||
pycodestyle==2.6.0
|
||||
# via flake8
|
||||
pyflakes==2.2.0
|
||||
# via flake8
|
||||
pylint==2.7.2
|
||||
# via -r requirements-dev.in
|
||||
pyparsing==2.4.7
|
||||
pyparsing==3.0.9
|
||||
# via packaging
|
||||
pytest==6.2.2
|
||||
pytest==7.1.2
|
||||
# via -r requirements-dev.in
|
||||
regex==2020.11.13
|
||||
# via
|
||||
# -r requirements-dev.in
|
||||
# -r requirements.txt
|
||||
# black
|
||||
six==1.15.0
|
||||
# via
|
||||
# tox
|
||||
# virtualenv
|
||||
tabulate==0.8.9
|
||||
# via
|
||||
# -r requirements-dev.in
|
||||
# -r requirements.txt
|
||||
toml==0.10.2
|
||||
# via
|
||||
# black
|
||||
# pylint
|
||||
# pytest
|
||||
# tox
|
||||
tox==3.22.0
|
||||
# via -r requirements-dev.in
|
||||
typed-ast==1.4.2
|
||||
# via
|
||||
# black
|
||||
# mypy
|
||||
typing-extensions==3.7.4.3
|
||||
# via
|
||||
# black
|
||||
# mypy
|
||||
virtualenv==20.4.2
|
||||
# via -r requirements.txt
|
||||
six==1.16.0
|
||||
# via tox
|
||||
wrapt==1.12.1
|
||||
# via astroid
|
||||
tabulate==0.8.9
|
||||
# via -r requirements.txt
|
||||
toml==0.10.2
|
||||
# via tox
|
||||
tomli==2.0.1
|
||||
# via
|
||||
# build
|
||||
# pep517
|
||||
# pytest
|
||||
tox==3.25.1
|
||||
# via -r requirements-dev.in
|
||||
virtualenv==20.16.3
|
||||
# via tox
|
||||
wheel==0.37.1
|
||||
# via pip-tools
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
# pip
|
||||
# setuptools
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#
|
||||
# pip-compile setup.py
|
||||
#
|
||||
polib==1.1.0
|
||||
polib==1.1.1
|
||||
# via pogrep (setup.py)
|
||||
regex==2020.11.13
|
||||
# via pogrep (setup.py)
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import pytest
|
||||
from test.support import change_cwd
|
||||
from pogrep import RED, GREEN, MAGENTA, colorize, NO_COLOR, process_path, find_in_po
|
||||
|
||||
try:
|
||||
from test.support.os_helper import change_cwd
|
||||
except ImportError:
|
||||
from test.support import change_cwd
|
||||
from pogrep import GrepColors, colorize, process_path, find_in_po
|
||||
|
||||
POText = """
|
||||
msgid ""
|
||||
|
@ -79,24 +83,36 @@ culpa qui officia deserunt mollit anim id est laborum."""
|
|||
TEST_PREFIXES = [["25:", "glossary.po:"], ["42:", "consectetur:"], ["42:", ""]]
|
||||
|
||||
|
||||
@pytest.mark.skipif(RED == "", reason="No curses support in this test env.")
|
||||
def test_pattern():
|
||||
assert RED + "fugiat" + NO_COLOR in colorize(
|
||||
text=TEST_TEXT, pattern="fugiat", prefixes=[]
|
||||
grep_colors = GrepColors()
|
||||
grep_colors.get_from_env_variables("ms=99")
|
||||
assert "\33[99m\33[K" + "fugiat" + grep_colors.NO_COLOR in colorize(
|
||||
text=TEST_TEXT, pattern="fugiat", grep_colors=grep_colors, prefixes=[]
|
||||
)
|
||||
assert "\33[99m\33[K" not in colorize(
|
||||
text=TEST_TEXT, pattern="hello", grep_colors=grep_colors, prefixes=[]
|
||||
)
|
||||
assert RED not in colorize(text=TEST_TEXT, pattern="hello", prefixes=[])
|
||||
|
||||
|
||||
@pytest.mark.skipif(RED == "", reason="No curses support in this test env.")
|
||||
def test_prefixes():
|
||||
text = " 42:" + TEST_TEXT
|
||||
assert GREEN + "42:" + NO_COLOR in colorize(
|
||||
text=text, pattern="fugiat", prefixes=TEST_PREFIXES
|
||||
grep_colors = GrepColors()
|
||||
grep_colors.get_from_env_variables("ln=99:fn=88:ms=77")
|
||||
assert "\33[99m\33[K" + "42:" + grep_colors.NO_COLOR in colorize(
|
||||
text=text, pattern="fugiat", grep_colors=grep_colors, prefixes=TEST_PREFIXES
|
||||
)
|
||||
text = " consectetur:" + text[1:]
|
||||
result = colorize(text=text, pattern="consectetur", prefixes=TEST_PREFIXES)
|
||||
assert MAGENTA + "consectetur:" + GREEN + "42:" + NO_COLOR in result
|
||||
assert RED + "consectetur" + NO_COLOR in result
|
||||
result = colorize(
|
||||
text=text,
|
||||
pattern="consectetur",
|
||||
grep_colors=grep_colors,
|
||||
prefixes=TEST_PREFIXES,
|
||||
)
|
||||
assert (
|
||||
"\33[88m\33[K" + "consectetur:" + "\33[99m\33[K" + "42:" + grep_colors.NO_COLOR
|
||||
in result
|
||||
)
|
||||
assert "\33[77m\33[K" + "consectetur" + grep_colors.NO_COLOR in result
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -141,3 +157,11 @@ def test_empty_and_recursive(few_files):
|
|||
"library/lib1.po",
|
||||
"venv/file1.po",
|
||||
}
|
||||
|
||||
|
||||
def test_read_grep_colors_envvar():
|
||||
grep_colors = GrepColors()
|
||||
grep_colors.get_from_env_variables("ms=:ln=99:fn=88")
|
||||
assert grep_colors.start("fn") == "\33[88m\33[K"
|
||||
assert grep_colors.start("ln") == "\33[99m\33[K"
|
||||
assert grep_colors.start("ms") == "\33[99m\33[K"
|
||||
|
|
8
tox.ini
8
tox.ini
|
@ -2,7 +2,7 @@
|
|||
max-line-length = 88
|
||||
|
||||
[tox]
|
||||
envlist = py36, py37, py38, py39, flake8, mypy, black, pylint
|
||||
envlist = py37, py38, py39, py310, flake8, mypy, black, pylint
|
||||
isolated_build = True
|
||||
skip_missing_interpreters = True
|
||||
|
||||
|
@ -19,8 +19,12 @@ deps = black
|
|||
commands = black --check --diff tests/ pogrep.py
|
||||
|
||||
[testenv:mypy]
|
||||
deps = mypy
|
||||
deps =
|
||||
mypy
|
||||
types-polib
|
||||
types-tabulate
|
||||
commands = mypy --ignore-missing-imports pogrep.py
|
||||
|
||||
[testenv:pylint]
|
||||
deps = pylint
|
||||
commands = pylint --disable format pogrep.py
|
||||
|
|
Loading…
Reference in New Issue