Compare commits

..

No commits in common. "develop" and "experiment/blog-generation" have entirely different histories.

47 changed files with 289 additions and 11226 deletions

1
.gitignore vendored
View File

@ -1,2 +1 @@
_gen _gen
static/syntax.css

View File

@ -1,7 +0,0 @@
all: static/syntax.css
static/syntax.css: static/light-syntax.css static/dark-syntax.css
cat static/light-syntax.css > $@
echo '@media (prefers-color-scheme: dark) { ' >> $@
cat static/dark-syntax.css >> $@
echo '}' >> $@

View File

@ -8,11 +8,9 @@ import os
import time import time
import select import select
import urllib.parse
import inotify.adapters import inotify.adapters
PORT = int(os.getenv('PORT', 8000)) PORT = 8000
THIS_DIR = os.path.dirname(os.path.abspath(__file__)) THIS_DIR = os.path.dirname(os.path.abspath(__file__))
@ -57,34 +55,16 @@ class Server(http.server.SimpleHTTPRequestHandler):
time.sleep(SLEEP_TIME) time.sleep(SLEEP_TIME)
return return
path = urllib.parse.unquote(self.path)
if path.strip('/') == '':
path = '/index.html'
if os.path.isdir(path.strip('/')):
if path.endswith('/'):
path = path.strip('/') + '/index.html'
else:
# Redirect to + /
self.send_response(301)
self.send_header('Location', path + '/')
self.end_headers()
return
if not os.path.exists(path.strip('/')):
self.send_response(404)
self.end_headers()
return
# send 200 response # send 200 response
self.send_response(200) self.send_response(200)
# send response headers # send response headers
self.end_headers() self.end_headers()
with open(path.strip('/'), 'rb') as f: with open(self.path.strip('/'), 'rb') as f:
# send the body of the response # send the body of the response
self.wfile.write(f.read()) self.wfile.write(f.read())
if not path.endswith('.html'): if not self.path.endswith('.html'):
return return
else: else:
# Append update waiter # Append update waiter

View File

@ -12,7 +12,6 @@ MARKDOWN_EXTRA_FEATURES = [
'markdown.extensions.extra', 'markdown.extensions.extra',
] ]
import copy
import json import json
import logging import logging
import sys import sys
@ -25,16 +24,12 @@ import re
from typing import List from typing import List
from bs4 import BeautifulSoup as bs4 from bs4 import BeautifulSoup as bs4
import bs4 as BeautifulSoup
import jinja2 import jinja2
import inotify.adapters import inotify.adapters
import yaml import yaml
import markdown import markdown
from unidecode import unidecode from unidecode import unidecode
SUMMARIZE_MAX_TOKENS = 1000
ITEMS_IN_RSS = 50
NIKOLA_DATE_RE = re.compile(r'^([0-2]\d|30|31)\.(0\d|1[012])\.(\d{4}), (\d{1,2}):(\d{2})$') NIKOLA_DATE_RE = re.compile(r'^([0-2]\d|30|31)\.(0\d|1[012])\.(\d{4}), (\d{1,2}):(\d{2})$')
COMPLETE_DATE_RE = re.compile(r'^(\d{4})-(0\d|1[012])-([0-2]\d|30|31) ' COMPLETE_DATE_RE = re.compile(r'^(\d{4})-(0\d|1[012])-([0-2]\d|30|31) '
@ -47,9 +42,6 @@ ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
STATIC_PATH = os.path.join(ROOT_DIR, 'static') STATIC_PATH = os.path.join(ROOT_DIR, 'static')
ARTICLE_TEMPLATE_NAME = 'article.tmpl.html' ARTICLE_TEMPLATE_NAME = 'article.tmpl.html'
BLOG_INDEX_TEMPLATE_NAME = 'blog_index.tmpl.html' BLOG_INDEX_TEMPLATE_NAME = 'blog_index.tmpl.html'
CATEGORY_LIST_TEMPLATE_NAME = 'category_list.tmpl.html'
ARTICLE_LIST_TEMPLATE_NAME = 'article_list.tmpl.html'
RSS_TEMPLATE_NAME = 'rss.tmpl.xml'
BLOG_INDEX_PAGE_SIZE = 10 BLOG_INDEX_PAGE_SIZE = 10
STATIC_RESOURCES = ( STATIC_RESOURCES = (
@ -63,21 +55,11 @@ JINJA_ENV = jinja2.Environment(
autoescape=jinja2.select_autoescape() autoescape=jinja2.select_autoescape()
) )
WATCH = True
if os.getenv('WATCH_AND_REBUILD', '1') == '0':
WATCH = False
def update_statics(): def update_statics():
global ARTICLE_TEMPLATE global ARTICLE_TEMPLATE
ARTICLE_TEMPLATE = JINJA_ENV.get_template(ARTICLE_TEMPLATE_NAME) ARTICLE_TEMPLATE = JINJA_ENV.get_template(ARTICLE_TEMPLATE_NAME)
global BLOG_INDEX_TEMPLATE global BLOG_INDEX_TEMPLATE
BLOG_INDEX_TEMPLATE = JINJA_ENV.get_template(BLOG_INDEX_TEMPLATE_NAME) BLOG_INDEX_TEMPLATE = JINJA_ENV.get_template(BLOG_INDEX_TEMPLATE_NAME)
global CATEGORY_LIST_TEMPLATE
CATEGORY_LIST_TEMPLATE = JINJA_ENV.get_template(CATEGORY_LIST_TEMPLATE_NAME)
global ARTICLE_LIST_TEMPLATE
ARTICLE_LIST_TEMPLATE = JINJA_ENV.get_template(ARTICLE_LIST_TEMPLATE_NAME)
global RSS_TEMPLATE
RSS_TEMPLATE = JINJA_ENV.get_template(RSS_TEMPLATE_NAME)
update_statics() update_statics()
@ -91,7 +73,6 @@ MONITORED_EVENT_TYPES = (
'IN_DELETE_SELF', 'IN_DELETE_SELF',
'IN_MOVE_SELF', 'IN_MOVE_SELF',
) )
LANG_PRIORITY = ('en', 'es', 'gl')
def parse_nikola_date(match): def parse_nikola_date(match):
@ -126,7 +107,6 @@ def slugify(title):
slug = unidecode(title).lower() slug = unidecode(title).lower()
slug = SLUG_REMOVE_RE.sub('', slug) slug = SLUG_REMOVE_RE.sub('', slug)
slug = SLUG_HYPHENATE_RE.sub('-', slug) slug = SLUG_HYPHENATE_RE.sub('-', slug)
slug = slug.strip('-')
return slug.strip() return slug.strip()
@ -171,8 +151,6 @@ def get_out_path(front_matter):
front_matter['slug'] = slugify(front_matter['title']) front_matter['slug'] = slugify(front_matter['title'])
out_path = os.path.join(str(front_matter['date'].year), front_matter['slug']) out_path = os.path.join(str(front_matter['date'].year), front_matter['slug'])
if front_matter.get('lang', LANG_PRIORITY[0]) != LANG_PRIORITY[0]:
out_path = os.path.join(front_matter['lang'], str(front_matter['date'].year), front_matter['slug'])
return out_path return out_path
@ -181,7 +159,6 @@ def load_all(top_dir_relative):
docs = {} docs = {}
count = 0
for root, dirs, files in os.walk(top): for root, dirs, files in os.walk(top):
for name in files: for name in files:
if all([not name.endswith(ext) for ext in EXTENSIONS]): if all([not name.endswith(ext) for ext in EXTENSIONS]):
@ -193,12 +170,9 @@ def load_all(top_dir_relative):
doc, front_matter = read_markdown(path) doc, front_matter = read_markdown(path)
out_path = get_out_path(front_matter) out_path = get_out_path(front_matter)
docs[path] = (doc, front_matter, out_path) docs[path] = (doc, front_matter, out_path)
print('\rLoading posts... {}'.format(count), end='', flush=True)
count += 1
else: else:
raise NotImplementedError('Unknown filetype: {}'.format(name)) raise NotImplementedError('Unknown filetype: {}'.format(name))
print(" [DONE]")
return docs return docs
@ -208,121 +182,22 @@ def load_doc(filepath):
return (doc, front_matter, out_path) return (doc, front_matter, out_path)
def render_article(doc, front_matter, f, out_path): def render_article(doc, front_matter, f):
extsep = '/' if '/' in out_path else '\\'
subdirs = len(out_path.split(extsep))
base_path = os.path.join(*(['..'] * subdirs))
result = ARTICLE_TEMPLATE.render( result = ARTICLE_TEMPLATE.render(
content=doc, content=doc,
title=front_matter['title'], title=front_matter['title'],
post_publication_date=front_matter['date'], post_publication_date=front_matter['date'],
post_tags=split_tags(front_matter['tags']), post_tags=split_tags(front_matter['tags']),
base_path=base_path,
) )
f.write(result) f.write(result)
def summarize(doc): def summarize(doc):
tree = bs4(doc, features='lxml') return bs4(doc, features='lxml').text[:1000]
html = list(tree.children)[0]
body = list(html.children)[0]
comments = tree.find_all(string=lambda text: isinstance(text, BeautifulSoup.Comment))
teaser_end = None
for comment in comments:
if 'TEASER_END' in comment:
teaser_end = comment
break
if 'gnucash' in doc:
assert teaser_end is not None
def recur_select_to_summarize(source, dest, num_tokens):
for item in source.children:
if num_tokens + len(item.text) < SUMMARIZE_MAX_TOKENS:
# All source fits
num_tokens += len(item.text)
dest.append(item)
else:
if not isinstance(item, BeautifulSoup.NavigableString):
# Let's take as much source as we can and then stop
subsect = bs4()
recur_select_to_summarize(item, subsect, num_tokens)
if len(list(subsect.children)) > 0:
dest.append(subsect)
break
def cut_after_element(reference):
while reference.next_sibling is None:
if reference.parent is None:
logging.warning("Reached root when looking for cutting point for teaser. Doc: {}".format(doc[:100]))
return
reference = reference.parent
nxt = reference.next_sibling
while nxt is not None:
was = nxt
if reference.next_sibling is not None:
# Move to the "right"
nxt = reference.next_sibling
else:
# Move "up and right"
nxt = reference.parent
if nxt is not None:
nxt = nxt.next_sibling
was.extract()
if teaser_end is None:
result = bs4()
recur_select_to_summarize(body, result, 0)
else:
summary = copy.copy(body)
comments = summary.find_all(string=lambda text: isinstance(text, BeautifulSoup.Comment))
teaser_end = None
for comment in comments:
if 'TEASER_END' in comment:
teaser_end = comment
break
assert teaser_end is not None, 'Error finding teaser end on copy'
cut_after_element(teaser_end)
result = bs4()
for child in summary.children:
result.append(child)
return result
def render_index(docs, dest_top): def render_index(docs, dest_top):
# Collect all languages accepted for all docs docs = sorted(docs.values(), key=lambda x: x[1]['date'], reverse=True)
docs_by_slug = {}
for (doc, front_matter, out_path) in docs.values():
if front_matter['slug'] not in docs_by_slug:
docs_by_slug[front_matter['slug']] = {}
docs_by_slug[front_matter['slug']][front_matter.get('lang', LANG_PRIORITY[0])] = (doc, front_matter, out_path)
# Remove duplicated for langs with less priority for off in range(0, len(docs), BLOG_INDEX_PAGE_SIZE):
selected_docs = []
for (doc, front_matter, out_path) in docs.values():
langs = docs_by_slug[front_matter['slug']]
lang_priority = LANG_PRIORITY.index(front_matter.get('lang', LANG_PRIORITY[0]))
min_lang_priority = min([
LANG_PRIORITY.index(lang)
for lang in langs.keys()
])
if lang_priority == min_lang_priority:
selected_docs.append((doc, front_matter, out_path, langs))
docs = sorted(selected_docs, key=lambda x: x[1]['date'], reverse=True)
index_ranges = range(0, len(docs), BLOG_INDEX_PAGE_SIZE)
for off in index_ranges:
page = docs[off: off + BLOG_INDEX_PAGE_SIZE] page = docs[off: off + BLOG_INDEX_PAGE_SIZE]
posts = [ posts = [
@ -332,22 +207,12 @@ def render_index(docs, dest_top):
"post_publication_date": front_matter['date'], "post_publication_date": front_matter['date'],
"post_tags": split_tags(front_matter['tags']), "post_tags": split_tags(front_matter['tags']),
"summary": summarize(doc), "summary": summarize(doc),
"link": out_path.rstrip('/') + '/',
} }
for (doc, front_matter, out_path, _alternatives) in page for (doc, front_matter, out_path) in page
] ]
prev_index_num = None
next_index_num = off // BLOG_INDEX_PAGE_SIZE + 1
if off > 0:
prev_index_num = off // BLOG_INDEX_PAGE_SIZE - 1
if next_index_num >= len(index_ranges):
next_index_num = None
result = BLOG_INDEX_TEMPLATE.render( result = BLOG_INDEX_TEMPLATE.render(
posts=posts, posts=posts,
prev_index_num=prev_index_num,
next_index_num=next_index_num,
) )
if off == 0: if off == 0:
@ -357,104 +222,6 @@ def render_index(docs, dest_top):
with open(os.path.join(dest_top, fname), 'wt') as f: with open(os.path.join(dest_top, fname), 'wt') as f:
f.write(result) f.write(result)
def render_categories(docs, dest_top):
categories = {}
for (doc, front_matter, out_path) in docs.values():
for tag in split_tags(front_matter['tags']):
if tag not in categories:
categories[tag] = []
categories[tag].append((doc, front_matter, out_path))
print("Found {} tags".format(len(categories), categories))
for tag, docs in categories.items():
docs = sorted(docs, key=lambda x: x[1]['date'], reverse=True)
posts = [
{
# "doc": doc,
"title": front_matter['title'],
"post_publication_date": front_matter['date'],
"post_tags": split_tags(front_matter['tags']),
# "summary": summarize(doc),
"link": out_path.rstrip('/') + '/',
}
for (doc, front_matter, out_path) in docs
]
result = CATEGORY_LIST_TEMPLATE.render(
posts=posts,
)
path = os.path.join(dest_top, "tags", tag.replace('/', '_'), "index.html")
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, 'wt') as f:
f.write(result)
def render_archive(docs, dest_top):
docs = sorted(docs.values(), key=lambda x: x[1]['date'], reverse=True)
posts = [
{
# "doc": doc,
"title": front_matter['title'],
"post_publication_date": front_matter['date'],
"post_tags": split_tags(front_matter['tags']),
# "summary": summarize(doc),
"link": out_path.rstrip('/') + '/',
}
for (doc, front_matter, out_path) in docs
]
result = ARTICLE_LIST_TEMPLATE.render(
posts=posts,
)
path = os.path.join(dest_top, "articles", "index.html")
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, 'wt') as f:
f.write(result)
def render_rss(docs, dest_top):
# Collect all languages accepted for all docs
docs_by_slug = {}
for (doc, front_matter, out_path) in docs.values():
if front_matter['slug'] not in docs_by_slug:
docs_by_slug[front_matter['slug']] = {}
docs_by_slug[front_matter['slug']][front_matter.get('lang', LANG_PRIORITY[0])] = (doc, front_matter, out_path)
# Remove duplicated for langs with less priority
selected_docs = []
for (doc, front_matter, out_path) in docs.values():
langs = docs_by_slug[front_matter['slug']]
lang_priority = LANG_PRIORITY.index(front_matter.get('lang', LANG_PRIORITY[0]))
min_lang_priority = min([
LANG_PRIORITY.index(lang)
for lang in langs.keys()
])
if lang_priority == min_lang_priority:
selected_docs.append((doc, front_matter, out_path, langs))
docs = sorted(selected_docs, key=lambda x: x[1]['date'], reverse=True)
posts = [
{
# "doc": doc,
"title": front_matter['title'],
"post_publication_date": front_matter['date'],
"post_tags": split_tags(front_matter['tags']),
"summary": summarize(doc),
"link": out_path.rstrip('/') + '/',
}
for (doc, front_matter, out_path, langs) in docs[:ITEMS_IN_RSS]
]
result = RSS_TEMPLATE.render(
posts=posts,
last_build_date=datetime.datetime.utcnow(),
)
path = os.path.join(dest_top, "rss.xml")
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, 'wt') as f:
f.write(result)
def regen_all(source_top, dest_top, docs=None): def regen_all(source_top, dest_top, docs=None):
if docs is None: if docs is None:
@ -465,11 +232,9 @@ def regen_all(source_top, dest_top, docs=None):
doc_full_path = os.path.join(dest_top, out_path) doc_full_path = os.path.join(dest_top, out_path)
os.makedirs(os.path.dirname(doc_full_path), exist_ok=True) os.makedirs(os.path.dirname(doc_full_path), exist_ok=True)
# print("==", doc_full_path) # print("==", doc_full_path)
full_out_path = doc_full_path + '/index.html' with open(doc_full_path + '.html', 'wt') as f:
os.makedirs(os.path.dirname(full_out_path), exist_ok=True)
with open(full_out_path, 'wt') as f:
try: try:
render_article(doc, front_matter, f, out_path) render_article(doc, front_matter, f)
except: except:
logging.error(traceback.format_exc()) logging.error(traceback.format_exc())
logging.error("Rendering failed 😿") logging.error("Rendering failed 😿")
@ -495,15 +260,6 @@ def regen_all(source_top, dest_top, docs=None):
# Render index # Render index
render_index(docs, dest_top) render_index(docs, dest_top)
# Render categories
render_categories(docs, dest_top)
# Render archive
render_archive(docs, dest_top)
# Render RSS
render_rss(docs, dest_top)
return docs return docs
@ -516,10 +272,6 @@ def main(source_top, dest_top):
docs = regen_all(source_top, dest_top) docs = regen_all(source_top, dest_top)
logging.info("Initial load completed in {:.2f}s".format(time.time() - t0)) logging.info("Initial load completed in {:.2f}s".format(time.time() - t0))
if not WATCH:
logging.info("Build completed in {:.2f}s".format(time.time() - t0))
return 0
## Updating ## Updating
for event in notifier.event_gen(yield_nones=False): for event in notifier.event_gen(yield_nones=False):
(ev, types, directory, file) = event (ev, types, directory, file) = event
@ -562,7 +314,6 @@ def main(source_top, dest_top):
else: else:
try: try:
print("Reloading: {}".format(filepath))
(doc, front_matter, out_path) = load_doc(filepath) (doc, front_matter, out_path) = load_doc(filepath)
except: except:
logging.error(traceback.format_exc()) logging.error(traceback.format_exc())
@ -572,13 +323,11 @@ def main(source_top, dest_top):
t0 = time.time() t0 = time.time()
docs[filepath] = (doc, front_matter, out_path) docs[filepath] = (doc, front_matter, out_path)
doc_full_path = os.path.join(dest_top, out_path) doc_full_path = os.path.join(dest_top, out_path)
print("Updated: {}.html".format(doc_full_path)) os.makedirs(os.path.dirname(doc_full_path), exist_ok=True)
os.makedirs(os.path.dirname(doc_full_path + '/index.html'), exist_ok=True)
# print("==", doc_full_path) # print("==", doc_full_path)
with open(doc_full_path + '/index.html', 'wt') as f: with open(doc_full_path + '.html', 'wt') as f:
try: try:
render_article(doc, front_matter, f, out_path) render_article(doc, front_matter, f)
render_archive(docs, dest_top)
except: except:
logging.error(traceback.format_exc()) logging.error(traceback.format_exc())
logging.error("Rendering failed 😿") logging.error("Rendering failed 😿")

