Skip to content
This repository has been archived by the owner on Jun 5, 2018. It is now read-only.

Commit

Permalink
Initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
calmh committed Oct 25, 2014
0 parents commit beb868c
Show file tree
Hide file tree
Showing 7 changed files with 697 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
bin
build
*.tar.gz
*.zip
19 changes: 19 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Copyright (C) 2014 Procera Networks, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

- The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
69 changes: 69 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
psmcli
======

This program implements a simple command line client to the PSM JSON-RPC
interface. It's primarily useful for exploration, debugging and making
small changes interactively.

psmcli is an open source component provided without any warranty or
support. Please read the LICENSE file.

Supported Features
------------------

* All PSM JSON-RPC commands.

* Tab completion of commands and object types.

* Parameter value hinting (using tab completion).

* JSON objects as parameters, using key1=val,key2=val syntax.

* Authentication, when required by PSM.

* Printing of the actual executed JSON-RPC command (when run with the
-v flag.)

Requirements
------------

A configured and enabled JSON-RPC source.

Example
-----

```$bash
$ psmcli psm.example.com:3994
psmcli 1.0.0 connected to 172.16.32.8:3994
PSM version 15.0.5.12 at psm.example.com
default@psm # subscriber list 1
{
"creationTime": "2014-09-15T08:17:08.623Z",
"expire": "2014-11-01T10:47:16.207Z",
"hostName": "syno-2",
"oid": 288230376151715606,
"parentOid": null,
"persistent": true,
"slot": 80,
"subscriberId": "20:c9:d0:43:0c:95",
"updateTime": "2014-10-25T10:47:16.207Z"
}
default@psm # object updateByAid subscriber 20:c9:d0:43:0c:95 hostName=newHostName,persistent=false
OK
default@psm # subscriber getByUid 288230376151715606
{
"creationTime": "2014-09-15T08:17:08.623Z",
"expire": "2014-11-01T10:47:16.207Z",
"hostName": "newHostName",
"oid": 288230376151715606,
"parentOid": null,
"persistent": false,
"slot": 80,
"subscriberId": "20:c9:d0:43:0c:95",
"updateTime": "2014-10-25T10:56:44.049Z"
}
default@psm #
```
46 changes: 46 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'

eval $(go env)
version=$(git describe --always --dirty)

pkg() {
name="psmcli-$version-$GOOS-$GOARCH"

rm -rf build
dst="build/$name"

mkdir -p "$dst"
cp README.md LICENSE "$dst"
go build -o "$dst/psmcli" -ldflags "-w -X main.Version $version"

if [[ "$GOOS" == "windows" ]] ; then
pushd build
zip -r "../$name.zip" "$name"
popd
else
tar zcvf "$name.tar.gz" -C build "$name"
fi

rm -rf build
}

case "${1:-default}" in
pkg)
pkg
;;

allpkg)
rm -f *.tar.gz *.zip
GOOS=linux GOARCH=amd64 pkg
GOOS=linux GOARCH=386 pkg
GOOS=windows GOARCH=amd64 pkg
GOOS=windows GOARCH=386 pkg
GOOS=darwin GOARCH=amd64 pkg
;;

default)
GOBIN="$(pwd)/bin" go install -ldflags "-w -X main.Version $version"
;;
esac
186 changes: 186 additions & 0 deletions completion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// psmcli
// Copyright (C) 2014 Procera Networks, Inc.

package main

import (
"fmt"
"log"
"sort"
"strings"

"code.google.com/p/go.crypto/ssh/terminal"
)

type completionMap map[string]completionWord

type completionWord struct {
placeholder bool // should be replaced by actual text by the user
optional bool // triggers "optional"-style formatting
next completionMap
}

type completer struct {
term *terminal.Terminal
inProgress bool
tabsPressed int
searchPos int
inPlaceholder bool
words completionMap
}

func (c *completer) importSMD(services map[string]smdService) {
c.words = make(completionMap)
for name, service := range services {
parts := strings.Split(name, ".")
words := c.words
for _, part := range parts {
word, ok := words[part]
if !ok {
word.next = make(completionMap)
words[part] = word
}
words = word.next
}
for _, param := range service.Parameters {
if param.Name == "type" && !param.Optional && param.Type == "string" {
// Handle "type" parameter specifically, as it can always expand to either "session", "subscriber" or "group".
next := make(completionMap)
for _, s := range []string{"session", "subscriber", "group"} {
word := completionWord{
placeholder: false,
optional: false,
next: next,
}
words[s] = word
}
words = next
} else {
// Add the parameter as a placeholder
word := completionWord{
placeholder: true,
optional: param.Optional,
next: make(completionMap),
}
words[param.Name] = word
words = word.next
}
}
}
}

type completionMatch struct {
line string
pos int
placeholder bool
}

type completionMatchSlice []completionMatch

func (s completionMatchSlice) Len() int {
return len(s)
}

func (s completionMatchSlice) Less(a, b int) bool {
return s[a].line < s[b].line
}

func (s completionMatchSlice) Swap(a, b int) {
s[a], s[b] = s[b], s[a]
}

func (c *completer) complete(line string, pos int, key rune) (string, int, bool) {
if key != '\t' {
c.inProgress = false
if c.inPlaceholder {
line = line[:pos]
c.inPlaceholder = false
return fmt.Sprintf("%s%c", line, key), pos + 1, true
}
return line, pos, false
}

if !c.inProgress || pos < c.searchPos {
c.tabsPressed = 0
c.inProgress = true
c.searchPos = pos
}

search := line[:c.searchPos]
allParts := strings.Fields(search)
if len(allParts) == 0 {
return line, pos, false
}
if line[c.searchPos-1] == ' ' {
allParts = append(allParts, "")
}

firstParts := allParts[:len(allParts)-1]
lastPart := allParts[len(allParts)-1]
words := c.words
for _, part := range firstParts {
word, found := words[part]
if !found {
return line, pos, false
}
words = word.next
}

var matches completionMatchSlice
for word, comp := range words {
if strings.HasPrefix(word, lastPart) {
if comp.placeholder {
// For placeholders, put the cursor at the start of the placeholder
newLine := strings.Join(firstParts, " ")
fullWord := "<" + word + ">"
if comp.optional {
fullWord = "[" + word + "]"
}
matches = append(matches, completionMatch{
line: newLine + " " + fullWord,
pos: len(newLine + " "),
placeholder: true,
})
} else {
// For regular matches, put the cursor at the end of the word
newLine := strings.Join(append(firstParts, word), " ")
matches = append(matches, completionMatch{
line: newLine,
pos: len(newLine),
})
}
}
}

if len(matches) == 0 {
return line, pos, false
}

if len(matches) == 1 && !matches[0].placeholder {
// Treat the only match specially, by appending a space and moving to
// the next word.
match := matches[0]
c.inProgress = false
c.inPlaceholder = false
return match.line + " ", match.pos + 1, true
}

sort.Sort(matches)
match := matches[c.tabsPressed%len(matches)]
c.inPlaceholder = match.placeholder
c.tabsPressed++
return match.line, match.pos, true
}

func (c *completer) printHelp() {
var commands []string
for word, comp := range c.words {
for subword := range comp.next {
commands = append(commands, word+" "+subword)
}
}
sort.Strings(commands)
for _, cmd := range commands {
log.Println(cmd)
}
}
Loading

0 comments on commit beb868c

Please sign in to comment.