#!/usr/bin/env sh
exec guile -L $(dirname $(dirname $(realpath "$0"))) -e '(@@ (examples doctests) main)' -s "$0" "$@"
; !#

(define-module (examples doctests)
              #:export (doctests-testmod))

(import (ice-9 optargs)
         (ice-9 rdelim)
         (ice-9 match)
         (oop goops)
         (texinfo reflection))

; define basic dir
(define* (dir #:key (all? #f))
   (if all?
      (map (λ (x) (cons (module-name x)
                        (module-map (λ (sym var) sym) (resolve-interface (module-name x)))))
           (cons (current-module) (module-uses (current-module))))
      (module-map (λ (sym var) sym) (current-module))))
; add support for giving the module as argument
(define-generic dir)
(define-method (dir (all? <boolean>)) (dir #:all? all?))
(define-method (dir (m <list>)) (module-map (λ (sym var) sym) (resolve-interface m)))
; add support for using modules directly (interfaces are also modules, so this catches both)
(define-method (dir (m <module>)) (module-map (λ (sym var) sym) m))

(define (string-index s fragment)
       "return the index of the first character of the FRAGMENT in string S."
       (let loop ((s s) (i 0))
           (if (= 0 (string-length s))
              #f
              (if (string-prefix? fragment s)
                 i
                 (loop (string-drop s 1) (+ i 1))))))

(define (doctests-extract-from-string s)
       "Extract all test calls from a given string.

          This is an example test:

          (test \"mytest\"
              (define v (make-vector 5 99))
              (test-assert (vector? v))
              (test-eqv 99 (vector-ref v 2))
              (vector-set! v 2 7)
              (test-eqv 7 (vector-ref v 2)))
          (test \"mytest2\"
              (test-assert #t))
"
       (let lp
           ((str s)
             (tests (list)))
           (if (string-null? str)
              (reverse tests)
              (let ((idx (string-index str "(test")))
                  (if (not idx)
                      (reverse tests)
                      (let ((sub (substring str idx)))
                          (lp ; recurse with the rest of the string
                             (with-input-from-string sub
                                 (λ () (read) (read-string)))
                             (cons
                                 (with-input-from-string sub
                                     (λ () (read)))
                                 tests))))))))

(define (doctests-testmod mod)
       "Execute all doctests in the current module"
       (let*
           ((names (module-map (λ (sym var) sym) mod))
             (filename
                 (if (module-filename mod) (string-join (string-split (module-filename mod) #\/) "-")
                     (string-join (cons "._" (map symbol->string (module-name mod))) "-")))
             (docstrings
                 (map (λ (x) (if (procedure? x) (procedure-documentation x)))
                     (map (λ (x) (module-ref mod x)) names))))
           (let loop
               ((names names)
                 (docstrings docstrings))
               (when (not (null? docstrings))
                   (when (string? (car docstrings))
                       (let*
                           ((name (car names))
                             (docstring (car docstrings)))
                           (let loop-tests
                              ((doctests (doctests-extract-from-string (car docstrings))))
                              (when (and (list? doctests) (not (null? doctests)))
                                 (let*
                                   ((doctest (car doctests))
                                     (testid
                                        (if (not (list? doctest))
                                             #f
                                             (string-join (list filename (symbol->string name) (car (cdr doctest)))
                                                         "--")))
                                     (cleaned
                                         (if (not (list? doctest))
                                           '#f
                                           (append
                                               (cons 'begin
                                                   (cons '(import (srfi srfi-64))
                                                       (cons
                                                           (list 'test-begin testid)
                                                           (cdr (cdr doctest)))))
                                               (list (list 'test-end testid))))))
                                   (when cleaned
                                       (let ()
                                           (eval cleaned mod))
                                       (newline))
                                   (loop-tests (cdr doctests)))))))
                   (loop (cdr names) (cdr docstrings))))))

(define %this-module (current-module))
(define (main args)
         (doctests-testmod %this-module))