~mrlee/www.leemeichin.com

d21dc11b81388ce205560d4f8a1c206b37ed5bba — Lee Meichin 13 days ago 9aa8f7f
Format code blocks
M posts/blogging-in-haskell.org => posts/blogging-in-haskell.org +3 -3
@@ 18,7 18,7 @@ Once you compile this file, you end up with a nice binary, e.g. ~site~, and /tha

As an example, on the home page, there is a ~git log~ output section. It's fairly primitive, although I intend to build out the functionality a bit more. Writing the functionality was fairly effortless, with the help of some other authors on the net:

◊codeblock['haskell]{
#+begin_src haskell
  data GitLog = Hash | Commit | Full
    deriving (Eq, Read)



@@ 54,7 54,7 @@ As an example, on the home page, there is a ~git log~ output section. It's fairl
  where
    ctx = field singularName (return . show . itemBody)
    logItem log = Item (fromString $ path ++ "/log/" ++ log) log
}
#+end_src

The result of adding this code, and then inserting it into the template context, is that I have a new template variable that I can loop over, for each log item. The practical use is fairly limited, but I like it because it adds a certain flavour to the site. Later on I will try to use a parser combinator library to be able to present the different parts of the log with more control.



@@ 67,4 67,4 @@ In any case, I've enjoyed playing around with Haskell in order to deploy this si
  ◊^[4]{◊<>["https://jaspervdj.be/hakyll"]}
  ◊^[5]{◊<>["https://www.getzola.org"]}
  ◊^[6]{◊<>["https://terminalcss.xyz"]}
}
\ No newline at end of file
}

M posts/can-you-crack-the-code.org => posts/can-you-crack-the-code.org +25 -25
@@ 12,7 12,7 @@ I presume you've seen this kind of puzzle before: there is a lock that requires 

-----

◊div[#:style "text-align: center"]{
#+begin_src text
  *CAN YOU CRACK THE CODE?*

  🔐 _ _ _ _


@@ 31,7 31,7 @@ I presume you've seen this kind of puzzle before: there is a lock that requires 

  *8* *5* *0* *4*  
  ◊p{Two numbers are correct, but in the wrong place.}
}
#+end_src

-----



@@ 45,7 45,7 @@ If you're unaware of Prolog, it's a /logical progamming/ language that, in its m

Mr Merritt is, to put it professionally, *god damn right*. Here's a valid Prolog program:

◊codeblock['prolog]{
#+begin_src prolog
  % https://swish.swi-prolog.org/p/KfdGtcJr.swinb

  president(trump).


@@ 56,7 56,7 @@ Mr Merritt is, to put it professionally, *god damn right*. Here's a valid Prolog
  cheese(wensleydale).
  person(trump).
  person(obama).
}
#+end_src

What we have here are some facts, both true and technically true. It's a fact that Obama is a president, as is Trump. It's also a fact that there is a brand of cheese in the UK called President. This is quite ambiguous as a result so some extra facts are supplied, namely that brie is a cheese as much as it is a President-brand cheese, and that Wensleydale is also a cheese. It goes without saying that Trump and Obama are people, so with those facts we should be able to do some querying.



@@ 64,15 64,15 @@ If you're doing this on your own machine, you can save those facts into a file (

Let's do some querying then, which will show you how Prolog might seem a bit back to front compared to what you're used to.

◊codeblock['prolog]{
#+begin_src prolog
  president(trump). % true.
}
#+end_src

So far, so boring. We stated `president(trump)` as a fact in our first prolog file, so this is basically confirming that we did so. Let's spice it up a little.

◊codeblock['prolog]{
#+begin_src prolog
  president(X). % trump; obama; brie.
}
#+end_src

◊aside{
  With the online editor you can click 'Next' to see all of the results, and in the console you can type ~;~. This can be done repeatedly until the input ends with ~.~, which says there are no more facts that fit the query)


@@ 84,15 84,15 @@ The fuck? What is ~X~?

Let's try one more thing, which should explain enough about Prolog to be dangerous.

◊codeblock['prolog]{
#+begin_src prolog
  president(X), cheese(X). % brie.
}
#+end_src

/Now we're cookin' wi' gas!} as we'd say back up north. A lot of what you do in prolog is chain little sentences like this together (using the comma operator ~,~, which means ~and~), and in this instance we're asking Prolog to get all the presidents, put them in ~X~, and then show me only the presidents that are also a cheese. The ◊code{./ finishes the sentence, or the query. Let's do a similar query to wrap this intro up, and you can see if your guess at the answer is the same as what this produces.
/Now we're cookin' wi' gas/ as we'd say back up north. A lot of what you do in prolog is chain little sentences like this together (using the comma operator ~,~, which means ~and~), and in this instance we're asking Prolog to get all the presidents, put them in ~X~, and then show me only the presidents that are also a cheese. The ~.~ finishes the sentence, or the query. Let's do a similar query to wrap this intro up, and you can see if your guess at the answer is the same as what this produces.

◊codeblock['prolog]{
#+begin_src prolog
  president(X), person(X). % trump, obama.
}
#+end_src

This is more or less the essence of Prolog, and your program is essentially a database of facts and rules, and then you use the program by querying those facts and rules. You'll make a query by providing what you _do_ know, and then placing a variable (or a placeholder) in the spots where you don't know the answer. You don't tell Prolog how exactly to compute that answer. And with that explained, I think we can try and crack this code.



@@ 102,7 102,7 @@ Here's the puzzle again, for reference:

-----

