Ejemplo n.º 1
0
def parse_inline_link(ctx: CTX, mark: str, location: Location,
                      content: Content, indentation: int) -> int:
    if mark != link_text_begin_mark:
        return PASS

    with ctx.using_stop_mark(link_text_end_mark):
        # It uses the original indentation
        #   so the paragraph can be continued.
        contents = parse_inline(ctx, content.get_location(), content,
                                indentation)

    if not content.pull(link_text_end_mark):
        raise StxError(f'Expected mark: {link_text_end_mark}')

    if content.pull(link_ref_begin_mark):
        out = StringIO()

        while not content.pull(link_ref_end_mark):
            c = content.peek()

            if c is None:
                raise StxError(f'Expected {link_ref_end_mark}',
                               content.get_location())

            out.write(c)
            content.move_next()

        reference = out.getvalue()
    else:
        reference = None

    ctx.composer.add(LinkText(location, contents, reference))

    return CONSUMED
Ejemplo n.º 2
0
def generate_styled_text(parent: Tag, styled_text: StyledText):
    if styled_text.style == 'strong':
        tag_name = 'strong'
    elif styled_text.style == 'emphasized':
        tag_name = 'em'
    elif styled_text.style == 'code':
        tag_name = 'code'
    elif styled_text.style == 'deleted':
        tag_name = 'del'
    else:
        tag_name = None

    if tag_name is not None:
        styled_tag = append_component_tag(parent, styled_text, tag_name)

        generate_components(styled_tag, styled_text.contents)
    else:
        if styled_text.style == 'double-quote':
            parent.append_literal('“')
            generate_components(parent, styled_text.contents)
            parent.append_literal('”')
        elif styled_text.style == 'single-quote':
            parent.append_literal('‘')
            generate_components(parent, styled_text.contents)
            parent.append_literal('’')
        else:
            raise StxError(f'Not supported style: {styled_text.style}.',
                           styled_text.location)
Ejemplo n.º 3
0
def parse_directive(ctx: CTX, mark: str, location: Location) -> int:
    if mark != directive_special_mark:
        return PASS

    content = ctx.reader.get_content()
    file_path = content.file_path

    entry = parse_entry(content)

    key = entry.name
    value = entry.value

    if content.column > 0:
        content.expect_end_of_line()

    if key == 'title':
        ctx.document.title = value.to_str()
    elif key == 'author':
        ctx.document.author = value.to_str()
    elif key == 'format':
        ctx.document.format = value.to_str()
    elif key == 'encoding':
        ctx.document.encoding = value.to_str()
    elif key == 'stylesheets':
        ctx.document.stylesheets = value.to_list()
    elif key == 'include':
        process_import(ctx, location, file_path, value)
    elif key == 'output':
        process_output(ctx, location, value)
    elif key == 'grammar':
        process_grammar(ctx, location, value)
    else:
        raise StxError(f'Unsupported directive: {key}')

    return CONSUMED
Ejemplo n.º 4
0
    def add(self, component: Component):
        if not self.attributes_buffer.empty():
            component.apply_attributes(self.attributes_buffer)

            unknown_attr_keys = self.attributes_buffer.unknown_keys()

            if len(unknown_attr_keys) > 0:
                # TODO improve error message
                raise StxError(f'Unknown attributes: {unknown_attr_keys} '
                               f'for {type(component)}.')

            self.attributes_buffer.clear()

        if len(self.pre_captions) > 0:
            if isinstance(component, Table):
                component.caption = self.pre_captions.pop()

                self.components.append(component)
            else:
                figure = Figure(component.location, component,
                                self.pre_captions.pop())

                self.components.append(figure)
        else:
            self.components.append(component)
Ejemplo n.º 5
0
    def expect_char(self, options: List[str]):
        c = self.read_next()

        if c not in options:
            raise StxError(f'Expected any of {options}')

        return c
Ejemplo n.º 6
0
def parse_inline_style(ctx: CTX, mark: str, location: Location,
                       content: Content, indentation: int) -> int:
    if mark == strong_begin_mark:
        end_mark = strong_end_mark
        style = 'strong'
    elif mark == emphasized_begin_mark:
        end_mark = emphasized_end_mark
        style = 'emphasized'
    elif mark == code_begin_mark:
        end_mark = code_end_mark
        style = 'code'
    elif mark == deleted_begin_mark:
        end_mark = deleted_end_mark
        style = 'deleted'
    elif mark == d_quote_begin_mark:
        end_mark = d_quote_end_mark
        style = 'double-quote'
    elif mark == s_quote_begin_mark:
        end_mark = s_quote_end_mark
        style = 'single-quote'
    else:
        return PASS

    with ctx.using_stop_mark(end_mark):
        # It uses the original indentation
        #   so the paragraph can be continued.
        contents = parse_inline(ctx, content.get_location(), content,
                                indentation)

    if not content.pull(end_mark):
        raise StxError(f'Expected mark: {end_mark}')

    ctx.composer.add(StyledText(location, contents, style))

    return CONSUMED
