def try_parse_text(content: Content) -> Optional[str]: c = content.peek() if c in TOKEN_CHARS: out = StringIO() while True: out.write(c) content.move_next() c = content.peek() if c is None or c not in TOKEN_CHARS: break return out.getvalue() elif c == TOKEN_DELIMITER_CHAR: content.move_next() out = StringIO() while True: c = content.peek() if c is None: raise Exception(f'Expected {TOKEN_DELIMITER_CHAR}') elif c == TOKEN_DELIMITER_CHAR: content.move_next() break elif c == TOKEN_ESCAPE_CHAR: content.move_next() c = content.peek() if c is None: raise Exception('Expected escaped char.') elif c in [TOKEN_ESCAPE_CHAR, TOKEN_DELIMITER_CHAR]: out.write(c) content.move_next() else: # TODO add unicode support raise Exception(f'Invalid escaped char: {c}') else: content.move_next() out.write(c) return out.getvalue() return None
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
def skip_void(content: Content): while True: c = content.peek() if c is None: break elif c in WHITESPACE_CHARS: content.move_next() else: break
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
def try_parse_item(content: Content, allow_entry_separator=True) -> Optional[Value]: c = content.peek() if c == EMPTY_CHAR: content.move_next() return Empty() group = try_parse_group(content) if group is not None: return group return try_parse_token_or_entry(content, allow_entry_separator)
def parse_values(content: Content) -> List[Value]: items = [] can_be_none = True while True: loc0 = content.get_location() value = try_parse_item(content) if value is None: if can_be_none: content.go_back(loc0) break else: raise Exception('Expected to read a group item') items.append(value) loc0 = content.get_location() skip_void(content) c = content.peek() if c == GROUP_SEPARATOR_CHAR: content.move_next() skip_void(content) can_be_none = False else: content.go_back(loc0) break return items
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)
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
def parse_table(ctx: CTX, mark: str, location: Location, content: Content) -> int: if mark == header_row_block_mark: header = True reuse_row = False elif mark == normal_row_block_mark: header = False reuse_row = False elif mark == cell_block_mark: header = False reuse_row = True else: return PASS table = ctx.composer.get_last_component() if not isinstance(table, Table): table = Table(location) ctx.composer.add(table) row = table.get_last_row() if reuse_row else None if row is None: row = TableRow(location, header) table.rows.append(row) # TODO is this ok? skip_void(content) indentation0 = content.column indentation = indentation0 while True: with ctx.using_stop_mark(cell_block_mark): cell = capture_component(ctx, indentation, True) row.cells.append(cell) if not ctx.reader.active(): break content = ctx.reader.get_content() loc0 = content.get_location() # Consume indentation when it is the beginning of the line if content.column == 0: if content.read_spaces(indentation0) < indentation0: content.go_back(loc0) break if content.peek() == cell_block_mark: content.move_next() content.read_spaces() indentation = content.column else: break return CONSUMED