From b01a5bdc62331dbfa075dc56b92e8f1f7e301c7e Mon Sep 17 00:00:00 2001 From: Richard Boyechko Date: Sun, 2 Jul 2023 23:53:41 -0700 Subject: [PATCH 1/6] Update format-related docstrings for consistency/helpfulness * `zk-format-function`'s docstring pointed to a very sparse `zk--format` docstring. * Other `*-format` variables had wrong information (`zk-format-function` should never be nil) and repeated information that pertains properly only to `zk-format-id-and-title`. --- zk.el | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/zk.el b/zk.el index de26758..9aabe64 100644 --- a/zk.el +++ b/zk.el @@ -175,8 +175,8 @@ See `zk-current-notes' for details." (defcustom zk-format-function #'zk-format-id-and-title "Function for formatting zk file information. -It should accept three variables: FORMAT-SPEC, ID, and TITLE. See -`zk--format' for details." +It should accept three variables: FORMAT-SPEC, ID, and TITLE. +See `zk-format-id-and-title' for an example." :type 'function) ;; Format variables @@ -190,15 +190,15 @@ replaced by a note's ID." (defcustom zk-link-and-title-format "%t [[%i]]" "Format for link and title when inserted to together. -By default (when `zk-format-function' is nil), the string `%t' will be -replaced by the note's title and `%i' will be replaced by its ID." +See `zk-format-id-and-title' for what the default control +sequences mean." :type 'string) (defcustom zk-completion-at-point-format "[[%i]] %t" "Format for completion table used by `zk-completion-at-point'. -By default (when `zk-format-function' is nil), the string `%t' will be -replaced by the note's title and `%i' will be replaced by its ID." +See `zk-format-id-and-title' for what the default control +sequences mean." :type 'string) ;; Link variables @@ -604,9 +604,9 @@ When NO-PROC is non-nil, bypass `zk--processor'." (defun zk-format-id-and-title (format id title) "Format ID and TITLE based on the `format-spec' FORMAT. -This is the default function set in `zk-format-function' and used by -`zk--format' therwise, replace the sequence `%t' with the TITLE and -`%i' with the ID." +The sequence `%t' in FORMAT is replaced with the TITLE +and `%i' with the ID. This is the default function +that `zk-format-function' is set to." (format-spec format `((?i . ,id) (?t . ,title)))) (defun zk--format (format id title) From b0790e043d6f479f546f31fdaddd1a8ffdc82724 Mon Sep 17 00:00:00 2001 From: Richard Boyechko Date: Sun, 2 Jul 2023 23:31:32 -0700 Subject: [PATCH 2/6] Replace hardcoded title regexp with zk-title-regexp It's generally a good practice to avoid hardcoded values, but this also allows the user to customize what the title looks like. --- zk.el | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/zk.el b/zk.el index 9aabe64..f6d20b6 100644 --- a/zk.el +++ b/zk.el @@ -117,16 +117,21 @@ rendered with spaces." (defcustom zk-id-time-string-format "%Y%m%d%H%M" "Format for new zk IDs. -For supported options, please consult `format-time-string'. -Note: the regexp to find zk IDs is set separately. -If you change this value, set `zk-id-regexp' so that -the zk IDs can be found." +For supported options, consult `format-time-string'. + +Note: The regexp to find zk IDs is set separately. If you change +this value, set `zk-id-regexp' so that the zk IDs can be found." :type 'string) (defcustom zk-id-regexp "\\([0-9]\\{12\\}\\)" "The regular expression used to search for zk IDs. Set it so that it matches strings generated with -`zk-id-format'." +`zk-id-time-string-format'." + :type 'regexp) + +(defcustom zk-title-regexp ".*?" + "The regular expression used to match the zk note's title. +This is only relevant if `zk-link-format' includes the title." :type 'regexp) (defcustom zk-tag-regexp "\\s#[a-zA-Z0-9]\\+" @@ -154,7 +159,7 @@ Must take a single STRING argument." :type 'function) (make-obsolete-variable 'zk-grep-function "The use of the - 'zk-grep-function' variable is deprecated. + 'zk-grep-function' variable is deprecated. 'zk-search-function' should be used instead" "0.5") @@ -302,15 +307,18 @@ Group 1 is the zk ID. Group 2 is the title." (concat "\\(?1:" zk-id-regexp "\\)" "." - "\\(?2:.*?\\)" + "\\(?2:" zk-title-regexp "\\)" "\\." zk-file-extension ".*")) (defun zk-link-regexp () - "Return the correct regexp for zk links. -The value is based on `zk-link-format' and `zk-id-regexp'." - (format (regexp-quote zk-link-format) zk-id-regexp)) + "Return the correct regexp matching zk links. +The value is based on `zk-link-format', `zk-id-regexp', and +`zk-title-regexp'." + (zk--format (regexp-quote zk-link-format) + zk-id-regexp + zk-title-regexp)) (defun zk--file-id (file) "Return the ID of the given zk FILE." From 5c449bfa395f7818290447b7ba14c1c528a46f86 Mon Sep 17 00:00:00 2001 From: Richard Boyechko Date: Sun, 2 Jul 2023 23:22:14 -0700 Subject: [PATCH 3/6] Extend zk-link-regexp to take ID/title & generate targeted regexps Since `zk--grep-file-list` expects a regexp anyway, might as well use the `zk-link-regexp` to generate it rather than doing it locally and repeating code. --- zk.el | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/zk.el b/zk.el index f6d20b6..dacefda 100644 --- a/zk.el +++ b/zk.el @@ -312,13 +312,14 @@ Group 2 is the title." zk-file-extension ".*")) -(defun zk-link-regexp () +(defun zk-link-regexp (&optional id title) "Return the correct regexp matching zk links. -The value is based on `zk-link-format', `zk-id-regexp', and -`zk-title-regexp'." +If ID and/or TITLE are given, use those, generating a regexp that +specifically matches them. Othewrise use `zk-id-regexp' and +`zk-title-regexp', respectively." (zk--format (regexp-quote zk-link-format) - zk-id-regexp - zk-title-regexp)) + (or id zk-id-regexp) + (or title zk-title-regexp))) (defun zk--file-id (file) "Return the ID of the given zk FILE." @@ -956,7 +957,7 @@ brackets \"[[\" initiates completion." (defun zk--backlinks-list (id) "Return list of notes that link to note with ID." - (zk--grep-file-list (regexp-quote (format zk-link-format id)))) + (zk--grep-file-list (zk-link-regexp id))) ;;;###autoload (defun zk-backlinks () From 5855ccca6a438597734e5eade5cadcc089b3ac18 Mon Sep 17 00:00:00 2001 From: Richard Boyechko Date: Tue, 4 Jul 2023 17:26:33 -0700 Subject: [PATCH 4/6] Extend zk-link-regexp to capture same groups as zk-file-name-regexp This increases consistency, which should help to avoid obscure bugs. --- zk.el | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/zk.el b/zk.el index dacefda..aa0ad6f 100644 --- a/zk.el +++ b/zk.el @@ -314,12 +314,16 @@ Group 2 is the title." (defun zk-link-regexp (&optional id title) "Return the correct regexp matching zk links. -If ID and/or TITLE are given, use those, generating a regexp that -specifically matches them. Othewrise use `zk-id-regexp' and -`zk-title-regexp', respectively." +If ID and/or TITLE are given, use those, generating a regexp +that specifically matches them. Othewrise use `zk-id-regexp' +and `zk-title-regexp', respectively. +The regexp captures these groups: + +Group 1 is the zk ID. +Group 2 is the title." (zk--format (regexp-quote zk-link-format) - (or id zk-id-regexp) - (or title zk-title-regexp))) + (concat "\\(?1:" (or id zk-id-regexp) "\\)") + (concat "\\(?2:" (or title zk-title-regexp) "\\)"))) (defun zk--file-id (file) "Return the ID of the given zk FILE." From fd69c0b50c266350c75fbc0e30bdec25a695bbd2 Mon Sep 17 00:00:00 2001 From: Richard Boyechko Date: Mon, 3 Jul 2023 00:00:18 -0700 Subject: [PATCH 5/6] Change zk-link-format to use same %-sequences as other *-format vars Having consistent syntax is helpful to avoid mistakes. Additionally, this change allows 1) simplifying `zk--format` so it doesn't need to handle two different sets of %-sequences, and 2) experimenting with other link markup, such as `[[%i][%t]]` for org-mode links without explicit link type or `[[%i|%t]]` for syntax used by MediaWiki. --- zk-org-link.el | 2 +- zk.el | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/zk-org-link.el b/zk-org-link.el index 2b3c67f..0f12977 100644 --- a/zk-org-link.el +++ b/zk-org-link.el @@ -51,7 +51,7 @@ :help-echo #'zk-org-link--help-echo) ;; Set up org-style link format by setting variables -(setq zk-link-format "[[zk:%s]]") +(setq zk-link-format "[[zk:%i]]") (setq zk-link-and-title-format "[[zk:%i][%t]]") (setq zk-enable-link-buttons nil) diff --git a/zk.el b/zk.el index aa0ad6f..8fe2964 100644 --- a/zk.el +++ b/zk.el @@ -186,10 +186,11 @@ See `zk-format-id-and-title' for an example." ;; Format variables -(defcustom zk-link-format "[[%s]]" +(defcustom zk-link-format "[[%i]]" "Format for inserted links. -Used in conjunction with `format', the string `%s' will be -replaced by a note's ID." + +See `zk-format-id-and-title' for what the default control +sequences mean." :type 'string) (defcustom zk-link-and-title-format "%t [[%i]]" @@ -623,11 +624,9 @@ that `zk-format-function' is set to." (format-spec format `((?i . ,id) (?t . ,title)))) (defun zk--format (format id title) - "Format ID and TITLE based on the `format-spec' FORMAT." - (if (eq format zk-link-format) - (format zk-link-format id) - (funcall zk-format-function format id title))) - + "Format ID and TITLE based on the `format-spec' FORMAT. +This is a wrapper around `zk-format-function', which see." + (funcall zk-format-function format id title)) ;;; Buttons From 7d3cce35abc394adc67feabca1c72af5293070ee Mon Sep 17 00:00:00 2001 From: Richard Boyechko Date: Fri, 14 Jul 2023 17:00:02 -0700 Subject: [PATCH 6/6] Fix bug with zk-backlinks; add zk--posix-regexp to sanitize regexps So far, functions like `zk--grep-file-list` have just passed Elisp-style regexp to `grep` and hoped it works, which it does for zk-ID or other literal strings. Changes to `zk-link-regexp` in PR #63 broke things because it introduced explicit capture groups (`\(?1:...\)`), which is not supported by POSIX regexps that `grep` uses. The new function, `zk--posix-regexp`, does some basic conversion from Elisp-style regexps to POSIX-style. There is package `pcre2el` that does it in a more complete and sophisticated way, but for our purposes, this should be enough. Functions that pass regexps to `grep`, such as `zk--grep-file-list`, `zk--grep-tag-list`, and `zk--grep-link-id-list` are updated to sanitize ("POSIXize") the regexps they pass. The cost is minuscule ("Elapsed time: 0.000379s" for 10,000 calls with a complex regexp). --- zk.el | 56 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/zk.el b/zk.el index 8fe2964..7e55980 100644 --- a/zk.el +++ b/zk.el @@ -427,6 +427,29 @@ file-paths." (buffer-file-name x))) (buffer-list)))) +(defun zk--posix-regexp (regexp &optional basic) + "Convert Elisp-style REGEXP to extended POSIX 1003.2 regexp. +If BASIC is non-nil, convert as much as possible to basic +regexp instead. See manual page `re_format(7)' for details." + (let (result) + ;; 1. For basic REs, warn the user about lack of \| (or) operator + (when (and basic (string-match "\\\\|" regexp)) + ;; FIXME: Basic REs don't have or (\|) operator, as in \(one\|two\); one + ;; would need to pass multiple -e command line args to grep. So, just + ;; treat the operator as normal text, but let the user know. + (warn "Operator \\| (or) cannot be used with basic regexps: %s" regexp) + (setq result regexp)) + ;; 2. Strip numbered groups for extended REs, numbered and shy groups for basic + (setq result + (if basic + (replace-regexp-in-string "\\\\(\\?[0-9]?:" "\\(" regexp nil 'literal) + (replace-regexp-in-string "\\\\(\\?[0-9]:" "\\(" regexp nil 'literal))) + ;; 3. Un-escape special characters (){}|+ for extended REs + (unless basic + (setq result + (replace-regexp-in-string "\\\\\\([(){}+|]\\)" "\\1" result))) + result)) + (defun zk--grep-file-list (str &optional extended invert) "Return a list of files containing regexp STR. If EXTENDED is non-nil, use egrep. If INVERT is non-nil, @@ -438,7 +461,8 @@ return list of files not matching the regexp." " --recursive" " --ignore-case" " --include=\\*." zk-file-extension - " --regexp=" (shell-quote-argument str) + " --regexp=" (shell-quote-argument + (zk--posix-regexp str (not extended))) " " zk-directory " 2>/dev/null")) "\n" t)) @@ -451,14 +475,15 @@ return list of files not matching the regexp." (defun zk--grep-tag-list () "Return list of tags from all notes in zk directory." - (let* ((files (shell-command-to-string (concat - "grep -ohir --include \\*." - zk-file-extension - " -e " - (shell-quote-argument - zk-tag-regexp) - " " - zk-directory " 2>/dev/null"))) + (let* ((files + (shell-command-to-string (concat + "grep -ohir --include \\*." + zk-file-extension + " -e " + (shell-quote-argument + (zk--posix-regexp zk-tag-regexp 'basic)) + " " + zk-directory " 2>/dev/null"))) (list (split-string files "\n" t "\s"))) (delete-dups list))) @@ -1013,12 +1038,13 @@ Select TAG, with completion, from list of all tags in zk notes." (defun zk--grep-link-id-list () "Return list of all ids that appear as links in `zk-directory' files." - (let* ((files (shell-command-to-string (concat - "grep -ohir -e " - (shell-quote-argument - (zk-link-regexp)) - " " - zk-directory " 2>/dev/null"))) + (let* ((files (shell-command-to-string + (concat + "grep -ohir -e " + (shell-quote-argument + (zk--posix-regexp (zk-link-regexp) 'basic)) + " " + zk-directory " 2>/dev/null"))) (list (split-string files "\n" t)) (ids (mapcar (lambda (x)