commit 41a42c7a59043a4c487b2ce29faa6969beea91be Author: Kyle Isom Date: Wed Feb 23 22:55:41 2022 -0800 Initial import. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7da037c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +output diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..dbfd58f --- /dev/null +++ b/Makefile @@ -0,0 +1,75 @@ +PY?=python3 +PELICAN?=pelican +PELICANOPTS= + +BASEDIR=$(CURDIR) +INPUTDIR=$(BASEDIR)/content +OUTPUTDIR=$(BASEDIR)/output +CONFFILE=$(BASEDIR)/pelicanconf.py +PUBLISHCONF=$(BASEDIR)/publishconf.py + + +DEBUG ?= 0 +ifeq ($(DEBUG), 1) + PELICANOPTS += -D +endif + +RELATIVE ?= 0 +ifeq ($(RELATIVE), 1) + PELICANOPTS += --relative-urls +endif + +help: + @echo 'Makefile for a pelican Web site ' + @echo ' ' + @echo 'Usage: ' + @echo ' make html (re)generate the web site ' + @echo ' make clean remove the generated files ' + @echo ' make regenerate regenerate files upon modification ' + @echo ' make publish generate using production settings ' + @echo ' make serve [PORT=8000] serve site at http://localhost:8000' + @echo ' make serve-global [SERVER=0.0.0.0] serve (as root) to $(SERVER):80 ' + @echo ' make devserver [PORT=8000] serve and regenerate together ' + @echo ' make ssh_upload upload the web site via SSH ' + @echo ' make rsync_upload upload the web site via rsync+ssh ' + @echo ' ' + @echo 'Set the DEBUG variable to 1 to enable debugging, e.g. make DEBUG=1 html ' + @echo 'Set the RELATIVE variable to 1 to enable relative urls ' + @echo ' ' + +html: + $(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) + +clean: + [ ! -d $(OUTPUTDIR) ] || rm -rf $(OUTPUTDIR) + +regenerate: + $(PELICAN) -r $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) + +serve: +ifdef PORT + $(PELICAN) -l $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) -p $(PORT) +else + $(PELICAN) -l $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) +endif + +serve-global: +ifdef SERVER + $(PELICAN) -l $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) -p $(PORT) -b $(SERVER) +else + $(PELICAN) -l $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) -p $(PORT) -b 0.0.0.0 +endif + + +devserver: +ifdef PORT + $(PELICAN) -lr $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) -p $(PORT) +else + $(PELICAN) -lr $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) +endif + +publish: + $(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR) -s $(PUBLISHCONF) $(PELICANOPTS) + + +.PHONY: html help clean regenerate serve serve-global devserver stopserver publish \ No newline at end of file diff --git a/content/files/bullet_scan.jpg b/content/files/bullet_scan.jpg new file mode 100644 index 0000000..8983399 Binary files /dev/null and b/content/files/bullet_scan.jpg differ diff --git a/content/files/exo-arch.jpg b/content/files/exo-arch.jpg new file mode 100644 index 0000000..0e00da0 Binary files /dev/null and b/content/files/exo-arch.jpg differ diff --git a/content/files/i/bullet_scan.jpg b/content/files/i/bullet_scan.jpg new file mode 100644 index 0000000..8983399 Binary files /dev/null and b/content/files/i/bullet_scan.jpg differ diff --git a/content/files/i/exo-arch.jpg b/content/files/i/exo-arch.jpg new file mode 100644 index 0000000..0e00da0 Binary files /dev/null and b/content/files/i/exo-arch.jpg differ diff --git a/content/files/i/on_exocortices_graph.jpg b/content/files/i/on_exocortices_graph.jpg new file mode 100644 index 0000000..c75a9fa Binary files /dev/null and b/content/files/i/on_exocortices_graph.jpg differ diff --git a/content/files/i/t/bullet_scan.jpg b/content/files/i/t/bullet_scan.jpg new file mode 100644 index 0000000..98fa42a Binary files /dev/null and b/content/files/i/t/bullet_scan.jpg differ diff --git a/content/files/i/t/exo-arch.jpg b/content/files/i/t/exo-arch.jpg new file mode 100644 index 0000000..bc9211a Binary files /dev/null and b/content/files/i/t/exo-arch.jpg differ diff --git a/content/files/i/t/on_exocortices_graph.jpg b/content/files/i/t/on_exocortices_graph.jpg new file mode 100644 index 0000000..d330b89 Binary files /dev/null and b/content/files/i/t/on_exocortices_graph.jpg differ diff --git a/content/files/on_exocortices_graph.jpg b/content/files/on_exocortices_graph.jpg new file mode 100644 index 0000000..c75a9fa Binary files /dev/null and b/content/files/on_exocortices_graph.jpg differ diff --git a/content/pages/about.md b/content/pages/about.md new file mode 100644 index 0000000..cdf06a4 --- /dev/null +++ b/content/pages/about.md @@ -0,0 +1,4 @@ +Title: About +Slug: about + +This notebook covers a project to build my own knowledge management system. diff --git a/content/pages/historical/bullet_notes.md b/content/pages/historical/bullet_notes.md new file mode 100644 index 0000000..53c8233 --- /dev/null +++ b/content/pages/historical/bullet_notes.md @@ -0,0 +1,75 @@ +Title: Bullet Journal Notes + +[![](/files/i/t/bullet_scan.jpg)](/files/i/bullet_scan.jpg) + +The original notes from my bullet journal that I took on my exocortex +are found on page 109-110 of that journal; page 108 is journal entries +for 2021-02-08 and 2021-02-16; page 111 starts with 2021-02-17. The +first entry in the mercurial logs for the first pass I started last +year was 2021-02-14. + +* Goal + - Collect artifacts + notes → current knowledge + - Daily writeups +* Ex. + - Gemlog + - notes.org + - books/*.org + - PDF docs +* SCM is a red herring +* Evernote + - notes/folders + - clipper + - tags + - everything searchable + - synced (may be red herring) +* Quiver + - cell types +* Jupyter notebooks + - code + - markdown +* swolfram + - [archive](https://writings.stephenwolfram.com/2019/02/seeking-the-productive-life-some-details-of-my-personal-infrastructure/) +* Solution + - Minimal viable metadata + - Data + - Tags + - Artifact link + - Central artifact repo + - Hash/link + - Node type (article, notes, ...) + - Type: Node = Folder|Page|Artifact + - How to create a searchable index? + - Retain old copies + - Index header + - Date retrieved + - Doc ID + - Source +* Artifact header + - Needs to support history + - Doc ID + - Date retrieved / stored + - Artifact date + - Source + - Artifact type + - Tags + - Category + - Blobs + - Format + - Blob ID +* Central artifact repository + - Metadata index + - Blob store + - Upload interface +* Elements of an exocortex + - Artifacts + - Artifact repository + - Notes + - Structure + - UI + - Query + - Exploratory + - Presentation + - Update + - Locality + - Totality diff --git a/content/pages/historical/index.md b/content/pages/historical/index.md new file mode 100644 index 0000000..2974e51 --- /dev/null +++ b/content/pages/historical/index.md @@ -0,0 +1,7 @@ +Title: Historical Notes +Slug: index + +These pages track the origins of the exocortex. + +* [Original bullet journal notes](/historical/bullet_notes) +* [On exocortices](/historical/on_exocortices) diff --git a/content/pages/historical/on_exocortices.md b/content/pages/historical/on_exocortices.md new file mode 100644 index 0000000..646af62 --- /dev/null +++ b/content/pages/historical/on_exocortices.md @@ -0,0 +1,299 @@ +Title: On Exocortices + +*This is from a gemlog post written on 2021-02-10.* + +This is a rough draft on some thoughts about exocortices that has been +simmering in the back of my mind lately. The catalyst for writing it +was reading Stephen Wolfram's (with all caveats that come with reading +his posts) entry "Seeking the Productive Life: Some Details of My +Personal Infrastructure". + +This is a rough draft on some thoughts about exocortices that has been +simmering in the back of my mind lately. The catalyst for writing it +was reading Stephen Wolfram's (with all caveats that come with reading +his posts) entry "Seeking the Productive Life: Some Details of My +Personal Infrastructure". + +## Background + +An exocortex is "a hypothetical artificial information-processing +system that would augment a brain's biological cognitive processes." I +have made many attempts at building my own, including + +* A web-based wiki (including my own custom solution, gitit, + MediaWiki, and others. +* Org-mode based notes, including my current notes/notes.org system + (with subdirectories for other things such as book notes) +* Evernote / Notion +* The Quiver MacOS app +* Experimenting in building custom exocortex software (e.g. kortex) +* A daily weblog (e.g. the old ai6ua.net site) and gemlog to summarize + important knowledge gained that day. + +Each of these has their own shortcomings that don't quite match up +with my expectations or desires. An exocortex must be a personalized +system adapted to its user to maximise knowledge capture. + +Succinctly put, the goal of an +[exocortex](https://en.wiktionary.org/wiki/exocortex) is to collect +artifacts and notes (including daily notes), organize them, and allow +for written summaries of current snapshots of my knowledge. Put +another way, "artifacts + notes + graph structure = exocortex". Note +that a folder hierarchy is a tree, which is a form of directed +graph. Symlinks inside a folder act as edges to notes outside of that +folder, refining the graph structure. + +This writeup is an attempt at characterising and exploring the +exocortex problem space to capture my goals, serve as a foundation for +the construction of such a system, and, through discussion of the +problem space, tease out the structure of the problem to discover a +closer approximation to the idealized reality of an exocortex system. + +## The elements of exocortices + +The elements of an exocortex, briefly touched on above and expanded +below, include + +* artifacts, +* the artifact repository, +* notes, +* structure, +* a query interface, +* an exploratory interface, +* a presentation interface, +* an update interface, +* locality, and +* totality. + +### Artifacts + +An artifact is any object that is not a textual writeup by me that +should be referenceable as part of the exocortex. A copy of a paper +from ArXiV might serve as an artifact. Importantly, artifacts must be +locally-available. They serve as a snapshot of some source of +knowledge, and should not be subject to link decay, future pay-walling +(or loss of access to a pay-walled system), or loss of +connectivity. An artifact should be timestamped: when was it captured? +When was the artifact created upstream? An artifact must also have +some associated upstream information --- how did it come to be in the +repository? + +### The artifact repository + +An artifact may be relevant to more than one field of interest; +accordingly, all artifacts should exist in a central repository. This +repository should support artifact histories (e.g. collecting updates +to artifacts, where the history is important in capturing a historical +view of knowledge), multiple formats (a book may exist in PDF, EPUB, +or other formats), and a mechanism for exploring, finding, and +updating docs. The repository must capture relevant metadata about +each artifact. + +### Notes + +A note is a written summary of a certain field. It should be in some +rich-text format that supports linking as well as basic +formatting. The ideal text format appears to be the org-mode format +given its rich formatting and ability to transition fluidly between +outline and full document; however, this may not be the final, most +effective format. A note is the distillation of artifacts into an +understandable form, providing avenues to discover specifics that may +need to be held in working memory only briefly. + +### Structure + +A structured format allows for fast and efficient knowledge +lookups. It grants the researcher a starting place with a set of rules +governing where and how things may be found. It imposes order over +chaos such that relevant kernels of knowledge may be retrieved and +examined in an expedient manner. The metaphor that humans seem to +adapt to the most readily is a graph structure, particularly those +that are generally hierarchical in nature. + +### A query interface + +The exocortex and the artifact repository both require a query +interface; they may be part of the same UI. A query UI allows a +researcher to pose questions of the exocortex, directly looking for +specific knowledge. + +The four interfaces (query, exploration, presentation, and update) may +all be facets of the same interface, and they may benefit from a +cohesive and unified interface; however, it is important that all of +these use cases are considered and supported. + +### An exploratory interface + +The exploratory interface allows a researcher to meander through the +knowledge store, exploring topics and potentially identifying new +areas to push the knowledge sphere out further. + +### A presentation interface + +The presentation interface allows a set of notes to be shared with +others; it should be possible to include some or all artifacts +associated with these notes. For example, it may not be appropriate to +share a copy of a book with the presentation, but it may be +appropriate to share a copy of some of the supporting papers. + +### An update interface + +The update interface is where knowledge is added to the exocortex, +whether through capturing an artifact or writing notes. + +### Locality + +An exocortex must be localized to the user, with the full repository +available offline. Quick input or scratch pad notes might be +available, but realistically, the cost of cloud storage and the +transfer sizes mean that having the full exocortex available is +unlikely. Instead, a hybrid model allowing quick captures of knowledge +available remotely combined with a full exocortex on a local system +presents the probably best solution. + +### Totality + +An exocortex represents the sum of the user's knowledge. There aren't +separate exocortices for different areas. Everything I know should go +into my exocortex. + +## Exploring the problem space + +In order to map out the structure of an exocortex, it's useful to +review what has worked and what hasn't. Each alternative presented +will consider what worked and what didn't to clarify what an effective +exocortex looks like. + +### Git-backed wikis and plaintext folders + +At a high-level, wikis like Gitit and folders of plain-text (including +org-mode) data are roughly equivalent; the differences lie primarily +in how they are presented. Neither approach works well for indexing or +organizing artifacts, and while some approaches like a scanner that +adds notes to a SQLite database (for improved search performance). + +Using a folder of org-mode notes is probably one of the better +note-taking interfaces that I have found; however, there is no notion +of an artifact repository without considerable manual work. + +The main downsides to this approach are the lack of good query and +exploration UIs, along with the lack of a useful artifact +repository. The upsides are good updates and presentation interfaces. + +### Evernote and Notion + +Evernote (and also notion) provide a unified, searchable interface +across multiple machines. Evernote in particular has a usable artifact +repository, although information about upstream sources isn't +available, nor are metadata about the object or the idea of multiple +formats and history. + +Evernote is a paid service, and neither is particularly extensible to +a user's needs. Exploring the exocortex is difficult, as there's no +notion of an entry point. Presenting nodes is met with some success, +albeit limited. + +### Quiver + +Quiver is an excellent note-taking application; however, it is +MacOS-only. It does have some ability to import web pages, but in +general it lacks any idea of an artifact repository. The ability to +intersperse different cell types is good. + +### Jupyter notebooks + +Jupyter notebooks provide an excellent interface for interspersing +computational ideas with prose; there is no notion of an artifact +repository, however. Linking notebooks isn't supported, and there is +no overall structure besides manual hyperlinking and a directory +structure. + +## The artifact repository + +The artifact repository is one of the two pillars of the exocortex; it +stores the "first hand" sources of knowledge. + +### The central index + +The first part of an artifact repository is a central index that +provides + +* references and linking to artifacts, +* a "blob" store that contains the artifacts, and +* some management interface that allows adding and editing metadata as + well as adding artifacts. + +An artifact entry in the index contains, at a minimm, + +* An artifact identifier +* Authorship information + +The artifact identifier is used to associate all related artifacts +(e.g. previous revisions, different formats, etc.) + +### Artifacts + +An artifact consists of multiple components: + +* A primary metadata entry that organizes artifacts +* Pointers to artifact "blobs" +* A historical record of changed blobs + +The metadata header for an artifact should contain, at a minimum, +fields for + +* Artifact identifier +* A list of revisions + +Each artifact can have zero or more blobs associated. For example, a +physical book reference might not have a blob associated; an ebook +might have multiple blobs corresponding to different formats; and a +webpage snapshot may have mulitple blobs representing revisions to the +page. + +A blob header stores + +* The artifact identifier +* The date retrieved or stored +* The date of the artifact itself +* The source +* Blob type information (e.g. a MIME type) +* A list of categories +* A list of tags + +The headers should probably be stored in a database of some kind; +SQLite is a good example for the first iteration. Blobs themselves +will need to be stored on disk, probably in a format related to a hash +of the blob contents, such as in a content-addressable store (CAS). + +## The exocortex + +The exocortex consists of a graph database that links notes. At a +broad level, it should probably start with a root node that points to +broad fields. The update interface should allow manipulation of nodes +as graph nodes in addition to allowing for adding and editing notes. A +node might be thought of as "type node = Note | ArtifactLink". That +is, a note can link to other notes or to artifacts. A proper node +title is the sum of the paths. For example, consider the following +structure linked below: + +[![](/files/i/t/on_exocortices_graph.jpg)](/files/i/on_exocortices_graph.jpg) + +Different possibilities for naming note3 include: + +* root->note2->note3 +* root=>note2=>note3 +* root/note2/note3 + +Personally, I prefer the arrow notation with equal sign. Each note can +be shortened to a partial path; e.g. "note2=>note3". The title for +each note can be stored in a metadata entry. + + +## Next steps + +A first step is to start constructing an artifact repository. Once +this is in place, a suitable graph database (for example, +[cayley](https://github.com/cayleygraph/cayley)) should be identified, +and an exocortex core developed. User interfaces will necessarily be +developed alongside these systems. diff --git a/content/pages/ls.md b/content/pages/ls.md new file mode 100644 index 0000000..8b187a9 --- /dev/null +++ b/content/pages/ls.md @@ -0,0 +1,4 @@ +Title: Pages + +* [Historical context](/historical/) +* [Design docs](/specs/) diff --git a/content/pages/specs/functional.md b/content/pages/specs/functional.md new file mode 100644 index 0000000..29e5c2d --- /dev/null +++ b/content/pages/specs/functional.md @@ -0,0 +1,238 @@ +Title: Functional Spec for the Exocortex +Tags: specs + +kExocortex is a tool for capturing and retaining knowledge, making it +searchable. + +This is the initial top-level draft to sort out the high-level vision. + +## Summary + +The more you learn, the harder it is to recall specific things. Fortunately, +computers are generally pretty good at remembering things. kExocortex is +my attempt at building a knowledge graph for long-term memory. + +In addition to having functionality like notetaking systems like +[Dendron](https://dendron.so), I'd like to keep track of what I call artifacts. +An artifact is a source of some knowledge; it might be a PDF copy of a book, an +image, or a URL. + +In a perfect world, I would have a local copy of everything with a remote backup. +The remote backup lets me restore the exocortex in the event of data loss. + +## Usage sketches + +### Research mode + +If I am researching a topic, I have a top-level node that contains all the +research I'm working on. I can link artifacts to a note, including URLs. One of +the reasons it makes sense to attach a URL to a document is that I can reuse +them, as well as go back and search URLs based on tags or categories. It would +make sense to tag any artifacts with relevant tags from the note once it is saved. + +For example, let's say that I am research graphing databases. In Dendron, this +note lives under `comp.database.graph`. I might find this O'Reilly book on +[Neo4J](https://go.neo4j.com/rs/710-RRC-335/images/Neo4j_Graph_Algorithms.pdf) +that discusses graph algorithms. I might link it here, and I might link it +under a Neo4J-specific node. I would store the PDF in an artifact repository, +adding relevant tags (such as "graph-database", "neo4j", "oreilly") and +categorize it under books, PDFs, comp/database/graph/neo4j. + +Going forward, if I want to revisit the book, I don't have to find it online +again. It's easily accessible from the artifact repository. + +The user interface for the knowledge graph should show a list of associated +artifacts. + +Nodes are also timestamped; I am leaning towards keep track of every time a +page was edited (but probably not the edits). If I know I was researching +graph databases last week, and I log the URLs I was reading as artifacts, +I have a better history of what I was reading. + +### Reading from a mobile device + +Sometimes I'm on my iPad or phone, and I want to save the link I'm reading. I +should be able to stash documents, URLs, etc, in the artifact repository. This +implies a remote endpoint that I can enter a URL and a tag, and have that +entered into the artifact repository later. + +### Cataloging artifacts + +If I've entered a bunch of artifacts, I should be able to see a list of ones +that need categorizing or that aren't attached to a node. + +### Autotagging + +The interface should search the text of a note to identify any tags. This +brings up an important feature: notes consist of cells, and each cell has a +type. The primary use case is to support markdown formatting and code blocks, +while not touching the code blocks during autotagging. For example, + +``` +--- +node: today.2022.02.21 +--- + +I figured out how to get Cayley running in production. + +\``` +cayleyd --some flag --other flag +\``` +``` + +The exocortex would see Cayley, identify that as a node, and add the tags for +that node to this one. It might see production and add that as a tag, e.g. for +ops-related stuff. + +### Fast capture + +I should be able to enter a quick note, which would go under a daily node tree. +Something like `quick.2022-02-27.1534`. + +This would get autotagged. Quick notes might also get a metadata tag indicating +whether I went back and integrated them into the rest of the knowledge graph. + +One way I could use this might be to text or email a note, or to have a quick +capture program on my computer. + + + +## Requirements & Assumptions + +What should it do? What assumptions are being made about it? What's +considered "in scope" and what won't the project try to do? + +Does it need to be compatible with any existing solutions or systems? + +If it's a daemon, how are you going to manage it? + +What are the dependencies that are assumed to be available? + +## System Design + +### Major components + +The system has two logical components: the artifact repository and the +knowledge graph. + +#### Artifact repository + +There should be, at a minimum, a local artifact repository. It will have its +own tooling and UI for interaction, as well as being linked to the knowledge +graph. + +Previous prototypes stored artifact metadata in SQLite, and the contents of the +artifacts in a blob store. The blob store is a content-addressable system for +retrieving arbitrary data. A remote option might use an S3-equivalent like +Minio. + +#### Knowledge graph + +The knowledge graph stores nodes. The current model stores the graph in SQLite, +using an external file sync (e.g. syncthing) to sync the databases across +machines. + +### Data model + +Previous prototypes used separate SQLite databases for the artifact repository +and the knowledge graph. + +#### Single SQLite database + +The concern with a single SQLite database is that it would be accessed by two +different systems, causing potential locking issues. + +This could be solved by a single unified backend server; this is the preferred +approach. + +#### Split SQLite databases + +The original prototype split the databases for performance reasons. However, +this was based on any empirical evidence. + +The major downside to this is that tags and categories are not shared between +the artifact repository and the knowledge graph. Categories might make sense +for splitting; e.g. an artifact category might be 'PDF' while a node might have +the category 'Research'. However, tags should be shared between both systems. + +#### PostgreSQL database + +Another option is to to use postgres. This brings a heavy ops cost, while +enabling a variety of replication and backup strategies. + +### Architectural overview + +[![The exocortex architecture](/files/i/t/exo-arch.jpg)](/files/i/exo-arch.jpg) + +There is a backend server, `exod`, that will have a gRPC endpoint for +communicating with frontends. The approach allows for a reverse-proxy front end +on a public server over Tailscale for remote devices. It also maintains a local +blob store, the database, and a connection to a remote minio server for backing +up blobs and retrieving missing blobs. + +If a standard HTTP API is needed, it can be added in later. One potential use +for this is for retrieving blobs (e.g. GET /artifacts/blob/id/...). + +## Supportability + +### Failure scenarios + +#### Data corruption + +If the data is corrupted locally, a local import from the remote end would +restore it. Alternatively, it may be restored from local backups. + +If the data is corrupted remotely, a local export to the remote end would +restore it. + +### Platform support + +The main program would ideally run on Linux primarily, but I'd like to be able +to use it on my Windows desktop too. + +### Packaging and deployment + +## Security + +The gRPC endpoint should be authenticated. The system is intended to operate +over localhost or a local network, so the use of TLS is probably untenable. +[minica](https://github.com/jsha/minica) is an option, but then key rotation +needs to be built in. + +A possible workaround is to only enable authentication (HTTP basic auth will +suffice) on the reverse proxy, which will also have TLS. + +## Project Dependencies + +The software should rely on no external sources, except for the software +packages that it uses. This can be mitigated with vendoring. + +## Open Issues + +* If I track each time a page was edited, does it make sense to roll this up? + e.g. I don't track edits to the second, but maybe to the hour or day. + + +## Milestones + +1. Specifications + a. Write up spec for the artifact repository data structures. + b. Write up a spec for the knowledge graph data structures. +2. Core systems + a. Build the artifact repository server. + b. Build the backend for the knowledge graph. + c. Build rough CLI interfaces to both. +3. Build the user interfaces. + a. Simple note taking. + b. Artifact upload and searching by tag, content type, title. + +## Review History + +This may not be applicable, but it's usually nice to have someone else +sanity check this. + +Keep a table of who reviewed the doc and when, for in-person reviews. Consider +having at least 1 in-person review. + + + diff --git a/content/pages/specs/index.md b/content/pages/specs/index.md new file mode 100644 index 0000000..7653760 --- /dev/null +++ b/content/pages/specs/index.md @@ -0,0 +1,4 @@ +Title: Design docs +Tags: specs + +* [Top-level functional spec](/specs/functional.html) diff --git a/content/posts/20220223.md b/content/posts/20220223.md new file mode 100644 index 0000000..e6c66e9 --- /dev/null +++ b/content/posts/20220223.md @@ -0,0 +1,32 @@ +Title: 20220223 +Slug: 20220223 +Date: 2022-02-23 22:22 PST +Modified: 2022-02-23 22:25 PST +Category: +Tags: journal +Authors: kyle +Summary: Design work on blobs. + +I finished writing out the basic blob database and structure types. The +blob was a structure like + +``` +type Blob struct { + ID string + Header *Header + ContentType string + Contents []byte +} +``` + +I decided to make it an `io.Reader` to make it better handle large files; +rather than load them entirely into memory, we can do a straight buffer. + +``` +type Blob struct { + ID string + Header *Header + ContentType string + r io.Reader +} +``` diff --git a/cpimg b/cpimg new file mode 100755 index 0000000..a7ec23b --- /dev/null +++ b/cpimg @@ -0,0 +1,13 @@ +#!/bin/sh + +set -eu + +imgdir="content/files/i" + +for img in $@ +do + imgbase="$(basename $img)" + imgpath="${imgdir}/${imgbase}" + cp $img $imgdir + convert -resize 800x600 "$imgpath" "${imgdir}/t/${imgbase}" +done diff --git a/monospace/LICENSE b/monospace/LICENSE new file mode 100644 index 0000000..fc0f07a --- /dev/null +++ b/monospace/LICENSE @@ -0,0 +1,13 @@ +DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +Version 2, December 2004 + +Copyright (C) 2004 Sam Hocevar + +Everyone is permitted to copy and distribute verbatim or modified +copies of this license document, and changing it is allowed as long +as the name is changed. + +DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/monospace/README.md b/monospace/README.md new file mode 100644 index 0000000..e23abb9 --- /dev/null +++ b/monospace/README.md @@ -0,0 +1,18 @@ +Monospace +========== + +Theme adapted from [Monospace for Wordpress](http://wordpress.org/themes/monospace) +Here is a screenshot for your viewing pleasure: + +![screengrab](screenshot.png) + +If you are using Markdown, you need to include the following option in your settings file to enable syntax highlighting. + + MD_EXTENSIONS = ['codehilite(css_class=codehilite code)'] + +Also, you might want to include the `DESCRIPTION` option (it appears in the left sidebar): + + DESCRIPTION = 'My blog and stuff ...' + +Nice and simple, +Enjoy! diff --git a/monospace/static/css/main.css b/monospace/static/css/main.css new file mode 100644 index 0000000..1c9b1df --- /dev/null +++ b/monospace/static/css/main.css @@ -0,0 +1,87 @@ +body { + margin: 0; + padding: 20px 0; + text-align: center; + font-family: Monospace; + color: #222; +} + +a,a:visited { + text-decoration: none; + color: #222; + font-weight: 700; +} + +a:hover { + color: #fff; + background-color: #111; +} + +h1 { + text-transform: uppercase; + line-height: 1.2; + padding: 0 0 15px; + margin: 0 0 15px; +} + +#wrap { + margin: 0 auto; + text-align: left; + font-size: 13px; + line-height: 1.4; +} + +#container { + max-width: 1000px; +} + +#sidebar { + overflow: hidden; + text-align: right; + height: auto; + padding: 0 5px 0 0; + border-right: 1px dotted #c8c8c8; +} + +#sidebar h1 { + border-bottom: 1px dotted #c8c8c8; +} + +#sidebar .description { + display: block; + width: 100%; + height: auto; + margin: 0 0 10px; +} + +.entry { + font-size: 14px; + line-height: 20px; + width: 700px; +} + +.entry img { + display: block; + border: 1px solid #bdbdbd!important; +} + +.entry a img:hover { + background-color: transparent; + border: 1px solid #e5e5e5!important; +} + +#footer { + text-align: center; + clear: both; +} + +#footer div { + margin: 30px 0 0; + padding: 30px 0 0; + border-top: 1px dotted #c8c8c8; +} + +#page-title { + position: relative; + top: 8px; +} diff --git a/monospace/static/css/main.old.css b/monospace/static/css/main.old.css new file mode 100644 index 0000000..b2ff1c8 --- /dev/null +++ b/monospace/static/css/main.old.css @@ -0,0 +1,69 @@ +/** + * Theme Name: Monospace + * Theme URI: http://vinicius.soylocoporti.org.br/monospace-wordpress-theme + * Description: A clean, extra-light, easy, objective, image free and 80 columns monospaced content theme. Made for code, manuals and e-mail publishing. Good for programming blogs, planets and posting by mail. + * Author: Vinicius Massuchetto + * Author URI: http://vinicius.soylocoporti.org.br + * Version: 1.91 + * Tags: white, blue, two-columns, fixed-width, threaded-comments, translation-ready + * License: GNU General Public License v2.0 + * License URI: http://www.gnu.org/licenses/gpl-2.0.html + */ + +@import url("pygment.css"); + +body { margin:0px; padding:20px 0px; text-align:center; font-family:Monospace; color:#222; } +a, a:visited { text-decoration:none; color:#222; font-weight: bold; } +a:hover { color:#FFF; background-color:#111 } +h1, h2, h3, h4, h5, h6, h7 { margin:0px; text-transform:uppercase; line-height: 1.2; } +h4, h5, h6 { font-size:14px; } +h1 { padding:0px 0px 15px; margin:0px 0px 15px 0px; } +blockquote { font-style:italic; background:#eee; margin:20px; padding:5px 10px; } +blockquote cite { display:block; padding:10px 20px 0 0; text-align:right; } +input, textarea { padding:5px; border:1px solid #8A8A8A; background:#EAEAEA; } +input:hover, textarea:hover, blockquote:hover { background:#E5E5E5; } + +#wrap { margin:0px auto; text-align:left; font-size: 13px; line-height: 1.4; } +#container { max-width: 1000px; } +#sidebar { overflow:hidden; text-align:right; height:auto; padding:0px 5px 0px 0px; border-right:1px dotted #C8C8C8; } +#sidebar li { list-style-type:none; } +#sidebar > li { margin:20px 0px; } +#sidebar h1 { border-bottom:1px dotted #C8C8C8; } +#sidebar .description { display:block; width:100%; height:auto; margin:0px 0px 10px 0px; } + +ul.sub-menu, ul.children { margin:0px 10px 0px 0px; } + +.post { padding:0px 0px 20px 0px; border-bottom:1px dotted #C8C8C8; } +.meta { margin:10px; padding:15px; background:#EAEAEA; clear:both; } +.meta span { display:block; clear:left; } +.thumbnail { margin:0px; padding:0px; } +.thumbnail img { float:right; } + +.entry { font-size: 14px; line-height: 20px; width: 700px; } +.entry h2, h3, h4, h5 { margin:30px 0px 10px 0px; } +.entry img { display:block; border:1px solid #BDBDBD !important; } +.entry img.wp-smiley { border:0px !important; } +.entry a img:hover { background-color:transparent; border:1px solid #E5E5E5 !important; } +.entry .aligncenter, div.aligncenter { margin:10px auto; } +.entry .alignleft { float: left; margin:10px 15px 10px 0px; } +.entry .alignright { float: right; margin:10px 0px 10px 15px; } +.entry .alignnone { margin:10px 0px; } +.entry p.attachment img { margin:0px auto; } +.entry code { font-weight: bold; } +.sticky { border-bottom:3px dotted #C8C8C8; } + +.literal-block { background-color: #ddd; padding: 6px 12px 6px 12px; } + +.related { margin:20px 0px 0px 0px; } +.nav { margin:30px 0px; text-align:center; } + +#footer { text-align:center; clear:both; } +#footer div { margin:30px 0px 0px 0px; padding:30px 0px 0px 0px; border-top:1px dotted #C8C8C8; } +#footer li { list-style-type:none; } +#footer .widget_tag_cloud h2 { display:none; } + +/* LWM EDITS */ +#page-title{ + position : relative; + top : 8px; +} diff --git a/monospace/static/css/pygment.css b/monospace/static/css/pygment.css new file mode 100644 index 0000000..f4581c2 --- /dev/null +++ b/monospace/static/css/pygment.css @@ -0,0 +1,2 @@ +.highlight code,.highlight pre{color:#fdce93;background-color:#3f3f3f;padding:10px;}.highlight .hll{background-color:#222}.highlight .c{color:#7f9f7f}.highlight .err{color:#e37170;background-color:#3d3535}.highlight .g{color:#7f9f7f}.highlight .k{color:#f0dfaf}.highlight .l{color:#ccc}.highlight .n{color:#dcdccc}.highlight .o{color:#f0efd0}.highlight .x{color:#ccc}.highlight .p{color:#41706f}.highlight .cm{color:#7f9f7f}.highlight .cp{color:#7f9f7f}.highlight .c1{color:#7f9f7f}.highlight .cs{color:#cd0000;font-weight:bold}.highlight .gd{color:#cd0000}.highlight .ge{color:#ccc;font-style:italic}.highlight .gr{color:red}.highlight .gh{color:#dcdccc;font-weight:bold}.highlight .gi{color:#00cd00}.highlight .go{color:gray}.highlight .gp{color:#dcdccc;font-weight:bold}.highlight .gs{color:#ccc;font-weight:bold}.highlight .gu{color:purple;font-weight:bold}.highlight .gt{color:#0040D0}.highlight .kc{color:#dca3a3}.highlight .kd{color:#ffff86}.highlight .kn{color:#dfaf8f;font-weight:bold}.highlight .kp{color:#cdcf99}.highlight .kr{color:#cdcd00}.highlight .kt{color:#00cd00}.highlight .ld{color:#cc9393}.highlight .m{color:#8cd0d3}.highlight .s{color:#cc9393}.highlight .na{color:#9ac39f}.highlight .nb{color:#efef8f}.highlight .nc{color:#efef8f}.highlight .no{color:#ccc}.highlight .nd{color:#ccc}.highlight .ni{color:#c28182}.highlight .ne{color:#c3bf9f;font-weight:bold}.highlight .nf{color:#efef8f}.highlight .nl{color:#ccc}.highlight .nn{color:#8fbede}.highlight .nx{color:#ccc}.highlight .py{color:#ccc}.highlight .nt{color:#9ac39f}.highlight .nv{color:#dcdccc}.highlight .ow{color:#f0efd0}.highlight .w{color:#ccc}.highlight .mf{color:#8cd0d3}.highlight .mh{color:#8cd0d3}.highlight .mi{color:#8cd0d3}.highlight .mo{color:#8cd0d3}.highlight .sb{color:#cc9393}.highlight .sc{color:#cc9393}.highlight .sd{color:#cc9393}.highlight .s2{color:#cc9393}.highlight .se{color:#cc9393}.highlight .sh{color:#cc9393}.highlight .si{color:#cc9393}.highlight .sx{color:#cc9393}.highlight .sr{color:#cc9393}.highlight .s1{color:#cc9393}.highlight .ss{color:#cc9393}.highlight .bp{color:#efef8f}.highlight .vc{color:#efef8f}.highlight .vg{color:#dcdccc}.highlight .vi{color:#ffffc7}.highlight .il{color:#8cd0d3} + diff --git a/monospace/templates/archives.html b/monospace/templates/archives.html new file mode 100644 index 0000000..bf2cb3e --- /dev/null +++ b/monospace/templates/archives.html @@ -0,0 +1,12 @@ +{% extends "base.html" %} +{% block content %} +
+

Archives for {{ SITENAME }}

+ +
    +{% for article in dates %} +
  • {{ article.date|strftime('%A, %Y %B %-d') }}: {{ article.title }}
  • +{% endfor %} +
+
+{% endblock %} diff --git a/monospace/templates/article.html b/monospace/templates/article.html new file mode 100644 index 0000000..6f5e2ab --- /dev/null +++ b/monospace/templates/article.html @@ -0,0 +1,31 @@ +{% extends "base.html" %} +{% block title %}{{ super() }} : {{ article.title }}{% endblock %} +{% block content %} +
+
+

{#{{ SITENAME }}#} {% if SITESUBTITLE %} {{ SITESUBTITLE }}{% endif %} {#:#} + {{ article.title }}

+ {% if not HIDE_DATE %}{% endif %} +
+
+ {{ article.content }} +
+ {% if article.tags %} +
+
+ Tags: + {% for tag in article.tags %} + + {% if not loop.last %}, {% endif %} + + {% endfor %} +
+ {% endif %} + + +

+{% endblock %} diff --git a/monospace/templates/author.html b/monospace/templates/author.html new file mode 100644 index 0000000..0b37290 --- /dev/null +++ b/monospace/templates/author.html @@ -0,0 +1,2 @@ +{% extends "index.html" %} +{% block title %}{{ SITENAME }} - {{ author }}{% endblock %} diff --git a/monospace/templates/authors.html b/monospace/templates/authors.html new file mode 100644 index 0000000..e69de29 diff --git a/monospace/templates/base.html b/monospace/templates/base.html new file mode 100644 index 0000000..13aecb5 --- /dev/null +++ b/monospace/templates/base.html @@ -0,0 +1,425 @@ + + + + {% block title %}{{ SITENAME }}{%endblock%} + + + + + + + + + +

+
+ + +

+ +
+ {% block content %} + {% endblock %} +
+
+ + +
+ + + diff --git a/monospace/templates/categories.html b/monospace/templates/categories.html new file mode 100644 index 0000000..e29be0c --- /dev/null +++ b/monospace/templates/categories.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} +{% block content %} +
    +{% for category, articles in categories %} +
  • {{ category }}
  • +{% endfor %} +
+{% endblock %} diff --git a/monospace/templates/category.html b/monospace/templates/category.html new file mode 100644 index 0000000..e2cc372 --- /dev/null +++ b/monospace/templates/category.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} +{% block title %}{{ SITENAME }} - {{ category }}{% endblock %} +{% block content %} +