View File

@ -1,165 +0,0 @@
import subprocess
import ops_cache
import copy
import tempfile
import os
@ops_cache.cache
def gen(headline_id, graph, doc_to_headline_remapping):
reference_node = headline_id
font_name = 'monospace'
linked_from_internal = set()
g = copy.deepcopy(graph)
if 'id:' + reference_node in doc_to_headline_remapping:
reference_node = doc_to_headline_remapping['id:' + reference_node].split(':', 1)[1]
centered_graph = { reference_node: g[reference_node] }
for l in g[reference_node]['links']:
lt = l['target']
if lt.startswith("id:"):
lt = lt[3:]
linked_from_internal.add(lt)
del g[reference_node]
new_nodes = True
in_emacs_tree = {
reference_node: set(),
}
while new_nodes:
new_nodes = False
removed = set()
for k, v in g.items():
if 'id:' + k in doc_to_headline_remapping:
k = doc_to_headline_remapping['id:' + k].split(':', 1)[1]
for link in v["links"]:
if link["target"].startswith("id:"):
link["target"] = link["target"][3:]
if link['target'] in centered_graph and link.get('relation') == 'in':
centered_graph[k] = v
for l in v["links"]:
if l.get('relation') == 'in':
t = l['target']
if t.startswith("id:"):
t = t[3:]
if '[' in t:
# Special case, to be handled on org_rw
continue
if t not in in_emacs_tree:
in_emacs_tree[t] = set()
in_emacs_tree[t].add(k)
v['links'] = [
l for l in v["links"]
if l.get('relation') != 'in'
]
for l in v['links']:
lt = l['target']
if lt.startswith("id:"):
lt = lt[3:]
linked_from_internal.add(lt)
removed.add(k)
new_nodes = True
break
for k in removed:
del g[k]
in_emacs = set(centered_graph.keys())
# One more round for the rest, not requiring "in"
for k, v in g.items():
if 'id:' + k in doc_to_headline_remapping:
k = doc_to_headline_remapping['id:' + k].split(':', 1)[1]
backlinked = False
for link in v["links"]:
if link["target"].startswith("id:"):
link["target"] = link["target"][3:]
if link['target'] in in_emacs:
centered_graph[k] = v
backlinked = True
removed.add(k)
if not backlinked and (k in linked_from_internal):
centered_graph[k] = v
removed.add(k)
g = centered_graph
with tempfile.NamedTemporaryFile(suffix='.dot', mode='wt') as f:
f.write('strict digraph {\n')
f.write('maxiter=10000\n')
f.write('splines=curved\n')
# f.write('splines=spline\n') # Not supported with edges to cluster
f.write('node[shape=rect, width=0.5, height=0.5]\n')
f.write('K=0.3\n')
f.write('edge[len = 1]\n')
def draw_subgraph(node_id, depth):
f.write("subgraph cluster_{} {{\n".format(node_id.replace("-", "_")))
f.write(' URL="./{}.node.html"\n'.format(node_id))
f.write(' class="{}"\n'.format('cluster-depth-' + str(depth - 1)))
f.write(" fontname=\"{}\"\n".format(font_name))
f.write(" label=\"{}\"\n".format(g[node_id]['title'].replace("\"", "'")))
f.write("\n")
# print("T: {}".format(in_emacs_tree), file=sys.stderr)
for k in in_emacs_tree[node_id]:
v = g[k]
if k in in_emacs_tree:
draw_subgraph(k, depth=depth + 1)
else:
print(" _" + k.replace("-", "_")
+ "[label=\"" + v["title"].replace("\"", "'") + "\", "
+ "URL=\"" + k + ".node.html\", "
+ "fontname=\"" + font_name + "\", "
+ "class=\"cluster-depth-" + str(depth) + "\""
+ "];", file=f)
f.write("\n}\n")
draw_subgraph(reference_node, 1)
for k, v in g.items():
if k not in in_emacs:
print("_" + k.replace("-", "_")
+ "[label=\"" + v["title"].replace("\"", "'") + "\", "
+ "fontname=\"" + font_name + "\", "
+ "URL=\"" + k + ".node.html\"];", file=f)
for k, v in g.items():
link_src = '_' + k.replace("-", "_")
if k in in_emacs_tree:
link_src = 'cluster_{}'.format(k.replace("-", "_"))
for link in v["links"]:
if link["target"].startswith("id:"):
link["target"] = link["target"][3:]
if '[' in link['target']:
# Special case, to be handled on org_rw
continue
if link['target'] not in g:
# Irrelevant
continue
if link['target'] in in_emacs_tree:
t = 'cluster_{}'.format(link['target'].replace("-", "_"))
else:
t = "_" + link["target"].replace("-", "_")
print(link_src + "->" + t, file=f)
f.write('}\n')
f.flush()
with tempfile.NamedTemporaryFile(suffix='.svg') as fsvg:
subprocess.call(['fdp', f.name, '-Tsvg', '-o', fsvg.name])
fsvg.seek(0)
return fsvg.read().decode()

View File

