123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- ;; TODO: remove this load path stuff
- (add-to-load-path (dirname (current-filename)))
- (define-module (grid-printer)
- #:version (0 2 0)
- #:export (<grid-config>
- make-grid-config))
- (use-modules (helpers string-helpers)
- (helpers list-helpers)
- (srfi srfi-9 gnu))
- ;; =======
- ;; HELPERS
- ;; =======
- (define-public (find-longest-string-length* lst)
- (find-longest* lst string-length))
- (define identity
- (λ (sth) sth))
- ;; ===
- ;; LIB
- ;; ===
- (define-immutable-record-type <grid-config>
- ;; define constructor
- (make-grid-config col-sep row-sep intersection empty
- col-pad row-pad
- pad-direction)
- ;; define predicate
- grid-config?
- ;; define accessors and functional setters
- (col-sep get-col-sep set-col-sep)
- (row-sep get-row-sep set-row-sep)
- (intersection get-intersection set-intersection)
- (empty get-empty set-empty)
- (col-pad get-col-pad set-col-pad)
- (row-pad get-row-pad set-row-pad)
- (pad-direction get-pad-direction set-pad-direction))
- (define-public default-grid-config
- (make-grid-config
- #|col-sep|#
- "|"
- #|row-sep|#
- "-"
- #|intersection|#
- "+"
- #|empty|#
- " "
- #|col-pad|#
- 1
- #|row-pad|#
- 1
- #|pad-direction|#
- 'left))
- (define-public print-segmented-line
- (lambda* (seg-count seg-no-borders-width seg-filling seg-border #:optional (port (current-output-port)))
- "Print a line of characters, which is divided into segments. The separators
- used to divide the line are given as arguments."
- (let loop ([segs-remaining seg-count])
- (cond [(> segs-remaining 0)
- (display seg-border port)
- (display (string-repeat seg-filling seg-no-borders-width) port)
- (loop (- segs-remaining 1))]
- [else (display seg-border port)
- (display "\n" port)]))))
- (define-public col-content-width
- (lambda (data-part-width grid-config)
- "Calculate the width of a column (or the column including all padding and
- other contained content) given a grid configuration and the width of the content
- in the column."
- ;; One padding unit could be multiple characters wide, if the string for
- ;; empty or the string for row separator consists of multiple characters.
- (define width-of-one-padding (max (string-length (get-empty grid-config))
- (string-length (get-row-sep grid-config))))
- (define padding-one-side (* (get-col-pad grid-config) width-of-one-padding))
- (define padding-total (* 2 padding-one-side))
- (+ padding-total data-part-width)))
- (define-public print-empty-line
- (lambda* (fields# data-part-width grid-config #:optional (port (current-output-port)))
- "Print a line of characters, which in terms of the grid's content is
- considered empty."
- (print-segmented-line fields#
- (col-content-width data-part-width grid-config)
- (get-empty grid-config)
- (get-col-sep grid-config)
- port)))
- (define-public print-content-line
- (lambda* (min-field-count field-contents data-part-width grid-config
- #:optional (port (current-output-port)))
- "Print a line of characters, which contains content of the grid."
- (cond
- [(null? field-contents)
- (print-empty-line min-field-count data-part-width grid-config port)]
- [else
- (let loop ([count min-field-count] [contents field-contents])
- (let ([col-content
- (string-join
- (list
- (string-repeat (get-empty grid-config)
- (get-col-pad grid-config))
- (string-padding (if (null? contents)
- (get-empty grid-config)
- (car contents))
- data-part-width
- (get-empty grid-config)
- #:padding-direction (get-pad-direction grid-config))
- (string-repeat (get-empty grid-config) (get-col-pad grid-config)))
- "")])
- (display (get-col-sep grid-config) port)
- (display col-content port))
- (if (> count 1)
- (loop (- count 1)
- (if (null? contents) '() (cdr contents)))
- (display (get-col-sep grid-config) port)))])
- (display "\n" port)))
- (define-public print-separating-line
- (lambda* (fields# data-part-width grid-config
- #:optional (port (current-output-port)))
- "Print a line of characters, which acts as separator between rows."
- (print-segmented-line fields#
- (col-content-width data-part-width grid-config)
- (get-row-sep grid-config)
- (get-intersection grid-config)
- port)))
- ;; alias for better readability
- (define-public output-padding-line print-empty-line)
- (define-public get-nth-cell-parts
- (lambda (cells cell-parts-ref)
- "Get the nth part of each cell in a given list of cells. "
- ;; When cells consist of lists of strings, those strings are supposed to be
- ;; printed on separate lines. When printing to a port, we have to print line
- ;; by line. In consequence we need to first print all first parts, the first
- ;; strings in those lists of strings, then the second parts and so on. For
- ;; that purpose of getting all nth parts of the cells in a single list
- ;; `get-nth-cell-parts` is defined here.
- (map (λ (cell)
- (list-ref cell cell-parts-ref))
- cells)))
- (define-public equalize-lines-count
- (lambda (cells fill-elem)
- "Fill cells with parts so that each cell has the same number of lines."
- (let ([desired-len (longest-sublist-length cells)])
- (map (λ (cell-parts)
- (stretch-list cell-parts desired-len fill-elem))
- cells))))
- ;; A grid row are one or more lines of text which are printed between separation
- ;; lines.
- (define-public print-grid-row
- (lambda* (row-data fields# data-part-width grid-config #:optional port)
- "Print a row of the grid."
- (define content-lines# (longest-sublist-length row-data))
- (define (iter-padding n)
- (cond [(> n 0)
- (output-padding-line fields#
- data-part-width
- grid-config
- port)
- (iter-padding (- n 1))]
- [else (display "" port)]))
- (define (iter-content cells)
- (let loop ([cell-parts-ref 0])
- (cond
- [(= cell-parts-ref content-lines#)
- (display "" port)]
- [else
- (let ([nth-cell-parts (get-nth-cell-parts cells cell-parts-ref)])
- (print-content-line fields#
- nth-cell-parts
- data-part-width
- grid-config port))
- (loop (+ cell-parts-ref 1))])))
- ;; Print the padding above the content.
- (iter-padding (get-row-pad grid-config))
- ;; Print the content in possibly arbitrary number of lines,
- ;; depending on cell-value-split-proc.
- (iter-content (equalize-lines-count row-data (get-empty grid-config)))
- ;; Print the padding below the content.
- (iter-padding (get-row-pad grid-config))))
- (define-public print-grid
- (lambda* (data
- #:optional (port (current-output-port))
- #:key (grid-config default-grid-config))
- "Print a grid."
- "The argument data is expected to be a list of rows, of which each is a list
- of cells of which each is a list of strings or a simple string."
- "print-grid requires the input to be 2-dimensional or 3-dimensional."
- "print-grid requires the input to be nested list of strings."
- (define fields# (longest-sublist-length data))
- (define longest-string-length (find-longest-string-length* data))
- (define (iter data)
- (cond
- [(null? data) (display "" port)]
- [else
- ;; print initial separating line, outer top border
- (print-separating-line fields# longest-string-length grid-config port)
- ;; Print one row of content. This could result in multiple
- ;; content containing lines, depending on what
- ;; cell-value-split-proc does.
- (print-grid-row (car data) fields# longest-string-length grid-config port)
- ;; Continue with the next data point.
- (iter (cdr data))]))
- (cond
- [(member (dimendionality data) '(2 3))
- (let ([fields# (longest-sublist-length data)])
- (iter data)
- ;; final separating line, outer bottom border
- (print-separating-line fields# longest-string-length grid-config port))]
- [else
- (error "data dimendionality is not 1 or 2")])))
|