ox-hugo
is an Emacs package to export Org files to Hugo-compatible
Markdown files. At the moment it supports only the Blackfriday
Markdown flavor.
- HUGO_SECTION
- The default Hugo section name for all the posts. See
here for more information on Hugo sections. It is
common for this property to be set to
posts
orblog
. - HUGO_BASE_DIR
- Root directory of the source for the Hugo site.
If this is set to
~/hugo/
, the exported Markdown files will be saved to~/hugo/content/<HUGO_SECTION>/
directory. By default, the Markdown files reside in a hierarchy under thecontent/
directory in the site root directory.
- Use CLOSED log drawer info if available to set the date in front-matter {{{issue(68)}}}.
- Code optimization: Use of
org-entry-get
at places instead of maintaining global variables.
- Make DateTime matching better; new internal variable
org-hugo--date-time-regexp
. Earlier time zones ahead of UTC (with+
sign) were not detected as dates inorg-hugo--quote-string
and thus were unnecessarily quoted.
So you will need to assign your copyright to FSF in order to get your patches accepted.
As a bonus, once you have assigned your copyright to FSF, doors open up for your future contributions to Emacs too!
If theox-hugo
exports do not work as expected, or if you get an
error backtrace,
- Open an Issue.
- Describe the problem you are seeing.
- Provide the debug info using
org-hugo-debug-info
:M-x org-hugo-debug-info
(that will copy the debug info in Markdown format to the kill ring)- Paste the Markdown contents in the GitHub issue.
- You can still hit the Preview tab of the Issue before submitting it.
ox-hugo
is an Org exporter backend that exports Org to
Hugo-compatible Markdown (Blackfriday) and also generates the
front-matter (in TOML or YAML format).
This project consists of ox-blackfriday.el
too. It is a derivation
of =ox-gfm= with support added for Blackfriday Markdown tables and
many other tweaks. ox-hugo
backend extends from this.
There are 2 major blogging flows that can be used with this package:
- One post per Org subtree (preferred)
- Export only the current post Org subtree, or
- Export all valid Hugo post subtrees in a loop.
- One post per Org file
- This works but you won’t be able to leverage Org-specific
benefits like tag and property inheritance, use of TODO states to
translate to post
draft
state, auto weight calculation for posts and menu items, etc.
- This works but you won’t be able to leverage Org-specific
benefits like tag and property inheritance, use of TODO states to
translate to post
See the Org Capture Setup Wiki page to see how to quickly create new posts.
See the Auto-export on Saving Wiki page to learn how to setup up
seeing live-preview of the Hugo-rendered HTML each time you do C-x
C-s
in the Org file — For now, this works only with the Subtree
export flow.
Before you read further, you can see below how ox-hugo
translates
Org to Markdown (Org on the left; exported Markdown with Hugo
front-matter on the right).
One post per Org subtree –
One post per Org file –
Using Org just as a markup like Markdown is a miniscule part of its complete feature-set. Org also allows stuff like:- Easy ordering/manipulation/commenting of subtrees
- Creating tables (with even formulas like in Excel)
- Directly including source code snippets from external files (instead of having to copy/paste them in)
- Running code snippets within the Org file and embedding the results (Org Babel)
- ..
Using Org for content writing allows using in-built Org features to translate to Hugo front-matter:
- Org uses an outline structure and can inherit meta data (tags and properties) from one subtree to children subtrees.
- Using that feature, one can tag one tree as emacs, and everything under that tree (all posts under that) will get that tag automatically.
- The same concept applies to inheriting any Org property meta data like menu entry, category, section name, etc.
- A subtree can be quickly marked to be in TODO state (default binding
C-c C-t
). A TODO post is marked as a draft Hugo post. - The menu-item weights and/or post weights can be set to be
auto-calculated so that the menu items or post order in the final
HTML appear in the same order as the respective subtrees in Org.
If the subtrees are re-ordered in Org, the weights get changed too.
- One can have a subtree with section property set to “posts” and all post subtrees under that will go to that section. Similarly another parent subtree can have that property set to “articles”, and so on.
- Images can be displayed inline in the Org buffer.
- After save hooks can be set up in Emacs so that each time I save the
file, only the current subtree in Org gets exported to
Markdown. With the Hugo server running with the new switch that auto
changes the preview to the last changed post (
--navigateToChanged
introduced in Hugo 0.25), the flow is seamless – Save the Org file and see the exact changed post in browser. - All posts can simply be subtrees in a single Org file. That way
one can take advantage of Org subtree filtering and searching
functions (
org-sparse-tree
bound toC-c /
by default). - (and much more..)
ox-hugo
export options are available in the Org Export Dispatcher
menu (the one you see when you hit C-c C-e
to initiate any export).
You can do that by adding the below to your config:
(with-eval-after-load 'ox
(require 'ox-hugo))
If you use use-package
, you can do the below instead:
(use-package ox-hugo
:after ox)
Before you export check that these properties are set as you need:
- HUGO_SECTION
- The default Hugo section name for all the posts. See
here for more information on Hugo sections. It is
common for this property to be set to
posts
orblog
. The default value is set usingorg-hugo-default-section-directory
. - HUGO_BASE_DIR
- Root directory of the source for the Hugo site. If
this is set to
~/hugo/
, the exported Markdown files will be saved to~/hugo/content/<HUGO_SECTION>/
directory. By default, the Markdown files reside in a hierarchy under thecontent/
directory in the site root directory (ref). If you try to export without setting this property, you will get this error:user-error: It is mandatory to set the HUGO_BASE_DIR property
Important: If you choose to export an Org subtree as a post, you
need to set the EXPORT_FILE_NAME
subtree property. That property is
used by this package to figure out where the current post starts.
The common ox-hugo
export bindings are:
Binding | Description |
---|---|
C-c C-e H H | Export only the current valid subtree (has the EXPORT_FILE_NAME property set) |
C-c C-e H A | Export all valid subtrees (those that have the EXPORT_FILE_NAME property set) |
C-c C-e H h | Export the whole Org file to a single post |
hugo
.
Check out the example single Org file. That is created for testing various Org->Hugo content and meta-data translation features. Here are the exported Markdown files.
- Clone this repo.
cd
to theexample-site/
directory and do:hugo server -D --navigateToChanged
--navigateToChanged
requires Hugo 0.25+.
- Above command will mention the localhost where the site is served. Open that in your browser.
- In emacs,
(require 'ox-hugo)
or evaluate theox-hugo.el
from the cloned repo. - Open the =all-posts.org= file.
C-c C-e H A
– That will export all subtrees in the file to Markdown files.- In few seconds, dozens of test posts will get created, with the
hugo server
aided preview in the browser zapping through each new created post (needs that new feature--navigateToChanged
introduced in Hugo 0.25).
cd
to your Hugo site base directory – the one that contains theconfig.toml
(orconfig.yaml
orconfig.json
).- Start the
hugo server
in that directory:hugo server -D --navigateToChanged
--navigateToChanged
requires Hugo 0.25+.
- Above command will mention the localhost where the site is served. Open that in your browser.
- Create a separate directory for Org content in the Hugo site base
directory. You can name it anything, but I prefer to name it
content-org
(Example 1 –ox-hugo
example site, Example 2 – My blog). - Create an Org file in there and follow the Usage section in the README or Wiki to export it.
When organizing the posts as Org subtrees, many Hugo front-matter variables get set implicitly using the meta-data parsed from the posts in Org.
Below, where subtree is mentioned, it implies a valid Hugo-post
subtree i.e. an Org subtree that has the EXPORT_FILE_NAME
property
set.
Hugo front-matter (TOML) | Org | Org description |
---|---|---|
title = "foo" | * foo | Subtree heading |
date = 2017-09-11T14:32:00-04:00 | CLOSED: [2017-09-11 Mon 14:32] | Auto-inserted CLOSED subtree property when switch to Org DONE state |
date = 2017-07-24 | :EXPORT_DATE: 2017-07-24 | Subtree property |
lastmod = <current date> | :EXPORT_HUGO_AUTO_SET_LASTMOD: t | Subtree property |
lastmod = <current date> | #+HUGO_AUTO_SET_LASTMOD: t | Org keyword |
tags = ["abc", "def"] | * foo :abc:def: | Subtree heading tags |
categories = ["x", "y"] | * foo :@x:@y: | Subtree heading tags with @ prefix |
draft = true | * TODO foo | Subtree heading Org Todo state set to TODO (or DRAFT ) |
draft = false | * foo | Subtree heading Org Todo state not set to TODO (or DRAFT ) |
weight = 123 | :EXPORT_HUGO_WEIGHT: auto | When set to auto , weight is auto-calculated |
weight = 123 (in [menu.foo] ) | :EXPORT_HUGO_MENU: :menu foo | Menu weight is auto-calculated unless specified |
- Precedence for
date
parsing:CLOSED
subtree property more thanEXPORT_DATE
subtree property more than#+DATE:
keyword.
Hugo front-matter (TOML) | Org |
---|---|
title = "foo" | #+TITLE: foo |
date = 2017-07-24 | #+DATE: 2017-07-24 |
lastmod = <current date> | #+HUGO_AUTO_SET_LASTMOD: t |
tags = ["abc", "def"] | #+HUGO_TAGS: abc def |
categories = ["x", "y"] | #+HUGO_CATEGORIES: x y |
draft = true | #+HUGO_DRAFT: true |
draft = false | #+HUGO_DRAFT: false (default) |
weight = 123 | #+HUGO_WEIGHT: 123 |
weight = 123 (in [menu.foo] ) | #+HUGO_MENU: :menu foo :weight 123 |
- The auto weight calculation for posts and menu items works only for subtree exports. For the complete-file export flow, one needs to specify the weights manually if needed.
.md
files.
See the Org source in =all-posts.org= under Formatting -> General heading and how it exports to Markdown in =general-formatting.md=.
Org | Markdown |
---|---|
*bold* | **bold** |
/italics/ | _italics_ |
==monospace== | `monospace` |
~key-binding~ | <kbd>key-binding</kbd> |
- if org-hugo-use-code-for-kbd is non-nil [default] | |
- Requires CSS to render the <kbd> tag as something special. | |
~key-binding~ | `key-binding` |
- if org-hugo-use-code-for-kbd is nil | |
+strike-through+ | ~~strike-through~~ |
_underline_ | <span class = "underline">underline</span> |
- Requires CSS to render this underline class as an underline. |
(Note: If you see two equal signs on each side of monospace in the Org column in the table above, it is a bug with GitHub’s Org renderer.. just see those as single equal signs on each side of monospace instead.)
If you are considering to try outox-hugo
, and if you have already
been using Hugo, it is normal for this thought to cross your mind:
I already have dozens or hundreds of posts written in Markdown. Do I need to convert them to Org if I want to start using
ox-hugo
?
The answer is No.
This package will export your future posts written in Org to
Markdown. And those files will live along with your already written
Markdown posts. So converting existing Markdown files to Org would be
purely the user’s choice, your choice – but that’s by no means a
necessity if you want to start using ox-hugo
.
.. And if at some point, you want to stop using ox-hugo
, you still
have the exported Markdown files.
There is a plan to create a hugo.el
that would do things like:
- New post creation using
org-capture
(port code from here). - Interactive functions to toggle draft state, add/remove/increment/decrement publishdate property.
- Option to use template
config.toml
and some default hugo theme. So all a new user would need to do is to (i) have thehugo
binary inPATH
(ii) define theirhugo
blog dir in thedefcustom
(iii)M-x hugo
. - Set separate faces for titles based on draft state and futureness.
- Matt Price (@titaniumbones)
- Puneeth Chaganti (@punchagan)
- Also thanks to holgerschurig.de, whyarethingsthewaytheyare.com and the =goorgoeous= project by Chase Adams (@chaseadamsio) for inspiration to start this project.
doc/ox-hugo-manual.org
file. That
includes the README.org
, CONTRIBUTING.org
, and these Wiki pages.
These are generated by running M-x ox-hugo-export-gh-doc
defined in
doc/export-gh-doc.el
. Before running that, you would need to:
cd doc/
git clone https://github.com/kaushalmodi/ox-hugo.wiki.git
If changes are only to this Wiki, I believe you can push them directly
using git
or manually edit those here.
If you are a one Org-file per post type of a person, that flow works
too! Just note that in this flow many of those #+HUGO_
properties
need to be managed manually.. just as one would manage the front-matter
in Markdown files — See the Org versions in the above screenshots for
comparison.
EXPORT_FILE_NAME
and
EXPORT_DATE
for each new post, here is an example org-capture
setup can help:
(with-eval-after-load 'org-capture
(defun org-hugo-new-subtree-post-capture-template ()
"Returns `org-capture' template string for new Hugo post.
See `org-capture-templates' for more information."
(let* (;; http://www.holgerschurig.de/en/emacs-blog-from-org-to-hugo/
(date (format-time-string (org-time-stamp-format :long :inactive) (org-current-time)))
(title (read-from-minibuffer "Post Title: ")) ;Prompt to enter the post title
(fname (org-hugo-slug title)))
(mapconcat #'identity
`(
,(concat "* TODO " title)
":PROPERTIES:"
,(concat ":EXPORT_FILE_NAME: " fname)
,(concat ":EXPORT_DATE: " date) ;Enter current date and time
":END:"
"%?\n") ;Place the cursor here finally
"\n")))
(add-to-list 'org-capture-templates
'("h" ;`org-capture' binding + h
"Hugo post"
entry
;; It is assumed that below file is present in `org-directory'
;; and that it has a "Blog Ideas" heading. It can even be a
;; symlink pointing to the actual location of all-posts.org!
(file+olp "all-posts.org" "Blog Ideas")
(function org-hugo-new-subtree-post-capture-template))))
Well.. you can do that with these steps, though, this works only with subtree-export flow at the moment.
- Add below to the very-end of your posts Org file:
* Footnotes * COMMENT Local Variables :ARCHIVE: # Local Variables: # eval: (add-hook 'after-save-hook #'org-hugo-export-subtree-to-md-after-save :append :local) # End:
Here I recommend adding the
* Footnotes
header too so that in case you add any Org footnotes, they go directly to that section you created. Otherwise, in the absence of an existing Footnotes heading, Org would create a new Footnotes heading at the end of the file – so the Local Variables heading will then no longer be at the end of the file. - Then save the file, and do
revert-buffer
. - You will be prompted to add that
eval
line to your Customize setup, hit!
to permanently save that setting and prevent future prompts.
You might find this step useful if you choose to write new posts using
org-capture
as explained in the Org Capture Setup Wiki page.
After saving the below to your emacs config and evaluating it, auto-exports will be prevented when saving a new post created using Org Capture.
(with-eval-after-load 'org-capture
;; Do not cause auto Org->Hugo export to happen when saving captures
(defun modi/org-capture--remove-auto-org-to-hugo-export-maybe ()
"Function for `org-capture-before-finalize-hook'.
Disable `org-hugo-export-subtree-to-md-after-save'."
(setq org-hugo-allow-export-after-save nil))
(defun modi/org-capture--add-auto-org-to-hugo-export-maybe ()
"Function for `org-capture-after-finalize-hook'.
Enable `org-hugo-export-subtree-to-md-after-save'."
(setq org-hugo-allow-export-after-save t))
(add-hook 'org-capture-before-finalize-hook #'modi/org-capture--remove-auto-org-to-hugo-export-maybe)
(add-hook 'org-capture-after-finalize-hook #'modi/org-capture--add-auto-org-to-hugo-export-maybe))
We start the hugo server
so that we can see the live-preview each
time the Org file is saved.
I recommend using Hugo version 0.25 at the minimum as that added
support for the awesome --navigateToChanged
switch!
Run below in your Hugo site root (the directory that contains the site
config.toml
) to start the server:
hugo server -D --navigateToChanged
By default the site is served locally on port 1313 on localhost. So the above step would have printed something like below at the end:
Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)
So open your favorite browser pointing to that address.
If you are like me, you might not need to repeat steps 3 and 4 above,
as you can leave the hugo
server running in a separate terminal, and
have a browser tab pinned to that localhost.
So with that, have the emacs and browser frames set up side-by-side, and edit your Org post.
Hit C-x C-s
and be in awe as the browser auto-refreshes to the
exact post you modified!