# Path: scripnix/util/command.py
# def common_command_and_options(command_name, add_dry_run=False):
#     """ Decorator that applies all of the common Click command and options in one step:
#             - command with short and long help option switches
#             - dry-run option switch (enabled by default)
#             - version option switch
#     """
#     def command_with_help_switches(fn):
#         """ Specify both the short and long help option switches.
#         """
#         return click.command(context_settings=dict(help_option_names=["-h", "--help"]))(fn)
# 
#     def dry_run_option(fn):
#         """ Add the dry-run option switch.
#         """
#         return click.option("--dry-run", "-D", is_flag=True, help="Show what would happen without actually doing it.")(fn)
# 
#     def version_option(fn):
#         """ Add the display version option switch.
#         """
#         # noinspection PyUnusedLocal
#         def show_version(ctx, param, value):
#             if not value or ctx.resilient_parsing:
#                 return  # pragma: no cover
#             from scripnix import __version__
#             click.echo("The {} command is part of Scripnix version {}.".format(command_name, __version__))
#             click.echo("Copyright 2016 Dave Rogers. Licensed under the GPLv3. See LICENSE.")
#             ctx.exit()
# 
#         return click.option("--version", "-V", is_flag=True, callback=show_version, expose_value=False, is_eager=True,
#                             help="Show version and exit.")(fn)
# 
#     def command_and_options(fn):
#         """ Combine the common command and options into a single function decorator.
#         """
#         if add_dry_run:
#             return version_option(dry_run_option(command_with_help_switches(fn)))
#         else:
#             return version_option(command_with_help_switches(fn))
# 
#     return command_and_options
# 
# Path: scripnix/util/common.py
# def check_root_user(command_name):
#     """ Raise a ClickException if the current user is not root.
#     """
#     if not is_root_user():
#         raise click.ClickException("You must be root to execute this command. Try running it as: sudo {}".format(command_name))
# 
# def config_values(*args):
#     """ Return the values of the given configuration keys in a tuple. Read the configuration settings from the conf.bash and sconf.bash
#         files in order from the 1. Scripnix installation conf/ directory, the system-wide /etc/scripnix/ directory, and the users' own
#         ~/.scripnix/ directory. Later settings override earlier ones. If a key isn't present, return None instead.
#     """
#     config = configparser.ConfigParser()
# 
#     def read_file_configuration(path, file_name, is_required):
#         file_path = os.path.join(path, file_name)
# 
#         try:
#             config.read_file(itertools.chain(['[DEFAULT]'], open(file_path)))
#         except FileNotFoundError:
#             if is_required:
#                 raise click.ClickException("The required configuration file '{}' was not found. "
#                                            "Please re-install Scripnix.".format(file_path))
# 
#     for config_file_name, is_root_required in (("conf.bash", False), ("sconf.bash", True)):
#         if is_root_required and not is_root_user():
#             continue
# 
#         for config_dir, is_file_required in ((BASE_CONFIG_DIR, True), (ROOT_CONFIG_DIR, False), (USER_CONFIG_DIR, False)):
#             read_file_configuration(path=config_dir, file_name=config_file_name, is_required=is_file_required)
# 
#     # Strip out leading and trailing single quotes from the values while converting to a dictionary (with uppercase keys as in the original
#     # configuration files).
#     config_dict = {k.upper(): v.strip("'") for k, v in dict(config["DEFAULT"]).items()}
#     return tuple([config_dict.get(a.upper(), None) for a in args])
# 
# def natural_sort_key(key):
#     """ Return the given key as an (int, str) tuple with any leading integer digits as the first element, and the trailing part of the
#         string as the second. A key that does not being with a digit will be returned with -1 as the first element.
#     """
#     int_key = ""
# 
#     for ch in key:
#         if ch.isdigit():
#             int_key += ch
#         else:
#             break
# 
#     str_key = key[len(int_key):]
#     return -1 if len(int_key) == 0 else int(int_key), str_key


import collections
import os
import pwd
import re
import stat
import subprocess
import click
from scripnix.util.command import common_command_and_options
from scripnix.util.common import check_root_user, config_values, natural_sort_key

# Structure for a single cron job.
CronJob = collections.namedtuple("CronJob", "minute hour day_of_the_month month day_of_the_week user command")

CRONTAB_HEADER = CronJob("m", "h", "dom", "mon", "dow", "user", "command")


def file_paths_in_dir(dir_path, only_executable):
    """ Return the list of executable files (with complete paths) found directly under the given directory path.
    """
    file_paths = []

    if os.path.isdir(dir_path):
        for file_name in os.listdir(dir_path):
            file_path = os.path.join(dir_path, file_name)

            if not only_executable or (only_executable and stat.S_IXUSR & os.stat(file_path)[stat.ST_MODE]):
                file_paths.append(file_path)

    return file_paths


def format_cron_jobs_table(cron_jobs, header, delimiter, do_sort):
    """ Return the given list of CronJob entries formatted as delimited lines of text, with an optional initial header line. Sort the lines
        by the hour and minute elements if do_sort is True.
    """
    # Shallow-copy to avoid propagating in-place changes.
    jobs = cron_jobs[:]

    if do_sort:

