Getting text of org-mode elements using org-element-map - emacs

I'm attempting to write an elisp function(s) to extract text from org files. Specifically, I want to be able to convert the values in an org-table into a list of lists, so that I can populate org-feeds-alist at startup time from a file outside of my .emacs.
My functions look like this:
(defun·org-config-parse-get-table-rows·(file)
"Return·table·rows·minus·header"
(with-temp-buffer
··(insert-file-contents·file)
··(cddr·(org-element-map·(org-element-parse-buffer)·'(table-row)·'identity))))
(defun·org-config-parse-get-table-cells·(file)
··(org-element-map·(org-config-parse-get-table-rows·file)·'(table-cell)·'identity))
I am testing it with the following table:
|·Name···········|·Fav·Color·|·Age·|·Sex····|
|----------------+-----------+-----+--------|
|·Jim············|·Blue······|··19·|·Male···|
|·Jane···········|·Green·····|··18·|·Female·|
|·Ort'hlrothl'gr·|·Unkown····|·-29·|·???····|
The closest I am able to get to retrieving the text of a single cell in the table is with the following:
(car·(last·(car·(org-config-parse-get-table-cells·"test.org"))))
which evaluates to:
#("Jim" 0 3
(:parent
...
Given a list of org elements returned by org-element-parse-buffer, what is the proper way of extracting the text of those elements as a string?
Thanks in advance.

As I understood, you want a list of lists from an org-mode table.
Here are the relevant bits of org-table-export:
(defun orgtbl->lists ()
(unless (org-at-table-p) (user-error "No table at point"))
(org-table-align)
(let* ((beg (org-table-begin))
(end (org-table-end))
(txt (buffer-substring-no-properties beg end))
(skip nil)
(lines (nthcdr 0 (org-split-string txt "[ \t]*\n[ \t]*")))
(lines (org-table-clean-before-export lines))
(i0 (if org-table-clean-did-remove-column 2 1))
(table (mapcar
(lambda (x)
(if (string-match org-table-hline-regexp x)
'hline
(org-remove-by-index
(org-split-string (org-trim x) "\\s-*|\\s-*")
nil i0)))
lines)))
table))
You can look at the original function if something is still missing.

Related

completion-at-point function that returns the cdr

I need some help understanding completion-at-point.
I have this minimal example, where I want to:
activate when I type "#"
search/complete on candidates car ...
... but return cdr, so result at point is, for example "#doe" (though I may need to extend this later to drop the "#" in some cases, like with LaTeX).
The actual use case is to insert a citation key in a document, but search on author, title, etc. The intention is for this to be used with solutions like corfu and company-capf.
In that code, which is a front-end to bibtex-completion like helm-bibtex and ivy-bibtex, I have a core bibtex-actions-read function based on completing-read-multiple for minibuffer completion.
With this capf, I want to use the same cached data to complete against for at-point completion.
With this test example, I get 1 and 2, which is what I want on the UI end.
(defun test-capf ()
"My capf."
(when (looking-back "#[a-zA-Z]*")
(list
(save-excursion
(backward-word)
(point))
(point)
(lambda (str pred action)
(let ((candidates '(("a title doe" . "doe")
("different title jones" . "jones")
("nothing smith" . "smith"))))
(complete-with-action action candidates str pred))))))
But how do I adapt it to this to add 3? That is, if I type "#not", corfu or company should display "nothing smith", and if I select that item, it should return "#smith" at-point.
Note: my package pretty much depends on completion-styles like orderless, so order is of course not significant.
Do I need to use an :exit-function here?
For completeness, here's the current actual function, which now says "no matches" when I try to use it.
(defun bibtex-actions-complete-key-at-point ()
"Complete citation key at point.
When inserting '#' in a buffer the capf UI will present user with
a list of entries, from which they can narrow against a string
which includes title, author, etc., and then select one. This
function will then return the key 'key', resulting in '#key' at
point."
;; FIX current function only returns "no match"
;; TODO this regex needs to adapt for mode/citation syntax
(when (looking-back "#[a-zA-Z]+" 5)
(let* ((candidates (bibtex-actions--get-candidates))
(begin (save-excursion (backward-word) (point)))
(end (point)))
(list begin end candidates :exclusive 'no
;; I believe I need an exit-function so I can insert the key instead
;; of the candidate string.
:exit-function
(lambda (chosen status)
(when (eq status 'finished)
(cdr (assoc chosen candidates))))))))
Any other tips or suggestions?
This Q&A is related, but I can't figure out how to adapt it.
Why not just keep the completion candidates in your completion table, not conses?
There are some useful wrappers in minibuffer.el around completion tables. In this case you could use completion-table-dynamic, as a wrapper to use a function as the COLLECTION argument to complete-with-action.
I think the more efficient way would just collect the cdrs of your current candidates and allow the C implementations of all-completions to find matches
(complete-with-action action (mapcar #'cdr candidates) str pred)
Or, calling a function to return current candidates
(completion-table-dynamic
(lambda (_str)
(mapcar #'cdr (my-current-candidates))))
Or, filtering in elisp
(let ((candidates '((...)))
(beg '...)
(end '...))
;; ...
(list beg end
(completion-table-dynamic
(lambda (str)
(cl-loop for (a . b) in candidates
if (string-prefix-p str a)
collect b)))))
The solution was an exit-function, with body like this:
(delete-char (- (length str)))
(insert (cdr (assoc str candidates)))))

Haskell with emacs org-mode: Variable not in scope

After wandering off in frustration from before, I've decided to try Haskell in Emacs org-mode again. I'm using Haskell stack-ghci (8.6.3), Emacs 26.2, org-mode 9.2.3 set up with intero. This code block
#+begin_src haskell :results raw :session *haskell*
pyth2 :: Int -> [(Int, Int, Int)]
pyth2 n =
[ (x, y, z)
| x <- [1 .. n]
, y <- [x .. n]
, z <- [y .. n]
, x ^ 2 + y ^ 2 == z ^ 2
]
#+end_src
produces this RESULTS:
*Main| *Main| *Main| *Main| *Main|
<interactive>:59:16: error: Variable not in scope: n
<interactive>:60:16: error: Variable not in scope: n
<interactive>:61:16: error: Variable not in scope: n
However, this
#+begin_src haskell :results raw
tripleMe x = x + x + x
#+end_src
works fine. I've added the :set +m to both ghci.conf and the individual code block to no effect. This code works fine in a separate hs file run in a separate REPL. The pyth2 code in a separate file also can be called from the org-mode started REPL and run just fine as well. Not sure how to proceed. Can include Emacs init info if necessary.
Over on the org-mode mailing list I got an answer that basically is saying the same as you, D. Gillis. He had a similar work-around that actually is more org-mode-centric. Under a heading where your code blocks will be put this "drawer"
:PROPERTIES:
:header-args:haskell: :prologue ":{\n" :epilogue ":}\n"
:END:
and then (possibly in a local variable) run
#+begin_src haskell :results output
:set prompt-cont ""
#+end_src
For reasons unknown I've had to include the :results output otherwise a cryptic error of "expecting a string" happens.
On a few other notes, haskell babel doesn't respond/care about the :session option, i.e., when you run a code block, a REPL *haskell* starts and that will be the sole REPL. Also, a haskell-mode started REPL doesn't play well with an existing org-mode initiated REPL, i.e., if you start a REPL from haskell-mode, it kills the original org-mode *haskkell*REPL, and any new attempt to run org-mode code blocks can't see this new, non-*haskell*REPL. Then if you kill the haskell-mode REPL and try to run org-mode blocks, you get
executing Haskell code block...
inferior-haskell-start-process: List contains a loop: ("--no-build" "--no-load" "--ghci-options=-ferror-spans" "--no-build" "--no-load" . #2)
... you're hosed -- and nothing seems to shake it, not any restart/refresh, nor killing, reloading the file, i.e., a complete restart of Emacs is necessary. Anyone knowing a better solution, please tells usses.
This is a GHCi issue.
The same error occurs when your code is copied directly into GHCi, which also gives a parse error when it encounters the new line after the equal sign. This first error isn't showing up here because org-babel only shows the value of the last expression (in this case, the error caused by the list comprehension).
I'm not entirely familiar with how Haskell-mode sends the code to GHCi, but it looks like it involves loading in the buffer into GHCi as a file, which may be why you didn't have this problem working from the hs file.
There are a few options to fix this, none of which are completely ideal:
Move some portion of the list into the first line (e.g. the first line could be pyth2 n = [).
Wrap the entire function definition with :{ and :}.
Write an Elisp function to modify what is being sent to GHCi and then changes it back after it is evaluated.
The first two options require you to format your code in a form that the GHCi will accept. In your example case, the first option may not be too bad, but this won't always be so trivial for all multi-line declarations (e.g. pattern-matching function declarations). The downside to the second option is that it requires adding brackets to the code that shouldn't be there in real source code.
To fix the issue of extraneous brackets being added, I've written an Elisp command (my-org-babel-execute-haskell-blocks) that places these brackets around code blocks that it finds, evaluates the region, and then deletes the brackets. Note that this function requires that blocks be separated from all other code with at least one empty line.
Calling my-org-babel-execute-haskell-blocks on your example declares the function without any errors.
EDIT: The previous function I gave failed to work on pattern matching declarations. I've rewritten the function to fix this issue as well as to be comment aware. This new function should be significantly more useful. However, it's worth noting that I didn't handle multi-line comments in a sophisticated manner, so code blocks with multi-line comments may not be wrapped properly.
(defun my-org-babel-execute-haskell-blocks ()
"Wraps :{ and :} around all multi-line blocks and then evaluates the source block.
Multi-line blocks are those where all non-indented, non-comment lines are declarations using the same token."
(interactive)
(save-excursion
;; jump to top of source block
(my-org-jump-to-top-of-block)
(forward-line)
;; get valid blocks
(let ((valid-block-start-ends (seq-filter #'my-haskell-block-valid-p (my-get-babel-blocks))))
(mapcar #'my-insert-haskell-braces valid-block-start-ends)
(org-babel-execute-src-block)
(mapcar #'my-delete-inserted-haskell-braces (reverse valid-block-start-ends)))))
(defun my-get-blocks-until (until-string)
(let ((block-start nil)
(block-list nil))
(while (not (looking-at until-string))
(if (looking-at "[[:space:]]*\n")
(when (not (null block-start))
(setq block-list (cons (cons block-start (- (point) 1))
block-list)
block-start nil))
(when (null block-start)
(setq block-start (point))))
(forward-line))
(when (not (null block-start))
(setq block-list (cons (cons block-start (- (point) 1))
block-list)))))
(defun my-get-babel-blocks ()
(my-get-blocks-until "#\\+end_src"))
(defun my-org-jump-to-top-of-block ()
(forward-line)
(org-previous-block 1))
(defun my-empty-line-p ()
(beginning-of-line)
(= (char-after) 10))
(defun my-haskell-type-declaration-line-p ()
(beginning-of-line)
(and (not (looking-at "--"))
(looking-at "^.*::.*$")))
(defun my-insert-haskell-braces (block-start-end)
(let ((block-start (car block-start-end))
(block-end (cdr block-start-end)))
(goto-char block-end)
(insert "\n:}")
(goto-char block-start)
(insert ":{\n")))
(defun my-delete-inserted-haskell-braces (block-start-end)
(let ((block-start (car block-start-end))
(block-end (cdr block-start-end)))
(goto-char block-start)
(delete-char 3)
(goto-char block-end)
(delete-char 3)))
(defun my-get-first-haskell-token ()
"Gets all consecutive non-whitespace text until first whitespace"
(save-excursion
(beginning-of-line)
(let ((starting-point (point)))
(re-search-forward ".*?[[:blank:]\n]")
(goto-char (- (point) 1))
(buffer-substring-no-properties starting-point (point)))))
(defun my-haskell-declaration-line-p ()
(beginning-of-line)
(or (looking-at "^.*=.*$") ;; has equals sign
(looking-at "^.*\n[[:blank:]]*|")
(looking-at "^.*where[[:blank:]]*$")))
(defun my-haskell-block-valid-p (block-start-end)
(let ((block-start (car block-start-end))
(block-end (cdr block-start-end))
(line-count 0))
(save-excursion
(goto-char block-start)
(let ((token 'nil)
(is-valid t))
;; eat top comments
(while (or (looking-at "--")
(looking-at "{-"))
(forward-line))
(when (my-haskell-type-declaration-line-p)
(progn
(setq token (my-get-first-haskell-token)
line-count 1)
(forward-line)))
(while (<= (point) block-end)
(let ((current-token (my-get-first-haskell-token)))
(cond ((string= current-token "") ; line with indentation
(when (null token) (setq is-valid nil))
(setq line-count (+ 1 line-count)))
((or (string= (substring current-token 0 2) "--") ;; skip comments
(string= (substring current-token 0 2) "{-"))
'())
((and (my-haskell-declaration-line-p)
(or (null token) (string= token current-token)))
(setq token current-token
line-count (+ 1 line-count)))
(t (setq is-valid nil)
(goto-char (+ 1 block-end))))
(forward-line)))
(and is-valid (> line-count 1))))))

modify plist inside list?

I have a list of plists such as
'((:atom Toddler :visited nil :on-clauses (0 1))
(:atom Child :visited nil :on-clauses 1))
how should I change the :onclauses property on a given :atom? I'd like to update this property, e.g. making the second plist (:atom Child :visited nil :on-clauses (1 2)) (only adding new values, never deleting the old ones).
the best I could do was to create a new list from scratch using
(remove-if-not #'(lambda(record)
(equal (getf record :atom) atom))
*ATOMS*)
to get the initial value, updating it, then using its analogue to get a list without this value, and append both together, but this is probably terribly inneficient (I know premature optimatization is bad, but I'm learning LISP and want to know how to do things properly!)
Use POSITION to find the plist with the specific :atom, and then REMF to remove the property from that plist.
(defun update-on-clauses (atom new-on-clause)
(let ((pos (position atom *atoms*
:key #'(lambda (record) (getf record :atom)))))
(when pos
(setf (getf (nth pos *atoms*) :on-clauses) new-on-clause))))
It might be simpler to make *ATOMS* an alist that maps atoms to property lists.
these three functions seem to do the trick, as plists are destructively modified.
(defun update-atom(atom ix)
"adds to the atom's on-clauses property another clause where it was found."
(let ((atomo (find-atom atom)))
(setf (getf atomo :on-clauses) (cons ix (get-indices atom)))))
(defun find-atom(atom) "returns the property list of a given atom"
(first (remove-if-not #'(lambda(record) (equal (getf record :atom) atom)) *ATOMS*)))
(defun get-indices(atom)
"gets indices of clauses where atom is found."
(getf (find-atom atom) :on-clauses))

How to access "two" in (("one" . "two")) in elisp

I'm very new to elisp and I'm trying to adapt some existing code.
While looping over a table (generated by the orgmode function org-clock-get-table-data) I try the following:
((equal column "Project") (insert (cdr row)))
which yeilds the following in the Messages buffer:
cond: Wrong type argument: char-or-string-p, (#("Verify CalTime accruals for vacation/sick" 0 41
(fontified t org-category #("Admin" 0 5 (fontified t org-category "Admin" org-category-position 32
line-prefix nil wrap-prefix nil ...)) org-category-position 32 line-prefix #("*" 0 1 (face org-
hide)) wrap-prefix #(" " 0 4 (face org-indent)) ...)) nil 30 (("wps" . "Administration")))
The value that I want to insert is "Administration" so I try this
((equal column "Project") (insert (nth 4 row)))
which yeilds the following in the minibuffer
Wrong type argument, char-or-string-p, (("wps" . "Administration"))
Can someone tell me how I can insert the string "Administration"?
EDIT
Thanks Wes and Drew:
(cdar row)
Doing the above yields the first element of the complex list that I saw in the Messages buffer earlier:
Verify CalTime accruals for vaction/sick
row seems to be a complex list of lists:
((equal column "Project") (insert (car row))); yeilds ^B
((equal column "Project") (insert (cdr row)))
yields:
cond: Wrong type argument: char-or-string-p, (#("Verify CalTime accruals for vacation/sick" 0 41
(fontified t org-category #("Admin" 0 5 (fontified t org-category "Admin" org-category-position 32
line-prefix nil wrap-prefix nil ...)) org-category-position 32 line-prefix #("*" 0 1 (face org-
hide)) wrap-prefix #(" " 0 4 (face org-indent)) ...)) nil 30 (("wps" . "Administration")))
I think each "#" in the output above represents a list item. The fact that (nth 4 row) gives the error output shown above supports this and suggests that something like this might work:
((equal column "Project") (insert (nth 2 (nth 4 row))))
;yeilds wrong type argument, char-or-string-p, nil
So there is probably some function that I need to use to decode that 4th list item....
That 4th element is the orgmode property that has been assigned to the clocktable entry on the row we are parsing. This list of properties is defined in my .emacs:
(setq org-global-properties
;; WPS = Web Platform Services. Time tracking for Google Sheet begun with CalTime Migration
'(("wps_ALL".
"Administration
ASG-Consulting
Chanc-Office-Website
")))
Instead of the assoc + cdr combination, you can also use assoc-default:
ELISP> (assoc-default "one" '(("one" . "two")))
=> "two"
Note that lists of the form (("wps" . "Administration") ("foo" . "bar")) are typically "alists", and so you may need to handle the situation where there is more than one item in your list.
Read: C-hig (elisp) Association Lists RET
and also: (elisp) Dotted Pair Notation
You may obtain a keyed item from an alist with (assoc) or (assq) depending on the form of equality needed for the test. Equivalent strings are not equal objects in elisp, so in this case you want assoc rather than assq.
That gives you the entire (KEY . VALUE) form as a result, and you obtain the cdr of that as usual; hence:
(let ((my-alist '(("wps" . "Administration") ("foo" . "bar"))))
(cdr (assoc "wps" my-alist)))
yields: "Administration"
Lists are complex to think about in elisp. Until you've done enough of them that you stop thinking about them. Ha ha. But read up on the functions like car (picks the first element in a list) and cdr (picks the rest of the rest) to get started. And then you can chain things by combining letters in short sequences at least. So the answer you want is:
(cdar row)

Treating the values from a list of slots and strings

I want to do a macro in common lisp which is supposed to take in one of its arguments a list made of slots and strings. Here is the prototype :
(defclass time-info ()
((name :initarg name)
(calls :initarg calls)
(second :initarg second)
(consing :initarg consing)
(gc-run-time :initarg gc-run-time)))
(defun print-table (output arg-list time-info-list) ())
The idea is to print a table based on the arg-list which defines its structure. Here is an example of a call to the function:
(print-table *trace-output*
'("|" name "||" calls "|" second "\")
my-time-info-list)
This print a table in ascII on the trace output. The problem, is that I don't know how to explicitely get the elements of the list to use them in the different parts of my macro.
I have no idea how to do this yet, but I'm sure it can be done. Maybe you can help me :)
I would base this on format. The idea is to build a format string
from your arg-list.
I define a helper function for that:
(defun make-format-string-and-args (arg-list)
(let ((symbols ()))
(values (apply #'concatenate 'string
(mapcar (lambda (arg)
(ctypecase arg
(string
(cl-ppcre:regex-replace-all "~" arg "~~"))
(symbol
(push arg symbols)
"~a")))
arg-list))
(nreverse symbols))))
Note that ~ must be doubled in format strings in order to escape them.
The printing macro itself then just produces a mapcar of format:
(defmacro print-table (stream arg-list time-info-list)
(let ((time-info (gensym)))
(multiple-value-bind (format-string arguments)
(make-format-string-and-args arg-list)
`(mapcar (lambda (,time-info)
(format ,stream ,format-string
,#(mapcar (lambda (arg)
(list arg time-info))
arguments)))
,time-info-list)))
You can then call it like this:
(print-table *trace-output*
("|" name "||" calls "|" second "\\")
my-time-info-list)
Please note the following errors in your code:
You need to escape \ in strings.
Second is already a function name exported from the common-lisp
package. You should not clobber that with a generic function.
You need to be more precise with your requirements. Macros and Functions are different things. Arrays and Lists are also different.
We need to iterate over the TIME-INFO-LIST. So that's the first DOLIST.
The table has a description for a line. Each item in the description is either a slot-name or a string. So we iterate over the description. That's the second DOLIST. A string is just printed. A symbol is a slot-name, where we retrieve the slot-value from the current time-info instance.
(defun print-table (stream line-format-description time-info-list)
(dolist (time-info time-info-list)
(terpri stream)
(dolist (slot-or-string line-format-description)
(princ (etypecase slot-or-string
(string slot-or-string)
(symbol (slot-value time-info slot-or-string)))
stream))))
Test:
> (print-table *standard-output*
'("|" name "||" calls "|" second "\\")
(list (make-instance 'time-info
:name "foo"
:calls 100
:second 10)
(make-instance 'time-info
:name "bar"
:calls 20
:second 20)))
|foo||100|10\
|bar||20|20\
First, you probably don't want the quote there, if you're using a macro (you do want it there if you're using a function, however). Second, do you want any padding between your separators and your values? Third, you're probably better off with a function, rather than a macro.
You also seem to be using "array" and "list" interchangeably. They're quite different things in Common Lisp. There are operations that work on generic sequences, but typically you would use one way of iterating over a list and another to iterate over an array.