From 1207f47bd2340922268a1340601d45e6c336c11a Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Fri, 7 Nov 2014 18:51:23 -0500 Subject: Introduce initialization logic and hooks --- sx-question.el | 9 ++++----- sx.el | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/sx-question.el b/sx-question.el index e9634f7..0ce5413 100644 --- a/sx-question.el +++ b/sx-question.el @@ -30,11 +30,10 @@ (require 'sx-request) ;; I don't know why this is here, but it was causing an API request on require. -(defvar sx-question-browse-filter nil - ;; (stack-filter-compile - ;; nil - ;; '(user.profile_image shallow_user.profile_image)) - ) +(defvar sx-question-browse-filter nil) +(sx-init-variable + sx-question-browse-filter + (sx-filter-compile nil '(user.profile_image shallow_user.profile_image))) ;; (stack-filter-store 'question-browse sx-question-browse-filter) diff --git a/sx.el b/sx.el index a1e504e..abf1ca8 100644 --- a/sx.el +++ b/sx.el @@ -112,6 +112,33 @@ is equivalent to `(let ,(mapcar (lambda (x) `(,x (cdr (assoc ',x ,alist)))) symbols) ,@body))) +(defcustom sx-init-hook nil + "Hook run when stack-mode initializes. + +Run after `sx-init--internal-hook'.") + +(defvar sx-init--internal-hook nil + "Hook run when stack-mode initializes. + +This is used internally to set initial values for variables such +as filters.") + +(defmacro sx-init-variable (variable value &optional setter) + "Set VARIABLE to VALUE using SETTER. +SETTER should be a function of two arguments. If SETTER is nil, +`set' is used." + (eval + `(add-hook + 'sx-init--internal-hook + (lambda () + (,(or setter #'setq) ,variable ,value)))) + nil) + +(defun stack-initialize () + (run-hooks + 'sx-init--internal-hook + 'sx-init-hook)) + (provide 'sx) ;;; sx.el ends here -- cgit v1.2.3 From b6043f63e5b3437633a53bcc191214bb8a7f936b Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sat, 8 Nov 2014 13:40:57 -0500 Subject: Finish name changes --- sx-auth.el | 2 +- sx-lto.el | 4 ++-- sx-network.el | 2 +- test/tests.el | 30 +++++++++++++++--------------- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/sx-auth.el b/sx-auth.el index 59be452..b23b448 100644 --- a/sx-auth.el +++ b/sx-auth.el @@ -68,7 +68,7 @@ questions)." (sx-cache-set "auth.el" `((access-token . ,sx-auth-access-token))))) (provide 'sx-auth) -;;; stack-auth.el ends here +;;; sx-auth.el ends here ;; Local Variables: ;; indent-tabs-mode: nil diff --git a/sx-lto.el b/sx-lto.el index 6bdd5d0..ad58570 100644 --- a/sx-lto.el +++ b/sx-lto.el @@ -66,14 +66,14 @@ by the API and read by `json-read'." '((((background light)) :background "Grey90") (((background dark)) :background "Grey10")) "Face used on the body content of questions and answers." - :group 'stack-mode-faces) + :group 'sx-faces) ;;; This is not used ATM since we got rid of HTML. But it can be used ;;; once we start extending markdown mode. (defcustom sx-lto-bullet (if (char-displayable-p ?•) " •" " -") "Bullet used on the display of lists." :type 'string - :group 'stack-mode) + :group 'sx) (defun sx-lto--body (data) "Get and cleanup `body_markdown' from DATA." diff --git a/sx-network.el b/sx-network.el index dcd2349..f756a26 100644 --- a/sx-network.el +++ b/sx-network.el @@ -29,7 +29,7 @@ (sx-request-make "sites")) (provide 'sx-network) -;;; stack-network.el ends here +;;; sx-network.el ends here ;; Local Variables: ;; indent-tabs-mode: nil diff --git a/test/tests.el b/test/tests.el index 2864428..c7861d8 100644 --- a/test/tests.el +++ b/test/tests.el @@ -1,20 +1,20 @@ -(defun -stack--nuke () +(defun -sx--nuke () (interactive) (mapatoms (lambda (symbol) - (if (string-prefix-p "stack-" (symbol-name symbol)) + (if (string-prefix-p "sx-" (symbol-name symbol)) (unintern symbol))))) ;;; Tests -(defvar stack-test-data-dir +(defvar sx-test-data-dir (expand-file-name "data-samples/" (or (file-name-directory load-file-name) "./")) "") -(defun stack-test-sample-data (method &optional directory) +(defun sx-test-sample-data (method &optional directory) (let ((file (concat (when directory (concat directory "/")) - stack-test-data-dir + sx-test-data-dir method ".el"))) (when (file-exists-p file) (with-temp-buffer @@ -27,14 +27,14 @@ sx-request-silent-p nil user-emacs-directory "." - stack-test-data-questions - (stack-test-sample-data "questions") - stack-test-data-sites - (stack-test-sample-data "sites")) + sx-test-data-questions + (sx-test-sample-data "questions") + sx-test-data-sites + (sx-test-sample-data "sites")) (setq package-user-dir (expand-file-name (format "../../.cask/%s/elpa" emacs-version) - stack-test-data-dir)) + sx-test-data-dir)) (package-initialize) (require 'cl-lib) (require 'sx) @@ -55,7 +55,7 @@ (sx-request-make "questions" '(())))) (ert-deftest test-tree-filter () - "`stack-core-filter-data'" + "`sx-core-filter-data'" ;; flat (should (equal @@ -117,7 +117,7 @@ (ert-deftest question-list-display () (cl-letf (((symbol-function #'sx-request-make) - (lambda (&rest _) stack-test-data-questions))) + (lambda (&rest _) sx-test-data-questions))) (list-questions nil) (switch-to-buffer "*question-list*") (goto-char (point-min)) @@ -127,9 +127,9 @@ (sx-question-list-next 5) (line-should-match "^\\s-+0\\s-+1\\s-+Babel doesn't wrap results in verbatim [ 0-9]+[ydhms] ago\\s-+\\[org-mode\\]") - ;; ;; Use this when we have a real stack-question buffer. - ;; (call-interactively 'stack-question-list-display-question) - ;; (should (equal (buffer-name) "*stack-question*")) + ;; ;; Use this when we have a real sx-question buffer. + ;; (call-interactively 'sx-question-list-display-question) + ;; (should (equal (buffer-name) "*sx-question*")) (switch-to-buffer "*question-list*") (sx-question-list-previous 4) (line-should-match -- cgit v1.2.3 From 7ed29c4dc940a871562aaa802ac53ddee4c66a27 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sat, 8 Nov 2014 14:12:42 -0500 Subject: Re-work filtering and caching * sx-auth.el - Use new symbolic cache access * sx-cache.el - Implement symbolic cache access * sx-filter.el - Use symbolic cache access - Compile and save filters on-demand (more work to be done to this end) * sx-question.el - Symbolic filters * sx-request.el - Protection against infinitely recursing when compiling a filter This will be re-worked into requests (a front-end function) and 'raw' requests (a back-end function). The front-end will add convenience to the back-end. * test/tests.el Remove outdated tests --- sx-auth.el | 2 +- sx-cache.el | 9 ++++++++- sx-filter.el | 54 +++++++++++++++++------------------------------------- sx-question.el | 9 +++------ sx-request.el | 8 ++++++-- test/tests.el | 21 +-------------------- 6 files changed, 36 insertions(+), 67 deletions(-) diff --git a/sx-auth.el b/sx-auth.el index b23b448..049827d 100644 --- a/sx-auth.el +++ b/sx-auth.el @@ -65,7 +65,7 @@ questions)." (if (string-equal "" sx-auth-access-token) (progn (setq sx-auth-access-token nil) (error "You must enter this code to use this client fully")) - (sx-cache-set "auth.el" `((access-token . ,sx-auth-access-token))))) + (sx-cache-set 'auth `((access-token . ,sx-auth-access-token))))) (provide 'sx-auth) ;;; sx-auth.el ends here diff --git a/sx-cache.el b/sx-cache.el index a090982..54ae94f 100644 --- a/sx-cache.el +++ b/sx-cache.el @@ -20,7 +20,12 @@ ;;; Commentary: +;; All caches are retrieved and set using symbols. The symbol should +;; be the sub-subpackage that is using the cache. For example, +;; `sx-pkg' would use `(sx-cache-get 'pkg)'. ;; +;; This symbol is then converted into a filename within +;; `sx-cache-directory'. ;;; Code: @@ -30,7 +35,9 @@ (defun sx-cache-get-file-name (filename) "Expands FILENAME in the context of `sx-cache-directory'." - (expand-file-name filename sx-cache-directory)) + (expand-file-name + (concat (symbol-name filename) ".el") + sx-cache-directory)) (defun sx-cache-get (cache) "Return the data within CACHE. diff --git a/sx-filter.el b/sx-filter.el index 7178259..0ba8186 100644 --- a/sx-filter.el +++ b/sx-filter.el @@ -32,17 +32,9 @@ ;;; Customizations -(defconst sx-filter-cache-file - "filters.el") - -(defvar sx-filter - 'default - "The current filter. -To customize the filter for the next call to `sx-request-make', -let-bind this variable to the output of a call to -`sx-filter-compile'. Be careful! If you're going to be using -this new filter a lot, create a variable for it. Creation -requests count against `sx-request-remaining-api-requests'!") +(defvar sx--filter-alist + (sx-cache-get 'filter) + "") ;;; Compilation @@ -67,32 +59,20 @@ or string." ;;; Storage and Retrieval -(defun sx-filter-get (filter) - "Retrieve named FILTER from `sx-filter-cache-file'." - (cdr (assoc filter (sx-cache-get sx-filter-cache-file)))) - -(defun sx-filter-store (name &optional filter) - "Store NAME as FILTER in `sx-filter-cache-file'. - -NAME should be a symbol and FILTER is a string as compiled by -`sx-filter-compile'. - -If NAME is a cons cell, (car NAME) is taken to be the actual NAME -and (cdr NAME) is taken to be the actual FILTER. In this case, -the second argument is simply ignored." - (let ((name (if (consp name) (car name) name)) - (filter (if (consp name) (cdr name) filter))) - (unless (symbolp name) - (error "Name must be a symbol: %S" name)) - (let* ((dict (sx-cache-get sx-filter-cache-file)) - (entry (assoc name dict))) - (if entry (setcdr entry filter) - (setq dict (cons (cons name filter) dict))) - - (sx-cache-set sx-filter-cache-file dict)))) - -(defun sx-filter-store-all (name-filter-alist) - (mapc #'sx-filter-store name-filter-alist)) +(defun sx-filter-get-var (filter-variable) + "Return the string representation of FILTER-VARIABLE." + (apply #'sx-filter-get filter-variable)) + +(defun sx-filter-get (&optional include exclude base) + "Return the string representation of the given filter." + ;; Maybe we alreay have this filter + (or (cdr (assoc (list include exclude base) sx--filter-alist)) + ;; If we don't, build it, save it, and return it. + (let ((filter (sx-filter-compile include exclude base))) + (when filter + (push (cons (list include exclude base) filter) sx--filter-alist) + (sx-cache-set 'filter sx--filter-alist) + filter)))) (provide 'sx-filter) ;;; sx-filter.el ends here diff --git a/sx-question.el b/sx-question.el index 0ce5413..8957e6f 100644 --- a/sx-question.el +++ b/sx-question.el @@ -30,12 +30,9 @@ (require 'sx-request) ;; I don't know why this is here, but it was causing an API request on require. -(defvar sx-question-browse-filter nil) -(sx-init-variable - sx-question-browse-filter - (sx-filter-compile nil '(user.profile_image shallow_user.profile_image))) - -;; (stack-filter-store 'question-browse sx-question-browse-filter) +(defvar sx-question-browse-filter + ;; Remember: INCLUDE EXCLUDE BASE + '(nil (user.profile_image shallow_user.profile_image))) (defun sx-question-get-questions (site &optional page) "Get the page PAGE of questions from SITE." diff --git a/sx-request.el b/sx-request.el index a62ee0e..8ca0314 100644 --- a/sx-request.el +++ b/sx-request.el @@ -26,6 +26,7 @@ (require 'json) (require 'url) (require 'sx) +(require 'sx-filter) (defcustom sx-request-silent-p t @@ -95,8 +96,11 @@ entire response as a complex alist." (call (sx-request--build method - (append `((filter . ,(cond (filter filter) - ((boundp 'stack-filter) stack-filter))) + (append `((filter . ,(unless (string-equal method "filter/create") + (sx-filter-get-var + (cond (filter filter) + ((boundp 'stack-filter) + stack-filter))))) (key . ,sx-request-api-key)) (if keyword-arguments keyword-arguments (sx-request--get-default-keyword-arguments method)))))) diff --git a/test/tests.el b/test/tests.el index c7861d8..7915ac0 100644 --- a/test/tests.el +++ b/test/tests.el @@ -9,8 +9,7 @@ (defvar sx-test-data-dir (expand-file-name "data-samples/" - (or (file-name-directory load-file-name) "./")) - "") + (or (file-name-directory load-file-name) "./"))) (defun sx-test-sample-data (method &optional directory) (let ((file (concat (when directory (concat directory "/")) @@ -89,24 +88,6 @@ ((1 . alpha) (2 . beta))] '(1 2 3))))) -(ert-deftest test-filters () - (let ((stack-cache-directory (make-temp-file "stack-test" t))) - (should-error (sx-filter-store "names must be symbols" - "this is a filter")) - ;; basic use - (should (equal '((test . "filter")) - (sx-filter-store 'test "filter"))) - ;; aggregation - (should (equal '((test2 . "filter2") (test . "filter")) - (sx-filter-store 'test2 "filter2"))) - ;; mutation - (should (equal '((test2 . "filter2") (test . "filter-test")) - (sx-filter-store 'test "filter-test"))) - ;; clean up (note: the file should exist) - (delete-file - (sx-cache-get-file-name - sx-filter-cache-file)))) - (defmacro line-should-match (regexp) "" `(let ((line (buffer-substring-no-properties -- cgit v1.2.3 From 611a00ffcd36915e7927c574dfdaec44d0c0fa58 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sat, 8 Nov 2014 14:17:24 -0500 Subject: Delineate and alphabetize API symbols It's ugly to look at, but diffs will be more precise. --- sx.el | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/sx.el b/sx.el index abf1ca8..54ad8d0 100644 --- a/sx.el +++ b/sx.el @@ -71,11 +71,41 @@ a string, just return it." ;;; Interpreting request data (defvar sx--api-symbols - '(accept_rate answer_count answer_id answers body body_markdown close_vote_count upvoted downvoted - comment_count comment_id creation_date delete_vote_count display_name - edited favorite_count is_accepted is_answered last_activity_date - last_edit_date last_editor link owner profile_image question_id - reopen_vote_count reputation score tags title user_id user_type view_count) + '( + accept_rate + answer_count + answer_id + answers + body + body_markdown + close_vote_count + comment_count + comment_id + creation_date + delete_vote_count + display_name + downvoted + edited + favorite_count + is_accepted + is_answered + last_activity_date + last_edit_date + last_editor + link + owner + profile_image + question_id + reopen_vote_count + reputation + score + tags + title + upvoted + user_id + user_type + view_count + ) "") (defun sx--deep-search (symbol list) -- cgit v1.2.3 From bbcbcea7d944e2ca6ae081b04b2aa71a92bec799 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sat, 8 Nov 2014 14:32:32 -0500 Subject: Add internal request function --- sx-request.el | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/sx-request.el b/sx-request.el index 8ca0314..f24294e 100644 --- a/sx-request.el +++ b/sx-request.el @@ -84,6 +84,58 @@ See `sx-request-get-default-keyword-arguments' and "0TE6s1tveCpP9K5r5JNDNQ((" "When passed, this key provides a higher request quota.") +(defun sx-request--make + (method &optional args silent) + (let ((url-automatic-caching sx-request-cache-p) + (url-inhibit-uncompression t) + (silent (or silent sx-request-silent-p)) + (call (sx-request--build method args))) + (unless silent (sx-message "Request: %S" call)) + (let ((response-buffer (cond + ((equal '(24 . 4) (cons emacs-major-version emacs-minor-version)) + (url-retrieve-synchronously call silent)) + (t (url-retrieve-synchronously call))))) + (if (not response-buffer) + (error "Something went wrong in `url-retrieve-synchronously'") + (with-current-buffer response-buffer + (let* ((data (progn + (goto-char (point-min)) + (if (not (search-forward "\n\n" nil t)) + (error "Response headers missing; response corrupt") + (delete-region (point-min) (point)) + (buffer-string)))) + (response (ignore-errors + (json-read-from-string data)))) + ;; If the response isn't nil, the response was in plain text + (unless response + ;; try to decompress the response + (setq response + (with-demoted-errors "`json-read' error: %S" + (shell-command-on-region + (point-min) (point-max) + sx-request-unzip-program + nil t) + (json-read-from-string + (buffer-substring + (point-min) (point-max))))) + ;; if it still fails, error outline + (unless response + (sx-message "Unable to parse response: %S" response) + (error "Response could not be read by `json-read-from-string'"))) + ;; If we get here, the response is a valid data structure + (when (assoc 'error_id response) + (error "Request failed: (%s) [%i %s] %S" + method + (cdr (assoc 'error_id response)) + (cdr (assoc 'error_name response)) + (cdr (assoc 'error_message response)))) + (when (< (setq sx-request-remaining-api-requests + (cdr (assoc 'quote_remaining response))) + sx-request-remaining-api-requests-message-threshold) + (sx-message "%d API requests reamining" + sx-request-remaining-api-requests)) + (cdr (assoc 'items response)))))))) + (defun sx-request-make (method &optional keyword-arguments filter silent) "Make a request to the StackExchange API using METHOD and -- cgit v1.2.3 From 606a886fa65571c649ad15337b5b5e4bcb18e953 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sat, 8 Nov 2014 14:41:06 -0500 Subject: Use the key for every request; no exceptions --- sx-request.el | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sx-request.el b/sx-request.el index f24294e..99c26c3 100644 --- a/sx-request.el +++ b/sx-request.el @@ -89,7 +89,10 @@ See `sx-request-get-default-keyword-arguments' and (let ((url-automatic-caching sx-request-cache-p) (url-inhibit-uncompression t) (silent (or silent sx-request-silent-p)) - (call (sx-request--build method args))) + (call (sx-request--build + method + (cons (cons 'key sx-request-api-key) + args)))) (unless silent (sx-message "Request: %S" call)) (let ((response-buffer (cond ((equal '(24 . 4) (cons emacs-major-version emacs-minor-version)) -- cgit v1.2.3 From 7c5cc6faf61f58cd1534dcab2c4dbb667c00476d Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sat, 8 Nov 2014 14:41:23 -0500 Subject: Implement front-end with back-end --- sx-request.el | 71 ++++++----------------------------------------------------- 1 file changed, 7 insertions(+), 64 deletions(-) diff --git a/sx-request.el b/sx-request.el index 99c26c3..3656b94 100644 --- a/sx-request.el +++ b/sx-request.el @@ -145,70 +145,13 @@ See `sx-request-get-default-keyword-arguments' and optional KEYWORD-ARGUMENTS. If no KEYWORD-ARGUMENTS are given, `sx-default-keyword-arguments-alist' is used. Return the entire response as a complex alist." - (let ((url-automatic-caching sx-request-cache-p) - (url-inhibit-uncompression t) - (silent (or silent sx-request-silent-p)) - (call - (sx-request--build - method - (append `((filter . ,(unless (string-equal method "filter/create") - (sx-filter-get-var - (cond (filter filter) - ((boundp 'stack-filter) - stack-filter))))) - (key . ,sx-request-api-key)) - (if keyword-arguments keyword-arguments - (sx-request--get-default-keyword-arguments method)))))) - ;; TODO: url-retrieve-synchronously can return nil if the call is - ;; unsuccessful should handle this case - (unless silent (sx-message "Request: %S" call)) - (let ((response-buffer (cond - ((= emacs-minor-version 4) - (url-retrieve-synchronously call silent)) - (t (url-retrieve-synchronously call))))) - (if (not response-buffer) - (error "Something went wrong in `url-retrieve-synchronously'") - (with-current-buffer response-buffer - (let* ((data (progn - (goto-char (point-min)) - (if (not (search-forward "\n\n" nil t)) - (error "Response headers missing") - (delete-region (point-min) (point)) - (buffer-string)))) - (response (ignore-errors - (json-read-from-string data)))) - ;; if response isn't nil, the response was in plain text - (unless response - ;; try to decompress the response - (setq response - (with-demoted-errors "JSON Error: %s" - (shell-command-on-region - (point-min) (point-max) - sx-request-unzip-program - nil t) - (json-read-from-string - (buffer-substring - (point-min) (point-max))))) - ;; If it still fails, error out - (unless response - (sx-message "Unable to parse response") - (sx-message "Printing response as message") - (message "%S" response) - (error "Response could not be read by json-read-string"))) - ;; At this point, either response is a valid data structure - ;; or we have already thrown an error - (when (assoc 'error_id response) - (error "Request failed: (%s) [%i %s] %s" - method - (cdr (assoc 'error_id response)) - (cdr (assoc 'error_name response)) - (cdr (assoc 'error_message response)))) - (when (< (setq sx-request-remaining-api-requests - (cdr (assoc 'quota_remaining response))) - sx-request-remaining-api-requests-message-threshold) - (sx-message "%d API requests remaining" - sx-request-remaining-api-requests)) - (cdr (assoc 'items response)))))))) + (sx-request--make + method + (cons (cons 'filter + (sx-filter-get-var + (cond (filter filter) + ((boundp 'stack-filter) stack-filter)))) + keyword-arguments))) (defun sx-request--build (method keyword-arguments &optional kv-value-sep) "Build the request string that will be used to process REQUEST -- cgit v1.2.3 From 6aa21b85ace92b01676c6da66372b409fe639920 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sat, 8 Nov 2014 15:49:17 -0500 Subject: Abstract out method calls Keeping method calls within `sx-request.el' was causing circular requirements. This commit sorts through all of the requirements for each of the files and ensures that this does not happen. Much of the content removed was for `sx-request-default-keyword-arguments-alist' and related items. It was unused, so it was pruned. If it is deemed necessary in the future, it should be included in `sx-method.el'. --- sx-auth.el | 19 +++++------ sx-filter.el | 4 +-- sx-method.el | 48 ++++++++++++++++++++++++++ sx-question-list.el | 6 ++-- sx-question.el | 6 ++-- sx-request.el | 98 +++++++++++++++++------------------------------------ sx.el | 3 +- test/tests.el | 1 + 8 files changed, 99 insertions(+), 86 deletions(-) create mode 100644 sx-method.el diff --git a/sx-auth.el b/sx-auth.el index 049827d..7912508 100644 --- a/sx-auth.el +++ b/sx-auth.el @@ -50,16 +50,15 @@ questions)." (interactive) (setq sx-auth-access-token - (let* ((sx-request-api-root sx-auth-root) - (url (sx-request--build - "dialog" - `((client_id . ,sx-auth-client-id) - (scope . (read_inbox - no_expiry - write_access)) - (redirect_uri . ,(url-hexify-string - sx-auth-redirect-uri))) - ","))) + (let ((url (sx-request-build + "dialog" + `((client_id . ,sx-auth-client-id) + (scope . (read_inbox + no_expiry + write_access)) + (redirect_uri . ,(url-hexify-string + sx-auth-redirect-uri))) + "," sx-auth-root))) (browse-url url) (read-string "Enter the access token displayed on the webpage: "))) (if (string-equal "" sx-auth-access-token) diff --git a/sx-filter.el b/sx-filter.el index 0ba8186..4ed3dbe 100644 --- a/sx-filter.el +++ b/sx-filter.el @@ -28,6 +28,7 @@ (require 'sx) (require 'sx-cache) +(require 'sx-request) ;;; Customizations @@ -53,8 +54,7 @@ or string." "filter/create" keyword-arguments))) (url-hexify-string - (cdr (assoc 'filter - (elt response 0))))))) + (cdr (assoc 'filter (elt response 0))))))) ;;; Storage and Retrieval diff --git a/sx-method.el b/sx-method.el new file mode 100644 index 0000000..8dc5a65 --- /dev/null +++ b/sx-method.el @@ -0,0 +1,48 @@ +;;; sx-request.el --- requests for stack-mode + +;; Copyright (C) 2014 Sean Allred + +;; Author: Sean Allred +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; + +;;; Code: +(require 'json) +(require 'url) +(require 'sx) +(require 'sx-request) +(require 'sx-filter) + +(defun sx-method-call + (method &optional keyword-arguments filter silent) + "Call METHOD with KEYWORD-ARGUMENTS using FILTER. + +If SILENT is non-nil, no messages will be printed. + +Return the entire response as a complex alist." + (sx-request-make + method + (cons (cons 'filter + (sx-filter-get-var + (cond (filter filter) + ((boundp 'stack-filter) stack-filter)))) + keyword-arguments))) + +(provide 'sx-method) +;;; sx-request.el ends here diff --git a/sx-question-list.el b/sx-question-list.el index 86e9194..caf24b1 100644 --- a/sx-question-list.el +++ b/sx-question-list.el @@ -20,11 +20,13 @@ ;;; Commentary: ;;; Code: -(require 'sx-question) -(require 'sx-time) (require 'tabulated-list) (require 'cl-lib) +(require 'sx) +(require 'sx-time) +(require 'sx-question) + ;;; Customization (defcustom sx-question-list-height 12 diff --git a/sx-question.el b/sx-question.el index 8957e6f..20a71cc 100644 --- a/sx-question.el +++ b/sx-question.el @@ -27,16 +27,14 @@ (require 'sx) (require 'sx-filter) (require 'sx-lto) -(require 'sx-request) +(require 'sx-method) -;; I don't know why this is here, but it was causing an API request on require. (defvar sx-question-browse-filter - ;; Remember: INCLUDE EXCLUDE BASE '(nil (user.profile_image shallow_user.profile_image))) (defun sx-question-get-questions (site &optional page) "Get the page PAGE of questions from SITE." - (sx-request-make + (sx-method-call "questions" `((site . ,site) (page . ,page)) diff --git a/sx-request.el b/sx-request.el index 3656b94..f987d2c 100644 --- a/sx-request.el +++ b/sx-request.el @@ -1,9 +1,9 @@ -;;; sx-request.el --- requests for stack-mode +;;; sx-request.el --- requests and url manipulation -*- lexical-binding: t; -*- ;; Copyright (C) 2014 Sean Allred -;; Author: Sean Allred -;; Keywords: +;; Author: Sean Allred +;; Keywords: help ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by @@ -23,10 +23,26 @@ ;; ;;; Code: -(require 'json) + (require 'url) +(require 'json) + (require 'sx) -(require 'sx-filter) + + +;;; Variables + +(defconst sx-request-api-key + "0TE6s1tveCpP9K5r5JNDNQ((" + "When passed, this key provides a higher request quota.") + +(defconst sx-request-api-version + "2.2" + "The current version of the API.") + +(defconst sx-request-api-root + (format "http://api.stackexchange.com/%s/" sx-request-api-version) + "The base URL to make requests from.") (defcustom sx-request-silent-p t @@ -51,45 +67,15 @@ recent call. Set by `sx-request-make'.") number, `sx-request-make' will begin printing out the number of requests left every time it finishes a call.") -(defcustom sx-request-default-keyword-arguments-alist - '(("filters/create") - ("sites") - ("questions" (site . emacs)) - (t nil)) - "Keywords to use as the default for a given method. - -The first element of each list is the method call the keywords -apply to. The remaining cons cells (and they must be conses) are -the values for each keyword. - -For each list, if no keywords are provided, the method's -arguments are forced to the default as determined by the API. - -For each cons cell, if the cdr is `nil', then the keyword will be -forced to the default as determined by the API. + +;;; Making Requests -See `sx-request-get-default-keyword-arguments' and -`sx-request-build-keyword-arguments'. -") - -(defconst sx-request-api-version - "2.2" - "The current version of the API.") - -(defconst sx-request-api-root - (format "http://api.stackexchange.com/%s/" sx-request-api-version) - "The base URL to make requests from.") - -(defconst sx-request-api-key - "0TE6s1tveCpP9K5r5JNDNQ((" - "When passed, this key provides a higher request quota.") - -(defun sx-request--make +(defun sx-request-make (method &optional args silent) (let ((url-automatic-caching sx-request-cache-p) (url-inhibit-uncompression t) (silent (or silent sx-request-silent-p)) - (call (sx-request--build + (call (sx-request-build method (cons (cons 'key sx-request-api-key) args)))) @@ -133,30 +119,19 @@ See `sx-request-get-default-keyword-arguments' and (cdr (assoc 'error_name response)) (cdr (assoc 'error_message response)))) (when (< (setq sx-request-remaining-api-requests - (cdr (assoc 'quote_remaining response))) + (cdr (assoc 'quota_remaining response))) sx-request-remaining-api-requests-message-threshold) (sx-message "%d API requests reamining" sx-request-remaining-api-requests)) (cdr (assoc 'items response)))))))) -(defun sx-request-make - (method &optional keyword-arguments filter silent) - "Make a request to the StackExchange API using METHOD and -optional KEYWORD-ARGUMENTS. If no KEYWORD-ARGUMENTS are given, -`sx-default-keyword-arguments-alist' is used. Return the -entire response as a complex alist." - (sx-request--make - method - (cons (cons 'filter - (sx-filter-get-var - (cond (filter filter) - ((boundp 'stack-filter) stack-filter)))) - keyword-arguments))) - -(defun sx-request--build (method keyword-arguments &optional kv-value-sep) + +;;; Support Functions + +(defun sx-request-build (method keyword-arguments &optional kv-value-sep root) "Build the request string that will be used to process REQUEST with the given KEYWORD-ARGUMENTS." - (let ((base (concat sx-request-api-root method)) + (let ((base (concat (or root sx-request-api-root) method)) (args (sx-request--build-keyword-arguments keyword-arguments kv-value-sep))) (if (string-equal "" args) @@ -181,16 +156,5 @@ false, use the symbol `false'. Each element is processed with alist)) "&")) -(defun sx-request--get-default-keyword-arguments (method) - "Gets the correct keyword arguments for METHOD." - (let ((entry (assoc method sx-request-default-keyword-arguments-alist))) - (cdr (or entry (assoc t sx-request-default-keyword-arguments-alist))))) - -;;; @todo sx-request-change-default-keyword-arguments -;;; (method new-keyword-arguments) -;;; @todo sx-request-change-default-keyword-arguments-for-key -;;; (method key new-value) - - (provide 'sx-request) ;;; sx-request.el ends here diff --git a/sx.el b/sx.el index 54ad8d0..2f7b6a7 100644 --- a/sx.el +++ b/sx.el @@ -25,7 +25,8 @@ ;;; Code: -;;; Requirements +;;; Utility Functions + (defun sx-message (format-string &rest args) "Display a message" (message "[stack] %s" (apply #'format format-string args))) diff --git a/test/tests.el b/test/tests.el index 7915ac0..a66394c 100644 --- a/test/tests.el +++ b/test/tests.el @@ -35,6 +35,7 @@ (expand-file-name (format "../../.cask/%s/elpa" emacs-version) sx-test-data-dir)) (package-initialize) + (require 'cl-lib) (require 'sx) (require 'sx-question) -- cgit v1.2.3 From b8da42344ebe3f380bb6f615c2ab13bb12c24876 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sat, 8 Nov 2014 16:54:51 -0500 Subject: Use macro --- sx-request.el | 22 ++++++++++------------ sx.el | 5 +++++ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/sx-request.el b/sx-request.el index f987d2c..4e2ba5c 100644 --- a/sx-request.el +++ b/sx-request.el @@ -112,18 +112,16 @@ number of requests left every time it finishes a call.") (sx-message "Unable to parse response: %S" response) (error "Response could not be read by `json-read-from-string'"))) ;; If we get here, the response is a valid data structure - (when (assoc 'error_id response) - (error "Request failed: (%s) [%i %s] %S" - method - (cdr (assoc 'error_id response)) - (cdr (assoc 'error_name response)) - (cdr (assoc 'error_message response)))) - (when (< (setq sx-request-remaining-api-requests - (cdr (assoc 'quota_remaining response))) - sx-request-remaining-api-requests-message-threshold) - (sx-message "%d API requests reamining" - sx-request-remaining-api-requests)) - (cdr (assoc 'items response)))))))) + (sx-assoc-let response + (when error_id + (error "Request failed: (%s) [%i %s] %S" + method error_id error_name error_message)) + (when (< (setq sx-request-remaining-api-requests + quota_remaining) + sx-request-remaining-api-requests-message-threshold) + (sx-message "%d API requests reamining" + sx-request-remaining-api-requests)) + items))))))) ;;; Support Functions diff --git a/sx.el b/sx.el index 2f7b6a7..00fda40 100644 --- a/sx.el +++ b/sx.el @@ -87,7 +87,11 @@ a string, just return it." display_name downvoted edited + error_id + error_name + error_message favorite_count + items is_accepted is_answered last_activity_date @@ -97,6 +101,7 @@ a string, just return it." owner profile_image question_id + quota_remaining reopen_vote_count reputation score -- cgit v1.2.3 From 5fcd0dbd2c180b85898f42a7cd24a5835d6e3eda Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sat, 8 Nov 2014 16:58:58 -0500 Subject: Use macro --- sx-filter.el | 4 ++-- sx.el | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/sx-filter.el b/sx-filter.el index 4ed3dbe..aa815a2 100644 --- a/sx-filter.el +++ b/sx-filter.el @@ -53,8 +53,8 @@ or string." (let ((response (sx-request-make "filter/create" keyword-arguments))) - (url-hexify-string - (cdr (assoc 'filter (elt response 0))))))) + (sx-assoc-let (elt response 0) + (url-hexify-string filter))))) ;;; Storage and Retrieval diff --git a/sx.el b/sx.el index 00fda40..6165714 100644 --- a/sx.el +++ b/sx.el @@ -91,6 +91,7 @@ a string, just return it." error_name error_message favorite_count + filter items is_accepted is_answered -- cgit v1.2.3 From debf27f3a4fba58f97593e4bf3b962a2dbad2bca Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sat, 8 Nov 2014 17:24:15 -0500 Subject: Add makefile --- makefile.make | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 makefile.make diff --git a/makefile.make b/makefile.make new file mode 100644 index 0000000..f76bc70 --- /dev/null +++ b/makefile.make @@ -0,0 +1,11 @@ +# This makefile runs the tests as Travis runs them. Be sure to test +# locally before you push if you are under the impression that the +# patch should work. This will cut down on the number of commits in +# the repository that, essentially, patch patches. + +all: 24.1 24.2 24.3 + +%: + evm install emacs-$@-bin || true + emacs --version + emacs --batch -L . -l ert -l test/tests.el -f ert-run-tests-batch-and-exit -- cgit v1.2.3 From 1f227cbdd34fcdf49680945e99e8b1c057f7b637 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sat, 8 Nov 2014 17:25:37 -0500 Subject: Rename makefile --- Makefile | 24 ++++++++++++++++++++++++ makefile.make | 11 ----------- 2 files changed, 24 insertions(+), 11 deletions(-) create mode 100644 Makefile delete mode 100644 makefile.make diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e812d5b --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +# This makefile runs the tests as Travis runs them. Be sure to test +# locally before you push if you are under the impression that the +# patch should work. This will cut down on the number of commits in +# the repository that, essentially, patch patches. +# +# To test Emacs 24.1, for example, use +# +# make 24.1 +# +# To test on all versions, of course, simply use +# +# make +# +# or +# +# make all +# + +all: 24.1 24.2 24.3 + +%: + evm install emacs-$@-bin || true + emacs --version + emacs --batch -L . -l ert -l test/tests.el -f ert-run-tests-batch-and-exit diff --git a/makefile.make b/makefile.make deleted file mode 100644 index f76bc70..0000000 --- a/makefile.make +++ /dev/null @@ -1,11 +0,0 @@ -# This makefile runs the tests as Travis runs them. Be sure to test -# locally before you push if you are under the impression that the -# patch should work. This will cut down on the number of commits in -# the repository that, essentially, patch patches. - -all: 24.1 24.2 24.3 - -%: - evm install emacs-$@-bin || true - emacs --version - emacs --batch -L . -l ert -l test/tests.el -f ert-run-tests-batch-and-exit -- cgit v1.2.3 From 4207f8a4764ad3a0bca402160a24cca714cf0cde Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sat, 8 Nov 2014 18:02:36 -0500 Subject: Consolidate keywords, update emails --- stack-exchange.el | 30 ++++++++++++++++++++++++++++++ sx-cache.el | 3 +-- sx-encoding.el | 3 +-- sx-request.el | 3 +-- sx-time.el | 3 +-- 5 files changed, 34 insertions(+), 8 deletions(-) create mode 100644 stack-exchange.el diff --git a/stack-exchange.el b/stack-exchange.el new file mode 100644 index 0000000..bca777b --- /dev/null +++ b/stack-exchange.el @@ -0,0 +1,30 @@ +;;; stack-exchange.el --- A StackExchange Mode -*- lexical-binding: t; -*- + +;; Copyright (C) 2014 Sean Allred + +;; Author: Sean Allred +;; Keywords: help, hypermedia, mail, news, tools + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; + +;;; Code: + +(mapc #'load (file-expand-wildcards "sx*.el")) + +(provide 'stack-exchange) +;;; stack-exchange.el ends here diff --git a/sx-cache.el b/sx-cache.el index a090982..ed4895a 100644 --- a/sx-cache.el +++ b/sx-cache.el @@ -2,8 +2,7 @@ ;; Copyright (C) 2014 Sean Allred -;; Author: Sean Allred -;; Keywords: help +;; Author: Sean Allred ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by diff --git a/sx-encoding.el b/sx-encoding.el index efb333e..8ac12ca 100644 --- a/sx-encoding.el +++ b/sx-encoding.el @@ -2,8 +2,7 @@ ;; Copyright (C) 2014 Sean Allred -;; Author: Sean Allred -;; Keywords: help +;; Author: Sean Allred ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by diff --git a/sx-request.el b/sx-request.el index a62ee0e..e9f52f3 100644 --- a/sx-request.el +++ b/sx-request.el @@ -2,8 +2,7 @@ ;; Copyright (C) 2014 Sean Allred -;; Author: Sean Allred -;; Keywords: +;; Author: Sean Allred ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by diff --git a/sx-time.el b/sx-time.el index 1cea76f..1c8e353 100644 --- a/sx-time.el +++ b/sx-time.el @@ -2,8 +2,7 @@ ;; Copyright (C) 2014 Sean Allred -;; Author: Sean Allred -;; Keywords: help +;; Author: Sean Allred ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by -- cgit v1.2.3 From 19f077473848b498841efb5ea7e698a95dec6663 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sat, 8 Nov 2014 19:40:43 -0500 Subject: Update comments --- sx-method.el | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sx-method.el b/sx-method.el index f69c381..6f0a36b 100644 --- a/sx-method.el +++ b/sx-method.el @@ -1,8 +1,8 @@ -;;; sx-request.el --- requests for stack-mode +;;; sx-method.el --- method calls ;; Copyright (C) 2014 Sean Allred -;; Author: Sean Allred +;; Author: Sean Allred ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by @@ -44,4 +44,4 @@ Return the entire response as a complex alist." keyword-arguments))) (provide 'sx-method) -;;; sx-request.el ends here +;;; sx-method.el ends here -- cgit v1.2.3 From 558d34708b4b8db7aea84ea8dc0c353892fbf13c Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sun, 9 Nov 2014 11:08:39 -0500 Subject: Add 24.4 support Also tests rejeep/evm#48 --- .travis.yml | 1 + Makefile | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 63f0cb6..ae882b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ env: - EVM_EMACS=emacs-24.1-bin - EVM_EMACS=emacs-24.2-bin - EVM_EMACS=emacs-24.3-bin + - EVM_EMACS=emacs-24.4-bin before_install: - sudo mkdir /usr/local/evm diff --git a/Makefile b/Makefile index e812d5b..b322342 100644 --- a/Makefile +++ b/Makefile @@ -16,9 +16,9 @@ # make all # -all: 24.1 24.2 24.3 +all: 24.1 24.2 24.3 24.4 -%: +.DEFAULT: evm install emacs-$@-bin || true emacs --version emacs --batch -L . -l ert -l test/tests.el -f ert-run-tests-batch-and-exit -- cgit v1.2.3 From 54cfa773584fe0d9828a58d9d09a19959515e21b Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sun, 9 Nov 2014 11:24:19 -0500 Subject: Avoid .DEFAULT rule in Makefile Instead, use a proper 'group' rule using the `VERSIONS' variable. Also, the use of `::' signals that the order doesn't matter at all; the recipes will be executed in the order they appear in the Makefile. --- Makefile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index b322342..1ca10c0 100644 --- a/Makefile +++ b/Makefile @@ -16,9 +16,11 @@ # make all # -all: 24.1 24.2 24.3 24.4 +VERSIONS = 24.1 24.2 24.3 24.4 -.DEFAULT: +all :: $(VERSIONS) + +$(VERSIONS) :: evm install emacs-$@-bin || true emacs --version emacs --batch -L . -l ert -l test/tests.el -f ert-run-tests-batch-and-exit -- cgit v1.2.3 From 3a1645bd1f446ff6cfef4eb901d05138ea9232bf Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sun, 9 Nov 2014 15:44:27 -0500 Subject: Compress Makefile usage Instead of using `make 24.1', use `make 1'. Also, skip install if it is present already. --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 1ca10c0..d24eccd 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ # # To test Emacs 24.1, for example, use # -# make 24.1 +# make 1 # # To test on all versions, of course, simply use # @@ -16,11 +16,11 @@ # make all # -VERSIONS = 24.1 24.2 24.3 24.4 +VERSIONS = 1 2 3 4 all :: $(VERSIONS) $(VERSIONS) :: - evm install emacs-$@-bin || true + evm install emacs-24.$@-bin --skip || true emacs --version emacs --batch -L . -l ert -l test/tests.el -f ert-run-tests-batch-and-exit -- cgit v1.2.3 From e771f785e6f411d0444e896d79b83faf293ca714 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sun, 9 Nov 2014 15:45:42 -0500 Subject: Ensure correct version is being tested The `--use' option for `evm' seems to be ineffective on my machine. This usage is (or should be) functionally equivalent; it links `emacs' to the correct version in EVM. --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index d24eccd..7e46ced 100644 --- a/Makefile +++ b/Makefile @@ -22,5 +22,6 @@ all :: $(VERSIONS) $(VERSIONS) :: evm install emacs-24.$@-bin --skip || true + evm use emacs-24.$@-bin emacs --version emacs --batch -L . -l ert -l test/tests.el -f ert-run-tests-batch-and-exit -- cgit v1.2.3 From d66ad8cf0b375d21cc3aaf783906bfeaf66b9e11 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sun, 9 Nov 2014 15:46:55 -0500 Subject: Add rules to install evm; cask --- Makefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Makefile b/Makefile index 7e46ced..61c3b66 100644 --- a/Makefile +++ b/Makefile @@ -25,3 +25,9 @@ $(VERSIONS) :: evm use emacs-24.$@-bin emacs --version emacs --batch -L . -l ert -l test/tests.el -f ert-run-tests-batch-and-exit + +install_cask: + curl -fsSkL https://raw.github.com/cask/cask/master/go | python + +install_evm: + curl -fsSkL https://raw.github.com/rejeep/evm/master/go | bash -- cgit v1.2.3 From 1faf387be8f9feb621ad1a5ab0eb23d6cf1f5444 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sun, 9 Nov 2014 15:47:09 -0500 Subject: Ensure Emacs uses hard tabs for this file --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index 61c3b66..28058d8 100644 --- a/Makefile +++ b/Makefile @@ -31,3 +31,7 @@ install_cask: install_evm: curl -fsSkL https://raw.github.com/rejeep/evm/master/go | bash + +# Local Variables: +# indent-tabs-mode: t +# End: -- cgit v1.2.3 From a3788b8f4f1ae24c48fa2c6c85bd789e08a4293c Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sun, 9 Nov 2014 15:47:23 -0500 Subject: Ensure dependencies for this version are in place --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 28058d8..7b0b698 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,7 @@ $(VERSIONS) :: evm install emacs-24.$@-bin --skip || true evm use emacs-24.$@-bin emacs --version + cask install emacs --batch -L . -l ert -l test/tests.el -f ert-run-tests-batch-and-exit install_cask: -- cgit v1.2.3 From 302be04d826a3f09c8cd3d313592f7976fa8c0cb Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Mon, 10 Nov 2014 22:53:10 -0500 Subject: Proper GZIP testing Test for a GZIP'd using magic bytes. See the appropriate answer on Emacs.SE (1) for details. (1): http://emacs.stackexchange.com/a/2978 --- sx-encoding.el | 29 +++++++++++++++++++++++++++++ sx-request.el | 28 +++++++++++----------------- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/sx-encoding.el b/sx-encoding.el index 8ac12ca..98f3a7c 100644 --- a/sx-encoding.el +++ b/sx-encoding.el @@ -74,6 +74,35 @@ (substring ss 1)))))))) (replace-regexp-in-string "&[^; ]*;" get-function string))) +(defun sx-encoding-gzipped-p (data) + "Checks for magic bytes in DATA. + +Check if the first two bytes of a string in DATA match magic +numbers identifying the gzip file format. See [1] for the file +format description. + +http://www.gzip.org/zlib/rfc-gzip.html + +http://emacs.stackexchange.com/a/2978" + (equal (substring (string-as-unibyte data) 0 2) + (unibyte-string 31 139))) + +(defun sx-encoding-gzipped-buffer-p (filename) + "Check if the BUFFER is gzip-compressed. + +See `gzip-check-magic' for details." + (sx-encoding-gzip-check-magic (buffer-string))) + +(defun sx-encoding-gzipped-file-p (file) + "Check if the FILE is gzip-compressed. + +See `gzip-check-magic' for details." + (let ((first-two-bytes (with-temp-buffer + (set-buffer-multibyte nil) + (insert-file-contents-literally file nil 0 2) + (buffer-string)))) + (gzip-check-magic first-two-bytes))) + (provide 'sx-encoding) ;;; sx-encoding.el ends here diff --git a/sx-request.el b/sx-request.el index 9d9dca4..12d2b14 100644 --- a/sx-request.el +++ b/sx-request.el @@ -92,24 +92,18 @@ number of requests left every time it finishes a call.") (error "Response headers missing; response corrupt") (delete-region (point-min) (point)) (buffer-string)))) - (response (ignore-errors + (response-zipped-p (sx-encoding-gzipped-p data)) + (data (if (not response-zipped-p) data + (shell-command-on-region + (point-min) (point-max) + sx-request-unzip-program + nil t) + (buffer-string))) + (response (with-demoted-errors "`json' error: %S" (json-read-from-string data)))) - ;; If the response isn't nil, the response was in plain text - (unless response - ;; try to decompress the response - (setq response - (with-demoted-errors "`json-read' error: %S" - (shell-command-on-region - (point-min) (point-max) - sx-request-unzip-program - nil t) - (json-read-from-string - (buffer-substring - (point-min) (point-max))))) - ;; if it still fails, error outline - (unless response - (sx-message "Unable to parse response: %S" response) - (error "Response could not be read by `json-read-from-string'"))) + (unless (not (and (not response) (string-equal data "{}"))) + (sx-message "Unable to parse response: %S" response) + (error "Response could not be read by `json-read-from-string'")) ;; If we get here, the response is a valid data structure (sx-assoc-let response (when error_id -- cgit v1.2.3 From 2e9b78adcb7f10c6ef645db3f11e6e444acc26e3 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Mon, 10 Nov 2014 22:57:31 -0500 Subject: Include encoding functions --- sx-request.el | 1 + 1 file changed, 1 insertion(+) diff --git a/sx-request.el b/sx-request.el index 12d2b14..92c3041 100644 --- a/sx-request.el +++ b/sx-request.el @@ -27,6 +27,7 @@ (require 'json) (require 'sx) +(require 'sx-encoding) ;;; Variables -- cgit v1.2.3 From 28500694aa199c1a1bb496067ffd956bb8a9f3a9 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Tue, 11 Nov 2014 08:46:53 -0500 Subject: Use correct function name --- sx-encoding.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sx-encoding.el b/sx-encoding.el index 98f3a7c..0b72365 100644 --- a/sx-encoding.el +++ b/sx-encoding.el @@ -101,7 +101,7 @@ See `gzip-check-magic' for details." (set-buffer-multibyte nil) (insert-file-contents-literally file nil 0 2) (buffer-string)))) - (gzip-check-magic first-two-bytes))) + (sx-encoding-gzipped-p first-two-bytes))) (provide 'sx-encoding) ;;; sx-encoding.el ends here -- cgit v1.2.3 From 94e649f7318b2518fe31af7cdea74b7f7bfde1e3 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Tue, 11 Nov 2014 08:47:46 -0500 Subject: Simplify logical structure --- sx-request.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sx-request.el b/sx-request.el index 92c3041..56362fc 100644 --- a/sx-request.el +++ b/sx-request.el @@ -102,7 +102,7 @@ number of requests left every time it finishes a call.") (buffer-string))) (response (with-demoted-errors "`json' error: %S" (json-read-from-string data)))) - (unless (not (and (not response) (string-equal data "{}"))) + (when (and (not response) (string-equal data "{}")) (sx-message "Unable to parse response: %S" response) (error "Response could not be read by `json-read-from-string'")) ;; If we get here, the response is a valid data structure -- cgit v1.2.3 From 67adfdc14d882d2cc208476b0fc15929161433e6 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Wed, 12 Nov 2014 21:07:40 +0000 Subject: New assoc-let implementation. Use .title instead of title. --- sx.el | 88 +++++++++++++++++++------------------------------------------------ 1 file changed, 24 insertions(+), 64 deletions(-) diff --git a/sx.el b/sx.el index 6165714..34be85e 100644 --- a/sx.el +++ b/sx.el @@ -71,82 +71,42 @@ a string, just return it." ;;; Interpreting request data -(defvar sx--api-symbols - '( - accept_rate - answer_count - answer_id - answers - body - body_markdown - close_vote_count - comment_count - comment_id - creation_date - delete_vote_count - display_name - downvoted - edited - error_id - error_name - error_message - favorite_count - filter - items - is_accepted - is_answered - last_activity_date - last_edit_date - last_editor - link - owner - profile_image - question_id - quota_remaining - reopen_vote_count - reputation - score - tags - title - upvoted - user_id - user_type - view_count - ) - "") - -(defun sx--deep-search (symbol list) - "Non-nil if SYMBOL is contained somewhere inside LIST." +(defun sx--deep-dot-search (data) + "Find symbols somewhere inside DATA which start with a `.'. +Returns a list where each element is a cons cell. The car is the +symbol, the cdr is the symbol without the `.'." (cond - ((symbolp list) - (eq symbol list)) - ((not (listp list)) - nil) - (t - (remove nil (mapcar (lambda (x) (sx--deep-search symbol x)) list))))) + ((symbolp data) + (let ((name (symbol-name data))) + (when (string-match "\\`\\." name) + ;; Return the cons cell inside a list, so it can be appended + ;; with other results in the clause below. + (list (cons data (intern (replace-match "" name))))))) + ((not (listp data)) nil) + (t (apply + #'append + (remove nil (mapcar #'sx--deep-dot-search data)))))) (defmacro sx-assoc-let (alist &rest body) - "Execute BODY while let-binding api symbols to their values in ALIST. -Any api symbol is any symbol listed in `sx--api-symbols'. Only -those present in BODY are letbound, which leads to optimal -performance. + "Execute BODY while let-binding dotted symbols to their values in ALIST. +Dotted symbol is any symbol starting with a `.'. Only those +present in BODY are letbound, which leads to optimal performance. For instance the following code (stack-core-with-data alist - (list title body)) + (list .title .body)) is equivalent to - (let ((title (cdr (assoc 'title alist))) - (body (cdr (assoc 'body alist)))) - (list title body))" + (let ((.title (cdr (assoc 'title alist))) + (.body (cdr (assoc 'body alist)))) + (list .title .body))" (declare (indent 1) (debug t)) - (let ((symbols (cl-member-if - (lambda (x) (sx--deep-search x body)) - sx--api-symbols))) - `(let ,(mapcar (lambda (x) `(,x (cdr (assoc ',x ,alist)))) symbols) + (let ((symbol-alist (sx--deep-dot-search body))) + `(let ,(mapcar (lambda (x) `(,(car x) (cdr (assoc ',(cdr x) ,alist)))) + symbol-alist) ,@body))) (defcustom sx-init-hook nil -- cgit v1.2.3 From 08eea895f1a445156c2e1c382bf167ba6d9d4515 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Wed, 12 Nov 2014 21:19:31 +0000 Subject: Update code to new assoc-let --- sx-filter.el | 2 +- sx-question-list.el | 16 ++++++++-------- sx-request.el | 9 +++++---- sx.el | 2 +- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/sx-filter.el b/sx-filter.el index aa815a2..acd8fc1 100644 --- a/sx-filter.el +++ b/sx-filter.el @@ -54,7 +54,7 @@ or string." "filter/create" keyword-arguments))) (sx-assoc-let (elt response 0) - (url-hexify-string filter))))) + (url-hexify-string .filter))))) ;;; Storage and Retrieval diff --git a/sx-question-list.el b/sx-question-list.el index caf24b1..ebd4e97 100644 --- a/sx-question-list.el +++ b/sx-question-list.el @@ -205,26 +205,26 @@ Used in the questions list to indicate a question was updated \"4d ago\"." (list data (vector - (list (int-to-string score) - 'face (if upvoted 'sx-question-list-score-upvoted + (list (int-to-string .score) + 'face (if .upvoted 'sx-question-list-score-upvoted 'sx-question-list-score)) - (list (int-to-string answer_count) - 'face (if (sx-question--accepted-answer data) + (list (int-to-string .answer_count) + 'face (if (sx-question--accepted-answer .data) 'sx-question-list-answers-accepted 'sx-question-list-answers)) (concat (propertize - title - 'face (if (sx-question--read-p data) + .title + 'face (if (sx-question--read-p .data) 'sx-question-list-read-question ;; Increment `sx-question-list--unread-count' for the mode-line. (cl-incf sx-question-list--unread-count) 'sx-question-list-unread-question)) (propertize " " 'display "\n ") - (propertize (concat (sx-time-since last_activity_date) + (propertize (concat (sx-time-since .last_activity_date) sx-question-list-ago-string) 'face 'sx-question-list-date) - (propertize (concat " [" (mapconcat #'identity tags "] [") "]") + (propertize (concat " [" (mapconcat #'identity .tags "] [") "]") 'face 'sx-question-list-tags) (propertize " " 'display "\n")))))) diff --git a/sx-request.el b/sx-request.el index 56362fc..f8feb22 100644 --- a/sx-request.el +++ b/sx-request.el @@ -107,15 +107,16 @@ number of requests left every time it finishes a call.") (error "Response could not be read by `json-read-from-string'")) ;; If we get here, the response is a valid data structure (sx-assoc-let response - (when error_id + (when .error_id (error "Request failed: (%s) [%i %s] %S" - method error_id error_name error_message)) + .method .error_id .error_name .error_message)) (when (< (setq sx-request-remaining-api-requests - quota_remaining) + .quota_remaining) sx-request-remaining-api-requests-message-threshold) (sx-message "%d API requests reamining" sx-request-remaining-api-requests)) - items))))))) + :hi + .items))))))) ;;; Support Functions diff --git a/sx.el b/sx.el index 34be85e..7ed56d3 100644 --- a/sx.el +++ b/sx.el @@ -81,7 +81,7 @@ symbol, the cdr is the symbol without the `.'." (when (string-match "\\`\\." name) ;; Return the cons cell inside a list, so it can be appended ;; with other results in the clause below. - (list (cons data (intern (replace-match "" name))))))) + (list (cons data (intern (replace-match "" nil nil name))))))) ((not (listp data)) nil) (t (apply #'append -- cgit v1.2.3 From 9c2e07f01f41c223b07356c4bd495e6f7edd1d0f Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Wed, 12 Nov 2014 17:56:00 -0500 Subject: Remove spurious line --- sx-request.el | 1 - 1 file changed, 1 deletion(-) diff --git a/sx-request.el b/sx-request.el index f8feb22..dd98ead 100644 --- a/sx-request.el +++ b/sx-request.el @@ -115,7 +115,6 @@ number of requests left every time it finishes a call.") sx-request-remaining-api-requests-message-threshold) (sx-message "%d API requests reamining" sx-request-remaining-api-requests)) - :hi .items))))))) -- cgit v1.2.3 From 2c172707073d6a8e558d81999d0ef14b60acb0af Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Wed, 12 Nov 2014 17:58:10 -0500 Subject: Move defmacro to head of file --- test/tests.el | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/tests.el b/test/tests.el index a66394c..b48761d 100644 --- a/test/tests.el +++ b/test/tests.el @@ -20,6 +20,14 @@ (insert-file-contents file) (read (buffer-string)))))) +(defmacro line-should-match (regexp) + "" + `(let ((line (buffer-substring-no-properties + (line-beginning-position) + (line-end-position)))) + (message "Line here is: %S" line) + (should (string-match ,regexp line)))) + (setq sx-request-remaining-api-requests-message-threshold 50000 debug-on-error t @@ -89,14 +97,6 @@ ((1 . alpha) (2 . beta))] '(1 2 3))))) -(defmacro line-should-match (regexp) - "" - `(let ((line (buffer-substring-no-properties - (line-beginning-position) - (line-end-position)))) - (message "Line here is: %S" line) - (should (string-match ,regexp line)))) - (ert-deftest question-list-display () (cl-letf (((symbol-function #'sx-request-make) (lambda (&rest _) sx-test-data-questions))) -- cgit v1.2.3 From ea1a5e0e1ade29688d7db420d564e494dea7974c Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Wed, 12 Nov 2014 18:16:28 -0500 Subject: Add tests for `sx-assoc-let' --- test/tests.el | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/tests.el b/test/tests.el index b48761d..6a48257 100644 --- a/test/tests.el +++ b/test/tests.el @@ -116,3 +116,19 @@ (sx-question-list-previous 4) (line-should-match "^\\s-+2\\s-+1\\s-+"Making tag completion table" Freezes/Blocks -- how to disable [ 0-9]+[ydhms] ago\\s-+\\[autocomplete\\]"))) + +(ert-deftest macro-test--sx-assoc-let () + "Tests macro expansion for `sx-assoc-let'" + (should + (equal '(let ((.test (cdr (assoc 'test data)))) + .test) + (macroexpand + '(sx-assoc-let data + .test)))) + (should + (equal '(let ((.test-one (cdr (assoc 'test-one data))) + (.test-two (cdr (assoc 'test-two data)))) + (cons .test-one .test-two)) + (macroexpand + '(sx-assoc-let data + (cons .test-one .test-two)))))) -- cgit v1.2.3 From 034e68548e8f41eac8150de33ceb3417b47ca52d Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Wed, 12 Nov 2014 20:11:12 -0500 Subject: Add `sx-question-list-visit' Visits question under point; default binding `v' --- sx-question-list.el | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sx-question-list.el b/sx-question-list.el index ebd4e97..bdbc2f1 100644 --- a/sx-question-list.el +++ b/sx-question-list.el @@ -132,6 +132,7 @@ Letters do not insert themselves; instead, they are commands. ("j" sx-question-list-view-next) ("k" sx-question-list-view-previous) ("g" sx-question-list-refresh) + ("v" sx-question-list-visit) ([?\r] sx-question-list-display-question))) (defvar sx-question-list--current-page "Latest" @@ -193,6 +194,14 @@ a new list before redisplaying." (mapcar #'sx-question-list--print-info question-list))) (when redisplay (tabulated-list-print 'remember))) +(defun sx-question-list-visit (&optional data) + "Visits question under point (or from DATA) using `browse-url'." + (interactive) + (unless data (setq data (tabulated-list-get-id))) + (unless data (error "No question here!")) + (sx-assoc-let data + (browse-url .link))) + (defcustom sx-question-list-ago-string " ago" "String appended to descriptions of the time since something happened. Used in the questions list to indicate a question was updated \"4d ago\"." -- cgit v1.2.3