├── LICENSE ├── README.md └── main.go /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Luke Champine 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jr 2 | == 3 | 4 | `jr` is a JSON-RPC command-line client written in Go. It is based on 5 | [jsonrpcake](https://github.com/joehillen/jsonrpcake), which was based on 6 | [HTTPie](https://github.com/jkbrzt/httpie). Usage of `jr` should be familiar 7 | to users of those programs. 8 | 9 | `jr` does not support the `@` syntax or colored output of the aforementioned 10 | programs. I'll add them if someone asks me to. 11 | 12 | `jr` *does* support an alternative parameter syntax: if you pass a single 13 | value without a `=`, it will be sent without being enclosed in an object. 14 | Ideally, you would be able to send multiple values this way (as an array), 15 | but Go's jsonrpc package does not support this. 16 | 17 | Installation 18 | ------------ 19 | 20 | ``` 21 | go get github.com/lukechampine/jr 22 | ``` 23 | 24 | Usage 25 | ----- 26 | 27 | ```bash 28 | # no hostname means localhost 29 | $ jr :3000 hello 30 | Hello, World! 31 | 32 | # string parameter 33 | $ jr :3000 hello name=Luke 34 | Hello, Luke! 35 | 36 | # bool parameter 37 | $ jr :3000 hello name=Luke excited:=false 38 | Hey, Luke. 39 | 40 | # stdin is not processed; must be valid JSON 41 | $ cat | jr :3000 hello 42 | { 43 | "name": "Luke", 44 | "excited": false 45 | } 46 | ^D 47 | Hey, Luke. 48 | ``` 49 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "io/ioutil" 9 | "net/rpc/jsonrpc" 10 | "os" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | func usage() { 16 | os.Stderr.WriteString(`USAGE: 17 | jr ADDRESS:PORT METHOD [PARAMETER...] 18 | 19 | Parameters are key/value pairs. They come in two forms: 20 | 21 | key=value (for strings; result is "key":"value") 22 | key:=value (for raw JSON; result is "key":value) 23 | 24 | Specifying one or more keys causes the request to be sent as a 25 | JSON object. To send a raw JSON value, you may use either a 26 | single value without a key (again using : for non-string types), 27 | or pipe the JSON to jr via stdin. 28 | 29 | Options: 30 | -no-format do not format JSON output (default: false) 31 | `) 32 | } 33 | 34 | func die(args ...interface{}) { 35 | fmt.Fprintln(os.Stderr, args...) 36 | os.Exit(1) 37 | } 38 | 39 | // parse arguments of the form bar, foo=bar, or foo:=3. 40 | func parseArgs(args []string) json.RawMessage { 41 | // single, unkeyed argument 42 | if len(args) == 1 && !strings.ContainsRune(args[0], '=') { 43 | arg := args[0] 44 | if len(arg) > 0 && arg[0] == ':' { 45 | // raw JSON 46 | arg = arg[1:] 47 | } else { 48 | // unquoted string 49 | arg = strconv.Quote(arg) 50 | } 51 | return json.RawMessage(arg) 52 | } 53 | 54 | for i, arg := range args { 55 | eq := strings.IndexByte(arg, '=') 56 | if eq == -1 { 57 | die("Invalid argument:", arg) 58 | } 59 | if arg[eq-1] == ':' { 60 | // raw JSON; only quote the key 61 | args[i] = fmt.Sprintf("%q:%s", arg[:eq-1], arg[eq+1:]) 62 | } else { 63 | // unquoted string; add quotes 64 | args[i] = fmt.Sprintf("%q:%q", arg[:eq], arg[eq+1:]) 65 | } 66 | } 67 | return json.RawMessage("{" + strings.Join(args, ",") + "}") 68 | } 69 | 70 | func main() { 71 | ugly := flag.Bool("no-format", false, "do not format JSON output") 72 | flag.Usage = usage 73 | flag.Parse() 74 | args := flag.Args() 75 | if len(args) < 2 { 76 | flag.Usage() 77 | os.Exit(2) 78 | } 79 | 80 | // detect whether the user is piping data to stdin 81 | // (not perfect; doesn't work with e.g. /dev/zero) 82 | stat, _ := os.Stdin.Stat() 83 | haveStdin := (stat.Mode() & os.ModeCharDevice) == 0 84 | 85 | // parse params, if supplied 86 | var params json.Marshaler 87 | if len(args) > 2 { 88 | params = parseArgs(args[2:]) 89 | } else if haveStdin { 90 | stdin, err := ioutil.ReadAll(os.Stdin) 91 | if err != nil { 92 | die("Couldn't read from stdin:", err) 93 | } 94 | params = json.RawMessage(stdin) 95 | } 96 | 97 | // connect to server 98 | cli, err := jsonrpc.Dial("tcp", args[0]) 99 | if err != nil { 100 | die("Couldn't connect to server:", err) 101 | } 102 | defer cli.Close() 103 | 104 | // call 105 | var reply json.RawMessage 106 | err = cli.Call(args[1], params, &reply) 107 | if err != nil { 108 | die("Call failed:", err) 109 | } 110 | if !json.Valid([]byte(reply)) { 111 | die("Call returned invalid JSON:", string(reply)) 112 | } 113 | 114 | // print response, formatting if requested 115 | buf := new(bytes.Buffer) 116 | if *ugly { 117 | buf.Write([]byte(reply)) 118 | } else { 119 | // reply is already validated, so no error possible 120 | json.Indent(buf, []byte(reply), "", "\t") 121 | } 122 | fmt.Println(buf) 123 | } 124 | --------------------------------------------------------------------------------