Compare commits

...

159 Commits

Author SHA1 Message Date
Sergio Martínez Portela
2f3c52f5f2 Push notes section to the top. 2024-07-21 19:54:23 +02:00
Sergio Martínez Portela
d630fb0f70 Add Malleable Software slides to homepage. 2024-07-21 19:48:23 +02:00
Sergio Martínez Portela
ce35091852 Add configurable subpath. 2024-05-05 12:29:26 +02:00
Sergio Martínez Portela
d9b85c8475 Fix height of arrows on external links. 2024-03-12 01:40:26 +01:00
Sergio Martínez Portela
9a020285ad Add clean styling for SOMETIME state. 2024-03-10 17:51:58 +01:00
Sergio Martínez Portela
e639df35a7 s/Mastodon/ActivityPub/ 2023-11-12 15:34:44 +01:00
Sergio Martínez Portela
89e50a6310 Fix color scheme on dark mode. 2023-11-01 23:37:57 +01:00
Sergio Martínez Portela
28122c3c31 Use dark-syntax.css only when dark mode is selected. 2023-10-05 08:53:50 +02:00
Sergio Martínez Portela
b8eadc8b1e Update CSS links on blog. 2023-10-03 00:22:13 +02:00
Sergio Martínez Portela
23f8fcefe5 Improve link checking. 2023-10-03 00:09:30 +02:00
Sergio Martínez Portela
600e737767 Re-render archives on file changes. 2023-10-03 00:09:30 +02:00
Sergio Martínez Portela
c588187ae3 Fix directory creation on re-render. 2023-10-03 00:09:30 +02:00
Sergio Martínez Portela
bd644e3788 No need to re-link videos or hrefs. 2023-10-03 00:09:30 +02:00
Sergio Martínez Portela
650b16df32 Add no-watch build on blog. 2023-10-03 00:09:30 +02:00
Sergio Martínez Portela
abfd4b16c5 Fix blog linking. 2023-10-03 00:07:45 +02:00
Sergio Martínez Portela
302ec0b764 [autoserve] Return 404 when appropriate. 2023-09-27 23:35:20 +02:00
Sergio Martínez Portela
6d78ad53c6 Fix path decoding. 2023-09-27 23:30:26 +02:00
Sergio Martínez Portela
00cb3fa203 Redirect '/directory' to '/directory/'. 2023-09-27 23:09:51 +02:00
Sergio Martínez Portela
816cedea4d wip: Script to test links for broken ones. 2023-09-22 00:05:24 +02:00
Sergio Martínez Portela
5b0873b0bd Implement RSS generation. 2023-09-21 00:16:56 +02:00
Sergio Martínez Portela
fc1a94cfcf Additional logging on loaded and reloaded posts. 2023-09-19 23:42:24 +02:00
Sergio Martínez Portela
21a857ea41 Implement handling of one post in multiple languages. 2023-09-19 23:42:24 +02:00
Sergio Martínez Portela
12b5e31e49 Handle root index autoserving. 2023-09-19 23:40:12 +02:00
Sergio Martínez Portela
7cc2407846 Link: "A sloppy guide to GnuCash's Python bindings" as post. 2023-09-18 23:56:19 +02:00
Sergio Martínez Portela
43824cc013 WIP: Explore parameters for gen_centered_graph.py 2023-09-18 23:55:32 +02:00
Sergio Martínez Portela
0003c80ad8 Miscellaneous styling changes. 2023-09-18 23:55:08 +02:00
Sergio Martínez Portela
747fbf015e Fix codehilite CSS. 2023-09-18 23:54:41 +02:00
Sergio Martínez Portela
eb1c3084c8 Implement article listing. 2023-09-17 23:39:15 +02:00
Sergio Martínez Portela
1c0b69d693 Fix navbar links. 2023-09-17 23:30:43 +02:00
Sergio Martínez Portela
f25bfb1ef6 Add category pages. 2023-09-17 23:30:24 +02:00
Sergio Martínez Portela
7d1524e270 Add links to older/newer sections of archive. 2023-09-17 22:55:45 +02:00
Sergio Martínez Portela
f1781bb1ae Implement proper article summarization for index. 2023-09-17 22:55:45 +02:00
Sergio Martínez Portela
da6536ff53 Show date but not time for each posts. 2023-09-17 22:55:45 +02:00
Sergio Martínez Portela
6275fc3694 Fix linking to blog articles from index. 2023-09-17 22:55:45 +02:00
Sergio Martínez Portela
59c9dfcf6b Fix autoserving of directory indexes. 2023-09-16 23:21:55 +02:00
Sergio Martínez Portela
7e1b71cf78 Reference notes from Homepage. Hide projects. 2023-09-16 18:44:19 +02:00
Sergio Martínez Portela
18f33b29e2 Material-er theme on dark graph render. 2023-09-16 18:44:16 +02:00
Sergio Martínez Portela
f81673d76a Merge branch 'dev/centered-graph-drawing' into develop 2023-09-16 18:43:32 +02:00
Sergio Martínez Portela
661a5e0cf8 Add centered graph as first element. 2023-06-26 23:56:32 +02:00
Sergio Martínez Portela
9784f78f1c Deduplicate graph edges. 2023-06-20 00:15:41 +02:00
Sergio Martínez Portela
dee465f6af Remap document IDs. 2023-06-20 00:09:08 +02:00
Sergio Martínez Portela
d6c8b9f3db Fix links originating from top-level of graph center. 2023-06-19 00:07:40 +02:00
Sergio Martínez Portela
539240079f Fix rendering of outgoing links on graph.
See: http://127.0.0.1:8000/notes/343fe43b-f687-4f83-8171-c966a6887898.node.html#343fe43b-f687-4f83-8171-c966a6887898
2023-06-18 23:56:30 +02:00
Sergio Martínez Portela
da20c14ae7 Fix generation of graphs where top-level headline has no backlines. 2023-06-18 23:24:33 +02:00
Sergio Martínez Portela
960a3693d3 Fix typo on intermediate dot file name. 2023-06-13 00:00:35 +02:00
Sergio Martínez Portela
fa789984f4 Implement long-operation cache. 2023-06-10 15:55:43 +02:00
Sergio Martínez Portela
135423b8e5 Wrap graph generation in python code, remove API dependency. 2023-06-10 15:35:06 +02:00
Sergio Martínez Portela
3f5ec66c3d Fix: render graph.json, a dependency, before node-centered-graph. 2023-06-09 21:04:04 +02:00
Sergio Martínez Portela
49a5ec3df2 Experiment with centered graph drawing. 2023-06-09 20:54:01 +02:00
Sergio Martínez Portela
b248f507c6 Handle file: links. 2023-06-06 18:32:19 +02:00
Sergio Martínez Portela
0e56cfbe10 Show TOC on the left side. 2023-05-14 21:06:27 +02:00
Sergio Martínez Portela
ce2ca99ebc Don't move node off center if no TOC is shown. 2023-05-14 21:06:08 +02:00
Sergio Martínez Portela
210a508a90 Add simple color loop when loading query. 2023-05-01 22:46:46 +02:00
Sergio Martínez Portela
bdda2f3676 Replace header collapse/expand for link copying. 2023-04-28 00:12:02 +02:00
Sergio Martínez Portela
fce2d3c935 Use a bluer, less-greener background color. 2023-04-28 00:11:59 +02:00
Sergio Martínez Portela
5ae3be6d0f Draw floating table-of-contents on the right when possible. 2023-04-28 00:05:40 +02:00
Sergio Martínez Portela
61b1b79f95 Don't draw tables of contents with <2 elements. 2023-04-26 23:52:49 +02:00
Sergio Martínez Portela
57af01c5be Add symbol for external links. 2023-04-26 23:31:01 +02:00
Sergio Martínez Portela
d10bd44178 requirements: Add pygments requirement. 2023-04-23 20:53:02 +02:00
Sergio Martínez Portela
9a7358a98a Fix argument order on render_table. 2023-01-12 00:19:04 +01:00
Sergio Martínez Portela
a465b409b1 Print a warning and avoid crash on orgit-rev links. 2023-01-12 00:18:46 +01:00
Sergio Martínez Portela
b916be8f0b Copy mobile viewport tags from https://eev.ee 2023-01-06 17:09:10 +01:00
Sergio Martínez Portela
84b86e456b Limit img size to max-width: 100%. 2023-01-05 20:52:26 +01:00
Sergio Martínez Portela
6d7078ae01 Link to Mastodon profile from homepage. 2022-12-28 23:00:12 +01:00
Sergio Martínez Portela
90d1c15ba7 Add markup rendering and basic styling. 2022-12-28 22:42:26 +01:00
Sergio Martínez Portela
f174be032e Softer border on results box. 2022-12-20 00:59:00 +01:00
Sergio Martínez Portela
fcb44ca1a6 Apply "unindent" to highlighted code blocks too. 2022-12-20 00:56:53 +01:00
Sergio Martínez Portela
2d0f71bca1 Hide "ATTACH" tags. 2022-12-14 23:55:30 +01:00
Sergio Martínez Portela
d004454192 Add support for attachments and image links. 2022-12-14 23:46:41 +01:00
Sergio Martínez Portela
31450effe7 Add Makefile to concatenate syntax style. 2022-11-29 23:54:53 +01:00
Sergio Martínez Portela
e268734a3f Add base table-of-contents, don't collapse headlines. 2022-11-29 00:20:31 +01:00
Sergio Martínez Portela
58bb7ccc49 Improve handling of lists with multiline items. 2022-11-24 00:09:58 +01:00
Sergio Martínez Portela
88029a27d0 Convert the linked-from list into a tagged list. 2022-11-22 20:28:14 +01:00
Sergio Martínez Portela
86e36d5b79 Fix consistency between Webkit & Gecko.
Also, minor style fixes.
2022-11-21 00:10:47 +01:00
Sergio Martínez Portela
913e2c54d3 Start exploring CSS design for tree view.
Heavily based on https://iamkate.com/code/tree-views/ .
2022-11-20 21:41:07 +01:00
Sergio Martínez Portela
b9ac52a1a9 Support light&dark syntax on code blocks. 2022-11-15 21:20:25 +01:00
Sergio Martínez Portela
fe052d3468 Separate words on different line chunks. 2022-11-15 21:12:13 +01:00
Sergio Martínez Portela
058f0caf73 Treat DB cleanup as error when not in interactive mode. 2022-11-15 21:11:58 +01:00
Sergio Martínez Portela
9a883d90dd Apply Syntax Highlight on code blocks. 2022-11-15 21:11:36 +01:00
Sergio Martínez Portela
e7dc8ad1e7 Make indentation lines clearer on dark mode. 2022-11-11 18:45:28 +01:00
Sergio Martínez Portela
87e4a8aa7d Remap document ids backlinks to main headlines. 2022-11-09 19:54:39 +01:00
Sergio Martínez Portela
ac445d2e7c Use logging.warning instead of deprecated logging.warn. 2022-11-09 01:01:49 +01:00
Sergio Martínez Portela
4849128fcd Add simple backlink connections. 2022-11-09 01:01:34 +01:00
Sergio Martínez Portela
08d35fc0b5 Handle more softly words with :// that don't match known link types. 2022-11-04 00:34:32 +01:00
Sergio Martínez Portela
76531e3cfc Add result box rendering. 2022-11-04 00:34:32 +01:00
Sergio Martínez Portela
212c41d848 Avoid having Headlines look like links when clicked. 2022-11-04 00:34:32 +01:00
Sergio Martínez Portela
1f286a0a54 Add simple style for results box. 2022-11-04 00:34:32 +01:00
Sergio Martínez Portela
38e5f57eab Use node collapse markers with better font compatibility.
These are temporal. This way of working moves the titles around when they get
     collapsed/expanded, so I should probably change it at some point.
