aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Allred <code@seanallred.com>2014-11-17 08:45:09 -0500
committerSean Allred <code@seanallred.com>2014-11-17 08:45:09 -0500
commit9df98091a366a3b1585ba52a21b0261b314ea8bd (patch)
tree1cef96fccf00072a0d76cfb2ad76051a59993a17
parent1a843499ae1ebc4ec56cfb27f79779293696cb2c (diff)
Documentation -- part one
Pushing this change to continue work elsewhere.
-rw-r--r--.agignore20
-rw-r--r--.gitignore2
-rw-r--r--sx-auth.el21
-rw-r--r--sx-cache.el31
-rw-r--r--sx-encoding.el77
-rw-r--r--sx-filter.el34
-rw-r--r--sx-method.el8
-rw-r--r--sx-question.el47
-rw-r--r--sx-request.el80
-rw-r--r--sx.org211
-rw-r--r--test/tests.el1
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
diff --git a/.gitignore b/.gitignore
index 2585bf5..cfaa152 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,5 @@
.dir-locals.el
/.stackmode/
/url/
+/sx.info
+/sx.texi
diff --git a/sx-auth.el b/sx-auth.el
index f32e7aa..96523f6 100644
--- a/sx-auth.el
+++ b/sx-auth.el
@@ -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. \"&quot;\") 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?
+ ;; "&#400;" 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 &quot;
- (or (plist-get plist (intern ss))
- ;; Handle things like &#39;
- (format "%c" (string-to-number
- (substring ss 1))))))))
+ (get-function
+ (lambda (s)
+ (let ((ss (substring s 1 -1)))
+ ;; Handle things like &quot;
+ (or (plist-get plist (intern ss))
+ ;; Handle things like &#39;
+ (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)))
diff --git a/sx.org b/sx.org
new file mode 100644
index 0000000..ec6c82b
--- /dev/null
+++ b/sx.org
@@ -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