git.m455.casa

m455.casa

clone url: git://git.m455.casa/m455.casa


posts/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