use std::path::PathBuf; use pronouns_today::PronounList; use pronouns_today::UserPreferences; use std::fs::OpenOptions; use std::fs::File; use std::io::Write; use pronouns_today::InstanceSettings; use argh::FromArgs; use serde::{Deserialize, Serialize}; #[derive(FromArgs, PartialEq, Debug)] /// Everything you need to selfhost a pronouns.today server pub struct PronounsTodayArgs { #[argh(subcommand)] pub command: SubCommand } #[derive(FromArgs, PartialEq, Debug)] #[argh(subcommand)] pub enum SubCommand { Run(Run), #[cfg(feature = "embed_static_assets")] DumpStatics(DumpStatics), } #[derive(FromArgs, PartialEq, Debug)] #[cfg(feature = "embed_static_assets")] #[argh(subcommand, name = "dump-statics")] /// Dump necessary static files to a directory to be served by a webserver. Serving /// static files with a dedicated webserver isn't necessary, but may improve performance. /// If serving static files, all files should be available under the /static/filename.ext /// route. pub struct DumpStatics { #[argh(option, default = "String::from(\"./static/\")")] /// the directory to write the static files to /// /// Defaults to ./static/ pub destination: String, } #[derive(FromArgs, PartialEq, Debug)] #[argh(subcommand, name = "run")] /// Run the server from the specified config file. Commandline options override config /// options. pub struct Run { #[cfg_attr(feature = "docker", argh( option, default = "PathBuf::from(\"/data/config.yml\")", short = 'c'))] #[cfg_attr(not(feature = "docker"), argh( option, default = "PathBuf::from(\"/etc/pronouns_today.yml\")", short = 'c'))] /// path to the config file (generated if needed). Defaults to /// /etc/pronouns_today.yml pub config: PathBuf, #[argh(switch)] /// don't attempt to read or generate a config file, just use command line args. Missing /// options will be filled in using sane defaults pub no_read_cfg: bool, #[argh(option, short = 'p')] /// the port to listen on pub port: Option, #[argh(option)] /// default pronoun probabilites (formatted as a prefstring, like the ones in the /// used in the custom URLs, e.g. "acaqeawdym") pub default_prefstr: Option, #[argh(option)] /// the pronouns to be supported by the server. see configuration file documentation /// for warnings when changing this value. formatted as a list of comma seperated /// five-form pronouns, e.g. she/her/her/hers/herself,he/him/his/his/himself pub pronouns: Option, } impl Run { /// Read the config from the provided file, and apply overrides from args /// /// If `--no-read-cfg` was passed, no config file is present, and there aren't /// enough args to populate the configuration, returns an [`Err`] with a list of /// arguments that were missing. pub fn load_config(self) -> Result { if !self.no_read_cfg { match File::open(&self.config) { Ok(raw_cfg) => { serde_yaml::from_reader(raw_cfg) .map_err(ConfigError::MalformedConfig) } Err(e) if e.kind() == std::io::ErrorKind::NotFound => { OpenOptions::new() .write(true) .create(true) .open(&self.config) .and_then(|mut c| c.write_all(include_bytes!("../assets/default_config.yml")) ) .map_err(ConfigError::IoError)?; Err(ConfigError::ConfigCreated(self.config.clone())) } Err(e) => { Err(ConfigError::IoError(e)) } } } else { Ok(Conf::default()) }.map(|config| config.update_with(self)) } } #[derive(Clone, Serialize, Deserialize, Debug)] /// A parsed config file for Pronouns.Today web pub struct Conf { #[serde(flatten)] /// General settings for the pronouns.today library pub instance_settings: InstanceSettings, /// The port for the server to bind to. Defaults to 1312 pub port: u16, } impl Conf { fn update_with(mut self, args: Run) -> Conf { self.port = args.port.unwrap_or(self.port); self.instance_settings.pronoun_list = args.pronouns.map(Into::into).unwrap_or(self.instance_settings.pronoun_list); self } } impl Default for Conf { fn default() -> Self { Conf { instance_settings: InstanceSettings::default(), port: 1312, } } } /// An error occured while trying to load the configuration pub enum ConfigError { /// There wase a problem reading/writing the config file IoError(std::io::Error), /// There wase a problem deserializing the config file MalformedConfig(serde_yaml::Error), /// The config file was missing, but has been created /// /// The program should now prompt the user to fill the config in, and then exit. The /// provided [`PathBuf`] contains the path to the new config file ConfigCreated(PathBuf), }