diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..0293be9 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,56 @@ +name: CI + +on: + pull_request: + push: + paths-ignore: + - LICENSE + - README.org + +jobs: + build: + runs-on: ${{ matrix.os }} + continue-on-error: ${{ matrix.experimental }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + emacs-version: + - 26.3 + - 27.2 + - 28.2 + - 29.4 + - 30.1 + experimental: [false] + include: + - os: ubuntu-latest + emacs-version: snapshot + experimental: true + - os: macos-latest + emacs-version: snapshot + experimental: true + - os: windows-latest + emacs-version: snapshot + experimental: true + exclude: + - os: macos-latest + emacs-version: 26.3 + - os: macos-latest + emacs-version: 27.2 + + steps: + - uses: jcs090218/setup-emacs@master + with: + version: ${{ matrix.emacs-version }} + + - uses: emacs-eask/setup-eask@master + with: + version: 'snapshot' + + - uses: actions/checkout@v4 + + - name: Run tests + run: | + eask package + eask install + eask compile diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..47fd8d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.elc +/*-autoloads.el + +/.eask +/dist diff --git a/Eask b/Eask new file mode 100644 index 0000000..64855be --- /dev/null +++ b/Eask @@ -0,0 +1,21 @@ +;; -*- mode: eask; lexical-binding: t -*- + +(package "org-static-blog" + "1.7.0" + "a simple org-mode based static blog generator") + +(website-url "https://github.com/bastibe/org-static-blog") +(keywords "outlines") + +(package-file "org-static-blog.el") + +(script "test" "echo \"Error: no test specified\" && exit 1") + +(source 'gnu) + +(depends-on "emacs" "26.1") + +(add-hook 'eask-before-compile-hook + (lambda () + (setq byte-compile-error-on-warn t) + (setq byte-compile-docstring-max-column 1000))) diff --git a/org-static-blog.el b/org-static-blog.el index f974e36..3aaa3e7 100644 --- a/org-static-blog.el +++ b/org-static-blog.el @@ -1,4 +1,4 @@ -;;; org-static-blog.el --- a simple org-mode based static blog generator +;;; org-static-blog.el --- a simple org-mode based static blog generator -*- lexical-binding: t; -*- ;; Author: Bastian Bechtold ;; Contrib: Shmavon Gazanchyan, Rafał -rsm- Marek, neeasade, @@ -7,7 +7,8 @@ ;; K. Scarlet, zsxh ;; URL: https://github.com/bastibe/org-static-blog ;; Version: 1.7.0 -;; Package-Requires: ((emacs "24.3")) +;; Package-Requires: ((emacs "26.1")) +;; Keywords: outlines ;;; Commentary: @@ -39,6 +40,7 @@ ;;; Code: +(require 'autoinsert) (require 'cl-extra) (require 'org) (require 'ox-html) @@ -116,13 +118,13 @@ The tags page lists all posts as headlines." (defcustom org-static-blog-rss-excluded-tag nil "Posts with this tag won't be included in the RSS feeds." :type '(choice (const :tag "None" nil) - (string :tag "Tag name")) + (string :tag "Tag name")) :safe t) (defcustom org-static-blog-no-comments-tag nil "Posts with this tag won't include comments." :type '(choice (const :tag "None" nil) - (string :tag "Tag name")) + (string :tag "Tag name")) :safe t) (defcustom org-static-blog-rss-extra "" @@ -268,95 +270,95 @@ Only if og tags are enabled. It can be overridden with the ;; localization support (defconst org-static-blog-texts '((other-posts - ("en" . "Other posts") - ("pl" . "Pozostałe wpisy") - ("ru" . "Другие публикации") - ("by" . "Іншыя публікацыі") - ("it" . "Altri articoli") - ("es" . "Otros artículos") - ("fr" . "Autres articles") - ("zh" . "其他帖子") - ("ja" . "他の投稿")) - (date-format - ("en" . "%d %b %Y") - ("pl" . "%Y-%m-%d") - ("ru" . "%d.%m.%Y") - ("by" . "%d.%m.%Y") - ("it" . "%d/%m/%Y") - ("es" . "%d/%m/%Y") - ("fr" . "%d-%m-%Y") - ("zh" . "%Y-%m-%d") - ("ja" . "%Y/%m/%d")) - (tags - ("en" . "Tags") - ("pl" . "Tagi") - ("ru" . "Ярлыки") - ("by" . "Ярлыкі") - ("it" . "Categorie") - ("es" . "Categoría") - ("fr" . "Tags") - ("zh" . "标签") - ("ja" . "タグ")) - (archive - ("en" . "Archive") - ("pl" . "Archiwum") - ("ru" . "Архив") - ("by" . "Архіў") - ("it" . "Archivio") - ("es" . "Archivo") - ("fr" . "Archive") - ("zh" . "归档") - ("ja" . "アーカイブ")) - (posts-tagged - ("en" . "Posts tagged") - ("pl" . "Wpisy z tagiem") - ("ru" . "Публикации с ярлыками") - ("by" . "Публікацыі") - ("it" . "Articoli nella categoria") - ("es" . "Artículos de la categoría") - ("fr" . "Articles tagués") - ("zh" . "打标签的帖子") - ("ja" . "タグ付けされた投稿")) - (no-prev-post - ("en" . "There is no previous post") - ("pl" . "Poprzedni wpis nie istnieje") - ("ru" . "Нет предыдущей публикации") - ("by" . "Няма папярэдняй публікацыі") - ("it" . "Non c'è nessun articolo precedente") - ("es" . "No existe un artículo precedente") - ("fr" . "Il n'y a pas d'article précédent") - ("zh" . "无更旧的帖子") - ("ja" . "前の投稿はありません")) - (no-next-post - ("en" . "There is no next post") - ("pl" . "Następny wpis nie istnieje") - ("ru" . "Нет следующей публикации") - ("by" . "Няма наступнай публікацыі") - ("it" . "Non c'è nessun articolo successivo") - ("es" . "No hay artículo siguiente") - ("fr" . "Il n'y a pas d'article suivants") - ("zh" . "无更新的帖子") - ("ja" . "次の投稿はありません")) - (title - ("en" . "Title: ") - ("pl" . "Tytuł: ") - ("ru" . "Заголовок: ") - ("by" . "Загаловак: ") - ("it" . "Titolo: ") - ("es" . "Título: ") - ("fr" . "Titre : ") - ("zh" . "标题:") - ("ja" . "タイトル: ")) - (filename - ("en" . "Filename: ") - ("pl" . "Nazwa pliku: ") - ("ru" . "Имя файла: ") - ("by" . "Імя файла: ") - ("it" . "Nome del file: ") - ("es" . "Nombre del archivo: ") - ("fr" . "Nom du fichier :") - ("zh" . "文件名:") - ("ja" . "ファイル名: ")))) + ("en" . "Other posts") + ("pl" . "Pozostałe wpisy") + ("ru" . "Другие публикации") + ("by" . "Іншыя публікацыі") + ("it" . "Altri articoli") + ("es" . "Otros artículos") + ("fr" . "Autres articles") + ("zh" . "其他帖子") + ("ja" . "他の投稿")) + (date-format + ("en" . "%d %b %Y") + ("pl" . "%Y-%m-%d") + ("ru" . "%d.%m.%Y") + ("by" . "%d.%m.%Y") + ("it" . "%d/%m/%Y") + ("es" . "%d/%m/%Y") + ("fr" . "%d-%m-%Y") + ("zh" . "%Y-%m-%d") + ("ja" . "%Y/%m/%d")) + (tags + ("en" . "Tags") + ("pl" . "Tagi") + ("ru" . "Ярлыки") + ("by" . "Ярлыкі") + ("it" . "Categorie") + ("es" . "Categoría") + ("fr" . "Tags") + ("zh" . "标签") + ("ja" . "タグ")) + (archive + ("en" . "Archive") + ("pl" . "Archiwum") + ("ru" . "Архив") + ("by" . "Архіў") + ("it" . "Archivio") + ("es" . "Archivo") + ("fr" . "Archive") + ("zh" . "归档") + ("ja" . "アーカイブ")) + (posts-tagged + ("en" . "Posts tagged") + ("pl" . "Wpisy z tagiem") + ("ru" . "Публикации с ярлыками") + ("by" . "Публікацыі") + ("it" . "Articoli nella categoria") + ("es" . "Artículos de la categoría") + ("fr" . "Articles tagués") + ("zh" . "打标签的帖子") + ("ja" . "タグ付けされた投稿")) + (no-prev-post + ("en" . "There is no previous post") + ("pl" . "Poprzedni wpis nie istnieje") + ("ru" . "Нет предыдущей публикации") + ("by" . "Няма папярэдняй публікацыі") + ("it" . "Non c'è nessun articolo precedente") + ("es" . "No existe un artículo precedente") + ("fr" . "Il n'y a pas d'article précédent") + ("zh" . "无更旧的帖子") + ("ja" . "前の投稿はありません")) + (no-next-post + ("en" . "There is no next post") + ("pl" . "Następny wpis nie istnieje") + ("ru" . "Нет следующей публикации") + ("by" . "Няма наступнай публікацыі") + ("it" . "Non c'è nessun articolo successivo") + ("es" . "No hay artículo siguiente") + ("fr" . "Il n'y a pas d'article suivants") + ("zh" . "无更新的帖子") + ("ja" . "次の投稿はありません")) + (title + ("en" . "Title: ") + ("pl" . "Tytuł: ") + ("ru" . "Заголовок: ") + ("by" . "Загаловак: ") + ("it" . "Titolo: ") + ("es" . "Título: ") + ("fr" . "Titre : ") + ("zh" . "标题:") + ("ja" . "タイトル: ")) + (filename + ("en" . "Filename: ") + ("pl" . "Nazwa pliku: ") + ("ru" . "Имя файла: ") + ("by" . "Імя файла: ") + ("it" . "Nome del file: ") + ("es" . "Nombre del archivo: ") + ("fr" . "Nom du fichier :") + ("zh" . "文件名:") + ("ja" . "ファイル名: ")))) (defun concat-to-dir (dir filename) "Concat filename to another path interpreted as a directory." @@ -370,7 +372,7 @@ Only if og tags are enabled. It can be overridden with the "\n" "\n" (when tDescription - (format "\n" tDescription)) + (format "\n" tDescription)) "" tTitle "\n" (when org-static-blog-enable-og-tags - (concat - "\n" - "\n" - (when tDescription - (format "\n" tDescription)) - (when tUrl - (format "\n" tUrl)) - (if tImage - (format "\n" - (org-static-blog-get-absolute-url tImage)) - (when (> (length org-static-blog-image) 0) - (format "\n" - (org-static-blog-get-absolute-url org-static-blog-image)))))) + (concat + "\n" + "\n" + (when tDescription + (format "\n" tDescription)) + (when tUrl + (format "\n" tUrl)) + (if tImage + (format "\n" + (org-static-blog-get-absolute-url tImage)) + (when (> (length org-static-blog-image) 0) + (format "\n" + (org-static-blog-get-absolute-url org-static-blog-image)))))) org-static-blog-page-header "\n" "\n" @@ -410,12 +412,12 @@ Only if og tags are enabled. It can be overridden with the "Return localized text. Depends on org-static-blog-langcode and org-static-blog-texts." (let* ((text-node (assoc text-id org-static-blog-texts)) - (text-lang-node (if text-node - (assoc org-static-blog-langcode text-node) - nil))) - (if text-lang-node - (cdr text-lang-node) - (concat "[" (symbol-name text-id) ":" org-static-blog-langcode "]")))) + (text-lang-node (if text-node + (assoc org-static-blog-langcode text-node) + nil))) + (if text-lang-node + (cdr text-lang-node) + (concat "[" (symbol-name text-id) ":" org-static-blog-langcode "]")))) ;;;###autoload @@ -428,28 +430,28 @@ With a prefix argument, all blog posts are re-rendered unconditionally." (interactive "P") (dolist (file (append (org-static-blog-get-post-filenames) - (org-static-blog-get-draft-filenames))) - (when (or force-render (org-static-blog-needs-publishing-p file)) - (org-static-blog-publish-file file))) + (org-static-blog-get-draft-filenames))) + (when (or force-render (org-static-blog-needs-publishing-p file)) + (org-static-blog-publish-file file))) ;; don't spam too many deprecation warnings: (let ((org-static-blog-enable-deprecation-warning nil)) - (org-static-blog-assemble-index) - (org-static-blog-assemble-rss) - (org-static-blog-assemble-archive) - (if org-static-blog-enable-tags - (org-static-blog-assemble-tags)))) + (org-static-blog-assemble-index) + (org-static-blog-assemble-rss) + (org-static-blog-assemble-archive) + (if org-static-blog-enable-tags + (org-static-blog-assemble-tags)))) (defun org-static-blog-needs-publishing-p (post-filename) "Check whether POST-FILENAME was changed since last render." (let ((pub-filename - (org-static-blog-matching-publish-filename post-filename))) - (not (and (file-exists-p pub-filename) - (file-newer-than-file-p pub-filename post-filename))))) + (org-static-blog-matching-publish-filename post-filename))) + (not (and (file-exists-p pub-filename) + (file-newer-than-file-p pub-filename post-filename))))) (defun org-static-blog-matching-publish-filename (post-filename) "Generate HTML file name for POST-FILENAME." (concat-to-dir org-static-blog-publish-directory - (org-static-blog-get-post-public-path post-filename))) + (org-static-blog-get-post-public-path post-filename))) (defun org-static-blog-get-post-filenames () "Returns a list of all posts." @@ -466,9 +468,9 @@ unconditionally." (require 'seq) (make-directory (file-name-directory file) t) (car (seq-filter - (lambda (buf) - (string= (with-current-buffer buf buffer-file-name) file)) - (buffer-list)))) + (lambda (buf) + (string= (with-current-buffer buf buffer-file-name) file)) + (buffer-list)))) ;; This macro is needed for many of the following functions. (defmacro org-static-blog-with-find-file (file contents &rest body) @@ -476,104 +478,103 @@ unconditionally." The buffer is disposed after the macro exits (unless it already existed before)." `(save-excursion - (let ((current-buffer (current-buffer)) - (buffer-exists (org-static-blog-file-buffer ,file)) - (result nil) - (auto-insert nil) - (contents ,contents)) - (if buffer-exists - (switch-to-buffer buffer-exists) - (find-file ,file)) - (erase-buffer) - (insert contents) - (setq result (progn ,@body)) - (basic-save-buffer) - (unless buffer-exists - (kill-buffer)) - (switch-to-buffer current-buffer) - result))) + (let ((current-buffer (current-buffer)) + (buffer-exists (org-static-blog-file-buffer ,file)) + (result nil) + (auto-insert nil) + (contents ,contents)) + (if buffer-exists + (switch-to-buffer buffer-exists) + (find-file ,file)) + (erase-buffer) + (insert contents) + (setq result (progn ,@body)) + (basic-save-buffer) + (unless buffer-exists + (kill-buffer)) + (switch-to-buffer current-buffer) + result))) (defun org-static-blog-get-date (post-filename) "Extract the `#+date:` from POST-FILENAME as date-time." (let ((case-fold-search t)) - (with-temp-buffer - (insert-file-contents post-filename) - (goto-char (point-min)) - (if (search-forward-regexp "^\\#\\+date:[ ]*[[<]?\\([^]>]+\\)[]>]?$" nil t) - (date-to-time (match-string 1)) - (time-since 0))))) + (with-temp-buffer + (insert-file-contents post-filename) + (goto-char (point-min)) + (if (search-forward-regexp "^\\#\\+date:[ ]*[[<]?\\([^]>]+\\)[]>]?$" nil t) + (date-to-time (match-string 1)) + (time-since 0))))) (defun org-static-blog-get-title (post-filename) "Extract the `#+title:` from POST-FILENAME." (let ((case-fold-search t)) - (with-temp-buffer - (insert-file-contents post-filename) - (goto-char (point-min)) - (if (search-forward-regexp "^\\#\\+title:[ ]*\\(.+\\)$" nil t) - (match-string 1) - (warn "%s file does not have a title, using %s as the title" post-filename post-filename) - post-filename)))) + (with-temp-buffer + (insert-file-contents post-filename) + (goto-char (point-min)) + (if (search-forward-regexp "^\\#\\+title:[ ]*\\(.+\\)$" nil t) + (match-string 1) + (warn "%s file does not have a title, using %s as the title" post-filename post-filename) + post-filename)))) (defun org-static-blog-get-description (post-filename) "Extract the `#+description:` from POST-FILENAME." (let ((case-fold-search t)) - (with-temp-buffer - (insert-file-contents post-filename) - (goto-char (point-min)) - (when (search-forward-regexp "^\\#\\+description:[ ]*\\(.+\\)$" nil t) - (let ((description (string-trim (match-string 1)))) - (unless (zerop (length description)) - description)))))) + (with-temp-buffer + (insert-file-contents post-filename) + (goto-char (point-min)) + (when (search-forward-regexp "^\\#\\+description:[ ]*\\(.+\\)$" nil t) + (let ((description (string-trim (match-string 1)))) + (unless (zerop (length description)) + description)))))) (defun org-static-blog-get-image (post-filename) "Extract the `#+image:` from POST-FILENAME." (let ((case-fold-search t)) - (with-temp-buffer - (insert-file-contents post-filename) - (goto-char (point-min)) - (when (search-forward-regexp "^\\#\\+image:[ ]*\\(.+\\)$" nil t) - (let ((image (string-trim (match-string 1)))) - (unless (zerop (length image)) - image)))))) + (with-temp-buffer + (insert-file-contents post-filename) + (goto-char (point-min)) + (when (search-forward-regexp "^\\#\\+image:[ ]*\\(.+\\)$" nil t) + (let ((image (string-trim (match-string 1)))) + (unless (zerop (length image)) + image)))))) (defun org-static-blog-get-tags (post-filename) "Extract the `#+filetags:` from POST-FILENAME as list of strings." (let ((case-fold-search t)) - (with-temp-buffer - (insert-file-contents post-filename) - (goto-char (point-min)) - (if (search-forward-regexp "^\\#\\+filetags:[ ]*:\\(.*\\):$" nil t) - (split-string (match-string 1) ":") - (if (search-forward-regexp "^\\#\\+filetags:[ ]*\\(.+\\)$" nil t) - (split-string (match-string 1)) - ))))) + (with-temp-buffer + (insert-file-contents post-filename) + (goto-char (point-min)) + (if (search-forward-regexp "^\\#\\+filetags:[ ]*:\\(.*\\):$" nil t) + (split-string (match-string 1) ":") + (if (search-forward-regexp "^\\#\\+filetags:[ ]*\\(.+\\)$" nil t) + (split-string (match-string 1))))))) (defun org-static-blog-get-tag-tree () "Return an association list of tags to filenames. -e.g. `(('foo' 'file1.org' 'file2.org') ('bar' 'file2.org'))`" +e.g. `((`foo' `file1.org' `file2.org') (`bar' `file2.org'))`" (let ((tag-tree '())) - (dolist (post-filename (org-static-blog-get-post-filenames)) - (let ((tags (org-static-blog-get-tags post-filename))) - (dolist (tag (remove org-static-blog-rss-excluded-tag tags)) - (if (assoc-string tag tag-tree t) - (push post-filename (cdr (assoc-string tag tag-tree t))) - (push (cons tag (list post-filename)) tag-tree))))) - tag-tree)) + (dolist (post-filename (org-static-blog-get-post-filenames)) + (let ((tags (org-static-blog-get-tags post-filename))) + (dolist (tag (remove org-static-blog-rss-excluded-tag tags)) + (if (assoc-string tag tag-tree t) + (push post-filename (cdr (assoc-string tag tag-tree t))) + (push (cons tag (list post-filename)) tag-tree))))) + tag-tree)) (defun org-static-blog--preview-region () "Find the start and end of the preview in the current buffer." (goto-char (point-min)) (if org-static-blog-preview-end - (when (or (search-forward (or org-static-blog-preview-start "

") nil t) - (search-forward "

" nil t)) - (let ((start (match-beginning 0))) - (or (search-forward org-static-blog-preview-end nil t) - (search-forward "

" nil t)) - (buffer-substring-no-properties start (point)))) - (when (search-forward (or org-static-blog-preview-start "

") nil t) - (let ((start (match-beginning 0))) - (search-forward "

") - (buffer-substring-no-properties start (point)))))) + (when (or (search-forward (or org-static-blog-preview-start "

") nil t) + (search-forward "

" nil t)) + (let ((start (match-beginning 0))) + (or (search-forward org-static-blog-preview-end nil t) + (search-forward "

" nil t)) + (buffer-substring-no-properties start (point)))) + (when (search-forward (or org-static-blog-preview-start "

") nil t) + (let ((start (match-beginning 0))) + (search-forward "

") + (buffer-substring-no-properties start (point)))))) (defun org-static-blog-get-preview (post-filename) "Get title, date, tags from POST-FILENAME and get the first paragraph from the rendered HTML. @@ -581,35 +582,35 @@ If the HTML body contains multiple paragraphs, include only the first paragraph, and display an ellipsis. Preamble and Postamble are excluded, too." (with-temp-buffer - (insert-file-contents (org-static-blog-matching-publish-filename post-filename)) - (let ((post-title (org-static-blog-get-title post-filename)) - (post-date (org-static-blog-get-date post-filename)) - (post-taglist (org-static-blog-post-taglist post-filename)) - (post-ellipsis "") - (preview-region (org-static-blog--preview-region))) - (when (and preview-region (search-forward "

" nil t)) - (setq post-ellipsis - (concat (when org-static-blog-preview-link-p - (format "" - (org-static-blog-get-post-url post-filename))) - org-static-blog-preview-ellipsis - (when org-static-blog-preview-link-p "\n")))) - ;; Put the substrings together. - (let ((title-link - (format "

%s

" - (org-static-blog-get-post-url post-filename) post-title)) - (date-link - (format-time-string (concat "
" - (org-static-blog-gettext 'date-format) - "
") - post-date))) - (concat - (if org-static-blog-preview-date-first-p - (concat date-link title-link) - (concat title-link date-link)) - preview-region - post-ellipsis - (format "
%s
" post-taglist)))))) + (insert-file-contents (org-static-blog-matching-publish-filename post-filename)) + (let ((post-title (org-static-blog-get-title post-filename)) + (post-date (org-static-blog-get-date post-filename)) + (post-taglist (org-static-blog-post-taglist post-filename)) + (post-ellipsis "") + (preview-region (org-static-blog--preview-region))) + (when (and preview-region (search-forward "

" nil t)) + (setq post-ellipsis + (concat (when org-static-blog-preview-link-p + (format "" + (org-static-blog-get-post-url post-filename))) + org-static-blog-preview-ellipsis + (when org-static-blog-preview-link-p "\n")))) + ;; Put the substrings together. + (let ((title-link + (format "

%s

" + (org-static-blog-get-post-url post-filename) post-title)) + (date-link + (format-time-string (concat "
" + (org-static-blog-gettext 'date-format) + "
") + post-date))) + (concat + (if org-static-blog-preview-date-first-p + (concat date-link title-link) + (concat title-link date-link)) + preview-region + post-ellipsis + (format "
%s
" post-taglist)))))) (defun org-static-blog-get-post-content (post-filename &optional exclude-title) @@ -618,33 +619,33 @@ Preamble and Postamble are excluded, too." ;; NB! The following code assumes the post is using default template. ;; See: org-static-blog-publish-file (let ((publish-filename (org-static-blog-matching-publish-filename post-filename))) - (with-temp-buffer - (insert-file-contents publish-filename) - (buffer-substring-no-properties - (progn - (goto-char (point-min)) - (if exclude-title - (progn (search-forward "

") - (search-forward "

")) - (search-forward "
")) - (point)) - (progn - (goto-char (point-max)) - ;; Search backward for the post content (by org-static-blog-render-post-content). - ;; See: org-static-blog-template - (search-backward "
") - (search-backward "
") - ;; If comments section exists, it is then one div backward. - ;; See: org-static-blog-post-postamble - (search-backward "
" nil t) - (point)))))) + (with-temp-buffer + (insert-file-contents publish-filename) + (buffer-substring-no-properties + (progn + (goto-char (point-min)) + (if exclude-title + (progn (search-forward "

") + (search-forward "

")) + (search-forward "
")) + (point)) + (progn + (goto-char (point-max)) + ;; Search backward for the post content (by org-static-blog-render-post-content). + ;; See: org-static-blog-template + (search-backward "
") + (search-backward "
") + ;; If comments section exists, it is then one div backward. + ;; See: org-static-blog-post-postamble + (search-backward "
" nil t) + (point)))))) (defun org-static-blog-get-absolute-url (relative-url) "Returns absolute URL based on the RELATIVE-URL passed to the function. -For example, when `org-static-blog-publish-url` is set to 'https://example.com/' -and `relative-url` is passed as 'archive.html' then the function -will return 'https://example.com/archive.html'." +For example, when `org-static-blog-publish-url` is set to `https://example.com/' +and `relative-url` is passed as `archive.html' then the function +will return `https://example.com/archive.html'." (concat-to-dir org-static-blog-publish-url relative-url)) (defun org-static-blog-get-post-url (post-filename) @@ -653,18 +654,18 @@ will return 'https://example.com/archive.html'." This function concatenates publish URL and generated custom filepath to the published HTML version of the post." (org-static-blog-get-absolute-url - (org-static-blog-get-post-public-path post-filename))) + (org-static-blog-get-post-public-path post-filename))) (defun org-static-blog-get-post-public-path (post-filename) "Returns post filepath in public directory. This function retrieves relative path to the post file in posts or drafts directories to published HTML version of the post." (let* ((true-post-filename (file-truename post-filename)) - (true-posts-directory (file-truename org-static-blog-posts-directory)) - (root-dir (if (string-prefix-p true-posts-directory true-post-filename) - true-posts-directory - (file-truename org-static-blog-drafts-directory)))) - (concat (file-name-sans-extension (file-relative-name true-post-filename root-dir)) ".html"))) + (true-posts-directory (file-truename org-static-blog-posts-directory)) + (root-dir (if (string-prefix-p true-posts-directory true-post-filename) + true-posts-directory + (file-truename org-static-blog-drafts-directory)))) + (concat (file-name-sans-extension (file-relative-name true-post-filename root-dir)) ".html"))) (defun org-static-blog-get-relative-path (post-filename) "Removes absolute directory path from POST-FILENAME and changes file extention @@ -672,11 +673,11 @@ from `.org` to `.html`. Returns filepath to HTML file relative to posts or draft Works with both posts and drafts directories. -For example, when `org-static-blog-posts-directory` is set to '~/blog/posts' -and `post-filename` is passed as '~/blog/posts/my-life-update.org' then the function -will return 'my-life-update.html'." +For example, when `org-static-blog-posts-directory` is set to `~/blog/posts' +and `post-filename` is passed as `~/blog/posts/my-life-update.org' then the function +will return `my-life-update.html'." (concat (file-name-sans-extension (file-relative-name post-filename org-static-blog-posts-directory)) - ".html")) + ".html")) ;;;###autoload (defun org-static-blog-publish-file (post-filename) @@ -686,39 +687,39 @@ The index, archive, tags, and RSS feed are not updated." (org-static-blog-with-find-file (org-static-blog-matching-publish-filename post-filename) (org-static-blog-template - (org-static-blog-get-title post-filename) - (concat - (org-static-blog-post-preamble post-filename) - (org-static-blog-render-post-content post-filename) - (org-static-blog-post-postamble post-filename)) - (org-static-blog-get-description post-filename) - (org-static-blog-get-image post-filename) - (org-static-blog-get-post-url post-filename)))) + (org-static-blog-get-title post-filename) + (concat + (org-static-blog-post-preamble post-filename) + (org-static-blog-render-post-content post-filename) + (org-static-blog-post-postamble post-filename)) + (org-static-blog-get-description post-filename) + (org-static-blog-get-image post-filename) + (org-static-blog-get-post-url post-filename)))) (defun org-static-blog-render-post-content (post-filename) "Render blog content as bare HTML without header." (let ((org-html-doctype "html5") - (org-html-html5-fancy t)) - (save-excursion - (let ((current-buffer (current-buffer)) - (buffer-exists (org-static-blog-file-buffer post-filename)) - (result nil)) - (with-temp-buffer - (if buffer-exists - (insert-buffer-substring buffer-exists) - (insert-file-contents post-filename)) - (org-mode) - (goto-char (point-min)) - (org-map-entries - (lambda () - (setq org-map-continue-from (point)) - (org-cut-subtree)) - org-static-blog-no-post-tag) - (setq result - (org-export-as 'org-static-blog-post-bare nil nil nil nil)) - (switch-to-buffer current-buffer) - result))))) + (org-html-html5-fancy t)) + (save-excursion + (let ((current-buffer (current-buffer)) + (buffer-exists (org-static-blog-file-buffer post-filename)) + (result nil)) + (with-temp-buffer + (if buffer-exists + (insert-buffer-substring buffer-exists) + (insert-file-contents post-filename)) + (org-mode) + (goto-char (point-min)) + (org-map-entries + (lambda () + (setq org-map-continue-from (point)) + (org-cut-subtree)) + org-static-blog-no-post-tag) + (setq result + (org-export-as 'org-static-blog-post-bare nil nil nil nil)) + (switch-to-buffer current-buffer) + result))))) (org-export-define-derived-backend 'org-static-blog-post-bare 'html :translate-alist '((template . (lambda (contents info) contents)))) @@ -728,32 +729,32 @@ The index, archive, tags, and RSS feed are not updated." The index page contains the last `org-static-blog-index-length` posts as full text posts." (let ((post-filenames (org-static-blog-get-post-filenames))) - ;; reverse-sort, so that the later `last` will grab the newest posts - (setq post-filenames (sort post-filenames (lambda (x y) (time-less-p (org-static-blog-get-date x) - (org-static-blog-get-date y))))) - (org-static-blog-assemble-multipost-page - (concat-to-dir org-static-blog-publish-directory org-static-blog-index-file) - (last post-filenames org-static-blog-index-length) - org-static-blog-index-front-matter))) + ;; reverse-sort, so that the later `last` will grab the newest posts + (setq post-filenames (sort post-filenames (lambda (x y) (time-less-p (org-static-blog-get-date x) + (org-static-blog-get-date y))))) + (org-static-blog-assemble-multipost-page + (concat-to-dir org-static-blog-publish-directory org-static-blog-index-file) + (last post-filenames org-static-blog-index-length) + org-static-blog-index-front-matter))) (defun org-static-blog-assemble-multipost-page (pub-filename post-filenames &optional front-matter) "Assemble a page that contains multiple posts one after another. Posts are sorted in descending time." (setq post-filenames (sort post-filenames (lambda (x y) (time-less-p (org-static-blog-get-date y) - (org-static-blog-get-date x))))) + (org-static-blog-get-date x))))) (org-static-blog-with-find-file pub-filename (org-static-blog-template - org-static-blog-publish-title - (concat - (when front-matter front-matter) - (apply 'concat (mapcar - (if org-static-blog-use-preview - 'org-static-blog-get-preview - 'org-static-blog-get-post-content) post-filenames)) - "\n")))) + org-static-blog-publish-title + (concat + (when front-matter front-matter) + (apply 'concat (mapcar + (if org-static-blog-use-preview + 'org-static-blog-get-preview + 'org-static-blog-get-post-content) post-filenames)) + "\n")))) @@ -764,7 +765,7 @@ Modify this function if you want to change a posts headline." (concat org-static-blog-post-preamble-text "
" (format-time-string (org-static-blog-gettext 'date-format) - (org-static-blog-get-date post-filename)) + (org-static-blog-get-date post-filename)) "
" "

" "" (org-static-blog-get-title post-filename) "" @@ -776,18 +777,18 @@ Modify this function if you want to change a posts headline." This part will be attached at the end of the post, after the taglist, in a
...
block." (let ((taglist-content "") - (tags (remove org-static-blog-no-comments-tag - (remove org-static-blog-rss-excluded-tag - (org-static-blog-get-tags post-filename))))) - (when (and tags org-static-blog-enable-tags) - (setq taglist-content (concat "" (org-static-blog-gettext 'tags) ": ")) - (dolist (tag tags) - (setq taglist-content (concat taglist-content "" tag " ")))) - taglist-content)) + (tags (remove org-static-blog-no-comments-tag + (remove org-static-blog-rss-excluded-tag + (org-static-blog-get-tags post-filename))))) + (when (and tags org-static-blog-enable-tags) + (setq taglist-content (concat "" (org-static-blog-gettext 'tags) ": ")) + (dolist (tag tags) + (setq taglist-content (concat taglist-content "" tag " ")))) + taglist-content)) (defun org-static-blog-post-postamble (post-filename) "Returns the tag list and comment box at the end of a post. @@ -795,76 +796,76 @@ This function is called for every post and the returned string is appended to the post body, and includes the tag list generated by followed by the HTML code for comments." (concat "
" - (org-static-blog-post-taglist post-filename) - "
" - (if (or (string= org-static-blog-post-comments "") - (member org-static-blog-no-comments-tag (org-static-blog-get-tags post-filename))) - "" - (concat "\n
" - org-static-blog-post-comments - "
")) - org-static-blog-post-postamble-text)) + (org-static-blog-post-taglist post-filename) + "

" + (if (or (string= org-static-blog-post-comments "") + (member org-static-blog-no-comments-tag (org-static-blog-get-tags post-filename))) + "" + (concat "\n
" + org-static-blog-post-comments + "
")) + org-static-blog-post-postamble-text)) (defun org-static-blog--prune-items (items) "Limit, if needed, the items to be included in an RSS feed." (if (and org-static-blog-rss-max-entries (> org-static-blog-rss-max-entries 0)) - (let ((excess (- (length items) org-static-blog-rss-max-entries))) - (if (> excess 0) (butlast items excess) items)) - items)) + (let ((excess (- (length items) org-static-blog-rss-max-entries))) + (if (> excess 0) (butlast items excess) items)) + items)) (defun org-static-blog--rss-filename (&optional tag) "Full path to the RSS file for the given TAG." (concat-to-dir org-static-blog-publish-directory - (concat tag (when tag "-") org-static-blog-rss-file))) + (concat tag (when tag "-") org-static-blog-rss-file))) (defun org-static-blog--write-rss (items &optional tag) "Generates an RSS file for the given TAG, or for all tags is TAG is nil." (let ((title (format "%s%s" - org-static-blog-publish-title - (if tag (concat " - " tag) ""))) - (url (format "%s%s" - org-static-blog-publish-url - (if tag (concat "/tag-" (downcase tag) ".html") ""))) - (items (sort items (lambda (x y) (time-less-p (car y) (car x)))))) - (org-static-blog-with-find-file - (org-static-blog--rss-filename tag) - (concat "\n" - "\n" - "\n" - "<![CDATA[" title "]]>\n" - "\n" - "" url "\n" - "" (let ((system-time-locale "C")) ; force dates to render as per RSS spec - (format-time-string "%a, %d %b %Y %H:%M:%S %z" (current-time))) - "\n" - org-static-blog-rss-extra - (apply 'concat (mapcar 'cdr (org-static-blog--prune-items items))) - "\n" - "\n")))) + org-static-blog-publish-title + (if tag (concat " - " tag) ""))) + (url (format "%s%s" + org-static-blog-publish-url + (if tag (concat "/tag-" (downcase tag) ".html") ""))) + (items (sort items (lambda (x y) (time-less-p (car y) (car x)))))) + (org-static-blog-with-find-file + (org-static-blog--rss-filename tag) + (concat "\n" + "\n" + "\n" + "<![CDATA[" title "]]>\n" + "\n" + "" url "\n" + "" (let ((system-time-locale "C")) ; force dates to render as per RSS spec + (format-time-string "%a, %d %b %Y %H:%M:%S %z" (current-time))) + "\n" + org-static-blog-rss-extra + (apply 'concat (mapcar 'cdr (org-static-blog--prune-items items))) + "\n" + "\n")))) (defun org-static-blog-assemble-rss () "Assemble the blog RSS feed. The RSS-feed is an XML file that contains every blog post in a machine-readable format." (let ((system-time-locale "en_US.utf-8") ; force dates to render as per RSS spec - (rss-items nil) - (rss-tag-items nil)) - (dolist (post-filename (org-static-blog-get-post-filenames)) - (let ((rss-date (org-static-blog-get-date post-filename)) - (rss-text (org-static-blog-get-rss-item post-filename)) - (tags (org-static-blog-get-tags post-filename))) - (when (or (null org-static-blog-rss-excluded-tag) - (not (member org-static-blog-rss-excluded-tag tags))) - (let ((item (cons rss-date rss-text))) - (add-to-list 'rss-items item) - (when org-static-blog-enable-tag-rss - (dolist (tag tags) - (let ((items (cons item (cdr (assoc tag rss-tag-items))))) - (setf (alist-get tag rss-tag-items nil t 'string=) items)))))))) - (org-static-blog--write-rss rss-items) - (message "%s" rss-tag-items) - (dolist (x rss-tag-items) (org-static-blog--write-rss (cdr x) (car x))))) + (rss-items nil) + (rss-tag-items nil)) + (dolist (post-filename (org-static-blog-get-post-filenames)) + (let ((rss-date (org-static-blog-get-date post-filename)) + (rss-text (org-static-blog-get-rss-item post-filename)) + (tags (org-static-blog-get-tags post-filename))) + (when (or (null org-static-blog-rss-excluded-tag) + (not (member org-static-blog-rss-excluded-tag tags))) + (let ((item (cons rss-date rss-text))) + (push item rss-items) + (when org-static-blog-enable-tag-rss + (dolist (tag tags) + (let ((items (cons item (cdr (assoc tag rss-tag-items))))) + (setf (alist-get tag rss-tag-items nil t 'string=) items)))))))) + (org-static-blog--write-rss rss-items) + (message "%s" rss-tag-items) + (dolist (x rss-tag-items) (org-static-blog--write-rss (cdr x) (car x))))) (defun org-static-blog-get-rss-item (post-filename) "Assemble RSS item from post-filename. @@ -876,11 +877,11 @@ The HTML content is taken from the rendered HTML post." (org-static-blog-get-post-content post-filename t) ; exclude headline! "]]>\n" (let ((categories "")) - (when (and (org-static-blog-get-tags post-filename) org-static-blog-enable-tags) - (dolist (tag (org-static-blog-get-tags post-filename)) - (setq categories (concat categories - " \n")))) - categories) + (when (and (org-static-blog-get-tags post-filename) org-static-blog-enable-tags) + (dolist (tag (org-static-blog-get-tags post-filename)) + (setq categories (concat categories + " \n")))) + categories) " " (url-encode-url (org-static-blog-get-post-url post-filename)) "\n" @@ -889,7 +890,7 @@ The HTML content is taken from the rendered HTML post." "\n" " " (let ((system-time-locale "C")) ; force dates to render as per RSS spec - (format-time-string "%a, %d %b %Y %H:%M:%S %z" (org-static-blog-get-date post-filename))) + (format-time-string "%a, %d %b %Y %H:%M:%S %z" (org-static-blog-get-date post-filename))) "\n" "\n")) @@ -898,18 +899,17 @@ The HTML content is taken from the rendered HTML post." The archive page contains single-line links and dates for every blog post, but no post body." (let ((archive-filename (concat-to-dir org-static-blog-publish-directory org-static-blog-archive-file)) - (archive-entries nil) - (post-filenames (org-static-blog-get-post-filenames))) - (setq post-filenames (sort post-filenames (lambda (x y) (time-less-p - (org-static-blog-get-date y) - (org-static-blog-get-date x))))) - (org-static-blog-with-find-file - archive-filename - (org-static-blog-template - org-static-blog-publish-title - (concat - "

" (org-static-blog-gettext 'archive) "

\n" - (apply 'concat (mapcar 'org-static-blog-get-post-summary post-filenames))))))) + (post-filenames (org-static-blog-get-post-filenames))) + (setq post-filenames (sort post-filenames (lambda (x y) (time-less-p + (org-static-blog-get-date y) + (org-static-blog-get-date x))))) + (org-static-blog-with-find-file + archive-filename + (org-static-blog-template + org-static-blog-publish-title + (concat + "

" (org-static-blog-gettext 'archive) "

\n" + (apply 'concat (mapcar 'org-static-blog-get-post-summary post-filenames))))))) (defun org-static-blog-get-post-summary (post-filename) "Assemble post summary for an archive page. @@ -928,69 +928,69 @@ archive headline." "Render the tag archive and tag pages." (org-static-blog-assemble-tags-archive) (dolist (tag (org-static-blog-get-tag-tree)) - (org-static-blog-assemble-multipost-page - (concat-to-dir org-static-blog-publish-directory - (concat "tag-" (downcase (car tag)) ".html")) - (cdr tag) - (concat "

" (org-static-blog-gettext 'posts-tagged) " \"" (car tag) "\":

")))) + (org-static-blog-assemble-multipost-page + (concat-to-dir org-static-blog-publish-directory + (concat "tag-" (downcase (car tag)) ".html")) + (cdr tag) + (concat "

" (org-static-blog-gettext 'posts-tagged) " \"" (car tag) "\":

")))) (defun org-static-blog-assemble-tags-archive-tag (tag) "Assemble single TAG for all filenames." (let ((post-filenames (cdr tag))) - (setq post-filenames - (sort post-filenames (lambda (x y) (time-less-p (org-static-blog-get-date x) - (org-static-blog-get-date y))))) - (concat "

" (org-static-blog-gettext 'posts-tagged) " \"" (downcase (car tag)) "\":

\n" - (apply 'concat (mapcar 'org-static-blog-get-post-summary post-filenames))))) + (setq post-filenames + (sort post-filenames (lambda (x y) (time-less-p (org-static-blog-get-date x) + (org-static-blog-get-date y))))) + (concat "

" (org-static-blog-gettext 'posts-tagged) " \"" (downcase (car tag)) "\":

\n" + (apply 'concat (mapcar 'org-static-blog-get-post-summary post-filenames))))) (defun org-static-blog-assemble-tags-archive () "Assemble the blog tag archive page. The archive page contains single-line links and dates for every blog post, sorted by tags, but no post body." (let ((tags-archive-filename (concat-to-dir org-static-blog-publish-directory org-static-blog-tags-file)) - (tag-tree (org-static-blog-get-tag-tree))) - (setq tag-tree (sort tag-tree (lambda (x y) (string-greaterp (car y) (car x))))) - (org-static-blog-with-find-file - tags-archive-filename - (org-static-blog-template - org-static-blog-publish-title - (concat - "

" (org-static-blog-gettext 'tags) "

\n" - (apply 'concat (mapcar 'org-static-blog-assemble-tags-archive-tag tag-tree))))))) + (tag-tree (org-static-blog-get-tag-tree))) + (setq tag-tree (sort tag-tree (lambda (x y) (string-greaterp (car y) (car x))))) + (org-static-blog-with-find-file + tags-archive-filename + (org-static-blog-template + org-static-blog-publish-title + (concat + "

" (org-static-blog-gettext 'tags) "

\n" + (apply 'concat (mapcar 'org-static-blog-assemble-tags-archive-tag tag-tree))))))) (defun org-static-blog-open-previous-post () "Opens previous blog post." (interactive) (let ((posts (sort (org-static-blog-get-post-filenames) - (lambda (x y) - (time-less-p (org-static-blog-get-date y) - (org-static-blog-get-date x))))) - (current-post (buffer-file-name))) - (while (and posts - (not (string-equal - (file-name-nondirectory current-post) - (file-name-nondirectory (car posts))))) - (setq posts (cdr posts))) - (if (> (cl-list-length posts) 1) - (find-file (cadr posts)) - (message (org-static-blog-gettext 'no-prev-post))))) + (lambda (x y) + (time-less-p (org-static-blog-get-date y) + (org-static-blog-get-date x))))) + (current-post (buffer-file-name))) + (while (and posts + (not (string-equal + (file-name-nondirectory current-post) + (file-name-nondirectory (car posts))))) + (setq posts (cdr posts))) + (if (> (cl-list-length posts) 1) + (find-file (cadr posts)) + (message (org-static-blog-gettext 'no-prev-post))))) (defun org-static-blog-open-next-post () "Opens next blog post." (interactive) (let ((posts (sort (org-static-blog-get-post-filenames) - (lambda (x y) - (time-less-p (org-static-blog-get-date x) - (org-static-blog-get-date y))))) - (current-post (buffer-file-name))) - (while (and posts - (not (string-equal - (file-name-nondirectory current-post) - (file-name-nondirectory (car posts))))) - (setq posts (cdr posts))) - (if (> (cl-list-length posts) 1) - (find-file (cadr posts)) - (message (org-static-blog-gettext 'no-next-post))))) + (lambda (x y) + (time-less-p (org-static-blog-get-date x) + (org-static-blog-get-date y))))) + (current-post (buffer-file-name))) + (while (and posts + (not (string-equal + (file-name-nondirectory current-post) + (file-name-nondirectory (car posts))))) + (setq posts (cdr posts))) + (if (> (cl-list-length posts) 1) + (find-file (cadr posts)) + (message (org-static-blog-gettext 'no-next-post))))) (defun org-static-blog-open-matching-publish-file () "Opens HTML for post." @@ -1005,19 +1005,19 @@ only a suggestion; You can choose any other file name if you so choose." (interactive) (let ((title (read-string (org-static-blog-gettext 'title)))) - (find-file (concat-to-dir - (if draft - org-static-blog-drafts-directory - org-static-blog-posts-directory) - (read-string (org-static-blog-gettext 'filename) - (concat (format-time-string "%Y-%m-%d-" (current-time)) - (replace-regexp-in-string org-static-blog-suggested-filename-cleaning-regexp - "-" (downcase title)) - ".org")))) - (insert "#+title: " title "\n" - "#+date: " (format-time-string "<%Y-%m-%d %H:%M>") "\n" - "#+description: \n" - "#+filetags: "))) + (find-file (concat-to-dir + (if draft + org-static-blog-drafts-directory + org-static-blog-posts-directory) + (read-string (org-static-blog-gettext 'filename) + (concat (format-time-string "%Y-%m-%d-" (current-time)) + (replace-regexp-in-string org-static-blog-suggested-filename-cleaning-regexp + "-" (downcase title)) + ".org")))) + (insert "#+title: " title "\n" + "#+date: " (format-time-string "<%Y-%m-%d %H:%M>") "\n" + "#+description: \n" + "#+filetags: "))) ;;;###autoload (defun org-static-blog-create-new-draft ()