Skip to content

Commit

Permalink
config: make extensions configurable
Browse files Browse the repository at this point in the history
Also adds .upf as a default.
  • Loading branch information
nmoroze committed Apr 24, 2024
1 parent 7a4620e commit fce872a
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 13 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ example.tcl:3:5: expected 1 space between words, got 3 [spacing]

## Usage

`tclint` is a command-line utility. It takes a list of paths as positional arguments, which may either be direct paths to source files, or directories which will be recursively searched for files ending in `.tcl`, `.sdc`, or `.xdc`.
`tclint` is a command-line utility. It takes a list of paths as positional arguments, which may either be direct paths to source files, or directories which will be recursively searched for files ending in `.tcl`, `.sdc`, `.xdc`, or `.upf`.

Collected files will be checked for lint violations. See the
[Violations](docs/violations.md) documentation page for a description of all
Expand Down
6 changes: 5 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ ignore = [
"spacing",
{ path = "files_with_bad_indent/", rules = ["indent"] }
]
# extensions of files to lint when searching directories. defaults to tcl, sdc,
# xdc, and upf.
extensions = ["tcl"]

[style]
# number of spaces to indent. can also be set to "tab". defaults to 4.
Expand All @@ -34,7 +37,7 @@ spaces-in-braces = true

## Filesets

The configuration file can define an arbitrary number of sub-configurations that apply to a specific set of paths. These sub-configs support the same set of fields as the global configuration, with the exception of `exclude`.
The configuration file can define an arbitrary number of sub-configurations that apply to a specific set of paths. These sub-configs support the same set of fields as the global configuration, with the exception of `exclude` and `extensions`.

The following example shows how to add two fileset sub-configs that each override different configuration fields:

Expand Down Expand Up @@ -65,6 +68,7 @@ configuration arguments:
--extend-ignore "rule1, rule2, ..."
--exclude "pattern1, pattern2, ..."
--extend-exclude "pattern1, pattern2, ..."
--extensions "tcl, xdc, ..."
--style-indent <indent>
--style-line-length <line_length>
--style-max-blank-lines <max_blank_lines>
Expand Down
14 changes: 13 additions & 1 deletion src/tclint/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ class Config:
exclude: List[Any] = dataclasses.field(default_factory=list)
ignore: List[Any] = dataclasses.field(default_factory=list)
command_plugins: List[str] = dataclasses.field(default_factory=list)
extensions: List[str] = dataclasses.field(
default_factory=lambda: ["tcl", "sdc", "xdc", "upf"]
)
style_indent: Union[str, int] = dataclasses.field(default=4)
style_line_length: int = dataclasses.field(default=80)
style_allow_aligned_sets: bool = dataclasses.field(default=False)
Expand Down Expand Up @@ -93,6 +96,7 @@ def _str2list(s):
],
),
"command_plugins": Use(validate_command_plugins),
"extensions": Use(_str2list),
"style_indent": Or(
lambda v: v == "tab", Use(int), error="indent must be integer or 'tab'"
),
Expand Down Expand Up @@ -133,8 +137,9 @@ def _validate_config(config):
}

schema = Schema({
# exclude is special - only has meaning in global context
# exclude and extensions can only be used in global context
Optional("exclude"): _VALIDATORS["exclude"],
Optional("extensions"): _VALIDATORS["extensions"],
**base_config,
Optional("fileset"): Schema([{"paths": [Use(pathlib.Path)], **base_config}]),
})
Expand Down Expand Up @@ -184,6 +189,9 @@ def add_bool(dest, yes_flag, no_flag):
config_group.add_argument(
"--extend-exclude", type=validator("exclude"), metavar='"path1, path2, ..."'
)
config_group.add_argument(
"--extensions", type=validator("extensions"), metavar='"tcl, xdc, ..."'
)
config_group.add_argument(
"--style-indent", type=validator("style_indent"), metavar="<indent>"
)
Expand Down Expand Up @@ -252,6 +260,10 @@ def __init__(self, global_config=None, fileset_configs=None):
def exclude(self):
return self._global_config.exclude

@property
def extensions(self):
return self._global_config.extensions

@classmethod
def from_dict(cls, config_dict: dict):
config_dict = _validate_config(config_dict)
Expand Down
19 changes: 11 additions & 8 deletions src/tclint/tclint.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,22 @@


def resolve_sources(
paths: List[pathlib.Path], exclude_patterns: List[str], exclude_root: pathlib.Path
paths: List[pathlib.Path],
exclude_patterns: List[str],
exclude_root: pathlib.Path,
extensions: List[str],
) -> List[Optional[pathlib.Path]]:
"""Resolves paths passed via CLI to a list of filepaths to lint.
`paths` is a list of paths that may be files or directories. Files are
returned verbatim if they exist, and directories are recursively searched
for files that have the extension .tcl, .xdc, or .sdc. Paths that match a
for files that have an extension specified in `extensions`. Paths that match a
pattern in `exclude_patterns` are ignored (based on gitignore pattern
format, see https://git-scm.com/docs/gitignore#_pattern_format).
Raises FileNotFoundError if a supplied path does not exist.
"""
# Extensions that may indicate tcl files
# TODO: make configurable
EXTENSIONS = [".tcl", ".xdc", ".sdc"]

