Source

missbehave / behave.scm

;; 
;; %%HEADER%%
;; 

(use missbehave defstruct fmt fmt-color data-structures chicken-syntax args posix files regex)
  
(define use-colors #t)

(define command-line-options
  (list (args:make-option (h help) #:none "Display this help" (usage port: (current-output-port) status: 0))
        (args:make-option (n nocolor) #:none "Don't display colors" (set! use-colors #f))
        (args:make-option (t tags) (required: "TAG") "Tags to filter. Can be used multiple times" )))

(define (usage #!key (port (current-error-port)) (status 1))
  (with-output-to-port port
    (lambda ()
      (print "Usage: " (car (argv)) " [options ...] file ...")
      (newline)
      (print (args:usage command-line-options))))
  (exit status))

(when (= 1 (length (argv)))
  (usage))
  

(defstruct report-agenda failed successful pending failed-examples pending-examples indentation-factor)
(defstruct failure result index)


(define (make-pretty-reporter)
  (let ((agenda (make-report-agenda failed: 0 successful: 0 pending: 0 failed-examples: (list) indentation-factor: 0)))
    (lambda (data #!key (mode 'adhoc))
      (case mode
        ((adhoc)   (report-adhoc! data agenda))
        ((summary) (report-summary! data agenda))))))

(define (count-parents context)
  (let loop ((parent (context-parent context)) (count 0))
    (cond
     (parent
      (loop (context-parent parent) (+ count 1)))
     (else count))))

(define (report-adhoc! data agenda)
  (cond
   ((context? data)
    (let* ((parents (count-parents data))
           (ifactor (if (or (not parents) (zero? parents)) 1 parents)))
      (report-agenda-indentation-factor-set! agenda parents)
      (if use-colors
          (indent (* 2 ifactor) (fmt #f (fmt-bold (context-description data))))
          (indent (* 2 ifactor) (fmt #f (context-description data))))
      (newline)))
   ((example? data) #t)
   ((example-result? data)
    (cond
     ((example-failed? data)
      (let ((failure-index (+ 1 (report-agenda-failed agenda))))
        (report-example 'failed data agenda failure-index)
        (report-agenda-failed-examples-set!
         agenda
         (cons (make-failure result: data index: failure-index)
               (report-agenda-failed-examples agenda)))
        (agenda-increment-failed! agenda)))
     ((example-pending? data)
      (report-example 'pending data agenda)
      (agenda-increment-pending! agenda))
     (else
      (report-example 'success data agenda)
      (agenda-increment-successful! agenda))))))

(define (report-summary! data agenda)
  (if use-colors
      (report-summary-with-colors! data agenda)
      (report-summary-plain! data agenda)))

(define (report-summary-with-colors! data agenda)
  (let ((failure-count (report-agenda-failed agenda)))
    (unless (zero? failure-count)
      (newline)
      (newline)
      (if (= failure-count 1)
          (fmt #t (fmt-red (fmt-bold "There has been 1 failure ")))
          (fmt #t (fmt-red (cat (fmt-bold (cat "There have been " (report-agenda-failed agenda) " failures"))))))
      (newline)
      (newline)
      (report-failures (report-agenda-failed-examples agenda))))
  (newline)
  (newline)
  (fmt #t (cat
           (fmt-bold
            (cat
             "Total: "
             (+ (report-agenda-failed agenda)
                (report-agenda-pending agenda)
                (report-agenda-successful agenda))))
           " "
           (fmt-green (fmt-bold
                       (cat
                        "Successful: "
                        (report-agenda-successful agenda)
                        " ")))
           (fmt-yellow (fmt-bold
                        (cat
                         "Pending: "
                         (report-agenda-pending agenda)
                         " ")))
           (fmt-red (fmt-bold
                     (cat
                      "Failures: "
                      (report-agenda-failed agenda)
                      " ")))))
  (newline)
  (zero? (report-agenda-failed agenda)))

(define (report-summary-plain! data agenda)
  (let ((failure-count (report-agenda-failed agenda)))
    (unless (zero? failure-count)
      (newline)
      (newline)
      (if (= failure-count 1)
          (printf "There has been 1 failure~%")
          (printf "There have been ~A failures ~%" (report-agenda-failed agenda)))
      (newline))
    (report-failures (report-agenda-failed-examples agenda)))
  (newline)
  (newline)
  (printf "Total: ~A Successful: ~A Pending: ~A Failures: ~A"
    (+ (report-agenda-failed agenda)
       (report-agenda-pending agenda)
       (report-agenda-successful agenda))
    (report-agenda-successful agenda)
    (report-agenda-pending agenda)
    (report-agenda-failed agenda))
  (newline)
  (zero? (report-agenda-failed agenda)))

(define (report-example status result agenda #!optional (failure-index #f))
  (if use-colors
      (report-example-colors status result agenda failure-index)
      (report-example-plain status result agenda failure-index)))

(define (indent n str)
  (let ((indention (make-string n #\space)))
    (display indention)
    (display (irregex-replace/all "\n" str (string-append "\n" indention)))))

(define (report-example-colors status result agenda #!optional (failure-index #f))
  (let ((example (example-result-example result))
        (ifactor (report-agenda-indentation-factor agenda)))
    
    (if (or (not ifactor) (zero? ifactor))
        (set! ifactor 1))
    
    (case status
      ((success)
       (indent (* ifactor 3) (fmt #f (fmt-green (cat "It " (example-description example)))))
       (newline))
      ((pending)
       (indent (* ifactor 3) (fmt #f (fmt-yellow (cat "[P] It " (example-description example)))))
       (newline))
      (else
       (indent (* ifactor 3) (fmt #f (fmt-red (cat "[F][" (number->string failure-index) "] It " (example-description example) ))))
       (newline)
       (indent (* ifactor 4) (fmt #f (fmt-red (example-result-messages result))))
       (newline)))))

(define (report-example-plain status result agenda #!optional (failure-index #f))
  (let ((example (example-result-example result))
        (ifactor (report-agenda-indentation-factor agenda)))
    (case status
      ((success)
       (indent (* ifactor 3) (fmt #f (cat "It " (example-description example))))
       (newline))
      ((pending)
       (indent (* ifactor 3) (fmt #f (cat "[P] It " (example-description example))))
       (newline))
      (else
       (indent (* ifactor 3) (fmt #f (cat "[F][" (number->string failure-index) "] It " (example-description example))))
       (newline)
       (indent (* ifactor 4) (fmt #f (example-result-messages result)))
       (newline)))))


(define (report-failures failures)
  (for-each
   (if use-colors report-failure-colors report-failure-plain)
   (reverse failures)))

(define (report-failure-plain failure)
  (let* ((result (failure-result failure))
         (file (example-result-spec-file result))
         (example (example-result-example result)))

    (printf "~A) in ~A~%" (failure-index failure) (relativize-path file))
    (printf "[F][~A] It ~A~%" (failure-index failure) (example-description example))
    (printf "~A~%" (example-result-messages result))
    (newline)))

(define (report-failure-colors failure)
  (let* ((result (failure-result failure))
         (file (example-result-spec-file result))
         (example (example-result-example result)))

    (fmt #t (fmt-bold (fmt-red (cat (failure-index failure) ") in " (relativize-path file)))))
    (newline)
    (indent 2 (fmt #f (fmt-red (cat "[F][" (number->string (failure-index failure)) "] It " (example-description example)))))
    (newline)
    (indent 4 (fmt #f (fmt-red (example-result-messages result))))
    (newline)
    (newline)))

(define (agenda-increment-successful! agenda)
  (report-agenda-successful-set! agenda (+ 1 (report-agenda-successful agenda))))

(define (agenda-increment-failed! agenda)
  (report-agenda-failed-set! agenda (+ 1 (report-agenda-failed agenda))))

(define (agenda-increment-pending! agenda)
  (report-agenda-pending-set! agenda (+ 1 (report-agenda-pending agenda))))

(define (run-files files #!optional (include-filter #f) (exclude-filter #f))
  (run-specification
   (call-with-specification
    (make-empty-specification)
    (lambda () (for-each eval-spec-file files)))
   include: include-filter
   exclude: exclude-filter
   reporter: (make-pretty-reporter)))

(define (eval-spec-file file)
  (let ((content (read-file file)))
    (unless (null? content)
      (eval (decorate-content content file)))))

(define (decorate-content content file)
  `(begin
     (use missbehave)
     (parameterize ((current-spec-file ,file))
       ,@content)))

(define (absolutize-path path)
  (let ((cwd (current-directory)))
    (if (absolute-pathname? path)
        (normalize-pathname path)
        (normalize-pathname (conc cwd "/" path)))))

(define (relativize-path path)
  (pathname-strip-directory (string-substitute (regexp-escape (current-directory)) "" path)))

(define (extract-tags options)
  (fold (lambda (element tags)
          (if (eq? (car element) 't)
              (cons  (string-split (cdr element) ":") tags)
              tags))
        '()
        options))

(define (create-include-filter tags)
  (fold (lambda (tag filter)
          (let ((label (string-translate* (car tag) '(("@" . "")))))
            (if (not (equal? "~" (string-take label 1)))
                (if (= 1 (length tag))
                    (cons (list (string->symbol label)) filter)
                    (cons (list (string->symbol label) (string->symbol (cadr tag))) filter))
                filter)))
        '()
        tags))

(define (create-exclude-filter tags)
  (fold (lambda (tag filter)
          (let ((label (string-translate* (car tag) '(("@" . "")))))
            (if (equal? "~" (string-take label 1))
                (if (= 1 (length tag))
                    (cons (list (string->symbol (string-drop label 1))) filter)
                    (cons (list (string->symbol (string-drop label 1)) (string->symbol (cadr tag))) filter))
                filter)))
        '()
        tags))


(receive (options files) (args:parse (command-line-arguments) command-line-options)
  (let* ((tags (extract-tags options))
         (include-filter (create-include-filter tags))
         (exclude-filter (create-exclude-filter tags))
         (files-absolute (map absolutize-path files)))
    
    (for-each (lambda (file)
                (unless (file-exists? file)
                  (fprintf (current-error-port) "The spec-file ~A does not exist~%" file)
                  (exit 1)))
              files-absolute)
    
    (if (run-files
         files-absolute
         (if (null? include-filter) #f include-filter)
         (if (null? exclude-filter) #f exclude-filter))
        (exit 0)
        (exit 2))))
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.