◊div[#:style "text-align: center"]{
#+begin_src text
  *CAN YOU CRACK THE CODE?*

  🔐 _ _ _ _


@@ 121,15 121,15 @@ Here's the puzzle again, for reference:

  *8* *5* *0* *4*  
  ◊p{Two numbers are correct, but in the wrong place.}
}
#+end_src

-----

According to Leon Sterling and Ehud Shapiro in /The Art of Prolog}◊^[3], this type of problem falls quite neatly under the umbrella of non-deterministic programming. This is because we're essentially going to build an algorithm that will use what they describe as a ◊code{generate and test/ solution. We're going to write something that will take our clues and run through all the possible answers until it lands on the only one that fits. We're not aiming for beautiful optimisation here so this good enough, although the code we write will be tightly coupled to the exact puzzle provided.
According to Leon Sterling and Ehud Shapiro in /The Art of Prolog/ ◊^[3], this type of problem falls quite neatly under the umbrella of non-deterministic programming. This is because we're essentially going to build an algorithm that will use what they describe as a ~generate and test~ solution. We're going to write something that will take our clues and run through all the possible answers until it lands on the only one that fits. We're not aiming for beautiful optimisation here so this good enough, although the code we write will be tightly coupled to the exact puzzle provided.

So, let's begin with our set of rules:

◊codeblock['prolog]{
#+begin_src prolog
  :- use_module(library(clpfd)). % we're working with numbers, this makes it easier.

  clue_1([9, 2, 8, 5]). % one number correct, but in the wrong place


@@ 137,7 137,7 @@ So, let's begin with our set of rules:
  clue_3([5, 2, 0, 1]). % one number is correct, and is also in the right place
  clue_4([6, 5, 0, 7]). % none of the numbers are correct, anywhere
  clue_5([8, 5, 2, 4]). % two numbers are correct, but in the wrong place
}
#+end_src

◊aside{
  If you're curious about the first `use_module` statement, beyond knowing that it makes things easier, check out the docs on /Constraint Logic Programming over Finite Domains/.◊^[4]


@@ 145,7 145,7 @@ So, let's begin with our set of rules:

These clues don't really mean anything by themselves, they're simple facts in Prolog terms, so we need to add a bit more to give these some meaning. All of this will go into the same file, as we're not ready to query yet.

◊codeblock['prolog]{
#+begin_src prolog
  % rule: a digit is correct but it is in the wrong place
  wrong_place(Digit, Index, Digits) :- nth1(Index1, Digits, Digit), Index \== Index1.



@@ 155,13 155,13 @@ These clues don't really mean anything by themselves, they're simple facts in Pr
  % rule: the digit is wrong.
  wrong(_, []).
  wrong(Digit, [D|Ds]) :- Digit #\= D, wrong(Digit, Ds).
}
#+end_src

I'll leave the in-depth explanation of these rules to another post for the sake of brevity, and also because I'm not that much of a Prolog expert. These are all used to add meaning to the facts, as with these rules we can now define logic such as /one number is correct but in the wrong position}, and ◊em{none of the numbers are correct/. We just have to painstakingly mix and match them.

The next bit is quite long, but this query is where we make the sausage. Commentary will be written inline for ease of copy and paste, until I come back and edit this post with a more digestible version.

◊codeblock['prolog]{
#+begin_src prolog
  crack_code(Code) :-
    % A, B, C and D represent the four digits of the code, which are all between 0 and 9.
    A in 0..9,


@@ 251,17 251,17 @@ The next bit is quite long, but this query is where we make the sausage. Comment
    % the only answer that makes sense given the previous
    % rules.
    Code = [A, B, C, D].
}
#+end_src

Did you solve the puzzle yourself? Do you remember the answer? If you don't care to copy and paste all of that, you can open up this ready made notebook◊^[5], and then run the following:

◊codeblock['prolog]{
#+begin_src prolog
  crack_code([A, B, C, D]),
  write('The first number is: '), write(A), write('\n'),
  write('The second number is: '), write(B), write('\n'),
  write('The third number is: '), write(C), write('\n'),
  write('The fourth number is: '), write(D), write('\n').
}
#+end_src

The exercise of writing that in a less brute-force manner is left to you, my beloved reader.



@@ 278,4 278,4 @@ Mad propz to the Prolog community on Reddit also, whose example solutions helped
  ◊^[4]{◊<>["https://www.swi-prolog.org/man/clpfd.html"]}
  ◊^[5]{◊<>["https://swish.swi-prolog.org/p/MgtEUnSv.swinb"]}
  ◊^[6]{◊<>["https://www.reddit.com/r/prolog/comments/fzww7m/cracking_this_puzzle_with_prolog/"]}
}
\ No newline at end of file
}

M posts/gettin-ziggy-with-it-pi-zero.org => posts/gettin-ziggy-with-it-pi-zero.org +20 -20
@@ 28,7 28,7 @@ The first and most complicated part of any low-level project is the bit where yo

The next step is to define a ~main~ function that grabs a file descriptor (or handle) corresponding to our OLED display. According to the aforementioned dev interface docs, we'll need to open a file and check it with ~ioctl~.

