Initial import.
|
@ -0,0 +1,2 @@
|
||||||
|
__pycache__
|
||||||
|
output
|
|
@ -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
|
After Width: | Height: | Size: 326 KiB |
After Width: | Height: | Size: 108 KiB |
After Width: | Height: | Size: 326 KiB |
After Width: | Height: | Size: 108 KiB |
After Width: | Height: | Size: 83 KiB |
After Width: | Height: | Size: 112 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 52 KiB |
After Width: | Height: | Size: 83 KiB |
|
@ -0,0 +1,4 @@
|
||||||
|
Title: About
|
||||||
|
Slug: about
|
||||||
|
|
||||||
|
This notebook covers a project to build my own knowledge management system.
|
|
@ -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
|
|
@ -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)
|
|
@ -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.
|
|
@ -0,0 +1,4 @@
|
||||||
|
Title: Pages
|
||||||
|
|
||||||
|
* [Historical context](/historical/)
|
||||||
|
* [Design docs](/specs/)
|
|
@ -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.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Title: Design docs
|
||||||
|
Tags: specs
|
||||||
|
|
||||||
|
* [Top-level functional spec](/specs/functional.html)
|
|
@ -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
|
||||||
|
}
|
||||||
|
```
|
|
@ -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
|
|
@ -0,0 +1,13 @@
|
||||||
|
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||||
|
Version 2, December 2004
|
||||||
|
|
||||||
|
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||||
|
|
||||||
|
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.
|
|
@ -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!
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<section id="content" class="body">
|
||||||
|
<h1>Archives for {{ SITENAME }}</h1>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{% for article in dates %}
|
||||||
|
<li>{{ article.date|strftime('%A, %Y %B %-d') }}: <a href='{{ SITEURL }}/{{ article.url }}'>{{ article.title }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,31 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}{{ super() }} : {{ article.title }}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<article class="h-entry">
|
||||||
|
<header>
|
||||||
|
<h1><a href="{{ SITEURL }}" id="site-title"> {#{{ SITENAME }}#} {% if SITESUBTITLE %} <strong>{{ SITESUBTITLE }}</strong>{% endif %}</a> {#:#}
|
||||||
|
<a class="u-url p-name" href="{{ SITEURL }}/{{ article.url }}" id="page-title">{{ article.title }}</a></h1>
|
||||||
|
{% if not HIDE_DATE %}<time class="dt-published" datetime="{{ article.date.isoformat() }}">{{ article.locale_date }}</time>{% endif %}
|
||||||
|
</header>
|
||||||
|
<div class="e-content">
|
||||||
|
{{ article.content }}
|
||||||
|
</div>
|
||||||
|
{% if article.tags %}
|
||||||
|
<hr />
|
||||||
|
<div>
|
||||||
|
Tags:
|
||||||
|
{% for tag in article.tags %}
|
||||||
|
<span itemprop="keywords">
|
||||||
|
<a href="{{ SITEURL }}/{{ tag.url }}" rel="tag">{{ tag }}</a>{% if not loop.last %}, {% endif %}
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div style="display:none" class="h-card">
|
||||||
|
<img class="u-photo" src="/files/images/me.jpg" alt="A photo of the author." />
|
||||||
|
<p class="p-name">Kyle Isom</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class
|
||||||
|
</article>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,2 @@
|
||||||
|
{% extends "index.html" %}
|
||||||
|
{% block title %}{{ SITENAME }} - {{ author }}{% endblock %}
|
|
@ -0,0 +1,425 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>{% block title %}{{ SITENAME }}{%endblock%}</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
|
<link href="{{ SITEURL }}/{{ FEED_ATOM }}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} ATOM Feed" />
|
||||||
|
<link href="{{ SITEURL }}/{{ FEED_RSS }}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} RSS Feed" />
|
||||||
|
<style>
|
||||||
|
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;
|
||||||
|
max-width: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="wrap" style="width:900px">
|
||||||
|
<div id="container" style="width:600px">
|
||||||
|
<div id="sidebar">
|
||||||
|
<h1><a href="{{ SITEURL }} " title="title">{{ SITENAME }}</a></h1>
|
||||||
|
<span class="description">{{ DESCRIPTION }} </span>
|
||||||
|
<a href="/">Home</a> |
|
||||||
|
<a href="/about.html">About</a> |
|
||||||
|
<a href="/specs/">Specs</a>|
|
||||||
|
<a href="/archives.html">Archives</a> |
|
||||||
|
<a href="/tags.html">Tags</a> |
|
||||||
|
<a href="/ls.html">Pages</a> |
|
||||||
|
<span class="feed">Feeds: <a href="/index.rss">RSS</a> | <a href="/index.atom">Atom</a></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr /><br />
|
||||||
|
|
||||||
|
<div class="entry">
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="footer">
|
||||||
|
<div id="credits">
|
||||||
|
<span>Adapted from <a href="http://wordpress.org/themes/monospace">Monospace</a> || Created with <a href="">Pelican</a></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,8 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<ul>
|
||||||
|
{% for category, articles in categories %}
|
||||||
|
<li><a href="{{ SITEURL }}/{{ category.url }}">{{ category }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}{{ SITENAME }} - {{ category }}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>{{ category }}</h1>
|
||||||
|
<ul>
|
||||||
|
{% for article in articles %}
|
||||||
|
<li><a href="{{ SITEURL }}"/{{ article.url }}">{{ article.title }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,33 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content_title %}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<h3>wntrmute :: exocortex</h3>
|
||||||
|
|
||||||
|
<p>This is a project to build an exocortex. It is a dump from my current
|
||||||
|
system into a format suitable for public sharing.</p>
|
||||||
|
|
||||||
|
{% if articles %}
|
||||||
|
<h3>Recent posts</h3>
|
||||||
|
<ul>
|
||||||
|
{% for article in dates|filter_journal %}
|
||||||
|
<li>{{ article.date|strftime('%Y-%m-%d') }}: <a href='{{ SITEURL }}/{{ article.url }}'>{{ article.title }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Recent journal entries</h3>
|
||||||
|
<p>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.</p>
|
||||||
|
<ul>
|
||||||
|
{% for article in dates|select_journal(limit=3) %}
|
||||||
|
<li>{{ article.date|strftime('%Y-%m-%d') }}: <a href='{{ SITEURL }}/{{ article.url }}'>{{ article.title }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<h3>Select pages</h3>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/specs/functional.html">Functional spec</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock content %}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}{{ page.title }}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<header>
|
||||||
|
<h1><a href="{{ SITEURL }}" id="site-title">{# {{ SITENAME }} #} {% if SITESUBTITLE %} <strong>{{ SITESUBTITLE }}</strong>{% endif %}</a> {#:#}
|
||||||
|
<a href="{{ SITEURL }}/{{ page.url }}" id="page-title">{{ page.title }}</a></h1>
|
||||||
|
</header>
|
||||||
|
<article>
|
||||||
|
{{ page.content }}
|
||||||
|
</article>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,15 @@
|
||||||
|
{% if DEFAULT_PAGINATION %}
|
||||||
|
<p class="paginator">
|
||||||
|
{% if articles_page.has_previous() %}
|
||||||
|
{% if articles_page.previous_page_number() == 1 %}
|
||||||
|
<a href="{{ SITEURL }}/{{ page_name }}.html">«</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ SITEURL }}/{{ page_name }}{{ articles_page.previous_page_number() }}.html">«</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
Page {{ articles_page.number }} / {{ articles_paginator.num_pages }}
|
||||||
|
{% if articles_page.has_next() %}
|
||||||
|
<a href="{{ SITEURL }}/{{ page_name }}{{ articles_page.next_page_number() }}.html">»</a>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{% extends "index.html" %}
|
||||||
|
<!-- tag.html -->
|
||||||
|
{% block title %}{{ SITENAME }} - {{ tag }}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<ul>
|
||||||
|
{% for article in articles %}
|
||||||
|
<li><a href = "{{ SITEURL }}/{{ article.url }}">{{ article.title }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,3 @@
|
||||||
|
<!-- taglist.html -->
|
||||||
|
{% if article.tags %}<p>tags: {% for tag in article.tags %}<a href="{{ SITEURL }}/{{ tag.url }}">{{ tag }}</a>{% endfor %}</p>{% endif %}
|
||||||
|
{% if PDF_PROCESSOR %}<p><a href="{{ SITEURL }}/pdf/{{ article.slug }}.pdf">get the pdf</a></p>{% endif %}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<!-- tags.html -->
|
||||||
|
<ul>
|
||||||
|
{% for tag, articles in tags|sort %}
|
||||||
|
<li><a href="{{ SITEURL }}/{{ tag.url }}">{{ tag }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{% if article.translations %}
|
||||||
|
Translations:
|
||||||
|
{% for translation in article.translations %}
|
||||||
|
<a href="{{ SITEURL }}/{{ translation.url }}">{{ translation.lang }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
|
@ -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:"
|
|
@ -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
|
|
@ -0,0 +1,75 @@
|
||||||
|
Page Hierarchy
|
||||||
|
==============
|
||||||
|
*Author: Ahmad Khayyat (<akhayyat@gmail.com>)*
|
||||||
|
|
||||||
|
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
|
||||||
|
<ul class="breadcrumb">
|
||||||
|
<li><a href="{{ SITEURL }}/" title="{{ SITENAME }}">
|
||||||
|
<i class="fa fa-home fa-lg"></i>
|
||||||
|
</a></li>
|
||||||
|
{% for parent in page.parents %}
|
||||||
|
<li><a href="{{ SITEURL }}/{{ parent.url }}">{{ parent.title }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
<li class="active">{{ page.title }}</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
[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
|
|
@ -0,0 +1 @@
|
||||||
|
from .page_hierarchy import *
|
|
@ -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)
|
|
@ -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
|
|
@ -0,0 +1,3 @@
|
||||||
|
pelican >= 3.7.1
|
||||||
|
markdown >= 3.0.0
|
||||||
|
invoke
|
|
@ -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))
|
||||||
|
|
|
@ -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 <<EOF > ${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
|