aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Allred <code@seanallred.com>2014-11-02 18:25:41 -0500
committerSean Allred <code@seanallred.com>2014-11-02 18:25:41 -0500
commit0ecf1dcfb01be97e0d98416dc8e0e2bc1664daeb (patch)
tree04741375a35e87c25a5e3da91c153e15c483b2c6
parent6766f1175ac3fad1a2928ff1f798e9c11caf465d (diff)
parente089914f96caf5e1a74acb75995634c34455a290 (diff)
Merge pull request #6 from vermiculus/precompiled-filters
Precompiled filters
-rw-r--r--.gitignore3
-rw-r--r--stack-core.el32
-rw-r--r--stack-filter.el62
-rw-r--r--stack-question.el5
-rw-r--r--test/tests.el52
5 files changed, 123 insertions, 31 deletions
diff --git a/.gitignore b/.gitignore
index 4acb5ef..2585bf5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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))))