Skip to content

Commit

Permalink
Fix bash autocompletion with new script + add . alias
Browse files Browse the repository at this point in the history
- Bash completion was updated to init services itself as `.Before` is no longer called
  by github.com/urface/cli since v1.22.10 (see urfave/cli#1094).
- Add a `.` alias in arg parsing allowing a stack or a service to be acted upon
  when the user is currently in its directory.
  • Loading branch information
0rax committed Feb 20, 2024
1 parent 9352340 commit a5c2002
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 61 deletions.
7 changes: 3 additions & 4 deletions commands/export.go
Expand Up @@ -10,10 +10,9 @@ import (
)

var ExportCommand = &cli.Command{
Name: "export",
Usage: "Export those *#%&! env vars ",
Action: BeforeAfterWrapper(ExportAction),
BashComplete: ServicesBashComplete,
Name: "export",
Usage: "Export those *#%&! env vars ",
Action: BeforeAfterWrapper(ExportAction),
}

func ExportAction(c *cli.Context) error {
Expand Down
3 changes: 2 additions & 1 deletion commands/install.go
Expand Up @@ -17,7 +17,8 @@ var InstallCommand = &cli.Command{
Name: "install",
Usage: "Installs all the services",
Action: BeforeAfterWrapper(InstallAction),
BashComplete: ServicesBashComplete}
BashComplete: ServicesBashComplete,
}

// InstallAction installs all the services (or the specified ones)
func InstallAction(c *cli.Context) error {
Expand Down
7 changes: 4 additions & 3 deletions commands/ps.go
Expand Up @@ -16,9 +16,10 @@ import (
)

var PsCommand = &cli.Command{
Name: "ps",
Usage: "Outputs the status of all services",
Action: BeforeAfterWrapper(PsAction),
Name: "ps",
Usage: "Outputs the status of all services",
Action: BeforeAfterWrapper(PsAction),
BashComplete: ServicesBashComplete,
}

// PsAction checks the status for every service and output
Expand Down
57 changes: 34 additions & 23 deletions commands/utils.go
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"math"
"os"
"path"
"path/filepath"
"strings"

log "github.com/cihub/seelog"
Expand All @@ -21,17 +23,28 @@ const niceness = "1"
func FilterServices(c *cli.Context) map[string]*services.Service {
excludeMode := 0
args := c.Args().Slice()
included := make(map[string]bool)

for _, s := range args {
name := s
if strings.HasPrefix(s, "~") {
name = strings.Replace(s, "~", "", 1)
}
// Remove trailing slash to help with file autocomplete
name = strings.TrimRight(name, "/")
// Alias `.` to the current service if it exists
if name == "." {
cwd, _ := os.Getwd()
name, _ = filepath.Rel(services.ProjectPath, cwd)
}
// Check if arg match a service or a stack
if _, ok := services.Registry[name]; ok {
if strings.HasPrefix(s, "~") {
excludeMode += 1
delete(services.Registry, name)
} else {
excludeMode -= 1
included[name] = true
}
} else if stack, ok := services.StackRegistry[name]; ok {
if strings.HasPrefix(s, "~") {
Expand All @@ -42,9 +55,12 @@ func FilterServices(c *cli.Context) map[string]*services.Service {
delete(services.StackRegistry, name)
} else {
excludeMode -= 1
for _, svc := range stack {
included[svc.Name] = true
}
}
} else {
_ = log.Errorf("Service or stack %s not found", s)
_ = log.Errorf("Service or stack %s not found", name)
return nil
}
}
Expand All @@ -53,35 +69,15 @@ func FilterServices(c *cli.Context) map[string]*services.Service {
os.Exit(1)
}
if excludeMode < 0 {
for name, svc := range services.Registry {
included := false
for _, s := range args {
if name == s {
included = true
break
}
if svc.Stack == s {
included = true
}
}
if !included {
for name := range services.Registry {
if !included[name] {
delete(services.Registry, name)
}
}
}
return services.Registry
}

func ServicesBashComplete(c *cli.Context) {
for stack := range services.StackRegistry {
fmt.Println(stack)
}
for name := range services.Registry {
fmt.Println(name)
fmt.Println("~" + name)
}
}

func BeforeAfterWrapper(f func(c *cli.Context) error) func(c *cli.Context) error {
return func(c *cli.Context) error {
err := config.GetBeforeFunc()(c)
Expand All @@ -97,6 +93,21 @@ func BeforeAfterWrapper(f func(c *cli.Context) error) func(c *cli.Context) error
}
}

func ServicesBashComplete(c *cli.Context) {
confVal := config.FindProjectConfig(c.String("config"))
config.ConfigPath, _ = filepath.Abs(confVal)
services.ProjectPath, _ = path.Split(config.ConfigPath)
config.ParseGlobalConfig()
services.Init()
for stack := range services.StackRegistry {
fmt.Println(stack)
}
for name := range services.Registry {
fmt.Println(name)
fmt.Println("~" + name)
}
}

// GetEnvForService returns all the environment variables for a given service
// including the ones specified in the global config
func GetEnvForService(c *cli.Context, service *services.Service) []string {
Expand Down
29 changes: 29 additions & 0 deletions config/config.go
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"os"
"os/exec"
"path/filepath"
"reflect"
"strings"

Expand All @@ -12,6 +13,8 @@ import (
"gopkg.in/yaml.v3"
)

const defaultConfigFile = "orchestra.yml"

var orchestra *Config
var ConfigPath string
var globalEnvs []string
Expand Down Expand Up @@ -82,6 +85,32 @@ func GetEnvForCommand(c *cli.Context) []string {
return envs
}

// If a config file is specified, return it, otherwise try to find the nearest
// defaultConfigFile in parents
func FindProjectConfig(config string) string {
if config != "" {
return config
}
dir, err := os.Getwd()
if err != nil {
return defaultConfigFile
}
for {
file := filepath.Join(dir, defaultConfigFile)
_, err := os.Stat(file)
if err == nil {
return file
}

if dir == "/" {
break
}

dir = filepath.Dir(dir)
}
return defaultConfigFile
}

func runCommands(c *cli.Context, cmds []string) error {
for _, command := range cmds {
cmdLine := strings.Split(command, " ")
Expand Down
32 changes: 3 additions & 29 deletions main.go
Expand Up @@ -16,35 +16,11 @@ import (

var app *cli.App

const defaultConfigFile = "orchestra.yml"

func findConfigFile() string {
dir, err := os.Getwd()
if err != nil {
return defaultConfigFile
}
for {
file := filepath.Join(dir, defaultConfigFile)
_, err := os.Stat(file)
if err == nil {
return file
}

if dir == "/" {
break
}

dir = filepath.Dir(dir)
}
return defaultConfigFile
}

func main() {
defer log.Flush()
app = cli.NewApp()
app.Name = "Orchestra"
app.Usage = "Orchestrate Go Services (Tifo)"
app.EnableBashCompletion = true
app.Commands = []*cli.Command{
commands.BuildCommand,
commands.ExportCommand,
Expand All @@ -56,21 +32,19 @@ func main() {
commands.StopCommand,
commands.TestCommand,
}
app.EnableBashCompletion = true
app.Flags = []cli.Flag{
&cli.StringFlag{
Name: "config, c",
Usage: "Specify a different config file to use (default: \"orchestra.yml\"",
Usage: "Specify a different config file to use (default: \"orchestra.yml\")",
EnvVars: []string{"ORCHESTRA_CONFIG"},
},
}
// init checks for an existing orchestra.yml in the current working directory
// and creates a new .orchestra directory (if doesn't exist)
app.Before = func(c *cli.Context) error {
confVal := c.String("config")
if confVal == "" {
confVal = findConfigFile()
}

confVal = config.FindProjectConfig(confVal)
config.ConfigPath, _ = filepath.Abs(confVal)
if _, err := os.Stat(config.ConfigPath); os.IsNotExist(err) {
fmt.Printf("No %s found. Have you specified the right directory?\n", confVal)
Expand Down
12 changes: 11 additions & 1 deletion services/services.go
Expand Up @@ -6,6 +6,7 @@ import (
"io/fs"
"os"
"path"
"path/filepath"
"sort"
"strconv"
"strings"
Expand Down Expand Up @@ -108,7 +109,12 @@ func (s *Service) IsRunning() bool {
}

func discoverStack(stack string) {
fd, _ := os.ReadDir(path.Join(ProjectPath, stack))
fd, err := os.ReadDir(path.Join(ProjectPath, stack))
if err != nil {
_ = log.Errorf("Error registering stack %s")
_ = log.Error(err.Error())
return
}
if stack != "" {
StackRegistry[stack] = make([]*Service, 0)
}
Expand Down Expand Up @@ -187,6 +193,10 @@ func DiscoverServices() {
if stack == "" || stack == "." {
discoverStack("")
} else {
if !filepath.IsLocal(stack) {
_ = log.Errorf("Can't register stack %s, path is not local", stack)
continue
}
discoverStack(stack)
}
}
Expand Down

0 comments on commit a5c2002

Please sign in to comment.