@ -1,7 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import sqlite3
import time
import json import json
import html import html
import logging import logging
@ -9,12 +7,6 @@ import os
import sys import sys
import uuid import uuid
from datetime import datetime from datetime import datetime
import traceback
import re
from itertools import chain
import shutil
import inotify.adapters
import org_rw import org_rw
from org_rw import OrgTime, dom, Link from org_rw import OrgTime, dom, Link
@ -22,79 +14,12 @@ from org_rw import dump as dump_org
from org_rw import load as load_org from org_rw import load as load_org
from org_rw import token_list_to_raw from org_rw import token_list_to_raw
import pygments
import pygments.lexers
import pygments.formatters
import gen_centered_graph
# Set custom states
for state in ("NEXT", "MEETING", "Q", "PAUSED", "SOMETIME", "TRACK", "WAITING"):
org_rw.DEFAULT_TODO_KEYWORDS.append(state)
for state in ("DISCARDED", "VALIDATING"):
org_rw.DEFAULT_DONE_KEYWORDS.append(state)
EXTENSIONS = [ EXTENSIONS = [
".org", ".org",
".org.txt", ".org.txt",
] ]
IMG_EXTENSIONS = set([
"svg",
"png",
"jpg",
"jpeg",
"gif",
])
SKIPPED_TAGS = set(['attach'])
DEFAULT_SUBPATH = "public"
WATCH = True
if os.getenv('WATCH_AND_REBUILD', '1') == '0':
WATCH = False
MIN_HIDDEN_HEADLINE_LEVEL = 2 MIN_HIDDEN_HEADLINE_LEVEL = 2
INDEX_ID = os.getenv("INDEX_ID", "ea48ec1d-f9d4-4fb7-b39a-faa7b6e2ba95")
SITE_NAME = "Código para llevar"
MONITORED_EVENT_TYPES = (
'IN_CREATE',
# 'IN_MODIFY',
'IN_CLOSE_WRITE',
'IN_DELETE',
'IN_MOVED_FROM',
'IN_MOVED_TO',
'IN_DELETE_SELF',
'IN_MOVE_SELF',
)
TEXT_OR_LINK_RE = re.compile(r'([^\s\[\]]+|.)')
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
STATIC_PATH = os.path.join(ROOT_DIR, 'static')
class NonExistingLocalNoteError(AssertionError):
def __init__(self, note_id, src_headline):
AssertionError.__init__(self)
self.note_id = note_id
self.src_headline = src_headline
def get_message(self):
return ("Cannot follow link to '{}' on headline '{}' ({})"
.format(self.note_id,
self.src_headline.id,
self.src_headline.title.get_text().strip()))
def is_git_path(path):
return any([chunk == ".git" for chunk in path.split(os.sep)])
def create_db(path):
if os.path.exists(path):
os.unlink(path)
db = sqlite3.connect(path)
db.execute('CREATE VIRTUAL TABLE note_search USING fts5(note_id, title, body, top_level_title, is_done, is_todo, tokenize="trigram");')
return db
def load_all(top_dir_relative): def load_all(top_dir_relative):
top = os.path.abspath(top_dir_relative) top = os.path.abspath(top_dir_relative)
@ -121,46 +46,26 @@ def load_all(top_dir_relative):
logging.info("Collected {} files".format(len(docs))) logging.info("Collected {} files".format(len(docs)))
return docs return docs
def regen_all(src_top, dest_top, subpath, *, docs=None, db=None):
files_generated = 0
cur = db.cursor()
cleaned_db = False
try:
cur.execute('DELETE FROM note_search;')
cleaned_db = True
except sqlite3.OperationalError as err:
if WATCH:
logging.warning("Error pre-cleaning DB, search won't be updated")
else:
raise
def main(src_top, dest_top):
docs = load_all(src_top) docs = load_all(src_top)
base_dirs = set() files_generated = 0
doc_to_headline_remapping = {} doc_to_headline_remapping = {}
os.makedirs(dest_top, exist_ok=True) os.makedirs(dest_top, exist_ok=True)
graph = {}
## Build headline list
# This includes a virtual headline for ID-referenced documents.
all_headlines = []
main_headlines_by_path = {}
main_headline_to_docid = {}
for doc in docs: for doc in docs:
relpath = os.path.relpath(doc.path, src_top) relpath = os.path.relpath(doc.path, src_top)
changed = False changed = False
headlines = list(doc.getAllHeadlines()) headlines = list(doc.getAllHeadlines())
related = None related = None
if not relpath.startswith(subpath + "/"):
# print("Skip:", relpath)
continue
base_dirs.add(os.path.dirname(relpath))
i = len(headlines) i = len(headlines)
while i > 0: while i > 0:
i -= 1 i -= 1
headline = headlines[i] headline = headlines[i]
if headline.title.get_text().strip().lower() == "related" and headline.depth == 1: if headline.title.strip().lower() == "related" and headline.depth == 1:
if related is not None: if related is not None:
print( print(
"Found duplicated related: {} vs {}".format( "Found duplicated related: {} vs {}".format(
@ -180,7 +85,10 @@ def regen_all(src_top, dest_top, subpath, *, docs=None, db=None):
print("Updated", relpath) print("Updated", relpath)
save_changes(doc) save_changes(doc)
all_headlines.extend(headlines) if not relpath.startswith("public/"):
# print("Skip:", relpath)
continue
main_headline = None main_headline = None
topHeadlines = doc.getTopHeadlines() topHeadlines = doc.getTopHeadlines()
@ -188,10 +96,12 @@ def regen_all(src_top, dest_top, subpath, *, docs=None, db=None):
or (len(topHeadlines) == 2 and related is not None)): or (len(topHeadlines) == 2 and related is not None)):
main_headline = [h for h in topHeadlines if h != related][0] main_headline = [h for h in topHeadlines if h != related][0]
main_headlines_by_path[doc.path] = main_headline
if doc.id is not None: if doc.id is not None:
endpath = os.path.join(dest_top, doc.id + ".node.html")
with open(endpath, "wt") as f:
doc_to_headline_remapping['id:' + doc.id] = 'id:' + main_headline.id doc_to_headline_remapping['id:' + doc.id] = 'id:' + main_headline.id
main_headline_to_docid[main_headline.id] = doc.id
f.write(as_document(render(main_headline, doc, headlineLevel=0)))
files_generated += 1 files_generated += 1
elif doc.id is not None: elif doc.id is not None:
logging.error("Cannot render document from id: {}. {} headlines {} related".format( logging.error("Cannot render document from id: {}. {} headlines {} related".format(
@ -200,10 +110,9 @@ def regen_all(src_top, dest_top, subpath, *, docs=None, db=None):
'with' if related is not None else 'without' 'with' if related is not None else 'without'
)) ))
# Build graph for headline in headlines:
graph = {} endpath = os.path.join(dest_top, headline.id + ".node.html")
backlink_graph = {}
for headline in all_headlines:
links = [] links = []
headline_links = list(headline.get_links()) headline_links = list(headline.get_links())
if headline == main_headline and related is not None: if headline == main_headline and related is not None:
@ -237,41 +146,15 @@ def regen_all(src_top, dest_top, subpath, *, docs=None, db=None):
"target": headline.parent.id, "target": headline.parent.id,
"relation": "in" "relation": "in"
}) })
for backlink in links:
if 'relation' in backlink and backlink['relation'] == 'in':
continue
target = backlink['target']
if target.startswith('id:'):
target = target[len('id:'):]
if target not in backlink_graph:
backlink_graph[target] = set()
backlink_graph[target].add(headline.id)
graph[headline.id] = { graph[headline.id] = {
"title": org_rw.org_rw.token_list_to_plaintext(headline.title.contents).strip(), "title": headline.title.strip(),
"links": links, "links": links,
"depth": headline.depth, "depth": headline.depth,
} }
if headline.id in main_headline_to_docid:
graph[main_headline_to_docid[headline.id]] = graph[headline.id]
topLevelHeadline = headline with open(endpath, "wt") as f:
while isinstance(topLevelHeadline.parent, org_rw.Headline): f.write(as_document(render(headline, doc, headlineLevel=0)))
topLevelHeadline = topLevelHeadline.parent files_generated += 1
# Save for full-text-search
cur.execute('''INSERT INTO note_search(note_id, title, body, top_level_title, is_done, is_todo) VALUES (?, ?, ?, ?, ?, ?);''',
(
headline.id,
headline.title.get_text(),
'\n'.join(headline.doc.dump_headline(headline, recursive=False)),
topLevelHeadline.title.get_text(),
headline.is_done,
headline.is_todo,
))
# Update graph, replace document ids with headline ids # Update graph, replace document ids with headline ids
for headline_data in graph.values(): for headline_data in graph.values():
@ -279,17 +162,6 @@ def regen_all(src_top, dest_top, subpath, *, docs=None, db=None):
if link['target'] in doc_to_headline_remapping: if link['target'] in doc_to_headline_remapping:
link['target'] = doc_to_headline_remapping[link['target']] link['target'] = doc_to_headline_remapping[link['target']]
# Remap document ids backlinks to main headlines
for doc_id, main_headline_id in doc_to_headline_remapping.items():
if doc_id.startswith('id:'):
doc_id = doc_id[len('id:'):]
if main_headline_id.startswith('id:'):
main_headline_id = main_headline_id[len('id:'):]
for backlink in backlink_graph.get(doc_id, []):
if main_headline_id not in backlink_graph:
backlink_graph[main_headline_id] = set()
backlink_graph[main_headline_id].add(backlink)
# Output graph files # Output graph files
graphpath = os.path.join(dest_top, "graph.json") graphpath = os.path.join(dest_top, "graph.json")
graph_explorer_path = os.path.join(dest_top, "graph.html") graph_explorer_path = os.path.join(dest_top, "graph.html")
@ -303,438 +175,115 @@ def regen_all(src_top, dest_top, subpath, *, docs=None, db=None):
json.dumps(graph))) json.dumps(graph)))
logging.info("Generated {} files".format(files_generated)) logging.info("Generated {} files".format(files_generated))
# Render docs after we've built the graph
# Render main headlines
full_graph_info = { "nodes": graph, "backlinks": backlink_graph, "main_headlines": main_headlines_by_path }
for _docpath, main_headline in main_headlines_by_path.items():
if main_headline.doc.id:
endpath = os.path.join(dest_top, main_headline.doc.id + ".node.html")
with open(endpath, "wt") as f:
f.write(render_as_document(main_headline, main_headline.doc, headlineLevel=0, graph=full_graph_info,
doc_to_headline_remapping=doc_to_headline_remapping,
title=org_rw.token_list_to_plaintext(main_headline.title.contents)))
# Render all headlines def print_tree(tree, indentation=0):
for headline in all_headlines:
endpath = os.path.join(dest_top, headline.id + ".node.html")
# Render HTML
with open(endpath, "wt") as f:
f.write(render_as_document(headline, headline.doc, headlineLevel=0, graph=full_graph_info,
doc_to_headline_remapping=doc_to_headline_remapping,
title=org_rw.token_list_to_plaintext(headline.title.contents)))
files_generated += 1
if headline.id == INDEX_ID:
index_endpath = os.path.join(dest_top, "index.html")
with open(index_endpath, "wt") as f:
f.write(render_as_document(headline, headline.doc, headlineLevel=0, graph=full_graph_info,
doc_to_headline_remapping=doc_to_headline_remapping,
title=org_rw.token_list_to_plaintext(headline.title.contents)))
files_generated += 1
cur.close()
db.commit()
logging.info("Copying attachments")
attachments_dir = os.path.join(dest_top, 'attachments')
os.makedirs(attachments_dir, exist_ok=True)
for base in base_dirs:
data_dir = os.path.join(src_top, base, 'data')
logging.info("Copying attachments from: {}".format(data_dir))
if not os.path.exists(data_dir):
continue
for subdir in os.listdir(data_dir):
shutil.copytree(os.path.join(data_dir, subdir),
os.path.join(attachments_dir, subdir),
dirs_exist_ok=True)
def main(src_top, dest_top, subpath):
notifier = inotify.adapters.InotifyTrees([src_top, STATIC_PATH])
## Initial load
t0 = time.time()
os.makedirs(dest_top, exist_ok=True)
db = create_db(os.path.join(dest_top, 'db.sqlite3'))
docs = regen_all(src_top, dest_top, subpath=subpath, db=db)
if not WATCH:
logging.info("Build completed in {:.2f}s".format(time.time() - t0))
return 0
logging.info("Initial load completed in {:.2f}s".format(time.time() - t0))
## Updating
for event in notifier.event_gen(yield_nones=False):
(ev, types, directory, file) = event
if not any([type in MONITORED_EVENT_TYPES for type in types]):
continue
if is_git_path(directory):
continue
filepath = os.path.join(directory, file)
print("CHANGED: {}".format(filepath))
t0 = time.time()
try:
docs = regen_all(src_top, dest_top, subpath=subpath, docs=docs, db=db)
except:
logging.error(traceback.format_exc())
logging.error("Loading new templates failed 😿")
continue
logging.info("Updated all in {:.2f}s".format(time.time() - t0))
def get_headline_with_name(target_name, doc):
target_name = target_name.strip()
for headline in doc.getAllHeadlines():
if headline.title.get_text().strip() == target_name:
return headline
return None
def assert_id_exists(id, src_headline, graph):
if id not in graph["nodes"]:
raise NonExistingLocalNoteError(id, src_headline)
def print_tree(tree, indentation=0, headline=None):
# if headline and headline.id != INDEX_ID:
# return
return return
for element in tree: for element in tree:
print(" " * indentation + "- " + str(type(element)))
if "children" in dir(element): if "children" in dir(element):
if len(element.children) > 0: if len(element.children) > 0:
print_element(element.children, indentation + 1, headline) print_tree(element.children, indentation + 1)
print() print()
elif "content" in dir(element):
for content in element.content:
print_element(content, indentation + 1, headline)
def print_element(element, indentation, headline): def render_property_drawer(element, acc):
if isinstance(element, org_rw.Link):
print(" " * indentation, "Link:", element.get_raw())
elif isinstance(element, str):
print(" " * indentation, "{" + element + "}", type(element))
else:
print_tree(element, indentation, headline)
def render_property_drawer(element, acc, headline, graph):
pass pass
def render_logbook_drawer(element, acc, headline, graph): def render_logbook_drawer(element, acc):
pass pass
def render_property_node(element, acc, headline, graph): def render_property_node(element, acc):
pass pass
def render_list_group(element, acc, headline, graph): def render_list_group(element, acc):
acc.append("<ul>") acc.append("<ul>")
render_tree(element.children, acc, headline, graph) render_tree(element.children, acc)
acc.append("</ul>") acc.append("</ul>")
def render_table(element, acc, headline, graph):
acc.append("<table>")
render_tree(element.children, acc, headline, graph)
acc.append("</table>")
def render_table_row(element, acc, headline, graph): def render_list_item(element, acc):
acc.append("<tr>")
for cell in element.cells:
acc.append("<td>")
acc.append(html.escape(cell))
acc.append("</td>")
acc.append("</tr>")
def render_table_separator_row(element, acc, headline, graph):
acc.append("<tr class='__table-separator'></tr>")
def render_list_item(element, acc, headline, graph):
acc.append("<li>") acc.append("<li>")
if element.tag is not None: if element.tag is not None:
acc.append("<span class='tag'>") acc.append("<span class='tag'>")
render_text_tokens(element.tag, acc, headline, graph) acc.append(html.escape(element.tag))
acc.append("</span>") acc.append("</span>")
acc.append("<span class='item'>") acc.append("<span class='item'>")
render_text_tokens(element.content, acc, headline, graph) render_text_tokens(element.content, acc)
acc.append("</span></li>") acc.append("</span></li>")
def render_block(content, acc, _class, is_code):
acc.append('<pre class="{}">'.format(_class))
if is_code:
acc.append('<code>')
# Remove indentation common to all lines def render_code_block(element, acc):
acc.append(unindent(content)) acc.append('<pre><code>')
if is_code: acc.append(html.escape(element.lines))
acc.append('</code>') acc.append('</code></pre>')
acc.append('</pre>')
def unindent(content): def render_results_block(element, acc):
base_indentation = min([ # TODO:
len(l) - len(l.lstrip(' ')) # acc.append('<pre class="results"><code>')
for l in content.split('\n') # render_tree(element.children, acc)
if len(l.strip()) > 0 # acc.append('</code></pre>')
])
content_lines = [
l[base_indentation:]
for l in content.split('\n')
]
return '\n'.join(content_lines)
def render_code_block(element, acc, headline, graph):
code = element.lines
if element.arguments is not None and len(element.arguments) > 0 :
try:
lexer = pygments.lexers.get_lexer_by_name(element.arguments.split()[0], stripall=True)
content = pygments.highlight(unindent(code),
lexer,
pygments.formatters.HtmlFormatter()
)
acc.append(content)
return
except pygments.util.ClassNotFound:
pass pass
logging.error("Cannot find lexer for {}".format(element.subtype.lower()))
content = html.escape(code)
render_block(content, acc, _class='code ' + element.subtype.lower(), is_code=True)
def render_results_block(element, acc, headline, graph): def render_text(element, acc):
items = [e.get_raw() for e in element.children] acc.append('<span class="text">')
content = '\n'.join(items) render_text_tokens(element.content, acc)
if len(content.strip()) > 0: acc.append('</span>')
render_block(content, acc, _class='results lang-text', is_code=False)
def render_org_text(element, acc, headline, graph): def render_text_tokens(tokens, acc):
as_dom = org_rw.text_to_dom(element.contents, element)
render_text_tokens(as_dom, acc, headline, graph)
def render_text(element, acc, headline, graph):
acc.append('<div class="text">')
render_text_tokens(element.content, acc, headline, graph)
acc.append('</div>')
def render_text_tokens(tokens, acc, headline, graph):
acc.append('<p>')
if isinstance(tokens, org_rw.Text):
tokens = tokens.contents
for chunk in tokens: for chunk in tokens:
if isinstance(chunk, str): if isinstance(chunk, str):
lines = chunk.split('\n\n') acc.append('{}</span> '.format(chunk))
contents = []
for line in lines:
line_chunks = []
for word in TEXT_OR_LINK_RE.findall(line):
if '://' in word and not (word.startswith('org-protocol://')):
if not (word.startswith('http://')
or word.startswith('https://')
or word.startswith('ftp://')
or word.startswith('ftps://')
):
logging.warning('Is this a link? {} (on {})\nLine: {}\nChunks: {}'.format(word, headline.doc.path, line, line_chunks))
line_chunks.append(html.escape(word))
else:
line_chunks.append('<a href="{url}" class="external">{description}</a>'
.format(url=word,
description=html.escape(word)))
else:
line_chunks.append(html.escape(word))
contents.append(' '.join(line_chunks))
acc.append('<span class="line">{}</span>'.format('</p><p>'.join(contents)))
elif isinstance(chunk, Link): elif isinstance(chunk, Link):
link_target = chunk.value link_target = chunk.value
is_internal_link = True if link_target.startswith('id:'):
link_target = './' + link_target[3:] + '.node.html'
description = chunk.description description = chunk.description
if description is None: if description is None:
description = chunk.value description = chunk.value
try: acc.append('<a href="{}">{}</a>'.format(
if link_target.startswith('id:'):
assert_id_exists(link_target[3:], headline, graph)
link_target = './' + link_target[3:] + '.node.html'
elif link_target.startswith('./') or link_target.startswith('../'):
if '::' in link_target:
logging.warning('Not implemented headline links to other files. Used on {}'.format(link_target))
else:
target_path = os.path.abspath(os.path.join(os.path.dirname(headline.doc.path), link_target))
if target_path not in graph['main_headlines']:
logging.warning('Link to doc not in graph: {}'.format(target_path))
else:
assert_id_exists(graph['main_headlines'][target_path].id, headline, graph)
link_target = './' + graph['main_headlines'][target_path].id + '.node.html'
elif link_target.startswith('attachment:'):
inner_target = link_target.split(':', 1)[1]
link_target = 'attachments/{}/{}/{}'.format(headline.id[:2], headline.id[2:], inner_target)
logging.warning('Not implemented `attachment:` links. Used on {}'.format(link_target))
elif link_target.startswith('* '):
target_headline = get_headline_with_name(link_target.lstrip('* '), headline.doc)
if target_headline is None:
logging.warning('No headline found corresponding to {}. On file {}'.format(link_target, headline.doc.path))
else:
assert_id_exists(target_headline.id, headline, graph)
link_target = './' + target_headline.id + '.node.html'
else:
is_internal_link = False
if link_target.startswith('orgit-rev'):
raise NonExistingLocalNoteError(link_target, headline)
elif link_target.startswith('file:'):
raise NonExistingLocalNoteError(link_target, headline)
elif not (
link_target.startswith('https://')
or link_target.startswith('http://')
or link_target.startswith('/')
):
raise NotImplementedError('Unknown link type: {}'
.format(link_target))
if link_target.rsplit('.', 1)[-1].lower() in IMG_EXTENSIONS:
acc.append('<a href="{}" class="img img-{}" ><img src="{}" /></a>'.format(
html.escape(link_target), html.escape(link_target),
'internal' if is_internal_link else 'external',
html.escape(link_target),
))
else:
acc.append('<a href="{}" class="{}" >{}</a>'.format(
html.escape(link_target),
'internal' if is_internal_link else 'external',
html.escape(description), html.escape(description),
)) ))
except NonExistingLocalNoteError as err: # else:
logging.warning(err.get_message()) # raise NotImplementedError('TextToken: {}'.format(chunk))
acc.append(html.escape(description))
elif isinstance(chunk, org_rw.MarkerToken):
tag = '<'
if chunk.closing:
tag += '/'
tag += {
org_rw.MarkerType.BOLD_MODE: 'strong',
org_rw.MarkerType.CODE_MODE: 'code',
org_rw.MarkerType.ITALIC_MODE: 'em',
org_rw.MarkerType.STRIKE_MODE: 's',
org_rw.MarkerType.UNDERLINED_MODE: 'span class="underlined"' if not chunk.closing else 'span',
org_rw.MarkerType.VERBATIM_MODE: 'span class="verbatim"' if not chunk.closing else 'span',
}[chunk.tok_type]
tag += '>'
acc.append(tag)
else:
raise NotImplementedError('TextToken: {}'.format(chunk))
acc.append('</p>')
def render_tag(element, acc, headline, graph): def render_tag(element, acc):
return { return {
dom.PropertyDrawerNode: render_property_drawer, dom.PropertyDrawerNode: render_property_drawer,
dom.LogbookDrawerNode: render_logbook_drawer, dom.LogbookDrawerNode: render_logbook_drawer,
dom.PropertyNode: render_property_node, dom.PropertyNode: render_property_node,
dom.ListGroupNode: render_list_group, dom.ListGroupNode: render_list_group,
dom.ListItem: render_list_item, dom.ListItem: render_list_item,
dom.TableNode: render_table,
dom.TableSeparatorRow: render_table_separator_row,
dom.TableRow: render_table_row,
dom.CodeBlock: render_code_block, dom.CodeBlock: render_code_block,
dom.Text: render_text, dom.Text: render_text,
dom.ResultsDrawerNode: render_results_block, dom.ResultsDrawerNode: render_results_block,
org_rw.Text: render_org_text, }[type(element)](element, acc)
}[type(element)](element, acc, headline, graph)
def render_tree(tree, acc, headline, graph): def render_tree(tree, acc):
for element in tree: for element in tree:
render_tag(element, acc, headline, graph) render_tag(element, acc)
def render_inline(tree, f, headline, graph):
acc = []
f(tree, acc, headline, graph)
return ''.join(acc)
def render_as_document(headline, doc, headlineLevel, graph, title, doc_to_headline_remapping): def render(headline, doc, headlineLevel):
if isinstance(headline.parent, org_rw.Headline):
topLevelHeadline = headline.parent
while isinstance(topLevelHeadline.parent, org_rw.Headline):
topLevelHeadline = topLevelHeadline.parent
return f"""<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{title} @ {SITE_NAME}</title>
<meta http-equiv="refresh" content="0;./{topLevelHeadline.id}.node.html#{headline.id}" />
<link href="../static/style.css" rel="stylesheet"/>
<link href="../static/syntax.css" rel="stylesheet"/>
</head>
<body>
<nav>
<h1><a href="./index.html">Código para llevar [Notes]</a></h1>
</nav>
<a href='./{topLevelHeadline.id}.node.html#{headline.id}'>Sending you to the main note... [{org_rw.token_list_to_plaintext(topLevelHeadline.title.contents)}]</a>
</body>
</html>
"""
else:
return as_document(render(headline, doc, graph=graph, headlineLevel=headlineLevel,
doc_to_headline_remapping=doc_to_headline_remapping),
title, render_toc(doc))
def render_toc(doc):
acc = ['<ul class="toc">']
for headline in doc.getTopHeadlines():
render_toc_headline(headline, acc)
acc.append('</ul>')
if sum([chunk == '<li>' for chunk in acc]) < 2:
# If < 2 headlines, ignore it
return None
return ''.join(acc)
def render_toc_headline(headline, acc):
acc.append('<li>')
acc.append(f'<a href="#{headline.id}">{html.escape(headline.title.get_text())}</a>')
children = list(headline.children)
if children:
acc.append('<ul>')
for child in children:
render_toc_headline(child, acc)
acc.append('</ul>')
acc.append('</li>')
def render_connections(headline_id, content, graph, doc_to_headline_remapping):
# if headline_id != 'aa29be89-70e7-4465-91ed-361cf0ce62f2':
# return
logging.info("Generating centered graph for {}".format(headline_id))
svg = gen_centered_graph.gen(headline_id, graph['nodes'], doc_to_headline_remapping)
content.append("<div class='connections'>{}</div>".format(svg))
def render(headline, doc, graph, headlineLevel, doc_to_headline_remapping):
try: try:
dom = headline.as_dom() dom = headline.as_dom()
except: except:
logging.error("Error generating DOM for {}".format(doc.path)) logging.error("Error generating DOM for {}".format(doc.path))
raise raise
print_tree(dom, indentation=2, headline=headline) print_tree(dom)
content = [] content = []
if headline.id and headlineLevel == 0: render_tree(dom, content)
render_connections(headline.id, content, graph, doc_to_headline_remapping=doc_to_headline_remapping)
render_tree(dom, content, headline, graph)
for child in headline.children: for child in headline.children:
content.append(render(child, doc, headlineLevel=headlineLevel+1, graph=graph, content.append(render(child, doc, headlineLevel=headlineLevel+1))
doc_to_headline_remapping=doc_to_headline_remapping))
if headline.state is None: if headline.state is None:
state = "" state = ""
@ -746,32 +295,17 @@ def render(headline, doc, graph, headlineLevel, doc_to_headline_remapping):
else: else:
todo_state = "done" todo_state = "done"
tag_list = [] display_state = 'collapsed'
for tag in headline.shallow_tags: if headlineLevel < MIN_HIDDEN_HEADLINE_LEVEL:
if tag.lower() not in SKIPPED_TAGS:
tag_list.append(f'<span class="tag">{html.escape(tag)}</span>')
tags = f'<span class="tags">{"".join(tag_list)}</span>'
display_state = 'expanded' display_state = 'expanded'
# # Update display based on document STARTUP config
# visual_level = doc.get_keywords('STARTUP', 'showall')
# if visual_level.startswith('show') and visual_level.endswith('levels'):
# visual_level_num = int(visual_level[len('show'):-len('levels')]) - 1
# # Note that level is 0 indexed inside this loop
# if headlineLevel >= visual_level_num:
# display_state = 'collapsed'
title = render_inline(headline.title, render_tag, headline, graph)
if headlineLevel > 0:
title = f"<a href=\"#{html.escape(headline.id)}\">{title}</a>"
return f""" return f"""
<div id="{html.escape(headline.id)}" class="node {todo_state} {display_state}"> <div id="{html.escape(headline.id)}" class="node {todo_state} {display_state}">
<h1 class="title"> <h1 class="title">
{state} {state}
{title} <a href=\"javascript:toggle_expand('{html.escape(headline.id)}')\">
{tags} {html.escape(headline.title)}
</a>
</h1> </h1>
<div class='contents'> <div class='contents'>
{''.join(content)} {''.join(content)}
@ -780,40 +314,27 @@ def render(headline, doc, graph, headlineLevel, doc_to_headline_remapping):
""" """
def as_document(html, title, global_toc): def as_document(html):
body_classes = []
if global_toc is None:
toc_section = ""
body_classes.append('no-toc')
else:
toc_section = f"""
<div class="global-table-of-contents">
<h2>Table of contents</h2>
{global_toc}
</div>
"""
return f"""<!DOCTYPE html> return f"""<!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8">
<title>{title} @ {SITE_NAME}</title>
<link href="../static/style.css" rel="stylesheet"/> <link href="../static/style.css" rel="stylesheet"/>
<link href="../static/syntax.css" rel="stylesheet"/> <script type="text/javascript">
<!-- v Fixes mobile viewports. --> function toggle_expand(header_id) {{
<meta name="HandheldFriendly" content="True"> var e = document.getElementById(header_id);
<meta name="MobileOptimized" content="320"> if (e.classList.contains('expanded')) {{
<meta name="viewport" content="width=device-width, initial-scale=1"> e.classList.add('collapsed');
e.classList.remove('expanded');
}}
else {{
e.classList.add('expanded');
e.classList.remove('collapsed');
}}
}}
</script>
</head> </head>
<body class="{' '.join(body_classes)}"> <body>
<nav>
<h1><a href="./index.html">Código para llevar [Notes]</a></h1>
<input type="text" id="searchbox" disabled="true" placeholder="Search (requires JS)" />
</nav>
{toc_section}
{html} {html}
<script src="../static/search-box.js"></script>
<script tye="text/javascript">_codigoparallevar_enable_search_box('#searchbox', {{placeholder: 'Search...'}})</script>
</body> </body>
</html> </html>
""" """
@ -826,13 +347,9 @@ def save_changes(doc):
if __name__ == "__main__": if __name__ == "__main__":
if len(sys.argv) not in (3, 4): if len(sys.argv) != 3:
print("Usage: {} SOURCE_TOP DEST_TOP <SUBPATH>".format(sys.argv[0])) print("Usage: {} SOURCE_TOP DEST_TOP".format(sys.argv[0]))
exit(0) exit(0)
logging.basicConfig(level=logging.INFO, format="%(levelname)-8s %(message)s") logging.basicConfig(level=logging.INFO, format="%(levelname)-8s %(message)s")
subpath = DEFAULT_SUBPATH main(sys.argv[1], sys.argv[2])
if len(sys.argv) == 4:
subpath = sys.argv[3]
exit(main(sys.argv[1], sys.argv[2], subpath=subpath))

