diff --git a/legacy/pyproject.toml b/legacy/pyproject.toml index f9cf5e2..ec1e27c 100644 --- a/legacy/pyproject.toml +++ b/legacy/pyproject.toml @@ -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" diff --git a/legacy/lcr_connector.py b/legacy/src/lcr_connector/__init__.py similarity index 55% rename from legacy/lcr_connector.py rename to legacy/src/lcr_connector/__init__.py index 3ff7f2a..66a37f0 100644 --- a/legacy/lcr_connector.py +++ b/legacy/src/lcr_connector/__init__.py @@ -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.", diff --git a/legacy/common.py b/legacy/src/lcr_connector/common.py similarity index 100% rename from legacy/common.py rename to legacy/src/lcr_connector/common.py diff --git a/legacy/dataset.py b/legacy/src/lcr_connector/dataset.py similarity index 99% rename from legacy/dataset.py rename to legacy/src/lcr_connector/dataset.py index 6d3b98b..726c20f 100644 --- a/legacy/dataset.py +++ b/legacy/src/lcr_connector/dataset.py @@ -1,6 +1,6 @@ from typing import Iterable from pathlib import Path -from common import LcrConnException +from .common import LcrConnException class Dataset: diff --git a/legacy/main.py b/legacy/src/lcr_connector/main.py similarity index 100% rename from legacy/main.py rename to legacy/src/lcr_connector/main.py diff --git a/legacy/query.py b/legacy/src/lcr_connector/query.py similarity index 98% rename from legacy/query.py rename to legacy/src/lcr_connector/query.py index 701e382..6baba5c 100644 --- a/legacy/query.py +++ b/legacy/src/lcr_connector/query.py @@ -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): diff --git a/legacy/resolver/__init__.py b/legacy/src/lcr_connector/resolver/__init__.py similarity index 100% rename from legacy/resolver/__init__.py rename to legacy/src/lcr_connector/resolver/__init__.py diff --git a/legacy/resolver/astar.py b/legacy/src/lcr_connector/resolver/astar.py similarity index 84% rename from legacy/resolver/astar.py rename to legacy/src/lcr_connector/resolver/astar.py index 35a569a..c4fe1e9 100644 --- a/legacy/resolver/astar.py +++ b/legacy/src/lcr_connector/resolver/astar.py @@ -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): """ diff --git a/legacy/resolver/common.py b/legacy/src/lcr_connector/resolver/common.py similarity index 100% rename from legacy/resolver/common.py rename to legacy/src/lcr_connector/resolver/common.py diff --git a/legacy/resolver/lut.py b/legacy/src/lcr_connector/resolver/lut.py similarity index 100% rename from legacy/resolver/lut.py rename to legacy/src/lcr_connector/resolver/lut.py diff --git a/legacy/uv.lock b/legacy/uv.lock index c774378..93837ed 100644 --- a/legacy/uv.lock +++ b/legacy/uv.lock @@ -4,5 +4,5 @@ requires-python = ">=3.11" [[package]] name = "lcr-connector" -version = "0.1.0" -source = { virtual = "." } +version = "1.0.0" +source = { editable = "." }