feat: finish legacy project and move it as packaged python project
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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.",
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import Iterable
|
||||
from pathlib import Path
|
||||
from common import LcrConnException
|
||||
from .common import LcrConnException
|
||||
|
||||
|
||||
class Dataset:
|
||||
@@ -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):
|
||||
@@ -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
4
legacy/uv.lock
generated
@@ -4,5 +4,5 @@ requires-python = ">=3.11"
|
||||
|
||||
[[package]]
|
||||
name = "lcr-connector"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
version = "1.0.0"
|
||||
source = { editable = "." }
|
||||
|
||||
Reference in New Issue
Block a user