extensions = [f".{ext}" if not ext.startswith(".") else ext for ext in extensions]
exclude_root = exclude_root.resolve()
exclude_patterns = [
re.sub(r"^\s*#", r"\#", pattern) for pattern in exclude_patterns
Expand Down Expand Up @@ -87,7 +87,7 @@ def is_excluded(path):
for dirpath, _, filenames in os.walk(path):
for name in filenames:
_, ext = os.path.splitext(name)
if ext in EXTENSIONS:
if ext.lower() in extensions:
child = pathlib.Path(dirpath) / name
if not is_excluded(child):
sources.append(child)
Expand Down Expand Up @@ -205,7 +205,10 @@ def main():
# the config file, unless -c is used (eslint rules)
exclude_root = pathlib.Path.cwd()
sources = resolve_sources(
args.source, exclude_patterns=config.exclude, exclude_root=exclude_root
args.source,
exclude_patterns=config.exclude,
exclude_root=exclude_root,
extensions=config.extensions,
)
except FileNotFoundError as e:
print(f"Invalid path provided: {e}")
Expand Down
3 changes: 3 additions & 0 deletions tests/data/tclint.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ ignore = [
"spacing",
{ path = "files_with_bad_indent/", rules = ["indent"] }
]
# extensions of files to lint when searching directories. defaults to tcl, sdc,
# xdc, and upf.
extensions = ["tcl"]

[style]
# number of spaces to indent. can also be set to "tab". defaults to 4.
Expand Down
1 change: 1 addition & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def test_example_config():
Rule("spacing"),
{"path": pathlib.Path("files_with_bad_indent/"), "rules": [Rule("indent")]},
]
assert global_.extensions == ["tcl"]

assert global_.style_indent == 2
assert global_.style_line_length == 100
Expand Down
38 changes: 36 additions & 2 deletions tests/test_tclint.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ def test_resolve_sources(tmp_path_factory):
cwd = os.getcwd()
os.chdir(tmp_path)

extensions = ["tcl"]

sources = tclint.resolve_sources(
[pathlib.Path(".")],
exclude_patterns=[
Expand All @@ -140,6 +142,7 @@ def test_resolve_sources(tmp_path_factory):
"/foo.tcl",
],
exclude_root=tmp_path,
extensions=extensions,
)

assert len(sources) == 1
Expand All @@ -151,7 +154,10 @@ def test_resolve_sources(tmp_path_factory):
in_path = other_dir / "foo.tcl"
in_path.touch()
sources = tclint.resolve_sources(
[in_path], exclude_patterns=[str(in_path)], exclude_root=tmp_path
[in_path],
exclude_patterns=[str(in_path)],
exclude_root=tmp_path,
extensions=extensions,
)
assert len(sources) == 1
assert sources[0] == in_path
Expand All @@ -160,7 +166,10 @@ def test_resolve_sources(tmp_path_factory):
top_src = tmp_path / "top.tcl"
top_src.touch()
sources = tclint.resolve_sources(
[top_src], exclude_patterns=["../top.tcl"], exclude_root=tmp_path / "src"
[top_src],
exclude_patterns=["../top.tcl"],
exclude_root=tmp_path / "src",
extensions=extensions,
)
assert len(sources) == 0

Expand All @@ -174,7 +183,32 @@ def test_resolve_sources(tmp_path_factory):
# extra space before #bar.tcl is important to make sure we don't just match ^#
exclude_patterns=["#foo.tcl", " #bar.tcl"],
exclude_root=other_other_dir,
extensions=extensions,
)
assert len(sources) == 0

os.chdir(cwd)


def test_resolve_sources_extensions(tmp_path):
foo_file = tmp_path / "file.foo"
foo_file.touch()
bar_file = tmp_path / "file.BAR"
bar_file.touch()

cwd = os.getcwd()
os.chdir(tmp_path)

sources = tclint.resolve_sources(
[tmp_path], exclude_patterns=[], exclude_root=tmp_path, extensions=["foo"]
)
assert len(sources) == 1
assert sources[0] == foo_file

sources = tclint.resolve_sources(
[tmp_path], exclude_patterns=[], exclude_root=tmp_path, extensions=["bar"]
)
assert len(sources) == 1
assert sources[0] == bar_file

os.chdir(cwd)

0 comments on commit fce872a

Please sign in to comment.