diff options
| author | Lars Ingebrigtsen | 2020-12-21 18:53:32 +0100 |
|---|---|---|
| committer | Lars Ingebrigtsen | 2020-12-21 18:53:40 +0100 |
| commit | 87e422f1044068a4d27e5e4bfdbc664d9e4bbc43 (patch) | |
| tree | 0a383378932cbe4b51a33299df2f3cbf9abaac20 | |
| parent | e352abeac16725c226c1246e3c83f71b8d3fa689 (diff) | |
| download | emacs-87e422f1044068a4d27e5e4bfdbc664d9e4bbc43.tar.gz emacs-87e422f1044068a4d27e5e4bfdbc664d9e4bbc43.zip | |
Beef up the Emacs string utility set a bit
* doc/lispref/strings.texi (Modifying Strings): Document them.
* lisp/emacs-lisp/shortdoc.el (string): Add examples.
* lisp/emacs-lisp/subr-x.el (string-clean-whitespace)
(string-fill, string-limit, string-lines, slice-string): New
functions.
| -rw-r--r-- | doc/lispref/strings.texi | 38 | ||||
| -rw-r--r-- | etc/NEWS | 5 | ||||
| -rw-r--r-- | lisp/emacs-lisp/shortdoc.el | 15 | ||||
| -rw-r--r-- | lisp/emacs-lisp/subr-x.el | 53 | ||||
| -rw-r--r-- | test/lisp/emacs-lisp/subr-x-tests.el | 26 |
5 files changed, 137 insertions, 0 deletions
diff --git a/doc/lispref/strings.texi b/doc/lispref/strings.texi index 0f157c39d63..e4ca2617512 100644 --- a/doc/lispref/strings.texi +++ b/doc/lispref/strings.texi | |||
| @@ -381,6 +381,44 @@ The default value of @var{separators} for @code{split-string}. Its | |||
| 381 | usual value is @w{@code{"[ \f\t\n\r\v]+"}}. | 381 | usual value is @w{@code{"[ \f\t\n\r\v]+"}}. |
| 382 | @end defvar | 382 | @end defvar |
| 383 | 383 | ||
| 384 | @defun slice-string string regexp | ||
| 385 | Split @var{string} into a list of strings on @var{regexp} boundaries. | ||
| 386 | As opposed to @code{split-string}, the boundaries are included in the | ||
| 387 | result set: | ||
| 388 | |||
| 389 | @example | ||
| 390 | (slice-string " two words " " +") | ||
| 391 | @result{} (" two" " words" " ") | ||
| 392 | @end example | ||
| 393 | @end defun | ||
| 394 | |||
| 395 | @defun string-clean-whitespace string | ||
| 396 | Clean up the whitespace in @var{string} by collapsing stretches of | ||
| 397 | whitespace to a single space character, as well as removing all | ||
| 398 | whitespace from the start and the end of @var{string}. | ||
| 399 | @end defun | ||
| 400 | |||
| 401 | @defun string-fill string length | ||
| 402 | Attempt to Word-wrap @var{string} so that no lines are longer than | ||
| 403 | @var{length}. Filling is done on whitespace boundaries only. If | ||
| 404 | there are individual words that are longer than @var{length}, these | ||
| 405 | will not be shortened. | ||
| 406 | @end defun | ||
| 407 | |||
| 408 | @defun string-limit string length | ||
| 409 | Return a string that's shorter than @var{length}. If @var{string} is | ||
| 410 | shorter than @var{length}, @var{string} is returned as is. If | ||
| 411 | @var{length} is positive, return a substring of @var{string} | ||
| 412 | consisting of the first @var{length} characters. If @var{length} is | ||
| 413 | negative, return a string of the @var{-length} last characters | ||
| 414 | instead. | ||
| 415 | @end defun | ||
| 416 | |||
| 417 | @defun string-lines string &optional omit-nulls | ||
| 418 | Split @var{string} into a list of strings on newline boundaries. If | ||
| 419 | @var{omit-nulls}, remove empty lines from the results. | ||
| 420 | @end defun | ||
| 421 | |||
| 384 | @node Modifying Strings | 422 | @node Modifying Strings |
| 385 | @section Modifying Strings | 423 | @section Modifying Strings |
| 386 | @cindex modifying strings | 424 | @cindex modifying strings |
| @@ -1441,6 +1441,11 @@ that makes it a valid button. | |||
| 1441 | ** Miscellaneous | 1441 | ** Miscellaneous |
| 1442 | 1442 | ||
| 1443 | +++ | 1443 | +++ |
| 1444 | *** A number of new string manipulation functions have been added. | ||
| 1445 | 'string-clean-whitespace', 'string-fill', 'string-limit', | ||
| 1446 | 'string-limit' and 'slice-string'. | ||
| 1447 | |||
| 1448 | +++ | ||
| 1444 | *** New variable 'current-minibuffer-command'. | 1449 | *** New variable 'current-minibuffer-command'. |
| 1445 | This is like 'this-command', but it is bound recursively when entering | 1450 | This is like 'this-command', but it is bound recursively when entering |
| 1446 | the minibuffer. | 1451 | the minibuffer. |
diff --git a/lisp/emacs-lisp/shortdoc.el b/lisp/emacs-lisp/shortdoc.el index 37d6170fee5..8b11b57ff7f 100644 --- a/lisp/emacs-lisp/shortdoc.el +++ b/lisp/emacs-lisp/shortdoc.el | |||
| @@ -139,10 +139,20 @@ There can be any number of :example/:result elements." | |||
| 139 | (substring | 139 | (substring |
| 140 | :eval (substring "foobar" 0 3) | 140 | :eval (substring "foobar" 0 3) |
| 141 | :eval (substring "foobar" 3)) | 141 | :eval (substring "foobar" 3)) |
| 142 | (string-limit | ||
| 143 | :eval (string-limit "foobar" 3) | ||
| 144 | :eval (string-limit "foobar" -3) | ||
| 145 | :eval (string-limit "foobar" 10)) | ||
| 142 | (split-string | 146 | (split-string |
| 143 | :eval (split-string "foo bar") | 147 | :eval (split-string "foo bar") |
| 144 | :eval (split-string "|foo|bar|" "|") | 148 | :eval (split-string "|foo|bar|" "|") |
| 145 | :eval (split-string "|foo|bar|" "|" t)) | 149 | :eval (split-string "|foo|bar|" "|" t)) |
| 150 | (slice-string | ||
| 151 | :eval (slice-string "foo-bar" "-") | ||
| 152 | :eval (slice-string "foo-bar--zot-" "-+")) | ||
| 153 | (string-lines | ||
| 154 | :eval (string-lines "foo\n\nbar") | ||
| 155 | :eval (string-lines "foo\n\nbar" t)) | ||
| 146 | (string-replace | 156 | (string-replace |
| 147 | :eval (string-replace "foo" "bar" "foozot")) | 157 | :eval (string-replace "foo" "bar" "foozot")) |
| 148 | (replace-regexp-in-string | 158 | (replace-regexp-in-string |
| @@ -167,6 +177,11 @@ There can be any number of :example/:result elements." | |||
| 167 | (string-remove-prefix | 177 | (string-remove-prefix |
| 168 | :no-manual t | 178 | :no-manual t |
| 169 | :eval (string-remove-prefix "foo" "foobar")) | 179 | :eval (string-remove-prefix "foo" "foobar")) |
| 180 | (string-clean-whitespace | ||
| 181 | :eval (string-clean-whitespace " foo bar ")) | ||
| 182 | (string-fill | ||
| 183 | :eval (string-fill "Three short words" 12) | ||
| 184 | :eval (string-fill "Long-word" 3)) | ||
| 170 | (reverse | 185 | (reverse |
| 171 | :eval (reverse "foo")) | 186 | :eval (reverse "foo")) |
| 172 | (substring-no-properties | 187 | (substring-no-properties |
diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index e6abb39ddc6..41a20795378 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el | |||
| @@ -264,6 +264,59 @@ carriage return." | |||
| 264 | (substring string 0 (- (length string) (length suffix))) | 264 | (substring string 0 (- (length string) (length suffix))) |
| 265 | string)) | 265 | string)) |
| 266 | 266 | ||
| 267 | (defun string-clean-whitespace (string) | ||
| 268 | "Clean up whitespace in STRING. | ||
| 269 | All sequences of whitespaces in STRING are collapsed into a | ||
| 270 | single space character, and leading/trailing whitespace is | ||
| 271 | removed." | ||
| 272 | (string-trim (replace-regexp-in-string "[ \t\n\r]+" " " string))) | ||
| 273 | |||
| 274 | (defun string-fill (string length) | ||
| 275 | "Try to word-wrap STRING so that no lines are longer than LENGTH. | ||
| 276 | Wrapping is done where there is whitespace. If there are | ||
| 277 | individual words in STRING that are longer than LENGTH, the | ||
| 278 | result will have lines that are longer than LENGTH." | ||
| 279 | (with-temp-buffer | ||
| 280 | (insert string) | ||
| 281 | (goto-char (point-min)) | ||
| 282 | (let ((fill-column length) | ||
| 283 | (adaptive-fill-mode nil)) | ||
| 284 | (fill-region (point-min) (point-max))) | ||
| 285 | (buffer-string))) | ||
| 286 | |||
| 287 | (defun string-limit (string length) | ||
| 288 | "Return (up to) a LENGTH substring of STRING. | ||
| 289 | If STRING is shorter or equal to LENGTH, the entire string is | ||
| 290 | returned unchanged. If STRING is longer than LENGTH, and LENGTH | ||
| 291 | is a positive number, return a a substring consisting of the | ||
| 292 | first LENGTH characters of STRING. If LENGTH is negative, return | ||
| 293 | a substring consisitng of thelast LENGTH characters of STRING." | ||
| 294 | (cond | ||
| 295 | ((<= (length string) length) string) | ||
| 296 | ((>= length 0) (substring string 0 length)) | ||
| 297 | (t (substring string (+ (length string) length))))) | ||
| 298 | |||
| 299 | (defun string-lines (string &optional omit-nulls) | ||
| 300 | "Split STRING into a list of lines. | ||
| 301 | If OMIT-NULLS, empty lines will be removed from the results." | ||
| 302 | (split-string string "\n" omit-nulls)) | ||
| 303 | |||
| 304 | (defun slice-string (string regexp) | ||
| 305 | "Split STRING at REGEXP boundaries and return a list of slices. | ||
| 306 | The boundaries that match REGEXP are not omitted from the results." | ||
| 307 | (let ((start-substring 0) | ||
| 308 | (start-search 0) | ||
| 309 | (result nil)) | ||
| 310 | (save-match-data | ||
| 311 | (while (string-match regexp string start-search) | ||
| 312 | (if (zerop (match-beginning 0)) | ||
| 313 | (setq start-search (match-end 0)) | ||
| 314 | (push (substring string start-substring (match-beginning 0)) result) | ||
| 315 | (setq start-substring (match-beginning 0) | ||
| 316 | start-search (match-end 0)))) | ||
| 317 | (push (substring string start-substring) result) | ||
| 318 | (nreverse result)))) | ||
| 319 | |||
| 267 | (defun replace-region-contents (beg end replace-fn | 320 | (defun replace-region-contents (beg end replace-fn |
| 268 | &optional max-secs max-costs) | 321 | &optional max-secs max-costs) |
| 269 | "Replace the region between BEG and END using REPLACE-FN. | 322 | "Replace the region between BEG and END using REPLACE-FN. |
diff --git a/test/lisp/emacs-lisp/subr-x-tests.el b/test/lisp/emacs-lisp/subr-x-tests.el index 9d14a5ab7ec..949bbb163eb 100644 --- a/test/lisp/emacs-lisp/subr-x-tests.el +++ b/test/lisp/emacs-lisp/subr-x-tests.el | |||
| @@ -582,5 +582,31 @@ | |||
| 582 | (should (equal (string-remove-suffix "a" "aa") "a")) | 582 | (should (equal (string-remove-suffix "a" "aa") "a")) |
| 583 | (should (equal (string-remove-suffix "a" "ba") "b"))) | 583 | (should (equal (string-remove-suffix "a" "ba") "b"))) |
| 584 | 584 | ||
| 585 | (ert-deftest subr-clean-whitespace () | ||
| 586 | (should (equal (string-clean-whitespace " foo ") "foo")) | ||
| 587 | (should (equal (string-clean-whitespace " foo \n\t Bar") "foo Bar"))) | ||
| 588 | |||
| 589 | (ert-deftest subr-string-fill () | ||
| 590 | (should (equal (string-fill "foo" 10) "foo")) | ||
| 591 | (should (equal (string-fill "foobar" 5) "foobar")) | ||
| 592 | (should (equal (string-fill "foo bar zot" 5) "foo\nbar\nzot")) | ||
| 593 | (should (equal (string-fill "foo bar zot" 7) "foo bar\nzot"))) | ||
| 594 | |||
| 595 | (ert-deftest subr-string-limit () | ||
| 596 | (should (equal (string-limit "foo" 10) "foo")) | ||
| 597 | (should (equal (string-limit "foo" 2) "fo")) | ||
| 598 | (should (equal (string-limit "foo" -2) "oo")) | ||
| 599 | (should (equal (string-limit "foo" 0) ""))) | ||
| 600 | |||
| 601 | (ert-deftest subr-string-lines () | ||
| 602 | (should (equal (string-lines "foo") '("foo"))) | ||
| 603 | (should (equal (string-lines "foo \nbar") '("foo " "bar")))) | ||
| 604 | |||
| 605 | (ert-deftest subr-slice-string () | ||
| 606 | (should (equal (slice-string "foo-bar" "-") '("foo" "-bar"))) | ||
| 607 | (should (equal (slice-string "foo-bar-" "-") '("foo" "-bar" "-"))) | ||
| 608 | (should (equal (slice-string "-foo-bar-" "-") '("-foo" "-bar" "-"))) | ||
| 609 | (should (equal (slice-string "ooo" "lala") '("ooo")))) | ||
| 610 | |||
| 585 | (provide 'subr-x-tests) | 611 | (provide 'subr-x-tests) |
| 586 | ;;; subr-x-tests.el ends here | 612 | ;;; subr-x-tests.el ends here |