From df7bb1d29acc2d09ebe18fa4cb66b2de8d57ff35 Mon Sep 17 00:00:00 2001 From: Bob Tolbert Date: Sat, 29 Jun 2013 15:56:58 -0600 Subject: [PATCH] Provide bin scripts for both Windows and *nix Summary: This update does away with the scripts in bin and changes setup.py to use entry_points in cmdline.py for the scripts 'hy' and 'hyc'. This fixes installing and running on Windows. The tests are updated to run the 'hy' script produced by setup.py and not from bin/hy. This is more correct and makes the tox tests run on both Window and *nix. For running hy or nosetests directly in the source tree, you do have to run 'python setup.py develop' first. But since tox runs and builds dists, all tox tests pass on all platforms. Also, since there is no built-in readline on Windows, the setup.py only on Windows requires 'pyreadline' as a replacement. Switched from optparse to argparse in cmdline.py Instead of trying to manually separate args meant for hy from args meant for a hy script, this switches from optparse to argparse for the CLI. argparse automatically peels out args meant for hy and leaves the rest, including the user hy script in options.args. This fixes the issue @paultag found running "hy foo" where foo is not a real file. Also added a test that makes sure trying to run a non-existent script exits instead of dropping the user into the REPL. Added argparse as setup.py resource (and removed from tox.ini) as well as removed uses of deprecated setf --- .gitignore | 1 + .travis.yml | 2 +- AUTHORS | 1 + bin/hy | 8 --- bin/hyc | 6 -- hy/cmdline.py | 57 +++++++++++----- make.bat | 116 +++++++++++++++++++++++++++++++++ setup.py | 15 +++-- tests/resources/argparse_ex.hy | 18 +++++ tests/test_bin.py | 44 +++++++++++-- 10 files changed, 225 insertions(+), 43 deletions(-) delete mode 100755 bin/hy delete mode 100755 bin/hyc create mode 100644 make.bat create mode 100755 tests/resources/argparse_ex.hy diff --git a/.gitignore b/.gitignore index 39a6fc8..e46a222 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.pyc *swp *hy*egg* +*pyreadline*egg* .tox *pycache* dist diff --git a/.travis.yml b/.travis.yml index 1b8ef6e..124afc7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ python: # command to install dependencies install: - pip install -r requirements.txt --use-mirrors - - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install importlib unittest2 astor --use-mirrors; fi + - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install argparse importlib unittest2 astor --use-mirrors; fi - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install astor --use-mirrors; fi - if [[ $TRAVIS_PYTHON_VERSION == 'pypy' ]]; then pip install astor --use-mirrors; fi - python setup.py -q install diff --git a/AUTHORS b/AUTHORS index 3630a44..eb253ba 100644 --- a/AUTHORS +++ b/AUTHORS @@ -14,4 +14,5 @@ * Thomas Ballinger * Morten Linderud * Guillermo Vayá +* Bob Tolbert * Ralph Möritz diff --git a/bin/hy b/bin/hy deleted file mode 100755 index 14cf5e0..0000000 --- a/bin/hy +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python - -import sys -from hy.cmdline import cmdline_handler - - -if __name__ == '__main__': - sys.exit(cmdline_handler("hy", sys.argv)) diff --git a/bin/hyc b/bin/hyc deleted file mode 100755 index c371b4b..0000000 --- a/bin/hyc +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env hy - -(import sys) -(import [hy.importer [write-hy-as-pyc]]) - -(write-hy-as-pyc (get sys.argv 1)) diff --git a/hy/cmdline.py b/hy/cmdline.py index 4eca900..48b5d8c 100644 --- a/hy/cmdline.py +++ b/hy/cmdline.py @@ -24,7 +24,7 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -import optparse +import argparse import code import ast import sys @@ -198,26 +198,40 @@ def run_icommand(source): return run_repl(hr) -USAGE = "usage: %prog [-h | -i cmd | -c cmd | file | -]" -VERSION = "%prog " + hy.__version__ +USAGE = "%(prog)s [-h | -i cmd | -c cmd | file | -] [arg] ..." +VERSION = "%(prog)s " + hy.__version__ EPILOG = """ file program read from script - program read from stdin + [arg] ... arguments passed to program in sys.argv[1:] """ def cmdline_handler(scriptname, argv): - parser = optparse.OptionParser(usage=USAGE, version=VERSION) - parser.add_option( - "-c", dest="command", metavar="COMMAND", - help="program passed in as string") - parser.add_option( - "-i", dest="icommand", metavar="ICOMMAND", - help="program passed in as string, then stay in repl") + parser = argparse.ArgumentParser( + prog="hy", + usage=USAGE, + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=EPILOG) + parser.add_argument("-c", dest="command", + help="program passed in as a string") + parser.add_argument( + "-i", dest="icommand", + help="program passed in as a string, then stay in REPL") - # Hylarious way of adding non-option options to help text - parser.format_epilog = lambda self: EPILOG + parser.add_argument("-v", action="version", version=VERSION) - (options, args) = parser.parse_args() + # this will contain the script/program name and any arguments for it. + parser.add_argument('args', nargs=argparse.REMAINDER, + help=argparse.SUPPRESS) + + # stash the hy exectuable in case we need it later + # mimics Python sys.executable + hy.executable = argv[0] + + options = parser.parse_args(argv[1:]) + + # reset sys.argv like Python + sys.argv = options.args if options.command: # User did "hy -c ..." @@ -227,14 +241,25 @@ def cmdline_handler(scriptname, argv): # User did "hy -i ..." return run_icommand(options.icommand) - if args: - if args[0] == "-": + if options.args: + if options.args[0] == "-": # Read the program from stdin return run_command(sys.stdin.read()) else: # User did "hy " - return run_file(args[0]) + return run_file(options.args[0]) # User did NOTHING! return run_repl() + + +# entry point for cmd line script "hy" +def hy_main(): + sys.exit(cmdline_handler("hy", sys.argv)) + + +# entry point for cmd line script "hyc" +def hyc_main(): + from hy.importer import write_hy_as_pyc + write_hy_as_pyc(sys.argv[1]) diff --git a/make.bat b/make.bat new file mode 100644 index 0000000..ccf6138 --- /dev/null +++ b/make.bat @@ -0,0 +1,116 @@ +@ECHO OFF + +REM Make batch file for Hy development + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo. No default step. Use setup.py + echo. + echo. Other targets: + echo. + echo. - docs + echo. - full + echo. + echo. - dev "test & flake" + echo. - flake + echo. - test + echo. - diff + echo. - tox + echo. - d + echo. - r + echo. + goto end +) + +if "%1" == "docs" ( +:docs + echo.docs not yet supported under Windows +goto :EOF +) + +if "%1" == "upload" ( +:upload + python setup.py sdist upload +goto :EOF +) + +if "%1" == "clear" ( +:clear + cls +goto :EOF +) + +if "%1" == "d" ( +:d + call :clear + call :dev +goto :EOF +) + +if "%1" == "test" ( +:test + call :venv + nosetests -sv +goto :EOF +) + +if "%1" == "venv" ( +:venv + echo.%VIRTUAL_ENV% | findstr /C:"hy" 1>nul + if errorlevel 1 ( + echo.You're not in a Hy virtualenv. FOR SHAME + ) ELSE ( + echo.We're properly in a virtualenv. Going ahead. + ) +goto :EOF +) + +if "%1" == "flake" ( +:flake + echo.flake8 hy + flake8 hy +goto :EOF +) + +if "%1" == "dev" ( +:dev + call :test + call :flake +goto :EOF +) + +if "%1" == "tox" ( +:tox + call :venv + tox -e "py26,py27,py32,py33,flake8" +goto :EOF +) + +if "%1" == "d" ( +:d + call :clear + call :dev +goto :EOF +) + +if "%i" == "diff" ( +:diff + git diff --color +goto :EOF +) + +if "%1" == "r" ( +:r + call :d + call :tox + call :diff +goto :EOF +) + +if "%1" == full ( + call :docs + call :d + call :tox +) \ No newline at end of file diff --git a/setup.py b/setup.py index c3bb159..8b6ba62 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (c) 2012 Paul Tagliamonte +# Copyright (c) 2012, 2013 Paul Tagliamonte # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), @@ -23,12 +23,15 @@ from hy import __appname__, __version__ from setuptools import setup import os +import sys long_description = """Hy is a Python <--> Lisp layer. It helps make things work nicer, and lets Python and the Hy lisp variant play nice together. """ install_requires = [] +if sys.version_info[0] == 2: + install_requires.append('argparse>=1.2.1') if os.name == 'nt': install_requires.append('pyreadline==2.0') @@ -36,10 +39,12 @@ setup( name=__appname__, version=__version__, install_requires=install_requires, - scripts=[ - "bin/hy", - "bin/hyc", - ], + entry_points={ + 'console_scripts': [ + 'hy = hy.cmdline:hy_main', + 'hyc = hy.cmdline:hyc_main' + ] + }, packages=[ 'hy', 'hy.lex', diff --git a/tests/resources/argparse_ex.hy b/tests/resources/argparse_ex.hy new file mode 100755 index 0000000..9b2500f --- /dev/null +++ b/tests/resources/argparse_ex.hy @@ -0,0 +1,18 @@ +#!/usr/bin/env hy + +(import sys) +(import argparse) + +(setv parser (argparse.ArgumentParser)) + +(.add_argument parser "-i") +(.add_argument parser "-c") + +(setv args (.parse_args parser)) + +;; using (cond) allows -i to take precedence over -c + +(cond (args.i + (print (str args.i))) + (args.c + (print (str "got c")))) diff --git a/tests/test_bin.py b/tests/test_bin.py index 4cdfb58..3a4849c 100644 --- a/tests/test_bin.py +++ b/tests/test_bin.py @@ -25,13 +25,18 @@ import subprocess import sys -def run_cmd(cmd): +def run_cmd(cmd, stdin_data=None): p = subprocess.Popen(cmd, + stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) stdout = "" stderr = "" + if stdin_data is not None: + p.stdin.write(stdin_data.encode('ASCII')) + p.stdin.flush() + p.stdin.close() # Read stdout and stderr otherwise if the PIPE buffer is full, we might # wait for ever… while p.poll() is None: @@ -41,28 +46,28 @@ def run_cmd(cmd): def test_bin_hy(): - ret = run_cmd("echo | bin/hy") + ret = run_cmd("hy", "") assert ret[0] == 0 def test_bin_hy_stdin(): - ret = run_cmd("echo \"(koan)\" | bin/hy") + ret = run_cmd("hy", '(koan)') assert ret[0] == 0 assert "monk" in ret[1] def test_bin_hy_cmd(): - ret = run_cmd("bin/hy -c \"(koan)\"") + ret = run_cmd("hy -c \"(koan)\"") assert ret[0] == 0 assert "monk" in ret[1] - ret = run_cmd("bin/hy -c \"(koan\"") + ret = run_cmd("hy -c \"(koan\"") assert ret[0] == 1 assert "LexException" in ret[1] def test_bin_hy_icmd(): - ret = run_cmd("echo \"(ideas)\" | bin/hy -i \"(koan)\"") + ret = run_cmd("hy -i \"(koan)\"", "(ideas)") assert ret[0] == 0 output = ret[1] @@ -71,16 +76,41 @@ def test_bin_hy_icmd(): def test_bin_hy_file(): - ret = run_cmd("bin/hy eg/nonfree/halting-problem/halting.hy") + ret = run_cmd("hy eg/nonfree/halting-problem/halting.hy") assert ret[0] == 0 assert "27" in ret[1] +def test_bin_hy_missing_file(): + ret = run_cmd("hy foobarbaz") + assert ret[0] == 1 + assert "No such file" in ret[2] + + +def test_bin_hy_file_with_args(): + ret = run_cmd("hy tests/resources/argparse_ex.hy -h") + assert ret[0] == 0 + assert "usage" in ret[1] + ret = run_cmd("hy tests/resources/argparse_ex.hy -c bar") + assert ret[0] == 0 + assert "got c" in ret[1] + ret = run_cmd("hy tests/resources/argparse_ex.hy -i foo") + assert ret[0] == 0 + assert "foo" in ret[1] + ret = run_cmd("hy tests/resources/argparse_ex.hy -i foo -c bar") + assert ret[0] == 0 + assert "foo" in ret[1] + + def test_hy2py(): # XXX Astor doesn't seem to support Python3 :( if sys.version_info[0] == 3: return + # and running this script this way doesn't work on Windows + if os.name == "nt": + return + i = 0 for dirpath, dirnames, filenames in os.walk("tests/native_tests"): for f in filenames: