webis.py 7.59 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
#!/bin/sh
# -*- coding: utf-8 ; mode: python -*-
"true" '''\'
# try running as python3, if that fails fall back to (any) python
command -v python3 > /dev/null
if [ $? -eq 0 ]; then
    exec env python3 "$0" "$@"
fi
command -v python > /dev/null
if [ $? -eq 0 ]; then
    exec env python "$0" "$@"
else
    echo -e "\033[91m[ERROR] Install Python and try again!" 1>&2
    exit 1
fi
'''
__doc__ = """The main webis command.
Steve Goering's avatar
Steve Goering committed
18

Martin Potthast's avatar
Martin Potthast committed
19
Copyright webis.de 2015-today
20
Authors: Janek Bevendorff, Steve Göring, Martin Potthast, Michael Völske
Steve Goering's avatar
Steve Goering committed
21
"""
Martin Potthast's avatar
Martin Potthast committed
22

23
import argparse
Martin Potthast's avatar
Martin Potthast committed
24
import collections
25
import json
Martin Potthast's avatar
Martin Potthast committed
26 27
import os
import sys
28

Steve Goering's avatar
Steve Goering committed
29 30
import loader

31
from lib import *
Martin Potthast's avatar
Martin Potthast committed
32
from log import *
33 34 35
from system import *


36 37 38 39 40 41 42
def check_commands_alias_definition(config):
    """Checks if the command alias definition has no collisions."""
    aliashisto = {}
    for command in config["commands_aliases"]:
        for alias in config["commands_aliases"][command]:
            aliashisto[alias] = aliashisto.get(alias, 0) + 1

Steve Goering's avatar
Steve Goering committed
43
    if len([x for x in aliashisto if aliashisto[x] > 1]):
44 45 46 47
        lError("Commands alias definition has collisions, please fix it.")
        sys.exit(1)


Martin Potthast's avatar
Martin Potthast committed
48 49 50
def load_config():
    """Returns the configuration, based on the JSON configuration file."""
    try:
51
        config = json.loads(read_file(os.path.dirname(
Martin Potthast's avatar
Martin Potthast committed
52
            os.path.realpath(__file__)) + "/config.json"))
53 54 55 56 57 58 59 60 61 62 63 64 65

        if not os.path.isdir(config["commands_directory"]):
            config["commands_directory"] = os.path.dirname(
                os.path.realpath(__file__)) + "/" + config["commands_directory"]

        if not os.path.isdir(config["commands_directory"]):
            lError(
                "moduls_directory {} is not a valid directory, "
                "check config.json".format(config["commands_directory"]))
            sys.exit(1)

        return config

Martin Potthast's avatar
Martin Potthast committed
66 67 68
    except Exception as e:
        lError("The file config.json is invalid.")
        sys.exit(1)
69

Steve Goering's avatar
Steve Goering committed
70

Martin Potthast's avatar
Martin Potthast committed
71 72 73
def get_commands(config):
    """Returns commands mapped to directories containing their subcommands."""
    commands = collections.OrderedDict()
74 75
    for directory in os.listdir(config["commands_directory"]):
        if (os.path.isdir(config["commands_directory"] + directory) and
Steve Goering's avatar
Steve Goering committed
76
                directory not in config["ignored_dirs"]):
77
            commands[directory] = config["commands_directory"] + directory
Martin Potthast's avatar
Martin Potthast committed
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
    return commands


def get_subcommands(config, commandpath):
    """Returns subcommands of a given command mapped to their script files."""
    subcommands = collections.OrderedDict()
    for script in os.listdir(commandpath):
        subcommandpath = commandpath + "/" + script
        if os.path.isfile(subcommandpath):
            allowed_scripts = config["allowed_scripts"]
            if check_subcommand_shebang(subcommandpath, allowed_scripts):
                subcommand = script.rsplit(".", 1)[0]
                subcommands[subcommand] = subcommandpath
    return subcommands


def get_subcommand_help(subcommandpath):
    """Returns the help message expected at the beginning of a command."""
    if not os.path.isfile(subcommandpath):
        return os.path.basename(subcommandpath)

    f = open(subcommandpath)
100 101 102 103 104 105 106
    lines = f.readlines()
    f.close()
    if len(lines) < 1 or lines[1][0] != "#":
        return ""
    return lines[1][1:].replace("\n", "").strip()


107 108
def print_help_table(config):
    """Prints a table with all commands, subcommands, and their descriptions."""
Johannes Kiesel's avatar
Johannes Kiesel committed
109 110
    print("(sub-)command                  | description                                       ")
    print("------------------------------ | --------------------------------------------------")
111 112 113
    commands = get_commands(config)
    for command in commands:
        command_help = str(config["commands_help"].get(command))
Johannes Kiesel's avatar
Johannes Kiesel committed
114
        print("{:30s} | {}".format("**"+command+"**", "**"+command_help+"**"))
115 116 117
        subcommands = get_subcommands(config, commands[command])
        for subcommand in subcommands:
            subcommand_help = str(get_subcommand_help(subcommands[subcommand]))
118
            print("- {:28s} | {}".format(subcommand, subcommand_help))
119 120


