diff options
author | bptato <nincsnevem662@gmail.com> | 2024-03-17 21:14:51 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2024-03-17 21:20:04 +0100 |
commit | fc8937b53327f99b5809f78e3257e62a05bd1c79 (patch) | |
tree | 64655d81478c9fb8ac93d67259c60cb4fd246b5f /src | |
parent | d385d07b197cef65c2d2a800378de9152551e3e6 (diff) | |
download | chawan-fc8937b53327f99b5809f78e3257e62a05bd1c79.tar.gz |
config: clean up/simplify
* Parse the default config at runtime. There's no significant performance difference, but this makes it much less painful to write config code. * Add better error reporting * Make fromJS2 easier to use * Unquote ChaPaths while parsing config
Diffstat (limited to 'src')
-rw-r--r-- | src/config/chapath.nim | 4 | ||||
-rw-r--r-- | src/config/config.nim | 531 | ||||
-rw-r--r-- | src/config/toml.nim | 2 | ||||
-rw-r--r-- | src/js/fromjs.nim | 10 | ||||
-rw-r--r-- | src/loader/headers.nim | 13 | ||||
-rw-r--r-- | src/loader/request.nim | 19 | ||||
-rw-r--r-- | src/local/client.nim | 14 | ||||
-rw-r--r-- | src/local/pager.nim | 26 | ||||
-rw-r--r-- | src/main.nim | 26 | ||||
-rw-r--r-- | src/types/color.nim | 34 |
10 files changed, 349 insertions, 330 deletions
diff --git a/src/config/chapath.nim b/src/config/chapath.nim index adf852a0..c27df1e9 100644 --- a/src/config/chapath.nim +++ b/src/config/chapath.nim @@ -284,8 +284,8 @@ proc unquote(p: string): ChaPathResult[string] = proc toJS*(ctx: JSContext, p: ChaPath): JSValue = toJS(ctx, $p) -proc fromJS2*(ctx: JSContext, val: JSValue, o: var JSResult[ChaPath]) = - o = cast[JSResult[ChaPath]](fromJS[string](ctx, val)) +proc fromJSChaPath*(ctx: JSContext; val: JSValue): JSResult[ChaPath] = + return cast[JSResult[ChaPath]](fromJS[string](ctx, val)) proc unquote*(p: ChaPath): ChaPathResult[string] = let s = ?unquote(string(p)) diff --git a/src/config/config.nim b/src/config/config.nim index e42fe9e4..b019ec70 100644 --- a/src/config/config.nim +++ b/src/config/config.nim @@ -8,6 +8,7 @@ import config/mailcap import config/mimetypes import config/toml import js/error +import js/fromjs import js/javascript import js/propertyenumlist import js/regex @@ -29,27 +30,11 @@ type FormatMode* = set[FormatFlags] + ChaPathResolved* = distinct string + ActionMap = object t: Table[string, string] - StaticSiteConfig = object - url: Opt[string] - host: Opt[string] - rewrite_url: Opt[string] - cookie: Opt[bool] - third_party_cookie: seq[string] - share_cookie_jar: Opt[string] - referer_from*: Opt[bool] - scripting: Opt[bool] - document_charset: seq[Charset] - images: Opt[bool] - stylesheet: Opt[string] - proxy: Opt[string] - - StaticOmniRule = object - match: string - substitute_url: string - SiteConfig* = object url*: Opt[Regex] host*: Opt[Regex] @@ -86,12 +71,12 @@ type document_charset* {.jsgetset.}: seq[Charset] ExternalConfig = object - tmpdir* {.jsgetset.}: ChaPath + tmpdir* {.jsgetset.}: ChaPathResolved editor* {.jsgetset.}: string - mailcap* {.jsgetset.}: seq[ChaPath] - mime_types* {.jsgetset.}: seq[ChaPath] - cgi_dir* {.jsgetset.}: seq[ChaPath] - urimethodmap* {.jsgetset.}: seq[ChaPath] + mailcap* {.jsgetset.}: seq[ChaPathResolved] + mime_types* {.jsgetset.}: seq[ChaPathResolved] + cgi_dir* {.jsgetset.}: seq[ChaPathResolved] + urimethodmap* {.jsgetset.}: seq[ChaPathResolved] download_dir* {.jsgetset.}: string w3m_cgi_compat* {.jsgetset.}: bool @@ -130,10 +115,10 @@ type force_pixels_per_column* {.jsgetset.}: bool force_pixels_per_line* {.jsgetset.}: bool - Config* = ref ConfigObj - ConfigObj* = object + Config* = ref object + jsctx: JSContext configdir {.jsget.}: string - `include` {.jsget.}: seq[ChaPath] + `include` {.jsget.}: seq[ChaPathResolved] start* {.jsget.}: StartConfig search* {.jsget.}: SearchConfig css* {.jsget.}: CSSConfig @@ -143,8 +128,8 @@ type input* {.jsget.}: InputConfig display* {.jsget.}: DisplayConfig #TODO getset - siteconf: seq[StaticSiteConfig] - omnirule: seq[StaticOmniRule] + siteconf*: seq[SiteConfig] + omnirule*: seq[OmniRule] page* {.jsget.}: ActionMap line* {.jsget.}: ActionMap @@ -162,102 +147,27 @@ jsDestructor(NetworkConfig) jsDestructor(DisplayConfig) jsDestructor(Config) -proc `[]=`(a: var ActionMap, b, c: string) = a.t[b] = c -proc `[]`*(a: ActionMap, b: string): string = a.t[b] -proc contains*(a: ActionMap, b: string): bool = b in a.t -proc getOrDefault(a: ActionMap, b: string): string = a.t.getOrDefault(b) -proc hasKeyOrPut(a: var ActionMap, b, c: string): bool = a.t.hasKeyOrPut(b, c) +converter toStr*(p: ChaPathResolved): string {.inline.} = + return string(p) -func getRealKey(key: string): string +proc fromJSChaPathResolved(ctx: JSContext; val: JSValue): + JSResult[ChaPathResolved] = + return cast[JSResult[ChaPathResolved]](fromJS[string](ctx, val)) -proc getter(a: ptr ActionMap, s: string): Opt[string] {.jsgetprop.} = - a.t.withValue(s, p): - return opt(p[]) +proc `[]=`(a: var ActionMap; b, c: string) = + a.t[b] = c -proc setter(a: ptr ActionMap, k, v: string) {.jssetprop.} = - let k = getRealKey(k) - if k == "": - return - a[][k] = v - var teststr = k - teststr.setLen(teststr.high) - for i in countdown(k.high, 0): - if teststr notin a[]: - a[][teststr] = "client.feedNext()" - teststr.setLen(i) +proc `[]`*(a: ActionMap; b: string): string = + a.t[b] -proc delete(a: ptr ActionMap, k: string): bool {.jsdelprop.} = - let k = getRealKey(k) - let ina = k in a[] - a[].t.del(k) - return ina +proc contains*(a: ActionMap; b: string): bool = + return b in a.t -func names(ctx: JSContext, a: ptr ActionMap): JSPropertyEnumList - {.jspropnames.} = - let L = uint32(a[].t.len) - var list = newJSPropertyEnumList(ctx, L) - for key in a[].t.keys: - list.add(key) - return list +proc getOrDefault(a: ActionMap; b: string): string = + return a.t.getOrDefault(b) -proc bindPagerKey(config: Config, key, action: string) {.jsfunc.} = - (addr config.page).setter(key, action) - -proc bindLineKey(config: Config, key, action: string) {.jsfunc.} = - (addr config.line).setter(key, action) - -proc hasprop(a: ptr ActionMap, s: string): bool {.jshasprop.} = - return s in a[] - -func getProxy*(config: Config): URL = - if config.network.proxy.isSome: - let s = config.network.proxy.get - let x = parseURL(s) - if x.isSome: - return x.get - else: - raise newException(Defect, "Invalid proxy URL: " & s) - return nil - -func getDefaultHeaders*(config: Config): Headers = - return newHeaders(config.network.default_headers) - -proc getSiteConfig*(config: Config, jsctx: JSContext): seq[SiteConfig] = - for sc in config.siteconf: - var conf = SiteConfig( - cookie: sc.cookie, - scripting: sc.scripting, - share_cookie_jar: sc.share_cookie_jar, - referer_from: sc.referer_from, - document_charset: sc.document_charset, - images: sc.images - ) - if sc.url.isSome: - conf.url = opt(compileMatchRegex(sc.url.get)) - elif sc.host.isSome: - conf.host = opt(compileMatchRegex(sc.host.get)) - for rule in sc.third_party_cookie: - conf.third_party_cookie.add(compileMatchRegex(rule).get) - if sc.rewrite_url.isSome: - let fun = jsctx.eval(sc.rewrite_url.get, "<siteconf>", - JS_EVAL_TYPE_GLOBAL) - conf.rewrite_url = getJSFunction[URL, URL](jsctx, fun) - if sc.proxy.isSome: - let x = parseURL(sc.proxy.get) - if x.isNone: - raise newException(Defect, "invalid URL: " & sc.proxy.get) - conf.proxy = opt(x.get) - result.add(conf) - -proc getOmniRules*(config: Config, jsctx: JSContext): seq[OmniRule] = - for rule in config.omnirule: - let re = compileMatchRegex(rule.match) - var conf = OmniRule( - match: re.get - ) - let fun = jsctx.eval(rule.substitute_url, "<siteconf>", JS_EVAL_TYPE_GLOBAL) - conf.substitute_url = getJSFunction[string, string](jsctx, fun) - result.add(conf) +proc hasKeyOrPut(a: var ActionMap; b, c: string): bool = + return a.t.hasKeyOrPut(b, c) func getRealKey(key: string): string = var realk: string @@ -301,11 +211,59 @@ func getRealKey(key: string): string = realk &= '\\' return realk -proc openFileExpand(dir: string, file: ChaPath): FileStream = - let file0 = file.unquote() - if file0.isNone: - raise newException(ValueError, file0.error) - let file = file0.get +proc getter(a: ptr ActionMap; s: string): Opt[string] {.jsgetprop.} = + a.t.withValue(s, p): + return opt(p[]) + +proc setter(a: ptr ActionMap; k, v: string) {.jssetprop.} = + let k = getRealKey(k) + if k == "": + return + a[][k] = v + var teststr = k + teststr.setLen(teststr.high) + for i in countdown(k.high, 0): + if teststr notin a[]: + a[][teststr] = "client.feedNext()" + teststr.setLen(i) + +proc delete(a: ptr ActionMap; k: string): bool {.jsdelprop.} = + let k = getRealKey(k) + let ina = k in a[] + a[].t.del(k) + return ina + +func names(ctx: JSContext, a: ptr ActionMap): JSPropertyEnumList + {.jspropnames.} = + let L = uint32(a[].t.len) + var list = newJSPropertyEnumList(ctx, L) + for key in a[].t.keys: + list.add(key) + return list + +proc bindPagerKey(config: Config; key, action: string) {.jsfunc.} = + (addr config.page).setter(key, action) + +proc bindLineKey(config: Config; key, action: string) {.jsfunc.} = + (addr config.line).setter(key, action) + +proc hasprop(a: ptr ActionMap; s: string): bool {.jshasprop.} = + return s in a[] + +func getProxy*(config: Config): URL = + if config.network.proxy.isSome: + let s = config.network.proxy.get + let x = parseURL(s) + if x.isSome: + return x.get + else: + raise newException(ValueError, "Invalid proxy URL: " & s) + return nil + +func getDefaultHeaders*(config: Config): Headers = + return newHeaders(config.network.default_headers) + +proc openFileExpand(dir, file: string): FileStream = if file.len == 0: return nil if file[0] == '/': @@ -314,7 +272,10 @@ proc openFileExpand(dir: string, file: ChaPath): FileStream = return newFileStream(dir / file) proc readUserStylesheet(dir, file: string): string = - let s = openFileExpand(dir, ChaPath(file)) + let x = ChaPath(file).unquote() + if x.isNone: + raise newException(ValueError, x.error) + let s = openFileExpand(dir, x.get) if s != nil: result = s.readAll() s.close() @@ -322,7 +283,7 @@ proc readUserStylesheet(dir, file: string): string = # The overall configuration will be obtained through the virtual concatenation # of several individual configuration files known as mailcap files. proc getMailcap*(config: Config): tuple[mailcap: Mailcap, errs: seq[string]] = - let configDir = getConfigDir() / "chawan" #TODO store this in config? + let configDir = config.configdir template uq(s: string): string = ChaPath(s).unquote.get let gopherPath = "${%CHA_LIBEXEC_DIR}/gopher2html -u \\$MAILCAP_URL".uq @@ -375,7 +336,7 @@ proc getMimeTypes*(config: Config): MimeTypes = if config.external.mime_types.len == 0: return DefaultGuess var mimeTypes: MimeTypes - let configDir = getConfigDir() / "chawan" #TODO store this in config? + let configDir = config.configdir var found = false for p in config.external.mime_types: let f = openFileExpand(configDir, p) @@ -389,7 +350,7 @@ proc getMimeTypes*(config: Config): MimeTypes = const DefaultURIMethodMap = parseURIMethodMap(staticRead"res/urimethodmap") proc getURIMethodMap*(config: Config): URIMethodMap = - let configDir = getConfigDir() / "chawan" #TODO store this in config? + let configDir = config.configdir var urimethodmap: URIMethodMap for p in config.external.urimethodmap: let f = openFileExpand(configDir, p) @@ -399,45 +360,62 @@ proc getURIMethodMap*(config: Config): URIMethodMap = return urimethodmap proc getForkServerConfig*(config: Config): ForkServerConfig = - let tmpdir0 = config.external.tmpdir.unquote() - if tmpdir0.isNone: - raise newException(ValueError, tmpdir0.error) return ForkServerConfig( - tmpdir: tmpdir0.get, + tmpdir: config.external.tmpdir, ambiguous_double: config.display.double_width_ambiguous ) -proc parseConfig(config: Config, dir: string, stream: Stream, name = "<input>", - laxnames = false) -proc parseConfig*(config: Config, dir: string, s: string, name = "<input>", - laxnames = false) - -proc loadConfig*(config: Config, s: string) {.jsfunc.} = - let s = if s.len > 0 and s[0] == '/': - s - else: - getCurrentDir() / s - if not fileExists(s): return - config.parseConfig(parentDir(s), newFileStream(s)) - -proc parseConfigValue(x: var object, v: TomlValue, k: string) -proc parseConfigValue(x: var bool, v: TomlValue, k: string) -proc parseConfigValue(x: var string, v: TomlValue, k: string) -proc parseConfigValue(x: var ChaPath, v: TomlValue, k: string) -proc parseConfigValue[T](x: var seq[T], v: TomlValue, k: string) -proc parseConfigValue(x: var Charset, v: TomlValue, k: string) -proc parseConfigValue(x: var int32, v: TomlValue, k: string) -proc parseConfigValue(x: var int64, v: TomlValue, k: string) -proc parseConfigValue(x: var Opt[ColorMode], v: TomlValue, k: string) -proc parseConfigValue(x: var Opt[FormatMode], v: TomlValue, k: string) -proc parseConfigValue(x: var FormatMode, v: TomlValue, k: string) -proc parseConfigValue(x: var RGBAColor, v: TomlValue, k: string) -proc parseConfigValue(x: var RGBColor, v: TomlValue, k: string) -proc parseConfigValue[T](x: var Opt[T], v: TomlValue, k: string) -proc parseConfigValue(x: var ActionMap, v: TomlValue, k: string) -proc parseConfigValue(x: var CSSConfig, v: TomlValue, k: string) -proc parseConfigValue[U, V](x: var Table[U, V], v: TomlValue, k: string) -proc parseConfigValue[T](x: var set[T], v: TomlValue, k: string) +type ConfigParser = object + config: Config + dir: string + warnings: seq[string] + +proc parseConfigValue(ctx: var ConfigParser; x: var object; v: TomlValue; + k: string) +proc parseConfigValue(ctx: var ConfigParser; x: var bool; v: TomlValue; + k: string) +proc parseConfigValue(ctx: var ConfigParser; x: var string; v: TomlValue; + k: string) +proc parseConfigValue(ctx: var ConfigParser; x: var ChaPath; v: TomlValue; + k: string) +proc parseConfigValue[T](ctx: var ConfigParser; x: var seq[T]; v: TomlValue; + k: string) +proc parseConfigValue(ctx: var ConfigParser; x: var Charset; v: TomlValue; + k: string) +proc parseConfigValue(ctx: var ConfigParser; x: var int32; v: TomlValue; + k: string) +proc parseConfigValue(ctx: var ConfigParser; x: var int64; v: TomlValue; + k: string) +proc parseConfigValue(ctx: var ConfigParser; x: var Opt[ColorMode]; + v: TomlValue; k: string) +proc parseConfigValue(ctx: var ConfigParser; x: var Opt[FormatMode]; + v: TomlValue; k: string) +proc parseConfigValue(ctx: var ConfigParser; x: var FormatMode; v: TomlValue; + k: string) +proc parseConfigValue(ctx: var ConfigParser; x: var RGBAColor; v: TomlValue; + k: string) +proc parseConfigValue(ctx: var ConfigParser; x: var RGBColor; v: TomlValue; + k: string) +proc parseConfigValue[T](ctx: var ConfigParser; x: var Opt[T]; v: TomlValue; + k: string) +proc parseConfigValue(ctx: var ConfigParser; x: var ActionMap; v: TomlValue; + k: string) +proc parseConfigValue(ctx: var ConfigParser; x: var CSSConfig; v: TomlValue; + k: string) +proc parseConfigValue[U; V](ctx: var ConfigParser; x: var Table[U, V]; + v: TomlValue; k: string) +proc parseConfigValue[T](ctx: var ConfigParser; x: var set[T]; v: TomlValue; + k: string) +proc parseConfigValue(ctx: var ConfigParser; x: var TomlTable; v: TomlValue; + k: string) +proc parseConfigValue(ctx: var ConfigParser; x: var Regex; v: TomlValue; + k: string) +proc parseConfigValue(ctx: var ConfigParser; x: var URL; v: TomlValue; + k: string) +proc parseConfigValue[T](ctx: var ConfigParser; x: var proc(x: T): JSResult[T]; + v: TomlValue; k: string) +proc parseConfigValue(ctx: var ConfigParser; x: var ChaPathResolved; + v: TomlValue; k: string) proc typeCheck(v: TomlValue, vt: ValueType, k: string) = if v.vt != vt: @@ -449,68 +427,84 @@ proc typeCheck(v: TomlValue, vt: set[ValueType], k: string) = raise newException(ValueError, "invalid type for key " & k & " (got " & $v.vt & ", expected " & $vt & ")") -proc parseConfigValue(x: var object, v: TomlValue, k: string) = +proc parseConfigValue(ctx: var ConfigParser; x: var object; v: TomlValue; + k: string) = typeCheck(v, VALUE_TABLE, k) for fk, fv in x.fieldPairs: - let kebabk = snakeToKebabCase(fk) - if kebabk in v: - let kkk = if k != "": - k & "." & fk - else: - fk - parseConfigValue(fv, v[kebabk], kkk) - -proc parseConfigValue[U, V](x: var Table[U, V], v: TomlValue, k: string) = + when typeof(fv) isnot JSContext: + let kebabk = snakeToKebabCase(fk) + if kebabk in v: + let kkk = if k != "": + k & "." & fk + else: + fk + ctx.parseConfigValue(fv, v[kebabk], kkk) + +proc parseConfigValue[U, V](ctx: var ConfigParser; x: var Table[U, V]; + v: TomlValue; k: string) = typeCheck(v, VALUE_TABLE, k) x.clear() for kk, vv in v: var y: V let kkk = k & "[" & kk & "]" - parseConfigValue(y, vv, kkk) + ctx.parseConfigValue(y, vv, kkk) x[kk] = y -proc parseConfigValue(x: var bool, v: TomlValue, k: string) = +proc parseConfigValue(ctx: var ConfigParser; x: var bool; v: TomlValue; + k: string) = typeCheck(v, VALUE_BOOLEAN, k) x = v.b -proc parseConfigValue(x: var string, v: TomlValue, k: string) = +proc parseConfigValue(ctx: var ConfigParser; x: var string; v: TomlValue; + k: string) = typeCheck(v, VALUE_STRING, k) x = v.s -proc parseConfigValue(x: var ChaPath, v: TomlValue, k: string) = +proc parseConfigValue(ctx: var ConfigParser; x: var ChaPath; + v: TomlValue; k: string) = typeCheck(v, VALUE_STRING, k) x = ChaPath(v.s) -proc parseConfigValue[T](x: var seq[T], v: TomlValue, k: string) = +proc parseConfigValue[T](ctx: var ConfigParser; x: var seq[T]; v: TomlValue; + k: string) = typeCheck(v, {VALUE_STRING, VALUE_ARRAY}, k) if v.vt != VALUE_ARRAY: var y: T - parseConfigValue(y, v, k) + 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 - parseConfigValue(y, v.a[i], k & "[" & $i & "]") + ctx.parseConfigValue(y, v.a[i], k & "[" & $i & "]") x.add(y) -proc parseConfigValue(x: var Charset, v: TomlValue, k: string) = +proc parseConfigValue(ctx: var ConfigParser; x: var TomlTable; v: TomlValue; + k: string) = + typeCheck(v, {VALUE_TABLE}, k) + x = v.t + +proc parseConfigValue(ctx: var ConfigParser; x: var Charset; v: TomlValue; + k: string) = typeCheck(v, VALUE_STRING, k) x = getCharset(v.s) if x == CHARSET_UNKNOWN: raise newException(ValueError, "unknown charset '" & v.s & "' for key " & k) -proc parseConfigValue(x: var int32, v: TomlValue, k: string) = +proc parseConfigValue(ctx: var ConfigParser; x: var int32; v: TomlValue; + k: string) = typeCheck(v, VALUE_INTEGER, k) x = int32(v.i) -proc parseConfigValue(x: var int64, v: TomlValue, k: string) = +proc parseConfigValue(ctx: var ConfigParser; x: var int64; v: TomlValue; + k: string) = typeCheck(v, VALUE_INTEGER, k) x = v.i -proc parseConfigValue(x: var Opt[ColorMode], v: TomlValue, k: string) = +proc parseConfigValue(ctx: var ConfigParser; x: var Opt[ColorMode]; + v: TomlValue; k: string) = typeCheck(v, VALUE_STRING, k) case v.s of "auto": x.err() @@ -522,16 +516,18 @@ proc parseConfigValue(x: var Opt[ColorMode], v: TomlValue, k: string) = raise newException(ValueError, "unknown color mode '" & v.s & "' for key " & k) -proc parseConfigValue(x: var Opt[FormatMode], v: TomlValue, k: string) = +proc parseConfigValue(ctx: var ConfigParser; x: var Opt[FormatMode]; + v: TomlValue; k: string) = typeCheck(v, {VALUE_STRING, VALUE_ARRAY}, k) if v.vt == VALUE_STRING and v.s == "auto": x.err() else: var y: FormatMode - parseConfigValue(y, v, k) + ctx.parseConfigValue(y, v, k) x.ok(y) -proc parseConfigValue(x: var FormatMode, v: TomlValue, k: string) = +proc parseConfigValue(ctx: var ConfigParser; x: var FormatMode; v: TomlValue; + k: string) = typeCheck(v, VALUE_ARRAY, k) for i in 0 ..< v.a.len: let kk = k & "[" & $i & "]" @@ -549,7 +545,8 @@ proc parseConfigValue(x: var FormatMode, v: TomlValue, k: string) = raise newException(ValueError, "unknown format mode '" & vv.s & "' for key " & kk) -proc parseConfigValue(x: var RGBAColor, v: TomlValue, k: string) = +proc parseConfigValue(ctx: var ConfigParser; x: var RGBAColor; v: TomlValue; + k: string) = typeCheck(v, VALUE_STRING, k) let c = parseRGBAColor(v.s) if c.isNone: @@ -557,7 +554,8 @@ proc parseConfigValue(x: var RGBAColor, v: TomlValue, k: string) = "' for key " & k) x = c.get -proc parseConfigValue(x: var RGBColor, v: TomlValue, k: string) = +proc parseConfigValue(ctx: var ConfigParser; x: var RGBColor; v: TomlValue; + k: string) = typeCheck(v, VALUE_STRING, k) let c = parseLegacyColor(v.s) if c.isNone: @@ -565,15 +563,17 @@ proc parseConfigValue(x: var RGBColor, v: TomlValue, k: string) = "' for key " & k) x = c.get -proc parseConfigValue[T](x: var Opt[T], v: TomlValue, k: string) = +proc parseConfigValue[T](ctx: var ConfigParser; x: var Opt[T]; v: TomlValue; + k: string) = if v.vt == VALUE_STRING and v.s == "auto": x.err() else: var y: T - parseConfigValue(y, v, k) + ctx.parseConfigValue(y, v, k) x.ok(y) -proc parseConfigValue(x: var ActionMap, v: TomlValue, k: string) = +proc parseConfigValue(ctx: var ConfigParser; x: var ActionMap; v: TomlValue; + k: string) = typeCheck(v, VALUE_TABLE, k) for kk, vv in v: typeCheck(vv, VALUE_STRING, k & "[" & kk & "]") @@ -584,14 +584,16 @@ proc parseConfigValue(x: var ActionMap, v: TomlValue, k: string) = discard x.hasKeyOrPut(buf, "client.feedNext()") x[rk] = vv.s -proc parseConfigValue[T: enum](x: var T, v: TomlValue, k: string) = +proc parseConfigValue[T: enum](ctx: var ConfigParser; x: var T; v: TomlValue; + k: string) = typeCheck(v, VALUE_STRING, k) let e = strictParseEnum[T](v.s) if e.isNone: raise newException(ValueError, "invalid value '" & v.s & "' for key " & k) x = e.get -proc parseConfigValue[T](x: var set[T], v: TomlValue, k: string) = +proc parseConfigValue[T](ctx: var ConfigParser; x: var set[T]; v: TomlValue; + k: string) = typeCheck(v, {VALUE_STRING, VALUE_ARRAY}, k) if v.vt == VALUE_STRING: var xx: T @@ -605,10 +607,9 @@ proc parseConfigValue[T](x: var set[T], v: TomlValue, k: string) = xx.parseConfigValue(v.a[i], kk) x.incl(xx) -var gdir {.compileTime.}: string -proc parseConfigValue(x: var CSSConfig, v: TomlValue, k: string) = +proc parseConfigValue(ctx: var ConfigParser; x: var CSSConfig; v: TomlValue; + k: string) = typeCheck(v, VALUE_TABLE, k) - let dir = gdir for kk, vv in v: let kkk = if k != "": k & "." & kk @@ -619,78 +620,128 @@ proc parseConfigValue(x: var CSSConfig, v: TomlValue, k: string) = typeCheck(vv, {VALUE_STRING, VALUE_ARRAY}, kkk) case vv.vt of VALUE_STRING: - x.stylesheet &= readUserStylesheet(dir, vv.s) + x.stylesheet &= readUserStylesheet(ctx.dir, vv.s) of VALUE_ARRAY: for child in vv.a: - x.stylesheet &= readUserStylesheet(dir, vv.s) + x.stylesheet &= readUserStylesheet(ctx.dir, vv.s) else: discard of "inline": typeCheck(vv, VALUE_STRING, kkk) x.stylesheet &= vv.s -proc parseConfig(config: Config, dir: string, t: TomlValue) = - gdir = dir - parseConfigValue(config[], t, "") - while config.`include`.len > 0: - #TODO: warn about recursive includes - var includes = config.`include` - config.`include`.setLen(0) - for s in includes: - when nimvm: - config.parseConfig(dir, staticRead(dir / string(s))) - else: - config.parseConfig(dir, openFileExpand(dir, s)) +proc parseConfigValue(ctx: var ConfigParser; x: var Regex; v: TomlValue; + k: string) = + typeCheck(v, VALUE_STRING, k) + let y = compileMatchRegex(v.s) + if y.isNone: + raise newException(ValueError, "invalid regex " & k & " : " & y.error) + x = y.get + +proc parseConfigValue(ctx: var ConfigParser; x: var URL; v: TomlValue; + k: string) = + typeCheck(v, VALUE_STRING, k) + let y = parseURL(v.s) + if y.isNone: + raise newException(ValueError, "invalid URL " & k) + x = y.get + +proc parseConfigValue[T](ctx: var ConfigParser; x: var proc(x: T): JSResult[T]; + v: TomlValue; k: string) = + typeCheck(v, VALUE_STRING, k) + let fun = ctx.config.jsctx.eval(v.s, "<config>", JS_EVAL_TYPE_GLOBAL) + x = getJSFunction[T, T](ctx.config.jsctx, fun) + +proc parseConfigValue(ctx: var ConfigParser; x: var ChaPathResolved; + v: TomlValue; k: string) = + typeCheck(v, VALUE_STRING, k) + let y = ChaPath(v.s).unquote() + if y.isErr: + raise newException(ValueError, y.error) + x = ChaPathResolved(y.get) + +type ParseConfigResult* = object + success*: bool + warnings*: seq[string] #TODO actually use warnings + errorMsg*: string + +proc parseConfig(config: Config; dir: string; stream: Stream; name = "<input>"; + laxnames = false): ParseConfigResult + +proc parseConfig(config: Config; dir: string; t: TomlValue): ParseConfigResult = + var ctx = ConfigParser(config: config, dir: dir) config.configdir = dir - #TODO: for omnirule/siteconf, check if substitution rules are specified? + try: + var myRes = ParseConfigResult(success: true) + ctx.parseConfigValue(config[], t, "") + #TODO: for omnirule/siteconf, check if substitution rules are specified? + while config.`include`.len > 0: + #TODO: warn about recursive includes + var includes = config.`include` + config.`include`.setLen(0) + for s in includes: + let res = config.parseConfig(dir, openFileExpand(dir, s)) + if not res.success: + return res + myRes.warnings.add(res.warnings) + myRes.warnings.add(ctx.warnings) + return myRes + except ValueError as e: + return ParseConfigResult( + success: false, + warnings: ctx.warnings, + errorMsg: e.msg + ) -proc parseConfig(config: Config, dir: string, stream: Stream, name = "<input>", - laxnames = false) = +proc parseConfig(config: Config; dir: string; stream: Stream; name = "<input>"; + laxnames = false): ParseConfigResult = let toml = parseToml(stream, dir / name, laxnames) if toml.isOk: - config.parseConfig(dir, toml.get) + return config.parseConfig(dir, toml.get) else: - when nimvm: - echo "Fatal error: Failed to parse config" - echo toml.error - else: - stderr.write("Fatal error: Failed to parse config\n") - stderr.write(toml.error & '\n') - quit(1) - -proc parseConfig*(config: Config, dir: string, s: string, name = "<input>", - laxnames = false) = - config.parseConfig(dir, newStringStream(s), name, laxnames) + return ParseConfigResult( + success: false, + errorMsg: "Fatal error: failed to parse config\n" & toml.error & '\n' + ) -proc staticReadConfig(): ConfigObj = - var config = Config() - config.parseConfig("res", staticRead"res/config.toml", "config.toml") - return config[] +proc parseConfig*(config: Config; dir, s: string; name = "<input>"; + laxnames = false): ParseConfigResult = + return config.parseConfig(dir, newStringStream(s), name, laxnames) -const defaultConfig = staticReadConfig() +const defaultConfig = staticRead"res/config.toml" -proc readConfig(config: Config, dir, name: string) = +proc readConfig(config: Config; dir, name: string): ParseConfigResult = let fs = if name.len > 0 and name[0] == '/': newFileStream(name) else: newFileStream(dir / name) if fs != nil: - config.parseConfig(dir, fs) + return config.parseConfig(dir, fs) + return ParseConfigResult(success: true) + +proc loadConfig*(config: Config; s: string) {.jsfunc.} = + let s = if s.len > 0 and s[0] == '/': + s + else: + getCurrentDir() / s + if not fileExists(s): + return + discard config.parseConfig(parentDir(s), newFileStream(s)) -proc getNormalAction*(config: Config, s: string): string = +proc getNormalAction*(config: Config; s: string): string = return config.page.getOrDefault(s) -proc getLinedAction*(config: Config, s: string): string = +proc getLinedAction*(config: Config; s: string): string = return config.line.getOrDefault(s) -proc readConfig*(pathOverride: Option[string]): Config = - result = Config() - result[] = defaultConfig +proc readConfig*(pathOverride: Option[string]; jsctx: JSContext): Config = + result = Config(jsctx: jsctx) + discard result.parseConfig("res", newStringStream(defaultConfig)) #TODO TODO TODO if pathOverride.isNone: when defined(debug): - result.readConfig(getCurrentDir() / "res", "config.toml") - result.readConfig(getConfigDir() / "chawan", "config.toml") + discard result.readConfig(getCurrentDir() / "res", "config.toml") + discard result.readConfig(getConfigDir() / "chawan", "config.toml") else: - result.readConfig(getCurrentDir(), pathOverride.get) + discard result.readConfig(getCurrentDir(), pathOverride.get) proc addConfigModule*(ctx: JSContext) = ctx.registerType(ActionMap) diff --git a/src/config/toml.nim b/src/config/toml.nim index e46491c5..fc32f387 100644 --- a/src/config/toml.nim +++ b/src/config/toml.nim @@ -58,7 +58,7 @@ type key*: seq[string] value*: TomlValue - TomlTable = ref object of TomlNode + TomlTable* = ref object of TomlNode key: seq[string] nodes: seq[TomlNode] map: Table[string, TomlValue] diff --git a/src/js/fromjs.nim b/src/js/fromjs.nim index 112efd75..be90e192 100644 --- a/src/js/fromjs.nim +++ b/src/js/fromjs.nim @@ -471,6 +471,11 @@ proc fromJSEmptyPromise(ctx: JSContext, val: JSValue): JSResult[EmptyPromise] = type FromJSAllowedT = (object and not (Result|Option|Table|JSValue|JSDict| JSArrayBuffer|JSArrayBufferView|JSUint8Array)) +macro fromJS2(ctx: JSContext; val: JSValue; x: static string): untyped = + let id = ident("fromJS" & x) + return quote do: + `id`(`ctx`, `val`) + proc fromJS*[T](ctx: JSContext, val: JSValue): JSResult[T] = when T is string: return fromJSString(ctx, val) @@ -511,11 +516,8 @@ proc fromJS*[T](ctx: JSContext, val: JSValue): JSResult[T] = return fromJSArrayBuffer(ctx, val) elif T is JSArrayBufferView: return fromJSArrayBufferView(ctx, val) - elif compiles(fromJS2(ctx, val, result)): - fromJS2(ctx, val, result) else: - static: - error("Unrecognized type " & $T) + return fromJS2(ctx, val, $T) const JS_ATOM_TAG_INT = cuint(1u32 shl 31) diff --git a/src/loader/headers.nim b/src/loader/headers.nim index f097fa4f..20764760 100644 --- a/src/loader/headers.nim +++ b/src/loader/headers.nim @@ -22,18 +22,15 @@ type jsDestructor(Headers) -proc fromJS2*(ctx: JSContext, val: JSValue, res: var JSResult[HeadersInit]) = +proc fromJSHeadersInit(ctx: JSContext; val: JSValue): JSResult[HeadersInit] = if JS_IsUndefined(val) or JS_IsNull(val): - res.err(nil) - return + return err(nil) if isSequence(ctx, val): let x = fromJS[seq[(string, string)]](ctx, val) if x.isSome: - res.ok(HeadersInit(t: HEADERS_INIT_SEQUENCE, s: x.get)) - else: - let x = fromJS[Table[string, string]](ctx, val) - if x.isSome: - res.ok(HeadersInit(t: HEADERS_INIT_TABLE, tab: x.get)) + return ok(HeadersInit(t: HEADERS_INIT_SEQUENCE, s: x.get)) + let x = ?fromJS[Table[string, string]](ctx, val) + return ok(HeadersInit(t: HEADERS_INIT_TABLE, tab: x)) proc fill*(headers: Headers, s: seq[(string, string)]) = for (k, v) in s: diff --git a/src/loader/request.nim b/src/loader/request.nim index f56e1f76..05b02114 100644 --- a/src/loader/request.nim +++ b/src/loader/request.nim @@ -176,31 +176,26 @@ type proxyUrl: URL mode: Opt[RequestMode] -proc fromJS2*(ctx: JSContext, val: JSValue, res: var JSResult[BodyInit]) = +proc fromJSBodyInit(ctx: JSContext, val: JSValue): JSResult[BodyInit] = if JS_IsUndefined(val) or JS_IsNull(val): - res.err(nil) - return + return err(nil) block formData: let x = fromJS[FormData](ctx, val) if x.isSome: - res.ok(BodyInit(t: BODY_INIT_FORM_DATA, formData: x.get)) - return + return ok(BodyInit(t: BODY_INIT_FORM_DATA, formData: x.get)) block blob: let x = fromJS[Blob](ctx, val) if x.isSome: - res.ok(BodyInit(t: BODY_INIT_BLOB, blob: x.get)) - return + return ok(BodyInit(t: BODY_INIT_BLOB, blob: x.get)) block searchParams: let x = fromJS[URLSearchParams](ctx, val) if x.isSome: - res.ok(BodyInit(t: BODY_INIT_URL_SEARCH_PARAMS, searchParams: x.get)) - return + return ok(BodyInit(t: BODY_INIT_URL_SEARCH_PARAMS, searchParams: x.get)) block str: let x = fromJS[string](ctx, val) if x.isSome: - res.ok(BodyInit(t: BODY_INIT_STRING, str: x.get)) - return - res.err(newTypeError("Invalid body init type")) + return ok(BodyInit(t: BODY_INIT_STRING, str: x.get)) + return err(newTypeError("Invalid body init type")) func newRequest*[T: string|Request](ctx: JSContext, resource: T, init = none(RequestInit)): JSResult[Request] {.jsctor.} = diff --git a/src/local/client.nim b/src/local/client.nim index 7947ce15..60bdf411 100644 --- a/src/local/client.nim +++ b/src/local/client.nim @@ -774,8 +774,9 @@ proc dumpBuffers(client: Client) = quit(1) stdout.close() -proc launchClient*(client: Client, pages: seq[string], - contentType: Option[string], cs: Charset, dump: bool) = +proc launchClient*(client: Client; pages: seq[string]; + contentType: Option[string]; cs: Charset; dump: bool; + warnings: seq[string]) = var infile: File var dump = dump if not dump: @@ -794,6 +795,7 @@ proc launchClient*(client: Client, pages: seq[string], client.loader.unregisterFun = proc(fd: int) = selector.unregister(fd) client.pager.launchPager(infile, selector) + client.pager.alerts.add(warnings) let clearFun = proc() = client.clearConsole() let showFun = proc() = @@ -881,17 +883,17 @@ proc addJSModules(client: Client, ctx: JSContext) = func getClient(client: Client): Client {.jsfget: "client".} = return client -proc newClient*(config: Config, forkserver: ForkServer): Client = +proc newClient*(config: Config; forkserver: ForkServer; jsctx: JSContext): + Client = setControlCHook(proc() {.noconv.} = quit(1)) - let jsrt = newJSRuntime() + let jsrt = JS_GetRuntime(jsctx) JS_SetModuleLoaderFunc(jsrt, normalizeModuleName, clientLoadJSModule, nil) - let jsctx = jsrt.newJSContext() let pager = newPager(config, forkserver, jsctx) let loader = forkserver.newFileLoader(LoaderConfig( urimethodmap: config.getURIMethodMap(), w3mCGICompat: config.external.w3m_cgi_compat, cgiDir: pager.cgiDir, - tmpdir: pager.tmpdir + tmpdir: config.external.tmpdir )) pager.setLoader(loader) let client = Client( diff --git a/src/local/pager.nim b/src/local/pager.nim index 6448d1a8..e5d7d062 100644 --- a/src/local/pager.nim +++ b/src/local/pager.nim @@ -12,7 +12,6 @@ when defined(posix): import std/posix import bindings/libregexp -import config/chapath import config/config import config/mailcap import config/mimetypes @@ -100,7 +99,7 @@ type Pager* = ref object alertState: PagerAlertState - alerts: seq[string] + alerts*: seq[string] askcharpromise*: Promise[string] askcursor: int askpromise*: Promise[bool] @@ -127,7 +126,6 @@ type mimeTypes: MimeTypes notnum*: bool # has a non-numeric character been input already? numload*: int # number of pages currently being loaded - omnirules: seq[OmniRule] precnum*: int32 # current number prefix (when vi-numeric-prefix is true) procmap*: seq[ProcMapItem] proxy: URL @@ -136,10 +134,8 @@ type reverseSearch: bool scommand*: string selector*: Selector[int] - siteconf: seq[SiteConfig] statusgrid*: FixedGrid term*: Terminal - tmpdir*: string unreg*: seq[Container] urimethodmap: URIMethodMap @@ -282,16 +278,9 @@ proc quit*(pager: Pager, code = 0) = pager.dumpAlerts() proc setPaths(pager: Pager): Err[string] = - let tmpdir0 = pager.config.external.tmpdir.unquote() - if tmpdir0.isErr: - return err("Error unquoting external.tmpdir: " & tmpdir0.error) - pager.tmpdir = tmpdir0.get var cgiDir: seq[string] for path in pager.config.external.cgi_dir: - let x = path.unquote() - if x.isErr: - return err("Error unquoting external.cgi-dir: " & x.error) - cgiDir.add(x.get) + cgiDir.add(path) pager.cgiDir = cgiDir return ok() @@ -302,9 +291,7 @@ proc newPager*(config: Config; forkserver: ForkServer; ctx: JSContext): Pager = forkserver: forkserver, mailcap: mailcap, mimeTypes: config.getMimeTypes(), - omnirules: config.getOmniRules(ctx), proxy: config.getProxy(), - siteconf: config.getSiteConfig(ctx), term: newTerminal(stdout, config), urimethodmap: config.getURIMethodMap() ) @@ -889,7 +876,7 @@ func getEditorCommand(pager: Pager; file: string; line = 1): string {.jsfunc.} = proc openInEditor(pager: Pager; input: var string): bool = try: - let tmpf = getTempFile(pager.tmpdir) + let tmpf = getTempFile(pager.config.external.tmpdir) if input != "": writeFile(tmpf, input) let cmd = pager.getEditorCommand(tmpf) @@ -931,7 +918,7 @@ proc applySiteconf(pager: Pager; url: var URL; charsetOverride: Charset; var charsets = pager.config.encoding.document_charset var userstyle = pager.config.css.stylesheet var proxy = pager.proxy - for sc in pager.siteconf: + for sc in pager.config.siteconf: if sc.url.isSome and not sc.url.get.match($url): continue elif sc.host.isSome and not sc.host.get.match(host): @@ -1019,7 +1006,7 @@ proc gotoURL(pager: Pager, request: Request, prevurl = none(URL), pager.container.findAnchor(request.url.anchor) proc omniRewrite(pager: Pager, s: string): string = - for rule in pager.omnirules: + for rule in pager.config.omnirule: if rule.match.match(s): let sub = rule.substitute_url(s) if sub.isSome: @@ -1514,9 +1501,8 @@ proc checkMailcap(pager: Pager; container: Container; stream: SocketStream; let entry = pager.mailcap.getMailcapEntry(contentType, "", url) if entry == nil: return CheckMailcapResult(connect: true, fdout: stream.fd, found: false) - let tmpdir = pager.tmpdir let ext = url.pathname.afterLast('.') - let tempfile = getTempFile(tmpdir, ext) + let tempfile = getTempFile(pager.config.external.tmpdir, ext) let outpath = if entry.nametemplate != "": unquoteCommand(entry.nametemplate, contentType, tempfile, url) else: diff --git a/src/main.nim b/src/main.nim index ea5a92dd..51b1219f 100644 --- a/src/main.nim +++ b/src/main.nim @@ -6,12 +6,11 @@ let forks = newForkServer() import std/options import std/os -import config/chapath import config/config import io/serversocket +import js/javascript import local/client import local/term -import types/opt import utils/strwidth import utils/twtstr @@ -178,9 +177,15 @@ Options: pages.add(param) inc i - let config = readConfig(configPath) + let jsrt = newJSRuntime() + let jsctx = jsrt.newJSContext() + let config = readConfig(configPath, jsctx) + var warnings = newSeq[string]() for opt in opts: - config.parseConfig(getCurrentDir(), opt, laxnames = true) + let res = config.parseConfig(getCurrentDir(), opt, laxnames = true) + if not res.success: + stderr.write(res.errorMsg) + quit(1) config.css.stylesheet &= stylesheet set_cjk_ambiguous(config.display.double_width_ambiguous) @@ -200,16 +205,11 @@ Options: help(1) forks.loadForkServerConfig(config) - let tmpdir0 = config.external.tmpdir.unquote() - if tmpdir0.isErr: - stderr.write("Error unquoting external.tmpdir: " & tmpdir0.error) - stderr.write("Exiting...") - quit(1) - SocketDirectory = tmpdir0.get - - let c = newClient(config, forks) + SocketDirectory = config.external.tmpdir + + let c = newClient(config, forks, jsctx) try: - c.launchClient(pages, ctype, cs, dump) + c.launchClient(pages, ctype, cs, dump, warnings) except CatchableError: c.flushConsole() raise diff --git a/src/types/color.nim b/src/types/color.nim index 2f3f7310..6d46031d 100644 --- a/src/types/color.nim +++ b/src/types/color.nim @@ -517,14 +517,10 @@ proc toJS*(ctx: JSContext, rgb: RGBColor): JSValue = res.pushHex(rgb.b) return toJS(ctx, res) -proc fromJS2*(ctx: JSContext, val: JSValue, o: var JSResult[RGBColor]) = - let s = fromJS[string](ctx, val) - if s.isSome: - o = parseLegacyColor(s.get) - else: - o.err(s.error) +proc fromJSRGBColor*(ctx: JSContext, val: JSValue): JSResult[RGBColor] = + return parseLegacyColor(?fromJS[string](ctx, val)) -proc toJS*(ctx: JSContext, rgba: RGBAColor): JSValue = +proc toJS*(ctx: JSContext; rgba: RGBAColor): JSValue = var res = "#" res.pushHex(rgba.r) res.pushHex(rgba.g) @@ -532,22 +528,12 @@ proc toJS*(ctx: JSContext, rgba: RGBAColor): JSValue = res.pushHex(rgba.a) return toJS(ctx, res) -proc fromJS2*(ctx: JSContext, val: JSValue, o: var JSResult[RGBAColor]) = +proc fromJSRGBAColor*(ctx: JSContext; val: JSValue): JSResult[RGBAColor] = if JS_IsNumber(val): # as hex - let x = fromJS[uint32](ctx, val) - if x.isSome: - o.ok(RGBAColor(x.get)) - else: - o.err(x.error) - else: - # parse - let s = fromJS[string](ctx, val) - if s.isSome: - let x = parseRGBAColor(s.get) - if x.isSome: - o.ok(x.get) - else: - o.err(newTypeError("Unrecognized color")) - else: - o.err(s.error) + return ok(RGBAColor(?fromJS[uint32](ctx, val))) + # parse + let x = parseRGBAColor(?fromJS[string](ctx, val)) + if x.isSome: + return ok(x.get) + return errTypeError("Unrecognized color") |