#![no_std]
#![forbid(unsafe_code)]
#![doc(
html_logo_url = "https://ardaku.github.io/mm/logo.svg",
html_favicon_url = "https://ardaku.github.io/mm/icon.svg",
html_root_url = "https://docs.rs/wasite"
)]
#![warn(
anonymous_parameters,
missing_copy_implementations,
missing_debug_implementations,
missing_docs,
nonstandard_style,
rust_2018_idioms,
single_use_lifetimes,
trivial_casts,
trivial_numeric_casts,
unreachable_pub,
unused_extern_crates,
unused_qualifications,
variant_size_differences
)]
extern crate alloc;
use alloc::string::String;
use core::num::NonZeroU16;
pub type Result<T = (), E = Error> = core::result::Result<T, E>;
#[derive(Debug)]
pub struct Error(#[allow(dead_code)] wasi::io::streams::StreamError);
#[derive(Debug)]
#[non_exhaustive]
pub struct Languages(String);
impl Languages {
fn parse_all(&self) -> impl Iterator<Item = &str> {
self.0.split(':')
}
pub fn collation(&self) -> impl Iterator<Item = &str> {
self.parse_all()
.filter_map(|l| l.strip_prefix("Collation="))
}
pub fn char_class(&self) -> impl Iterator<Item = &str> {
self.parse_all()
.filter_map(|l| l.strip_prefix("CharClass="))
}
pub fn monetary(&self) -> impl Iterator<Item = &str> {
self.parse_all().filter_map(|l| l.strip_prefix("Monetary="))
}
pub fn message(&self) -> impl Iterator<Item = &str> {
self.parse_all().filter_map(|l| l.strip_prefix("Message="))
}
pub fn numeric(&self) -> impl Iterator<Item = &str> {
self.parse_all().filter_map(|l| l.strip_prefix("Numeric="))
}
pub fn time(&self) -> impl Iterator<Item = &str> {
self.parse_all().filter_map(|l| l.strip_prefix("Time="))
}
pub fn other(&self) -> impl Iterator<Item = &str> {
self.parse_all().filter(|l| !l.contains('='))
}
}
#[derive(Debug)]
#[non_exhaustive]
pub struct User {
pub username: String,
pub langs: Languages,
}
#[derive(Debug)]
#[non_exhaustive]
pub struct Host {
pub name: String,
pub hostname: String,
pub timezone: String,
}
#[derive(Debug)]
#[non_exhaustive]
pub struct Environment {
pub user: User,
pub host: Host,
}
#[derive(Copy, Clone, Debug)]
#[non_exhaustive]
pub struct Dimensions {
pub width: NonZeroU16,
pub height: NonZeroU16,
}
#[derive(Copy, Clone, Debug)]
#[non_exhaustive]
pub struct Cursor {
pub column: NonZeroU16,
pub line: NonZeroU16,
}
#[derive(Copy, Clone, Debug)]
#[non_exhaustive]
pub struct State {
pub dimensions: Dimensions,
pub cursor: Cursor,
}
pub fn environment() -> Environment {
let mut env = Environment {
user: User {
username: String::new(),
langs: Languages(String::new()),
},
host: Host {
name: String::new(),
hostname: String::new(),
timezone: String::new(),
},
};
for (key, value) in wasi::cli::environment::get_environment() {
match key.as_str() {
"USER" => env.user.username = value,
"HOSTNAME" => env.host.hostname = value,
"NAME" => env.host.name = value,
"TZ" => env.host.timezone = value,
"LANGS" => env.user.langs = Languages(value),
_ => {}
}
}
env
}
pub fn state() -> Result<State> {
let err = Err(Error(wasi::io::streams::StreamError::Closed));
let stdout = wasi::cli::stdout::get_stdout();
stdout.blocking_flush().map_err(Error)?;
stdout.write(b"\x05").map_err(Error)?;
let stdin = wasi::cli::stdin::get_stdin();
let bytes = stdin.read(24).map_err(Error)?;
let string = String::from_utf8_lossy(&bytes);
let mut parts = string.split(';');
let Some(part_one) = parts.next() else {
return err;
};
let Some(part_two) = parts.next() else {
return err;
};
let mut cols = part_one.split('/');
let Some(column) = cols.next() else {
return err;
};
let Some(width) = cols.next() else {
return err;
};
let mut row = part_two.split('/');
let Some(line) = row.next() else {
return err;
};
let Some(height) = row.next() else {
return err;
};
stdout.write(b"\x06").map_err(Error)?;
stdout.blocking_flush().map_err(Error)?;
let Ok(column) = column.parse() else {
return err;
};
let Ok(width) = width.parse() else {
return err;
};
let Ok(line) = line.parse() else {
return err;
};
let Ok(height) = height.parse() else {
return err;
};
Ok(State {
dimensions: Dimensions { width, height },
cursor: Cursor { column, line },
})
}
#[non_exhaustive]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[allow(variant_size_differences)]
pub enum Command<'a> {
Clear,
Alert,
Raw(bool),
Title(&'a str),
Screen(bool),
}
pub fn execute(commands: &[Command<'_>]) -> Result {
let stdout = wasi::cli::stdout::get_stdout();
stdout.blocking_flush().map_err(Error)?;
for command in commands {
match command {
Command::Clear => stdout.write(b"\x00").map_err(Error)?,
Command::Alert => stdout.write(b"\x07").map_err(Error)?,
Command::Raw(enabled) => {
if *enabled {
stdout.write(b"\x03").map_err(Error)?;
} else {
stdout.write(b"\x02").map_err(Error)?;
}
}
Command::Title(title) => {
stdout.write(b"\x01").map_err(Error)?;
stdout.write(title.as_bytes()).map_err(Error)?;
stdout.write(b"\x04").map_err(Error)?;
}
Command::Screen(enabled) => {
if *enabled {
stdout.write(b"\x0F").map_err(Error)?;
} else {
stdout.write(b"\x0E").map_err(Error)?;
}
}
}
}
stdout.blocking_flush().map_err(Error)
}