Skip to content

Commit e7d8b0e

Browse files
authored
Ignore files listed in .gitignore, --exclude and --extend-exclude (#173)
* update test readme * ignore files listed in gitignore, exclude and extend-exclude * install depends * always compile exclude * add ignored files
1 parent 2354abe commit e7d8b0e

File tree

17 files changed

+227
-134
lines changed

17 files changed

+227
-134
lines changed

.github/workflows/unit-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
- name: Install dependencies
2727
run: |
2828
python -m pip install --upgrade pip
29-
pip install pytest pylama pylama_pylint coverage click robotframework==4.0
29+
pip install pytest pylama pylama_pylint coverage click robotframework==4.0 pathspec>=0.8.0
3030
# - name: Code quality with pylama
3131
- name: Run unit tests with coverage
3232
run:

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Unreleased
44

55
### Features
6+
- Ignore paths from .gitignore and ``--exclude`` option, allow to ignore paths through using ``--extend-exclude`` [#110](https://github.com/MarketSquare/robotframework-tidy/issues/110)
67
- Add extra indent for arguments in Suite Setup/Teardown, Test Setup/Teardown in AlignSettingsSection [#155](https://github.com/MarketSquare/robotframework-tidy/issues/155)
78
- OrderSettingsSection will now preserve order of imports. It can be configured to work as before and other settings order can be also preserved [#167](https://github.com/MarketSquare/robotframework-tidy/issues/167)
89
- Allow to disable selected transformers [#170](https://github.com/MarketSquare/robotframework-tidy/issues/170)

docs/source/configuration/index.rst

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,37 +10,20 @@ Configuration
1010
config_file
1111
configuring_transformers
1212

13-
Behaviour of robotidy can be changed through global options or by configuring specific transformer.
13+
Behaviour of *robotidy* can be changed through global options or by configuring specific transformer.
1414
To read more about configuring transformer go to :ref:`configuring-transformers`. To see how to configure robotidy
1515
using configuration files see :ref:`config-file`.
1616

1717
.. rubric:: Command line options
1818

19-
- ``--transform`` / ``-t``
20-
Used to select what transformers will be run (overwrites default transformers list)
21-
- ``--configure`` / ``-c``
22-
Used to configure transformers. See more :ref:`configuring-transformers`
23-
- ``--list`` / ``-l``
24-
List available transformers.
25-
- ``--desc`` / ``-d`` TRANSFORMER_NAME
26-
Show documentation for selected transformer. Pass ``all`` as transformer name to show documentation for all transformers.
27-
- ``--output`` / ``-o``
28-
Path to output file where source will be saved (by default the source file is overwritten).
29-
- ``--config`` FILE
30-
Path to configuration file.
31-
- ``--overwrite/--no-overwrite`` flag
32-
Flag to determine if changes should be written back to file (default: --overwrite).
33-
- ``--diff`` flag
34-
If this flag is set Robotidy will output diff view of each processed file.
35-
- ``--check`` flag
36-
Don't overwrite files and just return status.
37-
- ``--spacecount`` / ``-s``
38-
The number of spaces between cells (used by some of the transformers, for example ``NormalizeSeparators``).
39-
- ``--startline`` / ``-sl and ``--endline`` / ``-el``
40-
Used by some of the transformers to narrow down part of the file that is transformed. Line numbers start from 1.
41-
- ``--verbose`` / ``-v``
42-
More verbose output.
43-
- ``--version``
44-
Print Robotidy version.
45-
- ``help``
46-
Prints robotidy help.
19+
To list *robotidy* command line options run::
20+
21+
robotidy --help
22+
23+
.. rubric:: Ignored paths
24+
25+
Robotidy reads and ignores paths from ``.gitignore`` and ``--exclude``. You can overwrite default excludes by using
26+
``--exclude`` option. If you want to exclude additional paths on top of those from ``--exclude`` and ``.gitignore`` use
27+
``--extend-exclude`` with pattern::
28+
29+
robotidy --extend-exclude skip_me.robot|some_dir/* .

docs/source/external_transformers.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
External transformers
44
-------------------------------
55
It is possible to develop your own transformers. You can use module name (if it is installed in your env) or path to
6-
file with class to run external transformers with robotidy::
6+
file with class to run external transformers with *robotidy*::
77

88
robotidy --transform MyTransformers.YourCustomTransformer src
99
robotidy --transform C:\transformers\YourCustomTransformer2.py src

robotidy/api.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
from typing import Optional
55

66
from robotidy.app import Robotidy
7-
from robotidy.cli import find_and_read_config, TransformType
7+
from robotidy.cli import find_and_read_config, TransformType, validate_regex
8+
from robotidy.files import DEFAULT_EXCLUDES
89
from robotidy.utils import GlobalFormattingConfig
910

1011

1112
class RobotidyAPI(Robotidy):
1213
def __init__(self, src: str, output: Optional[str], **kwargs):
13-
config = find_and_read_config([src])
14+
config = find_and_read_config((src,))
1415
config = {
1516
k: str(v) if not isinstance(v, (list, dict)) else v
1617
for k, v in config.items()
@@ -24,10 +25,16 @@ def __init__(self, src: str, output: Optional[str], **kwargs):
2425
start_line=kwargs.get('startline', None) or int(config['startline']) if 'startline' in config else None,
2526
end_line=kwargs.get('endline', None) or int(config['endline']) if 'endline' in config else None
2627
)
28+
exclude = config.get('exclude', None)
29+
extend_exclude = config.get('extend_exclude', None)
30+
exclude = validate_regex(exclude if exclude is not None else DEFAULT_EXCLUDES)
31+
extend_exclude = validate_regex(extend_exclude)
2732
super().__init__(
2833
transformers=transformers,
2934
transformers_config=configurations,
3035
src=(),
36+
exclude=exclude,
37+
extend_exclude=extend_exclude,
3138
overwrite=False,
3239
show_diff=False,
3340
formatting_config=formatting_config,

robotidy/app.py

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,29 @@
11
from collections import defaultdict
22
from difflib import unified_diff
33
from pathlib import Path
4-
from typing import List, Tuple, Dict, Iterator, Iterable, Optional
4+
from typing import List, Tuple, Dict, Iterator, Iterable, Optional, Pattern
55

66
import click
77
from robot.api import get_model
88
from robot.errors import DataError
99

1010
from robotidy.transformers import load_transformers
11+
from robotidy.files import get_paths
1112
from robotidy.utils import (
1213
StatementLinesCollector,
1314
decorate_diff_with_color,
1415
GlobalFormattingConfig,
1516
ModelWriter
1617
)
1718

18-
INCLUDE_EXT = ('.robot', '.resource')
19-
2019

2120
class Robotidy:
2221
def __init__(self,
2322
transformers: List[Tuple[str, List]],
2423
transformers_config: List[Tuple[str, List]],
2524
src: Tuple[str, ...],
25+
exclude: Pattern,
26+
extend_exclude: Pattern,
2627
overwrite: bool,
2728
show_diff: bool,
2829
formatting_config: GlobalFormattingConfig,
@@ -31,7 +32,7 @@ def __init__(self,
3132
output: Optional[Path],
3233
force_order: bool
3334
):
34-
self.sources = self.get_paths(src)
35+
self.sources = get_paths(src, exclude, extend_exclude)
3536
self.overwrite = overwrite
3637
self.show_diff = show_diff
3738
self.check = check
@@ -89,28 +90,6 @@ def output_diff(self, path: str, old_model: StatementLinesCollector, new_model:
8990
colorized_output = decorate_diff_with_color(lines)
9091
click.echo(colorized_output.encode('ascii', 'ignore').decode('ascii'), color=True)
9192

92-
def get_paths(self, src: Tuple[str, ...]):
93-
sources = set()
94-
for s in src:
95-
path = Path(s).resolve()
96-
if path.is_file():
97-
sources.add(path)
98-
elif path.is_dir():
99-
sources.update(self.iterate_dir(path.iterdir()))
100-
elif s == '-':
101-
sources.add(path)
102-
103-
return sources
104-
105-
def iterate_dir(self, paths: Iterable[Path]) -> Iterator[Path]:
106-
for path in paths:
107-
if path.is_file():
108-
if path.suffix not in INCLUDE_EXT:
109-
continue
110-
yield path
111-
elif path.is_dir():
112-
yield from self.iterate_dir(path.iterdir())
113-
11493
@staticmethod
11594
def convert_configure(configure: List[Tuple[str, List]]) -> Dict[str, List]:
11695
config_map = defaultdict(list)

robotidy/cli.py

Lines changed: 47 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@
55
List,
66
Iterable,
77
Optional,
8-
Any
8+
Any,
9+
Pattern
910
)
1011

1112
import click
12-
import toml
13+
import re
1314

1415
from robotidy.app import Robotidy
1516
from robotidy.transformers import load_transformers
17+
from robotidy.files import read_pyproject_config, find_and_read_config, DEFAULT_EXCLUDES
1618
from robotidy.utils import (
1719
GlobalFormattingConfig,
1820
split_args_from_name_or_path,
@@ -81,70 +83,6 @@ def convert(self, value, param, ctx):
8183
return name, args
8284

8385

84-
def find_project_root(srcs: Iterable[str]) -> Path:
85-
"""Return a directory containing .git, or robotidy.toml.
86-
That directory will be a common parent of all files and directories
87-
passed in `srcs`.
88-
If no directory in the tree contains a marker that would specify it's the
89-
project root, the root of the file system is returned.
90-
"""
91-
if not srcs:
92-
return Path("/").resolve()
93-
94-
path_srcs = [Path(Path.cwd(), src).resolve() for src in srcs]
95-
96-
# A list of lists of parents for each 'src'. 'src' is included as a
97-
# "parent" of itself if it is a directory
98-
src_parents = [
99-
list(path.parents) + ([path] if path.is_dir() else []) for path in path_srcs
100-
]
101-
102-
common_base = max(
103-
set.intersection(*(set(parents) for parents in src_parents)),
104-
key=lambda path: path.parts,
105-
)
106-
107-
for directory in (common_base, *common_base.parents):
108-
if (directory / ".git").exists():
109-
return directory
110-
111-
if (directory / "robotidy.toml").is_file():
112-
return directory
113-
114-
if (directory / "pyproject.toml").is_file():
115-
return directory
116-
117-
return directory
118-
119-
120-
def find_and_read_config(src_paths: Iterable[str]) -> Dict[str, Any]:
121-
project_root = find_project_root(src_paths)
122-
config_path = project_root / 'robotidy.toml'
123-
if config_path.is_file():
124-
return read_pyproject_config(str(config_path))
125-
pyproject_path = project_root / 'pyproject.toml'
126-
if pyproject_path.is_file():
127-
return read_pyproject_config(str(pyproject_path))
128-
return {}
129-
130-
131-
def load_toml_file(path: str) -> Dict[str, Any]:
132-
try:
133-
config = toml.load(path)
134-
click.echo(f"Loaded configuration from {path}")
135-
return config
136-
except (toml.TomlDecodeError, OSError) as e:
137-
raise click.FileError(
138-
filename=path, hint=f"Error reading configuration file: {e}"
139-
)
140-
141-
142-
def read_pyproject_config(path: str) -> Dict[str, Any]:
143-
config = load_toml_file(path)
144-
config = config.get("tool", {}).get("robotidy", {})
145-
return {k.replace('--', '').replace('-', '_'): v for k, v in config.items()}
146-
147-
14886
def parse_opt(opt):
14987
while opt and opt[0] == '-':
15088
opt = opt[1:]
@@ -185,6 +123,21 @@ def read_config(ctx: click.Context, param: click.Parameter, value: Optional[str]
185123
ctx.default_map = default_map
186124

187125

126+
def validate_regex_callback(
127+
ctx: click.Context,
128+
param: click.Parameter,
129+
value: Optional[str],
130+
) -> Optional[Pattern]:
131+
return validate_regex(value)
132+
133+
134+
def validate_regex(value: Optional[str]) -> Optional[Pattern]:
135+
try:
136+
return re.compile(value) if value is not None else None
137+
except re.error:
138+
raise click.BadParameter("Not a valid regular expression")
139+
140+
188141
def print_description(name: str):
189142
transformers = load_transformers(None, {}, allow_disabled=True)
190143
transformer_by_names = {transformer.__class__.__name__: transformer for transformer in transformers}
@@ -242,6 +195,27 @@ def print_transformers_list():
242195
is_eager=True,
243196
metavar='[PATH(S)]'
244197
)
198+
@click.option(
199+
"--exclude",
200+
type=str,
201+
callback=validate_regex_callback,
202+
help=(
203+
"A regular expression that matches files and directories that should be"
204+
" excluded on recursive searches. An empty value means no paths are excluded."
205+
" Use forward slashes for directories on all platforms."
206+
f" [default: '{DEFAULT_EXCLUDES}']"
207+
),
208+
show_default=False,
209+
)
210+
@click.option(
211+
"--extend-exclude",
212+
type=str,
213+
callback=validate_regex_callback,
214+
help=(
215+
"Like --exclude, but adds additional files and directories on top of the"
216+
" excluded ones. (Useful if you simply want to add to the default)"
217+
),
218+
)
245219
@click.option(
246220
"--config",
247221
type=click.Path(
@@ -363,6 +337,8 @@ def cli(
363337
transform: List[Tuple[str, List]],
364338
configure: List[Tuple[str, List]],
365339
src: Tuple[str, ...],
340+
exclude: Optional[Pattern],
341+
extend_exclude: Optional[Pattern],
366342
overwrite: bool,
367343
diff: bool,
368344
check: bool,
@@ -395,6 +371,9 @@ def cli(
395371
print("No source path provided. Run robotidy --help to see how to use robotidy")
396372
ctx.exit(0)
397373

374+
if exclude is None:
375+
exclude = re.compile(DEFAULT_EXCLUDES)
376+
398377
if config and verbose:
399378
click.echo(f'Loaded {config} configuration file')
400379

@@ -408,6 +387,8 @@ def cli(
408387
transformers=transform,
409388
transformers_config=configure,
410389
src=src,
390+
exclude=exclude,
391+
extend_exclude=extend_exclude,
411392
overwrite=overwrite,
412393
show_diff=diff,
413394
formatting_config=formatting_config,

0 commit comments

Comments
 (0)