-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcde.el
executable file
·189 lines (158 loc) · 6.63 KB
/
cde.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
180
181
182
183
184
185
186
187
188
189
;;; cde.el --- Count an elided sequence -*- lexical-binding: t; -*-
;; Copyright (C) 2016 Aaron Harris
;; Author: Aaron Harris <[email protected]>
;; Keywords: convenience, local
;; Dependencies: `dash', `validate' (optional)
;; 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:
;; This module introduces a function `cde'. This function takes a
;; elided sequence of integers in string form and counts how many
;; integers that sequence represents. (The name stands for "count,
;; de-elide").
;;
;; What's an "elided sequence"? (And yes, I think that I coined the
;; term myself.) An elided sequence is a comma- and hyphen- delimited
;; string of integers, like you might see in the index of a text book
;; telling you which pages a particular term appears on. For example,
;; "1,3-7,10" is an elided sequence.
;;
;; More specifically, an elided sequence is a comma- delimited string
;; whose elements are called "ranges". A lone integer is a range, and
;; it just represents itself. Two integers separated by a hyphen are
;; also a range; this represents the set of all integers between and
;; including the two endpoints.
;;
;; If an elided sequence consists of disjoint ranges, and each range
;; is non-decreasing (that is, the first endpoint of a range is not
;; larger than the second), then `cde' returns the number of integers
;; in the union of the sets represented by those ranges. Note that it
;; does not verify these preconditions and may not give you a
;; meaningful answer if they are violated.
;;
;;
;; Extra features:
;;
;; - `cde' will accept lists, too. For instance, the elided sequence
;; "1-5,7,8-15" could be represented as the list '((1 5) 7 (8 15)).
;; This exists mainly because `cde' uses lists internally, so
;; providing list support requires no effort.
;;
;; - `cde' can interpret ranges whose endpoints are of the form "nX",
;; where n is an integer and X is an alphabetic character. If X is
;; the character "B", then this is interpreted as the integer n+p,
;; where p is the value of the variable `cde-page-size'; otherwise,
;; X is ignored and nX is treated as n.
;;
;; The idea is that a range of this form could be used to denote
;; lines on a page, where each side of the page is numbered from 1
;; to `cde-page-size'. For example, "1A-6B,13B-17B" would include
;; all lines on the front, the first six lines on the back, and
;; lines 13-17 on the back.
;;
;; - The command `cde-format' provides an interactive front-end to
;; `cde'. This command attempts to interpret the text under point
;; as an elided sequence. If it succeeds, it will wrap it in
;; parentheses (unless it's already so wrapped) and prepend the
;; result of calling `cde' on it.
;;; Code:
(require 'dash)
;;;; User Options
;;;;=============
(defgroup cde nil
"Count elided sequences."
:prefix "cde-"
:link '(emacs-commentary-link "cde")
:group 'applications)
(defcustom cde-page-size 50
"The increment size for alphabetic characters in `cde'.
See the documentation of `cde' for more information."
:type 'integer)
;;;; Subroutines
;;;;============
(defun cde--unpage (ref)
"Convert REF to an integer using `cde-page-size'.
Here REF should be a string of the form \"nX\", where n is an
integer and X is any string.
The return value is n unless X is the string \"B\", in which case
it is n plus the value of `cde-page-size'.
This function is used as a subroutine by `cde'."
(when (require 'validate nil :noerror)
(validate-variable 'cde-page-size))
(save-match-data
(string-match "\\([0-9]+\\)\\(B?\\)" ref)
(let ((n (string-to-int (match-string 1 ref)))
(x (match-string 2 ref)))
(+ n (if (equal x "B") cde-page-size 0)))))
(defun cde--list (ranges)
"Count the numbers in RANGES.
As `cde', but RANGES must be in list form."
(->> ranges
(mapcar (lambda (elt)
(if (and (consp elt) (cdr elt))
(- (cadr elt) (1- (car elt)))
1)))
(apply #'+)))
(defun cde--string (ranges)
"Count the numbers in RANGES.
As `cde', but RANGES must be in string form."
(save-match-data
(->> (split-string ranges ",")
(mapcar (lambda (range)
(->> (split-string range "-")
(mapcar #'cde--unpage))))
(cde--list))))
;;;; The main function
;;;;==================
;;;###autoload
(defun cde (ranges)
"Count the numbers in RANGES.
Here, RANGES may either be a comma-separated string of hyphenated
ranges, e.g. \"1-5,7,8-15\", or a list encoding the same
information, e.g., '((1 5) 7 (8 15)). For both of the examples above,
cde will return 14.
Additionally, the alphabetic character \"A\" and \"B\" are
interpreted as sides of a page, with the page size given by
`cde-page-size'. Thus if `cde-page-size' is 50 (the default),
then the string \"5A\" will be interpreted as the integer 5,
while the string \"5B\" will be interpreted as 55. This feature
is unavailable if RANGES is presented as a list."
(cond
((listp ranges) (cde--list ranges))
((stringp ranges) (cde--string ranges))))
;;;; Interactive front-ends
;;;;=======================
;;;###autoload
(defun cde-format (&optional verbose)
"Convert word at point with `cde'.
If the text at point looks like a suitable input for `cde',
replace it with a string of the form
N (RANGES)
where RANGES is the original word and N is the result of calling
`cde' on RANGES. The return value is the text inserted.
If the text at point is not suitable for `cde', do nothing and
return nil. Interactively, or with VERBOSE non-nil, print an
explanatory message."
(interactive "p")
(save-match-data
(cond
;; On successful match, do the replacement
((thing-at-point-looking-at
"(?\\(\\(?:[0-9]+[AB]?[,-]?\\)*[0-9]+[AB]?\\))?\\(,?\\)")
(let ((ranges (match-string 1))
(tail (match-string 2)))
(replace-match (format "%d (%s)%s" (cde ranges) ranges tail))))
;; Otherwise, maybe display a message.
(verbose
(message "Cannot use `cde' at point")
nil))))
(provide 'cde)
;;; cde.el ends here