package main import ( "archive/zip" "encoding/json" "fmt" "io" "os" "path/filepath" ) type Command struct { Name string Description string Action func([]string) (string, error) Arguments []string IsVarargs bool } var commands = []Command{ { Name: "install", Description: "Install one or more mod archives into modman", Arguments: []string{"game name", "archive path"}, IsVarargs: true, Action: func(arguments []string) (string, error) { var argumentCount = len(arguments) if argumentCount == 0 { return "", fmt.Errorf("expected game name") } var archivePaths = arguments[1:] var processed = 0 if gameError := WithGame(arguments[0], func(game *Game) error { for i := range archivePaths { var archivePath = archivePaths[i] var extension = filepath.Ext(archivePath) if len(extension) == 0 { continue } else { extension = extension[1:] } var extractor, extractorExists = extractors[extension] if !(extractorExists) { continue } if game.InstallMod(extractor, archivePath) != nil { continue } processed += 1 } return nil }); gameError != nil { return "", gameError } return fmt.Sprint(processed, " of ", len(archivePaths), " installed"), nil }, }, { Name: "remove", Description: "Remove one or more mods from modman", Arguments: []string{"game name", "mod name"}, IsVarargs: true, Action: func(arguments []string) (string, error) { var argumentCount = len(arguments) if argumentCount == 0 { return "", fmt.Errorf("expected game name") } var modsToRemove = arguments[1:] var removedMods = []string{} if gameError := WithGame(arguments[0], func(game *Game) error { var removed, removeError = game.RemoveMods(modsToRemove) if removeError != nil { return removeError } removedMods = removed return nil }); gameError != nil { return "", gameError } return fmt.Sprint(len(removedMods), " of ", len(removedMods), " removed"), nil }, }, { Name: "rename", Description: "Rename a mod within modman", Arguments: []string{"game name", "mod name", "new name"}, IsVarargs: false, Action: func(arguments []string) (string, error) { if len(arguments) != 3 { return "", fmt.Errorf("expected game name followed by mod name and new name") } if gameError := WithGame(arguments[0], func(game *Game) error { if removeError := game.RenameMod(arguments[1], arguments[2]); removeError != nil { return removeError } return nil }); gameError != nil { return "", gameError } return "", nil }, }, { Name: "manifest", Description: "Retrieve a manifest of all installed mods", Arguments: []string{"game name", "format"}, IsVarargs: false, Action: func(arguments []string) (string, error) { if len(arguments) != 2 { return "", fmt.Errorf("expected game name followed by format") } var modManifest = "" if gameError := WithGame(arguments[0], func(game *Game) error { var format = arguments[1] var formatter, formatterExists = formatters[format] if !(formatterExists) { return fmt.Errorf("unsupported format: `%s`", format) } var formattedManifest, formatError = formatter(game.Mods) if formatError != nil { return formatError } // TODO: Reconsider if always casting formatted data to string for output is a good // idea. modManifest = string(formattedManifest) return nil }); gameError != nil { return "", gameError } return modManifest, nil }, }, { Name: "deploy", Description: "Deploy all installed and enabled mods", Arguments: []string{"game name"}, IsVarargs: false, Action: func(arguments []string) (string, error) { // TODO: Implement. return "", fmt.Errorf("not implemented") }, }, } var extractors = map[string]Extractor{ "zip": func(archivePath string, destinationPath string) error { var zipReader, openReaderError = zip.OpenReader(archivePath) if openReaderError != nil { return openReaderError } defer func() { if closeError := zipReader.Close(); closeError != nil { panic(closeError.Error()) } }() if mkdirError := os.MkdirAll(destinationPath, 0755); mkdirError != nil { return mkdirError } for i := range zipReader.File { var file = zipReader.File[i] var fileReader, fileOpenError = file.Open() if fileOpenError != nil { return fileOpenError } defer func() { if closeError := fileReader.Close(); closeError != nil { panic(closeError.Error()) } }() var path = filepath.Join(destinationPath, file.Name) if file.FileInfo().IsDir() { if mkdirError := os.MkdirAll(path, file.Mode()); mkdirError != nil { return mkdirError } } else { if mkdirError := os.MkdirAll(filepath.Dir(path), file.Mode()); mkdirError != nil { return mkdirError } var extractedFile, fileOpenError = os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode()) if fileOpenError != nil { return fileOpenError } defer func() { if fileOpenError := extractedFile.Close(); fileOpenError != nil { panic(fileOpenError) } }() var _, copyError = io.Copy(extractedFile, fileReader) if copyError != nil { return copyError } } } return nil }, } var formatters = map[string]func(any) ([]byte, error){ "json": func(data any) ([]byte, error) { var marshalledJson, marshalError = json.Marshal(data) if marshalError != nil { return nil, marshalError } return marshalledJson, nil }, } func main() { var argCount = len(os.Args) if argCount == 1 { fmt.Println("Modman v0.1") fmt.Println("Enter one of the following commands following modman:") for i := range commands { var command = commands[i] fmt.Print("\t", command.Name) for j := range command.Arguments { fmt.Print(" [", command.Arguments[j], "]") } if command.IsVarargs { fmt.Println("...") } else { fmt.Println() } } return } var commandName = os.Args[1] for i := range commands { var command = commands[i] if command.Name == commandName { var response, actionError = command.Action(os.Args[2:]) if actionError != nil { fmt.Fprintln(os.Stderr, actionError.Error()) os.Exit(1) } if len(response) != 0 { fmt.Println(response) } return } } fmt.Fprintf(os.Stderr, "unknown command: `%s`\n", commandName) }