Skip to content

Commit

Permalink
Merge pull request #544 from kaushalmodi/org-hugo--get-anchor-refactor
Browse files Browse the repository at this point in the history
Add new defcustom `org-hugo-anchor-functions`
  • Loading branch information
kaushalmodi committed Jan 28, 2022
2 parents 430c967 + 558c953 commit be9d24d
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 24 deletions.
67 changes: 67 additions & 0 deletions doc/ox-hugo-manual.org
Expand Up @@ -3792,6 +3792,73 @@ Additionally, if it has the property ~EXPORT_HTML_CONTAINER_CLASS:
foo~, the "foo" class gets added to that container tag as well.

{{{test-search(container)}}}
*** Anchors
:PROPERTIES:
:EXPORT_FILE_NAME: anchors
:END:
#+begin_description
Anchors derived for sub-heading and other HTML elements in a page.
#+end_description
Hugo supports specifying custom ID's for headings in a page using the
~{#some-id}~ syntax. See its documentation on [[https://gohugo.io/content-management/cross-references/#heading-ids][Cross References:
Heading IDs]] for reference.

If the heading ID's are not specified using the above syntax, Hugo
will generate them automatically. While that is OK in general, it
becomes problematic when user generates [[* Table of Contents][TOC]] using Org, where the links
to headings on the page need to be embedded, or uses Org internal or
external links to reference other headings.

So in order to make the user experience smooth, anchors for all
headings are always derived withing ~ox-hugo~ and exported using the
~{#some-id}~ syntax.

The functions used for deriving the anchors can be customized using
the *~org-hugo-anchor-functions~* variable.

This variable is a list of functions which can return the anchor
string for a given Org heading element. The first function in the list
to return a non-nil value wins. So this scheme can be used to set a
precedence order that the user desires.

This is the default precedence order:

- ~org-hugo-get-custom-id~ :: Use the heading's ~:CUSTOM_ID~ property
if set, else return /nil/.
- ~org-hugo-get-heading-slug~ :: Derive anchor using the heading's
text. This could also return /nil/, if the heading has no
alphanumeric characters or no text (empty string).
- ~org-hugo-get-md5~ :: Derive anchor using the few 6 characters of
/md5/ based on the heading title text. It returns the /md5/ of an
empty string if nothing else works. That way, this function
guarantees to return some non-empty string.

Above precedence is set in the default value of
~org-hugo-anchor-functions~ --- ~'(org-hugo-get-custom-id
org-hugo-get-heading-slug org-hugo-get-md5)~.
**** Other anchor functions
- ~org-hugo-get-id~ :: This function returns the ID, if that property
is set for a heading.

If a user prefers to give higher precedence to Org ID than the
heading-derived-slug, they can customize ~org-hugo-anchor-functions~
to ~'(org-hugo-get-custom-id org-hugo-get-id
org-hugo-get-heading-slug org-hugo-get-md5)~.

Now if an Org heading looks like this:

#+begin_src org
,** Heading in a post
:PROPERTIES:
:ID: 74283e7c-a20b-1c22-af88-e41ff8055d17
:END:
#+end_src

, it will be exported as below in Markdown:

#+begin_src md
### Heading in a post {#74283e7c-a20b-1c22-af88-e41ff8055d17}
#+end_src
** Meta
:PROPERTIES:
:EXPORT_HUGO_MENU: :menu "7.meta"
Expand Down
104 changes: 82 additions & 22 deletions ox-hugo.el
Expand Up @@ -547,6 +547,36 @@ nil) plist were associated with them."
:group 'org-export-hugo
:type '(alist :key-type string :value-type (plist :key-type symbol :value-type boolean)))

(defcustom org-hugo-anchor-functions '(org-hugo-get-custom-id
org-hugo-get-heading-slug
org-hugo-get-md5)
"A list of functions for deriving the anchor of current Org heading.
The functions will be run in the order added to this variable
until the first one returns a non-nil value. So the functions in
this list are order-sensitive.
For example, if `org-hugo-get-custom-id' is the first element in
this list, the heading's `:CUSTOM_ID' property will have the
highest precedence in determining the heading's anchor string.
This variable is used in the `org-hugo--get-anchor' internal
function.
Functions added to this list should have 2 arguments (which could
even be declared as optional):
1. ELEMENT : Org element
2. INFO : General plist used as a communication channel
Some of the inbuilt functions that can be added to this list:
- `org-hugo-get-custom-id'
- `org-hugo-get-heading-slug'
- `org-hugo-get-md5'
- `org-hugo-get-id'"
:group 'org-export-hugo
:type '(repeat function))



;;; Define Back-End
Expand Down Expand Up @@ -1664,8 +1694,7 @@ a communication channel."
bullet " " heading tags-fmtd "\n\n"
(and contents (replace-regexp-in-string "^" " " contents)))))
(t
(let* ((anchor (format "{#%s}" ;https://gohugo.io/extras/crossreferences/
(org-hugo--get-anchor heading info)))
(let* ((anchor (format "{#%s}" (org-hugo--get-anchor heading info))) ;https://gohugo.io/extras/crossreferences/
(heading-title (org-hugo--heading-title style level loffset title
todo-fmtd tags-fmtd anchor numbers))
(wrap-element (org-hugo--container heading info))
Expand Down Expand Up @@ -1782,26 +1811,57 @@ The `slug' generated from that STR follows these rules:
(setq str (replace-regexp-in-string "--" "-" str)))
str))

