<commit_before>import textwrap
import sys
from io import StringIO
from itertools import chain
from pathlib import Path
from typing import List, Optional, TextIO, Union
from warnings import warn

from . import output, parse, io
from .exceptions import (
    ExistingSyntaxErrors,
    FileSkipComment,
    FileSkipSetting,
    IntroducedSyntaxErrors,
)
from .format import (
    format_natural,
    remove_whitespace,
    show_unified_diff,
    ask_whether_to_apply_changes_to_file,
)
from .io import Empty, File
from .settings import DEFAULT_CONFIG, FILE_SKIP_COMMENTS, Config

CIMPORT_IDENTIFIERS = ("cimport ", "cimport*", "from.cimport")
IMPORT_START_IDENTIFIERS = ("from ", "from.import", "import ", "import*") + CIMPORT_IDENTIFIERS
COMMENT_INDICATORS = ('"""', "'''", "'", '"', "#")

def _config(
    path: Optional[Path] = None, config: Config = DEFAULT_CONFIG, **config_kwargs
) -> Config:
    if path:
        if (
            config is DEFAULT_CONFIG
            and "settings_path" not in config_kwargs
            and "settings_file" not in config_kwargs
        ):
            config_kwargs["settings_path"] = path

    if config_kwargs and config is not DEFAULT_CONFIG:
        raise ValueError(
            "You can either specify custom configuration options using kwargs or "
            "passing in a Config object. Not Both!"
        )
    elif config_kwargs:
        config = Config(**config_kwargs)

    return config

def sorted_imports(
    input_stream: TextIO,
    output_stream: TextIO,
    extension: str = "py",
    config: Config = DEFAULT_CONFIG,
    file_path: Optional[Path] = None,
    disregard_skip: bool = False,
    **config_kwargs,
):
    config = _config(config=config, **config_kwargs)
    content_source = str(file_path or "Passed in content")
    if not disregard_skip:
        if file_path and config.is_skipped(file_path):
            raise FileSkipSetting(content_source)

    if config.atomic:
        try:
            file_content = input_stream.read()
            compile(file_content, content_source, "exec", 0, 1)
            input_stream = StringIO(file_content)
        except SyntaxError:
            raise ExistingSyntaxErrors(content_source)

    try:
        changed = sort_imports(input_stream, output_stream, extension=extension, config=config)
    except FileSkipComment:
        raise FileSkipComment(content_source)

    if config.atomic:
        output_stream.seek(0)
        try:
            compile(output_stream.read(), content_source, "exec", 0, 1)
            output_stream.seek(0)
        except SyntaxError:
            raise IntroducedSyntaxErrors(content_source)

    return changed

def check_imports(
    input_stream: TextIO,
    show_diff: bool = False,
    extension: str = "py",
    config: Config = DEFAULT_CONFIG,
    file_path: Optional[Path] = None,
    disregard_skip: bool = False,
    **config_kwargs,
) -> bool:
    config = _config(config=config, **config_kwargs)

    changed: bool = sorted_imports(
        input_stream=input_stream,
        output_stream=Empty,
        extension=extension,
        config=config,
        file_path=file_path,
        disregard_skip=disregard_skip,
        **config_kwargs,
    )

    if not changed:
        if config.verbose:
            print(f"SUCCESS: {file_path or ''} Everything Looks Good!")
        return True
    else:
        print(f"ERROR: {file_path or ''} Imports are incorrectly sorted and/or formatted.")
        if show_diff:
            output_stream = StringIO()
            input_stream.seek(0)
            file_contents = input_stream.read()
            sorted_imports(
                input_stream=StringIO(file_contents),
                output_stream=output_stream,
                extension=extension,
                config=config,
                file_path=file_path,
                disregard_skip=disregard_skip,
                **config_kwargs,
            )
            output_stream.seek(0)

            show_unified_diff(
                file_input=file_contents, file_output=output_stream.read(), file_path=file_path
            )
        return False

def sort_imports(
    input_stream: TextIO,
    output_stream: TextIO,
    extension: str = "py",
    config: Config = DEFAULT_CONFIG,
) -> bool:
    """Parses stream identifying sections of contiguous imports and sorting them

    Code with unsorted imports is read from the provided `input_stream`, sorted and then
    outputted to the specified `output_stream`.

    - `input_stream`: Text stream with unsorted import sections.
    - `output_stream`: Text stream to output sorted inputs into.
    - `config`: Config settings to use when sorting imports. Defaults settings.
        - *Default*: `isort.settings.DEFAULT_CONFIG`.
    - `extension`: The file extension or file extension rules that should be used.
        - *Default*: `"py"`.
        - *Choices*: `["py", "pyi", "pyx"]`.

    Returns `True` if there were changes that needed to be made (errors present) from what
    was provided in the input_stream, otherwise `False`.
    """
    line_separator: str = config.line_ending
    add_imports: List[str] = [format_natural(addition) for addition in config.add_imports]
    import_section: str = ""
    next_import_section: str = ""
    next_cimports: bool = False
    in_quote: str = ""
    first_comment_index_start: int = -1
    first_comment_index_end: int = -1
    contains_imports: bool = False
    in_top_comment: bool = False
    first_import_section: bool = True
    section_comments = [f"# {heading}" for heading in config.import_headings.values()]
    indent: str = ""
    isort_off: bool = False
    cimports: bool = False
    made_changes: bool = False

    for index, line in enumerate(chain(input_stream, (None,))):
        if line is None:
            if index == 0 and not config.force_adds:
                return False

            not_imports = True
            line = ""
            if not line_separator:
                line_separator = "\n"
        else:
            if not line_separator:
                line_separator = line[-1]

            for file_skip_comment in FILE_SKIP_COMMENTS:
                if file_skip_comment in line:
                    raise FileSkipComment("Passed in content")

            stripped_line = line.strip()
            if (
                (index == 0 or (index in (1, 2) and not contains_imports))
                and stripped_line.startswith("#")
                and stripped_line not in section_comments
            ):
                in_top_comment = True
            elif in_top_comment:
                if not line.startswith("#") or stripped_line in section_comments:
                    in_top_comment = False
                    first_comment_index_end = index - 1

            if (not stripped_line.startswith("#") or in_quote) and '"' in line or "'" in line:
                char_index = 0
                if first_comment_index_start == -1 and (
                    line.startswith('"') or line.startswith("'")
                ):
                    first_comment_index_start = index
                while char_index < len(line):
                    if line[char_index] == "\\":
                        char_index += 1
                    elif in_quote:
                        if line[char_index : char_index + len(in_quote)] == in_quote:
                            in_quote = ""
                            if first_comment_index_end < first_comment_index_start:
                                first_comment_index_end = index
                    elif line[char_index] in ("'", '"'):
                        long_quote = line[char_index : char_index + 3]
                        if long_quote in ('"""', "'''"):
                            in_quote = long_quote
                            char_index += 2
                        else:
                            in_quote = line[char_index]
                    elif line[char_index] == "#":
                        break
                    char_index += 1

            not_imports = bool(in_quote) or in_top_comment or isort_off
            if not (in_quote or in_top_comment):
                stripped_line = line.strip()
                if isort_off:
                    if stripped_line == "# isort: on":
                        isort_off = False
                elif stripped_line == "# isort: off":
                    not_imports = True
                    isort_off = True
                elif stripped_line == "# isort: split":
                    not_imports = True
                elif (
                    not stripped_line
                    or stripped_line.startswith("#")
                    and (not indent or indent + line.lstrip() == line)
                ):
                    import_section += line
                elif stripped_line.startswith(IMPORT_START_IDENTIFIERS):
                    contains_imports = True

                    new_indent = line[: -len(line.lstrip())]
                    import_statement = line
                    stripped_line = line.strip().split("#")[0]
                    while stripped_line.endswith("\\") or (
                        "(" in stripped_line and ")" not in stripped_line
                    ):
                        if stripped_line.endswith("\\"):
                            while stripped_line and stripped_line.endswith("\\"):
                                line = input_stream.readline()
                                stripped_line = line.strip().split("#")[0]
                                import_statement += line
                        else:
                            while ")" not in stripped_line:
                                line = input_stream.readline()
                                stripped_line = line.strip().split("#")[0]
                                import_statement += line

                    cimport_statement: bool = False
                    if (
                        import_statement.lstrip().startswith(CIMPORT_IDENTIFIERS)
                        or " cimport " in import_statement
                        or " cimport*" in import_statement
                        or " cimport(" in import_statement
                        or ".cimport" in import_statement
                    ):
                        cimport_statement = True

                    if cimport_statement != cimports or (new_indent != indent and import_section):
                        if import_section:
                            next_cimports = cimport_statement
                            next_import_section = import_statement
                            import_statement = ""
                            not_imports = True
                            line = ""
                        else:
                            cimports = cimport_statement

                    indent = new_indent
                    import_section += import_statement
                else:
                    not_imports = True

        if not_imports:
            if (
                add_imports
                and not in_top_comment
                and not in_quote
                and not import_section
                and not line.lstrip().startswith(COMMENT_INDICATORS)
            ):
                import_section = line_separator.join(add_imports) + line_separator
                contains_imports = True
                add_imports = []

            if next_import_section and not import_section:
                import_section = next_import_section
                next_import_section = ""

            if import_section:
                if add_imports and not indent:
                    import_section += line_separator.join(add_imports) + line_separator
                    contains_imports = True
                    add_imports = []

                if not indent:
                    import_section += line
                if not contains_imports:
                    output_stream.write(import_section)
                else:
                    leading_whitespace = import_section[: -len(import_section.lstrip())]
                    trailing_whitespace = import_section[len(import_section.rstrip()) :]
                    if first_import_section and not import_section.lstrip(
                        line_separator
                    ).startswith(COMMENT_INDICATORS):
                        import_section = import_section.lstrip(line_separator)
                        first_import_section = False

                    raw_import_section: str = import_section
                    if indent:
                        import_section = line_separator.join(
                            line.lstrip() for line in import_section.split(line_separator)
                        )
                        out_config = Config(
                            config=config, line_length=max(config.line_length - len(indent), 0)
                        )
                    else:
                        out_config = config

                    sorted_import_section = output.sorted_imports(
                        parse.file_contents(import_section, config=config),
                        out_config,
                        extension,
                        import_type="cimport" if cimports else "import",
                    )
                    if indent:
                        sorted_import_section = (
                            leading_whitespace
                            + textwrap.indent(sorted_import_section, indent).strip()
                            + trailing_whitespace
                        )

                    if not made_changes:
                        if config.ignore_whitespace:
                            compare_in = remove_whitespace(
                                raw_import_section, line_separator=line_separator
                            ).strip()
                            compare_out = remove_whitespace(
                                sorted_import_section, line_separator=line_separator
                            ).strip()
                        else:
                            compare_in = raw_import_section.strip()
                            compare_out = sorted_import_section.strip()

                        if compare_out != compare_in:
                            made_changes = True

                    output_stream.write(sorted_import_section)
                    if not line and not indent and next_import_section:
                        output_stream.write(line_separator)

                if indent:
                    output_stream.write(line)
                    if not next_import_section:
                        indent = ""

                if next_import_section:
                    cimports = next_cimports
                    contains_imports = True
                else:
                    contains_imports = False
                import_section = next_import_section
                next_import_section = ""
            else:
                output_stream.write(line)
                not_imports = False

    return made_changes

