diff options
author | bptato <nincsnevem662@gmail.com> | 2025-04-09 02:38:04 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2025-04-11 19:04:56 +0200 |
commit | c908f05f03bac0cbccd14ab13ea8d0df8b087611 (patch) | |
tree | 7b2999729b66c6179340abdcf2efd76c065b201c | |
parent | 581aa021d4b30075d01b892e78bf4757e8942c4e (diff) | |
download | chawan-c908f05f03bac0cbccd14ab13ea8d0df8b087611.tar.gz |
config: unify table arrays and tables
[[siteconf]] now just means [siteconf.0], etc. So you can now override parts of default siteconfs/omnirules, e.g. to change the Google search substitute-url, etc. To celebrate this, I've added some more default search engines: * wk: -> Wikipedia * wd: -> Wiktionary * ms: -> Marginalia Search These can be replaced by setting e.g. omnirule.wk = {}, etc. Also, siteconf = {} can be used to clear pre-defined siteconfs. This is an unfortunate deviation in semantics from TOML, but in practice the way it worked before didn't match the spec either, so at least it is now consistent.
-rw-r--r-- | doc/cha-config.5 | 49 | ||||
-rw-r--r-- | doc/config.md | 49 | ||||
-rw-r--r-- | res/config.toml | 27 | ||||
-rw-r--r-- | src/config/config.nim | 82 | ||||
-rw-r--r-- | src/config/toml.nim | 95 | ||||
-rw-r--r-- | src/local/pager.nim | 6 | ||||
-rw-r--r-- | src/main.nim | 20 | ||||
-rw-r--r-- | todo | 7 |
8 files changed, 195 insertions, 140 deletions
diff --git a/doc/cha-config.5 b/doc/cha-config.5 index 066d4bd2..af553073 100644 --- a/doc/cha-config.5 +++ b/doc/cha-config.5 @@ -5,21 +5,24 @@ .SH Configuration of Chawan Chawan supports configuration of various options like keybindings, user stylesheets, site preferences, etc. -The configuration format is almost toml, with the following exceptions: +The configuration format is similar to toml, with the following +exceptions: .IP \[bu] 2 Inline tables may span across multiple lines. .IP \[bu] 2 -Table arrays can be cleared by setting a variable by the same to the -empty array. -This allows users to disable default table array rules. -.PP -Example: -.IP -.EX -omnirule = [] # note: this must be placed at the beginning of the file. - -[[omnirule]] # this is legal. all default omni\-rules are now disabled. -.EE +Regular tables (\f[CR][table]\f[R]) and inline tables +(\f[CR]table = {}\f[R]) have different semantics. +The first is additive, meaning old values are not removed. +The second is destructive, and clears all definitions in the table +specified. +.IP \[bu] 2 +For backwards compatibility, \f[CR]table = []\f[R] is equivalent to +\f[CR]table = {}\f[R]. +.IP \[bu] 2 +\f[CR][[table\-array]]\f[R] is sugar for \f[CR][table\-array.n]\f[R], +where \f[CR]n\f[R] is the number of declared table arrays. +For example, you can declare anonymous siteconfs using the syntax +\f[CR][[siteconf]]\f[R]. .PP The canonical configuration file path is \[ti]/.chawan/config.toml, but the search path accommodates XDG basedirs as well: @@ -956,7 +959,9 @@ T} .SS Omnirule The omni\-bar (by default opened with C\-l) can be used to perform searches using omni\-rules. -These are to be placed in the table array \f[CR][[omnirule]]\f[R]. +These are to be specified as sub\-keys to table \f[CR][omnirule]\f[R]. +(The sub\-key itself is ignored; you can use anything as long it +doesn\[cq]t conflict with other keys.) .PP Examples: .IP @@ -964,11 +969,13 @@ Examples: # Search using DuckDuckGo Lite. # (This rule is included in the default config, although C\-k now invokes # Google search.) -[[omnirule]] +[omnirule.ddg] match = \[aq]\[ha]ddg:\[aq] substitute\-url = \[aq](x) => \[dq]https://lite.duckduckgo.com/lite/?kp=\-1&kd=\-1&q=\[dq] + encodeURIComponent(x.split(\[dq]:\[dq]).slice(1).join(\[dq]:\[dq]))\[aq] # Search using Wikipedia, Firefox\-style. +# The [[omnirule]] syntax introduces an anonymous omnirule; it is +# equivalent to the named one. [[omnirule]] match = \[aq]\[ha]\[at]wikipedia\[aq] substitute\-url = \[aq](x) => \[dq]https://en.wikipedia.org/wiki/Special:Search?search=\[dq] + encodeURIComponent(x.replace(/\[at]wikipedia/, \[dq]\[dq]))\[aq] @@ -1011,7 +1018,9 @@ T} .TE .SS Siteconf Configuration options can be specified for individual sites. -Entries are to be placed in the table array \f[CR][[siteconf]]\f[R]. +Entries are to be specified as sub\-keys to table \f[CR][siteconf]\f[R]. +(The sub\-key itself is ignored; you can use anything as long it +doesn\[cq]t conflict with other keys.) .PP Most siteconf options can also be specified globally; see the \[lq]overrides\[rq] field. @@ -1020,12 +1029,12 @@ Examples: .IP .EX # Enable cookies on the orange website for log\-in. -[[siteconf]] +[siteconf.hn] url = \[aq]https://news\[rs].ycombinator\[rs].com/.*\[aq] cookie = true # Redirect npr.org to text.npr.org. -[[siteconf]] +[siteconf.npr] host = \[aq](www\[rs].)?npr\[rs].org\[aq] rewrite\-url = \[aq]\[aq]\[aq] (x) => { @@ -1037,18 +1046,20 @@ x.pathname = s.at(s.length > 2 ? \-2 : 1); \[aq]\[aq]\[aq] # Allow cookie sharing on *sr.ht domains. -[[siteconf]] +[siteconf.sr\-ht] host = \[aq](.*\[rs].)?sr\[rs].ht\[aq] # either \[aq]something.sr.ht\[aq] or \[aq]sr.ht\[aq] cookie = true # enable cookies (read\-only; use \[dq]save\[dq] to persist them) share\-cookie\-jar = \[aq]sr.ht\[aq] # use the cookie jar of \[aq]sr.ht\[aq] for all matched hosts # Use the \[dq]vector\[dq] skin on Wikipedia. +# The [[siteconf]] syntax introduces an anonymous siteconf; it is +# equivalent to the above ones. [[siteconf]] url = \[aq]\[ha]https?://[a\-z]+\[rs].wikipedia\[rs].org/wiki/(?!.*useskin=.*)\[aq] rewrite\-url = \[aq]x => x.searchParams.append(\[dq]useskin\[dq], \[dq]vector\[dq])\[aq] # Make imgur send us images. -[[siteconf]] +[siteconf.imgur] host = \[aq](i\[rs].)?imgur\[rs].com\[aq] default\-headers = { User\-Agent = \[dq]Mozilla/5.0 chawan\[dq], diff --git a/doc/config.md b/doc/config.md index 75905e8c..3cb68e86 100644 --- a/doc/config.md +++ b/doc/config.md @@ -5,19 +5,19 @@ MANOFF --> # Configuration of Chawan Chawan supports configuration of various options like keybindings, user -stylesheets, site preferences, etc. The configuration format is almost -toml, with the following exceptions: +stylesheets, site preferences, etc. The configuration format is similar +to toml, with the following exceptions: * Inline tables may span across multiple lines. -* Table arrays can be cleared by setting a variable by the same to the - empty array. This allows users to disable default table array rules. - -Example: -``` -omnirule = [] # note: this must be placed at the beginning of the file. - -[[omnirule]] # this is legal. all default omni-rules are now disabled. -``` +* Regular tables (`[table]`) and inline tables (`table = {}`) have + different semantics. The first is additive, meaning old values are + not removed. The second is destructive, and clears all definitions in + the table specified. +* For backwards compatibility, `table = []` is equivalent to + `table = {}`. +* `[[table-array]]` is sugar for `[table-array.n]`, where `n` is the + number of declared table arrays. For example, you can declare + anonymous siteconfs using the syntax `[[siteconf]]`. The canonical configuration file path is ~/.chawan/config.toml, but the search path accommodates XDG basedirs as well: @@ -760,19 +760,24 @@ protocol.</td> ## Omnirule -The omni-bar (by default opened with C-l) can be used to perform searches using -omni-rules. These are to be placed in the table array `[[omnirule]]`. +The omni-bar (by default opened with C-l) can be used to perform +searches using omni-rules. These are to be specified as sub-keys to table +`[omnirule]`. (The sub-key itself is ignored; you can use anything as +long it doesn't conflict with other keys.) Examples: + ``` # Search using DuckDuckGo Lite. # (This rule is included in the default config, although C-k now invokes # Google search.) -[[omnirule]] +[omnirule.ddg] match = '^ddg:' substitute-url = '(x) => "https://lite.duckduckgo.com/lite/?kp=-1&kd=-1&q=" + encodeURIComponent(x.split(":").slice(1).join(":"))' # Search using Wikipedia, Firefox-style. +# The [[omnirule]] syntax introduces an anonymous omnirule; it is +# equivalent to the named one. [[omnirule]] match = '^@wikipedia' substitute-url = '(x) => "https://en.wikipedia.org/wiki/Special:Search?search=" + encodeURIComponent(x.replace(/@wikipedia/, ""))' @@ -808,8 +813,10 @@ returned, it will be parsed instead of the old one.</td> ## Siteconf -Configuration options can be specified for individual sites. Entries are -to be placed in the table array `[[siteconf]]`. +Configuration options can be specified for individual sites. Entries +are to be specified as sub-keys to table `[siteconf]`. (The sub-key +itself is ignored; you can use anything as long it doesn't conflict with +other keys.) Most siteconf options can also be specified globally; see the "overrides" field. @@ -817,12 +824,12 @@ Most siteconf options can also be specified globally; see the Examples: ``` # Enable cookies on the orange website for log-in. -[[siteconf]] +[siteconf.hn] url = 'https://news\.ycombinator\.com/.*' cookie = true # Redirect npr.org to text.npr.org. -[[siteconf]] +[siteconf.npr] host = '(www\.)?npr\.org' rewrite-url = ''' (x) => { @@ -834,18 +841,20 @@ rewrite-url = ''' ''' # Allow cookie sharing on *sr.ht domains. -[[siteconf]] +[siteconf.sr-ht] host = '(.*\.)?sr\.ht' # either 'something.sr.ht' or 'sr.ht' cookie = true # enable cookies (read-only; use "save" to persist them) share-cookie-jar = 'sr.ht' # use the cookie jar of 'sr.ht' for all matched hosts # Use the "vector" skin on Wikipedia. +# The [[siteconf]] syntax introduces an anonymous siteconf; it is +# equivalent to the above ones. [[siteconf]] url = '^https?://[a-z]+\.wikipedia\.org/wiki/(?!.*useskin=.*)' rewrite-url = 'x => x.searchParams.append("useskin", "vector")' # Make imgur send us images. -[[siteconf]] +[siteconf.imgur] host = '(i\.)?imgur\.com' default-headers = { User-Agent = "Mozilla/5.0 chawan", diff --git a/res/config.toml b/res/config.toml index d0a1a980..26d23485 100644 --- a/res/config.toml +++ b/res/config.toml @@ -107,16 +107,37 @@ force-lines = false force-pixels-per-column = false force-pixels-per-line = false -[[omnirule]] +[omnirule.ddg] match = '^ddg:' substitute-url = 'x => "https://lite.duckduckgo.com/lite/?kp=-1&kd=-1&q=" + encodeURIComponent(x.split(":").slice(1).join(":"))' -[[omnirule]] +[omnirule.go] match = '^go:' substitute-url = 'x => `https://www.google.com/search?gbv=1&ucbcb=1&oe=UTF-8&q=${x.split(":").slice(1).join(":")}`' +[omnirule.wk] +match = '^wk:' +substitute-url = ''' +x => "https://en.wikipedia.org/wiki/Special:Search?search=" + + encodeURIComponent(x.split(":").slice(1).join(":")) +''' + +[omnirule.wd] +match = '^wd:' +substitute-url = ''' +x => "https://en.wiktionary.org/w/index.php?title=Special:Search&search=" + + encodeURIComponent(x.split(":").slice(1).join(":")) +''' + +[omnirule.ms] +match = '^ms:' +substitute-url = ''' +x => "https://marginalia-search.com/search?query=" + + encodeURIComponent(x.split(":").slice(1).join(":")); +''' + # strip tracking -[[siteconf]] +[siteconf.google-com] url = '^https?://(www\.)?google\.com' rewrite-url = ''' x => { diff --git a/src/config/config.nim b/src/config/config.nim index 5ca8cec1..c17ceae4 100644 --- a/src/config/config.nim +++ b/src/config/config.nim @@ -88,7 +88,7 @@ type userStyle*: Option[StyleString] OmniRule* = ref object - match*: Regex + match*: Option[Regex] substituteUrl*: Option[JSValueFunction] StartConfig = object @@ -183,8 +183,8 @@ type userStyle*: StyleString #TODO getset Config* = ref object - jsctx*: JSContext jsvfns*: seq[JSValueFunction] + arraySeen*: TableRef[string, int] # table arrays seen dir* {.jsget.}: string `include` {.jsget.}: seq[ChaPathResolved] start* {.jsget.}: StartConfig @@ -198,8 +198,8 @@ type display* {.jsget.}: DisplayConfig #TODO getset protocol*: Table[string, ProtocolConfig] - siteconf*: seq[SiteConfig] - omnirule*: seq[OmniRule] + siteconf*: OrderedTable[string, SiteConfig] + omnirule*: OrderedTable[string, OmniRule] cmd*: CommandConfig page* {.jsget.}: ActionMap line* {.jsget.}: ActionMap @@ -325,6 +325,7 @@ proc readUserStylesheet(outs: var string; dir, file: string): Err[string] = ok() type ConfigParser = object + jsctx: JSContext config: Config dir: string warnings: seq[string] @@ -365,12 +366,12 @@ proc parseConfigValue(ctx: var ConfigParser; x: var CSSConfig; v: TomlValue; k: string): Err[string] proc parseConfigValue[U; V](ctx: var ConfigParser; x: var Table[U, V]; v: TomlValue; k: string): Err[string] +proc parseConfigValue[U; V](ctx: var ConfigParser; x: var OrderedTable[U, V]; + v: TomlValue; k: string): Err[string] proc parseConfigValue[U; V](ctx: var ConfigParser; x: var TableRef[U, V]; v: TomlValue; k: string): Err[string] proc parseConfigValue[T](ctx: var ConfigParser; x: var set[T]; v: TomlValue; k: string): Err[string] -proc parseConfigValue(ctx: var ConfigParser; x: var TomlTable; v: TomlValue; - k: string): Err[string] proc parseConfigValue(ctx: var ConfigParser; x: var Regex; v: TomlValue; k: string): Err[string] proc parseConfigValue(ctx: var ConfigParser; x: var URL; v: TomlValue; @@ -407,8 +408,10 @@ proc typeCheck(v: TomlValue; t: set[TomlValueType]; k: string): Err[string] = proc parseConfigValue(ctx: var ConfigParser; x: var object; v: TomlValue; k: string): Err[string] = ?typeCheck(v, tvtTable, k) + if v.tab.clear: + x = default(typeof(x)) for fk, fv in x.fieldPairs: - when typeof(fv) isnot JSContext|seq[JSValueFunction]: + when fk notin ["jsvfns", "arraySeen", "dir"]: let kebabk = camelToKebabCase(fk) if kebabk in v: let kkk = if k != "": @@ -420,29 +423,39 @@ proc parseConfigValue(ctx: var ConfigParser; x: var object; v: TomlValue; proc parseConfigValue(ctx: var ConfigParser; x: var ref object; v: TomlValue; k: string): Err[string] = - new(x) + ?typeCheck(v, tvtTable, k) + if x == nil: + new(x) ctx.parseConfigValue(x[], v, k) proc parseConfigValue[U, V](ctx: var ConfigParser; x: var Table[U, V]; v: TomlValue; k: string): Err[string] = ?typeCheck(v, tvtTable, k) - x.clear() + if v.tab.clear: + x.clear() + for kk, vv in v: + let kkk = k & "[" & kk & "]" + ?ctx.parseConfigValue(x.mgetOrPut(kk, default(V)), vv, kkk) + ok() + +proc parseConfigValue[U, V](ctx: var ConfigParser; x: var OrderedTable[U, V]; + v: TomlValue; k: string): Err[string] = + ?typeCheck(v, tvtTable, k) + if v.tab.clear: + x.clear() for kk, vv in v: - var y: V let kkk = k & "[" & kk & "]" - ?ctx.parseConfigValue(y, vv, kkk) - x[kk] = y + ?ctx.parseConfigValue(x.mgetOrPut(kk, default(V)), vv, kkk) ok() proc parseConfigValue[U, V](ctx: var ConfigParser; x: var TableRef[U, V]; v: TomlValue; k: string): Err[string] = ?typeCheck(v, tvtTable, k) - x = TableRef[U, V]() + if v.tab.clear or x == nil: + x = TableRef[U, V]() for kk, vv in v: - var y: V let kkk = k & "[" & kk & "]" - ?ctx.parseConfigValue(y, vv, kkk) - x[kk] = y + ?ctx.parseConfigValue(x.mgetOrPut(kk, default(V)), vv, kkk) ok() proc parseConfigValue(ctx: var ConfigParser; x: var bool; v: TomlValue; @@ -471,20 +484,12 @@ proc parseConfigValue[T](ctx: var ConfigParser; x: var seq[T]; v: TomlValue; ?ctx.parseConfigValue(y, v, k) x = @[y] else: - if not v.ad: - x.setLen(0) for i in 0 ..< v.a.len: var y: T ?ctx.parseConfigValue(y, v.a[i], k & "[" & $i & "]") x.add(y) ok() -proc parseConfigValue(ctx: var ConfigParser; x: var TomlTable; v: TomlValue; - k: string): Err[string] = - ?typeCheck(v, {tvtTable}, k) - x = v.tab - ok() - proc parseConfigValue(ctx: var ConfigParser; x: var Charset; v: TomlValue; k: string): Err[string] = ?typeCheck(v, tvtString, k) @@ -649,10 +654,10 @@ proc parseConfigValue(ctx: var ConfigParser; x: var URL; v: TomlValue; proc parseConfigValue(ctx: var ConfigParser; x: var JSValueFunction; v: TomlValue; k: string): Err[string] = ?typeCheck(v, tvtString, k) - let fun = ctx.config.jsctx.eval(v.s, "<config>", JS_EVAL_TYPE_GLOBAL) + let fun = ctx.jsctx.eval(v.s, "<config>", JS_EVAL_TYPE_GLOBAL) if JS_IsException(fun): - return err(k & ": " & ctx.config.jsctx.getExceptionMsg()) - if not JS_IsFunction(ctx.config.jsctx, fun): + return err(k & ": " & ctx.jsctx.getExceptionMsg()) + if not JS_IsFunction(ctx.jsctx, fun): return err(k & ": not a function") x = JSValueFunction(fun: fun) ctx.config.jsvfns.add(x) # so we can clean it up on exit @@ -806,12 +811,16 @@ proc parseConfigValue(ctx: var ConfigParser; x: var DeprecatedStyleString; ctx.parseConfigValue(string(x), v, k) proc parseConfig*(config: Config; dir: string; buf: openArray[char]; - warnings: var seq[string]; name = "<input>"; laxnames = false): Err[string] + warnings: var seq[string]; jsctx: JSContext; name: string; + laxnames = false): Err[string] proc parseConfig(config: Config; dir: string; t: TomlValue; - warnings: var seq[string]): Err[string] = - var ctx = ConfigParser(config: config, dir: dir) + warnings: var seq[string]; jsctx: JSContext): Err[string] = + var ctx = ConfigParser(config: config, dir: dir, jsctx: jsctx) ?ctx.parseConfigValue(config[], t, "") + for name, value in config.omnirule: + if value.match.isNone: + return err("omnirule." & name & ": missing match regex") #TODO: for omnirule/siteconf, check if substitution rules are specified? while config.`include`.len > 0: #TODO: warn about recursive includes @@ -821,17 +830,17 @@ proc parseConfig(config: Config; dir: string; t: TomlValue; let ps = newPosixStream(s) if ps == nil: return err("include file not found: " & s) - ?config.parseConfig(dir, ps.readAll(), warnings) + ?config.parseConfig(dir, ps.readAll(), warnings, jsctx, s.afterLast('/')) ps.sclose() warnings.add(ctx.warnings) ok() proc parseConfig*(config: Config; dir: string; buf: openArray[char]; - warnings: var seq[string]; name = "<input>"; laxnames = false): - Err[string] = - let toml = parseToml(buf, dir / name, laxnames) + warnings: var seq[string]; jsctx: JSContext; name: string; + laxnames = false): Err[string] = + let toml = parseToml(buf, dir / name, laxnames, config.arraySeen) if toml.isSome: - return config.parseConfig(dir, toml.get, warnings) + return config.parseConfig(dir, toml.get, warnings, jsctx) return err("Fatal error: failed to parse config\n" & toml.error) proc getNormalAction*(config: Config; s: string): string = @@ -868,8 +877,7 @@ proc openConfig*(dir: var string; override: Option[string]; return newPosixStream(dir / "config.toml") # called after parseConfig returns -proc initCommands*(config: Config): Err[string] = - let ctx = config.jsctx +proc initCommands*(ctx: JSContext; config: Config): Err[string] = let obj = JS_NewObject(ctx) defer: JS_FreeValue(ctx, obj) if JS_IsException(obj): diff --git a/src/config/toml.nim b/src/config/toml.nim index bf92d39c..6842d7fc 100644 --- a/src/config/toml.nim +++ b/src/config/toml.nim @@ -1,3 +1,17 @@ +# TOML parser. +# +# Note that while it says TOML on the tin, the actual configuration +# language only superficially resembles it. In particular, this dialect +# has a) strict ordering requirements, b) no real distinction between +# table arrays and tables, c) a distinction between inline tables and +# regular tables. For example, `table = {}` can be used to clear +# a table. +# +# The reason for this is that TOML is fundamentally unsuitable for +# layered configs, but we're stuck with it for historical reasons. +# One day I hope to come up with a better config language, but migration +# will be painful... + import std/options import std/tables import std/times @@ -24,8 +38,9 @@ type line: int root: TomlTable node: TomlNode + arraySeen: TableRef[string, int] currkey: seq[string] - tarray: bool + warnings: seq[string] laxnames: bool TomlValue* = ref object @@ -42,7 +57,6 @@ type tab*: TomlTable of tvtArray: a*: seq[TomlValue] - ad*: bool TomlNode = ref object of RootObj comment: string @@ -52,6 +66,7 @@ type value*: TomlValue TomlTable* = ref object of TomlNode + clear*: bool key: seq[string] nodes: seq[TomlNode] map: OrderedTable[string, TomlValue] @@ -99,11 +114,11 @@ func `$`*(val: TomlValue): string = of tvtTable: result = $val.t of tvtArray: - #TODO if ad table array probably result = "[" - for it in val.a: + for i, it in val.a.mypairs: + if i > 0: + result &= ',' result &= $it - result &= ',' result &= ']' func `[]`*(val: TomlValue; key: string): TomlValue = @@ -244,29 +259,34 @@ proc consumeBare(state: var TomlParser; buf: openArray[char]; c: char): proc flushLine(state: var TomlParser): Err[TomlError] = if state.node != nil: if state.node of TomlKVPair: + let node = TomlKVPair(state.node) var i = 0 - let keys = state.currkey & TomlKVPair(state.node).key + let keys = state.currkey & node.key var table = state.root while i < keys.len - 1: - if keys[i] in table.map: - let node = table.map[keys[i]] + let node = table.map.getOrDefault(keys[i]) + if node != nil: if node.t == tvtTable: table = node.tab - elif node.t == tvtArray: - assert state.tarray - table = node.a[^1].tab else: - let s = keys.join('.') + let s = keys.toOpenArray(0, i).join('.') return state.err("re-definition of node " & s) else: let node = TomlTable() table.map[keys[i]] = TomlValue(t: tvtTable, tab: node) table = node inc i - if keys[i] in table.map: + let value = node.value + if i == 0 and value.t == tvtArray and value.a.len == 0: + # old delete syntax + let s = keys.join('.') + state.arraySeen.del(s) + table.clear = true + elif keys[i] in table.map: return state.err("re-definition of node " & keys.join('.')) - table.map[keys[i]] = TomlKVPair(state.node).value - table.nodes.add(state.node) + else: + table.map[keys[i]] = value + table.nodes.add(state.node) state.node = nil inc state.line return ok() @@ -320,19 +340,26 @@ proc consumeKey(state: var TomlParser; buf: openArray[char]): proc consumeTable(state: var TomlParser; buf: openArray[char]): Result[TomlTable, TomlError] = let res = TomlTable() + var tarray = false while state.has(buf): let c = state.peek(buf, 0) case c of ' ', '\t': discard state.consume(buf) - of '\n': return ok(res) + of '\n': + if tarray: + return state.err("missing ] at table array key's end") + return ok(res) of ']': - if state.tarray: + if tarray: discard state.consume(buf) + let s = res.key.join('.') + inc state.arraySeen.mgetOrPut(s, 0) + res.key.add($state.arraySeen.getOrDefault(s)) return ok(res) else: return state.err("redundant ] character after key") of '[': - state.tarray = true + tarray = true discard state.consume(buf) of '"', '\'': res.key = ?state.consumeKey(buf) @@ -351,29 +378,7 @@ proc consumeNoState(state: var TomlParser; buf: openArray[char]): of ' ', '\t': discard of '[': discard state.consume(buf) - state.tarray = false let table = ?state.consumeTable(buf) - if state.tarray: - var node = state.root - for i in 0 ..< table.key.high: - if table.key[i] in node.map: - node = node.map[table.key[i]].tab - else: - let t2 = TomlTable() - node.map[table.key[i]] = TomlValue(t: tvtTable, tab: t2) - node = t2 - if table.key[^1] in node.map: - var last = node.map[table.key[^1]] - if last.t != tvtArray: - let key = table.key.join('.') - return state.err("re-definition of node " & key & - " as table array (was " & $last.t & ")") - let val = TomlValue(t: tvtTable, tab: table) - last.a.add(val) - else: - let val = TomlValue(t: tvtTable, tab: table) - let last = TomlValue(t: tvtArray, a: @[val], ad: true) - node.map[table.key[^1]] = last state.currkey = table.key state.node = table return ok(false) @@ -486,7 +491,8 @@ proc consumeArray(state: var TomlParser; buf: openArray[char]): TomlResult = proc consumeInlineTable(state: var TomlParser; buf: openArray[char]): TomlResult = - let res = TomlValue(t: tvtTable, tab: TomlTable()) + state.arraySeen.del(state.currkey.join('.')) + let res = TomlValue(t: tvtTable, tab: TomlTable(clear: true)) var key: seq[string] = @[] var haskey = false var val: TomlValue = nil @@ -565,13 +571,14 @@ proc consumeValue(state: var TomlParser; buf: openArray[char]): TomlResult = return ok(TomlValue(t: tvtString, s: "")) return state.err("unexpected end of file") -proc parseToml*(buf: openArray[char]; filename = "<input>"; laxnames = false): - TomlResult = +proc parseToml*(buf: openArray[char]; filename: string; laxnames: bool; + arraySeen: TableRef[string, int]): TomlResult = var state = TomlParser( line: 1, root: TomlTable(), filename: filename, - laxnames: laxnames + laxnames: laxnames, + arraySeen: arraySeen ) while state.has(buf): if ?state.consumeNoState(buf): diff --git a/src/local/pager.nim b/src/local/pager.nim index f8073e58..e84dc060 100644 --- a/src/local/pager.nim +++ b/src/local/pager.nim @@ -1863,7 +1863,7 @@ proc applySiteconf(pager: Pager; url: URL; charsetOverride: Charset; ) var cookieJarId = url.host let surl = $url - for sc in pager.config.siteconf: + for sc in pager.config.siteconf.values: if sc.url.isSome and not sc.url.get.match(surl): continue elif sc.host.isSome and not sc.host.get.match(host): @@ -2005,8 +2005,8 @@ proc gotoURL(pager: Pager; request: Request; prevurl = none(URL); return nil proc omniRewrite(pager: Pager; s: string): string = - for rule in pager.config.omnirule: - if rule.match.match(s): + for rule in pager.config.omnirule.values: + if rule.match.get.match(s): let fun = rule.substituteUrl.get let ctx = pager.jsctx var arg0 = ctx.toJS(s) diff --git a/src/main.nim b/src/main.nim index ff9ae25e..75166c34 100644 --- a/src/main.nim +++ b/src/main.nim @@ -5,6 +5,7 @@ when NimMajor < 2: import std/options import std/os import std/posix +import std/tables import chagashi/charset import config/chapath @@ -210,27 +211,30 @@ proc parse(ctx: var ParamParseContext) = const defaultConfig = staticRead"res/config.toml" proc initConfig(ctx: ParamParseContext; config: Config; - warnings: var seq[string]): Err[string] = + warnings: var seq[string]; jsctx: JSContext): Err[string] = let ps = openConfig(config.dir, ctx.configPath, warnings) if ps == nil and ctx.configPath.isSome: # The user specified a non-existent config file. return err("Failed to open config file " & ctx.configPath.get) putEnv("CHA_DIR", config.dir) - ?config.parseConfig("res", defaultConfig, warnings) + ?config.parseConfig("res", defaultConfig, warnings, jsctx, "res/config.toml") when defined(debug): if (let ps = newPosixStream(getCurrentDir() / "res/config.toml"); ps != nil): - ?config.parseConfig(getCurrentDir(), ps.readAll(), warnings) + ?config.parseConfig(getCurrentDir(), ps.readAll(), warnings, jsctx, + "res/config.toml") ps.sclose() if ps != nil: let src = ps.readAllOrMmap() - ?config.parseConfig(config.dir, src.toOpenArray(), warnings) + ?config.parseConfig(config.dir, src.toOpenArray(), warnings, jsctx, + "config.toml") deallocMem(src) ps.sclose() for opt in ctx.opts: - ?config.parseConfig(getCurrentDir(), opt, warnings, laxnames = true) + ?config.parseConfig(getCurrentDir(), opt, warnings, jsctx, "<input>", + laxnames = true) config.css.stylesheet &= ctx.stylesheet - ?config.initCommands() + ?jsctx.initCommands(config) isCJKAmbiguous = config.display.doubleWidthAmbiguous return ok() @@ -251,8 +255,8 @@ proc main() = let jsrt = newJSRuntime() let jsctx = jsrt.newJSContext() var warnings = newSeq[string]() - let config = Config(jsctx: jsctx) - if (let res = ctx.initConfig(config, warnings); res.isNone): + let config = Config(arraySeen: newTable[string, int]()) + if (let res = ctx.initConfig(config, warnings, jsctx); res.isNone): stderr.writeLine(res.error) quit(1) var history = true diff --git a/todo b/todo index beaf0384..9cfcb22f 100644 --- a/todo +++ b/todo @@ -13,13 +13,8 @@ display: - dark mode (basically max Y) config: - important: config editor -- switch from table arrays to tables - better siteconf URL matching - $TERM-based display config -- better path handling (e.g. inline files, so we could get rid of css - "include/inline" etc.) -- add per-scheme env var configuration (e.g. - proto.gemini.known-hosts = '/some/path'; maybe also with inline JS?) - add RPC for CGI scripts e.g. toggle settings/issue downloads/etc * also some way to set permissions for RPC calls mailcap: @@ -41,7 +36,7 @@ buffer: - configurable/better url filtering in loader - when the log buffer crashes, print its contents to stderr * easiest way seems to be to just dump its cache file -- add buffer groups +- add tabs - xhtml pager: - handle long lines |