From be7e140d08f5f0d4b0955af2caa385acbb7f4a26 Mon Sep 17 00:00:00 2001 From: KemoNine Date: Mon, 7 Nov 2022 22:54:27 -0500 Subject: [PATCH] add ox-hugo to org profile --- .../org-hugo-auto-export-mode.el | 48 + .../ox-hugo-20221028.1631/ox-blackfriday.el | 1672 ++++++ .../ox-hugo-autoloads.el | 296 + .../ox-hugo-deprecated.el | 442 ++ .../ox-hugo-pandoc-cite.el | 415 ++ org/elpa/ox-hugo-20221028.1631/ox-hugo-pkg.el | 14 + org/elpa/ox-hugo-20221028.1631/ox-hugo.el | 5002 +++++++++++++++++ org/elpa/tomelr-0.4.3.signed | 1 + org/elpa/tomelr-0.4.3/.elpaignore | 5 + org/elpa/tomelr-0.4.3/CHANGELOG.org | 235 + org/elpa/tomelr-0.4.3/README.org | 1243 ++++ org/elpa/tomelr-0.4.3/tomelr-autoloads.el | 26 + org/elpa/tomelr-0.4.3/tomelr-pkg.el | 2 + org/elpa/tomelr-0.4.3/tomelr.el | 487 ++ org/init.el | 6 +- 15 files changed, 9893 insertions(+), 1 deletion(-) create mode 100644 org/elpa/ox-hugo-20221028.1631/org-hugo-auto-export-mode.el create mode 100644 org/elpa/ox-hugo-20221028.1631/ox-blackfriday.el create mode 100644 org/elpa/ox-hugo-20221028.1631/ox-hugo-autoloads.el create mode 100644 org/elpa/ox-hugo-20221028.1631/ox-hugo-deprecated.el create mode 100644 org/elpa/ox-hugo-20221028.1631/ox-hugo-pandoc-cite.el create mode 100644 org/elpa/ox-hugo-20221028.1631/ox-hugo-pkg.el create mode 100644 org/elpa/ox-hugo-20221028.1631/ox-hugo.el create mode 100644 org/elpa/tomelr-0.4.3.signed create mode 100644 org/elpa/tomelr-0.4.3/.elpaignore create mode 100644 org/elpa/tomelr-0.4.3/CHANGELOG.org create mode 100644 org/elpa/tomelr-0.4.3/README.org create mode 100644 org/elpa/tomelr-0.4.3/tomelr-autoloads.el create mode 100644 org/elpa/tomelr-0.4.3/tomelr-pkg.el create mode 100644 org/elpa/tomelr-0.4.3/tomelr.el diff --git a/org/elpa/ox-hugo-20221028.1631/org-hugo-auto-export-mode.el b/org/elpa/ox-hugo-20221028.1631/org-hugo-auto-export-mode.el new file mode 100644 index 0000000..79536d5 --- /dev/null +++ b/org/elpa/ox-hugo-20221028.1631/org-hugo-auto-export-mode.el @@ -0,0 +1,48 @@ +;;; org-hugo-auto-export-mode.el --- Minor mode for auto-exporting using ox-hugo -*- lexical-binding: t -*- + +;; Authors: Kaushal Modi , Evgeni Kolev +;; URL: https://ox-hugo.scripter.co + +;;; Commentary: +;; +;; This is a minor mode for enabling auto-exporting of Org files via +;; ox-hugo. +;; +;; *It is NOT a stand-alone package.* + +;;; Usage: +;; +;; To enable this minor mode for a "content-org" directory, add below +;; to the .dir-locals.el: +;; +;; (("content-org/" +;; . ((org-mode . ((eval . (org-hugo-auto-export-mode))))))) + +;;; Code: + +(declare-function org-hugo-export-wim-to-md "ox-hugo") + +(defun org-hugo-export-wim-to-md-after-save () + "Function for `after-save-hook' to run `org-hugo-export-wim-to-md'. + +The exporting happens only when Org Capture is not in progress." + (unless (eq real-this-command 'org-capture-finalize) + (save-excursion + (org-hugo-export-wim-to-md)))) + +;;;###autoload +(define-minor-mode org-hugo-auto-export-mode + "Toggle auto exporting the Org file using `ox-hugo'." + :global nil + :lighter "" + (if org-hugo-auto-export-mode + ;; When the mode is enabled + (progn + (add-hook 'after-save-hook #'org-hugo-export-wim-to-md-after-save :append :local)) + ;; When the mode is disabled + (remove-hook 'after-save-hook #'org-hugo-export-wim-to-md-after-save :local))) + + +(provide 'org-hugo-auto-export-mode) + +;;; org-hugo-auto-export-mode.el ends here diff --git a/org/elpa/ox-hugo-20221028.1631/ox-blackfriday.el b/org/elpa/ox-hugo-20221028.1631/ox-blackfriday.el new file mode 100644 index 0000000..f141d1d --- /dev/null +++ b/org/elpa/ox-hugo-20221028.1631/ox-blackfriday.el @@ -0,0 +1,1672 @@ +;;; ox-blackfriday.el --- Blackfriday Markdown Back-End for Org Export Engine -*- lexical-binding: t -*- + +;; Authors: Matt Price +;; Kaushal Modi +;; URL: https://ox-hugo.scripter.co +;; Package-Requires: ((emacs "24.5")) +;; Version: 0.1 + +;;; Commentary: + +;; This library implements a Markdown back-end (Blackfriday flavor +;; (https://github.com/russross/blackfriday)) for Org exporter, based +;; on the ox-md exporter. + +;; It started off as a clone of Lars Tveito's GitHub Flavored Markdown +;; exporter (https://github.com/larstvei/ox-gfm). + +;;; Code: + +(require 'org) +(require 'ox-md) +(require 'ox-publish) +(require 'table) ;To support tables written in table.el format + +(require 'subr-x) ;For `string-remove-suffix' + + +;;; Variables + +(defvar org-blackfriday-width-cookies nil) +(defvar org-blackfriday-width-cookies-table nil) + +(defconst org-blackfriday-table-left-border "") +(defconst org-blackfriday-table-right-border " ") +(defconst org-blackfriday-table-separator "| ") + +(defconst org-blackfriday-html5-inline-elements + '(;; "a" ;Use Org [[link]] syntax instead + "abbr" "audio" + ;; "b" ;Use Org *bold* syntax instead + "bdi" "bdo" + ;; "br" ;Use "\\" or "#+options: \n:t" instead + "button" + "canvas" "cite" + ;; "code" ;Use Org =code= or ~code~ instead + "data" "datalist" "del" "dfn" + ;; "em" ;Use Org /italics/ syntax instead + "embed" + ;; "i" ;Use Org /italics/ syntax instead + "iframe" + ;; "img" ;Use Org image insertion syntax instead + "input" "ins" + "kbd" + "label" + "map" "mark" "meter" + "noscript" + "object" "output" + "picture" "progress" + "q" + "ruby" + "s" "samp" "script" "select" "slot" "small" "span" + ;; "strong" ;Use Org *bold* syntax instead + ;; "sub" ;Use Org abc_{subscript} syntax instead + ;; "sup" ;Use Org abc^{superscript} syntax instead + "svg" + "template" "textarea" "time" + "u" + "var" "video") + "HTML 5 inline elements. + +https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements#list_of_inline_elements.") + +(defvar org-blackfriday--hrule-inserted nil + "State variable to track if the horizontal rule was inserted. +This check is specifically track if that horizontal rule was +inserted after the first row of the table.") + +(defvar org-blackfriday--code-block-num-backticks-default 3 + "Variable to store the default number of backticks for code block. + +Note that this variable is *only* for internal use.") + +(defvar org-blackfriday--code-block-num-backticks org-blackfriday--code-block-num-backticks-default + "Variable to store the number of backticks for code block. +By default, it stays at 3. This number is incremented for few +corner cases. + +Note that this variable is *only* for internal use.") + +(defvar org-blackfriday--org-element-string '((src-block . "Code Snippet") + (table . "Table") + (figure . "Figure")) ;Note that `figure' is not an actual Org element + "Alist of strings used to represent various Org elements.") + +(defvar org-blackfriday--ltximg-directory "ltximg/" + "Sub directory created inside the site's static directory for LaTeX images. + +This sub directory is created when an export option like +`tex:dvisvgm' is used.") + + +;;; User-Configurable Variables + +(defgroup org-export-blackfriday nil + "Options for exporting Org mode files to Blackfriday Markdown." + :tag "Org Export Blackfriday" + :group 'org-export) + +(defcustom org-blackfriday-syntax-highlighting-langs + '(("ipython" . "python") + ("jupyter-python" . "python") + ("conf-toml" . "toml") + ("conf-space" . "cfg") + ("conf" . "cfg")) + "Alist mapping src block languages to their syntax highlighting languages. + +The key is the src block language name. The value is the +language name to be used in the exported Markdown. The value +language name would be one that Hugo's Chroma syntax highlighter +would understand. + +For most src languages, this variable will not need to be +customized. But there are some src block \"languages\" like +`ipython' and `jupyter-python' for which, the exported language +tag needs to be `python'." + :group 'org-export-blackfriday + :type '(repeat + (cons + (string "Src Block language") + (string "Syntax highlighting language")))) + + + +;;; Define Back-End + +(org-export-define-derived-backend 'blackfriday 'md + :filters-alist '((:filter-parse-tree . org-blackfriday-separate-elements)) + ;; Do not clutter the *Org Exporter Dispatch* menu. + ;; :menu-entry + ;; '(?b "Export to Blackfriday Flavored Markdown" + ;; ((?B "To temporary buffer" + ;; (lambda (a s v b) (org-blackfriday-export-as-markdown a s v))) + ;; (?b "To file" (lambda (a s v b) (org-blackfriday-export-to-markdown a s v))) + ;; (?o "To file and open" + ;; (lambda (a s v b) + ;; (if a (org-blackfriday-export-to-markdown t s v) + ;; (org-open-file (org-blackfriday-export-to-markdown nil s v))))))) + :translate-alist '((center-block . org-blackfriday-center-block) + (example-block . org-blackfriday-example-block) + (fixed-width . org-blackfriday-fixed-width) ;Org Babel Results + (footnote-reference . org-blackfriday-footnote-reference) + (inner-template . org-blackfriday-inner-template) + (italic . org-blackfriday-italic) + (item . org-blackfriday-item) + (latex-environment . org-blackfriday-latex-environment) + (latex-fragment . org-blackfriday-latex-fragment) + (line-break . org-html-line-break) ;"\\" at EOL forces a line break + (plain-list . org-blackfriday-plain-list) + (plain-text . org-blackfriday-plain-text) + (quote-block . org-blackfriday-quote-block) + (radio-target . org-blackfriday-radio-target) + (special-block . org-blackfriday-special-block) + (src-block . org-blackfriday-src-block) + (strike-through . org-blackfriday-strike-through) + (table-cell . org-blackfriday-table-cell) + (table-row . org-blackfriday-table-row) + (table . org-blackfriday-table) + (target . org-blackfriday-target) + (verse-block . org-blackfriday-verse-block))) + + +;;; Miscellaneous Helper Functions + +;;;; Check if a boolean plist value is non-nil +(defun org-blackfriday--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))) + (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))))) + +;;;; Table of contents +(defun org-blackfriday-format-toc (heading info) + "Return an appropriate table of contents entry for HEADING. + +INFO is a plist used as a communication channel." + (let* ((title (org-export-data (org-export-get-alt-title heading info) info)) + (level (1- (org-element-property :level heading))) + (indent (concat (make-string (* level 2) ? ))) + (anchor (or (org-element-property :CUSTOM_ID heading) + (org-export-get-reference heading info)))) + (concat indent "- [" title "]" "(#" anchor ")"))) + +;;;; Extra div hack +(defun org-blackfriday--extra-div-hack (info &optional tag) + "Return string for the \"extra div hack\". + +The empty HTML element tags like \"
\" is a hack to get +around a Blackfriday limitation. + +See https://github.com/kaushalmodi/ox-hugo/issues/93. + +INFO is a plist used as a communication channel. + +If TAG is not specified, it defaults to \"div\"." + (let ((tag (or tag "div"))) + (if (org-blackfriday--plist-get-true-p info :hugo-goldmark) + "" + (format "\n <%s>" tag tag)))) + +(defun org-blackfriday--get-ref-prefix (symbol) + "Return the prefix string for SYMBOL which can be an Org element type. + +Returns nil if the SYMBOL's prefix string isn't defined." + (let ((prefix-alist '((figure . "figure--") + (radio . "org-radio--") + (src-block . "code-snippet--") + (table . "table--") + (target . "org-target--")))) + (cdr (assoc symbol prefix-alist)))) + +;;;; Footnote section +(defun org-blackfriday-footnote-section (info &optional is-cjk) + "Format the footnote section. + +INFO is a plist used as a communication channel. + +IS-CJK should be set to non-nil if the language is Chinese, +Japanese or Korean." + (let ((fn-alist (org-export-collect-footnote-definitions info)) + ;; Fri Jul 21 14:33:25 EDT 2017 - kmodi + ;; TODO: Need to learn using cl-loop + ;; Below form from ox-md did not work. + ;; (fn-alist-stripped + ;; (cl-loop for (n raw) in fn-alist collect + ;; (cons n (org-trim (org-export-data raw info))))) + fn-alist-stripped) + (let ((n 1) + def) + (dolist (fn fn-alist) + ;; (message "fn: %S" fn) + ;; (message "fn: %s" (org-export-data fn info)) ;This gives error + ;; (message "fn nth 2 car: %s" (org-export-data (nth 2 fn) info)) + (setq def (org-trim (org-export-data (nth 2 fn) info))) + (if (org-blackfriday--plist-get-true-p info :hugo-goldmark) + (progn ;Goldmark + ;; Goldmark's "PHP Markdown Extra: Footnotes" extension + ;; supports multi-line footnotes -- + ;; https://github.com/yuin/goldmark/#footnotes-extension. + ;; 2nd and further lines in a multi-line footnote need to + ;; be indented by 4 spaces. + (setq def (replace-regexp-in-string "\n" "\n " def))) + (progn ;Blackfriday + ;; Support multi-line footnote definitions by folding all + ;; footnote definition lines into a single line as Blackfriday + ;; does not support that. + (setq def (if is-cjk + (replace-regexp-in-string + "\n" " " ;If the footnote still has newlines, replace them with spaces + (replace-regexp-in-string + ;; Do not insert spaces when joining newlines for + ;; CJK languages. + "\\([[:multibyte:]]\\)[[:blank:]]*\n[[:blank:]]*\\([[:multibyte:]]\\)" "\\1\\2" + def)) + (replace-regexp-in-string "\n" " " def))) + + ;; Replace multiple consecutive spaces with a single space. + (setq def (replace-regexp-in-string "[[:blank:]]+" " " def)))) + (push (cons n def) fn-alist-stripped) + (setq n (1+ n)))) + (when fn-alist-stripped + (mapconcat (lambda (fn) + ;; (message "dbg: fn: %0d -- %s" (car fn) (cdr fn)) + (format "[^fn:%d]: %s" + (car fn) ;footnote number + (cdr fn))) ;footnote definition + (nreverse fn-alist-stripped) + "\n")))) + +;;;; Table-Common +(defun org-blackfriday-table-col-width (table column info) + "Return width of TABLE at given COLUMN using INFO. + +INFO is a plist used as communication channel. Width of a column +is determined either by inquiring `org-blackfriday-width-cookies' +in the column, or by the maximum cell with in the column." + (let ((cookie (when (hash-table-p org-blackfriday-width-cookies) + (gethash column org-blackfriday-width-cookies)))) + (if (and (eq table org-blackfriday-width-cookies-table) + (not (eq nil cookie))) + cookie + (unless (and (eq table org-blackfriday-width-cookies-table) + (hash-table-p org-blackfriday-width-cookies)) + (setq org-blackfriday-width-cookies (make-hash-table)) + (setq org-blackfriday-width-cookies-table table)) + (let ((max-width 0) + (specialp (org-export-table-has-special-column-p table))) + (org-element-map + table + 'table-row + (lambda (row) + (setq max-width + (max (length + (org-export-data + (org-element-contents + (elt (if specialp + (car (org-element-contents row)) + (org-element-contents row)) + column)) + info)) + max-width))) + info) + (puthash column max-width org-blackfriday-width-cookies))))) + +;;;; Plain List Helper +(defun org-blackfriday--export-ordered-list-as-html-p (plain-list) + "Return non-nil if the PLAIN-LIST needs to be exported as HTML. + +The PLAIN-LIST is exported as HTML if the list is an ordered list +and a custom counter is used on second or later item in the list. + +Returns nil otherwise." + (let ((type (org-element-property :type plain-list)) + has-custom-counter) + (when (eq 'ordered type) + (let ((list-contents (org-element-contents plain-list)) + (item-num 1)) + (setq has-custom-counter + (catch 'break + (dolist (el list-contents) + (when (eq 'item (car el)) + (let* ((item-plist (car (cdr el))) + (counter (plist-get item-plist :counter))) + ;; (message "dbg: item num: %d counter: %S" item-num counter) + ;; Make special provision for the custom counter + ;; notation [@N] only if it's present on second + ;; or later items. + (when (and (> item-num 1) + counter) + (throw 'break t)))) + (cl-incf item-num)))))) + ;; (message "dbg: has custom counter: %S" has-custom-counter) + has-custom-counter)) + +;;;; Table Cell Alignment +;; Below function is heavily adapted from +;; `org-export-table-cell-alignment' from ox.el. The main difference +;; is that the below variation can return a `default' value too. +(defun org-blackfriday-table-cell-alignment (table-cell info) + "Return TABLE-CELL contents alignment. + +INFO is a plist used as the communication channel. + +Return alignment as specified by the last alignment cookie in the +same column as TABLE-CELL. If no such cookie is found, return +`default'. Possible values are `default', `left', `right' and +`center'." + (let* ((row (org-export-get-parent table-cell)) + (table (org-export-get-parent row)) + (cells (org-element-contents row)) + (columns (length cells)) + (column (- columns (length (memq table-cell cells)))) + (cache (or (plist-get info :table-cell-alignment-cache) + (let ((table (make-hash-table :test #'eq))) + (plist-put info :table-cell-alignment-cache table) + table))) + (align-vector (or (gethash table cache) + (puthash table (make-vector columns nil) cache)))) + (or (aref align-vector column) + (let (cookie-align) + (dolist (row (org-element-contents (org-export-get-parent row))) + (cond + ;; In a special row, try to find an alignment cookie at + ;; COLUMN. + ((org-export-table-row-is-special-p row info) + (let ((value (org-element-contents + (elt (org-element-contents row) column)))) + ;; Since VALUE is a secondary string, the following + ;; checks avoid useless expansion through + ;; `org-export-data'. + (when (and value + (not (cdr value)) + (stringp (car value)) + (string-match "\\`<\\([lrc]\\)?\\([0-9]+\\)?>\\'" + (car value)) + (match-string 1 (car value))) + (setq cookie-align (match-string 1 (car value)))))) + ;; Ignore table rules. + ((eq (org-element-property :type row) 'rule)))) + ;; Return value. Alignment specified by cookies has + ;; precedence over alignment deduced from cell's contents. + (aset align-vector + column + (cond ((equal cookie-align "l") 'left) + ((equal cookie-align "r") 'right) + ((equal cookie-align "c") 'center) + (t 'default))))))) + +;;;; Escape certain characters inside equations (Blackfriday bug workaround) +(defun org-blackfriday-escape-chars-in-equation (str) + "Escape few characters in STR so that Blackfriday doesn't parse them. + +Do not interpret underscores, asterisks and backquotes in equations as +Markdown formatting +characters (https://gohugo.io/content-management/formats#solution): + + \"_\" -> \"\\=\\_\" + \"*\" -> \"\\=\\*\" + \"`\" -> \"\\=\\`\" + +https://github.com/kaushalmodi/ox-hugo/issues/104 + +Blackfriday converts \"(r)\" to Registered Trademark symbol, +\"(c)\" to Copyright symbol, and \"(tm)\" to Trademark symbol if +the SmartyPants extension is enabled (and there is no way to +disable just this). So insert an extra space after the opening +parentheses in those strings to trick Blackfriday/smartParens +from activating inside equations. That extra space anyways +doesn't matter in equations. + + \"(c)\" -> \"( c)\" + \"(r)\" -> \"( r)\" + \"(tm)\" -> \"( tm)\" + +https://gohugo.io/content-management/formats#solution +https://github.com/kaushalmodi/ox-hugo/issues/138 + +Need to escape the backslash before any ASCII punctuation character: + + !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ + +For example: + + \"\\(\" -> \"\\\\(\" + \"\\)\" -> \"\\\\)\" + \"\\\\=[\" -> \"\\\\\\=[\" + \"\\\\=]\" -> \"\\\\\\=]\" + \"\\\\={\" -> \"\\\\\\={\" + \"\\\\=}\" -> \"\\\\\\=}\" + \"\\|\" -> \"\\\\|\" + + \"](\" -> \"\\]\\(\" + +Also escape the backslash at the end of the line, otherwise +it will be interpreted as a hard line break." + (let* ( + ;; Escape the backslash before punctuation characters, e.g., + ;; \( -> \\(, \) -> \\), \[ -> \\[, \] -> \\], \{ -> \\{, \} -> \\}, \| -> \\| + (escaped-str (replace-regexp-in-string "\\(\\\\[][(){}!\"#$%&'*+,./:;<=>?@\\^_`|~-]\\)" "\\\\\\1" str)) + ;; _ -> \_, * -> \*, ` -> \` + (escaped-str (replace-regexp-in-string "[_*`]" "\\\\\\&" escaped-str)) + ;; (c) -> ( c), (r) -> ( r), (tm) -> ( tm) + (escaped-str (replace-regexp-in-string "(\\(c\\|r\\|tm\\))" "( \\1)" escaped-str)) + ;; ]( -> \]\( + (escaped-str (replace-regexp-in-string "](" "\\\\]\\\\(" escaped-str)) + ;; Replace "\" at EOL with "\\" + (escaped-str (replace-regexp-in-string "\\\\[[:blank:]]*$" "\\\\\\\\" escaped-str))) + escaped-str)) + +;;;; Reset org-blackfriday--code-block-num-backticks +(defun org-blackfriday--reset-org-blackfriday--code-block-num-backticks (_backend) + "Reset `org-blackfriday--code-block-num-backticks' to its default value." + (setq org-blackfriday--code-block-num-backticks org-blackfriday--code-block-num-backticks-default)) +(add-hook 'org-export-before-processing-hook #'org-blackfriday--reset-org-blackfriday--code-block-num-backticks) + +;;;; Make CSS property string +(defun org-blackfriday--make-css-property-string (props) + "Return a list of CSS properties, as a string. +PROPS is a plist where values are either strings or nil. A prop +with a nil value will be omitted from the result. + +This function is adapted from `org-html--make-attribute-string'." + (let (ret) + (dolist (item props (mapconcat #'identity (nreverse ret) " ")) + (cond ((null item) + (pop ret)) + ((symbolp item) + (push (substring (symbol-name item) 1) ret)) + (t + (let ((key (car ret)) + (value (replace-regexp-in-string + "\"" """ (org-html-encode-plain-text item)))) + (setcar ret (format "%s: %s; " key value)))))))) + +;;;; Get CSS string +(defun org-blackfriday--get-style-str (elem) + "Get HTML style tag string for ELEM. + +If #+attr_html is used to specify one or more classes for ELEM +and if #+attr_css is also used, then an inline style string is +returned such that it applies the specified CSS to the first of +those specified classes. + +Returns an empty string if either #+attr_html or #+attr_css are +not used, or if a class name is not specified in #+attr_html." + (let* ((html-attr (org-export-read-attribute :attr_html elem)) + (class (plist-get html-attr :class)) + (first-class (when (stringp class) + (car (split-string class)))) + (style-str "")) + (when first-class + (let* ((css-props (org-export-read-attribute :attr_css elem)) + (css-props-str (org-blackfriday--make-css-property-string css-props))) + (when (org-string-nw-p css-props-str) + (setq style-str (format "\n\n" + first-class css-props-str))))) + style-str)) + +;;;; Wrap with HTML attributes +(defun org-blackfriday--div-wrap-maybe (elem contents info) + "Wrap the CONTENTS with HTML div tags. + +INFO is a plist used as a communication channel. + +The div wrapping is done only if HTML attributes are set for the +ELEM Org element using #+attr_html. + +If #+attr_css is also used, and if one or more classes are +specified in #+attr_html, then an inline style is also inserted +that applies the specified CSS to the first of those specified +classes. + +If CONTENTS is nil, and #+attr_css is used, return only the HTML +style tag." + (let* ((elem-type (org-element-type elem)) + (html-attr (let ((attr1 (org-export-read-attribute :attr_html elem))) + (when (equal elem-type 'paragraph) + ;; Remove "target" and "rel" attributes from the + ;; list of a paragraph's HTML attributes as they + ;; would be meant for links inside the paragraph + ;; instead of the paragraph itself. + (plist-put attr1 :target nil) + (plist-put attr1 :rel nil) + ;; Remove other attributes from the list of a + ;; paragraph's HTML attributes which would be meant + ;; for the inline images inside that paragraph. + (plist-put attr1 :src nil) + (plist-put attr1 :alt nil) + (plist-put attr1 :height nil) + (plist-put attr1 :width nil)) + attr1)) + (html-attr-str (org-blackfriday--make-attribute-string html-attr)) + (ret contents)) + (when (org-string-nw-p html-attr-str) + (setq ret (concat (org-blackfriday--get-style-str elem) + (if contents + (format "
%s\n\n%s\n
" + html-attr-str (org-blackfriday--extra-div-hack info) contents)) + ""))) + ret)) + +;;;; Sanitize URL +(defun org-blackfriday--url-sanitize-maybe (info url) + "Sanitize the URL by replace certain characters with their hex encoding. + +INFO is a plist used as a communication channel. + +Replaces \"_\" with \"%5F\" only if :hugo-goldmark is nil. + +Workaround for Blackfriday bug https://github.com/russross/blackfriday/issues/278." + (if (not (org-blackfriday--plist-get-true-p info :hugo-goldmark)) + (replace-regexp-in-string "_" "%5F" url) + url)) + +;;;; Blackfriday Issue 239 Workaround +(defun org-blackfriday--issue-239-workaround (code parent-type) + "Prefix Markdown list characters with zero width space. + +CODE is the content of the source or example block. PARENT-TYPE +is the type of the Org element wrapping that source or example +block. + +Hack to avert the Blackfriday bug: +https://github.com/russross/blackfriday/issues/239. Remove this +hack once that issue is resolved. + +Prefix the ASTERISK (0x2a), PLUS SIGN (0x2b) and HYPHEN-MINUS +\(0x2d) characters with ZERO WIDTH SPACE (0x200b), if they +appear at BOL (following optional spaces). + +Details: https://github.com/kaushalmodi/ox-hugo/issues/57." + ;; (message "[ox-bf bfissue 239 DBG] parent type: %S" parent-type) + (if (equal 'item parent-type) + (setq code (replace-regexp-in-string "^\\s-*[-+*] " "​\\&" code)) + ;; There's a ZERO WIDTH SPACE char (0x200b) here ^^, + ;; (after «"», but before «\\&"» above) + ;; It's not visible (because zero width), but it's there. + code)) + +;;;; Get Reference +(defun org-blackfriday--get-reference (elem) + "Return a reference for ELEM using its \"#+name\" if available. + +If the ELEM has its `name' defined, the anchor is derived from it: + +- If the `name' begins with \"code__\", \"tab__\", \"table__\", + \"img__\", \"fig__\" or \"figure__\", that prefix is removed as + this function adds its own appropriate prefix. +- Underscores and forward slashes in the `name' get replaced with + hyphens. + +This conditioned `name' is then appended to the +code/table/figure-appropriate prefix, and returned. + +Else, return nil. + +The return value, if non-nil, is a string." + (let ((name (org-element-property :name elem))) ;Value of #+name + ;; Reference cannot be created if #+name does not exist. + ;; (message "[ox-bf ref DBG] name: %S" name) + (when name + (let* ((elem-type (org-element-type elem)) + (prefix (or (org-blackfriday--get-ref-prefix elem-type) + (format "org-%s--" (symbol-name elem-type)))) + (name1 (let* ((tmp name) + ;; Remove commonly used code/table/figure + ;; prefixes in the #+name itself. + (tmp (replace-regexp-in-string "\\`\\(code\\|tab\\|table\\|img\\|fig\\|figure\\|\\)__" "" tmp)) + ;; Prefer to use hyphens instead of + ;; underscores in anchors. Also replace / + ;; chars with hyphens. + (tmp (replace-regexp-in-string "[_/]" "-" tmp))) + tmp))) + (format "%s%s" prefix name1))))) + +;;;; Translate +(defun org-blackfriday--translate (type info &optional str) + "Return translated string for element TYPE to the lang set by \"#+language\". + +TYPE is the Org element type. + +INFO is a plist holding contextual information. + +If TYPE is `src-block' and if \"Listing\" translates to +\"Listing\", translate the string associated with `src-block' +from `org-blackfriday--org-element-string'. + +Else if TYPE key exists in `org-blackfriday--org-element-string', +return the translated version of of the string associated in that +alist. + +Else if TYPE key does not exist in +`org-blackfriday--org-element-string', or if TYPE is nil, but STR +is non-nil, return the translation of STR directly. + +Else return an empty string." + (let ((elem-str (cdr (assoc type org-blackfriday--org-element-string)))) + (if elem-str + (cond + ((equal 'src-block type) + (let ((listing-tr (org-html--translate "Listing" info))) + (if (string= "Listing" listing-tr) + (org-html--translate elem-str info) + listing-tr))) + (t + (org-html--translate elem-str info))) + (if (stringp str) + (org-html--translate str info) + "")))) + +;;;; Convert string to a valid anchor name +(defun org-blackfriday--valid-html-anchor-name (str) + "Turn STR into a valid HTML anchor name. + +Replaces invalid characters with \"-\". The returned anchor name +will also never begin or end with \"-\". +" + (or (and (stringp str) + (string-trim + (replace-regexp-in-string "[^a-zA-Z0-9_-.]" "-" str) + "-")) + "")) + +;; Return HTML span tags for link targets. +(defun org-blackfriday--link-target (attr &optional desc) + "Format a link target in HTML. + +ATTR is a string representing the attributes of the target HTML tag. +DESC is either nil or the description string of the target." + (format "%s" (or attr "") (or desc ""))) + +(defun org-blackfriday--make-attribute-string (attributes) + "Return a list of attributes, as a string. +ATTRIBUTES is a plist where values are either strings or nil. + +An attribute with a nil value will be omitted from the result. + +An attribute with a \"t\" value will be added as a key-only or +boolean attribute. + +This function is mostly a copy of +`org-html--make-attribute-string', except that it parses `:foo +\"t\"' as setting a boolean \"foo\" attribute." + (let (output) + (dolist (item attributes (mapconcat 'identity (nreverse output) " ")) + (cond ((null item) + (pop output)) + ((symbolp item) + (push (substring (symbol-name item) 1) output)) + ((and (stringp item) + (string= item "t")) ;Example: (:control "t") -> "control" + ;; Do nothing + ) + (t + (let ((key (car output)) + (value (replace-regexp-in-string + "\"" """ (org-html-encode-plain-text item)))) + (setcar output (format "%s=\"%s\"" key value)))))))) + +;;;; Convert Org string to HTML +(defun org-blackfriday--org-contents-to-html (el) + "Convert Org contents in EL element to HTML." + (let* ((org-str (org-element-interpret-data (org-element-contents el))) + (html-str (org-export-string-as org-str 'html :body-only))) + html-str)) + + + +;;; Filter Functions + +;; This function is adapted from `org-md-separate-elements'. +(defun org-blackfriday-separate-elements (tree _backend info) + "Fix blank lines between elements. + +TREE is the parse tree being exported. + +INFO is a plist used as a communication channel. + +Enforce a blank line between elements. There are 3 exceptions +to this rule: + + 1. Preserve blank lines between sibling items in a plain list, + + 2. In an item, remove any blank line before the very first + paragraph and the next sub-list when the latter ends the + current item. + + 3. In an item, if a paragraph is immediately followed by an src + or example block, don't add a blank line after the paragraph. + + 4. In an item, if an src or example block doesn't have a caption + and is immediately followed by a paragraph, don't add a blank + line after that src or example block." + (org-element-map tree (remq 'item org-element-all-elements) ;Exception 1 in the doc-string + (lambda (el) + (let ((post-blank (cond + ;; Exception 2 in the doc-string. + ((and (eq (org-element-type el) 'paragraph) + (eq (org-element-type (org-element-property :parent el)) 'item) + (org-export-first-sibling-p el info) + (let ((next-el (org-export-get-next-element el info))) + (and (eq (org-element-type next-el) 'plain-list) + (not (org-export-get-next-element next-el info))))) + 0) + ;; Exception 3 in the doc-string (paragraph -> src-block). + ((and (eq (org-element-type el) 'paragraph) + (eq (org-element-type (org-element-property :parent el)) 'item) + (let ((next-el (org-export-get-next-element el info))) + (memq (org-element-type next-el) '(src-block example-block)))) + 0) + ;; Exception 4 in the doc-string (caption-less src-block -> paragraph). + ;; If an src or example block has a caption, + ;; that caption will be wrapped in an HTML + ;; div block. In that case, we *do* need to + ;; leave a blank line after the div block (CommonMark). + ((and (memq (org-element-type el) '(src-block example-block)) + (eq (org-element-type (org-element-property :parent el)) 'item) + (null (org-element-property :caption el)) ;<-- "no caption" check + (let ((next-el (org-export-get-next-element el info))) + (memq (org-element-type next-el) '(paragraph)))) + 0) + (t + 1)))) + (org-element-put-property el :post-blank post-blank) + ;; (message "[org-blackfriday-separate-elements DBG] %S post-blank: %d" + ;; (org-element-type el) + ;; (org-element-property :post-blank el)) + ))) + ;; Return updated tree. + tree) + + + +;;; Transcode Functions + +;;;; Center Block +(defun org-blackfriday-center-block (_center-block contents info) + "Center-align the text in CONTENTS using CSS. + +INFO is a plist used as a communication channel." + (let* ((class "org-center") + (style (format ".%s { margin-left: auto; margin-right: auto; text-align: center; }" class))) + (format "\n\n
%s\n\n%s\n
" + style class (org-blackfriday--extra-div-hack info) contents))) + +;;;; Example Block +(defun org-blackfriday-example-block (example-block _contents info) + "Transcode an EXAMPLE-BLOCK element into Blackfriday Markdown format. +CONTENTS is nil. INFO is a plist holding contextual +information." + (let* ((parent-element (org-export-get-parent example-block)) + (parent-type (car parent-element)) + (backticks (make-string org-blackfriday--code-block-num-backticks ?`)) + (example (or (plist-get info :md-code) ;if set in `org-hugo-example-block' + (org-export-format-code-default example-block info))) + (code-attr (if (plist-get info :md-code-attr) ;if set in `org-hugo-example-block' + (format " { %s }" (plist-get info :md-code-attr)) + "")) + ret) + ;; (message "[ox-bf example-block DBG] parent type: %S" parent-type) + (setq ret (org-blackfriday--issue-239-workaround example parent-type)) + (setq ret (format "%stext%s\n%s%s" backticks code-attr ret backticks)) + (setq ret (org-blackfriday--div-wrap-maybe example-block ret info)) + (when (equal 'quote-block parent-type) + ;; If the current example block is inside a quote block, future + ;; example/code blocks (especially the ones outside this quote + ;; block) will require higher number of backticks. Workaround + ;; for https://github.com/russross/blackfriday/issues/407. + (setq org-blackfriday--code-block-num-backticks + (1+ org-blackfriday--code-block-num-backticks))) + ;; Reset the temp info in the `info' plist. + (plist-put info :md-code nil) + (plist-put info :md-code-attr nil) + ret)) + +;;;; Fixed Width +(defun org-blackfriday-fixed-width (fixed-width _contents info) + "Transcode a FIXED-WIDTH element into Blackfriday Markdown format. +CONTENTS is nil. INFO is a plist holding contextual +information." + (let* ((parent-element (org-export-get-parent fixed-width)) + (parent-type (car parent-element)) + (backticks (make-string org-blackfriday--code-block-num-backticks ?`))) + (prog1 + (org-blackfriday--div-wrap-maybe + fixed-width + (format "%stext\n%s%s" + backticks + (let ((org-src-preserve-indentation t)) + ;; Preserve leading whitespace in the Org Babel Results + ;; blocks. + (org-export-format-code-default fixed-width info)) + backticks) + info) + (when (equal 'quote-block parent-type) + ;; If the current example block is inside a quote block, + ;; future example/code blocks (especially the ones outside + ;; this quote block) will require higher number of backticks. + ;; Workaround for + ;; https://github.com/russross/blackfriday/issues/407. + (setq org-blackfriday--code-block-num-backticks + (1+ org-blackfriday--code-block-num-backticks)))))) + +;;;; Footnote Reference +(defun org-blackfriday-footnote-reference (footnote-reference _contents info) + "Transcode a FOOTNOTE-REFERENCE element into Blackfriday Markdown format. +CONTENTS is nil. INFO is a plist holding contextual information." + ;; (message "footref: %s" footnote-reference) + (concat + ;; Insert separator between two footnotes in a row. + (let ((prev (org-export-get-previous-element footnote-reference info))) + (and (eq (org-element-type prev) 'footnote-reference) + (plist-get info :html-footnote-separator))) + (format "[^fn:%d]" (org-export-get-footnote-number footnote-reference info)))) + +;;;; Inner Template +(defun org-blackfriday-inner-template (contents info) + "Return body of document after converting it to Markdown syntax. +CONTENTS is the transcoded contents string. INFO is a plist +holding export options." + (let* ((depth (plist-get info :with-toc)) + (headings (and depth (org-export-collect-headlines info depth))) + (toc-tail (if headings "\n\n" "")) + (toc-string "")) + + (when headings + (dolist (heading headings) + (setq toc-string (concat toc-string + (org-blackfriday-format-toc heading info) + "\n")))) + (concat toc-string toc-tail contents "\n" + (org-blackfriday-footnote-section info)))) + +;;;; Italic +(defun org-blackfriday-italic (_italic contents _info) + "Transcode ITALIC object into Markdown format. +CONTENTS is the text within italic markup. INFO is a plist used +as a communication channel." + ;; (format "*%s*" contents) + ;; While above also works in almost all cases, it fails in cases + ;; like "*This is in italic, **and this is in bold-italics**, and + ;; back to just italic.*". + ;; As `org-md-bold' uses ** to mark bold text, switching to using + ;; underscores only for italics. + (format "_%s_" contents)) + +;;;; Item (list item) +(defun org-blackfriday-item (item contents info) + "Transcode an ITEM element into Blackfriday Markdown format. +CONTENTS holds the contents of the item. INFO is a plist holding +contextual information. + +Special note about descriptive lists: + +Blackfriday style descriptive list syntax is used if that list is +not nested in another list. + + Term1 + : Description of term 1 + +If that list is nested, `ox-md' style descriptive list is +exported instead: + + - **Term1:** Description of term 1." + (let ((parent-list (org-export-get-parent item))) + ;; If this item is in an ordered list and if this or any other + ;; item in this list is using a custom counter, export this list + ;; item in HTML. + (if (org-blackfriday--export-ordered-list-as-html-p parent-list) + (org-html-format-list-item contents 'ordered nil info + (org-element-property :counter item)) + (let* ((parent-list (org-export-get-parent item)) + (parent-list-type (org-element-property :type parent-list)) + (desc-list? (eq parent-list-type 'descriptive)) + (grandparent (when desc-list? + (org-export-get-parent parent-list))) + (grandparent-type (when desc-list? + (org-element-type grandparent))) + (list-is-nested (eq 'item grandparent-type)) + ;; Export the descriptive list items like that in + ;; ox-md.el if this descriptive list is nested in some + ;; other list, because the Blackfriday style descriptive + ;; list syntax seems to work only at top level (i.e. not + ;; when that list is nested). + (ox-md-style-desc-list (and desc-list? list-is-nested)) + (bf-style-desc-list (and desc-list? (not list-is-nested))) + (struct (org-element-property :structure item)) + (item-num (car (last (org-list-get-item-number + (org-element-property :begin item) + struct + (org-list-prevs-alist struct) + (org-list-parents-alist struct))))) + (bullet (cond + ((or (eq parent-list-type 'unordered) + ox-md-style-desc-list) + "-") + ((eq parent-list-type 'ordered) + (format "%d. " item-num)) + (t ;Non-nested descriptive list item + (when (> item-num 1) + "\n")))) ;Newline between each descriptive list item + (padding (when (and (not bf-style-desc-list) + (<= (length bullet) 3)) + (make-string (- 4 (length bullet)) ? ))) + (tag (when desc-list? + (let* ((tag1 (org-element-property :tag item)) + (tag1-str (org-export-data tag1 info))) + (when tag1 + (if ox-md-style-desc-list + (format "**%s:** " tag1-str) + (format "%s\n: " tag1-str))))))) + (concat bullet + padding + (pcase (org-element-property :checkbox item) + (`on "[X] ") + (`trans "[-] ") + (`off "[ ] ")) + tag + (and contents + (org-trim (replace-regexp-in-string "^" " " contents)))))))) + +;;;; Latex Environment +(defun org-blackfriday--update-ltximg-path (html-str) + "Update the path in HTML-STR to latex exported images directory. + +For example, this function converts + + "))))) + ret)) + +;;;; Plain Text +(defun org-blackfriday-plain-text (text info) + "Transcode TEXT element into Blackfriday Markdown format. +TEXT is the string to transcode. INFO is a plist used as a +communication channel. + +TEXT would contain the text from one paragraph i.e. the content +separated by blank lines. + +This function is almost same as `org-md-plain-text' except it +first escapes any existing \"\\\", and then escapes other string +matches with \"\\\" as needed." + (let ((orig-text text)) + ;; The below series of replacements in `text' is order + ;; sensitive. + ;; Protect `, * and \ + (setq text (replace-regexp-in-string "[`*\\]" "\\\\\\&" text)) + ;; Protect _ only if it is preceded or followed by a word boundary + ;; ("\b" doesn't work because _ itself is considered to be a word + ;; boundary). + ;; "foo_ bar" -> "foo\_ bar" + (setq text (replace-regexp-in-string "\\([[:graph:]]\\)\\([_]\\)\\([[:space:].!?]\\|\\'\\)" "\\1\\\\\\2\\3" text)) + ;; "foo _bar" -> "foo \_bar" + (setq text (replace-regexp-in-string "\\([[:space:]]\\|\\`\\)\\([_]\\)\\([[:graph:]]\\)" "\\1\\\\\\2\\3" text)) + ;; Protect the characters in `org-html-protect-char-alist' (`<', + ;; `>', `&'). + (setq text (org-html-encode-plain-text text)) + ;; Protect braces when verbatim shortcode mentions are detected. + (setq text (replace-regexp-in-string "{{%" "{{%" text)) + (setq text (replace-regexp-in-string "%}}" "%}}" text)) + ;; Protect ambiguous #. This will protect # at the beginning of a + ;; line, but not at the beginning of a paragraph. See + ;; `org-md-paragraph'. + (setq text (replace-regexp-in-string "\n#" "\n\\\\#" text)) + ;; Protect ambiguous `!' + (setq text (replace-regexp-in-string "\\(!\\)\\[" "\\\\!" text nil nil 1)) + ;; Convert to smart quotes, if required. + (when (plist-get info :with-smart-quotes) + (setq text (org-export-activate-smart-quotes text :html info orig-text))) + ;; Handle special strings, if required. + (when (plist-get info :with-special-strings) + (setq text (org-html-convert-special-strings text))) + ;; Handle break preservation, if required. + (when (plist-get info :preserve-breaks) + (setq text (replace-regexp-in-string "[ \t]*\n" "
\n" text))) + ;; Return value. + text)) + +;;;; Quote Block +(defun org-blackfriday-quote-block (quote-block contents info) + "Transcode QUOTE-BLOCK element into Blackfriday Markdown format. +CONTENTS is the quote-block contents. INFO is a plist used as a +communication channel." + (let* ((next (org-export-get-next-element quote-block info)) + (next-type (org-element-type next)) + (next-is-quote (eq 'quote-block next-type)) + (contents (org-md-quote-block quote-block contents info)) + ret) + ;; (message "[ox-bf quote-block DBG]") + (setq ret (org-blackfriday--div-wrap-maybe quote-block contents info)) + (setq ret (concat ret + ;; Two consecutive blockquotes in Markdown can be + ;; separated by a comment. + (when next-is-quote + "\n\n"))) + ret)) + +;;;; Radio Target +(defun org-blackfriday-radio-target (radio-target text _info) + "Transcode a RADIO-TARGET object from Org to HTML. + +TEXT is nil." + (let* ((prefix (org-blackfriday--get-ref-prefix 'radio)) + (ref (format "%s%s" + prefix + (org-blackfriday--valid-html-anchor-name + (org-element-property :value radio-target)))) + (attr (format " class=\"%s\" id=\"%s\"" + (string-remove-suffix "--" prefix) + ref))) + (org-blackfriday--link-target attr text))) + +;;;; Special Block +(defun org-blackfriday-special-block (special-block contents info) + "Transcode a SPECIAL-BLOCK element from Org to HTML. +CONTENTS holds the contents of the block. + +INFO is a plist used as a communication channel. + +This function is adapted from `org-html-special-block'." + (let* ((block-type (org-element-property :type special-block)) + (block-type-plist (org-element-property :type-plist special-block)) + (html5-inline-fancy (member block-type org-blackfriday-html5-inline-elements)) + (html5-block-fancy (member block-type org-html-html5-elements)) + (html5-fancy (or html5-inline-fancy html5-block-fancy)) + (attributes (org-export-read-attribute :attr_html special-block)) + (trim-pre-tag (or (plist-get info :trim-pre-tag) "")) + (trim-post-tag (or (plist-get info :trim-post-tag) ""))) + (unless html5-fancy + (let ((class (plist-get attributes :class))) + (setq attributes (plist-put attributes :class + (if class + (concat class " " block-type) + block-type))))) + (let* ((contents (or (org-trim + (if (plist-get block-type-plist :raw) + ;; https://lists.gnu.org/r/emacs-orgmode/2022-01/msg00132.html + (org-element-interpret-data (org-element-contents special-block)) + contents)) + "")) + ;; If #+name is specified, use that for the HTML element + ;; "id" attribute. + (name (org-element-property :name special-block)) + (attr-str (org-blackfriday--make-attribute-string + (if (or (not name) (plist-member attributes :id)) + attributes + (plist-put attributes :id name)))) + (attr-str (if (org-string-nw-p attr-str) + (concat " " attr-str) + ""))) + (cond + ((string= block-type "details") + ;; Recognize Org Special blocks like: + ;; #+begin_details + ;; #+begin_summary + ;; This is summary. + ;; #+end_summary + ;; Here are the details. + ;; #+end_details + (let ((div-open "
")) + (setq contents + (concat + ;; Wrap the "details" portion in the
tag + ;; with '
..
'. With + ;; that, CSS rules can be set specific to that + ;; details portion using "details .details". + (if (string-match "\\(?1:\\(?:.\\|\n\\)*\\)" contents) ;If summary exists + (replace-match (format "\\1\n%s" div-open) nil nil contents 1) + (concat div-open "\n\n" contents)) + ;; Newline is inserted before the closing
+ ;; tag for the reason explained below using the + ;; emacs-lisp Markdown code block. + "\n"))) + ;; Insert the "open" attribute only if user has ":open t" in + ;; "#+attr_html". + (when (org-string-nw-p attr-str) + (when (string-match "\\(?1:open\\(?2:=\"\\(?3:t\\)\"\\)\\)" attr-str) + (if (match-string 3 attr-str) ;if attr-str contains `open="t"' + (setq attr-str (replace-match "" nil nil attr-str 2)) + (setq attr-str (replace-match "" nil nil attr-str 1))))) + ;; Insert a newline before and after the `contents' to handle + ;; the cases where that could begin or end with a Markdown + ;; blocks like: + ;; ```emacs-lisp + ;; (message "foo") + ;; ``` + ;; An example scenario would be where such content could be + ;; present in the "inline"
or Special + ;; Blocks. + ;; Without those newlines, the Markdown converted content will + ;; look like below, and Blackfriday won't parse it correctly. + ;;
```emacs-lisp + ;; (message "foo") + ;; ```
+ (format "<%s%s>\n%s\n" + block-type attr-str contents block-type)) + ((string= block-type "summary") + (format "<%s%s>%s" + block-type attr-str + (org-trim + ;; Remove "

" and "

" tags; Hugo will auto-wrap + ;; newline-separated blocks with p tags. + (replace-regexp-in-string + "\n\n+" "\n\n" ;Remove extra newlines + (replace-regexp-in-string + "" "" + (org-blackfriday--org-contents-to-html special-block)))) + block-type)) + (html5-inline-fancy ;Inline HTML elements like `mark', `cite'. + (format "%s<%s%s>%s%s" + trim-pre-tag block-type attr-str + contents block-type trim-post-tag)) + (html5-block-fancy + (format "%s<%s%s>%s\n\n%s\n\n%s" + trim-pre-tag block-type attr-str + (org-blackfriday--extra-div-hack info block-type) + contents block-type trim-post-tag)) + (t + (if (or (org-string-nw-p trim-pre-tag) + (org-string-nw-p trim-post-tag)) + (progn ;Use tag if any of the trimming options is enabled. + (format "%s%s%s" + trim-pre-tag attr-str + contents trim-post-tag) + ) + (progn ;Use
tag otherwise. + (format "%s%s\n\n%s\n\n
%s" + trim-pre-tag attr-str + (org-blackfriday--extra-div-hack info) + contents trim-post-tag)))))))) + +;;;; Src Block +(defun org-blackfriday-src-block (src-block _contents info) + "Transcode SRC-BLOCK element into Blackfriday Markdown format. + +INFO is a plist used as a communication channel." + (let* ((lang (org-element-property :language src-block)) + (lang (or (cdr (assoc lang org-blackfriday-syntax-highlighting-langs)) lang)) + (code (or (plist-get info :md-code) ;if set in `org-hugo-src-block' + (org-export-format-code-default src-block info))) + (code-attr (if (plist-get info :md-code-attr) ;if set in `org-hugo-src-block' + (format " { %s }" (plist-get info :md-code-attr)) + "")) + (parent-element (org-export-get-parent src-block)) + (parent-type (car parent-element)) + (num-backticks-in-code (when (string-match "^[[:blank:]]*\\(`\\{3,\\}\\)" code) + (length (match-string-no-properties 1 code)))) + backticks) + ;; In order to show the code-fence backticks in a code-fenced code + ;; block, you need to have the wrapping code fence to have at + ;; least 1 more backtick in the fence compared to those in the + ;; being-wrapped code fence. This example will explain better: + ;; + ;; ````md + ;; ```emacs-lisp + ;; (message "Hello") + ;; ``` + ;; ```` + (when (and (numberp num-backticks-in-code) + (<= org-blackfriday--code-block-num-backticks num-backticks-in-code)) + (setq org-blackfriday--code-block-num-backticks (1+ num-backticks-in-code))) + (setq backticks (make-string org-blackfriday--code-block-num-backticks ?`)) + ;; (message "[ox-bf src-block DBG] code: %s" code) + ;; (message "[ox-bf src-block DBG] parent type: %S" parent-type) + (setq code (org-blackfriday--issue-239-workaround code parent-type)) + (prog1 + (format "%s%s%s\n%s%s" backticks lang code-attr code backticks) + (when (equal 'quote-block parent-type) + ;; If the current code block is inside a quote block, future + ;; example/code blocks (especially the ones outside this quote + ;; block) will require higher number of backticks. Workaround + ;; for https://github.com/russross/blackfriday/issues/407. + (setq org-blackfriday--code-block-num-backticks + (1+ org-blackfriday--code-block-num-backticks))) + ;; Reset the temp info in the `info' plist. + (plist-put info :md-code nil) + (plist-put info :md-code-attr nil)))) + +;;;; Strike-Through +(defun org-blackfriday-strike-through (_strike-through contents _info) + "Transcode strike-through text into Blackfriday Markdown format. +CONTENTS contains the text with strike-through markup." + (format "~~%s~~" contents)) + +;;;; Table-Cell +(defun org-blackfriday-table-cell (table-cell contents info) + "Transcode TABLE-CELL element into Blackfriday Markdown format. + +CONTENTS is content of the cell. INFO is a plist used as a +communication channel." + ;; (message "[ox-bf-table-cell DBG]") + ;; (message "[ox-bf-table-cell DBG] In contents: %s" contents) + (let* ((table (org-export-get-parent-table table-cell)) + (column (cdr (org-export-table-cell-address table-cell info))) + (width (org-blackfriday-table-col-width table column info)) + (left-border (if (org-export-table-cell-starts-colgroup-p table-cell info) "| " " ")) + (right-border " |") + (data (or contents "")) + (cell (concat left-border + data + (make-string (max 0 (- width (string-width data))) ?\s) + right-border)) + (cell-width (length cell))) + ;; Just calling `org-blackfriday-table-cell-alignment' will save + ;; the alignment info for the current cell/column to the INFO + ;; channel.. magic! + (org-blackfriday-table-cell-alignment table-cell info) + ;; Each cell needs to be at least 3 characters wide (4 chars, + ;; including the table border char "|"), otherwise + ;; Hugo/Blackfriday does not render that as a table. + (when (< cell-width 4) + (setq cell (concat (make-string (- 4 cell-width) ? ) cell))) + ;; (message "[ox-bf-table-cell DBG] Cell:\n%s" cell) + cell)) + +;;;; Table-Row +(defun org-blackfriday-table-row (table-row contents info) + "Transcode TABLE-ROW element into Blackfriday Markdown format. + +CONTENTS is cell contents of TABLE-ROW. INFO is a plist used as a +communication channel." + ;; (message "[ox-bf-table-row DBG]") + (let* ((table (org-export-get-parent-table table-row)) + (row-num (cl-position ;Begins with 0 + table-row + (org-element-map table 'table-row #'identity info))) + (row contents)) ;If CONTENTS is `nil', row has to be returned as `nil' too + ;; Reset the state variable when the first row of the table is + ;; received. + (when (eq 0 row-num) + (setq org-blackfriday--hrule-inserted nil)) + + ;; (message "[ox-bf-table-row DBG] Row # %0d In contents: %s,\ntable-row: %S" row-num contents table-row) + (when (and row + (eq 'rule (org-element-property :type table-row)) + ;; In Blackfriday, rule is valid only at second row. + (eq 1 row-num)) + (let ((cols (cdr (org-export-table-dimensions table info)))) + (setq row (concat org-blackfriday-table-left-border + (mapconcat + (lambda (col) + (let ((max-width (max 3 (+ 1 (org-blackfriday-table-col-width table col info))))) + (make-string max-width ?-))) + (number-sequence 0 (- cols 1)) + org-blackfriday-table-separator) + org-blackfriday-table-right-border)))) + + ;; If the first table row is "abc | def", it needs to have a rule + ;; under it for Blackfriday to detect the whole object as a table. + (when (and (stringp row) + (null org-blackfriday--hrule-inserted)) + ;; (message "[ox-bf-table-row DBG] row: %s" row) + (let ((rule (replace-regexp-in-string "[^|]" "-" row)) + (pos 0) + (new-rule "") + matches) + ;; (message "[ox-bf-table-row DBG] rule: %s" rule) + ;; https://emacs.stackexchange.com/a/7150/115 + (while (string-match "|-+" rule pos) + (push (match-string 0 rule) matches) + (setq pos (match-end 0))) + (setq matches (nreverse matches)) + ;; Get the align-vector that was saved in the INFO channel in + ;; `org-blackfriday-table-cell-alignment'. + (let* ((alignment-cache (plist-get info :table-cell-alignment-cache)) + (align-vector (gethash table alignment-cache)) + (col 0)) + ;; (message "[ox-bf-table-row DBG] align-vector: %S" align-vector) + (dolist (match matches) + (let ((align (aref align-vector col))) + (when (member align '(left center)) + (setq match (replace-regexp-in-string "\\`|-" "|:" match))) + (when (member align '(right center)) + (setq match (replace-regexp-in-string "-\\'" ":" match)))) + (setq new-rule (concat new-rule match)) + (setq col (1+ col)))) + (setq new-rule (concat new-rule "|")) + ;; (message "[ox-bf-table-row DBG] new-rule: %s" new-rule) + (setq row (concat row "\n" new-rule)) + (setq org-blackfriday--hrule-inserted t))) + ;; (message "[ox-bf-table-row DBG] Row:\n%s" row) + row)) + +;;;; Table +(defun org-blackfriday-table (table contents info) + "Transcode TABLE element into Blackfriday Markdown format. + +CONTENTS is contents of the table. INFO is a plist holding +contextual information." + ;; (message "[ox-bf-table DBG] In contents: %s" contents) + (if (eq (org-element-property :type table) 'table.el) + ;; "table.el" table. Convert it using appropriate tools. + (let ((tbl (org-html-table--table.el-table table info))) + (replace-regexp-in-string + "\\(\n\\)" "\\1/table.el\\2" tbl)) + ;; Standard table. + (let* ((rows (org-element-map table 'table-row 'identity info)) + (no-header (= (length rows) 1)) ;No header if table has just 1 row + (table-ref (org-blackfriday--get-reference table)) + (table-anchor (if table-ref + (format "\n" table-ref) + "")) + (caption (org-export-get-caption table)) + table-num + (blank-line-before-table "") + (caption-html (if (not caption) + "" + (let ((caption-prefix (org-blackfriday--translate 'table info)) + (caption-str + (org-html-convert-special-strings ;Interpret em-dash, en-dash, etc. + (org-export-data-with-backend caption 'html info)))) + (setq table-num (org-export-get-ordinal + table info + nil #'org-html--has-caption-p)) + (format (concat "
\n" + " %s:\n" + " %s\n" + "
\n") + (if table-ref ;Hyperlink the table prefix + number + (format "%s %s" + table-ref caption-prefix table-num) + (format "%s %s" + caption-prefix table-num)) + caption-str)))) + (attr (org-export-read-attribute :attr_html table)) + ;; At the moment only the `class' attribute is supported in + ;; #+attr_html above tables. + (table-class-user (plist-get attr :class)) + (table-class-auto (concat "table-" + (if table-num + (format "%d" table-num) + "nocaption"))) + (table-class (or table-class-user + table-class-auto)) + ;; If user has specified multiple classes for the table + ;; (space-separated), use only the first class in that list + ;; to specifying the styling in the \n\n" + table-class-this css-props-str))) + ;; Export user-specified table class explicitly. + (when (or (org-string-nw-p table-class-user) + (org-string-nw-p css-props-str)) + (setq table-pre (concat table-pre + (format "
%s\n" + table-class + (org-blackfriday--extra-div-hack info))))) + (when (org-string-nw-p table-pre) + (setq table-post (concat "\n" + "
\n"))) + + ;; If the table has only 1 row, do *not* make it a header row.. + ;; instead create an empty header row. + ;; For 1-row, tbl would look like this at this point: + ;; + ;; | a | b | + ;; |---|---| + ;; + ;; Below will convert that to: + ;; + ;; | | | + ;; |---|---| + ;; | a | b | + (when no-header + (string-match "\\`\\(.*\\)\n\\(.*\\)\n\\'" tbl) + (let* ((row-1 (match-string-no-properties 1 tbl)) + (hrule (match-string-no-properties 2 tbl)) + (dummy-header (replace-regexp-in-string "[-:]" " " hrule))) + (setq tbl (concat dummy-header "\n" hrule "\n" row-1)))) + ;; (message "[ox-bf-table DBG] Tbl:\n%s" tbl) + + ;; A blank line is needed to separate the Markdown table and + ;; the table anchor/caption HTML. + (unless (string= (concat table-pre table-anchor caption-html) "") + (setq blank-line-before-table "\n")) + + (concat table-pre table-anchor caption-html + blank-line-before-table tbl table-post)))) + +;;;; Target +(defun org-blackfriday--get-target-anchor (target) + "Get HTML anchor for TARGET element. + +By default, the returned anchor string is the HTML sanitized +target name (`:value' property of TARGET element) with a prefix +returned by `org-blackfriday--get-ref-prefix'. + +If the anchor string begins with \".\", the returned anchor +string is just the HTML sanitized target name without that \".\". + + TARGET NAME ANCHOR + + abc org-target--abc + abc def org-target--abc-def + .abc abc" + (let ((target-name (org-element-property :value target)) + (verbatim-target-prefix ".") ;This needs to be non-alpha-numeric, and not an Org-recognized link prefix like "#" + (prefix "")) + (unless (string-prefix-p verbatim-target-prefix target-name) + (setq prefix (org-blackfriday--get-ref-prefix 'target))) + ;; Below function will auto-remove the `verbatim-target-prefix' if + ;; present. + (setq target-name (org-blackfriday--valid-html-anchor-name target-name)) + (format "%s%s" prefix target-name))) + +(defun org-blackfriday-target (target _contents _info) + "Transcode a TARGET object from Org to HTML. +CONTENTS is nil." + (let* ((class (string-remove-suffix "--" + (org-blackfriday--get-ref-prefix 'target))) + (anchor (org-blackfriday--get-target-anchor target)) + (attr (format " class=\"%s\" id=\"%s\"" class anchor))) + (org-blackfriday--link-target attr))) + +;;;; Verse Block +(defun org-blackfriday-verse-block (_verse-block contents info) + "Transcode a VERSE-BLOCK element from Org to partial HTML. +CONTENTS is verse block contents. INFO is a plist holding +contextual information." + (let* ((ret contents) + ;; Org removes all the leading whitespace only from the first + ;; line. So the trick is to use the ">" character before any + ;; intended indentation on the first non-blank line. + (ret (replace-regexp-in-string "\\`\\([[:blank:]\n\r]*?\\)[[:blank:]]*>" "\\1" ret)) + (br (org-html-close-tag "br" nil info)) + (re (format "\\(?:%s\\)?[ \t]*\n" (regexp-quote br))) + ;; Replace each newline character with line break. Also + ;; remove any trailing "br" close-tag so as to avoid + ;; duplicates. + (ret (replace-regexp-in-string re (concat br "\n") ret)) + ;; Replace leading white spaces with non-breaking spaces. + (ret (replace-regexp-in-string + "^[[:blank:]]+" + (lambda (m) + (org-html--make-string (length m) " ")) + ret)) + (ret (format "
\n\n%s\n
" ret))) + ret)) + + + +;;; Interactive functions + +;;;###autoload +(defun org-blackfriday-export-as-markdown (&optional async subtreep visible-only) + "Export current buffer to a Github Flavored 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 heading 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 BLACKFRIDAY Export*\", which will +be displayed when `org-export-show-temporary-export-buffer' is +non-nil." + (interactive) + (org-export-to-buffer 'blackfriday "*Org BLACKFRIDAY Export*" + async subtreep visible-only nil nil (lambda () (text-mode)))) + +;;;###autoload +(defun org-blackfriday-convert-region-to-md () + "Convert text in the current region to Blackfriday Markdown. +The text is assumed to be in Org mode format. + +This can be used in any buffer. For example, you can write an +itemized list in Org mode syntax in a Markdown buffer and use +this command to convert it." + (interactive) + (org-export-replace-region-by 'blackfriday)) + +;;;###autoload +(defun org-blackfriday-export-to-markdown (&optional async subtreep visible-only) + "Export current buffer to a Github Flavored 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 heading properties +first. + +When optional argument VISIBLE-ONLY is non-nil, don't export +contents of hidden elements. + +Return output file's name." + (interactive) + (let ((outfile (org-export-output-file-name ".md" subtreep))) + (org-export-to-file 'blackfriday outfile async subtreep visible-only))) + +;;;###autoload +(defun org-blackfriday-publish-to-blackfriday (plist filename pub-dir) + "Publish an Org file to Blackfriday Markdown file. + +PLIST is the property list for the given project. FILENAME is +the filename of the Org file to be published. PUB-DIR is the +publishing directory. + +Return output file name." + (org-publish-org-to 'blackfriday filename ".md" plist pub-dir)) + + +(provide 'ox-blackfriday) + + +;;; ox-blackfriday.el ends here diff --git a/org/elpa/ox-hugo-20221028.1631/ox-hugo-autoloads.el b/org/elpa/ox-hugo-20221028.1631/ox-hugo-autoloads.el new file mode 100644 index 0000000..bab2597 --- /dev/null +++ b/org/elpa/ox-hugo-20221028.1631/ox-hugo-autoloads.el @@ -0,0 +1,296 @@ +;;; ox-hugo-autoloads.el --- automatically extracted autoloads -*- lexical-binding: t -*- +;; +;;; Code: + +(add-to-list 'load-path (directory-file-name + (or (file-name-directory #$) (car load-path)))) + + +;;;### (autoloads nil "org-hugo-auto-export-mode" "org-hugo-auto-export-mode.el" +;;;;;; (0 0 0 0)) +;;; Generated autoloads from org-hugo-auto-export-mode.el + +(autoload 'org-hugo-auto-export-mode "org-hugo-auto-export-mode" "\ +Toggle auto exporting the Org file using `ox-hugo'. + +This is a minor mode. If called interactively, toggle the +`Org-Hugo-Auto-Export mode' mode. If the prefix argument is +positive, enable the mode, and if it is zero or negative, disable +the mode. + +If called from Lisp, toggle the mode if ARG is `toggle'. Enable +the mode if ARG is nil, omitted, or is a positive number. +Disable the mode if ARG is a negative number. + +To check whether the minor mode is enabled in the current buffer, +evaluate `org-hugo-auto-export-mode'. + +The mode's hook is called both when the mode is enabled and when +it is disabled. + +\(fn &optional ARG)" t nil) + +(register-definition-prefixes "org-hugo-auto-export-mode" '("org-hugo-export-wim-to-md-after-save")) + +;;;*** + +;;;### (autoloads nil "ox-blackfriday" "ox-blackfriday.el" (0 0 0 +;;;;;; 0)) +;;; Generated autoloads from ox-blackfriday.el + +(autoload 'org-blackfriday-export-as-markdown "ox-blackfriday" "\ +Export current buffer to a Github Flavored 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 heading 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 BLACKFRIDAY Export*\", which will +be displayed when `org-export-show-temporary-export-buffer' is +non-nil. + +\(fn &optional ASYNC SUBTREEP VISIBLE-ONLY)" t nil) + +(autoload 'org-blackfriday-convert-region-to-md "ox-blackfriday" "\ +Convert text in the current region to Blackfriday Markdown. +The text is assumed to be in Org mode format. + +This can be used in any buffer. For example, you can write an +itemized list in Org mode syntax in a Markdown buffer and use +this command to convert it." t nil) + +(autoload 'org-blackfriday-export-to-markdown "ox-blackfriday" "\ +Export current buffer to a Github Flavored 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 heading properties +first. + +When optional argument VISIBLE-ONLY is non-nil, don't export +contents of hidden elements. + +Return output file's name. + +\(fn &optional ASYNC SUBTREEP VISIBLE-ONLY)" t nil) + +(autoload 'org-blackfriday-publish-to-blackfriday "ox-blackfriday" "\ +Publish an Org file to Blackfriday Markdown file. + +PLIST is the property list for the given project. FILENAME is +the filename of the Org file to be published. PUB-DIR is the +publishing directory. + +Return output file name. + +\(fn PLIST FILENAME PUB-DIR)" nil nil) + +(register-definition-prefixes "ox-blackfriday" '("org-blackfriday-")) + +;;;*** + +;;;### (autoloads nil "ox-hugo" "ox-hugo.el" (0 0 0 0)) +;;; Generated autoloads from ox-hugo.el + (put 'org-hugo-base-dir 'safe-local-variable 'stringp) + (put 'org-hugo-goldmark 'safe-local-variable 'booleanp) + (put 'org-hugo-section 'safe-local-variable 'stringp) + (put 'org-hugo-front-matter-format 'safe-local-variable 'stringp) + (put 'org-hugo-footer 'safe-local-variable 'stringp) + (put 'org-hugo-preserve-filling 'safe-local-variable 'booleanp) + (put 'org-hugo-delete-trailing-ws 'safe-local-variable 'booleanp) + (put 'org-hugo-use-code-for-kbd 'safe-local-variable 'booleanp) + (put 'org-hugo-allow-spaces-in-tags 'safe-local-variable 'booleanp) + (put 'org-hugo-prefer-hyphen-in-tags 'safe-local-variable 'booleanp) + (put 'org-hugo-auto-set-lastmod 'safe-local-variable 'booleanp) + (put 'org-hugo-suppress-lastmod-period 'safe-local-variable 'floatp) + (put 'org-hugo-export-with-toc 'safe-local-variable (lambda (x) (or (booleanp x) (integerp x)))) + (put 'org-hugo-export-with-section-numbers 'safe-local-variable (lambda (x) (or (booleanp x) (equal 'onlytoc x) (integerp x)))) + (put 'org-hugo-default-static-subdirectory-for-externals 'safe-local-variable 'stringp) + (put 'org-hugo-export-creator-string 'safe-local-variable 'stringp) + (put 'org-hugo-date-format 'safe-local-variable 'stringp) + (put 'org-hugo-paired-shortcodes 'safe-local-variable 'stringp) + (put 'org-hugo-link-desc-insert-type 'safe-local-variable 'booleanp) + (put 'org-hugo-container-element 'safe-local-variable 'stringp) + +(autoload 'org-hugo-slug "ox-hugo" "\ +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 because STR is often a post's sub-heading (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 \"..\", + \"..\", 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. +- If ALLOW-DOUBLE-HYPHENS is non-nil, at most two consecutive + hyphens are allowed in the returned string, otherwise consecutive + hyphens are not returned. +- No hyphens allowed at the leading or trailing end of the slug. + +\(fn STR &optional ALLOW-DOUBLE-HYPHENS)" nil nil) + +(autoload 'org-hugo-export-as-md "ox-hugo" "\ +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 heading 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. + +\(fn &optional ASYNC SUBTREEP VISIBLE-ONLY)" t nil) + +(autoload 'org-hugo-export-to-md "ox-hugo" "\ +Export current buffer to a Hugo-compatible Markdown file. + +This is the main exporting function which is called by both +`org-hugo--export-file-to-md' and +`org-hugo--export-subtree-to-md', and thus +`org-hugo-export-wim-to-md' too. + +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 heading properties +first. + +When optional argument VISIBLE-ONLY is non-nil, don't export +contents of hidden elements. + +Return output file's name. + +\(fn &optional ASYNC SUBTREEP VISIBLE-ONLY)" t nil) + +(autoload 'org-hugo-export-wim-to-md "ox-hugo" "\ +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 only that subtree. Return the return value of + `org-hugo--export-subtree-to-md'. + +- If the current subtree doesn't have that property, but one of + its parent subtrees has, export from that subtree's scope. + Return the return value of `org-hugo--export-subtree-to-md'. + +- If there are no valid Hugo post subtrees (that have the + \"EXPORT_FILE_NAME\" property) in the Org buffer the subtrees + have that property, do file-based + export (`org-hugo--export-file-to-md'), regardless of the value + of ALL-SUBTREES. Return the return value of + `org-hugo--export-file-to-md'. + +- If ALL-SUBTREES is non-nil and the Org buffer has at least 1 + valid Hugo post subtree, export all those valid post subtrees. + Return a list of output files. + +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'. + +\(fn &optional ALL-SUBTREES ASYNC VISIBLE-ONLY NOERROR)" t nil) + +(autoload 'org-hugo-debug-info "ox-hugo" "\ +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." t nil) + +(register-definition-prefixes "ox-hugo" '("org-hugo-")) + +;;;*** + +;;;### (autoloads nil "ox-hugo-deprecated" "ox-hugo-deprecated.el" +;;;;;; (0 0 0 0)) +;;; Generated autoloads from ox-hugo-deprecated.el + +(register-definition-prefixes "ox-hugo-deprecated" '("org-hugo-")) + +;;;*** + +;;;### (autoloads nil "ox-hugo-pandoc-cite" "ox-hugo-pandoc-cite.el" +;;;;;; (0 0 0 0)) +;;; Generated autoloads from ox-hugo-pandoc-cite.el + +(register-definition-prefixes "ox-hugo-pandoc-cite" '("org-hugo-pandoc-cite-")) + +;;;*** + +;;;### (autoloads nil nil ("ox-hugo-pkg.el") (0 0 0 0)) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; coding: utf-8 +;; End: +;;; ox-hugo-autoloads.el ends here diff --git a/org/elpa/ox-hugo-20221028.1631/ox-hugo-deprecated.el b/org/elpa/ox-hugo-20221028.1631/ox-hugo-deprecated.el new file mode 100644 index 0000000..15cfe19 --- /dev/null +++ b/org/elpa/ox-hugo-20221028.1631/ox-hugo-deprecated.el @@ -0,0 +1,442 @@ +;;; ox-hugo-deprecated.el --- Deprecated stuff from ox-hugo -*- lexical-binding: t -*- + +;; Authors: Kaushal Modi +;; URL: https://ox-hugo.scripter.co + +;;; Commentary: + +;; This file contains variables and functions deprecated from ox-hugo. +;; Do not depend on this file as it may disappear any day. + + +;;; Obsoletions + +;; Blackfriday support is being removed from `ox-hugo' as Hugo has +;; deprecated its support for a while. +;; https://github.com/kaushalmodi/ox-hugo/discussions/485 + +;;; Code: + +;; Silence byte-compiler +(defvar org-hugo--date-time-regexp) +(defvar org-hugo--subtree-coord) +(declare-function org-string-nw-p "org-macs") +(declare-function org-hugo--calc-weight "ox-hugo") +(declare-function org-hugo-slug "ox-hugo") +(declare-function org-hugo--front-matter-value-booleanize "ox-hugo") +(declare-function org-hugo--delim-str-to-list "ox-hugo") +(declare-function org-hugo--parse-property-arguments "ox-hugo") +;; + +(make-obsolete-variable 'org-hugo-blackfriday-options nil "Hugo has switched to use Goldmark as the default Markdown parser since v0.60." "Jan 15, 2022") +(make-obsolete-variable 'org-hugo-blackfriday-extensions nil "Hugo has switched to use Goldmark as the default Markdown parser since v0.60." "Jan 15, 2022") + + +;;; Variables + +(defvar org-hugo-blackfriday-options + '("taskLists" + "smartypants" + "smartypantsQuotesNBSP" + "angledQuotes" + "fractions" + "smartDashes" + "latexDashes" + "hrefTargetBlank" + "plainIDAnchors" + "extensions" + "extensionsmask") + "Deprecated Blackfriday parser option names.") + +(defvar org-hugo-blackfriday-extensions + '("noIntraEmphasis" + "tables" + "fencedCode" + "autolink" + "strikethrough" + "laxHtmlBlocks" + "spaceHeaders" + "hardLineBreak" + "tabSizeEight" + "footnotes" + "noEmptyLineBeforeBlock" + "headerIds" + "titleblock" + "autoHeaderIds" + "backslashLineBreak" + "definitionLists" + "joinLines") + "Deprecated Blackfriday extension names.") + + + +;;; Functions + +(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--yaml-quote-string ret))) + +;;; YAML Support +(defun org-hugo--yaml-quote-string (val &optional prefer-no-quotes) + "Wrap VAL with quotes as appropriate. + +VAL can be a string, symbol, number or nil. + +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." + (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)) + + ;; 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)) + (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--get-yaml-list-string (key list) + "Return KEY's LIST value as a YAML 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--yaml-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) + ", ") + "]")) + +(defun org-hugo--gen-yaml-front-matter (data) + "Generate Hugo front-matter in YAML format, 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." + (let ((sep "---\n") + (sign ":") + (front-matter "") + (indent (make-string 2 ? )) + (nested-string "") + (menu-string "") + (res-string "")) + (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" + (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--yaml-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 (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 + (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--yaml-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 + ;; For YAML, this string + ;; needs to be inserted + ;; only once. + (if (org-string-nw-p res-string) + "" + (format "resources%s\n" sign))) + (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 (format " %s%s\n" res-key sign)) + (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-list-string param-key param-value) + (org-hugo--yaml-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 (string= res-key "src") + (setq indent "- ") + (setq indent " ")) + (setq res-value (org-hugo--yaml-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 (format "%s%s\n" nested-parent-key sign)) + (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-list-string + nested-key + (mapcar #'org-hugo--return-valid-blackfriday-extension + nested-value)) + (org-hugo--get-yaml-list-string nested-key nested-value))) + ((null nested-value) + "false") + ((equal nested-value 't) + "true") + (t + (org-hugo--yaml-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-list-string key value)) + (t + (org-hugo--yaml-quote-string value nil))))))))))) + (concat sep front-matter nested-string menu-string res-string sep))) + + +(provide 'ox-hugo-deprecated) + +;;; ox-hugo-deprecated.el ends here diff --git a/org/elpa/ox-hugo-20221028.1631/ox-hugo-pandoc-cite.el b/org/elpa/ox-hugo-20221028.1631/ox-hugo-pandoc-cite.el new file mode 100644 index 0000000..0c82816 --- /dev/null +++ b/org/elpa/ox-hugo-20221028.1631/ox-hugo-pandoc-cite.el @@ -0,0 +1,415 @@ +;;; ox-hugo-pandoc-cite.el --- Pandoc Citations support for ox-hugo -*- lexical-binding: t -*- + +;; Authors: Kaushal Modi +;; URL: https://ox-hugo.scripter.co + +;;; Commentary: + +;; *This is NOT a stand-alone package.* +;; +;; It is used by ox-hugo to add support for parsing Pandoc Citations. + +;;; Code: + +(require 'org) + +(declare-function org-hugo--plist-get-true-p "ox-hugo") +(declare-function org-hugo--front-matter-value-booleanize "ox-hugo") + +(defcustom org-hugo-pandoc-cite-references-heading "References {#references}" + "Markdown title for Pandoc inserted references section." + :group 'org-export-hugo + :type 'string) + +(defvar org-hugo--fm-yaml) ;Silence byte-compiler + +(defvar org-hugo-pandoc-cite-pandoc-args-list + `("-f" "markdown" + "-t" ,(concat "markdown-citations" + "-simple_tables" + "+pipe_tables" + "-raw_attribute" + "-fenced_divs" + "-fenced_code_attributes" + "-bracketed_spans") + "--markdown-headings=atx" + "--id-prefix=fn:" + "--citeproc") + "Pandoc arguments used in `org-hugo-pandoc-cite--run-pandoc'. + +-f markdown : Convert *from* Markdown + +-t markdown : Convert *to* Markdown + -citations : Remove the \"citations\" extension. This will cause + citations to be expanded instead of being included as + markdown citations. + + -simple_tables : Remove the \"simple_tables\" style. + + +pipe_tables : Add the \"pipe_tables\" style insted that Blackfriday + understands. + + -fenced_divs : Do not replace HTML
tags with Pandoc fenced + divs \":::\". + + -fenced_code_attributes : Create fenced code blocks like + \"``` lang .. ```\" instead of \"``` {.lang} .. ```\". + + -bracketed_spans : Do not replace HTML tags with Pandoc + bracketed class notation \"{.some-class}\". + +--atx-headers : Use \"# foo\" style heading for output markdown. + +--id-prefix=fn: : Create footnote ID's like \"[^fn:1]\" instead of + \"[^1]\" to be consistent with default ox-hugo + exported Markdown footnote style. + +These arguments are added to the `pandoc' call in addition to the +\"--bibliography\", output file (\"-o\") and input file +arguments.") + +(defvar org-hugo-pandoc-cite-pandoc-meta-data + '("nocite" "csl" "link-citations") + "List of meta-data fields specific to Pandoc.") + +(defvar org-hugo-pandoc-cite--run-pandoc-buffer "*Pandoc Citations*" + "Buffer to contain the `pandoc' run output and errors.") + +(defvar org-hugo-pandoc-cite--references-header-regexp + "^
]+>" + "Regexp to match the Pandoc-inserted references header string. + +This string is present only if Pandoc has resolved one or more +references. + +Pandoc 2.11.4.") + +(defvar org-hugo-pandoc-cite--reference-entry-regexp + "^
]+>" + "Regexp to match the Pandoc-inserted reference entry strings. + +Pandoc 2.11.4.") + +(defun org-hugo-pandoc-cite--restore-fm-in-orig-outfile (orig-outfile fm &optional orig-full-contents) + "Restore the intended front-matter format in ORIG-OUTFILE. + +ORIG-OUTFILE is the Org exported file name. + +FM is the intended front-matter format. + +ORIG-FULL-CONTENTS is a string of ORIG-OUTFILE contents. If this +is nil it is created in this function. + +If FM is already in YAML format, this function doesn't do +anything. Otherwise, the YAML format front-matter in +ORIG-OUTFILE is replaced with TOML format." + (unless (string= fm org-hugo--fm-yaml) + (unless orig-full-contents + (setq orig-full-contents (with-temp-buffer + (insert-file-contents orig-outfile) + (buffer-substring-no-properties + (point-min) (point-max))))) + (setq fm (org-hugo-pandoc-cite--remove-pandoc-meta-data fm)) + (let* ((orig-contents-only + (replace-regexp-in-string + ;; The `orig-contents-only' will always be in YAML. + ;; Delete that first. + "\\`---\n\\(.\\|\n\\)+\n---\n" "" orig-full-contents)) + (toml-fm-plus-orig-contents (concat fm orig-contents-only))) + ;; (message "[ox-hugo-pandoc-cite] orig-contents-only: %S" orig-contents-only) + (write-region toml-fm-plus-orig-contents nil orig-outfile)))) + +(defun org-hugo-pandoc-cite--run-pandoc (orig-outfile bib-list) + "Run the `pandoc' process and return the generated file name. + +ORIG-OUTFILE is the Org exported file name. + +BIB-LIST is a list of one or more bibliography files." + ;; First kill the Pandoc run buffer if already exists (from a + ;; previous run). + (when (get-buffer org-hugo-pandoc-cite--run-pandoc-buffer) + (kill-buffer org-hugo-pandoc-cite--run-pandoc-buffer)) + (let* ((pandoc-outfile (make-temp-file ;ORIG_FILE_BASENAME.RANDOM.md + (concat (file-name-base orig-outfile) ".") + nil ".md")) + (bib-args (mapcar (lambda (bib-file) + (concat "--bibliography=" + bib-file)) + bib-list)) + (pandoc-arg-list (append + org-hugo-pandoc-cite-pandoc-args-list + bib-args + `("-o" ,pandoc-outfile ,orig-outfile))) ;-o + (pandoc-arg-list-str (mapconcat #'identity pandoc-arg-list " ")) + exit-code) + (message (concat "[ox-hugo] Post-processing citations using Pandoc command:\n" + " pandoc " pandoc-arg-list-str)) + + (setq exit-code (apply 'call-process + (append + `("pandoc" nil + ,org-hugo-pandoc-cite--run-pandoc-buffer :display) + pandoc-arg-list))) + + (unless (= 0 exit-code) + (user-error (format "[ox-hugo] Pandoc execution failed. See the %S buffer" + org-hugo-pandoc-cite--run-pandoc-buffer))) + pandoc-outfile)) + +(defun org-hugo-pandoc-cite--remove-pandoc-meta-data (fm) + "Remove Pandoc meta-data from front-matter string FM and return it. + +The list of Pandoc specific meta-data is defined in +`org-hugo-pandoc-cite-pandoc-meta-data'." + (with-temp-buffer + (insert fm) + (goto-char (point-min)) + (let ((regexp (format "^%s\\(:\\| =\\) " + (regexp-opt org-hugo-pandoc-cite-pandoc-meta-data 'words)))) + (delete-matching-lines regexp)) + (buffer-substring-no-properties (point-min) (point-max)))) + +(defun org-hugo-pandoc-cite--fix-pandoc-output (content loffset info) + "Fix the Pandoc output CONTENT and return it. + +LOFFSET is the heading level offset. + +Required fixes: + +- Prepend Pandoc inserted \"references\" class div with + `org-hugo-pandoc-cite-references-heading'. + +- When not using Goldmark (Hugo v0.60.0+), add the Blackfriday + required \"
\" hack to Pandoc divs with \"ref\" id's. + +- Unescape the Hugo shortcodes: \"{{\\\\=< shortcode \\\\=>}}\" -> + \"{{< shortcode >}}\" + +INFO is a plist used as a communication channel." + (with-temp-buffer + (insert content) + (let ((case-fold-search nil)) + (goto-char (point-min)) + + ;; Prepend the Pandoc inserted "references" class div with + ;; `org-hugo-pandoc-cite-references-heading' heading in Markdown. + (save-excursion + ;; There should be at max only one replacement needed for + ;; this. + (when (re-search-forward org-hugo-pandoc-cite--references-header-regexp nil :noerror) + (let ((references-heading "")) + (when (org-string-nw-p org-hugo-pandoc-cite-references-heading) + (let ((level-mark (make-string (+ loffset 1) ?#))) + (setq references-heading (concat level-mark " " org-hugo-pandoc-cite-references-heading)))) + (replace-match (concat references-heading "\n\n\\&" + (unless (org-hugo--plist-get-true-p info :hugo-goldmark) + "\n
\n")))))) ;See footnote 1 + + ;; Add the Blackfriday required hack to Pandoc ref divs. + (unless (org-hugo--plist-get-true-p info :hugo-goldmark) + (save-excursion + (while (re-search-forward org-hugo-pandoc-cite--reference-entry-regexp nil :noerror) + (replace-match "\\&\n
")))) ;See footnote 1 + + ;; Fix Hugo shortcodes. + (save-excursion + (let ((regexp (concat "{{\\\\<" + "\\(\\s-\\|\n\\)+" + "\\(?1:[[:ascii:][:nonascii:]]+?\\)" + "\\(\\s-\\|\n\\)+" + "\\\\>}}"))) + (while (re-search-forward regexp nil :noerror) + (let* ((sc-body (match-string-no-properties 1)) + (sc-body-no-newlines (replace-regexp-in-string "\n" " " sc-body)) + ;; Remove all backslashes except for the one + ;; preceding double-quotes, like in: + ;; {{< figure src="nested-boxes.svg" caption="Figure 1: + ;; PlantUML generated figure showing nested boxes" >}} + (sc-body-no-backlash (replace-regexp-in-string + "\"\"" "\\\\\\\\\"" + (replace-regexp-in-string + (rx "\\" (group anything)) "\\1" sc-body-no-newlines)))) + (replace-match (format "{{< %s >}}" sc-body-no-backlash) :fixedcase))))) + + ;; Fix square bracket. \[ abc \] -> [ abc ] + (save-excursion + (let ((regexp (concat + "\\\\\\[" + "\\(.+\\)" + "\\\\\\]"))) + (while (re-search-forward regexp nil :noerror) + (let* ((sc-body (match-string-no-properties 1))) + ;; (message "square bracket [%s]" sc-body) + (replace-match (format "[%s]" sc-body) :fixedcase))))) + + (buffer-substring-no-properties (point-min) (point-max))))) + +(defun org-hugo-pandoc-cite--parse-citations (info orig-outfile) + "Parse Pandoc Citations in ORIG-OUTFILE and update that file. + +INFO is a plist used as a communication channel. + +ORIG-OUTFILE is the Org exported file name." + (let ((bib-list (let ((bib-raw + (org-string-nw-p + (or (org-entry-get nil "EXPORT_BIBLIOGRAPHY" :inherit) + (format "%s" (plist-get info :bibliography)))))) + (when bib-raw + ;; Multiple bibliographies can be comma or + ;; newline separated. The newline separated + ;; bibliographies work only for the + ;; #+bibliography keyword; example: + ;; + ;; #+bibliography: bibliographies-1.bib + ;; #+bibliography: bibliographies-2.bib + ;; + ;; If using the subtree properties they need to + ;; be comma-separated (now don't use commas in + ;; those file names, you will suffer): + ;; + ;; :EXPORT_BIBLIOGRAPHY: bibliographies-1.bib, bibliographies-2.bib + (let ((bib-list-1 (org-split-string bib-raw "[,\n]"))) + ;; - Don't allow spaces around bib names. + ;; - Remove duplicate bibliographies. + (delete-dups + (mapcar (lambda (bib-file) + (let ((fname (org-trim bib-file))) + (unless (file-exists-p fname) + (user-error "[ox-hugo] Bibliography file %S does not exist" + fname)) + fname)) + bib-list-1))))))) + (if bib-list + (let ((fm (plist-get info :front-matter)) + (loffset (string-to-number + (or (org-entry-get nil "EXPORT_HUGO_LEVEL_OFFSET" :inherit) + (plist-get info :hugo-level-offset)))) + (pandoc-outfile (org-hugo-pandoc-cite--run-pandoc orig-outfile bib-list))) + ;; (message "[ox-hugo parse citations] fm :: %S" fm) + ;; (message "[ox-hugo parse citations] loffset :: %S" loffset) + ;; (message "[ox-hugo parse citations] pandoc-outfile :: %S" pandoc-outfile) + + (let* ((pandoc-outfile-contents (with-temp-buffer + (insert-file-contents pandoc-outfile) + (buffer-substring-no-properties + (point-min) (point-max)))) + (content-has-references (string-match-p + org-hugo-pandoc-cite--references-header-regexp + pandoc-outfile-contents))) + ;; Prepend the original ox-hugo generated front-matter to + ;; Pandoc output, only if the Pandoc output contains + ;; references. + (if content-has-references + (let* ((contents-fixed (org-hugo-pandoc-cite--fix-pandoc-output + pandoc-outfile-contents loffset info)) + (fm (org-hugo-pandoc-cite--remove-pandoc-meta-data fm)) + (fm-plus-content (concat fm "\n" contents-fixed))) + (write-region fm-plus-content nil orig-outfile)) + (org-hugo-pandoc-cite--restore-fm-in-orig-outfile orig-outfile fm) + (message (concat "[ox-hugo] Using the original Ox-hugo output instead " + "of Pandoc output as it contained no References")))) + (delete-file pandoc-outfile) + + (with-current-buffer org-hugo-pandoc-cite--run-pandoc-buffer + (if (> (point-max) 1) ;buffer is not empty + (message + (format + (concat "[ox-hugo] See the %S buffer for possible Pandoc warnings.\n" + " Review the exported Markdown file for possible missing citations.") + org-hugo-pandoc-cite--run-pandoc-buffer)) + ;; Kill the Pandoc run buffer if it is empty. + (kill-buffer org-hugo-pandoc-cite--run-pandoc-buffer)))) + (message "[ox-hugo] No bibliography file was specified")))) + +(defun org-hugo-pandoc-cite--parse-citations-maybe (info) + "Check if Pandoc needs to be run to parse citations; and run it. + +INFO is a plist used as a communication channel." + ;; (message "pandoc citations keyword: %S" + ;; (org-hugo--plist-get-true-p info :hugo-pandoc-citations)) + ;; (message "pandoc citations prop: %S" + ;; (org-entry-get nil "EXPORT_HUGO_PANDOC_CITATIONS" :inherit)) + (let* ((orig-outfile (plist-get info :outfile)) + (fm (plist-get info :front-matter)) + (has-nocite (string-match-p "^nocite\\(:\\| =\\) " fm)) + (orig-outfile-contents (with-temp-buffer + (insert-file-contents orig-outfile) + (buffer-substring-no-properties + (point-min) (point-max)))) + ;; http://pandoc.org/MANUAL.html#citations + ;; Each citation must have a key, composed of `@' + the + ;; citation identifier from the database, and may optionally + ;; have a prefix, a locator, and a suffix. The citation key + ;; must begin with a letter, digit, or _, and may contain + ;; alphanumerics, _, and internal punctuation characters + ;; (:.#$%&-+?<>~/). + ;; A minus sign (-) before the @ will suppress mention of the + ;; author in the citation. + (valid-citation-key-char-regexp "a-zA-Z0-9_:.#$%&+?<>~/-") + (citation-key-regexp (concat "[^" valid-citation-key-char-regexp "]" + "\\(-?@[a-zA-Z0-9_]" + "[" valid-citation-key-char-regexp "]+\\)")) + (has-@ (string-match-p citation-key-regexp orig-outfile-contents))) + ;; Either the nocite front-matter should be there, or the + ;; citation keys should be present in the `orig-outfile'. + (if (or has-nocite has-@) + (progn + (unless (executable-find "pandoc") + (user-error "[ox-hugo] pandoc executable not found in PATH")) + (org-hugo-pandoc-cite--parse-citations info orig-outfile)) + (org-hugo-pandoc-cite--restore-fm-in-orig-outfile + orig-outfile fm orig-outfile-contents)))) + +(defun org-hugo-pandoc-cite--meta-data-generator (data) + "Return YAML front-matter to pass citation meta-data to Pandoc. + +DATA is the alist containing all the post meta-data for +front-matter generation. + +Pandoc accepts `csl', `nocite' and `link-citations' variables via +a YAML front-matter. + +References: +- https://pandoc.org/MANUAL.html#citation-rendering +- https://pandoc.org/MANUAL.html#including-uncited-items-in-the-bibliography +- https://pandoc.org/MANUAL.html#other-relevant-metadata-fields" + (let* ((yaml ()) + (link-citations (cdr (assoc 'link-citations data))) + (link-citations (if (symbolp link-citations) + (symbol-name link-citations) + link-citations)) + (csl (cdr (assoc 'csl data))) + (nocite (cdr (assoc 'nocite data)))) + (push "---" yaml) + (when link-citations + (push (format "link-citations: %s" + (org-hugo--front-matter-value-booleanize link-citations)) + yaml)) + (when csl + (push (format "csl: %S" csl) yaml)) + (when nocite + (push (format "nocite: [%s]" + (string-join + (mapcar (lambda (elem) + (format "%S" (symbol-name elem))) + nocite) + ", ")) + yaml)) + (push "---\n" yaml) + ;; (message "[org-hugo-pandoc-cite--meta-data-generator DBG] yaml: %S" yaml) + (string-join (nreverse yaml) "\n"))) + + +(provide 'ox-hugo-pandoc-cite) + + + +;;; Footnotes + +;;;; Footnote 1 +;; The empty HTML element tags like "
" is a hack to get +;; around a Blackfriday limitation. Details: +;; https://github.com/kaushalmodi/ox-hugo/issues/93. + + +;;; ox-hugo-pandoc-cite.el ends here diff --git a/org/elpa/ox-hugo-20221028.1631/ox-hugo-pkg.el b/org/elpa/ox-hugo-20221028.1631/ox-hugo-pkg.el new file mode 100644 index 0000000..d19f3b4 --- /dev/null +++ b/org/elpa/ox-hugo-20221028.1631/ox-hugo-pkg.el @@ -0,0 +1,14 @@ +(define-package "ox-hugo" "20221028.1631" "Hugo Markdown Back-End for Org Export Engine" + '((emacs "26.3") + (tomelr "0.4.3")) + :commit "a66063a9915c859c57944564f0b8dbc7949d4449" :authors + '(("Kaushal Modi" . "kaushal.modi@gmail.com") + ("Matt Price" . "moptop99@gmail.com")) + :maintainer + '("Kaushal Modi" . "kaushal.modi@gmail.com") + :keywords + '("org" "markdown" "docs") + :url "https://ox-hugo.scripter.co") +;; Local Variables: +;; no-byte-compile: t +;; End: diff --git a/org/elpa/ox-hugo-20221028.1631/ox-hugo.el b/org/elpa/ox-hugo-20221028.1631/ox-hugo.el new file mode 100644 index 0000000..631ef57 --- /dev/null +++ b/org/elpa/ox-hugo-20221028.1631/ox-hugo.el @@ -0,0 +1,5002 @@ +;;; ox-hugo.el --- Hugo Markdown Back-End for Org Export Engine -*- lexical-binding: t -*- + +;; Author: Kaushal Modi +;; Matt Price +;; Version: 0.12.1 +;; Package-Requires: ((emacs "26.3") (tomelr "0.4.3")) +;; Keywords: Org, markdown, docs +;; URL: https://ox-hugo.scripter.co + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; 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 'tomelr) ;For `tomelr-encode' + +(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://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=f636cf91b6cbe322eca56e23283f4614548c9d65 +(require 'org-refile nil :noerror) ;For `org-get-outline-path' + +(require 'org) +(require 'org-id) ;For `org-id-find' + +;; For `org-info-emacs-documents', `org-info-other-documents' +;; org-info.el got renamed to ol-info.el in Org version 9.3. Remove +;; below if condition after the minimum emacs dependency is raised to +;; emacs 27.x. The Org version shipped with Emacs 26.3 is 9.1.9. +(if (version< (org-version) "9.3") + (require 'org-info) + (require 'ol-info)) + +(declare-function org-hugo-pandoc-cite--parse-citations-maybe "ox-hugo-pandoc-cite") +(declare-function org-hugo-pandoc-cite--meta-data-generator "ox-hugo-pandoc-cite") + +(require 'ox-hugo-deprecated) + + +(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 +;; heading tags. +;; https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=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))) + +;; `org-back-to-heading-or-point-min' was introduced in Org 9.5 in +;; https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=1bdff9f73dc1e7ff625a90e3e61350bdea99f29c. +;; If a user is using a slightly older version of Org (like 9.3), +;; define it. +(unless (fboundp #'org-back-to-heading-or-point-min) + (defun org-back-to-heading-or-point-min (&optional invisible-ok) + "Go back to heading or first point in buffer. +If point is before first heading go to first point in buffer +instead of back to heading." + (if (org-before-first-heading-p) + (goto-char (point-min)) + (org-back-to-heading invisible-ok)))) + +(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--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.") + +(defvar org-hugo--trim-pre-marker "" + "Special string to mark where whitespace should be trimmed before an element.") + +(defvar org-hugo--trim-post-marker "" + "Special string to mark where whitespace should be trimmed after an element.") + +(defvar org-hugo--opened-buffers '() + "List of buffers opened during an export, which will be auto-closed at the end. + +An export operation might need to open files for resolving links +pointing to other Org files or temporary buffers for +pre-processing an Org file. Each buffer opened during an Ox-Hugo +export gets added to this list, and they all are auto-closed at +the end of the export in `org-hugo--after-all-exports-function'.") + +(defvar org-hugo--disable-after-all-exports-hook nil + "If set, `org-hugo--after-all-exports-function' function is not called. + +This variable is set internally by `org-hugo-export-wim-to-md' +when its ALL-SUBTREES arg is set to a non-nil value. + +Setting this to non-nil will lead to slow or incorrect +exports. This variable is for internal use only, and must not be +modified.") + +(defvar org-hugo--all-subtrees-export--functions-to-silence + '(org-babel-exp-src-block ;Don't print "org-babel-exp process .." messages + write-region ;Don't print "Wrote .." messages + table-generate-source ;Don't print "Generating source..." messages + ) + "List of functions to silence in Echo and Messages buffers. + +These functions are silenced only when ALL-SUBTREES export is done.") + +(defconst org-hugo--preprocess-buffer t + "Enable pre-processing of the current Org buffer. + +This variable needs to be non-nil for the support of +cross-subtree Org internal links when using the subtree-based +export flow.") + +(defvar org-hugo--preprocessed-buffer nil + "Name of the pre-processed buffer.") + +(defconst org-hugo--preprocessed-buffer-dummy-file-suffix ".pre-processed.org" + "Dummy suffix (including file extension) for pre-processed buffers. + +Dummy Org file paths are created in +`org-hugo--get-pre-processed-buffer' by appending this variable +to the link targets out of the current subtree scope.") + + +;;; Obsoletions + +(define-obsolete-variable-alias 'org-hugo-default-section-directory 'org-hugo-section "Oct 31, 2018") +(define-obsolete-function-alias 'org-hugo-headline 'org-hugo-heading "Jan 3, 2022") + + + +;;; 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") + +(defcustom org-hugo-base-dir nil + "Base directory for Hugo. + +Set either this value, or the HUGO_BASE_DIR global property for +export." + :group 'org-export-hugo + :type 'directory) +;;;###autoload (put 'org-hugo-base-dir 'safe-local-variable 'stringp) + +(defcustom org-hugo-goldmark t + "Enable Goldmark or Commonmark compatible Markdown export. + +When nil, the hacks necessary for Blackfriday Markdown +processing are enabled. + +If using Hugo v0.60.0 (released Nov 2019), keep the default +value. + +https://github.com/kaushalmodi/ox-hugo/discussions/485." + :group 'org-export-hugo + :type 'boolean) +;;;###autoload (put 'org-hugo-goldmark 'safe-local-variable 'booleanp) + +(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 \"
\" 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 text." + :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 headings 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 heading 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 headings. + +When set to `onlytoc', none of the headings 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 +headings whose relative level is higher or equal to N. + +When set to any other non-nil value, numbering will happen for +all the headings. + +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 heading" nil) + (const :tag "Number all headings" 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** italics{{% /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 bold italics{{< /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-container-element "" + "HTML element to use for wrapping top level sections. +Can be set with the in-buffer HTML_CONTAINER property. + +When set to \"\", the top level sections are not wrapped in any +HTML element." + :group 'org-export-hugo + :type 'string) +;;;###autoload (put 'org-hugo-container-element 'safe-local-variable 'stringp) + +(defcustom org-hugo-special-block-type-properties '(("audio" . (:raw t)) + ("katex" . (:raw t)) + ("mark" . (:trim-pre t :trim-post t)) + ("tikzjax" . (:raw t)) + ("video" . (:raw t))) + "Alist for storing default properties for special block types. + +Each element of the alist is of the form (TYPE . PLIST) where +TYPE is a string holding the special block's type and PLIST is a +property list for that TYPE. + +The TYPE string could be any special block type like an HTML +inline or block tag, or name of a Hugo shortcode, or any random +string. + +Properties recognized in the PLIST: + +- :raw :: When set to t, the contents of the special block as + exported raw i.e. as typed in the Org buffer. + +- :trim-pre :: When set to t, the whitespace before the special + block is removed. + +- :trim-pre :: When set to t, the whitespace after the special + block is removed. + +For the special block types not specified in this variable, the +default behavior is same as if (:raw nil :trim-pre nil :trim-post +nil) plist were associated with them." + :group 'org-export-hugo + :type '(alist :key-type string :value-type (plist :key-type symbol :value-type boolean))) + +(defcustom org-hugo-anchor-functions '(org-hugo-get-page-or-bundle-name + org-hugo-get-custom-id + org-hugo-get-heading-slug + org-hugo-get-md5) + "A list of functions for deriving the anchor of current Org heading. + +The functions will be run in the order added to this variable +until the first one returns a non-nil value. So the functions in +this list are order-sensitive. + +For example, if `org-hugo-get-page-or-bundle-name' is the first +element in this list, the heading's `:EXPORT_FILE_NAME' property +will have the highest precedence in determining the heading's +anchor string. + +This variable is used in the `org-hugo--get-anchor' internal +function. + +Functions added to this list should have 2 arguments (which could +even be declared as optional): + +1. ELEMENT : Org element +2. INFO : General plist used as a communication channel + +Some of the inbuilt functions that can be added to this list: +- `org-hugo-get-page-or-bundle-name' +- `org-hugo-get-custom-id' +- `org-hugo-get-heading-slug' +- `org-hugo-get-md5' +- `org-hugo-get-id'" + :group 'org-export-hugo + :type '(repeat function)) + +(defcustom org-hugo-citations-plist '(:bibliography-section-heading "References") + "Property list for storing default properties for citation exports. + +Properties recognized in the PLIST: + +- :bibliography-section-heading :: Heading to insert before the bibliography + section. + +Auto-detection of bibliography section requires installing the +`citations' package from Melpa and adding `#+cite_export: csl' at +the top of the Org file. + +If `:bibliography-section-heading' set to an empty string, +bibliography heading auto-injection is not done." + :group 'org-export-hugo + :type '(plist :key-type symbol :value-type string)) + +(defcustom org-hugo-info-gnu-software '("3dldf" "8sync" + "a2ps" "acct" "acm" "adns" "alive" "anubis" "apl" + "archimedes" "aris" "artanis" "aspell" "auctex" "autoconf" "autoconf-archive" + "autogen" "automake" "avl" + "ballandpaddle" "barcode" "bash" "bayonne" "bazaar" "bc" "behistun" + "bfd" "binutils" "bison" "bool" "bpel2owfn" + "c-graph" "ccaudio" "ccd2cue" "ccide" "ccrtp" "ccscript" "cflow" + "cgicc" "chess" "cim" "classpath" "classpathx" "clisp" "combine" + "commoncpp" "complexity" "config" "consensus" "coreutils" "cpio" "cppi" + "cssc" "cursynth" + "dap" "datamash" "dc" "ddd" "ddrescue" "dejagnu" "denemo" + "dia" "dico" "diction" "diffutils" "direvent" "djgpp" "dominion" + "dr-geo" + "easejs" "ed" "edma" "electric" "emacs" "emacs-muse" "emms" + "enscript" "epsilon" + "fdisk" "ferret" "findutils" "fisicalab" "foliot" "fontopia" "fontutils" + "freedink" "freefont" "freeipmi" "freetalk" "fribidi" + "g-golf" "gama" "garpd" "gawk" "gcal" "gcc" "gcide" + "gcl" "gcompris" "gdb" "gdbm" "gengen" "gengetopt" "gettext" + "gforth" "ggradebook" "ghostscript" "gift" "gimp" "glean" "global" + "glpk" "glue" "gmediaserver" "gmp" "gnash" "gnat" "gnats" + "gnatsweb" "gnowsys" "gnu-c-manual" "gnu-crypto" "gnu-pw-mgr" "gnuae" "gnuastro" + "gnubatch" "gnubg" "gnubiff" "gnubik" "gnucap" "gnucash" "gnucobol" + "gnucomm" "gnudos" "gnufm" "gnugo" "gnuit" "gnujdoc" "gnujump" + "gnukart" "gnulib" "gnumach" "gnumed" "gnumeric" "gnump3d" "gnun" + "gnunet" "gnupg" "gnupod" "gnuprologjava" "gnuradio" "gnurobots" "gnuschool" + "gnushogi" "gnusound" "gnuspeech" "gnuspool" "gnustandards" "gnustep" "gnutls" + "gnutrition" "gnuzilla" "goptical" "gorm" "gpaint" "gperf" "gprolog" + "grabcomics" "greg" "grep" "gretl" "groff" "grub" "gsasl" + "gsegrafix" "gsl" "gslip" "gsrc" "gss" "gtick" "gtypist" + "guile" "guile-cv" "guile-dbi" "guile-gnome" "guile-ncurses" "guile-opengl" + "guile-rpc" "guile-sdl" "guix" "gurgle" "gv" "gvpe" "gwl" "gxmessage" + "gzip" + "halifax" "health" "hello" "help2man" "hp2xx" "html-info" "httptunnel" + "hurd" "hyperbole" + "icecat" "idutils" "ignuit" "indent" "inetutils" "inklingreader" "intlfonts" + "jacal" "jami" "java-getopt" "jel" "jitter" "jtw" "jwhois" + "kawa" "kopi" + "leg" "less" "libc" "libcdio" "libdbh" "liberty-eiffel" "libextractor" + "libffcall" "libgcrypt" "libiconv" "libidn" "libjit" "libmatheval" + "libmicrohttpd" "libredwg" "librejs" "libsigsegv" "libtasn1" "libtool" + "libunistring" "libxmi" "lightning" "lilypond" "lims" "linux-libre" "liquidwar6" + "lispintro" "lrzsz" "lsh" + "m4" "macchanger" "mailman" "mailutils" "make" "marst" "maverik" + "mc" "mcron" "mcsim" "mdk" "mediagoblin" "melting" "mempool" + "mes" "metaexchange" "metahtml" "metalogic-inference" "mifluz" "mig" "miscfiles" + "mit-scheme" "moe" "motti" "mpc" "mpfr" "mpria" "mtools" + "nana" "nano" "nano-archimedes" "ncurses" "nettle" "network" + "ocrad" "octave" "oleo" "oo-browser" "orgadoc" "osip" + "panorama" "parallel" "parted" "pascal" "patch" "paxutils" "pcb" + "pem" "pexec" "pies" "pipo" "plotutils" "poke" "polyxmass" + "powerguru" "proxyknife" "pspp" "psychosynth" "pth" "pythonwebkit" + "qexo" "quickthreads" + "r" "radius" "rcs" "readline" "recutils" "reftex" "remotecontrol" + "rottlog" "rpge" "rush" + "sather" "scm" "screen" "sed" "serveez" "sharutils" "shepherd" + "shishi" "shmm" "shtool" "sipwitch" "slib" "smalltalk" "social" + "solfege" "spacechart" "spell" "sqltutor" "src-highlite" "ssw" "stalkerfs" + "stow" "stump" "superopt" "swbis" "sysutils" + "taler" "talkfilters" "tar" "termcap" "termutils" "teseq" "teximpatient" + "texinfo" "texmacs" "time" "tramp" "trans-coord" "trueprint" + "unifont" "units" "unrtf" "userv" "uucp" + "vc-dwim" "vcdimager" "vera" "vmgen" + "wb" "wdiff" "websocket4j" "webstump" "wget" "which" "womb" + "xaos" "xboard" "xlogmaster" "xmlat" "xnee" "xorriso" + "zile") + "List of GNU software for Info manual links. +The software list is taken from https://www.gnu.org/software/." + :group 'org-export-hugo + :type '(repeat string)) + + + +;;; 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) + (drawer . org-hugo-drawer) + (example-block . org-hugo-example-block) + (export-block . org-hugo-export-block) + (export-snippet . org-hugo-export-snippet) + (headline . org-hugo-heading) + (inner-template . org-hugo-inner-template) + (inline-src-block . org-hugo-inline-src-block) + (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) ;Hugo/Goldmark does more correct conversion to smart quotes, especially for single quotes. + (:with-special-strings nil "-" nil) ;Hugo/Goldmark does the auto-conversion of "--" -> "–", "---" -> "—" and "..." -> "…" + (: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 org-hugo-base-dir) + (:hugo-goldmark "HUGO_GOLDMARK" nil org-hugo-goldmark) + (: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) ;Deprecated. See https://github.com/kaushalmodi/ox-hugo/discussions/485. + (: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 + (:html-container "HTML_CONTAINER" nil org-hugo-container-element) + (:html-container-class "HTML_CONTAINER_CLASS" nil "") + + ;; 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', `linenos' and +`:front_matter_extra' parameters, if added to any source block. +This parameter is used in `org-hugo-src-block'." + (let* ((param-keys-to-be-retained '(:hl_lines :linenos :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://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=6b2a7cb20b357e730de151522fe4204c96615f98." + (and (string-match-p "\\`-?\\([0-9]\\|\\([1-9]\\|[0-9]*\\.\\)[0-9]*\\)\\'" string) + (string-to-number string))) + +(defun org-hugo--org-info-export (path desc format) + "Add support for exporting [[info:..]] links for `hugo' format. + +See `org-link-parameters' for details about PATH, DESC and FORMAT." + (let* ((parts (split-string path "#\\|::")) + (manual (car parts)) + (node (or (nth 1 parts) "Top")) + (title (format "Emacs Lisp: (info \\\"(%s) %s\\\")" manual node)) + (desc (or desc + (if (string= node "Top") + (format "%s Info" (capitalize manual)) + (format "%s Info: %s" (capitalize manual) node)))) + ;; `link' below is mostly derived from the code in + ;; `org-info-map-html-url'. + (link (cond ((member manual org-info-emacs-documents) + (let ((manual-url (if (string= (downcase manual) "org") + "https://orgmode.org/manual" + (format "https://www.gnu.org/software/emacs/manual/html_node/%s" manual))) + (node-url (if (string= node "Top") + "index.html" + (concat (org-info--expand-node-name node) ".html")))) + (format "%s/%s" manual-url node-url))) + ((member manual org-hugo-info-gnu-software) + (let ((manual-url (format "https://www.gnu.org/software/%s/manual/html_node" manual)) + (node-url (if (string= node "Top") + "index.html" + (concat (org-info--expand-node-name node) ".html")))) + (format "%s/%s" manual-url node-url))) + ((cdr (assoc manual org-info-other-documents))) + (t + (concat manual ".html"))))) + (when (member format '(md hugo)) + (format "[%s](%s \"%s\")" desc link title)))) + +(defun org-hugo--org-cite-export-bibliography (orig-fun &rest args) + "Insert a heading before the exported bibliography. + +ORIG-FUN is the original function `org-cite-export-bibliography' +that this function is designed to advice using `:around'. ARGS +are the arguments of the ORIG-FUN." + (let ((bib (apply orig-fun args))) + (when (org-string-nw-p bib) + ;; Auto-inject Bibliography heading. + (let ((info (nth 2 args)) ;(org-cite-export-bibliography KEYWORD _ INFO) + (bib-heading (org-string-nw-p (plist-get org-hugo-citations-plist :bibliography-section-heading)))) + (when bib-heading + (let* ((bib-heading (org-blackfriday--translate nil info bib-heading)) + (loffset (string-to-number + (or (org-entry-get nil "EXPORT_HUGO_LEVEL_OFFSET" :inherit) + (plist-get info :hugo-level-offset)))) + (level-mark (make-string (+ loffset 1) ?#))) + (format "%s %s\n\n%s" level-mark bib-heading bib))))))) + +(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' and `org-hugo-export-as-md'. + +SUBTREEP is non-nil for subtree-based exports. + +This function is used to advise few functions. Those advices are +effective only while an ox-hugo export is in progress because +they get removed later in `org-hugo--after-1-export-function'. + +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) + (advice-add 'org-info-export :override #'org-hugo--org-info-export) + (advice-add 'org-cite-export-bibliography :around #'org-hugo--org-cite-export-bibliography)) + +(defun org-hugo--after-1-export-function (info outfile) + "Function to be run after exporting one post. + +The post could be exported using the subtree-based or file-based +method. + +This function is called in the end of `org-hugo-export-to-md', +and `org-hugo-export-as-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-cite-export-bibliography #'org-hugo--org-cite-export-bibliography) + (advice-remove 'org-info-export #'org-hugo--org-info-export) + (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)) + +(defun org-hugo--cleanup () + "Function to kill Ox-Hugo opened buffers and reset internal variables. + +This is an internal function." + (setq org-hugo--subtree-count 0) ;Reset the subtree count + + ;; Kill all the buffers opened by during an export. + (dolist (buf org-hugo--opened-buffers) + (kill-buffer buf)) + (setq org-hugo--opened-buffers nil) + + (setq org-hugo--preprocessed-buffer nil)) + +(defun org-hugo--after-all-exports-function () + "Function to be run after Ox-Hugo exports all the posts. + +This function is called in the end of +`org-hugo-export-wim-to-md', `org-hugo-export-to-md' and +`org-hugo-export-as-md' (if its ALL-SUBTREES arg is non-nil). + +This is an internal function." + (org-hugo--cleanup) + (dolist (fn org-hugo--all-subtrees-export--functions-to-silence) + (advice-remove fn #'org-hugo--advice-silence-messages))) + +;;;; HTMLized section number for heading +(defun org-hugo--get-heading-number (heading info &optional toc) + "Return htmlized section number for the HEADING. +INFO is a plist used as a communication channel. + +When the \"num\" export option is `onlytoc', heading number is +returned only if the optional argument TOC is non-nil. + +Return nil if there is no heading 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 heading info)) + (let ((number-str (mapconcat + 'number-to-string + (org-export-get-headline-number heading info) "."))) + (format "%s " number-str))))) + +;;;; Build TOC +(defun org-hugo--build-toc (info &optional n scope 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. + +When optional argument SCOPE is non-nil, build a table of +contents according to the specified element. + +When optional argument LOCAL is non-nil, build a table of +contents according to the current heading." + (let* ((toc-heading + (unless local + (format "\n
%s
\n" + (org-html--translate "Table of Contents" info)))) + (current-level nil) + (toc-items + (mapconcat + (lambda (heading) + (let* ((level-raw (org-export-get-relative-level heading info)) + (level (if scope + (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 heading))) + (todo-str (if todo + (concat (org-hugo--todo todo info) " ") + "")) + (heading-num-list (org-export-get-headline-number heading info)) + (number (if heading-num-list + ;; (message "[ox-hugo TOC DBG] heading-num-list: %S" heading-num-list) + (org-hugo--get-heading-number heading info :toc) + "")) + (toc-entry + (format "[%s%s](#%s)" + todo-str + (org-export-data-with-backend + (org-export-get-alt-title heading info) + (org-export-toc-entry-backend 'hugo) + info) + (org-hugo--get-anchor heading info))) + (tags (and (plist-get info :with-tags) + (not (eq 'not-in-toc (plist-get info :with-tags))) + (let ((tags (org-export-get-tags heading 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 scope) + "\n")) ;Newline between TOC items + ;; Remove blank lines from in-between TOC items, which can + ;; get introduced when using the "UNNUMBERED: t" heading + ;; 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 + (let ((toc-classes '("toc" "ox-hugo-toc")) + ;; `has-section-numbers' is non-nil if section numbers are + ;; present for even one heading. + (has-section-numbers (string-match-p "^\\s-*\\-\\s-\n" (string-join (reverse toc-classes) " ")) + (unless (org-hugo--plist-get-true-p info :hugo-goldmark) + "
\n") ;This is a nasty workaround till Hugo/Blackfriday support + toc-heading ;wrapping Markdown in HTML div's. + "\n" + toc-items ;https://github.com/kaushalmodi/ox-hugo/issues/93 + "\n\n" + "
\n" + ;; Special comment that can be use to filter out the TOC + ;; from .Summary in Hugo templates. + ;; + ;; {{ $summary_splits := split .Summary "" }} + ;; {{ if eq (len $summary_splits) 2 }} + ;; + ;; {{ index $summary_splits 1 | safeHTML }} + ;; {{ else }} + ;; + ;; {{ .Summary }} + ;; {{ end }} + "\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\", \"org\", +\"go-html-template\" or \"emacs-lisp\". + + - Shortcode with Markdown : {{% foo %}} -> {{%/* foo */%}} + + - Shortcode without Markdown : {{< foo >}} -> {{}} + +Return the escaped/unescaped string." + (if (member lang '("md" "org" "go-html-template" "emacs-lisp")) + (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))) + +;;;; 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 or the `org-hugo-base-dir' local variable"))) + (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. + +The date is derived with this precedence: + +1. `:logbook-date' property from INFO + +2. `CLOSED' time stamp if the point is in an Org subtree with the + `CLOSED' property set (usually generated automatically when + switching a heading's TODO state to \"DONE\") + +3. `EXPORT_DATE' property in current post subtree + +4. Date if set in the Org file's \"#+date\" keyword. This date is + formatted using the time format string FMT. + +If none of the above apply, return nil. + +INFO is a plist used as a communication channel." + (or + (plist-get info :logbook-date) + (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--org-date-time-to-rfc3339 (date-time info) + "Convert DATE-TIME to RFC 3339 format. + +DATE-TIME can be either Emacs format time list (example: return +value of `current-time'), or an Org date/time string. + +INFO is a plist used as a communication channel." + (let* ((date-time (if (stringp date-time) + (apply #'encode-time (org-parse-time-string date-time)) + date-time)) + (date-nocolon (format-time-string + (plist-get info :hugo-date-format) + date-time))) + ;; 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. + (and (stringp date-nocolon) + (replace-regexp-in-string + "\\([0-9]\\{2\\}\\)\\([0-9]\\{2\\}\\)\\'" "\\1:\\2" + date-nocolon)))) + +(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)) + ((equal date-key :hugo-lastmod) + (or (plist-get info :logbook-lastmod) ;lastmod derived from LOGBOOK gets higher precedence + (org-string-nw-p (plist-get info date-key)))) + ((and (equal date-key :hugo-publishdate) + (org-entry-get (point) "SCHEDULED")) + ;; Get the date from the "SCHEDULED" property. + (org-entry-get (point) "SCHEDULED")) + (t ;:hugo-publishdate, :hugo-expirydate + (org-string-nw-p (plist-get info date-key))))) + (dt-rfc3339 (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 + (org-hugo--org-date-time-to-rfc3339 date-raw info) + (error + ;; Set dt-rfc3339 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. If the lastmod value is + ;; derived from LOGBOOK, disable the + ;; auto-setting of lastmod. + ((and (equal date-key :hugo-lastmod) + (null (plist-get info :logbook-lastmod)) + (org-hugo--plist-get-true-p info :hugo-auto-set-lastmod)) + (let* ((curr-time (org-current-time)) + (lastmod-str (org-hugo--org-date-time-to-rfc3339 curr-time info))) + ;; (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)))) + dt-rfc3339)) + +;;;; 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 "%s" + (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)) "")))) + (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_FRAG keyword is set in the current or a +parent subtree, return the concatenation of the \"HUGO_SECTION\" +and the concatenated \"EXPORT_HUGO_SECTION_FRAG\" 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_FRAG" :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_FRAG" "/")))) + (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.) + +INFO is a plist used as a communication channel." + (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", .. + +;;;; Format tags into HTML +(defun org-hugo--tags (tags info) + "Format TAGS into HTML. +INFO is a plist containing export options. + +This function is almost identical to `org-html--tags' from +`ox-html' except that the tag separator is an empty string." + (when tags + (format "%s" + (mapconcat + (lambda (tag) + (format "%s" + (concat (plist-get info :html-tag-class-prefix) + (org-html-fix-class-name tag)) + tag)) + tags "")))) + +;;;; Check if the buffer has any valid post subtree +(defun org-hugo--buffer-has-valid-post-subtree-p () + "Return non-nil if the current Org buffer has at least one valid post subtree. + +A valid Hugo post subtree has the `:EXPORT_FILE_NAME:' property +set to a non-empty string." + (org-with-wide-buffer + (catch 'found + (org-map-entries + (lambda () (throw 'found t)) ;Return quickly on finding the first match + "EXPORT_FILE_NAME<>\"\"")))) + +;;;; Advice for silencing messages +(defun org-hugo--advice-silence-messages (orig-fun &rest args) + "Advice function that silences all messages in ORIG-FUN. +ARGS are the ORIG-FUN function's arguments." + (let ((inhibit-message t) ;Don't show the messages in Echo area + (message-log-max nil)) ;Don't show the messages in the *Messages* buffer + (apply orig-fun args))) + +;;;; Plainify (mimick the Hugo plainify function) +(defun org-hugo--plainify-string (str info) + "Return STR string without any markup. + +INFO is a plist used as a communication channel. + +If STR is an empty string or nil, return nil. + +This function aims to mimick the Hugo `plainify' function: +https://gohugo.io/functions/plainify/. For example, if STR is +\"string *with* some /markup/\", the returned string is \"string +with some markup\"." + (org-string-nw-p + (replace-regexp-in-string + "]+>" "" + (org-export-data-with-backend str 'html info)))) + + + +;;; Transcode Functions + +;;;; Code ( 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 "%s" (org-html-encode-plain-text + (org-element-property :value verbatim))) + (org-md-verbatim verbatim nil nil))) + +;;;; Drawer +(defun org-hugo--parse-logbook-entry (para parent-heading-title info) + "Parse a LOGBOOK `paragraph' element PARA and save data to INFO. + +If the LOGBOOK drawer is under a sub-heading, +PARENT-HEADING-TITLE will be that heading's \"plainified\" title +string. If LOGBOOK drawer is at the top level, this argument +will be nil. + +INFO is a plist used as a communication channel. + +This function updates these properties in INFO: `:logbook-date', +`:logbook-lastmod', `:logbook'." + ;; (pp para) + (let* ((logbook-entry ()) + (para-raw-str (org-export-data para info)) + ;; Parse the logbook entry's timestamp. + (timestamp + (org-element-map para 'timestamp + (lambda (ts) + ;; (pp ts) + (let* ((ts-raw-str (org-element-property :raw-value ts)) + (ts-str (org-hugo--org-date-time-to-rfc3339 ts-raw-str info))) + ;; (message "[ox-hugo logbook DBG] ts: %s, ts fmtd: %s" + ;; ts-raw-str ts-str) + (push `(timestamp . ,ts-str) logbook-entry) + ts-str)) ;lambda return for (org-element-map para 'timestamp + nil :first-match))) + ;; (message "\n[ox-hugo logbook DBG] paragraph raw str : %s" para-raw-str) + ;; (message "[ox-hugo logbook DBG] timestamp : %s" timestamp) + (unless timestamp + (user-error "No time stamp is recorded in the LOGBOOK drawer entry")) + + (cl-labels ((get-match-string-and-trim-quotes + (num str) + (org-string-nw-p + (replace-regexp-in-string + ;; Handle corner case: If a TODO state has "__" in them, the + ;; underscore will be escaped. Remove that "\". + "\\\\" "" + (save-match-data ;Required because `string-trim' changes match data + (string-trim + (or (match-string-no-properties num str) "") + "\"" "\""))))) + + ;; Parse (assq 'state org-log-note-headings) + (parse-state-change-maybe + () + (let ((state-change-re "^State\\s-+\\(?1:\".+?\"\\)*\\s-+from\\s-+\\(?2:\".+?\"\\)*")) + (when (string-match state-change-re para-raw-str) + (let ((to-state (get-match-string-and-trim-quotes 1 para-raw-str)) + ;; (from-state (get-match-string-and-trim-quotes 2 para-raw-str)) ;For debug + ) + ;; (message "[ox-hugo logbook DBG] state change : from %s to %s @ %s" + ;; from-state to-state timestamp) + (when to-state + (push `(to_state . ,to-state) logbook-entry) + ;; (message "[ox-hugo logbook DBG] org-done-keywords: %S" org-done-keywords) + (when (and (null parent-heading-title) ;Parse dates from only the toplevel LOGBOOK drawer. + (member to-state org-done-keywords)) + ;; The first parsed TODO state change entry will be the + ;; latest one, and `:logbook-date' would already have + ;; been set to that. So if `:logbook-lastmod' is not set, + ;; set that that to the value of `:logbook-date'. + ;; *This always works because the newest state change or note + ;; entry is always put to the top of the LOGBOOK.* + (unless (plist-get info :logbook-lastmod) + (when (plist-get info :logbook-date) + (plist-put info :logbook-lastmod (plist-get info :logbook-date)))) + ;; `:logbook-date' will keep on getting updating until the last + ;; parsed (first entered) "state changed to DONE" entry. + (plist-put info :logbook-date timestamp))) + ;; (when from-state ;For debug + ;; (push `(from_state . ,from-state) logbook-entry)) + ) + t))) + + ;; Parse (assq 'note org-log-note-headings) + (parse-note-maybe + () + (let ((note-re "^Note taken on .*?\n\\(?1:\\(.\\|\n\\)*\\)")) + (when (string-match note-re para-raw-str) + (let ((logbook-notes (plist-get info :logbook)) + (note (string-trim + (match-string-no-properties 1 para-raw-str)))) + ;; (message "[ox-hugo logbook DBG] note : %s @ %s" note timestamp) + (push `(note . ,note) logbook-entry) + ;; Update the `lastmod' field using the + ;; note's timestamp. + ;; *This always works because the newest state change or note + ;; entry is always put to the top of the LOGBOOK.* + (unless parent-heading-title ;Parse dates from only the toplevel LOGBOOK drawer. + (unless (plist-get info :logbook-lastmod) + (plist-put info :logbook-lastmod timestamp))) + + (let ((context-key (or parent-heading-title "_toplevel"))) + (unless (assoc context-key logbook-notes) + (push (cons context-key (list (cons 'notes (list)))) logbook-notes)) + (setcdr (assoc 'notes (assoc context-key logbook-notes)) + (append (cdr (assoc 'notes (assoc context-key logbook-notes))) + (list (nreverse logbook-entry))))) + (plist-put info :logbook logbook-notes)) + t)))) + + (save-match-data + (cond + ((parse-state-change-maybe)) + ((parse-note-maybe)) + (t + (user-error "LOGBOOK drawer entry is neither a state change, nor a note")))) + ;; (message "[org-hugo--parse-logbook-entry DBG] logbook derived `date' : %S" (plist-get info :logbook-date)) + ;; (message "[org-hugo--parse-logbook-entry DBG] logbook derived `lastmod' : %S" (plist-get info :logbook-lastmod)) + ;; (message "[org-hugo--parse-logbook-entry DBG] logbook entry : %S" logbook-entry) + nil))) + +(defun org-hugo-drawer (drawer contents info) + "Transcode a DRAWER element from Org to appropriate Hugo front-matter. +CONTENTS holds the contents of the block. INFO is a plist +holding contextual information." + (let* ((drawer-name (org-element-property :drawer-name drawer)) + (parent-heading (catch 'found + (let ((el drawer)) + (while t + (let ((p-el (org-export-get-parent el))) + (when (or (null p-el) + (equal 'headline (org-element-type p-el))) + ;; Return when there's no parent element + ;; or if the parent element is a `headline'. + (throw 'found p-el)) + (setq el p-el)))))) + (parent-heading-title (org-hugo--plainify-string + (org-element-property :title parent-heading) + info))) + ;; (message "[org-hugo-drawer DBG] parent-heading : %S" parent-heading) + ;; (message "[org-hugo-drawer DBG] parent-heading-title : %S" parent-heading-title)) + (cond + ;; :LOGBOOK: Drawer + ((equal drawer-name (org-log-into-drawer)) + ;; (message "[org-hugo-drawer DBG] elem type: %s" (org-element-type drawer)) + ;; (drawer + ;; .. + ;; (plain-list + ;; (item + ;; (paragraph + ;; + ;; (timestamp ))))) + (org-element-map drawer 'plain-list + (lambda (lst) + (org-element-map lst 'item + (lambda (item) + (org-element-map item 'paragraph + (lambda (para) + (org-hugo--parse-logbook-entry para parent-heading-title info)) + nil :first-match)) ;Each 'item element will have only one 'paragraph element + )) ;But a 'plain-list element can have multiple 'item elements, so loop through all + nil :first-match) ;The 'logbook element will have only one 'plain-list element + ;; Nothing from the LOGBOOK gets exported to the Markdown body + "") + ;; Other Org Drawers + (t + (org-html-drawer drawer contents info))))) + +;;;; Example Block +(defun org-hugo-example-block (example-block _contents info) + "Transcode an EXAMPLE-BLOCK element into Markdown format. + +CONTENTS is nil. INFO is a plist holding contextual +information." + (let* ((switches-str (org-element-property :switches example-block)) + ;; Below is a hack for allowing ":linenos " parameter + ;; in example block header, because the example-block Org + ;; element parses only "-switches", not ":parameters". + (linenos-style (and (org-string-nw-p switches-str) + (string-match ":linenos\\s-+\\([^ ]+\\)\\b" switches-str) + (match-string-no-properties 1 switches-str)))) + (org-element-put-property example-block :language "text") + (org-element-put-property example-block :linenos-style linenos-style) + (org-hugo-src-block example-block nil info))) + +;;;; 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)))) + +;;;; Heading +(defun org-hugo-heading (heading contents info) + "Transcode HEADING element into Markdown format. +CONTENTS is the heading contents. INFO is a plist used as +a communication channel." + (unless (org-element-property :footnote-section-p heading) + (let* ((numbers (org-hugo--get-heading-number heading info nil)) + (loffset (string-to-number (plist-get info :hugo-level-offset))) ;"" -> 0, "0" -> 0, "1" -> 1, .. + (level (org-export-get-relative-level heading info)) + (level-effective (+ loffset level)) + (title (org-export-data (org-element-property :title heading) info)) ;`org-export-data' required + (todo (and (org-hugo--plist-get-true-p info :with-todo-keywords) + (org-element-property :todo-keyword heading))) + (todo-fmtd (when todo + (concat (org-hugo--todo todo info) " "))) + (tags-fmtd (and (org-hugo--plist-get-true-p info :with-tags) + (let* ((tags-list (org-export-get-tags heading info)) + (tags-list (dolist (fn org-hugo-tag-processing-functions tags-list) + (setq tags-list (funcall fn tags-list info)))) + (tags-html (org-hugo--tags tags-list info))) + (when (org-string-nw-p tags-html) + (concat " " tags-html))))) + (priority + (and (org-hugo--plist-get-true-p info :with-priority) + (let ((char (org-element-property :priority heading))) + (and char (format "[#%c] " char))))) + (style (plist-get info :md-headline-style))) + ;; (message "[ox-hugo-heading DBG] num: %s" numbers) + ;; (message "[ox-hugo-heading DBG] with-tags: %S" (org-hugo--plist-get-true-p info :with-tags)) + ;; (message "[ox-hugo-heading DBG] tags: %S" (org-export-get-tags heading info)) + (cond + ;; Cannot create a heading. Fall-back to a list. + ((or (org-export-low-level-p heading 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 heading info)) "-" + (concat (number-to-string + (car (last (org-export-get-headline-number + heading info)))) + "."))) + (heading (concat todo-fmtd " " priority title))) ;Heading text without tags + (concat "\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
    or
      tag + ;; of that preceding list. + bullet " " heading tags-fmtd "\n\n" + (and contents (replace-regexp-in-string "^" " " contents))))) + (t + (let* ((anchor (format "{#%s}" (org-hugo--get-anchor heading info))) ;https://gohugo.io/extras/crossreferences/ + (heading-title (org-hugo--heading-title style level loffset title + todo-fmtd tags-fmtd anchor numbers)) + (wrap-element (org-hugo--container heading info)) + (content-str (or (org-string-nw-p contents) ""))) + (if wrap-element + (let* ((container-class (or (org-element-property :HTML_CONTAINER_CLASS heading) + (org-element-property :EXPORT_HTML_CONTAINER_CLASS heading) + (plist-get info :html-container-class))) + (container-class-str (when (org-string-nw-p container-class) + (concat " " container-class)))) + (format (concat "<%s class=\"outline-%d%s\">\n" + "%s%s\n" + "") + wrap-element level container-class-str + heading-title content-str + wrap-element)) + (format "%s%s" heading-title content-str)))))))) + +;;;;; Heading Helpers +(defun org-hugo--container (heading info) + "Get the HTML container element for HEADING. + +INFO is a plist used as a communication channel. + +If a heading has `:HTML_CONTAINER:' or `:EXPORT_HTML_CONTAINER:' +property, that is used for the container element. + +Else if the `:html-container' property is a non-empty string: + - For the top level headings, wrapping is done using that property. + - For second and lower level headings, wrapping is done using + the HTML
      tags. + +Else, no HTML element is wrapped around the HEADING." + (or (org-element-property :HTML_CONTAINER heading) ;property of the immediate heading + (org-element-property :EXPORT_HTML_CONTAINER heading) ;property of the immediate heading + (and (org-string-nw-p (plist-get info :html-container)) ;inherited :html-container: property if any + (if (= 1 (org-export-get-relative-level heading info)) + (plist-get info :html-container) + "div")))) + +;;;###autoload +(defun org-hugo-slug (str &optional allow-double-hyphens) + "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 because STR is often a post's sub-heading (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 \"..\", + \"..\", 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. +- If ALLOW-DOUBLE-HYPHENS is non-nil, at most two consecutive + hyphens are allowed in the returned string, otherwise consecutive + hyphens are not returned. +- No hyphens allowed at the leading or trailing end of the slug." + (let* (;; All lower-case + (str (downcase str)) + ;; Remove ".." HTML tags if present. + (str (replace-regexp-in-string "<\\(?1:[a-z]+\\)[^>]*>.*" "" 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))) + (unless allow-double-hyphens + (setq str (replace-regexp-in-string "--" "-" str))) + str)) + +(defun org-hugo-get-page-or-bundle-name (element info) + "Return ELEMENT's slug based on `:EXPORT_FILE_NAME' and `:EXPORT_HUGO_BUNDLE'. + +If the \"slug\" of the element is \"section/post\", return +\"post\". + +Return nil if ELEMENT doesn't have the EXPORT_FILE_NAME property +set. + +INFO is a plist used as a communication channel." + (let ((slug (org-hugo--heading-get-slug element info nil))) + (when (org-string-nw-p slug) + (file-name-base slug)))) + +(defun org-hugo-get-custom-id (element &optional _info) + "Return ELEMENT's `:CUSTOM_ID' property. + +Return nil if ELEMENT doesn't have the CUSTOM_ID property set." + (org-string-nw-p (org-element-property :CUSTOM_ID element))) + +(defun org-hugo-get-id (&optional element _info) + "Return the value of `:ID' property for ELEMENT. + +Return nil if id is not found." + (let ((element-begin (org-element-property :begin element))) + (save-excursion + (goto-char element-begin) + (org-id-get)))) + +(defun org-hugo-get-heading-slug (element info) + "Return the slug string derived from an Org heading ELEMENT. + +The slug string is parsed from the ELEMENT's `:title' property. + +INFO is a plist used as a communication channel. + +Return nil if ELEMENT's `:title' property is nil or an empty string." + (let ((title (org-export-data-with-backend + (org-element-property :title element) 'md info))) + (org-string-nw-p (org-hugo-slug title :allow-double-hyphens)))) + +(defun org-hugo-get-md5 (element info) + "Return md5 sum derived string using ELEMENT's title property. + +INFO is a plist used as a communication channel. + +This function will never return nil." + (let ((hash-len 6) + (title (or (org-string-nw-p (org-export-data-with-backend + (org-element-property :title element) 'md info)) + ""))) + (substring (md5 title) 0 hash-len))) + +(defun org-hugo--get-elem-with-prop (prop &optional pom _info) + "Find the first element with PROP property in the current tree. + +PROP is a property symbol with a : prefix, example: +`:EXPORT_FILE_NAME'. + +Optional argument POM is the position or marker from which the +upward search for PROP should begin. + +Return a cons of type (ELEM . PVAL) where ELEM is the element +containing the property PROP and PVAL is the property's value. + +Return nil if the PROP is not found or if the PVAL is nil. + +This function is created as a workaround for Org 9.5 and older +versions for the issue that `org-element-at-point' does not +return an element with all the inherited properties. That issue +is fixed in Org main branch at least as of 2022-03-17." + (org-with-wide-buffer + ;; (message (format "[search prop DBG] point 1 : %S" (point))) + (when pom + (goto-char pom)) + ;; (message (format "[search prop DBG] point 2 : %S" (point))) + (org-back-to-heading-or-point-min :invisible-ok) + (let ((elem (org-element-at-point)) + (level t) + pval) + (catch :found + (while elem + ;; (message (format "[search prop DBG] prop %S, elem : %S" prop elem)) + (setq pval (org-element-property prop elem)) + ;; (message "[search prop DBG] level %S, pval %S" level pval) + (when (or pval (null level)) + (if (null pval) + ;; There's probably no value to distinguish + ;; between the case where a property is not + ;; found, or the case where the property + ;; value is nil. Revisit this if that + ;; changes. + (throw :found nil) + (throw :found (cons elem pval)))) + (setq level (org-up-heading-safe)) + (setq elem (org-element-at-point))))))) + +(defun org-hugo--heading-get-slug (heading info &optional inherit-export-file-name) + "Return the slug string derived from an Org HEADING element. + +1. If HEADING has only `:EXPORT_FILE_NAME' and it's not a Hugo + page bundle, use that property as slug. + +2. If HEADING has a `:EXPORT_FILE_NAME' property, and its value + is either \"index\" or \"_index\", use `:EXPORT_HUGO_BUNDLE' + to derive the slug. \"index\" subtree is a Leaf Bundle, and + \"_index\" subtree is a Branch Bundle. + +3. If HEADING has a `:EXPORT_FILE_NAME' property, and its value + is neither \"index\" nor \"_index\", use that to derive the + slug. + +If INHERIT-EXPORT-FILE-NAME is non-nil, allow inheriting the +`:EXPORT_FILE_NAME' property from a parent subtree. + +The `:EXPORT_HUGO_SECTION' property or `#+hugo_section' keyword +value is prepended to all of the above options. + +INFO is a plist used as a communication channel. + +Return nil if none of the above are true." + (org-with-wide-buffer + (let ((heading-begin (org-element-property :begin heading))) + (when heading-begin + (goto-char heading-begin))) + (let ((file (org-string-nw-p (org-export-get-node-property :EXPORT_FILE_NAME heading inherit-export-file-name))) + bundle slug) + ;; (message "[org-hugo--heading-get-slug DBG] EXPORT_FILE_NAME: %S" file) + (when file + (setq bundle (let* ((elem-pval (org-hugo--get-elem-with-prop :EXPORT_HUGO_BUNDLE)) + (pval (when elem-pval + (cdr elem-pval)))) + pval)) + + (cond + ;; Leaf or branch bundle landing page. + ((and bundle file (member file '("index" ;Leaf bundle + "_index" ;Branch bundle + ))) + (setq slug bundle) + ;; (message "[org-hugo--heading-get-slug DBG] bundle slug: %S" slug) + ) + ;; It's a Hugo page bundle, but the file is neither index nor + ;; _index. So likely a page in a branch bundle. + ((and bundle file) + (setq slug (concat (file-name-as-directory bundle) file)) + ;; (message "[org-hugo--heading-get-slug DBG] branch bundle file slug: %S" slug) + ) + ;; Not a Hugo page bundle. + (t + (setq slug file))) + + ;; Prefix with section and fragmented sections if any. + (let ((pheading heading) + section fragment fragments) + (setq section (org-string-nw-p + (or (org-export-get-node-property :EXPORT_HUGO_SECTION heading :inherited) + (plist-get info :hugo-section)))) + + ;; Iterate over all parents of heading, and collect section + ;; path fragments. + (while (and pheading + (not (org-export-get-node-property :EXPORT_HUGO_SECTION pheading nil))) + ;; Add the :EXPORT_HUGO_SECTION_FRAG value to the fragment list. + (when (setq fragment (org-export-get-node-property :EXPORT_HUGO_SECTION_FRAG pheading nil)) + (push fragment fragments)) + (setq pheading (org-element-property :parent pheading))) + + (when section + (setq slug (concat (file-name-as-directory section) + (mapconcat #'file-name-as-directory fragments "") + slug))) + ;; (message "[org-hugo--heading-get-slug DBG] section: %S" section) + ;; (message "[org-hugo--heading-get-slug DBG] section + slug: %S" slug) + )) + ;; (message "[org-hugo--heading-get-slug DBG] FINAL slug: %S" slug) + slug))) + +(defun org-hugo--get-anchor(element info) + "Return anchor string for Org heading ELEMENT. + +The anchor is derived using the first function that returns a +non-nil value (a string) from the list +`org-hugo-anchor-functions'. + +INFO is a plist used as a communication channel. + +Return an empty string if all functions in +`org-hugo-anchor-functions' return nil." + (or (seq-some + (lambda (fn) (funcall fn element info)) + org-hugo-anchor-functions) + "")) + +(defun org-hugo--heading-title (style level loffset title &optional todo tags anchor numbers) + "Generate a heading title in the preferred Markdown heading style. + +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 heading title. + +Optional argument TODO is the Org TODO string. + +Optional argument TAGS is a string containing the current +heading's tags. + +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 ((heading (concat todo numbers title tags " " anchor "\n"))) + ;; Use "Setext" style + (if (and (eq style 'setext) (< level 3)) + (let* ((underline-char (if (= level 1) ?= ?-)) + (underline (concat (make-string (length heading) underline-char) + "\n"))) + (concat "\n" heading underline "\n")) + ;; Use "Atx" style + ;; Always translate level N Org heading to level N+1 Markdown + ;; heading because Markdown level 1 heading and HTML title both + ;; get the HTML

      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 " " heading "\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") + "")) + ;; Handling the case of special blocks inside markdown quote + ;; blocks. + (contents (replace-regexp-in-string + (concat "\\(\n\\s-*> \\)*" (regexp-quote org-hugo--trim-pre-marker)) + ;; ^^^^^^^^ Markdown quote blocks have lines beginning with "> ". + org-hugo--trim-pre-marker ;Keep the trim marker; it will be removed next. + contents)) + (contents (replace-regexp-in-string + (concat "\\([[:space:]]\\|\n\\)*" (regexp-quote org-hugo--trim-pre-marker)) + "\n" + contents)) + (contents (replace-regexp-in-string ;Trim stuff after selected exported elements + (concat (regexp-quote org-hugo--trim-post-marker) + ;; Pull up the contents from the next + ;; line, unless the next line is a list + ;; item (-), a heading (#) or a code block + ;; (`). + "\\([[:space:]>]\\|\n\\)+\\([^-#`]\\)") + " \\2" contents))) + + ;; (message "[org-hugo-inner-template DBG] toc-level: %s" toc-level) + (string-trim-left ;Remove any extra blank lines between front-matter and the content #consistency + (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)))))) + +;;;; Inline Src Block +(defun org-hugo-inline-src-block (inline-src-block _contents _info) + "Transcode INLINE-SRC-BLOCK object into HTML. + +Escape Hugo shortcodes if present in this element's value." + (let* ((lang (org-element-property :language inline-src-block)) + (code (org-hugo--escape-hugo-shortcode + (org-element-property :value inline-src-block) + lang))) + (org-element-put-property inline-src-block :value code) + (format "%s" + lang lang + (org-md-verbatim inline-src-block nil nil)))) + +;;;; 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)) + (if (and (stringp value) ;Hugo summary splitting + (string-match-p "\\`\\s-*more\\s-*\\'" value)) + (progn + ;; https://gohugo.io/content-management/summaries#user-defined-manual-summary-splitting + "") + (progn + value))) + ((and (equal "TOC" kwd) + (string-match-p "\\" value)) + (let* ((depth (and (string-match "\\<[0-9]+\\>" value) + (string-to-number (match-string 0 value)))) + (local? (string-match-p "\\" value)) + (scope ;From `org-md-keyword' + (cond + ((string-match ":target +\\(\".+?\"\\|\\S-+\\)" value) ;link + (org-export-resolve-link + (org-strip-quotes (match-string 1 value)) info)) + (local? keyword)))) + (when (and depth + (> depth 0)) + (let ((toc-str (org-hugo--build-toc info depth scope local?))) + (when toc-str + (org-remove-indentation toc-str)))))) + (t + (org-md-keyword keyword contents info))))) + +;;;; Links +(defun org-hugo--get-coderef-anchor-prefix (el) + "Get anchor prefix string for code refs in element EL. + +Return a cons (CODE-REFS . ANCHOR-PREFIX) where + +- CODE-REFS is an alist of the type (LINENUM . LABEL) where + + LINENUM is the line number where the code referenced labeled + LABEL was found. LABEL is a string. + +- ANCHOR-PREFIX is a string. + +Return nil if EL has no code references." + (let ((prefix "org-coderef") + (hash-len 6) + (code-refs (cdr (org-export-unravel-code el)))) + (when code-refs + (let* ((unique-id (substring + (md5 (format "%s" code-refs)) 0 hash-len)) + (anchor-prefix (format "%s--%s" prefix unique-id))) + (cons code-refs anchor-prefix))))) + +(defun org-hugo-link--resolve-coderef (ref info) + "Resolve a code reference REF. + +This function is heavily derived from +`org-export-resolve-coderef'. + +INFO is a plist used as a communication channel. + +Return a plist with these elements: + +- `:line-num' :: REF associated line number + +- `:ref' :: REF associated line number in source code (if the Org + element's `:use-labels' property is unset. This happens when + the `-r' switch is used) , or REF itself. + +- `:anchor-prefix' :: String prefix for REF's anchor. + +Throw an error if no block contains REF." + (or (org-element-map (plist-get info :parse-tree) '(example-block src-block) + (lambda (el) + (with-temp-buffer + (insert (org-trim (org-element-property :value el))) + (let* ((ref-info ()) + (label-fmt (or (org-element-property :label-fmt el) + org-coderef-label-format)) + (ref-re (org-src-coderef-regexp label-fmt ref))) + ;; Element containing REF is found. Resolve it to + ;; either a label or a line number, as needed. + (when (re-search-backward ref-re nil :noerror) + (let* ((line-num (+ (or (org-export-get-loc el info) 0) + (line-number-at-pos))) + (ref-str (format "%s" (if (org-element-property :use-labels el) + ref + line-num)))) + (setq ref-info (plist-put ref-info :line-num line-num)) + (setq ref-info (plist-put ref-info :ref ref-str)) + (let ((anchor-prefix (or (org-element-property :anchor-prefix el) ;set in `org-hugo-src-block' + (cdr (org-hugo--get-coderef-anchor-prefix el))))) + (setq ref-info (plist-put ref-info :anchor-prefix anchor-prefix)))) + ref-info)))) + info 'first-match) + (signal 'org-link-broken (list ref)))) + +(defun org-hugo--org-mode-light () + "Enable set current buffer's `major-mode' to `org-mode' quickly. + +It is necessary for the `major-mode' to be `org-mode' for many +functions like `org-link-search'." + (unless (derived-mode-p 'org-mode) + (let ((inhibit-modification-hooks t) + (org-mode-hook nil) ;Don't run any Org mode hook functions + (org-inhibit-startup t)) ;Don't run any Org buffer startup functions + (org-mode)))) + +(defun org-hugo--get-anchor-at-point (info) + "Return anchor string based on the current point. + +If point is in a `headline' element, derive the anchor using +`org-hugo--get-anchor'. + +Otherwise, if the current point has an Org target, get the target +anchor. + +If current element has `:EXPORT_FILE_NAME' property, return the +anchor as-is, otherwise prefix the anchor string with \"#\". + +Return an empty string if an anchor cannot be derived. + +INFO is a plist used as a communication channel." + (let ((elem (org-element-at-point)) + (anchor "")) + (cond + ((equal (org-element-type elem) 'headline) + (setq anchor (org-hugo--get-anchor elem info))) + (t + ;; If current point has an Org Target, get the target anchor. + (let ((target-elem (org-element-target-parser))) + (when (equal (org-element-type target-elem) 'target) + (setq anchor (org-blackfriday--get-target-anchor target-elem)))))) + (when (org-string-nw-p anchor) + ;; If the element has the `:EXPORT_FILE_NAME' it's not a + ;; sub-heading, but the subtree's main heading. Don't prefix + ;; the "#" in that case. + (unless (org-export-get-node-property :EXPORT_FILE_NAME elem nil) + (setq anchor (format "#%s" anchor)))) + ;; (message "[search and get anchor DBG] anchor: %S" anchor) + anchor)) + +(defun org-hugo--search-and-get-anchor (org-file search-str info) + "Return HTML anchor for the point where SEARCH-STR is found in ORG-FILE. + +ORG-FILE is the file path in which the SEARCH-STR is to be searched. + +SEARCH-STR needs to be a non-empty string. Example values: \"* +Some heading\", \"#some_custom_id\". + +If the search fails, return \"\". + +INFO is a plist used as a communication channel." + ;; (message "[search and get anchor DBG] org-file: %S" org-file) + ;; (message "[search and get anchor DBG] search-str: %S" search-str) + (let ((buffer (get-file-buffer org-file))) ;nil if `org-file' buffer is not already open + (unless (file-exists-p org-file) + (error "[org-hugo--search-and-get-anchor] Unable to open Org file `%s'" org-file)) + (with-current-buffer (find-file-noselect org-file) + (unless buffer + (add-to-list 'org-hugo--opened-buffers (current-buffer))) + ;; `org-mode' needs to be loaded for `org-link-search' to work + ;; correctly. Otherwise `org-link-search' returns starting + ;; points for incorrect subtrees. + (org-hugo--org-mode-light) + (org-export-get-environment) ;Eval #+bind keywords, etc. + (org-link-search search-str) ;This is extracted from the `org-open-file' function. + (org-hugo--get-anchor-at-point info)))) + +(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")))) + ;; (message "[org-hugo-link DBG] raw-path 1: %s" raw-path) + + (when (and (stringp raw-path) + link-is-url) + (setq raw-path (org-blackfriday--url-sanitize-maybe + info (url-encode-url raw-path)))) + ;; (message "[org-hugo-link DBG] raw-link: %s" raw-link) + ;; (message "[org-hugo-link DBG] raw-path 2: %s" raw-path) + ;; (message "[org-hugo-link DBG] link: %S" link) + ;; (message "[org-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")) ;<>, #+name, heading links + (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 type: %s" type) + ;; (message "[org-hugo-link DBG] destination: %s" destination) + ;; (message "[org-hugo-link DBG] link: %S" link) + ;; (message "[org-hugo-link DBG] link destination elem type: %S" (org-element-type destination)) + (pcase (org-element-type destination) + ;; External file. + (`plain-text + (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)))) + ;; (message "[org-hugo-link DBG] plain-text path: %s" path) + (if (org-id-find-id-file raw-path) + (let* ((anchor (org-hugo-link--heading-anchor-maybe link info)) + (ref (if (and (org-string-nw-p anchor) + (not (string-prefix-p "#" anchor))) + ;; If the "anchor" doesn't begin with + ;; "#", it's a direct reference to a + ;; post subtree. + anchor + (concat path anchor)))) + ;; (message "[org-hugo-link DBG] plain-text org-id anchor: %S" anchor) + (format "[%s]({{< relref \"%s\" >}})" (or desc path) ref)) + (if desc + (format "[%s](%s)" desc path) + (format "<%s>" path))))) + ;; Links of type [[* Some heading]]. + (`headline + (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)))) + ;; Links to other Org elements like source blocks, tables, + ;; paragraphs, standalone figures, <> links, etc. + (_ + (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 "[org-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 "[org-hugo-link DBG] link description: %s" description) + (when description + (let ((dest-link (cond + ;; Ref to a source block or table. + ((memq (org-element-type destination) '(src-block table)) + (org-blackfriday--get-reference destination)) + ;; Ref to a standalone figure. + ((and (org-html-standalone-image-p destination info) + (eq (org-element-type destination) 'paragraph)) + (let ((figure-ref (org-blackfriday--get-reference destination))) + (if (org-string-nw-p figure-ref) + (replace-regexp-in-string + "\\`org-paragraph--" + (org-blackfriday--get-ref-prefix 'figure) + figure-ref) + (org-export-get-reference destination info)))) + ;; Ref to a <>. + ((eq (org-element-type destination) 'target) + (org-blackfriday--get-target-anchor destination)) + ;; Ref to all other link destinations. + (t + (org-export-get-reference destination info))))) + (format "[%s](#%s)" description dest-link)))))))) + ((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" + "" + (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)) + " " + caption))) + (extension (file-name-extension raw-path)) + (inlined-svg (and (stringp extension) + (string= "svg" (downcase extension)) + (plist-get attr :inlined)))) + ;; (message "[org-hugo-link DBG] Inline image: %s, extension: %s" raw-path extension) + ;; (message "[org-hugo-link DBG] inlined svg? %S" inlined-svg) + ;; (message "[org-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. + "" "" + (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\"\\?>" "" + ;; Remove !DOCTYPE tag from the inlined SVG. + (replace-regexp-in-string + "]+>" "" + svg-contents)))) + (svg-html (if caption + (format "
      \n%s\n
      \n\n %s\n
      \n
      " + svg-contents-sanitized caption) + svg-contents-sanitized))) + ;; (message "[org-hugo-link DBG] svg contents: %s" svg-contents) + ;; (message "[org-hugo-link DBG] svg contents sanitized: %s" svg-contents-sanitized) + svg-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 "[org-hugo-link DBG] path: %s" path) + ;; (message "[org-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 "![%s](%s)" alt-text source))) + (;; Else if the image is inline (with non-alt-text + ;; attributes), use HTML tag syntax. + inline-image + ;; The "target" and "rel" attributes would be meant for + ;; tags. So do not pass them to the 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 . ,(when (org-string-nw-p caption) + (replace-regexp-in-string "\"" "\\\\\\&" caption))) ;Escape the double-quotes, if any. + (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-label (org-element-property :path link)) + (ref-info (org-hugo-link--resolve-coderef ref-label info)) + (desc (format (org-export-get-coderef-format ref-label desc) + (plist-get ref-info :ref)))) + ;; (message "[org-hugo-link DBG] coderef ref label: %s" ref-label) + ;; (message "[org-hugo-link DBG] coderef ref str: %s" (plist-get ref-info :ref)) + ;; (message "[org-hugo-link DBG] coderef anchor prefix: %s" (plist-get ref-info :anchor-prefix)) + ;; (message "[org-hugo-link DBG] coderef line num: %s" (plist-get ref-info :line-num)) + ;; (message "[org-hugo-link DBG] coderef desc: %s" desc) + (format "[%s](#%s-%s)" + desc + (plist-get ref-info :anchor-prefix) + (plist-get ref-info :line-num)))) + ((string= type "radio") + (let ((destination (org-export-resolve-radio-link link info))) + (format "[%s](#%s%s)" + desc + (org-blackfriday--get-ref-prefix 'radio) + (org-blackfriday--valid-html-anchor-name + (org-element-property :value destination))))) + (t ;[[file:foo.png]], [[file:foo.org::* Heading]], [[file:foo.org::#custom-id]], link type: file + (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 `((title . ,(plist-get attr :title)) + (style . ,(plist-get attr :style)) + (referrerpolicy . ,(plist-get attr :referrerpolicy)) + (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 "[org-hugo-link DBG] link params: %s" link-param-str) + ) + (concat type ":" raw-path)) + (;; Remove the "file://" prefix. + (string= type "file") + ;; (message "[org-hugo-link DBG] raw-path: %s" raw-path) + (let* ((path1 (replace-regexp-in-string "\\`file://" "" raw-path)) + (path-lc (downcase path1))) + (cond + (;; foo.org, foo.org::* Heading, foo.org::#custom_id + (string= ".org" (file-name-extension path-lc ".")) + (let ((ref "") + (anchor "")) + (if (string-suffix-p org-hugo--preprocessed-buffer-dummy-file-suffix path-lc) + (progn + (setq ref (string-remove-suffix + org-hugo--preprocessed-buffer-dummy-file-suffix + (file-name-nondirectory path1))) + ;; Dummy Org file paths created in + ;; `org-hugo--get-pre-processed-buffer' + ;; For dummy Org file paths, we are + ;; limiting to only "#" style search + ;; strings. + (when (string-match ".*\\.org::\\(#.*\\)" raw-link) + (setq anchor (match-string-no-properties 1 raw-link)))) + ;; Regular Org file paths. + (setq ref (file-name-sans-extension (file-name-nondirectory path1))) + (let ((link-search-str + ;; If raw-link is "./foo.org::#bar", + ;; set `link-search-str' to + ;; "#bar". + (when (string-match ".*\\.org::\\(.*\\)" raw-link) + (match-string-no-properties 1 raw-link)))) + ;; (message "[org-hugo-link DBG] link-search-str: %s" link-search-str) + (when link-search-str + (setq anchor (org-hugo--search-and-get-anchor raw-path link-search-str info))))) + ;; (message "[org-hugo-link file.org::*Heading DBG] ref = %s" ref) + ;; (message "[org-hugo-link file.org::*Heading DBG] anchor = %s" anchor) + (cond + ;; Link to a post subtree. In this case, + ;; the "anchor" is actually the post's + ;; slug. + ((and (org-string-nw-p anchor) (not (string-prefix-p "#" anchor))) + (format "{{< relref \"%s\" >}}" anchor)) + ;; Link to a non-post subtree, like a subheading in a post. + ((or (org-string-nw-p ref) (org-string-nw-p anchor)) + (format "{{< relref \"%s%s\" >}}" ref anchor)) + (t + "")))) + (t ;; attachments like foo.png + (org-hugo--attachment-rewrite-maybe path1 info))))) + (t + raw-path))) + (link-param-str (org-string-nw-p (org-trim link-param-str)))) + ;; (message "[org-hugo-link DBG] desc=%s path=%s" desc path) + ;; (message "[org-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 "%s" + (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 "%s" + path + link-param-str + ;; Below trick is to prevent Hugo from + ;; auto-hyperlinking the link in the + ;; description. Idea from + ;; https://stackoverflow.com/q/25706012/1219634. + (replace-regexp-in-string ":" ":" (org-link-unescape path))))) + ;; Neither link description, nor link attributes. + ((string-prefix-p "{{< relref " path) + (format "[%s](%s)" path path)) + ((org-string-nw-p path) + (format "<%s>" path)) + (t + ""))))))) + +(defun org-hugo-link--heading-anchor-maybe (link info) + "Return anchor of the heading pointed to by LINK. + +INFO is a plist used as a communication channel." + ;; (message "dbg link id: %S" (org-element-property :path link)) + (let* ((id-loc (org-id-find (org-element-property :path link))) + (id-file (car id-loc)) + (id-pos (cdr id-loc)) + (id-buffer (get-file-buffer id-file))) ;nil if `id-file' buffer is not already open + ;; (message "[org-hugo-link--heading-anchor-maybe DBG] id-loc: %S" id-loc) + (with-current-buffer (or id-buffer (find-file-noselect id-file :nowarn)) + (unless id-buffer + (add-to-list 'org-hugo--opened-buffers (current-buffer))) + (org-export-get-environment) ;Eval #+bind keywords, etc. + (goto-char id-pos) + (org-hugo--get-anchor-at-point info)))) + +;;;;; Helpers +(defun org-hugo--copy-resources-maybe (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--copy-ltximg-maybe (info) + "Copy `org-preview-latex-image-directory' contents into site's ltximg directory. + +INFO is a plist used as a communication channel." + (when (file-exists-p org-preview-latex-image-directory) + (let* ((hugo-base-dir (file-name-as-directory (plist-get info :hugo-base-dir))) + (static-ltximg-dir (file-truename + (file-name-as-directory + (expand-file-name + org-blackfriday--ltximg-directory + (expand-file-name "static" hugo-base-dir)))))) + (when (file-newer-than-file-p + org-preview-latex-image-directory static-ltximg-dir) + (copy-directory org-preview-latex-image-directory static-ltximg-dir + nil :parents :copy-contents) + (message "[ox-hugo] Copied contents of %S into %S" + org-preview-latex-image-directory static-ltximg-dir))))) + +(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 + ;; "", `path-true' is + ;; "/bar//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 + ;; "/", `path-true' is + ;; "/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 + ;; "/", `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--process-content (paragraph contents info) + "Process the content of paragraphs. + +- Prevent unwanted spaces when joining Chinese/Japanese lines. +- Join all lines in a paragraph into a single line if + `:hugo-preserve-filling' plist property is nil. +- Add \" \" HTML entity before footnote anchors so that the + anchors won't be on a separate line by themselves. + +Return the processed CONTENTS string from the PARAGRAPH element. +INFO is a plist used as a communication channel." + (let ((ret contents)) + ;; Join consecutive Chinese, Japanese lines into a single long + ;; 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 ret (replace-regexp-in-string + "\\([[:multibyte:]]\\)[[:blank:]]*\n[[:blank:]]*\\([[:multibyte:]]\\)" "\\1\\2" + ret)) + ;; (message "[org-hugo-paragraph--process-content DBG] contents 1: %s" contents) + ) + + ;; Join all content into a single line (followed by a newline) + ;; if :hugo-preserve-filling is nil. + (unless (org-hugo--plist-get-true-p info :hugo-preserve-filling) + (setq ret (concat (mapconcat 'identity (split-string ret) " ") "\n"))) + + ;; Special processing for footnotes. + (setq ret (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" + ret))) + + ;; Escape any lines starting with `#' which is the markup for + ;; headings in Markdown. + (setq ret (org-md-paragraph paragraph ret info)) + + ;; (message "[org-hugo-paragraph--process-content DBG] contents 2: %s" contents) + ret)) + +(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* ((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) + + (cond + ;; First paragraph in an item has no tag if it is alone or + ;; followed, at most, by a sub-list. (Below condition is taken + ;; as-is from `org-html-paragraph'). + ((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))))) + (org-hugo-paragraph--process-content paragraph contents info)) + + ;; Standalone image. + ((org-html-standalone-image-p paragraph info) + (let ((figure-ref (org-blackfriday--get-reference paragraph)) + label) + (when (org-string-nw-p figure-ref) + (setq figure-ref (replace-regexp-in-string + "\\`org-paragraph--" + (org-blackfriday--get-ref-prefix 'figure) + figure-ref))) + (setq label (if figure-ref + (format "\n\n" figure-ref) + "")) + (concat label contents))) + + ;; Normal paragraph. + (t + (let ((label (let ((paragraph-ref (and (org-element-property :name paragraph) + (org-export-get-reference paragraph info)))) + (if paragraph-ref + (format "\n\n" paragraph-ref) + "")))) + + ;; Wrap the paragraph with HTML div tag with user-specified + ;; attributes. + (org-blackfriday--div-wrap-maybe + paragraph + (concat label + (org-hugo-paragraph--process-content paragraph contents info)) + info)))))) + +;;;; 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: + - The HUGO_CODE_FENCE property is set to a non-nil value + (default), + - *AND* the Hugo \"highlight\" shortcode is not needed (see + below). + +Hugo v0.60.0 onwards, the `markup.highlight.codeFences' (new name +for the old `pygmentsCodeFences') config variable defaults to +true. See the \"Highlighting in Code Fences\" section on +https://gohugo.io/content-management/syntax-highlighting. +Attributes like highlighting code, \"linenos\", etc. are now +supported with code fences too. + +CONTENTS is nil. INFO is a plist used as a communication +channel. + +--- When is the \"highlight\" shortcode needed? --- + +It's needed only in Blackfriday mode (`org-hugo-goldmark' is +nil), and if any of these is true: + - 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). + - Set the `linenos' argument to the value passed by :linenos + (defaults to `true'). + - Coderefs are used. + +Note: If using a Hugo version older than v0.60.0, the user +*needs* to set the `pygmentsCodeFences' variable to `true' in +their Hugo site's config." + (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)))) + ;; (message "ox-hugo src [dbg] lang: %S" lang) + ;; (message "ox-hugo src [dbg] parameters: %S" parameters) + ;; (message "ox-hugo src [dbg] is-fm-extra: %S" is-fm-extra) + + ;; Extra front matter. + (cond + ((and is-fm-extra + (member lang '("toml" "yaml"))) + (let ((fm-format (plist-get info :hugo-front-matter-format))) + ;; 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)) + + ;; Regular src block. + (t + (let* (;; See `org-element-src-block-parser' for all SRC-BLOCK properties. + (line-num-p (org-element-property :number-lines src-block)) ;Non-nil if -n or +n switch is used + (linenos-style (or (cdr (assoc :linenos parameters)) + ;; If `org-hugo-src-block' is called from + ;; `org-hugo-example-block'. + (org-element-property :linenos-style src-block))) + ;; Convert `hl-lines' to string. If it's not a number, + ;; it's already a string, or nil. + (hl-lines (let* ((hl-lines-param (cdr (assoc :hl_lines parameters)))) + ;; (message "ox-hugo src [dbg] hl-lines-param: %S" hl-lines-param) + (if (numberp hl-lines-param) + (number-to-string hl-lines-param) + hl-lines-param))) + (code-refs-and-anchor (org-hugo--get-coderef-anchor-prefix src-block)) + (code-refs (let ((code-refs1 (car code-refs-and-anchor))) + (when code-refs1 + (setq line-num-p t)) + code-refs1)) + (goldmarkp (org-hugo--plist-get-true-p info :hugo-goldmark)) + ;; Use the `highlight' shortcode only if .. + (use-highlight-sc (or ;; HUGO_CODE_FENCE is nil, or .. + (null (org-hugo--plist-get-true-p info :hugo-code-fence)) + ;; "Blackfriday mode" is enabled and line numbering + ;; , highlighting or code refs are needed. + (and (or line-num-p hl-lines linenos-style code-refs) + (not goldmarkp)))) + (hl-lines (when (stringp hl-lines) + (if use-highlight-sc + (progn + ;; Syntax of hl_lines in `highlight' shortcode: + ;; {{< highlight emacs-lisp "hl_lines=1 3-5" >}} .. + (replace-regexp-in-string "," " " hl-lines)) ;"1,3-5" -> "1 3-5" + ;; Fenced code blocks + ;; Syntax of hl_lines in fenced code attributes: + ;; ```emacs-lisp { hl_lines=["1","3-5"] } .. + (format "[%s]" + (mapconcat + (lambda(el) (format "%S" el)) + (split-string hl-lines ",") ","))))) ;"1,3-5" -> "[\"1\",\"3-5\"]" + (src-ref (org-blackfriday--get-reference src-block)) + (src-anchor (if src-ref + (format "\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" + " %s:\n" + " %s\n" + "
      ") + (if src-ref ;Hyperlink the code snippet prefix + number + (format "%s %s" + src-ref caption-prefix src-block-num) + (format "%s %s" + caption-prefix src-block-num)) + caption-str)))) + (src-code (org-hugo--escape-hugo-shortcode + (org-export-format-code-default src-block info) + lang)) + (code-attr-str "") + src-code-wrap + ret) + ;; (message "ox-hugo src [dbg] line-num-p: %S" line-num-p) + ;; (message "ox-hugo src [dbg] parameters: %S" parameters) + ;; (message "ox-hugo src [dbg] code refs: %S" code-refs) + ;; (message "ox-hugo src [dbg] code-attr-str: %S" code-attr-str) + + (when (and goldmarkp (not use-highlight-sc)) + (let ((html-attr (org-export-read-attribute :attr_html src-block))) + (setq code-attr-str (org-html--make-attribute-string html-attr)))) + + (when (or linenos-style line-num-p) + ;; Set "linenos" to "true" if linenos-style is nil. + (setq linenos-style (or linenos-style "true")) + (if (org-string-nw-p code-attr-str) + (setq code-attr-str (format "%s, linenos=%s" code-attr-str linenos-style)) + (setq code-attr-str (format "linenos=%s" linenos-style))) + (let ((linenostart-str (and ;Extract the start line number of the src block + (string-match "\\`\\s-*\\([0-9]+\\)\\s-\\{2\\}" src-code) + (match-string-no-properties 1 src-code)))) + (when linenostart-str + (setq code-attr-str (format "%s, linenostart=%s" code-attr-str linenostart-str)))) + + (when line-num-p + ;; 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 src-code (replace-regexp-in-string "^\\s-*[0-9]+\\s-\\{2\\}" "" src-code)))) + + ;; (message "ox-hugo src [dbg] hl-lines: %S" hl-lines) + (when hl-lines + (if (org-string-nw-p code-attr-str) + (setq code-attr-str (format "%s, hl_lines=%s" code-attr-str hl-lines)) + (setq code-attr-str (format "hl_lines=%s" hl-lines)))) + + (when code-refs + (let* ((anchor-prefix (cdr code-refs-and-anchor)) + (anchor-str (format "anchorlinenos=true, lineanchors=%s" anchor-prefix))) + (org-element-put-property src-block :anchor-prefix anchor-prefix) + (setq code-attr-str (format "%s, %s" code-attr-str anchor-str)))) + + (unless use-highlight-sc + (plist-put info :md-code src-code) + (plist-put info :md-code-attr (org-string-nw-p code-attr-str))) + + (setq src-code-wrap + (if use-highlight-sc + (let ((hl-attr (if (org-string-nw-p code-attr-str) + (format " \"%s\"" code-attr-str) + ""))) + (format "{{< highlight %s%s >}}\n%s{{< /highlight >}}\n" + lang hl-attr src-code)) + (org-blackfriday-src-block src-block nil info))) + + (if (and goldmarkp (not use-highlight-sc)) + (setq ret (concat (org-blackfriday--get-style-str src-block) + src-anchor src-code-wrap caption-html)) + (setq ret (org-blackfriday--div-wrap-maybe + src-block + (concat src-anchor src-code-wrap caption-html) + info))) + 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 +`
      ' element with an optional `' 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'. + +If a block type has the `:trim-pre' property set to t in +`org-hugo-special-block-type-properties' or in the `#+header' +keyword above the special block, whitespace exported before that +block is trimmed. Similarly, if `:trim-post' property is set to +t, whitespace after that block is trimmed. + +INFO is a plist holding export options." + (let* ((block-type (org-element-property :type special-block)) + (block-type-plist (cdr (assoc block-type org-hugo-special-block-type-properties))) + (header (org-babel-parse-header-arguments + (car (org-element-property :header special-block)))) + (trim-pre (or (alist-get :trim-pre header) ;`:trim-pre' in #+header has higher precedence. + (plist-get block-type-plist :trim-pre))) + (trim-pre (org-hugo--value-get-true-p trim-pre)) ;If "nil", converts to nil + (trim-pre-tag (if trim-pre org-hugo--trim-pre-marker "")) + (last-element-p (null (org-export-get-next-element special-block info))) + (trim-post (unless last-element-p ;No need to add trim-post markers if this is the last element. + (or (alist-get :trim-post header) ;`:trim-post' in #+header has higher precedence. + (plist-get block-type-plist :trim-pre)))) + (trim-post (org-hugo--value-get-true-p trim-post)) ;If "nil", converts to nil + (trim-post-tag (if trim-post org-hugo--trim-post-marker "")) + (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' + (html-attr (org-export-read-attribute :attr_html special-block)) + (caption (plist-get html-attr :caption)) + (contents (when (stringp contents) + (org-trim + (if (plist-get block-type-plist :raw) + ;; https://lists.gnu.org/r/emacs-orgmode/2022-01/msg00132.html + (org-element-interpret-data (org-element-contents special-block)) + contents))))) + ;; (message "[ox-hugo-spl-blk DBG] block-type: %s" block-type) + ;; (message "[ox-hugo-spl-blk DBG] last element?: %s" (null (org-export-get-next-element special-block info))) + ;; (message "[ox-hugo-spl-blk DBG] %s: header: %s" block-type header) + ;; (message "[ox-hugo-spl-blk DBG] %s: trim-pre (type = %S): %S" block-type (type-of trim-pre) trim-pre) + ;; (message "[ox-hugo-spl-blk DBG] %s: trim-post (type = %S): %S" block-type (type-of trim-post) trim-post) + (plist-put info :type-plist block-type-plist) + (plist-put info :trim-pre-tag trim-pre-tag) + (plist-put info :trim-post-tag trim-post-tag) + (when contents + (cond + ((string= block-type "tikzjax") + (setq contents (format "%s%s%s" + "")) + (when (org-string-nw-p caption) + (setq contents (format "%s%s%s" + "
      \n" + contents + (format "\n
      %s
      \n
      " + caption)))) + contents) + ((string= block-type "description") + ;; Overwrite the value of the `:description' key in `info'. + (plist-put info :description (org-hugo--escape-hugo-shortcode contents "md")) + 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%s}}" + trim-pre-tag sc-open-char block-type sc-args sc-close-char)) + (sc-end (format "{{%s /%s %s}}%s" + sc-open-char block-type sc-close-char trim-post-tag))) + ;; (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 info)))))) + + + +;;; 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--copy-resources-maybe info) + (org-hugo--copy-ltximg-maybe 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")) + + (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 heading 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--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-menu-prop-to-alist (info) + "Return an alist of valid Hugo menu properties derived from INFO. + +INFO is a plist used as a communication channel." + (let* ((fm-format (plist-get info :hugo-front-matter-format)) + (menu-alist (org-hugo--parse-property-arguments (plist-get info :hugo-menu))) + (menu-ov-alist (org-hugo--parse-property-arguments (plist-get info :hugo-menu-override))) + (menu-props '(name url identifier pre post weight parent title)) + valid-menu-alist) + ;; (message "[org-hugo--parse-menu-prop-to-alist DBG] menu str: %S, alist: %S" str 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 + (cond + ((string= fm-format "toml") + (when (assoc 'menu menu-alist) + (setq valid-menu-alist (list (cdr (assoc 'menu menu-alist)))) + (let (menu-params) + (dolist (prop menu-props) + (let ((cell (or (assoc prop menu-ov-alist) + (assoc prop menu-alist)))) + (when cell + (push cell menu-params)))) + ;; Auto-set menu identifier if not already set by user. + (unless (assoc 'identifier menu-params) + (let ((id (org-hugo-slug (org-hugo--get-sanitized-title info)))) + (push `(identifier . ,id) menu-params))) + ;; Auto-set menu weight if not already set by user. + (unless (assoc 'weight menu-params) + (when org-hugo--subtree-coord + (push `(weight . ,(org-hugo--calc-weight)) menu-params))) + (setcdr valid-menu-alist menu-params)) + (setq valid-menu-alist (list valid-menu-alist)))) + ((string= fm-format "yaml") + (push 'menu menu-props) + (dolist (prop menu-props) + (let ((cell (or (assoc prop menu-ov-alist) + (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 heading 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. 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 + 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. + (let ((ret str) + (rgx "\\([^_]\\)__\\([^_]\\)")) + (while (string-match-p rgx ret) + (setq ret (replace-regexp-in-string rgx "\\1 \\2" ret))) ;"a__b" -> "a b" + ret)) + +(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--subtree-export-p (info) + "Return non-nil if the current export is subtree based. + +INFO is a plist used as a communication channel." + (memq 'subtree (plist-get info :export-options))) + +(defun org-hugo--string-unquote (str) + "Return STR after removing beginning and ending quotes if any. + +Return nil if STR is an empty string, or not a string." + (let ((unquoted-str (org-string-nw-p str))) ;Ensure that `str' is a non-empty string + (when (and unquoted-str + (string= (substring unquoted-str 0 1) "\"") ;First char is literally a " + (string= (substring unquoted-str -1) "\"")) ;Last char is literally a " + (setq unquoted-str (substring unquoted-str 1 -1))) + unquoted-str)) + +(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")) ;Includes tags inherited from #+filetags: too. + (all-t-and-c (or (when (stringp all-t-and-c-str) ;tags/categories from `all-t-and-c' are used + (org-split-string all-t-and-c-str ":")) ;only if HUGO_TAGS or HUGO_CATEGORIES are not set. + (and (null (org-hugo--subtree-export-p info)) ;Use #+filetags: for file-based exports if #+hugo_tags are not set. + org-file-tags))) + (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 headings + ;; 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 headings 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 info)) + (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 (unless (org-hugo--plist-get-true-p info :hugo-goldmark) + (require 'ox-hugo-deprecated) + (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 . ,(org-hugo--string-unquote (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))) + (data `,(append data weight-data custom-fm-data + (list + (cons 'menu menu-alist) + (cons 'resources resources) + (cons 'logbook (plist-get info :logbook))))) + 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 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] data: %S" data) + ;; (progn (message "[fm data DBG] ") (pp data)) + ;; (message "[fm tags DBG] %S" tags) + ;; (message "[fm categories DBG] %S" categories) + ;; (message "[fm keywords DBG] %S" keywords) + + ;; Append group tags to user-set tags if tag groups are defined in + ;; the buffer. + (when (and org-group-tags org-tag-groups-alist) + (let (tag-groups-alist-mod) + + ;; Copy `org-tag-groups-alist' to `tag-groups-alist-mod' while + ;; modifying the tags and categories as defined by + ;; `org-hugo-tag-processing-functions'. + (dolist (group org-tag-groups-alist) + (let ((group-mod group)) + (dolist (fn org-hugo-tag-processing-functions group-mod) + (setq group-mod (funcall fn group-mod info))) + (push group-mod tag-groups-alist-mod))) + + (dolist (t-or-c (append tags categories)) + (let ((to-be-searched `(,t-or-c))) + (while (> (length to-be-searched) 0) + ;; (message "[tag group DBG] t and c to search: %S" to-be-searched) + (let ((tc (pop to-be-searched))) + (dolist (group tag-groups-alist-mod) + ;; (message "[tag group DBG] Searching %s in %S" tc group) + (when (member tc group) + (let ((head-tag (car group))) + (if (org-hugo--category-p head-tag) + (let ((head-cat (replace-regexp-in-string "\\`@" "" head-tag))) + (unless (member head-cat categories) + (push head-cat categories) + ;; (message "[tag group DBG] .... Adding cat %s" head-cat) + )) + (unless (member head-tag tags) + (push head-tag tags) + ;; (message "[tag group DBG] .... Adding tag %s" head-tag) + )) + ;; Add the current `head-tag' as the new tag to + ;; search if current tag or category (`tc') is not + ;; the `head-tag', and if it's not already in the + ;; search list. + (unless (or (string= tc head-tag) + (member head-tag to-be-searched)) + (push head-tag to-be-searched)))))))))) + ;; (message "[tag group DBG] updated tags: %S" tags) + ;; (message "[tag group DBG] updated categories: %S" categories) + + ;; Overwrite the 'tags and 'categories key values in `data' with + ;; the updated values. + ;; https://stackoverflow.com/a/40815365/1219634 + (setf (alist-get 'tags data) tags) + (setf (alist-get 'categories data) categories)) + + (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)) + (progn + ;; 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. + (require 'ox-hugo-pandoc-cite) + (setq org-hugo--fm-yaml + (org-hugo-pandoc-cite--meta-data-generator data))) + (setq org-hugo--fm-yaml ret)) + ;; (message "org-hugo--fm-yaml: `%s'" org-hugo--fm-yaml) + 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))) + ;; (message "[org-hugo--calc-weight dbg] level = %S" level) + ;; (message "[org-hugo--calc-weight dbg] index = %S" index) + (+ (* 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\"." + (if (string= format "yaml") + (org-hugo--gen-yaml-front-matter data) + (let ((tomelr-indent-multi-line-strings t)) + (format "+++\n%s\n+++\n" (tomelr-encode data))))) + +(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_FRAG" + "HUGO_BUNDLE" + "HUGO_BASE_DIR" + "HUGO_GOLDMARK" + "HUGO_CODE_FENCE" + "HTML_CONTAINER" + "HTML_CONTAINER_CLASS" + "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" + "OPTIONS"))) + (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." + (let* ((subtree (car (org-hugo--get-elem-with-prop :EXPORT_FILE_NAME))) + (point (org-element-property :begin subtree))) ;`point' will be nil if `subtree' is nil + (when point + (goto-char point)) + subtree)) + +(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) + ;; (message "[org-hugo--get-post-subtree-coordinates dbg] current-pos: %S, scope: %S" + ;; current-pos scope) + (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." + (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 (cdr (org-hugo--get-elem-with-prop :commentedp))) + (commented-heading (when is-commented + (org-element-property :title + (car (org-hugo--get-elem-with-prop :commentedp))))) + 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)) + ;; FIXME: Sometimes `org-get-outline-path' returns the + ;; list with empty string elements. It's not clear + ;; why, but the below `cl-delete-if' workarounds works + ;; (for now). + (current-outline-path (cl-delete-if + (lambda (el) + (string= el "")) + (org-get-outline-path :with-self))) + ;; When batch-exporting subtrees, do not call + ;; `org-hugo--after-all-exports-function' after each + ;; subtree export. In that case, that function is + ;; called *after* looping through all the post + ;; subtrees. + (org-hugo--disable-after-all-exports-hook all-subtrees)) + ;; (message "[org-hugo--export-subtree-to-md dbg] @ point %S, current-outline-path: %S" + ;; (point) current-outline-path) + (cond + (is-commented + (if (string= title commented-heading) + (message "[ox-hugo] `%s' was not exported as it is commented out" title) + (message "[ox-hugo] `%s' was not exported as one of its parent subtrees `%s' is commented out" + title commented-heading))) + (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)) + + ;; (message "[org-hugo--export-subtree-to-md dbg] EXPORT_HUGO_MENU value: %S" + ;; (org-entry-get nil "EXPORT_HUGO_MENU" :inherit)) + ;; 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))) + + (let ((buffer (if org-hugo--preprocess-buffer + (let ((pre-proc-buf (or org-hugo--preprocessed-buffer + (org-hugo--get-pre-processed-buffer)))) + (unless org-hugo--preprocessed-buffer + (setq org-hugo--preprocessed-buffer pre-proc-buf) + (add-to-list 'org-hugo--opened-buffers pre-proc-buf)) + pre-proc-buf) + (current-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))))))) + 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 (org-hugo--buffer-has-valid-post-subtree-p))) + (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-pre-processed-buffer () + "Return a pre-processed copy of the current buffer. + +Internal links to other subtrees are converted to external +links." + (let ((pre-processed-buffer-prefix "*Ox-hugo Pre-processed ")) + (let* (;; 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)))) + + ;; Process all link elements in the AST. + (org-element-map ast '(link special-block) + (lambda (el) + (let ((el-type (org-element-type el))) + (cond + ((equal 'link el-type) + (let ((type (org-element-property :type el))) + (when (member type '("custom-id" "id" "fuzzy")) + (let* ((raw-link (org-element-property :raw-link el)) + (destination (if (string= type "fuzzy") + (progn + ;; Derived from ox.el -> `org-export-data'. If a broken link is seen + ;; and if `broken-links' option is not nil, ignore the error. + (condition-case err + (org-export-resolve-fuzzy-link el info) + (org-link-broken + (unless (or (plist-get info :with-broken-links) + ;; Parse the `:EXPORT_OPTIONS' property if set + ;; in a parent heading. + (plist-get + (org-export--parse-option-keyword + (or (cdr (org-hugo--get-elem-with-prop + :EXPORT_OPTIONS + (org-element-property :begin el))) + "")) + :with-broken-links)) + (user-error "Unable to resolve link: %S" (nth 1 err)))))) + (org-export-resolve-id-link el (org-export--collect-tree-properties ast info)))) + (source-path (org-hugo--heading-get-slug el info :inherit-export-file-name)) + (destination-path (org-hugo--heading-get-slug destination info :inherit-export-file-name)) + (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 el))) + ;; (message "[ox-hugo pre process DBG] link desc: %s" link-desc) + + ;; Override the link types to be files. We + ;; will be using out-of-subtree links as links + ;; to dummy files with + ;; `org-hugo--preprocessed-buffer-dummy-file-suffix' + ;; suffix. + (org-element-put-property el :type "file") + (org-element-put-property + el :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--preprocessed-buffer-dummy-file-suffix)) + ;; Hugo only supports anchors to headings, + ;; so if a "fuzzy" type link points to + ;; anything else than a heading, it should + ;; point to the file. + ((and (string= type "fuzzy") + (not (string-prefix-p "*" raw-link))) + (concat destination-path org-hugo--preprocessed-buffer-dummy-file-suffix)) + ;; In "custom-id" type links, the raw-link + ;; matches the anchor of the destination. + ((string= type "custom-id") + (concat destination-path org-hugo--preprocessed-buffer-dummy-file-suffix "::" 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-hugo--preprocessed-buffer-dummy-file-suffix "::#" 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 ((heading-title + (org-export-data-with-backend + (org-element-property :title destination) 'ascii info))) + ;; (message "[ox-hugo pre process DBG] destination heading: %s" heading-title) + (org-element-set-contents el heading-title))))))))) + ((equal 'special-block el-type) + ;; Handle empty Org special blocks. When empty + ;; blocks are found, set that elements content as "" + ;; instead of nil. + (unless (org-element-contents el) + (org-element-adopt-elements el ""))))) + nil)) ;Minor performance optimization: Make `org-element-map' lambda return a nil. + + (when (version< "25.99" emacs-version) ;`kill-matching-buffers' got `:no-ask' arg in emacs 26.1 + ;; https://git.savannah.gnu.org/cgit/emacs.git/commit/?id=70d01daceddeb4e4c49c79473c81420f65ffd290 + ;; First kill all the old pre-processed buffers if still left open + ;; for any reason. + (kill-matching-buffers (regexp-quote pre-processed-buffer-prefix) :internal-too :no-ask)) + + ;; Turn the AST with updated links into an Org buffer. + (let ((local-variables (buffer-local-variables)) + (bound-variables (org-export--list-bound-variables)) + (buffer (generate-new-buffer (concat pre-processed-buffer-prefix (buffer-name) " *")))) + (with-current-buffer buffer + (let (vars) + (org-hugo--org-mode-light) + ;; Copy specific buffer local variables and variables set + ;; through BIND keywords. Below snippet is copied from + ;; ox.el -> `org-export--generate-copy-script'. + (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))))) + + (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 heading 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-1-export-function info nil) + (org-hugo--after-all-exports-function)))) + +;;;###autoload +(defun org-hugo-export-to-md (&optional async subtreep visible-only) + "Export current buffer to a Hugo-compatible Markdown file. + +This is the main exporting function which is called by both +`org-hugo--export-file-to-md' and +`org-hugo--export-subtree-to-md', and thus +`org-hugo-export-wim-to-md' too. + +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 heading 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)) + ;; Don't print "Saving file .." for each exported file. This + ;; works in interactive mode i.e. when exporting posts from + ;; within emacs. But in --batch mode, setting + ;; `save-silently' to t, ironically prints a blank line + ;; instead of the "Saving file .." message. So leave this + ;; variable value at nil for --batch runs. + (save-silently (unless noninteractive + t)) + (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-1-export-function info outfile) + (unless org-hugo--disable-after-all-exports-hook + (org-hugo--after-all-exports-function))))) + +;;;###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 only that subtree. Return the return value of + `org-hugo--export-subtree-to-md'. + +- If the current subtree doesn't have that property, but one of + its parent subtrees has, export from that subtree's scope. + Return the return value of `org-hugo--export-subtree-to-md'. + +- If there are no valid Hugo post subtrees (that have the + \"EXPORT_FILE_NAME\" property) in the Org buffer the subtrees + have that property, do file-based + export (`org-hugo--export-file-to-md'), regardless of the value + of ALL-SUBTREES. Return the return value of + `org-hugo--export-file-to-md'. + +- If ALL-SUBTREES is non-nil and the Org buffer has at least 1 + valid Hugo post subtree, export all those valid post subtrees. + Return a list of output files. + +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'." + (interactive "P") + (let ((f-or-b-name (if (buffer-file-name) + (file-name-nondirectory (buffer-file-name)) + (buffer-name))) + (buf-has-subtree (org-hugo--buffer-has-valid-post-subtree-p)) + ret) + + ;; Auto-update `org-id-locations' if it's nil or empty hash table + ;; to avoid broken [[id:..]] type links. + (when (or (eq org-id-locations nil) (zerop (hash-table-count org-id-locations))) + (org-id-update-id-locations (directory-files "." :full "\.org\$" :nosort) :silent)) + + (org-hugo--cleanup) + + (save-window-excursion + (org-with-wide-buffer + (cond + ;; Publish all subtrees in the current Org buffer. + ((and buf-has-subtree all-subtrees) + (let ((start-time (current-time))) + ;; Make the *Messages* buffer less noisy when exporting all + ;; subtrees. + (dolist (fn org-hugo--all-subtrees-export--functions-to-silence) + (advice-add fn :around #'org-hugo--advice-silence-messages)) + + (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<>\"\"")) + + (let* ((elapsed-time (float-time (time-since start-time))) + (avg-time (/ elapsed-time org-hugo--subtree-count))) + (message "[ox-hugo] Exported %d subtree%s from %s in %0.3fs (%0.3fs avg)" + org-hugo--subtree-count + (if (= 1 org-hugo--subtree-count) "" "s") + f-or-b-name + elapsed-time + avg-time)) + (org-hugo--after-all-exports-function))) + + ;; Publish only the current valid Hugo post subtree. When + ;; exporting only one subtree, buffer pre-processing is done + ;; inside `org-hugo--export-subtree-to-md'. + ((and buf-has-subtree (not all-subtrees)) + (setq ret (org-hugo--export-subtree-to-md async visible-only))) + + ;; Attempt file-based export. + (t + (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-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 diff --git a/org/elpa/tomelr-0.4.3.signed b/org/elpa/tomelr-0.4.3.signed new file mode 100644 index 0000000..9ba9064 --- /dev/null +++ b/org/elpa/tomelr-0.4.3.signed @@ -0,0 +1 @@ +Good signature from 066DAFCB81E42C40 GNU ELPA Signing Agent (2019) (trust undefined) created at 2022-05-12T05:05:02-0400 using RSA \ No newline at end of file diff --git a/org/elpa/tomelr-0.4.3/.elpaignore b/org/elpa/tomelr-0.4.3/.elpaignore new file mode 100644 index 0000000..d6a32d6 --- /dev/null +++ b/org/elpa/tomelr-0.4.3/.elpaignore @@ -0,0 +1,5 @@ +.github +doc +test +Makefile +LICENSE diff --git a/org/elpa/tomelr-0.4.3/CHANGELOG.org b/org/elpa/tomelr-0.4.3/CHANGELOG.org new file mode 100644 index 0000000..a40a80e --- /dev/null +++ b/org/elpa/tomelr-0.4.3/CHANGELOG.org @@ -0,0 +1,235 @@ +# This file is auto-generated by running 'make changelog' from the repo root. + +* Changelog + +All notable changes to this project will be documented in this file. + +** *0.4.2* - <2022-05-11> + +[[https://github.com/kaushalmodi/tomelr/compare/19d128f0d2fd4ea8d4bf92cb1f5a235468b45d00...e45b0e43e80cc9df796027674a412708050ec8d6][19d128f...e45b0e4]] + +*** :bug: Bug Fixes +:PROPERTIES: +:CUSTOM_ID: bug-fixes-v0.4.2 +:END: + +- Don't allow multi-line strings to indent by more than 2 spaces … The `tomelr-indent-multi-line-strings` variable is set to `t' mainly … by ox-hugo. So these multi-line strings will most likely be pars… ([[https://github.com/kaushalmodi/tomelr/commit/e45b0e43e80cc9df796027674a412708050ec8d6][e45b0e4]]) + +*** :recycle: Refactor +:PROPERTIES: +:CUSTOM_ID: refactor-v0.4.2 +:END: + +- Use append instead of push+​​reverse ([[https://github.com/kaushalmodi/tomelr/commit/7f331a8c0d4ab1f25b6f8b8749f8cb7a6ad274a6][7f331a8]]) + +** *0.4.0* - <2022-05-11> + +[[https://github.com/kaushalmodi/tomelr/compare/2820bf1af3e5482df8aa1c9c35bd0d7333ce6a68...a5b2a0e6251ce62cd2ce515b961dba513966fcb9][2820bf1...a5b2a0e]] + +*** :bug: Bug Fixes +:PROPERTIES: +:CUSTOM_ID: bug-fixes-v0.4.0 +:END: + +- Case of table arrays in nested sub tables ([[https://github.com/kaushalmodi/tomelr/commit/0f7a6cf7f40717b3fd7735f3ee78978e2d031bdb][0f7a6cf]]) + +** *0.3.0* - <2022-05-05> + +[[https://github.com/kaushalmodi/tomelr/compare/8fc2257ec072a3fc3316c7f311722db50b37558e...2820bf1af3e5482df8aa1c9c35bd0d7333ce6a68][8fc2257...2820bf1]] + +*** :bug: Bug Fixes +:PROPERTIES: +:CUSTOM_ID: bug-fixes-v0.3.0 +:END: + +- Make plist parsing work on emacs 26.3 … The fix was to make tomelr depend on the newer map v3.2.1 and seq … v2.23 versions from GNU ELPA. ([[https://github.com/kaushalmodi/tomelr/commit/2820bf1af3e5482df8aa1c9c35bd0d7333ce6a68][2820bf1]]) + +** *0.2.3* - <2022-05-03> + +[[https://github.com/kaushalmodi/tomelr/compare/4e2edfe073d2a057a37b159d4e67282aa132f596...884674e168cbef35275a325f707c588ac2b5c866][4e2edfe...884674e]] + +*** :memo: Documentation +:PROPERTIES: +:CUSTOM_ID: documentation-v0.2.3 +:END: + +- Add installation instructions now that this is in GNU ELPA ([[https://github.com/kaushalmodi/tomelr/commit/9aa308665daa507655285d601d3e13657cb4523e][9aa3086]]) + +** *0.2.0* - <2022-05-03> + +[[https://github.com/kaushalmodi/tomelr/compare/568de5efb250c0bb4f19495c69b8b42b41fb186d...b4be72f240038d2db27540effcdd63e649b4df57][568de5e...b4be72f]] + +*** :sparkles: Features +:PROPERTIES: +:CUSTOM_ID: features-v0.2.0 +:END: + +- Add option for indenting multi-line strings … New defvar `tomelr-indent-multi-line-strings`. ([[https://github.com/kaushalmodi/tomelr/commit/3362213172237f40ff0d9aa3ddf12b4bb00a3564][3362213]]) + +*** :recycle: Refactor +:PROPERTIES: +:CUSTOM_ID: refactor-v0.2.0 +:END: + +- Rename tomelr predicate functions for consistency ([[https://github.com/kaushalmodi/tomelr/commit/b4be72f240038d2db27540effcdd63e649b4df57][b4be72f]]) + +** *0.1.0* - <2022-05-03> + +[[https://github.com/kaushalmodi/tomelr/compare/4434ccc64b1e311b53e8ecc906113bba2e16fa98...568de5efb250c0bb4f19495c69b8b42b41fb186d][4434ccc...568de5e]] + +*** :sparkles: Features +:PROPERTIES: +:CUSTOM_ID: features-v0.1.0 +:END: + +- Support string keys ([[https://github.com/kaushalmodi/tomelr/commit/ed13b73e9b68ac2c51f3545ac337bbfeba063a42][ed13b73]]) +- Auto-coerce string to boolean ([[https://github.com/kaushalmodi/tomelr/commit/ebe5959174812ffc3cf7d88040b854599b15a88a][ebe5959]]) +- Auto-coerce string to integers ([[https://github.com/kaushalmodi/tomelr/commit/a25d952a17d344ac3d7396ae78a34e21b9ada14e][a25d952]]) + +*** :bug: Bug Fixes +:PROPERTIES: +:CUSTOM_ID: bug-fixes-v0.1.0 +:END: + +- Auto-stringify symbols like 1.10.1 ([[https://github.com/kaushalmodi/tomelr/commit/ae983711be15d95abd22ae4d7b8c116031de60a0][ae98371]]) +- Auto-stringify and auto-quote symbol values ([[https://github.com/kaushalmodi/tomelr/commit/ec381fd723c9801caa2353a40d41e8cc8096ea29][ec381fd]]) +- Boolean coercing when value is a symbol true or false ([[https://github.com/kaushalmodi/tomelr/commit/c2d1328c4404e6af920dc431ba57ee00eef4ba36][c2d1328]]) +- Integer coercing of a number strings with underscores ([[https://github.com/kaushalmodi/tomelr/commit/a676192b435474fbff53fe361dbf983e3b8ac799][a676192]]) + +*** :recycle: Refactor +:PROPERTIES: +:CUSTOM_ID: refactor-v0.1.0 +:END: + +- Don't attempt to triple-quote TOML keys … Triple-quote only when the `type' input of `tomelr--print-stringlike' … is nil. ([[https://github.com/kaushalmodi/tomelr/commit/334b7cba54001708e6819b9df0abf0c553c0d0a2][334b7cb]]) +- Minor code reorg ([[https://github.com/kaushalmodi/tomelr/commit/b2ba4c46b59d7baa4a6d02ba64657c08776d2d0e][b2ba4c4]]) + +*** :hammer: Testing +:PROPERTIES: +:CUSTOM_ID: testing-v0.1.0 +:END: + +- Add a test for string scalar with blank lines ([[https://github.com/kaushalmodi/tomelr/commit/57bed2cca8b648d2abc6da525a3420b3e968efb4][57bed2c]]) + +** *0.0.2* - <2022-05-02> + +[[https://github.com/kaushalmodi/tomelr/compare/3aa4dc1dbdce5875166b9db76b6de0a0ad679b33...45542fb234fcc4fea50a5fed0c7682d0d3db0f9b][3aa4dc1...45542fb]] + +*** :bug: Bug Fixes +:PROPERTIES: +:CUSTOM_ID: bug-fixes-v0.0.2 +:END: + +- TT with key with array value are detected correctly … Use json-alist-p and json-plist-p for TOML Table detection. This … uncomplicated the TOML Table logic quite a bit. … Caveat: Lists of plist need t… ([[https://github.com/kaushalmodi/tomelr/commit/044b5e1a042aa1058792af607b1d7cd4cc70d144][044b5e1]]) +- List format array of plists now detected as TOML Table Array … Also simplify tomelr--toml-table-array-p. ([[https://github.com/kaushalmodi/tomelr/commit/171e5a76824f30730a9e80384a18f3888dd3cc2a][171e5a7]]) +- Compatibility for emacs 26.3 … listp also works instead of proper-list-p here. So use that instead. … proper-list-p was introduced in emacs 27.x. ([[https://github.com/kaushalmodi/tomelr/commit/d86fd721ce4746550038e53dffe34885b06e9225][d86fd72]]) + +*** :memo: Documentation +:PROPERTIES: +:CUSTOM_ID: documentation-v0.0.2 +:END: + +- Remove an invalid example ([[https://github.com/kaushalmodi/tomelr/commit/dc9b2a63f8536d0ee14e480af5f8f273b1a117a9][dc9b2a6]]) + +*** :recycle: Refactor +:PROPERTIES: +:CUSTOM_ID: refactor-v0.0.2 +:END: + +- Clean up unused code … Use json-alist-p and json-plist-p directly where applicable. ([[https://github.com/kaushalmodi/tomelr/commit/f9d670e1656f1400b544ff27980657cbf5f8357b][f9d670e]]) +- Remove unnecessary tomelr-encode-keyword … Also, The "keyword" term was confusing here; "boolean" makes more … sense. ([[https://github.com/kaushalmodi/tomelr/commit/41ccea4ebe0619bd6d38d3d8c2174e0b27587df0][41ccea4]]) +- Use `tomelr--toml-table-p` ([[https://github.com/kaushalmodi/tomelr/commit/4386d99a8596fa244c818b8ae9f341feeeb0b677][4386d99]]) + +*** :hammer: Testing +:PROPERTIES: +:CUSTOM_ID: testing-v0.0.2 +:END: + +- Add tests for json.el functions used in tomelr ([[https://github.com/kaushalmodi/tomelr/commit/406f4922a8677f07d14190d48061ae60169825d5][406f492]]) + +*** :bento: Other +:PROPERTIES: +:CUSTOM_ID: other-v0.0.2 +:END: + +- Revert "doc: Update the medley example" … This reverts commit commit # [[https://github.com/kaushalmodi/tomelr/commit/26f1fc2f3c0245e69c8c72b0cd01024f9d53078b][26f1fc2]]. ([[https://github.com/kaushalmodi/tomelr/commit/df0e73334f918ee9de7e1f0a7cd0fb9037a79faa][df0e733]]) + +** *0.0.1* - <2022-04-30> + +*** :boom: Breaking +:PROPERTIES: +:CUSTOM_ID: breaking-v0.0.1 +:END: + +- Set boolean false using :false value … This is so that null vs false can be distinguished in JSON. … If a lisp data value is nil, that key will be absent in TOML. ([[https://github.com/kaushalmodi/tomelr/commit/2ea3b5e032629a3974e2733f849cf47259e80e0d][2ea3b5e]]) + +*** :sparkles: Features +:PROPERTIES: +:CUSTOM_ID: features-v0.0.1 +:END: + +- Add s-exp->toml examples and spec ([[https://github.com/kaushalmodi/tomelr/commit/8bc506af5acd6e8f3ce47890185c5f4db1c3eb3e][8bc506a]]) +- Add plist example ([[https://github.com/kaushalmodi/tomelr/commit/846676a172d2bdd39e1e8b5628a7e88a3605f68b][846676a]]) +- First cut -- Port json-encode from json.el to tomelr-encode … Contains only the fixes needed to make the boolean key-value pair look … right in TOML. ([[https://github.com/kaushalmodi/tomelr/commit/52dc93201deb02a3d380d841e839f5f3e5f32c95][52dc932]]) +- Encode to multi-line TOML string automatically … .. if the string has newlines or quote chars. ([[https://github.com/kaushalmodi/tomelr/commit/7d8d41f15b6d5a2d2325160490482b133c56f845][7d8d41f]]) +- Recognize local date format YYYY-MM-DD ([[https://github.com/kaushalmodi/tomelr/commit/1d65064ffa0c6e1d5e9cb14a31de8ada38dc3395][1d65064]]) +- Recognize RFC 3339 formatted date-time +​​ offset ([[https://github.com/kaushalmodi/tomelr/commit/91800b26b8bff6b89fce887fbcadb9e956f412dd][91800b2]]) +- Skip converting keys whose values are nil ([[https://github.com/kaushalmodi/tomelr/commit/69217d47a65cb987d7d1ce32d3db5566a169ceca][69217d4]]) +- Convert Lisp lists to TOML arrays ([[https://github.com/kaushalmodi/tomelr/commit/96c890a68b9a587283bc7522c3893370cc522ca6][96c890a]]) +- Support basic TOML tables ([[https://github.com/kaushalmodi/tomelr/commit/cedb75df72f9aed0ad990b631f32d71f6ba1b79d][cedb75d]]) +- Support nested TOML tables ([[https://github.com/kaushalmodi/tomelr/commit/a1f434f03a761c50cd9813e27d5441d6b2c2902d][a1f434f]]) +- Add basic support for S-exp plists -> TOML conversion … Support added for scalars and lists. … Pending: tables, arrays of tables, etc. ([[https://github.com/kaushalmodi/tomelr/commit/2810504e840d8038b9a06fff732889f0f8cc73c8][2810504]]) +- Support basic TOML Table Arrays ([[https://github.com/kaushalmodi/tomelr/commit/ad8366d904dea6fc3f4af5bf57bcd92c6b37f57e][ad8366d]]) +- Make a very basic nested array of TTA work ([[https://github.com/kaushalmodi/tomelr/commit/a7b3a5703729682e88d6352932e235cbe04deb28][a7b3a57]]) +- Support (lightly tested) nested TOML Table Arrays ([[https://github.com/kaushalmodi/tomelr/commit/10a1994aedcbd95c35096b257cf1e9e6fd4554cb][10a1994]]) +- Implement everything planned in the initial spec … Fix converting of array of TOML tables represented by S-exp vectors. ([[https://github.com/kaushalmodi/tomelr/commit/e2b313ca3b3e4c98c18749671ac59bc1fe319c52][e2b313c]]) + +*** :bug: Bug Fixes +:PROPERTIES: +:CUSTOM_ID: bug-fixes-v0.0.1 +:END: + +- Dates will be strings in Lisp … refactor: Move "lists of lists" to a different section ([[https://github.com/kaushalmodi/tomelr/commit/28642f2e787a5424ebff30bbb6f7df2af54d6329][28642f2]]) +- Require subr-x for older Emacs versions ([[https://github.com/kaushalmodi/tomelr/commit/af40c0b40f8d3fe61ac711c00a32d6747d4e55e7][af40c0b]]) +- Use `=​​` and `length` separately instead of `length=​​` … length=​​ does not exist on 27.2 and older Emacs versions. … It was added in Emacs 28.1 in … https://git.savannah.gnu.org/cgit/emacs.git/comm… ([[https://github.com/kaushalmodi/tomelr/commit/98c9b8c1fc9eb3fbc0016d6692ae8aed95bbe003][98c9b8c]]) +- Don't run plist to TOML conversion test on emacs 26.3 and older ([[https://github.com/kaushalmodi/tomelr/commit/c0962ba15f0cf7ff944e822f623b2800b5ebfd73][c0962ba]]) +- Attempt to make tomelr--toml-table-p more robust ([[https://github.com/kaushalmodi/tomelr/commit/ca9245038a74f272b246979271cbf2adef09eb89][ca92450]]) +- Support TOML tables specified as plists ([[https://github.com/kaushalmodi/tomelr/commit/4c419bcee218a95d6669a5b198d1b71f6a8e7691][4c419bc]]) +- Support TOML tables arrays specified as plist vector ([[https://github.com/kaushalmodi/tomelr/commit/cff1f8aa890d8c08fe26243870d59aa39f602156][cff1f8a]]) +- Stricter condition before starting TOML table array check ([[https://github.com/kaushalmodi/tomelr/commit/38160ef271493293166f81ce1a3d52b58a484a8e][38160ef]]) +- Don't let array of TOML tables be recognized as TOML tables ([[https://github.com/kaushalmodi/tomelr/commit/0eb4fa04ac3e6741f743ba451b1ec7a019989b5e][0eb4fa0]]) +- Don't let TOML tables be recognized as TOML tables arrays ([[https://github.com/kaushalmodi/tomelr/commit/5959b90ffa499281306473c83b669353ecb85073][5959b90]]) +- Correct the spec for nested array of tables ([[https://github.com/kaushalmodi/tomelr/commit/baf81228bc812de55e4df9340dd34cc8cc5a2ab8][baf8122]]) +- Better detection of nested TTA, but still wip … This fix also breaks the plist support for TTA ([[https://github.com/kaushalmodi/tomelr/commit/0f4e7b4f2c40a2cdce735d614eba9b7ac4640d06][0f4e7b4]]) +- Detect TT with sub-tables correctly ([[https://github.com/kaushalmodi/tomelr/commit/b64eb07e99e9ab45cc88dc6b628f8bc828a0dc28][b64eb07]]) +- Detect nested TTA correctly when not present in first TT key ([[https://github.com/kaushalmodi/tomelr/commit/a33dbd1286cd1f539c1e07bd21dc60464dd2f667][a33dbd1]]) + +*** :memo: Documentation +:PROPERTIES: +:CUSTOM_ID: documentation-v0.0.1 +:END: + +- Add LOGBOOK drawer example ([[https://github.com/kaushalmodi/tomelr/commit/d96a3b235b9dc7181f8140cf23b75d28a853c941][d96a3b2]]) +- Discover `json-encoding-pretty-print` variable! ([[https://github.com/kaushalmodi/tomelr/commit/732140041e91528a7ee3c730ce10bac0931698c4][7321400]]) +- Add spec for nested tables and arrays of tables ([[https://github.com/kaushalmodi/tomelr/commit/bb85106ee98c1ee04100db9d298510b3f57e0751][bb85106]]) + +*** :recycle: Refactor +:PROPERTIES: +:CUSTOM_ID: refactor-v0.0.1 +:END: + +- Move TOML Table detection logic to a separate fn ([[https://github.com/kaushalmodi/tomelr/commit/3c068fb9d9319d2876de359d2bc9068b857e091b][3c068fb]]) + +*** :hammer: Testing +:PROPERTIES: +:CUSTOM_ID: testing-v0.0.1 +:END: + +- Add test for boolean scalar key-value pairs ([[https://github.com/kaushalmodi/tomelr/commit/05d2cafcd989b977fa3e9d05e293e9f8bae22fc4][05d2caf]]) +- Add test for integer scalar key-value pairs ([[https://github.com/kaushalmodi/tomelr/commit/c872e9efc1bcf0d9310160f825032c602500c346][c872e9e]]) +- Add test for float scalar key-value pairs ([[https://github.com/kaushalmodi/tomelr/commit/9c91e0dc07291aae8a8b2b4dd1cea52583165e14][9c91e0d]]) +- Add test for TOML Array of Arrays ([[https://github.com/kaushalmodi/tomelr/commit/f37841cc781ce322ba31806cf9ef1ca7578f5714][f37841c]]) +- Test that 'false is also considered as boolean false in TOML ([[https://github.com/kaushalmodi/tomelr/commit/6bbe740e52d40a5d87d62805af3ed89cc16779b9][6bbe740]]) +- Test `tomelr--toml-table-p` ([[https://github.com/kaushalmodi/tomelr/commit/0d4674f782bee99ee36aca079ede57adeccc384f][0d4674f]]) + +# This file is generated by git-cliff by running 'make changelog' from the repo root. diff --git a/org/elpa/tomelr-0.4.3/README.org b/org/elpa/tomelr-0.4.3/README.org new file mode 100644 index 0000000..e582e2e --- /dev/null +++ b/org/elpa/tomelr-0.4.3/README.org @@ -0,0 +1,1243 @@ +#+title: Emacs-Lisp Library for converting S-expressions to TOML +#+author: Kaushal Modi + +#+options: H:3 + +#+property: header-args :eval never-export + +[[https://github.com/kaushalmodi/tomelr/actions][https://github.com/kaushalmodi/tomelr/actions/workflows/test.yml/badge.svg]] [[https://elpa.gnu.org/packages/tomelr.html][https://elpa.gnu.org/packages/tomelr.svg]] [[https://www.gnu.org/licenses/gpl-3.0][https://img.shields.io/badge/License-GPL%20v3-blue.svg]] + +* Installation +~tomelr~ is a library that will typically be auto-installed via +another package requiring it. + +If you are developing a package and want to use this library, you can +install it locally using Emacs ~package.el~ as follows as it's +available via GNU ELPA: + +*M-x* ~package-install~ *RET* ~tomelr~ *RET* +* How to develop using this library +1. Add this library in the /Package-Requires/ header. Here's an + example from [[https://ox-hugo.scripter.co][~ox-hugo~]]: + #+begin_src emacs-lisp + ;; Package-Requires: ((emacs "24.4") (org "9.0") tomelr)) + #+end_src +2. Require it. + #+begin_src emacs-lisp + (require 'tomelr) + #+end_src +3. Use the ~tomelr-encode~ function. + - Input :: Lisp data expression in Alist or Plist format + - Output :: TOML string +** Example +*** Alist data +Here's an example of input *alist* that can be processed by +~tomelr-encode~. +#+begin_src emacs-lisp :eval no :noweb-ref data-example-alist +'((title . "Some Title") ;String + (author . ("fn ln")) ;List + (description . "some long description\nthat spans multiple\nlines") ;Multi-line string + (date . 2022-03-14T01:49:00-04:00) ;RFC 3339 date format + (tags . ("tag1" "tag2")) + (draft . "false") ;Boolean + (versions . ((emacs . "28.1.50") (org . "release_9.5-532-gf6813d"))) ;Map or TOML Table + (org_logbook . (((timestamp . 2022-04-08T14:53:00-04:00) ;Array of maps or TOML Tables + (note . "This note addition prompt shows up on typing the `C-c C-z` binding.\nSee [org#Drawers](https://www.gnu.org/software/emacs/manual/html_mono/org.html#Drawers).")) + ((timestamp . 2018-09-06T11:45:00-04:00) + (note . "Another note **bold** _italics_.")) + ((timestamp . 2018-09-06T11:37:00-04:00) + (note . "A note `mono`."))))) +#+end_src + +#+begin_src emacs-lisp :noweb yes :exports none :wrap src toml +(tomelr-encode + <>) +#+end_src + +#+RESULTS: +#+begin_src toml +title = "Some Title" +author = ["fn ln"] +description = """ +some long description +that spans multiple +lines""" +date = 2022-03-14T01:49:00-04:00 +tags = ["tag1", "tag2"] +draft = false +[versions] + emacs = "28.1.50" + org = "release_9.5-532-gf6813d" +[[org_logbook]] + timestamp = 2022-04-08T14:53:00-04:00 + note = """ +This note addition prompt shows up on typing the `C-c C-z` binding. +See [org#Drawers](https://www.gnu.org/software/emacs/manual/html_mono/org.html#Drawers).""" +[[org_logbook]] + timestamp = 2018-09-06T11:45:00-04:00 + note = "Another note **bold** _italics_." +[[org_logbook]] + timestamp = 2018-09-06T11:37:00-04:00 + note = "A note `mono`." +#+end_src +*** Plist data +Here's an example of input *plist* that can be processed by +~tomelr-encode~. +#+begin_src emacs-lisp :eval no :noweb-ref data-example-plist +'(:title "Some Title" ;String + :author ("fn ln") ;List + :description "some long description\nthat spans multiple\nlines" ;Multi-line string + :date 2022-03-14T01:49:00-04:00 ;RFC 3339 date format + :tags ("tag1" "tag2") + :draft "false" ;Boolean + :versions (:emacs "28.1.50" :org "release_9.5-532-gf6813d") ;Map or TOML Table + :org_logbook ((:timestamp 2022-04-08T14:53:00-04:00 ;Array of maps or TOML Tables + :note "This note addition prompt shows up on typing the `C-c C-z` binding.\nSee [org#Drawers](https://www.gnu.org/software/emacs/manual/html_mono/org.html#Drawers).") + (:timestamp 2018-09-06T11:45:00-04:00 + :note "Another note **bold** _italics_.") + (:timestamp 2018-09-06T11:37:00-04:00 + :note "A note `mono`."))) +#+end_src +*** TOML Output +You will get the below TOML output for either of the above input data: +#+begin_src emacs-lisp :noweb yes :exports results :wrap src toml +(tomelr-encode + <>) +#+end_src + +#+RESULTS: +#+begin_src toml +title = "Some Title" +author = ["fn ln"] +description = """ +some long description +that spans multiple +lines""" +date = 2022-03-14T01:49:00-04:00 +tags = ["tag1", "tag2"] +draft = false +[versions] + emacs = "28.1.50" + org = "release_9.5-532-gf6813d" +[[org_logbook]] + timestamp = 2022-04-08T14:53:00-04:00 + note = """ +This note addition prompt shows up on typing the `C-c C-z` binding. +See [org#Drawers](https://www.gnu.org/software/emacs/manual/html_mono/org.html#Drawers).""" +[[org_logbook]] + timestamp = 2018-09-06T11:45:00-04:00 + note = "Another note **bold** _italics_." +[[org_logbook]] + timestamp = 2018-09-06T11:37:00-04:00 + note = "A note `mono`." +#+end_src +* Limitations +Right now, the scalars and tables/array of tables does not get ordered +in the right order automatically. So the user needs to ensure that the +S-exp has all the scalars in the very beginning and then followed by +TOML tables and arrays of tables. +** Correct Use Example +​:white_check_mark: Put the scalars first and then maps or tables. +#+begin_src emacs-lisp :eval no :noweb-ref scalar-tables-order-correct +'((title . "Hello") ;First the scalar + (img . ((file . "foo.png") ;Then the map or table + (credit . "Bar Zoo")))) +#+end_src +#+begin_src emacs-lisp :noweb yes :exports results :wrap src toml +(tomelr-encode + <>) +#+end_src + +#+RESULTS: +#+begin_src toml +title = "Hello" +[img] + file = "foo.png" + credit = "Bar Zoo" +#+end_src +** Incorrect Use Example +​:x: *Don't do this!*: Map or table first and then scalar. +#+begin_src emacs-lisp :eval no :noweb-ref scalar-tables-order-wrong +'((img . ((file . "foo.png") + (credit . "Bar Zoo"))) + (title . "Hello")) +#+end_src + +*Incorrect order!* Now the ~title~ became part of the ~[img]~ table! + +#+begin_src emacs-lisp :noweb yes :exports results :wrap src toml +(tomelr-encode + <>) +#+end_src + +#+RESULTS: +#+begin_src toml +[img] + file = "foo.png" + credit = "Bar Zoo" +title = "Hello" +#+end_src +* Specification and Conversion Examples +[[https://scripter.co/defining-tomelr/][Companion blog post]] + +Following examples shown how S-expressions get translated to various +TOML object types. +** Scalars +*** DONE Boolean +CLOSED: [2022-04-28 Thu 16:48] +https://toml.io/en/v1.0.0#boolean +**** S-expression +#+begin_src emacs-lisp :eval no :noweb-ref scalar-boolean +'((bool1 . t) + (bool2 . :false)) +#+end_src +**** TOML +#+begin_src emacs-lisp :noweb yes :exports results :wrap src toml +(tomelr-encode + <>) +#+end_src + +#+RESULTS: +#+begin_src toml +bool1 = true +bool2 = false +#+end_src +**** JSON Reference +#+begin_src emacs-lisp :noweb yes :exports results +(json-encode-pretty + <>) +#+end_src + +#+RESULTS: +: { +: "bool1": true, +: "bool2": false +: } +*** DONE Integer +CLOSED: [2022-04-28 Thu 17:11] +https://toml.io/en/v1.0.0#integer +**** S-expression +#+begin_src emacs-lisp :eval no :noweb-ref scalar-integer +'((int1 . +99) + (int2 . 42) + (int3 . 0) + (int4 . -17)) +#+end_src +**** TOML +#+begin_src emacs-lisp :noweb yes :exports results :wrap src toml +(tomelr-encode + <>) +#+end_src + +#+RESULTS: +#+begin_src toml +int1 = 99 +int2 = 42 +int3 = 0 +int4 = -17 +#+end_src +**** JSON Reference +#+begin_src emacs-lisp :noweb yes :exports results +(json-encode-pretty + <>) +#+end_src + +#+RESULTS: +: { +: "int1": 99, +: "int2": 42, +: "int3": 0, +: "int4": -17 +: } +*** DONE Float +CLOSED: [2022-04-28 Thu 17:29] +https://toml.io/en/v1.0.0#float +**** S-expression +#+begin_src emacs-lisp :eval no :noweb-ref scalar-float +'((flt1 . +1.0) + (flt2 . 3.1415) + (flt3 . -0.01) + (flt4 . 5e+22) + (flt5 . 1e06) + (flt6 . -2E-2) + (flt7 . 6.626e-34)) +#+end_src +**** TOML +#+begin_src emacs-lisp :noweb yes :exports results :wrap src toml +(tomelr-encode + <>) +#+end_src + +#+RESULTS: +#+begin_src toml +flt1 = 1.0 +flt2 = 3.1415 +flt3 = -0.01 +flt4 = 5e+22 +flt5 = 1000000.0 +flt6 = -0.02 +flt7 = 6.626e-34 +#+end_src +**** JSON Reference +#+begin_src emacs-lisp :noweb yes :exports results +(json-encode-pretty + <>) +#+end_src + +#+RESULTS: +: { +: "flt1": 1.0, +: "flt2": 3.1415, +: "flt3": -0.01, +: "flt4": 5e+22, +: "flt5": 1000000.0, +: "flt6": -0.02, +: "flt7": 6.626e-34 +: } +*** DONE String +CLOSED: [2022-04-28 Thu 22:10] +https://toml.io/en/v1.0.0#string +**** S-expression +#+begin_src emacs-lisp :eval no :noweb-ref scalar-string +'((str1 . "Roses are red") + (str2 . "Roses are red\nViolets are blue")) +#+end_src +**** TOML +#+begin_src emacs-lisp :noweb yes :exports results :wrap src toml +(tomelr-encode + <>) +#+end_src + +#+RESULTS: +#+begin_src toml +str1 = "Roses are red" +str2 = """ +Roses are red +Violets are blue""" +#+end_src +**** JSON Reference +#+begin_src emacs-lisp :noweb yes :exports results +(json-encode-pretty + <>) +#+end_src + +#+RESULTS: +: { +: "str1": "Roses are red", +: "str2": "Roses are red\nViolets are blue" +: } +*** DONE Date +CLOSED: [2022-04-28 Thu 22:40] +https://toml.io/en/v1.0.0#local-date +**** S-expression +#+begin_src emacs-lisp :eval no :noweb-ref scalar-date +'((ld1 . "1979-05-27")) +#+end_src +**** TOML +#+begin_src emacs-lisp :noweb yes :exports results :wrap src toml +(tomelr-encode + <>) +#+end_src + +#+RESULTS: +#+begin_src toml +ld1 = 1979-05-27 +#+end_src +**** JSON Reference +#+begin_src emacs-lisp :noweb yes :exports results +(json-encode-pretty + <>) +#+end_src + +#+RESULTS: +: { +: "ld1": "1979-05-27" +: } +*** DONE Date + Time with Offset +CLOSED: [2022-04-28 Thu 22:55] +https://toml.io/en/v1.0.0#offset-date-time +**** S-expression +#+begin_src emacs-lisp :eval no :noweb-ref scalar-odt +'((odt1 . "1979-05-27T07:32:00Z") + (odt2 . "1979-05-27T00:32:00-07:00") + (odt3 . "1979-05-27T00:32:00.999999-07:00")) +#+end_src +**** TOML +#+begin_src emacs-lisp :noweb yes :exports results :wrap src toml +(tomelr-encode + <>) +#+end_src + +#+RESULTS: +#+begin_src toml +odt1 = 1979-05-27T07:32:00Z +odt2 = 1979-05-27T00:32:00-07:00 +odt3 = 1979-05-27T00:32:00.999999-07:00 +#+end_src +**** JSON Reference +#+begin_src emacs-lisp :noweb yes :exports results +(json-encode-pretty + <>) +#+end_src + +#+RESULTS: +: { +: "odt1": "1979-05-27T07:32:00Z", +: "odt2": "1979-05-27T00:32:00-07:00", +: "odt3": "1979-05-27T00:32:00.999999-07:00" +: } +** DONE Nil +CLOSED: [2022-04-29 Fri 00:11] +**** S-expression +#+begin_src emacs-lisp :eval no :noweb-ref nil-value +'((key1 . 123) + (key2 . nil) + (key3 . "abc") + (key4 . :false) + (key5 . t)) +#+end_src +**** TOML +#+begin_src emacs-lisp :noweb yes :exports results :wrap src toml +(tomelr-encode + <>) +#+end_src + +#+RESULTS: +#+begin_src toml +key1 = 123 +key3 = "abc" +key4 = false +key5 = true +#+end_src +**** JSON Reference +#+begin_src emacs-lisp :noweb yes :exports results +(json-encode-pretty + <>) +#+end_src + +#+RESULTS: +: { +: "key1": 123, +: "key2": null, +: "key3": "abc", +: "key4": false, +: "key5": true +: } +** TOML Arrays: Lists +https://toml.io/en/v1.0.0#array +*** DONE Plain Arrays +CLOSED: [2022-04-29 Fri 00:25] +**** S-expression +#+begin_src emacs-lisp :eval no :noweb-ref arrays +'((integers . (1 2 3)) + (integers2 . [1 2 3]) ;Same as above + (colors . ("red" "yellow" "green")) + ;; Mixed-type arrays are allowed + (numbers . (0.1 0.2 0.5 1 2 5))) +#+end_src +**** TOML +#+begin_src emacs-lisp :noweb yes :exports results :wrap src toml +(tomelr-encode + <>) +#+end_src + +#+RESULTS: +#+begin_src toml +integers = [1, 2, 3] +integers2 = [1, 2, 3] +colors = ["red", "yellow", "green"] +numbers = [0.1, 0.2, 0.5, 1, 2, 5] +#+end_src +**** JSON Reference +#+begin_src emacs-lisp :noweb yes :exports results +(json-encode-pretty + <>) +#+end_src + +#+RESULTS: +#+begin_example +{ + "integers": [ + 1, + 2, + 3 + ], + "integers2": [ + 1, + 2, + 3 + ], + "colors": [ + "red", + "yellow", + "green" + ], + "numbers": [ + 0.1, + 0.2, + 0.5, + 1, + 2, + 5 + ] +} +#+end_example +*** DONE Array of Arrays +CLOSED: [2022-04-29 Fri 00:34] +**** S-expression +#+begin_src emacs-lisp :eval no :noweb-ref array-of-arrays +'((nested_arrays_of_ints . [(1 2) (3 4 5)]) + (nested_mixed_array . [(1 2) ("a" "b" "c")])) +#+end_src +**** TOML +#+begin_src emacs-lisp :noweb yes :exports results :wrap src toml +(tomelr-encode + <>) +#+end_src + +#+RESULTS: +#+begin_src toml +nested_arrays_of_ints = [[1, 2], [3, 4, 5]] +nested_mixed_array = [[1, 2], ["a", "b", "c"]] +#+end_src +**** JSON Reference +#+begin_src emacs-lisp :noweb yes :exports results +(json-encode-pretty + <>) +#+end_src + +#+RESULTS: +#+begin_example +{ + "nested_arrays_of_ints": [ + [ + 1, + 2 + ], + [ + 3, + 4, + 5 + ] + ], + "nested_mixed_array": [ + [ + 1, + 2 + ], + [ + "a", + "b", + "c" + ] + ] +} +#+end_example +** TOML Tables: Maps or Dictionaries or Hash Tables +https://toml.io/en/v1.0.0#table +*** DONE Basic TOML Tables +CLOSED: [2022-04-29 Fri 13:41] +**** S-expression +#+begin_src emacs-lisp :eval no :noweb-ref tables +'((table-1 . ((key1 . "some string") + (key2 . 123))) + (table-2 . ((key1 . "another string") + (key2 . 456)))) +#+end_src +**** TOML +#+begin_src emacs-lisp :noweb yes :exports results :wrap src toml +(tomelr-encode + <>) +#+end_src + +#+RESULTS: +#+begin_src toml +[table-1] + key1 = "some string" + key2 = 123 +[table-2] + key1 = "another string" + key2 = 456 +#+end_src +**** JSON Reference +#+begin_src emacs-lisp :noweb yes :exports results +(json-encode-pretty + <>) +#+end_src + +#+RESULTS: +#+begin_example +{ + "table-1": { + "key1": "some string", + "key2": 123 + }, + "table-2": { + "key1": "another string", + "key2": 456 + } +} +#+end_example +*** DONE Nested TOML Tables +CLOSED: [2022-04-29 Fri 14:30] +**** S-expression +#+begin_src emacs-lisp :eval no :noweb-ref nested-tables +'((table-1 . ((table-1a . ((key1 . "some string") + (key2 . 123))) + (table-1b . ((key1 . "foo") + (key2 . 98765))))) + (menu . (("auto weight" . ((weight . 4033) + (identifier . "foo")))))) +#+end_src +**** TOML +#+begin_src emacs-lisp :noweb yes :exports results :wrap src toml +(tomelr-encode + <>) +#+end_src + +#+RESULTS: +#+begin_src toml +[table-1] + [table-1.table-1a] + key1 = "some string" + key2 = 123 + [table-1.table-1b] + key1 = "foo" + key2 = 98765 +[menu] + [menu."auto weight"] + weight = 4033 + identifier = "foo" +#+end_src +**** JSON Reference +#+begin_src emacs-lisp :noweb yes :exports results +(json-encode-pretty + <>) +#+end_src + +#+RESULTS: +#+begin_example +{ + "table-1": { + "table-1a": { + "key1": "some string", + "key2": 123 + }, + "table-1b": { + "key1": "foo", + "key2": 98765 + } + }, + "menu": { + "auto weight": { + "weight": 4033, + "identifier": "foo" + } + } +} +#+end_example +** TOML Array of Tables: Lists of Maps +https://toml.io/en/v1.0.0#array-of-tables +*** DONE Basic Array of Tables +CLOSED: [2022-04-29 Fri 18:14] +**** S-expression +#+begin_src emacs-lisp :eval no :noweb-ref table-arrays +'((products . (((name . "Hammer") + (sku . 738594937)) + () + ((name . "Nail") + (sku . 284758393) + (color . "gray")))) + (org_logbook . (((timestamp . 2022-04-08T14:53:00-04:00) + (note . "This note addition prompt shows up on typing the `C-c C-z` binding.\nSee [org#Drawers](https://www.gnu.org/software/emacs/manual/html_mono/org.html#Drawers).")) + ((timestamp . 2018-09-06T11:45:00-04:00) + (note . "Another note **bold** _italics_.")) + ((timestamp . 2018-09-06T11:37:00-04:00) + (note . "A note `mono`."))))) +#+end_src +**** TOML +#+begin_src emacs-lisp :noweb yes :exports results :wrap src toml +(tomelr-encode + <>) +#+end_src + +#+RESULTS: +#+begin_src toml +[[products]] + name = "Hammer" + sku = 738594937 +[[products]] +[[products]] + name = "Nail" + sku = 284758393 + color = "gray" +[[org_logbook]] + timestamp = 2022-04-08T14:53:00-04:00 + note = """ +This note addition prompt shows up on typing the `C-c C-z` binding. +See [org#Drawers](https://www.gnu.org/software/emacs/manual/html_mono/org.html#Drawers).""" +[[org_logbook]] + timestamp = 2018-09-06T11:45:00-04:00 + note = "Another note **bold** _italics_." +[[org_logbook]] + timestamp = 2018-09-06T11:37:00-04:00 + note = "A note `mono`." +#+end_src +**** JSON Reference +#+begin_src emacs-lisp :noweb yes :exports results +(json-encode-pretty + <>) +#+end_src + +#+RESULTS: +#+begin_example +{ + "products": [ + { + "name": "Hammer", + "sku": 738594937 + }, + null, + { + "name": "Nail", + "sku": 284758393, + "color": "gray" + } + ], + "org_logbook": [ + { + "timestamp": "2022-04-08T14:53:00-04:00", + "note": "This note addition prompt shows up on typing the `C-c C-z` binding.\nSee [org#Drawers](https://www.gnu.org/software/emacs/manual/html_mono/org.html#Drawers)." + }, + { + "timestamp": "2018-09-06T11:45:00-04:00", + "note": "Another note **bold** _italics_." + }, + { + "timestamp": "2018-09-06T11:37:00-04:00", + "note": "A note `mono`." + } + ] +} +#+end_example +*** DONE Nested Array of Tables +CLOSED: [2022-04-30 Sat 01:32] +**** S-expression +#+begin_src emacs-lisp :eval no :noweb-ref nested-table-arrays +'((fruits . (((name . "apple") + (physical . ((color . "red") + (shape . "round"))) + (varieties . (((name . "red delicious")) + ((name . "granny smith"))))) + ((name . "banana") + (varieties . (((name . "plantain")))))))) +#+end_src +**** TOML +#+begin_src emacs-lisp :noweb yes :exports results :wrap src toml +(tomelr-encode + <>) +#+end_src + +#+RESULTS: +#+begin_src toml +[[fruits]] + name = "apple" + [fruits.physical] + color = "red" + shape = "round" + [[fruits.varieties]] + name = "red delicious" + [[fruits.varieties]] + name = "granny smith" +[[fruits]] + name = "banana" + [[fruits.varieties]] + name = "plantain" +#+end_src +**** JSON Reference +#+begin_src emacs-lisp :noweb yes :exports results +(json-encode-pretty + <>) +#+end_src + +#+RESULTS: +#+begin_example +{ + "fruits": [ + { + "name": "apple", + "physical": { + "color": "red", + "shape": "round" + }, + "varieties": [ + { + "name": "red delicious" + }, + { + "name": "granny smith" + } + ] + }, + { + "name": "banana", + "varieties": [ + { + "name": "plantain" + } + ] + } + ] +} +#+end_example + +** DONE Combinations of all of the above +CLOSED: [2022-05-02 Mon 10:29] +*** S-expression +#+begin_src emacs-lisp :eval no :noweb-ref medley +'((title . "Keyword Collection") + (author . ("firstname1 lastname1" "firstname2 lastname2" "firstname3 lastname3")) + (aliases . ("/posts/keyword-concatenation" "/posts/keyword-merging")) + (images . ("image 1" "image 2")) + (keywords . ("keyword1" "keyword2" "three word keywords3")) + (outputs . ("html" "json")) + (series . ("series 1" "series 2")) + (tags . ("mega front-matter" "keys" "collection" "concatenation" "merging")) + (categories . ("cat1" "cat2")) + (videos . ("video 1" "video 2")) + (draft . :false) + (categories_weight . 999) + (tags_weight . 88) + (weight . 7) + (myfoo . "bar") + (mybaz . "zoo") + (alpha . 1) + (beta . "two words") + (gamma . 10) + (animals . ("dog" "cat" "penguin" "mountain gorilla")) + (strings-symbols . ("abc" "def" "two words")) + (integers . (123 -5 17 1234)) + (floats . (12.3 -5.0 -1.7e-05)) + (booleans . (t :false)) + (dog . ((legs . 4) + (eyes . 2) + (friends . ("poo" "boo")))) + (header . ((image . "projects/Readingabook.jpg") + (caption . "stay hungry stay foolish"))) + (collection . ((nothing . :false) + (nonnil . t) + (animals . ("dog" "cat" "penguin" "mountain gorilla")) + (strings-symbols . ("abc" "def" "two words")) + (integers . (123 -5 17 1234)) + (floats . (12.3 -5.0 -1.7e-05)) + (booleans . (t :false)))) + (menu . ((foo . ((identifier . "keyword-collection") + (weight . 10))))) + (resources . (((src . "*.png") + (name . "my-cool-image-:counter") + (title . "The Image #:counter") + (params . ((foo . "bar") + (floats . (12.3 -5.0 -1.7e-05)) + (strings-symbols . ("abc" "def" "two words")) + (animals . ("dog" "cat" "penguin" "mountain gorilla")) + (integers . (123 -5 17 1234)) + (booleans . (t :false)) + (byline . "bep")))) + ((src . "image-4.png") + (title . "The Fourth Image")) + ((src . "*.jpg") + (title . "JPEG Image #:counter"))))) +#+end_src +*** TOML +#+begin_src emacs-lisp :noweb yes :exports results :wrap src toml +(tomelr-encode + <>) +#+end_src + +#+RESULTS: +#+begin_src toml +title = "Keyword Collection" +author = ["firstname1 lastname1", "firstname2 lastname2", "firstname3 lastname3"] +aliases = ["/posts/keyword-concatenation", "/posts/keyword-merging"] +images = ["image 1", "image 2"] +keywords = ["keyword1", "keyword2", "three word keywords3"] +outputs = ["html", "json"] +series = ["series 1", "series 2"] +tags = ["mega front-matter", "keys", "collection", "concatenation", "merging"] +categories = ["cat1", "cat2"] +videos = ["video 1", "video 2"] +draft = false +categories_weight = 999 +tags_weight = 88 +weight = 7 +myfoo = "bar" +mybaz = "zoo" +alpha = 1 +beta = "two words" +gamma = 10 +animals = ["dog", "cat", "penguin", "mountain gorilla"] +strings-symbols = ["abc", "def", "two words"] +integers = [123, -5, 17, 1234] +floats = [12.3, -5.0, -1.7e-05] +booleans = [true, false] +[dog] + legs = 4 + eyes = 2 + friends = ["poo", "boo"] +[header] + image = "projects/Readingabook.jpg" + caption = "stay hungry stay foolish" +[collection] + nothing = false + nonnil = true + animals = ["dog", "cat", "penguin", "mountain gorilla"] + strings-symbols = ["abc", "def", "two words"] + integers = [123, -5, 17, 1234] + floats = [12.3, -5.0, -1.7e-05] + booleans = [true, false] +[menu] + [menu.foo] + identifier = "keyword-collection" + weight = 10 +[[resources]] + src = "*.png" + name = "my-cool-image-:counter" + title = "The Image #:counter" + [resources.params] + foo = "bar" + floats = [12.3, -5.0, -1.7e-05] + strings-symbols = ["abc", "def", "two words"] + animals = ["dog", "cat", "penguin", "mountain gorilla"] + integers = [123, -5, 17, 1234] + booleans = [true, false] + byline = "bep" +[[resources]] + src = "image-4.png" + title = "The Fourth Image" +[[resources]] + src = "*.jpg" + title = "JPEG Image #:counter" +#+end_src +*** JSON Reference +#+begin_src emacs-lisp :noweb yes :exports results +(json-encode-pretty + <>) +#+end_src + +#+RESULTS: +#+begin_example +{ + "title": "Keyword Collection", + "author": [ + "firstname1 lastname1", + "firstname2 lastname2", + "firstname3 lastname3" + ], + "aliases": [ + "/posts/keyword-concatenation", + "/posts/keyword-merging" + ], + "images": [ + "image 1", + "image 2" + ], + "keywords": [ + "keyword1", + "keyword2", + "three word keywords3" + ], + "outputs": [ + "html", + "json" + ], + "series": [ + "series 1", + "series 2" + ], + "tags": [ + "mega front-matter", + "keys", + "collection", + "concatenation", + "merging" + ], + "categories": [ + "cat1", + "cat2" + ], + "videos": [ + "video 1", + "video 2" + ], + "draft": false, + "categories_weight": 999, + "tags_weight": 88, + "weight": 7, + "myfoo": "bar", + "mybaz": "zoo", + "alpha": 1, + "beta": "two words", + "gamma": 10, + "animals": [ + "dog", + "cat", + "penguin", + "mountain gorilla" + ], + "strings-symbols": [ + "abc", + "def", + "two words" + ], + "integers": [ + 123, + -5, + 17, + 1234 + ], + "floats": [ + 12.3, + -5.0, + -1.7e-05 + ], + "booleans": [ + true, + false + ], + "dog": { + "legs": 4, + "eyes": 2, + "friends": [ + "poo", + "boo" + ] + }, + "header": { + "image": "projects/Readingabook.jpg", + "caption": "stay hungry stay foolish" + }, + "collection": { + "nothing": false, + "nonnil": true, + "animals": [ + "dog", + "cat", + "penguin", + "mountain gorilla" + ], + "strings-symbols": [ + "abc", + "def", + "two words" + ], + "integers": [ + 123, + -5, + 17, + 1234 + ], + "floats": [ + 12.3, + -5.0, + -1.7e-05 + ], + "booleans": [ + true, + false + ] + }, + "menu": { + "foo": { + "identifier": "keyword-collection", + "weight": 10 + } + }, + "resources": [ + { + "src": "*.png", + "name": "my-cool-image-:counter", + "title": "The Image #:counter", + "params": { + "foo": "bar", + "floats": [ + 12.3, + -5.0, + -1.7e-05 + ], + "strings-symbols": [ + "abc", + "def", + "two words" + ], + "animals": [ + "dog", + "cat", + "penguin", + "mountain gorilla" + ], + "integers": [ + 123, + -5, + 17, + 1234 + ], + "booleans": [ + true, + false + ], + "byline": "bep" + } + }, + { + "src": "image-4.png", + "title": "The Fourth Image" + }, + { + "src": "*.jpg", + "title": "JPEG Image #:counter" + } + ] +} +#+end_example +** DONE P-lists +CLOSED: [2022-04-30 Sat 01:55] +**** S-expression +#+begin_src emacs-lisp :eval no :noweb-ref p-list +'(:int 123 + :remove_this_key nil + :str "abc" + :bool_false :false + :bool_true t + :int_list (1 2 3) + :str_list ("a" "b" "c") + :bool_list (t :false t :false) + :list_of_lists [(1 2) (3 4 5)] + :map (:key1 123 + :key2 "xyz") + :list_of_maps [(:key1 123 + :key2 "xyz") + (:key1 567 + :key2 "klm")]) +#+end_src +**** TOML +#+begin_src emacs-lisp :noweb yes :exports results :wrap src toml +(tomelr-encode + <>) +#+end_src + +#+RESULTS: +#+begin_src toml +int = 123 +str = "abc" +bool_false = false +bool_true = true +int_list = [1, 2, 3] +str_list = ["a", "b", "c"] +bool_list = [true, false, true, false] +list_of_lists = [[1, 2], [3, 4, 5]] +[map] + key1 = 123 + key2 = "xyz" +[[list_of_maps]] + key1 = 123 + key2 = "xyz" +[[list_of_maps]] + key1 = 567 + key2 = "klm" +#+end_src +**** JSON Reference +#+begin_src emacs-lisp :noweb yes :exports results +(json-encode-pretty + <>) +#+end_src + +#+RESULTS: +#+begin_example +{ + "int": 123, + "remove_this_key": null, + "str": "abc", + "bool_false": false, + "bool_true": true, + "int_list": [ + 1, + 2, + 3 + ], + "str_list": [ + "a", + "b", + "c" + ], + "bool_list": [ + true, + false, + true, + false + ], + "list_of_lists": [ + [ + 1, + 2 + ], + [ + 3, + 4, + 5 + ] + ], + "map": { + "key1": 123, + "key2": "xyz" + }, + "list_of_maps": [ + { + "key1": 123, + "key2": "xyz" + }, + { + "key1": 567, + "key2": "klm" + } + ] +} +#+end_example + +* Development +** Running Tests +*** Run all tests +#+begin_src shell +make test +#+end_src +*** Run tests matching a specific string +Run ~make test MATCH=~. For example, to run all tests where +the name matches "scalar" completely or partially, run: + +#+begin_src shell +make test MATCH=scalar +#+end_src +* Credit +This library started off by extracting the JSON Encoding pieces from +the Emacs core library [[https://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/json.el][*json.el*]]. + +It was then refactored to meet the specification defined below. +* COMMENT Helper function +** JSON Reference pretty print string +The ~json-encode-pretty~ function defined here is used to pretty-print +the above JSON examples. + +#+begin_src emacs-lisp :results none +(defun json-encode-pretty (object) + "Return prettified JSONified version of OBJECT." + (with-temp-buffer + (let ((json-false :false) + (json-encoding-pretty-print t)) + (json-encode object)))) +#+end_src +* References +- [[https://toml.io/en/v1.0.0/][TOML v1.0.0 Spec]] +- [[https://toolkit.site/format.html][Online JSON/TOML/YAML converter]] +* COMMENT Local Variables :ARCHIVE: +# Local Variables: +# eval: (setq-local org-fold-core-style 'overlays) +# End: diff --git a/org/elpa/tomelr-0.4.3/tomelr-autoloads.el b/org/elpa/tomelr-0.4.3/tomelr-autoloads.el new file mode 100644 index 0000000..bb1c19a --- /dev/null +++ b/org/elpa/tomelr-0.4.3/tomelr-autoloads.el @@ -0,0 +1,26 @@ +;;; tomelr-autoloads.el --- automatically extracted autoloads -*- lexical-binding: t -*- +;; +;;; Code: + +(add-to-list 'load-path (directory-file-name + (or (file-name-directory #$) (car load-path)))) + + +;;;### (autoloads nil "tomelr" "tomelr.el" (0 0 0 0)) +;;; Generated autoloads from tomelr.el + +(register-definition-prefixes "tomelr" '("tomelr-")) + +;;;*** + +;;;### (autoloads nil nil ("tomelr-pkg.el") (0 0 0 0)) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; coding: utf-8 +;; End: +;;; tomelr-autoloads.el ends here diff --git a/org/elpa/tomelr-0.4.3/tomelr-pkg.el b/org/elpa/tomelr-0.4.3/tomelr-pkg.el new file mode 100644 index 0000000..b50dce6 --- /dev/null +++ b/org/elpa/tomelr-0.4.3/tomelr-pkg.el @@ -0,0 +1,2 @@ +;; Generated package description from tomelr.el -*- no-byte-compile: t -*- +(define-package "tomelr" "0.4.3" "Convert S-expressions to TOML" '((emacs "26.3") (map "3.2.1") (seq "2.23")) :commit "670e0a08f625175fd80137cf69e799619bf8a381" :authors '(("Kaushal Modi" . "kaushal.modi@gmail.com")) :maintainer '("Kaushal Modi" . "kaushal.modi@gmail.com") :keywords '("data" "tools" "toml" "serialization" "config") :url "https://github.com/kaushalmodi/tomelr/") diff --git a/org/elpa/tomelr-0.4.3/tomelr.el b/org/elpa/tomelr-0.4.3/tomelr.el new file mode 100644 index 0000000..e4abd4c --- /dev/null +++ b/org/elpa/tomelr-0.4.3/tomelr.el @@ -0,0 +1,487 @@ +;;; tomelr.el --- Convert S-expressions to TOML -*- lexical-binding: t -*- + +;; Copyright (C) 2022 Free Software Foundation, Inc. + +;; Author: Kaushal Modi +;; Version: 0.4.3 +;; Package-Requires: ((emacs "26.3") (map "3.2.1") (seq "2.23")) +;; Keywords: data, tools, toml, serialization, config +;; URL: https://github.com/kaushalmodi/tomelr/ + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; tomelr.el is a library for converting Lisp data expressions or +;; S-expressions to TOML format (https://toml.io/en/). + +;; It has one entry point `tomelr-encode' which accepts a Lisp data +;; expression, usually in an alist or plist form, and return a string +;; representing the TOML serializaitno format. + +;; Example using an alist as input: +;; +;; (tomelr-encode '((title . "My title") +;; (author . "Me") +;; (params . ((foo . 123))))) +;; +;; Output: +;; +;; title = "My title" +;; author = "Me" +;; [params] +;; foo = 123 + +;; Example using an plist as input: +;; +;; (tomelr-encode '(:title "My title" +;; :author "Me" +;; :params (:foo 123))) +;; +;; Above snippet will give as the same TOML output shown above. + +;; See the README.org on https://github.com/kaushalmodi/tomelr/ for +;; more examples and package details. + +;;; Code: + +(require 'json) +(require 'map) +(require 'subr-x) ;For `string-trim' on Emacs versions 27.2 and older + + +;;; Variables + +(defvar tomelr-false '(:false 'false) + "S-exp values to be interpreted as TOML `false'.") + +(defvar tomelr-encoding-default-indentation " " + "String used for a single indentation level during encoding. +This value is repeated for each further nested element.") + +(defvar tomelr-coerce-to-types '(boolean integer) + "List of TOML types to which the TOML strings will be attempted to be coerced. + +Valid symbols that can be present in this list: boolean, integer, float + +For example, if this list contains `boolean' and if a string +value is exactly \"true\", it will coerce to TOML boolean +`true'.") + +(defvar tomelr-indent-multi-line-strings nil + "Indent the multi-line TOML strings when non-nil. + +This option injects spaces after each newline to present the +multi-line strings in a more readable format. + +*Note: This option should be set to non-nil only if the TOML +string data is insensitive to horizontal space. Good examples of +this would be Org, Markdown or HTML strings.") + +;;;; Internal Variables +(defvar tomelr--print-indentation-prefix "\n" + "String used to start indentation during encoding.") + +(defvar tomelr--print-indentation-depth -1 + "Current indentation level during encoding. +Dictates repetitions of `tomelr-encoding-default-indentation'.") + +(defvar tomelr--print-table-hierarchy () + "Internal variable used to save TOML Table hierarchies. +This variable is used for both TOML Tables and Arrays of TOML +Tables.") + +(defvar tomelr--print-keyval-separator " = " + "String used to separate key-value pairs during encoding.") + +(defvar tomelr--date-time-regexp + (concat "\\`[[:digit:]]\\{4\\}-[[:digit:]]\\{2\\}-[[:digit:]]\\{2\\}" + "\\(?:[T ][[:digit:]]\\{2\\}:[[:digit:]]\\{2\\}:[[:digit:]]\\{2\\}\\(?:\\.[[:digit:]]+\\)*" + "\\(?:Z\\|[+-][[:digit:]]\\{2\\}:[[:digit:]]\\{2\\}\\)*\\)*\\'") + "Regexp to match RFC 3339 formatted date-time with offset. + +- https://toml.io/en/v1.0.0#offset-date-time +- https://tools.ietf.org/html/rfc3339#section-5.8 + +Examples: + 1979-05-27 + 1979-05-27T07:32:00Z + 1979-05-27 07:32:00Z + 1979-05-27T00:32:00-07:00 + 1979-05-27T00:32:00.999999+04:00.") + + + +;;; Error conditions + +(define-error 'tomelr-error "Unknown TOML error") +(define-error 'tomelr-key-format "Bad TOML object key" 'tomelr-error) + + + +;;; Utilities + +(defmacro tomelr--with-output-to-string (&rest body) + "Eval BODY in a temporary buffer bound to `standard-output'. +Return the resulting buffer contents as a string." + (declare (indent 0) (debug t)) + `(with-output-to-string + (with-current-buffer standard-output + ;; This affords decent performance gains. + (setq-local inhibit-modification-hooks t) + ,@body))) + +(defmacro tomelr--with-indentation (&rest body) + "Eval BODY with the TOML encoding nesting incremented by one step. +This macro sets up appropriate variable bindings for +`tomelr--print-indentation' to produce the correct indentation." + (declare (debug t) (indent 0)) + `(let ((tomelr--print-indentation-depth (1+ tomelr--print-indentation-depth))) + ,@body)) + +(defun tomelr--print-indentation () + "Insert the current indentation for TOML encoding at point." + (insert tomelr--print-indentation-prefix) + (dotimes (_ tomelr--print-indentation-depth) + (insert tomelr-encoding-default-indentation))) + + + +;;; Encoding + +;;;; Booleans +(defun tomelr--print-boolean (object) + "Insert TOML boolean true or false at point if OBJECT is a boolean. +Return nil if OBJECT is not recognized as a TOML boolean." + (prog1 (setq object (cond ((or + (eq object t) + (and (member 'boolean tomelr-coerce-to-types) + (member object '("true" true)))) + "true") + ((or + (member object tomelr-false) + (and (member 'boolean tomelr-coerce-to-types) + (member object '("false" false)))) + "false"))) + (and object (insert object)))) + +;;;; Strings +(defun tomelr--print-string (string) + "Insert a TOML representation of STRING at point. + +Return the same STRING passed as input." + ;; (message "[tomelr--print-string DBG] string = `%s'" string) + (let ((special-chars '((?b . ?\b) ;U+0008 + (?f . ?\f) ;U+000C + (?\\ . ?\\))) + (special-chars-re (rx (in ?\" ?\\ cntrl ?\u007F))) ;cntrl is same as (?\u0000 . ?\u001F) + ;; Use multi-line string quotation if the string contains a " + ;; char or a newline - """STRING""". + (multi-line (string-match-p "\n\\|\"" string)) + begin-q end-q) + + (cond + (multi-line + ;; From https://toml.io/en/v1.0.0#string, Any Unicode + ;; character may be used except those that must be escaped: + ;; backslash and the control characters other than tab, line + ;; feed, and carriage return (U+0000 to U+0008, U+000B, + ;; U+000C, U+000E to U+001F, U+007F). + (setq special-chars-re (rx (in ?\\ + (?\u0000 . ?\u0008) + ?\u000B ?\u000C + (?\u000E . ?\u001F) + ?\u007F))) + + (setq begin-q "\"\"\"\n") + (setq end-q "\"\"\"") + (when tomelr-indent-multi-line-strings + (let (;; Fix the indentation of multi-line strings to 2 + ;; spaces. If the indentation is increased to 4 or more + ;; spaces, those strings will get parsed as code blocks + ;; by Markdown parsers. + (indentation " ")) + (setq string + (concat + indentation ;Indent the first line in the multi-line string + (replace-regexp-in-string + "\\(\n\\)\\([^\n]\\)" ;Don't indent blank lines + (format "\\1%s\\2" indentation) + string) + "\n" indentation ;Indent the closing """ at the end of the multi-line string + ))))) + (t ;Basic quotation "STRING" + (push '(?\" . ?\") special-chars) + (push '(?t . ?\t) special-chars) ;U+0009 + (push '(?n . ?\n) special-chars) ;U+000A + (push '(?r . ?\r) special-chars) ;U+000D + (setq begin-q "\"") + (setq end-q begin-q))) + + (and begin-q (insert begin-q)) + (goto-char (prog1 (point) (princ string))) + (while (re-search-forward special-chars-re nil :noerror) + (let ((char (preceding-char))) + (delete-char -1) + (insert ?\\ (or + ;; Escape special characters + (car (rassq char special-chars)) + ;; Fallback: UCS code point in \uNNNN form. + (format "u%04x" char))))) + (and end-q (insert end-q)) + string)) + +(defun tomelr--print-stringlike (object &optional key-type) + "Insert OBJECT encoded as a TOML string at point. + +Possible values of KEY-TYPE are `normal-key', `table-key', +`table-array-key', or nil. + +Return nil if OBJECT cannot be encoded as a TOML string." + ;; (message "[tomelr--print-stringlike DBG] object = %S (type = %S) key type = %S" + ;; object (type-of object) key-type) + (let ((str (cond ;; Object is a normal, TT or TTA key + (key-type + (cond + ((stringp object) + (if (string-match-p "\\`[A-Za-z0-9_-]+\\'" object) + ;; https://toml.io/en/v1.0.0#keys + ;; Bare keys may only contain ASCII letters, ASCII digits, + ;; underscores, and dashes (A-Za-z0-9_-). + object + ;; Wrap string in double-quotes if it + ;; doesn't contain only A-Za-z0-9_- chars. + (format "\"%s\"" object))) + ;; Plist keys as in (:foo 123) + ((keywordp object) + (string-trim-left (symbol-name object) ":")) + ;; Alist keys as in ((foo . 123)) + ((symbolp object) + (symbol-name object)) + (t + (user-error "[tomelr--print-stringlike] Unhandled case of key-type")))) + + ;; Cases where object is a key value. + ((symbolp object) + (symbol-name object)) + ((stringp object) + object)))) + ;; (message "[tomelr--print-stringlike DBG] str = %S" str) + (when (member key-type '(table-key table-array-key)) + ;; (message "[tomelr--print-stringlike DBG] %S is symbol, type = %S, depth = %d" + ;; object key-type tomelr--print-indentation-depth) + (if (null (nth tomelr--print-indentation-depth tomelr--print-table-hierarchy)) + (setq tomelr--print-table-hierarchy + (append tomelr--print-table-hierarchy (list str))) + + ;; Throw away table keys collected at higher depths, if + ;; any, from earlier runs of this function. + (setq tomelr--print-table-hierarchy + (seq-take tomelr--print-table-hierarchy (1+ tomelr--print-indentation-depth))) + (setf (nth tomelr--print-indentation-depth tomelr--print-table-hierarchy) str)) + ;; (message "[tomelr--print-stringlike DBG] table hier: %S" tomelr--print-table-hierarchy) + ) + (cond + ;; TT keys + ((equal key-type 'table-key) + (princ (format "[%s]" (string-join tomelr--print-table-hierarchy ".")))) + ;; TTA keys + ((equal key-type 'table-array-key) + (princ (format "[[%s]]" (string-join tomelr--print-table-hierarchy ".")))) + ;; Normal keys (Alist and Plist keys) + ((equal key-type 'normal-key) + (princ str)) + (str + (cond + ((or + ;; RFC 3339 Date/Time + (string-match-p tomelr--date-time-regexp str) + + ;; Coercing + ;; Integer that can be stored in the system as a fixnum. + ;; For example, if `object' 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 object)) 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 (or (symbolp object) + (member 'integer tomelr-coerce-to-types)) + (string-match-p "\\`[+-]?[[:digit:]_]+\\'" str) + (if (functionp #'fixnump) ;`fixnump' and `bignump' get introduced in Emacs 27.x + (fixnump (string-to-number str)) + ;; On older Emacsen, `integerp' behaved the same as the + ;; new `fixnump'. + (integerp (string-to-number str))))) + (princ str)) + (t + (tomelr--print-string str)))) + (t + nil)))) + +(defun tomelr--print-key (key &optional key-type) + "Insert a TOML key representation of KEY at point. + +KEY-TYPE represents the type of key: `normal-key', `table-key' or +`table-array-key'. + +Signal `tomelr-key-format' if it cannot be encoded as a string." + (or (tomelr--print-stringlike key key-type) + (signal 'tomelr-key-format (list key)))) + +;;;; Objects +;; `tomelr-alist-p' is a slightly modified version of `json-alist-p'. +;; It fixes this scenario: (json-alist-p '((:a 1))) return t, which is wrong. +;; '((:a 1)) is an array of plist format maps, and not an alist. +;; (tomelr-alist-p '((:a 1))) returns nil as expected. +(defun tomelr-alist-p (list) + "Non-nil if and only if LIST is an alist with simple keys." + (declare (pure t) (side-effect-free error-free)) + (while (and (consp (car-safe list)) + (not (json-plist-p (car-safe list))) + (atom (caar list))) + ;; (message "[tomelr-alist-p DBG] INSIDE list = %S, car = %S, caar = %S, atom of caar = %S" + ;; list (car-safe list) (caar list) (atom (caar list))) + (setq list (cdr list))) + ;; (message "[tomelr-alist-p DBG] out 2 list = %S, is alist? %S" list (null list)) + (null list)) + +(defun tomelr-toml-table-p (object) + "Return non-nil if OBJECT can represent a TOML Table. + +Recognize both alist and plist format maps as TOML Tables. + +Examples: + +- Alist format: \\='((a . 1) (b . \"foo\")) +- Plist format: \\='(:a 1 :b \"foo\")" + (or (tomelr-alist-p object) + (json-plist-p object))) + +(defun tomelr--print-pair (key val) + "Insert TOML representation of KEY - VAL pair at point." + (let ((key-type (cond + ((tomelr-toml-table-p val) 'table-key) + ((tomelr-toml-table-array-p val) 'table-array-key) + (t 'normal-key)))) + ;; (message "[tomelr--print-pair DBG] key = %S, val = %S, key-type = %S" + ;; key val key-type) + (when val ;Don't print the key if val is nil + (tomelr--print-indentation) ;Newline before each key in a key-value pair + (tomelr--print-key key key-type) + ;; Skip putting the separator for table and table array keys. + (unless (member key-type '(table-key table-array-key)) + (insert tomelr--print-keyval-separator)) + (tomelr--print val)))) + +(defun tomelr--print-map (map) + "Insert a TOML representation of MAP at point. +This works for any MAP satisfying `mapp'." + ;; (message "[tomelr--print-map DBG] map = %S" map) + (unless (map-empty-p map) + (tomelr--with-indentation + (map-do #'tomelr--print-pair map)))) + +;;;; Lists (including alists and plists) +(defun tomelr--print-list (list) + "Insert a TOML representation of LIST at point." + (cond ((tomelr-toml-table-p list) + (tomelr--print-map list)) + ((listp list) + (tomelr--print-array list)) + ((signal 'tomelr-error (list list))))) + +;;;; Arrays +(defun tomelr-toml-table-array-p (object) + "Return non-nil if OBJECT can represent a TOML Table Array. + +Definition of a TOML Table Array (TTA): + +- OBJECT is TTA if it is of type ((TT1) (TT2) ..) where each element is a + TOML Table (TT)." + (when (or (listp object) + (vectorp object)) + (seq-every-p + (lambda (elem) (tomelr-toml-table-p elem)) + object))) + +(defun tomelr--print-tta-key () + "Print TOML Table Array key." + ;; (message "[tomelr--print-array DBG] depth = %d" tomelr--print-indentation-depth) + ;; Throw away table keys collected at higher depths, if + ;; any, from earlier runs of this function. + (setq tomelr--print-table-hierarchy + (seq-take tomelr--print-table-hierarchy (1+ tomelr--print-indentation-depth))) + + (tomelr--print-indentation) + (insert + (format "[[%s]]" (string-join tomelr--print-table-hierarchy ".")))) + +(defun tomelr--print-array (array) + "Insert a TOML representation of ARRAY at point." + ;; (message "[tomelr--print-array DBG] array = %S, TTA = %S" + ;; array (tomelr-toml-table-array-p array)) + (cond + ((tomelr-toml-table-array-p array) + (unless (= 0 (length array)) + (let ((first t)) + (mapc (lambda (elt) + (if first + (setq first nil) + (tomelr--print-tta-key)) + (tomelr--print elt)) + array)))) + (t + (insert "[") + (unless (= 0 (length array)) + (tomelr--with-indentation + (let ((first t)) + (mapc (lambda (elt) + (if first + (setq first nil) + (insert ", ")) + (tomelr--print elt)) + array)))) + (insert "]")))) + +;;;; Print wrapper +(defun tomelr--print (object) + "Insert a TOML representation of OBJECT at point. +See `tomelr-encode' that returns the same as a string." + (cond ((tomelr--print-boolean object)) + ((listp object) (tomelr--print-list object)) + ((tomelr--print-stringlike object)) + ((numberp object) (prin1 object)) + ((arrayp object) (tomelr--print-array object)) + ((signal 'tomelr-error (list object))))) + + + +;;; User API +(defun tomelr-encode (object) + "Return a TOML representation of OBJECT as a string. +If an error is detected during encoding, an error based on +`tomelr-error' is signaled." + (setq tomelr--print-table-hierarchy ()) + (string-trim + (tomelr--with-output-to-string (tomelr--print object)))) + + +(provide 'tomelr) + +;;; tomelr.el ends here diff --git a/org/init.el b/org/init.el index 45f2319..375e690 100644 --- a/org/init.el +++ b/org/init.el @@ -63,7 +63,7 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; additional packages (add-to-list 'package-selected-packages - '(org-super-agenda) + '(ox-hugo org-super-agenda) ) @@ -92,6 +92,10 @@ (setq org-support-shift-select t) (setq org-src-fontify-natively t) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; ox-hugo for blog +(with-eval-after-load 'ox (require 'ox-hugo)) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; tags (load "~/.emacs.d.profiles/org/config-org-tags")