Initial import.

This commit is contained in:
Kyle Isom 2022-02-23 22:55:41 -08:00
commit 41a42c7a59
48 changed files with 1876 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
__pycache__
output

75
Makefile Normal file
View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

BIN
content/files/exo-arch.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

4
content/pages/about.md Normal file
View File

@ -0,0 +1,4 @@
Title: About
Slug: about
This notebook covers a project to build my own knowledge management system.

View File

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

View File

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

View File

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

4
content/pages/ls.md Normal file
View File

@ -0,0 +1,4 @@
Title: Pages
* [Historical context](/historical/)
* [Design docs](/specs/)

View File

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

View File

@ -0,0 +1,4 @@
Title: Design docs
Tags: specs
* [Top-level functional spec](/specs/functional.html)

32
content/posts/20220223.md Normal file
View File

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

13
cpimg Executable file
View File

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

13
monospace/LICENSE Normal file
View File

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

18
monospace/README.md Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,2 @@
{% extends "index.html" %}
{% block title %}{{ SITENAME }} - {{ author }}{% endblock %}

View File

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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">&laquo;</a>
{% else %}
<a href="{{ SITEURL }}/{{ page_name }}{{ articles_page.previous_page_number() }}.html">&laquo;</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">&raquo;</a>
{% endif %}
</p>
{% endif %}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,6 @@
{% if article.translations %}
Translations:
{% for translation in article.translations %}
<a href="{{ SITEURL }}/{{ translation.url }}">{{ translation.lang }}</a>
{% endfor %}
{% endif %}

11
netlify.toml Normal file
View File

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

46
pelicanconf.py Normal file
View File

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

View File

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

View File

@ -0,0 +1 @@
from .page_hierarchy import *

View File

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

19
publishconf.py Normal file
View File

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

3
requirements.txt Normal file
View File

@ -0,0 +1,3 @@
pelican >= 3.7.1
markdown >= 3.0.0
invoke

80
tasks.py Normal file
View File

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

39
today Executable file
View File

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