branch: externals/denote commit 5fc634ebc43b29932ea784f25aaa98a654e7013a Author: Protesilaos Stavrou <i...@protesilaos.com> Commit: Protesilaos Stavrou <i...@protesilaos.com>
REMOVE denote-sequence.el as it will be its own package This is part of the wider reorganisation of the project, as discussed in issue 543: <https://github.com/protesilaos/denote/issues/543>. --- README.org | 224 +---------- denote-sequence.el | 1009 -------------------------------------------------- tests/denote-test.el | 209 ----------- 3 files changed, 13 insertions(+), 1429 deletions(-) diff --git a/README.org b/README.org index 1c18ed32e6..6f4bc2a75a 100644 --- a/README.org +++ b/README.org @@ -4034,18 +4034,13 @@ you get all the parameters included: [ The =denote-sequence.el= is part of {{{development-version}}}. ] -This section is about the optional extension =denote-sequence.el=, -which is part of the ~denote~ package. Its commands are autoloaded, so -they are avaialble in the =M-x= interface at the outset and will load -their file when they are invoked. Though users can explicitly load -the file on demand with: - -#+begin_src emacs-lisp -(require 'denote-sequence) -#+end_src +This section is about the external package ~denote-sequence~ (by +Protesilaos). The original idea was to include the code as part of the +~denote~ package, but we decided to keep each optional extension as a +separate package. Denote defines an optional file name component called the =SIGNATURE= -([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). This is a free form field that users can +([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). This is a free-form field that users can fill in with whatever text they want, such as to have a video split up into =part1= and =part2=, or to set some kind of priority like =a= and =b=, or even to have a special tag that stands out from the rest of @@ -4056,209 +4051,16 @@ hierarchical relationship between notes, such that the thoughts they expound on form sequences. For example, an article about the Labrador Retriever dog breed is a continuation of a thought process that extends something about dog breeds in general which, in turn, is a -topic that belongs to the wider theme of dogs. A sequence, then, is a -representation of such relationships. A note with a =SIGNATURE= of -~1=1~ (the ~=~ is the field separator of signatures, per the Denote -file-naming scheme) is thus the first child of note =1= and the -sibling of note ~1=2~. In this regard, something unrelated to dogs -will be its own parent, such as =2=, and so on. - -All the relevant functions we provide take care to automatically use -the right number for a given sequence ([[#h:6293ec17-05ef-4e41-9ae3-25df2ad86303][Create parent, child, or sibling sequence notes]]). -If, for example, we create a new child of parent ~1=1~, we make sure -that it is the largest number among any existing children, so if -~1=1=1~ already exists we use ~1=1=2~, and the like. - -The =denote-sequence.el= optional extension is not necessary for such -a workflow. Users can always define whatever =SIGNATURE= they want -manually. The purpose of this extension is to streamline this work. - -** Select a sequencing scheme for ~denote-sequence-scheme~ -:PROPERTIES: -:CUSTOM_ID: h:8c682f08-f162-4ddd-be03-805e87737d55 -:END: - -[ The =denote-sequence.el= is part of {{{development-version}}}. ] - -#+vindex: denote-sequence-scheme -The user option ~denote-sequence-scheme~ allows users to select either -the =numeric= scheme, which is like ~1=1=2~ or the =alphanumeric= -scheme, which is =1a2= for the same sequence ([[#h:373710df-a62e-4400-961c-87fac019b0a0][Convert from one sequencing scheme to another]]): - -- Numeric sequencing scheme :: A numeric sequence consists only of - numbers. The level of depth is derived from the number of fields in - the sequence, separated by the equals sign. Thus, the sequence - ~1=1=2~ consists of three levels of depth. For deeper sequences, the - numeric scheme will get longer, which some users may consider - unwieldy. The upside, however, is that is easier to reason about - larger numbers, such as ~1=100=2=50~. - -- Alphanumeric sequencing scheme :: An alphanumeric sequence combines - numbers and letters. The level of depth is undestand by the - alteration from numbers to letters and vice versa. As such, the - sequence =1a2= has three levels of depth. This scheme is more - compact, which users may like but can be harder to reason about - large numbers, such as =1zzzv2zx= corresponding to the numeric - ~1=100=2=50~ (this is because the number 26 is z, 27 is za, 52 is - zz, and so on). In practice, large numbers may not be a problem, - though this is something to keep in mind. - -*** Convert from one sequencing scheme to another -:PROPERTIES: -:CUSTOM_ID: h:373710df-a62e-4400-961c-87fac019b0a0 -:END: - -[ The =denote-sequence.el= is part of {{{development-version}}}. ] - -The decision on the desired ~denote-sequence-scheme~ wil affect new -notes long-term ([[#h:8c682f08-f162-4ddd-be03-805e87737d55][Select a sequencing scheme for ~denote-sequence-scheme~]]). -It thus is important to think through your needs and proceed accordingly. - -#+findex: denote-sequence-convert -Still, one cannot be sure which scheme they prefer until they -experiment with it. It then is inconvenient to manually revert to the -alternative scheme. To this end, we provide the command -~denote-sequence-convert~. It convers one or more files from their -current scheme to its counterpart. - -When called from inside a Denote file, it converts that file. When -called from a Dired buffer, it operates on the marked files. If no -files are marked, it works with the Dired file at point. - -Note that ~denote-sequence-convert~ DOES NOT REPARENT OR ANYHOW CHECK -THE RESULTING SEQUENCES FOR DUPLICATES ([[#h:98eb8ee5-93c4-49ba-9092-65a3b61c69c6][Re-parent a file to extend a given sequence]]). - -** Create parent, child, or sibling sequence notes -:PROPERTIES: -:CUSTOM_ID: h:6293ec17-05ef-4e41-9ae3-25df2ad86303 -:END: - -[ The =denote-sequence.el= is part of {{{development-version}}}. ] - -[ In the interest of simplicity, here we provide examples using the - =numeric= value of ~denote-sequence-scheme~, though the =alphanumeric= - will work as well ([[#h:8c682f08-f162-4ddd-be03-805e87737d55][Select a sequencing scheme for ~denote-sequence-scheme~]]). ] - -A new sequence note can be of the type =parent=, =child=, and -=sibling=. For the convenience of the user, we provide commands to -create such "sequence notes", link only between them (as opposed to -a link to any other file with the Denote file-naming scheme ([[#h:fc913d54-26c8-4c41-be86-999839e8ad31][Linking notes]])), and -re-parent them on demand. - -Concretely, we provide the following commands: +topic that belongs to the wider theme of dogs. -#+findex: denote-sequence -- ~denote-sequence~ :: The most general way to create a new sequence - note. It prompts for a type of sequence among =parent=, =child=, and - =sibling= and the rest of the work accordingly. If the new sequence - is not a parent, it thus prompts for an existing file to extend - from. The rest of the interaction is that of all the usual Denote - commands, such as to prompt for a title and keywords ([[#h:17896c8c-d97a-4faa-abf6-31df99746ca6][Points of entry]]). - -#+findex: denote-sequence-new-parent -- ~denote-sequence-new-parent~ :: This is a convenience wrapper of - ~denote-sequence~ which directly creates a parent sequence. - -#+findex: denote-sequence-new-child -- ~denote-sequence-new-child~ :: This is a convenience wrapper of - ~denote-sequence~ which directly creates a child of an existing - sequence, prompting for it using minibuffer completion. - -#+findex: denote-sequence-new-child-of-current -- ~denote-sequence-new-child-of-current~ :: This will create a new - child of the current file's sequence. If the current file does not - have such a sequence, then the command behaves the same as the - aforementioned ~denote-sequence-new-child~. - -#+findex: denote-sequence-new-sibling -- ~denote-sequence-new-sibling~ :: This is a convenience wrapper of - ~denote-sequence~ which directly creates a sibling of an existing - sequence, prompting for it using minibuffer completion. - -#+findex: denote-sequence-new-sibling-of-current -- ~denote-sequence-new-sibling-of-current~ :: This will create a new - sibling of the current file's sequence. If the current file does not - have such a sequence, then the command behaves the same as the - aforementioned ~denote-sequence-new-sibling~. - -** Find a relative of the current sequence -:PROPERTIES: -:CUSTOM_ID: h:8ec774f4-4154-4599-b8cf-fa767e18d22d -:END: - -[ The =denote-sequence.el= is part of {{{development-version}}}. ] - -#+findex: denote-sequence-find -While reading a file with a sequence, you may want to find what its -relatives are about. To this end, the command ~denote-sequence-find~ -prompts for a type among =parent=, =sibling=, =child=, and then asks -to select a file among those matching the given type. It then visits -the file. - -#+findex: denote-sequence-find-dired -Instead of selecting a single file, the command ~denote-sequence-find-dired~ -puts all the matching files in a bespoke Dired buffer ([[#h:7811300e-5758-4b86-89bf-1d904bf1598a][Show all or some sequences in a Dired buffer]]). - -** Link only to sequences -:PROPERTIES: -:CUSTOM_ID: h:e83902ff-4594-494b-88cd-722f6b4fb0b7 -:END: - -[ The =denote-sequence.el= is part of {{{development-version}}}. ] - -#+findex: denote-sequence-link -The command ~denote-sequence-link~ is a variant of the standard -~denote-link~ command which limits the list of files only to those -which contain a sequence ([[#h:fc913d54-26c8-4c41-be86-999839e8ad31][Linking notes]]). Consider it a convenience to -link to sequence notes more quickly. It is by no means necessary -though, as the regular linking commands will work as expected with any -Denote file, including those which contain a sequence as their file -name =SIGNATURE= ([[#h:d5ca722d-e7fa-46fa-9a57-6363b1d4186f][Write sequence notes or "folgezettel"]]). - -** Re-parent a file to extend a given sequence -:PROPERTIES: -:CUSTOM_ID: h:98eb8ee5-93c4-49ba-9092-65a3b61c69c6 -:END: - -[ The =denote-sequence.el= is part of {{{development-version}}}. ] - -#+findex: denote-sequence-reparent -The command ~denote-sequence-reparent~ can be used from inside a file -or for the file-at-point in Dired to make that file a child of a given -sequence. It does so by prompting for the target file using minibuffer -completion. Files available at this prompt are only those which -contain a sequence as their file name =SIGNATURE= ([[#h:d5ca722d-e7fa-46fa-9a57-6363b1d4186f][Write sequence notes or "folgezettel"]]). - -** Show all or some sequences in a Dired buffer -:PROPERTIES: -:CUSTOM_ID: h:7811300e-5758-4b86-89bf-1d904bf1598a -:END: - -[ The =denote-sequence.el= is part of {{{development-version}}}. ] +The ~denote-sequence~ package has a manual that explains these +concepts and relevant commands in further detail: -[ In the interest of simplicity, here we provide examples using the - =numeric= value of ~denote-sequence-scheme~, though the =alphanumeric= - will work as well ([[#h:8c682f08-f162-4ddd-be03-805e87737d55][Select a sequencing scheme for ~denote-sequence-scheme~]]). ] - -#+findex: denote-sequence-dired -The command ~denote-sequence-dired~ produces a bespoke and fully -fledged Dired buffers that contains all the sequences in their order -(as opposed to a regular Dired which sorts files using the =ls= -flags). - -With an optional =C-u= prefix argument, this command prompts for a -prefix to only show sequences that include it (e.g. only show notes -with ~1=1~, like ~1=1=1~ and ~1=1=2~ but not ~1=2~). - -With an optional double prefix argument of =C-u C-u=, this command -will prompt for the prefix as well as the level of depth to limit the -results to. Here "depth" means how deep to go in a sequence where, for -example, ~1=1=2~ is three levels of depth. It is possible to use an -empty string at the prefix prompt to not limit the results to any -prefix. - -A more specialised alternative for only relatives of a given sequence -is also available ([[#h:8ec774f4-4154-4599-b8cf-fa767e18d22d][Find a relative of the current sequence]]). ++ Package name (GNU ELPA): ~denote-sequence~ ++ Official manual: <https://protesilaos.com/emacs/denote-sequence> ++ Git repository: <https://github.com/protesilaos/denote-sequence> ++ Backronym: Denote... Sequences Efficiently Queue Unsorted Entries + Notwithstanding Curation Efforts. * Sort files by component :PROPERTIES: diff --git a/denote-sequence.el b/denote-sequence.el deleted file mode 100644 index d448545972..0000000000 --- a/denote-sequence.el +++ /dev/null @@ -1,1009 +0,0 @@ -;;; denote-sequence.el --- Sequence notes extension for Denote -*- lexical-binding: t -*- - -;; Copyright (C) 2024-2025 Free Software Foundation, Inc. - -;; Author: Protesilaos Stavrou <i...@protesilaos.com> -;; Maintainer: Protesilaos Stavrou <i...@protesilaos.com> -;; URL: https://github.com/protesilaos/denote - -;; This file is NOT part of GNU Emacs. - -;; 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 <https://www.gnu.org/licenses/>. - -;;; Commentary: - -;; Sequence notes extension for Denote. It uses the SIGNATURE file -;; name component of Denote to establish a hierarchy between notes. -;; As such, with the default numeric `denote-sequence-scheme', note -;; 1=1=2 is the second child of the first child of note 1. While with -;; the alphanumeric scheme, note 1a2 is the equivalent. The rest of -;; the Denote file naming scheme continues to apply as described in -;; the manual, as do all the other features of Denote. -;; -;; A new sequence note can be of the type `parent', `child', and -;; `sibling'. For the convenience of the user, we provide commands to -;; create such "sequence notes", link only between them (as opposed to -;; a link to any other file with the Denote file-naminng scheme), and -;; re-parent them on demand, as well as display them in a Dired buffer -;; in accordance with their inherent order. -;; -;; All the relevant functions we provide take care to automatically -;; use the right number for a given sequence, per the user option -;; `denote-sequence-scheme'. If, for example, we create a new child -;; for parent 1=1, we make sure that it is the new largest number -;; among any existing children, so if 1=1=1 already exists we use -;; 1=1=2, and so on. -;; -;; This optional extension is not necessary for such a workflow. -;; Users can always define whatever SIGNATURE they want manually. The -;; purpose of this extension is to streamline that work. - -;;; Code: - -;; TODO 2025-01-08: Test whether the built-in hierarchy.el can be used -;; to present the sequences in a nice way. What do we need and how -;; exactly do we use that library. -(require 'denote) - -(defgroup denote-sequence () - "Sequence notes extension for Denote." - :group 'denote - :link '(info-link "(denote) top") - :link '(info-link "(denote) Sequence notes") - :link '(url-link :tag "homepage" "https://protesilaos.com/emacs/denote")) - -(defcustom denote-sequence-scheme 'numeric - "Sequencing scheme to establish file hierarchies. -The value is the symbol `numeric' or `alphanumeric'. - -Numeric sequences (the default) are the easier to understand but also -are the longest. Each level of depth in the hierarchy is delimited by -an equals sign: the 1=1=2 thus refers to the second child of the first -child of parent 1. Each level of depth can be a number of any length, -like 1=40=2=20. - -Alphanumeric sequences are more compact than numeric ones. Their depth -is derived via the alternation from numbers to latin characters, such -that 1a2 refers to the second child of the first child of parent 1. -Because they alternate between numbers and letters, they do not use the -equals sign. When a number cannot be represented by a single letter, -two or more are used instead, such as the number 51 corresponding to -zx (z is 26 and x is 25)." - :group 'denote-sequence - :type '(choice (const :tag "Numeric like 1=1=2" numeric) - (const :tag "Alphanumeric like 1a2" alphanumeric))) - -(defconst denote-sequence-numeric-regexp "=?[0-9]+" - "Pattern of a numeric sequence.") - -(defconst denote-sequence-alphanumeric-regexp "\\([0-9]+\\)\\([[:alpha:]]+\\)?" - "Pattern of an alphanumeric sequence.") - -(defconst denote-sequence-types '(parent child sibling) - "Types of sequence.") - -(defun denote-sequence-numeric-p (sequence) - "Return SEQUENCE if it is numeric per `denote-sequence-scheme'." - (when (and (string-match-p denote-sequence-numeric-regexp sequence) - (not (string-match-p "[[:alpha:]]" sequence)) - (not (string-suffix-p "=" sequence))) - sequence)) - -(defun denote-sequence-alphanumeric-p (sequence) - "Return SEQUENCE if it is alphanumeric per `denote-sequence-scheme'." - (when (and (string-match-p denote-sequence-alphanumeric-regexp sequence) - (string-match-p "\\`[0-9]+" sequence) - (not (string-match-p "=" sequence))) - sequence)) - -(defun denote-sequence-p (sequence) - "Return SEQUENCE string is of a supported scheme. -Also see `denote-sequence-numeric-p' and `denote-sequence-alphanumeric-p'." - (when (or (denote-sequence-numeric-p sequence) - (denote-sequence-alphanumeric-p sequence)) - sequence)) - -(defun denote-sequence-with-error-p (sequence) - "Return SEQUENCE string if it matches `denote-sequence-numeric-regexp'." - (or (denote-sequence-p sequence) - (error "The sequence `%s' does not pass `denote-sequence-p'" sequence))) - -(defun denote-sequence--numeric-partial-p (string) - "Return non-nil if STRING likely is part of a numeric sequence." - (and (string-match-p "[0-9]+" string) - (not (string-match-p "[[:alpha:][:punct:]]" string)))) - -(defun denote-sequence--alphanumeric-partial-p (string) - "Return non-nil if STRING likely is part of an alphanumeric sequence." - (and (string-match-p "[[:alpha:]]+" string) - (not (string-match-p "[0-9[:punct:]]+" string)))) - -(defun denote-sequence-and-scheme-p (sequence &optional partial) - "Return the sequencing scheme of SEQUENCE, per `denote-sequence-scheme'. -Return a cons cell of the form (sequence . scheme), where the `car' is -SEQUENCE and the `cdr' is its sequencing scheme as a symbol among those -mentioned in `denote-sequence-scheme'. - -With optional PARTIAL as a non-nil value, assume SEQUENCE to be a string -that only represents part of a sequence, which itself consists entirely -of numbers or letters. - -Produce an error if the sequencing scheme cannot be established." - (cond - ((or (and partial (denote-sequence--alphanumeric-partial-p sequence)) - (denote-sequence-alphanumeric-p sequence)) - (cons sequence 'alphanumeric)) - ((or (and partial (denote-sequence--numeric-partial-p sequence)) - (denote-sequence-numeric-p sequence)) - (cons sequence 'numeric)) - (t (error "The sequence `%s' does not pass `denote-sequence-p'" sequence)))) - -(defun denote-sequence--scheme-of-strings (strings) - "Return the sequencing scheme of STRINGS, per `denote-sequence-scheme'." - (if (seq-find (lambda (string) (string-match-p "[[:alpha:]]" string)) strings) - 'alphanumeric - 'numeric)) - -(defun denote-sequence-file-p (file) - "Return the sequence if Denote signature of FILE is a sequence. -A sequence is string that conforms with `denote-sequence-p'." - (when-let* ((signature (denote-retrieve-filename-signature file))) - (denote-sequence-p signature))) - -(defun denote-sequence-join (strings scheme) - "Join STRINGS to form a sequence according to SCHEME. -SCHEME is a symbol among those mentioned in `denote-sequence-scheme'. -Return resulting sequence if it conforms with `denote-sequence-p'." - (pcase scheme - ('numeric (mapconcat #'identity strings "=")) - ('alphanumeric (apply #'concat strings)))) - -(defun denote-sequence-split (sequence &optional partial) - "Split the SEQUENCE string into a list. -SEQUENCE conforms with `denote-sequence-p'. If PARTIAL is non-nil, it -has the same meaning as in `denote-sequence-and-scheme-p'." - (pcase-let* ((`(,sequence . ,scheme) (denote-sequence-and-scheme-p sequence partial))) - (pcase scheme - ('numeric - (split-string sequence "=" t)) - ('alphanumeric - (let ((strings nil) - (start 0)) - (while (string-match denote-sequence-alphanumeric-regexp sequence start) - (push (match-string 1 sequence) strings) - (when-let* ((two (match-string 2 sequence))) - (push two strings) - (setq start (match-end 2))) - (setq start (match-end 1))) - (if strings - (nreverse strings) - (split-string sequence "" :omit-nulls))))))) - -(defun denote-sequence--alpha-to-number (string) - "Convert STRING of alphabetic characters to its numeric equivalent." - (let* ((strings (denote-sequence-split string :partial)) - (numbers (mapcar - (lambda (string) - (let ((num (- (string-to-char string) 96))) - (cond - ((and (> num 0) (<= num 26)) - num) - (t - (let ((times (/ num 26))) - (if-let* ((mod (% num 26)) - ((> mod 0)) - (suffix (+ mod 96))) - (list (* times 26) suffix) - (list (* times 26)))))))) - strings))) - (format "%s" (apply #'+ numbers)))) - -(defun denote-sequence--number-to-alpha (string) - "Convert STRING of numbers to its alphabetic equivalent." - (let ((num (string-to-number string))) - (cond - ((= num 0) - (char-to-string (+ num 97))) - ((and (> num 0) (<= num 26)) - (char-to-string (+ num 96))) - (t - (let ((times (/ num 26))) - (if-let* ((mod (% num 26)) - ((> mod 0)) - (prefix (make-string times ?z)) - (suffix (char-to-string (+ mod 96)))) - (concat prefix suffix) - (make-string times ?z))))))) - -(defun denote-sequence--alpha-to-number-complete (sequence) - "Like `denote-sequence--alpha-to-number' but for the complete SEQUENCE." - (if (denote-sequence-numeric-p sequence) - sequence - (let* ((parts (denote-sequence-split sequence)) - (converted-parts (mapcar - (lambda (string) - (if (denote-sequence--numeric-partial-p string) - string - (denote-sequence--alpha-to-number string))) - parts))) - (denote-sequence-join converted-parts 'numeric)))) - -(defun denote-sequence--number-to-alpha-complete (sequence) - "Like `denote-sequence--number-to-alpha' but for the complete SEQUENCE." - (if (denote-sequence-alphanumeric-p sequence) - sequence - (let* ((parts (denote-sequence-split sequence)) - (odd-is-numeric 0) - (converted-parts (mapcar - (lambda (string) - (setq odd-is-numeric (+ odd-is-numeric 1)) - (cond - ((= (% odd-is-numeric 2) 1) - string) - ((denote-sequence--alphanumeric-partial-p string) - string) - (t - (denote-sequence--number-to-alpha string)))) - parts))) - (denote-sequence-join converted-parts 'alphanumeric)))) - -(defun denote-sequence-make-conversion (string &optional string-is-sequence) - "Convert STRING to its counterpart sequencing scheme. -If STRING-IS-SEQUENCE then assume STRING to be a complete sequence, in -which case convert the entirety of it. Also see `denote-sequence-scheme'." - (cond - ((and string-is-sequence (denote-sequence-alphanumeric-p string)) - (denote-sequence--alpha-to-number-complete string)) - ((and string-is-sequence (denote-sequence-numeric-p string)) - (denote-sequence--number-to-alpha-complete string)) - ((denote-sequence--alphanumeric-partial-p string) - (denote-sequence--alpha-to-number string)) - ((denote-sequence--numeric-partial-p string) - (denote-sequence--number-to-alpha string)) - (t - (if string-is-sequence - (error "String `%s' did not pass `denote-sequence-p'" string) - (error "The `%s' must not contain both numbers and letters" string))))) - -(defun denote-sequence-increment (string) - "Increment number represented by STRING and return it as a string. -STRING is part of a sequence, not the entirety of it." - (cond - ((denote-sequence--numeric-partial-p string) - (number-to-string (+ (string-to-number string) 1))) - ((denote-sequence--alphanumeric-partial-p string) - (let* ((letters (split-string string "" :omit-nulls)) - (length-1 (= (length letters) 1)) - (first (car letters)) - (reverse (nreverse (copy-sequence letters))) - (last (car reverse))) - (cond - ((and length-1 (string= "z" first)) - "za") - (length-1 - (char-to-string (+ (string-to-char first) 1))) - ((string= "z" last) - (apply #'concat (append letters (list "a")))) - (t - (let ((last last)) - (apply #'concat - (append (butlast letters) - (list (char-to-string (+ (string-to-char last) 1)))))))))) - (t - (error "The string `%s' must contain only numbers or letters" string)))) - -(defun denote-sequence-depth (sequence) - "Get the depth of SEQUENCE. -For example, 1=2=1 and 1b1 are three levels of depth." - (length (denote-sequence-split sequence))) - -(defun denote-sequence--children-implied-p (sequence) - "Return non-nil if SEQUENCE implies children. -This does not actually check if there are children in the variable -`denote-directory', but only that SEQUENCE is greater than 1." - (> (denote-sequence-depth sequence) 1)) - -(defun denote-sequence--get-parent (sequence) - "Return implied parent of SEQUENCE, else nil. -Produce an error if SEQUENCE does not conform with `denote-sequence-p'. -The implied check here has the same meaning as described in -`denote-sequence--children-implied-p'." - (pcase-let* ((`(,sequence . ,scheme) (denote-sequence-and-scheme-p sequence))) - (when (and (denote-sequence-with-error-p sequence) - (denote-sequence--children-implied-p sequence)) - (let ((strings (thread-last - (denote-sequence-split sequence) - (butlast)))) - (denote-sequence-join strings scheme))))) - -(defun denote-sequence--get-files (&optional files) - "Return list of files or buffers in the variable `denote-directory'. -With optional FILES only consider those, otherwise use `denote-directory-files'." - (delete-dups (append (denote--buffer-file-names) (or files (denote-directory-files))))) - -(defun denote-sequence-get-all-files (&optional files) - "Return all files in variable `denote-directory' with a sequence. -A sequence is a Denote signature that conforms with `denote-sequence-p'. - -With optional FILES consider only those, otherwise use the return value -of `denote-directory-files'." - (seq-filter #'denote-sequence-file-p (denote-sequence--get-files files))) - -(defun denote-sequence--sequence-prefix-p (prefix sequence) - "Return non-nil if SEQUENCE has prefix sequence PREFIX. - -SEQUENCE is a Denote signatures that conforms with `denote-sequence-p'. -PREFIX is a list of strings containing the components of the prefix -sequence, as is returned by `denote-sequence-split'. - -If PREFIX is nil, return non-nil as if the SEQUENCE has PREFIX." - (let ((value (denote-sequence-split sequence)) - (depth (length prefix)) - (matched 0)) - (while (and value - (< matched depth) - (string-equal (pop value) (nth matched prefix))) - (setq matched (1+ matched))) - (= matched depth))) - -(defun denote-sequence-get-all-files-with-prefix (sequence &optional files) - "Return all files in variable `denote-directory' with prefix SEQUENCE. -A sequence is a Denote signature that conforms with `denote-sequence-p'. - -With optional FILES, operate on them, else use the return value of -`denote-directory-files'." - (when-let* (((not (string-empty-p sequence))) - (prefix (denote-sequence-split sequence))) - (seq-filter - (lambda (file) - (when-let* ((file-sequence (denote-sequence-file-p file))) - (denote-sequence--sequence-prefix-p prefix file-sequence))) - (denote-sequence-get-all-files files)))) - -(defun denote-sequence-get-all-files-with-max-depth (depth &optional files) - "Return all files with sequence depth up to DEPTH (inclusive). -With optional FILES, operate on them, else use the return value of -`denote-sequence-get-all-files'." - (delq nil - (mapcar - (lambda (file) - (when-let* ((sequence (denote-retrieve-filename-signature file)) - (components (denote-sequence-split sequence)) - ((>= depth (length components)))) - file)) - (denote-sequence-get-all-files files)))) - -(defun denote-sequence-get-all-sequences (&optional files) - "Return all sequences in `denote-directory-files'. -With optional FILES return all sequences among them instead. - -A sequence is a Denote signature that conforms with `denote-sequence-p'." - (delq nil (mapcar #'denote-sequence-file-p (denote-sequence-get-all-files files)))) - -(defun denote-sequence-get-all-sequences-with-prefix (sequence &optional sequences) - "Get all sequences which extend SEQUENCE. -With optional SEQUENCES operate on those, else use the return value of -`denote-sequence-get-all-sequences'. - -A sequence is a Denote signature that conforms with `denote-sequence-p'." - (when-let* (((not (string-empty-p sequence))) - (prefix (denote-sequence-split sequence))) - (seq-filter - (lambda (string) - (denote-sequence--sequence-prefix-p prefix string)) - (or sequences (denote-sequence-get-all-sequences))))) - -(defun denote-sequence-get-all-sequences-with-max-depth (depth &optional sequences) - "Get sequences up to DEPTH (inclusive). -With optional SEQUENCES operate on those, else use the return value of -`denote-sequence-get-all-sequences'." - (let* ((strings (or sequences (denote-sequence-get-all-sequences))) - (lists-all (mapcar #'denote-sequence-split strings)) - (lists (seq-filter (lambda (element) (>= depth (length element))) lists-all))) - (delete-dups - (mapcar - (lambda (strings) - (denote-sequence-join (seq-take strings depth) (denote-sequence--scheme-of-strings strings))) - lists)))) - -(defun denote-sequence--pad (sequence type) - "Create a new SEQUENCE with padded spaces for TYPE. -TYPE is a symbol among `denote-sequence-types'. The special TYPE `all' -means to pad the full length of the sequence." - (let* ((sequence-separator-p (denote-sequence--children-implied-p sequence)) - (split (denote-sequence-split sequence)) - (s (cond - ((eq type 'all) split) - (sequence-separator-p - (pcase type - ('parent (car split)) - ('sibling split) - ('child (car (nreverse split))) - (_ (error "The type `%s' is not among `denote-sequence-types'" type)))) - (t sequence)))) - (if (listp s) - (combine-and-quote-strings - (mapcar - (lambda (part) - (string-pad part 5 32 :pad-from-start)) - s) - "=") - (string-pad s 32 32 :pad-from-start)))) - -(defun denote-sequence--get-largest-by-order (sequences type) - "Sort SEQUENCES of TYPE to get largest in order, using `denote-sequence--pad'." - (car - (reverse - (sort sequences - (lambda (s1 s2) - (string< - (denote-sequence--pad s1 type) - (denote-sequence--pad s2 type))))))) - -(defun denote-sequence--string-length-sans-delimiter (string) - "Return length of STRING without the equals sign." - (if (eq denote-sequence-scheme 'numeric) - (length (replace-regexp-in-string "=" "" string)) - (length string))) - -(defun denote-sequence--get-largest-by-length (sequences) - "Compare length of SEQUENCES to determine the largest among them. -If there are more than one sequences of equal length, return them." - (let* ((seqs-with-length (mapcar (lambda (sequence) - (cons (denote-sequence--string-length-sans-delimiter sequence) sequence)) - sequences)) - (longest (apply #'max (mapcar #'car seqs-with-length))) - (largest-sequence (delq nil - (mapcar (lambda (element) - (unless (< (car element) longest) - (cdr element))) - seqs-with-length)))) - (if (= (length largest-sequence) 1) - (car largest-sequence) - largest-sequence))) - -(defun denote-sequence--get-largest (sequences type) - "Return largest sequence in SEQUENCES given TYPE. -TYPE is a symbol among `denote-sequence-types'." - (if (eq type 'child) - (let ((largest (denote-sequence--get-largest-by-length sequences))) - (if (listp largest) - (denote-sequence--get-largest-by-order largest type) - largest)) - (denote-sequence--get-largest-by-order sequences type))) - -(defun denote-sequence--get-start (&optional sequence prepend-delimiter) - "Return the start of a new sequence. -With optional SEQUENCE, do so based on the final level of depth therein. -This is usefule only for the alphanumeric `denote-sequence-scheme'. If -optional PREPEND-DELIMITER is non-nil, prepend the equals sign to the -number if `denote-sequence-scheme' is numeric." - (pcase denote-sequence-scheme - ('numeric (if prepend-delimiter "=1" "1")) - ('alphanumeric (if (denote-sequence--alphanumeric-partial-p (substring sequence -1)) - "1" - "a")))) - -(defun denote-sequence--get-new-parent (&optional sequences) - "Return a new to increment largest among sequences. -With optional SEQUENCES consider only those, otherwise operate on the -return value of `denote-sequence-get-all-sequences'." - (if-let* ((all (or sequences (denote-sequence-get-all-sequences)))) - (let* ((largest (denote-sequence--get-largest all 'parent)) - (first-component (car (denote-sequence-split largest))) - (current-number (string-to-number first-component))) - (number-to-string (+ current-number 1))) - (denote-sequence--get-start))) - -(defun denote-sequence-filter-scheme (sequences &optional scheme) - "Return list of SEQUENCES that are `denote-sequence-scheme' or SCHEME." - (let ((predicate (pcase (or scheme denote-sequence-scheme) - ('alphanumeric #'denote-sequence-alphanumeric-p) - ('numeric #'denote-sequence-numeric-p)))) - (seq-filter predicate sequences))) - -(defun denote-sequence--get-new-child (sequence &optional sequences) - "Return a new child of SEQUENCE. -Optional SEQUENCES has the same meaning as that specified in the -function `denote-sequence-get-all-sequences-with-prefix'." - (if-let* ((depth (+ (denote-sequence-depth sequence) 1)) - (all-unfiltered (denote-sequence-get-all-sequences-with-prefix sequence sequences)) - (start-child (denote-sequence--get-start sequence :prepend-delimiter))) - (if (= (length all-unfiltered) 1) - (format "%s%s" (car all-unfiltered) start-child) - (if-let* ((all-schemeless (cond - ((denote-sequence-get-all-sequences-with-max-depth depth all-unfiltered)) - (t all-unfiltered))) - (all (denote-sequence-filter-scheme all-schemeless)) - (largest (denote-sequence--get-largest all 'child))) - (if (denote-sequence--children-implied-p largest) - (pcase-let* ((`(,largest . ,scheme) (denote-sequence-and-scheme-p largest)) - (components (denote-sequence-split largest)) - (butlast (butlast components)) - (last-component (car (nreverse components))) - (new-number (denote-sequence-increment last-component))) - (denote-sequence-join - (if butlast - (append butlast (list new-number)) - (list largest new-number)) - scheme)) - (format "%s%s" largest start-child)) - (format "%s%s" sequence start-child))) - (error "Cannot find sequences given sequence `%s' using scheme `%s'" sequence denote-sequence-scheme))) - -(defun denote-sequence--get-prefix-for-siblings (sequence) - "Get the prefix of SEQUENCE such that it is possible to find its siblings." - (pcase-let ((`(,sequence . ,scheme) (denote-sequence-and-scheme-p sequence))) - (when (denote-sequence--children-implied-p sequence) - (denote-sequence-join (butlast (denote-sequence-split sequence)) scheme)))) - -(defun denote-sequence--get-new-sibling (sequence &optional sequences) - "Return a new sibling SEQUENCE. -Optional SEQUENCES has the same meaning as that specified in the -function `denote-sequence-get-all-sequences-with-prefix'." - (let* ((children-p (denote-sequence--children-implied-p sequence))) - (if-let* ((depth (denote-sequence-depth sequence)) - (all-unfiltered (if children-p - (denote-sequence-get-all-sequences-with-prefix - (denote-sequence--get-prefix-for-siblings sequence) - sequences) - (denote-sequence-get-all-sequences))) - (all-schemeless (denote-sequence-get-all-sequences-with-max-depth depth all-unfiltered)) - (all (denote-sequence-filter-scheme all-schemeless)) - ((member sequence all)) - (largest (if children-p - (denote-sequence--get-largest all 'sibling) - (denote-sequence--get-largest all 'parent)))) - (if children-p - (pcase-let* ((`(,largest . ,scheme) (denote-sequence-and-scheme-p largest)) - (components (denote-sequence-split largest)) - (butlast (butlast components)) - (last-component (car (nreverse components))) - (new-number (denote-sequence-increment last-component))) - (denote-sequence-join (append butlast (list new-number)) scheme)) - (number-to-string (+ (string-to-number largest) 1))) - (error "Cannot find sequences given sequence `%s' using scheme `%s'" sequence denote-sequence-scheme)))) - -(defun denote-sequence-get-new (type &optional sequence sequences) - "Return a sequence given TYPE among `denote-sequence-types'. -If TYPE is either `child' or `sibling', then optional SEQUENCE must be -non-nil and conform with `denote-sequence-p'. - -With optional SEQUENCES consider only those, otherwise operate on the -return value of `denote-sequence-get-all-sequences'." - (pcase type - ('parent (denote-sequence--get-new-parent sequences)) - ('child (denote-sequence--get-new-child sequence sequences)) - ('sibling (denote-sequence--get-new-sibling sequence sequences)) - (_ (error "The type `%s' is not among `denote-sequence-types'" type)))) - -(defun denote-sequence-get-relative (sequence type &optional files) - "Get files of TYPE given the SEQUENCE. -With optional FILES consider only those, otherwise operate on all files -returned by `denote-sequence-get-all-files'." - (let* ((depth (denote-sequence-depth sequence)) - (scheme (cdr (denote-sequence-and-scheme-p sequence))) - (components (denote-sequence-split sequence)) - (filter-common (lambda (comparison prefix) - (seq-filter - (lambda (file) - (funcall comparison (denote-sequence-depth (denote-retrieve-filename-signature file)) depth)) - (denote-sequence-get-all-files-with-prefix prefix files))))) - (pcase type - ('all-parents (let ((parents nil) - (butlast (butlast components))) - (while (>= (length butlast) 1) - (when-let* ((prefix (denote-sequence-join butlast scheme)) - (parent (seq-find - (lambda (file) - (string= (denote-retrieve-filename-signature file) prefix)) - (denote-sequence-get-all-files files)))) - (push parent parents) - (setq butlast (butlast butlast)))) - parents)) - ('parent (let ((butlast (denote-sequence-join (butlast components) scheme))) - (seq-find - (lambda (file) - (string= (denote-retrieve-filename-signature file) butlast)) - (denote-sequence-get-all-files files)))) - ('siblings (funcall filter-common '= (denote-sequence-join (butlast components) scheme))) - ('all-children (funcall filter-common '> sequence)) - ('children (seq-filter - (lambda (file) - (= (denote-sequence-depth (denote-sequence-file-p file)) (+ depth 1))) - (funcall filter-common '> sequence))) - (_ (error "The type `%s' is not among the allowed types" type))))) - -(defvar denote-sequence-type-history nil - "Minibuffer history of `denote-sequence-type-prompt'.") - -(defun denote-sequence-annotate-types (type) - "Annotate completion candidate of TYPE for `denote-sequence-type-prompt'." - (when-let* ((text (pcase type - ("parent" "Parent sequence") - ("sibling" "Sibling of another sequence") - ("child" "Child of another sequence")))) - (format "%s-- %s" - (propertize " " 'display '(space :align-to 10)) - (propertize text 'face 'completions-annotations)))) - -(defun denote-sequence-type-prompt (&optional prompt-text types annotation-fn) - "Prompt for sequence type among `denote-sequence-types'. -Return selected type as a symbol. - -With optional PROMPT-TEXT use it instead of the generic prompt. - -With optional TYPES use those instead of the `denote-sequence-types'. - -With optional ANNOTATION-FN use it to annotate the completion candidates -instead of the default `denote-sequence-annotate-types'." - (let ((default (car denote-sequence-type-history)) - (completion-extra-properties - (list :annotation-function (or annotation-fn #'denote-sequence-annotate-types)))) - (intern - (completing-read - (format-prompt (or prompt-text "Select sequence type") default) - (denote--completion-table 'denote-sequence-type (or types denote-sequence-types)) - nil :require-match nil - 'denote-sequence-type-history default)))) - -(defvar denote-sequence-file-history nil - "Minibuffer history for `denote-sequence-file-prompt'.") - -(defun denote-sequence-file-prompt (&optional prompt-text files-with-sequences) - "Prompt for file with sequence in variable `denote-directory'. -A sequence is a Denote signature that conforms with `denote-sequence-p'. - -With optional PROMPT-TEXT use it instead of a generic prompt. - -With optional FILES-WITH-SEQUENCES as a list of strings, use them as -completion candidates. Else use `denote-sequence-get-all-files'." - (if-let* ((relative-files (mapcar #'denote-get-file-name-relative-to-denote-directory - (or files-with-sequences (denote-sequence-get-all-files)))) - (prompt (format-prompt (or prompt-text "Select FILE with sequence") nil)) - (input (completing-read - prompt - (denote--completion-table 'file relative-files) - nil :require-match - nil 'denote-sequence-file-history))) - (concat (denote-directory) input) - (error "There are no sequence notes in the `denote-directory'"))) - -;;;###autoload -(defun denote-sequence (type &optional file-with-sequence) - "Create a new sequence note of TYPE among `denote-sequence-types'. -If TYPE is either `child' or `sibling', then it is an extension of -FILE-WITH-SEQUENCE. - -When called interactively, prompt for TYPE and, when necessary, for -FILE-WITH-SEQUENCE whose sequence will be used to derive a new sequence. -Files available at the minibuffer prompt are those returned by -`denote-sequence-get-all-files'." - (interactive - (let ((selected-type (denote-sequence-type-prompt))) - (list - selected-type - (when (memq selected-type (delq 'parent denote-sequence-types)) - (denote-sequence-file-prompt (format "Make a new %s of SEQUENCE" selected-type)))))) - (let* ((sequence (when file-with-sequence (denote-retrieve-filename-signature file-with-sequence))) - (new-sequence (denote-sequence-get-new type sequence)) - (denote-use-signature new-sequence)) - (call-interactively 'denote))) - -;;;###autoload -(defun denote-sequence-new-parent () - "Like `denote-sequence' to directly create new parent." - (interactive) - (let* ((new-sequence (denote-sequence-get-new 'parent)) - (denote-use-signature new-sequence)) - (call-interactively 'denote))) - -;;;###autoload -(defun denote-sequence-new-sibling (sequence) - "Like `denote-sequence' to directly create new sibling of SEQUENCE. -When called interactively, SEQUENCE is a file among files in the variable -`denote-directory' that have a sequence (per `denote-sequence-file-p'). - -When called from Lisp, SEQUENCE is a string that conforms with -`denote-sequence-p'." - (interactive - (list - (denote-retrieve-filename-signature - (denote-sequence-file-prompt "Make a new sibling of SEQUENCE")))) - (let* ((new-sequence (denote-sequence-get-new 'sibling sequence)) - (denote-use-signature new-sequence)) - (call-interactively 'denote))) - -(defun denote-sequence--get-file-in-dired-or-prompt (prompt-text) - "Get the file at point in Dired, the current one, or prompt with PROMPT-TEXT." - (cond - ((when-let* (((derived-mode-p 'dired-mode)) - (file-at-point (dired-get-filename nil t))) - (denote-sequence-file-p file-at-point))) - ((and buffer-file-name (denote-sequence-file-p buffer-file-name))) - (t - (denote-retrieve-filename-signature (denote-sequence-file-prompt prompt-text))))) - -;;;###autoload -(defun denote-sequence-new-sibling-of-current (sequence) - "Create a new sibling sequence of the current file with SEQUENCE. -If the current file does not have a sequence, then behave exactly like -`denote-sequence-new-sibling'." - (interactive (list (denote-sequence--get-file-in-dired-or-prompt "Make a new sibling of SEQUENCE"))) - (let* ((new-sequence (denote-sequence-get-new 'sibling sequence)) - (denote-use-signature new-sequence)) - (call-interactively 'denote))) - -;;;###autoload -(defun denote-sequence-new-child (sequence) - "Like `denote-sequence' to directly create new child of SEQUENCE. -When called interactively, SEQUENCE is a file among files in the variable -`denote-directory' that have a sequence (per `denote-sequence-file-p'). - -When called from Lisp, SEQUENCE is a string that conforms with -`denote-sequence-p'." - (interactive - (list - (denote-retrieve-filename-signature - (denote-sequence-file-prompt "Make a new child of SEQUENCE")))) - (let* ((new-sequence (denote-sequence-get-new 'child sequence)) - (denote-use-signature new-sequence)) - (call-interactively 'denote))) - -;;;###autoload -(defun denote-sequence-new-child-of-current (sequence) - "Create a new child sequence of the current file with SEQUENCE. -If the current file does not have a sequence, then behave exactly like -`denote-sequence-new-child'." - (interactive (list (denote-sequence--get-file-in-dired-or-prompt "Make a new child of SEQUENCE"))) - (let* ((new-sequence (denote-sequence-get-new 'child sequence)) - (denote-use-signature new-sequence)) - (call-interactively 'denote))) - -(defvar denote-sequence-relative-types - '(all-parents parent siblings children all-children) - "Types of sequence relatives.") - -(defun denote-sequence-annotate-relative-types (type) - "Annotate completion candidate of TYPE for `denote-sequence-type-prompt'." - (when-let* ((text (pcase type - ("all-parents" "All parent sequences") - ("parent" "Immediate parent") - ("siblings" "All siblings") - ("all-children" "All children") - ("children" "Immediate children")))) - (format "%s-- %s" - (propertize " " 'display '(space :align-to 15)) - (propertize text 'face 'completions-annotations)))) - -;;;###autoload -(defun denote-sequence-find (type) - "Find all relatives of the given TYPE using the current file's sequence. -Prompt for TYPE among `denote-sequence-relative-types' and then prompt -for a file among the matching files." - (interactive - (list - (denote-sequence-type-prompt "Find relatives of TYPE" - '(all-parents parent siblings children all-children) - #'denote-sequence-annotate-relative-types))) - (if-let* ((sequence (denote-sequence-file-p buffer-file-name))) - (if-let* ((matches (denote-sequence-get-relative sequence type)) - (relatives (delete buffer-file-name matches))) - (find-file (if (stringp relatives) - relatives - (denote-sequence-file-prompt "Select a relative" relatives))) - (user-error "The sequence `%s' has no relatives of type `%s'" sequence type)) - (user-error "The current file has no sequence"))) - -;;;###autoload -(defun denote-sequence-link (file &optional id-only) - "Link to FILE with sequence. -This is like the `denote-link' command but only accepts to link to a -file that conforms with `denote-sequence-file-p'. When called -interactively, only relevant files are shown for minibuffer completion -from the variable `denote-directory'. - -Optional ID-ONLY has the same meaning as the `denote-link' command." - (interactive (list (denote-sequence-file-prompt "Link to file with sequence"))) - (unless (denote-sequence-file-p file) - (error "Can only link to file with a sequence; else use `denote-link' and related")) - (let* ((type (denote-filetype-heuristics buffer-file-name)) - (description (denote-get-link-description file))) - (denote-link file type description id-only))) - -(defun denote-sequence-sort-files (files-with-sequence) - "Sort FILES-WITH-SEQUENCE according to their sequence." - (sort - files-with-sequence - (lambda (file-with-sequence-1 file-with-sequence-2) - (let ((s1 (denote-retrieve-filename-signature file-with-sequence-1)) - (s2 (denote-retrieve-filename-signature file-with-sequence-2))) - (string< - (denote-sequence--pad s1 'all) - (denote-sequence--pad s2 'all)))))) - -(defvar denote-sequence-history nil - "Minibuffer history of `denote-sequence-prompt'.") - -(defun denote-sequence-prompt (&optional prompt-text sequences) - "Prompt for a sequence. -With optional PROMPT-TEXT use it instead of a generic prompt. - -With optional SEQUENCES as a list of strings, use them as completion -candidates. Else use the return value of `denote-sequence-get-all-sequences'. -A sequence is a string conforming with `denote-sequence-p'. Any other string -is ignored." - (completing-read - (format-prompt (or prompt-text "Select an existing sequence (empty for all)") nil) - (or sequences (denote-sequence-get-all-sequences)) - #'denote-sequence-p :require-match nil 'denote-sequence-history)) - -(defun denote-sequence-depth-prompt (&optional prompt-text) - "Prompt for the depth of a sequence. -With optional PROMPT-TEXT use it instead of the generic one." - (read-number - (or prompt-text - (format "Get sequences up to this depth %s: " - (if (eq denote-sequence-scheme 'alphanumeric) - "(e.g. `1a2' is `3' levels of depth)" - "(e.g. `1=1=2' is `3' levels of depth)"))))) - -(defun denote-sequence--get-dired-buffer-name (&optional prefix depth) - "Return a string for `denote-sequence-dired' buffer. -Use optional PREFIX and DEPTH to format the string accordingly." - (let ((time (format-time-string "%F %T"))) - (cond - ((and prefix depth) - (format-message "*Denote sequences of prefix `%s' and depth `%s', %s*" prefix depth time)) - ((and prefix (not (string-empty-p prefix))) - (format-message "*Denote sequences of prefix `%s', %s*" prefix time)) - (t - (format "*Denote sequences, %s*" time))))) - -;;;###autoload -(defun denote-sequence-dired (&optional prefix depth) - "Produce a Dired listing of all sequence notes. -Sort sequences from smallest to largest. - -With optional PREFIX string, show only files whose sequence matches it. - -With optional DEPTH as a number, limit the list to files whose sequence -is that many levels deep. For example, 1=1=2 is three levels deep. - -For a more specialised case, see `denote-sequence-find-relatives-dired'." - (interactive - (let ((arg (prefix-numeric-value current-prefix-arg))) - (cond - ((= arg 16) - (list - (denote-sequence-prompt "Limit to files that extend SEQUENCE (empty for all)") - (denote-sequence-depth-prompt))) - ((= arg 4) - (list - (denote-sequence-prompt "Limit to files that extend SEQUENCE (empty for all)"))) - (t - nil)))) - (if-let* ((default-directory (denote-directory)) - (all (if prefix - (denote-sequence-get-all-files-with-prefix prefix) - (denote-sequence-get-all-files))) - (files-with-depth (if depth - (denote-sequence-get-all-files-with-max-depth depth all) - all)) - (files-sorted (denote-sequence-sort-files files-with-depth)) - (buffer-name (denote-sequence--get-dired-buffer-name prefix depth))) - (let ((dired-buffer (dired (cons buffer-name (mapcar #'file-relative-name files-sorted))))) - (with-current-buffer dired-buffer - (setq-local revert-buffer-function - (lambda (&rest _) - ;; FIXME 2025-01-04: Killing the buffer has - ;; the unintended side effect of affecting the - ;; window configuration when we call - ;; `denote-update-dired-buffers'. - (kill-buffer dired-buffer) - (denote-sequence-dired prefix depth))))) - (user-error "No Denote sequences matching those terms"))) - -;;;###autoload -(defun denote-sequence-find-dired (type) - "Like `denote-sequence-find' for TYPE but put the matching files in Dired. -Also see `denote-sequence-dired'." - (interactive - (list (denote-sequence-type-prompt "Find relatives of TYPE" - '(all-parents - parent - siblings - all-children - children)))) - (if-let* ((sequence (denote-sequence-file-p buffer-file-name))) - (if-let* ((default-directory (denote-directory)) - (relatives (delete buffer-file-name - (ensure-list - (denote-sequence-get-relative sequence type)))) - (files-sorted (denote-sequence-sort-files relatives))) - (dired (cons (format-message "*`%s' type relatives of `%s'" type sequence) - (mapcar #'file-relative-name files-sorted))) - (user-error "The sequence `%s' has no relatives of type `%s'" sequence type)) - (user-error "The current file has no sequence"))) - -;; TODO 2025-01-14: We need to have an operation that reparents -;; recursively. This can be done inside of the `denote-sequence-reparent', -;; where if it finds that the current file has children, it prompts -;; for a confirmation and then continues to reparent all of them. -;;;###autoload -(defun denote-sequence-reparent (current-file file-with-sequence) - "Re-parent the CURRENT-FILE to be a child of FILE-WITH-SEQUENCE. -If CURRENT-FILE has a sequence (the Denote file name signature), change -it. Else create a new one. - -When called interactively, CURRENT-FILE is either the current file, or a -special Org buffer (like those of `org-capture'), or the file at point in -Dired. - -When called interactively, prompt for FILE-WITH-SEQUENCE showing only -the files in the variable `denote-directory' which have a sequence. If -no such files exist, throw an error. - -When called from Lisp, CURRENT-FILE is a string pointing to a file. - -When called from Lisp, FILE-WITH-SEQUENCE is either a file with a -sequence (per `denote-sequence-file-p') or the sequence string as -such (per `denote-sequence-p'). In both cases, what matters is to know -the target sequence." - (interactive - (list - (if (denote--file-type-org-extra-p) - denote-last-path-after-rename - (denote--rename-dired-file-or-current-file-or-prompt)) - (denote-sequence-file-prompt - (format "Reparent `%s' to be a child of" - (propertize - (denote--rename-dired-file-or-current-file-or-prompt) - 'face 'denote-faces-prompt-current-name))))) - (let* ((target-sequence (or (denote-sequence-file-p file-with-sequence) - (denote-sequence-p file-with-sequence) - (user-error "No sequence of `denote-sequence-p' found in `%s'" file-with-sequence))) - (new-sequence (denote-sequence--get-new-child target-sequence))) - (denote-rename-file current-file 'keep-current 'keep-current new-sequence 'keep-current))) - -;;;###autoload -(defun denote-sequence-convert (files) - "Convert the sequence scheme of FILES to match `denote-sequence-scheme'. -When called from inside a Denote file, FILES is just the current file. -When called from a Dired buffer, FILES are the marked files. If no -files are marked, then the one at point is considered. - -Do not make any changes if the file among the FILES has no sequence or -if it already matches the value of `denote-sequence-scheme'. A file has -a sequence when it conforms with `denote-sequence-file-p'. - -This command is for users who once used a `denote-sequence-scheme' and -have since decided to switch to another. IT DOES NOT REPARENT OR ANYHOW -CHECK THE RESULTING SEQUENCES FOR DUPLICATES." - (interactive - (list - (if (derived-mode-p 'dired-mode) - (dired-get-marked-files) - buffer-file-name)) - dired-mode) - (unless (listp files) - (setq files (list files))) - (dolist (file files) - (when-let* ((old-sequence (denote-sequence-file-p file)) - (new-sequence (denote-sequence-make-conversion old-sequence :is-complete-sequence))) - (denote-rename-file file 'keep-current 'keep-current new-sequence 'keep-current))) - (denote-update-dired-buffers)) - -(provide 'denote-sequence) -;;; denote-sequence.el ends here diff --git a/tests/denote-test.el b/tests/denote-test.el index 209f906c7e..a6aded1723 100644 --- a/tests/denote-test.el +++ b/tests/denote-test.el @@ -588,215 +588,6 @@ does not involve the time zone." (let ((denote-journal-extras-title-format 'day-date-month-year-24h)) (denote-journal-extras-daily--title-format)))))) -;;;; Tests for denote-sequence.el - -(require 'denote-sequence) - -(ert-deftest dt-denote-sequence--get-new-exhaustive () - "Test if we get the correct parent, child, sibling, or relatives of a sequence. -Use the function `denote-sequence-get-new' for child and sibling with -the numeric and alphanumeric `denote-sequence-scheme', as well as the -function `denote-sequence-get-relative'." - (let* ((denote-sequence-scheme 'numeric) - (denote-directory (expand-file-name "denote-test" temporary-file-directory)) - (files - (mapcar - (lambda (file) - (let ((path (expand-file-name file (denote-directory)))) - (if (file-exists-p path) - path - (with-current-buffer (find-file-noselect path) - (save-buffer) - (kill-buffer (current-buffer))) - path))) - '("20241230T075023==1--test__testing.txt" - "20241230T075023==1=1--test__testing.txt" - "20241230T075023==1=1=1--test__testing.txt" - "20241230T075023==1=1=2--test__testing.txt" - "20241230T075023==1=2--test__testing.txt" - "20241230T075023==1=2=1--test__testing.txt" - "20241230T075023==1=2=1=1--test__testing.txt" - "20241230T075023==2--test__testing.txt" - "20241230T075023==10--test__testing.txt" - "20241230T075023==10=1--test__testing.txt" - "20241230T075023==10=1=1--test__testing.txt" - "20241230T075023==10=2--test__testing.txt" - "20241230T075023==10=10--test__testing.txt" - "20241230T075023==10=10=1--test__testing.txt"))) - (sequences (denote-sequence-get-all-sequences files))) - (should (string= (denote-sequence-get-new 'parent) "11")) - - (should (and (string= (denote-sequence-get-new 'child "1" sequences) "1=3") - (string= (denote-sequence-get-new 'child "1=1" sequences) "1=1=3") - (string= (denote-sequence-get-new 'child "1=1=2" sequences) "1=1=2=1") - (string= (denote-sequence-get-new 'child "1=2" sequences) "1=2=2") - (string= (denote-sequence-get-new 'child "1=2=1" sequences) "1=2=1=2") - (string= (denote-sequence-get-new 'child "2" sequences) "2=1"))) - (should-error (denote-sequence-get-new 'child "11" sequences)) - - (should (and (string= (denote-sequence-get-new 'sibling "1" sequences) "11") - (string= (denote-sequence-get-new 'sibling "1=1" sequences) "1=3") - (string= (denote-sequence-get-new 'sibling "1=1=1" sequences) "1=1=3") - (string= (denote-sequence-get-new 'sibling "1=1=2" sequences) "1=1=3") - (string= (denote-sequence-get-new 'sibling "1=2" sequences) "1=3") - (string= (denote-sequence-get-new 'sibling "1=2=1" sequences) "1=2=2") - (string= (denote-sequence-get-new 'sibling "2" sequences) "11"))) - (should-error (denote-sequence-get-new 'sibling "12" sequences)) - - (should (string= (denote-sequence-get-relative "1=2=1=1" 'parent files) - (expand-file-name "20241230T075023==1=2=1--test__testing.txt" denote-directory))) - (should (string= (denote-sequence-get-relative "10=1=1" 'parent files) - (expand-file-name "20241230T075023==10=1--test__testing.txt" denote-directory))) - (should (string= (denote-sequence-get-relative "10=10=1" 'parent files) - (expand-file-name "20241230T075023==10=10--test__testing.txt" denote-directory))) - (should (equal (denote-sequence-get-relative "1=2=1=1" 'all-parents files) - (list - (expand-file-name "20241230T075023==1--test__testing.txt" denote-directory) - (expand-file-name "20241230T075023==1=2--test__testing.txt" denote-directory) - (expand-file-name "20241230T075023==1=2=1--test__testing.txt" denote-directory)))) - (should (equal (denote-sequence-get-relative "10=1=1" 'all-parents files) - (list - (expand-file-name "20241230T075023==10--test__testing.txt" denote-directory) - (expand-file-name "20241230T075023==10=1--test__testing.txt" denote-directory)))) - (should (equal (denote-sequence-get-relative "10=10=1" 'all-parents files) - (list - (expand-file-name "20241230T075023==10--test__testing.txt" denote-directory) - (expand-file-name "20241230T075023==10=10--test__testing.txt" denote-directory)))) - (should (equal (denote-sequence-get-relative "1=1" 'siblings files) - (list - (expand-file-name "20241230T075023==1=1--test__testing.txt" denote-directory) - (expand-file-name "20241230T075023==1=2--test__testing.txt" denote-directory)))) - (should (equal (denote-sequence-get-relative "10=1" 'siblings files) - (list - (expand-file-name "20241230T075023==10=1--test__testing.txt" denote-directory) - (expand-file-name "20241230T075023==10=2--test__testing.txt" denote-directory) - (expand-file-name "20241230T075023==10=10--test__testing.txt" denote-directory)))) - (should (equal (denote-sequence-get-relative "1" 'children files) - (list - (expand-file-name "20241230T075023==1=1--test__testing.txt" denote-directory) - (expand-file-name "20241230T075023==1=2--test__testing.txt" denote-directory)))) - (should (equal (denote-sequence-get-relative "10" 'children files) - (list - (expand-file-name "20241230T075023==10=1--test__testing.txt" denote-directory) - (expand-file-name "20241230T075023==10=2--test__testing.txt" denote-directory) - (expand-file-name "20241230T075023==10=10--test__testing.txt" denote-directory)))) - (should (equal (denote-sequence-get-relative "1=1" 'all-children files) - (list - (expand-file-name "20241230T075023==1=1=1--test__testing.txt" denote-directory) - (expand-file-name "20241230T075023==1=1=2--test__testing.txt" denote-directory))))) - - (let* ((denote-sequence-scheme 'alphanumeric) - (denote-directory (expand-file-name "denote-test" temporary-file-directory)) - (files - (mapcar - (lambda (file) - (let ((path (expand-file-name file (denote-directory)))) - (if (file-exists-p path) - path - (with-current-buffer (find-file-noselect path) - (save-buffer) - (kill-buffer (current-buffer))) - path))) - '("20241230T075023==1--test__testing.txt" - "20241230T075023==1a--test__testing.txt" - "20241230T075023==1a1--test__testing.txt" - "20241230T075023==1a2--test__testing.txt" - "20241230T075023==1b--test__testing.txt" - "20241230T075023==1b1--test__testing.txt" - "20241230T075023==1b1a--test__testing.txt" - "20241230T075023==2--test__testing.txt" - "20241230T075023==10--test__testing.txt" - "20241230T075023==10a--test__testing.txt" - "20241230T075023==10b--test__testing.txt"))) - (sequences (denote-sequence-get-all-sequences files))) - (should (string= (denote-sequence-get-new 'parent) "11")) - - (should (and (string= (denote-sequence-get-new 'child "1" sequences) "1c") - (string= (denote-sequence-get-new 'child "1a" sequences) "1a3") - (string= (denote-sequence-get-new 'child "1a2" sequences) "1a2a") - (string= (denote-sequence-get-new 'child "1b" sequences) "1b2") - (string= (denote-sequence-get-new 'child "1b1" sequences) "1b1b") - (string= (denote-sequence-get-new 'child "2" sequences) "2a"))) - (should-error (denote-sequence-get-new 'child "11" sequences)) - - (should (and (string= (denote-sequence-get-new 'sibling "1" sequences) "11") - (string= (denote-sequence-get-new 'sibling "1a" sequences) "1c") - (string= (denote-sequence-get-new 'sibling "1a1" sequences) "1a3") - (string= (denote-sequence-get-new 'sibling "1a2" sequences) "1a3") - (string= (denote-sequence-get-new 'sibling "1b" sequences) "1c") - (string= (denote-sequence-get-new 'sibling "1b1" sequences) "1b2") - (string= (denote-sequence-get-new 'sibling "2" sequences) "11"))) - (should-error (denote-sequence-get-new 'sibling "12" sequences)) - - (should (string= (denote-sequence-get-relative "1b1a" 'parent files) - (expand-file-name "20241230T075023==1b1--test__testing.txt" denote-directory))) - (should (string= (denote-sequence-get-relative "10a" 'parent files) - (expand-file-name "20241230T075023==10--test__testing.txt" denote-directory))) - (should (equal (denote-sequence-get-relative "1b1a" 'all-parents files) - (list - (expand-file-name "20241230T075023==1--test__testing.txt" denote-directory) - (expand-file-name "20241230T075023==1b--test__testing.txt" denote-directory) - (expand-file-name "20241230T075023==1b1--test__testing.txt" denote-directory)))) - (should (equal (denote-sequence-get-relative "1a" 'siblings files) - (list - (expand-file-name "20241230T075023==1a--test__testing.txt" denote-directory) - (expand-file-name "20241230T075023==1b--test__testing.txt" denote-directory)))) - (should (equal (denote-sequence-get-relative "10a" 'siblings files) - (list - (expand-file-name "20241230T075023==10a--test__testing.txt" denote-directory) - (expand-file-name "20241230T075023==10b--test__testing.txt" denote-directory)))) - (should (equal (denote-sequence-get-relative "1" 'children files) - (list - (expand-file-name "20241230T075023==1a--test__testing.txt" denote-directory) - (expand-file-name "20241230T075023==1b--test__testing.txt" denote-directory)))) - (should (equal (denote-sequence-get-relative "10" 'children files) - (list - (expand-file-name "20241230T075023==10a--test__testing.txt" denote-directory) - (expand-file-name "20241230T075023==10b--test__testing.txt" denote-directory)))) - (should (equal (denote-sequence-get-relative "1a" 'all-children files) - (list - (expand-file-name "20241230T075023==1a1--test__testing.txt" denote-directory) - (expand-file-name "20241230T075023==1a2--test__testing.txt" denote-directory)))))) - -(ert-deftest dt-denote-sequence-split () - "Test that `denote-sequence-split' splits a sequence correctly." - (should (and (equal (denote-sequence-split "1") '("1")) - (equal (denote-sequence-split "1=1=2") '("1" "1" "2")) - (equal (denote-sequence-split "1za5zx") '("1" "za" "5" "zx"))))) - -(ert-deftest dt-denote-sequence-make-conversion () - "Test that `denote-sequence-make-conversion' converts from alpha to numeric and vice versa." - (should (and (string= (denote-sequence-make-conversion "3") "c") - (string= (denote-sequence-make-conversion "18") "r") - (string= (denote-sequence-make-conversion "26") "z") - (string= (denote-sequence-make-conversion "27") "za") - (string= (denote-sequence-make-conversion "130") "zzzzz") - (string= (denote-sequence-make-conversion "131") "zzzzza") - (string= (denote-sequence-make-conversion "c") "3") - (string= (denote-sequence-make-conversion "r") "18") - (string= (denote-sequence-make-conversion "z") "26") - (string= (denote-sequence-make-conversion "za") "27") - (string= (denote-sequence-make-conversion "zzzzz") "130") - (string= (denote-sequence-make-conversion "zzzzza") "131"))) - (should (and (string= (denote-sequence-make-conversion "1=1=2" :string-is-sequence) "1a2") - (string= (denote-sequence-make-conversion "1a2" :string-is-sequence) "1=1=2") - (string= (denote-sequence-make-conversion "1=27=2=55" :string-is-sequence) "1za2zzc") - (string= (denote-sequence-make-conversion "1za2zzc" :string-is-sequence) "1=27=2=55") - (string= (denote-sequence-make-conversion "1=1=2=2=4=1" :string-is-sequence) "1a2b4a") - (string= (denote-sequence-make-conversion "1a2b4a" :string-is-sequence) "1=1=2=2=4=1"))) - (should-error (denote-sequence-make-conversion "111=a" :string-is-sequence)) - (should-error (denote-sequence-make-conversion "a1" :string-is-sequence))) - -(ert-deftest dt-denote-sequence-increment () - "Test that `denote-sequence-increment' works with numbers and letters." - (should (and (string= (denote-sequence-increment "z") "za") - (string= (denote-sequence-increment "ab") "ac") - (string= (denote-sequence-increment "az") "aza") - (string= (denote-sequence-increment "bbcz") "bbcza"))) - (should (and (string= (denote-sequence-increment "1") "2") - (string= (denote-sequence-increment "10") "11"))) - (should-error (denote-sequence-increment "1=a"))) - (provide 'denote-test) ;;; denote-test.el ends here