master v0.0.1
rascul 2 years ago
parent 93746a5107
commit 55bf371d04

@ -0,0 +1,61 @@
use lazy_static::lazy_static;
use regex::Regex;
use crate::Client;
// this lazy static stuff is so not to recompile the re every time
// unwrap is ok here because compiler will catch it if the string is bad
lazy_static! {
static ref RE: Regex = Regex::new(r"^#!(?P<action>\w+)(?: (?P<text>.*))?$").unwrap();
}
/// type of log action
#[derive(Debug)]
pub enum Action {
/// specify client type
Client(Client),
/// insert a comment in the log
Comment(String),
/// delay replay
/// duration is number of seconds or 1 if no seconds specified or if duration can't be parsed
Delay(u64),
/// log line
/// duration is amount of time to sleep before printing the line
Line(String),
/// insert a marker
Mark,
/// No match
None,
}
impl From<&str> for Action {
fn from(line: &str) -> Self {
if line.starts_with("#!") {
let caps = RE.captures(&line).unwrap();
let action = if let Some(action) = caps.name("action") { action.as_str() } else { "" };
let text = if let Some(text) = caps.name("text") {
text.as_str().to_owned()
} else {
String::new()
};
match action {
"client" => Self::Client(Client::from(&text)),
"delay" => Self::Delay(text.parse::<u64>().unwrap_or(1)),
"mark" => Self::Mark,
_ => Self::None,
}
} else if line.starts_with("##") {
Self::Comment(line.to_owned())
} else {
Self::Line(String::new())
}
}
}

@ -0,0 +1,86 @@
use std::io;
use std::io::Read;
use std::thread::sleep;
use std::time::Duration;
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
#[derive(Debug)]
struct Chunk {
pub delta: i32,
pub text: String,
}
impl Chunk {
fn chunk32(chunk: &[u8; 8]) -> Result<(i32, i32)> {
let (dc, sc) = {
let (mut dc, mut sc): ([u8; 4], [u8; 4]) = ([0; 4], [0; 4]);
for i in 0..4 {
dc[i] = chunk[i];
sc[i] = chunk[i + 4];
}
(dc, sc)
};
let delta = i32::from_be_bytes(dc);
let size = i32::from_be_bytes(sc);
if delta < 0 || size < 1 || size > 100000 {
return Err("delta source is not int32_t".into());
}
Ok((delta, size))
}
fn chunk64(dc: [u8; 8], sc: [u8; 4]) -> Result<(i32, i32)> {
let delta = i64::from_be_bytes(dc);
let size = i32::from_be_bytes(sc);
if delta < 0 || delta > i32::MAX.into() || size < 1 || size > 100000 {
return Err("delta source is not int64_t".into());
}
let delta: i32 = delta.try_into()?;
Ok((delta, size))
}
pub fn read_chunk() -> Result<Self> {
let mut stdin = io::stdin();
let mut chunk: [u8; 8] = [0; 8];
stdin.read_exact(&mut chunk)?;
let (delta, size) = {
if let Ok((delta, size)) = Self::chunk32(&chunk) {
(delta, size)
} else {
let mut next_chunk: [u8; 4] = [0; 4];
stdin.read_exact(&mut next_chunk)?;
if let Ok((delta, size)) = Self::chunk64(chunk, next_chunk) {
(delta, size)
} else {
return Err("broken".into());
}
}
};
let mut text_chunk: Vec<u8> = vec![0; size as usize];
stdin.read_exact(&mut text_chunk)?;
let text = String::from_utf8(text_chunk)?;
Ok(Self { delta, text })
}
}
fn main() -> Result<()> {
while let Ok(chunk) = Chunk::read_chunk() {
sleep(Duration::from_millis(chunk.delta.try_into()?));
print!("{}", chunk.text);
}
println!();
Ok(())
}

@ -0,0 +1,37 @@
/// mud client type
use std::default::Default;
use serde::Deserialize;
/// client type to know log format
#[derive(Debug, Deserialize, PartialEq)]
pub enum Client {
/// mudlet
Mudlet,
/// tintin++
TinTin,
/// zmud
ZMud,
/// no client specified
None,
}
impl Default for Client {
fn default() -> Self {
Client::None
}
}
impl From<&String> for Client {
fn from(s: &String) -> Self {
match s.to_lowercase().as_str() {
"mudlet" => Self::Mudlet,
"tintin" => Self::TinTin,
"zmud" => Self::ZMud,
_ => Self::None,
}
}
}

@ -0,0 +1,5 @@
mod client;
mod tintin;
pub use client::Client;
pub use tintin::TinTin;

@ -0,0 +1 @@
pub struct TinTin {}

@ -0,0 +1,36 @@
//! Data from log submission form
use std::default::Default;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
#[serde(default = "FormData::default")]
pub struct FormData {
/// Title
#[serde(rename = "submit_title")]
pub title: String,
/// Player
#[serde(rename = "submit_player")]
pub player: String,
/// Public
#[serde(rename = "submit_public")]
pub public: String,
/// Log
#[serde(rename = "submit_log")]
pub log: String,
}
impl Default for FormData {
fn default() -> Self {
Self {
title: String::new(),
player: String::new(),
public: String::new(),
log: String::new(),
}
}
}

