diff mbox series

[bug#41253,v3] guix repl: Add script execution.

Message ID m1a71ryrsh.fsf@fastmail.net
State Accepted
Headers show
Series [bug#41253,v3] guix repl: Add script execution. | expand

Checks

Context Check Description
cbaines/comparison success View comparision
cbaines/git branch success View Git branch
cbaines/applying patch success View Laminar job

Commit Message

Konrad Hinsen May 29, 2020, 10:11 a.m. UTC
* 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

Comments

Ludovic Courtès June 4, 2020, 3:06 p.m. UTC | #1
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’.
Konrad Hinsen June 5, 2020, 8:48 a.m. UTC | #2
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
Konrad Hinsen June 5, 2020, 10:18 a.m. UTC | #3
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
Ludovic Courtès June 5, 2020, 4:36 p.m. UTC | #4
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’.
Konrad Hinsen June 6, 2020, 5:22 a.m. UTC | #5
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.
diff mbox series

Patch

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."
 
 (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)"