diff --git a/Makefile b/Makefile index 4957fff..1e66cd8 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,7 @@ LISP ?= sbcl +all: build + # Install some Quicklisp dependencies. ql-deps: # 2023-03: we want str:ensure-suffix, not yet in Quicklisp. diff --git a/README.md b/README.md index d19cd89..ba7688f 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,8 @@ This REPL is more user friendly than the default SBCL one: %q => Ends the session. ``` +The CIEL terminal REPL loads the `~/.cielrc` init file at start-up if present. Don't load with `--no-userinit`. + See more in [*the documentation*](https://ciel-lang.github.io/CIEL/#/). # Usage diff --git a/build-image.lisp b/build-image.lisp index c4aade7..447ad91 100644 --- a/build-image.lisp +++ b/build-image.lisp @@ -13,4 +13,9 @@ (in-package :ciel-user) +;; XXX: we would like to read our ~/.cielrc init file when resuming the core +;; in Slime. +;; Currently we load it only when starting the terminal REPL. +;; See the :toplevel option. + (sb-ext:save-lisp-and-die "ciel-core") diff --git a/ciel.asd b/ciel.asd index e6cb69c..b18be7b 100644 --- a/ciel.asd +++ b/ciel.asd @@ -136,6 +136,7 @@ ((:file "packages") (:file "json-pointer-minus") (:file "ciel"))) + (:file "utils") (:module "src/more-docstrings" :components ((:file "docstrings")))) @@ -152,6 +153,7 @@ :lisp-critic ;; it would be nice to integrate it with Slime. :magic-ed) :components ((:file "repl") + (:file "utils") (:file "scripting") (:file "shell-utils") (:file "repl-utils") diff --git a/docs/scripting.md b/docs/scripting.md index 6bf9d2d..9c851fd 100644 --- a/docs/scripting.md +++ b/docs/scripting.md @@ -539,3 +539,27 @@ This allows you to have a dumb "live reload" workflow with a simple editor and a (simple-auto-reload) (sleep most-positive-fixnum)) ~~~ + +## Misc + +### Load your scripts in the REPL + +Calling your scripts from the shell is pretty cool, what if you could +*also* have them available at your fingertips in a Lisp REPL? + +TLDR; + +```lisp +;; in ~/.cielrc +(ciel::load-without-shebang "~/path/to/yourscript.lisp") +``` + +As the name suggests, this `load` function works even if your file starts with a shebang line (which is not valid Lisp code, so the default `LOAD` function would fail). + +Y'know, sometimes you live longer in a Lisp REPL than in a shell +without noticing. Or simply, manipulating real objects in a text +buffer can be more practical than copy-pasting text in a rigid +terminal (even though Emacs' +[vterm](https://github.com/akermu/emacs-libvterm) is an excellent improvement too). + +> INFO: the `~/.cielrc` file is loaded at start-up of the terminal REPL (called with `ciel`), not yet when you start the core image in your IDE. diff --git a/repl.lisp b/repl.lisp index 9d6614b..98f0452 100755 --- a/repl.lisp +++ b/repl.lisp @@ -8,7 +8,7 @@ (:use :common-lisp :trivial-package-local-nicknames) (:import-from :magic-ed :magic-ed) - (:export sbcli help what *repl-version* *repl-name* *prompt* *prompt2* *result-indicator* *config-file* + (:export sbcli help what *repl-version* *repl-name* *prompt* *prompt2* *result-indicator* *init-file* *hist-file* *special* *syntax-highlighting* *pygmentize* *pygmentize-options*)) @@ -48,7 +48,7 @@ (defvar *prompt* (format nil "~a" (cl-ansi-text:green "ciel-user> "))) (defvar *prompt2* "....> ") (defvar *result-indicator* "=> ") -(defvar *config-file* "~/.cielrc") +(defvar *init-file* "~/.cielrc") (defvar *hist-file* "~/.ciel_history") (defvar *hist* (list)) (defvar *syntax-highlighting* nil) @@ -87,6 +87,11 @@ :if-does-not-exist :create) (write-line str out))) +(defun load-init-file (&optional (init-file *init-file*)) + "Load the ~/.cielrc init file. + Defaults to `*init-file*'." + (load init-file)) + (defun end () "Ends the session." (format t "~%Bye!~&") @@ -524,12 +529,13 @@ strings to match candidates against (for example in the form \"package:sym\")." (rl:redisplay) )) -(defun repl (&key noinform) +(defun repl (&key noinform no-usernit) "Toplevel REPL. CLI options: - -h, --help - --noinform: don't print the welcome banner. + - --no-userinit: don't load the user's cielrc init file. " (let ((argv (uiop:command-line-arguments))) @@ -556,10 +562,8 @@ strings to match candidates against (for example in the form \"package:sym\")." (rl:bind-keyseq "\\C-x\\C-e" #'edit-current-input) (rl:set-paren-blink-timeout 500) - (if (probe-file *config-file*) - (load *config-file*)) - ;; Print a banner and system info. + ;; Checking a CLI arg this way is an old, done before our use of Clingon. (unless (or noinform (member "--noinform" (uiop:command-line-arguments) :test #'string-equal)) (princ *banner*) @@ -570,6 +574,12 @@ strings to match candidates against (for example in the form \"package:sym\")." (write-char #\linefeed) (finish-output nil)) + ;; Load CIEL's user init file. + (unless (or no-usernit + (member "--no-userinit" (uiop:command-line-arguments) :test #'string-equal)) + (when (uiop:file-exists-p *init-file*) + (load-init-file))) + (when *hist-file* (read-hist-file)) (in-package :ciel-user) diff --git a/scripting.lisp b/scripting.lisp index 87804e7..7b2d342 100644 --- a/scripting.lisp +++ b/scripting.lisp @@ -8,33 +8,6 @@ Hash-table: file name (sans extension) -> file content (string). The name is case-insensitive (it's easier for typing things in the terminal).") -(defun maybe-ignore-shebang (in) - "If this file starts with #!, delete the shebang line, - so we can LOAD the file. - Return: a stream (it is LOADable)." - ;; thanks Roswell for the trick. - (let ((first-line (read-line in))) - (make-concatenated-stream - ;; remove shebang: - (make-string-input-stream - (format nil "~a" - (if (str:starts-with-p "#!" first-line) - "" - first-line))) - ;; rest of the file: - in))) - -(defun load-without-shebang (file) - "LOAD this file, but exclude the first line if it is a shebang line." - (with-open-file (file-stream file) - (load - (maybe-ignore-shebang file-stream)))) - -(defun has-shebang (file) - "Return T if the first line of this file is a shell shebang line (starts with #!)." - (with-open-file (s file) - (str:starts-with-p "#!" (read-line s)))) - ;; eval (defun wrap-user-code (s) "Wrap this user code to handle common conditions, such as a C-c C-c to quit gracefully." @@ -124,6 +97,16 @@ :long-name "scripts" :short-name #\z :key :scripts) + (clingon:make-option + :flag + :description "Don't load the ~/.cielrc init file at start-up (for the CIEL terminal REPL)." + :long-name "no-userinit" + :key :no-userinit) + (clingon:make-option + :flag + :description "Don't print the welcome banner." + :long-name "noinform" + :key :noinform) )) #+(or) @@ -259,6 +242,8 @@ ;; default: run CIEL's REPL. (t + ;; XXX: maybe pass all CLI options here, don't re-read them in the repl function. + ;; (which was the old way). (sbcli::repl))) (error (c) diff --git a/utils.lisp b/utils.lisp new file mode 100644 index 0000000..3c98fd6 --- /dev/null +++ b/utils.lisp @@ -0,0 +1,35 @@ +(in-package :ciel) + + +;;; Utilities that are useful enough to be available everywhere. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; These are used for the scripting capabilities. +;;; We can load a file with or without a shebang line. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun maybe-ignore-shebang (in) + "If this file starts with #!, delete the shebang line, + so we can LOAD the file. + Return: a stream (it is LOADable)." + ;; thanks Roswell for the trick. + (let ((first-line (read-line in))) + (make-concatenated-stream + ;; remove shebang: + (make-string-input-stream + (format nil "~a" + (if (str:starts-with-p "#!" first-line) + "" + first-line))) + ;; rest of the file: + in))) + +(defun load-without-shebang (file) + "LOAD this file, but exclude the first line if it is a shebang line." + (with-open-file (file-stream file) + (load + (maybe-ignore-shebang file-stream)))) + +(defun has-shebang (file) + "Return T if the first line of this file is a shell shebang line (starts with #!)." + (with-open-file (s file) + (str:starts-with-p "#!" (read-line s))))