Загрузить файлы в «src»

This commit is contained in:
Gregory Bednov 2026-04-09 21:50:36 +03:00
commit b9ecffc77a
3 changed files with 235 additions and 0 deletions

35
src/main.rs Normal file
View 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
View 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
View 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);
}