Welcome | Get started | Dive | Contribute | Topics | Reference | Changes | More
memo
: The memo parser¶
The lino.modlib.memo
plugin adds application-specific markup to
text fields .
One facet of this plugin is a simple built-in markup format called “memo”.
Another facet are suggesters. A suggester is when you define that a
“trigger text” will pop up a list of suggestions for auto-completion. For
example #
commonly refers to a topic or a ticket, or @
refers to
another site user.
A concrete real-world usage example is documented in Memo commands in Lino Noi.
Side note: Code snippets (lines starting with >>>
) in this document get
tested as part of our development workflow. The following
initialization snippet tells you which demo project is being used in
this document.
>>> from lino import startup
>>> startup('lino_book.projects.noi1e.settings.demo')
>>> from lino.api.doctest import *
Basic usage¶
The lino.modlib.memo.parser.Parser
is a simple markup parser that
expands “commands” found in an input string to produce a resulting output
string. Commands are in the form [KEYWORD ARGS]
. The caller defines
itself all commands, there are no predefined commands.
Let’s instantiate parser:
>>> from lino.modlib.memo.parser import Parser
>>> p = Parser()
We declare a command handler function url2html and register it:
>>> def url2html(parser, s, cmdname, mentions, context):
... print("[DEBUG] url2html() got %r" % s)
... if not s: return "XXX"
... url, text = s.split(None,1)
... return '<a href="%s">%s</a>' % (url,text)
>>> p.register_command('url', url2html)
The intended usage of our example handler is [url URL TEXT]
, where
URL is the URL to link to, and TEXT is the label of the link:
>>> print(p.parse('This is a [url http://xyz.com test].'))
[DEBUG] url2html() got 'http://xyz.com test'
This is a <a href="http://xyz.com">test</a>.
A command handler will be called with one parameter: the portion of
text between the KEYWORD and the closing square bracket. Not
including the whitespace after the keyword. It must return the text
which is to replace the [KEYWORD ARGS]
fragment. It is
responsible for parsing the text that it receives as parameter.
If an exception occurs during the command handler, the final exception message is inserted into the result.
To demonstrate this, our example implementation has a bug, it doesn’t support the case of having only a URL without TEXT:
>>> print(p.parse('This is a [url http://xyz.com].'))
[DEBUG] url2html() got 'http://xyz.com'
This is a [ERROR ... in ...'[url http://xyz.com]' at position 10-30].
We use an ellipsis in above code because the error message varies with Python versions.
Newlines preceded by a backslash will be removed before the command handler is called:
>>> print(p.parse('''This is [url http://xy\
... z.com another test].'''))
[DEBUG] url2html() got 'http://xyz.com another test'
This is <a href="http://xyz.com">another test</a>.
The whitespace between the KEYWORD and ARGS can be any whitespace, including newlines:
>>> print(p.parse('''This is a [url
... http://xyz.com test].'''))
[DEBUG] url2html() got 'http://xyz.com test'
This is a <a href="http://xyz.com">test</a>.
The ARGS part is optional (it’s up to the command handler to react accordingly, our handler function returns XXX in that case):
>>> print(p.parse('''This is a [url] test.'''))
[DEBUG] url2html() got ''
This is a XXX test.
The ARGS part may contain pairs of square brackets:
>>> print(p.parse('''This is a [url
... http://xyz.com test with [more] brackets].'''))
[DEBUG] url2html() got 'http://xyz.com test with [more] brackets'
This is a <a href="http://xyz.com">test with [more] brackets</a>.
Fragments of text between brackets that do not match any registered command will be left unchanged:
>>> print(p.parse('''This is a [1] test.'''))
This is a [1] test.
>>> print(p.parse('''This is a [foo bar] test.'''))
This is a [foo bar] test.
>>> print(p.parse('''Text with only [opening square bracket.'''))
Text with only [opening square bracket.
Special handling¶
Leading and trailing spaces are always removed from command text:
>>> print(p.parse("[url http://example.com Trailing space ]."))
[DEBUG] url2html() got 'http://example.com Trailing space'
<a href="http://example.com">Trailing space</a>.
>>> print(p.parse("[url http://example.com Leading space]."))
[DEBUG] url2html() got 'http://example.com Leading space'
<a href="http://example.com">Leading space</a>.
Non-breaking and zero-width spaces are treated like normal spaces:
>>> print(p.parse(u"[url\u00A0http://example.com example.com]."))
[DEBUG] url2html() got 'http://example.com example.com'
<a href="http://example.com">example.com</a>.
>>> print(p.parse(u"[url \u200bhttp://example.com example.com]."))
[DEBUG] url2html() got 'http://example.com example.com'
<a href="http://example.com">example.com</a>.
>>> print(p.parse(u"[url http://example.com example.com]."))
[DEBUG] url2html() got 'http://example.com example.com'
<a href="http://example.com">example.com</a>.
Limits¶
A single closing square bracket as part of ARGS will not produce the desired result:
>>> print(p.parse('''This is a [url
... http://xyz.com The character "\]"].'''))
[DEBUG] url2html() got 'http://xyz.com The character "\\'
This is a <a href="http://xyz.com">The character "\</a>"].
Execution flow statements like [if …] and [endif …] or [for
...]
and [endfor ...]
would be nice.
The [=expression]
form¶
>>> print(p.parse('''<ul>[="".join(['<li>%s</li>' % (i+1) for i in range(5)])]</ul>'''))
<ul><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li></ul>
You can specify a run-time context:
>>> ctx = { 'a': 3 }
>>> print(p.parse('''\
... The answer is [=a*a*5-a].''', context=ctx))
The answer is 42.
The Previewable
mixin¶
Adds three database fields body
,
body_short_preview
and body
. The two preview fields contain the parsed
version of the body, they are read-only and get updated automatically when the
body is updated. body_short_preview
contains only the first paragraph and a “more” indication if the full preview
has more. See also truncate_comment()
.
>>> def test(body):
... short, full = comments.Comment().parse_previews(body)
... print(short)
... print("------")
... print(full)
>>> test("Foo bar baz")
Foo bar baz
------
<p>Foo bar baz</p>
>>> test("<p>Foo</p><p>bar baz</p>")
Foo
bar baz
------
<p>Foo</p><p>bar baz</p>
>>> test("Foo\n\nbar baz")
Foo
bar baz
------
<p>Foo</p>
<p>bar baz</p>
Built-in memo commands¶
url¶
Insert a link to an external web page. The first argument is the URL (mandatory). If no other argument is given, the URL is used as text. Otherwise the remaining text is used as the link text.
The link will always open in a new window (target="_blank"
)
Usage examples:
[url http://www.example.com]
[url http://www.example.com example]
[url http://www.example.com another example]
py¶
Refer to a Python object. This is not being used on the field. A fundamental problem is that it works only in the currently running Python environment.
Usage examples:
[py lino]
[py lino.modlib.memo.parser]
[py lino_xl.lib.tickets.models.Ticket]
[py lino_xl.lib.tickets.models.Ticket tickets.Ticket]
The global memo parser contains two “built-in commands”:
>>> p = dd.plugins.memo.parser
The py
command is disabled since 20240920 because I don’t know anybody who
used it (except myself a few times for testing it) and because it requires
SETUP_INFO, which has an uncertain future.
>>> print(p.parse("[py lino]."))
<a href="https://gitlab.com/lino-framework/lino/blob/master/lino/__init__.py" target="_blank">lino</a>.
>>> print(p.parse("[py lino_xl.lib.tickets.models.Ticket]."))
<a href="https://gitlab.com/lino-framework/xl/blob/master/lino_xl/lib/tickets/models.py" target="_blank">lino_xl.lib.tickets.models.Ticket</a>.
>>> print(p.parse("[py lino_xl.lib.tickets.models.Ticket.foo]."))
<a href="Error in Python code (type object 'Ticket' has no attribute 'foo')" target="_blank">lino_xl.lib.tickets.models.Ticket.foo</a>.
>>> print(p.parse("[py lino_xl.lib.tickets.models.Ticket Ticket]."))
<a href="https://gitlab.com/lino-framework/xl/blob/master/lino_xl/lib/tickets/models.py" target="_blank">Ticket</a>.
Non-breaking spaces are removed from command text:
>>> print(p.parse("[py lino]."))
<a href="https://gitlab.com/lino-framework/lino/blob/master/lino/__init__.py" target="_blank">lino</a>.
Configuration¶
- memo.short_preview_length¶
How many characters to accept into the short preview.
Default is 300, cms sets it to 1200.
- memo.short_preview_image_height¶
Default value is
8em
.
Technical reference¶
- lino.modlib.memo.parse_previews(src, ar)¶
- lino.modlib.memo.truncate_comment(html_str, max_length=300)¶
Return the first paragraph of a string that can be either HTML or plain text, containing at most one paragraph with at most max_p_len characters.
- Html_str:
the raw string of html
- Max_length:
max number of characters to leave in the paragraph.
See usage examples in comments : The comments framework and Memo commands in Lino Noi and Truncating HTML texts.
- lino.modlib.memo.rich_text_to_elems(ar, description)¶
A RichTextField can contain either HTML markup or plain text.
- lino.modlib.memo.body_subject_to_elems(ar, title, description)¶
Convert the given title and description to a list of HTML elements.
Used by
lino.modlib.notify
and bylino_xl.lib.trading
- class lino.modlib.memo.Previewable¶
Adds three rich text fields (
lino.core.fields.RichTextField
):- body¶
An editable text body.
This is a
lino.core.fields.PreviewTextField
.
- class lino.modlib.memo.BabelPreviewable¶
A
Previewable
where thebody
field is a babel field.
- class lino.modlib.memo.PreviewableChecker¶
Check for previewables needing update.
- class lino.modlib.memo.Mention¶
Django model to represent a mention, i.e. the fact that some memo text of the owner points to some other database row.
- owner¶
The database row that mentions another one in a memo text.
- source¶
The mentioned database row.
- class lino.modlib.memo.MemoReferrable¶
Makes your model referable by a memo command.
Overrides
lino.core.model.Model.on_analyze()
to callparser.Parser.register_django_model()
whenmemo_command
is given.- memo_command¶
The name of the memo command to define.