Skip to content

read

Full documentation pages are generated for docstring reference only and may contain symbols imported from other modules. Imported symbols are not distinguished from locally defined symbols and will appear in any module that they are imported into. For better information on where symbols should be imported from, review the sourcecode on the github.

FoSpy.parsing.read

SYNTAX module-attribute

SYNTAX = {
    "block_header": {
        "single": {"open": "[", "close": "]"},
        "list": {"open": "[[", "close": "]]"},
    },
    "nested": {"open": "[", "close": "]"},
    "embedded": {
        "open": "{{{",
        "close": "END FOS EMBED }}}",
        "prefix": "#",
    },
    "key_value": {
        "delimiter": ":",
        "require_value": True,
        "prefix": False,
    },
    "comment": {"prefix": "//", "allow_leading_ws": True},
    "calc_comment": {
        "prefix": "!",
        "allow_leading_ws": True,
    },
    "indent_size": 4,
}

_debug module-attribute

_debug = Debug()

md module-attribute

md = {
    "_fos_comments": {},
    "_fos_key_comments": {},
    "_list_type": "",
    "_routine_paths": [],
}

mk module-attribute

mk = {
    "comments": "_fos_comments",
    "key_comments": "_fos_key_comments",
    "list_type": "_list_type",
    "routine_paths": "_routine_paths",
}

Debug

Source code in FoSpy/_debug.py
class Debug:
    def __init__(self):
        self.on = False

        frame = inspect.currentframe().f_back
        self.module_name = frame.f_globals.get("__name__", "<unknown>")
        self.label = f"|(Debug message from {self.module_name})"
        self.label_width = len(self.label)

    def _get_text_width(self, module=None):
        if module:
            label = f"|(Debug message from {module} via {self.module_name})"
            label_width = len(label)
        else:
            label = self.label
            label_width = self.label_width

        text_width = DEBUG_WIDTH - label_width
        return text_width, label, label_width


    def msg(self,msg, module=None):
        if not self.on:
            return

        text_width, label, label_width = self._get_text_width(module)

        wrapped = textwrap.fill(str(msg), width=text_width)

        for line in wrapped.splitlines():
            print(f'{line:<{text_width}}{label:>{label_width}}')

    def pmsg(self,msg,module=None,**kwargs):
        if not self.on:
            return

        text_width, label, label_width = self._get_text_width(module)

        buf = io.StringIO()
        pprint(msg,stream=buf, width=text_width,**kwargs)
        txt = buf.getvalue()
        for line in txt.splitlines():
            print(f'{line:<{text_width}}{label:>{label_width}}')

label instance-attribute

label = f'|(Debug message from {self.module_name})'

label_width instance-attribute

label_width = len(self.label)

module_name instance-attribute

module_name = frame.f_globals.get('__name__', '<unknown>')

on instance-attribute

on = False

__init__

__init__()
Source code in FoSpy/_debug.py
def __init__(self):
    self.on = False

    frame = inspect.currentframe().f_back
    self.module_name = frame.f_globals.get("__name__", "<unknown>")
    self.label = f"|(Debug message from {self.module_name})"
    self.label_width = len(self.label)

_get_text_width

_get_text_width(module=None)
Source code in FoSpy/_debug.py
def _get_text_width(self, module=None):
    if module:
        label = f"|(Debug message from {module} via {self.module_name})"
        label_width = len(label)
    else:
        label = self.label
        label_width = self.label_width

    text_width = DEBUG_WIDTH - label_width
    return text_width, label, label_width

msg

msg(msg, module=None)
Source code in FoSpy/_debug.py
def msg(self,msg, module=None):
    if not self.on:
        return

    text_width, label, label_width = self._get_text_width(module)

    wrapped = textwrap.fill(str(msg), width=text_width)

    for line in wrapped.splitlines():
        print(f'{line:<{text_width}}{label:>{label_width}}')

pmsg

pmsg(msg, module=None, **kwargs)
Source code in FoSpy/_debug.py
def pmsg(self,msg,module=None,**kwargs):
    if not self.on:
        return

    text_width, label, label_width = self._get_text_width(module)

    buf = io.StringIO()
    pprint(msg,stream=buf, width=text_width,**kwargs)
    txt = buf.getvalue()
    for line in txt.splitlines():
        print(f'{line:<{text_width}}{label:>{label_width}}')

