Загрузить файлы в «src»
This commit is contained in:
parent
c52756d17c
commit
b9ecffc77a
3 changed files with 235 additions and 0 deletions
35
src/main.rs
Normal file
35
src/main.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
mod alphabet;
|
||||
mod app;
|
||||
mod ciphertext;
|
||||
mod cli;
|
||||
mod decoder;
|
||||
mod storage;
|
||||
mod tui;
|
||||
|
||||
use clap::Parser;
|
||||
use cli::{Cli, Commands};
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
match cli.command {
|
||||
Commands::Generate {
|
||||
path,
|
||||
chars,
|
||||
chars_file,
|
||||
len,
|
||||
mut_rate,
|
||||
} => app::generate_cmd(path, chars, chars_file, len, mut_rate),
|
||||
Commands::Cipher {
|
||||
alphabet,
|
||||
file,
|
||||
output,
|
||||
} => app::cipher_cmd(alphabet, file, output),
|
||||
Commands::Decipher {
|
||||
alphabet,
|
||||
file,
|
||||
output,
|
||||
} => app::decipher_cmd(alphabet, file, output),
|
||||
Commands::Tui { alphabet, file } => tui::run_decoder_tui(alphabet, file),
|
||||
}
|
||||
}
|
||||
32
src/storage.rs
Normal file
32
src/storage.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use crate::alphabet::Alphabet;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::fs::File;
|
||||
use std::io::{Error, ErrorKind, Read, Result, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct AlphabetFile<R: CryptoRng> {
|
||||
file: File,
|
||||
pub dict: Alphabet<R>,
|
||||
}
|
||||
|
||||
impl<R: CryptoRng + Rng> AlphabetFile<R> {
|
||||
pub fn create(path: PathBuf, dict: Alphabet<R>) -> Result<Self> {
|
||||
let file = File::create(path)?;
|
||||
Ok(Self { file, dict })
|
||||
}
|
||||
|
||||
pub fn save(mut self) -> Result<()> {
|
||||
self.file.write_all(self.dict.into_bytes().as_slice())
|
||||
}
|
||||
|
||||
pub fn open(name: PathBuf, rng: R) -> Result<Self> {
|
||||
let mut file = File::open(&name)?;
|
||||
let mut bytes = Vec::new();
|
||||
file.read_to_end(&mut bytes)?;
|
||||
|
||||
let dict =
|
||||
Alphabet::from_bytes(rng, &bytes).map_err(|e| Error::new(ErrorKind::InvalidData, e))?;
|
||||
|
||||
Ok(Self { file, dict })
|
||||
}
|
||||
}
|
||||
168
src/tui.rs
Normal file
168
src/tui.rs
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
use crate::ciphertext::Ciphertext;
|
||||
use crate::decoder::DecoderState;
|
||||
use crate::storage::AlphabetFile;
|
||||
|
||||
use crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
execute,
|
||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
|
||||
};
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
widgets::{Block, Borders, List, ListItem, Paragraph},
|
||||
};
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
struct TuiGuard;
|
||||
|
||||
impl Drop for TuiGuard {
|
||||
fn drop(&mut self) {
|
||||
let _ = disable_raw_mode();
|
||||
let _ = execute!(io::stdout(), LeaveAlternateScreen);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_visible_symbols(
|
||||
terminal: &Terminal<CrosstermBackend<std::io::Stdout>>,
|
||||
) -> io::Result<usize> {
|
||||
let size = terminal.size()?;
|
||||
let inner_width = size.width.saturating_sub(2) as usize;
|
||||
Ok(inner_width.max(1))
|
||||
}
|
||||
|
||||
pub fn run() -> io::Result<()> {
|
||||
todo!("call run_decoder_tui(alphabet_path, ciphertext_path) from CLI later")
|
||||
}
|
||||
|
||||
pub fn run_decoder_tui(alphabet_path: PathBuf, ciphertext_path: PathBuf) -> io::Result<()> {
|
||||
let alphabet_bytes = std::fs::read(&alphabet_path)?;
|
||||
let bytes = std::fs::read(ciphertext_path)?;
|
||||
let ciphertext = Ciphertext::from_bytes(&bytes).map_err(io::Error::other)?;
|
||||
|
||||
let rng_seed = [7u8; 32];
|
||||
let mut state = DecoderState::new(alphabet_bytes, rng_seed, ciphertext.bits)?;
|
||||
|
||||
enable_raw_mode()?;
|
||||
execute!(io::stdout(), EnterAlternateScreen)?;
|
||||
let _guard = TuiGuard;
|
||||
|
||||
let mut terminal = Terminal::new(CrosstermBackend::new(io::stdout()))?;
|
||||
|
||||
loop {
|
||||
terminal.draw(|f| draw_ui(f, &state))?;
|
||||
|
||||
if event::poll(Duration::from_millis(100))? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.kind != KeyEventKind::Press {
|
||||
continue;
|
||||
}
|
||||
|
||||
let visible = get_visible_symbols(&terminal)?;
|
||||
|
||||
match key.code {
|
||||
KeyCode::Char('q') => break,
|
||||
KeyCode::Left | KeyCode::Char('h') => state.move_left(visible),
|
||||
KeyCode::Right | KeyCode::Char('l') => state.move_right(visible),
|
||||
KeyCode::Char('u') => {
|
||||
let _ = state.undo_current();
|
||||
}
|
||||
KeyCode::Char('1') => {
|
||||
let _ = state.apply_candidate(0);
|
||||
}
|
||||
KeyCode::Char('2') => {
|
||||
let _ = state.apply_candidate(1);
|
||||
}
|
||||
KeyCode::Char('3') => {
|
||||
let _ = state.apply_candidate(2);
|
||||
}
|
||||
KeyCode::Char('4') => {
|
||||
let _ = state.apply_candidate(3);
|
||||
}
|
||||
KeyCode::Char('5') => {
|
||||
let _ = state.apply_candidate(4);
|
||||
}
|
||||
KeyCode::Char('m') => {
|
||||
state.set_ranking_mode(crate::decoder::RankingMode::LocalFirst);
|
||||
}
|
||||
KeyCode::Char('M') => {
|
||||
state.set_ranking_mode(crate::decoder::RankingMode::FutureFirst);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw_ui(f: &mut Frame, state: &DecoderState) {
|
||||
let layout = Layout::vertical([
|
||||
Constraint::Length(5),
|
||||
Constraint::Min(10),
|
||||
Constraint::Length(3),
|
||||
])
|
||||
.split(f.area());
|
||||
|
||||
draw_text_panel(f, layout[0], state);
|
||||
draw_candidates_panel(f, layout[1], state);
|
||||
draw_help_panel(f, layout[2]);
|
||||
}
|
||||
|
||||
fn draw_text_panel(f: &mut Frame, area: Rect, state: &DecoderState) {
|
||||
let cursor = state.cursor;
|
||||
let rendered: String = state
|
||||
.rendered_text
|
||||
.chars()
|
||||
.enumerate()
|
||||
.flat_map(|(i, ch)| {
|
||||
if i == cursor {
|
||||
vec!['[', ch, ']']
|
||||
} else {
|
||||
vec![ch]
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let p = Paragraph::new(rendered)
|
||||
.block(Block::default().title("Decoded text").borders(Borders::ALL));
|
||||
|
||||
f.render_widget(p, area);
|
||||
}
|
||||
|
||||
fn draw_candidates_panel(f: &mut Frame, area: Rect, state: &DecoderState) {
|
||||
let items = match state.current_candidates() {
|
||||
Ok(cands) => cands
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, c)| {
|
||||
ListItem::new(format!(
|
||||
"{}. {:?} local={} future={}",
|
||||
i + 1,
|
||||
c.symbol,
|
||||
c.distance,
|
||||
c.future_distance
|
||||
))
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
Err(err) => vec![ListItem::new(format!("error: {}", err))],
|
||||
};
|
||||
|
||||
let list = List::new(items).block(
|
||||
Block::default()
|
||||
.title("Top-5 candidates")
|
||||
.borders(Borders::ALL),
|
||||
);
|
||||
|
||||
f.render_widget(list, area);
|
||||
}
|
||||
|
||||
fn draw_help_panel(f: &mut Frame, area: Rect) {
|
||||
let p = Paragraph::new("h/← left l/→ right 1..5 choose u undo q quit")
|
||||
.block(Block::default().title("Keys").borders(Borders::ALL));
|
||||
|
||||
f.render_widget(p, area);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue