diff --git a/README.md b/README.md new file mode 100644 index 0000000..ea46267 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# Codigo para llevar's generator + +This is the static site generator used to build [Codigo Para Llevar](https://codigoparallevar.com/) (my personal site). It contains: + +- A markdown blog (with content ported from acrylamid). Saved as `/blog/`. +- A set of org-mode based notes. Saved as `/notes/`. + +It also copies over some static assets (css, js, fonts). + +The scripts are hardcoded with the hostnames and paths for my own site, so you might want to update them. + +General documentation is in progress and might be replaced little by little by the more interactive [org-web-editor](https://code.codigoparallevar.com/kenkeiras/org-web-editor) once that one (1) supports all the features here and (2) has support for building static sites. + +## Instructions + +Generally, what you want to do is to run `make` once to prepare the static files, then run this to generate the notes. + +```bash +mkdir -p _gen +WATCH_AND_REBUILD=0 python3 scripts/generate.py _gen/notes [] +``` + +Use `WATCH_AND_REBUILD=1` (or empty) for automatic rebuilds. + +## Filtering + +This won't render **all** notes, but try to select the PUBLIC ones and skip the PRIVATE ones. + +PUBLIC files are contained on the DEFAULT_SUBPATH, PRIVATE headlines have the `:private:` tag. diff --git a/scripts/generate.py b/scripts/generate.py index cc18e2a..12bac97 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -46,13 +46,14 @@ IMG_EXTENSIONS = set([ "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 = "ea48ec1d-f9d4-4fb7-b39a-faa7b6e2ba95" +INDEX_ID = os.getenv("INDEX_ID", "ea48ec1d-f9d4-4fb7-b39a-faa7b6e2ba95") SITE_NAME = "Código para llevar" MONITORED_EVENT_TYPES = ( @@ -108,7 +109,9 @@ def load_all(top_dir_relative): path = os.path.join(root, name) try: - doc = load_org(open(path), extra_cautious=True) + doc = load_org(open(path), + environment={"org-todo-keywords": "TODO(t) NEXT(n) MEETING(m/!) Q(q) PAUSED(p!/!) EVENT(e/!) SOMETIME(s) WAITING(w@/!) TRACK(r/!) | DISCARDED(x@/!) VALIDATING(v!/!) DONE(d!/!)"}, + extra_cautious=True) docs.append(doc) except Exception as err: import traceback @@ -120,7 +123,22 @@ def load_all(top_dir_relative): logging.info("Collected {} files".format(len(docs))) return docs -def regen_all(src_top, dest_top, *, docs=None, db=None): +def remove_non_public_headlines(doc: org_rw.OrgDoc | org_rw.Headline): + if isinstance(doc, org_rw.OrgDoc): + doc.headlines = list(filter_private_headlines(doc.headlines)) + for hl in doc.headlines: + remove_non_public_headlines(hl) + else: + doc.children = list(filter_private_headlines(doc.children)) + for hl in doc.children: + remove_non_public_headlines(hl) + +def filter_private_headlines(headlines): + for hl in headlines: + if 'private' not in hl.tags: + yield hl + +def regen_all(src_top, dest_top, subpath, *, docs=None, db=None): files_generated = 0 cur = db.cursor() cleaned_db = False @@ -147,10 +165,12 @@ def regen_all(src_top, dest_top, *, docs=None, db=None): main_headline_to_docid = {} for doc in docs: relpath = os.path.relpath(doc.path, src_top) + + remove_non_public_headlines(doc) changed = False headlines = list(doc.getAllHeadlines()) related = None - if not relpath.startswith("public/"): + if not relpath.startswith(subpath + "/"): # print("Skip:", relpath) continue @@ -349,7 +369,7 @@ def regen_all(src_top, dest_top, *, docs=None, db=None): dirs_exist_ok=True) -def main(src_top, dest_top): +def main(src_top, dest_top, subpath): notifier = inotify.adapters.InotifyTrees([src_top, STATIC_PATH]) ## Initial load @@ -357,7 +377,7 @@ def main(src_top, dest_top): os.makedirs(dest_top, exist_ok=True) db = create_db(os.path.join(dest_top, 'db.sqlite3')) - docs = regen_all(src_top, dest_top, db=db) + 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)) @@ -375,7 +395,7 @@ def main(src_top, dest_top): print("CHANGED: {}".format(filepath)) t0 = time.time() try: - docs = regen_all(src_top, dest_top, docs=docs, db=db) + 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 😿") @@ -477,7 +497,7 @@ def unindent(content): len(l) - len(l.lstrip(' ')) for l in content.split('\n') if len(l.strip()) > 0 - ]) + ] or [0]) content_lines = [ l[base_indentation:] for l in content.split('\n') @@ -510,6 +530,12 @@ def render_results_block(element, acc, headline, graph): if len(content.strip()) > 0: render_block(content, acc, _class='results lang-text', is_code=False) +def render_generic_drawer_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='generic-drawer {}-drawer lang-text'.format(element.drawer_name), is_code=False) + 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) @@ -642,6 +668,7 @@ def render_tag(element, acc, headline, graph): dom.CodeBlock: render_code_block, dom.Text: render_text, dom.ResultsDrawerNode: render_results_block, + dom.GenericDrawerNode: render_generic_drawer_block, org_rw.Text: render_org_text, }[type(element)](element, acc, headline, graph) @@ -714,8 +741,11 @@ def render_connections(headline_id, content, graph, doc_to_headline_remapping): # 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("
{}
".format(svg)) + try: + svg = gen_centered_graph.gen(headline_id, graph['nodes'], doc_to_headline_remapping) + content.append("
{}
".format(svg)) + except: + logging.warning("Broken reference on headline ID={}".format(headline_id)) def render(headline, doc, graph, headlineLevel, doc_to_headline_remapping): try: @@ -735,10 +765,10 @@ def render(headline, doc, graph, headlineLevel, doc_to_headline_remapping): content.append(render(child, doc, headlineLevel=headlineLevel+1, graph=graph, doc_to_headline_remapping=doc_to_headline_remapping)) - if headline.state is None: + if headline.state is None or headline.state.get('name') is None: state = "" else: - state = f'{headline.state}' + state = f'{headline.state["name"]}' if headline.is_todo: todo_state = "todo" @@ -825,9 +855,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 ".format(sys.argv[0])) exit(0) logging.basicConfig(level=logging.INFO, format="%(levelname)-8s %(message)s") - exit(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)) diff --git a/scripts/search-server.sh b/scripts/search-server.sh index 49f0250..62abda0 100644 --- a/scripts/search-server.sh +++ b/scripts/search-server.sh @@ -12,4 +12,4 @@ 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 +exec docker run -it --rm -p $PORT:80 -e SNIPPET_SIZE=256 -e PORT=80 -e DB_PATH=/db.sqlite3 -v `pwd`/db.sqlite3:/db.sqlite3:ro search-server diff --git a/scripts/search-server/server.go b/scripts/search-server/server.go index 8914956..be6ab0d 100644 --- a/scripts/search-server/server.go +++ b/scripts/search-server/server.go @@ -33,6 +33,22 @@ func main() { port = port_num } + snippet_size := 128 + snippet_size_str, ok := os.LookupEnv("SNIPPET_SIZE") + if ok { + snippet_size_num, err := strconv.Atoi(snippet_size_str) + + if err != nil { + log.Fatal(err) + os.Exit(1) + } + if (snippet_size_num < 64) { + log.Fatal("Environment variale $SNIPPET_SIZE must be >= 64.") + os.Exit(1) + } + snippet_size = snippet_size_num + } + db, err := sql.Open("sqlite3", database_path) if err != nil { log.Fatal(err) @@ -63,8 +79,23 @@ func main() { c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS") query := c.Query("q") + body_type := c.Query("body") + + if ((body_type != "all") && (body_type != "none") && (body_type != "snippet")) { + body_type = "none" + } + + var stm *sql.Stmt + var err error + + if (body_type == "snippet") { + stm, err = db.Prepare("SELECT note_id, highlight(note_search, 1, '', ''), top_level_title, is_done, is_todo, snippet(note_search, 2, '', '', '', ?) FROM note_search(?)") + } else if (body_type == "all") { + stm, err = db.Prepare("SELECT note_id, highlight(note_search, 1, '', ''), top_level_title, is_done, is_todo, highlight(note_search, 2, '', '') FROM note_search(?)") + } else if (body_type == "none") { + stm, err = db.Prepare("SELECT note_id, highlight(note_search, 1, '', ''), top_level_title, is_done, is_todo FROM note_search(?)") + } - stm, err := db.Prepare("SELECT note_id, title, top_level_title, is_done, is_todo FROM note_search(?)") if err != nil { log.Fatal(err) @@ -76,8 +107,13 @@ func main() { } results := make([]map[string]string, 0) + var rows *sql.Rows - rows, err := stm.Query(query) + if (body_type == "snippet") { + rows, err = stm.Query(snippet_size, query) + } else { + rows, err = stm.Query(query) + } if err != nil { log.Fatal(err) c.JSON(500, gin.H{ @@ -94,13 +130,31 @@ func main() { var note_is_done string var note_is_todo string - err = rows.Scan( - ¬e_id, - ¬e_title, - ¬e_top_level_title, - ¬e_is_done, - ¬e_is_todo, - ) + item := make(map[string]string) + + if (body_type != "none") { + var note_highlight string + + err = rows.Scan( + ¬e_id, + ¬e_title, + ¬e_top_level_title, + ¬e_is_done, + ¬e_is_todo, + ¬e_highlight, + ) + if (body_type != "none") { + item["highlight"] = note_highlight + } + } else { + err = rows.Scan( + ¬e_id, + ¬e_title, + ¬e_top_level_title, + ¬e_is_done, + ¬e_is_todo, + ) + } if err != nil { log.Fatal(err) c.JSON(500, gin.H{ @@ -110,7 +164,6 @@ func main() { return } - item := make(map[string]string) item["id"] = note_id item["title"] = note_title item["top_level_title"] = note_top_level_title diff --git a/static/dark-syntax.css b/static/dark-syntax.css index 2938240..afdbd46 100644 --- a/static/dark-syntax.css +++ b/static/dark-syntax.css @@ -1,82 +1,85 @@ -pre { line-height: 125%; } -td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } -span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } -td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } -span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } -.hll { background-color: #49483e } -.c { color: #75715e } /* Comment */ -.err { color: #960050; background-color: #1e0010 } /* Error */ -.esc { color: #f8f8f2 } /* Escape */ -.g { color: #f8f8f2 } /* Generic */ -.k { color: #66d9ef } /* Keyword */ -.l { color: #ae81ff } /* Literal */ -.n { color: #f8f8f2 } /* Name */ -.o { color: #f92672 } /* Operator */ -.x { color: #f8f8f2 } /* Other */ -.p { color: #f8f8f2 } /* Punctuation */ -.ch { color: #75715e } /* Comment.Hashbang */ -.cm { color: #75715e } /* Comment.Multiline */ -.cp { color: #75715e } /* Comment.Preproc */ -.cpf { color: #75715e } /* Comment.PreprocFile */ -.c1 { color: #75715e } /* Comment.Single */ -.cs { color: #75715e } /* Comment.Special */ -.gd { color: #f92672 } /* Generic.Deleted */ -.ge { color: #f8f8f2; font-style: italic } /* Generic.Emph */ -.gr { color: #f8f8f2 } /* Generic.Error */ -.gh { color: #f8f8f2 } /* Generic.Heading */ -.gi { color: #a6e22e } /* Generic.Inserted */ -.go { color: #66d9ef } /* Generic.Output */ -.gp { color: #f92672; font-weight: bold } /* Generic.Prompt */ -.gs { color: #f8f8f2; font-weight: bold } /* Generic.Strong */ -.gu { color: #75715e } /* Generic.Subheading */ -.gt { color: #f8f8f2 } /* Generic.Traceback */ -.kc { color: #66d9ef } /* Keyword.Constant */ -.kd { color: #66d9ef } /* Keyword.Declaration */ -.kn { color: #f92672 } /* Keyword.Namespace */ -.kp { color: #66d9ef } /* Keyword.Pseudo */ -.kr { color: #66d9ef } /* Keyword.Reserved */ -.kt { color: #66d9ef } /* Keyword.Type */ -.ld { color: #e6db74 } /* Literal.Date */ -.m { color: #ae81ff } /* Literal.Number */ -.s { color: #e6db74 } /* Literal.String */ -.na { color: #a6e22e } /* Name.Attribute */ -.nb { color: #f8f8f2 } /* Name.Builtin */ -.nc { color: #a6e22e } /* Name.Class */ -.no { color: #66d9ef } /* Name.Constant */ -.nd { color: #a6e22e } /* Name.Decorator */ -.ni { color: #f8f8f2 } /* Name.Entity */ -.ne { color: #a6e22e } /* Name.Exception */ -.nf { color: #a6e22e } /* Name.Function */ -.nl { color: #f8f8f2 } /* Name.Label */ -.nn { color: #f8f8f2 } /* Name.Namespace */ -.nx { color: #a6e22e } /* Name.Other */ -.py { color: #f8f8f2 } /* Name.Property */ -.nt { color: #f92672 } /* Name.Tag */ -.nv { color: #f8f8f2 } /* Name.Variable */ -.ow { color: #f92672 } /* Operator.Word */ -.w { color: #f8f8f2 } /* Text.Whitespace */ -.mb { color: #ae81ff } /* Literal.Number.Bin */ -.mf { color: #ae81ff } /* Literal.Number.Float */ -.mh { color: #ae81ff } /* Literal.Number.Hex */ -.mi { color: #ae81ff } /* Literal.Number.Integer */ -.mo { color: #ae81ff } /* Literal.Number.Oct */ -.sa { color: #e6db74 } /* Literal.String.Affix */ -.sb { color: #e6db74 } /* Literal.String.Backtick */ -.sc { color: #e6db74 } /* Literal.String.Char */ -.dl { color: #e6db74 } /* Literal.String.Delimiter */ -.sd { color: #e6db74 } /* Literal.String.Doc */ -.s2 { color: #e6db74 } /* Literal.String.Double */ -.se { color: #ae81ff } /* Literal.String.Escape */ -.sh { color: #e6db74 } /* Literal.String.Heredoc */ -.si { color: #e6db74 } /* Literal.String.Interpol */ -.sx { color: #e6db74 } /* Literal.String.Other */ -.sr { color: #e6db74 } /* Literal.String.Regex */ -.s1 { color: #e6db74 } /* Literal.String.Single */ -.ss { color: #e6db74 } /* Literal.String.Symbol */ -.bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ -.fm { color: #a6e22e } /* Name.Function.Magic */ -.vc { color: #f8f8f2 } /* Name.Variable.Class */ -.vg { color: #f8f8f2 } /* Name.Variable.Global */ -.vi { color: #f8f8f2 } /* Name.Variable.Instance */ -.vm { color: #f8f8f2 } /* Name.Variable.Magic */ -.il { color: #ae81ff } /* Literal.Number.Integer.Long */ +/* Dark mode. */ +@media (prefers-color-scheme: dark) { + pre { line-height: 125%; } + td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } + span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } + td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } + span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } + .hll { background-color: #49483e } + .c { color: #75715e } /* Comment */ + .err { color: #960050; background-color: #1e0010 } /* Error */ + .esc { color: #f8f8f2 } /* Escape */ + .g { color: #f8f8f2 } /* Generic */ + .k { color: #66d9ef } /* Keyword */ + .l { color: #ae81ff } /* Literal */ + .n { color: #f8f8f2 } /* Name */ + .o { color: #f92672 } /* Operator */ + .x { color: #f8f8f2 } /* Other */ + .p { color: #f8f8f2 } /* Punctuation */ + .ch { color: #75715e } /* Comment.Hashbang */ + .cm { color: #75715e } /* Comment.Multiline */ + .cp { color: #75715e } /* Comment.Preproc */ + .cpf { color: #75715e } /* Comment.PreprocFile */ + .c1 { color: #75715e } /* Comment.Single */ + .cs { color: #75715e } /* Comment.Special */ + .gd { color: #f92672 } /* Generic.Deleted */ + .ge { color: #f8f8f2; font-style: italic } /* Generic.Emph */ + .gr { color: #f8f8f2 } /* Generic.Error */ + .gh { color: #f8f8f2 } /* Generic.Heading */ + .gi { color: #a6e22e } /* Generic.Inserted */ + .go { color: #66d9ef } /* Generic.Output */ + .gp { color: #f92672; font-weight: bold } /* Generic.Prompt */ + .gs { color: #f8f8f2; font-weight: bold } /* Generic.Strong */ + .gu { color: #75715e } /* Generic.Subheading */ + .gt { color: #f8f8f2 } /* Generic.Traceback */ + .kc { color: #66d9ef } /* Keyword.Constant */ + .kd { color: #66d9ef } /* Keyword.Declaration */ + .kn { color: #f92672 } /* Keyword.Namespace */ + .kp { color: #66d9ef } /* Keyword.Pseudo */ + .kr { color: #66d9ef } /* Keyword.Reserved */ + .kt { color: #66d9ef } /* Keyword.Type */ + .ld { color: #e6db74 } /* Literal.Date */ + .m { color: #ae81ff } /* Literal.Number */ + .s { color: #e6db74 } /* Literal.String */ + .na { color: #a6e22e } /* Name.Attribute */ + .nb { color: #f8f8f2 } /* Name.Builtin */ + .nc { color: #a6e22e } /* Name.Class */ + .no { color: #66d9ef } /* Name.Constant */ + .nd { color: #a6e22e } /* Name.Decorator */ + .ni { color: #f8f8f2 } /* Name.Entity */ + .ne { color: #a6e22e } /* Name.Exception */ + .nf { color: #a6e22e } /* Name.Function */ + .nl { color: #f8f8f2 } /* Name.Label */ + .nn { color: #f8f8f2 } /* Name.Namespace */ + .nx { color: #a6e22e } /* Name.Other */ + .py { color: #f8f8f2 } /* Name.Property */ + .nt { color: #f92672 } /* Name.Tag */ + .nv { color: #f8f8f2 } /* Name.Variable */ + .ow { color: #f92672 } /* Operator.Word */ + .w { color: #f8f8f2 } /* Text.Whitespace */ + .mb { color: #ae81ff } /* Literal.Number.Bin */ + .mf { color: #ae81ff } /* Literal.Number.Float */ + .mh { color: #ae81ff } /* Literal.Number.Hex */ + .mi { color: #ae81ff } /* Literal.Number.Integer */ + .mo { color: #ae81ff } /* Literal.Number.Oct */ + .sa { color: #e6db74 } /* Literal.String.Affix */ + .sb { color: #e6db74 } /* Literal.String.Backtick */ + .sc { color: #e6db74 } /* Literal.String.Char */ + .dl { color: #e6db74 } /* Literal.String.Delimiter */ + .sd { color: #e6db74 } /* Literal.String.Doc */ + .s2 { color: #e6db74 } /* Literal.String.Double */ + .se { color: #ae81ff } /* Literal.String.Escape */ + .sh { color: #e6db74 } /* Literal.String.Heredoc */ + .si { color: #e6db74 } /* Literal.String.Interpol */ + .sx { color: #e6db74 } /* Literal.String.Other */ + .sr { color: #e6db74 } /* Literal.String.Regex */ + .s1 { color: #e6db74 } /* Literal.String.Single */ + .ss { color: #e6db74 } /* Literal.String.Symbol */ + .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ + .fm { color: #a6e22e } /* Name.Function.Magic */ + .vc { color: #f8f8f2 } /* Name.Variable.Class */ + .vg { color: #f8f8f2 } /* Name.Variable.Global */ + .vi { color: #f8f8f2 } /* Name.Variable.Instance */ + .vm { color: #f8f8f2 } /* Name.Variable.Magic */ + .il { color: #ae81ff } /* Literal.Number.Integer.Long */ +} diff --git a/static/homepage.html b/static/homepage.html index a0ac06a..aa566eb 100644 --- a/static/homepage.html +++ b/static/homepage.html @@ -6,11 +6,13 @@