* [PATCH] Provide a uniform way to inform users about missing third-party packages
@ 2023-01-23 15:19 Ihor Radchenko
2023-02-17 14:12 ` Ihor Radchenko
0 siblings, 1 reply; 2+ messages in thread
From: Ihor Radchenko @ 2023-01-23 15:19 UTC (permalink / raw)
To: emacs-orgmode
[-- Attachment #1: Type: text/plain, Size: 1079 bytes --]
Hi,
In Org code, we have various places where we depend (optionally) on
third-party packages. The packages are typically loaded using `require'
and report the usual error about missing library if the third-party
package is not installed.
Such situation is _almost_ OK, except some scenarios when the feature
name we in `require' is not the same with the actual package name.
Inexperienced users may easily be confused by such errors.
In the attached patch, I am introducing a new macro that can be used
instead of `require' as the following:
(org-require-package 'skewer-repl "skewer-mode")
The macro calls `require' and if it fails, displays error/warning saying
"`this-command' failed to load required package \"package name\""
It will assist users about the actual third-party package name to be
installed.
WDYT?
While writing a patch, I also noticed
`org-cite-csl--barf-without-citeproc' doing similar job.
Should we just use the new macro instead?
Also, https://github.com/bard/mozrepl ob-js backend is no longer
supported. I am wondering if we should remove it.
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Provide-a-uniform-way-to-inform-users-about-missing-.patch --]
[-- Type: text/x-patch, Size: 18844 bytes --]
From 0140976d32f6fef3c2e2fa181f210dbd026008fe Mon Sep 17 00:00:00 2001
Message-Id: <0140976d32f6fef3c2e2fa181f210dbd026008fe.1674486794.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Mon, 23 Jan 2023 18:06:46 +0300
Subject: [PATCH] Provide a uniform way to inform users about missing
third-party packages
* lisp/org-macs.el (org-require-package): New macro trying to load a
library and displaying custom error message or warning on failure.
The actual package (not library) name can be provided as optional
argument.
* lisp/ob-R.el (org-babel-R-initiate-session):
* lisp/ob-clojure.el (ob-clojure-eval-with-inf-clojure):
(ob-clojure-eval-with-cider):
(ob-clojure-eval-with-slime):
* lisp/ob-forth.el (org-babel-forth-session-execute):
* lisp/ob-gnuplot.el (org-babel-execute:gnuplot):
(org-babel-gnuplot-initiate-session):
* lisp/ob-haskell.el (org-babel-interpret-haskell):
(org-babel-haskell-initiate-session):
* lisp/ob-js.el (org-babel-execute:js):
(org-babel-js-initiate-session):
* lisp/ob-julia.el (org-babel-julia-initiate-session):
* lisp/ob-lisp.el (org-babel-execute:lisp):
* lisp/ob-ocaml.el (org-babel-prep-session:ocaml):
* lisp/ob-octave.el (org-babel-octave-initiate-session):
* lisp/ob-processing.el (org-babel-processing-view-sketch):
* lisp/ob-ruby.el (org-babel-execute:ruby):
(org-babel-ruby-initiate-session):
* lisp/ol-bbdb.el (org-bbdb-open):
(org-bbdb-complete-link):
(org-bbdb-anniv-export-ical):
* lisp/org-agenda.el:
* lisp/org-plot.el (org-plot/gnuplot):
* lisp/org.el:
* lisp/ox-ascii.el (org-ascii-table):
* lisp/ox-html.el (org-html-htmlize-generate-css):
* lisp/ox-org.el: Use the new macro.
* lisp/oc-csl.el: Add FIXME. oc-csl uses a custom function doing
similar job.
* lisp/ob-js.el (org-babel-js-initiate-session): Add FIXME noting that
the third-party package is outdated.
---
lisp/ob-R.el | 2 +-
lisp/ob-clojure.el | 9 +++------
lisp/ob-forth.el | 2 +-
lisp/ob-gnuplot.el | 4 ++--
lisp/ob-haskell.el | 4 ++--
lisp/ob-js.el | 10 ++++++----
lisp/ob-julia.el | 3 ++-
lisp/ob-lisp.el | 6 +++---
lisp/ob-ocaml.el | 2 +-
lisp/ob-octave.el | 6 ++++--
lisp/ob-processing.el | 2 +-
lisp/ob-ruby.el | 4 ++--
lisp/oc-csl.el | 1 +
lisp/ol-bbdb.el | 8 ++++----
lisp/org-agenda.el | 3 +--
lisp/org-macs.el | 9 +++++++++
lisp/org-plot.el | 2 +-
lisp/org.el | 2 +-
lisp/ox-ascii.el | 2 +-
lisp/ox-html.el | 3 +--
lisp/ox-org.el | 3 +--
21 files changed, 48 insertions(+), 39 deletions(-)
diff --git a/lisp/ob-R.el b/lisp/ob-R.el
index 4ee091118..2d22a4657 100644
--- a/lisp/ob-R.el
+++ b/lisp/ob-R.el
@@ -276,7 +276,7 @@ (defun org-babel-R-initiate-session (session params)
(when (get-buffer session)
;; Session buffer exists, but with dead process
(set-buffer session))
- (require 'ess) (R)
+ (org-require-package 'ess "ESS") (R)
(let ((R-proc (get-process (or ess-local-process-name
ess-current-process-name))))
(while (process-get R-proc 'callbacks)
diff --git a/lisp/ob-clojure.el b/lisp/ob-clojure.el
index d993e0cb7..5f589db00 100644
--- a/lisp/ob-clojure.el
+++ b/lisp/ob-clojure.el
@@ -186,8 +186,7 @@ (defvar comint-prompt-regexp)
(defvar inf-clojure-comint-prompt-regexp)
(defun ob-clojure-eval-with-inf-clojure (expanded params)
"Evaluate EXPANDED code block with PARAMS using inf-clojure."
- (condition-case nil (require 'inf-clojure)
- (user-error "inf-clojure not available"))
+ (org-require-package 'inf-clojure)
;; Maybe initiate the inf-clojure session
(unless (and inf-clojure-buffer
(buffer-live-p (get-buffer inf-clojure-buffer)))
@@ -228,8 +227,7 @@ (defun ob-clojure-eval-with-inf-clojure (expanded params)
(defun ob-clojure-eval-with-cider (expanded params)
"Evaluate EXPANDED code block with PARAMS using cider."
- (condition-case nil (require 'cider)
- (user-error "cider not available"))
+ (org-require-package 'cider "Cider")
(let ((connection (cider-current-connection (cdr (assq :target params))))
(result-params (cdr (assq :result-params params)))
result0)
@@ -256,8 +254,7 @@ (defun ob-clojure-eval-with-cider (expanded params)
(defun ob-clojure-eval-with-slime (expanded params)
"Evaluate EXPANDED code block with PARAMS using slime."
- (condition-case nil (require 'slime)
- (user-error "slime not available"))
+ (org-require-package 'slime "SLIME")
(with-temp-buffer
(insert expanded)
(slime-eval
diff --git a/lisp/ob-forth.el b/lisp/ob-forth.el
index e5dcad6d0..bd9ec04a4 100644
--- a/lisp/ob-forth.el
+++ b/lisp/ob-forth.el
@@ -55,7 +55,7 @@ (defun org-babel-execute:forth (body params)
(car (last all-results))))))
(defun org-babel-forth-session-execute (body params)
- (require 'forth-mode)
+ (org-require-package 'forth-mode)
(let ((proc (forth-proc))
(rx " \\(\n:\\|compiled\n\\|ok\n\\)")
(result-start))
diff --git a/lisp/ob-gnuplot.el b/lisp/ob-gnuplot.el
index e3e42918c..79770c7fd 100644
--- a/lisp/ob-gnuplot.el
+++ b/lisp/ob-gnuplot.el
@@ -198,7 +198,7 @@ (defun org-babel-expand-body:gnuplot (body params)
(defun org-babel-execute:gnuplot (body params)
"Execute a block of Gnuplot code.
This function is called by `org-babel-execute-src-block'."
- (require 'gnuplot)
+ (org-require-package 'gnuplot)
(let ((session (cdr (assq :session params)))
(result-type (cdr (assq :results params)))
(body (org-babel-expand-body:gnuplot body params))
@@ -262,7 +262,7 @@ (defun org-babel-gnuplot-initiate-session (&optional session _params)
If there is not a current inferior-process-buffer in SESSION
then create one. Return the initialized session. The current
`gnuplot-mode' doesn't provide support for multiple sessions."
- (require 'gnuplot)
+ (org-require-package 'gnuplot)
(unless (string= session "none")
(save-window-excursion
(gnuplot-send-string-to-gnuplot "" "line")
diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el
index 7185ed61f..2b1441c2a 100644
--- a/lisp/ob-haskell.el
+++ b/lisp/ob-haskell.el
@@ -122,7 +122,7 @@ (defun org-babel-haskell-execute (body params)
(cdr (assq :rowname-names params)) (cdr (assq :rownames params))))))))
(defun org-babel-interpret-haskell (body params)
- (require 'inf-haskell)
+ (org-require-package 'inf-haskell "haskell-mode")
(add-hook 'inferior-haskell-hook
(lambda ()
(setq-local comint-prompt-regexp
@@ -167,7 +167,7 @@ (defun org-babel-haskell-initiate-session (&optional _session _params)
"Initiate a haskell session.
If there is not a current inferior-process-buffer in SESSION
then create one. Return the initialized session."
- (require 'inf-haskell)
+ (org-require-package 'inf-haskell "haskell-mode")
(or (get-buffer "*haskell*")
(save-window-excursion (run-haskell) (sleep-for 0.25) (current-buffer))))
diff --git a/lisp/ob-js.el b/lisp/ob-js.el
index 910c11686..e19a6e543 100644
--- a/lisp/ob-js.el
+++ b/lisp/ob-js.el
@@ -96,7 +96,7 @@ (defun org-babel-execute:js (body params)
;; Indium Node REPL. Separate case because Indium
;; REPL is not inherited from Comint mode.
((string= session "*JS REPL*")
- (require 'indium-repl)
+ (org-require-package 'indium-repl "indium")
(unless (get-buffer session)
(indium-run-node org-babel-js-cmd))
(indium-eval full-body))
@@ -168,7 +168,7 @@ (defun org-babel-js-initiate-session (&optional session _params)
((string= session "none")
(warn "Session evaluation of ob-js is not supported"))
((string= "*skewer-repl*" session)
- (require 'skewer-repl)
+ (org-require-package 'skewer-repl "skewer-mode")
(let ((session-buffer (get-buffer "*skewer-repl*")))
(if (and session-buffer
(org-babel-comint-buffer-livep (get-buffer session-buffer))
@@ -180,7 +180,7 @@ (defun org-babel-js-initiate-session (&optional session _params)
(skewer-repl)
session-buffer)))
((string= "*Javascript REPL*" session)
- (require 'js-comint)
+ (org-require-package 'js-comint)
(let ((session-buffer "*Javascript REPL*"))
(if (and (org-babel-comint-buffer-livep (get-buffer session-buffer))
(comint-check-proc session-buffer))
@@ -189,7 +189,9 @@ (defun org-babel-js-initiate-session (&optional session _params)
(sit-for .5)
session-buffer)))
((string= "mozrepl" org-babel-js-cmd)
- (require 'moz)
+ ;; FIXME: According to https://github.com/bard/mozrepl, this REPL
+ ;; is outdated and does not work for Firefox >54.
+ (org-require-package 'moz "mozrepl")
(let ((session-buffer (save-window-excursion
(run-mozilla nil)
(rename-buffer session)
diff --git a/lisp/ob-julia.el b/lisp/ob-julia.el
index cb5c7fa3b..495169da1 100644
--- a/lisp/ob-julia.el
+++ b/lisp/ob-julia.el
@@ -196,7 +196,8 @@ (defun org-babel-julia-initiate-session (session params)
(when (get-buffer session)
;; Session buffer exists, but with dead process
(set-buffer session))
- (require 'ess) (set-buffer (julia))
+ (org-require-package 'ess "ESS")
+ (set-buffer (julia))
(rename-buffer
(if (bufferp session)
(buffer-name session)
diff --git a/lisp/ob-lisp.el b/lisp/ob-lisp.el
index 048ef883c..03f23c82d 100644
--- a/lisp/ob-lisp.el
+++ b/lisp/ob-lisp.el
@@ -90,9 +90,9 @@ (defun org-babel-execute:lisp (body params)
"Execute a block of Common Lisp code with Babel.
BODY is the contents of the block, as a string. PARAMS is
a property list containing the parameters of the block."
- (require (pcase org-babel-lisp-eval-fn
- (`slime-eval 'slime)
- (`sly-eval 'sly)))
+ (pcase org-babel-lisp-eval-fn
+ (`slime-eval (org-require-package 'slime "SLIME"))
+ (`sly-eval (org-require-package 'sly "SLY")))
(org-babel-reassemble-table
(let ((result
(funcall (if (member "output" (cdr (assq :result-params params)))
diff --git a/lisp/ob-ocaml.el b/lisp/ob-ocaml.el
index 09224b98b..4ab58e150 100644
--- a/lisp/ob-ocaml.el
+++ b/lisp/ob-ocaml.el
@@ -109,7 +109,7 @@ (defun org-babel-execute:ocaml (body params)
(defvar tuareg-interactive-buffer-name)
(defun org-babel-prep-session:ocaml (session _params)
"Prepare SESSION according to the header arguments in PARAMS."
- (require 'tuareg)
+ (org-require-package 'tuareg)
(let ((tuareg-interactive-buffer-name (if (and (not (string= session "none"))
(not (string= session "default"))
(stringp session))
diff --git a/lisp/ob-octave.el b/lisp/ob-octave.el
index 9bf16b984..64eb2a32a 100644
--- a/lisp/ob-octave.el
+++ b/lisp/ob-octave.el
@@ -154,8 +154,10 @@ (defun org-babel-octave-initiate-session (&optional session _params matlabp)
"Create an octave inferior process buffer.
If there is not a current inferior-process-buffer in SESSION then
create. Return the initialized session."
- (if matlabp (require 'matlab) (or (require 'octave-inf nil 'noerror)
- (require 'octave)))
+ (if matlabp
+ (org-require-package 'matlab "matlab-mode")
+ (or (require 'octave-inf nil 'noerror)
+ (require 'octave)))
(unless (string= session "none")
(let ((session (or session
(if matlabp "*Inferior Matlab*" "*Inferior Octave*"))))
diff --git a/lisp/ob-processing.el b/lisp/ob-processing.el
index 4eeaf98e0..460e6e381 100644
--- a/lisp/ob-processing.el
+++ b/lisp/ob-processing.el
@@ -78,7 +78,7 @@ (defvar org-babel-processing-processing-js-filename "processing.js"
(defun org-babel-processing-view-sketch ()
"Show the sketch of the Processing block under point in an external viewer."
(interactive)
- (require 'processing-mode)
+ (org-require-package 'processing-mode)
(let ((info (org-babel-get-src-block-info)))
(if (string= (nth 0 info) "processing")
(let* ((body (nth 1 info))
diff --git a/lisp/ob-ruby.el b/lisp/ob-ruby.el
index b94bc73dd..ba8697731 100644
--- a/lisp/ob-ruby.el
+++ b/lisp/ob-ruby.el
@@ -86,7 +86,7 @@ (defun org-babel-execute:ruby (body params)
body params (org-babel-variable-assignments:ruby params)))
(result (if (member "xmp" result-params)
(with-temp-buffer
- (require 'rcodetools)
+ (org-require-package 'rcodetools "rcodetools (gem package)")
(insert full-body)
(xmp (cdr (assq :xmp-option params)))
(buffer-string))
@@ -161,7 +161,7 @@ (defun org-babel-ruby-initiate-session (&optional session params)
If there is not a current inferior-process-buffer in SESSION
then create one. Return the initialized session."
(unless (string= session "none")
- (require 'inf-ruby)
+ (org-require-package 'inf-ruby)
(let* ((command (cdr (or (assq :ruby params)
(assoc inf-ruby-default-implementation
inf-ruby-implementations))))
diff --git a/lisp/oc-csl.el b/lisp/oc-csl.el
index 432738a97..94c2ed94c 100644
--- a/lisp/oc-csl.el
+++ b/lisp/oc-csl.el
@@ -417,6 +417,7 @@ (defconst org-cite-csl--label-regexp
\f
;;; Internal functions
+;; FIXME: We use `org-require-package' in other places.
(defun org-cite-csl--barf-without-citeproc ()
"Raise an error if Citeproc library is not loaded."
(unless (featurep 'citeproc)
diff --git a/lisp/ol-bbdb.el b/lisp/ol-bbdb.el
index 47bd9d98c..915d83df2 100644
--- a/lisp/ol-bbdb.el
+++ b/lisp/ol-bbdb.el
@@ -255,7 +255,7 @@ (defun org-bbdb-export (path desc format _)
(defun org-bbdb-open (name _)
"Follow a BBDB link to NAME."
- (require 'bbdb-com)
+ (org-require-package 'bbdb-com "bbdb")
(let ((inhibit-redisplay (not debug-on-error)))
(if (fboundp 'bbdb-name)
(org-bbdb-open-old name)
@@ -369,7 +369,7 @@ (defun org-bbdb-anniversaries ()
"Extract anniversaries from BBDB for display in the agenda.
When called programmatically, this function expects the `date'
variable to be globally bound."
- (require 'bbdb)
+ (org-require-package 'bbdb)
(require 'diary-lib)
(unless (hash-table-p org-bbdb-anniv-hash)
(setq org-bbdb-anniv-hash
@@ -500,7 +500,7 @@ (defun org-bbdb-anniversaries-future (&optional n)
(defun org-bbdb-complete-link ()
"Read a bbdb link with name completion."
- (require 'bbdb-com)
+ (org-require-package 'bbdb-com "bbdb")
(let ((rec (bbdb-completing-read-record "Name: ")))
(concat "bbdb:"
(bbdb-record-name (if (listp rec)
@@ -509,7 +509,7 @@ (defun org-bbdb-complete-link ()
(defun org-bbdb-anniv-export-ical ()
"Extract anniversaries from BBDB and convert them to icalendar format."
- (require 'bbdb)
+ (org-require-package 'bbdb)
(require 'diary-lib)
(unless (hash-table-p org-bbdb-anniv-hash)
(setq org-bbdb-anniv-hash
diff --git a/lisp/org-agenda.el b/lisp/org-agenda.el
index d2aa8f3f2..3f2b5dcc8 100644
--- a/lisp/org-agenda.el
+++ b/lisp/org-agenda.el
@@ -3627,8 +3627,7 @@ (defun org-agenda-write (file &optional open nosettings agenda-bufname)
(kill-buffer (current-buffer))
(message "Org file written to %s" file)))
((member extension '("html" "htm"))
- (or (require 'htmlize nil t)
- (error "Please install htmlize from https://github.com/hniksic/emacs-htmlize"))
+ (org-require-package 'htmlize)
(declare-function htmlize-buffer "htmlize" (&optional buffer))
(set-buffer (htmlize-buffer (current-buffer)))
(when org-agenda-export-html-style
diff --git a/lisp/org-macs.el b/lisp/org-macs.el
index cda9c5e03..9083b82f8 100644
--- a/lisp/org-macs.el
+++ b/lisp/org-macs.el
@@ -107,6 +107,15 @@ (defvar org-fold-core-style)
\f
;;; Macros
+(defmacro org-require-package (symbol &optional name noerror)
+ "Try to load library SYMBOL and display error otherwise.
+With optional parameter NAME, use NAME as package name instead of
+SYMBOL. Show warning instead of error when NOERROR is non-nil."
+ `(unless (require ,symbol nil t)
+ (,(if noerror 'warn 'user-error)
+ "`%s' failed to load required package \"%s\""
+ this-command ,(or name symbol))))
+
(defmacro org-with-gensyms (symbols &rest body)
(declare (debug (sexp body)) (indent 1))
`(let ,(mapcar (lambda (s)
diff --git a/lisp/org-plot.el b/lisp/org-plot.el
index fe61e9ace..d39ffc4b4 100644
--- a/lisp/org-plot.el
+++ b/lisp/org-plot.el
@@ -641,7 +641,7 @@ (defun org-plot/gnuplot (&optional params)
If not given options will be taken from the +PLOT
line directly before or after the table."
(interactive)
- (require 'gnuplot)
+ (org-require-package 'gnuplot)
(save-window-excursion
(delete-other-windows)
(when (get-buffer "*gnuplot*") ; reset *gnuplot* if it already running
diff --git a/lisp/org.el b/lisp/org.el
index 00674d1fc..958305382 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -15436,7 +15436,7 @@ (define-minor-mode org-cdlatex-mode
\\{org-cdlatex-mode-map}"
:lighter " OCDL"
(when org-cdlatex-mode
- (require 'cdlatex)
+ (org-require-package 'cdlatex)
(run-hooks 'cdlatex-mode-hook)
(cdlatex-compute-tables))
(unless org-cdlatex-texmathp-advice-is-done
diff --git a/lisp/ox-ascii.el b/lisp/ox-ascii.el
index 9c4424b14..e5bdf92cb 100644
--- a/lisp/ox-ascii.el
+++ b/lisp/ox-ascii.el
@@ -1856,7 +1856,7 @@ (defun org-ascii-table (table contents info)
(cond ((eq (org-element-property :type table) 'org) contents)
((and (plist-get info :ascii-table-use-ascii-art)
(eq (plist-get info :ascii-charset) 'utf-8)
- (require 'ascii-art-to-unicode nil t))
+ (org-require-package 'ascii-art-to-unicode nil 'noerror))
(with-temp-buffer
(insert (org-remove-indentation
(org-element-property :value table)))
diff --git a/lisp/ox-html.el b/lisp/ox-html.el
index 5e58ccba3..37c474409 100644
--- a/lisp/ox-html.el
+++ b/lisp/ox-html.el
@@ -1823,8 +1823,7 @@ (defun org-html-htmlize-generate-css ()
to the function `org-html-htmlize-region-for-paste' will
produce code that uses these same face definitions."
(interactive)
- (unless (require 'htmlize nil t)
- (error "htmlize library missing. Aborting"))
+ (org-require-package 'htmlize)
(and (get-buffer "*html*") (kill-buffer "*html*"))
(with-temp-buffer
(let ((fl (face-list))
diff --git a/lisp/ox-org.el b/lisp/ox-org.el
index ed72cf4f2..9329eb159 100644
--- a/lisp/ox-org.el
+++ b/lisp/ox-org.el
@@ -320,8 +320,7 @@ (defun org-org-publish-to-org (plist filename pub-dir)
Return output file name."
(org-publish-org-to 'org filename ".org" plist pub-dir)
(when (plist-get plist :htmlized-source)
- (or (require 'htmlize nil t)
- (error "Please install htmlize from https://github.com/hniksic/emacs-htmlize"))
+ (org-require-package 'htmlize)
(require 'ox-html)
(let* ((org-inhibit-startup t)
(htmlize-output-type 'css)
--
2.39.1
[-- Attachment #3: Type: text/plain, Size: 224 bytes --]
--
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH] Provide a uniform way to inform users about missing third-party packages
2023-01-23 15:19 [PATCH] Provide a uniform way to inform users about missing third-party packages Ihor Radchenko
@ 2023-02-17 14:12 ` Ihor Radchenko
0 siblings, 0 replies; 2+ messages in thread
From: Ihor Radchenko @ 2023-02-17 14:12 UTC (permalink / raw)
To: emacs-orgmode
Ihor Radchenko <yantar92@posteo.net> writes:
> Subject: [PATCH] Provide a uniform way to inform users about missing
> third-party packages
Applied, onto main.
https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=fe92a3ced
--
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2023-02-17 14:12 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2023-01-23 15:19 [PATCH] Provide a uniform way to inform users about missing third-party packages Ihor Radchenko
2023-02-17 14:12 ` Ihor Radchenko
Code repositories for project(s) associated with this external index
https://git.savannah.gnu.org/cgit/emacs.git
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.