├── .dir-locals.el ├── .gitignore ├── README.org ├── TRANSFORM.org ├── guix-packaging.el └── snippets ├── markdown-mode └── .yas-parents ├── org-mode └── .yas-parents ├── scheme-mode ├── guix-gnu-package ├── guix-go-package ├── guix-go-package-no-version └── guix-node-package ├── sgml-mode └── repology-badge ├── text-mode └── guix-issue-url └── web-mode └── .yas-parents /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ;;; Directory Local Variables 2 | ;;; For more information see (info "(emacs) Directory Variables") 3 | 4 | ((prog-mode . ((mode . nameless) 5 | (nameless-current-name . "guix-packaging-")))) 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /guix-packaging.elc 2 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+options: toc:nil num:nil author:nil creator:nil date:nil 2 | 3 | guix-packaging (aka "guix-packaging.el") provides tools to create and maintain 4 | Guix packages quickly and with confidence. 5 | 6 | *Vision:* 7 | #+begin_quote 8 | Writing and maintaining Guix packages in Emacs should be a breeze. The 9 | information you need should be ready at your fingertips and any chores or 10 | guesswork that can be reasonably automated should be. In particular, maintainers 11 | of Emacs packages in Guix should be able to complete the entire cycle of package 12 | creation, testing, installation, updates, maintenance and discovery within 13 | Emacs. 14 | #+end_quote 15 | 16 | ** Creating & maintaining Guix packages 17 | 18 | *** Commands 19 | 20 | **** ~M-x~ ~guix-packaging-insert-input~ 21 | Prompts for a set of package strings with completion, for example ~ruby@2.7.2~, 22 | and inserts the corresponding Guix input form such as ~("ruby@2.7.2" 23 | ,ruby-2.7)~. 24 | 25 | Reads the Guile source code to find the appropriate symbol name, so it might not 26 | work with some packages that have unusual source code. 27 | 28 | **** ~M-x~ ~guix-packaging-hash-git~ 29 | Saves the Guix hash to the kill ring for a git repository URL at a given tag. 30 | 31 | Reads your mind to get the default URL: prefers the URL at point, falls back to 32 | the URL following =(url "= in the ~defun~ at point, or as a last resort, uses 33 | the next match in the buffer for ~goto-address-url-regexp~. 34 | 35 | **** ~M-x~ ~guix-packaging-refresh-packages~ 36 | Refresh the cache of information about available Guix packages. 37 | 38 | ----- 39 | 40 | *** Snippets 41 | 42 | **** scheme mode 43 | The ~guix-go~ snippet creates the skeleton of a go module package and assists 44 | you in quickly filling it out. You first provide the import path, from which the 45 | snippet infers the symbol & package names, the repo URL, the hash, and the 46 | homepage. All of these are presented in the snippet as default values that you 47 | can change as you go for unusual cases. 48 | 49 | The ~guix-go-noversion~ snippet is similar but instead provides a package 50 | skeleton suitable for those modules which don't have any releases. 51 | 52 | **** sgml, web, markdown, and org modes 53 | The ~guix-badge~ snippet inserts the HTML for a Repology badge showing the 54 | package status in Guix and linking to the project page in Repology, handy for 55 | project status pages. 56 | 57 | **** text mode 58 | The ~guix-issue-url~ snippet inserts a URL pointing to the Guix issue tracker. 59 | 60 | ----- 61 | 62 | *** Configuration 63 | 64 | **** Output & error buffers 65 | Set ~guix-packaging-output-buffer~ to control where status messages are sent, 66 | and ~guix-packaging-error-buffer~ for error messages. These can be the same if 67 | you prefer them interleaved. (Default: ~*guix-packaging*~) 68 | 69 | **** Guix 70 | Set ~guix-packaging-guix-executable~ to specify which executable file should be 71 | used to invoke Guix commands. (Default: =~/.config/guix/current/bin/guix=) 72 | 73 | Add extra Guile load paths to Guix commands that support them by putting them in 74 | ~guix-packaging-extra-load-paths~. 75 | 76 | **** Snippets 77 | Set ~guix-packaging-slug-dash-pattern~ to control which characters will get 78 | replaced with dashes when making a slug to use as a Guile symbol. 79 | 80 | ----- 81 | 82 | *** Structural transformation 83 | 84 | Structural transformation is an experimental feature. It assists you in changing 85 | the structure of a Guix package definition without affecting the content. 86 | 87 | It is documented in [[./TRANSFORM.org][TRANSFORM.ORG]]. 88 | 89 | ** Tracking packaging progress 90 | 91 | Some packages are complex due to a large number of transitive dependencies. I've 92 | found this to be true of software in the golang ecosystem where it's not 93 | uncommon to see 50+ transitive dependencies, and occasionally many more. 94 | 95 | guix-packaging provides some facilities to help track progress on complex packages. 96 | 97 | *** Commands 98 | 99 | **** ~M-x~ ~guix-packaging-go-mod-to-checklist-dwim~ 100 | Turns go module definitions into an org/markdown checklist, suitable to keep 101 | track of packaging progress. 102 | 103 | **** ~M-x~ ~guix-packaging-go-mod-to-checkbox~ 104 | Replace a single go module definition with a checkbox. 105 | -------------------------------------------------------------------------------- /TRANSFORM.org: -------------------------------------------------------------------------------- 1 | #+TITLE: Structural Transformation 2 | 3 | /guix-packaging.el/ provides stateful, reversible, content-agnostic code lenses to 4 | allow you to restructure package definitions to suit your preferences. 5 | 6 | This is similar to refactoring. For example, one might create a macro with tools 7 | like ~transpose-regions~ and ~replace-string~ to rename and re-order the 8 | sections of a package definition. However, after such a refactoring, you're left 9 | with code that is not trivial to merge with that from other collaborators. What 10 | relationship then will you maintain with the upstream? You can: 11 | 12 | 1. Maintain your own fork of Guix and stop submitting patches upstream 13 | 2. Send your structural edits as patches and try to convince everybody else to 14 | accept them 15 | 3. Undo your structural edits temporarily whenever it's time to send patches 16 | 17 | The structural transformation capabilites of /guix-packaging.el/ are designed to 18 | make *option 3* super convenient by enabling you to switch package definitions 19 | between different structures using just a few keystrokes. 20 | 21 | *** Definitions 22 | 23 | - Content-agnostic :: 24 | A code lens is content-agnostic when it affects only the structure of a 25 | package definition, not its meaning. 26 | - Stateful :: 27 | A code lens is stateful when it remembers what the code used to look like, 28 | even between editing sessions and even without using source control. 29 | - Reversible :: 30 | A code lens is reversible when you can use it to travel from one structural 31 | state to another without any restrictions or loss of information. 32 | 33 | Put together, these properties enable you to restructure your definitions with 34 | confidence that you can quickly return to a previous structural state without 35 | affecting the meaning of the code. 36 | 37 | ** How to use 38 | 39 | *** Dependencies 40 | 41 | To use structural transformation you need: 42 | 43 | - GNU Guix (tested with 1.2.0 and later) 44 | - GNU Emacs (tested with 27 and 28) 45 | - guix-packaging.el (provided in this repository) 46 | 47 | #+begin_quote 48 | /Note:/\\ 49 | In the future, this capability could be implemented without these dependencies, 50 | but because of my familiarity with using Emacs for developing experimental 51 | editing capabilities, it's easiest for me to develop it as an Emacs extension 52 | first. If you are interested in making these tools more accessible, please get 53 | in touch! 54 | #+end_quote 55 | 56 | *** Initialize (partially implemented) 57 | 58 | - Visit a file containing Guix package definitions, eg ~guix/gnu/packages/emacs-xyz.scm~ 59 | - Invoke ~M-x~ ~guix-packaging-init-states~. 60 | 61 | This instructs Emacs to analyze the structure of all the packages in the file, 62 | saving them to a hash table for quick lookup later. It might take a while, 63 | depending on how many packages there are to analyze. 64 | 65 | The current structure of all the packages will be tagged "default" and you'll 66 | refer to the initial structure by that name in the future. If you want to 67 | choose a different name, invoke the command using the universal argument (~C-u~ 68 | ~M-x~ ~guix-packaging-init-states~) and it will prompt you for a name. 69 | 70 | You need only use this command once for any given buffer containing package 71 | definitions. After invoking it, Emacs saves the results to disk so it can load 72 | them again without having to re-analyze all the code. 73 | 74 | *** Tag a new structural state (unimplemented) 75 | 76 | - Modify a package definition. For example, take the s-expression defining its 77 | /home-page/ and move it up near the top of the package, right after the /name/. 78 | - Place point (your cursor) within the package definition. 79 | - Invoke ~M-x~ ~guix-packaging-tag-state~ and provide a name tag, like 80 | "mine". Emacs will analyze the package at point, remember its new structure 81 | and save it to disk. If you provide the name of an already-tagged structure, 82 | it will prompt you to overwrite it. 83 | 84 | *** Visit another structural state (unimplemented) 85 | 86 | - Place point (your cursor) within a package definition, invoke ~M-x~ 87 | ~guix-packaging-visit-state~, select a tag, and press ~RET~. 88 | 89 | With the universal argument (~C-u~) this will visit the selected state in all 90 | package definitions in the current buffer for which that state is tagged. 91 | - To advance immediately to the next available state, instead invoke ~M-x~ 92 | ~guix-packaging-visit-next-state~. 93 | 94 | ** State of the sotware 95 | 96 | This is experimental, pre-alpha software. The above instructions don't work yet, 97 | there's much to do. The instructions are provided as a roadmap for what I intend 98 | to implement, so I can get feedback and plan my ongoing implementation work. 99 | That means your feedback is very welcome! 100 | 101 | *** Implemented types of structural lenses 102 | 103 | So far, we have two types: 104 | 105 | 1. Reordering sections 106 | 107 | You can change the order of sections in a package, for example by moving the 108 | /home-page/ section (usually near the bottom of a package) up near the top. 109 | 110 | In Guix packages, most sections can be re-ordered without affecting other 111 | sections. The current implementation is naive and assumes this is always 112 | true. To make it robust to cases where there are dependencies, it should 113 | eventually detect when the name of one section is used as a symbol in 114 | another, and factor that relationship out into a let-form to preserve meaning 115 | when necessary. 116 | 117 | 2. Renaming sections 118 | 119 | You can specify new names for sections. For example, if reading French is 120 | more comfortable, you could change /package/ to /paquet/, and /home-page/ to 121 | /page-d-accueil/. 122 | 123 | I haven't figured out yet what UI I want to have for this. Do I provide a 124 | command to rename a section? Or do I have the editor rename the section the 125 | regular way, and provide a mechanism to inform Emacs about this? In that 126 | case, do I try to auto-discover the relationship based on minimal content 127 | edit-distance? Obviously this does not work if you rename the section and 128 | overhaul the content at the same time, but maybe that's acceptable. 129 | 130 | In addition, ideally we will have a way to inform Guix about our new symbol 131 | names so that it can actually understand these packages. I think we can 132 | generate a Guile scheme file creating the appropriate aliases, such that you 133 | can put it on your include path and things will just work. 134 | 135 | **** Lens wishes 136 | 137 | I would like to implement other types of lenses: 138 | 139 | - Whitespace and vertical indentation within a section would be nice to control. 140 | It's not obvious to me how we do this, though. For one idea, we could 141 | implement a variety of formatters that provide output that's like what is 142 | currently in the Guix code-base. Then run the section through those formatters 143 | and see if any of them are an exact match. If so, you save that formatter as 144 | part of the style. But if there's no match, what do you do? Just treat 145 | whitespace as part of the content then, I suppose? That's what we do for all 146 | sections now, so falling back to that is no worse. 147 | - For packages that can be represented using either JSON or Guile, a lens to 148 | transform between these would be cool. However, there's a few problems. How do we 149 | deal with source files that contain both JSON and Guile? Do we have to split 150 | the transformed packages out into their own separate file or buffer? And how 151 | do we detect which packages can be so transformed without loss of information? 152 | - Some programmers (and the Guix style guide) prefer early-~let~, while some 153 | like to use ~let~ later on. We could capture these as stylistic lenses, but 154 | ensuring that they are content-agnostic and reifying the desired position of 155 | the ~let~ both seem like they will take care. 156 | 157 | *** Using a parser 158 | 159 | I would like to use a parser to understand the code instead of operating on a 160 | literal basis like the code does now. I'm thinking about writing Guile grammar 161 | for ~tree-sitter~ so we can use that to understand the structure. Scheme syntax 162 | is famously minimal, so this might provide a big win for not a lot of effort. It 163 | also has the benefit of being easy to use outside of Emacs. 164 | 165 | *** Generalizing beyond package definitions 166 | 167 | This concept can be generalized beyond package definitions. How far does it make 168 | sense to take it? Other forms, like defuns or gexps? Other lisps, like Clojure? 169 | Other languages, like C and JavaScript? I'd like to eventually explore this. 170 | -------------------------------------------------------------------------------- /guix-packaging.el: -------------------------------------------------------------------------------- 1 | ;;; guix-packaging.el --- Tools for writing and maintaining Guix packages -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright © 2020 Ryan Prior 4 | 5 | ;; Author: Ryan Prior 6 | ;; Keywords: guix tools snippets 7 | ;; Version: 1.5 8 | ;; Homepage: https://github.com/ryanprior/emacs-guix-packaging 9 | ;; Package-Requires: ((emacs "27.1") (dash "2.18.0") (yasnippet "0.14.0") (seq "2.22") (pulse "1.0")) 10 | 11 | ;; This file is not part of GNU Emacs. 12 | 13 | ;; emacs-guix-packaging is free software: you can redistribute it 14 | ;; and/or modify it under the terms of the GNU General Public License 15 | ;; as published by the Free Software Foundation, either version 3 of 16 | ;; the License, or (at your option) any later version. 17 | 18 | ;; This file is distributed in the hope that it will be useful, but 19 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of 20 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 21 | ;; General Public License for more details. 22 | 23 | ;; You should have received a copy of the GNU General Public License 24 | ;; along with this file. If not, see . 25 | 26 | ;;; Commentary: 27 | 28 | ;; guix-packaging (aka "guix-packaging.el") provides tools to create and 29 | ;; maintain Guix packages quickly and with confidence. 30 | 31 | 32 | ;; Commands 33 | ;; ════════ 34 | 35 | ;; `M-x' `guix-packaging-insert-input' 36 | ;; ─────────────────────────────────── 37 | 38 | ;; Prompts for a set of package strings with completion, for example 39 | ;; `ruby@2.7.2', and inserts the corresponding Guix input form such as 40 | ;; `("ruby@2.7.2" ,ruby-2.7)'. 41 | 42 | ;; Reads the Guile source code to find the appropriate symbol name, so it 43 | ;; might not work with some packages that have unusual source code. 44 | 45 | 46 | ;; `M-x' `guix-packaging-hash-git' 47 | ;; ─────────────────────────────── 48 | 49 | ;; Saves the Guix hash to the kill ring for a git repository URL at a 50 | ;; given tag. 51 | 52 | ;; Reads your mind to get the default URL: prefers the URL at point, 53 | ;; falls back to the URL following `(url "' in the `defun' at point, or 54 | ;; as a last resort, uses the next match in the buffer for 55 | ;; `goto-address-url-regexp'. 56 | 57 | 58 | ;; `M-x' `guix-packaging-go-mod-to-checklist-dwim' 59 | ;; ─────────────────────────────────────────────── 60 | 61 | ;; Turns go module definitions into an org/markdown checklist, suitable 62 | ;; to keep track of packaging progress. 63 | 64 | 65 | ;; `M-x' `guix-packaging-go-mod-to-checkbox' 66 | ;; ───────────────────────────────────────── 67 | 68 | ;; Replace a single go module definition with a checkbox. 69 | 70 | ;; `M-x' `guix-packaging-refresh-packages' 71 | ;; ─────────────────────────────────────── 72 | 73 | ;; Refresh the cache of information about available Guix packages. 74 | 75 | 76 | ;; Snippets 77 | ;; ════════ 78 | 79 | ;; scheme mode 80 | ;; ─────────── 81 | 82 | ;; The `guix-go' snippet creates the skeleton of a go module package and 83 | ;; assists you in quickly filling it out. You first provide the import 84 | ;; path, from which the snippet infers the symbol & package names, the 85 | ;; repo URL, the hash, and the homepage. All of these are presented in 86 | ;; the snippet as default values that you can change as you go for 87 | ;; unusual cases. 88 | 89 | ;; The `guix-go-noversion' snippet is similar but instead provides a 90 | ;; package skeleton suitable for those modules which don't have any 91 | ;; releases. 92 | 93 | 94 | ;; sgml, web, markdown, and org modes 95 | ;; ────────────────────────────────── 96 | 97 | ;; The `guix-badge' snippet inserts the HTML for a Repology badge showing 98 | ;; the package status in Guix and linking to the project page in 99 | ;; Repology, handy for project status pages. 100 | 101 | 102 | ;; text mode 103 | ;; ───────── 104 | 105 | ;; The `guix-issue-url' snippet inserts a URL pointing to the Guix issue 106 | ;; tracker. 107 | 108 | 109 | 110 | ;;; Code: 111 | 112 | (require 'cl-lib) 113 | (require 'dash) 114 | (require 'files) 115 | (require 'gv) 116 | (require 'pulse) 117 | (require 'rx) 118 | (require 'seq) 119 | (require 'simple) 120 | (require 'subr-x) 121 | (require 'thingatpt) 122 | (require 'yasnippet) 123 | 124 | (gv-define-simple-setter plist-get plist-put) 125 | 126 | (defgroup guix nil "Interface for the GNU Guix package manager." 127 | :prefix "guix-" 128 | :group 'external) 129 | 130 | (defgroup guix-packaging nil "Tools for writing and maintaining Guix packages." 131 | :prefix "guix-packaging-" 132 | :group 'guix) 133 | 134 | (defcustom guix-packaging-output-buffer "*guix-packaging*" 135 | "Buffer for the output of packaging commands." 136 | :type '(string) 137 | :group 'guix-packaging) 138 | 139 | (defcustom guix-packaging-error-buffer "*guix-packaging*" 140 | "Buffer for the error messages of packaging commands." 141 | :type '(string) 142 | :group 'guix-packaging) 143 | 144 | (defcustom guix-packaging-guix-executable "~/.config/guix/current/bin/guix" 145 | "The executable of guix." 146 | :type '((file :must-match t)) 147 | :group 'guix-packaging) 148 | 149 | (defcustom guix-packaging-extra-load-paths nil 150 | "Extra directories to add to the Guile load path when invoking Guix." 151 | :type '(repeat directory) 152 | :group 'guix-packaging) 153 | 154 | (defcustom guix-packaging-slug-dash-pattern (rx (any " " "_" "/" ".")) 155 | "Pattern matching characters to replace with dashes in a slug." 156 | :type '(regexp) 157 | :group 'guix-packaging) 158 | 159 | (defconst guix-packaging-go-mod-pattern 160 | (rx line-start 161 | (* space) 162 | (+ (not space)) 163 | "/" 164 | (+ (not space)) 165 | " " 166 | (+ (not space)) 167 | (* not-newline) 168 | line-end) 169 | "Pattern matching a single go module requirement.") 170 | 171 | (defconst guix-packaging-go-mod-start-pattern 172 | (rx line-start 173 | (* space) 174 | "require" 175 | (+ space) 176 | "(" 177 | (* space) 178 | line-end) 179 | "Pattern matching the beginning of a go module requirement block.") 180 | 181 | (defconst guix-packaging-go-mod-end-pattern 182 | (rx line-start 183 | (* space) 184 | ")" 185 | (* space) 186 | line-end) 187 | "Pattern matching the end of a go module requirement block.") 188 | 189 | (defconst guix-packaging--snippets-root 190 | (file-name-directory (or load-file-name 191 | (buffer-file-name))) 192 | "Directory where guix-packaging snippets reside.") 193 | 194 | (defvar guix-packaging--data-dir 195 | (concat user-emacs-directory "/var/guix-packaging/") 196 | "Directory for saving Guix package related data.") 197 | (mkdir guix-packaging--data-dir t) 198 | 199 | (defvar guix-packaging--strategies (make-hash-table :test #'equal) 200 | "Hash containing known strategies for Guix packages.") 201 | (defvar guix-packaging--save-after-remember t) 202 | 203 | (defconst guix-packaging--no-load-path-commands 204 | '("hash")) 205 | 206 | (defvar guix-packaging--all-guix-packages nil 207 | "List containing all packages in the local Guix.") 208 | 209 | (defun guix-packaging--load-strategies () 210 | "Load guix-packaging--strategies from disk." 211 | (let ((save-file (expand-file-name (concat guix-packaging--data-dir "/strategies.hash")))) 212 | (when (file-readable-p save-file) 213 | (with-temp-buffer 214 | (insert-file-contents-literally save-file) 215 | (goto-char (point-min)) 216 | (let ((data (read (current-buffer)))) 217 | (if (hash-table-p data) 218 | (setq guix-packaging--strategies data) 219 | (error "Invalid data in %s. Cannot load strategies." save-file))))))) 220 | (guix-packaging--load-strategies) 221 | 222 | 223 | 224 | (cl-defun guix-packaging--message (msg &key prefix) 225 | "Insert MSG into the `guix-packaging-output-buffer', optionally preceeded by PREFIX." 226 | (with-current-buffer (get-buffer-create guix-packaging-output-buffer) 227 | (save-excursion 228 | (goto-char (point-max)) 229 | (when prefix (insert prefix)) 230 | (insert msg) 231 | (newline) 232 | msg))) 233 | 234 | (defun guix-packaging--invoke-guix (cmd &rest args) 235 | (let ((load-strings (cl-map #'list (-partial #'format "-L \"%s\"") guix-packaging-extra-load-paths)) 236 | (cmd (cl-first (split-string cmd))) 237 | (args (or args (cl-rest (split-string cmd))))) 238 | (thread-first (if (-contains-p guix-packaging--no-load-path-commands cmd) 239 | (list guix-packaging-guix-executable cmd args) 240 | (list guix-packaging-guix-executable cmd load-strings args)) 241 | flatten-list 242 | (string-join " ") 243 | (guix-packaging--message :prefix "$ ") 244 | (concat " 2>/dev/null") 245 | shell-command-to-string))) 246 | 247 | (defun guix-packaging--make-slug (string) 248 | "Make a slug out of STRING. 249 | Replaces whitespaces, dots, slashes & underscores in STRING with 250 | dashes and removes other non-alphanumeric characters to make a 251 | slug suitable as a bland Lisp or scheme symbol." 252 | (thread-last string 253 | downcase 254 | (replace-regexp-in-string (rx (+ (regexp guix-packaging-slug-dash-pattern))) 255 | "-") 256 | (replace-regexp-in-string (rx (not (any alphanumeric "-"))) 257 | ""))) 258 | 259 | (defmacro guix-packaging--latch (current init) 260 | "CURRENT unless it's nil or an empty string, in which case INIT." 261 | `(if (or (string= "" ,current) 262 | (null ,current)) 263 | ,init 264 | ,current)) 265 | 266 | (cl-defun guix-packaging--goto-line (n &optional (buffer (current-buffer))) 267 | "Go to the Nth line." 268 | (with-current-buffer buffer 269 | (goto-char (point-min)) 270 | (forward-line (1- n)))) 271 | 272 | (cl-defun guix-packaging--do-on-each-line (func &optional (start (region-beginning)) (end (region-end))) 273 | "Run a command on each line. 274 | Move point to each line between START and END (or current 275 | selected region) and run FUNC each time." 276 | (let ((start (line-number-at-pos start)) 277 | (end (line-number-at-pos end))) 278 | (save-mark-and-excursion 279 | (set-mark nil) 280 | (guix-packaging--goto-line start) 281 | (while (<= (line-number-at-pos) end) 282 | (funcall func) 283 | (forward-line))))) 284 | 285 | (defmacro guix-packaging--with-scheme-buffer (&rest body) 286 | "Evaluate BODY using a temporary scheme-mode buffer." 287 | (declare (indent 0)) 288 | `(let ((geiser-mode-auto-p nil)) 289 | (with-temp-buffer 290 | (font-lock-mode -1) 291 | (scheme-mode) 292 | ,@body))) 293 | 294 | (defun guix-packaging--pulse-region (start end) 295 | (when (pulse-available-p) 296 | (pulse-momentary-highlight-region start end))) 297 | 298 | 299 | 300 | 301 | (defun thing-at-point--beginning-of-go-mod () 302 | "Go to the beginning of a go mod declaration." 303 | (goto-char (line-beginning-position)) 304 | (unless (looking-at-p guix-packaging-go-mod-pattern) 305 | (error "No go mod here")) 306 | (point)) 307 | 308 | (defun thing-at-point--end-of-go-mod () 309 | "Go to the end of a go mod declaration." 310 | (goto-char (line-end-position)) 311 | (unless (looking-back guix-packaging-go-mod-pattern (line-beginning-position)) 312 | (error "No go mod here")) 313 | (point)) 314 | 315 | (defun thing-at-point--bounds-of-go-mod-at-point () 316 | "Get boundaries of go mod declaration at point." 317 | (save-excursion 318 | (let* ((beg (thing-at-point--beginning-of-go-mod)) 319 | (end (thing-at-point--end-of-go-mod))) 320 | (cons beg end)))) 321 | 322 | (defun thing-at-point-go-mod-at-point () 323 | "Get go mod declaration at point." 324 | (let ((bounds (bounds-of-thing-at-point 'go-mod))) 325 | (buffer-substring (car bounds) (cdr bounds)))) 326 | 327 | (put 'go-mod 'beginning-op #'thing-at-point--beginning-of-go-mod) 328 | (put 'go-mod 'end-op #'thing-at-point--end-of-go-mod) 329 | (put 'go-mod 'bounds-of-thing-at-point #'thing-at-point--bounds-of-go-mod-at-point) 330 | (put 'go-mod 'thing-at-point #'thing-at-point-go-mod-at-point) 331 | 332 | 333 | 334 | (defun guix-packaging--tsv-to-plist (tsv-string) 335 | "Transform a TSV-STRING for a package into a plist." 336 | (let* ((fields (split-string tsv-string "\t")) 337 | (outputs (split-string (nth 2 fields) ",")) 338 | (location (-zip '(:file :line :char) 339 | (split-string (nth 3 fields) ":")))) 340 | `(:name ,(nth 0 fields) 341 | :version ,(nth 1 fields) 342 | :outputs ,outputs 343 | :location ,location))) 344 | 345 | (defun guix-packaging--map-tsv-to-plist (package-strings) 346 | "Transform tsv PACKAGE-STRINGS to plists." 347 | (cl-map #'list #'guix-packaging--tsv-to-plist package-strings)) 348 | 349 | (defun guix-packaging--pairs-to-plist (pairs &optional plist) 350 | "Add record PAIRS to PLIST." 351 | (cl-reduce (lambda (plist pair) 352 | "Add PAIR to PLIST." 353 | (plist-put plist 354 | (intern (concat ":" (cl-first pair))) 355 | (cl-second pair))) 356 | pairs 357 | :initial-value plist)) 358 | 359 | (defun guix-packaging--rec-to-plist (rec-string) 360 | "Transfrom a REC-STRING for a package into a plist." 361 | (let* ((reduced-string (replace-regexp-in-string (rx "\n" "+") " " rec-string)) 362 | (fields (thread-last (split-string reduced-string "\n" t) 363 | (cl-map #'list (-rpartial #'split-string ": ")) 364 | guix-packaging--pairs-to-plist)) 365 | (dependencies (split-string (plist-get fields :dependencies)))) 366 | (plist-put fields :dependencies dependencies))) 367 | 368 | (defun guix-packaging--defun-symbol () 369 | "Symbol that names the current defun." 370 | (save-excursion 371 | (beginning-of-thing 'defun) 372 | (goto-char (line-end-position)) 373 | (thing-at-point 'symbol t))) 374 | 375 | (defun guix-packaging--guile-symbols (&rest package-strings) 376 | "Hash of PACKAGE-STRINGS to their corresponding Guile symbols." 377 | (let* ((guix-packaging-guix-executable (concat "VISUAL=echo "guix-packaging-guix-executable)) 378 | (package-locations (thread-last (apply #'guix-packaging--invoke-guix "edit" package-strings) 379 | split-string 380 | (-partition 2)))) 381 | (cl-reduce 382 | (lambda (hash next) 383 | "Add Guile symbol NEXT data pair to HASH." 384 | (let* ((package-string (cl-first next)) 385 | (data-pair (cl-rest next)) 386 | (line (thread-first data-pair 387 | cl-first 388 | (string-trim "+") 389 | string-to-number)) 390 | (file-path (cl-second data-pair))) 391 | (guix-packaging--with-scheme-buffer 392 | (insert-file-contents file-path) 393 | (guix-packaging--goto-line line (current-buffer)) 394 | (puthash package-string (guix-packaging--defun-symbol) hash) 395 | hash))) 396 | (-zip package-strings package-locations) 397 | :initial-value (make-hash-table)))) 398 | 399 | (defun guix-packaging--disassemble-package () 400 | "Disassemble the package in the defun at point." 401 | (save-excursion 402 | (let ((defun-end (progn 403 | (end-of-defun) 404 | (1- (line-number-at-pos)))) 405 | (result `(:symbol ,(guix-packaging--defun-symbol) :symex ,(make-hash-table) :strategy (:parts nil)))) 406 | (beginning-of-defun) 407 | (search-forward "(package") 408 | (while (< (line-number-at-pos) defun-end) 409 | (forward-sexp) 410 | (let* ((body (thing-at-point 'sexp t)) 411 | (name (guix-packaging--with-scheme-buffer 412 | (insert body) 413 | (goto-char (point-min)) 414 | (forward-symbol 1) 415 | (thing-at-point 'symbol t))) 416 | (name-symbol (intern (format ":%s" name)))) 417 | (push name-symbol (plist-get (plist-get result :strategy) :parts)) 418 | (puthash name-symbol body (plist-get result :symex)))) 419 | (cl-callf reverse (plist-get (plist-get result :strategy) :parts)) 420 | result))) 421 | 422 | (defun guix-packaging--rename-section (section name) 423 | "Rename SECTION by replacing its first symbol with NAME. 424 | If NAME is nil, don't do anything." 425 | (if (null name) 426 | section 427 | (guix-packaging--with-scheme-buffer 428 | (insert section) 429 | (goto-char (point-min)) 430 | (forward-symbol 1) 431 | (cl-destructuring-bind (start . end) (bounds-of-thing-at-point 'symbol) 432 | (goto-char start) 433 | (delete-char (- end start))) 434 | (insert name) 435 | (buffer-string)))) 436 | 437 | (defun guix-packaging--alias-reverse (aliases) 438 | "Transform ALIASES such that they represent the reverse substitutions. 439 | For example, '(:greeting \"hello\" :name \"world\") 440 | becomes: '(:hello \"greeting\" :world \"name\")" 441 | (cl-loop for (key val) on aliases by #'cddr 442 | collect (intern (format ":%s" val)) 443 | collect (string-trim (format "%s" key) ":" ""))) 444 | 445 | (defun guix-packaging--assemble-package (name symex strategy) 446 | "Assemble a package definition from NAME and SYMEX. 447 | If STRATEGY is a plist with :sections corresponding to a list of 448 | symbols, sections of the package will appear in the specififed 449 | order." 450 | (let* ((all-keys (hash-table-keys symex)) 451 | (part-keys (plist-get strategy :parts)) 452 | (strategized-keys (-intersection part-keys all-keys)) 453 | (unknown-keys (-difference part-keys strategized-keys)) 454 | (other-keys (-difference all-keys part-keys)) 455 | (aliases (plist-get strategy :aliases)) 456 | (sections (cl-map 457 | #'list 458 | (lambda (part) 459 | "Fetch PART from symex hash and maybe rename it." 460 | (thread-first part (gethash symex) (guix-packaging--rename-section (plist-get aliases part)))) 461 | (append strategized-keys unknown-keys other-keys)))) 462 | (guix-packaging--with-scheme-buffer 463 | (insert (format "(define-public %s\n (package\n %s))" 464 | name 465 | (mapconcat #'identity sections "\n "))) 466 | (buffer-string)))) 467 | 468 | 469 | 470 | (defun guix-packaging--persist-strategies () 471 | "Persist guix-packaging--strategies to disk." 472 | (with-temp-buffer 473 | (prin1 guix-packaging--strategies (current-buffer)) 474 | (write-file (concat guix-packaging--data-dir "/strategies.hash")))) 475 | 476 | (cl-defun guix-packaging--remember-strategy (package &optional (tag "default")) 477 | "Save the strategy for PACKAGE, tagging it with TAG." 478 | (let* ((symbol (plist-get package :symbol)) 479 | (strategy (plist-get package :strategy))) 480 | (puthash (vector symbol tag) strategy guix-packaging--strategies))) 481 | 482 | (cl-defun guix-packaging--interactive-tag (&key (default "default")) 483 | "Prompt the user for a tag if CURRENT-PREFIX-ARG is non-nil. 484 | Otherwise provide DEFAULT. Return tag as a symbol." 485 | (if (consp current-prefix-arg) 486 | (thread-last "Tag for lens: " 487 | read-from-minibuffer 488 | list) 489 | (list default))) 490 | 491 | ;;;###autoload 492 | (defun guix-packaging-init-states () 493 | "Remember structural state for packages in the current buffer." 494 | (interactive (guix-packaging--interactive-tag :default "origin")) 495 | (let ((package (guix-packaging--disassemble-package))) 496 | (guix-packaging--remember-strategy package) 497 | (when guix-packaging--save-after-remember (guix-packaging--persist-strategies)) 498 | (cl-destructuring-bind (begin . end) (bounds-of-thing-at-point 'defun) 499 | (guix-packaging--pulse-region begin end)) 500 | (message "Initialized transformation of %s." (plist-get package :symbol)))) 501 | 502 | ;;;###autoload 503 | (defun guix-packaging-tag-state (tag) 504 | "Tag the structural state of the package at point." 505 | (interactive "MTag: ") 506 | (message "Adding as tag: %s" tag)) 507 | 508 | (cl-defun guix-packaging--get-strategy (symbol &optional (tag "default")) 509 | "Retrieve the remembered strategy for SYMBOL tagged with TAG, if any." 510 | (gethash (vector symbol tag) guix-packaging--strategies)) 511 | 512 | (cl-defun guix-packaging--list-available (&optional (search-regex "")) 513 | "Available packages in Guix matching SEARCH-REGEX, in a plist." 514 | (thread-first (guix-packaging--invoke-guix "package" "-A" search-regex) 515 | (split-string "\n" t) 516 | guix-packaging--map-tsv-to-plist)) 517 | 518 | ;;;###autoload 519 | (defun guix-packaging-refresh-packages () 520 | "Refresh the list of available Guix packages." 521 | (interactive) 522 | (setq guix-packaging--all-guix-packages 523 | (guix-packaging--list-available))) 524 | 525 | (defun guix-packaging--package (name &optional extra) 526 | "Info about the package NAME in a plist. 527 | If EXTRA is non-nil, fetch extra package info using `guix 528 | search'." 529 | (if extra 530 | (guix-packaging--rec-to-plist (guix-packaging--invoke-guix "show" name)) 531 | (seq-find (-rpartial #'plist-get :name) guix-packaging--all-guix-packages))) 532 | 533 | (defun guix-packaging--format-inputs (&rest package-strings) 534 | "Format PACKAGE-STRINGS as Guix package inputs." 535 | (let ((guile-symbols (apply #'guix-packaging--guile-symbols package-strings))) 536 | (cl-map #'list 537 | (lambda (package-string) 538 | "Format PACKAGE-STRING as a Guix package input." 539 | (format "(\"%s\" ,%s)" 540 | package-string 541 | (gethash package-string guile-symbols))) 542 | package-strings))) 543 | 544 | (defun guix-packaging--make-package-string (package) 545 | "The package-string (name@version) for PACKAGE." 546 | (format "%s@%s" 547 | (plist-get package :name) 548 | (plist-get package :version))) 549 | 550 | ;;;###autoload 551 | (defun guix-packaging-insert-input (&rest package-strings) 552 | "Insert the corresponding Guix package inputs for PACKAGE-STRINGS. 553 | eg. for ruby@2.7.2 insert (\"ruby@2.7.2\" ,ruby-2.7)." 554 | (interactive 555 | (completing-read-multiple 556 | "Search packages: " 557 | (cl-map #'list #'guix-packaging--make-package-string 558 | (or guix-packaging--all-guix-packages 559 | (guix-packaging-refresh-packages))))) 560 | (dolist (input (apply #'guix-packaging--format-inputs package-strings)) 561 | (insert input) 562 | (newline-and-indent))) 563 | 564 | ;;;###autoload 565 | (cl-defun guix-packaging-go-mod-to-checkbox (&optional (depth 1)) 566 | "Convert a go module requirement to a checkbox. 567 | Prepend 2 times DEPTH spaces, make a list item with a checkbox, 568 | and use the go module requirement as the label." 569 | (interactive "p") 570 | (save-excursion 571 | (goto-char (line-beginning-position)) 572 | (insert (thread-first depth 573 | (* 2) 574 | (make-string ? ))) 575 | (insert "- [ ]") 576 | (org-cycle-list-bullet (mod depth 3)) 577 | (fixup-whitespace) 578 | (forward-char) 579 | (search-forward " ") 580 | (delete-char -1) 581 | (delete-char 1) 582 | (insert "@"))) 583 | 584 | (defun guix-packaging--go-mod-region-to-checkboxes (&optional depth) 585 | "Convert the region from go module requirements a checklist. 586 | Prepend 2 times DEPTH spaces before each list element." 587 | (guix-packaging--do-on-each-line 588 | (lambda () 589 | "Convert to checkbox with given DEPTH and BUFFER." 590 | (guix-packaging-go-mod-to-checkbox depth)))) 591 | 592 | (defun guix-packaging--mark-go-mod () 593 | "Mark go module requirements. 594 | Use the current region or look around point in the current 595 | buffer. Use `guix-packaging-go-mod-pattern' to identify target 596 | lines." 597 | (let ((start (line-number-at-pos (if (use-region-p) 598 | (region-beginning) 599 | (point)))) 600 | (region-min-line-number nil) 601 | (in-mod-region (save-mark-and-excursion 602 | (beginning-of-line) 603 | (looking-at-p guix-packaging-go-mod-pattern))) 604 | (max-point (buffer-size))) 605 | (when in-mod-region 606 | (beginning-of-line) 607 | (while (and (not (eq (point) 1)) 608 | (looking-at-p guix-packaging-go-mod-pattern)) 609 | (forward-line -1)) 610 | (when (not (looking-at-p guix-packaging-go-mod-pattern)) 611 | (forward-line)) 612 | (setq region-min-line-number (line-number-at-pos)) 613 | (push-mark (point) t t) 614 | (guix-packaging--goto-line start) 615 | (while (and (not (eq (point) max-point)) 616 | (looking-at-p guix-packaging-go-mod-pattern)) 617 | (forward-line)) 618 | (when (not (looking-at-p guix-packaging-go-mod-pattern)) 619 | (forward-line -1)) 620 | (end-of-line) 621 | `(,region-min-line-number ,(line-number-at-pos))))) 622 | 623 | ;;;###autoload 624 | (cl-defun guix-packaging-go-mod-to-checklist-dwim (&optional depth (buffer (current-buffer))) 625 | "Convert the region of go module requirements to a checklist. 626 | Prepend 2 times DEPTH spaces before each list item. Use the 627 | region from BUFFER, or if no region is selected, widen to the go 628 | mod block at point." 629 | (interactive "p") 630 | (if (use-region-p) 631 | (guix-packaging--go-mod-region-to-checkboxes depth) 632 | (cl-destructuring-bind (&optional start end) 633 | (guix-packaging--mark-go-mod) 634 | (when (and start end) 635 | (with-current-buffer buffer 636 | (save-mark-and-excursion 637 | (guix-packaging--go-mod-region-to-checkboxes depth) 638 | (guix-packaging--goto-line (- start 1)) 639 | (beginning-of-line) 640 | (when (looking-at-p guix-packaging-go-mod-start-pattern) 641 | (delete-region (point) 642 | (1+ (line-end-position)))) 643 | (guix-packaging--goto-line (1+ end)) 644 | (beginning-of-line) 645 | (when (looking-at-p guix-packaging-go-mod-end-pattern) 646 | (delete-region (point) 647 | (1+ (min (buffer-size) 648 | (line-end-position))))))))))) 649 | 650 | (defun guix-packaging--tmp-repo-dir (repo-url) 651 | "The name of a temporary directory for REPO-URL." 652 | (format "/tmp/%s" (guix-packaging--make-slug repo-url))) 653 | 654 | (defun guix-packaging--git-clone-tmp (repo-url &optional branch) 655 | "Clone the git repository with the provided REPO-URL and BRANCH to a temporary directory." 656 | (let* ((shell-command-dont-erase-buffer t) 657 | (branch-options (when branch 658 | (format "--branch \"%s\" " branch))) 659 | (dest (guix-packaging--tmp-repo-dir repo-url)) 660 | (cmd (format "git clone -q --depth=1 %s%s %s" 661 | branch-options 662 | repo-url 663 | dest))) 664 | (when (file-directory-p dest) 665 | (guix-packaging--message (format "Removing existing %s" dest)) 666 | (delete-directory dest t)) 667 | (guix-packaging--message cmd :prefix "$ ") 668 | (shell-command cmd guix-packaging-output-buffer 669 | guix-packaging-error-buffer))) 670 | 671 | (defun guix-packaging--url-read-my-mind () 672 | "The URL around point, or the first URL of the defun at point, if any." 673 | (save-excursion 674 | (or (thing-at-point 'url) 675 | (-when-let* ((fn (bounds-of-thing-at-point 'defun)) 676 | (beginning (car fn)) 677 | (end (cdr fn))) 678 | (goto-char beginning) 679 | (search-forward-regexp 680 | (rx "(url" (* space) "\"") 681 | end) 682 | (thing-at-point 'url)) 683 | (progn 684 | (search-forward-regexp goto-address-url-regexp) 685 | (thing-at-point 'url))))) 686 | 687 | ;;;###autoload 688 | (defun guix-packaging-hash-git (&optional repo-url branch) 689 | "Save the hash of the git repository at REPO-URL to the kill ring. 690 | If BRANCH provided, git uses that branch (or tag.)" 691 | (interactive 692 | (let* ((default (guix-packaging--url-read-my-mind)) 693 | (repo-url (read-string (concat "Repository URL" 694 | (when default 695 | (concat " (default " default ")")) 696 | ": ") 697 | nil nil default)) 698 | (branch (read-string "Branch (default master): " 699 | nil nil "master"))) 700 | (list repo-url branch))) 701 | (if (zerop (guix-packaging--git-clone-tmp repo-url branch)) 702 | (thread-last repo-url 703 | guix-packaging--tmp-repo-dir 704 | (guix-packaging--invoke-guix "hash" "-rx") 705 | string-trim-right 706 | kill-new 707 | message) 708 | (when (called-interactively-p 'interactive) 709 | (message "Couldn't hash %s at branch %s. See %s for info." 710 | (propertize repo-url 'face 'link) 711 | branch 712 | (propertize guix-packaging-error-buffer 'face 713 | 'error))) 714 | nil)) 715 | 716 | 717 | 718 | ;;;###autoload 719 | (defun guix-packaging--snippets-initialize () 720 | "Initialize yasnippet to use the guix-packaging snippets." 721 | (let ((snip-dir (expand-file-name "snippets" guix-packaging--snippets-root))) 722 | (when (boundp #'yas-snippet-dirs) 723 | (add-to-list 'yas-snippet-dirs snip-dir t)) 724 | (yas-load-directory snip-dir))) 725 | 726 | ;;;###autoload 727 | (with-eval-after-load "yasnippet" 728 | (guix-packaging--snippets-initialize)) 729 | 730 | (provide 'guix-packaging) 731 | 732 | 733 | 734 | ;;; guix-packaging.el ends here 735 | -------------------------------------------------------------------------------- /snippets/markdown-mode/.yas-parents: -------------------------------------------------------------------------------- 1 | sgml-mode 2 | -------------------------------------------------------------------------------- /snippets/org-mode/.yas-parents: -------------------------------------------------------------------------------- 1 | sgml-mode -------------------------------------------------------------------------------- /snippets/scheme-mode/guix-gnu-package: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # contributor: Ryan Prior 3 | # name: guix-gnu-package 4 | # key: guix-gnu 5 | # group: guix 6 | # -- 7 | (define-public $1 8 | (package 9 | (name "$1") 10 | (version "$2") 11 | (source 12 | (origin 13 | (method git-fetch) 14 | (uri (git-reference 15 | (url "$3") 16 | (commit version))) 17 | (file-name (git-file-name name version)) 18 | (sha256 19 | (base32 "${4:$$(guix-packaging--latch yas-text (guix-packaging-hash-git (yas-field-value 3) (yas-field-value 2)))}")))) 20 | (build-system gnu-build-system) 21 | (home-page "${5:$$(guix-packaging--latch yas-text (yas-field-value 3))}") 22 | (synopsis "$6") 23 | (description 24 | "$7") 25 | (license license:$8))) 26 | -------------------------------------------------------------------------------- /snippets/scheme-mode/guix-go-package: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # contributor: Ryan Prior 3 | # name: guix-go-package 4 | # key: guix-go 5 | # group: guix 6 | # -- 7 | (define-public go-${2:$$(-> yas-text (guix-packaging--latch (yas-field-value 1)) guix-packaging--make-slug)} 8 | (package 9 | (name "go-$2") 10 | (version "$3") 11 | (source 12 | (origin 13 | (method git-fetch) 14 | (uri (git-reference 15 | (url "${4:$$(guix-packaging--latch yas-text (format "https://%s" (yas-field-value 1)))}") 16 | (commit (string-append "v" version)))) 17 | (file-name (git-file-name name version)) 18 | (sha256 19 | (base32 "${5:$$(guix-packaging--latch yas-text (guix-packaging-hash-git (yas-field-value 4) (concat "v" (yas-field-value 3))))}")))) 20 | (build-system go-build-system) 21 | (arguments 22 | '(#:import-path "$1")) 23 | (home-page "${6:$$(guix-packaging--latch yas-text (yas-field-value 4))}") 24 | (synopsis "$7") 25 | (description 26 | "$8") 27 | (license license:${9:expat}))) 28 | -------------------------------------------------------------------------------- /snippets/scheme-mode/guix-go-package-no-version: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # contributor: Ryan Prior 3 | # name: guix-go-package-no-version 4 | # key: guix-go-noversion 5 | # group: guix 6 | # -- 7 | (define-public go-${2:$$(-> yas-text (guix-packaging--latch (yas-field-value 1)) guix-packaging--make-slug)} 8 | (let ((commit "$3") 9 | (revision "0")) 10 | (package 11 | (name "go-$2") 12 | (version (git-version "0.0.0" revision commit)) 13 | (source 14 | (origin 15 | (method git-fetch) 16 | (uri (git-reference 17 | (url "https://${4:$$(guix-packaging--latch yas-text (yas-field-value 1))}") 18 | (commit commit))) 19 | (file-name (git-file-name name version)) 20 | (sha256 21 | (base32 22 | "$5")))) 23 | (build-system go-build-system) 24 | (arguments 25 | '(#:import-path "$1")) 26 | (home-page "https://${6:$$(guix-packaging--latch yas-text (yas-field-value 1))}") 27 | (synopsis "$7") 28 | (description 29 | "$8") 30 | (license license:${9:expat})))) 31 | -------------------------------------------------------------------------------- /snippets/scheme-mode/guix-node-package: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # contributor: Ryan Prior 3 | # name: guix-node-package 4 | # key: guix-node 5 | # group: guix 6 | # -- 7 | (define-public node-${2:$$(-> yas-text (guix-packaging--latch (yas-field-value 1)) guix-packaging--make-slug)} 8 | (package 9 | (name "node-$2") 10 | (version "$3") 11 | (source 12 | (origin 13 | (method git-fetch) 14 | (uri (git-reference 15 | (url "https://$1") 16 | (commit (string-append "v" version)))) 17 | (file-name (git-file-name name version)) 18 | (sha256 19 | (base32 "${5:$$(guix-packaging--latch yas-text (guix-packaging-hash-git (string-append "https://" (yas-field-value 1)) (concat "v" (yas-field-value 3))))}")))) 20 | (build-system node-build-system) 21 | (home-page "${6:$$(guix-packaging--latch yas-text (yas-field-value 4))}") 22 | (synopsis "$7") 23 | (description 24 | "$8") 25 | (license license:${9:expat}))) 26 | -------------------------------------------------------------------------------- /snippets/sgml-mode/repology-badge: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # contributor: Ryan Prior 3 | # name: guix-repology-badge 4 | # key: guix-badge 5 | # group: guix 6 | # -- 7 | GNU Guix package for $1 -------------------------------------------------------------------------------- /snippets/text-mode/guix-issue-url: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # name: Guix Issue Link 3 | # key: guix-issue-url 4 | # -- 5 | https://issues.guix.gnu.org/$1 -------------------------------------------------------------------------------- /snippets/web-mode/.yas-parents: -------------------------------------------------------------------------------- 1 | sgml-mode --------------------------------------------------------------------------------