generate-lib.scm
#lang scheme/base
;; code to generate basic files and folders needed for a project

(require "util.scm"
         (only-in mzlib/file normalize-path))

(provide generate generate-from-path)

;; XXX hardcoded ... replace with e.g., PLaneT reference...
(define PATH-TO-LP "/Users/rob/Documents/leftparen")

(define (generate args-vec)
  (generate-from-args-list (vector->list args-vec)))

(define (generate-from-path project-path args-vec)
  (generate-from-args-list (cons-to-end project-path (vector->list args-vec))))

;; Note: in order for this to work with generate-from-path, you need to make sure that
;; for every command, the last argument is always the project path.
(define (generate-from-args-list args)
  (match args
         ((list "project" fresh-project-path)
          (generate-project-dir fresh-project-path))
         ((list "script" project-path)
          (generate-script-dir project-path))
         ((list "scm" project-path)
          (generate-basic-scm-files project-path))
         ((list project-path)
          (e "You need to provide a command to the generate script."))
         ((list-rest command rst)
          (e "Generate command \"~A\" not understood." command))
         (else (e "Generate expression \"generate ~A\" not understood." args))))

(define (generate-project-dir fresh-project-path)
  (if (directory-exists? fresh-project-path)
      (e "The directory ~A already exists." fresh-project-path)
      (begin (ensure-existence-of-dir! fresh-project-path)
             (ensure-existence-of-dir! (build-path fresh-project-path "data"))
             (ensure-existence-of-dir! (build-path fresh-project-path "htdocs"))
             (ensure-existence-of-dir! (build-path fresh-project-path "htdocs/css"))
             (ensure-existence-of-dir! (build-path fresh-project-path "htdocs/js"))
             (ensure-existence-of-dir! (build-path fresh-project-path "htdocs/i"))
             (generate-basic-scm-files fresh-project-path)
             (generate-script-dir fresh-project-path))))

(define (generate-basic-scm-files project-path)
  ;; serve.scm
  (generate-file-with-expressions
   #:dir-must-exist #t
   (build-path project-path "serve.scm")
   #f
   `(require ,(expr-for-lp-require "leftparen.scm")
             "app.scm"
             "main.scm")
   (make-raw "\n")
   '(serve my-app
           #:listen-ip "127.0.0.1"
           #:port 8765
           #:htdocs-path '("htdocs"))
   )

  ;; app.scm
  (generate-file-with-expressions
   #:dir-must-exist #t
   (build-path project-path "app.scm")
   #f
   (make-raw "#lang scheme/base")
   (make-raw "\n")
   `(require ,(expr-for-lp-require "leftparen.scm"))
   (make-raw "\n")
   '(define-app my-app
      (index-page (url "/")))
   )
   
  ;; main.scm
  (generate-file-with-expressions
   #:dir-must-exist #t
   (build-path project-path "main.scm")
   #f
   (make-raw "#lang scheme/base")
   (make-raw "\n")
   `(require ,(expr-for-lp-require "leftparen.scm")
             "app.scm")
   (make-raw "\n")
   '(setting-set! *WEB_APP_URL* "http://localhost:8765/")
   '(define-page (index-page req)
      "Hello, World!")
   ))

(define (generate-script-dir project-path)
  (ensure-existence-of-dir! project-path #:must-previously-exist #t)
  ;; script/server
  (generate-file-with-expressions
   (build-path project-path "script/server")
   #f
   (make-raw (format "mzscheme -r ~A/serve.scm" (absolute-project-path-str project-path))))
  
  ;; script/generate
  (generate-file-with-expressions
   (build-path project-path "script/generate")
   #t
   `(require ,(expr-for-lp-require "generate-lib.scm"))
   `(generate-from-path ,(absolute-project-path-str project-path)
                        (current-command-line-arguments)))
  
  )

(define-struct raw (str))

(define (generate-file-with-expressions path-to-file is-script
                                        #:dir-must-exist (dir-must-exist #f)
                                        . expressions)
  (with-output-to-file-in-dir
   #:must-previously-exist dir-must-exist
   path-to-file
   (lambda ()
     (when is-script (write-string "\":\"; exec mzscheme -r $0 \"$@\"\n"))
     (for-each (lambda (e)
                 (if (raw? e) (write-string (raw-str e)) (write e))
                 (write-string "\n")) expressions)
     #t)))

(define (ensure-existence-of-dir! dir-path #:must-previously-exist (must-exist #f))
  (when (file-exists? dir-path)
    (e "A file called ~A instead of a directory was found." dir-path))
  (if must-exist
      (if (directory-exists? dir-path)
          #t
          (e "The directory ~A cannot be found." dir-path))
      (or (directory-exists? dir-path)
          (begin (make-directory dir-path)
                 (display (format "Created directory ~A.\n" dir-path))))))

;; it's an error if filename in path already exists
(define (with-output-to-file-in-dir path-to-file thunk
                                    #:must-previously-exist (must-exist #f))
  (receive (path filename is-dir) (split-path path-to-file)
    (ensure-existence-of-dir! path #:must-previously-exist must-exist)
    (with-output-to-file (build-path path filename) thunk #:mode 'text #:exists 'error)))

(define (expr-for-lp-require filename-rel-to-lib-root)
  `(file ,(path->string (build-path PATH-TO-LP filename-rel-to-lib-root))))

;; consumes a (maybe relative) project-path
(define (absolute-project-path-str project-path)
  (path->string (normalize-path (build-path project-path))))