Org B64: Base64-encode files into Org for easy sharing (Emacs package)

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

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

For more packages, see Software.


README.org

Introduction

What?

With Org B64 you can easily Base64-encode files into an org file. This makes possible the easy embedding as plain text of binaries such as images, audio, and compressed files for later extraction or sharing with friends.

This is similar to what happens when you attach a file to an email: the file is encoded as “plain text” and added to the message, which is recognized by the recipient’s email program and shown as an attachment. When you save the attachment, what the email program does is decode that text and write it to your disk.

Likewise, Org B64 Base64-encodes one or more files of your choice and add them as “example” special Org blocks. It also adds a function that can be used to decode one or more of these blocks, save them to disk as files, and verify that they match what is expected (technically-speaking, it matches the saved files against their provided SHA256 hashes).

What for?

When would you want this? Some cases:

  1. To share with another Org mode user.
  2. When you can only store or send one file.
  3. When you prefer to store data as plain text.
  4. When some system you use restricts filetypes.

If you end up using this package and find it useful, please let me know, and I might add the example to the list of use cases.

Usage, and what it looks like

You can either bind keys or use M-x org-b64-add-files whenever you want to add files to an org buffer.

Adding one file

When you run this command and choose one file, this is what happens:

Pressing C-c C-c over a #+call line will save the file in this folder: ~/org-b64/.

#+call: org-b64-save-file(d="~/org-b64/", f="report.pdf", f64=report.pdf.b64)

#+name: org-b64-save-file
#+begin_src bash :var d="", f="", f64="", sha=sha256...
#+end_src

#+name: sha256
#+begin_example...
#+end_example

#+name: report.pdf.b64
#+begin_example...
#+end_example

What are those?

  • Inside the org-b64-save-file block there’s a Bash function. It’s called by the #+call line.
  • Inside the sha256 block you find the SHA256 hashes of the files. It’s used to verify the files’ integrity after you saved them. It helps prevent corruption, because a decoded and saved file should have the same SHA256 value as the file that was once encoded by you or by whomever sent you these files.
  • Inside the other blocks you find the base64-encoded files. These are the actual data.

At the moment, you can choose between Bash (default) and Emacs Lisp for org-b64-save-file block. The former requires that the recipient have Bash installed. The latter requires that the recipient’s Emacs have the f.el library. It’s possible that future versions of this package may offer more options. Or perhaps it may simplify and unify it.

A note on speed

The two Bash versions of the decoding function (this one and the “expert” one) currently offer considerably faster decoding than the Emacs Lisp one. This is already noticeable with files of a few megabytes. While testing, I successfully added and extracted a 30MB media file with both Bash versions — but the Elisp one choked at this size.

If you know why the Lisp version is performing slower and how to improve it, please suggest.

Adding many files

When you run this command and choose one directory, this is what happens if that directory’s direct contents are three PDF files:

Pressing C-c C-c over a #+call line will save the file in this folder: ~/org-b64/.
C-c C-v C-s evaluates the subtree. C-c C-v C-b evaluates the buffer. Use one to save many at once.
If you then press undo (usually C-/), the results messages disappear, but you keep the effects.

#+call: org-b64-save-file(d="~/org-b64/", f="report1.pdf", f64=report1.pdf.b64)
#+call: org-b64-save-file(d="~/org-b64/", f="report2.pdf", f64=report2.pdf.b64)
#+call: org-b64-save-file(d="~/org-b64/", f="report3.pdf", f64=report3.pdf.b64)

#+name: org-b64-save-file
#+begin_src bash :var d="", f="", f64="", sha=sha256...
#+end_src

#+name: sha256
#+begin_example...
#+end_example

#+name: report1.pdf.b64
#+begin_example...
#+end_example

#+name: report2.pdf.b64
#+begin_example...
#+end_example

#+name: report3.pdf.b64
#+begin_example...
#+end_example

It’s the same idea, but with more files.

You can also use it non-interactively

This:

(org-b64-add-files nil "~/events/lastevent/pics/" "~/Desktop/event-pictures/")

