Source code for snakemake.linting
import textwrap
import shutil
import inspect
from abc import ABC, abstractmethod
from snakemake.logging import logger
# (?!\\+) is a negative lookahead, which removes trailing
# '+'s from the match. There is a minor risk, that a user
# intentionally uses file name (parts) with a trailing '+'
# intentionally. The regex extension _should_ allow simple
# regexes as '\s+' in place of a tab separator to pass.
NAME_PATTERN = "[a-zA-Z_][a-zA-Z_0-9]*(?!\\+)"
[docs]
class Linter(ABC):
def __init__(self, workflow, items):
self.items = items
self.workflow = workflow
[docs]
def read_item(self, item):
return item
[docs]
def lint(self, json=False):
json_lints = [] if json else None
linted = False
for item in self.items:
item_lints = [
lint
for lint_item in self.lints()
for lint in lint_item(self.read_item(item))
]
if not item_lints:
continue
linted = True
if json:
json_lints.append(
{
"for": self.item_desc_json(item),
"lints": [lint.__dict__ for lint in item_lints],
}
)
else:
logger.warning(
"Lints for {}:\n{}\n".format(
self.item_desc_plain(item),
"\n".join(map(" * {}".format, item_lints)),
)
)
return json_lints, linted
[docs]
@abstractmethod
def item_desc_json(self, item):
pass
[docs]
@abstractmethod
def item_desc_plain(self, item):
pass
[docs]
def lints(self):
return (
method
for name, method in inspect.getmembers(self)
if name.startswith("lint_")
)
[docs]
class Lint:
def __init__(self, title, body, links=None):
self.title = title
self.body = body
self.links = links or []
def __str__(self) -> str:
width, _ = shutil.get_terminal_size()
output = "{}:\n{}".format(
self.title,
"\n".join(
map(" {}".format, textwrap.wrap(self.body, max(width - 6, 20)))
),
)
if self.links:
output += "\n Also see:\n{}".format(
"\n".join(map(" {}".format, self.links))
)
return output