aboutsummaryrefslogtreecommitdiff
path: root/sx.el
diff options
context:
space:
mode:
Diffstat (limited to 'sx.el')
-rw-r--r--sx.el359
1 files changed, 34 insertions, 325 deletions
diff --git a/sx.el b/sx.el
index 05a50fb..9eb19bb 100644
--- a/sx.el
+++ b/sx.el
@@ -1,4 +1,4 @@
-;;; stack-core.el --- core functions for stack-mode -*- lexical-binding: t; -*-
+;;; sx.el --- core functions -*- lexical-binding: t; -*-
;; Copyright (C) 2014 Sean Allred
@@ -20,99 +20,17 @@
;;; 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.
+;; StackMode.
;;; Code:
;;; Requirements
-(require 'json)
-(require 'url)
-(require 'time-date)
-
-
-;;; Package Logging
-
-(defun stack-message (format-string &rest args)
+(defun sx-message (format-string &rest args)
"Display a message"
(message "[stack] %s" (apply #'format format-string args)))
-
-;;; 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.")
-
-(defconst stack-core-api-root
- (format "http://api.stackexchange.com/%s/" stack-core-api-version)
- "The base URL to make requests from.")
-
-(defvar stack-core-api-batch-request-separator
- ";"
- "The separator character to use when making batch requests.
-
-Do not change this unless you know what you are doing!")
-
-(defconst stack-core-api-key
- "0TE6s1tveCpP9K5r5JNDNQ(("
- "When passed, this key provides a higher request quota.")
-
-(defcustom stack-core-default-keyword-arguments-alist
- '(("filters/create")
- ("sites")
- ("questions" (site . emacs))
- (t nil))
- "Keywords to use as the default for a given method.
-
-The first element of each list is the method call the keywords
-apply to. The remaining cons cells (and they must be conses) are
-the values for each keyword.
-
-For each list, if no keywords are provided, the method's
-arguments are forced to the default as determined by the API.
-
-For each cons cell, if the cdr is `nil', then the keyword will be
-forced to the default as determined by the API.
-
-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.")
-
-(defcustom stack-core-silent-requests
- t
- "When `t', requests default to being silent.")
-
-
-;;; Keyword Arguments
-
-(defun stack-core-thing-as-string (thing)
+(defun sx--thing-as-string (thing &optional sequence-sep)
"Return a string representation of THING. If THING is already
a string, just return it."
(cond
@@ -120,248 +38,39 @@ a string, just return it."
((symbolp thing) (symbol-name thing))
((numberp thing) (number-to-string thing))
((sequencep thing)
- (mapconcat #'stack-core-thing-as-string
- thing stack-core-api-batch-request-separator))))
-
-(defun stack-core-get-default-keyword-arguments (method)
- "Gets the correct keyword arguments for METHOD."
- (let ((entry (assoc method stack-core-default-keyword-arguments-alist)))
- (cdr (or entry (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))
- "&"))
+ (mapconcat #'sx--thing-as-string
+ thing (if sequence-sep sequence-sep ";")))))
-
-;;; 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 filter silent)
- "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 ((url-automatic-caching stack-core-cache-requests)
- (url-inhibit-uncompression t)
- (silent (or silent stack-core-silent-requests))
- (call
- (stack-core-build-request
- method
- (append `((filter . ,(cond (filter filter)
- ((boundp 'stack-filter) stack-filter)))
- (key . ,stack-core-api-key))
- (if keyword-arguments keyword-arguments
- (stack-core-get-default-keyword-arguments method))))))
- ;; TODO: url-retrieve-synchronously can return nil if the call is
- ;; unsuccessful should handle this case
- (unless silent (stack-message "Request: %S" call))
- (let ((response-buffer (cond
- ((= emacs-minor-version 4)
- (url-retrieve-synchronously call silent))
- (t (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")
- (delete-region (point-min) (point))
- (buffer-string))))
- (response (ignore-errors
- (json-read-from-string data))))
- ;; if response isn't nil, the response was in plain text
- (unless response
- ;; try to decompress the response
- (setq response
- (with-demoted-errors "JSON Error: %s"
- (shell-command-on-region
- (point-min) (point-max)
- stack-core-unzip-program
- nil t)
- (json-read-from-string
- (buffer-substring
- (point-min) (point-max)))))
- ;; If it still fails, error out
- (unless response
- (stack-message "Unable to parse response")
- (stack-message "Printing response as message")
- (message "%S" response)
- (error "Response could not be read by json-read-string")))
- ;; At this point, either response is a valid data structure
- ;; or we have already thrown an error
- (when (assoc 'error_id response)
- (error "Request failed: (%s) [%i %s] %s"
- method
- (cdr (assoc 'error_id response))
- (cdr (assoc 'error_name response))
- (cdr (assoc 'error_message response))))
- (when (< (setq stack-core-remaining-api-requests
- (cdr (assoc 'quota_remaining response)))
- stack-core-remaining-api-requests-message-threshold)
- (stack-message "%d API requests remaining"
- stack-core-remaining-api-requests))
- (cdr (assoc 'items response))))))))
-
-(defun stack-core-filter-data (data desired-tree)
+(defun sx--filter-data (data desired-tree)
"Filters DATA and returns the DESIRED-TREE"
(if (vectorp data)
- (apply #'vector
- (mapcar (lambda (entry)
- (stack-core-filter-data
- entry desired-tree))
- data))
+ (apply #'vector
+ (mapcar (lambda (entry)
+ (sx--filter-data
+ entry desired-tree))
+ data))
(delq
nil
(mapcar (lambda (cons-cell)
- ;; TODO the resolution of `f' is O(2n) in the worst
- ;; case. It may be faster to implement the same
- ;; functionality as a `while' loop to stop looking the
- ;; list once it has found a match. Do speed tests.
- ;; See edfab4443ec3d376c31a38bef12d305838d3fa2e.
- (let ((f (or (memq (car cons-cell) desired-tree)
- (assoc (car cons-cell) desired-tree))))
- (when f
- (if (and (sequencep (cdr cons-cell))
- (sequencep (elt (cdr cons-cell) 0)))
- (cons (car cons-cell)
- (stack-core-filter-data
- (cdr cons-cell) (cdr f)))
- 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'.
-
-DATA will be written as returned by `prin1'."
- (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)
-
-(defvar stack-core--seconds-to-string
- ;; (LIMIT NAME VALUE)
- ;; We use an entry if the number of seconds in question is less than
- ;; LIMIT, but more than the previous entry's LIMIT.
- '((100 "s" 1)
- (6000 "m" 60.0)
- (108000 "h" 3600.0)
- (34560000 "d" 86400.0)
- (nil "y" 31557600.0))
- "Auxiliary variable used by `stack--time-since'.")
-
-(defun stack--time-since (time)
- "Convert the time interval since TIME (in seconds) to a short string."
- (let ((delay (- (time-to-seconds) time)))
- (concat
- (if (> 0 delay) "-" "")
- (if (= 0 delay) "0s"
- (setq delay (abs delay))
- (let ((sts stack-core--seconds-to-string) here)
- (while (and (car (setq here (pop sts)))
- (<= (car here) delay)))
- (concat (format "%.0f" (/ delay (car (cddr here))))
- (cadr here)))))))
-
-(defcustom stack-core-html-entities-plist
- '(Aacute "Á" aacute "á" Acirc "Â" acirc "â" acute "´" AElig "Æ" aelig "æ"
- Agrave "À" agrave "à" alefsym "ℵ" Alpha "Α" alpha "α" amp "&" and "∧"
- ang "∠" apos "'" aring "å" Aring "Å" asymp "≈" atilde "ã" Atilde "Ã"
- auml "ä" Auml "Ä" bdquo "„" Beta "Β" beta "β" brvbar "¦" bull "•"
- cap "∩" ccedil "ç" Ccedil "Ç" cedil "¸" cent "¢" Chi "Χ" chi "χ"
- circ "ˆ" clubs "♣" cong "≅" copy "©" crarr "↵" cup "∪" curren "¤"
- Dagger "‡" dagger "†" darr "↓" dArr "⇓" deg "°" Delta "Δ" delta "δ"
- diams "♦" divide "÷" eacute "é" Eacute "É" ecirc "ê" Ecirc "Ê" egrave "è"
- Egrave "È" empty "∅" emsp " " ensp " " Epsilon "Ε" epsilon "ε" equiv "≡"
- Eta "Η" eta "η" eth "ð" ETH "Ð" euml "ë" Euml "Ë" euro "€"
- exist "∃" fnof "ƒ" forall "∀" frac12 "½" frac14 "¼" frac34 "¾" frasl "⁄"
- Gamma "Γ" gamma "γ" ge "≥" gt ">" harr "↔" hArr "⇔" hearts "♥"
- hellip "…" iacute "í" Iacute "Í" icirc "î" Icirc "Î" iexcl "¡" igrave "ì"
- Igrave "Ì" image "ℑ" infin "∞" int "∫" Iota "Ι" iota "ι" iquest "¿"
- isin "∈" iuml "ï" Iuml "Ï" Kappa "Κ" kappa "κ" Lambda "Λ" lambda "λ"
- lang "〈" laquo "«" larr "←" lArr "⇐" lceil "⌈" ldquo "“" le "≤"
- lfloor "⌊" lowast "∗" loz "◊" lrm "" lsaquo "‹" lsquo "‘" lt "<"
- macr "¯" mdash "—" micro "µ" middot "·" minus "−" Mu "Μ" mu "μ"
- nabla "∇" nbsp "" ndash "–" ne "≠" ni "∋" not "¬" notin "∉"
- nsub "⊄" ntilde "ñ" Ntilde "Ñ" Nu "Ν" nu "ν" oacute "ó" Oacute "Ó"
- ocirc "ô" Ocirc "Ô" OElig "Œ" oelig "œ" ograve "ò" Ograve "Ò" oline "‾"
- omega "ω" Omega "Ω" Omicron "Ο" omicron "ο" oplus "⊕" or "∨" ordf "ª"
- ordm "º" oslash "ø" Oslash "Ø" otilde "õ" Otilde "Õ" otimes "⊗" ouml "ö"
- Ouml "Ö" para "¶" part "∂" permil "‰" perp "⊥" Phi "Φ" phi "φ"
- Pi "Π" pi "π" piv "ϖ" plusmn "±" pound "£" Prime "″" prime "′"
- prod "∏" prop "∝" Psi "Ψ" psi "ψ" quot "\"" radic "√" rang "〉"
- raquo "»" rarr "→" rArr "⇒" rceil "⌉" rdquo "”" real "ℜ" reg "®"
- rfloor "⌋" Rho "Ρ" rho "ρ" rlm "" rsaquo "›" rsquo "’" sbquo "‚"
- scaron "š" Scaron "Š" sdot "⋅" sect "§" shy "" Sigma "Σ" sigma "σ"
- sigmaf "ς" sim "∼" spades "♠" sub "⊂" sube "⊆" sum "∑" sup "⊃"
- sup1 "¹" sup2 "²" sup3 "³" supe "⊇" szlig "ß" Tau "Τ" tau "τ"
- there4 "∴" Theta "Θ" theta "θ" thetasym "ϑ" thinsp " " thorn "þ" THORN "Þ"
- tilde "˜" times "×" trade "™" uacute "ú" Uacute "Ú" uarr "↑" uArr "⇑"
- 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."
- :type '(repeat (choice symbol string))
- :group 'stack-core)
-
-(defun stack-core--decode-entities (string)
- (let* ((plist stack-core-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-int
- (substring ss 1))))))))
- (replace-regexp-in-string "&[^; ]*;" get-function string)))
-
-(provide 'stack-core)
-;;; stack-core.el ends here
+ ;; TODO the resolution of `f' is O(2n) in the worst
+ ;; case. It may be faster to implement the same
+ ;; functionality as a `while' loop to stop looking the
+ ;; list once it has found a match. Do speed tests.
+ ;; See edfab4443ec3d376c31a38bef12d305838d3fa2e.
+ (let ((f (or (memq (car cons-cell) desired-tree)
+ (assoc (car cons-cell) desired-tree))))
+ (when f
+ (if (and (sequencep (cdr cons-cell))
+ (sequencep (elt (cdr cons-cell) 0)))
+ (cons (car cons-cell)
+ (sx-filter-data
+ (cdr cons-cell) (cdr f)))
+ cons-cell))))
+ data))))
+
+(provide 'sx)
+;;; sx.el ends here
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End: