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…
Reference in new issue