diff --git a/.emacs.d/init.el b/.emacs.d/init.el index 3725182..eb3a3f7 100644 --- a/.emacs.d/init.el +++ b/.emacs.d/init.el @@ -1,3 +1,5 @@ +;;; -*- lexical-binding: t -*- + (defvar bootstrap-version) (let ((bootstrap-file (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory)) @@ -1583,7 +1585,11 @@ Returns ( . ) or nil." (use-package php-mode :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 :straight auctex @@ -3495,6 +3501,7 @@ Returns ( . ) or nil." "=" 'dired-narrow "-" 'dired-create-empty-file "~" 'vterm + "M-r" 'wdired-change-to-wdired-mode "" 'dired-up-directory "" 'dired-find-file "M-" 'dired-open-xdg)) @@ -3942,6 +3949,9 @@ Returns ( . ) or nil." (defface elfeed-blogs-entry nil "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 (elfeed-search-tag-face :foreground (doom-color 'yellow)) (elfeed-videos-entry :foreground (doom-color 'red)) @@ -3949,13 +3959,15 @@ Returns ( . ) or nil." (elfeed-emacs-entry :foreground (doom-color 'magenta)) (elfeed-music-entry :foreground (doom-color 'green)) (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 (setq elfeed-search-face-alist - '((twitter elfeed-twitter-entry) - (podcasts elfeed-podcasts-entry) + '((podcasts elfeed-podcasts-entry) (music elfeed-music-entry) + (gov elfeed-govt-entry) + (twitter elfeed-twitter-entry) (videos elfeed-videos-entry) (emacs elfeed-emacs-entry) (blogs elfeed-blogs-entry) @@ -3978,7 +3990,9 @@ Returns ( . ) or nil." (use-package elfeed-summary :commands (elfeed-summary) - :straight t) + :straight t + :config + (setq elfeed-summary-filter-by-title t)) (defun my/elfeed-toggle-score-sort () (interactive) @@ -4260,6 +4274,229 @@ Returns ( . ) or nil." "+" 'text-scale-increase "-" '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 :commands (erc erc-tls) :straight (:type built-in) diff --git a/.emacs.d/rdrview.tex b/.emacs.d/rdrview.tex new file mode 100644 index 0000000..5cb3923 --- /dev/null +++ b/.emacs.d/rdrview.tex @@ -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}% + {\begingroup\renewcommand{\@latex@error}[2]{\noimage}}% + {}% + {}% +\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} diff --git a/Emacs.org b/Emacs.org index 13b6996..13bc767 100644 --- a/Emacs.org +++ b/Emacs.org @@ -290,6 +290,11 @@ I decided not to keep configs for features that I do not use anymore because thi * Bootstrap 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 *** 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. @@ -2615,7 +2620,11 @@ Vue settings #+begin_src emacs-lisp (use-package php-mode :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 ** LaTeX *** AUCTeX @@ -5178,6 +5187,7 @@ My config mostly follows ranger's and vifm's keybindings which I'm used to. "=" 'dired-narrow "-" 'dired-create-empty-file "~" 'vterm + "M-r" 'wdired-change-to-wdired-mode "" 'dired-up-directory "" 'dired-find-file "M-" 'dired-open-xdg)) @@ -5694,6 +5704,7 @@ My notmuch config now resides in [[file:Mail.org][Mail.org]]. (message "Can't load mail.el")))) #+end_src *** Elfeed +**** General settings [[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. @@ -5785,6 +5796,9 @@ Setting up custom faces for certain tags to make the feed look a bit nicer. (defface elfeed-blogs-entry nil "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 (elfeed-search-tag-face :foreground (doom-color 'yellow)) (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-music-entry :foreground (doom-color 'green)) (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 (setq elfeed-search-face-alist - '((twitter elfeed-twitter-entry) - (podcasts elfeed-podcasts-entry) + '((podcasts elfeed-podcasts-entry) (music elfeed-music-entry) + (gov elfeed-govt-entry) + (twitter elfeed-twitter-entry) (videos elfeed-videos-entry) (emacs elfeed-emacs-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 (use-package elfeed-summary :commands (elfeed-summary) - :straight t) + :straight t + :config + (setq elfeed-summary-filter-by-title t)) #+end_src **** 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. @@ -6248,6 +6266,266 @@ I use it occasionally to open links in elfeed. "+" 'text-scale-increase "-" 'text-scale-decrease) #+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 =

=, 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 is a built-it Emacs IRC client.