# -*- coding: utf-8 -*-
"""
    sphinx.cmdline
    ~~~~~~~~~~~~~~

    sphinx-build command-line handling.

    :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS.
    :license: BSD, see LICENSE for details.
"""
from __future__ import print_function

import sys
import optparse
import traceback
from os import path

from six import text_type, binary_type
from docutils.utils import SystemMessage

from sphinx import __display_version__
from sphinx.errors import SphinxError
from sphinx.application import Sphinx
from sphinx.util import Tee, format_exception_cut_frames, save_traceback
from sphinx.util.console import red, nocolor, color_terminal
from sphinx.util.osutil import abspath, fs_encoding
from sphinx.util.pycompat import terminal_safe


USAGE = """\
Sphinx v%s
Usage: %%prog [options] sourcedir outdir [filenames...]

Filename arguments:
  without -a and without filenames, write new and changed files.
  with -a, write all files.
  with filenames, write these.
""" % __display_version__

EPILOG = """\
For more information, visit <http://sphinx-doc.org/>.
"""


class MyFormatter(optparse.IndentedHelpFormatter):
    def format_usage(self, usage):
        return usage

    def format_help(self, formatter):
        result = []
        if self.description:
            result.append(self.format_description(formatter))
        if self.option_list:
            result.append(self.format_option_help(formatter))
        return "\n".join(result)


