diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1d64237..df4987b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest] emacs_version: [25.1, 26.3] - ruby-version: [2.6] + ruby_version: [2.6] include: - emacs_version: 24.1 lint_ignore: 1 @@ -26,13 +26,17 @@ jobs: steps: - uses: actions/checkout@v2 + - uses: actions/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby_version }} + - uses: purcell/setup-emacs@master with: version: ${{ matrix.emacs_version }} - uses: dickmao/setup-paths@master with: - paths: local/bin:local/cask/bin:.rbenv/bin + paths: local/bin:local/cask/bin - uses: actions/cache@v1 if: startsWith(runner.os, 'Linux') @@ -46,13 +50,6 @@ jobs: path: ~/Library/Caches/rubocop_cache key: ${{ runner.os }}-rubocop - - uses: actions/cache@v1 - with: - path: ~/.rbenv - key: ${{ runner.os }}-rbenv-${{ hashFiles('**/.ruby-version') }} - restore-keys: | - {{ runner.os }}-rbenv- - - uses: actions/cache@v1 with: path: ~/local @@ -68,24 +65,16 @@ jobs: path: ~/.cask key: cask-000 - - name: macos-rbenv - if: startsWith(runner.os, 'macOS') - run: brew list rbenv &>/dev/null || HOMEBREW_NO_AUTO_UPDATE=1 brew install rbenv - - - name: linux-rbenv - if: startsWith(runner.os, 'Linux') - uses: | - masa-iwasaki/setup-rbenv@v1 - - - name: ruby - run: | - eval "eval $(rbenv init - --no-rehash)" - rbenv install -s ${{ matrix.ruby-version }} + - uses: actions/cache@v1 + with: + path: nndiscourse/vendor/bundle + key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems- - name: bundler run: | - eval "eval $(rbenv init - --no-rehash)" - gem install bundler + gem install --user-install bundler - name: apt-get if: startsWith(runner.os, 'Linux') @@ -113,5 +102,4 @@ jobs: - name: test run: | - eval "eval $(rbenv init - --no-rehash)" make test diff --git a/Makefile b/Makefile index 977818d..aa46a91 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ export CASK := $(shell which cask) ifeq ($(CASK),) $(error Please install CASK at https://cask.readthedocs.io/en/latest/guide/installation.html) endif -CASK_DIR := $(shell EMACS=$(EMACS) cask package-directory || exit 1) +CASK_DIR := $(shell $(CASK) package-directory || exit 1) SRC = $(shell $(CASK) files) PKBUILD = 2.3 VERSION = $(shell $(CASK) version) @@ -17,6 +17,12 @@ ELCTESTS = $(TESTSSRC:.el=.elc) autoloads: $(EMACS) -Q --batch --eval "(package-initialize)" --eval "(package-generate-autoloads \"nndiscourse\" \".\")" +README.rst: README.in.rst nndiscourse.el + grep ';;' nndiscourse.el \ + | awk '/;;;\s*Commentary/{within=1;next}/;;;\s*/{within=0}within' \ + | sed -e 's/^\s*;;*\s*//g' \ + | tools/readme-sed.sh "COMMENTARY" README.in.rst > README.rst + .PHONY: clean clean: $(CASK) clean-elc diff --git a/README.in.rst b/README.in.rst new file mode 100644 index 0000000..dfbd271 --- /dev/null +++ b/README.in.rst @@ -0,0 +1,67 @@ +|build-status| + +.. COMMENTARY (see Makefile) + +.. |build-status| + image:: https://github.com/dickmao/nndiscourse/workflows/CI/badge.svg + :target: https://github.com/dickmao/nndiscourse/actions + :alt: Build Status +.. |melpa-dev| + image:: http://melpa.milkbox.net/packages/nndiscourse-badge.svg + :target: http://melpa.milkbox.net/#/nndiscourse + :alt: MELPA development version + +.. image:: screenshot.png +.. |--| unicode:: U+2013 .. en dash +.. |---| unicode:: U+2014 .. em dash, trimming surrounding whitespace + :trim: + +Install +======= +As described in `Getting started`_, ensure melpa's whereabouts in ``init.el`` or ``.emacs``:: + + (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/")) + +Then + +:: + + M-x package-refresh-contents RET + M-x package-install RET nndiscourse RET + +Alternatively, directly clone this repo and ``make install``. + +Also see Troubleshooting_. + +Usage +===== +In your ``.emacs`` or ``init.el``, use ONE of the following: + +:: + + ;; Applies to first-time Gnus users + (custom-set-variables '(gnus-select-method (quote (nndiscourse "")))) + +or, if you're an existing Gnus user, + +:: + + ;; Applies to existing Gnus users + (add-to-list 'gnus-secondary-select-methods '(nndiscourse "")) + +Then ``M-x gnus``. + +Select a topic category via ``RET``. Rapidly catch yourself up via ``N`` and ``P``. Instantly catch-up with ``c``. + +From the ``*Group*`` buffer, press ``g`` to refresh all categories. ``M-g`` on a particular category to refresh individually. + +From the summary buffer, ``/o`` redisplays posts already read. ``x`` undisplays them. + +Gnus beginners may find the interface bewildering. In particular, categories with no unread posts do not display. Use ``L`` to bring them out of hiding. + +Troubleshooting +=============== +Clone this repo. Then install Cask_. Then try ``make test-run-interactive``. + +.. _Cask: https://cask.readthedocs.io/en/latest/guide/installation.html +.. _Getting started: http://melpa.org/#/getting-started diff --git a/README.rst b/README.rst index fd6fa9c..1df1d15 100644 --- a/README.rst +++ b/README.rst @@ -1 +1,67 @@ -Read ``emacs-china.org`` in Gnus. +|build-status| + +A Gnus backend for Discourse. + +.. |build-status| + image:: https://github.com/dickmao/nndiscourse/workflows/CI/badge.svg + :target: https://github.com/dickmao/nndiscourse/actions + :alt: Build Status +.. |melpa-dev| + image:: http://melpa.milkbox.net/packages/nndiscourse-badge.svg + :target: http://melpa.milkbox.net/#/nndiscourse + :alt: MELPA development version + +.. image:: screenshot.png +.. |--| unicode:: U+2013 .. en dash +.. |---| unicode:: U+2014 .. em dash, trimming surrounding whitespace + :trim: + +Install +======= +As described in `Getting started`_, ensure melpa's whereabouts in ``init.el`` or ``.emacs``:: + + (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/")) + +Then + +:: + + M-x package-refresh-contents RET + M-x package-install RET nndiscourse RET + +Alternatively, directly clone this repo and ``make install``. + +Also see Troubleshooting_. + +Usage +===== +In your ``.emacs`` or ``init.el``, use ONE of the following: + +:: + + ;; Applies to first-time Gnus users + (custom-set-variables '(gnus-select-method (quote (nndiscourse "")))) + +or, if you're an existing Gnus user, + +:: + + ;; Applies to existing Gnus users + (add-to-list 'gnus-secondary-select-methods '(nndiscourse "")) + +Then ``M-x gnus``. + +Select a topic category via ``RET``. Rapidly catch yourself up via ``N`` and ``P``. Instantly catch-up with ``c``. + +From the ``*Group*`` buffer, press ``g`` to refresh all categories. ``M-g`` on a particular category to refresh individually. + +From the summary buffer, ``/o`` redisplays posts already read. ``x`` undisplays them. + +Gnus beginners may find the interface bewildering. In particular, categories with no unread posts do not display. Use ``L`` to bring them out of hiding. + +Troubleshooting +=============== +Clone this repo. Then install Cask_. Then try ``make test-run-interactive``. + +.. _Cask: https://cask.readthedocs.io/en/latest/guide/installation.html +.. _Getting started: http://melpa.org/#/getting-started diff --git a/features/rpc.feature b/features/rpc.feature index 7a46ce3..4a3e5e9 100644 --- a/features/rpc.feature +++ b/features/rpc.feature @@ -8,7 +8,7 @@ Scenario: install And I go to word "david" And I press "RET" And I switch to buffer "*Article nndiscourse+meta.discourse.org:bug*" - Then I should see ":eyes:" + Then I should see "Recent Changes" Then prospective unreads for "nndiscourse+meta.discourse.org:bug" is 1 And I switch to buffer "*Summary nndiscourse+meta.discourse.org:bug*" And I press "q" diff --git a/nndiscourse.el b/nndiscourse.el index 2e0a8a0..2cb0e6e 100644 --- a/nndiscourse.el +++ b/nndiscourse.el @@ -262,6 +262,8 @@ Starting in emacs-src commit c1b63af, Gnus moved from obarrays to normal hashtab (make-mutex "nndiscourse--mutex-rpc-request")) "Only one jsonrpc output buffer, so avoid two requests using at the same time.") +(declare-function set-process-thread "process" t t) ;; emacs-25 + (defun nndiscourse-rpc-request (server method &rest args) "Make jsonrpc call to SERVER invoking METHOD ARGS. @@ -277,7 +279,7 @@ reinstantiated with every call. Return response of METHOD ARGS of type `json-object-type' or nil if failure." (when (and (nndiscourse-good-server server) (nndiscourse-server-opened server)) - (condition-case-unless-debug err + (condition-case err (let* ((port (nndiscourse-proc-info-port (cdr (assoc server nndiscourse-processes)))) (connection (json-rpc-connect nndiscourse-localhost port))) @@ -400,6 +402,47 @@ I am counting on `gnus-check-server` in `gnus-read-active-file-1' in (unless original-global-rbenv-mode (global-rbenv-mode -1)))))) +(defun nndiscourse-alist-get (key alist &optional default remove testfn) + "Replicated library function for emacs-25. + +Same argument meanings for KEY ALIST DEFAULT REMOVE and TESTFN." + (ignore remove) + (let ((x (if (not testfn) + (assq key alist) + (assoc key alist)))) + (if x (cdr x) default))) + +(gv-define-expander nndiscourse-alist-get + (lambda (do key alist &optional default remove testfn) + (macroexp-let2 macroexp-copyable-p k key + (gv-letplace (getter setter) alist + (macroexp-let2 nil p `(if (and ,testfn (not (eq ,testfn 'eq))) + (assoc ,k ,getter) + (assq ,k ,getter)) + (funcall do (if (null default) `(cdr ,p) + `(if ,p (cdr ,p) ,default)) + (lambda (v) + (macroexp-let2 nil v v + (let ((set-exp + `(if ,p (setcdr ,p ,v) + ,(funcall setter + `(cons (setq ,p (cons ,k ,v)) + ,getter))))) + `(progn + ,(cond + ((null remove) set-exp) + ((or (eql v default) + (and (eq (car-safe v) 'quote) + (eq (car-safe default) 'quote) + (eql (cadr v) (cadr default)))) + `(if ,p ,(funcall setter `(delq ,p ,getter)))) + (t + `(cond + ((not (eql ,default ,v)) ,set-exp) + (,p ,(funcall setter + `(delq ,p ,getter)))))) + ,v)))))))))) + (defun nndiscourse-register-process (port proc) "Register PORT and PROC with a server-name-qua-url. Return PROC if success, nil otherwise." @@ -409,7 +452,8 @@ Return PROC if success, nil otherwise." (prog1 proc (gnus-message 5 "nndiscourse-register-process: registering %s" (process-name proc)) - (setf (alist-get (process-name proc) nndiscourse-processes nil nil #'equal) + (setf (nndiscourse-alist-get (process-name proc) nndiscourse-processes + nil nil #'equal) (make-nndiscourse-proc-info :port port :process proc))) (prog1 nil (gnus-message 3 "`nndiscourse-register-process': dead process %s" @@ -418,12 +462,12 @@ Return PROC if success, nil otherwise." (defun nndiscourse-deregister-process (server) "Disavow any knowledge of SERVER's process." - (aif (alist-get server nndiscourse-processes nil nil #'equal) + (aif (nndiscourse-alist-get server nndiscourse-processes nil nil #'equal) (let ((proc (nndiscourse-proc-info-process it))) (gnus-message 5 "`nndiscourse-deregister-process': deregistering %s %s pid=%s" server (process-name proc) (process-id proc)) (delete-process proc))) - (setf (alist-get server nndiscourse-processes nil nil #'equal) nil)) + (setf (nndiscourse-alist-get server nndiscourse-processes nil nil #'equal) nil)) (deffoo nndiscourse-close-server (&optional server _defs) "Patterning after nnimap.el." @@ -531,7 +575,6 @@ Originally written by Paul Issartel." (nndiscourse--with-group server group (gnus-message 5 "nndiscourse-request-group-scan: scanning %s..." group) (nndiscourse-request-scan nil server) - (gnus-activate-group gnus-newsgroup-name t nil (gnus-info-method info)) (gnus-get-unread-articles-in-group (or info (gnus-get-info gnus-newsgroup-name)) (gnus-active (gnus-info-group info))) @@ -579,7 +622,8 @@ Originally written by Paul Issartel." (defun nndiscourse-get-categories (server) "Query SERVER /categories.json." (seq-filter (lambda (x) (eq json-false (plist-get x :read_restricted))) - (funcall #'nndiscourse-rpc-request server "categories"))) + (let ((cats (funcall #'nndiscourse-rpc-request server "categories"))) + (if (seqp cats) cats nil)))) (cl-defun nndiscourse-get-topics (server slug &key (page 0)) "Query SERVER /c/SLUG/l/latest.json, optionally for PAGE." @@ -589,7 +633,9 @@ Originally written by Paul Issartel." (cl-defun nndiscourse-get-posts (server &key (before 0)) "Query SERVER /posts.json for posts before BEFORE." - (plist-get (funcall #'nndiscourse-rpc-request server "posts" :before before) :latest_posts)) + (plist-get (let ((result (funcall #'nndiscourse-rpc-request server + "posts" :before before))) + (if (listp result) result nil)) :latest_posts)) (defun nndiscourse--number-to-header (server group topic-id post-number) "O(n) search for SERVER GROUP TOPIC-ID POST-NUMBER in headers." @@ -603,16 +649,23 @@ Originally written by Paul Issartel." (= post-number* (plist-get plst :post_number)))))))) (elt headers found))) +(defsubst nndiscourse-hash-count (table-or-obarray) + "Return number items in TABLE-OR-OBARRAY." + (let ((result 0)) + (nndiscourse--maphash (lambda (&rest _args) (cl-incf result)) table-or-obarray) + result)) + (defun nndiscourse--incoming (server) "Drink from the SERVER firehose." (interactive) (setq nndiscourse--debug-request-posts nil) - (unless (nndiscourse--maphash #'cons nndiscourse--categories-hashtb) + (when (zerop (nndiscourse-hash-count nndiscourse--categories-hashtb)) (nndiscourse-request-list server)) (cl-loop with new-posts for page-bottom = 1 then (plist-get (elt posts (1- (length posts))) :id) for posts = (nndiscourse-get-posts server :before (1- page-bottom)) + until (null posts) do (unless nndiscourse--last-id (setq nndiscourse--last-id (1- (plist-get (elt posts (1- (length posts))) :id)))) @@ -662,7 +715,7 @@ Originally written by Paul Issartel." (nconc (nndiscourse-get-headers server group) (list plst))))) (gnus-message 5 (concat "nndiscourse--incoming: " - (format "last-id: %d, " nndiscourse--last-id) + (format "last-id: %s, " nndiscourse--last-id) (let ((result "")) (nndiscourse--maphash (lambda (key value) diff --git a/tools/install-evm.sh b/tools/install-evm.sh deleted file mode 100644 index 3d6c737..0000000 --- a/tools/install-evm.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -# Install evm for Travis CI -# or if already installed, then check for updates -# Author: gonewest818 https://github.com/clojure-emacs/cider/pull/2139 -set -x - -WORKDIR=${HOME}/local -EVMDIR=$WORKDIR/evm - -. tools/retry.sh - -if [ -d $EVMDIR ] -then - cd $EVMDIR - git pull origin master -else - git clone https://github.com/rejeep/evm.git $EVMDIR -fi diff --git a/tools/install-virtualenv.sh b/tools/install-virtualenv.sh deleted file mode 100644 index 76e86df..0000000 --- a/tools/install-virtualenv.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -# Create virtualenvs for python{2,3} for Travis CI on OSX - -set -x - -WORKDIR=${HOME}/local - -. tools/retry.sh - -if [ "x$TRAVIS_OS_NAME" = "xosx" ]; then - brew list pyenv-virtualenv || HOMEBREW_NO_AUTO_UPDATE=1 brew install pyenv-virtualenv - - case "${TOXENV}" in - py27) - pyenv install -s 2.7.13 - pyenv virtualenv -f 2.7.13 py27 - ;; - py35) - pyenv install -s 3.5.2 - pyenv virtualenv -f 3.5.2 py35 - ;; - esac -fi