diff --git a/.gersemirc.example b/.gersemirc.example index 091d6eb..10595b7 100644 --- a/.gersemirc.example +++ b/.gersemirc.example @@ -7,5 +7,6 @@ indent: 4 line_length: 80 list_expansion: favour-inlining quiet: false +require_definitions: true unsafe: false workers: 8 \ No newline at end of file diff --git a/gersemi/__main__.py b/gersemi/__main__.py index 7ae7e7f..ce9b5ba 100644 --- a/gersemi/__main__.py +++ b/gersemi/__main__.py @@ -172,6 +172,18 @@ def create_argparser(): [default: cache enabled] """, ) + configuration_group.add_argument( + "--require-definitions", + "--no-require-definitions", + dest="require_definitions", + action=ToggleAction, + nargs=0, + default=None, + help=f""" + {conf_doc["require_definitions"]} + [default: definitions are required] + """, + ) parser.add_argument( dest="sources", diff --git a/gersemi/configuration.py b/gersemi/configuration.py index e129bdf..71bce9f 100644 --- a/gersemi/configuration.py +++ b/gersemi/configuration.py @@ -171,6 +171,22 @@ class Configuration: # pylint: disable=too-many-instance-attributes ), ) + require_definitions: bool = field( + default=True, + metadata=dict( + title="Require definitions", + description=doc( + """ + Require definitions of custom commands. When enabled + file which has unknown custom commands will have warnings + issued about that and result won't be cached. When disabled + it will be assumed that original formatting of unknown command + is the correct one. See: "Let's make a deal" section in README. + """ + ), + ), + ) + def summary(self): hasher = sha1() hasher.update(repr(self).encode("utf-8")) diff --git a/gersemi/configuration.schema.json b/gersemi/configuration.schema.json index c4a62f7..3878d38 100644 --- a/gersemi/configuration.schema.json +++ b/gersemi/configuration.schema.json @@ -88,6 +88,12 @@ "description": "Enables cache with data about files that are known to be formatted to speed up execution.", "title": "Enable cache", "type": "boolean" + }, + "require_definitions": { + "default": true, + "description": "Require definitions of custom commands. When enabled file which has unknown custom commands will have warnings issued about that and result won't be cached. When disabled it will be assumed that original formatting of unknown command is the correct one. See: \"Let's make a deal\" section in README.", + "title": "Require definitions", + "type": "boolean" } }, "title": "Configuration", diff --git a/gersemi/runner.py b/gersemi/runner.py index 9240648..e0c4c4b 100644 --- a/gersemi/runner.py +++ b/gersemi/runner.py @@ -26,6 +26,7 @@ from gersemi.tasks.show_diff import show_colorized_diff, show_diff from gersemi.utils import fromfile, smart_open from gersemi.keywords import Keywords +from gersemi.warnings import UnknownCommandWarning CHUNKSIZE = 16 @@ -131,18 +132,26 @@ def run_task( return task(formatted_file) -def consume_task_result(task_result: TaskResult, quiet: bool) -> Tuple[Path, int, bool]: +def consume_task_result( + task_result: TaskResult, configuration: Configuration +) -> Tuple[Path, int, bool]: if task_result.to_stdout != "": print_to_stdout(task_result.to_stdout) - if not quiet: - for warning in task_result.warnings: + warnings = ( + [w for w in task_result.warnings if not isinstance(w, UnknownCommandWarning)] + if not configuration.require_definitions + else task_result.warnings + ) + + if not configuration.quiet: + for warning in warnings: print_to_stderr(warning.get_message(fromfile(task_result.path))) if task_result.to_stderr != "": print_to_stderr(task_result.to_stderr) - return task_result.path, task_result.return_code, (len(task_result.warnings) > 0) + return task_result.path, task_result.return_code, (len(warnings) > 0) def create_pool(is_stdin_in_sources, num_workers): @@ -186,13 +195,13 @@ def select_task_for_already_formatted_files(mode: Mode): def handle_already_formatted_files( - mode: Mode, quiet: bool, already_formatted_files: Iterable[Path] + mode: Mode, configuration: Configuration, already_formatted_files: Iterable[Path] ) -> int: task = select_task_for_already_formatted_files(mode) formatter = NullFormatter() execute = partial(run_task, formatter=formatter, task=task) results = [ - consume_task_result(result, quiet) + consume_task_result(result, configuration) for result in map(execute, already_formatted_files) ] return compute_error_code(code for _, code, _ in results) @@ -220,7 +229,7 @@ def handle_files_to_format( execute = partial(run_task, formatter=formatter, task=task) results = [ - consume_task_result(result, configuration.quiet) + consume_task_result(result, configuration) for result in pool.imap_unordered(execute, files_to_format, chunksize=CHUNKSIZE) ] store_files_in_cache( @@ -246,7 +255,7 @@ def run(mode: Mode, configuration: Configuration, sources: Iterable[Path]): ) already_formatted_files_error_code = handle_already_formatted_files( - mode, configuration.quiet, already_formatted_files + mode, configuration, already_formatted_files ) files_to_format_error_code = handle_files_to_format( mode, configuration, cache, pool, files_to_format diff --git a/tests/test_executable.py b/tests/test_executable.py index b6f2747..f67ca29 100644 --- a/tests/test_executable.py +++ b/tests/test_executable.py @@ -905,7 +905,9 @@ def check_warnings(result, stderr): [ ((), False), (("--check",), True), + (("--require-definitions", "--check"), True), (("--in-place",), True), + (("--require-definitions", "--in-place"), True), (("--diff",), False), ], ) @@ -1013,6 +1015,78 @@ def test_warn_about_unknown_commands_with_stdin(tmpdir, args): ), without_definition.stderr +@pytest.mark.parametrize( + ["args", "check_cache"], + [ + ((), False), + (("--check",), True), + (("--in-place",), True), + (("--diff",), False), + ], +) +def test_dont_warn_about_unknown_commands_when_definition_arent_required( + tmpdir, args, check_cache +): + original = TESTS / "warn_about_unknown_commands" + + with cache_tests(original) as (target, gersemi_, inspector): + if check_cache: + inspector.assert_that_has_no_tables() + + cmakelists = Path(target) / "CMakeLists.txt" + without_definition = gersemi_( + *args, "--no-require-definitions", cmakelists, cwd=tmpdir + ) + assert without_definition.returncode == 0, ( + without_definition.returncode, + without_definition.stderr, + ) + assert without_definition.stderr == "", without_definition.stderr + + if check_cache: + inspector.assert_that_has_initialized_tables() + assert len(inspector.get_files()) > 0 + assert len(inspector.get_formatted()) > 0 + + with cache_tests(original) as (target, gersemi_, inspector): + if check_cache: + inspector.assert_that_has_no_tables() + + cmakelists = Path(target) / "CMakeLists.txt" + with create_dot_gersemirc(where=target, require_definitions=False): + without_definition = gersemi_(*args, cmakelists, cwd=tmpdir) + assert without_definition.returncode == 0, ( + without_definition.returncode, + without_definition.stderr, + ) + assert without_definition.stderr == "", without_definition.stderr + + if check_cache: + inspector.assert_that_has_initialized_tables() + assert len(inspector.get_files()) > 0 + assert len(inspector.get_formatted()) > 0 + + with cache_tests(original) as (target, gersemi_, inspector): + if check_cache: + inspector.assert_that_has_no_tables() + + cmakelists = Path(target) / "CMakeLists.txt" + with create_dot_gersemirc(where=target, require_definitions=True): + without_definition = gersemi_( + *args, "--no-require-definitions", cmakelists, cwd=tmpdir + ) + assert without_definition.returncode == 0, ( + without_definition.returncode, + without_definition.stderr, + ) + assert without_definition.stderr == "", without_definition.stderr + + if check_cache: + inspector.assert_that_has_initialized_tables() + assert len(inspector.get_files()) > 0 + assert len(inspector.get_formatted()) > 0 + + def test_cache_is_disabled(): original = TESTS / "custom_project" / "formatted"