Compare commits
17 Commits
6afa6d55d4
...
configurin
Author | SHA1 | Date | |
---|---|---|---|
afcfc7bb07 | |||
4c8df0c5df | |||
58209a2c6e | |||
9d18db19a2 | |||
7557f5041c | |||
e2a213b08d | |||
91cea649c9 | |||
5661770508 | |||
4120b2fff5 | |||
213442ac0f | |||
f355516c1d | |||
c348b2c834 | |||
9867ecdbb1 | |||
73a237aec8 | |||
2db7c774a2 | |||
4e2d6342b4 | |||
06778c219a |
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
_site
|
_site/
|
||||||
_cache
|
_cache/
|
||||||
|
dist/
|
||||||
|
30
LICENSE
Normal file
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
|
|
@ -111,7 +111,7 @@ article .header {
|
|||||||
|
|
||||||
@media (min-width: 640px) {
|
@media (min-width: 640px) {
|
||||||
body {
|
body {
|
||||||
width: 60rem;
|
width: 85rem;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
30
css/highlight.css
Normal file
30
css/highlight.css
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/* Generated by pandoc. */
|
||||||
|
table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode, table.sourceCode pre.sourceCode
|
||||||
|
{
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
/* border: 0; */
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
pre.sourceCode
|
||||||
|
{
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background-color: rgb(238,238,255);
|
||||||
|
padding: 10px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
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
BIN
images/Git-Icon-Black.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 684 B |
BIN
images/Git-Icon-Black_orig.png
Normal file
BIN
images/Git-Icon-Black_orig.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
BIN
images/git_16.png
Normal file
BIN
images/git_16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 485 B |
BIN
images/git_32.png
Normal file
BIN
images/git_32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 684 B |
BIN
images/profile_grayscale.jpg
Normal file
BIN
images/profile_grayscale.jpg
Normal file
Binary file not shown.
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
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)
|
||||||
|
- 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.
|
||||||
|
- **Other**
|
||||||
|
- I was involved in creating an email solicit platform.
|
22
js/api.js
Normal file
22
js/api.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
var getApiIpfsCurrent = function(onSuccess, onError) {
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('GET', '/api/ipfs/current', true);
|
||||||
|
xhr.setRequestHeader('Accept', 'application/json');
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
var res = null;
|
||||||
|
if (xhr.readyState === 4) {
|
||||||
|
if (xhr.status === 204 || xhr.status === 205) {
|
||||||
|
onSuccess();
|
||||||
|
} else if (xhr.status >= 200 && xhr.status < 300) {
|
||||||
|
try { res = JSON.parse(xhr.responseText); } catch (e) { onError(e); }
|
||||||
|
if (res) onSuccess(res);
|
||||||
|
} else {
|
||||||
|
try { res = JSON.parse(xhr.responseText); } catch (e) { onError(e); }
|
||||||
|
if (res) onError(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.send(null);
|
||||||
|
};
|
||||||
|
|
10
js/app.js
Normal file
10
js/app.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
$(document).ready(function() {
|
||||||
|
var success = function(x) {
|
||||||
|
$("#ipfs > em").html(x);
|
||||||
|
};
|
||||||
|
var error = function(x) {
|
||||||
|
$("#ipfs").hide();
|
||||||
|
console.log("ipfs hash not found: " + x);
|
||||||
|
};
|
||||||
|
getApiIpfsCurrent(success, error);
|
||||||
|
});
|
2
js/jquery-3.3.1.min.js
vendored
Normal file
2
js/jquery-3.3.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
78
posts/configs.md
Normal file
78
posts/configs.md
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
---
|
||||||
|
title: Extensible configuration Pt. 1
|
||||||
|
date: 2019-03-27
|
||||||
|
---
|
||||||
|
|
||||||
|
This is the first part of a series where I'm going through how to make
|
||||||
|
extensible configuration. There is nothing groundbreaking or new in this
|
||||||
|
series, it's just me going through different implementations and trying to
|
||||||
|
understand them.
|
||||||
|
|
||||||
|
The source material for this post is [the fixed point implementation](https://github.com/NixOS/nixpkgs/blob/master/lib/fixed-points.nix) for nix.
|
||||||
|
|
||||||
|
By extensible configuration, I'm talking about nix style extensible
|
||||||
|
configuration, like overlays, overrides and extensions. Let's see an example of
|
||||||
|
an extensible configuration.
|
||||||
|
|
||||||
|
``` nix
|
||||||
|
{ haskellPackages, fetchFromGitHub }:
|
||||||
|
|
||||||
|
let
|
||||||
|
purescript = fetchFromGitHub {
|
||||||
|
owner = "purescript";
|
||||||
|
repo = "purescript";
|
||||||
|
rev = "2cb4a6496052db726e099539be682b87585af494";
|
||||||
|
sha256 = "1v4gs08xnqgym6jj3drkzbic7ln3hfmflpbpij3qzwxsmqd2abr7";
|
||||||
|
}
|
||||||
|
hp = haskellPackages.extend (self: super: {
|
||||||
|
purescript = super.callCabal2nix "purescript" purescript {};
|
||||||
|
});
|
||||||
|
|
||||||
|
in
|
||||||
|
|
||||||
|
hp.purescript;
|
||||||
|
```
|
||||||
|
|
||||||
|
On a high level we are augmenting the `haskellPackages` attrset by replacing
|
||||||
|
the existing purescript package with a different one. The extension is a
|
||||||
|
function that takes two arguments, `self` and `super`. `super` is the original
|
||||||
|
non-lazy value and `self` is the lazy value that corresponds to the value at
|
||||||
|
end.
|
||||||
|
|
||||||
|
The first step on this journey is done by getting to know `fix`. Fix is
|
||||||
|
described being the least fixed point of a function. In practice it's a
|
||||||
|
function allowing declaring recursive functions without explicit recursion.
|
||||||
|
|
||||||
|
``` nix
|
||||||
|
fix = f: let x = f x; in x
|
||||||
|
```
|
||||||
|
|
||||||
|
With fix you can have access to the lazy `self` value. It's value is whatever
|
||||||
|
would have been computed in the end. As it is lazy, it is possible to end up in
|
||||||
|
a recursive loop if there is a cyclic dependency.
|
||||||
|
|
||||||
|
``` nix
|
||||||
|
let recursive = fix (self: {
|
||||||
|
foo = 3;
|
||||||
|
bar = self.foo + 1;
|
||||||
|
});
|
||||||
|
infinite = fix (self: {
|
||||||
|
foo = self.bar + 1;
|
||||||
|
bar = self.foo + 1;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
You can try those yourself. The first version is fine and returns an attrset
|
||||||
|
like you would expect. The second one has a cyclic dependency and nix helpfully
|
||||||
|
errors out.
|
||||||
|
|
||||||
|
The next step is making a function that has access to the unmodified original
|
||||||
|
values. This is managed through the `extends` function. It took a while for me to understand what's happening in there, but luckily nix has [good documentation](https://github.com/NixOS/nixpkgs/blob/67b1265fb3d38ead5a57fee838405a2d997777c2/lib/fixed-points.nix#L37-L65) for it.
|
||||||
|
|
||||||
|
``` nix
|
||||||
|
extends = f: rattrs: self: let super = rattrs self; in super // f self super
|
||||||
|
```
|
||||||
|
|
||||||
|
- https://elvishjerricco.github.io/2017/04/01/nix-style-configs-in-haskell.html
|
||||||
|
- https://github.com/NixOS/nixpkgs/blob/master/lib/fixed-points.nix
|
||||||
|
- https://chshersh.github.io/posts/2019-03-25-comonadic-builders
|
148
posts/demobot.md
Normal file
148
posts/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
|
||||||
|
~~~
|
@ -2,11 +2,15 @@ name: site
|
|||||||
version: 0.1.0.0
|
version: 0.1.0.0
|
||||||
build-type: Simple
|
build-type: Simple
|
||||||
cabal-version: >= 1.10
|
cabal-version: >= 1.10
|
||||||
|
license: BSD3
|
||||||
|
license-file: LICENSE
|
||||||
|
author: Mats Rauhala
|
||||||
|
maintainer: mats.rauhala@iki.fi
|
||||||
|
|
||||||
executable site
|
executable site
|
||||||
main-is: site.hs
|
main-is: site.hs
|
||||||
build-depends: base == 4.*
|
build-depends: base == 4.*
|
||||||
, hakyll == 4.10.*
|
, hakyll >= 4.10
|
||||||
, time
|
, time
|
||||||
ghc-options: -threaded
|
ghc-options: -threaded
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
|
46
site.hs
46
site.hs
@ -1,14 +1,12 @@
|
|||||||
--------------------------------------------------------------------------------
|
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
import Data.Monoid (mappend)
|
import Data.List (sortOn)
|
||||||
|
import Data.Time (defaultTimeLocale, formatTime)
|
||||||
import Hakyll
|
import Hakyll
|
||||||
import Data.List (sortBy, sortOn)
|
|
||||||
import Data.Time (formatTime, defaultTimeLocale)
|
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = hakyll $ do
|
main = hakyllWith defaultConfiguration{ deployCommand = "ipfs add -Q -r _site" } $do
|
||||||
match "images/*" $ do
|
match "images/*" $ do
|
||||||
route idRoute
|
route idRoute
|
||||||
compile copyFileCompiler
|
compile copyFileCompiler
|
||||||
@ -21,33 +19,30 @@ main = hakyll $ do
|
|||||||
route idRoute
|
route idRoute
|
||||||
compile compressCssCompiler
|
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"
|
route $ setExtension "html"
|
||||||
compile $ pandocCompiler
|
compile $ pandocCompiler
|
||||||
>>= loadAndApplyTemplate "templates/default.html" defaultContext
|
>>= loadAndApplyTemplate "templates/default.html" defaultContext
|
||||||
>>= relativizeUrls
|
>>= relativizeUrls
|
||||||
|
|
||||||
match "posts/guides/*" $ do
|
match "posts/*" $ do
|
||||||
route $ setExtension "html"
|
route $ setExtension "html"
|
||||||
compile $ pandocCompiler
|
compile $ pandocCompiler
|
||||||
>>= loadAndApplyTemplate "templates/post.html" postCtx
|
>>= loadAndApplyTemplate "templates/post.html" postCtx
|
||||||
>>= loadAndApplyTemplate "templates/default.html" postCtx
|
>>= loadAndApplyTemplate "templates/default.html" postCtx
|
||||||
>>= relativizeUrls
|
>>= relativizeUrls
|
||||||
|
|
||||||
match "posts/brainstorming/*" $ do
|
create ["posts.html"] $ do
|
||||||
route $ setExtension "html"
|
|
||||||
compile $ pandocCompiler
|
|
||||||
>>= loadAndApplyTemplate "templates/post.html" postCtx
|
|
||||||
>>= loadAndApplyTemplate "templates/default.html" postCtx
|
|
||||||
>>= relativizeUrls
|
|
||||||
|
|
||||||
create ["guides.html"] $ do
|
|
||||||
route idRoute
|
route idRoute
|
||||||
compile $ do
|
compile $ do
|
||||||
posts <- modFirst =<< loadAll "posts/guides/*"
|
posts <- modFirst =<< loadAll "posts/*"
|
||||||
let archiveCtx =
|
let archiveCtx =
|
||||||
listField "posts" postCtx (return posts) `mappend`
|
listField "posts" postCtx (return posts) <>
|
||||||
constField "title" "Guides" `mappend`
|
constField "title" "Posts" <>
|
||||||
defaultContext
|
defaultContext
|
||||||
|
|
||||||
makeItem ""
|
makeItem ""
|
||||||
@ -55,17 +50,6 @@ main = hakyll $ do
|
|||||||
>>= loadAndApplyTemplate "templates/default.html" archiveCtx
|
>>= loadAndApplyTemplate "templates/default.html" archiveCtx
|
||||||
>>= relativizeUrls
|
>>= 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
|
match "templates/*" $ compile templateBodyCompiler
|
||||||
|
|
||||||
@ -78,8 +62,8 @@ modFirst = fmap reverse . modified
|
|||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
postCtx :: Context String
|
postCtx :: Context String
|
||||||
postCtx =
|
postCtx =
|
||||||
dateField "date" "%B %e, %Y" `mappend`
|
dateField "date" "%B %e, %Y" <>
|
||||||
modifiedField "modified" "%B %e, %Y" `mappend`
|
modifiedField "modified" "%B %e, %Y" <>
|
||||||
defaultContext
|
defaultContext
|
||||||
where
|
where
|
||||||
modifiedField key format = field key $ \i -> do
|
modifiedField key format = field key $ \i -> do
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>rauhala.info - $title$</title>
|
<title>rauhala.info - $title$</title>
|
||||||
<link rel="stylesheet" href="/css/default.css" />
|
<link rel="stylesheet" href="/css/default.css" />
|
||||||
|
<link rel="stylesheet" href="/css/highlight.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
@ -13,10 +14,11 @@
|
|||||||
<a href="/">rauhala.info</a>
|
<a href="/">rauhala.info</a>
|
||||||
</div>
|
</div>
|
||||||
<nav>
|
<nav>
|
||||||
<a href="/">Home</a>
|
<!-- Git logo from https://git-scm.com/downloads/logos -->
|
||||||
<a href="/guides.html">Guides</a>
|
<!-- Logo by Jason Long -->
|
||||||
<a href="/about.html">About</a>
|
<a href="https://git.rauhala.info"><img src="/images/git_16.png" alt="git" /></a>
|
||||||
<a href="/contact.html">Contact</a>
|
<a href="/contact.html">Contact</a>
|
||||||
|
<a href="/posts.html">Posts</a>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@ -28,6 +30,10 @@
|
|||||||
<footer>
|
<footer>
|
||||||
Site proudly generated by
|
Site proudly generated by
|
||||||
<a href="http://jaspervdj.be/hakyll">Hakyll</a>
|
<a href="http://jaspervdj.be/hakyll">Hakyll</a>
|
||||||
|
<span id="ipfs">and found on IPFS as <em></em></ipfs>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
<script type="application/javascript" src="/js/jquery-3.3.1.min.js"></script>
|
||||||
|
<script type="application/javascript" src="/js/api.js"></script>
|
||||||
|
<script type="application/javascript" src="/js/app.js"></script>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
A list of small and big guides.
|
Me writing out interesting ideas and experiments.
|
||||||
|
|
||||||
$partial("templates/post-list.html")$
|
$partial("templates/post-list.html")$
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<ul>
|
<ul>
|
||||||
$for(posts)$
|
$for(posts)$
|
||||||
<li>
|
<li>
|
||||||
<a href="$url$">$title$</a> - $date$
|
<a href="$url$">$title$</a> - $modified$
|
||||||
</li>
|
</li>
|
||||||
$endfor$
|
$endfor$
|
||||||
</ul>
|
</ul>
|
||||||
|
Reference in New Issue
Block a user