will encode all files that are (non-recursively) found under the first directory in your disk and will insert at point in the current Org buffer, together with the default function (because of the nil). The extraction function will point extraction to the second directory above, presumably because you want to suggest to your recipient that this one be used. If it doesn’t exist in the machine it is extracted to, it will be created.

Adding files in “expert mode”

There’s a separate command that you can use to add files: M-x org-b64-add-files-expert.

It also defaults to Bash, which in fact is (currently) the only choice. The difference is that the function is much simplified and assumes that no explanation is needed: the recipients know what they’re doing, will look at the code before executing, will modify paths and names as desired, etc. Instead of the separate call lines, it creates a single code block that saves everything (but which the person can delete some lines of for selection, etc).

This version is simpler and looks cleaner — but has last safeguards. No detection whether a file already exists at the saving location (to avoid overwriting), for example.

You may want to choose this one either if:

  1. it’s for your own use and you know your way around code; you know what you’re doing, or
  2. you know that your recipients have Bash and know what they’re doing.

This is what it looks like when you run that command and choose the same directory. Unlike the previous ones, here I expand the org-b64-save-files block to show its simplicity.

~/org-b64/

#+name: org-b64-save-files
#+header: :var f00=report1.pdf.b64 :var f01=report2.pdf.b64 :var f02=report3.pdf.b64
#+begin_src bash :results table :var sha=sha256
mkdir -p ~/org-b64/ && cd "$_" || exit
<<< "$f00"  base64 -d  > "report1.pdf"
<<< "$f01"  base64 -d  > "report2.pdf"
<<< "$f02"  base64 -d  > "report3.pdf"
<<< "$sha"  sha256sum -c | tr -d :
#+end_src

#+name: sha256
#+begin_example...
#+end_example

#+name: report1.pdf.b64
#+begin_example...
#+end_example

#+name: report2.pdf.b64
#+begin_example...
#+end_example

#+name: report3.pdf.b64
#+begin_example...
#+end_example

In “expert” mode, there’s a single src block that saves them all. Here, the results of SHA256 verification show up on a table after execution. Like this:

#+results: org-b64-save-files
| report1.pdf | OK |
| report2.pdf | OK |
| report3.pdf | OK |

If a saved file doesn’t match the expected for some reason, you should see something like this:

#+results: org-b64-save-files
| report1.pdf | FAILED |
| report2.pdf | OK     |
| report3.pdf | OK     |

Installation

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

Having downloaded and installed the package and its dependencies, adapt the configurations below to your init.el file.

(use-package org-b64
  :commands (org-b64-add-files
             org-b64-add-files-expert)
  :bind (:map org-mode-map
         ("C-c 6" . org-b64-add-files)
         ("C-c 4" . org-b64-add-files-expert)))

These keys are only suggestions. Use what suits you best.

Alternatively, if you don’t have ‘use-package’:

