; Execute tar/tgz by instrumenting the <load> function.
; See below for newlispdoc documentation.

; Initial symbol declaration to ensure them being contexts.
(define TarData:TarData)
(define TarIndex:TarIndex)

; Set up a tar file for instrumented loading and load a script from it.
(define (load-tar tar script)
  "" ; Discard any previous loading
  (clear-tar)
  
  "" ; Read tar/tgz file into memory. Distinguish between
  "" ; uncompressed and gzip compressed by magic.
  (define TarData:TarData
    (cond ((= '(31 -117 8 0)
              (let ((f (open tar "r")) (v ""))
                (when f (read f v 4) (close f) (unpack "c c c c" v))))
           (unless zlib (module "zlib.lsp"))
           (zlib:gz-read-file tar))
          (true (read-file tar))))

  "" ; Build up TarIndex from TarData, with "." naming the first file.
  "" ; Each entry begins with file name as zero terminated ascii, then
  "" ; at offset 124, the size as a zero terminated ascii string of up
  "" ; to 11 octal digits, then file content at offset 512, and then
  "" ; trailing padding up to next 512 byte block boundary.
  "" ; Also zero padding at the end of the tar file.
  (let ((i 0) (table '()) (szfmt (dup "b" 11)))
    (while (and (< i (length TarData)) (!= (char (i 1 TarData))))
      (let ((h (string (i TarData))) (sz 0))
        (dolist (x (unpack szfmt ((+ i 124) TarData)))
          (setf sz (+ (<< sz 3) (- x 48))))
        (when (= i) (TarIndex "." h)) ; this captures the first name
        (TarIndex h (list (+ i 512) sz)) ; name => (start length)
        (inc i (* (+ (/ sz 512) 2) 512)) ; next file at next block boundary
        )))
  
  "" ; Instrument the <load> function to load tar/tgz sources first.
  (unless newlisp-load (constant 'newlisp-load load))
  (constant 'load (fn (f)
                    (let (h (TarIndex f))
                      (if h (eval-string ((h 0) (h 1) TarData))
                        (newlisp-load f)))))

  "" ; Load the main script file if given and present, otherwise load
  "" ; the first file of the tar.
  (if (and script (TarIndex script)) (load script) (load (TarIndex ".")))
  ) ; end of load-tar

; Function to clean up by restoring the <load> function and clearing symbols
(define (clear-tar)
  (when newlisp-load (constant 'load newlisp-load))
  (define TarData:TarData)
  (define TarIndex:TarIndex)
  ) ; end of clear-tar

; Return the content of the named entry in the tar file.
(define (read-tar name)
  (let ((h (TarIndex name))) (when h ((h 0) (h 1) TarData))))

; Apply automatically to "-tar myapp.tgz" on the command line.
; (replace the find clause with 1 to unconditionally use the second argument)
(let ((i (find "-tar" (main-args))))
  (when (and i (> (length (main-args)) (inc i)))
    (apply load-tar (i (main-args)))))

"lsptar.lsp"
######################################################################
;; @module lsptar.lsp
;; @description Run newLISP program(s) in a tar file.
;; @author Ralph Ronnquist, Realthing Entertainment Pty Ltd
;; @version 1.0, 2015-04-08 initial release
;; @version 1.1, 2015-06-05 handle gzip compressed tar
;; @version 1.2, 2015-06-08 revised to using -tar argument
;; @version 1.3, 2015-06-10 fixed up invocation alternatives
;
;; <h1>Run NewLISP Application in "tar" or "tgz" File</h1>
;
;; This module defines a
;; @link #_load-tar load-tar
;; function for loading newlisp sources from a tar file, and it
;; includes a use of this to discover and act on a "-tar myapp.tgz
;; main.lsp" command line argument phrase by "running" it. Or more
;; precisely, it acts by instrumenting the
;; @link http://www.newlisp.org/downloads/newlisp_manual.html#load load
;; method to load files from "myapp.tgz" where possible, and then it
;; loads "main.lsp" from "myapp.tgz".
;;
;; Alternatively, if the main script argument is omitted, or not in
;; the tar file, then the first file of the tar is loaded instead. In
;; either case, the
;; @link http://www.newlisp.org/downloads/newlisp_manual.html#load load
;; function remains instrumented to load from the tar file first, both
;; during and after the main script loading.
;;
;; If the loading of the main script returns, the
;; @link http://www.newlisp.org/downloads/newlisp_manual.html#load load
;; function remains instrumented and the in-core copy of the tar file
;; remains. Subsequent
;; @link http://www.newlisp.org/downloads/newlisp_manual.html#load load
;; will keep loading from the tar file first, and the function
;; @link #_read-tar read-tar
;; can be used to read data files from the tar file.  The function
;; @link #_clear-tar clear-tar
;; may be used to restore the
;; @link http://www.newlisp.org/downloads/newlisp_manual.html#load load
;; function (which is preserved as <newlisp-load>), and discard the
;; tar file. Also, any subsequent
;; @link #_load-tar load-tar
;; will discard a previous tar file.
;;
;; <h3>Using lsptar.lsp in
;; @link http://www.newlisp.org/downloads/newlisp_manual.html#initialization .init.lsp
;; </h3>
;
;; The module is set up to be used as an initial script on the command
;; line. Thus, a principle command line using lsptar.lsp would be as
;; follows.
;
;; <pre>  % newlisp lsptar.lsp -tar myapp.tgz</pre>
;
;; Obviously "mayapp.tgz" could be something else; it is just the name
;; of the tar file to be run. As such, it may be either uncompressed
;; or gzip compressed; lsptar.lsp detects which it is by looking at
;; the first few bytes.
;;
;; The lsptar.lsp script is "silent" if the "-tar myapp.tgz" argument
;; phrase is missing. The idea is that it's part of your .init.lsp
;; script, so that you don't need to mention it on the command line,
;; and you still can use newlisp as per usual without the "-tar"
;; argument. In that case, the command line would simply be as
;; follows.
;
;; <pre>  % newlisp -tar myapp.tgz</pre>
;; It may come in handy to bewilder an audience.
;;
;; <h3>Embedded lsptar</h3>
;; To install this module as an
;; @link http://www.newlisp.org/downloads/newlisp_manual.html#link embedded
;; script interpreter, you need to do the following:
;; <pre>  % newlisp lsptar.lsp -x lsptar
;;  % chmod a+x lsptar</pre>
;
;; But before that, you might want to revise lsptar.lsp to explicitly
;; use the command line without requiring a "-tar" marker
;; argument. Doing so gives you the executable program <lsptar> to use
;; as script interpreter, as in the following examples (assuming
;; lsptar in PATH):
;
;; <ul>
;; <pre> % lsptar myapp.tgz</pre>
;; or
;; <pre> % lsptar myapp.tgz main.lsp</pre>
;; </ul>
;
;; Further, you might also define an executable wrapper script, say
;; ".wrapper" with a single line, as follows:
;
;; <pre>  #!/full/path/to/lsptar myapp.tgz</pre>
;; And set up symbolic links for alternative main scripts:
;; <pre>  % ln -s .wrapper foo.lsp</pre>
;; <pre>  % ln -s .wrapper bar.lsp</pre>
;
;; By that set up, you get foo.lsp and bar.lsp to be run as
;; alternative main scripts in the gzip compressed tar file
;; "myapp.tgz", and the wrapper file itself will load the first file
;; in myapp.tgz as main script. If you take some care in the naming
;; of files, it may look a bit like a <busybox> set up.
;;
;; <h3>Using lsptar.lsp on the command line</h3>
;
;; You use the lsptar.lsp module as is, without embedding, by
;; nominating the tar file, as in:
;
;; <pre>  % newlisp lsptar.lsp -tar myapp.tgz main.lsp</pre>
;; or possibly (not recommended):
;; <pre>  % newlisp -tar myapp.tgz main.lsp lsptar.lsp</pre>
;
;; The latter example will of course first load "main.lsp" from the
;; current working directory, if it is found there, and then, if the
;; main.lsp loading returns, lsptar.lsp will load it from the tar
;; file, if it is found there. Basically the second example relies on
;; newlisp being gracefully silent about "peculiar" and missing files,
;; but it's a recipe for major confusion if main.lsp happens to be
;; around.
;;
;; <h3>Notes</h3>
;
;; Note that the whole tar is read (and uncompressed if needed) into
;; memory in full, and things will go haywire with excessively large
;; tar files.
;;
;; Note that uncompression uses the newlisp standard module
;; @link http://www.newlisp.org/code/modules/zlib.lsp.html zlib.lsp.
;;
;; Note that the original, standard
;; @link http://www.newlisp.org/downloads/newlisp_manual.html#load load
;; function is preserved as <newlisp-load>.
;;
;; Note that the tar file index building is tied to the 
;; @link http://www.gnu.org/software/tar/manual/html_node/Standard.html GNU&nbsp;tar&nbsp;file&nbspformat,
;; and it might not work well with other formats. If you feel strongly
;; for using a different tar file format, you may need to rework the
;; index building procedure accordingly.
;;
;; Note that in order to use a tar file for data only, it should
;; contain an empty first file. Data content is then obtained after
;; loading by using the
;; @link #_read-tar read-tar
;; function. Or if you are keen, by explicitly slicing <TarData> with
;; region lookup through <TarIndex>.
;;
;; Note that tar loading only handles path names into the tar, and all
;; other
;; @link http://www.newlisp.org/downloads/newlisp_manual.html#load load
;; function usage variants succumb to their normal handling.
;;
;; <h1>Reference Documentation of Functions</h1>
;; @syntax (load-tar <tar> <main>)
;
;; Loads <main> and all its dependent files from the tar file
;; <tar>. If <main> is omitted, nil or not present in <tar>, then the
;; first file in <tar> is loaded instead. The tar file <tar> may be
;; uncomressed or gzip compressed, where the latter requires the
;; newlisp standard module
;; @link http://www.newlisp.org/code/modules/zlib.lsp.html zlib.lsp.
;;
;; If and when the loading of the main file returns, the tar file
;; remains in core, and the
;; @link http://www.newlisp.org/downloads/newlisp_manual.html#load load
;; function remains instrumented. Any subsequent
;; @link http://www.newlisp.org/downloads/newlisp_manual.html#load load
;; will be from the tar file first. Use
;; @link #_clean-tar clean-tar
;; to discard the in-core tar file, and restore the
;; @link http://www.newlisp.org/downloads/newlisp_manual.html#load load
;; function.
;;
;; @syntax (clear-tar)
;
;; Cleans up after
;; @link #_load-tar load-tar
;; by restoring the
;; @link http://www.newlisp.org/downloads/newlisp_manual.html#load load
;; function from <newlisp-load>, and clearing the symbols <TarData>
;; and <TarIndex>.
;;
;; @syntax (read-tar name)
;
;; Returns the content of the named entry in the current tar file,
;; previously loaded via
;; @link #_load-tar load-tar.
;; Use this function to read data files in the tar file.  A "data
;; only" tar file needs a blank first entry. The function returns nil
;; if <name> is not in the current tar file.




syntax highlighting with newLISP and newLISPdoc