emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
From: Eric Abrahamsen <eric@ericabrahamsen.net>
To: emacs-orgmode@gnu.org
Subject: Re: [feature request] A new cookie type [!] showing the last note taken
Date: Mon, 14 Sep 2020 12:49:38 -0700	[thread overview]
Message-ID: <87h7s0yw8d.fsf@ericabrahamsen.net> (raw)
In-Reply-To: <871rj5hlhv.fsf@nicolasgoaziou.fr>

[-- Attachment #1: Type: text/plain, Size: 4731 bytes --]

Nicolas Goaziou <mail@nicolasgoaziou.fr> writes:

> Hello,
> Ihor Radchenko <yantar92@gmail.com> writes:
>> I did not know this and I cannot find any reference about such behaviour
>> in manual (info:org#Markup for Rich Contents).
> You can find it looking for "line break" in the index. It points there:
> (info "(org) Paragraphs").
>>>> However, it is unused it unordered lists. We might define a note as a
>>>> unnumbered list item with [@note]:
>>>> - [@note] This is note
>>> That's a reasonable syntax extension, maybe too English-centered. Maybe
>>> a more abstract [@!] would be better.
>> It also looks better for me.
>> Should I open separate bug report proposing this syntax extension?
> Thinking again about it, I'm not sold about this idea. Is extending the
> syntax the way to go? 
> Sure, in the proposal above, it is relatively cheap. But what is the
> meaning of an item marked as a "note"? Everything in an entry could be
> a note; this is not limited to items. Moreover, if we are considering it
> to be a note just because it was automatically inserted using
> `org-add-log-note', then we are binding the tool to the syntax. This is
> a mistake.
> I would like to reconsider the solution to your use case. With a non-nil
> value for `org-log-into-drawer', it is possible to guess the location of
> the last inserted note, assuming manually inserted ones—if there is
> any—also follow the same pattern. Under such a reasonable configuration,
> it should not be too difficult to write a function extracting the last
> note, or a whole library operating on notes (ideas: move log-related
> functions to a new "org-log.el" library, simplify it using Org Capture
> as the machinery, extend it…).

Here's code I've had in my config for quite a while. It gets the
logbook, and shows the most recent item, bound to "V" in the agenda. It
also includes an aborted function to do more thorough parsing of the log
items -- I abandoned that because it was too gross.

I actually also have a library called org-log.el around here somewhere,
but that does quantitative logging of values, which I don't think has
enough general interest. But I'm attaching that, too, just in case.

Anyway, I'd be happy to work on this.

(defun org-get-log-list ()
  "Return the entry's log items, or nil.
The items are returned as a list of 'item elements."
    (goto-char (org-log-beginning))
    (let* ((el (org-element-at-point))
	   list-string list-items)
      ;; Should we try harder to make sure it's actually the log list?
      (when (eq (org-element-type el) 'plain-list)
	(setq list-string
	       (org-element-property :contents-begin el)
	       (org-element-property :contents-end el)))
	  (insert list-string)
	  (setq list-items
		    'item #'identity))))

(defun org-agenda-show-log-item ()
  "Echo the text of the entry's most recent log note."
  (let (log-list item)
     nil (setq log-list (org-get-log-list)))
    (if (not log-list)
	(message "Entry has no log")
      (setq item
	    (if org-log-states-order-reversed
		(car log-list)
	      (last log-list)))
      (message (org-element-interpret-data
		(org-element-contents item))))))

(with-eval-after-load 'org-agenda
  (org-defkey org-agenda-mode-map "V" 'org-agenda-show-log-item))

(defun org-parse-log-list (log-list)
  "Turn a parsed plain log list (as returned by
  `org-get-log-list') into something more interesting.

Specifically, this means to extract information from the plain
text of each log item, and put it onto the item's property
list.  Right now this function extracts  "
   (lambda (item)
     (let* ((pars (org-element-map item 'paragraph #'identity))
	    (head-par (car pars))
	    ;; This is absolutely horrible: assume that, if the first
	    ;; four characters of the log note heading match a value
	    ;; from `org-log-note-headings', then that's the kind of
	    ;; heading we've got.
	    (horrible (mapcar
		       (lambda (x) (cons (car x) (substring (cdr x) 0 3)))
	    (item-type (or (rassoc (substring head-par 0 3) horrible)
	    (timestamp (car-safe (org-element-map head-par 'timestamp #'identity)))
	    state-from state-to schedule deadline)
       (cond ((eq item-type 'state)
	      (setq state-to
		    (and (string-match "State \\([^[:space:]]*\\) " head-par)
			 (match-string 1 head-par)))
	      (setq state-from
		    (and (string-match "from \\([^[:space:]]*\\) " head-par)
			 (match-string 1 head-par)))

[-- Attachment #2: org-log.el --]
[-- Type: text/plain, Size: 5320 bytes --]

;;; org-log.el --- Quantitative logging for Org headings  -*- lexical-binding: t; -*-

;; Copyright (C) 2019  Free Software Foundation, Inc.

;; Author: Eric Abrahamsen <eric@ericabrahamsen.net>
;; Maintainer: Eric Abrahamsen <eric@ericabrahamsen.net>

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; This library provides hooks and functions for quantitative logging
;; in Org: meaning that storing a log note may prompt for one or more
;; quantitative values, and store them as part of the note.  Those
;; values can later be viewed as tables.  Essentially this is a
;; generalization of org-clock, to allow logging of other values.
;; Like org-clock, it also includes a command to view logged data as
;; an Org table.

;; In terms of format, a logged value appears as an all-caps label,
;; followed by a colon, followed by the data itself, followed by a
;; semi-colon.  For instance:

;; BP: 120/60; PULSE: 45;

;; The `org-log-buffer-setup-hook' is used to prompt the user for log
;; values to store.  It does this by looking for a LOG_VALUES property
;; on the heading, which should be a space-separated list of all-caps
;; value labels.  These labels can also contain information about the
;; units used for the value.  Units are specified immediately after
;; the value label in square brackets, like so:


;; This unit information will be automatically incorporated into
;; tables created to view logged data.  Call the
;; `calc-view-units-table' command to see all valid units; you can
;; define new units using `calc-define-unit', which see.

;;; Code:

(require 'org)
(require 'calc-units)

(defun org-log-prompt ()
  "Prompt the user for log values.
Insert values into the current log note."
  ;; This is called in the log buffer.  Only fire for state and note;
  ;; later add clock-out.
  (when (memq org-log-note-purpose '(state note))
    (let ((values
	   (with-current-buffer (marker-buffer org-log-note-marker)
	       (goto-char org-log-note-marker)
	       (org-entry-get (point) "LOG_VALUES" 'selective)))))
      (when (and (stringp values) (null (string-empty-p values)))
	(unless (bolp)		     ; This might follow a clock line.
	  (insert "; "))
	(dolist (val (split-string values))
	  ;; Maybe strip off units.
	  (setq val (substring val 0 (string-match-p "\\[" val)))
	  (insert val ": ")
	  (insert (read-string (format "%s: " val)) "; "))))))

(defun org-log--collect-data (id)
  "Collect log data from heading with id ID.
When valid data is found, it is returned as a list of lists.
Each sublist starts with the timestamp of the log entry, followed
by data keys and values, in the order they were found in the log

If no valid data is found, return nil."
    (org-id-goto id)
    (goto-char (org-log-beginning))
    (let* ((struct (org-list-struct))
	   (labels (org-entry-get (point) "LOG_VALUES" 'selective))
	   (entries (when (and (stringp labels)
			       (null (string-empty-p labels)))
		      ;; First element is a list of value labels.
		      (list (cons "TIMESTAMP"
				  (mapcar (lambda (str)
					     str 0 (string-match-p "\\[" str)))
					  (split-string labels))))))
	   elt data)
      (when (and entries struct)
	;; Get onto the list item.
	(while (equal 'item (org-element-type
			     (setq elt (org-element-at-point))))
	  ;; Move past state/timestamp line.
	  (while (re-search-forward "[[:upper:]]+: \\([^;]+\\)"
				    (point-at-eol) t)
	    (push (match-string-no-properties 1) data))
	  (when data
	      (forward-line -1)
	      ;; Get the log entry timestamp.
	      (setq data
		    (cons (if (re-search-forward org-ts-regexp-inactive
						 (point-at-eol) t)
			      (match-string-no-properties 0)
			  (nreverse data))))
	    (push data entries)
	    (setq data nil))
	  (goto-char (org-element-property :end elt)))
	(nreverse entries)))))

(defun org-log-create-dblock ()
  "Create a dblock with a table for this heading's log data."
  (let ((id (org-id-get-create)))
    ;; Anyway, this call is the heart of it.
     `(:name "log" :id ,id))

(defun org-dblock-write:log (params)
  "Write the log dblock table."
  (let ((data (org-log--collect-data (plist-get params :id))))
    (when data
	 (format "|%s|\n" (mapconcat #'capitalize (pop data) "|"))
	 (mapconcat (lambda (row)
		      (format "|%s|" (mapconcat #'identity row "|")))
		    data "\n")))

(add-hook 'org-log-buffer-setup-hook #'org-log-prompt)

(provide 'org-log)
;;; org-log.el ends here

  reply	other threads:[~2020-09-14 19:50 UTC|newest]

Thread overview: 23+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-08-29  6:40 Ihor Radchenko
2020-08-30  8:50 ` Nicolas Goaziou
2020-08-30 10:40   ` Ihor Radchenko
2020-08-30 11:03     ` Nicolas Goaziou
2020-08-30 12:18       ` Ihor Radchenko
2020-08-30 12:57         ` Nicolas Goaziou
2020-08-30 14:58           ` Eric Abrahamsen
2020-08-31  2:26           ` Ihor Radchenko
2020-08-31  8:42             ` Julius Müller
2020-08-31 11:55               ` Ihor Radchenko
2020-08-31 14:58             ` Nicolas Goaziou
2020-09-02  2:02               ` Ihor Radchenko
2020-09-03  9:16                 ` Nicolas Goaziou
2020-09-05  5:52                   ` Ihor Radchenko
2020-09-09 14:00                     ` Nicolas Goaziou
2020-09-10  9:24                       ` Ihor Radchenko
2020-09-13 13:09                         ` Nicolas Goaziou
2020-09-14 19:49                           ` Eric Abrahamsen [this message]
2020-09-05 22:14 ` Allen Li
2020-09-06  1:23   ` Ihor Radchenko
2020-09-06 18:16     ` Eric Abrahamsen
2021-01-17 15:38 ` Christopher Miles
2021-01-18  2:07   ` Ihor Radchenko

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:

  List information: https://www.orgmode.org/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=87h7s0yw8d.fsf@ericabrahamsen.net \
    --to=eric@ericabrahamsen.net \
    --cc=emacs-orgmode@gnu.org \
    --subject='Re: [feature request] A new cookie type ['\!'] showing the last note taken' \


* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

Code repositories for project(s) associated with this inbox:


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).