Skip to content

Commit

Permalink
feat: add cli support for mobius
Browse files Browse the repository at this point in the history
  • Loading branch information
pyadav committed Jan 20, 2024
1 parent ef8a8e8 commit 93957a2
Show file tree
Hide file tree
Showing 6 changed files with 393 additions and 26 deletions.
22 changes: 22 additions & 0 deletions backend/cmd/cli.go
@@ -0,0 +1,22 @@
package cmd

import (
"github.com/MakeNowJust/heredoc"
"github.com/spf13/cobra"
)

func New() *cobra.Command {
cmd := &cobra.Command{
Use: "mobius <command> <subcommand> [flags]",
Short: "🐦 Mobius is an open-source, lightweight, high-performance ai studio gateway",
Long: heredoc.Doc(`
Mobius is an open-source, lightweight, high-performance ai studio gateway.
`),
SilenceUsage: true,
SilenceErrors: true,
}

cmd.AddCommand(ServerCommand())
SetHelp(cmd)
return cmd
}
262 changes: 262 additions & 0 deletions backend/cmd/help.go
@@ -0,0 +1,262 @@
package cmd

import (
"bytes"
"fmt"
"regexp"
"strings"

"github.com/muesli/termenv"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)

const (
USAGE = "Usage"
CORECMD = "Core commands"
HELPCMD = "Help topics"
OTHERCMD = "Other commands"
FLAGS = "Flags"
IFLAGS = "Inherited flags"
ARGUMENTS = "Arguments"
EXAMPLES = "Examples"
ENVS = "Environment variables"
LEARN = "Learn more"
FEEDBACK = "Feedback"
)

// SetHelp sets a custom help and usage function.
// It allows to group commands in different sections
// based on cobra commands annotations.
func SetHelp(cmd *cobra.Command) {
cmd.PersistentFlags().Bool("help", false, "Show help for command")

cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
rootHelpFunc(cmd, args)
})
cmd.SetUsageFunc(rootUsageFunc)
cmd.SetFlagErrorFunc(rootFlagErrorFunc)
}

func rootUsageFunc(command *cobra.Command) error {
command.Printf("Usage: %s", command.UseLine())

subcommands := command.Commands()
if len(subcommands) > 0 {
command.Print("\n\nAvailable commands:\n")
for _, c := range subcommands {
if c.Hidden {
continue
}
command.Printf(" %s\n", c.Name())
}
return nil
}

flagUsages := command.LocalFlags().FlagUsages()
if flagUsages != "" {
command.Println("\n\nFlags:")
command.Print(indent(dedent(flagUsages), " "))
}
return nil
}

func rootFlagErrorFunc(cmd *cobra.Command, err error) error {
if err == pflag.ErrHelp {
return err
}
return err
}

func rootHelpFunc(command *cobra.Command, args []string) {
if isRootCmd(command.Parent()) && len(args) >= 2 && args[1] != "--help" && args[1] != "-h" {
nestedSuggestFunc(command, args[1])
return
}

coreCommands := []string{}
groupCommands := map[string][]string{}
helpCommands := []string{}
otherCommands := []string{}

for _, c := range command.Commands() {
if c.Short == "" || c.Hidden {
continue
}
s := rpad(c.Name(), c.NamePadding()+3) + c.Short

g, ok := c.Annotations["group"]
if ok && g == "core" {
coreCommands = append(coreCommands, s)
} else if ok && g == "help" {
helpCommands = append(helpCommands, s)
} else if ok && g != "" {
groupCommands[g] = append(groupCommands[g], s)
} else {
otherCommands = append(otherCommands, s)
}
}

// If there are no core and other commands, assume everything is a core command
if len(coreCommands) == 0 && len(groupCommands) == 0 {
coreCommands = otherCommands
otherCommands = []string{}
}

type helpEntry struct {
Title string
Body string
}

text := command.Long

if text == "" {
text = command.Short
}

helpEntries := []helpEntry{}
if text != "" {
helpEntries = append(helpEntries, helpEntry{"", text})
}

helpEntries = append(helpEntries, helpEntry{USAGE, command.UseLine()})

if len(coreCommands) > 0 {
helpEntries = append(helpEntries, helpEntry{CORECMD, strings.Join(coreCommands, "\n")})
}

for name, cmds := range groupCommands {
if len(cmds) > 0 {
helpEntries = append(helpEntries, helpEntry{fmt.Sprint(toTitle(name) + " commands"), strings.Join(cmds, "\n")})
}
}

if len(otherCommands) > 0 {
helpEntries = append(helpEntries, helpEntry{OTHERCMD, strings.Join(otherCommands, "\n")})
}

if len(helpCommands) > 0 {
helpEntries = append(helpEntries, helpEntry{HELPCMD, strings.Join(helpCommands, "\n")})
}

flagUsages := command.LocalFlags().FlagUsages()
if flagUsages != "" {
helpEntries = append(helpEntries, helpEntry{FLAGS, dedent(flagUsages)})
}

inheritedFlagUsages := command.InheritedFlags().FlagUsages()
if inheritedFlagUsages != "" {
helpEntries = append(helpEntries, helpEntry{IFLAGS, dedent(inheritedFlagUsages)})
}

if _, ok := command.Annotations["help:arguments"]; ok {
helpEntries = append(helpEntries, helpEntry{ARGUMENTS, command.Annotations["help:arguments"]})
}

if command.Example != "" {
helpEntries = append(helpEntries, helpEntry{EXAMPLES, command.Example})
}

if _, ok := command.Annotations["help:learn"]; ok {
helpEntries = append(helpEntries, helpEntry{LEARN, command.Annotations["help:learn"]})
}

if _, ok := command.Annotations["help:feedback"]; ok {
helpEntries = append(helpEntries, helpEntry{FEEDBACK, command.Annotations["help:feedback"]})
}

out := command.OutOrStdout()
for _, e := range helpEntries {
if e.Title != "" {
// If there is a title, add indentation to each line in the body
fmt.Fprintln(out, bold(e.Title))
fmt.Fprintln(out, indent(strings.Trim(e.Body, "\r\n"), " "))
} else {
// If there is no title print the body as is
fmt.Println(e.Body)
}
fmt.Fprintln(out)
}
}