@ -0,0 +1,27 @@
// use serde::Serialize;
// #[derive(Debug, Serialize)]
// pub struct Line {
// /// delay before printing the line, in milliseconds
// pub delay: u64,
// /// text of the line
// pub text: String,
// }
// impl From<&str> for Line {
// fn from(s: &str) -> Self {
// Self {
// delay: 0,
// text: s.to_owned(),
// }
// }
// }
pub trait Line {
/// delay before printing the line, in milliseconds
fn delay(&self) -> u64;
/// text to print
fn text(&self) -> &str;
}

@ -0,0 +1,51 @@
mod action;
mod client;
mod form_data;
mod line;
mod options;
mod routes;
mod wot_log;
use actix_files::Files;
use actix_web::{middleware, web, App, HttpServer};
use handlebars::Handlebars;
use log::info;
use action::Action;
use client::Client;
use form_data::FormData;
//use line::Line;
use options::Options;
use wot_log::WotLog;
pub fn default_false() -> bool {
false
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let options = Options::init();
rag::init().unwrap();
let mut handlebars = Handlebars::new();
handlebars.register_templates_directory(".html", "./templates").unwrap();
let handlebars_ref = web::Data::new(handlebars);
info!("Configuration (see --help to change):");
info!("{:?}", options);
HttpServer::new(move || {
App::new()
.app_data(handlebars_ref.clone())
.wrap(middleware::Logger::default())
.service(routes::index::get)
.service(routes::submit::get)
.service(routes::submit::post)
.service(Files::new("/static", "static").show_files_listing())
})
.bind((options.address, options.port))?
.workers(options.workers)
.run()
.await
}

@ -0,0 +1,27 @@
use clap::Parser;
#[derive(Debug, Parser)]
#[clap(author, version, about, long_about = None)]
pub struct Options {
/// IP address to listen on
#[clap(short, long, default_value = "127.0.0.1")]
pub address: String,
/// Port to use
#[clap(short, long, default_value = "8807")]
pub port: u16,
/// URL to access
#[clap(short, long, default_value = "http://localhost:8807")]
pub url: String,
/// Number of HTTP workers
#[clap(short, long, default_value = "4")]
pub workers: usize,
}
impl Options {
pub fn init() -> Self {
Self::parse()
}
}

@ -0,0 +1,13 @@
use actix_web::{get, web, HttpResponse};
use handlebars::Handlebars;
use serde_json::json;
#[get("/")]
pub async fn get(hb: web::Data<Handlebars<'_>>) -> HttpResponse {
let data = json!({
"name": "rewot"
});
let body = hb.render("index", &data).unwrap();
HttpResponse::Ok().body(body)
}

@ -0,0 +1,2 @@
pub mod index;
pub mod submit;

@ -0,0 +1,31 @@
use actix_web::{get, post, web, HttpResponse};
use handlebars::Handlebars;
use serde_json::json;
use crate::FormData;
use crate::WotLog;
#[get("/submit")]
pub async fn get(hb: web::Data<Handlebars<'_>>) -> HttpResponse {
let data = json!({
"name": "rewot"
});
let body = hb.render("submit", &data).unwrap();
HttpResponse::Ok().body(body)
}
#[post("/submit")]
pub async fn post(form: web::Form<FormData>, hb: web::Data<Handlebars<'_>>) -> HttpResponse {
let form_data = form.into_inner();
let wot_log = WotLog::from(&form_data);
wot_log.parse();
let data = json!({
"name": "rewot"
});
let body = hb.render("submit", &data).unwrap();
HttpResponse::Ok().body(body)
}

@ -0,0 +1,62 @@
use lazy_static::lazy_static;
use log::{debug, error, info};
use regex::Regex;
use serde::Deserialize;
use crate::default_false;
use crate::Action;
use crate::Client;
use crate::FormData;
#[derive(Debug, Deserialize)]
pub struct WotLog {
/// ID of the log
#[serde(default = "String::new")]
pub id: String,
/// Title of the log
#[serde(default = "String::new")]
pub title: String,
/// Name of the player
#[serde(default = "String::new")]
pub player: String,
/// Is the log public
#[serde(default = "default_false")]
pub public: bool,
/// Log
#[serde(default = "String::new")]
pub log: String,
/// Client type
#[serde(default = "Client::default")]
pub client: Client,
}
impl From<&FormData> for WotLog {
fn from(form_data: &FormData) -> Self {
Self {
id: "".into(),
title: form_data.title.clone(),
player: form_data.player.clone(),
public: if form_data.public == "public" { true } else { false },
log: form_data.log.clone(),
client: Client::None,
}
}
}
impl WotLog {
/// Read and parse the log
pub fn parse(self: &Self) {
let mut lineno: u128 = 1;
for line in self.log.lines() {
let line = line.trim_end_matches('\r');
let action = Action::from(line);
lineno += 1;
}
}
}
Loading…
Cancel
Save