aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bot/sx-bot.el31
-rwxr-xr-xbot/sx-bot.sh20
-rw-r--r--sx-auth.el2
-rw-r--r--sx-button.el6
-rw-r--r--sx-filter.el4
-rw-r--r--sx-interaction.el14
-rw-r--r--sx-question-mode.el1
-rw-r--r--sx-question-print.el129
-rw-r--r--sx-request.el30
-rw-r--r--sx-search.el4
-rw-r--r--sx-tab.el55
11 files changed, 169 insertions, 127 deletions
diff --git a/bot/sx-bot.el b/bot/sx-bot.el
index b32a69c..1a8bceb 100644
--- a/bot/sx-bot.el
+++ b/bot/sx-bot.el
@@ -48,12 +48,11 @@ File is savedd in `sx-bot-out-dir'."
(with-temp-file file-name
(let* (print-length
(repr (prin1-to-string
- (sort (cdr data)
- #'string-lessp))))
+ (sort (cdr data) #'string-lessp))))
(insert repr "\n")
(goto-char (point-min))
(while (search-forward "\" \"" nil t)
- (replace-match "\"\n \"" nil t))))
+ (replace-match "\"\n\"" nil t))))
(message "Wrote %S" file-name)
file-name))
@@ -61,19 +60,27 @@ File is savedd in `sx-bot-out-dir'."
"Get a list of all tags of all sites and save to disk."
(make-directory sx-bot-out-dir t)
(let* ((url-show-status nil)
- (site-tokens (sx-site-get-api-tokens))
+ (site-tokens (mapcar (lambda (site) ;Fix bad sites
+ (pcase site
+ ("metamunitybuilding" "meta.communitybuilding")
+ ((or "gamification" "meta.gamification") nil)
+ (_ site)))
+ (sx-site-get-api-tokens)))
(number-of-sites (length site-tokens))
(current-site-number 0)
- (sx-request-all-items-delay 0.25))
+ (sx-request-remaining-api-requests-message-threshold 4000)
+ (sx-request-all-items-delay 0))
(mapcar
(lambda (site)
- (message "[%d/%d] Working on %S"
- (cl-incf current-site-number)
- number-of-sites
- site)
- (sx-bot-write-to-file
- (cons (concat site ".el")
- (sx-tag--get-all site))))
+ (when site
+ (message "[%d/%d] Working on %S"
+ (cl-incf current-site-number)
+ number-of-sites
+ site)
+ (with-demoted-errors (concat "Failed to get " site ": %S")
+ (sx-bot-write-to-file
+ (cons (concat site ".el")
+ (sx-tag--get-all site))))))
site-tokens)))
diff --git a/bot/sx-bot.sh b/bot/sx-bot.sh
index 22c7284..32f6123 100755
--- a/bot/sx-bot.sh
+++ b/bot/sx-bot.sh
@@ -1,6 +1,9 @@
-#!/usr/bin/bash
+#!/usr/bin/env bash
+
+[[ -z "$EMACS" ]] && EMACS="emacs";
DESTINATION_BRANCH=data
+CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
function notify-done {
local title
@@ -20,17 +23,20 @@ function notify-done {
}
function generate-tags {
- emacs -Q --batch \
- -L "./" -L "./bot/" -l sx-bot \
- -f sx-bot-fetch-and-write-tags
- ret = $?
+ $EMACS -Q --batch \
+ -L "./" -L "./bot/" -l sx-bot \
+ -f sx-bot-fetch-and-write-tags
+ ret=$?
notify-done
return ${ret}
}
-git branch ${DESTINATION_BRANCH} &&
+git checkout ${DESTINATION_BRANCH} &&
git pull &&
generate-tags &&
git stage data/ &&
git commit -m "Update tag data" &&
- echo 'Ready for "git push"'
+ git push &&
+ echo 'Bot finished.'
+
+git checkout ${CURRENT_BRANCH}
diff --git a/sx-auth.el b/sx-auth.el
index cba310d..5fc30ca 100644
--- a/sx-auth.el
+++ b/sx-auth.el
@@ -35,7 +35,7 @@
(defconst sx-auth-root
"https://stackexchange.com/oauth/dialog")
(defconst sx-auth-redirect-uri
- "http://vermiculus.github.io/sx.el/auth/auth.htm")
+ "http://seanallred.com/sx.el/auth/auth.htm")
(defconst sx-auth-client-id
"3291")
(defvar sx-auth-access-token
diff --git a/sx-button.el b/sx-button.el
index 05ebadb..a4fcb76 100644
--- a/sx-button.el
+++ b/sx-button.el
@@ -45,6 +45,12 @@
(require 'sx)
(require 'sx-question)
+(declare-function sx-accept "sx-interaction")
+(declare-function sx-answer "sx-interaction")
+(declare-function sx-comment "sx-interaction")
+(declare-function sx-open-link "sx-interaction")
+(declare-function sx-question-mode-hide-show-section "sx-question-mode")
+
;;; Face
(defface sx-custom-button
diff --git a/sx-filter.el b/sx-filter.el
index 5848e34..31e0470 100644
--- a/sx-filter.el
+++ b/sx-filter.el
@@ -36,7 +36,7 @@
;;; Customizations
(defvar sx--filter-alist
- (sx-cache-get 'filter)
+ nil
"An alist of known filters. See `sx-filter-compile'.
Structure:
@@ -96,6 +96,8 @@ be returned. Otherwise, compile INCLUDE, EXCLUDE, and BASE into
a filter with `sx-filter-compile' and push the association onto
`sx--filter-alist'. Re-cache the alist with `sx-cache-set' and
return the compiled filter."
+ (unless sx--filter-alist
+ (setq sx--filter-alist (sx-cache-get 'filter)))
(or (cdr (assoc (list include exclude base) sx--filter-alist))
(let ((filter (sx-filter-compile include exclude base)))
(when filter
diff --git a/sx-interaction.el b/sx-interaction.el
index ec11710..5d1039e 100644
--- a/sx-interaction.el
+++ b/sx-interaction.el
@@ -215,9 +215,21 @@ Element can be a question, answer, or comment."
(sx-display-question
(sx-question-get-question .site_par .id) 'focus))
(t (error "Don't know how to open this link, please file a bug report: %s"
- link)
+ link)
nil))))))
+;;;###autoload
+(defun sx-org-get-link ()
+ "Add a link to this post to Org's memory."
+ (when (memq major-mode '(sx-question-mode sx-question-list-mode))
+ (sx-assoc-let (sx--data-here)
+ (when .link
+ (org-store-link-props :type 'http
+ :link .link
+ :description .title)))))
+(eval-after-load "org"
+ '(add-to-list 'org-store-link-functions #'sx-org-get-link))
+
;;; Displaying
(defun sx-display (&optional data)
diff --git a/sx-question-mode.el b/sx-question-mode.el
index 9276381..4263f6e 100644
--- a/sx-question-mode.el
+++ b/sx-question-mode.el
@@ -243,6 +243,7 @@ on the current buffer use
\\{sx-question-mode}"
(setq header-line-format sx-question-mode--header-line)
(setq mode-line-format sx-question-mode--mode-line)
+ (buffer-disable-undo (current-buffer))
(set (make-local-variable 'nobreak-char-display) nil)
;; Determine how to close this window.
(unless (window-parameter nil 'quit-restore)
diff --git a/sx-question-print.el b/sx-question-print.el
index 5b5ef4f..6599532 100644
--- a/sx-question-print.el
+++ b/sx-question-print.el
@@ -185,10 +185,10 @@ replaced with the comment."
("Newer" . sx-answer-newer-p)
("More active" . sx-answer-more-active-p))))
(append (mapcar (lambda (x) (cons (concat (car x) " first") (cdr x)))
- methods)
+ methods)
(mapcar (lambda (x) (cons (concat (car x) " last")
- (sx--invert-predicate (cdr x))))
- methods))))
+ (sx--invert-predicate (cdr x))))
+ methods))))
(defcustom sx-question-mode-answer-sort-function
#'sx-answer-higher-score-p
@@ -196,12 +196,10 @@ replaced with the comment."
:type
(cons 'choice
(mapcar (lambda (x) `(const :tag ,(car x) ,(cdr x)))
- sx-question-mode--sort-methods))
+ sx-question-mode--sort-methods))
:group 'sx-question-mode)
-(defcustom sx-question-mode-use-images
- (eval-when-compile
- (image-type-available-p 'imagemagick))
+(defcustom sx-question-mode-use-images (image-type-available-p 'imagemagick)
"Non-nil if SX should download and display images.
By default, this is `t' if the `imagemagick' image type is
available (checked with `image-type-available-p'). If this image
@@ -237,9 +235,9 @@ QUESTION must be a data structure returned by `json-read'."
(sx-question-mode--print-close-reason .closed_reason .closed_date .closed_details))
(sx-question-mode--print-section question)
(mapc #'sx-question-mode--print-section
- (cl-remove-if
- #'sx--deleted-p
- (cl-sort .answers sx-question-mode-answer-sort-function))))
+ (cl-remove-if
+ #'sx--deleted-p
+ (cl-sort .answers sx-question-mode-answer-sort-function))))
(insert "\n\n ")
(insert-text-button "Write an Answer" :type 'sx-button-answer)
;; Go up
@@ -255,9 +253,9 @@ DETAILS, when given is an alist further describing the close."
(let-alist details
(insert "\n "
(propertize (format " %s as %s, %s ago. "
- (if .on_hold "Put on hold" "Closed")
- reason
- (sx-time-since date))
+ (if .on_hold "Put on hold" "Closed")
+ reason
+ (sx-time-since date))
'face 'sx-question-mode-closed)
"\n")
(when .description
@@ -321,8 +319,8 @@ DATA can represent a question or an answer."
(sx-time-seconds-to-date .creation_date)
(when .last_edit_date
(format sx-question-mode-last-edit-format
- (sx-time-since .last_edit_date)
- (sx-user--format "%d" .last_editor))))
+ (sx-time-since .last_edit_date)
+ (sx-user--format "%d" .last_editor))))
'sx-question-mode-date)
;; Score and upvoted/downvoted status.
@@ -393,18 +391,18 @@ The comment is indented, filled, and then printed according to
" "))
(insert
(format sx-question-mode-comments-format
- (sx-user--format "%d" .owner)
- (substring
- ;; We use temp buffer, so that image overlays don't get
- ;; inserted with the comment.
- (with-temp-buffer
- ;; We fill with three spaces at the start, so the comment is
- ;; slightly indented.
- (sx-question-mode--insert-markdown (concat " " (sx--squash-whitespace .body_markdown)))
- (buffer-string))
- ;; Then we remove the spaces from the first line, since we'll
- ;; add the username there anyway.
- 3))))))
+ (sx-user--format "%d" .owner)
+ (substring
+ ;; We use temp buffer, so that image overlays don't get
+ ;; inserted with the comment.
+ (with-temp-buffer
+ ;; We fill with three spaces at the start, so the comment is
+ ;; slightly indented.
+ (sx-question-mode--insert-markdown (concat " " (sx--squash-whitespace .body_markdown)))
+ (buffer-string))
+ ;; Then we remove the spaces from the first line, since we'll
+ ;; add the username there anyway.
+ 3))))))
(defun sx-question-mode--insert-header (&rest args)
"Insert propertized ARGS.
@@ -506,6 +504,10 @@ font-locks code-blocks according to mode."
(goto-char end)))))
(goto-char (point-max)))))
+(defconst sx-question-mode-hr
+ (propertize (make-string 72 ?―)
+ 'face 'markdown-header-rule-face))
+
(defun sx-question-mode--insert-markdown (text)
"Return TEXT fontified according to `markdown-mode'."
(let ((beg (point)))
@@ -535,7 +537,8 @@ font-locks code-blocks according to mode."
`((,(rx (or blank line-start)
(group-n 1 (and "@" (1+ (not space))))
symbol-end)
- 1 font-lock-builtin-face)))
+ 1 font-lock-builtin-face)
+ ("^---+$" 0 '(face nil display ,sx-question-mode-hr))))
;; Everything.
(font-lock-fontify-region (point-min) (point-max))
(replace-regexp-in-string "[[:blank:]]+\\'" "" (buffer-string))))
@@ -573,6 +576,21 @@ This can be inline Markdown code or a Markdown code-block."
(save-excursion
(sx-question-mode--skip-and-fontify-pre 'dont-fontify)))))
+(defun sx-question-mode--standalone-tag-p (string)
+ "Return non-nil if STRING ends in \"/>\"."
+ (string-match "/[[:blank:]]*>\\'" string))
+
+(defun sx-question-mode--next-tag (tag &optional closing end)
+ "Move point to the next occurrence of html TAG, or return nil.
+Don't move past END.
+If CLOSING is non-nil, find a closing tag."
+ (search-forward-regexp
+ (format sx-question-mode--html-tag-regexp
+ (if closing
+ (concat "/[[:blank:]]*" tag)
+ tag))
+ end 'noerror))
+
(defun sx-question-mode--process-html-tags (beg end-marker)
"Hide all html tags between BEG and END and possibly interpret them.
END-MARKER should be a marker."
@@ -580,17 +598,20 @@ END-MARKER should be a marker."
;; nested in itself (e.g., <kbd><kbd></kbd></kbd>).
(set-marker-insertion-type end-marker t)
(goto-char beg)
- (while (search-forward-regexp
- (format sx-question-mode--html-tag-regexp "[[:alpha:]]+")
- end-marker 'noerror)
+ (while (sx-question-mode--next-tag "[[:alpha:]]+" nil end-marker)
(unless (sx-question-mode--inside-code-p)
(let ((tag (match-string 1))
+ (full (match-string 0))
(l (match-beginning 0)))
(replace-match "")
- (when (search-forward-regexp
- (format sx-question-mode--html-tag-regexp (concat "/" tag))
- ;; Searching for a match has no bounds.
- end-marker 'noerror)
+ (pcase tag
+ (`"hr"
+ (unless (looking-at-p "^") (insert "\n"))
+ (insert (propertize "---" 'display sx-question-mode-hr))
+ (unless (eq (char-after) ?\n) (insert "\n")))
+ (`"br" (insert "\n ")))
+ (when (and (not (sx-question-mode--standalone-tag-p full))
+ (sx-question-mode--next-tag tag 'closing))
(let ((r (copy-marker (match-beginning 0))))
;; The code tag is special, because it quotes everything inside.
(if (string= tag "code")
@@ -599,18 +620,18 @@ END-MARKER should be a marker."
(replace-match "")
;; Handle stuff between the two tags.
(save-match-data (sx-question-mode--process-html-tags l r))
- (cond
- ((string= tag "kbd")
- (add-text-properties l r '(face sx-question-mode-kbd-tag))
- (when (looking-at-p
- (format sx-question-mode--html-tag-regexp "kbd"))
- (insert " ")))
- ((string= tag "sub")
- (add-text-properties
- l r '(face sx-question-mode-sub-sup-tag display (raise -0.3))))
- ((string= tag "sup")
- (add-text-properties
- l r '(face sx-question-mode-sub-sup-tag display (raise +0.3))))))))))))
+ (pcase tag
+ (`"kbd"
+ (add-text-properties l r '(face sx-question-mode-kbd-tag))
+ (when (looking-at-p
+ (format sx-question-mode--html-tag-regexp "kbd"))
+ (insert " ")))
+ (`"sub"
+ (add-text-properties
+ l r '(face sx-question-mode-sub-sup-tag display (raise -0.3))))
+ (`"sup"
+ (add-text-properties
+ l r '(face sx-question-mode-sub-sup-tag display (raise +0.3))))))))))))
;;; Handling links
@@ -693,11 +714,11 @@ URL is used as 'help-echo and 'url properties."
;; Mouse-over
'help-echo
(format sx-button--link-help-echo
- ;; If TEXT is a shortened url, we don't shorten URL.
- (propertize (if (and (stringp text)
- (string-match "^https?:" text))
- url (sx--shorten-url url))
- 'face 'font-lock-function-name-face))
+ ;; If TEXT is a shortened url, we don't shorten URL.
+ (propertize (if (and (stringp text)
+ (string-match "^https?:" text))
+ url (sx--shorten-url url))
+ 'face 'font-lock-function-name-face))
;; For visiting and stuff.
'sx-button-url url
'sx-button-copy url
@@ -715,7 +736,7 @@ If ID is nil, use FALLBACK-ID instead."
(goto-char (point-min))
(when (search-forward-regexp
(format sx-question-mode--reference-regexp
- (or id fallback-id))
+ (or id fallback-id))
nil t)
(match-string-no-properties 1)))))
@@ -751,6 +772,8 @@ move point, don't create the code-block button."
(let ((beg (line-beginning-position)))
;; To identify code-blocks we need to be at start of line.
(goto-char beg)
+ (when (fboundp 'markdown-syntax-propertize)
+ (markdown-syntax-propertize (point) (point-max)))
(when (markdown-match-pre-blocks (line-end-position))
(unless dont-fontify
(sx-babel--make-pre-button beg (point)))
diff --git a/sx-request.el b/sx-request.el
index 4914705..10bec4a 100644
--- a/sx-request.el
+++ b/sx-request.el
@@ -92,13 +92,27 @@ number of requests left every time it finishes a call."
:group 'sx
:type 'integer)
-(defvar sx-request-all-items-delay
- 1
+(defvar sx-request-all-items-delay 0
"Delay in seconds with each `sx-request-all-items' iteration.
It is good to use a reasonable delay to avoid rate-limiting.")
;;; Making Requests
+(defvar sx--backoff-time nil)
+
+(defun sx-request--wait-while-backoff ()
+ (when sx--backoff-time
+ (message "Waiting for backoff time: %s" sx--backoff-time)
+ (let ((time (cadr (current-time))))
+ (if (> (- sx--backoff-time time) 1000)
+ ;; If backoff-time is more than 1000 seconds in the future,
+ ;; we've likely just looped around the "least significant"
+ ;; bits of `current-time'.
+ (setq sx--backoff-time time)
+ (when (< time sx--backoff-time)
+ (message "Backoff detected, waiting %s seconds" (- sx--backoff-time time))
+ (sleep-for (+ 0.3 (- sx--backoff-time time))))))))
+
(defun sx-request-all-items (method &optional args request-method
stop-when)
"Call METHOD with ARGS until there are no more items.
@@ -120,10 +134,10 @@ access the response wrapper."
(sx-request-make method `((page . ,current-page) ,@args)
request-method process-function)))
(while (not (funcall stop-when response))
- (setq current-page (1+ current-page)
- return-value
- (nconc return-value
- (cdr (assoc 'items response))))
+ (let-alist response
+ (setq current-page (1+ current-page)
+ return-value
+ (nconc return-value .items)))
(sleep-for sx-request-all-items-delay)
(setq response
(sx-request-make method `((page . ,current-page) ,@args)
@@ -156,6 +170,7 @@ then read with `json-read-from-string'.
`sx-request-remaining-api-requests' is updated appropriately and
the main content of the response is returned."
(declare (indent 1))
+ (sx-request--wait-while-backoff)
(let* ((url-automatic-caching t)
(url-inhibit-uncompression t)
(url-request-data (sx-request--build-keyword-arguments args nil))
@@ -202,6 +217,9 @@ the main content of the response is returned."
(when .error_id
(error "Request failed: (%s) [%i %s] %S"
.method .error_id .error_name .error_message))
+ (when .backoff
+ (message "Backoff received %s" .backoff)
+ (setq sx--backoff-time (+ (cadr (current-time)) .backoff)))
(when (< (setq sx-request-remaining-api-requests .quota_remaining)
sx-request-remaining-api-requests-message-threshold)
(sx-message "%d API requests remaining"
diff --git a/sx-search.el b/sx-search.el
index 4e6c4f2..885cb53 100644
--- a/sx-search.el
+++ b/sx-search.el
@@ -54,9 +54,9 @@ Either QUERY or TAGS must be non-nil, or the search will
fail. EXCLUDED-TAGS is only is used if TAGS is also provided.
KEYWORDS is passed to `sx-method-call'."
- (sx-method-call 'search
+ (sx-method-call 'search/advanced
:keywords `((page . ,page)
- (intitle . ,query)
+ (q . ,query)
(tagged . ,tags)
(nottagged . ,excluded-tags)
,@keywords)
diff --git a/sx-tab.el b/sx-tab.el
index 98b1b26..3b7a9aa 100644
--- a/sx-tab.el
+++ b/sx-tab.el
@@ -149,42 +149,27 @@ variables, but before refreshing the display."
(setq sx-question-list--order-methods
sx-tab--order-methods))
;;;###autoload
-(autoload 'sx-tab-all-questions
- (expand-file-name
- "sx-tab" (when load-file-name (file-name-directory load-file-name)))
- nil t)
+(autoload 'sx-tab-all-questions "sx-tab" nil t)
(sx-tab--define "Unanswered"
(sx-question-list--make-pager 'questions 'unanswered))
;;;###autoload
-(autoload 'sx-tab-unanswered
- (expand-file-name
- "sx-tab" (when load-file-name (file-name-directory load-file-name)))
- nil t)
+(autoload 'sx-tab-unanswered "sx-tab" nil t)
(sx-tab--define "Unanswered-My-Tags"
(sx-question-list--make-pager 'questions 'unanswered/my-tags))
;;;###autoload
-(autoload 'sx-tab-unanswered-my-tags
- (expand-file-name
- "sx-tab" (when load-file-name (file-name-directory load-file-name)))
- nil t)
+(autoload 'sx-tab-unanswered-my-tags "sx-tab" nil t)
(sx-tab--define "Featured"
(sx-question-list--make-pager 'questions 'featured))
;;;###autoload
-(autoload 'sx-tab-featured
- (expand-file-name
- "sx-tab" (when load-file-name (file-name-directory load-file-name)))
- nil t)
+(autoload 'sx-tab-featured "sx-tab" nil t)
(sx-tab--define "Starred"
(sx-question-list--make-pager 'me 'favorites))
;;;###autoload
-(autoload 'sx-tab-starred
- (expand-file-name
- "sx-tab" (when load-file-name (file-name-directory load-file-name)))
- nil t)
+(autoload 'sx-tab-starred "sx-tab" nil t)
;;; Inter-modes navigation
@@ -213,10 +198,7 @@ belongs to."
(setq sx-question-list--order-methods
sx-tab--order-methods))
;;;###autoload
-(autoload 'sx-tab-frontpage
- (expand-file-name
- "sx-tab" (when load-file-name (file-name-directory load-file-name)))
- nil t)
+(autoload 'sx-tab-frontpage "sx-tab" nil t)
(sx-tab--define "Newest"
sx-tab--basic-question-pager
@@ -225,10 +207,7 @@ belongs to."
(setq sx-question-list--order-methods
sx-tab--order-methods))
;;;###autoload
-(autoload 'sx-tab-newest
- (expand-file-name
- "sx-tab" (when load-file-name (file-name-directory load-file-name)))
- nil t)
+(autoload 'sx-tab-newest "sx-tab" nil t)
(sx-tab--define "TopVoted"
sx-tab--basic-question-pager
@@ -237,10 +216,7 @@ belongs to."
(setq sx-question-list--order-methods
sx-tab--order-methods))
;;;###autoload
-(autoload 'sx-tab-topvoted
- (expand-file-name
- "sx-tab" (when load-file-name (file-name-directory load-file-name)))
- nil t)
+(autoload 'sx-tab-topvoted "sx-tab" nil t)
(sx-tab--define "Hot"
sx-tab--basic-question-pager
@@ -249,10 +225,7 @@ belongs to."
(setq sx-question-list--order-methods
sx-tab--order-methods))
;;;###autoload
-(autoload 'sx-tab-hot
- (expand-file-name
- "sx-tab" (when load-file-name (file-name-directory load-file-name)))
- nil t)
+(autoload 'sx-tab-hot "sx-tab" nil t)
(sx-tab--define "Week"
sx-tab--basic-question-pager
@@ -261,10 +234,7 @@ belongs to."
(setq sx-question-list--order-methods
sx-tab--order-methods))
;;;###autoload
-(autoload 'sx-tab-week
- (expand-file-name
- "sx-tab" (when load-file-name (file-name-directory load-file-name)))
- nil t)
+(autoload 'sx-tab-week "sx-tab" nil t)
(sx-tab--define "Month"
sx-tab--basic-question-pager
@@ -273,10 +243,7 @@ belongs to."
(setq sx-question-list--order-methods
sx-tab--order-methods))
;;;###autoload
-(autoload 'sx-tab-month
- (expand-file-name
- "sx-tab" (when load-file-name (file-name-directory load-file-name)))
- nil t)
+(autoload 'sx-tab-month "sx-tab" nil t)
(provide 'sx-tab)
;;; sx-tab.el ends here