// Display helpful error message in case subcommand name was mistyped.
func nestedSuggestFunc(command *cobra.Command, arg string) {
command.Printf("unknown command %q for %q\n", arg, command.CommandPath())

var candidates []string
if arg == "help" {
candidates = []string{"--help"}
} else {
if command.SuggestionsMinimumDistance <= 0 {
command.SuggestionsMinimumDistance = 2
}
candidates = command.SuggestionsFor(arg)
}

if len(candidates) > 0 {
command.Print("\nDid you mean this?\n")
for _, c := range candidates {
command.Printf("\t%s\n", c)
}
}

command.Print("\n")
_ = rootUsageFunc(command)
}

func isRootCmd(command *cobra.Command) bool {
return command != nil && !command.HasParent()
}

var lineRE = regexp.MustCompile(`(?m)^`)

func indent(s, indent string) string {
if len(strings.TrimSpace(s)) == 0 {
return s
}
return lineRE.ReplaceAllLiteralString(s, indent)
}

func bold(text string) termenv.Style {
h := termenv.String(text).Bold()
return h
}

func toTitle(text string) string {
heading := cases.Title(language.Und).String(text)
return heading
}

func dedent(s string) string {
lines := strings.Split(s, "\n")
minIndent := -1

for _, l := range lines {
if len(l) == 0 {
continue
}

indent := len(l) - len(strings.TrimLeft(l, " "))
if minIndent == -1 || indent < minIndent {
minIndent = indent
}
}

if minIndent <= 0 {
return s
}

var buf bytes.Buffer
for _, l := range lines {
fmt.Fprintln(&buf, strings.TrimPrefix(l, strings.Repeat(" ", minIndent)))
}
return strings.TrimSuffix(buf.String(), "\n")
}

// rpad adds padding to the right of a string.
func rpad(s string, padding int) string {
template := fmt.Sprintf("%%-%ds ", padding)
return fmt.Sprintf(template, s)
}
68 changes: 68 additions & 0 deletions backend/cmd/server.go
@@ -0,0 +1,68 @@
package cmd

import (
"log"
"log/slog"
"os"
"os/signal"
"syscall"

"github.com/MakeNowJust/heredoc"
"github.com/missingstudio/studio/backend/internal/connectrpc"
"github.com/missingstudio/studio/backend/internal/httpserver"

"github.com/spf13/cobra"
)

func ServerCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "server",
Aliases: []string{"s"},
Short: "Server management",
Long: "Server management commands.",
Example: heredoc.Doc(`
$ mobius server start
`),
}

cmd.AddCommand(serverStartCommand())
return cmd
}

func serverStartCommand() *cobra.Command {
var configFile string

c := &cobra.Command{
Use: "start",
Short: "Start server and proxy default on port 8080",
Example: "frontier server start",
RunE: func(cmd *cobra.Command, args []string) error {
connectMux, err := connectrpc.NewConnectMux(connectrpc.Deps{})
if err != nil {
log.Fatal("connect rpc mux not created", err)
return err
}

connectsrv := httpserver.New(connectMux, httpserver.WithAddr("127.0.0.1", "8080"))

interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM)
defer signal.Stop(interrupt)

select {
case s := <-interrupt:
slog.Info("received interrupt signal", "signal", s.String())
case err := <-connectsrv.Notify():
slog.Error("got error from connect server", "error", err.Error())
}

if err := connectsrv.Shutdown(); err != nil {
slog.Error("go error on connect server shutdown", "error", err.Error())
}
return nil
},
}

c.Flags().StringVarP(&configFile, "config", "c", "", "config file path")
return c
}
13 changes: 12 additions & 1 deletion backend/go.mod
Expand Up @@ -9,25 +9,36 @@ require (
connectrpc.com/grpchealth v1.3.0
connectrpc.com/grpcreflect v1.2.0
connectrpc.com/validate v0.1.0
github.com/MakeNowJust/heredoc v1.0.0
github.com/missingstudio/studio/protos v0.0.0-00010101000000-000000000000
github.com/muesli/termenv v0.15.2
github.com/rs/cors v1.10.1
github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.4
github.com/zeebo/assert v1.3.1
golang.org/x/net v0.20.0
golang.org/x/text v0.14.0
)

require (
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-20230824200731-b9b8148056b9.1 // indirect
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/bufbuild/protovalidate-go v0.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/cel-go v0.17.4 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/sys v0.16.0 // indirect
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect
Expand Down

0 comments on commit 93957a2

Please sign in to comment.