easyrip 4.13.3__py3-none-any.whl → 4.14.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.
easyrip/__main__.py CHANGED
@@ -17,15 +17,18 @@ from .easyrip_command import (
17
17
  Cmd_type,
18
18
  Cmd_type_val,
19
19
  CmdCompleter,
20
- FuzzyCompleter,
21
- NestedCompleter,
22
20
  Opt_type,
23
21
  OptCompleter,
24
22
  nested_dict,
25
23
  )
26
24
  from .easyrip_config.config import Config_key, config
27
25
  from .easyrip_main import Ripper, get_input_prompt, init, log, run_command
28
- from .easyrip_prompt import ConfigFileHistory, SmartPathCompleter, easyrip_prompt
26
+ from .easyrip_prompt import (
27
+ ConfigFileHistory,
28
+ CustomPromptCompleter,
29
+ SmartPathCompleter,
30
+ easyrip_prompt,
31
+ )
29
32
  from .global_val import C_D, C_Z
30
33
 
31
34
 
@@ -130,14 +133,7 @@ def run() -> NoReturn:
130
133
  (
131
134
  _ctv_to_nc(cmd_ctv_tuple),
132
135
  OptCompleter(opt_tree=_ctv_to_nd(ct.value for ct in Opt_type)),
133
- FuzzyCompleter(
134
- NestedCompleter(
135
- {
136
- k: NestedCompleter({v: None})
137
- for k, v in easyrip_prompt.get_custom_prompt().items()
138
- }
139
- ),
140
- ),
136
+ CustomPromptCompleter(),
141
137
  )
142
138
  ),
143
139
  history=prompt_history,
@@ -253,19 +253,22 @@ class Cmd_type(enum.Enum):
253
253
  description=(
254
254
  "history\n" # .
255
255
  " Show prompt history\n"
256
- "clear | clean\n"
256
+ "history_clear\n"
257
257
  " Delete history file\n"
258
258
  "add <name:string> <cmd:string>\n"
259
259
  " Add a custom prompt\n"
260
260
  " e.g. prompt add myprompt echo my prompt\n"
261
261
  "del <name:string>\n"
262
262
  " Delete a custom prompt"
263
+ "show\n"
264
+ " Show custom prompt"
263
265
  ),
264
266
  childs=(
265
267
  Cmd_type_val(("history",)),
266
- Cmd_type_val(("clear", "clean")),
268
+ Cmd_type_val(("history_clear",)),
267
269
  Cmd_type_val(("add",)),
268
270
  Cmd_type_val(("del",)),
271
+ Cmd_type_val(("show",)),
269
272
  ),
270
273
  )