def check_file(
    filename: Union[str, Path],
    show_diff: bool = False,
    config: Config = DEFAULT_CONFIG,
    file_path: Optional[Path] = None,
    disregard_skip: bool = False,
    **config_kwargs,
) -> bool:
    with io.read_file(filename) as source_file:
        return check_imports(
            source_file.stream,
            show_diff=show_diff,
            extension=source_file.extension or "py",
            config=config,
            file_path=source_file.path,
            disregard_skip=disregard_skip,
            **config_kwargs,
        )

def sort_file(
    filename: Union[str, Path],
    extension: str = "py",
    config: Config = DEFAULT_CONFIG,
    file_path: Optional[Path] = None,
    disregard_skip: bool = False,
    ask_to_apply: bool = False,
    show_diff: bool = False,
    write_to_stdout: bool = False,
    **config_kwargs,
):
    with io.read_file(filename) as source_file:
        changed: bool = False
        try:
            if write_to_stdout:
                changed = sorted_imports(
                    input_stream=source_file.stream,
                    output_stream=sys.stdout,
                    config=config,
                    file_path=source_file.path,
                    disregard_skip=disregard_skip,
                    **config_kwargs,
                )
            else:
                tmp_file = source_file.path.with_suffix(source_file.path.suffix + ".isorted")
                try:
                    with tmp_file.open(
                        "w", encoding=source_file.encoding, newline=""
                    ) as output_stream:
                        changed = sorted_imports(
                            input_stream=source_file.stream,
                            output_stream=output_stream,
                            config=config,
                            file_path=source_file.path,
                            disregard_skip=disregard_skip,
                            **config_kwargs,
                        )
                    if changed:
                        if show_diff or ask_to_apply:
                            source_file.stream.seek(0)
                            show_unified_diff(
                                file_input=source_file.stream.read(),
                                file_output=tmp_file.read_text(encoding=source_file.encoding),
                                file_path=source_file.path,
                            )
                            if ask_to_apply and not ask_whether_to_apply_changes_to_file(
                                str(source_file.path)
                            ):
                                return
                        tmp_file.replace(source_file.path)
                finally:
                    try:  # Python 3.8+: use `missing_ok=True` instead of try except.
                        tmp_file.unlink()
                    except FileNotFoundError:
                        pass
        except ExistingSyntaxErrors:
            warn("{file_path} unable to sort due to existing syntax errors")
        except IntroducedSyntaxErrors:
            warn("{file_path} unable to sort as isort introduces new syntax errors")
<commit_msg>Report files as they are fixed<commit_after>import textwrap
import sys
from io import StringIO
from itertools import chain
from pathlib import Path
from typing import List, Optional, TextIO, Union
from warnings import warn

from . import output, parse, io
from .exceptions import (
    ExistingSyntaxErrors,
    FileSkipComment,
    FileSkipSetting,
    IntroducedSyntaxErrors,
)
from .format import (
    format_natural,
    remove_whitespace,
    show_unified_diff,
    ask_whether_to_apply_changes_to_file,
)
from .io import Empty, File
from .settings import DEFAULT_CONFIG, FILE_SKIP_COMMENTS, Config

CIMPORT_IDENTIFIERS = ("cimport ", "cimport*", "from.cimport")
IMPORT_START_IDENTIFIERS = ("from ", "from.import", "import ", "import*") + CIMPORT_IDENTIFIERS
COMMENT_INDICATORS = ('"""', "'''", "'", '"', "#")

def _config(
    path: Optional[Path] = None, config: Config = DEFAULT_CONFIG, **config_kwargs
) -> Config:
    if path:
        if (
            config is DEFAULT_CONFIG
            and "settings_path" not in config_kwargs
            and "settings_file" not in config_kwargs
        ):
            config_kwargs["settings_path"] = path

    if config_kwargs and config is not DEFAULT_CONFIG:
        raise ValueError(
            "You can either specify custom configuration options using kwargs or "
            "passing in a Config object. Not Both!"
        )
    elif config_kwargs:
        config = Config(**config_kwargs)

    return config

def sorted_imports(
    input_stream: TextIO,
    output_stream: TextIO,
    extension: str = "py",
    config: Config = DEFAULT_CONFIG,
    file_path: Optional[Path] = None,
    disregard_skip: bool = False,
    **config_kwargs,
):
    config = _config(config=config, **config_kwargs)
    content_source = str(file_path or "Passed in content")
    if not disregard_skip:
        if file_path and config.is_skipped(file_path):
            raise FileSkipSetting(content_source)

    if config.atomic:
        try:
            file_content = input_stream.read()
            compile(file_content, content_source, "exec", 0, 1)
            input_stream = StringIO(file_content)
        except SyntaxError:
            raise ExistingSyntaxErrors(content_source)

    try:
        changed = sort_imports(input_stream, output_stream, extension=extension, config=config)
    except FileSkipComment:
        raise FileSkipComment(content_source)

    if config.atomic:
        output_stream.seek(0)
        try:
            compile(output_stream.read(), content_source, "exec", 0, 1)
            output_stream.seek(0)
        except SyntaxError:
            raise IntroducedSyntaxErrors(content_source)

    return changed

def check_imports(
    input_stream: TextIO,
    show_diff: bool = False,
    extension: str = "py",
    config: Config = DEFAULT_CONFIG,
    file_path: Optional[Path] = None,
    disregard_skip: bool = False,
    **config_kwargs,
) -> bool:
    config = _config(config=config, **config_kwargs)

    changed: bool = sorted_imports(
        input_stream=input_stream,
        output_stream=Empty,
        extension=extension,
        config=config,
        file_path=file_path,
        disregard_skip=disregard_skip,
        **config_kwargs,
    )

    if not changed:
        if config.verbose:
            print(f"SUCCESS: {file_path or ''} Everything Looks Good!")
        return True
    else:
        print(f"ERROR: {file_path or ''} Imports are incorrectly sorted and/or formatted.")
        if show_diff:
            output_stream = StringIO()
            input_stream.seek(0)
            file_contents = input_stream.read()
            sorted_imports(
                input_stream=StringIO(file_contents),
                output_stream=output_stream,
                extension=extension,
                config=config,
                file_path=file_path,
                disregard_skip=disregard_skip,
                **config_kwargs,
            )
            output_stream.seek(0)

            show_unified_diff(
                file_input=file_contents, file_output=output_stream.read(), file_path=file_path
            )
        return False

