# Created 2024-09-20 ven. 11:45
#+title: 7d.nz
#+author: Phil. Estival
* • [2021-07-11 dim.] Blogging with org     :litterate:programming:org:elisp:
Here are the explanations and the source code of the
blogging engine developed for this website (7d.nz).
It's build with Emacs and org-mode,
creates static HTML pages and got inspired by ox-hugo.

While you reader can start reading it here,
you'll probably be more at ease under Emacs especially
when dwelling into the details of the code.  Lucky for
you, every pages of this website provides the org source
its footer.

Update: [2024-06-02 dim.]  Org 9.7.2
** License
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.
** Quickstart                                                        :ATTACH:
One way to understand how it works could be to evaluate
the following code under Emacs, and then start blogging
right away just by modifying existing headlines or saving
new ones. Even safer and perhaps inspirational
will be to read it first.

Tested on emacs 28 to 30 and org 9.5 to 9.7.

Once autoexport is enabled
— and you can see it by having the following expression
"autoexport: enabled ?" highlighted —
saving the file when the cursor will be over a section
in a DONE state will export that section as a blog post.

All you need to run the static generator is a copy of this
file, assuming compatible versions of the libraries. The
main entry point to setup the blog is the following code
block wich in turns evaluate the legit elisp blocks down
below.


<<setup>>
#+name: org-blog
#+begin_src elisp :results silent
  ;; Skip elisp confirmation on interactive evaluation
  ;; and evaluate all blocks in this file.
  ;; Be careful if you set your own experimental ones along the way.
  ;; You can declare "#+begin_src emacs-lisp" to keeps elisps blocks
  ;; in this file not run by this function
  (message "setting up org-blog auto-export mode")
  (save-excursion
    (setq org-confirm-babel-evaluate
          (lambda (lang body) (not (equal "elisp" lang))))

    (condition-case err
        (while (org-next-block-if-any)
          (cond (
                 (looking-at "^#\\+begin_src +\\(elisp\\) *$")
                 (let (
                       (l (match-end 0)))
                   (search-forward "#+end_src")
                   (setq init-org-block (+ 1 init-org-block))
                   ;;(princ init-org-block)
                   (princ " ")
                   (eval-region l (match-beginning 0))
                   ))))

      (error () (message "%s at line %s" (error-message-string err) (line-number-at-pos)))))
  ;; alternatively: jump to the headline and (org-babel-execute-subtree)
  (org-blog-auto-export-mode)
  (add-hook 'before-save-hook 'time-stamp)

  (org-display-inline-images)
  (setq htmlize-force-inline-images t)
  (setq org-blog-regen-toc&tags nil)
#+end_src

org-next-block-if-any is defined in init.el.

to export the entry point TOC & tag list upon saving:
: (setq org-blog-regen-toc&tags t)

Turn it off if you're just publishing one article.
: (setq org-blog-regen-toc&tags nil)


keep
: org-blog-regen-toc&tags t

and
: (setq org-blog-regen-tags-p nil)

to update the front page without regenerating the tags pages.
** How to read this document

- online https://7d.nz/Blogging-with-org.html
- from an org-renderer of github or gitlab
  https://gitlab.com/7dnz/org-weblog. Please note that
  most online org renderers are still incomplete, and
  some text will be missing.
- from the original sources in org and with your
  prefered editor. You'll find them either in the
  source repository, or by looking down below of
  the HTML pages: 7d.nz/org/org-weblog.

Enjoy the reading and good luck with the coding
part.  If you go into the details, you may noticed
a few inconsistancies in the function prefixes
(org-blog, org-weblog, blog-), as I haven't stated
yet on the definitive naming.
** Introduction                                                     :0:intro:

After having tried many of the blogging engines available
from org-mode, I ended with the following list of
specifications:

- The entire blog should be kept in one file with one
  headline as a blog entry. The first time I read about this
  idea was on Arthur Malabarba's blog: Endless Parenthesis
  (This is also the place I landed on when I first searched
  init.org).  Endless Parentheses uses ox-jekyll.
- One article per top level section. While it should
  also be possible to manage multiple blogs, with one
  blog per top level section, and one article per level
  2 section.
- Only Emacs, no other dependencies, static and minimalist
  design.
- The publishing process should be as fast, automatized
  and seamless as possible, exporting only what is needed
  to render one article at a time, located by
  the cursor position.
- The closest thing I found to what I was looking for
  was Kaushal's scripter.co with ox-hugo.  this
  blogging system is neat. It's one of the best that's
  been around and the org-mode source is clean.
  However his setup file is big. The guy is connected
  to dozens of social medias and I didn't start from
  the bare config but from his own personnal
  repository. Much tweaking is required to dig into
  hugo and go-templates (btw I don't have any proper
  syntax coloring for this). Hugo is a good solid
  blogging engine, and it certainly does provide much
  more than what I'll get here all by myself, but still...
- [ ] [2023-06-13 mar.]  one addendum to the specifications
  was recently suggested to me by email: we should define a
  template property, and an org-weblog-templates alist that
  holds all the different templates styles parameters. The
  next step would be to externalize the styling along with
  the author signature.
- The next version of the org-weblog should invoke
  org-publish.

In the end, deriving the html exporter and programming
it's behavior turned out to be the way to go.

The general idea activated here is a literate
programming flow. That flow combines two aspects in the
work:
- the programming part, which I'll explain in
  detail here, since this is the place where the code is
  written, commented and elaborated,

- The literate part, that is the
  discipline one has to keep up in order to organize
  ideas and thoughts and communicate them.

And now that the blog is ready, I can finally communicate
more about past, present and future software projects and
the many aspects of programming without bothering too much
about the technicalities.

The next chapter that will also gets integrated into
this blog is about collective review. That's still
work in progress at the time I'm writing this [2021-12-20 lun.],
and I'm getting a bit late on the topic.

Literate programming is about programming and
literature.  You have to consider that programming is
indeed a kind of literature, and that informatics is
all about text, much more than numbers: the mere
existence of transistors, electricity and 0 and 1 is
99% of the time of no concern to programmers — and, as a
matter of fact, to anyone else — unlike the texts
they're working on.

The publishing medium matters here because the program
is all about publishing online.
** Structure

./img/jumpb2.png


One headline = one post.

An org headline starts like this
: *  TODO [#3] [2021-05-24 Mon 23:07] blogging with org-mode     :code:article:post:

But Many times it will be simpler than that:
: * [2021-07-20 Tue] News from the front



This will produce a new post in a file named after a
"slug" from the
headline.  If needed, that filename can be explicitly
defined with the EXPORT_FILE_NAME property.  This has
however a drawback when using stored links, as they
reference the original file, but after, in the export.

Top level entries are sometimes called "headline level
1", sometimes, "H1", even this can be confused with
HTML.  That behaviour can be overriden if one really
needs to store the blog posts under one section.

The state (TODO/DONE) determines if a post must
be published or not.

When in a DONE state (such as a square ■, in this
document, according to the top directives[fn::the top
statemenents in an org file starting with #+, they
would rather better be named directives rather than
keywords, but it's too late now.]) every time C-x C-s
is hit while the cursor is in the section, it is
exported, while the summary and the index.org gets
updated accordingly.

The timestamp headlines order the post chronologically
in the blog.  The tags are also exported for grouping
and categorization.  Priorities are ignored.

The index page is a list of blog entries with a short
summary of the articles.

So, once the auto-export in enabled,
writing in org file will export the subtree
to it's own org-file, which in turns gets
exported to html.

The Html backend exporter still doesn't handle the case
of a few fuzzy links, so we will have to take care of
that.

Pagination isn't implemented yet.  A simple index
increment from url /page/1 to /n would do.  /page/1
might be a special case if it's also the landing page
but it won't be necessarly the "recent posts" list on
the front page (maybe I'll keep some on top headline,
or even use the org list for this order).
*** UP heading

The point has to return to a headline in order to get
the information about the section or sub-section we're
currently writing in.
#+begin_src elisp

  (defun org-up-heading (level)
    "return up to closest headline at LEVEL"
    (condition-case err
        (outline-up-heading
         (- (nth 1 (org-heading-components))
            level))
      (error () nil)))  ;; already at top level

  (defun heading-components-at-level (level)
    "return the top level heading components
     of the current section. Said differently : get back to the
     root (level 1) of the current section, down to n level"
    (save-excursion
      (condition-case err
          (outline-up-heading
           (- (nth 1 (org-heading-components))
              level))
        (error () nil))  ;; already at top level
      (org-heading-components)))
#+end_src

: (org-up-heading 1)
: (org-up-heading 3)
: (heading-components-at-level 1)
** Date/title extraction

We separate the date (if any) and the title.  When's
there's no date (at least, no detected date — that is
when the regexp didn't match), the post in considered
as holding only a title, and will be set to the
current date and time when an export occurs.

#+begin_src elisp
  ;; derivation of org-ts-regexp0,
  ;; for spaces, grouping, and text after.
  ;; Matches  :
  ;; [2021-05-25 Tue 01:03] title
  ;; [2021-05-25 Lun.] title
  (defconst org-timestamp-and-title
    "\\(\\([0-9]\\{4\\}\\)-\\([0-9]\\{2\\}\\)-\\([0-9]\\{2\\}\\) +\\([^]+0-9>
      -]+\\)?\\( +\\([0-9]\\{1,2\\}:[0-9]\\{2\\}\\)\\)?\\)] \\(.*\\)")

  (defun split-datetime+title (str)
    (list :timestamp (match-string 1 str)
          :year (match-string 2 str)
          :month (match-string 3 str)
          ;; :day (match-string 4 str)
          ;; :weekday (match-string 5 str)
          :time (match-string 7 str)
          :title (match-string 8 str)))

  (defun timestamp-and-title (str)
    """ on a bare string return current time + string """
    (let ((time) (current-time))
      (if (string-match org-timestamp-and-title str)
          (split-datetime+title str)
        ;; else date of export
        (list :timestamp (format-time-string "[%Y-%m-%d %a %H:%M]" time)
              :year  (format-time-string "%Y" time)
              :month (format-time-string "%m" time)
              :time  (format-time-string "%H:%M" time)
              :title str))))

  (defun org-html-title-only (str)
    (if (string-match org-timestamp-and-title str)
        (split-datetime+title str)
      (str)))
#+end_src

Tests :
#+begin_src elisp
  (format-time-string "[%Y-%m-%d %H:%M]" (current-time))
  (timestamp-and-title "log.el.org")
  (timestamp-and-title (nth 4(heading-components-at-level 1)))
  (plist-get (timestamp-and-title (nth 4 (heading-components-at-level 1))) :timestamp)
  (plist-get (timestamp-and-title (nth 4 (heading-components-at-level 1))) :title)
  (timestamp-and-title "[2021-07-11 Sun] blog.el.org")
  (timestamp-and-title "[2021-07-11 Lun.] blog.el.org")
  (timestamp-and-title "[2021-07-11 Sun 00:00] log.el.org")
#+end_src
** Slugs
let's take a look at a Python "sluggifier".  A
better implementation with i18n support certainly
exists in Django.
#+begin_src python
  s = s.lower()
  # "[some] _ article's_title--"
  # "[some]___article's_title__"

  acct  = "ãàáäâẽèéëêìíïîõòóöôùúüûñç"
  wacct = "aaaaaeeeeeiiiiooooouuuunc"

  for i in range(len(acct)):
      s = s.replace(acct[i], wacct[i])

  s = s.replace("œ", "oe")

  acct  = "-/_,:;."

  for i in range(len(acct)):
      s = s.replace(acct[i], " ")

  # "some___articles_title__"
  # "some   articles title  "
  # s = s.replace('_', ' ')

  # "some   articles title  "
  # "some articles title "
  s = re.sub('\s+', ' ', s)

  # "some articles title  "
  # "some articles title"
  s = s.strip()

  # "some articles title"
  # "some-articles-title"
  s = s.replace(' ', '-')

  # "[some]___article's_title__"
  # "some___articles_title__"
  s = re.sub('\W', '-', s)

  # remove subsequent --
  return s.strip('-')
