Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

https://github.com/tgbugs/orgstrap

Executable Org files
https://github.com/tgbugs/orgstrap

emacs emacs-lisp org-mode

Last synced: 3 months ago
JSON representation

Executable Org files

Lists

README

        

# -*- org-adapt-indentation: nil; org-edit-src-content-indentation: 0; orgstrap-cypher: sha256; orgstrap-norm-func-name: orgstrap-norm-func--dprp-1-0; orgstrap-block-checksum: 24dbc0d54579db9f0972c6218bdfc1b924e54d8b230d82c2e9c55c833af19cdd; -*-
# [[orgstrap][jump to orgstrap block for this file]]
#+title: Orgstrap: Executable Org files
#+startup: showall
#+options: num:nil \n:nil

#+todo: TODO STARTED | DONE EXPIRED

#+property: header-args :eval no-export
#+property: header-args:elisp :lexical yes

#+latex_header: \usepackage[margin=0.8in]{geometry}
#+latex_header: \setlength\parindent{0pt}

#+link: yt https://youtu.be/
#+link: gh https://github.com/

# [[file:./README.pdf]]
# [[file:./README.html]]
#+HTML:
#+HTML:
# orgstrap
# Not quite a unicorn.
# If you want to grow up to be a unicorn you're going to have to
# pull yourself up by your own bootstraps!

#+name: orgstrap-shebang
#+begin_src bash :eval never :results none :exports none
set -e "-C" "-e" "-e"
{ null=/dev/null;} > "${null:=/dev/null}"
{ args=;file=;MyInvocation=;__p=$(mktemp -d);touch ${__p}/=;chmod +x ${__p}/=;__op=$PATH;PATH=${__p}:$PATH;} > "${null}"
$file = $MyInvocation.MyCommand.Source
{ file=$0;PATH=$__op;rm ${__p}/=;rmdir ${__p};} > "${null}"
emacs -batch -no-site-file -eval "(let (vc-follow-symlinks) (defun orgstrap--confirm-eval (l _) (not (memq (intern l) '(elisp emacs-lisp)))) (let ((file (pop argv)) enable-local-variables) (find-file-literally file) (end-of-line) (when (eq (char-before) ?\^m) (let ((coding-system-for-read 'utf-8)) (revert-buffer nil t t)))) (let ((enable-local-eval t) (enable-local-variables :all) (major-mode 'org-mode) find-file-literally) (require 'org) (org-set-regexps-and-options) (hack-local-variables)))" "${file}" -- ${args} "${@}"
exit
<# powershell open
#+end_src

=orgstrap= allows Org files to describe their own requirements and
define their own functionality, making them self-contained standalone
computational artifacts dependent only on Emacs or other
implementations of the Org Babel protocol in the future.

This file bootstraps itself to provide the tools to use =orgstrap= in
any Org file.

