clone url: git://git.m455.casa/m455.casa
markup/posts/2023/a-shitty-config-file-parser-in-fennel.txt
1 | title: a shitty config file parser in fennel |
2 |
|
3 | 2023-04-28 21:51 |
4 |
|
5 | i'm working on rewriting my blog enging again (lol) in fennel (again), and made |
6 | this simple config file parser for the following config file format: |
7 |
|
8 | ``` |
9 | name = someone's name |
10 | domain = verycoolwebsite.com |
11 | email = awsick@cool.com |
12 | ``` |
13 |
|
14 | it turned out to be pretty easy too! i made use of fennel's threader macro, |
15 | `->`, which just takes an initial value and an arbitrary amount of functions, |
16 | and passes the initial value as the first argument to the next function, and |
17 | then passes the return value of the first function as the first argument to the |
18 | next functions, and repeats for every function that was provided. |
19 |
|
20 | i think the main reason i like this function is that it makes me clearly see the |
21 | steps a value goes through as it gets passed from one function to another. it |
22 | has a nice top-to-bottom visual, for me at least. in scheme, i would usually do |
23 | a series of `let*` statements, which became a little noisy for me. i could have |
24 | always imported the [https://wiki.call-cc.org/eggref/5/clojurian|clojurian] egg |
25 | in chicken scheme to get this, or use compose, but, alas, i am in the mood to |
26 | rewrite shit. |
27 |
|
28 | anyway, here's what it looks like, along with a couple of helper functions i |
29 | wrote, and some help from the adored lua library, |
30 | [https://github.com/rxi/lume|lume], assuming you have a `modules` directory in |
31 | the same directory as this source code, and the `lume.lua` file in the `modules` |
32 | directory: |
33 |
|
34 | ``` |
35 | (local lume (require "modules/lume")) |
36 |
|
37 | ;; converts a file to a sequence of lines |
38 | (fn file->lines [path] |
39 | (with-open [f (io.open path :r)] |
40 | (icollect [i (f:lines)] i))) |
41 |
|
42 | ;; changes "key = val" to ["key" "val"] |
43 | (fn split-trim [str del] |
44 | (-> (lume.split str del) |
45 | (lume.map lume.trim))) |
46 |
|
47 | (fn config-file->table [file] |
48 | (-> (file->lines file) |
49 | (lume.map #(split-trim $1 "=")) |
50 | (lume.map #(let [[k v] $1] {k v})) |
51 | (table.unpack) |
52 | (lume.merge))) |
53 | ``` |
54 |
|
55 | you can kinda see how it works if i toss some comments in there: |
56 |
|
57 | ``` |
58 | (fn config-file->table [file] |
59 | (-> (file->lines file) ;; => ["key1 = val" "key2 = val"] |
60 | (lume.map #(split-trim $1 "=")) ;; => [["key1" "val"] ["key2" "val"]] |
61 | (lume.map #(let [[k v] $1] {k v})) ;; => [{:key1 "val"} {:key2 "val"}] |
62 | (table.unpack) ;; => {:key1 "val"} {:key2 "val"} |
63 | (lume.merge))) ;; {:key1 "val" :key2 "val"} |
64 | ``` |
65 |
|
66 | then all you gotta do is just assign the `config-file->table` function's return |
67 | value to a variable, and then reference the values in the table using the keys, |
68 | like you normally would when interacting with a table. |
69 |
|
70 | for example, if we have the following config file format in a file called |
71 | `config.txt`: |
72 |
|
73 | ``` |
74 | name = someone's name |
75 | domain = verycoolwebsite.com |
76 | email = awsick@cool.com |
77 | ``` |
78 |
|
79 | then we can assign the `config-file->table` function's return value to a local |
80 | variable, `config`, and reference that variable as a table using one of the |
81 | following approaches: |
82 |
|
83 | ``` |
84 | ;; approach 1 |
85 | (let [config (config-file->table "config.txt")] |
86 | (. config :name)) |
87 |
|
88 | ;; approach 2 |
89 | (let [config (config-file->table "config.txt")] |
90 | config.name) |
91 | ``` |
92 |
|