{{ category }}

+ +{% endblock %} diff --git a/monospace/templates/index.html b/monospace/templates/index.html new file mode 100644 index 0000000..5c6c584 --- /dev/null +++ b/monospace/templates/index.html @@ -0,0 +1,33 @@ +{% extends "base.html" %} +{% block content_title %}{% endblock %} +{% block content %} +

wntrmute :: exocortex

+ +

This is a project to build an exocortex. It is a dump from my current +system into a format suitable for public sharing.

+ +{% if articles %} +

Recent posts

+
    +{% for article in dates|filter_journal %} +
  • {{ article.date|strftime('%Y-%m-%d') }}: {{ article.title }}
  • +{% endfor %} +
+ +

Recent journal entries

+

Journal entries are writeups of stuff that happened over the course +of a day; the current day's entry might be updated over the course of +the day.

+
    +{% for article in dates|select_journal(limit=3) %} +
  • {{ article.date|strftime('%Y-%m-%d') }}: {{ article.title }}
  • +{% endfor %} +
+{% endif %} + +

Select pages

+ + +{% endblock content %} diff --git a/monospace/templates/page.html b/monospace/templates/page.html new file mode 100644 index 0000000..c9d5ff6 --- /dev/null +++ b/monospace/templates/page.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} +{% block title %}{{ page.title }}{% endblock %} +{% block content %} +
+

