"enum_dispatch",
"env_logger 0.9.0",
"fehler",
- "flexi_logger",
- "fs2",
"glob",
"humantime-serde",
"image",
"inventory",
"lazy-init",
"lazy-regex",
- "lazy_static",
"libc",
"mio 0.8.2",
"num",
"openssh-keys",
"ordered-float",
"otter-support",
- "paste",
"percent-encoding",
- "pwd",
- "rand 0.8.5",
"regex",
"serde",
"serde_with",
"tempfile",
"tera",
"thiserror",
- "toml",
"typetag",
"uds",
"unicase",
"console",
"derive-into-owned",
"fehler",
+ "flexi_logger",
+ "fs2",
+ "lazy_static",
"log",
"nix 0.23.1",
"num-derive",
"num-traits",
"otter-base",
"parking_lot",
+ "paste",
+ "pwd",
+ "rand 0.8.5",
"rmp",
"rmp-serde",
"serde",
"serde_with",
"strum",
+ "toml",
"unicode-width",
]
either="1"
enum_dispatch="0.3.5"
env_logger="0.9"
-fs2="0.4"
glob="0.3"
humantime-serde="1"
inventory="0.2"
lazy-init="0.5"
lazy-regex="2"
-lazy_static="1"
libc="0.2"
num="0.4"
once_cell="1"
openssh-keys="0.5"
ordered-float="2"
-paste="1"
percent-encoding="2"
-pwd="1"
-rand="0.8"
regex="1"
structopt="0.3"
sha2="0.10"
subtle="2.4"
tempfile="3"
tera="1.10"
-toml="0.5"
typetag="0.1.6"
uds="0.2"
unicase="2"
async-condvar-fair = { version="0.2", features=["parking_lot_0_12"] }
const-default = { version="1", features=["derive" ] }
enum-map = { version="2" , features=["serde" ] }
-flexi_logger = { version="0.22" , features=["specfile" ] }
image = { version = "0.24", default-features=false, features=["jpeg","png"] }
index_vec = { version="0.1.1", features=["serde" ] }
mio = { version="0.8", features=["os-ext", "os-poll" ] }
// -------------------- private crates ----------
-use otter::config::DAEMON_STARTUP_REPORT;
+use otter_support::config::DAEMON_STARTUP_REPORT;
// ==================== public constants ====================
});
ServerConfig::read(Some(&config_filename), default())
.context("read config file")?;
- Ok::<_,AE>((otter::config::config(), config_filename))
+ Ok::<_,AE>((otter_support::config::config(), config_filename))
})().map_err(|e| ArgumentParseError(
format!("failed to find/load config: {}", &e)
))
use parking_lot::{Mutex, const_mutex, MutexGuard};
-use authproofs::*;
-
#[path="sshkeys.rs"]
pub mod sshkeys;
pub mod loaded_acl {
use crate::prelude::*;
- use authproofs::*;
pub trait Perm: FromPrimitive + ToPrimitive +
Copy + Eq + Hash + Debug + Sync + Send + 'static
const SPLIT: &[char] = &[',', ' '];
for libs in opts.libs.split(SPLIT) {
- let tlibs = Config1::PathGlob(libs.to_owned());
+ let tlibs = ShapelibConfig1::PathGlob(libs.to_owned());
load_global_libs(&[tlibs.clone()])?;
}
let mut items: Vec<ItemForOutput> = default();
//---------- displaing/presenting/authorising ----------
+#[ext(pub)]
impl Authorisation<InstanceName> {
- pub fn bundles(self) -> Authorisation<Id> { self.so_promise() }
+ fn bundles(self) -> Authorisation<Id> { self.so_promise() }
}
impl Display for State {
#[error("{0}")] CoordinateOverflow (#[from] CoordinateOverflow),
#[error("TOML syntax error: {0}")] TomlSyntaxError(String),
#[error("TOML structure error: {0}")] TomlStructureError(String),
- #[error("RNG is real, command not supported")] RngIsReal,
- #[error("Time is real, command not supported")] TimeIsReal,
+ #[error("{0}, command not supported")] RngIsReal(#[from] RngIsReal),
+ #[error("{0}, command not supported")] TimeIsReal(#[from] TimeIsReal),
#[error("upload truncated")] UploadTruncated,
#[error("upload corrupted")] UploadCorrupted,
#[error("too many bundles")] TooManyBundles,
pub use educe;
pub use either;
pub use env_logger;
-pub use flexi_logger;
-pub use fs2;
pub use glob;
pub use humantime_serde::{self, re::humantime};
pub use index_vec;
pub use lazy_init;
-pub use lazy_static;
pub use inventory;
pub use libc;
pub use once_cell;
pub use ordered_float;
-pub use pwd;
pub use regex;
pub use sha2;
pub use slotmap;
pub use tera;
-pub use toml;
pub use uds;
pub use vecdeque_stableix;
pub use zip as zipfile;
BadPieceStateForOperation,
}
-pub type StartupError = anyhow::Error;
-
pub use Fatal::{NoClient,NoPlayer};
pub enum AggregatedIE {
// (in order of lock acquisition (L first), so also in order of criticality
// (perf impact); outermost first, innermost last)
- // slow global locks:
- pub save_area_lock: Mutex<Option<File>>,
// <- accounts::accounts ->
games_table: RwLock<GamesTable>,
// <- InstanceContainer ->
// inner locks which the game needs:
dirty: Mutex<VecDeque<InstanceRef>>,
- pub config: RwLock<WholeServerConfig>,
// fast global lookups
players: RwLock<TokenTable<PlayerId>>,
}
}
+#[ext(pub)]
impl<A> Unauthorised<InstanceRef, A> {
#[throws(GameBeingDestroyed)]
- pub fn lock<'r>(&'r self) -> Unauthorised<InstanceGuard<'r>, A> {
+ fn lock<'r>(&'r self) -> Unauthorised<InstanceGuard<'r>, A> {
let must_not_escape = self.by_ref(Authorisation::promise_any());
Unauthorised::of(must_not_escape.lock()?)
}
- pub fn lock_even_destroying<'r>(&'r self) -> Unauthorised<InstanceGuard<'r>, A> {
+ fn lock_even_destroying<'r>(&'r self) -> Unauthorised<InstanceGuard<'r>, A> {
let must_not_escape = self.by_ref(Authorisation::promise_any());
Unauthorised::of(InstanceGuard {
c: must_not_escape.lock_even_destroying(),
})
}
- pub fn lock_bundles<'r>(&'r self) -> Unauthorised<BundlesGuard<'_>, A> {
+ fn lock_bundles<'r>(&'r self) -> Unauthorised<BundlesGuard<'_>, A> {
let must_not_escape = self.by_ref(Authorisation::promise_any());
Unauthorised::of(must_not_escape.lock_bundles())
}
pub use std::hash::Hash;
pub use std::iter;
pub use std::iter::{repeat_with};
-pub use std::marker::PhantomData;
pub use std::num::{NonZeroUsize, TryFromIntError, Wrapping};
-pub use std::net::{IpAddr, SocketAddr, ToSocketAddrs, Ipv6Addr, Ipv4Addr};
pub use std::path::PathBuf;
pub use std::str;
pub use std::str::FromStr;
pub use std::sync::atomic::AtomicBool;
pub use std::sync::mpsc;
pub use std::thread::{self, sleep};
-pub use std::time::{self, Duration, Instant};
pub use async_condvar_fair::{Condvar, BatonExt as _};
pub use boolinator::Boolinator as _;
pub use enum_dispatch::enum_dispatch;
pub use enum_map::{Enum, EnumMap};
pub use fehler::{throw, throws};
-pub use flexi_logger::LogSpecification;
-pub use fs2::FileExt;
pub use index_vec::{define_index_type, index_vec, IndexSlice, IndexVec};
pub use lazy_regex::regex;
-pub use lazy_static::lazy_static;
-pub use log::{log, log_enabled};
pub use ordered_float::OrderedFloat;
-pub use paste::paste;
pub use percent_encoding::percent_decode_str;
pub use percent_encoding::utf8_percent_encode;
pub use percent_encoding::NON_ALPHANUMERIC;
-pub use rand::distributions::Alphanumeric;
-pub use rand::thread_rng;
-pub use rand::Rng;
-pub use rand::prelude::SliceRandom;
pub use regex::Regex;
pub use sha2::{Sha512, Sha512_256};
pub use slotmap::{dense::DenseSlotMap, SparseSecondaryMap, Key as _};
pub use crate::ensure_eq;
pub use crate::format_by_fmt_hex;
pub use crate::impl_via_ambassador;
-pub use crate::trace_dbg;
pub use crate::{want, wantok, wants, want_let, want_failed_internal};
pub use crate::serde_with_compat;
pub use crate::accounts::loaded_acl::{self, EffectiveACL, LoadedAcl, PermSet};
pub use crate::accounts::*;
-pub use crate::authproofs::{self, Authorisation, Unauthorised};
-pub use crate::authproofs::AuthorisationSuperuser;
pub use crate::asseturl::*;
pub use crate::bundles::{self, InstanceBundles, MgmtBundleListExt};
pub use crate::commands::{AccessTokenInfo, AccessTokenReport, MgmtError};
pub use crate::commands::{MgmtGameInstruction, MgmtGameResponse};
pub use crate::commands::{MgmtBundleList, MgmtGameUpdateMode};
pub use crate::commands::{ProgressUpdateMode};
-pub use crate::config::*;
pub use crate::debugreader::DebugReader;
pub use crate::digestrw::{self, *};
pub use crate::error::*;
-pub use crate::fake_rng::*;
-pub use crate::fake_time::*;
pub use crate::fastsplit::*;
pub use crate::gamestate::*;
pub use crate::global::*;
pub use crate::slotmap_slot_idx::*;
pub use crate::spec::*;
pub use crate::spec::piece_specs::{FaceColourSpecs, SimpleCommon};
-pub use crate::toml_de;
pub use crate::timedfd::*;
pub use crate::updates::*;
pub use crate::utils::*;
pub type SvgData = Vec<u8>;
pub type Colour = Html;
-pub const MS: time::Duration = time::Duration::from_millis(1);
-
// ---------- type abbreviations ----------
// accounts.rs
pub mod accounts;
pub mod asseturl;
-pub mod authproofs;
pub mod bundles;
pub mod clock;
pub mod commands;
-pub mod config;
pub mod currency;
pub mod deck;
pub mod dice;
pub mod ui;
pub mod utils;
-#[path = "fake-rng.rs"] pub mod fake_rng;
-#[path = "fake-time.rs"] pub mod fake_time;
#[path = "materials-format.rs"] pub mod materials_format;
#[path = "shapelib-toml.rs"] pub mod shapelib_toml;
#[path = "slotmap-slot-idx.rs"] pub mod slotmap_slot_idx;
-#[path = "toml-de.rs"] pub mod toml_de;
use parking_lot::{const_rwlock, RwLock};
use parking_lot::RwLockReadGuard;
+use ShapelibConfig1 as Config1;
+use ShapelibExplicit1 as Explicit1;
+
//==================== structs and definitions ====================
// Naming convention:
//==================== configu and loading global libs ====================
-#[derive(Deserialize,Debug,Clone)]
-#[serde(untagged)]
-pub enum Config1 {
- PathGlob(String),
- Explicit(Explicit1),
-}
-
-#[derive(Deserialize,Debug,Clone)]
-pub struct Explicit1 {
- pub name: String,
- pub catalogue: String,
- pub dirname: String,
-}
-
#[throws(LibraryLoadError)]
pub fn load_1_global_library(l: &Explicit1) {
let toml_path = &l.catalogue;
count, &l.name, &l.catalogue, &l.dirname);
}
-impl Config1 {
+#[ext(pub)]
+impl ShapelibConfig1 {
fn resolve(&self) -> Result<Box<dyn ExactSizeIterator<Item=Explicit1>>, LibraryLoadError> {
use Config1::*;
Ok(match self {
}
}
-//---------- IpAddress ----------
-
-pub trait IpAddress: Debug {
- fn with_port(&self, port: u16) -> SocketAddr;
-}
-
-impl<A> IpAddress for A where A: Into<IpAddr> + Debug + Clone {
- fn with_port(&self, port: u16) -> SocketAddr {
- match (self.clone().into(), port)
- .to_socket_addrs()
- .map(|i| i.at_most_one()) {
- Ok(Ok(Some(addr))) => addr,
- x => panic!("{:?},{} gave {:?}", self, port, x),
- }
- }
-}
-
//---------- get_or_extend_with ----------
// todo: DerefMut
-//========== toml ====================
-
-#[derive(Debug,Copy,Clone,Eq,PartialEq,Ord,PartialOrd)]
-pub struct TomlQuote<'s>(pub &'s str);
-
-// We reimplement this because the toml crate doesn't expose it, and
-// looking at the github issues etc. for that crate isn't encuraging.
-impl<'s> Display for TomlQuote<'s> {
- #[throws(fmt::Error)]
- fn fmt(&self, f: &mut fmt::Formatter) {
- for c in self.0.chars() {
- match c {
- '"' | '\\'=> write!(f, "\\{}", c)?,
- c if (c < ' ' && c != '\t') || c == '\x7f' => {
- write!(f, r#"\u{:04x}"#, c as u32).unwrap();
- continue;
- }
- c => write!(f, "{}", c)?,
- }
- }
- }
-}
-
-#[test]
-fn toml_quote_string_test(){
- assert_eq!(TomlQuote(r#"w \ " \a\7fƒ."#).to_string(),
- r#"w \\ \" \u0007\u007fƒ."#);
-}
-
-pub fn toml_merge<'u,
- S: 'u + AsRef<str>,
- KV: IntoIterator<Item=(&'u S, &'u toml::Value)>
- >(
- table: &mut toml::value::Table,
- updates: KV,
-) {
- use toml::value::{Table, Value};
- type TME<'e> = toml::map::Entry<'e>;
-
- let mut kv = updates.into_iter().map(|(k, v)| (k.as_ref(), v));
- inner(table, &mut kv);
-
- fn inner<'u>(
- table: &mut Table,
- updates: &'u mut dyn Iterator<Item=(&'u str, &'u Value)>
- ) {
- for (k, v) in updates {
- let e = table.entry(k);
- match e {
- TME::Vacant(ve) => {
- ve.insert(v.clone());
- }
- TME::Occupied(mut oe) => match (oe.get_mut(), v) {
- (Value::Table(old), Value::Table(new)) => {
- toml_merge(old, new);
- }
- (Value::Array(old), Value::Array(new)) => {
- old.extend(new.iter().cloned());
- }
- (old, new) => {
- *old = new.clone();
- }
- }
- }
- }
- }
-}
-
//========== .insert() and .remove() on various Entry ==========
macro_rules! entry_define_insert_remove {
//========== miscellaneous macros ==========
-paste!{
- #[cfg(debug_assertions)]
- pub fn [<x x x>]<T>() -> T { panic!("todo item triggered") }
-}
-
#[macro_export]
macro_rules! impl_via_ambassador{
{
} )* }
}
-#[macro_export]
-macro_rules! trace_dbg {
- ($msg:expr $(,$val:expr)*) => {
- if log_enabled!(log::Level::Trace) {
- #[allow(unused_mut)]
- let mut buf = format!("{}", &$msg);
- $( write!(&mut buf, " {}={:?}", stringify!($val), &$val).unwrap(); )*
- trace!("{}", buf);
- }
- }
-
-}
-
#[macro_export]
macro_rules! ensure_eq {
($v1:expr, $v2:expr) => {
console="0.15"
derive-into-owned="0.2"
fehler="1"
+fs2="0.4"
+lazy_static="1"
log="0.4"
nix="0.23"
num-derive="0.3"
parking_lot="0.12"
+paste="1"
+pwd="1"
+rand="0.8"
rmp="0.8"
rmp-serde="1"
+toml="0.5"
unicode-width="0.1"
+flexi_logger = { version="0.22" , features=["specfile" ] }
+
# Repeated in other Cargo.toml's because importing does not work properly
num-traits="0.2"
serde_with="1"
pub wasm_dir: Option<String>,
pub log: Option<toml::Value>,
pub bundled_sources: Option<String>,
- pub shapelibs: Option<Vec<shapelib::Config1>>,
+ pub shapelibs: Option<Vec<ShapelibConfig1>>,
pub specs_dir: Option<String>,
pub sendmail: Option<String>,
/// for auth keys, split on spaces
pub check_bundled_sources: Option<bool>,
}
+#[derive(Deserialize,Debug,Clone)]
+#[serde(untagged)]
+pub enum ShapelibConfig1 {
+ PathGlob(String),
+ Explicit(ShapelibExplicit1),
+}
+
+#[derive(Deserialize,Debug,Clone)]
+pub struct ShapelibExplicit1 {
+ pub name: String,
+ pub catalogue: String,
+ pub dirname: String,
+}
+
#[derive(Debug,Clone)]
pub struct WholeServerConfig {
server: Arc<ServerConfig>,
pub libexec_dir: String,
pub usvg_bin: String,
pub bundled_sources: String,
- pub shapelibs: Vec<shapelib::Config1>,
+ pub shapelibs: Vec<ShapelibConfig1>,
pub specs_dir: String,
pub sendmail: String,
pub ssh_proxy_bin: String,
let shapelibs = shapelibs.unwrap_or_else(||{
let glob = defpath(None, DEFAULT_LIBRARY_GLOB);
- vec![ shapelib::Config1::PathGlob(glob) ]
+ vec![ ShapelibConfig1::PathGlob(glob) ]
});
let sendmail = prctx.resolve(&sendmail.unwrap_or_else(
}
}
+lazy_static! {
+ static ref SAVE_AREA_LOCK: Mutex<Option<File>> = default();
+
+ static ref CONFIG: RwLock<WholeServerConfig> = default();
+}
+
pub fn config() -> Arc<ServerConfig> {
- GLOBAL.config.read().server.clone()
+ CONFIG.read().server.clone()
}
pub fn log_config() -> LogSpecification {
- GLOBAL.config.read().log.clone()
+ CONFIG.read().log.clone()
}
fn set_config(whole: WholeServerConfig) {
- *GLOBAL.config.write() = whole;
+ *CONFIG.write() = whole;
}
impl ServerConfig {
#[throws(AE)]
pub fn lock_save_area(&self) {
- let mut st = GLOBAL.save_area_lock.lock();
+ let mut st = SAVE_AREA_LOCK.lock();
let st = &mut *st;
if st.is_none() {
let lockfile = format!("{}/lock", config().save_dir);
}
pub fn save_dir(&self) -> &String {
- let st = GLOBAL.save_area_lock.lock();
+ let st = SAVE_AREA_LOCK.lock();
let mut _f: &File = st.as_ref().unwrap();
&self.save_dir
}
pub use anyhow;
pub use chrono;
pub use chrono_tz;
+pub use flexi_logger;
+pub use fs2;
+pub use lazy_static;
pub use log;
pub use nix;
pub use parking_lot;
+pub use pwd;
+pub use rand;
pub use rmp_serde;
+pub use toml;
pub use unicode_width;
#[serde(transparent)]
pub struct FakeRngSpec(Option<Vec<String>>);
+#[derive(Serialize,Deserialize,Error,Debug,Clone)]
+#[error("RNG is real")]
+pub struct RngIsReal;
+
impl FakeRngSpec {
pub fn make_game_rng(self) -> RngWrap { RngWrap( match self.0 {
None => None,
impl RngWrap {
pub fn is_fake(&self) -> bool { self.0.is_some() }
- #[throws(MgmtError)]
+ #[throws(RngIsReal)]
pub fn set_fake(&self, v: Vec<String>, _: AuthorisationSuperuser) {
- let mut fake = self.0.as_ref().ok_or(ME::RngIsReal)?.lock();
+ let mut fake = self.0.as_ref().ok_or(RngIsReal)?.lock();
fake.i = 0;
fake.ents = v;
}
type Millis = u32;
type Micros = u64;
+#[derive(Serialize,Deserialize,Error,Debug,Clone)]
+#[error("Time is real")]
+pub struct TimeIsReal;
+
#[derive(Deserialize,Debug,Clone,Default)]
#[serde(transparent)]
pub struct FakeTimeConfig(pub Option<FakeTimeSpec>);
fake.start + Duration::from_micros(fake.current)
}
- #[throws(MgmtError)]
+ #[throws(TimeIsReal)]
pub fn set_fake(&self, fspec: FakeTimeSpec, _: AuthorisationSuperuser) {
- let mut guard = self.fakeable.as_ref().ok_or(ME::TimeIsReal)?.lock();
+ let mut guard = self.fakeable.as_ref().ok_or(TimeIsReal)?.lock();
*guard = FakeClock::from_spec(fspec)
}
pub use std::io;
pub use std::io::ErrorKind;
pub use std::io::{BufRead, BufReader, BufWriter, Read, Write};
+pub use std::marker::PhantomData;
+pub use std::net::{IpAddr, SocketAddr, ToSocketAddrs, Ipv6Addr, Ipv4Addr};
pub use std::os::linux::fs::MetadataExt as _; // todo why linux for st_mode??
pub use std::os::unix;
pub use std::os::unix::ffi::OsStrExt;
pub use std::os::unix::process::{CommandExt, ExitStatusExt};
pub use std::process::{exit, Child, Command, Stdio};
pub use std::sync::Arc;
+pub use std::time::{self, Duration, Instant};
pub use anyhow::{anyhow, ensure, Context};
pub use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt};
pub use derive_into_owned::IntoOwned;
+pub use flexi_logger::LogSpecification;
+pub use fs2::FileExt;
+pub use lazy_static::lazy_static;
pub use log::{debug, error, info, trace, warn};
+pub use log::{log, log_enabled};
pub use nix::unistd::{self, Uid};
pub use nix::sys::time::TimeSpec;
pub use nix::time::clock_gettime;
pub use num_derive::{ToPrimitive, FromPrimitive};
pub use num_traits::{Bounded, FromPrimitive, ToPrimitive};
+pub use paste::paste;
+pub use rand::distributions::Alphanumeric;
+pub use rand::thread_rng;
+pub use rand::Rng;
+pub use rand::prelude::SliceRandom;
pub use serde::ser::SerializeTuple;
pub use serde::{de::DeserializeOwned, Deserialize, Serialize};
pub use serde::de::Error as _;
//pub use crate::debugmutex::{Mutex, MutexGuard};
pub use crate::matches_doesnot;
+pub use crate::trace_dbg;
+pub use crate::authproofs::{self, Authorisation, Unauthorised};
+pub use crate::authproofs::AuthorisationSuperuser;
pub use crate::childio;
+pub use crate::config::*;
pub use crate::debugmutex::DebugIdentify;
+pub use crate::fake_rng::*;
+pub use crate::fake_time::*;
pub use crate::packetframe::{FrameReader, FrameWriter, ReadFrame, WriteFrame};
pub use crate::packetframe::{ReadExt, ResponseWriter};
pub use crate::packetframe::{PacketFrameReadError, PacketFrameWriteError};
pub use crate::progress::{self, ProgressInfo, OriginatorExt as _};
pub use crate::support::*;
pub use crate::termprogress;
+pub use crate::toml_de;
pub use crate::tz::*;
+pub type StartupError = anyhow::Error;
+
+pub const MS: time::Duration = time::Duration::from_millis(1);
+
// ---------- type abbreviations ----------
pub type AE = anyhow::Error;
pub mod imports;
pub mod prelude;
+pub mod authproofs;
pub mod childio;
+pub mod config;
pub mod debugmutex;
pub mod packetframe;
pub mod progress;
pub mod support;
pub mod termprogress;
pub mod tz;
+
+#[path = "fake-rng.rs"] pub mod fake_rng;
+#[path = "fake-time.rs"] pub mod fake_time;
+#[path = "toml-de.rs"] pub mod toml_de;
use crate::prelude::*;
+//========== miscellany ==========
+// (roughly in order of implementation length)
+
+//---------- IpAddress ----------
+
+pub trait IpAddress: Debug {
+ fn with_port(&self, port: u16) -> SocketAddr;
+}
+
+impl<A> IpAddress for A where A: Into<IpAddr> + Debug + Clone {
+ fn with_port(&self, port: u16) -> SocketAddr {
+ match (self.clone().into(), port)
+ .to_socket_addrs()
+ .map(|i| i.at_most_one()) {
+ Ok(Ok(Some(addr))) => addr,
+ x => panic!("{:?},{} gave {:?}", self, port, x),
+ }
+ }
+}
+
+//========== toml ====================
+
+#[derive(Debug,Copy,Clone,Eq,PartialEq,Ord,PartialOrd)]
+pub struct TomlQuote<'s>(pub &'s str);
+
+// We reimplement this because the toml crate doesn't expose it, and
+// looking at the github issues etc. for that crate isn't encuraging.
+impl<'s> Display for TomlQuote<'s> {
+ #[throws(fmt::Error)]
+ fn fmt(&self, f: &mut fmt::Formatter) {
+ for c in self.0.chars() {
+ match c {
+ '"' | '\\'=> write!(f, "\\{}", c)?,
+ c if (c < ' ' && c != '\t') || c == '\x7f' => {
+ write!(f, r#"\u{:04x}"#, c as u32).unwrap();
+ continue;
+ }
+ c => write!(f, "{}", c)?,
+ }
+ }
+ }
+}
+
+#[test]
+fn toml_quote_string_test(){
+ assert_eq!(TomlQuote(r#"w \ " \a\7fƒ."#).to_string(),
+ r#"w \\ \" \u0007\u007fƒ."#);
+}
+
+pub fn toml_merge<'u,
+ S: 'u + AsRef<str>,
+ KV: IntoIterator<Item=(&'u S, &'u toml::Value)>
+ >(
+ table: &mut toml::value::Table,
+ updates: KV,
+) {
+ use toml::value::{Table, Value};
+ type TME<'e> = toml::map::Entry<'e>;
+
+ let mut kv = updates.into_iter().map(|(k, v)| (k.as_ref(), v));
+ inner(table, &mut kv);
+
+ fn inner<'u>(
+ table: &mut Table,
+ updates: &'u mut dyn Iterator<Item=(&'u str, &'u Value)>
+ ) {
+ for (k, v) in updates {
+ let e = table.entry(k);
+ match e {
+ TME::Vacant(ve) => {
+ ve.insert(v.clone());
+ }
+ TME::Occupied(mut oe) => match (oe.get_mut(), v) {
+ (Value::Table(old), Value::Table(new)) => {
+ toml_merge(old, new);
+ }
+ (Value::Array(old), Value::Array(new)) => {
+ old.extend(new.iter().cloned());
+ }
+ (old, new) => {
+ *old = new.clone();
+ }
+ }
+ }
+ }
+ }
+}
+
//========== Timestamp ==========
#[derive(Copy,Clone,Debug,Serialize,Deserialize,Eq,Ord,PartialEq,PartialOrd)]
}
}
+//========== miscellaneous macros ==========
+
+paste!{
+ #[cfg(debug_assertions)]
+ pub fn [<x x x>]<T>() -> T { panic!("todo item triggered") }
+}
+
+#[macro_export]
+macro_rules! trace_dbg {
+ ($msg:expr $(,$val:expr)*) => {
+ if log_enabled!(log::Level::Trace) {
+ #[allow(unused_mut)]
+ let mut buf = format!("{}", &$msg);
+ $( write!(&mut buf, " {}={:?}", stringify!($val), &$val).unwrap(); )*
+ trace!("{}", buf);
+ }
+ }
+
+}
+
//========== matches_doesnot ==========
#[macro_export] // <- otherwise bogus warning `unused_macros`
// There is NO WARRANTY.
use crate::crates::*;
-use otter_support::crates::*;
use otter_base::crates::*;
use std::fmt::{Debug, Display};