(defun org-hugo--get-anchor(element info &optional title-str)
"Return an Org heading's anchor.
(defun org-hugo-get-custom-id(element &optional _info)
"Return ELEMENT's `:CUSTOM_ID' property.
Return nil if ELEMENT doesn't have the CUSTOM_ID property set."
(org-string-nw-p (org-element-property :CUSTOM_ID element)))

(defun org-hugo-get-id(&optional _element _info)
"Return the value of `:ID' property at point.
The anchor is derived in the following precedence:
Return nil if id is not found."
(org-id-get))

1. `:CUSTOM_ID' property of the ELEMENT if set
2. Optional TITLE-STR string argument to this function
3. `:title' property of the ELEMENT. If ELEMENT is an Org heading,
the `:title' will be the heading string.
(defun org-hugo-get-heading-slug(element info)
"Return the slug string derived from an Org heading ELEMENT.
The slug string is parsed from the ELEMENT's `:title' property.
INFO is a plist used as a communication channel.
If the anchor cannot be derived from any of the above, return
nil."
(or (org-element-property :CUSTOM_ID element)
(let ((title (or (org-string-nw-p title-str)
(org-export-data-with-backend
(org-element-property :title element) 'md info))))
(when (org-string-nw-p title)
(org-hugo-slug title :allow-double-hyphens)))))
Return nil if ELEMENT's `:title' property is nil or an empty string."
(let ((title (org-export-data-with-backend
(org-element-property :title element) 'md info)))
(org-string-nw-p (org-hugo-slug title :allow-double-hyphens))))

(defun org-hugo-get-md5(element info)
"Return md5 sum derived string using ELEMENT's title property.
INFO is a plist used as a communication channel.
This function will never return nil."
(let ((hash-len 6)
(title (or (org-string-nw-p (org-export-data-with-backend
(org-element-property :title element) 'md info))
"")))
(substring (md5 title) 0 hash-len)))

(defun org-hugo--get-anchor(element info)
"Return anchor string for Org heading ELEMENT.
The anchor is derived using the first function that returns a
non-nil value (a string) from the list
`org-hugo-anchor-functions'.
INFO is a plist used as a communication channel.
Return an empty string if all functions in
`org-hugo-anchor-functions' return nil."
(or (seq-some
(lambda (fn) (funcall fn element info))
org-hugo-anchor-functions)
""))

(defun org-hugo--heading-title (style level loffset title &optional todo tags anchor numbers)
"Generate a heading title in the preferred Markdown heading style.
Expand Down Expand Up @@ -1918,7 +1978,7 @@ channel."
(defun org-hugo--get-coderef-anchor-prefix (el)
"Get anchor prefix string for code refs in element EL.
Returns a cons (CODE-REFS . ANCHOR-PREFIX) where
Return a cons (CODE-REFS . ANCHOR-PREFIX) where
- CODE-REFS is an alist of the type (LINENUM . LABEL) where
Expand All @@ -1927,7 +1987,7 @@ Returns a cons (CODE-REFS . ANCHOR-PREFIX) where
- ANCHOR-PREFIX is a string.
Returns nil if EL has no code references."
Return nil if EL has no code references."
(let ((prefix "org-coderef")
(hash-len 6)
(code-refs (cdr (org-export-unravel-code el))))
Expand All @@ -1945,7 +2005,7 @@ This function is heavily derived from
INFO is a plist used as a communication channel.
Returns a plist with these elements:
Return a plist with these elements:
- `:line-num' :: REF associated line number
Expand Down Expand Up @@ -2050,7 +2110,7 @@ and rewrite link paths to make blogging more seamless."
(t
title))
;; Reference
(org-hugo--get-anchor destination info title))))
(org-hugo--get-anchor destination info))))
;; Links to other Org elements like source blocks, tables,
;; paragraphs, standalone figures, <<target>> links, etc.
(_
Expand Down Expand Up @@ -2544,7 +2604,7 @@ INFO is a plist used as a communication channel."
- Add \"&nbsp;\" HTML entity before footnote anchors so that the
anchors won't be on a separate line by themselves.
Returns the processed CONTENTS string from the PARAGRAPH element.
Return the processed CONTENTS string from the PARAGRAPH element.
INFO is a plist used as a communication channel."
(let ((ret contents))
;; Join consecutive Chinese, Japanese lines into a single long
Expand Down
4 changes: 2 additions & 2 deletions test/site/content/posts/equations-bf-escaping.md
Expand Up @@ -9,7 +9,7 @@ draft = false
`ox-hugo` Issue #[138](https://github.com/kaushalmodi/ox-hugo/issues/138)


## `\|``\\|` {#}
## `\|``\\|` {#9d819a}

\\[
C(w,b) = \frac{1}{2n} \sum\_x{{\\|y(x)-a\\|}^2}
Expand Down Expand Up @@ -40,7 +40,7 @@ y &= a^L
\end{align}


## `\{``\\{`, `\}``\\}` {#}
## `\{``\\{`, `\}``\\}` {#3d16e4}

`ox-hugo` Issue #[258](https://github.com/kaushalmodi/ox-hugo/issues/258)

Expand Down

0 comments on commit be9d24d

Please sign in to comment.