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

180 lines
5.8 KiB
EmacsLisp
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;; 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: