diff --git a/README.md b/README.md index ba30599..689a769 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,21 @@ Exports the following functions: - `totp(key, time_step, digits=6)`: generate a `digits`-long TOTP code from (bytes-only) key, using `time_step` as the basis (30 seconds is default and the most common) - `get_epoch()`: a helper platform-agnostic function to get the current Unix timestamp in seconds +### `deck.text` + +A library that implements a [DeckText](decktext-spec.md) format parser. Exports a single class, `DeckText`, with the following methods: + +- `doc = DeckText(src=None)` constructor: initialize a DeckText document object from the passed source +- `doc.load(src)`: load DeckText source into the existing document object. + +After the source is loaded, the document object exposes two properties: `title` and `pages`. + +The `title` property is a normal string that represents the document's title (read from the `
', '', ''],
+ 'CODE_END': ['
', '', '', ''],
+ 'STRIKE_BEGIN': ['', '', ''], + 'IGNORE': ['<.+?>'] +} + +# compile regexes for this mapping +_ctagmap = [] +for tagkey in _tagmap: + finaltag = f'{_tag_delim}{tagkey}{_tag_delim}' + for tagitem in _tagmap[tagkey]: + if tagkey != 'IGNORE': + _ctagmap.append((re.compile(tagitem.lower()), finaltag)) + _ctagmap.append((re.compile(tagitem.upper()), finaltag)) +# IGNORE tag needs to be added last +if 'IGNORE' in _tagmap: + for tagitem in _tagmap['IGNORE']: + _ctagmap.append((re.compile(tagitem), '')) + +# output format replacements (tags and main HTML entities) + +_render_tags = { + f'{_tag_delim}BOLD_BEGIN{_tag_delim}': '**', + f'{_tag_delim}BOLD_END{_tag_delim}': '**', + f'{_tag_delim}ITALIC_BEGIN{_tag_delim}': '_', + f'{_tag_delim}ITALIC_END{_tag_delim}': '_', + f'{_tag_delim}CODE_BEGIN{_tag_delim}': '`', + f'{_tag_delim}CODE_END{_tag_delim}': '`', + f'{_tag_delim}STRIKE_BEGIN{_tag_delim}': '~~', + f'{_tag_delim}STRIKE_END{_tag_delim}': '~~', + f'{_tag_delim}QUOTE_BEGIN{_tag_delim}': '>> ', + f'{_tag_delim}QUOTE_END{_tag_delim}': '', + f'{_tag_delim}LI_BEGIN{_tag_delim}': '* ', + f'{_tag_delim}LI_END{_tag_delim}': '', + '<': '<', + '>': '>', + '"': '"', + ''': "'", + '&': '&' +} + +# main class start +class DeckText: + def __init__(self, src=None): + self.title = '' + self.pages = [] + if src: + self.load(src) + + def _render_page(self, pagetext): + page_links = [] # list of tuples in (url, title) format + linkpool = pagetext = pagetext.strip() + linknum = 1 + while True: # parse links first + linkmatch = _link_regex.search(linkpool) + if linkmatch is None: + break + fullref = linkmatch.group(0) + url = linkmatch.group(1).strip() + title = linkmatch.group(2).strip() + page_links.append((url, title)) + linkpool = linkpool.replace(fullref, '') + pagetext = pagetext.replace(fullref, f'{title} ([{linknum}])') + linknum += 1 + for rtag in _render_tags: # render the rest of the tags + pagetext = pagetext.replace(rtag, _render_tags[rtag]) + return { + 'content': pagetext, + 'links': page_links + } + + def load(self, src:str): + if len(src) > 0: + # pre-parse stage 1: replace ambiguous tags with own tagmap + for tagkey in _ctagmap: + src = tagkey[0].sub(tagkey[1], src) + # pre-parse stage 2: extract document title + try: + self.title = src.split(_title_start_boundary)[1].split(_title_end_boundary)[0].strip() + except: + self.title = '' + # pre-parse stage 3: only leave the contents between the DeckText begin/end markers + fragments = [] + for part in src.split(_start_boundary)[1:]: # skip the part before the first marker + frag = part # by default, we may not use an end marker although that's bad + try: + frag = part.split(_end_boundary)[0] + except: + pass + fragments.append(frag.strip()) + # parse stage: shape individual pages and pass them to the renderer + self.pages = list(map(self._render_page, '\n'.join(fragments).split(_page_boundary))) + else: # reset on empty string + self.title = '' + self.pages = [] diff --git a/decktext-spec.md b/decktext-spec.md new file mode 100644 index 0000000..f06d4c6 --- /dev/null +++ b/decktext-spec.md @@ -0,0 +1,121 @@ +# DeckText specification and manual + +DeckText is a strictly defined subset of HTML (Hypertext Markup Language) aimed at simple, lightweight, text-only document authoring with basic formatting and page-oriented structure for consumption on low-powered portable devices with line-oriented UI, while being backwards-compatible with any existing Web browser implementation. + +## Rationale + +Rendering full HTML is a very compute-intensive task, especially for the devices that do not need all the features. A subset that would allow conveying all required text-only information, keeping the parser as simple as possible (while still being backwards-compatible with the original HTML specification), is a necessity for such devices without having to switch to alternative "small Web" protocols such as Gopher, Gemini, Nex or Spartan. + +## General definitions + +A **DeckText site** is a simple HTML document that conforms to this specification. Although it is allowed to have multiple HTML documents, it's recommended for the entire site to be served as a single-file HTTP(S) download. + +A DeckText site consists of: + +- document title, +- DeckText beginning and end markers, +- page separators, +- page contents consisting of a strict subset of HTML tags. + +A **DeckText-only client** (henceforth DTOC) is a software application that allows its users to browse DeckText sites without needing to implement a full HTML rendering engine. + +DTOCs MUST support UTF-8 encoding inside the documents and render them accordingly. Particular Unicode character set availability outside ASCII range is not required as it depends on the fonts installed on the target device. + +## Beginning and end markers + +To mark the beginning and the end of a DeckText-compliant document fragment within an HTML page, the following case-insensitive markers are used: + +- `` marks the beginning of DeckText, +- `` marks the end of DeckText. + +A single HTML page MAY contain several begin-end marker pairs, and a DTOC MUST read through all these sections and compile the document to display from all of them. + +Between the begin-end markers, only text content is allowed. + +## Title + +Just like any HTML document, a valid DeckText document MUST contain a single `'], + 'QUOTE_END': ['
`, ``, `