Emacs pra blogueirinho!

Como agora eu escrevo o blog usando arquivos .rst, escrevo usando o editor de texto normal que uso no dia-a-dia, e como uso o emacs, por que não fazer um esqueminha já pra me mostrar o post renderizado enquanto estou escrevendo? É sobre isso o post de hoje.

A ideia geral

A ideia principal é que quando eu salve o arquivo que estou editando eu veja html gerado pelo aphotoblog renderizado num navegador, dividindo minha tela no meio. Pra isso eu preciso buildar o html assim que eu salvar o arquivo, já ter um servidor servindo no diretório e aí abrir um navegador na página certa depois de buildar a coisa.

Pra fazer isso eu vou subir uma instância do tupi assim que eu começar a escrever um post, quando eu salvar o arquivo, uso o after-save-hook do emacs pra rodar uma função que vai rodar o comando de build e por fim abrir a página html do post usando o eaf-browser.

Voltando um pouco: .dir-locals.el

A minha configuração do emacs usa bastante o .dir-locals.el. Esse é um arquivo que a gente coloca num diretório e quando um arquivo nesse diretório (ou em seus sub-diretórios) esse arquivo vai ser lido e as variáveis nesse arquivo estarão disponíveis no buffer.

O .dir-locals.el é algo assim:

((text-mode . ((some-var . "the-value")))
 (prog-mode . ((other-var . "t"))))

E aí para acessar essas variáveis use-se o hack-local-variables:

(hack-local-variables)
(message some-var)

E assim eu seto variáveis de configuração para um projeto.

De volta ao caminho

Bom, com a coisa do .dir-locals.el explicada, já dá pra fazer o que tem que ser feito. Pra repassar, a ideia é a seguinte:

Quando eu abrir um arquivo .rst que será um post, eu inicio uma instância do webserver e quando eu salvar o arquivo eu faço o build do post e abro um navegador mostrando o post já com o html renderizado.

Pra fazer isso a coisa é assim:

(require 'vterm)
(require 'eaf-browser)

(defun pdj:run-in-term-on-background (command &optional term-name)
  "Run COMMAND in a new vterm buffer in background, without poping to buffer.

If TERM-NAME is provided, use it as the buffer name."
  (interactive "P")

  (let ((vterm-buffer-name (or term-name
                               (generate-new-buffer-name pdj:vterm-buffer-name))))
    (vterm--internal #'pdj:--dummy-pop-to-buffer vterm-buffer-name)
    (vterm-send-string command)
    (vterm-send-return)))

(defcustom pdj:ablog-tupi-bin "tupi" "the tupi binary")
(defcustom pdj:ablog-tupi-buffer-name "ablog-tupi" "buffer name for the tupi process")
(defcustom pdj:ablog-build-dir "_website/build"
  "build directory for the blog html")
(defcustom pdj:ablog-build-cmd "make build" "command to build the blog html")
(defcustom pdj:ablog-build-buffer-name "ablog-build" "buffer name for the blog build")
(defcustom pdj:ablog-tupi-url "http://localhost:8080" "url serving the blog html")

(setq pdj:--ablog-tupi-started nil)


(defun pdj:ablog-set-start-tupi-cmd ()
  (hack-local-variables)

  (setq pdj:ablog-start-tupi-cmd (concat pdj:ablog-tupi-bin " "
                                         " -root "
                                         pdj:project-directory
                                         pdj:ablog-build-dir
                                         " -default-to-index")))

(defun pdj:ablog-start-tupi()
  "Starts a tupi instance in a vterm buffer"
  (unless (equal pdj:--ablog-tupi-started t)
    (pdj:run-in-term-on-project-directory-on-background
     pdj:ablog-start-tupi-cmd
     pdj:ablog-tupi-buffer-name)

    (setq pdj:--ablog-tupi-started t)))

(defun pdj:ablog-setup-venv ()
  (hack-local-variables)
  (venv-workon pdj:venv-name))


(defun pdj:ablog-build ()
  "Builds the blog html and calls the apropriate callback for success or error"

  ;; aqui a gente usa o esquema do vterm de executar comandos elisp a partir
  ;; do shell com o `vterm_cmd`
  (setq pdj:--ablog-build-script (format" %s
if [[ \"$?\" == \"0\" ]]
then
  vterm_cmd ablog-build-success %s
else
  vterm_cmd ablog-build-fail
fi" pdj:ablog-build-cmd (pdj:ablog-web-path)))

  (pdj:run-in-term-on-project-directory-on-background
   pdj:--ablog-build-script
   pdj:ablog-build-buffer-name))


(defun pdj:ablog-build-success (webpath)
  "Open or refresh the web browser after the blog html is built"

  (let ((kill-buffer-query-functions nil))
    (kill-buffer pdj:ablog-build-buffer-name))
  (setq pdj:--ablog-post-url (concat pdj:ablog-tupi-url webpath))

  (eaf-open-browser-other-window pdj:--ablog-post-url)
  (eaf-py-proxy-insert_or_refresh_page)
  (other-window 1))

(defun pdj:ablog-build-fail ()
  "Pops to the ablog build buffer"

  (pop-to-buffer pdj:ablog-build-buffer-name))

(defun pdj:ablog-web-path  ()
  "Returns the path to be used in the url for the current post buffer"

  (hack-local-variables)
  (setq pdj--rel-dir (replace-regexp-in-string
                      pdj:project-directory "" (buffer-file-name)))

  (setq pdj--rel-dir (replace-regexp-in-string
                      ".rst" "/" pdj--rel-dir))

  (setq pdj:--ablog-web-path (concat "/" pdj--rel-dir)))


(defun pdj:ablog-set-eval-cmds ()
  "Adds pdj:ablog-build-success and pdj:ablog-build-fail to vterm-eval-cmds list"

  (push (list "ablog-build-fail" 'pdj:ablog-build-fail) vterm-eval-cmds)
  (push (list "ablog-build-success" 'pdj:ablog-build-success) vterm-eval-cmds))


(defun pdj:ablog-all-hooks()
  (hack-local-variables)
  (venv-workon pdj:venv-name)
  (pdj:ablog-set-start-tupi-cmd)
  (pdj:ablog-set-eval-cmds)
  (pdj:ablog-start-tupi))

(defun pdj:ablog-setup()

  (add-hook 'rst-mode-hook 'pdj:ablog-all-hooks)
  (add-hook 'after-save-hook (lambda ()
                               (when (eq major-mode 'rst-mode)
                                 (pdj:ablog-build)))))

E é basicamente isso!