feat(emacs): first version of rdrview & pandoc

This commit is contained in:
Pavel Korytov 2022-04-30 20:20:16 +03:00
parent 86bb113b32
commit a33abd485d
3 changed files with 695 additions and 10 deletions

View file

@ -1,3 +1,5 @@
;;; -*- lexical-binding: t -*-
(defvar bootstrap-version) (defvar bootstrap-version)
(let ((bootstrap-file (let ((bootstrap-file
(expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory)) (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
@ -1583,7 +1585,11 @@ Returns (<buffer> . <workspace-index>) or nil."
(use-package php-mode (use-package php-mode
:straight t :straight t
:mode "\\.php\\'") :mode "\\.php\\'"
:config
(add-hook 'php-mode-hook #'smartparens-mode)
(add-hook 'php-mode-hook #'lsp)
(my/set-smartparens-indent 'php-mode))
(use-package tex (use-package tex
:straight auctex :straight auctex
@ -3495,6 +3501,7 @@ Returns (<buffer> . <workspace-index>) or nil."
"=" 'dired-narrow "=" 'dired-narrow
"-" 'dired-create-empty-file "-" 'dired-create-empty-file
"~" 'vterm "~" 'vterm
"M-r" 'wdired-change-to-wdired-mode
"<left>" 'dired-up-directory "<left>" 'dired-up-directory
"<right>" 'dired-find-file "<right>" 'dired-find-file
"M-<return>" 'dired-open-xdg)) "M-<return>" 'dired-open-xdg))
@ -3942,6 +3949,9 @@ Returns (<buffer> . <workspace-index>) or nil."
(defface elfeed-blogs-entry nil (defface elfeed-blogs-entry nil
"Face for the elfeed entries with tag \"blogs\"") "Face for the elfeed entries with tag \"blogs\"")
(defface elfeed-govt-entry nil
"Face for the elfeed entries with tag \"blogs\"")
(my/use-doom-colors (my/use-doom-colors
(elfeed-search-tag-face :foreground (doom-color 'yellow)) (elfeed-search-tag-face :foreground (doom-color 'yellow))
(elfeed-videos-entry :foreground (doom-color 'red)) (elfeed-videos-entry :foreground (doom-color 'red))
@ -3949,13 +3959,15 @@ Returns (<buffer> . <workspace-index>) or nil."
(elfeed-emacs-entry :foreground (doom-color 'magenta)) (elfeed-emacs-entry :foreground (doom-color 'magenta))
(elfeed-music-entry :foreground (doom-color 'green)) (elfeed-music-entry :foreground (doom-color 'green))
(elfeed-podcasts-entry :foreground (doom-color 'yellow)) (elfeed-podcasts-entry :foreground (doom-color 'yellow))
(elfeed-blogs-entry :foreground (doom-color 'orange))) (elfeed-blogs-entry :foreground (doom-color 'orange))
(elfeed-govt-entry :foreground (doom-color 'dark-cyan)))
(with-eval-after-load 'elfeed (with-eval-after-load 'elfeed
(setq elfeed-search-face-alist (setq elfeed-search-face-alist
'((twitter elfeed-twitter-entry) '((podcasts elfeed-podcasts-entry)
(podcasts elfeed-podcasts-entry)
(music elfeed-music-entry) (music elfeed-music-entry)
(gov elfeed-govt-entry)
(twitter elfeed-twitter-entry)
(videos elfeed-videos-entry) (videos elfeed-videos-entry)
(emacs elfeed-emacs-entry) (emacs elfeed-emacs-entry)
(blogs elfeed-blogs-entry) (blogs elfeed-blogs-entry)
@ -3978,7 +3990,9 @@ Returns (<buffer> . <workspace-index>) or nil."
(use-package elfeed-summary (use-package elfeed-summary
:commands (elfeed-summary) :commands (elfeed-summary)
:straight t) :straight t
:config
(setq elfeed-summary-filter-by-title t))
(defun my/elfeed-toggle-score-sort () (defun my/elfeed-toggle-score-sort ()
(interactive) (interactive)
@ -4260,6 +4274,229 @@ Returns (<buffer> . <workspace-index>) or nil."
"+" 'text-scale-increase "+" 'text-scale-increase
"-" 'text-scale-decrease) "-" 'text-scale-decrease)
(defun my/rdrview-get (url callback)
(let* ((buffer (generate-new-buffer "rdrview"))
(proc (start-process "rdrview" buffer "rdrview"
url "-T" "title,sitename,body"
"-H")))
(set-process-sentinel
proc
(lambda (process _msg)
(let ((status (process-status process))
(code (process-exit-status process)))
(cond ((and (eq status 'exit) (= code 0))
(progn
(funcall callback
(with-current-buffer (process-buffer process)
(buffer-string)))
(kill-buffer (process-buffer process))) )
((or (and (eq status 'exit) (> code 0))
(eq status 'signal))
(let ((err (with-current-buffer (process-buffer process)
(buffer-string))))
(kill-buffer (process-buffer process))
(user-error "Error in rdrview: %s" err)))))))
proc))
(defun my/rdrview-parse (dom-string)
(let ((dom (with-temp-buffer
(insert dom-string)
(libxml-parse-html-region (point-min) (point-max)))))
(let (title sitename content)
(dolist (child (dom-children (car (dom-by-id dom "readability-page-1"))))
(when (listp child)
(cond
((eq (car child) 'h1)
(setq title (dom-text child)))
((eq (car child) 'h2)
(setq sitename (dom-text child)))
((eq (car child) 'div)
(setq content child)))))
(dom-search
content
(lambda (el)
(when (listp el)
(pcase (car el)
('h2 (setf (car el) 'h1))
('h3 (setf (car el) 'h2))
('h4 (setf (car el) 'h3))
('h5 (setf (car el) 'h4))
('h6 (setf (car el) 'h5))))))
`((title . ,title)
(sitename . ,sitename)
(content . ,(with-temp-buffer
(dom-print content)
(buffer-string)))))))
(setq my/rdrview-template (expand-file-name
(concat user-emacs-directory "rdrview.tex")))
(cl-defun my/rdrview-render (content type variables callback
&key file-name overwrite)
(unless file-name
(setq file-name (format "/tmp/%d.pdf" (random 100000000))))
(let (params
(temp-file-name (format "/tmp/%d.%s" (random 100000000) type)))
(cl-loop for (key . value) in variables
when value
do (progn
(push "--variable" params)
(push (format "%s=%s" key value) params)))
(setq params (nreverse params))
(if (and (file-exists-p file-name) (not overwrite))
(funcall callback file-name)
(with-temp-file temp-file-name
(insert content))
(let ((proc (apply #'start-process
"pandoc" (get-buffer-create "*Pandoc*") "pandoc"
temp-file-name "-o" file-name
"--pdf-engine=xelatex" "--template" my/rdrview-template
params)))
(set-process-sentinel
proc
(lambda (process _msg)
(let ((status (process-status process))
(code (process-exit-status process)))
(cond ((and (eq status 'exit) (= code 0))
(progn
(message "Done!")
(funcall callback file-name)))
((or (and (eq status 'exit) (> code 0))
(eq status 'signal))
(user-error "Error in pandoc. Check the *Pandoc* buffer"))))))))))
(defun my/get-languages (url)
(let ((main-lang "english")
(other-lang "russian"))
(when (string-match-p (rx ".ru") url)
(setq main-lang "russian"
other-lang "english"))
(list main-lang other-lang)))
(defun my/rdrview-open (url overwrite)
(interactive
(let ((url (read-from-minibuffer
"URL: "
(if (bound-and-true-p elfeed-show-entry)
(elfeed-entry-link elfeed-show-entry)))))
(when (string-empty-p url)
(user-error "URL is empty"))
(list url current-prefix-arg)))
(my/rdrview-get
url
(lambda (res)
(let ((data (my/rdrview-parse res))
(langs (my/get-languages url)))
(my/rdrview-render
(alist-get 'content data)
'html
`((title . ,(alist-get 'title data))
(subtitle . ,(alist-get 'sitename data))
(main-lang . ,(nth 0 langs))
(other-lang . ,(nth 1 langs)))
(lambda (file-name)
(start-process "xdg-open" nil "xdg-open" file-name)))))))
(setq my/elfeed-pdf-dir (expand-file-name "~/.elfeed/pdf/"))
(defun my/elfeed-open-pdf (entry overwrite)
(interactive (list elfeed-show-entry current-prefix-arg))
(let ((authors (mapcar (lambda (m) (plist-get m :name)) (elfeed-meta entry :authors)))
(feed-title (elfeed-feed-title (elfeed-entry-feed entry)))
(tags (mapconcat #'symbol-name (elfeed-entry-tags entry) ", "))
(date (format-time-string "%a, %e %b %Y" (seconds-to-time (elfeed-entry-date entry))))
(content (elfeed-deref (elfeed-entry-content entry)))
(file-name (concat my/elfeed-pdf-dir
(elfeed-ref-id (elfeed-entry-content entry))
".pdf"))
(main-language "english")
(other-language "russian"))
(unless content
(user-error "No content!"))
(setq subtitle
(cond
((seq-empty-p authors) feed-title)
((and (not (seq-empty-p (car authors)))
(string-match-p (regexp-quote (car authors)) feed-title)) feed-title)
(t (concat (string-join authors ", ") "\\\\" feed-title))))
(when (member 'ru (elfeed-entry-tags entry))
(setq main-language "russian")
(setq other-language "english"))
(my/rdrview-render
(if (bound-and-true-p my/elfeed-show-rdrview-html)
my/elfeed-show-rdrview-html
content)
(elfeed-entry-content-type entry)
`((title . ,(elfeed-entry-title entry))
(subtitle . ,subtitle)
(date . ,date)
(tags . ,tags)
(main-lang . ,main-language)
(other-lang . ,other-language))
(lambda (file-name)
(start-process "xdg-open" nil "xdg-open" file-name))
:file-name file-name
:overwrite current-prefix-arg)))
(defvar-local my/elfeed-show-rdrview-html nil)
(defun my/rdrview-elfeed-show ()
(interactive)
(unless elfeed-show-entry
(user-error "No elfeed entry in this buffer!"))
(my/rdrview-get
(elfeed-entry-link elfeed-show-entry)
(lambda (result)
(let* ((data (my/rdrview-parse result))
(inhibit-read-only t)
(title (elfeed-entry-title elfeed-show-entry))
(date (seconds-to-time (elfeed-entry-date elfeed-show-entry)))
(authors (elfeed-meta elfeed-show-entry :authors))
(link (elfeed-entry-link elfeed-show-entry))
(tags (elfeed-entry-tags elfeed-show-entry))
(tagsstr (mapconcat #'symbol-name tags ", "))
(nicedate (format-time-string "%a, %e %b %Y %T %Z" date))
(content (alist-get 'content data))
(feed (elfeed-entry-feed elfeed-show-entry))
(feed-title (elfeed-feed-title feed))
(base (and feed (elfeed-compute-base (elfeed-feed-url feed)))))
(erase-buffer)
(insert (format (propertize "Title: %s\n" 'face 'message-header-name)
(propertize title 'face 'message-header-subject)))
(when elfeed-show-entry-author
(dolist (author authors)
(let ((formatted (elfeed--show-format-author author)))
(insert
(format (propertize "Author: %s\n" 'face 'message-header-name)
(propertize formatted 'face 'message-header-to))))))
(insert (format (propertize "Date: %s\n" 'face 'message-header-name)
(propertize nicedate 'face 'message-header-other)))
(insert (format (propertize "Feed: %s\n" 'face 'message-header-name)
(propertize feed-title 'face 'message-header-other)))
(when tags
(insert (format (propertize "Tags: %s\n" 'face 'message-header-name)
(propertize tagsstr 'face 'message-header-other))))
(insert (propertize "Link: " 'face 'message-header-name))
(elfeed-insert-link link link)
(insert "\n")
(cl-loop for enclosure in (elfeed-entry-enclosures elfeed-show-entry)
do (insert (propertize "Enclosure: " 'face 'message-header-name))
do (elfeed-insert-link (car enclosure))
do (insert "\n"))
(insert "\n")
(if content
(elfeed-insert-html content base)
(insert (propertize "(empty)\n" 'face 'italic)))
(setq-local my/elfeed-show-rdrview-html content)
(goto-char (point-min))))))
(with-eval-after-load 'elfeed
(general-define-key
:states '(normal)
:keymaps 'elfeed-show-mode-map
"gp" #'my/rdrview-elfeed-show
"gv" #'my/elfeed-open-pdf))
(use-package erc (use-package erc
:commands (erc erc-tls) :commands (erc erc-tls)
:straight (:type built-in) :straight (:type built-in)

170
.emacs.d/rdrview.tex Normal file
View file

@ -0,0 +1,170 @@
\documentclass[a4paper, 12pt]{extarticle}
% ====== Math ======
\usepackage{amsmath} % Math stuff
\usepackage{amssymb}
\usepackage{mathspec}
% ====== List ======
\usepackage{enumitem}
\usepackage{etoolbox}
\setlist{nosep, topsep=-10pt} % Remove sep-s beetween list elements
\setlist[enumerate]{label*=\arabic*.}
\setlist[enumerate,1]{after=\vspace{0.5\baselineskip}}
\setlist[itemize,1]{after=\vspace{0.5\baselineskip}}
\AtBeginEnvironment{itemize}{%
\setlist[enumerate]{label=\arabic*.}
\setlist[enumerate,1]{after=\vspace{0\baselineskip}}
}
\providecommand{\tightlist}{%
\setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
% ====== Link ======
\usepackage{xcolor}
\usepackage{hyperref} % Links
\hypersetup{
colorlinks=true,
citecolor=blue,
filecolor=blue,
linkcolor=blue,
urlcolor=blue,
}
% Linebreaks for urls
\expandafter\def\expandafter\UrlBreaks\expandafter{\UrlBreaks% save the current one
\do\a\do\b\do\c\do\d\do\e\do\f\do\g\do\h\do\i\do\j%
\do\k\do\l\do\m\do\n\do\o\do\p\do\q\do\r\do\s\do\t%
\do\u\do\v\do\w\do\x\do\y\do\z\do\A\do\B\do\C\do\D%
\do\E\do\F\do\G\do\H\do\I\do\J\do\K\do\L\do\M\do\N%
\do\O\do\P\do\Q\do\R\do\S\do\T\do\U\do\V\do\W\do\X%
\do\Y\do\Z}
% ====== Captions ======
% TODO
% ====== Table ======
\usepackage{array}
\usepackage{booktabs}
\usepackage{longtable}
\usepackage{multirow}
\usepackage{calc}
% ====== Images ======
\usepackage{graphicx} % Pictures
\makeatletter
\def\maxwidth{\ifdim\Gin@nat@width>\linewidth\linewidth\else\Gin@nat@width\fi}
\def\maxheight{\ifdim\Gin@nat@height>\textheight\textheight\else\Gin@nat@height\fi}
\makeatother
% Scale images if necessary, so that they will not overflow the page
% margins by default, and it is still possible to overwrite the defaults
% using explicit options in \includegraphics[width, height, ...]{}
\setkeys{Gin}{width=\maxwidth,height=\maxheight,keepaspectratio}
% Set default figure placement to htbp
\makeatletter
\def\fps@figure{htbp}
\makeatother
\newcommand{\noimage}{%
\setlength{\fboxsep}{-\fboxrule}%
\fbox{\phantom{\rule{150pt}{100pt}}}% Framed box
}
\makeatletter
\patchcmd{\Gin@ii}
{\begingroup}% <search>
{\begingroup\renewcommand{\@latex@error}[2]{\noimage}}% <replace>
{}% <success>
{}% <failure>
\makeatother
% ====== Misc ======
\usepackage{fancyvrb}
\usepackage{csquotes}
\usepackage[normalem]{ulem}
% Quotes and verses style
\AtBeginEnvironment{quote}{\singlespacing}
\AtBeginEnvironment{verse}{\singlespacing}
% ====== Text spacing ======
\usepackage{setspace} % String spacing
\onehalfspacing{}
\usepackage{indentfirst}
\setlength\parindent{0cm}
\setlength\parskip{6pt}
% ====== Page layout ======
\usepackage[ % Margins
left=2cm,
right=2cm,
top=2cm,
bottom=2cm
]{geometry}
% ====== Document sectioning ======
\usepackage{titlesec}
\titleformat*{\section}{\bfseries}
\titleformat*{\subsection}{\bfseries}
\titleformat*{\subsubsection}{\bfseries}
\titleformat*{\paragraph}{\bfseries}
\titleformat*{\subparagraph}{\bfseries\itshape}% chktex 6
\titlespacing*{\section}{0cm}{12pt}{3pt}
\titlespacing*{\subsection}{0cm}{12pt}{3pt}
\titlespacing*{\subsubsection}{0cm}{12pt}{0pt}
\titlespacing*{\paragraph}{0pt}{6pt}{6pt}
\titlespacing*{\subparagraph}{0pt}{6pt}{3pt}
\makeatletter
\providecommand{\subtitle}[1]{
\apptocmd{\@title}{\par {\large #1 \par}}{}{}
}
\makeatother
% ====== Pandoc =======
$if(highlighting-macros)$
$highlighting-macros$
$endif$
% ====== Language ======
\usepackage{polyglossia}
\setdefaultlanguage{$main-lang$}
\setotherlanguage{$other-lang$}
\defaultfontfeatures{Ligatures={TeX}}
\setmainfont{Open Sans}
\newfontfamily\cyrillicfont{Open Sans}
\setmonofont[Scale=0.9]{DejaVu Sans Mono}
\newfontfamily{\cyrillicfonttt}{DejaVu Sans Mono}[Scale=0.8]
\usepackage{bidi}
\usepackage{microtype}
\setlength{\emergencystretch}{3pt}
$if(title)$
\title{$title$}
$endif$
$if(subtitle)$
\subtitle{$subtitle$}
$endif$
$if(author)$
\author{$for(author)$$author$$sep$ \and $endfor$}
$endif$
$if(date)$
\date{$date$}
$endif$
\begin{document}
\maketitle{}
$body$
\end{document}

288
Emacs.org
View file

@ -290,6 +290,11 @@ I decided not to keep configs for features that I do not use anymore because thi
* Bootstrap * Bootstrap
Setting up the environment, performance tuning and a few basic settings. Setting up the environment, performance tuning and a few basic settings.
First things first, lexical binding.
#+begin_src emacs-lisp
;;; -*- lexical-binding: t -*-
#+end_src
** Packages ** Packages
*** straight.el *** straight.el
Straight.el is my Emacs package manager of choice. Its advantages & disadvantages over other options are listed pretty thoroughly in the README file in the repo. Straight.el is my Emacs package manager of choice. Its advantages & disadvantages over other options are listed pretty thoroughly in the README file in the repo.
@ -2615,7 +2620,11 @@ Vue settings
#+begin_src emacs-lisp #+begin_src emacs-lisp
(use-package php-mode (use-package php-mode
:straight t :straight t
:mode "\\.php\\'") :mode "\\.php\\'"
:config
(add-hook 'php-mode-hook #'smartparens-mode)
(add-hook 'php-mode-hook #'lsp)
(my/set-smartparens-indent 'php-mode))
#+end_src #+end_src
** LaTeX ** LaTeX
*** AUCTeX *** AUCTeX
@ -5178,6 +5187,7 @@ My config mostly follows ranger's and vifm's keybindings which I'm used to.
"=" 'dired-narrow "=" 'dired-narrow
"-" 'dired-create-empty-file "-" 'dired-create-empty-file
"~" 'vterm "~" 'vterm
"M-r" 'wdired-change-to-wdired-mode
"<left>" 'dired-up-directory "<left>" 'dired-up-directory
"<right>" 'dired-find-file "<right>" 'dired-find-file
"M-<return>" 'dired-open-xdg)) "M-<return>" 'dired-open-xdg))
@ -5694,6 +5704,7 @@ My notmuch config now resides in [[file:Mail.org][Mail.org]].
(message "Can't load mail.el")))) (message "Can't load mail.el"))))
#+end_src #+end_src
*** Elfeed *** Elfeed
**** General settings
[[https://github.com/skeeto/elfeed][elfeed]] is an Emacs RSS client. [[https://github.com/skeeto/elfeed][elfeed]] is an Emacs RSS client.
The advice there sets =shr-use-fonts= to nil while rendering HTML, so the =elfeed-show= buffer will use monospace font. The advice there sets =shr-use-fonts= to nil while rendering HTML, so the =elfeed-show= buffer will use monospace font.
@ -5785,6 +5796,9 @@ Setting up custom faces for certain tags to make the feed look a bit nicer.
(defface elfeed-blogs-entry nil (defface elfeed-blogs-entry nil
"Face for the elfeed entries with tag \"blogs\"") "Face for the elfeed entries with tag \"blogs\"")
(defface elfeed-govt-entry nil
"Face for the elfeed entries with tag \"blogs\"")
(my/use-doom-colors (my/use-doom-colors
(elfeed-search-tag-face :foreground (doom-color 'yellow)) (elfeed-search-tag-face :foreground (doom-color 'yellow))
(elfeed-videos-entry :foreground (doom-color 'red)) (elfeed-videos-entry :foreground (doom-color 'red))
@ -5792,13 +5806,15 @@ Setting up custom faces for certain tags to make the feed look a bit nicer.
(elfeed-emacs-entry :foreground (doom-color 'magenta)) (elfeed-emacs-entry :foreground (doom-color 'magenta))
(elfeed-music-entry :foreground (doom-color 'green)) (elfeed-music-entry :foreground (doom-color 'green))
(elfeed-podcasts-entry :foreground (doom-color 'yellow)) (elfeed-podcasts-entry :foreground (doom-color 'yellow))
(elfeed-blogs-entry :foreground (doom-color 'orange))) (elfeed-blogs-entry :foreground (doom-color 'orange))
(elfeed-govt-entry :foreground (doom-color 'dark-cyan)))
(with-eval-after-load 'elfeed (with-eval-after-load 'elfeed
(setq elfeed-search-face-alist (setq elfeed-search-face-alist
'((twitter elfeed-twitter-entry) '((podcasts elfeed-podcasts-entry)
(podcasts elfeed-podcasts-entry)
(music elfeed-music-entry) (music elfeed-music-entry)
(gov elfeed-govt-entry)
(twitter elfeed-twitter-entry)
(videos elfeed-videos-entry) (videos elfeed-videos-entry)
(emacs elfeed-emacs-entry) (emacs elfeed-emacs-entry)
(blogs elfeed-blogs-entry) (blogs elfeed-blogs-entry)
@ -5830,7 +5846,9 @@ The default interface of elfeed is just a list of all entries, so it gets hard t
#+begin_src emacs-lisp #+begin_src emacs-lisp
(use-package elfeed-summary (use-package elfeed-summary
:commands (elfeed-summary) :commands (elfeed-summary)
:straight t) :straight t
:config
(setq elfeed-summary-filter-by-title t))
#+end_src #+end_src
**** elfeed-score **** elfeed-score
[[https://github.com/sp1ff/elfeed-score][elfeed-score]] is a package that implements scoring for the elfeed entries. Entries are scored by a set of rules for tags/title/content/etc and sorted by that score. [[https://github.com/sp1ff/elfeed-score][elfeed-score]] is a package that implements scoring for the elfeed entries. Entries are scored by a set of rules for tags/title/content/etc and sorted by that score.
@ -6248,6 +6266,266 @@ I use it occasionally to open links in elfeed.
"+" 'text-scale-increase "+" 'text-scale-increase
"-" 'text-scale-decrease) "-" 'text-scale-decrease)
#+end_src #+end_src
*** Reader View & PDFs
**** rdrview
[[https://github.com/eafer/rdrview][rdrview]] is a command-line tool that provides Firefox Reader view as a command-line tool. A Guix definition is available in [[https://github.com/SqrtMinusOne/channel-q][my Guix channel]].
The basic idea here is to take an arbitrary web page and convert it to PDF via pandoc.
So, first we need to get the =rdrview= representation of the URL:
#+begin_src emacs-lisp
(defun my/rdrview-get (url callback)
(let* ((buffer (generate-new-buffer "rdrview"))
(proc (start-process "rdrview" buffer "rdrview"
url "-T" "title,sitename,body"
"-H")))
(set-process-sentinel
proc
(lambda (process _msg)
(let ((status (process-status process))
(code (process-exit-status process)))
(cond ((and (eq status 'exit) (= code 0))
(progn
(funcall callback
(with-current-buffer (process-buffer process)
(buffer-string)))
(kill-buffer (process-buffer process))) )
((or (and (eq status 'exit) (> code 0))
(eq status 'signal))
(let ((err (with-current-buffer (process-buffer process)
(buffer-string))))
(kill-buffer (process-buffer process))
(user-error "Error in rdrview: %s" err)))))))
proc))
#+end_src
After that, process the rdrview output. First, it outputs metadata to the resulting HTML, so this part parses the DOM and retrieves the header and the name of the site.
Second, for some reason the header enumeration starts with =<h2>=, so this also shifts headers up by one.
#+begin_src emacs-lisp
(defun my/rdrview-parse (dom-string)
(let ((dom (with-temp-buffer
(insert dom-string)
(libxml-parse-html-region (point-min) (point-max)))))
(let (title sitename content)
(dolist (child (dom-children (car (dom-by-id dom "readability-page-1"))))
(when (listp child)
(cond
((eq (car child) 'h1)
(setq title (dom-text child)))
((eq (car child) 'h2)
(setq sitename (dom-text child)))
((eq (car child) 'div)
(setq content child)))))
(dom-search
content
(lambda (el)
(when (listp el)
(pcase (car el)
('h2 (setf (car el) 'h1))
('h3 (setf (car el) 'h2))
('h4 (setf (car el) 'h3))
('h5 (setf (car el) 'h4))
('h6 (setf (car el) 'h5))))))
`((title . ,title)
(sitename . ,sitename)
(content . ,(with-temp-buffer
(dom-print content)
(buffer-string)))))))
#+end_src
**** Opening stuff in PDF viewer
Now, we need to render the resulting HTML to a pdf. To do that, we can use =pandoc= with a [[file:.emacs.d/rdrview.tex][custom template]].
#+begin_src emacs-lisp
(setq my/rdrview-template (expand-file-name
(concat user-emacs-directory "rdrview.tex")))
(cl-defun my/rdrview-render (content type variables callback
&key file-name overwrite)
(unless file-name
(setq file-name (format "/tmp/%d.pdf" (random 100000000))))
(let (params
(temp-file-name (format "/tmp/%d.%s" (random 100000000) type)))
(cl-loop for (key . value) in variables
when value
do (progn
(push "--variable" params)
(push (format "%s=%s" key value) params)))
(setq params (nreverse params))
(if (and (file-exists-p file-name) (not overwrite))
(funcall callback file-name)
(with-temp-file temp-file-name
(insert content))
(let ((proc (apply #'start-process
"pandoc" (get-buffer-create "*Pandoc*") "pandoc"
temp-file-name "-o" file-name
"--pdf-engine=xelatex" "--template" my/rdrview-template
params)))
(set-process-sentinel
proc
(lambda (process _msg)
(let ((status (process-status process))
(code (process-exit-status process)))
(cond ((and (eq status 'exit) (= code 0))
(progn
(message "Done!")
(funcall callback file-name)))
((or (and (eq status 'exit) (> code 0))
(eq status 'signal))
(user-error "Error in pandoc. Check the *Pandoc* buffer"))))))))))
#+end_src
And putting all of this together to get a PDF representation of an arbitrary URL.
#+begin_src emacs-lisp
(defun my/get-languages (url)
(let ((main-lang "english")
(other-lang "russian"))
(when (string-match-p (rx ".ru") url)
(setq main-lang "russian"
other-lang "english"))
(list main-lang other-lang)))
(defun my/rdrview-open (url overwrite)
(interactive
(let ((url (read-from-minibuffer
"URL: "
(if (bound-and-true-p elfeed-show-entry)
(elfeed-entry-link elfeed-show-entry)))))
(when (string-empty-p url)
(user-error "URL is empty"))
(list url current-prefix-arg)))
(my/rdrview-get
url
(lambda (res)
(let ((data (my/rdrview-parse res))
(langs (my/get-languages url)))
(my/rdrview-render
(alist-get 'content data)
'html
`((title . ,(alist-get 'title data))
(subtitle . ,(alist-get 'sitename data))
(main-lang . ,(nth 0 langs))
(other-lang . ,(nth 1 langs)))
(lambda (file-name)
(start-process "xdg-open" nil "xdg-open" file-name)))))))
#+end_src
**** Rendering elfeed entries as PDFs
This also goes really well with elfeed, because for these RSS feeds that have a well-formed HTML part there's even no need to invoke =rdrview=, we can just feed the HTML to =pandoc=.
TODO escape title
#+begin_src emacs-lisp
(setq my/elfeed-pdf-dir (expand-file-name "~/.elfeed/pdf/"))
(defun my/elfeed-open-pdf (entry overwrite)
(interactive (list elfeed-show-entry current-prefix-arg))
(let ((authors (mapcar (lambda (m) (plist-get m :name)) (elfeed-meta entry :authors)))
(feed-title (elfeed-feed-title (elfeed-entry-feed entry)))
(tags (mapconcat #'symbol-name (elfeed-entry-tags entry) ", "))
(date (format-time-string "%a, %e %b %Y" (seconds-to-time (elfeed-entry-date entry))))
(content (elfeed-deref (elfeed-entry-content entry)))
(file-name (concat my/elfeed-pdf-dir
(elfeed-ref-id (elfeed-entry-content entry))
".pdf"))
(main-language "english")
(other-language "russian"))
(unless content
(user-error "No content!"))
(setq subtitle
(cond
((seq-empty-p authors) feed-title)
((and (not (seq-empty-p (car authors)))
(string-match-p (regexp-quote (car authors)) feed-title)) feed-title)
(t (concat (string-join authors ", ") "\\\\" feed-title))))
(when (member 'ru (elfeed-entry-tags entry))
(setq main-language "russian")
(setq other-language "english"))
(my/rdrview-render
(if (bound-and-true-p my/elfeed-show-rdrview-html)
my/elfeed-show-rdrview-html
content)
(elfeed-entry-content-type entry)
`((title . ,(elfeed-entry-title entry))
(subtitle . ,subtitle)
(date . ,date)
(tags . ,tags)
(main-lang . ,main-language)
(other-lang . ,other-language))
(lambda (file-name)
(start-process "xdg-open" nil "xdg-open" file-name))
:file-name file-name
:overwrite current-prefix-arg)))
#+end_src
**** Viewing elfeed entries view rdrview
However, in some cases RSS feeds supply only a short description of the content instead of the actual content. If that's the case, we can use =rdrview= to replace the actual content.
So, the following is the corresponding modification of =elfeed-show-refresh--mail-style= function:
#+begin_src emacs-lisp
(defvar-local my/elfeed-show-rdrview-html nil)
(defun my/rdrview-elfeed-show ()
(interactive)
(unless elfeed-show-entry
(user-error "No elfeed entry in this buffer!"))
(my/rdrview-get
(elfeed-entry-link elfeed-show-entry)
(lambda (result)
(let* ((data (my/rdrview-parse result))
(inhibit-read-only t)
(title (elfeed-entry-title elfeed-show-entry))
(date (seconds-to-time (elfeed-entry-date elfeed-show-entry)))
(authors (elfeed-meta elfeed-show-entry :authors))
(link (elfeed-entry-link elfeed-show-entry))
(tags (elfeed-entry-tags elfeed-show-entry))
(tagsstr (mapconcat #'symbol-name tags ", "))
(nicedate (format-time-string "%a, %e %b %Y %T %Z" date))
(content (alist-get 'content data))
(feed (elfeed-entry-feed elfeed-show-entry))
(feed-title (elfeed-feed-title feed))
(base (and feed (elfeed-compute-base (elfeed-feed-url feed)))))
(erase-buffer)
(insert (format (propertize "Title: %s\n" 'face 'message-header-name)
(propertize title 'face 'message-header-subject)))
(when elfeed-show-entry-author
(dolist (author authors)
(let ((formatted (elfeed--show-format-author author)))
(insert
(format (propertize "Author: %s\n" 'face 'message-header-name)
(propertize formatted 'face 'message-header-to))))))
(insert (format (propertize "Date: %s\n" 'face 'message-header-name)
(propertize nicedate 'face 'message-header-other)))
(insert (format (propertize "Feed: %s\n" 'face 'message-header-name)
(propertize feed-title 'face 'message-header-other)))
(when tags
(insert (format (propertize "Tags: %s\n" 'face 'message-header-name)
(propertize tagsstr 'face 'message-header-other))))
(insert (propertize "Link: " 'face 'message-header-name))
(elfeed-insert-link link link)
(insert "\n")
(cl-loop for enclosure in (elfeed-entry-enclosures elfeed-show-entry)
do (insert (propertize "Enclosure: " 'face 'message-header-name))
do (elfeed-insert-link (car enclosure))
do (insert "\n"))
(insert "\n")
(if content
(elfeed-insert-html content base)
(insert (propertize "(empty)\n" 'face 'italic)))
(setq-local my/elfeed-show-rdrview-html content)
(goto-char (point-min))))))
#+end_src
Setting keybindings for elfeed:
#+begin_src emacs-lisp
(with-eval-after-load 'elfeed
(general-define-key
:states '(normal)
:keymaps 'elfeed-show-mode-map
"gp" #'my/rdrview-elfeed-show
"gv" #'my/elfeed-open-pdf))
#+end_src
*** ERC *** ERC
ERC is a built-it Emacs IRC client. ERC is a built-it Emacs IRC client.