bitranox/rst_include

View on GitHub
rst_include/libs/lib_check_files.py

Summary

Maintainability
A
55 mins
Test Coverage
A
94%
# STDLIB
from io import TextIOWrapper
from typing import List, IO, Tuple, Union

# OWN
import lib_log_utils
import pathlib3x as pathlib

# PROJECT
try:
    # for pytest
    from . import lib_classes
    from .lib_classes import RstFile, SourceLine
    from . import lib_test
except (ImportError, ModuleNotFoundError):          # pragma: no cover
    # for local doctest in pycharm
    import lib_classes                              # type: ignore # pragma: no cover
    from lib_classes import RstFile, SourceLine     # type: ignore # pragma: no cover
    import lib_test                                 # type: ignore # pragma: no cover


def check_source_and_target(source: Union[pathlib.Path, IO[str]],
                            target: Union[pathlib.Path, IO[str], None],
                            in_place: bool) -> Tuple[Union[pathlib.Path, IO[str]], Union[pathlib.Path, IO[str], None]]:
    """

    >>> # Setup
    >>> path_test_dir = pathlib.Path(__file__).parent.parent.parent / 'tests'
    >>> path_test_file_exists = path_test_dir / 'include1.py'
    >>> path_test_file_not_exists = path_test_dir / 'does_not_exist.py'

    >>> # create TextIOWrapper Object
    >>> import io, os
    >>> output = io.BytesIO()
    >>> wrapper = io.TextIOWrapper(output, encoding = 'utf8', line_buffering=True)
    >>> assert wrapper.write('test sys_stdin') == 14
    >>> assert wrapper.seek(0, 0) == 0

    >>> # test source = sys.stdin, target=sys.stdout, in_place = False
    >>> source, target = check_source_and_target(wrapper, wrapper, in_place=False)

    >>> # test source = file, in_place = True
    >>> source, target = check_source_and_target(source=path_test_file_exists, target=None, in_place=True)

    >>> # test source = sys.stdin, in_place=True
    >>> source, target = check_source_and_target(source=wrapper, target=None, in_place=True)
    Traceback (most recent call last):
    ...
    SyntaxError: if You use option --inplace You need to specify a input file

    """
    log_and_raise_if_source_file_not_ok(source)
    if in_place:
        if not isinstance(source, pathlib.Path):
            raise SyntaxError('if You use option --inplace You need to specify a input file')
        elif isinstance(target, pathlib.Path) and (target == source):
            lib_log_utils.log_warning('if You use option --inplace You dont need to specify the target file, its ignored')
        elif target and not isinstance(source, TextIOWrapper):
            raise SyntaxError('You used option --inplace and specified a target file different from the input file')
        target = source
    else:
        log_and_raise_if_source_file_equals_target_file(source, target)
        log_warning_if_target_file_exist(target)

    if isinstance(target, TextIOWrapper):
        lib_log_utils.log_settings.quiet = True

    return source, target


def log_and_raise_if_source_file_not_ok(source: Union[str, pathlib.Path, IO[str]]) -> None:
    """

    >>> # Setup
    >>> path_test_dir = pathlib.Path(__file__).parent.parent.parent / 'tests'
    >>> path_test_file_exists = path_test_dir / 'include1.py'
    >>> path_test_file_not_exists = path_test_dir / 'does_not_exist.py'

    >>> # create TextIOWrapper Object
    >>> import io, os
    >>> output = io.BytesIO()
    >>> wrapper = io.TextIOWrapper(output, encoding = 'utf8', line_buffering=True)
    >>> assert wrapper.write('test sys_stdin') == 14
    >>> assert wrapper.seek(0, 0) == 0

    >>> # test file exists
    >>> log_and_raise_if_source_file_not_ok(path_test_file_exists)

    >>> # test file not exists
    >>> log_and_raise_if_source_file_not_ok(path_test_file_not_exists)
    Traceback (most recent call last):
    ...
    FileNotFoundError: RST File ".../tests/does_not_exist.py" does not exist

    >>> # test source is sys.stdin
    >>> log_and_raise_if_source_file_not_ok(wrapper)

    >>> # test source is str
    >>> log_and_raise_if_source_file_not_ok('test')

    """

    if isinstance(source, pathlib.Path):
        if not source.is_file():
            error_message = f'RST File "{source}" does not exist'
            lib_log_utils.log_error(error_message)
            raise FileNotFoundError(error_message)


