package main import ( "fmt" "io/ioutil" "os" "os/exec" "os/user" "path/filepath" "strings" "github.com/spf13/cobra" "gopkg.in/yaml.v3" ) const binroot = "/town/launcher" var rootCmd = &cobra.Command{ Use: "town", Short: "Run commands unique to tilde.town", } var contribCmd = &cobra.Command{ Use: "contrib", Short: "community-maintained town commands", } var adminCmd = &cobra.Command{ Use: "admin", Short: "commands used for administering the town", } func isAdmin() (bool, error) { u, err := user.Current() if err != nil { return false, fmt.Errorf("failed to get information about current user: %w", err) } adminGroup, err := user.LookupGroup("admin") if err != nil { return false, fmt.Errorf("failed to get admin group info: %w", err) } groupIds, err := u.GroupIds() if err != nil { return false, fmt.Errorf("failed to get groups info: %w", err) } for _, groupId := range groupIds { if groupId == adminGroup.Gid { return true, nil } } return false, nil } func init() { rootCmd.AddCommand(contribCmd) parseCommands(rootCmd, "core") parseCommands(contribCmd, "contrib") admin, err := isAdmin() if err != nil { panic(fmt.Sprintf("failed to check admin status: %s", err)) } if admin { rootCmd.AddCommand(adminCmd) parseCommands(adminCmd, "admin") } // I feel like the example/documentation yaml can be frontmatter for non-binary files. to start // i'll just do the accompanying yaml file. } func parseCommands(targetCmd *cobra.Command, path string) { binPath := filepath.Join(binroot, path) files, err := ioutil.ReadDir(binPath) if err != nil { panic(fmt.Sprintf("failed to list directory %s: %s", binPath, err)) } for _, file := range files { if strings.HasSuffix(file.Name(), "yml") { parseCommand(targetCmd, filepath.Join(binPath, file.Name())) } } } type commandDoc struct { ShortDesc string `yaml:"shortDesc"` LongDesc string `yaml:"longDesc"` Examples string } func parseCommand(targetCmd *cobra.Command, yamlPath string) { executablePath := strings.TrimSuffix(yamlPath, ".yml") // TODO handle when files lack executable bit _, err := os.Stat(executablePath) if err != nil { fmt.Fprintf(os.Stderr, "could not find matching executable for %s; skipping...", yamlPath) return } yamlBytes, err := ioutil.ReadFile(yamlPath) if err != nil { fmt.Fprintf(os.Stderr, "could not read %s; skipping...", yamlPath) return } doc := commandDoc{} err = yaml.Unmarshal(yamlBytes, &doc) if err != nil { fmt.Fprintf(os.Stderr, "could not parse %s; skipping...", yamlPath) return } parsedCmd := &cobra.Command{ Use: filepath.Base(executablePath), RunE: execWrapper(executablePath), } if doc.ShortDesc != "" { parsedCmd.Short = doc.ShortDesc } if doc.LongDesc != "" { parsedCmd.Long = doc.LongDesc } if doc.Examples != "" { parsedCmd.Example = doc.Examples } targetCmd.AddCommand(parsedCmd) } func execWrapper(executablePath string) func(*cobra.Command, []string) error { return func(cmd *cobra.Command, args []string) error { execCmd := exec.Command(executablePath, args...) execCmd.Stderr = os.Stderr execCmd.Stdout = os.Stdout execCmd.Stdin = os.Stdin return execCmd.Run() } } func main() { rootCmd.Execute() }