View File

@ -1,75 +0,0 @@
import sqlite3
import json
import logging
from typing import Optional
import xdg
import os
import datetime
CACHE_DB: Optional[sqlite3.Connection] = None
CACHE_PATH = os.path.join(xdg.xdg_cache_home(), 'codigoparallevar', 'ops.sqlite3')
def init_db():
global CACHE_DB
os.makedirs(os.path.dirname(CACHE_PATH), exist_ok=True)
CACHE_DB = sqlite3.connect(CACHE_PATH)
cur = CACHE_DB.cursor()
cur.execute('''CREATE TABLE IF NOT EXISTS ops(
in_val TEXT PRIMARY KEY,
code TEXT,
out_val TEXT,
added_at DateTime
);
''')
CACHE_DB.commit()
cur.close()
def query_cache(in_val, code):
if CACHE_DB is None:
init_db()
assert CACHE_DB is not None
cur = CACHE_DB.cursor()
cur.execute('''SELECT out_val FROM ops WHERE in_val = ? AND code = ?''', (in_val, code))
# Should return only one result, right? 🤷
results = cur.fetchall()
assert len(results) < 2
if len(results) == 0:
return None
else:
return results[0][0]
def save_cache(in_val, code, out_val):
if CACHE_DB is None:
init_db()
assert CACHE_DB is not None
cur = CACHE_DB.cursor()
cur.execute('''
INSERT INTO ops(in_val, code, out_val, added_at)
VALUES (?, ?, ?, ?);''',
(in_val, code, out_val, datetime.datetime.now()))
CACHE_DB.commit()
cur.close()
def cache(fun):
fun_code = fun.__code__.co_code.decode('latin-1')
def wrapped(*kargs, **kwargs):
in_val = json.dumps({
'kargs': kargs,
'kwargs': kwargs,
'fun_code': fun_code,
})
cache_result = query_cache(in_val, fun_code)
found_in_cache = cache_result is not None
if not found_in_cache:
out_val = fun(*kargs, **kwargs)
save_cache(in_val, fun_code, out_val)
else:
out_val = cache_result
logging.info("{} bytes in, {} bytes out (in_cache: {})".format(len(in_val), len(out_val), found_in_cache))
return out_val
return wrapped

