lexor.command.to — lexor 0.1.2c0 documentation

# Source code for lexor.command.to

    """to

    Execute lexor by transforming a file "to" another language.

    """

    import re
    import os
    import sys
    import textwrap
    import argparse
    from lexor.command import config, error, warn
    from lexor.core.parser import Parser
    from lexor.core.writer import Writer
    from lexor.core.converter import Converter

    DEFAULTS = {
        'parse_lang': 'lexor:_',
        'log': 'lexor:log',
        'lang': 'html[_:_]'
    }

    DESC = """
    Transform the inputfile to another language. To see the available
    languages use the `lang` command.

    examples:

        lexor file.html to markdown[cstyle:wstyle]
        lexor file.html to markdown[cstyle:otherlang.wstyle]
        lexor file.html to html~min,plain,_~
        lexor file.html to html~plain,_~ mk[cstyle:wstyle,cstyle1,cstyle2]

        lexor file.xml to xml~_@tab="****"~

        lexor Makefile to xml --from makefile
        lexor Makefile to xml --from makefile:otherstyle

      Store output to `output.html` and store warnings in `log.html`:
          lexor doc.md > output.html 2> log.html

      Pipe the output from another program:
           cat doc.md | lexor --from markdown

      Write to files without displaying output:
          lexor --quiet --nodisplay --write doc.md

    """

    def split_at(delimiter, text, opens='[<(', closes=']>)', quotes='"\''):
        """Custom function to split at commas. Taken from stackoverflow
        http://stackoverflow.com/a/20599372/788553"""
        result = []
        buff = ""
        level = 0
        is_quoted = False
        for char in text:
            if char in delimiter and level == 0 and not is_quoted:
                result.append(buff)
                buff = ""
            else:
                buff += char
                if char in opens:
                    level += 1
                elif char in closes:
                    level -= 1
                elif char in quotes:
                    is_quoted = not is_quoted
        if not buff == "":
            result.append(buff)
        return result

    def language_style(lang_str):
        """Parses a language string. In particular, the options --from
        and --log. """
        tmp = split_at(':', lang_str.lower())
        if len(tmp) == 2:
            input_lang = tmp[0]
            input_style = style_parameters(tmp[1])
        else:
            index = tmp[0].find('@')
            if index == -1:
                input_lang = tmp[0]
                input_style = style_parameters('_')
            else:
                input_lang = tmp[0][:index]
                input_style = style_parameters(tmp[0][index:])
        if input_lang == '':
            input_lang = '_'
        if input_style['name'] == '':
            input_style['name'] = '_'
        return input_lang, input_style

    def parse_styles(lang_str):
        """Parses a language string. In particular, the options --from
        and --log. """
        tmp = split_at(':', lang_str)
        if len(tmp) == 2:
            cstyle = style_parameters(tmp[0])
            wstyle = style_parameters(tmp[1])
        else:
            cstyle = style_parameters(tmp[0])
            wstyle = style_parameters('_')
        if cstyle['name'] == '':
            cstyle['name'] = '_'
        if wstyle['name'] == '':
            wstyle['name'] = '_'
        return cstyle, wstyle

    def style_parameters(style):
        """Parsers a style name along with its parameters. """
        style = split_at('@', style)
        style_dict = {'name': style[0], 'params': dict()}
        for ele in style[1:]:
            try:
                var, val = ele.split('=')
            except ValueError:
                msg = 'style arguments must be of the form @var=val'
                raise argparse.ArgumentTypeError(msg)
            try:
                if val[0] == '[':
                    val = val[1:-1]
            except IndexError:
                msg = 'To give empty values try @var=[]'
                raise argparse.ArgumentTypeError(msg)
            style_dict['params'][var] = val
        return style_dict

    def input_language(tolang):
        """Parses the tolang argument. """
        type_ = 'w'
        match = re.match("(.+)~(.+)~", tolang)
        if not match:
            type_ = 'c'
            index = tolang.find('[')
            if index == -1:
                if tolang[-1] == ']':
                    msg = 'did you mean to write lang[...]?'
                    raise argparse.ArgumentTypeError(msg)
                lang_name = tolang
                styles = '_'
            else:
                if tolang[-1] != ']':
                    msg = 'must be of the form lang[...] or lang~...~'
                    raise argparse.ArgumentTypeError(msg)
                lang_name = tolang[:index]
                styles = tolang[index+1:-1]
        else:
            lang_name, styles = match.groups()
        if type_ == 'w':
            msg = '`:` is not supported in "%s~%s~"' % (lang_name, styles)
            styles = split_at(',', styles)
            for style in styles:
                if ':' in style:
                    pass
                    #raise argparse.ArgumentTypeError(msg)
            styles = [style_parameters(ele) for ele in styles]
        else:
            styles = [parse_styles(ele) for ele in split_at(',', styles)]
        return type_, lang_name, styles

    def add_parser(subp, fclass):
        """Add a parser to the main subparser. """
        tmpp = subp.add_parser('to',
                               help='transform inputfile to another language',
                               formatter_class=fclass,
                               description=textwrap.dedent(DESC))
        tmpp.add_argument('tolang', metavar='lang', nargs='*',
                          type=input_language,
                          help='language to which it will be converted')
        tmpp.add_argument('--from', type=language_style, metavar='FROM',
                          dest='parse_lang',
                          help='language to be parsed in')
        tmpp.add_argument('--log', type=language_style,
                          help='language in which the logs will be written')
        tmpp.add_argument('--write', '-w', action='store_true',
                          help='write to file')
        tmpp.add_argument('--quiet', '-q', action='store_true',
                          help='suppress warning messages')
        tmpp.add_argument('--nodisplay', '-n', action='store_true',
                          help="suppress output")

    def get_input(input_file, cfg, default='_'):
        """Returns the text to be parsed along with the name assigned to
        that text. The last output is the extension of the file. """
        if input_file is '_':
            return sys.stdin.read(), 'STDIN', 'STDIN', default
        found = False
        if input_file[0] != '/':
            root = cfg['lexor']['root']
            paths = cfg['edit']['path'].split(':')
            for path in paths:
                if path[0] in ['/', '.']:
                    abspath = '%s/%s' % (path, input_file)
                else:
                    abspath = '%s/%s/%s' % (root, path, input_file)
                if os.path.exists(abspath):
                    found = True
                    break
        else:
            abspath = input_file
            if os.path.exists(abspath):
                found = True
        if not found:
            error("ERROR: The file '%s' does not exist.\n" % input_file)
        text = open(abspath, 'r').read()
        textname = input_file
        path = os.path.realpath(abspath)
        name = os.path.basename(path)
        name = os.path.splitext(name)
        file_name = name[0]
        file_ext = name[1][1:].lower()
        if file_ext == '':
            file_ext = default  # The default language to parse
        return text, textname, file_name, file_ext

    def run():
        """Run the command. """
        arg = config.CONFIG['arg']
        cfg = config.get_cfg(['to', 'edit'])

        text, t_name, f_name, f_ext = get_input(arg.inputfile, cfg)

        parse_lang = cfg['to']['parse_lang']
        if isinstance(parse_lang, str):
            parse_lang = language_style(parse_lang)
        if arg.parse_lang is None and f_ext != '_':
            parse_lang = (f_ext, parse_lang[1])
        in_lang, in_style = parse_lang

        log = cfg['to']['log']
        if isinstance(log, str):
            log = language_style(log)
        else:
            default_log = DEFAULTS['log'].split(':')
            if log[0] == '_':
                log = (default_log[0], log[1])
            if log[1]['name'] == '_':
                log[1]['name'] = default_log[1]

        parser = Parser(in_lang, in_style['name'], in_style['params'])
        log_writer = Writer(log[0], log[1]['name'], log[1]['params'])
        if hasattr(parser.style_module, 'VERSIONS'):
            versions = parser.style_module.VERSIONS
            msg = 'WARNING: No version specified in configuration.\n' \
                  'Using the first module in this list:\n\n  %s\n\n'
            warn(msg % '\n  '.join(versions))
        try:
            parser.parse(text, t_name)
        except ImportError:
            msg = "ERROR: Parsing style not found: [%s:%s]\n"
            error(msg % (in_lang, in_style['name']))
        try:
            write_log(log_writer, parser.log, arg.quiet)
        except ImportError:
            msg = "ERROR: Writing log style not found: [%s:%s]\n"
            error(msg % (parser.log.lang, parser.log.style))
        if not arg.tolang:
            arg.tolang.append(input_language(cfg['to']['lang']))
        convert_and_write(f_name, parser, in_lang, log, arg)

    def convert_and_write(f_name, parser, in_lang, log, arg):
        """Auxiliary function to reduce the number of branches in run. """
        log_writer = Writer(log[0], log[1]['name'], log[1]['params'])
        writer = Writer()
        converter = Converter()
        param = {
            'parser': parser,
            'converter': converter,
            'writer': writer,
            'log_writer': log_writer,
            'f_name': f_name,
            'in_lang': in_lang,
            'arg': arg
        }
        for (action, lang, styles) in arg.tolang:
            param['styles'] = styles
            param['lang'] = lang
            if action == 'c':
                run_converter(param)
            if action == 'w':
                run_writer(param)

    def write_log(writer, log, quiet):
        """Write the log file to stderr. """
        if quiet is False and len(log) > 0:
            writer.write(log, sys.stderr)

    def write_document(writer, doc, fname, arg):
        """Auxiliary function for convert_and_write. """
        if arg.nodisplay is False:
            writer.write(doc, sys.stdout)
        if arg.write is True:
            writer.write(doc, fname)

    def run_converter(param):
        """Auxiliary function for convert and write. """
        lang = param['lang']
        f_name = param['f_name']
        in_lang = param['in_lang']
        arg = param['arg']
        parser = param['parser']
        writer = param['writer']
        log_writer = param['log_writer']
        for style in param['styles']:
            cstyle = style[0]['name']
            param['converter'].set(in_lang, lang, cstyle, style[0]['params'])
            try:
                doc, log = param['converter'].convert(parser.doc)
            except ImportError:
                msg = "ERROR: Converting style not found: [%s ==> %s:%s]\n"
                warn(msg % (in_lang, lang, cstyle))
                continue
            wstyle = style[1]['name']
            if '.' in wstyle:
                (lang, wstyle) = wstyle.split('.')
            if wstyle == '_':
                wstyle = 'default'
            writer.set(lang, wstyle, style[1]['params'])
            fname = '%s.%s.%s' % (f_name, wstyle, lang)
            write_log(log_writer, log, arg.quiet)
            try:
                write_document(writer, doc, fname, arg)
            except ImportError:
                msg = "ERROR: Writing style not found: [%s:%s]\n"
                warn(msg % (lang, wstyle))
                continue

    def run_writer(param):
        """Auxiliary function for convert and write. """
        lang = param['lang']
        f_name = param['f_name']
        arg = param['arg']
        parser = param['parser']
        writer = param['writer']
        for style in param['styles']:
            sname = 'default' if style['name'] == '_' else style['name']
            writer.set(lang, style['name'], style['params'])
            fname = '%s.%s.%s' % (f_name, sname, lang)
            try:
                write_document(writer, parser.doc, fname, arg)
            except ImportError:
                msg = "ERROR: Writing style not found: [%s:%s]\n"
                warn(msg % (lang, style['name']))