2022-11-04 00:34:32 +01:00
Sergio Martínez Portela
0ebda876f7 Update headline expansion based on document STARTUP config. 2022-11-01 18:42:59 +01:00
Sergio Martínez Portela
847e2cfd74 Don't crash when failing to update note_search DB. 2022-11-01 12:19:52 +01:00
Sergio Martínez Portela
613aa4c88f Make result-bot TODO tag more badge-like. 2022-11-01 12:19:34 +01:00
Sergio Martínez Portela
964e2501ee Mark focused links on click. 2022-11-01 12:19:20 +01:00
Sergio Martínez Portela
208a9b2e97 Show notes TO-DO on search, mark them as such. 2022-10-30 23:58:44 +01:00
Sergio Martínez Portela
b676e2f949 Do search on all indexed fields. 2022-10-30 23:44:31 +01:00
Sergio Martínez Portela
7d20941765 Use experimental trigram tokenizer in SQLite FTS. 2022-10-30 23:38:25 +01:00
Sergio Martínez Portela
486c88c583 Take full headline body for search function.
Using just `get_contents` doesn't get list items or tables.
2022-10-30 23:31:45 +01:00
Sergio Martínez Portela
c80ada2a40 Simple style changes around search box. 2022-10-26 23:17:36 +02:00
Sergio Martínez Portela
9053bf30f6 Fix premature return when TODOs found. 2022-10-26 22:45:50 +02:00
Sergio Martínez Portela
25a65253dd Show message when no results are found on search.
Also, lower debounce time to 250ms.
2022-10-26 22:42:24 +02:00
Sergio Martínez Portela
c8b3a99e7a Mark internal links on light style too. 2022-10-25 23:52:54 +02:00
Sergio Martínez Portela
d023955ee0 Skip links to private notes. 2022-10-25 23:45:30 +02:00
Sergio Martínez Portela
250bcde6d5 Set site language on homepage. 2022-10-25 23:36:10 +02:00
Sergio Martínez Portela
34d0d2ead3 Detect local links that result in a non-public note. 2022-10-25 23:35:46 +02:00
Sergio Martínez Portela
955dc433df Visually identify internal links. 2022-10-23 21:50:33 +02:00
Sergio Martínez Portela
2eab1b4351 Fix: don't hide text until custom font is loaded. 2022-10-23 21:40:10 +02:00
Sergio Martínez Portela
20b945aa31 Add usual task states to the default settings. 2022-10-23 21:30:44 +02:00
Sergio Martínez Portela
8d8dcbfdce Fix bare links next to [ or ] characters. 2022-10-23 21:30:30 +02:00
Sergio Martínez Portela
a6607ba0f3 WIP: Support bare links (no []). 2022-10-23 21:16:22 +02:00
Sergio Martínez Portela
3e9f323b56 Remove reference to git:// links. 2022-10-23 18:33:00 +02:00
Sergio Martínez Portela
4d3997bce1 Render * Target links. 2022-10-23 18:28:08 +02:00
Sergio Martínez Portela
ce8fd431b6 Allow rendering of links that require graph knowledge.
- Fix rendering of `./filename.org` links.
2022-10-23 18:22:43 +02:00
Sergio Martínez Portela
8dd624d339 Cleanup old notes on upload before building new ones. 2022-10-21 20:18:01 +02:00
Sergio Martínez Portela
a18c94dca0 Decide on SEARCH_ENDPOINT based on current URL. 2022-10-21 20:17:41 +02:00
Sergio Martínez Portela
f66d69776b Create directory before filling it in. 2022-10-21 00:23:50 +02:00
Sergio Martínez Portela
fcd9854a17 Disable collapsing top-level headline. 2022-10-20 23:44:35 +02:00
Sergio Martínez Portela
bcfd8b1d93 Reduce unused height on headlines. 2022-10-18 23:29:12 +02:00
Sergio Martínez Portela
de92f74867 Link to notes index from title. 2022-10-18 23:18:57 +02:00
Sergio Martínez Portela
d71c28d1e8 Show top-level title in seach, hide to-do's. 2022-10-18 23:13:21 +02:00
Sergio Martínez Portela
7bad44cfb6 Redirect from subnote to parent note. 2022-10-18 22:47:04 +02:00
Sergio Martínez Portela
a313597dcf Mark tags as [tag]. 2022-10-18 01:17:11 +02:00
Sergio Martínez Portela
df7f65fa00 Test different background color. 2022-10-18 01:16:30 +02:00
Sergio Martínez Portela
b214a8148a Prepare for note deployment. 2022-10-18 01:16:14 +02:00
Sergio Martínez Portela
7ddf926fa7 Lighten dark-mode links on homepage. 2022-10-17 00:10:40 +02:00
Sergio Martínez Portela
d81db05633 Show results on notes's search-box. 2022-10-17 00:10:06 +02:00
Sergio Martínez Portela
a616d903fb Implement basic popup creation for note search. 2022-10-04 23:55:57 +02:00
Sergio Martínez Portela
d7905f5b0a Add base for search UI. 2022-10-03 23:40:57 +02:00
Sergio Martínez Portela
7fdf378cee Add script to demo search-server's docker usage. 2022-10-03 00:52:11 +02:00
Sergio Martínez Portela
14d7d0f85e Initial search-server version. 2022-10-03 00:52:11 +02:00
Sergio Martínez Portela
bc3bf30669 Full-text-search with a Quick&Dirty SQLite DB. 2022-09-30 00:13:22 +02:00
Sergio Martínez Portela
8d136312b7 Add body max width. 2022-09-29 23:49:10 +02:00
Sergio Martínez Portela
2d6a994476 Add language as class on code block. 2022-09-29 23:48:56 +02:00
Sergio Martínez Portela
cc8d7d0bc5 Render note tables. 2022-09-28 00:04:06 +02:00
Sergio Martínez Portela
f6254a6c53 Add selectable port on autoserve script. 2022-09-26 23:44:22 +02:00
Sergio Martínez Portela
8ef3c5a636 Use Atkinson-Hyperlegible font. 2022-09-26 23:44:07 +02:00
Sergio Martínez Portela
9990c77964 Add new post to homepage. 2022-09-17 21:14:49 +02:00
Sergio Martínez Portela
e73a7b7062 Avoid line-breaks inside list items. 2022-09-02 00:33:42 +02:00
Sergio Martínez Portela
856ae09b16 feat: Show note tags. 2022-09-02 00:31:16 +02:00
Sergio Martínez Portela
2a90ea8a26 Test font-selection taken from Hackliza.org blog. 2022-08-30 00:44:33 +02:00
Sergio Martínez Portela
4a64a9c732 chore(org-rw): Update headline title handling. 2022-08-28 19:37:53 +02:00
Sergio Martínez Portela
b610d356e8 Render headlines expanded by default. 2022-08-28 14:10:08 +02:00
Sergio Martínez Portela
cf7cc38bd8 Render titles with markup. 2022-08-28 14:09:57 +02:00
Sergio Martínez Portela
89bf34ed46 Remove common indentation of code blocks. 2022-08-27 13:33:47 +02:00
Sergio Martínez Portela
74493fa79d Render tokens of item-list tags. 2022-08-27 13:32:40 +02:00
Sergio Martínez Portela
08a4dc295e [style] Color TODO states. 2022-08-23 21:30:46 +02:00
Sergio Martínez Portela
0bd897d62e [style] Center note top title. 2022-08-23 21:30:26 +02:00
Sergio Martínez Portela
2cbb4f60ad [style] Make note sections more integrated. 2022-08-23 21:30:04 +02:00
Sergio Martínez Portela
246d585213 Fix headline title selection when taking ID from document. 2022-08-23 00:05:03 +02:00
Sergio Martínez Portela
a4981632e5 Fix evaluation of git paths. 2022-08-20 19:17:08 +02:00
Sergio Martínez Portela
d40b49a027 Add title to rendered notes. 2022-08-20 18:21:26 +02:00
Sergio Martínez Portela
f1b84f5615 Add simple styling to root node. 2022-08-20 17:54:41 +02:00
Sergio Martínez Portela
2b18c6a9b3 Fix text paragraph separation. 2022-08-20 17:51:24 +02:00
Sergio Martínez Portela
18ceb6bca5 Make debug print_tree more comprehensive. 2022-08-20 17:51:24 +02:00
Sergio Martínez Portela
9231013ea9 Add simplistic reloading to notes rendering script. 2022-08-20 14:27:45 +02:00
Sergio Martínez Portela
399f00a54f Add index.html generation. 2022-08-20 13:59:15 +02:00
Sergio Martínez Portela
ef1d71dc2f Merge branch 'experiment/graph-explorer-generation' into develop 2022-08-19 19:39:42 +02:00
Sergio Martínez Portela
1280de0ff9 Merge branch 'experiment/blog-generation' into develop 2022-08-19 19:39:19 +02:00
Sergio Martínez Portela
768065165f Reduce link arrow sizes. 2022-08-19 19:37:53 +02:00
Sergio Martínez Portela
f5e8fb25f7 Fill page with chart. 2022-08-19 19:36:42 +02:00
Sergio Martínez Portela
cf524ecee4 Add styling to link titles. 2022-08-19 19:36:32 +02:00
47 changed files with 11228 additions and 291 deletions

