TANGLElib
Table of Contents
1 Tangle Introduction
This paper is part of my Commonplace Book
These tangle functions simplify the products of tangling, or extracting source code from an OrgMode source file.
2 Usage, operations
To make this as simple as possible, in an emacs window, with two open buffers,
- in one, tangle the file, spilling it's contents into a file or files
- in the other, a shell buffer, from the commandline
tangle_all
Some conventions are required:
- files are tangled to the local ./.tangle directory. Here's an example:
#+BEGIN_SRC sh :tangle ./.tangle/stack.0
- files may be collected into larger files by running the tangled fragments thru an include program, each of which must be a readable text file, for example:
@include aux.0 @include aux.1 @include aux.2 @include aux.3 @include report.all @include stack.0 @include trace.0 @include om.0 aux_init () { report_init stack_init trace_init om_init om_iam } aux_init 1>&2
- tangle master files have a suffix all
- there is no requirement (other than good taste) on tangled prefix or suffix names. In this sense, "good taste" include "no spaces"
3 Code
3.1 tangle_all
This is the user's top function.
tangle_all () { tangle_new pushd $(needir .untangle) > /dev/null tangle_mklibsapps popd }
3.2 include
The include script. Look for an iawk or gawk reference on the web. The key is:
@include filename
and it is recursive.
# WAS: http://mcgowans.org/pubs/marty3/commonplace/software/swdairy.html cat ${*:--} | awk ' BEGIN { stderr = "/dev/stderr" logfil = "include.not" errfmt = "TRACE include.%s(%s)\n" } function this_line (res) { printf "%3d %d %s\n", NF, res, $0 > stderr return res } function with_file (file) { printf errfmt, "with_file", file > stderr if (this_line( (getline line < file) == -1 )) { printf "needfile %s\n", file > logfil print } else { # side effect, "file" is opened, so, # close it to be able to read it: close(file) printf errfmt, "about to INCLUDE", file > stderr system( "include " file) } # and since we may have just read it, close it again # if we opened it, or for the first time if we did not. # why? an app may include a file more than once. close(file) } function may_include() { # printf errfmt, "may_include", "" > stderr # HTML Comment, replace with 7/26/15, mcgowan@alum.mit.edu # return this_line($1 == "<!--" && $2=="include" && $4 == "-->") # iawk-style include, not bound simply awk programs return this_line($1 ~ /^@include$/) } { if ( may_include() ) { # with_file( $3 ), ex 7/26/15 # e.g. @include filename with_file( $2 ) next } } NF { print } '
3.3 tanglelib
This is the tangle function library, tanglelib. Note the use of the @include directive.
each file should have one syntax, either include or script.
- installation
source installlib binfile tangle
- including
@include tangle.0 @include tangle.1
- tangle main
copyright_tangle () { comment "Copyright (C) 2016, JYATL - Just Yet Another Testing Lab"; comment "mailto: mcgowan (at) alum DOT mit DOT edu"; } tangle_new () { : .UNTANGLE and .TANGLE are sibling directories, the OrgMode TANGLE; : puts stuff in TMP w/o any sense of changed or not; : run from the .UNTANGLE directory, this pulls CHANGED files fm TMP; pushd ./.tangle > /dev/null; local tangleDir=$(needir ../.untangle) for f in *; do ifdiff $f $tangleDir/$f; done; popd > /dev/null } ifdiff () { : better name MVifdiff, sinc; : mv 1 to 2 if files DONT compare; trace_call $*; exists $2 || touch $2; cmp $1 $2 > /dev/null || mv $1 $2 } tangle_err () { report_notargcount 1 $#; trace_call $*; tangle_include $1 ${1%.*}.{out,err}; } tangle_out () { report_notargcount 1 $#; trace_call $*; tangle_include $1 ${1%.*}.{out,err}; } tangle_include () { : ~ INPUT Output Error : fails if "include.not" has data, the ERR is the fence. report_notargcount 3 $# Input Output Error && return 1 : report_notcalledby tangle_untangle AND return 2 rm -f include.not include $1 >$2 2>$3 [[ -s include.not ]] && { rm -f $3 cat include.not return 3 } return 0 } tangle_included () { : ~ FILE.suf : where if file.ERR does not exist, its recreated : this function knows the format of the error log from INCLUDE : and may leave an incomplete *.OUT file set ${1%.*}.{all,out,err} [[ -f $3 ]] || { tangle_include $* } report_notfile $3 && return 1 trace_call $*; cat $3 | awk ' $2 ~ /include.with_file/ { sub(/.*with_file./, "") sub(/[)].*/,"") print }' } tangle_mklib () { : read LIB,APP files for included members,; : updating modifications to the OUT file,; : changing mode to +X, and linking to the runtime; for f in $*; do case $f in *.lib) tangle_mkone lib $f ;; *.app) tangle_mkone app $f ;; *) report_usage $f is neither APP nor LIB continue ;; esac done } tangle_mkone () { report_notcalledby tangle_mklib && return 1 report_notargcount 2 $# suf {file}.{suf} && return 2; suf=$1; shift set ${1%.*}; report_notfile $1.$suf && return 3; local out=$(which ${1}$suf) [[ -f "$out" ]] || { out=$(dotty)/$suf; doit touch $out; chmod +x $out; } : there is a LIB,APP to receive the OUT file, so; set $1.$suf $1.{out,err}; trace_call $* $suf: $out; : to regenerate the OUT, rm the ERR; [[ -f $3 ]] || { tangle_include $* || { comment tangle_include 1st; return 4; } }; : now if any INCLUDED have changed; newest $out $1 $(tangle_included $1) || { tangle_include $* || { comment tangle_include 2nd; return 5; } chmod +x $2; ln -f $2 $out } return 0 } tangle_untangle () { : ~ file.ALL report_notcalledby tangle_mklibsapps && return 1 report_notargcount 1 $# {file}.all && return 2; report_notfile $1 && return 3; set ${1%.all}.{all,out,err} trace_call $* : to regenerate the OUT, touch the ALL. [[ -f $3 ]] || { tangle_include $* || { comment tangle_include 1st; return 4; } }; : now if any INCLUDED have changed; new_fromby out,all tangle_out $1 # newest $out $1 $(tangle_included $1) || { # tangle_include $* || { comment tangle_include 2nd; return 5; } # } # return 0 } tangle_mklibsapps () { trace_call $*; : lib contents are in *.lib; : app contents are in *.app; :; local all for all in *.all; do tangle_untangle $all done } tangle_init () { trace_call $*; om_iam; echo 'tangle mklibsapps' } tangle_init 1>&2
4 Reported failures
- tangle_mklib
- no arguments
- the argument is not a .lib file
- the file has no associated runtime lib. To use this for the first time, touch a file on user's PATH, make it exectuable. e.g. if this.lib is the master tangle file, then $HOME/bin/thislib would be a suitable destinantion for the shell library.
- include
- writes list of un-found files in the include.not file
5 Rationale
Rather than attempt to tangle into the target file, since these are shell libraries, the tangle s land in the tmp sub-directory, and then pulled into the adjacent .untangle sub-directory, as needed.
Since I haven't figured out how to modulate the tangling from the parent, and I want to preserve modification times on files which don't change, I use the two adjancent directories. The key functions in this process are ifdiff, newest, and tangle_included. The latter uses the include script to find the dependent files. The former pair keep unchanged files from being gratuitiously updated.
HELP org-babel-tangle C-c C-v C-t is the answer
It takes two to TANGLE
6 Lessons tangle
I've learned, of historical note, and needs revision.
With this file, I've learned how to have code live with it's documentation, and almost install it. That lesson awaits understanding how to hang a "hook" while extracting the source code.
Here are the steps I go thru:
- edit the code or it's documentation. (may use C-c ' in a code block),
- "tangle" the code block(s) – see the link above, which ship the code off to it's place to live.
- execute the 'installation' instructions below. these are only in the Org file, since they're hidden from the HTML output. alternatively, do the same thing from a terminal window, where the commands may be tested independently of emacs/OrgMode.
Hanging the hook: org-babel-post-tangle-hook promises to allow post-processing, i.e. making the ../bin files excutable and linking the text files to the adjactent ../lib directory.
And, just maybe, this should become the ASSERTlib, change every instance of report_ to assert_?