def log_and_raise_if_source_file_equals_target_file(source: Union[str, pathlib.Path, IO[str]],
                                                    target: Union[str, pathlib.Path, IO[str], None]) -> None:
    """

    >>> # Setup
    >>> path_test_dir = pathlib.Path(__file__).parent.parent.parent / 'tests'
    >>> path_test_file_exists1 = path_test_dir / 'include1.py'
    >>> path_test_file_exists2 = path_test_dir / 'include2.py'

    >>> # create TextIOWrapper Object
    >>> import io, os
    >>> output = io.BytesIO()
    >>> wrapper = io.TextIOWrapper(output, encoding = 'utf8', line_buffering=True)
    >>> assert wrapper.write('test sys_stdin') == 14
    >>> assert wrapper.seek(0, 0) == 0

    >>> # check input sys.stdin, output sys.stdout
    >>> log_and_raise_if_source_file_equals_target_file(wrapper, wrapper)

    >>> # check not same file
    >>> log_and_raise_if_source_file_equals_target_file(path_test_file_exists1, path_test_file_exists2)

    >>> # check same file
    >>> log_and_raise_if_source_file_equals_target_file(path_test_file_exists1, path_test_file_exists1)
    Traceback (most recent call last):
    ...
    FileExistsError: RST File ".../tests/include1.py": source and target must not be the same

    """

    if isinstance(source, pathlib.Path) and source == target:
        error_message = f'RST File "{source}": source and target must not be the same'
        lib_log_utils.log_error(error_message)
        raise FileExistsError(error_message)


def log_warning_if_target_file_exist(path_target: Union[str, pathlib.Path, IO[str], None]) -> None:
    """
    >>> # Setup
    >>> path_test_dir = pathlib.Path(__file__).parent.parent.parent / 'tests'
    >>> path_test_file_exists = path_test_dir / 'include1.py'
    >>> path_test_file_not_exists = path_test_dir / 'does_not_exist'

    >>> # TEST
    >>> log_warning_if_target_file_exist(path_target=path_test_file_exists)
    >>> log_warning_if_target_file_exist(path_target=path_test_file_not_exists)

    """
    if isinstance(path_target, pathlib.Path):
        if path_target.is_file():
            lib_log_utils.log_warning(f'RST File "{path_target}" exists and will be overwritten')


def read_input(source: Union[str, pathlib.Path, IO[str]], encoding: str = 'utf-8-sig') -> str:
    """
    >>> # Setup
    >>> path_test_dir = pathlib.Path(__file__).parent.parent.parent / 'tests'
    >>> path_test_file = path_test_dir / 'test_read.rst'
    >>> # create TextIOWrapper Object
    >>> import io, os
    >>> output = io.BytesIO()
    >>> wrapper = io.TextIOWrapper(output, encoding = 'utf8', line_buffering=True)
    >>> assert wrapper.write('test sys_stdin') == 14
    >>> assert wrapper.seek(0, 0) == 0

    >>> # read from string
    >>> assert read_input('test string') == 'test string'

    >>> # read from input file
    >>> assert read_input(path_test_file) == 'test file'

    >>> # read from sys.stdin
    >>> assert read_input(wrapper) == 'test sys_stdin'

    """

    if isinstance(source, pathlib.Path):
        content = source.read_text(encoding=encoding)
    elif isinstance(source, TextIOWrapper):
        content = source.read()
    elif isinstance(source, str):
        content = source
    else:
        raise TypeError('type of source not valid')
    return content


