Skip to main content

Append a new Org mode heading to an existing Org mode file

Project description

* appendorgheading

This script appends [[https://orgmode.org][Org mode]] headings to any existing Org mode file
which are plain text files with the extension =.org=.

You can use this script to generate arbitrary Org mode headings. It's
mainly a wrapper script for the function =orgformat()= of [[https://github.com/novoid/orgformat][orgformat]].

** Why

The author is using this script to log things to an =errors.org= file
which is part of his Org mode agenda:

: example_script.sh >out.log 2>&1 || \
: appendorgheading --output errors.org \
: --filecontent out.log \
: --level 2 \
: --keyword "TODO"
: --title "example_script: some error occurred" \
: --daily

When this =example_script.sh= returns an [[http://tldp.org/LDP/abs/html/exit-status.html][exit status]] which is not
zero, =appendorgheading= is adding a new heading to the file
=errors.org= at level 2 (two asterisks) with the title
"example_script: some error occurred".

Using the =--daily= parameter, there is a time-stamp attached which
re-occurs on a daily basis. This looks similar to
=<2019-12-29 Sun 16:01 +1d>= which causes this entry to appear on
every agenda so that it gets recognized and hopefully resolved. On
resolving the issue, the author deletes (or achives) this heading.

If these parameters are always the same, you can use the configuration
file (see section below) to store the default values. Using this, you
can reduce the example call above to:

: example_script.sh >out.log 2>&1 || appendorgheading

The =errors.org= gets a new heading that looks like:

: ** TODO example_script: some error occurred
:
: #+BEGIN_EXAMPLE
: <This is the content of
: the file out.log.>
: #+END_EXAMPLE

Of course, there are much more command line parameters to add tags,
priorities, and other Org mode heading properties. See section
"Usage" below.

** Installation

This tool needs [[http://www.python.org/downloads/][Python 3 to be installed]].

You can install this script either via [[https://packaging.python.org/tutorials/installing-packages/][pip]] which is the recommended
way. Or you can install it using the source code, e.g., by cloning the
[[https://github.com/novoid/appendorgheading/][GitHub repository]].

Install via [[https://pip.pypa.io/en/stable/][pip]]: ~pip install appendorgheading~

** First Steps

After installing, I'd recommend you to read the help screen
=appendorgheading -h= or the *Usage* section below in order to get an
overview on the possibilities.

Then, you'd like to do *generate a stub of a configuration file* via:
: appendorgheading --generateconfigfile testconfig.ini

Alternatively, you can use as many command line options as you like when generating the configuration file similar to:
: appendorgheading --generateconfigfile testconfig.ini --output ~/org/errors.org --level 2 --daily

This will populate the generated configuration file with provided settings.

If you do like the generated configuration file, rename and move it to
one of the two locations in order to *activate the configuration file*:

1. file =.appendorgheading= in the current working directory OR
2. in your home directory: =~/.appendorgheading=

Provided settings do have following priorities:
1. command line options
2. =.appendorgheading= in the current working directory
3. =~/.appendorgheading=

In other words: command line options always overrule any configuration
file settings. Therefore, you can put your default settings in a
configuration file and overrule via command line parameters whenever
necessary to keep the average command line parameters at a minimum
just as in the "Why" section.

If you are unsure, you can always *use the test option* =--dryrun=
which doesn't change any files at all and tells you what would happen.

** Usage

# #+BEGIN_SRC sh :results output :wrap src
# ./appendorgheading/__init__.py --help | sed 'sX/home/vkX\$HOMEX'
# #+END_SRC

#+BEGIN_EXAMPLE
usage: appendorgheading.py [-h] [--output <FILE.ORG>] [--level <level>] [--keyword <TODO>]
[--priority "PRIO"] [--title "HEADING TITLE"]
[--tags "STRING WITH TAGS"]
[--scheduled "STRING WITH DATE/TIME-STAMP"]
[--deadline "STRING WITH DATE/TIME-STAMP"]
[--properties "KEY1:VALUE1; KEY2:VALUE2"] [--section "STRING"]
[--filecontent <FILE>] [--blocktype <BLOCK_TYPE>] [--nosanitize] [--daily]
[--nodaily] [--dryrun] [--generateconfigfile <FILE>] [-v] [-q]
[--version]

This tool appends Org mode formatted headings to existing Org mode files.

The optional configuration file ".appendorgheading" can be placed:
1. the current directory ... OR ...
2. the home directory ("~/.appendorgheading")
Command line parameters override configuration file entries.

A typical use-case for this script is logging:
The author is using this to log events to some kind of 'errors.org' which is part
of his Org mode agenda.

This will use the default settings from your configuration file and log to the
defined Org mode file only if "example_script.sh" has an exit status not equal
to zero. It also appends the content of the log file for further analysis.

options:
-h, --help show this help message and exit
--output <FILE.ORG> Path to the Org mode file to append to.
--level <level> The heading level (number of asterisks): 1, 2, 3, ...
--keyword <TODO> TODO keyword such as "TODO", "ERROR", ...
--priority "PRIO" Priority indicator such as "A" or "C".
--title "HEADING TITLE"
Title of the heading.
--tags "STRING WITH TAGS"
One or more tags (if multiple: in quotes, separated by spaces).
--scheduled "STRING WITH DATE/TIME-STAMP"
An Org mode date- or time-stamp such as "<2019-12-29 Sun>" which is added as
SCHEDULED.
--deadline "STRING WITH DATE/TIME-STAMP"
An Org mode date- or time-stamp such as "<2019-12-29 Sun>" which is added as
DEADLINE.
--properties "KEY1:VALUE1; KEY2:VALUE2"
A string with key-value pairs. Colons separate keys from values, semicolons
separate the key-value-pairs.
--section "STRING" This is used as the section text or body of the heading.
--filecontent <FILE> Path to a filename whose content gets appended to the section body within a block.
If no "--blocktype" is provided, the default block type is "EXAMPLE".
--blocktype <BLOCK_TYPE>
If "--filecontent" is given, use this type of block:
"#+BEGIN_BLOCKTYPE…#+END_BLOCKTYPE". This could be one of: 'SRC', 'VERSE', 'QUOTE',
'ORG', 'EXAMPLE', 'NONE', 'src', 'verse', 'quote', 'org', 'example', 'none'. 'NONE'
is a special block type as the BEGIN/END lines are omitted and the raw file content
will be inserted.
--nosanitize If "--filecontent" is given, things like "*" or "#+BEGIN_" lines are not prepend
by ",". This is especially handy when using "--filecontent NONE".
--daily Add a time-stamp for today which is recurring on a daily basis.
--nodaily Override setting for "--daily" from the configuration file.
--dryrun Enable dryrun mode: just simulate what would happen, do not modify files.
--generateconfigfile <FILE>
Path to a filename which gets created or overwritten with a configuration file that
contains default values or the values given by the parameters.
-v, --verbose Enable verbose mode.
-q, --quiet Enable quiet mode.
--version Display version and exit.

:copyright: (c) by Karl Voit <tools@Karl-Voit.at>
:license: GPL v3 or any later version
:URL: https://github.com/novoid/appendorgheading
:bugreports: via github or <tools@Karl-Voit.at>
:version: 2025-02-06
#+END_EXAMPLE

** Examples

Additional to the example from the "Why"-section above, here are some other use-cases for this tool:

*** Checking for file age

Use-case: The author is generating an [[https://en.wikipedia.org/wiki/ICalendar][ical/ics-file]] via [[https://en.wikipedia.org/wiki/Cron][cron]]. If this
is failing somehow because of Emacs tripping over some problematic Org
mode data, this ics file gets old without noticing. Since I am using
this to update [[https://radicale.org/][a calendar server]], this is a silent issue.

With =appendorgheading=, I can add a check for this using the shell script =appendorgheading-if-file-too-old.sh=:

#+BEGIN_SRC sh
#!/bin/sh
set -o errexit

FILENAME="${1}"
MAXDAYS="${2}"
HELPTEXT="${3}"

OLD=$(stat -c %Y "${FILENAME}")
NOW=$(date +%s)
DIFFDAYS=$(( ($NOW - $OLD) / (60*60*24) ))
DIFFHRS=$(( ($NOW - $OLD) / (60*60) ))

[ $DIFFDAYS -gt $MAXDAYS ] && /usr/local/bin/appendorgheading --title "\"${FILENAME}\" is older than $DIFFDAYS days" \
--quiet \
--section "File is $DIFFHRS hours old: $(ls -la "${FILENAME}")\n\n${HELPTEXT}"
exit 0
#end
#+END_SRC

My invocation command looks like:

: appendorgheading-if-file-too-old.sh /home/vk/.emacs.d/var/export/agenda-export.ics 0 "check with id:2019-11-19-HOWTO-check-and-fix-Org-agenda-radicale"

An example output in the resulting Org mode file could look like:

: *** "/home/vk/.emacs.d/var/export/agenda-export.ics" is older than 3 days
:
: <2020-06-02 Tue 10:57 +1d>
: File is 75 hours old: -rw-r--r-- 1 vk vk 270538328 May 28 2020 agenda-export.ics
:
: check with id:2019-11-19-HOWTO-check-and-fix-Org-agenda-radicale

... which then finds its way to my agenda, reminding me to check this issue.

** Changelog

- 2019.12.30.1: first version
- 2020.06.02.1: added command line option for =--nodaily= to override configuration file preference
- 2025.02.06.1: added =--blocktype= option
- 2025.02.06.2: added =--nosanitize= option

** Similar Projects
:PROPERTIES:
:CREATED: [2025-11-04 Tue 18:42]
:END:

- <2025-11-04 Tue> [[https://flandrew.srht.site/listful/sw-bash-append2org.html][Append2Org]] (bash script)
- Source: https://sr.ht/~flandrew/append2org
- License: This project follows the [[https://reuse.software/][REUSE]] [[https://reuse.software/spec][Specification]] ([[https://reuse.software/faq][FAQ]]), which in turn is built upon [[https://spdx.org][SPDX]].

* How to Thank Me

I'm glad you like my tools. If you want to support me:

- Send old-fashioned *postcard* per snailmail - I love personal feedback!
- see [[http://tinyurl.com/j6w8hyo][my address]]
- Send feature wishes or improvements as an issue on GitHub
- Create issues on GitHub for bugs
- Contribute merge requests for bug fixes
- Check out my other cool [[https://github.com/novoid][projects on GitHub]]

* Local Variables :noexport:
# Local Variables:
# mode: auto-fill
# mode: flyspell
# eval: (ispell-change-dictionary "en_US")
# End:

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

appendorgheading-2026.3.1.1.tar.gz (53.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

appendorgheading-2026.3.1.1-py3-none-any.whl (38.3 kB view details)

Uploaded Python 3

File details

Details for the file appendorgheading-2026.3.1.1.tar.gz.

File metadata

  • Download URL: appendorgheading-2026.3.1.1.tar.gz
  • Upload date:
  • Size: 53.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.9 {"installer":{"name":"uv","version":"0.9.9"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for appendorgheading-2026.3.1.1.tar.gz
Algorithm Hash digest
SHA256 1cd15aac2d8dd9604a00aa547a99cf88a28c22c37f81b040f7930c740abbcedb
MD5 c09cdb4b5196020a9bbcb40e20aad363
BLAKE2b-256 f17a6a79ac67f23f972cdd2985b7e36c5d99a7acecee261b9142535befad98da

See more details on using hashes here.

File details

Details for the file appendorgheading-2026.3.1.1-py3-none-any.whl.

File metadata

  • Download URL: appendorgheading-2026.3.1.1-py3-none-any.whl
  • Upload date:
  • Size: 38.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.9 {"installer":{"name":"uv","version":"0.9.9"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for appendorgheading-2026.3.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 52db48a841df85c798b4311c05edb2e2206d614f0044c747f04777ed9dcef89a
MD5 4932eabb0972b2892002559163d7e662
BLAKE2b-256 154958845b56f050b90b082b676cf6c93779afd421643ecb129a555d8423dd96

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page