def sort_imports(
    input_stream: TextIO,
    output_stream: TextIO,
    extension: str = "py",
    config: Config = DEFAULT_CONFIG,
) -> bool:
    """Parses stream identifying sections of contiguous imports and sorting them

    Code with unsorted imports is read from the provided `input_stream`, sorted and then
    outputted to the specified `output_stream`.

    - `input_stream`: Text stream with unsorted import sections.
    - `output_stream`: Text stream to output sorted inputs into.
    - `config`: Config settings to use when sorting imports. Defaults settings.
        - *Default*: `isort.settings.DEFAULT_CONFIG`.
    - `extension`: The file extension or file extension rules that should be used.
        - *Default*: `"py"`.
        - *Choices*: `["py", "pyi", "pyx"]`.

    Returns `True` if there were changes that needed to be made (errors present) from what
    was provided in the input_stream, otherwise `False`.
    """
    line_separator: str = config.line_ending
    add_imports: List[str] = [format_natural(addition) for addition in config.add_imports]
    import_section: str = ""
    next_import_section: str = ""
    next_cimports: bool = False
    in_quote: str = ""
    first_comment_index_start: int = -1
    first_comment_index_end: int = -1
    contains_imports: bool = False
    in_top_comment: bool = False
    first_import_section: bool = True
    section_comments = [f"# {heading}" for heading in config.import_headings.values()]
    indent: str = ""
    isort_off: bool = False
    cimports: bool = False
    made_changes: bool = False

    for index, line in enumerate(chain(input_stream, (None,))):
        if line is None:
            if index == 0 and not config.force_adds:
                return False

            not_imports = True
            line = ""
            if not line_separator:
                line_separator = "\n"
        else:
            if not line_separator:
                line_separator = line[-1]

            for file_skip_comment in FILE_SKIP_COMMENTS:
                if file_skip_comment in line:
                    raise FileSkipComment("Passed in content")

            stripped_line = line.strip()
            if (
                (index == 0 or (index in (1, 2) and not contains_imports))
                and stripped_line.startswith("#")
                and stripped_line not in section_comments
            ):
                in_top_comment = True
            elif in_top_comment:
                if not line.startswith("#") or stripped_line in section_comments:
                    in_top_comment = False
                    first_comment_index_end = index - 1

            if (not stripped_line.startswith("#") or in_quote) and '"' in line or "'" in line:
                char_index = 0
                if first_comment_index_start == -1 and (
                    line.startswith('"') or line.startswith("'")
                ):
                    first_comment_index_start = index
                while char_index < len(line):
                    if line[char_index] == "\\":
                        char_index += 1
                    elif in_quote:
                        if line[char_index : char_index + len(in_quote)] == in_quote:
                            in_quote = ""
                            if first_comment_index_end < first_comment_index_start:
                                first_comment_index_end = index
                    elif line[char_index] in ("'", '"'):
                        long_quote = line[char_index : char_index + 3]
                        if long_quote in ('"""', "'''"):
                            in_quote = long_quote
                            char_index += 2
                        else:
                            in_quote = line[char_index]
                    elif line[char_index] == "#":
                        break
                    char_index += 1

            not_imports = bool(in_quote) or in_top_comment or isort_off
            if not (in_quote or in_top_comment):
                stripped_line = line.strip()
                if isort_off:
                    if stripped_line == "# isort: on":
                        isort_off = False
                elif stripped_line == "# isort: off":
                    not_imports = True
                    isort_off = True
                elif stripped_line == "# isort: split":
                    not_imports = True
                elif (
                    not stripped_line
                    or stripped_line.startswith("#")
                    and (not indent or indent + line.lstrip() == line)
                ):
                    import_section += line
                elif stripped_line.startswith(IMPORT_START_IDENTIFIERS):
                    contains_imports = True

                    new_indent = line[: -len(line.lstrip())]
                    import_statement = line
                    stripped_line = line.strip().split("#")[0]
                    while stripped_line.endswith("\\") or (
                        "(" in stripped_line and ")" not in stripped_line
                    ):
                        if stripped_line.endswith("\\"):
                            while stripped_line and stripped_line.endswith("\\"):
                                line = input_stream.readline()
                                stripped_line = line.strip().split("#")[0]
                                import_statement += line
                        else:
                            while ")" not in stripped_line:
                                line = input_stream.readline()
                                stripped_line = line.strip().split("#")[0]
                                import_statement += line

                    cimport_statement: bool = False
                    if (
                        import_statement.lstrip().startswith(CIMPORT_IDENTIFIERS)
                        or " cimport " in import_statement
                        or " cimport*" in import_statement
                        or " cimport(" in import_statement
                        or ".cimport" in import_statement
                    ):
                        cimport_statement = True

                    if cimport_statement != cimports or (new_indent != indent and import_section):
                        if import_section:
                            next_cimports = cimport_statement
                            next_import_section = import_statement
                            import_statement = ""
                            not_imports = True
                            line = ""
                        else:
                            cimports = cimport_statement

                    indent = new_indent
                    import_section += import_statement
                else:
                    not_imports = True

        if not_imports:
            if (
                add_imports
                and not in_top_comment
                and not in_quote
                and not import_section
                and not line.lstrip().startswith(COMMENT_INDICATORS)
            ):
                import_section = line_separator.join(add_imports) + line_separator
                contains_imports = True
                add_imports = []

            if next_import_section and not import_section:
                import_section = next_import_section
                next_import_section = ""

            if import_section:
                if add_imports and not indent:
                    import_section += line_separator.join(add_imports) + line_separator
                    contains_imports = True
                    add_imports = []

                if not indent:
                    import_section += line
                if not contains_imports:
                    output_stream.write(import_section)
                else:
                    leading_whitespace = import_section[: -len(import_section.lstrip())]
                    trailing_whitespace = import_section[len(import_section.rstrip()) :]
                    if first_import_section and not import_section.lstrip(
                        line_separator
                    ).startswith(COMMENT_INDICATORS):
                        import_section = import_section.lstrip(line_separator)
                        first_import_section = False

                    raw_import_section: str = import_section
                    if indent:
                        import_section = line_separator.join(
                            line.lstrip() for line in import_section.split(line_separator)
                        )
                        out_config = Config(
                            config=config, line_length=max(config.line_length - len(indent), 0)
                        )
                    else:
                        out_config = config

                    sorted_import_section = output.sorted_imports(
                        parse.file_contents(import_section, config=config),
                        out_config,
                        extension,
                        import_type="cimport" if cimports else "import",
                    )
                    if indent:
                        sorted_import_section = (
                            leading_whitespace
                            + textwrap.indent(sorted_import_section, indent).strip()
                            + trailing_whitespace
                        )

                    if not made_changes:
                        if config.ignore_whitespace:
                            compare_in = remove_whitespace(
                                raw_import_section, line_separator=line_separator
                            ).strip()
                            compare_out = remove_whitespace(
                                sorted_import_section, line_separator=line_separator
                            ).strip()
                        else:
                            compare_in = raw_import_section.strip()
                            compare_out = sorted_import_section.strip()

                        if compare_out != compare_in:
                            made_changes = True

                    output_stream.write(sorted_import_section)
                    if not line and not indent and next_import_section:
                        output_stream.write(line_separator)

                if indent:
                    output_stream.write(line)
                    if not next_import_section:
                        indent = ""

                if next_import_section:
                    cimports = next_cimports
                    contains_imports = True
                else:
                    contains_imports = False
                import_section = next_import_section
                next_import_section = ""
            else:
                output_stream.write(line)
                not_imports = False

    return made_changes

def check_file(
    filename: Union[str, Path],
    show_diff: bool = False,
    config: Config = DEFAULT_CONFIG,
    file_path: Optional[Path] = None,
    disregard_skip: bool = False,
    **config_kwargs,
) -> bool:
    with io.read_file(filename) as source_file:
        return check_imports(
            source_file.stream,
            show_diff=show_diff,
            extension=source_file.extension or "py",
            config=config,
            file_path=source_file.path,
            disregard_skip=disregard_skip,
            **config_kwargs,
        )

def sort_file(
    filename: Union[str, Path],
    extension: str = "py",
    config: Config = DEFAULT_CONFIG,
    file_path: Optional[Path] = None,
    disregard_skip: bool = False,
    ask_to_apply: bool = False,
    show_diff: bool = False,
    write_to_stdout: bool = False,
    **config_kwargs,
):
    with io.read_file(filename) as source_file:
        changed: bool = False
        try:
            if write_to_stdout:
                changed = sorted_imports(
                    input_stream=source_file.stream,
                    output_stream=sys.stdout,
                    config=config,
                    file_path=source_file.path,
                    disregard_skip=disregard_skip,
                    **config_kwargs,
                )
            else:
                tmp_file = source_file.path.with_suffix(source_file.path.suffix + ".isorted")
                try:
                    with tmp_file.open(
                        "w", encoding=source_file.encoding, newline=""
                    ) as output_stream:
                        changed = sorted_imports(
                            input_stream=source_file.stream,
                            output_stream=output_stream,
                            config=config,
                            file_path=source_file.path,
                            disregard_skip=disregard_skip,
                            **config_kwargs,
                        )
                    if changed:
                        if show_diff or ask_to_apply:
                            source_file.stream.seek(0)
                            show_unified_diff(
                                file_input=source_file.stream.read(),
                                file_output=tmp_file.read_text(encoding=source_file.encoding),
                                file_path=source_file.path,
                            )
                            if ask_to_apply and not ask_whether_to_apply_changes_to_file(
                                str(source_file.path)
                            ):
                                return
                        tmp_file.replace(source_file.path)
                        if not config.quiet:
                            print(f"Fixing {source_file.path}")
                finally:
                    try:  # Python 3.8+: use `missing_ok=True` instead of try except.
                        tmp_file.unlink()
                    except FileNotFoundError:
                        pass
        except ExistingSyntaxErrors:
            warn("{file_path} unable to sort due to existing syntax errors")
        except IntroducedSyntaxErrors:
            warn("{file_path} unable to sort as isort introduces new syntax errors")
<|endoftext|>


<commit_before>"""Process the arguments for a single repository and execute."""
# =============================================================================
# CONTENTS
# -----------------------------------------------------------------------------
# abdi_processargs
#
# Public Functions:
#   setup_parser
#   setup_repo_arg_parser
#   configure_sendmail
#   setup_sigterm_handler
#   make_exception_message_handler
#   make_exception_delay_handler
#   get_retry_delays
#   run_once
#
# -----------------------------------------------------------------------------
# (this contents block is generated, edits will be lost)
# =============================================================================

from __future__ import absolute_import

import contextlib
import platform
import signal
import sys
import traceback

import phlcon_reviewstatecache
import phlmail_sender
import phlsys_conduit
import phlsys_git
import phlsys_pluginmanager
import phlsys_sendmail
import phlsys_strtotime

import abdmail_mailer
import abdt_classicnaming
import abdt_compositenaming
import abdt_conduit
import abdt_errident
import abdt_git
import abdt_rbranchnaming
import abdt_repoconfig
import abdt_reporeporter
import abdt_shareddictoutput
import abdt_tryloop

import abdi_processrepo

def setup_parser(parser):
    parser.add_argument(
        '--sys-admin-emails',
        metavar="EMAIL",
        nargs="+",
        type=str,
        required=True,
        help="email addresses to send important system events to")
    parser.add_argument(
        '--sendmail-binary',
        metavar="PROGRAM",
        type=str,
        default="sendmail",
        help="program to send the mail with (e.g. sendmail, catchmail)")
    parser.add_argument(
        '--sendmail-type',
        metavar="TYPE",
        type=str,
        default="sendmail",
        help="type of program to send the mail with (sendmail, catchmail), "
        "this will affect the parameters that Arycd will use.")

