diff options
author | Sean Allred <code@seanallred.com> | 2014-10-30 14:44:05 -0400 |
---|---|---|
committer | Sean Allred <code@seanallred.com> | 2014-10-30 14:44:05 -0400 |
commit | ed39aa60f9feca9cbfba29cdd3bb606f02639c7a (patch) | |
tree | b4d046cf66ab56cbebe0b8bed0b5704309d3a6f5 /stack-core.el |
Initial commit
Well, not really. If you would like the old version history, please get
in touch with me at code@seanallred.com. It's a wreck though and
contains inconsistent information.
Diffstat (limited to 'stack-core.el')
-rw-r--r-- | stack-core.el | 180 |
1 files changed, 180 insertions, 0 deletions
diff --git a/stack-core.el b/stack-core.el new file mode 100644 index 0000000..2cdf63c --- /dev/null +++ b/stack-core.el @@ -0,0 +1,180 @@ +;;; stack-core.el --- core functions for stack-mode -*- lexical-binding: t; -*- + +;; Copyright (C) 2014 Sean Allred + +;; Author: Sean Allred <code@seanallred.com> +;; 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 <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; This file defines basic commands used by all other parts of +;; StackMode. Currently, there are sections that are pretty wildly +;; different from each other (e.g. `Filters' and `Questions'. These +;; will eventually be migrated to their own files with related functions +;; of their ilk -- for now, it is more convenient to keep them handy. + +;;; Code: + + +;;; Requirements +(require 'json) +(require 'url) + + +;;; Package Logging + +(defun stack-message (format-string &rest args) + (message "[stack] %s" (apply #'format format-string args))) + + +;;; Constants and Customizable Options + +(defconst stack-core-api-version + "2.2" + "The current version of the API.") + +(defconst stack-core-api-root + (format "http://api.stackexchange.com/%s/" stack-core-api-version) + "The base URL to make requests from.") + +(defcustom stack-core-default-keyword-arguments-alist + '(("filters/create" . nil) + (t . ((site . emacs)))) + "An alist of keywords to use as the default for a given method. +This collection is in itself an alist; the key is the method and +the value is an alist of the default arguments for this method. +The value for `t' is the default-default... a super-default, if +you will. + +See `stack-core-get-default-keyword-arguments' and +`stack-core-build-keyword-arguments'. +") + +(defcustom stack-core-cache-requests + t + "Cache requests made to the StackExchange API.") + +(defcustom stack-core-unzip-program + "gunzip" + "program used to unzip the response") + +(defvar stack-core-remaining-api-requests + nil + "The number of API requests remaining according to the most +recent call. Set by `stack-core-make-request'.") + +(defcustom stack-core-remaining-api-requests-message-threshold + 50 + "After `stack-core-remaining-api-requests' drops below this +number, `stack-core-make-request' will begin printing out the +number of requests left every time it finishes a call.") + + +;;; Keyword Arguments + +(defun stack-core-thing-as-string (thing) + "Return a string representation of THING. If THING is already +a string, just return it." + (cond + ((stringp thing) thing) + ((symbolp thing) (symbol-name thing)) + ((numberp thing) (number-to-string thing)))) + +(defun stack-core-get-default-keyword-arguments (method) + "Gets the correct keyword arguments for METHOD." + (let ((entry + (car + (delq + nil + (mapcar (lambda (pair) + (if (equal method (car pair)) + (cdr pair))) + stack-core-default-keyword-arguments-alist))))) + (if entry entry + (cdr (assoc t stack-core-default-keyword-arguments-alist))))) + +;;; @todo stack-core-change-default-keyword-arguments +;;; (method new-keyword-arguments) +;;; @todo stack-core-change-default-keyword-arguments-for-key +;;; (method key new-value) + +(defun stack-core-build-keyword-arguments (alist) + "Build a \"key=value&key=value&...\"-style string with the elements +of ALIST. If any value in the alist is `nil', that pair will not +be included in the return. If you wish to pass a notion of +false, use the symbol `false'. Each element is processed with +`stack-core-thing-as-string'." + (mapconcat + (lambda (pair) + (concat + (stack-core-thing-as-string (car pair)) + "=" + (stack-core-thing-as-string (cdr pair)))) + (delq nil (mapcar + (lambda (pair) + (when (cdr pair) pair)) + alist)) + "&")) + + +;;; Making Requests of StackExchange + +(defun stack-core-build-request (method keyword-arguments) + "Build the request string that will be used to process REQUEST +with the given KEYWORD-ARGUMENTS." + (let ((base (concat stack-core-api-root method)) + (args (stack-core-build-keyword-arguments keyword-arguments))) + (if (string-equal "" args) + base + (concat base "?" args)))) + +(defun stack-core-make-request (method &optional keyword-arguments) + "Make a request to the StackExchange API using METHOD and +optional KEYWORD-ARGUMENTS. If no KEYWORD-ARGUMENTS are given, +`stack-core-default-keyword-arguments-alist' is used. Return the +entire response as a complex alist." + (let ((response + (json-read-from-string + (let ((call (stack-core-build-request + method + (cons `(filter . ,stack-core-filter) + (if keyword-arguments keyword-arguments + (stack-core-get-default-keyword-arguments method))))) + (url-automatic-caching stack-core-cache-requests)) + ;; TODO: url-retrieve-synchronously can return nil if the call is + ;; unsuccessful should handle this case + (stack-message "Request: %s" call) + (with-current-buffer (url-retrieve-synchronously call) + (goto-char (point-min)) + (if (not (search-forward "\n\n" nil t)) + (error "Response corrupted") + (delete-region (point-min) (point)) + (buffer-string))))))) + (setq stack-core-remaining-api-requests + (cdr (assoc 'quota_remaining response))) + (when (< stack-core-remaining-api-requests + stack-core-remaining-api-requests-message-threshold) + (stack-message "%d API requests remaining" + stack-core-remaining-api-requests)) + response)) + +(provide 'stack-core) +;;; stack-core.el ends here + +;; Local Variables: +;; fill-column: 72 +;; End: |