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]
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."
readme = "README.md"
authors = [
{ name = "yyc12345", email = "yyc12321@outlook.com" }
]
requires-python = ">=3.11"
classifiers = [
"Private :: Do Not Upload",
]
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
from dataclasses import dataclass
from pathlib import Path
from typing import TypeVar, cast
from common import Circuit, CircuitDeviceScale, JointKind
from dataset import (
from typing import TypeVar, Callable
from .common import Circuit, CircuitDeviceScale, JointKind, DeviceKind
from .dataset import (
DatasetCollection,
to_human_readable_value,
from_human_readable_value,
)
from query import Response
from resolver import Resolver, LutResolver, AStarResolver
from .query import Request, ResponsePriority, Response
from .resolver import Resolver, LutResolver, AStarResolver
_T = TypeVar("_T", bound=str)
_TStrEnum = TypeVar("_TStrEnum", bound=enum.StrEnum)
class AppResolver(enum.StrEnum):
@@ -74,13 +74,46 @@ class App:
"""
print("LCR Connector")
print('Type "help" for more info. Type "exit" to quit.')
print("query: do a query.")
print("help: show all command.")
print("exit: exit this app.")
self.__op_main()
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:
shit = self.__accept_command(("query", "help", "exit"))
match self.__accept_command(("query", "help", "exit")):
match self.__accept_command(App.MainCmd):
case "query":
self.__op_query()
case "help":
@@ -93,7 +126,35 @@ class App:
break
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:
cnt = len(response)
@@ -127,37 +188,95 @@ class App:
print("Page {} of {}.".format(current_page + 1, all_page + 1))
print("f: previous page. b: next page. q: quit this viewer.")
# check command
match self.__accept_command(("f", "b", "q")):
case "f":
match self.__accept_command(App.PageViewerCmd):
case App.PageViewerCmd.PREVIOUS_PAGE:
current_page = max(0, current_page - 1)
case "b":
case App.PageViewerCmd.NEXT_PAGE:
current_page = min(all_page, current_page + 1)
case "q":
case App.PageViewerCmd.QUIT:
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:
print("> ", end=None)
words = input()
words = words.strip()
if words in valid_commands:
return cast(_T, words)
if words in cmd_enum:
return cmd_enum(words)
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:
words = input()
try:
pending = from_human_readable_value(words)
except ValueError:
print("Wrong value, please try again.")
continue
if pending > 0:
return pending
if words.endswith("%"):
value = self.__parse_plain_float(
words[:-1], lambda x: x >= 0 and x <= 100
)
if value is not None:
value = value / 100 * target_value
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:
match joint_kind:
@@ -188,7 +307,7 @@ class App:
print(f"{dev3}")
if __name__ == "__main__":
def main() -> None:
parser = argparse.ArgumentParser(
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.",

View File

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

View File

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

View File

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

4
legacy/uv.lock generated
View File

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