├── .envrc ├── deps.edn ├── .github └── workflows │ └── test.yml ├── Makefile ├── derivation.nix ├── flake.lock ├── README.adoc ├── flake.nix ├── test └── rep │ ├── test_drivers.clj │ └── core_test.clj ├── CHANGELOG.adoc ├── rep.1.adoc ├── rc └── rep.kak ├── LICENSE ├── test-deps.nix └── rep.c /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:deps 2 | {org.clojure/tools.cli {:mvn/version "0.4.1"} 3 | nrepl/nrepl {:mvn/version "0.5.3"}} 4 | :aliases 5 | {:test 6 | {:extra-paths ["test"] 7 | :extra-deps {midje {:mvn/version "1.9.4"}}}}} 8 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: "Test" 2 | on: 3 | pull_request: 4 | push: 5 | jobs: 6 | tests: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2.4.0 10 | - uses: cachix/install-nix-action@v15 11 | with: 12 | extra_nix_config: | 13 | access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} 14 | - run: nix flake check 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | prefix = /usr/local 2 | ifeq ($(OS),Windows_NT) 3 | CC=gcc 4 | LIBS=-lws2_32 5 | prefix="C:\\Program Files\\rep" 6 | endif 7 | 8 | all: rep test rep.1 9 | 10 | rep: rep.c 11 | $(CC) -g -O2 -o rep rep.c $(LIBS) 12 | 13 | 14 | rep.1: rep.1.adoc 15 | a2x -f manpage rep.1.adoc 16 | 17 | test: 18 | : 19 | 20 | .PHONY: install 21 | install: 22 | mkdir -p $(prefix)/bin/ $(prefix)/share/man/man1/ $(prefix)/share/kak/autoload/plugins/ 23 | cp rep $(prefix)/bin/ 24 | cp rep.1 $(prefix)/share/man/man1/ 25 | cp rc/rep.kak $(prefix)/share/kak/autoload/plugins/rep.kak 26 | -------------------------------------------------------------------------------- /derivation.nix: -------------------------------------------------------------------------------- 1 | { stdenv, lib, asciidoc-full }: 2 | 3 | stdenv.mkDerivation rec { 4 | pname = "rep"; 5 | version = "0.2.2"; 6 | 7 | src = ./.; 8 | 9 | nativeBuildInputs = [ 10 | asciidoc-full 11 | ]; 12 | 13 | postPatch = '' 14 | substituteInPlace rc/rep.kak --replace '$(rep' '$('"$out/bin/rep" 15 | ''; 16 | makeFlags = [ "prefix=$(out)" ]; 17 | 18 | meta = with lib; { 19 | description = "Single-shot nREPL client"; 20 | homepage = "https://github.com/eraserhd/rep"; 21 | license = licenses.epl10; 22 | platforms = platforms.all; 23 | maintainers = [ maintainers.eraserhd ]; 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "locked": { 5 | "lastModified": 1649676176, 6 | "narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=", 7 | "owner": "numtide", 8 | "repo": "flake-utils", 9 | "rev": "a4b154ebbdc88c8498a5c7b01589addc9e9cb678", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "numtide", 14 | "repo": "flake-utils", 15 | "type": "github" 16 | } 17 | }, 18 | "nixpkgs": { 19 | "locked": { 20 | "lastModified": 1652252629, 21 | "narHash": "sha256-SvT64apetqc8P5nYp1/fOZvUmHUPdPFUZbhSpKy+1aI=", 22 | "owner": "NixOS", 23 | "repo": "nixpkgs", 24 | "rev": "d2fc6856824cb87742177eefc8dd534bdb6c3439", 25 | "type": "github" 26 | }, 27 | "original": { 28 | "id": "nixpkgs", 29 | "type": "indirect" 30 | } 31 | }, 32 | "root": { 33 | "inputs": { 34 | "flake-utils": "flake-utils", 35 | "nixpkgs": "nixpkgs" 36 | } 37 | } 38 | }, 39 | "root": "root", 40 | "version": 7 41 | } 42 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | rep 2 | === 3 | 4 | https://github.com/eraserhd/rep 5 | 6 | A single-shot nREPL client designed for shell invocation. 7 | 8 | This connects to a running nREPL server (like kind started with `lein repl`, 9 | for example), sends some code to be evaluated, and prints the results and 10 | output. 11 | 12 | .... 13 | $ rep '(clojure.tools.namespace.repl/refresh)' 14 | :reloading () 15 | :ok 16 | .... 17 | 18 | Unlike other nREPL clients, `rep` does not try to maintain a persistent 19 | connection, meaning that thread-local variables and bindings like `*e` and 20 | `*1` will not persist across invocations of `rep`. Perhaps there are 21 | other limitations because of this? 22 | 23 | Installation 24 | ------------ 25 | 26 | On Unix-like systems: 27 | 28 | .... 29 | $ make && sudo make install 30 | .... 31 | 32 | On Windows, this can be built with Mingw: 33 | 34 | .... 35 | > mingw32-make.exe 36 | .... 37 | 38 | Usage, Options, and Examples 39 | ---------------------------- 40 | 41 | See https://github.com/eraserhd/rep/blob/develop/rep.1.adoc[the rep manpage]. 42 | 43 | Building with Nix 44 | ----------------- 45 | 46 | You can use https://nixos.org/nix/download.html[Nix] as the build tool. 47 | 48 | .... 49 | $ nix-build . 50 | .... 51 | 52 | A `result` symlink will appear in the current directory point to the build 53 | output. 54 | 55 | Running Tests 56 | ------------- 57 | 58 | To run all the tests that CI runs, the way CI runs them (do this before 59 | issuing a pull request): 60 | 61 | .... 62 | $ nix-build release.nix 63 | .... 64 | 65 | Using with Kakoune 66 | ------------------ 67 | 68 | The `rc/` folder contains scripts which add a `,e` user mode to Kakoune. To 69 | link this to Kakoune's autoload directory, do the following: 70 | 71 | .... 72 | $ make && make install 73 | $ ln -sf /usr/local/share/kak/autoload/plugins/rep.kak ~/.config/kak/autoload/ 74 | .... 75 | 76 | `rep` must be in the path for the plugin to work. 77 | 78 | License 79 | ------- 80 | 81 | Copyright © 2018 Jason M. Felice 82 | 83 | Distributed under the Eclipse Public License either version 1.0 or (at 84 | your option) any later version. 85 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "A single-shot nREPL client designed for shell invocation."; 3 | inputs = { 4 | flake-utils.url = "github:numtide/flake-utils"; 5 | }; 6 | outputs = { self, nixpkgs, flake-utils }: 7 | let 8 | packageOutputs = flake-utils.lib.eachDefaultSystem (system: 9 | let 10 | pkgs = nixpkgs.legacyPackages.${system}; 11 | rep = pkgs.callPackage ./derivation.nix {}; 12 | in { 13 | packages = { 14 | default = rep; 15 | inherit rep; 16 | }; 17 | }); 18 | 19 | checkSystems = [ 20 | "aarch64-darwin" 21 | "aarch64-linux" 22 | "x86_64-darwin" 23 | "x86_64-linux" 24 | ]; 25 | checkOutputs = flake-utils.lib.eachSystem checkSystems (system: 26 | let 27 | pkgs = nixpkgs.legacyPackages.${system}; 28 | rep = pkgs.callPackage ./derivation.nix {}; 29 | in { 30 | checks = { 31 | unit-test = let 32 | java-deps = import ./test-deps.nix { inherit (pkgs) fetchMavenArtifact fetchgit lib; }; 33 | in pkgs.stdenv.mkDerivation { 34 | name = "rep-unit-tests"; 35 | src = ./.; 36 | buildInputs = with pkgs; [ rep jdk17_headless ]; 37 | CLASSPATH = java-deps.makeClasspaths { extraClasspaths = [ "test" "src" ]; }; 38 | REP_TEST_DRIVER = "native"; 39 | REP_TO_TEST = "${rep}/bin/rep"; 40 | buildPhase = '' 41 | rm -rf target/ 42 | mkdir -p target/ 43 | java clojure.main -e " 44 | (require 'midje.repl) 45 | (System/exit (min 255 (:failures (midje.repl/load-facts :all)))) 46 | " 47 | ''; 48 | installPhase = '' 49 | touch $out 50 | ''; 51 | }; 52 | }; 53 | }); 54 | 55 | overlayOutputs = { 56 | overlays.default = final: prev: { 57 | rep = prev.callPackage ./derivation.nix {}; 58 | }; 59 | }; 60 | in 61 | packageOutputs // checkOutputs // overlayOutputs; 62 | } 63 | -------------------------------------------------------------------------------- /test/rep/test_drivers.clj: -------------------------------------------------------------------------------- 1 | (ns rep.test-drivers 2 | (:require 3 | [clojure.java.shell :refer [sh]] 4 | [clojure.java.io :as io] 5 | [clojure.string :as str] 6 | [midje.sweet :refer :all] 7 | [nrepl.misc :refer [response-for]] 8 | [nrepl.server] 9 | [nrepl.transport :as t])) 10 | 11 | (defn- rep-args [args server user-dir] 12 | (->> args 13 | (remove map?) 14 | (map (fn [arg] 15 | (-> arg 16 | (str/replace "${port}" (str (:port server))) 17 | (str/replace "${user.dir}" user-dir)))))) 18 | 19 | (defn rep-native-driver 20 | "An integration driver which runs the `rep` binary." 21 | [server & args] 22 | (let [rep-bin (or (System/getenv "REP_TO_TEST") 23 | "default/rep") 24 | starting-dir (System/getProperty "user.dir") 25 | {:keys [port-file] 26 | :or {port-file ".nrepl-port"}} 27 | (first (filter map? args))] 28 | (spit (str starting-dir "/target/" port-file) (str (:port server))) 29 | (apply sh rep-bin (concat (rep-args args server starting-dir) [:dir (io/file (str starting-dir "/target"))])))) 30 | 31 | (defn- wrap-rep-test-op [f] 32 | (fn [{:keys [op transport] :as message}] 33 | (if (= "rep-test-op" op) 34 | (let [value (str 35 | (if-let [foo (:foo message)] 36 | (format "foo=%s;" (pr-str foo)) 37 | "") 38 | (if-let [bar (:bar message)] 39 | (format "bar=%s;" (pr-str bar)) 40 | "") 41 | (pr-str "hello"))] 42 | (t/send transport (response-for message :status :done :value value :intvalue 67))) 43 | (f message)))) 44 | 45 | (def ^:private handler 46 | (nrepl.server/default-handler wrap-rep-test-op)) 47 | 48 | (defn rep [& args] 49 | (let [server (binding [*file* nil] 50 | (nrepl.server/start-server :handler handler))] 51 | (try 52 | (apply rep-native-driver server args) 53 | (finally 54 | (nrepl.server/stop-server server))))) 55 | 56 | (defn prints [s & flags] 57 | (let [flags (into #{} flags) 58 | k (if (flags :to-stderr) 59 | :err 60 | :out)] 61 | (contains {k s}))) 62 | 63 | (defn exits-with [code] 64 | (contains {:exit code})) 65 | -------------------------------------------------------------------------------- /CHANGELOG.adoc: -------------------------------------------------------------------------------- 1 | Change Log 2 | ========== 3 | 4 | https://github.com/eraserhd/rep/compare/v0.2.2...HEAD[Unreleased] 5 | ----------------------------------------------------------------- 6 | 7 | * Support for Mingw 8 | * Test on aarch64-darwin and aarch64-linux 9 | 10 | https://github.com/eraserhd/rep/compare/v0.2.1...v0.2.2[v0.2.2] 11 | --------------------------------------------------------------- 12 | 13 | === Fixed 14 | 15 | * Default for --port argument was not as advertised, so the default was 16 | changed (#7). 17 | * Exit with error on "status" of "error", even if no exception was thrown 18 | (#9). 19 | * An informative error is printed to stderr if a `namespace-not-found` status 20 | is seen (#9). 21 | 22 | === Kakoune 23 | 24 | * Fix issue preventing evaluation of code in the correct namespace. 25 | 26 | https://github.com/eraserhd/rep/compare/v0.2.0...v0.2.1[v0.2.1] 27 | --------------------------------------------------------------- 28 | 29 | * Build fix for nixpkgs 30 | 31 | https://github.com/eraserhd/rep/compare/v0.1.2...v0.2.0[v0.2.0] 32 | --------------------------------------------------------------- 33 | 34 | * Completely rewrite in C for portability. 35 | 36 | === Added 37 | 38 | * `--port` accept `@FNAME@RELATIVE` to find a port file in a parent. 39 | * Finding `.nrepl-port` in the current directory or a parent is now the 40 | default behavior. 41 | * List-valued replies can now be formatted using `%{KEY,SUBFORMAT}` 42 | syntax, and `%.` in SUBFORMAT refers to the current value. 43 | * `--verbose` option for debugging. 44 | 45 | https://github.com/eraserhd/rep/compare/v0.1.1...v0.1.2[v0.1.2] 46 | ---------------------------------------------------------------- 47 | 48 | === Added 49 | 50 | * Add `--op` to specify an operation other than "eval". 51 | * Add `--print` option to specify which keys in response messages to print 52 | and how. 53 | * Add `--send` to send additional keys in request messages. 54 | 55 | === Kakoune 56 | 57 | * `,eR` replaces selection with its evaluated result. 58 | 59 | https://github.com/eraserhd/rep/compare/v0.1.0...v0.1.1[v0.1.1] - 2019-01-08 60 | -------------------------------------------------------------------------- 61 | 62 | === Added 63 | 64 | * Add `-n`, `--namespace` option to specify the namespace of evaluated code. 65 | * Add `-l`, `--line` option to specify the file, line number, and column of 66 | evaluated code. 67 | * Now releasing Linux binaries (thanks Circle CI!) 68 | * Documentation updatings, including an example of how an editor might invoke 69 | `rep` 70 | 71 | === Kakoune 72 | 73 | * Evaluates code in the namespace of the file if it can detect an `ns` form 74 | at the top. 75 | * Sends file name and position when evaluating code. 76 | 77 | 0.1.0 - 2018-11-30 78 | ------------------ 79 | 80 | === Added 81 | 82 | * First release with basic features (able to evaluate code and print 83 | results). 84 | -------------------------------------------------------------------------------- /test/rep/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns rep.core-test 2 | (:require 3 | [midje.sweet :refer :all] 4 | [rep.test-drivers :refer [rep prints exits-with]])) 5 | 6 | (facts "about basic evaluation of code" 7 | (rep "(+ 2 2)") => (prints "4\n") 8 | (rep "(+ 1 1)") => (exits-with 0) 9 | (rep "(println 'hello)") => (prints "hello\nnil\n") 10 | (rep "(.write ^java.io.Writer *err* \"error\")") => (prints "error" :to-stderr) 11 | (rep "(throw (ex-info \"x\" {}))") => (prints #"ExceptionInfo" :to-stderr) 12 | (rep "(throw (ex-info \"x\" {}))") => (exits-with 1) 13 | (rep "-n" "ns.does.not.exist" "(+ 1 1)") => (exits-with 1) 14 | (rep "-n" "ns.does.not.exist" "(+ 1 1)") => (prints "the namespace does not exist\n" :to-stderr)) 15 | 16 | (facts "about help" 17 | (rep "-h") => (prints #"rep: Single-shot nREPL client") 18 | (rep "--help") => (prints #"rep: Single-shot nREPL client")) 19 | 20 | (facts "about invalid switches" 21 | (rep "-/") => (prints #"invalid option" :to-stderr) 22 | (rep "-/") => (exits-with 2)) 23 | 24 | (facts "about specifying the nREPL port" 25 | (rep "-p" "@.nrepl-port" "42") => (prints "42\n") 26 | (rep "-p" "@.does-not-exist" "42") => (prints #"\.does-not-exist: No such file or directory\n" :to-stderr) 27 | (rep "-p" "@foo.txt" "69" {:port-file "foo.txt"}) => (prints "69\n") 28 | (rep "-p" "${port}" "77" {:port-file "bad"}) => (prints "77\n") 29 | (rep "-p" "localhost:${port}" "99" {:port-file "bad"}) => (prints "99\n") 30 | (rep "-p" "@.nrepl-port@target/src/foo/bar.clj" "111") => (prints "111\n") 31 | (rep "-p" "@${user.dir}/target/.nrepl-port" "11") => (prints "11\n") 32 | (rep "-p" "@.nrepl-port@/not-exist/foo/bar/baz" "91") => (prints "rep: No ancestor of /not-exist/foo/bar/baz contains .nrepl-port\n" :to-stderr)) 33 | 34 | (facts "about specifying the eval namespace" 35 | (facts "about sending a bare namespace name" 36 | (rep "-n" "user" "(str *ns*)") => (prints "\"user\"\n") 37 | (rep "-n" "rep.core-test" "(str *ns*)") => (prints "\"rep.core-test\"\n"))) 38 | 39 | (facts "about specifying line numbers" 40 | (rep "(throw (Exception.))") => (prints #"\(:1\)" :to-stderr) 41 | (rep "-l" "27" "(throw (Exception.))") => (prints #"\(:27\)" :to-stderr) 42 | (rep "-l" "27:11" "(do (def foo) (meta #'foo))") => (prints #":line 27") 43 | (rep "-l" "27:11" "(do (def foo) (meta #'foo))") => (prints #":column 15") 44 | (rep "-l" "foo.clj:18" "(do (def foo) (meta #'foo))") => (prints #":file \"foo.clj\"") 45 | (rep "-l" "foo.clj:18" "(do (def foo) (meta #'foo))") => (prints #":line 18") 46 | (rep "-l" "foo.clj" "(do (def foo) (meta #'foo))") => (prints #":file \"foo.clj\"") 47 | (rep "-l" "foo.clj:18:11" "(do (def foo) (meta #'foo))") => (prints #":file \"foo.clj\"") 48 | (rep "-l" "foo.clj:18:11" "(do (def foo) (meta #'foo))") => (prints #":line 18") 49 | (rep "-l" "foo.clj:18:11" "(do (def foo) (meta #'foo))") => (prints #":column 15")) 50 | 51 | (facts "about specifying the operation" 52 | (rep "--op=eval" "(+ 1 2)") => (prints "3\n") 53 | (rep "--op=rep-test-op") => (prints "\"hello\"\n")) 54 | 55 | (facts "about --print" 56 | (fact "it can print arbitrary keys" 57 | (rep "--print=ns" "(+ 1 1)") => (prints "user")) 58 | (fact "it overrides any default formats the first time given" 59 | (rep "--print=value,1,-%{value}-%n" "(+ 2 3)") => (prints "-5-\n")) 60 | (fact "it can print to stderr" 61 | (rep "--print=value,2,>%{value}<" "(+ 1 1)") => (prints ">2<" :to-stderr)) 62 | (fact "it can be given multiple times for one KEY" 63 | (rep "--print=value,1,<%{value}>" "--print=value,1,<<%{value}>>" "2") => (prints #"<2><<2>>")) 64 | (fact "it can print integral values" 65 | (rep "--print=intvalue,1,<%{intvalue}>" "--op=rep-test-op") => (prints #"<67>"))) 66 | 67 | (facts "about --no-print" 68 | (fact "it can suppress a key" 69 | (rep "--no-print=out" "(println 'whaat?)") => (prints "nil\n"))) 70 | 71 | (facts "about sending additional fields" 72 | (rep "--op=rep-test-op" "--send=foo,string,quux") => (prints "foo=\"quux\";\"hello\"\n") 73 | (rep "--op=rep-test-op" "--send=bar,integer,42") => (prints "bar=42;\"hello\"\n")) 74 | 75 | (facts "about the examples in the documentation" 76 | (rep "--op=ls-sessions" "--print=sessions,1,%{sessions,session=%.%n;}") => (prints #"^session=[a-fA-F0-9]{6}")) 77 | -------------------------------------------------------------------------------- /rep.1.adoc: -------------------------------------------------------------------------------- 1 | = REP(1) 2 | :doctype: manpage 3 | 4 | 5 | == NAME 6 | rep - a single-shot nREPL client 7 | 8 | == SYNOPSIS 9 | *rep* ['OPTIONS'] 'EXPR' ... 10 | 11 | == DESCRIPTION 12 | 13 | `rep` connects to a running nREPL server, sends a bit of code, and prints 14 | the result. 15 | 16 | Unlike other nREPL clients, `rep` does not try to maintain a persistent 17 | connection, meaning that thread-local variables and bindings like `*e` and 18 | `*1` will not persist across invocations of `rep`. 19 | 20 | == OPTIONS 21 | *--*:: 22 | End of options. Useful to send code which starts with a dash. 23 | 24 | *-h, --help*:: 25 | Show a summary of help options. 26 | 27 | *-l, --line*='[FILE:]LINE[:COLUMN]':: 28 | Specify the FILE, LINE number, and COLUMN of the code being evaluated. 29 | This is used by for compile errors, exceptions, and metadata on evaluated 30 | symbols, so specifying this will improve your error messages. 31 | 32 | LINE must be supplied if COLUMN is supplied, but all other combinations 33 | are allowed. 34 | 35 | *-n, --namespace*=NS:: 36 | Evaluate code in NS. 'ns' forms themselves should be evaluated in 'user' 37 | in case they don't already exist. 'user' is the default. 38 | 39 | *--no-print*=KEY:: 40 | Do not print KEY. Used to suppress output for one of the keys printed by 41 | default, `out`, `err`, or `value`. (See *--print*.) 42 | 43 | *--op*=OP:: 44 | Specify an nREPL operation. The default is "eval". 45 | 46 | *-p, --port*='@FILE|@FNAME@RELATIVE|[HOST:]PORT':: 47 | If 'FILE' is given, FILE is read for host and port. If 'FNAME' and 48 | 'RELATIVE' are given, `rep` finds a parent directory of 'RELATIVE' which 49 | contains 'FNAME' and reads that file. The default is '@.nrepl-port@.', 50 | which will find the running nREPL if it was invoked by Leiningen. 51 | 52 | *--print*=KEY[,FD[,FORMAT]]:: 53 | Print response messages with KEY in them to FD, using FORMAT. In FORMAT, 54 | `%{key}` prints *key* from the response message, `%%` prints a literal 55 | `%`, and `%n` prints a newline. If FD is omitted, 1 (stdout) is used. If 56 | FORMAT is omitted, `%{KEY}` is used. Multiple *--print* options can be 57 | specified and they are collected. The defaults are `out,1,%{out}`, 58 | `err,2,%{err}`, and `value,1,%{value}%n`. The first time *--print* is 59 | given on the command-line for KEY, any default formats for KEY are 60 | removed. List-valued keys can be printed with `%{key,SUBFORMAT}`, where 61 | SUBFORMAT is evaluated for every element and `%.` refers to the current 62 | element value. If SUBFORMAT is not specified, `%.%n` is used. 63 | 64 | *--send*=KEY,TYPE,VALUE:: 65 | Send KEY in request message with VALUE. TYPE can be either `string` or 66 | `integer`. 67 | 68 | *-v, --verbose*:: 69 | Dump all messages sent and received. 70 | 71 | == EXAMPLES 72 | `rep '(+ 2 2)'`:: 73 | Evaluate a simple expression in the running nREPL server and print its 74 | result. 75 | 76 | `rep '(clojure.tools.namespace.repl/refresh)'`:: 77 | Cause the running nREPL to reload any files which were changed. 78 | 79 | `rep -l src/foo/myfile.clj:26:7 -n foo.myfile '(+ 26 99)'`:: 80 | This is how an editor might invoke `rep` when the user requests evaluation 81 | of the form under the cursor. In addition to extracting the form under 82 | the cursor, the editor will need some mechanism to extract the file's 83 | namespace for the `-n` option. `rep` will print `125`, which the editor 84 | can display in its echo area, log, scratch buffer, or what not. 85 | 86 | `rep --op=list-sessions --print=sessions,1,%{sessions,var=%.;%n}`:: 87 | Print the list of the active nREPL sessions on each line, where each line 88 | looks like `var=d041fed3-522f-4e7a-8da1-4b192eef0a24`. 89 | 90 | `rep --op=slurp --send=url,string,http://eraserhead.net/ --print=body`:: 91 | Use CIDER's "slurp" operation to retrieve http://eraserhead.net/. 92 | 93 | `rep --op=format-code "(if true\n(+ 1 1) 42)" --print=formatted-code`:: 94 | Use CIDER's "format-code" operation. 95 | 96 | == EXIT STATUS 97 | *0*:: 98 | Success 99 | 100 | *1*:: 101 | One of the expressions given threw an exception. 102 | 103 | *2*:: 104 | An error occurred while parsing options. 105 | 106 | *255*:: 107 | Some other kind of exception occurred during processing. 108 | 109 | == BUGS 110 | 111 | Some errors, such as `namespace-not-found` aren't reported as exceptions, and 112 | rep currently cannot suppress or change their formatting. 113 | -------------------------------------------------------------------------------- /rc/rep.kak: -------------------------------------------------------------------------------- 1 | declare-option -hidden str rep_evaluate_output 2 | declare-option -hidden str rep_namespace 3 | 4 | define-command -hidden rep-find-namespace %{ 5 | evaluate-commands -draft %{ 6 | set-option buffer rep_namespace '' 7 | # Probably will get messed up if the file starts with comments 8 | # containing parens. 9 | execute-keys 'gkm' 10 | evaluate-commands %sh{ 11 | ns=$(rep --port="@.nrepl-port@${kak_buffile-.}" -- "(second '$kak_selection)" 2>/dev/null) 12 | if [ $? -ne 0 ]; then 13 | printf 'fail "could not parse namespace"\n' 14 | else 15 | printf 'set-option buffer rep_namespace %s\n' "$ns" 16 | fi 17 | } 18 | } 19 | } 20 | 21 | define-command \ 22 | -params 0.. \ 23 | -docstring %{rep-evaluate-selection: Evaluate selected code in REPL and echo result. 24 | Switches: 25 | -namespace Evaluate in . Default is the current file's ns or user if not found.} \ 26 | rep-evaluate-selection %{ 27 | evaluate-commands %{ 28 | set-option global rep_evaluate_output '' 29 | try %{ rep-find-namespace } 30 | evaluate-commands -itersel -draft %{ 31 | evaluate-commands %sh{ 32 | add_port() { 33 | if [ -n "$kak_buffile" ]; then 34 | rep_command="$rep_command --port=\"@.nrepl-port@$kak_buffile\"" 35 | fi 36 | } 37 | add_file_line_and_column() { 38 | anchor="${kak_selection_desc%,*}" 39 | anchor_line="${anchor%.*}" 40 | anchor_column="${anchor#*.}" 41 | cursor="${kak_selection_desc#*,}" 42 | cursor_line="${cursor%.*}" 43 | cursor_column="${cursor#*.}" 44 | if [ $anchor_line -lt $cursor_line ]; then 45 | start="$anchor_line:$anchor_column" 46 | elif [ $anchor_line -eq $cursor_line ] && [ $anchor_column -lt $cursor_column ]; then 47 | start="$anchor_line:$anchor_column" 48 | else 49 | start="$cursor_line:$cursor_column" 50 | fi 51 | rep_command="$rep_command --line=\"$kak_buffile:$start\"" 52 | } 53 | add_namespace() { 54 | ns="$kak_opt_rep_namespace" 55 | while [ $# -gt 0 ]; do 56 | case "$1" in 57 | -namespace) shift; ns="$1";; 58 | esac 59 | shift 60 | done 61 | if [ -n "$ns" ]; then 62 | rep_command="$rep_command --namespace=$ns" 63 | fi 64 | } 65 | error_file=$(mktemp) 66 | rep_command='value=$(rep' 67 | add_port 68 | add_file_line_and_column 69 | add_namespace "$@" 70 | rep_command="$rep_command"' -- "$kak_selection" 2>"$error_file" |sed -e "s/'"'"'/'"''"'/g")' 71 | eval "$rep_command" 72 | error=$(sed "s/'/''/g" <"$error_file") 73 | rm -f "$error_file" 74 | printf "set-option -add global rep_evaluate_output '%s'\n" "$value" 75 | [ -n "$error" ] && printf "fail '%s'\n" "$error" 76 | } 77 | } 78 | echo -- "%opt{rep_evaluate_output}" 79 | } 80 | } 81 | 82 | define-command \ 83 | -docstring %{rep-evaluate-file: Evaluate the entire file in the REPL.} \ 84 | rep-evaluate-file %{ 85 | evaluate-commands -draft %{ 86 | execute-keys '%' 87 | rep-evaluate-selection -namespace user 88 | } 89 | echo -- "%opt{rep_evaluate_output}" 90 | } 91 | 92 | define-command \ 93 | -docstring %{rep-replace-selection: Evaluate selection and replace with result.} \ 94 | rep-replace-selection %{ 95 | rep-evaluate-selection %arg{@} 96 | evaluate-commands -save-regs r %{ 97 | set-register r "%opt{rep_evaluate_output}" 98 | execute-keys '"rR' 99 | } 100 | } 101 | 102 | declare-user-mode rep 103 | map global user e ': enter-user-mode rep' 104 | map -docstring 'evaluate the selection in the REPL' global rep e ': rep-evaluate-selection' 105 | map -docstring 'evaluate this file in the REPL' global rep f ': rep-evaluate-file' 106 | map -docstring 'replace selection with result' global rep R ': rep-replace-selection' 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor to control, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of New York and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | -------------------------------------------------------------------------------- /test-deps.nix: -------------------------------------------------------------------------------- 1 | # generated by clj2nix-1.1.0-rc 2 | { fetchMavenArtifact, fetchgit, lib }: 3 | 4 | let repos = [ 5 | "https://repo1.maven.org/maven2/" 6 | "https://repo.clojars.org/" ]; 7 | 8 | in rec { 9 | makePaths = {extraClasspaths ? []}: 10 | if (builtins.typeOf extraClasspaths != "list") 11 | then builtins.throw "extraClasspaths must be of type 'list'!" 12 | else (lib.concatMap (dep: 13 | builtins.map (path: 14 | if builtins.isString path then 15 | path 16 | else if builtins.hasAttr "jar" path then 17 | path.jar 18 | else if builtins.hasAttr "outPath" path then 19 | path.outPath 20 | else 21 | path 22 | ) 23 | dep.paths) 24 | packages) ++ extraClasspaths; 25 | makeClasspaths = {extraClasspaths ? []}: 26 | if (builtins.typeOf extraClasspaths != "list") 27 | then builtins.throw "extraClasspaths must be of type 'list'!" 28 | else builtins.concatStringsSep ":" (makePaths {inherit extraClasspaths;}); 29 | packageSources = builtins.map (dep: dep.src) packages; 30 | packages = [ 31 | rec { 32 | name = "clojure/org.clojure"; 33 | src = fetchMavenArtifact { 34 | inherit repos; 35 | artifactId = "clojure"; 36 | groupId = "org.clojure"; 37 | sha512 = "7fdcc6fe280c44966cbe38b2758cacb3eadfade07eca9ce0a50bbf71129a6ce0e7b1959067f6b7f47500b30bdf7a68891e18600d5b1c2afc7288f0bbb330ec2f"; 38 | version = "1.9.0"; 39 | 40 | }; 41 | paths = [ src ]; 42 | } 43 | 44 | rec { 45 | name = "joda-time/joda-time"; 46 | src = fetchMavenArtifact { 47 | inherit repos; 48 | artifactId = "joda-time"; 49 | groupId = "joda-time"; 50 | sha512 = "3a6749ecd71ee8d5781821c36d77850a810e72ee33757ec4ee9e3d424676dced7eeb955a432f45edb3694dc14dbe1ee4c608545d6a445b29b86979a7c9829384"; 51 | version = "2.9.9"; 52 | 53 | }; 54 | paths = [ src ]; 55 | } 56 | 57 | rec { 58 | name = "commons-codec/commons-codec"; 59 | src = fetchMavenArtifact { 60 | inherit repos; 61 | artifactId = "commons-codec"; 62 | groupId = "commons-codec"; 63 | sha512 = "d9586162b257386b5871e7e9ae255a38014a9efaeef5148de5e40a3b0200364dad8516bddd554352aa2e5337bec2cc11df88c76c4fdde96a40f3421aa60650d7"; 64 | version = "1.11"; 65 | 66 | }; 67 | paths = [ src ]; 68 | } 69 | 70 | rec { 71 | name = "core.specs.alpha/org.clojure"; 72 | src = fetchMavenArtifact { 73 | inherit repos; 74 | artifactId = "core.specs.alpha"; 75 | groupId = "org.clojure"; 76 | sha512 = "b4f5eee01da39914e6024dd529d1f72952d5a9dae65e1e41bf386b1e86a004a0d197b5be95aa70e7e8d6438c92b7fa8fc0c5039f2013e97c0b91c22d86fb7968"; 77 | version = "0.1.24"; 78 | 79 | }; 80 | paths = [ src ]; 81 | } 82 | 83 | rec { 84 | name = "suchwow/marick"; 85 | src = fetchMavenArtifact { 86 | inherit repos; 87 | artifactId = "suchwow"; 88 | groupId = "marick"; 89 | sha512 = "1847d7a3f7e6309ec76f1cfd918841ec123129c9e4828264290da0a534c6b22c173357ea890e153e59e2e5aa4c876b9f7269ddab09ad4dbab483f6275fdcc8e3"; 90 | version = "6.0.2"; 91 | 92 | }; 93 | paths = [ src ]; 94 | } 95 | 96 | rec { 97 | name = "colorize/colorize"; 98 | src = fetchMavenArtifact { 99 | inherit repos; 100 | artifactId = "colorize"; 101 | groupId = "colorize"; 102 | sha512 = "bb754a1e85da2a261b6b0ee226b5e52d96fb89fda4c245487b91e8f2d98dedf46e2ca255522913e4701277e761d818f40e446fca01fcb96486e79789a83c3d17"; 103 | version = "0.1.1"; 104 | 105 | }; 106 | paths = [ src ]; 107 | } 108 | 109 | rec { 110 | name = "spec.alpha/org.clojure"; 111 | src = fetchMavenArtifact { 112 | inherit repos; 113 | artifactId = "spec.alpha"; 114 | groupId = "org.clojure"; 115 | sha512 = "b8fc40ed9bc52b545e699ed188dd61bfd144ee67f0c70364b8f2715e9f1fea608d3721db7f618f6ef4bc3056e3c2984c626080486ca710f3595dda8ba23730ac"; 116 | version = "0.1.143"; 117 | 118 | }; 119 | paths = [ src ]; 120 | } 121 | 122 | rec { 123 | name = "tools.cli/org.clojure"; 124 | src = fetchMavenArtifact { 125 | inherit repos; 126 | artifactId = "tools.cli"; 127 | groupId = "org.clojure"; 128 | sha512 = "28992520462503c069f853c1d5a47e67159a09499c41db74d97de79cc9d3eaaa7aee12905ea4cc9e9b6672595378966b874b6b2b28354d246fd1540a0b74ea73"; 129 | version = "0.4.1"; 130 | 131 | }; 132 | paths = [ src ]; 133 | } 134 | 135 | rec { 136 | name = "puget/mvxcvi"; 137 | src = fetchMavenArtifact { 138 | inherit repos; 139 | artifactId = "puget"; 140 | groupId = "mvxcvi"; 141 | sha512 = "d93ec2165f24de69927a68d7e4a58b3b0a5f510a4384434823bce65990fc4e241d9d51d6b72d3e36509d8cc69dab9c68656aa1ff2db7d131261cbe0643d1c16b"; 142 | version = "1.0.2"; 143 | 144 | }; 145 | paths = [ src ]; 146 | } 147 | 148 | rec { 149 | name = "tools.macro/org.clojure"; 150 | src = fetchMavenArtifact { 151 | inherit repos; 152 | artifactId = "tools.macro"; 153 | groupId = "org.clojure"; 154 | sha512 = "65ce5e29379620ac458274c53cd9926e4b764fcaebb1a2b3bc8aef86bbe10c79e654b028bc4328905d2495a680fa90f5002cf5c47885f6449fad43a04a594b26"; 155 | version = "0.1.5"; 156 | 157 | }; 158 | paths = [ src ]; 159 | } 160 | 161 | rec { 162 | name = "pretty/io.aviso"; 163 | src = fetchMavenArtifact { 164 | inherit repos; 165 | artifactId = "pretty"; 166 | groupId = "io.aviso"; 167 | sha512 = "4db5a38fda3d32db0d47c13e7b321f393764d0200e7a5d61ee7ad89bf9bef172d80f98e99d9e8df7416fd73a2a40d8418543758e754285b8f1670de782d11d64"; 168 | version = "0.1.35"; 169 | 170 | }; 171 | paths = [ src ]; 172 | } 173 | 174 | rec { 175 | name = "slingshot/slingshot"; 176 | src = fetchMavenArtifact { 177 | inherit repos; 178 | artifactId = "slingshot"; 179 | groupId = "slingshot"; 180 | sha512 = "ff2b2a27b441d230261c7f3ec8c38aa551865e05ab6438a74bd12bfcbc5f6bdc88199d42aaf5932b47df84f3d2700c8f514b9f4e9b5da28d29da7ff6b09a7fb5"; 181 | version = "0.12.2"; 182 | 183 | }; 184 | paths = [ src ]; 185 | } 186 | 187 | rec { 188 | name = "tools.namespace/org.clojure"; 189 | src = fetchMavenArtifact { 190 | inherit repos; 191 | artifactId = "tools.namespace"; 192 | groupId = "org.clojure"; 193 | sha512 = "5b42402985b6147050204539be75159f471d234c97d172e1c56b962de9ed2737dcb6987f32221309cacb963d1e15c54a27038ff918ff11e5c79e3341634f99ba"; 194 | version = "0.3.0-alpha4"; 195 | 196 | }; 197 | paths = [ src ]; 198 | } 199 | 200 | rec { 201 | name = "fipp/fipp"; 202 | src = fetchMavenArtifact { 203 | inherit repos; 204 | artifactId = "fipp"; 205 | groupId = "fipp"; 206 | sha512 = "6d8484301d97e2a05867b13178c11cf11891117023aea7c24aa7142b6513d6d4dd7190850082fa429590ac2b0c631dad7d448b3bfdc99f0a50b439dc9309e7c7"; 207 | version = "0.6.13"; 208 | 209 | }; 210 | paths = [ src ]; 211 | } 212 | 213 | rec { 214 | name = "clj-time/clj-time"; 215 | src = fetchMavenArtifact { 216 | inherit repos; 217 | artifactId = "clj-time"; 218 | groupId = "clj-time"; 219 | sha512 = "0922a685db72947f2e4275e6ffb9b1475ffa3362af354ac5b4f8dc07c9f27fafe178ce2da50755d05f667e38ae18e1b031417dbaf01b2db899e8b689ce61dbcb"; 220 | version = "0.14.4"; 221 | 222 | }; 223 | paths = [ src ]; 224 | } 225 | 226 | rec { 227 | name = "arrangement/mvxcvi"; 228 | src = fetchMavenArtifact { 229 | inherit repos; 230 | artifactId = "arrangement"; 231 | groupId = "mvxcvi"; 232 | sha512 = "1b69aa23a86d96d4aadde5f09c8b0e847cbbab6f6762b01499e1be63aa34f655d5427edfeea0d21b91a5a35fee447c5dd72a792ebea1dcb9cdc7906866560d34"; 233 | version = "1.1.1"; 234 | 235 | }; 236 | paths = [ src ]; 237 | } 238 | 239 | rec { 240 | name = "clj-tuple/clj-tuple"; 241 | src = fetchMavenArtifact { 242 | inherit repos; 243 | artifactId = "clj-tuple"; 244 | groupId = "clj-tuple"; 245 | sha512 = "dd626944d0aba679a21b164ed0c77ea84449359361496cba810f83b9fdeab751e5889963888098ce4bf8afa112dbda0a46ed60348a9c01ad36a2e255deb7ab6d"; 246 | version = "0.2.2"; 247 | 248 | }; 249 | paths = [ src ]; 250 | } 251 | 252 | rec { 253 | name = "flare/flare"; 254 | src = fetchMavenArtifact { 255 | inherit repos; 256 | artifactId = "flare"; 257 | groupId = "flare"; 258 | sha512 = "4e46d13382540e29d9dc1ccc5ecdab4a5cfe56f6524a1cb319f1a53517adb3c45a52a4b59b9ff54ce2496619ca2362c9e94a07e36b3fdddcfeb1922070367915"; 259 | version = "0.2.9"; 260 | 261 | }; 262 | paths = [ src ]; 263 | } 264 | 265 | rec { 266 | name = "riddley/riddley"; 267 | src = fetchMavenArtifact { 268 | inherit repos; 269 | artifactId = "riddley"; 270 | groupId = "riddley"; 271 | sha512 = "b478ecba9d1ab9d38c84a42354586fcece763000907b40c97bc43c0f16dc560b0860144efe410193cb3b7cb0149fbc1724fdd737cc3ba53de23618f5b30e6f9f"; 272 | version = "0.1.12"; 273 | 274 | }; 275 | paths = [ src ]; 276 | } 277 | 278 | rec { 279 | name = "core.unify/org.clojure"; 280 | src = fetchMavenArtifact { 281 | inherit repos; 282 | artifactId = "core.unify"; 283 | groupId = "org.clojure"; 284 | sha512 = "2613d5b17cdd6bfbfd62db1ec5e9f0dce1034bc40b3c91f88088646542edc8836722276b0b7d47c5aefacfb123aee1a74a6a3cbe43859f1ecc9099f1435cc29d"; 285 | version = "0.5.7"; 286 | 287 | }; 288 | paths = [ src ]; 289 | } 290 | 291 | rec { 292 | name = "java.classpath/org.clojure"; 293 | src = fetchMavenArtifact { 294 | inherit repos; 295 | artifactId = "java.classpath"; 296 | groupId = "org.clojure"; 297 | sha512 = "e583db27471744d51ec2a5a64de7bfb6d438de33ebc4318cb168e0e58b38d435f4ce14a82bf4783700156ee82ab7ae6690ea166e809c94c323cb5a2f8918fe8c"; 298 | version = "0.2.3"; 299 | 300 | }; 301 | paths = [ src ]; 302 | } 303 | 304 | rec { 305 | name = "environ/environ"; 306 | src = fetchMavenArtifact { 307 | inherit repos; 308 | artifactId = "environ"; 309 | groupId = "environ"; 310 | sha512 = "60b028d337abbadb8d9c50c8af00c269a7d84ea656a0119cd170d3f6b93067020ec3104579c92c5e3c7fd96b363ecf37d8c43a69445bf6d1b431cb603ac3d09d"; 311 | version = "1.1.0"; 312 | 313 | }; 314 | paths = [ src ]; 315 | } 316 | 317 | rec { 318 | name = "core.rrb-vector/org.clojure"; 319 | src = fetchMavenArtifact { 320 | inherit repos; 321 | artifactId = "core.rrb-vector"; 322 | groupId = "org.clojure"; 323 | sha512 = "5f737bf3ca3acf567b2b5c14b5761c8c38e94e1f6168f8cba9f46d2ae41334ae3d68d2c00663827a6214094d96b9767f6803f66ab44b0012c6f2e3c2997b1796"; 324 | version = "0.0.13"; 325 | 326 | }; 327 | paths = [ src ]; 328 | } 329 | 330 | rec { 331 | name = "math.combinatorics/org.clojure"; 332 | src = fetchMavenArtifact { 333 | inherit repos; 334 | artifactId = "math.combinatorics"; 335 | groupId = "org.clojure"; 336 | sha512 = "ac9e614c2be39f0218cc6ff58b797bed773a163e0177423d3b57c45f8e71f899401ffeef3814c27546dcbad511202651a12d35cf9183991e238fb3e974854985"; 337 | version = "0.1.4"; 338 | 339 | }; 340 | paths = [ src ]; 341 | } 342 | 343 | rec { 344 | name = "tools.reader/org.clojure"; 345 | src = fetchMavenArtifact { 346 | inherit repos; 347 | artifactId = "tools.reader"; 348 | groupId = "org.clojure"; 349 | sha512 = "ac36ba633ea9a2f4309b72fb5fd4dbe2f88870d9766ac57c45ab3a4997b73487056def3bf2aa996e01f07c91015147e480bc6d935916d9c13d8018125c0df186"; 350 | version = "0.10.0"; 351 | 352 | }; 353 | paths = [ src ]; 354 | } 355 | 356 | rec { 357 | name = "dynapath/org.tcrawley"; 358 | src = fetchMavenArtifact { 359 | inherit repos; 360 | artifactId = "dynapath"; 361 | groupId = "org.tcrawley"; 362 | sha512 = "1b0caf390515212e6b151d6c227b1a62e430e682b6c811736edba3cc918344053e35c092e12afd523198ed6244018450931776f8388e61a593f266476b6db19e"; 363 | version = "1.0.0"; 364 | 365 | }; 366 | paths = [ src ]; 367 | } 368 | 369 | rec { 370 | name = "potemkin/potemkin"; 371 | src = fetchMavenArtifact { 372 | inherit repos; 373 | artifactId = "potemkin"; 374 | groupId = "potemkin"; 375 | sha512 = "85ae548671efff81399dace661f8e4ce5150377475d31701a22b4f3c2bd3b615518fa938fffe3249c945ad1bcd7295243967429144bbce57a22e2303c7081875"; 376 | version = "0.4.4"; 377 | 378 | }; 379 | paths = [ src ]; 380 | } 381 | 382 | rec { 383 | name = "nrepl/nrepl"; 384 | src = fetchMavenArtifact { 385 | inherit repos; 386 | artifactId = "nrepl"; 387 | groupId = "nrepl"; 388 | sha512 = "c763d0b37b54948b55635577aff245908996f88e29e286e2907086fc81fca7892c72df1105e4284859f6d068eeb510a105d7cc1289a3f0bd1b6d526f78b09f51"; 389 | version = "0.5.3"; 390 | 391 | }; 392 | paths = [ src ]; 393 | } 394 | 395 | rec { 396 | name = "specter/com.rpl"; 397 | src = fetchMavenArtifact { 398 | inherit repos; 399 | artifactId = "specter"; 400 | groupId = "com.rpl"; 401 | sha512 = "9037cbe78efbdc18eabc8cd3919fa7fca5312b6fe3efccd146d7b0becbd281d8bfc5b4d6fb25abebdf603f88708f8ab9ebddc57ac93b642fc45083d30cbd8258"; 402 | version = "1.0.5"; 403 | 404 | }; 405 | paths = [ src ]; 406 | } 407 | 408 | rec { 409 | name = "test.check/org.clojure"; 410 | src = fetchMavenArtifact { 411 | inherit repos; 412 | artifactId = "test.check"; 413 | groupId = "org.clojure"; 414 | sha512 = "bf57571a9d31d50cf15b38134f4d7c34d03eb458bc62b30c7a1dbf233e300c67f1fda6673dbd1584a0497cf8875f972e6697e7f13d0c3e70e4254697b1b75cc6"; 415 | version = "0.10.0-alpha3"; 416 | 417 | }; 418 | paths = [ src ]; 419 | } 420 | 421 | rec { 422 | name = "bencode/nrepl"; 423 | src = fetchMavenArtifact { 424 | inherit repos; 425 | artifactId = "bencode"; 426 | groupId = "nrepl"; 427 | sha512 = "b3b93809354d45953329b67fb79795cccfb995bf19985f71adba26376ecb19810fdfbc75f9ad31f00a42dba161e6746a6b933dffd7346971771670e6f6903442"; 428 | version = "1.0.0"; 429 | 430 | }; 431 | paths = [ src ]; 432 | } 433 | 434 | rec { 435 | name = "midje/midje"; 436 | src = fetchMavenArtifact { 437 | inherit repos; 438 | artifactId = "midje"; 439 | groupId = "midje"; 440 | sha512 = "1715271f2b125335eb67c5d5cab7eeade3fdb7f1b58eb03de9bf5d127a0f07a44c1af297d19cb4f6dd17b8d59c6eac1f0e983429dc07ca2485f964465c347e7d"; 441 | version = "1.9.4"; 442 | 443 | }; 444 | paths = [ src ]; 445 | } 446 | 447 | rec { 448 | name = "google-diff-match-patch/org.clojars.brenton"; 449 | src = fetchMavenArtifact { 450 | inherit repos; 451 | artifactId = "google-diff-match-patch"; 452 | groupId = "org.clojars.brenton"; 453 | sha512 = "896083e1a0c9353122c01c974f86159b389c8cafd1aed73154e494c77a4edc19952dbb56ee4b8dbbd2a2fa6e6fb1495d93338c0d3c12d4a3507864bba8f11d03"; 454 | version = "0.1"; 455 | 456 | }; 457 | paths = [ src ]; 458 | } 459 | 460 | ]; 461 | } 462 | -------------------------------------------------------------------------------- /rep.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #if defined(_WIN32) || defined(WIN32) 8 | #include 9 | #include 10 | #include 11 | #include 12 | #else 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #endif 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #ifndef PATH_MAX 26 | #define PATH_MAX 256 27 | #endif 28 | 29 | void fail(const char* message) 30 | { 31 | fprintf(stderr, "%s\n", message); 32 | exit(255); 33 | } 34 | 35 | void options_fail(const char* message) 36 | { 37 | fprintf(stderr, "%s\n", message); 38 | exit(2); 39 | } 40 | 41 | void error(const char* what) 42 | { 43 | perror(what); 44 | exit(255); 45 | } 46 | 47 | char* strdup_up_to(const char* input, char ch) 48 | { 49 | char* result = strdup(input); 50 | char* p = strchr(result, ch); 51 | if (p) 52 | *p = '\0'; 53 | return result; 54 | } 55 | 56 | /* --- bvalue ------------------------------------------------------------- */ 57 | 58 | enum bvalue_type 59 | { 60 | BVALUE_INTEGER, 61 | BVALUE_BYTESTRING, 62 | BVALUE_LIST, 63 | BVALUE_DICTIONARY 64 | }; 65 | 66 | struct bvalue 67 | { 68 | enum bvalue_type type; 69 | union { 70 | int ivalue; 71 | struct { 72 | size_t size; 73 | size_t allocated; 74 | char data[1]; 75 | } bsvalue; 76 | struct { 77 | struct bvalue *item; 78 | struct bvalue *tail; 79 | } lvalue; 80 | struct { 81 | struct bvalue *key; 82 | struct bvalue *value; 83 | struct bvalue *tail; 84 | } dvalue; 85 | } value; 86 | }; 87 | 88 | struct bvalue* make_bvalue_integer(int n) 89 | { 90 | struct bvalue* result = (struct bvalue*)malloc(sizeof(struct bvalue)); 91 | result->type = BVALUE_INTEGER; 92 | result->value.ivalue = n; 93 | return result; 94 | } 95 | 96 | struct bvalue* allocate_bvalue_bytestring(size_t allocated) 97 | { 98 | struct bvalue* result = (struct bvalue*)malloc(sizeof(struct bvalue) + allocated); 99 | result->type = BVALUE_BYTESTRING; 100 | result->value.bsvalue.size = 0; 101 | result->value.bsvalue.allocated = allocated; 102 | result->value.bsvalue.data[0] = '\0'; 103 | return result; 104 | } 105 | 106 | struct bvalue* make_bvalue_bytestring(char* bytes, size_t size) 107 | { 108 | struct bvalue* result = allocate_bvalue_bytestring(size); 109 | result->value.bsvalue.size = size; 110 | memcpy(result->value.bsvalue.data, bytes, size); 111 | result->value.bsvalue.data[size] = '\0'; 112 | return result; 113 | } 114 | 115 | struct bvalue* make_bvalue_list(struct bvalue* item, struct bvalue* tail) 116 | { 117 | struct bvalue* result = (struct bvalue*)malloc(sizeof(struct bvalue)); 118 | result->type = BVALUE_LIST; 119 | result->value.lvalue.item = item; 120 | result->value.lvalue.tail = tail; 121 | return result; 122 | } 123 | 124 | struct bvalue* make_bvalue_dictionary(struct bvalue* key, struct bvalue* value, struct bvalue* tail) 125 | { 126 | struct bvalue* result = (struct bvalue*)malloc(sizeof(struct bvalue)); 127 | result->type = BVALUE_DICTIONARY; 128 | result->value.dvalue.key = key; 129 | result->value.dvalue.value = value; 130 | result->value.dvalue.tail = tail; 131 | return result; 132 | } 133 | 134 | void free_bvalue(struct bvalue* value) 135 | { 136 | while (value) 137 | { 138 | struct bvalue* next = NULL; 139 | switch (value->type) 140 | { 141 | case BVALUE_INTEGER: 142 | case BVALUE_BYTESTRING: 143 | break; 144 | case BVALUE_LIST: 145 | free_bvalue(value->value.lvalue.item); 146 | next = value->value.lvalue.tail; 147 | break; 148 | case BVALUE_DICTIONARY: 149 | free_bvalue(value->value.dvalue.key); 150 | free_bvalue(value->value.dvalue.value); 151 | next = value->value.dvalue.tail; 152 | break; 153 | } 154 | free(value); 155 | value = next; 156 | } 157 | } 158 | 159 | _Bool bvalue_equals_string(struct bvalue* value, const char* s) 160 | { 161 | if (!value) 162 | return false; 163 | if (BVALUE_BYTESTRING != value->type) 164 | return false; 165 | size_t length = strlen(s); 166 | if (length != value->value.bsvalue.size) 167 | return false; 168 | return !memcmp(value->value.bsvalue.data, s, length); 169 | } 170 | 171 | struct bvalue* bvalue_dictionary_get(struct bvalue* dictionary, const char* key) 172 | { 173 | size_t key_length = strlen(key); 174 | for ( ; dictionary; dictionary = dictionary->value.dvalue.tail) 175 | { 176 | if (BVALUE_DICTIONARY != dictionary->type) 177 | continue; 178 | if (!dictionary->value.dvalue.key) 179 | continue; 180 | if (!bvalue_equals_string(dictionary->value.dvalue.key, key)) 181 | continue; 182 | return dictionary->value.dvalue.value; 183 | } 184 | return NULL; 185 | } 186 | 187 | _Bool bvalue_list_contains_string(struct bvalue* list, const char* s) 188 | { 189 | if (!list) 190 | return false; 191 | if (BVALUE_LIST != list->type) 192 | return false; 193 | for (; list; list = list->value.lvalue.tail) 194 | if (bvalue_equals_string(list->value.lvalue.item, s)) 195 | return true; 196 | return false; 197 | } 198 | 199 | _Bool bvalue_has_status(struct bvalue* reply, const char* status_text) 200 | { 201 | struct bvalue* status = bvalue_dictionary_get(reply, "status"); 202 | if (!status) 203 | return false; 204 | if (bvalue_equals_string(status, status_text)) 205 | return true; 206 | if (bvalue_list_contains_string(status, status_text)) 207 | return true; 208 | return false; 209 | } 210 | 211 | void bvalue_append_string(struct bvalue** value, const char* bytes, size_t length) 212 | { 213 | if (0 == length) 214 | return; 215 | if (BVALUE_BYTESTRING != (*value)->type) 216 | fail("not a bytestring"); 217 | size_t new_length = (*value)->value.bsvalue.size + length; 218 | size_t new_allocated = (*value)->value.bsvalue.allocated; 219 | while (new_allocated < new_length) 220 | new_allocated <<= 1; 221 | if (new_allocated == (*value)->value.bsvalue.allocated) 222 | { 223 | memcpy((*value)->value.bsvalue.data + (*value)->value.bsvalue.size, bytes, length); 224 | (*value)->value.bsvalue.size = new_length; 225 | } 226 | else 227 | { 228 | struct bvalue* old = *value; 229 | *value = allocate_bvalue_bytestring(new_allocated); 230 | (*value)->value.bsvalue.size = new_length; 231 | memcpy((*value)->value.bsvalue.data, old->value.bsvalue.data, old->value.bsvalue.size); 232 | memcpy((*value)->value.bsvalue.data + old->value.bsvalue.size, bytes, length); 233 | free_bvalue(old); 234 | } 235 | } 236 | 237 | void bvalue_append_bvalue(struct bvalue** targetp, struct bvalue* value) 238 | { 239 | char ivalue[64]; 240 | switch (value->type) 241 | { 242 | case BVALUE_INTEGER: 243 | sprintf(ivalue, "%d", value->value.ivalue); 244 | bvalue_append_string(targetp, ivalue, strlen(ivalue)); 245 | break; 246 | case BVALUE_BYTESTRING: 247 | bvalue_append_string(targetp, value->value.bsvalue.data, value->value.bsvalue.size); 248 | break; 249 | case BVALUE_LIST: 250 | case BVALUE_DICTIONARY: 251 | fail("unhandled case of bvalue_append_bvalue()"); 252 | break; 253 | } 254 | } 255 | 256 | const char PERCENT = '%'; 257 | const char NEWLINE = '\n'; 258 | 259 | void bvalue_append_format(struct bvalue** targetp, struct bvalue* value, const char* format) 260 | { 261 | for (const char* p = format; *p; p++) 262 | { 263 | if (*p != '%') 264 | { 265 | bvalue_append_string(targetp, p, 1); 266 | continue; 267 | } 268 | switch (p[1]) 269 | { 270 | case '%': 271 | p++; 272 | bvalue_append_string(targetp, &PERCENT, 1); 273 | break; 274 | case 'n': 275 | p++; 276 | bvalue_append_string(targetp, &NEWLINE, 1); 277 | break; 278 | case '.': 279 | p++; 280 | bvalue_append_bvalue(targetp, value); 281 | break; 282 | case '{': 283 | p += 2; 284 | const char* end = strchr(p, '}'); 285 | if (!end) 286 | fail("no closing brace in format"); 287 | char *key_name = NULL; 288 | char *element_pattern = NULL; 289 | if (strchr(p, ',') && strchr(p, ',') < end) 290 | { 291 | key_name = strdup_up_to(p, ','); 292 | element_pattern = strdup_up_to(strchr(p, ',') + 1, '}'); 293 | } 294 | else 295 | { 296 | key_name = strdup_up_to(p, '}'); 297 | element_pattern = strdup("%.%n"); 298 | } 299 | struct bvalue* embed = bvalue_dictionary_get(value, key_name); 300 | free(key_name); 301 | if (embed) 302 | { 303 | if (BVALUE_LIST == embed->type) 304 | { 305 | for (struct bvalue* i = embed; i; i = i->value.lvalue.tail) 306 | bvalue_append_format(targetp, i->value.lvalue.item, element_pattern); 307 | } 308 | else 309 | bvalue_append_bvalue(targetp, embed); 310 | } 311 | free(element_pattern); 312 | p = end; 313 | break; 314 | default: 315 | fail("invalid character in format"); 316 | } 317 | } 318 | } 319 | 320 | struct bvalue* bvalue_format(struct bvalue* value, const char* format) 321 | { 322 | struct bvalue* result = allocate_bvalue_bytestring(128); 323 | bvalue_append_format(&result, value, format); 324 | return result; 325 | } 326 | 327 | 328 | void bvalue_dump(struct bvalue* value, const char* prefix) 329 | { 330 | switch (value->type) 331 | { 332 | case BVALUE_BYTESTRING: 333 | printf("\"%s\"", value->value.bsvalue.data); 334 | break; 335 | case BVALUE_INTEGER: 336 | printf("%d", value->value.ivalue); 337 | break; 338 | case BVALUE_DICTIONARY: 339 | { 340 | printf("{"); 341 | char *new_prefix = (char*)malloc(strlen(prefix)+3); 342 | strcpy(new_prefix, prefix); 343 | strcat(new_prefix, " "); 344 | for (struct bvalue* iterator = value; iterator; iterator = iterator->value.dvalue.tail) 345 | { 346 | printf("\n%s", new_prefix); 347 | bvalue_dump(iterator->value.dvalue.key, new_prefix); 348 | printf(": "); 349 | bvalue_dump(iterator->value.dvalue.value, new_prefix); 350 | } 351 | free(new_prefix); 352 | printf("\n%s}", prefix); 353 | } 354 | break; 355 | case BVALUE_LIST: 356 | { 357 | printf("["); 358 | char *new_prefix = (char*)malloc(strlen(prefix)+3); 359 | strcpy(new_prefix, prefix); 360 | strcat(new_prefix, " "); 361 | for (struct bvalue* iterator = value; iterator; iterator = iterator->value.lvalue.tail) 362 | { 363 | printf("\n%s", new_prefix); 364 | bvalue_dump(iterator->value.lvalue.item, new_prefix); 365 | } 366 | free(new_prefix); 367 | printf("\n%s]", prefix); 368 | } 369 | break; 370 | } 371 | } 372 | 373 | 374 | /* --- breader ------------------------------------------------------------ */ 375 | 376 | struct breader 377 | { 378 | int fd; 379 | int peeked_char; 380 | }; 381 | 382 | struct bvalue* breader_read(struct breader* reader); 383 | 384 | struct breader* make_breader(int fd) 385 | { 386 | struct breader* reader = (struct breader*)malloc(sizeof(struct breader)); 387 | reader->fd = fd; 388 | reader->peeked_char = EOF; 389 | return reader; 390 | } 391 | 392 | void free_breader(struct breader* reader) 393 | { 394 | free(reader); 395 | } 396 | 397 | int bread_next_char(struct breader* reader) 398 | { 399 | if (EOF != reader->peeked_char) 400 | { 401 | int result = reader->peeked_char; 402 | reader->peeked_char = EOF; 403 | return result; 404 | } 405 | char ch = 0; 406 | int count = recv(reader->fd, &ch, 1, 0); 407 | if (count < 0) 408 | error("recv"); 409 | if (0 == count) 410 | return EOF; 411 | return ch; 412 | } 413 | 414 | int bread_peek_char(struct breader* reader) 415 | { 416 | if (EOF == reader->peeked_char) 417 | reader->peeked_char = bread_next_char(reader); 418 | return reader->peeked_char; 419 | } 420 | 421 | struct bvalue* bread_dictionary(struct breader* reader) 422 | { 423 | struct bvalue* result = NULL; 424 | struct bvalue** iterator = &result; 425 | 426 | bread_next_char(reader); 427 | while('e' != bread_peek_char(reader)) 428 | { 429 | struct bvalue* key = breader_read(reader); 430 | struct bvalue* value = breader_read(reader); 431 | *iterator = make_bvalue_dictionary(key, value, NULL); 432 | iterator = &(*iterator)->value.dvalue.tail; 433 | } 434 | bread_next_char(reader); 435 | return result; 436 | } 437 | 438 | struct bvalue* bread_list(struct breader* reader) 439 | { 440 | struct bvalue* result = NULL; 441 | struct bvalue** iterator = &result; 442 | bread_next_char(reader); 443 | while('e' != bread_peek_char(reader)) 444 | { 445 | struct bvalue* item = breader_read(reader); 446 | *iterator = make_bvalue_list(item, NULL); 447 | iterator = &(*iterator)->value.lvalue.tail; 448 | } 449 | bread_next_char(reader); 450 | return result; 451 | } 452 | 453 | struct bvalue* bread_integer(struct breader* reader) 454 | { 455 | int value = 0; 456 | _Bool negative = false; 457 | bread_next_char(reader); 458 | while('e' != bread_peek_char(reader)) 459 | { 460 | int ch = bread_next_char(reader); 461 | if (ch == '-') 462 | negative = true; 463 | else 464 | value = value * 10 + (ch - '0'); 465 | } 466 | bread_next_char(reader); 467 | if (negative) 468 | value = -value; 469 | return make_bvalue_integer(value); 470 | } 471 | 472 | struct bvalue* bread_bytestring(struct breader* reader) 473 | { 474 | size_t length = 0; 475 | char *bytes = NULL; 476 | while (':' != bread_peek_char(reader)) 477 | length = length*10 + (bread_next_char(reader) - '0'); 478 | bread_next_char(reader); 479 | 480 | bytes = (char*)malloc(length + 1); 481 | if (NULL == bytes) 482 | error("malloc"); 483 | for (size_t i = 0; i < length; i++) 484 | bytes[i] = bread_next_char(reader); 485 | bytes[length] = '\0'; 486 | struct bvalue* result = make_bvalue_bytestring(bytes, length); 487 | free(bytes); 488 | return result; 489 | } 490 | 491 | struct bvalue* breader_read(struct breader* reader) 492 | { 493 | switch (bread_peek_char(reader)) 494 | { 495 | case 'd': 496 | return bread_dictionary(reader); 497 | case 'l': 498 | return bread_list(reader); 499 | case 'i': 500 | return bread_integer(reader); 501 | case '0': case '1': case '2': case '3': case '4': 502 | case '5': case '6': case '7': case '8': case '9': 503 | return bread_bytestring(reader); 504 | case EOF: 505 | fail("Unexpected EOF"); 506 | default: 507 | fail("bad character in nREPL stream"); 508 | } 509 | return NULL; 510 | } 511 | 512 | /* -- print option -------------------------------------------------------- */ 513 | 514 | struct print_option 515 | { 516 | struct print_option* next; 517 | char* key; 518 | int fd; 519 | char* format; 520 | }; 521 | 522 | struct print_option* make_print_option(const char* optarg) 523 | { 524 | struct print_option* print = (struct print_option*)malloc(sizeof(struct print_option)); 525 | memset(print, 0, sizeof(struct print_option)); 526 | print->next = NULL; 527 | print->fd = 1; 528 | if (!strchr(optarg, ',')) 529 | { 530 | print->key = strdup(optarg); 531 | print->format = (char*)malloc(strlen(optarg) + 4); 532 | sprintf(print->format, "%%{%s}", optarg); 533 | } 534 | else 535 | { 536 | print->key = strdup_up_to(optarg, ','); 537 | const char* p = strchr(optarg, ',') + 1; 538 | print->fd = atoi(p); 539 | p = strchr(p, ','); 540 | if (NULL == p) 541 | options_fail("--print option is either KEY or KEY,FD,FORMAT"); 542 | ++p; 543 | print->format = strdup(p); 544 | } 545 | return print; 546 | } 547 | 548 | void free_print_options(struct print_option* print) 549 | { 550 | while (print) 551 | { 552 | if (print->key) 553 | free(print->key); 554 | if (print->format) 555 | free(print->format); 556 | void* p = print; 557 | print = print->next; 558 | free(p); 559 | } 560 | } 561 | 562 | void append_print_option(struct print_option** options, struct print_option* new) 563 | { 564 | while (*options) 565 | options = &(*options)->next; 566 | *options = new; 567 | } 568 | 569 | void remove_print_option(struct print_option** options, const char* key) 570 | { 571 | while (*options) 572 | { 573 | if (!strcmp((*options)->key, key)) 574 | { 575 | struct print_option* dead = *options; 576 | *options = (*options)->next; 577 | dead->next = NULL; 578 | free_print_options(dead); 579 | } 580 | else 581 | options = &(*options)->next; 582 | } 583 | } 584 | 585 | struct print_option* make_default_print_options(void) 586 | { 587 | struct print_option* result = make_print_option("out,1,%{out}"); 588 | append_print_option(&result, make_print_option("err,2,%{err}")); 589 | append_print_option(&result, make_print_option("value,1,%{value}%n")); 590 | return result; 591 | } 592 | 593 | /* -- options ------------------------------------------------------------- */ 594 | 595 | struct options 596 | { 597 | char* port; 598 | char* namespace; 599 | char* op; 600 | char* code; 601 | char* filename; 602 | int line; 603 | int column; 604 | char* send; 605 | _Bool help; 606 | struct print_option* print; 607 | _Bool verbose; 608 | }; 609 | 610 | struct sockaddr_in options_address(struct options* options, const char* port); 611 | 612 | char *read_file(const char* filename, char* buffer, size_t buffer_size) 613 | { 614 | FILE *portfile = fopen(filename, "r"); 615 | if (NULL == portfile) 616 | return NULL; 617 | if (NULL == fgets(buffer, buffer_size, portfile)) 618 | { 619 | fclose(portfile); 620 | return NULL; 621 | } 622 | fclose(portfile); 623 | return buffer; 624 | } 625 | 626 | struct sockaddr_in options_address_from_file(struct options* options, const char* filename) 627 | { 628 | char linebuffer[256]; 629 | if (!read_file(filename, linebuffer, sizeof(linebuffer))) 630 | error(filename); 631 | return options_address(options, linebuffer); 632 | } 633 | 634 | struct sockaddr_in options_address_from_relative_file(struct options* options, const char* directory_in, const char* filename) 635 | { 636 | char *directory = strdup(directory_in); 637 | for (;;) 638 | { 639 | #if defined(_WIN32) || defined(WIN32) 640 | struct _stat statb; 641 | #else 642 | struct stat statb; 643 | #endif 644 | char* path_to_check = (char*)malloc(strlen(directory) + strlen(filename) + 2); 645 | sprintf(path_to_check, "%s/%s", directory, filename); 646 | #if defined(_WIN32) || defined(WIN32) 647 | if (0 == _stat(path_to_check, &statb)) 648 | #else 649 | if (0 == stat(path_to_check, &statb)) 650 | #endif 651 | { 652 | struct sockaddr_in result = options_address_from_file(options, path_to_check); 653 | free(path_to_check); 654 | free(directory); 655 | return result; 656 | } 657 | free(path_to_check); 658 | 659 | char* old_directory = strdup(directory); 660 | char* parent_directory = dirname(directory); 661 | if (!strcmp(old_directory, parent_directory)) 662 | { 663 | char error_message[PATH_MAX + 128]; 664 | sprintf(error_message, "rep: No ancestor of %s contains %s", directory_in, filename); 665 | fail(error_message); 666 | } 667 | free(old_directory); 668 | char* new_directory = strdup(parent_directory); 669 | free(directory); 670 | directory = new_directory; 671 | } 672 | } 673 | 674 | char* make_path_absolute(const char* path) 675 | { 676 | if (*path == '/') 677 | return strdup(path); 678 | char* absolute_directory = (char*)malloc(strlen(path) + PATH_MAX + 2); 679 | if (NULL == getcwd(absolute_directory, PATH_MAX)) 680 | error("getcwd"); 681 | strcat(absolute_directory, "/"); 682 | strcat(absolute_directory, path); 683 | return absolute_directory; 684 | } 685 | 686 | struct sockaddr_in options_address(struct options* options, const char* port) 687 | { 688 | if (*port == '@' && !strchr(port + 1, '@')) 689 | return options_address_from_file(options, port + 1); 690 | else if (*port == '@') 691 | { 692 | const char* relative_directory = strchr(port + 1, '@') + 1; 693 | char* absolute_directory = make_path_absolute(relative_directory); 694 | char* filename = strdup_up_to(port + 1, '@'); 695 | struct sockaddr_in result = options_address_from_relative_file(options, absolute_directory, filename); 696 | free(absolute_directory); 697 | free(filename); 698 | return result; 699 | } 700 | struct sockaddr_in address = 701 | { 702 | .sin_family = AF_INET, 703 | .sin_addr.s_addr = htonl(INADDR_LOOPBACK), 704 | .sin_port = 0, 705 | }; 706 | if (strchr(port, ':')) 707 | { 708 | char *host_part = strdup_up_to(port, ':'); 709 | #if defined(_WIN32) || defined(WIN32) 710 | size_t hostl = strlen(host_part) + 1; 711 | wchar_t *whost = calloc(sizeof(wchar_t), hostl); 712 | mbstowcs(whost, host_part, hostl); 713 | LPCWSTR whost_part = whost; 714 | if (!InetPtonW(AF_INET, whost_part, &address.sin_addr)) 715 | #else 716 | if (!inet_aton(host_part, &address.sin_addr)) 717 | #endif 718 | { 719 | struct hostent *ent = gethostbyname(host_part); 720 | if (NULL == ent) 721 | error(host_part); 722 | if (NULL == ent->h_addr) 723 | { 724 | fprintf(stderr, "%s has no addresses\n", host_part); 725 | exit(255); 726 | } 727 | address.sin_addr.s_addr = *(u_long *)ent->h_addr_list[0]; 728 | } 729 | free(host_part); 730 | #if defined(_WIN32) || defined(WIN32) 731 | whost_part = NULL; 732 | free(whost); 733 | #endif 734 | port = strchr(port, ':') + 1; 735 | } 736 | address.sin_port = htons(atoi(port)); 737 | return address; 738 | } 739 | 740 | char* collect_code(int argc, char *argv[], int start) 741 | { 742 | size_t code_size = 0; 743 | for (int i = optind; i < argc; ++i) 744 | code_size += strlen(argv[i]) + 1; 745 | char *code = (char *)malloc(code_size); 746 | code[0] = '\0'; 747 | for (int i = optind; i < argc; ++i) 748 | { 749 | if (code[0]) 750 | strcat(code, " "); 751 | strcat(code, argv[i]); 752 | } 753 | return code; 754 | } 755 | 756 | enum { 757 | OPT_OP = 127, 758 | OPT_NO_PRINT, 759 | OPT_PRINT, 760 | OPT_SEND, 761 | }; 762 | 763 | 764 | const char SHORT_OPTIONS[] = "hl:n:p:v"; 765 | const struct option LONG_OPTIONS[] = 766 | { 767 | { "help", 0, NULL, 'h' }, 768 | { "line", 1, NULL, 'l' }, 769 | { "namespace", 1, NULL, 'n' }, 770 | { "no-print", 1, NULL, OPT_NO_PRINT }, 771 | { "op", 1, NULL, OPT_OP }, 772 | { "port", 1, NULL, 'p' }, 773 | { "print", 1, NULL, OPT_PRINT }, 774 | { "send", 1, NULL, OPT_SEND }, 775 | { "verbose", 0, NULL, 'v' }, 776 | { NULL, 0, NULL, 0 } 777 | }; 778 | 779 | struct options* new_options(void) 780 | { 781 | struct options* options = (struct options*)malloc(sizeof(struct options)); 782 | options->port = strdup("@.nrepl-port@."); 783 | options->op = strdup("eval"); 784 | options->namespace = strdup("user"); 785 | options->code = NULL; 786 | options->help = false; 787 | options->line = -1; 788 | options->column = -1; 789 | options->filename = NULL; 790 | options->send = strdup(""); 791 | options->print = make_default_print_options(); 792 | options->verbose = false; 793 | return options; 794 | } 795 | 796 | void options_parse_line(struct options* options, const char* line) 797 | { 798 | int colons = 0; 799 | int nondigits = 0; 800 | for (const char* p = line; *p; ++p) 801 | { 802 | if (*p == ':') 803 | ++colons; 804 | else if (!isdigit(*p)) 805 | ++nondigits; 806 | } 807 | if (2 == colons) 808 | { 809 | options->filename = strdup_up_to(line, ':'); 810 | const char* p = strchr(line, ':') + 1; 811 | options->line = atoi(p); 812 | p = strchr(p, ':') + 1; 813 | options->column = atoi(p); 814 | } 815 | else if (1 == colons && 0 == nondigits) 816 | { 817 | options->line = atoi(line); 818 | options->column = atoi(strchr(line, ':') + 1); 819 | } 820 | else if (1 == colons) 821 | { 822 | options->filename = strdup_up_to(line, ':'); 823 | options->line = atoi(strchr(line, ':') + 1); 824 | } 825 | else if (0 == colons && 0 == nondigits) 826 | options->line = atoi(line); 827 | else if (0 == colons) 828 | { 829 | options->filename = strdup(line); 830 | options->line = 1; 831 | } 832 | else 833 | options_fail("invalid value for --line"); 834 | } 835 | 836 | void options_parse_send(struct options* options, const char* send) 837 | { 838 | char* key = NULL; 839 | char* type = NULL; 840 | 841 | if (NULL == strchr(send, ',')) 842 | options_fail("--send value must be KEY,TYPE,VALUE"); 843 | key = strdup_up_to(send, ','); 844 | send = strchr(send, ',') + 1; 845 | 846 | if (NULL == strchr(send, ',')) 847 | options_fail("--send value must be KEY,TYPE,VALUE"); 848 | type = strdup_up_to(send, ','); 849 | send = strchr(send, ',') + 1; 850 | 851 | options->send = (char*)realloc(options->send, strlen(options->send) + strlen(key) + 16 + strlen(send) + 16); 852 | sprintf(options->send + strlen(options->send), "%lu:%s", strlen(key), key); 853 | if (!strcmp(type, "string")) 854 | sprintf(options->send + strlen(options->send), "%lu:%s", strlen(send), send); 855 | else if (!strcmp(type, "integer")) 856 | sprintf(options->send + strlen(options->send), "i%de", atoi(send)); 857 | else 858 | options_fail("--send TYPE must be 'string' or 'integer'"); 859 | } 860 | 861 | struct options* parse_options(int argc, char* argv[]) 862 | { 863 | struct options* options = new_options(); 864 | int opt; 865 | _Bool have_print = false; 866 | while ((opt = getopt_long(argc, argv, SHORT_OPTIONS, LONG_OPTIONS, NULL)) != -1) 867 | { 868 | switch (opt) 869 | { 870 | case 'h': 871 | options->help = true; 872 | break; 873 | case 'l': 874 | options_parse_line(options, optarg); 875 | break; 876 | case 'n': 877 | free(options->namespace); 878 | options->namespace = strdup(optarg); 879 | break; 880 | case 'p': 881 | free(options->port); 882 | options->port = strdup(optarg); 883 | break; 884 | case 'v': 885 | options->verbose = true; 886 | break; 887 | case OPT_OP: 888 | free(options->op); 889 | options->op = strdup(optarg); 890 | break; 891 | case OPT_NO_PRINT: 892 | remove_print_option(&options->print, optarg); 893 | break; 894 | case OPT_PRINT: 895 | if (!have_print) 896 | { 897 | free_print_options(options->print); 898 | options->print = NULL; 899 | have_print = true; 900 | } 901 | append_print_option(&options->print, make_print_option(optarg)); 902 | break; 903 | case OPT_SEND: 904 | options_parse_send(options, optarg); 905 | break; 906 | case '?': 907 | exit(2); 908 | } 909 | } 910 | options->code = collect_code(argc, argv, optind); 911 | return options; 912 | } 913 | 914 | void free_options(struct options* options) 915 | { 916 | if (options->port) 917 | free(options->port); 918 | if (options->op) 919 | free(options->op); 920 | if (options->namespace) 921 | free(options->namespace); 922 | if (options->code) 923 | free(options->code); 924 | if (options->filename) 925 | free(options->filename); 926 | if (options->send) 927 | free(options->send); 928 | free_print_options(options->print); 929 | free(options); 930 | } 931 | 932 | /* -- nrepl --------------------------------------------------------------- */ 933 | 934 | struct nrepl 935 | { 936 | struct options* options; 937 | int fd; 938 | struct breader *decode; 939 | _Bool exception_occurred; 940 | char* session; 941 | }; 942 | 943 | struct nrepl* make_nrepl(struct options* options) 944 | { 945 | struct nrepl* nrepl = (struct nrepl*)malloc(sizeof(struct nrepl)); 946 | nrepl->options = options; 947 | nrepl->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 948 | if (nrepl->fd == -1) 949 | error("socket"); 950 | nrepl->decode = make_breader(nrepl->fd); 951 | nrepl->exception_occurred = false; 952 | nrepl->session = NULL; 953 | return nrepl; 954 | } 955 | 956 | void free_nrepl(struct nrepl* nrepl) 957 | { 958 | free_options(nrepl->options); 959 | if (nrepl->fd >= 0) 960 | close(nrepl->fd); 961 | if (nrepl->decode) 962 | free_breader(nrepl->decode); 963 | if (nrepl->session) 964 | free(nrepl->session); 965 | free(nrepl); 966 | } 967 | 968 | void nrepl_receive_until_done(struct nrepl* nrepl) 969 | { 970 | _Bool done = false; 971 | while (!done) 972 | { 973 | struct bvalue* reply = breader_read(nrepl->decode); 974 | 975 | if (nrepl->options->verbose) 976 | { 977 | printf("<< "); 978 | bvalue_dump(reply, "<< "); 979 | printf("\n"); 980 | } 981 | 982 | struct bvalue* new_session = bvalue_dictionary_get(reply, "new-session"); 983 | if (new_session && BVALUE_BYTESTRING == new_session->type) 984 | { 985 | if (nrepl->session) 986 | free(nrepl->session); 987 | nrepl->session = strdup(new_session->value.bsvalue.data); 988 | } 989 | 990 | for (struct print_option* print = nrepl->options->print; print; print = print->next) 991 | { 992 | if (NULL == bvalue_dictionary_get(reply, print->key)) 993 | continue; 994 | struct bvalue* s = bvalue_format(reply, print->format); 995 | (void)write(print->fd, s->value.bsvalue.data, s->value.bsvalue.size); 996 | free_bvalue(s); 997 | } 998 | 999 | struct bvalue* ex = bvalue_dictionary_get(reply, "ex"); 1000 | if (ex) 1001 | nrepl->exception_occurred = true; 1002 | 1003 | if (bvalue_has_status(reply, "done")) 1004 | done = true; 1005 | if (bvalue_has_status(reply, "error")) 1006 | nrepl->exception_occurred = true; 1007 | if (bvalue_has_status(reply, "namespace-not-found")) 1008 | { 1009 | static const char MESSAGE[] = "the namespace does not exist\n"; 1010 | (void)write(2, MESSAGE, strlen(MESSAGE)); 1011 | } 1012 | 1013 | free_bvalue(reply); 1014 | } 1015 | } 1016 | 1017 | void nrepl_send(struct nrepl* nrepl, const char* format, ...) 1018 | { 1019 | va_list vargs; 1020 | 1021 | va_start(vargs, format); 1022 | size_t length = vsnprintf(NULL, 0, format, vargs); 1023 | va_end(vargs); 1024 | 1025 | char* message = (char*)malloc(length + 1); 1026 | va_start(vargs, format); 1027 | vsnprintf(message, length+1, format, vargs); 1028 | va_end(vargs); 1029 | 1030 | if (nrepl->options->verbose) 1031 | { 1032 | printf(">> "); 1033 | fwrite(message, 1, length, stdout); 1034 | printf("\n"); 1035 | } 1036 | 1037 | if (send(nrepl->fd, message, length, 0) != length) 1038 | error("send"); 1039 | 1040 | free(message); 1041 | 1042 | nrepl_receive_until_done(nrepl); 1043 | } 1044 | 1045 | int nrepl_exec(struct nrepl* nrepl) 1046 | { 1047 | nrepl->exception_occurred = false; 1048 | 1049 | struct sockaddr_in address = options_address(nrepl->options, nrepl->options->port); 1050 | if (-1 == connect(nrepl->fd, (struct sockaddr*)&address, sizeof(address))) 1051 | error("connect"); 1052 | 1053 | nrepl_send(nrepl, "d2:op5:clonee"); 1054 | 1055 | char extra_options[512] = ""; 1056 | if (nrepl->options->line != -1) 1057 | sprintf(extra_options + strlen(extra_options), "4:linei%de", nrepl->options->line); 1058 | if (nrepl->options->column != -1) 1059 | sprintf(extra_options + strlen(extra_options), "6:columni%de", nrepl->options->column); 1060 | if (nrepl->options->filename) 1061 | sprintf(extra_options + strlen(extra_options), "4:file%lu:%s", strlen(nrepl->options->filename), nrepl->options->filename); 1062 | 1063 | nrepl_send(nrepl, "d2:op%lu:%s2:ns%lu:%s7:session%lu:%s4:code%lu:%s%s%se", 1064 | strlen(nrepl->options->op), nrepl->options->op, 1065 | strlen(nrepl->options->namespace), nrepl->options->namespace, 1066 | strlen(nrepl->session), nrepl->session, 1067 | strlen(nrepl->options->code), nrepl->options->code, 1068 | nrepl->options->send, 1069 | extra_options); 1070 | 1071 | nrepl_send(nrepl, "d2:op5:close7:session%lu:%se", 1072 | strlen(nrepl->session), nrepl->session); 1073 | 1074 | if (nrepl->exception_occurred) 1075 | return 1; 1076 | return 0; 1077 | } 1078 | 1079 | /* ------------------------------------------------------------------------ */ 1080 | 1081 | void help(void) 1082 | { 1083 | printf("\ 1084 | rep: Single-shot nREPL client\n\ 1085 | Synopsis:\n\ 1086 | rep [OPTIONS] [--] [CODE ...]\n\ 1087 | Options:\n\ 1088 | -h, --help Show this help screen.\n\ 1089 | -l, --line=[FILE:]LINE[:COLUMN] Set reference file, line, and column for errors.\n\ 1090 | -n, --namespace=NS Evaluate code in NS (default: user).\n\ 1091 | --no-print=KEY Suppress output for KEY.\n\ 1092 | --op=OP nREPL operation (default: eval).\n\ 1093 | -p, --port=ADDRESS TCP port, host:port, @portfile, or @FNAME@RELATIVE.\n\ 1094 | --print=KEY|KEY,FD,FORMAT Print FORMAT to FD when KEY is present.\n\ 1095 | --send=KEY,TYPE,VALUE Send additional KEY of VALUE in request.\n\ 1096 | -v, --verbose Show all messages sent and received.\n\ 1097 | \n"); 1098 | } 1099 | 1100 | int main(int argc, char *argv[]) 1101 | { 1102 | #if defined(_WIN32) || defined(WIN32) 1103 | WSADATA wsa; 1104 | if (WSAStartup(MAKEWORD(2,2), &wsa) != 0) { 1105 | printf("Error starting up windows sockets: %d", WSAGetLastError()); 1106 | return 1; 1107 | } 1108 | #endif 1109 | 1110 | struct options* options = parse_options(argc, argv); 1111 | if (options->help) 1112 | { 1113 | help(); 1114 | exit(0); 1115 | } 1116 | struct nrepl* nrepl = make_nrepl(options); 1117 | int error_code = nrepl_exec(nrepl); 1118 | free_nrepl(nrepl); 1119 | exit(error_code); 1120 | } 1121 | --------------------------------------------------------------------------------