create_list_block_dict

create_list_block_dict(lines)
Source code in FoSpy/parsing/read.py
def create_list_block_dict(lines):

    open_br = SYNTAX["nested"]["open"]
    close_br = SYNTAX["nested"]["close"]

    block_list = []
    current_lines = []
    keys = []

    nested = 0
    m = False
    for l in lines:
        if rx.COMMENT_LINE.match(l):
            continue
        m = rx.LOOP_KEY.match(l)
        if m:
            break

    if m: # loop_key mode
        getting_keys = True
        comments = []
        key_comments = {}

        key_idx = 0
        embedding = False
        for line in lines:
            # _debug.msg(f'processing list line: {line}')

            if embedding:
                if rx.EMBEDDED_END.match(line):
                    embedding = False
                current_lines.append(line)
                continue

            is_loop_key = rx.LOOP_KEY.match(line)
            is_key_val = rx.KEY_VALUE.match(line)
            is_comment = rx.COMMENT_LINE.match(line)
            is_embed_start = rx.EMBEDDED_START.match(line)
            if getting_keys and is_comment:
                comments.append(is_comment.group("text").lstrip())
            elif nested>0 or is_comment:
                current_lines.append(line)
            elif is_loop_key:
                if not getting_keys:
                    raise SyntaxError(f"Error on line: '{line}'. You cannot declare more loop keys after starting values.")
                key = is_loop_key.group("key")
                if key in keys:
                    raise ValueError(f"Duplicate key found: '{key}'. Each key can only be specified once at the beginning of a looped list block.")
                key_comments[key.lstrip("-")] = comments
                comments = []
                keys.append(key)
                # _debug.msg(f'added key: {key}')

            elif is_key_val:
                if getting_keys:
                    getting_keys = False
                    for l in comments:
                        current_lines.append(format_comment(l))
                embedding = is_embed_start
                current_lines.append(line)

            else:
                if getting_keys:
                    getting_keys = False
                    for l in comments:
                        current_lines.append(format_comment(l))
                if key_idx == len(keys):
                    block_list.append(create_single_block_dict(current_lines, _list_type="looped"))
                    trailing_comments = []
                    for l in reversed(current_lines):
                        if rx.COMMENT_LINE.match(l):
                            trailing_comments.append(l)
                        else:
                            break

                    trailing_comments.reverse()
                    current_lines = trailing_comments
                    key_idx = 0
                embedding = rx.EMBEDDED_START.match(line)
                key = keys[key_idx]
                current_lines.append(format_key_value(key, line))
                key_idx += 1


            nested += line.count(open_br)
            nested -= line.count(close_br)
            if nested<0:
                raise ValueError("Mismatched brackets when trying to parse blocks.")

        if current_lines:
            # _debug.msg("sending lines to single block:")
            for l in current_lines:
                # _debug.msg(l)
                pass
            # _debug.msg("---")
            block_list.append(create_single_block_dict(current_lines, _list_type="looped"))

        block_list[0][mk["key_comments"]] = key_comments
    else:
        embedding = False
        for line in lines:
            is_key_val, is_comment = rx.KEY_VALUE.match(line), rx.COMMENT_LINE.match(line)
            if embedding or rx.EMBEDDED_START.match(line):
                if rx.EMBEDDED_END.match(line):
                    embedding = False
                elif rx.EMBEDDED_START.match(line):
                    embedding = True
                current_lines.append(line)
                continue

            if not (is_key_val or is_comment or nested>0):
                raise SyntaxError(f"Failed to parse key: value pair from line: '{line}'")
            key = is_key_val.group("key") if is_key_val else None
            if nested > 0:
                current_lines.append(line)
            elif key in keys:
                block_list.append(create_single_block_dict(current_lines))
                trailing_comments = []
                for l in reversed(current_lines):
                    if rx.COMMENT_LINE.match(l):
                        trailing_comments.append(l)
                    else:
                        break

                trailing_comments.reverse()
                current_lines = [*trailing_comments, line]
                keys = [key]
            else:
                if key is not None:
                    keys.append(key)
                #_debug.pmsg(keys)
                current_lines.append(line)
            nested += line.count(open_br)
            nested -= line.count(close_br)
            _debug.msg(f"Current nested: {nested} | Current line: {line}")
            if nested<0:
                raise ValueError("Mismatched brackets when trying to parse blocks.")

        block_list.append(create_single_block_dict(current_lines))
    if nested != 0:
        raise ValueError("Mismatched brackets when trying to parse blocks.")
    return block_list

