So originally I wanted to write a up a nice example to do the replacements via the scanf macro:
[https://nim-lang.github.io/Nim/strscans.html](https://nim-lang.github.io/Nim/strscans.html) by defining tuples of strings to match against and their replacements, but I hit a dead end, because an element of a const tuple doesn't count as a static string for the pattern. Also scanf turned out to be more problematic than I thought, because the $* term does not like to match any string until the end. But since your book is (at least partly) about Nim macros and writing macros is fun, I built the following even longer version of your code, haha. It also includes a custom matcher that matches anything until the end of the string. # File: web.nim import strutils, os, strscans, macros let input = open("rage.md") let form = """ <p> <form name='form' method='get'> <input type='type='text' name='Name'> </form> </p> """ echo "Content-type: text/html\n\n<html>" echo """ <head> <meta http-equiv= 'Content-type' content= 'text/html; charset=utf-8' /> </head> """ echo "<body>" proc rest(input: string; match: var string, start: int): int = ## matches until the end of the string match = input[start .. input.high] # result is either 1 (string is empty) or the number of found chars result = max(1, input.len - start) macro match(args, line: typed): untyped = ## match the `args` via `scanf` in `line`. `args` must be a `[]` of ## `(scanf string matcher, replacement string)` tuples, where the latter ## has to include a single `$#` to indicate the position of the replacement. ## The order of the `args` is important, since an if statement is built. let argImpl = args.getImpl expectKind argImpl, nnkBracket result = newStmtList() let matched = genSym(nskVar, "matched") result.add quote do: var `matched`: string var ifStmt = nnkIfStmt.newTree() for el in argImpl: expectKind el, nnkTupleConstr let toMatch = el[0] let toReplace = el[1] let ifBody = nnkStmtList.newTree(nnkCall.newTree(ident"echo", nnkCall.newTree(ident"%", toReplace, matched)), nnkAsgn.newTree(matched, newLit(""))) let ifCond = nnkCall.newTree(ident"scanf", line, toMatch, matched) ifStmt.add nnkElifBranch.newTree(ifCond, ifBody) result.add ifStmt echo result.repr const h1title = ("# ${rest}", "<h2>$#</h2>") const h2title = ("## ${rest}", "<h1>$#</h1>") const elseLine = ("${rest}", "$#<br/>") const replacements = [h1title, h2title, elseLine] for line in input.lines: match(replacements, line) # produces: # var matched: string # if scanf("# ${rest}", line, matched): # echo h1title[1] % matched # if scanf("## ${rest}", line, matched): # echo h2title[1] % matched # if scanf("${rest}", line, matched): # echo elseLine[1] % matched echo form let qs = getEnv("QUERY_STRING", "none").split({'+'}).join(" ") if qs != "none" and qs.len > 0: let output = open("visitors.txt", fmAppend) write(output, qs&"\n") output.close let inputVisitors= open("visitors.txt") for line in inputVisitors.lines: match(replacements, line) inputVisitors.close echo "</body></html>" input.close Run This is totally not practicle I'd say and one's better off writing something by hand or using the excellent [https://github.com/zevv/npeg](https://github.com/zevv/npeg) by @zevv. Still fun though. And if someone wants to improve on this... Finally, to just remove a prefix of a string, you may just use removePrefix from strutils: [https://nim-lang.github.io/Nim/strutils.html#removePrefix%2Cstring%2Cstring](https://nim-lang.github.io/Nim/strutils.html#removePrefix%2Cstring%2Cstring) Note that it only works inplace on string. You could use the new outplace though: [https://github.com/nim-lang/Nim/pull/12599](https://github.com/nim-lang/Nim/pull/12599)