diff --git a/cmd/root.go b/cmd/root.go index 91c2c0e2..772bef7f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -30,6 +30,7 @@ import ( "perfspect/cmd/report" "perfspect/cmd/telemetry" "perfspect/internal/app" + "perfspect/internal/script" "perfspect/internal/util" "github.com/pkg/errors" @@ -114,6 +115,7 @@ Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} rootCmd.AddCommand(config.Cmd) rootCmd.AddGroup([]*cobra.Group{{ID: "other", Title: "Other Commands:"}}...) rootCmd.AddCommand(updateCmd) + rootCmd.AddCommand(extractCmd) // Global (persistent) flags rootCmd.PersistentFlags().BoolVar(&flagDebug, app.FlagDebugName, false, "enable debug logging and retain temporary directories") rootCmd.PersistentFlags().BoolVar(&flagSyslog, app.FlagSyslogName, false, "write logs to syslog instead of a file") @@ -489,6 +491,29 @@ func getLatestManifest() (manifest, error) { return latestManifest, nil } +// define the extract command +const ( + extractCommandName = "extract" +) + +var extractCmd = &cobra.Command{ + GroupID: "other", + Use: extractCommandName, + Short: "Extract the embedded resources (for developers)", + RunE: func(cmd *cobra.Command, args []string) error { + appContext := cmd.Parent().Context().Value(app.Context{}).(app.Context) + // extract the internal/script module's embedded resources + err := util.ExtractAllResources(script.Resources, appContext.OutputDir) + if err != nil { + slog.Error("Failed to extract script resources", slog.String("error", err.Error())) + fmt.Printf("Error: failed to extract script resources: %v\n", err) + return err + } + fmt.Printf("Extracted script resources to %s\n", appContext.OutputDir) + return nil + }, +} + // SyslogHandler is a slog.Handler that logs to syslog. type SyslogHandler struct { writer *syslog.Writer diff --git a/internal/util/util.go b/internal/util/util.go index 3c21ab51..ba9dfdb2 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -208,6 +208,31 @@ func GeoMean(vals []float64) (val float64) { return } +// ExtractAllResources recurcively extracts all resources as files into the specified directory. +func ExtractAllResources(resources embed.FS, dir string) error { + // walk the embedded filesystem starting at "resources" + return fs.WalkDir(resources, "resources", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + relPath, err := filepath.Rel("resources", path) + if err != nil { + return err + } + relPath = filepath.Join(dir, relPath) + if d.IsDir() { + if relPath == "." { + return nil + } + // create directory + return os.MkdirAll(relPath, 0700) + } + // extract file + _, err = ExtractResource(resources, path, filepath.Dir(relPath)) + return err + }) +} + // ExtractResource extracts a resource from the given embed.FS and saves it to the specified temporary directory. // It returns the path to the saved resource file and any error encountered during the process. func ExtractResource(resources embed.FS, resourcePath string, tempDir string) (string, error) {