Skip to content

Commit

Permalink
CLI: Refactoring with prompts and auto-completion (#1072)
Browse files Browse the repository at this point in the history
  • Loading branch information
ishank011 authored Aug 12, 2020
1 parent 89ae463 commit 2f9d60a
Show file tree
Hide file tree
Showing 45 changed files with 1,071 additions and 345 deletions.
7 changes: 7 additions & 0 deletions changelog/unreleased/cli-prompts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Enhancement: Refactor Reva CLI with prompts

The current CLI is a bit cumbersome to use with users having to type commands
containing all the associated config with no prompts or auto-completes. This PR
refactors the CLI with these functionalities.

https://github.com/cs3org/reva/pull/1072
235 changes: 235 additions & 0 deletions cmd/reva/arguments.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
// Copyright 2018-2020 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.

package main

import (
"bytes"
"encoding/gob"
"errors"
"sync"
"time"

"github.com/c-bata/go-prompt"
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
)

type argumentCompleter struct {
suggestions []prompt.Suggest
expiration time.Time
sync.RWMutex
}

func (c *Completer) loginArgumentCompleter() []prompt.Suggest {
if s, ok := checkCache(c.loginArguments); ok {
return s
}

types := []string{}
suggests := []prompt.Suggest{}

b, err := executeCommand(loginCommand(), "-list")
if err == nil {
dec := gob.NewDecoder(&b)
if err := dec.Decode(&types); err != nil {
return []prompt.Suggest{}
}

for _, t := range types {
suggests = append(suggests, prompt.Suggest{Text: t})
}
}

cacheSuggestions(c.loginArguments, suggests)
return suggests
}

func (c *Completer) lsArgumentCompleter(onlyDirs bool) []prompt.Suggest {
if onlyDirs {
if s, ok := checkCache(c.lsDirArguments); ok {
return s
}
} else {
if s, ok := checkCache(c.lsArguments); ok {
return s
}
}

info := []*provider.ResourceInfo{}
suggests := []prompt.Suggest{}

b, err := executeCommand(lsCommand(), "/home")
if err == nil {
dec := gob.NewDecoder(&b)
if err := dec.Decode(&info); err != nil {
return []prompt.Suggest{}
}

suggests = append(suggests, prompt.Suggest{Text: "/home"})
for _, r := range info {
if !onlyDirs || r.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER {
suggests = append(suggests, prompt.Suggest{Text: r.Path})
}
}
}

if onlyDirs {
cacheSuggestions(c.lsDirArguments, suggests)
} else {
cacheSuggestions(c.lsArguments, suggests)
}
return suggests
}

func (c *Completer) ocmShareArgumentCompleter() []prompt.Suggest {
if s, ok := checkCache(c.ocmShareArguments); ok {
return s
}

info := []*ocm.Share{}
suggests := []prompt.Suggest{}

b, err := executeCommand(ocmShareListCommand())
if err == nil {
dec := gob.NewDecoder(&b)
if err := dec.Decode(&info); err != nil {
return []prompt.Suggest{}
}

for _, r := range info {
suggests = append(suggests, prompt.Suggest{Text: r.Id.OpaqueId})
}
}

cacheSuggestions(c.ocmShareArguments, suggests)
return suggests
}

func (c *Completer) ocmShareReceivedArgumentCompleter() []prompt.Suggest {
if s, ok := checkCache(c.ocmShareReceivedArguments); ok {
return s
}

info := []*ocm.ReceivedShare{}
suggests := []prompt.Suggest{}

b, err := executeCommand(ocmShareListReceivedCommand())
if err == nil {
dec := gob.NewDecoder(&b)
if err := dec.Decode(&info); err != nil {
return []prompt.Suggest{}
}

for _, r := range info {
suggests = append(suggests, prompt.Suggest{Text: r.Share.Id.OpaqueId})
}
}

cacheSuggestions(c.ocmShareReceivedArguments, suggests)
return suggests
}

func (c *Completer) shareArgumentCompleter() []prompt.Suggest {
if s, ok := checkCache(c.shareArguments); ok {
return s
}

info := []*collaboration.Share{}
suggests := []prompt.Suggest{}

b, err := executeCommand(shareListCommand())
if err == nil {
dec := gob.NewDecoder(&b)
if err := dec.Decode(&info); err != nil {
return []prompt.Suggest{}
}

for _, r := range info {
suggests = append(suggests, prompt.Suggest{Text: r.Id.OpaqueId})
}
}

cacheSuggestions(c.shareArguments, suggests)
return suggests
}

func (c *Completer) shareReceivedArgumentCompleter() []prompt.Suggest {
if s, ok := checkCache(c.shareReceivedArguments); ok {
return s
}

info := []*collaboration.ReceivedShare{}
suggests := []prompt.Suggest{}

b, err := executeCommand(shareListReceivedCommand())
if err == nil {
dec := gob.NewDecoder(&b)
if err := dec.Decode(&info); err != nil {
return []prompt.Suggest{}
}

for _, r := range info {
suggests = append(suggests, prompt.Suggest{Text: r.Share.Id.OpaqueId})
}
}

cacheSuggestions(c.shareReceivedArguments, suggests)
return suggests
}

func executeCommand(cmd *command, args ...string) (bytes.Buffer, error) {
var b bytes.Buffer
var err error
if err = cmd.Parse(args); err != nil {
return b, err
}
defer cmd.ResetFlags()

c := make(chan error, 1)
go func() {
c <- cmd.Action(&b)
}()

select {
case err = <-c:
if err != nil {
return b, err
}
case <-time.After(500 * time.Millisecond):
return b, errors.New("command timed out")
}
return b, nil
}

func checkCache(a *argumentCompleter) ([]prompt.Suggest, bool) {
a.RLock()
defer a.RUnlock()
if time.Now().Before(a.expiration) {
return a.suggestions, true
}
return nil, false
}

func cacheSuggestions(a *argumentCompleter, suggests []prompt.Suggest) {
a.Lock()
a.suggestions = suggests
a.expiration = time.Now().Add(time.Second * 10)
a.Unlock()
}
11 changes: 7 additions & 4 deletions cmd/reva/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,17 @@ package main
import (
"flag"
"fmt"
"io"
)

// command is the representation to create commands
// Command is the representation to create commands
type command struct {
*flag.FlagSet
Name string
Action func() error
Action func(w ...io.Writer) error
Usage func() string
Description func() string
ResetFlags func()
}

// newCommand creates a new command
Expand All @@ -40,14 +42,15 @@ func newCommand(name string) *command {
Usage: func() string {
return fmt.Sprintf("Usage: %s", name)
},
Action: func() error {
Action: func(w ...io.Writer) error {
fmt.Println("Hello REVA")
return nil
},
Description: func() string {
return "TODO description"
},
FlagSet: fs,
FlagSet: fs,
ResetFlags: func() {},
}
return cmd
}
52 changes: 26 additions & 26 deletions cmd/reva/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,37 +34,17 @@ const (
editorPermission string = "editor"
)

func getConfigFile() string {
user, err := gouser.Current()
if err != nil {
panic(err)
}

return path.Join(user.HomeDir, ".reva.config")
type config struct {
Host string `json:"host"`
}

func getTokenFile() string {
func getConfigFile() string {
user, err := gouser.Current()
if err != nil {
panic(err)
}

return path.Join(user.HomeDir, ".reva-token")
}

func writeToken(token string) {
err := ioutil.WriteFile(getTokenFile(), []byte(token), 0600)
if err != nil {
panic(err)
}
}

func readToken() (string, error) {
data, err := ioutil.ReadFile(getTokenFile())
if err != nil {
return "", err
}
return string(data), nil
return path.Join(user.HomeDir, ".reva.config")
}

func readConfig() (*config, error) {
Expand All @@ -89,8 +69,28 @@ func writeConfig(c *config) error {
return ioutil.WriteFile(getConfigFile(), data, 0600)
}

type config struct {
Host string `json:"host"`
func getTokenFile() string {
user, err := gouser.Current()
if err != nil {
panic(err)
}

return path.Join(user.HomeDir, ".reva-token")
}

func readToken() (string, error) {
data, err := ioutil.ReadFile(getTokenFile())
if err != nil {
return "", err
}
return string(data), nil
}

func writeToken(token string) {
err := ioutil.WriteFile(getTokenFile(), []byte(token), 0600)
if err != nil {
panic(err)
}
}

func read(r *bufio.Reader) (string, error) {
Expand Down
Loading

0 comments on commit 2f9d60a

Please sign in to comment.