cut.scm 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. (library (commands cut)
  2. (export cut)
  3. (import (except (rnrs base) error map)
  4. (only (guile)
  5. lambda* λ
  6. ;; control flow
  7. when
  8. unless
  9. ;; ports
  10. current-input-port
  11. current-output-port
  12. current-error-port
  13. with-input-from-port
  14. with-output-to-port
  15. with-error-to-port
  16. call-with-input-file
  17. eof-object?
  18. ;; string formatting
  19. simple-format
  20. ;; basic shell procedures guile provides
  21. getcwd
  22. chdir
  23. ;; strings
  24. string-split
  25. peek)
  26. (ice-9 exceptions)
  27. ;; pipes
  28. ;; (ice-9 popen)
  29. ;; (ice-9 textual-ports)
  30. ;; (ice-9 binary-ports)
  31. ;; ftw stands for file-tree-walk
  32. ;; for file-system-tree
  33. (ice-9 ftw)
  34. ;; for match-lambda
  35. (ice-9 match)
  36. ;; lists
  37. (srfi srfi-1)
  38. ;; receive form
  39. (srfi srfi-8)
  40. ;; strings
  41. (srfi srfi-13)
  42. (file)
  43. (alias)
  44. (list-helpers)
  45. (string-helpers)
  46. (alist-helpers)
  47. (shell-state)
  48. (exceptions)
  49. (commands utils))
  50. ;; TODO cleanup imports
  51. (define cut-single-line
  52. (λ (line fields delimiter output-delimiter)
  53. (let ([parts (string-split line (λ (c) (char=? c delimiter)))])
  54. (define cut-with-fields-list
  55. (λ ()
  56. (let ([selected-parts
  57. (take-indices parts
  58. (map (λ (field) (- field 1))
  59. (unique fields #:eq-test = #:less <)))])
  60. (string-join selected-parts output-delimiter))))
  61. (define cut-with-fields-range
  62. (λ ()
  63. (string-join (take-range parts
  64. (- (car fields) 1)
  65. (- (cdr fields) 1))
  66. output-delimiter)))
  67. (define cut-with-single-field
  68. (λ ()
  69. (let ([index (- fields 1)])
  70. ;; IDEA: Handle ranges with too high upper limit without error -- maybe.
  71. (guard (con [(eq? (exception-kind con) 'out-of-range)
  72. (raise-exception
  73. (make-exception
  74. (make-error)
  75. (make-exception-with-message
  76. "passed out of bounds field number to cut")
  77. (make-exception-with-irritants (list parts index))
  78. (make-exception-with-origin 'cut)))])
  79. (let ([result (list-ref parts index)])
  80. result)))))
  81. (cond
  82. [(list? fields)
  83. (cut-with-fields-list)]
  84. [(pair? fields)
  85. (cut-with-fields-range)]
  86. [(and (integer? fields) (positive? fields))
  87. (cut-with-single-field)]
  88. [else
  89. (raise-exception
  90. (make-exception
  91. (make-non-continuable-error)
  92. (make-exception-with-message "fields arguments not a list, pair, or integer")
  93. (make-exception-with-irritants (list fields))
  94. (make-exception-with-origin 'cut)))]))))
  95. (define cut
  96. (lambda* (fields
  97. #:optional
  98. (filename #f)
  99. #:key
  100. (delimiter "\t")
  101. (output-delimiter #f)
  102. ;; TODO: Implement taking the complement of fields. For
  103. ;; this we would need to know how many parts there are.
  104. (complement #f)
  105. ;; command interface
  106. (previous-result '())
  107. (shell-state default-shell-state)
  108. (silent #f))
  109. "Cuts INPUT into parts and selects the parts specified via
  110. FIELDS. FIELDS can be a list of numbers or a pair of
  111. numbers, representing a range."
  112. (let ([actual-output-delimiter
  113. (if output-delimiter output-delimiter (char->string delimiter))])
  114. (cond
  115. ;; read from input file
  116. [filename
  117. (values (call-with-input-file filename
  118. (λ (port)
  119. (process-input-port port
  120. (λ (line)
  121. (cut-single-line line
  122. fields
  123. delimiter
  124. actual-output-delimiter)))))
  125. shell-state)]
  126. ;; read from input port
  127. [(null? previous-result)
  128. ;; TODO: should this case exist?
  129. (values (let ([port (current-input-port)])
  130. (process-input-port port
  131. (λ (line)
  132. (cut-single-line line
  133. fields
  134. delimiter
  135. actual-output-delimiter))))
  136. shell-state)]
  137. ;; use previous result
  138. [else
  139. (values
  140. (map (λ (line)
  141. (cut-single-line line
  142. fields
  143. delimiter
  144. actual-output-delimiter))
  145. previous-result)
  146. shell-state)])))))
  147. ;; TODO: idea: allow list of delimiters