mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-01-21 07:55:00 +00:00
doc: wikiparser: Preserve spaces during parsing and generation
- Matches the original XML more accurately and simplifies some parsing. Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org> Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
parent
aa27037c71
commit
d760ee004f
@ -166,10 +166,7 @@ class Paragraph(Element):
|
||||
xml = ''
|
||||
|
||||
for item_xml in items_xml:
|
||||
if item_xml[0] in '.,:;-_!?':
|
||||
xml += item_xml
|
||||
else:
|
||||
xml += ' ' + item_xml
|
||||
xml += item_xml
|
||||
|
||||
return f'<para>{xml}</para>'
|
||||
|
||||
@ -555,7 +552,6 @@ def split_formatted(text, delimiter, end_delimiter=None):
|
||||
Return (formatted_text, remaining_text) if it is found.
|
||||
Return (None, text) otherwise.
|
||||
"""
|
||||
text = text.strip()
|
||||
end_delimiter = end_delimiter or delimiter
|
||||
content = None
|
||||
if text.startswith(delimiter):
|
||||
@ -698,7 +694,6 @@ def parse_text(line, context=None, parse_links=True):
|
||||
content = re.split(r"''|`|{{|__|\[\[", line)[0]
|
||||
if content:
|
||||
line = line.replace(content, '', 1)
|
||||
content = content.strip()
|
||||
result += parse_plain_text(content, parse_links=parse_links)
|
||||
continue
|
||||
|
||||
@ -711,7 +706,6 @@ def parse_plain_text(content, parse_links=True):
|
||||
"""Parse a line or plain text and generate plain text and URL objects."""
|
||||
result = []
|
||||
while content:
|
||||
content = content.strip()
|
||||
wiki_link_match = re.search(
|
||||
r'(?: |^)([A-Z][a-z0-9]+([A-Z][a-z0-9]+)+)(?: |$)', content)
|
||||
link_match = re.search(r'(https?://[^<> ]+[^<> .:\(\)])', content)
|
||||
@ -737,9 +731,6 @@ def parse_plain_text(content, parse_links=True):
|
||||
# Replace occurrences of !WikiText with WikiText
|
||||
text = re.sub(r'([^A-Za-z]|^)!', r'\g<1>', text)
|
||||
|
||||
# Gobble multiple spaces
|
||||
text = re.sub(r' +', r' ', text)
|
||||
|
||||
result.append(PlainText(text))
|
||||
|
||||
if end:
|
||||
@ -755,8 +746,8 @@ def parse_table_row(line, context=None):
|
||||
row_cells = re.split(r'\|\|', line)[1:-1]
|
||||
row_items = []
|
||||
for cell in row_cells:
|
||||
content = cell.strip()
|
||||
if content:
|
||||
content = cell
|
||||
if content.strip():
|
||||
# remove <tablestyle=...> that was already processed
|
||||
content = re.sub('<tablestyle=[^>]+>', '', content)
|
||||
align = None
|
||||
@ -804,8 +795,21 @@ def parse_list(list_data, context=None):
|
||||
|
||||
else:
|
||||
content = list_data.pop(0)[2]
|
||||
new_content = ''
|
||||
in_code_block = False
|
||||
for line in content.splitlines(True):
|
||||
if line.startswith(' ' * current_level) and not in_code_block:
|
||||
line = line[current_level:]
|
||||
|
||||
if line.strip().startswith('{{{'):
|
||||
in_code_block = True
|
||||
elif line.strip().startswith('}}}'):
|
||||
in_code_block = False
|
||||
|
||||
new_content += line
|
||||
|
||||
parsed_list.add_item(
|
||||
ListItem(parse_wiki(content, context),
|
||||
ListItem(parse_wiki(new_content, context),
|
||||
override_marker=override_marker))
|
||||
|
||||
return parsed_list, list_data
|
||||
@ -885,91 +889,94 @@ def parse_wiki(text, context=None, begin_marker=None, end_marker=None):
|
||||
[Heading(5, 'heading 5th level')]
|
||||
|
||||
>>> parse_wiki('plain text')
|
||||
[Paragraph([PlainText('plain text')])]
|
||||
[Paragraph([PlainText('plain text ')])]
|
||||
>>> parse_wiki(' plain multispaced text ')
|
||||
[Paragraph([PlainText('plain multispaced text')])]
|
||||
[Paragraph([PlainText(' plain multispaced text ')])]
|
||||
>>> parse_wiki('https://freedombox.org')
|
||||
[Paragraph([Url('https://freedombox.org')])]
|
||||
[Paragraph([Url('https://freedombox.org'), PlainText(' ')])]
|
||||
>>> parse_wiki("''italic''")
|
||||
[Paragraph([ItalicText('italic')])]
|
||||
[Paragraph([ItalicText('italic'), PlainText(' ')])]
|
||||
>>> parse_wiki("'''bold'''")
|
||||
[Paragraph([BoldText('bold')])]
|
||||
[Paragraph([BoldText('bold'), PlainText(' ')])]
|
||||
>>> parse_wiki("normal text followed by '''bold text'''")
|
||||
[Paragraph([PlainText('normal text followed by'), BoldText('bold text')])]
|
||||
[Paragraph([PlainText('normal text followed by '), BoldText('bold text'), \
|
||||
PlainText(' ')])]
|
||||
>>> parse_wiki('`monospace`')
|
||||
[Paragraph([MonospaceText('monospace')])]
|
||||
[Paragraph([MonospaceText('monospace'), PlainText(' ')])]
|
||||
>>> parse_wiki('{{{code}}}')
|
||||
[Paragraph([CodeText('code')])]
|
||||
[Paragraph([CodeText('code'), PlainText(' ')])]
|
||||
>>> parse_wiki('__underline__')
|
||||
[Paragraph([UnderlineText('underline')])]
|
||||
[Paragraph([UnderlineText('underline'), PlainText(' ')])]
|
||||
>>> parse_wiki('~-smaller text-~')
|
||||
[Paragraph([SmallerTextWarning(), PlainText('smaller text')])]
|
||||
[Paragraph([SmallerTextWarning(), PlainText('smaller text ')])]
|
||||
>>> parse_wiki('!FreedomBox')
|
||||
[Paragraph([PlainText('FreedomBox')])]
|
||||
[Paragraph([PlainText('FreedomBox ')])]
|
||||
>>> parse_wiki('making a point!')
|
||||
[Paragraph([PlainText('making a point!')])]
|
||||
[Paragraph([PlainText('making a point! ')])]
|
||||
>>> parse_wiki('Back to [[FreedomBox/Manual|manual]] page.')
|
||||
[Paragraph([PlainText('Back to'), Link('FreedomBox/Manual', \
|
||||
[PlainText('manual')]), PlainText('page.')])]
|
||||
[Paragraph([PlainText('Back to '), Link('FreedomBox/Manual', \
|
||||
[PlainText('manual')]), PlainText(' page. ')])]
|
||||
>>> parse_wiki('[[attachment:Searx.webm|Searx installation and first steps\
|
||||
|&do=get]]')
|
||||
[Paragraph([Link('attachment:Searx.webm', \
|
||||
[PlainText('Searx installation and first steps')], '&do=get')])]
|
||||
[PlainText('Searx installation and first steps')], '&do=get'), \
|
||||
PlainText(' ')])]
|
||||
>>> parse_wiki('[[https://onionshare.org/|Onionshare]]')
|
||||
[Paragraph([Link('https://onionshare.org/', [PlainText('Onionshare')])])]
|
||||
[Paragraph([Link('https://onionshare.org/', [PlainText('Onionshare')]), \
|
||||
PlainText(' ')])]
|
||||
|
||||
>>> parse_wiki('/!\\\\')
|
||||
[Paragraph([EmbeddedAttachment('icons/alert.png', \
|
||||
[PlainText('icons/alert.png')], 'height=20')])]
|
||||
[PlainText('icons/alert.png')], 'height=20'), PlainText(' ')])]
|
||||
>>> parse_wiki('(./)')
|
||||
[Paragraph([EmbeddedAttachment('icons/checkmark.png', \
|
||||
[PlainText('icons/checkmark.png')], 'height=20')])]
|
||||
[PlainText('icons/checkmark.png')], 'height=20'), PlainText(' ')])]
|
||||
>>> parse_wiki('{X}')
|
||||
[Paragraph([EmbeddedAttachment('icons/icon-error.png', \
|
||||
[PlainText('icons/icon-error.png')], 'height=20')])]
|
||||
[PlainText('icons/icon-error.png')], 'height=20'), PlainText(' ')])]
|
||||
>>> parse_wiki('{i}')
|
||||
[Paragraph([EmbeddedAttachment('icons/icon-info.png', \
|
||||
[PlainText('icons/icon-info.png')], 'height=20')])]
|
||||
[PlainText('icons/icon-info.png')], 'height=20'), PlainText(' ')])]
|
||||
>>> parse_wiki('{o}')
|
||||
[Paragraph([EmbeddedAttachment('icons/star_off.png', \
|
||||
[PlainText('icons/star_off.png')], 'height=20')])]
|
||||
[PlainText('icons/star_off.png')], 'height=20'), PlainText(' ')])]
|
||||
>>> parse_wiki('{*}')
|
||||
[Paragraph([EmbeddedAttachment('icons/star_on.png', \
|
||||
[PlainText('icons/star_on.png')], 'height=20')])]
|
||||
[PlainText('icons/star_on.png')], 'height=20'), PlainText(' ')])]
|
||||
|
||||
>>> parse_wiki('{{attachment:cockpit-enable.png}}')
|
||||
[Paragraph([EmbeddedAttachment('cockpit-enable.png', \
|
||||
[PlainText('cockpit-enable.png')])])]
|
||||
[PlainText('cockpit-enable.png')]), PlainText(' ')])]
|
||||
>>> parse_wiki('{{attachment:Backups_Step1_v49.png|Backups: Step 1|\
|
||||
width=800}}')
|
||||
[Paragraph([EmbeddedAttachment('Backups_Step1_v49.png', \
|
||||
[PlainText('Backups: Step 1')], 'width=800')])]
|
||||
[PlainText('Backups: Step 1')], 'width=800'), PlainText(' ')])]
|
||||
|
||||
>>> parse_wiki(' * single item')
|
||||
[List('bulleted', [ListItem([Paragraph([PlainText('single item')])])])]
|
||||
[List('bulleted', [ListItem([Paragraph([PlainText('single item ')])])])]
|
||||
>>> parse_wiki(' * first item\\n * second item')
|
||||
[List('bulleted', [ListItem([Paragraph([PlainText('first item')])]), \
|
||||
ListItem([Paragraph([PlainText('second item')])])])]
|
||||
[List('bulleted', [ListItem([Paragraph([PlainText('first item ')])]), \
|
||||
ListItem([Paragraph([PlainText('second item ')])])])]
|
||||
>>> parse_wiki('text to introduce\\n * a list')
|
||||
[Paragraph([PlainText('text to introduce')]), \
|
||||
List('bulleted', [ListItem([Paragraph([PlainText('a list')])])])]
|
||||
[Paragraph([PlainText('text to introduce ')]), \
|
||||
List('bulleted', [ListItem([Paragraph([PlainText('a list ')])])])]
|
||||
>>> parse_wiki(' . first item\\n . second item')
|
||||
[List('plain', [ListItem([Paragraph([PlainText('first item')])]), \
|
||||
ListItem([Paragraph([PlainText('second item')])])])]
|
||||
[List('plain', [ListItem([Paragraph([PlainText('first item ')])]), \
|
||||
ListItem([Paragraph([PlainText('second item ')])])])]
|
||||
>>> parse_wiki(' * item 1\\n * item 1.1')
|
||||
[List('bulleted', [ListItem([Paragraph([PlainText('item 1')]), \
|
||||
List('bulleted', [ListItem([Paragraph([PlainText('item 1.1')])])])])])]
|
||||
[List('bulleted', [ListItem([Paragraph([PlainText('item 1 ')]), \
|
||||
List('bulleted', [ListItem([Paragraph([PlainText('item 1.1 ')])])])])])]
|
||||
>>> parse_wiki(' 1. item 1\\n 1. item 1.1')
|
||||
[List('numbered', [ListItem([Paragraph([PlainText('item 1')]), \
|
||||
List('numbered', [ListItem([Paragraph([PlainText('item 1.1')])])])])])]
|
||||
[List('numbered', [ListItem([Paragraph([PlainText('item 1 ')]), \
|
||||
List('numbered', [ListItem([Paragraph([PlainText('item 1.1 ')])])])])])]
|
||||
>>> parse_wiki(' * single,\\n multiline item')
|
||||
[List('bulleted', \
|
||||
[ListItem([Paragraph([PlainText('single,'), \
|
||||
PlainText('multiline item')])])])]
|
||||
[ListItem([Paragraph([PlainText('single, '), \
|
||||
PlainText('multiline item ')])])])]
|
||||
>>> parse_wiki(' * single,\\n \\n multipara item')
|
||||
[List('bulleted', \
|
||||
[ListItem([Paragraph([PlainText('single,')]), \
|
||||
Paragraph([PlainText('multipara item')])])])]
|
||||
[ListItem([Paragraph([PlainText('single, ')]), \
|
||||
Paragraph([PlainText('multipara item ')])])])]
|
||||
|
||||
>>> parse_wiki('----')
|
||||
[HorizontalRule(4)]
|
||||
@ -980,9 +987,9 @@ Paragraph([PlainText('multipara item')])])])]
|
||||
[Table([TableRow([TableItem([Paragraph([BoldText('A')])]), \
|
||||
TableItem([Paragraph([BoldText('B')])]), \
|
||||
TableItem([Paragraph([BoldText('C')])])]), \
|
||||
TableRow([TableItem([Paragraph([PlainText('1')])]), \
|
||||
TableItem([Paragraph([PlainText('2')])]), \
|
||||
TableItem([Paragraph([PlainText('3')])])])])]
|
||||
TableRow([TableItem([Paragraph([PlainText('1 ')])]), \
|
||||
TableItem([Paragraph([PlainText('2 ')])]), \
|
||||
TableItem([Paragraph([PlainText('3 ')])])])])]
|
||||
|
||||
>>> parse_wiki("||<tablestyle='border:1px solid black;width: 80%'>A||")
|
||||
[Table([TableRow([TableItem([Paragraph([PlainText('A')])])])], \
|
||||
@ -1020,52 +1027,53 @@ from="## BEGIN_INCLUDE", to="## END_INCLUDE")>>')
|
||||
|
||||
>>> parse_wiki('a\\n\\n## END_INCLUDE\\n\\nb', \
|
||||
None, None, '## END_INCLUDE')
|
||||
[Paragraph([PlainText('a')])]
|
||||
[Paragraph([PlainText('a ')])]
|
||||
>>> parse_wiki('a\\n\\n## BEGIN_INCLUDE\\n\\nb' \
|
||||
'\\n\\n## END_INCLUDE\\n\\nc', \
|
||||
None, '## BEGIN_INCLUDE', '## END_INCLUDE')
|
||||
[Paragraph([PlainText('b')])]
|
||||
[Paragraph([PlainText('b ')])]
|
||||
|
||||
>>> parse_wiki('a<<BR>>\\nb')
|
||||
[Paragraph([PlainText('a')]), Paragraph([PlainText('b')])]
|
||||
[Paragraph([PlainText('a')]), Paragraph([PlainText('b ')])]
|
||||
>>> parse_wiki('{{{#!wiki caution\\n\\nOnce some other app is set as the \
|
||||
home page, you can only navigate to the !FreedomBox Service (Plinth) by \
|
||||
typing https://myfreedombox.rocks/plinth/ into the browser. <<BR>>\\n\
|
||||
''/freedombox'' can also be used as an alias to ''/plinth''\\n}}}')
|
||||
''/freedombox'' can also be used as an alias to ''/plinth''\\n}}}')
|
||||
[Admonition('caution', [Paragraph([PlainText('Once some other app is set \
|
||||
as the home page, you can only navigate to the FreedomBox Service (Plinth) by \
|
||||
typing '), Url('https://myfreedombox.rocks/plinth/'), PlainText('into the \
|
||||
browser.')]), Paragraph([PlainText('/freedombox can also be used as an alias \
|
||||
typing '), Url('https://myfreedombox.rocks/plinth/'), PlainText(' into the \
|
||||
browser. ')]), Paragraph([PlainText('/freedombox can also be used as an alias \
|
||||
to /plinth')])])]
|
||||
|
||||
>>> parse_wiki('{{{\\nmulti-line\\n\
|
||||
preformatted text (source code)\\n}}}''')
|
||||
[CodeText('multi-line\\npreformatted text (source code)')]
|
||||
>>> parse_wiki('text to introduce {{{ a singleliner}}}')
|
||||
[Paragraph([PlainText('text to introduce'), CodeText(' a singleliner')])]
|
||||
>>> parse_wiki('text to introduce \\n{{{\\n a multiliner\\nstarting at\
|
||||
[Paragraph([PlainText('text to introduce '), CodeText(' a singleliner'),\
|
||||
PlainText(' ')])]
|
||||
>>> parse_wiki('text to introduce\\n{{{\\n a multiliner\\nstarting at\
|
||||
\\n different indents.\\n}}}')
|
||||
[Paragraph([PlainText('text to introduce')]), \
|
||||
[Paragraph([PlainText('text to introduce ')]), \
|
||||
CodeText(' a multiliner\\nstarting at\\n different indents.')]
|
||||
>>> parse_wiki('Blah, blah:\\n {{{\\nmulti-line\\nformatted text\\n\
|
||||
starting at col #1\\n}}}')
|
||||
[Paragraph([PlainText('Blah, blah:')]), \
|
||||
[Paragraph([PlainText('Blah, blah: ')]), \
|
||||
CodeText('multi-line\\nformatted text\\nstarting at col #1')]
|
||||
>>> parse_wiki(' * Blah, blah:\\n {{{\\nmulti-line\\nformatted text\
|
||||
\\nstarting at col #1\\n}}}')
|
||||
[List('bulleted', \
|
||||
[ListItem([Paragraph([PlainText('Blah, blah:')]), \
|
||||
[ListItem([Paragraph([PlainText('Blah, blah: ')]), \
|
||||
CodeText('multi-line\\nformatted text\\nstarting at col #1')])])]
|
||||
>>> parse_wiki(' {{{\\n nmap -p 80 --open -sV 192.168.0.0/24 \
|
||||
(replace the ip/netmask with the one the router uses)\\n }}}\\n In \
|
||||
most cases you can look at your current IP address, and change the last \
|
||||
digits with zero to find your home network, like so: XXX.XXX.XXX.0/24')
|
||||
[CodeText(' nmap -p 80 --open -sV 192.168.0.0/24 (replace the \
|
||||
ip/netmask with the one the router uses)'), Paragraph([PlainText('In \
|
||||
ip/netmask with the one the router uses)'), Paragraph([PlainText(' In \
|
||||
most cases you can look at your current IP address, and change the last \
|
||||
digits with zero to find your home network, like so: XXX.XXX.XXX.0/24')])]
|
||||
>>> parse_wiki('text to introduce \\n----\\n<<TableOfContents()>>')
|
||||
[Paragraph([PlainText('text to introduce')]), \
|
||||
digits with zero to find your home network, like so: XXX.XXX.XXX.0/24 ')])]
|
||||
>>> parse_wiki('text to introduce\\n----\\n<<TableOfContents()>>')
|
||||
[Paragraph([PlainText('text to introduce ')]), \
|
||||
HorizontalRule(4), TableOfContents()]
|
||||
|
||||
>>> parse_wiki(' If this command shows an error such as ''new key but \
|
||||
@ -1074,69 +1082,69 @@ the keys:\\n {{{\\n$ gpg --keyserver keys.gnupg.net --recv-keys \
|
||||
BCBEBD57A11F70B23782BC5736C361440C9BC971\\n$ gpg --keyserver keys.gnupg.net \
|
||||
--recv-keys 7D6ADB750F91085589484BE677C0C75E7B650808\\n$ gpg --keyserver \
|
||||
keys.gnupg.net --recv-keys 013D86D8BA32EAB4A6691BF85D4153D6FE188FC8\\n }}}')
|
||||
[Paragraph([PlainText('If this command shows an error such as new key but \
|
||||
contains no user ID - skipped, then use a different keyserver to download the \
|
||||
keys:')]), CodeText('$ gpg --keyserver keys.gnupg.net --recv-keys \
|
||||
[Paragraph([PlainText(' If this command shows an error such as new key \
|
||||
but contains no user ID - skipped, then use a different keyserver to download \
|
||||
the keys: ')]), CodeText('$ gpg --keyserver keys.gnupg.net --recv-keys \
|
||||
BCBEBD57A11F70B23782BC5736C361440C9BC971\\n$ gpg --keyserver keys.gnupg.net \
|
||||
--recv-keys 7D6ADB750F91085589484BE677C0C75E7B650808\\n$ gpg --keyserver \
|
||||
keys.gnupg.net --recv-keys 013D86D8BA32EAB4A6691BF85D4153D6FE188FC8')]
|
||||
|
||||
>>> parse_wiki('User documentation:\\n * List of \
|
||||
[[FreedomBox/Features|applications]] offered by !FreedomBox.')
|
||||
[Paragraph([PlainText('User documentation:')]), List('bulleted', \
|
||||
[ListItem([Paragraph([PlainText('List of'), Link('FreedomBox/Features', \
|
||||
[PlainText('applications')]), PlainText('offered by FreedomBox.')])])])]
|
||||
[Paragraph([PlainText('User documentation: ')]), List('bulleted', \
|
||||
[ListItem([Paragraph([PlainText('List of '), Link('FreedomBox/Features', \
|
||||
[PlainText('applications')]), PlainText(' offered by FreedomBox. ')])])])]
|
||||
|
||||
>>> parse_wiki('\
|
||||
* Within !FreedomBox Service (Plinth)\\n\
|
||||
1. select ''Apps''\\n\
|
||||
2. go to ''Radicale (Calendar and Addressbook)'' and \\n\
|
||||
2. go to ''Radicale (Calendar and Addressbook)'' and\\n\
|
||||
3. install the application. After the installation is complete, make sure \
|
||||
the application is marked "enabled" in the !FreedomBox interface. Enabling \
|
||||
the application launches the Radicale CalDAV/CardDAV server. \\n\
|
||||
the application launches the Radicale CalDAV/CardDAV server.\\n\
|
||||
4. define the access rights:\\n\
|
||||
* Only the owner of a calendar/addressbook can view or make changes\\n\
|
||||
* Any user can view any calendar/addressbook, but only the owner can make \
|
||||
changes\\n\
|
||||
* Any user can view or make changes to any calendar/addressbook')
|
||||
[List('bulleted', [\
|
||||
ListItem([Paragraph([PlainText('Within FreedomBox Service (Plinth)')]), \
|
||||
List('numbered', [ListItem([Paragraph([PlainText('select Apps')])]), \
|
||||
ListItem([Paragraph([PlainText('Within FreedomBox Service (Plinth) ')]), \
|
||||
List('numbered', [ListItem([Paragraph([PlainText('select Apps ')])]), \
|
||||
ListItem([Paragraph([PlainText('go to Radicale (Calendar and Addressbook) \
|
||||
and')])]), \
|
||||
and ')])]), \
|
||||
ListItem([Paragraph([PlainText('install the application. After the \
|
||||
installation is complete, make sure the application is marked "enabled" in \
|
||||
the FreedomBox interface. Enabling the application launches the Radicale \
|
||||
CalDAV/CardDAV server.')])]), \
|
||||
ListItem([Paragraph([PlainText('define the access rights:')]), \
|
||||
CalDAV/CardDAV server. ')])]), \
|
||||
ListItem([Paragraph([PlainText('define the access rights: ')]), \
|
||||
List('bulleted', [ListItem([Paragraph([PlainText('Only the owner of a \
|
||||
calendar/addressbook can view or make changes')])]), \
|
||||
calendar/addressbook can view or make changes ')])]), \
|
||||
ListItem([Paragraph([PlainText('Any user can view any calendar/addressbook, \
|
||||
but only the owner can make changes')])]), \
|
||||
but only the owner can make changes ')])]), \
|
||||
ListItem([Paragraph([PlainText('Any user can view or make changes to any \
|
||||
calendar/addressbook')])])])])])])])]
|
||||
calendar/addressbook ')])])])])])])])]
|
||||
|
||||
>>> parse_wiki('[[attachment:freedombox-screenshot-home.png|\
|
||||
{{attachment:freedombox-screenshot-home.png|Home Page|width=300}}]]')
|
||||
[Paragraph([Link('attachment:freedombox-screenshot-home.png', \
|
||||
[EmbeddedAttachment('freedombox-screenshot-home.png', \
|
||||
[PlainText('Home Page')], 'width=300')])])]
|
||||
[PlainText('Home Page')], 'width=300')]), PlainText(' ')])]
|
||||
|
||||
>>> parse_wiki(" * New wiki and manual content licence: \
|
||||
''[[https://creativecommons.org/licenses/by-sa/4.0/|Creative Commons \
|
||||
Attribution-ShareAlike 4.0 International]]'' (from June 13rd 2016).")
|
||||
[List('bulleted', [ListItem([Paragraph([PlainText('New wiki and manual \
|
||||
content licence:'), Link('https://creativecommons.org/licenses/by-sa/4.0/', \
|
||||
content licence: '), Link('https://creativecommons.org/licenses/by-sa/4.0/', \
|
||||
[ItalicText('Creative Commons Attribution-ShareAlike 4.0 International')]), \
|
||||
PlainText('(from June 13rd 2016).')])])])]
|
||||
PlainText(' (from June 13rd 2016). ')])])])]
|
||||
|
||||
>>> parse_wiki('An alternative to downloading these images is to \
|
||||
[[InstallingDebianOn/TI/BeagleBone|install Debian]] on the !BeagleBone and \
|
||||
then [[FreedomBox/Hardware/Debian|install FreedomBox]] on it.')
|
||||
[Paragraph([PlainText('An alternative to downloading these images is to'),\
|
||||
Link('InstallingDebianOn/TI/BeagleBone', [PlainText('install Debian')]), \
|
||||
PlainText('on the BeagleBone and then'), Link('FreedomBox/Hardware/Debian', \
|
||||
[PlainText('install FreedomBox')]), PlainText('on it.')])]
|
||||
[Paragraph([PlainText('An alternative to downloading these images is to ')\
|
||||
, Link('InstallingDebianOn/TI/BeagleBone', [PlainText('install Debian')]), \
|
||||
PlainText(' on the BeagleBone and then '), Link('FreedomBox/Hardware/Debian', \
|
||||
[PlainText('install FreedomBox')]), PlainText(' on it. ')])]
|
||||
|
||||
>>> parse_wiki("'''Synchronizing contacts'''\\n 1. Click on the hamburger \
|
||||
menus of CalDAV and CardDAV and select either \\"Refresh ...\\" in case of \
|
||||
@ -1144,13 +1152,14 @@ existing accounts or \\"Create ...\\" in case of new accounts (see the second \
|
||||
screenshot below).\\n 1. Check the checkboxes for the address books and \
|
||||
calendars you want to synchronize and click on the sync button in the header. \
|
||||
(see the third screenshot below)")
|
||||
[Paragraph([BoldText('Synchronizing contacts')]), List('numbered', \
|
||||
[Paragraph([BoldText('Synchronizing contacts'), PlainText(' ')]), \
|
||||
List('numbered', \
|
||||
[ListItem([Paragraph([PlainText('Click on the hamburger menus of CalDAV and \
|
||||
CardDAV and select either "Refresh ..." in case of existing accounts or \
|
||||
"Create ..." in case of new accounts (see the second screenshot below).')])]),\
|
||||
"Create ..." in case of new accounts (see the second screenshot below). ')])]),\
|
||||
ListItem([Paragraph([PlainText('Check the checkboxes for the address books \
|
||||
and calendars you want to synchronize and click on the sync button in the \
|
||||
header. (see the third screenshot below)')])])])]
|
||||
header. (see the third screenshot below) ')])])])]
|
||||
|
||||
>>> parse_wiki("After Roundcube is installed, it can be accessed at \
|
||||
{{{https://<your freedombox>/roundcube}}}. Enter your username and password. \
|
||||
@ -1164,19 +1173,19 @@ the IMAP server. Using encrypted connection to your IMAP server is strongly \
|
||||
recommended. To do this, prepend 'imaps://' at the beginning of your IMAP \
|
||||
server address. For example, ''imaps://imap.example.org''.")
|
||||
[Paragraph([PlainText('After Roundcube is installed, it can be accessed \
|
||||
at'), CodeText('https://<your freedombox>/roundcube'), PlainText('. Enter \
|
||||
at '), CodeText('https://<your freedombox>/roundcube'), PlainText('. Enter \
|
||||
your username and password. The username for many mail services will be the \
|
||||
full email address such as'), ItalicText('exampleuser@example.org'), \
|
||||
PlainText('and not just the username like'), ItalicText('exampleuser'), \
|
||||
full email address such as '), ItalicText('exampleuser@example.org'), \
|
||||
PlainText(' and not just the username like '), ItalicText('exampleuser'), \
|
||||
PlainText(". Enter the address of your email service's IMAP server address in \
|
||||
the"), ItalicText('Server'), PlainText('field. You can try providing your \
|
||||
domain name here such as'), ItalicText('example.org'), PlainText('for email \
|
||||
address'), ItalicText('exampleuser@example.org'), PlainText("and if this does \
|
||||
not work, consult your email provider's documentation for the address of the \
|
||||
IMAP server. Using encrypted connection to your IMAP server is strongly \
|
||||
recommended. To do this, prepend 'imaps://' at the beginning of your IMAP \
|
||||
server address. For example,"), ItalicText('imaps://imap.example.org'), \
|
||||
PlainText('.')])]
|
||||
the "), ItalicText('Server'), PlainText(' field. You can try providing your \
|
||||
domain name here such as '), ItalicText('example.org'), PlainText(' for email \
|
||||
address '), ItalicText('exampleuser@example.org'), PlainText(" and if this \
|
||||
does not work, consult your email provider's documentation for the address \
|
||||
of the IMAP server. Using encrypted connection to your IMAP server is \
|
||||
strongly recommended. To do this, prepend 'imaps://' at the beginning of \
|
||||
your IMAP server address. For example, "), \
|
||||
ItalicText('imaps://imap.example.org'), PlainText('. ')])]
|
||||
|
||||
>>> parse_wiki('Tor Browser is the recommended way to browse the web \
|
||||
using Tor. You can download the Tor Browser from \
|
||||
@ -1184,19 +1193,19 @@ https://www.torproject.org/projects/torbrowser.html and follow the \
|
||||
instructions on that site to install and run it.')
|
||||
[Paragraph([PlainText('Tor Browser is the recommended way to browse the \
|
||||
web using Tor. You can download the Tor Browser from '), Url('\
|
||||
https://www.torproject.org/projects/torbrowser.html'), PlainText('and follow \
|
||||
the instructions on that site to install and run it.')])]
|
||||
https://www.torproject.org/projects/torbrowser.html'), PlainText(' and follow \
|
||||
the instructions on that site to install and run it. ')])]
|
||||
|
||||
>>> parse_wiki('After installation a web page becomes available on \
|
||||
https://<your-freedombox>/_minidlna.')
|
||||
[Paragraph([PlainText('After installation a web page becomes available on \
|
||||
https://<your-freedombox>/_minidlna.')])]
|
||||
https://<your-freedombox>/_minidlna. ')])]
|
||||
|
||||
>>> parse_wiki('or http://10.42.0.1/.')
|
||||
[Paragraph([PlainText('or '), Url('http://10.42.0.1/'), PlainText('.')])]
|
||||
[Paragraph([PlainText('or '), Url('http://10.42.0.1/'), PlainText('. ')])]
|
||||
|
||||
>>> parse_wiki('or http://10.42.0.1/:')
|
||||
[Paragraph([PlainText('or '), Url('http://10.42.0.1/'), PlainText(':')])]
|
||||
[Paragraph([PlainText('or '), Url('http://10.42.0.1/'), PlainText(': ')])]
|
||||
|
||||
>>> parse_wiki('||<style="text-align: center;"> [[FreedomBox/Hardware/\
|
||||
A20-OLinuXino-Lime2|{{attachment:a20-olinuxino-lime2_thumb.jpg|A20 OLinuXino \
|
||||
@ -1207,18 +1216,23 @@ MICRO|width=235,height=132}}]]<<BR>> [[FreedomBox/Hardware/A20-OLinuXino-MICRO\
|
||||
|A20 OLinuXino MICRO]] ||<style="text-align: center;"> [[FreedomBox/Hardware/\
|
||||
APU|{{attachment:apu1d_thumb.jpg|PC Engines APU|width=235,height=157}}]]<<BR>>\
|
||||
[[FreedomBox/Hardware/APU|PC Engines APU]] ||')
|
||||
[Table([TableRow([TableItem([Paragraph([Link('FreedomBox/Hardware/\
|
||||
A20-OLinuXino-Lime2', [EmbeddedAttachment('a20-olinuxino-lime2_thumb.jpg', [\
|
||||
PlainText('A20 OLinuXino Lime2')], 'width=235,height=159')])]), Paragraph([\
|
||||
Link('FreedomBox/Hardware/A20-OLinuXino-Lime2', [PlainText('A20 OLinuXino \
|
||||
Lime2')])])], 'center'), TableItem([Paragraph([Link('FreedomBox/Hardware/\
|
||||
A20-OLinuXino-MICRO', [EmbeddedAttachment('a20-olinuxino-micro_thumb.jpg', [\
|
||||
PlainText('A20 OLinuXino MICRO')], 'width=235,height=132')])]), Paragraph([\
|
||||
Link('FreedomBox/Hardware/A20-OLinuXino-MICRO', [PlainText('A20 OLinuXino \
|
||||
MICRO')])])], 'center'), TableItem([Paragraph([Link('FreedomBox/Hardware/\
|
||||
APU', [EmbeddedAttachment('apu1d_thumb.jpg', [PlainText('PC Engines APU')], \
|
||||
'width=235,height=157')])]), Paragraph([Link('FreedomBox/Hardware/APU', [\
|
||||
PlainText('PC Engines APU')])])], 'center')])])]
|
||||
[Table([TableRow([TableItem([Paragraph([PlainText(' '), \
|
||||
Link('FreedomBox/Hardware/A20-OLinuXino-Lime2', \
|
||||
[EmbeddedAttachment('a20-olinuxino-lime2_thumb.jpg', \
|
||||
[PlainText('A20 OLinuXino Lime2')], 'width=235,height=159')])]), \
|
||||
Paragraph([PlainText(' '), Link('FreedomBox/Hardware/A20-OLinuXino-Lime2', \
|
||||
[PlainText('A20 OLinuXino Lime2')]), PlainText(' ')])], 'center'), \
|
||||
TableItem([Paragraph([PlainText(' '), \
|
||||
Link('FreedomBox/Hardware/A20-OLinuXino-MICRO', \
|
||||
[EmbeddedAttachment('a20-olinuxino-micro_thumb.jpg', \
|
||||
[PlainText('A20 OLinuXino MICRO')], 'width=235,height=132')])]), \
|
||||
Paragraph([PlainText(' '), Link('FreedomBox/Hardware/A20-OLinuXino-MICRO', \
|
||||
[PlainText('A20 OLinuXino MICRO')]), PlainText(' ')])], 'center'), \
|
||||
TableItem([Paragraph([PlainText(' '), Link('FreedomBox/Hardware/APU', \
|
||||
[EmbeddedAttachment('apu1d_thumb.jpg', [PlainText('PC Engines APU')], \
|
||||
'width=235,height=157')])]), Paragraph([PlainText(' '), \
|
||||
Link('FreedomBox/Hardware/APU', [PlainText('PC Engines APU')]), \
|
||||
PlainText(' ')])], 'center')])])]
|
||||
|
||||
>>> parse_wiki(" 1. When created, go to the virtual machine's Settings -> \
|
||||
[Network] -> [Adapter 1]->[Attached to:] and choose the network type your \
|
||||
@ -1228,14 +1242,14 @@ that this exposes the !FreedomBox's services to your entire local network.")
|
||||
[List('numbered', [ListItem([Paragraph([PlainText("When created, go to \
|
||||
the virtual machine's Settings -> [Network] -> [Adapter 1]->[Attached to:] \
|
||||
and choose the network type your want the machine to use according to the \
|
||||
explanation in Network Configuration below. The recommended type is the"), \
|
||||
ItalicText('Bridged adapter'), PlainText("option, but be aware that this \
|
||||
exposes the FreedomBox's services to your entire local network.")])])])]
|
||||
explanation in Network Configuration below. The recommended type is the "), \
|
||||
ItalicText('Bridged adapter'), PlainText(" option, but be aware that this \
|
||||
exposes the FreedomBox's services to your entire local network. ")])])])]
|
||||
|
||||
>>> parse_wiki('After logging in, you can become root with the command \
|
||||
`sudo su`.\\n \\n=== Build Image ===')
|
||||
[Paragraph([PlainText('After logging in, you can become root with the \
|
||||
command'), MonospaceText('sudo su'), PlainText('.')]), \
|
||||
command '), MonospaceText('sudo su'), PlainText('. ')]), \
|
||||
Heading(3, 'Build Image')]
|
||||
|
||||
>>> parse_wiki('Quassel Core will be initialized too.\\n\\n\
|
||||
@ -1246,15 +1260,16 @@ width=394}}\\n\
|
||||
1. Click the `Add` button to launch `Add Core Account` dialog.\\n\
|
||||
')
|
||||
[Paragraph(\
|
||||
[PlainText('Quassel Core will be initialized too.')]), \
|
||||
[PlainText('Quassel Core will be initialized too. ')]), \
|
||||
List('numbered', \
|
||||
[ListItem([Paragraph([PlainText('Launch Quassel Client. You will be greeted \
|
||||
with a wizard to'), MonospaceText('Connect to Core'), PlainText('.')]), \
|
||||
Paragraph([EmbeddedAttachment('quassel-client-1-connect-to-core.png', \
|
||||
[PlainText('Connect to Core')], 'width=394')])]), \
|
||||
ListItem([Paragraph([PlainText('Click the'), MonospaceText('Add'), \
|
||||
PlainText('button to launch'), MonospaceText('Add Core Account'), \
|
||||
PlainText('dialog.')])])])]
|
||||
with a wizard to '), MonospaceText('Connect to Core'), PlainText('. ')]), \
|
||||
Paragraph([PlainText(' '), \
|
||||
EmbeddedAttachment('quassel-client-1-connect-to-core.png', \
|
||||
[PlainText('Connect to Core')], 'width=394'), PlainText(' ')])]), \
|
||||
ListItem([Paragraph([PlainText('Click the '), MonospaceText('Add'), \
|
||||
PlainText(' button to launch '), MonospaceText('Add Core Account'), \
|
||||
PlainText(' dialog. ')])])])]
|
||||
|
||||
"""
|
||||
elements = []
|
||||
@ -1264,7 +1279,7 @@ PlainText('dialog.')])])])]
|
||||
if begin_marker:
|
||||
removed_lines = []
|
||||
while lines:
|
||||
line = lines.pop(0).strip()
|
||||
line = lines.pop(0)
|
||||
removed_lines.append(line)
|
||||
if line.startswith(begin_marker):
|
||||
break
|
||||
@ -1375,9 +1390,10 @@ PlainText('dialog.')])])])]
|
||||
break
|
||||
else:
|
||||
next_list_item += '\n' + line
|
||||
elif candidate.strip().startswith('{{'):
|
||||
elif (candidate.strip().startswith('{{')
|
||||
and next_list_item[-1] != '\n'):
|
||||
# Add line break before inline image
|
||||
next_list_item += '<<BR>>\n' + lines.pop(0)
|
||||
next_list_item += ' <<BR>>\n' + lines.pop(0)
|
||||
else:
|
||||
next_list_item += '\n' + lines.pop(0)
|
||||
|
||||
@ -1396,7 +1412,7 @@ PlainText('dialog.')])])])]
|
||||
list_type = ListType.BULLETED
|
||||
else:
|
||||
list_type = ListType.NUMBERED
|
||||
content = line.lstrip(match.group(2) + ' ')
|
||||
content = ' ' * indent + line.lstrip(match.group(2) + ' ')
|
||||
list_data.append((list_type, indent, content))
|
||||
|
||||
new_list, _ = parse_list(list_data, context)
|
||||
@ -1457,13 +1473,17 @@ PlainText('dialog.')])])])]
|
||||
if line.strip():
|
||||
texts = []
|
||||
br = '<<BR>>'
|
||||
texts.extend(parse_text(line.rstrip(br), context))
|
||||
space_line = line.rstrip(br) if br in line else line + ' '
|
||||
texts.extend(parse_text(space_line, context))
|
||||
if br not in line:
|
||||
# Collect text until next empty line is reached.
|
||||
while lines and lines[0].strip():
|
||||
if end_marker and lines[0].strip().startswith(end_marker):
|
||||
break
|
||||
|
||||
if br in line:
|
||||
break
|
||||
|
||||
# If any of the syntax that ends a paragraph
|
||||
paragraph_breakers = ['{{{', '##', '----', '||']
|
||||
if any([
|
||||
@ -1476,7 +1496,8 @@ PlainText('dialog.')])])])]
|
||||
break
|
||||
|
||||
line = lines.pop(0)
|
||||
texts.extend(parse_text(line.rstrip(br), context))
|
||||
space_line = line.rstrip(br) if br in line else line + ' '
|
||||
texts.extend(parse_text(space_line, context))
|
||||
|
||||
elements.append(Paragraph(texts))
|
||||
|
||||
@ -1492,13 +1513,13 @@ def generate_inner_docbook(parsed_wiki, context=None):
|
||||
>>> generate_inner_docbook([\
|
||||
Heading(1, 'heading 1st level'), \
|
||||
Heading(2, 'heading 2nd level'), \
|
||||
Paragraph([PlainText('plain text')]), \
|
||||
Paragraph([PlainText('plain text ')]), \
|
||||
Heading(3, 'heading 3rd level'), \
|
||||
Heading(2, 'heading 2nd level'), \
|
||||
])
|
||||
'<section><title>heading 1st level</title>\
|
||||
<section><title>heading 2nd level</title>\
|
||||
<para>plain text</para>\
|
||||
<para>plain text </para>\
|
||||
<section><title>heading 3rd level</title>\
|
||||
</section></section>\
|
||||
<section><title>heading 2nd level</title>\
|
||||
@ -1507,8 +1528,8 @@ Heading(2, 'heading 2nd level'), \
|
||||
>>> generate_inner_docbook([Heading(1, 'Date & Time')])
|
||||
'<section><title>Date & Time</title></section>'
|
||||
|
||||
>>> generate_inner_docbook([Paragraph([PlainText('plain text')])])
|
||||
'<para>plain text</para>'
|
||||
>>> generate_inner_docbook([Paragraph([PlainText('plain text ')])])
|
||||
'<para>plain text </para>'
|
||||
|
||||
>>> generate_inner_docbook([Paragraph([Url('https://freedombox.org')])])
|
||||
'<para><ulink url="https://freedombox.org"/></para>'
|
||||
@ -1520,7 +1541,7 @@ Heading(2, 'heading 2nd level'), \
|
||||
'<para><emphasis role="strong">bold</emphasis></para>'
|
||||
|
||||
>>> generate_inner_docbook([Paragraph([\
|
||||
PlainText('normal text followed by'), BoldText('bold text')])])
|
||||
PlainText('normal text followed by '), BoldText('bold text')])])
|
||||
'<para>normal text followed by \
|
||||
<emphasis role="strong">bold text</emphasis></para>'
|
||||
|
||||
@ -1639,9 +1660,9 @@ Url('http://example.com')])])
|
||||
|
||||
>>> generate_inner_docbook([Paragraph([\
|
||||
PlainText('User documentation:')]), \
|
||||
List('bulleted', [ListItem([Paragraph([PlainText('List of'), \
|
||||
List('bulleted', [ListItem([Paragraph([PlainText('List of '), \
|
||||
Link('FreedomBox/Features', [PlainText('applications')]), \
|
||||
PlainText('offered by FreedomBox.')])])])])
|
||||
PlainText(' offered by FreedomBox.')])])])])
|
||||
'<para>User documentation:</para><itemizedlist><listitem><para>List of \
|
||||
<ulink url="https://wiki.debian.org/FreedomBox/Features#">applications\
|
||||
</ulink> offered by FreedomBox.</para></listitem></itemizedlist>'
|
||||
@ -1650,52 +1671,52 @@ PlainText('offered by FreedomBox.')])])])])
|
||||
ListItem([Paragraph([PlainText('Within FreedomBox Service (Plinth)')]), \
|
||||
List('numbered', [ListItem([Paragraph([PlainText('select Apps')])]), \
|
||||
ListItem([Paragraph([PlainText('go to Radicale (Calendar and Addressbook) \
|
||||
and')])]), \
|
||||
and ')])]), \
|
||||
ListItem([Paragraph([PlainText('install the application. After the \
|
||||
installation is complete, make sure the application is marked "enabled" in \
|
||||
the FreedomBox interface. Enabling the application launches the Radicale \
|
||||
CalDAV/CardDAV server.')])]), \
|
||||
ListItem([Paragraph([PlainText('define the access rights:')]), \
|
||||
CalDAV/CardDAV server. ')])]), \
|
||||
ListItem([Paragraph([PlainText('define the access rights: ')]), \
|
||||
List('bulleted', [ListItem([Paragraph([PlainText('Only the owner of a \
|
||||
calendar/addressbook can view or make changes')])]), \
|
||||
calendar/addressbook can view or make changes ')])]), \
|
||||
ListItem([Paragraph([PlainText('Any user can view any calendar/addressbook, \
|
||||
but only the owner can make changes')])]), \
|
||||
but only the owner can make changes ')])]), \
|
||||
ListItem([Paragraph([PlainText('Any user can view or make changes to any \
|
||||
calendar/addressbook')])])])])])])])])
|
||||
calendar/addressbook ')])])])])])])])])
|
||||
'<itemizedlist>\
|
||||
<listitem><para>Within FreedomBox Service (Plinth)</para> \
|
||||
<orderedlist numeration="arabic">\
|
||||
<listitem><para>select Apps</para></listitem>\
|
||||
<listitem><para>go to Radicale (Calendar and Addressbook) and</para>\
|
||||
<listitem><para>go to Radicale (Calendar and Addressbook) and </para>\
|
||||
</listitem>\
|
||||
<listitem><para>install the application. After the installation is complete, \
|
||||
make sure the application is marked "enabled" in the FreedomBox interface. \
|
||||
Enabling the application launches the Radicale CalDAV/CardDAV server.</para>\
|
||||
Enabling the application launches the Radicale CalDAV/CardDAV server. </para>\
|
||||
</listitem>\
|
||||
<listitem><para>define the access rights:</para> \
|
||||
<listitem><para>define the access rights: </para> \
|
||||
<itemizedlist>\
|
||||
<listitem><para>Only the owner of a calendar/addressbook can view or make \
|
||||
changes</para></listitem>\
|
||||
changes </para></listitem>\
|
||||
<listitem><para>Any user can view any calendar/addressbook, but only the \
|
||||
owner can make changes</para></listitem>\
|
||||
<listitem><para>Any user can view or make changes to any calendar/addressbook\
|
||||
owner can make changes </para></listitem>\
|
||||
<listitem><para>Any user can view or make changes to any calendar/addressbook \
|
||||
</para></listitem></itemizedlist>\
|
||||
</listitem></orderedlist>\
|
||||
</listitem></itemizedlist>'
|
||||
|
||||
>>> generate_inner_docbook([Paragraph([PlainText('An alternative to \
|
||||
downloading these images is to'), Link('InstallingDebianOn/TI/BeagleBone', \
|
||||
[PlainText('install Debian')]), PlainText('on the BeagleBone and then'), \
|
||||
downloading these images is to '), Link('InstallingDebianOn/TI/BeagleBone', \
|
||||
[PlainText(' install Debian')]), PlainText(' on the BeagleBone and then '), \
|
||||
Link('FreedomBox/Hardware/Debian', [PlainText('install FreedomBox')]), \
|
||||
PlainText('on it.')])])
|
||||
PlainText(' on it. ')])])
|
||||
'<para>An alternative to downloading these images is to \
|
||||
<ulink url="https://wiki.debian.org/InstallingDebianOn/TI/BeagleBone#">\
|
||||
install Debian</ulink> on the BeagleBone and then \
|
||||
install Debian</ulink> on the BeagleBone and then \
|
||||
<ulink url="https://wiki.debian.org/FreedomBox/Hardware/Debian#">install \
|
||||
FreedomBox</ulink> on it.</para>'
|
||||
FreedomBox</ulink> on it. </para>'
|
||||
|
||||
>>> generate_inner_docbook([Paragraph([PlainText('After Roundcube is \
|
||||
installed, it can be accessed at'), CodeText('https://<your freedombox>\
|
||||
installed, it can be accessed at '), CodeText('https://<your freedombox>\
|
||||
/roundcube'), PlainText('.')])])
|
||||
'<para>After Roundcube is installed, it can be accessed at <code>\
|
||||
https://<your freedombox>/roundcube</code>.</para>'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user