Add user option denote-file-name-letter-casing

This commit is contained in:
Protesilaos Stavrou 2023-10-09 20:08:12 +03:00
parent ecb5f8501b
commit 4fa3433402
No known key found for this signature in database
GPG key ID: 99BD6459CD5CA3EA
2 changed files with 153 additions and 29 deletions

View file

@ -1091,7 +1091,8 @@ the following:
- retains the file's existing name and make it the =TITLE= field, per
Denote's file-naming scheme;
- downcases and sluggifies the =TITLE=, per our conventions;
- sluggifies the =TITLE= and adjusts its letter casing, per our
conventions ([[*Contol the letter casing of file names][Contol the letter casing of file names]]);
- prepends an identifier to the =TITLE=;
@ -1268,8 +1269,9 @@ 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.
The =TITLE= field is the title of the note, as provided by the user. It
automatically gets downcased and hyphenated. An entry about "Economics
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
in the Euro Area" produces an =economics-in-the-euro-area= string for
the =TITLE= of the file name.
@ -1363,15 +1365,65 @@ holds the relevant value. In simple terms:
+ What we count as "illegal characters" are converted into hyphens.
+ Input for a file title is hyphenated and downcased. The original
value is preserved in the note's contents ([[#h:13218826-56a5-482a-9b91-5b6de4f14261][Front matter]]).
+ 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 converted into hyphens. Keywords are always downcased.
are converted into hyphens.
+ 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]]).
** Contol the letter casing of file names
:PROPERTIES:
:CUSTOM_ID: h:6ae1ab8c-5e36-4216-8e93-f37f4447582c
:END:
[ Part of {{{development-version}}}. ]
#+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.
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))
#+end_example
What these cons cells of =(COMPONENT . METHOD)= are:
- The =COMPONENT= is an unquoted symbol among =title=, =signature=,
=keywords=, which refer 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.
** Features of the file-naming scheme for searching or filtering
:PROPERTIES:
:CUSTOM_ID: h:1a953736-86c2-420b-b566-fb22c97df197

118
denote.el
View file

