emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] externals/denote 6da2d7b2e4 11/12: Merge pull request #210 from j


From: ELPA Syncer
Subject: [elpa] externals/denote 6da2d7b2e4 11/12: Merge pull request #210 from jeanphilippegg/slug-functions
Date: Thu, 4 Jan 2024 06:57:54 -0500 (EST)

branch: externals/denote
commit 6da2d7b2e4a8b01065c8f2f96e01d14cd04a3f76
Merge: 15736c648e 63d25e0cfd
Author: Protesilaos Stavrou <info@protesilaos.com>
Commit: GitHub <noreply@github.com>

    Merge pull request #210 from jeanphilippegg/slug-functions
    
    Slug functions
---
 README.org           | 171 ++++++++++++-----------------
 denote.el            | 305 ++++++++++++++++++++++++++++-----------------------
 tests/denote-test.el |  55 ++++------
 3 files changed, 261 insertions(+), 270 deletions(-)

diff --git a/README.org b/README.org
index c18b9a9e6e..02aa3d7863 100644
--- a/README.org
+++ b/README.org
@@ -1341,7 +1341,7 @@ directories.
 Every note produced by Denote follows this pattern by default
 ([[#h:17896c8c-d97a-4faa-abf6-31df99746ca6][Points of entry]]):
 
-: DATE--TITLE__KEYWORDS.EXTENSION
+: DATE==SIGNATURE--TITLE__KEYWORDS.EXTENSION
 
 The =DATE= field represents the date in year-month-day format followed
 by the capital letter =T= (for "time") and the current time in
@@ -1349,9 +1349,19 @@ hour-minute-second notation.  The presentation is 
compact:
 =20220531T091625=.  The =DATE= serves as the unique identifier of each
 note and, as such, is also known as the file's ID or identifier.
 
+File names can include a string of alphanumeric characters in the
+=SIGNATURE= field. Signatures have no clearly defined purpose and are up
+to the user to define. One use-case is to use them to establish
+sequential relations between files (e.g. 1, 1a, 1b, 1b1, 1b2, ...).
+
+Signatures are an optional extension to Denote's file-naming scheme.
+They can be added to newly created files on demand, with the command
+~denote-signature~, or by modifying the value of the user option
+~denote-prompts~.
+
 The =TITLE= field is the title of the note, as provided by the user.
 It automatically gets downcased by default and is also hyphenated
-([[#h:6ae1ab8c-5e36-4216-8e93-f37f4447582c][Contol the letter casing of file 
names]]).  An entry about "Economics
+([[#h:ae8b19a1-7f67-4258-96b3-370a72c43f4e][Sluggification of file name 
components]]).  An entry about "Economics
 in the Euro Area" produces an =economics-in-the-euro-area= string for
 the =TITLE= of the file name.
 
@@ -1360,12 +1370,12 @@ underscore (the separator is inserted automatically).  
Each keyword is
 a string provided by the user at the relevant prompt which broadly
 describes the contents of the entry.
 
-Each of the keywords is a single word, with multiple keywords
-providing the multi-dimensionality needed for advanced searches
-through Denote files.  Users who need to compose a keyword out of
-multiple words are encouraged to apply a letter casing convention such
-as camelCase/CamelCase and set the ~denote-file-name-letter-casing~
-user option accordingly ([[#h:6ae1ab8c-5e36-4216-8e93-f37f4447582c][Contol the 
letter casing of file names]]).
+Each of the keywords is a single word, with multiple keywords providing
+the multi-dimensionality needed for advanced searches through Denote
+files.  Users who need to compose a keyword out of multiple words such
+as camelCase/CamelCase and are encouraged to use the
+~denote-file-name-slug-functions~ user option accordingly
+([[#h:ae8b19a1-7f67-4258-96b3-370a72c43f4e][Sluggification of file name 
components]]).
 
 #+vindex: denote-file-type
 The =EXTENSION= is the file type.  By default, it is =.org= (~org-mode~)
@@ -1391,23 +1401,6 @@ invoking =M-x re-builder=).
 
 [[#h:1a953736-86c2-420b-b566-fb22c97df197][Features of the file-naming scheme 
for searching or filtering]].
 
-As an optional extension to the above, file names can include a string
-of alphanumeric characters in the =SIGNATURE= field.  Signatures have
-no clearly defined purpose and are up to the user to define.  One
-use-case is to use them to establish sequential relations between
-files (e.g. 1, 1a, 1b, 1b1, 1b2, ...).  A full file name with a
-signature looks like this:
-
-: DATE==SIGNATURE--TITLE__KEYWORDS.EXTENSION
-
-The =SIGNATURE= field is anchored by the equals sign and thus retains
-the aforementioned searching/anchoring feature of =--= and =__=.
-
-Signatures are an optional extension to Denote's file-naming scheme.
-They can be added to newly created files on demand, with the command
-~denote-signature~, or by modifying the value of the user option
-~denote-prompts~.
-
 The ~denote-prompts~ can be configured in such ways to yield the
 following file name permutations:
 
@@ -1427,90 +1420,70 @@ scheme we apply guarantees that a listing is readable 
in a variety of
 contexts.  The Denote file-naming scheme is, in essence, an effective,
 low-tech invention.
 
-** Sluggified title, keywords, and signature
+** Sluggification of file name components
 :PROPERTIES:
 :CUSTOM_ID: h:ae8b19a1-7f67-4258-96b3-370a72c43f4e
 :END:
 
-Denote has to be highly opinionated about which characters can be used
-in file names and the file's front matter in order to enforce its
-file-naming scheme.  The variable ~denote-excluded-punctuation-regexp~
-holds the relevant value.  In simple terms:
+Files names can contain any character that the file system
+permits. Denote imposes a few additional restrictions:
+
++ The tokens "==", =__= and =--= are interpreted by Denote and should
+  appear only once. =_= is the separator for keywords.
+
++ The dot character is not allowed in a note's file name, except to
+  indicate the extension.
+
+By default, Denote enforce other rules to file names through the user
+option ~denote-file-name-slug-functions~.
+
+These rules are applied to file names by default:
 
-+ What we count as "illegal characters" are converted into hyphens.
++ What we count as "illegal characters" are removed.  The variable
+  ~denote-excluded-punctuation-regexp~ holds the relevant value.
 
 + Input for a file title is hyphenated.  The original value is
   preserved in the note's contents 
([[#h:13218826-56a5-482a-9b91-5b6de4f14261][Front matter]]).
 
-+ Keywords should not have spaces or other delimiters. If they do,
-  they are removed, meaning that =hello-world= becomes =helloworld=.
-  This is because hyphens in keywords do not work everywhere, such as
-  in Org.
++ Spaces or other delimiters are removed from keywords, meaning that
+  =hello-world= becomes =helloworld=.  This is because hyphens in
+  keywords do not work everywhere, such as in Org.
 
 + Signatures are like the above, but use the equals sign instead of
   hyphens.
 
-All file name components are downcases by default, though users can
-configure this behaviour ([[#h:6ae1ab8c-5e36-4216-8e93-f37f4447582c][Contol 
the letter casing of file names]]).
-Consider a =helloWorld= or =HelloWorld= convention for those cases
-where you would want to have a hyphen between consistuent words of a
-keyword.
++ All file name components are downcased.  Consider a =helloWorld= or
+  =HelloWorld= convention for those cases where you would want to have a
+  hyphen between consistuent words of a keyword.
 
-** Contol the letter casing of file names
-:PROPERTIES:
-:CUSTOM_ID: h:6ae1ab8c-5e36-4216-8e93-f37f4447582c
-:END:
-
-#+vindex: denote-file-name-letter-casing
-The user option ~denote-file-name-letter-casing~ controls the letter
-casing of the individual components of file names 
([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]).
-The default method is to downcase everything.
+#+vindex: denote-file-name-slug-functions
+The user option ~denote-file-name-slug-functions~ can be used to control
+the sluggification of the components of file names 
([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming
+scheme]]).  The default method is explained in the previous section.
 
 The value of this user option is an alist where each element is a cons
 cell of the form =(COMPONENT . METHOD)=.  For example, here is the
 default value:
 
 #+begin_example emacs-lisp
-'((title . downcase)
-  (signature . downcase)
-  (keywords . downcase)
-  (t . downcase))
+'((title . denote-sluggify-title)
+  (signature . denote-sluggify-signature)
+  (keyword . denote-sluggify-keyword))
 #+end_example
 
 What these cons cells of =(COMPONENT . METHOD)= are:
 
 - The =COMPONENT= is an unquoted symbol among =title=, =signature=,
-  =keywords=, which refers to the corresponding component of the file
-  name.  The special ~t~ =COMPONENT= is a fallback value in case the
-  others are not specified.
-
-- The =METHOD= is the letter casing scheme, which is an unquoted
-  symbol of either =downcase= or =verbatim=.  A nil value has the same
-  meaning as =downcase=.  Other non-nil =METHOD= types are reserved
-  for possible future use.
-
-  The =downcase= =METHOD= converts user input for the given
-  =COMPONENT= into lower case.  The benefit of this approach (which is
-  the default behaviour) is that file names remain consistent over the
-  long-term.  The user never needs to account for varying letter
-  casing while working with them.
-
-  The =verbatim= =METHOD= means that Denote will not affect the letter
-  casing of user input when generating the given file name =COMPONENT=.
-  Conventions like CamelCase or camelCase are respected.  The user
-  thus assumes responsibility to keep file names in a good state over
-  the long term.
-
-As an example, we can downcase the title, but preserve the letter
-casing of the signature and keyword components with this:
+  =keyword=, which refers to the corresponding component of the file
+  name.
 
-#+begin_src emacs-lisp
-(setq denote-file-name-letter-casing
-      '((title . downcase)
-        (signature . verbatim)
-        (keywords . verbatim)
-        (t . downcase)))
-#+end_src
+- The =METHOD= is the function to be used to format the given component.
+  This function should take a string as its parameter and return the
+  string formatted for the file name.  In the case of the `keyword'
+  component, the function receives a SINGLE string representing a single
+  keyword and return it formatted for the file name.  Joining the
+  keywords together is handled by Denote.  Note that the `keyword'
+  function is also applied to the keywords of the front matter.
 
 ** Features of the file-naming scheme for searching or filtering
 :PROPERTIES:
@@ -3856,10 +3829,10 @@ might change them without further notice.
 
 #+findex: denote-sluggify
 + Function ~denote-sluggify~ :: Make =STR= an appropriate slug for
-  file names and related 
([[#h:ae8b19a1-7f67-4258-96b3-370a72c43f4e][Sluggified title and keywords]]).
+  file names and related 
([[#h:ae8b19a1-7f67-4258-96b3-370a72c43f4e][Sluggification of file name 
components]]).
 
-#+findex: denote-sluggify-and-join
-+ Function ~denote-sluggify-and-join~ :: Sluggify =STR= while joining
+#+findex: denote-sluggify-keyword
++ Function ~denote-sluggify-keyword~ :: Sluggify =STR= while joining
   separate words.
 
 #+findex: denote-desluggify
@@ -3869,11 +3842,11 @@ might change them without further notice.
 
 #+findex: denote-sluggify-signature
 + Function ~denote-sluggify-signature~ :: Make =STR= an appropriate
-  slug for signatures ([[#h:6ae1ab8c-5e36-4216-8e93-f37f4447582c][Contol the 
letter casing of file names]]).
+  slug for signatures 
([[#h:ae8b19a1-7f67-4258-96b3-370a72c43f4e][Sluggification of file name 
components]]).
 
 #+findex: denote-sluggify-keywords
 + Function ~denote-sluggify-keywords~ :: Sluggify =KEYWORDS=, which is
-  a list of strings ([[#h:ae8b19a1-7f67-4258-96b3-370a72c43f4e][Sluggified 
title and keywords]]).
+  a list of strings ([[#h:ae8b19a1-7f67-4258-96b3-370a72c43f4e][Sluggification 
of file name components]]).
 
 #+findex: denote-filetype-heuristics
 + Function ~denote-filetype-heuristics~ :: Return likely file type of
@@ -3889,8 +3862,8 @@ might change them without further notice.
 
 #+findex: denote-format-file-name
 + Function ~denote-format-file-name~ :: Format file name. =DIR-PATH=,
-  =ID=, =KEYWORDS=, =TITLE-SLUG=, =EXTENSION= and =SIGNATURE-SLUG= are
-  expected to be supplied by ~denote~ or equivalent command.
+  =ID=, =KEYWORDS=, =TITLE=, =EXTENSION= and =SIGNATURE= are expected to
+  be supplied by ~denote~ or equivalent command.
 
   =DIR-PATH= is a string pointing to a directory. It ends with a
   forward slash (the function ~denote-directory~ makes sure this is
@@ -3907,11 +3880,9 @@ might change them without further notice.
   nil value, in which case the relevant file name component is not
   added to the base file name.
 
-  =TITLE-SLUG= and =SIGNATURE-SLUG= are strings which, in principle,
-  are sluggified before passed as arguments here (per
-  ~denote-sluggify~ and ~denote-sluggify-signature~). They can be an
-  empty string or a nil value, in which case their respective file
-  name component is not added to the base file name.
+  =TITLE= and =SIGNATURE= are strings. They can be an empty string, in
+  which case their respective file name component is not added to the
+  base file name.
 
   =EXTENSION= is a string that contains a dot followed by the file
   type extension. It can be an empty string or a nil value, in which
@@ -4004,15 +3975,13 @@ might change them without further notice.
   filter the candidates per the given regular expression.
 
 #+findex: denote-keywords-prompt
-+ Function ~denote-keywords-prompt~ :: Prompt for one or more
-  keywords. Read entries as separate when they are demarcated by the
++ Function ~denote-keywords-prompt~ :: Prompt for one or more keywords.
+  Read entries as separate when they are demarcated by the
   ~crm-separator~, which typically is a comma. With optional
   =PROMPT-TEXT=, use it to prompt the user for keywords. Else use a
   generic prompt. With optional =INITIAL-KEYWORDS= use them as the
-  initial minibuffer text. Process the return value with
-  ~denote-keywords-sort~ and sort with ~string-collate-lessp~ if the
-  user option ~denote-sort-keywords~ is non-nil. [ The optional
-  =INITIAL-KEYWORDS= argument is part of {{{development-version}}}. ]
+  initial minibuffer text. [ The optional =INITIAL-KEYWORDS= argument is
+  part of {{{development-version}}}. ]
 
 #+findex: denote-title-prompt
 + Function ~denote-title-prompt~ :: Prompt for title string. With
diff --git a/denote.el b/denote.el
index 23c7df9be5..04b0750afc 100644
--- a/denote.el
+++ b/denote.el
@@ -154,7 +154,7 @@ directory and also checks if a safe local value should be 
used."
   '("emacs" "philosophy" "politics" "economics")
   "List of strings with predefined keywords for `denote'.
 Also see user options: `denote-infer-keywords',
-`denote-sort-keywords', `denote-file-name-letter-casing'."
+`denote-sort-keywords', `denote-file-name-slug-functions'."
   :group 'denote
   :package-version '(denote . "0.1.0")
   :type '(repeat string))
@@ -501,49 +501,56 @@ and `denote-link-after-creating-with-command'."
   :link '(info-link "(denote) Choose which commands to prompt for")
   :type '(repeat symbol))
 
-(defcustom denote-file-name-letter-casing
-  '((title . downcase)
-    (signature . downcase)
-    (keywords . downcase)
-    (t . downcase))
-  "Specify the method Denote uses to affect the letter casing of file names.
+(defvar denote-file-name-slug-functions
+  '((title . denote-sluggify-title)
+    (signature . denote-sluggify-signature)
+    (keyword . denote-sluggify-keyword))
+  "Specify the method Denote uses to format the components of the file name.
 
 The value is an alist where each element is a cons cell of the
 form (COMPONENT . METHOD).
 
 - The COMPONENT is an unquoted symbol among `title', `signature',
-  `keywords', which refers to the corresponding component of the
-  file name.  The special t COMPONENT is a fallback value in case
-  the others are not specified.
-
-- The METHOD is the letter casing scheme, which is an unquoted
-  symbol of either `downcase' or `verbatim'.  A nil value has the
-  same meaning as `downcase'.  Other non-nil METHOD types are
-  reserved for possible future use.
-
-  The `downcase' METHOD converts user input for the given
-  COMPONENT into lower case.  The benefit of this approach (which
-  is the default behaviour) is that file names remain consistent
-  over the long-term.  The user never needs to account for
-  varying letter casing while working with them.
-
-  The `verbatim' METHOD means that Denote will not affect the
-  letter casing of user input when generating the given file name
-  COMPONENT.  As such, conventions like CamelCase or camelCase
-  are respected.  The user thus assumes responsibility to keep
-  file names in a good state over the long term."
-  :group 'denote
-  :type '(alist
-          :key (choice :tag "File name component"
-                       (const :tag "The --TITLE component of the file name" 
title)
-                       (const :tag "The ==SIGNATURE component of the file 
name" signature)
-                       (const :tag "The __KEYWORDS component of the file name" 
keywords)
-                       (const :tag "Fallback for any unspecified file name 
component" t))
-          :value (choice :tag "Letter casing method"
-                         (const :tag "Downcase file names (default)" downcase)
-                         (const :tag "Accept file name inputs verbatim" 
verbatim)))
-  :link '(info-link "(denote) Contol the letter casing of file names")
-  :package-version '(denote . "2.1.0"))
+  `keyword' (notice the absence of `s', see below), which
+  refers to the corresponding component of the file name.
+
+- The METHOD is the function to be used to format the given
+  component. This function should take a string as its parameter
+  and return the string formatted for the file name. In the case
+  of the `keyword' component, the function receives a SINGLE
+  string representing a single keyword and return it formatted
+  for the file name. Joining the keywords together is handled by
+  Denote.
+
+Note that the `keyword' function is also applied to the keywords
+of the front matter.
+
+By default, if a function is not specified for a component, we
+use `denote-sluggify-title', `denote-sluggify-keyword' and
+`denote-sluggify-signature'.")
+
+(make-obsolete
+ 'denote-file-name-letter-casing
+ 'denote-file-name-slug-functions
+ "3.0.0")
+
+(defvar denote-file-name-deslug-functions
+  '((title . denote-desluggify-title)
+    (signature . denote-desluggify-signature)
+    (keyword . denote-desluggify-keyword))
+  "Specify the method Denote uses to reverse the process of `denote-sluggify'.
+
+Since `denote-sluggify' is destructive, this is just an attempt
+to get back a more human-friendly component. This is useful when
+you want to retrieve a title or signature from the file name and
+display it as the default input in commands such as
+`denote-rename-file'.
+
+See the documentation of `denote-file-name-slug-functions'.
+
+By default, if a function is not specified for a component, we
+use `denote-desluggify-title', `denote-desluggify-keyword' and
+`denote-desluggify-signature'.")
 
 ;;;; Main variables
 
@@ -556,13 +563,13 @@ The note's ID is derived from the date and time of its 
creation.")
 (defconst denote-id-regexp "\\([0-9]\\{8\\}\\)\\(T[0-9]\\{6\\}\\)"
   "Regular expression to match `denote-id-format'.")
 
-(defconst denote-signature-regexp "==\\([[:alnum:][:nonascii:]=]*\\)"
+(defconst denote-signature-regexp "==\\([^.]*?\\)\\(--.*\\|__.*\\|\\..*\\)*$"
   "Regular expression to match the SIGNATURE field in a file name.")
 
-(defconst denote-title-regexp "--\\([[:alnum:][:nonascii:]-]*\\)"
+(defconst denote-title-regexp "--\\([^.]*?\\)\\(--.*\\|__.*\\|\\..*\\)*$"
   "Regular expression to match the TITLE field in a file name.")
 
-(defconst denote-keywords-regexp "__\\([[:alnum:][:nonascii:]_-]*\\)"
+(defconst denote-keywords-regexp "__\\([^.]*?\\)\\(--.*\\|__.*\\|\\..*\\)*$"
   "Regular expression to match the KEYWORDS field in a file name.")
 
 (defconst denote-excluded-punctuation-regexp 
"[][{}!@#$%^&*()=+'\"?,.\|;:~`‘’“”/]*"
@@ -618,6 +625,21 @@ as the aforementioned variables."
       (setq str (replace-regexp-in-string regexp "" str))))
   str)
 
+(defun denote--slug-no-punct-for-signature (str &optional extra-characters)
+  "Remove punctuation (except = signs) from STR.
+
+This works the same way as `denote--slug-no-punct', except that =
+signs are not removed from STR.
+
+EXTRA-CHARACTERS is an optional string. See
+`denote--slug-no-punct' for its documentation."
+  (dolist (regexp (list denote-excluded-punctuation-regexp
+                        denote-excluded-punctuation-extra-regexp
+                        extra-characters))
+    (when (stringp regexp)
+      (setq str (replace-regexp-in-string (string-replace "=" "" regexp) "" 
str))))
+  str)
+
 (defun denote--slug-hyphenate (str)
   "Replace spaces and underscores with hyphens in STR.
 Also replace multiple hyphens with a single one and remove any
@@ -628,25 +650,23 @@ leading and trailing hyphen."
     "-\\{2,\\}" "-"
     (replace-regexp-in-string "_\\|\s+" "-" str))))
 
-(defun denote-letter-case (component args)
-  "Apply letter casing specified by COMPONENT to ARGS.
-COMPONENT is a symbol representing a file name component, as
-described in the user option `denote-file-name-letter-casing'."
-  (if (or (eq (alist-get component denote-file-name-letter-casing) 'verbatim)
-          (eq (alist-get t denote-file-name-letter-casing) 'verbatim))
-      args
-    (funcall #'downcase args)))
-
-(defun denote-sluggify (str &optional component)
+(defun denote-sluggify (component str)
   "Make STR an appropriate slug for file name COMPONENT.
 
-COMPONENT is a symbol used to retrieve the letter casing method
-corresponding to the file name field is references.  COMPONENT is
-described in the user option `denote-file-name-letter-casing'.
+Apply the function specified in `denote-file-name-slug-function'
+to COMPONENT which is one of `title', `signature', `keyword'."
+  (let ((slug-function (alist-get component denote-file-name-slug-functions)))
+    (cond ((eq component 'title)
+           (funcall (or slug-function #'denote-sluggify-title) str))
+          ((eq component 'keyword)
+           (funcall (or slug-function #'denote-sluggify-keyword) str))
+          ((eq component 'signature)
+           (funcall (or slug-function #'denote-sluggify-signature) str)))))
 
-A nil value of COMPONENT has the same meaning as applying
-`downcase' to STR."
-  (denote-letter-case component (denote--slug-hyphenate (denote--slug-no-punct 
str))))
+(make-obsolete
+ 'denote-letter-case
+ 'denote-sluggify
+ "3.0.0")
 
 (defun denote--slug-put-equals (str)
   "Replace spaces and underscores with equals signs in STR.
@@ -658,35 +678,64 @@ any leading and trailing signs."
     "=\\{2,\\}" "="
     (replace-regexp-in-string "_\\|\s+" "=" str))))
 
+(defun denote-sluggify-title (str)
+  "Make STR an appropriate slug for title."
+  (downcase (denote--slug-hyphenate (denote--slug-no-punct str))))
+
 (defun denote-sluggify-signature (str)
-  "Make STR an appropriate slug for signatures.
-Perform letter casing according to `denote-file-name-letter-casing'."
-  (denote-letter-case 'signature (denote--slug-put-equals 
(denote--slug-no-punct str "-+"))))
+  "Make STR an appropriate slug for signature."
+  (downcase (denote--slug-put-equals (denote--slug-no-punct-for-signature str 
"-+"))))
 
-(defun denote-sluggify-and-join (str)
+(defun denote-sluggify-keyword (str)
   "Sluggify STR while joining separate words."
-  (denote-letter-case
-   'keywords
+  (downcase
    (replace-regexp-in-string
     "-" ""
     (denote--slug-hyphenate (denote--slug-no-punct str)))))
 
+(make-obsolete
+ 'denote-sluggify-and-join
+ 'denote-sluggify-keyword
+ "3.0.0")
+
 (defun denote-sluggify-keywords (keywords)
   "Sluggify KEYWORDS, which is a list of strings."
-  (mapcar #'denote-sluggify-and-join keywords))
-
-;; TODO 2023-05-22: Review name of `denote-desluggify' to signify what
-;; the doc string warns about.
-(defun denote-desluggify (str)
+  (mapcar (lambda (keyword)
+            (denote-sluggify 'keyword keyword))
+          keywords))
+
+(defun denote-desluggify (component str)
+  "Attempt to reverse the process of `denote-sluggify' for STR on COMPONENT.
+
+Apply the function specified in `denote-file-name-deslug-function'
+to COMPONENT which is one of `title', `signature', `keyword'."
+  (let ((deslug-function (alist-get component 
denote-file-name-deslug-functions)))
+    (cond ((eq component 'title)
+           (funcall (or deslug-function #'denote-desluggify-title) str))
+          ((eq component 'keyword)
+           (funcall (or deslug-function #'denote-desluggify-keyword) str))
+          ((eq component 'signature)
+           (funcall (or deslug-function #'denote-desluggify-signature) str)))))
+
+(defun denote-desluggify-title (str)
   "Upcase first char in STR and dehyphenate STR, inverting `denote-sluggify'.
 The intent of this function is to be used on individual strings,
 such as the TITLE component of a Denote file name, but not on the
-entire file name.  Put differently, it does not work with
-signatures and keywords."
+entire file name."
   (let ((str (replace-regexp-in-string "-" " " str)))
     (aset str 0 (upcase (aref str 0)))
     str))
 
+;; NOTE 2024-01-01: This is not used for now.
+(defun denote-desluggify-signature (str)
+  "Reverse of `denote-sluggify-signature' for STR."
+  str)
+
+;; NOTE 2023-12-25: This is not used for now.
+(defun denote-desluggify-keyword (str)
+  "Reverse of `denote-sluggify-keyword' for STR."
+  str)
+
 (defun denote--file-empty-p (file)
   "Return non-nil if FILE is empty."
   (zerop (or (file-attribute-size (file-attributes file)) 0)))
@@ -722,8 +771,7 @@ For our purposes, a note must not be a directory, must 
satisfy
 
 (defun denote-file-has-signature-p (file)
   "Return non-nil if FILE has a Denote identifier."
-  (string-match-p denote-signature-regexp
-                  (file-name-nondirectory file)))
+  (denote-retrieve-filename-signature file))
 
 (make-obsolete 'denote-file-directory-p nil "2.0.0")
 
@@ -957,11 +1005,8 @@ PATH must be a Denote-style file name where keywords are 
prefixed
 with an underscore.
 
 If PATH has no such keywords, return nil."
-  (let* ((file-name (file-name-nondirectory path))
-         (kws (when (string-match denote-keywords-regexp file-name)
-                (match-string-no-properties 1 file-name))))
-    (when kws
-      (split-string kws "_"))))
+  (when-let ((kws (denote-retrieve-filename-keywords path)))
+    (split-string kws "_")))
 
 (defun denote--inferred-keywords ()
   "Extract keywords from `denote-directory-files'.
@@ -1014,13 +1059,8 @@ With optional PROMPT-TEXT, use it to prompt the user for
 keywords.  Else use a generic prompt.  With optional
 INITIAL-KEYWORDS use them as the initial minibuffer text.
 
-Process the return value with `denote-keywords-sort' and sort
-with `string-collate-lessp' if the user option
-`denote-sort-keywords' is non-nil.
-
 Return an empty list if the minibuffer input is empty."
-  (denote-keywords-sort
-   (denote--keywords-crm (denote-keywords) prompt-text initial-keywords)))
+  (denote--keywords-crm (denote-keywords) prompt-text initial-keywords))
 
 (defun denote-keywords-sort (keywords)
   "Sort KEYWORDS if `denote-sort-keywords' is non-nil.
@@ -1038,10 +1078,7 @@ KEYWORDS is a list of strings, per 
`denote-keywords-prompt'."
   "Combine KEYWORDS list of strings into a single string.
 Keywords are separated by the underscore character, per the
 Denote file-naming scheme."
-  (mapconcat
-   (lambda (k)
-     (denote-letter-case 'keywords k))
-   keywords "_"))
+  (string-join keywords "_"))
 
 (defun denote--keywords-add-to-history (keywords)
   "Append KEYWORDS to `denote--keyword-history'."
@@ -1516,7 +1553,7 @@ that internally)."
            ((not (string-blank-p title))))
       title
     (if-let ((title (denote-retrieve-filename-title file)))
-        (denote-desluggify title)
+        (denote-desluggify 'title title)
       (file-name-base file))))
 
 (defun denote--retrieve-location-in-xrefs (identifier)
@@ -1544,9 +1581,9 @@ See `denote--retrieve-locations-in-xrefs'."
 
 ;;;;; Common helpers for new notes
 
-(defun denote-format-file-name (dir-path id keywords title-slug extension 
signature-slug)
+(defun denote-format-file-name (dir-path id keywords title extension signature)
   "Format file name.
-DIR-PATH, ID, KEYWORDS, TITLE-SLUG, EXTENSION and SIGNATURE-SLUG are
+DIR-PATH, ID, KEYWORDS, TITLE, EXTENSION and SIGNATURE are
 expected to be supplied by `denote' or equivalent command.
 
 DIR-PATH is a string pointing to a directory.  It ends with a
@@ -1564,11 +1601,9 @@ by `denote-keywords-combine'.  KEYWORDS can be an empty 
list or
 a nil value, in which case the relevant file name component is
 not added to the base file name.
 
-TITLE-SLUG and SIGNATURE-SLUG are strings which, in principle,
-are sluggified before passed as arguments here (per
-`denote-sluggify' and `denote-sluggify-signature').  They can be
-an empty string or a nil value, in which case their respective
-file name component is not added to the base file name.
+TITLE and SIGNATURE are strings. They can be an empty string, in
+which case their respective file name component is not added to
+the base file name.
 
 EXTENSION is a string that contains a dot followed by the file
 type extension.  It can be an empty string or a nil value, in
