diff options
-rw-r--r-- | .agignore | 20 | ||||
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | sx-auth.el | 21 | ||||
-rw-r--r-- | sx-cache.el | 31 | ||||
-rw-r--r-- | sx-encoding.el | 77 | ||||
-rw-r--r-- | sx-filter.el | 34 | ||||
-rw-r--r-- | sx-method.el | 8 | ||||
-rw-r--r-- | sx-question.el | 47 | ||||
-rw-r--r-- | sx-request.el | 80 | ||||
-rw-r--r-- | sx.org | 211 | ||||
-rw-r--r-- | test/tests.el | 1 |
11 files changed, 436 insertions, 96 deletions
diff --git a/.agignore b/.agignore new file mode 100644 index 0000000..e00db68 --- /dev/null +++ b/.agignore @@ -0,0 +1,20 @@ +# Backup files +*~ +\#*\# + +# Compiled Elisp +*.elc + +# Generated by tests +/.cask/ +/.stackmode/ +/url/ + +# User-local variables +.dir-locals.el + +# Test files +test/data-samples + +# Info files +*.info @@ -8,3 +8,5 @@ .dir-locals.el /.stackmode/ /url/ +/sx.info +/sx.texi @@ -46,7 +46,26 @@ what you are doing!") Authentication is required to read your personal data (such as notifications) and to write with the API (asking and answering -questions)." +questions). + +When this function is called, `browse-url' is used to send the +user to an authorization page managed by StackExchange. The +following privileges are requested: + +* read_inbox + use SX to manage and visit items in your inbox + +* write_acesss + write comments, ask questions, and post answers on your + behalf + +* no_expiry + do not pester you to reauthorize again + +After authorization with StackExchange, the user is then +redirected to a website managed by SX. The access token required +to use authenticated methods is included in the hash (which is +parsed and displayed prominently on the page)." (interactive) (setq sx-auth-access-token diff --git a/sx-cache.el b/sx-cache.el index e3b356b..80b6ced 100644 --- a/sx-cache.el +++ b/sx-cache.el @@ -30,10 +30,15 @@ (defcustom sx-cache-directory (expand-file-name ".stackmode" user-emacs-directory) - "Directory containined cached files and precompiled filters.") + "Directory containining cached data.") + +(defun sx-cache--ensure-sx-cache-directory-exists () + "Ensure `sx-cache-directory' exists." + (unless (file-exists-p sx-cache-directory) + (mkdir sx-cache-directory))) (defun sx-cache-get-file-name (filename) - "Expands FILENAME in the context of `sx-cache-directory'." + "Expand FILENAME in the context of `sx-cache-directory'." (expand-file-name (concat (symbol-name filename) ".el") sx-cache-directory)) @@ -41,28 +46,28 @@ (defun sx-cache-get (cache &optional form) "Return the data within CACHE. -If CACHE does not exist, evaluate FORM and set it to its return. +If CACHE does not exist, use `sx-cache-set' to set CACHE to the +result of evaluating FORM. -As with `sx-cache-set', CACHE is a file name within the -context of `sx-cache-directory'." - (unless (file-exists-p sx-cache-directory) - (mkdir sx-cache-directory)) +CACHE is resolved to a file name by `sx-cache-get-file-name'." + (sx-cache--ensure-sx-cache-directory-exists) (let ((file (sx-cache-get-file-name cache))) + ;; If the file exists, return the data it contains (if (file-exists-p file) (with-temp-buffer (insert-file-contents (sx-cache-get-file-name cache)) (read (buffer-string))) + ;; Otherwise, set CACHE to the evaluation of FORM. + ;; `sx-cache-set' returns the data that CACHE was set to. (sx-cache-set cache (eval form))))) (defun sx-cache-set (cache data) - "Set the content of CACHE to DATA. + "Set the content of CACHE to DATA and save changes permanently. -As with `sx-cache-get', CACHE is a file name within the -context of `sx-cache-directory'. +DATA will be written as returned by `prin1'. -DATA will be written as returned by `prin1'." - (unless (file-exists-p sx-cache-directory) - (mkdir sx-cache-directory)) +CACHE is resolved to a file name by `sx-cache-get-file-name'." + (sx-cache--ensure-sx-cache-directory-exists) (write-region (prin1-to-string data) nil (sx-cache-get-file-name cache)) data) diff --git a/sx-encoding.el b/sx-encoding.el index 9d48e60..91d2765 100644 --- a/sx-encoding.el +++ b/sx-encoding.el @@ -19,8 +19,6 @@ ;;; Commentary: -;; - ;;; Code: (require 'cl-lib) @@ -62,28 +60,48 @@ ucirc "û" Ucirc "Û" ugrave "ù" Ugrave "Ù" uml "¨" upsih "ϒ" Upsilon "Υ" upsilon "υ" uuml "ü" Uuml "Ü" weierp "℘" Xi "Ξ" xi "ξ" yacute "ý" Yacute "Ý" yen "¥" yuml "ÿ" Yuml "Ÿ" Zeta "Ζ" zeta "ζ" zwj "" zwnj "") - "Plist of html entities to replace when displaying question titles and other text." + "Plist of HTML entities and their respective glyphs." :type '(repeat (choice symbol string)) :group 'sx) (defun sx-encoding-decode-entities (string) + "Decode HTML entities (e.g. \""\") in STRING. + +Done according to `sx-encoding-html-entities-plist'. If this +list does not contain the entity, it is assumed to be a number +and converted to a string (with `char-to-string'). + +Return the decoded string." + ;; @TODO Why are we limiting ourselves to two digits, here? + ;; "Ɛ" is perfectly valid, corresponding to "Ɛ". (Note that + ;; "Ɛ" is not in our plist.) (let* ((plist sx-encoding-html-entities-plist) - (get-function (lambda (s) (let ((ss (substring s 1 -1))) - ;; Handle things like " - (or (plist-get plist (intern ss)) - ;; Handle things like ' - (format "%c" (string-to-number - (substring ss 1)))))))) + (get-function + (lambda (s) + (let ((ss (substring s 1 -1))) + ;; Handle things like " + (or (plist-get plist (intern ss)) + ;; Handle things like ' + (char-to-string + (string-to-number + (substring ss 1)))))))) (replace-regexp-in-string "&[^; ]*;" get-function string))) (defun sx-encoding-normalize-line-endings (string) - "Normalize the line endings for STRING" + "Normalize the line endings for STRING. + +The API returns strings that use Windows-style line endings. +These are largely useless in an Emacs environment. Windows uses +\"\\r\\n\", Unix uses just \"\\n\". Deleting \"\\r\" is +sufficient for conversion." (delete ?\r string)) (defun sx-encoding-clean-content (string) - "Cleans STRING for display. + "Clean STRING for display. + Applies `sx-encoding-normalize-line-endings' and -`sx-encoding-decode-entities'." +`sx-encoding-decode-entities' (in that order) to prepare STRING +for sane display." (sx-encoding-decode-entities (sx-encoding-normalize-line-endings string))) @@ -91,17 +109,24 @@ Applies `sx-encoding-normalize-line-endings' and (defun sx-encoding-clean-content-deep (data) "Clean DATA recursively where necessary. -See `sx-encoding-clean-content'." +If DATA is a list or a vector, map this function over DATA and +return as the the same type of structure. + +If DATA is a cons cell (but not a list), use +`sx-encoding-clean-content-deep' on the `cdr' of DATA. + +If DATA is a string, return DATA after applying +`sx-encoding-clean-content'. + +Otherwise, return DATA. + +This function is highly specialized for the data structures +returned by `json-read' via `sx-request-make'. It may fail in +some cases." (if (consp data) - ;; If we're looking at a cons cell, test to see if is a list. If - ;; it is, map ourselves over the entire list. If it is not, - ;; reconstruct the cons cell using a cleaned cdr. (if (listp (cdr data)) (cl-map #'list #'sx-encoding-clean-content-deep data) (cons (car data) (sx-encoding-clean-content-deep (cdr data)))) - ;; If we're looking at an atom, clean and return if we're looking - ;; at a string, map if we're looking at a vector, and just return - ;; if we aren't looking at either. (cond ((stringp data) (sx-encoding-clean-content data)) @@ -112,26 +137,24 @@ See `sx-encoding-clean-content'." (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 +Check if the first two bytes of a string in DATA match the magic +numbers identifying the gzip file format. -http://emacs.stackexchange.com/a/2978" +See URL `http://www.gzip.org/zlib/rfc-gzip.html'." + ;; Credit: 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." +See `sx-encoding-gzipped-p'." (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." +See `sx-encoding-gzipped-p'." (let ((first-two-bytes (with-temp-buffer (set-buffer-multibyte nil) (insert-file-contents-literally file nil 0 2) diff --git a/sx-filter.el b/sx-filter.el index 90681e8..1fb2861 100644 --- a/sx-filter.el +++ b/sx-filter.el @@ -19,8 +19,6 @@ ;;; Commentary: -;; - ;;; Code: @@ -35,25 +33,32 @@ (defvar sx--filter-alist (sx-cache-get 'filter) - "") + "An alist of known filters. See `sx-filter-compile'. + +Structure: + + (((INCLUDE EXCLUDE BASE ) . \"compiled filter \") + ((INCLUDE2 EXCLUDE2 BASE2) . \"compiled filter2\") + ...)") ;;; Compilation -;;; TODO allow BASE to be a precompiled filter name +;;; @TODO allow BASE to be a precompiled filter name (defun sx-filter-compile (&optional include exclude base) "Compile INCLUDE and EXCLUDE into a filter derived from BASE. -INCLUDE and EXCLUDE must both be lists; BASE should be a symbol -or string." +INCLUDE and EXCLUDE must both be lists; BASE should be a string. + +Returns the compiled filter as a string." (let ((keyword-arguments `((include . ,(if include (sx--thing-as-string include))) (exclude . ,(if exclude (sx--thing-as-string exclude))) (base . ,(if base base))))) - (let ((response (sx-request-make - "filter/create" - keyword-arguments))) - (sx-assoc-let (elt response 0) + (let ((response (elt (sx-request-make + "filter/create" + keyword-arguments) 0))) + (sx-assoc-let response .filter)))) @@ -64,10 +69,13 @@ or string." (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 + "Return the string representation of the given filter. + +If the filter data exist together in `sx--filter-alist', that +value will be returned. Otherwise, compile INCLUDE, EXCLUDE, and +BASE into a filter with `sx-filter-compile' and push the +association onto `sx--filter-alist'." (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) diff --git a/sx-method.el b/sx-method.el index 6f0a36b..8838134 100644 --- a/sx-method.el +++ b/sx-method.el @@ -19,8 +19,6 @@ ;;; Commentary: -;; - ;;; Code: (require 'json) (require 'url) @@ -29,12 +27,12 @@ (require 'sx-filter) (defun sx-method-call - (method &optional keyword-arguments filter silent) + (method &optional keyword-arguments filter) "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. -Return the entire response as a complex alist." +See `sx-request-make' and `sx-filter-get-var'." (sx-request-make method (cons (cons 'filter diff --git a/sx-question.el b/sx-question.el index fc44bd8..0342479 100644 --- a/sx-question.el +++ b/sx-question.el @@ -19,8 +19,6 @@ ;;; Commentary: -;; - ;;; Code: @@ -42,10 +40,16 @@ answer.owner answer.body_markdown answer.comments) - (user.profile_image shallow_user.profile_image))) + (user.profile_image shallow_user.profile_image)) + "The filter applied with `sx-question-get-questions' and + `sx-question-get-question'.") (defun sx-question-get-questions (site &optional page) - "Get the page PAGE of questions from SITE." + "Get the page PAGE of questions from SITE. + +Return a list of questions, each consed with (site SITE). + +`sx-method-call' is used with `sx-question-browse-filter'." (mapcar (lambda (question) (cons (cons 'site site) question)) (sx-method-call @@ -55,7 +59,9 @@ sx-question-browse-filter))) (defun sx-question-get-question (site id) - "Get the question ID from SITE." + "Query SITE for a question ID and return it. + +If ID doesn't exist on SITE, raise an error." (let ((res (sx-method-call (format "questions/%s" id) `((site . ,site)) @@ -66,20 +72,30 @@ ;;; Question Properties + (defvar sx-question--user-read-list nil "Alist of questions read by the user. -Each element has the form (SITE . QUESTION-LIST). -And each element in QUESTION-LIST has the form (QUESTION_ID . LAST-VIEWED-DATE).") + +Each element has the form + + (SITE . QUESTION-LIST) + +where each element in QUESTION-LIST has the form + + (QUESTION_ID . LAST-VIEWED-DATE).") (defun sx-question--ensure-read-list (site) - "Ensure the `sx-question--user-read-list' has been read from cache. + "Ensure `sx-question--user-read-list' has been read from cache. + If no cache exists for it, initialize one with SITE." (unless sx-question--user-read-list (setq sx-question--user-read-list (sx-cache-get 'read-questions `(list ,site))))) (defun sx-question--read-p (question) - "Non-nil if QUESTION has been read since last updated." + "Non-nil if QUESTION has been read since last updated. + +See `sx-question--user-read-list'." (sx-assoc-let question (sx-question--ensure-read-list .site) (let ((ql (cdr (assoc .site sx-question--user-read-list)))) @@ -88,7 +104,9 @@ If no cache exists for it, initialize one with SITE." .last_activity_date))))) (defun sx-question--mark-read (question) - "Mark QUESTION as being read, until it is updated again." + "Mark QUESTION as being read until it is updated again. + +See `sx-question--user-read-list'." (sx-assoc-let question (sx-question--ensure-read-list .site) (let ((site-cell (assoc .site sx-question--user-read-list)) @@ -102,21 +120,18 @@ If no cache exists for it, initialize one with SITE." ((setq cell (assoc .question_id site-cell)) (setcdr cell .last_activity_date)) ;; Question wasn't present. - (t - (setcdr site-cell (cons q-cell (cdr site-cell))))))) - ;; This causes a small lag on `j' and `k' as the list gets large. - ;; Should we do this on a timer? + (t (setcdr site-cell (cons q-cell (cdr site-cell))))))) ;; Save the results. (sx-cache-set 'read-questions sx-question--user-read-list)) (defun sx-question--accepted-answer-id (question) - "Return accepted answer in QUESTION, or nil if none." + "Return accepted answer in QUESTION or nil if none exists." (sx-assoc-let question (and (integerp .accepted_answer_id) .accepted_answer_id))) (defun sx-question--tag-format (tag) - "Formats TAG for display" + "Formats TAG for display." (concat "[" tag "]")) (provide 'sx-question) diff --git a/sx-request.el b/sx-request.el index 6dc54e7..0434a7b 100644 --- a/sx-request.el +++ b/sx-request.el @@ -19,7 +19,30 @@ ;;; Commentary: +;; API requests are handled on three separate tiers: +;; +;; `sx-method-call': ;; +;; This is the function that should be used most often, since it +;; runs necessary checks (authentication) and provides basic +;; processing of the result for consistency. +;; +;; `sx-request-make': +;; +;; This is the fundamental function for interacting with the API. +;; It makes no provisions for 'common' usage, but it does ensure +;; data is retrieved successfully or an appropriate signal is +;; thrown. +;; +;; `url.el' and `json.el': +;; +;; The whole solution is built upon `url-retrieve-synchronously' +;; for making the request and `json-read-from-string' for parsing +;; it into a properly symbolic data structure. +;; +;; When at all possible, use ~sx-method-call~. There are specialized +;; cases for the use of ~sx-request-make~ outside of =sx-method.el=, but +;; these must be well-documented inline with the code. ;;; Code: @@ -44,26 +67,28 @@ (format "https://api.stackexchange.com/%s/" sx-request-api-version) "The base URL to make requests from.") -(defcustom sx-request-silent-p - t - "When `t', requests default to being silent.") - +;;; @TODO Shouldn't this be made moot by our caching system? (defcustom sx-request-cache-p t "Cache requests made to the StackExchange API.") (defcustom sx-request-unzip-program "gunzip" - "program used to unzip the response") + "Program used to unzip the response if it is compressed. + +This program must accept compressed data on standard input.") (defvar sx-request-remaining-api-requests nil - "The number of API requests remaining according to the most -recent call. Set by `sx-request-make'.") + "The number of API requests remaining. + +Set by `sx-request-make'.") (defcustom sx-request-remaining-api-requests-message-threshold 50 - "After `sx-request-remaining-api-requests' drops below this + "Lower bound for printed warnings of API usage limits. + +After `sx-request-remaining-api-requests' drops below this number, `sx-request-make' will begin printing out the number of requests left every time it finishes a call.") @@ -71,26 +96,38 @@ number of requests left every time it finishes a call.") ;;; Making Requests (defun sx-request-make - (method &optional args silent) + (method &optional args) + "Make a request to the API, executing METHOD with ARGS. + +You should almost certainly be using `sx-method-call' instead of +this function. + +The full call is built with `sx-request-build', prepending +`sx-request-api-key' to receive a higher quota. This call is +then resolved with `url-retrieve-synchronously' to a temporary +buffer that it returns. The headers are then stripped using a +search a blank line (\"\\n\\n\"). The main body of the response +is then tested with `sx-encoding-gzipped-buffer-p' for +compression. If it is compressed, `sx-request-unzip-program' is +called to uncompress the response. The uncompressed respons is +then read with `json-read-from-string'. +`sx-request-remaining-api-requests' is updated appropriately and +the main content of the response is returned." (let ((url-automatic-caching sx-request-cache-p) (url-inhibit-uncompression t) - (silent (or silent sx-request-silent-p)) (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)) - (url-retrieve-synchronously call silent)) - (t (url-retrieve-synchronously call))))) + (sx-message "Request: %S" call) + (let ((response-buffer (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") + (error "Headers missing; response corrupt") (delete-region (point-min) (point)) (buffer-string)))) (response-zipped-p (sx-encoding-gzipped-p data)) @@ -100,6 +137,8 @@ number of requests left every time it finishes a call.") sx-request-unzip-program nil t) (buffer-string))) + ;; @TODO should use `condition-case' here -- set + ;; RESPONSE to 'corrupt or something (response (with-demoted-errors "`json' error: %S" (json-read-from-string data)))) (when (and (not response) (string-equal data "{}")) @@ -110,8 +149,7 @@ number of requests left every time it finishes a call.") (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) + (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)) @@ -121,8 +159,10 @@ number of requests left every time it finishes a call.") ;;; 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." + "Construct METHOD to use KEYWORD-ARGUMENTS. + +The KEYWORD-ARGUMENTS are joined with KV-VALUE-SEP when it +contains a 'vector'. See `sx-request--build-keyword-arguments'." (let ((base (concat (or root sx-request-api-root) method)) (args (sx-request--build-keyword-arguments keyword-arguments kv-value-sep))) @@ -0,0 +1,211 @@ +#+MACRO: version 0.1 +#+MACRO: versiondate 16 November 2014 +#+MACRO: updated last updated {{{versiondate}}} + +#+TITLE: SX: A StackExchange Client (v{{{version}}}) +#+DATE: 16 November 2014 +#+AUTHOR: @@texinfo:@url{@@www.github.com/vermiculus/stack-mode@@texinfo:}@@ +#+LANGUAGE: en + +#+OPTIONS: ':t toc:t + +#+TEXINFO_FILENAME: sx.info +#+TEXINFO_HEADER: @syncodeindex pg cp + +#+TEXINFO_DIR_CATEGORY: Texinfo documentation system +#+TEXINFO_DIR_TITLE: SX: (StackExchange Client) +#+TEXINFO_DIR_DESC: A StackExchange client for Emacs + +#+TEXINFO_PRINTED_TITLE: SX: A StackExchange Client +#+SUBTITLE: for version {{{version}}}, last updated {{{versiondate}}} + +* Copying + :PROPERTIES: + :COPYING: t + :END: + +This manual is for SX (version {{{version}}}, {{{updated}}}), a +StackExchange client for Emacs. + +Copyright © 2014 Free Software Foundation, Inc. + +#+BEGIN_QUOTE +Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, +Version 1.3 or any later version published by the Free Software +Foundation; with no Invariant Sections, with no Front-Cover Texts, +and with no Back-Cover Texts. A copy of the license is included in +the section entitled "GNU Free Documentation License". +#+END_QUOTE + +* Introduction +SX is a StackExchange client for Emacs. This means that it supports +many of the same features that the official web interface does, but in +a way that is /specialized/ for Emacs: + +- question browsing for any site on the network +- asking, answering, and commenting +- advanced searching and filtering +- offline archiving +- inbox notifications +- ... + +All of these features are implemented in a way that makes sense with +Emacs conventions. Of course, the core convention of Emacs is +arbitrary customizability -- [[#hooks][hack away]]! + +* Basic Usage +** Authenticating +Use ~sx-auth-authenticate~. Calling this function will open up a +webpage on StackExchange that will prompt you to authorize this +application. Once you do this, StackExchange will redirect you to our +own authorization landing page. This landing page will prominately +display your access token. (This is /your/ token -- keep this +private!) Enter this token into Emacs. Emacs will set and save it to +a cache file. + +** Browsing Questions +To browse a list of questions retrieved from the site, use +~list-questions~. This pulls the first page of questions from the +current site and displays them in a list. Refresh the page with =g=, +use =n= and =p= to navigate without viewing the question, =j= and =k= +to do the same /with/ viewing the question, and =RET= to visit the +question at point. + +* List of Hooks + :PROPERTIES: + :CUSTOM_ID: hooks + :END: + +# Do not list internal hooks. + +- ~sx-init-hook~ :: Run when ~sx-initialize~ is called. + +* Developer's Reference +Creating a logically consistent piece of software requires that all +developers know the patterns established and resources made available +to them. These sections hope to do just that. +** Caching +All cached data is stored in =sx-cache-directory=. This defaults to +=~/.emacs.d/.stackmode= where =user-emacs-directory= is =~/.emacs.d=. +Each piece of cached data is stored in its own file. + +There are two main functions for interfacing with the cache system: + +- ~sx-cache-get~ :: Gets a cached variable's value, setting it if it + does not exist. This function will, as a + side-effect, ensure that the cache file exists. +- ~sx-cache-set~ :: Sets a cached variable's value. + +Each of these functions will ensure =sx-cache-directory= exists. + +** Making API Requests +API requests are handled on three separate tiers: + +- ~sx-method-call~ :: This is the function that should be used most + often, since it runs necessary checks (authentication) and + provides basic processing of the result for consistency. +- ~sx-request-make~ :: This is the fundamental function for + interacting with the API. It makes no provisions for 'common' + usage, but it does ensure data is retrieved successfully or an + appropriate signal is thrown. +- =url.el= and =json.el= :: The whole solution is built upon =url.el= + for making the request and =json.el= for parsing it into a + properly symbolic data structure. + +When at all possible, use ~sx-method-call~. There are specialized +cases for the use of ~sx-request-make~ outside of =sx-method.el=, but +these must be well-documented inline with the code. + +*** ~sx-request-make~ +Requests are made by building a parameter string of the format + +#+BEGIN_EXAMPLE + key=value&key=value +#+END_EXAMPLE + +and appending it to a root and a method: + +#+BEGIN_EXAMPLE + <root>/<method>?<parameters> +#+END_EXAMPLE + +This works fine for 'GET' requests of the API, but will not work for +'POST'. A revised request system is being created that will support +both of methods. + +** Working with Data +The API returns data in JSON format. When a method is called, the +response is enclosed in a 'wrapper' that includes various metadata: + +- remaining requests for your quota +- if the response has been truncated +- etc. + +When ~sx-request-make~ receives the response, it sets variables +related to the response (most notably the number of remaining +requests) and returns just the main content of the response. In order +to access this content, you could use a lengthy ~let~-binding or you +could use the ~sx-assoc-let~ macro. That is, + +#+BEGIN_SRC elisp + (sx-assoc-let data + (some fancy .function with a .property + in 'data)) +#+END_SRC + +expands to + +#+BEGIN_SRC elisp + (let + ((.function (cdr (assoc 'function data))) + (.property (cdr (assoc 'property data)))) + (some fancy .function with a .property + in 'data)) +#+END_SRC + +Use the following to highlight the special =.property= forms: + +#+BEGIN_SRC elisp + +#+END_SRC +* About this Document +This document is maintained in Org format. Updates to the source code +should almost always be accompanied by updates to this document. Soem +distinctions are made which may not be apparent when viewing the +document with Info. + +** Markup Conventions +Markup is used consistently as follows: + +- packages :: =package.el= +- keybinding :: =C-x C-s= (use ~kbd~ format) +- values :: =value= +- symbols :: =symbol= +- functions :: ~function~ + +To make the Info export readable, lists and source code blocks are +separated from body text with a blank line (as to start a new +paragraph). + +** Document Attributes +Attributes should be given in uppercase: + +#+BEGIN_SRC org + ,#+BEGIN_SRC elisp + (some elisp) + ,#+END_SRC +#+END_SRC + +** Source Code Blocks +The language for Emacs Lisp source code blocks should be given as +=elisp= and its content should be indented by two spaces. See +~org-edit-src-content-indentation~. + +* COMMENT Local Variables +# LocalWords: StackExchange SX inbox sx API url json inline Org +# LocalWords: Markup keybinding keybindings customizability + +# Local Variables: +# org-export-date-timestamp-format: "$B %e %Y" +# End: diff --git a/test/tests.el b/test/tests.el index 6a48257..bb23310 100644 --- a/test/tests.el +++ b/test/tests.el @@ -31,7 +31,6 @@ (setq sx-request-remaining-api-requests-message-threshold 50000 debug-on-error t - sx-request-silent-p nil user-emacs-directory "." sx-test-data-questions |