emacs-orgmode
[Top][All Lists]
Advanced

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

Re: [BUG] colview.el regexp - capture operator when title is empty


From: Sławomir Grochowski
Subject: Re: [BUG] colview.el regexp - capture operator when title is empty
Date: Sun, 25 Aug 2024 14:35:20 +0200

I tried to describe the problem better as well as my thought process:

The `org-columns-compile-format' function has a bug in regexp.
When an empty parentheses `()` is in the 'column format string' -
and it always is when user do not provide a column title - then the
regexp can't capture the operator which is in curly brackets `{}`.

Try yourself with code below:
#+begin_src elisp
(org-columns-compile-format"%ITEM(){operator}") 
; return => (("ITEM" "ITEM" nil nil nil)) 
; should be => (("ITEM" "ITEM" nil operator nil)) 
#+end_src

I'm not very skilled in regex, so following Ihor's advice I decided to
write the regex in the form of 'rx' which is more readable. Below is
the modified function:

#+begin_src elisp
  (defun org-columns-compile-format-rx (fmt)
    "Turn a column format string FMT into an alist of specifications.

    The alist has one entry for each column in the format.  The elements of
    that list are:
    property    the property name, as an upper-case string
    title       the title field for the columns, as a string
    width       the column width in characters, can be nil for automatic width
    operator    the summary operator, as a string, or nil
    printf      a printf format for computed values, as a string, or nil

    This function updates `org-columns-current-fmt-compiled'."
    (setq org-columns-current-fmt-compiled nil)
    (let ((start 0))
      (while (string-match
              (rx "%"
                  (optional (group (+ digit)))
                  (group (one-or-more (in alnum "_-")))
                  (optional "(" (group (one-or-more (not (any ")")))) ")")
                  (optional "{" (group (one-or-more (not (any "}")))) "}")
                  (zero-or-more space))
            fmt start)
        (setq start (match-end 0))
        (let* ((width (and (match-end 1) (string-to-number (match-string 1 
fmt))))
             (prop (match-string-no-properties 2 fmt))
             (title (or (match-string-no-properties 3 fmt) prop))
             (operator (match-string-no-properties 4 fmt)))
        (push (if (not operator) (list (upcase prop) title width nil nil)
                (let (printf)
                  (when (string-match ";" operator)
                    (setq printf (substring operator (match-end 0)))
                    (setq operator (substring operator 0 (match-beginning 0))))
                  (list (upcase prop) title width operator printf)))
              org-columns-current-fmt-compiled)))
      (setq org-columns-current-fmt-compiled
          (nreverse org-columns-current-fmt-compiled))))
#+end_src

I am checking if in this particular case it returns the same result 
as 'org-columns-compile-format': 

#+begin_src elisp
 (org-columns-compile-format-rx "%ITEM(){operator}") ;; => (("ITEM" "ITEM" nil 
nil nil))
#+end_src

#+begin_src elisp
  (equal 
   (org-columns-compile-format "%ITEM(){operator}") 
   (org-columns-compile-format-rx "%ITEM(){operator}")) ;; => t
#+end_src

Yes, in this particular case it works the same. To make it easier to
capture the error, I only take a piece of code responsible for the
regexp:

#+begin_src elisp
(let ((text "%25ITEM(){operator}")
        (pattern (rx "%"
                  (optional (group (+ digit)))
                  (group (one-or-more (in alnum "_-")))
                  (optional "(" (group (one-or-more (not (any ")")))) ")")
                  (optional "{" (group (one-or-more (not (any "}")))) "}")
                  (zero-or-more space))))
    (if (string-match pattern text)
        (mapcar (lambda (i) (match-string i text))
                (number-sequence 0 (/ (length (match-data)) 2)))))
;=> ("%25ITEM" "25" "ITEM" nil)
#+end_src

Through trial and error, I came to a solution that provides correct
results. I changed the expression 'one-or-more' to 'zero-or-more' for
'title' and 'operator':

#+begin_src elisp
(let ((text "%25ITEM(){operator}")
        (pattern (rx "%"
                  (optional (group (+ digit)))
                  (group (one-or-more (in alnum "_-")))
                  (optional "(" (group (zero-or-more (not (any ")")))) ")")
                  (optional "{" (group (zero-or-more (not (any "}")))) "}")
                  (zero-or-more space))))
    (if (string-match pattern text)
        (mapcar (lambda (i) (match-string i text))
                (number-sequence 0 (/ (length (match-data)) 2)))))
;=> ("%25ITEM(){operator}" "25" "ITEM" "" "operator" nil)
#+end_src

This fixed a problem but also changed the return value. Now, when we
have empty parentheses '()', it will return an empty string
instead of null.

Therefore, in the function `org-columns-compile-format`, I added calls
to `org-string-nw-p` for the variables 'title' and 'operator'.

Additionally, I think that adding empty parentheses '()' should be removed
when we do not specify a 'column title'. Because it does not provide
any value. So I added another call to `org-string-nw-p' in function
`org-columns-new'.

Patch below:
<#part type="text/x-diff" 
filename="~/.emacs.d/straight/repos/org/0001-lisp-org-colview.el-org-columns-compile-format-regex.patch"
 disposition=inline>
<#/part>

Regards,
-- 
Slawomir Grochowski



reply via email to

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