my-emacs-d/elpa/sx-20160125.1601/sx-tag.el

180 lines
5.8 KiB
EmacsLisp
Raw Normal View History

2016-09-22 15:58:26 +00:00
;;; sx-tag.el --- retrieving list of tags and handling tags -*- lexical-binding: t; -*-
;; 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:
;;; Code:
(eval-when-compile
'(require 'cl-lib))
(require 'sx)
(require 'sx-method)
(require 'sx-button)
(defface sx-tag
'((t :underline nil :inherit font-lock-function-name-face))
"Face used on the question tags in the question buffer."
:group 'sx-question-mode-faces
:group 'sx-question-list-faces)
;;; Getting the list from a site
(defconst sx-tag-filter
(sx-filter-from-nil
(tag.name
tag.synonyms))
"Filter used when querying tags.")
(defun sx-tag--get-all (site &optional no-synonyms)
"Retrieve all tags for SITE.
If NO-SYNONYMS is non-nil, don't return synonyms."
(cl-reduce
(lambda (so-far tag)
(let-alist tag
(cons .name
(if no-synonyms so-far
(append .synonyms so-far)))))
(sx-method-call 'tags
:get-all t
:filter sx-tag-filter
:site site)
:initial-value nil))
(defun sx-tag--get-some-tags-containing (site string)
"Return at most 100 tags for SITE containing STRING.
Returns an array."
(sx-method-call 'tags
:auth nil
:filter sx-tag-filter
:site site
:keywords `((inname . ,string))))
(defun sx-tag--get-some-tag-names-containing (site string)
"Return at most 100 tag names for SITE containing STRING.
Returns a list."
(mapcar (lambda (x) (cdr (assoc 'name x)))
(sx-tag--get-some-tags-containing site string)))
;;; Getting tags from our data branch. Without the API.
;;;; @TODO: Once the cache is finished, this can probably be made into
;;;; a cache variasble with 1 day expiration time.
(defvar sx-tag-list-alist nil
"Alist where the tag list for each site is stored.
Elements are of the type (SITE . TAG-LIST).")
(defun sx-tag-list--get (site)
"Retrieve all tags from SITE in a single request.
This does not access the API. Instead, it uses
`sx-request-get-data', which accesses SX's tag cache."
(or (cdr (assoc site sx-tag-list-alist))
(let ((list (sx-request-get-data (concat "tags/" site))))
(push (cons site list) sx-tag-list-alist)
list)))
;;; Check tag validity
(defun sx-tag--invalid-name-p (site tags)
"Nil if TAGS exist in SITE.
TAGS can be a string (the tag name) or a list of strings.
Fails if TAGS is a list with more than 100 items.
Return the list of invalid tags in TAGS."
(and (listp tags) (> (length tags) 100)
(error "Invalid argument. TAG has more than 100 items"))
(let ((result
(mapcar
(lambda (x) (cdr (assoc 'name x)))
(sx-method-call 'tags
:id (sx--thing-as-string tags)
:submethod 'info
:auth nil
:filter sx-tag-filter
:site site))))
(cl-remove-if (lambda (x) (member x result)) tags)))
;;; Prompt the user for tags.
(defvar sx-tag-history nil
"Tags history for interactive prompts.")
;;; @TODO: Make it so that hitting BACKSPACE with an empty input
;;; deletes a previously submitted tag.
(defun sx-tag-multiple-read (site prompt &optional initial-value)
"Interactively read a list of tags for SITE.
Call `sx-completing-read' multiple times, until input is empty,
with completion options given by the tag list of SITE.
Return a list of tags given by the user.
PROMPT is a string displayed to the user and should not end with
a space nor a colon. INITIAL-VALUE is a list of already-selected
tags."
(let ((completion-list (sx-tag-list--get site))
(list (reverse initial-value))
(empty-string
(propertize "--\x000-some-string-representing-empty-\x000--"
'display "DONE"))
input)
(while (not (string=
empty-string
(setq input (sx-completing-read
(concat prompt " ["
(mapconcat #'identity (reverse list) ",")
"]: ")
completion-list
nil 'require-match nil 'sx-tag-history
empty-string))))
(push input list))
(reverse list)))
;;; Printing
(defun sx-tag--format (tag &optional meta)
"Format and return TAG for display.
If META is non-nil, the tag is for the meta site."
(with-temp-buffer
(sx-tag--insert tag meta)
(buffer-string)))
(defun sx-tag--insert (tag &optional meta)
"Insert TAG button.
If META is non-nil, the tag is for the meta site."
(insert-text-button (concat "[" tag "]")
'sx-button-copy tag
'sx-tag tag
'sx-tag-meta meta
:type 'sx-button-tag))
(defun sx-tag--format-tags (tags &optional site)
"Format and concatenate a sequence of TAGS.
Returns a string of all tags in TAGS, separated by a space.
SITE is the site to which the tags refer, it is only used to
decide whether they are main or meta tags. SITE can also be t or
nil, which respectively indicate meta and main."
(let ((is-meta
(if (stringp site) (string-match (rx string-start "meta.") site)
site)))
(mapconcat (lambda (tag) (sx-tag--format tag is-meta))
tags " ")))
(provide 'sx-tag)
;;; sx-tag.el ends here
;; Local Variables:
;; indent-tabs-mode: nil
;; End: