Compare commits
180 Commits
experiment
...
develop
Author | SHA1 | Date | |
---|---|---|---|
|
2f3c52f5f2 | ||
|
d630fb0f70 | ||
|
ce35091852 | ||
|
d9b85c8475 | ||
|
9a020285ad | ||
|
e639df35a7 | ||
|
89e50a6310 | ||
|
28122c3c31 | ||
|
b8eadc8b1e | ||
|
23f8fcefe5 | ||
|
600e737767 | ||
|
c588187ae3 | ||
|
bd644e3788 | ||
|
650b16df32 | ||
|
abfd4b16c5 | ||
|
302ec0b764 | ||
|
6d78ad53c6 | ||
|
00cb3fa203 | ||
|
816cedea4d | ||
|
5b0873b0bd | ||
|
fc1a94cfcf | ||
|
21a857ea41 | ||
|
12b5e31e49 | ||
|
7cc2407846 | ||
|
43824cc013 | ||
|
0003c80ad8 | ||
|
747fbf015e | ||
|
eb1c3084c8 | ||
|
1c0b69d693 | ||
|
f25bfb1ef6 | ||
|
7d1524e270 | ||
|
f1781bb1ae | ||
|
da6536ff53 | ||
|
6275fc3694 | ||
|
59c9dfcf6b | ||
|
7e1b71cf78 | ||
|
18f33b29e2 | ||
|
f81673d76a | ||
|
661a5e0cf8 | ||
|
9784f78f1c | ||
|
dee465f6af | ||
|
d6c8b9f3db | ||
|
539240079f | ||
|
da20c14ae7 | ||
|
960a3693d3 | ||
|
fa789984f4 | ||
|
135423b8e5 | ||
|
3f5ec66c3d | ||
|
49a5ec3df2 | ||
|
b248f507c6 | ||
|
0e56cfbe10 | ||
|
ce2ca99ebc | ||
|
210a508a90 | ||
|
bdda2f3676 | ||
|
fce2d3c935 | ||
|
5ae3be6d0f | ||
|
61b1b79f95 | ||
|
57af01c5be | ||
|
d10bd44178 | ||
|
9a7358a98a | ||
|
a465b409b1 | ||
|
b916be8f0b | ||
|
84b86e456b | ||
|
6d7078ae01 | ||
|
90d1c15ba7 | ||
|
f174be032e | ||
|
fcb44ca1a6 | ||
|
2d0f71bca1 | ||
|
d004454192 | ||
|
31450effe7 | ||
|
e268734a3f | ||
|
58bb7ccc49 | ||
|
88029a27d0 | ||
|
86e36d5b79 | ||
|
913e2c54d3 | ||
|
b9ac52a1a9 | ||
|
fe052d3468 | ||
|
058f0caf73 | ||
|
9a883d90dd | ||
|
e7dc8ad1e7 | ||
|
87e4a8aa7d | ||
|
ac445d2e7c | ||
|
4849128fcd | ||
|
08d35fc0b5 | ||
|
76531e3cfc | ||
|
212c41d848 | ||
|
1f286a0a54 | ||
|
38e5f57eab | ||
|
0ebda876f7 | ||
|
847e2cfd74 | ||
|
613aa4c88f | ||
|
964e2501ee | ||
|
208a9b2e97 | ||
|
b676e2f949 | ||
|
7d20941765 | ||
|
486c88c583 | ||
|
c80ada2a40 | ||
|
9053bf30f6 | ||
|
25a65253dd | ||
|
c8b3a99e7a | ||
|
d023955ee0 | ||
|
250bcde6d5 | ||
|
34d0d2ead3 | ||
|
955dc433df | ||
|
2eab1b4351 | ||
|
20b945aa31 | ||
|
8d8dcbfdce | ||
|
a6607ba0f3 | ||
|
3e9f323b56 | ||
|
4d3997bce1 | ||
|
ce8fd431b6 | ||
|
8dd624d339 | ||
|
a18c94dca0 | ||
|
f66d69776b | ||
|
fcd9854a17 | ||
|
bcfd8b1d93 | ||
|
de92f74867 | ||
|
d71c28d1e8 | ||
|
7bad44cfb6 | ||
|
a313597dcf | ||
|
df7f65fa00 | ||
|
b214a8148a | ||
|
7ddf926fa7 | ||
|
d81db05633 | ||
|
a616d903fb | ||
|
d7905f5b0a | ||
|
7fdf378cee | ||
|
14d7d0f85e | ||
|
bc3bf30669 | ||
|
8d136312b7 | ||
|
2d6a994476 | ||
|
cc8d7d0bc5 | ||
|
f6254a6c53 | ||
|
8ef3c5a636 | ||
|
9990c77964 | ||
|
e73a7b7062 | ||
|
856ae09b16 | ||
|
2a90ea8a26 | ||
|
4a64a9c732 | ||
|
b610d356e8 | ||
|
cf7cc38bd8 | ||
|
89bf34ed46 | ||
|
74493fa79d | ||
|
08a4dc295e | ||
|
0bd897d62e | ||
|
2cbb4f60ad | ||
|
246d585213 | ||
|
a4981632e5 | ||
|
d40b49a027 | ||
|
f1b84f5615 | ||
|
2b18c6a9b3 | ||
|
18ceb6bca5 | ||
|
9231013ea9 | ||
|
399f00a54f | ||
|
ef1d71dc2f | ||
|
1280de0ff9 | ||
|
768065165f | ||
|
f5e8fb25f7 | ||
|
cf524ecee4 | ||
|
5a9fbfb253 | ||
|
3bf1a86c31 | ||
|
f3096eb5d9 | ||
|
9483fc1ba4 | ||
|
189a94c930 | ||
|
feefd1f4d5 | ||
|
8d831af216 | ||
|
031305f5de | ||
|
ba9eba887b | ||
|
830c26e333 | ||
|
29a9d25381 | ||
|
31a303ab55 | ||
|
4ebbd47a61 | ||
|
ef6072fd27 | ||
|
612c38711d | ||
|
85e254d75c | ||
|
7afb6be68f | ||
|
a3ad99cb61 | ||
|
33f22f5f04 | ||
|
57ed8fa15c | ||
|
a8c4d6ef48 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
_gen
|
_gen
|
||||||
|
static/syntax.css
|
7
Makefile
Normal file
7
Makefile
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
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 '}' >> $@
|
142
scripts/autoserve.py
Normal file
142
scripts/autoserve.py
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
#!/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()
|
596
scripts/blog.py
Normal file
596
scripts/blog.py
Normal file
@ -0,0 +1,596 @@
|
|||||||
|
#!/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])
|
@ -1,40 +0,0 @@
|
|||||||
#!/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])
|
|
165
scripts/gen_centered_graph.py
Normal file
165
scripts/gen_centered_graph.py
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
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()
|
@ -1,14 +1,20 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import sqlite3
|
||||||
|
import time
|
||||||
import json
|
import json
|
||||||
import html
|
import html
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import uuid
|
import uuid
|
||||||
import xapian
|
|
||||||
import shutil
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import traceback
|
||||||
|
import re
|
||||||
|
from itertools import chain
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
import inotify.adapters
|
||||||
|
|
||||||
import org_rw
|
import org_rw
|
||||||
from org_rw import OrgTime, dom, Link
|
from org_rw import OrgTime, dom, Link
|
||||||
@ -16,12 +22,79 @@ from org_rw import dump as dump_org
|
|||||||
from org_rw import load as load_org
|
from org_rw import load as load_org
|
||||||
from org_rw import token_list_to_raw
|
from org_rw import token_list_to_raw
|
||||||
|
|
||||||
|
import pygments
|
||||||
|
import pygments.lexers
|
||||||
|
import pygments.formatters
|
||||||
|
import gen_centered_graph
|
||||||
|
|
||||||
|
# Set custom states
|
||||||
|
for state in ("NEXT", "MEETING", "Q", "PAUSED", "SOMETIME", "TRACK", "WAITING"):
|
||||||
|
org_rw.DEFAULT_TODO_KEYWORDS.append(state)
|
||||||
|
|
||||||
|
for state in ("DISCARDED", "VALIDATING"):
|
||||||
|
org_rw.DEFAULT_DONE_KEYWORDS.append(state)
|
||||||
|
|
||||||
EXTENSIONS = [
|
EXTENSIONS = [
|
||||||
".org",
|
".org",
|
||||||
".org.txt",
|
".org.txt",
|
||||||
]
|
]
|
||||||
|
IMG_EXTENSIONS = set([
|
||||||
|
"svg",
|
||||||
|
"png",
|
||||||
|
"jpg",
|
||||||
|
"jpeg",
|
||||||
|
"gif",
|
||||||
|
])
|
||||||
|
SKIPPED_TAGS = set(['attach'])
|
||||||
|
DEFAULT_SUBPATH = "public"
|
||||||
|
|
||||||
|
WATCH = True
|
||||||
|
if os.getenv('WATCH_AND_REBUILD', '1') == '0':
|
||||||
|
WATCH = False
|
||||||
|
|
||||||
MIN_HIDDEN_HEADLINE_LEVEL = 2
|
MIN_HIDDEN_HEADLINE_LEVEL = 2
|
||||||
|
INDEX_ID = os.getenv("INDEX_ID", "ea48ec1d-f9d4-4fb7-b39a-faa7b6e2ba95")
|
||||||
|
SITE_NAME = "Código para llevar"
|
||||||
|
|
||||||
|
MONITORED_EVENT_TYPES = (
|
||||||
|
'IN_CREATE',
|
||||||
|
# 'IN_MODIFY',
|
||||||
|
'IN_CLOSE_WRITE',
|
||||||
|
'IN_DELETE',
|
||||||
|
'IN_MOVED_FROM',
|
||||||
|
'IN_MOVED_TO',
|
||||||
|
'IN_DELETE_SELF',
|
||||||
|
'IN_MOVE_SELF',
|
||||||
|
)
|
||||||
|
|
||||||
|
TEXT_OR_LINK_RE = re.compile(r'([^\s\[\]]+|.)')
|
||||||
|
|
||||||
|
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
STATIC_PATH = os.path.join(ROOT_DIR, 'static')
|
||||||
|
|
||||||
|
class NonExistingLocalNoteError(AssertionError):
|
||||||
|
def __init__(self, note_id, src_headline):
|
||||||
|
AssertionError.__init__(self)
|
||||||
|
self.note_id = note_id
|
||||||
|
self.src_headline = src_headline
|
||||||
|
|
||||||
|
def get_message(self):
|
||||||
|
return ("Cannot follow link to '{}' on headline '{}' ({})"
|
||||||
|
.format(self.note_id,
|
||||||
|
self.src_headline.id,
|
||||||
|
self.src_headline.title.get_text().strip()))
|
||||||
|
|
||||||
|
def is_git_path(path):
|
||||||
|
return any([chunk == ".git" for chunk in path.split(os.sep)])
|
||||||
|
|
||||||
|
def create_db(path):
|
||||||
|
if os.path.exists(path):
|
||||||
|
os.unlink(path)
|
||||||
|
|
||||||
|
db = sqlite3.connect(path)
|
||||||
|
db.execute('CREATE VIRTUAL TABLE note_search USING fts5(note_id, title, body, top_level_title, is_done, is_todo, tokenize="trigram");')
|
||||||
|
return db
|
||||||
|
|
||||||
def load_all(top_dir_relative):
|
def load_all(top_dir_relative):
|
||||||
top = os.path.abspath(top_dir_relative)
|
top = os.path.abspath(top_dir_relative)
|
||||||
@ -48,26 +121,46 @@ def load_all(top_dir_relative):
|
|||||||
logging.info("Collected {} files".format(len(docs)))
|
logging.info("Collected {} files".format(len(docs)))
|
||||||
return docs
|
return docs
|
||||||
|
|
||||||
|
def regen_all(src_top, dest_top, subpath, *, docs=None, db=None):
|
||||||
def main(src_top, dest_top):
|
|
||||||
docs = load_all(src_top)
|
|
||||||
files_generated = 0
|
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
|
||||||
|
|
||||||
|
docs = load_all(src_top)
|
||||||
|
base_dirs = set()
|
||||||
doc_to_headline_remapping = {}
|
doc_to_headline_remapping = {}
|
||||||
|
|
||||||
os.makedirs(dest_top, exist_ok=True)
|
os.makedirs(dest_top, exist_ok=True)
|
||||||
graph = {}
|
|
||||||
|
## Build headline list
|
||||||
|
# This includes a virtual headline for ID-referenced documents.
|
||||||
|
all_headlines = []
|
||||||
|
main_headlines_by_path = {}
|
||||||
|
main_headline_to_docid = {}
|
||||||
for doc in docs:
|
for doc in docs:
|
||||||
relpath = os.path.relpath(doc.path, src_top)
|
relpath = os.path.relpath(doc.path, src_top)
|
||||||
changed = False
|
changed = False
|
||||||
headlines = list(doc.getAllHeadlines())
|
headlines = list(doc.getAllHeadlines())
|
||||||
related = None
|
related = None
|
||||||
|
if not relpath.startswith(subpath + "/"):
|
||||||
|
# print("Skip:", relpath)
|
||||||
|
continue
|
||||||
|
|
||||||
|
base_dirs.add(os.path.dirname(relpath))
|
||||||
i = len(headlines)
|
i = len(headlines)
|
||||||
while i > 0:
|
while i > 0:
|
||||||
i -= 1
|
i -= 1
|
||||||
headline = headlines[i]
|
headline = headlines[i]
|
||||||
if headline.title.strip().lower() == "related" and headline.depth == 1:
|
if headline.title.get_text().strip().lower() == "related" and headline.depth == 1:
|
||||||
if related is not None:
|
if related is not None:
|
||||||
print(
|
print(
|
||||||
"Found duplicated related: {} vs {}".format(
|
"Found duplicated related: {} vs {}".format(
|
||||||
@ -87,10 +180,7 @@ def main(src_top, dest_top):
|
|||||||
print("Updated", relpath)
|
print("Updated", relpath)
|
||||||
save_changes(doc)
|
save_changes(doc)
|
||||||
|
|
||||||
if not relpath.startswith("public/"):
|
all_headlines.extend(headlines)
|
||||||
# print("Skip:", relpath)
|
|
||||||
continue
|
|
||||||
|
|
||||||
main_headline = None
|
main_headline = None
|
||||||
topHeadlines = doc.getTopHeadlines()
|
topHeadlines = doc.getTopHeadlines()
|
||||||
|
|
||||||
@ -98,12 +188,10 @@ def main(src_top, dest_top):
|
|||||||
or (len(topHeadlines) == 2 and related is not None)):
|
or (len(topHeadlines) == 2 and related is not None)):
|
||||||
|
|
||||||
main_headline = [h for h in topHeadlines if h != related][0]
|
main_headline = [h for h in topHeadlines if h != related][0]
|
||||||
|
main_headlines_by_path[doc.path] = main_headline
|
||||||
if doc.id is not None:
|
if doc.id is not None:
|
||||||
endpath = os.path.join(dest_top, doc.id + ".node.html")
|
|
||||||
with open(endpath, "wt") as f:
|
|
||||||
doc_to_headline_remapping['id:' + doc.id] = 'id:' + main_headline.id
|
doc_to_headline_remapping['id:' + doc.id] = 'id:' + main_headline.id
|
||||||
|
main_headline_to_docid[main_headline.id] = doc.id
|
||||||
f.write(as_document(render(main_headline, doc, headlineLevel=0)))
|
|
||||||
files_generated += 1
|
files_generated += 1
|
||||||
elif doc.id is not None:
|
elif doc.id is not None:
|
||||||
logging.error("Cannot render document from id: {}. {} headlines {} related".format(
|
logging.error("Cannot render document from id: {}. {} headlines {} related".format(
|
||||||
@ -112,9 +200,10 @@ def main(src_top, dest_top):
|
|||||||
'with' if related is not None else 'without'
|
'with' if related is not None else 'without'
|
||||||
))
|
))
|
||||||
|
|
||||||
for headline in headlines:
|
# Build graph
|
||||||
endpath = os.path.join(dest_top, headline.id + ".node.html")
|
graph = {}
|
||||||
|
backlink_graph = {}
|
||||||
|
for headline in all_headlines:
|
||||||
links = []
|
links = []
|
||||||
headline_links = list(headline.get_links())
|
headline_links = list(headline.get_links())
|
||||||
if headline == main_headline and related is not None:
|
if headline == main_headline and related is not None:
|
||||||
@ -140,7 +229,7 @@ def main(src_top, dest_top):
|
|||||||
elif l.value.startswith('./'):
|
elif l.value.startswith('./'):
|
||||||
pass # TODO: Properly handle
|
pass # TODO: Properly handle
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError('On document {}, link to {}'.format(doc.path, l.value))
|
logging.warning('On document {}, unknown link to {}'.format(doc.path, l.value))
|
||||||
|
|
||||||
if headline.parent:
|
if headline.parent:
|
||||||
if isinstance(headline.parent, org_rw.Headline):
|
if isinstance(headline.parent, org_rw.Headline):
|
||||||
@ -148,15 +237,41 @@ def main(src_top, dest_top):
|
|||||||
"target": headline.parent.id,
|
"target": headline.parent.id,
|
||||||
"relation": "in"
|
"relation": "in"
|
||||||
})
|
})
|
||||||
|
for backlink in links:
|
||||||
|
if 'relation' in backlink and backlink['relation'] == 'in':
|
||||||
|
continue
|
||||||
|
|
||||||
|
target = backlink['target']
|
||||||
|
if target.startswith('id:'):
|
||||||
|
target = target[len('id:'):]
|
||||||
|
|
||||||
|
if target not in backlink_graph:
|
||||||
|
backlink_graph[target] = set()
|
||||||
|
|
||||||
|
backlink_graph[target].add(headline.id)
|
||||||
|
|
||||||
graph[headline.id] = {
|
graph[headline.id] = {
|
||||||
"title": headline.title.strip(),
|
"title": org_rw.org_rw.token_list_to_plaintext(headline.title.contents).strip(),
|
||||||
"links": links,
|
"links": links,
|
||||||
"depth": headline.depth,
|
"depth": headline.depth,
|
||||||
}
|
}
|
||||||
|
if headline.id in main_headline_to_docid:
|
||||||
|
graph[main_headline_to_docid[headline.id]] = graph[headline.id]
|
||||||
|
|
||||||
with open(endpath, "wt") as f:
|
topLevelHeadline = headline
|
||||||
f.write(as_document(render(headline, doc, headlineLevel=0)))
|
while isinstance(topLevelHeadline.parent, org_rw.Headline):
|
||||||
files_generated += 1
|
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,
|
||||||
|
))
|
||||||
|
|
||||||
# Update graph, replace document ids with headline ids
|
# Update graph, replace document ids with headline ids
|
||||||
for headline_data in graph.values():
|
for headline_data in graph.values():
|
||||||
@ -164,6 +279,17 @@ def main(src_top, dest_top):
|
|||||||
if link['target'] in doc_to_headline_remapping:
|
if link['target'] in doc_to_headline_remapping:
|
||||||
link['target'] = doc_to_headline_remapping[link['target']]
|
link['target'] = doc_to_headline_remapping[link['target']]
|
||||||
|
|
||||||
|
# Remap document ids backlinks to main headlines
|
||||||
|
for doc_id, main_headline_id in doc_to_headline_remapping.items():
|
||||||
|
if doc_id.startswith('id:'):
|
||||||
|
doc_id = doc_id[len('id:'):]
|
||||||
|
if main_headline_id.startswith('id:'):
|
||||||
|
main_headline_id = main_headline_id[len('id:'):]
|
||||||
|
for backlink in backlink_graph.get(doc_id, []):
|
||||||
|
if main_headline_id not in backlink_graph:
|
||||||
|
backlink_graph[main_headline_id] = set()
|
||||||
|
backlink_graph[main_headline_id].add(backlink)
|
||||||
|
|
||||||
# Output graph files
|
# Output graph files
|
||||||
graphpath = os.path.join(dest_top, "graph.json")
|
graphpath = os.path.join(dest_top, "graph.json")
|
||||||
graph_explorer_path = os.path.join(dest_top, "graph.html")
|
graph_explorer_path = os.path.join(dest_top, "graph.html")
|
||||||
@ -171,162 +297,444 @@ def main(src_top, dest_top):
|
|||||||
json.dump(obj=graph, fp=f, indent=2)
|
json.dump(obj=graph, fp=f, indent=2)
|
||||||
graph_explorer_path = os.path.join(dest_top, "graph.html")
|
graph_explorer_path = os.path.join(dest_top, "graph.html")
|
||||||
with open(graph_explorer_path, 'wt') as f:
|
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()
|
source = template.read()
|
||||||
f.write(source.replace('<!-- REPLACE_THIS_WITH_GRAPH -->',
|
f.write(source.replace('<!-- REPLACE_THIS_WITH_GRAPH -->',
|
||||||
json.dumps(graph)))
|
json.dumps(graph)))
|
||||||
|
|
||||||
logging.info("Generated {} files".format(files_generated))
|
logging.info("Generated {} files".format(files_generated))
|
||||||
|
|
||||||
# Generate index files
|
# Render docs after we've built the graph
|
||||||
t0 = datetime.utcnow()
|
# Render main headlines
|
||||||
logging.info("Generating text index...")
|
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)))
|
||||||
|
|
||||||
xapian_db = os.path.join(dest_top, "xapian")
|
# Render all headlines
|
||||||
if os.path.exists(xapian_db):
|
for headline in all_headlines:
|
||||||
shutil.rmtree(xapian_db)
|
endpath = os.path.join(dest_top, headline.id + ".node.html")
|
||||||
db = xapian.WritableDatabase(xapian_db, xapian.DB_CREATE)
|
|
||||||
|
|
||||||
indexer = xapian.TermGenerator()
|
# Render HTML
|
||||||
stemmer = xapian.Stem("english")
|
with open(endpath, "wt") as f:
|
||||||
indexer.set_stemmer(stemmer)
|
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 = {}
|
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
|
||||||
|
|
||||||
for doc in docs:
|
cur.close()
|
||||||
relpath = os.path.relpath(doc.path, src_top)
|
db.commit()
|
||||||
|
|
||||||
if not relpath.startswith("public/"):
|
logging.info("Copying attachments")
|
||||||
# print("Skip:", relpath)
|
attachments_dir = os.path.join(dest_top, 'attachments')
|
||||||
|
os.makedirs(attachments_dir, exist_ok=True)
|
||||||
|
for base in base_dirs:
|
||||||
|
data_dir = os.path.join(src_top, base, 'data')
|
||||||
|
logging.info("Copying attachments from: {}".format(data_dir))
|
||||||
|
if not os.path.exists(data_dir):
|
||||||
continue
|
continue
|
||||||
|
for subdir in os.listdir(data_dir):
|
||||||
changed = False
|
shutil.copytree(os.path.join(data_dir, subdir),
|
||||||
for hl in doc.getAllHeadlines():
|
os.path.join(attachments_dir, subdir),
|
||||||
xapian_doc = xapian.Document()
|
dirs_exist_ok=True)
|
||||||
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 print_tree(tree, indentation=0):
|
def main(src_top, dest_top, subpath):
|
||||||
|
notifier = inotify.adapters.InotifyTrees([src_top, STATIC_PATH])
|
||||||
|
|
||||||
|
## Initial load
|
||||||
|
t0 = time.time()
|
||||||
|
|
||||||
|
os.makedirs(dest_top, exist_ok=True)
|
||||||
|
db = create_db(os.path.join(dest_top, 'db.sqlite3'))
|
||||||
|
docs = regen_all(src_top, dest_top, subpath=subpath, db=db)
|
||||||
|
|
||||||
|
if not WATCH:
|
||||||
|
logging.info("Build completed in {:.2f}s".format(time.time() - t0))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
logging.info("Initial load completed in {:.2f}s".format(time.time() - t0))
|
||||||
|
## Updating
|
||||||
|
for event in notifier.event_gen(yield_nones=False):
|
||||||
|
(ev, types, directory, file) = event
|
||||||
|
if not any([type in MONITORED_EVENT_TYPES for type in types]):
|
||||||
|
continue
|
||||||
|
if is_git_path(directory):
|
||||||
|
continue
|
||||||
|
filepath = os.path.join(directory, file)
|
||||||
|
print("CHANGED: {}".format(filepath))
|
||||||
|
t0 = time.time()
|
||||||
|
try:
|
||||||
|
docs = regen_all(src_top, dest_top, subpath=subpath, docs=docs, db=db)
|
||||||
|
except:
|
||||||
|
logging.error(traceback.format_exc())
|
||||||
|
logging.error("Loading new templates failed 😿")
|
||||||
|
continue
|
||||||
|
logging.info("Updated all in {:.2f}s".format(time.time() - t0))
|
||||||
|
|
||||||
|
def get_headline_with_name(target_name, doc):
|
||||||
|
target_name = target_name.strip()
|
||||||
|
for headline in doc.getAllHeadlines():
|
||||||
|
if headline.title.get_text().strip() == target_name:
|
||||||
|
return headline
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def assert_id_exists(id, src_headline, graph):
|
||||||
|
if id not in graph["nodes"]:
|
||||||
|
raise NonExistingLocalNoteError(id, src_headline)
|
||||||
|
|
||||||
|
def print_tree(tree, indentation=0, headline=None):
|
||||||
|
# if headline and headline.id != INDEX_ID:
|
||||||
|
# return
|
||||||
return
|
return
|
||||||
for element in tree:
|
for element in tree:
|
||||||
print(" " * indentation + "- " + str(type(element)))
|
|
||||||
if "children" in dir(element):
|
if "children" in dir(element):
|
||||||
if len(element.children) > 0:
|
if len(element.children) > 0:
|
||||||
print_tree(element.children, indentation + 1)
|
print_element(element.children, indentation + 1, headline)
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
elif "content" in dir(element):
|
||||||
|
for content in element.content:
|
||||||
|
print_element(content, indentation + 1, headline)
|
||||||
|
|
||||||
def render_property_drawer(element, acc):
|
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):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def render_logbook_drawer(element, acc):
|
def render_logbook_drawer(element, acc, headline, graph):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def render_property_node(element, acc):
|
def render_property_node(element, acc, headline, graph):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def render_list_group(element, acc):
|
def render_list_group(element, acc, headline, graph):
|
||||||
acc.append("<ul>")
|
acc.append("<ul>")
|
||||||
render_tree(element.children, acc)
|
render_tree(element.children, acc, headline, graph)
|
||||||
acc.append("</ul>")
|
acc.append("</ul>")
|
||||||
|
|
||||||
|
def render_table(element, acc, headline, graph):
|
||||||
|
acc.append("<table>")
|
||||||
|
render_tree(element.children, acc, headline, graph)
|
||||||
|
acc.append("</table>")
|
||||||
|
|
||||||
def render_list_item(element, acc):
|
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):
|
||||||
acc.append("<li>")
|
acc.append("<li>")
|
||||||
if element.tag is not None:
|
if element.tag is not None:
|
||||||
acc.append("<span class='tag'>")
|
acc.append("<span class='tag'>")
|
||||||
acc.append(html.escape(element.tag))
|
render_text_tokens(element.tag, acc, headline, graph)
|
||||||
acc.append("</span>")
|
acc.append("</span>")
|
||||||
|
|
||||||
acc.append("<span class='item'>")
|
acc.append("<span class='item'>")
|
||||||
render_text_tokens(element.content, acc)
|
render_text_tokens(element.content, acc, headline, graph)
|
||||||
acc.append("</span></li>")
|
acc.append("</span></li>")
|
||||||
|
|
||||||
|
def render_block(content, acc, _class, is_code):
|
||||||
|
acc.append('<pre class="{}">'.format(_class))
|
||||||
|
if is_code:
|
||||||
|
acc.append('<code>')
|
||||||
|
|
||||||
def render_code_block(element, acc):
|
# Remove indentation common to all lines
|
||||||
acc.append('<pre><code>')
|
acc.append(unindent(content))
|
||||||
acc.append(html.escape(element.lines))
|
if is_code:
|
||||||
acc.append('</code></pre>')
|
acc.append('</code>')
|
||||||
|
acc.append('</pre>')
|
||||||
|
|
||||||
def render_results_block(element, acc):
|
def unindent(content):
|
||||||
# TODO:
|
base_indentation = min([
|
||||||
# acc.append('<pre class="results"><code>')
|
len(l) - len(l.lstrip(' '))
|
||||||
# render_tree(element.children, acc)
|
for l in content.split('\n')
|
||||||
# acc.append('</code></pre>')
|
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
|
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_text(element, acc):
|
def render_results_block(element, acc, headline, graph):
|
||||||
acc.append('<span class="text">')
|
items = [e.get_raw() for e in element.children]
|
||||||
render_text_tokens(element.content, acc)
|
content = '\n'.join(items)
|
||||||
acc.append('</span>')
|
if len(content.strip()) > 0:
|
||||||
|
render_block(content, acc, _class='results lang-text', is_code=False)
|
||||||
|
|
||||||
def render_text_tokens(tokens, acc):
|
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
|
||||||
for chunk in tokens:
|
for chunk in tokens:
|
||||||
if isinstance(chunk, str):
|
if isinstance(chunk, str):
|
||||||
acc.append('{}</span> '.format(chunk))
|
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)))
|
||||||
|
|
||||||
elif isinstance(chunk, Link):
|
elif isinstance(chunk, Link):
|
||||||
link_target = chunk.value
|
link_target = chunk.value
|
||||||
if link_target.startswith('id:'):
|
is_internal_link = True
|
||||||
link_target = './' + link_target[3:] + '.node.html'
|
|
||||||
description = chunk.description
|
description = chunk.description
|
||||||
if description is None:
|
if description is None:
|
||||||
description = chunk.value
|
description = chunk.value
|
||||||
|
|
||||||
acc.append('<a href="{}">{}</a>'.format(
|
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),
|
html.escape(link_target),
|
||||||
|
'internal' if is_internal_link else 'external',
|
||||||
|
html.escape(link_target),
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
acc.append('<a href="{}" class="{}" >{}</a>'.format(
|
||||||
|
html.escape(link_target),
|
||||||
|
'internal' if is_internal_link else 'external',
|
||||||
html.escape(description),
|
html.escape(description),
|
||||||
))
|
))
|
||||||
# else:
|
except NonExistingLocalNoteError as err:
|
||||||
# raise NotImplementedError('TextToken: {}'.format(chunk))
|
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>')
|
||||||
|
|
||||||
|
|
||||||
def render_tag(element, acc):
|
def render_tag(element, acc, headline, graph):
|
||||||
return {
|
return {
|
||||||
dom.PropertyDrawerNode: render_property_drawer,
|
dom.PropertyDrawerNode: render_property_drawer,
|
||||||
dom.LogbookDrawerNode: render_logbook_drawer,
|
dom.LogbookDrawerNode: render_logbook_drawer,
|
||||||
dom.PropertyNode: render_property_node,
|
dom.PropertyNode: render_property_node,
|
||||||
dom.ListGroupNode: render_list_group,
|
dom.ListGroupNode: render_list_group,
|
||||||
dom.ListItem: render_list_item,
|
dom.ListItem: render_list_item,
|
||||||
|
dom.TableNode: render_table,
|
||||||
|
dom.TableSeparatorRow: render_table_separator_row,
|
||||||
|
dom.TableRow: render_table_row,
|
||||||
dom.CodeBlock: render_code_block,
|
dom.CodeBlock: render_code_block,
|
||||||
dom.Text: render_text,
|
dom.Text: render_text,
|
||||||
dom.ResultsDrawerNode: render_results_block,
|
dom.ResultsDrawerNode: render_results_block,
|
||||||
}[type(element)](element, acc)
|
org_rw.Text: render_org_text,
|
||||||
|
}[type(element)](element, acc, headline, graph)
|
||||||
|
|
||||||
|
|
||||||
def render_tree(tree, acc):
|
def render_tree(tree, acc, headline, graph):
|
||||||
for element in tree:
|
for element in tree:
|
||||||
render_tag(element, acc)
|
render_tag(element, acc, headline, graph)
|
||||||
|
|
||||||
|
def render_inline(tree, f, headline, graph):
|
||||||
|
acc = []
|
||||||
|
f(tree, acc, headline, graph)
|
||||||
|
return ''.join(acc)
|
||||||
|
|
||||||
|
|
||||||
def render(headline, doc, headlineLevel):
|
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):
|
||||||
try:
|
try:
|
||||||
dom = headline.as_dom()
|
dom = headline.as_dom()
|
||||||
except:
|
except:
|
||||||
logging.error("Error generating DOM for {}".format(doc.path))
|
logging.error("Error generating DOM for {}".format(doc.path))
|
||||||
raise
|
raise
|
||||||
print_tree(dom)
|
print_tree(dom, indentation=2, headline=headline)
|
||||||
|
|
||||||
content = []
|
content = []
|
||||||
render_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)
|
||||||
|
|
||||||
for child in headline.children:
|
for child in headline.children:
|
||||||
content.append(render(child, doc, headlineLevel=headlineLevel+1))
|
content.append(render(child, doc, headlineLevel=headlineLevel+1, graph=graph,
|
||||||
|
doc_to_headline_remapping=doc_to_headline_remapping))
|
||||||
|
|
||||||
if headline.state is None:
|
if headline.state is None:
|
||||||
state = ""
|
state = ""
|
||||||
@ -338,17 +746,32 @@ def render(headline, doc, headlineLevel):
|
|||||||
else:
|
else:
|
||||||
todo_state = "done"
|
todo_state = "done"
|
||||||
|
|
||||||
display_state = 'collapsed'
|
tag_list = []
|
||||||
if headlineLevel < MIN_HIDDEN_HEADLINE_LEVEL:
|
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'
|
display_state = 'expanded'
|
||||||
|
# # Update display based on document STARTUP config
|
||||||
|
# visual_level = doc.get_keywords('STARTUP', 'showall')
|
||||||
|
# if visual_level.startswith('show') and visual_level.endswith('levels'):
|
||||||
|
# visual_level_num = int(visual_level[len('show'):-len('levels')]) - 1
|
||||||
|
# # Note that level is 0 indexed inside this loop
|
||||||
|
# if headlineLevel >= visual_level_num:
|
||||||
|
# display_state = 'collapsed'
|
||||||
|
|
||||||
|
title = render_inline(headline.title, render_tag, headline, graph)
|
||||||
|
|
||||||
|
if headlineLevel > 0:
|
||||||
|
title = f"<a href=\"#{html.escape(headline.id)}\">{title}</a>"
|
||||||
|
|
||||||
return f"""
|
return f"""
|
||||||
<div id="{html.escape(headline.id)}" class="node {todo_state} {display_state}">
|
<div id="{html.escape(headline.id)}" class="node {todo_state} {display_state}">
|
||||||
<h1 class="title">
|
<h1 class="title">
|
||||||
{state}
|
{state}
|
||||||
<a href=\"javascript:toggle_expand('{html.escape(headline.id)}')\">
|
{title}
|
||||||
{html.escape(headline.title)}
|
{tags}
|
||||||
</a>
|
|
||||||
</h1>
|
</h1>
|
||||||
<div class='contents'>
|
<div class='contents'>
|
||||||
{''.join(content)}
|
{''.join(content)}
|
||||||
@ -357,27 +780,40 @@ def render(headline, doc, headlineLevel):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def as_document(html):
|
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>
|
||||||
|
"""
|
||||||
return f"""<!DOCTYPE html>
|
return f"""<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>{title} @ {SITE_NAME}</title>
|
||||||
<link href="../static/style.css" rel="stylesheet"/>
|
<link href="../static/style.css" rel="stylesheet"/>
|
||||||
<script type="text/javascript">
|
<link href="../static/syntax.css" rel="stylesheet"/>
|
||||||
function toggle_expand(header_id) {{
|
<!-- v Fixes mobile viewports. -->
|
||||||
var e = document.getElementById(header_id);
|
<meta name="HandheldFriendly" content="True">
|
||||||
if (e.classList.contains('expanded')) {{
|
<meta name="MobileOptimized" content="320">
|
||||||
e.classList.add('collapsed');
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
e.classList.remove('expanded');
|
|
||||||
}}
|
|
||||||
else {{
|
|
||||||
e.classList.add('expanded');
|
|
||||||
e.classList.remove('collapsed');
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<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}
|
||||||
{html}
|
{html}
|
||||||
|
|
||||||
|
<script src="../static/search-box.js"></script>
|
||||||
|
<script tye="text/javascript">_codigoparallevar_enable_search_box('#searchbox', {{placeholder: 'Search...'}})</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
@ -390,9 +826,13 @@ def save_changes(doc):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
if len(sys.argv) != 3:
|
if len(sys.argv) not in (3, 4):
|
||||||
print("Usage: {} SOURCE_TOP DEST_TOP".format(sys.argv[0]))
|
print("Usage: {} SOURCE_TOP DEST_TOP <SUBPATH>".format(sys.argv[0]))
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO, format="%(levelname)-8s %(message)s")
|
logging.basicConfig(level=logging.INFO, format="%(levelname)-8s %(message)s")
|
||||||
main(sys.argv[1], sys.argv[2])
|
subpath = DEFAULT_SUBPATH
|
||||||
|
|
||||||
|
if len(sys.argv) == 4:
|
||||||
|
subpath = sys.argv[3]
|
||||||
|
exit(main(sys.argv[1], sys.argv[2], subpath=subpath))
|
||||||
|
75
scripts/ops_cache.py
Normal file
75
scripts/ops_cache.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
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
|
3
scripts/requirements.txt
Normal file
3
scripts/requirements.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Markdown
|
||||||
|
Jinja2
|
||||||
|
pygments
|
15
scripts/search-server.sh
Normal file
15
scripts/search-server.sh
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#!/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
|
1
scripts/search-server/.gitignore
vendored
Normal file
1
scripts/search-server/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
search-server
|
47
scripts/search-server/Dockerfile
Normal file
47
scripts/search-server/Dockerfile
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
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"]
|
66
scripts/search-server/go.mod
Normal file
66
scripts/search-server/go.mod
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
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
|
||||||
|
)
|
295
scripts/search-server/go.sum
Normal file
295
scripts/search-server/go.sum
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
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=
|
130
scripts/search-server/server.go
Normal file
130
scripts/search-server/server.go
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
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(
|
||||||
|
¬e_id,
|
||||||
|
¬e_title,
|
||||||
|
¬e_top_level_title,
|
||||||
|
¬e_is_done,
|
||||||
|
¬e_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))
|
||||||
|
}
|
59
scripts/test-links.py
Normal file
59
scripts/test-links.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
#!/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]))
|
@ -1,8 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
|
|
||||||
cd static
|
|
||||||
scp homepage.html root@codigoparallevar.com:/mnt/vols/misc/codigoparallevar/index.html
|
|
30
scripts/upload.sh
Normal file
30
scripts/upload.sh
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#!/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
|
19
scripts/wait_for_update.js
Normal file
19
scripts/wait_for_update.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
(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();
|
||||||
|
})();
|
51
static/article.tmpl.html
Normal file
51
static/article.tmpl.html
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<!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>
|
54
static/article_list.tmpl.html
Normal file
54
static/article_list.tmpl.html
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<!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>
|
6
static/blog.css
Normal file
6
static/blog.css
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
body {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: fit-content;
|
||||||
|
max-width: 100ex;
|
||||||
|
padding: 0 1ex;
|
||||||
|
}
|
68
static/blog_index.tmpl.html
Normal file
68
static/blog_index.tmpl.html
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<!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>
|
54
static/category_list.tmpl.html
Normal file
54
static/category_list.tmpl.html
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<!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>
|
85
static/dark-syntax.css
Normal file
85
static/dark-syntax.css
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/* 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 */
|
||||||
|
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 169 KiB |
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 172 KiB |
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 138 KiB |
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 136 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -7,6 +7,12 @@
|
|||||||
<script type='text/javascript'>
|
<script type='text/javascript'>
|
||||||
var NODE_GRAPH=<!-- REPLACE_THIS_WITH_GRAPH -->;
|
var NODE_GRAPH=<!-- REPLACE_THIS_WITH_GRAPH -->;
|
||||||
</script>
|
</script>
|
||||||
|
<style>
|
||||||
|
text {
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<script>
|
<script>
|
||||||
// Copyright 2021 Observable, Inc.
|
// Copyright 2021 Observable, Inc.
|
||||||
// Released under the ISC license.
|
// Released under the ISC license.
|
||||||
@ -80,15 +86,15 @@ function ForceGraph({
|
|||||||
// Add arrowheads
|
// Add arrowheads
|
||||||
svg.append('defs').append('marker')
|
svg.append('defs').append('marker')
|
||||||
.attr('id', 'arrowhead')
|
.attr('id', 'arrowhead')
|
||||||
.attr('viewBox', '-0 -5 10 10')
|
.attr('viewBox', '-0 -2.5 5 5')
|
||||||
.attr('refX', 13)
|
.attr('refX', 10)
|
||||||
.attr('refY', 0)
|
.attr('refY', 0)
|
||||||
.attr('orient', 'auto')
|
.attr('orient', 'auto')
|
||||||
.attr('markerWidth', 13)
|
.attr('markerWidth', 7)
|
||||||
.attr('markerHeight', 13)
|
.attr('markerHeight', 7)
|
||||||
.attr('xoverflow', 'visible')
|
.attr('xoverflow', 'visible')
|
||||||
.append('svg:path')
|
.append('svg:path')
|
||||||
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
|
.attr('d', 'M 0,-2.5 L 5 ,0 L 0,2.5')
|
||||||
.attr('fill', '#999')
|
.attr('fill', '#999')
|
||||||
.style('stroke','none');
|
.style('stroke','none');
|
||||||
|
|
||||||
@ -263,6 +269,8 @@ function ForceGraph({
|
|||||||
linkLabel: (d) => { const e = edges[d.index]; if (e.relation) { return e.relation; } else { return ''; } },
|
linkLabel: (d) => { const e = edges[d.index]; if (e.relation) { return e.relation; } else { return ''; } },
|
||||||
});
|
});
|
||||||
holder.appendChild(chart);
|
holder.appendChild(chart);
|
||||||
|
chart.height = '100vh';
|
||||||
|
chart.width = '100vw';
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,15 +1,21 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Código para llevar</title>
|
<title>Código para llevar</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
|
background-color: white;
|
||||||
|
font-family: sans-serif;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
max-width: 100ex;
|
max-width: 100ex;
|
||||||
padding: 0 1ex;
|
padding: 0 1ex;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
.header h1 {
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
.links section {
|
.links section {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
@ -26,8 +32,9 @@
|
|||||||
.links p {
|
.links p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
h2 a {
|
h2 a, a h2 {
|
||||||
color: black;
|
color: black;
|
||||||
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
color: #00e;
|
color: #00e;
|
||||||
@ -40,21 +47,21 @@
|
|||||||
border-right: 1px solid #000;
|
border-right: 1px solid #000;
|
||||||
}
|
}
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
html {
|
body {
|
||||||
background-color: #0f110e;
|
background-color: #1d1f21;
|
||||||
color: #fafafe;
|
color: #fafafe;
|
||||||
}
|
}
|
||||||
h2 a {
|
h2 a, a h2 {
|
||||||
color: #fafafe;
|
color: #fafafe;
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
color: #66f;
|
color: #00fdf2;
|
||||||
}
|
}
|
||||||
#social a {
|
#social a {
|
||||||
border-color: #fff;
|
border-color: #fff;
|
||||||
}
|
}
|
||||||
.links section {
|
.links section {
|
||||||
background-color: #262826;
|
background-color: #262628;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -67,11 +74,19 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="links">
|
<div class="links">
|
||||||
|
<a href="/notes">
|
||||||
|
<section>
|
||||||
|
<h2>Notes</h2>
|
||||||
|
<p>Some publicly-visible notes from a sort of knowledge graph that I use as information dump.</p>
|
||||||
|
</section>
|
||||||
|
</a>
|
||||||
<section>
|
<section>
|
||||||
<h2><a href="/blog">Blog</a></h2>
|
<h2><a href="/blog">Blog</a></h2>
|
||||||
<p>
|
<p>
|
||||||
Latest posts:
|
Latest posts:
|
||||||
<ul>
|
<ul>
|
||||||
|
<li><a href="https://codigoparallevar.com/blog/2023/programmatic-access-to-gnucash-using-python/">[2023] A sloppy guide to GnuCash's Python bindings</a></li>
|
||||||
|
<li><a href="https://codigoparallevar.com/blog/2022/detecting-non-halting-programs/">[2022] Detecting non halting programs</a></li>
|
||||||
<li><a href="https://codigoparallevar.com/blog/2022/get-process-progress-reading-file/">[2022] Get process's progress when reading a file</a></li>
|
<li><a href="https://codigoparallevar.com/blog/2022/get-process-progress-reading-file/">[2022] Get process's progress when reading a file</a></li>
|
||||||
<li><a href="https://codigoparallevar.com/blog/2022/a-simple-status-indicator/">[2022] A simple status indicator</a></li>
|
<li><a href="https://codigoparallevar.com/blog/2022/a-simple-status-indicator/">[2022] A simple status indicator</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -80,19 +95,48 @@
|
|||||||
<section>
|
<section>
|
||||||
<h2>Collaborations</h2>
|
<h2>Collaborations</h2>
|
||||||
<p>
|
<p>
|
||||||
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>
|
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>
|
<a href="https://hackliza.gal/posts/contas_rapidas_no_terminal/">(galician)</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<h2>Projects</h2>
|
<h2>Talks / Slides</h2>
|
||||||
<p>
|
<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>.
|
<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>
|
</p>
|
||||||
</section>
|
</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>.
|
||||||
|
</p>
|
||||||
|
</section -->
|
||||||
<section id="social">
|
<section id="social">
|
||||||
<h2>Find me</h2>
|
<h2>Find me</h2>
|
||||||
<p>
|
<p>
|
||||||
|
<a href="https://social.codigoparallevar.com/@kenkeiras">ActivityPub</a>
|
||||||
<a href="https://github.com/kenkeiras">GitHub</a>
|
<a href="https://github.com/kenkeiras">GitHub</a>
|
||||||
<a href="https://gitlab.com/kenkeiras">GitLab</a>
|
<a href="https://gitlab.com/kenkeiras">GitLab</a>
|
||||||
<a href="https://programaker.com/users/kenkeiras">PrograMaker</a>
|
<a href="https://programaker.com/users/kenkeiras">PrograMaker</a>
|
||||||
|
64
static/light-syntax.css
Normal file
64
static/light-syntax.css
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
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 */
|
22
static/rss.tmpl.xml
Normal file
22
static/rss.tmpl.xml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?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>
|
129
static/search-box.js
Normal file
129
static/search-box.js
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
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()
|
724
static/style.css
724
static/style.css
@ -1,30 +1,311 @@
|
|||||||
|
/* 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 styling */
|
||||||
.node {
|
.node {
|
||||||
max-width: min(650px, 100ex);
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.node .node {
|
.node .node {
|
||||||
margin: 2em 0ex 2em 0.5ex;
|
|
||||||
padding: 1ex 0 1ex 1ex;
|
padding: 1ex 0 1ex 1ex;
|
||||||
box-shadow: 0px 1px 3px 0 rgba(0,0,0,0.3);
|
border-left: 1px dashed #2c3e50;
|
||||||
border-left: 2px solid #2c3e50;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.node.collapsed > .contents {
|
.node.collapsed > .contents {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.node .node.collapsed > .title::before {
|
/* Item list */
|
||||||
content: "🮥";
|
.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.expanded > .title::before {
|
.node .contents ul li,
|
||||||
content: "🮦";
|
.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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Headers */
|
/* Headers */
|
||||||
h1 {
|
body > .node > h1 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node .node h1 {
|
||||||
font-size: 150%;
|
font-size: 150%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,6 +328,36 @@ h1.title .state {
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h1.title .state.todo-True {
|
||||||
|
background-color: rgba(255,0,0,0.5);
|
||||||
|
}
|
||||||
|
h1.title .state.todo-False {
|
||||||
|
background-color: rgba(0,255,0,0.25);
|
||||||
|
}
|
||||||
|
h1.title .state.todo-True.state-SOMETIME {
|
||||||
|
background-color: #ddd;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
h1.title .tags {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
h1.title .tags .tag {
|
||||||
|
font-size: 50%;
|
||||||
|
vertical-align: middle;
|
||||||
|
/* background-color: rgba(255,255,255,0.3); */
|
||||||
|
background-color: rgba(0,0,0,0.1);
|
||||||
|
padding: 4px;
|
||||||
|
margin-left: 2px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
h1.title .tags .tag:before {
|
||||||
|
content: '[';
|
||||||
|
}
|
||||||
|
h1.title .tags .tag:after {
|
||||||
|
content: ']';
|
||||||
|
}
|
||||||
/* Lists */
|
/* Lists */
|
||||||
li .tag {
|
li .tag {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@ -56,24 +367,399 @@ li .tag::after {
|
|||||||
content: " :: ";
|
content: " :: ";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.internal::before {
|
||||||
|
content: '{ ';
|
||||||
|
}
|
||||||
|
a.internal::after {
|
||||||
|
content: ' }';
|
||||||
|
}
|
||||||
|
a.external::after {
|
||||||
|
content: ' ↗';
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Markup */
|
||||||
|
.underlined {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Codehilite fix */
|
||||||
|
.codehilitetable, .codehilitetable tr, .codehilitetable td {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.codehilitetable .linenodiv pre {
|
||||||
|
margin: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
line-height: 1.2em;
|
||||||
|
font-family: Menlo, Monaco, "Courier New", monospace;
|
||||||
|
font-size: medium;
|
||||||
|
}
|
||||||
|
.codehilitetable .code code {
|
||||||
|
font-size: medium;
|
||||||
|
}
|
||||||
/* Code blocks */
|
/* Code blocks */
|
||||||
pre {
|
pre {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 0.5ex;
|
padding: 0.25ex;
|
||||||
padding-left: 0.5ex;
|
box-shadow: 0px 1px 2px 1px rgba(0, 0, 0, 0.25);
|
||||||
padding-left: 1.5ex;
|
border-radius: 2px;
|
||||||
background-color: #eee8d5;
|
|
||||||
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.26);
|
|
||||||
}
|
}
|
||||||
pre > code {
|
pre > code {
|
||||||
display: block;
|
display: block;
|
||||||
line-height: 1.2em;
|
line-height: 1.2em;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
code {
|
pre code {
|
||||||
padding: 0.5ex;
|
padding: 1ex;
|
||||||
font-size: medium;
|
font-size: medium;
|
||||||
border: 2px solid #eee8d5;
|
background: #fff;
|
||||||
background: #fdf6e3;
|
color: #000;
|
||||||
color: #073642;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user