def setup_repo_arg_parser(parser):

    parser.add_argument(
        '--instance-uri',
        metavar="URI",
        type=str,
        required=True,
        help="instance to connect to, e.g. http://127.0.0.1")

    parser.add_argument(
        '--arcyd-user',
        metavar="USER",
        type=str,
        required=True,
        help="username for Arcyd to use")

    parser.add_argument(
        '--arcyd-cert',
        metavar="CERT",
        type=str,
        required=True,
        help="Phabricator Conduit API certificate to use, this is the "
        "value that you will find in your user account in Phabricator "
        "at: http://your.server.example/settings/panel/conduit/. "
        "It can also be found in ~/.arcrc.")

    parser.add_argument(
        '--arcyd-email',
        metavar="FROM",
        type=str,
        required=True,
        help="email address for Arcyd to send mails from")

    parser.add_argument(
        '--admin-email',
        metavar="TO",
        type=str,
        required=True,
        help="email address to send important system events to")

    parser.add_argument(
        '--repo-desc',
        metavar="DESC",
        type=str,
        required=True,
        help="description to use in emails")

    parser.add_argument(
        '--repo-path',
        metavar="PATH",
        type=str,
        required=True,
        help="path to the repository on disk")

    parser.add_argument(
        '--repo-snoop-url',
        metavar="URL",
        type=str,
        help="URL to use to snoop the latest contents of the repository, this "
             "is used by Arcyd to more efficiently determine if it needs to "
             "fetch the repository or not.  The efficiency comes from "
             "re-using connections to the same host when querying.  The "
             "contents returned by the URL are expected to change every time "
             "the git repository changes, a good example of a URL to supply "
             "is to the 'info/refs' address if you're serving up the repo "
             "over http or https.  "
             "e.g. 'http://server.test/git/myrepo/info/refs'.")

    parser.add_argument(
        '--https-proxy',
        metavar="PROXY",
        type=str,
        help="proxy to use, if necessary")

    parser.add_argument(
        '--sleep-secs',
        metavar="TIME",
        type=int,
        default=60,
        help="time to wait between fetches")

    parser.add_argument(
        '--review-url-format',
        metavar="STRING",
        type=str,
        help="e.g. 'http://my.phabricator/D{review}'")

    parser.add_argument(
        '--branch-url-format',
        metavar="STRING",
        type=str,
        help="e.g. 'http://my.git/gitweb?p=r.git;a=log;h=refs/heads/{branch}'")

    parser.add_argument(
        '--try-touch-path',
        metavar="PATH",
        type=str,
        required=True,
        help="file to touch when trying to update a repo")

    parser.add_argument(
        '--ok-touch-path',
        metavar="PATH",
        type=str,
        required=True,
        help="file to touch when successfully updated a repo")

    parser.add_argument(
        "--plugins",
        metavar="MODULE_NAME",
        nargs="+",
        type=str,
        default=[],
        required=False,
        help="List the plugins to be loaded. MODULE_NAME must be present "
        "in /testbed/plugins/ directory as this feature is WIP.")

    parser.add_argument(
        "--trusted-plugins",
        metavar="MODULE_NAME",
        nargs="+",
        type=str,
        default=[],
        required=False,
        help="List the trusted plugins to be loaded. MODULE_NAME must be "
        "present in /testbed/plugins/ directory as this feature is WIP."
        "See /testbed/plugins/README.md for detail about trusted-plugins")

def configure_sendmail(args):
    if args.sendmail_binary:
        phlsys_sendmail.Sendmail.set_default_binary(
            args.sendmail_binary)

    if args.sendmail_type:
        phlsys_sendmail.Sendmail.set_default_params_from_type(
            args.sendmail_type)

# XXX: belongs in phlsys somewhere
def setup_sigterm_handler():
    def HandleSigterm(unused1, unused2):
        # raises 'SystemExit' exception, which will allow us to clean up
        sys.exit(1)
    signal.signal(signal.SIGTERM, HandleSigterm)

def _send_mail(mailsender, emails, uname, subject, tb, body_prefix, message):
    body = uname + "\n" + tb
    body += str(body_prefix)
    body += str(message)
    print body
    mailsender.send(
        subject=str(subject),
        message=body,
        to_addresses=emails)

def make_exception_message_handler(
        args, arcyd_reporter, repo, subject, body_prefix):
    uname = str(platform.uname())
    emails = args.sys_admin_emails

    mailsender = phlmail_sender.MailSender(
        phlsys_sendmail.Sendmail(),
        "arcyd@" + platform.node())

    def msg_exception(message):
        tb = traceback.format_exc()
        _send_mail(
            mailsender, emails, uname, subject, tb, body_prefix, message)

        short_tb = traceback.format_exc(1)
        detail = ""
        if repo:
            detail += "processing repo: {repo}\n".format(repo=repo)
        detail += "exception: {exception}".format(exception=short_tb)
        arcyd_reporter.log_system_error('processargs', detail)

    return msg_exception

def make_exception_delay_handler(args, arcyd_reporter, repo):
    return make_exception_message_handler(
        args,
        arcyd_reporter,
        repo,
        "arcyd paused with exception",
        "will wait: ")

def get_retry_delays():
    strToTime = phlsys_strtotime.duration_string_to_time_delta
    retry_delays = [strToTime(d) for d in ["10 minutes", "1 hours"]]
    return retry_delays

def run_once(repo, args, out, arcyd_reporter, conduits, url_watcher):

    reporter = abdt_reporeporter.RepoReporter(
        arcyd_reporter,
        repo,
        args.repo_desc,
        abdt_shareddictoutput.ToFile(args.try_touch_path),
        abdt_shareddictoutput.ToFile(args.ok_touch_path))

    with arcyd_reporter.tag_timer_context('process args'):
        with contextlib.closing(reporter):
            _run_once(
                args, out, reporter, arcyd_reporter, conduits, url_watcher)

def _set_attrib_if_not_none(config, key, value):
    if value:
        getattr(config, key)  # raise if 'key' doesn't exist already
        setattr(config, key, value)

def _make_config_from_args(args):
    config = abdt_repoconfig.Data()
    if args.admin_email:
        config.admin_emails = [args.admin_email]
    _set_attrib_if_not_none(
        config, 'description', args.repo_desc)
    _set_attrib_if_not_none(
        config, 'branch_url_format', args.branch_url_format)
    _set_attrib_if_not_none(
        config, 'review_url_format', args.review_url_format)
    return config

def _determine_config(args, sys_clone):
    # combine all the available configs
    default_config = abdt_repoconfig.make_default_data()
    args_config = _make_config_from_args(args)
    repo_config = abdt_repoconfig.data_from_repo_or_none(sys_clone)
    config = abdt_repoconfig.merge_data_objects(
        default_config, args_config, repo_config)
    abdt_repoconfig.validate_data(config)
    return config

def _run_once(args, out, reporter, arcyd_reporter, conduits, url_watcher):

    with arcyd_reporter.tag_timer_context('process branches prolog'):
        sys_clone = phlsys_git.GitClone(args.repo_path)
        config = _determine_config(args, sys_clone)

        did_fetch = _fetch_if_needed(
            url_watcher,
            args.repo_snoop_url,
            sys_clone,
            config.description)

        if did_fetch:
            # determine the config again now that we've fetched the repo
            config = _determine_config(args, sys_clone)

        arcyd_conduit = _connect(conduits, args, arcyd_reporter)

        reporter.set_config(config)

        sender = phlmail_sender.MailSender(
            phlsys_sendmail.Sendmail(), args.arcyd_email)

        # TODO: this should be a URI for users not conduit
        mailer = abdmail_mailer.Mailer(
            sender,
            config.admin_emails,
            config.description,
            args.instance_uri)

        pluginManager = phlsys_pluginmanager.PluginManager(
            args.plugins, args.trusted_plugins)

        out.display("process (" + config.description + "): ")

        branch_url_callable = None
        if config.branch_url_format:
            def make_branch_url(branch_name):
                return config.branch_url_format.format(branch=branch_name)
            branch_url_callable = make_branch_url

        arcyd_reporter.tag_timer_decorate_object_methods_individually(
            sys_clone, 'base_git')
        arcyd_clone = abdt_git.Clone(sys_clone, "origin", config.description)

        branch_naming = abdt_compositenaming.Naming(
            abdt_classicnaming.Naming(),
            abdt_rbranchnaming.Naming())

        arcyd_reporter.tag_timer_decorate_object_methods_individually(
            arcyd_clone, 'git')

        branches = abdt_git.get_managed_branches(
            arcyd_clone,
            config.description,
            branch_naming,
            branch_url_callable)

        for branch in branches:
            arcyd_reporter.tag_timer_decorate_object_methods_individually(
                branch, 'branch')

    try:
        with arcyd_reporter.tag_timer_context('process branches'):
            abdi_processrepo.process_branches(
                branches,
                arcyd_conduit,
                mailer,
                pluginManager,
                reporter)
    except Exception:
        reporter.on_traceback(traceback.format_exc())
        raise

    reporter.on_completed()

def _fetch_if_needed(url_watcher, snoop_url, repo, repo_desc):

    did_fetch = False

    def prune_and_fetch():
        repo.call('remote', 'prune', 'origin')
        repo.call('fetch')

    # fetch only if we need to
    if not snoop_url or url_watcher.has_url_recently_changed(snoop_url):
            abdt_tryloop.tryloop(
                prune_and_fetch, abdt_errident.FETCH_PRUNE, repo_desc)
            did_fetch = True

    return did_fetch

