#! /usr/bin/env python3
# -*- coding: utf-8 -*-

# flo-detect-files-without-final-newline --- Detect files in one or more
#                                            directory trees that don't have
#                                            a final newline
# Copyright (C) 2014  Florent Rougon
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 dated June, 1991.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import sys, os, subprocess, argparse, locale, re


def process_command_line():
    params = argparse.Namespace()

    parser = argparse.ArgumentParser(
        usage="""\
%(prog)s [OPTION ...] DIRECTORY ...
Detect files in one or more directory trees that don't have a final newline.""",
        description="""\
Recurse into every DIRECTORY argument and print the paths of non-empty files
that don't end with byte 10 (in decimal notation; 0A in hex.).""",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        # I want --help but not -h (it might be useful for something else)
        add_help=False)

    parser.add_argument('dirs', metavar="DIRECTORY", nargs='+',
                        help="""directory to recurse into""")
    parser.add_argument('-a', '--all', action="store_const", const=True,
                        default=False, help="""\
      test every file instead of restricting to those that file(1)
      reports as containing text""")
    parser.add_argument('-0', dest="null_separator", action="store_const",
                        const=True, default=False, help="""\
      print a NUL byte after each file path instead of a newline""")
    parser.add_argument('--help', action="help",
                        help="display this message and exit")

    params = parser.parse_args(namespace=params)

    return params


def os_walk_error_handler(exception):
    raise exception


def main():
    global params

    locale.setlocale(locale.LC_ALL, '')
    params = process_command_line()

    textfile_cre = re.compile(r"^text/")
    separator = "\000" if params.null_separator else "\n"

    for root in params.dirs:
        for dirpath, dirnames, filenames in os.walk(
            root, onerror=os_walk_error_handler):
            for fname in filenames:
                fpath = os.path.join(dirpath, fname)
                # Skip zero-sized files
                if os.path.getsize(fpath) == 0:
                    continue
                elif not params.all:
                    file_type = subprocess.check_output(
                        ["file", "--brief", "--mime", fpath],
                        universal_newlines=True)
                    # Skip non-text files
                    if not textfile_cre.match(file_type):
                        continue

                with open(fpath, "rb") as f:
                    contents = f.read()
                    if contents[-1] != 0x0a:
                        print(fpath, end=separator)

    sys.exit(0)

if __name__ == "__main__": main()
