├── LICENSE ├── README.md ├── quick ├── quick-exec ├── quick-exec-p ├── setup.py └── test.clj /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 4 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 5 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial code and documentation 12 | distributed under this Agreement, and 13 | b) in the case of each subsequent Contributor: 14 | i) changes to the Program, and 15 | ii) additions to the Program; 16 | 17 | where such changes and/or additions to the Program originate from and are 18 | distributed by that particular Contributor. A Contribution 'originates' from 19 | a Contributor if it was added to the Program by such Contributor itself or 20 | anyone acting on such Contributor's behalf. Contributions do not include 21 | additions to the Program which: (i) are separate modules of software 22 | distributed in conjunction with the Program under their own license 23 | agreement, and (ii) are not derivative works of the Program. 24 | 25 | "Contributor" means any person or entity that distributes the Program. 26 | 27 | "Licensed Patents" mean patent claims licensable by a Contributor which are 28 | necessarily infringed by the use or sale of its Contribution alone or when 29 | combined with the Program. 30 | 31 | "Program" means the Contributions distributed in accordance with this Agreement. 32 | 33 | "Recipient" means anyone who receives the Program under this Agreement, 34 | including all Contributors. 35 | 36 | 2. GRANT OF RIGHTS 37 | a) Subject to the terms of this Agreement, each Contributor hereby grants 38 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 39 | reproduce, prepare derivative works of, publicly display, publicly perform, 40 | distribute and sublicense the Contribution of such Contributor, if any, and 41 | such derivative works, in source code and object code form. 42 | b) Subject to the terms of this Agreement, each Contributor hereby grants 43 | Recipient a non-exclusive, worldwide, royalty-free patent license under 44 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 45 | transfer the Contribution of such Contributor, if any, in source code and 46 | object code form. This patent license shall apply to the combination of the 47 | Contribution and the Program if, at the time the Contribution is added by 48 | the Contributor, such addition of the Contribution causes such combination 49 | to be covered by the Licensed Patents. The patent license shall not apply 50 | to any other combinations which include the Contribution. No hardware per 51 | se is licensed hereunder. 52 | c) Recipient understands that although each Contributor grants the licenses to 53 | its Contributions set forth herein, no assurances are provided by any 54 | Contributor that the Program does not infringe the patent or other 55 | intellectual property rights of any other entity. Each Contributor 56 | disclaims any liability to Recipient for claims brought by any other entity 57 | based on infringement of intellectual property rights or otherwise. As a 58 | condition to exercising the rights and licenses granted hereunder, each 59 | Recipient hereby assumes sole responsibility to secure any other 60 | intellectual property rights needed, if any. For example, if a third party 61 | patent license is required to allow Recipient to distribute the Program, it 62 | is Recipient's responsibility to acquire that license before distributing 63 | the Program. 64 | d) Each Contributor represents that to its knowledge it has sufficient 65 | copyright rights in its Contribution, if any, to grant the copyright 66 | license set forth in this Agreement. 67 | 68 | 3. REQUIREMENTS 69 | 70 | A Contributor may choose to distribute the Program in object code form under its 71 | own license agreement, provided that: 72 | 73 | a) it complies with the terms and conditions of this Agreement; and 74 | b) its license agreement: 75 | i) effectively disclaims on behalf of all Contributors all warranties and 76 | conditions, express and implied, including warranties or conditions of 77 | title and non-infringement, and implied warranties or conditions of 78 | merchantability and fitness for a particular purpose; 79 | ii) effectively excludes on behalf of all Contributors all liability for 80 | damages, including direct, indirect, special, incidental and 81 | consequential damages, such as lost profits; 82 | iii) states that any provisions which differ from this Agreement are offered 83 | by that Contributor alone and not by any other party; and 84 | iv) states that source code for the Program is available from such 85 | Contributor, and informs licensees how to obtain it in a reasonable 86 | manner on or through a medium customarily used for software exchange. 87 | 88 | When the Program is made available in source code form: 89 | 90 | a) it must be made available under this Agreement; and 91 | b) a copy of this Agreement must be included with each copy of the Program. 92 | Contributors may not remove or alter any copyright notices contained within 93 | the Program. 94 | 95 | Each Contributor must identify itself as the originator of its Contribution, if 96 | any, in a manner that reasonably allows subsequent Recipients to identify the 97 | originator of the Contribution. 98 | 99 | 4. COMMERCIAL DISTRIBUTION 100 | 101 | Commercial distributors of software may accept certain responsibilities with 102 | respect to end users, business partners and the like. While this license is 103 | intended to facilitate the commercial use of the Program, the Contributor who 104 | includes the Program in a commercial product offering should do so in a manner 105 | which does not create potential liability for other Contributors. Therefore, if 106 | a Contributor includes the Program in a commercial product offering, such 107 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify 108 | every other Contributor ("Indemnified Contributor") against any losses, damages 109 | and costs (collectively "Losses") arising from claims, lawsuits and other legal 110 | actions brought by a third party against the Indemnified Contributor to the 111 | extent caused by the acts or omissions of such Commercial Contributor in 112 | connection with its distribution of the Program in a commercial product 113 | offering. The obligations in this section do not apply to any claims or Losses 114 | relating to any actual or alleged intellectual property infringement. In order 115 | to qualify, an Indemnified Contributor must: a) promptly notify the Commercial 116 | Contributor in writing of such claim, and b) allow the Commercial Contributor to 117 | control, and cooperate with the Commercial Contributor in, the defense and any 118 | related settlement negotiations. The Indemnified Contributor may participate in 119 | any such claim at its own expense. 120 | 121 | For example, a Contributor might include the Program in a commercial product 122 | offering, Product X. That Contributor is then a Commercial Contributor. If that 123 | Commercial Contributor then makes performance claims, or offers warranties 124 | related to Product X, those performance claims and warranties are such 125 | Commercial Contributor's responsibility alone. Under this section, the 126 | Commercial Contributor would have to defend claims against the other 127 | Contributors related to those performance claims and warranties, and if a court 128 | requires any other Contributor to pay any damages as a result, the Commercial 129 | Contributor must pay those damages. 130 | 131 | 5. NO WARRANTY 132 | 133 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN 134 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 135 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, 136 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each 137 | Recipient is solely responsible for determining the appropriateness of using and 138 | distributing the Program and assumes all risks associated with its exercise of 139 | rights under this Agreement , including but not limited to the risks and costs 140 | of program errors, compliance with applicable laws, damage to or loss of data, 141 | programs or equipment, and unavailability or interruption of operations. 142 | 143 | 6. DISCLAIMER OF LIABILITY 144 | 145 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 146 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 147 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST 148 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 149 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 150 | OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS 151 | GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 152 | 153 | 7. GENERAL 154 | 155 | If any provision of this Agreement is invalid or unenforceable under applicable 156 | law, it shall not affect the validity or enforceability of the remainder of the 157 | terms of this Agreement, and without further action by the parties hereto, such 158 | provision shall be reformed to the minimum extent necessary to make such 159 | provision valid and enforceable. 160 | 161 | If Recipient institutes patent litigation against any entity (including a 162 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 163 | (excluding combinations of the Program with other software or hardware) 164 | infringes such Recipient's patent(s), then such Recipient's rights granted under 165 | Section 2(b) shall terminate as of the date such litigation is filed. 166 | 167 | All Recipient's rights under this Agreement shall terminate if it fails to 168 | comply with any of the material terms or conditions of this Agreement and does 169 | not cure such failure in a reasonable period of time after becoming aware of 170 | such noncompliance. If all Recipient's rights under this Agreement terminate, 171 | Recipient agrees to cease use and distribution of the Program as soon as 172 | reasonably practicable. However, Recipient's obligations under this Agreement 173 | and any licenses granted by Recipient relating to the Program shall continue and 174 | survive. 175 | 176 | Everyone is permitted to copy and distribute copies of this Agreement, but in 177 | order to avoid inconsistency the Agreement is copyrighted and may only be 178 | modified in the following manner. The Agreement Steward reserves the right to 179 | publish new versions (including revisions) of this Agreement from time to time. 180 | No one other than the Agreement Steward has the right to modify this Agreement. 181 | The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation 182 | may assign the responsibility to serve as the Agreement Steward to a suitable 183 | separate entity. Each new version of the Agreement will be given a 184 | distinguishing version number. The Program (including Contributions) may always 185 | be distributed subject to the version of the Agreement under which it was 186 | received. In addition, after a new version of the Agreement is published, 187 | Contributor may elect to distribute the Program (including its Contributions) 188 | under the new version. Except as expressly stated in Sections 2(a) and 2(b) 189 | above, Recipient receives no rights or licenses to the intellectual property of 190 | any Contributor under this Agreement, whether expressly, by implication, 191 | estoppel or otherwise. All rights in the Program not expressly granted under 192 | this Agreement are reserved. 193 | 194 | This Agreement is governed by the laws of the State of New York and the 195 | intellectual property laws of the United States of America. No party to this 196 | Agreement will bring a legal action under this Agreement more than one year 197 | after the cause of action arose. Each party waives its rights to a jury trial in 198 | any resulting litigation. 199 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | quick-clojure 2 | ============= 3 | 4 | Run clojure scripts and lein commands quickly using a persistent nREPL session. Heavily inspired by [grenchman](http://leiningen.org/grench.html). This script differs in grenchman in several ways: it is written in Python, so no compiling or downloading platform-specific binaries is required. Also, the script automatically starts a headless nREPL server if none is currently running (however, see the *Caveats* section below). 5 | 6 | Installation 7 | ============ 8 | 9 | ```pip install quick-clojure``` 10 | 11 | Usage 12 | ===== 13 | 14 | The ```quick-clojure``` python package installs the ```quick```, ```quick-exec```, and ```quick-exec-p``` commands. ```quick``` is the main script. ```quick-exec``` and ```quick-exec-p``` are the equivalents of the ```lein-exec``` and ```lein-exec-p``` scripts. They are there for you to use in shebang lines. (See the excellent [lein-exec plugin](https://github.com/kumarshantanu/lein-exec) for more information). 15 | 16 | ``` 17 | Commands: 18 | 19 | eval FORM Evals given form. 20 | repl [PORT] Connects a repl to a running nREPL server. 21 | run NAMESPACE[/FUNCTION] [ARGS...] Runs existing defn. 22 | lein [TASK ARGS...] Runs a Leiningen task. 23 | start [PORT] Start a nREPL server. 24 | kill [PORT] Kill a running nREPL server. 25 | restart [PORT] Restart a running nREPL server. 26 | 27 | Running with no arguments will read code from stdin. 28 | ``` 29 | 30 | Tips 31 | ====== 32 | 33 | [Repload](https://github.com/john2x/repload) Integration: You can force the server to run ```(repload)``` before each command by setting the ```REPLOAD``` environment variable. This should automatically reload any user-defined symbols. It should be useful if you're writing and testing development code. 34 | 35 | You can set the ```DEBUG``` environment variable to debug nREPL message. The orange ones are client -> server messages, the red ones are server -> client messages. 36 | 37 | Caveats 38 | ========= 39 | 40 | There is currently a race condition when starting up the nREPL server for the first time, which could result in multiple nREPL servers starting up, only one of which is referenced by the ```~/.lein/.repl-port``` file. So if you are planning on running a bunch of code in parallel, start the server first with a ```quick start``` command, at least until I figure out how to fix it. Patches welcome :) 41 | 42 | Bugs 43 | ========= 44 | 45 | I'm sure there are lots of them :) If you find any please write an issue or pull request so I can fix them. 46 | 47 | -------------------------------------------------------------------------------- /quick: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | Quick runs Clojure code quickly. 5 | 6 | Commands: 7 | 8 | eval FORM Evals given form. 9 | repl [PORT] Connects a repl to a running nREPL server. 10 | run NAMESPACE[/FUNCTION] [ARGS...] Runs existing defn. 11 | lein [TASK ARGS...] Runs a Leiningen task. 12 | start [PORT] Start a nREPL server. 13 | kill [PORT] Kill a running nREPL server. 14 | restart [PORT] Restart a running nREPL server. 15 | 16 | Running with no arguments will read code from stdin. 17 | ''' 18 | 19 | # for python 3 compatibility, in case nrepl author ever ports to python 3 20 | from __future__ import (absolute_import, division, print_function, unicode_literals) 21 | from future import standard_library 22 | from future.builtins import * 23 | from future.utils import native_str, native 24 | 25 | import os, os.path, sys, subprocess, re, readline, warnings, uuid, __builtin__, time 26 | from pprint import pformat 27 | import nrepl 28 | 29 | devnull = open(os.devnull,'w') 30 | debug = sys.stderr if 'DEBUG' in os.environ else devnull 31 | 32 | def find_root(cwd=None): 33 | if cwd is None: cwd = os.getcwd() 34 | while not os.path.dirname(cwd) == cwd: 35 | if os.path.isfile(os.path.join(cwd, 'project.clj')): 36 | return cwd 37 | cwd = os.path.dirname(cwd) 38 | return None 39 | 40 | def get_port(toplevel=None): 41 | ''' Figure out what port the nREPL server is set to. ''' 42 | # check to see if LEIN_REPL_PORT is set. 43 | if os.environ.get('LEIN_REPL_PORT') is not None and os.environ.get('LEIN_REPL_PORT').isdigit(): 44 | return int(os.environ.get('LEIN_REPL_PORT')) 45 | 46 | # look for first project.clj in current directory and all parent directories to 47 | # find the project directory. Check to see if a .nrepl-port file exists in the 48 | # project directory. 49 | if toplevel is None or toplevel == False: 50 | root = find_root() 51 | if root is not None: 52 | nrepl_port = os.path.join(root, '.nrepl-port') 53 | if os.path.isfile(nrepl_port): 54 | with open(nrepl_port) as file: 55 | port = file.read() 56 | if port is not None and port.isdigit(): 57 | return int(port) 58 | else: 59 | return None 60 | 61 | # Check to see if ~/.lein/repl-port file exists. 62 | if toplevel is None or toplevel == True: 63 | repl_port = os.path.expanduser('~/.lein/repl-port') 64 | if os.path.isfile(repl_port): 65 | with open(repl_port) as file: 66 | port = file.read() 67 | if port is not None and port.isdigit(): 68 | return int(port) 69 | # no running port number could be found 70 | return None 71 | 72 | # TODO: fix race condition 73 | def start_nrepl(port=None, toplevel=False): 74 | cwd = {'cwd': os.path.expanduser('~')} if toplevel else {} 75 | proc = subprocess.Popen( 76 | ['lein','repl',':headless']+([':port',port] if port is not None else []), 77 | stdout=subprocess.PIPE, stderr=devnull, stdin=devnull, **cwd) 78 | 79 | line = proc.stdout.readline() 80 | port = None 81 | while port is None and line is not None: 82 | print(line, end='', file=sys.stderr) 83 | match = re.match(r"^.*port ([0-9]+).*$", line.rstrip()) 84 | if match is not None: 85 | port = int(match.group(1)) 86 | break 87 | line = proc.stdout.readline() 88 | 89 | if port is None: 90 | raise Exception("Could not determine port of nREPL instance") 91 | return port 92 | 93 | def start(port=None, quiet=True, toplevel=None): 94 | running_port = get_port(toplevel=toplevel) 95 | if running_port: 96 | if not quiet: 97 | print('nREPL server is already running on port %s' % (running_port), file=sys.stderr) 98 | return running_port 99 | # if no port could be found, spawn a "lein repl :headless" as a background 100 | # process, then read the port number from stdout. 101 | return start_nrepl(port=port, toplevel=toplevel) 102 | 103 | def kill(port=None, quiet=True): 104 | running_port = get_port() 105 | if running_port is None: 106 | print('No nREPL session is currently running') 107 | sys.exit(1) 108 | evaluate('(System/exit 0)', port=running_port) 109 | if not quiet: 110 | print('Terminated nREPL instance on port %s' % (running_port)) 111 | 112 | def restart(port=None): 113 | kill() 114 | port = start_nrepl(port) 115 | print("Restarted nREPL on port %s" % (port)) 116 | 117 | class EvalError(Exception): pass 118 | 119 | current_id = None 120 | def write(con, message): 121 | if 'id' in message: current_id = message['id'] 122 | con.write(message) 123 | print('\033[93m'+pformat(message)+'\033[0m', file=debug) 124 | 125 | def read(con): 126 | message = con.read() 127 | print('\033[91m'+pformat(message)+'\033[0m', file=debug) 128 | return message 129 | 130 | def load_repload(port=None, con=None, session=None, id='main'): 131 | code = """(require 'repload) 132 | (require '[repload :refer [repload]]) 133 | (repload)""" 134 | evaluate(code, port=port, con=con, session=session, out=debug, err=debug) 135 | 136 | # TODO: handle exit status 137 | def evaluate(code, port=None, con=None, out=sys.stdout, err=sys.stderr, 138 | session=None, id='main', toplevel=None, **kwargs): 139 | # connect to the nREPL server 140 | if port is None: port = start(toplevel=toplevel) 141 | if con is None: 142 | url = 'nrepl://localhost:%i' % (port) 143 | con = nrepl.connect(native_str(url)) 144 | 145 | # get a new session id 146 | if session is None: 147 | write(con, {'id': 'init', 'op': 'clone'}) 148 | message = read(con) 149 | session = message['new-session'] 150 | if session is None: raise Exception("No session returned from clone op") 151 | if 'REPLOAD' in os.environ: load_repload(port=port, con=con, session=session, id=id) 152 | 153 | # run the eval command on the code 154 | opts = {'op': 'eval', 'code': code, 'id': id, 'session': session} 155 | opts.update(kwargs) 156 | write(con, opts) 157 | 158 | # handle any messages sent back until all the task ids are status done 159 | value = None 160 | ids = {id: True} 161 | while any(ids): 162 | try: 163 | message = read(con); 164 | if message is None: 165 | print('Got None message', file=debug) 166 | break 167 | elif not isinstance(message,dict): 168 | warnings.warn('Got non-dict message: '+pformat(message)) 169 | elif 'status' in message and isinstance(message['status'],list) and len(message['status'])>0: 170 | if message['status'][0] == 'done' and 'id' in message: 171 | del ids[message['id']] 172 | elif message['status'][0] == 'eval-error': 173 | pass 174 | elif message['status'][0] == 'need-input': 175 | input_id = str(uuid.uuid4()) 176 | opts = {'op': 'stdin', 177 | 'session': session, 178 | 'stdin': sys.stdin.read(1) if sys.stdin.isatty() else sys.stdin.read(), 179 | 'id': input_id} 180 | ids[input_id] = True 181 | write(con, opts) 182 | else: 183 | raise Exception('Eval returned bad exit status: %s' % (message['status'][0])) 184 | elif 'value' in message: 185 | value = message 186 | elif 'out' in message: 187 | print(message['out'], end='', file=out) 188 | elif 'err' in message: 189 | print(message['err'], end='', file=err) 190 | else: 191 | warnings.warn("Don't know how to handle message: %s" % (message)) 192 | except KeyboardInterrupt: 193 | if current_id is not None: 194 | opts = {'op': 'interrupt', 195 | 'session': session, 196 | 'interrupt-id': current_id, 197 | 'id': 'interrupt-'+str(uuid.uuid4())} 198 | write(con, opts) 199 | return value 200 | 201 | def repl(port=None, con=None): 202 | readline_config = ['set blink-matching-paren on'] 203 | for line in readline_config: 204 | readline.parse_and_bind(line) 205 | 206 | # get a connection 207 | if port is None: port = start() 208 | if con is None: 209 | url = 'nrepl://localhost:%i' % (port) 210 | con = nrepl.connect(native_str(url)) 211 | 212 | # get a session 213 | write(con, {'id': 'init', 'op': 'clone'}) 214 | message = read(con); 215 | session = message['new-session'] 216 | if session is None: raise Exception("No session returned from clone op") 217 | if 'REPLOAD' in os.environ: load_repload(port=port, con=con, session=session, id=id) 218 | 219 | # get the default namespace 220 | ns = evaluate('nil', port=port, con=con, session=session)['ns'] 221 | 222 | # send a dummy form to get the current namespace 223 | # start the repl 224 | while (True): 225 | try: 226 | line = input('%s=>' % (ns)) 227 | code = """(try %s (catch Exception e (clojure.stacktrace/print-cause-trace e)))""" % (line) 228 | result = evaluate(code, port=port, con=con, session=session) 229 | if result is not None: 230 | if 'ns' in result: 231 | ns = result['ns'] 232 | print(result['value']) 233 | except (EOFError, KeyboardInterrupt): 234 | print() 235 | break 236 | except (EvalError) as e: 237 | print(str(e), file=sys.stderr) 238 | except: 239 | print('Unexpected error:', sys.exc_info()[0]) 240 | raise 241 | 242 | def escape(*symbols): 243 | return ' '.join(map(lambda x: '"'+x.replace(r'["\\]',r'\\\1')+'"', symbols)) 244 | 245 | def run(name, *args): 246 | code = """(let [raw (symbol %s) 247 | ns (symbol (or (namespace raw) raw)) 248 | m-sym (if (namespace raw) (symbol (name raw)) '-main)] 249 | (require ns) 250 | (try ((ns-resolve ns m-sym) %s) 251 | (catch Exception e 252 | (let [c (:exit-code (ex-data e))] 253 | (clojure.stacktrace/print-cause-trace e) 254 | (if (number? c) c 0)))))""" % (escape(name), escape(*args)) 255 | result = evaluate(code, ns='user') 256 | return None if result is None else int(result['value']) 257 | 258 | def lein(*args): 259 | cwd = os.getcwd() 260 | root = find_root() 261 | code = """(binding [*cwd* %s, *exit-process?* false] 262 | (System/setProperty "leiningen.original.pwd" %s) 263 | (defmethod leiningen.core.eval/eval-in :default 264 | [project form] 265 | (leiningen.core.eval/eval-in 266 | (assoc project :eval-in :nrepl) form)) 267 | (defmethod leiningen.core.eval/eval-in :trampoline 268 | [& _] (throw (Exception. "trampoline disabled"))) 269 | (try (-main %s)) 270 | (catch clojure.lang.ExceptionInfo e 271 | (let [c (:exit-code (ex-data e))] 272 | (if (number? c) c 0))))""" % ( 273 | escape(cwd if root is None else root), 274 | escape(cwd), 275 | escape(*args)) 276 | result = evaluate(code, ns='leiningen.core.main', toplevel=True) 277 | return None if result is None or result['value'] == 'nil' else __builtin__.int(result['value']) % 128 278 | 279 | # parse command-line arguments 280 | exit_code = None 281 | if len(sys.argv) > 2 and sys.argv[1] == 'eval': print(evaluate(sys.argv[2])['value']) 282 | elif len(sys.argv) > 1 and sys.argv[1] == 'start': start(*sys.argv[2:3], quiet=False) 283 | elif len(sys.argv) > 1 and sys.argv[1] == 'kill': kill(*sys.argv[2:3], quiet=False) 284 | elif len(sys.argv) > 1 and sys.argv[1] == 'restart': restart(*sys.argv[2:3]) 285 | elif len(sys.argv) > 1 and sys.argv[1] == 'repl': repl(*sys.argv[2:3]) 286 | elif len(sys.argv) > 2 and sys.argv[1] == 'run': exit_code = run(*sys.argv[2:]) 287 | elif len(sys.argv) > 1 and sys.argv[1] == 'lein': exit_code = lein(*sys.argv[2:]) 288 | elif not sys.stdin.isatty(): print(evaluate(sys.stdin.read())['value']) 289 | else: 290 | print(__doc__, file=sys.stderr) 291 | sys.exit(1) 292 | 293 | exit_code = 0 if exit_code is None else exit_code 294 | print("Exited with status: "+pformat(exit_code), file=debug) 295 | sys.exit(exit_code) 296 | -------------------------------------------------------------------------------- /quick-exec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os, sys 3 | os.execvp(os.path.join(os.path.dirname(__file__),'quick'), 4 | [os.path.join(os.path.dirname(__file__),'quick'), 5 | 'lein','exec']+sys.argv[1:]) 6 | -------------------------------------------------------------------------------- /quick-exec-p: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os, sys 3 | os.execvp(os.path.join(os.path.dirname(__file__),'quick'), 4 | [os.path.join(os.path.dirname(__file__),'quick'), 5 | 'lein','exec','-p']+sys.argv[1:]) 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | ''' 2 | quick 3 | ------------------- 4 | 5 | Run clojure scripts and lein commands quickly using a persistent nREPL session. 6 | 7 | Links 8 | ````` 9 | * `development version `_ 10 | ''' 11 | 12 | import os 13 | from setuptools import setup, find_packages 14 | 15 | setup(name="quick-clojure", 16 | version='0.13', 17 | packages=find_packages(), 18 | # metadata for upload to PyPI 19 | author="Ben Booth", 20 | author_email="benwbooth@gmail.com", 21 | description="Run clojure scripts and lein commands quickly using a persistent nREPL session.", 22 | long_description=__doc__, 23 | test_suite='test', 24 | license="EPL v1.0", 25 | keywords="clojure repl nrepl leiningen lein", 26 | url="https://github.com/benwbooth/quick-clojure", 27 | zip_safe=True, 28 | platforms='any', 29 | classifiers=['Development Status :: 4 - Beta', 30 | 'Environment :: Console', 31 | 'Intended Audience :: Developers', 32 | 'License :: OSI Approved :: MIT License', 33 | 'Natural Language :: English', 34 | 'Operating System :: OS Independent', 35 | 'Programming Language :: Python', 36 | 'Topic :: Software Development :: Interpreters', 37 | 'Topic :: Software Development :: Libraries :: Python Modules', 38 | 'Topic :: Utilities'], 39 | scripts=['quick','quick-exec','quick-exec-p'], 40 | install_requires=['nrepl-python-client','future']) 41 | -------------------------------------------------------------------------------- /test.clj: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env quick-exec 2 | (println "Hello World!") 3 | (println "Press ENTER") (read-line) 4 | (println "Hello World!") 5 | ;(leiningen.core.main/exit 3) 6 | ;(throw (ex-info nil {:exit-code 0 :suppress-msg true})) 7 | (throw (ex-info "SomeException" {})) 8 | --------------------------------------------------------------------------------