def main(argv):
    if not color_terminal():
        nocolor()

    parser = optparse.OptionParser(USAGE, epilog=EPILOG, formatter=MyFormatter())
    parser.add_option('--version', action='store_true', dest='version',
                      help='show version information and exit')

    group = parser.add_option_group('General options')
    group.add_option('-b', metavar='BUILDER', dest='builder', default='html',
                     help='builder to use; default is html')
    group.add_option('-a', action='store_true', dest='force_all',
                     help='write all files; default is to only write new and '
                     'changed files')
    group.add_option('-E', action='store_true', dest='freshenv',
                     help='don\'t use a saved environment, always read '
                     'all files')
    group.add_option('-d', metavar='PATH', default=None, dest='doctreedir',
                     help='path for the cached environment and doctree files '
                     '(default: outdir/.doctrees)')
    group.add_option('-j', metavar='N', default=1, type='int', dest='jobs',
                     help='build in parallel with N processes where possible')
    # this option never gets through to this point (it is intercepted earlier)
    # group.add_option('-M', metavar='BUILDER', dest='make_mode',
    #                 help='"make" mode -- as used by Makefile, like '
    #                 '"sphinx-build -M html"')

    group = parser.add_option_group('Build configuration options')
    group.add_option('-c', metavar='PATH', dest='confdir',
                     help='path where configuration file (conf.py) is located '
                     '(default: same as sourcedir)')
    group.add_option('-C', action='store_true', dest='noconfig',
                     help='use no config file at all, only -D options')
    group.add_option('-D', metavar='setting=value', action='append',
                     dest='define', default=[],
                     help='override a setting in configuration file')
    group.add_option('-A', metavar='name=value', action='append',
                     dest='htmldefine', default=[],
                     help='pass a value into HTML templates')
    group.add_option('-t', metavar='TAG', action='append',
                     dest='tags', default=[],
                     help='define tag: include "only" blocks with TAG')
    group.add_option('-n', action='store_true', dest='nitpicky',
                     help='nit-picky mode, warn about all missing references')

    group = parser.add_option_group('Console output options')
    group.add_option('-v', action='count', dest='verbosity', default=0,
                     help='increase verbosity (can be repeated)')
    group.add_option('-q', action='store_true', dest='quiet',
                     help='no output on stdout, just warnings on stderr')
    group.add_option('-Q', action='store_true', dest='really_quiet',
                     help='no output at all, not even warnings')
    group.add_option('-N', action='store_true', dest='nocolor',
                     help='do not emit colored output')
    group.add_option('-w', metavar='FILE', dest='warnfile',
                     help='write warnings (and errors) to given file')
    group.add_option('-W', action='store_true', dest='warningiserror',
                     help='turn warnings into errors')
    group.add_option('-T', action='store_true', dest='traceback',
                     help='show full traceback on exception')
    group.add_option('-P', action='store_true', dest='pdb',
                     help='run Pdb on exception')

    # parse options
    try:
        opts, args = parser.parse_args(list(argv[1:]))
    except SystemExit as err:
        return err.code

    # handle basic options
    if opts.version:
        print('Sphinx (sphinx-build) %s' % __display_version__)
        return 0

    # get paths (first and second positional argument)
    try:
        srcdir = abspath(args[0])
        confdir = abspath(opts.confdir or srcdir)
        if opts.noconfig:
            confdir = None
        if not path.isdir(srcdir):
            print('Error: Cannot find source directory `%s\'.' % srcdir,
                  file=sys.stderr)
            return 1
        if not opts.noconfig and not path.isfile(path.join(confdir, 'conf.py')):
            print('Error: Config directory doesn\'t contain a conf.py file.',
                  file=sys.stderr)
            return 1
        outdir = abspath(args[1])
        if srcdir == outdir:
            print('Error: source directory and destination directory are same.',
                  file=sys.stderr)
            return 1
    except IndexError:
        parser.print_help()
        return 1
    except UnicodeError:
        print(
            'Error: Multibyte filename not supported on this filesystem '
            'encoding (%r).' % fs_encoding, file=sys.stderr)
        return 1

    # handle remaining filename arguments
    filenames = args[2:]
    err = 0
    for filename in filenames:
        if not path.isfile(filename):
            print('Error: Cannot find file %r.' % filename, file=sys.stderr)
            err = 1
    if err:
        return 1

    # likely encoding used for command-line arguments
    try:
        locale = __import__('locale')  # due to submodule of the same name
        likely_encoding = locale.getpreferredencoding()
    except Exception:
        likely_encoding = None

    if opts.force_all and filenames:
        print('Error: Cannot combine -a option and filenames.', file=sys.stderr)
        return 1

    if opts.nocolor:
        nocolor()

    doctreedir = abspath(opts.doctreedir or path.join(outdir, '.doctrees'))

    status = sys.stdout
    warning = sys.stderr
    error = sys.stderr

    if opts.quiet:
        status = None
    if opts.really_quiet:
        status = warning = None
    if warning and opts.warnfile:
        try:
            warnfp = open(opts.warnfile, 'w')
        except Exception as exc:
            print('Error: Cannot open warning file %r: %s' %
                  (opts.warnfile, exc), file=sys.stderr)
            sys.exit(1)
        warning = Tee(warning, warnfp)
        error = warning

    confoverrides = {}
    for val in opts.define:
        try:
            key, val = val.split('=')
        except ValueError:
            print('Error: -D option argument must be in the form name=value.',
                  file=sys.stderr)
            return 1
        if likely_encoding and isinstance(val, binary_type):
            try:
                val = val.decode(likely_encoding)
            except UnicodeError:
                pass
        confoverrides[key] = val

    for val in opts.htmldefine:
        try:
            key, val = val.split('=')
        except ValueError:
            print('Error: -A option argument must be in the form name=value.',
                  file=sys.stderr)
            return 1
        try:
            val = int(val)
        except ValueError:
            if likely_encoding and isinstance(val, binary_type):
                try:
                    val = val.decode(likely_encoding)
                except UnicodeError:
                    pass
        confoverrides['html_context.%s' % key] = val

    if opts.nitpicky:
        confoverrides['nitpicky'] = True

    app = None
    try:
        app = Sphinx(srcdir, confdir, outdir, doctreedir, opts.builder,
                     confoverrides, status, warning, opts.freshenv,
                     opts.warningiserror, opts.tags, opts.verbosity, opts.jobs)
        app.build(opts.force_all, filenames)
        return app.statuscode
    except (Exception, KeyboardInterrupt) as err:
        if opts.pdb:
            import pdb
            print(red('Exception occurred while building, starting debugger:'),
                  file=error)
            traceback.print_exc()
            pdb.post_mortem(sys.exc_info()[2])
        else:
            print(file=error)
            if opts.verbosity or opts.traceback:
                traceback.print_exc(None, error)
                print(file=error)
            if isinstance(err, KeyboardInterrupt):
                print('interrupted!', file=error)
            elif isinstance(err, SystemMessage):
                print(red('reST markup error:'), file=error)
                print(terminal_safe(err.args[0]), file=error)
            elif isinstance(err, SphinxError):
                print(red('%s:' % err.category), file=error)
                print(terminal_safe(text_type(err)), file=error)
            elif isinstance(err, UnicodeError):
                print(red('Encoding error:'), file=error)
                print(terminal_safe(text_type(err)), file=error)
                tbpath = save_traceback(app)
                print(red('The full traceback has been saved in %s, if you want '
                          'to report the issue to the developers.' % tbpath),
                      file=error)
            elif isinstance(err, RuntimeError) and 'recursion depth' in str(err):
                print(red('Recursion error:'), file=error)
                print(terminal_safe(text_type(err)), file=error)
                print(file=error)
                print('This can happen with very large or deeply nested source '
                      'files.  You can carefully increase the default Python '
                      'recursion limit of 1000 in conf.py with e.g.:', file=error)
                print('    import sys; sys.setrecursionlimit(1500)', file=error)
            else:
                print(red('Exception occurred:'), file=error)
                print(format_exception_cut_frames().rstrip(), file=error)
                tbpath = save_traceback(app)
                print(red('The full traceback has been saved in %s, if you '
                          'want to report the issue to the developers.' % tbpath),
                      file=error)
                print('Please also report this if it was a user error, so '
                      'that a better error message can be provided next time.',
                      file=error)
                print('A bug report can be filed in the tracker at '
                      '<https://github.com/sphinx-doc/sphinx/issues>. Thanks!',
                      file=error)
            return 1
