basic lektor project for inf frontpage
Getty Ritter
6 years ago
1 | *~ |
Binary diff not shown
Binary diff not shown
1 | @font-face { | |
2 | font-family: league-spartan; | |
3 | src: url("/static/leaguespartan-bold.ttf"); | |
4 | } | |
5 | ||
6 | body { | |
7 | font-family: "Arial", "Helvetica", sans-serif; | |
8 | font-size: 14pt; | |
9 | margin-left: 0px; | |
10 | margin-right: 0px; | |
11 | } | |
12 | .header { | |
13 | background-color: #444; | |
14 | color: #fff; | |
15 | width: 100%; | |
16 | margin: 0 auto; | |
17 | } | |
18 | h1 { | |
19 | padding-left: 40px; | |
20 | text-transform: uppercase; | |
21 | font-family: league-spartan; | |
22 | letter-spacing: 5px; | |
23 | height: 80px; | |
24 | position: relative; | |
25 | } | |
26 | .text { | |
27 | padding-left: 50px; | |
28 | position: absolute; | |
29 | top: 50%; | |
30 | margin-top: -15px; | |
31 | } | |
32 | .menu { | |
33 | display: inline-block; | |
34 | text-transform: uppercase; | |
35 | font-size: 16pt; | |
36 | letter-spacing: 2px; | |
37 | width: 25% | |
38 | margin-left: 20px; | |
39 | margin-right: auto; | |
40 | line-height: 140%; | |
41 | } | |
42 | .menu ul { | |
43 | list-style-type: none; | |
44 | } | |
45 | .content { | |
46 | padding: 40px; | |
47 | float: right; | |
48 | display: inline-block; | |
49 | width: 70%; | |
50 | } | |
51 | a:link { color: #bbbbbb; } | |
52 | a:hover { color: #bbbbbb; } | |
53 | a:active { color: #999999; } | |
54 | a:visited { color: #999999; } |
Binary diff not shown
1 | title: Infinite Negative Utility | |
2 | --- | |
3 | body: | |
4 | ||
5 | Hi, I'm Getty Ritter. I'm a kind of programmer-artist-writer-dilettante, and this is my mostly-tech-focused web site. | |
6 | ||
7 | Right now I live in Portland, Oregon, and work as a computer scientist and engineer. My background is primarily in computer science, with a focus on programming language theory and proof theory, and in linguistics, with a focus on historical linguistics. | |
8 | ||
9 | I also write prose—mostly [short stories](http://librarianofalexandria.com/category/stories/) and [drabbles](http://librarianofalexandria.com/category/fascicles/)—and create art in the form of [paintings](http://thefireattheshoemakersestate.tumblr.com/tagged/painting), [sketches](http://thefireattheshoemakersestate.tumblr.com/tagged/sketching), [ink drawings](http://thefireattheshoemakersestate.tumblr.com/tagged/ink-drawing), and [pixel art](http://thefireattheshoemakersestate.tumblr.com/tagged/pixel-art). I'm an experimental cook, amateur mixologist, and not a particularly good homebrewer. I speak half a dozen languages poorly and none well. Past hobbies that I'd like to get back to some day include blacksmithing, creating and maintaing bonsai, and conlang creation—but who has time? |
1 | _model: page | |
2 | --- | |
3 | title: config-ini | |
4 | --- | |
5 | body: | |
6 | ||
7 | The `config-ini` library is a Haskell library for doing elementary INI file parsing in a quick and painless way. You can find the source code [on Github](https://github.com/aisamanra/config-ini) and the full documentation for the library [on Hackage](http://hackage.haskell.org/package/config-ini). | |
8 | ||
9 | There are two ways of using the library: one of them involves a traditional monadic DSL which parses field-by-field, and the other is a powerful bidirectional DSL which allows you to use the same declarative specification to parse, serialize, and diff-minimally update INI files. | |
10 | ||
11 | Consider the following basic INI file: | |
12 | ||
13 | ~~~.ini | |
14 | [NETWORK] | |
15 | host = example.com | |
16 | port = 7878 | |
17 | ||
18 | # here is a comment | |
19 | [LOCAL] | |
20 | user = terry | |
21 | ~~~ | |
22 | ||
23 | We want to parse this into a Haskell data structure defined using this type, with its associated lenses: | |
24 | ||
25 | ~~~haskell | |
26 | ||
27 | data Config = Config | |
28 | { _cfHost :: String | |
29 | , _cfPort :: Int | |
30 | , _cfUser :: Maybe Text | |
31 | } deriving (Eq, Show) | |
32 | ||
33 | makeLenses ''Config | |
34 | ~~~ | |
35 | ||
36 | Using `config-ini`'s basic API, we can extract the fields using basic functions like `fieldOf` and `section`, which lookup and deserialize values from the INI file, and construct a `Config` value from those: | |
37 | ||
38 | ~~~haskell | |
39 | configParser :: IniParser Config | |
40 | configParser = do | |
41 | (host, port) <- section "NETWORK" $ do | |
42 | host <- fieldOf "host" string | |
43 | port <- fieldOf "port" number | |
44 | pure (host, port) | |
45 | user <- sectionMb "LOCAL" $ field "user" | |
46 | return Config | |
47 | { _cfHost = host | |
48 | , _cfPort = port | |
49 | , _cfUser = user | |
50 | } | |
51 | ~~~ | |
52 | ||
53 | In order to use `config-ini`'s bidirectional API, we instead have to use the generated lenses and associate them with _descriptions_ of how to look up those individual fields, like this: | |
54 | ||
55 | ~~~haskell | |
56 | configSpec :: IniSpec Config () | |
57 | configSpec = do | |
58 | section "NETWORK" $ do | |
59 | cfHost .= field "host" string | |
60 | cfPort .= field "port" number | |
61 | section "LOCAL" $ do | |
62 | cfUser .=? field "user" | |
63 | ~~~ | |
64 | ||
65 | This defines a specification that we can use to create a parser, but in order to actually construct a value, we need a default `Config` value to supply to it, which we pass along with the `IniSpec` to the `ini` function, which gives us a value of type `Ini`, from which we can derive a parser: | |
66 | ||
67 | ~~~haskell | |
68 | configIni :: Ini Config | |
69 | configIni = | |
70 | let defConfig = Config "localhost" 8080 Nothing | |
71 | in ini defConfig configSpec | |
72 | ||
73 | myParseIni :: Text -> Either String Config | |
74 | myParseIni t = fmap getIniValue (parseIni t configIni) | |
75 | ~~~ | |
76 | ||
77 | However, we can also _serialize_ the `Ini` value, which takes the default value and turns it into a textual INI file. We can also use the `Ini` value to parse a file into a new `Ini` value, update it, and then re-serialize: the `Ini` value will contain all of the "structural" information about the file, like whitespace and comments and ordering, which means that the update is _diff-minimal_: all unchanged values will remain in the same order, changed value will be modified in-place with retained comments, and new values will appear grouped together at the end of their sections. |
1 | _model: page | |
2 | --- | |
3 | title: cube-cotillion | |
4 | --- | |
5 | body: | |
6 | ||
7 | The `cube-cotillion` library is a heavily Scotty-inspired framework | |
8 | for writing services over SSH. | |
9 | ||
10 | ## Example | |
11 | ||
12 | This example allows anyone to authenticate, and responds to two | |
13 | commands, `greet` and `greet [name]`, with a short greeting. | |
14 | ||
15 | ~~~.haskell | |
16 | {-# LANGUAGE OverloadedStrings #-} | |
17 | ||
18 | module Main where | |
19 | ||
20 | import Data.Monoid (mconcat) | |
21 | import Network.CubeCotillion | |
22 | ||
23 | main :: IO () | |
24 | main = do | |
25 | key <- loadKey "server-keys" | |
26 | cubeCotillion 8080 key $ do | |
27 | cmd "greet" $ do | |
28 | bs "Hello, world!\n" | |
29 | cmd "greet :name" $ do | |
30 | name <- param "name" | |
31 | bs $ mconcat ["Hello, ", name, "!\n"] | |
32 | ~~~ | |
33 | ||
34 | While running this service on localhost, we can connect to and | |
35 | use it like so: | |
36 | ||
37 | ~~~ | |
38 | [gdritter@mu ~]$ ssh -p 8080 localhost greet | |
39 | Hello, world! | |
40 | [gdritter@mu ~]$ ssh -p 8080 localhost greet Eberhardt | |
41 | Hello, Eberhardt! | |
42 | ~~~ | |
43 | ||
44 | ## Why? | |
45 | ||
46 | HTTP is often used as a protocol for exposing certain kinds of | |
47 | services, but HTTP also lacks certain kinds of built-in features, | |
48 | which are often reimplemented in various different ways: for | |
49 | example, connection multiplexing, compression of conveyed | |
50 | information, and user authentication and identity. All of these | |
51 | are features trivially supported by the SSH protocol already. | |
52 | Additionally, tools for working with SSH are ubiquitous, and | |
53 | developers often already have existing SSH identities. | |
54 | ||
55 | That doesn't necessarily mean that SSH is a great protocol to | |
56 | use to build services on top of. I frankly don't _know_ if | |
57 | that would be a good idea or not! That's why `cube-cotillion` | |
58 | exists: to experiment with building these kinds of services | |
59 | in a quick and easy way. | |
60 | ||
61 | ## Why The Name? | |
62 | ||
63 | The design of the library is heavily inspired by the | |
64 | lightweight Haskell web frameworks | |
65 | [Scotty](http://hackage.haskell.org/package/scotty) and | |
66 | [Spock](http://hackage.haskell.org/package/Spock), both | |
67 | of which are named after Star Trek Characters. I figured | |
68 | I should follow suit, and choose the name of one of my | |
69 | [favorite Star Trip characters, too](https://www.youtube.com/watch?v=O2XOLoeBPEk). | |
70 |
1 | _model: page | |
2 | --- | |
3 | title: s-cargot | |
4 | --- | |
5 | body: | |
6 | ||
7 | S-Cargot is a library for parsing and emitting S-expressions, designed to be flexible, customizable, and extensible. Different uses of S-expressions often understand subtly different variations on what an S-expression is. The goal of S-Cargot is to create several reusable components that can be repurposed to nearly any S-expression variant. | |
8 | ||
9 | The source code for S-Cargot is [on Github](https://github.com/aisamanra/s-cargot) and the full library documentation is [on Hackage](http://hackage.haskell.org/package/s-cargot). | |
10 | ||
11 | The library is _very_ flexible, and is designed to accommodate defining _families_ of s-expression languages rather than assuming that all s-expression formats share much beyond their parenthesis-based syntax. Here, is a large, verbose example which a minimal arithmetic language with both decimal and hexadecimal numeric literals and then uses `s-cargot` to derive both serializers and pretty-printers for the language: | |
12 | ||
13 | ~~~~haskell | |
14 | {-# LANGUAGE OverloadedStrings #-} | |
15 | ||
16 | module SCargotExample where | |
17 | ||
18 | import Control.Applicative ((<|>)) | |
19 | import Data.Char (isDigit) | |
20 | import Data.SCargot | |
21 | import Data.SCargot.Repr.Basic | |
22 | import Data.Text (Text, pack) | |
23 | import Numeric (readHex) | |
24 | import Text.Parsec (anyChar, char, digit, many1, manyTill, newline, satisfy, string) | |
25 | import Text.Parsec.Text (Parser) | |
26 | ||
27 | -- Our operators are going to represent addition, subtraction, or | |
28 | -- multiplication | |
29 | data Op = Add | Sub | Mul deriving (Eq, Show) | |
30 | ||
31 | -- The atoms of our language are either one of the aforementioned | |
32 | -- operators, or positive integers | |
33 | data Atom = AOp Op | ANum Int deriving (Eq, Show) | |
34 | ||
35 | -- Once parsed, our language will consist of the applications of | |
36 | -- binary operators with literal integers at the leaves | |
37 | data Expr = EOp Op Expr Expr | ENum Int deriving (Eq, Show) | |
38 | ||
39 | -- Conversions to and from our Expr type | |
40 | toExpr :: SExpr Atom -> Either String Expr | |
41 | toExpr (A (AOp op) ::: l ::: r ::: Nil) = EOp op <$> toExpr l <*> toExpr r | |
42 | toExpr (A (ANum n)) = pure (ENum n) | |
43 | toExpr sexpr = Left ("Unable to parse expression: " ++ show sexpr) | |
44 | ||
45 | fromExpr :: Expr -> SExpr Atom | |
46 | fromExpr (EOp op l r) = A (AOp op) ::: fromExpr l ::: fromExpr r ::: Nil | |
47 | fromExpr (ENum n) = A (ANum n) ::: Nil | |
48 | ||
49 | -- Parser and serializer for our Atom type | |
50 | pAtom :: Parser Atom | |
51 | pAtom = ((ANum . read) <$> many1 digit) | |
52 | <|> (char '+' *> pure (AOp Add)) | |
53 | <|> (char '-' *> pure (AOp Sub)) | |
54 | <|> (char '*' *> pure (AOp Mul)) | |
55 | ||
56 | sAtom :: Atom -> Text | |
57 | sAtom (AOp Add) = "+" | |
58 | sAtom (AOp Sub) = "-" | |
59 | sAtom (AOp Mul) = "*" | |
60 | sAtom (ANum n) = pack (show n) | |
61 | ||
62 | -- Our comment syntax is going to be Haskell-like: | |
63 | hsComment :: Parser () | |
64 | hsComment = string "--" >> manyTill anyChar newline >> return () | |
65 | ||
66 | -- Our custom reader macro: grab the parse stream and read a | |
67 | -- hexadecimal number from it: | |
68 | hexReader :: Reader Atom | |
69 | hexReader _ = (A . ANum . rd) <$> many1 (satisfy isHexDigit) | |
70 | where isHexDigit c = isDigit c || c `elem` hexChars | |
71 | rd = fst . head . readHex | |
72 | hexChars :: String | |
73 | hexChars = "AaBbCcDdEeFf" | |
74 | ||
75 | -- Our final s-expression parser and printer: | |
76 | myLangParser :: SExprParser Atom Expr | |
77 | myLangParser | |
78 | = setComment hsComment -- set comment syntax to be Haskell-style | |
79 | $ addReader '#' hexReader -- add hex reader | |
80 | $ setCarrier toExpr -- convert final repr to Expr | |
81 | $ mkParser pAtom -- create spec with Atom type | |
82 | ||
83 | mkLangPrinter :: SExprPrinter Atom Expr | |
84 | mkLangPrinter | |
85 | = setFromCarrier fromExpr | |
86 | $ setIndentStrategy (const Align) | |
87 | $ basicPrint sAtom | |
88 | ||
89 | >>> decode myLangParser "(+ (* 2 20) 10) (* 10 10)" | |
90 | [EOp Add (EOp Mul (ENum 2) (ENum 20)) (ENum 10),EOp Mul (ENum 10) (ENum 10)] | |
91 | ~~~~ | |
92 |
1 | _model: page | |
2 | --- | |
3 | title: telml | |
4 | --- | |
5 | body: | |
6 | ||
7 | **TeLML** (short for _TeX-Like Markup Language_) is a markup language I created for personal projects, specifically to serve as a lightweight but extensible markup language. I've implemented it in several languages, but the current reference implementation is in Haskell, and can be found [on Gitub](https://github.com/aisamanra/telml). In addition to using it locally as a markup language for projects-in-progress, it is also the markup language that powers [What Happens When Computer](https://what.happens.when.computer/), my short-posts-on-technical-topics blog. | |
8 | ||
9 | The core of TeLML is the _tag_, which looks mostly like a LaTeX command invocation (e.g. `\em{foo}`), with the following major differences: | |
10 | - All tags in TeLML have a _mandatory_ payload, which is surrounded in curly braces. Thus, `\br` is not a valid TeLML tag, but `\br{}` is. | |
11 | - All tags in TeLML can have multiple "arguments" separated by vertical bars, so a tag with multiple values might look like `\link{https://duckduckgo.com/|this}`. | |
12 | - All special characters can be escaped with another backslash, so that `\\br\{\}` is the TeLML transcription of the string `\br{}` rather than a nullary tag. | |
13 | ||
14 | The TeLML format is split into two parts: the _core_ format, which only defines the data model and how to parse and serialize it, and the _markup_ format, which adds a set of HTML-like basic tags on top (including inline tags like `\em{...}` and `\strong{...}` as well as block-level tags like `\blockquote{...}` and `\code{...}`.) The libraries linked to above also allow uses that are _extensible_, in which new bespoke tags can be added for particular purposes. | |
15 | ||
16 | For a more thorough description, see the following links: | |
17 | - [A description of the exact grammar and data model used in TeLML](https://git.gdritter.com/telml/blob/master/telml/README.md) | |
18 | - [The TeLML markup module](https://git.gdritter.com/telml/blob/master/telml-markup/README.md) that defines the tag semantics as well as an API for adding new custom tags | |
19 | - [A document giving short rationale for why I wrote this instead of using another markup language](https://git.gdritter.com/telml/blob/master/README.md) |
1 | _model: page | |
2 | --- | |
3 | title: Bricoleur | |
4 | --- | |
5 | body: | |
6 | ||
7 | The `bricoleur` tool is an unfinished personal tool designed for drafting blog posts that include source code. | |
8 | ||
9 | The fundamental problem that `bricoleur` aims to solve is that it's easy for executable source code to get out of sync with a blog-post-in-progress. There's a temptation (for me, at least) to edit variable names or modify source layout of source code snippets as I write a blog post, and if I forget to update a variable name somewhere or make a typo, it's possible I've produced a blog post that includes invalid source code. I sometimes avoid this by always copy/pasting from working source examples, but that can be tedious. | |
10 | ||
11 | With `bricoleur`, I start writing my post with placeholders, which are indicated with guillemets. The post can be in any format, but for my purposes here, let's assume it's in Markdown. I can write a post like this, say, in a file called `post.md`: | |
12 | ||
13 | `````` | |
14 | The hello world program in Python looks like this: | |
15 | ```python | |
16 | «hello» | |
17 | ``` | |
18 | `````` | |
19 | ||
20 | I can then write, say in `main.py`, the actual source code I wanted to include: | |
21 | ||
22 | ```python | |
23 | print("Hello, world!") | |
24 | ``` | |
25 | ||
26 | I can then tie them together with a "bricoleur" file, which is conventionally just named `bricoleur`: | |
27 | ||
28 | ``` | |
29 | (document | |
30 | # this tells us that we're assembling "post.md" | |
31 | "post.md" | |
32 | # and then we define the fragments we care about | |
33 | { | |
34 | # this fragment's name is "hello" | |
35 | name "hello" | |
36 | # to test this fragment, we run "python main.py" | |
37 | cmd [ "python main.py" ] | |
38 | # and this fragment should be replaced by the contents | |
39 | # the file "main.py" | |
40 | expose (file "main.py") | |
41 | } | |
42 | ) | |
43 | ``` | |
44 | ||
45 | Once I've done this, I can use the `bricoleur` tool in two ways: for one, I can run `bricoleur test` and it'll execute the relevant commands and tell me whether they succeeded, and for another, I can run `bricoleur splice` and it'll stitch the contents of the source file at the relevant place in the original, producing the output: | |
46 | ||
47 | `````` | |
48 | The hello world program in Python looks like this: | |
49 | ```python | |
50 | print("Hello, world!") | |
51 | ``` | |
52 | `````` | |
53 | ||
54 | I can do more than this: for example, instead of exposing the entirety of a file, I can indicate particular chunks of the file via special comments, and I can expose multiple files, or even have multiple "subprojects" that each get tested individually in different ways. The [README file](https://git.gdritter.com/bricoleur/blob/master/README.md) has some more examples. | |
55 | ||
56 | I haven't properly released this tool yet, and thus make no guarantees about it, but if you're interested in trying it out, you can find the source code [on my personal git server](https://git.gdritter.com/bricoleur/), or clone the repo with | |
57 | ||
58 | ``` | |
59 | $ git clone https://git.gdritter.com/bricoleur/ | |
60 | ``` | |
61 | ||
62 | This will also require a copy of the [`adnot`](https://git.gdritter.com/adnot/) library. |
1 | title: Projects | |
2 | --- | |
3 | body: | |
4 | ||
5 | This is a list of the projects: | |
6 | ||
7 | * Project 1 | |
8 | * Project 2 | |
9 | * Project 3 |
1 | _model: page | |
2 | --- | |
3 | title: Matterhorn | |
4 | --- | |
5 | body: I haven't been contributing as actively recently, but I was one of the core developers and maintainers of the [Matterhorn](https://github.com/matterhorn-chat/matterhorn) chat client, which is a rich terminal-based chat client intended for the [Mattermost](https://mattermost.com/) chat system. |
Binary diff not shown
1 | [model] | |
2 | name = Page | |
3 | label = {{ this.title }} | |
4 | ||
5 | [fields.title] | |
6 | label = Title | |
7 | type = string | |
8 | ||
9 | [fields.body] | |
10 | label = Body | |
11 | type = markdown |
1 | <!doctype html> | |
2 | <meta charset="utf-8"> | |
3 | <link rel="stylesheet" href="{{ '/static/style.css'|url }}"> | |
4 | <title>Infinite Negative Utility: {% block title %}index{% endblock %}</title> | |
5 | ||
6 | <style type="text/css" > | |
7 | </style> | |
8 | ||
9 | <body> | |
10 | <div class="header"><h1><img src="/static/inu-logo-small.png"/><span class="text" >infinite negative utility</span></h1></div> | |
11 | <div > | |
12 | <nav class="menu"> | |
13 | <ul class="nav navbar-nav"> | |
14 | <li><a href="/">home</a></li> | |
15 | <li>projects</li> | |
16 | <ul> | |
17 | <li><a href="/projects/bricoleur">bricoleur</a></li> | |
18 | <li><a href="/projects/matterhorn">matterhorn</a></li> | |
19 | </ul> | |
20 | <li>libraries</li> | |
21 | <ul> | |
22 | <li><a href="/libraries/s-cargot">s-cargot</a></li> | |
23 | <li><a href="/libraries/config-ini">config-ini</a></li> | |
24 | <li><a href="/libraries/cube-cotillion">cube cotillion</a></li> | |
25 | <li><a href="/libraries/telml">telml</a></li> | |
26 | </ul> | |
27 | <li>art & writing</li> | |
28 | <ul> | |
29 | <li><a href="https://thefireattheshoemakersestate.tumblr.com/">art</a></li> | |
30 | <li><a href="http://librarianofalexandria.com/">prose</a></li> | |
31 | <li><a href="http://blog.infinitenegativeutility.com/">technical</a></li> | |
32 | <li><a href="http://what.happens.when.computer/">informative</a></li> | |
33 | <li><a href="https://twitter.com/aisamanra">tweets</a></li> | |
34 | <li><a href="https://mastodon.social/users/keweddji">toots</a></li> | |
35 | </ul> | |
36 | <li><a href="http://gdritter.com/resume.pdf">résumé</a></li> | |
37 | <li><a href="/about">about</a></li> | |
38 | </ul> | |
39 | </nav> | |
40 | <div class="content"> | |
41 | {% block body %}{% endblock %} | |
42 | </div> | |
43 | </div> | |
44 | </body> |
1 | {% macro render_pagination(pagination) %} | |
2 | <div class="pagination"> | |
3 | {% if pagination.has_prev %} | |
4 | <a href="{{ pagination.prev|url }}">« Previous</a> | |
5 | {% else %} | |
6 | <span class="disabled">« Previous</span> | |
7 | {% endif %} | |
8 | | {{ pagination.page }} | | |
9 | {% if pagination.has_next %} | |
10 | <a href="{{ pagination.next|url }}">Next »</a> | |
11 | {% else %} | |
12 | <span class="disabled">Next »</span> | |
13 | {% endif %} | |
14 | </div> | |
15 | {% endmacro %} |