#! /usr/bin/env python

import sys
import traceback
import getopt

import doit
from doit import main
from doit import cmdparse

# TODO cmd list should be automatically generated.
DOIT_USAGE = """
doit -- automation tool
http://python-doit.sourceforge.net/

Commands:
 doit [run]             run tasks
 doit list              list tasks from dodo file
 doit forget            clear successful run status from DB
 doit dodo-sample       create a sample dodo.py file

 doit help              show help / reference
 doit help <command>    show command usage
"""

## cmd line options
##########################################################
########## run
# print version and exit
opt_version = {'name': 'version',
               'short':'',
               'long': 'version',
               'type': bool,
               'default': False,
               'help': "show version"
               }

# display cmd line usage
opt_help = {'name': 'help',
            'short':'',
            'long': 'help',
            'type': bool,
            'default': False,
            'help':"show help"
            }

# select dodo file containing tasks
opt_dodo = {'name': 'dodoFile',
            'short':'f',
            'long': 'file',
            'type': str,
            'default': 'dodo.py',
            'help':"load task from dodo FILE [default: %(default)s]"
            }

# choose internal dependency file.
opt_depfile = {'name': 'dep_file',
               'short':'',
               'long': '',
               'type': str,
               'default': ".doit.db",
               'help': "file used to save successful runs"
               }

# always execute task
opt_always = {'name': 'always',
              'short': 'a',
              'long': 'always-execute',
              'type': bool,
              'default': False,
              'help': "always execute tasks even if up-to-date [default: "
                      "%(default)s]"
              }

# verbosity
opt_verbosity = {'name':'verbosity',
                 'short':'v',
                 'long':'verbosity',
                 'type':int,
                 'default': 0,
                 'help':
"""0 capture (do not print) stdout/stderr from task.
1 capture stdout only.
2 dont capture anything (print everything immediately).
[default: %(default)s]"""
                 }
run_doc = {'purpose': "run tasks",
           'usage': "[TASK/TARGET...]",
           'description': None}

def cmd_run(params, args):
    """execute cmd run"""

    # special parameters that dont run anything
    if params["version"]:
        print ".".join([str(i) for i in doit.__version__])
        return 0
    if params["help"]:
        print DOIT_USAGE
        return 0


    # check if command is "run". default command is "run"
    if len(args) == 0 or args[0] not in params['sub']:
        dodo_module = main.get_module(params['dodoFile'])
        command_names = params['sub'].keys()
        dodo_tasks = main.load_task_generators(dodo_module, command_names)
        filter_ = args or dodo_tasks['default_tasks']
        return main.doit_run(params['dep_file'], dodo_tasks['task_list'],
                             filter_, params['verbosity'], params['always'])

    # sub cmd different from "run" on cmd line. parse arguments again
    commands = params['sub']
    sub_cmd = args.pop(0)
    return commands[sub_cmd](args, dodoFile=params['dodoFile'], sub=commands)



##########################################################
########## list

list_doc = {'purpose': "list tasks from dodo file",
            'usage': "",
            'description': None}

opt_listall = {'name': 'all',
               'short':'',
               'long': 'all',
               'type': bool,
               'default': False,
               'help': "list include all sub-tasks from dodo file"
               }

# TODO list should support "args" as a filter.
def cmd_list(params, args):
    dodo_module = main.get_module(params['dodoFile'])
    command_names = params['sub'].keys()
    dodo_tasks = main.load_task_generators(dodo_module, command_names)
    return main.doit_list(dodo_tasks['task_list'], params['all'])


##########################################################
########## forget

forget_doc = {'purpose': "clear successful run status from internal DB",
              'usage': "[TASK ...]",
              'description': None}

def cmd_forget(params, args):
    dodo_module = main.get_module(params['dodoFile'])
    command_names = params['sub'].keys()
    dodo_tasks = main.load_task_generators(dodo_module, command_names)
    return main.doit_forget(params['dep_file'], dodo_tasks['task_list'], args)


##########################################################
########## dodo-sample

dodo_sample_description = \
"""Print a commented sample dodo file to stdout.
This file contains an example of all supported features."""

dodo_sample_doc = {'purpose': "create a dodo file from command line",
                   'usage': "",
                   'description': dodo_sample_description}