Ejemplo n.º 7
0
def register_type(format_key: str,
                  handler_type: Type[OutputAction],
                  override=False):
    if not override and format_key in _handler_types:
        raise StxError(f'Output handler already registered: {format_key}')

    _handler_types[format_key] = handler_type
Ejemplo n.º 8
0
def check_unknown_options(options: Union[dict, Value], call: FunctionCall):
    if isinstance(options, dict):
        unknown_options = options.keys()

        if len(unknown_options) > 0:
            raise StxError(f'Unknown options for function {see(call.key)}:'
                           f' {see(unknown_options)}.')
    elif isinstance(options, Value):
        options_any = options.to_any()

        if options_any is not None:
            raise StxError(
                f'Unknown options for function {see(call.key)}:'
                f' {see(options_any)}.', call.location)
    else:
        raise StxError(f'Unknown options for function {see(call.key)}.')
Ejemplo n.º 9
0
def parse_literal(ctx: CTX, mark: str, location: Location, content: Content,
                  indentation_before_mark: int) -> int:
    if mark != literal_area_mark:
        return PASS

    content.read_spaces()

    function_location = content.get_location()

    function = try_parse_entry(content)

    content.expect_end_of_line()

    out = StringIO()

    while True:
        line = content.read_line(indentation_before_mark)

        if line is None:
            raise StxError(f'Expected: {mark}', content.get_location())
        elif line.startswith(escape_char):
            line = line[1:]  # remove escape char

            if not line.startswith(mark) and not line.startswith(escape_char):
                raise StxError(f'Invalid escaped sequence, expected:'
                               f' {see(mark)} or {see(escape_char)}.')
        elif line.rstrip() == mark:
            break

        out.write(line)

    text = out.getvalue()

    if function is not None:
        component = FunctionCall(
            function_location,
            inline=False,
            key=function.name,
            options=function.value,
            argument=Literal(location, text),
        )
    else:
        component = Literal(location, text)

    ctx.composer.add(component)

    return CONSUMED
Ejemplo n.º 10
0
def capture(ctx: CTX):
    ctx.document.content = capture_component(ctx,
                                             indentation=0,
                                             breakable=False)

    if ctx.reader.active():
        raise StxError('Unexpected content.', ctx.reader.get_location())

    # TODO improve error messages
    if len(ctx.composer.stack) > 0:
        raise StxError('unclosed components')
    elif not ctx.composer.attributes_buffer.empty():
        raise StxError(
            f'not consumed attributes: {ctx.composer.attributes_buffer}')
    elif len(ctx.composer.pre_captions) > 0:
        raise StxError('not consumed pre captions: ',
                       ctx.composer.pre_captions[0].location)
Ejemplo n.º 11
0
    def expect_end_of_line(self):
        with self.checkout() as trx:
            text = self.read_until(['\n'], consume_last=True)

            if len(text.strip()) > 0:
                trx.cancel()
                raise StxError('Expected end of line.')

            trx.save()
Ejemplo n.º 12
0
def parse_entry(content: Content) -> Entry:
    location = content.get_location()

    entry = try_parse_entry(content)

    if entry is None:
        raise StxError('Expected token or entry', location)

    return entry
Ejemplo n.º 13
0
def make_output_action(document: Document, location: Location,
                       arguments: Value) -> OutputAction:
    format_value = arguments.try_token()

    if format_value is not None:
        actual_format = format_value.to_str()
        actual_target = OutputStdOut()
        actual_theme = None
        actual_options = Empty()
    else:
        args_map = arguments.to_map()

        format_value = args_map.pop('format', None)
        target_value = args_map.pop('target', None)
        theme_value = args_map.pop('theme', None)
        options_value = args_map.pop('options', None)

        if len(args_map) > 0:
            for unknown_key in args_map.keys():
                logger.warning(f'Unknown output argument: {see(unknown_key)}',
                               location)

        if format_value is not None:
            actual_format = format_value.to_str()
        else:
            raise StxError('Expected output format.', location)

        if target_value is not None:
            actual_target = OutputTarget.make(document, target_value.to_str())
        else:
            actual_target = OutputStdOut()

        if theme_value is not None:
            actual_theme = theme_value.to_str()
        else:
            actual_theme = None

        if options_value is not None:
            actual_options = options_value
        else:
            actual_options = Empty()

    handler_type = get_type(actual_format)

    return handler_type(
        document=document,
        location=location,
        format_key=actual_format,
        target=actual_target,
        options=actual_options,
        theme=actual_theme,
    )