◊codeblock['zig]{
#+begin_src zig
  const std = @import("std");

  const c = @cImport({


@@ 52,7 52,7 @@ The next step is to define a ~main~ function that grabs a file descriptor (or ha

    stdout.print("Init successful.\n", .{});
  }
}
#+end_src

You might have noticed something odd: we're not really writing much Zig here, it's practically 95% interop with C. The beauty of Zig is that this interop is so simple and intuitive that it's the /easiest/ way to get started if you're going to be linking against existing C libraries. Get the software working first, abstract it later, as they say, and you might already start to get an idea of what we could convert into idiomatic Zig libraries in future.



@@ 62,7 62,7 @@ There's also the ~try~ macro, used in combination with the ~!void~ return type, 

What we've actually done, however, is open the device file ~/dev/i2c-1~, and then used the ~ioctl~ library to specify which device in particular we want to talk to. You can find out this value by running ~i2cdevice -y 1~, like so:

◊codeblock['text]{
#+begin_src text
  pi@raspberrypi:~ $ i2cdetect -y 1
      0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
  00:          -- -- -- -- -- -- -- -- -- -- -- -- --


@@ 73,7 73,7 @@ What we've actually done, however, is open the device file ~/dev/i2c-1~, and the
  50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  70: -- -- -- -- -- -- -- --
}
#+end_src

◊aside{
  In my case, the device can be accessed at address ~0x3C~, which is how I defined ~i2c_addr~ above.


@@ 93,9 93,9 @@ Are you writing this code on the Pi itself? Probably not, I imagine, and nor do 

Let's build a binary, then. Save your code into a file, say, ~stardust.zig~ and then proceed.

◊codeblock['bash]{
#+begin_src bash
  zig build-exe stardust.zig  -target arm-linux-musleabihf -mcpu arm1176jzf_s -O ReleaseSafe -lc
}
#+end_src

To unpack that a little, the ~target~ is a triplet stating that we want to build this using the musl◊^[9] libc ABI, on a 32bit ARM architecture. ~mcpu~ goes along with that to make sure the resulting binary will work on our Pi Zero. I grabbed these values from an issue on Zig's github repo◊^[10], so credit goes to the author of that issue for unintentionally guiding me forward.



@@ 105,9 105,9 @@ Passing the optimiser flag (~-O~) isn't strictly necessary, so you can omit this

Once the build finishes, you should find a shiny new executable called ~stardust~ in the same directory as your code. You can get it onto your Pi with ~scp~, like so:

◊codeblock['bash]{
#+begin_src bash
  scp stardust pi@raspberrypi:~/stardust
}
#+end_src

◊aside{
  You will need to change ~pi@raspberrypi~ to whatever else you've configured if you've changed the defaults.


@@ 121,9 121,9 @@ Let's move on and make this kitten purr. Meow 🐈.

◊h2{Getting this show on the road}

In true /draw the rest of the fucking owl} fashion◊^[11], what follows is a bit of a code-dump since the primary method of communicating with your OLED display is to, literally, write a few bytes to a file. The registers available and what can be written to them are often described in a meticulously detailed datasheet◊^[12], but they're not exactly light reading and we can save a bit of time by grabbing the info from elsewhere. A lot of the constants that follow are gracefully derived from those listed in a certain ◊code{owenosborn/'s wiringPi-based driver.◊^[13]. Credit where credit's due, eh.
In true /draw the rest of the fucking owl/ fashion◊^[11], what follows is a bit of a code-dump since the primary method of communicating with your OLED display is to, literally, write a few bytes to a file. The registers available and what can be written to them are often described in a meticulously detailed datasheet◊^[12], but they're not exactly light reading and we can save a bit of time by grabbing the info from elsewhere. A lot of the constants that follow are gracefully derived from those listed in a certain ~owenosborn~'s wiringPi-based driver.◊^[13]. Credit where credit's due, eh.

◊codeblock['zig]{
#+begin_src zig
  const SET_CONTRAST = 0x81;
  const SET_DISPLAY_ALL_ON_RESUME = 0xA4;
  const SET_DISPLAY_ALL_ON = 0xA5;


@@ 148,13 148,13 @@ In true /draw the rest of the fucking owl} fashion◊^[11], what follows is a bi
  const SET_COM_SCAN_DEC = 0xC8;
  const SET_SEG_REMAP = 0xA0;
  const SET_CHARGE_PUMP = 0x8D;
}
#+end_src

The registers available to an i◊sup{2}c compatible device will depend on the device itself, so it's not really safe to copy and paste these without knowing exactly what you're dealing with. This is driver level code so it's not like you'll get some fancy validation error if you write the wrong bytes, you'll more likely fuck it up and burn down your house◊^[14].

Next we'll want to init the display and get it into a clean state, with the cursor pointing at the first pixel.

◊codeblock['zig]{
#+begin_src zig
  fn init_display(fd: fs.File) !void {
      const cmds = [_]u8{
          SET_MULTIPLEX_RATIO, 0x3F,                   0x00,


@@ 192,7 192,7 @@ Next we'll want to init the display and get it into a clean state, with the curs
          _ = try fd.write(&[2]u8{ 0x00, cmd });
      }
  }
}
#+end_src

Wow, actual Zig code! The formatting may look a little odd because that's what ~zig fmt~ decides is appropriate.



@@ 200,16 200,16 @@ Wow, actual Zig code! The formatting may look a little odd because that's what ~

The recurring theme in all of these new functions is that the entire basis of their existence is to create an array of two bytes, and then write them to file descriptor we opened right at the start. The data structure looks something like this:

◊codeblock['c]{
#+begin_src c
  buf[0] = 0x00; // the register to be written to
  buf[1] = 0x??; // the value to assign to that register
}
#+end_src

The file opened in ~main~ isn't a traditional file as you know it, but it points to all of the devices connected to your GPIO header on the Pi. Therefore, if you know enough about the hardware at a low enough level, you can control all of them by writing the right bytes to the right register, at the right address.

The rest of the code, e.g. ~reset_cursor~, resets the state of the display in such a way that you can write a pixel and the cursor will advance, linearly, to the next one.

◊codeblock['zig]{
#+begin_src zig
  fn fill(fd: fs.File) !void {
      var i: usize = 0;



@@ 218,7 218,7 @@ The rest of the code, e.g. ~reset_cursor~, resets the state of the display in su
          i += 1;
      }
  }
}
#+end_src

This ~fill~ function will (rather quickly) turn the display solid white, updating each pixel one at a time. Before we continue though, let's go through some more Zig specifics; namely, ~inline~.



@@ 236,7 236,7 @@ Zig has some nice language features intended to replace and improve upon C/C++ p

This post is getting pretty long-winded, and all I wanted to do was show how to set some pixels on a tiny display. Let's wrap this up then, since we're almost ready to recompile. Just one finishing touch, which is to call the functions we defined. Update ~main~ to look like this:

◊codeblock['zig]{
#+begin_src zig
  pub fn main() !void {
      const stdout = std.io.getStdOut().outStream();
      const fd = try fs.openFileAbsolute(i2c_device, fs.File.OpenFlags{ .write = true, .read = true });


@@ 259,7 259,7 @@ This post is getting pretty long-winded, and all I wanted to do was show how to 
      try stdout.print("fill\n", .{});
      try fill(fd);
  }
}
#+end_src

Once you're done, rebuild the binary and ~scp~ it over, like you did the first time. SSH into your Pi and run it again (i.e ~./stardust~), and see your display light up! 🥳



@@ 284,4 284,4 @@ Hopefully that worked, but if it didn't, get in touch with your feedback at wtf@
  ◊^[14]{Possibly exaggerated for effect. Possibly.}
  ◊^[15]{◊<>["https://github.com/adafruit/Adafruit_CircuitPython_SSD1306/blob/master/adafruit_ssd1306.py"]}
  ◊^[16]{◊<>["https://kristoff.it/blog/what-is-zig-comptime/"]}
}
\ No newline at end of file
}

