jsmake.ss
#!/usr/bin/env mzscheme -mtv
#lang mzscheme

; This is a rough and ready script for compiling a bunch of JavaScript fragments
; into a single file. You write a "spec file" like this:
;
;     ----- SNIP -----
;     ("des1.js" ("src1a.js" "src1b.js" "src1c.js"))
;     ("des2.js" ("src2a.js" "src2b.js" "src2c.js"))
;     ("des3.js" ("src3a.js" "src3b.js" "src3c.js"))
;     ----- SNIP -----
;
; and run the script like:
;
;    jsmake.ss <spec-file>
;
; an optional "-R" command line parameter overwrites any existing destination files.

(require scheme/cmdline
         scheme/match
         (file "base.ss")
         (file "file.ss"))

; Global variables -------------------------------

(define replace? #f)

; Procedures -------------------------------------

;; read-spec-file : (U path string) -> spec
;;
;; where spec : (list-of (list string (list-of string)))
;;
;; The spec structure is interpreted as:
;;
;;     (list-of (list des-filename (list-of src-filename)))
(define (read-spec path)
  (let ([in (open-input-file path)])
    (let loop ([data (read in)])
      (if (eof-object? data)
          null
          (cons data (loop (read in)))))))

(define (process-spec spec)
  (for-each (match-lambda
              [(list (? string? des) (? list? src))
               (printf "Concatenating files: ~a -> ~a...~n" src des)
               (for-each (lambda (src)
                           (unless (file-exists? src)
                             (raise-exn exn:unlib 
                               (format "File does not exist: ~a." src))))
                         src)
               (when (file-exists? des)
                 (if replace?
                     (delete-file des)
                     (raise-exn exn:fail:unlib 
                       (format "File exists: ~a (use the -R option to auto-replace)." des))))
               (concatenate-files des src)]
              [other
               (raise-exn exn:unlib 
                 (format "Badly formatted spec: ~a~n" other))])
            spec))

; Main script body -------------------------------

(define (main . argv)
  (command-line "jsmake" argv
                (once-each
                 [("-R" "--replace")
                  "Replace existing files."
                  (set! replace? #t)])
                (args (filename)
                      (with-handlers ([exn:unlib?
                                       (lambda (exn)
                                         (printf "~a~n" (exn-message exn))
                                         (printf "Bailing.~n")
                                         (exit 1))])
                        (process-spec (read-spec filename)))
                      (printf "Done.~n"))))