Convert internal links for subtree exports

Fixes https://github.com/kaushalmodi/ox-hugo/issues/30.

- Locate correct subtree to be exported
- Made CUSTOM_ID references unique
- Updated .md files with unique CUSTOM_ID references
- Workaround to prevent exporting of empty special blocks
- Mark preprocessed buffer as unmodified
- Support crosspost custom-id, id and fuzzy link types
- Update test site with cross-post links
- Require 'org-id in setup-ox-hugo.el to handle ID links
- Add instruction to handle ID links to test site
- Kill the preprocessed buffer after exporting
- Fix check for non-nil destination-filename
- Support preprocessing of cross-post "fuzzy" and "id" type links
- Support section paths in the preprocessing buffer
- Retrieve #+HUGO_SECTION keyword from in-buffer settings
master
Folkert van der Beek 7 years ago committed by Kaushal Modi
parent 872c641282
commit ed4f18066c
  1. 136
      ox-hugo.el
  2. 1
      test/setup-ox-hugo.el
  3. 67
      test/site/content-org/all-posts.org
  4. 4
      test/site/content/posts/citations-example-toml.md
  5. 4
      test/site/content/posts/citations-example-yaml.md
  6. 9
      test/site/content/posts/link-destination.md
  7. 65
      test/site/content/posts/links-outside-the-same-post.md

