Skip to content

Commit b089016

Browse files
committed
refactor into Runnable commands
1 parent ae6ef61 commit b089016

File tree

7 files changed

+242
-104
lines changed

7 files changed

+242
-104
lines changed

Cargo.lock

Lines changed: 14 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "swordfish-rs"
3-
version = "0.5.0"
3+
version = "0.5.1"
44
edition = "2021"
55
license-file = "LICENSE"
66
description = "Cli tool for typing effect in Termainl for screencasts"
@@ -20,6 +20,7 @@ name = "swordfish"
2020
anyhow = "1"
2121
clap = { version = "3", features = ["derive"] }
2222
colored = "2"
23+
enum_dispatch = "0.3"
2324
serde = { version = "1", features = ["derive"] }
2425
serde_yaml = "0.9"
2526
shellwords = "1"
@@ -28,3 +29,4 @@ shellwords = "1"
2829
strip = true
2930
lto = true
3031
codegen-units = 1
32+
opt-level = "z" # Optimize for size.

src/commands.rs

Lines changed: 160 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,164 @@
1+
use anyhow::Result;
2+
use std::{cmp, process};
3+
use std::io::{self, stdout, Write as IOWrite};
14
use serde::{Deserialize};
5+
use enum_dispatch::enum_dispatch;
6+
use crate::{functions, terminal, state::State};
27

