kaushalmodi / ox-hugo

A carefully crafted Org exporter back-end for Hugo
https://ox-hugo.scripter.co
GNU General Public License v3.0
867 stars 130 forks source link

Exporting fails in case of multiple entries with same title #660

Closed mmk2410 closed 2 years ago

mmk2410 commented 2 years ago

Actual Behavior

Given an org file (like the one below) where two or more posts have the same headline (but different EXPORT_FILE_NAME) the exporting process fails with "Heading not unique on level 1: My Blog Post".

I jumped a bit through the Git history and could narrow the introduction of this regression down to commit f9c9aaf0160bb552d438cda20b7627a41dfaf125 (the ones before work, starting with this the error occurs). Sadly I have nearly no knowledge of Emacs Lisp, ox-hugo and/or org (and its API) to further narrow down the faulty code.

Maybe this is also an user error, but I could not find any information about such an breaking change in the changelog for 0.12.1.

Expected Behavior

The exporting process runs without any errors, as done before f9c9aaf0160bb552d438cda20b7627a41dfaf125.

How to Reproduce the Issue

The issue may be reproduced by saving the following example and exporting either just one subtree or all subtrees of the buffer.

Example Org File

#+HUGO_SECTION: blog
#+HUGO_BASE_DIR: ./

* My Blog Post
:PROPERTIES:
:EXPORT_FILE_NAME: my-blog-post-2
:END:

This is an example blog post.

* My Blog Post
:PROPERTIES:
:EXPORT_FILE_NAME: my-blog-post-1
:END:

This is another example blog post.

My personal website provides a "real world" example of this issue with a build in a pristine Emacs environment: https://gitlab.com/mmk2410/mmk2410.org, especially https://gitlab.com/mmk2410/mmk2410.org/-/pipelines.

Generated Markdown File or Error

No markdown files are produced, but the following error is printed out:

Heading not unique on level 1: My Blog Post

Ox-Hugo Debug Information

