From ed39aa60f9feca9cbfba29cdd3bb606f02639c7a Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Thu, 30 Oct 2014 14:44:05 -0400 Subject: 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. --- .gitignore | 6 + README.org | 91 ++++++++++++++ resources/emacs.svg | 296 ++++++++++++++++++++++++++++++++++++++++++++ resources/stackexchange.svg | 185 +++++++++++++++++++++++++++ stack-core.el | 180 +++++++++++++++++++++++++++ stack-filter.el | 73 +++++++++++ stack-question.el | 38 ++++++ tests.el | 6 + 8 files changed, 875 insertions(+) create mode 100644 .gitignore create mode 100644 README.org create mode 100644 resources/emacs.svg create mode 100644 resources/stackexchange.svg create mode 100644 stack-core.el create mode 100644 stack-filter.el create mode 100644 stack-question.el create mode 100644 tests.el diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9cd819c --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# Emacs backup files +*~ +\#*\# + +# Compiled Elisp +*.elc diff --git a/README.org b/README.org new file mode 100644 index 0000000..cbd2c36 --- /dev/null +++ b/README.org @@ -0,0 +1,91 @@ +#+Title: Stack-Mode for Emacs +#+Author: Sean Allred +#+Date: [2014-10-30 Thu] + +=stack-mode= hopes to be a fully-featured Stack Exchange mode for +Emacs 24+ using version 2.2 (and subsequent versions as available) of +the Stack Exchange API. + +This README will eventually hold a high-level feature list and details +on installation and configuration. +* Contributing +Please do, but note that the repository history is likely to be +rebased on a single commit once v0.1 is reached. Things at this stage +are volatile and highly experimental, so the codebase changes quickly +and sometimes arbitrarily. + +The best way to contribute right now is via discussion. Open an issue +describing a feature you'd like (That isn't already listed) or a +package you think would come in handy. +** Wishlist +- Proper unit tests :: Nothing too in-depth -- just a start using 'the + right way' would be /very/ nice +- Automatic testing system (usable with Travis CI) :: Along with + unit-testing, it would be best to have this done in Travis CI so + that pull requests can be examined before merge. +- OAuth :: Without this, we can't write with the API. This is + obviously something we want to do, but I don't understand + even /regular/ OAuth (web server -- web server). If + someone can take care of this, I'd be forever grateful. +* Planned Features +** Network Exploration +#+BEGIN_EXAMPLE + Logged in as Sean + + 14 unread inbox items + 1 unread notification + + Favorites............................................................. + Emacs emacs + TeX, LaTeX, and Friends tex + StackOverflow stackoverflow + StackApps stackapps + Mathematics math + + Other Sites........................................................... + +#+END_EXAMPLE +** Question Viewing +It's not terribly clear how this whould be approached, but I have in +the past assumed using an Org-mode-like buffer (with special bindings) +that includes the question and all answers and comments. This is +likely to evolve as the project better understands what it is. + +Whatever the format, the user will be able to archive the question for +offline reference. +** Question Browsing +If point is on a question: +#+BEGIN_EXAMPLE + Full title: AUCTeX, preview-latex, and Ghostscript (Emacs) + Asker: vermiculus (572) + Answers: 1 (Not Accepted) Active: [2013-02-27 Wed 15:44] + Tags: emacs auctex preview ghostscript + Bounty: 50 +#+END_EXAMPLE +Else: +#+BEGIN_EXAMPLE + Site: TeX, LaTeX, and Friends + Users: 400 + Unanswered: 15 (0.003) +#+END_EXAMPLE +The information displays (and its format) will be customizable using +format strings and special markers. +** Question Asking +** Commenting +With a pop-up buffer similar to =twittering-mode=. +** Not Planned +The following features are not currently planned for this mode, +regardless of API support. This is mostly because I have no idea what +I'm doing. +- chat +* Resources +- [[https://api.stackexchange.com/docs][SX.API v2.2]] +- [[http://stackapps.com/apps/oauth/register][StackApps Registration Page]] +- [[http://www.emacswiki.org/emacs/ModeTutorial][Creating Major Modes for Emacs]] +** Icon +Stack Exchange Mode for Emacs has no explicit use for an icon, +although standard SVG files have been gathered in =resources/= if +anyone would fancy a crack at it. + +- [[file:resources/emacs.svg][Emacs icon]] +- [[file:resources/stackexchange.svg][Stack Exchange icon]] diff --git a/resources/emacs.svg b/resources/emacs.svg new file mode 100644 index 0000000..835a48f --- /dev/null +++ b/resources/emacs.svg @@ -0,0 +1,296 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/stackexchange.svg b/resources/stackexchange.svg new file mode 100644 index 0000000..99470d6 --- /dev/null +++ b/resources/stackexchange.svg @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + 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 +;; 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 . + +;;; 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: diff --git a/stack-filter.el b/stack-filter.el new file mode 100644 index 0000000..e5d49d3 --- /dev/null +++ b/stack-filter.el @@ -0,0 +1,73 @@ +;;; stack-filter.el --- filters for stack-mode -*- lexical-binding: t; -*- + +;; Copyright (C) 2014 Sean Allred + +;; Author: Sean Allred +;; 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 . + +;;; Commentary: + +;; + +;;; Code: + + +;;; Dependencies + +(if (boundp 'production) + (require 'stack-core) + (setq load-path (cons "." load-path)) + (load "stack-core.el")) + + +;;; Customizations + +(defcustom stack-core-filter + (stack-core-compile-filter + nil ;don't include anything extra + '(user.profile_image ;don't include pictures (yet) + shallow_user.profile_image) ; + 'withbody) ;we want the body! + "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 +for it. Creation requests count against +`stack-core-remaining-api-requests'!") + + +;;; Filter compilation + +(defun stack-core-compile-filter (&optional include exclude base) + "Compile a StackExchange filter including fields from INCLUDE, +excluding those from EXCLUDE, using BASE as a base filter. + +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))))) + (let ((response (stack-core-make-request "filter/create" keyword-arguments))) + (url-hexify-string + (cdr (assoc 'filter (elt (cdr (assoc 'items response)) 0))))))) + +(provide 'stack-filter) +;;; stack-filter.el ends here diff --git a/stack-question.el b/stack-question.el new file mode 100644 index 0000000..83247d8 --- /dev/null +++ b/stack-question.el @@ -0,0 +1,38 @@ +;;; stack-question.el --- question logic for stack-mode -*- lexical-binding: t; -*- + +;; Copyright (C) 2014 Sean Allred + +;; Author: Sean Allred +;; 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 . + +;;; Commentary: + +;; + +;;; Code: + +(if (boundp 'production) + (require 'stack-core) + (setq load-path (cons "." load-path)) + (load "stack-core.el")) + +(defun stack-question-parse (data) + "Parse and return the questions from DATA as returned by +`stack-core-make-request'" + (cdr (assoc 'items data))) + +(provide 'stack-question) +;;; stack-question.el ends here diff --git a/tests.el b/tests.el new file mode 100644 index 0000000..57cd42b --- /dev/null +++ b/tests.el @@ -0,0 +1,6 @@ +;;; Tests + +(setq *t (stack-core-make-request "questions")) + +(prog1 t (prin1 (elt (stack-core-parse-questions *t) 0) + #'insert)) -- cgit v1.2.3