use std::io::{BufRead, BufReader, Read, Write};
use std::str::FromStr;
#[derive(std::fmt::Debug)]
pub enum LogLevel {
Error,
Warn,
Info,
Debug,
Trace,
}
impl FromStr for LogLevel {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"--error" => Ok(Self::Error),
"--warn" => Ok(Self::Warn),
"--info" => Ok(Self::Info),
"--debug" => Ok(Self::Debug),
"--trace" => Ok(Self::Trace),
_ => Err(()),
}
}
}
pub fn get_package_name() -> String {
let pat1 = regex::Regex::new(r"^\s*path\s*=.*?src/bin/(?P<pkg_name>[\w|-]+)\.rs").unwrap();
let file = std::fs::File::open("./Cargo.toml").expect("Cargo.toml file cannot be found!");
let reader = BufReader::new(file);
for line in reader.lines() {
let text = line.unwrap();
if pat1.is_match(&text) {
let name = pat1
.captures(&text)
.unwrap()
.name("pkg_name")
.unwrap()
.as_str()
.replace("-", "_");
return name.into();
}
}
panic!("the target file cannot be found, put `path = 'src/bin/<+target+>.rs'` in your Cargo.toml file!");
}
pub(crate) fn get_file_path<'a>(index: &'a str, name: String) -> String {
match index {
"readme" => name + "/README.md",
"config" => name + "/dyer.cfg",
"cargo" => name + "/Cargo.toml",
"affix" => name + "/src/affix.rs",
"entity" => name + "/src/entity.rs",
"parser" => name + "/src/parser.rs",
"actor" => name + "/src/actor.rs",
"middleware" => name + "/src/middleware.rs",
"pipeline" => name + "/src/pipeline.rs",
_ => {
println!("Invalid name: {}", index);
panic!()
}
}
}
pub(crate) fn get_file_intro(index: &str) -> &str {
match index {
"readme" => {
r#"<!--
-This is a markdown file generated by dyer-cli
- Instructions of the project specified here
--!>"#
}
"affix" => {
r#"use dyer::*;
#[dyer::affix]
pub struct Aff {}
#[dyer::async_trait]
impl Affixor for Aff {
async fn init(&mut self) { }
async fn invoke(&mut self) -> Option<Request> {
None
}
async fn after_invoke(&mut self) {}
async fn before_parse(&mut self, _: Option<&mut Result<Response, MetaResponse>>) {}
async fn parse(&mut self, _: Option<Result<Response, MetaResponse>>) -> Option<Affix> {
None
}
async fn after_parse(&mut self) {}
async fn close(&mut self) {}
}
"#
}
"entity" => {
r#"use serde::{Deserialize, Serialize};
// the Entity to be used
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Item1 {
pub field1: String,
pub field2: i32,
}
/* serve as a container for all entities, and generic parameter of dyer::App
* attribute #[dyer::entity(entities)] mark the enum and use it as container to all data to be collected
*/
#[dyer::entity(entities)]
#[derive(Serialize, Debug, Clone)]
pub enum Entities {
Item1(Item1),
}
"#
}
"middleware" => {
r#"use crate::entity::*;
use dyer::*;
/* attribute #[dyer::middleware(attr)] mark the method and use it as that in `MiddleWare`
* attr could be :
* handle_entity/handle_req/handle_task/handle_affix
* /handle_res/handle_err/handle_yerr
*/
#[dyer::middleware(handle_entity)]
pub async fn handle_entities(_items: &mut Vec<Entities>, _app: &mut App<Entities>) {}
"#
}
"parser" => {
r#"use crate::entity::*;
use dyer::*;
/* note that call this function to parse via specifying task.parser:
* let task = Task::builder();
* ...
* .parser(parse_func)
* .body(Body::empty(), "actor_marker")
* .unwrap();
* that means function `parse_func` is called to parse the Response.
* attribute #[dyer::parser] mark the method and use it extract entities from `Response` whose
* parser is parse_func
*/
#[dyer::parser]
pub fn parse_func(_res: Response) -> Parsed<Entities> {
Parsed::new()
}"#
}
"pipeline" => {
r#"use dyer::*;
use crate::entity::*;
/*
* something to do before sending entities to pipeline
* the return type inside `Option` requires complete path(starts with `std` or crate in `Cargo.toml`)
* attribute #[dyer::pipeline(attr)] mark the method and use it as that in `PipeLine`
* attr could be:
* initializer/disposer/process_entity/process_yerr
*/
#[dyer::pipeline(initializer)]
async fn func_name(_app: &mut App<Entities>) -> Option<std::fs::File>
{
None
}
"#
}
"actor" => {
r#"pub mod affix;
pub mod entity;
pub mod middleware;
pub mod pipeline;
pub mod parser;
use affix::*;
use entity::*;
use parser::*;
use dyer::*;
// attribute #[dyer::actor] mark the struct and use it as a type implemented trait `Actor`
#[dyer::actor]
pub struct MyActor {
pub start_uri: String,
}
#[dyer::async_trait]
impl Actor<Entities, Aff> for MyActor {
// create an instance
async fn new() -> Self {
MyActor{
start_uri: "https://example.com/some/path/to/site".into()
}
}
// preparation before opening actor
async fn open_actor(&mut self, _app: &mut App<Entities>) {}
/*
* `Task` to be executed when starting `dyer`. Note that this function must reproduce a
* non-empty vector, if not, the whole program will be left at blank.
*/
async fn entry_task(&mut self) -> Result<Vec<Task>, Box<dyn std::error::Error>> {
let task = Task::get( &self.start_uri )
.parser(parse_func)
// here the marker `MyActor` is the same as
// the type implemented trait `Acotr`
// change it if you like as long as it is unique
.body(Body::empty(), "MyActor")
.unwrap();
Ok(vec![task])
}
/* the generator of `Affix`
* `dyer` consume the returned `Request`, generate a `Response` fed to the closure
* to generate a `Affix`
*/
async fn entry_affix(&mut self) -> Option<Aff> {
None
}
// preparation before closing actor
async fn close_actor(&mut self, _app: &mut App<Entities>) {}
}"#
}
"cargo" => {
r#"[package]
name = "<+name+>"
version = "0.1.0"
authors = ["your name"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "<+name+>"
path = "src/actor.rs"
[[bin]]
name = "<+name+>"
path = "src/bin/<+name+>.rs"
[dependencies]
dyer = { version = "3.3", features = [] }
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.20", features = ["rt-multi-thread"]}
simple_logger = "1.11" "#
}
"config" => {
r#"## ArgApp
skip: true,
spawn_task_max: 100,
buf_task: 10000,
round_entity: 50,
data_dir: data/
nap: 15.0,
join_gap: 7.0,
## ArgAffix
arg_affix.is_on: false,
arg_affix.affix_min: 0,
arg_affix.affix_max: 0,
## ArgRate
rate.cycle: 600.0,
rate.load: 99.0,
rate.rate_low: 0.333,
rate.err: 0,
rate.interval: 30.0,
"#
}
_ => "",
}
}
pub(crate) fn run_command(cmd: &str, options: Vec<&str>) {
let stdout = std::process::Command::new(cmd)
.args(options)
.stdout(std::process::Stdio::piped())
.spawn()
.unwrap()
.stdout
.ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::Other,
"Could not capture standard output.",
)
})
.unwrap();
let reader = BufReader::new(stdout);
reader
.lines()
.filter_map(|line| line.ok())
.filter(|line| line.find("src\\main.rs").is_none())
.filter(|line| line.find("src/main.rs").is_none())
.for_each(|line| println!("{}", line));
}
pub(crate) fn change_log_level(level: &str) {
let mut file = std::fs::OpenOptions::new()
.read(true)
.open("src/bin/main.rs")
.unwrap();
let mut buf = String::new();
file.read_to_string(&mut buf).unwrap();
drop(file);
let ll = level.strip_prefix("--").unwrap();
let l = &("log::LevelFilter::".to_string() + &to_camelcase(ll));
let buf = buf.replace("log::LevelFilter::Error", l);
let buf = buf.replace("log::LevelFilter::Warn", l);
let buf = buf.replace("log::LevelFilter::Info", l);
let buf = buf.replace("log::LevelFilter::Debug", l);
let buf = buf.replace("log::LevelFilter::Trace", l);
let buf = buf.replace("log::LevelFilter::Off", l);
let mut file = std::fs::OpenOptions::new()
.truncate(true)
.write(true)
.open("src/bin/main.rs")
.unwrap();
file.write_all(buf.as_bytes()).unwrap();
}
pub(crate) fn to_camelcase(s: &str) -> String {
let mut r = String::with_capacity(s.len());
let mut ch = s[..].chars();
let e = ch.next().unwrap().to_uppercase();
r.push(e.to_string().chars().next().unwrap());
while let Some(t) = ch.next() {
r.push(t);
}
r
}