Flashcards in the terminal
Tags: bash, linux
Published: 2022-04-25 08:05:06

Several weeks ago, I wrote a webdriver app in Clojure to pull directory information from congregate websites.  The general idea was this:

> I have trouble remembering names and faces.  What if I had a flashcard system to help me memorize them?

However, I got bored before I could finish.

## Rivendell update

I've been hard at work this week updating Rivendell for Elvish v0.17.  I recently finished testing and documenting the `fun` module, which houses most of the functional pieces.  You can view the docs [here](https://github.com/crinklywrappr/rivendell/blob/update-for-0.17/doc/fun.md).

I have another blog post about how I generated that documentation if you find that interesting.

{% embed https://dev.to/crinklywrappr/generating-docs-from-tests-l64 %}

With the progress made on Rivendell, I think now would be an excellent time to finish the flashcard app.

## Setup instructions

Since Rivendell is currently under development, I am using Elvish's [rsync](https://elv.sh/ref/epm.html#custom-package-domains) functionality.

Clone rivendell and switch to the `update-for-0.17` branch.  Then place this file in `~/.local/share/elvish/lib/dev/epm-domain.cfg`

```json
{
    "method": "rsync",
    "location": <path to rivendell parent directory>,
    "levels": "1"
}
```

Then, add this to your `~/.config/elvish/rc.elv`.

```elvish
use epm
epm:install &silent-if-installed=$true dev/rivendell
epm:upgrade dev/rivendell
use dev/rivendell/fun
```

Last, you'll need a terminal that understands the [Terminal Graphics Protocol](https://sw.kovidgoyal.net/kitty/graphics-protocol/).  I'll be using [Kitty](https://sw.kovidgoyal.net/kitty/).

## Needed functions

The functions we need from Rivendell are [shuffle](https://github.com/crinklywrappr/rivendell/blob/update-for-0.17/doc/fun.md#shuffle) and [destruct](https://github.com/crinklywrappr/rivendell/blob/update-for-0.17/doc/fun.md#destruct).  Next, we need a way to display images.  If you want to replicate my environment, I have this function defined in my `rc.elv` for displaying images.

```elvish
fn icat {|@a| kitty +kitten icat $@a }
```

Next, we need a way to score guesses.  For that, let's define a simple Levenshtein function.  Low scores are better.

```elvish
use dev/rivendell/base b
use dev/rivendell/fun f
use math

fn levenshtein {|w1 w2|
  var cell-value = {|same-char prev-row cur-row col-idx|
    math:min (b:inc $prev-row[$col-idx]) ^
             (b:inc (b:end $cur-row)) ^
             (+ (if $same-char { put 0 } else { put 1 }) ^
                $prev-row[(b:dec $col-idx)])
  }

  var row-idx = 1
  var max-rows = (b:inc (count $w2))
  var @prev-row = (range (b:inc (count $w1)))

  while (!= $row-idx $max-rows) {
    var ch2 = $w2[(b:dec $row-idx)]
    set row-idx = (b:inc $row-idx)
    set prev-row = (f:reduce {|cur-row i|
        var same-char = (eq $w1[(b:dec $i)] $ch2)
        b:append $cur-row ($cell-value $same-char $prev-row $cur-row $i)
    } [$row-idx] (range 1 (count $prev-row)))
  }

  b:end $prev-row
}
```

That code takes advantage of Elvish's module [aliasing feature](https://elv.sh/ref/language.html#importing-modules-with-use) and also demonstrates a few more simple Rivendell functions, [reduce](https://github.com/crinklywrappr/rivendell/blob/update-for-0.17/doc/fun.md#reduce) being the most interesting.

I placed this code in a file called `levenshtein.elv` and imported it with `use ./levenshtein l`.

Next, we need a function to tie these together.  It's not fancy.  I just defined this in the terminal.

```elvish
fn guess {|name image|
  icat $image; echo
  var guess = (read-line)
  var score = (l:levenshtein $name $guess)
  put $name $score
}
```

We have all the components now.  So let's pull our directory info and start scoring guesses.


## Running the flashcard "app"

Is this thing categorically different from an app?  Probably.

After you've built the [congr](https://github.com/crinklywrappr/congr/) project from the first post, execute with:

```elvish
var @directory = (java -jar target/uberjar/congr-0.1.0-SNAPSHOT-standalone.jar site username password)
```

That last command may take several seconds.

Next, shuffle it.

```elvish
var @shuffled = (each {|x| echo $x | from-json} $directory | fun:shuffle)
```

Last, start guessing.

```elvish
each (fun:destruct $guess~) $shuffled
```

For privacy reasons I won't show actual names and photos from the directory, but you can view a screenshot with mocked information from `knowyourmeme.com`.

![action shot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uoxrxzknrltmg2onmsj1.png)
 

## Conclusion

I always envisioned a stupid simple terminal "app" for this project, so it appears I succeeded.  Furthermore, I appreciate that Clojure and Elvish worked together to make the experience genuinely hacker-ish.

Happy hacking!