M posts/hakyll-on-devops-pipelines.org => posts/hakyll-on-devops-pipelines.org +31 -31
@@ 8,14 8,14 @@

In a way, this is total overkill for a static site. If I have the repo cloned on my machine and I want to publish a new post, I can do it in two commands:

◊codeblock['bash]{
#+begin_src bash
  stack exec site build
  scp -r _site/ deploy@mrlee.dev:/var/www/www.mrlee.dev/
}
#+end_src

It's flawed compared to using ~rsync~, as it won't remove existing files, but it does the job in less than a second or two.

The thing is, this isn't so quick if I want to publish a post from a different computer that doesn't have any programming tools installed. I would have to install ~stack~◊^[1], which is a build tool for Haskell, and then I would have to run ~stack build~. This can take at least half an hour as the command will pull down the correct version of ~GHC~ and a 'snapshot' (basically a huge collection of all the Hackage◊^[2] libraries available for that build) before it even /thinks} about compiling my ◊code{site.hs/ file. It also means to committing a few gigs of storage space for all of that.
The thing is, this isn't so quick if I want to publish a post from a different computer that doesn't have any programming tools installed. I would have to install ~stack~ ◊^[1], which is a build tool for Haskell, and then I would have to run ~stack build~. This can take at least half an hour as the command will pull down the correct version of ~GHC~ and a 'snapshot' (basically a huge collection of all the Hackage◊^[2] libraries available for that build) before it even /thinks/ about compiling my ~site.hs~ file. It also means to committing a few gigs of storage space for all of that.

I like to write from my little Surface Pro when I'm out and about, so I'd rather not do a full-blown compilation on that for the sake of my battery. Enter Azure DevOps Pipelines◊^[3].



@@ 25,44 25,44 @@ Let's do a step-by-step walk through my setup.

-----

◊codeblock['yaml]{
#+begin_src yaml
  trigger:
    - master
  pool:
    vmImage: 'ubuntu-latest'
}
#+end_src

This is pretty much CI boilerplate. The build will run on any PR that targets ~master~, and it uses Ubuntu as the underlying image. I'm not doing any Docker stuff here.

◊codeblock['yaml]{
#+begin_src yaml
  jobs:
  - job: build
    steps: ...
}
#+end_src

I only have a couple of jobs in this pipeline, to keep it simple. The next bunch of steps are nested under this.

◊codeblock['yaml]{
#+begin_src yaml
  - script: |
        mkdir -p ~/.local/bin $(Build.BinariesDirectory)
        curl -L https://get.haskellstack.org/stable/linux-x86_64.tar.gz | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack'
    displayName: Install Stack
}
#+end_src

Won't get far without grabbing the latest stable Stack binary.

◊codeblock['yaml]{
#+begin_src yaml
  - task: Cache@2
    displayName: Cache Stack/GHC snapshot
    inputs:
      key: 'stack | root'
      path: .stack/
      cacheHitVar: 'STACK_SNAPSHOT_RESTORED'
}
#+end_src

Later on there will be a step that runs ~stack build~, which will take about 40 minutes in CI. It would be a waste to repeatedly download all of that, so I'm caching the root stack folder for good measure. The ~cacheHitVar~ is something we will reference later.

◊codeblock['yaml]{
#+begin_src yaml
  - task: Cache@2
    displayName: Cache local stack deps
    inputs:


@@ 73,83 73,83 @@ Later on there will be a step that runs ~stack build~, which will take about 40 

This is the same as the last step, but it's for the dependencies my static site requires. I want to cache these separately so adding a new project dependency doesn't force a full refresh of the Stack snapshot.

◊codeblock['yaml]{
#+begin_src yaml
  - script: |
        export PATH=$HOME/.local/bin:$PATH
        stack --no-terminal --stack-root $(System.DefaultWorkingDirectory)/.stack setup
    displayName: Build Snapshot
    condition: ne(variables.STACK_SNAPSHOT_RESTORED, 'true')
}
#+end_src

Notice the ~STACK_SNAPSHOT_RESTORED~ condition at the bottom there? This step sets up GHC and the Stack snapshot, but only if one wasn't restored from the cache. If the cache has it, then it will have alread been fetched.

◊codeblock['yaml]{
#+begin_src yaml
  - script: |
        export PATH=$HOME/.local/bin:$PATH
        stack --no-terminal --stack-root  $(System.DefaultWorkingDirectory)/.stack build
    displayName: Build Dependencies
    condition: ne(variables.STACK_DEPS_RESTORED, 'true')
}
#+end_src

This is the same as above, but for the project dependencies. So far so good. We're almost done now.

◊codeblock['yaml]{
#+begin_src yaml
  - script: |
        export PATH=$HOME/.local/bin:$PATH
        stack --no-terminal --stack-root $(System.DefaultWorkingDirectory)/.stack install --local-bin-path $(Build.BinariesDirectory)
    displayName: Build Site Executable
}
#+end_src

Since I've already run ~stack build~, this just copies the binary to a different location, which I use to store it as a build artifact. ~Build.BinariesDirectory~ is a special place on the VM to store compiled build artifacts. It doesn't matter where specifically that is, only that it's the same across steps.

◊codeblock['yaml]{
#+begin_src yaml
  - task: PublishBuildArtifacts@1
    displayName: Save static site binary
    inputs:
      pathToPublish: $(Build.BinariesDirectory)
      artifactName: site
}
#+end_src

This is where that binaries directory comes into play, as I can tell Azure to upload everything in there as a build artifact, which I can then refer to in another job. This isn't quite the same as a cache, as a build is not expected to fail if the cache goes missing. It would fail if the binary isn't there though.

So, that's the first step done, but what about actually publishing a post? I have two jobs for that, which are very similar (one for draft posts/staging, one for prod). I'll describe one of them.

◊codeblock['yaml]{
#+begin_src yaml
  - job: deploy_published
    dependsOn: build
    condition: and(succeeded(), eq(variables['build.sourceBranchName'], 'master'))
    steps: ...
}
#+end_src

The key to this step is the condition. This will run only if the ~build~ job was successful, /and/ the branch being built is the master branch. Practically, this only runs if I push straight to master or merge a PR. The staging version runs only on PRs.

◊codeblock['yaml]{
#+begin_src yaml
  - task: DownloadBuildArtifacts@0
    displayName: Download site binary
    inputs:
      artifactName: site
      downloadPath: $(System.DefaultWorkingDirectory)
}
#+end_src

Time to put that binary I compiled to good use. It downloads it into the main working directory and I'll call it directly in a later step. The executable is self-contained (or otherwise dynamically links stuff the image already has), so I don't need to pull down Stack/GHC stuff again.

◊codeblock['yaml]{
#+begin_src yaml
  - script: |
        export PATH=$(System.DefaultWorkingDirectory)/site:$PATH
        chmod +x $(System.DefaultWorkingDirectory)/site/site
        site build
    displayName: Build with published posts
}
#+end_src

This is the same as running ~stack exec site build~ on my local machine. It compiles the static site, so finally I'll have a new version to upload.

◊codeblock['yaml]{
#+begin_src yaml
  - task: InstallSSHKey@0
    displayName: Setup SSH
    inputs:
      knownHostsEntry: '$(NexusKnownHost)'
      sshKeySecureFile: 'nexus_deploy'
}
#+end_src

I host this blog on my own little VPS, which means that the server needs to know that the CI is authorised to connect to it with its SSH key. This is the same as having a deploy key on GitHub, and requires generating a keypair to be stored in CI, with the public key being added to your ~authorized_keys~ file of the appropriate user on the server.



@@ 159,7 159,7 @@ I host this blog on my own little VPS, which means that the server needs to know

There's only step left now, and that's to deploy!

◊codeblock['yaml]{
#+begin_src yaml
  - task: CopyFilesOverSSH@0
    displayName: Deploy to prod
    inputs:


@@ 169,7 169,7 @@ There's only step left now, and that's to deploy!
      targetFolder: '/var/www/www.mrlee.dev'
      cleanTargetFolder: true
      readyTimeout: '20000'
}
#+end_src

This is similar to running ~rsync~ to deploy, except that it knows where to get your private key from and where to connect to. This is defined elsewhere in Azure DevOps, through the UI, rather than in the YAML file.



@@ 183,4 183,4 @@ To see the pipeline in full, you can check out the full YAML file◊^[5]. I've b
  ◊^[3]{◊<>["https://dev.azure.com"]}
  ◊^[4]{◊<>["https://github.com/features/actions"]}
  ◊^[5]{◊<>["https://git.sr.ht/~mrlee/www.kamelasa.dev/tree/9decaf4732dc7bc4510fcf23979af8657bdb01bd/item/azure/pipeline.yml"]}
}
\ No newline at end of file
}

