plum-tools 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,20 @@
1
+ default_ssh_conf:
2
+ user: root
3
+ port: 22
4
+ identityfile: ~/.ssh/id_rsa
5
+
6
+ host_type_default: 10.10.100
7
+ host_type_2: 192.168.1
8
+
9
+ ipmi_interval: 100
10
+
11
+ projects:
12
+ default:
13
+ src: ~/PythonProjects/plum_tools
14
+ dest: /home/seekplum/plum_tools
15
+ exclude:
16
+ - .idea
17
+ - .vscode
18
+ - '*.pyc'
19
+ - '*.log'
20
+ delete: 0
plum_tools/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ """
2
+ #=============================================================================
3
+ # ProjectName: plum_tools
4
+ # FileName: __init__.py
5
+ # Author: seekplum
6
+ # Email: 1131909224m@sina.cn
7
+ # HomePage: seekplum.github.io
8
+ # Create: 2019-04-04 22:01
9
+ #=============================================================================
10
+ """
plum_tools/conf.py ADDED
@@ -0,0 +1,65 @@
1
+ """
2
+ #=============================================================================
3
+ # ProjectName: plum_tools
4
+ # FileName: conf
5
+ # Desc: 相关常量配置信息
6
+ # Author: seekplum
7
+ # Email: 1131909224m@sina.cn
8
+ # HomePage: seekplum.github.io
9
+ # Create: 2018-07-06 18:39
10
+ #=============================================================================
11
+ """
12
+
13
+ import os
14
+ from enum import IntEnum, StrEnum
15
+
16
+ VERSION = "0.5.0"
17
+
18
+
19
+ STASH_UUID = "plum123456789987654321plum"
20
+ COMMAND_TIMEOUT = 3 # 执行命令超时时间
21
+ PROCESSES_NUMBER = 100 # 进程数量
22
+ LOCAL_HOST = "__localhost__"
23
+
24
+
25
+ class GitCommand(StrEnum):
26
+ """git 相关命令"""
27
+
28
+ STASH_LIST = "git stash list" # 检查是否有文件在储藏区
29
+ STATUS_DEFAULT = "git status" # 检查文件状态
30
+ STATUS_SHORT = "git status -s" # 检查文件状态,简短输出,只能看到文件是否有改动,无法确认是否落后、超前远程分支
31
+ BRANCH_ABBREV = "git rev-parse --abbrev-ref HEAD" # 查询当前分支名
32
+ STASH_SAVE = 'git stash save "%s"' # 保存修改的文件到储藏区
33
+ STASH_POP = "git stash pop --index %s" # 把储藏的文件恢复
34
+ GIT_CHECKOUT = "git checkout %s" # 切换分支
35
+
36
+ PULL_KEYWORD = '"git pull"' # 落后远程分支关键字
37
+ PUSH_KEYWORD = '"git push"' # 超前远程分支关键字
38
+
39
+
40
+ class OsCommand(StrEnum):
41
+ """系统 相关命令"""
42
+
43
+ FIND_COMMAND = 'find %s -name ".git"' # 通过系统命令查找文件路径
44
+ PING_COMMAND = "ping -W 3 -c 1 %s" # ping命令 -W 超时时间 -c 次数
45
+ # ipmi命令
46
+ IPMI_COMMAND = "ipmitool -I lanplus -H %(ip)s -U %(user)s -P %(password)s %(command)s"
47
+ STAT_COMMAND = "stat %s"
48
+
49
+
50
+ class SSHConfig(IntEnum):
51
+ """ssh配置相关"""
52
+
53
+ DEFAULT_SSH_PORT = 22
54
+ CONNECT_TIMEOUT = 3
55
+
56
+
57
+ class PathConfig(StrEnum):
58
+ """相关配置文件"""
59
+
60
+ HOME = os.environ["HOME"]
61
+ ROOT = os.path.dirname(os.path.abspath(__file__))
62
+ PLUM_YML_NAME = ".plum_tools.yaml" # 项目需要的配置文件名
63
+ PLUM_YML_PATH = os.path.join(HOME, PLUM_YML_NAME) # 项目需要的配置文件路径
64
+ SSH_CONFIG_NAME = ".ssh/config" # ssh配置文件名
65
+ SSH_CONFIG_PATH = os.path.join(HOME, SSH_CONFIG_NAME) # ssh配置文件路径
@@ -0,0 +1,38 @@
1
+ """
2
+ #=============================================================================
3
+ # ProjectName: plum-tools
4
+ # FileName: exceptions
5
+ # Desc: 所有的异常类
6
+ # Author: seekplum
7
+ # Email: 1131909224m@sina.cn
8
+ # HomePage: seekplum.github.io
9
+ # Create: 2018-07-05 22:02
10
+ #=============================================================================
11
+ """
12
+
13
+
14
+ class RunCmdError(Exception):
15
+ """执行命令异常"""
16
+
17
+ def __init__(self, message: str, out_msg: str, err_msg: str) -> None:
18
+ """初始化参数
19
+
20
+ :param message: 错误提示信息
21
+ :param out_msg: 执行命令输出结果
22
+ :param err_msg: 执行命令错误信息
23
+ """
24
+ super().__init__(message) # pylint: disable=R1725
25
+ self.out_msg = out_msg
26
+ self.err_msg = err_msg
27
+
28
+
29
+ class RunCmdTimeout(Exception):
30
+ """执行系统命令超时"""
31
+
32
+
33
+ class SSHException(Exception):
34
+ """ssh异常"""
35
+
36
+
37
+ class SystemTypeError(Exception):
38
+ """系统类型异常"""
@@ -0,0 +1,142 @@
1
+ import ast
2
+ import sys
3
+ import typing as t
4
+ from enum import StrEnum
5
+ from pathlib import Path
6
+
7
+ from .utils.parser import add_extra_argument, get_base_parser
8
+
9
+ STD_LIB_MODULE_NAMES = sys.stdlib_module_names
10
+ MODULE_NAME_PAIRS = {
11
+ "PIL": "pillow",
12
+ "grpc": "grpcio",
13
+ }
14
+
15
+
16
+ class FindMode(StrEnum):
17
+ STD = "std" # 标准库
18
+ ALL = "all" # 所有库
19
+ THIRD_PARTY = "3rd-party" # 第三方库。这个会把项目本身的包也包含进来
20
+
21
+
22
+ ModuleDict = t.TypedDict(
23
+ "ModuleDict",
24
+ {
25
+ "name": str,
26
+ "type": t.Literal["import", "from"],
27
+ "origin_name": str,
28
+ "mode": t.Literal[FindMode.STD, FindMode.THIRD_PARTY],
29
+ },
30
+ )
31
+ GeneratorModuleDict = t.Generator[ModuleDict, None, None]
32
+
33
+
34
+ def calc_mode(name: str) -> t.Literal[FindMode.STD, FindMode.THIRD_PARTY]:
35
+ if name in STD_LIB_MODULE_NAMES:
36
+ return FindMode.STD
37
+ return FindMode.THIRD_PARTY
38
+
39
+
40
+ def parse_imports(source: str) -> GeneratorModuleDict:
41
+ tree = ast.parse(source)
42
+
43
+ for node in ast.walk(tree):
44
+ if isinstance(node, ast.Import):
45
+ for name in node.names:
46
+ first_name = name.name.split(".")[0]
47
+ yield ModuleDict(
48
+ type="import",
49
+ origin_name=name.name,
50
+ name=first_name,
51
+ mode=calc_mode(first_name),
52
+ )
53
+ elif isinstance(node, ast.ImportFrom):
54
+ if node.module:
55
+ if node.level > 0: # 不考虑相对导入
56
+ continue
57
+ first_name = node.module.split(".")[0]
58
+ yield ModuleDict(
59
+ type="from",
60
+ origin_name=node.module,
61
+ name=first_name,
62
+ mode=calc_mode(first_name),
63
+ )
64
+
65
+
66
+ def find_imports_by_file(file_path: str) -> GeneratorModuleDict:
67
+ with open(file_path, encoding="utf-8") as f:
68
+ yield from parse_imports(f.read())
69
+
70
+
71
+ def find_imports(
72
+ project_dir: str, *, mode: FindMode = FindMode.ALL, ignore_paths: t.List[str] | None = None
73
+ ) -> GeneratorModuleDict:
74
+ exists = set()
75
+ project_path = Path(project_dir)
76
+ ignore_path_set = {str(Path(path)) for path in ignore_paths or []}
77
+ ignore_dir_parts = {".venv", "__pycache__"}
78
+ my_module_names = {path.name for path in project_path.iterdir()}
79
+ src_path = project_path / "src"
80
+ if src_path.is_dir():
81
+ my_module_names.update(path.name for path in src_path.iterdir())
82
+
83
+ for file in project_path.rglob("*.py"):
84
+ file_path = str(file)
85
+ relative_parts = file.relative_to(project_path).parts
86
+ # 遍历目录中的所有文件,除了.venv和__pycache__目录
87
+ if file_path in ignore_path_set or any(part in ignore_dir_parts for part in relative_parts):
88
+ continue
89
+ if file.absolute() == Path(__file__).absolute():
90
+ continue
91
+ try:
92
+ for module in find_imports_by_file(file_path):
93
+ if module["name"] in exists or module["name"] in my_module_names:
94
+ continue
95
+ exists.add(module["name"])
96
+ if mode in (FindMode.ALL, module["mode"]):
97
+ yield module
98
+ except Exception as e:
99
+ print(f"Error parsing {file_path}: {e}")
100
+
101
+
102
+ def main():
103
+ parser = get_base_parser()
104
+ parser.add_argument(
105
+ "--mode",
106
+ "-m",
107
+ type=FindMode,
108
+ choices=list(FindMode),
109
+ default=FindMode.THIRD_PARTY,
110
+ help="Find mode",
111
+ )
112
+ parser.add_argument(
113
+ "--project-dir",
114
+ "-p",
115
+ type=str,
116
+ dest="project_dir",
117
+ default=".",
118
+ help="Project directory",
119
+ )
120
+ parser.add_argument(
121
+ "--ignore-path",
122
+ "-i",
123
+ required=False,
124
+ action="store",
125
+ dest="ignore_paths",
126
+ nargs="+",
127
+ help="Ignore paths",
128
+ )
129
+ add_extra_argument(parser)
130
+ args = parser.parse_args()
131
+ modules = sorted(
132
+ find_imports(args.project_dir, mode=args.mode, ignore_paths=args.ignore_paths),
133
+ key=lambda x: (x["mode"], x["name"]),
134
+ )
135
+ extra_modules = args.extra
136
+ for module in modules:
137
+ name = module["name"]
138
+ if name in MODULE_NAME_PAIRS:
139
+ name = MODULE_NAME_PAIRS[name]
140
+ elif name in extra_modules:
141
+ name = extra_modules[name]
142
+ print(name)
plum_tools/gitrepo.py ADDED
@@ -0,0 +1,144 @@
1
+ """
2
+ #=============================================================================
3
+ # ProjectName: plum-tools
4
+ # FileName: exceptions
5
+ # Desc: 查找指定路径下所有被改动的git仓库
6
+ # Author: seekplum
7
+ # Email: 1131909224m@sina.cn
8
+ # HomePage: seekplum.github.io
9
+ # Create: 2018-07-05 22:02
10
+ #=============================================================================
11
+ """
12
+
13
+ import functools
14
+ import os
15
+ from collections.abc import Generator
16
+ from multiprocessing import Pool
17
+
18
+ from .conf import PROCESSES_NUMBER
19
+ from .utils.git import check_is_git_repository, check_repository_modify_status, check_repository_stash
20
+ from .utils.parser import get_base_parser
21
+ from .utils.printer import print_error, print_warn
22
+
23
+
24
+ def find_git_project_for_python(path: str) -> Generator[str, None, None]:
25
+ """查找目录下所有的 git仓库
26
+
27
+ :param path 要被检查的目录
28
+ :example path "/tmp/git"
29
+
30
+ >>> for path in find_git_project_for_python("/tmp"):
31
+ ... print path
32
+
33
+
34
+ """
35
+ for root, _, _ in os.walk(path):
36
+ if check_is_git_repository(root):
37
+ yield root
38
+
39
+
40
+ def check_project(path: str, stash: bool = True) -> dict:
41
+ """检查git项目
42
+
43
+ :param path 仓库路径
44
+ :example path /tmp/git
45
+
46
+ :param stash 是否显示储藏信息
47
+ :example stash True
48
+
49
+ :return result {
50
+ path: 仓库路径
51
+ output: 检查输出信息
52
+ status:
53
+ True 仓库有文件进行了修改或有储藏文件
54
+ False 仓库没有和远程一致。且没有储藏文件
55
+ }
56
+ :example result {
57
+ "path": "/tmp/git",
58
+ "status": False,
59
+ "output": ""
60
+ }
61
+ """
62
+ result = {
63
+ "path": path,
64
+ "status": False,
65
+ }
66
+ # 检查文件是否改动
67
+ status_result, status_out = check_repository_modify_status(path)
68
+ if status_result:
69
+ result["status"] = True
70
+ result["output"] = status_out
71
+ return result
72
+
73
+ if not stash:
74
+ return result
75
+ # 检查是否有文件储藏
76
+ stash_result, stash_out = check_repository_stash(path)
77
+ if stash_result:
78
+ result["status"] = True
79
+ result["output"] = stash_out
80
+ return result
81
+
82
+
83
+ def check_projects(projects: list[str], detail: bool, stash: bool = True) -> None:
84
+ """检查指导目录下所有的仓库是否有修改
85
+
86
+ 当仓库中有内容被修改时,打印黄色警告信息
87
+
88
+ :param projects 需要检查的目录列表
89
+ :example ["/tmp"]
90
+
91
+ :param detail 是否显示详细错误信息
92
+ :example False
93
+
94
+ :param stash 是否显示储藏信息
95
+ :example False
96
+ """
97
+ targets = [path for project_path in projects for path in find_git_project_for_python(project_path)]
98
+ with Pool(processes=PROCESSES_NUMBER) as pool:
99
+ result = pool.map(functools.partial(check_project, stash=stash), targets)
100
+ for item in result:
101
+ # 仓库中文件没有被改动而且没有文件被储藏了
102
+ if not item["status"]:
103
+ continue
104
+ print_warn(item["path"])
105
+
106
+ # 打印详细错误信息
107
+ if not detail:
108
+ continue
109
+ print_error(item["output"])
110
+
111
+
112
+ def main() -> None:
113
+ """程序主入口"""
114
+ parser = get_base_parser()
115
+ parser.add_argument(
116
+ "-p",
117
+ "--path",
118
+ action="store",
119
+ required=False,
120
+ dest="path",
121
+ nargs="+",
122
+ default=[os.environ["HOME"]],
123
+ help="The directory path to check",
124
+ )
125
+ parser.add_argument(
126
+ "-d",
127
+ "--detail",
128
+ action="store_true",
129
+ required=False,
130
+ dest="detail",
131
+ default=False,
132
+ help="display staged details",
133
+ )
134
+ parser.add_argument(
135
+ "-s",
136
+ "--stash",
137
+ action="store_true",
138
+ required=False,
139
+ dest="stash",
140
+ default=False,
141
+ help="display stash details",
142
+ )
143
+ args = parser.parse_args()
144
+ check_projects(args.path, args.detail, args.stash)
plum_tools/gitstash.py ADDED
@@ -0,0 +1,106 @@
1
+ """
2
+ #=============================================================================
3
+ # ProjectName: plum_tools
4
+ # FileName: gitstash
5
+ # Desc: 外部传入一个branch,保存本地未提交的修改,然后切换到branch,将上次该branch保存的未提交的结果stash pop出来
6
+ # 命令: gitstash master
7
+ # 描述: 在当前路径下切换到master分支
8
+ # Author: seekplum
9
+ # Email: 1131909224m@sina.cn
10
+ # HomePage: seekplum.github.io
11
+ # Create: 2018-07-07 16:29
12
+ #=============================================================================
13
+ """
14
+
15
+ import os
16
+ import sys
17
+
18
+ from .conf import STASH_UUID, GitCommand
19
+ from .exceptions import RunCmdError
20
+ from .utils.git import check_repository_modify_status, check_repository_stash, get_current_branch_name
21
+ from .utils.parser import get_base_parser
22
+ from .utils.printer import print_warn
23
+ from .utils.utils import cd, run_cmd
24
+
25
+
26
+ class GitCheckoutStash:
27
+ """切换分支前储藏文件"""
28
+
29
+ def __init__(self, current_branch: str, new_branch: str) -> None:
30
+ """初始化信息
31
+
32
+ :param current_branch: 当前分支名
33
+ :example current_branch master
34
+
35
+ :param new_branch: 新的分支名
36
+ :example new_branch release-1.0.0
37
+ """
38
+ self._current_branch = current_branch
39
+ self._current_path = os.getcwd() # 当前执行命令的路径
40
+ self._new_branch = new_branch
41
+ self._mark = "-"
42
+ self._stash_uuid = f"{self._current_branch}{self._mark}{STASH_UUID}"
43
+
44
+ def _stash(self) -> None:
45
+ """储藏文件"""
46
+ # 分支有修改,先进行stash储藏
47
+ is_modify, _ = check_repository_modify_status(self._current_path)
48
+ if is_modify:
49
+ cmd = GitCommand.STASH_SAVE % self._stash_uuid
50
+ with cd(self._current_path):
51
+ run_cmd(cmd)
52
+
53
+ def _check_branch(self) -> bool:
54
+ """检查分支是否一致
55
+
56
+ :return
57
+ True: 当前分支和要切换的分支一致
58
+ False: 当前分支和要切换的分支不一致
59
+ """
60
+ return self._current_branch == self._new_branch
61
+
62
+ def _checkout(self) -> None:
63
+ """切换分支"""
64
+ # 切换到新分支
65
+ cmd = GitCommand.GIT_CHECKOUT % self._new_branch
66
+ with cd(self._current_path):
67
+ run_cmd(cmd)
68
+
69
+ def _apply(self) -> None:
70
+ """恢复储藏的文件"""
71
+ # 检查切换后的分支是否有储藏文件
72
+ is_stash, stash_out = check_repository_stash(self._current_path)
73
+ if not is_stash:
74
+ return
75
+ for line in stash_out.splitlines():
76
+ stash_string, _ = line.split(":", 1)
77
+ stash_save = f"{self._new_branch}{self._mark}{STASH_UUID}"
78
+ if stash_save.strip() in line:
79
+ cmd = GitCommand.STASH_POP % stash_string
80
+ with cd(self._current_path):
81
+ run_cmd(cmd)
82
+ break
83
+
84
+ def checkout(self) -> None:
85
+ """储藏文件切换分支"""
86
+ # 不需要切换
87
+ if self._check_branch():
88
+ return
89
+ self._stash()
90
+ self._checkout()
91
+ self._apply()
92
+
93
+
94
+ def main() -> None:
95
+ """程序主入口"""
96
+ parser = get_base_parser()
97
+ parser.add_argument(dest="branch", action="store", help="specify branch")
98
+ args = parser.parse_args()
99
+ new_branch = args.branch
100
+ try:
101
+ current_branch = get_current_branch_name()
102
+ except RunCmdError:
103
+ print_warn("当前目录不是一个git仓库")
104
+ sys.exit(1)
105
+ stash = GitCheckoutStash(current_branch, new_branch)
106
+ stash.checkout()