Skip to content

Commit

Permalink
feat: string.Template.substitute
Browse files Browse the repository at this point in the history
  • Loading branch information
litlighilit committed Jun 7, 2024
1 parent 2d824f8 commit c330f09
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 0 deletions.
66 changes: 66 additions & 0 deletions src/pylib/Lib/string.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@

import ../pystring
import ./collections/abc
from std/strutils import `%`
import std/macros

const
ascii_lowercase* = str "abcdefghijklmnopqrstuvwxyz"
Expand Down Expand Up @@ -34,3 +37,66 @@ func capwords*(a: StringLike, sep: StringLike): PyStr =
result += pystring.capitalize(word)
result += ssep

type Template* = distinct string ##[
.. hint:: Currently inhert `Template` is not supported.
.. warning:: Currently in `substitute`,
.. warning:: Currently `substitute` is implemented via `%` in std/strutils,
which will causes two different behaviors from Python's Template:
1. the variables are compared with `cmpIgnoreStyle`,
whereas in Python they are just compared 'ignorecase' by default.
2. digit or `#` following the dollar (e.g. `$1`) is allowed,
and will be substituted by variable at such position,
whereas in Python such will cause `ValueError`.
]##

func substitute*(templ: Template): PyStr = str templ
macro substitute*(templ: Template, kws: varargs[untyped]): PyStr =
## `Template.substitute(**kws)`
##
var arrNode = newNimNode nnkBracket
for kw in kws:
expectKind kw, nnkExprEqExpr
arrNode.add newLit $kw[0]
arrNode.add kw[1]
result = newCall(bindSym("%"), newCall("string", templ), arrNode)
result = newCall(bindSym"str", result)

macro substitute*(templ: Template, mapping: Mapping, kws: varargs[untyped]): PyStr =
## `Template.substitute(mapping, **kws)`
##
## where `kws` is preferred if the same key occurs in `mapping`

# nim's `%` in std/strutils uses first key-value pair found,
# so put `kws` in the front of seq
let seqVar = genSym(nskVar, "subsRes")
let seqDef = if kws.len == 0:
newNimNode(nnkVarSection).add(
nnkIdentDefs.newTree(seqVar,
parseExpr"seq[system.string]", newEmptyNode()))
else:
var arrNode = newNimNode nnkBracket # will be prefixed with `@` to become a seq
for kw in kws:
expectKind kw, nnkExprEqExpr
arrNode.add newLit $kw[0]
arrNode.add newCall("$", kw[1])
newVarStmt(seqVar, prefix(arrNode, "@"))
result = newStmtList()
result.add seqDef
let reprId = ident("repr")
let mappingId = if mapping.kind in {nnkIdent, nnkSym}:
mapping
else:
let mapId = genSym(nskLet, "mappingIdent")
result.add newLetStmt(mapId, mapping)
mapId
result.add quote do:
for k in `mappingId`.keys():
add `seqVar`, k.`reprId`
add `seqVar`, `mappingId`[k].`reprId`
var res = newCall(bindSym("%"), newCall("string", templ), seqVar)
res = newCall(bindSym"str", res)
result.add res

result = newBlockStmt result # make sure seq is destoryed
7 changes: 7 additions & 0 deletions tests/tlib.nim
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ test "Lib/string":
check "hello δδ".capwords == "Hello Δδ" ## support Unicode
check "01234".capwords == "01234"

let templ = Template("$who likes $what")
check templ.substitute(who="tim", what="kung pao") == "tim likes kung pao"

expect ValueError:
let d = dict(who="tim")
discard templ.substitute(d)

# TODO: more tests.
test "Lib/math":
checkpoint "log"
Expand Down

0 comments on commit c330f09

Please sign in to comment.