From 469df1133372945e26d0574c4bdf4241d1b09062 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 3 Aug 2012 22:46:15 +0200 Subject: Rearrange Hopefully this will make it a little clearer. More rearranging might happen in the future. --- avandu.el | 430 +++++++++++++++++++++++++++++++++----------------------------- 1 file changed, 229 insertions(+), 201 deletions(-) (limited to 'avandu.el') diff --git a/avandu.el b/avandu.el index 1ba8bca..1d441a6 100644 --- a/avandu.el +++ b/avandu.el @@ -49,10 +49,25 @@ (require 'url) (require 'simple) +(defconst avandu-entity-replacement-alist + '(("hellip" . 8230) + ("qout" . 34) + ("amp" . 38) + ("nbsp" . 32)) + "What to replace the part between & and ; of HTML entities with + names.") + +;; Customization (defgroup avandu nil "Tiny Tiny RSS interface for emacs." :group 'applications) +;; Faces +(defface avandu-overview-excerpt + '((t (:inherit shadow :slant italic))) + "Face for article excerpts in avandu overview." + :group 'avandu) + (defface avandu-overview-feed '((((class color) (background dark)) @@ -63,16 +78,6 @@ "Face for feed titles in avandu overview." :group 'avandu) -(defface avandu-overview-unread-article - '((((class color) - (background dark)) - (:foreground "orange3" :weight bold :family "sans")) - (((class color) - (background light)) - (:foregroung "red4" :weight bold :family "sans"))) - "Face for unread article titles in avandu overview." - :group 'avandu) - (defface avandu-overview-read-article '((((class color) (background dark)) @@ -83,33 +88,27 @@ "Face for read article titles in avandu overview." :group 'avandu) -(defface avandu-overview-excerpt - '((t (:inherit shadow :slant italic))) - "Face for article excerpts in avandu overview." +(defface avandu-overview-unread-article + '((((class color) + (background dark)) + (:foreground "orange3" :weight bold :family "sans")) + (((class color) + (background light)) + (:foregroung "red4" :weight bold :family "sans"))) + "Face for unread article titles in avandu overview." :group 'avandu) +;; User options (defcustom avandu-tt-rss-api-url nil "URL of your Tiny Tiny RSS instance. For example: http://tt-rss.org/demo/api/" :group 'avandu :type 'string) +;; Variables (defvar avandu--session-id nil "*internal* Session id for avandu.") -(defvar avandu-user nil - "Username of your Tiny Tiny RSS account.") - -(defvar avandu-password nil - "Password for your Tiny Tiny RSS account.") - -(defvar avandu-feed-button-map - (let ((map (make-sparse-keymap))) - (set-keymap-parent map button-map) - (define-key map "c" 'avandu-feed-catchup) - map) - "Keymap for feeds in `avandu-overview-mode'.") - (defvar avandu-article-button-map (let ((map (make-sparse-keymap))) (set-keymap-parent map button-map) @@ -118,6 +117,13 @@ map) "Keymap for articles in `avandu-overview-mode'.") +(defvar avandu-feed-button-map + (let ((map (make-sparse-keymap))) + (set-keymap-parent map button-map) + (define-key map "c" 'avandu-feed-catchup) + map) + "Keymap for feeds in `avandu-overview-mode'.") + (defvar avandu-overview-map (let ((map (make-sparse-keymap))) (set-keymap-parent map special-mode-map) @@ -128,56 +134,143 @@ map) "Keymap for `avandu-overview-mode'.") -(defconst avandu-entity-replacement-alist - '(("hellip" . 8230) - ("qout" . 34) - ("amp" . 38) - ("nbsp" . 32)) - "What to replace the part between & and ; of HTML entities with - names.") +(defvar avandu-password nil + "Password for your Tiny Tiny RSS account.") -(define-derived-mode avandu-overview-mode special-mode "Avandu:Overview" - "Major mode fo the avandu overview screen. +(defvar avandu-user nil + "Username of your Tiny Tiny RSS account.") -This screen shows the articles categorized by feed as a list. It -doesn't sort the list, so you'll have to set that up in tt-rss. +;; Macros +(defmacro avandu--next-button-of-type (direction type) + "Go DIRECTION and find the next button of a TYPE." + (let ((prop (case type + (feed 'feed-id) + (article 'article-id) + (t (error "Invalid type")))) + (next-point-function (case direction + (forward 'point-min) + (backward 'point-max) + (t (error "Invalid direction")))) + (next-button-function (case direction + (forward 'next-button) + (backward 'previous-button) + (t (error "Invalid direction"))))) + `(let ((pos (point)) + found-value) + (while (not found-value) + (let ((button (,next-button-function pos))) + (unless button + (setq pos (,next-point-function) + button (or (button-at pos) + (,next-button-function pos)))) + (setq found-value (button-get button ',prop) + pos (overlay-start button)))) + (goto-char pos)))) -\\{avandu-overview-map} -\\" - (use-local-map avandu-overview-map) - (set (make-local-variable 'revert-buffer-function) - #'(lambda (ignore-auto noconfirm) (avandu-list)))) +(defmacro avandu-getset (var prompt &optional passwdp) + "Ask the user for, and then save, VAR with PROMPT. Use +`read-passwd' if PASSWDP and `read-string' otherwise." + `(or ,var (setq ,var (,(if passwdp 'read-passwd 'read-string) + ,prompt)))) + +;; Internal +(defun avandu--check-login () + "Check to see if we're (still) logged in, try to login +otherwise. Signals an error if we're not logged in *and* login +was unsuccesful." + (unless (or (and avandu--session-id (avandu-logged-in-p)) + (avandu-login)) + (avandu--clear-data) + (error "Could not log in to tt-rss"))) + +(defun avandu--clean-text (text) + "Go through TEXT and remove any trailing and leading whitespace +from it, then look for any HTML entities and either replace them +with their char value or with the value in +`avandu-entity-replacement-alist'." + (with-temp-buffer + (insert text) + (while (re-search-forward + "\\`[[:space:][:cntrl:]]+\\|[[:space:][:cntrl:]]+\\'" nil t) + (replace-match "")) + + (goto-char (point-min)) + (while (search-forward "&" nil t) + (let ((pos (point))) + (save-excursion + (when (search-forward ";" nil t) + (let* ((sstring (buffer-substring pos (1- (point)))) + (char-code + (if (= (char-after pos) ?#) + (unless (string-match-p "[^[:digit:]]" + (substring sstring 1)) + (string-to-number (substring sstring 1))) + (assoc sstring avandu-entity-replacement-alist)))) + (when char-code + (delete-region (1- pos) (point)) + (insert-char (if (consp char-code) + (cdr char-code) + char-code) 1))))))) + + (setq text (buffer-string))) + text) (defun avandu--clear-data () "Clean up login data. This makes for a clean slate next time." (setq avandu-user nil - avandu-password nil - avandu--session-id nil)) + avandu--session-id nil) + (clear-string avandu-password)) + +(defun avandu--get-session-id (results) + "Get the session id from RESULTS." + (cdr (assq 'session_id (assq 'content results)))) (defun avandu--get-status-id (results) "Get the status id from RESULTS." (cdr (assq 'status results))) -(defun avandu--get-session-id (results) - "Get the session id from RESULTS." - (cdr (assq 'session_id (assq 'content results)))) +(defun avandu--insert-article-excerpt (excerpt) + "Insert the excerpt of an article." + (let ((start-pos (point)) + end-pos + (text (replace-regexp-in-string + "[ \t\n]*$" "" (avandu--clean-text excerpt)))) + (unless (or (not text) (string= text "")) + (insert + (propertize + text + 'face 'avandu-overview-excerpt)) + (indent-region start-pos (point) tab-width) + (fill-region start-pos (point)) + (insert-char ?\n 1)))) -(defun avandu--check-login () - "Check to see if we're (still) logged in, try to login -otherwise. Signals an error if we're not logged in *and* login -was unsuccesful." - (unless (or (and avandu--session-id (avandu-logged-in-p)) - (avandu-login)) - (avandu--clear-data) - (error "Could not log in to tt-rss"))) +(defun avandu--insert-article-title (id link title) + "Insert a button with the label TITLE and store ID and LINK in +the article-id and link properties, respectively." + (insert-button + (replace-regexp-in-string "^[ \n\t]*\\|[ \n\t]*$" "" title) + 'face 'avandu-overview-unread-article + 'article-id id + 'link link + 'keymap avandu-article-button-map + 'action #'(lambda (button) + (message "%s" (button-get button 'link)))) + (insert-char ?\n 1)) -(defmacro avandu-getset (var prompt &optional passwdp) - "Ask the user for, and then save, VAR with PROMPT. Use -`read-passwd' if PASSWDP and `read-string' otherwise." - `(or ,var (setq ,var (,(if passwdp 'read-passwd 'read-string) - ,prompt)))) +(defun avandu--insert-feed-title (id title) + "Insert a button with the label TITLE and store ID in the +feed-id property." + (unless (eq (point) (point-min)) (insert-char ?\n 1)) + (insert-button + (replace-regexp-in-string "^[ \n\t]*\\|[ \n\t]*$" "" title) + 'face 'avandu-overview-feed + 'feed-id id + 'keymap avandu-feed-button-map + 'action #'(lambda (button) + (message "%s" (button-label button)))) + (insert-char ?\n 2)) -(defun avandu-send-command (data) +(defun avandu--send-command (data) "Send a command with parameters DATA to tt-rss. The current session-id is added to the request and then DATA is passed on to `json-encode'. @@ -185,7 +278,7 @@ session-id is added to the request and then DATA is passed on to DATA should be an association list with at least an OP value. For example: - (avandu-send-command '((op . \"isLoggedIn\"))) + (avandu--send-command '((op . \"isLoggedIn\"))) This function returns the result of `json-read' passed over the returned json." @@ -205,6 +298,14 @@ returned json." (kill-buffer buffer) result)) +;; Commands +(defun avandu-browse-article () + "Browse the current button's article url." + (interactive) + (let ((button (button-at (point)))) + (browse-url (button-get button 'link)) + (avandu-mark-article-read button))) + (defun avandu-feed-catchup () "Send a request to tt-rss to \"Catch up\" with a feed. This means that all the (unread) articles in a feed will be marked @@ -213,37 +314,14 @@ returned json." (interactive) (let* ((button (button-at (point))) (id (button-get button 'feed-id))) - (avandu-send-command `((op . "catchupFeed") + (avandu--send-command `((op . "catchupFeed") (feed_id . ,id)))) (revert-buffer)) -(defun avandu-mark-article-read (&optional button) - "Send a request to tt-rss to mark an article as read. - -BUTTON, if given, should be a button widget, as created by -`button-insert' and such, which contains FEED-ID. If BUTTON is -nil, it will be assumed that `point' is currently within the -bounds of a button." - (interactive) - (let* ((button (or button (button-at (point)))) - (id (button-get button 'article-id))) - (avandu-send-command `((op . "updateArticle") - (article_ids . ,id) - (mode . 0) - (field . 2))) - (button-put button 'face 'avandu-overview-read-article)) - (avandu-next-article)) - -(defun avandu-logout () - "Logout from Tiny Tiny RSS." - (interactive) - (avandu-send-command '((op . "logout"))) - (avandu--clear-data)) - (defun avandu-logged-in-p () "Send a request to tt-rss to see if we're (still) logged in. This function returns t if we are, or nil if we're not." - (let* ((response (avandu-send-command '((op . "isLoggedIn")))) + (let* ((response (avandu--send-command '((op . "isLoggedIn")))) (result (cdr (assq 'status (assq 'content response))))) (if (eq result :json-false) nil @@ -255,7 +333,7 @@ in. This function returns t if we are, or nil if we're not." and saved in memory. This function returns t on succes, nil otherwise." (interactive) - (let ((result (avandu-send-command + (let ((result (avandu--send-command `((op . "login") (user . ,(avandu-getset avandu-user "Username: ")) (password @@ -266,139 +344,69 @@ otherwise." t) nil))) +(defun avandu-logout () + "Logout from Tiny Tiny RSS." + (interactive) + (avandu--send-command '((op . "logout"))) + (avandu--clear-data)) + +(defun avandu-mark-article-read (&optional button) + "Send a request to tt-rss to mark an article as read. + +BUTTON, if given, should be a button widget, as created by +`button-insert' and such, which contains FEED-ID. If BUTTON is +nil, it will be assumed that `point' is currently within the +bounds of a button." + (interactive) + (let* ((button (or button (button-at (point)))) + (id (button-get button 'article-id))) + (avandu--send-command `((op . "updateArticle") + (article_ids . ,id) + (mode . 0) + (field . 2))) + (button-put button 'face 'avandu-overview-read-article)) + (avandu-next-article)) + (defun avandu-new-articles-count () "Send a request to tt-rss for the total number of unread feeds." (interactive) (avandu--check-login) - (let ((result (avandu-send-command '((op . "getUnread"))))) + (let ((result (avandu--send-command '((op . "getUnread"))))) (message (cdr (assq 'unread (assq 'content result)))))) -(defmacro avandu--next-button-of-type (direction type) - "Go DIRECTION and find the next button of a TYPE." - (let ((prop (case type - (feed 'feed-id) - (article 'article-id) - (t (error "Invalid type")))) - (next-point-function (case direction - (forward 'point-min) - (backward 'point-max) - (t (error "Invalid direction")))) - (next-button-function (case direction - (forward 'next-button) - (backward 'previous-button) - (t (error "Invalid direction"))))) - `(let ((pos (point)) - found-value) - (while (not found-value) - (let ((button (,next-button-function pos))) - (unless button - (setq pos (,next-point-function) - button (or (button-at pos) - (,next-button-function pos)))) - (setq found-value (button-get button ',prop) - pos (overlay-start button)))) - (goto-char pos)))) - (defun avandu-next-article () "Search forward for the next article." (interactive) (avandu--next-button-of-type forward article)) -(defun avandu-previous-article () - "Go backward and find the next article." - (interactive) - (avandu--next-button-of-type backward article)) - (defun avandu-next-feed () "Go forward and find the next feed." (interactive) (avandu--next-button-of-type forward feed)) +(defun avandu-previous-article () + "Go backward and find the next article." + (interactive) + (avandu--next-button-of-type backward article)) + (defun avandu-previous-feed () "Go backward and find the next feed." (interactive) (avandu--next-button-of-type backward feed)) -(defun avandu--insert-feed-title (id title) - "Insert a button with the label TITLE and store ID in the -feed-id property." - (unless (eq (point) (point-min)) (insert-char ?\n 1)) - (insert-button - (replace-regexp-in-string "^[ \n\t]*\\|[ \n\t]*$" "" title) - 'face 'avandu-overview-feed - 'feed-id id - 'keymap avandu-feed-button-map - 'action #'(lambda (button) - (message "%s" (button-label button)))) - (insert-char ?\n 2)) - -(defun avandu-browse-article () - "Browse the current button's article url." - (interactive) - (let ((button (button-at (point)))) - (browse-url (button-get button 'link)) - (avandu-mark-article-read button))) - -(defun avandu--insert-article-title (id link title) - "Insert a button with the label TITLE and store ID and LINK in -the article-id and link properties, respectively." - (insert-button - (replace-regexp-in-string "^[ \n\t]*\\|[ \n\t]*$" "" title) - 'face 'avandu-overview-unread-article - 'article-id id - 'link link - 'keymap avandu-article-button-map - 'action #'(lambda (button) - (message "%s" (button-get button 'link)))) - (insert-char ?\n 1)) - -(defun avandu-clean-text (text) - "Go through TEXT and remove any trailing and leading whitespace -from it, then look for any HTML entities and either replace them -with their char value or with the value in -`avandu-entity-replacement-alist'." - (with-temp-buffer - (insert text) - (while (re-search-forward - "\\`[[:space:][:cntrl:]]+\\|[[:space:][:cntrl:]]+\\'" nil t) - (replace-match "")) - - (goto-char (point-min)) - (while (search-forward "&" nil t) - (let ((pos (point))) - (save-excursion - (when (search-forward ";" nil t) - (let* ((sstring (buffer-substring pos (1- (point)))) - (char-code - (if (= (char-after pos) ?#) - (unless (string-match-p "[^[:digit:]]" - (substring sstring 1)) - (string-to-number (substring sstring 1))) - (assoc sstring avandu-entity-replacement-alist)))) - (when char-code - (delete-region (1- pos) (point)) - (insert-char (if (consp char-code) - (cdr char-code) - char-code) 1))))))) +;; Overview +(define-derived-mode avandu-overview-mode special-mode "Avandu:Overview" + "Major mode fo the avandu overview screen. - (setq text (buffer-string))) - text) +This screen shows the articles categorized by feed as a list. It +doesn't sort the list, so you'll have to set that up in tt-rss. -(defun avandu--insert-article-excerpt (excerpt) - "Insert the excerpt of an article." - (let ((start-pos (point)) - end-pos - (text (replace-regexp-in-string - "[ \t\n]*$" "" (avandu-clean-text excerpt)))) - (unless (or (not text) (string= text "")) - (insert - (propertize - text - 'face 'avandu-overview-excerpt)) - (indent-region start-pos (point) tab-width) - (fill-region start-pos (point)) - (insert-char ?\n 1)))) +\\{avandu-overview-map} +\\" + (use-local-map avandu-overview-map) + (set (make-local-variable 'revert-buffer-function) + #'(lambda (ignore-auto noconfirm) (avandu-list)))) ;;;###autoload (defun avandu-list () @@ -407,10 +415,10 @@ by feed." (interactive) (avandu--check-login) (let ((buffer (get-buffer-create "*avandu-overview*")) - (result (avandu-send-command '((op . "getHeadlines") - (feed_id . -4) - (view_mode . "unread") - (show_excerpt . t)))) + (result (avandu--send-command '((op . "getHeadlines") + (feed_id . -4) + (view_mode . "unread") + (show_excerpt . t)))) feed-id) (with-current-buffer buffer (setq buffer-read-only nil) @@ -449,3 +457,23 @@ by feed." ;; (marked . :json-false) ;; (unread . t) ;; (id . 109)) + +;; (get-api-level) +;; (get-version) +;; (login user password) +;; (logout) +;; (is-logged-in) +;; (get-unread) +;; (get-counters output-mode) +;; (get-feeds category-id unread-only limit offset) +;; (get-categories unread-only) +;; (get-headlines feed-id limit skip filter categoryp show-excerpt show-content view-mode include-attachments since-id search search-mode match-on) +;; (update-article article-ids mode field data) +;; (get-article article-id) +;; (get-config icons-dir icons-url daemon-is-running num-feeds) +;; (update-feed feed-id) +;; (get-pref pref-name) +;; (catchup-feed feed-id categoryp) +;; (get-counters output-mode) +;; (get-labels article-id) +;; (set-article-label article-ids label-id assingp) -- cgit v1.2.3-54-g00ecf