diff --git a/CHANGES.md b/CHANGES.md index e9aafbd9..12d960eb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ ### Fixes - Do not count documentation length when aligning all columns in settings section [#156](https://github.com/MarketSquare/robotframework-tidy/issues/156) +- Acknowledge ``--lineseparator`` option [#163](https://github.com/MarketSquare/robotframework-tidy/issues/163) ## 1.4.0 diff --git a/robotidy/app.py b/robotidy/app.py index 38d1306d..73ed6d50 100644 --- a/robotidy/app.py +++ b/robotidy/app.py @@ -11,7 +11,8 @@ from robotidy.utils import ( StatementLinesCollector, decorate_diff_with_color, - GlobalFormattingConfig + GlobalFormattingConfig, + ModelWriter ) INCLUDE_EXT = ('.robot', '.resource') @@ -74,7 +75,8 @@ def transform(self, model): def save_model(self, model): if self.overwrite: - model.save(output=self.output) + output = self.output or model.source + ModelWriter(output=output, newline=self.formatting_config.line_sep).write(model) def output_diff(self, path: str, old_model: StatementLinesCollector, new_model: StatementLinesCollector): if not self.show_diff: diff --git a/robotidy/utils.py b/robotidy/utils.py index 05b3a2b1..32deaca8 100644 --- a/robotidy/utils.py +++ b/robotidy/utils.py @@ -7,6 +7,7 @@ Token ) from robot.parsing.model import Statement +from robot.utils.robotio import file_writer from click import style @@ -201,3 +202,20 @@ def get_normalized_candidates(candidates): norm_cand['alignvariables'] = ['AlignVariablesSection'] norm_cand['assignmentnormalizer'] = ['NormalizeAssignments'] return norm_cand + + +class ModelWriter(ModelVisitor): + def __init__(self, output, newline): + self.writer = file_writer(output, newline=newline) + self.close_writer = True + + def write(self, model): + try: + self.visit(model) + finally: + if self.close_writer: + self.writer.close() + + def visit_Statement(self, statement): # noqa + for token in statement.tokens: + self.writer.write(token.value) diff --git a/tests/utest/test_cli.py b/tests/utest/test_cli.py index 110e6c82..1394b02e 100644 --- a/tests/utest/test_cli.py +++ b/tests/utest/test_cli.py @@ -1,25 +1,24 @@ -from unittest.mock import patch +import os from pathlib import Path - from unittest.mock import MagicMock, Mock + import pytest from click import FileError, NoSuchOption -from .utils import run_tidy, save_tmp_model from robotidy.cli import ( find_project_root, read_pyproject_config, read_config ) -from robotidy.utils import node_within_lines from robotidy.transformers import load_transformers from robotidy.transformers.AlignSettingsSection import AlignSettingsSection from robotidy.transformers.ReplaceRunKeywordIf import ReplaceRunKeywordIf from robotidy.transformers.SmartSortKeywords import SmartSortKeywords +from robotidy.utils import node_within_lines from robotidy.version import __version__ +from .utils import run_tidy -@patch('robotidy.app.Robotidy.save_model', new=save_tmp_model) class TestCli: @pytest.mark.parametrize('src', [ None, @@ -88,7 +87,7 @@ def test_too_many_arguments_for_transform(self): def test_find_project_root_from_src(self): src = Path(Path(__file__).parent, 'testdata', 'nested', 'test.robot') path = find_project_root([src]) - assert path == Path(Path(__file__).parent, 'testdata') + assert path == Path(Path(__file__).parent, 'testdata', 'nested') def test_read_robotidy_config(self): """ robotidy.toml follows the same format as pyproject starting from 1.2.0 """ @@ -101,7 +100,7 @@ def test_read_robotidy_config(self): 'ReplaceRunKeywordIf' ] } - config_path = str(Path(Path(__file__).parent, 'testdata', 'robotidy.toml')) + config_path = str(Path(Path(__file__).parent, 'testdata', 'config', 'robotidy.toml')) config = read_pyproject_config(config_path) assert config == expected_config @@ -176,7 +175,7 @@ def test_read_config_from_param(self): 'ReplaceRunKeywordIf' ] } - config_path = str(Path(Path(__file__).parent, 'testdata', 'robotidy.toml')) + config_path = str(Path(Path(__file__).parent, 'testdata', 'config', 'robotidy.toml')) ctx_mock = MagicMock() ctx_mock.command.params = None param_mock = Mock() @@ -193,7 +192,7 @@ def test_read_config_without_param(self): 'ReplaceRunKeywordIf' ] } - config_path = str(Path(Path(__file__).parent, 'testdata', 'robotidy.toml')) + config_path = str(Path(Path(__file__).parent, 'testdata', 'config', 'robotidy.toml')) ctx_mock = MagicMock() ctx_mock.params = {'src': [config_path]} ctx_mock.command.params = None @@ -300,3 +299,25 @@ def test_configure_transformer_overwrite(self): {'AlignVariablesSection': ['up_to_column=4']} ) assert transformers[0].up_to_column + 1 == 4 + + @pytest.mark.parametrize('line_sep', ['unix', 'windows', 'native', None]) + def test_line_sep(self, line_sep): + source = Path(Path(__file__).parent, 'testdata', 'line_sep', 'test.robot') + expected = Path(Path(__file__).parent, 'testdata', 'line_sep', 'expected.robot') + actual = Path(Path(__file__).parent, 'actual', 'test.robot') + if line_sep is not None: + run_tidy(['--lineseparator', line_sep, str(source)], output='test.robot') + else: + run_tidy([str(source)], output='test.robot') + line_end = { + 'unix': '\n', + 'windows': '\r\n', + 'native': os.linesep, + None: os.linesep + }[line_sep] + with open(str(expected)) as f: + expected_str = f.read() + expected_str = expected_str.replace('\n', line_end) + with open(str(actual), newline='') as f: + actual_str = f.read() + assert actual_str == expected_str, 'Line endings does not match' diff --git a/tests/utest/testdata/robotidy.toml b/tests/utest/testdata/config/robotidy.toml similarity index 100% rename from tests/utest/testdata/robotidy.toml rename to tests/utest/testdata/config/robotidy.toml diff --git a/tests/utest/testdata/line_sep/expected.robot b/tests/utest/testdata/line_sep/expected.robot new file mode 100644 index 00000000..6623b18f --- /dev/null +++ b/tests/utest/testdata/line_sep/expected.robot @@ -0,0 +1,9 @@ +*** Settings *** +Library library.py + +Force Tags tag + +*** Keywords *** +Keyword + Log information + No Operation diff --git a/tests/utest/testdata/line_sep/test.robot b/tests/utest/testdata/line_sep/test.robot new file mode 100644 index 00000000..ae6130eb --- /dev/null +++ b/tests/utest/testdata/line_sep/test.robot @@ -0,0 +1,9 @@ +*** Settings *** +Library library.py + +Force Tags tag + +*** Keywords *** +Keyword + Log information + No Operation diff --git a/tests/utest/testdata/nested/test.robot b/tests/utest/testdata/nested/nested/deeper/test.robot similarity index 100% rename from tests/utest/testdata/nested/test.robot rename to tests/utest/testdata/nested/nested/deeper/test.robot diff --git a/tests/utest/testdata/nested/robotidy.toml b/tests/utest/testdata/nested/robotidy.toml new file mode 100644 index 00000000..d767cfc9 --- /dev/null +++ b/tests/utest/testdata/nested/robotidy.toml @@ -0,0 +1,8 @@ +[tool.robotidy] +overwrite = false +diff = false +spacecount = 4 +transform = [ + "DiscardEmptySections:allow_only_comments=True", + "ReplaceRunKeywordIf" +] \ No newline at end of file diff --git a/tests/utest/utils.py b/tests/utest/utils.py index aa93b1ee..64aa5e04 100644 --- a/tests/utest/utils.py +++ b/tests/utest/utils.py @@ -1,22 +1,19 @@ from pathlib import Path -from typing import List +from typing import List, Optional from click.testing import CliRunner from robotidy.cli import cli -def save_tmp_model(self, model): - """ Decorator that disables default robotidy save to file mechanism and replace with mocked one. - That way we can save output to 'actual' directory for easy comparison with expected files. """ - path = Path(Path(__file__).parent, 'actual', Path(model.source).name) - print(path) - model.save(output=path) - - -def run_tidy(args: List[str] = None, exit_code: int = 0): +def run_tidy(args: List[str] = None, exit_code: int = 0, output: Optional[str] = None): runner = CliRunner() arguments = args if args is not None else [] + if output: + output_path = str(Path(Path(__file__).parent, 'actual', output)) + else: + output_path = str(Path(Path(__file__).parent, 'actual', 'tmp')) + arguments = ['--output', output_path] + arguments result = runner.invoke(cli, arguments) if result.exit_code != exit_code: print(result.output)