Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve autocompletion #74

Merged
merged 6 commits into from
Jul 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,21 @@ You can check everything is setup correctly by running:
sm2 --diagnostic
```

### Enabling tab completion
Run the commands below which will copy a completion script into a file called `sm2.bash` in the directory
`~/.local/share/bash-completion/completions`, if the directory doesn't exist then it will create it.
```shell
mkdir -p ~/.local/share/bash-completion/completions
sm2 --generate-autocomplete > ~/.local/share/bash-completion/completions/sm2.bash
```
If you are using zsh as your shell and not using Oh-My-Zsh then you may need to enable bash-completion support by adding the following to your `~/.zshrc` file
```shell
# Load bash completion functions
autoload -Uz +X compinit && compinit
autoload -Uz +X bashcompinit && bashcompinit
```


### Upgrading Service Manager 2
As of v1.0.9 `sm2` can update itself - simply run `sm2 -update`. You will need to ensure `sm2` is available on your `$PATH`.

Expand Down
20 changes: 0 additions & 20 deletions cli/autocomplete.go

This file was deleted.

128 changes: 86 additions & 42 deletions cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,55 +5,59 @@ import (
"flag"
"fmt"
"os"
"reflect"
"regexp"
"strconv"
"strings"
)

type UserOption struct {
appendArgs string // not exported, content decoded into ExtraArgs
AutoComplete bool // generates an autocomplete script
CheckPorts bool // finds duplicate ports
Clean bool // used with --start to force re-downloading
Config string // uses a different service-manager-config folder
Debug string // debug info about a service, used to determine why it failed to start
Diagnostic bool // runs tests to determine if there are problems with the install
ExtraArgs map[string][]string // parsed from content of AppendArgs
ExtraServices []string // ids of services to start
FromSource bool // used with --start to run from source rather than bin
FormatPlain bool // flag for setting enabling machine friendly/undecorated output
Latest bool // used in conjunction with --restart to check for latest version of service(s) being restarted
List bool // lists all the services
Logs string // prints the logs of a service, running or otherwise
NoProgress bool // hides the animated download progress meter
NoVpnCheck bool // skips checking if vpn is connected before starting a service
Offline bool // prints downloaded services, used with --start bypasses download and uses local copy
Port int // overrides service port, only works with the first service when starting multiple
Ports bool // prints all the ports
Prune bool // deletes .state files of services with a status of FAIL
Release string // specify a version when starting one service. unlikely old sm, cannot be used without a version
Restart bool // restarts a service or profile
ReverseProxy bool // starts a reverse-proxy on 3000 (override with --port)
Search string // searches for services/profiles
Start bool // starts a service, multiple services or a profile(s)
Status bool // shows status of everything that's running
StatusShort bool // same as --status but is the -s short version of the cmd
StopAll bool // stops all the services that are running
Stop bool // stops a service, multiple services or profile(s)
Update bool // update sm2 if a newer version is available
UpdateConfig bool // pulls the latest copy of service-manager-config
Verbose bool // shows extra logging
Version bool // prints sm2 version number
Verify bool // checks if a given service or profile is running
Wait int // waits given number of secs after starting services for then to respond to pings
Workers int // sets the number of concurrent downloads/service starts
DelaySeconds int // sets the pause in seconds between starting services
appendArgs string // not exported, content decoded into ExtraArgs
AutoComplete bool // generates an autocomplete response
CheckPorts bool // finds duplicate ports
Clean bool // used with --start to force re-downloading
CompWordCount int // used with --autocomplete number of words in completion
CompPreviousWord string // used with --autocomplete previous of word in completion
Config string // uses a different service-manager-config folder
Debug string // debug info about a service, used to determine why it failed to start
Diagnostic bool // runs tests to determine if there are problems with the install
ExtraArgs map[string][]string // parsed from content of AppendArgs
ExtraServices []string // ids of services to start
FromSource bool // used with --start to run from source rather than bin
FormatPlain bool // flag for setting enabling machine friendly/undecorated output
GenerateAutoComplete bool // generates an autocomplete script
Latest bool // used in conjunction with --restart to check for latest version of service(s) being restarted
List bool // lists all the services
Logs string // prints the logs of a service, running or otherwise
NoProgress bool // hides the animated download progress meter
NoVpnCheck bool // skips checking if vpn is connected before starting a service
Offline bool // prints downloaded services, used with --start bypasses download and uses local copy
Port int // overrides service port, only works with the first service when starting multiple
Ports bool // prints all the ports
Prune bool // deletes .state files of services with a status of FAIL
Release string // specify a version when starting one service. unlikely old sm, cannot be used without a version
Restart bool // restarts a service or profile
ReverseProxy bool // starts a reverse-proxy on 3000 (override with --port)
Search string // searches for services/profiles
Start bool // starts a service, multiple services or a profile(s)
Status bool // shows status of everything that's running
StatusShort bool // same as --status but is the -s short version of the cmd
StopAll bool // stops all the services that are running
Stop bool // stops a service, multiple services or profile(s)
Update bool // update sm2 if a newer version is available
UpdateConfig bool // pulls the latest copy of service-manager-config
Verbose bool // shows extra logging
Version bool // prints sm2 version number
Verify bool // checks if a given service or profile is running
Wait int // waits given number of secs after starting services for then to respond to pings
Workers int // sets the number of concurrent downloads/service starts
DelaySeconds int // sets the pause in seconds between starting services
}

func Parse(args []string) (*UserOption, error) {

opts := new(UserOption)
flagset := buildFlagSet(opts)
flagset := BuildFlagSet(opts)
flagset.Parse(fixupInvalidFlags(args))

if opts.Workers <= 0 {
Expand Down Expand Up @@ -153,20 +157,23 @@ func releaseIsValid(release string) bool {
return rx.MatchString(release)
}

func buildFlagSet(opts *UserOption) *flag.FlagSet {
func BuildFlagSet(opts *UserOption) *flag.FlagSet {
flagset := flag.NewFlagSet("servicemanager", flag.ExitOnError)

setUsage(flagset)
flagset.StringVar(&opts.appendArgs, "appendArgs", "", "A map of args to append for services you are starting. i.e. '{\"SERVICE_NAME\":[\"-DFoo=Bar\",\"SOMETHING\"],\"SERVICE_TWO\":[\"APPEND_THIS\"]}'")
flagset.BoolVar(&opts.AutoComplete, "generate-autocomplete", false, "generates bash completions script")
flagset.BoolVar(&opts.AutoComplete, "autocomplete", false, "generates bash completions response (used by bash-completions)")
flagset.BoolVar(&opts.CheckPorts, "checkports", false, "finds services using the same port number")
flagset.BoolVar(&opts.Clean, "clean", false, "forces reinstall of service (use with --start)")
flagset.StringVar(&opts.CompPreviousWord, "comp-pword", "", "used with --autocomplete by script generated using --generate-autocomplete")
flagset.IntVar(&opts.CompWordCount, "comp-cword", 1, "used with --autocomplete by script generated using --generate-autocomplete")
flagset.StringVar(&opts.Config, "config", "", "sets an alternate directory for service-manager-config")
flagset.StringVar(&opts.Debug, "debug", "", "infomation on why a given `service` may not have started")
flagset.BoolVar(&opts.Diagnostic, "diagnostic", false, "a suite of checks to debug issues with service manager")
flagset.BoolVar(&opts.FromSource, "src", false, "run service from source (use with --start)")
flagset.BoolVar(&opts.FormatPlain, "format-plain", false, "list services without formatting")
flagset.BoolVar(&opts.GenerateAutoComplete, "generate-autocomplete", false, "generates bash completions script")
flagset.BoolVar(&opts.Latest, "latest", false, "used in conjunction with -restart to check for latest version of service(s) being restarted")
flagset.BoolVar(&opts.List, "list", false, "lists all available services")
flagset.BoolVar(&opts.List, "list", false, "lists all available services and profiles")
flagset.StringVar(&opts.Logs, "logs", "", "shows the stdout logs for a service")
flagset.BoolVar(&opts.NoProgress, "noprogress", false, "prevents download progress being shown (use with --start)")
flagset.BoolVar(&opts.NoVpnCheck, "no-vpn-check", defaultVpnCheck(), "disables checking if the vpn is connected")
Expand Down Expand Up @@ -194,3 +201,40 @@ func buildFlagSet(opts *UserOption) *flag.FlagSet {

return flagset
}

// Based on flag.DefaultUsage to use -- for long arguments
func setUsage(f *flag.FlagSet) {
f.Usage = func() {
f.VisitAll(func(flag *flag.Flag) {
var s string
if len(flag.Name) == 1 {
s = fmt.Sprintf(" -%s", flag.Name)
} else {
s = fmt.Sprintf(" --%s", flag.Name)
}
usage := flag.Usage
flagType := strings.TrimSuffix(
strings.TrimPrefix(
reflect.TypeOf(flag.Value).String(),
"*flag."),
"Value")
if flagType == "bool" {
flagType = ""
}
if len(flagType) > 0 {
s += " " + flagType
}
// Boolean flags of one ASCII letter are so common we
// treat them specially, putting their usage on the same line.
if len(s) <= 4 { // space, space, '-', 'x'.
s += "\t"
} else {
// Four spaces before the tab triggers good alignment
// for both 4- and 8-space tab stops.
s += "\n \t"
}
s += strings.ReplaceAll(usage, "\n", "\n \t")
fmt.Fprint(f.Output(), s, "\n")
})
}
}
2 changes: 1 addition & 1 deletion makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))

BINARY := sm2
VERSION := 1.0.9
VERSION := 1.0.10
BUILD := `git rev-parse HEAD`

# Setup linker flags option for build that interoperate with variable names in src code
Expand Down
85 changes: 85 additions & 0 deletions servicemanager/autocomplete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package servicemanager

import (
"flag"
"fmt"
"sm2/cli"
"sort"
"strings"
)

// Print a valid bash autocomplete config to stdout
// intended use would be to pipe it into a file in the os's autocomplete folder
func GenerateAutoCompletionScript() {
fmt.Println("# Below is a bash completion script for tab completion")
fmt.Println(
`_serv_words()
{
local count cur
cur=${COMP_WORDS[COMP_CWORD]}
prev=${COMP_WORDS[COMP_CWORD-1]}
count=${COMP_CWORD}
words=$(sm2 --autocomplete --comp-cword $count --comp-pword \"$prev\" )
COMPREPLY=( $(compgen -W "$words" -- $cur) )
return 0
}
complete -F _serv_words sm2`)
}

func (sm *ServiceManager) GenerateAutocompleteResponse() string {
count := sm.Commands.CompWordCount
prev := strings.ReplaceAll(sm.Commands.CompPreviousWord, "\"", "")
if dontComplete(prev) {
return ""
}
var words strings.Builder
opts := new(cli.UserOption)
flagSet := cli.BuildFlagSet(opts)
flagSet.VisitAll(func(f *flag.Flag) {
if len(f.Name) == 1 {
words.WriteString(fmt.Sprintf("-%s ", f.Name))
} else {
words.WriteString(fmt.Sprintf("--%s ", f.Name))
}
})

if count >= 2 {
keys := make([]string, len(sm.Services)+len(sm.Profiles))
i := 0
for k := range sm.Services {
keys[i] = k
i++

}

for k := range sm.Profiles {
keys[i] = k
i++

}
sort.Strings(keys)
words.WriteString(strings.Join(keys, " "))
}
return words.String()
}

// Non boolean arguments can't be autocompleted
func dontComplete(previousWord string) bool {
switch strings.ReplaceAll(previousWord, "--", "-") {
case
"-appendArgs",
"-comp-cword",
"-comp-pword",
"-config",
"-debug",
"-logs",
"-port",
"-ports",
"-search",
"-wait",
"-workers",
"-delay-seconds":
return true
}
return false
}
5 changes: 3 additions & 2 deletions servicemanager/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"fmt"
"os"
"regexp"
"sm2/cli"
"sm2/version"
)

Expand Down Expand Up @@ -124,8 +123,10 @@ func (sm *ServiceManager) Run() {
}
} else if sm.Commands.Update {
err = update(sm.Config.TmpDir)
} else if sm.Commands.GenerateAutoComplete {
GenerateAutoCompletionScript()
} else if sm.Commands.AutoComplete {
cli.GenerateAutoCompletions()
fmt.Println(sm.GenerateAutocompleteResponse())
} else {
// show help if they're not using --update-config with another command
if !sm.Commands.UpdateConfig {
Expand Down