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