Start ansi formatting utilities ready for look command.
This commit is contained in:
parent
d362c2f371
commit
1a46405a49
9
Cargo.lock
generated
9
Cargo.lock
generated
@ -32,6 +32,13 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ansi"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"ansi_macro",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ansi_macro"
|
name = "ansi_macro"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -103,7 +110,7 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|||||||
name = "blastmud_game"
|
name = "blastmud_game"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_macro",
|
"ansi",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64 0.20.0",
|
"base64 0.20.0",
|
||||||
"bcrypt",
|
"bcrypt",
|
||||||
|
@ -4,4 +4,6 @@ members = [
|
|||||||
"blastmud_listener",
|
"blastmud_listener",
|
||||||
"blastmud_interfaces",
|
"blastmud_interfaces",
|
||||||
"blastmud_game",
|
"blastmud_game",
|
||||||
|
"ansi_macro",
|
||||||
|
"ansi",
|
||||||
]
|
]
|
||||||
|
7
ansi/Cargo.toml
Normal file
7
ansi/Cargo.toml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[package]
|
||||||
|
name = "ansi"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
ansi_macro = { path = "../ansi_macro" }
|
229
ansi/src/lib.rs
Normal file
229
ansi/src/lib.rs
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
pub use ansi_macro::ansi;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
/// Removes all non-printable characters except tabs and newlines.
|
||||||
|
/// Doesn't attempt to remove printable characters as part of an
|
||||||
|
/// escape - so use this for untrusted input that you don't expect
|
||||||
|
/// to contain ansi escapes at all.
|
||||||
|
pub fn ignore_special_characters(input: &str) -> String {
|
||||||
|
input.chars().filter(|c| *c == '\t' || *c == '\n' ||
|
||||||
|
(*c >= ' ' && *c <= '~')).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
|
struct AnsiState {
|
||||||
|
col: u64,
|
||||||
|
background: u64, // 0 means default.
|
||||||
|
foreground: u64,
|
||||||
|
bold: bool,
|
||||||
|
underline: bool,
|
||||||
|
strike: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
|
struct AnsiEvent<'l> (
|
||||||
|
AnsiParseToken<'l>,
|
||||||
|
Rc<AnsiState>
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
|
enum AnsiParseToken<'l> {
|
||||||
|
Character(char),
|
||||||
|
ControlSeq(&'l str),
|
||||||
|
Newline,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emits events with only LF, spaces, tabs, and a small set of
|
||||||
|
/// character attributes (colours, bold, underline). Anything else
|
||||||
|
/// sent will be emitted as printable characters. Tabs are replaced
|
||||||
|
/// with 4 spaces.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct AnsiIterator<'l> {
|
||||||
|
underlying: std::iter::Enumerate<std::str::Chars<'l>>,
|
||||||
|
input: &'l str,
|
||||||
|
state: Rc<AnsiState>,
|
||||||
|
pending_col: bool,
|
||||||
|
inject_spaces: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl AnsiIterator<'_> {
|
||||||
|
fn new<'l>(input: &'l str) -> AnsiIterator<'l> {
|
||||||
|
AnsiIterator { underlying: input.chars().enumerate(),
|
||||||
|
input: input,
|
||||||
|
state: Rc::new(AnsiState {
|
||||||
|
col: 0,
|
||||||
|
background: 0,
|
||||||
|
foreground: 0,
|
||||||
|
bold: false,
|
||||||
|
underline: false,
|
||||||
|
strike: false
|
||||||
|
}),
|
||||||
|
pending_col: false,
|
||||||
|
inject_spaces: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <'l>Iterator for AnsiIterator<'l> {
|
||||||
|
type Item = AnsiEvent<'l>;
|
||||||
|
|
||||||
|
fn next(self: &mut Self) -> Option<AnsiEvent<'l>> {
|
||||||
|
if self.pending_col {
|
||||||
|
Rc::make_mut(&mut self.state).col += 1;
|
||||||
|
self.pending_col = false;
|
||||||
|
}
|
||||||
|
if self.inject_spaces > 0 {
|
||||||
|
self.pending_col = true;
|
||||||
|
self.inject_spaces -= 1;
|
||||||
|
return Some(AnsiEvent::<'l>(AnsiParseToken::Character(' '), self.state.clone()));
|
||||||
|
}
|
||||||
|
while let Some((i0, c)) = self.underlying.next() {
|
||||||
|
if c == '\n' {
|
||||||
|
Rc::make_mut(&mut self.state).col = 0;
|
||||||
|
return Some(AnsiEvent::<'l>(AnsiParseToken::Newline, self.state.clone()));
|
||||||
|
} else if c == '\t' {
|
||||||
|
for _ in 0..4 {
|
||||||
|
self.pending_col = true;
|
||||||
|
self.inject_spaces = 3;
|
||||||
|
return Some(AnsiEvent::<'l>(AnsiParseToken::Character(' '), self.state.clone()));
|
||||||
|
}
|
||||||
|
} else if c >= ' ' && c <= '~' {
|
||||||
|
self.pending_col = true;
|
||||||
|
return Some(AnsiEvent::<'l>(AnsiParseToken::Character(c), self.state.clone()));
|
||||||
|
} else if c == '\x1b' {
|
||||||
|
if let Some((_, c2)) = self.underlying.next() {
|
||||||
|
if c2 != '[' {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some((_, cs1)) = self.underlying.next() {
|
||||||
|
let mut imax = i0;
|
||||||
|
let mut cs_no: i64 = cs1 as i64 - b'0' as i64;
|
||||||
|
if cs_no < 0 || cs_no > 9 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some((i2, cs2)) = self.underlying.next() {
|
||||||
|
let cs_no2: i64 = cs2 as i64 - b'0' as i64;
|
||||||
|
if cs_no2 >= 0 && cs_no2 <= 9 {
|
||||||
|
if let Some((i3, cs3)) = self.underlying.next() {
|
||||||
|
if cs3 == 'm' {
|
||||||
|
cs_no *= 10;
|
||||||
|
cs_no += cs_no2;
|
||||||
|
imax = i3;
|
||||||
|
} else { continue; }
|
||||||
|
}
|
||||||
|
} else if cs2 != 'm' {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
imax = i2;
|
||||||
|
}
|
||||||
|
let st = Rc::make_mut(&mut self.state);
|
||||||
|
match cs_no {
|
||||||
|
0 => {
|
||||||
|
st.background = 0;
|
||||||
|
st.foreground = 0;
|
||||||
|
st.bold = false;
|
||||||
|
st.underline = false;
|
||||||
|
st.strike = false;
|
||||||
|
}
|
||||||
|
1 => { st.bold = true; }
|
||||||
|
4 => { st.underline = true; }
|
||||||
|
9 => { st.strike = true; }
|
||||||
|
24 => { st.underline = false; }
|
||||||
|
i if i >= 30 && i <= 37 => {
|
||||||
|
st.foreground = i as u64 - 29;
|
||||||
|
}
|
||||||
|
i if i >= 40 && i <= 47 => {
|
||||||
|
st.foreground = i as u64 - 39;
|
||||||
|
}
|
||||||
|
_ => continue
|
||||||
|
}
|
||||||
|
drop(st);
|
||||||
|
return Some(AnsiEvent::<'l>(
|
||||||
|
AnsiParseToken::ControlSeq(
|
||||||
|
&self.input[i0..(imax + 1)]
|
||||||
|
), self.state.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Strips out basic colours / character formatting codes cleanly. Tabs are
|
||||||
|
/// changed to spaces, and newlines are preserved. All other ANSI non-printables
|
||||||
|
/// are stripped but might display incorrectly.
|
||||||
|
pub fn strip_special_characters(input: &str) -> String {
|
||||||
|
let mut buf: String = String::new();
|
||||||
|
let it = AnsiIterator::new(input);
|
||||||
|
for AnsiEvent(e, _) in it {
|
||||||
|
match e {
|
||||||
|
AnsiParseToken::Character(c) => buf.push(c),
|
||||||
|
AnsiParseToken::Newline => buf.push('\n'),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allows basic colours / character formatting codes. Tabs are
|
||||||
|
/// changed to spaces, and newlines are preserved. All other ANSI non-printables
|
||||||
|
/// are stripped but might display incorrectly.
|
||||||
|
pub fn limit_special_characters(input: &str) -> String {
|
||||||
|
let mut buf: String = String::new();
|
||||||
|
let it = AnsiIterator::new(input);
|
||||||
|
for AnsiEvent(e, _) in it {
|
||||||
|
match e {
|
||||||
|
AnsiParseToken::Character(c) => buf.push(c),
|
||||||
|
AnsiParseToken::Newline => buf.push('\n'),
|
||||||
|
AnsiParseToken::ControlSeq(t) => buf.push_str(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Flows a second column around a first column, limiting the width of both
|
||||||
|
/// columns as specified, and adding a gutter.
|
||||||
|
pub fn flow_around(col1: &str, col1_width: u64, gutter: &str,
|
||||||
|
col2: &str, col2_width: u64) -> String {
|
||||||
|
"not yet".to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ignore_special_characters_removes_esc() {
|
||||||
|
assert_eq!(ignore_special_characters("hello\x1b[world"), "hello[world");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn strip_special_characters_makes_plaintext() {
|
||||||
|
assert_eq!(strip_special_characters("a\tb"), "a b");
|
||||||
|
assert_eq!(
|
||||||
|
strip_special_characters(ansi!("<red>hello<green>world")),
|
||||||
|
"helloworld");
|
||||||
|
assert_eq!(
|
||||||
|
strip_special_characters("hello\r\x07world\n"),
|
||||||
|
"helloworld\n");
|
||||||
|
assert_eq!(
|
||||||
|
strip_special_characters("hello\r\x07world\n"),
|
||||||
|
"helloworld\n");
|
||||||
|
assert_eq!(
|
||||||
|
strip_special_characters("Test\x1b[5;5fing"),
|
||||||
|
"Test5fing");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn limit_special_characters_strips_some_things() {
|
||||||
|
assert_eq!(limit_special_characters(ansi!("a<bgred><green>b<bggreen><red>c<reset>d")),
|
||||||
|
ansi!("a<bgred><green>b<bggreen><red>c<reset>d"));
|
||||||
|
assert_eq!(limit_special_characters("Test\x1b[5;5fing"),
|
||||||
|
"Test5fing");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -8,7 +8,7 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = "0.20.0"
|
base64 = "0.20.0"
|
||||||
blastmud_interfaces = { path = "../blastmud_interfaces" }
|
blastmud_interfaces = { path = "../blastmud_interfaces" }
|
||||||
ansi_macro = { path = "../ansi_macro" }
|
ansi = { path = "../ansi" }
|
||||||
deadpool = "0.9.5"
|
deadpool = "0.9.5"
|
||||||
deadpool-postgres = { version = "0.10.3", features = ["serde"] }
|
deadpool-postgres = { version = "0.10.3", features = ["serde"] }
|
||||||
futures = "0.3.25"
|
futures = "0.3.25"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::message_handler::ListenerSession;
|
use crate::message_handler::ListenerSession;
|
||||||
use crate::DResult;
|
use crate::DResult;
|
||||||
use crate::db::DBPool;
|
use crate::db::DBPool;
|
||||||
use ansi_macro::ansi;
|
use ansi::ansi;
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
use crate::models::session::Session;
|
use crate::models::session::Session;
|
||||||
|
|
||||||
|
@ -1,20 +1,21 @@
|
|||||||
use super::ListenerSession;
|
use super::ListenerSession;
|
||||||
use crate::DResult;
|
use crate::DResult;
|
||||||
use crate::db::{DBTrans, DBPool};
|
use crate::db::{DBTrans, DBPool};
|
||||||
use ansi_macro::ansi;
|
use ansi::ansi;
|
||||||
use phf::phf_map;
|
use phf::phf_map;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use crate::models::{session::Session, user::User};
|
use crate::models::{session::Session, user::User};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
|
|
||||||
mod parsing;
|
|
||||||
mod ignore;
|
|
||||||
mod help;
|
|
||||||
mod quit;
|
|
||||||
mod less_explicit_mode;
|
|
||||||
mod register;
|
|
||||||
mod agree;
|
mod agree;
|
||||||
|
mod help;
|
||||||
|
mod ignore;
|
||||||
|
mod less_explicit_mode;
|
||||||
mod login;
|
mod login;
|
||||||
|
mod look;
|
||||||
|
mod parsing;
|
||||||
|
mod quit;
|
||||||
|
mod register;
|
||||||
|
|
||||||
pub struct VerbContext<'l> {
|
pub struct VerbContext<'l> {
|
||||||
session: &'l ListenerSession,
|
session: &'l ListenerSession,
|
||||||
@ -59,14 +60,16 @@ static ALWAYS_AVAILABLE_COMMANDS: UserVerbRegistry = phf_map! {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static UNREGISTERED_COMMANDS: UserVerbRegistry = phf_map! {
|
static UNREGISTERED_COMMANDS: UserVerbRegistry = phf_map! {
|
||||||
"less_explicit_mode" => less_explicit_mode::VERB,
|
"agree" => agree::VERB,
|
||||||
"register" => register::VERB,
|
|
||||||
"login" => login::VERB,
|
|
||||||
"connect" => login::VERB,
|
"connect" => login::VERB,
|
||||||
"agree" => agree::VERB
|
"less_explicit_mode" => less_explicit_mode::VERB,
|
||||||
|
"login" => login::VERB,
|
||||||
|
"register" => register::VERB,
|
||||||
};
|
};
|
||||||
|
|
||||||
static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! {
|
static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! {
|
||||||
|
"l" => look::VERB,
|
||||||
|
"look" => look::VERB,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn resolve_handler(ctx: &VerbContext, cmd: &str) -> Option<&'static UserVerbRef> {
|
fn resolve_handler(ctx: &VerbContext, cmd: &str) -> Option<&'static UserVerbRef> {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use super::{VerbContext, UserVerb, UserVerbRef, UResult, user_error};
|
use super::{VerbContext, UserVerb, UserVerbRef, UResult, user_error};
|
||||||
use crate::models::user::{User, UserTermData};
|
use crate::models::user::{User, UserTermData};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use ansi_macro::ansi;
|
use ansi::ansi;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
|
||||||
pub struct Verb;
|
pub struct Verb;
|
||||||
|
@ -3,7 +3,7 @@ use super::{
|
|||||||
CommandHandlingError::UserError
|
CommandHandlingError::UserError
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use ansi_macro::ansi;
|
use ansi::ansi;
|
||||||
use phf::phf_map;
|
use phf::phf_map;
|
||||||
|
|
||||||
static ALWAYS_HELP_PAGES: phf::Map<&'static str, &'static str> = phf_map! {
|
static ALWAYS_HELP_PAGES: phf::Map<&'static str, &'static str> = phf_map! {
|
||||||
|
14
blastmud_game/src/message_handler/user_commands/look.rs
Normal file
14
blastmud_game/src/message_handler/user_commands/look.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
use super::{VerbContext, UserVerb, UserVerbRef, UResult, UserError};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
pub struct Verb;
|
||||||
|
#[async_trait]
|
||||||
|
impl UserVerb for Verb {
|
||||||
|
async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, _remaining: &str) -> UResult<()> {
|
||||||
|
let user = ctx.user_dat.as_ref()
|
||||||
|
.ok_or_else(|| UserError("Not logged in".to_owned()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static VERB_INT: Verb = Verb;
|
||||||
|
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;
|
@ -2,7 +2,7 @@ use super::{
|
|||||||
VerbContext, UserVerb, UserVerbRef, UResult
|
VerbContext, UserVerb, UserVerbRef, UResult
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use ansi_macro::ansi;
|
use ansi::ansi;
|
||||||
|
|
||||||
pub struct Verb;
|
pub struct Verb;
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
@ -3,7 +3,7 @@ use async_trait::async_trait;
|
|||||||
use super::{user_error, parsing::parse_username};
|
use super::{user_error, parsing::parse_username};
|
||||||
use crate::models::{user::User, item::Item};
|
use crate::models::{user::User, item::Item};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use ansi_macro::ansi;
|
use ansi::ansi;
|
||||||
use tokio::time;
|
use tokio::time;
|
||||||
|
|
||||||
pub struct Verb;
|
pub struct Verb;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use crate::DResult;
|
use crate::DResult;
|
||||||
use crate::db::DBPool;
|
use crate::db::DBPool;
|
||||||
use crate::models::item::Item;
|
use crate::models::item::Item;
|
||||||
use itertools::Itertools;
|
|
||||||
use std::collections::{BTreeSet, BTreeMap};
|
use std::collections::{BTreeSet, BTreeMap};
|
||||||
use log::info;
|
use log::info;
|
||||||
|
|
||||||
@ -69,6 +68,7 @@ pub async fn refresh_static_content(pool: &DBPool) -> DResult<()> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use itertools::Itertools;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use super::StaticItem;
|
use super::StaticItem;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use ansi_macro::ansi;
|
use ansi::ansi;
|
||||||
use crate::models::item::Item;
|
use crate::models::item::Item;
|
||||||
|
|
||||||
pub struct Zone {
|
pub struct Zone {
|
||||||
|
Loading…
Reference in New Issue
Block a user