def _connect(conduits, args, arcyd_reporter):

    key = (
        args.instance_uri, args.arcyd_user, args.arcyd_cert, args.https_proxy)
    if key not in conduits:
        # create an array so that the 'connect' closure binds to the 'conduit'
        # variable as we'd expect, otherwise it'll just modify a local variable
        # and this 'conduit' will remain 'None'
        # XXX: we can do better in python 3.x (nonlocal?)
        conduit = [None]

        def connect():
            # nonlocal conduit # XXX: we'll rebind in python 3.x, instead
            conduit[0] = phlsys_conduit.Conduit(
                args.instance_uri,
                args.arcyd_user,
                args.arcyd_cert,
                https_proxy=args.https_proxy)

        with arcyd_reporter.tag_timer_context('conduit connect'):
            abdt_tryloop.tryloop(
                connect, abdt_errident.CONDUIT_CONNECT, args.instance_uri)

        conduit = conduit[0]
        arcyd_reporter.tag_timer_decorate_object_methods_individually(
            conduit, 'base_conduit')
        reviewstate_cache = phlcon_reviewstatecache.ReviewStateCache()
        reviewstate_cache.set_conduit(conduit)
        arcyd_conduit = abdt_conduit.Conduit(conduit, reviewstate_cache)
        arcyd_reporter.tag_timer_decorate_object_methods_individually(
            arcyd_conduit, 'conduit')
        conduits[key] = arcyd_conduit
    else:
        arcyd_conduit = conduits[key]

    return arcyd_conduit

#------------------------------------------------------------------------------
# Copyright (C) 2013-2014 Bloomberg Finance L.P.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
#------------------------------- END-OF-FILE ----------------------------------
<commit_msg>abdi_processargs: refactor, simplify _run_once<commit_after>"""Process the arguments for a single repository and execute."""
# =============================================================================
# CONTENTS
# -----------------------------------------------------------------------------
# abdi_processargs
#
# Public Functions:
#   setup_parser
#   setup_repo_arg_parser
#   configure_sendmail
#   setup_sigterm_handler
#   make_exception_message_handler
#   make_exception_delay_handler
#   get_retry_delays
#   run_once
#
# -----------------------------------------------------------------------------
# (this contents block is generated, edits will be lost)
# =============================================================================

from __future__ import absolute_import

import contextlib
import platform
import signal
import sys
import traceback

import phlcon_reviewstatecache
import phlmail_sender
import phlsys_conduit
import phlsys_git
import phlsys_pluginmanager
import phlsys_sendmail
import phlsys_strtotime

import abdmail_mailer
import abdt_classicnaming
import abdt_compositenaming
import abdt_conduit
import abdt_errident
import abdt_git
import abdt_rbranchnaming
import abdt_repoconfig
import abdt_reporeporter
import abdt_shareddictoutput
import abdt_tryloop

import abdi_processrepo

def setup_parser(parser):
    parser.add_argument(
        '--sys-admin-emails',
        metavar="EMAIL",
        nargs="+",
        type=str,
        required=True,
        help="email addresses to send important system events to")
    parser.add_argument(
        '--sendmail-binary',
        metavar="PROGRAM",
        type=str,
        default="sendmail",
        help="program to send the mail with (e.g. sendmail, catchmail)")
    parser.add_argument(
        '--sendmail-type',
        metavar="TYPE",
        type=str,
        default="sendmail",
        help="type of program to send the mail with (sendmail, catchmail), "
        "this will affect the parameters that Arycd will use.")

def setup_repo_arg_parser(parser):

    parser.add_argument(
        '--instance-uri',
        metavar="URI",
        type=str,
        required=True,
        help="instance to connect to, e.g. http://127.0.0.1")

    parser.add_argument(
        '--arcyd-user',
        metavar="USER",
        type=str,
        required=True,
        help="username for Arcyd to use")

    parser.add_argument(
        '--arcyd-cert',
        metavar="CERT",
        type=str,
        required=True,
        help="Phabricator Conduit API certificate to use, this is the "
        "value that you will find in your user account in Phabricator "
        "at: http://your.server.example/settings/panel/conduit/. "
        "It can also be found in ~/.arcrc.")

    parser.add_argument(
        '--arcyd-email',
        metavar="FROM",
        type=str,
        required=True,
        help="email address for Arcyd to send mails from")

    parser.add_argument(
        '--admin-email',
        metavar="TO",
        type=str,
        required=True,
        help="email address to send important system events to")

    parser.add_argument(
        '--repo-desc',
        metavar="DESC",
        type=str,
        required=True,
        help="description to use in emails")

    parser.add_argument(
        '--repo-path',
        metavar="PATH",
        type=str,
        required=True,
        help="path to the repository on disk")

    parser.add_argument(
        '--repo-snoop-url',
        metavar="URL",
        type=str,
        help="URL to use to snoop the latest contents of the repository, this "
             "is used by Arcyd to more efficiently determine if it needs to "
             "fetch the repository or not.  The efficiency comes from "
             "re-using connections to the same host when querying.  The "
             "contents returned by the URL are expected to change every time "
             "the git repository changes, a good example of a URL to supply "
             "is to the 'info/refs' address if you're serving up the repo "
             "over http or https.  "
             "e.g. 'http://server.test/git/myrepo/info/refs'.")

    parser.add_argument(
        '--https-proxy',
        metavar="PROXY",
        type=str,
        help="proxy to use, if necessary")

    parser.add_argument(
        '--sleep-secs',
        metavar="TIME",
        type=int,
        default=60,
        help="time to wait between fetches")

    parser.add_argument(
        '--review-url-format',
        metavar="STRING",
        type=str,
        help="e.g. 'http://my.phabricator/D{review}'")

    parser.add_argument(
        '--branch-url-format',
        metavar="STRING",
        type=str,
        help="e.g. 'http://my.git/gitweb?p=r.git;a=log;h=refs/heads/{branch}'")

    parser.add_argument(
        '--try-touch-path',
        metavar="PATH",
        type=str,
        required=True,
        help="file to touch when trying to update a repo")

    parser.add_argument(
        '--ok-touch-path',
        metavar="PATH",
        type=str,
        required=True,
        help="file to touch when successfully updated a repo")

    parser.add_argument(
        "--plugins",
        metavar="MODULE_NAME",
        nargs="+",
        type=str,
        default=[],
        required=False,
        help="List the plugins to be loaded. MODULE_NAME must be present "
        "in /testbed/plugins/ directory as this feature is WIP.")

    parser.add_argument(
        "--trusted-plugins",
        metavar="MODULE_NAME",
        nargs="+",
        type=str,
        default=[],
        required=False,
        help="List the trusted plugins to be loaded. MODULE_NAME must be "
        "present in /testbed/plugins/ directory as this feature is WIP."
        "See /testbed/plugins/README.md for detail about trusted-plugins")

def configure_sendmail(args):
    if args.sendmail_binary:
        phlsys_sendmail.Sendmail.set_default_binary(
            args.sendmail_binary)

    if args.sendmail_type:
        phlsys_sendmail.Sendmail.set_default_params_from_type(
            args.sendmail_type)

# XXX: belongs in phlsys somewhere
def setup_sigterm_handler():
    def HandleSigterm(unused1, unused2):
        # raises 'SystemExit' exception, which will allow us to clean up
        sys.exit(1)
    signal.signal(signal.SIGTERM, HandleSigterm)

def _send_mail(mailsender, emails, uname, subject, tb, body_prefix, message):
    body = uname + "\n" + tb
    body += str(body_prefix)
    body += str(message)
    print body
    mailsender.send(
        subject=str(subject),
        message=body,
        to_addresses=emails)

def make_exception_message_handler(
        args, arcyd_reporter, repo, subject, body_prefix):
    uname = str(platform.uname())
    emails = args.sys_admin_emails

    mailsender = phlmail_sender.MailSender(
        phlsys_sendmail.Sendmail(),
        "arcyd@" + platform.node())

    def msg_exception(message):
        tb = traceback.format_exc()
        _send_mail(
            mailsender, emails, uname, subject, tb, body_prefix, message)

        short_tb = traceback.format_exc(1)
        detail = ""
        if repo:
            detail += "processing repo: {repo}\n".format(repo=repo)
        detail += "exception: {exception}".format(exception=short_tb)
        arcyd_reporter.log_system_error('processargs', detail)

    return msg_exception

def make_exception_delay_handler(args, arcyd_reporter, repo):
    return make_exception_message_handler(
        args,
        arcyd_reporter,
        repo,
        "arcyd paused with exception",
        "will wait: ")

def get_retry_delays():
    strToTime = phlsys_strtotime.duration_string_to_time_delta
    retry_delays = [strToTime(d) for d in ["10 minutes", "1 hours"]]
    return retry_delays

def run_once(repo, args, out, arcyd_reporter, conduits, url_watcher):

    reporter = abdt_reporeporter.RepoReporter(
        arcyd_reporter,
        repo,
        args.repo_desc,
        abdt_shareddictoutput.ToFile(args.try_touch_path),
        abdt_shareddictoutput.ToFile(args.ok_touch_path))

    with arcyd_reporter.tag_timer_context('process args'):
        with contextlib.closing(reporter):
            _run_once(
                args, out, reporter, arcyd_reporter, conduits, url_watcher)

def _set_attrib_if_not_none(config, key, value):
    if value:
        getattr(config, key)  # raise if 'key' doesn't exist already
        setattr(config, key, value)

def _make_config_from_args(args):
    config = abdt_repoconfig.Data()
    if args.admin_email:
        config.admin_emails = [args.admin_email]
    _set_attrib_if_not_none(
        config, 'description', args.repo_desc)
    _set_attrib_if_not_none(
        config, 'branch_url_format', args.branch_url_format)
    _set_attrib_if_not_none(
        config, 'review_url_format', args.review_url_format)
    return config

