#!/usr/bin/env python3

"""Find files with non-ASCII characters."""

import argparse
import fnmatch
import os
import re
import sys


# Extensions of known binaries.
IGNORE_EXTENSIONS = [
    ".a",
    ".ai",
    ".aps",
    ".bmp",
    ".book",
    ".build",
    ".csproj",
    ".dbf",
    ".dll",
    ".doc",
    ".dox",
    ".eps",
    ".exe",
    ".fm",
    ".fpt",
    ".gdx",
    ".gif",
    ".gz",
    ".ico",
    ".jar",
    ".jpeg",
    ".jpg",
    ".lib",
    ".msi",
    ".pdf",
    ".pfx",
    ".png",
    ".rtx",
    ".sbn",
    ".sbx",
    ".shp",
    ".shx",
    ".tar",
    ".tgz",
    ".ttf",
    ".zip",
    "~"
]


def ignore_file(filename, globs):
    """Determine if a file should be ignored."""

    # Skip known binaries.
    for ext in IGNORE_EXTENSIONS:
        if filename.endswith(ext):
            return True

    if not globs:
        return False

    for glob in globs:
        if fnmatch.fnmatch(filename, glob):
            return False

    return True


def find_files(args):
    """Do the actual finding."""
    # pylint: disable=too-many-branches

    binary_re = re.compile(rb"[^\t\r\n\x20-\x7e]")

    for dirpath, dirnames, filenames in os.walk("."):
        # Skip git metadata directories.
        # Skip Subversion metadata directories.
        # Skip cvs metadata directories.
        # Skip cmake generated directories.
        for name in [".git", ".svn", "CVS", "CMakeFiles"]:
            if name in dirnames:
                dirnames.remove(name)

        for filename in sorted(filenames):
            if not ignore_file(filename, args.filespec):
                # Assemble the filename.
                filename = os.path.join(dirpath, filename)
                if filename.startswith("./"):
                    filename = filename[2:]

                with open(filename, "rb") as fil:
                    if args.files_with_matches:
                        contents = fil.read()
                        if binary_re.search(contents):
                            print(filename)
                    else:
                        nlines = 0
                        for lineno, line in enumerate(fil.readlines()):
                            if binary_re.search(line):
                                nlines += 1
                                if nlines > args.max_lines:
                                    print(
                                        "%s:more than %i lines" % (
                                            filename,
                                            args.max_lines
                                        )
                                    )
                                    break
                                line = line[:-1].decode(
                                    "ascii",
                                    errors="replace"
                                )
                                if len(line) > 80:
                                    line = line[:65] + " [...truncated]"
                                if args.line_number:
                                    print(
                                        "%s:%i:%s" % (
                                            filename,
                                            lineno + 1,
                                            line
                                        )
                                    )
                                else:
                                    print("%s:%s" % (filename, line))


def main():
    """Main."""
    arg_parser = argparse.ArgumentParser(
        description="Find files with non-ASCII characters."
    )
    arg_parser.add_argument(
        "-l",
        "--files-with-matches",
        action="store_true",
        dest="files_with_matches",
        default=False,
        help="print only the filename"
    )
    arg_parser.add_argument(
        "-n",
        "--line-number",
        action="store_true",
        dest="line_number",
        default=False,
        help="prefix each line of output with the line number"
    )
    arg_parser.add_argument(
        "--max-lines",
        metavar="N",
        default=10,
        help="max number of lines to print per file (default=%(default)i)"
    )
    arg_parser.add_argument(
        "filespec",
        nargs="*",
        help="glob filespec (default is all files)"
    )
    args = arg_parser.parse_args()

    find_files(args)

    return 0


if __name__ == "__main__":
    sys.exit(main())
