From 774aad2ca8744473d3477588582a74b81b69402e Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Tue, 11 Mar 2014 13:37:29 -0500 Subject: [PATCH] defmain macro; handles the whole if __name__ == __main__ / main function dance Example: (defmain [&rest args] (print "now we're having a fun time!") (print args)) Which outputs: $ hy test.hy now we're having a fun time! (['test.hy'],) Includes documentation and tests. --- docs/language/api.rst | 38 +++++++++++++++++++++++++++++++++++ hy/core/macros.hy | 15 ++++++++++++++ tests/resources/bin/main.hy | 5 +++++ tests/resources/bin/nomain.hy | 4 ++++ tests/test_bin.py | 24 ++++++++++++++++++++++ 5 files changed, 86 insertions(+) create mode 100644 tests/resources/bin/main.hy create mode 100644 tests/resources/bin/nomain.hy diff --git a/docs/language/api.rst b/docs/language/api.rst index 57f6029..f9d21ad 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -441,6 +441,44 @@ symbols for function names as the first parameter, `defn-alias` and => (alias) "Hello!" + +defmain +------- + +.. versionadded:: 0.9.13 + +The `defmain` macro defines a main function that is immediately called +with sys.argv as arguments if and only if this file is being executed +as a script. In other words this: + +.. code-block:: clj + + (defmain [&rest args] + (do-something-with args)) + +is the equivalent of:: + + def main(*args): + do_something_with(args) + return 0 + + if __name__ == "__main__": + import sys + retval = main(*sys.arg) + + if isinstance(retval, int): + sys.exit(retval) + +Note, as you can see above, if you return an integer from this +function, this will be used as the exit status for your script. +(Python defaults to exit status 0 otherwise, which means everything's +okay!) + +(Since (sys.exit 0) is not run explicitly in case of a non-integer +return from defmain, it's good to put (defmain) as the last bit of +code in your file.) + + .. _defmacro: defmacro diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 531350e..33f74de 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -171,6 +171,21 @@ (let ~(HyList (map (fn [x] `[~x (gensym (slice '~x 2))]) syms)) ~@body)))) + +(defmacro defmain [args &rest body] + "Write a function named \"main\" and do the if __main__ dance" + (let [[retval (gensym)]] + `(do + (defn main [~@args] + ~@body) + + (when (= --name-- "__main__") + (import sys) + (setv ~retval (apply main sys.argv)) + (if (integer? ~retval) + (sys.exit ~retval)))))) + + (defmacro-alias [defn-alias defun-alias] [names lambda-list &rest body] "define one function with several names" (let [[main (first names)] diff --git a/tests/resources/bin/main.hy b/tests/resources/bin/main.hy new file mode 100644 index 0000000..fbf169f --- /dev/null +++ b/tests/resources/bin/main.hy @@ -0,0 +1,5 @@ +(defmain [&rest args] + (print args) + (print "Hello World") + (if (in "exit1" args) + 1)) diff --git a/tests/resources/bin/nomain.hy b/tests/resources/bin/nomain.hy new file mode 100644 index 0000000..ea559dd --- /dev/null +++ b/tests/resources/bin/nomain.hy @@ -0,0 +1,4 @@ +(print "This Should Still Works") + +(defn main [] + (print "This Should Not Work")) diff --git a/tests/test_bin.py b/tests/test_bin.py index eb0d595..f6fb11c 100644 --- a/tests/test_bin.py +++ b/tests/test_bin.py @@ -142,3 +142,27 @@ def test_bin_hy_builtins(): assert str(exit) == "Use (exit) or Ctrl-D (i.e. EOF) to exit" assert str(quit) == "Use (quit) or Ctrl-D (i.e. EOF) to exit" + + +def test_bin_hy_main(): + ret = run_cmd("hy tests/resources/bin/main.hy") + assert ret[0] == 0 + assert "Hello World" in ret[1] + + +def test_bin_hy_main_args(): + ret = run_cmd("hy tests/resources/bin/main.hy test 123") + assert ret[0] == 0 + assert "test" in ret[1] + assert "123" in ret[1] + + +def test_bin_hy_main_exitvalue(): + ret = run_cmd("hy tests/resources/bin/main.hy exit1") + assert ret[0] == 1 + + +def test_bin_hy_no_main(): + ret = run_cmd("hy tests/resources/bin/nomain.hy") + assert ret[0] == 0 + assert "This Should Still Work" in ret[1]