M posts/my-favourite-thing-about-programming.org => posts/my-favourite-thing-about-programming.org +7 -7
@@ 20,24 20,24 @@ Currently I'm playing with Haskell and also taking it a bit more seriously, to s

For example, this is the function I have for generating the estimated reading time you see at the top of every post here:

◊codeblock['haskell]{
#+begin_src haskell
  ertField :: String -> Snapshot -> Context String
  ertField name snapshot = field name $ \item -> do
    body <- itemBody <$> loadSnapshot (itemIdentifier item) snapshot
    let words = length (T.words . T.pack $ body)
    return $ show $ round $ words // 250
}
#+end_src

That's a lot of symbols doing a lot of legwork! And while this is difficult to understand for an untrained eye, it would become more familiar after understanding some of the basic 'rules' of Haskell and the 'styles' of programming you can use. Of course, you can always take it too far:

◊codeblock['haskell]{
#+begin_src haskell
  (👏) = ($)

  ertField name snapshot = field name 👏 \item -> do
    body <- itemBody <$> loadSnapshot (itemIdentifier item) snapshot
    let words = length (T.words . T.pack 👏 body)
    return 👏 show 👏 round 👏 words // 250
}
#+end_src

That should go down well with the Twitter crowd.



@@ 47,13 47,13 @@ Moving on, there's Lisp. My familiarity with Lisp comes from customising my emac

