123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- (library (commands cut)
- (export cut)
- (import (except (rnrs base) error map)
- (only (guile)
- lambda* λ
- ;; control flow
- when
- unless
- ;; ports
- current-input-port
- current-output-port
- current-error-port
- with-input-from-port
- with-output-to-port
- with-error-to-port
- call-with-input-file
- eof-object?
- ;; string formatting
- simple-format
- ;; basic shell procedures guile provides
- getcwd
- chdir
- ;; strings
- string-split
- peek)
- (ice-9 exceptions)
- ;; pipes
- ;; (ice-9 popen)
- ;; (ice-9 textual-ports)
- ;; (ice-9 binary-ports)
- ;; ftw stands for file-tree-walk
- ;; for file-system-tree
- (ice-9 ftw)
- ;; for match-lambda
- (ice-9 match)
- ;; lists
- (srfi srfi-1)
- ;; receive form
- (srfi srfi-8)
- ;; strings
- (srfi srfi-13)
- (file)
- (alias)
- (list-helpers)
- (string-helpers)
- (alist-helpers)
- (shell-state)
- (exceptions)
- (commands utils))
- ;; TODO cleanup imports
- (define cut-single-line
- (λ (line fields delimiter output-delimiter)
- (let ([parts (string-split line (λ (c) (char=? c delimiter)))])
- (define cut-with-fields-list
- (λ ()
- (let ([selected-parts
- (take-indices parts
- (map (λ (field) (- field 1))
- (unique fields #:eq-test = #:less <)))])
- (string-join selected-parts output-delimiter))))
- (define cut-with-fields-range
- (λ ()
- (string-join (take-range parts
- (- (car fields) 1)
- (- (cdr fields) 1))
- output-delimiter)))
- (define cut-with-single-field
- (λ ()
- (let ([index (- fields 1)])
- ;; IDEA: Handle ranges with too high upper limit without error -- maybe.
- (guard (con [(eq? (exception-kind con) 'out-of-range)
- (raise-exception
- (make-exception
- (make-error)
- (make-exception-with-message
- "passed out of bounds field number to cut")
- (make-exception-with-irritants (list parts index))
- (make-exception-with-origin 'cut)))])
- (let ([result (list-ref parts index)])
- result)))))
- (cond
- [(list? fields)
- (cut-with-fields-list)]
- [(pair? fields)
- (cut-with-fields-range)]
- [(and (integer? fields) (positive? fields))
- (cut-with-single-field)]
- [else
- (raise-exception
- (make-exception
- (make-non-continuable-error)
- (make-exception-with-message "fields arguments not a list, pair, or integer")
- (make-exception-with-irritants (list fields))
- (make-exception-with-origin 'cut)))]))))
- (define cut
- (lambda* (fields
- #:optional
- (filename #f)
- #:key
- (delimiter "\t")
- (output-delimiter #f)
- ;; TODO: Implement taking the complement of fields. For
- ;; this we would need to know how many parts there are.
- (complement #f)
- ;; command interface
- (previous-result '())
- (shell-state default-shell-state)
- (silent #f))
- "Cuts INPUT into parts and selects the parts specified via
- FIELDS. FIELDS can be a list of numbers or a pair of
- numbers, representing a range."
- (let ([actual-output-delimiter
- (if output-delimiter output-delimiter (char->string delimiter))])
- (cond
- ;; read from input file
- [filename
- (values (call-with-input-file filename
- (λ (port)
- (process-input-port port
- (λ (line)
- (cut-single-line line
- fields
- delimiter
- actual-output-delimiter)))))
- shell-state)]
- ;; read from input port
- [(null? previous-result)
- ;; TODO: should this case exist?
- (values (let ([port (current-input-port)])
- (process-input-port port
- (λ (line)
- (cut-single-line line
- fields
- delimiter
- actual-output-delimiter))))
- shell-state)]
- ;; use previous result
- [else
- (values
- (map (λ (line)
- (cut-single-line line
- fields
- delimiter
- actual-output-delimiter))
- previous-result)
- shell-state)])))))
- ;; TODO: idea: allow list of delimiters
|