| git.druid.rocks | index | druid520 | radium | radium-conf.el |
radium-conf.el
;;; radium-conf.el, cfg.rp project dsl -*- lexical-binding: t; -*-
;;
;; per-project config for the radium tooling, in a cfg.rp at the
;; project root: suckless `key = value` lines under [sections], like
;; toml minus the ceremony. values are raw strings to end of line, no
;; quotes, no types. comments (`;` or `#`) only on their own lines -
;; inline comments would fight shell commands, which are legal values
;; here.
;;
;; ; build the tags db in the background when it's missing
;; [tags]
;; gtags = yes
;;
;; ; override auto-detection and mk.conf targets
;; [build]
;; tool = gmake
;; run_cmd = ./bin/serve -v
;;
;; ; push templates, {remote}/{branch} get substituted
;; [push]
;; remote = origin
;; branch = trunk
;; deploy = git push {remote} {branch} && ssh box 'do-deploy'
;;
;; sections can grow freely; anything that doesn't know a key just
;; ignores it. every lookup falls back gracefully, so a bare project
;; without the file behaves exactly like before.
(defconst radium-conf-file "cfg.rp")
(defun radium-conf-root ()
"the project root holding cfg.rp, located upward from
default-directory, or nil when there isn't one."
(locate-dominating-file default-directory radium-conf-file))
(defun radium-conf-parse (file)
"parse FILE into ((section . ((key . value)...))...). blank lines and
comment lines are skipped."
(let (sections current)
(when (file-exists-p file)
(with-temp-buffer
(insert-file-contents file)
(goto-char (point-min))
(while (not (eobp))
(let ((line (string-trim
(buffer-substring-no-properties
(line-beginning-position) (line-end-position)))))
(cond
((or (string-empty-p line)
(string-prefix-p ";" line)
(string-prefix-p "#" line)))
((string-match "\\`\\[\\([^]]+\\)\\]\\'" line)
(setq current (intern (string-trim (match-string 1 line))))
(unless (assq current sections)
(push (cons current nil) sections)))
((and current
(string-match "\\`\\([^=]+\\)=\\(.*\\)\\'" line))
(let ((sec (assq current sections)))
(setcdr sec
(cons (cons (intern (string-trim (match-string 1 line)))
(string-trim (match-string 2 line)))
(cdr sec)))))
;; anything else - a `[section] ; trailing text' header
;; (breaks the fully-anchored section regex above), a
;; key=value line before any [section] has been seen, a
;; typo'd header missing its closing `]' - silently
;; falling through here used to mean the line just
;; vanished with `current' left pointing at whatever
;; section came before it, so every key after it got
;; silently misattributed to the wrong section: verified
;; directly, a `[qemu] ; comment' header left `kernel='
;; on the next line landing in the PRIOR section, no
;; error at all. loud beats silently-wrong here.
(t
(message "err: %s:%d: cfg.rp line not understood, ignoring: %s"
file (line-number-at-pos) line)))
(forward-line 1))))
(mapcar (lambda (sec)
(cons (car sec) (nreverse (cdr sec))))
(nreverse sections)))))
(defun radium-conf (section key &optional default)
"the value of KEY in cfg.rp's SECTION for the project at point,
or DEFAULT when the file, section or key is missing."
(let* ((root (radium-conf-root))
(file (and root (expand-file-name radium-conf-file root))))
(if (and file (file-exists-p file))
(let ((sec (assq section (radium-conf-parse file))))
(or (and sec (cdr (assq key (cdr sec)))) default))
default)))
(provide 'radium-conf)