def _determine_config(args, repo):
    # combine all the available configs
    default_config = abdt_repoconfig.make_default_data()
    args_config = _make_config_from_args(args)
    repo_config = abdt_repoconfig.data_from_repo_or_none(repo)
    config = abdt_repoconfig.merge_data_objects(
        default_config, args_config, repo_config)
    abdt_repoconfig.validate_data(config)
    return config

def _run_once(args, out, reporter, arcyd_reporter, conduits, url_watcher):

    with arcyd_reporter.tag_timer_context('process branches prolog'):
        repo = abdt_git.Clone(
            phlsys_git.GitClone(args.repo_path), "origin", args.repo_desc)

        _fetch_if_needed(
            url_watcher,
            args.repo_snoop_url,
            repo,
            args.repo_desc)

        config = _determine_config(args, repo)

        arcyd_conduit = _connect(conduits, args, arcyd_reporter)

        reporter.set_config(config)

        sender = phlmail_sender.MailSender(
            phlsys_sendmail.Sendmail(), args.arcyd_email)

        # TODO: this should be a URI for users not conduit
        mailer = abdmail_mailer.Mailer(
            sender,
            config.admin_emails,
            config.description,
            args.instance_uri)

        pluginManager = phlsys_pluginmanager.PluginManager(
            args.plugins, args.trusted_plugins)

        out.display("process (" + config.description + "): ")

        branch_url_callable = None
        if config.branch_url_format:
            def make_branch_url(branch_name):
                return config.branch_url_format.format(branch=branch_name)
            branch_url_callable = make_branch_url

        branch_naming = abdt_compositenaming.Naming(
            abdt_classicnaming.Naming(),
            abdt_rbranchnaming.Naming())

        arcyd_reporter.tag_timer_decorate_object_methods_individually(
            repo, 'git')

        branches = abdt_git.get_managed_branches(
            repo,
            config.description,
            branch_naming,
            branch_url_callable)

        for branch in branches:
            arcyd_reporter.tag_timer_decorate_object_methods_individually(
                branch, 'branch')

    try:
        with arcyd_reporter.tag_timer_context('process branches'):
            abdi_processrepo.process_branches(
                branches,
                arcyd_conduit,
                mailer,
                pluginManager,
                reporter)
    except Exception:
        reporter.on_traceback(traceback.format_exc())
        raise

    reporter.on_completed()

def _fetch_if_needed(url_watcher, snoop_url, repo, repo_desc):

    did_fetch = False

    def prune_and_fetch():
        repo.call('remote', 'prune', 'origin')
        repo.call('fetch')

    # fetch only if we need to
    if not snoop_url or url_watcher.has_url_recently_changed(snoop_url):
            abdt_tryloop.tryloop(
                prune_and_fetch, abdt_errident.FETCH_PRUNE, repo_desc)
            did_fetch = True

    return did_fetch

def _connect(conduits, args, arcyd_reporter):

    key = (
        args.instance_uri, args.arcyd_user, args.arcyd_cert, args.https_proxy)
    if key not in conduits:
        # create an array so that the 'connect' closure binds to the 'conduit'
        # variable as we'd expect, otherwise it'll just modify a local variable
        # and this 'conduit' will remain 'None'
        # XXX: we can do better in python 3.x (nonlocal?)
        conduit = [None]

        def connect():
            # nonlocal conduit # XXX: we'll rebind in python 3.x, instead
            conduit[0] = phlsys_conduit.Conduit(
                args.instance_uri,
                args.arcyd_user,
                args.arcyd_cert,
                https_proxy=args.https_proxy)

        with arcyd_reporter.tag_timer_context('conduit connect'):
            abdt_tryloop.tryloop(
                connect, abdt_errident.CONDUIT_CONNECT, args.instance_uri)

        conduit = conduit[0]
        arcyd_reporter.tag_timer_decorate_object_methods_individually(
            conduit, 'base_conduit')
        reviewstate_cache = phlcon_reviewstatecache.ReviewStateCache()
        reviewstate_cache.set_conduit(conduit)
        arcyd_conduit = abdt_conduit.Conduit(conduit, reviewstate_cache)
        arcyd_reporter.tag_timer_decorate_object_methods_individually(
            arcyd_conduit, 'conduit')
        conduits[key] = arcyd_conduit
    else:
        arcyd_conduit = conduits[key]

    return arcyd_conduit

#------------------------------------------------------------------------------
# Copyright (C) 2013-2014 Bloomberg Finance L.P.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
#------------------------------- END-OF-FILE ----------------------------------
<|endoftext|>


<commit_before># Tai Sakuma <tai.sakuma@gmail.com>
import os
import shutil
import datetime
import tempfile
import imp
import logging
import tarfile
import gzip
import json
import re

try:
    import cPickle as pickle
except:
    import pickle

import alphatwirl

from alphatwirl.misc.deprecation import _renamed_class_method_option

##__________________________________________________________________||
class WorkingArea(object):
    """A working area for tasks

    Parameters
    ----------
    topdir : str
        a path to a directory in which a new directory will be created
    python_modules :list
        names of Python modules to be shipped to worker nodes
    """

    @_renamed_class_method_option(old='dir', new='topdir')
    def __init__(self, topdir, python_modules=None):

        if python_modules is None:
            python_modules = ()

        self.topdir = topdir
        self.python_modules = python_modules
        self.path = None
        self.last_package_index = -1 # so it starts from 0

        self.executable = 'run.py'
        self.extra_input_files = set()

    def __repr__(self):
        name_value_pairs = (
            ('topdir', self.topdir),
            ('python_modules', self.python_modules),
        )
        return '{}({})'.format(
            self.__class__.__name__,
            ', '.join(['{}={!r}'.format(n, v) for n, v in name_value_pairs]),
        )

    def open(self):
        self.path = self._prepare_dir(self.topdir)
        self._copy_executable(area_path=self.path)
        self._save_logging_levels(area_path=self.path)
        self._put_python_modules(modules=self.python_modules, area_path=self.path)

    def put_package(self, package):

        self.last_package_index += 1
        package_index = self.last_package_index

        package_path = self.package_path(package_index)
        # relative to self.path, e.g., 'task_00009.p.gz'

        package_fullpath = os.path.join(self.path, package_path)
        # e.g., '{path}/tpd_20161129_122841_HnpcmF/task_00009.p.gz'

        with gzip.open(package_fullpath, 'wb') as f:
           pickle.dump(package, f, protocol=pickle.HIGHEST_PROTOCOL)
           f.close()

        result_path = self.result_path(package_index)
        # e.g., 'results/task_00009/result.p.gz'

        result_fullpath = os.path.join(self.path, result_path)
        # e.g., '{path}/tpd_20161129_122841_HnpcmF/results/task_00009/result.p.gz'

        alphatwirl.mkdir_p(result_fullpath)

        return package_index

    def package_path(self, package_index):
        ret = 'task_{:05d}.p.gz'.format(package_index)
        # e.g., 'task_00009.p.gz'

        return ret

    def result_path(self, package_index):
        dirname = 'task_{:05d}'.format(package_index)
        # e.g., 'task_00009'

        ret = os.path.join('results', dirname, 'result.p.gz')
        # e.g., 'results/task_00009/result.p.gz'

        return ret

    def collect_result(self, package_index):

        result_path = self.result_path(package_index)
        # e.g., 'results/task_00009/result.p.gz'

        result_fullpath = os.path.join(self.path, result_path)
        # e.g., '{path}/tpd_20161129_122841_HnpcmF/results/task_00009/result.p.gz'

        try:
           with gzip.open(result_fullpath, 'rb') as f:
              result = pickle.load(f)
        except (IOError, EOFError) as e:
           logger = logging.getLogger(__name__)
           logger.warning(e)
           return None

        return result

    def close(self):
        self.path = None

    def _prepare_dir(self, dir):

        alphatwirl.mkdir_p(dir)

        prefix = 'tpd_{:%Y%m%d_%H%M%S}_'.format(datetime.datetime.now())
        # e.g., 'tpd_20161129_122841_'

        path = tempfile.mkdtemp(prefix=prefix, dir=dir)
        # e.g., '{path}/tpd_20161129_122841_HnpcmF'

        return path

    def _copy_executable(self, area_path):
        thisdir = os.path.dirname(__file__)
        src = os.path.join(thisdir, self.executable)
        shutil.copy(src, area_path)

    def _save_logging_levels(self, area_path):
        logger_names = logging.Logger.manager.loggerDict.keys()
        loglevel_dict = {l: logging.getLogger(l).getEffectiveLevel() for l in logger_names}

        filename = 'logging_levels.json.gz'
        path = os.path.join(area_path, filename)

        json_str = json.dumps(loglevel_dict, indent=4, sort_keys=True)
        json_str = re.sub(r' *\n', '\n', json_str, flags=re.MULTILINE)
        json_str += "\n"
        json_bytes = json_str.encode('utf-8')
        with gzip.open(path, "w") as f:
            f.write(json_bytes)

        self.extra_input_files.add(filename)

    def _put_python_modules(self, modules, area_path):

        if not modules: return

        filename = 'python_modules.tar.gz'
        path = os.path.join(area_path, filename)
        tar = tarfile.open(path, 'w:gz')

        def tar_filter(tarinfo):
            exclude_extensions = ('.pyc', )
            exclude_names = ('.git', )
            if os.path.splitext(tarinfo.name)[1] in exclude_extensions: return None
            if os.path.basename(tarinfo.name) in exclude_names: return None
            return tarinfo

        for module in modules:
            imp_tuple = imp.find_module(module)
            path = imp_tuple[1]
            arcname = os.path.join('python_modules', module + imp_tuple[2][0])
            tar.add(path, arcname=arcname, filter=tar_filter)
        tar.close()

        self.extra_input_files.add(filename)

##__________________________________________________________________||
<commit_msg>update docstrings in WorkingArea<commit_after># Tai Sakuma <tai.sakuma@gmail.com>
import os
import shutil
import datetime
import tempfile
import imp
import logging
import tarfile
import gzip
import json
import re

