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