summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am1
-rw-r--r--gnu/system/linux-initrd.scm4
-rw-r--r--guix/build/bournish.scm172
3 files changed, 176 insertions, 1 deletions
diff --git a/Makefile.am b/Makefile.am
index 9620e600d1..9beeb9d564 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -98,6 +98,7 @@ MODULES =					\
   guix/build/gremlin.scm			\
   guix/build/emacs-utils.scm			\
   guix/build/graft.scm				\
+  guix/build/bournish.scm			\
   guix/search-paths.scm				\
   guix/packages.scm				\
   guix/import/utils.scm				\
diff --git a/gnu/system/linux-initrd.scm b/gnu/system/linux-initrd.scm
index f6bd84f02e..8ca74104fb 100644
--- a/gnu/system/linux-initrd.scm
+++ b/gnu/system/linux-initrd.scm
@@ -227,6 +227,7 @@ loaded at boot time in the order in which they appear."
      #~(begin
          (use-modules (gnu build linux-boot)
                       (guix build utils)
+                      (guix build bournish)   ;add the 'bournish' meta-command
                       (srfi srfi-26))
 
          (with-output-to-port (%make-void-port "w")
@@ -242,7 +243,8 @@ loaded at boot time in the order in which they appear."
                       #:qemu-guest-networking? #$qemu-networking?
                       #:volatile-root? '#$volatile-root?))
      #:name "base-initrd"
-     #:modules '((guix build utils)
+     #:modules '((guix build bournish)
+                 (guix build utils)
                  (guix build syscalls)
                  (gnu build linux-boot)
                  (gnu build linux-modules)
diff --git a/guix/build/bournish.scm b/guix/build/bournish.scm
new file mode 100644
index 0000000000..4022796658
--- /dev/null
+++ b/guix/build/bournish.scm
@@ -0,0 +1,172 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2016 Ludovic Courtès <ludo@gnu.org>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix build bournish)
+  #:use-module (system base language)
+  #:use-module (system base compile)
+  #:use-module (system repl command)
+  #:use-module (system repl common)
+  #:use-module (ice-9 rdelim)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 ftw)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-26)
+  #:export (%bournish-language))
+
+;;; Commentary:
+;;;
+;;; This is a super minimal Bourne-like shell language for Guile.  It is meant
+;;; to be used at the REPL as a rescue shell.  In a way, this is to Guile what
+;;; eshell is to Emacs.
+;;;
+;;; Code:
+
+(define (expand-variable str)
+  "Return STR or code to obtain the value of the environment variable STR
+refers to."
+  ;; XXX: No support for "${VAR}".
+  (if (string-prefix? "$" str)
+      `(or (getenv ,(string-drop str 1)) "")
+      str))
+
+(define* (display-tabulated lst
+                             #:key (columns 3)
+                             (column-width (/ 78 columns)))
+  "Display the list of string LST in COLUMNS columns of COLUMN-WIDTH
+characters."
+  (define len (length lst))
+  (define pad
+    (if (zero? (modulo len columns))
+        0
+        columns))
+  (define items-per-column
+    (quotient (+ len pad) columns))
+  (define items (list->vector lst))
+
+  (let loop ((indexes (unfold (cut >= <> columns)
+                              (cut * <> items-per-column)
+                              1+
+                              0)))
+    (unless (>= (first indexes) items-per-column)
+      (for-each (lambda (index)
+                  (let ((item (if (< index len)
+                                  (vector-ref items index)
+                                  "")))
+                    (display (string-pad-right item column-width))))
+                indexes)
+      (newline)
+      (loop (map 1+ indexes)))))
+
+(define ls-command-implementation
+  ;; Run-time support procedure.
+  (case-lambda
+    (()
+     (display-tabulated (scandir ".")))
+    (files
+     (let ((files (filter (lambda (file)
+                            (catch 'system-error
+                              (lambda ()
+                                (lstat file))
+                              (lambda args
+                                (let ((errno (system-error-errno args)))
+                                  (format (current-error-port) "~a: ~a~%"
+                                          file (strerror errno))
+                                  #f))))
+                          files)))
+       (display-tabulated files)))))
+
+(define (ls-command . files)
+  `((@@ (guix build bournish) ls-command-implementation) ,@files))
+
+(define (which-command program)
+  `(search-path ((@@ (guix build bournish) executable-path))
+                ,program))
+
+(define (cat-command file)
+  `(call-with-input-file ,file
+     (lambda (port)
+       ((@ (guix build utils) dump-port) port (current-output-port))
+       *unspecified*)))
+
+(define (help-command . _)
+  (display "\
+Hello, this is Bournish, a minimal Bourne-like shell in Guile!
+
+The shell is good enough to navigate the file system and run commands but not
+much beyond that.  It is meant to be used as a rescue shell in the initial RAM
+disk and is probably not very useful apart from that.  It has a few built-in
+commands such as 'ls' and 'cd'; it lacks globbing, pipes---everything.\n"))
+
+(define %not-colon (char-set-complement (char-set #\:)))
+(define (executable-path)
+  "Return the search path for programs as a list."
+  (match (getenv "PATH")
+    (#f  '())
+    (str (string-tokenize str %not-colon))))
+
+(define %commands
+  ;; Built-in commands.
+  `(("echo"   ,(lambda strings `(list ,@strings)))
+    ("cd"     ,(lambda (dir) `(chdir ,dir)))
+    ("pwd"    ,(lambda () `(getcwd)))
+    ("rm"     ,(lambda (file) `(delete-file ,file)))
+    ("cp"     ,(lambda (source dest) `(copy-file ,source ,dest)))
+    ("help"   ,help-command)
+    ("ls"     ,ls-command)
+    ("which"  ,which-command)
+    ("cat"    ,cat-command)))
+
+(define (read-bournish port env)
+  "Read a Bournish expression from PORT, and return the corresponding Scheme
+code as an sexp."
+  (match (string-tokenize (read-line port))
+    ((command args ...)
+     (match (assoc command %commands)
+       ((command proc)                            ;built-in command
+        (apply proc (map expand-variable args)))
+       (#f
+        (let ((command (if (string-prefix? "\\" command)
+                           (string-drop command 1)
+                           command)))
+          `(system* ,command ,@(map expand-variable args))))))))
+
+(define %bournish-language
+  (let ((scheme (lookup-language 'scheme)))
+    (make-language #:name 'bournish
+                   #:title "Bournish"
+                   #:reader read-bournish
+                   #:compilers (language-compilers scheme)
+                   #:decompilers (language-decompilers scheme)
+                   #:evaluator (language-evaluator scheme)
+                   #:printer (language-printer scheme)
+                   #:make-default-environment
+                   (language-make-default-environment scheme))))
+
+;; XXX: ",L bournish" won't work unless we call our module (language bournish
+;; spec), which is kinda annoying, so provide another meta-command.
+(define-meta-command ((bournish guix) repl)
+  "bournish
+Switch to the Bournish language."
+  (let ((current (repl-language repl)))
+    (format #t "Welcome to ~a, a minimal Bourne-like shell!~%To switch back, type `,L ~a'.\n"
+            (language-title %bournish-language)
+            (language-name current))
+    (current-language %bournish-language)
+    (set! (repl-language repl) %bournish-language)))
+
+;;; bournish.scm ends here