With emacs in particular, it made it trivially easy for me to launch a Rails console inside a deployed Kubernetes pod.

◊codeblock['commonlisp]{
#+begin_src commonlisp
  (defun inf-ruby-console-k8s (env)
    (interactive (list (completing-read "Environment: "
                                        '("dev" "staging" "preprod") nil t)))
    (shell-command (concat "kubectl config use-context " env))
    (inf-ruby-console-run "kubectl exec -it ruby-app -- rails c" (concat "k8s-ruby-" env)))
}
#+end_src

I mapped it to a certain keybinding and a panel would open to the side within a second, ready for me to use. I don't think I'd have the patience to try and reproduce that in, say, VS Code, without using a task runner. Emacs itself is entirely programmable so you don't need to worry about setting up extension boilerplate to make minor modifications.



@@ 67,4 67,4 @@ For as long as that remains true I think I'll always have new toys to play with.

◊footnotes{
  ◊^[1]{◊<>["http://raytracerchallenge.com"]}
}
\ No newline at end of file
}

M posts/rewrite-it-in-lisp.org => posts/rewrite-it-in-lisp.org +8 -8
@@ 20,15 20,15 @@ That was the easy, albeit boring, part. The real challenge was taking the bits a

This is where Pollen gets interesting: each source file is essentially converted into a huge S-expression, known in Pollen as tagged X-expressions. A simple example would look like this:

◊codeblock['racket]{
#+begin_src racket
  (root (div ((class "main")) (p "Hello" (span "world"))))
}
#+end_src

There is no templating language, per-se, in Pollen. The ~◊"◊"~ syntax is a small sugar over normal Lisp syntax, and all you're ultimately doing is calling a function that you've defined somewhere in your code (or in the same file). These functions can do anything--there's nothing special about them--so long as they return a valid X-expression, which means that you don't really have to learn how to extend a language or figure out how to integrate with one.

As such, a functional approach is still the easiest way to add new capabilities to your project, although it may not always be enough if you need to handle state across pages. An example is my implementation for links with footnotes:

◊codeblock['racket]{
#+begin_src racket
  (define (footnotes . refs)
  `(hr 
    (section [(class "footnotes")]


@@ 50,11 50,11 @@ As such, a functional approach is still the easiest way to add new capabilities 
           (role "doc-backlink")
           (href ,(format "#fnref~a" ref-num)))
          "↩"))))
}
#+end_src

I use it like this:

◊codeblock['text]{
#+begin_src text
  ◊"◊"footnotes{
    ◊"◊"^[1]{◊"◊"<>["https://beautifulracket.com/"]}
    ◊"◊"^[2]{◊"◊"<>["https://en.m.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours"]}


@@ 66,7 66,7 @@ I use it like this:

The estimated reading time was also easier to implement than I first thought. Rather than splitting the text into words as I did in the previous version of the site, I figured I'd get a decent enough estimation by dividing the size of the file in bytes by an average English word-length, assuming a reading speed of 250 words a minute.

◊codeblock['racket]{
#+begin_src racket
  (define (post->size post) (number->string (file-size (post->path post))))

  (define average-word-length 4.7)


@@ 77,7 77,7 @@ The estimated reading time was also easier to implement than I first thought. Ra
    (/
     (/ (string->number (post->size post)) average-word-length)
     words-per-minute)))
}
#+end_src

You can see the rest of what I scripted in the main `pollen.rkt` file for this site◊^[4]



@@ 91,4 91,4 @@ Overall, I'm happy with the change. It doesn't take 45 minutes to re-compile my 
  ◊^[3]{◊<>["https://docs.racket-lang.org/pollen"]}
  ◊^[4]{◊<>["https://git.sr.ht/~mrlee/www.kamelasa.dev/tree/main/item/pollen.rkt"]}
  ◊^[5]{◊<>["https://git.sr.ht/~mrlee/www.kamelasa.dev/tree/main/item/redirs.caddy"]}
}
\ No newline at end of file
}

M posts/ruby-sorcery.org => posts/ruby-sorcery.org +17 -17
@@ 15,7 15,7 @@ The first part of this series of posts is all about /Pattern Matching/.

Ruby's pattern matching support, introduced experimentally in 2.7, is a lot more powerful than you may expect. All you need is to replace ~when~ with ~in~ and your ~case~ statements become capable of matching against /anything/.

◊codeblock['ruby]{
#+begin_src ruby
  require 'base64'
  
  class ParsedJson; end;


@@ 42,13 42,13 @@ Ruby's pattern matching support, introduced experimentally in 2.7, is a lot more

  handle_response({ status: :forbidden, code: 403 })
  # NotAllowedError (forbidden)
}
#+end_src

◊h3{Custom destructuring}

You can deeply match any object in Ruby so long as you define a method to represent it as a hash, or a method to represent it as an array. Or both.

◊codeblock['ruby]{
#+begin_src ruby
  class PlayingCard
    attr_reader :value, :colour, :suit
    


@@ 70,11 70,11 @@ You can deeply match any object in Ruby so long as you define a method to repres
      }
    end
  end
}
#+end_src

This ~PlayingCard~ class is now capable of pattern matching.

◊codeblock['ruby]{
#+begin_src ruby
  def face_card?(playing_card)
    case playing_card
    in { value: 'K' | 'Q' | 'J' } then true


@@ 84,13 84,13 @@ This ~PlayingCard~ class is now capable of pattern matching.

  face_card?(PlayingCard.new(value: '3', colour: :red, suit: :spades))
  #=> false
}
#+end_src

◊h3{Pinning}

That's fairly basic, what about pattern matching poker? Matching one card is easy, but suppose you want to match a hand.

◊codeblock['ruby]{
#+begin_src ruby
  class PokerHand
    attr_reader :cards
    


@@ 102,13 102,13 @@ That's fairly basic, what about pattern matching poker? Matching one card is eas
      cards
    end
  end
}
#+end_src

Now that a hand of cards is represented, it should be possible to use pattern matching to find a winning play, say... a Royal Flush. For this to work, /variable pinning/ is required, because a Royal Flush requires the colour and suit to be the same for each card.

This particular solution depends on the hand being ordered, but that's fine, a lot of computational problems become simpler if you sort them first. For the sake of example, assume that has already happened.

◊codeblock['ruby]{
#+begin_src ruby
  def royal_flush?(hand)
    case hand
    in [['1', c, s], ['10', ^c, ^s], ['J', ^c, ^s], ['Q', ^c, ^s], ['K', ^c, ^s]]


@@ 131,7 131,7 @@ This particular solution depends on the hand being ordered, but that's fine, a l

  royal_flush?(my_hand)
  # => true
}
#+end_src

The clever bit here is that the first part of the match (~[1, c, s]~) is used to constrain the rest of the pattern. So if ~c~ is ~:red~, then ~^c~ also has to be ~:red~ in order to match.



@@ 141,7 141,7 @@ You'll see this a lot if you're familiar with Elixir or other languages that do 

Building on the poker example, maybe it's valid to play the Joker, but only if the dealer has allowed it?

◊codeblock['ruby]{
#+begin_src ruby
  def joker_allowed?
    true
  end


@@ 158,7 158,7 @@ Building on the poker example, maybe it's valid to play the Joker, but only if t
  valid_call?(PlayingCard.new(value: :Joker, colour: nil, suit: nil))
  # => joker allowed
  # => true
}
#+end_src

◊h2{Destructuring assignment without ~case~}



@@ 168,7 168,7 @@ In fact, this method also allows you to use pattern matching while destructuring

You also have to be absolutely sure you're matching the right thing.

◊codeblock['ruby]{
#+begin_src ruby
  card = PlayingCard.new(value: '7', suit: :diamonds, colour: :red)

  card in { value: ('1'..'10') => v, suit: :diamonds  => s}


@@ 181,13 181,13 @@ You also have to be absolutely sure you're matching the right thing.
  rescue NoMatchingPatternError
    puts 'son, I am disappoint'
  end
}
#+end_src

◊h2{Optimisations}

If you recall earlier examples, I defined ~destructure_keys(*)~, which meant that I was explicitly ignoring the arguments normally passed to the method. This is useful in simple cases, but when dealing with complex objects you might want to be a bit more thoughtful about how you return a value. For example, converting the entire structure of the object into a hash might not be appropriate.

◊codeblock['ruby]{
#+begin_src ruby
  # When used in pattern matching, this class will only destructure into the provided keys
  
  class PokerHand


@@ 195,7 195,7 @@ If you recall earlier examples, I defined ~destructure_keys(*)~, which meant tha
      cards.map { |card| card.slice(keys) }
    end
  end
}
#+end_src

-----



@@ 207,4 207,4 @@ Check in soon to see another deep-dive into Ruby Sorcery.

◊footnotes{
  ◊^[1]{◊<>["https://docs.ruby-lang.org/en/3.0.0/doc/syntax/pattern_matching_rdoc.html"]}
}
\ No newline at end of file
}

M posts/to-simpler-times.org => posts/to-simpler-times.org +11 -11
@@ 22,7 22,7 @@ Even though it worked it all felt a bit... manky. It's clearly not the way softw

I threw all of that out at one point and moved back over to GitHub Pages, because that's where my repo was. The last few posts here were published in a manner not so dissimilar to this:

◊codeblock['bash]{
#+begin_src bash
  emacs posts/my-new-post.md &
  # write the damn post
  git add posts/my-new-post.md


@@ 42,7 42,7 @@ I threw all of that out at one point and moved back over to GitHub Pages, becaus
  git push -f
  stack exec site build
  #.........
}
#+end_src

It's not the worst thing ever, except that if I switch to a different computer, like my laptop, I have to do a lot of setup to be able to write and deploy. I'd rather focus on my writing and automate the rest of it away, which brings us back to CI and complication.



@@ 50,7 50,7 @@ So, back to basics!

The site has moved once again, back to a VPS hosted somewhere in the UK. Caddy◊^[4] is doing the hard work as an elegant alternative to nginx or apache and for simple setups you can't really go wrong with it if you just want a server with HTTPS by default. Here's how I configured this site:

◊codeblock['caddy]{
#+begin_src caddy
  kamelasa.dev {
    redir https://www.kamelasa.dev{uri}
  }


@@ 59,19 59,19 @@ The site has moved once again, back to a VPS hosted somewhere in the UK. Caddy
    root * /var/www/kamelasa.dev
    file_server
  }
}
#+end_src

Deploying to this server is a case of firing off a couple of ~ssh~, ~scp~ or ~rsync~ requests using a separate user with its own SSH key, and as soon as the command is finished running the changes are visible online.◊^[5]

This leads me to the final bit. Modern tech feels more complicated as it tends towards distributed solutions: put thing /x} here, deploy service ◊em{y/ there, sync them up with webhooks, and hope the network holds up to the task. Earlier tech feels more complicated because the documentation is intricate and detailed and requires some fidgeting around with.
This leads me to the final bit. Modern tech feels more complicated as it tends towards distributed solutions: put thing /x/ here, deploy service /y/ there, sync them up with webhooks, and hope the network holds up to the task. Earlier tech feels more complicated because the documentation is intricate and detailed and requires some fidgeting around with.

It took me just about a day to figure out how to host my own ~apt~ repository for Debian◊^[6], compiling information from various manuals, blog posts and examples. It was mostly a case of creating a GPG key and setting up a correct directory structure for ~apt-ftparchive~◊^[7] to do its business, with a little bit of extra config. I'll go into detail about that another time, but let it be said it does the job tremendously in any Debian-based CI pipeline.

◊codeblock['bash]{
cd www.kamelasa.dev
sudo apt install kamelasa
kamelasa build
}
#+begin_src bash
  cd www.kamelasa.dev
  sudo apt install kamelasa
  kamelasa build
#+end_src

-----



@@ 87,4 87,4 @@ On another note, this site now also left GitHub for Sourcehut◊^[8] and, at ris
  ◊^[7]{◊<>["https://manpages.debian.org/buster/apt-utils/apt-ftparchive.1.en.html"]}
  ◊^[8]{◊<>["https://sourcehut.org"]}
  ◊^[9]{◊<>["https://lists.sr.ht/~mrlee/kamelasa.dev-discuss"]}
}
\ No newline at end of file
}

M posts/using-ruby-c-in-ruby.org => posts/using-ruby-c-in-ruby.org +14 -14
@@ 12,7 12,7 @@ One of those thoughts stuck out in particular, because of how ridiculous it soun

Let's apply some method to this madness and set up some bindings, otherwise we're dead in the water. Let's be descriptive and call our FFI module ~LibRuby~. No naming conflicts at all there, /no sirree/!

◊codeblock['ruby]{
#+begin_src ruby
  require 'ffi'

  module LibRuby


@@ 29,7 29,7 @@ Let's apply some method to this madness and set up some bindings, otherwise we'r
    attach_function :rb_funcall, [:value, :id, :int, :varargs], :value
    attach_function :rb_str_new_cstr, [:string], :value
  end
}
#+end_src

If you look at the code in this module, you'll notice that I used ~attach_variable~ to get access to the Kernel module, and ~attach_function~ for the method calls. The ~:id~ and ~:value~ types are just aliases for ~:pointer~, because ~VALUE~ and ~ID~ in the C API are themselves pointers. It's for the sake of documentation, so it's clearer what order you pass arguments in.



@@ 45,35 45,35 @@ Let's take it from the top and talk through this ungodly incantation. Go ahead a

Once you're done, you can save some keystrokes by importing that module.

◊codeblock['ruby]{
  include LibRuby
}
#+begin_src ruby
  include LibRub
#+end_src

In order to call ~puts~ in Ruby through the C API, we'll need to get a reference to the module it's defined in (~Kernel~), and also get the method name as a symbol (like you might normally do with ~.to_sym~).

◊codeblock['ruby]{
#+begin_src ruby
  kernel = LibRuby.rb_mKernel
  puts_method = rb_intern('puts')
}
#+end_src

Oh, before we continue, better disable the garbage collector. This is a simple way to stop the oscillating turbine from splattering unpleasant substances around the room. (More on that later, but see if you can guess why.)

◊codeblock['ruby]{
#+begin_src ruby
  GC.disable
}
#+end_src

We can't just pass in a normal string to ~puts~ without things going 💥, as everything is an object in Ruby and therefore we need to
get a pointer to a ~String~ instance (or in internal Ruby lingo, one of those ~VALUE~s).

◊codeblock['ruby]{
#+begin_src ruby
  str = rb_str_new_cstr('welcome, mortals')
}

Now we have all of the ingredients to make the actual call, which syntactically and aesthetically blows idiomatic Ruby out of the water. Delicately paste this into your console and you should see the string printed out. You'll also get a return value like ~#<FFI::Pointer address=0x0000000000000008>~, which will refer to ~Qnil~. ~Qnil~ is a pointer to Ruby's ~nil~ object.

◊codeblock['ruby]{
#+begin_src ruby
  rb_funcall(kernel, puts_method, 1, :value, str)
}
#+end_src

Run it again a few times, and with different strings. If you're feeling experimental, attach more functions in ~LibRuby~ and see what else you can print out! Ruby's extension documentation should be a good place to start◊^[3].



@@ 91,6 91,6 @@ Until then, I'll see you further into the abyss.

◊footnotes{
  ◊^[1]{◊<>["https://github.com/ffi/ffi"]}
  ◊^[2]{~`gem install ffi -- --enable-system-libffi`~}
  ◊^[2]{~gem install ffi -- --enable-system-libffi~}
  ◊^[3]{◊<>["https://ruby-doc.org/core-2.7.0/doc/extension_rdoc.html"]}
}
\ No newline at end of file
}