shell.scm 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. (library (shell)
  2. (export call
  3. cd
  4. cut
  5. list-dir
  6. ls
  7. pwd
  8. run-command
  9. shell)
  10. (import (except (rnrs base) error)
  11. (only (guile)
  12. lambda* λ
  13. ;; control flow
  14. when
  15. unless
  16. ;; ports
  17. current-output-port
  18. current-error-port
  19. with-output-to-port
  20. with-error-to-port
  21. close-port
  22. pipe
  23. ;; other
  24. setvbuf
  25. eof-object?
  26. ;; string formatting
  27. simple-format
  28. ;; basic shell procedures guile provides
  29. getcwd
  30. chdir
  31. ;; other
  32. error
  33. ;; strings
  34. string-split
  35. )
  36. (ice-9 exceptions)
  37. ;; pipes
  38. (ice-9 popen)
  39. (ice-9 textual-ports)
  40. (ice-9 binary-ports)
  41. (ice-9 receive)
  42. ;; ftw stands for file-tree-walk
  43. ;; for file-system-tree
  44. (ice-9 ftw)
  45. ;; for match-lambda
  46. (ice-9 match)
  47. ;; let-values
  48. (srfi srfi-11)
  49. ;; strings
  50. (srfi srfi-13)
  51. (prefix (file) file:)
  52. (alias)
  53. (list-helpers)
  54. (string-helpers))
  55. (define identity (λ (any) any))
  56. (define read-from-write-to
  57. (lambda* (in-port out-port #:key (bytes-count 1024))
  58. "Read from an IN-PORT and write to OUT-PORT, BYTES-COUNT
  59. bytes at a time."
  60. (let loop ([bv (get-bytevector-n in-port bytes-count)])
  61. (unless (eof-object? bv)
  62. (put-bytevector out-port bv)
  63. (loop (get-bytevector-n in-port bytes-count))))))
  64. (define run-command
  65. (lambda* (cmd
  66. #:key
  67. (cmd-out-port (current-output-port))
  68. (err-out-port (current-error-port)))
  69. "Allow the user to give output port and error port to the
  70. function."
  71. (with-output-to-port cmd-out-port
  72. (λ ()
  73. (with-error-to-port err-out-port
  74. (λ ()
  75. (let* (;; Run the actual command. If an error
  76. ;; happens, it should write to the
  77. ;; err-write port. Output of the command
  78. ;; should be written to an output port,
  79. ;; which corresponds to the input-port,
  80. ;; which is returned by open-input-pipe.
  81. [in-port (open-input-pipe cmd)]
  82. ;; Read in block mode.
  83. [_ignored (setvbuf in-port 'block)])
  84. ;; Write to caller given command output port.
  85. (read-from-write-to in-port cmd-out-port)
  86. ;; Get the exit code of the command.
  87. (close-pipe in-port))))))))
  88. (define shell
  89. (lambda* (command)
  90. "Run a shell COMMAND. Return 3 values: (1) exit code, (2)
  91. command output, (3) error output."
  92. ;; Construct pairs of input and outout ports using
  93. ;; `pipe'. Whatever is written to the output port can
  94. ;; be read from the input port.
  95. (match-let ([(cmd-in . cmd-out) (pipe)]
  96. [(err-in . err-out) (pipe)])
  97. (let ([exit-code
  98. (run-command command
  99. ;; Write command output to the
  100. ;; out port, so that it can be
  101. ;; read from in port.
  102. #:cmd-out-port cmd-out
  103. ;; Write error output to the
  104. ;; error out port, so that it
  105. ;; can be read from the error in
  106. ;; port.
  107. #:err-out-port err-out)])
  108. ;; Do not forget to close the out port and error
  109. ;; out port.
  110. (close-port cmd-out)
  111. (close-port err-out)
  112. ;; Read the (error) output of the command and
  113. ;; return it.
  114. (let ([output-message (get-string-all cmd-in)]
  115. [error-message (get-string-all err-in)])
  116. (values exit-code
  117. output-message
  118. error-message))))))
  119. (define call
  120. (lambda* (command
  121. #:key
  122. (display-exit-code #f)
  123. (exit-code-formatter
  124. (λ (exit-code) (string-append (number->string exit-code) "\n")))
  125. (cmd-out-formatter identity)
  126. (err-out-formatter identity))
  127. "Like shell, but displays the results of running the shell
  128. COMMAND, instead of returning them. How output is displayed
  129. can be optionally specified via keyword arguments
  130. EXIT-CODE-FORMATTER, CMD-OUT-FORMATTER,
  131. ERR-OUT-FORMATTER. The keyword argument DISPLAY-EXIT-CODE is
  132. a flag that enables or disables display of the exit code."
  133. (let-values ([(exit-code cmd-output err-output) (shell command)])
  134. (when display-exit-code
  135. (simple-format #t "~a" (exit-code-formatter exit-code)))
  136. (simple-format #t "~a" (cmd-out-formatter cmd-output))
  137. (simple-format #t "~a" (err-out-formatter err-output)))))
  138. ;; EXAMPLE CALLS:
  139. #;(with-output-to-file "test-output.log"
  140. (λ ()
  141. (call "ls -al" #:display-exit-code #t)))
  142. #;(with-output-to-file "test-output.log"
  143. (λ ()
  144. (with-input-from-file "test-input.log"
  145. (λ ()
  146. (call "cut -d ' ' -f 1-2" #:display-exit-code #t)))))
  147. ;; IDEA: Write a function which works like this: (direct function #:in #:out)
  148. (define pwd
  149. (lambda* (#:key (output-port (current-output-port)))
  150. "Return the current working directory."
  151. (getcwd)))
  152. (define cut
  153. (lambda* (input fields
  154. #:key
  155. (delimiter "\t")
  156. (output-delimiter #f)
  157. (complement #f))
  158. "Cuts INPUT into parts and selects the parts specified via
  159. FIELDS. FIELDS can be a list of numbers or a pair of
  160. numbers, representing a range."
  161. (let ([parts (string-split input (λ (c) (char=? c delimiter)))]
  162. [actual-output-delimiter (if output-delimiter
  163. output-delimiter
  164. (char->string delimiter))])
  165. (cond
  166. [(list? fields)
  167. (string-join (take-indices parts
  168. (map (λ (field) (- field 1))
  169. (unique fields #:eq-test = #:less <)))
  170. actual-output-delimiter)]
  171. [(pair? fields)
  172. (string-join (take-range parts
  173. (- (car fields) 1)
  174. (- (cdr fields) 1))
  175. actual-output-delimiter)]
  176. [(and (integer? fields)
  177. (positive? fields))
  178. (list-ref parts fields)]
  179. [else
  180. (raise-exception
  181. (make-exception
  182. (make-non-continuable-error)
  183. (make-exception-with-message "fields arguments not alist or a pair")
  184. (make-exception-with-irritants (list fields))
  185. (make-exception-with-origin 'cut)))]))))
  186. ;; EXAMPLE CALLS
  187. ;; (cut (pwd) '(1 . 3) #:delimiter #\/)
  188. ;; (cut (pwd) '(1 2 3) #:delimiter #\/)
  189. ;; (cut (pwd) '(1 2) #:delimiter #\/)
  190. ;; (cut (pwd) 2 #:delimiter #\/)
  191. (define cd
  192. (λ (filename)
  193. (chdir filename)))
  194. (define format-ls-flat-file-entry
  195. (λ (entry)
  196. (match entry
  197. [(name stat)
  198. (simple-format #f "~a\n" name)]
  199. [dir
  200. (error "called format-ls-flat-file-entry with dir")])))
  201. (define ls
  202. (lambda* (#:key
  203. (filename (pwd #:print #f))
  204. (output-port (current-output-port)))
  205. (let ([fs-tree (file-system-tree filename)])
  206. (match fs-tree
  207. ;; directory
  208. [(name stat children ...)
  209. (for-each (λ (formatted-entry)
  210. (simple-format output-port "~a" formatted-entry))
  211. (map format-ls-flat-file-entry
  212. children))]
  213. ;; flat file
  214. [flat-file
  215. (format-ls-flat-file-entry flat-file)]))))
  216. ;; just an alias
  217. (alias list-dir ls))