Repos / mcross / b74362c954
commit b74362c954ba21b55f34597cd2682f1c1918b38e
Author: Bùi Thành Nhân <hi@imnhan.com>
Date: Thu Jun 18 16:39:50 2020 +0700
unified conf file / cli args
Config by priority: default > conf file > cli arg
It supports long form and short form but quite limited:
- Key-val style only, no on/off switch style e.g. `mcross --dark`
- Isn't POSIX-compliant i.e. `-d1` doesn't work
The Click library might help but I'm not sure if strict POSIX compliance
is really worth complicating the dep tree...
diff --git a/README.md b/README.md
index c91d21e..c6b6474 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,22 @@ # Installation
# Usage
-CLI arguments: `--textfont`, `--monofont`
+Run `mcross -h` to get a full list of CLI arguments. The same arguments can
+also be defined in a TOML config file: run `mcross-info` to know where this
+file should be for your OS. For example, running mcross like this:
+
+```sh
+mcross --background-color pink -t "Ubuntu"
+```
+
+is the same as putting this in `$HOME/.config/mcross/mcross.toml` for linux:
+
+```toml
+background-color = "pink"
+text-font = "Ubuntu"
+```
+
+The priority is CLI arg > config file > default.
Keyboard shortcuts:
@@ -75,8 +90,9 @@ # Feature checklist
- [x] non-blocking I/O using curio
- [x] more visual indicators: waiting cursor, status bar
- [x] parse gemini's advanced line types
-- [ ] properly handle mime types (gemini/plaintext/binary)
-- [ ] configurable document styling
+- [x] render `text/*` mime types with correct charset
+- [ ] handle `binary/*` mime types
+- [x] configurable document styling
- [ ] human-friendly distribution
- [ ] TOFU TLS (right now it always accepts self-signed certs)
diff --git a/poetry.lock b/poetry.lock
index 4262ef3..43ef576 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,3 +1,11 @@
+[[package]]
+category = "main"
+description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+name = "appdirs"
+optional = false
+python-versions = "*"
+version = "1.4.4"
+
[[package]]
category = "dev"
description = "Disable App Nap on OS X 10.9"
@@ -260,6 +268,14 @@ optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
version = "1.14.0"
+[[package]]
+category = "main"
+description = "Python Library for Tom's Obvious, Minimal Language"
+name = "toml"
+optional = false
+python-versions = "*"
+version = "0.10.1"
+
[[package]]
category = "dev"
description = "Traitlets Python config system"
@@ -309,10 +325,14 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
testing = ["jaraco.itertools", "func-timeout"]
[metadata]
-content-hash = "6aa72780c0ec9d3ed80370f35c09d1b1915c49675ab97732c49e2af47c5aa08f"
+content-hash = "4804febcb012c5490a8635fcf2e94dfb8e3db1e586c2a4534ee6bf806f74dc42"
python-versions = "^3.7"
[metadata.files]
+appdirs = [
+ {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
+ {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
+]
appnope = [
{file = "appnope-0.1.0-py2.py3-none-any.whl", hash = "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0"},
{file = "appnope-0.1.0.tar.gz", hash = "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"},
@@ -391,6 +411,10 @@ six = [
{file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"},
{file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"},
]
+toml = [
+ {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"},
+ {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"},
+]
traitlets = [
{file = "traitlets-4.3.3-py2.py3-none-any.whl", hash = "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44"},
{file = "traitlets-4.3.3.tar.gz", hash = "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"},
diff --git a/pyproject.toml b/pyproject.toml
index b5f935a..1a3544d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "mcross"
-version = "0.5.16"
+version = "0.5.17"
description = "Do you remember www?"
authors = ["nhanb <hi@imnhan.com>"]
license = "MIT"
@@ -13,10 +13,13 @@ repository = "https://git.sr.ht/~nhanb/mcross"
[tool.poetry.scripts]
mcross = "mcross:run"
+mcross-info = "mcross:info"
[tool.poetry.dependencies]
python = "^3.7"
curio = "^1.2"
+appdirs = "^1.4.4"
+toml = "^0.10.1"
[tool.poetry.dev-dependencies]
python-language-server = "^0.31.10"
diff --git a/src/mcross/__init__.py b/src/mcross/__init__.py
index eb8be0e..c99ebbe 100644
--- a/src/mcross/__init__.py
+++ b/src/mcross/__init__.py
@@ -1,17 +1,20 @@
-import argparse
-
-from .gui.controller import Controller
-
-
def run():
+ from . import conf
+ from .gui.controller import Controller
- # Parse CLI arguments
- argparser = argparse.ArgumentParser()
- argparser.add_argument("--textfont")
- argparser.add_argument("--monofont")
- argparser.add_argument("--dark", action="store_true")
- args = argparser.parse_args()
+ conf.init()
# Actually start the program
- c = Controller(args)
+ c = Controller()
c.run()
+
+
+def info():
+ from . import conf
+ from pprint import pprint
+
+ conf.init()
+
+ print("Config file:", conf.CONFIG_FILE)
+ print("Config:")
+ pprint(conf._conf)
diff --git a/src/mcross/conf.py b/src/mcross/conf.py
new file mode 100644
index 0000000..f37c43a
--- /dev/null
+++ b/src/mcross/conf.py
@@ -0,0 +1,78 @@
+import argparse
+import os
+from collections import namedtuple
+from pathlib import Path
+
+import toml
+from appdirs import user_config_dir
+
+CONFIG_DIR = Path(user_config_dir("mcross", False))
+CONFIG_FILE = CONFIG_DIR / "mcross.toml"
+
+_conf = None
+
+ConfDef = namedtuple("ConfDef", ["name", "short_name", "type", "default"])
+# argparse's add_argument() will ensure name/short_name uniqueness for free
+conf_definitions = [
+ ConfDef("text-font", "f", str, "Source Serif Pro"),
+ ConfDef("mono-font", "m", str, "Ubuntu Mono"),
+ ConfDef("background-color", "b", str, "#fff8dc"),
+ ConfDef("text-color", "t", str, "black"),
+ ConfDef("link-color", "l", str, "brown"),
+ ConfDef("list-item-color", "i", str, "#044604"),
+]
+
+
+def init():
+ default_conf = load_default_conf()
+ file_conf = load_conf_file()
+ cli_conf = parse_conf_args()
+
+ global _conf
+ _conf = {**default_conf, **file_conf, **cli_conf}
+ return _conf
+
+
+def load_default_conf():
+ return {confdef.name: confdef.default for confdef in conf_definitions}
+
+
+def load_conf_file():
+ if not CONFIG_DIR.is_dir():
+ os.mkdir(CONFIG_DIR)
+
+ if not CONFIG_FILE.is_file():
+ return {}
+
+ try:
+ data = toml.load(CONFIG_FILE)
+ return {
+ confdef.name: data[confdef.name]
+ for confdef in conf_definitions
+ if confdef.name in data
+ }
+ except Exception as e:
+ print("Unexpected error reading config file:", str(e))
+ return {}
+
+
+def parse_conf_args():
+ argparser = argparse.ArgumentParser()
+ for confdef in conf_definitions:
+ argparser.add_argument(
+ f"-{confdef.short_name}", f"--{confdef.name}", type=confdef.type,
+ )
+ args = argparser.parse_args()
+ return {key.replace("_", "-"): val for key, val in vars(args).items() if val}
+
+
+def get(key):
+ return _conf[key]
+
+
+if __name__ == "__main__":
+ init()
+ import pprint
+
+ print("Final conf:")
+ pprint.pprint(_conf)
diff --git a/src/mcross/gui/controller.py b/src/mcross/gui/controller.py
index de350c2..6397404 100644
--- a/src/mcross/gui/controller.py
+++ b/src/mcross/gui/controller.py
@@ -21,13 +21,11 @@
class Controller:
- def __init__(self, args):
+ def __init__(self):
self.root = Tk()
self.root.alt_shortcuts = set()
self.model = Model()
- self.view = View(
- self.root, self.model, fonts=(args.textfont, args.monofont), dark=args.dark
- )
+ self.view = View(self.root, self.model)
self.root.title("McRoss Browser")
self.root.geometry("800x600")
diff --git a/src/mcross/gui/view.py b/src/mcross/gui/view.py
index 06fdd1f..bd0fa34 100644
--- a/src/mcross/gui/view.py
+++ b/src/mcross/gui/view.py
@@ -2,6 +2,7 @@
import sys
from tkinter import Text, Tk, font, ttk
+from .. import conf
from ..document import (
GeminiNode,
H1Node,
@@ -71,7 +72,7 @@ class View:
back_callback = None
forward_callback = None
- def __init__(self, root: Tk, model: Model, fonts=(None, None), dark=False):
+ def __init__(self, root: Tk, model: Model):
self.model = model
# first row - address bar + buttons
@@ -142,31 +143,28 @@ def on_ctrl_l(ev):
text = ReadOnlyText(row2, wrap="word")
self.text = text
self.render_page()
- if fonts[0] is None:
- text_font = pick_font(
- [
- "Charis SIL",
- "Source Serif Pro",
- "Cambria",
- "Georgia",
- "DejaVu Serif",
- "Times New Roman",
- "Times",
- "TkTextFont",
- ]
- )
- else:
- text_font = fonts[0]
+ text_font = pick_font(
+ [
+ conf.get("text-font"),
+ "Charis SIL",
+ "Source Serif Pro",
+ "Cambria",
+ "Georgia",
+ "DejaVu Serif",
+ "Times New Roman",
+ "Times",
+ "TkTextFont",
+ ]
+ )
- if fonts[1] is None:
- mono_font = pick_font(["Ubuntu Mono", "Consolas", "Courier", "TkFixedFont"])
- else:
- mono_font = fonts[1]
+ mono_font = pick_font(
+ [conf.get("mono-font"), "Ubuntu Mono", "Consolas", "Courier", "TkFixedFont"]
+ )
text.config(
font=(text_font, 13),
- bg="#212121" if dark else "#fff8dc",
- fg="#eee" if dark else "black",
+ bg=conf.get("background-color"),
+ fg=conf.get("text-color"),
padx=5,
pady=5,
# hide blinking insertion cursor:
@@ -176,13 +174,13 @@ def on_ctrl_l(ev):
height=1,
)
text.pack(side="left", fill="both", expand=True)
- text.tag_config("link", foreground="#ff8a65" if dark else "brown")
+ text.tag_config("link", foreground=conf.get("link-color"))
text.tag_bind("link", "<Enter>", self._on_link_enter)
text.tag_bind("link", "<Leave>", self._on_link_leave)
text.tag_bind("link", "<Button-1>", self._on_link_click)
text.tag_config("pre", font=(mono_font, 13))
text.tag_config("plaintext", font=(mono_font, 13))
- text.tag_config("listitem", foreground="#64c664" if dark else "#044604")
+ text.tag_config("listitem", foreground=conf.get("list-item-color"))
base_heading_font = font.Font(font=text["font"])
base_heading_font.config(weight="bold")