bournish.scm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2016, 2017 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2016 Efraim Flashner <efraim@flashner.co.il>
  4. ;;; Copyright © 2017 Ricardo Wurmus <rekado@elephly.net>
  5. ;;;
  6. ;;; This file is part of GNU Guix.
  7. ;;;
  8. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  9. ;;; under the terms of the GNU General Public License as published by
  10. ;;; the Free Software Foundation; either version 3 of the License, or (at
  11. ;;; your option) any later version.
  12. ;;;
  13. ;;; GNU Guix is distributed in the hope that it will be useful, but
  14. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  15. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. ;;; GNU General Public License for more details.
  17. ;;;
  18. ;;; You should have received a copy of the GNU General Public License
  19. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  20. (define-module (guix build bournish)
  21. #:use-module (system base language)
  22. #:use-module (system base compile)
  23. #:use-module (system repl command)
  24. #:use-module (system repl common)
  25. #:use-module (ice-9 rdelim)
  26. #:use-module (ice-9 match)
  27. #:use-module (ice-9 ftw)
  28. #:use-module (srfi srfi-1)
  29. #:use-module (srfi srfi-11)
  30. #:use-module (srfi srfi-26)
  31. #:export (%bournish-language))
  32. ;;; Commentary:
  33. ;;;
  34. ;;; This is a super minimal Bourne-like shell language for Guile. It is meant
  35. ;;; to be used at the REPL as a rescue shell. In a way, this is to Guile what
  36. ;;; eshell is to Emacs.
  37. ;;;
  38. ;;; Code:
  39. (define (expand-variable str)
  40. "Return STR or code to obtain the value of the environment variable STR
  41. refers to."
  42. ;; XXX: No support for "${VAR}".
  43. (if (string-prefix? "$" str)
  44. `(or (getenv ,(string-drop str 1)) "")
  45. str))
  46. (define* (display-tabulated lst
  47. #:key
  48. (terminal-width 80)
  49. (column-gap 2))
  50. "Display the list of string LST in as many columns as needed given
  51. TERMINAL-WIDTH. Use COLUMN-GAP spaces between two subsequent columns."
  52. (define len (length lst))
  53. (define column-width
  54. ;; The width of a column. Assume all the columns have the same width
  55. ;; (GNU ls is smarter than that.)
  56. (+ column-gap (reduce max 0 (map string-length lst))))
  57. (define columns
  58. (max 1
  59. (quotient terminal-width column-width)))
  60. (define pad
  61. (if (zero? (modulo len columns))
  62. 0
  63. columns))
  64. (define items-per-column
  65. (quotient (+ len pad) columns))
  66. (define items (list->vector lst))
  67. (let loop ((indexes (unfold (cut >= <> columns)
  68. (cut * <> items-per-column)
  69. 1+
  70. 0)))
  71. (unless (>= (first indexes) items-per-column)
  72. (for-each (lambda (index)
  73. (let ((item (if (< index len)
  74. (vector-ref items index)
  75. "")))
  76. (display (string-pad-right item column-width))))
  77. indexes)
  78. (newline)
  79. (loop (map 1+ indexes)))))
  80. (define ls-command-implementation
  81. ;; Run-time support procedure.
  82. (case-lambda
  83. (()
  84. (display-tabulated (scandir ".")))
  85. (files
  86. (let ((files (append-map (lambda (file)
  87. (catch 'system-error
  88. (lambda ()
  89. (match (stat:type (lstat file))
  90. ('directory
  91. ;; Like GNU ls, list the contents of
  92. ;; FILE rather than FILE itself.
  93. (match (scandir file
  94. (match-lambda
  95. ((or "." "..") #f)
  96. (_ #t)))
  97. (#f
  98. (list file))
  99. ((files ...)
  100. (map (cut string-append file "/" <>)
  101. files))))
  102. (_
  103. (list file))))
  104. (lambda args
  105. (let ((errno (system-error-errno args)))
  106. (format (current-error-port) "~a: ~a~%"
  107. file (strerror errno))
  108. '()))))
  109. files)))
  110. (display-tabulated files)))))
  111. (define (ls-command . files)
  112. `((@@ (guix build bournish) ls-command-implementation) ,@files))
  113. (define (which-command program)
  114. `(search-path ((@@ (guix build bournish) executable-path))
  115. ,program))
  116. (define (cat-command file)
  117. `(call-with-input-file ,file
  118. (lambda (port)
  119. ((@ (guix build utils) dump-port) port (current-output-port))
  120. *unspecified*)))
  121. (define (rm-command . args)
  122. "Emit code for the 'rm' command."
  123. (cond ((member "-r" args)
  124. `(for-each (@ (guix build utils) delete-file-recursively)
  125. (list ,@(delete "-r" args))))
  126. (else
  127. `(for-each delete-file (list ,@args)))))
  128. (define (lines+chars port)
  129. "Return the number of lines and number of chars read from PORT."
  130. (let loop ((lines 0) (chars 0))
  131. (match (read-char port)
  132. ((? eof-object?) ;done!
  133. (values lines chars))
  134. (#\newline ;recurse
  135. (loop (1+ lines) (1+ chars)))
  136. (_ ;recurse
  137. (loop lines (1+ chars))))))
  138. (define (file-exists?* file)
  139. "Like 'file-exists?' but emits a warning if FILE is not accessible."
  140. (catch 'system-error
  141. (lambda ()
  142. (stat file))
  143. (lambda args
  144. (let ((errno (system-error-errno args)))
  145. (format (current-error-port) "~a: ~a~%"
  146. file (strerror errno))
  147. #f))))
  148. (define (wc-print file)
  149. (let-values (((lines chars)
  150. (call-with-input-file file lines+chars)))
  151. (format #t "~a ~a ~a~%" lines chars file)))
  152. (define (wc-l-print file)
  153. (let-values (((lines chars)
  154. (call-with-input-file file lines+chars)))
  155. (format #t "~a ~a~%" lines file)))
  156. (define (wc-c-print file)
  157. (let-values (((lines chars)
  158. (call-with-input-file file lines+chars)))
  159. (format #t "~a ~a~%" chars file)))
  160. (define (wc-command-implementation . files)
  161. (for-each wc-print (filter file-exists?* files)))
  162. (define (wc-l-command-implementation . files)
  163. (for-each wc-l-print (filter file-exists?* files)))
  164. (define (wc-c-command-implementation . files)
  165. (for-each wc-c-print (filter file-exists?* files)))
  166. (define (wc-command . args)
  167. "Emit code for the 'wc' command."
  168. (cond ((member "-l" args)
  169. `((@@ (guix build bournish) wc-l-command-implementation)
  170. ,@(delete "-l" args)))
  171. ((member "-c" args)
  172. `((@@ (guix build bournish) wc-c-command-implementation)
  173. ,@(delete "-c" args)))
  174. (else
  175. `((@@ (guix build bournish) wc-command-implementation) ,@args))))
  176. (define (reboot-command . args)
  177. "Emit code for 'reboot'."
  178. ;; Normally Bournish is used in the initrd, where 'reboot' is provided
  179. ;; directly by (guile-user). In other cases, just bail out.
  180. `(if (defined? 'reboot)
  181. (reboot)
  182. (begin
  183. (format (current-error-port)
  184. "I don't know how to reboot, sorry about that!~%")
  185. #f)))
  186. (define (help-command . _)
  187. (display "\
  188. Hello, this is Bournish, a minimal Bourne-like shell in Guile!
  189. The shell is good enough to navigate the file system and run commands but not
  190. much beyond that. It is meant to be used as a rescue shell in the initial RAM
  191. disk and is probably not very useful apart from that. It has a few built-in
  192. commands such as 'ls' and 'cd'; it lacks globbing, pipes---everything.\n"))
  193. (define %not-colon (char-set-complement (char-set #\:)))
  194. (define (executable-path)
  195. "Return the search path for programs as a list."
  196. (match (getenv "PATH")
  197. (#f '())
  198. (str (string-tokenize str %not-colon))))
  199. (define %commands
  200. ;; Built-in commands.
  201. `(("echo" ,(lambda strings `(list ,@strings)))
  202. ("cd" ,(lambda (dir) `(chdir ,dir)))
  203. ("pwd" ,(lambda () `(getcwd)))
  204. ("rm" ,rm-command)
  205. ("cp" ,(lambda (source dest) `(copy-file ,source ,dest)))
  206. ("help" ,help-command)
  207. ("ls" ,ls-command)
  208. ("which" ,which-command)
  209. ("cat" ,cat-command)
  210. ("wc" ,wc-command)
  211. ("reboot" ,reboot-command)))
  212. (define (read-bournish port env)
  213. "Read a Bournish expression from PORT, and return the corresponding Scheme
  214. code as an sexp."
  215. (match (read-line port)
  216. ((? eof-object? eof)
  217. eof)
  218. ((= string-tokenize (command args ...))
  219. (match (assoc command %commands)
  220. ((command proc) ;built-in command
  221. (apply proc (map expand-variable args)))
  222. (#f
  223. (let ((command (if (string-prefix? "\\" command)
  224. (string-drop command 1)
  225. command)))
  226. `(system* ,command ,@(map expand-variable args))))))))
  227. (define %bournish-language
  228. (let ((scheme (lookup-language 'scheme)))
  229. ;; XXX: The 'scheme' language lacks a "joiner", so we add one here. This
  230. ;; allows us to have 'read-bournish' read one shell statement at a time
  231. ;; instead of having to read until EOF.
  232. (set! (language-joiner scheme)
  233. (lambda (exps env)
  234. (match exps
  235. (() '(begin))
  236. ((exp) exp)
  237. (_ `(begin ,@exps)))))
  238. (make-language #:name 'bournish
  239. #:title "Bournish"
  240. ;; The reader does all the heavy lifting.
  241. #:reader read-bournish
  242. #:compilers `((scheme . ,(lambda (exp env options)
  243. (values exp env env))))
  244. #:decompilers '()
  245. #:evaluator (language-evaluator scheme)
  246. #:printer (language-printer scheme)
  247. #:make-default-environment
  248. (language-make-default-environment scheme))))
  249. ;; XXX: ",L bournish" won't work unless we call our module (language bournish
  250. ;; spec), which is kinda annoying, so provide another meta-command.
  251. (define-meta-command ((bournish guix) repl)
  252. "bournish
  253. Switch to the Bournish language."
  254. (let ((current (repl-language repl)))
  255. (format #t "Welcome to ~a, a minimal Bourne-like shell!~%To switch back, type `,L ~a'.\n"
  256. (language-title %bournish-language)
  257. (language-name current))
  258. (current-language %bournish-language)
  259. (set! (repl-language repl) %bournish-language)))
  260. ;;; bournish.scm ends here