Debug Info # Debug information for `ox-hugo` ## Emacs Version GNU Emacs 27.2 (build 1, x86_64-redhat-linux-gnu, GTK+ Version 3.24.31, cairo version 1.17.4) of 2022-03-24 ## Org Version Org mode version 9.5.4 (9.5.4-ge0b05b @ /home/marcel/.emacs.d/elpa/org-9.5.4/) ## Hugo Version =hugo= binary not found in PATH ### Org `load-path` shadows **Warning**: Possible mixed installation of Org /home/marcel/.emacs.d/elpa/org-9.5.4/ox hides /usr/share/emacs/27.2/lisp/org/ox /home/marcel/.emacs.d/elpa/org-9.5.4/ox-texinfo hides /usr/share/emacs/27.2/lisp/org/ox-texinfo /home/marcel/.emacs.d/elpa/org-9.5.4/ox-publish hides /usr/share/emacs/27.2/lisp/org/ox-publish /home/marcel/.emacs.d/elpa/org-9.5.4/ox-org hides /usr/share/emacs/27.2/lisp/org/ox-org /home/marcel/.emacs.d/elpa/org-9.5.4/ox-odt hides /usr/share/emacs/27.2/lisp/org/ox-odt /home/marcel/.emacs.d/elpa/org-9.5.4/ox-md hides /usr/share/emacs/27.2/lisp/org/ox-md /home/marcel/.emacs.d/elpa/org-9.5.4/ox-man hides /usr/share/emacs/27.2/lisp/org/ox-man /home/marcel/.emacs.d/elpa/org-9.5.4/ox-latex hides /usr/share/emacs/27.2/lisp/org/ox-latex /home/marcel/.emacs.d/elpa/org-9.5.4/ox-icalendar hides /usr/share/emacs/27.2/lisp/org/ox-icalendar /home/marcel/.emacs.d/elpa/org-9.5.4/ox-html hides /usr/share/emacs/27.2/lisp/org/ox-html /home/marcel/.emacs.d/elpa/org-9.5.4/ox-beamer hides /usr/share/emacs/27.2/lisp/org/ox-beamer /home/marcel/.emacs.d/elpa/org-9.5.4/ox-ascii hides /usr/share/emacs/27.2/lisp/org/ox-ascii /home/marcel/.emacs.d/elpa/org-9.5.4/org hides /usr/share/emacs/27.2/lisp/org/org /home/marcel/.emacs.d/elpa/org-9.5.4/org-version hides /usr/share/emacs/27.2/lisp/org/org-version /home/marcel/.emacs.d/elpa/org-9.5.4/org-timer hides /usr/share/emacs/27.2/lisp/org/org-timer /home/marcel/.emacs.d/elpa/org-9.5.4/org-tempo hides /usr/share/emacs/27.2/lisp/org/org-tempo /home/marcel/.emacs.d/elpa/org-9.5.4/org-table hides /usr/share/emacs/27.2/lisp/org/org-table /home/marcel/.emacs.d/elpa/org-9.5.4/org-src hides /usr/share/emacs/27.2/lisp/org/org-src /home/marcel/.emacs.d/elpa/org-9.5.4/org-refile hides /usr/share/emacs/27.2/lisp/org/org-refile /home/marcel/.emacs.d/elpa/org-9.5.4/org-protocol hides /usr/share/emacs/27.2/lisp/org/org-protocol /home/marcel/.emacs.d/elpa/org-9.5.4/org-plot hides /usr/share/emacs/27.2/lisp/org/org-plot /home/marcel/.emacs.d/elpa/org-9.5.4/org-pcomplete hides /usr/share/emacs/27.2/lisp/org/org-pcomplete /home/marcel/.emacs.d/elpa/org-9.5.4/org-num hides /usr/share/emacs/27.2/lisp/org/org-num /home/marcel/.emacs.d/elpa/org-9.5.4/org-mouse hides /usr/share/emacs/27.2/lisp/org/org-mouse /home/marcel/.emacs.d/elpa/org-9.5.4/org-mobile hides /usr/share/emacs/27.2/lisp/org/org-mobile /home/marcel/.emacs.d/elpa/org-9.5.4/org-macs hides /usr/share/emacs/27.2/lisp/org/org-macs /home/marcel/.emacs.d/elpa/org-9.5.4/org-macro hides /usr/share/emacs/27.2/lisp/org/org-macro /home/marcel/.emacs.d/elpa/org-9.5.4/org-loaddefs hides /usr/share/emacs/27.2/lisp/org/org-loaddefs /home/marcel/.emacs.d/elpa/org-9.5.4/org-list hides /usr/share/emacs/27.2/lisp/org/org-list /home/marcel/.emacs.d/elpa/org-9.5.4/org-lint hides /usr/share/emacs/27.2/lisp/org/org-lint /home/marcel/.emacs.d/elpa/org-9.5.4/org-keys hides /usr/share/emacs/27.2/lisp/org/org-keys /home/marcel/.emacs.d/elpa/org-9.5.4/org-inlinetask hides /usr/share/emacs/27.2/lisp/org/org-inlinetask /home/marcel/.emacs.d/elpa/org-9.5.4/org-indent hides /usr/share/emacs/27.2/lisp/org/org-indent /home/marcel/.emacs.d/elpa/org-9.5.4/org-id hides /usr/share/emacs/27.2/lisp/org/org-id /home/marcel/.emacs.d/elpa/org-9.5.4/org-habit hides /usr/share/emacs/27.2/lisp/org/org-habit /home/marcel/.emacs.d/elpa/org-9.5.4/org-goto hides /usr/share/emacs/27.2/lisp/org/org-goto /home/marcel/.emacs.d/elpa/org-9.5.4/org-footnote hides /usr/share/emacs/27.2/lisp/org/org-footnote /home/marcel/.emacs.d/elpa/org-9.5.4/org-feed hides /usr/share/emacs/27.2/lisp/org/org-feed /home/marcel/.emacs.d/elpa/org-9.5.4/org-faces hides /usr/share/emacs/27.2/lisp/org/org-faces /home/marcel/.emacs.d/elpa/org-9.5.4/org-entities hides /usr/share/emacs/27.2/lisp/org/org-entities /home/marcel/.emacs.d/elpa/org-9.5.4/org-element hides /usr/share/emacs/27.2/lisp/org/org-element /home/marcel/.emacs.d/elpa/org-9.5.4/org-duration hides /usr/share/emacs/27.2/lisp/org/org-duration /home/marcel/.emacs.d/elpa/org-9.5.4/org-datetree hides /usr/share/emacs/27.2/lisp/org/org-datetree /home/marcel/.emacs.d/elpa/org-9.5.4/org-ctags hides /usr/share/emacs/27.2/lisp/org/org-ctags /home/marcel/.emacs.d/elpa/org-9.5.4/org-crypt hides /usr/share/emacs/27.2/lisp/org/org-crypt /home/marcel/.emacs.d/elpa/org-9.5.4/org-compat hides /usr/share/emacs/27.2/lisp/org/org-compat /home/marcel/.emacs.d/elpa/org-9.5.4/org-colview hides /usr/share/emacs/27.2/lisp/org/org-colview /home/marcel/.emacs.d/elpa/org-9.5.4/org-clock hides /usr/share/emacs/27.2/lisp/org/org-clock /home/marcel/.emacs.d/elpa/org-9.5.4/org-capture hides /usr/share/emacs/27.2/lisp/org/org-capture /home/marcel/.emacs.d/elpa/org-9.5.4/org-attach hides /usr/share/emacs/27.2/lisp/org/org-attach /home/marcel/.emacs.d/elpa/org-9.5.4/org-attach-git hides /usr/share/emacs/27.2/lisp/org/org-attach-git /home/marcel/.emacs.d/elpa/org-9.5.4/org-archive hides /usr/share/emacs/27.2/lisp/org/org-archive /home/marcel/.emacs.d/elpa/org-9.5.4/org-agenda hides /usr/share/emacs/27.2/lisp/org/org-agenda /home/marcel/.emacs.d/elpa/org-9.5.4/ol hides /usr/share/emacs/27.2/lisp/org/ol /home/marcel/.emacs.d/elpa/org-9.5.4/ol-w3m hides /usr/share/emacs/27.2/lisp/org/ol-w3m /home/marcel/.emacs.d/elpa/org-9.5.4/ol-rmail hides /usr/share/emacs/27.2/lisp/org/ol-rmail /home/marcel/.emacs.d/elpa/org-9.5.4/ol-mhe hides /usr/share/emacs/27.2/lisp/org/ol-mhe /home/marcel/.emacs.d/elpa/org-9.5.4/ol-irc hides /usr/share/emacs/27.2/lisp/org/ol-irc /home/marcel/.emacs.d/elpa/org-9.5.4/ol-info hides /usr/share/emacs/27.2/lisp/org/ol-info /home/marcel/.emacs.d/elpa/org-9.5.4/ol-gnus hides /usr/share/emacs/27.2/lisp/org/ol-gnus /home/marcel/.emacs.d/elpa/org-9.5.4/ol-eww hides /usr/share/emacs/27.2/lisp/org/ol-eww /home/marcel/.emacs.d/elpa/org-9.5.4/ol-eshell hides /usr/share/emacs/27.2/lisp/org/ol-eshell /home/marcel/.emacs.d/elpa/org-9.5.4/ol-docview hides /usr/share/emacs/27.2/lisp/org/ol-docview /home/marcel/.emacs.d/elpa/org-9.5.4/ol-bibtex hides /usr/share/emacs/27.2/lisp/org/ol-bibtex /home/marcel/.emacs.d/elpa/org-9.5.4/ol-bbdb hides /usr/share/emacs/27.2/lisp/org/ol-bbdb /home/marcel/.emacs.d/elpa/org-9.5.4/ob hides /usr/share/emacs/27.2/lisp/org/ob /home/marcel/.emacs.d/elpa/org-9.5.4/ob-tangle hides /usr/share/emacs/27.2/lisp/org/ob-tangle /home/marcel/.emacs.d/elpa/org-9.5.4/ob-table hides /usr/share/emacs/27.2/lisp/org/ob-table /home/marcel/.emacs.d/elpa/org-9.5.4/ob-sqlite hides /usr/share/emacs/27.2/lisp/org/ob-sqlite /home/marcel/.emacs.d/elpa/org-9.5.4/ob-sql hides /usr/share/emacs/27.2/lisp/org/ob-sql /home/marcel/.emacs.d/elpa/org-9.5.4/ob-shell hides /usr/share/emacs/27.2/lisp/org/ob-shell /home/marcel/.emacs.d/elpa/org-9.5.4/ob-sed hides /usr/share/emacs/27.2/lisp/org/ob-sed /home/marcel/.emacs.d/elpa/org-9.5.4/ob-screen hides /usr/share/emacs/27.2/lisp/org/ob-screen /home/marcel/.emacs.d/elpa/org-9.5.4/ob-scheme hides /usr/share/emacs/27.2/lisp/org/ob-scheme /home/marcel/.emacs.d/elpa/org-9.5.4/ob-sass hides /usr/share/emacs/27.2/lisp/org/ob-sass /home/marcel/.emacs.d/elpa/org-9.5.4/ob-ruby hides /usr/share/emacs/27.2/lisp/org/ob-ruby /home/marcel/.emacs.d/elpa/org-9.5.4/ob-ref hides /usr/share/emacs/27.2/lisp/org/ob-ref /home/marcel/.emacs.d/elpa/org-9.5.4/ob-python hides /usr/share/emacs/27.2/lisp/org/ob-python /home/marcel/.emacs.d/elpa/org-9.5.4/ob-processing hides /usr/share/emacs/27.2/lisp/org/ob-processing /home/marcel/.emacs.d/elpa/org-9.5.4/ob-plantuml hides /usr/share/emacs/27.2/lisp/org/ob-plantuml /home/marcel/.emacs.d/elpa/org-9.5.4/ob-perl hides /usr/share/emacs/27.2/lisp/org/ob-perl /home/marcel/.emacs.d/elpa/org-9.5.4/ob-org hides /usr/share/emacs/27.2/lisp/org/ob-org /home/marcel/.emacs.d/elpa/org-9.5.4/ob-octave hides /usr/share/emacs/27.2/lisp/org/ob-octave /home/marcel/.emacs.d/elpa/org-9.5.4/ob-ocaml hides /usr/share/emacs/27.2/lisp/org/ob-ocaml /home/marcel/.emacs.d/elpa/org-9.5.4/ob-maxima hides /usr/share/emacs/27.2/lisp/org/ob-maxima /home/marcel/.emacs.d/elpa/org-9.5.4/ob-matlab hides /usr/share/emacs/27.2/lisp/org/ob-matlab /home/marcel/.emacs.d/elpa/org-9.5.4/ob-makefile hides /usr/share/emacs/27.2/lisp/org/ob-makefile /home/marcel/.emacs.d/elpa/org-9.5.4/ob-lua hides /usr/share/emacs/27.2/lisp/org/ob-lua /home/marcel/.emacs.d/elpa/org-9.5.4/ob-lob hides /usr/share/emacs/27.2/lisp/org/ob-lob /home/marcel/.emacs.d/elpa/org-9.5.4/ob-lisp hides /usr/share/emacs/27.2/lisp/org/ob-lisp /home/marcel/.emacs.d/elpa/org-9.5.4/ob-lilypond hides /usr/share/emacs/27.2/lisp/org/ob-lilypond /home/marcel/.emacs.d/elpa/org-9.5.4/ob-latex hides /usr/share/emacs/27.2/lisp/org/ob-latex /home/marcel/.emacs.d/elpa/org-9.5.4/ob-js hides /usr/share/emacs/27.2/lisp/org/ob-js /home/marcel/.emacs.d/elpa/org-9.5.4/ob-java hides /usr/share/emacs/27.2/lisp/org/ob-java /home/marcel/.emacs.d/elpa/org-9.5.4/ob-haskell hides /usr/share/emacs/27.2/lisp/org/ob-haskell /home/marcel/.emacs.d/elpa/org-9.5.4/ob-groovy hides /usr/share/emacs/27.2/lisp/org/ob-groovy /home/marcel/.emacs.d/elpa/org-9.5.4/ob-gnuplot hides /usr/share/emacs/27.2/lisp/org/ob-gnuplot /home/marcel/.emacs.d/elpa/org-9.5.4/ob-fortran hides /usr/share/emacs/27.2/lisp/org/ob-fortran /home/marcel/.emacs.d/elpa/org-9.5.4/ob-forth hides /usr/share/emacs/27.2/lisp/org/ob-forth /home/marcel/.emacs.d/elpa/org-9.5.4/ob-exp hides /usr/share/emacs/27.2/lisp/org/ob-exp /home/marcel/.emacs.d/elpa/org-9.5.4/ob-eval hides /usr/share/emacs/27.2/lisp/org/ob-eval /home/marcel/.emacs.d/elpa/org-9.5.4/ob-eshell hides /usr/share/emacs/27.2/lisp/org/ob-eshell /home/marcel/.emacs.d/elpa/org-9.5.4/ob-emacs-lisp hides /usr/share/emacs/27.2/lisp/org/ob-emacs-lisp /home/marcel/.emacs.d/elpa/org-9.5.4/ob-dot hides /usr/share/emacs/27.2/lisp/org/ob-dot /home/marcel/.emacs.d/elpa/org-9.5.4/ob-ditaa hides /usr/share/emacs/27.2/lisp/org/ob-ditaa /home/marcel/.emacs.d/elpa/org-9.5.4/ob-css hides /usr/share/emacs/27.2/lisp/org/ob-css /home/marcel/.emacs.d/elpa/org-9.5.4/ob-core hides /usr/share/emacs/27.2/lisp/org/ob-core /home/marcel/.emacs.d/elpa/org-9.5.4/ob-comint hides /usr/share/emacs/27.2/lisp/org/ob-comint /home/marcel/.emacs.d/elpa/org-9.5.4/ob-clojure hides /usr/share/emacs/27.2/lisp/org/ob-clojure /home/marcel/.emacs.d/elpa/org-9.5.4/ob-calc hides /usr/share/emacs/27.2/lisp/org/ob-calc /home/marcel/.emacs.d/elpa/org-9.5.4/ob-awk hides /usr/share/emacs/27.2/lisp/org/ob-awk /home/marcel/.emacs.d/elpa/org-9.5.4/ob-R hides /usr/share/emacs/27.2/lisp/org/ob-R /home/marcel/.emacs.d/elpa/org-9.5.4/ob-C hides /usr/share/emacs/27.2/lisp/org/ob-C Study the output of `M-x list-load-path-shadows`. ## `ox-hugo` defcustoms
org-hugo-section "posts"
org-hugo-use-code-for-kbd nil
org-hugo-preserve-filling t
org-hugo-delete-trailing-ws t
org-hugo-prefer-hyphen-in-tags t
org-hugo-allow-spaces-in-tags t
org-hugo-tag-processing-functions (org-hugo–tag-processing-fn-replace-with-spaces-maybe org-hugo–tag-processing-fn-replace-with-hyphens-maybe)
org-hugo-auto-set-lastmod nil
org-hugo-export-with-toc nil
org-hugo-export-with-section-numbers nil
org-hugo-front-matter-format "toml"
org-hugo-default-static-subdirectory-for-externals "ox-hugo"
org-hugo-external-file-extensions-allowed-for-copying ("jpg" "jpeg" "tiff" "png" "svg" "gif" "mp4" "pdf" "odt" "doc" "ppt" "xls" "docx" "pptx" "xlsx")
org-hugo-date-format "%Y-%m-%dT%T%z"
org-hugo-paired-shortcodes ""
org-hugo-suppress-lastmod-period 0.0
org-hugo-front-matter-format "toml"
kaushalmodi commented 2 years ago

