//! Общие утилиты командной строки для демонстрационных программ. //! //! Каждый пример компилируется как отдельный binary. Этот модуль убирает повторение: //! первый аргумент всегда serial-порт, второй - ширина дисплея, остальные параметры //! зависят от конкретного примера. #![allow( dead_code, reason = "модуль компилируется отдельно для каждого примера, поэтому часть API используется в соседних примерах" )] use escpos_vfd::{Preset, VfdConfig}; use std::error::Error; use std::str::FromStr; use std::vec::IntoIter; const DEFAULT_PORT: &str = "/dev/cu.usbmodem101"; const DEFAULT_WIDTH: usize = 20; /// Общие аргументы примера: готовая конфигурация, ширина дисплея и оставшиеся параметры. pub struct ExampleArgs { /// Конфигурация VFD на основе пресета текущего дисплея. pub config: VfdConfig, /// Ширина дисплея в символах. pub columns: usize, remaining: IntoIter, } impl ExampleArgs { /// Читает общие аргументы из командной строки и применяет значения по умолчанию. pub fn from_env() -> Result> { let mut values = std::env::args().skip(1); // Примеры должны запускаться без длинной CLI-команды на авторском стенде, но // первый аргумент позволяет сразу проверить другой USB/COM-порт. let port_name = values.next().unwrap_or_else(|| DEFAULT_PORT.to_string()); // Геометрия дисплея участвует в проверке координат и обрезке строк. Меняем // только ширину, потому что демонстрации ниже рассчитаны на две строки. let columns = values .next() .and_then(|value| value.parse().ok()) .unwrap_or(DEFAULT_WIDTH); let config = preset_config(port_name, columns)?; Ok(Self { columns, config, remaining: values.collect::>().into_iter(), }) } /// Читает следующий аргумент нужного типа или возвращает переданное значение по умолчанию. pub fn parse_or(&mut self, default: T) -> T where T: FromStr, { self.remaining .next() .and_then(|value| value.parse().ok()) .unwrap_or(default) } /// Читает следующий строковый аргумент или возвращает строку по умолчанию. pub fn string_or(&mut self, default: &str) -> String { self.remaining.next().unwrap_or_else(|| default.to_string()) } } /// Приостанавливает пример на указанное число миллисекунд. pub fn sleep_ms(delay_ms: u64) { std::thread::sleep(std::time::Duration::from_millis(delay_ms)); } /// Создаёт конфигурацию пресета текущего дисплея с переопределяемой шириной. pub fn preset_config( port_name: impl Into, columns: usize, ) -> Result> { let mut config = VfdConfig::preset(port_name, Preset::Epson20x2Cp866)?; // Пресет сохраняет baud/code table/кодировку, а ширину даём менять из CLI, чтобы // теми же примерами проверять 16x2, 20x2 и другие ESC/POS-совместимые VFD. config.display.columns = columns; config.validate()?; Ok(config) }