create_single_block_dict

create_single_block_dict(lines, _list_type='explicit')
Source code in FoSpy/parsing/read.py
def create_single_block_dict(lines, _list_type="explicit"):
    """"""

    """ _debug.msg('Processing the following lines as a single block:')
    for line in lines:
        _debug.msg(line)
    _debug.msg('-----') """
    if len(lines)==0:
        return {}

    # Single string mode, process block as a multiline string instead of key: value pairs
    if lines[0].strip() == ";;;":
        return " ".join([l.strip() for l in lines[1:]]).strip()


    open_br = SYNTAX["nested"]["open"]
    close_br = SYNTAX["nested"]["close"]

    nested_lines = []
    nested_type = None
    out_dict = {mk["list_type"]:_list_type, mk["comments"]:{}}
    nested = 0
    nested_key = None
    comments = []
    embedding = False
    for line in lines:
        # _debug.msg(f'processing: {line}')
        is_comment = rx.COMMENT_LINE.match(line)
        if is_comment:
            if nested > 0:
                nested_lines.append(line)
            else:
                comments.append(is_comment.group("text").lstrip())
        elif embedding:
            if rx.EMBEDDED_END.match(line):
                embedding = False
                out_dict[nested_key] = nested_lines
                nested_lines = []
                nested_key = None
                nested_type = None
                _debug.msg("Ending Embedding")
            else:
                nested_lines.append(line)
        elif nested==0:
            m = rx.KEY_VALUE.match(line)
            if m:
                key, val = m.group("key"), m.group("val")
            else:
                raise SyntaxError(f"Failed to parse key: value pair from line: '{line}'")
            # _debug.msg(f"processed: key:'{key}', val:'{val}'")
            if key.startswith("-"):
                key = key[1:]
                val = format_field("template")
            if key in out_dict:
                raise ValueError(f"Duplicate key found: '{key}' Each key can only appear once within a block.")

            emb = rx.EMBEDDED_START.match(line)
            m = rx.NESTED_START.match(val) or emb
            if val in (empty_nested(True),empty_nested(False)):
                out_dict[key] = []
                out_dict[mk["comments"]][key]=comments
                comments = []

            elif m:
                # _debug.msg(f'{key} is nested')

                nested_key = key
                if rx.EMBEDDED_START.match(line):
                    embedding = True
                    _debug.msg("Starting embedding")

                elif m.group("list"):
                    nested_type = "list"
                    nested += 2

                else:
                    nested_type = "single"
                    nested += 1

                out_dict[mk["comments"]][nested_key] = comments
                comments = []

                remainder = m.group("rest") if not emb else None
                if remainder:
                    nested_lines.append(remainder)
                continue
            else:
                out_dict[key] = val
                out_dict[mk["comments"]][key] = comments
                comments = []
        else:
            nested += line.count(open_br)
            nested -= line.count(close_br)

            if nested < 0:
                raise ValueError("Mismatched brackets when trying to parse blocks.")
            elif nested == 0:
                # _debug.msg(f'ended nesting on line: {line}')
                stripped = line.rstrip(close_br)
                if stripped:
                    nested_lines.append(stripped)
                val = [create_single_block_dict(nested_lines)] if nested_type == "single" else create_list_block_dict(nested_lines)
                if isinstance(val[0], str):
                    val = val[0]
                out_dict[nested_key] = val
                nested_lines = []
                nested_key = None
                nested_type = None
            else:
                nested_lines.append(line)
    for meta_key in mk.values():
        if meta_key not in out_dict:
            out_dict[meta_key] = md[meta_key]

    if nested != 0:
        raise ValueError("Mismatched brackets when trying to parse blocks.")

    return out_dict