def cmd_dodo_sample(params, args):
    print """
## this is a quick reference. with small examples. for full instruction check
## the tutorial on the website.
## NOTE this file contains references for files that doesn't exist so you
## can NOT run it!

# name tasks that will be executed by default if nothing specified on
# command line
DEFAULT_TASKS = ['hello', 'py']


### task actions

# python-action
def my_function(foo, bar=0, zeta=0):
    # do whatever here
    #must return True to indicate it was completed successfully
    return True

# command-action
cmd_simple = "echo foo bar"
cmd_two = "echo more"


### tasks - a method that starts with the string "task_"
# it is actually a task generator. tasks are described by
# its: action, dependencies, targets.

def task_hello():
    return {'action': [cmd_simple, cmd_two],
            'dependencies': ['file1.txt'],
            'targets': ['target1.out']
            }


### python actions can also take args & kwargs
def task_py():
    return {'action': my_function,
            'args': [5],
            'kwargs': {'zeta': 6},
            }


### sub-task. create may tasks with same action. must have a name.
MY_FILES = ['file1.txt', 'file2.txt', 'file3.txt']
def task_many():
    for fin in MY_FILES:
        yield {'name': fin,
               'action': "echo %s" % fin}


### dependencies can be another task. use ":" before task name
def task_target_dep():
    return {'action': "echo xxx",
            'dependencies': [':many']
            }



### run task only once if target exist. set dependency "True"
def task_run_once():
    return {'action': "echo xxx > out.txt",
            'dependencies': [True],
            'targets': ['out.txt']
            }


### group tasks. just set action to None. add group as depenencies.
def task_mygroup():
    return {'action': None,
            'dependencies': [':py', ':run_once']
            }


### task setup env. good for functional tests!
# pass an object that opnionally defines methods: "setup" and
# "cleanup". tasks can share the same object.
class SetupSample(object):
    def __init__(self, server):
        self.server = server
        self.executed = 0
        self.cleaned = 0

    def setup(self):
        # start server
        self.executed += 1

    def cleanup(self):
        # stop server
        self.cleaned += 1

setupX = SetupSample('x')
setupY = SetupSample('y')

def task_withenvX():
    for fin in MY_FILES:
        yield {'name': fin,
               'action':'echo x',
               'setup': setupX}

def task_withenvY():
    return {'action':'echo x',
            'setup': setupY}

"""
    return 0




##########################################################
########## help

help_doc = {'purpose': "show help",
            'usage': "",
            'description': None}

def cmd_help(params, args):
    if len(args) == 1 and args[0] in params['sub']:
        print params['sub'][args[0]].help()
    else:
        print DOIT_USAGE
    return 0


##########################################################



def cmd_main():
    subCmd = {} # all sub-commands

    # help command
    subCmd['help'] = cmdparse.Command('help', (), cmd_help, help_doc)

    # run command
    run_options = (opt_version, opt_help, opt_dodo,
                   opt_depfile, opt_always, opt_verbosity)
    subCmd['run'] = cmdparse.Command('run', run_options, cmd_run, run_doc)

    # list command
    list_options = (opt_dodo, opt_listall)
    subCmd['list'] = cmdparse.Command('list', list_options, cmd_list, list_doc)

    # forget command
    forget_options = (opt_depfile,)
    subCmd['forget'] = cmdparse.Command('forget', forget_options,
                                        cmd_forget, forget_doc)

    # dodo-sample command
    subCmd['dodo-sample'] = cmdparse.Command('dodo-sample', () ,
                                             cmd_dodo_sample, dodo_sample_doc)


    try:
        return subCmd['run'](sys.argv[1:], sub=subCmd)

    # wrong command line usage. help user
    except getopt.GetoptError, err:
        print str(err)
        print "see: doit help <cmd>"
        return 1

    # in python 2.4 SystemExit and KeyboardInterrupt subclass
    # from Exception.
    # TODO maybe I should do something to help the user find out who
    # is raising SystemExit. because it shouldnt happen...
    except (SystemExit, KeyboardInterrupt), exp:
        raise

    # dont show traceback for user errors.
    except (doit.main.InvalidDodoFile, doit.main.InvalidCommand,
            doit.task.InvalidTask), err:
        print "ERROR:", str(err)
        return 1

    # make sure exception is printed out. we migth have redirected stderr
    except Exception, e:
        sys.__stderr__.write(traceback.format_exc())
        return 1


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