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,

  1. in one, tangle the file, spilling it's contents into a file or files
  2. in the other, a shell buffer, from the commandline
  3. 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.
  1. installation
    source installlib 
    binfile tangle
    
  2. including
    @include tangle.0
    @include tangle.1
    
  3. 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
    1. no arguments
    2. the argument is not a .lib file
    3. 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
    1. 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:

  1. edit the code or it's documentation. (may use C-c ' in a code block),
  2. "tangle" the code block(s) – see the link above, which ship the code off to it's place to live.
  3. 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_?

7 References

Author: Marty

Created: 2018-07-10 Tue 16:50

Emacs 24.4.1 (Org mode 8.2.10)

Validate