ledger-mode
ledger-mode copied to clipboard
Report breaks subsequent format specifiers when some is not given a value
Hello, I've been trying to extend the report functionalities to create my own kind of report. I'd like to first prompt the user to apply the arguments and later give a report query. This is the code I've wrote based on reading the documentation:
;; add my custom config
(ledger-reports-add "custom/balance" "%(binary) %(myargs) -f %(ledger-file) bal %(myquery)")
(defun ledger-report-queryvalue-format-specifier ()
"Return report query."
(ledger-read-string-with-default "Report query" nil))
(defun ledger-report-argsvalue-format-specifier ()
"Return args."
(ledger-read-string-with-default "Args" nil))
(add-to-list
'ledger-report-format-specifiers
(cons "myquery" #'ledger-report-queryvalue-format-specifier))
(add-to-list
'ledger-report-format-specifiers
(cons "myargs" #'ledger-report-argsvalue-format-specifier))
Now inside a ledger file I run the following command from the minibuffer:
(ledger-report "custom/balance" nil) ;; execute it and in the next prompts I've input the following
> Args: --flat
> Report query: assets
In this scenario it works correctly because I've input something, however if I had passed no input to args, report breaks trying to execute the following command:
/bin/bash: -c: line 0: syntax error near unexpected token `('
/bin/bash: -c: line 0: `ledger --columns 104 --color --force-color '' -f %(ledger-file) bal assets'
If I had input something in the args prompt, but no report query, then it would have worked. Is this a bug or can I possibly fix with a different function implementation for retrieving user input?
Thanks
Here's the docstring for ledger-report-format-specifiers:
An alist mapping ledger report format specifiers to implementing functions.
The function is called with no parameters and expected to return a string, or a list of strings, that should replace the format specifier. Single strings are quoted with `shell-quote-argument'; lists of strings are simply concatenated (no quoting).
I'm guessing that if argsvalue-format-specifier returned a list of strings, they would not be quoted?
@bcc32 good guess, I've changed the code to return, when not provided a value, an empty string "", a string with a space " ", a list of strings '("") and still the same problem...
The list of strings: '("") works fine for me, and doesn't add a quoted string to the command. I would expect the empty string and string with a space to add a quoted string to the command.
In particular, I added to ledger-reports:
("custom" "ledger custom %(testing)")
and to ledger-report-format-specifiers:
("testing" . bcc32-ledger-testing)
(defun bcc32-ledger-testing ()
'(""))
Running the custom report shows that the command line is ledger custom rather than ledger custom ''.
@bcc32 Yes, for a single argument it works, however as I mentioned in the title, the problem happens when you have subsequent arguments. To use your own example, if I modify it to the following:
(ledger-reports-add "custom" "ledger custom %(testing) %(other)")
(defun bcc32-ledger-testing ()
'(""))
(add-to-list 'ledger-report-format-specifiers (cons "testing" #'bcc32-ledger-testing))
(add-to-list 'ledger-report-format-specifiers (cons "other" #'bcc32-ledger-testing))
It breaks because the next other specifier is not expanded as the first one...
/bin/bash: -c: line 0: syntax error near unexpected token `('
/bin/bash: -c: line 0: `ledger custom %(other)'
Ah, my mistake. It appears this can potentially happen whenever a format specifier expands to something shorter than the format specifier itself:
(defun bcc32-ledger-testing-1 ()
"a")
(defun bcc32-ledger-testing-2 ()
"b")
(add-to-list 'ledger-reports '("custom" "ledger custom %(testing-1) %(testing-2)"))
I see that %(testing-1) is expanded to a, but %(testing-2) is not expanded.
I believe the culprit is this condition in the while loop in ledger-report-expand-format-specifiers:
(defun ledger-report-expand-format-specifiers (report-cmd)
"Expand format specifiers in REPORT-CMD with thing under point."
(save-match-data
(let ((expanded-cmd report-cmd))
(set-match-data (list 0 0))
(while (string-match "%(\\([^)]*\\))" expanded-cmd
(if (> (length expanded-cmd) (match-end 0))
(match-end 0)
(1- (length expanded-cmd))))
(let* ((specifier (match-string 1 expanded-cmd))
(f (cdr (assoc specifier ledger-report-format-specifiers))))
(if f
(let* ((arg (save-match-data
(with-current-buffer ledger-buf
(funcall f))))
(quoted (if (listp arg)
(mapconcat #'identity arg " ")
(shell-quote-argument arg))))
(setq expanded-cmd (replace-match quoted t t expanded-cmd))))))
expanded-cmd)))
Perhaps, rather than searching forwarding from the end of the match, this should search forward from the beginning of the match, plus one (to avoid looping infinitely when the match isn't replaced)?
i.e.,
- (if (> (length expanded-cmd) (match-end 0))
- (match-end 0)
+ (if (> (length expanded-cmd) (1+ (match-beginning 0)))
+ (1+ (match-beginning 0))