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