diff options
author | Kenny Ballou <kb@devnulllabs.io> | 2022-05-21 03:33:36 -0600 |
---|---|---|
committer | Kenny Ballou <kb@devnulllabs.io> | 2022-05-21 03:33:36 -0600 |
commit | 09a6ed6b7a273d92e3ae0c586d0964d0faff0bb4 (patch) | |
tree | e82eefe4d80260b5864eb57b175e044302417008 /gnu | |
parent | 048e7637e99a6b796b7d3e26ab54d060ae92209f (diff) | |
download | dotfiles-09a6ed6b7a273d92e3ae0c586d0964d0faff0bb4.tar.gz dotfiles-09a6ed6b7a273d92e3ae0c586d0964d0faff0bb4.tar.xz |
Copy rde/emacs services files; remove rde channel
Current combination of channels, the abcdw/rde channel cannot build.
Currently, only the home-emacs-service is being used.
Signed-off-by: Kenny Ballou <kb@devnulllabs.io>
Diffstat (limited to 'gnu')
-rw-r--r-- | gnu/home-services-utils.scm | 452 | ||||
-rw-r--r-- | gnu/home-services/emacs.scm | 372 |
2 files changed, 824 insertions, 0 deletions
diff --git a/gnu/home-services-utils.scm b/gnu/home-services-utils.scm new file mode 100644 index 00000000..5bebb1c4 --- /dev/null +++ b/gnu/home-services-utils.scm @@ -0,0 +1,452 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz> +;;; Copyright © 2021 Andrew Tropin <andrew@trop.in> +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. + +(define-module (gnu home-services-utils) + #:use-module (gnu services configuration) + #:use-module (gnu home services utils) + #:use-module (guix ui) + #:use-module (guix diagnostics) + #:use-module (guix gexp) + #:use-module (guix monads) + #:use-module (guix i18n) + #:use-module (guix profiles) + #:use-module (guix packages) + #:use-module (guix build-system trivial) + + #:use-module (ice-9 curried-definitions) + #:use-module (ice-9 match) + #:use-module (ice-9 string-fun) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-9) + #:use-module (srfi srfi-171) + #:use-module (srfi srfi-26) + #:use-module (srfi srfi-43) + + #:re-export (filter-configuration-fields + + list-of + + list-of-strings? + alist? + text-config? + serialize-text-config + generic-serialize-alist-entry + generic-serialize-alist + + maybe-object->string + object->snake-case-string + object->camel-case-string) + + #:export (slurp-file-gexp + + alist-entry->mixed-text + boolean->yes-or-no + boolean->true-or-false + list->human-readable-list + + ini-config? + generic-serialize-ini-config + generic-serialize-git-ini-config + + yaml-config? + serialize-yaml-config + + string-or-gexp? + serialize-string-or-gexp + + gexp-text-config? + serialize-gexp-text-config + + rest + maybe-list + optional + wrap-package + + define-enum + enum-name + enum-value)) + + +;;; +;;; User's utils. +;;; + +(define* (slurp-file-gexp file #:key (encoding "UTF-8")) + "Returns a gexp, which reads all the content of the FILE and returns +it as a string. FILE must be a file-like object." + (when (not (file-like? file)) + (raise (formatted-message + (G_ "~a is not a file-like object.") + file))) + #~(call-with-input-file #$file + (@ (ice-9 textual-ports) get-string-all) + #:encoding #$encoding)) + + +;;; +;;; Configuration related helpers. +;;; + +(define* ((alist-entry->mixed-text prefix sep #:optional (suffix "\n")) + alist-entry) + "Create a list from ALIST-ENTRY, which can be used with +@code{mixed-text-file} for example to create key-value configuration +file or shell script. + +PREFIX is the string to prefix the key-value pair with. For example, +@code{\"export\"} will return @code{'(\"export\" KEY SEP VALUE)}, +where KEY is the first element of ALIST-ENTRY, and VALUE is the second +element of ALIST-ENTRY. + +SEP is the separator between the key and the value. + +SUFFIX is the optional argument, default to newline. + +Different things will happen depending on the value of VALUE: +@itemize @bullet +@item If VALUE is #f, ignore everything in the entry and just return +an empty list. + +@item If VALUE is #t or not provided (empty list), ignore the VALUE +and SEP and just return a list of PREFIX and KEY followed by a +SUFFIX. + +@item If VALUE is a flat list, it will get added to the resulting +list. If not flat the exception will be raised. + +@item If VALUE is not a list (string, file-like object, etc), return a +list of PREFIX, KEY, SEP and VALUE followed by a SUFFIX. + +The following code +@lisp +((alist-entry->mixed-text \"export \" \"=\") '(\"EDITOR\" . \"emacsclient\")) +((alist-entry->mixed-text \"export \" \"=\") '(\"EDITOR\" . #t)) +((alist-entry->mixed-text \"export \" \"=\") '(\"EDITOR\")) +((alist-entry->mixed-text \"export \" \"=\") '(\"EDITOR\" . #f)) +((alist-entry->mixed-text \"export \" \"=\") '(\"EDITOR\" . (\"emacsclient\" \"vim\"))) +@end lisp + +would yield + +@example +(\"export \" \"EDITOR\" \"=\" \"emacsclient\" \"\n\") +(\"export \" \"EDITOR\" \"\n\") +(\"export \" \"EDITOR\" \"\n\") +() +(\"export \" \"EDITOR\" \"=\" \"emacsclient\" \"vim\" \"\n\") +@end example" + (define (not-alist-entry-error) + (raise (formatted-message + (G_ "~a has to be an association list entry") + alist-entry))) + (match alist-entry + ((key . value) + (let* ((values (cond + ((eq? value #f) + #f) + ((or (eq? value #t) (null? value)) + '("")) + ((list? value) + (if (any list? value) + (raise (formatted-message + (G_ "~a is not a flat list") + value)) + value)) + (else + (list value)))) + (sep (if (eq? values '("")) + "" + sep))) + (if values + `(,prefix ,key ,sep ,@values ,suffix) + '()))) + (_ (not-alist-entry-error)))) + +(define* (boolean->yes-or-no bool #:optional (capitalize? #f)) + "Convert a boolean BOOL to \"yes\" or \"no\". +Setting CAPITALIZE? to @code{#t} will capitalize the word, it is set to +@code{#f} by default." + (let ((word (if (eq? bool #t) "yes" "no"))) + (if capitalize? + (string-capitalize word) + word))) + +(define* (boolean->true-or-false bool #:optional (capitalize? #f)) + "Convert a boolean BOOL to \"true\" or \"false\". +Setting CAPITALIZE? to @code{#t} will capitalize the word, it is set to +@code{#f} by default." + (let ((word (if bool "true" "false"))) + (if capitalize? + (string-capitalize word) + word))) + +;; TODO: Remove once upstreamed +(define* (list->human-readable-list lst + #:key + (cumulative? #f) + (proc identity)) + "Turn a list LST into a sequence of terms readable by humans. +If CUMULATIVE? is @code{#t}, use ``and'', otherwise use ``or'' before +the last term. + +PROC is a procedure to apply to each of the elements of a list before +turning them into a single human readable string. + +@example +(list->human-readable-list '(1 4 9) #:cumulative? #t #:proc sqrt) +@result{} \"1, 2, and 3\" +@end example + +yields:" + (let* ((word (if cumulative? "and " "or ")) + (init (append (drop-right lst 1)))) + (format #f "~a" (string-append + (string-join + (map (compose maybe-object->string proc) init) + ", " 'suffix) + word + (maybe-object->string (proc (last lst))))))) + + + +;;; +;;; Serializers. +;;; + +(define ini-config? list?) +(define (generic-serialize-ini-config-section section proc) + "Format a section from SECTION for an INI configuration. +Apply the procedure PROC on SECTION after it has been converted to a string" + (format #f "[~a]\n" (proc section))) + +(define default-ini-format-section + (match-lambda + ((section subsection) + (string-append (maybe-object->string section) " " + (maybe-object->string subsection))) + (section + (maybe-object->string section)))) + +(define* (generic-serialize-ini-config + #:key + (combine-ini string-join) + (combine-alist string-append) + (combine-section-alist string-append) + (format-section default-ini-format-section) + serialize-field + fields) + "Create an INI configuration from nested lists FIELDS. This uses +@code{generic-serialize-ini-config-section} and @{generic-serialize-alist} to +serialize the section and the association lists, respectively. + +@example +(generic-serialize-ini-config + #:serialize-field (lambda (a b) (format #f \"~a = ~a\n\" a b)) + #:format-section (compose string-capitalize symbol->string) + #:fields '((application ((key . value))))) +@end example + +@result{} \"[Application]\nkey = value\n\"" + (combine-ini + (map (match-lambda + ((section alist) + (combine-section-alist + (generic-serialize-ini-config-section section format-section) + (generic-serialize-alist combine-alist serialize-field alist)))) + fields) + "\n")) + +(define* (generic-serialize-git-ini-config + #:key + (combine-ini string-join) + (combine-alist string-append) + (combine-section-alist string-append) + (format-section default-ini-format-section) + serialize-field + fields) + "Like @code{generic-serialize-ini-config}, but the section can also +have a @dfn{subsection}. FORMAT-SECTION will take a list of two +elements: the section and the subsection." + (combine-ini + (map (match-lambda + ((section subsection alist) + (combine-section-alist + (generic-serialize-ini-config-section + (list section subsection) format-section) + (generic-serialize-alist combine-alist serialize-field alist))) + ((section alist) + (combine-section-alist + (generic-serialize-ini-config-section section format-section) + (generic-serialize-alist combine-alist serialize-field alist)))) + fields) + "\n")) + +(define yaml-config? list?) +(define (make-yaml-indent depth) + (make-string (* 2 depth) #\space)) + +(define ((serialize-yaml-value depth) value) + (let* ((tab (make-yaml-indent depth))) + (cond + ((string? value) + (list (format #f "'~a'" value))) + ((boolean? value) + (list (format #f "~a" (if value "true" "false")))) + ((file-like? value) + (list value)) + ((alist? value) + (serialize-yaml-alist value #:depth (1+ depth))) + ((vector? value) + (serialize-yaml-vector value #:depth depth)) + (else (list (format #f "~a" value)))))) + +(define ((serialize-yaml-key depth) key) + (when (vector? key) + (raise (formatted-message + (G_ "Vector as key value are not supported by serializer, \ +try to avoid them. ~a") key))) + ((serialize-yaml-value depth) key)) + +(define ((serialize-yaml-key-value depth) key value) + (let ((tab (make-yaml-indent depth))) + `("\n" + ,tab + ,@((serialize-yaml-key depth) key) ": " + ,@((serialize-yaml-value depth) value)))) + +(define ((serialize-yaml-vector-elem depth) elem) + (let ((tab (make-yaml-indent (1+ depth)))) + (cons* + "\n" tab "- " + ((serialize-yaml-value (1+ depth)) elem)))) + +(define* (serialize-yaml-vector vec #:key (depth 0)) + (append-map (serialize-yaml-vector-elem depth) (vector->list vec))) + +(define* (serialize-yaml-alist lst #:key (depth 0)) + (generic-serialize-alist append (serialize-yaml-key-value depth) lst)) + +(define (serialize-yaml-config config) + "Simplified yaml serializer, which supports only a subset of yaml, use +it with caution." + (serialize-yaml-alist config)) + +(define (string-or-gexp? sg) (or (string? sg) (gexp? sg))) +(define (serialize-string-or-gexp field-name val) "") + +;; Guix proper has a different version of text-config. +(define (gexp-text-config? config) + (and (list? config) (every string-or-gexp? config))) +(define (serialize-gexp-text-config field-name val) + #~(string-append #$@(interpose val "\n" 'suffix))) + +;;; +;;; Miscellaneous. +;;; + +(define rest cdr) + +;; Confusing with maybe-list type. +(define (maybe-list a) + "If A is a list, return it, otherwise return a singleton list with A." + (if (list? a) + a + (list a))) + +;; If EXPR1 evaluates to a non-@code{#f} value and EXPR2 is specified, +;; return EXPR2; if it isn't specified, return EXPR1. Otherwise, return +;; an empty list @code{'()}. +(define-syntax optional + (syntax-rules () + ((_ expr1) + (if expr1 expr1 '())) + ((_ expr1 expr2) + (if expr1 expr2 '())))) + +(define (wrap-package pkg executable-name gexp) + "Create a @code{<package>} object that is a wrapper for PACKAGE, and +runs GEXP. NAME is the name of the executable that will be put in the store." + (let* ((wrapper-name (string-append executable-name "-wrapper")) + (wrapper (program-file wrapper-name gexp))) + (package + (inherit pkg) + (name wrapper-name) + (source wrapper) + (propagated-inputs `((,(package-name pkg) ,pkg))) + (build-system trivial-build-system) + (arguments + `(#:modules + ((guix build utils)) + #:builder + (begin + (use-modules (guix build utils) + (srfi srfi-1)) + (let* ((bin (string-append %output "/bin")) + (wrapper (assoc-ref %build-inputs "source"))) + (mkdir-p bin) + (copy-file wrapper (string-append bin "/" ,executable-name))))))))) + + +;;; +;;; Enums. +;;; + +(define-record-type <enum> + (make-enum name value) + enum? + (name enum-name) + (value enum-value)) + +;; Copied from (gnu services configuration) +(define-syntax-rule (id ctx parts ...) + "Assemble PARTS into a raw (unhygienic) identifier." + (datum->syntax ctx (symbol-append (syntax->datum parts) ...))) + +;; (define-enum pinentry-flavor +;; '(emacs gtk qt ncurses tty)) +;; +;; (pinentry-flavor? 'gtk) +;; => #t +;; +;; (enum-value pinentry-flavor) +;; => '(emacs gtk qt ncurses tty) +;; +;; (pinentry-flavor? 'vim) +;; exception: `pinetry-flavor' must be one of `emacs', `gtk', `qt', +;; `ncurses', or `tty', was given `vim' + +(define-syntax define-enum + (lambda (x) + (syntax-case x () + ((_ stem value) + (with-syntax ((stem? (id #'stem #'stem #'?)) + (msg (list->human-readable-list + (second (syntax->datum #'value)) + #:proc (cut format #f "`~a'" <>)))) + #'(begin + (define stem (make-enum (quote stem) value)) + + (define (stem? val) + (if (member val value) + #t + (raise (formatted-message + (G_ "`~a' must of ~a, was given: ~s") + (enum-name stem) + (syntax->datum msg) + val)))))))))) diff --git a/gnu/home-services/emacs.scm b/gnu/home-services/emacs.scm new file mode 100644 index 00000000..ff3dda06 --- /dev/null +++ b/gnu/home-services/emacs.scm @@ -0,0 +1,372 @@ +(define-module (gnu home-services emacs) + #:use-module (gnu home services) + #:use-module (gnu home-services-utils) + #:use-module (gnu home services shepherd) + #:use-module (gnu packages emacs) + #:use-module (gnu services configuration) + + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-26) + #:use-module (ice-9 curried-definitions) + #:use-module (ice-9 pretty-print) + + #:use-module (guix packages) + #:use-module (guix gexp) + #:use-module (guix utils) + #:use-module (guix build-system emacs) + #:use-module ((guix licenses) #:prefix license:) + #:export (home-emacs-service-type + home-emacs-configuration + home-emacs-extension + elisp-configuration-package)) + +(define packages? (list-of package?)) + +(define serialize-packages empty-serializer) +(define serialize-boolean empty-serializer) + +(define elisp-config? list?) +(define (serialize-elisp-config field-name val) + (define (serialize-list-element elem) + (cond + ((gexp? elem) + elem) + (else + #~(string-trim-right + (with-output-to-string + (lambda () + ((@ (ice-9 pretty-print) pretty-print) + '#$elem + #:max-expr-width 79))) + #\newline)))) + + #~(string-append + #$@(interpose + (map serialize-list-element val) + "\n" 'suffix))) + +(define-configuration home-emacs-configuration + (package + (package emacs) + "Emacs package to use.") + (elisp-packages + (packages '()) + "List of Emacs Lisp packages to install.") + (rebuild-elisp-packages? + (boolean #f) + "Rebuild Emacs Lisp packages with version of Emacs specified in +PACKAGE field.") + (server-mode? + (boolean #f) + "Create a shepherd service, which starts emacs in a server-mode. Use +can use @command{emacsclient} to connect to the server (@pxref{Emacs +Server,,,emacs.info}).") + (xdg-flavor? + (boolean #t) + "Whether to place all the configuration files in +@file{$XDG_CONFIG_HOME/emacs}.") + (init-el + (elisp-config '()) + "List of expressions, each expression can be a Sexp or Gexp. + +Sexp is a Emacs Lisp form, preferably valid. Be aware, if you include +values of Guile variables, they won't be automatically converted to +Elisp. Strings doesn't require conversion, but for example booleans +do: @code{#t} -> @code{t}, @code{#f} -> @code{nil}. Be careful here. + +However, Sexp can contain file-like objects; String with path to a +corresponding file will appear in place of each such object. See an +example below for more details. + +Gexp should be string-valued. The value of Gexp will be appended to +resulting Emacs Lisp file. + +The list of expressions will be interposed with \\n and everything +will end up in @file{init.el}. + +@example +(let ((guile-bool-value #f)) + (home-emacs-configuration + (init-el + `((setq rg-binary ,(file-append ripgrep \"/bin/rg\")) + (load-file ,(local-file \"./emacs/test-init.el\")) + \"just a string\" + ;; Make sure you converted guile values to Elisp + (setq tmp-boolean ,(if guile-bool-value 't 'nil)) + ,(if guile-bool-value '(setq v1 nil) '(setq v2 t)) + + ,#~\"\\n;;; Section with gexps results:\" + + ,(slurp-file-gexp (local-file \"./emacs/test-init.el\")) + ,#~(string-append \"(princ \" \"'hello)\") + ,#~\"\\n\" + ,#~\";; Another comment\")))) +@end example + +would yield something like: + +@example +(setq rg-binary + \"/gnu/store/dw884p9d2jb83j4fqvdj2i10fn9xgwqd-ripgrep-12.1.1/bin/rg\") +(load-file + \"/gnu/store/9b1s48crng5dy9xmxskcdnillw18bkg2-test-init.el\") +\"just a string\" +(setq tmp-boolean nil) +(setq v2 t) + +;;; Section with gexps results: +;; Here is +\"a sample\" +;; content of test-init.el + +(princ 'hello) + + +;; Another comment +@end example") + (early-init-el + (elisp-config '()) + "List of expressions, each expression can be a Sexp or Gexp. +Same as @code{init-el}, but result will go to @file{early-init.el}.")) + + +(define ((update-emacs-argument-for-package target-emacs) p) + "Set @code{#:emacs} to EMACS-PACKAGE for package P. To build elisp +packages with proper GNU Emacs version." + (if (equal? + (package-build-system p) + emacs-build-system) + (package + (inherit p) + (arguments + (substitute-keyword-arguments (package-arguments p) + ((#:emacs e #f) target-emacs)))) + p)) + +(define (emacs-argument-updater target-emacs) + "Recursively updates @code{#:emacs} argument for package and all the +inputs." + (package-mapping (update-emacs-argument-for-package target-emacs) + (lambda (p) #f))) + +(define (updated-elisp-packages config) + (let* ((emacs-package (home-emacs-configuration-package config)) + (elisp-packages (home-emacs-configuration-elisp-packages config)) + + (updated-elisp-packages + (if (home-emacs-configuration-rebuild-elisp-packages? config) + (map (emacs-argument-updater emacs-package) + elisp-packages) + elisp-packages))) + updated-elisp-packages)) + +(define (add-emacs-packages config) + (append (updated-elisp-packages config) + ;; It's important for packages to go first to override + ;; built-in emacs packages in case of collisions + (list (home-emacs-configuration-package config)))) + + +(define (add-emacs-shepherd-service config) + (optional (home-emacs-configuration-server-mode? config) + (list (shepherd-service + (documentation "Emacs server. Use @code{emacsclient} to +connect to it.") + (provision '(emacs-server)) + (start #~(make-forkexec-constructor + (list #$(file-append + (home-emacs-configuration-package config) + "/bin/emacs") "--fg-daemon") + #:log-file (string-append + (or (getenv "XDG_LOG_HOME") + (format #f "~a/.local/var/log" + (getenv "HOME"))) + "/emacs.log"))) + (stop #~(make-kill-destructor)))))) + +;; (define* (mixed-text-file name #:rest text) +;; "Return an object representing store file NAME containing TEXT. TEXT is a +;; sequence of strings and file-like objects, as in: + +;; (mixed-text-file \"profile\" +;; \"export PATH=\" coreutils \"/bin:\" grep \"/bin\") + +;; This is the declarative counterpart of 'text-file*'." +;; (define build +;; (gexp (call-with-output-file (ungexp output "out") +;; (lambda (port) +;; ;; TODO: Upstream the fix? +;; (set-port-encoding! port "UTF-8") +;; (display (string-append (ungexp-splicing text)) port))))) + +;; (computed-file name build)) + +(define (get-emacs-configuration-files config) + (let* ((xdg-flavor? (home-emacs-configuration-xdg-flavor? config))) + (define prefix-file + (cut string-append + (if xdg-flavor? + "emacs/" + "emacs.d/") + <>)) + + (define (filter-fields field) + (filter-configuration-fields home-emacs-configuration-fields + (list field))) + + (define (serialize-field field) + (serialize-configuration + config + (filter-fields field))) + + (define (file-if-not-empty field) + (let ((file-name (string-append + (string-drop-right (symbol->string field) 3) + ".el")) + (field-obj (car (filter-fields field)))) + (optional (not (null? ((configuration-field-getter field-obj) config))) + `(,(prefix-file file-name) + ,(mixed-text-file + file-name + (serialize-field field)))))) + + (filter + (compose not null?) + (list + (file-if-not-empty 'init-el) + (file-if-not-empty 'early-init-el))))) + +(define (add-emacs-dot-configuration config) + (if (home-emacs-configuration-xdg-flavor? config) + '() + (get-emacs-configuration-files config))) + +(define (add-emacs-xdg-configuration config) + (if (home-emacs-configuration-xdg-flavor? config) + (get-emacs-configuration-files config) + '())) + +(define-configuration home-emacs-extension + (elisp-packages + (packages '()) + "List of additional Emacs Lisp packages.") + (init-el + (elisp-config '()) + "List of expressions to add to @code{init-el}. See +@code{home-emacs-service-type} for more information.") + (early-init-el + (elisp-config '()) + "List of expressions to add to @code{ealy-init-el}. See +@code{home-emacs-service-type} for more information.")) + +(define (home-emacs-extensions original-config extension-configs) + (home-emacs-configuration + (inherit original-config) + (elisp-packages + (append (home-emacs-configuration-elisp-packages original-config) + (append-map + home-emacs-extension-elisp-packages extension-configs))) + (init-el + (append (home-emacs-configuration-init-el original-config) + (append-map + home-emacs-extension-init-el extension-configs))) + (early-init-el + (append (home-emacs-configuration-early-init-el original-config) + (append-map + home-emacs-extension-early-init-el extension-configs))))) + + +(define home-emacs-service-type + (service-type (name 'home-emacs) + (extensions + (list (service-extension + home-profile-service-type + add-emacs-packages) + (service-extension + home-shepherd-service-type + add-emacs-shepherd-service) + (service-extension + home-files-service-type + add-emacs-dot-configuration) + (service-extension + home-xdg-configuration-files-service-type + add-emacs-xdg-configuration))) + (compose identity) + (extend home-emacs-extensions) + (default-value (home-emacs-configuration)) + (description "Install and configure GNU Emacs, the +extensible, self-documenting editor."))) + +(define (generate-home-emacs-documentation) + (generate-documentation + `((home-emacs-configuration + ,home-emacs-configuration-fields)) + 'home-emacs-configuration)) + + +(define* (elisp-configuration-package + package-name elisp-expressions + #:key + summary authors maintainers url keywords commentary + (elisp-packages '()) + (autoloads? #t)) + "Takes a list of Elisp expressions, create emacs-NAME package. +@code{#~\";;;###autoload\"} can be used to make next expression be +loaded on startup." + + (define (package->package-input pkg) + (list ((@ (guix packages) package-name) pkg) pkg)) + + (define (add-autoloads elisp-expressions) + (fold-right + (lambda (e acc) + (if (list? e) + (cons* #~";;;###autoload" e #~"" acc) + (cons* e #~"" acc))) + '() elisp-expressions)) + + (package + (name (string-append "emacs-" package-name)) + (version "1.0.0") + (build-system emacs-build-system) + (source + (mixed-text-file + (string-append package-name ".el") + (serialize-elisp-config + #f + (append + ;; <https://www.gnu.org/software/emacs/manual/html_node/elisp/Library-Headers.html> + (list #~(format #f ";;; ~a.el --- ~a\n" #$package-name + (or #$summary "No description provided"))) + (if authors + (list #~(format #f ";; Author: ~a\n;;" + #$(string-join authors "\n;; "))) + '()) + (if maintainers + (list #~(format #f ";; Maintainer: ~a\n;;" + #$(string-join maintainers "\n;; "))) + '()) + (if url (list #~(format #f ";; URL: ~a" #$url)) '()) + (if keywords + (list #~#$(format #f ";; Keywords: ~a" + (string-join (map object->string keywords) ", "))) + '()) + (if commentary + (list #~"\n;;; Commentary:\n" + #~#$(string-join + (map + (lambda (x) + (if (string-null? x) + x + (string-append ";; " x))) + (string-split commentary #\newline)) "\n")) + '()) + (list #~"\n;;; Code:\n") + ((if autoloads? add-autoloads identity) elisp-expressions) + (list `(provide ',(string->symbol package-name)) + #~#$(format #f"\n;;; ~a.el ends here" package-name)))))) + (propagated-inputs (map package->package-input elisp-packages)) + (synopsis (or summary "Generated Emacs configuration package")) + (description "Package generated by @code{elisp-configuration-package}.") + (home-page "https://www.gnu.org/software/guix/") + (license license:gpl3+))) |