import argparse
import configargparse
import os
import sys
import json
import logfile
import traceback
import StringIO
import contextlib
import __builtin__
from pyhocon import ConfigFactory

class ArgumentParser(configargparse.Parser):
    def convert_setting_to_command_line_arg(self, action, key, value):
        args = []
        if action is None:
            command_line_key = \
            command_line_key = action.option_strings[-1]

        if isinstance(action, argparse._StoreTrueAction):
            if value is True:
        elif isinstance(action, argparse._StoreFalseAction):
            if value is False:
        elif isinstance(action, argparse._StoreConstAction):
            if value == action.const:
        elif isinstance(action, argparse._CountAction):
            for _ in range(value):
        elif action is not None and value == action.default:
        elif isinstance(value, list):
            args.extend([str(e) for e in value])
        return args

class HoconConfigFileParser(object):
    def parse(self, stream):
            basedir = os.path.dirname(
        except AttributeError:
            basedir = os.getcwd()
        return dict(ConfigFactory.parse_string(, basedir=basedir))

    def serialize(self, items):
        return json.dumps(items, sort_keys=True, indent=2, separators=(',', ': '))

    def get_syntax_description(self):
        return ('Config files should use HOCON syntax. HOCON is a superset of '
                'JSON; for more, see '

_options_parser = ArgumentParser(conflict_handler='resolve', add_help=False,
_options_parser.add_argument('--run_dir', '-R', type=str, default=None,
                             help='The directory in which to write log files, parameters, etc. '
                                  'Will be created if it does not exist. If None, output files '
                                  'will not be written.')
_options_parser.add_argument('--config', '-C', default=None, is_config_file=True,
                             help='Path to a JSON or HOCON file containing option settings. '
                                  'Can be loaded from the config.json of a previous run to rerun '
                                  'an experiment. If None, only options given as command line '
                                  'arguments will be used.')
_options_parser.add_argument('--overwrite', '-O', action='store_true',
                             help='If True, allow overwriting the contents of the run directory. '
                                  'Otherwise, an error will be raised if the run directory '
                                  'contains a config.json to prevent accidental overwriting. ')

def get_options_parser():
    return _options_parser

_options = None

def options(allow_partial=False, read=False):
    Get the object containing the values of the parsed command line options.

    :param bool allow_partial: If `True`, ignore unrecognized arguments and allow
        the options to be re-parsed next time `options` is called. This
        also suppresses overwrite checking (the check is performed the first
        time `options` is called with `allow_partial=False`).
    :param bool read: If `True`, do not create or overwrite a `config.json`
        file, and do not check whether such file already exists. Use for scripts
        that read from the run directory rather than/in addition to writing to it.

    :return argparse.Namespace: An object storing the values of the options specified
        to the parser returned by `get_options_parser()`.
    global _options

    if allow_partial:
        opts, extras = _options_parser.parse_known_args()
        if opts.run_dir:
        return opts

    if _options is None:
        # Add back in the help option (only show help and quit once arguments are finalized)
        _options_parser.add_argument('-h', '--help', action='help', default=argparse.SUPPRESS,
                                     help='show this help message and exit')
        _options = _options_parser.parse_args()
        if _options.run_dir:
            mkdirp(_options.run_dir, overwrite=_options.overwrite or read)

        if not read:
            options_dump = vars(_options)
            # People should be able to rerun an experiment with -C config.json safely.
            # Don't include the overwrite option, since using a config from an experiment
            # done with -O should still require passing -O for it to be overwritten again.
            del options_dump['overwrite']
            # And don't write the name of the other config file in this new one! It's
            # probably harmless (config file interpretation can't be chained with the
            # config option), but still confusing.
            del options_dump['config']
            dump_pretty(options_dump, 'config.json')
    return _options

class OverwriteError(Exception):

def mkdirp(dirname, overwrite=True):
    Create a directory at the path given by `dirname`, if it doesn't
    already exist. If `overwrite` is False, raise an error when trying
    to create a directory that already has a config.json file in it.
    Otherwise do nothing if the directory already exists. (Note that an
    existing directory without a config.json will not raise an error
    except OSError:
        if not os.path.isdir(dirname):
        config_path = os.path.join(dirname, 'config.json')
        if not overwrite and os.path.lexists(config_path):
            raise OverwriteError('%s exists and already contains a config.json. To allow '
                                 'overwriting, pass the -O/--overwrite option.' % dirname)

def get_file_path(filename):
    opts = options(allow_partial=True)
    if not opts.run_dir:
        return None
    return os.path.join(opts.run_dir, filename)

def open(filename, *args, **kwargs):
    file_path = get_file_path(filename)
    if not file_path:
        # create a dummy file because we don't have a run dir
        return contextlib.closing(StringIO.StringIO())
    return, *args, **kwargs)

def boolean(arg):
    """Convert a string to a bool treating 'false' and 'no' as False."""
    if arg in ('true', 'True', 'yes', '1', 1):
        return True
    elif arg in ('false', 'False', 'no', '0', 0):
        return False
        raise argparse.ArgumentTypeError(
            'could not interpret "%s" as true or false' % (arg,))

def redirect_output():
    outfile = get_file_path('stdout.log')
    if outfile is None:

def dump(data, filename, lines=False, *args, **kwargs):
        with open(filename, 'w') as outfile:
            if lines:
                for item in data:
                    json.dump(item, outfile, *args, **kwargs)
                json.dump(data, outfile, *args, **kwargs)
    except IOError:
        print >>sys.stderr, 'Unable to write %s' % filename
    except TypeError:
        print >>sys.stderr, 'Unable to write %s' % filename

def dump_pretty(data, filename):
    dump(data, filename,
         sort_keys=True, indent=2, separators=(',', ': '))