1
.gitignore vendored
View File

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

7
Makefile Normal file
View 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 '}' >> $@

View File

@ -8,9 +8,11 @@ import os
import time
import select
import urllib.parse
import inotify.adapters
PORT = 8000
PORT = int(os.getenv('PORT', 8000))
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
@ -55,16 +57,34 @@ class Server(http.server.SimpleHTTPRequestHandler):
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(self.path.strip('/'), 'rb') as f:
with open(path.strip('/'), 'rb') as f:
# send the body of the response
self.wfile.write(f.read())
if not self.path.endswith('.html'):
if not path.endswith('.html'):
return
else:
# Append update waiter

View File

@ -12,6 +12,7 @@ MARKDOWN_EXTRA_FEATURES = [
'markdown.extensions.extra',
]
import copy
import json
import logging
import sys
@ -24,12 +25,16 @@ 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) '
@ -42,6 +47,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 = (
@ -55,11 +63,21 @@ JINJA_ENV = jinja2.Environment(
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()
@ -73,6 +91,7 @@ MONITORED_EVENT_TYPES = (
'IN_DELETE_SELF',
'IN_MOVE_SELF',
)
LANG_PRIORITY = ('en', 'es', 'gl')
def parse_nikola_date(match):
@ -107,6 +126,7 @@ def slugify(title):
slug = unidecode(title).lower()
slug = SLUG_REMOVE_RE.sub('', slug)
slug = SLUG_HYPHENATE_RE.sub('-', slug)
slug = slug.strip('-')
return slug.strip()
@ -151,6 +171,8 @@ def get_out_path(front_matter):
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
@ -159,6 +181,7 @@ def load_all(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]):
@ -170,9 +193,12 @@ def load_all(top_dir_relative):
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
@ -182,22 +208,121 @@ def load_doc(filepath):
return (doc, front_matter, out_path)
def render_article(doc, front_matter, f):
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):
return bs4(doc, features='lxml').text[:1000]
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):
docs = sorted(docs.values(), key=lambda x: x[1]['date'], reverse=True)
# 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)
for off in range(0, len(docs), BLOG_INDEX_PAGE_SIZE):
# 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 = [
@ -207,12 +332,22 @@ def render_index(docs, dest_top):
"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 page
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:
@ -222,6 +357,104 @@ def render_index(docs, dest_top):
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:
@ -232,9 +465,11 @@ def regen_all(source_top, dest_top, docs=None):
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)
with open(doc_full_path + '.html', 'wt') as f:
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)
render_article(doc, front_matter, f, out_path)
except:
logging.error(traceback.format_exc())
logging.error("Rendering failed 😿")
@ -260,6 +495,15 @@ def regen_all(source_top, dest_top, docs=None):
# 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
@ -272,6 +516,10 @@ def main(source_top, dest_top):
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
@ -314,6 +562,7 @@ def main(source_top, dest_top):
else:
try:
print("Reloading: {}".format(filepath))
(doc, front_matter, out_path) = load_doc(filepath)
except:
logging.error(traceback.format_exc())
@ -323,11 +572,13 @@ def main(source_top, dest_top):
t0 = time.time()
docs[filepath] = (doc, front_matter, out_path)
doc_full_path = os.path.join(dest_top, out_path)
os.makedirs(os.path.dirname(doc_full_path), exist_ok=True)
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 + '.html', 'wt') as f:
with open(doc_full_path + '/index.html', 'wt') as f:
try:
render_article(doc, front_matter, f)
render_article(doc, front_matter, f, out_path)
render_archive(docs, dest_top)
except:
logging.error(traceback.format_exc())
logging.error("Rendering failed 😿")

