;;
;; bild - a simple build tool
;;
;;; Notice:
;;
;; This is under active development. For now this is just a convenience wrapper
;; around `nix build`. The below commentary describes how this tool *should*
;; work.
;;
;;; Commentary:
;;
;; Design constraints
;;
;; - only input is a namespace, no subcommands, no packages
;; - no need to write specific build rules
;;   - one rule for hs, one for rs, one for scm, and so on
;; - no need to distinguish between exe and lib, just have a single output
;; - never concerned with deployment/packaging - leave that to another tool (scp? tar?))
;;
;; Features
;;
;; - namespace maps to filesystem
;;   - no need for `bild -l` for listing available targets. Use `ls` or `tree`
;;   - you build namespaces, not files/modules/packages/etc
;; - namespace maps to language modules
;;   - build settings can be set in the file comments
;; - pwd is always considered the the source directory, no `src` vs `doc` etc.
;; - build methods automaticatly detected with file extensions
;; - flags modify the way to interact with the build
;;   - -s = jump into a shell and/or repl
;;   - -p = turn on profiling
;;   - -t = limit build by type
;;   - -e = exclude some regex in the ns tree
;;   - -o = optimize level
;;
;; Example Commands
;;
;;     bild [-rpt] <target..>
;;
;; The general scheme is to build the things described by the targets. A target
;; is a namespace. You can list as many as you want, but you must list at least
;; one. It could just be `.` for the current directory. Build outputs will go
;; into the _bild directory in the root of the project.
;;
;;     bild biz.web
;;
;; Or `bild biz/web`. This shows building a file at ./biz/web.hs, this will
;; translate to something like `ghc --make Biz.Web`.
;;
;;     bild -r <target>
;;
;; Starts a repl/shell for target.
;;   - if target.hs, load ghci
;;   - if target.scm, load scheme repl
;;   - if target.clj, load a clojure repl
;;   - if target.nix, load nix-shell
;;   - and so on.
;;
;;     bild -p <target>
;;
;; build target with profiling (if available)
;;
;;     bild -t nix target
;;
;; only build target.nix, not target.hs and so on (in the case of multiple
;; targets with the same name but different extension).
;;
;; Here is an example integration with GHC. Given the following command-line
;; invocation to compile the namespace 'com.simatime.bild' which depends on
;; 'com.simatime.lib':
;;
;;     ghc com/simatime/bild.hs -i com/simatime/lib.hs -o _bild/bild -v \
;;         -main-is Biz.Bild.main
;;
;; The general template of which is:
;;
;;     ghc <source> -i <deps..> -o <target> -main-is <target>.main
;;
;; Some definitions:
;;
;; - <source> is some source file
;; - <deps..> is the stack of dependencies
;; - <target> is the target namespace, indicated by 'bild <target>'
;;
;; To fill out the build template, we can parse the file for known
;; namespaces. The general recipe is:
;;
;; 1. Create a list of namespaces in my git repo. This can be cached, or I can
;;    integrate with git somehow.
;; 2. Read the <source> file corresponding to <target>
;; 3. Look for 'import <namespace>', where <namespace> is a namespace in the
;;    aforementioned cache.
;; 4. If found, then save current build as a continuation and compile
;;    <namespace>. Result gets put on the dependency stack
;; 5. When finished, return to building <target>
;;
;; Once the build command template is filled out, we can create the nix expression.
;;
;; Questions
;;
;; - how to import (third-party) dependencies?
;;   1 just don't have them...? yeah right
;;   2 the target.nix could be the build description for target.hs
;;   3 just use a default.nix for the com.whatever
;;   4 have a deps.nix file
;;   5 list them in the file with other settings. Starting with Haskell,
;;     have comments like `{-: PACKAGE base <5.0.0.0 :-}' or `-- : PACKAGE base <5.0.0.0'.
;;     Other languages could use `#:` for the special prefix, basically just
;;     a comment plus colon.
;; - how to handle multiple output formats?
;;   - e.g. that ghcjs and ghc take the same input files...
;;   - say you have a .md file, you want to bild it to pdf, html, and more. What do?
;;   - i guess the nix file could return a set of drvs instead of a single drv
;;
;; TODO
;; - stream output from 'nix build' subprocess
;; - get rid of guile notes during execution
;; - ns<->path macro
;; - support list (scheme namespace) in ns<->path fns
;;
;;; Code:

(define-module (Biz Bild)
  #:use-module ((ice-9 popen) #:prefix popen/)
  #:use-module ((ice-9 format) #:select (format))
  #:use-module ((ice-9 rdelim) #:prefix rdelim/)
  #:use-module ((Alpha Core) #:select (fmt))
  #:use-module ((Alpha Shell) #:prefix sh/)
  #:use-module ((Alpha String) #:prefix string/)
  #:export (ns?
            ns->path
            path->ns
            main))

(define (main args)
  (let* ((root (sh/exec "git rev-parse --show-toplevel"))
         (target (cadr args))
         (path (ns->path target)))
    (display (fmt ":: bild ~a...\r" target))
    (sh/exec (fmt "nix build -f ~a/default.nix ~a"
                  root target))
    (display (fmt ":: bilt ~a" target))))

(define ns? symbol?)

(define (ns->path ns)
  (let ((to-path (lambda (s) (string/replace s #\. #\/))))
    (cond
     ((symbol? ns) (to-path (symbol->string ns)))
     ((string? ns) (to-path ns))
     (else (error "ns should be a string or symbol")))))

(define (path->ns path)
  (let ((to-ns (lambda (s) (string/replace s #\/ #\.))))
    (cond
     ((symbol? path) (to-ns (symbol->string path)))
     ((string? path) (to-ns path))
     (else (error "path should be a string or symbol")))))