# Path: scripnix/util/command.py
# def common_command_and_options(command_name, add_dry_run=False):
#     """ Decorator that applies all of the common Click command and options in one step:
#             - command with short and long help option switches
#             - dry-run option switch (enabled by default)
#             - version option switch
#     """
#     def command_with_help_switches(fn):
#         """ Specify both the short and long help option switches.
#         """
#         return click.command(context_settings=dict(help_option_names=["-h", "--help"]))(fn)
# 
#     def dry_run_option(fn):
#         """ Add the dry-run option switch.
#         """
#         return click.option("--dry-run", "-D", is_flag=True, help="Show what would happen without actually doing it.")(fn)
# 
#     def version_option(fn):
#         """ Add the display version option switch.
#         """
#         # noinspection PyUnusedLocal
#         def show_version(ctx, param, value):
#             if not value or ctx.resilient_parsing:
#                 return  # pragma: no cover
#             from scripnix import __version__
#             click.echo("The {} command is part of Scripnix version {}.".format(command_name, __version__))
#             click.echo("Copyright 2016 Dave Rogers. Licensed under the GPLv3. See LICENSE.")
#             ctx.exit()
# 
#         return click.option("--version", "-V", is_flag=True, callback=show_version, expose_value=False, is_eager=True,
#                             help="Show version and exit.")(fn)
# 
#     def command_and_options(fn):
#         """ Combine the common command and options into a single function decorator.
#         """
#         if add_dry_run:
#             return version_option(dry_run_option(command_with_help_switches(fn)))
#         else:
#             return version_option(command_with_help_switches(fn))
# 
#     return command_and_options
# 
# Path: scripnix/util/common.py
# def check_root_user(command_name):
#     """ Raise a ClickException if the current user is not root.
#     """
#     if not is_root_user():
#         raise click.ClickException("You must be root to execute this command. Try running it as: sudo {}".format(command_name))
# 
# def config_values(*args):
#     """ Return the values of the given configuration keys in a tuple. Read the configuration settings from the conf.bash and sconf.bash
#         files in order from the 1. Scripnix installation conf/ directory, the system-wide /etc/scripnix/ directory, and the users' own
#         ~/.scripnix/ directory. Later settings override earlier ones. If a key isn't present, return None instead.
#     """
#     config = configparser.ConfigParser()
# 
#     def read_file_configuration(path, file_name, is_required):
#         file_path = os.path.join(path, file_name)
# 
#         try:
#             config.read_file(itertools.chain(['[DEFAULT]'], open(file_path)))
#         except FileNotFoundError:
#             if is_required:
#                 raise click.ClickException("The required configuration file '{}' was not found. "
#                                            "Please re-install Scripnix.".format(file_path))
# 
#     for config_file_name, is_root_required in (("conf.bash", False), ("sconf.bash", True)):
#         if is_root_required and not is_root_user():
#             continue
# 
#         for config_dir, is_file_required in ((BASE_CONFIG_DIR, True), (ROOT_CONFIG_DIR, False), (USER_CONFIG_DIR, False)):
#             read_file_configuration(path=config_dir, file_name=config_file_name, is_required=is_file_required)
# 
#     # Strip out leading and trailing single quotes from the values while converting to a dictionary (with uppercase keys as in the original
#     # configuration files).
#     config_dict = {k.upper(): v.strip("'") for k, v in dict(config["DEFAULT"]).items()}
#     return tuple([config_dict.get(a.upper(), None) for a in args])
# 
# def natural_sort_key(key):
#     """ Return the given key as an (int, str) tuple with any leading integer digits as the first element, and the trailing part of the
#         string as the second. A key that does not being with a digit will be returned with -1 as the first element.
#     """
#     int_key = ""
# 
#     for ch in key:
#         if ch.isdigit():
#             int_key += ch
#         else:
#             break
# 
#     str_key = key[len(int_key):]
#     return -1 if len(int_key) == 0 else int(int_key), str_key


# Path: scripnix/pybin/gather_cron_jobs.py
import collections
import os
import pwd
import re
import stat
import subprocess
import click
from scripnix.util.command import common_command_and_options
from scripnix.util.common import check_root_user, config_values, natural_sort_key


# Structure for a single cron job.
CronJob = collections.namedtuple("CronJob", "minute hour day_of_the_month month day_of_the_week user command")

CRONTAB_HEADER = CronJob("m", "h", "dom", "mon", "dow", "user", "command")


def file_paths_in_dir(dir_path, only_executable):
    """ Return the list of executable files (with complete paths) found directly under the given directory path.
    """
    file_paths = []

    if os.path.isdir(dir_path):
        for file_name in os.listdir(dir_path):
            file_path = os.path.join(dir_path, file_name)

            if not only_executable or (only_executable and stat.S_IXUSR & os.stat(file_path)[stat.ST_MODE]):
                file_paths.append(file_path)

    return file_paths


def format_cron_jobs_table(cron_jobs, header, delimiter, do_sort):
    """ Return the given list of CronJob entries formatted as delimited lines of text, with an optional initial header line. Sort the lines
        by the hour and minute elements if do_sort is True.
    """
    # Shallow-copy to avoid propagating in-place changes.
    jobs = cron_jobs[:]

    if do_sort:

        jobs.sort(key=lambda e: (natural_sort_key(e.hour), natural_sort_key(e.minute), natural_sort_key(e.user)))