def read_source_lines(source: Union[str, pathlib.Path, IO[str]], encoding: str = 'utf-8-sig') -> List[SourceLine]:
    """

    >>> # Setup
    >>> path_test_dir = pathlib.Path(__file__).parent.parent.parent / 'tests'
    >>> path_read_test_file = path_test_dir / 'test_read.rst'
    >>> path_write_test_file = path_test_dir / 'write_test.txt'
    >>> if path_write_test_file.exists(): path_write_test_file.unlink()

    >>> # create TextIOWrapper Object
    >>> import io, os
    >>> output = io.BytesIO()
    >>> wrapper = io.TextIOWrapper(output, encoding = 'utf8', line_buffering=True)
    >>> assert wrapper.write('test sys_stdin') == 14
    >>> assert wrapper.seek(0, 0) == 0

    >>> # read from input file
    >>> l_source_lines = read_source_lines(path_read_test_file)
    >>> assert l_source_lines[0].line_number == 0
    >>> assert l_source_lines[0].content == 'test file'

    >>> assert write_output(path_write_test_file,'line0\\n line1') == 'line0\\n line1'
    >>> l_source_lines = read_source_lines(path_write_test_file)
    >>> assert l_source_lines[0].line_number == 0
    >>> assert l_source_lines[0].content == 'line0'
    >>> assert l_source_lines[1].line_number == 1
    >>> assert l_source_lines[1].content == ' line1'

    >>> # read from sys.stdin
    >>> l_source_lines = read_source_lines(wrapper)
    >>> assert l_source_lines[0].line_number == 0
    >>> assert l_source_lines[0].content == 'test sys_stdin'

    >>> # read from str
    >>> l_source_lines = read_source_lines('test from str')
    >>> assert l_source_lines[0].line_number == 0
    >>> assert l_source_lines[0].content == 'test from str'

    >>> # TEARDOWN
    >>> if path_write_test_file.exists(): path_write_test_file.unlink()

    """

    if isinstance(source, pathlib.Path):
        with open(str(source), encoding=encoding, mode='r') as sourcefile:
            content_lines = sourcefile.readlines()
    elif isinstance(source, TextIOWrapper):
        content_lines = source.readlines()
    elif isinstance(source, str):
        content_lines = source.split('\n')
    else:
        raise TypeError('unknown type of source line')

    l_source_lines = list()
    line_number = 0
    for content in content_lines:
        source_line = lib_classes.SourceLine()
        source_line.line_number = line_number
        source_line.content = content.rstrip()
        line_number = line_number + 1
        l_source_lines.append(source_line)
    return l_source_lines


def write_output(target: Union[pathlib.Path, IO[str], None], content: str, encoding: str = 'utf-8') -> str:
    """
    writes the output to the target. If the target is None, only the text will be returned
    >>> # Setup
    >>> path_test_dir = pathlib.Path(__file__).parent.parent.parent / 'tests'
    >>> path_test_write_test_file = path_test_dir / 'write_test.txt'
    >>> if path_test_write_test_file.exists(): path_test_write_test_file.unlink()
    >>> # create TextIOWrapper Object
    >>> import io, os
    >>> output = io.BytesIO()
    >>> wrapper = io.TextIOWrapper(output, encoding = 'utf8', line_buffering=True)
    >>> assert wrapper.seek(0, 0) == 0


    >>> # write to file
    >>> assert write_output(path_test_write_test_file, 'test') == 'test'
    >>> assert path_test_write_test_file.read_text() == 'test'

    >>> # write to stdout
    >>> assert write_output(wrapper, 'test') == 'test'
    >>> assert wrapper.seek(0, 0) == 0
    >>> assert wrapper.read() == 'test'

    >>> # Teardown
    >>> path_test_write_test_file.unlink(missing_ok=True)

    """

    if isinstance(target, pathlib.Path):                # write to file
        target.write_text(content, encoding=encoding)
    elif isinstance(target, TextIOWrapper):             # write to sys.stdout
        target.write(content)
    return content                                      # return text