about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--doc/cha.158
-rw-r--r--src/config/config.nim15
-rw-r--r--src/main.nim306
-rw-r--r--src/utils/twtstr.nim7
4 files changed, 209 insertions, 177 deletions
diff --git a/doc/cha.1 b/doc/cha.1
index fdda5fdc..ba5ffe47 100644
--- a/doc/cha.1
+++ b/doc/cha.1
@@ -35,11 +35,18 @@ provided, each letter is parsed as a separate short form.
 \fB\-\-abcd\fR.)
 
 .TP
+\fB\-c, \-\-css\fR \fIstylesheet\fR
+Temporarily modify the user stylesheet. If a user stylesheet is already
+being used, the stylesheet given is appended to that.
+.TP
 \fB\-d, \-\-dump\fR
 Start in headless mode, and sequentially print the opened files to stdout.
 This option is implicitly enabled if stdout is not a tty (e.g. when piping
 \fIcha\fR output).
 .TP
+\fB\-h, \-\-help\fR
+Print a short version of this page, then exit.
+.TP
 \fB\-o, \-\-opt\fR \fIconfig\fR
 Override configuration options. This accepts the configuration format is
 described in \fBcha-config\fR(5), in other words the passed string must be
@@ -50,14 +57,23 @@ parameters, unrecognized bare keywords are converted to strings. So this
 works: \fB--opt\fR abcd.option-name=\fIoption-value\fR. However, symbols must
 still be quoted.)
 .TP
