offgrep 0.2.0__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {offgrep-0.2.0 → offgrep-0.3.1}/PKG-INFO +2 -3
- {offgrep-0.2.0 → offgrep-0.3.1}/README.md +1 -2
- {offgrep-0.2.0 → offgrep-0.3.1}/pyproject.toml +1 -1
- offgrep-0.3.1/src/offgrep/__main__.py +49 -0
- offgrep-0.3.1/src/offgrep/arguments.py +426 -0
- {offgrep-0.2.0 → offgrep-0.3.1}/src/offgrep/commons.py +5 -0
- offgrep-0.3.1/src/offgrep/filesystem.py +434 -0
- offgrep-0.3.1/src/offgrep/output.py +185 -0
- {offgrep-0.2.0 → offgrep-0.3.1}/src/offgrep/path_matching.py +12 -4
- offgrep-0.3.1/src/offgrep/pattern_matching.py +326 -0
- offgrep-0.2.0/src/offgrep/__main__.py +0 -266
- offgrep-0.2.0/src/offgrep/filesystem.py +0 -343
- {offgrep-0.2.0 → offgrep-0.3.1}/LICENSE +0 -0
- {offgrep-0.2.0 → offgrep-0.3.1}/src/offgrep/__init__.py +0 -0
- {offgrep-0.2.0 → offgrep-0.3.1}/src/offgrep/git.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: offgrep
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: A lousy ripgrep rip-off in pure Python
|
|
5
5
|
Keywords: grep,recursive
|
|
6
6
|
Author: Rainer Schwarzbach
|
|
@@ -67,5 +67,4 @@ instead of **offgrep**.
|
|
|
67
67
|
|
|
68
68
|
If you – however – are in a super restricted network (eg. government)
|
|
69
69
|
without a chance to install **ripgrep**,
|
|
70
|
-
but are able to install Python packages, **offgrep** might be
|
|
71
|
-
alternative.
|
|
70
|
+
but are able to install Python packages, **offgrep** might be suitable for you.
|
|
@@ -18,5 +18,4 @@ instead of **offgrep**.
|
|
|
18
18
|
|
|
19
19
|
If you – however – are in a super restricted network (eg. government)
|
|
20
20
|
without a chance to install **ripgrep**,
|
|
21
|
-
but are able to install Python packages, **offgrep** might be
|
|
22
|
-
alternative.
|
|
21
|
+
but are able to install Python packages, **offgrep** might be suitable for you.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Call as script
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from .arguments import parse_arguments
|
|
10
|
+
from .filesystem import FileSelectionOptions, iter_selected_files
|
|
11
|
+
from .output import OutputOptions
|
|
12
|
+
from .pattern_matching import PatternOptions, find_in_files
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def grep_files(
|
|
16
|
+
file_selection_opts: FileSelectionOptions,
|
|
17
|
+
pattern_opts: PatternOptions,
|
|
18
|
+
output_opts: OutputOptions,
|
|
19
|
+
) -> int:
|
|
20
|
+
"""Search for text in files"""
|
|
21
|
+
target_paths = list(iter_selected_files(file_selection_opts))
|
|
22
|
+
return find_in_files(
|
|
23
|
+
target_paths, pattern_opts, output_opts, encoding=file_selection_opts.encoding
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def list_files(file_selection_opts: FileSelectionOptions) -> int:
|
|
28
|
+
"""List files that would be searched"""
|
|
29
|
+
for single_file in iter_selected_files(file_selection_opts):
|
|
30
|
+
print(single_file)
|
|
31
|
+
#
|
|
32
|
+
return 0
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def main() -> int:
|
|
36
|
+
"""Main script interface
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
the script returncode
|
|
40
|
+
"""
|
|
41
|
+
file_selection_opts, maybe_pattern_opts, output_opts = parse_arguments()
|
|
42
|
+
if isinstance(maybe_pattern_opts, PatternOptions):
|
|
43
|
+
return grep_files(file_selection_opts, maybe_pattern_opts, output_opts)
|
|
44
|
+
#
|
|
45
|
+
return list_files(file_selection_opts)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
sys.exit(main()) # pragma: no cover
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Command line argument parsing
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from argparse import (
|
|
8
|
+
ArgumentParser,
|
|
9
|
+
Namespace,
|
|
10
|
+
RawDescriptionHelpFormatter,
|
|
11
|
+
SUPPRESS,
|
|
12
|
+
)
|
|
13
|
+
from importlib.metadata import PackageNotFoundError, version as metadata_version
|
|
14
|
+
from logging import CRITICAL, DEBUG, INFO, WARNING, basicConfig, debug, warning
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from sys import stdin
|
|
17
|
+
from tomllib import loads as tomli_loads
|
|
18
|
+
|
|
19
|
+
from .commons import EMPTY, UTF_8, split_without_empty_strings
|
|
20
|
+
from .filesystem import (
|
|
21
|
+
FileSelectionOptions,
|
|
22
|
+
IgnoreFileMode,
|
|
23
|
+
Priority,
|
|
24
|
+
VCSDirectory,
|
|
25
|
+
is_an_eligible_path,
|
|
26
|
+
)
|
|
27
|
+
from .output import (
|
|
28
|
+
OutputOptions,
|
|
29
|
+
HIGHLIGHTERS,
|
|
30
|
+
SUPPORTED_COLORS,
|
|
31
|
+
print_highlighter_examples,
|
|
32
|
+
)
|
|
33
|
+
from .pattern_matching import PatternOptions
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
DEFAULT_PRE_IGNORE = ",".join(f"{dirname}/" for dirname in VCSDirectory)
|
|
37
|
+
DEFAULT_HIGHLIGHTER_KEY = "u"
|
|
38
|
+
|
|
39
|
+
PACKAGE_NAME = "offgrep"
|
|
40
|
+
"""The package name for metadata inspection"""
|
|
41
|
+
|
|
42
|
+
_EPILOG = """The provided options (or usage in a shell pipeline)
|
|
43
|
+
determine how the positional arguments are interpreted:
|
|
44
|
+
|
|
45
|
+
(1) offgrep [OPTIONS] PATTERN [PATH ...],
|
|
46
|
+
the first positional argument is interpreted as the pattern,
|
|
47
|
+
the remaining ones as paths.
|
|
48
|
+
(2) offgrep [OPTIONS] -e PATTERN ... [PATH ...]
|
|
49
|
+
all positional argument are interpreted as paths.
|
|
50
|
+
(3) offgrep [OPTIONS] -f PATTERNFILE ... [PATH ...]
|
|
51
|
+
all positional argument are interpreted as paths.
|
|
52
|
+
(4) offgrep [OPTIONS] --files [PATH ...]
|
|
53
|
+
all positional argument are interpreted as paths.
|
|
54
|
+
(5) offgrep [OPTIONS] --type-list
|
|
55
|
+
all positional arguments are ignored.
|
|
56
|
+
(6) command | offgrep [OPTIONS] PATTERN
|
|
57
|
+
the first positional argument is interpreted as the pattern
|
|
58
|
+
if no pattern has been provided through -e or -f,
|
|
59
|
+
the remaining positional arguments are ignored.
|
|
60
|
+
|
|
61
|
+
If no paths were provided as positional arguments in scenarios 1 through 4,
|
|
62
|
+
the current working directory will be used as the starting point.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def get_file_contents(file_name: str, up_dirs: int = 0) -> str:
|
|
67
|
+
"""get contents of a file in an ancestor directory
|
|
68
|
+
of the current file
|
|
69
|
+
|
|
70
|
+
...
|
|
71
|
+
"""
|
|
72
|
+
file_dir_path = Path(__file__).resolve()
|
|
73
|
+
for _ in range(up_dirs):
|
|
74
|
+
file_dir_path = file_dir_path.parent
|
|
75
|
+
#
|
|
76
|
+
file_path = file_dir_path / file_name
|
|
77
|
+
return file_path.read_text(encoding=UTF_8)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def get_metadata_version(
|
|
81
|
+
metadata_file_name: str = "pyproject.toml", up_dirs: int = 3
|
|
82
|
+
) -> str:
|
|
83
|
+
"""get version information from _metadata_file_name_ in an ancestor directory
|
|
84
|
+
|
|
85
|
+
...
|
|
86
|
+
"""
|
|
87
|
+
try:
|
|
88
|
+
metadata_file_contents = get_file_contents(metadata_file_name, up_dirs=up_dirs)
|
|
89
|
+
except IOError as io_error:
|
|
90
|
+
return str(io_error)
|
|
91
|
+
#
|
|
92
|
+
metadata = tomli_loads(metadata_file_contents)
|
|
93
|
+
try:
|
|
94
|
+
# logging.warning(str(metadata.items()))
|
|
95
|
+
version = metadata["project"]["version"]
|
|
96
|
+
except KeyError:
|
|
97
|
+
return f"Error: no version information in metadata from {metadata_file_name}"
|
|
98
|
+
#
|
|
99
|
+
return f"{version} (read directly from {metadata_file_name})"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def determine_ignore_file_mode() -> IgnoreFileMode:
|
|
103
|
+
"""dummy for now"""
|
|
104
|
+
return IgnoreFileMode.AUTOMATIC
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def determine_types(types_arg: list[str] | None) -> list[str]:
|
|
108
|
+
"""dummy for now"""
|
|
109
|
+
if isinstance(types_arg, list):
|
|
110
|
+
for single_type in types_arg:
|
|
111
|
+
warning("--type %s option not implemented yet", single_type)
|
|
112
|
+
#
|
|
113
|
+
#
|
|
114
|
+
return []
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def print_supported_file_tyoes() -> None:
|
|
118
|
+
"""Print the list of supported file types"""
|
|
119
|
+
warning("--type-list option not implemented yet")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _add_output_options(parser: ArgumentParser) -> None:
|
|
123
|
+
"""Add output options to a parser instance"""
|
|
124
|
+
output_group = parser.add_argument_group("Output options")
|
|
125
|
+
loglevel_mutex = output_group.add_mutually_exclusive_group()
|
|
126
|
+
loglevel_mutex.add_argument(
|
|
127
|
+
"--verbose",
|
|
128
|
+
action="store_true",
|
|
129
|
+
help="log with level INFO",
|
|
130
|
+
)
|
|
131
|
+
loglevel_mutex.add_argument(
|
|
132
|
+
"--debug",
|
|
133
|
+
action="store_true",
|
|
134
|
+
help="log with level DEBUG",
|
|
135
|
+
)
|
|
136
|
+
loglevel_mutex.add_argument(
|
|
137
|
+
"-q",
|
|
138
|
+
"--quiet",
|
|
139
|
+
action="store_true",
|
|
140
|
+
help="no output except for CRITICAL errors",
|
|
141
|
+
)
|
|
142
|
+
match_highlight_mutex = output_group.add_mutually_exclusive_group()
|
|
143
|
+
match_highlight_mutex.add_argument(
|
|
144
|
+
"--hi",
|
|
145
|
+
"--highlight-matches",
|
|
146
|
+
nargs="?",
|
|
147
|
+
choices=sorted(HIGHLIGHTERS),
|
|
148
|
+
const=DEFAULT_HIGHLIGHTER_KEY,
|
|
149
|
+
help="Highlight matches with the provided profile."
|
|
150
|
+
" Default ist %(default)r (no highlighting) if not specified,"
|
|
151
|
+
" or %(const)r if provided without a value."
|
|
152
|
+
" Use the --highlighters option for an overview with examples.",
|
|
153
|
+
)
|
|
154
|
+
for color in SUPPORTED_COLORS:
|
|
155
|
+
match_highlight_mutex.add_argument(
|
|
156
|
+
f"--{color}",
|
|
157
|
+
action="store_const",
|
|
158
|
+
dest="hi",
|
|
159
|
+
const=color[0],
|
|
160
|
+
help=f"Highlight matches in {color}.",
|
|
161
|
+
)
|
|
162
|
+
#
|
|
163
|
+
match_highlight_mutex.add_argument(
|
|
164
|
+
"--highlighters",
|
|
165
|
+
action="store_true",
|
|
166
|
+
help="Show highlighter effects on examples.",
|
|
167
|
+
)
|
|
168
|
+
output_group.add_argument(
|
|
169
|
+
"--stats",
|
|
170
|
+
action="store_true",
|
|
171
|
+
help="Collect and show statistics.",
|
|
172
|
+
)
|
|
173
|
+
output_group.add_argument(
|
|
174
|
+
"--files",
|
|
175
|
+
action="store_true",
|
|
176
|
+
help="Print the path of each file that would be searched,"
|
|
177
|
+
" without actually performing the search.",
|
|
178
|
+
)
|
|
179
|
+
output_group.add_argument(
|
|
180
|
+
"--type-list",
|
|
181
|
+
action="store_true",
|
|
182
|
+
help="NOT IMPLEMTED YET: print the list of supported file types.",
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _add_file_options(parser: ArgumentParser) -> None:
|
|
187
|
+
"""Add file options to a parser instance"""
|
|
188
|
+
file_group = parser.add_argument_group("File selection options")
|
|
189
|
+
file_group.add_argument(
|
|
190
|
+
"--default-ignore-dirs",
|
|
191
|
+
metavar="DIRECTORY-NAMES",
|
|
192
|
+
help="DEPRECATED, please use --pre-ignore or --post-ignore instead."
|
|
193
|
+
" Original purpose: always ignore the directories named here,"
|
|
194
|
+
" separated by commas.",
|
|
195
|
+
)
|
|
196
|
+
file_group.add_argument(
|
|
197
|
+
"--default-ignore-priority",
|
|
198
|
+
choices=list(Priority),
|
|
199
|
+
type=Priority,
|
|
200
|
+
help="DEPRECATED, please use --pre-ignore or --post-ignore instead."
|
|
201
|
+
" Original purpose: priority for the ignore rules specified through"
|
|
202
|
+
" --default-ignore-dirs."
|
|
203
|
+
f" Choices: %(choices)s ({str(Priority.LOW)!r} is applied first but might"
|
|
204
|
+
" be overridden by eg. directory defined rules,"
|
|
205
|
+
f" while {str(Priority.HIGH)!r} is applied last but cannot be overridden anymore)."
|
|
206
|
+
f" The preset is {str(Priority.LOW)!r}.",
|
|
207
|
+
)
|
|
208
|
+
file_group.add_argument(
|
|
209
|
+
"--pre-ignore",
|
|
210
|
+
metavar="PATTERNS",
|
|
211
|
+
help="gitignore-style patterns for files and/or directories ignored"
|
|
212
|
+
" before applying found ignore file rules, joined with commas (,)."
|
|
213
|
+
" Defaults to VCS repository directories (%(default)r).",
|
|
214
|
+
)
|
|
215
|
+
file_group.add_argument(
|
|
216
|
+
"--post-ignore",
|
|
217
|
+
metavar="PATTERNS",
|
|
218
|
+
help="gitignore-style patterns for files and/or directories ignored"
|
|
219
|
+
" after applying found ignore file rules, joined with commas (,)."
|
|
220
|
+
" Empty by default (%(default)r).",
|
|
221
|
+
)
|
|
222
|
+
file_group.add_argument(
|
|
223
|
+
"-t",
|
|
224
|
+
"--type",
|
|
225
|
+
nargs="+",
|
|
226
|
+
metavar="FILETYPE",
|
|
227
|
+
help="NOT IMPLEMTED YET: search only in files of type %(metavar)s.",
|
|
228
|
+
# See --type-list output for supported file types.",
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _add_pattern_options(parser: ArgumentParser) -> None:
|
|
233
|
+
"""Add pattern options to a parser instance"""
|
|
234
|
+
pattern_group = parser.add_argument_group("Pattern options")
|
|
235
|
+
pattern_group.add_argument(
|
|
236
|
+
"-e",
|
|
237
|
+
"--expression",
|
|
238
|
+
nargs="+",
|
|
239
|
+
dest="patterns",
|
|
240
|
+
metavar="PATTERN",
|
|
241
|
+
help="Patterns (by default, reular exoressions) to search for."
|
|
242
|
+
" Use this option with multiple arguments if you want to seatch for"
|
|
243
|
+
" multpile patterns at once.",
|
|
244
|
+
)
|
|
245
|
+
pattern_group.add_argument(
|
|
246
|
+
"-f",
|
|
247
|
+
"--file",
|
|
248
|
+
nargs="+",
|
|
249
|
+
type=Path,
|
|
250
|
+
dest="patternfiles",
|
|
251
|
+
metavar="PATTERNFILE",
|
|
252
|
+
help="Files containing patterns (by default, regular expressions",
|
|
253
|
+
)
|
|
254
|
+
pattern_group.add_argument(
|
|
255
|
+
"-F",
|
|
256
|
+
"--fixed-strings",
|
|
257
|
+
action="store_true",
|
|
258
|
+
help="treat the given pattern(s) as fixed strings instead of regular"
|
|
259
|
+
" expressions so special characters – .+*()[]{} – do not need to be escaped,"
|
|
260
|
+
" also saving some energy.",
|
|
261
|
+
)
|
|
262
|
+
pattern_group.add_argument(
|
|
263
|
+
"-i",
|
|
264
|
+
"--ignore-case",
|
|
265
|
+
action="store_false",
|
|
266
|
+
dest="case_sensitive",
|
|
267
|
+
help="case-insensitive matching",
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def build_argument_parser() -> ArgumentParser:
|
|
272
|
+
"""build the commandline argument parser"""
|
|
273
|
+
try:
|
|
274
|
+
version = metadata_version(PACKAGE_NAME)
|
|
275
|
+
except PackageNotFoundError:
|
|
276
|
+
version = get_metadata_version()
|
|
277
|
+
#
|
|
278
|
+
main_parser = ArgumentParser(
|
|
279
|
+
prog="offgrep",
|
|
280
|
+
description="searches text in (text) files recursively",
|
|
281
|
+
epilog=_EPILOG,
|
|
282
|
+
formatter_class=RawDescriptionHelpFormatter,
|
|
283
|
+
)
|
|
284
|
+
main_parser.set_defaults(
|
|
285
|
+
encoding=UTF_8,
|
|
286
|
+
hi="z",
|
|
287
|
+
pre_ignore=DEFAULT_PRE_IGNORE,
|
|
288
|
+
post_ignore=EMPTY,
|
|
289
|
+
)
|
|
290
|
+
main_parser.add_argument("-V", "--version", action="version", version=version)
|
|
291
|
+
main_parser.add_argument(
|
|
292
|
+
"--encoding",
|
|
293
|
+
help="Encoding of all files (default: %(default)s)",
|
|
294
|
+
)
|
|
295
|
+
_add_output_options(main_parser)
|
|
296
|
+
_add_file_options(main_parser)
|
|
297
|
+
_add_pattern_options(main_parser)
|
|
298
|
+
main_parser.add_argument(
|
|
299
|
+
"positional",
|
|
300
|
+
nargs="*",
|
|
301
|
+
help=SUPPRESS,
|
|
302
|
+
)
|
|
303
|
+
return main_parser
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def make_file_selection_options(args: Namespace) -> FileSelectionOptions:
|
|
307
|
+
"""Initailize an FSO instance from the arguments"""
|
|
308
|
+
pre_ignore = split_without_empty_strings(args.pre_ignore)
|
|
309
|
+
post_ignore = split_without_empty_strings(args.post_ignore)
|
|
310
|
+
if args.default_ignore_dirs:
|
|
311
|
+
ignore_list = [f"{dirname}/" for dirname in args.default_ignore_dirs]
|
|
312
|
+
match args.default_ignore_priority:
|
|
313
|
+
case Priority.LOW:
|
|
314
|
+
pre_ignore = ignore_list
|
|
315
|
+
case Priority.HIGH:
|
|
316
|
+
post_ignore = ignore_list
|
|
317
|
+
#
|
|
318
|
+
#
|
|
319
|
+
#
|
|
320
|
+
if args.files:
|
|
321
|
+
paths: list[Path] = list(map(Path, args.positional))
|
|
322
|
+
if not paths:
|
|
323
|
+
paths.append(Path.cwd())
|
|
324
|
+
#
|
|
325
|
+
return FileSelectionOptions(
|
|
326
|
+
paths=paths,
|
|
327
|
+
pre_ignore=pre_ignore,
|
|
328
|
+
post_ignore=post_ignore,
|
|
329
|
+
types=determine_types(args.type),
|
|
330
|
+
ignore_file_mode=determine_ignore_file_mode(),
|
|
331
|
+
encoding=args.encoding,
|
|
332
|
+
)
|
|
333
|
+
#
|
|
334
|
+
return FileSelectionOptions(
|
|
335
|
+
paths=[],
|
|
336
|
+
pre_ignore=pre_ignore,
|
|
337
|
+
post_ignore=post_ignore,
|
|
338
|
+
types=determine_types(args.type),
|
|
339
|
+
ignore_file_mode=determine_ignore_file_mode(),
|
|
340
|
+
encoding=args.encoding,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def initialize_logging(args: Namespace) -> None:
|
|
345
|
+
"""Initialize logging"""
|
|
346
|
+
loglevel = WARNING
|
|
347
|
+
if args.debug:
|
|
348
|
+
loglevel = DEBUG
|
|
349
|
+
elif args.verbose:
|
|
350
|
+
loglevel = INFO
|
|
351
|
+
elif args.quiet:
|
|
352
|
+
loglevel = CRITICAL
|
|
353
|
+
#
|
|
354
|
+
basicConfig(level=loglevel)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def parse_arguments() -> tuple[
|
|
358
|
+
FileSelectionOptions, PatternOptions | None, OutputOptions
|
|
359
|
+
]:
|
|
360
|
+
"""Parse commandline arguments and configure logging
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
the parsed command line arguments
|
|
364
|
+
"""
|
|
365
|
+
main_parser = build_argument_parser()
|
|
366
|
+
args = main_parser.parse_args()
|
|
367
|
+
initialize_logging(args)
|
|
368
|
+
debug(repr(args))
|
|
369
|
+
if args.highlighters:
|
|
370
|
+
print_highlighter_examples()
|
|
371
|
+
main_parser.exit()
|
|
372
|
+
#
|
|
373
|
+
if args.type_list:
|
|
374
|
+
print_supported_file_tyoes()
|
|
375
|
+
main_parser.exit()
|
|
376
|
+
#
|
|
377
|
+
produce_output = not args.quiet
|
|
378
|
+
if args.files:
|
|
379
|
+
return (
|
|
380
|
+
make_file_selection_options(args),
|
|
381
|
+
None,
|
|
382
|
+
OutputOptions(show_files=produce_output),
|
|
383
|
+
)
|
|
384
|
+
#
|
|
385
|
+
fso = make_file_selection_options(args)
|
|
386
|
+
path_candidates = args.positional[:]
|
|
387
|
+
patterns: list[str] = []
|
|
388
|
+
if args.patterns:
|
|
389
|
+
patterns.extend(args.patterns)
|
|
390
|
+
elif args.patternfiles:
|
|
391
|
+
for single_file in args.patternfiles:
|
|
392
|
+
patterns.extend(single_file.read_text(encoding=args.encoding).splitlines())
|
|
393
|
+
#
|
|
394
|
+
else:
|
|
395
|
+
try:
|
|
396
|
+
patterns = [path_candidates.pop(0)]
|
|
397
|
+
except IndexError:
|
|
398
|
+
main_parser.exit(
|
|
399
|
+
status=1,
|
|
400
|
+
message="This command variant requires at least one pattern"
|
|
401
|
+
" as a positional argument.",
|
|
402
|
+
)
|
|
403
|
+
#
|
|
404
|
+
#
|
|
405
|
+
if stdin.isatty():
|
|
406
|
+
paths: list[Path] = list(map(Path, path_candidates))
|
|
407
|
+
if not paths:
|
|
408
|
+
paths.append(Path.cwd())
|
|
409
|
+
#
|
|
410
|
+
fso.paths = paths
|
|
411
|
+
#
|
|
412
|
+
po = PatternOptions(
|
|
413
|
+
patterns=patterns,
|
|
414
|
+
plaintext=args.fixed_strings,
|
|
415
|
+
case_sensitive=args.case_sensitive,
|
|
416
|
+
)
|
|
417
|
+
suppress_file_prefix = not fso.paths or (
|
|
418
|
+
len(fso.paths) == 1 and is_an_eligible_path(fso.paths[0])
|
|
419
|
+
)
|
|
420
|
+
oo = OutputOptions(
|
|
421
|
+
highlighter=HIGHLIGHTERS[args.hi],
|
|
422
|
+
show_files=not suppress_file_prefix,
|
|
423
|
+
show_results=produce_output,
|
|
424
|
+
show_stats=args.stats,
|
|
425
|
+
)
|
|
426
|
+
return fso, po, oo
|
|
@@ -39,3 +39,8 @@ SLASH = "/"
|
|
|
39
39
|
|
|
40
40
|
UTF_8 = "utf-8"
|
|
41
41
|
"""Explicit default encoding"""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def split_without_empty_strings(source: str, delimiter: str = ",") -> list[str]:
|
|
45
|
+
"""Split result without empty strings"""
|
|
46
|
+
return [part for part in source.split(delimiter) if part]
|