branch: externals/hyperbole
commit b820f5ca80fdcfca4a35ac39ce7c674cc9b2009d
Merge: 4e87c32c4b 61117aa76d
Author: bw <[email protected]>
Commit: bw <[email protected]>
Merge branch 'master' into rsw
---
ChangeLog | 33 +++++++++++
hywiki.el | 152 ++++++++------------------------------------------
test/hywiki-tests.el | 154 +++++++++++++++++++++++++++++++++++++++++++--------
3 files changed, 188 insertions(+), 151 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index 0dcc9fc9e6..1f90e13749 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,36 @@
+2025-11-02 Mats Lidell <[email protected]>
+
+* test/hywiki-tests.el
+ (hywiki-tests--wikiword-step-check-edit-wikiword-in-emacs-lisp-mode):
+ Add test.
+ (hywiki-tests--wikiword-identified-in-emacs-lisp-mode): Update test to
+ verify WikiWord face.
+
+2025-10-31 Mats Lidell <[email protected]>
+
+* test/hywiki-tests.el (hywiki-tests--publish-special-cases): Add test
+ that triggers problems in publishing. Test is constructed to work by
+ verifying what is produced. Comments point to what is the expected
+ result.
+
+2025-10-29 Mats Lidell <[email protected]>
+
+* test/hywiki-tests.el (hywiki-tests--org-link-export): Add environment
+ and mock call to hywiki--org-link-html-format.
+ (hywiki-tests--search-section): Helper for finding id strings in
+ generated html.
+ (hywiki-tests--published-html-links-to-word-and-section): Use
+ hywiki-tests--search-section.
+
+* hywiki.el (hywiki-org-link-export): Add info parameter for getting the
environment.
+ (hywiki-org-link-export): Use hywiki--org-link-html-format.
+ (hywiki-publish-to-html): Simplify function since advice has been removed.
+ (hywiki--org-link-html-format): Add method for link formating in html.
+
+ (hywiki--org-format-reference):
+ (hywiki--org-export-new-title-reference):
+ (hywiki--org-export-get-reference): Not used, removed.
+
2025-10-26 Bob Weiner <[email protected]>
* hycontrol.el (hycontrol-frame-zoom): Fix to check for an autoloaded
function, 'zoom-frm-in'.
diff --git a/hywiki.el b/hywiki.el
index 06030fc0cf..fd30f92674 100644
--- a/hywiki.el
+++ b/hywiki.el
@@ -3,7 +3,7 @@
;; Author: Bob Weiner
;;
;; Orig-Date: 21-Apr-24 at 22:41:13
-;; Last-Mod: 26-Oct-25 at 11:48:45 by Bob Weiner
+;; Last-Mod: 2-Nov-25 at 17:09:25 by Mats Lidell
;;
;; SPDX-License-Identifier: GPL-3.0-or-later
;;
@@ -2845,11 +2845,29 @@ If not found, set it up and return the new project
properties."
(concat hywiki-org-link-type ":"))
(hywiki-word-read)))
+(defun hywiki--org-link-html-format (path-stem suffix desc info)
+ "Format an html link using Org ids."
+ (let* ((heading (and suffix (not (string-empty-p suffix)) (substring suffix
1)))
+ (link-obj (org-element-create
+ 'link
+ (list
+ :type "file"
+ :path (concat path-stem ".org")
+ :search-option (and heading (concat "*" heading))
+ :format 'bracket)))
+ ;; Export as HTML
+ (exported (org-export-data link-obj info))
+ ;; Extract the href
+ (href (if (string-match "href=\"\\([^\"]+\\)\"" exported)
+ (match-string 1 exported)
+ exported)))
+ (format "<a href=\"%s\">%s</a>" href desc)))
+
;;; Next two functions derived from the denote package.
;;;###autoload
-(defun hywiki-org-link-export (link description format)
+(defun hywiki-org-link-export (link description format info)
"Export a HyWikiWord Org-format `hy:' link to various formats.
-The LINK, DESCRIPTION, and FORMAT are provided by the export
+The LINK, DESCRIPTION, FORMAT and INFO are provided by the export
backend."
(let* ((path-word-suffix (hywiki-reference-to-referent link :full-data))
(path (when path-word-suffix
@@ -2866,11 +2884,7 @@ backend."
(if path
(pcase format
(`ascii (format "[%s] <%s:%s>" hywiki-org-link-type desc path))
- (`html (format "<a href=\"%s.html%s\">%s</a>"
- path-stem
- (hpath:spaces-to-dashes-markup-anchor
- (or suffix ""))
- desc))
+ (`html (hywiki--org-link-html-format path-stem suffix desc info))
(`latex (format "\\href{%s.latex}{%s}" (replace-regexp-in-string
"[\\{}$%&_#~^]" "\\\\\\&" path-stem) desc))
(`md (format "[%s](%s.md%s)" desc path-stem
(hpath:spaces-to-dashes-markup-anchor
@@ -2983,12 +2997,7 @@ Customize this directory with:
;; Export Org to html with useful link ids.
;; Instead of random ids like "orga1b2c3", use heading titles with
;; spaces replaced with dashes, made unique when necessary.
- (unwind-protect
- (progn
- ;; (advice-add #'org-export-get-reference :override
#'hywiki--org-export-get-reference)
- (org-publish-project "hywiki" all-pages-flag))
- ;; (advice-remove #'org-export-get-reference
#'hywiki--org-export-get-reference)
- nil))
+ (org-publish-project "hywiki" all-pages-flag))
(defun hywiki-referent-exists-p (&optional word start end)
"Return the HyWikiWord at point or optional HyWiki WORD, if has a referent.
@@ -3798,121 +3807,6 @@ This must be called within a `save-excursion' or it may
move point."
(goto-char (1- (point))))
(hywiki-maybe-highlight-between-page-names))
-;;; ************************************************************************
-;;; Private Org export override functions
-;;; ************************************************************************
-
-;; Thanks to alphapapa for the GPLed code upon which these hywiki--org
-;; functions are based. These change the html ids that Org export
-;; generates to use the text of headings rather than randomly
-;; generated ids.
-
-(require 'cl-extra) ;; for `cl-some'
-(require 'ox) ;; for `org-export-get-reference'
-(require 'url-util) ;; for `url-hexify-string'
-
-(defun hywiki--org-export-get-reference (datum info)
- "Return a unique reference for DATUM, as a string.
-Like `org-export-get-reference' but uses modified heading strings as
-link ids rather than generated ids. To form an id, spaces in headings
-are replaced with dashes and to make each id unique, heading parent
-ids are prepended separated by '--'.
-
-DATUM is either an element or an object. INFO is the current
-export state, as a plist.
-
-References for the current document are stored in the
-`:internal-references' property. Its value is an alist with
-associations of the following types:
-
- (REFERENCE . DATUM) and (SEARCH-CELL . ID)
-
-REFERENCE is the reference string to be used for object or
-element DATUM. SEARCH-CELL is a search cell, as returned by
-`org-export-search-cells'. ID is a number or a string uniquely
-identifying DATUM within the document.
-
-This function also checks `:crossrefs' property for search cells
-matching DATUM before creating a new reference."
- (let ((cache (plist-get info :internal-references)))
- (or (car (rassq datum cache))
- (let* ((crossrefs (plist-get info :crossrefs))
- (cells (org-export-search-cells datum))
- ;; Preserve any pre-existing association between
- ;; a search cell and a reference, i.e., when some
- ;; previously published document referenced a location
- ;; within current file (see
- ;; `org-publish-resolve-external-link').
- ;;
- ;; However, there is no guarantee that search cells are
- ;; unique, e.g., there might be duplicate custom ID or
- ;; two headings with the same title in the file.
- ;;
- ;; As a consequence, before reusing any reference to
- ;; an element or object, we check that it doesn't refer
- ;; to a previous element or object.
- (new (or (when (org-element-property :raw-value datum)
- ;; Heading with a title
- (hywiki--org-export-new-title-reference datum cache))
- (cl-some
- (lambda (cell)
- (let ((stored (cdr (assoc cell crossrefs))))
- (when stored
- (let ((old (org-export-format-reference
stored)))
- (and (not (assoc old cache)) stored)))))
- cells)
- (org-export-format-reference
- (org-export-new-reference cache))))
- (reference-string new))
- ;; Cache contains both data already associated to
- ;; a reference and in-use internal references, so as to make
- ;; unique references.
- (dolist (cell cells) (push (cons cell new) cache))
- ;; Retain a direct association between reference string and
- ;; DATUM since (1) not every object or element can be given
- ;; a search cell (2) it permits quick lookup.
- (push (cons reference-string datum) cache)
- (plist-put info :internal-references cache)
- reference-string))))
-
-(defun hywiki--org-export-new-title-reference (datum cache)
- "Return new heading title reference for DATUM that is unique in CACHE."
- (let* ((title (org-element-property :raw-value datum))
- (ref (hywiki--org-format-reference title))
- (parent (org-element-property :parent datum))
- raw-parent)
- (while (cl-some (lambda (elt) (equal ref (car elt)))
- cache)
- ;; Title not unique: make it so.
- (if parent
- ;; Append ancestor title.
- (setq raw-parent (org-element-property :raw-value parent)
- title (if (and (stringp raw-parent) (not (string-empty-p
raw-parent)))
- (concat raw-parent "--" title)
- title)
- ref (hywiki--org-format-reference title)
- parent (org-element-property :parent parent))
- ;; No more ancestors: add and increment a number.
- (when (string-match "\\`\\([[:unibyte:]]\\)+?\\(--\\([0-9]+\\)\\)?\\'"
- ref)
- (let ((num (match-string 3 ref)))
- (setq parent (match-string 1 ref)
- parent (if (stringp parent) (concat parent "--") "")
- num (if num
- (string-to-number num)
- 0)
- num (1+ num)
- ref (format "%s%s" parent num))))))
- ref))
-
-(defun hywiki--org-format-reference (title)
- "Format TITLE string as an html id."
- (url-hexify-string
- (replace-regexp-in-string "\\[\\[\\([a-z]+:\\)?\\|\\]\\[\\|\\]\\]" ""
- (subst-char-in-string
- ?\ ?-
- (substring-no-properties title)))))
-
;;; ************************************************************************
;;; Private initializations
;;; ************************************************************************
diff --git a/test/hywiki-tests.el b/test/hywiki-tests.el
index 1ba55ee86e..049f7704bc 100644
--- a/test/hywiki-tests.el
+++ b/test/hywiki-tests.el
@@ -3,7 +3,7 @@
;; Author: Mats Lidell
;;
;; Orig-Date: 18-May-24 at 23:59:48
-;; Last-Mod: 18-Oct-25 at 13:31:29 by Bob Weiner
+;; Last-Mod: 2-Nov-25 at 18:54:18 by Mats Lidell
;;
;; SPDX-License-Identifier: GPL-3.0-or-later
;;
@@ -921,29 +921,32 @@ Both mod-time and checksum must be changed for a test to
return true."
(let* ((hywiki-directory (make-temp-file "hywiki" t))
(wikipage (cdr (hywiki-add-page "WikiWord")))
(filename (when wikipage (file-name-nondirectory wikipage)))
- (filename-stem (when filename (file-name-sans-extension filename))))
+ (filename-stem (when filename (file-name-sans-extension filename)))
+ (info "environment"))
(unwind-protect
(progn
(find-file wikipage)
(should (string-match-p
(format "\\[hy\\] <doc:.*%s>" filename)
- (hywiki-org-link-export "WikiWord" "doc" 'ascii)))
- (should (string-match-p
- (format "<a href=\".*%s.html\">doc</a>" filename-stem)
- (hywiki-org-link-export "WikiWord" "doc" 'html)))
+ (hywiki-org-link-export "WikiWord" "doc" 'ascii info)))
+ ;; FIXME: Solving this case with a mock for now.
+ (mocklet (((hywiki--org-link-html-format "WikiWord" "" "doc"
"environment") => (format "<a href=\".*%s.html\">doc</a>" "WikiWord")))
+ (should (string-match-p
+ (format "<a href=\".*%s.html\">doc</a>" filename-stem)
+ (hywiki-org-link-export "WikiWord" "doc" 'html info))))
(should (string-match-p
(format "\\[doc\\](.*%s.md)" filename-stem)
- (hywiki-org-link-export "WikiWord" "doc" 'md)))
+ (hywiki-org-link-export "WikiWord" "doc" 'md info)))
(should (string-match-p
(format "\\href{.*%s.latex}{doc}" filename-stem)
- (hywiki-org-link-export "WikiWord" "doc" 'latex)))
+ (hywiki-org-link-export "WikiWord" "doc" 'latex info)))
(should (string-match-p
(format "@uref{.*%s.texi,doc}" filename-stem)
- (hywiki-org-link-export "WikiWord" "doc" 'texinfo)))
+ (hywiki-org-link-export "WikiWord" "doc" 'texinfo info)))
(should (string-match-p
(format ".*%s" filename)
- (hywiki-org-link-export "WikiWord" "doc" 'unknown)))
- (should (string= "NotAWikiPage" (hywiki-org-link-export
"NotAWikiPage" "doc" 'ascii))))
+ (hywiki-org-link-export "WikiWord" "doc" 'unknown info)))
+ (should (string= "NotAWikiPage" (hywiki-org-link-export
"NotAWikiPage" "doc" 'ascii nil))))
(hy-delete-file-and-buffer wikipage)
(hywiki-tests--delete-hywiki-dir-and-buffer hywiki-directory))))
@@ -997,6 +1000,17 @@ body B
(hy-delete-file-and-buffer wikipage)
(hywiki-tests--delete-hywiki-dir-and-buffer hywiki-directory)))))
+
+(defun hywiki-tests--search-section (section)
+ "Find SECTION in current buffer and return the id string.
+Search for elements of type <h?>...</h?> for the id string. Example:
+<h4 id=\"org7c18f23\"><span class=\"section-number-4\">1.1.1.</span>
section</h4>
+would return org7c18f23."
+ (save-excursion
+ (beginning-of-buffer)
+ (when (re-search-forward (format "<h. id=\"\\(.*?\\)\">.*</span> %s</h.>"
section) nil t)
+ (match-string-no-properties 1))))
+
(ert-deftest hywiki-tests--published-html-links-to-word-and-section ()
"Verify published html links to WikiWord and section."
;; org-publish does not work properly to support HyWiki export prior
@@ -1045,6 +1059,13 @@ WikiWord#Csection-subsection
(should (file-exists-p wikipage-html))
(should (file-exists-p wikiword-html))
+ (let (idA idB idC)
+ ;; Verify anchors are generated and fetch their ids
+ (with-current-buffer (find-file-noselect wikiword-html)
+ (setq idA (should (hywiki-tests--search-section "Asection")))
+ (setq idB (should (hywiki-tests--search-section "Bsection
subsection")))
+ (setq idC (should (hywiki-tests--search-section
"Csection-subsection"))))
+
;; Verify links are generated
(with-current-buffer (find-file-noselect wikipage-html)
;; (First check we even get the wikipage with sections)
@@ -1053,12 +1074,73 @@ WikiWord#Csection-subsection
(should (= 1 (count-matches (regexp-quote "WikiWord#Bsection
subsection") (point-min) (point-max))))
(should (= 1 (count-matches (regexp-quote
"WikiWord#Csection-subsection") (point-min) (point-max))))
- ;; (print (buffer-substring-no-properties (point-min) (point-max)))
;; Then verify the href links are generated
(should (= 1 (count-matches (regexp-quote "<a
href=\"WikiWord.html\">WikiWord</a>") (point-min) (point-max))))
- (should (= 1 (count-matches (regexp-quote "<a
href=\"WikiWord.html#Asection\">WikiWord#Asection</a>") (point-min)
(point-max))))
- (should (= 1 (count-matches (regexp-quote "<a
href=\"WikiWord.html#Bsection-subsection\">WikiWord#Bsection subsection</a>")
(point-min) (point-max))))
- (should (= 1 (count-matches (regexp-quote "<a
href=\"WikiWord.html#Csection-subsection\">WikiWord#Csection-subsection</a>")
(point-min) (point-max))))))
+ (should (= 1 (count-matches
+ (format "<a
href=\"WikiWord.html#%s\">WikiWord#Asection</a>" idA) (point-min) (point-max))))
+ (should (= 1 (count-matches
+ (format "<a
href=\"WikiWord.html#%s\">WikiWord#Bsection subsection</a>" idB) (point-min)
(point-max))))
+ (should (= 1 (count-matches
+ (format "<a
href=\"WikiWord.html#%s\">WikiWord#Csection-subsection</a>" idC) (point-min)
(point-max)))))))
+ (hy-delete-files-and-buffers (list wikipage wikiword wikipage-html
wikiword-html
+ (expand-file-name "index.org"
hywiki-directory)
+ (expand-file-name "index.html"
hywiki-org-publishing-directory)))
+ (hy-delete-dir-and-buffer hywiki-org-publishing-directory)
+ (hywiki-tests--delete-hywiki-dir-and-buffer hywiki-directory))))
+
+(ert-deftest hywiki-tests--publish-special-cases ()
+ "Verify different special cases."
+ ;; org-publish does not work properly to support HyWiki export prior
+ ;; to version 9.7, so this will be skipped for Emacs 28 and 29.
+ (skip-unless (string-greaterp org-version "9.6.999"))
+ (let* ((hywiki-directory (make-temp-file "hywiki_" t))
+ org-publish-project-alist
+ (hywiki-org-publishing-directory (make-temp-file "public_html_" t))
+ (wikipage (cdr (hywiki-add-page "WikiPage")))
+ (wikipage-html (expand-file-name "WikiPage.html"
hywiki-org-publishing-directory))
+ (wikiword (cdr (hywiki-add-page "WikiWord")))
+ (wikiword-html (expand-file-name "WikiWord.html"
hywiki-org-publishing-directory))
+ (href "<a href=\"WikiWord.html\">WikiWord</a>"))
+ (unwind-protect
+ (progn
+ (hywiki-org-set-publish-project)
+
+ (with-current-buffer (find-file-noselect wikiword)
+ (erase-buffer)
+ (insert "Text\n")
+ (save-buffer))
+
+ (should (file-exists-p hywiki-directory))
+ (should (file-exists-p wikipage))
+ (should (file-exists-p wikiword))
+
+ (dolist (v `(("WikiWord WikiWord" . ,(format "%s %s" href href))
+ ("\"WikiWord WikiWord\"" . ,(format "\"%s%s\"" href
href))
+ ;; ^ Missing a
space!?
+ ("WikiWord Text WikiWord" . ,(format "%s Text %s" href
href))
+ ("\"WikiWord Text WikiWord\"" . ,(format "\"%s%s\""
href href))
+ ;; ^ Missing
" Text "
+ ("WikiWord WikiWord WikiWord" . ,(format "%s %s %s"
href href href))
+ ;; (cons "\"WikiWord WikiWord WikiWord\"" (format "\"%s
%s %s\"" href href href))
+ ;; ^ Crashes due to (wrong-type-argument
integer-or-marker-p nil) caused by buffer-substring-no-properties(nil nil)
+ ))
+ (let ((input (car v))
+ (regex-output (cdr v)))
+ ;; Setup WikiPage.
+ (with-current-buffer (find-file-noselect wikipage)
+ (erase-buffer)
+ (insert input)
+ (save-buffer))
+
+ ;; Export the wiki
+ (hywiki-publish-to-html t)
+
+ ;; Verify Export
+ (ert-info ((format "Publish '%s' => Expect '%s'" input
regex-output))
+ (with-current-buffer (find-file-noselect wikipage-html t)
+ (revert-buffer t t)
+ (should (= 1 (count-matches regex-output (point-min)
(point-max)))))))))
+ ;; Unwind
(hy-delete-files-and-buffers (list wikipage wikiword wikipage-html
wikiword-html
(expand-file-name "index.org"
hywiki-directory)
(expand-file-name "index.html"
hywiki-org-publishing-directory)))
@@ -1730,11 +1812,38 @@ Insert test in the middle of other text."
(p1 . t) (p4) (-1 . "Hiho")))))
(hywiki-tests--delete-hywiki-dir-and-buffer hywiki-directory)))))
+(ert-deftest
hywiki-tests--wikiword-step-check-edit-wikiword-in-emacs-lisp-mode ()
+ "Run the step check to verify WikiWord is identified under change in a
docstring.
+A WikiWord is completed, then last char is deleted and reinserted. The
+face is verified during the change."
+ (hywiki-tests--preserve-hywiki-mode
+ (let* ((hywiki-directory (make-temp-file "hywiki" t))
+ (wiki-page (cdr (hywiki-add-page "WikiWord"))))
+ (unwind-protect
+ (progn
+ (hywiki-mode 1)
+ (with-temp-buffer
+ (emacs-lisp-mode)
+ (insert "\
+(defun func ()
+ \"WikiWor)
+")
+ ;; Set point after WikiWor
+ (goto-char 1)
+ (should (search-forward "WikiWor"))
+
+ ;; Complete WikiWord and verify highlighting
+ (hywiki-tests--run-test-case
+ '(("d\"" . "WikiWord") (p2 . t) (-1) ("d" . "WikiWord")))))
+ (hy-delete-file-and-buffer wiki-page)
+ (hywiki-tests--delete-hywiki-dir-and-buffer hywiki-directory)))))
+
(ert-deftest hywiki-tests--wikiword-identified-in-emacs-lisp-mode ()
"Verify WikiWord is identified when surrounded by delimiters in
`emacs-lisp-mode'."
(hywiki-tests--preserve-hywiki-mode
- (let ((hsys-org-enable-smart-keys t)
- (hywiki-directory (make-temp-file "hywiki" t)))
+ (let* ((hsys-org-enable-smart-keys t)
+ (hywiki-directory (make-temp-file "hywiki" t))
+ (wiki-page (cdr (hywiki-add-page "WikiWord"))))
(unwind-protect
(progn
(hywiki-mode 1)
@@ -1749,14 +1858,14 @@ Insert test in the middle of other text."
(insert (format ";; %s" v))
(hywiki-tests--command-execute #'newline 1 'interactive)
(goto-char 9)
- (should (string= "WikiWord" (hywiki-word-at))))
+ (should (string= "WikiWord" (hywiki-tests--word-at))))
(with-temp-buffer
(emacs-lisp-mode)
(insert (format "(setq var \"%s\")" v))
(hywiki-tests--command-execute #'newline 1 'interactive)
(goto-char 16)
- (should (string= "WikiWord" (hywiki-word-at)))))
+ (should (string= "WikiWord" (hywiki-tests--word-at)))))
;; Does not match as a WikiWord
(dolist (v '("WikiWord#"))
@@ -1765,14 +1874,15 @@ Insert test in the middle of other text."
(insert (format ";; %s" v))
(hywiki-tests--command-execute #'newline 1 'interactive)
(goto-char 9)
- (should-not (hywiki-word-at)))
+ (should-not (hywiki-tests--word-at)))
(with-temp-buffer
(emacs-lisp-mode)
(insert (format "(setq var \"%s\")" v))
(hywiki-tests--command-execute #'newline 1 'interactive)
(goto-char 16)
- (should-not (hywiki-word-at)))))
+ (should-not (hywiki-tests--word-at)))))
+ (hy-delete-file-and-buffer wiki-page)
(hywiki-tests--delete-hywiki-dir-and-buffer hywiki-directory)))))
(ert-deftest hywiki-tests--wikiword-identified-in-strings-in-emacs-lisp-mode ()
@@ -2000,7 +2110,7 @@ Insert test in the middle of other text."
(wikiHi (cdr (hywiki-add-page "Hi")))
(action-key-modeline-buffer-id-function nil)) ; Avoid treemacs.
(unwind-protect
- (progn
+ (progn
(hywiki-directory-edit)
(should (equal 'dired-mode major-mode))
(should (string= default-directory (file-name-as-directory
hywiki-directory))))