Ejemplo n.º 14
0
def process_grammar(ctx: CTX, location: Location, value: Value):
    d = value.to_dict()

    lang = d['lang']
    file = d['file']
    rule = d.get('rule', lang)

    file = resolve_sibling(ctx.document.source_file, file)

    try:
        registry.register_grammar_from_file(lang, file, rule)
    except Exception as e:
        raise StxError(str(e), location) from e
Ejemplo n.º 15
0
    def move_next(self):
        if self.position < self._length:
            new_line = (self._content[self.position] == LF_CHAR)

            self.position += 1

            if new_line:
                self.line += 1
                self.column = 0
            else:
                self.column += 1
        else:
            raise StxError('EOF')
Ejemplo n.º 16
0
def resolve_image(document: Document, call: FunctionCall) -> Component:
    options = utils.make_options_dict(call, 'src')

    src = options.pop('src', None)
    alt = options.pop('alt', None)

    utils.check_unknown_options(options, call)

    if src is None:
        raise StxError(f'Missing `src` parameter in image.', call.location)
    elif not alt:
        logger.warning('Image without `alt`', call.location)

    return Image(call.location, src, alt)
Ejemplo n.º 17
0
def parse_inline(ctx: CTX, location: Location, content: Content,
                 indentation: int) -> List[Component]:
    ctx.composer.push()

    try:
        while not content.halted():
            if content.test(ctx.stop_mark):
                break

            location = content.get_location()

            mark = content.read_mark(inline_marks)

            signal = (parse_inline_function(ctx, mark, location, content)
                      or parse_inline_container(ctx, mark, location, content,
                                                indentation)
                      or parse_inline_style(ctx, mark, location, content,
                                            indentation)
                      or parse_inline_link(ctx, mark, location, content,
                                           indentation)
                      or parse_inline_token(ctx, mark, location, content,
                                            indentation)
                      or parse_inline_text(ctx, mark, location, content,
                                           indentation))

            if signal == PASS:
                raise StxError(f'Not implemented mark: {mark}')
            elif signal == CONSUMED:
                pass
            elif signal == EXIT:
                break
            else:
                raise AssertionError(f'Illegal signal: {signal}')
    except StxError as e:
        raise StxError('Error parsing inline content.', location) from e

    return ctx.composer.pop()
Ejemplo n.º 18
0
def parse_inline_container(ctx: CTX, mark: str, location: Location,
                           content: Content, indentation: int) -> int:
    if mark != container_begin_mark:
        return PASS

    with ctx.using_stop_mark(container_end_mark):
        # It uses the original indentation
        #   so the paragraph can be continued.
        contents = parse_inline(ctx, content.get_location(), content,
                                indentation)

    if not content.pull(container_end_mark):
        raise StxError(f'Expected mark: {container_end_mark}')

    if not content.pull(function_begin_mark):
        raise StxError(f'Expected mark: {function_begin_mark}')

    skip_void(content)

    function_location = content.get_location()

    function = parse_entry(content)

    skip_void(content)

    if not content.pull(function_end_mark):
        raise StxError(f'Expected mark: {function_end_mark}')

    ctx.composer.add(
        FunctionCall(function_location,
                     inline=True,
                     key=function.name,
                     options=function.value,
                     argument=Composite(location, contents)))

    return CONSUMED
Ejemplo n.º 19
0
def resolve_function_call(document: Document, call: FunctionCall):
    # Check if it is already resolved
    if call.result is not None:
        return 0

    processor = registry.get(call.key)

    if processor is None:
        raise StxError(f'Function not found: {see(call.key)}', call.location)

    call.result = processor(document, call)

    if call.result is None:
        raise call.error('Function call did not produce a component.')

    return 1 + resolve_component(document, call.result)
Ejemplo n.º 20
0
def parse_inline_component(ctx: CTX, mark: str, location: Location,
                           content: Content, indentation: int):
    inlines = parse_inline(ctx, location, content, indentation)

    if len(inlines) == 0:
        # TODO return empty component
        raise StxError('Missing content.', location)
    elif len(inlines) == 1 and inlines[0].display_mode == DisplayMode.BLOCK:
        component = inlines[0]
    else:
        display_mode = DisplayMode.compute_display_mode(inlines)

        if display_mode == DisplayMode.BLOCK:
            component = Composite(location, inlines)
        else:
            component = Paragraph(location, inlines)

    ctx.composer.add(component)
Ejemplo n.º 21
0
def make_options_dict(call: FunctionCall, key_for_str: Optional[str] = None):
    options = call.options

    if key_for_str is not None:
        options_str = options.try_str()

        if options_str is not None:
            return {key_for_str: options_str}

    value = options.to_any()

    if value is None:
        return {}
    elif isinstance(value, dict):
        return value

    raise StxError(f'Unsupported arguments for function {call.key}.',
                   call.location)
Ejemplo n.º 22
0
    def push_post_caption(self, caption: Component):
        consumed = False

        for comps in reversed(self.stack):
            if len(comps) > 0:
                component = comps[-1]

                if isinstance(component, Table):
                    component.caption = caption
                else:
                    figure = Figure(component.location, component, caption)

                    comps[-1] = figure

                consumed = True
                break

        if not consumed:
            raise StxError('Expected component for post-caption.')
Ejemplo n.º 23
0
def resolve_embed(document: Document, call: FunctionCall) -> Component:
    options = utils.make_options_dict(call, 'src')

    src = options.pop('src', None)

    utils.check_unknown_options(options, call)

    if src is None:
        raise StxError(f'Missing `src` parameter in embed.', call.location)

    src = resolve_sibling(document.source_file, src)

    logger.info(f'Embedding: {src}')

    with open(src, 'r', encoding='UTF-8') as f:
        text = f.read()

    # TODO add support for more type of files

    return Literal(call.location, text, source=src)
Ejemplo n.º 24
0
def try_parse_group(content: Content):
    c = content.peek()

    if c == GROUP_BEGIN_CHAR:
        content.move_next()

        skip_void(content)

        items = parse_values(content)

        skip_void(content)

        c = content.peek()

        if c != GROUP_END_CHAR:
            raise StxError('Expected group end char', content.get_location())

        content.move_next()

        return Group(items)

    return None
Ejemplo n.º 25
0
def parse_inline_function(ctx: CTX, mark: str, location: Location,
                          content: Content) -> int:
    if mark != function_begin_mark:
        return PASS

    skip_void(content)

    function_location = content.get_location()

    function = parse_entry(content)

    skip_void(content)

    if not content.pull(function_end_mark):
        raise StxError(f'Expected mark: {function_end_mark}')

    ctx.composer.add(
        FunctionCall(function_location,
                     inline=True,
                     key=function.name,
                     options=function.value))

    return CONSUMED
Ejemplo n.º 26
0
def try_parse_token_or_entry(
        content: Content,
        allow_entry_separator=True) -> Union[Token, Entry, None]:
    text = try_parse_text(content)

    if text is None:
        return None

    loc0 = content.get_location()

    skip_void(content)

    c = content.peek()

    if c == ENTRY_SEPARATOR_CHAR and allow_entry_separator:
        content.move_next()

        skip_void(content)

        entry_name = text
        entry_value = try_parse_item(content, allow_entry_separator=False)

        if entry_value is None:
            raise StxError('Expected an entry value', content.get_location())

        return Entry(entry_name, entry_value)

    group = try_parse_group(content)

    if group is not None:
        return Entry(text, group)

    # go before skipping void
    content.go_back(loc0)

    return Token(text)
Ejemplo n.º 27
0
 def error(self, message: str) -> Exception:
     return StxError(message, self.location)
Ejemplo n.º 28
0
def parse_inline_text(ctx: CTX, mark: str, location: Location,
                      content: Content, indentation: int) -> int:
    if mark is not None:
        return PASS

    out = StringIO()

    completed = False

    while content.peek() is not None:
        # Check if the text is broken by an inline or stop mark
        if content.test_any(inline_marks):
            break
        elif content.test(ctx.stop_mark):
            break

        c = content.peek()

        if c == '\n':
            out.write(c)
            content.move_next()

            # Check if the text is completed by an empty line
            if content.consume_empty_line():
                completed = True
                break

            loc0 = content.get_location()

            spaces = content.read_spaces(indentation)

            # Check if the text is completed by indentation change
            if spaces < indentation:
                content.go_back(loc0)
                completed = True
                break

            # Check if the text is completed by a non-inline mark
            if content.test_any(not_inline_marks):
                content.go_back(loc0)
                completed = True
                break
        elif c == escape_char:
            content.move_next()

            escaped_mark = content.pull_any(all_marks)
            if escaped_mark is not None:
                out.write(escaped_mark)
            elif content.pull(ctx.stop_mark):
                out.write(ctx.stop_mark)
            elif content.pull(escape_char):
                out.write(escape_char)
            else:
                raise StxError('invalid escaped char')
        else:
            out.write(c)
            content.move_next()

    text = out.getvalue()

    if text == '':
        return EXIT

    ctx.composer.add(PlainText(location, text))

    if completed:
        return EXIT
    return CONSUMED
Ejemplo n.º 29
0
    def push_attribute(self, key: str, value: Value):
        if key in self.attributes_buffer.keys():
            raise StxError(f'The attribute {see(key)} was already defined.')

        self.attributes_buffer.put(key, value)
Ejemplo n.º 30
0
def generate_function_call(parent: Tag, call: FunctionCall):
    if call.result is None:
        raise StxError(f'Function {call.key} was not processed.',
                       call.location)

    generate_component(parent, call.result)