@ -603,9 +603,68 @@ leading and trailing hyphen."
"-\\{2,\\}" "-"
(replace-regexp-in-string "_\\|\s+" "-" str))))
(defun denote-sluggify (str)
"Make STR an appropriate slug for file names and related."
(downcase (denote--slug-hyphenate (denote--slug-no-punct str))))
(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.
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 refer 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)))
:package-version '(denote . "2.1.0"))
(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)
"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'.
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))))
(define-obsolete-function-alias
'denote--sluggify
@ -622,13 +681,21 @@ any leading and trailing signs."
"=\\{2,\\}" "="
(replace-regexp-in-string "_\\|\s+" "=" str))))
(defun denote-sluggify-signature (str)
"Make STR an appropriate slug for signatures."
(downcase (denote--slug-put-equals (denote--slug-no-punct str))))
(defun denote-sluggify-signature (str &optional component)
"Make STR an appropriate slug for signatures.
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'.
A nil value of COMPONENT has the same meaning as applying
`downcase' to STR."
(denote-letter-case component (denote--slug-put-equals (denote--slug-no-punct str))))
(defun denote-sluggify-and-join (str)
"Sluggify STR while joining separate words."
(downcase
(denote-letter-case
'keywords
(replace-regexp-in-string
"-" ""
(denote--slug-hyphenate (denote--slug-no-punct str)))))
@ -641,11 +708,12 @@ any leading and trailing signs."
(defun denote-sluggify-keywords (keywords)
"Sluggify KEYWORDS, which is a list of strings."
(if (listp keywords)
(mapcar
(if denote-allow-multi-word-keywords
#'denote-sluggify
#'denote-sluggify-and-join)
keywords)
;; FIXME 2023-10-09: What to do with `denote-allow-multi-word-keywords
(mapcar
(if denote-allow-multi-word-keywords
#'denote-sluggify
#'denote-sluggify-and-join)
keywords)
(error "`%s' is not a list" keywords)))
(define-obsolete-function-alias
@ -1024,7 +1092,10 @@ KEYWORDS is a list of strings, per `denote-keywords-prompt'."
(defun denote--keywords-combine (keywords)
"Format KEYWORDS output of `denote-keywords-prompt'."
(mapconcat #'downcase keywords "_"))
(mapconcat
(lambda (k)
(denote-letter-case 'keywords k))
keywords "_"))
(defun denote--keywords-add-to-history (keywords)
"Append KEYWORDS to `denote--keyword-history'."
@ -1544,8 +1615,8 @@ are expected to be supplied by `denote' or equivalent command."
(defun denote--format-front-matter-keywords (keywords file-type)
"Format KEYWORDS according to FILE-TYPE for the file's front matter.
Apply `downcase' to KEYWORDS."
(let ((kw (mapcar #'downcase (denote-sluggify-keywords keywords))))
Apply `denote-letter-case' to KEYWORDS."
(let ((kw (denote-sluggify-keywords keywords)))
(funcall (denote--keywords-value-function file-type) kw)))
(make-obsolete-variable 'denote-text-front-matter-delimiter nil "0.6.0")
@ -1568,10 +1639,10 @@ construct path to DIR."
(denote-format-file-name
dir id
(denote-sluggify-keywords keywords)
(denote-sluggify title)
(denote-sluggify title 'title)
(denote--file-extension file-type)
(when signature
(denote-sluggify-signature signature))))
(denote-sluggify-signature signature 'signature))))
;; Adapted from `org-hugo--org-date-time-to-rfc3339' in the `ox-hugo'
;; package: <https://github.com/kaushalmodi/ox-hugo>.
@ -2420,7 +2491,7 @@ files)."
(extension (denote-get-file-extension file))
(file-type (denote-filetype-heuristics file))
(new-name (denote-format-file-name
dir id keywords (denote-sluggify title) extension signature))
dir id keywords (denote-sluggify title 'title) extension signature))
(max-mini-window-height 0.33)) ; allow minibuffer to be resized
(when (denote-rename-file-prompt file new-name)
(denote-rename-file-and-buffer file new-name)
@ -2462,7 +2533,7 @@ of the file. This needs to be done manually."
(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) new-extension))
dir id keywords (denote-sluggify title 'title) new-extension))
(max-mini-window-height 0.33)) ; allow minibuffer to be resized
(when (and (not (eq old-extension new-extension))
(denote-rename-file-prompt file new-name))
@ -2480,7 +2551,8 @@ Specifically, do the following:
- retain the file's existing name and make it the TITLE field,
per Denote's file-naming scheme;
- downcase and sluggify the TITLE, per our conventions;
- `denote-letter-case' and sluggify the TITLE, per our
conventions (check the user option `denote-file-name-letter-casing');
- prepend an identifier to the TITLE;
@ -2520,7 +2592,7 @@ Specifically, do the following:
(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) extension signature)))
(new-name (denote-format-file-name dir id keywords (denote-sluggify title '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)
@ -2573,7 +2645,7 @@ proceed with the renaming."
;; want to throw an exception if any is nil.
dir id
(or (denote-retrieve-keywords-value file file-type) nil)
(denote-sluggify title) extension
(denote-sluggify title 'title) extension
(or (denote-retrieve-filename-signature file) nil))))
(when (or auto-confirm
(denote-rename-file-prompt file new-name))
@ -2627,7 +2699,7 @@ their respective front matter."
(keywords (denote-retrieve-keywords-value file file-type))
(extension (denote-get-file-extension file))
(new-name (denote-format-file-name
dir id keywords (denote-sluggify title) extension signature)))
dir id keywords (denote-sluggify title 'title) extension signature)))
(denote-rename-file-and-buffer file new-name)))
(revert-buffer))
(user-error "No marked files; aborting")))