-
Notifications
You must be signed in to change notification settings - Fork 40
/
sx-tag.el
179 lines (149 loc) · 5.82 KB
/
sx-tag.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
;;; 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: