diff --git a/src/linktools/__init__.py b/src/linktools/__init__.py index bde5e026..a9dbd779 100644 --- a/src/linktools/__init__.py +++ b/src/linktools/__init__.py @@ -34,4 +34,4 @@ from ._logging import LogHandler from ._config import Config from ._tools import ToolContainer, ToolExecError, Tool -from ._environ import Environ, environ +from ._environ import BaseEnviron, environ diff --git a/src/linktools/__main__.py b/src/linktools/__main__.py index b1701080..d0ec438a 100644 --- a/src/linktools/__main__.py +++ b/src/linktools/__main__.py @@ -33,10 +33,9 @@ from rich import get_console from rich.tree import Tree -from . import cli from ._environ import environ +from .cli import BaseCommand, walk_commands from .decorator import cached_property -from .version import __description__ class CategoryInfo: @@ -52,11 +51,14 @@ def __repr__(self): class CommandInfo: - def __init__(self, name: str, category: CategoryInfo, command: cli.Command): - self.name = name + def __init__(self, category: CategoryInfo, command: BaseCommand): self.category = category self.command = command + @property + def name(self): + return self.command.name + @property def description(self): return self.command.description @@ -65,7 +67,7 @@ def __repr__(self): return self.name -class Command(cli.Command): +class Command(BaseCommand): @cached_property def _commands(self): @@ -80,10 +82,9 @@ def _commands(self): for category in module_categories: commands[category] = [] path = os.path.join(module_path, category.name) - for name, command in cli.walk_commands(path): + for command in walk_commands(path): commands[category].append( CommandInfo( - name=name, category=category, command=command, ) @@ -123,7 +124,8 @@ def run(self, args: [str]) -> Optional[int]: node.add(f"👉 {command.category.prefix}[bold red]{command.name}[/bold red]: {command.description}") console = get_console() - console.print(__description__, highlight=False) + if environ.description: + console.print(environ.description, highlight=False) console.print(tree, highlight=False) diff --git a/src/linktools/_environ.py b/src/linktools/_environ.py index e432a258..ed59fba6 100644 --- a/src/linktools/_environ.py +++ b/src/linktools/_environ.py @@ -26,6 +26,7 @@ / ==ooooooooooooooo==.o. ooo= // ,`\--{)B ," /_==__==========__==_ooo__ooo=_/' /___________," """ +import abc import json import os import pathlib @@ -39,74 +40,72 @@ __description__ as __module_description__, \ __version__ as __module_version__ -_T = TypeVar("_T") +T = TypeVar("T") +root_path = os.path.dirname(__file__) +asset_path = os.path.join(root_path, "assets") -class Environ: +class BaseEnviron(abc.ABC): __missing__ = object() @property + @abc.abstractmethod def name(self) -> str: - return __module_name__ - - @property - def description(self) -> str: - return __module_description__ + """ + 模块名 + """ + pass @property + @abc.abstractmethod def version(self) -> str: - return __module_version__ - - @cached_property - def logger(self): - from ._logging import get_logger + """ + 模块版本号 + """ + pass - return get_logger(prefix=self.name) - - def get_logger(self, name: str = None): - from ._logging import get_logger - - return get_logger(name=name, prefix=self.name) + @property + def description(self) -> str: + """ + 模块描述 + """ + return "" - @cached_property + @property + @abc.abstractmethod def root_path(self) -> str: - return os.path.abspath(os.path.join(os.path.dirname(__file__))) - - def get_asset_path(self, *paths: [str]): - return self._get_path(self.root_path, "assets", *paths, create=False, create_parent=False) + """ + 模块根目录 + """ + pass @cached_property def data_path(self): - path = self.get_config("SETTING_DATA_PATH") + """ + 存放文件目录 + """ + path = self.get_config("DATA_PATH") if not path: - path = os.path.join(self.get_config("SETTING_STORAGE_PATH"), "data") + path = os.path.join(self.get_config("STORAGE_PATH"), "data") return path @cached_property def temp_path(self): - path = self.get_config("SETTING_TEMP_PATH") + """ + 存放临时文件目录 + """ + path = self.get_config("TEMP_PATH") if not path: - path = os.path.join(self.get_config("SETTING_STORAGE_PATH"), "temp") + path = os.path.join(self.get_config("STORAGE_PATH"), "temp") return path - def get_data_path(self, *paths: [str], create_parent: bool = False): - return self._get_path(self.data_path, *paths, create=False, create_parent=create_parent) - - def get_data_dir(self, *paths: [str], create: bool = False): - return self._get_path(self.data_path, *paths, create=create, create_parent=False) - - def get_temp_path(self, *paths: [str], create_parent: bool = False): - return self._get_path(self.temp_path, *paths, create=False, create_parent=create_parent) - - def get_temp_dir(self, *paths: [str], create: bool = False): - return self._get_path(self.temp_path, *paths, create=create, create_parent=False) - @classmethod def _get_path(cls, root_path: str, *paths: [str], create: bool = False, create_parent: bool = False): target_path = parent_path = os.path.abspath(root_path) for path in paths: target_path = os.path.abspath(os.path.join(parent_path, path)) - if target_path == parent_path or parent_path != os.path.commonpath([parent_path, target_path]): + common_path = os.path.commonpath([parent_path, target_path]) + if target_path == parent_path or parent_path != common_path: raise Exception(f"Unsafe path \"{path}\"") parent_path = target_path dir_path = None @@ -119,6 +118,53 @@ def _get_path(cls, root_path: str, *paths: [str], create: bool = False, create_p os.makedirs(dir_path) return target_path + def get_path(self, *paths: str): + """ + 获取根目录下的子目录 + """ + return self._get_path(self.root_path, *paths, create=False, create_parent=False) + + def get_data_path(self, *paths: str, create_parent: bool = False): + """ + 获取数据目录下的子路径 + """ + return self._get_path(self.data_path, *paths, create=False, create_parent=create_parent) + + def get_data_dir(self, *paths: str, create: bool = False): + """ + 获取数据目录下的子目录 + """ + return self._get_path(self.data_path, *paths, create=create, create_parent=False) + + def get_temp_path(self, *paths: str, create_parent: bool = False): + """ + 获取临时文件目录下的子路径 + """ + return self._get_path(self.temp_path, *paths, create=False, create_parent=create_parent) + + def get_temp_dir(self, *paths: str, create: bool = False): + """ + 获取临时文件目录下的子目录 + """ + return self._get_path(self.temp_path, *paths, create=create, create_parent=False) + + @cached_property + def logger(self): + """ + 模块根logger + """ + from ._logging import get_logger + + return get_logger(prefix=self.name) + + def get_logger(self, name: str = None): + """ + 获取模块名作为前缀的logger + """ + from ._logging import get_logger + + return get_logger(name=name, prefix=self.name) + @cached_property def _config(self): from ._config import Config @@ -126,14 +172,10 @@ def _config(self): config = Config() # 初始化全局存储路径配置,优先级低于data、temp路径 - config["SETTING_STORAGE_PATH"] = \ - os.environ.get("SETTING_STORAGE_PATH") or \ + config["STORAGE_PATH"] = \ + os.environ.get("STORAGE_PATH") or \ os.path.join(str(pathlib.Path.home()), f".{__module_name__}") - # 初始化data、temp路径配置 - config["SETTING_DATA_PATH"] = os.environ.get("SETTING_DATA_PATH") # default {SETTING_STORAGE_PATH}/data - config["SETTING_TEMP_PATH"] = os.environ.get("SETTING_TEMP_PATH") # default {SETTING_STORAGE_PATH}/temp - # 初始化下载相关参数 config["SETTING_DOWNLOAD_USER_AGENT"] = \ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " \ @@ -142,8 +184,8 @@ def _config(self): "Safari/537.36" # 导入configs文件夹中所有配置文件 - config.from_file(self.get_asset_path("tools.yml"), load=yaml.safe_load) - config.from_file(self.get_asset_path("android-tools.json"), load=json.load) + config.from_file(self._get_path(asset_path, "tools.yml"), load=yaml.safe_load) + config.from_file(self._get_path(asset_path, "android-tools.json"), load=json.load) # 导入环境变量LINKTOOLS_SETTING中的配置文件 config.from_envvar("LINKTOOLS_SETTING", silent=True) @@ -151,6 +193,9 @@ def _config(self): return config def get_configs(self, namespace: str, lowercase: bool = True, trim_namespace: bool = True) -> Dict[str, Any]: + """ + 根据命名空间获取配置列表 + """ rv = {} for k, v in self._config.items(): if not k.startswith(namespace): @@ -164,28 +209,46 @@ def get_configs(self, namespace: str, lowercase: bool = True, trim_namespace: bo rv[key] = v return rv - def get_config(self, key, type: Type[_T] = None, default: _T = None) -> Optional[_T]: + def get_config(self, key, cast_type: Type[T] = None, accept_empty: bool = False, default: T = None) -> Optional[T]: + """ + 获取指定配置,优先会从环境变量中获取 + """ try: - value = self._config.get(key, self.__missing__) - if value is not self.__missing__: - return value if type is None else type(value) + new_key = f"{self.name}_{key}".upper() + if new_key in os.environ: + value = os.environ.get(new_key) + if accept_empty or value: + return value if cast_type is None else cast_type(value) except Exception as e: - self.logger.debug(f"Get config \"{key}\" error: {e}") + self.logger.debug(f"Get config \"{key}\" from system environ error: {e}") try: - value = os.environ.get(key, self.__missing__) - if value is not self.__missing__: - return value if type is None else type(value) + if key in self._config: + value = self._config.get(key) + if accept_empty or value: + return value if cast_type is None else cast_type(value) except Exception as e: - self.logger.debug(f"Get environ \"{key}\" error: {e}") + self.logger.debug(f"Get config \"{key}\" error: {e}") return default + def update_configs(self, **kwargs) -> None: + """ + 更新配置 + """ + self._config.update(**kwargs) + def set_config(self, key: str, value: Any) -> None: + """ + 更新配置 + """ self._config[key] = value @cached_property def tools(self): + """ + 工具集 + """ from ._tools import ToolContainer tools = ToolContainer() @@ -207,6 +270,9 @@ def tools(self): return tools def get_tool(self, name: str, **kwargs): + """ + 获取指定工具 + """ tool = self.tools[name] if len(kwargs) != 0: tool = tool.copy(**kwargs) @@ -214,39 +280,88 @@ def get_tool(self, name: str, **kwargs): @property def debug(self) -> bool: - return self.get_config("__DEBUG__", default=False) + """ + debug模式 + """ + from . import utils + + return self.get_config("DEBUG", cast_type=utils.bool, default=False) @debug.setter def debug(self, value: bool): - self.set_config("__DEBUG__", value) + """ + debug模式 + """ + self.set_config("DEBUG", value) @property def show_log_time(self) -> bool: - return self.get_config("__SHOW_LOG_TIME__", default=False) + """ + 显示日志时间 + """ + from . import utils + + return self.get_config("SHOW_LOG_TIME", cast_type=utils.bool, default=False) @show_log_time.setter def show_log_time(self, value: bool): + """ + 显示日志时间 + """ from ._logging import LogHandler handler = LogHandler.get_instance() if handler: handler.show_time = value - self.set_config("__SHOW_LOG_TIME__", value) + self.set_config("SHOW_LOG_TIME", value) @property def show_log_level(self) -> bool: - return self.get_config("__SHOW_LOG_LEVEL__", default=True) + """ + 显示日志级别 + """ + from . import utils + + return self.get_config("SHOW_LOG_LEVEL", cast_type=utils.bool, default=True) @show_log_level.setter def show_log_level(self, value: bool): + """ + 显示日志级别 + """ from ._logging import LogHandler handler = LogHandler.get_instance() if handler: handler.show_level = value - self.set_config("__SHOW_LOG_LEVEL__", value) + self.set_config("SHOW_LOG_LEVEL", value) @property def system(self) -> str: + """ + 系统名称 + """ return self.tools.system +class Environ(BaseEnviron): + + @property + def name(self) -> str: + return __module_name__ + + @property + def description(self) -> str: + return __module_description__ + + @property + def version(self) -> str: + return __module_version__ + + @property + def root_path(self) -> str: + return root_path + + def get_asset_path(self, *paths: str): + return self._get_path(asset_path, *paths) + + environ = Environ() diff --git a/src/linktools/cli/__init__.py b/src/linktools/cli/__init__.py index 5ae8a8c4..10bb5af7 100644 --- a/src/linktools/cli/__init__.py +++ b/src/linktools/cli/__init__.py @@ -28,4 +28,4 @@ """ from ._cli import walk_commands -from ._command import Command, AndroidCommand, IOSCommand +from ._command import BaseCommand, AndroidCommand, IOSCommand diff --git a/src/linktools/cli/_cli.py b/src/linktools/cli/_cli.py index a46ac374..52ccbb97 100644 --- a/src/linktools/cli/_cli.py +++ b/src/linktools/cli/_cli.py @@ -4,19 +4,11 @@ from importlib.util import module_from_spec from pkgutil import walk_packages -from ._command import Command +from ._command import BaseCommand from .._environ import environ def walk_commands(path: str): - # for entry in sorted(os.scandir(path), key=lambda o: o.name): - # entry: os.DirEntry = entry - # if entry.is_dir() and not entry.name.startswith("_"): - # yield from walk_commands( - # os.path.join(path, entry.name), - # f"{package}.{entry.name}" - # ) - for finder, name, is_pkg in sorted(walk_packages(path=[path]), key=lambda i: i[1]): if not is_pkg: try: @@ -24,7 +16,7 @@ def walk_commands(path: str): module = module_from_spec(spec) spec.loader.exec_module(module) command = getattr(module, "command", None) - if command and isinstance(command, Command): - yield name, command + if command and isinstance(command, BaseCommand): + yield command except Exception as e: environ.logger.warning(f"Ignore {name}, caused by {e.__class__.__name__}: {e}") diff --git a/src/linktools/cli/_command.py b/src/linktools/cli/_command.py index 208583f7..9a081428 100644 --- a/src/linktools/cli/_command.py +++ b/src/linktools/cli/_command.py @@ -42,16 +42,20 @@ from rich.prompt import IntPrompt from rich.table import Table -from .._environ import Environ, environ +from .._environ import BaseEnviron, environ from .._logging import LogHandler from ..decorator import cached_property from ..utils import ignore_error -class Command(abc.ABC): +class BaseCommand(abc.ABC): @property - def environ(self) -> Environ: + def name(self): + return self.__module__ + + @property + def environ(self) -> BaseEnviron: return environ @property @@ -185,7 +189,7 @@ def __call__(self, args: [str] = None) -> int: return exit_code -class AndroidCommand(Command, ABC): +class AndroidCommand(BaseCommand, ABC): @property def known_errors(self) -> Tuple[Type[BaseException]]: @@ -310,7 +314,7 @@ def wrapper(): help="use last device") -class IOSCommand(Command, ABC): +class IOSCommand(BaseCommand, ABC): @property def known_errors(self) -> Tuple[Type[BaseException]]: diff --git a/src/linktools/cli/commands/android/adb.py b/src/linktools/cli/commands/android/adb.py index fa6451ea..cf2b43ca 100755 --- a/src/linktools/cli/commands/android/adb.py +++ b/src/linktools/cli/commands/android/adb.py @@ -29,11 +29,11 @@ from argparse import ArgumentParser from typing import Optional -from linktools import cli from linktools.android import Adb +from linktools.cli import AndroidCommand -class Command(cli.AndroidCommand): +class Command(AndroidCommand): """ Adb that supports multiple devices """ diff --git a/src/linktools/cli/commands/android/agent.py b/src/linktools/cli/commands/android/agent.py index d4eadad8..1cf4fdd5 100755 --- a/src/linktools/cli/commands/android/agent.py +++ b/src/linktools/cli/commands/android/agent.py @@ -29,10 +29,10 @@ from argparse import ArgumentParser from typing import Optional -from linktools import cli +from linktools.cli import AndroidCommand -class Command(cli.AndroidCommand): +class Command(AndroidCommand): """ Debug android-tools.apk """ diff --git a/src/linktools/cli/commands/android/app.py b/src/linktools/cli/commands/android/app.py index 9d2a0540..4b5511b9 100755 --- a/src/linktools/cli/commands/android/app.py +++ b/src/linktools/cli/commands/android/app.py @@ -29,9 +29,10 @@ from argparse import ArgumentParser from typing import Optional -from linktools import utils, environ, cli +from linktools import utils, environ from linktools.android import Package, Permission, \ Component, Activity, Service, Receiver, Provider, IntentFilter +from linktools.cli import AndroidCommand class PrintLevel: @@ -224,7 +225,7 @@ def _print_intent(cls, stream: PrintStreamWrapper, intent: IntentFilter, indent: stream.print("Type [%s]" % type, indent=indent + 4, level=level) -class Command(cli.AndroidCommand): +class Command(AndroidCommand): """ Fetch application info """ diff --git a/src/linktools/cli/commands/android/debug.py b/src/linktools/cli/commands/android/debug.py index cf75a34a..18ff3a91 100755 --- a/src/linktools/cli/commands/android/debug.py +++ b/src/linktools/cli/commands/android/debug.py @@ -29,10 +29,11 @@ from argparse import ArgumentParser from typing import Optional -from linktools import utils, cli +from linktools import utils +from linktools.cli import AndroidCommand -class Command(cli.AndroidCommand): +class Command(AndroidCommand): """ Debug app by jdb """ diff --git a/src/linktools/cli/commands/android/frida.py b/src/linktools/cli/commands/android/frida.py index 3fbdfe55..3703faa2 100755 --- a/src/linktools/cli/commands/android/frida.py +++ b/src/linktools/cli/commands/android/frida.py @@ -29,12 +29,13 @@ from argparse import ArgumentParser from typing import Optional -from linktools import utils, environ, cli +from linktools import utils, environ +from linktools.cli import AndroidCommand from linktools.frida import FridaApplication, FridaShareScript, FridaScriptFile, FridaEvalCode from linktools.frida.android import AndroidFridaServer -class Command(cli.AndroidCommand): +class Command(AndroidCommand): """ Easy to use frida (require Android device rooted) """ diff --git a/src/linktools/cli/commands/android/info.py b/src/linktools/cli/commands/android/info.py index 5e737cb7..7b78570f 100644 --- a/src/linktools/cli/commands/android/info.py +++ b/src/linktools/cli/commands/android/info.py @@ -29,7 +29,8 @@ from argparse import ArgumentParser from typing import Optional -from linktools import environ, cli +from linktools import environ +from linktools.cli import AndroidCommand props = ( "ro.product.manufacturer", @@ -70,7 +71,7 @@ ) -class Command(cli.AndroidCommand): +class Command(AndroidCommand): """ Fetch device information """ diff --git a/src/linktools/cli/commands/android/intent.py b/src/linktools/cli/commands/android/intent.py index 33c69e49..4eaaf5c2 100755 --- a/src/linktools/cli/commands/android/intent.py +++ b/src/linktools/cli/commands/android/intent.py @@ -32,10 +32,11 @@ from argparse import ArgumentParser from typing import Optional -from linktools import utils, environ, cli +from linktools import utils, environ +from linktools.cli import AndroidCommand -class Command(cli.AndroidCommand): +class Command(AndroidCommand): """ Common intent actions """ diff --git a/src/linktools/cli/commands/android/objection.py b/src/linktools/cli/commands/android/objection.py index 7f31c7ec..5aabebea 100644 --- a/src/linktools/cli/commands/android/objection.py +++ b/src/linktools/cli/commands/android/objection.py @@ -29,11 +29,12 @@ from argparse import ArgumentParser from typing import Optional -from linktools import utils, environ, cli +from linktools import utils, environ +from linktools.cli import AndroidCommand from linktools.frida.android import AndroidFridaServer -class Command(cli.AndroidCommand): +class Command(AndroidCommand): """ Easy to use objection (require Android device rooted) """ diff --git a/src/linktools/cli/commands/android/pidcat.py b/src/linktools/cli/commands/android/pidcat.py index 9a5835f4..e1294b71 100755 --- a/src/linktools/cli/commands/android/pidcat.py +++ b/src/linktools/cli/commands/android/pidcat.py @@ -25,7 +25,7 @@ from subprocess import PIPE from typing import Optional -from linktools import cli +from linktools.cli import AndroidCommand __version__ = '2.1.0' @@ -55,7 +55,7 @@ def get_char_width(char): return 1 -class Command(cli.AndroidCommand): +class Command(AndroidCommand): """ Filter logcat by package name """ diff --git a/src/linktools/cli/commands/android/top.py b/src/linktools/cli/commands/android/top.py index c2eca0d8..64f263b0 100755 --- a/src/linktools/cli/commands/android/top.py +++ b/src/linktools/cli/commands/android/top.py @@ -32,10 +32,11 @@ from argparse import ArgumentParser from typing import Optional -from linktools import utils, cli, environ +from linktools import utils, environ +from linktools.cli import AndroidCommand -class Command(cli.AndroidCommand): +class Command(AndroidCommand): """ Fetch current running app's basic information """ diff --git a/src/linktools/cli/commands/common/grep.py b/src/linktools/cli/commands/common/grep.py index 5edbe6ce..16cf411e 100755 --- a/src/linktools/cli/commands/common/grep.py +++ b/src/linktools/cli/commands/common/grep.py @@ -40,7 +40,8 @@ from rich.highlighter import NullHighlighter from rich.text import Text -from linktools import utils, environ, cli +from linktools import utils, environ +from linktools.cli import BaseCommand pprint = functools.partial(get_console().print, sep="", markup=False, highlight=NullHighlighter) @@ -191,7 +192,7 @@ def match_content(self, content): return out -class Command(cli.Command): +class Command(BaseCommand): """ Match files with regular expression """ diff --git a/src/linktools/cli/commands/common/shell.py b/src/linktools/cli/commands/common/shell.py index ce68a241..43820c07 100644 --- a/src/linktools/cli/commands/common/shell.py +++ b/src/linktools/cli/commands/common/shell.py @@ -7,10 +7,11 @@ from argparse import ArgumentParser from typing import Optional -from linktools import utils, environ, cli +from linktools import utils, environ +from linktools.cli import BaseCommand -class Command(cli.Command): +class Command(BaseCommand): """ Shell with environment variables already initialized """ diff --git a/src/linktools/cli/commands/common/tools.py b/src/linktools/cli/commands/common/tools.py index f2efb5cc..eac60b76 100755 --- a/src/linktools/cli/commands/common/tools.py +++ b/src/linktools/cli/commands/common/tools.py @@ -31,10 +31,11 @@ from argparse import ArgumentParser from typing import Optional -from linktools import environ, cli +from linktools import environ +from linktools.cli import BaseCommand -class Command(cli.Command): +class Command(BaseCommand): """ Tools downloaded from the web """ diff --git a/src/linktools/cli/commands/ios/frida.py b/src/linktools/cli/commands/ios/frida.py index c07d16b5..b3d28400 100755 --- a/src/linktools/cli/commands/ios/frida.py +++ b/src/linktools/cli/commands/ios/frida.py @@ -29,12 +29,13 @@ from argparse import ArgumentParser from typing import Optional -from linktools import utils, environ, cli +from linktools import utils, environ +from linktools.cli import IOSCommand from linktools.frida import FridaApplication, FridaShareScript, FridaScriptFile, FridaEvalCode from linktools.frida.ios import IOSFridaServer -class Command(cli.IOSCommand): +class Command(IOSCommand): """ Easy to use frida (require iOS device jailbreak) """ diff --git a/src/linktools/cli/commands/ios/objection.py b/src/linktools/cli/commands/ios/objection.py index 2906f91d..215e9bf4 100644 --- a/src/linktools/cli/commands/ios/objection.py +++ b/src/linktools/cli/commands/ios/objection.py @@ -29,11 +29,12 @@ from argparse import ArgumentParser from typing import Optional -from linktools import utils, environ, cli +from linktools import utils, environ +from linktools.cli import IOSCommand from linktools.frida.ios import IOSFridaServer -class Command(cli.IOSCommand): +class Command(IOSCommand): """ Easy to use objection (require iOS device jailbreak) """ diff --git a/src/linktools/cli/commands/ios/scp.py b/src/linktools/cli/commands/ios/scp.py index 90bdcadb..c47045b8 100644 --- a/src/linktools/cli/commands/ios/scp.py +++ b/src/linktools/cli/commands/ios/scp.py @@ -8,7 +8,8 @@ import paramiko from paramiko.ssh_exception import SSHException -from linktools import utils, cli +from linktools import utils +from linktools.cli import IOSCommand from linktools.ios import Device _REMOTE_PATH_PREFIX = "@" @@ -31,7 +32,7 @@ def __fspath__(self): return self._path -class Command(cli.IOSCommand): +class Command(IOSCommand): """ OpenSSH secure file copy (require iOS device jailbreak) """ diff --git a/src/linktools/cli/commands/ios/sib.py b/src/linktools/cli/commands/ios/sib.py index 013fe62b..09822e74 100644 --- a/src/linktools/cli/commands/ios/sib.py +++ b/src/linktools/cli/commands/ios/sib.py @@ -4,11 +4,11 @@ from argparse import ArgumentParser from typing import Optional -from linktools import cli +from linktools.cli import IOSCommand from linktools.ios import Sib -class Command(cli.IOSCommand): +class Command(IOSCommand): """ Sib that supports multiple devices """ diff --git a/src/linktools/cli/commands/ios/ssh.py b/src/linktools/cli/commands/ios/ssh.py index a377e040..45336b43 100644 --- a/src/linktools/cli/commands/ios/ssh.py +++ b/src/linktools/cli/commands/ios/ssh.py @@ -32,11 +32,12 @@ import paramiko from paramiko.ssh_exception import SSHException -from linktools import cli, utils +from linktools import utils +from linktools.cli import IOSCommand from linktools.ios import Device -class Command(cli.IOSCommand): +class Command(IOSCommand): """ OpenSSH remote login client (require iOS device jailbreak) """ diff --git a/src/linktools/utils/_url.py b/src/linktools/utils/_url.py index f046364e..ec3ffc7e 100644 --- a/src/linktools/utils/_url.py +++ b/src/linktools/utils/_url.py @@ -65,7 +65,6 @@ def user_agent(style=None) -> str: user_agent = _UserAgent() if style: - print(user_agent[style]) return user_agent[style] return user_agent.random diff --git a/src/linktools/utils/_utils.py b/src/linktools/utils/_utils.py index 116017a4..7ed322c9 100755 --- a/src/linktools/utils/_utils.py +++ b/src/linktools/utils/_utils.py @@ -104,7 +104,7 @@ def ignore_error(fn: Callable[..., _T], *args, **kwargs) -> _T: # noinspection PyShadowingBuiltins -def cast(type: type, obj: object, default=None): +def cast(type: Type[_T], obj: Any, default: _T = None) -> Optional[_T]: """ 类型转换 :param type: 目标类型 @@ -118,7 +118,7 @@ def cast(type: type, obj: object, default=None): return default -def int(obj: object, default: int = 0) -> int: +def int(obj: Any, default: int = 0) -> "int": """ 转为int :param obj: 需要转换的值 @@ -128,31 +128,36 @@ def int(obj: object, default: int = 0) -> int: return cast(type(0), obj, default=default) -def bool(obj: object, default: bool = False) -> "bool": +def bool(obj: Any, default: bool = False) -> "bool": """ 转为bool :param obj: 需要转换的值 :param default: 默认值 :return: 转换后的值 """ - return cast(type(True), obj, default=default) + bool_type = type(True) + if isinstance(obj, bool_type): + return obj + if isinstance(obj, str): + return cast(lambda o: o.lower() == "true", obj, default=default) + return cast(bool_type, obj, default=default) -def is_contain(obj: object, key: object) -> "bool": +def is_contain(obj: Any, key: Any) -> "bool": """ 是否包含内容 :param obj: 对象 :param key: 键 :return: 是否包含 """ - if object is None: + if obj is None: return False if isinstance(obj, Iterable): return key in obj return False -def is_empty(obj: object) -> "bool": +def is_empty(obj: Any) -> "bool": """ 对象是否为空 :param obj: 对象