@ -3790,6 +3790,117 @@ exporting all valid Hugo post subtrees from the current Org file.
(message "Point is not in a valid Hugo post subtree; move to one and try again"))
valid-subtree-found))))
(defun org-hugo--get-element-path (element info)
"Return the section path of ELEMENT.
INFO is a plist holding export options."
(let ((root (or (org-export-get-node-property :EXPORT_HUGO_SECTION element t)
(plist-get info :hugo-section)))
(filename (org-export-get-node-property :EXPORT_FILE_NAME element t))
current-element
fragment
fragments)
(setq current-element element)
;; Iterate over all parents of current-element, and collect section path fragments
(while (and current-element (not (org-export-get-node-property :EXPORT_HUGO_SECTION current-element nil)))
;; Add the :EXPORT_HUGO_SECTION* value to the fragment list
(when (setq fragment (org-export-get-node-property :EXPORT_HUGO_SECTION* current-element nil))
(push fragment fragments))
(setq current-element (org-element-property :parent current-element)))
;; Return the root section, section fragments and filename concatenated
(concat
(file-name-as-directory root)
(mapconcat #'file-name-as-directory fragments "")
filename)))
(defun org-hugo--preprocess-buffer ()
"Return a preprocessed copy of the current buffer.
Internal links to other subtrees are converted to external
links."
(let* ((buffer (generate-new-buffer (buffer-name)))
;; Create an abstract syntax tree (AST) of the org document in the current buffer
(ast (org-element-parse-buffer))
(org-use-property-inheritance (org-hugo--selective-property-inheritance))
(info (org-combine-plists
(list :parse-tree ast)
(org-export--get-export-attributes 'hugo)
(org-export--get-buffer-attributes)
(org-export-get-environment 'hugo)))
(local-variables (buffer-local-variables))
(bound-variables (org-export--list-bound-variables))
vars)
(with-current-buffer buffer
(let ((inhibit-modification-hooks t)
(org-mode-hook nil)
(org-inhibit-startup t))
(org-mode)
;; Copy specific buffer local variables and variables set
;; through BIND keywords.
(dolist (entry local-variables vars)
(when (consp entry)
(let ((var (car entry))
(val (cdr entry)))
(and (not (memq var org-export-ignored-local-variables))
(or (memq var
'(default-directory
buffer-file-name
buffer-file-coding-system))
(assq var bound-variables)
(string-match "^\\(org-\\|orgtbl-\\)"
(symbol-name var)))
;; Skip unreadable values, as they cannot be
;; sent to external process.
(or (not val) (ignore-errors (read (format "%S" val))))
(push (set (make-local-variable var) val) vars)))))
;; Process all link elements in the AST
(org-element-map ast 'link
(lambda (link)
(let ((type (org-element-property :type link)))
(when (member type '("custom-id" "id" "fuzzy"))
(let* ((raw-link (org-element-property :raw-link link))
(destination (if (string= type "fuzzy")
(org-export-resolve-fuzzy-link link info)
(org-export-resolve-id-link link info)))
(source-path (org-hugo--get-element-path link info))
(destination-path (org-hugo--get-element-path destination info)))
;; Change the link if it points to a valid destination outside the subtree
(unless (equal source-path destination-path)
(let ((link-copy (org-element-copy link)))
(apply #'org-element-adopt-elements link-copy (org-element-contents link))
(org-element-put-property link-copy :type "file")
(org-element-put-property link-copy :path
(cond
;; If the destination is a heading with the :EXPORT_FILE_NAME
;; property defined, the link should point to the file (without
;; anchor)
((org-element-property :EXPORT_FILE_NAME destination)
(concat destination-path ".org"))
;; Hugo only supports anchors to headlines, so if a "fuzzy" type
;; link points to anything else than a headline, it should point
;; to the file.
((and (string= type "fuzzy") (not (string-prefix-p "*" raw-link)))
(concat destination-path ".org"))
;; In "custom-id" type links, the raw-link matches the anchor of
;; the destination.
((string= type "custom-id")
(concat destination-path ".org::" raw-link))
;; In "id" and "fuzzy" type links, the anchor of the destination
;; is derived from the :CUSTOM_ID property or the title
(t
(let* ((custom-id (org-element-property :CUSTOM_ID destination))
(title (org-element-property :raw-value destination))
(anchor (or custom-id (org-hugo-slug title))))
(concat destination-path ".org::#" anchor)))))
(org-element-set-element link link-copy))))))))
;; Workaround to prevent exporting of empty special blocks
(org-element-map ast 'special-block
(lambda (block)
(when (null (org-element-contents block))
(org-element-adopt-elements block ""))))
;; Turn the AST with updated links into an org document
(insert (org-element-interpret-data ast))
(set-buffer-modified-p nil)))
buffer))
;;; Interactive functions
@ -3923,13 +4034,17 @@ The optional argument NOERROR is passed to
(if all-subtrees
(progn
(setq org-hugo--subtree-count 0) ;Reset the subtree count
(setq ret (org-map-entries
(lambda ()
(org-hugo--export-subtree-to-md async visible-only :print-subtree-count))
;; Export only the subtrees where
;; EXPORT_FILE_NAME property is not
;; empty.
"EXPORT_FILE_NAME<>\"\""))
(let ((buffer (org-hugo--preprocess-buffer)))
(with-current-buffer buffer
(setq ret (org-map-entries
(lambda ()
(org-hugo--export-subtree-to-md async visible-only
:print-subtree-count))
;; Export only the subtrees where
;; EXPORT_FILE_NAME property is not
;; empty.
"EXPORT_FILE_NAME<>\"\""))
(kill-buffer buffer)))
(if ret
(message "[ox-hugo] Exported %d subtree%s from %s"
org-hugo--subtree-count
@ -3942,7 +4057,12 @@ The optional argument NOERROR is passed to
(setq ret (org-hugo--export-file-to-md f-or-b-name async visible-only noerror))))
;; Publish only the current subtree.
(setq ret (org-hugo--export-subtree-to-md async visible-only))
(let ((buffer (org-hugo--preprocess-buffer))
(outline-path (org-get-outline-path t)))
(with-current-buffer buffer
(goto-char (org-find-olp outline-path t))
(setq ret (org-hugo--export-subtree-to-md async visible-only)))
(kill-buffer buffer))
(unless ret
(setq ret (org-hugo--export-file-to-md f-or-b-name async visible-only noerror)))))))
ret))

@ -197,6 +197,7 @@ Emacs installation. If Emacs is installed using
;; (message (list-load-path-shadows :stringp))
)
(require 'org-id)
(require 'ox-hugo)
(defun org-hugo-export-all-wim-to-md ()
(org-hugo-export-wim-to-md :all-subtrees nil nil :noerror))

@ -3020,6 +3020,65 @@ C = W\log_{2} (1+\mathrm{SNR})
\end{equation}
*Here we refer to equation \ref{eq:1}.*
** Links outside the same post
:PROPERTIES:
:EXPORT_FILE_NAME: links-outside-the-same-post
:END:
{{{oxhugoissue(30)}}}
*** External links with search options :external_links:
Links between documents can contain some search options. Only links
to a heading with a *:CUSTOM_ID* property will be resolved to the
appropriate location in the linked file. Links to headlines and
links to targets will be resolved to the containing file.
[[file:link-destination.org::#external-target][Link to CUSTOM_ID]]
[[file:link-destination.org::*external-target][Link to a headline]]
[[file:link-destination.org::external-target][Link to a target]]
*** Internal links :internal_links:
Internal links point to targets in the current subtree that will be
exported to the same Hugo post as the link source. To handle links to
an *:ID* property, the =org-id= feature must first be loaded, either
through =org-customize= or by adding =(require 'org-id)= in your Emacs
init file.
[[#internal-target][Link to CUSTOM_ID within the same post]]
[[id:8e65ff86-3f9a-48ef-9b43-751a2e8a9372][Link to ID within the same post]]
[[*Internal target][Link to headline within the same post]]
[[internal-target][Link to target within the same post]]
*** Cross-post links :crosspost_links:
Cross-post links are internal links pointing to targets in a different
subtree that will be exported to another Hugo post than the link
source in subtree-based exports. The Hugo's `ref` and `relref`
shortcodes only supports anchors to headlines, so links to a heading,
a *:CUSTOM_ID* property, or an *:ID* property will be resolved to the
appropriate location in the linked file, but links to targets will be
resolved to the containing post.
[[#external-target][Link to CUSTOM_ID outside the same post]]
[[id:de0df718-f9b4-4449-bb0a-eb4402fa5fcb][Link to ID outside the same post]]
[[*External target][Link to headline outside current post]]
[[external-target][Link to target outside the same post]]
[[#link-destination][Link to subtree by CUSTOM_ID]]
[[id:1e5e0bcd-caea-40ad-a75b-e488634c2678][Link to subtree by ID]]
[[*Link destination][Link to subtree by headline]]
*** Internal target
:PROPERTIES:
:CUSTOM_ID: internal-target
:ID: 8e65ff86-3f9a-48ef-9b43-751a2e8a9372
:END:
<<internal-target>>
*** Link destination
:PROPERTIES:
:CUSTOM_ID: link-destination
:EXPORT_FILE_NAME: link-destination
:ID: 1e5e0bcd-caea-40ad-a75b-e488634c2678
:END:
**** External target
:PROPERTIES:
:CUSTOM_ID: external-target
:ID: de0df718-f9b4-4449-bb0a-eb4402fa5fcb
:END:
<<external-target>>
* Equations :equations:mathjax:
** Inline equations
:PROPERTIES:
@ -4860,10 +4919,10 @@ Here is a test example file with an in-text citation where someone
important says something important (e.g. @loncar2016). And here is
another bit of blah with a footnote citation.[fn:5]
See [[#citation-example-section-2]].
See [[#citation-example-toml-section-2]].
*** Section 2
:PROPERTIES:
:CUSTOM_ID: citation-example-section-2
:CUSTOM_ID: citation-example-toml-section-2
:END:
Content in section 2.
*** Testing random Hugo shortcode
@ -4927,10 +4986,10 @@ Here is a test example file with an in-text citation where someone
important says something important (e.g. @loncar2016). And here is
another bit of blah with a footnote citation.[fn:5]
See [[#citation-example-section-2]].
See [[#citation-example-yaml-section-2]].
*** Section 2
:PROPERTIES:
:CUSTOM_ID: citation-example-section-2
:CUSTOM_ID: citation-example-yaml-section-2
:END:
Content in section 2.
** Citation Linking :link_citations:

@ -17,9 +17,9 @@ Here is a test example file with an in-text citation where someone
important says something important (e.g. Loncar (2016)). And here is
another bit of blah with a footnote citation.[^fn:1]
See [Section 2](#citation-example-section-2).
See [Section 2](#citation-example-toml-section-2).
## Section 2 {#citation-example-section-2}
## Section 2 {#citation-example-toml-section-2}
Content in section 2.

@ -16,9 +16,9 @@ Here is a test example file with an in-text citation where someone
important says something important (e.g. Loncar (2016)). And here is
another bit of blah with a footnote citation.[^fn:1]
See [Section 2](#citation-example-section-2).
See [Section 2](#citation-example-yaml-section-2).
## Section 2 {#citation-example-section-2}
## Section 2 {#citation-example-yaml-section-2}
Content in section 2.

@ -0,0 +1,9 @@
+++
title = "Link destination"
tags = ["links"]
draft = false
+++
## External target {#external-target}
<a id="org9e3fe16"></a>

@ -0,0 +1,65 @@
+++
title = "Links outside the same post"
tags = ["links"]
draft = false
+++
`ox-hugo` Issue #[30](https://github.com/kaushalmodi/ox-hugo/issues/30)
## External links with search options {#external-links-with-search-options}
Links between documents can contain some search options. Only links
to a heading with a **:CUSTOM\_ID** property will be resolved to the
appropriate location in the linked file. Links to headlines and
links to targets will be resolved to the containing file.
[Link to CUSTOM\_ID]({{< relref "link-destination#external-target" >}})
[Link to a headline]({{< relref "link-destination" >}})
[Link to a target]({{< relref "link-destination" >}})
## Internal links {#internal-links}
Internal links point to targets in the current subtree that will be
exported to the same Hugo post as the link source. To handle links to
an **:ID** property, the `org-id` feature must first be loaded, either
through `org-customize` or by adding `(require 'org-id)` in your Emacs
init file.
[Link to CUSTOM\_ID within the same post](#internal-target)
[Link to ID within the same post](#internal-target)
[Link to headline within the same post](#internal-target)
[Link to target within the same post](#org2549435)
## Cross-post links {#cross-post-links}
Cross-post links are internal links pointing to targets in a different
subtree that will be exported to another Hugo post than the link
source in subtree-based exports. The Hugo's \`ref\` and \`relref\`
shortcodes only supports anchors to headlines, so links to a heading,
a **:CUSTOM\_ID** property, or an **:ID** property will be resolved to the
appropriate location in the linked file, but links to targets will be
resolved to the containing post.
[Link to CUSTOM\_ID outside the same post]({{< relref "link-destination#external-target" >}})
[Link to ID outside the same post]({{< relref "link-destination#external-target" >}})
[Link to headline outside current post]({{< relref "link-destination#external-target" >}})
[Link to target outside the same post]({{< relref "link-destination" >}})
[Link to subtree by CUSTOM\_ID]({{< relref "link-destination" >}})
[Link to subtree by ID]({{< relref "link-destination" >}})
[Link to subtree by headline]({{< relref "link-destination" >}})
## Internal target {#internal-target}
<a id="org2549435"></a>
## Link destination {#link-destination}
### External target {#external-target}
<a id="org4f91ca4"></a>
Loading…
Cancel
Save