try:
    import cPickle as pickle
except:
    import pickle

import alphatwirl

from alphatwirl.misc.deprecation import _renamed_class_method_option

##__________________________________________________________________||
class WorkingArea(object):
    """A working area for tasks

    This is an area where pickled tasks, pickled results, archived
    Python modules, etc are placed.

    Parameters
    ----------
    topdir : str
        a path to a directory in which a new directory will be created
    python_modules :list
        names of Python modules to be shipped to worker nodes

    """

    @_renamed_class_method_option(old='dir', new='topdir')
    def __init__(self, topdir, python_modules=None):

        if python_modules is None:
            python_modules = ()

        self.topdir = topdir
        self.python_modules = python_modules
        self.path = None
        self.last_package_index = -1 # so it starts from 0

        self.executable = 'run.py'
        self.extra_input_files = set()

    def __repr__(self):
        name_value_pairs = (
            ('topdir', self.topdir),
            ('python_modules', self.python_modules),
        )
        return '{}({})'.format(
            self.__class__.__name__,
            ', '.join(['{}={!r}'.format(n, v) for n, v in name_value_pairs]),
        )

    def open(self):
        """Open the working area

        Returns
        -------
        None
        """

        self.path = self._prepare_dir(self.topdir)
        self._copy_executable(area_path=self.path)
        self._save_logging_levels(area_path=self.path)
        self._put_python_modules(modules=self.python_modules, area_path=self.path)

    def put_package(self, package):
        """Put a package

        Parameters
        ----------
        package :
            a task package

        Returns
        -------
        int
            A package index

        """

        self.last_package_index += 1
        package_index = self.last_package_index

        package_path = self.package_path(package_index)
        # relative to self.path, e.g., 'task_00009.p.gz'

        package_fullpath = os.path.join(self.path, package_path)
        # e.g., '{path}/tpd_20161129_122841_HnpcmF/task_00009.p.gz'

        with gzip.open(package_fullpath, 'wb') as f:
           pickle.dump(package, f, protocol=pickle.HIGHEST_PROTOCOL)
           f.close()

        result_path = self.result_path(package_index)
        # e.g., 'results/task_00009/result.p.gz'

        result_fullpath = os.path.join(self.path, result_path)
        # e.g., '{path}/tpd_20161129_122841_HnpcmF/results/task_00009/result.p.gz'

        alphatwirl.mkdir_p(result_fullpath)

        return package_index

    def package_path(self, package_index):
        ret = 'task_{:05d}.p.gz'.format(package_index)
        # e.g., 'task_00009.p.gz'

        return ret

    def result_path(self, package_index):
        dirname = 'task_{:05d}'.format(package_index)
        # e.g., 'task_00009'

        ret = os.path.join('results', dirname, 'result.p.gz')
        # e.g., 'results/task_00009/result.p.gz'

        return ret

    def collect_result(self, package_index):
        """Collect the result of a task

        Parameters
        ----------
        package_index :
            a package index

        Returns
        -------
        obj
            The result of the task

        """

        result_path = self.result_path(package_index)
        # e.g., 'results/task_00009/result.p.gz'

        result_fullpath = os.path.join(self.path, result_path)
        # e.g., '{path}/tpd_20161129_122841_HnpcmF/results/task_00009/result.p.gz'

        try:
           with gzip.open(result_fullpath, 'rb') as f:
              result = pickle.load(f)
        except (IOError, EOFError) as e:
           logger = logging.getLogger(__name__)
           logger.warning(e)
           return None

        return result

    def close(self):
        """Close the working area

        Returns
        -------
        None

        """
        self.path = None

    def _prepare_dir(self, dir):

        alphatwirl.mkdir_p(dir)

        prefix = 'tpd_{:%Y%m%d_%H%M%S}_'.format(datetime.datetime.now())
        # e.g., 'tpd_20161129_122841_'

        path = tempfile.mkdtemp(prefix=prefix, dir=dir)
        # e.g., '{path}/tpd_20161129_122841_HnpcmF'

        return path

    def _copy_executable(self, area_path):
        thisdir = os.path.dirname(__file__)
        src = os.path.join(thisdir, self.executable)
        shutil.copy(src, area_path)

    def _save_logging_levels(self, area_path):
        logger_names = logging.Logger.manager.loggerDict.keys()
        loglevel_dict = {l: logging.getLogger(l).getEffectiveLevel() for l in logger_names}

        filename = 'logging_levels.json.gz'
        path = os.path.join(area_path, filename)

        json_str = json.dumps(loglevel_dict, indent=4, sort_keys=True)
        json_str = re.sub(r' *\n', '\n', json_str, flags=re.MULTILINE)
        json_str += "\n"
        json_bytes = json_str.encode('utf-8')
        with gzip.open(path, "w") as f:
            f.write(json_bytes)

        self.extra_input_files.add(filename)

    def _put_python_modules(self, modules, area_path):

        if not modules: return

        filename = 'python_modules.tar.gz'
        path = os.path.join(area_path, filename)
        tar = tarfile.open(path, 'w:gz')

        def tar_filter(tarinfo):
            exclude_extensions = ('.pyc', )
            exclude_names = ('.git', )
            if os.path.splitext(tarinfo.name)[1] in exclude_extensions: return None
            if os.path.basename(tarinfo.name) in exclude_names: return None
            return tarinfo

        for module in modules:
            imp_tuple = imp.find_module(module)
            path = imp_tuple[1]
            arcname = os.path.join('python_modules', module + imp_tuple[2][0])
            tar.add(path, arcname=arcname, filter=tar_filter)
        tar.close()

        self.extra_input_files.add(filename)

##__________________________________________________________________||
<|endoftext|>


<commit_before>import pytest
import pkgutil

# this is the package we are inspecting -- for example 'email' from stdlib
import altair.examples

def iter_submodules(package, include_packages=False):
    """Iterate importable submodules of a module"""
    prefix = package.__name__ + "."
    for importer, modname, ispkg in pkgutil.iter_modules(package.__path__,
                                                         prefix):
        if not include_packages and ispkg:
            continue
        yield __import__(modname, fromlist="dummy")

@pytest.mark.parametrize('example', iter_submodules(altair.examples))
def test_examples_output(example):
    assert example.v.to_dict() == example.expected_output
<commit_msg>TST: raise expected error when no web connectino<commit_after>import pytest
import pkgutil

# this is the package we are inspecting -- for example 'email' from stdlib
import altair.examples

try:
    # Python 3.X
    from urllib.error import URLError
except ImportError:
    # Python 2.X
    from urllib2 import URLError

def iter_submodules(package, include_packages=False):
    """Iterate importable submodules of a module"""
    prefix = package.__name__ + "."
    for importer, modname, ispkg in pkgutil.iter_modules(package.__path__,
                                                         prefix):
        if not include_packages and ispkg:
            continue

        try:
            yield __import__(modname, fromlist="dummy")
        except URLError:
            pytest.xfail("Expected failure: no internet connection")

@pytest.mark.parametrize('example', iter_submodules(altair.examples))
def test_examples_output(example):
    assert example.v.to_dict() == example.expected_output
<|endoftext|>


<commit_before># Copyright 2013 The Servo Project Developers. See the COPYRIGHT
# file at the top-level directory of this distribution.
#
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
# option. This file may not be copied, modified, or distributed
# except according to those terms.

import fileinput
import re
import random

def is_comment(line):
    return re.search(r"\/\/.*", line)

def init_variables(if_blocks):
    random_index = random.randint(0, len(if_blocks) - 1)
    start_counter = 0
    end_counter = 0
    lines_to_delete = []
    line_to_mutate = if_blocks[random_index]
    return random_index, start_counter, end_counter, lines_to_delete, line_to_mutate

def deleteStatements(file_name, line_numbers):
    for line in fileinput.input(file_name, inplace=True):
        if fileinput.lineno() not in line_numbers:
            print line.rstrip()

class Strategy:
    def __init__(self):
        self._strategy_name = ""
        self._replace_strategy = {}

    def mutate(self, file_name):
        line_numbers = []
        for line in fileinput.input(file_name):
            if not is_comment(line) and re.search(self._replace_strategy['regex'], line):
                line_numbers.append(fileinput.lineno())
        if len(line_numbers) == 0:
            return -1
        else:
            mutation_line_number = line_numbers[random.randint(0, len(line_numbers) - 1)]
            for line in fileinput.input(file_name, inplace=True):
                if fileinput.lineno() == mutation_line_number:
                    line = re.sub(self._replace_strategy['regex'], self._replace_strategy['replaceString'], line)
                print line.rstrip()
            return mutation_line_number

class AndOr(Strategy):
    def __init__(self):
        Strategy.__init__(self)
        logical_and = r"(?<=\s)&&(?=\s)"
        self._replace_strategy = {
            'regex': logical_and,
            'replaceString': '||'
        }

class IfTrue(Strategy):
    def __init__(self):
        Strategy.__init__(self)
        if_condition = r"(?<=if\s)(.*)(?=\s\{)"
        self._replace_strategy = {
            'regex': if_condition,
            'replaceString': 'true'
        }

class IfFalse(Strategy):
    def __init__(self):
        Strategy.__init__(self)
        if_condition = r"(?<=if\s)(.*)(?=\s\{)"
        self._replace_strategy = {
            'regex': if_condition,
            'replaceString': 'false'
        }

class ModifyComparision(Strategy):
    def __init__(self):
        Strategy.__init__(self)
        less_than_equals = r"(?<=\s)(\<)\=(?=\s)"
        greater_than_equals = r"(?<=\s)(\<)\=(?=\s)"
        self._replace_strategy = {
            'regex': (less_than_equals + '|' + greater_than_equals),
            'replaceString': r"\1"
        }