@@ -1587,12 +1622,12 @@ which case it is not added to the base file name."
    ((not (string-match-p denote-id-regexp id))
     (error "ID `%s' does not match `denote-id-regexp'" id)))
   (let ((file-name (concat dir-path id)))
-    (when (and signature-slug (not (string-empty-p signature-slug)))
-      (setq file-name (concat file-name "==" signature-slug)))
-    (when (and title-slug (not (string-empty-p title-slug)))
-      (setq file-name (concat file-name "--" title-slug)))
+    (when (not (string-empty-p signature))
+      (setq file-name (concat file-name "==" (denote-sluggify 'signature 
signature))))
+    (when (not (string-empty-p title))
+      (setq file-name (concat file-name "--" (denote-sluggify 'title title))))
     (when keywords
-      (setq file-name (concat file-name "__" (denote-keywords-combine 
keywords))))
+      (setq file-name (concat file-name "__" (denote-keywords-combine 
(denote-sluggify-keywords keywords)))))
     (concat file-name extension)))
 
 (defun denote--format-front-matter-title (title file-type)
@@ -1601,9 +1636,9 @@ which case it is not added to the base file name."
 
 (defun denote--format-front-matter-keywords (keywords file-type)
   "Format KEYWORDS according to FILE-TYPE for the file's front matter.
-Apply `denote-letter-case' to KEYWORDS."
-  (let ((kw (denote-sluggify-keywords keywords)))
-    (funcall (denote--keywords-value-function file-type) kw)))
+Apply `denote-sluggify' to KEYWORDS."
+  (let ((kws (denote-sluggify-keywords keywords)))
+    (funcall (denote--keywords-value-function file-type) kws)))
 
 (defun denote--format-front-matter (title date keywords id filetype)
   "Front matter for new notes.
@@ -1621,11 +1656,7 @@ values of `denote-file-type'."
 Use ID, TITLE, KEYWORDS, FILE-TYPE and SIGNATURE to construct
 path to DIR."
   (denote-format-file-name
-   dir id
-   (denote-sluggify-keywords keywords)
-   (denote-sluggify title 'title)
-   (denote--file-extension file-type)
-   (denote-sluggify-signature signature)))
+   dir id keywords title (denote--file-extension file-type) signature))
 
 ;; Adapted from `org-hugo--org-date-time-to-rfc3339' in the `ox-hugo'
 ;; package: <https://github.com/kaushalmodi/ox-hugo>.
@@ -1814,9 +1845,7 @@ When called from Lisp, all arguments are optional.
      (append args nil)))
   (let* ((title (or title ""))
          (file-type (denote--valid-file-type (or file-type denote-file-type)))
-         (kws (if (called-interactively-p 'interactive)
-                  keywords
-                (denote-keywords-sort keywords)))
+         (kws (denote-keywords-sort keywords))
          (date (if (or (null date) (string-empty-p date))
                    (current-time)
                  (denote--valid-date date)))
@@ -2492,15 +2521,16 @@ file-naming scheme."
        (format "Rename `%s' with keywords (empty to remove)" file-in-prompt)
        (denote-convert-file-name-keywords-to-crm (or 
(denote-retrieve-filename-keywords file) "")))
       (denote-signature-prompt
-       (string-replace "=" " " (or (denote-retrieve-filename-signature file) 
""))
+       (or (denote-retrieve-filename-signature file) "")
        (format "Rename `%s' with signature (empty to remove)" file-in-prompt))
       current-prefix-arg)))
   (let* ((dir (file-name-directory file))
          (id (or (denote-retrieve-filename-identifier file)
                  (denote-create-unique-file-identifier file 
(denote--get-all-used-ids) ask-date)))
+         (keywords (denote-keywords-sort keywords))
          (extension (denote-get-file-extension file))
          (file-type (denote-filetype-heuristics file))
-         (new-name (denote-format-file-name dir id keywords (denote-sluggify 
title 'title) extension (denote-sluggify-signature signature)))
+         (new-name (denote-format-file-name dir id keywords title extension 
signature))
          (max-mini-window-height denote-rename-max-mini-window-height))
     (when (or denote-rename-no-confirm (denote-rename-file-prompt file 
new-name))
       (denote-rename-file-and-buffer file new-name)
@@ -2531,14 +2561,15 @@ the changes made to the file: perform them outright."
                  (title (denote-title-prompt
                          (denote--retrieve-title-or-filename file file-type)
                          (format "Rename `%s' with title (empty to remove)" 
file-in-prompt)))
-                 (keywords (denote-keywords-prompt
-                            (format "Rename `%s' with keywords (empty to 
remove)" file-in-prompt)
-                            (denote-convert-file-name-keywords-to-crm (or 
(denote-retrieve-filename-keywords file) ""))))
+                 (keywords (denote-keywords-sort
+                            (denote-keywords-prompt
+                             (format "Rename `%s' with keywords (empty to 
remove)" file-in-prompt)
+                             (denote-convert-file-name-keywords-to-crm (or 
(denote-retrieve-filename-keywords file) "")))))
                  (signature (denote-signature-prompt
-                             (string-replace "=" " " (or 
(denote-retrieve-filename-signature file) ""))
+                             (or (denote-retrieve-filename-signature file) "")
                              (format "Rename `%s' with signature (empty to 
remove)" file-in-prompt)))
                  (extension (denote-get-file-extension file))
-                 (new-name (denote-format-file-name dir id keywords 
(denote-sluggify title 'title) extension (denote-sluggify-signature 
signature))))
+                 (new-name (denote-format-file-name dir id keywords title 
extension signature)))
             (denote-rename-file-and-buffer file new-name)
             (when (denote-file-is-writable-and-supported-p new-name)
               (if (denote--edit-front-matter-p new-name file-type)
@@ -2566,8 +2597,8 @@ Specifically, do the following:
 - retain the file's existing name and make it the TITLE field,
   per Denote's file-naming scheme;
 
-- `denote-letter-case' and sluggify the TITLE, according to our
-  conventions (check the user option `denote-file-name-letter-casing');
+- sluggify the TITLE, according to our conventions (check the
+  user option `denote-file-name-slug-functions');
 
 - prepend an identifier to the TITLE;
 
@@ -2590,18 +2621,19 @@ Specifically, do the following:
   (declare (interactive-only t))
   (interactive nil dired-mode)
   (if-let ((marks (dired-get-marked-files)))
-      (let ((keywords (denote-keywords-prompt "Rename marked files with 
keywords, overwriting existing (empty to ignore/remove)"))
+      (let ((keywords (denote-keywords-sort
+                       (denote-keywords-prompt "Rename marked files with 
keywords, overwriting existing (empty to ignore/remove)")))
             (used-ids (unless (seq-every-p #'denote-file-has-identifier-p 
marks)
                         (denote--get-all-used-ids))))
         (dolist (file marks)
           (let* ((dir (file-name-directory file))
                  (id (or (denote-retrieve-filename-identifier file)
                          (denote-create-unique-file-identifier file used-ids)))
-                 (signature (string-replace "=" " " (or 
(denote-retrieve-filename-signature file) "")))
+                 (signature (or (denote-retrieve-filename-signature file) ""))
                  (file-type (denote-filetype-heuristics file))
                  (title (denote--retrieve-title-or-filename file file-type))
                  (extension (denote-get-file-extension file))
-                 (new-name (denote-format-file-name dir id keywords 
(denote-sluggify title 'title) extension (denote-sluggify-signature 
signature))))
+                 (new-name (denote-format-file-name dir id keywords title 
extension signature)))
             (denote-rename-file-and-buffer file new-name)
             (when (denote-file-is-writable-and-supported-p new-name)
               (if (denote--edit-front-matter-p new-name file-type)
@@ -2639,12 +2671,11 @@ does internally."
   (if-let ((file-type (denote-filetype-heuristics file))
            (title (denote-retrieve-front-matter-title-value file file-type))
            (id (denote-retrieve-filename-identifier file)))
-      (let* ((sluggified-title (denote-sluggify title 'title))
-             (keywords (denote-retrieve-front-matter-keywords-value file 
file-type))
-             (signature (string-replace "=" " " (or 
(denote-retrieve-filename-signature file) "")))
+      (let* ((keywords (denote-retrieve-front-matter-keywords-value file 
file-type))
+             (signature (or (denote-retrieve-filename-signature file) ""))
              (extension (denote-get-file-extension file))
              (dir (file-name-directory file))
-             (new-name (denote-format-file-name dir id keywords 
sluggified-title extension (denote-sluggify-signature signature))))
+             (new-name (denote-format-file-name dir id keywords title 
extension signature)))
         (when (or auto-confirm
                   (denote-rename-file-prompt file new-name))
           (denote-rename-file-and-buffer file new-name)
@@ -2708,7 +2739,7 @@ relevant front matter."
    (list
     (buffer-file-name)
     (denote-title-prompt)
-    (denote-keywords-prompt)))
+    (denote-keywords-sort (denote-keywords-prompt))))
   (when-let ((denote-file-is-writable-and-supported-p file)
              (id (denote-retrieve-filename-identifier file))
              (file-type (denote-filetype-heuristics file)))
@@ -2751,8 +2782,7 @@ of the file.  This needs to be done manually."
          (signature (or (denote-retrieve-filename-signature file) ""))
          (old-extension (denote-get-file-extension file))
          (new-extension (denote--file-extension new-file-type))
-         (new-name (denote-format-file-name
-                    dir id keywords (denote-sluggify title 'title) 
new-extension signature))
+         (new-name (denote-format-file-name dir id keywords title 
new-extension signature))
          (max-mini-window-height denote-rename-max-mini-window-height))
     (when (and (not (eq old-extension new-extension))
                (denote-rename-file-prompt file new-name))
@@ -2828,17 +2858,17 @@ and seconds."
   :group 'denote-faces
   :package-version '(denote . "2.1.0"))
 
-;; For character classes, evaluate: (info "(elisp) Char Classes")
 (defvar denote-faces--file-name-regexp
-  (concat "\\(?1:[0-9]\\{8\\}\\)\\(?10:T\\)\\(?2:[0-9]\\{6\\}\\)"
-          "\\(?:\\(?3:==\\)\\(?4:[[:alnum:][:nonascii:]=]*?\\)\\)?"
-          "\\(?:\\(?5:--\\)\\(?6:[[:alnum:][:nonascii:]-]*?\\)\\)?"
-          "\\(?:\\(?7:__\\)\\(?8:[[:alnum:][:nonascii:]_-]*?\\)\\)?"
+  (concat "\\(?11:[\t\s]+\\|.*/\\)?"
+          "\\(?1:[0-9]\\{8\\}\\)\\(?10:T\\)\\(?2:[0-9]\\{6\\}\\)"
+          "\\(?:\\(?3:==\\)\\(?4:[^.]*?\\)\\)?"
+          "\\(?:\\(?5:--\\)\\(?6:[^.]*?\\)\\)?"
+          "\\(?:\\(?7:__\\)\\(?8:[^.]*?\\)\\)?"
           "\\(?9:\\..*\\)?$")
   "Regexp of file names for fontification.")
 
 (defconst denote-faces-file-name-keywords
-  `((,(concat "\\(?11:[\t\s]+\\|.*/\\)?" denote-faces--file-name-regexp)
+  `((,denote-faces--file-name-regexp
      (11 'denote-faces-subdirectory nil t)
      (1 'denote-faces-date)
      (10 'denote-faces-time-delimiter nil t)
@@ -3969,6 +3999,7 @@ Consult the manual for template samples."
          (id (denote--find-first-unused-id
               (format-time-string denote-id-format date)
               (denote--get-all-used-ids)))
+         (keywords (denote-keywords-sort keywords))
          (directory (if (denote--dir-in-denote-directory-p subdirectory)
                         (file-name-as-directory subdirectory)
                       (denote-directory)))
diff --git a/tests/denote-test.el b/tests/denote-test.el
index f61d9b9b68..07b15bd019 100644
--- a/tests/denote-test.el
+++ b/tests/denote-test.el
@@ -59,7 +59,7 @@ leading and trailing hyphen."
 (ert-deftest denote-test--denote-sluggify ()
   "Test that `denote-sluggify' sluggifies the string.
 To sluggify is to (i) downcase, (ii) hyphenate, (iii) de-punctuate, and (iv) 
remove spaces from the string."
-  (should (equal (denote-sluggify " ___ !~!!$%^ This iS a tEsT ++ ?? ")
+  (should (equal (denote-sluggify 'title " ___ !~!!$%^ This iS a tEsT ++ ?? ")
                  "this-is-a-test")))
 
 (ert-deftest denote-test--denote--slug-put-equals ()
@@ -79,13 +79,13 @@ accounts for what we describe in 
`denote-test--denote--slug-put-equals'."
   (should (equal (denote-sluggify-signature "--- ___ !~!!$%^ This -iS- a tEsT 
++ ?? ")
                  "this=is=a=test")))
 
-(ert-deftest denote-test--denote-sluggify-and-join ()
-  "Test that `denote-sluggify-and-join' sluggifies the string while joining 
words.
+(ert-deftest denote-test--denote-sluggify-keyword ()
+  "Test that `denote-sluggify-keyword' sluggifies the string while joining 
words.
 In this context, to join words is to elimitate any space or
 delimiter between them.
 
 Otherwise, this is like `denote-test--denote-sluggify'."
-  (should (equal (denote-sluggify-and-join "--- ___ !~!!$%^ This iS a - tEsT 
++ ?? ")
+  (should (equal (denote-sluggify-keyword "--- ___ !~!!$%^ This iS a - tEsT ++ 
?? ")
                  "thisisatest")))
 
 (ert-deftest denote-test--denote-sluggify-keywords ()
@@ -98,8 +98,8 @@ The function also account for the value of the user option
 
 (ert-deftest denote-test--denote-desluggify ()
   "Test that `denote-desluggify' upcases first character and de-hyphenates 
string."
-  (should (equal (denote-desluggify "this-is-a-test") "This is a test"))
-  (should (null (equal (denote-desluggify "this=is=a=test") "This is a 
test"))))
+  (should (equal (denote-desluggify 'title "this-is-a-test") "This is a test"))
+  (should (null (equal (denote-desluggify 'title "this=is=a=test") "This is a 
test"))))
 
 (ert-deftest denote-test--denote--file-empty-p ()
   "Test that `denote--file-empty-p' returns non-nil on empty file."
@@ -275,56 +275,56 @@ Extend what we do in 
`denote-test--denote-file-type-extensions'."
     (should-error (denote-format-file-name
                     nil
                     id
-                    (denote-sluggify-keywords kws)
-                    (denote-sluggify title)
+                    kws
+                    title
                     (denote--file-extension 'org)
                     ""))
 
     (should-error (denote-format-file-name
                     ""
                     id
-                    (denote-sluggify-keywords kws)
-                    (denote-sluggify title)
+                    kws
+                    title
                     (denote--file-extension 'org)
                     ""))
 
     (should-error (denote-format-file-name
                    denote-directory ; notice this is the `let' bound value 
without the suffix
                    id
-                   (denote-sluggify-keywords kws)
-                   (denote-sluggify title)
+                   kws
+                   title
                    (denote--file-extension 'org)
                    ""))
 
     (should-error (denote-format-file-name
                    (denote-directory)
                    nil
-                   (denote-sluggify-keywords kws)
-                   (denote-sluggify title)
+                   kws
+                   title
                    (denote--file-extension 'org)
                    ""))
 
     (should-error (denote-format-file-name
                    (denote-directory)
                    ""
-                   (denote-sluggify-keywords kws)
-                   (denote-sluggify title)
+                   kws
+                   title
                    (denote--file-extension 'org)
                    ""))
 
     (should-error (denote-format-file-name
                    (denote-directory)
                    "0123456"
-                   (denote-sluggify-keywords kws)
-                   (denote-sluggify title)
+                   kws
+                   title
                    (denote--file-extension 'org)
                    ""))
 
     (should (equal (denote-format-file-name
                     (denote-directory)
                     id
-                    (denote-sluggify-keywords kws)
-                    (denote-sluggify title)
+                    kws
+                    title
                     (denote--file-extension 'org)
                     "")
                    "/tmp/test-denote/20231128T055311--some-test__one_two.org"))
@@ -341,19 +341,10 @@ Extend what we do in 
`denote-test--denote-file-type-extensions'."
     (should (equal (denote-format-file-name
                     (denote-directory)
                     id
-                    nil
-                    nil
-                    (denote--file-extension 'org)
-                    "")
-                   "/tmp/test-denote/20231128T055311.org"))
-
-    (should (equal (denote-format-file-name
-                    (denote-directory)
-                    id
-                    (denote-sluggify-keywords kws)
-                    (denote-sluggify title)
+                    kws
+                    title
                     (denote--file-extension 'org)
-                    (denote-sluggify-signature "sig"))
+                    "sig")
                    
"/tmp/test-denote/20231128T055311==sig--some-test__one_two.org"))))
 
 (ert-deftest denote-test--denote-get-file-extension ()



reply via email to

[Prev in Thread] Current Thread [Next in Thread]