Elisp output issue

I’m having a little problem writing some elisp code for a custom function in emacs. I need to write a custom function that takes the following property values

:PROPERTIES:
:EXPORT_HUGO_TAGS: tag1, tag2
:END

and converts it to the TOML front matter

+++
[taxonomies]
tags = [ “tag1”, “tag2” ]
+++

Obviously, I’m using the ox-hugo package. With the benign guidance of our AI overlords, I’ve (we?) come up with the following custom function

(defun my/hugo-custom-front-matter (props)
  (let* ((title (plist-get props :title))
         (author (plist-get props :author))
         (date (plist-get props :date))
         (tags (split-string (plist-get props :hugo_tags) ", " t)))
    (concat "+++\n"
            (when title (format "title = \"%s\"\n" title))
            (when author (format "author = \"%s\"\n" author))
            (when date (format "date = \"%s\"\n" date))
            "[taxonomies]\n"
            (format "tags = [%s]\n" (mapconcat (lambda (tag) (format "\"%s\"" tag)) tags ", "))
            "+++\n")))

(with-eval-after-load 'ox-hugo
  (advice-add 'org-hugo--build-front-matter :override #'my/hugo-custom-front-matter))

(use-package ox-hugo
  :ensure t
  :after ox)

Unfortunately, this has resulted in the complete absence of the line

[taxonomies]

and the tags line gets formatted like the following:

tags = [“tag1”, [“,”, “tag2”]]

I would have thought that the elisp code would have produced the correct output, but obviously, it does not.

Any suggestions that would out-wit ChatGPT?

Thanks,
Mike

This is a full feast of LLM hallucination:

  1. There is no org-hugo--build-front-matter, only org-hugo--gen-front-matter
  2. That function takes two arguments, so an :override would not work
  3. It’s not a prop-based function, but a data based one. Similar, but not the same.

With a bit of search, we find that org-hugo--gen-front-matter takes two arguments, data and format. The former contains all the metadata, the latter the format. We only want to manipulate the metadata, namely, we want to change '(tags ("tag1" "tag2" "tag3")) into '(taxonomies (tags ("tag1 "tag2 "tag3"))). Also, since [taxonomies] is a new section within TOML, we should move that to the end of the metadata, otherwise we will end up with

[taxonomies]
  tags = ["tag1", "tag2", "tag3"]
draft = false

which would set taxonomies.draft. Whoops. Technically, that cannot happen in YAML, so we could check whether (string= format "yaml"), but that’s left as an exercise:

(defun my/org-hugo--gen-front-matter (args)
  "Move tags within ARGS car to the end and wrap them in taxonomies."
  (let ((data (car args))
        (format (cadr args)))
    (when-let ((tags (assq 'tags data)))
      (assq-delete-all 'tags data)
      (setq data (append data `((taxonomies ,tags)))))
    (list data format)))

(use-package ox-hugo
  :after ox
  :config
  (advice-add 'org-hugo--gen-front-matter :filter-args  #'my/org-hugo--gen-front-matter))

By the way, your markup for multiple entries in EXPORT_HUGO_TAGS is wrong; multiple values should be separated by space, not by comma:

 :EXPORT_HUGO_TAGS: tag1  tag2
1 Like

Wow! Thanks ashraz! That’s fantastic.

I asked that stupid thing three times. I think I’m the stupid one for not just going directly to the source.

Thanks not only for the solution, but also the discussion on the techniques to solve this problem. I really appreciate it.

Mike

I don’t blame you. I blame the hype around LLMs. Thing is, the code seems a bit reasonable, getting :title and other properties, right? But then you notice that it uses let*, even though none of the bindings need to be in sequence. Then you remember that (when expr expr) is (and expr expr). Not a big difference, but more idiomatic if you don’t need a (progn).

What do you mean here?

Would it be idiomatic to use ands in favor of whens?

That’s a good question. Maybe I misspoke in the previous post. For me, it always depends on the context. (and ...) feels more natural for me if I want to use the resulting value, e.g.

(let ((result (and (consp input) (car input)))
  ...)

whereas when feels like a control-oriented structure with side-effects:

(when (consp input)
  (message "Requesting %s to get %s" (car input) (cdr input))
  (some-request-with-side-effects (car input))

But that might be just me, especially since I’m coming from Haskell, where when :: Applicative f => Bool -> f () -> f() and thus when cannot return a value there. Instead, when is used for side-effects only.

That being said, Common Lisp the Language, 2nd Editionseems to agree there:

As a matter of style, when is normally used to conditionally produce some side effects, and the value of the when form is normally not used. If the value is relevant, then it may be stylistically more appropriate to use and or if.

So for me: when if side-effects are required and the resulting value is not relevant, and if values are required and there are no side effects.

Other’s might disagree though, so maybe using “idiomatic” was a bit much. :sweat_smile:

1 Like

That makes sense. Thanks for a thoughtful reply!

I have no experience with Haskell, so that signature looks like black magic :sweat_smile: There’s a difference between => and ->? There’s a difference between f () and f()? Oh, well … can’t know everything.

The missing space was a typo, sorry. Here’s how to read the type signature:

when :: Applicative f        -- for any action 'f' that implements Applicative
     => Bool -> f () -> f () -- `when` takes a Bool and an action with no result and gives an action with no result

More or less. The => separates the requirements of the types from the argument’s types.

Maybe an easier example:

add :: Num a => a -> a -> a -- need addition only
add x y = x + y

addIfPositive :: (Ord a, Num a) => a -> a -> a -- need comparison and addition
addIfPositive x y = if y > 0 then x + y else x

Somewhat, at least

That clears some of it up, thank you!

Veering completely off topic, I’m sorry … but how come the a isn’t overloaded in your last two examples? I kind of see how we can say that “a is numbers, so addition is to take two a’s and return a third”. What I don’t understand is how we can say that “a is an ‘orderable number’ and a is a ‘number’, addIfPositive returns a number”, how do we know which is which when we write

(Ord a, Num a) => a -> a -> a

it seems to me we’d have to write something like

(Ord b, Num a) => b -> a -> a?

Fell free to not reply or tell me to take this somewhere else :sweat_smile:

Edit: Thanks for taking the time to answer this in a dm, @ashraz !

FWIW, using when vs. and seems like a matter of preference. I strongly prefer using when because it clearly distinguishes the condition from the value. If there are multiple conditions, I strongly prefer (when (and A B) VALUE) over (and A B VALUE).

Some say that and is more idiomatic, but I think it’s more of a mildly more popular convention.