import tables
import options
import os
import streams
import buffer/cell
import config/toml
import io/request
import io/urlfilter
import js/javascript
import js/regex
import types/color
import types/cookie
import types/referer
import types/url
import utils/twtstr
type
ColorMode* = enum
MONOCHROME, ANSI, EIGHT_BIT, TRUE_COLOR
FormatMode* = set[FormatFlags]
ActionMap = Table[string, string]
StaticSiteConfig = object
url: Option[string]
host: Option[string]
subst: Option[string]
cookie: Option[bool]
thirdpartycookie: seq[string]
sharecookiejar: Option[string]
refererfrom*: Option[bool]
scripting: Option[bool]
StaticOmniRule = object
match: string
subst: string
SiteConfig* = object
url*: Option[Regex]
host*: Option[Regex]
subst*: (proc(s: URL): Option[URL])
cookie*: Option[bool]
thirdpartycookie*: seq[Regex]
sharecookiejar*: Option[string]
refererfrom*: Option[bool]
scripting*: Option[bool]
OmniRule* = object
match*: Regex
subst*: (proc(s: string): Option[string])
Config* = ref ConfigObj
ConfigObj* = object
searchwrap* {.jsget, jsset.}: bool
maxredirect*: int
prependhttps* {.jsget, jsset.}: bool
termreload*: bool
nmap*: ActionMap
lemap*: ActionMap
stylesheet* {.jsget, jsset.}: string
startup*: string
ambiguous_double*: bool
hlcolor*: RGBAColor
headless*: bool
colormode*: Option[ColorMode]
formatmode*: Option[FormatMode]
noformatmode*: FormatMode
altscreen*: Option[bool]
mincontrast*: int
editor*: string
tmpdir*: string
siteconf: seq[StaticSiteConfig]
omnirules: seq[StaticOmniRule]
forceclear*: bool
emulateoverline*: bool
visualhome*: string
BufferConfig* = object
userstyle*: string
filter*: URLFilter
cookiejar*: CookieJar
headers*: HeaderList
refererfrom*: bool
referrerpolicy*: ReferrerPolicy
scripting*: bool
ForkServerConfig* = object
tmpdir*: string
ambiguous_double*: bool
const DefaultHeaders* = {
"User-Agent": "chawan",
"Accept": "text/html,text/*;q=0.5",
"Accept-Language": "en;q=1.0",
"Pragma": "no-cache",
"Cache-Control": "no-cache",
}.toTable().newHeaderList()[]
func getForkServerConfig*(config: Config): ForkServerConfig =
return ForkServerConfig(
tmpdir: config.tmpdir,
ambiguous_double: config.ambiguous_double
)
proc getBufferConfig*(config: Config, location: URL, cookiejar: CookieJar = nil,
headers: HeaderList = nil, refererfrom = false, scripting = false): BufferConfig =
result = BufferConfig(
userstyle: config.stylesheet,
filter: newURLFilter(scheme = some(location.scheme), default = true),
cookiejar: cookiejar,
headers: headers,
refererfrom: refererfrom,
scripting: scripting
)
new(result.headers)
result.headers[] = DefaultHeaders
proc getSiteConfig*(config: Config, jsctx: JSContext): seq[SiteConfig] =
for sc in config.siteconf:
var conf = SiteConfig(
cookie: sc.cookie,
scripting: sc.scripting,
sharecookiejar: sc.sharecookiejar,
refererfrom: sc.refererfrom
)
if sc.url.isSome:
conf.url = compileRegex(sc.url.get, 0)
elif sc.host.isSome:
conf.host = compileRegex(sc.host.get, 0)
for rule in sc.thirdpartycookie:
conf.thirdpartycookie.add(compileRegex(rule, 0).get)
if sc.subst.isSome:
let fun = jsctx.eval(sc.subst.get, "<siteconf>", JS_EVAL_TYPE_GLOBAL)
let f = getJSFunction[URL, URL](jsctx, fun)
conf.subst = f.get
result.add(conf)
proc getOmniRules*(config: Config, jsctx: JSContext): seq[OmniRule] =
for rule in config.omnirules:
let re = compileRegex(rule.match, 0)
var conf = OmniRule(
match: re.get
)
let fun = jsctx.eval(rule.subst, "<siteconf>", JS_EVAL_TYPE_GLOBAL)
let f = getJSFunction[string, string](jsctx, fun)
conf.subst = f.get
result.add(conf)
func getRealKey(key: string): string =
var realk: string
var control = 0
var meta = 0
var skip = false
for c in key:
if c == '\\':
skip = true
elif skip:
realk &= c
skip = false
elif c == 'M' and meta == 0:
inc meta
elif c == 'C' and control == 0:
inc control
elif c == '-' and control == 1:
inc control
elif c == '-' and meta == 1:
inc meta
elif meta == 1:
realk &= 'M' & c
meta = 0
elif control == 1:
realk &= 'C' & c
control = 0
else:
if meta == 2:
realk &= '\e'
meta = 0
if control == 2:
realk &= getControlChar(c)
control = 0
else:
realk &= c
if control == 1:
realk &= 'C'
if meta == 1:
realk &= 'M'
return realk
func constructActionTable*(origTable: Table[string, string]): Table[string, string] =
var strs: seq[string]
for k in origTable.keys:
let realk = getRealKey(k)
var teststr = ""
for c in realk:
teststr &= c
strs.add(teststr)
for k, v in origTable:
let realk = getRealKey(k)
var teststr = ""
for c in realk:
teststr &= c
if strs.contains(teststr):
result[teststr] = "client.feedNext()"
result[realk] = v
proc readUserStylesheet(dir, file: string): string =
if file.len == 0:
return ""
if file[0] == '~' or file[0] == '/':
var f: File
if f.open(expandPath(file)):
result = f.readAll()
f.close()
else:
var f: File
if f.open(dir / file):
result = f.readAll()
f.close()
proc parseConfig(config: Config, dir: string, stream: Stream)
proc parseConfig*(config: Config, dir: string, s: string)
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 bindPagerKey*(config: Config, key, action: string) {.jsfunc.} =
let k = getRealKey(key)
config.nmap[k] = action
var teststr = ""
for c in k:
teststr &= c
if teststr notin config.nmap:
config.nmap[teststr] = "client.feedNext()"
proc bindLineKey*(config: Config, key, action: string) {.jsfunc.} =
let k = getRealKey(key)
config.lemap[k] = action
var teststr = ""
for c in k:
teststr &= c
if teststr notin config.nmap:
config.lemap[teststr] = "client.feedNext()"
proc parseConfig(config: Config, dir: string, t: TomlValue) =
for k, v in t:
case k
of "include":
if v.vt == VALUE_STRING:
when nimvm:
config.parseConfig(dir, staticRead(dir / v.s))
else:
config.parseConfig(dir, newFileStream(dir / v.s))
elif t.vt == VALUE_ARRAY:
for v in t.a:
when nimvm:
config.parseConfig(dir, staticRead(dir / v.s))
else:
config.parseConfig(dir, newFileStream(dir / v.s))
of "search":
for k, v in v:
case k
of "wrap":
config.searchwrap = v.b
of "start":
for k, v in v:
case k
of "visual-home":
config.visualhome = v.s
of "run-script":
config.startup = v.s
of "headless":
config.headless = v.b
of "network":
for k, v in v:
case k
of "max-redirect":
config.maxredirect = int(v.i)
of "prepend-https":
config.prependhttps = v.b
of "page":
for k, v in v:
config.nmap[getRealKey(k)] = v.s
of "line":
for k, v in v:
config.lemap[getRealKey(k)] = v.s
of "css":
for k, v in v:
case k
of "include":
case v.vt
of VALUE_STRING:
config.stylesheet &= readUserStylesheet(dir, v.s)
of VALUE_ARRAY:
for child in v.a:
config.stylesheet &= readUserStylesheet(dir, v.s)
else: discard
of "inline":
config.stylesheet &= v.s
of "display":
template get_format_mode(v: TomlValue): FormatMode =
var mode: FormatMode
for vv in v.a:
case vv.s
of "bold": mode.incl(FLAG_BOLD)
of "italic": mode.incl(FLAG_ITALIC)
of "underline": mode.incl(FLAG_UNDERLINE)
of "reverse": mode.incl(FLAG_REVERSE)
of "strike": mode.incl(FLAG_STRIKE)
of "overline": mode.incl(FLAG_OVERLINE)
of "blink": mode.incl(FLAG_BLINK)
mode
for k, v in v:
case k
of "alt-screen":
if v.vt == VALUE_BOOLEAN:
config.altscreen = some(v.b)
elif v.vt == VALUE_STRING and v.s == "auto":
config.altscreen = none(bool)
of "color-mode":
case v.s
of "auto": config.colormode = none(ColorMode)
of "monochrome": config.colormode = some(MONOCHROME)
of "ansi": config.colormode = some(ANSI)
of "8bit": config.colormode = some(EIGHT_BIT)
of "24bit": config.colormode = some(TRUE_COLOR)
of "format-mode":
if v.vt == VALUE_STRING and v.s == "auto":
config.formatmode = none(FormatMode)
elif v.vt == VALUE_ARRAY:
config.formatmode = some(get_format_mode v)
of "no-format-mode":
config.noformatmode = get_format_mode v
of "highlight-color":
config.hlcolor = parseRGBAColor(v.s).get
of "double-width-ambiguous":
config.ambiguous_double = v.b
of "minimum-contrast":
config.mincontrast = int(v.i)
of "force-clear": config.forceclear = v.b
of "emulate-overline": config.emulateoverline = v.b
of "external":
for k, v in v:
case k
of "editor": config.editor = v.s
of "tmpdir": config.tmpdir = v.s
of "siteconf":
for v in v:
var conf = StaticSiteConfig()
for k, v in v:
case k
of "url": conf.url = some(v.s)
of "host": conf.host = some(v.s)
of "rewrite-url": conf.subst = some(v.s)
of "referer-from": conf.refererfrom = some(v.b)
of "cookie": conf.cookie = some(v.b)
of "third-party-cookie":
if v.vt == VALUE_STRING:
conf.thirdpartycookie = @[v.s]
else:
for v in v.a:
conf.thirdpartycookie.add(v.s)
of "share-cookie-jar": conf.sharecookiejar = some(v.s)
of "scripting": conf.scripting = some(v.b)
assert conf.url.isSome != conf.host.isSome
config.siteconf.add(conf)
of "omnirule":
if v.vt == VALUE_ARRAY and v.a.len == 0:
config.omnirules.setLen(0)
else:
for v in v:
var rule = StaticOmniRule()
for k, v in v:
case k
of "match": rule.match = v.s
of "substitute-url": rule.subst = v.s
if rule.match != "":
assert rule.subst != "", "Unspecified substitution for rule " & rule.match
config.omnirules.add(rule)
proc parseConfig(config: Config, dir: string, stream: Stream) =
config.parseConfig(dir, parseToml(stream))
proc parseConfig*(config: Config, dir: string, s: string) =
config.parseConfig(dir, newStringStream(s))
proc staticReadConfig(): ConfigObj =
var config = new(Config)
config.parseConfig("res", staticRead"res/config.toml")
return config[]
const defaultConfig = staticReadConfig()
proc readConfig(config: Config, dir: string) =
let fs = newFileStream(dir / "config.toml")
if fs != nil:
config.parseConfig(dir, fs)
proc getNormalAction*(config: Config, s: string): string =
if config.nmap.hasKey(s):
return config.nmap[s]
return ""
proc getLinedAction*(config: Config, s: string): string =
if config.lemap.hasKey(s):
return config.lemap[s]
return ""
proc readConfig*(): Config =
new(result)
result[] = defaultConfig
when defined(debug):
result.readConfig(getCurrentDir() / "res")
result.readConfig(getConfigDir() / "chawan")
proc addConfigModule*(ctx: JSContext) =
ctx.registerType(Config)