Append2Org — Send your clipboard to an Org file as a subtree (Bash package)

Below you find the latest version of (1) the package's README and (2) its main source file.

For the git repository and issue tracker, see the project's page on sr.ht.

For more packages, see Software.


README.org

Overview

Send the contents of your clipboard to the end of an Org file, as an Org subtree.

This is somewhat like org-refile — but it works from anywhere in the system, even when Emacs isn't open or available.

Usage

Copy something to your clipboard. For example, this string:

t a Test Append2Org
Will it copy?

(The t and a are optional: they add TODO status and priority cookie.)

Then run in a terminal:

append2org

This will send the contents of the clipboard to the end of the file $ORG_X, as an Org subtree.

A notification popup will show up with the Emacs icon and a text like this:

📥 INBOX
** TODO [#A] Test Append2Org
Will it copy?

If no letter is passed after append2org, it defaults to x.
Here, we assume that $ORG_X points to inbox.org, whereas this:

append2org f

would use the file $ORG_F instead.

You define variables (and the files they point to) in ~/.config/append2org.rc.
This package includes an example of append2org.rc file.

You could also define in your system a keyboard shortcut to launch append2org.
You'd then be able to do this from anywhere, with just a key sequence, without bringing up a terminal.

Installation

See my page Software for the most up-to-date instructions on how to download and install my packages.

Dependencies: xclip and zenity must be available in your system.

Copy append2org.rc to ~/.config and adapt it to your needs.

Notes

Modifying a file that isn't opened by Emacs as a buffer will "break" its undo-tree history.

Once you open it, you'll get a warning message:

Buffer has been modified since undo-tree history was saved to
      "/some/long/path/.!some!other!path!yourfile.org.~undo-tree~";
could not load undo-tree history

This issue isn't specific to this package.

You'd get this message regardless of how the file was changed outside of Emacs.

Contributing

See my page Software for information about how to contribute to my packages.

News

0.2.0

Release

License

This project follows the REUSE Specification (FAQ), which in turn is built upon SPDX.

Therefore, license and copyright information can be found in:

  • each file's comment header, or
  • an adjacent file of the same name with the additional extension .license, or
  • the .reuse/dep5 file

The full text of the licenses can be found in the LICENSES subdirectory.


append2org

Structure

## Append2Org --- Send your clipboard to an Org file as a subtree
## Commentary
### See the README for more information
## Code
### Settings
### Functions
#### Load variables
#### Produce output
#### Utilities
#### Error messages
#### Main
### Run it and exit
## Append2Org ends here

Contents

#!/usr/bin/env bash

## Append2Org --- Send your clipboard to an Org file as a subtree

# SPDX-FileCopyrightText: © flandrew <https://flandrew.srht.site/listful>
# SPDX-License-Identifier: GPL-3.0-or-later

#---------------------#
# Author:  flandrew   #
# Created: 2020-12-01 #
# Updated: 2025-12-17 #
#---------------------#
# Version: 0.2.1      #
#---------------------#

## Commentary
#
# Send the contents of clipboard to the end of an Org file, as an Org subtree.
#
# This is somewhat like org-refile — but it works from anywhere in the system,
# even when Emacs isn't open or available.
#
### See the README for more information
#
# A local README.org should be available in the same directory as this file.
#
# You can also read it online:
#   https://flandrew.srht.site/listful/sw-bash-append2org.html
#
#############################################################################

#⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
## Code
### Settings

require() { hash "$@" || exit 127 ;}
require xclip zenity

#⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
### Functions
#### Load variables

loadvars() { : ~/.config/append2org.rc
             # USER: this file^ must exist
             if [[ -f "$_" ]]
             then . "$_"
             else echo "$_ is missing; create it" >&2
                  exit 1
             fi ;}

#⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
#### Produce output

output()   {
    local input="$1"
    # Let's further fix the input.
    #   We want its first line to always start with "** ".
    #   We also want to be able to batch-add entries.
    if   [[ "${input:0:3}" == "** " ]]; then : ""
    elif [[ "${input:0:3}" == "***" ]]; then : "1 s/^/** (new entry)\n/"
    elif [[ "${input:0:2}" == "* "  ]]; then : "/^[*]+ / s/^/*/"
    else                                     : "1 s/^/** /"
    fi
    input="$(sed -E "$_" <<< "$input")"

    # Then we check if there's a "todo" followed or not by [abc].
    # This is simply so we can feed clipboard with things like:
    #   "todoa Wash cat" and have it sent to inbox as "** TODO [#A] Wash cat".
    : "${input:0:8}"
    : "${_,,}"
    if   [[ "$_" == "** todoa" ]]; then : "1 s/...todoa/** TODO [#A]/I"
    elif [[ "$_" == "** todob" ]]; then : "1 s/...todob/** TODO [#B]/I"
    elif [[ "$_" == "** todoc" ]]; then : "1 s/...todoc/** TODO [#C]/I"
    elif [[ "$_" == "** todo " ]]; then : "1 s/...todo /** TODO /I"
    else                                : ""
    fi
    input="$(sed -E "$_" <<< "$input")"

    # We can be even lazier:
    #   "t a Wash cat".
    : "${input:0:7}"
    : "${_,,}"
    if     [[ "$_" == "** t a " ]]; then : "1 s/...t../** TODO [#A]/I"
    elif   [[ "$_" == "** t b " ]]; then : "1 s/...t../** TODO [#B]/I"
    elif   [[ "$_" == "** t c " ]]; then : "1 s/...t../** TODO [#C]/I"
    else                                 : ""
    fi
    sed -E "$_" <<< "$input"
}

#⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
#### Utilities

zenemacs() { local wi=(--notification --timeout=10 --text "$1")
             # (Using ":" with "*" here is like using ls and then tail -n1.)
             : /usr/share/emacs/*/etc/images/icons/hicolor/128x128/apps/emacs.png
             [[ -f "$_" ]] && wi+=(--window-icon="$_")
             zenity "${wi[@]}" 2>/dev/null & }

# Remove from the whole input: leading and trailing spaces: [ \t\n\v].
# Then make sure a trailing \n is included.
trim()     { : "$(</dev/stdin)"
             : "${_#"${_%%[^[:space:]]*}"}"
             : "${_%"${_##*[^[:space:]]}"}"
             printf '%s\n' "$_" ;}
trimmed()  { xclip -o -selection clipboard | trim ;}

#⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
#### Error messages

_zerrmsg() { zenemacs "😕 $1\n   (so I did nothing...)" ;}
emptymsg() { _zerrmsg "Clipboard was empty"   ;}
novarmsg() { _zerrmsg "$1: empty variable"    ;}
nofilmsg() { _zerrmsg "$1: file not found"    ;}
nowrtmsg() { _zerrmsg "$1: file not writable" ;}

#⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
#### Main

main() {
    local orgfile orglttr orgbase trimmed

    # Set all variables
    loadvars                              #Examples:
    : "${1:-x}"                           # $1; if no $1, return "x"
    orgvari="ORG_${_^^}"                  # Add "ORG"; uppercase it: "ORG_X"
    orgfile="${!orgvari}"                 # Value of "$ORG_X": ~/org/inbox.org
    orgname="$(basename "$orgfile" .org)" # Basename: inbox
    trimmed="$(trimmed)"

    # Make sure we'd be able to append
    if   ! [[ -n "$trimmed" ]]; then emptymsg           ; exit 2
    elif ! [[ -n "$orgfile" ]]; then novarmsg "$orgvari"; exit 3
    elif ! [[ -f "$orgfile" ]]; then nofilmsg "$orgfile"; exit 4
    elif ! [[ -w "$orgfile" ]]; then nowrtmsg "$orgfile"; exit 5
    fi

    # No errors? Then append to the org file,
    # and show notification message — something like this:
    # 📥 INBOX\n<Contents we appended>
    output "$trimmed" | tee -a "$orgfile" |
        zenemacs "${ORG_MSG_INCOMING_SYMBOL:-📥} ${orgname^^}\n$(</dev/stdin)"
}

#⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
### Run it and exit

main "$@"
exit 0

# Local Variables:
# coding:                     utf-8
# indent-tabs-mode:           nil
# sentence-end-double-space:  nil
# outline-regexp:             "###* "
# End:

## Append2Org ends here
📆 2025-W44-7📆 2025-11-02