8+
#[enum_dispatch]
39
#[derive(Debug, Deserialize)]
410
#[serde(rename_all = "snake_case")]
5-
pub enum Command<'a> {
6-
Clear,
7-
Erase {msec: u32, by_chars: Option<&'a str>, amount: Option<u32>},
8-
Execute {line: &'a str},
9-
NewLine,
10-
Pause,
11-
Prompt {text: &'a str, color: Option<&'a str>},
12-
Wait {msec: u32},
13-
Write {msec: u32, color: Option<&'a str>, text: &'a str},
14-
Turbo {by: u32},
15-
}
11+
pub enum Command {
12+
Clear(Clear),
13+
Erase(Erase),
14+
Execute(Execute),
15+
NewLine(NewLine),
16+
Pause(Pause),
17+
Prompt(Prompt),
18+
Wait(Wait),
19+
Write(Write),
20+
Turbo(Turbo),
21+
}
22+
23+
#[enum_dispatch(Command)]
24+
pub trait Runnable {
25+
fn run(&self, state: &mut State) -> Result<()>;
26+
}
27+
28+
#[derive(Debug, Deserialize)]
29+
pub struct Clear {}
30+
31+
impl Runnable for Clear {
32+
fn run(&self, state: &mut State) -> Result<()> {
33+
print!("\x1B[2J\x1B[1;1H");
34+
functions::show_prompt(&state.prompt)?;
35+
state.cursor = 0;
36+
Ok(())
37+
}
38+
}
39+
40+
#[derive(Debug, Deserialize)]
41+
pub struct Erase {
42+
pub msec: u32,
43+
pub by_chars: Option<String>,
44+
pub amount: Option<u32>,
45+
}
46+
47+
impl Runnable for Erase {
48+
fn run(&self, state: &mut State) -> Result<()> {
49+
let deletions = match (self.by_chars.as_ref(), self.amount) {
50+
(Some(by_chars), None) => by_chars.len(),
51+
(None, Some(amount)) => amount as usize,
52+
(Some(by_chars), Some(amount)) => amount as usize + by_chars.len(),
53+
(None, None) => 0,
54+
};
55+
56+
// Remove the deletions up till the cursor
57+
let deletions = cmp::min(deletions, state.cursor);
58+
state.cursor -= deletions;
59+
functions::erase(deletions, self.msec, state.speed_factor)?;
60+
Ok(())
61+
}
62+
}
63+
64+
#[derive(Debug, Deserialize)]
65+
pub struct Execute {
66+
pub line: String,
67+
}
68+
69+
impl Runnable for Execute {
70+
fn run(&self, state: &mut State) -> Result<()> {
71+
println!("");
72+
let words = shellwords::split(&self.line).unwrap();
73+
74+
if let Some((cmd, args)) = words.split_first() {
75+
process::Command::new(cmd).args(args).spawn()?;
76+
}
77+
functions::delay(crate::DELAY_AFTER_EXECUTE, state.speed_factor);
78+
functions::show_prompt(&state.prompt)?;
79+
state.cursor = 0;
80+
Ok(())
81+
}
82+
}
83+
84+
#[derive(Debug, Deserialize)]
85+
pub struct NewLine {}
86+
87+
impl Runnable for NewLine {
88+
fn run(&self, state: &mut State) -> Result<()> {
89+
print!("\n");
90+
functions::show_prompt(&state.prompt)?;
91+
state.cursor = 0;
92+
Ok(())
93+
}
94+
}
95+
96+
#[derive(Debug, Deserialize)]
97+
pub struct Pause {}
98+
99+
impl Runnable for Pause {
100+
fn run(&self, _state: &mut State) -> Result<()> {
101+
let mut answer = String::new();
102+
io::stdin().read_line(&mut answer)?;
103+
Ok(())
104+
}
105+
}
106+
107+
#[derive(Debug, Deserialize)]
108+
pub struct Prompt {
109+
pub text: String,
110+
pub color: Option<String>,
111+
}
112+
113+
impl Runnable for Prompt {
114+
fn run(&self, state: &mut State) -> Result<()> {
115+
let ps1 = terminal::colorful(&self.text, self.color.as_deref());
116+
state.prompt = Some(ps1);
117+
functions::show_prompt(&state.prompt)?;
118+
state.cursor = 0;
119+
Ok(())
120+
}
121+
}
122+
123+
#[derive(Debug, Deserialize)]
124+
pub struct Wait {
125+
pub msec: u32,
126+
}
127+
128+
impl Runnable for Wait {
129+
fn run(&self, state: &mut State) -> Result<()> {
130+
functions::delay(self.msec, state.speed_factor);
131+
Ok(())
132+
}
133+
}
134+
135+
#[derive(Debug, Deserialize)]
136+
pub struct Write {
137+
pub msec: u32,
138+
pub color: Option<String>,
139+
pub text: String,
140+
}
141+
142+
impl Runnable for Write {
143+
fn run(&self, state: &mut State) -> Result<()> {
144+
for c in self.text.chars() {
145+
functions::delay(self.msec, state.speed_factor);
146+
print!("{}", terminal::colorful(&c.to_string(), self.color.as_deref()));
147+
stdout().flush()?;
148+
}
149+
state.cursor += self.text.len();
150+
Ok(())
151+
}
152+
}
153+
154+
#[derive(Debug, Deserialize)]
155+
pub struct Turbo {
156+
pub by: u32,
157+
}
158+
159+
impl Runnable for Turbo {
160+
fn run(&self, state: &mut State) -> Result<()> {
161+
state.speed_factor = self.by;
162+
Ok(())
163+
}
164+
}

src/functions.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use anyhow::Result;
2+
use std::{thread, time};
3+
use std::io::{stdout, Write};
4+
use crate::terminal::BACKSPACE;
5+
6+
pub fn delay(msec: u32, speed_factor: u32) {
7+
let t = msec / speed_factor;
8+
thread::sleep(time::Duration::from_millis(t.into()));
9+
}
10+
11+
pub fn erase(amount: usize, msec: u32, speed_factor: u32) -> Result<()> {
12+
for _ in 0..amount {
13+
delay(msec, speed_factor);
14+
print!("{} {}", BACKSPACE, BACKSPACE);
15+
stdout().flush()?;
16+
}
17+
Ok(())
18+
}
19+
20+
pub fn show_prompt(prompt: &Option<String>) -> Result<()> {
21+
if let Some(ps1) = prompt {
22+
print!("{ps1} ");
23+
stdout().flush()?;
24+
}
25+
Ok(())
26+
}

src/lib.rs

Lines changed: 10 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
mod commands;
22
mod terminal;
3+
mod functions;
4+
mod state;
35

46
use anyhow::Result;
5-
use std::{cmp, process, thread, time};
6-
use std::io::{self, stdout, Write};
77
use commands::Command;
8-
use terminal::BACKSPACE;
8+
use state::State;
9+
use commands::Runnable;
910

1011
pub fn from_yaml(data: &str) -> Result<Vec<Command>> {
1112
let yaml = serde_yaml::from_str(data)?;
@@ -15,95 +16,15 @@ pub fn from_yaml(data: &str) -> Result<Vec<Command>> {
1516
const DELAY_AFTER_EXECUTE: u32 = 250;
1617

1718
pub fn execute(commands: Vec<Command>) -> Result<()> {
18-
let mut prompt = None;
19-
let mut cursor = 0;
20-
let mut speed_factor = 1;
19+
let mut state = State {
20+
prompt: None,
21+
cursor: 0,
22+
speed_factor: 1,
23+
};
2124

2225
for cmd in commands {
23-
match cmd {
24-
Command::Write {msec, text, color} => {
25-
for c in text.chars() {
26-
delay(msec, speed_factor);
27-
print!("{}", terminal::colorful(&c.to_string(), color));
28-
stdout().flush()?;
29-
}
30-
cursor += text.len();
31-
},
32-
Command::Erase {msec, by_chars, amount} => {
33-
let deletions = match (by_chars, amount) {
34-
(Some(by_chars), None) => by_chars.len(),
35-
(None, Some(amount)) => amount as usize,
36-
(Some(by_chars), Some(amount)) => amount as usize + by_chars.len(),
37-
(None, None) => 0,
38-
};
39-
40-
// Remove the deletions up till the cursor
41-
let deletions = cmp::min(deletions, cursor);
42-
cursor -= deletions;
43-
erase(deletions, msec, speed_factor)?;
44-
},
45-
Command::Execute {line} => {
46-
println!("");
47-
let words = shellwords::split(line).unwrap();
48-
49-
if let Some((cmd, args)) = words.split_first() {
50-
process::Command::new(cmd).args(args).spawn()?;
51-
}
52-
delay(DELAY_AFTER_EXECUTE, speed_factor);
53-
show_prompt(&prompt)?;
54-
cursor = 0;
55-
},
56-
Command::Wait {msec} => {
57-
delay(msec, speed_factor);
58-
},
59-
Command::Pause => {
60-
let mut answer = String::new();
61-
io::stdin().read_line(&mut answer)?;
62-
},
63-
Command::Clear => {
64-
print!("\x1B[2J\x1B[1;1H");
65-
show_prompt(&prompt)?;
66-
cursor = 0;
67-
}
68-
Command::Prompt {text, color} => {
69-
let ps1 = terminal::colorful(text, color);
70-
prompt = Some(ps1);
71-
show_prompt(&prompt)?;
72-
cursor = 0;
73-
},
74-
Command::NewLine => {
75-
print!("\n");
76-
show_prompt(&prompt)?;
77-
cursor = 0;
78-
},
79-
Command::Turbo {by} => {
80-
speed_factor = by;
81-
},
82-
}
26+
cmd.run(&mut state)?;
8327
}
8428

8529
Ok(())
8630
}
87-
88-
89-
fn show_prompt(prompt: &Option<String>) -> Result<()> {
90-
if let Some(ps1) = prompt {
91-
print!("{ps1} ");
92-
stdout().flush()?;
93-
}
94-
Ok(())
95-
}
96-
97-
fn erase(amount: usize, msec: u32, speed_factor: u32) -> Result<()> {
98-
for _ in 0..amount {
99-
delay(msec, speed_factor);
100-
print!("{} {}", BACKSPACE, BACKSPACE);
101-
stdout().flush()?;
102-
}
103-
Ok(())
104-
}
105-
106-
fn delay(msec: u32, speed_factor: u32) {
107-
let t = msec / speed_factor;
108-
thread::sleep(time::Duration::from_millis(t.into()));
109-
}

src/state.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pub struct State {
2+
pub prompt: Option<String>,
3+
pub cursor: usize,
4+
pub speed_factor: u32,
5+
}

0 commit comments

Comments
 (0)