filecompile-timefile-readnim-lang

How to read in files with a specific file ending at compile time in nim?


I am working on a desktop application using nim's webgui package, which sort of works like electron in that it renders a gui using HTML + CSS + JS. However, instead of bundling its own browser and having a backend in node, it uses the browser supplied by the OS (Epiphany under Linux/GNOME, Edge under Windows, Safari under iOS) and allows writing the backend in nim.

In that context I am basically writing an SPA in Angular and need to load in the HTML, JS and CSS files at compile-time into my binary. Reading from a known absolute filepath is not an issue, you can use nim's staticRead method for that. However, I would like to avoid having to adjust the filenames in my application code all the time, e.g. when a new build of the SPA changes a file name from main.a72efbfe86fbcbc6.js to main.b72efbfe86fbcbc6.js.

There is an iterator in std/os that you can use at runtime called walkFiles and walkPattern, but these fail when used at compileTime!

import std/[os, sequtils, strformat, strutils]

const resourceFolder = "/home/philipp/dev/imagestable/html" # Put into config file
const applicationFiles = toSeq(walkFiles(fmt"{resourceFolder}/*"))
/home/philipp/.choosenim/toolchains/nim-#devel/lib/pure/os.nim(2121, 11) Error: cannot 'importc' variable at compile time; glob

How do I get around this?


Solution

  • Thanks to enthus1ast from nim's discord server I arrived at an answer: using the collect macro with the walkDir iterator.

    The walkDir iterator does not make use of things that are only available at runtime and thus can be safely used at compiletime. With the collect macro you can iterate over all your files in a specific directory and collect their paths into a compile-time seq!

    Basically you start writing collect-block, which is a simple for-loop that at its end evaluates to some form of value. The collect macro will put them all into a seq at the end.

    The end result looks pretty much like this:

    import std/[sequtils, sugar, strutils, strformat, os]
    import webgui
    
    const resourceFolder = "/home/philipp/dev/imagestable/html"
    
    proc getFilesWithEnding(folder: string, fileEnding: string): seq[string] {.compileTime.} =
      result = collect:
        for path in walkDir(folder):
          if path.path.endswith(fmt".{fileEnding}"): path.path
    
    proc readFilesWithEnding(folder: string, fileEnding: string): seq[string] {.compileTime.} =
      result = getFilesWithEnding(folder, fileEnding).mapIt(staticRead(it))