Compare commits

..

1 Commits

Author SHA1 Message Date
Sergio Martínez Portela
16657d95ee Test generating and querying Xapian indexes. 2022-06-04 19:24:02 +02:00
51 changed files with 247 additions and 11971 deletions

1
.gitignore vendored
View File

@ -1,2 +1 @@
_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

@ -1,142 +0,0 @@
#!/usr/bin/env python3
import sys
import http.server
import socketserver
import threading
import os
import time
import select
import urllib.parse
import inotify.adapters
PORT = int(os.getenv('PORT', 8000))
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
MONITORED_EVENT_TYPES = (
'IN_CREATE',
# 'IN_MODIFY',
'IN_CLOSE_WRITE',
'IN_DELETE',
'IN_MOVED_FROM',
'IN_MOVED_TO',
'IN_DELETE_SELF',
'IN_MOVE_SELF',
)
WAITING_RESPONSES = []
SLEEP_TIME = 0.5
COUNTER = 0
MAX_WAITS = 100
class Server(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
if self.path.strip('/') == '__wait_for_changes':
WAITING_RESPONSES.append(self)
print(len(WAITING_RESPONSES), "waiting responses")
global COUNTER
ticket, COUNTER = COUNTER, COUNTER + 1
while self in WAITING_RESPONSES:
# This is an horribe way to wait! ... but it may work for quick tests 🤷
if COUNTER - ticket > MAX_WAITS:
# Connection closed by the other side
print("Manually closed for cleanup")
WAITING_RESPONSES.remove(self)
# send 502 response, timeout
self.send_response(502)
# send response headers
self.end_headers()
return
time.sleep(SLEEP_TIME)
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
self.send_response(200)
# send response headers
self.end_headers()
with open(path.strip('/'), 'rb') as f:
# send the body of the response
self.wfile.write(f.read())
if not path.endswith('.html'):
return
else:
# Append update waiter
with open(os.path.join(THIS_DIR, 'wait_for_update.js'), 'rb') as f:
new_data = b'<script>' + f.read() + b'</script>'
self.wfile.write(new_data)
new_data_len = len(new_data)
return
def notify_reloads():
while len(WAITING_RESPONSES) > 0:
# Close opened connections
res = WAITING_RESPONSES.pop(0)
try:
# send 200 response
res.send_response(200)
# send response headers
res.end_headers()
except Exception as e:
print("ERROR:", e)
global COUNTER
COUNTER = 0
def start_notifier():
notifier = inotify.adapters.InotifyTree(os.getcwd())
should_reload = False
for event in notifier.event_gen(yield_nones=True):
if event is None:
if should_reload:
print("Reloading!")
should_reload = False
notify_reloads()
continue
(ev, types, directory, file) = event
if any([type in MONITORED_EVENT_TYPES for type in types]):
print("Detected change!", types, directory, file)
should_reload = True
def serve():
Handler = Server
notifier_thread = threading.Thread(target=start_notifier)
with http.server.ThreadingHTTPServer(("127.0.0.1", PORT), Handler) as httpd:
print("serving at port", PORT)
notifier_thread.start()
httpd.serve_forever()
if __name__ == '__main__':
serve()

View File

@ -1,596 +0,0 @@
#!/usr/bin/env python3
MARKDOWN_EXTENSION = '.md'
EXTENSIONS = [
MARKDOWN_EXTENSION,
]
MARKDOWN_EXTRA_FEATURES = [
# See more in: https://python-markdown.github.io/extensions/
'markdown.extensions.fenced_code',
'markdown.extensions.codehilite',
'markdown.extensions.extra',
]
import copy
import json
import logging
import sys
import os
import datetime
import shutil
import traceback
import time
import re
from typing import List
from bs4 import BeautifulSoup as bs4
import bs4 as BeautifulSoup
import jinja2
import inotify.adapters
import yaml
import markdown
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})$')
COMPLETE_DATE_RE = re.compile(r'^(\d{4})-(0\d|1[012])-([0-2]\d|30|31) '
+ r'(\d{2}):(\d{2})(:\d{2})( .+)?$')
SLUG_HYPHENATE_RE = re.compile(r'[\s\-]+')
SLUG_REMOVE_RE = re.compile(r'[^\s\-a-zA-Z0-9]*')
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
STATIC_PATH = os.path.join(ROOT_DIR, 'static')
ARTICLE_TEMPLATE_NAME = 'article.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
STATIC_RESOURCES = (
('style.css', 'css/style.css'),
('light-syntax.css', 'css/light-syntax.css'),
('dark-syntax.css', 'css/dark-syntax.css', ('@media (prefers-color-scheme: dark) {\n', '\n}')),
)
JINJA_ENV = jinja2.Environment(
loader=jinja2.FileSystemLoader(STATIC_PATH),
autoescape=jinja2.select_autoescape()
)
WATCH = True
if os.getenv('WATCH_AND_REBUILD', '1') == '0':
WATCH = False
def update_statics():
global ARTICLE_TEMPLATE
ARTICLE_TEMPLATE = JINJA_ENV.get_template(ARTICLE_TEMPLATE_NAME)
global BLOG_INDEX_TEMPLATE
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()
MONITORED_EVENT_TYPES = (
'IN_CREATE',
# 'IN_MODIFY',
'IN_CLOSE_WRITE',
'IN_DELETE',
'IN_MOVED_FROM',
'IN_MOVED_TO',
'IN_DELETE_SELF',
'IN_MOVE_SELF',
)
LANG_PRIORITY = ('en', 'es', 'gl')
def parse_nikola_date(match):
return datetime.datetime(year=int(match.group(3)),
month=int(match.group(2)),
day=int(match.group(1)),
hour=int(match.group(4)),
minute=int(match.group(5)),
# Note this final assumption is not good
# and might get you in trouble if trying
# to sort closely-published posts
# when others are in complete-date format
tzinfo=datetime.timezone.utc,
)
def parse_complete_date(match):
return datetime.datetime.strptime(match.group(0), '%Y-%m-%d %H:%M:%S %Z%z')
def split_tags(tags: str) -> List[str]:
if isinstance(tags, str):
return [tag.strip() for tag in tags.split(',')]
elif isinstance(tags, list):
return tags
else:
raise NotImplementedError("Unknown tag type: {}".format(type(tags)))
def slugify(title):
"""
Made for compatibility with Nikola's slugify within CodigoParaLlevar blog.
"""
slug = unidecode(title).lower()
slug = SLUG_REMOVE_RE.sub('', slug)
slug = SLUG_HYPHENATE_RE.sub('-', slug)
slug = slug.strip('-')
return slug.strip()
def read_markdown(path):
with open(path, 'rt') as f:
data = f.read()
if data.startswith('---'):
start = data.index('\n')
if '---\n' not in data[start:]:
raise Exception('Front matter not finished on: {}'.format(path))
front_matter_str, content = data[start:].split('---\n', 1)
front_matter = yaml.load(front_matter_str, Loader=yaml.SafeLoader)
else:
raise Exception('Front matter is needed for proper rendering. Not found on: {}'.format(
path
))
doc = markdown.markdown(content, extensions=MARKDOWN_EXTRA_FEATURES)
return doc, front_matter
def get_out_path(front_matter):
if 'date' in front_matter:
if m := NIKOLA_DATE_RE.match(front_matter['date']):
front_matter['date'] = parse_nikola_date(m)
elif m := COMPLETE_DATE_RE.match(front_matter['date']):
front_matter['date'] = parse_complete_date(m)
else:
raise NotImplementedError('Unknown date format: {}'.format(
front_matter['date']))
else:
raise Exception('No date found on: {}'.format(
path
))
if 'slug' not in front_matter:
if 'title' not in front_matter:
raise Exception('No title found on: {}'.format(
path
))
front_matter['slug'] = slugify(front_matter['title'])
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
def load_all(top_dir_relative):
top = os.path.abspath(top_dir_relative)
docs = {}
count = 0
for root, dirs, files in os.walk(top):
for name in files:
if all([not name.endswith(ext) for ext in EXTENSIONS]):
# The logic is negative... but it works
continue
if name.endswith(MARKDOWN_EXTENSION):
path = os.path.join(root, name)
doc, front_matter = read_markdown(path)
out_path = get_out_path(front_matter)
docs[path] = (doc, front_matter, out_path)
print('\rLoading posts... {}'.format(count), end='', flush=True)
count += 1
else:
raise NotImplementedError('Unknown filetype: {}'.format(name))
print(" [DONE]")
return docs
def load_doc(filepath):
doc, front_matter = read_markdown(filepath)
out_path = get_out_path(front_matter)
return (doc, front_matter, out_path)
def render_article(doc, front_matter, f, out_path):
extsep = '/' if '/' in out_path else '\\'
subdirs = len(out_path.split(extsep))
base_path = os.path.join(*(['..'] * subdirs))
result = ARTICLE_TEMPLATE.render(
content=doc,
title=front_matter['title'],
post_publication_date=front_matter['date'],
post_tags=split_tags(front_matter['tags']),
base_path=base_path,
)
f.write(result)
def summarize(doc):
tree = bs4(doc, features='lxml')
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):
# 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)
index_ranges = range(0, len(docs), BLOG_INDEX_PAGE_SIZE)
for off in index_ranges:
page = docs[off: off + BLOG_INDEX_PAGE_SIZE]
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, _alternatives) 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(
posts=posts,
prev_index_num=prev_index_num,
next_index_num=next_index_num,
)
if off == 0:
fname = 'index.html'
else:
fname = 'index-{}.html'.format(off // BLOG_INDEX_PAGE_SIZE)
with open(os.path.join(dest_top, fname), 'wt') as f:
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):
if docs is None:
docs = load_all(source_top)
# Render posts
for (doc, front_matter, out_path) in docs.values():
doc_full_path = os.path.join(dest_top, out_path)
os.makedirs(os.path.dirname(doc_full_path), exist_ok=True)
# print("==", doc_full_path)
full_out_path = doc_full_path + '/index.html'
os.makedirs(os.path.dirname(full_out_path), exist_ok=True)
with open(full_out_path, 'wt') as f:
try:
render_article(doc, front_matter, f, out_path)
except:
logging.error(traceback.format_exc())
logging.error("Rendering failed 😿")
continue
# Render statics
for static in STATIC_RESOURCES:
src_path = static[0]
dest_path = static[1]
if len(static) > 2:
before, after = static[2]
else:
before, after = '', ''
target_dest = os.path.join(dest_top, dest_path)
os.makedirs(os.path.dirname(target_dest), exist_ok=True)
with open(os.path.join(STATIC_PATH, src_path), 'rt') as src:
data = before + src.read() + after
with open(target_dest, 'wt') as f:
f.write(data)
# Render index
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
def main(source_top, dest_top):
notifier = inotify.adapters.InotifyTrees([source_top, STATIC_PATH])
## Initial load
t0 = time.time()
logging.info("Initial load...")
docs = regen_all(source_top, dest_top)
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
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
filepath = os.path.join(directory, file)
if filepath.startswith(STATIC_PATH):
t0 = time.time()
try:
update_statics()
except:
logging.error(traceback.format_exc())
logging.error("Loading new templates failed 😿")
continue
is_static_resource = False
for static in STATIC_RESOURCES:
src_path = static[0]
dest_path = static[1]
if file == os.path.basename(src_path):
is_static_resource = True
if len(static) > 2:
before, after = static[2]
else:
before, after = '', ''
target_dest = os.path.join(dest_top, dest_path)
os.makedirs(os.path.dirname(target_dest), exist_ok=True)
with open(os.path.join(STATIC_PATH, src_path), 'rt') as src:
data = before + src.read() + after
with open(target_dest, 'wt') as f:
f.write(data)
if is_static_resource:
logging.info("Updated static resources in {:.2f}s".format(time.time() - t0))
else:
docs = regen_all(source_top, dest_top, docs)
logging.info("Updated all in {:.2f}s".format(time.time() - t0))
else:
try:
print("Reloading: {}".format(filepath))
(doc, front_matter, out_path) = load_doc(filepath)
except:
logging.error(traceback.format_exc())
logging.error("Skipping update 😿")
continue
t0 = time.time()
docs[filepath] = (doc, front_matter, 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 + '/index.html'), exist_ok=True)
# print("==", doc_full_path)
with open(doc_full_path + '/index.html', 'wt') as f:
try:
render_article(doc, front_matter, f, out_path)
render_archive(docs, dest_top)
except:
logging.error(traceback.format_exc())
logging.error("Rendering failed 😿")
continue
logging.info("Updated all in {:.2f}s".format(time.time() - t0))
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Usage: {} SOURCE_TOP DEST_TOP".format(sys.argv[0]))
exit(0)
logging.basicConfig(level=logging.INFO, format="%(levelname)-8s %(message)s")
main(sys.argv[1], sys.argv[2])