{# {{ SITENAME }} #} {% if SITESUBTITLE %} {{ SITESUBTITLE }}{% endif %} {#:#} + {{ page.title }}

+
+
+ {{ page.content }} +
+{% endblock %} diff --git a/monospace/templates/pagination.html b/monospace/templates/pagination.html new file mode 100644 index 0000000..83c587a --- /dev/null +++ b/monospace/templates/pagination.html @@ -0,0 +1,15 @@ +{% if DEFAULT_PAGINATION %} +

+ {% if articles_page.has_previous() %} + {% if articles_page.previous_page_number() == 1 %} + « + {% else %} + « + {% endif %} + {% endif %} + Page {{ articles_page.number }} / {{ articles_paginator.num_pages }} + {% if articles_page.has_next() %} + » + {% endif %} +

+{% endif %} diff --git a/monospace/templates/tag.html b/monospace/templates/tag.html new file mode 100644 index 0000000..7fed3c0 --- /dev/null +++ b/monospace/templates/tag.html @@ -0,0 +1,10 @@ +{% extends "index.html" %} + +{% block title %}{{ SITENAME }} - {{ tag }}{% endblock %} +{% block content %} + +{% endblock %} diff --git a/monospace/templates/taglist.html b/monospace/templates/taglist.html new file mode 100644 index 0000000..3fabcb1 --- /dev/null +++ b/monospace/templates/taglist.html @@ -0,0 +1,3 @@ + +{% if article.tags %}

tags: {% for tag in article.tags %}{{ tag }}{% endfor %}

{% endif %} +{% if PDF_PROCESSOR %}

get the pdf

{% endif %} diff --git a/monospace/templates/tags.html b/monospace/templates/tags.html new file mode 100644 index 0000000..21434d2 --- /dev/null +++ b/monospace/templates/tags.html @@ -0,0 +1,9 @@ +{% extends "base.html" %} +{% block content %} + +
    +{% for tag, articles in tags|sort %} +
  • {{ tag }}
  • +{% endfor %} +
+{% endblock %} diff --git a/monospace/templates/translations.html b/monospace/templates/translations.html new file mode 100644 index 0000000..0079883 --- /dev/null +++ b/monospace/templates/translations.html @@ -0,0 +1,6 @@ +{% if article.translations %} +Translations: + {% for translation in article.translations %} + {{ translation.lang }} + {% endfor %} +{% endif %} diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 0000000..2a152d0 --- /dev/null +++ b/netlify.toml @@ -0,0 +1,11 @@ +[build] +command = "make publish" +publish = "output" + +[[headers]] + for = "/*" + [headers.values] + X-Frame-Options = "SAMEORIGIN" + X-Content-Type-Options = "nosniff" + Referrer-Policy = "strict-origin-when-cross-origin" + Content-Security-Policy = "default-src: 'none'; img-src: https:" diff --git a/pelicanconf.py b/pelicanconf.py new file mode 100644 index 0000000..6e955a0 --- /dev/null +++ b/pelicanconf.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- # +from __future__ import unicode_literals + +SITEURL = 'https://exon.wntrmute.net' +AUTHOR = u'kyle' +SITENAME = u'Exocortex Notes' +DESCRIPTION = 'Development log and information about the wntrmute exocortex' +THEME = 'monospace' +TIMEZONE = 'America/Los_Angeles' +DEFAULT_LANG = u'en' + +DATE_FORMAT = '%F' + +PATH = 'content' +STATIC_PATHS = ['files'] +EXTRA_PATH_METADATA = { } + +PLUGIN_PATHS = ['plugins'] +PLUGINS = ['pagehier'] + +#ARTICLE_URL = 'blog/{date:%Y}/{date:%m}/{date:%d}/{slug}/' +#ARTICLE_SAVE_AS = 'blog/{date:%Y}/{date:%m}/{date:%d}/{slug}/index.html' +PAGE_URL = '{slug}.html' +PAGE_SAVE_AS = '{slug}.html' +SLUGIFY_SOURCE = 'basename' + +def select_journal(articles, limit=5): + return [article for article in articles if 'journal' in article.tags][:limit] + +def filter_journal(articles, limit=5): + return [article for article in articles if 'journal' not in article.tags][:limit] + +JINJA_FILTERS = { + 'select_journal': select_journal, + 'filter_journal': filter_journal, +} + +# Blogroll (('name',' http://example.net/')) +LINKS = (); +DEFAULT_PAGINATION = 10 + + +# These are used in development; publishconf will override them. +RELATIVE_URLS = True +LOAD_CONTENT_CACHE = False diff --git a/plugins/pagehier/README.md b/plugins/pagehier/README.md new file mode 100644 index 0000000..e87b71a --- /dev/null +++ b/plugins/pagehier/README.md @@ -0,0 +1,75 @@ +Page Hierarchy +============== +*Author: Ahmad Khayyat ()* + +A [Pelican][1] plugin that creates a URL hierarchy for pages that +matches the filesystem hierarchy of their sources. + +For example, to have the following filesystem structure of page +sources result in the URLs listed next to each file, + +```text +└── content/pages/ # PAGE_DIR + ├── about.md # URL: pages/about/ + ├── projects.md # URL: pages/projects/ + ├── projects/ # (directory) + │ ├── p1.md # URL: pages/projects/p1/ + │ ├── p2.md # URL: pages/projects/p2/ + │ └── p2/ # (directory) + │ └── features.md # URL: pages/projects/p2/features/ + └── contact.md # URL: pages/contact/ +``` + +you can use this plugin with the following Pelican settings: + +```python +# pelicanconf.py +PAGE_URL = '{slug}/' +PAGE_SAVE_AS = '{slug}/index.html' +SLUGIFY_SOURCE = 'basename' +``` + +When generating the `url` and `save_as` attributes, the plugin +prefixes the page's `slug` by its relative path. Although the initial +`slug` is generated from the page's `title` by default, it can be +generated from the source file basename by setting the +`SLUGIFY_SOURCE` setting to `'basename'`, as shown in the settings +snippet above. The `slug` can also be set using [`PATH_METADATA`][2]. + +This plugin is compatible with [Pelican translations][3]. + +Parent and Children Pages +------------------------- +This plugin also adds three attributes to each page object: + +- `parent`: the immediate parent page. `None` if the page is + top-level. If a translated page has no parent, the default-language + parent is used. + +- `parents`: a list of all ancestor pages, starting from the top-level + ancestor. + +- `children`: a list of all immediate child pages, in no specific + order. + +These attributes can be used to generate breadcrumbs or nested +navigation menus. For example, this is a template excerpt for +breadcrumbs: + +```html + + +``` + + +[1]: http://getpelican.com/ +[2]: http://docs.getpelican.com/en/latest/settings.html#path-metadata +[3]: http://docs.getpelican.com/en/latest/settings.html#translations diff --git a/plugins/pagehier/__init__.py b/plugins/pagehier/__init__.py new file mode 100644 index 0000000..6a28b3e --- /dev/null +++ b/plugins/pagehier/__init__.py @@ -0,0 +1 @@ +from .page_hierarchy import * diff --git a/plugins/pagehier/page_hierarchy.py b/plugins/pagehier/page_hierarchy.py new file mode 100644 index 0000000..9fa6c3a --- /dev/null +++ b/plugins/pagehier/page_hierarchy.py @@ -0,0 +1,85 @@ +from pelican import signals, contents +import os.path +from copy import copy +from itertools import chain + +''' +This plugin creates a URL hierarchy for pages that matches the +directory hierarchy of their sources. +''' + +class UnexpectedException(Exception): pass + +def get_path(page, settings): + ''' Return the dirname relative to PAGE_PATHS prefix. ''' + path = os.path.split(page.get_relative_source_path())[0] + '/' + path = path.replace( os.path.sep, '/' ) + # Try to lstrip the longest prefix first + for prefix in sorted(settings['PAGE_PATHS'], key=len, reverse=True): + if not prefix.endswith('/'): prefix += '/' + if path.startswith(prefix): + return path[len(prefix):-1] + raise UnexpectedException('Page outside of PAGE_PATHS ?!?') + +def in_default_lang(page): + # page.in_default_lang property is undocumented (=unstable) interface + return page.lang == page.settings['DEFAULT_LANG'] + +def override_metadata(content_object): + if type(content_object) is not contents.Page: + return + page = content_object + path = get_path(page, page.settings) + + def _override_value(page, key): + metadata = copy(page.metadata) + # We override the slug to include the path up to the filename + metadata['slug'] = os.path.join(path, page.slug) + # We have to account for non-default language and format either, + # e.g., PAGE_SAVE_AS or PAGE_LANG_SAVE_AS + infix = '' if in_default_lang(page) else 'LANG_' + return page.settings['PAGE_' + infix + key.upper()].format(**metadata) + + for key in ('save_as', 'url'): + if not hasattr(page, 'override_' + key): + setattr(page, 'override_' + key, _override_value(page, key)) + +def set_relationships(generator): + def _all_pages(): + return chain(generator.pages, generator.translations) + + # initialize parents and children lists + for page in _all_pages(): + page.parent = None + page.parents = [] + page.children = [] + + # set immediate parents and children + for page in _all_pages(): + # Parent of /a/b/ is /a/, parent of /a/b.html is /a/ + parent_url = os.path.dirname(page.url[:-1]) + if parent_url: parent_url += '/' + for page2 in _all_pages(): + if page2.url == parent_url and page2 != page: + page.parent = page2 + page2.children.append(page) + # If no parent found, try the parent of the default language page + if not page.parent and not in_default_lang(page): + for page2 in generator.pages: + if (page.slug == page2.slug and + os.path.dirname(page.source_path) == + os.path.dirname(page2.source_path)): + # Only set the parent but not the children, obviously + page.parent = page2.parent + + # set all parents (ancestors) + for page in _all_pages(): + p = page + while p.parent: + page.parents.insert(0, p.parent) + p = p.parent + + +def register(): + signals.content_object_init.connect(override_metadata) + signals.page_generator_finalized.connect(set_relationships) diff --git a/publishconf.py b/publishconf.py new file mode 100644 index 0000000..da467eb --- /dev/null +++ b/publishconf.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +# -*coding: utf-8 -*# +from __future__ import unicode_literals + +# This file is only used if you use `make publish` or +# explicitly specify it as your config file. + +import os +import sys +sys.path.append(os.curdir) +from pelicanconf import * + +RELATIVE_URLS = False +FEED_DOMAIN = SITEURL +FEED_ATOM = "index.atom" +FEED_RSS = "index.rss" +DELETE_OUTPUT_DIRECTORY = True +FEED_USE_SUMMARY = False +RSS_FEED_SUMMARY_ONLY = False diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..738b547 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +pelican >= 3.7.1 +markdown >= 3.0.0 +invoke diff --git a/tasks.py b/tasks.py new file mode 100644 index 0000000..1c46a3a --- /dev/null +++ b/tasks.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- + +import os +import shutil +import sys +import datetime + +from invoke import task +from invoke.util import cd +from pelican.server import ComplexHTTPRequestHandler, RootedHTTPServer + +CONFIG = { + # Local path configuration (can be absolute or relative to tasks.py) + 'deploy_path': 'output', + # Remote server configuration + 'production': 'kyle@ai6ua.net:22', + 'dest_path': 'sites/ai6ua', + # Port for `serve` + 'port': 8000, +} + +@task +def clean(c): + """Remove generated files""" + if os.path.isdir(CONFIG['deploy_path']): + shutil.rmtree(CONFIG['deploy_path']) + os.makedirs(CONFIG['deploy_path']) + +@task +def build(c): + """Build local version of site""" + c.run('pelican -s pelicanconf.py') + +@task +def rebuild(c): + """`build` with the delete switch""" + c.run('pelican -d -s pelicanconf.py') + +@task +def regenerate(c): + """Automatically regenerate site upon file modification""" + c.run('pelican -r -s pelicanconf.py') + +@task +def serve(c): + """Serve site at http://localhost:8000/""" + + class AddressReuseTCPServer(RootedHTTPServer): + allow_reuse_address = True + + server = AddressReuseTCPServer( + CONFIG['deploy_path'], + ('', CONFIG['port']), + ComplexHTTPRequestHandler) + + sys.stderr.write('Serving on port {port} ...\n'.format(**CONFIG)) + server.serve_forever() + +@task +def reserve(c): + """`build`, then `serve`""" + build(c) + serve(c) + +@task +def preview(c): + """Build production version of site""" + c.run('pelican -s publishconf.py') + + +@task +def publish(c): + """Publish to production via rsync""" + c.run('pelican -s publishconf.py') + c.run( + 'rsync --delete --exclude ".DS_Store" -pthrvz -c ' + '{} {production}:{dest_path}'.format( + CONFIG['deploy_path'].rstrip('/') + '/', + **CONFIG)) + diff --git a/today b/today new file mode 100755 index 0000000..4f04e44 --- /dev/null +++ b/today @@ -0,0 +1,39 @@ +#!/bin/bash + +set -eux + +TODAY="$(date +'%Y%m%d')" +DATETIME="$(date +'%F %H:%M')" +POSTPATH="content/posts/${TODAY}.md" +PUBDATE="$(date +'%F %H:%M %Z')" +TITLE="$@" +if [ -z "$TITLE" ] +then + TITLE="${TODAY}" + SLUG="${TITLE}" +else + TITLE="$@" + SLUG="$(echo ${TITLE}|tr [A-Z] [a-z]|tr ' ' '-')" +fi + +if [ ! -e "${POSTPATH}" ] +then + cat < ${POSTPATH} +Title: ${TITLE} +Slug: ${SLUG} +Date: $(date +'%F') +Modified: $(date +'%F') +Category: +Tags: +Authors: kyle +Summary: +EOF +else + sed -i -e "s/Modified: .*$/Modified: ${PUBDATE}/" $POSTPATH +fi + + +#EDITOR="$(command -v em)" + +EDITOR="${EDITOR:-gvim}" +$EDITOR $POSTPATH