Thanks for bisecting this. This is an unexpected regression because I introduced the use of org-find-olp from the Org library in https://github.com/kaushalmodi/ox-hugo/commit/f9c9aaf0160bb552d438cda20b7627a41dfaf125.

https://github.com/kaushalmodi/ox-hugo/blob/85d11219a58d3a7927d7fe67144987a574fa54aa/ox-hugo.el#L4576

Thanks to your minimal reproducible example, I can easily see that error now. It wasn't caught by the test suite or my personal use because I never had subtrees with the same heading and at the same level.

Unfortunately, I don't have a "fix" for this because the org-find-olp finds a unique subtree using its headline hierarchy.

E.g. if the headline hierarchy is:

* abc
** def

the path is ("abc" "def").

This method was more robust compared to tracking the starting point of a post subtree in an Org file. I switched to this method because the earlier starting-point-tracking method failed if an earlier post subtree in the same file contained links to other subtrees.


Is it possible to organize your posts with identical headings in different subtrees so that their "full path" is unique? Taking your minimal example, this fixes the error:

* foo
** My Blog Post
:PROPERTIES:
:EXPORT_FILE_NAME: my-blog-post-1
:END:

This is an example blog post.
* bar
** My Blog Post
:PROPERTIES:
:EXPORT_FILE_NAME: my-blog-post-2
:END:

