about summary refs log blame commit diff stats
path: root/src/config/config.nim
blob: d3cad0b15b3538f8eedcd1cefa850c58e11a5b49 (plain) (tree)
1
2
3
4
5
6
7
8
9
             
              
         
              
 
                  
                  
                 
                   

                    
                  
                   
                    
                
                   
 
    

                                           
 
                                
 
                                   
 
                           

                        
                         
                        

                                  
                              
                           



                         

                      


                                       
                         

                                   
                              
                            


                    

                                             
                         
                     
                                      
                     
                                        
                     
                    
                     
                                        
                    
                           



                                   
                             
                            
                     

                   
                                   
                                  
                     
                          
                       


                        
                      

                         

                                   
                    




                            







                                     





                                                             
                                                                                
                                                                                                        




                                                                         

                             


                                   
 

                                                                        

                          
                              

                                        




                                              

                                                          
                       
                                                                           
                                                 









                                                                       
                                                     

                      
 
                                      
                   
                 
              




                  
                
                  
                                
              
                                   
                 
                                   
                 


                                
                      
              


                      
         

                     



                                  
           
                  

                  

                

              
                                                                                     















                                             
 
                                                    












                                      




























                                                                    
                                                             

                


                              
                                                        
             
                                                           


                               
                                                          
               





                                                             








                                 


                    
                          


                                       



















                                                               











                                                          

                    




                                                      










                                                              


                                                       



                                                  
                              
                                       
                                                 
                                                           




                                        




                                     


                                                  

                                                         






                                                                
                                                    


                                                  







                                              
                                                 
                              
                                                                                      
                                      
 
                                                               

                                            
                                                           

                                             



                                                        
 
                                        
 
                                              

                                             
                               
 


                                                          
           
 
                                                         

                            
           
 


                            
                      

                                              


                                       
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)