View File

@ -1,3 +1,2 @@
Markdown Markdown
Jinja2 Jinja2
pygments

View File

@ -1,15 +0,0 @@
#!/usr/bin/env bash
set -eu
PORT=${PORT:-3001}
cd "$(dirname "$0")/search-server"
docker build -t search-server .
cd ../../_gen/notes/
set -x
exec docker run -it --rm -p $PORT:80 -e PORT=80 -e DB_PATH=/db.sqlite3 -v `pwd`/db.sqlite3:/db.sqlite3:ro search-server

View File

@ -1 +0,0 @@
search-server

View File

@ -1,47 +0,0 @@
FROM golang:1.19-alpine as builder
# Install build dependencies
RUN apk add alpine-sdk
# Create appuser.
ENV USER=appuser
ENV UID=10001
# See https://stackoverflow.com/a/55757473/12429735
RUN adduser \
--disabled-password \
--gecos "" \
--home "/nonexistent" \
--shell "/sbin/nologin" \
--no-create-home \
--uid "${UID}" \
"${USER}"
# Prepare dependencies
RUN mkdir /build
ADD go.mod go.sum /build/
WORKDIR /build
RUN go mod download
RUN go mod verify
# Prepare app
ADD server.go /build/
# Build as static binary
RUN CGO_ENABLED=1 go build --tags "fts5" -ldflags='-w -s -extldflags "-static"' -o /build/search-server
# Copy binary to empty image
FROM scratch
# Import the user and group files from the builder.
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /etc/group /etc/group
# Prepare environment
ENV GIN_MODE=release
# Copy executable
COPY --from=builder /build/search-server /server
# Use an unprivileged user.
USER appuser:appuser
ENTRYPOINT ["/server"]

View File

@ -1,66 +0,0 @@
module codigoparallevar/search-server
go 1.19
require github.com/gin-gonic/gin v1.8.1
require (
github.com/Microsoft/go-winio v0.4.14 // indirect
github.com/awesome-gocui/gocui v0.6.0 // indirect
github.com/awesome-gocui/keybinding v1.0.0 // indirect
github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/docker/cli v0.0.0-20190906153656-016a3232168d // indirect
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v0.7.3-0.20190309235953-33c3200e0d16 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/fatih/color v1.7.0 // indirect
github.com/fsnotify/fsnotify v1.4.7 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.10.0 // indirect
github.com/goccy/go-json v0.9.7 // indirect
github.com/gogo/protobuf v1.3.0 // indirect
github.com/google/uuid v1.1.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/logrusorgru/aurora v0.0.0-20190803045625-94edacc10f9b // indirect
github.com/lunixbochs/vtclean v1.0.0 // indirect
github.com/magiconair/properties v1.8.1 // indirect
github.com/mattn/go-colorable v0.1.2 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.4 // indirect
github.com/mattn/go-sqlite3 v1.14.15 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.1.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/pelletier/go-toml v1.4.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee // indirect
github.com/pkg/errors v0.8.1 // indirect
github.com/sirupsen/logrus v1.4.2 // indirect
github.com/spf13/afero v1.2.2 // indirect
github.com/spf13/cast v1.3.0 // indirect
github.com/spf13/cobra v0.0.5 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.4.0 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
github.com/wagoodman/dive v0.10.0 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect
golang.org/x/text v0.3.6 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

View File

@ -1,295 +0,0 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/awesome-gocui/gocui v0.5.0/go.mod h1:1QikxFaPhe2frKeKvEwZEIGia3haiOxOUXKinrv17mA=
github.com/awesome-gocui/gocui v0.6.0 h1:hhDJiQC12tEsJNJ+iZBBVaSSLFYo9llFuYpQlL5JZVI=
github.com/awesome-gocui/gocui v0.6.0/go.mod h1:1QikxFaPhe2frKeKvEwZEIGia3haiOxOUXKinrv17mA=
github.com/awesome-gocui/keybinding v1.0.0 h1:CrnjCfEhWpjcqIQUan9IllaXeRGELdwfjeUmY7ljbng=
github.com/awesome-gocui/keybinding v1.0.0/go.mod h1:z0TyCwIhaT97yU+becTse8Dqh2CvYT0FLw0R0uTk0ag=
github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc h1:wGNpKcHU8Aadr9yOzsT3GEsFLS7HQu8HxQIomnekqf0=
github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc/go.mod h1:tOy3o5Nf1bA17mnK4W41gD7PS3u4Cv0P0pqFcoWMy8s=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/docker/cli v0.0.0-20190906153656-016a3232168d h1:gwX/88xJZfxZV1yjhhuQpWTmEgJis7/XGCVu3iDIZYU=
github.com/docker/cli v0.0.0-20190906153656-016a3232168d/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v0.7.3-0.20190309235953-33c3200e0d16 h1:dmUn0SuGx7unKFwxyeQ/oLUHhEfZosEDrpmYM+6MTuc=
github.com/docker/docker v0.7.3-0.20190309235953-33c3200e0d16/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.0 h1:G8O7TerXerS4F6sx9OV7/nRfJdnXgHZu/S/7F2SN+UE=
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/logrusorgru/aurora v0.0.0-20190803045625-94edacc10f9b h1:PMbSa9CgaiQR9NLlUTwKi+7aeLl3GG5JX5ERJxfQ3IE=
github.com/logrusorgru/aurora v0.0.0-20190803045625-94edacc10f9b/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg=
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU=
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee h1:P6U24L02WMfj9ymZTxl7CxS73JC99x3ukk+DBkgQGQs=
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee/go.mod h1:3uODdxMgOaPYeWU7RzZLxVtJHZ/x1f/iHkBZuKJDzuY=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/wagoodman/dive v0.10.0 h1:JaitQBVwmfZD5mvLkBHk1LUq6jwsjvnNS6mgIl7YNZQ=
github.com/wagoodman/dive v0.10.0/go.mod h1:8IDxfzmg3+5DQwK6/sGyMpJr95ejuv511+rF9CTNYdQ=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -1,130 +0,0 @@
package main
import (
"github.com/gin-gonic/gin"
"database/sql"
"log"
"os"
"fmt"
"strconv"
_ "github.com/mattn/go-sqlite3"
)
func main() {
database_path, ok := os.LookupEnv("DB_PATH")
if !ok {
log.Fatal("Environment variable $DB_PATH must point to sqlite3 database with text indices.")
os.Exit(1)
}
port := 3000
port_str, ok := os.LookupEnv("PORT")
if ok {
port_num, err := strconv.Atoi(port_str)
if err != nil {
log.Fatal(err)
os.Exit(1)
}
if (port_num < 1) || (port_num > 65535) {
log.Fatal("Environment variale $PORT must be a number between 1 and 65535.")
os.Exit(1)
}
port = port_num
}
db, err := sql.Open("sqlite3", database_path)
if err != nil {
log.Fatal(err)
os.Exit(1)
}
r := gin.Default()
api := r.Group("/api")
api.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
api.OPTIONS("/search", func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Accept-Encoding, Authorization, accept, origin, Cache-Control, X-Requested-With")
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
c.AbortWithStatus(204)
})
api.GET("/search", func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Accept-Encoding, Authorization, accept, origin, Cache-Control, X-Requested-With")
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
query := c.Query("q")
stm, err := db.Prepare("SELECT note_id, title, top_level_title, is_done, is_todo FROM note_search(?)")
if err != nil {
log.Fatal(err)
c.JSON(500, gin.H{
"success": false,
"message": "Error preparing note-search query",
})
return
}
results := make([]map[string]string, 0)
rows, err := stm.Query(query)
if err != nil {
log.Fatal(err)
c.JSON(500, gin.H{
"success": false,
"message": "Error querying note DB",
})
return
}
for rows.Next() {
var note_id string
var note_title string
var note_top_level_title string
var note_is_done string
var note_is_todo string
err = rows.Scan(
&note_id,
&note_title,
&note_top_level_title,
&note_is_done,
&note_is_todo,
)
if err != nil {
log.Fatal(err)
c.JSON(500, gin.H{
"success": false,
"message": "Error reading note DB results",
})
return
}
item := make(map[string]string)
item["id"] = note_id
item["title"] = note_title
item["top_level_title"] = note_top_level_title
item["is_done"] = note_is_done
item["is_todo"] = note_is_todo
results = append(results, item)
}
c.JSON(200, gin.H{
"results": gin.H{
"notes": results,
},
})
})
r.Run(fmt.Sprintf(":%v", port))
}

View File