This is another example blog post.
kaushalmodi commented 2 years ago

I'm not closing this issue yet. I'm considering a low priority enhancement request/note for myself:

mmk2410 commented 2 years ago

Thank you very much for your reply! I understand your reasoning and agree. To be honest, I cannot think of any good reason for having multiple blog posts with the same title at the same level. Although, it may be relevant for multi-lingual blogs where two translations are (for whatever reason) identical.

In my case, there are three quite old posts for which I'll either follow your proposed multiple subtree approach or rename them.

Thank you very much for your effort and for maintaining the package.

kaushalmodi commented 2 years ago

I don't have any experience with multi-lingual web sites. But probably something like this works:

* English
:PROPERTIES:
:EXPORT_LANGUAGE: en
:EXPORT_HUGO_SECTION: posts/en
:END:
** My Blog Post
:PROPERTIES:
:EXPORT_FILE_NAME: my-blog-post
:END:
This is an example post in English.
* German
:PROPERTIES:
:EXPORT_LANGUAGE: de
:EXPORT_HUGO_SECTION: posts/de
:END:
** My Blog Post
:PROPERTIES:
:EXPORT_FILE_NAME: my-blog-post
:END:
This an example post in German, but I cannot write it in German
because I don't know German :).

Another issue with post headings with the exact same strings is that intra-post links like [[* My other post]] won't work because now Org will fail to find a unique heading if that "My other post" heading occurred many times.

Now that I think about it, there's probably very little value in supporting post headings with exact same text. Even if I could write a workaround to the default behavior of org-find-olp, that still won't be robust as the intra-post link to a post with reused titles will fail.


Solution: Don't have identical text for post titles. If you really really need to do that, then may be put them in separate .org files.

@mmk2410 Would that solution be acceptable?

mmk2410 commented 2 years ago

Now that I think about it, there's probably very little value in supporting post headings with exact same text.

I have the same feeling. And the problem with internal links is another good point that I didn't even think about before.

Solution: Don't have identical text for post titles. If you really really need to do that, then may be put them in separate .org files.

@mmk2410 Would that solution be acceptable?

Yes, it certainly would. I already changed the title of the conflicting posts in my blog yesterday.

kaushalmodi commented 2 years ago

I appreciate the reply. I'll then close this issue.