-\fB\-c, \-\-css\fR \fIstylesheet\fR
-Temporarily modify the user stylesheet. If a user stylesheet is already
-being used, the stylesheet given is appended to that.
+\fB\-r, \-\-run\fR \fIscript\fR/\fIfile\fR
+Execute the string provided as a JS script, or execute the supplied JS
+file. If the file ends in .mjs, it is executed as an ES module.
+.br
+(To execute an inline script as a module, the following hack can be used:
+.br
+\fIcha \fB-r \fR'await new Promise(x => setTimeout(x, 1000));
+console.log("hello from ESM!"); //.mjs'
+.br
+In other words, we add a comment \fI//.mjs\fR to the end of the script.)
 .TP
-\fB\-T, \-\-type\fR \fIcontent-type\fR
-Override the content type of all input files. Useful when the content type
-cannot be guessed from the file extension, or when reading a non-plaintext
-file from stdin.
+\fB\-v, \-\-version\fR
+Print information about the browser's version, then exit.
+.TP
+\fB\-C, \-\-config\fR \fIfile\fR
+Override the default configuration search path. Both absolute and relative
+paths are allowed.
 .TP
 \fB\-I, \-\-input-charset\fR \fIcharset\fR
 Override the character set of all input files. Useful when Chawan is
@@ -65,34 +81,22 @@ incorrectly recognizing the input character set. (Note: if this happens
 often, consider changing the default input charset recognition list
 \fIencoding.document-charset\fR in the configuration.)
 .TP
+\fB\-M, \-\-monochrome\fR
+Override the output character set. This is a shortcut for
+\fB\-o display.color\-mode=\fImonochrome\fR.
+.TP
 \fB\-O, \-\-output-charset\fR \fIcharset\fR
 Override the output character set. This is a shortcut for
 \fB\-o encoding.display\-charset=\fIcharset\fR.
 .TP
-\fB\-M, \-\-monochrome\fR
-Override the output character set. This is a shortcut for
-\fB\-o display.color\-mode=\fImonochrome\fR.
+\fB\-T, \-\-type\fR \fIcontent-type\fR
+Override the content type of all input files. Useful when the content type
+cannot be guessed from the file extension, or when reading a non-plaintext
+file from stdin.
 .TP
 \fB\-V, \-\-visual\fR
 Start in visual mode: the page specified in \fIstart.visual-home\fR is opened.
 .TP
-\fB\-r, \-\-run\fR \fIscript\fR/\fIfile\fR
-Execute the string provided as a JS script, or execute the supplied JS
-file. If the file ends in .mjs, it is executed as an ES module.
-.br
-(To execute an inline script as a module, the following hack can be used:
-.br
-\fIcha \fB-r \fR'await new Promise(x => setTimeout(x, 1000));
-console.log("hello from ESM!"); //.mjs'
-.br
-In other words, we add a comment \fI//.mjs\fR to the end of the script.)
-.TP
-\fB\-h, \-\-help\fR
-Print a short version of this page, then exit.
-.TP
-\fB\-v, \-\-version\fR
-Print information about the browser's version, then exit.
-.TP
 \fB\-\-\fP
 Interpret all following arguments as files. (e.g. if you have a file named
 \fI\-o\fR, open it as \fIcha \fB--\fR \fI-o\fR.
diff --git a/src/config/config.nim b/src/config/config.nim
index 3d2769d4..99f9d9da 100644
--- a/src/config/config.nim
+++ b/src/config/config.nim
@@ -625,8 +625,8 @@ proc staticReadConfig(): ConfigObj =
 
 const defaultConfig = staticReadConfig()
 
-proc readConfig(config: Config, dir: string) =
-  let fs = newFileStream(dir / "config.toml")
+proc readConfig(config: Config, dir, name: string) =
+  let fs = openFileExpand(dir, name)
   if fs != nil:
     config.parseConfig(dir, fs)
 
@@ -636,12 +636,15 @@ proc getNormalAction*(config: Config, s: string): string =
 proc getLinedAction*(config: Config, s: string): string =
   return config.line.getOrDefault(s)
 
-proc readConfig*(): Config =
+proc readConfig*(pathOverride: Option[string]): Config =
   result = Config()
   result[] = defaultConfig
-  when defined(debug):
-    result.readConfig(getCurrentDir() / "res")
-  result.readConfig(getConfigDir() / "chawan")
+  if pathOverride.isNone:
+    when defined(debug):
+      result.readConfig(getCurrentDir() / "res", "config.toml")
+    result.readConfig(getConfigDir() / "chawan", "config.toml")
+  else:
+    result.readConfig(getCurrentDir(), pathOverride.get)
 
 proc addConfigModule*(ctx: JSContext) =
   ctx.registerType(ActionMap)
diff --git a/src/main.nim b/src/main.nim
index 86066c28..47e00959 100644
--- a/src/main.nim
+++ b/src/main.nim
@@ -18,182 +18,200 @@ when defined(profile):
 import config/config
 import io/serversocket
 import local/client
-import types/opt
 import utils/twtstr
 
 import chakasu/charset
 
-let conf = readConfig()
-set_cjk_ambiguous(conf.display.double_width_ambiguous)
-let params = commandLineParams()
+proc main() =
+  let params = commandLineParams()
 
-proc version(long: static bool = false): string =
-  result = "Chawan browser v0.1 "
-  when defined(debug):
-    result &= "(debug)"
-  else:
-    result &= "(release)"
+  proc version(long: static bool = false): string =
+    result = "Chawan browser v0.1 "
+    when defined(debug):
+      result &= "(debug)"
+    else:
+      result &= "(release)"
 
-proc help(i: int) =
-  let s = version() & """
+  proc help(i: int) =
+    let s = version() & """
 
 Usage: cha [options] [URL(s) or file(s)...]
 Options:
     --                          Interpret all following arguments as URLs
-    -d, --dump                  Print page to stdout
     -c, --css <stylesheet>      Pass stylesheet (e.g. -c 'a{color: blue}')
+    -d, --dump                  Print page to stdout
+    -h, --help                  Print this usage message
     -o, --opt <config>          Pass config options (e.g. -o 'page.q="quit()"')
-    -T, --type <type>           Specify content mime type
+    -r, --run <script/file>     Run passed script or file
+    -v, --version               Print version information
+    -C, --config <file>         Override config path
     -I, --input-charset <enc>   Specify document charset
-    -O, --display-charset <enc> Specify display charset
     -M, --monochrome            Set color-mode to 'monochrome'
-    -V, --visual                Visual startup mode
-    -r, --run <script/file>     Run passed script or file
-    -h, --help                  Print this usage message
-    -v, --version               Print version information"""
-  if i == 0:
-    stdout.write(s & '\n')
-  else:
-    stderr.write(s & '\n')
-  quit(i)
-
-var i = 0
-var ctype = none(string)
-var cs = CHARSET_UNKNOWN
-var pages: seq[string]
-var dump = false
-var visual = false
-var escape_all = false
-
-while i < params.len:
-  let param = params[i]
-
-  if escape_all: # after --
-    pages.add(param)
-    inc i
-    continue
+    -O, --display-charset <enc> Specify display charset
+    -T, --type <type>           Specify content mime type
+    -V, --visual                Visual startup mode"""
 
-  proc getnext(): string =
-    inc i
-    if i < params.len:
-      return params[i]
-    help(1)
+    if i == 0:
+      stdout.write(s & '\n')
+    else:
+      stderr.write(s & '\n')
+    quit(i)
+
+  var ctype = none(string)
+  var cs = CHARSET_UNKNOWN
+  var pages: seq[string]
+  var dump = false
+  var visual = false
+  var escape_all = false
+  var opts: seq[string]
+  var stylesheet = ""
+  var configPath = none(string)
+
+  var i = 0
+  while i < params.len:
+    let param = params[i]
+
+    if escape_all: # after --
+      pages.add(param)
+      inc i
+      continue
 
-  proc pversion() =
-    echo version(true)
-    quit(0)
+    proc getnext(): string =
+      inc i
+      if i < params.len:
+        return params[i]
+      help(1)
 
-  proc pmonochrome() =
-    conf.display.colormode.ok(MONOCHROME)
+    proc pconfig() =
+      configPath = some(getnext())
 
-  proc pvisual() =
-    visual = true
+    proc pversion() =
+      echo version(true)
+      quit(0)
 
-  proc ptype() =
-    ctype = some(getnext())
+    proc pmonochrome() =
+      opts.add("display.color-mode = monochrome")
 
-  proc pgetCharset(): Charset =
-    let s = getnext()
-    let cs = getCharset(s)
-    if cs == CHARSET_UNKNOWN:
-      stderr.write("Unknown charset " & s & "\n")
-      quit(1)
-    return cs
+    proc pvisual() =
+      visual = true
 
-  proc pinput_charset() =
-    cs = pgetCharset()
+    proc ptype() =
+      ctype = some(getnext())
 
-  proc poutput_charset() =
-    conf.encoding.display_charset.ok(pgetCharset())
+    proc pgetCharset(): Charset =
+      let s = getnext()
+      let cs = getCharset(s)
+      if cs == CHARSET_UNKNOWN:
+        stderr.write("Unknown charset " & s & "\n")
+        quit(1)
+      return cs
 
-  proc pdump() =
-    dump = true
+    proc pinput_charset() =
+      cs = pgetCharset()
 
-  proc pcss() =
-    conf.css.stylesheet &= getnext()
+    proc poutput_charset() =
+      opts.add("encoding.display-charset = '" & $pgetCharset() & "'")
 
-  proc popt() =
-    conf.parseConfig(getCurrentDir(), getnext(), laxnames = true)
+    proc pdump() =
+      dump = true
 
-  proc phelp() =
-    help(0)
+    proc pcss() =
+      stylesheet &= getnext()
 
-  proc prun() =
-    conf.start.startup_script = getnext()
-    conf.start.headless = true
-    dump = true
+    proc popt() =
+      opts.add(getnext())
 
-  if param.len == 0:
-    inc i
-    continue
+    proc phelp() =
+      help(0)
 
-  const NeedsNextParam = {'T', 'I', 'O', 'c', 'o', 'r'}
+    proc prun() =
+      let script = dqEscape(getnext())
+      opts.add("start.startup-script = \"\"\"" & script & "\"\"\"")
+      opts.add("start.headless = true")
+      dump = true
 
-  if param[0] == '-':
-    if param.len == 1:
-      # If param == "-", i.e. it is a single dash, then ignore it.
-      # (Some programs use single-dash to read from stdin, but we do that
-      # automatically when stdin is not a tty. So ignoring it entirely
-      # is probably for the best.)
+    if param.len == 0:
       inc i
       continue
-    if param[1] != '-':
-      for j in 1 ..< param.len:
-        if j != param.high and param[j] in NeedsNextParam:
-          # expecting next parameter, but not the last char...
-          help(1)
-        case param[j]
-        of 'v': pversion()
-        of 'M': pmonochrome()
-        of 'V': pvisual()
-        of 'T': ptype()
-        of 'I': pinput_charset()
-        of 'O': poutput_charset()
-        of 'd': pdump()
-        of 'c': pcss()
-        of 'o': popt()
-        of 'h': phelp()
-        of 'r': prun()
+
+    const NeedsNextParam = {'C', 'I', 'O', 'T', 'c', 'o', 'r'}
+
+    if param[0] == '-':
+      if param.len == 1:
+        # If param == "-", i.e. it is a single dash, then ignore it.
+        # (Some programs use single-dash to read from stdin, but we do that
+        # automatically when stdin is not a tty. So ignoring it entirely
+        # is probably for the best.)
+        inc i
+        continue
+      if param[1] != '-':
+        for j in 1 ..< param.len:
+          if j != param.high and param[j] in NeedsNextParam:
+            # expecting next parameter, but not the last char...
+            help(1)
+          case param[j]
+          of 'C': pconfig()
+          of 'I': pinput_charset()
+          of 'M': pmonochrome()
+          of 'O': poutput_charset()
+          of 'T': ptype()
+          of 'V': pvisual()
+          of 'c': pcss()
+          of 'd': pdump()
+          of 'h': phelp()
+          of 'o': popt()
+          of 'r': prun()
+          of 'v': pversion()
+          else: help(1)
+      else:
+        case param
+        of "--config": pconfig()
+        of "--input-charset": pinput_charset()
+        of "--monochrome": pmonochrome()
+        of "--output-charset": poutput_charset()
+        of "--type": ptype()
+        of "--visual": pvisual()
+        of "--css": pcss()
+        of "--dump": pdump()
+        of "--help": phelp()
+        of "--opt": popt()
+        of "--run": prun()
+        of "--version": pversion()
+        of "--": escape_all = true
         else: help(1)
     else:
-      case param
-      of "--version": pversion()
-      of "--monochrome": pmonochrome()
-      of "--visual": pvisual()
-      of "--type": ptype()
-      of "--input-charset": pinput_charset()
-      of "--output-charset": poutput_charset()
-      of "--dump": pdump()
-      of "--css": pcss()
-      of "--opt": popt()
-      of "--help": phelp()
-      of "--run": prun()
-      of "--": escape_all = true
-      else: help(1)
-  else:
-    pages.add(param)
-  inc i
-
-if pages.len == 0 and stdin.isatty():
-  if visual:
-    pages.add(conf.start.visual_home)
-  else:
-    let http = getEnv("HTTP_HOME")
-    if http != "": pages.add(http)
+      pages.add(param)
+    inc i
+
+  let config = readConfig(configPath)
+  for opt in opts:
+    config.parseConfig(getCurrentDir(), opt, laxnames = true)
+  config.css.stylesheet &= stylesheet
+
+  set_cjk_ambiguous(config.display.double_width_ambiguous)
+
+  if pages.len == 0 and stdin.isatty():
+    if visual:
+      pages.add(config.start.visual_home)
     else:
-      let www = getEnv("WWW_HOME")
-      if www != "": pages.add(www)
-
-if pages.len == 0 and not conf.start.headless:
-  if stdin.isatty:
-    help(1)
-
-forks.loadForkServerConfig(conf)
-SocketDirectory = conf.external.tmpdir
-
-let c = newClient(conf, forks, getpid())
-try:
-  c.launchClient(pages, ctype, cs, dump)
-except CatchableError:
-  c.flushConsole()
-  raise
+      let http = getEnv("HTTP_HOME")
+      if http != "": pages.add(http)
+      else:
+        let www = getEnv("WWW_HOME")
+        if www != "": pages.add(www)
+
+  if pages.len == 0 and not config.start.headless:
+    if stdin.isatty:
+      help(1)
+
+  forks.loadForkServerConfig(config)
+  SocketDirectory = config.external.tmpdir
+
+  let c = newClient(config, forks, getpid())
+  try:
+    c.launchClient(pages, ctype, cs, dump)
+  except CatchableError:
+    c.flushConsole()
+    raise
+
+main()
diff --git a/src/utils/twtstr.nim b/src/utils/twtstr.nim
index f8557a85..327fae81 100644
--- a/src/utils/twtstr.nim
+++ b/src/utils/twtstr.nim
@@ -563,6 +563,13 @@ func htmlEscape*(s: string): string =
       res &= c
   return res
 
+func dqEscape*(s: string): string =
+  result = newStringOfCap(s.len)
+  for c in s:
+    if c == '"':
+      result &= '\\'
+    result &= c
+
 #basically std join but with char
 func join*(ss: openarray[string], sep: char): string =
   if ss.len == 0: