blob: 1f0e77ad690769101a568b8e2060669c22fe1f6e [file] [log] [blame]
# Copyright 2020 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
"""File finding utility."""
import argparse
import logging
import os
import sys
import shlex
import subprocess
from pathlib import Path
import pw_cli.log
_LOG = logging.getLogger(__name__)
def _error_unknown_arg(unknown_arg):
_LOG.error('Unrecognized argument: %s', unknown_arg)
_LOG.info('')
_LOG.info('Did you mean to pass this argument to the exec command?')
_LOG.info('Insert a -- in front of it to forward it through:')
_LOG.info('')
index = sys.argv.index(unknown_arg)
args_rest = sys.argv[index:]
if "--" in args_rest:
args_rest.remove("--")
fixed_cmd = [*sys.argv[:index], '--', *args_rest]
_LOG.info(' %s', ' '.join(shlex.quote(arg) for arg in fixed_cmd))
_LOG.info('')
def build_argument_parser():
"""Setup find-files argparse."""
def log_level(arg: str) -> int:
try:
return getattr(logging, arg.upper())
except AttributeError as exc:
raise argparse.ArgumentTypeError(
f'{arg.upper()} is not a valid log level') from exc
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('-l',
'--loglevel',
type=log_level,
default=logging.INFO,
help='Set the log level '
'(debug, info, warning, error, critical)')
parser.add_argument("-s",
"--starting-dir",
default=os.getcwd(),
help="The starting directory to run a find. "
"Default: {}".format(os.getcwd()))
parser.add_argument("--type",
dest="file_type",
choices=["d", "f"],
help="Limit results to directories 'd' or files 'f'.")
parser.add_argument("-p",
"--pattern",
metavar="PATTERN",
dest="patterns",
action="append",
required=True,
help="Glob patterns to search with. Wildcard is '*'. "
"The '**' pattern means 'this directory and all "
"subdirectories, recursively'. Multiple patterns can "
"be used and are combined with a logical or.")
parser.add_argument('exec_args',
metavar="-- EXEC_ARGS",
nargs=argparse.REMAINDER,
help="Run a command on each file. The command should "
"be specified at the end after a '--'. Any args "
"matching '%%f' will be replaced with the file name.")
return parser
def main() -> int:
"""Simple find-file utility similar to the Unix find command."""
parser = build_argument_parser()
args, unused_extra_args = parser.parse_known_args()
pw_cli.log.install(args.loglevel)
if unused_extra_args:
_error_unknown_arg(unused_extra_args[0])
return 1
starting_dir = os.path.realpath(
os.path.expanduser(os.path.expandvars(args.starting_dir)))
if not os.path.exists(starting_dir):
_LOG.error("Starting directory '%s' not found.", args.starting_dir)
return 1
results = []
for pattern in args.patterns:
for path in Path(starting_dir).glob(pattern):
if args.file_type:
if args.file_type == "d" and path.is_dir():
results.append(path)
elif args.file_type == "f" and path.is_file():
results.append(path)
else:
results.append(path)
exec_args = []
if args.exec_args:
if args.exec_args[0] != '--':
_error_unknown_arg(args.exec_args[0])
return 1
exec_args = args.exec_args[1:]
for file_name in [
str(p.relative_to(os.getcwd())) for p in sorted(results)
]:
print(file_name)
if exec_args:
command = [file_name if arg == "%f" else arg for arg in exec_args]
_LOG.debug("Running: %s", " ".join(command))
subprocess.run(command, check=False)
return 0
if __name__ == '__main__':
sys.exit(main())