@ -1,59 +0,0 @@
#!/usr/bin/env python3
import logging
import os
import sys
import urllib.parse
from bs4 import BeautifulSoup as bs4
from tqdm import tqdm
def main(files_top):
print("Listing files...")
found_files = []
for root, dirs, files in os.walk(files_top):
for name in files:
if name.endswith('.html'):
found_files.append(os.path.join(root, name))
print("\r{} files".format(len(found_files)), end='', flush=True)
print()
found_broken = 0
for fpath in tqdm(found_files):
with open(fpath) as f:
tree = bs4(f.read(), features='lxml', parser='html5')
for tag, attr in [('a', 'href'), ('img', 'src'), ('audio', 'src'), ('video', 'src')]:
for link in tree.find_all(tag):
if attr not in link.attrs:
continue
link.attrs[attr] = link.attrs[attr].split('#')[0]
if not link.attrs[attr]:
continue
if ':' in link[attr]:
continue
if link[attr].startswith('/'):
target = os.path.join(os.path.abspath(files_top), urllib.parse.unquote(link[attr].lstrip('/')))
else:
target = os.path.join(os.path.dirname(fpath), urllib.parse.unquote(link[attr]))
if os.path.isdir(target):
pass
elif not os.path.exists(target):
print("[{}] -[ error ]-> {} | {}".format(fpath, target, link[attr]))
found_broken += 1
if found_broken:
print(f"Found {found_broken} broken links")
exit(1)
else:
exit(0)
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: {} FILES_TOP".format(sys.argv[0]))
exit(0)
logging.basicConfig(level=logging.INFO, format="%(levelname)-8s %(message)s")
exit(main(sys.argv[1]))

View File

@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -eu
cd "$(dirname "$0")/.."
cd static
scp homepage.html root@codigoparallevar.com:/mnt/vols/misc/codigoparallevar/index.html

View File

@ -1,30 +0,0 @@
#!/usr/bin/env bash
set -eu
cd "$(dirname "$0")/.."
# Upload homepage
cd static
scp homepage.html root@codigoparallevar.com:/mnt/vols/misc/codigoparallevar/index.html
# Build notes
cd ../scripts
rm -Rf ../_gen/notes
WATCH_AND_REBUILD=0 python3 generate.py ~/.logs/brain ../_gen/notes
rm -Rf ../_gen/blog
WATCH_AND_REBUILD=0 python3 blog.py ~/cloud/nextcloud/blog/posts/ ../_gen/blog
rm -Rf ../_gen/static
cp -Rv ../static ../_gen/static
# Upload notes
cd ../_gen
rsync -HPaz static/ --delete-after --exclude='*.html' root@codigoparallevar.com:/mnt/vols/misc/codigoparallevar/static/
rsync -HPaz notes/ --delete-after --exclude='xapian' --exclude='*.sqlite3' root@codigoparallevar.com:/mnt/vols/misc/codigoparallevar/notes/
rsync -HPaz notes/db.sqlite3 root@codigoparallevar.com:/mnt/vols/misc/codigoparallevar-api/
rsync -HPaz blog/ --delete-after --exclude='xapian' --exclude='*.sqlite3' root@codigoparallevar.com:/mnt/vols/misc/codigoparallevar/blog/
# Restart API server
ssh root@codigoparallevar.com docker restart notes-api-server

View File

@ -2,21 +2,18 @@
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>{{ title }} @ Código para llevar [blog]</title> <title>{{ title }} @ Código para llevar</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="{{ base_path }}/../static/style.css" /> <link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="{{ base_path }}/../static/light-syntax.css" /> <link rel="stylesheet" href="../css/light-syntax.css" />
<link rel="stylesheet" href="{{ base_path }}/../static/dark-syntax.css" /> <link rel="stylesheet" href="../css/dark-syntax.css" />
</head> </head>
<body class="blog"> <body>
<div class="site-header"> <div class="site-header">
<h1 class="site-name"><a href="{{ base_path }}/">Codigo para llevar [blog]</a></h1> <h1 class="site-name"><a href="/">Codigo para llevar</a></h1>
<nav class="site-links"> <nav class="site-links">
<span class="fancy-link"> <span class="fancy-link">
<a href="{{ base_path }}/../">Home</a> <a href="https://codigoparallevar.com/">Home</a>
</span>
<span class="fancy-link">
<a href="{{ base_path }}/../notes/">Notes</a>
</span> </span>
<span class="fancy-link"> <span class="fancy-link">
<a href="https://github.com/kenkeiras">GitHub</a> <a href="https://github.com/kenkeiras">GitHub</a>
@ -33,12 +30,12 @@
<article class="post"> <article class="post">
<h2 class="post-title">{{ title }}</h2> <h2 class="post-title">{{ title }}</h2>
<div class="post-metadata"> <div class="post-metadata">
<time class="post-publication-date" datetime="{{ post_publication_date.date() }}"> <time datetime="{{ post_publication_date }}">
{{ post_publication_date.date() }} {{ post_publication_date }}
</time> </time>
<ul class="post-tags"> <ul class="post-tags">
{% for post_tag in post_tags %} {% for post_tag in post_tags %}
<li class="post-tag"><a href="{{ base_path }}/tags/{{ post_tag |urlencode|replace('/', '_') }}/"</a>{{ post_tag }}</a></li> <li class="post-tag">{{ post_tag }}</li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>

View File

@ -1,54 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Código para llevar</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../../static/style.css" />
<link rel="stylesheet" href="../../static/light-syntax.css" />
<link rel="stylesheet" href="../../static/dark-syntax.css" />
</head>
<body class="blog">
<div class="site-header">
<h1 class="site-name"><a href="../">Codigo para llevar [blog]</a></h1>
<nav class="site-links">
<span class="fancy-link">
<a href="../../">Home</a>
</span>
<span class="fancy-link">
<a href="../../notes/">Notes</a>
</span>
<span class="fancy-link">
<a href="https://github.com/kenkeiras">GitHub</a>
</span>
<span class="fancy-link">
<a href="https://gitlab.com/kenkeiras">GitLab</a>
</span>
<span class="fancy-link">
<a href="https://programaker.com/users/kenkeiras">PrograMaker</a>
</span>
</nav>
</div>
<div class="post-list content">
<ul>
{% for post in posts %}
<li class="post">
<div class="post-metadata">
<time class="post-publication-date" datetime="{{ post.post_publication_date.date() }}">
{{ post.post_publication_date.date() }}
</time>
</div>
<h4 class="post-title"><a href="../{{ post.link }}">{{ post.title }}</a></h4>
<div class="post-metadata">
<ul class="post-tags">
{% for post_tag in post.post_tags %}
<li class="post-tag"><a href="../tags/{{ post_tag |urlencode|replace('/', '_') }}/">{{ post_tag }}</a></li>
{% endfor %}
</ul>
</div>
</li>
{% endfor %}
</ul>
</div>
</body>
</html>

View File

@ -4,19 +4,16 @@
<meta charset="utf-8"> <meta charset="utf-8">
<title>Código para llevar</title> <title>Código para llevar</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../static/style.css" /> <link rel="stylesheet" href="css/style.css" />
<link rel="stylesheet" href="../static/light-syntax.css" /> <link rel="stylesheet" href="css/light-syntax.css" />
<link rel="stylesheet" href="../static/dark-syntax.css" /> <link rel="stylesheet" href="css/dark-syntax.css" />
</head> </head>
<body class="blog"> <body>
<div class="site-header"> <div class="site-header">
<h1 class="site-name"><a href="./">Codigo para llevar [blog]</a></h1> <h1 class="site-name"><a href="/">Codigo para llevar</a></h1>
<nav class="site-links"> <nav class="site-links">
<span class="fancy-link"> <span class="fancy-link">
<a href="../">Home</a> <a href="https://codigoparallevar.com/">Home</a>
</span>
<span class="fancy-link">
<a href="../notes/">Notes</a>
</span> </span>
<span class="fancy-link"> <span class="fancy-link">
<a href="https://github.com/kenkeiras">GitHub</a> <a href="https://github.com/kenkeiras">GitHub</a>
@ -33,14 +30,14 @@
{% for post in posts %} {% for post in posts %}
<div class="post-container"> <div class="post-container">
<article class="post"> <article class="post">
<h2 class="post-title"><a href="{{ post.link }}">{{ post.title }}</a></h2> <h2 class="post-title">{{ post.title }}</h2>
<div class="post-metadata"> <div class="post-metadata">
<time class="post-publication-date" datetime="{{ post.post_publication_date.date() }}"> <time datetime="{{ post.post_publication_date }}">
{{ post.post_publication_date.date() }} {{ post.post_publication_date }}
</time> </time>
<ul class="post-tags"> <ul class="post-tags">
{% for post_tag in post.post_tags %} {% for post_tag in post.post_tags %}
<li class="post-tag"><a href="tags/{{ post_tag |urlencode|replace('/', '_') }}/"</a>{{ post_tag }}</a></li> <li class="post-tag">{{ post_tag }}</li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
@ -51,18 +48,5 @@
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
<div class="index-pages">
{% if prev_index_num != None %}
{% if prev_index_num == 0 %}
<a class="newer-posts" href="index.html">Newer posts</a>
{% else %}
<a class="newer-posts" href="index-{{ prev_index_num }}.html">Newer posts</a>
{% endif %}
{% endif %}
{% if next_index_num %}
<a class="older-posts" href="index-{{ next_index_num }}.html">Older posts</a>
{% endif %}
</div>
</body> </body>
</html> </html>

View File

@ -1,54 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Código para llevar</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../../../static/style.css" />
<link rel="stylesheet" href="../../../static/light-syntax.css" />
<link rel="stylesheet" href="../../../static/dark-syntax.css" />
</head>
<body class="blog">
<div class="site-header">
<h1 class="site-name"><a href="../../">Codigo para llevar [blog]</a></h1>
<nav class="site-links">
<span class="fancy-link">
<a href="../../../">Home</a>
</span>
<span class="fancy-link">
<a href="../../../notes/">Notes</a>
</span>
<span class="fancy-link">
<a href="https://github.com/kenkeiras">GitHub</a>
</span>
<span class="fancy-link">
<a href="https://gitlab.com/kenkeiras">GitLab</a>
</span>
<span class="fancy-link">
<a href="https://programaker.com/users/kenkeiras">PrograMaker</a>
</span>
</nav>
</div>
<div class="post-list content">
<ul>
{% for post in posts %}
<li class="post">
<div class="post-metadata">
<time class="post-publication-date" datetime="{{ post.post_publication_date.date() }}">
{{ post.post_publication_date.date() }}
</time>
</div>
<h4 class="post-title"><a href="../../{{ post.link }}">{{ post.title }}</a></h4>
<div class="post-metadata">
<ul class="post-tags">
{% for post_tag in post.post_tags %}
<li class="post-tag"><a href="../../tags/{{ post_tag |urlencode|replace('/', '_') }}/">{{ post_tag }}</a></li>
{% endfor %}
</ul>
</div>
</li>
{% endfor %}
</ul>
</div>
</body>
</html>

View File

