Загрузить файлы в «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