z_mirror/fmm
git clone https://git.raiguard.me/z_mirror/fmm
a557023
refactor
1use anyhow:: anyhow; 2use anyhow:: Result ; 3use directories:: BaseDirs ; 4use serde:: Deserialize ; 5use serde_with::{ serde_as, DisplayFromStr }; 6use std:: collections:: HashMap ; 7use std:: fs; 8use std:: path:: PathBuf ; 9use structopt:: StructOpt ; 10use thiserror:: Error ; 11 12use crate :: dependency:: ModDependencyType ; 13use crate :: directory:: Directory ; 14use crate :: sync; 15use crate :: types:: * ; 16 17# [ derive ( Debug , Default )] 18pub struct Actions { 19pub remove : Vec < ModIdent >, 20pub disable : ModifyType , 21pub download : Vec < ModIdent >, 22pub enable : ModifyType , 23} 24 25# [ derive ( Debug )] 26pub enum ModifyType { 27All , 28Some ( Vec < ModIdent >), 29None , 30} 31 32impl Default for ModifyType { 33fn default () ->Self { 34Self :: None 35} 36} 37 38# [ derive ( Debug )] 39pub struct Config { 40pub game_dir : Option < PathBuf >, 41pub mods_dir : PathBuf , 42pub portal_auth : Option < PortalAuth >, 43} 44 45# [ derive ( Debug , Deserialize )] 46pub struct PortalAuth { 47pub username : String , 48pub token : String , 49} 50 51pub fn proc_input () ->Result <( Actions , Config , Directory )> { 52// Input 53let args =Args :: from_args (); 54let config_file =ConfigFile :: new ( & args. config ) ?. unwrap_or_default (); 55 56// Merge config options 57let config ={ 58let game_dir = args. game_dir . or ( config_file. game_dir ); 59Config { 60game_dir : game_dir. clone (), 61mods_dir : match [ game_dir. clone (), args. mods_dir . or ( config_file. mods_dir )] { 62[ _, Some ( mods_dir)] if mods_dir. exists () => mods_dir, 63[ Some ( game_dir), None ] if game_dir. exists () =>{ 64let mut mods_dir = game_dir; 65 mods_dir. push ( "mods" ); 66if !mods_dir. exists () { 67return Err ( anyhow! ( "Could not find mods directory" )); 68} 69 mods_dir 70} 71 _ =>return Err ( anyhow! ( "Invalid game or mods directories" )), 72}, 73portal_auth : config_file. portal . or_else ( ||{ 74if let Some ( game_dir) =& game_dir{ 75let mut player_data_path = game_dir. clone (); 76 player_data_path. push ( "player-data.json" ); 77if player_data_path. exists () { 78let player_data_json = fs:: read_to_string ( & player_data_path). ok () ?; 79if let PlayerDataJson { 80service_token : Some ( token), 81service_username : Some ( username), 82} = serde_json:: from_str ( & player_data_json). ok () ? 83{ 84return Some ( PortalAuth { token, username}); 85} 86} 87} 88None 89}), 90} 91}; 92 93// Process actions 94let mut directory =Directory :: new ( & config. mods_dir ) ?; 95let mut actions =Actions { 96remove : args. remove , 97 ..Default :: default () 98}; 99 100// Disabling 101if args. disable_all { 102 actions. disable =ModifyType :: All ; 103} else if !args. disable . is_empty () { 104 actions. disable =ModifyType :: Some ( args. disable ); 105} 106 107// TODO: Downloading 108 109// Enabling 110// TODO: This whole thing needs to be cleaned up 111if args. enable_all { 112 actions. enable =ModifyType :: All ; 113} else { 114if let Some ( path) = args. sync { 115if let Ok ( save_file) = sync:: SaveFile :: from ( path) { 116 actions. enable =match actions. enable { 117ModifyType :: None =>ModifyType :: Some ( save_file. mods ), 118ModifyType :: Some ( mut mods) =>{ 119 mods. extend ( save_file. mods ); 120ModifyType :: Some ( mods) 121} 122ModifyType :: All =>ModifyType :: All , 123} 124} 125} 126if let Some ( set_name) = args. enable_set { 127if let Some ( set) = config_file. sets . and_then ( |mut sets| sets. remove ( & set_name)) { 128 actions. enable =match actions. enable { 129ModifyType :: None =>ModifyType :: Some ( set), 130ModifyType :: Some ( mut mods) =>{ 131 mods. extend ( set); 132ModifyType :: Some ( mods) 133} 134ModifyType :: All =>todo! (), 135}; 136} 137} 138if !args. enable . is_empty () { 139 actions. enable =match actions. enable { 140ModifyType :: None =>ModifyType :: Some ( args. enable ), 141ModifyType :: Some ( mut mods) =>{ 142 mods. extend ( args. enable ); 143 144ModifyType :: Some ( mods) 145} 146ModifyType :: All =>ModifyType :: All , 147}; 148} 149 150// Add dependencies to the list 151if let ModifyType :: Some ( ref mut mods) = actions. enable { 152let mut cycle = mods. clone (); 153while !cycle. is_empty () { 154 cycle = cycle 155. iter () 156. filter ( |mod_ident| mod_ident. name !="base" ) 157. filter_map ( |mod_ident|{ 158 directory 159. get_mut ( mod_ident) 160. and_then ( |mod_version|{ 161 mod_version. get_info_json () ?. dependencies . as_ref () 162}) 163. map ( |dependencies|{ 164 dependencies 165. iter () 166. filter ( |dependency|{ 167 dependency. name !="base" 168 &&matches! ( 169 dependency. dep_type , 170ModDependencyType :: NoLoadOrder 171 |ModDependencyType :: Required 172) 173}) 174. map ( |dependency|ModIdent { 175name : dependency. name . clone (), 176version_req : dependency. version_req . clone (), 177}) 178. collect ::< Vec < ModIdent >>() 179}) 180}) 181. flatten () 182. collect (); 183 184 mods. extend ( cycle. clone ()); 185} 186} 187} 188 189Ok (( actions, config, directory)) 190} 191 192# [ derive ( StructOpt )] 193# [ structopt ( name = "fmm" , about = "Manage your Factorio mods." )] 194struct Args { 195/// The path to the configuration file 196# [ structopt ( long )] 197config : Option < PathBuf >, 198/// The directory of the Factorio installation 199# [ structopt ( long )] 200game_dir : Option < PathBuf >, 201/// The directory where mods are kept. Defaults to factorio-dir/mods 202# [ structopt ( long )] 203mods_dir : Option < PathBuf >, 204/// Disables all mods in the directory 205# [ structopt ( short = "o" , long )] 206disable_all : bool , 207/// Disables the given mods. Mods are formatted as `Name` 208# [ structopt ( short , long )] 209disable : Vec < ModIdent >, 210/// Enables all mods in the directory 211# [ structopt ( short = "a" , long )] 212enable_all : bool , 213/// Enables the mods in the given set 214# [ structopt ( short = "E" , long )] 215enable_set : Option < String >, 216/// Enables the given mods. Mods are formatted as `Name` or `Name@Version` 217# [ structopt ( short , long )] 218enable : Vec < ModIdent >, 219/// Lists all mods in the directory 220# [ structopt ( short , long )] 221list : bool , 222/// Removes the given mods from the mods directory. Mods are formatted as `Name` or `Name@Version` 223# [ structopt ( short , long )] 224remove : Vec < ModIdent >, 225/// A path to a save file to sync with 226# [ structopt ( short , long )] 227sync : Option < PathBuf >, 228} 229 230# [ serde_as ] 231# [ derive ( Deserialize , Default )] 232struct ConfigFile { 233game_dir : Option < PathBuf >, 234mods_dir : Option < PathBuf >, 235portal : Option < PortalAuth >, 236# [ serde_as ( as = "Option<HashMap<_, Vec<DisplayFromStr>>>" )] 237sets : Option < HashMap < String , Vec < ModIdent >>>, 238} 239 240impl ConfigFile { 241fn new ( path : & Option < PathBuf >) ->Result < Option < Self >, ConfigFileErr > { 242let config_path: Option < PathBuf > = path 243. clone () 244. or ({ 245BaseDirs :: new (). map ( |base_dirs|{ 246let mut config_path: PathBuf = base_dirs. config_dir (). into (); 247 config_path. push ( "fmm" ); 248 config_path. push ( "fmm.toml" ); 249 config_path 250}) 251}) 252. filter ( |config_path| config_path. exists ()); 253 254if config_path. is_none () { 255return Ok ( None ); 256} 257 258let file = fs:: read_to_string ( config_path. unwrap ()). map_err ( |_|ConfigFileErr :: Open ) ?; 259 260let config: ConfigFile = toml:: from_str ( & file). map_err ( |_|ConfigFileErr :: ParseFile ) ?; 261Ok ( Some ( config)) 262} 263} 264 265# [ derive ( Debug , Error )] 266enum ConfigFileErr { 267# [ error ( "Could not open config file." )] 268Open , 269# [ error ( "Could not parse config file." )] 270ParseFile , 271} 272 273# [ derive ( Deserialize )] 274# [ serde ( rename_all = "kebab-case" )] 275struct PlayerDataJson { 276service_username : Option < String >, 277service_token : Option < String >, 278}