class MinusToPlus(Strategy):
    def __init__(self):
        Strategy.__init__(self)
        arithmetic_minus = r"(?<=\s)\-(?=\s.+)"
        minus_in_shorthand = r"(?<=\s)\-(?=\=)"
        self._replace_strategy = {
            'regex': (arithmetic_minus + '|' + minus_in_shorthand),
            'replaceString': '+'
        }

class PlusToMinus(Strategy):
    def __init__(self):
        Strategy.__init__(self)
        arithmetic_plus = r"(?<=[^\"]\s)\+(?=\s[^A-Z\'?\":\{]+)"
        plus_in_shorthand = r"(?<=\s)\+(?=\=)"
        self._replace_strategy = {
            'regex': (arithmetic_plus + '|' + plus_in_shorthand),
            'replaceString': '-'
        }

class AtomicString(Strategy):
    def __init__(self):
        Strategy.__init__(self)
        string_literal = r"(?<=\").+(?=\")"
        self._replace_strategy = {
            'regex': string_literal,
            'replaceString': ' '
        }

class DuplicateLine(Strategy):
    def __init__(self):
        Strategy.__init__(self)
        self._strategy_name = "duplicate"
        append_statement = r".+?append\(.+?\).*?;"
        remove_statement = r".+?remove\(.*?\).*?;"
        push_statement = r".+?push\(.+?\).*?;"
        pop_statement = r".+?pop\(.+?\).*?;"
        plus_equals_statement = r".+?\s\+\=\s.*"
        minus_equals_statement = r".+?\s\-\=\s.*"
        self._replace_strategy = {
            'regex': (append_statement + '|' + remove_statement + '|' + push_statement +
                      '|' + pop_statement + '|' + plus_equals_statement + '|' + minus_equals_statement),
            'replaceString': r"\g<0>\n\g<0>",
        }

class DeleteIfBlock(Strategy):
    def __init__(self):
        Strategy.__init__(self)
        self.if_block = r"^\s+if\s(.+)\s\{"
        self.else_block = r"\selse(.+)\{"

    def mutate(self, file_name):
        code_lines = []
        if_blocks = []
        for line in fileinput.input(file_name):
            code_lines.append(line)
            if re.search(self.if_block, line):
                if_blocks.append(fileinput.lineno())
        if len(if_blocks) == 0:
            return -1
        random_index, start_counter, end_counter, lines_to_delete, line_to_mutate = init_variables(if_blocks)
        while line_to_mutate <= len(code_lines):
            current_line = code_lines[line_to_mutate - 1]
            next_line = code_lines[line_to_mutate]
            if re.search(self.else_block, current_line) is not None \
                    or re.search(self.else_block, next_line) is not None:
                if_blocks.pop(random_index)
                if len(if_blocks) == 0:
                    return -1
                else:
                    random_index, start_counter, end_counter, lines_to_delete, line_to_mutate = \
                        init_variables(if_blocks)
                    continue
            lines_to_delete.append(line_to_mutate)
            for ch in current_line:
                if ch == "{":
                    start_counter += 1
                elif ch == "}":
                    end_counter += 1
                if start_counter and start_counter == end_counter:
                    deleteStatements(file_name, lines_to_delete)
                    return lines_to_delete[0]
            line_to_mutate += 1

def get_strategies():
    return AndOr, IfTrue, IfFalse, ModifyComparision, PlusToMinus, MinusToPlus, \
        AtomicString, DuplicateLine, DeleteIfBlock

class Mutator:
    def __init__(self, strategy):
        self._strategy = strategy

    def mutate(self, file_name):
        return self._strategy.mutate(file_name)
<commit_msg>Do not mutate if let<commit_after># Copyright 2013 The Servo Project Developers. See the COPYRIGHT
# file at the top-level directory of this distribution.
#
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
# option. This file may not be copied, modified, or distributed
# except according to those terms.

import fileinput
import re
import random

def is_comment(line):
    return re.search(r"\/\/.*", line)

def init_variables(if_blocks):
    random_index = random.randint(0, len(if_blocks) - 1)
    start_counter = 0
    end_counter = 0
    lines_to_delete = []
    line_to_mutate = if_blocks[random_index]
    return random_index, start_counter, end_counter, lines_to_delete, line_to_mutate

def deleteStatements(file_name, line_numbers):
    for line in fileinput.input(file_name, inplace=True):
        if fileinput.lineno() not in line_numbers:
            print line.rstrip()

class Strategy:
    def __init__(self):
        self._strategy_name = ""
        self._replace_strategy = {}

    def mutate(self, file_name):
        line_numbers = []
        for line in fileinput.input(file_name):
            if not is_comment(line) and re.search(self._replace_strategy['regex'], line):
                line_numbers.append(fileinput.lineno())
        if len(line_numbers) == 0:
            return -1
        else:
            mutation_line_number = line_numbers[random.randint(0, len(line_numbers) - 1)]
            for line in fileinput.input(file_name, inplace=True):
                if fileinput.lineno() == mutation_line_number:
                    line = re.sub(self._replace_strategy['regex'], self._replace_strategy['replaceString'], line)
                print line.rstrip()
            return mutation_line_number

class AndOr(Strategy):
    def __init__(self):
        Strategy.__init__(self)
        logical_and = r"(?<=\s)&&(?=\s)"
        self._replace_strategy = {
            'regex': logical_and,
            'replaceString': '||'
        }

class IfTrue(Strategy):
    def __init__(self):
        Strategy.__init__(self)
        if_condition = r"(?<=if\s)\s*(?!let\s)(.*)(?=\s\{)"
        self._replace_strategy = {
            'regex': if_condition,
            'replaceString': 'true'
        }

class IfFalse(Strategy):
    def __init__(self):
        Strategy.__init__(self)
        if_condition = r"(?<=if\s)\s*(?!let\s)(.*)(?=\s\{)"
        self._replace_strategy = {
            'regex': if_condition,
            'replaceString': 'false'
        }

class ModifyComparision(Strategy):
    def __init__(self):
        Strategy.__init__(self)
        less_than_equals = r"(?<=\s)(\<)\=(?=\s)"
        greater_than_equals = r"(?<=\s)(\<)\=(?=\s)"
        self._replace_strategy = {
            'regex': (less_than_equals + '|' + greater_than_equals),
            'replaceString': r"\1"
        }

class MinusToPlus(Strategy):
    def __init__(self):
        Strategy.__init__(self)
        arithmetic_minus = r"(?<=\s)\-(?=\s.+)"
        minus_in_shorthand = r"(?<=\s)\-(?=\=)"
        self._replace_strategy = {
            'regex': (arithmetic_minus + '|' + minus_in_shorthand),
            'replaceString': '+'
        }

class PlusToMinus(Strategy):
    def __init__(self):
        Strategy.__init__(self)
        arithmetic_plus = r"(?<=[^\"]\s)\+(?=\s[^A-Z\'?\":\{]+)"
        plus_in_shorthand = r"(?<=\s)\+(?=\=)"
        self._replace_strategy = {
            'regex': (arithmetic_plus + '|' + plus_in_shorthand),
            'replaceString': '-'
        }

class AtomicString(Strategy):
    def __init__(self):
        Strategy.__init__(self)
        string_literal = r"(?<=\").+(?=\")"
        self._replace_strategy = {
            'regex': string_literal,
            'replaceString': ' '
        }

class DuplicateLine(Strategy):
    def __init__(self):
        Strategy.__init__(self)
        self._strategy_name = "duplicate"
        append_statement = r".+?append\(.+?\).*?;"
        remove_statement = r".+?remove\(.*?\).*?;"
        push_statement = r".+?push\(.+?\).*?;"
        pop_statement = r".+?pop\(.+?\).*?;"
        plus_equals_statement = r".+?\s\+\=\s.*"
        minus_equals_statement = r".+?\s\-\=\s.*"
        self._replace_strategy = {
            'regex': (append_statement + '|' + remove_statement + '|' + push_statement +
                      '|' + pop_statement + '|' + plus_equals_statement + '|' + minus_equals_statement),
            'replaceString': r"\g<0>\n\g<0>",
        }

class DeleteIfBlock(Strategy):
    def __init__(self):
        Strategy.__init__(self)
        self.if_block = r"^\s+if\s(.+)\s\{"
        self.else_block = r"\selse(.+)\{"

    def mutate(self, file_name):
        code_lines = []
        if_blocks = []
        for line in fileinput.input(file_name):
            code_lines.append(line)
            if re.search(self.if_block, line):
                if_blocks.append(fileinput.lineno())
        if len(if_blocks) == 0:
            return -1
        random_index, start_counter, end_counter, lines_to_delete, line_to_mutate = init_variables(if_blocks)
        while line_to_mutate <= len(code_lines):
            current_line = code_lines[line_to_mutate - 1]
            next_line = code_lines[line_to_mutate]
            if re.search(self.else_block, current_line) is not None \
                    or re.search(self.else_block, next_line) is not None:
                if_blocks.pop(random_index)
                if len(if_blocks) == 0:
                    return -1
                else:
                    random_index, start_counter, end_counter, lines_to_delete, line_to_mutate = \
                        init_variables(if_blocks)
                    continue
            lines_to_delete.append(line_to_mutate)
            for ch in current_line:
                if ch == "{":
                    start_counter += 1
                elif ch == "}":
                    end_counter += 1
                if start_counter and start_counter == end_counter:
                    deleteStatements(file_name, lines_to_delete)
                    return lines_to_delete[0]
            line_to_mutate += 1

def get_strategies():
    return AndOr, IfTrue, IfFalse, ModifyComparision, PlusToMinus, MinusToPlus, \
        AtomicString, DuplicateLine, DeleteIfBlock

class Mutator:
    def __init__(self, strategy):
        self._strategy = strategy

    def mutate(self, file_name):
        return self._strategy.mutate(file_name)
<|endoftext|>