Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: consistent zk-link-format and POSIX regexp conversion for grep #68

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
2 changes: 1 addition & 1 deletion zk-org-link.el
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
126 changes: 82 additions & 44 deletions zk.el
Original file line number Diff line number Diff line change
Expand Up @@ -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]\\+"
Expand Down Expand Up @@ -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")

Expand All @@ -175,30 +180,31 @@ 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

(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]]"
"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
Expand Down Expand Up @@ -302,15 +308,23 @@ 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))
(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.
The regexp captures these groups:

Group 1 is the zk ID.
Group 2 is the title."
(zk--format (regexp-quote zk-link-format)
(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."
Expand Down Expand Up @@ -413,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,
Expand All @@ -424,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))
Expand All @@ -437,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)))

Expand Down Expand Up @@ -604,17 +643,15 @@ 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)
"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

Expand Down Expand Up @@ -948,7 +985,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 ()
Expand Down Expand Up @@ -1001,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)
Expand Down
Loading