(define-module (doctest docstring)
    #:export (docstring->text docstring->doctests min*))
(use-modules (srfi srfi-1))
(use-modules (doctest util))

(define (min* lst)
    "
        Find minimum non-negative integers LST, where
        0 is considered to be more then any positive number.

        +++ (min* '(1 0 2))
        --- 1
    "
    (fold (lambda (n seed)
	      (cond
	       ((zero? n) seed)
	       ((zero? seed) n)
	       (else (min seed n))))
	  0 lst))

(define (substring-safe str index)
    "
        Return substring of STR, starting at INDEX,
        or empty string, if STR is too short.

        +++ (substring-safe \"1234\" 3)
        --- \"4\"
        +++ (substring-safe \"1234\" 6)
        --- \"\"
    "
    (if (< (string-length str) index)
	(string)
	(substring str index)))

(define (count-leading-whitespaces str)
    "
        Return count of leading whitespaces in STR.

        +++ (count-leading-whitespaces \"   str\")
        --- 3
    "
    (length (take-while char-whitespace? (string->list str))))

(define (docstring->string-list docstring)
    "
        Transformate docstring to allow pretty printing in source
        and in documentation.

        Algorithm of transformation:
        1. Split docstring on list of string by newlines.
        2. Disregard string, consising of whitespaces only
           at begin and end of list.
        3. Calculate minimal number of starting spaces in
           all non-empty strings.
        4. Reduce number of starting spaces in all non-empty
           strings by minimum, calculated at step 3.

        All whitespaces treated equally, using tabulation
        is discouraged.
    "
    (define string-list (drop-around string-blank?
				     (string-split docstring #\newline)))
    (define whitespace-count (min* (map count-leading-whitespaces string-list)))
    (map (lambda (str) (substring-safe str whitespace-count)) string-list)
)

(define (doctest? str) (prefix-of? "+++" str))
(define (expectation? str) (prefix-of? "---" str))
(define (line->sexp line) (read (open-input-string (substring line 3))))
(define (docstring->doctests docstring)
    "
        Return list of doctests in DOCSTRING.

        Exceptions are not supported yet.
    "
    (map (lambda (group)
	     (map line->sexp
		  (cons (car group)
			(take-while expectation?
				    (cdr group)))))
	 (group-by doctest?
		   (docstring->string-list docstring))))

(define (docstring->text docstring)
    "
        Convert docstring to text, suitable for printing
        by stripping leading whitespaces.
    "
    (string-join (docstring->string-list) "\n"))
