Compare commits
16 Commits
6afa6d55d4
...
books
Author | SHA1 | Date | |
---|---|---|---|
2e9a9155f8 | |||
1ab9c3af46 | |||
b93aab5742 | |||
7557f5041c | |||
e2a213b08d | |||
91cea649c9 | |||
5661770508 | |||
4120b2fff5 | |||
213442ac0f | |||
f355516c1d | |||
c348b2c834 | |||
9867ecdbb1 | |||
73a237aec8 | |||
2db7c774a2 | |||
4e2d6342b4 | |||
06778c219a |
5
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
_site
|
||||
_cache
|
||||
_site/
|
||||
_cache/
|
||||
dist/
|
||||
|
30
LICENSE
Normal file
@ -0,0 +1,30 @@
|
||||
Copyright (c) 2018, Mats Rauhala
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* Neither the name of Mats Rauhala nor the names of other
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -1,29 +0,0 @@
|
||||
---
|
||||
title: About
|
||||
---
|
||||
|
||||

|
||||
|
||||
I'm a software developer from southern Finland. I'm currently working as a
|
||||
Haskell developer at Relex Oy involved in an internal tool. I also have
|
||||
experience with systems integrations and backend web development.
|
||||
|
||||
I'm also an aviation enthusiast. I have a glider pilots license and a touring
|
||||
motor glider pilots license (LAPL(S)+TMG).
|
||||
|
||||
My [GPG key](./resources/2104943D6033C.txt)
|
||||
|
||||
### Notable experience and interests
|
||||
|
||||
- **Haskell**
|
||||
- Working on an internal Haskell based tool at Relex Oy
|
||||
- Some contributions to [darcshub](https://hub.darcs.net/)
|
||||
- Some contributions to [xmonad](https://github.com/xmonad)
|
||||
- Dozens of personal projects, most of which ended up being just tests for
|
||||
different libraries, techniques or algorithms.
|
||||
- **Systems integrations**
|
||||
- Working as a consultant from Avoltus Oy to different companies using
|
||||
[Mulesoft](https://developer.mulesoft.com/). Systems include webshops,
|
||||
different databases, crms and erps.
|
||||
- **Java**
|
||||
- Bunch of different smaller projects while working at Avoltus Oy
|
2
attributions.md
Normal file
@ -0,0 +1,2 @@
|
||||
- Book by rivercon from the Noun Project
|
||||
- [Git logo](https://git-scm.com/downloads/logos) by Jason Long
|
7
books.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<books>
|
||||
<book>
|
||||
<date>2019-05-28</date>
|
||||
<title>Uppo-Nallen talviturkki</title>
|
||||
<status>read</status>
|
||||
</book>
|
||||
</books>
|
5
books/2014-07-10-pandoras-star.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
title: Uppo-Nalle ja Nukku-Ukko
|
||||
status: reading
|
||||
---
|
||||
|
4
books/2016-11-04-the-fractal-prince.md
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: The Fractal Prince
|
||||
status: reading
|
||||
---
|
4
books/2019-05-28-nukku-ukko.md
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: Uppo-Nalle ja Nukku-Ukko
|
||||
status: reading
|
||||
---
|
5
books/2019-05-28-upponallen-talviturkki.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
title: Uppo-Nallen talviturkki
|
||||
status: read
|
||||
---
|
||||
|
@ -51,6 +51,15 @@ article .header {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
display: inline-flex;
|
||||
align-self: center;
|
||||
top: .40em;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media (max-width: 319px) {
|
||||
body {
|
||||
width: 90%;
|
||||
|
18
css/highlight.css
Normal file
@ -0,0 +1,18 @@
|
||||
/* Generated by pandoc. */
|
||||
table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode, table.sourceCode pre
|
||||
{ margin: 0; padding: 0; border: 0; vertical-align: baseline; border: none; }
|
||||
td.lineNumbers { border-right: 1px solid #AAAAAA; text-align: right; color: #AAAAAA; padding-right: 5px; padding-left: 5px; }
|
||||
td.sourceCode { padding-left: 5px; }
|
||||
.sourceCode span.kw { color: #007020; font-weight: bold; }
|
||||
.sourceCode span.dt { color: #902000; }
|
||||
.sourceCode span.dv { color: #40a070; }
|
||||
.sourceCode span.bn { color: #40a070; }
|
||||
.sourceCode span.fl { color: #40a070; }
|
||||
.sourceCode span.ch { color: #4070a0; }
|
||||
.sourceCode span.st { color: #4070a0; }
|
||||
.sourceCode span.co { color: #60a0b0; font-style: italic; }
|
||||
.sourceCode span.ot { color: #007020; }
|
||||
.sourceCode span.al { color: red; font-weight: bold; }
|
||||
.sourceCode span.fu { color: #06287e; }
|
||||
.sourceCode span.re { }
|
||||
.sourceCode span.er { color: red; font-weight: bold; }
|
@ -1,3 +1,3 @@
|
||||
{ haskellPackages }:
|
||||
{ haskellPackages, haskell }:
|
||||
|
||||
haskellPackages.callCabal2nix "site" ./. {}
|
||||
haskell.lib.disableSharedExecutables (haskellPackages.callCabal2nix "site" ./. {})
|
||||
|
BIN
images/Git-Icon-Black.png
Normal file
After Width: | Height: | Size: 684 B |
BIN
images/Git-Icon-Black_orig.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
1
images/book_read.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg id="read" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 56 70" style="enable-background:new 0 0 56 56;" xml:space="preserve"><path d="M48.2026367,4.003418c0-0.5522461-0.4477539-1-1-1H17.7766724C17.7706909,3.0032959,17.7657471,3,17.7597656,3 s-0.0109253,0.0032959-0.0169067,0.003418h-4.8380737c-2.8188477,0-5.1118164,2.4799805-5.1118164,5.5288086v38.4257812 c0,0.0093994,0.005127,0.0172119,0.0053711,0.0265503C7.8088379,50.3031616,10.303833,53,13.3701172,53h33.8369141 c0.3862305,0,0.737793-0.2226562,0.9033203-0.5712891c0.1655273-0.3491211,0.1152344-0.762207-0.1289062-1.0615234 c-2.0512695-2.5102539-2.0512695-6.296875,0-8.8071289c0.2003784-0.2456665,0.262085-0.5665894,0.1891479-0.8672485 c0.0089111-0.053772,0.0320435-0.102356,0.0320435-0.1586304V4.003418z M46.2026367,40.9272461H18.7597656V5.003418h27.4428711 V40.9272461z M12.9047852,5.003418h3.8549805v35.9238281h-3.3896484c-1.3610229,0-2.6083374,0.5328979-3.5771484,1.4140625 V8.5322266C9.7929688,6.5864258,11.1889648,5.003418,12.9047852,5.003418z M45.3833008,51H13.3701172 c-1.9702148,0-3.5727539-1.8105469-3.5727539-4.0361328s1.6025391-4.0366211,3.5727539-4.0366211h32.0131836 C44.1420898,45.4414062,44.1420898,48.4858398,45.3833008,51z"/></svg>
|
After Width: | Height: | Size: 1.3 KiB |
1
images/book_reading.svg
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
images/git_16.png
Normal file
After Width: | Height: | Size: 485 B |
BIN
images/git_32.png
Normal file
After Width: | Height: | Size: 684 B |
BIN
images/profile_grayscale.jpg
Normal file
After Width: | Height: | Size: 9.2 KiB |
@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Home
|
||||
---
|
||||
|
||||
<h2>Welcome</h2>
|
||||
|
||||
<p>Not much here yet. See the top bar</p>
|
31
index.markdown
Normal file
@ -0,0 +1,31 @@
|
||||
---
|
||||
title: Mats Rauhala
|
||||
---
|
||||
|
||||

|
||||
|
||||
I'm a software developer from southern Finland. I'm currently working as a
|
||||
Haskell developer at Relex Oy involved in an internal tool. I also have
|
||||
experience with systems integrations and backend web development.
|
||||
|
||||
I'm also an aviation enthusiast. I have a glider pilots license and a touring
|
||||
motor glider pilots license (LAPL(S)+TMG).
|
||||
|
||||
My [GPG key](./resources/2104943D6033C.txt)
|
||||
|
||||
### Notable experience and interests
|
||||
|
||||
- **Haskell**
|
||||
- Working on an internal Haskell based tool at Relex Oy
|
||||
- Some contributions to [darcshub](https://hub.darcs.net/)
|
||||
- Some contributions to [xmonad](https://github.com/xmonad)
|
||||
- My [Github profile](https://github.com/MasseR)
|
||||
- My [Gitea profile](https://git.rauhala.info/MasseR)
|
||||
- **Systems integrations**
|
||||
- Working as a consultant from Avoltus Oy to different companies using
|
||||
[Mulesoft](https://developer.mulesoft.com/). Systems include webshops,
|
||||
different databases, CRMs and ERPs.
|
||||
- **Java**
|
||||
- Bunch of different smaller projects while working at Avoltus Oy.
|
||||
- **Other**
|
||||
- I was involved in creating an email solicit platform.
|
148
posts/guides/demobot.md
Normal file
@ -0,0 +1,148 @@
|
||||
---
|
||||
title: Functional architecture Pt. 1
|
||||
date: 2018-12-25
|
||||
---
|
||||
|
||||
|
||||
I'm lucky enough to work with Haskell professionally which gives me some view
|
||||
to good and maintainable real world architecture. In my opinion, one of the
|
||||
biggest contributing factors to how your general architecture is defined, is
|
||||
determined by the base application monad stack you are using.
|
||||
|
||||
Our actual product is mostly in the regular `LoggingT (ReaderT app IO)` base
|
||||
monad with whatever style you would imagine with that base monad in place. It's
|
||||
not entirely consistent, but close enough.
|
||||
|
||||
With all the talk about just having `IO`, `ReaderT app IO`, free monads or
|
||||
tagless final monads, I thought of trying different styles. For this post I'm
|
||||
focusing on the tagless final since it's most interesting for me right now.
|
||||
|
||||
`IO`
|
||||
|
||||
: The most basic style. This is pretty much only suitable for the most basic
|
||||
of needs.
|
||||
|
||||
`ReaderT app IO`
|
||||
|
||||
: How we mostly define the base monad. This is a really good way of doing
|
||||
things, it gives you a lot of leeway on how you can define the rest of your
|
||||
application.
|
||||
|
||||
`Free monads`
|
||||
|
||||
: Free monads are a way of having a small constrained DSL or monad stack for
|
||||
defining your application. By constraining the user, you are also reducing the
|
||||
area for bugs. There is also some possibility for introspection, but usually
|
||||
this isn't a usable feature. Also since free monad applications need the full
|
||||
AST, they're quite a bit slower than the other solutions.
|
||||
|
||||
`Tagless final`
|
||||
|
||||
: This is something I'm the least familiar with. If I have understood
|
||||
correctly, free monads and tagless final are more or less equivalent solutions
|
||||
in their power, but in tagless final you aren't creating the AST anywhere,
|
||||
which also means that you aren't paying for it either.
|
||||
|
||||
That out of the way, I had a small project idea for a bot that's easy to
|
||||
contribute to, difficult to make errors and easy to reason about. The project
|
||||
is at most a proof-of-concept and most definitely not production quality.
|
||||
Still, I hope it's complex enough to showcase the architecture.
|
||||
|
||||
The full source code is available [at my git repository](https://git.rauhala.info/MasseR/demobot).
|
||||
|
||||
For the architecture to make sense, let me introduce two different actors: a
|
||||
*core contributor* that's familiar with Haskell and a *external contributor*
|
||||
that's familiar with programming, not necessarily with Haskell.
|
||||
|
||||
The repository is split into two parts, the library and the application.
|
||||
|
||||
The library
|
||||
|
||||
: Provides the restricted monad classes (tagless final), extension points and
|
||||
the core bot main loop.
|
||||
|
||||
The application
|
||||
|
||||
: Provides the implementation for the tagless final type classes, meaning
|
||||
that the application defines how the networking stack is handled, how database
|
||||
connectivity is done and so on. It also collects all the extensions for that
|
||||
specific application.
|
||||
|
||||
The *core contributor* is responsible for maintaining the library as well as
|
||||
the type class instances for the application type. The *external contributor*
|
||||
is responsible for maintaining one or multiple extensions that are restricted
|
||||
in their capability and complexity.
|
||||
|
||||
I'm restricting the capabilities of the monad in the library and extensions,
|
||||
meaning that I'm not allowing any IO. For example the networking is handled by
|
||||
a single `MonadNetwork` type class. This is the most complex type class in the
|
||||
library right now, using type families for defining a specific extension point
|
||||
for the messages. This could be something like 'event type' for Flowdock
|
||||
messages or 'source channel' for IRC messages.
|
||||
|
||||
~~~haskell
|
||||
data Request meta = Request { content :: Text
|
||||
, meta :: meta }
|
||||
data Response meta = Response { content :: Text
|
||||
, meta :: meta }
|
||||
|
||||
class Monad m => MonadNetwork m where
|
||||
type Meta m :: *
|
||||
recvMsg :: m (Request (Meta m))
|
||||
putMsg :: Response (Meta m) -> m ()
|
||||
~~~
|
||||
|
||||
Then we have the extension point which is more or less just a `Request -> m (Maybe Response)`. I'm using rank n types here for qualifying the `Meta`
|
||||
extension point and forcing the allowed type classes to be a subset of the
|
||||
application monad stack, I don't want extension writers to be able to write
|
||||
messages to the bot network by themselves.
|
||||
|
||||
~~~haskell
|
||||
data Extension meta =
|
||||
Extension { act :: forall m. (meta ~ Meta m, MonadExtension m) => Request meta -> m (Maybe (Response meta))
|
||||
, name :: String }
|
||||
~~~
|
||||
|
||||
Last part of the library is the main loop, which is basically a free monad
|
||||
(tagless final) waiting for an interpreter. At least in this POC I find this
|
||||
style to be really good, it's really simplified, easy to read and hides a lot
|
||||
of the complexity, while bringing forth the core algorithm.
|
||||
|
||||
~~~haskell
|
||||
mainLoop :: forall m. (MonadCatch m, MonadBot m) => [Extension (Meta m)] -> m ()
|
||||
mainLoop extensions = forever $ catch go handleFail
|
||||
where
|
||||
handleFail :: SomeException -> m ()
|
||||
handleFail e = logError $ tshow e
|
||||
go :: m ()
|
||||
go = do
|
||||
msg <- recvMsg
|
||||
responses <- catMaybes <$> mapM (`act` msg) extensions
|
||||
mapM_ putMsg responses
|
||||
~~~
|
||||
|
||||
Then comes the actual application where we write the effectful interpreters. In
|
||||
this POC the interpreter is just a `LoggingT IO a` with the semantics of
|
||||
stdin/stdout. This is the only file where we're actually interacting with the
|
||||
outside world, everything else is just pure code.
|
||||
|
||||
~~~haskell
|
||||
instance MonadNetwork AppM where
|
||||
type Meta AppM = ()
|
||||
recvMsg = Request <$> liftIO T.getLine <*> pure ()
|
||||
putMsg Response{..} = liftIO . T.putStrLn $ content
|
||||
~~~
|
||||
|
||||
Writing the extensions was the responsibility of *external contributors* and we
|
||||
already saw how the actual extension point was defined above. Using these
|
||||
extension points is really simple and here we see how the implementation is
|
||||
just a simple `Request -> m (Maybe Response)`.
|
||||
|
||||
~~~haskell
|
||||
extension :: Extension ()
|
||||
extension = Extension{..}
|
||||
where
|
||||
name = "hello world"
|
||||
act Request{..} | "hello" `T.isPrefixOf` content = return $ Just $ Response "Hello to you" ()
|
||||
| otherwise = return Nothing
|
||||
~~~
|
@ -6,10 +6,11 @@ let
|
||||
shell = pkgs.buildEnv {
|
||||
name = "site-shell";
|
||||
paths = [];
|
||||
buildInputs = [
|
||||
haskellPackages.ghcid
|
||||
haskellPackages.hasktags
|
||||
(haskellPackages.ghcWithHoogle (h: site.buildInputs ++ site.propagatedBuildInputs))
|
||||
buildInputs = with haskellPackages; [
|
||||
ghcid
|
||||
hasktags
|
||||
cabal-install
|
||||
(ghcWithHoogle (h: site.buildInputs ++ site.propagatedBuildInputs))
|
||||
];
|
||||
};
|
||||
|
||||
|
@ -2,11 +2,18 @@ name: site
|
||||
version: 0.1.0.0
|
||||
build-type: Simple
|
||||
cabal-version: >= 1.10
|
||||
license: BSD3
|
||||
license-file: LICENSE
|
||||
author: Mats Rauhala
|
||||
maintainer: mats.rauhala@iki.fi
|
||||
|
||||
executable site
|
||||
main-is: site.hs
|
||||
build-depends: base == 4.*
|
||||
, hakyll == 4.10.*
|
||||
, hakyll >= 4.10
|
||||
, time
|
||||
, xml-conduit
|
||||
, xml-lens
|
||||
, lens
|
||||
ghc-options: -threaded
|
||||
default-language: Haskell2010
|
||||
|
49
site.hs
@ -5,10 +5,14 @@ import Hakyll
|
||||
import Data.List (sortBy, sortOn)
|
||||
import Data.Time (formatTime, defaultTimeLocale)
|
||||
|
||||
data Book =
|
||||
Book { title :: String
|
||||
, date :: Day
|
||||
, status :: String }
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
main :: IO ()
|
||||
main = hakyll $ do
|
||||
main = hakyllWith defaultConfiguration{ deployCommand = "ipfs add -Q -r _site" } $do
|
||||
match "images/*" $ do
|
||||
route idRoute
|
||||
compile copyFileCompiler
|
||||
@ -21,12 +25,30 @@ main = hakyll $ do
|
||||
route idRoute
|
||||
compile compressCssCompiler
|
||||
|
||||
match (fromList ["about.markdown", "contact.markdown"]) $ do
|
||||
match "js/*" $ do
|
||||
route idRoute
|
||||
compile compressCssCompiler
|
||||
|
||||
match (fromList ["index.markdown", "contact.markdown"]) $ do
|
||||
route $ setExtension "html"
|
||||
compile $ pandocCompiler
|
||||
>>= loadAndApplyTemplate "templates/default.html" defaultContext
|
||||
>>= relativizeUrls
|
||||
|
||||
match "books/*" $ do
|
||||
route $ setExtension "html"
|
||||
compile $ pandocCompiler
|
||||
>>= loadAndApplyTemplate "templates/post.html" postCtx
|
||||
>>= loadAndApplyTemplate "templates/default.html" postCtx
|
||||
>>= relativizeUrls
|
||||
|
||||
match "posts/incomplete/*" $ do
|
||||
route $ setExtension "html"
|
||||
compile $ pandocCompiler
|
||||
>>= loadAndApplyTemplate "templates/post.html" postCtx
|
||||
>>= loadAndApplyTemplate "templates/default.html" postCtx
|
||||
>>= relativizeUrls
|
||||
|
||||
match "posts/guides/*" $ do
|
||||
route $ setExtension "html"
|
||||
compile $ pandocCompiler
|
||||
@ -41,6 +63,18 @@ main = hakyll $ do
|
||||
>>= loadAndApplyTemplate "templates/default.html" postCtx
|
||||
>>= relativizeUrls
|
||||
|
||||
create ["books.html"] $ do
|
||||
route idRoute
|
||||
compile $ do
|
||||
books <- reverse <$> loadAll "books/*"
|
||||
let ctx = listField "books" postCtx (pure books) <>
|
||||
constField "title" "Books" <>
|
||||
defaultContext
|
||||
makeItem ""
|
||||
>>= loadAndApplyTemplate "templates/books.html" ctx
|
||||
>>= loadAndApplyTemplate "templates/default.html" ctx
|
||||
>>= relativizeUrls
|
||||
|
||||
create ["guides.html"] $ do
|
||||
route idRoute
|
||||
compile $ do
|
||||
@ -55,17 +89,6 @@ main = hakyll $ do
|
||||
>>= loadAndApplyTemplate "templates/default.html" archiveCtx
|
||||
>>= relativizeUrls
|
||||
|
||||
match "index.html" $ do
|
||||
route idRoute
|
||||
compile $ do
|
||||
let indexCtx =
|
||||
constField "title" "Home" `mappend`
|
||||
defaultContext
|
||||
|
||||
getResourceBody
|
||||
>>= applyAsTemplate indexCtx
|
||||
>>= loadAndApplyTemplate "templates/default.html" indexCtx
|
||||
>>= relativizeUrls
|
||||
|
||||
match "templates/*" $ compile templateBodyCompiler
|
||||
|
||||
|
9
templates/books.html
Normal file
@ -0,0 +1,9 @@
|
||||
A collection of books I have read lately.
|
||||
|
||||
<ul class="books">
|
||||
$for(books)$
|
||||
<li>
|
||||
<img src="/images/book_$status$.svg" class="icon" /><a href="$url$">$title$</a> - $date$
|
||||
</li>
|
||||
$endfor$
|
||||
</ul>
|
@ -6,6 +6,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>rauhala.info - $title$</title>
|
||||
<link rel="stylesheet" href="/css/default.css" />
|
||||
<link rel="stylesheet" href="/css/highlight.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
@ -13,9 +14,9 @@
|
||||
<a href="/">rauhala.info</a>
|
||||
</div>
|
||||
<nav>
|
||||
<a href="/">Home</a>
|
||||
<a href="/guides.html">Guides</a>
|
||||
<a href="/about.html">About</a>
|
||||
<!-- Git logo from https://git-scm.com/downloads/logos -->
|
||||
<!-- Logo by Jason Long -->
|
||||
<a href="https://git.rauhala.info"><img src="/images/git_16.png" alt="git" /></a>
|
||||
<a href="/contact.html">Contact</a>
|
||||
</nav>
|
||||
</header>
|
||||
@ -27,7 +28,7 @@
|
||||
|
||||
<footer>
|
||||
Site proudly generated by
|
||||
<a href="http://jaspervdj.be/hakyll">Hakyll</a>
|
||||
<a href="http://jaspervdj.be/hakyll">Hakyll</a><br />
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<ul>
|
||||
$for(posts)$
|
||||
<li>
|
||||
<a href="$url$">$title$</a> - $date$
|
||||
<a href="$url$">$title$</a> - $modified$
|
||||
</li>
|
||||
$endfor$
|
||||
</ul>
|
||||
|