From: Ian Jackson Date: Sat, 21 Nov 2020 20:08:07 +0000 (+0000) Subject: wip config specs in otter X-Git-Tag: otter-0.2.0~423 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=ad9e04f1ed39033551fdc987ca571149f37d7645;p=otter.git wip config specs in otter Signed-off-by: Ian Jackson --- diff --git a/Cargo.lock.example b/Cargo.lock.example index 9727eb58..c27d1cfa 100644 --- a/Cargo.lock.example +++ b/Cargo.lock.example @@ -1115,6 +1115,7 @@ dependencies = [ "chrono", "chrono-tz", "delegate", + "derive_more", "either", "failure", "fehler", diff --git a/Cargo.toml b/Cargo.toml index edb4edc5..cc055521 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ boolinator = "2" chrono = "0.4" chrono-tz = "0.5" delegate = "0.4" +derive_more = "0.99" either = "1" failure = "0.1.8" # for pwd fehler = "1" diff --git a/src/bin/daemon-otter.rs b/src/bin/daemon-otter.rs index 95e2fa8c..76104cc8 100644 --- a/src/bin/daemon-otter.rs +++ b/src/bin/daemon-otter.rs @@ -106,7 +106,7 @@ fn main() { // todo test suite for web api let config_filename = env::args().nth(1); - ServerConfig::read(config_filename.as_ref().map(String::as_str), true)?; + ServerConfig::read(config_filename.as_ref().map(String::as_str))?; std::env::set_var("ROCKET_CLI_COLORS","off"); diff --git a/src/bin/otter.rs b/src/bin/otter.rs index 672639c2..3975118e 100644 --- a/src/bin/otter.rs +++ b/src/bin/otter.rs @@ -7,6 +7,7 @@ use otter::imports::*; use argparse::{self,ArgumentParser,action::{TypedAction,ParseResult}}; use argparse::action::{Action,IFlagAction,IArgAction}; +use derive_more::Display; use std::rc::Rc; use std::cell::RefCell; use std::cell::Cell; @@ -67,6 +68,7 @@ struct MainOpts { socket_path: String, verbose: i32, superuser: bool, + specs_dir: Option, } impl MainOpts { @@ -175,6 +177,56 @@ impl From for Box { fn from(a: AccessOpt) -> Self { a.0 } } +#[derive(Error,Debug,Display)] +struct ExecutableRelatedError(String); + +#[throws(ExecutableRelatedError)] +pub fn find_executable() -> String { + let e = env::current_exe() + .map_err(|e| ExecutableRelatedError( + format!("could not find current executable ({})", &e) + ))?; + let s = e.to_str() + .ok_or_else(|| ExecutableRelatedError( + format!("current executable has non-UTF8 filename!") + ))?; + s.into() +} + +pub fn in_basedir(verbose: bool, + from: Result, + from_what: &str, + from_exp_in: &str, + now_what: &str, + then_in: &str, + leaf: &str, + local_subdir: &str) + -> String +{ + match (||{ + let mut comps = from?.rsplit('/').skip(1); + if_chain! { + if Some(from_exp_in) == comps.next(); + if let Some(path) = comps.next(); + then { Ok(path) } + else { Err(ExecutableRelatedError( + format!("{} is not in a directory called {}", from_what, from_exp_in) + )) } + } + })() { + Err(whynot) => { + let r = format!("{}/{}", local_subdir, leaf); + if verbose { + eprintln!("{}: looking for {} in {}", &whynot.0, now_what, &r); + } + r + } + Ok(basedir) => { + format!("{}/{}/{}", basedir, then_in, leaf) + } + } +} + fn main() { #[derive(Default,Debug)] struct RawMainArgs { @@ -188,6 +240,7 @@ fn main() { superuser: bool, subcommand: String, subargs: Vec, + spec_dir: Option, }; let (subcommand, subargs, mo) = parse_args::( env::args().collect(), @@ -248,12 +301,37 @@ fn main() { .add_option(&["--super"], StoreTrue, "enable game server superuser access"); + ap.refer(&mut rma.spec_dir) + .add_option(&["--spec-dir"], StoreOption, + "directory for table and game specs"); + ap }, &|RawMainArgs { account, nick, timezone, access, socket_path, verbose, config_filename, superuser, - subcommand, subargs, + subcommand, subargs, spec_dir, }|{ + let config_info = Thunk::new(move ||{ + let config_filename = config_filename + .unwrap_or_else(||{ + let exe = find_executable(); + in_basedir(verbose > 1, exe, "current executable", "bin", + "config file", "etc", DEFAULT_CONFIG_LEAFNAME, + ".") + }); + ServerConfig::read(Some(&config_filename)) + .context("read config file")?; + Ok::<_,AE>((otter::config::config(), config_filename)) + }); + + let spec_dir = spec_dir.unwrap_or_else(||{ + let cfg = &config_info.as_ref()?.1; + let spec_dir = in_basedir(verbose > 1, cfg, "config filename", "etc", + "game and table specs", "specs", "", ".") + .strip_suffix("/").to_string(); + Ok(spec_dir) + })?; + let account : AccountName = account.map(Ok::<_,APE>).unwrap_or_else(||{ let user = env::var("USER").map_err(|e| ArgumentParseError( format!("default account needs USER env var: {}", &e) @@ -263,14 +341,7 @@ fn main() { subaccount: "".into(), }) })?; - let config = Thunk::new(||{ - ServerConfig::read( - config_filename.as_ref().map(String::as_str), - verbose > 1 - ) - .context("read config file")?; - Ok::<_,AE>(otter::config::config()) - }); + let socket_path = socket_path.map(Ok::<_,APE>).unwrap_or_else(||{ Ok(config.as_ref()?.command_socket.clone()) })?; @@ -282,6 +353,7 @@ fn main() { socket_path, verbose, superuser, + spec_dir, })) }, Some(&|w|{ writeln!(w, "\nSubcommands:")?; @@ -618,7 +690,14 @@ fn setup_table(_ma: &MainOpts, spec: &TableSpec) -> Vec { */ #[throws(AE)] -fn read_spec(filename: &str, what: &str) -> T { +fn read_spec(ma: &MainOpts, specname: &str, + kind: &str, what: &str) -> T { + let filename = if specname.contains('/') { + specname.to_string() + } else { + in_basedir(ma.basedir, "specs","specs", + format!("{}.{}.toml", specname, kind)) + }; (||{ let mut f = File::open(filename).context("open")?; let mut buf = String::new(); @@ -659,13 +738,16 @@ mod reset_game { fn subargs(sa: &mut Args) -> ArgumentParser { use argparse::*; let mut ap = ArgumentParser::new(); - ap.refer(&mut sa.table_file).metavar("TABLE-SPEC-TOML") + ap.refer(&mut sa.table_file) + .metavar("TABLE-SPEC[-TOML]") .add_option(&["--reset-table"],StoreOption, "reset the players and access too"); ap.refer(&mut sa.table_name).required() .add_argument("TABLE-NAME",Store,"table name"); ap.refer(&mut sa.game_file).required() - .add_argument("GAME-SPEC-TOML",Store,"game spec"); + .add_argument("GAME-SPEC[-TOML]",Store, + "game spec (path to .toml file, or found in specs directory if no '/')" + ); ap } diff --git a/src/config.rs b/src/config.rs index 581cb16a..241ca097 100644 --- a/src/config.rs +++ b/src/config.rs @@ -9,7 +9,8 @@ pub const EXIT_SITUATION : i32 = 8; pub const EXIT_USAGE : i32 = 12; pub const EXIT_DISASTER : i32 = 16; -pub const DEFAULT_CONFIG_FILENAME : &str = "server.toml"; +pub const DEFAULT_CONFIG_DIR : &str = "/etc/otter"; +pub const DEFAULT_CONFIG_LEAFNAME : &str = "server.toml"; const DEFAULT_SAVE_DIRECTORY : &str = "save"; const DEFAULT_COMMAND_SOCKET : &str = "command.socket"; // in save dir @@ -114,65 +115,12 @@ fn set_config(config: ServerConfig) { *GLOBAL.config.write().unwrap() = Arc::new(config) } -fn default_basedir_from_executable(verbose: bool) -> Option { - #[throws(StartupError)] - fn inner(verbose: bool) -> Option { - match match env::current_exe() - .map(|p| p.to_str().map(|s| s.to_string())) - { - Err(e) => Err(format!( - "could not find current executable ({})", &e - )), - Ok(None) => Err(format!( - "current executable has non-UTF8 filename!" - )), - Ok(Some(basedir)) if basedir.rsplit('/').nth(1) == Some("bin") => Ok( - format!("{}/", basedir) - ), - Ok(_) => Err(format!( - "current executable is not in a directory called bin" - )), - } { - Err(whynot) => { - if verbose { - eprintln!("{}: looking for ancillary files in current directory", - &whynot); - } - None - }, - Ok(f) => Some(f), - } - } - - inner(verbose).unwrap_or_else(|e|{ - eprintln!("startup error finding installation pieces: {}", &e); - exit(EXIT_DISASTER) - }) -} - -fn in_basedir - (basedir: Option<&String>, - subdir: &str, - localdir: &str, - leaf: &str) -> String -{ - match basedir { - Some(basedir) => format!("{}/{}/{}", basedir, subdir, leaf), - None => format!( "{}/{}", localdir, leaf), - } -} - impl ServerConfig { #[throws(StartupError)] - pub fn read(config_filename: Option<&str>, verbose: bool) { - let basedir = Thunk::new( - move || default_basedir_from_executable(verbose) - ); - let config_filename = config_filename - .map( - |s| s.to_string() - ).unwrap_or_else( - || in_basedir(basedir.as_ref(), "etc", "", DEFAULT_CONFIG_FILENAME) + pub fn read(config_filename: Option<&str>) { + let config_filename = config_filename.map(|s| s.to_string()) + .unwrap_or_else( + || format!("{}/{}", DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_LEAFNAME) ); let mut buf = String::new(); File::open(&config_filename).with_context(||config_filename.to_string())?