1
0

feat: use AI to migrate project (no fix now)

This commit is contained in:
2026-06-28 20:47:00 +08:00
parent 7665de0889
commit aa6c4f72bd
10 changed files with 2551 additions and 15 deletions

View File

@@ -1,3 +1,641 @@
fn main() {
println!("Hello, world!");
use std::io::{self, Write};
use std::path::PathBuf;
use clap::Parser;
use lcrconn::{
from_human_readable_value, to_human_readable_value, BfsResolver, Circuit, CircuitDeviceScale,
DatasetCollection, DeviceKind, JointKind, LcrConnError, LutResolver, Request, Resolver,
Response, ResponsePriority, MAX_RESPONSE_CNT,
};
// ============================================================================
// Command-line arguments
// ============================================================================
/// The resolver for the app.
#[derive(Clone, Debug, clap::ValueEnum)]
pub enum AppResolver {
/// The look-up table resolver.
#[value(name = "lut")]
Lut,
/// The BFS resolver.
#[value(name = "bfs")]
Bfs,
}
/// The configuration for the app.
struct AppConfig {
/// The resolver for the app.
resolver: AppResolver,
/// The path to the resistor dataset file.
resistor_dataset: PathBuf,
/// The path to the capacitor dataset file.
capacitor_dataset: PathBuf,
/// The path to the inductor dataset file.
inductor_dataset: PathBuf,
}
/// Get the resistor, capacitor, or inductor circuit which has the closest value
/// for your given value within at most 3 devices.
#[derive(Parser)]
#[command(
name = "LCR Connector",
about = "Get the resistor, capacitor, or inductor circuit which has the closest value for your given value within at most 3 devices."
)]
struct Args {
/// The resolver you want to use.
#[arg(short = 's', long)]
resolver: AppResolver,
/// The path to the resistor dataset file.
#[arg(short = 'r', long, value_name = "RESISTOR.TXT")]
resistor_dataset: PathBuf,
/// The path to the inductor dataset file.
#[arg(short = 'l', long, value_name = "INDUCTOR.TXT")]
inductor_dataset: PathBuf,
/// The path to the capacitor dataset file.
#[arg(short = 'c', long, value_name = "CAPACITOR.TXT")]
capacitor_dataset: PathBuf,
}
impl From<Args> for AppConfig {
fn from(args: Args) -> Self {
Self {
resolver: args.resolver,
resistor_dataset: args.resistor_dataset,
capacitor_dataset: args.capacitor_dataset,
inductor_dataset: args.inductor_dataset,
}
}
}
// ============================================================================
// Interactive command enums
// ============================================================================
/// The command for the main menu.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum MainCmd {
Query,
Help,
Exit,
}
fn parse_main_cmd(s: &str) -> Option<MainCmd> {
match s {
"query" => Some(MainCmd::Query),
"help" => Some(MainCmd::Help),
"exit" => Some(MainCmd::Exit),
_ => None,
}
}
/// The device choice for query.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum QueryDeviceChoice {
Resistor,
Capacitor,
Inductor,
}
impl QueryDeviceChoice {
fn parse(s: &str) -> Option<Self> {
match s {
"r" => Some(Self::Resistor),
"c" => Some(Self::Capacitor),
"l" => Some(Self::Inductor),
_ => None,
}
}
fn to_device_kind(self) -> DeviceKind {
match self {
Self::Resistor => DeviceKind::Resistor,
Self::Capacitor => DeviceKind::Capacitor,
Self::Inductor => DeviceKind::Inductor,
}
}
}
/// The sort priority for query results.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum QuerySortPriority {
LessDevices,
MoreAccuracy,
}
impl QuerySortPriority {
fn parse(s: &str) -> Option<Self> {
match s {
"l" => Some(Self::LessDevices),
"a" => Some(Self::MoreAccuracy),
_ => None,
}
}
fn to_response_priority(self) -> ResponsePriority {
match self {
Self::LessDevices => ResponsePriority::LessDevices,
Self::MoreAccuracy => ResponsePriority::MoreAccuracy,
}
}
}
/// The command for the page viewer.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum PageViewerCmd {
PreviousPage,
NextPage,
Quit,
}
impl PageViewerCmd {
fn parse(s: &str) -> Option<Self> {
match s {
"f" => Some(Self::PreviousPage),
"b" => Some(Self::NextPage),
"q" => Some(Self::Quit),
_ => None,
}
}
}
// ============================================================================
// Input utilities
// ============================================================================
/// Read a single line from stdin, trimmed of surrounding whitespace.
fn read_line() -> String {
let mut line = String::new();
io::stdin()
.read_line(&mut line)
.expect("Failed to read from stdin");
line.trim().to_string()
}
/// Parse a plain float value.
///
/// # Arguments
///
/// * `user_value` - The value to parse.
/// * `checker` - A function that checks if the input is valid.
/// It takes a float as input and returns a bool. True means the input is valid,
/// otherwise False.
///
/// # Returns
///
/// The parsed value if it is valid, otherwise `None`.
fn parse_plain_float(user_value: &str, checker: impl Fn(&f64) -> bool) -> Option<f64> {
// try parsing it first
let value = user_value.parse::<f64>().ok()?;
// then check it by checker
if checker(&value) {
Some(value)
} else {
None
}
}
/// Parse a human-readable device value.
///
/// # Arguments
///
/// * `user_value` - The value to parse.
///
/// # Returns
///
/// The parsed value if it is valid and positive, otherwise `None`.
fn parse_human_readable_value(user_value: &str) -> Option<f64> {
// parse it
let value = from_human_readable_value(user_value).ok()?;
// then check its range
if value > 0.0 {
Some(value)
} else {
None
}
}
// ============================================================================
// Response display utilities
// ============================================================================
/// Get the unit string for a device kind.
fn get_device_unit(device_kind: DeviceKind) -> &'static str {
match device_kind {
DeviceKind::Resistor => "\u{2126}",
DeviceKind::Capacitor => "F",
DeviceKind::Inductor => "H",
}
}
/// Format a device value for display in the circuit graph.
fn to_circuit_graph_value(value: f64, device_kind: DeviceKind) -> String {
// Remove sign and append device unit
let hr = to_human_readable_value(value);
let without_sign = &hr[1..];
format!("{}{}", without_sign, get_device_unit(device_kind))
}
/// Format a device value for the plan header.
fn to_plan_head_value(value: f64, device_kind: DeviceKind) -> String {
// Remove sign and append device unit
let hr = to_human_readable_value(value);
let without_sign = &hr[1..];
format!("{}{}", without_sign, get_device_unit(device_kind))
}
/// Format a difference value for the plan header.
fn to_plan_head_diff(value: f64, device_kind: DeviceKind) -> String {
// Keep the sign and append device unit
format!("{}{}", to_human_readable_value(value), get_device_unit(device_kind))
}
/// Format a relative difference as percentage.
fn to_plan_head_diff_pct(value: f64) -> String {
// Keep the sign and format it as percentage style without trailing device unit
format!("{:.2}%", value * 100.0)
}
// YYC MARK:
// The function showing circuit graph should be maintained carefully.
// First, we want they are show in console properly,
// And we also want they have good code view.
//
// I notices that the number part of the output of `to_human_readable_value` will only be
// "+999.9999" or "+9.9999e+00". So its maximum of its length is 11, considering the possibility,
// that the absolute value of exponential part is larger than 99, is close to zero.
// After putting the scale unit and device unit together like " nF",
// the whole maximum size of the built string is 14.
//
// So we need pick a larger number and odd number for the space for showing device value,
// because odd value can be divided by two so it can be split as two parts equally
// for the convenient alignment of some circuit graphs.
// My picked value is 16.
// So you will see that I use `:^16` for a center alignment to given string.
//
// After this, we also need set the padding value carefully.
// This value should consider the length of f-string syntax, pre-defined chars and required chars.
// To make sure a pretty showcase both in code and display.
/// Illustrate a one-device circuit.
fn illustrate_one_device_circuit(circuit: &Circuit, device_kind: DeviceKind) {
let dev1 = to_circuit_graph_value(circuit.first_device_value(), device_kind);
println!("──[{:^16}]──", dev1);
}
/// Illustrate a two-device circuit.
fn illustrate_two_device_circuit(circuit: &Circuit, device_kind: DeviceKind) -> Result<(), LcrConnError> {
let dev1 = to_circuit_graph_value(circuit.first_device_value(), device_kind);
let j2 = circuit.second_device_joint()?;
let dev2 = to_circuit_graph_value(circuit.second_device_value()?, device_kind);
match j2 {
JointKind::Series => {
println!("──[{:^16}]──[{:^16}]──", dev1, dev2);
}
JointKind::Parallel => {
let sep0 = " ".repeat(6 + (16 - 10));
println!(" ┌──[{:^16}]──┐ ", dev1);
println!("──┤ {} ├──", sep0);
println!(" └──[{:^16}]──┘ ", dev2);
}
}
Ok(())
}
/// Illustrate a three-device circuit.
fn illustrate_three_device_circuit(circuit: &Circuit, device_kind: DeviceKind) -> Result<(), LcrConnError> {
let dev1 = to_circuit_graph_value(circuit.first_device_value(), device_kind);
let j2 = circuit.second_device_joint()?;
let dev2 = to_circuit_graph_value(circuit.second_device_value()?, device_kind);
let j3 = circuit.third_device_joint()?;
let dev3 = to_circuit_graph_value(circuit.third_device_value()?, device_kind);
match j2 {
JointKind::Series => match j3 {
JointKind::Series => {
// All in series
println!("──[{:^16}]──[{:^16}]──[{:^16}]──", dev1, dev2, dev3);
}
JointKind::Parallel => {
// First series then parallel
let sep0 = "\u{2500}".repeat(6 + ((16 - 10) / 2));
let sep1 = " ".repeat(6 + 2 * (16 - 10));
println!(" ┌──[{:^16}]──[{:^16}]──┐ ", dev1, dev2);
println!("──┤ {} ├──", sep1);
println!(" └───{}[{:^16}]{}───┘ ", sep0, dev3, sep0);
}
},
JointKind::Parallel => match j3 {
JointKind::Series => {
// First parallel then series
let sep0 = " ".repeat(6 + (16 - 10));
println!(" {} ┌──[{:^16}]──┐ ", sep0, dev1);
println!("──[{:^16}]──┤ {} ├──", dev3, sep0);
println!(" {} └──[{:^16}]──┘ ", sep0, dev2);
}
JointKind::Parallel => {
// All in parallel
println!(" ┌──[{:^16}]──┐ ", dev1);
println!("──┼──[{:^16}]──┼──", dev2);
println!(" └──[{:^16}]──┘ ", dev3);
}
},
}
Ok(())
}
/// Illustrate a circuit based on its device scale.
fn illustrate_circuit(circuit: &Circuit, device_kind: DeviceKind) -> Result<(), LcrConnError> {
match circuit.device_scale() {
CircuitDeviceScale::One => {
illustrate_one_device_circuit(circuit, device_kind);
}
CircuitDeviceScale::Two => {
illustrate_two_device_circuit(circuit, device_kind)?;
}
CircuitDeviceScale::Three => {
illustrate_three_device_circuit(circuit, device_kind)?;
}
}
Ok(())
}
/// Illustrate a response item.
fn illustrate_response(response: &Response, index: usize) -> Result<(), LcrConnError> {
let item = &response[index];
let device_kind = response.device_kind();
// print header
println!(
"Plan {:<4} Value: {:<16} Diff: {} ({})",
index + 1,
to_plan_head_value(item.value(), device_kind),
to_plan_head_diff(item.difference(), device_kind),
to_plan_head_diff_pct(item.relative_difference()),
);
// print circuit graph
illustrate_circuit(item.circuit(), device_kind)?;
Ok(())
}
// ============================================================================
// App
// ============================================================================
/// The app.
struct App {
/// The resolver for the app.
resolver: Box<dyn Resolver>,
}
impl App {
/// Create a new app with the given configuration.
///
/// # Errors
///
/// See [`DatasetCollection::from_file`] and [`LutResolver::new`].
fn new(config: AppConfig) -> Result<Self, LcrConnError> {
let datasets = DatasetCollection::from_file(
&config.resistor_dataset,
&config.capacitor_dataset,
&config.inductor_dataset,
)?;
let resolver: Box<dyn Resolver> = match config.resolver {
AppResolver::Lut => Box::new(LutResolver::new(&datasets)?),
AppResolver::Bfs => Box::new(BfsResolver::new(datasets)),
};
Ok(Self { resolver })
}
/// Run the app.
fn run(&self) -> Result<(), LcrConnError> {
println!("LCR Connector");
println!("Type \"help\" for more info. Type \"exit\" to quit.");
self.op_main()?;
Ok(())
}
// ========================================================================
// Subcommand Processors
// ========================================================================
fn op_main(&self) -> Result<(), LcrConnError> {
loop {
match self.accept_command(parse_main_cmd) {
MainCmd::Query => self.op_query()?,
MainCmd::Help => {
println!("LCR Connector Help:");
println!();
println!("query: do a query.");
println!("help: show all command.");
println!("exit: exit this app.");
}
MainCmd::Exit => break,
}
}
Ok(())
}
fn op_query(&self) -> Result<(), LcrConnError> {
// collecting request infos
println!("What are you connecting?");
println!("r: resistor");
println!("l: inductor");
println!("c: capacitor");
let device_kind = self
.accept_command(QueryDeviceChoice::parse)
.to_device_kind();
println!("Your target value?");
println!("Example: \"2.1k\", \"0.75m\", \"3.2M\" and etc.");
let target_value = self.accept_device_value();
println!("Your tolerance?");
println!("It can be absolute value like \"2.1k\".");
println!("Or relative value to your target value like \"19.5%\".");
let tolerance = self.accept_device_value_tolerance(target_value);
println!("How to sort result?");
println!("a: more accuracy");
println!("l: less component");
let response_priority = self
.accept_command(QuerySortPriority::parse)
.to_response_priority();
println!("How may result are you expected?");
let count_limit = self.accept_count_value();
// build request and ask resolver
let request = Request::new(
device_kind,
target_value,
tolerance,
response_priority,
count_limit,
)?;
let response = self.resolver.resolve(&request)?;
// use page viewer to show result
self.op_page_viewer(&response)?;
Ok(())
}
fn op_page_viewer(&self, response: &Response) -> Result<(), LcrConnError> {
let cnt = response.len();
if cnt == 0 {
println!("Sorry, no result!");
println!("Please consider adjusting your requirements and try again.");
return Ok(());
}
const ITEMS_PER_PAGE: usize = 10;
let all_page = cnt / ITEMS_PER_PAGE;
let mut current_page = 0usize;
loop {
// print list
for i in 0..ITEMS_PER_PAGE - 1 {
// build index and check it
let index = current_page * (ITEMS_PER_PAGE - 1) + i;
if index >= cnt {
continue;
}
// and print it
illustrate_response(response, index)?;
}
// print page footer
println!();
println!("Page {} of {}.", current_page + 1, all_page + 1);
println!("f: previous page. b: next page. q: quit this viewer.");
// check command
match self.accept_command(PageViewerCmd::parse) {
PageViewerCmd::PreviousPage => current_page = current_page.saturating_sub(1),
PageViewerCmd::NextPage => current_page = all_page.min(current_page + 1),
PageViewerCmd::Quit => break,
}
}
Ok(())
}
// ========================================================================
// Command Utilities
// ========================================================================
/// Accept a command from the user.
///
/// Loops until a valid command is entered.
fn accept_command<T>(&self, parser: impl Fn(&str) -> Option<T>) -> T {
loop {
self.show_prompt_arrow();
let words = read_line();
if words.is_empty() {
continue;
}
match parser(&words) {
Some(cmd) => return cmd,
None => println!("Unknown command, please try again."),
}
}
}
/// Accept a count value from the user.
fn accept_count_value(&self) -> usize {
loop {
self.show_prompt_arrow();
let words = read_line();
if words.is_empty() {
continue;
}
match words.parse::<usize>() {
Ok(value) => {
if value > MAX_RESPONSE_CNT || value == 0 {
println!("Wrong value, please try again.");
} else {
return value;
}
}
Err(_) => {
println!("Wrong value, please try again.");
}
}
}
}
/// Accept a device value from the user.
fn accept_device_value(&self) -> f64 {
loop {
self.show_prompt_arrow();
let words = read_line();
if words.is_empty() {
continue;
}
let value = parse_human_readable_value(&words);
match value {
Some(v) => return v,
None => println!("Wrong value, please try again."),
}
}
}
/// Accept a tolerance value from the user.
///
/// The tolerance can be an absolute value (like "2.1k") or a percentage
/// relative to the target value (like "19.5%").
fn accept_device_value_tolerance(&self, target_value: f64) -> f64 {
loop {
self.show_prompt_arrow();
let words = read_line();
if words.is_empty() {
continue;
}
let value: Option<f64> = if let Some(pct_str) = words.strip_suffix('%') {
let value = parse_plain_float(pct_str, |x| *x >= 0.0 && *x <= 100.0);
value.map(|v| v / 100.0 * target_value)
} else {
parse_human_readable_value(&words)
};
match value {
Some(v) => return v,
None => println!("Wrong value, please try again."),
}
}
}
fn show_prompt_arrow(&self) {
print!("> ");
io::stdout().flush().expect("Failed to flush stdout");
}
}
// ============================================================================
// Entry point
// ============================================================================
fn main() {
let args = Args::parse();
let config = AppConfig::from(args);
let app = match App::new(config) {
Ok(app) => app,
Err(e) => {
eprintln!("Error: {}", e);
std::process::exit(1);
}
};
if let Err(e) = app.run() {
eprintln!("Error: {}", e);
std::process::exit(1);
}
}