Rename crate to escpos-vfd and add tokio support Introduce a configurable crate-ready API with presets, typed errors, optional Tokio async support, and expanded documentation.
87 lines
4.0 KiB
Rust
87 lines
4.0 KiB
Rust
//! Общие утилиты командной строки для демонстрационных программ.
|
|
//!
|
|
//! Каждый пример компилируется как отдельный 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<String>,
|
|
}
|
|
|
|
impl ExampleArgs {
|
|
/// Читает общие аргументы из командной строки и применяет значения по умолчанию.
|
|
pub fn from_env() -> Result<Self, Box<dyn Error>> {
|
|
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::<Vec<_>>().into_iter(),
|
|
})
|
|
}
|
|
|
|
/// Читает следующий аргумент нужного типа или возвращает переданное значение по умолчанию.
|
|
pub fn parse_or<T>(&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<String>,
|
|
columns: usize,
|
|
) -> Result<VfdConfig, Box<dyn Error>> {
|
|
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)
|
|
}
|