(require 'org-b64)

and bind keys accordingly.

Security

Org B64 has not yet been thoroughly tested by others. I did my best to make it robust against data losses, but GPL’s warnings apply: you use it at your own risk.

Since it deals with reading and writing files (which, in case of bugs, could cause loss of data), test it well and exercise some caution. If you experience problems, please do report so that critical bugs can be fixed.

Limitations

The current version has limitations. Here are some that I noticed:

  1. It is certain to break with “misbehaved” filenames. The current version assumes you will add nice directories and files that have no spaces, quotes, newlines, or semicolons in it. To say nothing of parentheses. This may be improved in the future.
  2. You may find memory problems when adding too many files or files that are too large. This is because Org tries to syntax highlight and/or hide the encoded data. I don’t know what the actual limit is, and it will depend on your machine. Disabling font-lock-mode while processing a large file may help a bit.

Contributing

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

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.


org-b64.el

Structure

;;; org-b64.el --- Base64-encode files into Org for easy sharing -*- lexical-binding: t -*-
;;; Commentary:
;;;; For all the details, please do see the README
;;; Acknowledgments:
;;; Code:
;;;; Libraries
;;;; Package metadata
;;;; Customizable variables
;;;; Other variables
;;;; Functions
;;;;; Borrowed
;;;;; Auxiliary
;;;;; Core
;;;; Wrapping up
;;; org-b64.el ends here

Contents

;;; org-b64.el --- Base64-encode files into Org for easy sharing -*- lexical-binding: t -*-

;; SPDX-FileCopyrightText:  © flandrew <https://keyoxide.org/191F5B3E212EF1E515C19918AF32B9A5C1CCCB2D>
;; SPDX-License-Identifier: GPL-3.0-or-later

;;---------------------------------------------------------------------------
;; Author:            flandrew
;; Created:           2021-06-09
;; Version:           0.3.6-git
;; Homepage:          <https://flandrew.srht.site/listful/software.html>
;; Keywords:          files, outlines
;; Package-Requires:  ((emacs "25.1"))
;;---------------------------------------------------------------------------

;; This file is part of Org B64, which is NOT part of GNU Emacs.

;; 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 MERCHANTABILITY or
;; FITNESS FOR A PARTICULAR PURPOSE. For more details, see the full license at
;; either LICENSES/GPL-3.0-or-later.txt or <https://www.gnu.org/licenses/>.

;;; Commentary:
;;
;; With Org B64 you can easily Base64-encode files into an org file. This
;; makes possible the easy embedding as plain text of binaries such as images,
;; audio, and compressed files for later extraction or sharing with friends.
;;
;;;; For all the details, please do see the README
;;
;; Open it easily with any of the below:
;;   (find-file-read-only              "README.org")   <--- C-x C-e here¹, or
;;   (find-file-read-only-other-frame  "README.org")   <--- C-x C-e here¹, or
;;   (find-file-read-only-other-window "README.org")   <--- C-x C-e here¹
;;
;; or read it online:
;;   <https://flandrew.srht.site/listful/sw-emacs-org-b64.html>
;;
;; ¹ or the key that ‘eval-last-sexp’ is bound to, if not C-x C-e.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; Acknowledgments:
;;
;; Function ‘org-b64--f-read-bytes’ was copied verbatim from f.el's
;; f-read-bytes’, so that there would be no external dependencies for
;; *creating* org-b64 blocks.
;;
;; About f.el:
;;   SPDX-FileCopyrightText:  © 2013 Johan Andersson
;;   SPDX-License-Identifier: GPL-3.0-or-later
;;   Author:                  Johan Andersson
;;   Homepage:                <https://github.com/rejeep/f.el>
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;



;;; Code:
;;;; Libraries

(require 'subr-x)    ; string-join
(require 'seq)       ; seq-position
(require 'org)       ; org-previous-block’, ‘org-cycle
(require 'org-macs)  ; org-with-wide-buffer
(require 'lisp-mnt)  ; lm-summary’, ‘lm-version’, ‘lm-homepage


;;;; Package metadata

(defconst org-b64--name "Org B64")

(defconst org-b64--dot-el
  (format "%s.el" (file-name-sans-extension (eval-and-compile
                                              (or load-file-name
                                                  buffer-file-name)))))
(defconst org-b64--readme-org
  (expand-file-name "README.org" (file-name-directory org-b64--dot-el)))

(defconst org-b64--summary  (lm-summary  org-b64--dot-el))
(defconst org-b64--version  (lm-version  org-b64--dot-el))
(defconst org-b64--homepage (lm-homepage org-b64--dot-el))


;;;; Customizable variables

(defgroup org-b64 nil
  (format "%s." org-b64--summary)
  :group 'files
  :group 'org
  :link  '(emacs-library-link :tag "Lisp file" "org-b64.el")
  :link  `(file-link :tag "README.org" ,org-b64--readme-org)
  :link  `(url-link  :tag "Homepage"   ,org-b64--homepage))

(defcustom org-b64-hide-blocks-on-startup t
  "When on (non-nil), add a ‘#+startup’ line to make blocks show up folded."
  :type 'boolean
  :group 'org-b64)

(defcustom org-b64-add-instruction-p t
  "Whether to ever add instruction on how to batch-save."
  :type 'boolean
  :group 'org-b64)

(defcustom org-b64-min-files-to-add-instruction 3
  "Minimum number of files needed to add instruction on how to batch-save.
Only considered when ‘org-b64-add-instruction-p’ is non-nil."
  :type 'integer
  :group 'org-b64)

(defcustom org-b64-rec-path "~/org-b64/"
  "The suggested path for files to be decoded+saved later.
This is what your recipients will see, and where it will be saved, if they
don’t change it."
  :type 'directory
  :group 'org-b64)

(defcustom org-b64-save-file-function :bash
  "Choice of function to use for the decoding block.
Bash:  requires that the recipients have Bash in their system.
Elisp: requires that the recipients have the f.el library in their Emacs."
  :type '(choice (const :tag "Bash"  :bash)
                 (const :tag "Elisp" :elisp))
  :group 'org-b64)


;;;; Other variables

(defvar org-b64-save-file--elisp-b64d  "
KGFuZCAob3JnLXN0cmluZy1udy1wIGQpCiAgICAgKG9yZy1zdHJpbmctbnctcCBmKQogICAgIChv
cmctc3RyaW5nLW53LXAgKHN1YnN0cmluZyBmNjQgMCAzKSkgOyBtdWNoIGZhc3RlcgogICAgIChp
ZiAobm90IChyZXF1aXJlICdmIG5pbCB0KSkKICAgICAgICAgKG1lc3NhZ2UgIldlIG5lZWQgdGhl
IGYuZWwgbGlicmFyeSIpCiAgICAgICAobGV0ICgoZmlsZSAoY29uY2F0IGQgZikpCiAgICAgICAg
ICAgICAoZXJyLW1zZyAiYnV0IGRvZXMgbm90IHNlZW0gdG8gbWF0Y2ggdGhlIG9yaWdpbmFsLiBW
ZXJpZnkuIikpCiAgICAgICAgICh3aGVuIChpZiAoZmlsZS1yZWd1bGFyLXAgZmlsZSkKICAgICAg
ICAgICAgICAgICAgICh5ZXMtb3Itbm8tcCAoY29uY2F0ICJGaWxlICIgZiAiIGV4aXN0cy4gT3Zl
cndyaXRlPyIpKQogICAgICAgICAgICAgICAgIHQpCiAgICAgICAgICAgKG1rZGlyIGQgIi1wIikK
ICAgICAgICAgICAoZi13cml0ZS1ieXRlcyAoYmFzZTY0LWRlY29kZS1zdHJpbmcgZjY0KSBmaWxl
KQogICAgICAgICAgIChpZiAobGV0ICgoY2FzZS1mb2xkLXNlYXJjaCAnaWdub3JlLWNhc2UpCiAg
ICAgICAgICAgICAgICAgICAgIChyZSAocmVnZXhwLXF1b3RlIChzZWN1cmUtaGFzaCAnc2hhMjU2
IChmLXJlYWQgZmlsZSkpKSkpCiAgICAgICAgICAgICAgICAgKHN0cmluZy1tYXRjaC1wIHJlIHNo
YSkpCiAgICAgICAgICAgICAgIChtZXNzYWdlICJGaWxlICVzIGhhcyBiZWVuIHNhdmVkIGNvcnJl
Y3RseS4iIGYpCiAgICAgICAgICAgICAobWVzc2FnZSAiRmlsZSAlcyBoYXMgYmVlbiBzYXZlZCwg
JXMiIGYgZXJyLW1zZykpKSkpKQo="
  "Elisp function block to be inserted.
It will later be used to decode and write files when the user wants it.

Parameters ‘d’, ‘f’ and ‘f64’ will be passed by the ‘#+call:’ line.

It’s base-64’d here for convenience: it preserves indentation. Please do
decode and verify it with (org-b64--verify-save-file-function :elisp).")

(defvar org-b64-save-file--bash-b64d  "
KFtbIC16ICIkZCIgXV0gfHwgW1sgLXogIiRmIiBdXSB8fCBbWyAteiAiJGY2NCIgXV0pICYmIGV4
aXQKZGlyPSIkKHNlZCAic3xefnwkSE9NRXwiIDw8PCAiJGQiKSI7IGZkPSIkZGlyLyRmIgpta2Rp
ciAtcCAiJGRpciIgJiYgY2QgIiRkaXIiIHx8IGV4aXQKW1sgLWYgIiRmZCIgXV0gJiYgeyBlY2hv
ICJGaWxlICRmIGV4aXN0cyBhbmQgd2FzIE5PVCBvdmVyd3JpdHRlbiI7IGV4aXQgO30gfHwKICAg
IHsgYmFzZTY0IC1kID4gIiRmZCIgPDw8ICIkZjY0IiAmJiBlY2hvIC1uICJGaWxlICRmIGhhcyBi
ZWVuIHNhdmVkICIKICAgICAgcz0iJChzaGEyNTZzdW0gIiRmIikiCiAgICAgIFxncmVwIC1xICIk
e3MvLyAqL30gICokZiIgPDw8ICIkc2hhIiAmJgogICAgICAgICAgZWNobyAiY29ycmVjdGx5LiIg
fHwKICAgICAgICAgIGVjaG8gImJ1dCBkb2VzIG5vdCBzZWVtIHRvIG1hdGNoIHRoZSBvcmlnaW5h
bC4gRXJyb3I/IFZlcmlmeS4iIDt9Cg=="
  "Bash function block to be inserted.
It will later be used to decode and write files when the user wants it.

Parameters ‘d’, ‘f’ and ‘f64’ will be passed by the ‘#+call:’ line.

It’s base-64’d here for convenience: it avoids multiple escapings. Please do
decode and verify it with (org-b64--verify-save-file-function :bash).")


;;;; Functions
;;;;; Borrowed

;; Copied verbatim from the f.el library. Added "org-b64-" prefix.
(defun org-b64--f-read-bytes (path)
  "Read binary data from PATH.
Return the binary data as unibyte string."
  (with-temp-buffer
    (set-buffer-multibyte nil)
    (setq buffer-file-coding-system 'binary)
    (insert-file-contents-literally path)
    (buffer-substring-no-properties (point-min) (point-max))))


;;;;; Auxiliary

(defun org-b64--verify-save-file-function (opt)
  "Decodes a save-file-function function for verification and editing.
Options OPT for FUN are: ‘:bash’ and ‘:elisp’."
  (let* ((mode (pcase opt
                 (:bash  'shell-script-mode)
                 (:elisp 'emacs-lisp-mode)))
         (bfb  (pcase opt
                 (:bash  'org-b64-save-file--bash-b64d)
                 (:elisp 'org-b64-save-file--elisp-b64d)))
         (buf  (get-buffer-create (concat "*" (symbol-name bfb) "*")))
         (dec  (base64-decode-string (symbol-value bfb))))
    (switch-to-buffer buf)   (funcall mode)   (insert dec)))

(defun org-b64--only-files (directory &optional full)
  "Given a DIRECTORY, return list with its regular files. Non-recursive.
If FULL is non-nil, return absolute filenames instead of relative."
  (remove 'nil (mapcar (lambda (elt) (when (file-regular-p elt) elt))
                       (directory-files directory full))))

(defun org-b64--listify-and-expand-filenames (path)
  "Given a PATH, return a list of all valid regular files.
If PATH is a file, make a list with it. If a directory, make a list of all
regular files in it, non-recursively. If it doesn’t exist, return error.
An empty string is the same as ‘default-directory’, so it returns a list of
all files in it."
  (or (file-exists-p path)
      (user-error "File or directory %s doesn’t exist" path))
  (cond ((file-directory-p path) (org-b64--only-files path t))
        ((file-regular-p path) (list (expand-file-name path)))
        (t  (error "Not a file or directory: %s" path))))

(defun org-b64--select-src-files (path)
  "Given a PATH, return a list with selected files."
  (org-b64--listify-and-expand-filenames
   (or path
       (read-file-name "File or directory to base64-encode: "
                       default-directory)
       (user-error "No file or directory was selected"))))

(defun org-b64--hide-last-block ()
  "Collapse the last block inserted.
You can toggle a block’s visibility with \\[org-cycle]."
  (org-previous-block 1) (org-cycle) (next-logical-line))

(defun org-b64--stringer (src-files str-format separator)
  "Make strings for block headers and contents.
You need SRC-FILES, a STR-FORMAT and a SEPARATOR."
  (string-join (mapcar (lambda (f)
                         (let ((fname (file-name-nondirectory f)))
                           (format str-format
                                   (seq-position src-files f)
                                   fname)))
                       src-files)
               separator))

(defun org-b64--make-hideblocks-line ()
  "Add a line to hide blocks’ contents, so that they show up nicely folded."
  (when org-b64-hide-blocks-on-startup (insert "#+startup: hideblocks\n\n")))

(defun org-b64--make-bash-expert-block (src-files rec-path)
  "Given a list of full-path SRC-FILES, create Bash \"expert\" block.
REC-PATH is the path suggested for the recipient."
  (insert "\n#+name: org-b64-save-files"
          "\n#+header: "
          (org-b64--stringer src-files
                             ":var f%.2d=%s.b64"
                             " ")
          "\n#+begin_src bash :results table :var sha=sha256"
          "\nmkdir -p " rec-path " && cd \"$_\" || exit\n"
          (org-b64--stringer src-files
                             "<<< \"$f%.2d\"  base64 -d  > \"%s\""
                             "\n")
          "\n<<< \"$sha\"  sha256sum -c | tr -d :"
          "\n#+end_src\n")
  (org-b64--hide-last-block))

(defun org-b64--make-sha256-list-block (src-files)
  "Given a list of full-path SRC-FILES, create a block with their sha256."
  (insert "\n#+name: sha256"
          "\n#+begin_example\n"
          (string-join (mapcar (lambda (f)
                                 (shell-command-to-string
                                  (concat "sha256sum \"" f "\" | "
                                          "sed 's|  .*/|  |'")))
                               src-files))
          "#+end_example\n")
  (org-b64--hide-last-block))

(defun org-b64--make-encoded-blocks (src-files)
  "Given a list of full-path SRC-FILES, create base64-encoded blocks."
  (dolist (file src-files)
    (insert "\n#+name: " (file-name-nondirectory file) ".b64"
            "\n#+begin_example\n"
            (base64-encode-string (org-b64--f-read-bytes file))
            "\n#+end_example\n")
    (org-b64--hide-last-block)))

(defun org-b64--make-instructions (src-files rec-path)
  "Given a list of full-path SRC-FILES, create instructions text.
REC-PATH is the path suggested for the recipient."
  (insert "Pressing =C-c C-c= over a =#+call= line will save the file "
          "in this folder: [[" rec-path "]].\n")
  (and org-b64-add-instruction-p
       (>= (length src-files) org-b64-min-files-to-add-instruction)
       (insert "=C-c C-v C-s= evaluates the subtree. "
               "=C-c C-v C-b= evaluates the buffer. "
               "Use one to save many at once.\n"
               "If you then press undo (usually =C-/=), the results messages "
               "disappear, but you keep the effects.\n"))
  (insert "\n"))

(defun org-b64--make-call-lines (src-files rec-path)
  "Given a list of full-path SRC-FILES, create #+call: lines.
REC-PATH is the path suggested for the recipient."
  (dolist (file src-files)
    (let ((fname  (file-name-nondirectory file)))
      (insert "#+call: org-b64-save-file("
              "d=\""  rec-path "\", "
              "f=\""  fname    "\", "
              "f64="  fname    ".b64)\n"))))

(defun org-b64--make-src-block (dec-fun)
  "Given a chosen decoding function (DEC-FUN), create src block."
  (insert "\n#+name: org-b64-save-file\n"
          "#+header: :var d=\"\" :var f=\"\" :var f64=\"\" :var sha=sha256\n"
          ;; remove the ":"
          "#+begin_src " (substring (symbol-name dec-fun) 1) "\n"
          (base64-decode-string (pcase dec-fun
                                  (:bash  org-b64-save-file--bash-b64d)
                                  (:elisp org-b64-save-file--elisp-b64d)
                                  (_      (error "Function missing"))))
          "#+end_src\n")
  (org-b64--hide-last-block))


;;;;; Core

;;;###autoload
(defun org-b64-add-files (&optional dec-fun src-path rec-path)
  "Base64-encode files and insert into the current org buffer.
Read a source path from the minibuffer; then create org-babel
blocks which, when executed, save the corresponding files in the
recipient’s computer. The saved files’ SHA256 hashes are verified
against the provided ones.

To save a file, press \\[org-ctrl-c-ctrl-c] on the block.

DEC-FUN is the decoding function to be used. When nil, it defaults
to ‘org-b64-save-file-function’. Note that this refers to the
function that will be inserted and shown to the recipient. With one
\\[universal-argument], it uses the other function (Elisp if the
default is Bash, and vice-versa).

SRC-PATH is a file or directory to encode. If you provide a
directory, all regular files in it (non-recursive) will be encoded
individually. If nil, you’ll be prompted to choose.

REC-PATH is the suggested path for files to be decoded+saved later.
This is what your recipient will see, and where it would be saved,
if they don’t change it. If nil, it uses the value from
org-b64-rec-path’."
  (interactive "P")
  (setq dec-fun  (pcase dec-fun
                   ('()  org-b64-save-file-function)
                   ('(4) (if (equal :bash org-b64-save-file-function)
                             :elisp
                           :bash))
                   (_    dec-fun))
        rec-path (file-name-as-directory (or rec-path org-b64-rec-path)))
  (let ((src-files (org-b64--select-src-files src-path)))
    ;; 0. Instructions :: how the recipient can save it.
    ;; 1. The #+call :: calls function with encoded data’s name as argument.
    ;; 2. The #+src bash|elisp  :: function used to decode and write the file.
    ;; 3. An #+example block    :: sha256 list
    ;; 4. More #+example blocks :: base-64’d data
    (org-with-wide-buffer
     (org-b64--make-hideblocks-line)
     (org-b64--make-instructions      src-files rec-path)
     (org-b64--make-call-lines        src-files rec-path)
     (org-b64--make-src-block         dec-fun)
     (org-b64--make-sha256-list-block src-files)
     (org-b64--make-encoded-blocks    src-files))))

;;;###autoload
(defun org-b64-add-files-expert (&optional _ src-path rec-path)
  "Make Base64 blocks from files. No warnings, no frills.
Less safeguards, but cleaner. *Use responsibly*.

It assumes that no explanation is needed; that the recipients know what
they’re doing; that they will look at the code before executing; that they
will modify paths and names as desired; etc.

To use this function, you need Bash. The recipient, too, needs Bash
to decode.

See ‘org-b64-add-files’ for explanation on SRC-PATH and REC-PATH."
  (interactive)
  (setq rec-path (file-name-as-directory (or rec-path org-b64-rec-path)))
  (let ((src-files (org-b64--select-src-files src-path)))
    ;; 1. The #+src bash block  :: decode and save files
    ;; 2. An #+example block    :: sha256 list
    ;; 3. More #+example blocks :: base-64’d data
    (org-with-wide-buffer
     (org-b64--make-hideblocks-line)
     (insert "[[" rec-path "]]\n")  ; link to destination directory
     (org-b64--make-bash-expert-block src-files rec-path)
     (org-b64--make-sha256-list-block src-files)
     (org-b64--make-encoded-blocks    src-files))))


;;;; Wrapping up

(provide 'org-b64)

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

;;; org-b64.el ends here