#+end_src
*** intermediate functions

#+begin_src elisp
  (defun replace-chars-in-string (set-1 with-set-2 str)
    "Replace index-wise the character from set 1 in str by those of set 2 inside a string"
    (dotimes (i (length set-1))
      (setq str (string-replace
                 (char-to-string (aref set-1 i))
                 (char-to-string (aref with-set-2 i))
                 str)))
    str)
#+end_src

#+begin_src elisp
  (defun replace-chars (set-1 char-2 str)
    "Replace the character from set 1 in str with char-2"
    (dotimes (i (length set-1))
      (setq str (string-replace
                 (char-to-string (aref set-1 i))
                 char-2
                 str)))
    str)
#+end_src

: (char-to-string 231)


#+begin_src emacs-lisp

  (replace-chars-in-string
   "ãàáäâẽèéëêìíïîõòóöôùúüûñç"
   "aaaaaeeeeeiiiiooooouuuunc"  "[some]  _   àrticle's_titlé--")


  (replace-chars
   "ãàáäâẽèéëêìíïîõòóöôùúüûñç"
   "X"  "[some]  _   àrticle's_titlé--")
  ;; (replace-chars-in-string
  ;;   "/_,:;."
  ;;   "       "  str)
  ;; (replace-regexp-in-string (regexp-quote "\s+") " " str nil 'literal)
  ;; (replace-regexp-in-string (regexp-quote "\s") "-" str nil 'literal)
  ;; (replace-chars-in-string
  ;;   "[](){}'"
  ;;   "-------" str) ;; < \W
  ;; (replace-regexp-in-string "\-+$" "" str nil 'literal) ;; suppress finals ---
#+end_src
*** Sluggyfier
#+begin_src elisp
  (defun sluggify (str)
    ;; suppress
    (replace-regexp-in-string
     "-+" "-" ;; multiple occurences
     (replace-regexp-in-string
      "^-+" "" ;; begining and
      (replace-regexp-in-string
       "-+$" "" ;; final --
       (replace-chars-in-string
        "/_,:;."
        "------"
        (replace-regexp-in-string
         (regexp-quote "\s")  "-"
         (replace-chars-in-string
          "[](){}'"
          "-------" ;; \W
          (replace-chars-in-string
           "ãàáäâẽèéëêìíïîõòóöôùúüûñç"
           "aaaaaeeeeeiiiiooooouuuunc"  str))))))))
#+end_src

A slightly better option would look like so
#+begin_src elisp
  (defun sluggif1 (str)
    (let((replacements
          '(
            ("[](){}'" . "-")
            ("/_,:;." . "-")
            ("-+$" . "") ;; final --
            ("^-+" . "") ;; begining and
            ("-+"  . "-") ;; multiple occurences
            ;;((regexp-quote "\s") .  "-")
            ))
         (result))
      ;; \W
      (setq str(replace-chars-in-string ;; french accent
                "ãàáäâẽèéëêìíïîõòóöôùúüûñç"
                "aaaaaeeeeeiiiiooooouuuunc"  str))

      (cl-loop for (key . value) in replacements
               collect (setq str (replace-chars key value str)))))
#+end_src
** HTML backend derivation

I'm using the standard html backend derivation
file:/usr/share/emacs/28.0.5/straight/repos/org/lisp/ox-html.el
negating a few options to get it to a bare minimum.

translate-alist indicates what function
is in charge of translating each org elements.
*** Checking if a headline is already in the toc

Html ids are unique, but headlines are not.
Add a number counter to differentiate identical headlines

#+begin_src elisp
  (defvar headline-list '() "the list of all headlines met during one export")
#+end_src


Generalized to this function:
#+begin_src elisp
  (defun append-next-numeral (string-list item)
    "add a numeral to `ITEM` present in the list `STRINGLIST`
     until it's unique, then add it to the list"
    (let( (crnt item) (i 0))
      (while (member crnt string-list)
        (setq crnt (concat item (format "-%s" (setq i (+ i 1))))))
      crnt))
#+end_src

(setq headline-list nil)
That we use this way:
#+begin_src emacs-lisp
  (add-to-list 'headline-list (append-next-numeral headline-list "title"))
#+end_src
*** Headline

As described at the begining, headlines follow this structure :
#+begin_example
  ,*  TODO [#3] [2021-05-24 Mon 23:07] blog.el.org     :dev:article:post:
#+end_example

For section of level 1 and more the title and the tags needs to
be exported, state and priority are discarded.  Date is
displayed in its own part.

Here's a derivation from org-html-headline.

#+begin_src elisp
  (defun exporting-article-p (info) (plist-get info :export-article))

  (defun org-blog-headline (headline contents info)
    "Transcode a HEADLINE element from Org to HTML.
       CONTENTS holds the contents of the headline.  INFO is a plist
       holding contextual information."
    (unless (org-element-property :footnote-section-p headline)
      (let* ((numberedp nil) ;; don't number the section ;;(org-export-numbered-headline-p headline info))
             (numbers (org-export-get-headline-number headline info))
             (level (+ (org-export-get-relative-level headline info)
                       (1- (plist-get info :html-toplevel-hlevel))))
             (todo (and (plist-get info :with-todo-keywords)
                        (let ((todo (org-element-property :todo-keyword headline)))
                          (and todo (org-export-data todo info)))))
             (todo-type (and todo (org-element-property :todo-type headline)))
             (priority (and (plist-get info :with-priority)
                            (org-element-property :priority headline)))
             (title (org-export-data (org-element-property :title headline) info))
             (tags (and (plist-get info :with-tags)
                        (org-export-get-tags headline info)))
             (tags-elem (if tags
                            (format " <div class='tags'>%s</div>"
                                    (mapconcat (lambda(c)(format "<a href='%s.html'>%s</a>" c c))
                                               tags " "))
                          ""))
             (full-text (funcall (plist-get info :html-format-headline-function)
                                 todo todo-type priority title tags info))
             (contents (or contents ""))
             (id (replace-regexp-in-string
                  "['\" ]" "-"
                  (or (org-element-property :CUSTOM_ID headline)
                      (org-export-get-reference headline info))
                  (org-export-get-reference headline info)))
             (formatted-text
              (if (plist-get info :export-article)
                  (format "<a href='#%s'>%s</a>" id full-text)
                (format "<a href='%s'>%s</a>" (sluggify full-text) full-text))))

        (setq title-num (append-next-numeral
                         headline-list
                         (replace-regexp-in-string "['\" ]" "-" title))) ;; to avoid dup ids

        (add-to-list 'headline-list title-num)
        (if (org-export-low-level-p headline info)
            ;; This is a deep sub-tree: export it as a list item.
            (let* ((html-type (if numberedp "ol" "ul")))
              (concat
               (and (org-export-first-sibling-p headline info)
                    (apply #'format "<%s class=\"org-%s\">\n"
                           (make-list 2 html-type)))
               (org-html-format-list-item
                contents (if numberedp 'ordered 'unordered)
                nil info nil
                (concat (org-html--anchor id nil nil info) formatted-text)) "\n"
               (and (org-export-last-sibling-p headline info)
                    (format "</%s>\n" html-type))))


          ;; TODO : L1 tags are handled in index.org
          ;; manage others tags here
          ;; when met, open tag.org, and insert a ref to upper entry + #headline
          ;; [2021-12-20 Mon 20:03] will do when the Missing refs are fixed

          ;; Standard headline.  Export it as a section.
          (let* ((extra-class
                  (org-element-property :HTML_CONTAINER_CLASS headline))
                 (headline-class
                  (org-element-property :HTML_HEADLINE_CLASS headline))
                 (first-content (car (org-element-contents headline))))

            ;; inner tags met in L2 sections get a reference in a tag file
            (when(and tags
                      (> level 1)
                      (plist-get info :export-article))
              ;; an article itself, not the other exports.
              ;; The variable is set for an article, not a toc, not the index
              (message "exporting inner tags")
              (blog-ref-inner-tags (org-export-output-file-name "") tags)
              (message "exporting inner tags DONE"))

            (format "<%s  class='%s'>%s%s</%s>\n" ;; id='%s'
                    (org-html--container headline info)
                    ;;(concat "section-" (org-export-get-reference headline info))
                    (concat (format "section-%d" level)
                            (and extra-class " ")
                            extra-class)

                    (if (exporting-article-p info)
                         ;; (concat  "\n<h" level " id='" title
                        ;;          "'><a href='#" title "'> " title " </a>" tags-elem "</h"level">\n")
                        ;; normal article with a ToC
                        (format "\n<h%d id='%s'><a href='#%s'> %s </a>%s</h%d>\n"
                                level
                                title-num
                                title-num
                                ;;(make-string (- level 1) ?#)  ;; # ## ###...
                                title
                                ;;tags
                                ;;(mapconcat #'identity tags " ")
                                tags-elem
                                level)

                      ;; an entry from the index or the tag list -> follow the link
                      (format "\n<h%d id='%s'><span class='timestamp'>%s</span>&nbsp;<a href='%s.html'>%s</a>%s</h%d>\n"
                              level title-num
                              (or (org-element-property :DATE headline) "")
                              (or (org-element-property :EXPORT_FILE_NAME headline)
                                  (sluggify title)) ;; or
                              title
                              tags-elem
                              level))

                    (if (eq (org-element-type first-content) 'section) contents
                      (concat (org-html-section first-content "" info) contents))
                    (org-html--container headline info)))))))
#+end_src

- [2021-09-02 jeu. 15:10]  (C-c ok, mais unbalanced with C-x e ...
  seems related to the pairing of "< >" along the parentheses.
  got to fix this.


#+begin_src elisp
  (setq org-export-headline-levels 4)
#+end_src

[2023-06-18 dim.]I see yet an other in a bug
*** Source code block
Add a class for prism.js.
#+begin_src elisp
  (defun src-code-block (src-block contents info)
    "Transcode a SRC-BLOCK element from Org to ASCII.
     CONTENTS is nil.  INFO is a plist used as a communication
     channel."
    (concat
     (format "<pre class='code line-numbers'>
  <code class='language-%s'>%s</code><button class='expand'>expand</button></pre>"
             (org-element-property :language src-block)
             (replace-regexp-in-string
              "\"" "&quot;" (org-html-encode-plain-text
                             (org-element-normalize-string
                              (org-export-format-code-default src-block info)))))))
#+end_src

The proper way to go would be to stack the detected
programming languages in the current page and insert
the relevant css for each of them.

Prism current configuration :
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+awk+bash+c+css-extras+go+graphql+latex+lisp+makefile+mongodb+python+regex+rust+sql+typescript&plugins=line-highlight+line-numbers+inline-color+normalize-whitespace+toolbar+copy-to-clipboard+treeview

Total filesize: 71.3KB (84% JavaScript + 16% CSS)
*** TODO Statistic cookie
This is handled fine with org-js.
It needs special care to insert formatting
that doesn't clutter the links in titles.
#+begin_src elisp
  (defun blog-html-statistics-cookie (statistics-cookie _contents _info)
    "[25/100]"
    (let ((cookie-value (org-element-property :value statistics-cookie)))
      (format "%s" cookie-value)))
#+end_src
*** Org html template head & body

Blog title is displayed in every page.
#+begin_src elisp
  (defcustom org-weblog-title
    "7d.nz"
    "The name of the blog" :group 'org-weblog)
#+end_src

<meta></meta>

#+begin_src elisp
  (setq org-html-validation-link "
        <a href='https://creativecommons.org/licenses/by-sa/4.0/deed.en'><img src='img/button-cc-by-sa.png' alt='cc-by-sa'></a>
        <a href='https://orgmode.org'><img src='img/button-orgmode.png' alt='orgmode'></a>
        <a href='https://validator.w3.org/check?uri=referer'><img src='img/button-html5.png' alt='html5'></a>")
#+end_src


#+begin_src elisp

  (defun org-blog--build-meta-info (info)
    "Return meta tags for exported document.
     INFO is a plist used as a communication channel."
        (message "meta")
    (let* ((protect-string
            (lambda (str)
              (replace-regexp-in-string
               "\"" "&quot;" (org-html-encode-plain-text str))))
           ;; having possibly an additionnal element (the date)
           ;; the list is either a car with the raw title (no date)
           ;; or a list of 2 element (date + title)
           (title (or (nth 2 (plist-get info :title)) (car (plist-get info :title))))
           ;;(title (plist-get (org-export-data (plist-get info :title) info)))
           ;;(title (if (eq title nil) "???")) ;;(car(plist-get info :title))))
           (title (if (org-string-nw-p title) title " "))
           (author (nth 1 (car(org-collect-keywords '("AUTHOR")))))
           ;; There can be multiple #+author. Consider 1 here.
           ;; (author (and (plist-get info :with-author)
           ;;         (let ((auth (plist-get info :author)))
           ;;     (and auth
           ;;          ;; Return raw Org syntax, skipping non
           ;;          ;; exportable objects.
           ;;          (org-element-interpret-data
           ;;           (org-element-map auth
           ;;         (cons 'plain-text org-element-all-objects)
           ;;       'identity info))))))
           (description (plist-get info :description))
           (keywords (plist-get info :keywords))
           (charset (or (and org-html-coding-system
                             (fboundp 'coding-system-get)
                             (coding-system-get org-html-coding-system
                                                'mime-charset))
                        "utf-8")))

      (concat
       (when (plist-get info :time-stamp-file)
         (format-time-string
          (concat "<!-- "
                  (plist-get info :html-metadata-timestamp-format)
                  " -->\n")))
       (format
        (if (org-html-html5-p info)
            (org-html-close-tag "meta" "charset=\"%s\"" info)
          (org-html-close-tag
           "meta" "http-equiv=\"Content-Type\" content=\"text/html;charset=%s\""
           info))
        charset) "\n"
       (let ((viewport-options
              (cl-remove-if-not (lambda (cell) (org-string-nw-p (cadr cell)))
                                (plist-get info :html-viewport))))
         (and viewport-options
              (concat
               (org-html-close-tag
                "meta name='viewport'"
                (format "content='%s'"
                        (mapconcat
                         (lambda (elm) (format "%s=%s" (car elm) (cadr elm)))
                         viewport-options ", "))
                info)
               "\n")))
       (format "<title>%s%s</title>\n" org-weblog-title
               (if (s-equals? title org-weblog-title) ""
                 (concat " — " title) ))
       (org-html-close-tag "meta" "name='generator' content='Org-mode'" info)
       "\n"
       (and (org-string-nw-p author)
            (concat
             (org-html-close-tag "meta"
                                 (format "name='author' content='%s'"
                                         (funcall protect-string author))
                                 info)
             "\n"))
       (and (org-string-nw-p description)
            (concat
             (org-html-close-tag "meta"
                                 (format "name='description' content='%s'\n"
                                         (funcall protect-string description))
                                 info)
             "\n"))
       (and (org-string-nw-p keywords)
            (concat
             (org-html-close-tag "meta"
                                 (format "name='keywords' content='%s'"
                                         (funcall protect-string keywords))
                                 info)
             "\n")))))


  ;; since toc is redefined
  (defun org-blog-inner-template (contents info)
    "Return body of document string after HTML conversion.
          CONTENTS is the transcoded contents string.  INFO is a plist
          holding export options."
    (concat
     "<a href='/' class='homebtn'><img src='img/7dnzbw.png'/></a>"
     "<img id='toggle-theme' src='img/toggle-theme.png'>"
     ;; Table of contents.
     (let ((depth (plist-get info :with-toc)))
       (when depth (org-blog-toc depth info)))
     "<article" (when (not (exporting-article-p info)) " class='index'" ) ">"
     ;; Document contents.
     contents
     ;; Footnotes section.
     (org-html-footnote-section info)
     "</article>" ))


  (defun org-blog-template (contents info)
    "Return complete document string after HTML conversion.
          CONTENTS is the transcoded contents string.  INFO is a plist
          holding export options."

    (concat
     (when (and (not (org-html-html5-p info)) (org-html-xhtml-p info))
       (let* ((xml-declaration (plist-get info :html-xml-declaration))
              (decl (or (and (stringp xml-declaration) xml-declaration)
                        (cdr (assoc (plist-get info :html-extension)
                                    xml-declaration))
                        (cdr (assoc "html" xml-declaration))
                        "")))
         (when (not (or (not decl) (string= "" decl)))
           (format "%s\n"
                   (format decl
                           (or (and org-html-coding-system
                                    (fboundp 'coding-system-get)
                                    (coding-system-get org-html-coding-system 'mime-charset))
                               "iso-8859-1"))))))
     (org-html-doctype info)
     "\n"
     (concat "<html"
             (cond ((org-html-xhtml-p info)
                    (format
                     " xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"%s\" xml:lang=\"%s\""
                     (plist-get info :language) (plist-get info :language)))
                   ((org-html-html5-p info)
                    (format " lang=\"%s\"" (plist-get info :language))))
             ">\n")
     "<head>\n"
     (org-blog--build-meta-info info)
     (org-html--build-head info)
     ;;(org-html--build-mathjax-config info)
     (let ((link-up (org-trim (plist-get info :html-link-up)))
           (link-home (org-trim (plist-get info :html-link-home))) ;; todo
           (title (plist-get info :title)))
       (unless (and (string= link-up "") (string= link-home ""))
         (format (plist-get info :html-home/up-format)
                 (or link-up link-home)
                 (or link-home link-up))))
     ;; Preamble.
     (org-html--build-pre/postamble 'preamble info)
     "</head>\n<body>
       <div id='top-line'></div>
       <div class='modal'></div>"

     ;; Document contents.
     (let ((div (assq 'content (plist-get info :html-divs))))
       (format "<%s id=\"%s\">\n" (nth 1 div) (nth 2 div)))
     ;; Document title.
     (when (plist-get info :with-title) ;; there's necessary one (headline Lvl 1 is the title)
       (let ((title (and (plist-get info :with-title)
                         (plist-get info :title)))
             (subtitle (plist-get info :subtitle))
             (html5-fancy (org-html--html5-fancy-p info))
             (tags (plist-get info :title-tags)))

         (format "<h1 class='page-title'><a href='index.html'>%s</a></h1>" org-weblog-title)
         (when title
           (format
            (concat
             "<header" (when (exporting-article-p info) " class='article'" ) ">
              <h1 class='page-title'><a href='index.html'>
                <img src='img/7dnz.png' alt='%s'></a>
              </h1>
              </header>
              <div class='title" (when (not (exporting-article-p info)) " index" ) "'>
              <h1 class='title'>%s</h1>
              <h2 class='timestamp'>%s</h2>
              %s
             </div>")

            org-weblog-title
            (or (nth 2 title) (car title))
            (or (plist-get (nth 1(nth 1 title)) :raw-value) " ")
            (if tags
                (format " <span class='tags'>%s</span>"
                        (mapconcat
                         (lambda (c)
                           (and (not (string-empty-p c))
                                (format "<a href='%s.html'>%s</a>" c c)))
                         (split-string tags ":") " "))
              "")

            ;;(nth 1 title)'
            ;;(org-export-data title info)
            ;;(plist-get :timestamp (timestamp-and-title (org-export-data title info)))
            ;;(plist-get :raw-value (nth 1 title))
            ;;(car (plist-get info :title))
            ;;(org-export-data title info)
            (if subtitle
                (format
                 (if html5-fancy
                     "<p class=\"subtitle\">%s</p>\n"
                   (concat "\n" (org-html-close-tag "br" nil info) "\n"
                           "<span class=\"subtitle\">%s</span>\n"))
                 (org-export-data subtitle info))
              "")))))

     contents
     (format "</%s>\n" (nth 1 (assq 'content (plist-get info :html-divs))))
     "<footer>"
     (when (exporting-article-p info)
       (let ((o (sluggify title)))
         (format "<div class='prepostamble'>
                     <p class='orgfile'><a href='org/%s.org'>%s.org</a></p>
                     <p class='orgfile'><a href='org/%s.org.html'>%s.org.html</a></p>
                  </div>" o o o o)))
     ;; Postamble.
     (org-blog--build-pre/postamble 'postamble info)


     "  <div class='footer-icons'>
        <a href='http://github.com/flintforge'><img src='img/gh-mini.png' alt='github/flintforge'></a>
        <a href='http://gitlab.com/7dnz'><img src='img/gitlab.svg' alt='gitlab/7d.nz'></a>
        <a href='./index.xml'><img src='img/rss.png' alt='rss'></a> &nbsp;
        <span id='hits'></span>
        <a href='./index.html'>
        <img class='licence' src='./img/footlogo.png' alt='7dnz'>
        </a>
        </div>"
     "</footer>"
     "<div id='bottom-line'></div>"
     ;; Possibly use the Klipse library live code blocks.
     ;; (when (plist-get info :html-klipsify-src)
     ;;   (concat "<script>" (plist-get info :html-klipse-selection-script)
     ;;           "</script><script src=\""
     ;;           org-html-klipse-js
     ;;           "\"></script><link rel=\"stylesheet\" type=\"text/css\" href=\""
     ;;           org-html-klipse-css "\"/>"))
     ;; Closing document.
     "</body>\n</html>"))
#+end_src


The TOC highlighter is here here :  file:./js/script.js


The title when given to the exporter
is a list of strings : TODO, priority, timestamp and title,
the timestamp beeing an org-element
#+begin_example
  (⬛ [#A] (timestamp (:type inactive :raw-value [2021-07-11 Sun]
  :year-start 2021 :month-start 7 :day-start 11 :hour-start nil
  :minute-start nil :year-end 2021 :month-end 7 :day-end 11 :hour-end
  nil :minute-end nil :begin 8 :end 25 :post-blank 1 :parent #0))
  blog.el.org)
#+end_example

Without a date, this is what it will looks like
#+begin_example
  (⬛ [#A] blog.el.org)
#+end_example
*** TODO customize the postamble
date, author,
validation link,
#+begin_src elisp
  (setq org-html-postamble-format
  '("en" "<p class=\"author\">Author: %a (%e)</p>
  <p class=\"date\">Date: %d</p>
  <p class=\"creator\">%c</p>
  <p class=\"validation\">%v</p>"))
#+end_src


#+begin_src elisp
   (defun org-blog--build-pre/postamble (type info)
     "Return document preamble or postamble as a string, or nil.
  TYPE is either `preamble' or `postamble', INFO is a plist used as a
  communication channel."
     (let ((section (plist-get info (intern (format ":html-%s" type))))
           (spec (org-html-format-spec info)))
       (when section
         (let ((section-contents
                (if (functionp section) (funcall section info)
                  (cond
                   ((stringp section) (format-spec section spec))
                   ((eq section 'auto)
                    (let ((date (cdr (assq ?d spec)))
                          (author (cdr (assq ?a spec)))
                          (email (cdr (assq ?e spec)))
                          (creator (cdr (assq ?c spec)))
                          (validation-link (cdr (assq ?v spec))))
                      (concat
                       (and (plist-get info :with-date)
                            (org-string-nw-p date)
                            (format "<p class=\"date\">%s: %s</p>\n"
                                    (org-html--translate "Date" info)
                                    date))
                       (and (plist-get info :with-author)
                            (org-string-nw-p author)
                            (format "<p class=\"author\">%s: %s</p>\n"
                                    (org-html--translate "Author" info)
                                    author))
                       (and (plist-get info :with-email)
                            (org-string-nw-p email)
                            (format "<p class=\"email\">%s: %s</p>\n"
                                    (org-html--translate "Email" info)
                                    email))
                       (and (plist-get info :time-stamp-file)
                            (format
                             "<p class=\"date\">%s: %s</p>\n"
                             (org-html--translate "Last update" info)
                             (format-time-string
                              (plist-get info :html-metadata-timestamp-format))))
                       (and (plist-get info :with-creator)
                            (org-string-nw-p creator)
                            (format "<p class=\"creator\">%s</p>\n" creator))
                       (and (org-string-nw-p validation-link)
                            (format "<p class=\"validation\">%s</p>\n"
                                    validation-link)))))
                   (t
                    (let ((formats (plist-get info (if (eq type 'preamble)
                                                       :html-preamble-format
                                                     :html-postamble-format)))
                          (language (plist-get info :language)))
                      (format-spec
                       (cadr (or (assoc-string language formats t)
                                 (assoc-string "en" formats t)))
                       spec)))))))
           (let ((div (assq type (plist-get info :html-divs))))
             (when (org-string-nw-p section-contents)
               (concat
                (format "<%s id=\"%s\" class=\"%s\">\n"
                        (nth 1 div)
                        (nth 2 div)
                        org-html--pre/postamble-class)
                (org-element-normalize-string section-contents)
                (format "</%s>\n" (nth 1 div)))))))))
#+end_src
*** TOC toc

(add-to-list 'headline-list (append-next-numeral headline-list "title"))

#+begin_src elisp

  (defun org-blog-toc (depth info &optional scope)
    "Build a table of contents.
       DEPTH is an integer specifying the depth of the table.  INFO is
       a plist used as a communication channel.  Optional argument SCOPE
       is an element defining the scope of the table.  Return the table
       of contents as a string, or nil if it is empty."

    (setq headline-list nil) ;; reinitialize the headline list
    (let ((toc-entries
           (mapcar (lambda (headline)
                     (cons (org-blog--format-toc-headline headline info)  ;; format headlines
                           (org-export-get-relative-level headline info)))
                   (org-export-collect-headlines info depth scope))))

      (when toc-entries
        (let ((toc (concat
                    "<a id='toc-button'>"
                    "<svg viewBox='-5 0 10 8' width='30'> <line y2='8' stroke='#000' stroke-width='10' stroke-dasharray='2 1'></line>"
                    "</svg></a>"
                    ;; (when (exporting-article-p info) "<a href='index.html'><img src='./img/7dnz.png'></a>")
                    "<nav id='text-TOC'>"
                    (org-html--toc-text toc-entries)
                    "</nav>"
                    "\n")))
          (if scope toc
            (let ((outer-tag (if (org-html--html5-fancy-p info) "nav" "div")))
              (concat (format "<%s id='TOC' class='toc'>\n" outer-tag)
                      (let ((top-level (plist-get info :html-toplevel-hlevel)))
                        ;;(format "<h%d>%s</h%d>\n" top-level (org-html--translate "" info) top-level) ;;"Table of Contents"
                        )
                      toc
                      (format "</%s>\n" outer-tag))))))))
#+end_src

#+begin_src elisp

  (defun org-blog--format-toc-headline (headline info)
    "Return an appropriate table of contents entry for HEADLINE.
     INFO is a plist used as a communication channel."
    (let* ((headline-number (org-export-get-headline-number headline info))
           (todo (and (plist-get info :with-todo-keywords)
                      (let ((todo (org-element-property :todo-keyword headline)))
                        (and todo (org-export-data todo info)))))
           (todo-type (and todo (org-element-property :todo-type headline)))
           (priority (and (plist-get info :with-priority)
                          (org-element-property :priority headline)))
           (text  (string-replace "\"" "&quot;"
                                     (org-export-data-with-backend
                                      (org-export-get-alt-title headline info)
                                      (org-export-toc-entry-backend 'html)
                                      info)))
           (headline-x (append-next-numeral
                        headline-list
                        (replace-regexp-in-string "['\" ]" "-" text)))
           (tags (and (eq (plist-get info :with-tags) t)
                      (org-export-get-tags headline info))))

      (add-to-list 'headline-list headline-x)
      (if (plist-get info :export-article)
          (format "<a href=\"#%s\">%s</a>"
                  ;; Label.
                  ;; text
                  headline-x
                  ;;(or (org-element-property :CUSTOM_ID headline)
                  ;;(org-export-get-reference headline info))
                  ;; Body.
                  (concat
                   ;; section numbering; todo: make it optional
                   ;; (and (not (org-export-low-level-p headline info))
                   ;;      (org-export-numbered-headline-p headline info)
                   ;;      (concat (mapconcat #'number-to-string headline-number ".")
                   ;;              ". "))
                   text
                   ;;(apply (plist-get info :html-format-headline-function)
                   ;;todo todo-type priority text tags :section-number nil)

                   ))
        (format "<a href='%s.html'>%s</a>" (sluggify text) text) ;; todo : headline with formatting

        )))
#+end_src
*** node properties
#+begin_src emacs-lisp
  (defun org-html-node-property (node-property _contents _info)
    "Transcode a NODE-PROPERTY element from Org to HTML.
     CONTENTS is nil.  INFO is a plist holding contextual
     information."
    (format "%s ??:%?? s"
            (org-element-property :key node-property)
            (let ((value (org-element-property :value node-property)))
              (if value (concat " " value) ""))))
#+end_src
*** footnotes / sidenotes

When the screen is large enough, (or allowing a slide on the left)
fotnotes may be displayed as sidenotes
 [fn::like so, right next to the place where it's defined. We may consider distributing the note
 on the left or on the right of the column according to where it is
 in the text (closer to the right or to the left) are links rendered correctly?
]. The regular display should be kept, for proper display
on tablets or prints. However forward footnote references needs to be fixed.[fn:2]


#+begin_src elisp
  (defun org-blog-sidenote (footnote-reference _contents info)
    "Transcode a FOOTNOTE-REFERENCE element from Org to HTML.
  CONTENTS is nil.  INFO is a plist holding contextual information."
    (concat
     ;; Insert separator between two footnotes in a row.
     (let ((prev (org-export-get-previous-element footnote-reference info)))
       (when (eq (org-element-type prev) 'footnote-reference)
         (plist-get info :html-footnote-separator)))
     (let* ((n (org-export-get-footnote-number footnote-reference info))
            (sid (format "sfn.%d" n)) ; #id of sidenote
            (ids (format "fns.%d" n)) ; #fn to side
            (idr (format "fnr.%d" n)) ; #id of reference (bottom)
            (id (format "fn.%d%s"
                        n
                        (if (org-export-footnote-first-reference-p footnote-reference info)
                            ""
                          ".100"))))

       ; display footnote referencing a note on the side (inline) or at the bottom of page
       (concat
        "<span class='sidenote'>"
        (org-html--anchor sid n
                          (format " class='footnum' href='#fns.%d' role='doc-backlink'" n) info)
        (org-export-data
         (org-export-get-footnote-definition footnote-reference info) info)
        "</span>"
        (format
         (plist-get info :html-footnote-format)
         (org-html--anchor ids n
                           (format " class='footref fn-side' href='#sfn.%d' role='doc-backlink'" n) info))
        (format
         (plist-get info :html-footnote-format)
         (org-html--anchor idr  n
                           (format " class='footref fn-bottom' href='#fn.%d' role='doc-backlink'" n) info))
        ))))
#+end_src


para ?

[fn:2] bug The footnote text is considered as regular text (the same way the fontifiation occurs is emacs)
*** Derived backend definition
missing sections in the translation will make the
export only partial, but without any warning.

: (setq org-html-html5-fancy t)


#+begin_src elisp
  (require 'ox-html)


  (let ((org-export-before-parsing-hook '(org-ref-bbl-preprocess)))
    (org-export-define-derived-backend 'weblog 'html

    ;;: menu-entry ;; broken
     ;;'(?2 "Export to blog"
    ;; ((?f "To file" org-export-subtree-to-html)))
          ;;((?H "To temporary buffer" org-html-export-as-html))

    :options-alist
    '((:html-head-include-default-style nil "html-style" nil)
      (:html-head-include-scripts nil "html-scripts" nil)
      (:html-head-include-scripts nil "html-scripts" nil)
      (:html-doctype "HTML_DOCTYPE" nil "html5")
      (:html-html5-fancy nil t t)
      ;;(:html-postamble-format nil nil org-html-postamble-format)
      ;;(:html-self-link-headlines nil t t)
      )
    :translate-alist
    '(
      (drawer . nil) ;; not visible
      (src-block . src-code-block)
      (inner-template . org-blog-inner-template)
      (headline . org-blog-headline)
      (template . org-blog-template)
      (statistics-cookie . blog-html-statistics-cookie)
      (link . org-blog-html-link)
      (footnote-reference . org-blog-sidenote)
      (comment-block . nil) ;; this is ignored, so we use org-export-org first
      ;;(property-drawer . nil)
      ;;(paragraph . org-weblog-html-paragraph)
      ;;(node-property . org-html-node-property)
      ;;(underline . org-blog-html-underline)
      )))
#+end_src
*** Preserve nbsp

#+begin_src elisp
  (defun my-html-nobreak-space-filter (text backend info)
    (and (org-export-derived-backend-p backend 'weblog)
         (replace-regexp-in-string " " "&nbsp;" text)))

  (add-to-list 'org-export-filter-plain-text-functions
               #'my-html-nobreak-space-filter)
#+end_src
*** TODO paragraph

Need to override this one just to handle images
differently in ToC and article.
Consider using two exporters:
org-article-weblog
and org-toc-weblog.
it's using :html-self-link-headlines as marker

Is the html5-fancy also in a similar situation ?

#+begin_src elisp
  (defun org-weblog-html--wrap-image (contents info &optional caption label)
    "see org-html--wrap-image in ox-html.el
     this adds minature management
    "
    (let ((html5-fancy (org-html--html5-fancy-p info)))
      (format "\n"

              ;; (if (plist-get info :html-self-link-headlines) ;; TODO and replace by ToC-gen
                  (if html5-fancy
                      "<figure%s>\n%s%s\n</figure>"
                    "<div%s class=\"figure\">\n%s%s\n</div>")
              ;; ID.
              (if (org-string-nw-p label) (format " id=\"%s\"" label) "")
              ;; Contents.
              (if html5-fancy contents (format "<p>%s</p>" contents))
              ;; Caption.
              (if (not (org-string-nw-p caption)) ""
                (format (if html5-fancy "\n<figcaption>%s</figcaption>"
                          "\n<p>%s</p>")
                        caption)))))
#+end_src

#+begin_src elisp
  (defun org-weblog-html-paragraph (paragraph contents info)
    "Transcode a PARAGRAPH element from Org to HTML.
  CONTENTS is the contents of the paragraph, as a string.  INFO is
  the plist used as a communication channel."
    (let* ((parent (org-export-get-parent paragraph))
           (parent-type (org-element-type parent))
           (style '((footnote-definition " class=\"footpara\"")
                    (org-data " class=\"footpara\"")))
           (attributes (org-html--make-attribute-string
                        (org-export-read-attribute :attr_html paragraph)))
           (extra (or (cadr (assq parent-type style)) "")))
      (cond
       ((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)))))
        ;; First paragraph in an item has no tag if it is alone or
        ;; followed, at most, by a sub-list.
        contents)
       ((org-html-standalone-image-p paragraph info)
        ;; Standalone image.
        (let ((caption
               (let ((raw (org-export-data
                           (org-export-get-caption paragraph) info))
                     (org-html-standalone-image-predicate
                      #'org-html--has-caption-p))
                 (if (not (org-string-nw-p raw)) raw
                   (concat "<span class=\"figure-number\">"
                           (format (org-html--translate "Figure %d:" info)
                                   (org-export-get-ordinal
                                    (org-element-map paragraph 'link
                                      #'identity info t)
                                    info nil #'org-html-standalone-image-p))
                           " </span>"
                           raw))))
              (label (org-html--reference paragraph info)))

          (org-weblog-html--wrap-image contents info caption label)))
       ;; Regular paragraph.
       (t (format "<p%s%s>\n%s</p>"
                  (if (org-string-nw-p attributes)
                      (concat " " attributes) "")
                  extra contents)))))
#+end_src
*** images
#+begin_src elisp
  (defun org-blog-html-link(link desc info)
    (if (plist-get info :export-article)
      (org-html-link link desc info)
      (org-string-nw-p desc))
    )
#+end_src
*** Style.css
file:./css/style.css
#+begin_src elisp
  (setq org-html-head"

  <link rel='stylesheet' type='text/css' href='css/fonts/PT-sans.css'/>
  <link rel='stylesheet' type='text/css' href='css/style.css'/>
  <link rel='stylesheet' type='text/css' href='css/prism.css'/>
  <script src='js/jquery.min.js' rel='preload'></script>
  <script src='js/prism.js' rel='preload'></script>
  <script src='js/script.js'></script>
  <link rel='shortcut icon' href='favicon.gif'/>
    ")
#+end_src

#+results: #+begin_example


<link rel='stylesheet' type='text/css' href='css/fonts/PT-sans.css'/>
<link rel='stylesheet' type='text/css' href='css/style.css'/>
<link rel='stylesheet' type='text/css' href='css/prism.css'/>
<script src='js/jquery.min.js' rel='preload'></script>
<script src='js/prism.js' rel='preload'></script>
<script src='js/script.js'></script>
<link rel='shortcut icon' href='favicon.gif'/>

#+end_example
*** Head
For memo,
here is a  <head>
#+begin_src html
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5">
  <meta name="referrer" content="no-referrer">

  <script src='script.js'>
  <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
  <link rel="manifest" href="/manifest.json">
  <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
  <meta name="msapplication-TileColor" content="#ffc40d" />
  <meta name="theme-color" content="#ffffff" />

  <link href="https://github.com/7d.nz/" rel="me">

  <meta property="og:title" content="" />
  <meta property="og:description"
        content=" …
                 " />
  <meta property="og:type" content="article" />
  <meta property="og:url" content="" />

  <meta property="article:published_time" content="2020-11-02T00:00:00&#43;01:00"/>
  <meta property="article:modified_time" content="2020-11-02T00:00:00&#43;01:00"/>

  <meta name="twitter:card" content="summary"/><meta name="twitter:image" content="/android-chrome-256x256.png"/>
  <meta name="twitter:title" content=""/>
  <meta name="twitter:description" content=""/>
  <meta name="DC.Creator" content="Phil.Estival"/>
#+end_src
** • Export to html: entry point                                     :tagged:
./img/parachute.png

note that org-entry-get looks into org-special-properties first.

: (org-entry-get nil "FILE" t)
: (org-entry-get nil "TODO" t)
: (org-entry-get nil "ALLTAGS" t)


If an interactive export of the entire buffer is desired :
#+begin_src emacs-lisp
  (defun org-blog-export-to-html
      (&optional async subtreep visible-only body-only ext-plist)
    "Export current buffer to a HTML file for a blog page.

     If narrowing is active in the current buffer, only export its
     narrowed part.

     If a region is active, export that region.

     A non-nil optional argument ASYNC means the process should happen
     asynchronously.  The resulting file should be accessible through
     the `org-export-stack' interface.

     When optional argument SUBTREEP is non-nil, export the sub-tree
     at point, extracting information from the headline properties
     first.

     When optional argument VISIBLE-ONLY is non-nil, don't export
     contents of hidden elements.

     When optional argument BODY-ONLY is non-nil, only write code
     between \"<body>\" and \"</body>\" tags.

     EXT-PLIST, when provided, is a property list with external
     parameters overriding Org default settings, but still inferior to
     file-local settings.

     Return output file's name."
      (message "export-to")
    (interactive)
    (let* (
           (filename (or (org-entry-get nil "EXPORT_FILE_NAME" t)
                         (sluggify (plist-get (timestamp-and-title (nth 4 (heading-components-at-level 1))) :title) )))

           ;;(org-export-coding-system org-html-coding-system)
      )

      (message "[ox-blog] exporting to html file %s.html" filename)

      ;;(message "TAGS : %s" (org-entry-get nil "ALLTAGS" t))
      ;; here the tags display fine
      ;; in the template, there's only the first L2 section
      ;; find a way to STUFF a property transmitted along the way

      (org-export-to-file 'weblog (concat filename ".html")
        async subtreep visible-only body-only ext-plist
      )))
#+end_src
** Export subtree

This is from ox-hugo.el
#+begin_src emacs-lisp
  ;; 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-blog--get-tags 'org-get-tags)
      (defalias 'org-blog--get-tags 'org-get-tags-at)))
#+end_src

Now, we aren't using any of these,
but they point out intresting display feature
that may be introduced later on.
#+begin_src emacs-lisp
  (defun org-hugo--selective-property-inheritance ()
    "Return a list of properties that should be inherited."
    (let ((prop-list '(;;"HUGO_FRONT_MATTER_FORMAT"
                       ;;"HUGO_PREFER_HYPHEN_IN_TAGS"
                       ;;"HUGO_PRESERVE_FILLING"
                       ;;"HUGO_DELETE_TRAILING_WS"
                       ;;"HUGO_ALLOW_SPACES_IN_TAGS"
                       ;;"HUGO_BLACKFRIDAY"
                       ;;"HUGO_SECTION"
                       ;;"HUGO_SECTION*"
                       ;;"HUGO_BUNDLE"
                       ;;"HUGO_BASE_DIR"
                       ;;"HUGO_CODE_FENCE"
                       ;;"HUGO_MENU"
                       ;;"HUGO_CUSTOM_FRONT_MATTER"
                       ;;"HUGO_DRAFT"
                       ;;"HUGO_ISCJKLANGUAGE"
                       ;;"KEYWORDS"
                       ;;"HUGO_MARKUP"
                       ;;"HUGO_OUTPUTS"
                       ;;"HUGO_TAGS"
                       ;;"HUGO_CATEGORIES"
                       ;;"HUGO_SERIES"
                       ;;"HUGO_TYPE"
                       ;;"HUGO_LAYOUT"
                       ;;"HUGO_WEIGHT"
                       ;;"HUGO_RESOURCES"
                       ;;"HUGO_FRONT_MATTER_KEY_REPLACE"
                       ;;"HUGO_DATE_FORMAT"
                       ;;"HUGO_WITH_LOCALE"
                       ;;"HUGO_LOCALE"
                       ;;"HUGO_PAIRED_SHORTCODES"
                       "DATE" ;Useful for inheriting same date to same posts in different languages
                       ;;"HUGO_PUBLISHDATE"
                       ;;"HUGO_EXPIRYDATE"
                       ;;"HUGO_LASTMOD"
                       ;;"HUGO_SLUG" ;Useful for inheriting same slug to same posts in different languages
                       ;;"HUGO_PANDOC_CITATIONS"
                       ;;"BIBLIOGRAPHY"
                       ;;"HUGO_AUTO_SET_LASTMOD"
                       "AUTHOR")))
      (mapcar (lambda (str)
                (concat "EXPORT_" str))
              prop-list)))
#+end_src

ox-hugo.el::3904

Renamed it so there's no confusion if ox-blog is also active:
#+begin_src elisp
  (defun org-blog--get-element-path (element info)
    "Return the section path of ELEMENT.
  INFO is a plist holding export options."
    (let ((root (or (org-export-get-node-property :EXPORT_HUGO_SECTION element :inherited)
                    (plist-get info :hugo-section)))
          (filename (org-export-get-node-property :EXPORT_FILE_NAME element :inherited))
          (current-element element)
          fragment fragments)
      ;; Iterate over all parents of current-element, and collect
      ;; section path fragments.
      (while (and current-element
                  (not (org-export-get-node-property :EXPORT_HUGO_SECTION current-element nil)))
        ;; Add the :EXPORT_HUGO_SECTION* value to the fragment list.
        (when (setq fragment (org-export-get-node-property :EXPORT_HUGO_SECTION* current-element nil))
          (push fragment fragments))
        (setq current-element (org-element-property :parent current-element)))
      ;; Return the root section, section fragments and filename
      ;; concatenated.
      (concat
       (file-name-as-directory root)
       (mapconcat #'file-name-as-directory fragments "")
       filename)))
#+end_src

#+begin_src elisp
  (defun org-blog--get-pre-processed-buffer ()
    "Returns a pre-processed copy of the current buffer.
     Internal links to other subtrees are converted to external
     links."
    (let* ((buffer (generate-new-buffer (concat "*Ox-hugo Pre-processed " (buffer-name) " *")))
           ;; Create an abstract syntax tree (AST) of the Org document
           ;; in the current buffer.
           (ast (org-element-parse-buffer))
           ;;(org-use-property-inheritance (org-hugo--selective-property-inheritance))
           (info (org-combine-plists
                  (list :parse-tree ast)
                  (org-export--get-export-attributes 'hugo)
                  (org-export--get-buffer-attributes)
                  (org-export-get-environment 'hugo)))
           (local-variables (buffer-local-variables))
           (bound-variables (org-export--list-bound-variables))
           vars)
      (with-current-buffer buffer
        (let ((inhibit-modification-hooks t)
              (org-mode-hook nil)
              (org-inhibit-startup t))

          (org-mode)
          ;; Copy specific buffer local variables and variables set
          ;; through BIND keywords.
          (dolist (entry local-variables vars)
            (when (consp entry)
              (let ((var (car entry))
                    (val (cdr entry)))
                (and (not (memq var org-export-ignored-local-variables))
                     (or (memq var
                               '(default-directory
                                  buffer-file-name
                                  buffer-file-coding-system))
                         (assq var bound-variables)
                         (string-match "^\\(org-\\|orgtbl-\\)"
                                       (symbol-name var)))
                     ;; Skip unreadable values, as they cannot be
                     ;; sent to external process.
                     (or (not val) (ignore-errors (read (format "%S" val))))
                     (push (set (make-local-variable var) val) vars)))))

          ;; Process all link elements in the AST.
          (org-element-map ast 'link
            (lambda (link)
              (let ((type (org-element-property :type link)))
                (when (member type '("custom-id" "id" "fuzzy"))
                  (let* ((raw-link (org-element-property :raw-link link))
                         (destination (if (string= type "fuzzy")
                                          (org-export-resolve-fuzzy-link link info)
                                        (org-export-resolve-id-link link info)))
                         (source-path (org-blog--get-element-path link info)) ;; see above
                         (destination-path (org-blog--get-element-path destination info))
                         (destination-type (org-element-type destination)))
                    ;; (message "[ox-hugo pre process DBG] destination type: %s" destination-type)

                    ;; Change the link if it points to a valid
                    ;; destination outside the subtree.
                    (unless (equal source-path destination-path)
                      (let ((link-desc (org-element-contents link))
                            (link-copy (org-element-copy link)))
                        ;; (message "[ox-hugo pre process DBG] link desc: %s" link-desc)
                        (apply #'org-element-adopt-elements link-copy link-desc)
                        (org-element-put-property link-copy :type "file")
                        (org-element-put-property
                         link-copy :path
                         (cond
                          ;; If the destination is a heading with the
                          ;; :EXPORT_FILE_NAME property defined, the
                          ;; link should point to the file (without
                          ;; anchor).
                          ((org-element-property :EXPORT_FILE_NAME destination)
                           (concat destination-path ".org"))
                          ;; Hugo only supports anchors to headlines, so
                          ;; if a "fuzzy" type link points to anything
                          ;; else than a headline, it should point to
                          ;; the file.
                          ((and (string= type "fuzzy")
                                (not (string-prefix-p "*" raw-link)))
                           (concat destination-path ".org"))
                          ;; In "custom-id" type links, the raw-link
                          ;; matches the anchor of the destination.
                          ((string= type "custom-id")
                           (concat destination-path ".org::" raw-link))
                          ;; In "id" and "fuzzy" type links, the anchor
                          ;; of the destination is derived from the
                          ;; :CUSTOM_ID property or the title.
                          (t
                           (let ((anchor (org-hugo--get-anchor destination info)))
                             (concat destination-path ".org::#" anchor)))))
                        ;; If the link destination is a heading and if
                        ;; user hasn't set the link description, set the
                        ;; description to the destination heading title.
                        (when (and (null link-desc)
                                   (equal 'headline destination-type))
                          (let ((headline-title
                                 (org-export-data-with-backend
                                  (org-element-property :title destination) 'ascii info)))
                            ;; (message "[ox-hugo pre process DBG] destination heading: %s" headline-title)
                            (org-element-set-contents link-copy headline-title)))
                        (org-element-set-element link link-copy))))))))

          ;; Workaround to prevent exporting of empty special blocks.
          (org-element-map ast 'special-block
            (lambda (block)
              (when (null (org-element-contents block))
                (org-element-adopt-elements block ""))))

          ;; Turn the AST with updated links into an Org document.
          (insert (org-element-interpret-data ast))
          (set-buffer-modified-p nil)))
      buffer))
#+end_src


UUID to keep track of sections.
I tried not to, but this is unavoidable in the end.
#+begin_src elisp
  (defun uuid ()
    (md5 (format "%s%s%s%s%s%s%s%s%s"
                 (user-uid)
                 (emacs-pid)
                 (system-name)
                 (user-full-name)
                 (current-time)
                 (emacs-uptime)
                 (garbage-collect)
                 ;;(buffer-string)
                 (random)
                 (recent-keys))))
#+end_src


#+begin_src elisp
  (defcustom org-blog-regen-toc&tags t
    "is the toc and tags updated upon saving ?
  this can be long and should be turned off to speed things up"
    :group 'org-blog
  )
#+end_src

(setq org-blog-regen-toc&tags nil)
(setq org-blog-regen-toc&tags t)
#+results:
: org-blog-regen-toc&tags


#+begin_src elisp

    (defun org-export-subtree-to-html (&optional async visible-only all-subtrees)
      "Export the current subtree to a html post.
    inspired by the ox-hugo exporter.

  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.
  - or, 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."

      (setq headline-list '()) ;; flush the list of headlines (prevent dups ids)
      (setq narrowed (buffer-narrowed-p))

      ;; (dolist (fn '(org-babel-exp-src-block write-region)
      ;;    (advice-add fn :around #'org-hugo--advice-silence-messages)))

      ;; Publish only the current subtree
      (save-restriction
        (save-excursion
          (ignore-errors
            ;;(org-back-to-heading :invisible-ok)

            (condition-case err
                (outline-up-heading
                 (+ -1(nth 1 (org-heading-components))))
              (error () nil))  ;; already at top level
            (when (not narrowed)
              (org-narrow-to-subtree)))

          (let ((subtree (org-element-at-point)))
            (if (not (member (nth 2 (org-heading-components)) org-done-keywords)) ;; todo state in the DONE set
                (progn
                  (widen)
                  (message "section is not in a DONE state (%s)" (org-heading-components)))
              ;; then proceed ..
              ;(message "subtree ???")
              (let* ((info (org-combine-plists
                            (org-export--get-export-attributes
                             'blog subtree visible-only)
                            (org-export--get-buffer-attributes)
                            (org-export-get-environment 'blog subtree)))
                     (id (save-excursion (org-up-heading 1) (org-entry-get nil "ID")))
                     (export-file-name (save-excursion (org-up-heading 1)
                                                       (org-entry-get nil "EXPORT_FILE_NAME")))
                     (is-commented (org-element-property :commentedp subtree))
                     (dir default-directory)

                     (time-&-title (timestamp-and-title
                                    (org-element-property :title subtree)))
                     (title (plist-get time-&-title :title))
                     (title-tags (org-entry-get nil "ALLTAGS" t))
                     (exclude-tags (plist-get info :exclude-tags)) ;; list of exclusion tags
                     (is-excluded (seq-intersection ;; is this section excluded ?
                                   '(split-string (or title-tags "") ":")
                                   '(exclude-tags)))
                     )

                (cond
                 (is-commented
                  (message "[ox-weblog] `%s' was not exported as that subtree is commented"
                           title))
                 (is-excluded
                  (message "[ox-weblog] `%s' was not exported as it is tagged with an exclude tag `%s'"
                           title matched-exclude-tag))
                 (t
                  (if all-subtrees
                      (progn
                        (setq org-blog--subtree-count (1+ org-blog--subtree-count))
                        (message "[ox-weblog] %d/ exporting `%s' .." org-blog--subtree-count title))
                    (message "[ox-weblog] exporting `%s' .." title))

                  ;; keep track of the post by setting an ID if there was none
                  (when (not id)
                    (save-excursion
                      (setq id (uuid))
                      (message "insert new id %s" id)
                      (org-up-heading 1)
                      (org-entry-put nil "ID" id)
                      ))

                  ;; Do the buffer pre-processing only if the user is
                  ;; exporting only the current post subtree.
                  (let*((current-outline-path (org-get-outline-path :with-self))
                        (buffer (org-blog--get-pre-processed-buffer)) ;;  TODO  verify this (links conversion)
                        (sourcefile (f-filename (buffer-file-name))) ;; this file
                        (filename (or (org-entry-get nil "EXPORT_FILE_NAME" t)
                                      (sluggify title)) )
                        (file.org (concat filename ".org"))
                        (org/file.org (concat"./org/"  file.org))
                        ;;(tmp/file.org (concat"/tmp/"  file.org))
                        (file.html (concat filename ".html"))
                        (article-title (concat org-weblog-title " :: " title)))


                    (message "htmlizing")
                    (cd dir)
                    ;;(with-current-buffer buffer
                    (with-current-buffer (org-org-export-as-org)
                      ;;(kill-buffer filename)
                      ;;(uniquify-rename-buffer title)
                      (outline-show-all)

                      (if (eq t(compare-strings (concat filename ".org") nil nil
                                                sourcefile nil nil))
                          (message
                           "[ox-blog] org post export ( source file (%s) and post title (%s) share
                                 the same name. Would overwrite the former. Skipping."
                           sourcefile filename)
                        ;;(progn
                        ;;(when (get-buffer file.org) (kill-buffer file.org))
                        ;; otherwise write file throws an error and they'll stack buffer<1><2>..
                        ;;(org-org-export-as-org))
                        (write-file org/file.org))
                      ;;(message "wrote %s" org/file.org)

                      (cd dir)
                      (with-current-buffer (htmlize-buffer) ;; why not the same dir ?
                        (rename-buffer article-title)
                        (write-file (concat org/file.org ".html") nil)
                        (kill-buffer)
                        )

                      (kill-buffer))

                    ;;(message ">>> %s %s %s" dir filename title)
                    (with-current-buffer buffer
                      ;;(org-next-visible-heading) << [BUG] wants to save the buffer...
                      ;; (goto-char (point-max)) ;; ...hence
                      ;; (org-up-heading 1) ;; bibliography is exported too
                      (org-up-heading 1)

                      (message "[ox-blog] exporting to html > %s.html" filename)
                      (setq ret
                            ;; the tags are retrieved fine until here
                            ;; but inside the template,
                            ;; calling for (org-entry-get nil "ALLTAGS" t) or returning to top header
                            ;; will only get the first L2 section.
                            ;; Pass the tags headline here, into the ext-plist.
                            (org-export-to-file 'weblog file.html
                              async :subtreep visible-only nil
                              (list :title-tags title-tags :export-article t )))

                      (message "exported %s %s (%s)" filename title title-tags)

                      (cd dir)
                      ;; negate for export only (no index update)
                      (when org-blog-regen-toc&tags
                        (save-excursion
                          (blog-toc-insert-headline-and-summary
                           filename time-&-title title-tags id export-file-name)))

                      (message "killing buffer %s" file.org)
                      ;;(kill-buffer file.org)
                      (message "subtree exported")
                      )
                    (message "killing buffer %s" buffer)
                    (kill-buffer buffer)
                    (other-window 1)
                    )))
                ret))))))

    ;; (dolist (fn '(org-babel-exp-src-block write-region)
    ;;    (advice-remove fn #'org-hugo--advice-silence-messages)))

    ;; ((when (not narrowed)
    ;;    (widen));; but should do it only if it wasn't narrowed in the first place
    ;;  )
#+end_src
*** Elementary styles
#+begin_src elisp
  (setq org-html-text-markup-alist
    '((bold . "<b>%s</b>")
      (code . "<code class='code'>%s</code>")
      (italic . "<i>%s</i>")
      (strike-through . "<del>%s</del>")
      (underline . "<u>%s</u>")
      (verbatim . "<code class='verbatim'>%s</code>")))
#+end_src
** Parts from ox-hugo
*** save hook, export
#+begin_src elisp

  (defun org-blog-export-to-html-after-save ()
    "Function for `after-save-hook' to run `org-export-subtree-to-html'.
  Need to check what goes on when Org Capture in progress."
    (unless (eq real-this-command 'org-capture-finalize)
          (save-excursion
        (org-export-subtree-to-html))))
            ;;;(org-export-blog))))

  ;; which we can shortcut to the export-subtree


  ;;;###autoload
  (define-minor-mode org-blog-auto-export-mode
    "Toggle auto exporting the subtree upon saving"
    :global nil
    :lighter "autoexport"
    :keymap org-mode-map
    ;; :keymap '(("C-x M-b t" . (setq org-blog-regen-tags-p (not org-blog-regen-toc&tags)))
    ;;          ("C-x M-b T" . (setq org-blog-regen-toc&tags (not org-blog-regen-toc&tags))))

    (if org-blog-auto-export-mode
        ;; mode is enabled
        (add-hook 'after-save-hook #'org-blog-export-to-html-after-save :append :local)
      ;; mode is disabled
      (remove-hook 'after-save-hook #'org-blog-export-to-html-after-save :local)))


  ;; export valid subtree

  (defun org-blog--get-valid-subtree ()
    "Return the Org element for a valid blog post subtree.
  The condition to check validity is that the STATE of the
  entry is in a DONE state.

  As this function is intended to be called inside a valid
  post subtree, doing so also moves the point to the beginning of
  the heading of that subtree.

  Return nil if a valid post subtree is not found.  The point
  will be moved in this case too."

    (if (member (nth 3 (org-element-at-point)) org-done-keywords)
        (cl-return entry)
      (cl-return nil)))
#+end_src

this is the entry point when in a headline to be exported.
And which can be hooked up to be saved.


[2021-06-02 mer. 00:39]  got it working for the export to md.
It asks for the target filename.

- [X] shoud be auto set with the slug
- [X] remove the filename/target assoc
- [X] next, give it the derived html backend.
- [X] use the tags to build indexes/Table of contents (which is an org file)


(org-export-subtree-to-html)   (profiler-report)  (profiler-stop)
*** TODO Change the target/publishing  directory

Here's how ox-hugo does:
#+begin_src elisp
  ;;;; Publication Directory
  (defun org-hugo--get-pub-dir (info)
    "Return the post publication directory path.

  The publication directory is created if it does not exist.

  INFO is a plist used as a communication channel."
    (let* ((base-dir (if (plist-get info :hugo-base-dir)
                         (file-name-as-directory (plist-get info :hugo-base-dir))
                       (user-error "It is mandatory to set the HUGO_BASE_DIR property")))
           (content-dir "content/")
           (section-path (org-hugo--get-section-path info))
           (bundle-dir (let ((bundle-path (or ;Hugo bundle set in the post subtree gets higher precedence
                                           (org-hugo--entry-get-concat nil "EXPORT_HUGO_BUNDLE" "/")
                                           (plist-get info :hugo-bundle)))) ;This is mainly to support per-file flow
                         (if bundle-path
                             (file-name-as-directory bundle-path)
                           "")))
           (pub-dir (let ((dir (concat base-dir content-dir section-path bundle-dir)))
                      (make-directory dir :parents) ;Create the directory if it does not exist
                      dir)))
      (file-truename pub-dir)))
#+end_src


#+begin_src elisp
  (defun org-hugo-export-to-md (&optional async subtreep visible-only)
    "Export current buffer to a Hugo-compatible Markdown file.

  If narrowing is active in the current buffer, only export its
  narrowed part.

  If a region is active, export that region.

  A non-nil optional argument ASYNC means the process should happen
  asynchronously.  The resulting file should be accessible through
  the `org-export-stack' interface.

  When optional argument SUBTREEP is non-nil, export the sub-tree
  at point, extracting information from the headline properties
  first.

  When optional argument VISIBLE-ONLY is non-nil, don't export
  contents of hidden elements.

  Return output file's name."
    (interactive)
    (org-hugo--before-export-function subtreep)
    ;; Allow certain `ox-hugo' properties to be inherited.  It is
    ;; important to set the `org-use-property-inheritance' before
    ;; setting the `info' var so that properties like
    ;; EXPORT_HUGO_SECTION get inherited.
    (let* ((org-use-property-inheritance (org-hugo--selective-property-inheritance))
       (info (org-combine-plists
          (org-export--get-export-attributes
           'hugo subtreep visible-only)
          (org-export--get-buffer-attributes)
          (org-export-get-environment 'hugo subtreep)))
       (pub-dir (org-hugo--get-pub-dir info))
       (outfile (org-export-output-file-name ".md" subtreep pub-dir)))
    ;; (message "[org-hugo-export-to-md DBG] section-dir = %s" section-dir)
    (prog1
      (org-export-to-file 'hugo outfile async subtreep visible-only)
      (org-hugo--after-export-function info outfile))))
#+end_src
*** • auto-export notice
Add a highlight when autoexport: enabled ? is active

This doesn't work in comment
and won't override an existing formating (like a strike)
nor a comment

#+begin_src elisp

  (defgroup org-web-blog nil
   "Eye candy for Org weblog"
   :tag "Org Web Blog"
   :group 'org-appearance)

  (defface blog-auto-export-enabled '((t :inherit default :foreground "chartreuse" :weight "bold"))
    "Face used to mention auto export is enabled"
    :group 'org-web-blog)

  (defface greenout '((t :inherit default :foreground "chartreuse" :background "chartreuse"))
    "Face blackout the ongoing interogation"
    :group 'org-web-blog)

  ;; (defface blackout '((t :inherit default :foreground "#081020" :background "#081020"))
  ;;   "Face blackout the ongoing interogation"
  ;;   :group 'org-web-blog)

  (add-hook 'org-blog-auto-export-mode-hook
   (lambda ()
     (font-lock-add-keywords nil
       '(("\\<autoexport: \\(enabled\\)" 1 'blog-auto-export-enabled prepend)
         ("\\<enabled \\(\\?\\)" 1 'greenout prepend)
         ))
     (font-lock-fontify-buffer)
     ))

  ;;
  ;;(setq 'org-blog-auto-export-mode-hook nil)
  ;;(font-lock-refresh-defaults)
  ;; to restore defaults
#+end_src
** Tags and summary
*** First attempts
with org-elements.el.
There's some doc on worg
but really, the sources of org say everything.
**** The complex way
Reparse elements. They are cons with (type, properties, &rest elements)

#+begin_src emacs-lisp

  (defun org-element-mapper (p)
    (cl-case (org-element-type p)
       ;;('paragraph  (org-element-mapper (org-element-contents p)))
       ('paragraph  (mapconcat #'identity (mapcar #'org-element-mapper (org-element-contents p)) ""))
       ('bold (format "*%s*" (car (org-element-contents p))))
       ('italic (format "/%s/"  (car (org-element-contents p))))
       ('verbatim (format "=%s= "  (org-element-property :value p)))
       ('plain-text (format "%s" p))
       ('link (org-element-property :raw-link p))
       ('src-block (format "\n#+src\n%s" (org-element-contents p)))
       (t
         ;;(if (consp p) p
         ;; (mapcar #'org-element-mapper p)
          (format "<%s> >%s [%s] " (org-element-type p) (type-of p) p )))))
#+end_src

#+begin_src emacs-lisp :results raw
  (org-element-map (org-element-parse-buffer) 'paragraph
    (lambda (paragraph)
      (let ((parent (org-element-property :parent paragraph)))
        (and (eq (org-element-type parent) 'section)
             (let ((first-child (car (org-element-contents parent))))
               (eq first-child paragraph))
             ;; Return value.
             paragraph))))
#+end_src

#+begin_src emacs-lisp
  (org-element-map (org-element-parse-buffer) 'paragraph
    (lambda (paragraph)
        (let ((parent (org-element-property :parent paragraph)))
          (and (eq (org-element-type parent) 'section)
               (let ((first-child (car (org-element-contents parent))))
                 (eq first-child paragraph))
               ;; Return value.
               (org-element-mapper paragraph)))))
#+end_src
**** other attempts
#+begin_src emacs-lisp :results raw
  (org-element-map (org-element-parse-buffer ) 'paragraph
    (lambda (paragraph)
      (let ((parent (org-element-property :parent paragraph)))
        (and (eq (org-element-type parent) 'section)
             (let ((first-child (car (org-element-contents parent))))
               (eq first-child paragraph))
             ;; Return value.
             (org-element-interpret-data paragraph)))))
#+end_src


#+begin_src emacs-lisp :results raw

  (org-element-map
    (org-element-parse-buffer 'object t)
    #'(paragraph)
    ;;#'org-element-mapper
    #'org-element-interpret-data
    nil nil )
#+end_src


#+begin_src emacs-lisp :results raw

  (save-excursion
     (org-up-heading 1)
     (org-narrow-to-subtree)
     (let* ((tree (org-element-parse-buffer 'object t)))
        (org-element-map tree '(paragraph)
          ;;#'identity
            (lambda (p) (org-element-contents p))
           nil nil t )))
#+end_src

#+begin_src emacs-lisp :results raw

  (save-excursion
    (let* (
           (tree (org-element-parse-buffer 'object t))
           )
      (org-element-map ;; tree
          tree
          '(paragraph bold)
        ;;#'identity
        ;;'org-element-type
        (lambda (p) (car (org-element-contents p)))
        nil nil nil t
        ;(lambda (p) (car (org-element-contents p))
        ))))
#+end_src
**** The easy way : org-interpret data

Getting only the paragraph
#+begin_src emacs-lisp :results raw replace

  (save-excursion
    (mapconcat #'identity
               (org-element-map
                   (org-element-parse-buffer 'object t)
                   '(paragraph)
                 ;; #'org-element-mapper
                 'org-element-interpret-data
                 nil nil )
               " "))
#+end_src
** Summary


#+begin_src elisp

  (defgroup org-weblog nil "the not so easy org blog" :version 26.1)

  (defcustom org-blog-words-in-summary
    100 "maximum number of words in a blog entry summary" :group 'org-weblog)
#+end_src

Gettings visible paragraphs
#+begin_src emacs-lisp
  (mapconcat #'identity (org-element-map (org-element-parse-buffer 'object t)
                            '(paragraph)
                          #'org-element-interpret-data
                          nil nil ) " ")
#+end_src

Take the begining of the text following the headline
to make a summary displayed in ./index.org.

#+begin_src elisp
  (defun subtree-summary ()
    (save-excursion
      (save-restriction
        (org-up-heading 1)
        (org-narrow-to-subtree)
        (org-show-all)
        (setq blog-org-structure
              (org-element-map
                  (org-element-parse-buffer 'object t)
                  'paragraph #'org-element-interpret-data))
        (or (org-entry-get nil "SUMMARY" t)
            (with-temp-buffer ;;file "/tmp/t"
              (insert
               (mapconcat #'identity
                          blog-org-structure
                          " "))
              (goto-char (point-min))
              (setq num 0)
              (while (and
                      (< num org-blog-words-in-summary)
                      (not (eq "summarystop" (word-at-point)))) ;; [2021-11-29 Mon 15:20] nope doesn't match
                (forward-word) ;; -strictly to ignore a comment line
                (setq num (+ 1 num)))
              (concat (buffer-substring-no-properties (point-min) (point)) "...")))
        )))
#+end_src


Young and unaware programmer as I once was, I did code my own string
library in C++. I met many bugs while doing (double frees among other
niceties...) learned the usage of operators and put them everywhere.

While coding it, I realized that a high level programming language or
a clever compiler would make it possible to create an editor able
to behave equally in an interactive mode and in a program: functions
behaviour could be tested interactively on samples; strings
operators and memory buffers would need to be 100% correct.
** ToC of articles: links and summaries

We don't create the index.org file,
there should already be one (for now)
with the following structure, so that
org-insert-heading-after-current works.
That's mostly because I'm going fast on that section

index.org
#+begin_src org
  ,#+TITLE: 7d.nz
  ,#+EMAIL: pe@7d.nz
  ,#+RSS_FEED_URL: https://7d.nz
#+end_src

./index.org is the default website's landing page
Open index.org and run (org-export-to-file 'weblog "index.html")
to change the ToC.

#+begin_src elisp
  (defcustom org-blog-toc-entry-file "index"
    "the index file holding the main ToC entry with all articles titles and summary" :group 'blaxorg)
#+end_src

and perhaps later paginated

#+begin_src elisp
  (defun blog-toc-insert-headline-and-summary
      (filename ttitle tags id export-filename)
    "add a new entry in index.org"
    (message "updating %s :: %s"  org-blog-toc-entry-file ".org" id)
    (let((summary (subtree-summary))
         (current (current-buffer))
         (index·org (concat org-blog-toc-entry-file ".org"))
         (index·html (concat org-blog-toc-entry-file ".html"))
         )

      ;; the form (with-temp-buffer ... insert-file)
      ;; is the only one I found to open, write and return
      ;; correctly from excursion
      (with-current-buffer(find-file index·org)

        (goto-char (point-min))
        ;; look for an entry already holding the id. If so, cut it.
        (while (and (outline-next-heading)
                    (if (equal (org-entry-get nil "ID") id)
                        (progn (org-cut-subtree) nil)
                      t)))

        (org-insert-heading)   ;; insert heading, date, title, tags and summmary
        (insert (concat
                 (plist-get ttitle :title) "  " tags "\n"
                 summary
                 "\n"
                 ))

        ;; #+OPTIONS: prop:t should  takes care of that
        ;; (#+OPTIONS: prop:t doesn't seem to work)
        (org-entry-put nil "ID" id) ;; insert id prop
        (when export-filename
          (org-entry-put nil "EXPORT_FILE_NAME" export-filename))
        (org-entry-put nil "DATE" (plist-get ttitle :timestamp))
        (mark-whole-buffer)       ;; sort
        (org-sort-entries nil ?R nil nil "DATE") ;; most recent first
        (deactivate-mark)
        ;; non async for now so we can debug a few things
        (org-export-to-file 'weblog index·html) ;; nil nil nil nil
        ;;'(:html-self-link-headlines t)) ;; dblcheck that option plz
        (write-file index·org)
        (when org-blog-regen-tags-p (org-blog-export-toc-tags))
        (org-delete-keyword "TITLE") ;; that's a bit hacky. Better style it with a display:none
        (org-insert-keyword "TITLE" " ")
        )
      (if (get-buffer index·org) ;; refresh buffer *index.org* if it's open
          (with-current-buffer index·org (revert-buffer t t)))))
#+end_src

#+begin_src example
  (blog-toc-insert-headline-and-summary
     "test.org"
     (timestamp-and-title (nth 4(heading-components-at-level 1)))
     (nth 5 (heading-components-at-level 1)) 0 "ex.org")
#+end_src
** ox-rss

#+begin_src emacs-lisp
  (use-package ox-rss)
  (let(
       (index·org (concat org-blog-toc-entry-file ".org"))
       )
    (with-current-buffer(find-file index·org)
      (org-delete-keyword "TITLE")
      (org-insert-keyword "TITLE" org-weblog-title)
      (org-rss-export-to-rss)
      (org-delete-keyword "TITLE")
      (org-insert-keyword "TITLE" " ")))
#+end_src

or C-e r r from index.org.
** Look for an entry  with a given property (id)
There is only one entry to a given article in the index.  In a
sophisticated solution, those files are linked in a merkel tree by
consecutive revision. Only one sha1 would then be required to retrieve
an entire branch and history, but this is not git nor venti.

#+begin_src emacs-lisp
  (while (and (outline-next-heading)
              (if (equal (org-entry-get nil "ID") id)
                  (progn (org-cut-subtree) nil)
                t)))
#+end_src
** To sort main ToC entries by time
#+begin_src emacs-lisp
  (mark-whole-buffer)
  (org-sort-entries nil ?T) ;; most recent first
#+end_src
** To prevent duplicates

When exporting a section and before writing the org file, the export
is triggered by title or body change.  a title change also implies, if
there's no EXPORT_FILE_NAME, a filename change but the text body might
have stayed the same.  In wich case, we ought to simply rename the
file.

Grepping for :ID: will also point duplicates.

The following is an idea to identify and book-keep exports.

check the body sha1 isn't found in the dir.sig file :
the body is the post section past the first headline.
If no such file exists, create it and append
the sha1 of the text-body followed by filename

Register it in a dir.sig file.
This is where to look for :
- filenames. They're unique as they are in the same directory.
- sha1 of text body. unique.

If one or the other is found twice, adjust accordingly.
- body changes : proceed, update the sha1
- title changes: move filename, update title.

If there's only an EXPORT_FILE_NAME change,
this is considering as both a change in body and title.

If there are both changes in body and title
then we can no longer keep track of the change.
We could still introduce a section Id in the properties,
but I'm trying to avoid to clutter the sources with
extra data. The sha1 may go in the listing and summary.
Here is a good place to mention we could map org-data
onto something more racidal : folders.

#+begin_src elisp
  (defun bookkeep-export-buffer()
     (goto-line 2)
     (sha1 buffer (point) (point-max)))
#+end_src

In the end, it's a far more complicated
solution than preserving an ID property.
Let's move on.
** Tags
*** list of tags in file


#+begin_src elisp
  (defun org-buffer-tags()
    (let (tags)
      (org-with-point-at (point-min)
        (while (outline-next-heading)
          ;; should be same level, but there's only L1 headlines in index.org
          (let ((curnt-tags (nth 5 (org-heading-components))))
            (when curnt-tags
              (mapcar (lambda (v) (setq tags (append tags (list v))))
                      (org-split-string curnt-tags ":"))))))
      (seq-uniq tags)))
#+end_src
(org-buffer-tags)

but there's already (org-get-buffer-tags).
(mapcar 'car (org-get-buffer-tags)).
*** Org export to org
#+begin_src elisp
  (defun blog-export-to-org-tag(tag)
    "export only selected tag as <tag>.org
     good care is advised with tags and filenames if they're not in their own folders"
    (setq org-export-select-tags '(tag))
    (org-org-export-to-org)
    (setq org-export-select-tags '("export"))) ;; restore export settings
#+end_src

Need to set an export file name,
so look for it, remove it if there's one
and set that one instead.
The cursor mark can stays after colon.

#+begin_example
  ,#+EXPORT_FILE_NAME: dev.org
#+end_example

Then Call org-kill-line, insert new name, continue.


To get the export file name:
/usr/local/share/emacs/lisp/org/ox.el:6417

#+begin_src emacs-lisp

  (org-with-point-at (point-min)
    (catch :found
      (let ((case-fold-search t))
        (while (re-search-forward
                "^[ \t]*#\\+EXPORT_FILE_NAME:[ \t]+\\S-" nil t)
          (let ((element (org-element-at-point)))
            (when (eq 'keyword (org-element-type element))
              (throw :found
                     (org-element-property :value element))))))))
#+end_src

From where I derived this generic function
#+begin_src elisp

  (defun org-search-keyword(keyword)
    (org-with-point-at (point-min)
      (catch :found
        (let ((case-fold-search t))
          (while (re-search-forward
                  (concat "^[ \t]*#\\+" keyword ":[ \t]+\\S-")
                  nil t)
            (let ((element (org-element-at-point)))
              (when (eq 'keyword (org-element-type element))
                (throw :found
                       (org-element-property :value element)))))))))
#+end_src

: (org-search-keyword "EXPORT_FILE_NAME")
: (org-insert-directive "EXPORT_FILE_NAME" "/tmp/t.org")


They are called #+keywords, but I think they should would better be named directives.
#+begin_src elisp
  (defun org-insert-keyword(keyword value)
    (org-with-point-at (point-min)
      (insert (concat "#+" keyword ": " value "\n"))))
#+end_src

#+begin_src elisp
  (defun org-delete-keyword(keyword)
    "kill a line with a keyword"
    (org-with-point-at (point-min)
      ;; clean EXPORT_FILE_NAME, if there's any
      (when (org-search-keyword keyword)
        (goto-char(point-min))
        (re-search-forward
         (concat "^[ \t]*#\\+" keyword ":[ \t]+")
         nil t)
        (org-beginning-of-line)
        (org-kill-line))))
#+end_src

Problem here:  I may need to derive the backend
for headlines with EXPORT_FILE_NAME property set
to correcly link the appropriate file.

Or I can completly ignore this feature and move on.
#+begin_src emacs-lisp
  (defun org-export-all-tags()
    (org-delete-keyword "EXPORT_FILE_NAME")
    (org-insert-keyword "EXPORT_FILE_NAME" "")
    ;;(insert (concat "#+EXPORT_FILE_NAME: \n"))
    (mapcar (lambda (tag)
              ((save-excursion
                  (insert tag ".org") ;; insert new name
                  (org-export-to-org-tag tag))
               (org-kill-line))
            (org-buffer-tags))
    (org-delete-keyword "EXPORT_FILE_NAME")))
#+end_src

#+begin_src elisp
  (defun org-blog-export-toc-tags()
    "for all tags at headline level 1
     produce one .org and one .html file with headlines and summaries"
    (message "generating tags files")
    (org-delete-keyword "EXPORT_FILE_NAME")
    (org-with-point-at (point-min)
      (insert (concat "#+EXPORT_FILE_NAME: \n"))
      (left-char)
      (mapcar (lambda (tag)
                (let ((org-file (concat tag ".org"))
                      (html-file (concat tag ".html")))
                  (save-excursion
                    (org-kill-line)
                    (insert org-file) ;; EXPORT_FILE_NAME: <org-file>
                    (setq org-export-select-tags (list tag))
                    (org-delete-keyword "TITLE")
                    (org-insert-keyword "TITLE" tag)
                    ;;(org-org-export-to-org) ; no need
                    ;;(with-current-buffer (find-file org-file)
                    (org-export-to-file 'weblog html-file); nil nil nil nil ;'(:html-self-link-headlines nil) ;;  ))
                    (org-kill-line) ;; back to #+EXPORT_FILE_NAME: █
                    )))
              ;;(mapcar 'car (org-get-buffer-tags)) ;; where does it differ from below ?
              (org-buffer-tags))
      (setq org-export-select-tags '("export"))))
#+end_src
** Language identifier mapping
The Syntax Highlighter uses sometimes other language identifiers for
source blocks than org-mode.  For example, where org-mode uses sh,
Syntax Highlighter uses bash.

The mappings goes in an alist for later use.

#+begin_src emacs-lisp
  (defconst ox-blog-language-terms
      ("emacs-lisp" . "lisp")
      ("elisp" . "lisp")
      ("sh" . "bash")))
#+end_src
** Exporting source code blocks

The source code exporting function, ox-blog-src-block, is
modelled on org-html-src-block in org-mode/lisp/ox-html.el.

To keep things simple, the caption and label code is deleted.  The
language identifier is mapped using the mapping defined in the alist
above. The HTML-formatting of the source code is removed, instead of
org-html-format-code we use org-export-unravel-code.  At last the
pre formatting in angles is changed to code in brackets.

#+begin_src elisp
  (defun ox-blog-src-block (src-block contents info)
    "Transcode a SRC-BLOCK element from Org to HTML.
     CONTENTS holds the contents of the item.  INFO is a plist holding
     contextual information."
    (if (org-export-read-attribute :attr_html src-block :textarea)
        (org-html--textarea-block src-block)
      (let ((language-term
             (or (cdr (assoc (org-element-property :language src-block)
                             ox-blog-language-terms))
                 (org-element-property :language src-block)))
            (code (car (org-export-unravel-code src-block))))
        (if (not language-term)
            (format "<pre class=\"example\"%s>\n%s</pre>" label code)
          (format "<div class=\"org-src-container\">\n%s\n</div>"
                  (format "\n[code lang=\"%s\"]%s[/code]" language-term code))))))
#+end_src
** Wrapping up and autoload

The following goes in to file:./.dir-locals.el

#+begin_src emacs-lisp :tangle .dir-locals.el
  ((nil . ((indent-tabs-mode . nil)
           (fill-column . 70)
           (sentence-end-double-space . t)))
   ("org-sources"  . ((org-mode . ( (eval . (org-blog-auto-export-mode)))))))
#+end_src
This will start the org-blog-auto-export,
loading the required libraries when opening
files in the directory.
** Less verbose message
https://scripter.co/using-emacs-advice-to-silence-messages-from-functions/
#+begin_src elisp
  (defun org-hugo--advice-silence-messages (orig-fun &rest args)
    "Advice function that silences all messages in ORIG-FUN."
    (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)))
#+end_src

or multiple function :
#+begin_src emacs-lisp
  (dolist (fn '(org-babel-exp-src-block write-region)
              (advice-add fn :around #'org-hugo--advice-silence-messages))
#+end_src
#+begin_src elisp
  ;;(advice-add 'org-blog-export-to-html :around #'org-hugo--advice-silence-messages)
#+end_src
** Possible improvements
Interactivity could be improved by activating
impatient mode.

The publishing process isn't long.
but since org-html-export-to-html can run asynchronously,
the async option could initiate a new emacs process,
which doesn't need to load your working init.el
and freeze the running session.

So, we'll go into starting emacs with an alternate init
file and deal about process management in a next chapter.

: emacs -q -l export.el ...

+argument ? -eval expression?
How does command line works in Emacs by the way?
Do we really need an asynchronous process?
What does ob-comint asyncs does?
Did you enjoy the reading?
Thank you, have a nice day and come back later for more.