Martin Potthast's avatar
Martin Potthast committed
121 122 123 124 125 126 127
def check_subcommand_shebang(subcommandpath, allowed_scripts):
    """Checks if the shebang of a given script is among the allowed shebangs."""
    shebang = get_shebang(subcommandpath)
    for interpreter in allowed_scripts:
        if interpreter in shebang:
            return True
    return False
Steve Goering's avatar
Steve Goering committed
128

129

Martin Potthast's avatar
Martin Potthast committed
130 131 132
def run_subcommand(subcommand, subcommandpath, params=[]):
    """Runs subcommand passing params and returns its exit code."""
    lDbg("Running " + subcommand + " at " + subcommandpath)
133 134 135
    pp = os.environ.get('PYTHONPATH')
    webis_dir = os.path.dirname(os.path.realpath(__file__))
    os.environ['PYTHONPATH'] = webis_dir if pp is None else pp + ':' + webis_dir
136 137
    if is_verbose_logging():
        os.environ['WEBIS_DEBUG'] = '1'
Martin Potthast's avatar
Martin Potthast committed
138
    cmd = " ".join([subcommandpath] + params)
139 140
    return_value = os.system(cmd)
    if return_value == 0:
141
        lDbg("Done.")
142
    else:
Martin Potthast's avatar
Martin Potthast committed
143
        lError("An error occurred while executing " + cmd)
144
    return return_value
145

146

147 148 149
def translate_alias_to_command(config, alias):
    """Transforms a given alias to the unique command."""
    for command in config["commands_aliases"]:
Steve Goering's avatar
Steve Goering committed
150
        if alias in config["commands_aliases"][command]:
151 152
            return command

153 154

def main(args):
Martin Potthast's avatar
Martin Potthast committed
155
    config = load_config()
156 157
    check_commands_alias_definition(config)

Martin Potthast's avatar
Martin Potthast committed
158 159
    commands = get_commands(config)
    parser = argparse.ArgumentParser()
160
    parser.register('action', 'parsers', AliasedSubParsersAction)
161 162 163
    parser.add_argument('-H', '--helptable',
                        help='Print help of commands and subcommands',
                        action='store_true')
164 165
    parser.add_argument('-v', '--verbose', help='Print debug messages.',
                        action='store_true')
166

Martin Potthast's avatar
Martin Potthast committed
167 168 169 170 171
    subparsers = {}
    allcommands = {}
    commandparsers = parser.add_subparsers(
        metavar='<command>', dest='<command>', help='Available commands:')
    for command in commands:
172 173 174 175 176 177
        help_msg = config["commands_help"].get(
            command,
            colorred(
                "Please add a short description of {} "
                "in config.json[commands_help].".format(command)))

Steve Goering's avatar
Steve Goering committed
178
        commandparser = commandparsers.add_parser(
179 180 181
            command, aliases=config["commands_aliases"].get(command, []),
            help=help_msg)

Martin Potthast's avatar
Martin Potthast committed
182 183 184 185 186 187 188 189 190
        subparsers[command] = commandparser
        subcommandparsers = commandparser.add_subparsers(
            metavar='<subcommand>', dest='<subcommand>',
            help='Available subcommands:')
        subcommands = get_subcommands(config, commands[command])
        allcommands[command] = subcommands
        for subcommand in subcommands:
            subcommandparser = subcommandparsers.add_parser(
                subcommand, help=str(
Steve Goering's avatar
Steve Goering committed
191
                    get_subcommand_help(subcommands[subcommand])),
Martin Potthast's avatar
Martin Potthast committed
192 193 194 195 196
                add_help=False)
            subparsers[subcommand] = subcommandparser

    if len(args) == 0:
        parser.print_help()
197 198
        return 0

Martin Potthast's avatar
Martin Potthast committed
199 200
    parseresults = parser.parse_known_args(args)
    argsdict = vars(parseresults[0])
201
    set_verbose_logging(argsdict['verbose'])
Martin Potthast's avatar
Martin Potthast committed
202
    parameter = parseresults[1]
Steve Goering's avatar
Steve Goering committed
203

204 205 206 207
    if argsdict['helptable']:
        print_help_table(config)
        return 0

Steve Goering's avatar
Steve Goering committed
208
    if argsdict['<command>'] not in subparsers:
Steve Goering's avatar
Steve Goering committed
209 210
        argsdict['<command>'] = translate_alias_to_command(
            config, argsdict['<command>'])
211

Steve Goering's avatar
Steve Goering committed
212
    if len(argsdict) == 2 and argsdict['<subcommand>'] is None:
Steve Goering's avatar
Steve Goering committed
213
        subparsers[argsdict['<command>']].print_help()
Martin Potthast's avatar
Martin Potthast committed
214
        return 0
215

Steve Goering's avatar
Steve Goering committed
216 217 218 219
    return run_subcommand(
        argsdict['<subcommand>'],
        allcommands[argsdict['<command>']][argsdict['<subcommand>']],
        parameter)
220 221 222


if __name__ == "__main__":
Janek Bevendorff's avatar
Janek Bevendorff committed
223
    sys.exit(main(sys.argv[1:]) >> 8)