dict_from_file

dict_from_file(filepath)
Source code in FoSpy/parsing/read.py
def dict_from_file(filepath):
    blocks = {}
    comments = {}
    current_block = "metadata"
    current_type = "single"
    embedding = False
    break_line = None
    break_num = 0
    with open(filepath, "r", encoding="utf-8") as f:
        endComments = []
        for line in f:
            break_num += 1
            if rx.EMBEDDED_END.match(line):
                embedding = False
                block.append(line)
                continue

            if embedding:
                if rx.EMBEDDED_START.match(line):
                    break_line = line
                    break
                block.append(line)
                continue

            txt = line.strip()     
            if txt == "" or rx.CALC_COMMENT_LINE.match(txt):
                continue

            if rx.EMBEDDED_START.match(txt):
                _debug.msg(f"Starting Embedding on line: {txt}")
                embedding = True

            block = blocks.get(current_block)
            if block is None:
                blocks[current_block] = (current_type,[])
                block = blocks[current_block][1]
            else:
                block = block[1]


            m = rx.BLOCK_HEADER.match(txt)
            if m:
                name_found = False
                for regex_name, typ in zip(("list_name", "single_name"),("list","single")):
                    if m.group(regex_name) is not None:
                        current_block = m.group(regex_name).lower()
                        current_type = typ
                        name_found = True
                        break
                if not name_found:
                    raise SyntaxError(f"Line: '{txt}' was identified as a block header but no header name could be identified")

                #_debug.msg(f"Identified {current_type} block header in line: {txt}")
                #_debug.msg(f"Block name: {current_block}")

                comments[current_block] = [rx.COMMENT_LINE.match(l).group("text").lstrip() for l in endComments]
                endComments = []
                continue

            if rx.COMMENT_LINE.match(txt):
                endComments.append(txt)
                continue

            for l in [*endComments,txt]:
                block.append(l.strip())
            endComments = []
    if embedding:
        raise SyntaxError("An embedded document was never closed before the end of the file or starting a new embed.\n"
                          f"Current Line (line #{break_num}): {"<end of FOS file>\n" if not break_line else break_line}"
                          "Ensure that there are spaces between '#', 'END FOS EMBED', and '}}}' when ending an "
                          "embedded document.")

    for block, (typ, lines) in blocks.items():
        if typ == "single":
            blockDict = create_single_block_dict(lines)
            if blockDict == {}:
                blocks[block] = []
            else:
                blocks[block] = [blockDict]
        elif typ == "list":
            blocks[block] = create_list_block_dict(lines)
        else:
            raise ValueError(f"Unrecognized block type: '{typ}', expected either single or list")
    blocks[mk["comments"]] = comments

    for meta_key in mk.values():
        if meta_key not in blocks:
            try:
                blocks[meta_key] = md[meta_key].copy()
            except:
                blocks[meta_key] = md[meta_key]

    return blocks

empty_nested

empty_nested(is_list)
Source code in FoSpy/parsing/format_fos.py
def empty_nested(is_list):
    spec = SYNTAX["nested"]
    open_br = spec["open"]
    opn = open_br * (2 if is_list else 1)
    close_br = spec["close"]
    close = close_br *(2 if is_list else 1)

    return f"{opn}{close}"

format_comment

format_comment(text, ind=0)
Source code in FoSpy/parsing/format_fos.py
def format_comment(text: str, ind: int = 0):
    spec = SYNTAX["comment"]
    prefix = spec["prefix"]
    return _indent(f"{prefix} {text}", ind)

format_field

format_field(label)
Source code in FoSpy/parsing/format_fos.py
def format_field(label:str):
    return f"<!{label.upper()}-FIELD>"

format_key_value

format_key_value(key, val, ind=0, looped=False)
Source code in FoSpy/parsing/format_fos.py
def format_key_value(key: str, val: str, ind=0, looped=False):
    spec = SYNTAX["key_value"]
    delim = spec["delimiter"]
    prefix = spec.get("prefix")
    if looped:
        return _indent(val, ind)

    if prefix:
        key = f"{prefix}{key}"

    return _indent(f"{key}{delim}{"" if " " in delim else " "}{val}",ind)