* guix/scripts/repl.scm: Add filename options for script execution. * doc/guix.texi: Document script execution by "guix repl" --- doc/guix.texi | 38 +++++++++++++++----- guix/scripts/repl.scm | 80 ++++++++++++++++++++++++++----------------- 2 files changed, 78 insertions(+), 40 deletions(-) diff --git a/doc/guix.texi b/doc/guix.texi index d6fbd85fde..68f1a8bba3 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -238,7 +238,7 @@ Programming Interface * Derivations:: Low-level interface to package derivations. * The Store Monad:: Purely functional interface to the store. * G-Expressions:: Manipulating build expressions. -* Invoking guix repl:: Fiddling with Guix interactively. +* Invoking guix repl:: Programming Guix in Guile Defining Packages @@ -5415,7 +5415,7 @@ package definitions. * Derivations:: Low-level interface to package derivations. * The Store Monad:: Purely functional interface to the store. * G-Expressions:: Manipulating build expressions. -* Invoking guix repl:: Fiddling with Guix interactively. +* Invoking guix repl:: Programming Guix in Guile @end menu @node Package Modules @@ -8134,12 +8134,31 @@ has an associated gexp compiler, such as a @code{<package>}. @node Invoking guix repl @section Invoking @command{guix repl} -@cindex REPL, read-eval-print loop -The @command{guix repl} command spawns a Guile @dfn{read-eval-print loop} -(REPL) for interactive programming (@pxref{Using Guile Interactively,,, guile, -GNU Guile Reference Manual}). Compared to just launching the @command{guile} +@cindex REPL, read-eval-print loop, script +The @command{guix repl} command makes it easier to program Guix in Guile +by launching a Guile @dfn{read-eval-print loop} (REPL) for interactive +programming (@pxref{Using Guile Interactively,,, guile, +GNU Guile Reference Manual}), or by running Guile scripts +(@pxref{Running Guile Scripts,,, guile, +GNU Guile Reference Manual}). +Compared to just launching the @command{guile} command, @command{guix repl} guarantees that all the Guix modules and all its -dependencies are available in the search path. You can use it this way: +dependencies are available in the search path. + +The general syntax is: + +@example +guix repl @var{options} @var{files} +@end example + +When at least one @var{files} argument is provided, @var{files} are +executed as Guile scripts in the given order: + +@example +$ guix repl my-script.scm +@end example + +Otherwise a Guile REPL is started: @example $ guix repl @@ -8188,11 +8207,12 @@ Add @var{directory} to the front of the package module search path (@pxref{Package Modules}). This allows users to define their own packages and make them visible to -the command-line tool. +the scripts or REPL. @item -q Inhibit loading of the @file{~/.guile} file. By default, that -configuration file is loaded when spawning a @code{guile} REPL. +configuration file is loaded when executing scripts or spawning +a @code{guile} REPL. @end table @c ********************************************************************* diff --git a/guix/scripts/repl.scm b/guix/scripts/repl.scm index ff1f208894..f4cb744bbd 100644 --- a/guix/scripts/repl.scm +++ b/guix/scripts/repl.scm @@ -1,6 +1,7 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org> ;;; Copyright © 2020 Simon Tournier <zimon.toutoune@gmail.com> +;;; Copyright © 2020 Konrad Hinsen <konrad.hinsen@fastmail.net> ;;; ;;; This file is part of GNU Guix. ;;; @@ -32,10 +33,12 @@ ;;; Commentary: ;;; -;;; This command provides a Guile REPL +;;; This command provides a Guile script runner and REPL in an environment +;;; that contains all the modules comprising Guix. (define %default-options - `((type . guile))) + `((scripts . ()) + (type . guile))) (define %options (list (option '(#\h "help") #f #f @@ -63,8 +66,9 @@ (define (show-help) - (display (G_ "Usage: guix repl [OPTIONS...] -Start a Guile REPL in the Guix execution environment.\n")) + (display (G_ "Usage: guix repl [OPTIONS...] [FILES...] +In the Guix execution environment, run FILES as Guile scripts, +or start a Guile REPL if no FILES are given,\n")) (display (G_ " -t, --type=TYPE start a REPL of the given TYPE")) (display (G_ " @@ -135,12 +139,13 @@ call THUNK." \f (define (guix-repl . args) (define opts - ;; Return the list of package names. (args-fold* args %options (lambda (opt name arg result) (leave (G_ "~A: unrecognized option~%") name)) (lambda (arg result) - (leave (G_ "~A: extraneous argument~%") arg)) + (alist-cons 'scripts + (cons arg (cdr (assq 'scripts result))) + result)) %default-options)) (define user-config @@ -148,29 +153,42 @@ call THUNK." (lambda (home) (string-append home "/.guile")))) + (define (set-user-module) + (set-current-module user-module) + (when (and (not (assoc-ref opts 'ignore-dot-guile?)) + user-config + (file-exists? user-config)) + (load user-config))) + (with-error-handling - (let ((type (assoc-ref opts 'type))) - (call-with-connection (assoc-ref opts 'listen) - (lambda () - (case type - ((guile) - (save-module-excursion - (lambda () - (set-current-module user-module) - (when (and (not (assoc-ref opts 'ignore-dot-guile?)) - user-config - (file-exists? user-config)) - (load user-config)) - - ;; Do not exit repl on SIGINT. - ((@@ (ice-9 top-repl) call-with-sigint) - (lambda () - (start-repl)))))) - ((machine) - (machine-repl)) - (else - (leave (G_ "~a: unknown type of REPL~%") type)))))))) - -;; Local Variables: -;; eval: (put 'call-with-connection 'scheme-indent-function 1) -;; End: + (let ((scripts (reverse (assoc-ref opts 'scripts)))) + + (for-each (lambda (script) + (save-module-excursion + (lambda () + (set-user-module) + (load script)))) + scripts) + + (when (null? scripts) + (let ((type (assoc-ref opts 'type))) + (call-with-connection (assoc-ref opts 'listen) + (lambda () + (case type + ((guile) + (save-module-excursion + (lambda () + (set-user-module) + ;; Do not exit repl on SIGINT. + ((@@ (ice-9 top-repl) call-with-sigint) + (lambda () + (start-repl)))))) + ((machine) + (machine-repl)) + (else + (leave (G_ "~a: unknown type of REPL~%") type))))))))) + + ;; Local Variables: + ;; eval: (put 'call-with-connection 'scheme-indent-function 1) + ;; End: + ) -- 2.26.2
From: Konrad Hinsen <konrad.hinsen@fastmail.net> * guix/scripts/repl.scm: Add filename options for script execution. * doc/guix.texi (Invoking guix repl): Document it. * tests/guix-repl.sh: Test it. --- Makefile.am | 1 + doc/guix.texi | 38 +++++++++++++++----- guix/scripts/repl.scm | 80 ++++++++++++++++++++++++++----------------- tests/guix-repl.sh | 70 +++++++++++++++++++++++++++++++++++++ 4 files changed, 149 insertions(+), 40 deletions(-) create mode 100644 tests/guix-repl.sh diff --git a/Makefile.am b/Makefile.am index 752445afcb..eb4e360c6d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -471,6 +471,7 @@ SH_TESTS = \ tests/guix-environment-container.sh \ tests/guix-graph.sh \ tests/guix-describe.sh \ + tests/guix-repl.sh \ tests/guix-lint.sh TESTS = $(SCM_TESTS) $(SH_TESTS) diff --git a/doc/guix.texi b/doc/guix.texi index d6fbd85fde..68f1a8bba3 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -238,7 +238,7 @@ Programming Interface * Derivations:: Low-level interface to package derivations. * The Store Monad:: Purely functional interface to the store. * G-Expressions:: Manipulating build expressions. -* Invoking guix repl:: Fiddling with Guix interactively. +* Invoking guix repl:: Programming Guix in Guile Defining Packages @@ -5415,7 +5415,7 @@ package definitions. * Derivations:: Low-level interface to package derivations. * The Store Monad:: Purely functional interface to the store. * G-Expressions:: Manipulating build expressions. -* Invoking guix repl:: Fiddling with Guix interactively. +* Invoking guix repl:: Programming Guix in Guile @end menu @node Package Modules @@ -8134,12 +8134,31 @@ has an associated gexp compiler, such as a @code{<package>}. @node Invoking guix repl @section Invoking @command{guix repl} -@cindex REPL, read-eval-print loop -The @command{guix repl} command spawns a Guile @dfn{read-eval-print loop} -(REPL) for interactive programming (@pxref{Using Guile Interactively,,, guile, -GNU Guile Reference Manual}). Compared to just launching the @command{guile} +@cindex REPL, read-eval-print loop, script +The @command{guix repl} command makes it easier to program Guix in Guile +by launching a Guile @dfn{read-eval-print loop} (REPL) for interactive +programming (@pxref{Using Guile Interactively,,, guile, +GNU Guile Reference Manual}), or by running Guile scripts +(@pxref{Running Guile Scripts,,, guile, +GNU Guile Reference Manual}). +Compared to just launching the @command{guile} command, @command{guix repl} guarantees that all the Guix modules and all its -dependencies are available in the search path. You can use it this way: +dependencies are available in the search path. + +The general syntax is: + +@example +guix repl @var{options} @var{files} +@end example + +When at least one @var{files} argument is provided, @var{files} are +executed as Guile scripts in the given order: + +@example +$ guix repl my-script.scm +@end example + +Otherwise a Guile REPL is started: @example $ guix repl @@ -8188,11 +8207,12 @@ Add @var{directory} to the front of the package module search path (@pxref{Package Modules}). This allows users to define their own packages and make them visible to -the command-line tool. +the scripts or REPL. @item -q Inhibit loading of the @file{~/.guile} file. By default, that -configuration file is loaded when spawning a @code{guile} REPL. +configuration file is loaded when executing scripts or spawning +a @code{guile} REPL. @end table @c ********************************************************************* diff --git a/guix/scripts/repl.scm b/guix/scripts/repl.scm index ff1f208894..f4cb744bbd 100644 --- a/guix/scripts/repl.scm +++ b/guix/scripts/repl.scm @@ -1,6 +1,7 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org> ;;; Copyright © 2020 Simon Tournier <zimon.toutoune@gmail.com> +;;; Copyright © 2020 Konrad Hinsen <konrad.hinsen@fastmail.net> ;;; ;;; This file is part of GNU Guix. ;;; @@ -32,10 +33,12 @@ ;;; Commentary: ;;; -;;; This command provides a Guile REPL +;;; This command provides a Guile script runner and REPL in an environment +;;; that contains all the modules comprising Guix. (define %default-options - `((type . guile))) + `((scripts . ()) + (type . guile))) (define %options (list (option '(#\h "help") #f #f @@ -63,8 +66,9 @@ (define (show-help) - (display (G_ "Usage: guix repl [OPTIONS...] -Start a Guile REPL in the Guix execution environment.\n")) + (display (G_ "Usage: guix repl [OPTIONS...] [FILES...] +In the Guix execution environment, run FILES as Guile scripts, +or start a Guile REPL if no FILES are given,\n")) (display (G_ " -t, --type=TYPE start a REPL of the given TYPE")) (display (G_ " @@ -135,12 +139,13 @@ call THUNK." \f (define (guix-repl . args) (define opts - ;; Return the list of package names. (args-fold* args %options (lambda (opt name arg result) (leave (G_ "~A: unrecognized option~%") name)) (lambda (arg result) - (leave (G_ "~A: extraneous argument~%") arg)) + (alist-cons 'scripts + (cons arg (cdr (assq 'scripts result))) + result)) %default-options)) (define user-config @@ -148,29 +153,42 @@ call THUNK." (lambda (home) (string-append home "/.guile")))) + (define (set-user-module) + (set-current-module user-module) + (when (and (not (assoc-ref opts 'ignore-dot-guile?)) + user-config + (file-exists? user-config)) + (load user-config))) + (with-error-handling - (let ((type (assoc-ref opts 'type))) - (call-with-connection (assoc-ref opts 'listen) - (lambda () - (case type - ((guile) - (save-module-excursion - (lambda () - (set-current-module user-module) - (when (and (not (assoc-ref opts 'ignore-dot-guile?)) - user-config - (file-exists? user-config)) - (load user-config)) - - ;; Do not exit repl on SIGINT. - ((@@ (ice-9 top-repl) call-with-sigint) - (lambda () - (start-repl)))))) - ((machine) - (machine-repl)) - (else - (leave (G_ "~a: unknown type of REPL~%") type)))))))) - -;; Local Variables: -;; eval: (put 'call-with-connection 'scheme-indent-function 1) -;; End: + (let ((scripts (reverse (assoc-ref opts 'scripts)))) + + (for-each (lambda (script) + (save-module-excursion + (lambda () + (set-user-module) + (load script)))) + scripts) + + (when (null? scripts) + (let ((type (assoc-ref opts 'type))) + (call-with-connection (assoc-ref opts 'listen) + (lambda () + (case type + ((guile) + (save-module-excursion + (lambda () + (set-user-module) + ;; Do not exit repl on SIGINT. + ((@@ (ice-9 top-repl) call-with-sigint) + (lambda () + (start-repl)))))) + ((machine) + (machine-repl)) + (else + (leave (G_ "~a: unknown type of REPL~%") type))))))))) + + ;; Local Variables: + ;; eval: (put 'call-with-connection 'scheme-indent-function 1) + ;; End: + ) diff --git a/tests/guix-repl.sh b/tests/guix-repl.sh new file mode 100644 index 0000000000..b93d48248d --- /dev/null +++ b/tests/guix-repl.sh @@ -0,0 +1,70 @@ +# GNU Guix --- Functional package management for GNU +# Copyright © 2020 Simon Tournier <zimon.toutoune@gmail.com> +# +# 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/>. + +# +# Test the `guix repl' command-line utility. +# + +guix repl --version + +test_directory="`mktemp -d`" +export test_directory +trap 'chmod -Rf +w "$test_directory"; rm -rf "$test_directory"' EXIT + +tmpfile="$test_directory/foo.scm" +rm -f "$tmpfile" +trap 'rm -f "$tmpfile"' EXIT + +module_dir="t-guix-repl-$$" +mkdir "$module_dir" +trap 'rm -rf "$module_dir"' EXIT + + +cat > "$tmpfile"<<EOF +(use-modules (guix packages) + (gnu packages base)) + +(format #t "~a\n" (package-name coreutils)) +EOF + +# Inhibit loading of ~/.guile to avoid conflict +test "`guix repl -q "$tmpfile"`" = "coreutils" + + +cat > "$module_dir/foo.scm"<<EOF +(define-module (foo) + #:use-module (guix packages) + #:use-module (gnu packages base)) + +(define-public dummy + (package (inherit hello) + (name "dummy") + (version "42") + (synopsis "dummy package") + (description "dummy package. Only used for testing purposes."))) +EOF + +cat > "$tmpfile"<<EOF +(use-modules (guix packages) + (foo)) + +(format #t "~a\n" (package-version dummy)) +EOF + +# Inhibit loading of ~/.guile to avoid conflict +test "`guix repl -q "$tmpfile" -L "$module_dir"`" = "42" -- 2.26.1
Dear Konrad,
Nice!
Thank you!
On Thu, 14 May 2020 at 11:19, Konrad Hinsen <konrad.hinsen@fastmail.net> wrote:
>
> * guix/scripts/repl.scm: Add filename options for script execution.
> * doc/guix.texi: Document script execution by "guix repl"
I have sent a v2 adding tests and rewording the commit message -- to
be what I understand the compliance; sorry if I did a mistake.
Well, I am not sure by the tests.
Last, I am not convinced by the @cindex term and I think "@cindex
repl, guix repl" or "@cindex REPL, guix repl" is better. But that's
only matter of taste. :-)
All the best,
simon
* guix/scripts/repl.scm: Add filename options for script execution. * doc/guix.texi (Invoking guix repl): Document it. * tests/guix-repl.sh: Test it. --- Makefile.am | 1 + doc/guix.texi | 45 ++++++++++++++++++----- guix/scripts/repl.scm | 84 ++++++++++++++++++++++++++----------------- tests/guix-repl.sh | 78 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 167 insertions(+), 41 deletions(-) create mode 100644 tests/guix-repl.sh diff --git a/Makefile.am b/Makefile.am index 5b64386b53..859b6a4bc2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -473,6 +473,7 @@ SH_TESTS = \ tests/guix-environment-container.sh \ tests/guix-graph.sh \ tests/guix-describe.sh \ + tests/guix-repl.sh \ tests/guix-lint.sh TESTS = $(SCM_TESTS) $(SH_TESTS) diff --git a/doc/guix.texi b/doc/guix.texi index 5b9942d420..aeb88a95b6 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -239,7 +239,7 @@ Programming Interface * Derivations:: Low-level interface to package derivations. * The Store Monad:: Purely functional interface to the store. * G-Expressions:: Manipulating build expressions. -* Invoking guix repl:: Fiddling with Guix interactively. +* Invoking guix repl:: Programming Guix in Guile Defining Packages @@ -5466,7 +5466,7 @@ package definitions. * Derivations:: Low-level interface to package derivations. * The Store Monad:: Purely functional interface to the store. * G-Expressions:: Manipulating build expressions. -* Invoking guix repl:: Fiddling with Guix interactively. +* Invoking guix repl:: Programming Guix in Guile @end menu @node Package Modules @@ -8240,12 +8240,38 @@ has an associated gexp compiler, such as a @code{<package>}. @node Invoking guix repl @section Invoking @command{guix repl} -@cindex REPL, read-eval-print loop -The @command{guix repl} command spawns a Guile @dfn{read-eval-print loop} -(REPL) for interactive programming (@pxref{Using Guile Interactively,,, guile, -GNU Guile Reference Manual}). Compared to just launching the @command{guile} +@cindex REPL, read-eval-print loop, script +The @command{guix repl} command makes it easier to program Guix in Guile +by launching a Guile @dfn{read-eval-print loop} (REPL) for interactive +programming (@pxref{Using Guile Interactively,,, guile, +GNU Guile Reference Manual}), or by running Guile scripts +(@pxref{Running Guile Scripts,,, guile, +GNU Guile Reference Manual}). +Compared to just launching the @command{guile} command, @command{guix repl} guarantees that all the Guix modules and all its -dependencies are available in the search path. You can use it this way: +dependencies are available in the search path. + +The general syntax is: + +@example +guix repl @var{options} @var{files} +@end example + +When at least one @var{files} argument is provided, @var{files} are +executed as Guile scripts in the given order: + +@example +$ guix repl my-script.scm +@end example + +To pass arguments to the script, use @code{--} to prevent them from +being interpreted as arguments to @command{guix repl} itself: + +@example +$ guix repl -- my-script.scm --input=foo.txt +@end example + +Without any filename argument, a Guile REPL is started: @example $ guix repl @@ -8294,11 +8320,12 @@ Add @var{directory} to the front of the package module search path (@pxref{Package Modules}). This allows users to define their own packages and make them visible to -the command-line tool. +the script or REPL. @item -q Inhibit loading of the @file{~/.guile} file. By default, that -configuration file is loaded when spawning a @code{guile} REPL. +configuration file is loaded when executing scripts or spawning +a @code{guile} REPL. @end table @c ********************************************************************* diff --git a/guix/scripts/repl.scm b/guix/scripts/repl.scm index ff1f208894..f8d0483ad5 100644 --- a/guix/scripts/repl.scm +++ b/guix/scripts/repl.scm @@ -1,6 +1,7 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org> ;;; Copyright © 2020 Simon Tournier <zimon.toutoune@gmail.com> +;;; Copyright © 2020 Konrad Hinsen <konrad.hinsen@fastmail.net> ;;; ;;; This file is part of GNU Guix. ;;; @@ -32,10 +33,12 @@ ;;; Commentary: ;;; -;;; This command provides a Guile REPL +;;; This command provides a Guile script runner and REPL in an environment +;;; that contains all the modules comprising Guix. (define %default-options - `((type . guile))) + `((script-args . ()) + (type . guile))) (define %options (list (option '(#\h "help") #f #f @@ -63,8 +66,9 @@ (define (show-help) - (display (G_ "Usage: guix repl [OPTIONS...] -Start a Guile REPL in the Guix execution environment.\n")) + (display (G_ "Usage: guix repl [OPTIONS...] [-- FILE ARGS...] +In the Guix execution environment, run FILE as a Guile script with +command-line arguments ARGS. If no FILE is given, start a Guile REPL,\n")) (display (G_ " -t, --type=TYPE start a REPL of the given TYPE")) (display (G_ " @@ -135,42 +139,58 @@ call THUNK." \f (define (guix-repl . args) (define opts - ;; Return the list of package names. (args-fold* args %options (lambda (opt name arg result) (leave (G_ "~A: unrecognized option~%") name)) (lambda (arg result) - (leave (G_ "~A: extraneous argument~%") arg)) + (alist-cons 'script-args + (cons arg (cdr (assq 'script-args result))) + result)) %default-options)) - + (define user-config (and=> (getenv "HOME") (lambda (home) (string-append home "/.guile")))) + (define (set-user-module) + (set-current-module user-module) + (when (and (not (assoc-ref opts 'ignore-dot-guile?)) + user-config + (file-exists? user-config)) + (load user-config))) + (with-error-handling - (let ((type (assoc-ref opts 'type))) - (call-with-connection (assoc-ref opts 'listen) - (lambda () - (case type - ((guile) - (save-module-excursion - (lambda () - (set-current-module user-module) - (when (and (not (assoc-ref opts 'ignore-dot-guile?)) - user-config - (file-exists? user-config)) - (load user-config)) - - ;; Do not exit repl on SIGINT. - ((@@ (ice-9 top-repl) call-with-sigint) - (lambda () - (start-repl)))))) - ((machine) - (machine-repl)) - (else - (leave (G_ "~a: unknown type of REPL~%") type)))))))) - -;; Local Variables: -;; eval: (put 'call-with-connection 'scheme-indent-function 1) -;; End: + (let ((script-args (reverse (assoc-ref opts 'script-args)))) + + (unless (null? script-args) + ;; Run script + (save-module-excursion + (lambda () + (set-program-arguments script-args) + (set-user-module) + (load (car script-args))))) + + (when (null? script-args) + ;; Start REPL + (let ((type (assoc-ref opts 'type))) + (call-with-connection (assoc-ref opts 'listen) + (lambda () + (case type + ((guile) + (save-module-excursion + (lambda () + (set-user-module) + ;; Do not exit repl on SIGINT. + ((@@ (ice-9 top-repl) call-with-sigint) + (lambda () + (start-repl)))))) + ((machine) + (machine-repl)) + (else + (leave (G_ "~a: unknown type of REPL~%") type))))))))) + + ;; Local Variables: + ;; eval: (put 'call-with-connection 'scheme-indent-function 1) + ;; End: + ) diff --git a/tests/guix-repl.sh b/tests/guix-repl.sh new file mode 100644 index 0000000000..78b775baf3 --- /dev/null +++ b/tests/guix-repl.sh @@ -0,0 +1,78 @@ +# GNU Guix --- Functional package management for GNU +# Copyright © 2020 Simon Tournier <zimon.toutoune@gmail.com> +# Copyright © 2020 Konrad Hinsen <konrad.hinsen@fastmail.net> +# +# 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/>. + +# +# Test the `guix repl' command-line utility. +# + +guix repl --version + +test_directory="`mktemp -d`" +export test_directory +trap 'chmod -Rf +w "$test_directory"; rm -rf "$test_directory"' EXIT + +tmpfile="$test_directory/foo.scm" +rm -f "$tmpfile" +trap 'rm -f "$tmpfile"' EXIT + +module_dir="t-guix-repl-$$" +mkdir "$module_dir" +trap 'rm -rf "$module_dir"' EXIT + + +cat > "$tmpfile"<<EOF +(use-modules (guix packages) + (gnu packages base)) + +(format #t "~a\n" (package-name coreutils)) +EOF + +# Inhibit loading of ~/.guile to avoid conflict +test "`guix repl -q "$tmpfile"`" = "coreutils" + + +cat > "$module_dir/foo.scm"<<EOF +(define-module (foo) + #:use-module (guix packages) + #:use-module (gnu packages base)) + +(define-public dummy + (package (inherit hello) + (name "dummy") + (version "42") + (synopsis "dummy package") + (description "dummy package. Only used for testing purposes."))) +EOF + +cat > "$tmpfile"<<EOF +(use-modules (guix packages) + (foo)) + +(format #t "~a\n" (package-version dummy)) +EOF + +# Inhibit loading of ~/.guile to avoid conflict +test "`guix repl -q "$tmpfile" -L "$module_dir"`" = "42" + +cat > "$tmpfile"<<EOF +(format #t "~a\n" (cdr (command-line))) +EOF + +# Inhibit loading of ~/.guile to avoid conflict +test "`guix repl -q -- "$tmpfile" -a --input=foo.txt`" = "(-a --input=foo.txt)" -- 2.26.2
Hi Simon, > I have sent a v2 adding tests and rewording the commit message -- to > be what I understand the compliance; sorry if I did a mistake. > Well, I am not sure by the tests. Way better than no tests! I just submitted a v3, following a thread on help-guix (see https://www.mail-archive.com/help-guix@gnu.org/msg09328.html). The v3 patch allows guix repl script.scm but also guix repl – script.scm -a -b –-input=foo.txt In exchange, only a single script can be run. This is perfectly in line with how Guile works, but it breaks your nice interpretation of "REPL" as applying to script execution ;-) Cheers, Konrad
Hi Konrad, On Fri, 29 May 2020 at 12:16, Konrad Hinsen <konrad.hinsen@fastmail.net> wrote: > I just submitted a v3, following a thread on help-guix > (see https://www.mail-archive.com/help-guix@gnu.org/msg09328.html). May I ask why this mai-archive.com instead of lists.gnu.org/archive/html/help-guix? > The v3 patch allows > > guix repl script.scm > > but also > > guix repl – script.scm -a -b –-input=foo.txt Nice! > In exchange, only a single script can be run. This is perfectly Do you mean that guix repl script_1.scm script_2.scm does not work? > in line with how Guile works, but it breaks your nice interpretation > of "REPL" as applying to script execution ;-) I can live with that. :-) Thank you for the patch!! Hope it will be merged soon. :-) Cheers, simon ps: Do you know why "M-x debbugs-gnu" is doing a mess with this thread?
Hi Simon, > May I ask why this mai-archive.com instead of > lists.gnu.org/archive/html/help-guix? Because that's what DuckDuckGo found for me. I can't remember where all those list archives are, I just search! >> In exchange, only a single script can be run. This is perfectly > > Do you mean that > > guix repl script_1.scm script_2.scm > > does not work? Well, it does in a way, but differently from before: it runs script₁.scm and passes it script₂.scm as its (only) argument. > Do you know why "M-x debbugs-gnu" is doing a mess with this thread? No, I have never heard about "M-x debbugs-gnu". Sounds interesting though... Cheers, Konrad
Hello! Konrad Hinsen <konrad.hinsen@fastmail.net> skribis: > * guix/scripts/repl.scm: Add filename options for script execution. > * doc/guix.texi (Invoking guix repl): Document it. > * tests/guix-repl.sh: Test it. I like it! It cannot be used as a shebang, can it? There’s also the Makefile.am change that could be mentioned in the commit log. Some comments: > +When at least one @var{files} argument is provided, @var{files} are > +executed as Guile scripts in the given order: > + > +@example > +$ guix repl my-script.scm > +@end example > + > +To pass arguments to the script, use @code{--} to prevent them from > +being interpreted as arguments to @command{guix repl} itself: > + > +@example > +$ guix repl -- my-script.scm --input=foo.txt > +@end example I’d remove “$” from the examples. > +Without any filename argument, a Guile REPL is started: s/filename/file name/ > @item -q > Inhibit loading of the @file{~/.guile} file. By default, that > -configuration file is loaded when spawning a @code{guile} REPL. > +configuration file is loaded when executing scripts or spawning > +a @code{guile} REPL. I think this change is unnecessary (see below). > + (display (G_ "Usage: guix repl [OPTIONS...] [-- FILE ARGS...] > +In the Guix execution environment, run FILE as a Guile script with > +command-line arguments ARGS. If no FILE is given, start a Guile REPL,\n")) ^ ^ Please add a space and change comma to period. > (lambda (arg result) > - (leave (G_ "~A: extraneous argument~%") arg)) > + (alist-cons 'script-args > + (cons arg (cdr (assq 'script-args result))) > + result)) I’d change that to just: (append `((script . ,arg) (ignore-dot-guile . #t)) result) and later we can collect all the values associated with 'script. I think “script” is clearer than “script-args” (and more in-line with the naming conventions.) Last but not least: I think -q should always be implied when running a script. ~/.guile is really meant for the REPL, nothing else. > + ;; Local Variables: > + ;; eval: (put 'call-with-connection 'scheme-indent-function 1) > + ;; End: > + ) Please move the comment to the bottom, outside the parens, as before, otherwise this paren will feel lonely. :-) Thoughts? Looks like we’re almost done. Thank you! Ludo’.
Hi Ludo, Thanks for your feedback! > It cannot be used as a shebang, can it? It can. And that might be worth documenting. Here's an example: ===== File foo.scm ========================================= #!/usr/bin/env -S guix repl !# (use-modules (ice-9 format)) (format #t "foo called with arguments: ~s\n"(command-line)) ===== End of file foo.scm ================================== hinsen@guix ~$ ~/foo.scm a b c ;;; note: source file /home/hinsen/temp/foo.scm ;;; newer than compiled /home/hinsen/.cache/guile/ccache/3.0-LE-8-4.2/home/hinsen/temp/foo.scm.go foo called with arguments: ("/home/hinsen/temp/foo.scm" "a" "b" "c") The ugly part is that the script needs to be called with '–' as its first argument if any of the following arguments start with dashes: hinsen@guix ~$ ~/foo.scm -- --help ;;; note: source file /home/hinsen/temp/foo.scm ;;; newer than compiled /home/hinsen/.cache/guile/ccache/3.0-LE-8-4.2/home/hinsen/temp/foo.scm.go foo called with arguments: ("/home/hinsen/temp/foo.scm" "--help") That could be fixed at the price of a command line interface for "guix repl" that deviates a bit from Guix conventions. For example, is there's a file name argument, pass all arguments following it to the script, eliminating the need for –. I'll try that in a v4, and also take into account all your remaining remarks. Just one note on the examples: >> +@example >> +$ guix repl -- my-script.scm --input=foo.txt >> +@end example > > I’d remove “$” from the examples. There are many examples in guix.texi with $, and also many without. Plus some with # as the command line prompt. It makes sense to add the command line prompt for examples that also show output, so I am not sure what the best convention is - but it would be nice to apply a uniform style everywhere (but NOT as part of this patch, of course). Cheers, Konrad
A technical question: is there any way to suppress the error message
about the source being newer than the cached version? This is really
annoying when running scripts.
> hinsen@guix ~$ ~/foo.scm a b c
> ;;; note: source file /home/hinsen/temp/foo.scm
> ;;; newer than compiled /home/hinsen/.cache/guile/ccache/3.0-LE-8-4.2/home/hinsen/temp/foo.scm.go
> foo called with arguments: ("/home/hinsen/temp/foo.scm" "a" "b" "c")
Cheers,
Konrad
Hello Konrad, Konrad Hinsen <konrad.hinsen@fastmail.net> skribis: >> It cannot be used as a shebang, can it? > > It can. And that might be worth documenting. Here's an example: > > ===== File foo.scm ========================================= > #!/usr/bin/env -S guix repl > !# > (use-modules (ice-9 format)) > > (format #t "foo called with arguments: ~s\n"(command-line)) > ===== End of file foo.scm ================================== > > hinsen@guix ~$ ~/foo.scm a b c > ;;; note: source file /home/hinsen/temp/foo.scm > ;;; newer than compiled /home/hinsen/.cache/guile/ccache/3.0-LE-8-4.2/home/hinsen/temp/foo.scm.go > foo called with arguments: ("/home/hinsen/temp/foo.scm" "a" "b" "c") Nice! > The ugly part is that the script needs to be called with '–' as its > first argument if any of the following arguments start with dashes: > > hinsen@guix ~$ ~/foo.scm -- --help > ;;; note: source file /home/hinsen/temp/foo.scm > ;;; newer than compiled /home/hinsen/.cache/guile/ccache/3.0-LE-8-4.2/home/hinsen/temp/foo.scm.go > foo called with arguments: ("/home/hinsen/temp/foo.scm" "--help") > > That could be fixed at the price of a command line interface for "guix > repl" that deviates a bit from Guix conventions. For example, > is there's a file name argument, pass all arguments following it to the > script, eliminating the need for –. Yes, that makes sense to me. > I'll try that in a v4, and also take into account all your remaining > remarks. Just one note on the examples: > >>> +@example >>> +$ guix repl -- my-script.scm --input=foo.txt >>> +@end example >> >> I’d remove “$” from the examples. > > There are many examples in guix.texi with $, and also many without. Plus > some with # as the command line prompt. Yeah, the manual is kinda inconsistent, and I’m self-inconsistent to tell the truth. :-) I’ve come to the conclusion that snippets that contain only input should be written without a prompt, for easier copy/pasting. (I’ve seen Python documentation where JS magic allows people to toggle prompt display, I find it nice.) > A technical question: is there any way to suppress the error message > about the source being newer than the cached version? This is really > annoying when running scripts. > >> hinsen@guix ~$ ~/foo.scm a b c >> ;;; note: source file /home/hinsen/temp/foo.scm >> ;;; newer than compiled /home/hinsen/.cache/guile/ccache/3.0-LE-8-4.2/home/hinsen/temp/foo.scm.go >> foo called with arguments: ("/home/hinsen/temp/foo.scm" "a" "b" "c") I guess ~/.cache/…/*.go exists because you first ran “guile foo.scm”, no? In that case, you can simply remove that .go file. The ‘guix’ command turns off auto-compilation so ‘guix repl’ scripts would always be interpreted, for better or worse. Thanks, Ludo’.
* guix/scripts/repl.scm: Add filename options for script execution. * doc/guix.texi (Invoking guix repl): Document it. * tests/guix-repl.sh: Test it. * Makefile.am: (SH_TESTS): Add it. --- Makefile.am | 1 + doc/guix.texi | 51 +++++++++++++++++++++----- guix/scripts/repl.scm | 82 ++++++++++++++++++++++++++++-------------- tests/guix-repl.sh | 84 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 184 insertions(+), 34 deletions(-) create mode 100644 tests/guix-repl.sh diff --git a/Makefile.am b/Makefile.am index 5b64386b53..859b6a4bc2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -473,6 +473,7 @@ SH_TESTS = \ tests/guix-environment-container.sh \ tests/guix-graph.sh \ tests/guix-describe.sh \ + tests/guix-repl.sh \ tests/guix-lint.sh TESTS = $(SCM_TESTS) $(SH_TESTS) diff --git a/doc/guix.texi b/doc/guix.texi index 056bf011f6..b95709d0c6 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -239,7 +239,7 @@ Programming Interface * Derivations:: Low-level interface to package derivations. * The Store Monad:: Purely functional interface to the store. * G-Expressions:: Manipulating build expressions. -* Invoking guix repl:: Fiddling with Guix interactively. +* Invoking guix repl:: Programming Guix in Guile Defining Packages @@ -5472,7 +5472,7 @@ package definitions. * Derivations:: Low-level interface to package derivations. * The Store Monad:: Purely functional interface to the store. * G-Expressions:: Manipulating build expressions. -* Invoking guix repl:: Fiddling with Guix interactively. +* Invoking guix repl:: Programming Guix in Guile @end menu @node Package Modules @@ -8246,12 +8246,47 @@ has an associated gexp compiler, such as a @code{<package>}. @node Invoking guix repl @section Invoking @command{guix repl} -@cindex REPL, read-eval-print loop -The @command{guix repl} command spawns a Guile @dfn{read-eval-print loop} -(REPL) for interactive programming (@pxref{Using Guile Interactively,,, guile, -GNU Guile Reference Manual}). Compared to just launching the @command{guile} +@cindex REPL, read-eval-print loop, script +The @command{guix repl} command makes it easier to program Guix in Guile +by launching a Guile @dfn{read-eval-print loop} (REPL) for interactive +programming (@pxref{Using Guile Interactively,,, guile, +GNU Guile Reference Manual}), or by running Guile scripts +(@pxref{Running Guile Scripts,,, guile, +GNU Guile Reference Manual}). +Compared to just launching the @command{guile} command, @command{guix repl} guarantees that all the Guix modules and all its -dependencies are available in the search path. You can use it this way: +dependencies are available in the search path. + +The general syntax is: + +@example +guix repl @var{options} @var{file} @var{args} +@end example + +When a @var{file} argument is provided, @var{file} is +executed as a Guile scripts: + +@example +guix repl my-script.scm +@end example + +To pass arguments to the script, use @code{--} to prevent them from +being interpreted as arguments to @command{guix repl} itself: + +@example +guix repl -- my-script.scm --input=foo.txt +@end example + +To make a script executable directly from the shell, using the guix +executable that is on the user's search path, add the following two +lines at the top of the script: + +@example +@code{#!/usr/bin/env -S guix repl --} +@code{!#} +@end example + +Without a file name argument, a Guile REPL is started: @example $ guix repl @@ -8300,7 +8335,7 @@ Add @var{directory} to the front of the package module search path (@pxref{Package Modules}). This allows users to define their own packages and make them visible to -the command-line tool. +the script or REPL. @item -q Inhibit loading of the @file{~/.guile} file. By default, that diff --git a/guix/scripts/repl.scm b/guix/scripts/repl.scm index ff1f208894..4c2537b55d 100644 --- a/guix/scripts/repl.scm +++ b/guix/scripts/repl.scm @@ -1,6 +1,7 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org> ;;; Copyright © 2020 Simon Tournier <zimon.toutoune@gmail.com> +;;; Copyright © 2020 Konrad Hinsen <konrad.hinsen@fastmail.net> ;;; ;;; This file is part of GNU Guix. ;;; @@ -22,6 +23,7 @@ #:use-module (guix scripts) #:use-module (guix repl) #:use-module (srfi srfi-1) + #:use-module (srfi srfi-26) #:use-module (srfi srfi-37) #:use-module (ice-9 match) #:use-module (rnrs bytevectors) @@ -32,7 +34,8 @@ ;;; Commentary: ;;; -;;; This command provides a Guile REPL +;;; This command provides a Guile script runner and REPL in an environment +;;; that contains all the modules comprising Guix. (define %default-options `((type . guile))) @@ -63,8 +66,9 @@ (define (show-help) - (display (G_ "Usage: guix repl [OPTIONS...] -Start a Guile REPL in the Guix execution environment.\n")) + (display (G_ "Usage: guix repl [OPTIONS...] [-- FILE ARGS...] +In the Guix execution environment, run FILE as a Guile script with +command-line arguments ARGS. If no FILE is given, start a Guile REPL.\n")) (display (G_ " -t, --type=TYPE start a REPL of the given TYPE")) (display (G_ " @@ -135,12 +139,13 @@ call THUNK." \f (define (guix-repl . args) (define opts - ;; Return the list of package names. (args-fold* args %options (lambda (opt name arg result) (leave (G_ "~A: unrecognized option~%") name)) (lambda (arg result) - (leave (G_ "~A: extraneous argument~%") arg)) + (append `((script . ,arg) + (ignore-dot-guile . #t)) + result)) %default-options)) (define user-config @@ -148,28 +153,53 @@ call THUNK." (lambda (home) (string-append home "/.guile")))) + (define (set-user-module) + (set-current-module user-module) + (when (and (not (assoc-ref opts 'ignore-dot-guile?)) + user-config + (file-exists? user-config)) + (load user-config))) + + (define script (reverse + (map cdr + (filter (lambda (opt) + (eq? (car opt) 'script)) + opts)))) + (define script-file + (let ((file (car script)) + (directory (getcwd))) + (canonicalize-path + (cond ((string-prefix? "/" file) file) + (else (string-append directory "/" file)))))) + (with-error-handling - (let ((type (assoc-ref opts 'type))) - (call-with-connection (assoc-ref opts 'listen) - (lambda () - (case type - ((guile) - (save-module-excursion - (lambda () - (set-current-module user-module) - (when (and (not (assoc-ref opts 'ignore-dot-guile?)) - user-config - (file-exists? user-config)) - (load user-config)) - - ;; Do not exit repl on SIGINT. - ((@@ (ice-9 top-repl) call-with-sigint) - (lambda () - (start-repl)))))) - ((machine) - (machine-repl)) - (else - (leave (G_ "~a: unknown type of REPL~%") type)))))))) + + (unless (null? script) + ;; Run script + (save-module-excursion + (lambda () + (set-program-arguments (cons script-file (cdr script))) + (set-user-module) + (load script-file)))) + + (when (null? script) + ;; Start REPL + (let ((type (assoc-ref opts 'type))) + (call-with-connection (assoc-ref opts 'listen) + (lambda () + (case type + ((guile) + (save-module-excursion + (lambda () + (set-user-module) + ;; Do not exit repl on SIGINT. + ((@@ (ice-9 top-repl) call-with-sigint) + (lambda () + (start-repl)))))) + ((machine) + (machine-repl)) + (else + (leave (G_ "~a: unknown type of REPL~%") type))))))))) ;; Local Variables: ;; eval: (put 'call-with-connection 'scheme-indent-function 1) diff --git a/tests/guix-repl.sh b/tests/guix-repl.sh new file mode 100644 index 0000000000..2750188468 --- /dev/null +++ b/tests/guix-repl.sh @@ -0,0 +1,84 @@ +# GNU Guix --- Functional package management for GNU +# Copyright © 2020 Simon Tournier <zimon.toutoune@gmail.com> +# Copyright © 2020 Konrad Hinsen <konrad.hinsen@fastmail.net> +# +# 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/>. + +# +# Test the `guix repl' command-line utility. +# + +guix repl --version + +test_directory="`mktemp -d`" +export test_directory +trap 'chmod -Rf +w "$test_directory"; rm -rf "$test_directory"' EXIT + +tmpfile="$test_directory/foo.scm" +rm -f "$tmpfile" +trap 'rm -f "$tmpfile"' EXIT + +module_dir="t-guix-repl-$$" +mkdir "$module_dir" +trap 'rm -rf "$module_dir"' EXIT + + +cat > "$tmpfile"<<EOF +(use-modules (guix packages) + (gnu packages base)) + +(format #t "~a\n" (package-name coreutils)) +EOF + +test "`guix repl "$tmpfile"`" = "coreutils" + + +cat > "$module_dir/foo.scm"<<EOF +(define-module (foo) + #:use-module (guix packages) + #:use-module (gnu packages base)) + +(define-public dummy + (package (inherit hello) + (name "dummy") + (version "42") + (synopsis "dummy package") + (description "dummy package. Only used for testing purposes."))) +EOF + +cat > "$tmpfile"<<EOF +(use-modules (guix packages) + (foo)) + +(format #t "~a\n" (package-version dummy)) +EOF + +test "`guix repl "$tmpfile" -L "$module_dir"`" = "42" + +cat > "$tmpfile"<<EOF +(format #t "~a\n" (cdr (command-line))) +EOF + +test "`guix repl -- "$tmpfile" -a b --input=foo.txt`" = "(-a b --input=foo.txt)" + +cat > "$tmpfile"<<EOF +#!/usr/bin/env -S guix repl -- +!# +(format #t "~a\n" (cdr (command-line))) +EOF +chmod 755 $tmpfile + +test "`"$tmpfile" -a b --input=foo.txt`" = "(-a b --input=foo.txt)" -- 2.26.2
Hi Ludo, Patch v4 is on its way to the world impatiently waiting for it ;-) >> That could be fixed at the price of a command line interface for "guix >> repl" that deviates a bit from Guix conventions. For example, >> is there's a file name argument, pass all arguments following it to the >> script, eliminating the need for –. > > Yes, that makes sense to me. I found a much simpler solution: putting "guix repl –" on the shebang line makes it all work. This is documented in the manual and by a test case. > I guess ~/.cache/…/*.go exists because you first ran “guile foo.scm”, > no? In that case, you can simply remove that .go file. That did the job, thanks! Cheers, Konrad.
Hi, and sorry for the delay! Konrad Hinsen <konrad.hinsen@fastmail.net> skribis: > * guix/scripts/repl.scm: Add filename options for script execution. > * doc/guix.texi (Invoking guix repl): Document it. > * tests/guix-repl.sh: Test it. > * Makefile.am: (SH_TESTS): Add it. I have some comments regarding style and how to deal with multiple scripts, but I think we’re pretty much there. > +The general syntax is: > + > +@example > +guix repl @var{options} @var{file} @var{args} Should be: [@var{file} @var{args}@dots{}] The square brackets show it’s optional. > +When a @var{file} argument is provided, @var{file} is > +executed as a Guile scripts: “When one or more @var{file} argument is provided, each @var{file} is executed as a Guile program:” > + (define script (reverse > + (map cdr > + (filter (lambda (opt) > + (eq? (car opt) 'script)) > + opts)))) To avoid car/cdr (info "(guix) Data Types and Pattern Matching"), I suggest something along these lines: (define scripts ;plural, no? (reverse (filter-map (match-lambda (('script . script) script) (_ #f)) opts))) > + (define script-file > + (let ((file (car script)) > + (directory (getcwd))) > + (canonicalize-path > + (cond ((string-prefix? "/" file) file) > + (else (string-append directory "/" file)))))) I think we can just use file names as they arrive, without attempting to canonicalize them or anything. > + (unless (null? script) > + ;; Run script > + (save-module-excursion > + (lambda () > + (set-program-arguments (cons script-file (cdr script))) > + (set-user-module) > + (load script-file)))) => (for-each load scripts) > +cat > "$tmpfile"<<EOF > +#!/usr/bin/env -S guix repl -- Rather: #!$(type -P env) This will ensure it works even in Guix build environments. Could you send an updated patch? Thank you! Ludo’.
* guix/scripts/repl.scm: Add filename options for script execution. * doc/guix.texi (Invoking guix repl): Document it. * tests/guix-repl.sh: Test it. * Makefile.am: (SH_TESTS): Add it. --- Makefile.am | 1 + doc/guix.texi | 51 +++++++++++++++++++++----- guix/scripts/repl.scm | 84 +++++++++++++++++++++++++++++-------------- tests/guix-repl.sh | 84 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 186 insertions(+), 34 deletions(-) create mode 100644 tests/guix-repl.sh diff --git a/Makefile.am b/Makefile.am index 9cf9318e8a..8988cdfa12 100644 --- a/Makefile.am +++ b/Makefile.am @@ -477,6 +477,7 @@ SH_TESTS = \ tests/guix-environment-container.sh \ tests/guix-graph.sh \ tests/guix-describe.sh \ + tests/guix-repl.sh \ tests/guix-lint.sh TESTS = $(SCM_TESTS) $(SH_TESTS) diff --git a/doc/guix.texi b/doc/guix.texi index 15e077a41c..8ad3a833c6 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -239,7 +239,7 @@ Programming Interface * Derivations:: Low-level interface to package derivations. * The Store Monad:: Purely functional interface to the store. * G-Expressions:: Manipulating build expressions. -* Invoking guix repl:: Fiddling with Guix interactively. +* Invoking guix repl:: Programming Guix in Guile Defining Packages @@ -5474,7 +5474,7 @@ package definitions. * Derivations:: Low-level interface to package derivations. * The Store Monad:: Purely functional interface to the store. * G-Expressions:: Manipulating build expressions. -* Invoking guix repl:: Fiddling with Guix interactively. +* Invoking guix repl:: Programming Guix in Guile @end menu @node Package Modules @@ -8248,12 +8248,47 @@ has an associated gexp compiler, such as a @code{<package>}. @node Invoking guix repl @section Invoking @command{guix repl} -@cindex REPL, read-eval-print loop -The @command{guix repl} command spawns a Guile @dfn{read-eval-print loop} -(REPL) for interactive programming (@pxref{Using Guile Interactively,,, guile, -GNU Guile Reference Manual}). Compared to just launching the @command{guile} +@cindex REPL, read-eval-print loop, script +The @command{guix repl} command makes it easier to program Guix in Guile +by launching a Guile @dfn{read-eval-print loop} (REPL) for interactive +programming (@pxref{Using Guile Interactively,,, guile, +GNU Guile Reference Manual}), or by running Guile scripts +(@pxref{Running Guile Scripts,,, guile, +GNU Guile Reference Manual}). +Compared to just launching the @command{guile} command, @command{guix repl} guarantees that all the Guix modules and all its -dependencies are available in the search path. You can use it this way: +dependencies are available in the search path. + +The general syntax is: + +@example +guix repl @var{options} [@var{file} @var{args}] +@end example + +When a @var{file} argument is provided, @var{file} is +executed as a Guile scripts: + +@example +guix repl my-script.scm +@end example + +To pass arguments to the script, use @code{--} to prevent them from +being interpreted as arguments to @command{guix repl} itself: + +@example +guix repl -- my-script.scm --input=foo.txt +@end example + +To make a script executable directly from the shell, using the guix +executable that is on the user's search path, add the following two +lines at the top of the script: + +@example +@code{#!/usr/bin/env -S guix repl --} +@code{!#} +@end example + +Without a file name argument, a Guile REPL is started: @example $ guix repl @@ -8302,7 +8337,7 @@ Add @var{directory} to the front of the package module search path (@pxref{Package Modules}). This allows users to define their own packages and make them visible to -the command-line tool. +the script or REPL. @item -q Inhibit loading of the @file{~/.guile} file. By default, that diff --git a/guix/scripts/repl.scm b/guix/scripts/repl.scm index ff1f208894..51fffa278a 100644 --- a/guix/scripts/repl.scm +++ b/guix/scripts/repl.scm @@ -1,6 +1,7 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org> ;;; Copyright © 2020 Simon Tournier <zimon.toutoune@gmail.com> +;;; Copyright © 2020 Konrad Hinsen <konrad.hinsen@fastmail.net> ;;; ;;; This file is part of GNU Guix. ;;; @@ -22,6 +23,7 @@ #:use-module (guix scripts) #:use-module (guix repl) #:use-module (srfi srfi-1) + #:use-module (srfi srfi-26) #:use-module (srfi srfi-37) #:use-module (ice-9 match) #:use-module (rnrs bytevectors) @@ -32,7 +34,8 @@ ;;; Commentary: ;;; -;;; This command provides a Guile REPL +;;; This command provides a Guile script runner and REPL in an environment +;;; that contains all the modules comprising Guix. (define %default-options `((type . guile))) @@ -63,8 +66,9 @@ (define (show-help) - (display (G_ "Usage: guix repl [OPTIONS...] -Start a Guile REPL in the Guix execution environment.\n")) + (display (G_ "Usage: guix repl [OPTIONS...] [-- FILE ARGS...] +In the Guix execution environment, run FILE as a Guile script with +command-line arguments ARGS. If no FILE is given, start a Guile REPL.\n")) (display (G_ " -t, --type=TYPE start a REPL of the given TYPE")) (display (G_ " @@ -135,12 +139,13 @@ call THUNK." \f (define (guix-repl . args) (define opts - ;; Return the list of package names. (args-fold* args %options (lambda (opt name arg result) (leave (G_ "~A: unrecognized option~%") name)) (lambda (arg result) - (leave (G_ "~A: extraneous argument~%") arg)) + (append `((script . ,arg) + (ignore-dot-guile . #t)) + result)) %default-options)) (define user-config @@ -148,28 +153,55 @@ call THUNK." (lambda (home) (string-append home "/.guile")))) + (define (set-user-module) + (set-current-module user-module) + (when (and (not (assoc-ref opts 'ignore-dot-guile?)) + user-config + (file-exists? user-config)) + (load user-config))) + + (define script + (reverse + (filter-map (match-lambda + (('script . script) script) + (_ #f)) + opts))) + + (define script-file + (let ((file (car script)) + (directory (getcwd))) + (canonicalize-path + (cond ((string-prefix? "/" file) file) + (else (string-append directory "/" file)))))) + (with-error-handling - (let ((type (assoc-ref opts 'type))) - (call-with-connection (assoc-ref opts 'listen) - (lambda () - (case type - ((guile) - (save-module-excursion - (lambda () - (set-current-module user-module) - (when (and (not (assoc-ref opts 'ignore-dot-guile?)) - user-config - (file-exists? user-config)) - (load user-config)) - - ;; Do not exit repl on SIGINT. - ((@@ (ice-9 top-repl) call-with-sigint) - (lambda () - (start-repl)))))) - ((machine) - (machine-repl)) - (else - (leave (G_ "~a: unknown type of REPL~%") type)))))))) + + (unless (null? script) + ;; Run script + (save-module-excursion + (lambda () + (set-program-arguments (cons script-file (cdr script))) + (set-user-module) + (load script-file)))) + + (when (null? script) + ;; Start REPL + (let ((type (assoc-ref opts 'type))) + (call-with-connection (assoc-ref opts 'listen) + (lambda () + (case type + ((guile) + (save-module-excursion + (lambda () + (set-user-module) + ;; Do not exit repl on SIGINT. + ((@@ (ice-9 top-repl) call-with-sigint) + (lambda () + (start-repl)))))) + ((machine) + (machine-repl)) + (else + (leave (G_ "~a: unknown type of REPL~%") type))))))))) ;; Local Variables: ;; eval: (put 'call-with-connection 'scheme-indent-function 1) diff --git a/tests/guix-repl.sh b/tests/guix-repl.sh new file mode 100644 index 0000000000..e1c2b8241f --- /dev/null +++ b/tests/guix-repl.sh @@ -0,0 +1,84 @@ +# GNU Guix --- Functional package management for GNU +# Copyright © 2020 Simon Tournier <zimon.toutoune@gmail.com> +# Copyright © 2020 Konrad Hinsen <konrad.hinsen@fastmail.net> +# +# 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/>. + +# +# Test the `guix repl' command-line utility. +# + +guix repl --version + +test_directory="`mktemp -d`" +export test_directory +trap 'chmod -Rf +w "$test_directory"; rm -rf "$test_directory"' EXIT + +tmpfile="$test_directory/foo.scm" +rm -f "$tmpfile" +trap 'rm -f "$tmpfile"' EXIT + +module_dir="t-guix-repl-$$" +mkdir "$module_dir" +trap 'rm -rf "$module_dir"' EXIT + + +cat > "$tmpfile"<<EOF +(use-modules (guix packages) + (gnu packages base)) + +(format #t "~a\n" (package-name coreutils)) +EOF + +test "`guix repl "$tmpfile"`" = "coreutils" + + +cat > "$module_dir/foo.scm"<<EOF +(define-module (foo) + #:use-module (guix packages) + #:use-module (gnu packages base)) + +(define-public dummy + (package (inherit hello) + (name "dummy") + (version "42") + (synopsis "dummy package") + (description "dummy package. Only used for testing purposes."))) +EOF + +cat > "$tmpfile"<<EOF +(use-modules (guix packages) + (foo)) + +(format #t "~a\n" (package-version dummy)) +EOF + +test "`guix repl "$tmpfile" -L "$module_dir"`" = "42" + +cat > "$tmpfile"<<EOF +(format #t "~a\n" (cdr (command-line))) +EOF + +test "`guix repl -- "$tmpfile" -a b --input=foo.txt`" = "(-a b --input=foo.txt)" + +cat > "$tmpfile"<<EOF +#!$(type -P env) -S guix repl -- +!# +(format #t "~a\n" (cdr (command-line))) +EOF +chmod 755 $tmpfile + +test "`"$tmpfile" -a b --input=foo.txt`" = "(-a b --input=foo.txt)" -- 2.26.2
Hi Ludo, Patch v5 is on its way! A few comments: > Should be: [@var{file} @var{args}@dots{}] > The square brackets show it’s optional. OK. >> +When a @var{file} argument is provided, @var{file} is >> +executed as a Guile scripts: > > “When one or more @var{file} argument is provided, each @var{file} is > executed as a Guile program:” No, that's no longer true. Only one script can be run at a time, because guix repl script1.scm script2.scm now means "run script1.scm with script2.scm as its argument". And therefore... > (define scripts ;plural, no? This is not a plural. But filter-map is indeed nicer! >> + (define script-file >> + (let ((file (car script)) >> + (directory (getcwd))) >> + (canonicalize-path >> + (cond ((string-prefix? "/" file) file) >> + (else (string-append directory "/" file)))))) > > I think we can just use file names as they arrive, without attempting to > canonicalize them or anything. That's what I thought (and tried) as well, at first. Problems: - It doesn't work when run via pre-inst-env with a non-absolute filename for the script. The script is looked up relative to the directory containing repl.scm. - The script filename is also the first item of (command-line) when called inside the script, and that's useful only it it's an absolute filename. >> +cat > "$tmpfile"<<EOF >> +#!/usr/bin/env -S guix repl -- > > Rather: > > #!$(type -P env) I didn't know that was possible on a shebang line! > Could you send an updated patch? Done! Cheers, Konrad
Hi, Konrad Hinsen <konrad.hinsen@fastmail.net> skribis: >> “When one or more @var{file} argument is provided, each @var{file} is >> executed as a Guile program:” > > No, that's no longer true. Only one script can be run at a time, because > > guix repl script1.scm script2.scm > > now means "run script1.scm with script2.scm as its argument". And therefore... Ah OK, sorry for the confusion. >>> + (define script-file >>> + (let ((file (car script)) >>> + (directory (getcwd))) >>> + (canonicalize-path >>> + (cond ((string-prefix? "/" file) file) >>> + (else (string-append directory "/" file)))))) >> >> I think we can just use file names as they arrive, without attempting to >> canonicalize them or anything. > > That's what I thought (and tried) as well, at first. Problems: > > - It doesn't work when run via pre-inst-env with a non-absolute > filename for the script. The script is looked up relative > to the directory containing repl.scm. Oh right, that’s because we’re using ‘load’. We should instead do: (load-in-vicinity "." file) Alternatively, (primitive-load file), but in that case the script would be systematically interpreted. > - The script filename is also the first item of (command-line) > when called inside the script, and that's useful only it it's > an absolute filename. In what way is it useful? >>> +cat > "$tmpfile"<<EOF >>> +#!/usr/bin/env -S guix repl -- >> >> Rather: >> >> #!$(type -P env) > > I didn't know that was possible on a shebang line! It’s not, but here it’s evaluated as part of the here-document expansion. Thanks! Ludo’.
* guix/scripts/repl.scm: Add filename options for script execution. * doc/guix.texi (Invoking guix repl): Document it. * tests/guix-repl.sh: Test it. * Makefile.am: (SH_TESTS): Add it. --- Makefile.am | 1 + doc/guix.texi | 51 +++++++++++++++++++++----- guix/scripts/repl.scm | 77 +++++++++++++++++++++++++-------------- tests/guix-repl.sh | 84 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 179 insertions(+), 34 deletions(-) create mode 100644 tests/guix-repl.sh diff --git a/Makefile.am b/Makefile.am index 9cf9318e8a..8988cdfa12 100644 --- a/Makefile.am +++ b/Makefile.am @@ -477,6 +477,7 @@ SH_TESTS = \ tests/guix-environment-container.sh \ tests/guix-graph.sh \ tests/guix-describe.sh \ + tests/guix-repl.sh \ tests/guix-lint.sh TESTS = $(SCM_TESTS) $(SH_TESTS) diff --git a/doc/guix.texi b/doc/guix.texi index 15e077a41c..8ad3a833c6 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -239,7 +239,7 @@ Programming Interface * Derivations:: Low-level interface to package derivations. * The Store Monad:: Purely functional interface to the store. * G-Expressions:: Manipulating build expressions. -* Invoking guix repl:: Fiddling with Guix interactively. +* Invoking guix repl:: Programming Guix in Guile Defining Packages @@ -5474,7 +5474,7 @@ package definitions. * Derivations:: Low-level interface to package derivations. * The Store Monad:: Purely functional interface to the store. * G-Expressions:: Manipulating build expressions. -* Invoking guix repl:: Fiddling with Guix interactively. +* Invoking guix repl:: Programming Guix in Guile @end menu @node Package Modules @@ -8248,12 +8248,47 @@ has an associated gexp compiler, such as a @code{<package>}. @node Invoking guix repl @section Invoking @command{guix repl} -@cindex REPL, read-eval-print loop -The @command{guix repl} command spawns a Guile @dfn{read-eval-print loop} -(REPL) for interactive programming (@pxref{Using Guile Interactively,,, guile, -GNU Guile Reference Manual}). Compared to just launching the @command{guile} +@cindex REPL, read-eval-print loop, script +The @command{guix repl} command makes it easier to program Guix in Guile +by launching a Guile @dfn{read-eval-print loop} (REPL) for interactive +programming (@pxref{Using Guile Interactively,,, guile, +GNU Guile Reference Manual}), or by running Guile scripts +(@pxref{Running Guile Scripts,,, guile, +GNU Guile Reference Manual}). +Compared to just launching the @command{guile} command, @command{guix repl} guarantees that all the Guix modules and all its -dependencies are available in the search path. You can use it this way: +dependencies are available in the search path. + +The general syntax is: + +@example +guix repl @var{options} [@var{file} @var{args}] +@end example + +When a @var{file} argument is provided, @var{file} is +executed as a Guile scripts: + +@example +guix repl my-script.scm +@end example + +To pass arguments to the script, use @code{--} to prevent them from +being interpreted as arguments to @command{guix repl} itself: + +@example +guix repl -- my-script.scm --input=foo.txt +@end example + +To make a script executable directly from the shell, using the guix +executable that is on the user's search path, add the following two +lines at the top of the script: + +@example +@code{#!/usr/bin/env -S guix repl --} +@code{!#} +@end example + +Without a file name argument, a Guile REPL is started: @example $ guix repl @@ -8302,7 +8337,7 @@ Add @var{directory} to the front of the package module search path (@pxref{Package Modules}). This allows users to define their own packages and make them visible to -the command-line tool. +the script or REPL. @item -q Inhibit loading of the @file{~/.guile} file. By default, that diff --git a/guix/scripts/repl.scm b/guix/scripts/repl.scm index ff1f208894..e2679f4301 100644 --- a/guix/scripts/repl.scm +++ b/guix/scripts/repl.scm @@ -1,6 +1,7 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org> ;;; Copyright © 2020 Simon Tournier <zimon.toutoune@gmail.com> +;;; Copyright © 2020 Konrad Hinsen <konrad.hinsen@fastmail.net> ;;; ;;; This file is part of GNU Guix. ;;; @@ -22,6 +23,7 @@ #:use-module (guix scripts) #:use-module (guix repl) #:use-module (srfi srfi-1) + #:use-module (srfi srfi-26) #:use-module (srfi srfi-37) #:use-module (ice-9 match) #:use-module (rnrs bytevectors) @@ -32,7 +34,8 @@ ;;; Commentary: ;;; -;;; This command provides a Guile REPL +;;; This command provides a Guile script runner and REPL in an environment +;;; that contains all the modules comprising Guix. (define %default-options `((type . guile))) @@ -63,8 +66,9 @@ (define (show-help) - (display (G_ "Usage: guix repl [OPTIONS...] -Start a Guile REPL in the Guix execution environment.\n")) + (display (G_ "Usage: guix repl [OPTIONS...] [-- FILE ARGS...] +In the Guix execution environment, run FILE as a Guile script with +command-line arguments ARGS. If no FILE is given, start a Guile REPL.\n")) (display (G_ " -t, --type=TYPE start a REPL of the given TYPE")) (display (G_ " @@ -135,12 +139,13 @@ call THUNK." \f (define (guix-repl . args) (define opts - ;; Return the list of package names. (args-fold* args %options (lambda (opt name arg result) (leave (G_ "~A: unrecognized option~%") name)) (lambda (arg result) - (leave (G_ "~A: extraneous argument~%") arg)) + (append `((script . ,arg) + (ignore-dot-guile . #t)) + result)) %default-options)) (define user-config @@ -148,28 +153,48 @@ call THUNK." (lambda (home) (string-append home "/.guile")))) + (define (set-user-module) + (set-current-module user-module) + (when (and (not (assoc-ref opts 'ignore-dot-guile?)) + user-config + (file-exists? user-config)) + (load user-config))) + + (define script + (reverse + (filter-map (match-lambda + (('script . script) script) + (_ #f)) + opts))) + (with-error-handling - (let ((type (assoc-ref opts 'type))) - (call-with-connection (assoc-ref opts 'listen) - (lambda () - (case type - ((guile) - (save-module-excursion - (lambda () - (set-current-module user-module) - (when (and (not (assoc-ref opts 'ignore-dot-guile?)) - user-config - (file-exists? user-config)) - (load user-config)) - - ;; Do not exit repl on SIGINT. - ((@@ (ice-9 top-repl) call-with-sigint) - (lambda () - (start-repl)))))) - ((machine) - (machine-repl)) - (else - (leave (G_ "~a: unknown type of REPL~%") type)))))))) + + (unless (null? script) + ;; Run script + (save-module-excursion + (lambda () + (set-program-arguments script) + (set-user-module) + (load-in-vicinity "." (car script))))) + + (when (null? script) + ;; Start REPL + (let ((type (assoc-ref opts 'type))) + (call-with-connection (assoc-ref opts 'listen) + (lambda () + (case type + ((guile) + (save-module-excursion + (lambda () + (set-user-module) + ;; Do not exit repl on SIGINT. + ((@@ (ice-9 top-repl) call-with-sigint) + (lambda () + (start-repl)))))) + ((machine) + (machine-repl)) + (else + (leave (G_ "~a: unknown type of REPL~%") type))))))))) ;; Local Variables: ;; eval: (put 'call-with-connection 'scheme-indent-function 1) diff --git a/tests/guix-repl.sh b/tests/guix-repl.sh new file mode 100644 index 0000000000..e1c2b8241f --- /dev/null +++ b/tests/guix-repl.sh @@ -0,0 +1,84 @@ +# GNU Guix --- Functional package management for GNU +# Copyright © 2020 Simon Tournier <zimon.toutoune@gmail.com> +# Copyright © 2020 Konrad Hinsen <konrad.hinsen@fastmail.net> +# +# 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/>. + +# +# Test the `guix repl' command-line utility. +# + +guix repl --version + +test_directory="`mktemp -d`" +export test_directory +trap 'chmod -Rf +w "$test_directory"; rm -rf "$test_directory"' EXIT + +tmpfile="$test_directory/foo.scm" +rm -f "$tmpfile" +trap 'rm -f "$tmpfile"' EXIT + +module_dir="t-guix-repl-$$" +mkdir "$module_dir" +trap 'rm -rf "$module_dir"' EXIT + + +cat > "$tmpfile"<<EOF +(use-modules (guix packages) + (gnu packages base)) + +(format #t "~a\n" (package-name coreutils)) +EOF + +test "`guix repl "$tmpfile"`" = "coreutils" + + +cat > "$module_dir/foo.scm"<<EOF +(define-module (foo) + #:use-module (guix packages) + #:use-module (gnu packages base)) + +(define-public dummy + (package (inherit hello) + (name "dummy") + (version "42") + (synopsis "dummy package") + (description "dummy package. Only used for testing purposes."))) +EOF + +cat > "$tmpfile"<<EOF +(use-modules (guix packages) + (foo)) + +(format #t "~a\n" (package-version dummy)) +EOF + +test "`guix repl "$tmpfile" -L "$module_dir"`" = "42" + +cat > "$tmpfile"<<EOF +(format #t "~a\n" (cdr (command-line))) +EOF + +test "`guix repl -- "$tmpfile" -a b --input=foo.txt`" = "(-a b --input=foo.txt)" + +cat > "$tmpfile"<<EOF +#!$(type -P env) -S guix repl -- +!# +(format #t "~a\n" (cdr (command-line))) +EOF +chmod 755 $tmpfile + +test "`"$tmpfile" -a b --input=foo.txt`" = "(-a b --input=foo.txt)" -- 2.26.2
Hi Ludo, > Oh right, that’s because we’re using ‘load’. We should instead do: > > (load-in-vicinity "." file) Nice! That makes it cleaner indeed, since... >> - The script filename is also the first item of (command-line) >> when called inside the script, and that's useful only it it's >> an absolute filename. > > In what way is it useful? I though it was a POSIX requirement, but (1) I didn't find anything on that topic and (2) plaing Guile just passes on what I type on the command line, so I won't try to do "better". v6 is on the way! Cheers, Konrad
Hi,
Konrad Hinsen <konrad.hinsen@fastmail.net> skribis:
> * guix/scripts/repl.scm: Add filename options for script execution.
> * doc/guix.texi (Invoking guix repl): Document it.
> * tests/guix-repl.sh: Test it.
> * Makefile.am: (SH_TESTS): Add it.
Perfect, applied!
If you want you can followup with an update of ‘etc/news.scm’ to that
people can learn about this change.
Thank you for your work & for your patience!
Ludo’.