@ -1,5 +1,3 @@
/* Dark mode. */
@media (prefers-color-scheme: dark) {
pre { line-height: 125%; } pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
@ -82,4 +80,3 @@
.vi { color: #f8f8f2 } /* Name.Variable.Instance */ .vi { color: #f8f8f2 } /* Name.Variable.Instance */
.vm { color: #f8f8f2 } /* Name.Variable.Magic */ .vm { color: #f8f8f2 } /* Name.Variable.Magic */
.il { color: #ae81ff } /* Literal.Number.Integer.Long */ .il { color: #ae81ff } /* Literal.Number.Integer.Long */
}

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 169 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 172 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 138 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 136 KiB

View File

@ -7,12 +7,6 @@
<script type='text/javascript'> <script type='text/javascript'>
var NODE_GRAPH=<!-- REPLACE_THIS_WITH_GRAPH -->; var NODE_GRAPH=<!-- REPLACE_THIS_WITH_GRAPH -->;
</script> </script>
<style>
text {
font-family: sans-serif;
font-size: 10px;
}
</style>
<script> <script>
// Copyright 2021 Observable, Inc. // Copyright 2021 Observable, Inc.
// Released under the ISC license. // Released under the ISC license.
@ -86,15 +80,15 @@ function ForceGraph({
// Add arrowheads // Add arrowheads
svg.append('defs').append('marker') svg.append('defs').append('marker')
.attr('id', 'arrowhead') .attr('id', 'arrowhead')
.attr('viewBox', '-0 -2.5 5 5') .attr('viewBox', '-0 -5 10 10')
.attr('refX', 10) .attr('refX', 13)
.attr('refY', 0) .attr('refY', 0)
.attr('orient', 'auto') .attr('orient', 'auto')
.attr('markerWidth', 7) .attr('markerWidth', 13)
.attr('markerHeight', 7) .attr('markerHeight', 13)
.attr('xoverflow', 'visible') .attr('xoverflow', 'visible')
.append('svg:path') .append('svg:path')
.attr('d', 'M 0,-2.5 L 5 ,0 L 0,2.5') .attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#999') .attr('fill', '#999')
.style('stroke','none'); .style('stroke','none');
@ -269,8 +263,6 @@ function ForceGraph({
linkLabel: (d) => { const e = edges[d.index]; if (e.relation) { return e.relation; } else { return ''; } }, linkLabel: (d) => { const e = edges[d.index]; if (e.relation) { return e.relation; } else { return ''; } },
}); });
holder.appendChild(chart); holder.appendChild(chart);
chart.height = '100vh';
chart.width = '100vw';
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,18 +1,16 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Código para llevar</title> <title>Código para llevar</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<style> <style>
body { body {
background-color: white;
font-family: sans-serif; font-family: sans-serif;
margin: 0 auto; margin: 0 auto;
width: fit-content; width: fit-content;
max-width: 100ex; max-width: 100ex;
padding: 0 1ex; padding: 0 1ex;
color: black;
} }
.header h1 { .header h1 {
text-align: center; text-align: center;
@ -32,9 +30,8 @@
.links p { .links p {
margin: 0; margin: 0;
} }
h2 a, a h2 { h2 a {
color: black; color: black;
text-decoration: underline;
} }
a { a {
color: #00e; color: #00e;
@ -47,21 +44,21 @@
border-right: 1px solid #000; border-right: 1px solid #000;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
body { html {
background-color: #1d1f21; background-color: #0f110e;
color: #fafafe; color: #fafafe;
} }
h2 a, a h2 { h2 a {
color: #fafafe; color: #fafafe;
} }
a { a {
color: #00fdf2; color: #66f;
} }
#social a { #social a {
border-color: #fff; border-color: #fff;
} }
.links section { .links section {
background-color: #262628; background-color: #262826;
} }
} }
</style> </style>
@ -74,19 +71,11 @@
</div> </div>
<div class="links"> <div class="links">
<a href="/notes">
<section>
<h2>Notes</h2>
<p>Some publicly-visible notes from a sort of knowledge graph that I use as information dump.</p>
</section>
</a>
<section> <section>
<h2><a href="/blog">Blog</a></h2> <h2><a href="/blog">Blog</a></h2>
<p> <p>
Latest posts: Latest posts:
<ul> <ul>
<li><a href="https://codigoparallevar.com/blog/2023/programmatic-access-to-gnucash-using-python/">[2023] A sloppy guide to GnuCash's Python bindings</a></li>
<li><a href="https://codigoparallevar.com/blog/2022/detecting-non-halting-programs/">[2022] Detecting non halting programs</a></li>
<li><a href="https://codigoparallevar.com/blog/2022/get-process-progress-reading-file/">[2022] Get process's progress when reading a file</a></li> <li><a href="https://codigoparallevar.com/blog/2022/get-process-progress-reading-file/">[2022] Get process's progress when reading a file</a></li>
<li><a href="https://codigoparallevar.com/blog/2022/a-simple-status-indicator/">[2022] A simple status indicator</a></li> <li><a href="https://codigoparallevar.com/blog/2022/a-simple-status-indicator/">[2022] A simple status indicator</a></li>
</ul> </ul>
@ -109,34 +98,15 @@
</p> </p>
</section> </section>
<section> <section>
<h2>Talks / Slides</h2>
<p>
<ul>
<li>
Malleable Software
(<a href="/slides/hackliza2024/software-maleable/software-maleable.odp">galician, </a>
for <a href="https://hackliza.gal">Hackliza</a>
<a href="/slides/hackliza2024/software-maleable/software-maleable.pdf">[PDF]</a>
<a href="/slides/hackliza2024/software-maleable/software-maleable.odp">[ODP]</a>)
(<a href="/slides/eslibre2024/software-maleable.odp">spanish,</a>
for <a href="https://eslib.re/2024/">esLibre 2024</a>
<a href="/slides/eslibre2024/software-maleable.pdf">[PDF]</a>
<a href="/slides/eslibre2024/software-maleable.odp">[ODP]</a>).
</li>
</ul>
</p>
</section>
<!-- section>
<h2>Projects</h2> <h2>Projects</h2>
<p> <p>
My most stable project is <a href="https://programaker.com">PrograMaker</a>. My most stable project is <a href="https://programaker.com">PrograMaker</a>.
Other work-in-progress is in <a href="https://github.com/kenkeiras">GitHub</a>. Other work-in-progress is in <a href="https://github.com/kenkeiras">GitHub</a>.
</p> </p>
</section --> </section>
<section id="social"> <section id="social">
<h2>Find me</h2> <h2>Find me</h2>
<p> <p>
<a href="https://social.codigoparallevar.com/@kenkeiras">ActivityPub</a>
<a href="https://github.com/kenkeiras">GitHub</a> <a href="https://github.com/kenkeiras">GitHub</a>
<a href="https://gitlab.com/kenkeiras">GitLab</a> <a href="https://gitlab.com/kenkeiras">GitLab</a>
<a href="https://programaker.com/users/kenkeiras">PrograMaker</a> <a href="https://programaker.com/users/kenkeiras">PrograMaker</a>

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Código para llevar</title>
<link>https://codigoparallevar.com/blog/</link>
<description>Blog from a programmer adrift.</description>
<atom:link href="https://codigoparallevar.com/blog/rss.xml" rel="self" type="application/rss+xml"></atom:link>
<language>en</language>
<copyright>Contents © 2023 kenkeiras - Creative Commons License 4.0 BY-NC-SA</copyright>
<lastBuildDate>{{ last_build_date.strftime("%a, %d %b %Y %H:%M:%S %z") }}</lastBuildDate>
<ttl>3600</ttl>
{% for post in posts %}
<item>
<title>{{ post.title }}</title>
<description>{{ post.summary }}</description>
<link>https://codigoparallevar.com/blog/{{ post.link }}</link>
<pubDate>{{ post.post_publication_date.strftime("%a, %d %b %Y %H:%M:%S %z") }}</pubDate>
</item>
{% endfor %}
</channel>
</rss>

View File

@ -1,129 +0,0 @@
function _codigoparallevar_enable_search_box(selector, options) {
const element = document.querySelector(selector);
if ('placeholder' in options) {
element.setAttribute('placeholder', options.placeholder);
}
// Prepare backdrop
const resultsBoxBackdrop = document.createElement('div');
resultsBoxBackdrop.setAttribute('class', 'results-box-container hidden');
const resultsBox = document.createElement('div');
resultsBox.setAttribute('class', 'results-box');
// Results box contents
const innerSearchBox = document.createElement('input');
innerSearchBox.setAttribute('type', 'text');
innerSearchBox.setAttribute('placeholder', element.getAttribute('placeholder'));
resultsBox.appendChild(innerSearchBox);
const resultsList = document.createElement('ul');
resultsBox.appendChild(resultsList);
const noResultsBox = document.createElement('div');
noResultsBox.setAttribute('class', 'no-results-box hidden');
noResultsBox.innerText = 'No results 🤷';
resultsBox.appendChild(noResultsBox);
resultsBoxBackdrop.appendChild(resultsBox);
document.body.appendChild(resultsBoxBackdrop);
// Popup cancellation
resultsBoxBackdrop.onclick = () => {
resultsBoxBackdrop.classList.add('hidden');
};
resultsBox.onclick = (ev) => {
ev.stopPropagation();
};
// Element triggers popup
element.onfocus = () => {
resultsBoxBackdrop.classList.remove('hidden');
innerSearchBox.focus();
const wasKeyDown = document.onkeydown;
document.onkeydown = (ev) => {
if (ev.key === 'Escape') {
resultsBoxBackdrop.classList.add('hidden');
document.onkeydown = wasKeyDown;
ev.stopPropagation();
}
};
};
const DEBOUNCE_TIME = 250; // Milliseconds
const MIN_LENGTH = 3;
const SEARCH_ENDPOINT = (window.location.host.startsWith('localhost')
? 'http://localhost:3001/api/search'
: 'https://api.codigoparallevar.com/api/search'
);
let debounceWaiter = null;
let currentQuery = null;
let lastVal = null;
const doQuery = () => {
const val = innerSearchBox.value.trim();
if ((val.length < MIN_LENGTH) || (val === lastVal)) {
return;
}
lastVal = val;
resultsBox.classList.add('loading');
const uri = SEARCH_ENDPOINT + '?q=' + encodeURIComponent(val);
let query = fetch(uri);
currentQuery = query;
query
.then(res => res.json())
.then((body) => {
if (query !== currentQuery) {
console.log("Query out-raced 🤷");
return;
}
resultsBox.classList.remove('loading');
resultsList.innerHTML = '';
for (const list of [
body.results.notes.filter(n => n.is_todo !== "1"),
body.results.notes.filter(n => n.is_todo === "1"),
]){
for (const note of list) {
const resultCard = document.createElement('li');
const resultContents = document.createElement('a');
resultContents.setAttribute('href', './' + note.id + '.node.html');
const resultTitle = document.createElement('h2');
resultTitle.innerText = `${note.title} (${note.top_level_title})`;
if (note.is_todo === "1") {
resultTitle.setAttribute('class', 'is-todo');
}
else if (note.is_done === "1") {
resultTitle.setAttribute('class', 'is-done');
}
resultContents.appendChild(resultTitle);
resultCard.appendChild(resultContents);
resultsList.appendChild(resultCard);
}
}
if (body.results.notes.length == 0) {
noResultsBox.classList.remove('hidden');
}
else {
noResultsBox.classList.add('hidden');
}
});
};
element.removeAttribute('disabled');
innerSearchBox.onkeyup = (ev) => {
if (debounceWaiter !== null) {
clearTimeout(debounceWaiter);
}
debounceWaiter = setTimeout(doQuery, DEBOUNCE_TIME);
};
}
// // TODO: Remove this when dev is done
// _codigoparallevar_enable_search_box('#searchbox', {placeholder: 'Search...'})
// document.querySelector('#searchbox').focus()

View File

@ -2,310 +2,35 @@
html, body { html, body {
margin: 0; margin: 0;
padding: 0; padding: 0;
font-family: 'Atkinson Hyperlegible', 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 110%;
} }
body {
max-width: 80ex;
margin: 0 auto;
padding: 0.5ex 1ex;
background-color: white;
color: black;
}
body.blog {
padding: 0;
}
body nav {
text-align: center;
}
body nav h1 {
text-align: center;
color: #000;
display: inline-block;
}
a:focus {
background-color: rgb(0, 0, 238);
color: white;
}
h1 a:focus,
h2 a:focus,
h3 a:focus,
h4 a:focus,
h5 a:focus,
h6 a:focus
{
background-color: inherit;
color: #2c3e50;
}
/* Search box */
body nav input {
background-color: transparent;
color: #000;
border: none;
border-bottom: 1px solid #888;
}
.results-box-container {
z-index: 5;
position: fixed;
width: 100vw;
height: 100vh;
background-color: rgba(0,0,0,0.3);
top: 0;
left: 0;
}
.results-box-container.hidden {
display: none;
}
.results-box {
min-width: 50vw;
max-width: 90vw;
min-height: 20ex;
max-height: 90vh;
overflow: auto;
border-radius: 4px;
margin: 2rem auto;
box-shadow: 0px 1px 2px 1px rgba(0, 0, 0, 0.25);
background-color: #fff;
}
.results-box-container .results-box input {
width: 90%;
margin: 0 auto;
padding-top: 2ex;
display: block;
background-color: transparent;
color: #000;
border: none;
border-bottom: 1px solid #888;
outline: none;
font-size: 100%;
}
.results-box-container .results-box input:focus {
border-bottom: 1px solid #000;
}
@keyframes loading-query {
from {
border-bottom-color: hsl(0 80% 40%);
}
30% {
border-bottom-color: hsl(80 80% 40%);
}
60% {
border-bottom-color: hsl(180 80% 40%);
}
to {
border-bottom-color: hsl(360 80% 40%);
}
}
.results-box-container .results-box.loading input {
animation-name: loading-query;
border-bottom-width: 2px;
animation-duration: 2s;
margin-bottom: -1px;
animation-iteration-count: infinite;
animation-timing-function: linear;
}
/* Search box results */
.results-box ul {
list-style: none;
padding: 0;
}
.results-box ul li {
padding: 0.25ex;
margin: 1ex;
border-radius: 4px;
box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.25);
}
.results-box ul li h2 {
font-size: 110%;
padding: 1.25ex;
display: block;
margin: 0;
}
.results-box li h2.is-todo::before {
content: 'TODO';
display: inline-block;
background-color: #D00;
padding: 0.25ex;
border-radius: 4px;
font-size: 90%;
margin-right: 0.5ex;
}
.no-results-box {
padding: 1rem;
}
.no-results-box.hidden {
display: none;
}
a.img {
display: block;
}
img {
max-width: 100%;
}
@font-face {
font-family: "Atkinson Hyperlegible";
src: url('./fonts/atkinson-hyperlegible/eot/Atkinson-Hyperlegible-Regular-102.eot');
src: url('./fonts/atkinson-hyperlegible/eot/Atkinson-Hyperlegible-Regular-102.eot') format('embedded-opentype'),
url('./fonts/atkinson-hyperlegible/woff2/Atkinson-Hyperlegible-Regular-102a.woff2') format('woff2'),
url('./fonts/atkinson-hyperlegible/woff/Atkinson-Hyperlegible-Regular-102.woff') format('woff'),
url('./fonts/atkinson-hyperlegible/ttf/Atkinson-Hyperlegible-Regular-102.ttf') format('truetype');
/* Make sure text is displayed ASAP, even if this font is not ready. */
font-display: swap;
}
/* Node styling */ /* Node styling */
.node { .node {
max-width: min(650px, 100ex);
margin: 0 auto; margin: 0 auto;
} }
.node .node { .node .node {
margin: 2em 0ex 2em 0.5ex;
padding: 1ex 0 1ex 1ex; padding: 1ex 0 1ex 1ex;
border-left: 1px dashed #2c3e50; box-shadow: 0px 1px 3px 0 rgba(0,0,0,0.3);
border-left: 2px solid #2c3e50;
} }
.node.collapsed > .contents { .node.collapsed > .contents {
display: none; display: none;
} }
/* Item list */ .node .node.collapsed > .title::before {
.node .contents ul, content: "🮥";
.global-table-of-contents ul {
--tree-spacing : 1rem;
--tree-radius : 0.75ex;
--tree-line-separation: 0.5rem;
--tree-color: #ccc;
--tree-border-radius: 5px;
list-style: none;
} }
.node .contents ul li, .node .node.expanded > .title::before {
.global-table-of-contents ul li{ content: "🮦";
position: relative;
padding-left: calc(var(--tree-spacing) * 2);
}
.node .contents ul li::after,
.global-table-of-contents ul li::after {
content: '';
display: block;
position: absolute;
top: calc(var(--tree-spacing) / 2 - var(--tree-radius));
left: calc(var(--tree-spacing) - var(--tree-radius) - 1px);
width: calc(2 * var(--tree-radius));
height: calc(2 * var(--tree-radius));
border-radius: 50%;
background: var(--tree-color);
}
.node .contents ul li::before,
.global-table-of-contents ul li::before {
content: ' ';
width: var(--tree-spacing);
display: inline-block;
border-bottom: 2px dashed var(--tree-color);
vertical-align: super;
margin-right: calc(var(--tree-line-separation) + 0.5ex);
margin-left: calc(0px - (var(--tree-line-separation) * 4) - 2px);
}
/* Nested item list */
.node .contents ul ul,
.global-table-of-contents ul ul {
padding: 0;
margin-left: calc(var(--tree-spacing));
list-style: none;
}
.node .contents ul > li > ul,
.global-table-of-contents ul > li > ul {
margin-left: calc(0px - var(--tree-spacing));
}
.node .contents ul ul li,
.global-table-of-contents ul ul li {
margin-left: calc(0px - var(--tree-radius) / 2 + 2px);
border-left: 2px solid var(--tree-color);
}
.node .contents ul ul li::marker,
.global-table-of-contents ul ul li::marker {
content: '';
}
.node .contents ul ul li::after,
.global-table-of-contents ul ul li::after {
left: calc(var(--tree-spacing) * 2 - 0.5ex);
}
.node .contents ul ul li::before,
.global-table-of-contents ul ul li::before {
width: calc(var(--tree-spacing) * 2);
height: calc(var(--tree-spacing) + 5px);
margin-top: -100%;
border-radius: 0;
top: calc(-0.5ex - 2px);
border-left: 2px solid var(--tree-color);
border-bottom-style: solid;
}
.node .contents ul ul li:last-of-type::before,
.global-table-of-contents ul ul li:last-of-type::before {
border-bottom-left-radius: var(--tree-border-radius);
}
.node .contents ul li:last-of-type,
.global-table-of-contents ul li:last-of-type {
border-color: transparent;
}
.node .node > .title::before {
content: "#";
display: inline-block;
color: #888;
}
.node .title {
margin: 0;
}
/* Inhibit <p> tags inside inlined items */
/* TODO: Remove need for this on generator */
.item p {
display: inline;
}
h1 p,h2 p,h3 p,h4 p,h5 p,h6 p, li p {
display: inline;
}
.connections ul {
margin-top: 0;
} }
/* Headers */ /* Headers */
body > .node > h1 { h1 {
text-align: center;
}
.node .node h1 {
font-size: 150%; font-size: 150%;
} }
@ -328,36 +53,6 @@ h1.title .state {
border-radius: 5px; border-radius: 5px;
} }
h1.title .state.todo-True {
background-color: rgba(255,0,0,0.5);
}
h1.title .state.todo-False {
background-color: rgba(0,255,0,0.25);
}
h1.title .state.todo-True.state-SOMETIME {
background-color: #ddd;
color: black;
}
h1.title .tags {
float: right;
}
h1.title .tags .tag {
font-size: 50%;
vertical-align: middle;
/* background-color: rgba(255,255,255,0.3); */
background-color: rgba(0,0,0,0.1);
padding: 4px;
margin-left: 2px;
border-radius: 5px;
}
h1.title .tags .tag:before {
content: '[';
}
h1.title .tags .tag:after {
content: ']';
}
/* Lists */ /* Lists */
li .tag { li .tag {
font-weight: bold; font-weight: bold;
@ -367,41 +62,11 @@ li .tag::after {
content: ":: "; content: ":: ";
} }
a.internal::before {
content: '{ ';
}
a.internal::after {
content: ' }';
}
a.external::after {
content: ' ↗';
vertical-align: top;
}
/* Markup */
.underlined {
text-decoration: underline;
}
/* Codehilite fix */
.codehilitetable, .codehilitetable tr, .codehilitetable td {
border: none;
}
.codehilitetable .linenodiv pre {
margin: 0;
box-shadow: none;
line-height: 1.2em;
font-family: Menlo, Monaco, "Courier New", monospace;
font-size: medium;
}
.codehilitetable .code code {
font-size: medium;
}
/* Code blocks */ /* Code blocks */
pre { pre {
overflow: auto; overflow: auto;
padding: 0.25ex; padding: 0.25ex;
box-shadow: 0px 1px 2px 1px rgba(0, 0, 0, 0.25); box-shadow: 0px 2px 4px 2px rgba(0, 0, 0, 0.26);
border-radius: 2px; border-radius: 2px;
} }
pre > code { pre > code {
@ -410,20 +75,15 @@ pre > code {
overflow: auto; overflow: auto;
} }
pre code { pre code {
padding: 1ex; padding: 0.5ex;
font-size: medium; font-size: medium;
background: #fff; background: #fff;
color: #000; color: #000;
border: none; border: none;
font-size: 85%; font-size: 85%;
border-radius: 4px;
} }
.highlight pre { code {
padding: 0.5ex;
}
code, .verbatim {
padding: 0.25ex 0.5ex; padding: 0.25ex 0.5ex;
margin: 0.25ex; margin: 0.25ex;
background: #eee; background: #eee;
@ -432,16 +92,6 @@ code, .verbatim {
font-size: 85%; font-size: 85%;
} }
/* Results */
.results.lang-text {
border-radius: 4px;
border: 1px solid rgba(255,255,255,0.5);
padding: 1ex;
max-height: 80vh;
overflow-y: auto;
}
.content { .content {
margin: 1ex; margin: 1ex;
} }
@ -452,51 +102,37 @@ article.post {
} }
/* Header */ /* Header */
.site-header { .site-header {
background-color: #002b36; background-color: #F7F7FF;
border-bottom: rgba(0,0,0,0.1) 1px solid; border-bottom: rgba(0,0,0,0.1) 1px solid;
text-align: center; text-align: center;
padding: 1ex; padding: 1ex;
color: #fff;
} }
.site-header h1 { .site-header h1 {
margin-top: 0; margin-top: 0;
font-size: 200%; font-size: 200%;
font-family: monospace, sans; font-family: monospace, sans;
color: inherit; color: #000;
}
.site-header .site-links a {
color: #00fdf2;
} }
.site-header .site-links .fancy-link { .site-header .site-links .fancy-link {
border-right: 1px solid #fff; border-right: 1px solid #000;
padding-left: 0.75ex; padding-left: 0.75ex;
} }
.site-header .site-links .fancy-link:last-child {
.site-header .site-links .fancy-link:last-of-type {
border: none; border: none;
} }
/* Post header */ /* Post header */
.post-metadata .post-publication-date {
background-color: #024;
color: #fff;
display: inline-block;
padding: 0 0.5ex;
border-radius: 4px;
}
.post-metadata ul.post-tags { .post-metadata ul.post-tags {
list-style: none; list-style: none;
display: inline; display: inline;
padding: 0; padding: 0;
} }
.post-metadata ul.post-tags li.post-tag a::before { .post-metadata ul.post-tags li.post-tag::before {
content: '#'; content: '#';
} }
.post-metadata ul.post-tags li.post-tag { .post-metadata ul.post-tags li {
display: inline; display: inline;
font-style: italic;
} }
/* Post index. */ /* Post index. */
@ -506,127 +142,28 @@ article.post {
/* padding: 1ex; */ /* padding: 1ex; */
margin-bottom: 1em; margin-bottom: 1em;
padding-bottom: 1em; padding-bottom: 1em;
border-bottom: #000 2px dashed; border-bottom: #000 1px dashed;
} }
.index-pages {
display: block;
margin: 0 auto;
width: fit-content;
}
.index-pages a {
padding: 1ex;
display: inline-block;
background-color: #024;
color: #fff;
border-radius: 4px;
}
.older-posts::after {
content: ' >';
}
.newer-posts::before {
content: '< ';
}
/* Categories and archive */
.post-list .post .post-metadata,
.post-list .post h4 {
display: inline;
}
.post-list .post {
margin-top: 1rem;
}
/* Tables. */
table, th, td, tr {
border: 1px solid black;
border-collapse: collapse;
}
td {
padding: 0.5ex;
}
tr.__table-separator {
border-bottom: 0.5ex solid black;
}
.connections svg {
max-width: 100%;
height: auto;
}
.connections svg .node polygon,
.connections svg .cluster polygon {
fill: white;
}
.connections svg #graph0 > polygon {
/* Main box */
fill: transparent;
stroke: none;
}
/* Side-to-side */
@media (min-width: 120ex) {
body:not(.no-toc) {
margin: 0;
max-width: none;
}
body:not(.no-toc) > .node {
margin-left: 25rem;
}
body:not(.no-toc) .global-table-of-contents {
position: fixed;
left: 1ex;
max-width: 25rem;
top: 1ex;
overflow: auto;
max-height: 100vh;
}
}
/* Dark mode. */ /* Dark mode. */
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
html, body { html {
background-color: #1d1f21; background-color: #111;
color: #fafafe; color: #fafafe;
} }
.node .node {
border-color: #8c9ea0;
}
.node .node > .title::before {
color: #aaa;
}
h2 a { h2 a {
color: #fafafe; color: #fafafe;
} }
a { a {
color: #00fdf2; color: #94dcff;
}
a:focus {
background-color: #00fdf2;
color: black;
}
h1 a:focus,
h2 a:focus,
h3 a:focus,
h4 a:focus,
h5 a:focus,
h6 a:focus
{
background-color: inherit;
color: #f7da4a;
} }
h1,h2,h3,h4,h5,h6 { h1,h2,h3,h4,h5,h6 {
color: #f7da4a; color: #f7da4a;
} }
/* Header */ /* Header */
.site-header { .site-header {
background-color: #303033; background-color: #303330;
border-bottom: rgba(0,0,0,0.1) 1px solid; border-bottom: rgba(0,0,0,0.1) 1px solid;
} }
.site-header h1 { .site-header h1 {
@ -636,130 +173,23 @@ tr.__table-separator {
border-right: 1px solid #fff; border-right: 1px solid #fff;
} }
/* Nav bar */
body nav h1 {
color: #eee;
}
body nav input {
color: #ddd;
border-bottom: 1px solid #888;
}
.results-box-container .results-box input {
color: #ddd;
border-bottom: 1px solid #888;
}
.results-box {
box-shadow: none;
background-color: #262628;
}
.results-box ul li {
background-color: #303033;
box-shadow: none;
}
.results-box ul li h2 {
color: white;
}
.results-box-container .results-box input:focus {
border-bottom: 1px solid #fff;
}
/* Code blocks */ /* Code blocks */
.highlight pre {
padding: 1ex;
background-color: #262628;
}
pre { pre {
padding: 0.25ex; padding: 0.5ex;
background-color: inherit; background-color: inherit;
box-shadow: 0px 1px 2px 1px rgba(0, 0, 0, 0.0); box-shadow: none;
} }
pre code { pre code {
padding: 1ex; padding: 1ex;
font-size: medium; font-size: medium;
border: none; border: none;
background: #262628; background: #000;
color: #fff; color: #fff;
} }
code, .verbatim { code {
background: #262628; background: #262826;
color: #FFF; color: #FFF;
font-family: Menlo, Monaco, "Courier New", monospace; font-family: Menlo, Monaco, "Courier New", monospace;
} }
/* Results */
.results.lang-text {
border: 1px solid rgba(255,255,255,0.25);
}
.node .contents ul,
.global-table-of-contents ul {
--tree-color: #aaa;
}
/* Tables. */
table, th, td, tr {
border: 1px solid #eee;
}
tr.__table-separator {
border-bottom: 0.5ex solid #eee;
}
.connections svg polygon {
stroke: white;
fill: #222;
}
.connections svg .edge polygon {
stroke: white;
fill: white;
}
.connections svg .node polygon,
.connections svg .cluster polygon {
stroke: transparent;
fill: #303030;
}
.connections svg .cluster-depth-1 polygon {
stroke: transparent;
fill: #353535;
}
.connections svg .cluster-depth-2 polygon {
stroke: transparent;
fill: #3a3939;
}
.connections svg .cluster-depth-3 polygon {
stroke: transparent;
fill: #444444;
}
.connections svg .cluster-depth-4 polygon {
stroke: transparent;
fill: #484847;
}
.connections svg .cluster-depth-5 polygon {
stroke: transparent;
fill: #515151;
}
.connections svg .cluster-depth-6 polygon {
stroke: transparent;
fill: #565555;
}
.connections svg .cluster-depth-7 polygon {
stroke: transparent;
fill: #5a5a5a;
}
.connections svg text {
fill: white;
}
.connections svg path {
stroke: white;
}
} }