package main import ( "archive/zip" "encoding/json" "fmt" "io" "os" "path/filepath" ) type Command struct { Name string Description string Action CommandAction Arguments []string IsVarargs bool } type CommandAction func([]string, []string) (string, error) var commands = []Command{ { Name: "install", Description: "Install one or more mod archives into modman", Arguments: []string{"game name", "archive path"}, IsVarargs: true, Action: func(requiredArguments []string, providedArguments []string) (string, error) { var argumentCount = len(providedArguments) if argumentCount < len(requiredArguments) { return "", fmt.Errorf("expected game name folowed by at least one archive path") } var archivePaths = providedArguments[1:] if gameError := WithGame(providedArguments[0], func(game *Game) error { for i := range archivePaths { var archivePath = archivePaths[i] var extension = filepath.Ext(archivePath) if len(extension) == 0 { return fmt.Errorf("missing file extension: `%s`", archivePath) } var extractor, extractorExists = extractors[extension[1:]] if !(extractorExists) { return fmt.Errorf("unsupported file format: `%s`", archivePath) } if installError := game.InstallMod( extractor, archivePath); installError != nil { return installError } } return nil }); gameError != nil { return "", gameError } return "mods installed", nil }, }, { Name: "remove", Description: "Remove one or more mods from modman", Arguments: []string{"game name", "mod name"}, IsVarargs: true, Action: func(requiredArguments []string, providedArguments []string) (string, error) { if len(providedArguments) < len(requiredArguments) { return "", fmt.Errorf("expected %s followed by one or more %ss", requiredArguments[0], requiredArguments[1]) } if gameError := WithGame(providedArguments[0], func(game *Game) error { return game.RemoveMods(providedArguments[1:]) }); gameError != nil { return "", gameError } return "removed mods", nil }, }, { Name: "rename", Description: "Rename a mod within modman", Arguments: []string{"game name", "mod name", "new name"}, IsVarargs: false, Action: func(requiredArguments []string, providedArguments []string) (string, error) { if len(providedArguments) != len(requiredArguments) { return "", fmt.Errorf("expected %s followed by %s and %s", requiredArguments[0], requiredArguments[1], requiredArguments[2]) } if gameError := WithGame(providedArguments[0], func(game *Game) error { if removeError := game.RenameMod(providedArguments[1], providedArguments[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(requiredArguments []string, providedArguments []string) (string, error) { if len(providedArguments) != len(requiredArguments) { return "", fmt.Errorf("expected %s followed by %s", requiredArguments[0], requiredArguments[1]) } var modManifest = "" if gameError := WithGame(providedArguments[0], func(game *Game) error { var format = providedArguments[1] var formatter, formatterExists = formatters[format] if !(formatterExists) { return fmt.Errorf("unsupported format: `%s`", format) } var formattedManifest, formatError = formatter(game.ModOrder) 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(requiredArguments []string, arguments []string) (string, error) { if len(arguments) != len(requiredArguments) { return "", fmt.Errorf("expected %s", requiredArguments[0]) } if gameError := WithGame(arguments[0], func(game *Game) error { var deployError = game.Deploy() if deployError != nil { return deployError } return nil }); gameError != nil { return "", gameError } return "deployed", nil }, }, { Name: "disable", Description: "Disable one or more installed mods", Arguments: []string{"game name", "mod name"}, IsVarargs: true, Action: func(requiredArguments []string, arguments []string) (string, error) { if len(arguments) < len(requiredArguments) { return "", fmt.Errorf("expected %s followed by one or more %ss", requiredArguments[0], requiredArguments[1]) } if gameError := WithGame(arguments[0], func(game *Game) error { if enableError := game.SwitchMods(false, arguments[1:]); enableError != nil { return enableError } return nil }); gameError != nil { return "", gameError } return "enabled", nil }, }, { Name: "enable", Description: "Enable one or more installed mods", Arguments: []string{"game name", "mod name"}, IsVarargs: true, Action: func(requiredArguments []string, arguments []string) (string, error) { if len(arguments) < len(requiredArguments) { return "", fmt.Errorf("expected %s followed by one or more %ss", requiredArguments[0], requiredArguments[1]) } if gameError := WithGame(arguments[0], func(game *Game) error { if enableError := game.SwitchMods(true, arguments[1:]); enableError != nil { return enableError } return nil }); gameError != nil { return "", gameError } return "enabled", nil }, }, } 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(command.Arguments, 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) }