1
0

feat: finish legacy project and move it as packaged python project

This commit is contained in:
2026-06-16 20:44:57 +08:00
parent 480e11dbf4
commit a27c5505df
11 changed files with 169 additions and 36 deletions

View File

@@ -1,7 +1,20 @@
[project] [project]
name = "lcr-connector" name = "lcr-connector"
version = "0.1.0" version = "1.0.0"
description = "Use as much 3 devices to reach target value for resistor, capacitor and inductor." description = "Use as much 3 devices to reach target value for resistor, capacitor and inductor."
readme = "README.md" readme = "README.md"
authors = [
{ name = "yyc12345", email = "yyc12321@outlook.com" }
]
requires-python = ">=3.11" requires-python = ">=3.11"
classifiers = [
"Private :: Do Not Upload",
]
dependencies = [] dependencies = []
[project.scripts]
lcr-connector = "lcr_connector:main"
[build-system]
requires = ["uv_build>=0.8.0,<0.9"]
build-backend = "uv_build"

View File

@@ -2,17 +2,17 @@ import enum
import argparse import argparse
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import TypeVar, cast from typing import TypeVar, Callable
from common import Circuit, CircuitDeviceScale, JointKind from .common import Circuit, CircuitDeviceScale, JointKind, DeviceKind
from dataset import ( from .dataset import (
DatasetCollection, DatasetCollection,
to_human_readable_value, to_human_readable_value,
from_human_readable_value, from_human_readable_value,
) )
from query import Response from .query import Request, ResponsePriority, Response
from resolver import Resolver, LutResolver, AStarResolver from .resolver import Resolver, LutResolver, AStarResolver
_T = TypeVar("_T", bound=str) _TStrEnum = TypeVar("_TStrEnum", bound=enum.StrEnum)
class AppResolver(enum.StrEnum): class AppResolver(enum.StrEnum):
@@ -74,13 +74,46 @@ class App:
""" """
print("LCR Connector") print("LCR Connector")
print('Type "help" for more info. Type "exit" to quit.') print('Type "help" for more info. Type "exit" to quit.')
print("query: do a query.") self.__op_main()
print("help: show all command.")
print("exit: exit this app.")
class MainCmd(enum.StrEnum):
QUERY = "query"
HELP = "help"
EXIT = "exit"
class QueryDeviceChoice(enum.StrEnum):
RESISTOR = "r"
CAPACITOR = "c"
INDUCTOR = "l"
def to_device_kind(self) -> DeviceKind:
match self:
case App.QueryDeviceChoice.RESISTOR:
return DeviceKind.RESISTOR
case App.QueryDeviceChoice.CAPACITOR:
return DeviceKind.CAPACITOR
case App.QueryDeviceChoice.INDUCTOR:
return DeviceKind.INDUCTOR
class QuerySortPriority(enum.StrEnum):
LESS_DEVICES = "l"
MORE_ACCURACY = "a"
def to_response_priority(self) -> ResponsePriority:
match self:
case App.QuerySortPriority.LESS_DEVICES:
return ResponsePriority.LESS_DEVICES
case App.QuerySortPriority.MORE_ACCURACY:
return ResponsePriority.MORE_ACCURACY
class PageViewerCmd(enum.StrEnum):
PREVIOUS_PAGE = "f"
NEXT_PAGE = "b"
QUIT = "q"
def __op_main(self) -> None:
while True: while True:
shit = self.__accept_command(("query", "help", "exit")) match self.__accept_command(App.MainCmd):
match self.__accept_command(("query", "help", "exit")):
case "query": case "query":
self.__op_query() self.__op_query()
case "help": case "help":
@@ -93,7 +126,35 @@ class App:
break break
def __op_query(self) -> None: def __op_query(self) -> None:
pass # collecting request infos
print("What are you connecting?")
print("r: resistor")
print("l: inductor")
print("c: capacitor")
device_kind = self.__accept_command(App.QueryDeviceChoice).to_device_kind()
print("Your target value?")
print('Example: "2.1k", "0.75m", "3.2M" and etc.')
target_value = self.__accept_device_value()
print("Your tolerance?")
print('It can be absolute value like "2.1k".')
print('Or relative value to your target value like "19.5%".')
tolerance = self.__accept_device_value_tolerance(target_value)
print("How to sort result?")
print("l: less component")
print("a: more accuracy")
response_priority = self.__accept_command(
App.QuerySortPriority
).to_response_priority()
# build request and ask resolver
request = Request(device_kind, target_value, tolerance, response_priority, 100)
response = self.__resolver.resolve(request)
# use page viewer to show result
self.__op_page_viewer(response)
def __op_page_viewer(self, response: Response) -> None: def __op_page_viewer(self, response: Response) -> None:
cnt = len(response) cnt = len(response)
@@ -127,37 +188,95 @@ class App:
print("Page {} of {}.".format(current_page + 1, all_page + 1)) print("Page {} of {}.".format(current_page + 1, all_page + 1))
print("f: previous page. b: next page. q: quit this viewer.") print("f: previous page. b: next page. q: quit this viewer.")
# check command # check command
match self.__accept_command(("f", "b", "q")): match self.__accept_command(App.PageViewerCmd):
case "f": case App.PageViewerCmd.PREVIOUS_PAGE:
current_page = max(0, current_page - 1) current_page = max(0, current_page - 1)
case "b": case App.PageViewerCmd.NEXT_PAGE:
current_page = min(all_page, current_page + 1) current_page = min(all_page, current_page + 1)
case "q": case App.PageViewerCmd.QUIT:
break break
def __accept_command(self, valid_commands: tuple[_T, ...]) -> _T: def __accept_command(self, cmd_enum: type[_TStrEnum]) -> _TStrEnum:
"""
Accept a command from the user.
:param cmd_enum: The type of the command. It must be a subclass of `enum.StrEnum`.
:return: The command. It is an instance of `cmd_enum`.
"""
while True: while True:
print("> ", end=None) print("> ", end=None)
words = input() words = input()
words = words.strip() words = words.strip()
if words in valid_commands: if words in cmd_enum:
return cast(_T, words) return cmd_enum(words)
print("Unknown command, please try again.") print("Unknown command, please try again.")
def __accept_value_input(self) -> float: def __accept_device_value(self) -> float:
while True:
words = input()
value = self.__parse_human_readable_value(words)
if value is None:
print("Wrong value, please try again.")
else:
return value
def __accept_device_value_tolerance(self, target_value: float) -> float:
while True: while True:
words = input() words = input()
try: if words.endswith("%"):
pending = from_human_readable_value(words) value = self.__parse_plain_float(
except ValueError: words[:-1], lambda x: x >= 0 and x <= 100
print("Wrong value, please try again.") )
continue if value is not None:
value = value / 100 * target_value
if pending > 0:
return pending
else: else:
print("Value out of range, please try again.") value = self.__parse_human_readable_value(words)
if value is None:
print("Wrong value, please try again.")
else:
return value
def __parse_plain_float(
self, user_value: str, checker: Callable[[float], bool]
) -> float | None:
"""
Parse a plain float value.
:param user_value: The value to parse.
:param 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.
:return: The parsed value if it is valid, otherwise None.
"""
# try parsing it first
try:
value = float(user_value)
except ValueError:
return None
# then check it by checker
if checker(value):
return value
else:
return None
def __parse_human_readable_value(self, user_value: str) -> float | None:
"""
Parse a human-readable device value.
:param user_value: The value to parse.
:return: The parsed value if it is valid, otherwise None.
"""
# parse it
try:
value = from_human_readable_value(user_value)
except ValueError:
return None
# then check its range
if value > 0:
return value
else:
return None
def __get_joint_kind_symbol(self, joint_kind: JointKind) -> str: def __get_joint_kind_symbol(self, joint_kind: JointKind) -> str:
match joint_kind: match joint_kind:
@@ -188,7 +307,7 @@ class App:
print(f"{dev3}") print(f"{dev3}")
if __name__ == "__main__": def main() -> None:
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
prog="LCR Connector", prog="LCR Connector",
description="Get the resistor, capacitor, or inductor circuit which has the closest value for your given value within at most 3 devices.", description="Get the resistor, capacitor, or inductor circuit which has the closest value for your given value within at most 3 devices.",

View File

@@ -1,6 +1,6 @@
from typing import Iterable from typing import Iterable
from pathlib import Path from pathlib import Path
from common import LcrConnException from .common import LcrConnException
class Dataset: class Dataset:

View File

@@ -2,7 +2,7 @@ import enum
from functools import cached_property from functools import cached_property
from dataclasses import dataclass from dataclasses import dataclass
from typing import Iterator from typing import Iterator
from common import DeviceKind, Circuit from .common import DeviceKind, Circuit
class ResponsePriority(enum.Enum): class ResponsePriority(enum.Enum):

View File

@@ -1,7 +1,8 @@
from typing import Iterator from typing import Iterator
from .common import Resolver, Request, ResultPriority from .common import Resolver
from ..dataset import DatasetCollection from ..dataset import DatasetCollection
from ..common import Circuit from ..common import Circuit
from ..query import Request, Response
class AStarResolver(Resolver): class AStarResolver(Resolver):
""" """

4
legacy/uv.lock generated
View File

@@ -4,5 +4,5 @@ requires-python = ">=3.11"
[[package]] [[package]]
name = "lcr-connector" name = "lcr-connector"
version = "0.1.0" version = "1.0.0"
source = { virtual = "." } source = { editable = "." }