|
|
;;; ox-hugo.el --- Hugo Markdown Back-End for Org Export Engine -*- lexical-binding: t -*- |
|
|
|
|
|
;; Authors: Kaushal Modi <kaushal.modi@gmail.com> |
|
|
;; Matt Price <moptop99@gmail.com> |
|
|
;; URL: https://ox-hugo.scripter.co |
|
|
;; Package-Requires: ((emacs "24.4") (org "9.0")) |
|
|
;; Keywords: Org, markdown, docs |
|
|
;; Version: 0.7 |
|
|
|
|
|
;;; Commentary: |
|
|
|
|
|
;; ox-hugo implements a Markdown back-end for Org exporter. The |
|
|
;; exported Markdown is compatible with the Hugo static site generator |
|
|
;; (https://gohugo.io/). This exporter also generates the post |
|
|
;; front-matter in TOML or YAML. |
|
|
|
|
|
;; To start using this exporter, add the below to your Emacs config: |
|
|
;; |
|
|
;; (with-eval-after-load 'ox |
|
|
;; (require 'ox-hugo)) |
|
|
;; |
|
|
;; With the above evaluated, the ox-hugo exporter options will be |
|
|
;; available in the Org Export Dispatcher. The ox-hugo export |
|
|
;; commands have bindings beginning with "H" (for Hugo). |
|
|
;; |
|
|
;; # Blogging Flows |
|
|
;; |
|
|
;; 1. one-post-per-subtree flow :: A single Org file can have multiple |
|
|
;; Org subtrees which export to individual Hugo posts. Each of |
|
|
;; those subtrees that has the EXPORT_FILE_NAME property set is |
|
|
;; called a 'valid Hugo post subtree' in this package and its |
|
|
;; documentation. |
|
|
;; |
|
|
;; 2. one-post-per-file flow :: A single Org file exports to only |
|
|
;; *one* Hugo post. An Org file intended to be exported by this |
|
|
;; flow must not have any 'valid Hugo post subtrees', and instead |
|
|
;; must have the #+title property set. |
|
|
;; |
|
|
;; # Commonly used export commands |
|
|
;; |
|
|
;; ## For both one-post-per-subtree and one-post-per-file flows |
|
|
;; |
|
|
;; - C-c C-e H H -> Export "What I Mean". |
|
|
;; - If point is in a 'valid Hugo post subtree', |
|
|
;; export that subtree to a Hugo post in |
|
|
;; Markdown. |
|
|
;; - If the file is intended to be exported as a |
|
|
;; whole (i.e. has the #+title keyword), |
|
|
;; export the whole Org file to a Hugo post in |
|
|
;; Markdown. |
|
|
;; |
|
|
;; - C-c C-e H A -> Export *all* "What I Mean" |
|
|
;; - If the Org file has one or more 'valid Hugo |
|
|
;; post subtrees', export them to Hugo posts in |
|
|
;; Markdown. |
|
|
;; - If the file is intended to be exported as a |
|
|
;; whole (i.e. no 'valid Hugo post subtrees' |
|
|
;; at all, and has the #+title keyword), |
|
|
;; export the whole Org file to a Hugo post in |
|
|
;; Markdown. |
|
|
;; |
|
|
;; ## For only the one-post-per-file flow |
|
|
;; |
|
|
;; - C-c C-e H h -> Export the Org file to a Hugo post in Markdown. |
|
|
|
|
|
;; Do M-x customize-group, and select `org-export-hugo' to see the |
|
|
;; available customization options for this package. |
|
|
|
|
|
;; See this package's website for more instructions and examples: |
|
|
;; |
|
|
;; https://ox-hugo.scripter.co |
|
|
|
|
|
;;; Code: |
|
|
|
|
|
(require 'ox-blackfriday) |
|
|
(require 'ffap) ;For `ffap-url-regexp' |
|
|
(require 'ob-core) ;For `org-babel-parse-header-arguments' |
|
|
;; `org-refile.el' is new in Org 9.4 |
|
|
;; https://code.orgmode.org/bzg/org-mode/commit/f636cf91b6cbe322eca56e23283f4614548c9d65 |
|
|
(require 'org-refile nil :noerror) ;For `org-get-outline-path' |
|
|
(declare-function org-hugo-pandoc-cite--parse-citations-maybe "ox-hugo-pandoc-cite") |
|
|
|
|
|
(defvar ffap-url-regexp) ;Silence byte-compiler |
|
|
|
|
|
|
|
|
;; Using the correct function for getting inherited Org tags. |
|
|
;; Starting Org 9.2, `org-get-tags' returns all the inherited tags |
|
|
;; instead of returning only the local tags i.e. only the current |
|
|
;; headline tags. |
|
|
;; https://code.orgmode.org/bzg/org-mode/commit/fbe56f89f75a8979e0ba48001a822518df2c66fe |
|
|
|
|
|
;; For Org <= 9.1, `org-get-tags' returned a list of tags *only* at |
|
|
;; the current heading, while `org-get-tags-at' returned inherited |
|
|
;; tags too. |
|
|
(with-no-warnings |
|
|
(if (fboundp #'org--get-local-tags) ;If using Org 9.2+ |
|
|
(defalias 'org-hugo--get-tags 'org-get-tags) |
|
|
(defalias 'org-hugo--get-tags 'org-get-tags-at))) |
|
|
|
|
|
(defvar org-hugo--subtree-coord nil |
|
|
"Variable to store the current valid Hugo subtree coordinates. |
|
|
It holds the value returned by |
|
|
`org-hugo--get-post-subtree-coordinates'.") |
|
|
|
|
|
(defvar org-hugo--subtree-count 0 |
|
|
"Variable to count of number of subtrees getting exported. |
|
|
This variable is used when exporting all subtrees in a file.") |
|
|
|
|
|
(defvar org-hugo--fm nil |
|
|
"Variable to store the current Hugo post's front-matter string. |
|
|
|
|
|
This variable is used to cache the original ox-hugo generated |
|
|
front-matter that's used after Pandoc Citation parsing.") |
|
|
|
|
|
(defvar org-hugo--fm-yaml nil |
|
|
"Variable to store the current Hugo post's front-matter string in YAML format. |
|
|
|
|
|
Pandoc understands meta-data only in YAML format. So when Pandoc |
|
|
Citations are enabled, Pandoc is handed over the file with this |
|
|
YAML front-matter.") |
|
|
|
|
|
(defvar org-hugo-blackfriday-options |
|
|
'("taskLists" |
|
|
"smartypants" |
|
|
"smartypantsQuotesNBSP" |
|
|
"angledQuotes" |
|
|
"fractions" |
|
|
"smartDashes" |
|
|
"latexDashes" |
|
|
"hrefTargetBlank" |
|
|
"plainIDAnchors" |
|
|
"extensions" |
|
|
"extensionsmask") |
|
|
"Blackfriday option names as used inside Hugo. |
|
|
Note that these names are case-sensitive. |
|
|
|
|
|
This is a list of strings. |
|
|
|
|
|
Stable Hugo version reference: |
|
|
https://gohugo.io/content-management/formats/#blackfriday-options |
|
|
|
|
|
Development Hugo version reference: |
|
|
https://github.com/gohugoio/hugo/blob/master/docs/content/readfiles/bfconfig.md |
|
|
|
|
|
taskLists |
|
|
- default: `true' |
|
|
- Purpose: `false' turns off GitHub-style automatic task/TODO list |
|
|
generation. |
|
|
|
|
|
smartypants |
|
|
- default: `true' |
|
|
- Purpose: `false' disables smart punctuation substitutions, including |
|
|
smart quotes, smart dashes, smart fractions, etc. If |
|
|
`true', it may be fine-tuned with the `angledQuotes', |
|
|
`fractions', `smartDashes', and `latexDashes' flags. |
|
|
|
|
|
smartypantsQuotesNBSP |
|
|
- default: `false' |
|
|
- Purpose: `true' enables French style Guillemets with non-breaking |
|
|
space inside the quotes. |
|
|
|
|
|
angledQuotes |
|
|
- default: `false' |
|
|
- Purpose: `true' enables smart, angled double quotes. |
|
|
Example: \"Hugo\" renders to «Hugo» instead of “Hugo”. |
|
|
|
|
|
fractions |
|
|
- default: `true' |
|
|
- Purpose: `false' disables smart fractions. |
|
|
- Example: 5/12 renders to 5⁄12(<sup>5</sup>⁄<sub>12</sub>). |
|
|
- Caveat: Even with \"fractions = false\", Blackfriday still converts |
|
|
1/2, 1/4, and 3/4 respectively to ½ (½), ¼ |
|
|
(¼) and ¾ (¾), but only these three. |
|
|
|
|
|
smartDashes |
|
|
- default: `true' |
|
|
- Purpose: `false' disables smart dashes; i.e., the conversion of |
|
|
multiple hyphens into an en-dash or em-dash. If `true', |
|
|
its behavior can be modified with the `latexDashes' flag. |
|
|
|
|
|
latexDashes |
|
|
- default: `true' |
|
|
- Purpose: `false' disables LaTeX-style smart dashes and selects |
|
|
conventional smart dashes. Assuming `smartDashes': If |
|
|
`true', -- is translated into – (–), whereas --- |
|
|
is translated into — (—). However, spaced single |
|
|
hyphen between two words is translated into an en dash |
|
|
e.g., \"12 June - 3 July\" becomes \"12 June – 3 |
|
|
July\" upon rendering. |
|
|
|
|
|
hrefTargetBlank |
|
|
- default: `false' |
|
|
- Purpose: `true' opens external links in a new window or tab. |
|
|
|
|
|
plainIDAnchors |
|
|
- default: `true' |
|
|
- Purpose: `true' renders any heading and footnote IDs without the |
|
|
document ID. |
|
|
- Example: renders \"#my-heading\" instead of |
|
|
\"#my-heading:bec3ed8ba720b970\". |
|
|
|
|
|
extensions |
|
|
- default: [] |
|
|
- Purpose: Enable one or more Blackfriday's Markdown extensions (if |
|
|
they aren't Hugo defaults). |
|
|
- Example: Include `hardLineBreak' in the list to enable Blackfriday's |
|
|
EXTENSION_HARD_LINE_BREAK. |
|
|
|
|
|
extensionsmask |
|
|
- default: [] |
|
|
- Purpose: Enable one or more of Blackfriday's Markdown extensions (if |
|
|
they aren't Hugo defaults). Example: Include `autoHeaderIds' |
|
|
as `false' in the list to disable Blackfriday's |
|
|
EXTENSION_AUTO_HEADER_IDS |
|
|
|
|
|
See `org-hugo-blackfriday-extensions' for valid Blackfriday |
|
|
extensions.") |
|
|
|
|
|
(defvar org-hugo-blackfriday-extensions |
|
|
'("noIntraEmphasis" |
|
|
"tables" |
|
|
"fencedCode" |
|
|
"autolink" |
|
|
"strikethrough" |
|
|
"laxHtmlBlocks" |
|
|
"spaceHeaders" |
|
|
"hardLineBreak" |
|
|
"tabSizeEight" |
|
|
"footnotes" |
|
|
"noEmptyLineBeforeBlock" |
|
|
"headerIds" |
|
|
"titleblock" |
|
|
"autoHeaderIds" |
|
|
"backslashLineBreak" |
|
|
"definitionLists" |
|
|
"joinLines") |
|
|
"Blackfriday extension names as used inside Hugo. |
|
|
Note that these names are case-sensitive. |
|
|
|
|
|
This is a list of strings. |
|
|
|
|
|
Stable Hugo version reference: |
|
|
https://gohugo.io/content-management/formats/#blackfriday-extensions |
|
|
|
|
|
Development Hugo version references: |
|
|
https://github.com/gohugoio/hugo/blob/master/docs/content/readfiles/bfconfig.md |
|
|
https://github.com/russross/blackfriday#extensions |
|
|
https://github.com/russross/blackfriday/blob/master/markdown.go |
|
|
https://github.com/gohugoio/hugo/blob/master/helpers/content.go |
|
|
|
|
|
noIntraEmphasis |
|
|
- default: enabled |
|
|
- Purpose: The \"_\" character is commonly used inside words when |
|
|
discussing code, so having Markdown interpret it as an |
|
|
emphasis command is usually the wrong thing. When enabled, |
|
|
Blackfriday lets you treat all emphasis markers as normal |
|
|
characters when they occur inside a word. |
|
|
|
|
|
tables |
|
|
- default: enabled |
|
|
- Purpose: When enabled, tables can be created by drawing them in the |
|
|
input using the below syntax: |
|
|
- Example: |
|
|
Name | Age |
|
|
--------|------ |
|
|
Bob | 27 |
|
|
Alice | 23 |
|
|
|
|
|
fencedCode |
|
|
- default: enabled |
|
|
- Purpose: When enabled, in addition to the normal 4-space indentation |
|
|
to mark code blocks, you can explicitly mark them and |
|
|
supply a language (to make syntax highlighting simple). |
|
|
|
|
|
You can use 3 or more backticks to mark the beginning of |
|
|
the block, and the same number to mark the end of the |
|
|
block. |
|
|
- Example: |
|
|
```emacs-lisp |
|
|
(message \"Hello\") |
|
|
``` |
|
|
|
|
|
autolink |
|
|
- default: enabled |
|
|
- Purpose: When enabled, URLs that have not been explicitly marked as |
|
|
links will be converted into links. |
|
|
|
|
|
strikethrough |
|
|
- default: enabled |
|
|
- Purpose: When enabled, text wrapped with two tildes will be crossed |
|
|
out. |
|
|
- Example: ~~crossed-out~~ |
|
|
|
|
|
laxHtmlBlocks |
|
|
- default: disabled |
|
|
- Purpose: When enabled, loosen up HTML block parsing rules. |
|
|
«Needs more information» |
|
|
|
|
|
spaceHeaders |
|
|
- default: enabled |
|
|
- Purpose: When enabled, be strict about prefix header rules. |
|
|
«Needs more information» |
|
|
|
|
|
hardLineBreak |
|
|
- default: disabled |
|
|
- Purpose: When enabled, newlines in the input translate into line |
|
|
breaks in the output, like in Org verse blocks. |
|
|
|
|
|
tabSizeEight |
|
|
- default: disabled |
|
|
- Purpose: When enabled, expand tabs to eight spaces instead of four. |
|
|
|
|
|
footnotes |
|
|
- default: enabled |
|
|
- Purpose: When enabled, Pandoc-style footnotes will be supported. |
|
|
The footnote marker in the text that will become a |
|
|
superscript text; the footnote definition will be placed in |
|
|
a list of footnotes at the end of the document. |
|
|
- Example: |
|
|
This is a footnote.[^1] |
|
|
|
|
|
[^1]: the footnote text. |
|
|
|
|
|
noEmptyLineBeforeBlock |
|
|
- default: disabled |
|
|
- Purpose: When enabled, no need to insert an empty line to start a |
|
|
(code, quote, ordered list, unordered list) block. |
|
|
|
|
|
headerIds |
|
|
- default: enabled |
|
|
- Purpose: When enabled, allow specifying header IDs with {#id}. |
|
|
|
|
|
titleblock |
|
|
- default: disabled |
|
|
- Purpose: When enabled, support Pandoc-style title blocks. |
|
|
http://pandoc.org/MANUAL.html#extension-pandoc_title_block |
|
|
|
|
|
autoHeaderIds |
|
|
- default: enabled |
|
|
- Purpose: When enabled, auto-create the header ID's from the headline |
|
|
text. |
|
|
|
|
|
backslashLineBreak |
|
|
- default: enabled |
|
|
- Purpose: When enabled, translate trailing backslashes into line |
|
|
breaks. |
|
|
|
|
|
definitionLists |
|
|
- default: enabled |
|
|
- Purpose: When enabled, a simple definition list is made of a |
|
|
single-line term followed by a colon and the definition for |
|
|
that term. |
|
|
- Example: |
|
|
Cat |
|
|
: Fluffy animal everyone likes |
|
|
|
|
|
Internet |
|
|
: Vector of transmission for pictures of cats |
|
|
|
|
|
Terms must be separated from the previous definition by a |
|
|
blank line. |
|
|
|
|
|
joinLines |
|
|
- default: enabled |
|
|
- Purpose: When enabled, delete newlines and join the lines. This |
|
|
behavior is similar to the default behavior in Org.") |
|
|
|
|
|
(defvar org-hugo--internal-list-separator "\n" |
|
|
"String used to separate elements in list variables. |
|
|
|
|
|
Examples are internal variables holding Hugo tags, categories and |
|
|
keywords. |
|
|
|
|
|
This variable is for internal use only, and must not be |
|
|
modified.") |
|
|
|
|
|
(defvar org-hugo--date-time-regexp (concat "\\`[[:digit:]]\\{4\\}-[[:digit:]]\\{2\\}-[[:digit:]]\\{2\\}" |
|
|
"\\(?:T[[:digit:]]\\{2\\}:[[:digit:]]\\{2\\}:[[:digit:]]\\{2\\}" |
|
|
"\\(?:Z\\|[+-][[:digit:]]\\{2\\}:[[:digit:]]\\{2\\}\\)*\\)*\\'") |
|
|
"Regexp to match the Hugo time stamp strings. |
|
|
|
|
|
Reference: https://tools.ietf.org/html/rfc3339#section-5.8 |
|
|
|
|
|
Examples: |
|
|
2017-07-31 |
|
|
2017-07-31T17:05:38 |
|
|
2017-07-31T17:05:38Z |
|
|
2017-07-31T17:05:38+04:00 |
|
|
2017-07-31T17:05:38-04:00.") |
|
|
|
|
|
|
|
|
|
|
|
;;; User-Configurable Variables |
|
|
|
|
|
(defgroup org-export-hugo nil |
|
|
"Options for exporting Org mode files to Hugo-compatible Markdown." |
|
|
:tag "Org Export Hugo" |
|
|
:group 'org-export |
|
|
:version "25.2") |
|
|
|
|
|
(define-obsolete-variable-alias 'org-hugo-default-section-directory 'org-hugo-section "Oct 31, 2018") |
|
|
(defcustom org-hugo-section "posts" |
|
|
"Default section for Hugo posts. |
|
|
|
|
|
This variable is the name of the directory under the \"content/\" |
|
|
directory where all Hugo posts should go by default." |
|
|
:group 'org-export-hugo |
|
|
:type 'directory) |
|
|
;;;###autoload (put 'org-hugo-section 'safe-local-variable 'stringp) |
|
|
|
|
|
(defcustom org-hugo-front-matter-format "toml" |
|
|
"Front-matter format. |
|
|
This variable can be set to either \"toml\" or \"yaml\"." |
|
|
:group 'org-export-hugo |
|
|
:type '(choice |
|
|
(const :tag "TOML" "toml") |
|
|
(const :tag "YAML" "yaml"))) |
|
|
;;;###autoload (put 'org-hugo-front-matter-format 'safe-local-variable 'stringp) |
|
|
|
|
|
(defcustom org-hugo-footer "" |
|
|
"String to be appended at the end of each Hugo post. |
|
|
|
|
|
The string needs to be in a Hugo-compatible Markdown format or HTML." |
|
|
:group 'org-export-hugo |
|
|
:type 'string) |
|
|
;;;###autoload (put 'org-hugo-footer 'safe-local-variable 'stringp) |
|
|
|
|
|
(defcustom org-hugo-preserve-filling t |
|
|
"When non-nil, text filling done in Org will be retained in Markdown." |
|
|
:group 'org-export-hugo |
|
|
:type 'boolean) |
|
|
;;;###autoload (put 'org-hugo-preserve-filling 'safe-local-variable 'booleanp) |
|
|
|
|
|
(defcustom org-hugo-delete-trailing-ws t |
|
|
"When non-nil, delete trailing whitespace in Markdown output. |
|
|
Trailing empty lines at the end of the Markdown output are also deleted. |
|
|
|
|
|
One might want to set this variable to nil if they want to |
|
|
preserve the trailing whitespaces in Markdown for the purpose of |
|
|
forcing line-breaks. |
|
|
|
|
|
The trailing whitespace deleting is skipped if |
|
|
`org-export-preserve-breaks' is set to non-nil; either via that |
|
|
variable or via the OPTIONS keyword \"\\n:t\" (See (org) Export |
|
|
settings). |
|
|
|
|
|
\(In below Markdown, underscores are used to represent spaces.) |
|
|
|
|
|
abc__ |
|
|
def__ |
|
|
|
|
|
Those trailing whitespaces render to \"<br />\" tags in the Hugo |
|
|
generated HTML. But the same result can also be achived by using the |
|
|
Org Verse block or Blackfriday hardLineBreak extension." |
|
|
:group 'org-export-hugo |
|
|
:type 'boolean) |
|
|
;;;###autoload (put 'org-hugo-delete-trailing-ws 'safe-local-variable 'booleanp) |
|
|
|
|
|
(defcustom org-hugo-use-code-for-kbd nil |
|
|
"When non-nil, ~text~ will translate to <kbd>text</kbd>." |
|
|
:group 'org-export-hugo |
|
|
:type 'boolean) |
|
|
;;;###autoload (put 'org-hugo-use-code-for-kbd 'safe-local-variable 'booleanp) |
|
|
|
|
|
(defcustom org-hugo-allow-spaces-in-tags t |
|
|
"When non-nil, replace double underscores in Org tags with spaces. |
|
|
|
|
|
See `org-hugo--tag-processing-fn-replace-with-spaces-maybe' for |
|
|
more information. |
|
|
|
|
|
This variable affects the Hugo tags and categories (set via Org |
|
|
tags using the \"@\" prefix)." |
|
|
:group 'org-export-hugo |
|
|
:type 'boolean) |
|
|
;;;###autoload (put 'org-hugo-allow-spaces-in-tags 'safe-local-variable 'booleanp) |
|
|
|
|
|
(defcustom org-hugo-prefer-hyphen-in-tags t |
|
|
"When non-nil, replace single underscores in Org tags with hyphens. |
|
|
|
|
|
See `org-hugo--tag-processing-fn-replace-with-hyphens-maybe' for |
|
|
more information. |
|
|
|
|
|
This variable affects the Hugo tags and categories (set via Org |
|
|
tags using the \"@\" prefix)." |
|
|
:group 'org-export-hugo |
|
|
:type 'boolean) |
|
|
;;;###autoload (put 'org-hugo-prefer-hyphen-in-tags 'safe-local-variable 'booleanp) |
|
|
|
|
|
(defcustom org-hugo-tag-processing-functions '(org-hugo--tag-processing-fn-replace-with-spaces-maybe |
|
|
org-hugo--tag-processing-fn-replace-with-hyphens-maybe) |
|
|
"List of functions that are called in order to process the Org tags. |
|
|
Each function has to accept two arguments: |
|
|
|
|
|
Arg 1: TAG-LIST which is a list of Org tags of the type |
|
|
\(\"TAG1\" \"TAG2\" ..). |
|
|
Arg 2: INFO which is a plist holding contextual information. |
|
|
|
|
|
Each function should then return a list of strings, which would |
|
|
be processed form of TAG-LIST. |
|
|
|
|
|
All the functions are called in order, and the output of one |
|
|
function is fed as the TAG-LIST input of the next called |
|
|
function. |
|
|
|
|
|
The `org-hugo--tag-processing-fn-replace-with-spaces-maybe' |
|
|
function skips any processing and returns its input TAG-LIST as |
|
|
it is if `org-hugo-allow-spaces-in-tags' is nil. |
|
|
|
|
|
The `org-hugo--tag-processing-fn-replace-with-hyphens-maybe' |
|
|
function skips any processing and returns its input TAG-LIST as |
|
|
it is if `org-hugo-prefer-hyphen-in-tags' is nil." |
|
|
:group 'org-export-hugo |
|
|
:type '(repeat (function))) |
|
|
|
|
|
(defcustom org-hugo-auto-set-lastmod nil |
|
|
"When non-nil, set the lastmod field in front-matter to current time." |
|
|
:group 'org-export-hugo |
|
|
:type 'boolean) |
|
|
;;;###autoload (put 'org-hugo-auto-set-lastmod 'safe-local-variable 'booleanp) |
|
|
|
|
|
(defcustom org-hugo-suppress-lastmod-period 0.0 |
|
|
"Suppressing period (in seconds) for adding the lastmod front-matter. |
|
|
|
|
|
The suppressing period is calculated as a delta between the |
|
|
\"date\" and auto-calculated \"lastmod\" values. This value can |
|
|
be 0.0 or a positive float. |
|
|
|
|
|
The default value is 0.0 (seconds), which means that the lastmod |
|
|
parameter will be added to front-matter even if the post is |
|
|
modified within just 0.1 seconds after the initial creation of |
|
|
it (when the \"date\" is set). |
|
|
|
|
|
If the value is 86400.0, the lastmod parameter will not be added |
|
|
to the front-matter within 24 hours from the initial exporting. |
|
|
|
|
|
This variable is effective only if auto-setting of the |
|
|
\"lastmod\" parameter is enabled i.e. if |
|
|
`org-hugo-auto-set-lastmod' or `EXPORT_HUGO_AUTO_SET_LASTMOD' is |
|
|
non-nil." |
|
|
:group 'org-export-hugo |
|
|
:type 'float) |
|
|
;;;###autoload (put 'org-hugo-suppress-lastmod-period 'safe-local-variable 'floatp) |
|
|
|
|
|
(defcustom org-hugo-export-with-toc nil |
|
|
"When non-nil, Markdown format TOC will be inserted. |
|
|
|
|
|
The TOC contains headlines with levels up |
|
|
to`org-export-headline-levels'. When an integer, include levels |
|
|
up to N in the toc, this may then be different from |
|
|
`org-export-headline-levels', but it will not be allowed to be |
|
|
larger than the number of headline levels. When nil, no table of |
|
|
contents is made. |
|
|
|
|
|
This option can also be set with the OPTIONS keyword, |
|
|
e.g. \"toc:nil\", \"toc:t\" or \"toc:3\"." |
|
|
:group 'org-export-hugo |
|
|
:type '(choice |
|
|
(const :tag "No Table of Contents" nil) |
|
|
(const :tag "Full Table of Contents" t) |
|
|
(integer :tag "TOC to level"))) |
|
|
;;;###autoload (put 'org-hugo-export-with-toc 'safe-local-variable (lambda (x) (or (booleanp x) (integerp x)))) |
|
|
|
|
|
(defcustom org-hugo-export-with-section-numbers nil |
|
|
"Configuration for adding section numbers to headlines. |
|
|
|
|
|
When set to `onlytoc', none of the headlines will be numbered in |
|
|
the exported post body, but TOC generation will use the section |
|
|
numbers. |
|
|
|
|
|
When set to an integer N, numbering will only happen for |
|
|
headlines whose relative level is higher or equal to N. |
|
|
|
|
|
When set to any other non-nil value, numbering will happen for |
|
|
all the headlines. |
|
|
|
|
|
This option can also be set with the OPTIONS keyword, |
|
|
e.g. \"num:onlytoc\", \"num:nil\", \"num:t\" or \"num:3\"." |
|
|
:group 'org-export-hugo |
|
|
:type '(choice |
|
|
(const :tag "Don't number only in body" 'onlytoc) |
|
|
(const :tag "Don't number any headline" nil) |
|
|
(const :tag "Number all headlines" t) |
|
|
(integer :tag "Number to level"))) |
|
|
;;;###autoload (put 'org-hugo-export-with-section-numbers 'safe-local-variable (lambda (x) (or (booleanp x) (equal 'onlytoc x) (integerp x)))) |
|
|
|
|
|
(defcustom org-hugo-default-static-subdirectory-for-externals "ox-hugo" |
|
|
"Default sub-directory in Hugo static directory for external files. |
|
|
If the source path for external files does not contain |
|
|
\"static\", `ox-hugo` cannot know what directory structure to |
|
|
create inside the Hugo static directory. So all such files are |
|
|
copied to this sub-directory inside the Hugo static directory." |
|
|
:group 'org-export-hugo |
|
|
:type 'string) |
|
|
;;;###autoload (put 'org-hugo-default-static-subdirectory-for-externals 'safe-local-variable 'stringp) |
|
|
|
|
|
(defcustom org-hugo-external-file-extensions-allowed-for-copying |
|
|
'("jpg" "jpeg" "tiff" "png" "svg" "gif" |
|
|
"mp4" |
|
|
"pdf" "odt" |
|
|
"doc" "ppt" "xls" |
|
|
"docx" "pptx" "xlsx") |
|
|
"List of external file extensions allowed for copying to Hugo static dir. |
|
|
If an Org link references a file with one of these extensions, |
|
|
and if that file is not in the Hugo static directory, that file |
|
|
is copied over to the static directory. |
|
|
|
|
|
The auto-copying behavior is disabled if this variable is set to |
|
|
nil." |
|
|
:group 'org-export-hugo |
|
|
:type '(repeat string)) |
|
|
|
|
|
(defcustom org-hugo-export-creator-string |
|
|
(format "Emacs %s (Org mode%s + ox-hugo)" |
|
|
emacs-version |
|
|
(if (fboundp 'org-version) |
|
|
(concat " " (org-version)) |
|
|
"")) |
|
|
"Information about the creator of the document. |
|
|
This option can also be set on with the CREATOR keyword." |
|
|
:group 'org-export-hugo |
|
|
:type '(string :tag "Creator string")) |
|
|
;;;###autoload (put 'org-hugo-export-creator-string 'safe-local-variable 'stringp) |
|
|
|
|
|
(defcustom org-hugo-date-format "%Y-%m-%dT%T%z" |
|
|
"Date format used for exporting date in front-matter. |
|
|
|
|
|
Front-matter date parameters: `date', `publishDate', |
|
|
`expiryDate', `lastmod'. |
|
|
|
|
|
Note that the date format must match the date specification from |
|
|
RFC3339. See `org-hugo--date-time-regexp' for reference and |
|
|
examples of compatible date strings. |
|
|
|
|
|
Examples of RFC3339-compatible values for this variable: |
|
|
|
|
|
- %Y-%m-%dT%T%z (default) -> 2017-07-31T17:05:38-04:00 |
|
|
- %Y-%m-%dT%T -> 2017-07-31T17:05:38 |
|
|
- %Y-%m-%d -> 2017-07-31 |
|
|
|
|
|
Note that \"%Y-%m-%dT%T%z\" actually produces a date string like |
|
|
\"2017-07-31T17:05:38-0400\"; notice the missing colon in the |
|
|
time-zone portion. |
|
|
|
|
|
A colon is needed to separate the hours and minutes in the |
|
|
time-zone as per RFC3339. This gets fixed in the |
|
|
`org-hugo--format-date' function, so that \"%Y-%m-%dT%T%z\" now |
|
|
results in a date string like \"2017-07-31T17:05:38-04:00\". |
|
|
|
|
|
See `format-time-string' to learn about the date format string |
|
|
expression." |
|
|
:group 'org-export-hugo |
|
|
:type 'string) |
|
|
;;;###autoload (put 'org-hugo-date-format 'safe-local-variable 'stringp) |
|
|
|
|
|
(defcustom org-hugo-paired-shortcodes "" |
|
|
"Space-separated string of paired shortcode strings. |
|
|
|
|
|
Shortcode string convention: |
|
|
|
|
|
- Begin the string with \"%\" for shortcodes whose content can |
|
|
contain Markdown, and thus needs to be passed through the |
|
|
Hugo Markdown processor. The content can also contain HTML. |
|
|
|
|
|
Example of a paired markdown shortcode: |
|
|
|
|
|
{{% mdshortcode %}}Content **bold** <i>italics</i>{{% /mdshortcode %}} |
|
|
|
|
|
- Absence of the \"%\" prefix would imply that the shortcode's |
|
|
content should not be passed to the Markdown parser. The |
|
|
content can contain HTML though. |
|
|
|
|
|
Example of a paired non-markdown (default) shortcode: |
|
|
|
|
|
{{< myshortcode >}}Content <b>bold</b> <i>italics</i>{{< /myshortcode >}} |
|
|
|
|
|
For example these shortcode strings: |
|
|
|
|
|
- %mdshortcode : Paired markdown shortcode |
|
|
- myshortcode : Paired default shortcode |
|
|
|
|
|
would be collectively added to this variable as: |
|
|
|
|
|
\"%mdshortcode myshortcode\" |
|
|
|
|
|
Hugo shortcodes documentation: |
|
|
https://gohugo.io/content-management/shortcodes/." |
|
|
:group 'org-export-hugo |
|
|
:type 'string) |
|
|
;;;###autoload (put 'org-hugo-paired-shortcodes 'safe-local-variable 'stringp) |
|
|
|
|
|
(defcustom org-hugo-link-desc-insert-type nil |
|
|
"Insert the element type in link descriptions for numbered elements. |
|
|
|
|
|
String representing the type is inserted for these Org elements |
|
|
if they are numbered (i.e. both \"#+name\" and \"#+caption\" are |
|
|
specified for them): |
|
|
|
|
|
- src-block : \"Code Snippet\" |
|
|
- table: \"Table\" |
|
|
- figure: \"Figure\"." |
|
|
:group 'org-export-hugo |
|
|
:type 'boolean) |
|
|
;;;###autoload (put 'org-hugo-link-desc-insert-type 'safe-local-variable 'booleanp) |
|
|
|
|
|
(defcustom org-hugo-langs-no-descr-in-code-fences '() |
|
|
"List of languages whose descriptors should not be exported to Markdown. |
|
|
|
|
|
This variable is effective only if the HUGO_CODE_FENCE option is |
|
|
non-nil (default), AND if none of the Hugo \"highlight\" |
|
|
shortcode features are needed (see `org-hugo-src-block' for more |
|
|
information). |
|
|
|
|
|
If `pygmentsCodeFences' in the Hugo site's config is `true', and |
|
|
if a language is not supported by Pygments, the HTML of that |
|
|
fenced code block is not rendered correctly by Hugo. In such |
|
|
cases, it is better to leave out the language descriptor and |
|
|
allow the code block to render as an Org example block. |
|
|
|
|
|
This variable helps get around the above issue which is present |
|
|
only when using the Pygments syntax highlighter. This issue does |
|
|
not exist when using the Chroma syntax highlighter. So, setting |
|
|
this variable to something like (org) is useful only if using the |
|
|
Pygments syntax highlighter. |
|
|
|
|
|
It is suggested to instead leave this value at its default value |
|
|
and use the Chroma syntax highlighter (default) in Hugo v0.28 and |
|
|
newer." |
|
|
:group 'org-export-hugo |
|
|
:type '(repeat symbol)) |
|
|
|
|
|
|
|
|
|
|
|
;;; Define Back-End |
|
|
|
|
|
(org-export-define-derived-backend 'hugo 'blackfriday ;hugo < blackfriday < md < html |
|
|
:menu-entry |
|
|
'(?H "Export to Hugo-compatible Markdown" |
|
|
((?H "Subtree or File to Md file " |
|
|
(lambda (a _s v _b) |
|
|
(org-hugo-export-wim-to-md nil a v))) |
|
|
(?h "File to Md file" |
|
|
(lambda (a s v _b) |
|
|
(org-hugo-export-to-md a s v))) |
|
|
(?O "Subtree or File to Md file and open " |
|
|
(lambda (a _s v _b) |
|
|
(if a |
|
|
(org-hugo-export-wim-to-md nil :async v) |
|
|
(org-open-file (org-hugo-export-wim-to-md nil nil v))))) |
|
|
(?o "File to Md file and open" |
|
|
(lambda (a s v _b) |
|
|
(if a |
|
|
(org-hugo-export-to-md :async s v) |
|
|
(org-open-file (org-hugo-export-to-md nil s v))))) |
|
|
(?A "All subtrees (or File) to Md file(s) " |
|
|
(lambda (a _s v _b) |
|
|
(org-hugo-export-wim-to-md :all-subtrees a v))) |
|
|
(?t "File to a temporary Md buffer" |
|
|
(lambda (a s v _b) |
|
|
(org-hugo-export-as-md a s v))))) |
|
|
;;;; translate-alist |
|
|
:translate-alist '((code . org-hugo-kbd-tags-maybe) |
|
|
(example-block . org-hugo-example-block) |
|
|
(export-block . org-hugo-export-block) |
|
|
(export-snippet . org-hugo-export-snippet) |
|
|
(headline . org-hugo-headline) |
|
|
(inner-template . org-hugo-inner-template) |
|
|
(keyword . org-hugo-keyword) |
|
|
(link . org-hugo-link) |
|
|
(paragraph . org-hugo-paragraph) |
|
|
(src-block . org-hugo-src-block) |
|
|
(special-block . org-hugo-special-block)) |
|
|
:filters-alist '((:filter-body . org-hugo-body-filter)) |
|
|
;;;; options-alist |
|
|
;; KEY KEYWORD OPTION DEFAULT BEHAVIOR |
|
|
:options-alist '(;; Variables not setting the front-matter directly |
|
|
(:with-toc nil "toc" org-hugo-export-with-toc) |
|
|
(:section-numbers nil "num" org-hugo-export-with-section-numbers) |
|
|
(:author "AUTHOR" nil user-full-name newline) |
|
|
(:creator "CREATOR" nil org-hugo-export-creator-string) |
|
|
(:with-smart-quotes nil "'" nil) ;Don't use smart quotes; that is done automatically by Blackfriday |
|
|
(:with-special-strings nil "-" nil) ;Don't use special strings for ndash, mdash; that is done automatically by Blackfriday |
|
|
(:with-sub-superscript nil "^" '{}) ;Require curly braces to be wrapped around text to sub/super-scripted |
|
|
(:hugo-with-locale "HUGO_WITH_LOCALE" nil nil) |
|
|
(:hugo-front-matter-format "HUGO_FRONT_MATTER_FORMAT" nil org-hugo-front-matter-format) |
|
|
(:hugo-level-offset "HUGO_LEVEL_OFFSET" nil "1") |
|
|
(:hugo-preserve-filling "HUGO_PRESERVE_FILLING" nil org-hugo-preserve-filling) ;Preserve breaks so that text filling in Markdown matches that of Org |
|
|
(:hugo-delete-trailing-ws "HUGO_DELETE_TRAILING_WS" nil org-hugo-delete-trailing-ws) |
|
|
(:hugo-section "HUGO_SECTION" nil org-hugo-section) |
|
|
(:hugo-bundle "HUGO_BUNDLE" nil nil) |
|
|
(:hugo-base-dir "HUGO_BASE_DIR" nil nil) |
|
|
(:hugo-code-fence "HUGO_CODE_FENCE" nil t) ;Prefer to generate triple-backquoted Markdown code blocks by default. |
|
|
(:hugo-use-code-for-kbd "HUGO_USE_CODE_FOR_KBD" nil org-hugo-use-code-for-kbd) |
|
|
(:hugo-prefer-hyphen-in-tags "HUGO_PREFER_HYPHEN_IN_TAGS" nil org-hugo-prefer-hyphen-in-tags) |
|
|
(:hugo-allow-spaces-in-tags "HUGO_ALLOW_SPACES_IN_TAGS" nil org-hugo-allow-spaces-in-tags) |
|
|
(:hugo-auto-set-lastmod "HUGO_AUTO_SET_LASTMOD" nil org-hugo-auto-set-lastmod) |
|
|
(:hugo-custom-front-matter "HUGO_CUSTOM_FRONT_MATTER" nil nil space) |
|
|
(:hugo-blackfriday "HUGO_BLACKFRIDAY" nil nil space) |
|
|
(:hugo-front-matter-key-replace "HUGO_FRONT_MATTER_KEY_REPLACE" nil nil space) |
|
|
(:hugo-date-format "HUGO_DATE_FORMAT" nil org-hugo-date-format) |
|
|
(:hugo-paired-shortcodes "HUGO_PAIRED_SHORTCODES" nil org-hugo-paired-shortcodes space) |
|
|
(:hugo-pandoc-citations "HUGO_PANDOC_CITATIONS" nil nil) |
|
|
(:bibliography "BIBLIOGRAPHY" nil nil newline) ;Used in ox-hugo-pandoc-cite |
|
|
|
|
|
;; Front-matter variables |
|
|
;; https://gohugo.io/content-management/front-matter/#front-matter-variables |
|
|
;; aliases |
|
|
(:hugo-aliases "HUGO_ALIASES" nil nil space) |
|
|
;; audio |
|
|
(:hugo-audio "HUGO_AUDIO" nil nil) |
|
|
;; date |
|
|
;; "date" is parsed from the Org #+date or subtree property EXPORT_HUGO_DATE |
|
|
(:date "DATE" nil nil) |
|
|
;; description |
|
|
(:description "DESCRIPTION" nil nil) |
|
|
;; draft |
|
|
;; "draft" value interpreted by the TODO state of a |
|
|
;; post as Org subtree gets higher precedence. |
|
|
(:hugo-draft "HUGO_DRAFT" nil nil) |
|
|
;; expiryDate |
|
|
(:hugo-expirydate "HUGO_EXPIRYDATE" nil nil) |
|
|
;; headless (only for Page Bundles - Hugo v0.35+) |
|
|
(:hugo-headless "HUGO_HEADLESS" nil nil) |
|
|
;; images |
|
|
(:hugo-images "HUGO_IMAGES" nil nil newline) |
|
|
;; isCJKLanguage |
|
|
(:hugo-iscjklanguage "HUGO_ISCJKLANGUAGE" nil nil) |
|
|
;; keywords |
|
|
;; "keywords" is parsed from the Org #+keywords or |
|
|
;; subtree property EXPORT_KEYWORDS. |
|
|
(:keywords "KEYWORDS" nil nil newline) |
|
|
;; layout |
|
|
(:hugo-layout "HUGO_LAYOUT" nil nil) |
|
|
;; lastmod |
|
|
(:hugo-lastmod "HUGO_LASTMOD" nil nil) |
|
|
;; linkTitle |
|
|
(:hugo-linktitle "HUGO_LINKTITLE" nil nil) |
|
|
;; locale (used in Hugo internal templates) |
|
|
(:hugo-locale "HUGO_LOCALE" nil nil) |
|
|
;; markup |
|
|
(:hugo-markup "HUGO_MARKUP" nil nil) ;default is "md" |
|
|
;; menu |
|
|
(:hugo-menu "HUGO_MENU" nil nil space) |
|
|
(:hugo-menu-override "HUGO_MENU_OVERRIDE" nil nil space) |
|
|
;; outputs |
|
|
(:hugo-outputs "HUGO_OUTPUTS" nil nil space) |
|
|
;; publishDate |
|
|
(:hugo-publishdate "HUGO_PUBLISHDATE" nil nil) |
|
|
;; series |
|
|
(:hugo-series "HUGO_SERIES" nil nil newline) |
|
|
;; slug |
|
|
(:hugo-slug "HUGO_SLUG" nil nil) |
|
|
;; taxomonomies - tags, categories |
|
|
(:hugo-tags "HUGO_TAGS" nil nil newline) |
|
|
;; #+hugo_tags are used to set the post tags in Org |
|
|
;; files written for file-based exports. But for |
|
|
;; subtree-based exports, the EXPORT_HUGO_TAGS |
|
|
;; property can be used to override inherited tags |
|
|
;; and Org-style tags. |
|
|
(:hugo-categories "HUGO_CATEGORIES" nil nil newline) |
|
|
;; #+hugo_categories are used to set the post |
|
|
;; categories in Org files written for file-based |
|
|
;; exports. But for subtree-based exports, the |
|
|
;; EXPORT_HUGO_CATEGORIES property can be used to |
|
|
;; override inherited categories and Org-style |
|
|
;; categories (Org-style tags with "@" prefix). |
|
|
;; resources |
|
|
(:hugo-resources "HUGO_RESOURCES" nil nil space) |
|
|
;; title |
|
|
;; "title" is parsed from the Org #+title or the subtree heading. |
|
|
;; type |
|
|
(:hugo-type "HUGO_TYPE" nil nil) |
|
|
;; url |
|
|
(:hugo-url "HUGO_URL" nil nil) |
|
|
;; videos |
|
|
(:hugo-videos "HUGO_VIDEOS" nil nil newline) |
|
|
;; weight |
|
|
(:hugo-weight "HUGO_WEIGHT" nil nil space))) |
|
|
|
|
|
|
|
|
|
|
|
;;; Miscellaneous Helper Functions |
|
|
|
|
|
;;;; Check if a value is non-nil |
|
|
(defun org-hugo--value-get-true-p (value) |
|
|
"Return non-nil if VALUE is non-nil. |
|
|
Return nil if VALUE is nil, \"nil\" or \"\"." |
|
|
(cond |
|
|
((or (equal t value) |
|
|
(equal nil value)) |
|
|
value) |
|
|
((and (stringp value) |
|
|
(string= value "nil")) |
|
|
nil) |
|
|
(t |
|
|
;; "" -> nil |
|
|
;; "t" -> "t" |
|
|
;; "anything else" -> "anything else" |
|
|
;; 123 -> nil |
|
|
(org-string-nw-p value)))) |
|
|
|
|
|
;;;; Check if a boolean plist value is non-nil |
|
|
(defun org-hugo--plist-get-true-p (info key) |
|
|
"Return non-nil if KEY in INFO is non-nil. |
|
|
Return nil if the value of KEY in INFO is nil, \"nil\" or \"\". |
|
|
|
|
|
This is a special version of `plist-get' used only for keys that |
|
|
are expected to hold a boolean value. |
|
|
|
|
|
INFO is a plist used as a communication channel." |
|
|
(let ((value (plist-get info key))) |
|
|
;; (message "dbg: org-hugo--plist-get-true-p:: key:%S value:%S" key value) |
|
|
(org-hugo--value-get-true-p value))) |
|
|
|
|
|
;;;; Workaround to retain custom parameters in src-block headers post `org-babel-exp-code' |
|
|
;; http://lists.gnu.org/archive/html/emacs-orgmode/2017-10/msg00300.html |
|
|
(defun org-hugo--org-babel-exp-code (orig-fun &rest args) |
|
|
"Return the original code block formatted for export. |
|
|
ORIG-FUN is the original function `org-babel-exp-code' that this |
|
|
function is designed to advice using `:around'. ARGS are the |
|
|
arguments of the ORIG-FUN. |
|
|
|
|
|
This advice retains the `:hl_lines' and `:front_matter_extra' |
|
|
parameters, if added to any source block. This parameter is used |
|
|
in `org-hugo-src-block'. |
|
|
|
|
|
This advice is added to the ORIG-FUN only while an ox-hugo export |
|
|
is in progress. See `org-hugo--before-export-function' and |
|
|
`org-hugo--after-export-function'." |
|
|
(let* ((param-keys-to-be-retained '(:hl_lines :front_matter_extra)) |
|
|
(info (car args)) |
|
|
(parameters (nth 2 info)) |
|
|
(ox-hugo-params-str (let ((str "")) |
|
|
(dolist (param parameters) |
|
|
(dolist (retain-key param-keys-to-be-retained) |
|
|
(when (equal retain-key (car param)) |
|
|
(let ((val (cdr param))) |
|
|
(setq str |
|
|
(concat str " " |
|
|
(symbol-name retain-key) " " |
|
|
(cond |
|
|
((stringp val) |
|
|
val) |
|
|
((numberp val) |
|
|
(number-to-string val)) |
|
|
(t |
|
|
(user-error "Invalid value %S assigned to %S" |
|
|
val retain-key))))))))) |
|
|
(org-string-nw-p (org-trim str)))) |
|
|
ret) |
|
|
;; (message "[ox-hugo ob-exp] info: %S" info) |
|
|
;; (message "[ox-hugo ob-exp] parameters: %S" parameters) |
|
|
;; (message "[ox-hugo ob-exp] ox-hugo-params-str: %S" ox-hugo-params-str) |
|
|
(setq ret (apply orig-fun args)) |
|
|
(when ox-hugo-params-str |
|
|
(let ((case-fold-search t)) |
|
|
(setq ret (replace-regexp-in-string "\\`#\\+begin_src .*" |
|
|
(format "\\& %s" ox-hugo-params-str) ret)))) |
|
|
;; (message "[ox-hugo ob-exp] ret: %S" ret) |
|
|
ret)) |
|
|
|
|
|
|
|
|
;;;; Workaround to fix the regression in the behavior of `org-babel--string-to-number'. |
|
|
;; https://lists.gnu.org/r/emacs-orgmode/2020-02/msg00931.html |
|
|
(defun org-hugo--org-babel--string-to-number (string) |
|
|
"If STRING represents a number return its value. |
|
|
Otherwise return nil. |
|
|
|
|
|
This function restores the behavior of |
|
|
`org-babel--string-to-number' to that of before |
|
|
https://code.orgmode.org/bzg/org-mode/commit/6b2a7cb20b357e730de151522fe4204c96615f98." |
|
|
(and (string-match-p "\\`-?\\([0-9]\\|\\([1-9]\\|[0-9]*\\.\\)[0-9]*\\)\\'" string) |
|
|
(string-to-number string))) |
|
|
|
|
|
(defun org-hugo--before-export-function (subtreep) |
|
|
"Function to be run before an ox-hugo export. |
|
|
|
|
|
This function is called in the very beginning of |
|
|
`org-hugo-export-to-md', `org-hugo-export-as-md' and |
|
|
`org-hugo-publish-to-md'. |
|
|
|
|
|
SUBTREEP is non-nil for subtree-based exports. |
|
|
|
|
|
This is an internal function." |
|
|
(unless subtreep |
|
|
;; Reset the variables that are used only for subtree exports. |
|
|
(setq org-hugo--subtree-coord nil)) |
|
|
(advice-add 'org-babel-exp-code :around #'org-hugo--org-babel-exp-code) |
|
|
(advice-add 'org-babel--string-to-number :override #'org-hugo--org-babel--string-to-number)) |
|
|
|
|
|
(defun org-hugo--after-export-function (info outfile) |
|
|
"Function to be run after an ox-hugo export. |
|
|
|
|
|
This function is called in the very end of |
|
|
`org-hugo-export-to-md', `org-hugo-export-as-md' and |
|
|
`org-hugo-publish-to-md'. |
|
|
|
|
|
INFO is a plist used as a communication channel. |
|
|
|
|
|
OUTFILE is the Org exported file name. |
|
|
|
|
|
This is an internal function." |
|
|
(advice-remove 'org-babel--string-to-number #'org-hugo--org-babel--string-to-number) |
|
|
(advice-remove 'org-babel-exp-code #'org-hugo--org-babel-exp-code) |
|
|
(when (and outfile |
|
|
(org-hugo--pandoc-citations-enabled-p info)) |
|
|
(require 'ox-hugo-pandoc-cite) |
|
|
(plist-put info :outfile outfile) |
|
|
(plist-put info :front-matter org-hugo--fm) |
|
|
(org-hugo-pandoc-cite--parse-citations-maybe info)) |
|
|
(setq org-hugo--fm nil) |
|
|
(setq org-hugo--fm-yaml nil)) |
|
|
|
|
|
;;;; HTMLized section number for headline |
|
|
(defun org-hugo--get-headline-number (headline info &optional toc) |
|
|
"Return htmlized section number for the HEADLINE. |
|
|
INFO is a plist used as a communication channel. |
|
|
|
|
|
When the \"num\" export option is `onlytoc', headline number is |
|
|
returned only if the optional argument TOC is non-nil. |
|
|
|
|
|
Return nil if there is no headline number, or if it has been |
|
|
disabled." |
|
|
(let ((onlytoc (equal 'onlytoc (plist-get info :section-numbers)))) |
|
|
(when (and (if toc |
|
|
t |
|
|
(not onlytoc)) ;If `toc' is nil, but `onlytoc' is non-nil, return nil |
|
|
(org-export-numbered-headline-p headline info)) |
|
|
(let ((number-str (mapconcat |
|
|
'number-to-string |
|
|
(org-export-get-headline-number headline info) "."))) |
|
|
(format "<span class=\"section-num\">%s</span> " number-str))))) |
|
|
|
|
|
;;;; Build TOC |
|
|
(defun org-hugo--build-toc (info &optional n keyword local) |
|
|
"Return table of contents as a string. |
|
|
|
|
|
INFO is a plist used as a communication channel. |
|
|
|
|
|
Optional argument N, when non-nil, is a positive integer |
|
|
specifying the depth of the table. |
|
|
|
|
|
Optional argument KEYWORD specifies the TOC keyword, if any, from |
|
|
which the table of contents generation has been initiated. |
|
|
|
|
|
When optional argument LOCAL is non-nil, build a table of |
|
|
contents according to the current headline." |
|
|
(let* ((toc-headline |
|
|
(unless local |
|
|
(format "\n<div class=\"heading\">%s</div>\n" |
|
|
(org-html--translate "Table of Contents" info)))) |
|
|
(current-level nil) |
|
|
(toc-items |
|
|
(mapconcat |
|
|
(lambda (headline) |
|
|
(let* ((level-raw (org-export-get-relative-level headline info)) |
|
|
(level (if local |
|
|
(let* ((current-level-inner |
|
|
(progn |
|
|
(unless current-level |
|
|
(setq current-level level-raw)) |
|
|
current-level)) |
|
|
(relative-level |
|
|
(1+ (- level-raw current-level-inner)))) |
|
|
;; (message (concat "[ox-hugo build-toc DBG] " |
|
|
;; "current-level-inner:%d relative-level:%d") |
|
|
;; current-level-inner relative-level) |
|
|
relative-level) |
|
|
level-raw)) |
|
|
(indentation (make-string (* 4 (1- level)) ?\s)) |
|
|
(todo (and (org-hugo--plist-get-true-p info :with-todo-keywords) |
|
|
(org-element-property :todo-keyword headline))) |
|
|
(todo-str (if todo |
|
|
(concat (org-hugo--todo todo info) " ") |
|
|
"")) |
|
|
(headline-num-list (org-export-get-headline-number headline info)) |
|
|
(number (if headline-num-list |
|
|
;; (message "[ox-hugo TOC DBG] headline-num-list: %S" headline-num-list) |
|
|
(org-hugo--get-headline-number headline info :toc) |
|
|
"")) |
|
|
(toc-entry |
|
|
(format "[%s%s](#%s)" |
|
|
todo-str |
|
|
(org-export-data-with-backend |
|
|
(org-export-get-alt-title headline info) |
|
|
(org-export-toc-entry-backend 'hugo) |
|
|
info) |
|
|
(org-hugo--get-anchor headline info))) |
|
|
(tags (and (plist-get info :with-tags) |
|
|
(not (eq 'not-in-toc (plist-get info :with-tags))) |
|
|
(let ((tags (org-export-get-tags headline info))) |
|
|
(and tags |
|
|
(format ":%s:" |
|
|
(mapconcat #'identity tags ":"))))))) |
|
|
;; (message "[ox-hugo build-toc DBG] level:%d, number:%s" level number) |
|
|
;; (message "[ox-hugo build-toc DBG] indentation: %S" indentation) |
|
|
;; (message "[ox-hugo build-toc DBG] todo: %s | %s" todo todo-str) |
|
|
(concat indentation "- " number toc-entry tags))) |
|
|
(org-export-collect-headlines info n (and local keyword)) |
|
|
"\n")) ;Newline between TOC items |
|
|
;; Remove blank lines from in-between TOC items, which can |
|
|
;; get introduced when using the "UNNUMBERED: t" headline |
|
|
;; property. |
|
|
(toc-items (org-string-nw-p |
|
|
(replace-regexp-in-string "\n\\{2,\\}" "\n" toc-items)))) |
|
|
;; (message "[ox-hugo build-toc DBG] toc-items:%s" toc-items) |
|
|
(when toc-items |
|
|
(concat (when (string-match-p "^\\s-*\\-\\s-<span class=\"section\\-num\"" toc-items) |
|
|
;; Hide the bullets if section numbers are present for |
|
|
;; even one heading. |
|
|
(concat "<style>\n" |
|
|
" .ox-hugo-toc ul {\n" |
|
|
" list-style: none;\n" |
|
|
" }\n" |
|
|
"</style>\n")) |
|
|
(format "<div class=\"ox-hugo-toc toc%s\">\n" |
|
|
(if local |
|
|
" local" |
|
|
"")) |
|
|
"<div></div>\n" ;This is a nasty workaround till Hugo/Blackfriday support |
|
|
toc-headline ;wrapping Markdown in HTML div's. |
|
|
"\n" |
|
|
toc-items ;https://github.com/kaushalmodi/ox-hugo/issues/93 |
|
|
"\n\n" |
|
|
"</div>\n" |
|
|
;; Special comment that can be use to filter out the TOC |
|
|
;; from .Summary in Hugo templates. |
|
|
;; |
|
|
;; {{ $summary_splits := split .Summary "<!--endtoc-->" }} |
|
|
;; {{ if eq (len $summary_splits) 2 }} |
|
|
;; <!-- If that endtoc special comment is present, output only the part after that comment as Summary. --> |
|
|
;; {{ index $summary_splits 1 | safeHTML }} |
|
|
;; {{ else }} |
|
|
;; <!-- Print the whole Summary if endtoc special comment is not found. --> |
|
|
;; {{ .Summary }} |
|
|
;; {{ end }} |
|
|
"<!--endtoc-->\n")))) |
|
|
|
|
|
;;;; Escape Hugo shortcode |
|
|
(defun org-hugo--escape-hugo-shortcode (code lang) |
|
|
"Escape Hugo shortcodes if present in CODE string. |
|
|
|
|
|
The escaping is enabled only if LANG is \"md\". |
|
|
|
|
|
- Shortcode with Markdown : {{% foo %}} -> {{%/* foo */%}} |
|
|
|
|
|
- Shortcode without Markdown : {{< foo >}} -> {{</* foo */>}} |
|
|
|
|
|
Return the escaped/unescaped string." |
|
|
(if (string= lang "md") |
|
|
(replace-regexp-in-string |
|
|
"\\({{<\\)\\([^}][^}]*\\)\\(>}}\\)" "\\1/*\\2*/\\3" |
|
|
(replace-regexp-in-string |
|
|
"\\({{%\\)\\([^}][^}]*\\)\\(%}}\\)" "\\1/*\\2*/\\3" code)) |
|
|
code)) |
|
|
|
|
|
;;;; Hugo Version |
|
|
(defun org-hugo--hugo-version () |
|
|
"Return hugo version. |
|
|
|
|
|
If hugo is found in PATH, return (LONG . SHORT). |
|
|
|
|
|
LONG is the exact string returned by \"hugo version\". |
|
|
|
|
|
SHORT is the short version of above. |
|
|
Examples: \"0.31.1\", \"0.31.99\" (for \"0.32-DEV\" version). |
|
|
|
|
|
If hugo is not found, return nil." |
|
|
(when (executable-find "hugo") |
|
|
(let* ((long-ver (org-trim (shell-command-to-string "hugo version"))) |
|
|
(short-ver (replace-regexp-in-string ".* v\\([^ ]+\\) .*" "\\1" long-ver))) |
|
|
(when (string-match "-DEV-.*" short-ver) |
|
|
;; Replace "-DEV-*" in version string with "-BETA" because |
|
|
;; `version-to-list' does not understand "-DEV". |
|
|
(setq short-ver (replace-match "-BETA" nil nil short-ver)) |
|
|
;; Below, convert "0.32-DEV" -> "0.31.99" (example) so that |
|
|
;; version strings can be compared with functions like |
|
|
;; `version<'. |
|
|
(let* ((short-ver-list (version-to-list short-ver)) |
|
|
(major-ver (nth 0 short-ver-list)) |
|
|
(minor-ver (nth 1 short-ver-list)) |
|
|
(micro-ver (nth 2 short-ver-list))) |
|
|
;; micro-ver will be -2 for "-beta" (DEV) versions. |
|
|
(setq micro-ver 99) ;Assuming that the real micro-ver will never become 99 |
|
|
(if (= 0 minor-ver) ;Example: "1.0-DEV" -> (1 0 99) -> (0 99 99) |
|
|
(progn |
|
|
(setq minor-ver 99) ;Assuming that the max minor version is 99 |
|
|
(setq major-ver (1- major-ver))) ;Assuming that major-ver is not 0 to begin with |
|
|
(setq minor-ver (1- minor-ver))) ;Example: "0.32-DEV" -> (0 32 99) -> (0 31 99) |
|
|
(setq short-ver-list (list major-ver minor-ver micro-ver)) |
|
|
(setq short-ver (mapconcat #'number-to-string short-ver-list ".")))) |
|
|
(cons long-ver short-ver)))) |
|
|
|
|
|
;;;; Resources Alist Merging |
|
|
(defun org-hugo--get-resources-alist (resources) |
|
|
"Generate a merged RESOURCES alist. |
|
|
|
|
|
All parameters for the same \"src\" are merged together in the |
|
|
same Lisp form. Parameters that are none of \"src\", \"title\" |
|
|
or \"name\" are packed into an alist with `car' as \"params\"." |
|
|
;; (message "[resources IN DBG]: %S" resources) |
|
|
(when resources |
|
|
(let (src1 all-src src-cons src-already-exists) |
|
|
(dolist (res resources) |
|
|
;; (message "res: %S" res) |
|
|
(let ((key (car res))) |
|
|
(cond |
|
|
((equal key 'src) |
|
|
(unless (null src1) |
|
|
(setq src1 (nreverse src1)) |
|
|
(if src-already-exists |
|
|
(setcdr src-already-exists (cdr src1)) |
|
|
(push src1 all-src))) |
|
|
(setq src-cons res) |
|
|
(setq src-already-exists (assoc src-cons all-src)) |
|
|
;; (message "%S exists? %S" (cdr src-cons) src-already-exists) |
|
|
(setq src1 (or (nreverse src-already-exists) (list res))) |
|
|
;; (message "src1 temp: %S" src1) |
|
|
) |
|
|
((member key '(title name)) |
|
|
(push res src1)) |
|
|
(t ;Resource Params |
|
|
(let* ((params-cons (assoc 'params src1)) |
|
|
(params (cdr params-cons))) |
|
|
(if params |
|
|
(progn |
|
|
;; (message "params 1: %S" params) |
|
|
(push res params) |
|
|
(setq params (nreverse params)) |
|
|
;; (message "params 2: %S" params) |
|
|
(setcdr params-cons params)) |
|
|
(setq params (list res)) |
|
|
(push `(params . ,params) src1)) |
|
|
;; (message "src1 temp 2: %S" src1) |
|
|
(setcdr (assoc 'params src1) params)))))) |
|
|
(setq src1 (nreverse src1)) |
|
|
;; (message "src1: %S" src1) |
|
|
(if src-already-exists |
|
|
(setcdr src-already-exists (cdr src1)) |
|
|
(push src1 all-src)) |
|
|
;; Retain the order of src |
|
|
(setq all-src (nreverse all-src)) |
|
|
;; (message "all-src: %S" all-src) |
|
|
all-src))) |
|
|
|
|
|
;;;; List to YAML/TOML list string |
|
|
(defun org-hugo--get-yaml-toml-list-string (key list) |
|
|
"Return KEY's LIST value as a YAML/TOML list, represented as a string. |
|
|
|
|
|
KEY is a string and LIST is a list where an element can be a |
|
|
symbol, number or a non-empty string. Examples: |
|
|
|
|
|
\(\"abc\" \"def\") -> \"[\\\"abc\\\", \\\"def\\\"]\"." |
|
|
(concat "[" |
|
|
(mapconcat #'identity |
|
|
(mapcar (lambda (v) |
|
|
(org-hugo--quote-string |
|
|
(cond |
|
|
((symbolp v) |
|
|
(symbol-name v)) |
|
|
((numberp v) |
|
|
(number-to-string v)) |
|
|
((org-string-nw-p v) |
|
|
v) |
|
|
(t |
|
|
(user-error "Invalid element %S in `%s' value %S" v key list))))) |
|
|
list) |
|
|
", ") |
|
|
"]")) |
|
|
|
|
|
;;;; Publication Directory |
|
|
(defun org-hugo--get-pub-dir (info) |
|
|
"Return the post publication directory path. |
|
|
|
|
|
The publication directory is created if it does not exist. |
|
|
|
|
|
INFO is a plist used as a communication channel." |
|
|
(let* ((base-dir (if (plist-get info :hugo-base-dir) |
|
|
(file-name-as-directory (plist-get info :hugo-base-dir)) |
|
|
(user-error "It is mandatory to set the HUGO_BASE_DIR property"))) |
|
|
(content-dir "content/") |
|
|
(section-path (org-hugo--get-section-path info)) |
|
|
(bundle-dir (let ((bundle-path (or ;Hugo bundle set in the post subtree gets higher precedence |
|
|
(org-hugo--entry-get-concat nil "EXPORT_HUGO_BUNDLE" "/") |
|
|
(plist-get info :hugo-bundle)))) ;This is mainly to support per-file flow |
|
|
(if bundle-path |
|
|
(file-name-as-directory bundle-path) |
|
|
""))) |
|
|
(pub-dir (let ((dir (concat base-dir content-dir section-path bundle-dir))) |
|
|
(make-directory dir :parents) ;Create the directory if it does not exist |
|
|
dir))) |
|
|
(file-truename pub-dir))) |
|
|
|
|
|
;;;; Get the publish date for the current post |
|
|
(defun org-hugo--get-date (info fmt) |
|
|
"Return current post's publish date as a string. |
|
|
|
|
|
1. If the point is in an Org subtree which has the `CLOSED' property |
|
|
set (usually generated automatically when switching a headline's |
|
|
TODO state to \"DONE\"), get the `CLOSED' time stamp. |
|
|
|
|
|
2. If that's not the case, but the subtree has the `EXPORT_DATE' |
|
|
property set, use the date from that. |
|
|
|
|
|
3. Else, try to get the date from the \"#+date\" keyword in the Org |
|
|
file, and format it using the time format string FMT. If this |
|
|
keyword is not set either, return nil. |
|
|
|
|
|
INFO is a plist used as a communication channel." |
|
|
(or |
|
|
(org-entry-get (point) "CLOSED") |
|
|
(org-string-nw-p |
|
|
(org-export-data (plist-get info :date) info)) ;`org-export-data' required |
|
|
(org-string-nw-p |
|
|
(org-export-get-date info fmt)))) |
|
|
|
|
|
;;;; Format Dates |
|
|
(defun org-hugo--format-date (date-key info) |
|
|
"Return a date string formatted in Hugo-compatible format. |
|
|
|
|
|
DATE-KEY is the key in INFO from which the date is to be |
|
|
retrieved. INFO is a plist used as a communication channel. |
|
|
|
|
|
Possible values of DATE-KEY are `:date', `:hugo-lastmod', |
|
|
`:hugo-publishdate', and `:hugo-expirydate'. |
|
|
|
|
|
Return nil if the retrieved date from INFO is nil or if the date |
|
|
cannot be formatted in Hugo-compatible format." |
|
|
(let* ((date-fmt (plist-get info :hugo-date-format)) |
|
|
(date-raw (cond |
|
|
((equal date-key :date) |
|
|
;; (message "[ox-hugo date DBG] 1 %s" (plist-get info date-key)) |
|
|
;; (message "[ox-hugo date DBG] 2 %s" (org-export-data (plist-get info date-key) info)) |
|
|
(org-hugo--get-date info date-fmt)) |
|
|
((and (equal date-key :hugo-publishdate) |
|
|
(org-entry-get (point) "SCHEDULED")) |
|
|
;; Get the date from the "SCHEDULED" property. |
|
|
(org-entry-get (point) "SCHEDULED")) |
|
|
((and (equal date-key :hugo-expirydate) |
|
|
(org-entry-get (point) "DEADLINE")) |
|
|
;; Get the date from the "DEADLINE" property. |
|
|
(org-entry-get (point) "DEADLINE")) |
|
|
(t ;:hugo-lastmod, :hugo-publishdate, :hugo-expirydate |
|
|
(org-string-nw-p (plist-get info date-key))))) |
|
|
(date-nocolon (cond |
|
|
;; If the date set for the DATE-KEY parameter |
|
|
;; is already in Hugo-compatible format, use |
|
|
;; it. |
|
|
((and (stringp date-raw) |
|
|
(string-match-p org-hugo--date-time-regexp date-raw)) |
|
|
date-raw) |
|
|
;; Else if it's any other string (like |
|
|
;; "<2018-01-23 Tue>"), try to parse that |
|
|
;; date. |
|
|
((stringp date-raw) |
|
|
(condition-case err |
|
|
(format-time-string |
|
|
date-fmt |
|
|
(apply #'encode-time (org-parse-time-string date-raw))) |
|
|
(error |
|
|
;; Set date-nocolon to nil if error |
|
|
;; happens. An example: If #+date is set |
|
|
;; to 2012-2017 to set the copyright |
|
|
;; years, just set the date to nil instead |
|
|
;; of throwing an error like: |
|
|
;; org-parse-time-string: Not a standard |
|
|
;; Org time string: 2012-2017 |
|
|
(message |
|
|
(format "[ox-hugo] Date will not be set in the front-matter: %s" |
|
|
(nth 1 err))) |
|
|
nil))) |
|
|
;; Else (if nil) and user want to auto-set the |
|
|
;; lastmod field. |
|
|
((and (equal date-key :hugo-lastmod) |
|
|
(org-hugo--plist-get-true-p info :hugo-auto-set-lastmod)) |
|
|
(let* ((curr-time (org-current-time)) |
|
|
(lastmod-str (format-time-string date-fmt curr-time))) |
|
|
;; (message "[ox-hugo suppress-lastmod] current-time = %S (decoded = %S)" |
|
|
;; curr-time (decode-time curr-time)) |
|
|
;; (message "[ox-hugo suppress-lastmod] lastmod-str = %S" |
|
|
;; lastmod-str ) |
|
|
(if (= 0.0 org-hugo-suppress-lastmod-period) |
|
|
(progn |
|
|
;; (message "[ox-hugo suppress-lastmod] not suppressed") |
|
|
lastmod-str) |
|
|
(let ((date-str (org-string-nw-p (org-hugo--get-date info date-fmt)))) |
|
|
;; (message "[ox-hugo suppress-lastmod] date-str = %S" |
|
|
;; date-str) |
|
|
(when date-str |
|
|
(let* ((date-time (apply #'encode-time |
|
|
(mapcar (lambda (el) (or el 0)) |
|
|
(parse-time-string date-str)))) |
|
|
;; It's safe to assume that |
|
|
;; `current-time' will always |
|
|
;; be >= the post date. |
|
|
(delta (float-time |
|
|
(time-subtract curr-time date-time))) |
|
|
(suppress-period (if (< 0.0 org-hugo-suppress-lastmod-period) |
|
|
org-hugo-suppress-lastmod-period |
|
|
(- org-hugo-suppress-lastmod-period)))) |
|
|
;; (message "[ox-hugo suppress-lastmod] date-time = %S (decoded = %S)" |
|
|
;; date-time (decode-time date-time)) |
|
|
;; (message "[ox-hugo suppress-lastmod] delta = %S" delta) |
|
|
;; (message "[ox-hugo suppress-lastmod] suppress-period = %S" |
|
|
;; suppress-period) |
|
|
(when (>= delta suppress-period) |
|
|
lastmod-str))))))) |
|
|
;; Else.. do nothing. |
|
|
(t |
|
|
nil))) |
|
|
;; Hugo expects the date stamp in this format (RFC3339 -- See |
|
|
;; `org-hugo--date-time-regexp'.) i.e. if the date contains |
|
|
;; the time-zone, a colon is required to separate the hours |
|
|
;; and minutes in the time-zone section. |
|
|
;; 2017-07-06T14:59:45-04:00 |
|
|
;; But by default the "%z" placeholder for time-zone (see |
|
|
;; `format-time-string') produces the zone time-string as |
|
|
;; "-0400" (Note the missing colon). Below simply adds a |
|
|
;; colon between "04" and "00" in that example. |
|
|
(date-str (and (stringp date-nocolon) |
|
|
(replace-regexp-in-string "\\([0-9]\\{2\\}\\)\\([0-9]\\{2\\}\\)\\'" "\\1:\\2" |
|
|
date-nocolon)))) |
|
|
date-str)) |
|
|
|
|
|
;;;; Replace Front-matter Keys |
|
|
(defun org-hugo--replace-keys-maybe (data info) |
|
|
"Return DATA with its keys replaced, maybe. |
|
|
|
|
|
The keys in DATA are replaced if HUGO_FRONT_MATTER_KEY_REPLACE is |
|
|
set appropriately. |
|
|
|
|
|
The replacement syntax is: |
|
|
|
|
|
#+hugo_front_matter_key_replace: oldkey>newkey |
|
|
|
|
|
If newkey is a special string \"nil\", oldkey will be removed |
|
|
from the front-matter. |
|
|
|
|
|
You can also do multiple key replacements: |
|
|
|
|
|
#+hugo_front_matter_key_replace: oldkey1>newkey1 oldkey2>newkey2 |
|
|
|
|
|
Above examples are using the keyword |
|
|
HUGO_FRONT_MATTER_KEY_REPLACE, but the same also applies when |
|
|
using its subtree property form |
|
|
:EXPORT_HUGO_FRONT_MATTER_KEY_REPLACE:. |
|
|
|
|
|
Note that: |
|
|
|
|
|
1. There are no spaces around the special character \">\". |
|
|
2. Spaces are used to only separate multiple replacements are shown in |
|
|
the second example above. |
|
|
3. The replacements are literal.. there are no regular expressions |
|
|
involved. |
|
|
|
|
|
INFO is a plist used as a communication channel." |
|
|
(let* ((repl-str (plist-get info :hugo-front-matter-key-replace)) |
|
|
(repl-str (when (org-string-nw-p repl-str) |
|
|
(org-trim repl-str)))) |
|
|
(when repl-str |
|
|
;; (message "[ox-hugo replace-key str DBG] %S" repl-str) |
|
|
(let* ((repl-list (split-string repl-str)) ;`repl-str' is space-separated |
|
|
(repl-alist (let (alist) |
|
|
(dolist (repl repl-list) |
|
|
(when (and (stringp repl) ;`repl' would look like "oldkey>newkey" |
|
|
(string-match-p ">" repl)) |
|
|
(let* ((pair (split-string repl ">")) |
|
|
(key-orig-str (org-string-nw-p (nth 0 pair))) |
|
|
(key-repl-str (org-string-nw-p (nth 1 pair))) |
|
|
(repl-pair (when (and key-orig-str |
|
|
key-repl-str) |
|
|
(cons (intern key-orig-str) |
|
|
(intern key-repl-str))))) |
|
|
(when repl-pair |
|
|
;; (message "[ox-hugo pair DBG] %S" pair) |
|
|
;; (message "[ox-hugo repl-pair DBG] %S" repl-pair) |
|
|
;; (message "[ox-hugo repl-pair car DBG] %S" (car repl-pair)) |
|
|
;; (message "[ox-hugo repl-pair cdr DBG] %S" (cdr repl-pair)) |
|
|
(push repl-pair alist))))) |
|
|
alist))) |
|
|
;; (message "[ox-hugo replace-key list DBG] %S" repl-list) |
|
|
;; (message "[ox-hugo replace-key alist DBG] %S" repl-alist) |
|
|
(dolist (repl repl-alist) |
|
|
(let ((key-orig (car repl)) |
|
|
(key-repl (cdr repl))) |
|
|
(let ((found-key-cell (assoc key-orig data))) |
|
|
(when found-key-cell |
|
|
;; (message "[ox-hugo replace-key found-key-cell DBG] %S" found-key-cell) |
|
|
;; (message "[ox-hugo replace-key key-orig DBG] %S" key-orig) |
|
|
;; (message "[ox-hugo replace-key key-repl DBG] %S" key-repl) |
|
|
(if (string= "nil" key-repl) |
|
|
;; Setting value of a front-matter key to nil will |
|
|
;; cause that key to be removed during export. |
|
|
;; See `org-hugo--gen-front-matter'. |
|
|
(setf (cdr found-key-cell) nil) |
|
|
;; https://emacs.stackexchange.com/a/3398/115 |
|
|
(setf (car found-key-cell) key-repl)))))))) |
|
|
data)) |
|
|
|
|
|
;;;; TODO keywords |
|
|
(defun org-hugo--todo (todo info) |
|
|
"Format TODO keywords into HTML. |
|
|
|
|
|
This function is almost like `org-html--todo' except that: |
|
|
- An \"org-todo\" class is always added to the span element. |
|
|
- `org-hugo--replace-underscores-with-spaces' is used to replace |
|
|
double-underscores in TODO with spaces. |
|
|
|
|
|
INFO is a plist used as a communication channel." |
|
|
(when todo |
|
|
;; (message "[DBG todo] todo: %S" todo) |
|
|
;; (message "[DBG todo] org-done-keywords: %S" org-done-keywords) |
|
|
;; (message "[DBG todo] is a done keyword? %S" (member todo org-done-keywords)) |
|
|
;; (message "[DBG todo] html-todo-kwd-class-prefix: %S" (plist-get info :html-todo-kwd-class-prefix)) |
|
|
(format "<span class=\"org-todo %s %s%s\">%s</span>" |
|
|
(if (member todo org-done-keywords) "done" "todo") |
|
|
(or (org-string-nw-p (plist-get info :html-todo-kwd-class-prefix)) "") |
|
|
(org-html-fix-class-name todo) |
|
|
(org-hugo--replace-underscores-with-spaces todo)))) |
|
|
|
|
|
;;;; Parse draft state |
|
|
(defun org-hugo--parse-draft-state (info) |
|
|
"Parse the draft state of the post heading at point. |
|
|
|
|
|
Return a \"true\" or \"false\" string. |
|
|
|
|
|
For per-subtree export flow, the draft state parsed from the Org |
|
|
TODO state has a higher precedence than the value of HUGO_DRAFT |
|
|
keyword/property. |
|
|
|
|
|
INFO is a plist used as a communication channel." |
|
|
(let* ((todo-keyword (org-entry-get (point) "TODO")) |
|
|
(draft (cond |
|
|
((stringp todo-keyword) |
|
|
(if (member todo-keyword org-done-keywords) |
|
|
nil |
|
|
(progn |
|
|
(when (string= "DRAFT" todo-keyword) |
|
|
(let ((title (org-entry-get (point) "ITEM"))) ;Post title |
|
|
(message "[ox-hugo] `%s' post is marked as a DRAFT" title))) |
|
|
t))) |
|
|
(;; If the HUGO_DRAFT keyword/property *is* set, but |
|
|
;; not to nil. |
|
|
(plist-get info :hugo-draft) |
|
|
(let* ((draft-1 (org-hugo--front-matter-value-booleanize (plist-get info :hugo-draft))) |
|
|
(is-draft (if (string= "true" draft-1) t nil))) |
|
|
(when is-draft |
|
|
(let* ((entry (org-element-at-point)) |
|
|
(is-subtree (org-element-property :EXPORT_FILE_NAME entry)) |
|
|
(title (if is-subtree |
|
|
(org-entry-get (point) "ITEM") |
|
|
(or (car (plist-get info :title)) "<EMPTY TITLE>")))) |
|
|
(message "[ox-hugo] `%s' post is marked as a DRAFT" title))) |
|
|
is-draft)) |
|
|
(t ;Neither of Org TODO state and HUGO_DRAFT keyword/property are set |
|
|
nil))) |
|
|
(draft-bool-str (org-hugo--front-matter-value-booleanize (symbol-name draft)))) |
|
|
;; (message "dbg: draft-state: todo keyword=%S HUGO_DRAFT=%S draft=%S" |
|
|
;; todo-keyword (plist-get info :hugo-draft) draft-bool-str) |
|
|
draft-bool-str)) |
|
|
|
|
|
;;;; Check if Pandoc Citations parsing is needed |
|
|
(defun org-hugo--pandoc-citations-enabled-p (info) |
|
|
"Return non-nil if Pandoc Citation parsing is enabled. |
|
|
|
|
|
INFO is a plist used as a communication channel." |
|
|
(let* ((pandoc-citations-enabled--prop-val |
|
|
(org-entry-get nil "EXPORT_HUGO_PANDOC_CITATIONS" :inherit :literal-nil)) |
|
|
(pandoc-citations-enabled--plist-val |
|
|
(org-hugo--plist-get-true-p info :hugo-pandoc-citations)) |
|
|
(pandoc-enabled (or pandoc-citations-enabled--prop-val |
|
|
pandoc-citations-enabled--plist-val)) |
|
|
(pandoc-enabled-bool (org-hugo--value-get-true-p pandoc-enabled))) |
|
|
;; (message "[ox-hugo DBG pandoc-citations-enabled--prop-val] %S" pandoc-citations-enabled--prop-val) |
|
|
;; (message "[ox-hugo DBG pandoc-citations-enabled--plist-val] %S" pandoc-citations-enabled--plist-val) |
|
|
;; (message "[ox-hugo DBG pandoc-enabled-bool] %S" pandoc-enabled-bool) |
|
|
pandoc-enabled-bool)) |
|
|
|
|
|
;;;; Get a property value and concat it with its parent value |
|
|
(defun org-hugo--entry-get-concat (pom property &optional sep) |
|
|
"Concatenate an Org Property value with its inherited value. |
|
|
|
|
|
Get value of PROPERTY for entry or content at point-or-marker |
|
|
POM. If a parent subtree has the same PROPERTY set, append the |
|
|
current property value to that, following the optional SEP. |
|
|
|
|
|
SEP is the concatenation separator string. If it is nil, it |
|
|
defaults to \"\". |
|
|
|
|
|
This function internally calls `org-entry-get' with its INHERIT |
|
|
argument set to non-nil and the LITERAL-NIL argument set to nil. |
|
|
|
|
|
If the property is present but empty, the return value is the |
|
|
empty string. If the property is not present at all, nil is |
|
|
returned. In any other case, return the value as a string. |
|
|
Search is case-insensitive." |
|
|
(let ((sep (or sep "")) |
|
|
(value-no-concat (org-entry-get pom property :inherit))) |
|
|
;; (message "[ox-hugo section concat DBG] value-no-concat: %S" value-no-concat) |
|
|
(if value-no-concat |
|
|
;; Get the value of PROPERTY from the parent relative to |
|
|
;; current point. |
|
|
(let ((value-here-no-inherit (org-entry-get pom property nil)) |
|
|
(value-parent (org-with-wide-buffer |
|
|
(when (org-up-heading-safe) |
|
|
(org-hugo--entry-get-concat nil property sep))))) |
|
|
;; (message "[ox-hugo section concat DBG] value-here-no-inherit: %S" value-here-no-inherit) |
|
|
;; (message "[ox-hugo section concat DBG] value-parent: %S" value-parent) |
|
|
(if value-here-no-inherit |
|
|
(format "%s%s%s" |
|
|
(or value-parent "") |
|
|
(if value-parent |
|
|
(if (and (org-string-nw-p sep) |
|
|
(string-suffix-p sep value-parent)) |
|
|
"" ;Don't add the `sep' if `value-parent' already ends with that `sep' |
|
|
sep) |
|
|
"") |
|
|
value-no-concat) |
|
|
;; Use the value from parent directly if the property is not |
|
|
;; set in the current subtree. |
|
|
value-parent)) |
|
|
nil))) |
|
|
|
|
|
(defun org-hugo--get-section-path (info) |
|
|
"Return the Hugo section path. |
|
|
This is the path relative to the Hugo \"content\" directory. |
|
|
|
|
|
If the EXPORT_HUGO_SECTION* keyword is set in the current or a |
|
|
parent subtree, return the concatenation of the \"HUGO_SECTION\" |
|
|
and the concatenated \"EXPORT_HUGO_SECTION*\" values as a path. |
|
|
|
|
|
Else, return the \"HUGO_SECTION\" path. |
|
|
|
|
|
The function always returns a string. |
|
|
|
|
|
INFO is a plist used as a communication channel." |
|
|
(let* ((hugo-section-prop (org-entry-get nil "EXPORT_HUGO_SECTION" :inherit)) |
|
|
(hugo-section-kwd (plist-get info :hugo-section)) |
|
|
(hugo-section-frag-prop (org-entry-get nil "EXPORT_HUGO_SECTION*" :inherit)) |
|
|
(section-path-1 (or hugo-section-prop ;EXPORT_HUGO_SECTION gets higher precedence |
|
|
hugo-section-kwd)) ;This is mainly to support per-file flow |
|
|
section-path) |
|
|
;; (message "[ox-hugo section-path DBG] hugo-section-prop: %S" hugo-section-prop) |
|
|
;; (message "[ox-hugo section-path DBG] hugo-section-kwd: %S" hugo-section-kwd) |
|
|
;; (message "[ox-hugo section-path DBG] hugo-section-frag-prop: %S" hugo-section-frag-prop) |
|
|
;; (message "[ox-hugo section-path DBG] section path-1: %S" section-path-1) |
|
|
(unless section-path-1 |
|
|
(user-error "It is mandatory to set the HUGO_SECTION property")) |
|
|
(when (org-string-nw-p hugo-section-frag-prop) |
|
|
(setq section-path-1 |
|
|
(concat (file-name-as-directory section-path-1) ;Add trailing slash if absent |
|
|
(org-hugo--entry-get-concat nil "EXPORT_HUGO_SECTION*" "/")))) |
|
|
(setq section-path (file-name-as-directory section-path-1)) |
|
|
;; (message "[ox-hugo section-path DBG] section path: %S" section-path) |
|
|
section-path)) |
|
|
|
|
|
;;;; Get Language |
|
|
(defun org-hugo--get-lang (info) |
|
|
"Return the language used for the content. |
|
|
|
|
|
The returned value is a string that can consist of only English |
|
|
alphabets and an underscore. |
|
|
|
|
|
The first 2 characters of this string is a language codes as per |
|
|
ISO 639-1 standard. See |
|
|
https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes. |
|
|
|
|
|
INFO is a plist used as a communication channel." |
|
|
(let ((lang (plist-get info :lang-iso-code))) |
|
|
(unless lang |
|
|
(setq lang |
|
|
(or (plist-get info :hugo-locale) |
|
|
;; https://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.html |
|
|
(getenv "LANGUAGE") |
|
|
(getenv "LC_ALL") |
|
|
(getenv "LANG"))) |
|
|
(when (stringp lang) |
|
|
(setq lang |
|
|
(replace-regexp-in-string "\\`\\([a-z]+_[A-Z]+\\).*\\'" "\\1" lang))) |
|
|
(setq lang (org-string-nw-p lang)) |
|
|
(when lang |
|
|
;; (message "[org-hugo--get-lang DBG] language: %s" lang) |
|
|
(plist-put info :lang-iso-code lang))) |
|
|
lang)) |
|
|
|
|
|
;;;; Check if lang is CJ(K) |
|
|
(defun org-hugo--lang-cjk-p (info) |
|
|
"Return non-nil is the language is Chinese or Japanese. |
|
|
|
|
|
(Check for Korean language has not been added as no `ox-hugo' |
|
|
user has requested for it.)" |
|
|
(let* ((lang (org-hugo--get-lang info)) |
|
|
(lang-2chars (when (and (stringp lang) |
|
|
(>= (length lang) 2)) |
|
|
(substring lang 0 2)))) |
|
|
(and lang-2chars |
|
|
(member lang-2chars '("zh" ;"zh", "zh_CH", .. |
|
|
"ja"))))) ;"ja", .. |
|
|
|
|
|
|
|
|
|
|
|
;;; Transcode Functions |
|
|
|
|
|
;;;; Code (<kdb> tags) |
|
|
(defun org-hugo-kbd-tags-maybe (verbatim _contents info) |
|
|
"Wrap text in VERBATIM object with HTML kbd tags. |
|
|
The kdb wrapping is done if `org-hugo-use-code-for-kbd' is non-nil. |
|
|
|
|
|
CONTENTS is nil. INFO is a plist used as a communication |
|
|
channel." |
|
|
(if (org-hugo--plist-get-true-p info :hugo-use-code-for-kbd) |
|
|
(format "<kbd>%s</kbd>" (org-element-property :value verbatim)) |
|
|
(org-md-verbatim verbatim nil nil))) |
|
|
|
|
|
;;;; Example Block |
|
|
(defun org-hugo-example-block (example-block _contents info) |
|
|
"Transcode an EXAMPLE-BLOCK element into Markdown format. |
|
|
|
|
|
If the example blocks are *not* set to be exported with line |
|
|
numbers (See (org) Literal examples), Markdown style |
|
|
triple-backquoted code blocks with \"text\" \\='language\\=' are |
|
|
created. |
|
|
|
|
|
Otherwise, a \"text\" \\='language\\=' code block wrapped in Hugo |
|
|
\"highlight\" shortcode (See |
|
|
https://gohugo.io/content-management/syntax-highlighting) is |
|
|
created. |
|
|
|
|
|
CONTENTS is nil. INFO is a plist holding contextual |
|
|
information." |
|
|
(let (;; See `org-element-example-block-parser' for all EXAMPLE-BLOCK properties. |
|
|
(number-lines (org-element-property :number-lines example-block)) ;Non-nil if -n or +n switch is used |
|
|
ret) |
|
|
(if number-lines |
|
|
(let* ((text (org-export-format-code-default example-block info)) |
|
|
(linenostart-str (progn |
|
|
;; Extract the start line number of the example block. |
|
|
(string-match "\\`\\([0-9]+\\)\\s-\\{2\\}" text) |
|
|
(match-string-no-properties 1 text))) |
|
|
(linenos-str (format "\"linenos=table, linenostart=%s\"" linenostart-str))) |
|
|
;; Remove Org-inserted numbers from the beginning of each |
|
|
;; line as the Hugo highlight shortcode will be used instead |
|
|
;; of literally inserting the line numbers. |
|
|
(setq text (replace-regexp-in-string "^[0-9]+\\s-\\{2\\}" "" text)) |
|
|
(setq text (format "{{< highlight text %s>}}\n%s{{< /highlight >}}\n" linenos-str text)) |
|
|
(setq ret (org-blackfriday--div-wrap-maybe example-block text))) |
|
|
(setq ret (org-blackfriday-example-block example-block nil info))) |
|
|
ret)) |
|
|
|
|
|
;;;; Export Snippet |
|
|
(defun org-hugo-export-snippet (export-snippet _contents _info) |
|
|
"Transcode a EXPORT-SNIPPET object from Org to Hugo-compatible Markdown. |
|
|
CONTENTS is nil. INFO is a plist holding contextual information. |
|
|
|
|
|
Example: |
|
|
|
|
|
\"@@hugo:foo@@\" |
|
|
|
|
|
exports verbatim to \"foo\" only when exported using `hugo' |
|
|
backend. |
|
|
|
|
|
Export snippets with backend tags \"markdown:\" and \"md:\" are |
|
|
also handled. Exporting of export snippets with backend tag |
|
|
\"html:\" uses the HTML exporter." |
|
|
(cond |
|
|
((member (org-export-snippet-backend export-snippet) '(hugo markdown md)) |
|
|
;; ox-md.el does not support export snippets, so let's handle |
|
|
;; Markdown export snippets here as well. |
|
|
(org-element-property :value export-snippet)) |
|
|
;; Also include HTML export snippets. |
|
|
(t |
|
|
(org-export-with-backend 'html export-snippet nil nil)))) |
|
|
|
|
|
;;;; Export Block |
|
|
(defun org-hugo-export-block (export-block _contents _info) |
|
|
"Transcode a EXPORT-BLOCK element from Org to Hugo-compatible Markdown. |
|
|
CONTENTS is nil. INFO is a plist holding contextual information. |
|
|
|
|
|
Example: |
|
|
|
|
|
#+begin_export hugo |
|
|
foo |
|
|
#+end_export |
|
|
|
|
|
exports verbatim to \"foo\" only when exported using `hugo' |
|
|
backend. |
|
|
|
|
|
If the backend tag is \"markdown\"/\"md\" or \"html\", exporting |
|
|
of those blocks falls back to the respective exporters." |
|
|
(cond |
|
|
((string= (org-element-property :type export-block) "HUGO") |
|
|
(org-remove-indentation (org-element-property :value export-block))) |
|
|
;; Also include Markdown and HTML export blocks. |
|
|
;; ox-md handles HTML export blocks too. |
|
|
(t |
|
|
(org-export-with-backend 'md export-block nil nil)))) |
|
|
|
|
|
;;;; Headline |
|
|
(defun org-hugo-headline (headline contents info) |
|
|
"Transcode HEADLINE element into Markdown format. |
|
|
CONTENTS is the headline contents. INFO is a plist used as |
|
|
a communication channel." |
|
|
(unless (org-element-property :footnote-section-p headline) |
|
|
(let* ((numbers (org-hugo--get-headline-number headline info nil)) |
|
|
(loffset (string-to-number (plist-get info :hugo-level-offset))) ;"" -> 0, "0" -> 0, "1" -> 1, .. |
|
|
(level (org-export-get-relative-level headline info)) |
|
|
(level-effective (+ loffset level)) |
|
|
(title (org-export-data (org-element-property :title headline) info)) ;`org-export-data' required |
|
|
(todo (and (org-hugo--plist-get-true-p info :with-todo-keywords) |
|
|
(org-element-property :todo-keyword headline))) |
|
|
(todo-fmtd (when todo |
|
|
(concat (org-hugo--todo todo info) " "))) |
|
|
(tags (and (org-hugo--plist-get-true-p info :with-tags) |
|
|
(let ((tag-list (org-export-get-tags headline info))) |
|
|
(and tag-list |
|
|
(format " :%s:" |
|
|
(mapconcat #'identity tag-list ":")))))) |
|
|
(priority |
|
|
(and (org-hugo--plist-get-true-p info :with-priority) |
|
|
(let ((char (org-element-property :priority headline))) |
|
|
(and char (format "[#%c] " char))))) |
|
|
(style (plist-get info :md-headline-style))) |
|
|
;; (message "[ox-hugo-headline DBG] num: %s" numbers) |
|
|
(cond |
|
|
;; Cannot create a headline. Fall-back to a list. |
|
|
((or (org-export-low-level-p headline info) |
|
|
(not (memq style '(atx setext))) |
|
|
(and (eq style 'atx) (> level-effective 6)) |
|
|
(and (eq style 'setext) (> level-effective 2))) |
|
|
(let ((bullet |
|
|
(if (not (org-export-numbered-headline-p headline info)) "-" |
|
|
(concat (number-to-string |
|
|
(car (last (org-export-get-headline-number |
|
|
headline info)))) |
|
|
"."))) |
|
|
(heading (concat todo-fmtd " " priority title))) ;Headline text without tags |
|
|
(concat "<!--list-separator-->\n\n" |
|
|
;; Above is needed just in case the body of the |
|
|
;; section above is ending with a plain list. That |
|
|
;; HTML comment will force-end the <ul> or <ol> tag |
|
|
;; of that preceding list. |
|
|
bullet " " heading tags "\n\n" |
|
|
(and contents (replace-regexp-in-string "^" " " contents))))) |
|
|
(t |
|
|
(let* ((anchor (format "{#%s}" ;https://gohugo.io/extras/crossreferences/ |
|
|
(org-hugo--get-anchor headline info))) |
|
|
(headline-title (org-hugo--headline-title style level loffset title |
|
|
todo-fmtd anchor numbers)) |
|
|
(content-str (or (org-string-nw-p contents) ""))) |
|
|
(format "%s%s" headline-title content-str))))))) |
|
|
|
|
|
;;;;; Headline Helpers |
|
|
;;;###autoload |
|
|
(defun org-hugo-slug (str) |
|
|
"Convert string STR to a `slug' and return that string. |
|
|
|
|
|
A `slug' is the part of a URL which identifies a particular page |
|
|
on a website in an easy to read form. |
|
|
|
|
|
Example: If STR is \"My First Post\", it will be converted to a |
|
|
slug \"my-first-post\", which can become part of an easy to read |
|
|
URL like \"https://example.com/posts/my-first-post/\". |
|
|
|
|
|
In general, STR is a string. But it can also be a string with |
|
|
Markdown markup as that string passed to this function is often |
|
|
the sub-headings of a post (which can contain bold, italics, |
|
|
link, etc markup). |
|
|
|
|
|
The `slug' generated from that STR follows these rules: |
|
|
|
|
|
- Contain only lower case alphabet, number and hyphen characters |
|
|
([[:alnum:]-]). |
|
|
- Not have *any* HTML tag like \"<code>..</code>\", |
|
|
\"<span class=..>..</span>\", etc. |
|
|
- Not contain any URLs (if STR happens to be a Markdown link). |
|
|
- Replace \".\" in STR with \"dot\", \"&\" with \"and\", |
|
|
\"+\" with \"plus\". |
|
|
- Replace parentheses with double-hyphens. So \"foo (bar) baz\" |
|
|
becomes \"foo--bar--baz\". |
|
|
- Replace non [[:alnum:]-] chars with spaces, and then one or |
|
|
more consecutive spaces with a single hyphen. |
|
|
- At most two consecutive hyphens are allowed. |
|
|
- No hyphens allowed at the leading or trailing end of the slug." |
|
|
(let* (;; All lower-case |
|
|
(str (downcase str)) |
|
|
;; Remove "<FOO>..</FOO>" HTML tags if present. |
|
|
(str (replace-regexp-in-string "<\\(?1:[a-z]+\\)[^>]*>.*</\\1>" "" str)) |
|
|
;; Remove URLs if present in the string. The ")" in the |
|
|
;; below regexp is the closing parenthesis of a Markdown |
|
|
;; link: [Desc](Link). |
|
|
(str (replace-regexp-in-string (concat "\\](" ffap-url-regexp "[^)]+)") "]" str)) |
|
|
;; Replace "&" with " and ", "." with " dot ", "+" with |
|
|
;; " plus ". |
|
|
(str (replace-regexp-in-string |
|
|
"&" " and " |
|
|
(replace-regexp-in-string |
|
|
"\\." " dot " |
|
|
(replace-regexp-in-string |
|
|
"\\+" " plus " str)))) |
|
|
;; Replace all characters except alphabets, numbers and |
|
|
;; parentheses with spaces. |
|
|
(str (replace-regexp-in-string "[^[:alnum:]()]" " " str)) |
|
|
;; On emacs 24.5, multibyte punctuation characters like ":" |
|
|
;; are considered as alphanumeric characters! Below evals to |
|
|
;; non-nil on emacs 24.5: |
|
|
;; (string-match-p "[[:alnum:]]+" ":") |
|
|
;; So replace them with space manually.. |
|
|
(str (if (version< emacs-version "25.0") |
|
|
(let ((multibyte-punctuations-str ":")) ;String of multibyte punctuation chars |
|
|
(replace-regexp-in-string (format "[%s]" multibyte-punctuations-str) " " str)) |
|
|
str)) |
|
|
;; Remove leading and trailing whitespace. |
|
|
(str (replace-regexp-in-string "\\(^[[:space:]]*\\|[[:space:]]*$\\)" "" str)) |
|
|
;; Replace 2 or more spaces with a single space. |
|
|
(str (replace-regexp-in-string "[[:space:]]\\{2,\\}" " " str)) |
|
|
;; Replace parentheses with double-hyphens. |
|
|
(str (replace-regexp-in-string "\\s-*([[:space:]]*\\([^)]+?\\)[[:space:]]*)\\s-*" " -\\1- " str)) |
|
|
;; Remove any remaining parentheses character. |
|
|
(str (replace-regexp-in-string "[()]" "" str)) |
|
|
;; Replace spaces with hyphens. |
|
|
(str (replace-regexp-in-string " " "-" str)) |
|
|
;; Remove leading and trailing hyphens. |
|
|
(str (replace-regexp-in-string "\\(^[-]*\\|[-]*$\\)" "" str))) |
|
|
str)) |
|
|
|
|
|
(defun org-hugo--get-anchor(element info &optional title-str) |
|
|
"Return an Org headline's CUSTOM_ID or it's title's slug. |
|
|
|
|
|
If an Org ELEMENT has the CUSTOM_ID property defined, return |
|
|
that. |
|
|
|
|
|
INFO is a plist used as a communication channel. |
|
|
|
|
|
If the CUSTOM_ID property is not defined, and if TITLE-STR is |
|
|
nil, derive the title string from the INFO, pass that to |
|
|
`org-hugo-slug' and return its output. |
|
|
|
|
|
If the CUSTOM_ID property is not defined, and if TITLE-STR is a |
|
|
non-empty string, pass that to `org-hugo-slug' and return its |
|
|
output." |
|
|
(let ((ret (org-element-property :CUSTOM_ID element))) |
|
|
(unless ret |
|
|
(let ((title (or (org-string-nw-p title-str) |
|
|
(org-export-data-with-backend |
|
|
(org-element-property :title element) 'md info)))) |
|
|
(setq ret (org-hugo-slug title)))) |
|
|
ret)) |
|
|
|
|
|
(defun org-hugo--headline-title (style level loffset title &optional todo anchor numbers) |
|
|
"Generate a headline title in the preferred Markdown headline style. |
|
|
|
|
|
STYLE is the preferred style (`atx' or `setext'). |
|
|
LEVEL is the header level. |
|
|
LOFFSET is the offset (a non-negative number) that is added to the |
|
|
Markdown heading level for `atx' style. |
|
|
TITLE is the headline title. |
|
|
|
|
|
Optional argument TODO is the Org TODO string. |
|
|
|
|
|
Optional argument ANCHOR is the Hugo anchor tag for the section as a |
|
|
string. |
|
|
|
|
|
Optional argument NUMBERS, if non-nil, is an htmlized string |
|
|
containing the TITLE's number." |
|
|
(let ((headline (concat todo numbers title " " anchor "\n"))) |
|
|
;; Use "Setext" style |
|
|
(if (and (eq style 'setext) (< level 3)) |
|
|
(let* ((underline-char (if (= level 1) ?= ?-)) |
|
|
(underline (concat (make-string (length headline) underline-char) |
|
|
"\n"))) |
|
|
(concat "\n" headline underline "\n")) |
|
|
;; Use "Atx" style |
|
|
;; Always translate level N Org headline to level N+1 Markdown |
|
|
;; headline because Markdown level 1 headline and HTML title both |
|
|
;; get the HTML <h1> tag, and we do not want the top-most heading |
|
|
;; of a post to look the exact same as the post's title. |
|
|
(let ((level-mark (make-string (+ loffset level) ?#))) |
|
|
(concat "\n" level-mark " " headline "\n"))))) |
|
|
|
|
|
;;;; Inner Template |
|
|
(defun org-hugo-inner-template (contents info) |
|
|
"Return body of document after converting it to Hugo-compatible Markdown. |
|
|
CONTENTS is the transcoded contents string. INFO is a plist |
|
|
holding export options." |
|
|
(let* ((toc-level (plist-get info :with-toc)) |
|
|
(toc-level (if (and toc-level |
|
|
(not (wholenump toc-level))) |
|
|
(plist-get info :headline-levels) |
|
|
toc-level)) |
|
|
(toc (if (and toc-level |
|
|
(wholenump toc-level) |
|
|
(> toc-level 0)) ;TOC will be exported only if toc-level is positive |
|
|
(concat (org-hugo--build-toc info toc-level) "\n") |
|
|
""))) |
|
|
;; (message "[org-hugo-inner-template DBG] toc-level: %s" toc-level) |
|
|
(org-trim (concat |
|
|
toc |
|
|
contents |
|
|
;; Make sure CONTENTS is separated from table of contents |
|
|
;; and footnotes with at least a blank line. |
|
|
"\n" |
|
|
(org-blackfriday-footnote-section info (org-hugo--lang-cjk-p info)))))) |
|
|
|
|
|
;;;; Keyword |
|
|
(defun org-hugo-keyword (keyword contents info) |
|
|
"Transcode a KEYWORD element into Hugo-compatible Markdown format. |
|
|
CONTENTS is nil. INFO is a plist used as a communication |
|
|
channel." |
|
|
(let ((kwd (org-element-property :key keyword)) |
|
|
(value (org-element-property :value keyword))) |
|
|
(cond |
|
|
((and (equal "HUGO" kwd) ;Hugo summary splitting |
|
|
(stringp value) |
|
|
(string-match-p "\\`\\s-*more\\s-*\\'" value)) |
|
|
;; https://gohugo.io/content-management/summaries#user-defined-manual-summary-splitting |
|
|
"<!--more-->") |
|
|
((and (equal "TOC" kwd) |
|
|
(string-match-p "\\<headlines\\>" value)) |
|
|
(let ((depth (and (string-match "\\<[0-9]+\\>" value) |
|
|
(string-to-number (match-string 0 value)))) |
|
|
(local? (string-match-p "\\<local\\>" value))) |
|
|
(when (and depth |
|
|
(> depth 0)) |
|
|
(org-remove-indentation |
|
|
(org-hugo--build-toc info depth keyword local?))))) |
|
|
(t |
|
|
(org-md-keyword keyword contents info))))) |
|
|
|
|
|
;;;; Links |
|
|
(defun org-hugo-link (link desc info) |
|
|
"Convert LINK to Markdown format. |
|
|
|
|
|
DESC is the link's description. |
|
|
INFO is a plist used as a communication channel. |
|
|
|
|
|
Unlike `org-md-link', this function will also copy local images |
|
|
and rewrite link paths to make blogging more seamless." |
|
|
(let* ((raw-link (org-element-property :raw-link link)) |
|
|
(raw-path (org-element-property :path link)) |
|
|
(type (org-element-property :type link)) |
|
|
(link-is-url (member type '("http" "https" "ftp" "mailto")))) |
|
|
(when (and (stringp raw-path) |
|
|
link-is-url) |
|
|
(setq raw-path (org-blackfriday--url-sanitize |
|
|
(url-encode-url raw-path)))) |
|
|
;; (message "[ox-hugo-link DBG] link: %S" link) |
|
|
;; (message "[ox-hugo-link DBG] link path: %s" (org-element-property :path link)) |
|
|
;; (message "[ox-hugo-link DBG] link filename: %s" (expand-file-name (plist-get (car (cdr link)) :path))) |
|
|
;; (message "[ox-hugo-link DBG] link type: %s" type) |
|
|
(cond |
|
|
;; Link type is handled by a special function. |
|
|
((org-export-custom-protocol-maybe link desc 'md)) |
|
|
((member type '("custom-id" "id" "fuzzy")) |
|
|
(let ((destination (if (string= type "fuzzy") |
|
|
(org-export-resolve-fuzzy-link link info) |
|
|
(org-export-resolve-id-link link info)))) |
|
|
;; (message "[org-hugo-link DBG] link destination elem type: %S" (org-element-type destination)) |
|
|
(pcase (org-element-type destination) |
|
|
(`plain-text ;External file |
|
|
(let ((path (progn |
|
|
;; Treat links to `file.org' as links to `file.md'. |
|
|
(if (string= ".org" (downcase (file-name-extension destination "."))) |
|
|
(concat (file-name-sans-extension destination) ".md") |
|
|
destination)))) |
|
|
(if desc |
|
|
(format "[%s](%s)" desc path) |
|
|
(format "<%s>" path)))) |
|
|
(`headline ;Links of type [[* Some heading]] |
|
|
(let ((title (org-export-data (org-element-property :title destination) info))) |
|
|
(format |
|
|
"[%s](#%s)" |
|
|
;; Description |
|
|
(cond ((org-string-nw-p desc)) |
|
|
((org-export-numbered-headline-p destination info) |
|
|
(mapconcat #'number-to-string |
|
|
(org-export-get-headline-number destination info) |
|
|
".")) |
|
|
(t |
|
|
title)) |
|
|
;; Reference |
|
|
(org-hugo--get-anchor destination info title)))) |
|
|
(_ |
|
|
(let ((description |
|
|
(or (org-string-nw-p desc) |
|
|
(let ((number (org-export-get-ordinal |
|
|
destination info |
|
|
nil #'org-html--has-caption-p))) |
|
|
(when number |
|
|
(let ((num-str (if (atom number) |
|
|
(number-to-string number) |
|
|
(mapconcat #'number-to-string number ".")))) |
|
|
;; (message "[ox-hugo-link DBG] num-str: %s" num-str) |
|
|
(if org-hugo-link-desc-insert-type |
|
|
(let* ((type (org-element-type destination)) |
|
|
;; Org doesn't have a specific |
|
|
;; element for figures. So if |
|
|
;; the element is `paragraph', |
|
|
;; and as this element has an |
|
|
;; ordinal, we will assume that |
|
|
;; to be a figure. |
|
|
(type (if (equal 'paragraph type) |
|
|
'figure |
|
|
type)) |
|
|
(type-str (org-blackfriday--translate type info))) |
|
|
(format "%s %s" type-str num-str)) |
|
|
num-str))))))) |
|
|
;; (message "[ox-hugo-link DBG] link description: %s" description) |
|
|
(when description |
|
|
(format "[%s](#%s)" |
|
|
description |
|
|
(if (memq (org-element-type destination) '(src-block table)) |
|
|
(org-blackfriday--get-reference destination) |
|
|
(org-export-get-reference destination info))))))))) |
|
|
((org-export-inline-image-p link org-html-inline-image-rules) |
|
|
;; (message "[org-hugo-link DBG] processing an image: %s" desc) |
|
|
(let* ((parent (org-export-get-parent link)) |
|
|
(parent-type (org-element-type parent)) |
|
|
;; If this is a hyper-linked image, it's parent type will |
|
|
;; be a link too. Get the parent of *that* link in that |
|
|
;; case. |
|
|
(grand-parent (when (eq parent-type 'link) |
|
|
(org-export-get-parent parent))) |
|
|
(useful-parent (if grand-parent |
|
|
grand-parent |
|
|
parent)) |
|
|
(attr (org-export-read-attribute :attr_html useful-parent)) |
|
|
(caption (or |
|
|
;; Caption set using #+caption takes higher precedence. |
|
|
(org-string-nw-p |
|
|
(org-export-data ;Look for caption set using #+caption |
|
|
(org-export-get-caption (org-export-get-parent-element link)) |
|
|
info)) |
|
|
(plist-get attr :caption))) |
|
|
(caption (when (org-string-nw-p caption) |
|
|
(format "%s%s%s%s" |
|
|
;; Tue Feb 13 11:32:45 EST 2018 - kmodi |
|
|
;; Add the span tag once |
|
|
;; https://github.com/gohugoio/hugo/issues/4406 |
|
|
;; gets resolved. |
|
|
"" ;"<span class=\\\"figure-number\\\">" |
|
|
(format (org-html--translate |
|
|
(concat |
|
|
(cdr (assoc 'figure org-blackfriday--org-element-string)) |
|
|
" %d:") |
|
|
info) |
|
|
(org-export-get-ordinal |
|
|
useful-parent info |
|
|
nil #'org-html--has-caption-p)) |
|
|
" " ;" </span>" |
|
|
;; Escape the double-quotes, if any. |
|
|
(replace-regexp-in-string "\"" "\\\\\"" caption)))) |
|
|
(extension (downcase (file-name-extension raw-path))) |
|
|
(inlined-svg (and (stringp extension) |
|
|
(string= "svg" extension) |
|
|
(plist-get attr :inlined)))) |
|
|
;; (message "[ox-hugo-link DBG] Inline image: %s, extension: %s" raw-path extension) |
|
|
;; (message "[ox-hugo-link DBG] inlined svg? %S" inlined-svg) |
|
|
;; (message "[ox-hugo-link DBG] caption: %s" caption) |
|
|
(if inlined-svg |
|
|
(let* ((svg-contents (with-temp-buffer |
|
|
(insert-file-contents raw-path) |
|
|
(fill-region (point-min) (point-max)) ;Make huge one-liner SVGs sane |
|
|
(buffer-substring-no-properties (point-min) (point-max)))) |
|
|
(svg-contents-sanitized (replace-regexp-in-string |
|
|
;; Remove the HTML comments. |
|
|
"<!--\\(.\\|\n\\)*?-->" "" |
|
|
(replace-regexp-in-string |
|
|
;; Remove the xml document tag as that cannot be inlined in-between |
|
|
;; a Markdown (or even an HTML) file. |
|
|
"<\\?xml version=\"1\\.0\" encoding=\"UTF-8\" standalone=\"no\"\\?>" "" |
|
|
svg-contents))) |
|
|
(caption-html (if (not caption) |
|
|
"" |
|
|
(format (concat "\n\n<div class=\"figure-caption\">\n" |
|
|
" %s\n" |
|
|
"</div>") |
|
|
(org-html-convert-special-strings ;Interpret em-dash, en-dash, etc. |
|
|
(org-export-data-with-backend caption 'html info)))))) |
|
|
;; (message "[ox-hugo-link DBG] svg contents: %s" svg-contents) |
|
|
;; (message "[ox-hugo-link DBG] svg contents sanitized: %s" svg-contents-sanitized) |
|
|
(concat svg-contents-sanitized caption-html)) |
|
|
(let* ((path (org-hugo--attachment-rewrite-maybe raw-path info)) |
|
|
(inline-image (not (org-html-standalone-image-p useful-parent info))) |
|
|
(source (if link-is-url |
|
|
(concat type ":" path) |
|
|
path)) |
|
|
(num-attr (/ (length attr) 2)) ;(:alt foo) -> num-attr = 1 |
|
|
(alt-text (plist-get attr :alt))) |
|
|
;; (message "[ox-hugo-link DBG] path: %s" path) |
|
|
;; (message "[ox-hugo-link DBG] inline image? %s" inline-image) |
|
|
;; (message "[org-hugo-link DBG] attr: %s num of attr: %d" |
|
|
;; attr (length attr)) |
|
|
;; (message "[org-hugo-link DBG] parent-type: %s" parent-type) |
|
|
;; (message "[org-hugo-link DBG] useful-parent-type: %s" |
|
|
;; (org-element-type useful-parent)) |
|
|
(cond |
|
|
(;; Use the Markdown image syntax if the image is inline and |
|
|
;; there are no HTML attributes for the image, or just one |
|
|
;; attribute, the `alt-text'. |
|
|
(and inline-image |
|
|
(or (= 0 num-attr) |
|
|
(and alt-text |
|
|
(= 1 num-attr)))) |
|
|
(let ((alt-text (if alt-text |
|
|
alt-text |
|
|
""))) |
|
|
(format "" alt-text source))) |
|
|
(;; Else if the image is inline (with non-alt-text |
|
|
;; attributes), use HTML <img> tag syntax. |
|
|
inline-image |
|
|
;; The "target" and "rel" attributes would be meant for <a> |
|
|
;; tags. So do not pass them to the <img> tag. |
|
|
(plist-put attr :target nil) |
|
|
(plist-put attr :rel nil) |
|
|
(org-html--format-image source attr info)) |
|
|
(t ;Else use the Hugo `figure' shortcode. |
|
|
;; Hugo `figure' shortcode named parameters. |
|
|
;; https://gohugo.io/content-management/shortcodes/#figure |
|
|
(let ((figure-params `((src . ,source) |
|
|
(alt . ,alt-text) |
|
|
(caption . ,caption) |
|
|
(link . ,(plist-get attr :link)) |
|
|
(title . ,(plist-get attr :title)) |
|
|
(class . ,(plist-get attr :class)) |
|
|
(attr . ,(plist-get attr :attr)) |
|
|
(attrlink . ,(plist-get attr :attrlink)) |
|
|
(width . ,(plist-get attr :width)) |
|
|
(height . ,(plist-get attr :height)) |
|
|
;; While the `target' and `rel' |
|
|
;; attributes are not supported by |
|
|
;; the inbuilt Hugo `figure' |
|
|
;; shortcode, they can be used as |
|
|
;; intended if a user has a custom |
|
|
;; `figure' shortcode with the |
|
|
;; support added for those. |
|
|
(target . ,(plist-get attr :target)) |
|
|
(rel . ,(plist-get attr :rel)))) |
|
|
(figure-param-str "")) |
|
|
(dolist (param figure-params) |
|
|
(let ((name (car param)) |
|
|
(val (cdr param))) |
|
|
(when val |
|
|
(setq figure-param-str (concat figure-param-str |
|
|
(format "%s=\"%s\" " |
|
|
name val)))))) |
|
|
;; (message "[org-hugo-link DBG] figure params: %s" figure-param-str) |
|
|
(format "{{< figure %s >}}" (org-trim figure-param-str))))))))) |
|
|
((string= type "coderef") |
|
|
(let ((ref (org-element-property :path link))) |
|
|
(format (org-export-get-coderef-format ref desc) |
|
|
(org-export-resolve-coderef ref info)))) |
|
|
((equal type "radio") |
|
|
(let ((destination (org-export-resolve-radio-link link info))) |
|
|
(format "[%s](#%s)" desc (org-export-get-reference destination info)))) |
|
|
(t |
|
|
(let* ((link-param-str "") |
|
|
(path (cond |
|
|
(link-is-url |
|
|
;; Taken from ox-html.el -- Extract attributes |
|
|
;; from parent's paragraph. HACK: Only do this |
|
|
;; for the first link in parent (inner image link |
|
|
;; for inline images). This is needed as long as |
|
|
;; attributes cannot be set on a per link basis. |
|
|
(let* ((attr |
|
|
(let ((parent (org-export-get-parent-element link))) |
|
|
(and (eq (org-element-map parent 'link #'identity info :first-match) link) |
|
|
(org-export-read-attribute :attr_html parent)))) |
|
|
;; https://www.w3schools.com/tags/tag_link.asp |
|
|
(link-params `((media . ,(plist-get attr :media)) |
|
|
(target . ,(plist-get attr :target)) |
|
|
(rel . ,(plist-get attr :rel)) |
|
|
(sizes . ,(plist-get attr :sizes)) |
|
|
(type . ,(plist-get attr :type))))) |
|
|
(dolist (param link-params) |
|
|
(let ((name (car param)) |
|
|
(val (cdr param))) |
|
|
(when val |
|
|
(setq link-param-str (concat link-param-str |
|
|
(format "%s=\"%s\" " |
|
|
name val)))))) |
|
|
;; (message "[ox-hugo-link DBG] link params: %s" link-param-str) |
|
|
) |
|
|
(concat type ":" raw-path)) |
|
|
(;; Remove the "file://" prefix. |
|
|
(string= type "file") |
|
|
;; (message "[ox-hugo-link DBG] raw-path: %s" raw-path) |
|
|
(let ((path1 (replace-regexp-in-string "\\`file://" "" raw-path))) |
|
|
(if (string= ".org" (downcase (file-name-extension path1 "."))) |
|
|
(let ((raw-link-minus-org-file |
|
|
;; If raw-link is "./foo.org::#bar", |
|
|
;; set `raw-link-minus-org-file' to |
|
|
;; "#bar". |
|
|
(if (string-match ".*\\.org::\\(#.*\\)" raw-link) |
|
|
(match-string-no-properties 1 raw-link) |
|
|
""))) |
|
|
(format "{{< relref \"%s%s\" >}}" |
|
|
(file-name-sans-extension |
|
|
(file-name-nondirectory path1)) |
|
|
raw-link-minus-org-file)) |
|
|
(org-hugo--attachment-rewrite-maybe path1 info)))) |
|
|
(t |
|
|
raw-path))) |
|
|
(link-param-str (org-string-nw-p (org-trim link-param-str)))) |
|
|
;; (message "[ox-hugo-link DBG] desc=%s path=%s" desc path) |
|
|
;; (message "[ox-hugo-link DBG] link-param-str=%s" link-param-str) |
|
|
(cond |
|
|
;; Link description is a `figure' shortcode but does not |
|
|
;; already have the `link' parameter set. |
|
|
((and desc |
|
|
(string-match-p "\\`{{<\\s-*figure\\s-+" desc) |
|
|
(not (string-match-p "\\`{{<\\s-*figure\\s-+.*link=" desc))) |
|
|
(replace-regexp-in-string "\\s-*>}}\\'" |
|
|
(format " link=\"%s\"\\&" path) |
|
|
desc)) |
|
|
;; Both link description and link attributes are present. |
|
|
((and desc |
|
|
link-param-str) |
|
|
(format "<a href=\"%s\" %s>%s</a>" |
|
|
(org-html-encode-plain-text path) |
|
|
link-param-str |
|
|
(org-link-unescape desc))) |
|
|
;; Only link description, but no link attributes. |
|
|
(desc |
|
|
(let* ((path-has-space (and |
|
|
(not (string-prefix-p "{{< relref " path)) |
|
|
(string-match-p "\\s-" path))) |
|
|
(path (if path-has-space |
|
|
;; https://github.com/kaushalmodi/ox-hugo/issues/376 |
|
|
;; https://github.com/gohugoio/hugo/issues/6742#issuecomment-573924706 |
|
|
(format "<%s>" path) |
|
|
path))) |
|
|
(format "[%s](%s)" desc path))) |
|
|
;; Only link attributes, but no link description. |
|
|
(link-param-str |
|
|
(let ((path (org-html-encode-plain-text path))) |
|
|
(format "<a href=\"%s\" %s>%s</a>" |
|
|
path |
|
|
link-param-str |
|
|
(org-link-unescape path)))) |
|
|
;; Neither link description, nor link attributes. |
|
|
(t |
|
|
(if (string-prefix-p "{{< relref " path) |
|
|
(format "[%s](%s)" path path) |
|
|
(format "<%s>" path))))))))) |
|
|
|
|
|
;;;;; Helpers |
|
|
(defun org-hugo--maybe-copy-resources (info) |
|
|
"Copy resources to the bundle directory if needed. |
|
|
|
|
|
INFO is a plist used as a communication channel." |
|
|
(let* ((exportables org-hugo-external-file-extensions-allowed-for-copying) |
|
|
(bundle-dir (and (plist-get info :hugo-bundle) |
|
|
(org-hugo--get-pub-dir info))) |
|
|
(resources (org-hugo--parse-property-arguments (plist-get info :hugo-resources)))) |
|
|
(when (and bundle-dir resources) |
|
|
(dolist (resource resources) |
|
|
(let ((key (car resource))) |
|
|
(when (equal key 'src) |
|
|
(let* ((val (cdr resource)) |
|
|
(sources (file-expand-wildcards val))) |
|
|
(dolist (source sources) |
|
|
(let ((src-path (file-truename source))) |
|
|
(when (and (file-exists-p src-path) |
|
|
(member (file-name-extension src-path) exportables)) |
|
|
(let* ((dest-path (concat bundle-dir source)) |
|
|
(dest-path-dir (file-name-directory dest-path))) |
|
|
(unless (file-exists-p dest-path-dir) |
|
|
(mkdir dest-path-dir :parents)) |
|
|
(when (file-newer-than-file-p src-path dest-path) |
|
|
(message "[ox-hugo] Copied resource %S to %S" src-path dest-path) |
|
|
(copy-file src-path dest-path :ok-if-already-exists))))))))))))) |
|
|
|
|
|
(defun org-hugo--attachment-rewrite-maybe (path info) |
|
|
"Copy local images and pdfs to the static/bundle directory if needed. |
|
|
Also update the link paths to match those. |
|
|
|
|
|
PATH is the path to the image or any other attachment. |
|
|
|
|
|
INFO is a plist used as a communication channel." |
|
|
;; (message "[ox-hugo attachment DBG] The Hugo section is: %s" (plist-get info :hugo-section)) |
|
|
;; (message "[ox-hugo attachment DBG] The Hugo base dir is: %s" (plist-get info :hugo-base-dir)) |
|
|
(let* ((pub-dir (org-hugo--get-pub-dir info)) ;This needs to happen first so that the check for HUGO_BASE_DIR happens. |
|
|
(hugo-base-dir (file-name-as-directory (plist-get info :hugo-base-dir))) |
|
|
(path-unhexified (url-unhex-string path)) |
|
|
(path-true (file-truename path-unhexified)) |
|
|
(exportables org-hugo-external-file-extensions-allowed-for-copying) |
|
|
(bundle-dir (and (plist-get info :hugo-bundle) pub-dir)) |
|
|
(bundle-name (when bundle-dir |
|
|
(let* ((content-dir (file-truename |
|
|
(file-name-as-directory |
|
|
(expand-file-name "content" hugo-base-dir)))) |
|
|
(is-home-branch-bundle (string= bundle-dir content-dir))) |
|
|
(cond |
|
|
(is-home-branch-bundle |
|
|
"_home") |
|
|
(t ;`bundle-dir'="/foo/bar/" -> `bundle-name'="bar" |
|
|
(file-name-base (directory-file-name bundle-dir))))))) |
|
|
(static-dir (file-truename |
|
|
(file-name-as-directory |
|
|
(expand-file-name "static" hugo-base-dir)))) |
|
|
(dest-dir (or bundle-dir static-dir)) |
|
|
ret) |
|
|
(unless (file-directory-p static-dir) |
|
|
(user-error "Please create the %s directory" static-dir)) |
|
|
;; (message "[ox-hugo DBG attch rewrite] Image export dir is: %s" static-dir) |
|
|
;; (message "[ox-hugo DBG attch rewrite] path: %s" path) |
|
|
;; (message "[ox-hugo DBG attch rewrite] path-true: %s" path-true) |
|
|
;; (message "[ox-hugo DBG attch rewrite] bundle-dir: %s" bundle-dir) |
|
|
;; (message "[ox-hugo DBG attch rewrite] bundle-name: %s" bundle-name) |
|
|
;; (message "[ox-hugo DBG attch rewrite] default-dir: %s" default-directory) |
|
|
;; (message "[ox-hugo DBG attch rewrite] dest-dir: %s" dest-dir) |
|
|
(if (and (file-exists-p path-true) |
|
|
(member (file-name-extension path-unhexified) exportables) |
|
|
(file-directory-p dest-dir)) |
|
|
(progn |
|
|
;; Check if `path-true' is already inside `dest-dir'. |
|
|
(if (string-match (regexp-quote dest-dir) path-true) |
|
|
(progn |
|
|
;; If so, return *only* the path considering the |
|
|
;; destination directory as root. |
|
|
(setq ret (concat "/" (substring path-true (match-end 0))))) |
|
|
(let* ((file-name-relative-path |
|
|
(cond |
|
|
((string-match "/static/" path-true) |
|
|
;; `path-true' is "/foo/static/bar/baz.png", |
|
|
;; return "bar/baz.png". |
|
|
;; (message "[ox-hugo DBG attch rewrite] path contains static") |
|
|
;; If path-true contains "/static/", set the |
|
|
;; `dest-dir' to `static-dir' (even if this is a |
|
|
;; page bundle). |
|
|
(setq dest-dir static-dir) |
|
|
(substring path-true (match-end 0))) |
|
|
(bundle-dir |
|
|
(cond |
|
|
((string-match (concat "/" (regexp-quote bundle-name) "/") path-true) |
|
|
;; This is a page bundle. `bundle-name' is |
|
|
;; "<BUNDLE_NAME>", `path-true' is |
|
|
;; "<ORG_FILE_DIR>/bar/<BUNDLE_NAME>/zoo/baz.png", |
|
|
;; return "zoo/baz.png". |
|
|
;; (message "[ox-hugo DBG attch rewrite BUNDLE 1] bundle-name: %s" bundle-name) |
|
|
;; (message "[ox-hugo DBG attch rewrite BUNDLE 1] attch along with Org content: %s" |
|
|
;; (substring path-true (match-end 0))) |
|
|
(substring path-true (match-end 0))) |
|
|
((string-match (regexp-quote default-directory) path-true) |
|
|
;; This is a page bundle. `default-path' is |
|
|
;; "<ORG_FILE_DIR>/", `path-true' is |
|
|
;; "<ORG_FILE_DIR>/bar/baz.png", return |
|
|
;; "bar/baz.png". |
|
|
;; (message "[ox-hugo DBG attch rewrite BUNDLE 2] attch along with Org content: %s" |
|
|
;; (substring path-true (match-end 0))) |
|
|
(substring path-true (match-end 0))) |
|
|
(t |
|
|
;; This is a page bundle. `default-path' is |
|
|
;; "<ORG_FILE_DIR>/", `path-true' is |
|
|
;; "/foo/bar/baz.png", return "baz.png". |
|
|
;; (message "[ox-hugo DBG attch rewrite BUNDLE 3] attch neither in static nor in Org file dir") |
|
|
(file-name-nondirectory path-unhexified)))) |
|
|
(t |
|
|
;; Else, `path-true' is "/foo/bar/baz.png", |
|
|
;; return "ox-hugo/baz.png". "ox-hugo" is the |
|
|
;; default value of |
|
|
;; `org-hugo-default-static-subdirectory-for-externals'. |
|
|
;; (message "[ox-hugo DBG attch rewrite] neither BUNDLE nor contains static") |
|
|
(concat |
|
|
(file-name-as-directory org-hugo-default-static-subdirectory-for-externals) |
|
|
(file-name-nondirectory path-unhexified))))) |
|
|
(dest-path (concat dest-dir file-name-relative-path)) |
|
|
(dest-path-dir (file-name-directory dest-path))) |
|
|
;; The `dest-dir' would already exist. But if |
|
|
;; `file-name-relative-path' is "images/image.png" or |
|
|
;; "foo/bar.txt", it's likely that "`dest-dir'/images" |
|
|
;; or "`dest-dir'/foo" might not exist. So create those |
|
|
;; if needed below. |
|
|
(unless (file-exists-p dest-path-dir) |
|
|
(mkdir dest-path-dir :parents)) |
|
|
;; (message "[ox-hugo DBG attch rewrite] file-name-relative-path: %s" file-name-relative-path) |
|
|
;; (message "[ox-hugo DBG attch rewrite] dest-path: %s" dest-path) |
|
|
;; (message "[ox-hugo DBG attch rewrite] dest-path-dir: %s" dest-path-dir) |
|
|
|
|
|
;; Do the copy only if the file to be copied is newer or |
|
|
;; doesn't exist in the static dir. |
|
|
(when (file-newer-than-file-p path-true dest-path) |
|
|
(message "[ox-hugo] Copied %S to %S" path-true dest-path) |
|
|
(copy-file path-true dest-path :ok-if-already-exists)) |
|
|
(setq ret (if (and bundle-dir |
|
|
(string= bundle-dir dest-dir)) |
|
|
;; If attachments are copied to the bundle |
|
|
;; directory, don't prefix the path as "/" |
|
|
;; as those paths won't exist at the site |
|
|
;; base URL. |
|
|
file-name-relative-path |
|
|
(concat "/" file-name-relative-path)))))) |
|
|
(setq ret path)) |
|
|
;; (message "[ox-hugo DBG attch rewrite] returned path: %s" ret) |
|
|
ret)) |
|
|
|
|
|
;;;; Paragraph |
|
|
(defun org-hugo-paragraph (paragraph contents info) |
|
|
"Transcode PARAGRAPH element into Hugo Markdown format. |
|
|
CONTENTS is the paragraph contents. INFO is a plist used as a |
|
|
communication channel." |
|
|
(let (;; The label is mainly for paragraphs that are standalone |
|
|
;; images with #+name keyword. |
|
|
(label (let ((lbl (and (org-element-property :name paragraph) |
|
|
(org-export-get-reference paragraph info)))) |
|
|
(if lbl |
|
|
(format "<a id=\"%s\"></a>\n\n" lbl) |
|
|
""))) |
|
|
ret) |
|
|
|
|
|
;; (message "[org-hugo-paragraph DBG] para 1: %s" contents) |
|
|
|
|
|
;; Join consecutive Chinese, Japanese lines into a single long |
|
|
;; line without unwanted space inbetween. |
|
|
(when (org-hugo--lang-cjk-p info) |
|
|
;; https://emacs-china.org/t/ox-hugo-auto-fill-mode-markdown/9547/5 |
|
|
;; Example: 这是一个测试 -> 这是一个测试文本 ("This is a test text") |
|
|
;; 文本 |
|
|
(setq contents (replace-regexp-in-string |
|
|
"\\([[:multibyte:]]\\)[[:blank:]]*\n[[:blank:]]*\\([[:multibyte:]]\\)" "\\1\\2" |
|
|
contents)) |
|
|
;; (message "[org-hugo-paragraph DBG] para 2: %s" contents) |
|
|
) |
|
|
|
|
|
(unless (org-hugo--plist-get-true-p info :hugo-preserve-filling) |
|
|
(setq contents (concat (mapconcat 'identity (split-string contents) " ") "\n"))) |
|
|
|
|
|
(setq contents (replace-regexp-in-string |
|
|
;; Glue footnotes to the words before them using |
|
|
;; so that the footnote reference does not |
|
|
;; end up on a new line by itself. |
|
|
;; "something FN" -> "something FN" |
|
|
"[[:blank:]]+\\(\\[\\^[^]]+\\]\\)" " \\1" |
|
|
(replace-regexp-in-string |
|
|
;; "FN ." -> "FN." |
|
|
"\\(\\[\\^[^]]+\\]\\)[[:blank:]]*\\([.]+\\)" "\\1\\2" |
|
|
contents))) |
|
|
;; (message "[org-hugo-paragraph DBG] para 3: %s" contents) |
|
|
(setq ret (concat label |
|
|
(org-md-paragraph paragraph contents info))) |
|
|
|
|
|
;; Wrap the paragraph with HTML div tag with user-specified |
|
|
;; attributes, unless the paragraph is a standalone image (or few |
|
|
;; other conditions as shown below). These conditions are taken |
|
|
;; from `org-html-paragraph'. |
|
|
(let* ((parent (org-export-get-parent paragraph)) |
|
|
(parent-type (org-element-type parent))) |
|
|
;; (message "[ox-hugo-para DBG] standalone image? %s\ncontents: %s" |
|
|
;; (org-html-standalone-image-p paragraph info) |
|
|
;; contents) |
|
|
(unless (or |
|
|
;; First paragraph in an item has no tag if it is alone |
|
|
;; or followed, at most, by a sub-list. |
|
|
(and (eq parent-type 'item) |
|
|
(not (org-export-get-previous-element paragraph info)) |
|
|
(let ((followers (org-export-get-next-element paragraph info 2))) |
|
|
(and (not (cdr followers)) |
|
|
(memq (org-element-type (car followers)) '(nil plain-list))))) |
|
|
;; Standalone image. |
|
|
(org-html-standalone-image-p paragraph info)) |
|
|
(setq ret (org-blackfriday--div-wrap-maybe paragraph ret)))) |
|
|
ret)) |
|
|
|
|
|
;;;; Source Blocks |
|
|
(defun org-hugo-src-block (src-block _contents info) |
|
|
"Convert SRC-BLOCK element to Hugo-compatible Markdown. |
|
|
|
|
|
The Markdown style triple-backquoted code blocks are created if: |
|
|
- If the HUGO_CODE_FENCE property is set to a non-nil value |
|
|
(default), |
|
|
- *AND* if none of the Hugo \"highlight\" shortcode features |
|
|
are needed (see below). |
|
|
|
|
|
The code block is wrapped in Hugo \"highlight\" shortcode (See |
|
|
https://gohugo.io/content-management/syntax-highlighting) if one |
|
|
of the above conditions is false. |
|
|
|
|
|
Note that even with the default non-nil value of HUGO_CODE_FENCE, |
|
|
the user *needs* to set the `pygmentsCodeFences' variable to |
|
|
`true' in their Hugo site's config, otherwise syntax highlighting |
|
|
will not work in the generated fenced code blocks! |
|
|
|
|
|
Hugo \"highlight\" shortcode features: |
|
|
- Code blocks with line numbers (if the -n or +n switch is used) |
|
|
- Highlight certains lines in the code block (if the :hl_lines |
|
|
parameter is used) |
|
|
|
|
|
CONTENTS is nil. INFO is a plist used as a communication |
|
|
channel." |
|
|
(let* ((lang (org-element-property :language src-block)) |
|
|
(parameters-str (org-element-property :parameters src-block)) |
|
|
(parameters (org-babel-parse-header-arguments parameters-str)) |
|
|
(is-fm-extra (cdr (assoc :front_matter_extra parameters))) |
|
|
(fm-format (plist-get info :hugo-front-matter-format))) |
|
|
;; (message "ox-hugo src [dbg] lang: %S" lang) |
|
|
;; (message "ox-hugo src [dbg] is-fm-extra: %S" is-fm-extra) |
|
|
;; (message "ox-hugo src [dbg] parameters: %S" parameters) |
|
|
(if (and is-fm-extra |
|
|
(member lang '("toml" "yaml"))) |
|
|
(progn |
|
|
;; The fm-extra src block lang and user-set fm-format have to |
|
|
;; be the same. Else. that src block is completely discarded. |
|
|
(when (string= lang fm-format) |
|
|
(let ((fm-extra (org-export-format-code-default src-block info))) |
|
|
;; (message "ox-hugo src [dbg] fm-extra: %S" fm-extra) |
|
|
(plist-put info :fm-extra fm-extra))) |
|
|
;; Do not export the `:front_matter_extra' TOML/YAML source |
|
|
;; blocks in Markdown body. |
|
|
nil) |
|
|
(let* (;; See `org-element-src-block-parser' for all SRC-BLOCK properties. |
|
|
(number-lines (org-element-property :number-lines src-block)) ;Non-nil if -n or +n switch is used |
|
|
(hl-lines (cdr (assoc :hl_lines parameters))) |
|
|
(hl-lines (cond |
|
|
((stringp hl-lines) |
|
|
(replace-regexp-in-string "," " " hl-lines)) ;"1,3-4" -> "1 3-4" |
|
|
((numberp hl-lines) |
|
|
(number-to-string hl-lines)))) |
|
|
(src-ref (org-blackfriday--get-reference src-block)) |
|
|
(src-anchor (if src-ref |
|
|
(format "<a id=\"%s\"></a>\n" src-ref) |
|
|
"")) |
|
|
(caption (org-export-get-caption src-block)) |
|
|
(caption-html (if (not caption) |
|
|
"" |
|
|
(let* ((src-block-num (org-export-get-ordinal |
|
|
src-block info |
|
|
nil #'org-html--has-caption-p)) |
|
|
(caption-prefix (org-blackfriday--translate 'src-block info)) |
|
|
(caption-str |
|
|
(org-html-convert-special-strings ;Interpret em-dash, en-dash, etc. |
|
|
(org-export-data-with-backend caption 'html info)))) |
|
|
(format (concat "\n\n<div class=\"src-block-caption\">\n" |
|
|
" <span class=\"src-block-number\">%s</span>:\n" |
|
|
" %s\n" |
|
|
"</div>") |
|
|
(if src-ref ;Hyperlink the code snippet prefix + number |
|
|
(format "<a href=\"#%s\">%s %s</a>" |
|
|
src-ref caption-prefix src-block-num) |
|
|
(format "%s %s" |
|
|
caption-prefix src-block-num)) |
|
|
caption-str)))) |
|
|
content |
|
|
ret) |
|
|
;; (message "ox-hugo src [dbg] number-lines: %S" number-lines) |
|
|
;; (message "ox-hugo src [dbg] parameters: %S" parameters) |
|
|
(setq content |
|
|
(cond |
|
|
;; If both number-lines and hl-lines are nil |
|
|
;; , AND if :hugo-code-fence is non-nil (which is, by default). |
|
|
((and (null number-lines) |
|
|
(null hl-lines) |
|
|
(org-hugo--plist-get-true-p info :hugo-code-fence)) |
|
|
(let ((content1 (org-blackfriday-src-block src-block nil info))) |
|
|
(when (and org-hugo-langs-no-descr-in-code-fences |
|
|
(member (intern lang) org-hugo-langs-no-descr-in-code-fences)) |
|
|
;; When using Pygments, with the pygmentsCodeFences |
|
|
;; options enabled in Hugo, `org' is not recognized as a |
|
|
;; "language", because Pygments does not have a lexer for |
|
|
;; Org. |
|
|
;; Issue on Pygments repo: |
|
|
;; https://bitbucket.org/birkenfeld/pygments-main/issues/719/wishlist-support-org |
|
|
;; So attempt to do below: |
|
|
;; ```org |
|
|
;; # Org comment |
|
|
;; ``` |
|
|
;; will not result in a <code> tag wrapped block in HTML. |
|
|
;; |
|
|
;; So override the language to be an empty string in such cases. |
|
|
;; |
|
|
;; *Note* that this issue does NOT exist if using Chroma, |
|
|
;; which is the default syntax highlighter after Hugo |
|
|
;; v0.28. |
|
|
(setq content1 (replace-regexp-in-string (concat "\\`\\(```+\\)" lang) "\\1" content1))) |
|
|
(setq content1 (org-hugo--escape-hugo-shortcode content1 lang)) |
|
|
content1)) |
|
|
;; If number-lines is non-nil |
|
|
;; , or if hl-lines is non-nil |
|
|
;; , or if :hugo-code-fence is nil |
|
|
(t |
|
|
(let ((code (org-export-format-code-default src-block info)) |
|
|
(linenos-str "") |
|
|
(hllines-str "") |
|
|
;; Formatter string where the first arg si linenos-str and |
|
|
;; second is hllines-str. |
|
|
(highlight-args-str "%s%s")) |
|
|
(when (or number-lines |
|
|
hl-lines) |
|
|
(setq highlight-args-str " \"%s%s\"")) |
|
|
(when number-lines |
|
|
(let ((linenostart-str (progn |
|
|
;; Extract the start line number of the code block |
|
|
(string-match "\\`\\s-*\\([0-9]+\\)\\s-\\{2\\}" code) |
|
|
(match-string-no-properties 1 code)))) |
|
|
(setq linenos-str (format "linenos=table, linenostart=%s" linenostart-str))) |
|
|
;; Remove Org-inserted numbers from the beginning of each |
|
|
;; line as the Hugo highlight shortcode will be used instead |
|
|
;; of literally inserting the line numbers. |
|
|
(setq code (replace-regexp-in-string "^\\s-*[0-9]+\\s-\\{2\\}" "" code))) |
|
|
(when hl-lines |
|
|
(setq hllines-str (concat "hl_lines=" hl-lines)) |
|
|
(when number-lines |
|
|
(setq hllines-str (concat ", " hllines-str)))) |
|
|
(setq code (org-hugo--escape-hugo-shortcode code lang)) |
|
|
(format "{{< highlight %s%s >}}\n%s{{< /highlight >}}\n" |
|
|
lang |
|
|
(format highlight-args-str linenos-str hllines-str) |
|
|
code))))) |
|
|
(setq ret (concat src-anchor content caption-html)) |
|
|
(setq ret (org-blackfriday--div-wrap-maybe src-block ret)) |
|
|
ret)))) |
|
|
|
|
|
;;;; Special Block |
|
|
(defun org-hugo-special-block (special-block contents info) |
|
|
"Transcode a SPECIAL-BLOCK element from Org to Hugo-compatible Markdown. |
|
|
CONTENTS holds the contents of the block. |
|
|
|
|
|
If the special block is of type \"description\", the value of |
|
|
`:description' key of the INFO plist gets overwritten by the |
|
|
contents of that block. |
|
|
|
|
|
Else if the special block is of type \"details\", an HTML |
|
|
`<details>' element with an optional `<summary>' element is |
|
|
created. The \"summary\" portion if present comes first, and is |
|
|
separated from the following \"details\" portion using a solo |
|
|
\"---\" string on a newline. See |
|
|
https://ox-hugo.scripter.co/doc/details-and-summary/ for more. |
|
|
|
|
|
Else if the SPECIAL-BLOCK type matches one of the shortcodes set |
|
|
in HUGO_PAIRED_SHORTCODES property, export them as Markdown or |
|
|
non-Markdown shortcodes. See `org-hugo-paired-shortcodes' for |
|
|
more information. |
|
|
|
|
|
For all other special blocks, processing is passed on to |
|
|
`org-blackfriday-special-block'. |
|
|
|
|
|
INFO is a plist holding export options." |
|
|
(let ((block-type (org-element-property :type special-block)) |
|
|
(paired-shortcodes (let* ((str (plist-get info :hugo-paired-shortcodes)) |
|
|
(str-list (when (org-string-nw-p str) |
|
|
(split-string str " ")))) |
|
|
str-list)) |
|
|
(sc-regexp "\\`%%?%s\\'") ;Regexp to match an element from `paired-shortcodes' |
|
|
(contents (when (stringp contents) |
|
|
(org-trim contents)))) |
|
|
(when contents |
|
|
(cond |
|
|
((string= block-type "description") |
|
|
;; Overwrite the value of the `:description' key in `info'. |
|
|
(plist-put info :description contents) |
|
|
nil) |
|
|
;; https://emacs.stackexchange.com/a/28685/115 |
|
|
((cl-member block-type paired-shortcodes |
|
|
;; If `block-type' is "foo", check if any of the |
|
|
;; elements in `paired-shortcodes' is "foo" or |
|
|
;; "%foo". |
|
|
:test (lambda (b sc) ;`sc' would be an element from `paired-shortcodes' |
|
|
(string-match-p (format sc-regexp b) sc))) |
|
|
(let* ((attr-sc (org-export-read-attribute :attr_shortcode special-block)) |
|
|
;; Positional arguments. |
|
|
(pos-args (and (null attr-sc) |
|
|
;; If the shortcode attributes are not of |
|
|
;; the type ":foo bar" but are something |
|
|
;; like "foo bar". |
|
|
(let* ((raw-list (org-element-property :attr_shortcode special-block)) |
|
|
(raw-str (mapconcat #'identity raw-list " "))) |
|
|
(org-string-nw-p raw-str)))) |
|
|
;; Named arguments. |
|
|
(named-args (unless pos-args |
|
|
(org-string-nw-p (org-html--make-attribute-string attr-sc)))) |
|
|
(sc-args (or pos-args named-args)) |
|
|
(sc-args (if sc-args |
|
|
(concat " " sc-args " ") |
|
|
" ")) |
|
|
(matched-sc-str (car |
|
|
(cl-member block-type paired-shortcodes |
|
|
:test (lambda (b sc) ;`sc' would be an element from `paired-shortcodes' |
|
|
(string-match-p (format sc-regexp b) sc))))) |
|
|
(sc-open-char (if (string-prefix-p "%" matched-sc-str) |
|
|
"%" |
|
|
"<")) |
|
|
(sc-close-char (if (string-prefix-p "%" matched-sc-str) |
|
|
"%" |
|
|
">")) |
|
|
(sc-begin (format "{{%s %s%s%s}}" |
|
|
sc-open-char block-type sc-args sc-close-char)) |
|
|
(sc-end (format "{{%s /%s %s}}" |
|
|
sc-open-char block-type sc-close-char))) |
|
|
;; (message "[ox-hugo-spl-blk DBG] attr-sc1: %s" |
|
|
;; (org-element-property :attr_shortcode special-block)) |
|
|
;; (message "[ox-hugo-spl-blk DBG] attr-sc: %s" attr-sc) |
|
|
;; (message "[ox-hugo-spl-blk DBG] pos-args: %s" pos-args) |
|
|
;; (message "[ox-hugo-spl-blk DBG] named-args: %s" named-args) |
|
|
(format "%s\n%s\n%s" |
|
|
sc-begin contents sc-end))) |
|
|
(t |
|
|
(org-blackfriday-special-block special-block contents nil)))))) |
|
|
|
|
|
|
|
|
|
|
|
;;; Filter Functions |
|
|
|
|
|
;;;; Body Filter |
|
|
(defun org-hugo-body-filter (body _backend info) |
|
|
"Add front-matter to the BODY of the document. |
|
|
|
|
|
BODY is the result of the export. |
|
|
INFO is a plist holding export options." |
|
|
;; Copy the page resources to the bundle directory. |
|
|
(org-hugo--maybe-copy-resources info) |
|
|
;; (message "[ox-hugo body filter] ITEM %S" (org-entry-get (point) "ITEM")) |
|
|
;; (message "[ox-hugo body filter] TAGS: %S" (org-entry-get (point) "TAGS")) |
|
|
;; (message "[ox-hugo body filter] ALLTAGS: %S" (org-entry-get (point) "ALLTAGS")) |
|
|
|
|
|
;; `org-md-plain-text' would have escaped all underscores in plain |
|
|
;; text i.e. "_" would have been converted to "\_". |
|
|
;; We need to undo that underscore escaping in Emoji codes for those |
|
|
;; to work. |
|
|
;; Example: Convert ":raised\_hands:" back to ":raised_hands:". |
|
|
;; More Emoji codes: https://www.emoji.codes/ |
|
|
;; (Requires setting "enableEmoji = true" in config.toml.) |
|
|
(setq body (replace-regexp-in-string |
|
|
"\\(:[a-z0-9]+\\)[\\]\\(_[a-z0-9]+:\\)" |
|
|
"\\1\\2" |
|
|
body)) |
|
|
|
|
|
(when (and (org-hugo--plist-get-true-p info :hugo-delete-trailing-ws) |
|
|
(not (org-hugo--plist-get-true-p info :preserve-breaks))) |
|
|
(setq body (with-temp-buffer |
|
|
(insert body) |
|
|
(delete-trailing-whitespace (point-min) nil) |
|
|
(buffer-substring-no-properties (point-min) (point-max))))) |
|
|
(let ((fm (save-excursion |
|
|
(save-restriction |
|
|
;; The point is at the beginning of the heading body |
|
|
;; in this function! So move the point back by 1 char |
|
|
;; to bring it into the Org headline before calling |
|
|
;; `org-hugo--get-front-matter', because in there we |
|
|
;; use `org-entry-get' at (point) to retrieve certain |
|
|
;; property values. |
|
|
(widen) |
|
|
(ignore-errors ;If the point is at beginning of buffer even after widening |
|
|
(backward-char)) |
|
|
;; (message "[body filter DBG] line at pt: %s" (thing-at-point 'line)) |
|
|
(org-hugo--get-front-matter info)))) |
|
|
(fm-extra (plist-get info :fm-extra)) |
|
|
(body (if (org-string-nw-p body) ;Insert extra newline if body is non-empty |
|
|
(format "\n%s" body) |
|
|
""))) |
|
|
;; (message "[body filter DBG fm] %S" fm) |
|
|
;; (message "[body filter DBG fm-extra] %S" fm-extra) |
|
|
(when fm-extra |
|
|
;; If fm-extra is present, append it to the end of the |
|
|
;; front-matter, before the closing "+++" or "---" marker. |
|
|
(setq fm (replace-regexp-in-string "\\(\\+\\+\\+\\|---\\)\n*\\'" |
|
|
(concat fm-extra "\\&") |
|
|
fm))) |
|
|
(setq org-hugo--fm fm) |
|
|
(if (org-hugo--pandoc-citations-enabled-p info) |
|
|
(format "%s%s%s" org-hugo--fm-yaml body org-hugo-footer) |
|
|
(format "%s%s%s" fm body org-hugo-footer)))) |
|
|
|
|
|
;;;;; Hugo Front-Matter |
|
|
(defun org-hugo--quote-string (val &optional prefer-no-quotes format) |
|
|
"Wrap VAL with quotes as appropriate. |
|
|
|
|
|
VAL can be a string, symbol, number or nil. |
|
|
|
|
|
If VAL contains newlines, format it according to TOML or YAML |
|
|
FORMAT to preserve them. |
|
|
|
|
|
VAL is returned as-it-is under the following cases: |
|
|
- It is a number. |
|
|
- It is a string and is already wrapped with double quotes. |
|
|
- It is a string and it's value is \"true\" or \"false\". |
|
|
- It is a string representing a date. |
|
|
- It is a string representing an integer or float. |
|
|
|
|
|
If VAL is nil or an empty string, a quoted empty string \"\" is |
|
|
returned. |
|
|
|
|
|
If optional argument PREFER-NO-QUOTES is non-nil, return the VAL |
|
|
as-it-is if it's a string with just alphanumeric characters. |
|
|
|
|
|
Optional argument FORMAT can be \"toml\" or \"yaml\"." |
|
|
(cond |
|
|
((null val) ;nil |
|
|
val) |
|
|
((numberp val) |
|
|
val) |
|
|
((symbolp val) |
|
|
(format "\"%s\"" (symbol-name val))) |
|
|
((stringp val) |
|
|
(cond |
|
|
((org-string-nw-p val) ;If `val' is a non-empty string |
|
|
(cond |
|
|
((or (and (string= (substring val 0 1) "\"") ;First char is literally a " |
|
|
(string= (substring val -1) "\"")) ;Last char is literally a " |
|
|
(and prefer-no-quotes ;If quotes are not preferred and `val' is only alpha-numeric |
|
|
(string-match-p "\\`[a-zA-Z0-9]+\\'" val)) |
|
|
;; or if it an integer that can be stored in the system as |
|
|
;; a fixnum. For example, if `val' is |
|
|
;; "10040216507682529280" that needs more than 64 bits to |
|
|
;; be stored as a signed integer, it will be automatically |
|
|
;; stored as a float. So (integerp (string-to-number |
|
|
;; val)) will return nil [or `fixnump' instead of |
|
|
;; `integerp' in Emacs 27 or newer] |
|
|
;; https://github.com/toml-lang/toml#integer Integer |
|
|
;; examples: 7, +7, -7, 7_000 |
|
|
(and (string-match-p "\\`[+-]?[[:digit:]_]+\\'" val) |
|
|
(if (functionp #'fixnump) ;`fixnump' and `bignump' get introduced in Emacs 27.x |
|
|
(fixnump (string-to-number val)) |
|
|
(integerp (string-to-number val)))) ;On older Emacsen, `integerp' behaved the same as the new `fixnump' |
|
|
(string= "true" val) |
|
|
(string= "false" val) |
|
|
;; or if it is a date (date, publishDate, expiryDate, lastmod) |
|
|
(string-match-p org-hugo--date-time-regexp val) |
|
|
;; or if it is a float |
|
|
;; https://github.com/toml-lang/toml#float |
|
|
;; Float examples (decimals): 7.8, +7.8, -7.8 |
|
|
(string-match-p "\\`[+-]?[[:digit:]_]+\\.[[:digit:]_]+\\'" val) |
|
|
;; Float examples (exponentials): 7e-8, -7E+8, 1.7e-05 |
|
|
(string-match-p "\\`[+-]?[[:digit:]_]+\\(\\.[[:digit:]_]+\\)*[eE][+-]?[[:digit:]_]+\\'" val) |
|
|
;; Special float values (infinity/NaN) |
|
|
;; Looks like Hugo is not supporting these.. Tue Mar 20 18:05:40 EDT 2018 - kmodi |
|
|
;; (let ((case-fold-search nil)) |
|
|
;; (string-match-p "\\`[+-]?\\(inf\\|nan\\)\\'" val)) |
|
|
) |
|
|
val) |
|
|
((string-match-p "\n" val) ;Multi-line string |
|
|
;; The indentation of the multi-line string is needed only for the |
|
|
;; YAML format. But the same is done for TOML too just for better |
|
|
;; presentation. |
|
|
(setq val (replace-regexp-in-string "^" " " val)) |
|
|
|
|
|
(if (and (stringp format) |
|
|
(string= format "yaml")) |
|
|
(progn |
|
|
;; https://yaml-multiline.info/ |
|
|
;; |
|
|
;; | |foo : > |
|
|
;; |abc | abc |
|
|
;; | >>> | |
|
|
;; |def | |
|
|
;; | | def |
|
|
;; |
|
|
;; In Org, a single blank line is used to start a new |
|
|
;; paragraph. In the YAML multi-line string, that needs to |
|
|
;; be 2 blank lines. |
|
|
(setq val (replace-regexp-in-string "\n[[:blank:]]*\n" "\n\n\n" val)) |
|
|
(format ">\n%s" val)) |
|
|
;; Escape the backslashes (only for multi-line TOML). |
|
|
(setq val (replace-regexp-in-string "\\\\" "\\\\\\\\" val)) |
|
|
|
|
|
;; Remove indentation/space from blank lines if any. |
|
|
(setq val (replace-regexp-in-string "\n[[:blank:]]*\n" "\n\n" val)) |
|
|
(format "\"\"\"\n%s\n \"\"\"" val))) ;Triple-quote |
|
|
(t ;Single-line string |
|
|
;; Below 2 replacements are order-dependent.. first escape the |
|
|
;; backslashes, then escape the quotes with backslashes. |
|
|
|
|
|
;; Escape the backslashes (for both TOML and YAML). |
|
|
(setq val (replace-regexp-in-string "\\\\" "\\\\\\\\" val)) |
|
|
;; Escape the double-quotes. |
|
|
(setq val (replace-regexp-in-string "\"" "\\\\\"" val)) |
|
|
(concat "\"" val "\"")))) |
|
|
(t ;If `val' is any empty string |
|
|
"\"\""))) |
|
|
(t ;Return empty string if anything else |
|
|
"\"\""))) |
|
|
|
|
|
(defun org-hugo--parse-property-arguments (str) |
|
|
"Return an alist converted from a string STR of Hugo property value. |
|
|
|
|
|
STR is of type \":KEY1 VALUE1 :KEY2 VALUE2 ..\". Given that, the |
|
|
returned value is ((KEY1 . VALUE1) (KEY2 . VALUE2) ..). |
|
|
|
|
|
Example: Input STR \":foo bar :baz 1 :zoo \\\"two words\\\"\" would |
|
|
convert to ((foo . \"bar\") (baz . 1) (zoo . \"two words\"))." |
|
|
(let ((alist (org-babel-parse-header-arguments str))) |
|
|
(dolist (pair alist) |
|
|
;; :KEY -> KEY |
|
|
(let ((key (intern (replace-regexp-in-string "\\`:" "" (symbol-name (car pair)))))) |
|
|
(setcar pair key))) |
|
|
alist)) |
|
|
|
|
|
(defun org-hugo--front-matter-value-booleanize (str) |
|
|
"Return a \"true\" or \"false\" string for input STR." |
|
|
(let ((str-lower (and (stringp str) |
|
|
(downcase str)))) |
|
|
(cond |
|
|
((or (null str) |
|
|
(string= "nil" str-lower) |
|
|
(string= "false" str-lower) |
|
|
(string= "no" str-lower)) |
|
|
"false") |
|
|
((or (string= "t" str) |
|
|
(string= "true" str) |
|
|
(string= "yes" str)) |
|
|
"true") |
|
|
(t |
|
|
(user-error "%S needs to represent a boolean value" str))))) |
|
|
|
|
|
(defun org-hugo--parse-blackfriday-prop-to-alist (str) |
|
|
"Return an alist of valid Hugo blackfriday properties converted from STR. |
|
|
|
|
|
For example, input STR: |
|
|
|
|
|
\":fractions :smartdashes nil :angledquotes t\" |
|
|
|
|
|
would convert to: |
|
|
|
|
|
((fractions . \"false\") (smartDashes . \"false\") (angledQuotes . \"true\")) |
|
|
|
|
|
The \"true\" and \"false\" strings in the return value are due to |
|
|
`org-hugo--front-matter-value-booleanize'." |
|
|
(let ((blackfriday-alist (org-hugo--parse-property-arguments str)) |
|
|
valid-blackfriday-alist) |
|
|
(dolist (ref-prop org-hugo-blackfriday-options) |
|
|
(dolist (user-prop blackfriday-alist) |
|
|
(when (string= (downcase (symbol-name (car user-prop))) |
|
|
(downcase ref-prop)) |
|
|
(let* ((key (intern ref-prop)) |
|
|
(value (cdr user-prop)) |
|
|
(value (if (or (equal key 'extensions) |
|
|
(equal key 'extensionsmask)) |
|
|
(org-hugo--delim-str-to-list value) |
|
|
(org-hugo--front-matter-value-booleanize value)))) |
|
|
(push (cons key value) |
|
|
valid-blackfriday-alist))))) |
|
|
valid-blackfriday-alist)) |
|
|
|
|
|
(defun org-hugo--return-valid-blackfriday-extension (ext) |
|
|
"Return valid case-sensitive string for Blackfriday extension EXT. |
|
|
|
|
|
Example: If EXT is \"hardlinebreak\", |
|
|
\"\"hardLineBreak\"\" (quoted string) is returned." |
|
|
(let (ret) |
|
|
(dolist (ref-ext org-hugo-blackfriday-extensions) |
|
|
;; (message "ox-hugo bf valid ext DBG: ext=%s ref-ext=%s" ext ref-ext) |
|
|
(when (string= (downcase ext) (downcase ref-ext)) |
|
|
(setq ret ref-ext))) |
|
|
(unless ret |
|
|
(user-error "Invalid Blackfriday extension name %S, see `org-hugo-blackfriday-extensions'" |
|
|
ext)) |
|
|
(org-hugo--quote-string ret))) |
|
|
|
|
|
(defun org-hugo--parse-menu-prop-to-alist (str) |
|
|
"Return an alist of valid Hugo menu properties converted from STR. |
|
|
|
|
|
Example: Input STR \":name foo :weight 80\" would convert |
|
|
to ((name . \"foo\") (weight . 80))." |
|
|
(let ((menu-alist (org-hugo--parse-property-arguments str)) |
|
|
valid-menu-alist) |
|
|
;; Hugo menu properties: https://gohugo.io/content-management/menus/ |
|
|
;; "title" property for menus was introduced in Hugo v0.32. |
|
|
;; https://github.com/gohugoio/hugo/commit/9df3736fec164c51d819797416dc263f2869be77 |
|
|
(dolist (prop '(menu name url identifier pre post weight parent title)) ;children prop is probably read-only |
|
|
(let ((cell (assoc prop menu-alist))) |
|
|
(when cell |
|
|
(push cell valid-menu-alist)))) |
|
|
valid-menu-alist)) |
|
|
|
|
|
(defun org-hugo--get-sanitized-title (info) |
|
|
"Return sanitized version of an Org headline TITLE as a string. |
|
|
|
|
|
INFO is a plist used as a communication channel. |
|
|
|
|
|
Extract the document title from INFO (unless exporting title is |
|
|
disabled by setting `org-export-with-title' to nil or using the |
|
|
OPTIONS keyword e.g. \"title:nil\"). |
|
|
|
|
|
If the extracted document title is nil, and exporting the title |
|
|
is disabled, return nil. |
|
|
|
|
|
If the extracted document title is non-nil, return it after |
|
|
removing all markup characters. |
|
|
|
|
|
Also double-quote the title if it doesn't already contain any |
|
|
double-quotes." |
|
|
(let ((title (when (plist-get info :with-title) |
|
|
(plist-get info :title)))) |
|
|
(when title |
|
|
;; "Raw" backend that returns emphasis elements without any |
|
|
;; markup characters -- |
|
|
;; http://lists.gnu.org/r/emacs-orgmode/2017-12/msg00490.html |
|
|
(let* ((raw-backend |
|
|
|
|
|
(let ((get-raw (lambda (object contents _) |
|
|
(or contents |
|
|
(org-element-property :value object))))) |
|
|
(org-export-create-backend |
|
|
:parent 'ascii |
|
|
:transcoders (mapcar (lambda (type) |
|
|
(cons type get-raw)) |
|
|
'(bold code italic strike-through |
|
|
underline verbatim)))))) |
|
|
(setq title (org-export-data-with-backend title raw-backend info)) |
|
|
;; Hugo does not render Markdown in the titles and so the |
|
|
;; Blackfriday smartDashes conversion does not work there. So |
|
|
;; do that here instead. Convert "---" to EM DASH, "--" to EN |
|
|
;; DASH, and "..." to HORIZONTAL ELLIPSIS. |
|
|
|
|
|
;; Below two replacements are order sensitive! |
|
|
(setq title (replace-regexp-in-string "---\\([^-]\\)" "—\\1" title)) ;EM DASH |
|
|
(setq title (replace-regexp-in-string "--\\([^-]\\)" "–\\1" title)) ;EN DASH |
|
|
|
|
|
(setq title (replace-regexp-in-string "\\.\\.\\." "…" title)) ;HORIZONTAL ELLIPSIS |
|
|
|
|
|
;; Double-quote the title so that even if the title contains |
|
|
;; just numbers or a date, it still gets rendered as a string |
|
|
;; type in Hugo. Do this only if the title doesn't already |
|
|
;; contain double-quotes. |
|
|
(unless (string-match "\"" title) |
|
|
(setq title (format "\"%s\"" title))))) |
|
|
title)) |
|
|
|
|
|
(defun org-hugo--replace-underscores-with-spaces (str) |
|
|
"Replace double underscores in STR with single spaces. |
|
|
|
|
|
For example, \"some__thing\" would get converted to \"some |
|
|
thing\"." |
|
|
;; It is safe to assume that no one would want leading/trailing |
|
|
;; spaces in `str'.. so not checking for "__a" or "a__" cases. |
|
|
(replace-regexp-in-string "\\([^_]\\)__\\([^_]\\)" "\\1 \\2" str)) ;"a__b" -> "a b" |
|
|
|
|
|
(defun org-hugo--tag-processing-fn-replace-with-spaces-maybe (tag-list info) |
|
|
"Replace double underscores in TAG-LIST elements with single spaces. |
|
|
|
|
|
For example, an element \"some__tag\" would get converted to |
|
|
\"some tag\". |
|
|
|
|
|
This replacement is enabled if `org-hugo-allow-spaces-in-tags' or |
|
|
HUGO_ALLOW_SPACES_IN_TAGS property is set to a non-nil value. |
|
|
|
|
|
TAG-LIST which is a list of Org tags of the type \(\"TAG1\" |
|
|
\"TAG2\" ..). INFO is a plist used as a communication channel. |
|
|
|
|
|
This is one of the processing functions in |
|
|
`org-hugo-tag-processing-functions'." |
|
|
(let ((allow-spaces (org-hugo--plist-get-true-p info :hugo-allow-spaces-in-tags))) |
|
|
(if allow-spaces |
|
|
(mapcar #'org-hugo--replace-underscores-with-spaces tag-list) |
|
|
tag-list))) |
|
|
|
|
|
(defun org-hugo--tag-processing-fn-replace-with-hyphens-maybe (tag-list info) |
|
|
"Replace single underscores in TAG-LIST elements with single hyphens. |
|
|
And triple underscores will be replaced with single underscores. |
|
|
|
|
|
For example, an element \"some_tag\" would get converted to |
|
|
\"some-tag\", and \"some___tag\" to \"some_tag\". |
|
|
|
|
|
This replacement is enabled if `org-hugo-prefer-hyphen-in-tags' |
|
|
or HUGO_PREFER_HYPHEN_IN_TAGS property is set to a non-nil value. |
|
|
|
|
|
TAG-LIST which is a list of Org tags of the type \(\"TAG1\" |
|
|
\"TAG2\" ..). INFO is a plist used as a communication channel. |
|
|
|
|
|
This is one of the processing functions in |
|
|
`org-hugo-tag-processing-functions'." |
|
|
(let ((prefer-hyphens (org-hugo--plist-get-true-p info :hugo-prefer-hyphen-in-tags))) |
|
|
(if prefer-hyphens |
|
|
(mapcar |
|
|
(lambda (tag) |
|
|
(setq tag (replace-regexp-in-string "\\`_\\([^_]\\)" "-\\1" tag)) ;"_a" -> "-a" |
|
|
(setq tag (replace-regexp-in-string "\\`___\\([^_]\\)" "_\\1" tag)) ;"___a" -> "_a" |
|
|
(setq tag (replace-regexp-in-string "\\([^_]\\)_\\'" "\\1-" tag)) ;"a_" -> "a-" |
|
|
(setq tag (replace-regexp-in-string "\\([^_]\\)___\\'" "\\1_" tag)) ;"a___" -> "a_" |
|
|
(setq tag (replace-regexp-in-string "\\([^_]\\)_\\([^_]\\)" "\\1-\\2" tag)) ;"a_b" -> "a-b" |
|
|
(setq tag (replace-regexp-in-string "\\([^_]\\)___\\([^_]\\)" "\\1_\\2" tag)) ;"a___b" -> "a_b" |
|
|
tag) |
|
|
tag-list) |
|
|
tag-list))) |
|
|
|
|
|
(defun org-hugo--delim-str-to-list (str) |
|
|
"Function to transform string STR to a list of strings. |
|
|
|
|
|
The function assumes STR to use |
|
|
`org-hugo--internal-list-separator' as delimiter. |
|
|
|
|
|
The function does the following in order: |
|
|
|
|
|
1. Trim leading/trailing spaces from STR. |
|
|
2. Convert that string to a list using |
|
|
`org-hugo--internal-list-separator' as the separator. |
|
|
3. Break up each element of that list into further string elements, |
|
|
delimited by spaces. Though, spaces within quoted string are |
|
|
retained. This is done using `org-babel-parse-header-arguments'. |
|
|
4. Return the transformed list of strings. |
|
|
|
|
|
Example: \"one\\n\\\"two words\\\" three\\nfour\" |
|
|
-> (\"one\" \"two words\" \"three\" \"four\"). |
|
|
|
|
|
Return nil if STR is not a string." |
|
|
(when (stringp str) |
|
|
(let* ((str (org-trim str)) |
|
|
(str-list (split-string str org-hugo--internal-list-separator)) |
|
|
ret) |
|
|
(dolist (str-elem str-list) |
|
|
(let* ((format-str ":dummy '(%s)") ;The :dummy key is discarded in the `lst' var below. |
|
|
(alist (org-babel-parse-header-arguments (format format-str str-elem))) |
|
|
(lst (cdr (car alist))) |
|
|
(str-list2 (mapcar (lambda (elem) |
|
|
(cond |
|
|
((symbolp elem) |
|
|
(symbol-name elem)) |
|
|
(t |
|
|
elem))) |
|
|
lst))) |
|
|
(setq ret (append ret str-list2)))) |
|
|
ret))) |
|
|
|
|
|
(defun org-hugo--category-p (tag) |
|
|
"Return non-nil if TAG begins with \"@\". |
|
|
|
|
|
Org tags that begin with \"@\" are set as the categories field in |
|
|
the Hugo front-matter." |
|
|
(and (stringp tag) |
|
|
(string-match-p "\\`@" tag))) |
|
|
|
|
|
(defun org-hugo--get-front-matter (info) |
|
|
"Return the Hugo front-matter string. |
|
|
|
|
|
INFO is a plist used as a communication channel." |
|
|
;; (message "[hugo front-matter DBG] info: %S" (pp info)) |
|
|
(let* ((fm-format (plist-get info :hugo-front-matter-format)) |
|
|
(author-list (and (plist-get info :with-author) |
|
|
(let ((author-raw |
|
|
(org-string-nw-p |
|
|
(org-export-data (plist-get info :author) info)))) ;`org-export-data' required |
|
|
(when author-raw |
|
|
;; Multiple authors can be comma or |
|
|
;; newline separated. The newline |
|
|
;; separated authors work only for the |
|
|
;; #+author keyword; example: |
|
|
;; #+author: Author1 |
|
|
;; #+author: Author2 |
|
|
;; |
|
|
;; If using the subtree properties they |
|
|
;; need to be comma-separated: |
|
|
;; :EXPORT_AUTHOR: Author1, Author2 |
|
|
(let ((author-list-1 (org-split-string author-raw "[,\n]"))) |
|
|
;; Don't allow spaces around author names. |
|
|
;; Also remove duplicate authors. |
|
|
(delete-dups (mapcar #'org-trim author-list-1))))))) |
|
|
(creator (and (plist-get info :with-creator) |
|
|
(plist-get info :creator))) |
|
|
(locale (and (plist-get info :hugo-with-locale) |
|
|
(org-hugo--get-lang info))) |
|
|
(description (org-string-nw-p (plist-get info :description))) |
|
|
(aliases-raw (let ((aliases-raw-1 (org-string-nw-p (plist-get info :hugo-aliases)))) |
|
|
(when aliases-raw-1 |
|
|
(org-split-string aliases-raw-1 " ")))) |
|
|
(aliases (let (alias-list) |
|
|
(dolist (alias aliases-raw) |
|
|
(unless (string-match-p "/" alias) |
|
|
(let ((section (file-name-as-directory ;Suffix section with "/" if it isn't already |
|
|
(org-export-data (plist-get info :hugo-section) info)))) |
|
|
(setq alias (concat "/" section alias)))) |
|
|
(setq alias-list (append alias-list `(,alias)))) |
|
|
alias-list)) |
|
|
(outputs-raw (org-string-nw-p (plist-get info :hugo-outputs))) |
|
|
(outputs (when outputs-raw |
|
|
(org-split-string outputs-raw " "))) |
|
|
(draft (org-hugo--parse-draft-state info)) |
|
|
(headless (when (org-hugo--plist-get-true-p info :hugo-headless) |
|
|
(org-hugo--front-matter-value-booleanize (org-hugo--plist-get-true-p info :hugo-headless)))) |
|
|
(all-t-and-c-str (org-entry-get (point) "ALLTAGS")) |
|
|
(all-t-and-c (when (stringp all-t-and-c-str) |
|
|
(org-split-string all-t-and-c-str ":"))) |
|
|
(tags (or |
|
|
;; Look for tags set using HUGO_TAGS keyword, or |
|
|
;; EXPORT_HUGO_TAGS property if available. |
|
|
(org-hugo--delim-str-to-list (plist-get info :hugo-tags)) |
|
|
;; Else use Org tags (the ones set in headlines |
|
|
;; and/or inherited) if any. |
|
|
(let* ((tags-list (cl-remove-if #'org-hugo--category-p all-t-and-c)) |
|
|
(tags-list (dolist (fn org-hugo-tag-processing-functions tags-list) |
|
|
(setq tags-list (funcall fn tags-list info))))) |
|
|
;; (message "[get fm DBG] tags: tags-list = %S" tags-list) |
|
|
tags-list))) |
|
|
(categories (or |
|
|
;; Look for categories set using HUGO_CATEGORIES |
|
|
;; keyword, or EXPORT_HUGO_CATEGORIES property |
|
|
;; if available. |
|
|
(org-hugo--delim-str-to-list (plist-get info :hugo-categories)) |
|
|
;; Else use categories set using Org tags with |
|
|
;; "@" prefix (the ones set in headlines and/or |
|
|
;; inherited) if any. |
|
|
(let* ((categories-list (cl-remove-if-not #'org-hugo--category-p all-t-and-c)) |
|
|
(categories-list (dolist (fn org-hugo-tag-processing-functions categories-list) |
|
|
(setq categories-list (funcall fn categories-list info)))) |
|
|
(categories-list (mapcar (lambda (str) |
|
|
;; Remove "@" from beg of categories. |
|
|
(replace-regexp-in-string "\\`@" "" str)) |
|
|
categories-list))) |
|
|
;; (message "dbg: categories: categories-list = %s" categories-list) |
|
|
categories-list))) |
|
|
(keywords (org-hugo--delim-str-to-list (plist-get info :keywords))) |
|
|
(weight-data (let ((wt-raw-list (org-hugo--parse-property-arguments (plist-get info :hugo-weight))) |
|
|
weight-data-1) |
|
|
(dolist (wt-raw wt-raw-list) |
|
|
(let (key value) |
|
|
;; (message "weight DBG wt-raw: %S" wt-raw) |
|
|
;; (message "weight DBG cdr wt-raw: %S" (cdr wt-raw)) |
|
|
;; (message "weight DBG org-hugo--subtree-coord: %S" org-hugo--subtree-coord) |
|
|
(cond |
|
|
((null (cdr wt-raw)) ;`wt-raw' is not of the type (TAXONOMY . WEIGHT) |
|
|
(setq key 'weight) |
|
|
(setq value (cond |
|
|
((and org-hugo--subtree-coord |
|
|
(equal (car wt-raw) 'auto)) ;(auto) |
|
|
(org-hugo--calc-weight)) |
|
|
((and (equal (car wt-raw) 'auto) ;Auto weight ineffective for file-based exports |
|
|
(null org-hugo--subtree-coord)) |
|
|
nil) |
|
|
(t |
|
|
(string-to-number (symbol-name (car wt-raw))))))) |
|
|
(t |
|
|
(setq key (if (equal (car wt-raw) 'page) ;`wt-raw' is of the type (page . WEIGHT) |
|
|
'weight |
|
|
(intern (format "%s_weight" (symbol-name (car wt-raw)))))) |
|
|
(setq value (cond |
|
|
((and org-hugo--subtree-coord |
|
|
(equal (cdr wt-raw) "auto")) ;(TAXONOMY . "auto") or (page . "auto") |
|
|
(org-hugo--calc-weight)) |
|
|
((numberp (cdr wt-raw)) |
|
|
(cdr wt-raw)) |
|
|
((and (equal (cdr wt-raw) "auto") ;Auto weight ineffective for file-based exports |
|
|
(null org-hugo--subtree-coord)) |
|
|
nil) |
|
|
(t |
|
|
(user-error "Ox-hugo: Invalid weight %S" (cdr wt-raw))))))) |
|
|
;; (message "weight DBG key: %S" key) |
|
|
;; (message "weight DBG value: %S" value) |
|
|
(push (cons key value) weight-data-1))) |
|
|
;; (message "weight DBG weight-data: %S" weight-data-1) |
|
|
weight-data-1)) |
|
|
(menu-alist (org-hugo--parse-menu-prop-to-alist (plist-get info :hugo-menu))) |
|
|
(menu-alist-override (org-hugo--parse-menu-prop-to-alist (plist-get info :hugo-menu-override))) |
|
|
;; If menu-alist-override is non-nil, update menu-alist with values from that. |
|
|
(menu-alist (let ((updated-menu-alist menu-alist)) |
|
|
(dolist (override-prop menu-alist-override) |
|
|
(let* ((override-key (car override-prop)) |
|
|
(override-val (cdr override-prop)) |
|
|
(matching-prop (assoc override-key updated-menu-alist))) |
|
|
(if matching-prop |
|
|
(setcdr matching-prop override-val) |
|
|
(push override-prop updated-menu-alist)))) |
|
|
updated-menu-alist)) |
|
|
(custom-fm-data (org-hugo--parse-property-arguments (plist-get info :hugo-custom-front-matter))) |
|
|
(resources (org-hugo--get-resources-alist |
|
|
(org-hugo--parse-property-arguments (plist-get info :hugo-resources)))) |
|
|
(blackfriday (org-hugo--parse-blackfriday-prop-to-alist (plist-get info :hugo-blackfriday))) |
|
|
(data `(;; The order of the elements below will be the order in which the front-matter |
|
|
;; variables will be ordered. |
|
|
(title . ,(org-hugo--get-sanitized-title info)) |
|
|
(audio . ,(plist-get info :hugo-audio)) |
|
|
(author . ,author-list) |
|
|
(description . ,description) |
|
|
(date . ,(org-hugo--format-date :date info)) |
|
|
(publishDate . ,(org-hugo--format-date :hugo-publishdate info)) |
|
|
(expiryDate . ,(org-hugo--format-date :hugo-expirydate info)) |
|
|
(aliases . ,aliases) |
|
|
(images . ,(org-hugo--delim-str-to-list (plist-get info :hugo-images))) |
|
|
(isCJKLanguage . ,(org-hugo--plist-get-true-p info :hugo-iscjklanguage)) |
|
|
(keywords . ,keywords) |
|
|
(layout . ,(plist-get info :hugo-layout)) |
|
|
(lastmod . ,(org-hugo--format-date :hugo-lastmod info)) |
|
|
(linkTitle . ,(plist-get info :hugo-linktitle)) |
|
|
(markup . ,(plist-get info :hugo-markup)) |
|
|
(outputs . ,outputs) |
|
|
(series . ,(org-hugo--delim-str-to-list (plist-get info :hugo-series))) |
|
|
(slug . ,(plist-get info :hugo-slug)) |
|
|
(tags . ,tags) |
|
|
(categories . ,categories) |
|
|
(type . ,(plist-get info :hugo-type)) |
|
|
(url . ,(plist-get info :hugo-url)) |
|
|
(videos . ,(org-hugo--delim-str-to-list (plist-get info :hugo-videos))) |
|
|
(draft . ,draft) |
|
|
(headless . ,headless) |
|
|
(creator . ,creator) |
|
|
(locale . ,locale) |
|
|
(blackfriday . ,blackfriday) |
|
|
(menu . ,menu-alist) |
|
|
(resources . ,resources))) |
|
|
(data `,(append data weight-data custom-fm-data)) |
|
|
ret) |
|
|
;; (message "[get fm DBG] tags: %s" tags) |
|
|
;; (message "dbg: hugo tags: %S" (plist-get info :hugo-tags)) |
|
|
;; (message "[get fm info DBG] %S" info) |
|
|
;; (message "[get fm blackfriday DBG] %S" blackfriday) |
|
|
;; (message "[get fm menu DBG] %S" menu-alist) |
|
|
;; (message "[get fm menu override DBG] %S" menu-alist-override) |
|
|
;; (message "[custom fm data DBG] %S" custom-fm-data) |
|
|
;; (message "[fm resources OUT DBG] %S" resources) |
|
|
;; (message "[fm data DBG] %S" data) |
|
|
;; (message "[fm tags DBG] %S" tags) |
|
|
;; (message "[fm categories DBG] %S" categories) |
|
|
;; (message "[fm keywords DBG] %S" keywords) |
|
|
(setq data (org-hugo--replace-keys-maybe data info)) |
|
|
(setq ret (org-hugo--gen-front-matter data fm-format)) |
|
|
(if (and (string= "toml" fm-format) |
|
|
(org-hugo--pandoc-citations-enabled-p info)) |
|
|
;; Pandoc parses fields like csl and nocite from YAML |
|
|
;; front-matter. So create the `org-hugo--fm-yaml' |
|
|
;; front-matter in YAML format just for Pandoc. |
|
|
(setq org-hugo--fm-yaml (org-hugo--gen-front-matter data "yaml")) |
|
|
(setq org-hugo--fm-yaml ret)) |
|
|
ret)) |
|
|
|
|
|
(defun org-hugo--calc-weight () |
|
|
"Calculate the weight for a Hugo post or menu item. |
|
|
|
|
|
The returned weight = INDEX + 1000*LEVEL. See |
|
|
`org-hugo--get-post-subtree-coordinates' learn about INDEX and |
|
|
LEVEL." |
|
|
(let* ((level (car org-hugo--subtree-coord)) |
|
|
(index (cdr org-hugo--subtree-coord))) |
|
|
(+ (* 1000 level) index))) |
|
|
|
|
|
(defun org-hugo--gen-front-matter (data format) |
|
|
"Generate the Hugo post front-matter, and return that string. |
|
|
|
|
|
DATA is an alist of the form \((KEY1 . VAL1) (KEY2 . VAL2) .. \), |
|
|
where KEY is a symbol and VAL is a string. |
|
|
|
|
|
Generate the front-matter in the specified FORMAT. Valid values |
|
|
are \"toml\" and \"yaml\"." |
|
|
(let ((sep (cond ((string= format "toml") "+++\n") |
|
|
((string= format "yaml") "---\n") |
|
|
(t ""))) |
|
|
(sign (cond ((string= format "toml") " =") ;Conventional to have space on the left side of "=" in TOML |
|
|
((string= format "yaml") ":") ;Conventional to have no space on the left side of ":" in YAML |
|
|
(t ""))) |
|
|
(front-matter "") |
|
|
(indent (make-string 2 ? )) |
|
|
(nested-string "") |
|
|
(menu-string "") |
|
|
(res-string "")) |
|
|
;; (message "hugo fm format: %s" format) |
|
|
(dolist (pair data) |
|
|
(let ((key (symbol-name (car pair))) |
|
|
(value (cdr pair))) |
|
|
;; (message "[hugo fm key value DBG] %S %S" key value) |
|
|
(unless (or (null value) ;Skip writing front-matter variables whose value is nil |
|
|
(and (stringp value) ;or an empty string. |
|
|
(string= "" value))) |
|
|
;; In TOML/YAML, the value portion needs to be wrapped in |
|
|
;; double quotes. |
|
|
;; TOML example: |
|
|
;; title = "My Post" |
|
|
;; YAML example: |
|
|
;; title: "My Post" |
|
|
|
|
|
;; In TOML, the menu information in the front-matter is as a |
|
|
;; table. So it needs to be always added to the end of the |
|
|
;; front-matter. So generate the `menu-string' separately |
|
|
;; and then append it to `front-matter' at the end. Do the |
|
|
;; same for blackfriday param values. |
|
|
(cond |
|
|
((string= key "menu") |
|
|
(unless (listp value) |
|
|
(user-error (concat "The `menu' front-matter did not get the expected " |
|
|
"list value; probably because HUGO_MENU was not " |
|
|
"used to set its value.\n" |
|
|
"Usage examples: \":EXPORT_HUGO_MENU: :menu main\" or " |
|
|
"\"#+hugo_menu: :menu main\""))) |
|
|
;; Menu name needs to be non-nil to insert menu info in front-matter. |
|
|
(when (assoc 'menu value) |
|
|
(let* ((menu-alist value) |
|
|
;; Menu entry string might need to be quoted if |
|
|
;; it contains spaces, for example. |
|
|
(menu-entry (org-hugo--quote-string (cdr (assoc 'menu menu-alist)) :prefer-no-quotes)) |
|
|
(menu-entry-str "") |
|
|
(menu-value-str "")) |
|
|
;; Auto-set menu identifier if not already set by user. |
|
|
(unless (assoc 'identifier menu-alist) |
|
|
(let ((title (cdr (assoc 'title data)))) |
|
|
(push `(identifier . ,(org-hugo-slug title)) menu-alist))) |
|
|
|
|
|
;; Auto-set menu weight if not already set by user. |
|
|
(unless (assoc 'weight menu-alist) |
|
|
(when org-hugo--subtree-coord |
|
|
(push `(weight . ,(org-hugo--calc-weight)) menu-alist))) |
|
|
|
|
|
;; (message "[menu alist DBG] = %S" menu-alist) |
|
|
(when menu-entry |
|
|
(setq menu-entry-str (cond ((string= format "toml") |
|
|
(format "[menu.%s]\n" menu-entry)) |
|
|
((string= format "yaml") |
|
|
(prog1 |
|
|
(format "menu%s\n%s%s%s\n" |
|
|
sign indent menu-entry sign) |
|
|
(setq indent (concat indent indent)))) ;Double the indent for next use |
|
|
(t |
|
|
""))) |
|
|
(dolist (menu-pair menu-alist) |
|
|
(let ((menu-key (symbol-name (car menu-pair))) |
|
|
(menu-value (cdr menu-pair))) |
|
|
;; (message "menu DBG: %S %S %S" menu-entry menu-key menu-value) |
|
|
(unless (string= "menu" menu-key) |
|
|
(when menu-value |
|
|
;; Cannot skip quote wrapping for values of keys inside menu. |
|
|
;; Attempting to do: |
|
|
;; [menu.foo] |
|
|
;; parent = main |
|
|
;; # parent = "main" # But this works |
|
|
;; gives this error: |
|
|
;; ERROR 2017/07/21 10:56:07 failed to parse page metadata |
|
|
;; for "singles/post-draft.md": Near line 10 (last key parsed |
|
|
;; 'menu.foo.parent'): expected value but found "main" instead. |
|
|
(setq menu-value (org-hugo--quote-string menu-value)) |
|
|
(setq menu-value-str |
|
|
(concat menu-value-str |
|
|
(format "%s%s%s %s\n" |
|
|
indent menu-key sign menu-value))))))) |
|
|
(setq menu-string (concat menu-entry-str menu-value-str)))))) |
|
|
((string= key "resources") |
|
|
(unless (listp value) |
|
|
(user-error (concat "The `resources' front-matter did not get the expected " |
|
|
"list value; probably because HUGO_RESOURCES was not " |
|
|
"used to set its value.\n" |
|
|
"Usage examples: \":EXPORT_HUGO_RESOURCES: :src \"my-image.png\" :title \"My Image\" " |
|
|
"or \"#+hugo_resources: :src \"my-image.png\" :title \"My Image\""))) |
|
|
(when value |
|
|
(dolist (res-alist value) |
|
|
(let ((res-entry-str "") |
|
|
(res-value-str "") |
|
|
res-src-present |
|
|
res-param-str) |
|
|
(setq res-entry-str (cond ((string= format "toml") |
|
|
"[[resources]]\n") |
|
|
((string= format "yaml") |
|
|
;; For YAML, this string |
|
|
;; needs to be inserted |
|
|
;; only once. |
|
|
(if (org-string-nw-p res-string) |
|
|
"" |
|
|
(format "resources%s\n" sign))) |
|
|
(t |
|
|
""))) |
|
|
(dolist (res-pair res-alist) |
|
|
;; (message "[resources DBG] res-pair: %S" res-pair) |
|
|
(let* ((res-key (symbol-name (car res-pair))) |
|
|
(res-value (cdr res-pair))) |
|
|
;; (message "[resources DBG]: %S %S" res-key res-value) |
|
|
(cond ((string= res-key "params") |
|
|
(setq indent (make-string 4 ? )) |
|
|
(setq res-param-str (cond ((string= format "toml") |
|
|
(format " [resources.%s]\n" res-key)) |
|
|
((string= format "yaml") |
|
|
(format " %s%s\n" res-key sign)) |
|
|
(t |
|
|
""))) |
|
|
(dolist (param-pair res-value) ;res-value would be an alist of params |
|
|
(let ((param-key (symbol-name (car param-pair))) |
|
|
(param-value (cdr param-pair)) |
|
|
param-value-str) |
|
|
;; (message "[resources DBG] param-key: %S" param-key) |
|
|
;; (message "[resources DBG] param-value: %S" param-value) |
|
|
(setq param-value-str (if (listp param-value) |
|
|
(org-hugo--get-yaml-toml-list-string param-key param-value) |
|
|
(org-hugo--quote-string param-value))) |
|
|
(setq res-param-str |
|
|
(concat res-param-str |
|
|
(format "%s%s%s %s\n" |
|
|
indent param-key sign param-value-str))))) |
|
|
;; (message "[resources params DBG] %s" res-param-str) |
|
|
) |
|
|
(t |
|
|
(when (string= res-key "src") |
|
|
(setq res-src-present t)) |
|
|
(if (and (string= res-key "src") |
|
|
(string= format "yaml")) |
|
|
(setq indent "- ") |
|
|
(setq indent " ")) |
|
|
(setq res-value (org-hugo--quote-string res-value)) |
|
|
(setq res-value-str |
|
|
(concat res-value-str |
|
|
(format "%s%s%s %s\n" |
|
|
indent res-key sign res-value))))))) |
|
|
(unless res-src-present |
|
|
(user-error "`src' must be set for the `resources'")) |
|
|
(setq res-string (concat res-string res-entry-str res-value-str res-param-str)))))) |
|
|
(;; Front-matter with nested map values: blackfriday, custom front-matter. |
|
|
;; Only 1 level of nesting is supported. |
|
|
(and (listp value) ;Example value: '((legs . 4) ("eyes" . 2) (friends . (poo boo))) |
|
|
(eq 0 (cl-count-if (lambda (el) ;Check if value is a list of lists (or conses) |
|
|
(not (listp el))) |
|
|
value))) |
|
|
(let ((nested-parent-key key) |
|
|
(nested-alist value) |
|
|
(nested-parent-key-str "") |
|
|
(nested-keyval-str "")) |
|
|
;; (message "[nested entry DBG] = %s" nested-parent-key) |
|
|
;; (message "[nested alist DBG] = %S" nested-alist) |
|
|
(setq nested-parent-key-str (cond ((string= format "toml") |
|
|
(format "[%s]\n" nested-parent-key)) |
|
|
((string= format "yaml") |
|
|
(format "%s%s\n" nested-parent-key sign)) |
|
|
(t |
|
|
""))) |
|
|
(dolist (nested-pair nested-alist) |
|
|
(unless (consp nested-pair) |
|
|
(user-error "Ox-hugo: Custom front-matter values with nested maps need to be an alist of conses")) |
|
|
;; (message "[nested pair DBG] = %S" nested-pair) |
|
|
(let* ((nested-key (car nested-pair)) |
|
|
(nested-key (cond |
|
|
((symbolp nested-key) |
|
|
(symbol-name nested-key)) |
|
|
(t |
|
|
nested-key))) |
|
|
(nested-value (cdr nested-pair)) |
|
|
(nested-value (cond |
|
|
((and nested-value |
|
|
(listp nested-value)) |
|
|
(if (and (string= nested-parent-key "blackfriday") |
|
|
(or (string= nested-key "extensions") |
|
|
(string= nested-key "extensionsmask"))) |
|
|
(org-hugo--get-yaml-toml-list-string |
|
|
nested-key |
|
|
(mapcar #'org-hugo--return-valid-blackfriday-extension |
|
|
nested-value)) |
|
|
(org-hugo--get-yaml-toml-list-string nested-key nested-value))) |
|
|
((null nested-value) |
|
|
"false") |
|
|
((equal nested-value 't) |
|
|
"true") |
|
|
(t |
|
|
(org-hugo--quote-string nested-value))))) |
|
|
;; (message "nested DBG: %S KEY %S->%S VALUE %S->%S" nested-parent-key |
|
|
;; (car nested-pair) nested-key |
|
|
;; (cdr nested-pair) nested-value) |
|
|
(when nested-value |
|
|
(setq nested-keyval-str |
|
|
(concat nested-keyval-str |
|
|
(format "%s%s%s %s\n" |
|
|
indent nested-key sign nested-value)))))) |
|
|
(when (org-string-nw-p nested-keyval-str) |
|
|
(setq nested-string (concat nested-string nested-parent-key-str |
|
|
nested-keyval-str))))) |
|
|
(t |
|
|
(setq front-matter |
|
|
(concat front-matter |
|
|
(format "%s%s %s\n" |
|
|
key |
|
|
sign |
|
|
(cond (;; Tags, categories, keywords, aliases, |
|
|
;; custom front-matter which are lists. |
|
|
(listp value) |
|
|
(org-hugo--get-yaml-toml-list-string key value)) |
|
|
(t |
|
|
(org-hugo--quote-string value nil format))))))))))) |
|
|
(concat sep front-matter nested-string menu-string res-string sep))) |
|
|
|
|
|
(defun org-hugo--selective-property-inheritance () |
|
|
"Return a list of properties that should be inherited." |
|
|
(let ((prop-list '("HUGO_FRONT_MATTER_FORMAT" |
|
|
"HUGO_PREFER_HYPHEN_IN_TAGS" |
|
|
"HUGO_PRESERVE_FILLING" |
|
|
"HUGO_DELETE_TRAILING_WS" |
|
|
"HUGO_ALLOW_SPACES_IN_TAGS" |
|
|
"HUGO_BLACKFRIDAY" |
|
|
"HUGO_SECTION" |
|
|
"HUGO_SECTION*" |
|
|
"HUGO_BUNDLE" |
|
|
"HUGO_BASE_DIR" |
|
|
"HUGO_CODE_FENCE" |
|
|
"HUGO_MENU" |
|
|
"HUGO_CUSTOM_FRONT_MATTER" |
|
|
"HUGO_DRAFT" |
|
|
"HUGO_ISCJKLANGUAGE" |
|
|
"KEYWORDS" |
|
|
"HUGO_MARKUP" |
|
|
"HUGO_OUTPUTS" |
|
|
"HUGO_TAGS" |
|
|
"HUGO_CATEGORIES" |
|
|
"HUGO_SERIES" |
|
|
"HUGO_TYPE" |
|
|
"HUGO_LAYOUT" |
|
|
"HUGO_WEIGHT" |
|
|
"HUGO_RESOURCES" |
|
|
"HUGO_FRONT_MATTER_KEY_REPLACE" |
|
|
"HUGO_DATE_FORMAT" |
|
|
"HUGO_WITH_LOCALE" |
|
|
"HUGO_LOCALE" |
|
|
"HUGO_PAIRED_SHORTCODES" |
|
|
"DATE" ;Useful for inheriting same date to same posts in different languages |
|
|
"HUGO_PUBLISHDATE" |
|
|
"HUGO_EXPIRYDATE" |
|
|
"HUGO_LASTMOD" |
|
|
"HUGO_SLUG" ;Useful for inheriting same slug to same posts in different languages |
|
|
"HUGO_PANDOC_CITATIONS" |
|
|
"BIBLIOGRAPHY" |
|
|
"HUGO_AUTO_SET_LASTMOD" |
|
|
"LANGUAGE" |
|
|
"AUTHOR"))) |
|
|
(mapcar (lambda (str) |
|
|
(concat "EXPORT_" str)) |
|
|
prop-list))) |
|
|
|
|
|
(defun org-hugo--get-valid-subtree () |
|
|
"Return the Org element for a valid Hugo post subtree. |
|
|
The condition to check validity is that the EXPORT_FILE_NAME |
|
|
property is defined for the subtree element. |
|
|
|
|
|
As this function is intended to be called inside a valid Hugo |
|
|
post subtree, doing so also moves the point to the beginning of |
|
|
the heading of that subtree. |
|
|
|
|
|
Return nil if a valid Hugo post subtree is not found. The point |
|
|
will be moved in this case too." |
|
|
(catch 'break |
|
|
(while :infinite |
|
|
(let* ((entry (org-element-at-point)) |
|
|
(fname (org-string-nw-p (org-element-property :EXPORT_FILE_NAME entry))) |
|
|
level) |
|
|
(when fname |
|
|
(throw 'break entry)) |
|
|
;; Keep on jumping to the parent heading if the current |
|
|
;; entry does not have an EXPORT_FILE_NAME property. |
|
|
(setq level (org-up-heading-safe)) |
|
|
;; If no more parent heading exists, break out of the loop |
|
|
;; and return nil |
|
|
(unless level |
|
|
(throw 'break nil)))))) |
|
|
|
|
|
(defun org-hugo--get-post-subtree-coordinates (subtree) |
|
|
"Return the coordinates for the current valid Hugo post SUBTREE. |
|
|
|
|
|
The Org element returned by `org-hugo--get-valid-subtree' is a |
|
|
valid Hugo post subtree. |
|
|
|
|
|
The returned value is of type (LEVEL . INDEX) where LEVEL is the |
|
|
level number of the subtree and INDEX is as explained in the |
|
|
below example. |
|
|
|
|
|
If we have |
|
|
|
|
|
* Level 1 |
|
|
** Level A |
|
|
** Level B |
|
|
** Level C |
|
|
* Level 2 |
|
|
|
|
|
the INDEX will be 1 for Level 1 and Level A, 2 for Level |
|
|
B and Level 2, and 3 for Level C. |
|
|
|
|
|
So the value returned for Level C will be (2 . 3)." |
|
|
(save-excursion |
|
|
(let ((level (org-element-property :level subtree)) |
|
|
(index 1) |
|
|
(current-pos (point)) |
|
|
(scope (if (org-up-heading-safe) |
|
|
'tree ;Map entries only in parent subtree scope if parent exists |
|
|
nil))) ;Else map in the whole buffer (provided the MATCH conditions below) |
|
|
(when level |
|
|
(org-map-entries (lambda () |
|
|
(when (< (point) current-pos) |
|
|
(setq index (1+ index)))) |
|
|
;; Loop through only headings that are at the |
|
|
;; same level as SUBTREE, and those which have |
|
|
;; the EXPORT_FILE_NAME property defined. |
|
|
(concat "+LEVEL=" |
|
|
(number-to-string level) |
|
|
"+EXPORT_FILE_NAME<>\"\"") |
|
|
scope) |
|
|
(cons level index))))) |
|
|
|
|
|
(defun org-hugo--export-file-to-md (f-or-b-name &optional async visible-only noerror) |
|
|
"Export the Org file as a whole. |
|
|
|
|
|
Note: This is an internal function, use |
|
|
`org-hugo-export-wim-to-md' instead. |
|
|
|
|
|
F-OR-B-NAME is the name of the file or buffer (if not a file |
|
|
buffer) to be exported. |
|
|
|
|
|
A non-nil optional argument ASYNC means the process should happen |
|
|
asynchronously. The resulting file should be accessible through the |
|
|
`org-export-stack' interface. |
|
|
|
|
|
When optional argument VISIBLE-ONLY is non-nil, don't export |
|
|
contents of hidden elements. |
|
|
|
|
|
Return the exported file name if the file has the #+title |
|
|
keyword. |
|
|
|
|
|
Else return nil and throw a user error. If NOERROR is non-nil, |
|
|
use `message' to display the error message instead of signaling a |
|
|
user error." |
|
|
(let* ((info (org-combine-plists |
|
|
(org-export--get-export-attributes |
|
|
'hugo nil visible-only) |
|
|
(org-export--get-buffer-attributes) |
|
|
(org-export-get-environment 'hugo))) |
|
|
(title (car (plist-get info :title))) |
|
|
ret) |
|
|
(if title |
|
|
(let* ((all-tags-1 (plist-get info :hugo-tags)) |
|
|
(all-tags (when all-tags-1 |
|
|
(split-string |
|
|
(replace-regexp-in-string "\"" "" all-tags-1)))) |
|
|
(exclude-tags (plist-get info :exclude-tags)) |
|
|
is-excluded matched-exclude-tag) |
|
|
(when all-tags |
|
|
;; (message "[org-hugo--export-file-to-md DBG] exclude-tags = %s" exclude-tags) |
|
|
(dolist (exclude-tag exclude-tags) |
|
|
(when (member exclude-tag all-tags) |
|
|
(setq matched-exclude-tag exclude-tag) |
|
|
(setq is-excluded t)))) |
|
|
(cond |
|
|
(is-excluded |
|
|
(message "[ox-hugo] %s was not exported as it is tagged with an exclude tag `%s'" |
|
|
f-or-b-name matched-exclude-tag)) |
|
|
|
|
|
(t |
|
|
(message "[ox-hugo] Exporting `%s' (%s)" title f-or-b-name) |
|
|
(setq ret (org-hugo-export-to-md async nil visible-only))))) |
|
|
|
|
|
(let ((msg "The entire file is attempted to be exported, but it is missing the #+title keyword") |
|
|
(error-fn (if noerror |
|
|
#'message |
|
|
#'user-error))) |
|
|
(apply error-fn |
|
|
(list (format "[ox-hugo] %s: %s" f-or-b-name msg))))) |
|
|
ret)) |
|
|
|
|
|
(defun org-hugo--export-subtree-to-md (&optional async visible-only all-subtrees) |
|
|
"Export the current subtree to a Hugo post. |
|
|
|
|
|
Note: This is an internal function, use |
|
|
`org-hugo-export-wim-to-md' instead. |
|
|
|
|
|
A non-nil optional argument ASYNC means the process should happen |
|
|
asynchronously. The resulting file should be accessible through the |
|
|
`org-export-stack' interface. |
|
|
|
|
|
When optional argument VISIBLE-ONLY is non-nil, don't export |
|
|
contents of hidden elements. |
|
|
|
|
|
When optional argument ALL-SUBTREES is non-nil, print the |
|
|
subtree-number being exported. |
|
|
|
|
|
- If point is under a valid Hugo post subtree, export it, and |
|
|
also return the exported file name. |
|
|
|
|
|
- If point is not under a valid Hugo post subtree, but one exists |
|
|
elsewhere in the Org file, do not export anything, but still |
|
|
return t. |
|
|
|
|
|
- Else, return nil." |
|
|
;; Publish only the current subtree |
|
|
(ignore-errors |
|
|
(org-back-to-heading :invisible-ok)) |
|
|
(let ((subtree (org-hugo--get-valid-subtree))) |
|
|
(if subtree |
|
|
;; If subtree is a valid Hugo post subtree, proceed .. |
|
|
(let* ((info (org-combine-plists |
|
|
(org-export--get-export-attributes |
|
|
'hugo subtree visible-only) |
|
|
(org-export--get-buffer-attributes) |
|
|
(org-export-get-environment 'hugo subtree))) |
|
|
(exclude-tags (plist-get info :exclude-tags)) |
|
|
(is-commented (org-element-property :commentedp subtree)) |
|
|
is-excluded matched-exclude-tag ret) |
|
|
;; (message "[org-hugo--export-subtree-to-md DBG] exclude-tags = |
|
|
;; %s" exclude-tags) |
|
|
(let ((all-tags (let ((org-use-tag-inheritance t)) |
|
|
(org-hugo--get-tags)))) |
|
|
(when all-tags |
|
|
(dolist (exclude-tag exclude-tags) |
|
|
(when (member exclude-tag all-tags) |
|
|
(setq matched-exclude-tag exclude-tag) |
|
|
(setq is-excluded t))))) |
|
|
|
|
|
;; (message "[current subtree DBG] subtree: %S" subtree) |
|
|
;; (message "[current subtree DBG] is-commented:%S, tags:%S, |
|
|
;; is-excluded:%S" is-commented tags is-excluded) |
|
|
(let ((title (org-element-property :title subtree))) |
|
|
(cond |
|
|
(is-commented |
|
|
(message "[ox-hugo] `%s' was not exported as that subtree is commented" |
|
|
title)) |
|
|
(is-excluded |
|
|
(message "[ox-hugo] `%s' was not exported as it is tagged with an exclude tag `%s'" |
|
|
title matched-exclude-tag)) |
|
|
(t |
|
|
(if all-subtrees |
|
|
(progn |
|
|
(setq org-hugo--subtree-count (1+ org-hugo--subtree-count)) |
|
|
(message "[ox-hugo] %d/ Exporting `%s' .." org-hugo--subtree-count title)) |
|
|
(message "[ox-hugo] Exporting `%s' .." title)) |
|
|
;; Get the current subtree coordinates for |
|
|
;; auto-calculation of menu item weight, page or |
|
|
;; taxonomy weights .. |
|
|
(when (or |
|
|
;; .. if the menu front-matter is specified. |
|
|
(or |
|
|
(org-entry-get nil "EXPORT_HUGO_MENU" :inherit) |
|
|
(save-excursion |
|
|
(goto-char (point-min)) |
|
|
(let ((case-fold-search t)) |
|
|
(re-search-forward "^#\\+hugo_menu:.*:menu" nil :noerror)))) |
|
|
;; .. or if auto-calculation is needed for page |
|
|
;; or taxonomy weights. |
|
|
(or |
|
|
(let ((page-or-taxonomy-weight (org-entry-get nil "EXPORT_HUGO_WEIGHT" :inherit))) |
|
|
(and (stringp page-or-taxonomy-weight) |
|
|
(string-match-p "auto" page-or-taxonomy-weight))) |
|
|
(save-excursion |
|
|
(goto-char (point-min)) |
|
|
(let ((case-fold-search t)) |
|
|
(re-search-forward "^#\\+hugo_weight:.*auto" nil :noerror))))) |
|
|
(setq org-hugo--subtree-coord |
|
|
(org-hugo--get-post-subtree-coordinates subtree))) |
|
|
|
|
|
;; If `all-subtrees' is non-nil, the Org buffer would |
|
|
;; already be pre-processed in |
|
|
;; `org-hugo-export-wim-to-md', so do not do that again. |
|
|
(if all-subtrees |
|
|
(setq ret (org-hugo-export-to-md async :subtreep visible-only)) |
|
|
|
|
|
;; Do the buffer pre-processing only if the user is |
|
|
;; exporting only the current valid Hugo post subtree. |
|
|
(let ((current-outline-path (org-get-outline-path :with-self)) |
|
|
(buffer (org-hugo--get-pre-processed-buffer))) |
|
|
(with-current-buffer buffer |
|
|
(goto-char (org-find-olp current-outline-path :this-buffer)) |
|
|
(setq ret (org-hugo-export-to-md async :subtreep visible-only))) |
|
|
(kill-buffer buffer)))))) |
|
|
ret) |
|
|
|
|
|
;; If the point is not in a valid subtree, check if there's a |
|
|
;; valid subtree elsewhere in the same Org file. |
|
|
(let ((valid-subtree-found |
|
|
(catch 'break |
|
|
(org-map-entries |
|
|
(lambda () |
|
|
(throw 'break t)) |
|
|
;; Only map through subtrees where EXPORT_FILE_NAME |
|
|
;; property is not empty. |
|
|
"EXPORT_FILE_NAME<>\"\"")))) |
|
|
(when valid-subtree-found |
|
|
(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 :inherited) |
|
|
(plist-get info :hugo-section))) |
|
|
(filename (org-export-get-node-property :EXPORT_FILE_NAME element :inherited)) |
|
|
(current-element element) |
|
|
fragment fragments) |
|
|
;; 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--get-pre-processed-buffer () |
|
|
"Return a pre-processed copy of the current buffer. |
|
|
|
|
|
Internal links to other subtrees are converted to external |
|
|
links." |
|
|
(let* ((buffer (generate-new-buffer (concat "*Ox-hugo Pre-processed " (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)) |
|
|
(destination-type (org-element-type destination))) |
|
|
;; (message "[ox-hugo pre process DBG] destination type: %s" destination-type) |
|
|
|
|
|
;; Change the link if it points to a valid |
|
|
;; destination outside the subtree. |
|
|
(unless (equal source-path destination-path) |
|
|
(let ((link-desc (org-element-contents link)) |
|
|
(link-copy (org-element-copy link))) |
|
|
;; (message "[ox-hugo pre process DBG] link desc: %s" link-desc) |
|
|
(apply #'org-element-adopt-elements link-copy link-desc) |
|
|
(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 ((anchor (org-hugo--get-anchor destination info))) |
|
|
(concat destination-path ".org::#" anchor))))) |
|
|
;; If the link destination is a heading and if |
|
|
;; user hasn't set the link description, set the |
|
|
;; description to the destination heading title. |
|
|
(when (and (null link-desc) |
|
|
(equal 'headline destination-type)) |
|
|
(let ((headline-title |
|
|
(org-export-data-with-backend |
|
|
(org-element-property :title destination) 'ascii info))) |
|
|
;; (message "[ox-hugo pre process DBG] destination heading: %s" headline-title) |
|
|
(org-element-set-contents link-copy headline-title))) |
|
|
(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 |
|
|
|
|
|
;;;###autoload |
|
|
(defun org-hugo-export-as-md (&optional async subtreep visible-only) |
|
|
"Export current buffer to a Hugo-compatible Markdown buffer. |
|
|
|
|
|
If narrowing is active in the current buffer, only export its |
|
|
narrowed part. |
|
|
|
|
|
If a region is active, export that region. |
|
|
|
|
|
A non-nil optional argument ASYNC means the process should happen |
|
|
asynchronously. The resulting buffer should be accessible |
|
|
through the `org-export-stack' interface. |
|
|
|
|
|
When optional argument SUBTREEP is non-nil, export the sub-tree |
|
|
at point, extracting information from the headline properties |
|
|
first. |
|
|
|
|
|
When optional argument VISIBLE-ONLY is non-nil, don't export |
|
|
contents of hidden elements. |
|
|
|
|
|
Export is done in a buffer named \"*Org Hugo Export*\", which |
|
|
will be displayed when `org-export-show-temporary-export-buffer' |
|
|
is non-nil. |
|
|
|
|
|
Return the buffer the export happened to." |
|
|
(interactive) |
|
|
(org-hugo--before-export-function subtreep) |
|
|
;; Allow certain `ox-hugo' properties to be inherited. |
|
|
(let ((org-use-property-inheritance (org-hugo--selective-property-inheritance)) |
|
|
(info (org-combine-plists |
|
|
(org-export--get-export-attributes |
|
|
'hugo subtreep visible-only) |
|
|
(org-export--get-buffer-attributes) |
|
|
(org-export-get-environment 'hugo subtreep)))) |
|
|
(prog1 |
|
|
(org-export-to-buffer 'hugo "*Org Hugo Export*" |
|
|
async subtreep visible-only nil nil (lambda () (text-mode))) |
|
|
(org-hugo--after-export-function info nil)))) |
|
|
|
|
|
;;;###autoload |
|
|
(defun org-hugo-export-to-md (&optional async subtreep visible-only) |
|
|
"Export current buffer to a Hugo-compatible Markdown file. |
|
|
|
|
|
If narrowing is active in the current buffer, only export its |
|
|
narrowed part. |
|
|
|
|
|
If a region is active, export that region. |
|
|
|
|
|
A non-nil optional argument ASYNC means the process should happen |
|
|
asynchronously. The resulting file should be accessible through |
|
|
the `org-export-stack' interface. |
|
|
|
|
|
When optional argument SUBTREEP is non-nil, export the sub-tree |
|
|
at point, extracting information from the headline properties |
|
|
first. |
|
|
|
|
|
When optional argument VISIBLE-ONLY is non-nil, don't export |
|
|
contents of hidden elements. |
|
|
|
|
|
Return output file's name." |
|
|
(interactive) |
|
|
(org-hugo--before-export-function subtreep) |
|
|
;; Allow certain `ox-hugo' properties to be inherited. It is |
|
|
;; important to set the `org-use-property-inheritance' before |
|
|
;; setting the `info' var so that properties like |
|
|
;; EXPORT_HUGO_SECTION get inherited. |
|
|
(let* ((org-use-property-inheritance (org-hugo--selective-property-inheritance)) |
|
|
(info (org-combine-plists |
|
|
(org-export--get-export-attributes |
|
|
'hugo subtreep visible-only) |
|
|
(org-export--get-buffer-attributes) |
|
|
(org-export-get-environment 'hugo subtreep))) |
|
|
(pub-dir (org-hugo--get-pub-dir info)) |
|
|
(outfile (org-export-output-file-name ".md" subtreep pub-dir))) |
|
|
;; (message "[org-hugo-export-to-md DBG] section-dir = %s" section-dir) |
|
|
(prog1 |
|
|
(org-export-to-file 'hugo outfile async subtreep visible-only) |
|
|
(org-hugo--after-export-function info outfile)))) |
|
|
|
|
|
;;;###autoload |
|
|
(defun org-hugo-export-wim-to-md (&optional all-subtrees async visible-only noerror) |
|
|
"Export the current subtree/all subtrees/current file to a Hugo post. |
|
|
|
|
|
This is an Export \"What I Mean\" function: |
|
|
|
|
|
- If the current subtree has the \"EXPORT_FILE_NAME\" property, export |
|
|
that subtree. |
|
|
- If the current subtree doesn't have that property, but one of its |
|
|
parent subtrees has, then export from that subtree's scope. |
|
|
- If none of the subtrees have that property (or if there are no Org |
|
|
subtrees at all), call `org-hugo--export-file-to-md'. |
|
|
|
|
|
- If ALL-SUBTREES is non-nil, export all valid Hugo post subtrees |
|
|
\(that have the \"EXPORT_FILE_NAME\" property) in the current file |
|
|
to multiple Markdown posts. |
|
|
- If ALL-SUBTREES is non-nil, and again if none of the subtrees have |
|
|
that property (or if there are no Org subtrees), call |
|
|
`org-hugo--export-file-to-md'. |
|
|
|
|
|
A non-nil optional argument ASYNC means the process should happen |
|
|
asynchronously. The resulting file should be accessible through |
|
|
the `org-export-stack' interface. |
|
|
|
|
|
When optional argument VISIBLE-ONLY is non-nil, don't export |
|
|
contents of hidden elements. |
|
|
|
|
|
The optional argument NOERROR is passed to |
|
|
`org-hugo--export-file-to-md'. |
|
|
|
|
|
- If ALL-SUBTREES is non-nil: |
|
|
- If valid subtrees are found, return the list of output files. |
|
|
- If no valid subtrees are found, return value is the same as |
|
|
that of `org-hugo--export-file-to-md'. |
|
|
|
|
|
- If ALL-SUBTREES is nil: |
|
|
- If `org-hugo--export-subtree-to-md' returns a non-nil value, return that. |
|
|
- Else return the value of `org-hugo--export-file-to-md'." |
|
|
(interactive "P") |
|
|
(let ((f-or-b-name (if (buffer-file-name) |
|
|
(file-name-nondirectory (buffer-file-name)) |
|
|
(buffer-name))) |
|
|
ret) |
|
|
(save-window-excursion |
|
|
(save-restriction |
|
|
(widen) |
|
|
(save-excursion |
|
|
(if all-subtrees |
|
|
(progn ;Publish all valid Hugo post subtrees in the file. |
|
|
(setq org-hugo--subtree-count 0) ;Reset the subtree count |
|
|
(let ((buffer (org-hugo--get-pre-processed-buffer))) |
|
|
(with-current-buffer buffer |
|
|
(setq ret (org-map-entries |
|
|
(lambda () |
|
|
(org-hugo--export-subtree-to-md |
|
|
async visible-only :all-subtrees)) |
|
|
;; 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 |
|
|
(if (= 1 org-hugo--subtree-count) "" "s") |
|
|
f-or-b-name) |
|
|
(message "[ox-hugo] No valid Hugo post subtrees were found"))) |
|
|
|
|
|
;; Publish only the current valid Hugo post subtree. |
|
|
(setq ret (org-hugo--export-subtree-to-md async visible-only))) |
|
|
|
|
|
;; If `ret' is nil, no valid Hugo subtree was found. So |
|
|
;; call `org-hugo--export-file-to-md' directly. In that |
|
|
;; function, it will be checked if the whole Org file can be |
|
|
;; exported. |
|
|
(unless ret |
|
|
(setq ret (org-hugo--export-file-to-md f-or-b-name async visible-only noerror)))))) |
|
|
ret)) |
|
|
|
|
|
;;;###autoload |
|
|
(defun org-hugo-debug-info () |
|
|
"Get Emacs, Org and Hugo version and ox-hugo customization info. |
|
|
The information is converted to Markdown format and copied to the |
|
|
kill ring. The same information is displayed in the Messages |
|
|
buffer and returned as a string in Org format." |
|
|
(interactive) |
|
|
(let* ((emacs-version (emacs-version)) |
|
|
(org-version (org-version nil :full)) |
|
|
(hugo-version (org-hugo--hugo-version)) |
|
|
(hugo-version (if hugo-version |
|
|
(car hugo-version) ;Long version |
|
|
"=hugo= binary not found in PATH")) |
|
|
(info-org (mapconcat #'identity |
|
|
`("* Debug information for =ox-hugo=" |
|
|
"** Emacs Version" |
|
|
"#+begin_example" ;Prevent underscores from being interpreted as subscript markup |
|
|
,(format "%s%s" |
|
|
emacs-version |
|
|
(if emacs-repository-version |
|
|
(format " (commit %s)" emacs-repository-version) |
|
|
"")) |
|
|
"#+end_example" |
|
|
"** Org Version" |
|
|
"#+begin_example" ;Prevent the forward slashes in paths being interpreted as Org markup |
|
|
,org-version |
|
|
"#+end_example" |
|
|
"** Hugo Version" |
|
|
"#+begin_example" ;Prevent the forward slashes in paths being interpreted as Org markup |
|
|
,hugo-version |
|
|
"#+end_example" |
|
|
"*** Org =load-path= shadows" |
|
|
,(let* ((str (list-load-path-shadows :stringp)) |
|
|
(str-list (split-string str "\n" :omit-nulls)) |
|
|
(org-shadow-str "")) |
|
|
(dolist (shadow str-list) |
|
|
(when (string-match-p ".*org.+hides.+org.*" shadow) |
|
|
(setq org-shadow-str (concat org-shadow-str shadow "\n")))) |
|
|
(if (org-string-nw-p org-shadow-str) |
|
|
(mapconcat #'identity |
|
|
`("*Warning*: Possible mixed installation of Org" |
|
|
"#+begin_example" ;Prevent the forward slashes in paths being interpreted as Org markup |
|
|
,(org-trim org-shadow-str) |
|
|
"#+end_example" |
|
|
"Study the output of =M-x list-load-path-shadows=.") |
|
|
"\n") |
|
|
"No Org mode shadows found in =load-path=")) |
|
|
"** =ox-hugo= defcustoms" |
|
|
,(format "|org-hugo-section |%S|" org-hugo-section) |
|
|
,(format "|org-hugo-use-code-for-kbd |%S|" org-hugo-use-code-for-kbd) |
|
|
,(format "|org-hugo-preserve-filling |%S|" org-hugo-preserve-filling) |
|
|
,(format "|org-hugo-delete-trailing-ws |%S|" org-hugo-delete-trailing-ws) |
|
|
,(format "|org-hugo-prefer-hyphen-in-tags |%S|" org-hugo-prefer-hyphen-in-tags) |
|
|
,(format "|org-hugo-allow-spaces-in-tags |%S|" org-hugo-allow-spaces-in-tags) |
|
|
,(format "|org-hugo-tag-processing-functions |%S|" org-hugo-tag-processing-functions) |
|
|
,(format "|org-hugo-auto-set-lastmod |%S|" org-hugo-auto-set-lastmod) |
|
|
,(format "|org-hugo-export-with-toc |%S|" org-hugo-export-with-toc) |
|
|
,(format "|org-hugo-export-with-section-numbers |%S|" org-hugo-export-with-section-numbers) |
|
|
,(format "|org-hugo-front-matter-format |%S|" org-hugo-front-matter-format) |
|
|
,(format "|org-hugo-default-static-subdirectory-for-externals |%S|" org-hugo-default-static-subdirectory-for-externals) |
|
|
,(format "|org-hugo-external-file-extensions-allowed-for-copying |%S|" org-hugo-external-file-extensions-allowed-for-copying) |
|
|
,(format "|org-hugo-date-format |%S|" org-hugo-date-format) |
|
|
,(format "|org-hugo-paired-shortcodes |%S|" org-hugo-paired-shortcodes) |
|
|
,(format "|org-hugo-langs-no-descr-in-code-fences |%S|" org-hugo-langs-no-descr-in-code-fences) |
|
|
,(format "|org-hugo-suppress-lastmod-period |%S|" org-hugo-suppress-lastmod-period) |
|
|
,(format "|org-hugo-front-matter-format |%S|" org-hugo-front-matter-format)) |
|
|
"\n")) |
|
|
(org-export-with-toc nil) |
|
|
(info-md (progn |
|
|
(require 'ox-md) |
|
|
(org-export-string-as info-org 'md :body-only)))) |
|
|
(kill-new info-md) |
|
|
(message "%s" info-org) |
|
|
info-org)) |
|
|
|
|
|
|
|
|
(provide 'ox-hugo) |
|
|
|
|
|
;;; ox-hugo.el ends here
|
|
|
|