chiark / gitweb /
changelog: document further make-release changes
[otter.git] / base / misc.rs
1 // Copyright 2020-2021 Ian Jackson and contributors to Otter
2 // SPDX-License-Identifier: AGPL-3.0-or-later
3 // There is NO WARRANTY.
4
5 // This is in this crate for convenience, not because it's to do with
6 // Z coordinates.
7
8 use crate::prelude::*;
9
10 pub const SVG_SCALE: f64 = 6.;
11
12 pub fn timestring_abbreviate<'x>(base: &str, this: &'x str)
13                                  -> (&'x str, bool)
14 {
15   fn split(s: &str) -> ArrayVec<&str, 3> {
16     s.splitn(3, ' ').collect()
17   }
18   let base3 = split(base);
19   let this3 = split(this);
20   let matches = |i| base3.get(i) == this3.get(i);
21   if_chain! {
22     if matches(0) && matches(2);
23     if let Some(abbrev) = this3.get(1);
24     then { (abbrev, true) }
25     else { (this, false) }
26   }
27 }
28
29 pub fn raw_angle_transform(compass: u8) -> String {
30   assert!(compass < 8);
31   if compass == 0 { default() }
32   else { format!("rotate({})", -45 * (compass as i16)) }
33 }
34
35 #[throws(fmt::Error)]
36 pub fn die_cooldown_path<W: fmt::Write>(mut w: W, r: f64, remprop: f64) {
37   // This is somewhat entangled with die.svg and
38   // dice/cooldown-template-extractor.
39   //
40   // Editing these might need to be done together.
41   write!(w, "M 0,-{r} A")?;
42
43   let mut arcto = move |proportion: f64| {
44     let angle = proportion * TAU;
45     let x = r * angle.sin();
46     let y = -r * angle.cos();
47     write!(w, " {r},{r} 0 0 1 {x},{y}")
48     //                  | | `sweep-flag (1 = clockwise)
49     //                  | `large-arc-flag (see below)
50     //                  `"angle" (ellipse skew angle)
51   };
52   
53   // This avoids ever trying to draw an arc segment that is around 180 degrees.
54   // If we did so there could be rounding errors that would mean we might
55   // disagree with the SVG renderer about whether the angle is <=> 180.
56   for split in [0.49, 0.98] {
57     if split >= remprop { break }
58     arcto(split)?;
59   }
60   arcto(remprop)?;
61 }
62
63 #[test]
64 fn die_cooldown_path_test() {
65   let t80 = |remprop, exp: &str| {
66     let mut got = String::new();
67     die_cooldown_path(&mut got, 80., remprop).unwrap();
68     assert_eq!(&got, exp, "\nfor {remprop} {exp}");
69   };
70   t80(1./3., "M 0,-80 A 80,80 0 0 1 69.2820323027551,39.999999999999986");
71   t80(1.   , "M 0,-80 A 80,80 0 0 1 5.023241562345087,79.84213827426173 80,80 0 0 1 -10.026658685144373,-79.36917610515822 80,80 0 0 1 -0.000000000000019594348786357652,-80");
72   t80(0.5  , "M 0,-80 A 80,80 0 0 1 5.023241562345087,79.84213827426173 80,80 0 0 1 0.000000000000009797174393178826,80");
73   t80(0.6  , "M 0,-80 A 80,80 0 0 1 5.023241562345087,79.84213827426173 80,80 0 0 1 -47.02282018339784,64.72135954999581");
74   t80(0.9  , "M 0,-80 A 80,80 0 0 1 5.023241562345087,79.84213827426173 80,80 0 0 1 -47.02282018339787,-64.72135954999578");
75   t80(0.99  , "M 0,-80 A 80,80 0 0 1 5.023241562345087,79.84213827426173 80,80 0 0 1 -10.026658685144373,-79.36917610515822 80,80 0 0 1 -5.023241562345061,-79.84213827426173");
76 }
77
78 pub fn default<T:Default>() -> T { Default::default() }
79
80 #[macro_export]
81 macro_rules! display_as_debug {
82   {$x:ty $( , $($gen_tt:tt)* )?} => {
83     impl $( $($gen_tt)* )? std::fmt::Display for $x {
84       #[throws(std::fmt::Error)]
85       fn fmt(&self, f: &mut std::fmt::Formatter) {
86         <Self as Debug>::fmt(self, f)?
87       }
88     }
89   }
90 }
91 pub use crate::display_as_debug;
92
93 pub type SvgAttrs = Vec<(HtmlLit,Html)>;
94
95 pub fn space_table_attrs(table_size: PosC<f64>) -> SvgAttrs {
96   let PosC { coords: [x, y] } = table_size;
97   vec![
98     (Html::lit("viewBox"), hformat!("0 0 {} {}", x, y) ),
99     (Html::lit("width"  ), (SVG_SCALE * x).to_html()  ),
100     (Html::lit("height" ), (SVG_SCALE * y).to_html()  ),
101   ]
102 }
103
104 pub fn space_rect_attrs(table_size: PosC<f64>) -> SvgAttrs {
105   vec![
106     (Html::lit("width" ), table_size.x().to_html()  ),
107     (Html::lit("height"), table_size.y().to_html()  ),
108   ]
109 }
110
111 #[macro_export]
112 macro_rules! if_let {
113   { $($variant:ident)::+ ($binding:pat) = $input:expr;
114     else $($otherwise:tt)*
115   } => {
116     let $binding = match $input {
117       $($variant)::+ (y) => y,
118       _ => { $($otherwise)* },
119     };
120   };
121   { $($variant:ident)::+ ($binding:pat) = $input:expr;
122     $($otherwise:tt)*
123   } => {
124     let $binding = match $input {
125       $($variant)::+  (y) => y,
126       $($otherwise)*,
127     };
128   };
129   { $($variant:ident)::+ {$binding:ident} = $input:expr;
130     else $($otherwise:tt)*
131   } => {
132     let $binding = match $input {
133       $($variant)::+ { $binding } => $binding,
134       _ => { $($otherwise)* },
135     };
136   };
137   { $($variant:ident)::+ {$binding:ident} = $input:expr;
138     $($otherwise:tt)*
139   } => {
140     let $binding = match $input {
141       $($variant)::+ { $binding } => $binding,
142       $($otherwise)*,
143     };
144   };
145 }
146
147 #[ext(pub, name=DebugExt)]
148 impl<T:Debug> T {
149   fn to_debug(&self) -> String { format!("{:?}", self) }
150 }
151
152 pub fn dbgc_helper(file: &'static str, line: u32,
153                    values: &[(&'static str, &dyn Debug)]) {
154   let buf = (||{
155     let mut buf = String::new();
156     write!(buf, "[{}:{}]", file, line)?;
157     for (s, v) in values.iter() {
158       write!(buf, " {}={:?}", s, v)?;
159     }
160     write!(buf, "\n")?;
161     Ok::<_,fmt::Error>(buf)
162   })();
163   let buf = buf.unwrap_or_else(
164     |e| format!("error formatting for dbgc! {}\n", e));
165   eprint!("{}", buf);
166 }
167
168 #[macro_export]
169 macro_rules! dbgc {
170     // NOTE: We cannot use `concat!` to make a static string as a format argument
171     // of `eprintln!` because `file!` could contain a `{` or
172     // `$val` expression could be a block (`{ .. }`), in which case the `eprintln!`
173     // will be malformed.
174     () => {
175       $crate::misc::dbgc_helper(std::file!(), std::line!(), &[])
176     };
177     ($val:expr $(,)?) => {
178         // Use of `match` here is intentional because it affects the lifetimes
179         // of temporaries - https://stackoverflow.com/a/48732525/1063961
180         match $val {
181             tmp => {
182                 $crate::misc::dbgc_helper(std::file!(), std::line!(),
183                                           &[(std::stringify!($val), &tmp)]);
184                 tmp
185             }
186         }
187     };
188     ($($val:expr),+ $(,)?) => {
189       $crate::misc::dbgc_helper(std::file!(), std::line!(),
190                                 &[$((std::stringify!($val), &$val)),+])
191     };
192 }