;;; eev-tla.el --- eev links based on TLAs, i.e., three-letter acronyms.
;; Copyright (C) 2021 Free Software Foundation, Inc.
;;
;; This file is part of GNU eev.
;;
;; GNU eev 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 eev 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 Emacs. If not, see .
;;
;; Author: Eduardo Ochs
;; Maintainer: Eduardo Ochs
;; Version: 20210618
;; Keywords: e-scripts
;;
;; Latest version:
;; htmlized:
;; See also:
;;
;;
;; (find-eev-intro)
;; (find-here-links-intro)
;;; Commentary:
;; This is a new, experimental feature that is not loaded by default.
;; It is a better version of a tool that I've used for some years to
;; index both the sources and the PDFs of my LaTeX files.
;;
;; In 2018 I extended the idea of "very short names" to "three letter
;; acronyms" (TLAs), and I started to use TLAs to point to my .tex
;; files; for example, the `M-x lod' would open the file
;; ~/LATEX/2019logicday.tex, `M-x lodp' would display its PDF, and
;; these two sexps
;;
;; (lodp 6 "set-comprehensions")
;; (lod "set-comprehensions")
;;
;; would point to a) the page 6 of the PDF and b) to the anchor
;; "<>" in the .tex file... after each section
;; anchor in the .tex I had a pair of sexps like those two above, that
;; I could copy to other files to use as hyperlinks to both the output
;; and the source of that section. With this my LaTeXing life became
;; much better - because with this I had a very good way to point to
;; my LaTeX tricks!...
;;
;; In 2021 I sent an e-mail to Erich Ruff after seeing this e-mail
;; that he sent to the Hyperbole mailing list:
;;
;; https://lists.gnu.org/archive/html/hyperbole-users/2021-05/msg00042.html
;;
;; we exchanged ideas in a series of private e-mails, and I wrote this
;; prototype to see if it could be a convenient way to make his long
;; sexp hyperlinks shorter.
;;
;; This version supposes that we have a bijection between TLAs
;; (symbols) and file names (strings). In my other, older,
;; implementation I sometimes had two TLAs associated to the same file
;; name, like this:
;;
;; c2m202fp -> "~/LATEX/2020-2-C2-fracs-parcs.pdf"
;; c2fp -> "~/LATEX/2020-2-C2-fracs-parcs.pdf"
;;
;; note that both `c2fp' and `c2m202fp' are symbols longer than three
;; characters, but I'd refer to both of them as "TLA"s anyway.
;;
;; Here are the design principles that I used:
;;
;; 1. We may have hundreds of files that we want to create TLA-links
;; for. A "TLA" - a "three-letter acronym" may be longer than
;; three characters.
;;
;; 2. It should be easy to create a TLA, and a `code-tla', for a new
;; file.
;;
;; 3. Some of the files that we may want to access with TLAs may be
;; read-only in some sense - so it's not feasible to define the
;; TLAs associated to them in their "Local Variables:" sections,
;; because we do not want to change them to add local variables.
;; See:
;;
;; (find-enode "File Variables" ";;; Local Variables:")
;;
;; 4. Creating links to a file that has a TLA associated to it has
;; to be very fast. If we are in
;;
;; /mnt/fichte/fuchs_erich-fichte_im_gespraech_1_1762-1798.txt
;;
;; and we are after the anchor "<>", with the
;; region being the string "some text that is not an anchor",
;; then there must be a short sequence of keystrokes that
;; produces a temporary buffer containing these lines, among
;; other stuff:
;;
;; (fim1a "Fichte Charakter")
;; (fim1 "some text that is not an anchor")
;;
;; The conversion from the file name and the `fim1' is done using
;; a hash table.
;;
;; 5. If we create a TLA for a file whose name ends in .tex, like this,
;;
;; (code-tla "lod" "~/LATEX/2019logicday.tex")
;;
;; then the `code-tla' should also create functions `lodp' and
;; `lodt' such that
;;
;; (lodp 6 "set-comprehensions")
;; (loda "set-comprehensions")
;; (lodt "calculate")
;;
;; are equivalent to:
;;
;; (find-pdf-page "~/LATEX/2019logicday.tex" 6)
;; (find-anchor "~/LATEX/2019logicday.tex" "set-comprehensions")
;; (find-pdf-text "~/LATEX/2019logicday.tex" "calculate")
;; To try this, do:
;; (load "eev-tla.el")
;; and then use `M-3 M-j' to generate the temporary buffers,
;; and `M-3 M-3 M-j' to insert a {tla}p/{tla}a pair.
;; «.hash-table» (to "hash-table")
;; «.code-tla» (to "code-tla")
;; «.find-tla-def-links» (to "find-tla-def-links")
;; «.find-tla-links» (to "find-tla-links")
;;
;; «.find-pdf-txt» (to "find-pdf-txt")
;; «.find-pdf-txt-links» (to "find-pdf-txt-links")
;; «hash-table» (to ".hash-table")
(setq ee-tla-table (make-hash-table :test 'equal))
;; Tests: (ee-tla-canonicalize nil)
;; (ee-tla-canonicalize "/home/edrx/foo")
(defun ee-tla-canonicalize (o)
(if (stringp o) (ee-shorten-file-name (ee-expand o)) o))
;; Here the argument tla has to be a symbol.
(defun ee-tla-set (tla fname)
(setq fname (ee-tla-canonicalize fname))
(puthash fname tla ee-tla-table)
(puthash tla fname ee-tla-table))
(defun ee-tla-get (o) (gethash o ee-tla-table))
(defun ee-tla-remove (o) (remhash o ee-tla-table))
(defun ee-tla-fname () (ee-tla-canonicalize (buffer-file-name)))
(defun ee-tla-tla () (ee-tla-get (ee-tla-fname)))
;; Based on: (find-eev "eev-edit.el" "ee-copy-this-line-to-kill-ring")
(defun ee-tla-tag ()
(save-excursion
(if (re-search-backward (ee-tag-re) nil 'no-error)
(ee-no-properties (match-string 1)))))
;; Test: (find-estring (ee-tla-table-to-string))
;;
(defun ee-tla-table-to-string ()
(let ((lines (cl-loop for k being the hash-keys of ee-tla-table
collect (format "%S -> %S\n" k (ee-tla-get k)))))
(apply 'concat (sort lines 'string<))))
;; Tests:
;; (code-tla "ats" "~/LATEX/2020ats.tex")
;; (code-tla "pyt" "~/LATEX/2020pythontex.tex")
;; (code-tla "dnv" "~/LATEX/2020dednat6-video.tex")
;; (code-tla "qui" "~/LATEX/2020quiver.tex")
;; (code-tla "grt" "~/LATEX/2020groth-tops.tex")
;; (code-tla "grd" "~/LATEX/2021groth-tops-defs.tex")
;; (code-tla "grc" "~/LATEX/2021groth-tops-children.tex")
;; (code-tla "grs" "~/LATEX/2021groth-tops-children-slides.tex")
;; (code-tla "has" "~/LATEX/2021haskell.tex")
;; (code-tla "exc" "~/LATEX/2021excuse.tex")
;; (grt)
;; (grta "grotop-J")
;; (grta "grotop-J" "first")
;; (grtp 11 "grotop-J" "first")
;;; _ _ _
;;; ___ ___ __| | ___ | |_| | __ _
;;; / __/ _ \ / _` |/ _ \_____| __| |/ _` |
;;; | (_| (_) | (_| | __/_____| |_| | (_| |
;;; \___\___/ \__,_|\___| \__|_|\__,_|
;;;
;; «code-tla» (to ".code-tla")
;; Skel: (find-code-xxx-links "tla" "tla fname" "")
;; Tests: (find-code-tla "qux" "~/LATEX/2019J-ops-algebra.tex")
;; (find-code-tla 'qux "~/LATEX/2019J-ops-algebra.tex")
;; (find-code-tla "qux" "~/LATEX/2019J-ops-algebra.txt")
;; (find-code-tla 'qux "~/LATEX/2019J-ops-algebra.txt")
;; Note that here the first argument can be either a string or a
;; symbol - try the tests above!
(defun code-tla (tla fname)
(eval (ee-read (ee-code-tla tla fname))))
(defun find-code-tla (tla fname)
(find-estring-elisp (ee-code-tla tla fname)))
(defun ee-code-tla (tla fname)
(let* ((fnamepdf (ee-tla-tex-to-pdf fname))
(haspdf (stringp fnamepdf)))
(concat
(ee-template0 "\
;; (find-code-tla \"{tla}\" \"{fname}\")
;; (code-tla \"{tla}\" \"{fname}\")
(ee-tla-set '{tla} \"{fname}\")
(defun {tla} (&rest pos-spec-list)
(interactive)
(apply 'find-fline \"{fname}\" pos-spec-list))
(defun {tla}a (&rest pos-spec-list)
(apply 'find-anchor \"{fname}\" pos-spec-list))
")
(if haspdf
(ee-template0 "
(defun {tla}p (&optional page &rest rest)
(interactive)
(find-pdf-page \"{fnamepdf}\" page))
(defun {tla}t (&optional page &rest rest)
(interactive)
(apply 'find-pdf-text \"{fnamepdf}\" page rest))
") "")
)))
;; Tests: (ee-tla-tex-to-pdf "~/LATEX/2019J-ops-algebra.foo")
;; (ee-tla-tex-to-pdf "~/LATEX/2019J-ops-algebra.tex")
;;
(defun ee-tla-tex-to-pdf (fname)
(if (string-match ".tex$" fname)
(replace-regexp-in-string ".tex$" ".pdf" fname)))
;; «find-tla-def-links» (to ".find-tla-def-links")
;; Skel: (find-find-links-links-new "tla-def" "fname tla" "haspdf")
;;
(defun find-tla-def-links (&optional fname tla &rest pos-spec-list)
"Visit a temporary buffer containing hyperlinks for tla-def."
(interactive)
(setq fname (or fname "{fname}"))
(setq tla (or tla "{tla}"))
(let* ((haspdf "{haspdf}"))
(apply
'find-elinks-elisp
`((find-tla-def-links ,fname ,tla ,@pos-spec-list)
;; Convention: the first sexp always regenerates the buffer.
(find-efunction 'find-tla-def-links)
,(ee-template0 "\
;; This file doesn't have a TLA. Change the last argument above...
;; (find-estring (ee-tla-table-to-string))
;; (find-fline \"~/.emacs\" \"(code-tla '{tla} \")
;; (find-fline \"~/.emacs.tlas\" \"(code-tla '{tla} \")
;; (ee-copy-rest 2 '(find-fline \"~/.emacs\"))
;; (ee-copy-rest 1 '(find-fline \"~/.emacs.tlas\"))
;; (find-code-tla '{tla} \"{fname}\")
(code-tla '{tla} \"{fname}\")\
")
)
pos-spec-list)))
;; «find-tla-links» (to ".find-tla-links")
;; Skel: (find-find-links-links-new "tla" "tla fname tag" "haspdf")
;; Test: (find-tla-links)
;;
(defun find-tla-links (&optional tla fname tag &rest pos-spec-list)
"Visit a temporary buffer containing hyperlinks for tla."
(interactive)
(setq tla (or tla "{tla}"))
(setq fname (or fname "{fname}"))
(setq tag (or tag "{tag}"))
(let* ((haspdf "{haspdf}"))
(apply
'find-elinks-elisp
`((find-tla-links ',tla ,fname ,tag ,@pos-spec-list)
;; Convention: the first sexp always regenerates the buffer.
(find-efunction 'find-tla-links)
""
,(ee-template0 "\
;; Links to here:
({tla})
({tla}a \"{tag}\")
({tla}p 1 \"{tag}\")
({tla}a \"{tag}\")
({tla}t 1)
;; Inspect or remove the current association:
;; (find-estring (ee-tla-table-to-string) \"{tla} ->\")
;; (find-estring (ee-tla-table-to-string) \"-> {tla}\")
;; (ee-tla-remove '{tla})
;; (ee-tla-remove \"{fname}\")
;; Save the current association:
;; (find-fline \"~/.emacs\" \"(code-tla '{tla} \")
;; (find-fline \"~/.emacs.tlas\" \"(code-tla '{tla} \")
;; (ee-copy-rest 1 '(find-fline \"~/.emacs\"))
;; (ee-copy-rest 0 '(find-fline \"~/.emacs.tlas\"))
(code-tla '{tla} \"{fname}\")\
")
)
pos-spec-list)))
;; (ee-tla-remove 'etl)
;; (ee-tla-remove (ee-tla-fname))
;; (find-estring (ee-tla-table-to-string))
;; (eejump-3)
(defun find-tla-here-links ()
"Visit a temporary buffer containing hyperlinks for TLAs.
If the current file has a TLA associated to it, run `find-tla-links';
if it doesn't, run `find-tla-def-links'.\n
See: (find-eevfile \"eev-tla.el\" \";; Commentary:\")"
(interactive)
(let* ((fname (ee-tla-fname))
(tla (ee-tla-tla))
(tag (ee-tla-tag)))
(if tla (find-tla-links tla fname tag)
(find-tla-def-links fname))))
(defun eejump-3 () (find-tla-here-links))
;; Tests: (find-estring (ee-tla-link 'foo 99 "TaG"))
;; (find-estring (ee-tla-link 'foo 9 "TaG"))
;; (find-estring (ee-tla-link))
;;
(defun ee-tla-link (&optional tla n tag)
(setq tla (or tla "{tla}"))
(setq n (format "%s" (or n 1)))
(setq tag (or tag "{tag}"))
(let ((s (replace-regexp-in-string "." " " n)))
(ee-template0 "\
% ({tla}p {n} \"{tag}\")
% ({tla}a {s} \"{tag}\")
")))
(defun eejump-33 ()
(eek "C-a")
(insert (ee-tla-link (ee-tla-tla) 99 (ee-tla-tag))))
;; Let's make `tla' point to this file,
(code-tla 'tla (ee-eevfile "eev-tla.el"))
;; so that people will start with a non-empty
;; `ee-tla-table'. Try:
;; (find-estring (ee-tla-table-to-string) "tla ->")
;; (find-estring (ee-tla-table-to-string) "-> tla")
;;; __ _ _ _ __ _ _
;;; / _(_)_ __ __| | _ __ __| |/ _| | |___ _| |_
;;; | |_| | '_ \ / _` |_____| '_ \ / _` | |_ _____| __\ \/ / __|
;;; | _| | | | | (_| |_____| |_) | (_| | _|_____| |_ > <| |_
;;; |_| |_|_| |_|\__,_| | .__/ \__,_|_| \__/_/\_\\__|
;;; |_|
;;
;; «find-pdf-txt» (to ".find-pdf-txt")
;; This is a variant of `find-pdf-text' that implements a part of what
;; Erich Ruff suggested here:
;; https://lists.gnu.org/archive/html/eev/2021-06/msg00013.html
;;
;; These two sexps are similar, but the second uses a saved ".txt":
;; (find-pdf-text "~/Coetzee99.pdf" 3 "LECTURE I")
;; (find-pdf-txt "~/Coetzee99.pdf" 3 "LECTURE I")
;;
;; This is a VERY EARLY prototype.
;; I haven't yet tried to integrate this with `code-tla' or with:
;;
;; (find-pdf-like-intro "7. Shorter hyperlinks to PDF files")
;; (find-pdf-like-intro "8. `find-pdf'-pairs")
;; (code-pdf-page "livesofanimals" "~/Coetzee99.pdf")
;; (code-pdf-text "livesofanimals" "~/Coetzee99.pdf" -110)
;;
;; TODO: a `code-pdf-txt' that works like `code-pdf-text' but uses
;; `find-pdf-txt' instead of `find-pdf-text'.
;; Test: (find-pdf-text "~/Coetzee99.pdf")
;; (find-pdf-text "~/Coetzee99.pdf" 1)
;; (find-pdf-text "~/Coetzee99.pdf" 3)
;; (find-pdf-text "~/Coetzee99.pdf" 3 "LECTURE I")
;; (find-sh0 "rm -fv ~/Coetzee99.txt ~/Coetzee99.txt~")
;; (find-pdf-txt "~/Coetzee99.pdf")
;; (find-pdf-txt "~/Coetzee99.pdf" 3)
;; (find-pdf-txt "~/Coetzee99.pdf" 3 "LECTURE I")
;; See: (find-efunctionpp 'find-pdf-text)
;; (find-efunctionpp 'find-pdftotext-text)
;; (find-eev "eev-pdflike.el" "find-pdftotext-text")
;; (find-eev "eev-pdflike.el" "find-sh-page")
;; (find-eev "eev-pdflike.el" "ee-goto-position-page")
;;
(defun find-pdf-txt (fnamepdf &rest pos-spec-list)
"Open the .txt associated to FNAMEPDF or run `find-pdf-txt-links' to create it."
(let ((fnametxt (ee-fnamepdf-to-fnametxt fnamepdf)))
(if (file-exists-p fnametxt)
(progn (find-fline fnametxt)
(apply 'ee-goto-position-page pos-spec-list))
(find-pdf-txt-links fnamepdf))))
;; «find-pdf-txt-links» (to ".find-pdf-txt-links")
;; Test: (find-sh0 "rm -fv ~/Coetzee99.txt ~/Coetzee99.txt~")
;; (find-pdf-txt-links "~/Coetzee99.pdf")
;; Skel: (find-find-links-links-new "pdf-txt" "fnamepdf" "fnametxt")
;;
(defun find-pdf-txt-links (&optional fnamepdf &rest pos-spec-list)
"`find-pdf-txt' runs this when the .txt file for FNAMEPDF does not exist."
(interactive)
(setq fnamepdf (or fnamepdf "{fnamepdf}"))
(let* ((fnametxt (or (ee-fnamepdf-to-fnametxt fnamepdf) "{fnametxt}")))
(apply
'find-elinks
`((find-pdf-txt-links ,fnamepdf ,@pos-spec-list)
;; Convention: the first sexp always regenerates the buffer.
(find-efunction 'find-pdf-txt-links)
""
(find-pdf-text-insert 3 ,fnamepdf)
(ee-copy-rest 1 '(find-fline ,fnametxt))
)
pos-spec-list)))
;; Tests: (ee-fnamepdf-to-fnametxt "~/Coetzee99.pdf")
;; (ee-fnamepdf-to-fnametxt "~/Coetzee99.FOO")
;;
(defun ee-fnamepdf-to-fnametxt (fnamepdf)
(if (string-match ".pdf$" fnamepdf)
(replace-regexp-in-string ".pdf$" ".txt" fnamepdf)))
;; Tests: (ee-find-pdftotext-text "~/Coetzee99.pdf")
;; (find-estring "(find-pdf-text-insert 2 \"~/Coetzee99.pdf\")")
;; See: (find-efunctionpp 'find-pdf-text)
;; (find-efunctionpp 'find-pdftotext-text)
;; (find-eev "eev-pdflike.el" "find-pdftotext-text")
;; (find-efunction 'ee-find-pdftotext-text)
;; (find-eev "eev-plinks.el" "find-callprocess")
;;
(defun find-pdf-text-insert (nlines fnamepdf)
"Move down NLINES and insert FNAMEPDF converted to text."
(save-excursion
(let ((next-line-add-newlines t))
(dotimes (i nlines) (next-line 1)))
(insert (find-callprocess00 (ee-find-pdftotext-text fnamepdf)))))
(provide 'eev-tla)
;; Local Variables:
;; coding: utf-8-unix
;; no-byte-compile: t
;; End: