z_mirror/fmm

git clone https://git.raiguard.me/z_mirror/fmm

Caleb HeuerDon't print to console directly from ModPortale75cd02

trunk
6.5 KiB249 linesraw
1package cli
2
3import (
4	"cmp"
5	"errors"
6	"fmt"
7	"io"
8	"os"
9	"path/filepath"
10	"slices"
11	"strings"
12
13	fmm "codeberg.org/raiguard/fmm/lib"
14	"github.com/fred1268/go-clap/clap"
15)
16
17const usageStr string = `usage: fmm [flags...] <command> [args...]
18flags:
19  --game-path         Path of the game directory. Defaults to current directory.
20  --mods-path         Path of the mods directory. Defaults to './mods'.
21  --help              Show usage information.
22commands:
23  add     [args...]   Download and enable the given mods and their dependencies.
24  disable [args...]   Disable the given mods, or all mods if none are given.
25  enable  [args...]   Enable the given mods and their dependencies.
26  help                Show usage information.
27  list    [files...]  List all mods in the mods directory, or in the given save files.
28  sync    [args...]   Disable all mods, then download and enable the given mods and their dependencies.
29                      If a save file is provided, merge startup mod settings with the settings contained in that save.
30  update  [args...]   Update the given mods, or all mods if none are given.
31  upload  [files...]  Upload the given mod zip files to the mod portal.
32environment:
33  FACTORIO_PATH       Default Factorio game directory to use if the current or previous directories are invalid.
34  FACTORIO_MODS_PATH  Default Factorio mods directory to use. Defaults to FACTORIO_PATH/mods.
35  FACTORIO_USERNAME   Factorio username to use for mod portal operations.
36  FACTORIO_TOKEN      Factorio token to use for mod portal operations.
37  FACTORIO_API_KEY    Factorio API key to use for mod portal operations.`
38
39type Args struct {
40	GamePath    string   `clap:"--game-path,-g"`
41	ModsPath    string   `clap:"--mods-path,-d"`
42	Help        bool     `clap:"--help,-h"`
43	CommandArgs []string `clap:"trailing"`
44}
45
46func Run(args []string) {
47	var parsedArgs Args
48	// TODO: Utilize this result for better errors.
49	_, err := clap.Parse(args, &parsedArgs)
50	if err != nil {
51		errorln(err)
52		os.Exit(1)
53	}
54
55	if parsedArgs.Help {
56		printUsage()
57	}
58
59	if len(parsedArgs.GamePath) == 0 {
60		parsedArgs.GamePath = "."
61	}
62	if len(parsedArgs.ModsPath) == 0 {
63		parsedArgs.ModsPath = "./mods"
64	}
65
66	if len(parsedArgs.CommandArgs) == 0 {
67		printUsage("no operation was specified")
68	}
69
70	var task func(*fmm.Manager, []string)
71	switch parsedArgs.CommandArgs[0] {
72	case "add", "a":
73		task = add
74	case "disable", "d":
75		task = disable
76	case "enable", "e":
77		task = enable
78	case "help", "h", "-h", "--help", "-help":
79		printUsage()
80	case "list", "ls":
81		task = list
82	case "sync", "s":
83		task = sync
84	case "update", "u":
85		task = update
86	case "upload", "ul":
87		task = upload
88	default:
89		printUsage("unrecognized operation", parsedArgs.CommandArgs[0])
90	}
91	parsedArgs.CommandArgs = parsedArgs.CommandArgs[1:]
92
93	manager, err := fmm.NewManager(parsedArgs.GamePath, parsedArgs.ModsPath)
94	if err != nil {
95		if !errors.Is(err, fmm.ErrInvalidGameDirectory) {
96			abort(err)
97		}
98		gamePath := os.Getenv("FACTORIO_PATH")
99		modsPath := os.Getenv("FACTORIO_MODS_PATH")
100		if modsPath == "" {
101			modsPath = filepath.Join(gamePath, "mods")
102		}
103		manager, err = fmm.NewManager(gamePath, modsPath)
104		if err != nil {
105			abort(err)
106		}
107	}
108
109	manager.Portal.SetPrintlnCallback(func(msg ...any) {
110		fmt.Println(msg...)
111	})
112
113	if !manager.HasPlayerData() {
114		manager.SetPlayerData(fmm.PlayerData{
115			Token:    os.Getenv("FACTORIO_TOKEN"),
116			Username: os.Getenv("FACTORIO_USERNAME"),
117		})
118	}
119
120	manager.SetApiKey(os.Getenv("FACTORIO_API_KEY"))
121
122	stdinStat, _ := os.Stdin.Stat()
123	if stdinStat.Mode()&os.ModeNamedPipe > 0 {
124		bytes, err := io.ReadAll(os.Stdin)
125		if err == nil {
126			parsedArgs.CommandArgs = append(parsedArgs.CommandArgs, strings.Split(strings.TrimSpace(string(bytes)), "\n")...)
127		}
128	}
129
130	task(manager, parsedArgs.CommandArgs)
131
132	if err := manager.Save(); err != nil {
133		errorln(errors.Join(errors.New("unable to save modifications"), err))
134	}
135}
136
137func add(manager *fmm.Manager, args []string) {
138	mods, _ := getMods(args)
139	for _, mod := range manager.ExpandDependencies(mods, true) {
140		ver, err := manager.Add(mod)
141		if err != nil {
142			errorf("failed to add %s\n", mod.ToString())
143			errorln(err)
144		} else if ver != nil {
145			fmt.Println("enabled", mod.Name, ver.ToString(false))
146		}
147	}
148}
149
150func disable(manager *fmm.Manager, args []string) {
151	if len(args) == 0 {
152		manager.DisableAll()
153		fmt.Println("disabled all mods")
154		return
155	}
156
157	mods, _ := getMods(args)
158	for _, mod := range mods {
159		if err := manager.Disable(mod.Name); err != nil {
160			errorf("failed to disable %s\n", mod.ToString())
161			errorln(err)
162		} else {
163			fmt.Println("disabled", mod.Name)
164		}
165	}
166}
167
168func enable(manager *fmm.Manager, args []string) {
169	mods, _ := getMods(args)
170	for _, mod := range manager.ExpandDependencies(mods, false) {
171		ver, err := manager.Enable(mod)
172		if err != nil {
173			errorf("failed to enable %s\n", mod.ToString())
174			errorln(err)
175		} else if ver != nil {
176			fmt.Println("enabled", mod.Name, ver.ToString(false))
177		}
178	}
179}
180
181func list(manager *fmm.Manager, args []string) {
182	mods := []fmm.ModIdent{}
183	if len(args) == 0 {
184		mods = manager.GetMods()
185	} else {
186		for _, filepath := range args {
187			fileInfo, err := fmm.ParseSaveFile(filepath)
188			if err != nil {
189				fmt.Println(err)
190			}
191			mods = append(mods, fileInfo.ActiveMods...)
192		}
193	}
194	slices.SortFunc(mods, func(a fmm.ModIdent, b fmm.ModIdent) int {
195		if a.Name != b.Name {
196			return cmp.Compare(a.Name, b.Name)
197		}
198		switch a.Version.Cmp(b.Version) {
199		case fmm.VersionLt:
200			return -1
201		case fmm.VersionGt:
202			return 1
203		case fmm.VersionEq:
204			return 0
205		// Should be unreachable
206		default:
207			return 0
208		}
209	})
210	for _, mod := range mods {
211		fmt.Println(mod.ToString())
212	}
213}
214
215func sync(manager *fmm.Manager, args []string) {
216	manager.DisableAll()
217	fmt.Println("disabled all mods")
218	mods, settings := getMods(args)
219	for _, mod := range manager.ExpandDependencies(mods, true) {
220		ver, err := manager.Add(mod)
221		if err != nil {
222			errorf("failed to add %s\n", mod.ToString())
223			errorln(err)
224		} else if ver != nil {
225			fmt.Println("enabled", mod.Name, ver.ToString(false))
226		}
227	}
228	if settings != nil {
229		err := manager.MergeStartupModSettings(settings)
230		if err != nil {
231			fmt.Println(errors.Join(errors.New("failed to sync startup mod settings"), err))
232		} else {
233			fmt.Println("synced startup mod settings")
234		}
235	}
236}
237
238func update(manager *fmm.Manager, args []string) {
239	mods, _ := getMods(args)
240	manager.CheckDownloadUpdates(mods)
241}
242
243func upload(manager *fmm.Manager, files []string) {
244	for _, file := range files {
245		if err := manager.Portal.UploadMod(file); err != nil {
246			fmt.Println(err)
247		}
248	}
249}