commit abd57a6868a0f8d025cb2627bd98dc3afc38ab4e
parent 4a32febf299c21eab605bcdc300090ab2831b811
Author: Oscar Benedito <oscar@oscarbenedito.com>
Date:   Fri, 30 Apr 2021 17:42:23 +0200

Add vimwiki to HTML script and template

Diffstat:
M.config/nvim/init.vim | 16+++++++++++++++-
A.local/share/vimwiki/template.html | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.local/share/vimwiki/wiki2html.py | 169+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 279 insertions(+), 1 deletion(-)

diff --git a/.config/nvim/init.vim b/.config/nvim/init.vim @@ -41,7 +41,21 @@ autocmd BufRead,BufNewFile *.sty set filetype=tex nnoremap <Leader>n <Plug>IMAP_JumpForward " vimwiki -let g:vimwiki_list=[{ 'path': '~/Documents/wiki', 'path_html': '~/Documents/wiki/build/html', 'custom_wiki2html': '~/Documents/wiki/build/build.py', 'syntax': 'markdown', 'ext': '.md' }] +" first category is used for important meta files to be shown on root, last +" category is used for uncategorized files +let vw_toc='toc.md' +let vw_categories='Arrel Notes Temporal Receptes Blog Projectes Tecnologia Programari Altres Arxiu Sense\ categoria' +let g:vimwiki_list=[{ + \ 'path': '~/Documents/wiki', + \ 'path_html': '~/.cache/vimwiki/html', + \ 'custom_wiki2html': '~/.local/share/vimwiki/wiki2html.py', + \ 'syntax': 'markdown', + \ 'ext': '.md', + \ 'template_path': '~/.local/share/vimwiki/', + \ 'template_default': 'template', + \ 'template_ext': '.html', + \ 'custom_wiki2html_args': vw_toc . ' ' . vw_categories }] + let g:vimwiki_folding='expr' let g:vimwiki_global_ext=0 autocmd FileType vimwiki set foldlevel=1 diff --git a/.local/share/vimwiki/template.html b/.local/share/vimwiki/template.html @@ -0,0 +1,95 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <title>{{ title }} - Oscar's Wiki</title> + <style> +body { + margin: 1em auto; + max-width: 800px; + line-height: 1.5; + padding: 0 1em; + font-family: sans-serif; +} +a { + color: #0022dd; +} +a:hover { + color: #330099; +} +blockquote { + padding: 0 0 0 1.2rem; + border-left: 3px solid #707070; + color: #707070; +} +code { + border: 1px solid #e0e0e0; + background-color: #f5f5f5; + padding: 2px 4px; + font-family: monospace; + line-height: 1.5; + border-radius: 2px; +} +pre { + border-radius: 4px; + overflow-x: auto; + border: 1px solid #e0e0e0; + background-color: #f5f5f5; +} +pre code { + display: block; + padding: 1em; + border: none; + background: none; +} +hr { + border: 0; + border-top: 2px solid #777; + height: 2px; +} +.header { + text-align: center; + margin: 2rem 0; +} +.header > p { + margin: 0; +} +a.nav { + color: #000; + text-decoration: none; +} +a.nav:hover { + text-decoration: underline; +} +.metadata { + display: flex; + justify-content: space-between; + align-items: center; +} +.category { + font-size: 14px; + float: left; + background-color: #e5e5e5; + padding: 5px 20px; + border-radius: 2px; + margin-right: 10px; + margin-bottom: 10px; +} +.date { + margin-right: 0px; + color: #555; +} + </style> + </head> + <body> + <div class="header"> + <p><a style="font-size: 2.5em; font-weight: bold;" class="nav" href="{{ root_path }}index.html">Oscar's Wiki</a></p> + <p><a class="nav" href="{{ root_path }}toc.html">Table of Contents</a></p> + </div> + <hr/> +<h1 style="margin-bottom: .25em;">{{ title }}</h1> +{{ metadata }} +{{ content }} + </body> +</html> diff --git a/.local/share/vimwiki/wiki2html.py b/.local/share/vimwiki/wiki2html.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +# Vimwiki to HTML converter +# Copyright (C) 2021 Oscar Benedito +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +import os +import re +import sys +import datetime +import markdown + + +def get_file_metadata_and_text(file, wiki_path, categories): + metadata = {} + metadata['path'] = os.path.splitext(os.path.relpath(file, wiki_path))[0] + + with open(file, 'r') as f: + text = f.read() + + # read headers and remove from text + end = 0 + for match in re.finditer(r'\s*<!--\s*(.+?)\s*:\s*(.+?)\s*-->\s*|.+', text): + if not match.group(1): + break + metadata[match.group(1)] = match.group(2) + end = match.end() + text = text[end:] + + # read title and remove from text + match = re.match('# (.+?)\s*\n', text) + if match is not None: + metadata['title'] = match.group(1) + text = text[match.end():] + else: + metadata['title'] = metadata['path'] + + # read description + match = re.match('\*([^* ].+?)\*\s*\n', text) + if match is not None: + metadata['description'] = match.group(1) + + if 'category' not in metadata: + metadata['category'] = categories[-1] + + return metadata, text + + +def make_toc_file(path, wiki_path, categories): + # generate categories' data + cat_info = { c : [] for c in categories } + for root, dirs, files in os.walk(wiki_path): + for f in files: + if f[-3:] == '.md' and os.path.join(wiki_path, root, f) != path: + metadata = get_file_metadata_and_text(os.path.join(wiki_path, root, f), wiki_path, categories)[0] + cat_info[metadata['category']].append(metadata) + + # generate markdown + text = '<!-- category: {} -->\n# Table of Contents\n'.format(categories[0]) + for cat, files in cat_info.items(): + if len(files) != 0: + text += '\n## ' + cat + '\n\n' if cat != categories[0] else '\n' + for f in sorted(files, key=lambda x : x['title']): + date = ' (' + f['date'] + ')' if 'date' in f else '' + des = ': ' + f['description'] if 'description' in f else '' + text += '- [' + f['title'] + '](' + f['path'] + ')' + date + des + '\n' + + # print to file + with open(path, 'w') as f: + f.write(text) + + +def href_to_html(match): + href = match.group(2) + if not re.search('://', match.group(2)): + href += '.html' + if match.group(3): + href += '#' + match.group(3).replace(' ', '-').lower() + return "[{}]({})".format(match.group(1), href) + + +def wiki_to_html(input_file, output_file, template_file, root_path, wiki_path, categories): + params, text = get_file_metadata_and_text(input_file, wiki_path, categories) + + # format links for HTML + text = re.sub('\[([^]]+)\]\(([^)#]*)(?:#([^)]*))?\)', href_to_html, text) + + params['metadata'] = '' + if params['category'] != categories[0]: + params['metadata'] += '<span class="category">' + params['category'] + '</span>' + if 'date' in params: + args = [int(i) for i in params['date'].split('-')] + date_str = datetime.date(args[0], args[1], args[2]).strftime('%B %-d, %Y') + params['metadata'] += '<span class="date">' + date_str + '</span>' + if params['metadata'] != '': + params['metadata'] = '<div class="metadata">' + params['metadata'] + '</div>' + + params['root_path'] = root_path + params['content'] = markdown.markdown(text, extensions=['footnotes', 'fenced_code'], tab_length=2) + + with open(template_file, 'r') as f: + html = f.read() + + # render template variables + html = re.sub(r'{{\s*([^}\s]+)\s*}}', lambda m: str(params.get(m.group(1), m.group(0))), html) + + with open(output_file, 'w') as f: + f.write(html) + + +# Arguments received: +# 0. executable path +# 1. force : [0/1] overwrite an existing file +# 2. syntax : the syntax chosen for this wiki +# 3. extension : the file extension for this wiki +# 4. output_dir : the full path of the output directory +# 5. input_file : the full path of the wiki page +# 6. css_file : the full path of the css file for this wiki (ignored here) +# 7. template_path : the full path to the wiki's templates +# 8. template_default : the default template name +# 9. template_ext : the extension of template files +# 10. root_path : a count of ../ for pages buried in subdirs. '-' if in root +# 11. toc_path : path from root to TOC (without extension) +# 12-.categories : the rest of the arguments are the categories in order. +# The first category is used for meta files that should appear directly +# in the root directory. The last category is used for files that have +# not set any category. +if __name__ == '__main__': + output_dir = sys.argv[4] + input_file = sys.argv[5] + output_file = os.path.join(output_dir, os.path.splitext(os.path.basename(input_file))[0]) + '.html' + template_path = os.path.join(sys.argv[7], sys.argv[8]) + sys.argv[9] + root_path = sys.argv[10] if sys.argv[10] != '-' else '' + + # if force is not enabled and HTML is already up to date, exit + if sys.argv[1] == '0' and os.path.getmtime(output_file) > os.path.getmtime(input_file): + sys.exit(0) + + if sys.argv[2] != 'markdown': + print('Error: Unsupported syntax', file=sys.stderr) + sys.exit(1) + + # global variables + wiki_path = os.path.join(os.path.dirname(input_file), root_path) + categories = [ c for c in sys.argv[12:] ] + + # make TOC if any files have been updated after TOC + toc_path = os.path.join(wiki_path, sys.argv[11]) + for root, dirs, files in os.walk(wiki_path): + if os.path.getmtime(toc_path) < os.path.getmtime(os.path.join(wiki_path, root)): + make_toc_file(toc_path, wiki_path, categories) + break + for f in files: + if os.path.getmtime(toc_path) < os.path.getmtime(os.path.join(wiki_path, root, f)): + make_toc_file(toc_path, wiki_path, categories) + break + + wiki_to_html(input_file, output_file, template_path, root_path, wiki_path, categories)