diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | stack-core.el | 32 | ||||
-rw-r--r-- | stack-filter.el | 62 | ||||
-rw-r--r-- | stack-question.el | 5 | ||||
-rw-r--r-- | test/tests.el | 52 |
5 files changed, 123 insertions, 31 deletions
@@ -5,3 +5,6 @@ # Compiled Elisp *.elc /.cask/ +.dir-locals.el +/.stackmode/ +/url/ diff --git a/stack-core.el b/stack-core.el index 9a535cd..c2aff15 100644 --- a/stack-core.el +++ b/stack-core.el @@ -44,6 +44,10 @@ ;;; Constants and Customizable Options +(defcustom stack-cache-directory + (expand-file-name ".stackmode" user-emacs-directory) + "Directory containined cached files and precompiled filters.") + (defconst stack-core-api-version "2.2" "The current version of the API.") @@ -241,5 +245,33 @@ entire response as a complex alist." cons-cell)))) data)))) +(defun stack-cache-get-file-name (filename) + "Expands FILENAME in the context of `stack-cache-directory'." + (expand-file-name filename stack-cache-directory)) + +(defun stack-cache-get (cache) + "Return the data within CACHE. + +As with `stack-cache-set', CACHE is a file name within the +context of `stack-cache-directory'." + (unless (file-exists-p stack-cache-directory) + (mkdir stack-cache-directory)) + (let ((file (stack-cache-get-file-name cache))) + (when (file-exists-p file) + (with-temp-buffer + (insert-file-contents (stack-cache-get-file-name cache)) + (read (buffer-string)))))) + +(defun stack-cache-set (cache data) + "Set the content of CACHE to DATA. + +As with `stack-cache-get', CACHE is a file name within the +context of `stack-cache-directory'." + (unless (file-exists-p stack-cache-directory) + (mkdir stack-cache-directory)) + (write-region (prin1-to-string data) nil + (stack-cache-get-file-name cache)) + data) + (provide 'stack-core) ;;; stack-core.el ends here diff --git a/stack-filter.el b/stack-filter.el index 1525a4b..4210549 100644 --- a/stack-filter.el +++ b/stack-filter.el @@ -32,9 +32,13 @@ ;;; Customizations +(defconst stack-filter-cache-file + "filters.el") + (defvar stack-filter 'default - "The current filter. To customize the filter for the next call + "The current filter. +To customize the filter for the next call to `stack-core-make-request', let-bind this variable to the output of a call to `stack-core-compile-filter'. Be careful! If you're going to be using this new filter a lot, create a variable @@ -42,28 +46,58 @@ for it. Creation requests count against `stack-core-remaining-api-requests'!") -;;; Filter compilation +;;; Compilation +;;; TODO allow BASE to be a precompiled filter name (defun stack-filter-compile (&optional include exclude base) - "Compile a StackExchange filter including fields from INCLUDE, -excluding those from EXCLUDE, using BASE as a base filter. + "Compile INCLUDE and EXCLUDE into a filter derived from BASE. INCLUDE and EXCLUDE must both be lists; BASE should be a symbol or string." (let ((keyword-arguments - `((include . ,(if include (mapconcat - #'stack-core-thing-as-string - include ";"))) - (exclude . ,(if exclude (mapconcat - #'stack-core-thing-as-string - exclude ";"))) - (base . ,(if base base))))) + `((include . ,(if include (mapconcat + #'stack-core-thing-as-string + include ";"))) + (exclude . ,(if exclude (mapconcat + #'stack-core-thing-as-string + exclude ";"))) + (base . ,(if base base))))) (let ((response (stack-core-make-request - "filter/create" - keyword-arguments))) + "filter/create" + keyword-arguments))) (url-hexify-string (cdr (assoc 'filter - (elt response 0))))))) + (elt response 0))))))) + + +;;; Storage and Retrieval + +(defun stack-filter-get (filter) + "Retrieve named FILTER from `stack-filter-cache-file'." + (cdr (assoc filter (stack-cache-get stack-filter-cache-file)))) + +(defun stack-filter-store (name &optional filter) + "Store NAME as FILTER in `stack-filter-cache-file'. + +NAME should be a symbol and FILTER is a string as compiled by +`stack-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 (stack-cache-get stack-filter-cache-file)) + (entry (assoc name dict))) + (if entry (setcdr entry filter) + (setq dict (cons (cons name filter) dict))) + + (stack-cache-set stack-filter-cache-file dict)))) + +(defun stack-filter-store-all (name-filter-alist) + (mapc #'stack-filter-store name-filter-alist)) (provide 'stack-filter) ;;; stack-filter.el ends here diff --git a/stack-question.el b/stack-question.el index 3902a39..eb5b8a3 100644 --- a/stack-question.el +++ b/stack-question.el @@ -28,9 +28,12 @@ (require 'stack-filter) (defvar stack-question-browse-filter - (stack-filter-compile nil + (stack-filter-compile + nil '(user.profile_image shallow_user.profile_image))) +(stack-filter-store 'question-browse stack-question-browse-filter) + (defun stack-question-get-questions (site &optional page) "Get the page PAGE of questions from SITE." (stack-core-make-request diff --git a/test/tests.el b/test/tests.el index 44b0de0..e097244 100644 --- a/test/tests.el +++ b/test/tests.el @@ -5,25 +5,27 @@ (if (string-prefix-p "stack-" (symbol-name symbol)) (unintern symbol))))) +;;; Tests + (defun stack-test-sample-data (method &optional directory) - (with-current-buffer - (find-file-noselect - (concat "data-samples/" - (when directory (concat directory "/")) - method ".el")) - (eval (read (if (string-equal "" (buffer-string)) - "'no-value" - (buffer-string)))))) + (let ((file (concat "data-samples/" + (when directory (concat directory "/")) + method ".el"))) + (when (file-exists-p file) + (with-temp-buffer + (insert-file-contents file) + (read (buffer-string)))))) -(setq stack-test-data-questions - (stack-test-sample-data "questions") - stack-test-data-sites - (stack-test-sample-data "sites")) +(setq + stack-core-remaining-api-requests-message-threshold 50000 + debug-on-error t + stack-core-silent-requests nil + user-emacs-directory "." -;;; Tests - -(setq stack-core-remaining-api-requests-message-threshold 50000) -(setq debug-on-error t) + stack-test-data-questions + (stack-test-sample-data "questions") + stack-test-data-sites + (stack-test-sample-data "sites")) (require 'stack-core) (require 'stack-question) @@ -75,3 +77,21 @@ ((should-not-go)) ((1 . alpha) (2 . beta))] '(1 2 3))))) + +(ert-deftest test-filters () + (let ((stack-cache-directory (make-temp-file "stack-test" t))) + (should-error (stack-filter-store "names must be symbols" + "this is a filter")) + ;; basic use + (should (equal '((test . "filter")) + (stack-filter-store 'test "filter"))) + ;; aggregation + (should (equal '((test2 . "filter2") (test . "filter")) + (stack-filter-store 'test2 "filter2"))) + ;; mutation + (should (equal '((test2 . "filter2") (test . "filter-test")) + (stack-filter-store 'test "filter-test"))) + ;; clean up (note: the file should exist) + (delete-file + (stack-cache-get-file-name + stack-filter-cache-file)))) |