40
scripts/brain-query Executable file
View File

@ -0,0 +1,40 @@
#!/usr/bin/env python3
import xapian
import sys
import os
import json
def main(path, query):
db = xapian.Database(path, xapian.DB_OPEN)
docid_map_path = os.path.join(path, "docid_map.json")
with open(docid_map_path, 'rt') as f:
docid_to_node = json.load(f)
qp = xapian.QueryParser()
stemmer = xapian.Stem("english")
qp.set_stemmer(stemmer)
qp.set_database(db)
qp.set_stemming_strategy(xapian.QueryParser.STEM_SOME)
xap_query = qp.parse_query(query)
print("Parsed query is: {}".format(xap_query))
enquire = xapian.Enquire(db)
enquire.set_query(xap_query)
matches = enquire.get_mset(0, 10)
for match in matches:
print(
"ID {} {}% | DocId: {}".format(
match.rank + 1,
match.percent,
docid_to_node[str(match.document.get_docid())],
)
)
if __name__ == '__main__':
if len(sys.argv) != 3:
print("Brain-Query")
print("Usage: {} <path> <query>".format(sys.argv[0]))
exit(0)
main(sys.argv[1], sys.argv[2])

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,20 +1,14 @@
#!/usr/bin/env python3
import sqlite3
import time
import json
import html
import logging
import os
import sys
import uuid
from datetime import datetime
import traceback
import re
from itertools import chain
import xapian
import shutil
import inotify.adapters
from datetime import datetime
import org_rw
from org_rw import OrgTime, dom, Link
@ -22,79 +16,12 @@ from org_rw import dump as dump_org
from org_rw import load as load_org
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 = [
".org",
".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
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):
top = os.path.abspath(top_dir_relative)
@ -121,46 +48,26 @@ def load_all(top_dir_relative):
logging.info("Collected {} files".format(len(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)
base_dirs = set()
files_generated = 0
doc_to_headline_remapping = {}
os.makedirs(dest_top, exist_ok=True)
## Build headline list
# This includes a virtual headline for ID-referenced documents.
all_headlines = []
main_headlines_by_path = {}
main_headline_to_docid = {}
graph = {}
for doc in docs:
relpath = os.path.relpath(doc.path, src_top)
changed = False
headlines = list(doc.getAllHeadlines())
related = None
if not relpath.startswith(subpath + "/"):
# print("Skip:", relpath)
continue
base_dirs.add(os.path.dirname(relpath))
i = len(headlines)
while i > 0:
i -= 1
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:
print(
"Found duplicated related: {} vs {}".format(
@ -180,7 +87,10 @@ def regen_all(src_top, dest_top, subpath, *, docs=None, db=None):
print("Updated", relpath)
save_changes(doc)
all_headlines.extend(headlines)
if not relpath.startswith("public/"):
# print("Skip:", relpath)
continue
main_headline = None
topHeadlines = doc.getTopHeadlines()
@ -188,11 +98,13 @@ def regen_all(src_top, dest_top, subpath, *, docs=None, db=None):
or (len(topHeadlines) == 2 and related is not None)):
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:
doc_to_headline_remapping['id:' + doc.id] = 'id:' + main_headline.id
main_headline_to_docid[main_headline.id] = doc.id
files_generated += 1
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
f.write(as_document(render(main_headline, doc, headlineLevel=0)))
files_generated += 1
elif doc.id is not None:
logging.error("Cannot render document from id: {}. {} headlines {} related".format(
relpath,
@ -200,78 +112,51 @@ def regen_all(src_top, dest_top, subpath, *, docs=None, db=None):
'with' if related is not None else 'without'
))
# Build graph
graph = {}
backlink_graph = {}
for headline in all_headlines:
links = []
headline_links = list(headline.get_links())
if headline == main_headline and related is not None:
headline_links.extend(list(related.get_links()))
for headline in headlines:
endpath = os.path.join(dest_top, headline.id + ".node.html")
for l in headline_links:
if l.value.startswith('http://') or l.value.startswith('https://'):
pass # Ignore for now, external URL
elif l.value.startswith('id:'):
links.append({'target': l.value})
elif l.value.startswith('attachment:'):
pass # Ignore, attachment
elif l.value.startswith('file:'):
pass # Ignore, attachment
elif l.value.startswith('notmuch:'):
pass # Ignore, mail
elif l.value.startswith('orgit-rev:'):
pass # Ignore, mail
elif l.value.startswith('*'):
pass # Ignore, internal
elif not ':' in l.value.split()[0]:
pass # Ignore, internal
elif l.value.startswith('./'):
pass # TODO: Properly handle
else:
logging.warning('On document {}, unknown link to {}'.format(doc.path, l.value))
links = []
headline_links = list(headline.get_links())
if headline == main_headline and related is not None:
headline_links.extend(list(related.get_links()))
if headline.parent:
if isinstance(headline.parent, org_rw.Headline):
links.append({
"target": headline.parent.id,
"relation": "in"
})
for backlink in links:
if 'relation' in backlink and backlink['relation'] == 'in':
continue
for l in headline_links:
if l.value.startswith('http://') or l.value.startswith('https://'):
pass # Ignore for now, external URL
elif l.value.startswith('id:'):
links.append({'target': l.value})
elif l.value.startswith('attachment:'):
pass # Ignore, attachment
elif l.value.startswith('file:'):
pass # Ignore, attachment
elif l.value.startswith('notmuch:'):
pass # Ignore, mail
elif l.value.startswith('orgit-rev:'):
pass # Ignore, mail
elif l.value.startswith('*'):
pass # Ignore, internal
elif not ':' in l.value.split()[0]:
pass # Ignore, internal
elif l.value.startswith('./'):
pass # TODO: Properly handle
else:
raise NotImplementedError('On document {}, link to {}'.format(doc.path, l.value))
target = backlink['target']
if target.startswith('id:'):
target = target[len('id:'):]
if headline.parent:
if isinstance(headline.parent, org_rw.Headline):
links.append({
"target": headline.parent.id,
"relation": "in"
})
graph[headline.id] = {
"title": headline.title.strip(),
"links": links,
"depth": headline.depth,
}
if target not in backlink_graph:
backlink_graph[target] = set()
backlink_graph[target].add(headline.id)
graph[headline.id] = {
"title": org_rw.org_rw.token_list_to_plaintext(headline.title.contents).strip(),
"links": links,
"depth": headline.depth,
}
if headline.id in main_headline_to_docid:
graph[main_headline_to_docid[headline.id]] = graph[headline.id]
topLevelHeadline = headline
while isinstance(topLevelHeadline.parent, org_rw.Headline):
topLevelHeadline = topLevelHeadline.parent
# 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,
))
with open(endpath, "wt") as f:
f.write(as_document(render(headline, doc, headlineLevel=0)))
files_generated += 1
# Update graph, replace document ids with headline ids
for headline_data in graph.values():
@ -279,17 +164,6 @@ def regen_all(src_top, dest_top, subpath, *, docs=None, db=None):
if link['target'] in doc_to_headline_remapping:
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
graphpath = os.path.join(dest_top, "graph.json")
graph_explorer_path = os.path.join(dest_top, "graph.html")
@ -297,444 +171,162 @@ def regen_all(src_top, dest_top, subpath, *, docs=None, db=None):
json.dump(obj=graph, fp=f, indent=2)
graph_explorer_path = os.path.join(dest_top, "graph.html")
with open(graph_explorer_path, 'wt') as f:
with open(os.path.join(os.path.dirname(os.path.abspath(dest_top)), '..', 'static', 'graph_explorer.html'), 'rt') as template:
with open(os.path.join(os.path.dirname(os.path.abspath(dest_top)), 'static', 'graph_explorer.html'), 'rt') as template:
source = template.read()
f.write(source.replace('<!-- REPLACE_THIS_WITH_GRAPH -->',
json.dumps(graph)))
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)))
# Generate index files
t0 = datetime.utcnow()
logging.info("Generating text index...")
# Render all headlines
for headline in all_headlines:
endpath = os.path.join(dest_top, headline.id + ".node.html")
xapian_db = os.path.join(dest_top, "xapian")
if os.path.exists(xapian_db):
shutil.rmtree(xapian_db)
db = xapian.WritableDatabase(xapian_db, xapian.DB_CREATE)
# 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
indexer = xapian.TermGenerator()
stemmer = xapian.Stem("english")
indexer.set_stemmer(stemmer)
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
docid_to_node = {}
cur.close()
db.commit()
for doc in docs:
relpath = os.path.relpath(doc.path, src_top)
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):
if not relpath.startswith("public/"):
# print("Skip:", relpath)
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)
changed = False
for hl in doc.getAllHeadlines():
xapian_doc = xapian.Document()
content = "\n".join(doc.dump_headline(hl))
xapian_doc.set_data(content)
indexer.set_document(xapian_doc)
indexer.index_text(content)
doc_id = db.add_document(xapian_doc)
docid_to_node[doc_id] = { 'hl': hl.id, 'doc': doc.path }
docid_map_path = os.path.join(xapian_db, "docid_map.json")
with open(docid_map_path, 'wt') as f:
json.dump(docid_to_node, f)
logging.info("Text index generated in {}".format(datetime.utcnow() - t0))
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
def print_tree(tree, indentation=0):
return
for element in tree:
print(" " * indentation + "- " + str(type(element)))
if "children" in dir(element):
if len(element.children) > 0:
print_element(element.children, indentation + 1, headline)
print_tree(element.children, indentation + 1)
print()
elif "content" in dir(element):
for content in element.content:
print_element(content, indentation + 1, headline)
def print_element(element, indentation, headline):
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):
def render_property_drawer(element, acc):
pass
def render_logbook_drawer(element, acc, headline, graph):
def render_logbook_drawer(element, acc):
pass
def render_property_node(element, acc, headline, graph):
def render_property_node(element, acc):
pass
def render_list_group(element, acc, headline, graph):
def render_list_group(element, acc):
acc.append("<ul>")
render_tree(element.children, acc, headline, graph)
render_tree(element.children, acc)
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):
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):
def render_list_item(element, acc):
acc.append("<li>")
if element.tag is not None:
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 class='item'>")
render_text_tokens(element.content, acc, headline, graph)
render_text_tokens(element.content, acc)
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
acc.append(unindent(content))
if is_code:
acc.append('</code>')
acc.append('</pre>')
def render_code_block(element, acc):
acc.append('<pre><code>')
acc.append(html.escape(element.lines))
acc.append('</code></pre>')
def unindent(content):
base_indentation = min([
len(l) - len(l.lstrip(' '))
for l in content.split('\n')
if len(l.strip()) > 0
])
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
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):
# TODO:
# acc.append('<pre class="results"><code>')
# render_tree(element.children, acc)
# acc.append('</code></pre>')
pass
def render_results_block(element, acc, headline, graph):
items = [e.get_raw() for e in element.children]
content = '\n'.join(items)
if len(content.strip()) > 0:
render_block(content, acc, _class='results lang-text', is_code=False)
def render_text(element, acc):
acc.append('<span class="text">')
render_text_tokens(element.content, acc)
acc.append('</span>')
def render_org_text(element, acc, headline, graph):
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
def render_text_tokens(tokens, acc):
for chunk in tokens:
if isinstance(chunk, str):
lines = chunk.split('\n\n')
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)))
acc.append('{}</span> '.format(chunk))
elif isinstance(chunk, Link):
link_target = chunk.value
is_internal_link = True
if link_target.startswith('id:'):
link_target = './' + link_target[3:] + '.node.html'
description = chunk.description
if description is None:
description = chunk.value
try:
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),
'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),
))
except NonExistingLocalNoteError as err:
logging.warning(err.get_message())
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>')
acc.append('<a href="{}">{}</a>'.format(
html.escape(link_target),
html.escape(description),
))
# else:
# raise NotImplementedError('TextToken: {}'.format(chunk))
def render_tag(element, acc, headline, graph):
def render_tag(element, acc):
return {
dom.PropertyDrawerNode: render_property_drawer,
dom.LogbookDrawerNode: render_logbook_drawer,
dom.PropertyNode: render_property_node,
dom.ListGroupNode: render_list_group,
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.Text: render_text,
dom.ResultsDrawerNode: render_results_block,
org_rw.Text: render_org_text,
}[type(element)](element, acc, headline, graph)
}[type(element)](element, acc)
def render_tree(tree, acc, headline, graph):
def render_tree(tree, acc):
for element in tree:
render_tag(element, acc, headline, graph)
def render_inline(tree, f, headline, graph):
acc = []
f(tree, acc, headline, graph)
return ''.join(acc)
render_tag(element, acc)
def render_as_document(headline, doc, headlineLevel, graph, title, doc_to_headline_remapping):
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):
def render(headline, doc, headlineLevel):
try:
dom = headline.as_dom()
except:
logging.error("Error generating DOM for {}".format(doc.path))
raise
print_tree(dom, indentation=2, headline=headline)
print_tree(dom)
content = []
if headline.id and headlineLevel == 0:
render_connections(headline.id, content, graph, doc_to_headline_remapping=doc_to_headline_remapping)
render_tree(dom, content, headline, graph)
render_tree(dom, content)
for child in headline.children:
content.append(render(child, doc, headlineLevel=headlineLevel+1, graph=graph,
doc_to_headline_remapping=doc_to_headline_remapping))
content.append(render(child, doc, headlineLevel=headlineLevel+1))
if headline.state is None:
state = ""
@ -746,32 +338,17 @@ def render(headline, doc, graph, headlineLevel, doc_to_headline_remapping):
else:
todo_state = "done"
tag_list = []
for tag in headline.shallow_tags:
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'
# # 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>"
display_state = 'collapsed'
if headlineLevel < MIN_HIDDEN_HEADLINE_LEVEL:
display_state = 'expanded'
return f"""
<div id="{html.escape(headline.id)}" class="node {todo_state} {display_state}">
<h1 class="title">
{state}
{title}
{tags}
<a href=\"javascript:toggle_expand('{html.escape(headline.id)}')\">
{html.escape(headline.title)}
</a>
</h1>
<div class='contents'>
{''.join(content)}
@ -780,40 +357,27 @@ def render(headline, doc, graph, headlineLevel, doc_to_headline_remapping):
"""
def as_document(html, title, global_toc):
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>
"""
def as_document(html):
return f"""<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{title} @ {SITE_NAME}</title>
<link href="../static/style.css" rel="stylesheet"/>
<link href="../static/syntax.css" rel="stylesheet"/>
<!-- v Fixes mobile viewports. -->
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script type="text/javascript">
function toggle_expand(header_id) {{
var e = document.getElementById(header_id);
if (e.classList.contains('expanded')) {{
e.classList.add('collapsed');
e.classList.remove('expanded');
}}
else {{
e.classList.add('expanded');
e.classList.remove('collapsed');
}}
}}
</script>
</head>
<body class="{' '.join(body_classes)}">
<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}
<body>
{html}
<script src="../static/search-box.js"></script>
<script tye="text/javascript">_codigoparallevar_enable_search_box('#searchbox', {{placeholder: 'Search...'}})</script>
</body>
</html>
"""
@ -826,13 +390,9 @@ def save_changes(doc):
if __name__ == "__main__":
if len(sys.argv) not in (3, 4):
print("Usage: {} SOURCE_TOP DEST_TOP <SUBPATH>".format(sys.argv[0]))
if len(sys.argv) != 3:
print("Usage: {} SOURCE_TOP DEST_TOP".format(sys.argv[0]))
exit(0)
logging.basicConfig(level=logging.INFO, format="%(levelname)-8s %(message)s")
subpath = DEFAULT_SUBPATH
if len(sys.argv) == 4:
subpath = sys.argv[3]
exit(main(sys.argv[1], sys.argv[2], subpath=subpath))
main(sys.argv[1], sys.argv[2])

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 +0,0 @@
Markdown
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

@ -1,19 +0,0 @@
(function (){
var wait_for_update = function() {
console.debug("Waiting for changes...");
fetch('/__wait_for_changes').then(r => {
if (r.status !== 200) {
setTimeout(
wait_for_update,
1000,
);
}
else {
// Reload
window.location = window.location;
}
});
};
wait_for_update();
})();

View File

@ -1,51 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{ title }} @ Código para llevar [blog]</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="{{ base_path }}/../static/style.css" />
<link rel="stylesheet" href="{{ base_path }}/../static/light-syntax.css" />
<link rel="stylesheet" href="{{ base_path }}/../static/dark-syntax.css" />
</head>
<body class="blog">
<div class="site-header">
<h1 class="site-name"><a href="{{ base_path }}/">Codigo para llevar [blog]</a></h1>
<nav class="site-links">
<span class="fancy-link">
<a href="{{ base_path }}/../">Home</a>
</span>
<span class="fancy-link">
<a href="{{ base_path }}/../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="content">
<article class="post">
<h2 class="post-title">{{ title }}</h2>
<div class="post-metadata">
<time class="post-publication-date" datetime="{{ post_publication_date.date() }}">
{{ post_publication_date.date() }}
</time>
<ul class="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>
{% endfor %}
</ul>
</div>
<div class="post-content">
{{ content | safe }}
</div>
</article>
</div>
</body>
</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,6 +0,0 @@
body {
margin: 0 auto;
width: fit-content;
max-width: 100ex;
padding: 0 1ex;
}

View File

@ -1,68 +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-index content">
{% for post in posts %}
<div class="post-container">
<article class="post">
<h2 class="post-title"><a href="{{ post.link }}">{{ post.title }}</a></h2>
<div class="post-metadata">
<time class="post-publication-date" datetime="{{ post.post_publication_date.date() }}">
{{ post.post_publication_date.date() }}
</time>
<ul class="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>
{% endfor %}
</ul>
</div>
<div class="post-content">
{{ post.summary | safe }}
</div>
</article>
</div>
{% endfor %}
</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>
</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,85 +0,0 @@
/* Dark mode. */
@media (prefers-color-scheme: dark) {
pre { line-height: 125%; }
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; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.hll { background-color: #49483e }
.c { color: #75715e } /* Comment */
.err { color: #960050; background-color: #1e0010 } /* Error */
.esc { color: #f8f8f2 } /* Escape */
.g { color: #f8f8f2 } /* Generic */
.k { color: #66d9ef } /* Keyword */
.l { color: #ae81ff } /* Literal */
.n { color: #f8f8f2 } /* Name */
.o { color: #f92672 } /* Operator */
.x { color: #f8f8f2 } /* Other */
.p { color: #f8f8f2 } /* Punctuation */
.ch { color: #75715e } /* Comment.Hashbang */
.cm { color: #75715e } /* Comment.Multiline */
.cp { color: #75715e } /* Comment.Preproc */
.cpf { color: #75715e } /* Comment.PreprocFile */
.c1 { color: #75715e } /* Comment.Single */
.cs { color: #75715e } /* Comment.Special */
.gd { color: #f92672 } /* Generic.Deleted */
.ge { color: #f8f8f2; font-style: italic } /* Generic.Emph */
.gr { color: #f8f8f2 } /* Generic.Error */
.gh { color: #f8f8f2 } /* Generic.Heading */
.gi { color: #a6e22e } /* Generic.Inserted */
.go { color: #66d9ef } /* Generic.Output */
.gp { color: #f92672; font-weight: bold } /* Generic.Prompt */
.gs { color: #f8f8f2; font-weight: bold } /* Generic.Strong */
.gu { color: #75715e } /* Generic.Subheading */
.gt { color: #f8f8f2 } /* Generic.Traceback */
.kc { color: #66d9ef } /* Keyword.Constant */
.kd { color: #66d9ef } /* Keyword.Declaration */
.kn { color: #f92672 } /* Keyword.Namespace */
.kp { color: #66d9ef } /* Keyword.Pseudo */
.kr { color: #66d9ef } /* Keyword.Reserved */
.kt { color: #66d9ef } /* Keyword.Type */
.ld { color: #e6db74 } /* Literal.Date */
.m { color: #ae81ff } /* Literal.Number */
.s { color: #e6db74 } /* Literal.String */
.na { color: #a6e22e } /* Name.Attribute */
.nb { color: #f8f8f2 } /* Name.Builtin */
.nc { color: #a6e22e } /* Name.Class */
.no { color: #66d9ef } /* Name.Constant */
.nd { color: #a6e22e } /* Name.Decorator */
.ni { color: #f8f8f2 } /* Name.Entity */
.ne { color: #a6e22e } /* Name.Exception */
.nf { color: #a6e22e } /* Name.Function */
.nl { color: #f8f8f2 } /* Name.Label */
.nn { color: #f8f8f2 } /* Name.Namespace */
.nx { color: #a6e22e } /* Name.Other */
.py { color: #f8f8f2 } /* Name.Property */
.nt { color: #f92672 } /* Name.Tag */
.nv { color: #f8f8f2 } /* Name.Variable */
.ow { color: #f92672 } /* Operator.Word */
.w { color: #f8f8f2 } /* Text.Whitespace */
.mb { color: #ae81ff } /* Literal.Number.Bin */
.mf { color: #ae81ff } /* Literal.Number.Float */
.mh { color: #ae81ff } /* Literal.Number.Hex */
.mi { color: #ae81ff } /* Literal.Number.Integer */
.mo { color: #ae81ff } /* Literal.Number.Oct */
.sa { color: #e6db74 } /* Literal.String.Affix */
.sb { color: #e6db74 } /* Literal.String.Backtick */
.sc { color: #e6db74 } /* Literal.String.Char */
.dl { color: #e6db74 } /* Literal.String.Delimiter */
.sd { color: #e6db74 } /* Literal.String.Doc */
.s2 { color: #e6db74 } /* Literal.String.Double */
.se { color: #ae81ff } /* Literal.String.Escape */
.sh { color: #e6db74 } /* Literal.String.Heredoc */
.si { color: #e6db74 } /* Literal.String.Interpol */
.sx { color: #e6db74 } /* Literal.String.Other */
.sr { color: #e6db74 } /* Literal.String.Regex */
.s1 { color: #e6db74 } /* Literal.String.Single */
.ss { color: #e6db74 } /* Literal.String.Symbol */
.bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */
.fm { color: #a6e22e } /* Name.Function.Magic */
.vc { color: #f8f8f2 } /* Name.Variable.Class */
.vg { color: #f8f8f2 } /* Name.Variable.Global */
.vi { color: #f8f8f2 } /* Name.Variable.Instance */
.vm { color: #f8f8f2 } /* Name.Variable.Magic */
.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'>
var NODE_GRAPH=<!-- REPLACE_THIS_WITH_GRAPH -->;
</script>
<style>
text {
font-family: sans-serif;
font-size: 10px;
}
</style>
<script>
// Copyright 2021 Observable, Inc.
// Released under the ISC license.
@ -86,15 +80,15 @@ function ForceGraph({
// Add arrowheads
svg.append('defs').append('marker')
.attr('id', 'arrowhead')
.attr('viewBox', '-0 -2.5 5 5')
.attr('refX', 10)
.attr('viewBox', '-0 -5 10 10')
.attr('refX', 13)
.attr('refY', 0)
.attr('orient', 'auto')
.attr('markerWidth', 7)
.attr('markerHeight', 7)
.attr('markerWidth', 13)
.attr('markerHeight', 13)
.attr('xoverflow', 'visible')
.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')
.style('stroke','none');
@ -269,8 +263,6 @@ function ForceGraph({
linkLabel: (d) => { const e = edges[d.index]; if (e.relation) { return e.relation; } else { return ''; } },
});
holder.appendChild(chart);
chart.height = '100vh';
chart.width = '100vw';
</script>
</body>
</html>

View File

@ -1,22 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<html>
<head>
<meta charset="utf-8">
<title>Código para llevar</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
background-color: white;
font-family: sans-serif;
body {
margin: 0 auto;
width: fit-content;
max-width: 100ex;
padding: 0 1ex;
color: black;
}
.header h1 {
text-align: center;
}
}
.links section {
margin-top: 1em;
display: inline-block;
@ -32,9 +26,8 @@
.links p {
margin: 0;
}
h2 a, a h2 {
h2 a {
color: black;
text-decoration: underline;
}
a {
color: #00e;
@ -47,21 +40,21 @@
border-right: 1px solid #000;
}
@media (prefers-color-scheme: dark) {
body {
background-color: #1d1f21;
html {
background-color: #0f110e;
color: #fafafe;
}
h2 a, a h2 {
h2 a {
color: #fafafe;
}
a {
color: #00fdf2;
color: #66f;
}
#social a {
border-color: #fff;
}
.links section {
background-color: #262628;
background-color: #262826;
}
}
</style>
@ -74,19 +67,11 @@
</div>
<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>
<h2><a href="/blog">Blog</a></h2>
<p>
Latest posts:
<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/a-simple-status-indicator/">[2022] A simple status indicator</a></li>
</ul>
@ -95,48 +80,19 @@
<section>
<h2>Collaborations</h2>
<p>
Latest posts in <a href="https://hackliza.gal">Hackliza</a>:
<ul>
<li>
<a href="https://hackliza.gal/en/posts/python-visual-profiling/">Visual profiling in Python (english)</a>
<a href="https://hackliza.gal/posts/python-visual-profiling/">(galician)</a>
</li>
<li>
<a href="https://hackliza.gal/en/posts/quick_math_on_terminal/">Quick math on the terminal (english)</a>
<a href="https://hackliza.gal/posts/contas_rapidas_no_terminal/">(galician)</a>
</li>
</ul>
Latest post in <a href="https://hackliza.gal">Hackliza</a>: <a href="https://hackliza.gal/en/posts/quick_math_on_terminal/">Quick math on the terminal (english)</a>
<a href="https://hackliza.gal/posts/contas_rapidas_no_terminal/">(galician)</a>
</p>
</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>
<p>
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>.
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>.
</p>
</section -->
</section>
<section id="social">
<h2>Find me</h2>
<p>
<a href="https://social.codigoparallevar.com/@kenkeiras">ActivityPub</a>
<a href="https://github.com/kenkeiras">GitHub</a>
<a href="https://gitlab.com/kenkeiras">GitLab</a>
<a href="https://programaker.com/users/kenkeiras">PrograMaker</a>

View File

@ -1,64 +0,0 @@
pre { line-height: 125%; }
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; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.hll { background-color: #ffffcc }
.c { color: #008800; font-style: italic } /* Comment */
.err { color: #a61717; background-color: #e3d2d2 } /* Error */
.g { color: #2c2cff } /* Generic */
.k { color: #2c2cff } /* Keyword */
.x { background-color: #ffffe0 } /* Other */
.ch { color: #008800; font-style: italic } /* Comment.Hashbang */
.cm { color: #008800; font-style: italic } /* Comment.Multiline */
.cp { color: #008800; font-style: italic } /* Comment.Preproc */
.cpf { color: #008800; font-style: italic } /* Comment.PreprocFile */
.c1 { color: #008800; font-style: italic } /* Comment.Single */
.cs { color: #008800; font-style: italic } /* Comment.Special */
.gd { color: #2c2cff } /* Generic.Deleted */
.ge { color: #008800 } /* Generic.Emph */
.gr { color: #d30202 } /* Generic.Error */
.gh { color: #2c2cff } /* Generic.Heading */
.gi { color: #2c2cff } /* Generic.Inserted */
.go { color: #2c2cff } /* Generic.Output */
.gp { color: #2c2cff } /* Generic.Prompt */
.gs { color: #2c2cff } /* Generic.Strong */
.gu { color: #2c2cff } /* Generic.Subheading */
.gt { color: #2c2cff } /* Generic.Traceback */
.kc { color: #2c2cff; font-weight: bold } /* Keyword.Constant */
.kd { color: #2c2cff } /* Keyword.Declaration */
.kn { color: #2c2cff } /* Keyword.Namespace */
.kp { color: #2c2cff } /* Keyword.Pseudo */
.kr { color: #353580; font-weight: bold } /* Keyword.Reserved */
.kt { color: #2c2cff } /* Keyword.Type */
.m { color: #2c8553; font-weight: bold } /* Literal.Number */
.s { color: #800080 } /* Literal.String */
.nb { color: #2c2cff } /* Name.Builtin */
.nf { font-weight: bold; font-style: italic } /* Name.Function */
.nv { color: #2c2cff; font-weight: bold } /* Name.Variable */
.w { color: #bbbbbb } /* Text.Whitespace */
.mb { color: #2c8553; font-weight: bold } /* Literal.Number.Bin */
.mf { color: #2c8553; font-weight: bold } /* Literal.Number.Float */
.mh { color: #2c8553; font-weight: bold } /* Literal.Number.Hex */
.mi { color: #2c8553; font-weight: bold } /* Literal.Number.Integer */
.mo { color: #2c8553; font-weight: bold } /* Literal.Number.Oct */
.sa { color: #800080 } /* Literal.String.Affix */
.sb { color: #800080 } /* Literal.String.Backtick */
.sc { color: #800080 } /* Literal.String.Char */
.dl { color: #800080 } /* Literal.String.Delimiter */
.sd { color: #800080 } /* Literal.String.Doc */
.s2 { color: #800080 } /* Literal.String.Double */
.se { color: #800080 } /* Literal.String.Escape */
.sh { color: #800080 } /* Literal.String.Heredoc */
.si { color: #800080 } /* Literal.String.Interpol */
.sx { color: #800080 } /* Literal.String.Other */
.sr { color: #800080 } /* Literal.String.Regex */
.s1 { color: #800080 } /* Literal.String.Single */
.ss { color: #800080 } /* Literal.String.Symbol */
.bp { color: #2c2cff } /* Name.Builtin.Pseudo */
.fm { font-weight: bold; font-style: italic } /* Name.Function.Magic */
.vc { color: #2c2cff; font-weight: bold } /* Name.Variable.Class */
.vg { color: #2c2cff; font-weight: bold } /* Name.Variable.Global */
.vi { color: #2c2cff; font-weight: bold } /* Name.Variable.Instance */
.vm { color: #2c2cff; font-weight: bold } /* Name.Variable.Magic */
.il { color: #2c8553; font-weight: bold } /* Literal.Number.Integer.Long */

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

@ -1,311 +1,30 @@
/* Default theme */
html, body {
margin: 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 {
max-width: min(650px, 100ex);
margin: 0 auto;
}
.node .node {
margin: 2em 0ex 2em 0.5ex;
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 {
display: none;
}
/* Item list */
.node .contents ul,
.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 .node.collapsed > .title::before {
content: "🮥";
}
.node .contents ul li,
.global-table-of-contents ul li{
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;
.node .node.expanded > .title::before {
content: "🮦";
}
/* Headers */
body > .node > h1 {
text-align: center;
}
.node .node h1 {
h1 {
font-size: 150%;
}
@ -328,438 +47,33 @@ h1.title .state {
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 */
li .tag {
font-weight: bold;
}
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 */
pre {
overflow: auto;
padding: 0.25ex;
box-shadow: 0px 1px 2px 1px rgba(0, 0, 0, 0.25);
border-radius: 2px;
padding: 0.5ex;
padding-left: 0.5ex;
padding-left: 1.5ex;
background-color: #eee8d5;
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.26);
}
pre > code {
display: block;
line-height: 1.2em;
overflow: auto;
}
pre code {
padding: 1ex;
code {
padding: 0.5ex;
font-size: medium;
background: #fff;
color: #000;
border: none;
font-size: 85%;
border-radius: 4px;
}
.highlight pre {
padding: 0.5ex;
}
code, .verbatim {
padding: 0.25ex 0.5ex;
margin: 0.25ex;
background: #eee;
color: #600;
font-family: Menlo, Monaco, "Courier New", monospace;
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 {
margin: 1ex;
}
article.post {
max-width: min(650px, 100ex);
margin: 0 auto;
}
/* Header */
.site-header {
background-color: #002b36;
border-bottom: rgba(0,0,0,0.1) 1px solid;
text-align: center;
padding: 1ex;
color: #fff;
}
.site-header h1 {
margin-top: 0;
font-size: 200%;
font-family: monospace, sans;
color: inherit;
}
.site-header .site-links a {
color: #00fdf2;
}
.site-header .site-links .fancy-link {
border-right: 1px solid #fff;
padding-left: 0.75ex;
}
.site-header .site-links .fancy-link:last-of-type {
border: none;
}
/* 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 {
list-style: none;
display: inline;
padding: 0;
}
.post-metadata ul.post-tags li.post-tag a::before {
content: '#';
}
.post-metadata ul.post-tags li.post-tag {
display: inline;
font-style: italic;
}
/* Post index. */
.post-index .post-container {
/* box-shadow: 0px 2px 4px 2px rgba(0, 0, 0, 0.26); */
/* border-radius: 2px; */
/* padding: 1ex; */
margin-bottom: 1em;
padding-bottom: 1em;
border-bottom: #000 2px 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. */
@media (prefers-color-scheme: dark) {
html, body {
background-color: #1d1f21;
color: #fafafe;
}
.node .node {
border-color: #8c9ea0;
}
.node .node > .title::before {
color: #aaa;
}
h2 a {
color: #fafafe;
}
a {
color: #00fdf2;
}
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 {
color: #f7da4a;
}
/* Header */
.site-header {
background-color: #303033;
border-bottom: rgba(0,0,0,0.1) 1px solid;
}
.site-header h1 {
color: #fff;
}
.site-header .site-links .fancy-link {
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 */
.highlight pre {
padding: 1ex;
background-color: #262628;
}
pre {
padding: 0.25ex;
background-color: inherit;
box-shadow: 0px 1px 2px 1px rgba(0, 0, 0, 0.0);
}
pre code {
padding: 1ex;
font-size: medium;
border: none;
background: #262628;
color: #fff;
}
code, .verbatim {
background: #262628;
color: #FFF;
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;
}
border: 2px solid #eee8d5;
background: #fdf6e3;
color: #073642;
}