diff --git a/cmd/serve.go b/cmd/serve.go index 646a7ed..f5d1910 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -7,6 +7,7 @@ import ( "github.com/criteo/command-launcher/internal/backend" "github.com/criteo/command-launcher/internal/config" "github.com/criteo/command-launcher/internal/context" + "github.com/criteo/command-launcher/internal/server" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -30,7 +31,7 @@ func AddServeCmd(rootCmd *cobra.Command, appCtx context.LauncherContext, back ba port = serveFlags.port } fmt.Printf("Starting server on port %d", port) - if err := back.Serve(port); err != nil { + if err := server.Serve(&back, port); err != nil { fmt.Printf("Failed to start server: %s\n", err) os.Exit(1) } diff --git a/internal/backend/api.go b/internal/backend/api.go index 4bc0886..9511509 100644 --- a/internal/backend/api.go +++ b/internal/backend/api.go @@ -62,9 +62,6 @@ type Backend interface { // Return dropin local repsository DropinRepository() repository.PackageRepository - // start the server as daemon - Serve(port int) error - // Print out the command resolution details Debug() } diff --git a/internal/backend/server.go b/internal/backend/server.go deleted file mode 100644 index f3a2881..0000000 --- a/internal/backend/server.go +++ /dev/null @@ -1,25 +0,0 @@ -package backend - -import ( - "fmt" - "net/http" -) - -func StartHttpServer(port int) error { - http.HandleFunc("/", HelloHandler) - http.HandleFunc("/health", HealthHandler) - return http.ListenAndServe(fmt.Sprintf(":%d", port), nil) -} - -func HealthHandler(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("OK")) -} - -func HelloHandler(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("Hello, world!")) -} - -// Implement the serve funcation in default backend -func (backend *DefaultBackend) Serve(port int) error { - return StartHttpServer(port) -} diff --git a/internal/server/server.go b/internal/server/server.go new file mode 100644 index 0000000..63b8126 --- /dev/null +++ b/internal/server/server.go @@ -0,0 +1,168 @@ +package server + +import ( + "bytes" + "embed" + "fmt" + "html/template" + "net/http" + "sort" + "strings" + + . "github.com/criteo/command-launcher/internal/backend" + "github.com/criteo/command-launcher/internal/command" +) + +type Server struct { + backend *Backend +} + +//go:embed static +var fs embed.FS + +//go:embed templates +var templates embed.FS + +func (server *Server) IndexHandler(w http.ResponseWriter, r *http.Request) { + index, _ := fs.ReadFile("static/index.html") + w.Write(index) +} + +type Command struct { + FullName string + Group string + Name string + Package string + Registry string + Short string + Long string + Examples []command.ExampleEntry + Flags []command.Flag + SubCmds []*Command +} + +type CommandIndex struct { + Commands []*Command +} + +func (server *Server) CommandIndexHandler(w http.ResponseWriter, r *http.Request) { + repos := (*(server.backend)).AllRepositories() + cmdMap := map[string]*Command{} + + for _, repo := range repos { + pkgs := repo.InstalledPackages() + for _, pkg := range pkgs { + cmds := pkg.Commands() + for _, cmd := range cmds { + if cmd.Name() == "__setup__" { + continue + } + if cmd.Group() == "" { // this is top level command + // add group command + if groupCmd, ok := cmdMap[cmd.FullName()]; !ok { + groupCmd = &Command{ + FullName: cmd.FullName(), + Group: cmd.Group(), + Name: cmd.Name(), + Package: pkg.Name(), + Registry: repo.Name(), + SubCmds: []*Command{}, + } + cmdMap[cmd.FullName()] = groupCmd + } + } else { // this is a sub command + // get the group command full name + groupCmdFullName := fmt.Sprintf("%s@@%s@%s", cmd.Group(), pkg.Name(), repo.Name()) + if groupCmd, ok := cmdMap[groupCmdFullName]; !ok { + groupCmd = &Command{ + FullName: groupCmdFullName, + Group: "", + Name: cmd.Group(), + Package: pkg.Name(), + Registry: repo.Name(), + SubCmds: []*Command{ + &Command{ + FullName: cmd.FullName(), + Group: cmd.Group(), + Name: cmd.Name(), + Package: pkg.Name(), + Registry: repo.Name(), + SubCmds: []*Command{}, + }, + }, + } + cmdMap[groupCmdFullName] = groupCmd + } else { + groupCmd.SubCmds = append(groupCmd.SubCmds, &Command{ + FullName: cmd.FullName(), + Group: cmd.Group(), + Name: cmd.Name(), + Package: pkg.Name(), + Registry: repo.Name(), + SubCmds: []*Command{}, + }) + } + + } + } + } + } + + // load the template + tmpl_text, _ := templates.ReadFile("templates/cmd_index.html") + tmpl, _ := template.New("command_index").Parse(string(tmpl_text)) + + groups := make([]*Command, 0, len(cmdMap)) + for _, cmd := range cmdMap { + groups = append(groups, cmd) + } + + sort.Slice(groups, func(i, j int) bool { + // return fmt.Sprintf("%s@%s", groups[i].Package, groups[i].Registry) < fmt.Sprintf("%s@%s", groups[j].Package, groups[j].Registry) + return groups[i].FullName < groups[j].FullName + }) + + var tpl bytes.Buffer + tmpl.Execute(&tpl, CommandIndex{Commands: groups}) + + html_wrapper, _ := templates.ReadFile("templates/html_wrapper.html") + content := strings.ReplaceAll(string(html_wrapper), "#INPUT#", tpl.String()) + w.Write([]byte(content)) +} + +func (server *Server) CommandHandler(w http.ResponseWriter, r *http.Request) { + fullName := strings.TrimPrefix(r.URL.Path, "/command/") + fmt.Println(r.URL.Path, fullName) + cmd, _ := (*(server.backend)).FindCommandByFullName(fullName) + + tmpl_text, _ := templates.ReadFile("templates/command.html") + tmpl, _ := template.New("command").Parse(string(tmpl_text)) + command := Command{ + FullName: cmd.FullName(), + Group: cmd.Group(), + Name: cmd.Name(), + Package: cmd.PackageName(), + Registry: cmd.RepositoryID(), + Short: cmd.ShortDescription(), + Long: cmd.LongDescription(), + Examples: cmd.Examples(), + Flags: cmd.Flags(), + } + + tmpl.Execute(w, command) +} + +func (server *Server) HealthHandler(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("OK")) +} + +// Implement the serve funcation in default backend +func Serve(backend *Backend, port int) error { + server := Server{backend: backend} + + http.HandleFunc("/", server.CommandIndexHandler) + http.HandleFunc("/test", server.CommandIndexHandler) + http.HandleFunc("/command/", server.CommandHandler) + http.HandleFunc("/health", server.HealthHandler) + return http.ListenAndServe(fmt.Sprintf(":%d", port), nil) +} diff --git a/internal/server/static/index.html b/internal/server/static/index.html new file mode 100644 index 0000000..828579c --- /dev/null +++ b/internal/server/static/index.html @@ -0,0 +1,112 @@ + + + + Command Launcher + + + +
+ +
+

Index

+
+ moab: + init + build +
+
+ doc: + html + confluence +
+
+ +
+ +
+

moab init 🔗

+

full name:init@moab@moab@dropinpackage:moabregistry:dropin

+

Initiate a moab workspace

+
+ Examples +
+

Initiate a java moab workspace and checkout one repository

+
moab init java software-factory/example-projects
+
+
+
+ Try it out +
+ + + + + + + + + + + + + enable +
+ +
+
Output
+

+  npm install -g @command-launcher/cli
+  another line
+  $ls -la
+  .
+  ..
+  .git
+        
+
+
+ +
+

moab build

+

package:moabregistry:dropin

+

Build all the projects in current folder

+
+ Examples +
+

Initiate a java moab workspace and checkout one repository

+
moab init java software-factory/example-projects
+
+
+
+ Try it out +
+ + + + + exclude-ide +
+ +
+
Output
+

+  npm install -g @command-launcher/cli
+  another line
+  $ls -la
+  .
+  ..
+  .git
+        
+
+
+
+
+ + + diff --git a/internal/server/templates/cmd_index.html b/internal/server/templates/cmd_index.html new file mode 100644 index 0000000..081602f --- /dev/null +++ b/internal/server/templates/cmd_index.html @@ -0,0 +1,13 @@ +
+

Index

+ {{range .Commands}} +
+ Registry: {{.Registry}}Package: {{.Package}}

+ {{.Group}} {{.Name}}
+ {{range .SubCmds}} + {{.Group}} {{.Name}}
+ {{end}} +

+
+ {{end}} +
diff --git a/internal/server/templates/command.html b/internal/server/templates/command.html new file mode 100644 index 0000000..dbb6ebd --- /dev/null +++ b/internal/server/templates/command.html @@ -0,0 +1,76 @@ + + + + {{.Group}} {{.Name}} + + + +
+ + +
+ +

{{.Group}} {{.Name}}

+

full name:{{.FullName}}package:{{.Package}}registry:{{.Registry}}

+

{{.Short}}

+
+ Examples + {{range .Examples}} +
+

{{.Scenario}}

+
{{.Command}}
+
+ {{end}} +
+
+ Try it out +
+ + + + + + + + + + {{range .Flags}} + + {{if eq .FlagType "bool"}} + + + enable + + {{else}} + + + + {{end}} + + {{end}} + +
+ +
+
Output
+

+  npm install -g @command-launcher/cli
+  another line
+  $ls -la
+  .
+  ..
+  .git
+        
+
+
+
+ + + diff --git a/internal/server/templates/html_wrapper.html b/internal/server/templates/html_wrapper.html new file mode 100644 index 0000000..3e34c96 --- /dev/null +++ b/internal/server/templates/html_wrapper.html @@ -0,0 +1,17 @@ + + + + Test Page + + + + +
+ +#INPUT# + +
+ + + diff --git a/internal/server/templates/index.html b/internal/server/templates/index.html new file mode 100644 index 0000000..21a85e5 --- /dev/null +++ b/internal/server/templates/index.html @@ -0,0 +1,23 @@ + + + + Command Launcher + + + +
+ + {{.Index}} + {{range .Commands}} + {{.}} + {{end}} +
+ +