271
274
  translate = Cmd_type_val(
easyrip/easyrip_main.py CHANGED
@@ -345,7 +345,7 @@ def run_ripper_list(
345
345
  log.info("Execute shutdown in {}s", shutdown_sec)
346
346
  if os.name == "nt":
347
347
  _cmd = (
348
- f'shutdown /s /t {shutdown_sec} /c "{gettext("{} run completed, shutdown in {}s", PROJECT_TITLE, shutdown_sec)}"',
348
+ f'shutdown /s /t {shutdown_sec} /d p:4:0 /c "{gettext("{} run completed, shutdown in {}s", PROJECT_TITLE, shutdown_sec)}"',
349
349
  )
350
350
  elif os.name == "posix":
351
351
  _cmd = (f"shutdown -h +{shutdown_sec // 60}",)
@@ -776,7 +776,7 @@ def run_command(command: Iterable[str] | str) -> bool:
776
776
  for line in f.read().splitlines():
777
777
  log.send(line, is_format=False)
778
778
 
779
- case "clear" | "clean":
779
+ case "history_clear":
780
780
  easyrip_prompt.clear_history()
781
781
 
782
782
  case "add":
@@ -797,6 +797,15 @@ def run_command(command: Iterable[str] | str) -> bool:
797
797
  log.info("Del custom prompt {!r}", _name)
798
798
  return easyrip_prompt.del_custom_prompt(_name)
799
799
 
800
+ case "show":
801
+ if _custom_prompt := easyrip_prompt.get_custom_prompt():
802
+ log.send(
803
+ "\n".join(
804
+ f"{k!r} = {v!r}" for k, v in _custom_prompt.items()
805
+ ),
806
+ is_format=False,
807
+ )
808
+
800
809
  case Cmd_type.translate:
801
810
  if not (_infix := cmd_list[1]):
802
811
  log.error("Need target infix")
easyrip/easyrip_prompt.py CHANGED
@@ -83,94 +83,95 @@ class ConfigFileHistory(FileHistory):
83
83
  super().store_string(string)
84
84
 
85
85
 
86
+ def _highlight_fuzzy_match(
87
+ suggestion: str,
88
+ user_input: str,
89
+ style_config: dict | None = None,
90
+ ) -> StyleAndTextTuples:
91
+ """
92
+ 高亮显示模糊匹配结果
93
+
94
+ Args:
95
+ suggestion: 建议的完整字符串
96
+ user_input: 用户输入的匹配字符
97
+ style_config: 样式配置字典
98
+
99
+ Returns:
100
+ 包含样式信息的格式化文本
101
+
102
+ """
103
+ if style_config is None:
104
+ style_config = {
105
+ "match_char": "class:fuzzymatch.inside.character",
106
+ "match_section": "class:fuzzymatch.inside",
107
+ "non_match": "class:fuzzymatch.outside",
108
+ }
109
+
110
+ if not user_input:
111
+ # 用户没有输入,返回原始字符串
112
+ return [(style_config["non_match"], suggestion)]
113
+
114
+ # 找到最佳匹配位置
115
+ result = []
116
+
117
+ # 简化的模糊匹配算法
118
+ pattern = ".*?".join(map(re.escape, user_input))
119
+ regex = re.compile(pattern, re.IGNORECASE)
120
+
121
+ match = regex.search(suggestion)
122
+ if not match:
123
+ # 没有匹配,返回原始字符串
124
+ return [(style_config["non_match"], suggestion)]
125
+
126
+ start, end = match.span()
127
+ match_text = suggestion[start:end]
128
+
129
+ # 匹配段之前的文本
130
+ if start > 0:
131
+ result.append((style_config["non_match"], suggestion[:start]))
132
+
133
+ # 匹配段内部的字符
134
+ input_chars = list(user_input)
135
+ for char in match_text:
136
+ if input_chars and char.lower() == input_chars[0].lower():
137
+ result.append((style_config["match_char"], char))
138
+ input_chars.pop(0)
139
+ else:
140
+ result.append((style_config["match_section"], char))
141
+
142
+ # 匹配段之后的文本
143
+ if end < len(suggestion):
144
+ result.append((style_config["non_match"], suggestion[end:]))
145
+
146
+ return result
147
+
148
+
149
+ def _fuzzy_filter_and_sort(names: list[str], match_str: str) -> list[str]:
150
+ """模糊过滤和排序"""
151
+ if not match_str:
152
+ return sorted(names)
153
+
154
+ # 构建模糊匹配模式
155
+ pattern = ".*?".join(map(re.escape, match_str))
156
+ regex = re.compile(f"(?=({pattern}))", re.IGNORECASE)
157
+
158
+ matches = []
159
+ for filename in names:
160
+ regex_matches = list(regex.finditer(filename))
161
+ if regex_matches:
162
+ # 找到最佳匹配(最左、最短)
163
+ best = min(regex_matches, key=lambda m: (m.start(), len(m.group(1))))
164
+ matches.append((best.start(), len(best.group(1)), filename))
165
+
166
+ # 按匹配质量排序:先按匹配位置,再按匹配长度
167
+ matches.sort(key=lambda x: (x[0], x[1]))
168
+ return [item[2] for item in matches]
169
+
170
+
86
171
  class SmartPathCompleter(Completer):
87
172
  def __init__(self) -> None:
88
173
  pass
89
174
 
90
- def _highlight_fuzzy_match(
91
- self,
92
- suggestion: str,
93
- user_input: str,
94
- style_config: dict | None = None,
95
- ) -> StyleAndTextTuples:
96
- """
97
- 高亮显示模糊匹配结果
98
-
99
- Args:
100
- suggestion: 建议的完整字符串
101
- user_input: 用户输入的匹配字符
102
- style_config: 样式配置字典
103
-
104
- Returns:
105
- 包含样式信息的格式化文本
106
-
107
- """
108
- if style_config is None:
109
- style_config = {
110
- "match_char": "class:fuzzymatch.inside.character",
111
- "match_section": "class:fuzzymatch.inside",
112
- "non_match": "class:fuzzymatch.outside",
113
- }
114
-
115
- if not user_input:
116
- # 用户没有输入,返回原始字符串
117
- return [(style_config["non_match"], suggestion)]
118
-
119
- # 找到最佳匹配位置
120
- result = []
121
-
122
- # 简化的模糊匹配算法
123
- pattern = ".*?".join(map(re.escape, user_input))
124
- regex = re.compile(pattern, re.IGNORECASE)
125
-
126
- match = regex.search(suggestion)
127
- if not match:
128
- # 没有匹配,返回原始字符串
129
- return [(style_config["non_match"], suggestion)]
130
-
131
- start, end = match.span()
132
- match_text = suggestion[start:end]
133
-
134
- # 匹配段之前的文本
135
- if start > 0:
136
- result.append((style_config["non_match"], suggestion[:start]))
137
-
138
- # 匹配段内部的字符
139
- input_chars = list(user_input)
140
- for char in match_text:
141
- if input_chars and char.lower() == input_chars[0].lower():
142
- result.append((style_config["match_char"], char))
143
- input_chars.pop(0)
144
- else:
145
- result.append((style_config["match_section"], char))
146
-
147
- # 匹配段之后的文本
148
- if end < len(suggestion):
149
- result.append((style_config["non_match"], suggestion[end:]))
150
-
151
- return result
152
-
153
- def _fuzzy_filter_and_sort(self, filenames: list[str], match_str: str) -> list[str]:
154
- """模糊过滤和排序"""
155
- if not match_str:
156
- return sorted(filenames)
157
-
158
- # 构建模糊匹配模式
159
- pattern = ".*?".join(map(re.escape, match_str))
160
- regex = re.compile(f"(?=({pattern}))", re.IGNORECASE)
161
-
162
- matches = []
163
- for filename in filenames:
164
- regex_matches = list(regex.finditer(filename))
165
- if regex_matches:
166
- # 找到最佳匹配(最左、最短)
167
- best = min(regex_matches, key=lambda m: (m.start(), len(m.group(1))))
168
- matches.append((best.start(), len(best.group(1)), filename))
169
-
170
- # 按匹配质量排序:先按匹配位置,再按匹配长度
171
- matches.sort(key=lambda x: (x[0], x[1]))
172
- return [item[2] for item in matches]
173
-
174
175
  def get_completions(
175
176
  self,
176
177
  document: Document,
@@ -187,7 +188,7 @@ class SmartPathCompleter(Completer):
187
188
  os.listdir(directory) if os.path.isdir(directory) else []
188
189
  )
189
190
 
190
- for filename in self._fuzzy_filter_and_sort(filenames, basename):
191
+ for filename in _fuzzy_filter_and_sort(filenames, basename):
191
192
  full_name = (
192
193
  filename if directory == "." else os.path.join(directory, filename)
193
194
  )
@@ -203,8 +204,29 @@ class SmartPathCompleter(Completer):
203
204
  yield Completion(
204
205
  text=completion,
205
206
  start_position=-len(text),
206
- display=self._highlight_fuzzy_match(filename, basename),
207
+ display=_highlight_fuzzy_match(filename, basename),
207
208
  )
208
209
 
209
210
  except OSError:
210
211
  pass
212
+
213
+
214
+ class CustomPromptCompleter(Completer):
215
+ def get_completions(
216
+ self,
217
+ document: Document,
218
+ complete_event: CompleteEvent, # noqa: ARG002
219
+ ) -> Iterable[Completion]:
220
+ text = document.text_before_cursor
221
+ words = text.split()
222
+
223
+ custom_prompt = easyrip_prompt.get_custom_prompt()
224
+ for word in words[-1:]:
225
+ for name in _fuzzy_filter_and_sort(list(custom_prompt), word):
226
+ target_cmd = custom_prompt[name]
227
+ yield Completion(
228
+ text=target_cmd,
229
+ start_position=-len(word),
230
+ display=_highlight_fuzzy_match(name, word),
231
+ display_meta=target_cmd,
232
+ )
easyrip/global_val.py CHANGED
@@ -4,7 +4,7 @@ from functools import cache
4
4
  from pathlib import Path
5
5
 
6
6
  PROJECT_NAME = "Easy Rip"
7
- PROJECT_VERSION = "4.13.3"
7
+ PROJECT_VERSION = "4.14.0"
8
8
  PROJECT_TITLE = f"{PROJECT_NAME} v{PROJECT_VERSION}"
9
9
  PROJECT_URL = "https://github.com/op200/EasyRip"
10
10
  PROJECT_RELEASE_API = "https://api.github.com/repos/op200/EasyRip/releases/latest"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easyrip
3
- Version: 4.13.3
3
+ Version: 4.14.0
4
4
  Author: op200
5
5
  License-Expression: AGPL-3.0-or-later
6
6
  Project-URL: Homepage, https://github.com/op200/EasyRip
@@ -1,10 +1,10 @@
1
1
  easyrip/__init__.py,sha256=DULQoFEAEHYk7dS8Zxky56so7qDPqHm7jUc_Zop1eXw,616
2
- easyrip/__main__.py,sha256=QkzhmJPZUf79J-9ugJbks46fyYNG6dPn1K29nzfFVvU,5474
3
- easyrip/easyrip_command.py,sha256=g6gOJUrtQrSH2Hh6np2JgDqnWKVIWcV8SpMlkxApNgw,30210
2
+ easyrip/__main__.py,sha256=AEYyrxi1iUR7nEU7zln8JEHmetjhLqOue4VhFZFR5gI,5158
3
+ easyrip/easyrip_command.py,sha256=1_CbICn_4U6xKEt0TCU753Oa2wC7_1966IoKNIdJoWI,30303
4
4
  easyrip/easyrip_log.py,sha256=R-dM3CWUBFITtG7GSD1zy4X4MhZqxkoiBPjlIpI76cY,15573
5
- easyrip/easyrip_main.py,sha256=jadrI1qyjEw_x-RSx6TCxXoqwTIjQbamsen8lA71Ewc,47757
6
- easyrip/easyrip_prompt.py,sha256=he5gBUDwoDTPyriizFnwY39SX3UHV3eX6cHrCM7OdvI,6644
7
- easyrip/global_val.py,sha256=q5DwBxWzBDjF2w-1TYxm9YeZmz65ttkgytFuJjGuCsI,866
5
+ easyrip/easyrip_main.py,sha256=9KMpKUal8ApWZr80AQ7UwO1n6HUSWeVo2SuJ4pMW1f8,48130
6
+ easyrip/easyrip_prompt.py,sha256=TOmRJDigGRz7wRWFNakJdfNI1tn9vGekq6FH5ypmQfA,7068
7
+ easyrip/global_val.py,sha256=DRe2CSZP2HEBCcvEUVQRQLqR9_AfEQx-rQiYyjp7-NM,866
8
8
  easyrip/utils.py,sha256=N1rMF1MyoC-YFBgy10_u29cFoowfhR-5Viea93O7wQ4,8750
9
9
  easyrip/easyrip_config/config.py,sha256=KWXZMEYxdXYUGLQ-MR0A7nnOwR6QZdVrWBopfb2QZSA,9869
10
10
  easyrip/easyrip_config/config_key.py,sha256=_jjdKOunskUoG7UUWOz3QZK-s4LF_x6hmM9MKttyS2Q,766
@@ -24,9 +24,9 @@ easyrip/ripper/sub_and_font/__init__.py,sha256=cBT7mxL7RRFaJXFPXuZ7RT-YK6FbnanaU
24
24
  easyrip/ripper/sub_and_font/ass.py,sha256=EhDkVY5JXU77euWPId7H2v85j444m8ZLm7wUid7TYd8,35307
25
25
  easyrip/ripper/sub_and_font/font.py,sha256=X2dPcPzbwQf3fv_g_mxO-zY7puVAX9Nv-9QHn88q4oA,7745
26
26
  easyrip/ripper/sub_and_font/subset.py,sha256=--rAA3VH1rm_jBOC3yMs3rOJpn3tPuvfXqkimbBtx3s,18653
27
- easyrip-4.13.3.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
28
- easyrip-4.13.3.dist-info/METADATA,sha256=fgp3xx0u6tSa8VIrAxNWMUYHSjotJOsDVF6eILHJZ20,3507
29
- easyrip-4.13.3.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
30
- easyrip-4.13.3.dist-info/entry_points.txt,sha256=D6GBMMTzZ-apgX76KyZ6jxMmIFqGYwU9neeLLni_qKI,49
31
- easyrip-4.13.3.dist-info/top_level.txt,sha256=kuEteBXm-Gf90jRQgH3-fTo-Z-Q6czSuUEqY158H4Ww,8
32
- easyrip-4.13.3.dist-info/RECORD,,
27
+ easyrip-4.14.0.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
28
+ easyrip-4.14.0.dist-info/METADATA,sha256=NiCk9-cdBcFFvcSrKArJmsZKCZheat4MnfPKmLkJ7zo,3507
29
+ easyrip-4.14.0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
30
+ easyrip-4.14.0.dist-info/entry_points.txt,sha256=D6GBMMTzZ-apgX76KyZ6jxMmIFqGYwU9neeLLni_qKI,49
31
+ easyrip-4.14.0.dist-info/top_level.txt,sha256=kuEteBXm-Gf90jRQgH3-fTo-Z-Q6czSuUEqY158H4Ww,8
32
+ easyrip-4.14.0.dist-info/RECORD,,