;;; my-org-pukiwiki.el --- Simple Org to PukiWiki exporter -*- lexical-binding: t; -*- (require 'subr-x) (defgroup my-org-pukiwiki nil "Simple Org to PukiWiki exporter." :group 'tools) (defcustom my-org-pukiwiki-coding-system 'japanese-shift-jis "Output coding system." :type 'coding-system :group 'my-org-pukiwiki) (defun my-org-pukiwiki--metadata-p (line) (string-match-p "^#\\+\\(TITLE\\|AUTHOR\\|OPTIONS\\|DATE\\|EMAIL\\|LANGUAGE\\|KEYWORDS\\):" line)) (defun my-org-pukiwiki--heading (line) (if (string-match "^\\(\\*+\\)[ \t]+\\(.*\\)$" line) (let* ((level (length (match-string 1 line))) (title (match-string 2 line))) (cond ((= level 1) (concat "* " title)) ((= level 2) (concat "** " title)) ((= level 3) (concat "*** " title)) (t (concat "'''" title "'''")))) line)) (defun my-org-pukiwiki--list (line) (cond ((string-match "^[ \t]*-[ \t]+\\(.*\\)$" line) (concat "-" (match-string 1 line))) ((string-match "^[ \t]*[0-9]+\\.[ \t]+\\(.*\\)$" line) (concat "+" (match-string 1 line))) (t line))) (defun my-org-pukiwiki--table-separator-p (line) (string-match-p "^[ \t]*|[-+| \t]+|[ \t]*$" line)) (defun my-org-pukiwiki--table (line) (cond ((my-org-pukiwiki--table-separator-p line) nil) ((string-match-p "^[ \t]*|" line) (let ((s (string-trim line))) (setq s (replace-regexp-in-string "=" "" s)) (if (string-match-p "|[ \t]*キー[ \t]*|\\|[ \t]*関数[ \t]*|\\|[ \t]*機能[ \t]*|" s) (concat s "h") s))) (t line))) (defun my-org-pukiwiki--inline (line) (setq line (replace-regexp-in-string "=\\([^=\n]+\\)=" "''\\1''" line)) (setq line (replace-regexp-in-string "~\\([^~\n]+\\)~" "''\\1''" line)) line) (defun my-org-pukiwiki--link (line) ;; [[file:foo.png]] (setq line (replace-regexp-in-string "\\[\\[file:\\([^]]+\\)\\]\\]" "#ref(\\1)" line)) ;; [[https://example.org][label]] (setq line (replace-regexp-in-string "\\[\\[\\([^]]+\\)\\]\\[\\([^]]+\\)\\]\\]" "[[\\2>\\1]]" line)) ;; [[https://example.org]] (setq line (replace-regexp-in-string "\\[\\[\\(https?://[^]]+\\)\\]\\]" "[[\\1]]" line)) line) (defun my-org-pukiwiki--sjis-safe (text) (let ((s text)) (setq s (replace-regexp-in-string "–\\|—\\|―" "--" s)) (setq s (replace-regexp-in-string "“\\|”" "\"" s)) (setq s (replace-regexp-in-string "‘\\|’" "'" s)) (setq s (replace-regexp-in-string "…" "..." s)) (setq s (replace-regexp-in-string "⏳" "Busy" s)) (setq s (replace-regexp-in-string "→" "->" s)) (setq s (replace-regexp-in-string "←" "<-" s)) (setq s (replace-regexp-in-string "⇒" "=>" s)) (setq s (replace-regexp-in-string "├──" "+-" s)) (setq s (replace-regexp-in-string "└──" "+-" s)) (setq s (replace-regexp-in-string "│" "|" s)) s)) (defun my-org-pukiwiki-convert-string (text) "Convert Org TEXT to PukiWiki text." (let ((lines (split-string text "\n")) (out nil) (in-block nil)) (dolist (line lines) (cond ;; src/example block start ((string-match-p "^#\\+begin_\\(src\\|example\\)" line) (setq in-block t)) ;; src/example block end ((string-match-p "^#\\+end_\\(src\\|example\\)" line) (setq in-block nil)) ;; preformatted block: leading half-width space (in-block (push (concat " " line) out)) ;; skip Org metadata ((my-org-pukiwiki--metadata-p line) nil) ;; skip caption/name lines ((string-match-p "^#\\+\\(CAPTION\\|NAME\\):" line) nil) ;; normal line (t (setq line (my-org-pukiwiki--heading line)) (setq line (my-org-pukiwiki--list line)) (setq line (my-org-pukiwiki--table line)) (when line (setq line (my-org-pukiwiki--inline line)) (setq line (my-org-pukiwiki--link line)) (push line out))))) (my-org-pukiwiki--sjis-safe (string-join (nreverse out) "\n")))) (defun my-org-pukiwiki-export-buffer () "Convert current Org buffer and show result in *PukiWiki*." (interactive) (let ((text (my-org-pukiwiki-convert-string (buffer-string)))) (with-current-buffer (get-buffer-create "*PukiWiki*") (erase-buffer) (insert text) (goto-char (point-min)) (pop-to-buffer (current-buffer))))) (defun my-org-pukiwiki-export-file (&optional file) "Export current Org buffer to a Shift-JIS PukiWiki file." (interactive) (let* ((src (buffer-file-name)) (dst (or file (if src (concat (file-name-sans-extension src) ".pukiwiki") "output.pukiwiki"))) (text (my-org-pukiwiki-convert-string (buffer-string))) (coding-system-for-write my-org-pukiwiki-coding-system)) (with-temp-file dst (insert text)) (message "Exported PukiWiki: %s" dst))) (defun my-org-pukiwiki-copy-to-clipboard () "Convert current Org buffer to PukiWiki text and copy it to kill-ring." (interactive) (let ((text (my-org-pukiwiki-convert-string (buffer-string)))) (kill-new text) (message "Copied PukiWiki text to clipboard"))) (provide 'my-org-pukiwiki) ;;; my-org-pukiwiki.el ends here