Intro
This file is a literate configuration of a Guix and Emacs system. Tangling it
(C-c C-v t
) produces home.scm
, which could be then installed as usual.
Using this template you’ll be able to structure a home-environment
definition in
a way that is convenient to you. Besides regular noweb
features, a new source
code block language is implemented, allowing templated configuration files (see
example below). Emacs configuration is included in the home-environment
as a
package
(see the definition), which is built by tangling this org file.
A couple settings to ease the use in emacs:
- set tangle parameters
- eval and wrap
cfg
blocks automatically
Header
#+PROPERTY: header-args :noweb-prefix no :noweb yes :eval no
#+PROPERTY: header-args:elisp :tangle config.el
#+PROPERTY: header-args:cfg :eval yes :cache yes :wrap "src text :noweb-ref guix-file"
Footer
# Local Variables:
# eval: (add-hook 'after-save-hook (lambda () (org-babel-tangle nil nil "scheme")) 0 t)
# eval: (add-hook 'org-babel-pre-tangle-hook #'org-babel-execute-buffer 0 t)
# End:
Template
Main template which tangles to home.scm
.
#+begin_src scheme :tangle home.scm
(use-modules
(gnu)
(gnu services)
(gnu home services)
(gnu home services desktop)
(guix gexp)
(guix git-download)
(guix build-system copy)
(guix packages)
(ice-9 receive))
(define (pkg x)
(if (string? x)
(receive (package out)
(specification->package+output x)
(if (string= "out" out) package
(list package out)))
x))
(home-environment
(services
(cons*
(simple-service
'files home-files-service-type
`((".guile" ,%default-dotguile)
(".config/guix/channels.scm"
,(plain-file
"channels.scm"
(simple-format #f "~s" '(list <<guix-channel>>))))
<<guix-file>>))
<<guix-srv>>
%base-home-services))
(packages (map pkg (list <<guix-pkg>>))))
Channels
guix
#+begin_src scheme :noweb-ref guix-channel
(channel
(name 'guix)
(url "https://git.guix.gnu.org/guix.git")
(branch "master")
(introduction
(make-channel-introduction
"9edb3f66fd807b096b48283debdcddccfea34bad"
(openpgp-fingerprint
"BBB0 2DDF 2CEA F6A8 0D1D E643 A2A0 6DF2 A33A 54FA"))))
Example content
package
#+begin_src scheme :noweb-ref guix-pkg
"emacs-next" "emacs-org"
service
#+begin_src scheme :noweb-ref guix-srv
(service home-dbus-service-type)
simple files
#+begin_src scheme :noweb-ref guix-file
("plain" ,(plain-file "file" "hello, world"))
("local" ,(local-file "." "file" #:recursive? #t))
templated files
A cfg
source block with :dest
parameter will produce a result, which when wrapped using :wrap "src scheme :noweb-ref guix-file"
, would be weaved automatically in the main
template.
#+begin_src cfg :dest "tmpl"
this template gets converted to mixed-text-file
ANT unicode characters mark scheme code
some-program = π (pkg "msmtp") π/bin/msmtp
("tmpl"
,(mixed-text-file
"cfg-file"
"this template gets converted to mixed-text-file\n"
"ANT unicode characters mark scheme code\n"
"some-program = " (pkg "msmtp") "/bin/msmtp"))
Emacs config
ob-cfg.el
A helper script that renders cfg
source code blocks.
;; -*- lexical-binding: t; -*-
(require 'rx)
(defun org-babel-execute:cfg (body params)
"Wrap BODY in a (mixed-text-file). If PARAMS contains :file, use its
contents instead of BODY. If PARAMS contain :chmod, additionally wrap
with (chmod-computed-file)."
(let-alist params
(when .:file
(with-temp-buffer
(insert-file-contents-literally .:file nil)
(setf body (buffer-string))))
(concat
(if .:dest (format "(%S\n," .:dest) "")
(if .:chmod "(chmod-computed-file " "")
"(mixed-text-file\n \"cfg-file\"\n"
;; split string into parts 'string-partπscheme-partπ'
;; - output each line of 'string-part' quoted.
;; - output 'scheme-part' as is
;; - note: add an empty 'scheme-part' at the end of input string
;; so that the last 'string-part' is matched
(replace-regexp-in-string
(rx (minimal-match
(group (zero-or-more anything))
?π (group (zero-or-more anything)) ?π))
(lambda (e)
(let ((string-part (match-string 1 e))
(scheme-part (match-string 2 e)))
(concat
(if (string-empty-p string-part) ""
(string-join
(mapcar
(lambda (l) (prin1-to-string l nil '((escape-newlines . t))))
(string-lines string-part nil t))
"\n"))
scheme-part)))
(concat body "ππ") t t)
")"
(if .:chmod (format " #o%o)" (org-babel-interpret-file-mode .:chmod)) "")
(if .:dest ")" ""))))
config package
#+begin_src scheme :noweb-ref emacs-config :noweb yes
(package
(name "emacs-config")
(version "0")
(source (local-file "." "src" #:recursive? #t))
;; you really should put the files in git and enable filtering
;; #:select? (git-predicate ".")
(build-system copy-build-system)
(arguments
(list
#:install-plan ''(("." "" #:include (".el")))
#:phases
#~(modify-phases %standard-phases
(add-before 'install 'tangle
(lambda* (#:key inputs #:allow-other-keys)
(substitute* "literate.org"
(("\\(guix[/]pkg '([^ )]+)" _ pkg)
(format #f "(concat ~s"
(or (assoc-ref inputs pkg)
(error (format #f "~a is required for emacs config but not present in inputs" pkg))))))
(invoke "emacs" "-Q" "-l" "ob-cfg.el" "--batch" "--eval"
(simple-format #f "~s"
'(progn
(require 'ob-tangle)
(setq org-confirm-babel-evaluate nil)
(with-current-buffer
(find-file-noselect "literate.org")
(org-babel-tangle nil "early-init.el" "elisp")))))
(delete-file "literate.org"))))))
(inputs (map pkg '("msmtp")))
(native-inputs (map pkg '("emacs-org" "emacs-next-minimal")))
(description "emacs config")
(home-page #f)
(synopsis #f)
(license #f))
#+begin_src scheme :noweb-ref guix-file
(".config/emacs" ,<<emacs-config>>)
config parts
#+begin_src text :noweb-ref emacs-dep
"msmtp"
#+begin_src elisp
(load (concat user-emacs-directory "ob-cfg.el"))
(require 'sendmail)
(setq sendmail-program (guix/pkg 'msmtp "/bin/msmtp"))