View 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()

View File

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

View File

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

15
scripts/search-server.sh Normal file
View 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
View File

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

View 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"]

View 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
)

View 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=

View 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(
&note_id,
&note_title,
&note_top_level_title,
&note_is_done,
&note_is_todo,
)
if err != nil {
log.Fatal(err)
c.JSON(500, gin.H{
"success": false,
"message": "Error reading note DB results",
})
return
}
item := make(map[string]string)
item["id"] = note_id
item["title"] = note_title
item["top_level_title"] = note_top_level_title
item["is_done"] = note_is_done
item["is_todo"] = note_is_todo
results = append(results, item)
}
c.JSON(200, gin.H{
"results": gin.H{
"notes": results,
},
})
})
r.Run(fmt.Sprintf(":%v", port))
}

59
scripts/test-links.py Normal file
View 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]))

View File

@ -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
View 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

View File

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

View 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>

View File

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

View File

@ -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>

View File

@ -1,3 +1,5 @@
/* 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; }
@ -80,3 +82,4 @@ span.linenos.special { color: #000000; background-color: #ffffc0; padding-left:
.vi { color: #f8f8f2 } /* Name.Variable.Instance */
.vm { color: #f8f8f2 } /* Name.Variable.Magic */
.il { color: #ae81ff } /* Literal.Number.Integer.Long */
}

File diff suppressed because it is too large Load Diff

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

View File

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

View File

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

22
static/rss.tmpl.xml Normal file
View 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
View 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()

View File

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