unofficial mirror of guix-devel@gnu.org 
 help / color / mirror / code / Atom feed
* Proof of Concept: Import Emacs' use-packaged packages into Guix' manifest.scm
@ 2022-12-18  1:54 Mekeor Melire
  2022-12-18  8:11 ` Liliana Marie Prikler
                   ` (2 more replies)
  0 siblings, 3 replies; 13+ messages in thread
From: Mekeor Melire @ 2022-12-18  1:54 UTC (permalink / raw)
  To: Guix Devel

[-- Attachment #1: Type: text/plain, Size: 3390 bytes --]

Hello,

I'd like to share some code, thus prove a concept, ask for 
feedback, and ask if it could makes sense, under certain 
circumstances, to provide similar code in GNU Guix. The code can 
be used in a ~manifest.scm~ file (but probably could also be used 
with ~guix package -e~ or inside ~guix repl~) where it can be used 
to import all ~emacs-*~ packages that are loaded with 
~(use-package PACKAGE [...])~ inside an Elisp-file like ~init.el~:

#+begin_src scheme
;; ~/some/manifest.scm
(specifications->manifest
 (append
   (el/file->specifications "/home/user/.emacs.d/init.el")
   (list
     "emacs"
     "git"
     "gnupg"
     "mu"
     "syncthing")))
#+end_src

It has many severe caveats, including but not limited to:

1. The code uses Guile/Scheme's ~read~ function which interprets 
the Elisp-file as Scheme. Thus, the code does not really find out 
which packages are loaded, but rather it only looks for the 
~(use-package PACKAGE [...])~ pattern of an S-expression. E.g. 
something like ~(apply 'use-package foo)~ won't be caught. (I 
don't expect Guile's Emacs-Lisp language-feature to be mature 
enough to be usable for this.)

2. Not all external packages are loaded with ~use-package~. E.g. 
~(require 'foo)~ might be used directly. But it'd be possible to 
extend the code to allow several ways/patterns of importing 
packages, and maybe also to specify custom patterns.

3. Not all S-expressions that match against ~(use-package 
PACKAGE)~ are load external packages. E.g. ~(if nil (use-package 
foo))~ won't load anything at all. Also, neither ~(use-package 
emacs)~ nor ~(use-package savehist)~ will load anything, because 
they ~require~ features that ship with GNU Emacs by default. 
That's why the functions in the appended code offer an optional 
keyword argument, #:block, in order to block a list packages.

4. The ~manifest.scm~ is not the single source of truth anymore. 
I.e. this code compromises the purpose of a manifest. (It's 
possible to extend the code such that the Elisp-file's hash can 
optionally be verified with given hash.)

Nevertheless, for me, personally, it's pretty neat and handy to 
use, because I don't need to maintain the list of emacs-packages 
in two places. I also think that it could come pretty handy for 
many others, at least in order to initialize their user-profile, 
by running something like ~guix package -e '(some-magic 
"/home/user/.emacs.d/init.el")'~.

What do you think? Should this go into a separate, private 
channel? Into the Guix Cookbook? Into Guix, if so, then probably 
with lots of changes? Should it just stay here, in this mailing 
list thread? Or do you think this is just a bad idea in general?

I should mention that I go the idea for this code from the 
Nix-Community's Emacs-Overlay which offers a similar feature; as 
described in their README as follows:

#+begin_src nix
# 
   https://github.com/nix-community/emacs-overlay#extra-library-functionality
{
 environment.systemPackages = [
   (emacsWithPackagesFromUsePackage {
     # Your Emacs config file. Org mode babel files are also
     # supported.
     # NB: Config files cannot contain unicode characters, since
     #     they're being parsed in nix, which lacks unicode
     #     support.
     # config = ./emacs.org;
     config = ./emacs.el;
# ...
})]}
#+end_src

Here's the code. Please show mercy, it's my first real Scheme 
code:


[-- Attachment #2: el-manifest.scm --]
[-- Type: text/plain, Size: 4133 bytes --]

(define-module (manifest)
  #:autoload (gnu packages)        (specifications->manifest)
  #:autoload (srfi srfi-1)         (drop fold-right)
  #:autoload (ice-9 textual-ports) (get-string-all))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; LIBRARY

(define el/default-block
;;   "Default list of packages, as symbols, which will be blocked. It's a subset
;; default features provided by GNU Emacs."
  '(emacs hl-line outline savehist))

(define* (el/file->specifications content-f #:key (block el/default-block))
  "Given the file-path CONTENT-F, interprete it as an Emacs-Lisp source-code
and extract a list of package-names, as strings, that have been loaded using
(use-package PACKAGE [...])."
  (call-with-input-file content-f
    (lambda (input) (el/port->specifications input #:block block))))

(define* (el/string->specifications content-s #:key (block el/default-block))
  "Given a string CONTENT-S, interprete it as an Emacs-Lispsource-code and
extract a list of package-names, as strings, that have been loaded using
(use-package PACKAGE [...])."
  (call-with-input-string content-s
    (lambda (input) (el/port->specifications input #:block block))))

(define* (el/port->specifications content-p #:key (block el/default-block))
  "Given the input-port CONTENT-P, interprete it as an Emacs-Lisp source-code
and extract a list of package-names, as strings, that have been loaded using
(use-package PACKAGE [...])."
  (let*
    ((content-string-or-eof
       (get-string-all content-p))
      (content-string
        (if
          (eof-object? content-string-or-eof)
          ""
          content-string-or-eof))
      ;; let's wrap the file into a (quote ...) so that a single invocation of ~read~ will read the whole file
      (quoted-content-string
        (string-append "(quote " content-string "\n)"))
      (content-expression
        (with-input-from-string quoted-content-string read)))
    (map
      (lambda (package-string) (string-append "emacs-" package-string))
      (map
        symbol->string
        (filter
          (lambda (package-expression) (not (member package-expression block)))
          (el/find-used-packages content-expression (list)))))))

(define (el/find-used-packages expression accumulator)
  "Given a quoted expression EXPRESSION and an accumulator ACCUMULATOR,
being a list of package names, add all package-names into ACCUMULATOR, that
have been loaded with (use-package PACKAGE [...]), and return that possibly
expanded list."
  (cond
    ;; in case of no s-expression, return accumulator
    ((not (list? (peek expression))) accumulator)
    ;; in case of an empty s-expression, (), return accumulator
    ((nil? expression) accumulator)
    ;; in case of an s-expression with a single element, (X), walk into that element
    ((nil? (cdr expression))
      (el/find-used-packages (car expression) accumulator))
    ;; in case of an use-package s-expression, (use-package PACKAGE [...]), add PACKAGE to the accumulator and also add those elements which possibly result from walking through the remaining elements
    ((and
       (eq? (car expression) 'use-package)
       (symbol? (cadr expression)))
      (fold-right
        el/find-used-packages
        (let
          ((package (cadr expression)))
          (if
            (member package accumulator)
            accumulator
            (cons package accumulator)))
        (drop expression 2)))
    ;; in case of a s-expression with at least two elements, (X Y [...]), walk through all of them
    (#t
      (fold-right
        el/find-used-packages
        accumulator
        expression))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; MANIFEST

(specifications->manifest
  (append

    (el/file->specifications "/home/user/.emacs.d/configuration.el" #:block
      ;; block some packages as they won't be loaded on this host:
      (cons*
        'gist
        'guix-emacs
        'hide-lines
        'json
        'kubernetes
        'vertico-directory
        el/default-block))

    (list
      "emacs"
      "git"
      "isync"
      "mu")))

^ permalink raw reply	[flat|nested] 13+ messages in thread
[parent not found: <875ydxi4xn.fsf@posteo.de>]

end of thread, other threads:[~2023-02-03  2:32 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-12-18  1:54 Proof of Concept: Import Emacs' use-packaged packages into Guix' manifest.scm Mekeor Melire
2022-12-18  8:11 ` Liliana Marie Prikler
2022-12-19 10:15   ` Andrew Tropin
2022-12-20  9:45     ` Mekeor Melire
2022-12-20 14:56       ` Andrew Tropin
2022-12-20  9:16   ` Mekeor Melire
2022-12-20 15:06     ` Andrew Tropin
2022-12-19 10:42 ` zimoun
2022-12-28  0:51 ` Mitchell Schmeisser via Development of GNU Guix and the GNU System distribution.
2023-02-02  9:44   ` Mekeor Melire
2023-02-03  2:20     ` Mitchell Schmeisser via Development of GNU Guix and the GNU System distribution.
2023-02-03  2:31     ` Mitchell Schmeisser via Development of GNU Guix and the GNU System distribution.
     [not found] <875ydxi4xn.fsf@posteo.de>
2022-12-27 18:52 ` Mitchell Schmeisser via Development of GNU Guix and the GNU System distribution.

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/guix.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).