chiark / gitweb /
Use new Html everywhere
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Tue, 30 Mar 2021 01:37:34 +0000 (02:37 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Tue, 30 Mar 2021 01:37:34 +0000 (02:37 +0100)
Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
23 files changed:
apitest/at-otter.rs
daemon/api.rs
daemon/cmdlistener.rs
daemon/session.rs
src/accounts.rs
src/bin/otterlib.rs
src/clock.rs
src/deck.rs
src/gamestate.rs
src/global.rs
src/hand.rs
src/hidden.rs
src/keydata.rs
src/lib.rs
src/pcrender.rs
src/pieces.rs
src/prelude.rs
src/shapelib-toml.rs
src/shapelib.rs
src/spec.rs
src/ui.rs
src/updates.rs
wdriver/wdt-hand.rs

index a9ea0de0079f22fc14d85be32cc3c9063c17ffb2..4b8e37febca8dd7415d71f9cd3135ba91e9a37d3 100644 (file)
@@ -537,7 +537,9 @@ impl Ctx {
     // ----- alice: claim alices' hand -----
 
     let [hand] = a_pieces.iter_enumerated()
-      .filter(|(_i,p)| p.info["desc"] == otter::hand::UNCLAIMED_DESC)
+      .filter(|(_i,p)| {
+        p.info["desc"] == otter::hand::UNCLAIMED_DESC.as_html_str()
+      })
       .map(|(i,_)| i)
       .collect::<ArrayVec<[_;1]>>()
       .into_inner().unwrap();
index 54320492f9f55e1c2d02045196a512fc20b6217c..902b36f1ac7509a8d0b3a6d8da2052ed44d8c8db 100644 (file)
@@ -319,7 +319,7 @@ api_route!{
       let pri = piece_pri(ioccults, &gs.occults, player, gpl, piece, gpc, ipc)
         .ok_or(POE::PieceGone)?;
 
-      let pcs = pri.describe(ioccults,&gs.occults, gpc, ipc).0;
+      let pcs = pri.describe(ioccults,&gs.occults, gpc, ipc);
 
       gpc.held = Some(player);
 
@@ -327,10 +327,10 @@ api_route!{
 
       let pls = &htmlescape::encode_minimal(&gpl.nick);
 
-      let logent = LogEntry { html: Html(match was {
-        Some(was) => format!("{} wrested {} from {}", pls, pcs, was),
-        None => format!("{} wrested {}", pls, pcs),
-      })};
+      let logent = LogEntry { html: match was {
+        Some(was) => hformat!("{} wrested {} from {}", pls, pcs, was),
+        None => hformat!("{} wrested {}", pls, pcs),
+      }};
 
       (WhatResponseToClientOp::Predictable,
        update, vec![logent]).into()
index 5ef042a04ac26869df4083bddfb8d7944c77cf2e..afc698e2010197ffa102448acc06cf9c40b270e6 100644 (file)
@@ -159,7 +159,7 @@ fn execute(cs: &mut CommandStream, cmd: MgmtCommand) -> MgmtResponse {
       let auth = authorise_by_account(cs, &ag, &game)?;
 
       let gs = otter::gamestate::GameState {
-        table_colour: Html::lit("green"),
+        table_colour: Html::lit("green").into(),
         table_size: DEFAULT_TABLE_SIZE,
         pieces: default(),
         players: default(),
@@ -350,8 +350,8 @@ fn execute_game_insn<'cs, 'igr, 'ig: 'igr>(
       ig.gs.table_size = size;
       (U{ pcs: vec![],
           log: vec![ LogEntry {
-            html: Html(format!("{} resized the table to {}x{}",
-                               &who.0, size.x(), size.y())),
+            html: hformat!("{} resized the table to {}x{}",
+                           who, size.x(), size.y()),
           }],
           raw: Some(vec![ PreparedUpdateEntry::SetTableSize(size) ]) },
        Fine, None, ig)
@@ -363,8 +363,8 @@ fn execute_game_insn<'cs, 'igr, 'ig: 'igr>(
       ig.gs.table_colour = colour.clone();
       (U{ pcs: vec![],
           log: vec![ LogEntry {
-            html: Html(format!("{} recoloured the tabletop to {}",
-                               &who.0, &colour.0)),
+            html: hformat!("{} recoloured the tabletop to {}",
+                           &who, &colour),
           }],
           raw: Some(vec![ PreparedUpdateEntry::SetTableColour(colour) ]) },
        Fine, None, ig)
@@ -378,7 +378,7 @@ fn execute_game_insn<'cs, 'igr, 'ig: 'igr>(
       let (ig, auth) = cs.check_acl(ag, ig, PCH::Instance, &[TP::Play])?;
       let nick = nick.ok_or(ME::MustSpecifyNick)?;
       let logentry = LogEntry {
-        html: Html(format!("{} [{}] joined the game", &nick, &account)),
+        html: hformat!("{} [{}] joined the game", nick, account),
       };
       let timezone = &arecord.timezone;
       let tz = tz_from_str(&timezone);
@@ -482,10 +482,8 @@ fn execute_game_insn<'cs, 'igr, 'ig: 'igr>(
         ig.check_new_nick(&new_nick)?;
         let gpl = ig.gs.players.byid_mut(player)?;
         log.push(LogEntry {
-          html: Html(format!("{} changed {}'s nick to {}",
-                             &who.0,
-                             htmlescape::encode_minimal(&gpl.nick),
-                             htmlescape::encode_minimal(&new_nick))),
+          html: hformat!("{} changed {}'s nick to {}",
+                         &who, gpl.nick, new_nick),
         });
         gpl.nick = new_nick;
       }
@@ -532,10 +530,10 @@ fn execute_game_insn<'cs, 'igr, 'ig: 'igr>(
         }
         let new_links = Arc::new(new_links);
         *ig_links = new_links.clone();
-        Ok(Html(
-          format!("{} set the links to off-server game resources",
-                  &who.0)
-        ))
+        Ok(
+          hformat!("{} set the links to off-server game resources",
+                   who)
+        )
       })?
     }
 
@@ -547,10 +545,10 @@ fn execute_game_insn<'cs, 'igr, 'ig: 'igr>(
         new_links[kind] = Some(url.into_string());
         let new_links = Arc::new(new_links);
         *ig_links = new_links.clone();
-        Ok(Html(
-          format!("{} set the link {}",
-                  &who.0, &show.0)
-        ))
+        Ok(
+          hformat!("{} set the link {}",
+                  who, show)
+        )
       })?
     }
 
@@ -560,10 +558,10 @@ fn execute_game_insn<'cs, 'igr, 'ig: 'igr>(
         new_links[kind] = None;
         let new_links = Arc::new(new_links);
         *ig_links = new_links.clone();
-        Ok(Html(
-          format!("{} removed the link {}",
-                  &who.0, &kind)
-        ))
+        Ok(
+          hformat!("{} removed the link {}",
+                  who, &kind)
+        )
       })?
     }
 
@@ -599,8 +597,8 @@ fn execute_game_insn<'cs, 'igr, 'ig: 'igr>(
       let (gpl, ipl, update) = got.into_iter().next()
         .ok_or(PlayerNotFound)?;
 
-      let html = Html(
-        format!("{} [{}] left the game [{}]"
+      let html =
+        hformat!("{} [{}] left the game [{}]"
                 ,
                 (|| Some(gpl?.nick))()
                 .unwrap_or("<partial data!>".into())
@@ -613,7 +611,7 @@ fn execute_game_insn<'cs, 'igr, 'ig: 'igr>(
                 ,
                 &account
                 )
-      );
+        ;
 
       (U{ pcs: vec![],
           log: vec![ LogEntry { html }],
@@ -654,7 +652,7 @@ fn execute_game_insn<'cs, 'igr, 'ig: 'igr>(
         let pri = PieceRenderInstructions::new_visible(default());
         pri.describe(ioccults,&gs.occults, gpc, &ipc)
       } else {
-        Html::lit("&lt;piece partially missing from game state!&gt;")
+        "<piece partially missing from game state>".to_html()
       };
       if let Some(gpc) = gpc {
         ipc.p.into_inner().delete_hook(&gpc, gs);
@@ -662,8 +660,8 @@ fn execute_game_insn<'cs, 'igr, 'ig: 'igr>(
       if let Some(occilk) = ipc.occilk { ig.ioccults.ilks.dispose(occilk); }
       (U{ pcs: vec![(piece, PieceUpdateOp::Delete())],
           log: vec![ LogEntry {
-            html: Html(format!("A piece {} was removed from the game",
-                          desc_html.0)),
+            html: hformat!("A piece {} was removed from the game",
+                          desc_html),
           }],
           raw: None },
        Fine,
@@ -741,7 +739,7 @@ fn execute_game_insn<'cs, 'igr, 'ig: 'igr>(
 
       (U{ pcs: updates,
           log: vec![ LogEntry {
-            html: Html(format!("{} added {} pieces", &who.0, count_len)),
+            html: hformat!("{} added {} pieces", who, count_len),
           }],
           raw: None },
        Fine, None, ig_g)
@@ -762,7 +760,7 @@ fn execute_game_insn<'cs, 'igr, 'ig: 'igr>(
       }
       (U{ pcs: vec![ ],
           log: vec![ LogEntry {
-            html: Html(format!("{} cleared the log", &who.0)),
+            html: hformat!("{} cleared the log", who),
           } ],
           raw: None },
        Fine, None, ig)
@@ -772,8 +770,8 @@ fn execute_game_insn<'cs, 'igr, 'ig: 'igr>(
       let (ig, _) = cs.check_acl(&ag, ig, PCH::Instance, &[TP::Super])?;
       ig.acl = acl.into();
       let mut log = vec![ LogEntry {
-        html: Html(format!("{} set the table access control list",
-                           &who.0)),
+        html: hformat!("{} set the table access control list",
+                       who),
       } ];
 
       #[throws(InternalError)]
@@ -813,7 +811,7 @@ fn execute_game_insn<'cs, 'igr, 'ig: 'igr>(
             "<i>partial data?!</i>".to_string()
           };
           log.push(LogEntry {
-            html: Html(format!("{} removed a player {}", &who.0, &show)),
+            html: hformat!("{} removed a player {}", who, show),
           });
           updates.push(update);
         }
@@ -856,11 +854,11 @@ fn execute_for_game<'cs, 'igr, 'ig: 'igr>(
       .filter(|(_,ipr)| ipr.ipl.acctid == acctid)
       .next();
     if let Some(gpl) = ig.gs.players.get(player);
-    then { Html(format!("{} [{}]",
-                        &htmlescape::encode_minimal(&gpl.nick),
-                        &account)) }
-    else { Html(format!("[{}]",
-                        &account)) }
+    then { hformat!("{} [{}]",
+                        gpl.nick,
+                        account) }
+    else { hformat!("[{}]",
+                        account) }
   };
   let res = (||{
     for insn in insns.drain(0..) {
@@ -983,7 +981,7 @@ impl UpdateHandler {
 
         if bulk.logs {
           buf.log_updates(vec![LogEntry {
-            html: Html(format!("{} (re)configured the game", &who.0)),
+            html: hformat!("{} (re)configured the game", who),
           }]);
         }
 
index a6c7b54a2479c96f1371b031308fdbda5551bc3e..29e1d3152039825e3a0b16cc9f328d692f9fac66 100644 (file)
@@ -174,10 +174,10 @@ fn session_inner(form: Json<SessionForm>,
         l.into_iter()
           .map(|(trk,trv)|{
             let when = trv.latest;
-            let html = Html(format!(
+            let html = hformat!(
               "player state accessed via {} [{}]",
-              &trk.desc.0, &trk.account
-            ));
+              trk.desc, trk.account
+            );
             Arc::new(CommittedLogEntry { when, logent: LogEntry { html } })
           })
       },
index a5f211419cbe32f4f75d8b259724ee9f61042ee7..a55b2662309a14892e758c8c4eac1ad53ba1d5eb 100644 (file)
@@ -200,6 +200,7 @@ impl Display for AccountName {
     ], |s| f.write_str(s))?
   }
 }
+hformat_as_display!{ AccountName }
 
 impl FromStr for AccountName {
   type Err = InvalidScopedName;
index 3c2f674bd3721c665925efe5a2f79b1c57b6bd37..f04f25e1e2e9e4d52978b2347f158127ddaa1d83 100644 (file)
@@ -127,13 +127,22 @@ fn preview(items: Vec<ItemForOutput>) {
   println!(r#"<table rules="all">"#);
   for s in &pieces {
     let Prep { spec, p, uos, bbox, size } = s;
+
+    macro_rules! println {
+      ($($x:tt)*) => ({
+        let mut s = Html::new();
+        hwrite!(&mut s, $($x)*).unwrap();
+        std::println!("{}", &s.as_html_str());
+      })
+    }
+
     println!(r#"<tr>"#);
     println!(r#"<th align="left"><kbd>{}</kbd></th>"#,
-             Html::from_txt(&spec.lib).0);
+             Html::from_txt(&spec.lib));
     println!(r#"<th align="left"><kbd>{}</kbd></th>"#,
-             Html::from_txt(&spec.item).0);
+             Html::from_txt(&spec.item));
     println!(r#"<th align="left">{}</th>"#,
-             p.describe_html(&GPiece::dummy(), &default())?.0);
+             p.describe_html(&GPiece::dummy(), &default())?);
     let only1 = s.face_cols() == 1;
 
     for facecol in 0..(if only1 { 1 } else { max_facecols }) {
@@ -169,22 +178,24 @@ fn preview(items: Vec<ItemForOutput>) {
                &viewport, wh[0], wh[1]);
         if inseveral == 1 {
           let dasharray = player_num_dasharray(1.try_into().unwrap());
-          print!(r#"<path d="{}" stroke-dasharray="{}"
+          println!(r#"<path d="{}" stroke-dasharray="{}"
                           fill="none" stroke="{}" />"#,
-                 &surround.0, &dasharray.0, HELD_SURROUND_COLOUR);
+                 &surround, &dasharray, HELD_SURROUND_COLOUR);
         }
-        let mut html = Html("".into());
+        let mut html = Html::lit("").into();
         let gpc = GPiece { face: face.into(), ..GPiece::dummy() };
         p.svg_piece(&mut html, &gpc, &GameState::dummy(), default())?;
-        println!("{}</svg>", html.0);
+        println!("{}</svg>", html);
       }
       println!("</td>");
 
       if max_uos > 0 {
         println!(r#"<td>{}</td>"#,
                  uos.iter()
-                 .map(|uo| Html::from_txt(uo).0)
-                 .join(" "));
+                 .map(|uo| Html::from_txt(uo))
+                 .collect::<Vec<Html>>()
+                 .iter()
+                 .hjoin(&Html::lit(" ")));
       }
     };
     println!("</tr>");
index 070889dfc960a349e547f6c7b9b9724f439dc632..f918a26dee06d48d23bb25ffe88ce931e71b8a78 100644 (file)
@@ -142,6 +142,7 @@ impl fmt::Debug for User {
     write!(f, "User({})", self)
   }
 }
+hformat_as_display!{User}
 
 #[derive(Debug,Clone,Copy,Error,Serialize,Deserialize)]
 struct BadClockUserError;
@@ -439,7 +440,6 @@ impl PieceTrait for Clock {
   #[throws(IE)]
   fn svg_piece(&self, f: &mut Html, gpc: &GPiece, gs: &GameState,
                vpid: VisiblePieceId) {
-    let f = &mut f.0;
     let state = gpc.xdata()?
       .ok_or_else(|| internal_logic_error("missing/wrong xdata"))?;
     let urenders = self.urender(&state, gpc.held, &gs.players);
@@ -470,20 +470,20 @@ impl PieceTrait for Clock {
       }
     }
     
-    write!(f, r##"
+    hwrite!(f, r##"
 <g transform="translate(-20,-7)">"##,
     )?;
     for (y, u) in izip!(Y.iter(), urenders.iter()) {
-      write!(f, r##"
+      hwrite!(f, r##"
   <rect y="{}" fill="{}" width="40" height="7"/>"##,
-             y,
-             u.st.show().background,
+              y,
+              Html::lit(u.st.show().background),
       )?;
     }
-    write!(f, r##"
+    hwrite!(f, r##"
   <rect fill="none" stroke="black" width="40" height="14"></rect>
   <clipPath id="def.{}.cl"><rect width="40" height="14"></rect></clipPath>"##,
-           vpid
+            &vpid
     )?;
     for (user, y, u) in izip!(USERS.iter(), Y.iter(), urenders.iter()) {
       let y = y + 6.;
@@ -491,49 +491,51 @@ impl PieceTrait for Clock {
       let mins = u.remaining.tv_sec() / 60;
       let secs = u.remaining.tv_sec() % 60;
       let mins = mins.to_string();
-      let mins_pad = iter::repeat("&nbsp;").take(3 - mins.len())
-        .collect::<String>();
+      let mins_pad = Html::from_html_string(
+        iter::repeat("&nbsp;").take(3 - mins.len())
+          .collect()
+      );
 
-      let pointer = r##"
+      let pointer = Html::lit(r##"
   pointer-events="none"
-             "##;
-      let font = r##"
+             "##);
+      let font = Html::lit(r##"
   font-family="Latin Modern Mono, monospace" font-size="6" font-weight="700"
-             "##;
-      write!(f, r##"
+             "##);
+      hwrite!(f, r##"
   <text x="1" y="{}" {} {} fill="{}" >{}{}{}</text>"##,
-             y, font, pointer, show.text,
-             mins_pad, mins, show.sigil
+             y, font, pointer, Html::lit(show.text),
+             mins_pad, HtmlStr::from_html_str(&mins), Html::lit(show.sigil)
       )?;
-      write!(f, r##"
+      hwrite!(f, r##"
   <text x="14" y="{}" {} {} fill="{}" >{:02}</text>"##,
-             y, font, pointer, show.text,
+             y, font, pointer, Html::lit(show.text),
              secs
       )?;
       let nick_y = y - 0.5;
       if let Some(nick) = u.nick {
-        write!(f, r##"
+        hwrite!(f, r##"
   <text x="21" y="{}" {} clip-path="url(#def.{}.cl)" font-size="4">{}</text>
               "##,
                nick_y, pointer,
                vpid,
-               htmlescape::encode_minimal(nick),
+               nick,
         )?;
       } else {
-        write!(f, r##"
+        hwrite!(f, r##"
   <text x="27" y="{}" fill="pink" stroke="red" {}
    stroke-width="0.1" font-size="4">({})</text>"##,
                nick_y, pointer, user,
         )?;
       }
     }
-    write!(f, r##"
+    hwrite!(f, r##"
 </g>"##)?;
   }
 
   #[throws(IE)]
   fn describe_html(&self, _gpc: &GPiece, _goccults: &GameOccults) -> Html {
-    Html::lit("the chess clock")
+    Html::lit("the chess clock").into()
   }
 
   #[throws(InternalError)]
@@ -542,8 +544,11 @@ impl PieceTrait for Clock {
     let state: &State = gpc.xdata_exp()?;
 
     let for_users = || izip!(&USERS, &USERINFOS, &state.users).map(
-      |(&user, userinfo, ust)| (user, userinfo, ust,
-                                userinfo.idchar.to_ascii_uppercase())
+      |(&user, userinfo, ust)| {
+        let upchar = userinfo.idchar.to_ascii_uppercase();
+        let upchar = IsHtmlFormatted(upchar);
+        (user, userinfo, ust, upchar)
+      }
     );
 
     for (user, userinfo, _ust, upchar) in for_users() {
@@ -552,11 +557,11 @@ impl PieceTrait for Clock {
           kind: UoKind::Piece,
           def_key: userinfo.idchar,
           opname: format!("start-{}", userinfo.idchar),
-          desc: Html(if state.current.is_none() {
-            format!("Start, with player {}", &upchar)
+          desc: if state.current.is_none() {
+            hformat!("Start, with player {}", &upchar)
           } else {
-            format!("Make player {} current", &upchar)
-          }),
+            hformat!("Make player {} current", &upchar)
+          },
           wrc: WRC::Predictable,
         });
       }
@@ -567,7 +572,7 @@ impl PieceTrait for Clock {
         kind: UoKind::Piece,
         def_key: 'S',
         opname: "stop".to_string(),
-        desc: Html::lit("Stop"),
+        desc: Html::lit("Stop").into(),
         wrc: WRC::Predictable,
       });
     }
@@ -576,7 +581,7 @@ impl PieceTrait for Clock {
         kind: UoKind::Piece,
         def_key: 'R',
         opname: "reset".to_string(),
-        desc: Html::lit("Reset"),
+        desc: Html::lit("Reset").into(),
         wrc: WRC::Unpredictable,
       });
     }
@@ -585,17 +590,17 @@ impl PieceTrait for Clock {
       if let Some(_gpl) = gs.players.get(ust.player) {
         upd.push(UoDescription {
           kind: UoKind::Piece,
-          def_key: upchar,
+          def_key: upchar.0,
           opname: format!("unclaim-{}", userinfo.idchar),
-          desc: Html(format!("Clear player {}", &upchar)),
+          desc: hformat!("Clear player {}", &upchar),
           wrc: WRC::Unpredictable,
         });
       } else {
         upd.push(UoDescription {
           kind: UoKind::Piece,
-          def_key: upchar,
+          def_key: upchar.0,
           opname: format!("claim-{}", userinfo.idchar),
-          desc: Html(format!("Become player {}", &upchar)),
+          desc: hformat!("Become player {}", &upchar),
           wrc: WRC::Unpredictable,
         });
       }
@@ -683,7 +688,7 @@ impl PieceTrait for Clock {
     let log = log_did_to_piece(ioccults,&gs.occults, gpl, gpc, ipc, &did)
       .unwrap_or_else(|e| {
         error!("failed to log: {:?}", &e);
-        vec![LogEntry { html: Html::lit("&lt;failed to log&gt;") }]
+        vec![LogEntry { html: "<failed to log>".to_html() }]
       });
 
     gpc.moveable = moveable;
index 86c3d9df6541aaa76afb9bd00b7ebe88e60739f8..e5e7e6f1ab84786f688acfd6cc216c9299488460 100644 (file)
@@ -4,10 +4,10 @@
 
 use crate::prelude::*;
 
-pub const CORE_DESC: &str = "a play/pickup deck";
-pub const DISABLED_DESC: &str = "a play/pickup deck (disabled)";
-pub const COUNTING_DESC: &str = "a play pile (counting)";
-pub const ENABLED_DESC: &str = "a pickup deck (enabled)";
+pub const CORE_DESC    : HtmlLit = Html::lit("a play/pickup deck");
+pub const DISABLED_DESC: HtmlLit = Html::lit("a play/pickup deck (disabled)");
+pub const COUNTING_DESC: HtmlLit = Html::lit("a play pile (counting)");
+pub const ENABLED_DESC : HtmlLit = Html::lit("a pickup deck (enabled)");
 
 #[derive(Debug,Serialize,Deserialize)]
 struct Deck {
@@ -102,20 +102,18 @@ impl PieceTrait for Deck {
         label.svg(f,
                   &self.shape.outline,
                   self.shape.edges.get(0), 
-                  &Html(count.to_string()))?;
+                  &count.to_html())?;
       }
     }
   }
 
   #[throws(IE)]
   fn describe_html(&self, gpc: &GPiece, goccults: &GameOccults) -> Html {
-    Html::lit(
-      match self.state(gpc, goccults)? {
-        Disabled => DISABLED_DESC,
-        Counting => ENABLED_DESC,
-        Enabled => ENABLED_DESC,
-      }
-    )
+    match self.state(gpc, goccults)? {
+      Disabled => DISABLED_DESC,
+      Counting => ENABLED_DESC,
+      Enabled => ENABLED_DESC,
+    }.into()
   }
 
   delegate!{
@@ -133,7 +131,7 @@ impl PieceTrait for Deck {
         kind: UoKind::Piece,
         def_key: 'A',
         opname: "activate".to_owned(),
-        desc: Html::lit("Enable pickup deck"),
+        desc: Html::lit("Enable pickup deck").into(),
         wrc: WRC::Unpredictable,
       });
     }
@@ -142,7 +140,7 @@ impl PieceTrait for Deck {
         kind: UoKind::Piece,
         def_key: 'C',
         opname: "counting".to_owned(),
-        desc: Html::lit("Make into counting play pile"),
+        desc: Html::lit("Make into counting play pile").into(),
         wrc: WRC::Unpredictable,
       });
     }
@@ -151,7 +149,7 @@ impl PieceTrait for Deck {
         kind: UoKind::Piece,
         def_key: 'S',
         opname: "deactivate".to_owned(),
-        desc: Html::lit("Disable pickup deck"),
+        desc: Html::lit("Disable pickup deck").into(),
         wrc: WRC::Unpredictable,
       });
     }
@@ -170,7 +168,7 @@ impl PieceTrait for Deck {
     let gpc = gpieces.byid_mut(piece)?;
 
     let gpl = gplayers.byid_mut(player)?;
-    let nick = Html(htmlescape::encode_minimal(&gpl.nick));
+    let nick = gpl.nick.to_html();
 
     dbgc!("ui op k entry", &opname);
     
@@ -180,9 +178,9 @@ impl PieceTrait for Deck {
     let old_state = self.state(gpc, &goccults).map_err(err_via_response)?;
   
     let (new_state, did) = match opname {
-      "activate"   => (Enabled,  format!("enabled {}",         CORE_DESC)),
-      "counting"   => (Counting, format!("set {} to counting", CORE_DESC)),
-      "deactivate" => (Disabled, format!("disabled {}",        CORE_DESC)),
+      "activate"   => (Enabled,  hformat!("enabled {}",         CORE_DESC)),
+      "counting"   => (Counting, hformat!("set {} to counting", CORE_DESC)),
+      "deactivate" => (Disabled, hformat!("disabled {}",        CORE_DESC)),
       _ => throw!(OE::BadOperation),
     };
   
@@ -228,7 +226,7 @@ impl PieceTrait for Deck {
       dbgc!("creating occ done", &xupdates);
     }
 
-    let log = vec![ LogEntry { html: Html(format!("{} {}", nick.0, did)) }];
+    let log = vec![ LogEntry { html: hformat!("{} {}", nick, did) }];
 
     dbgc!("ui op k did main");
     
index d6a9cb2658a2133f1b4632e9da00aefb97e6a1cf..a1c73509623fb005afa0f6ada564738389a53dd6 100644 (file)
@@ -19,10 +19,6 @@ pub struct Generation(pub u64);
 
 visible_slotmap_key!{ VisiblePieceId(b'.') }
 
-#[derive(Clone,Serialize,Deserialize,Hash,Eq,Ord,PartialEq,PartialOrd)]
-#[serde(transparent)]
-pub struct Html(pub String);
-
 #[derive(Copy,Clone,Debug,Serialize,Deserialize,Eq,Ord,PartialEq,PartialOrd)]
 #[serde(transparent)]
 pub struct Timestamp(pub u64); /* time_t */
@@ -113,6 +109,7 @@ mod item_name {
     #[throws(fmt::Error)]
     fn fmt(&self, f: &mut fmt::Formatter) { f.write_str(&self.0)? }
   }
+  hformat_as_display!{ GoodItemName }
 
   impl TryFrom<String> for GoodItemName {
     type Error = LibraryLoadError;
@@ -327,25 +324,6 @@ impl ClampTable for Pos {
   }
 }
 
-impl Html {
-  // todo convert to display_as but I need to write display_as::typed::Is
-  pub fn lit(s: &str) -> Self { Html(s.to_owned()) }
-  pub fn from_txt(s: &str) -> Self {
-    Html(htmlescape::encode_minimal(&s))
-  }
-}
-
-impl Debug for Html {
-  fn fmt(&self, f: &mut Formatter) -> fmt::Result {
-    const MAX: usize = 23;
-    if self.0.len() < MAX {
-      write!(f, "<{}>", &self.0)
-    } else {
-      write!(f, "<{}>...", &self.0[0..MAX-3])
-    }
-  }
-}
-
 // ---------- game state - rendering etc. ----------
 
 impl GPiece {
@@ -457,7 +435,7 @@ fn xdata_get_mut_inner<
 
 impl GameState {
   pub fn dummy() -> Self { GameState {
-    table_colour: Html::lit("green"),
+    table_colour: Html::lit("green").into(),
     table_size: PosC::new(300,200),
     pieces: default(),
     gen: Generation(0),
@@ -488,7 +466,7 @@ impl GameState {
       let front = self.log.front_mut().unwrap();
       let front = &mut front.1;
       let logent = LogEntry {
-        html: Html::lit("[Earlier log entries expired]"),
+        html: Html::lit("[Earlier log entries expired]").into(),
       };
       *front = Arc::new(CommittedLogEntry { logent, when: front.when });
     }
index 488ca3360aba6f7313fb09a98ae5469ec62bba81..986520491e9c2cb164f34c03339d40f12fc593d8 100644 (file)
@@ -471,8 +471,10 @@ impl Instance {
       })
     }).collect::<Vec<_>>();
     let render = RenderContext { players };
-    let html = Html(nwtemplates::render("player-info-pane.tera", &render)
-      .context("render player info template")?);
+    let html = Html::from_html_string(
+      nwtemplates::render("player-info-pane.tera", &render)
+        .context("render player info template")?
+    );
     html
   }
 }
@@ -518,13 +520,12 @@ impl Display for InstanceName {
   }
 }
 
-fn link_a_href<K:Display>(k: &K, v: &str) -> Html {
-  let url = htmlescape::encode_minimal(v);
-  Html(format!("<a href={url}>{kind}</a>", url=url, kind=k))
+fn link_a_href(k: &HtmlStr, v: &str) -> Html {
+  hformat!("<a href={}>{}</a>", v, k)
 }
 impl From<(LinkKind, &str)> for Html {
   fn from((k, v): (LinkKind, &str)) -> Html {
-    link_a_href(&k, v)
+    link_a_href(&k.to_html(), v)
   }
 }
 
@@ -533,14 +534,16 @@ impl From<&LinksTable> for Html {
     let mut s = links.iter()
       .filter_map(|(k,v)| {
         let v = v.as_ref()?;
-        Some(Html::from((k, v.as_str())).0)
+        Some(Html::from((k, v.as_str())))
       })
       .chain(iter::once(
-        link_a_href(&"Shapelib", "/_/shapelib.html").0
+        link_a_href(Html::lit("Shapelib").into(), "/_/shapelib.html")
       ))
-      .join(" ");
-    if s.len() != 0 { s = format!("links: {}", s) }
-    Html(s)
+      .collect::<Vec<Html>>()
+      .iter()
+      .hjoin(&Html::lit(" "));
+    if s.len() != 0 { s = hformat!("links: {}", s) }
+    s
   }
 }
 
index e35be6376ea81c64148530d0ba3cb26cea9b6664..3bdf39ba0665e9deffec58cf42eab0ba43d21213 100644 (file)
@@ -4,7 +4,7 @@
 
 use crate::prelude::*;
 
-pub const UNCLAIMED_DESC: &str = "a hand repository";
+pub const UNCLAIMED_DESC: HtmlLit = Html::lit("a hand repository");
 
 #[derive(Debug,Clone,Serialize,Deserialize)]
 struct MagicOwner {
@@ -81,7 +81,7 @@ impl Hand {
       if let Some(xdata) = xdata;
       if let Some(owner) = &xdata.owner;
       then { owner.desc.clone() }
-      else { Html(UNCLAIMED_DESC.into()) }
+      else { UNCLAIMED_DESC.into() }
     }
   }
 }
@@ -98,9 +98,9 @@ impl PieceTrait for Hand {
       then { Some(owned) }
       else { None }
     };
-    self.shape.svg_piece_raw(f, gpc.face, &mut |f: &mut String| {
+    self.shape.svg_piece_raw(f, gpc.face, &mut |f: &mut Html| {
       if let Some(owned) = owned {
-        write!(f, r##" stroke-dasharray="{}" "##, &owned.dasharray.0)?;
+        hwrite!(f, r##" stroke-dasharray="{}" "##, &owned.dasharray)?;
       }
       Ok(())
     })?;
@@ -112,7 +112,7 @@ impl PieceTrait for Hand {
         label.svg(f,
                   &self.shape.outline,
                   self.shape.edges.get(0), 
-                  &Html(htmlescape::encode_minimal(&gpl.nick)))?;
+                  &gpl.nick.to_html())?
       }
     }
   }
@@ -139,14 +139,14 @@ impl PieceTrait for Hand {
         kind: UoKind::Piece,
         def_key: 'C',
         opname: "deactivate".to_owned(),
-        desc: Html::lit("Deactivate hand"),
+        desc: Html::lit("Deactivate hand").into(),
         wrc: WRC::Unpredictable,
       }}
       else { UoDescription {
         kind: UoKind::Piece,
         def_key: 'C',
         opname: "claim".to_owned(),
-        desc: Html::lit("Claim this as your hand"),
+        desc: Html::lit("Claim this as your hand").into(),
         wrc: WRC::Unpredictable,
       }}
     })
@@ -170,7 +170,7 @@ impl PieceTrait for Hand {
 
     let dasharray = player_dasharray(gplayers, player);
     let gpl = gplayers.byid_mut(player)?;
-    let nick = Html(htmlescape::encode_minimal(&gpl.nick));
+    let nick = gpl.nick.to_html();
 
     dbgc!("ui op k entry", &opname, &xdata);
 
@@ -179,7 +179,7 @@ impl PieceTrait for Hand {
     {
       ("claim", false) => {
         dbgc!("claiming");
-        let new_desc = Html(format!("{}'s hand", &nick.0));
+        let new_desc = hformat!("{}'s hand", &nick);
         let new_owner = Some(MagicOwner {
           player,
           dasharray,
@@ -208,7 +208,7 @@ impl PieceTrait for Hand {
                              region, piece, views)?;
 
         dbgc!("creating occ done", &new_owner, &xupdates);
-        (new_owner, xupdates, format!("claimed {}", &old_desc.0))
+        (new_owner, xupdates, hformat!("claimed {}", &old_desc))
       }
       ("deactivate", true) => {
         let xupdates =
@@ -217,7 +217,7 @@ impl PieceTrait for Hand {
                              to_recalculate, piece)
           .map_err(|ie| ApiPieceOpError::ReportViaResponse(ie.into()))?;
 
-        (None, xupdates, format!("deactivated {}", &old_desc.0))
+        (None, xupdates, hformat!("deactivated {}", &old_desc))
       }
       ("claim", true) |
       ("deactivate", false) => {
@@ -228,7 +228,7 @@ impl PieceTrait for Hand {
       }
     };
 
-    let log = vec![ LogEntry { html: Html(format!("{} {}", nick.0, did)) }];
+    let log = vec![ LogEntry { html: hformat!("{} {}", nick, did) }];
 
     dbgc!("ui op k did main");
     
index 23372f3315d66a6009ab4d59df286b902bcd98a9..7f262023fdd3c062a07bbda8d3da8b5654adf376 100644 (file)
@@ -686,17 +686,17 @@ pub fn recalculate_occultation_piece(
       to_recalculate, piece,
       || (vanilla_wrc, vanilla_op, vanilla_log).into(),
       vec![],
-      |old, new, Html(show)| vec![ LogEntry { html: Html(format!(
+      |old, new, show| vec![ LogEntry { html: hformat!(
         "{} {}",
-        &who_by.0,
+        &who_by,
         match (old, new) {
-          (None, None) => format!("modified {} somehow", show),
-          (Some(old), None) => format!("produced {} from {}", show, &old.0),
-          (None, Some(new)) => format!("placed {} into {}", show, &new.0),
-          (Some(old), Some(new)) => format!("moved {} from {} to {}",
-                                            show, &old.0, &new.0),
-        },
-      ))}],
+          (None, None) => hformat!("modified {} somehow", show),
+          (Some(old), None) => hformat!("produced {} from {}", show, old),
+          (None, Some(new)) => hformat!("placed {} into {}", show, new),
+          (Some(old), Some(new)) => hformat!("moved {} from {} to {}",
+                                            show, old, new),
+        }
+      )}],
       |puos, log| PieceUpdate {
         wrc: WRC::Unpredictable,
         ops: PieceUpdateOps::PerPlayer(puos),
index 16569b66247861e90462498d665e233e05fcdc8d..7fd360cd70eb6ff46469400341535317c1359e7e 100644 (file)
@@ -53,6 +53,7 @@ macro_rules! visible_slotmap_key {
     pub struct $x(pub slotmap::KeyData);
 
     impl_for_slotmap_key!($x($sep));
+    hformat_as_display!{$x}
   }
 }
 #[macro_export]
index 5ddbdc100633de61338e6f65ee8cb4a88803cc99..a15a6b3cfa7276d6ec34bf8aa5f03325a1bb7c82 100644 (file)
@@ -18,6 +18,7 @@ pub mod debugreader;
 pub mod error;
 pub mod gamestate;
 pub mod global;
+pub mod html;
 pub mod hand;
 pub mod hidden;
 pub mod keydata;
index ce196c2c7c71f5683fd311705f2104e670813296..f5c889a07e16a68d8cfa085677e5e3e08020598d 100644 (file)
@@ -9,7 +9,7 @@ pub type VisiblePieceAngle = PieceAngle;
 #[derive(Clone,Debug)]
 #[derive(Serialize,Deserialize)]
 #[serde(transparent)]
-pub struct VisibleAngleTransform(String);
+pub struct VisibleAngleTransform(Html);
 
 #[derive(Debug,Clone)]
 pub struct PieceRenderInstructions {
@@ -29,9 +29,10 @@ pub type PriOcculted = PriOccultedGeneral<Pos, ZLevel>;
 
 impl VisiblePieceAngle {
   pub fn to_transform(self) -> VisibleAngleTransform {
-    VisibleAngleTransform(base_misc::raw_angle_transform(
-      self.to_compass().into()
-    ))
+    VisibleAngleTransform(Html::from_html_string(
+      base_misc::raw_angle_transform(
+        self.to_compass().into()
+      )))
   }
 
   pub fn to_compass(self) -> CompassAngle {
@@ -63,7 +64,7 @@ impl<P,Z> PriOccultedGeneral<P,Z> {
     self.describe_fallible(ioccults, goccults, gpc, ipc)
       .unwrap_or_else(|e| {
         error!("error describing piece: {:?}", e);
-        Html::lit("&lt;internal error describing piece&gt;")
+        "<internal error describing piece>".to_html()
       })
   }
 
@@ -184,8 +185,8 @@ impl PieceRenderInstructions {
 
     let transform = angle.to_transform();
 
-    let mut defs = Html(String::new());
-    write!(&mut defs.0,
+    let mut defs = Html::new();
+    hwrite!(&mut defs,
            r##"<g id="piece{}" transform="{}" data-dragraise="{}">"##,
            pri.vpid, &transform.0, dragraise)?;
 
@@ -198,10 +199,10 @@ impl PieceRenderInstructions {
       },
     };
 
-    write!(&mut defs.0, r##"</g>"##)?;
-    write!(&mut defs.0,
+    hwrite!(&mut defs, r##"</g>"##)?;
+    hwrite!(&mut defs,
            r##"<path id="surround{}" d="{}"/>"##,
-           pri.vpid, o.surround_path()?.0)?;
+           pri.vpid, o.surround_path()?)?;
     defs
   }
 
@@ -223,7 +224,7 @@ impl PieceRenderInstructions {
         kind: UoKind::Global,
         def_key: 'f'.into(),
         opname: "flip".to_string(),
-        desc: Html::lit("flip"),
+        desc: Html::lit("flip").into(),
       })
     }
     ipc.show(y).add_ui_operations(&mut out, gs, gpc)?;
index b1546741f79852ac6812bda1b65b71f58c243845..c87f366613540b552841e7273b852c794352ef0b 100644 (file)
@@ -46,7 +46,7 @@ impl From<SVGProcessingError> for MgmtError {
 }
 
 #[throws(SvgE)]
-pub fn svg_rescale_path(input: &Html, scale: f64) -> Html {
+pub fn svg_rescale_path(input: &HtmlStr, scale: f64) -> Html {
   type BM = u64;
   type BI = u32;
   #[derive(Debug,Copy,Clone)]
@@ -71,7 +71,7 @@ pub fn svg_rescale_path(input: &Html, scale: f64) -> Html {
   let mut map = ALWAYS_MAP;
   let mut first = iter::once(());
 
-  for w in input.0.split_ascii_whitespace() {
+  for w in input.as_html_str().split_ascii_whitespace() {
     if first.next().is_none() { write!(&mut out, " ")?; }
     match w {
       "L" | "l" | "M" | "m" |
@@ -91,7 +91,7 @@ pub fn svg_rescale_path(input: &Html, scale: f64) -> Html {
   }
 
   trace!("rescaled by {}: {:?} as {:?}", scale, input, &out);
-  Html(out)
+  Html::from_html_string(out)
 }
 
 #[throws(SvgE)]
@@ -101,14 +101,14 @@ pub fn svg_circle_path(diam: f64) -> Html {
      a 1 1 0 1 0 0  2  z"
   );
   let scale = diam * 0.5;
-  let path = svg_rescale_path(&unit_path, scale)?;
+  let path = svg_rescale_path(unit_path.into(), scale)?;
   path
 }
 
 #[throws(SvgE)]
 pub fn svg_rectangle_path(PosC{coords: [x,y]}: PosC<f64>) -> Html {
-  Html(format!("M {} {} h {} v {} h {} z",
-               -x*0.5, -y*0.5, x, y, -x))
+  hformat!("M {} {} h {} v {} h {} z",
+           -x*0.5, -y*0.5, x, y, -x)
 }
 
 #[dyn_upcast]
@@ -133,14 +133,19 @@ impl PieceTrait for SimpleShape {
                _gs: &GameState, _vpid: VisiblePieceId) {
     self.svg_piece_raw(f, gpc.face, &mut |_|Ok(()))?;
   }
-  #[throws(IE)]
-  fn describe_html(&self, gpc: &GPiece, _goccults: &GameOccults) -> Html {
-    Html(if_chain! {
+//  #[throws(IE)]
+  fn describe_html(&self, gpc: &GPiece, _goccults: &GameOccults)
+//                   -> Html
+-> Result<Html, IE>
+  {
+Ok::<_,IE>(
+    if_chain! {
       if let face = gpc.face;
       if let Some(colour) = self.colours.get(face);
-      then { format!("a {} {}", colour.0, self.desc.0) }
-      else { format!("a {}", self.desc.0) }
-    })
+      then { hformat!("a {} {}", colour, self.desc) }
+      else { hformat!("a {}", self.desc) }
+    }
+)
   }
   fn nfaces(&self) -> RawFaceId {
     self.count_faces().try_into().unwrap()
@@ -179,9 +184,9 @@ impl PieceLabelLoaded {
              def_colour: Option<&Colour>,
              text: &Html) {
     let colour = {
-      if let Some(c) = &self.colour { &c.0 }
-      else if let Some(c) = def_colour { &c.0 }
-      else { "black" }
+      if let Some(c) = &self.colour { c.borrow() }
+      else if let Some(c) = def_colour { c.borrow() }
+      else { Html::lit("black").into() }
     };
     let fontsz = 4.;
     let PosC{ coords: [x,y] } = {
@@ -201,9 +206,9 @@ impl PieceLabelLoaded {
       *y += 0.5 * fontsz;
       pos
     };
-    write!(f.0,
- r##"<text x="{}" y="{}" font-size="{}" fill="{}">{}</text>"##,
-               x, y, fontsz, colour, &text.0
+    hwrite!(f,
           r##"<text x="{}" y="{}" font-size="{}" fill="{}">{}</text>"##,
+            x, y, fontsz, colour, &text
     )?;
   }  
 }
@@ -261,33 +266,32 @@ impl<Desc, Outl:'static> GenericSimpleShape<Desc, Outl>
   #[throws(IE)]
   pub fn svg_piece_raw(
     &self, f: &mut Html, face: FaceId,
-    stroke_attrs_hook: &mut dyn FnMut(&mut String) -> Result<(),IE>,
+    stroke_attrs_hook: &mut dyn FnMut(&mut Html) -> Result<(),IE>,
   ) {
-    let f = &mut f.0;
-    let ef = |f: &mut String, cmap: &ColourMap, attrname: &str, otherwise| {
+    let ef = |f: &mut Html, cmap: &ColourMap, attrname: &str, otherwise| {
       if let Some(colour) = cmap.get(face) {
-        write!(f, r##" {}="{}""##, attrname, colour.0)
+        hwrite!(f, r##" {}="{}""##, attrname, colour)
       } else {
-        write!(f, "{}", otherwise)
+        hwrite!(f, "{}", otherwise)
       }
     };
     let path = self.outline_path(1.0)?;
 
     if self.colours.len() == 0 {
-      write!(f,
+      hwrite!(f,
              r##"<path fill="none" \
                   stroke-width="{}" stroke="transparent" d="{}"/>"##,
              INVISIBLE_EDGE_SENSITIVE,
-             &path.0)?;
+             &path)?;
     }
-    write!(f, r##"<path"##)?;
+    hwrite!(f, r##"<path"##)?;
     ef(f, &self.colours, "fill", r##" fill="none""##)?;
     if self.edges.len() != 0 {
-      write!(f, r##" stroke-width="{}""##, &self.edge_width)?;
+      hwrite!(f, r##" stroke-width="{}""##, &self.edge_width)?;
     }
     stroke_attrs_hook(f)?;
     ef(f, &self.edges, "stroke", "")?;
-    write!(f, r##" d="{}"/>"##, &path.0)?;
+    hwrite!(f, r##" d="{}"/>"##, &path)?;
   }
 }
 
@@ -306,7 +310,7 @@ impl SimplePieceSpec for piece_specs::Disc {
   fn load_raw(&self) -> (SimpleShape, &SimpleCommon) {
     let outline = CircleShape { diam: self.diam as f64 };
     (SimpleShape::new(
-      Html::lit("disc"),
+      Html::lit("disc").into(),
       outline.into(),
       "simple-disc",
       &self.common,
@@ -343,7 +347,7 @@ impl SimplePieceSpec for piece_specs::Rect {
     let desc = Html::lit(
       if outline.xy.x() == outline.xy.y()
       { "square" } else { "rectangle" }
-    );
+    ).into();
     (SimpleShape::new(
       desc,
       outline.into(),
index 8d98bb88403c9752f2913e1aba10f8a5656958b8..cc097b5d7f2c70a21b7d79651752101ba32e3a7e 100644 (file)
@@ -115,6 +115,7 @@ pub use crate::dbgc;
 pub use crate::{deref_to_field, deref_to_field_mut};
 pub use crate::ensure_eq;
 pub use crate::from_instance_lock_error;
+pub use crate::{hformat, hformat_as_display, hwrite};
 pub use crate::matches_doesnot;
 pub use crate::trace_dbg;
 
@@ -133,6 +134,7 @@ pub use crate::fake_rng::*;
 pub use crate::gamestate::*;
 pub use crate::global::*;
 pub use crate::hidden::*;
+pub use crate::html::*;
 pub use crate::keydata::*;
 pub use crate::mgmtchannel::*;
 pub use crate::nwtemplates;
index de5bf79bcd0d5fe191b1cb05161ed1277d34ac37..4b03ba9dbb227a8ab3f9a65888d8bbff60ef4569 100644 (file)
@@ -215,8 +215,9 @@ pub struct FileData {
   #[cfg(doc)] pub r_file_spec: String,
   #[cfg(not(doc))] pub r_file_spec: (),
 
-  /// HTML desscription.  (Shown hn the game log, for example.)
-  pub desc: Html,
+  /// Desscription.  (Shown hn the game log, for example.)
+  /// Will be HTML-escaped.
+  pub desc: String,
 }
 
 #[cfg(doc)]
index 24a26d2d92a62df7b0447ca1d7400aef47635831..4514e3bdd13cdf56589bfd2f9a8120d22e14e032 100644 (file)
@@ -200,7 +200,7 @@ pub struct ItemEnquiryData {
 
 impl ItemEnquiryData {
   pub fn line_for_list(&self) -> String {
-    format!("{:20}  {}", self.itemname.as_str(), self.f0desc.0)
+    format!("{:20}  {}", self.itemname, self.f0desc.as_html_str())
   }
 }
 
@@ -249,10 +249,10 @@ impl FaceTransform {
 
   #[throws(IE)]
   fn write_svgd(&self, f: &mut Html, svgd: &Html) {
-    write!(&mut f.0,
+    hwrite!(f,
            r##"<g transform="scale({} {}) translate({} {})">{}</g>"##,
            self.scale[0], self.scale[1], -self.centre[0], -self.centre[1],
-           svgd.0)?;
+           svgd)?;
   }
 }
 
@@ -366,7 +366,7 @@ impl Contents {
         SpE::InternalError(m.to_string())
       })?;
 
-    Html(svg_data)
+    Html::from_html_string(svg_data)
   }
 
   #[throws(SpecError)]
@@ -645,14 +645,14 @@ fn load_catalogue(libname: &str, dirname: &str, toml_path: &str) -> Contents {
           if ! group.d.colours.contains_key(colour.0.as_str()) {
             throw!(LLE::OccultationColourMissing(colour.0.clone()));
           }
-          let colour: Colour = colour.try_into()?;
           let item_name = subst(item_name.as_str(), "_c", &colour.0)?;
+          let desc = subst(&fe.desc, "_colour", "")?.to_html();
           OccData::Internal(Arc::new(OccData_Internal {
             item_name: Arc::new(item_name.try_into()?),
-            desc: Html(subst(&fe.desc.0, "_colour", "")?),
             outline: outline.clone(),
             xform: FaceTransform::from_group(&group.d)?,
             svgd: default(),
+            desc,
           }))
         },
         Some(OccultationMethod::ByBack { ilk }) => {
@@ -663,11 +663,11 @@ fn load_catalogue(libname: &str, dirname: &str, toml_path: &str) -> Contents {
         },
       };
 
-      let mut add1 = |item_name: &GoodItemName, desc: Html| {
+      let mut add1 = |item_name: &GoodItemName, desc: &str| {
         let desc = if let Some(desc_template) = &group.d.desc_template {
-          Html(subst(desc_template, "_desc", &desc.0)?)
+          subst(desc_template, "_desc", &desc)?.to_html()
         } else {
-          desc
+          desc.to_html()
         };
         let idata = ItemData {
           group: group.clone(),
@@ -691,12 +691,12 @@ fn load_catalogue(libname: &str, dirname: &str, toml_path: &str) -> Contents {
       };
 
       if group.d.colours.is_empty() {
-        add1(&item_name, fe.desc.clone())?;
+        add1(&item_name, &fe.desc.clone())?;
       } else {
         for (colour, recolourdata) in &group.d.colours {
           let t_item_name = subst(item_name.as_str(), "_c", &recolourdata.abbrev)?;
-          let t_desc = Html(subst(&fe.desc.0, "_colour", colour)?);
-          add1(&t_item_name.try_into()?, t_desc)?;
+          let t_desc = subst(&fe.desc, "_colour", colour)?;
+          add1(&t_item_name.try_into()?, &t_desc)?;
         }
 
       }
@@ -927,8 +927,8 @@ impl TryFrom<String> for FileList {
       };
       let item_spec = n()?;
       let _r_file_spec = n()?;
-      let desc = Html(remain.to_owned());
-      o.push(FileData{ item_spec, r_file_spec: (), desc  });
+      let desc = remain.to_owned();
+      o.push(FileData{ item_spec, r_file_spec: (), desc });
     }
     Ok(FileList(o))
   }
index 5e5d5dbdbc0adb70088af2a06d00df030e6949df..164193c822291cefefe2ec499c234cd24d35e3c7 100644 (file)
@@ -26,6 +26,7 @@ use otter_base::misc::display_as_debug;
 use crate::accounts::AccountName;
 use crate::error::UnsupportedColourSpec;
 use crate::gamestate::PieceSpec;
+use crate::hformat_as_display;
 use crate::prelude::default;
 
 pub use implementation::PlayerAccessSpec;
@@ -140,6 +141,7 @@ pub enum LinkKind {
   Voice,
   Info,
 }
+hformat_as_display!{LinkKind}
 
 //---------- player accesses, should perhaps be in commands.rs ----------
 
@@ -382,7 +384,7 @@ pub mod implementation {
                -> Result<AccessTokenReport, TDE>;
     fn describe_html(&self) -> Html {
       let inner = Html::from_txt(&format!("{:?}", self));
-      Html(format!("<code>{}</code>", inner.0))
+      hformat!("<code>{}</code>", inner)
     }
   }
 
@@ -550,7 +552,7 @@ pub mod implementation {
       if !RE.is_match(s) {
         throw!(UnsupportedColourSpec);
       }
-      Html(spec.0.clone())
+      Html::from_html_string(spec.0.clone())
     }
   }
 
index 94cf81f0561852ea8a17373aeb8fcd56ed18679c..9ae5e6a65be85e845307ed034d18e660a57641ae 100644 (file)
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -26,7 +26,7 @@ pub fn player_num_dasharray(player_num: NonZeroUsize) -> Html {
   }
   let spc = dasharray.pop();
   assert_eq!(spc,Some(' '));
-  Html(dasharray)
+  Html::from_html_string(dasharray)
 }
 
 pub fn player_dasharray(gplayers: &GPlayers, player: PlayerId) -> Html {
index bb1cec353277130544fa0ba89b67b9221d2bdadd..61e9509a55baee801154ad850288d16188a92d99 100644 (file)
@@ -147,7 +147,7 @@ impl<T:JsonLen> SecondarySlotMap<PlayerId, T> {
 }
 
 impl JsonLen for Html {
-  fn json_len(&self) -> usize { self.0.as_bytes().len() }
+  fn json_len(&self) -> usize { self.as_html_str().as_bytes().len() }
 }
 
 // ---------- piece updates ----------
@@ -274,7 +274,7 @@ pub fn log_did_to_piece_whoby(ioccults: &IOccults, goccults: &GameOccults,
                               gpc: &GPiece, ipc: &IPiece, did: &str)
                               -> (Vec<LogEntry>, Option<Html>)
 {
-  let who_by = Html(htmlescape::encode_minimal(&by_gpl.nick));
+  let who_by = by_gpl.nick.to_html();
   let y = gpc.fully_visible_to_everyone();
   let desc = (||{
     Ok::<_,IE>(match ipc.show_or_instead(ioccults, y)? {
@@ -283,15 +283,15 @@ pub fn log_did_to_piece_whoby(ioccults: &IOccults, goccults: &GameOccults,
     })
   })().unwrap_or_else(|e|{
     error!("failed to format during logging: {:?}", e);
-    Html::lit("&lt;internal error&gt;")
+    Html::lit("&lt;internal error&gt;").into()
   });
 
-  let log = vec![ LogEntry { html: Html(format!(
+  let log = vec![ LogEntry { html: hformat!(
     "{} {} {}",
-    &who_by.0,
+    who_by,
     did,
-    desc.0,
-  ))}];
+    desc,
+  )}];
   (log, Some(who_by))
 }
 
@@ -406,22 +406,22 @@ impl PreparedUpdateEntry {
         ims.json_len(player)
       }
       Log(logent) => {
-        logent.logent.html.0.as_bytes().len() * 28
+        logent.logent.html.json_len() * 28
       }
       AddPlayer {
         player:_,
         data: DataLoadPlayer { dasharray, nick, },
         new_info_pane,
       } => {
-        dasharray.0.as_bytes().len() +
-        nick     .0.as_bytes().len() + 100
-          + new_info_pane.0.len()
+        dasharray.json_len() +
+        nick     .json_len() + 100
+          + new_info_pane.json_len()
       }
       RemovePlayer { player:_, new_info_pane } => {
-        new_info_pane.0.len() + 100
+        new_info_pane.json_len() + 100
       }
       SetTableColour(colour) => {
-        colour.0.as_bytes().len() + 50
+        colour.json_len() + 50
       }
       SetLinks(links) => {
         links.iter().filter_map(
@@ -446,8 +446,8 @@ impl DataLoadPlayer {
     let gplayers = &ig.gs.players;
     let dasharray = player_dasharray(gplayers, player);
     let nick = gplayers.get(player).map(|gpl| gpl.nick.as_str())
-      .unwrap_or("<unknown-player>");
-    let nick = Html(htmlescape::encode_minimal(&nick));
+      .unwrap_or("<unknown-player>")
+      .to_html();
     DataLoadPlayer {
       dasharray,
       nick,
index 1abb761fceee1ea37d2df77ce78edeb6d163f45a..303176db4fd43872c8a10b04ed7d528227d20e54 100644 (file)
@@ -23,8 +23,8 @@ pub fn player_dasharray(player: &'static str) -> String {
   let (player,_) = player.get_idx_version();
   let player: usize = player.try_into().unwrap();
   let player = player.try_into().unwrap();
-  let dasharray = player_num_dasharray(player).0;
-  dasharray
+  let dasharray = player_num_dasharray(player);
+  dasharray.into_html_string()
 }
 
 impl Ctx {