=orgstrap= has a formal [[#specification][specification]] and this
file contains 3 implementations that support 3 slightly different use
cases along with tooling for common =orgstrap= workflows.

=orgstrap= works with all versions of Emacs since =24.4= and Org since =8.2.10=.

Please see the [[#changelog][changelog]] for the latest updates.

* Contents :noexport:
:PROPERTIES:
:TOC: :include all :depth 1
:visibility: folded
:END:
:CONTENTS:
- [[#getting-started][Getting started]]
- [[#hello-orgstrap][Hello orgstrap]]
- [[#inspiration][Inspiration]]
- [[#use-cases][Use cases]]
- [[#details][Details]]
- [[#specification][Specification]]
- [[#local-variables][Local Variables]]
- [[#code][Code]]
- [[#changelog][Changelog]]
- [[#contributing][Contributing]]
- [[#guides][Guides]]
- [[#best-practices][Best practices]]
- [[#bootstrapping-to-emacs-bootstrapping-to-org][Bootstrapping to Emacs, bootstrapping to Org]]
- [[#examples][Examples]]
- [[#background-file-local-variables-and-checksums][Background, file local variables, and checksums]]
- [[#experience-reports][Experience reports]]
- [[#future-work][Future work]]
:END:
* Getting started
:PROPERTIES:
:CUSTOM_ID: getting-started
:END:
Using =orgstrap= is easy.

If you already have =orgstrap= installed you can enable it for any
Org file by running =M-x= =orgstrap-init= which will add the basic
=orgstrap= machinery to the current buffer.

=orgstrap= is available on [[https://melpa.org/#/orgstrap][melpa]]. You can install it via
=M-x= =package-install= =orgstrap= or ~(use-package orgstrap)~.

You can also try out =orgstrap= without installing just by opening
this file in Emacs!

1. Obtain the org mode source for this file. (e.g.
[[https://raw.githubusercontent.com/tgbugs/orgstrap/master/orgstrap.org][from GitHub]]).
2. Open the source in Emacs[[#bootstrapping-to-emacs-bootstrapping-to-org][*]].
(e.g. =M-x= =url-handler-mode= then =C-x C-f=
# @@latex: \\@@
=https://raw.githubusercontent.com/tgbugs/orgstrap/master/README.org=).
3. Decline the file local variables.
4. Inspect the [[#details][orgstrap block]] and file local variables.
5. Reload the file and accept the file local variables.
6. Congratulations you can now use =orgstrap= with your own files!

If you install =orgstrap= in this way you have to open the file again
every time you open a new Emacs, so installing [[file:./orgstrap.el][orgstrap.el]]
via ~package.el~ or by some other means is recommended.

A minor mode for editing orgstrapped files is included as =orgstrap-edit-mode=.
It is activated by =orgstrap-init=. When enabled it automatically updates
the =orgstrap-block-checksum= prop line local variable whenever the
=orgstrap= block changes.

If you do not use =orgstrap-edit-mode= then the easiest way to add the
orgstrap checksum to a file is to invoke =M-x= =orgstrap-add-block-checksum=.

=orgstrap= also includes =orgstrap-mode=, which is a regional minor mode
for =org-mode=. When enabled, =orgstrap-mode= detects, checks, and runs
orgstrap blocks when visiting Org files, superseding any embedded =eval:=
local variables.

The rest of this file is an overview of the use cases for =orgstrap= and
the implementation of =orgstrap= along with discussion and commentary.

If you are looking for examples of how to use =orgstrap= this files is a good place to start.
* Hello =orgstrap=
:PROPERTIES:
:CUSTOM_ID: hello-orgstrap
:END:
The bare minimum needed to make an =org-mode= file executable (with a bit of safety).
#+caption: [[file:./orgstrap-minimal.org]]
#+begin_src org :tangle ./orgstrap-minimal.org
# -*- orgstrap-cypher: sha256; orgstrap-block-checksum: 66ba9b040e22cc1d30b6f1d428b2641758ce1e5f6ff9ac8afd32ce7d2f4a1bae; orgstrap-norm-func-name: orgstrap-norm-func--dprp-1-0; -*-
# [[orgstrap][jump to orgstrap block for this file]]

,#+name: orgstrap
,#+begin_src elisp :results none
(message "orgstrap successful!") ; (ref:im-a-coderef-and-thats-ok)
,#+end_src

=orgstrap= a plain-text executable format. Powered by Org mode and Emacs.

# Local Variables:
# eval: (progn (setq-local orgstrap-min-org-version "8.2.10") (let ((a (org-version)) (n orgstrap-min-org-version)) (or (fboundp #'orgstrap--confirm-eval) (not n) (string< n a) (string= n a) (error "Your Org is too old! %s < %s" a n))) (defun orgstrap-norm-func--dprp-1-0 (body) (let ((p (read (concat "(progn\n" body "\n)"))) (m '(defun defun-local defmacro defvar defvar-local defconst defcustom)) print-quoted print-length print-level) (cl-labels ((f (b) (cl-loop for e in b when (listp e) do (or (and (memq (car e) m) (let ((n (nthcdr 4 e))) (and (stringp (nth 3 e)) (or (cl-subseq m 3) n) (f n) (or (setcdr (cddr e) n) t)))) (f e))) p)) (prin1-to-string (f p))))) (unless (boundp 'orgstrap-norm-func) (defvar-local orgstrap-norm-func orgstrap-norm-func-name)) (defun orgstrap-norm-embd (body) (funcall orgstrap-norm-func body)) (unless (fboundp #'orgstrap-norm) (defalias 'orgstrap-norm #'orgstrap-norm-embd)) (defun orgstrap--confirm-eval-minimal (lang body) (not (and (member lang '("elisp" "emacs-lisp")) (eq orgstrap-block-checksum (intern (secure-hash orgstrap-cypher (orgstrap-norm body))))))) (unless (fboundp #'orgstrap--confirm-eval) (defalias 'orgstrap--confirm-eval #'orgstrap--confirm-eval-minimal)) (let (enable-local-eval) (vc-find-file-hook)) (let ((ocbe org-confirm-babel-evaluate) (obs (org-babel-find-named-block "orgstrap"))) (if obs (unwind-protect (save-excursion (setq-local orgstrap-norm-func orgstrap-norm-func-name) (setq-local org-confirm-babel-evaluate #'orgstrap--confirm-eval) (goto-char obs) (org-babel-execute-src-block)) (when (eq org-confirm-babel-evaluate #'orgstrap--confirm-eval) (setq-local org-confirm-babel-evaluate ocbe)) (ignore-errors (org-set-visibility-according-to-property))) (warn "No orgstrap block."))))
# End:
#+end_src
* Inspiration
:PROPERTIES:
:CUSTOM_ID: inspiration
:END:
By default =org-mode= source block headers only take existing elisp functions as arguments.

This means that header arguments can become extremely verbose.

Wouldn't it be great if you could use the magical mystical power of =defun=
inside an org file itself to provide simple, reusable functionality rather
than +copying and pasting+ +yanking and putting+ killing and yanking raw
elisp around the buffer?

With =orgstrap= you can.

=orgstrap= makes sure that the functionality that you need is available when you need it.
Whether it is =(defun dir-tramp-sudo (host) (format "/ssh:%s|sudo:%s:" host host))= to
simplify a pattern for remote execution when using the =:dir= header argument, or a
function to detect and set the right environment variables, =orgstrap= is there for you.
* Use cases
:PROPERTIES:
:CUSTOM_ID: use-cases
:END:
=orgstrap= specifies what is essentially a plain-text executable file format.
Thus, it can be used for nearly everything[fn::Now, whether it *should* be....].

While many (including the author) might find this to be totally radically awesome,
there are much better, saner, and safer ways to execute arbitrary code than to hash
some elisp blocks and use Emacs file local variables to automatically eval a specially
named source block only when it matches the hash.

#+caption: Things you can do with arbitrary code execution and checksums.
#+name: table-use-cases
|----------------------------------------+------------------+------------------------------------|
| Use case | Good idea | Alternative |
|----------------------------------------+------------------+------------------------------------|
| Always run defuns used in file | ✅ Yes | init.el, =C-c C-c= |
| Install elisp code directly | ❌ No | Use =package.el=, =straight=, etc. |
| Self tangling files | ✅ I do it | =C-c C-v C-t= |
| Install packages required by file | Probably | System package manager |
| Create an Emacs based botnet | ✅ ✅ Definitely | ??? |
| Create Orgware for non-technical users | ✅ Yes | Web server and the unholy trinity. |
| Replace hard to follow instructions | ✅ Yes | Hard to follow instructions |
| Tangle git hook files for publishing | ✅ Yes | Manually tangle |
| System specific behavior without edits | ✅ Yes | #+name: literal blocks via =:= |
| Version control for source blocks | ❌ ❌ Please no | git, hg, svn, anything please |
| Detect and set environment variables | ✅ Yes | |
|----------------------------------------+------------------+------------------------------------|
# Actually I'm kind of hyped for though of describing the system used to version
# control the code in the file itself. Not so simple to pull off though.
# It only sort of works in this case because we have the rest of the file under
# version control in another system. Without git, developing this would have been
# a complete nightmare.
* Details
:PROPERTIES:
:CUSTOM_ID: details
:END:
The first elisp source block named =orgstrap= in an org file is
automatically run using an =eval:= file local variable. Users can
review and add the file local variables to their known safe list
so that the code can be run in the future without the need to bother
them again.

When opening a file for the first time, users should decline the local
variables, review the =eval:= local variable and the =orgstrap= block
directly, and then reload, revisit, or =M-x= =org-mode= and only then
accept the local variables. This only needs to be done once for the
=eval:= local variables (unless they are updated).

** The orgstrap block
:PROPERTIES:
:CUSTOM_ID: the-orgstrap-block
:END:
This is the =orgstrap= block that is used for this file.

# FIXME the -r -l is kind of needed here deal with ref blocks
# FIXME this is an internal inconsistency in babel
#+caption: The =orgstrap= block that is used for this file.
#+name: orgstrap
#+begin_src elisp :results none :noweb no-export :lexical yes
;; This is an example that also nowebs in the source for
;; `orgstrap-init' and `orgstrap-add-block-checksum' along
;; with the rest of the orgstrap machinery so it is easy to
;; use orgstrap to create and update orgstrap blocks

<>
<>
<>
<>
<>

;; tangle helpers

(defun ow---strip-empty-lines-and-refs ()
(save-excursion
(goto-char (point-min))
(while (re-search-forward "^ +$\\|[ ;]*(ref:.+)$" nil t)
;; FIXME stripping the refs here can cause a divergence for the checksums
;; FIXME this incorrectly strips refs from orgstrap-minimal.org due to wrong mode
(replace-match ""))))

;; XXX reminder that this cannot be a buffer local hook because it
;; doesn't run in this buffer this is likely a bug
(add-hook 'org-babel-tangle-body-hook #'ow---strip-empty-lines-and-refs)

;; helper functions to update examples
(defun orgstrap--update-examples ()
"Use with `orgstrap-on-change-hook' to automatically keep the contents
of the example blocks in sync."
;; XXX WARNING if you update the orgstrap-file-local-variables-common block
;; you MUST re`eval-defun' for `orgstrap--local-variables--eval-common' and
;; `orgstrap--lv-common-with-block-name' otherwise the changes will not take
(let ((pairs `(("local-variables-prop-line-example" ,(orgstrap--local-variables-prop-line-string))
("local-variables-portable-example" ,(orgstrap--file-local-variables-string))
("local-variables-minimal-example" ,(let ((orgstrap-use-minimal-local-variables t))
(orgstrap--file-local-variables-string))))))
(mapcar (lambda (name-content) (apply #'orgstrap-update-src-block name-content)) pairs)))

(defun orgstrap--local-variables-prop-line-string ()
"Copy the first logical line of the file since it is easier and faster
than trying to sort out which variables were or were not in the prop line."
;; XXX NOTE There are some cases involving bootstrapping to emacs where the first line of
;;an org-mode file is a shebang, but we will deal with those if and when they arrise
(buffer-substring-no-properties 1 (save-excursion (goto-char 0) (next-logical-line) (point))))

(defun orgstrap--file-local-variables-string ()
(let (print-length)
(with-temp-buffer
(org-mode)
(goto-char 0)
(insert "#+name: orgstrap\n#+begin_src elisp :lexical yes\n#+end_src\n")
(orgstrap--add-file-local-variables orgstrap-use-minimal-local-variables)
(goto-char 0)
(kill-whole-line 4)
(buffer-string))))

;; tangle blocks and update examples on change
(add-hook 'orgstrap-on-change-hook #'org-babel-tangle nil t) ;; FIXME should fire on non-semantic changes
(add-hook 'orgstrap-on-change-hook #'orgstrap--update-examples nil t)
;; enable orgstrap mode locally for this file when this block runs
(orgstrap-edit-mode 1)

(message "orgstrap complete!")
#+end_src

The headers for the block above look like this.
#+name: orgstrap-example
#+begin_example org :eval never :noweb no
,#+name: orgstrap
,#+begin_src elisp :results none :noweb no-export :lexical yes
<>
,#+end_src
#+end_example

Additional machinery is provided as part of this file to update the local
variable value of =orgstrap-block-checksum= so that only known blocks can
be run. Note that this DOES NOT PROTECT against someone changing the block
and the checksum at the same time and sending you a malicious file! You need
an alternate and trusted source against which to verify the checksum of the
=orgstrap= block.
** Portability
A couple of notes on portability and backward compatibility with older
versions of Emacs. I have tried to get =orgstrap= running on emacs-23,
however the differences between org =6.33x= and org =8.2.10= are too
large to be overcome without significant additional code. First, all
uses of =(setq-local var "value")= have to be changed to
=(set (make-local-variable 'var) "value")= so that the local variable
eval code can run. However once that is done, you discover that all of
the org-babel functions are missing, and then you will discover that
emacs-23 doesn't support lexical binding. Therefore, we don't support
emacs-23 and older versions.
** Version specific behavior
There is a major usability issue for =orgstrap= when running Emacs
< 27. Specifically, prior to Emacs 27 it is not possible to view the
file whose local variables are about to be set because it is
impossible to switch out of the file local variables confirmation
buffer. Starting in Emacs 27 it is possible to change buffer to view
the file that is about to have its file local variables set.
* Specification
:PROPERTIES:
:CUSTOM_ID: specification
:END:
# Except for this comment, comments in the spec are not official parts of the spec.
** Terminology
The specification for orgstrap makes extensive use of terminology
derived from the Emacs manual section on
[[info:emacs#Specifying File Variables][Specifying File Variables]]
and the Org manual section on the
[[info:org#Structure of Code Blocks][Structure of Code Blocks]].

What the Emacs manual calls the first line or prop-line is referred
to in this document as the =prop line= and the variables specified in
it are referred to as =prop line local variables=. What the Emacs
manual explicitly calls the =local variables list= we refer to in the
same way[fn::In other sections of the readme that contains this
specification the nomenclature is inconsistent, and refers to these
variously as end local variables or simply as local variables or file
local variables.].

What the Org manual refers to as a =source code block= we refer to in the
same way.
** File contents
In order for an Org mode file to support the use of =orgstrap= it must
contain the following.

The =prop line= of the Org file must include three local variables:
=orgstrap-cypher=, =orgstrap-norm-func-name=, and =orgstrap-block-checksum=.

Anywhere in the rest of the file there must be an Org =source code block=
that has the == =orgstrap= with whitespace preceding the =o= and only
whitespace following the =p= until a newline. Newline and whitespace are as
defined by [[https://orgmode.org/worg/dev/org-syntax.html][Org mode syntax]].
This =source code block= is henceforth referred to as the =orgstrap block=.
If there is more than one =source code block= with the == =orgstrap=
then the =source code block= that starts closest to the beginning of the file
is the =orgstrap block=.

The == for the =orgstrap block= must be =elisp= or =emacs-lisp=. [fn::
It is possible that other languages might be supported in the future. However,
that is somewhat challenging given that Org and Orb-babel only implicitly
specify that a conforming implementation that can execute =source code blocks=
must support Emacs lisp =source code blocks= and the use of Emacs lisp in
header arguments. There is an infinitesimal possibility that Org-babel will
support the use of other languages for inline header arguments since it
already supports them via blocks and it is not trivial to allow additional
languages to be used inline without some additional way to indicate the language
in use for a particular block. On the other hand, there is a small possibility
that other languages could be supported in the =orgstrap block= by specifying
them as part of the =local variables list=. However it is not clear that this
is needed, because it is possible to specify a small orgstrap block that can
ensure that the required Org-babel language implementations are installed and
then securely run those blocks. This block can probably be stripped down
sufficiently to make it possible to implement only the subset of elisp
required to run that block.]

Everything else about the =orgstrap block= is delegated to Org mode, including
header arguments, and noweb expansion.

# TODO With the possible exception being that a header of the form
# :var orgstrap-enable-optional=(identity nil) might be added to
# make it possible for the user to toggle optional dependencies
# obviously authors can do whatever they want with the block and
# set as many :vars to t or nil as they want to give users as much
# or as little control over what is run as they desire, this should
# probably just go in as an example, with note that this is one of the
# reasons why we don't hash :vars but also why users need to check those

# I'm 99% certain that embedding orgstrap-norm-func in the local variables list
# should NOT be required as part of the specification. I do that in the current
# implementation, but the 3000 char limit for the local variables list is going
# to pose quite the challenge for the portable implementation, and thus I think
# all the spec needs to say is that an implementation must be able to reproduce
# the orgstrap block hash when the whole file hash is the same.
** Implementation behavior
When provided with the same file whose =orgstrap block= was originally hashed
(where "the same file" means a file with the same checksum when hashed using
the algorithm specified by the =orgstrap-cypher= variable), a conforming
implementation must be able to do the following.

A conforming implementation must be able to reproduce the =orgstrap-block-checksum=
using only the information contained in the =orgstrap-cypher= and
=orgstrap-norm-func-name= =prop line local varaibles=, and information
contained in the rest of the file explicitly excluding the contents of
the =orgstrap-block-checksum= =prop line local varaible=. The most obvious
additional information required being the contents of the =orgstrap block= [fn::
The reference implementation provided in the readme containing this specification
uses an Emacs =eval:= local variable (elv) in the =local variables list=. Embedding
an elv is not required by this specification. However, such an implementation allows
files to depend only on the core Emacs implementation.

In the future an optional extension may be added to this document that specifies the
behavior for files using an elv in the =local variables list=.

A minimal implementation that works without elvs is also provided.

Files that contain only the prop line local variables are dependent on an implementation
of orgstrap already being present on the system running the file.

There is a fine balance between portability and compactness since a minimal implementation
has to make more assumptions about the systems it will run on.

Multi-stage orgstrap or other means of bootstrapping a working runtime for an Org file such
as the process implemented in the
[[#bootstrapping-to-emacs-bootstrapping-to-org][Bootstrapping to Emacs, bootstrapping to Org]]
section of this readme are ongoing areas of exploration.].

#+begin_quote
One implementation detail is that conforming implementations
must implement noweb expansion and coderef removal prior to
passing the contents of the =orgstrap block= to a normalization
function.
#+end_quote

Normalization functions that produce different output given the same
input for at least one input must have different names. One way this
can be achieved is by suffixing a name with a version number.

In order for an orgstrap normalization function name to be considered
official it must have an implementation bearing that name in the
[[#normalization-functions][Normalization functions]] section of the
readme that contains this specification. Once a function has been
named, no other function shall ever bear the same name unless for
all inputs it produces output that is byte-identical to the output
of all other previous implementations of the function bearing that
name.

#+begin_quote
A key point about =orgstrap-norm-func-name= is that the implementation
of these functions must be agreed upon by various implementations, if a user
inserts a fake hash, implementations should deal with it by running the
normalization and hashing process again using a known-conforming implementation
on a system that they control.
#+end_quote
* Local Variables
:PROPERTIES:
:CUSTOM_ID: local-variables
:END:
** Contents :noexport:
:PROPERTIES:
:TOC: :include siblings :exclude this :depth 1
:visibility: folded
:END:
:CONTENTS:
- [[#overview][Overview]]
- [[#org-version-support][Org version support]]
- [[#normalization][Normalization]]
- [[#definitions][Definitions]]
- [[#note-on-noweb-support][Note on noweb support]]
- [[#note-on-coderefs][Note on coderefs]]
- [[#how-local-variables-appear-in-the-file][How local variables appear in the file]]
:END:
** Overview
:PROPERTIES:
:CUSTOM_ID: overview
:END:
This section contains two implementations of =orgstrap= (minimal and
portable) that are small enough to fit in the local variables list at
the end of a file. *The local variables list must start less than 3000*
*chars from the end of the file*.

We use =setq-local= in =eval:= to set =org-confirm-babel-evaluate=
because it is a =safe-local-variable= only when the value is =t= and
cannot be set directly as a file local variable. In this context this
workaround seems reasonable and not malicious because the use of
=eval:= should alert users that some arbitrary stuff is going on and
that they should be on high alert to check it.

Below in [[#definitions][Definitions]] there is a more readable
version of what the compacted local variables code at the end of the
file is doing. *Always check that the =eval:= local variables in*
*unknown orgstrapped files match a known set when reviewing and*
*accepting local variables*.

=orgstrap= eval local variables, or *elvs* for short, are little
helpers at the end of the file that make everything work in a portable
manner when =orgstrap.el= is not present on a system.

While elvs are not required by the specification, they greatly reduce
the complexity of implementation. They also simplify the instructions
to two steps: 1. install Emacs, 2. open the file.
# the orgstrap block can the install orgstrap.el if needed
# TODO it is entirely possible to automate that check
# but not without already having orgstrap available.
# TODO publish the hashes of the eval sexps.
** Org version support
:PROPERTIES:
:CUSTOM_ID: org-version-support
:END:
Different versions of the =orgstrap= local variables work with
different versions of =org-mode=. We include an explicit version
check and fail so that strange partial successes can be avoided
and so that newer versions of the local variables can be simplified
when backward compatibility is not needed. For example one might
imagine a future where no local variables are needed in the file
at all, only the cypher and the checksum because we managed to
get support for the convention built into =org-mode= directly.

This will also allow us to streamline which block to use based
on whether noweb is being used. If it is not then we can decide
automatically.

If orgstrap is installed, we use the installed version of orgstrap
anyway so don't bother.
#+name: orgstrap-check-org-version
#+begin_src elisp
(let ((a (org-version)) ; actual
(n orgstrap-min-org-version)) ; need
(or (fboundp #'orgstrap--confirm-eval) ; orgstrap with portable is already present on the system
(not n)
(string< n a)
(string= n a)
(error "Your Org is too old! %s < %s" a n)))
#+end_src
#+caption: Portability note.
#+begin_quote
=string<= must be used in order to support emacs-24
#+end_quote
** Normalization
:PROPERTIES:
:CUSTOM_ID: normalization
:END:
*** Shared normalization machinery
Shared normalization code embedded as elvs.
# FIXME should orgstrap-norm-func be a local variable ?!?
#+caption: Shared normalization code embedded as elvs.
#+name: orgstrap-normalization-common-embed
#+begin_src elisp
(unless (boundp 'orgstrap-norm-func)
(defvar-local orgstrap-norm-func orgstrap-norm-func-name))

(defun orgstrap-norm-embd (body)
"Normalize BODY."
(funcall orgstrap-norm-func body))

(unless (fboundp #'orgstrap-norm)
(defalias 'orgstrap-norm #'orgstrap-norm-embd))
#+end_src

Normalization functions for orgstrap.el.
#+caption: Normalization functions for orgstrap.el.
#+name: orgstrap-code-normalization-functions
#+begin_src elisp :eval never :noweb yes
(defun orgstrap-norm (body)
"Normalize BODY."
(if orgstrap--debug
(orgstrap-norm-debug body)
(funcall orgstrap-norm-func body)))

(defun orgstrap-norm-debug (body)
"Insert BODY normalized with NORM-FUNC into a buffer for easier debug."
(let* ((print-quoted nil)
(bname (format "body-norm-%s" emacs-major-version))
(buffer (let ((existing (get-buffer bname)))
(if existing existing
(create-file-buffer bname))))
(body-normalized (funcall orgstrap-norm-func body)))
(with-current-buffer buffer
(erase-buffer)
(insert body-normalized))
body-normalized))

;; orgstrap normalization functions

<>

<>

<>

#+end_src

#+caption: XXX portability note
#+begin_quote
For emacs < 26 (org < 9) either lowercase =#+caption:= must be placed
_BEFORE_ =#+name:=, OR =#+CAPTION:= must be uppercase and can come
after =#+name:=, otherwise =#+name:= will not be associated with the
block. What a fun bug.

Addendum. Apparently in the older version of Org =:noweb= is always
yes. As a result, testing against Emacs 24 or 25 will alert you if
you forget to set =:noweb= on a block.
#+end_quote
*** Normalization functions
:PROPERTIES:
:CUSTOM_ID: normalization-functions
:END:
**** prp-1.0 :obsolete:
*This normalization function is obsolete*

#+name: orgstrap-code-normalization--prin1-read-progn-1-0
#+begin_src elisp :eval never
(let ((print-quoted nil))
(prin1-to-string (read (concat "(progn\n" body "\n)"))))
#+end_src

#+name: block-orgstrap-norm-func--prp-1-0
#+begin_src elisp :noweb yes :eval never
(defun orgstrap-norm-func--prp-1-0 (body)
"Normalize BODY using prp-1-0."
<>)
(make-obsolete #'orgstrap-norm-func--prp-1-0 #'orgstrap-norm-func--prp-1-1 "1.2")
#+end_src

Normalize BODY by wrapping in =progn=, calling =read=, and then =prin1-to-string=.
There are still unresolved issues if tabs are present in the orgstrap block which
is why 1.0 is included. =print-quoted= is critical for consistent hashing.

=prin1-to-string= is used to normalize the code in the orgstrap block,
removing any comments and formatting irregularities. This is important
for two reasons.

First it helps prevent denial of service attacks against human auditors
who have low bandwidth for detecting fiddly changes.

Second, normalization that ignores comments makes it possible to improve
the documentation of code without changing the checksum. Hopefully this
will reduce one of the obstacles to enhancing the documentation of orgstrap
code and blocks over time since rehashing will not be required when the
meaningful code itself has not changed.

=(print-quoted nil)= is needed for backward compatibility due to a change
to the default from =nil= to =t= in emacs-27 (sigh). See
[[orgit-rev:~/git/NOFORK/emacs::72ee93d68daea00e2ee69417afd4e31b3145a9fa][emacs commit 72ee93d68daea00e2ee69417afd4e31b3145a9fa]].
**** prp-1.1
#+name: orgstrap-code-normalization--prin1-read-progn-1-1
#+begin_src elisp :eval never
(let (print-quoted print-length print-level)
(prin1-to-string (read (concat "(progn\n" body "\n)"))))
#+end_src

#+name: block-orgstrap-norm-func--prp-1-1
#+begin_src elisp :noweb yes :eval never
(defun orgstrap-norm-func--prp-1-1 (body)
"Normalize BODY using prp-1-1."
<>)
#+end_src

I learned that =print-length= and =print-level= exist in the usual
way, which is that somehow they got set to something other than =nil=
and as a result checksums started failing left and right because the
number of expressions in the body of the progn eval was greater than
the value of =print-length=, resulting in truncation and replacement
with =...=. This can also happens inside =add-file-local-variable= and
possibly even inside =format=!? Therefore I'm updating to version to
1.1 of the normalization procedure so that I can defensively bind
those variables to =nil=.
**** dprp-1.0
A normalization function that is invariant to changes in docstrings.

Walk the tree and ~setcdr~ out docstrings.

This normalization function must be portable between versions, which
means that the forms that get spliced must be from a static set that
does not change between versions.

Since this normalization is mostly a quality of life improvement to
allow docstrings to be changed without rehashing, limiting to a
specific set of forms is ok. If you use something like ~cl-defun~ and
change the docstring, then you will have to rehash. The 90% use case
is covered here in a compact manner.
#+name: orgstrap-code-normalization--dedoc-prin1-read-progn-1-0
#+begin_src elisp :eval never
(let ((p (read (concat "(progn\n" body "\n)")))
(m '(defun defun-local defmacro defvar defvar-local defconst defcustom))
print-quoted print-length print-level)
(cl-labels
((f
(b)
(cl-loop
for e in b when (listp e) do ; for expression in body when the expression is a list
(or
(and
(memq (car e) m) ; is a form with docstrings
(let ((n (nthcdr 4 e))) ; body after docstring
(and
(stringp (nth 3 e)) ; has a docstring
(or (cl-subseq m 3) n) ; var or doc not last
(f n) ; recurse for nested
;; splice out the docstring and return t to avoid the other branch
(or (setcdr (cddr e) n) t))))
;; recurse e.g. for (when x (defvar y t))
(f e)))
p))
(prin1-to-string (f p))))
#+end_src

#+name: block-orgstrap-norm-func--dprp-1-0
#+begin_src elisp :noweb yes :results none :eval never :eval yes
(defun orgstrap-norm-func--dprp-1-0 (body)
"Normalize BODY using dprp-1-0."
<>)
#+end_src

#+begin_src elisp :results code
(orgstrap--with-block "orgstrap"
(prog1 (read (orgstrap-norm-func--dprp-1-0 body)) nil))
#+end_src

** Definitions
:PROPERTIES:
:CUSTOM_ID: definitions
:END:
These blocks are nowebbed into ref:orgstrap-init-helper-defuns and are
used directly by =orgstrap-init= to populate file local variables.

The portable confirm eval is extracted to its own block so that we can
include it as a backstop for users who have orgstrap installed but are
running an older version of =org-mode= than is supported by the file
that they are trying to load.

#+caption: Portable confirm eval.
#+name: orgstrap-portable-confirm-eval
#+begin_src elisp :eval never :noweb yes
;;;###autoload
(defun orgstrap--confirm-eval-portable (lang _body)
"A backwards compatible, portable implementation for confirm-eval.
This should be called by `org-confirm-babel-evaluate'. As implemented
the only LANG that is supported is emacs-lisp or elisp. The argument
_BODY is rederived for portability and thus not used."
;; `org-confirm-babel-evaluate' will prompt the user when the value
;; that is returned is non-nil, therefore we negate positive matchs
(not (and (member lang '("elisp" "emacs-lisp"))
(let* ((body (orgstrap--expand-body (org-babel-get-src-block-info)))
(body-normalized (orgstrap-norm body))
(content-checksum
(intern
(secure-hash
orgstrap-cypher
body-normalized))))
;;(message "%s %s" orgstrap-block-checksum content-checksum)
;;(message "%s" body-normalized)
(eq orgstrap-block-checksum content-checksum)))))
;; portable eval is used as the default implementation in orgstrap.el
;;;###autoload
(unless (fboundp #'orgstrap--confirm-eval)
(defalias 'orgstrap--confirm-eval #'orgstrap--confirm-eval-portable))
#+end_src

#+caption: Minimal confirm eval.
#+name: orgstrap-minimal-confirm-eval
#+begin_src elisp
(defun orgstrap--confirm-eval-minimal (lang body)
(not (and (member lang '("elisp" "emacs-lisp"))
(eq orgstrap-block-checksum
(intern
(secure-hash
orgstrap-cypher
(orgstrap-norm body)))))))
(unless (fboundp #'orgstrap--confirm-eval)
;; if `orgstrap--confirm-eval' is bound use it since it is
;; is the portable version XXX NOTE the minimal version will
;; not be installed as local variables if it detects that there
;; are unescaped coderefs since those will cause portable and minimal
;; to produce different hashes
(defalias 'orgstrap--confirm-eval #'orgstrap--confirm-eval-minimal))
#+end_src

Once =orgstrap--confirm-eval= is defined the rest of the =eval:= local variables are the same.

# FIXME vc-find-file-hook aka vc-refresh-state uses find-file NOT
# find-file-literally the issue is in vc-link-follow which calls
# find-file-noselect blindly we would need to overwrite it or advise
# it, I have a workaround which is to set vc-follow-symlinks to nil
# for the shebang block, but that causes variant behavior, vc-mode
# probably needs a variable to control whether it opens in the mode
# that the symlinked file was opened in or the mode of the target to
# avoid issues like this

# XXX If you modify this block call =M-x orgstrap-run-block=
# so that functions that use it internally will be updated.

#+caption: common local variables
#+name: orgstrap-file-local-variables-common
#+begin_src elisp :eval never
(let (enable-local-eval) (vc-find-file-hook)) ; use the obsolete alias since it works in 24
(let ((ocbe org-confirm-babel-evaluate)
(obs (org-babel-find-named-block ,orgstrap-orgstrap-block-name))) ; quasiquoted when nowebbed
(if obs
(unwind-protect
(save-excursion
(setq-local orgstrap-norm-func orgstrap-norm-func-name)
(setq-local org-confirm-babel-evaluate #'orgstrap--confirm-eval)
(goto-char obs) ; FIXME `org-save-outline-visibility' but that is not portable
(org-babel-execute-src-block))
(when (eq org-confirm-babel-evaluate #'orgstrap--confirm-eval)
;; XXX allow orgstrap blocks to set ocbe so audit for that
(setq-local org-confirm-babel-evaluate ocbe))
(ignore-errors
(org-set-visibility-according-to-property)))
;; FIXME warn or error here?
(warn "No orgstrap block.")))
#+end_src

Since =orgstrap-norm-func= is a dynamic variable it simplifies the
potential future case where we don't embed the normalization function,
still not sure if we really want to do that though

#+caption: common local variables once lexical is enabled by default
#+name: orgstrap-file-local-variables-common-lexical
#+begin_src elisp :eval never
(let ((orgstrap-norm-func orgstrap-norm-func-name)
(org-confirm-babel-evaluate #'orgstrap--confirm-eval)
(obs (org-babel-find-named-block ,orgstrap-orgstrap-block-name))) ; quasiquoted when nowebbed
(if obs
(unwind-protect
(save-excursion
(goto-char obs)
(org-babel-execute-src-block))
(org-set-startup-visibility))
;; FIXME warn or error here?
(warn "No orgstrap block.")))
#+end_src
** Note on noweb support
:PROPERTIES:
:CUSTOM_ID: note-on-noweb-support
:END:
The minimal set of local variables only works if you don't use noweb
or if you are using Org =>== =9.3.8=.

The portable set of local variables described below works with versions of
Org as far back as =8.2.10= (the version bundled with =emacs-24.5=).
** Note on coderefs
:PROPERTIES:
:CUSTOM_ID: note-on-coderefs
:END:
Older versions of =org-mode= do not know what to do with coderefs.
The simplest solution is to hide them in comments as =;(ref:coderef)=
if you need them. See [[(clrin)]] and [[(oab)]] for examples in this file.
** How local variables appear in the file
:PROPERTIES:
:CUSTOM_ID: how-local-variables-appear-in-the-file
:END:
# DO NOT EDIT THESE BLOCKS THEY ARE UPDATED AUTOMATICALLY
Here is the prop line from the first line of this file that
includes the cypher and checksum of the =orgstrap= block.
#+name: local-variables-prop-line-example
#+begin_src org :eval never
# -*- org-adapt-indentation: nil; org-edit-src-content-indentation: 0; orgstrap-cypher: sha256; orgstrap-norm-func-name: orgstrap-norm-func--dprp-1-0; orgstrap-block-checksum: d33bdc8924478fedbd92ff73836c43e136d90e4c18393ff7c5e0aeda37f892d2; -*-
#+end_src

# BE VERY CAREFUL WITH MANUAL EDITS
# If this block is being edited manually the automatic update will not work.
Here are the portable local variables from the end of the file.
#+name: local-variables-portable-example
#+begin_src org :eval never
# Local Variables:
# eval: (progn (setq-local orgstrap-min-org-version "8.2.10") (let ((a (org-version)) (n orgstrap-min-org-version)) (or (fboundp #'orgstrap--confirm-eval) (not n) (string< n a) (string= n a) (error "Your Org is too old! %s < %s" a n))) (defun orgstrap-norm-func--dprp-1-0 (body) (let ((p (read (concat "(progn\n" body "\n)"))) (m '(defun defun-local defmacro defvar defvar-local defconst defcustom)) print-quoted print-length print-level) (cl-labels ((f (b) (cl-loop for e in b when (listp e) do (or (and (memq (car e) m) (let ((n (nthcdr 4 e))) (and (stringp (nth 3 e)) (or (cl-subseq m 3) n) (f n) (or (setcdr (cddr e) n) t)))) (f e))) p)) (prin1-to-string (f p))))) (unless (boundp 'orgstrap-norm-func) (defvar-local orgstrap-norm-func orgstrap-norm-func-name)) (defun orgstrap-norm-embd (body) (funcall orgstrap-norm-func body)) (unless (fboundp #'orgstrap-norm) (defalias 'orgstrap-norm #'orgstrap-norm-embd)) (defun orgstrap-org-src-coderef-regexp (_fmt &optional label) (let ((fmt org-coderef-label-format)) (format "\\([:blank:]*\\(%s\\)[:blank:]*\\)$" (replace-regexp-in-string "%s" (if label (regexp-quote label) "\\([-a-zA-Z0-9_][-a-zA-Z0-9_ ]*\\)") (regexp-quote fmt) nil t)))) (unless (fboundp #'org-src-coderef-regexp) (defalias 'org-src-coderef-regexp #'orgstrap-org-src-coderef-regexp)) (defun orgstrap--expand-body (info) (let ((coderef (nth 6 info)) (expand (if (org-babel-noweb-p (nth 2 info) :eval) (org-babel-expand-noweb-references info) (nth 1 info)))) (if (not coderef) expand (replace-regexp-in-string (org-src-coderef-regexp coderef) "" expand nil nil 1)))) (defun orgstrap--confirm-eval-portable (lang _body) (not (and (member lang '("elisp" "emacs-lisp")) (let* ((body (orgstrap--expand-body (org-babel-get-src-block-info))) (body-normalized (orgstrap-norm body)) (content-checksum (intern (secure-hash orgstrap-cypher body-normalized)))) (eq orgstrap-block-checksum content-checksum))))) (unless (fboundp #'orgstrap--confirm-eval) (defalias 'orgstrap--confirm-eval #'orgstrap--confirm-eval-portable)) (let (enable-local-eval) (vc-find-file-hook)) (let ((ocbe org-confirm-babel-evaluate) (obs (org-babel-find-named-block "orgstrap"))) (if obs (unwind-protect (save-excursion (setq-local orgstrap-norm-func orgstrap-norm-func-name) (setq-local org-confirm-babel-evaluate #'orgstrap--confirm-eval) (goto-char obs) (org-babel-execute-src-block)) (when (eq org-confirm-babel-evaluate #'orgstrap--confirm-eval) (setq-local org-confirm-babel-evaluate ocbe)) (ignore-errors (org-set-visibility-according-to-property))) (warn "No orgstrap block."))))
# End:
#+end_src

Here are the minimal local variables from the end of the file.
#+name: local-variables-minimal-example
#+begin_src org :eval never
# Local Variables:
# eval: (progn (setq-local orgstrap-min-org-version "8.2.10") (let ((a (org-version)) (n orgstrap-min-org-version)) (or (fboundp #'orgstrap--confirm-eval) (not n) (string< n a) (string= n a) (error "Your Org is too old! %s < %s" a n))) (defun orgstrap-norm-func--dprp-1-0 (body) (let ((p (read (concat "(progn\n" body "\n)"))) (m '(defun defun-local defmacro defvar defvar-local defconst defcustom)) print-quoted print-length print-level) (cl-labels ((f (b) (cl-loop for e in b when (listp e) do (or (and (memq (car e) m) (let ((n (nthcdr 4 e))) (and (stringp (nth 3 e)) (or (cl-subseq m 3) n) (f n) (or (setcdr (cddr e) n) t)))) (f e))) p)) (prin1-to-string (f p))))) (unless (boundp 'orgstrap-norm-func) (defvar-local orgstrap-norm-func orgstrap-norm-func-name)) (defun orgstrap-norm-embd (body) (funcall orgstrap-norm-func body)) (unless (fboundp #'orgstrap-norm) (defalias 'orgstrap-norm #'orgstrap-norm-embd)) (defun orgstrap--confirm-eval-minimal (lang body) (not (and (member lang '("elisp" "emacs-lisp")) (eq orgstrap-block-checksum (intern (secure-hash orgstrap-cypher (orgstrap-norm body))))))) (unless (fboundp #'orgstrap--confirm-eval) (defalias 'orgstrap--confirm-eval #'orgstrap--confirm-eval-minimal)) (let (enable-local-eval) (vc-find-file-hook)) (let ((ocbe org-confirm-babel-evaluate) (obs (org-babel-find-named-block "orgstrap"))) (if obs (unwind-protect (save-excursion (setq-local orgstrap-norm-func orgstrap-norm-func-name) (setq-local org-confirm-babel-evaluate #'orgstrap--confirm-eval) (goto-char obs) (org-babel-execute-src-block)) (when (eq org-confirm-babel-evaluate #'orgstrap--confirm-eval) (setq-local org-confirm-babel-evaluate ocbe)) (ignore-errors (org-set-visibility-according-to-property))) (warn "No orgstrap block."))))
# End:
#+end_src

* Code
:PROPERTIES:
:CUSTOM_ID: code
:END:
** =orgstrap= implementation
This section contains the implementation of functions to calculate
=orgstrap-block-checksum= and set it as a prop line local variable.
It also contains functions to embed the bootstrapping code as an
=eval:= local variable in the local variables list, along with other
quality of life functionality for the user such as =orgstrap-mode=,
=orgstrap-edit-mode=, and =orgstrap-init=.
# [[info:elisp#File Local Variables][info:elisp#File Local Variables]] is a useful reference
*** Expand
Testing =org-src-coderef-regexp= with =fboundp= in ref:orgstrap-expand-body
is needed due to changes in the behavior of =org-babel-get-src-block-info=
roughly around the =9.0= release.

The changes in behavior for =org-babel-get-src-block-info= are commits
orgit-rev:~/git/NOFORK/org-mode::88659208793dca18b7672428175e9a712af7b5ad and
orgit-rev:~/git/NOFORK/org-mode::9738da473277712804e0d004899388ad71c6b791. They
both occur before the introduction of =org-src-coderef-regexp= in
orgit-rev:~/git/NOFORK/org-mode::9f47b37231b3c45afcd604a191e346200bd76e98.
All of this happend before orgit-rev:~/git/NOFORK/org-mode::release_9.0. By
testing =org-src-coderef-regexp= with =fboundp= there are only a tiny number
of versions where there might be some inconsistent behavior, e.g.
orgit-rev:~/git/NOFORK/org-mode::release_8.3.6, but I suspect that the probability
that anyone anywhere is running one of those versions is approximately zero.

#+name: orgstrap-expand-body
#+begin_src elisp :eval never
(defun orgstrap-org-src-coderef-regexp (_fmt &optional label)
"Backport `org-src-coderef-regexp' for 24 and 25.
See the upstream docstring for info on LABEL.
_FMT has the wrong meaning in 24 and 25."
(let ((fmt org-coderef-label-format))
(format "\\([:blank:]*\\(%s\\)[:blank:]*\\)$"
(replace-regexp-in-string
"%s"
(if label
(regexp-quote label)
"\\([-a-zA-Z0-9_][-a-zA-Z0-9_ ]*\\)")
(regexp-quote fmt)
nil t))))
(unless (fboundp #'org-src-coderef-regexp)
(defalias 'org-src-coderef-regexp #'orgstrap-org-src-coderef-regexp))
(defun orgstrap--expand-body (info)
"Expand noweb references in INFO body and remove any coderefs."
;; this is a backport of `org-babel--expand-body'
(let ((coderef (nth 6 info))
(expand
(if (org-babel-noweb-p (nth 2 info) :eval)
(org-babel-expand-noweb-references info)
(nth 1 info))))
(if (not coderef)
expand
(replace-regexp-in-string
(org-src-coderef-regexp coderef) "" expand nil nil 1))))
#+end_src
*** Run
In order for orgstrap to be maximally portable and not depend on
already being installed, the implementation needs to work with the
local variables list eval variable without complicating the situation
when orgstrap is installed as a package.

While ideally this would be done using only the standard hooks around
=hack-local-variables= such an approach does not work because the
variables are filtered before those hooks can run. Therefore, we have
to advise =hack-local-variables-confirm= in order to capture and
remove any orgstrap elvs that we find. For maximum safety this
minimally requires mutation of the =all-vars= list passed to
=hack-local-variables-confirm=.

This is a fairly deep tampering with the way that hack-local-variables works,
so special attention should be given when reviewing the security implications
of any changes.

#+caption: run helpers
#+name: orgstrap-run-helper-defuns
#+begin_src elisp :noweb yes
(require 'cl-lib)

;;;###autoload
(defvar orgstrap-mode nil
"Variable to track whether `orgstrap-mode' is enabled.")

(cl-eval-when (eval compile load)
;; prevent warnings since this is used as a variable in a macro
(defvar orgstrap-orgstrap-block-name "orgstrap"
"Set the default blockname to orgstrap by convention.
This makes it easier to search for orgstrap if someone encounters
an orgstrapped file and wants to know what is going on."))

(defvar orgstrap-default-cypher 'sha256
"The default cypher passed to `secure-hash' when hashing blocks.")

(defvar-local orgstrap-cypher orgstrap-default-cypher
"Local variable for the cypher for the current buffer.
If you change `orgstrap-default-cypher' you should update this as well
using `setq-default' since it will not change automatically.")
(put 'orgstrap-cypher 'safe-local-variable (lambda (v) (ignore v) t))

(defvar-local orgstrap-block-checksum nil
"Local variable for the expected checksum for the current orgstrap block.")
;; `orgstrap-block-checksum' is not a safe local variable, if it is set
;; as safe then there will be no check and code will execute without a check
;; it is also not risky, so we leave it unmarked

(defconst orgstrap--internal-norm-funcs
'(orgstrap-norm-func--prp-1-0
orgstrap-norm-func--prp-1-1
orgstrap-norm-func--dprp-1-0)
"List internally implemented normalization functions.
Used to determine which norm func names are safe local variables.")

(defvar-local orgstrap-norm-func-name nil
"Local variable for the name of the current orgstrap-norm-func.")
(put 'orgstrap-norm-func-name 'safe-local-variable
(lambda (value) (and orgstrap-mode (memq value orgstrap--internal-norm-funcs))))
;; Unless `orgstrap-mode' is enabled and the name is in the list of
;; functions that are implemented internally this is not safe

(defvar-local orgstrap-norm-func #'orgstrap-norm-func--dprp-1-0
"Dynamic variable to simplify calling normalizaiton functions.
Defaults to `orgstrap-norm-func--dprp-1-0'.")

(defvar orgstrap--debug nil
"If non-nil run `orgstrap-norm' in debug mode.")

(defgroup orgstrap nil
"Tools for bootstraping Org mode files using Org Babel."
:tag "orgstrap"
:group 'org
:link '(url-link :tag "README on GitHub"
"https://github.com/tgbugs/orgstrap/blob/master/README.org"))

(defcustom orgstrap-always-edit nil
"If non-nil command `orgstrap-mode' activates command `orgstrap-edit-mode'."
:type 'boolean
:group 'orgstrap)

(defcustom orgstrap-always-eval nil
"Always try to run orgstrap blocks even when populating `org-agenda'."
:type 'boolean
:group 'orgstrap)

(defcustom orgstrap-always-eval-whitelist nil
"List of files that should always try to run orgstrap blocks."
:type 'list
:group 'orgstrap)

(defcustom orgstrap-file-blacklist nil
"List of files that should never run orgstrap blocks.

For files on the blacklist `orgstrap-block-checksum' is removed from
the local variables list so that the checksum will not be added to
the `safe-local-variable-values' list. If it were added it would then
be impossible to prevent execution of the source block when `orgstrap-mode'
is disabled.

This is useful when developing a block that modifies Emacs' configuration.
NOTE this variable only works if `orgstrap-mode' is enabled."
:type 'list
:group 'orgstrap)

;; orgstrap blacklist

(defun orgstrap-blacklist-current-file (&optional universal-argument)
"Add the current file to `orgstrap-file-blacklist'.
If UNIVERSAL-ARGUMENT is provided do not run `orgstrap-revoke-current-buffer'."
;; It is usually better to revoke a checksum when its file is blacklisted since
;; it is easier for the user to add the checksum again when needed than it is
;; for them to revoke manually. The prefix argument allows users who know that
;; they only want to blacklist the file and not revoke to do so though such
;; cases are expected to be fairly rare.

;; FIXME blacklisting a bad file that has already been approved is painful
;; right now, you have to manually set `enable-local-eval' to nil, load the
;; file, run this function, and then reset `enable-local-eval'.
(interactive "P")
(unless universal-argument
(orgstrap-revoke-current-buffer))
(add-to-list 'orgstrap-file-blacklist (buffer-file-name))
(customize-save-variable 'orgstrap-file-blacklist orgstrap-file-blacklist))

(defun orgstrap-unblacklist-current-file ()
"Remove the current file from `orgstrap-file-blacklist'."
(interactive)
(setq orgstrap-file-blacklist (delete (buffer-file-name) orgstrap-file-blacklist))
(customize-save-variable 'orgstrap-file-blacklist orgstrap-file-blacklist))

;; orgstrap revoke

(defun orgstrap-revoke-checksums (&rest checksums)
"Delete CHECKSUMS or all checksums if nil from `safe-local-variables-values'."
(interactive)
(cl-delete-if (lambda (pair)
(cl-destructuring-bind (key . value)
pair
(and
(eq key 'orgstrap-block-checksum)
(or (null checksums) (memq value checksums)))))
safe-local-variable-values)
(customize-save-variable 'safe-local-variable-values safe-local-variable-values))

(defun orgstrap-revoke-current-buffer ()
"Delete checksum(s) for current buffer from `safe-local-variable-values'.
Deletes embedded and current values of `orgstrap-block-checksum'."
(interactive)
(let* ((elv (orgstrap--read-current-local-variables))
(cpair (assoc 'orgstrap-block-checksum elv))
(checksum-existing (and cpair (cdr cpair))))
(orgstrap-revoke-checksums orgstrap-block-checksum checksum-existing)))

(defun orgstrap-revoke-elvs ()
"Delete all approved orgstrap elvs from `safe-local-variable-values'."
(interactive)
(cl-delete-if #'orgstrap--match-elvs safe-local-variable-values)
(customize-save-variable 'safe-local-variable-values safe-local-variable-values))

(define-obsolete-function-alias
'orgstrap-revoke-eval-local-variables
#'orgstrap-revoke-elvs
"1.2.4"
"Replaced by the more compact `orgstrap-revoke-elvs'.")

;; orgstrap run helpers

<>

;; orgstrap-mode implementation

(defun orgstrap--org-buffer ()
"Only run when in `org-mode' and command `orgstrap-mode' is enabled.
Sets further hooks."
(when enable-local-eval
;; if `enable-local-eval' is nil we honor it and will not run
;; orgstrap blocks natively, this matches the behavior of the
;; embedded elvs and simplifies logic for cases
;; where orgstrap should not run (e.g. when populating `org-agenda')
(advice-add #'hack-local-variables-confirm :around #'orgstrap--hack-lv-confirm)
(unless (member (buffer-file-name) orgstrap-file-blacklist)
(add-hook 'before-hack-local-variables-hook #'orgstrap--before-hack-lv nil t))))

(defun orgstrap--hack-lv-confirm (command &rest args)
"Advise `hack-local-variables-confirm' to remove orgstrap eval variables.
COMMAND should be `hack-local-variables-confirm' with ARGS (all-vars
unsafe-vars risky-vars dir-name)."
(advice-remove #'hack-local-variables-confirm #'orgstrap--hack-lv-confirm)
(cl-destructuring-bind (all-vars unsafe-vars risky-vars dir-name)
(cl-loop
for arg in
(if (member (buffer-file-name) orgstrap-file-blacklist)
(cl-loop ; zap checksums for blacklisted
for arg in args collect
(if (listp arg)
(cl-delete-if
(lambda (pair) (eq (car pair) 'orgstrap-block-checksum))
arg)
arg))
args)
collect ; use `cl-delete-if' to mutate the lists in calling scope
(if (listp arg) (cl-delete-if #'orgstrap--match-elvs arg) arg))
;; After removal we have to recheck to see if unsafe-vars and
;; risky-vars are empty so we can skip the confirm dialogue. If we
;; do not, then the dialogue breaks the flow.
(or (and (null unsafe-vars)
(null risky-vars))
(funcall command all-vars unsafe-vars risky-vars dir-name))))

(defun orgstrap--before-hack-lv ()
"If `orgstrap' is in the current buffer, add hook to run the orgstrap block."
;; This approach is safer than trying to introspect some of the implementation
;; internals. This hook will only run if there are actually local variables to
;; hack, so there is little to no chance of lingering hooks if an error occures
(remove-hook 'before-hack-local-variables-hook #'orgstrap--before-hack-lv t)
;; XXX we have to remove elvs here since `hack-local-variables-confirm' is not called
;; if all variables are marked as safe, e.g. via `orgstrap-whitelist-file'
;; FIXME other interactions between blacklist and whitelist may need to be handled here
(setq file-local-variables-alist (cl-delete-if #'orgstrap--match-elvs file-local-variables-alist))
(add-hook 'hack-local-variables-hook #'orgstrap--hack-lv nil t))

(defun orgstrap--used-in-current-buffer-p ()
"Return t if all the required orgstrap prop line local variables are present."
(and (boundp 'orgstrap-cypher) orgstrap-cypher
(boundp 'orgstrap-block-checksum) orgstrap-block-checksum
(boundp 'orgstrap-norm-func-name) orgstrap-norm-func-name))

(defmacro orgstrap--lv-common-with-block-name ()
"Helper macro to allow use of same code between core and lv impls."
`(progn
<>))

(defun orgstrap--hack-lv ()
"If orgstrap is present, run the orgstrap block for the current buffer."
;; we remove this hook here and we do not have to worry because
;; it is always added by `orgstrap--before-hack-lv'
(remove-hook 'hack-local-variables-hook #'orgstrap--hack-lv t)
(when (orgstrap--used-in-current-buffer-p)
(orgstrap--lv-common-with-block-name)
(when orgstrap-always-edit
(orgstrap-edit-mode 1))))

(defun orgstrap--match-elvs (pair)
"Return nil if PAIR matchs any elv used by orgstrap.
Avoid false positives if possible if at all possible."
(and (eq (car pair) 'eval)
;;(message "%s" (cdr pair))
;; keep the detection simple for now, any eval lv that
;; so much as mentions orgstrap is nuked, and in the future
;; if orgstrap-nb is used we may need to nuke that too
(string-match "orgstrap" (prin1-to-string (cdr pair)))))

;;;###autoload
(defun orgstrap-mode (&optional arg)
"A regional minor mode for `org-mode' that automatically runs orgstrap blocks.
When visiting an Org file or activating `org-mode', if orgstrap prop line local
variables are detect then use the installed orgstrap implementation to run the
orgstrap block. If orgstrap embedded local variables are present, they will not
be executed. `orgstrap-mode' is not a normal minor mode since it does not run
any hooks and when enabled only adds a function to `org-mode-hook'. ARG is the
universal prefix argument."
(interactive "P")
(ignore arg)
(let ((turn-on (not orgstrap-mode)))
(cond (turn-on
(add-hook 'org-mode-hook #'orgstrap--org-buffer)
(setq orgstrap-mode t)
(message "orgstrap-mode enabled"))
(arg) ; orgstrap-mode already enabled so don't disable it
(t
(remove-hook 'org-mode-hook #'orgstrap--org-buffer)
(setq orgstrap-mode nil)
(message "orgstrap-mode disabled")))))

;; orgstrap do not run aka `org-agenda' eval protection

(defun orgstrap--advise-no-eval-lv (command &rest args)
"Advise COMMAND to disable elvs for files loaded inside it.
ARGS vary by COMMAND.

If the elvs are disabled then `orgstrap-block-checksum' is added
to the `ignored-local-variables' list for files loaded inside
COMMAND. This makes it possible to open orgstrapped files where
the elvs will not run without having to accept the irrelevant
variable for `orgstrap-block-checksum'."
;; continually prompting users to accept a local variable when they
;; cannot inspect the file and when accidentally accepting could
;; allow unchecked execution at some point in the future is bad
;; better to simply pretend that the elvs and the block checksum
;; do not even exist unless the file is explicitly on a whitelist

;; orgstrapped files are just plain old org files in this context
;; since agenda doesn't use any babel functionality ... of course
;; I can totally imagine using orgstrap to automatically populate
;; an org file or update an org file using orgstrap to keep the
;; agenda in sync with some external source ... so need a variable
;; to control this
(if orgstrap-always-eval
(apply command args)
(let* ((enable-local-eval
(and args
orgstrap-always-eval-whitelist
(member (car args)
orgstrap-always-eval-whitelist)
enable-local-eval))
(ignored-local-variables
(if enable-local-eval ignored-local-variables
(cons 'orgstrap-block-checksum ignored-local-variables))))
(apply command args))))

(advice-add #'org-get-agenda-file-buffer :around #'orgstrap--advise-no-eval-lv)
#+end_src
*** Edit
#+caption: edit helpers
#+name: orgstrap-edit-helper-defuns
#+begin_src emacs-lisp :results none :lexical yes :noweb yes
;;; edit helpers
(defvar orgstrap--clone-stamp-source-buffer-block nil
"Source code buffer and block for `orgstrap-stamp'.")

(defcustom orgstrap-on-change-hook nil
"Hook run via `before-save-hook' when command `orgstrap-edit-mode' is enabled.
Only runs when the contents of the orgstrap block have changed."
:type 'hook
:group 'orgstrap)

(defcustom orgstrap-use-minimal-local-variables nil
"Set whether minimal, smaller but less portable variables are used.
If nil then backward compatible local variables are used instead.
If the value is customized to be non-nil then compact local variables
are used and `orgstrap-min-org-version' is set accordingly. If the
current version of org mode does not support the features required to
use the minimal variables then the portable variables are used instead."
:type 'boolean
:group 'orgstrap)

;; edit utility functions
(defun orgstrap--current-buffer-cypher ()
"Return the cypher used for the current buffer.
The value is `orgstrap-cypher' if it is bound otherwise
`orgstrap-default-cypher' is returned."
(if (boundp 'orgstrap-cypher) orgstrap-cypher orgstrap-default-cypher))

<>

<>

(defun orgstrap--goto-named-src-block (blockname)
"Goto org block named BLOCKNAME.
Like `org-babel-goto-named-src-block' but non-interactive, does
not use the mark ring, and errors if the block is not found."
(let ((obs (org-babel-find-named-block blockname)))
(if obs (goto-char obs)
(error "No block named %s" blockname))))

(defmacro orgstrap--with-block (blockname &rest macro-body)
"Go to the source block named BLOCKNAME and execute MACRO-BODY.
The macro provides local bindings for four names:
`info', `params', `body-unexpanded', and `body'."
(declare (indent defun))
`(save-excursion
(let* ((info
(org-save-outline-visibility 'use-markers
(orgstrap--goto-named-src-block ,blockname)
(org-babel-get-src-block-info)))
(params (nth 2 info))
(body-unexpanded (nth 1 info))
(body (orgstrap--expand-body info)))
,@macro-body)))

(defun orgstrap--update-on-change ()
"Run via the `before-save-hook' local variable.
Test if the checksum of the orgstrap block has changed,
if so update the `orgstrap-block-checksum' local variable
and then run `orgstrap-on-change-hook'."
(let* ((elv (orgstrap--read-current-local-variables))
(cpair (assoc 'orgstrap-block-checksum elv))
(checksum-existing (and cpair (cdr cpair)))
(checksum (orgstrap-get-block-checksum)))
(unless (eq checksum-existing (intern checksum))
(remove-hook 'before-save-hook #'orgstrap--update-on-change t)
;; for some reason tangling from a buffer counts as saving from that buffer
;; so have to remove the hook to avoid infinite loop
(unwind-protect
(save-excursion
(undo)
(undo-boundary) ; insert an undo boundary so that the
;; changes to the checksum are transparent to the user
(undo) ; undo the undo above
(orgstrap-add-block-checksum nil checksum)
(run-hooks 'orgstrap-on-change-hook))
(add-hook 'before-save-hook #'orgstrap--update-on-change nil t)))))

(defun orgstrap--get-actual-params (params)
"Filter defaults, nulls, and junk from src block PARAMS."
(let ((defaults (append org-babel-default-header-args
org-babel-default-header-args:emacs-lisp)))
(cl-remove-if (lambda (pair)
(or (member pair defaults)
(memq (car pair) '(:result-params :result-type))
(null (cdr pair))))
params)))

(defun orgstrap-header-source-element (header-name &optional block-name &rest more-names)
"Given HEADER-NAME find the element that provides its value.
If BLOCK-NAME is non-nil then search for headers for that block,
otherwise search for headers associated with the current block.
If MORE-NAMES are provided return the value for each (or nil)."
;; get the current headers, see if the value is set anywhere
;; or if it is default, search for default anyway just to be sure
;; return nil if not found
;; when searching for any header go to the end of the src line
;; `re-search-backward' from that point for :header-arg but not
;; going beyond the affiliated keywords for the current element
;; (if you can get affiliated keywords for the current element
;; that might simplify the search as well? check the impl for how
;; the actual values are obtained during execution etc)
;; when found use `org-element-at-point' to obtain the element

;; in another function the operates on the element
;; the element will give start, end, value, etc.
;; find bounds of value from element or sub element
;; delete the value, replace with new value
(ignore header-name block-name more-names)
(error "Not implemented TODO"))

(defun orgstrap-update-src-block-header (name new-params &optional update)
"Add header arguments to block NAME from NEW-PARAMS from some other block.
Existing header arguments will NOT be removed if they are not included in
NEW-PARAMS. If UPDATE is non-nil existing header arguments are updated."
(let ((new-act-params (orgstrap--get-actual-params new-params)))
(orgstrap--with-block name
(ignore body body-unexpanded)
(let ((existing-act-params (orgstrap--get-actual-params params)))
(dolist (pair new-act-params)
(cl-destructuring-bind (key . value)
pair
(let ((header-arg (substring (symbol-name key) 1)))
(if (assq key existing-act-params)
(if update
(unless (member pair existing-act-params)
;; TODO remove existing
;; `org-babel-insert-header-arg' does not remove
;; and it is not trivial to find the actual location
;; of an existing header argument there are 4 places
;; that we will have to look and then in some cases
;; we will have to append even if we do find them
(org-babel-insert-header-arg header-arg value)
;; This message works around the fact that we don't
;; have replace here, only append TODO consider
;; changing the way update works to be nil, replace,
;; or append once an in-place replace is implemented
(message "%s superseded for block %s." key name))
(warn "%s already defined for block %s!" key name))
(org-babel-insert-header-arg header-arg value)))))))))

(unless (fboundp #'flatten-tree)
;; backwards compatibility for Emacs < 27
(defun flatten-tree (tree)
(let (elems)
(while (consp tree)
(let ((elem (pop tree)))
(while (consp elem)
(push (cdr elem) tree)
(setq elem (car elem)))
(if elem (push elem elems))))
(if tree (push tree elems))
(nreverse elems))))

(defun orgstrap--check-portable-subset (body)
"Ensure that BODY uses only symbols that are portable for `prin1-to-string'."
;; XXX Note that [.] may diverge again because `.asdf' can be read without
;; escaping the leading . whereas `\?asdf' cannot. This is an artifact of
;; the c implementation of `prin1' being reused to handle both chars.
(let* ((l (flatten-tree (read (concat "(progn\n" body "\n)"))))
(symbols (cl-remove-duplicates (cl-remove-if-not #'symbolp l)))
(bads (cl-remove-if-not
(lambda (s) (string-match "[.?]" (cl-subseq (symbol-name s) 1)))
symbols)))
(when bads
(error "checksum failed: non-portable symbols detected: %S" bads))))

;; edit user facing functions
(defun orgstrap-get-block-checksum (&optional cypher)
"Calculate the `orgstrap-block-checksum' for the current buffer using CYPHER."
(interactive)
(orgstrap--with-block orgstrap-orgstrap-block-name
(ignore params body-unexpanded)
(orgstrap--check-portable-subset body)
(let ((cypher (or cypher (orgstrap--current-buffer-cypher)))
(body-normalized (orgstrap-norm body)))
(secure-hash cypher body-normalized))))

(defun orgstrap-add-block-checksum (&optional cypher checksum)
"Add `orgstrap-block-checksum' to file local variables of `current-buffer'.

The optional CYPHER argument should almost never be used,
instead change the value of `orgstrap-default-cypher' or manually
change the file property line variable. CHECKSUM can be passed
directly if it has been calculated before and only needs to be set.

If `orgstrap-save-developer-checksums' is non-nil then add the checksum to
`orsgrap-developer-checksums'."
(interactive)
(let* ((cypher (or cypher (orgstrap--current-buffer-cypher)))
(orgstrap-block-checksum (or checksum (orgstrap-get-block-checksum cypher))))
(when orgstrap-block-checksum
(save-excursion
(add-file-local-variable-prop-line 'orgstrap-cypher cypher)
(add-file-local-variable-prop-line 'orgstrap-norm-func-name orgstrap-norm-func)
(add-file-local-variable-prop-line 'orgstrap-block-checksum (intern orgstrap-block-checksum)))
(when orgstrap-save-developer-checksums
(add-to-list 'orgstrap-developer-checksums (intern orgstrap-block-checksum))))
orgstrap-block-checksum))

(defun orgstrap-run-block ()
"Evaluate the orgstrap block for the current buffer."
;; bind to :orb or something like that
(interactive)
(save-excursion
(orgstrap--goto-named-src-block orgstrap-orgstrap-block-name)
(org-babel-execute-src-block)))

(defun orgstrap-clone (&optional universal-argument)
"Set current block or orgstrap block as the source for `orgstrap-stamp'.
If a UNIVERSAL-ARGUMENT is supplied then the orgstrap block is always used."
;; TODO consider whether to avoid the inversion of behavior around C-u
;; namely that nil -> always from orgstrap block, C-u -> current block
;; this would avoid confusion where unprefixed could produce both
;; behaviors and only switch when already on a src block
(interactive "P")
(let ((current-element (org-element-at-point))
(current-buffer (current-buffer)))
(if (and (eq (org-element-type current-element) 'src-block)
(not universal-argument))
(let ((block-name (org-element-property :name current-element)))
(if block-name
(setq orgstrap--clone-stamp-source-buffer-block
(cons current-buffer block-name))
(warn "The current block has no name, it cannot be a clone source!")))
(if (orgstrap--used-in-current-buffer-p)
(setq orgstrap--clone-stamp-source-buffer-block
(cons current-buffer orgstrap-orgstrap-block-name))
(warn "orgstrap is not used in the current buffer!")))))

(defun orgstrap-stamp (&optional universal-argument overwrite)
"Stamp orgstrap block via `orgstrap-clone' to current buffer.
If UNIVERSAL-ARGUMENT is \\='(16) aka (C-u C-u) this will OVERWRITE any existing
block. If you are not calling this interactively all as (orgstrap-stamp nil t)
for calirty. You cannot stamp an orgstrap block into its own buffer."
(interactive "P")
(unless (eq major-mode 'org-mode)
(user-error "`orgstrap-stamp' only works in org-mode buffers"))
(unless orgstrap--clone-stamp-source-buffer-block
(user-error "No value to clone! Use `orgstrap-clone' first"))
(let ((overwrite (or overwrite (equal universal-argument '(16))))
(source-buffer (car orgstrap--clone-stamp-source-buffer-block))
(source-block-name (cdr orgstrap--clone-stamp-source-buffer-block))
(target-buffer (current-buffer)))
(when (eq source-buffer target-buffer)
(error "Source and target are the same buffer. Not stamping!"))
(cl-destructuring-bind (source-body
source-params
org-adapt-indentation
org-edit-src-content-indentation)
(save-window-excursion
(with-current-buffer source-buffer
(orgstrap--with-block source-block-name
(ignore body-unexpanded)
(list body
params
org-adapt-indentation
org-edit-src-content-indentation))))
(if (and (not overwrite)
(member orgstrap-orgstrap-block-name
(org-babel-src-block-names)))
(warn "orgstrap block already exists not stamping!")
(orgstrap--add-orgstrap-block source-body) ; FIXME somehow the hash is different !?!??!
(orgstrap-update-src-block-header orgstrap-orgstrap-block-name source-params t)
(orgstrap-add-block-checksum) ; I think it is correct to add the checksum here
(message "Stamped orgsrap block from %s" (buffer-file-name source-buffer))))))

;;;###autoload
(define-minor-mode orgstrap-edit-mode
"Minor mode for editing with orgstrapped files."
:init-value nil :lighter "" :keymap nil
(unless (eq major-mode 'org-mode)
(setq orgstrap-edit-mode 0)
(user-error "`orgstrap-edit-mode' only works with org-mode buffers"))

(cond (orgstrap-edit-mode
(add-hook 'before-save-hook #'orgstrap--update-on-change nil t))
(t
(remove-hook 'before-save-hook #'orgstrap--update-on-change t))))
#+end_src
# orgstrap-embed-normalization-code
# is a potential future variable but for sanity
# I am leaving it out for now because it is easier
# to have a rule that says "always use orgstrap-embedded-norm-func"
# and then we don't have to wonder about it, the size tradeoff can
# be made by the user based on their use case
*** Dev
#+caption: dev helpers
#+name: orgstrap-dev-helper-defuns
#+begin_src elisp
;;; dev helpers

(defcustom orgstrap-developer-checksums-file (concat user-emacs-directory "orgstrap-developer-checksums.el")
"Path to developer checksums file."
:type 'path
:group 'orgstrap)

(defcustom orgstrap-save-developer-checksums nil ; FIXME naming
"Whether or not to save checksums of orgstrap blocks under development."
:type 'boolean
:group 'orgstrap
:set (lambda (variable value)
(set-default variable value)
(if value
(add-hook 'orgstrap-on-change-hook #'orgstrap-save-developer-checksums)
(remove-hook 'orgstrap-on-change-hook #'orgstrap-save-developer-checksums))))

(defvar orgstrap-developer-checksums nil ; not custom because it is saved elsewhere
"List of checksums for orgstrap blocks created or modified by the user.")

(defun orgstrap--pp-to-string (value)
"Ensure that we actually print the whole VALUE not just the summarized subset."
(let (print-level print-length)
(pp-to-string value)))

(defun orgstrap-revoke-developer-checksums (&optional universal-argument)
"Remove all saved developer checksums. UNIVERSAL-ARGUMENT is a placeholder."
(interactive "P") (ignore universal-argument)
(setq orgstrap-developer-checksums nil)
(orgstrap-save-developer-checksums t))

(defun orgstrap-save-developer-checksums (&optional overwrite)
"Function to update `orgstrap-developer-checksums-file'.
If OVERWRITE is non-nil then overwrite the existing checksums."
(interactive "P")
(if orgstrap-save-developer-checksums
(let* ((checksums orgstrap-developer-checksums)
(buffer (find-file-noselect orgstrap-developer-checksums-file)))
(with-current-buffer buffer
(unwind-protect
(progn
(lock-buffer)
(let* ((saved (and (not (= (buffer-size) 0)) (cadr (nth 2 (read (buffer-string))))))
;; XXX NOTE saved is not used to updated `orgstrap-developer-checksums' here
;; FIXME massively inefficient
(combined (or (and (not overwrite)
(cl-remove-duplicates (append checksums saved)))
checksums)))
;; TODO do we need to check whether combined and saved are different?
;; (message "checksums: %s\nsaved: %s\ncombined: %s" checksums saved combined)
(erase-buffer)
(insert ";;; -*- mode: emacs-lisp; lexical-binding: t -*-\n")
(insert ";;; DO NOT EDIT THIS FILE IT IS AUTOGENERATED AND WILL BE OVERWRITTEN!\n\n")
(insert (string-replace
" " "\n"
(orgstrap--pp-to-string `(setq orgstrap-developer-checksums ',combined))))
(insert "\n;;; set developer checksums as safe local variables\n\n")
(insert
(orgstrap--pp-to-string
'(mapcar (lambda (checksum-value)
(add-to-list 'safe-local-variable-values
(cons 'orgstrap-block-checksum checksum-value)))
orgstrap-developer-checksums)))
(pp-buffer)
(indent-region (point-min) (point-max))
(save-buffer)))
(unlock-buffer)
(kill-buffer))))
(warn "No checksums were saved because `orgstrap-save-developer-checksums' is not set.")))

#+end_src
*** Init
A note on filter aka =cl-remove-if-not= in =orgstrap--add-file-local-variables= at [[(clrin)]].
| emacs version | require |
|---------------+---------|
| < 24 | 'cl |
| < 25 | 'cl-lib |
| < 27 | 'seq |
The most portable thing to do for now is =(require 'cl-lib)= since we
don't currently support anything below 23. Then use =cl-remove-if-not=.

There is a similar issue with =pcase=, which is that in =emacs-24= the
syntax was closer to =cl-case= when dealing with symbols. Since =cl-lib=
is already in use, =cl-case= is the logical solution for portability.

Not all functionality works in older versions of Org. For example see
[[(obubb-issue)][update block issue]] which is caused by the fact that
~org-babel-update-block-body~ is broken prior to revision
orgit-rev:~/git/NOFORK/org-mode::7d6b8f51ec1993a66a385b98b2df42d0853fe289
which is not present in the versions of Org released with Emacs < 26.

# We have to cache this result to avoid ocbe issues when tangling
# XXX this also has to be manually converted to the : style because
# old versions of org don't support :cache yes and we wind up with
# circular dependencies
#+name: orgstrap-shebang-body-command
#+begin_src elisp :results code :exports none :cache yes
(orgstrap--with-block "orgstrap-shebang" body)
#+end_src

# XXX must keep this in sync manually to support old versions of org
#+name: orgstrap-shebang-body
: "set -e \"-C\" \"-e\" \"-e\"\n{ null=/dev/null;} > \"${null:=/dev/null}\"\n{ args=;file=;MyInvocation=;__p=$(mktemp -d);touch ${__p}/=;chmod +x ${__p}/=;__op=$PATH;PATH=${__p}:$PATH;} > \"${null}\"\n$file = $MyInvocation.MyCommand.Source\n{ file=$0;PATH=$__op;rm ${__p}/=;rmdir ${__p};} > \"${null}\"\nemacs -batch -no-site-file -eval \"(let (vc-follow-symlinks) (defun orgstrap--confirm-eval (l _) (not (memq (intern l) '(elisp emacs-lisp)))) (let ((file (pop argv)) enable-local-variables) (find-file-literally file) (end-of-line) (when (eq (char-before) ?\\^m) (let ((coding-system-for-read 'utf-8)) (revert-buffer nil t t)))) (let ((enable-local-eval t) (enable-local-variables :all) (major-mode 'org-mode) find-file-literally) (require 'org) (org-set-regexps-and-options) (hack-local-variables)))\" \"${file}\" -- ${args} \"${@}\"\nexit\n<# powershell open"

#+caption: init helpers
#+name: orgstrap-init-helper-defuns
#+begin_src emacs-lisp :results none :lexical yes :noweb yes
;;; init helpers
(defvar orgstrap-link-message "jump to the orgstrap block for this file"
"Default message for file internal links.")

(defvar-local orgstrap--local-variables nil
"Variable to capture local variables from `hack-local-variables'.")

;; local variable generation functions

(defun orgstrap--get-min-org-version (info minimal)
"Get minimum org mode version needed by the orgstrap block for this file.
INFO is the source block info. MINIMAL sets whether to use minimal local vars."
(if minimal
(let ((coderef (or (nth 6 info) org-coderef-label-format))
(noweb (org-babel-noweb-p (nth 2 info) :eval)))
(if noweb
"9.3.8"
(let* ((body (or (nth 1 info) ""))
(crrx (org-src-coderef-regexp coderef))
(pos (string-match crrx body))
(commented
(and pos (string-match
(concat (rx ";" (zero-or-more whitespace)) crrx) body))))
;; FIXME the right way to do this is similar to what is done in
;; `org-export-resolve-coderef' but for now we know we are in elisp
(if (or (not pos) commented)
"8.2.10"
"9.3.8"))))
"8.2.10"))

(defun orgstrap--have-min-org-version (info minimal)
"See if current version of org meets minimum requirements for orgstrap block.
INFO is the source block info.
MINIMAL is passed to `orgstrap--get-min-org-version'."
(let ((actual (org-version))
(need (orgstrap--get-min-org-version info minimal)))
(or (not need)
(string< need actual)
(string= need actual))))

(defun orgstrap--dedoc (sexp)
"Remove docstrings from SEXP. WARNING mutates sexp!"
(let ((m '(defun defun-local defmacro defvar defvar-local defconst defcustom)))
(cl-loop
for e in sexp when (listp e) do ; for expression in sexp when the expression is a list
(or
(and
(memq (car e) m) ; is a form with docstrings
(let ((n (nthcdr 4 e))) ; body after docstring
(and
(stringp (nth 3 e)) ; has a docstring
(or (cl-subseq m 3) n) ; var or doc not last
(orgstrap--dedoc n) ; recurse for nested
;; splice out the docstring and return t to avoid the other branch
(or (setcdr (cddr e) n) t))))
;; recurse e.g. for (when x (defvar y t))
(orgstrap--dedoc e))))
sexp)

(defun orgstrap--local-variables--check-version (info &optional minimal)
"Return the version check local variables given INFO and MINIMAL."
`(
(setq-local orgstrap-min-org-version ,(orgstrap--get-min-org-version info minimal))
<>))

(defun orgstrap--local-variables--norm (&optional norm-func-name)
"Return the normalization function for local variables given NORM-FUNC-NAME."
(let ((norm-func-name (or norm-func-name (default-value 'orgstrap-norm-func))))
(cl-case norm-func-name
(orgstrap-norm-func--dprp-1-0
'(
<>))
(orgstrap-norm-func--prp-1-1
'(
<>))
(orgstrap-norm-func--prp-1-0
(error "`orgstrap-norm-func--prp-1-0' is deprecated.
Please update `orgstrap-norm-func-name' to `orgstrap-norm-func--prp-1-1'"))
(otherwise (error "Don't know that normalization function %s" norm-func-name)))))

(defun orgstrap--local-variables--norm-common ()
"Return the common normalization functions for local variables."
'(
<>))

(defun orgstrap--local-variables--eval (info &optional minimal)
"Return the portable or MINIMAL elvs given INFO."
(let* ((minimal (or minimal orgstrap-use-minimal-local-variables))
(minimal (and minimal (orgstrap--have-min-org-version info minimal))))
(if minimal
'(
<>)
'( ;(ref:elv-noweb-issue)
;; if you automatically reindent it will break these two
<>

<>))))

(defun orgstrap--local-variables--eval-common ()
"Return the common eval check functions for local variables."
`( ; quasiquote to fill in `orgstrap-orgstrap-block-name'
<>))

;; init utility functions

(defun orgstrap--new-heading-elisp-block (heading block-name &optional header-args noexport)
"Create a new elisp block named BLOCK-NAME in a new heading titled HEADING.
The heading is inserted at the top of the current file.
HEADER-ARGS is an alist of symbols that are converted to strings.
If NOEXPORT is non-nil then the :noexport: tag is added to the heading."
(declare (indent 1))
(save-excursion
(goto-char (point-min))
(outline-next-heading) ;; alternately outline-next-heading
(org-meta-return)
(insert (format "%s%s\n" heading (if noexport " :noexport:" "")))
;;(org-edit-headline heading)
;;(when noexport (org-set-tags "noexport"))
(move-end-of-line 1)
(insert "\n#+name: " block-name "\n")
(insert "#+begin_src elisp")
(mapc (lambda (header-arg-value)
(insert " :" (symbol-name (car header-arg-value))
" " (symbol-name (cdr header-arg-value))))
header-args)
(insert "\n#+end_src\n")))

(defun orgstrap--trap-hack-locals (command &rest args)
"Advice for `hack-local-variables-filter' to do nothing except the following.
Set `orgstrap--local-variables' to the reversed list of read variables which
are the first argument in the lambda list ARGS.
COMMAND is unused since we don't actually want to hack the local variables,
just get their current values."
(ignore command)
(setq-local orgstrap--local-variables (reverse (car args)))
nil)

(defun orgstrap--read-current-local-variables ()
"Return the local variables for the current file without applying them."
(interactive)
;; orgstrap--local-variables is a temporary local variable that is used to
;; capture the input to `hack-local-variables-filter' it is unset at the end
;; of this function so that it cannot accidentally be used when it might be stale
(setq-local orgstrap--local-variables nil)
(let ((enable-local-variables t))
(advice-add #'hack-local-variables-filter :around #'orgstrap--trap-hack-locals)
(unwind-protect
(hack-local-variables nil)
(advice-remove #'hack-local-variables-filter #'orgstrap--trap-hack-locals))
(let ((local-variables orgstrap--local-variables))
(makunbound 'orgstrap--local-variables)
local-variables)))

(defun orgstrap--add-link-to-orgstrap-block (&optional link-message)
"Add an `org-mode' link pointing to the orgstrap block for the current file.
The link is placed in comment on the second line of the file. LINK-MESSAGE
can be used to override the default value set via `orgstrap-link-message'"
(interactive) ; TODO prompt for message with C-u ?
(goto-char (point-min))
(next-logical-line) ; required to get correct behavior?
(let ((link-message (or link-message orgstrap-link-message)))
(unless (save-excursion
(re-search-forward
(format "^# \\[\\[%s\\]\\[.+\\]\\]$"
orgstrap-orgstrap-block-name)
nil t)) ; XXX for some reason save-excursion fails so we have to reset
(goto-char (point-min))
(next-logical-line) ; use logical-line to avoid issues with visual line mode
(insert (format "# [[%s][%s]]\n"
orgstrap-orgstrap-block-name
(or link-message orgstrap-link-message))))))

(defun orgstrap--add-orgstrap-block (&optional block-contents)
"Add a new elisp source block with #+name: orgstrap to the current buffer.
If a block with that name already exists raise an error.
Insert BLOCK-CONTENTS if they are supplied."
(interactive)
(let ((all-block-names (org-babel-src-block-names)))
(if (member orgstrap-orgstrap-block-name all-block-names)
(warn "orgstrap block already exists not adding!")
(goto-char (point-max))
(insert "\n")
(orgstrap--new-heading-elisp-block "Bootstrap"
orgstrap-orgstrap-block-name
'((results . none)
(exports . none)
(lexical . yes))
'noexport)
(goto-char (point-max))
(insert "\n** Local Variables :ARCHIVE:\n")
(orgstrap--with-block orgstrap-orgstrap-block-name
(ignore params body-unexpanded body)
(when block-contents
;; FIXME `org-babel-update-block-body' is broken in < 26 (ref:obubb-issue)
;; for now warn and fail if the version is known bad NOTE trying to backport
;; is not simple because there are changes to the function signatures
(if (string< org-version "8.3.4")
(warn "Your version of Org is too old to use this feature! %s < 8.3.4"
org-version)
(org-babel-update-block-body block-contents)))
nil))))

(defun orgstrap--lv-command (info &optional minimal norm-func-name)
"Create the elvs for an orgstrapped file.
INFO is the output of `org-babel-get-src-block-info' for the orgstrap block.
MINIMAL determines whether a non-portable block has been requested.
NORM-FUNC-NAME names the function used to normalize orgstrap blocks."
(let ((lv-cver (orgstrap--local-variables--check-version
info
minimal))
(lv-norm (orgstrap--local-variables--norm
norm-func-name))
(lv-ncom (orgstrap--local-variables--norm-common))
(lv-eval (orgstrap--local-variables--eval
info
minimal))
(lv-ecom (orgstrap--local-variables--eval-common)))
(cons 'progn (orgstrap--dedoc (append lv-cver lv-norm lv-ncom lv-eval lv-ecom)))))

(defun orgstrap--add-file-local-variables (&optional minimal norm-func-name)
"Add the file local variables needed to make orgstrap work.
MINIMAL is used to control whether the portable or minimal block is used.
If MINIMAL is set but the orgstrap block uses features like noweb and
uncommented coderefs and function `org-version' is too old, then the portable
block will be used. NORM-FUNC-NAME is an optional argument that can be provided
to determine which normalization function is used independent of the current
buffer or global setting for `orgstrap-norm-func'.

When run, this function replaces any existing orgstrap elv with the latest
implementation available according to the preferences for the current buffer
and configuration. Other elvs are retained if they are present, and the
orgstrap elv is always added first."
;; switching comments probably wont work ? we can try
;; Use a prefix argument (i.e. C-u) to add file local variables comments instead of in a :noexport:
(interactive)
(let ((info (save-excursion
(orgstrap--goto-named-src-block orgstrap-orgstrap-block-name)
(org-babel-get-src-block-info)))
(elv (orgstrap--read-current-local-variables)))
(let ((lv-command (orgstrap--lv-command info minimal norm-func-name))
(commands-existing (mapcar #'cdr (cl-remove-if-not (lambda (l) (eq (car l) 'eval)) elv)))) ;(ref:clrin)
(let* ((stripped
(cl-remove-if
(lambda (cmd) (orgstrap--match-elvs (cons 'eval cmd)))
commands-existing))
(eval-commands (cons lv-command stripped)))
(when commands-existing
(delete-file-local-variable 'eval))
(let ((print-escape-newlines t) ; needed to preserve the escaped newlines
;; if `print-length' or `print-level' is accidentally set
;; `add-file-local-variable' will truncate the sexp with and elispsis
;; this is clearly a bug in `add-file-local-variable' and possibly in
;; something deeper, `print-length' is the only one that has actually
;; caused issues, but better safe than sorry
print-length print-level)
(mapcar (lambda (sexp) (add-file-local-variable 'eval sexp)) eval-commands))))))

(defun orgstrap--before-first-dull ()
"Goto the first non-empty line not starting with a sharp sign."
(goto-char (point-min))
(re-search-forward "\n[^#\n \t]")
(beginning-of-line))

(defun orgstrap--goto-elvs ()
"Goto the start of the elvs for the current buffer.
If no elvs are found goto `point-max' instead."
(widen)
(goto-char (point-max))
(search-backward "\n\^L" (max (- (point-max) 3000) (point-min)) 'move)
(when (let ((case-fold-search t))
(search-forward "Local Variables:" nil t))
(beginning-of-line)))

(defconst orgstrap--shebang-body
<>
"Shebang block body content.")

(defun orgstrap--add-shebang-block (&optional update)
"Add a shebang block to the current buffer."
;; goto correct location
;; create empty bash block
;; fill block
;; go to start of elvs
;; add powershell closing line
(let ((block-name "orgstrap-shebang")
(header-args '((eval . never) (results . none) (exports . none))))
(if (org-babel-find-named-block block-name)
(if update
(orgstrap-update-src-block "orgstrap-shebang" orgstrap--shebang-body)
(warn "A shebang block already exists. Not adding."))
(if update
(warn "A shebang block does not exist. Not updating.")
(save-excursion
(orgstrap--before-first-dull)
(insert "\n#+name: " block-name "\n")
(insert "#+begin_src bash")
(mapc (lambda (header-arg-value)
(insert " :" (symbol-name (car header-arg-value))
" " (symbol-name (cdr header-arg-value))))
header-args)
(insert "\n#+end_src\n")
(orgstrap-update-src-block "orgstrap-shebang" orgstrap--shebang-body)

(orgstrap--goto-elvs)
(insert (format "# close powershell comment %s>\n" "#")))))))

(defun orgstrap-update-shebang-block (&optional universal-argument)
"Update an existing shebang block. UNIVERSAL-ARGUMENT is ignored."
(interactive "P")
(ignore universal-argument)
(orgstrap--add-shebang-block 'update))

;; init user facing functions

;;;###autoload
(defun orgstrap-init (&optional prefix-argument shebang)
"Initialize orgstrap in a buffer and enable command `orgstrap-edit-mode'.
If PREFIX-ARGUMENT is non-nil and has a value of 4 or 64 init will attempt
to use the minimal local variables if possible.

If SHEBANG is non-nil or PREFIX-ARGUMENT is greater than or equal to 16
then a shebang block will also be added to the file.

Example usage.
M-x orgstrap-init -> portable elvs
C-u M-x orgstrap-init -> minimal elvs
C-u C-u M-x orgstrap-init -> portable elvs + shebang
C-u C-u C-u M-x orgstrap-init -> minimal elvs + shebang"
(interactive "P")
(unless (eq major-mode 'org-mode)
(error "Cannot orgstrap, buffer not in `org-mode' it is in %s!" major-mode))
;; TODO option for no link?
;; TODO option for local variables in comments vs noexport
(let (onf)
(let ((shebang (or shebang (and prefix-argument (>= (car prefix-argument) 16))))
(orgstrap-norm-func
(or (cdr (assoc 'orgstrap-norm-func-name (orgstrap--read-current-local-variables)))
(default-value 'orgstrap-norm-func))))
(save-excursion
(orgstrap--add-orgstrap-block)
(orgstrap-add-block-checksum)
(orgstrap--add-link-to-orgstrap-block)
;; FIXME sometimes local variables don't populate due to an out of range error
(orgstrap--add-file-local-variables
(or (and prefix-argument (memq (car prefix-argument) '(4 64))) orgstrap-use-minimal-local-variables))
(when shebang (orgstrap--add-shebang-block))
(orgstrap-edit-mode 1)
(setq onf orgstrap-norm-func)))
;; reset to ensure that a stale value is not inserted on next save
(setq-local orgstrap-norm-func onf)))
#+end_src

# Note that multi-line strings cause issues with indentation if they are
# nowebbed with leading whitespace. We avoid this by left aligning the
# [[(elv-noweb-issue)][noweb references]] so that no leading whitespace
# is inserted. This is something to watch out for in general when trying
# to ensure consistent hashing.

# I suspect that the underlying issue may be an org-mode bug related
# to incorrect handling of leading whitespace when inserting contents
# into a new block

# dedoc testing
# (orgstrap--dedoc '(defvar lol 'hello))
# (orgstrap--dedoc '(defvar lol 'hello "there"))
# (orgstrap--dedoc '(defun lol () "there" 1))
# (orgstrap--dedoc '(defun lol (arg) "there" arg))
# (orgstrap--dedoc '(defun lol (arg) "there"))
# (orgstrap--dedoc '(defmacro lol (arg) "there"))
# (orgstrap--dedoc '(defmacro lol (arg) "there" arg))
# (orgstrap--dedoc '((defmacro lol (arg) "there" arg) (defvar lol 'hello "there")))
*** Extras
#+caption: extra helpers
#+name: orgstrap-extra-helper-defuns
#+begin_src elisp :noweb yes
;;; extra helpers

(defun orgstrap-update-src-block (name content)
"Set the content of source block named NAME to string CONTENT.
XXX NOTE THAT THIS CANNOT BE USED WITH #+BEGIN_EXAMPLE BLOCKS."
;; FIXME this seems to fail if the existing block is empty?
;; or at least adding file local variables fails?
(let ((block (org-babel-find-named-block name)))
(if block
(save-excursion
(orgstrap--goto-named-src-block name)
(org-babel-update-block-body content))
(error "No block with name %s" name))))

(defun orgstrap-get-src-block-checksum (&optional cypher)
"Calculate of the checksum of the current source block using CYPHER."
(interactive)
(let* ((info (org-babel-get-src-block-info))
(params (nth 2 info))
(body-unexpanded (nth 1 info))
(body (orgstrap--expand-body info))
(body-normalized
(orgstrap-norm body))
(cypher (or cypher (orgstrap--current-buffer-cypher))))
(ignore params body-unexpanded)
(secure-hash cypher body-normalized)))

(defun orgstrap-get-named-src-block-checksum (name &optional cypher)
"Calculate the checksum of the first sourc block named NAME using CYPHER."
(interactive)
(orgstrap--with-block name
(ignore params body-unexpanded)
(let ((cypher (or cypher (orgstrap--current-buffer-cypher)))
(body-normalized
(orgstrap-norm body)))
(secure-hash cypher body-normalized))))

(defun orgstrap-run-additional-blocks (&rest name-checksum) ;(ref:oab)
"Securely run additional blocks in languages other than elisp.
Do this by providing the name of the block and the checksum to be embedded
in the orgstrap block as NAME-CHECKSUM pairs."
(ignore name-checksum)
(error "TODO"))

(defun orgstrap--get-elvs (&optional from-flv-alist)
"Return the elvs as they are written in the current buffer.
If FROM-FLV-ALIST is not null display the elvs that are in
`file-local-variables-alist'."
(cl-loop
for var in
(if from-flv-alist
file-local-variables-alist
(orgstrap--read-current-local-variables))
when (orgstrap--match-elvs var)
return (cdr var)))

(defun orgstrap-inspect-elvs (&optional from-flv-alist)
"Display the elvs for the current buffer.
If FROM-FLV-ALIST is not null display the elvs that are in
`file-local-variables-alist'."
(interactive "P")
(let ((buffer (get-buffer-create (format "%s orgstrap elvs" (buffer-file-name))))
(elvs (orgstrap--get-elvs from-flv-alist))
(cypher orgstrap-cypher)
print-length print-level)
(with-current-buffer buffer
(emacs-lisp-mode)
(read-only-mode)
(let ((inhibit-read-only t))
(erase-buffer)
;; TODO insert checksum in comment
(insert ";; -*- elvs-checksum: "
(secure-hash cypher (orgstrap-norm (pp-to-string elvs)))
"; -*-\n")
(insert (pp-to-string elvs))
(goto-char (point-min))
(while (re-search-forward "(\\(let\\|defun\\|when\\|unless\\|if\\|read\\)" nil t)
(join-line 1))
(indent-region (point-min) (point-max)))
(local-set-key (kbd "q") #'quit-window)
(goto-char (point-min)))
(display-buffer buffer)))

(defun orgstrap--whitelist-current-buffer ()
"Mark local variable values in the current buffer as safe."
(let ((lvs (orgstrap--read-current-local-variables)))
(customize-push-and-save 'safe-local-variable-values lvs)))

;; extra user facing functions

;;;###autoload
(defun orgstrap-whitelist-file (path)
"Add local variables in PATH as safe custom variable values.
This is useful when distributing orgstrapped files.

Use with a command similar to the following.
Since -batch implies -q, `user-init-file' must be passed explicitly.

emacs -batch -eval \\
\"(let ((user-init-file (pop argv)) (file (pop argv))) (package-initialize) (orgstrap-whitelist-file file))\" \\
~/.emacs.d/init.el /path/to/whitelist.org"
(let (enable-local-variables) ; < 28 don't run orgstrap block
;; `find-file-literally' is broken on 27 so regularize behavior
(with-current-buffer (find-file-literally path)
(orgstrap--whitelist-current-buffer)
(kill-buffer))))
#+end_src

Ideally we want to call [[(oab)][orgstrap-run-additional-blocks]] as
=(orgstrap-run-additional-blocks "additional-block-name" "checksum-value-hash-thing" "ab2" "cs2")=
It probably makes sense to house this in its own orgstrap-aux block or something.
I want to keep the file local variables as minimal as possible, so having another
aux block that could be automatically updated with the names and hashes of additional
blocks would be nice ... probably via something like =orgstrap-add-additional-block=
but it will not go in the local variables because we want there to be some hope of
orgstrap being portable to other platforms outside of Emacs at some point in the
very distant future, so keeping the machinery outside of the org file itself as
minimal as possible is critical.
** orgstrap.el :noexport:
# XXX TODO it would be a super cool feature if xref could resolve to elisp source
# blocks in org-mode files, because then half the need for the .el file would go away
#+caption: Retangle this if something changes.
#+name: orgstrap.el
#+header: :exports none
#+begin_src elisp -r -l "\([[:space:]]\|;\)*(ref:%s)$" :noweb yes :eval never :tangle ./orgstrap.el
;;; orgstrap.el --- Bootstrap an Org file using file local variables -*- lexical-binding: t -*-

;; Author: Tom Gillespie
;; URL: https://github.com/tgbugs/orgstrap
;; Keywords: lisp org org-mode bootstrap
;; Version: 1.5.5 (ref:orgstrap.el-version)
;; Package-Requires: ((emacs "24.4"))

;;;; License and Commentary

;; License:
;; SPDX-License-Identifier: GPL-3.0-or-later

;;; Commentary:

;; orgstrap is a specification and tooling for bootstrapping Org files.

;; It allows Org files to describe their own requirements, and
;; define their own functionality, making them self-contained,
;; standalone computational artifacts, dependent only on Emacs,
;; or other implementations of the Org-babel protocol in the future.

;; orgstrap.el is an elisp implementation of the orgstrap conventions.
;; It defines a regional minor mode for `org-mode' that runs orgstrap
;; blocks. It also provides `orgstrap-init' and `orgstrap-edit-mode'
;; to simplify authoring of orgstrapped files. For more details see
;; README.org which is also the literate source for this orgstrap.el
;; file in the git repo at
;; https://github.com/tgbugs/orgstrap/blob/master/README.org
;; or wherever you can find git:c1b28526ef9931654b72dff559da2205feb87f75

;; Code in an orgstrap block is usually meant to be executed directly by its
;; containing Org file. However, if the code is something that will be reused
;; over time outside the defining Org file, then it may be better to tangle and
;; load the file so that it is easier to debug/xref functions. The code in
;; this orgstrap.el file in particular is tangled for inclusion in one of the
;; *elpas so as to protect the orgstrap namespace and to make it eaiser to
;; use orgstrap in Emacs.

;; The license for the orgstrap.el code reflects the fact that the
;; code for expanding and hashing blocks reuses code from ob-core.el,
;; which at the time of writing is licensed as part of Emacs.

;;; Code:

(require 'org)

(require 'org-element)

<>

<>

<>

<>

<>

(provide 'orgstrap)

;;; orgstrap.el ends here

#+end_src
# have to have an empty line at the end so that a newline shows up
# when tangled ... surely this is a bug?
** Testing :noexport:
*** Simple
#+name: test-portable
#+begin_src bash :var THIS_FILE=(buffer-file-name) :results none
emacs-24 -Q $THIS_FILE
emacs-25 -Q $THIS_FILE
emacs-26 -Q $THIS_FILE
emacs-27 -Q $THIS_FILE
emacs-28 -Q $THIS_FILE
emacs-29-vcs -Q $THIS_FILE
#+end_src

#+name: test-minimal
#+begin_src bash :var THIS_FILE=(buffer-file-name) :results none
emacs-24 -Q orgstrap-minimal.org
emacs-25 -Q orgstrap-minimal.org
emacs-26 -Q orgstrap-minimal.org
emacs-27 -Q orgstrap-minimal.org
emacs-28 -Q orgstrap-minimal.org
emacs-29-vcs -Q orgstrap-minimal.org
#+end_src
*** Matrix
Before running the tests below you need to generate [[file:./orgstrap-autoloads.el]].
Newer version of =autoload-generate-file-autoloads= add functions that may not be
supported by older versions of Emacs. Thus you should run this on the oldest version
of Emacs you will be testing against.

# XXX This must be run with emacs-26 to avoid bytecode compatibility issues
#+name: generate-autoloads-for-test
#+begin_src elisp :results none
(require 'autoload)
(with-current-buffer (find-file-noselect "orgstrap-autoloads.el")
(erase-buffer)
(let* ((cb (current-buffer))
(fn (buffer-file-name cb))
(generated-autoload-file fn))
(autoload-generate-file-autoloads "orgstrap.el" cb fn))
(save-buffer)
(kill-buffer))
#+end_src

# XXX note that you cannot use append t with add-to-list for local variables
# it puts any additional values in the wrong place, need to check other cases
# reminder that you can't use bare bangs ! anywhere in bash scripts :/
# XXX also reminder that unbound variables will break at export time
# XXX remove elc files before running this due to bytecode mismatches
# XXX regenerate autoloads, it seems that the version in 26 works for everyone
#+name: test-matrix-run
#+begin_src bash :results drawer output
versions=( 24 25 26 27 28 29-vcs )
test_files=( test-no-lv-list.org test-lv-list-portable test-lv-list-minimal )
for v in ${versions[@]}; do
[ -d test/emacs-$v ] || mkdir -p test/emacs-$v
for f in ${test_files[@]};do
# uncomment and reorder to debug tests
#f=test-lv-list-minimal
#emacs -Q \
emacs-$v -Q -batch \
-eval "(setq user-init-file (concat default-directory \"test/emacs-${v}/init.el\"))" \
-l orgstrap-autoloads.el \
-eval "(message \"\n%s\"(emacs-version))" \
-f toggle-debug-on-error \
-eval "(add-to-list 'load-path \"$(pwd)/\")" \
-eval "(orgstrap-mode)" \
-eval "(defun orgstrap-test () (error \"test failed.\"))" \
-eval "(orgstrap-whitelist-file \"${f}\")" \
-visit $f \
-eval "(orgstrap-test)" 2>&1
done
done
#+end_src
**** TODO Full matrix
#+begin_src elisp
;; create buffer
;; fill buffer
;; set code
;; hash
;; save buffer
;; open in all the other impls having set the checksum as accepted
;; with orgstrap-mode enabled
;; without orgstrap-mode enabled
;; with minimal
;; with portable
;; with noweb
;; without noweb
;; iterate over norm funcs
;; orgstrap-norm-func-name mismatch
;; orgstrap-norm-func-name not in internal list
;; .org extension and mode: org local variable
(defconst orgstrap--test-matrix
`(((orgstrap-mode (nil t))
(lv-type (nil minimal portable))
(noweb (nil t))
;; (comments (nil link noweb)) ; comments aren't actually relevant here I think?
(norm-func ,orgstrap--internal-norm-funcs)
(path-suffix-lv ((".org" . (mode . org))
(".org" nil)
("" . (mode . org))
("" . (mode . nil))))))
"The dimensions of the test files that need to be generated."
)
#+end_src
**** TODO One time tests
1. Blacklist and open, then unblacklist and open.
This requires an actual file since we need buffer file name.
2. Need a way to test revoke as well.
**** Test files
# TODO automatically generate all of these test files
# maybe even generate them as buffers that can be run
# by loading this files with orgstrap--test t?

# #+header: :comments link # broken atm
#+name: test-no-lv-list
#+begin_src org :tangle ./test-no-lv-list.org
# -*- orgstrap-cypher: sha256; orgstrap-norm-func-name: orgstrap-norm-func--dprp-1-0; orgstrap-block-checksum: 8d941e14e89664b834f5b28c070f9f7b0ec55b092b55cc23dd903c010fdaeda5; -*-
# [[orgstrap][jump to the orgstrap block for this file]]

,* Bootstrap :noexport:

,#+name: orgstrap
,#+begin_src elisp :results none :lexical yes
(defun orgstrap-test ()
(if (cl-remove-if-not #'orgstrap--match-elvs
file-local-variables-alist)
(error "elv is still present!")
(message "No local variables here!")))
(message "orgstrap successful")
,#+end_src
#+end_src

#+name: test-lv-list-portable
#+begin_src org :tangle ./test-lv-list-portable :noweb yes
# -*- mode: org; orgstrap-cypher: sha256; orgstrap-norm-func-name: orgstrap-norm-func--dprp-1-0; orgstrap-block-checksum: 14e85d1213ef7a6739ca6ca7361a227b0a55346d4c7c6457bdd5f7ba91ff5dff; -*-
# [[orgstrap][jump to the orgstrap block for this file]]

,* Bootstrap :noexport:

,#+name: orgstrap
,#+begin_src elisp :results none :lexical yes
(defun orgstrap-test ()
(if (cl-remove-if-not #'orgstrap--match-elvs
file-local-variables-alist)
(error "elv is still present!")
(message "Portable local variables here!")))
(message "orgstrap successful")
,#+end_src

,** Local Variables :ARCHIVE:

<>
#+end_src

#+name: test-lv-list-minimal
#+begin_src org :tangle ./test-lv-list-minimal :noweb yes
# -*- mode: org; orgstrap-cypher: sha256; orgstrap-norm-func-name: orgstrap-norm-func--dprp-1-0; orgstrap-block-checksum: 3008580fd616cdfca904c7508ae023f782585229a87adab33fb8c2d391f89561; -*-
# [[orgstrap][jump to the orgstrap block for this file]]

,* Bootstrap :noexport:

,#+name: orgstrap
,#+begin_src elisp :results none :lexical yes
(defun orgstrap-test ()
(if (cl-remove-if-not #'orgstrap--match-elvs
file-local-variables-alist)
(error "elv is still present!")
(message "Minimal local variables here!")))
(message "orgstrap successful")
,#+end_src

,** Local Variables :ARCHIVE:

<>
#+end_src
** Release :noexport:
*** Flycheck
Use ~flycheck-mode~ on [[file:./orgstrap.el]] to checkdoc for melpa.
Don't forget to run ~flycheck-package-setup~ to get better reports.
*** Byte compile
Before a release run the following block and fix any byte compile
errors and warnings. Using Emacs 26 ensures that bytecode is forward
and backward compatible.
#+begin_src elisp :noweb yes :results drawer
(ow-run-command "emacs" "-batch" "-f" "batch-byte-compile" "orgstrap.el")
(ow-run-command "emacs-26" "-batch" "-f" "batch-byte-compile" "orgstrap.el")
(delete-file "orgstrap.elc")
#+end_src
*** Run test matrix
Run ref:test-matrix-run.
#+call: test-matrix-run()
*** Run init tests
These are not automated at the moment. Run ref:test-portable and
do the following for each version of Emacs.

1. Accept lvs.
2. For Emacs >= 26 ~orgstrap-clone~.
3. switch to ~*scratch*~
4. enable ~org-mode~
5. ~orgstrap-init~
6. optional edit block
7. optional save to file and test reload the saved file
8. For Emacs >= 26 in ~*scratch*~ buffer undo
9. For Emacs >= 26 ~orgstrap-stamp~
10. For Emacs >= 26 check that the checksum matches the checksum for this file
11. quit

#+call: test-portable()
*** Final steps
Things that need to be done for a release.
- Bump the version number in the [[(orgstrap.el-version)][orgstrap.el header comment]].
You will need to manually retangle after this step.
- Update the [[#changelog][changelog]].
- Convert the changelog entry to markdown for the GitHub release.
=C-c C-e C-s m M=.
* Changelog
:PROPERTIES:
:CUSTOM_ID: changelog
:END:
** 1.5.5
- Fix missing paren.

Missed due to a duplicate orgstrap block for debug in the same file.

** 1.5.4
- Update ~orgstrap--shebang-body~ to set ~find-file-literally~ to nil.

If you use shebang blocks you should update them so that a call to
~find-file~ on ~buffer-file-name~ of the orgstrapped file will
continue without prompting the user.

** 1.5.3
- Ignore errors from ~org-set-visibility-according-to-property~.

Recent changes to ~org-set-visibility-according-to-property~ result
in errors if it is called when a file is not in ~org-mode~ and/or
when a buffer is in ~org-mode~ but Emacs is ~noninteractive~.

This requires and update to the elvs which wraps the call in
~ignore-errors~ which is more space efficient than e.g. testing
whether we are non-interactive and also mostly +fool+ future proof.

If you use orgstrap shebang blocks you should updated your elvs.

** 1.5.2
- Update ~orgstrap--shebang-body~ to simplify and improve safety.

If you use shebang blocks you should update them so that shebang
blocks are not affected by leaking environment variables. See
[[./shebang.org]] for a full explication of the operation of each
line of shell/powershell code.

- Add ability to update shebang blocks to latest version.

New interactive function ~orgstrap-update-shebang-block~ updates an
existing shebang block to the latest ~orgstrap--shebang-body~.
Internally ~orgstrap--add-shebang-block~ was updated to accept an
optional ~update~ argument which is used to control whether to add a
new or update an existing block.

** 1.5.1
- Update ~orgstrap--shebang-body~ so that shebang blocks pass args correctly.

If you use shebang blocks you should update them so that they pass
the correct args to emacs. See [[./shebang.org]] for more details on
the changes.

** 1.5
- Regularize behavior of ~orgstrap-whitelist-file~.

An internal call to ~find-file-literally~ has different behavior in
27 vs 28 (see notes about that in [[./shebang.org]]).

- Rename all normalization functions to remove use of =.=.

See https://debbugs.gnu.org/cgi/bugreport.cgi?bug=55645 for details.

*You will need to update any orgstrapped files to use the new names.*
The simplest way to do this is to update to to 1.5 and then delete
the prop line local variables and then run ~orgstrap-init~.

- Add a check to ensure that only portable symbol names are used.

Symbols with =[.?]= appearing anywhere other than at the start of
the symbol are banned since their prin1 representation diverges
across the 28/29 boundary.

If you have orgstrap blocks that contain such symbols you will need
to change the symbol names. An error will be raised when calling
~orgstrap-add-block-checksum~ with a list of symbols that need to be
changed.
** 1.4
- Update ~orgstrap-init~ so that it can insert a shebang block.

Use 2 or more prefix arguments to add a shebang block when using
~orgsgrap-init~. For example =C-u C-u C-u M-x orgstrap-init=.
** 1.3
- Remove all ~orgstrap-do~ variables.

The core of orgstrap is not the right place to maintain these. They
will reappear under ~ow-do~ in the future.

- Add ~orgstrap-norm-func--dprp-1.0~ and make it the default norm func.

The default normalization function for orgstrap is now invariant to
changes in the docstring for ~defun~, ~defun-local~, ~defmacro~,
~defvar~, ~defvar-local~, ~defconst~, and ~defcustom~. This allows
improved documentation without requiring the user to re-audit.

Note that ~orgstrap-norm-func--prp-1.1~ has NOT been deprecated, but
is no longer the default. It is still useful if for whatever reason
you want to minimize the elvs.

- Add support for batch execution.

The preferred method is to use an org shebang block (see
[[./shebang.org]]) It is also possible to maintain an automatically
updating list of developer checksums. This approach was deemed to be
silly given shebang blocks, however the functionality is retained.

- Add ~orgstrap-whitelist-file~ to make it easier to mark known safe
files in batch.

See the docstring for example usage.

- Add ~orgstrap-inspect-elvs~ to inspect the elvs for the current buffer.

The command also calculates the elvs checksum for comparison.

Known elv checksums for this release are below. The order is
minimal, minimal-noweb, and portable (aka minimal-noweb-eval).

For prp-1.1 \\
=446d0c80d72bb89dd149181e6a24eafa011d12d6dc99fad958a03ddebd9a95ad= \\
=b294539a74f2a1932d39790d6377a4229bd3d5e84df64d968baa8ff3f85349cc= \\
=543e3400c80e2cc7b9bf94b1799d1460b240776c2c7415a2f6c0c7a9507978ef= \\

For dprp-1.0 \\
=aa080a6469c22dfe960c43fa3bff3b92a6bc3da9383ec8fcd7d0a019192e7aa0= \\
=72c52a3483905aff6b83c6cd2c36899a2a8d1cbc603b6e5ce8c9e98f0dd7b099= \\
=9e33bc67b8850147962edcece4ea1193bb6cf3711264a26bde91b1f838912ffc= \\

- Fix ~org-edit-mode~ so that it now activates correctly.

- Fix ~orgstrap-init~ so that it no longer misplaces the link to the
orgstrap block if there is already content in the buffer.

- Fix ~orgstrap-init~ so that invocation in files with existing elvs
updates only the existing orgstrap elv and preserves other elvs.

- Fix ~orgstrap-init~ to read and pass the current value of
~orgstrap-norm-func-name~ when creating local variables.

If ~orgstrap-norm-func-name~ is missing, the default value of
~orgstrap-norm-func~ is used.

This prevents klobbering while also providing an easy way to update
the normalization function --- just update the variable value and
run ~orgstrap-init~.

- Fix ~orgstrap-norm-func~ by always declaring it with ~defvar-local~.

- Update the elvs to handle issues with symlinks and vc mode. The
core functionality remains compatible.

- Update the elvs so that ~org-confirm-babel-evaluate~ can be set by
an orgstrap block without having to modify the elvs.

- Update the elvs so that they only restore visibility set via
property drawers.

This makes it possible to use the orgstrap block to control initial
visibility and narrowing to simplify the presentation of orgstrapped
files (and avoid distracting users with the orgstrap machinery).

- Update ~orgstrap-init~ to put the Bootstrap section at the end of
the file and to put the elvs in an archived heading inside that.

This pattern has been found to be quite effective for a number of
different use cases.

** 1.2.7
- Fix bad defaults on ~orgstrap-do-*~ custom variables.

If these are not set to t by default then it is impossible
individual blocks to know whether a nil value was intentional on the
part of the user or not. Users _must_ set values to nil in their
config if they do not want certain sections to run.
** 1.2.6
- Ignore ~orgstrap-block-checksum~ when loading ~org-agenda~.

If ~enable-local-eval~ is t (following the behavior described in the
[[#122][1.2.2]] changelog), then ~orgstrap-block-checksum~ is not
ignored and if the local variable value has not been added to the
safe list then the user will be prompted.

- Add ~orgstrap-do-*~ variables.

Boolean control variables that can be used enable/disable standard
functionality/steps needed by org files. See [[file:./do.org]] for
more details.
** 1.2.5
- Improve behavior of ~orgstrap-blacklist-current-file~.

Now revokes the current buffer checksum by default, this can be
overridden by providing a universal argument. The blacklist is
immediately saved via ~customize-save-variable~.

- Fix ~orgstrap-mode~ to use the universal argument.

Behavior is now correct when ~(orgstrap-mode t)~ is called.
** 1.2.4
- Add ~orgstrap-clone~ and ~orgstrap-stamp~ commands.

~orgstrap-clone~ stores the current buffer and current or orgstrap
block ~orgstrap-stamp~ copies the expanded contents and headers of
that block to a new orgstrap block in a new file. Useful in cases
where users want to duplicate functionality in a new file.

NOTE ~orgstrap-stamp~ only works for org version >= 8.3.4 which
means that it does not work for versions of Emacs < 26.

- ~orgstrap--add-orgstrap-block~ add ~block-contents~ argument.

This simplifies the implementation of stamp, and makes it possible
to set the initial contents of the orgstrap block programmatically.
There are some lingering issues with indentation that may need to
be resolved for this to work seamlessly.

- Fix byte compile bug from destructuring-bind not being aliased.

- Fix for issues with noweb blocks containing multi-line docstrings.

We need this to test ~orgstrap-clone~ with this readme file. Without it
the checksum will not match in the stamped file due to differences in
the leading whitespace in docstrings.

- Make ~orgstrap-revoke-eval-local-variables~ obsolete.

Replaced by the more compact ~orgstrap-revoke-elvs~.
** 1.2.3
- Add ~orgstrap-file-blacklist~ to block eval of specific files.

The functionality only works if ~orgstrap-mode~ is enabled.
** 1.2.2
:PROPERTIES:
:CUSTOM_ID: 122
:END:
- Do not run eval local variables when loading org-agenda.

~orgstrap-always-eval~ can be set to ~t~ by users who want to
evaluate orgstrap blocks in all situations. More granular control is
provided by adding the full path of files that should always try to
run their orgstrap block to ~orgstrap-always-eval-whitelist~.

In all cases, if the global setting for ~enable-local-eval~ is more
restrictive then it is honored (i.e., nil will block any execution
and the default ~'maybe~ will continue to prompt).

- Add ability to revoke previously approved orgstrap-block-checksums.

Rapid revocation of permissions is an important part of any security
system. Therefore we now provide a way to revoke all previously
approved values for ~orgstrap-block-checksum~ in a single command
~orgstrap-revoke-checksums~. This command can also be provided
with a specific list of checksums to revoke. Another convenience function
~orgstrap-revoke-current-buffer~ is provided that revokes the checksum
of the orgstrap block for the current buffer.

- Add ability to revoke previously approved eval local variables.

As with revocations for ~orgstrap-block-checksum~ values, we also
need a way to revoke eval local variables. There is no granular
control. Use ~orgstrap-revoke-eval-local-variables~ to nuke all
orgstrap eval local variables from orbit.
** 1.2.1
- Fix bad startup visibility when using orgstrap.

*You should run =M-:= =(orgstrap--add-file-local-variables)= to*
*update embedded eval local variables.*

Running =org-babel-execute-src-block= changes the visibility of the
tree holding the orgstrap block. As a result the startup visibility
of any org file using orgstrap was incorrect. Adding a call to
=org-set-startup-visibility= in the unwind forms ensures that
startup visibility is correct.
** 1.2
- Add =orgstrap-norm-func--prp-1.1= and make it the default norm func.

This change does not effect the default behavior of =orgstrap=. The
reason for the change is to defensively shadow =print-length= and
=print-level= to =nil= so that if they are somehow non-nil, Emacs
will not truncate the contents of the src block prior to hashing.

- Mark =orgstrap-norm-func--prp-1.0= as obsolete.

*You should update any files using prp-1.0 to use prp-1.1.*

Whenever there is a case where a change in the environment can cause
a change in the output of a normalization function there is a risk
that it could be exploited.
** 1.1.1
- Fix =orgstrap--hack-lv= to remove itself from the local
=hack-local-variables-hook=.
** 1.1
:PROPERTIES:
:CUSTOM_ID: 11
:END:
- Renamed existing =orgstrap-mode= to =orgstrap-edit-mode=.

This is a *BREAKING CHANGE*. Please update your workflows.

- Added the new =orgstrap-mode= implementation.

This is a regional minor mode for =org-mode= which makes it possible
to use orgstrap without the embedded local variables. This allows
for greater security at the expense of portability, depending on the
exact use case. By a stroke of good fortune it is possible to use
the =hack-local-variables= hooks to trap and remove the embedded
local variables if they are present so that the orgstrap block is
not evaluated twice.

- Added =orgstrap-always-edit= as a custom variable.

If non-nil then =orgstrap-edit-mode= will be automatically activated
by =orgstrap-mode=.

- =orgstrap--add-file-local-variables= update existing =eval:= vars.

This change makes it vastly easier to switch between portable and
minimal implementations, and should make it easier to switch the
normalization function once we get that implemented.

If an existing orgstrap eval file local variable is detected it is
removed and the latest version is added. Other =eval:= variables are
not modified. Note however that the orgstrap eval variable will
always be placed first.
* Contributing
:PROPERTIES:
:CUSTOM_ID: contributing
:END:
The primary =orgstrap= repository lives at https://github.com/tgbugs/orgstrap.

There are a number of ways to contribute to =orgstrap=.

1. Have an Org file that use =orgstrap=? Create an issue or a pull
request to add it to the list of [[#examples-from-around-the-web][examples from around the web]].

2. Encounter a bug? Please [[https://github.com/tgbugs/orgstrap/issues/new/choose][submit an issue]]!

3. Feel like writing some elisp? Check out [[#future-work][future work]]
for a list of potential projects (TODO status not visible on GitHub).
* Guides
** User guide
This guide is for users of Org files that have =orgstrap= blocks.

This includes Emacs users who have their own configs as well as users
who might be encountering Emacs for the first time.

** Developer guide
This guide is for developers who want to use =orgstrap= in their own
org files.

It covers workflows for development, distribution, and maintenance of
orgstrap files.

It also covers best practices and effective strategies for making Org
files accessible to users.
*** There can be only one. Dealing with elisp's absent namespaces.
A key issue that orgstrap must contend with is the fact that there is
only one global namespace for all elisp functions. Variables are not
an issue for orgstrap because buffer local variables provide
sufficient separation.

To this end there are two conventions that orgstrap and orgware follow.
The ersatz namespaces ~orgstrap---~ and ~ow---~ may be used in any
orgstrap block. Developers may assume that any such definitions will
remain unchanged at least until another orgstrap block runs. Clearly it
is impossible to know for sure that no one else will use a function
with the same name ~ow---you-re-standing-on-my-toe-!~ that has different
behavior. We can make an attempt to keep a record of all known ~ow---~
function names, but ultimately it is up to the user to run a check.
# TODO need to implement a local ow--- collision checker that searchers
# all orgs files for ow--- usage and reports any collisions
# TODO implement a way to report a name collision to upstream
** Contributor guide
This guide is for developers who want to contribute to the core
=orgstrap= implementation or documentation.
*** Setup
*** Testing
*** Making changes
*** Submitting a pull request
* Best practices
:PROPERTIES:
:CUSTOM_ID: best-practices
:END:
** Accepting local variables
In short. If you use orgstrap.el don't accept the elvs.

Suggestions for users. If you run an orgstrapped file via -Q or
similar, then you have to accept the file. The elvs probably aren't
going to include package initialize and require orgstrap, but maybe
they could. Essentially, in -Q mode the user should know that they
have to initialize it all themselves (orgware could do it for them).

When you aren't running in -Q mode and you DO have orgstrap.el
installed on your system then I strongly suggest that you should NOT
accept, or even actively remove, any and all approved elvs and use
orgstrap-mode since it has a number of features that can enhance the
security of execution such as blacklists etc.

** Use the system package manager.
There is a big difference between using a script to install a program directly
from the internet and using a script to ask the host system to install a program.

Even if you audit a random script from the internet it is unlikely that you will
be able to do due diligence. On the other hand, if you ask your system package
manager to install something for you, there is a much better chance that it has
at least been somewhat audited, and there is usually an existing process for
getting a package into the system which helps to mitigate certain types of attacks.

To give a military example it is the difference between inspecting and accepting a
package from a random person because they say you asked for it yesterday (maybe you
did!) versus only every allowing packages to come through procurement. You are much
less likely to get a bomb or a packaged rigged to exfil data if you go through
procurement because there is an established process for how to do things and that
process enshrines generations experience about how to not get blown up by the pizza guy.

So, if you are writing instructions that require a certain tool, it is better to tell
whoever is following them to ask procurement to get the tool for them than to tell them
to going out to the hardware store and get it themselves, or worse, give them the address
of a random tool delivery man who happens to be a good buddy of yours. Even if everyone
involved is trustworthy those kinds of relationships are much easier for some third party
to compromise and use for their own purposes.

The obvious corollary when you are the user rather than the author, is that if you
encounter instructions that ask you to directly install software from a random place
you should be suspicious, even, perhaps especially, if that random place is housed
within a larger reputable site. If you're not in a hurry, ask for the software to be
packaged, or package it yourself so that it can go through the process.
** Opening orgstrapped files as an Emacs user
One problem that orgstrap has is that an orgstrap block can modify the
Emacs configuration. Modifying the config is often critical to get the
desired behavior for the particular target user population. This is
not an issue if the users do not use Emacs for anything else. However,
if the user opening the file is an Emacs user then blocks that modify
the configuration are a serious problem.

There are three ways around this issue: one a standard convention for
naming a variable to control evaluation of config related code or two
always use ~emacs -q~ and of course three a combination of both.

In the first case a set of standard conventions for variable names can
be used to control whether some, or all configuration variables are
set. However, this can only attain the status of a best practice
because orgstrap blocks run arbitrary code and there is no way to
enforce the convention (thus why it is in this section).

One convention that I have been testing is to ...
# TODO enable-*
# orgstrap-enable-config ? not a good name ...

In the second case we suggest that users always open orgstrapped files
with ~emacs -q~. This is as close as we can get to having a sandboxed
execution environment. If the user also wants to load their config
then they would have to use ~-l~ as well. This is a pain, but without
converting all of the variables into buffer local variables this is
unlikely to work.

Another major drawback of using ~emacs -q~ is that there are many
more advanced features enabled by orgstrap.el that cannot be used
without running ~emacs -q -l orgstrap.el~ or some equivalent. This
adds significant complexity to the command line invocation. There
is no easy way to work around this since the features of interest
are ones that must be available before the orgstrap block is run.
Another approach is to use ~emacs -q -f package-initialize~.

There is also the issue of how to handle potential name collisions.
# TODO orgstrap--- prefix is too long, but is one solution

In summary, Emacs and orgstrap are _very_ sharp tools. I have tried to
provide a bit of protection via the checksum mechanism, however if you
are an Emacs user, you should probably always check the orgstrap block
to make sure that it won't completely klobber your config.

If you are authoring a file that uses orgstrap, it is friendly to put
any config related code in its own block so that it can be controlled
globally via ~the-orgstrap-variable-name-to-be-determined~.
* Bootstrapping to Emacs, bootstrapping to Org
:PROPERTIES:
:CUSTOM_ID: bootstrapping-to-emacs-bootstrapping-to-org
:END:
See [[file:./get-emacs.org]].
* Examples
:PROPERTIES:
:CUSTOM_ID: examples
:END:
** Examples from around the web
:PROPERTIES:
:CUSTOM_ID: examples-from-around-the-web
:END:
- [[https://raw.githubusercontent.com/SciCrunch/sparc-curation/master/docs/apinatomy.org][apinatomy.org]] \\
An executable Org file for running pipelines that build computational models of anatomy.
- [[https://raw.githubusercontent.com/SciCrunch/sparc-curation/master/docs/queries.org][queries.org]] \\
An org file set up as an interface to query knowledge bases that is
configured to provide a familiar (cua+) interface for users who may
be unfamiliar with Emacs. Has examples for loading packages, and of
one way to hide configuration to avoid information overload. A
simpler version of the file (which leverages the same orgstrap block
because the files are distributed together) is at [[https://raw.githubusercontent.com/SciCrunch/sparc-curation/master/docs/sckan/scratch.org][scratch.org]].
- [[https://raw.githubusercontent.com/SciCrunch/sparc-curation/master/docs/sckan/welcome.org][welcome.org]] \\
An org file that acts as a static welcome page for a docker image,
with links to other interactive org files. Has an example of how to
use narrowing to hide the orgstrap machinery from the user.
# TODO release.org
# TODO git-share/README.org
** Tooling for orgstrap
- [[./reval.org][reval]]
- [[./orgware.org][orgware]]
- [[./shebang.org][shebang]]
*** Services
Configurations for services that can be reused across orgstrap files.
- [[./services/blazegraph.org][Blazegraph]]
** Useful orgstrap blocks
Include these in part or whole to simplify common orgstrap workflows.
- [[file:reval.org::#minimal][reval-minimal]]
# - [[file:orgware.org::#run-command][run-process-as-command]]
# - [[file:orgware.org::#securl][securl]]
* Background, file local variables, and checksums
:PROPERTIES:
:CUSTOM_ID: background-file-local-variables-and-checksums
:END:
As mentioned above, the primary use case for =orgstrap= was that I was sick of having
to work around the limitation that I had to do one of four things. I either one, had
to remember to eval the source block containing defuns used later before I could
eval other source blocks that used those functions in headers, or two, had to put those
functions in =init.el=, destroying the ability to use org files as standalone self describing
portable and reusable computational artifacts, three, had to copy and paste verbose
elisp bits around to achieve what I wanted, or four, had to double tangle a file so that
the results of the first tangle could be loaded before calling the second tangle so that
the functionality would be available (this also produces the situation described in three).
Furthermore, it is hard for humans to follow all the steps needed to get everything
working -- even when 'everything' is just invoking =C-c C-c= on a single source block
I still forget. This can lead to _bad things_ if some of those source blocks were
interdependent, or proceeded with a nil, etc.

File local variables to the rescue!
I'm slightly embarrassed to say how long it took me to arrive at the current solution.
I had known for quite a while that file local variables are a pathway to +abilities that+
the evils of arbitrary code execution, but it didn't click that all I was looking for was
the ability to just run some arbitrary elisp code every time a particular file was loaded,
which of course is exactly what file local variables are for.

The only question then was how to avoid the very real dangers of enabling arbitrary code
execution of plain text. Actually it was more along the lines of "How can I keep org-babel
happy without also pwning myself?" Fortunately =org-confirm-babel-evaluate= can be customized
to be a function that accepts the body of the code to be evaluated. Therefore we can do the
following.

When creating a file.
1. *Hash the block to be run before distributing the file.*
Make sure to test if there are any changes to the header.
For example I have a bad habit of accidentally setting
=:noweb no-export= incorrectly without the dash and that will
prevent the checksum from updating if a nowebbed block changes.
2. *Embed the checksum in the file local variable property line.*
The property line is highly visible as the first line of the
file. This makes it easy for users to verify that the embedded
checksum matches a known independent checksum (running step 2).
Thus if the embedded checksum does not match a known checksum
the user will notice, and if the code to be executed does not
match the embedded checksum then the user will at least be
prompted by org-mode to run the block even in the case where
they accepted the file local variables. Emacs also prompts for
verification of the property line value which is another
opportunity for the user to check.
3. *Publish the checksum independent of the file itself.*
It is trivial for someone to change the contents of the orgstrap block
and rerun =M-x= =orgstrap-add-block-checksum=. Therefore known checksums
need to be published independent of the files themselves.

When running a file.
1. *Audit, accept, and store permanently the eval file local variables.*
Storing audited variables permanently is critical for improving signal to noise
so that unexpected mismatches retain their salience and can elicit the correct
response (i.e., suspicion).
# XXX there may be an issue here if the property line tags along with the rest
# because we want to be able to mark the exact variables used in this file
# as safe and if they are couple to a random hash that is bad
2. *Audit the orgstrap block*
I assume most people are not going to do this. However, one of the advantages
of the current approach is that the same orgstrap blocks can be reused across
multiple files which reduces the audit load such that one only needs to review
unique orgstrap blocks, not all files. [fn::NOTE there are certain patterns inside
blocks that are NOT safe to accept because they introduce a level of indirection
that orgstrap cannot verify. Examples of these kinds of dangerous blocks are ones
that make any reference to other blocks in the file via some means other than noweb.
This isn't really surprising, and for use cases where =org-babel-execute-src-block=
is called multiple times on different blocks, the default execution protection will
work. In addition, any blocks which want to run automatically without prompting should
use the =orgstrap--confirm-eval= function (see [[file:::#future-work][Future work]]).]
3. *Verify that the embedded checksum matches the independent checksum.*
A known embedded checksum matching the content checksum only means that the content
matches the content observed by the provider of the independent checksum
(assuming no hash collisions).
4. *Observe whether org-mode complains that the orgstrap block has changed.*
* Experience reports
:PROPERTIES:
:CUSTOM_ID: experience-reports
:END:
** First trial
The first use case for ~orgstrap~ beyond personal use was to create a
stand alone application that would allow a user to run, modify, and
create SPARQL queries running from a local server[fn::The file itself
is in a private git repo at the moment, but the functionality will be
extracted and made public, and the repo itself will be as well.]

The primary users had no prior Emacs experience. Under supervision via
a video call they were able to follow the instructions to download
Emacs, download a zip of the file plus data and additional software,
unzip, and click on the file (which opened in Emacs by default), and
accept the local variables.

There were a couple of hiccups. In one case Java for macos was
missing. In the other case the built-in version of Org mode was not
correctly replaced so Emacs had to be restarted. In the second case
there was also an issue with multiple windows being opened and the
confirmation window for the local variables thus being hard to find.

Even with the slowdown they were able to get up and running within
about 20 minutes. This is an enormous improvement over previous
attempts which involved many hard to follow instructions that could
take nearly 3 hours to complete and debug.
* Future work
:PROPERTIES:
:CUSTOM_ID: future-work
:visibility: children
:END:
** TODO separate user-emacs-directory
:PROPERTIES:
:CREATED: [2023-02-01 Wed 21:22]
:END:
Emacs 29 introduces the long desired command line option
~--init-directory=DIR~. This solves a number of issues related to
sandboxing the impact on existing Emacs configurations of using
orgstrap for various things. That is somewhat secondary however.

More importantly, after a significant amount of experience working
with this setup, it seems fairly clear to me that the default behavior
when using orgstrap shebang blocks is that orgstrap configuration
files and more importantly packages should be kept separate from the
default ~user-emacs-directory~ to avoid a wide variety of issues.

Probably add this to ~ow-enable-use-packages~ with options to use
something like ~~/.config/orgstrap/~ or ~~/.orgstrap.d/~ and
additionally to sandbox packages for the whole file. I'm thinking that
probably won't be necessary for most use cases.
** TODO detect whether dprp-1.0 is needed, otherwise use prp-1.1
:PROPERTIES:
:CREATED: [2021-09-26 Sun 14:26]
:END:
The hashes that these generate are the same so long as there aren't
any docstrings. For orgstrap blocks that don't use comments, we can
save quite a bit of space in this way.
** DONE bug secondary runs of the orgstrap block klobber modified obce
:PROPERTIES:
:CREATED: [2021-08-19 Thu 21:47]
:END:
[[file:~/git/sparc-curation/docs/queries.org::orgstrap][orgstrap]]
the quick fix is just to manually remove the step where we restore ocbe
** DONE somehow a stale orgstrap norm func managed to sneak in I have no idea how
:PROPERTIES:
:CREATED: [2021-08-11 Wed 12:32]
:END:
I'm 99% sure that this was coming from release.org and
orgstrap-norm-func was not being reset and sticking around and messing stuff up.

This was part of the issue, though it wasn't that it was not being
reset, it was that ~orgstrap-init~ did not source the default value
because ~orgstrap-norm-func~ was incorrectly marked as a global
dynamic variable instead of as ~defvar-local~.

The other part of the issue was the we were not using the current
~orgstrap-norm-func-name~ for the buffer during ~orgstrap-init~.

Even all that wasn't quite right. ~orgstrap-norm-func~ has to be
overwritten before the checksum is added for existing files, otherwise
the stale value will persist for files where local variables were
actually accepted instead of ignored.
** TODO data section
base64 (or whatever) encode a compressed data blob, stick it in an
archived heading and then add =--pack= and =--unpack= and =--repack=
or something equivalent. This is probably the most reasonable way
to manage distributing org files that have dynamic data associated
with them, such as an sqlite database or something.

# TODO consider adding --check sha256 or one of the others
# TODO -7 -8 and -9 when the input file is > 8 16 and 32 mb
#+begin_src bash :eval never
xz -zk file
base64 -w 127 file.xz > file.xz.base64 # 127 is a nicely sized prime
xz -d file.xz
xz -dc file.lz > /dev/null
#+end_src

#+begin_src elisp
(math-prime-test 109 9999)
(math-prime-test 113 9999)
(math-prime-test 127 9999) ; this one
#+end_src

These are likely to be of interest since they can also be used to tar a whole
directory, at which point it is possible to deal with the dissociation issue.
=dired-compress-files-alist=
=dired-do-compress=
=dired-compress-file=

=base64-encode-region=
=base64-decode-region=

Well, that was productive for figuring out what was going wrong.
Turns out that =:ARCHIVE:= sections are still seen by flyspell and by
auto-complete. =narrow-to-region= seems to have the behavior we want
but it doesn't do multiple. For some reason archived text is still
being searched by =auto-complete-mode= which causes massive slowdowns.

I have looked into modifying =org-hide-archived-subtrees= so that
isearch does not open and search inside, however it does not seem to
make any difference.

apparently flyspell doesn't honor read-only so we have to use something else
and it also seems to ignore the first regexp I list here, so no good solutions
thus far, I still think that narrowing to before and after are the best solution ...
#+begin_src elisp
(add-to-list 'ispell-skip-region-alist '("^\\* data :ARCHIVE:$" . "=$"))
(add-to-list 'ispell-skip-region-alist '("^#+begin_data$" . "^#+end_data$"))
(auto-complete-mode 0)
(rainbow-delimiters-org-mode 0)
(flyspell-mode 0)
#+end_src

#+begin_src elisp
(use-package zones) ; not part of the core so hard to make use of
#+end_src

#+begin_src org
,#+startup: showall
,* data :ARCHIVE:
:PROPERTIES:
:visibility: folded
:END:
put the big stuff here
,* Bootstrap :noexport:

,#+begin_src elisp
(let ((inhibit-read-only t))
(add-text-properties 21 44543976
'(read-only t)))
,#+end_src
#+end_src
** DONE orgstrap-inspect-elvs
** DONE symlinks and vc
:PROPERTIES:
:CREATED: [2021-08-02 Mon 01:23]
:END:
vc is called via find-file-hook which explicitly runs after
hack-local-variables which explains how we are getting in so early
with the orgstrap blocks, a solution has been found
** Safe local variable values need backlinks.
~orgstrap-block-checksum-sources~ alist as a custom variable so that it is
easier for people to know what came from where in a summary even though
we have the revoke functionality.
** DONE orgstrap-edit-mode fails to active when orgstrap-mode is enabled
:PROPERTIES:
:CREATED: [2020-11-29 Sun 00:10]
:END:
annoying
this is because orgstrap-mode is idiotically broken and can't be activated
globally despite being a global minor mode >_<
** Tutorial videos for various workflows
Record a series of short screen casts to illustrate common orgstrap
authoring and consumption workflows.
** TODO Display contents of orgstrap block in other window during confirm
:PROPERTIES:
:CREATED: [2020-10-08 Thu 23:27]
:END:
One major usability feature would be to figure out how to display the
full body of the orgstrap block in the other window when the confirm
local variables dialogue was presented. It seems easy enough when
orgstrap.el is installed, however it seems like it might be hard to
implement a minimal version, but maybe not, it is basically just save
excursion and rearrange so that the local variables confirm buffer and
the orgstrap block are the only two windows visible.

Another issue is whether it is possible to do this in Emacs < 27,
since it is not possible to switch out of the confirm dialogue.

Probably use =org-babel-expand-src-block= via =call-interactively=.
This will eat into the elvs budget. This also addresses some of the
security concerns.
** EXPIRED Async blocks
:PROPERTIES:
:CREATED: [2020-10-02 Fri 17:03]
:END:
One issue that needs to be resolved is how to ensure that slow running blocks
don't freeze Emacs. Ideally this could be done via ob-async, however using
ob-async essentially means that we have to either tangle the block or we have
to sneakily noweb it in if it is an elisp block, or something, to ensure that
the inferior Emacs process has the definitions. Tangle and inject a call to
load the file in the prologue or something?

The answer is that orgstrap isn't the place to handle these issues beyond
providing documentation on best practices.

The best practice for this is to use the orgstrap block in a sane manner.
For interactive use orgstrap blocks should include variable settings and
defuns at most. Little to no actual computation should be done at that stage.

Longer running processes, such as tangling or building etc, should be masked
in =(when noninteractive body ...)=. That allows orgstrap blocks to make
the functionality defined in the file available via command line arguments
(including editing via ./orgstrapped.org --edit).

In this context the evolution of orgstrap do will be to provide a library to
make command line interaction with orgstrapped files discoverable. We are most
of the way there because there is already an implementation of docopt for elisp.
** Spec extension to support arbitrary orgstrap block names.
Since all the conventions for how this is done are defined locally by each file, you could
in principle rename the special block as you see fit, perhaps from =orgstrap= to =main= if
you need to pretend that the file is actually c source code with some special syntax.
However, this is not advisable if you care about portability since it depends on an
implementation detail of orgstrap.el which is not required by the specification. Namely
that =orgstrap-orgstrap-block-name= is not required as one of the prop line local
variables. Given the desire for the orgstrap machinery to be as unobtrusive as possible,
it is unlikely that support for an arbitrary name for the block will be added to the spec.

That said, it is worth considering how and whether to update the spec so that a conforming
implementation could do this if it wanted to. All the change does is move a convention to
an optional variable. Maybe a compact variable such as orgstrap-bn could be specified as
an optional prop line variable, and if absent the orgstrap block name defaults to
orgstrap, otherwise the block name searched is the value of the variable.

Still not 100% sure about this. It would increase the complexity of the implementation for
sure. It will require updating how and when we populate the link to orgstrap block, and
makes auditing more difficult. It also opens up a way to trick the user, namely by having
a link to an innocent looking orgstrap block, and no convention set in the prop line, or
maybe even having it in the prop line (people are habitual and assume things are not
present when they are), and then using setq-local, or some other means to change the block
name to a malicious block elsewhere in the file. Implementations would have to know to
check for this and fail if it was detected. Basically we would have to specify that if a
block named orgstrap is present in a file when orgstrap-bn is present and not set to
orgstrap, then it is a fatal error and orgstrap will not continue. Sticking this in the
900 or so chars we have left for further features seems like it would be a stretch, but
might be possible.
** Security considerations
=orgstrap= currently does not check all the headers or vars properties that materialized
onto a source block we probably need to do this. For the time being users need to check
for any hidden header properties that might be attached if the source block is buried
within a tree somewhere. See ~org-babel-one-header-arg-safe-p~ for one way that this
might be implemented in the elvs.
** DONE Batch mode
This is more effectively implemented in [[./shebang.org]] because it
bypasses the orgstrap checksum entirely. It is still secure because
the user has to intentionally run the org file as a script. The
overall complexity for the user is lower as well since they do not
have to maintain or worry about the batch helper file, and the churn
in the batch helper file is also eliminated. This section is retained
for the record.

There are a number of use cases for being able to process orgstrapped files in batch mode.
For example being able to load a file and have it automatically tangle itself vastly simplifies
a number of different workflows. =emacs -q --batch -l orgstrap-known-safe.el my-file.org= seems
like a reasonable approach. Essentially =orgstrap-known-safe.el= needs to contain the safe eval
blocks and the audited hashes so that local variable prompts will not be triggered since they
always return no when in batch mode. One additional feature is be to able to pass the checksum
on the command line. The eval variables would still have to be loaded in some way, but avoiding
the need to open and edit =orgstrap-known-safe.el= for each new file, and possibly edit it again
to remove the approval in the future.

#+begin_src elisp :results none
(let ((buffer (find-file-noselect "orgstrap-batch-helper.el.example")))
(with-current-buffer buffer
(erase-buffer)
(let (print-length print-level (print-escape-newlines t))
(insert ";;; -*- mode: emacs-lisp; lexical-binding: t -*-\n\n")
(insert ";;; add audited checksums here\n\n")
(insert "(setq-local\n orgstrap-audited-checksums\n '(\n\n ))\n\n")
;; TODO insert test file block checksums
(insert ";;; set audiated checksums as safe local variables\n\n")
(insert
(pp-to-string
'(mapcar (lambda (checksum-value)
(add-to-list 'safe-local-variable-values (cons 'orgstrap-block-checksum checksum-value)))
orgstrap-audited-checksums))))
(insert "\n;;; helper local variables\n\n")
(cl-loop
for local-variable in '((orgstrap-cypher . sha256)
(orgstrap-norm-func-name . orgstrap-norm-func--prp-1-1)
(orgstrap-norm-func-name . orgstrap-norm-func--dprp-1-0))
do
(let (print-length print-level (print-escape-newlines t))
(insert (prin1-to-string
`(add-to-list 'safe-local-variable-values ',local-variable)))
(insert "\n")))
(insert "\n;;; known eval local variables\n\n")
)
(cl-loop
for (eval-local-variable elv-checksum) in
(cl-remove-duplicates
(cl-loop
for block-name in '("example-noweb-no" "example-noweb-yes" "example-noweb-eval")
append
(cl-loop
for minimal in '(nil t)
append
(cl-loop
for norm-func-name in
'(;; orgstrap-norm-func--prp-1-0 ; deprecated do not include
orgstrap-norm-func--prp-1-1
orgstrap-norm-func--dprp-1-0)
collect
(let ((info (save-excursion
(orgstrap--goto-named-src-block block-name)
(org-babel-get-src-block-info))))
(setf (nth 6 info) "hrm")
(list (orgstrap--lv-command info minimal norm-func-name)
(secure-hash
orgstrap-cypher
(orgstrap-norm
(let (print-quoted print-length print-level)
(prin1-to-string (orgstrap--lv-command info minimal norm-func-name)))))
)))))
:test #'equal)
do
(with-current-buffer buffer
(let (print-length print-level (print-escape-newlines t))
(insert (prin1-to-string `(add-to-list 'orgstrap-known-elvs ,elv-checksum)))
(insert "\n")
(insert (prin1-to-string
`(add-to-list 'safe-local-eval-forms ',eval-local-variable)))
(insert "\n"))))
(with-current-buffer buffer
(save-buffer)))
#+end_src

#+name: example-noweb-no
#+begin_src elisp :noweb no
#+end_src

#+name: example-noweb-yes
#+begin_src elisp :noweb yes
#+end_src

#+name: example-noweb-eval
#+begin_src elisp :noweb eval
#+end_src

#+name: test-batch-helper
#+begin_src bash
emacs -q --batch \
-l orgstrap-batch-helper.el \
--eval "(message \"\n%s\"(emacs-version))" \
--eval "(defun orgstrap-test () (error \"Test failed.\"))" \
-f toggle-debug-on-error \
--visit test-lv-list-minimal \
--eval "(message \"done\")" 2>&1

emacs -q --batch \
-l orgstrap-batch-helper.el \
--eval "(message \"\n%s\"(emacs-version))" \
--eval "(defun orgstrap-test () (error \"Test failed.\"))" \
-f toggle-debug-on-error \
--visit test-lv-list-portable \
--eval "(message \"done\")" 2>&1
#+end_src

** Run once
In principle the simplest way to do this is to use the =:cache yes= header on a block.
However, unless the state is persisted into a users =init.el= file or equivalent, then
the file would need a way to know that it had not been run when opened again in a new
Emacs session. Similar issue with opening the same file in multiple Emacs sessions at
the same time. The block simply will not run again if the cached result is present.

Therefore, since =:cache yes= by itself is a dead end for ensuring that functionality
is always available any time a file is loaded there are a couple of options.
1. Persist to =init.el=. This is evil.
2. Request to tangle and install as package.
A variant of this is simply to use package.el to install
the desired functionality in a persistent way in combination
with accept klobbering.
3. Figure out how to transparently wrap an elisp block in =unless=.
4. Advise =defun= (say what!?)? @@comment: TERROR@@
5. Figure out how to un-cache a block when Emacs exits.
This will fail in nasty, unpredictable, and hard to debug ways.
6. Set =:cache (if (boundp 'orgstrap-already-run) "yes" "no")=.
This ALMOST works. If =:cache no= embedded the sha1 sum then
we would be golden. *This seems like the best bet.*
7. Accept klobbering.
8. Advise org-babel-eval to run with org-babel-sha1-sum even when cache is not set to yes

Another possibility would be
1. put checksums of orgstrap blocks that have been run in a list
2. use a special header arg ~:run-once yes~ to mark blocks that should not run if
their checksum is already on the list.

If used with multiple blocks/multiple revals this would make it possible to
run only a subset
** Tangle once
When bootstrapping a new system there are many times when want to create a
file only if it does not already exist. The =:tangle= header does not support
this use case, but we can implement it anyway using the example below.
#+name: tangle-once-example
#+begin_src org
,#+name: orgstrap
,#+begin_src elisp
(defun tangle-once (path) (if (file-exists-p path) "no" path))
,#+end_src

,#+begin_src bash :tangle (tangle-once "./path-to-tangle")
echo lol
,#+end_src
# I think I've seen this before but you apparently can't have ,#+end_src on the line before #+end_src ... fun bug
#+end_src
** Multiple blocks
There must be only a single one of those blocks so that the rest of
the blocks can safely use the functions defined in the orgstrap block.

A single elisp block is sufficient to enable nearly all use cases involving
tangling source blocks to file without having to fight the prompts. However,
it is very much not sufficient for any use cases that involve other languages.
This is particularly an issue for org files that want to bootstrap whole systems.

The simplest solution to me seems to be to add a second prompt variable which is
an alist of source block checksums and names[fn::the names are not technically required
but are for human readability]. As soon as the =orgstrap= block is run
=orgstrap--confirm-eval= is no longer needed and can be replace with a function
that validates the other blocks from the prompt variable.

This seems like a tractable approach, but also over complicated because it is surely
easier in a case like this where blocks are very unlikely to be reused across org files
to simply =(setq-local org-confirm-babel-evaluate nil)= and tell people to audit the
whole file. The alternative in that case might be to hash all the source blocks and
validate all of them at once at the start of the orgstrap block. This might need some
additional machinery, not entirely sure, maybe just have =orgstrap-all-blocks-checksum=
that can be used in cases like that. The advantage here is that the core of the process
can be verified once and then the documentation around it can change and grow as needed.
** STARTED Support for orgstrap blocks beyond elisp
See https://github.com/tgbugs/laundry for the start of work
implementing org mode in Racket that would make it possible to have a
practical discussion about how to approach Org babel beyond Emacs.

The spec may need to be extended to allow multiple orgstrap blocks
with the same name. We might need to make a provision for the
implementation specific language blocks to allow multiple names
with the block for the main implementation language getting priority,
however that may add significantly more complexity than e.g. just
adding =orgstrap-elisp=, =orgstrap-racket=, =orgstrap-{lang}= as
block names that are also searched.

This is unlikely to be useful any time soon given that there aren't
full implementations of org mode outside of Emacs. If some setup is
written in another language the best approach is to call the other
block using the orgstrap block.

If at some point it becomes possible to use another language in the
top level of org, then I imagine that such functionality will go in as
a property or a file local variable or something like that. Early
candidates for other languages that might be supportable are other
lisp dialects. With such mechanisms in place, it would be relatively
straight forward to lift the restriction on the language type, or
rather, to include the normalized language name as part of the hash,
or possibly to include it as a prop line local variable. More
exploration would be required, but until there is some other
implementation that has an extension language that is not elisp, this
is a moot point.
** DONE Remove defun docstrings from hashing
One additional source of noise in addition to comments are defun and
defmacro docstrings. These should be dropped from the tree if they are
present. This is now partially implemented via =orgstrap--dedoc=.

The issues in [[*1.2.4][1.2.4]] with inconsistent noweb block
indentation. Is yet another reason for this. There is some lingering
inconsistency in exactly how many leading spaces are included when
nowebbing in elisp forms that include a multi-line string. This is one
of those extremely annoying but hard to fix issues related to noweb
and leading whitespace.
** Deterministic semantics preserving reordering
Reorder the expressions used in the orgstrap block alphabetically (or something like that)
according to a deterministic rule, but not in a way that changes program semantics.
For example a function definition cannot be moved after a top level invocation of that
function.
1. defuns with different names can be reordered
2. defuns with the same name can be reordered as a block but cannot
internally be reordered because the order of shadowing matters
3. While it might be nice to completely erase the names of functions as well
as internal variable names, this would make it trivial to shadow existing
function names in ways that are malicious. The exact names matter, so we
have to preserve them. Also the cost of not being able to tell that
=(lambda (a) (+ a a))= and =(lambda (b) (+ b b))= are the same seems fairly
small.
4. One potential approach is to lift all defuns to the top, and then function calls
or whatever the more generic procedure invocation means. The simple local rule
is that all definitions must occur before usage except in the case where there is
a shadowing event that happens after a first invocation. This is annoying, but
if a call to a function happens before that function is defined we have to assume
that the call is calling some other function and those statements cannot be reordered.
So the ordering is calls to functions with names matching any later defuns or
any later assignment. Then defuns and assignments, finally procedure invocations
which might also include assignments. I get the sense that this is covered under
some part of compiler theory but can't quite put my finger on it.
** Figure out how to demo loading the packages used in this file
#+begin_src elisp
(use-package org-ref) ; for ref:
(use-package ox-gfm) ; simplify export of changelog for release
(use-package flycheck-package) ; linting for melpa
#+end_src
** Orgware run
One potential way to simplify command line execution of orgstrapped
files is to write a wrapper that is aware of the internal variables to
control various aspects of orgstrap behavior, such as config, testing,
tangling, setup, build/make, dependencies, exporting, publishing, etc.
Essentially we wind up with a set of functionalities that are commonly
needed and set conventions for how to run only the desired subset.

Dealing with dependencies between functionalities is probably out of
scope. The assumption is that Emacs and system packages are required
for all further functionality, but beyond that it is not clear.

# This is also related to the self describing nature of the orgstrap
# approach which means that the diversity explosion steps, such as
# dealing with the huge variety of package managers (discussed above),
# will have to accumulate over time in the files themselves, or in a
# portability layer that outside the files (e.g. via reval)
** Testing and continuous integration
Ideally would like to move toward using ert for testing in this file.
Generally it would be good to have an established workflow for testing
orgstrap files beyond just nowebbing into ~when orgstrap-do-test~.

As those workflows become established it would be nice to have a set
of minimal wrappers for the various CI systems that will be sufficient
to kick off the testing. See https://github.com/purcell/nix-emacs-ci
for one way we might approach this.
** STARTED Lexical variant
This saves about 300 chars over the current minimal version.
#+begin_src elisp :results none
(cl-defun orgstrap--length-of-sexp-at-point (&aux print-level print-length (print-escape-newlines t))
(interactive)
(message
"%s"
(length
(prin1-to-string
(read
;; (org-babel-get-src-block-info)
(thing-at-point 'sexp))))))

(defalias 'length-of-sexp-at-point #'orgstrap--length-of-sexp-at-point)
#+end_src

#+begin_src elisp
(let ((n "8.2.10") ; need
(a (org-version)) ; actual
(org-confirm-babel-evaluate #'orgstrap--confirm-eval)
(obs (org-babel-find-named-block "orgstrap")))
(or
(fboundp #'orgstrap--confirm-eval)
(not n)
(string< n a)
(string= n a)
(error "Org too old! %s < %s" a n))
(defun orgstrap-norm-func--prp-1-1 (body)
(let (print-quoted print-length print-level)
(prin1-to-string (read (concat "(progn\n" body "\n)")))))
(unless (fboundp #'orgstrap-norm)
(defun orgstrap-norm (body) (funcall orgstrap-norm-func-name body)))
(unless (fboundp 'orgstrap--confirm-eval)
(defun orgstrap--confirm-eval (lang body)
(not (and (member lang '("elisp" "emacs-lisp"))
(eq orgstrap-block-checksum
(intern (secure-hash orgstrap-cypher (orgstrap-norm body))))))))
(unwind-protect
(save-excursion
(goto-char obs)
(org-babel-execute-src-block))
(org-set-startup-visibility)))
#+end_src
** DONE Buffer local functions
See [[file:./defl.org]] for an awful hack.
** DONE fix orgstrap--update-on-change hook so that it does not modify buffer fold state
CLOSED: [2021-08-29 Sun 16:36]
:PROPERTIES:
:CREATED: [2020-10-21 Wed 01:28]
:END:
Not sure exactly which function is causing this but getting the
checksum of the block seems like it might be the one, unfolding and
not restoring the state.

The issue was in =orgstrap--with-block= because =goto-char= will cause
outlines to open and we have to use =org-save-outline-visibility= with
=use-markers= set to ensure that folding is restored.

=save-restriction= was not what we needed.
https://stackoverflow.com/a/44158824 put me on the right track.
** DONE fix orgstrap-init formatting issue where comment goes in headline
:PROPERTIES:
:CREATED: [2020-10-19 Mon 18:01]
:END:
** DONE blacklist and whitelist
:PROPERTIES:
:CREATED: [2020-10-15 Thu 22:32]
:END:
#+begin_example elisp
(defcustom orgstrap-blacklist-files nil
"List of files that should not be orgstrapped."
;; TODO but that should still have orgstrap-edit-mode enabled?
:type 'list
:group 'orgstrap)
#+end_example
** TODO BUG :comments link breaks prop line :noexport:
or rather not breaks, but tries to co-exist with it which duplicates the line
which seems to break the ability to detangle among other things
** TODO behavior for activating orgstrap-mode in a buffer
What to do if we are in a buffer that has an orgstrap block?
I think the answer is to check for the local variables, and if they
are present, do nothing, if they are absent run the block?
** TODO resolve the issue with tabs in < 26 :noexport:
~prin1-to-string~ has inconsistent behavior when the string in question contains
an escaped tab character ~\t~. This can make checksums inconsistent.
** DONE command to checksum the file local variables :noexport:
This is now done as part of ~orgstrap-inspect-elvs~.
** TODO ruby org so that github can render footnotes correctly :noexport:
[[file:~/git/NOFORK/org-ruby]]
** DONE orgstrap-mode should not skip filter
:PROPERTIES:
:CREATED: [2020-10-01 Thu 21:04]
:END:
A potential issue in when running in =orgstrap-mode=. When the block
checksum has not been confirmed =orgstrap--hack-lv-confirm= might
filter/skip the checksum. I don't think this is actually what I
observed (see below).
*** I think this was never an issue
:PROPERTIES:
:CREATED: [2020-10-08 Thu 22:59]
:END:
I can't seem to reproduce this issue using the following.
#+begin_src bash
emacs -q -l orgstrap-autoloads.el \
-f toggle-debug-on-error \
--eval "(add-to-list 'load-path \"$(pwd)/\")" \
--eval "(orgstrap-mode)" orgstrap-minimal.org
#+end_src

I'm fairly certain that the issue that I was actually observing was
the fact that when there is a checksum _mismatch_, then nothing is
displayed and the block asks you to eval it -- you should decline and
inspect the block.

The issue it seems is actually that the block that we are being asked
to evaluate is not visible in the other window. So I have added a todo
item for that.
** DONE Smart update/upgrade using the version specifier
This is much easier now that I have put all the normalization and hashing
machinery in a single elv, and it no longer needs to rely
on the version of the block, just the version of the normalization function,
which is currently embedded along with everything else.
** DONE orgstrap-mode
If orgstrap is installed, then having a orgstrap-mode which could add itself to
org-mode-hook when enabled would supersede the local variables list implementation, or
possibly just do it when the local variables version was absent. Disabling the local
variables when orgstrap-mode is detect to be on might be possible, but without
it the user would have to explicitly decline the variables or risk having the orgstrap
block run twice. Going to have to get this resolved before doing any announcements since
it could leave users with quite a bit of pain if we don't get that check in before people
start using it.
** DONE Auto update block checksum on save
Before save hook and/or before commit hook to automatically update the block checksum.
** DONE use orgstrap to automatically keep example blocks in sync :noexport:
** DONE melpa :noexport:
Woo! https://melpa.org/#/orgstrap
gh:melpa/melpa/pull/7111
** DONE Determine whether to use minimal or portable based on the :noweb header
This is essentially what is implemented via =orgstrap--have-min-org-version=.
The choice to prefer the minimal local variables is left up to the user and
is controlled via the =orgstrap-use-minimal-local-variables= custom variable.
The only time when minimal is not used is if the version of org that is drafting
the file is too old (i.e. < =9.3.8=).
* Local Variables Footer :noexport:
:PROPERTIES:
:visibility: folded
:END:
# close powershell comment #>
# Local Variables:
# eval: (progn (setq-local orgstrap-min-org-version "8.2.10") (let ((a (org-version)) (n orgstrap-min-org-version)) (or (fboundp #'orgstrap--confirm-eval) (not n) (string< n a) (string= n a) (error "Your Org is too old! %s < %s" a n))) (defun orgstrap-norm-func--dprp-1-0 (body) (let ((p (read (concat "(progn\n" body "\n)"))) (m '(defun defun-local defmacro defvar defvar-local defconst defcustom)) print-quoted print-length print-level) (cl-labels ((f (b) (cl-loop for e in b when (listp e) do (or (and (memq (car e) m) (let ((n (nthcdr 4 e))) (and (stringp (nth 3 e)) (or (cl-subseq m 3) n) (f n) (or (setcdr (cddr e) n) t)))) (f e))) p)) (prin1-to-string (f p))))) (unless (boundp 'orgstrap-norm-func) (defvar-local orgstrap-norm-func orgstrap-norm-func-name)) (defun orgstrap-norm-embd (body) (funcall orgstrap-norm-func body)) (unless (fboundp #'orgstrap-norm) (defalias 'orgstrap-norm #'orgstrap-norm-embd)) (defun orgstrap-org-src-coderef-regexp (_fmt &optional label) (let ((fmt org-coderef-label-format)) (format "\\([:blank:]*\\(%s\\)[:blank:]*\\)$" (replace-regexp-in-string "%s" (if label (regexp-quote label) "\\([-a-zA-Z0-9_][-a-zA-Z0-9_ ]*\\)") (regexp-quote fmt) nil t)))) (unless (fboundp #'org-src-coderef-regexp) (defalias 'org-src-coderef-regexp #'orgstrap-org-src-coderef-regexp)) (defun orgstrap--expand-body (info) (let ((coderef (nth 6 info)) (expand (if (org-babel-noweb-p (nth 2 info) :eval) (org-babel-expand-noweb-references info) (nth 1 info)))) (if (not coderef) expand (replace-regexp-in-string (org-src-coderef-regexp coderef) "" expand nil nil 1)))) (defun orgstrap--confirm-eval-portable (lang _body) (not (and (member lang '("elisp" "emacs-lisp")) (let* ((body (orgstrap--expand-body (org-babel-get-src-block-info))) (body-normalized (orgstrap-norm body)) (content-checksum (intern (secure-hash orgstrap-cypher body-normalized)))) (eq orgstrap-block-checksum content-checksum))))) (unless (fboundp #'orgstrap--confirm-eval) (defalias 'orgstrap--confirm-eval #'orgstrap--confirm-eval-portable)) (let (enable-local-eval) (vc-find-file-hook)) (let ((ocbe org-confirm-babel-evaluate) (obs (org-babel-find-named-block "orgstrap"))) (if obs (unwind-protect (save-excursion (setq-local orgstrap-norm-func orgstrap-norm-func-name) (setq-local org-confirm-babel-evaluate #'orgstrap--confirm-eval) (goto-char obs) (org-babel-execute-src-block)) (when (eq org-confirm-babel-evaluate #'orgstrap--confirm-eval) (setq-local org-confirm-babel-evaluate ocbe)) (ignore-errors (org-set-visibility-according-to-property))) (warn "No orgstrap block."))))
# End: