pyquipu-cli 0.2.0__tar.gz → 0.2.1__tar.gz

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.
Files changed (55) hide show
  1. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/PKG-INFO +5 -5
  2. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/pyproject.toml +5 -5
  3. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/commands/workspace.py +1 -0
  4. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/tui.py +14 -0
  5. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/tests/unit/test_tui_reachability.py +7 -7
  6. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/tests/unit/test_view_model.py +3 -3
  7. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/.gitignore +0 -0
  8. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/__init__.py +0 -0
  9. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/commands/__init__.py +0 -0
  10. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/commands/axon.py +0 -0
  11. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/commands/cache.py +0 -0
  12. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/commands/cache.stitcher.yaml +0 -0
  13. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/commands/export.py +0 -0
  14. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/commands/export.stitcher.yaml +0 -0
  15. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/commands/helpers.py +0 -0
  16. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/commands/helpers.stitcher.yaml +0 -0
  17. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/commands/navigation.py +0 -0
  18. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/commands/query.py +0 -0
  19. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/commands/query.stitcher.yaml +0 -0
  20. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/commands/remote.py +0 -0
  21. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/commands/run.py +0 -0
  22. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/commands/show.py +0 -0
  23. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/commands/show.stitcher.yaml +0 -0
  24. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/commands/ui.py +0 -0
  25. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/config.py +0 -0
  26. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/config.stitcher.yaml +0 -0
  27. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/logger_config.py +0 -0
  28. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/logger_config.stitcher.yaml +0 -0
  29. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/main.py +0 -0
  30. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/rendering.py +0 -0
  31. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/rendering.stitcher.yaml +0 -0
  32. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/tui.css +0 -0
  33. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/tui.stitcher.yaml +0 -0
  34. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/ui_utils.py +0 -0
  35. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/ui_utils.stitcher.yaml +0 -0
  36. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/view_model.py +0 -0
  37. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/src/quipu/cli/view_model.stitcher.yaml +0 -0
  38. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/stitcher.lock +0 -0
  39. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/tests/integration/conftest.py +0 -0
  40. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/tests/integration/test_cache_commands.py +0 -0
  41. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/tests/integration/test_cache_commands.stitcher.yaml +0 -0
  42. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/tests/integration/test_cli_interaction.py +0 -0
  43. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/tests/integration/test_cli_interaction.stitcher.yaml +0 -0
  44. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/tests/integration/test_export_command.py +0 -0
  45. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/tests/integration/test_export_command.stitcher.yaml +0 -0
  46. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/tests/integration/test_navigation_commands.py +0 -0
  47. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/tests/integration/test_query_commands.py +0 -0
  48. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/tests/integration/test_query_commands.stitcher.yaml +0 -0
  49. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/tests/integration/test_unfriendly_paths.py +0 -0
  50. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/tests/integration/test_unfriendly_paths.stitcher.yaml +0 -0
  51. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/tests/integration/test_workspace_commands.py +0 -0
  52. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/tests/unit/test_tui_logic.py +0 -0
  53. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/tests/unit/test_tui_logic.stitcher.yaml +0 -0
  54. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/tests/unit/test_tui_reachability.stitcher.yaml +0 -0
  55. {pyquipu_cli-0.2.0 → pyquipu_cli-0.2.1}/tests/unit/test_view_model.stitcher.yaml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyquipu-cli
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: The command-line interface for Quipu.
5
5
  Project-URL: Homepage, https://github.com/quipu-dev/Quipu
6
6
  Project-URL: Repository, https://github.com/quipu-dev/Quipu
@@ -20,11 +20,11 @@ Classifier: Topic :: Software Development :: Version Control
20
20
  Classifier: Topic :: Text Processing
21
21
  Classifier: Typing :: Typed
22
22
  Requires-Python: >=3.10
23
- Requires-Dist: pyquipu-application~=0.2.0
23
+ Requires-Dist: pyquipu-application~=0.2.1
24
24
  Requires-Dist: pyquipu-bus~=0.2.0
25
25
  Requires-Dist: pyquipu-common~=0.1.1
26
- Requires-Dist: pyquipu-engine~=0.2.0
27
- Requires-Dist: pyquipu-runtime~=0.2.0
28
- Requires-Dist: pyquipu-spec~=0.1.0
26
+ Requires-Dist: pyquipu-engine~=0.2.1
27
+ Requires-Dist: pyquipu-runtime~=0.2.1
28
+ Requires-Dist: pyquipu-spec~=0.1.1
29
29
  Requires-Dist: textual>=0.58.0
30
30
  Requires-Dist: typer>=0.9.0
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pyquipu-cli"
7
- version = "0.2.0"
7
+ version = "0.2.1"
8
8
  authors = [{ name="doucx", email="doucxldh@gmail.com" }]
9
9
  description = "The command-line interface for Quipu."
10
10
  requires-python = ">=3.10"
@@ -25,11 +25,11 @@ classifiers = [
25
25
  keywords = ["quipu", "devops", "cli", "tui", "git", "history"]
26
26
 
27
27
  dependencies = [
28
- "pyquipu-spec ~= 0.1.0",
29
- "pyquipu-engine ~= 0.2.0",
30
- "pyquipu-runtime ~= 0.2.0",
28
+ "pyquipu-spec ~= 0.1.1",
29
+ "pyquipu-engine ~= 0.2.1",
30
+ "pyquipu-runtime ~= 0.2.1",
31
31
  "pyquipu-common ~= 0.1.1",
32
- "pyquipu-application ~= 0.2.0",
32
+ "pyquipu-application ~= 0.2.1",
33
33
  "pyquipu-bus ~= 0.2.0",
34
34
  "typer >= 0.9.0",
35
35
  "textual >= 0.58.0",
@@ -74,6 +74,7 @@ def register(app: typer.Typer):
74
74
  bus.success("workspace.discard.noChanges", short_hash=latest_node.short_hash)
75
75
  ctx.exit(0)
76
76
 
77
+ assert target_tree_hash is not None
77
78
  diff_stat_str = engine.git_db.get_diff_stat(target_tree_hash, current_hash)
78
79
 
79
80
  if not force:
@@ -102,10 +102,12 @@ class QuipuUiApp(App[Optional[UiResult]]):
102
102
  self.engine.close()
103
103
 
104
104
  def _update_header(self):
105
+ assert self.view_model is not None
105
106
  mode = "Markdown" if self.markdown_enabled else "Raw Text"
106
107
  self.sub_title = f"Page {self.view_model.current_page} / {self.view_model.total_pages} | View: {mode} (m)"
107
108
 
108
109
  def _load_page(self, page_number: int) -> None:
110
+ assert self.view_model is not None
109
111
  logger.debug(f"TUI: Loading page {page_number}")
110
112
  self.view_model.load_page(page_number)
111
113
  logger.debug(f"TUI: Page {page_number} loaded with {len(self.view_model.current_page_nodes)} nodes.")
@@ -124,6 +126,7 @@ class QuipuUiApp(App[Optional[UiResult]]):
124
126
  self.query_one(DataTable).action_cursor_down()
125
127
 
126
128
  def action_toggle_hidden(self) -> None:
129
+ assert self.view_model is not None
127
130
  self.view_model.toggle_unreachable()
128
131
  self._refresh_table()
129
132
 
@@ -138,29 +141,34 @@ class QuipuUiApp(App[Optional[UiResult]]):
138
141
  pass
139
142
 
140
143
  def action_checkout_node(self) -> None:
144
+ assert self.view_model is not None
141
145
  selected_node = self.view_model.get_selected_node()
142
146
  if selected_node:
143
147
  self.exit(result=("checkout", selected_node.output_tree))
144
148
 
145
149
  def action_dump_content(self) -> None:
150
+ assert self.view_model is not None
146
151
  selected_node = self.view_model.get_selected_node()
147
152
  if selected_node:
148
153
  content = self.view_model.get_public_content(selected_node)
149
154
  self.exit(result=("dump", content))
150
155
 
151
156
  def action_previous_page(self) -> None:
157
+ assert self.view_model is not None
152
158
  if self.view_model.current_page > 1:
153
159
  self._load_page(self.view_model.current_page - 1)
154
160
  else:
155
161
  self.bell()
156
162
 
157
163
  def action_next_page(self) -> None:
164
+ assert self.view_model is not None
158
165
  if self.view_model.current_page < self.view_model.total_pages:
159
166
  self._load_page(self.view_model.current_page + 1)
160
167
  else:
161
168
  self.bell()
162
169
 
163
170
  def _refresh_table(self):
171
+ assert self.view_model is not None
164
172
  table = self.query_one(DataTable)
165
173
  table.clear()
166
174
  # 从 ViewModel 获取要渲染的节点
@@ -170,6 +178,7 @@ class QuipuUiApp(App[Optional[UiResult]]):
170
178
  self._update_header()
171
179
 
172
180
  def _populate_table(self, table: DataTable, nodes: List[QuipuNode]):
181
+ assert self.view_model is not None
173
182
  # 移除了过滤逻辑,因为 ViewModel 已经处理
174
183
  tracks: list[Optional[str]] = []
175
184
 
@@ -228,6 +237,7 @@ class QuipuUiApp(App[Optional[UiResult]]):
228
237
  return node.summary or "No description"
229
238
 
230
239
  def _focus_current_node(self, table: DataTable):
240
+ assert self.view_model is not None
231
241
  current_output_tree_hash = self.view_model.current_output_tree_hash
232
242
  logger.debug(f"DEBUG: Attempting focus. HEAD={current_output_tree_hash}")
233
243
 
@@ -273,6 +283,7 @@ class QuipuUiApp(App[Optional[UiResult]]):
273
283
  logger.error(f"DEBUG: Failed to focus current node: {e}", exc_info=True)
274
284
 
275
285
  def _update_loading_preview(self):
286
+ assert self.view_model is not None
276
287
  node = self.view_model.get_selected_node()
277
288
  if not node:
278
289
  return
@@ -321,6 +332,7 @@ class QuipuUiApp(App[Optional[UiResult]]):
321
332
 
322
333
  case ContentViewSate.SHOWING_CONTENT:
323
334
  container.set_class(True, "split-mode")
335
+ assert self.view_model is not None
324
336
  node = self.view_model.get_selected_node()
325
337
 
326
338
  if node:
@@ -341,6 +353,7 @@ class QuipuUiApp(App[Optional[UiResult]]):
341
353
 
342
354
  @on(DataTable.RowHighlighted)
343
355
  def on_row_highlighted(self, event: DataTable.RowHighlighted) -> None:
356
+ assert self.view_model is not None
344
357
  # 1. Update data model
345
358
  if event.row_key.value:
346
359
  self.view_model.select_node_by_key(event.row_key.value)
@@ -367,6 +380,7 @@ class QuipuUiApp(App[Optional[UiResult]]):
367
380
 
368
381
  def action_toggle_view(self) -> None:
369
382
  if self.content_view_state == ContentViewSate.HIDDEN:
383
+ assert self.view_model is not None
370
384
  # If a node is selected, transition to loading, otherwise do nothing
371
385
  if self.view_model.get_selected_node():
372
386
  self._set_state(ContentViewSate.LOADING)
@@ -55,14 +55,14 @@ class TestUiReachability:
55
55
 
56
56
  ancestors = {"a", "root"}
57
57
  view_model = view_model_factory([node_root, node_a, node_b], current_hash="a", ancestors=ancestors)
58
- app = QuipuUiApp(work_dir=Path("."))
59
- app.view_model = view_model
60
- app.show_unreachable = False
61
58
 
62
- nodes_on_page = view_model.load_page(1)
63
- rendered_nodes = [
64
- node for node in nodes_on_page if app.show_unreachable or app.view_model.is_reachable(node.output_tree)
65
- ]
59
+ # 模拟 TUI 切换视图以隐藏不可达节点
60
+ # ViewModel 默认 show_unreachable=True
61
+ view_model.toggle_unreachable()
62
+ assert view_model.show_unreachable is False
63
+
64
+ view_model.load_page(1)
65
+ rendered_nodes = view_model.get_nodes_to_render()
66
66
 
67
67
  assert node_b not in rendered_nodes
68
68
  assert node_a in rendered_nodes
@@ -12,9 +12,9 @@ class MockHistoryReader(HistoryReader):
12
12
  def __init__(
13
13
  self,
14
14
  nodes: List[QuipuNode],
15
- ancestors: Set[str] = None,
16
- descendants: Set[str] = None,
17
- private_data: dict[str, str] = None,
15
+ ancestors: Optional[Set[str]] = None,
16
+ descendants: Optional[Set[str]] = None,
17
+ private_data: Optional[dict[str, str]] = None,
18
18
  ):
19
19
  self._nodes = sorted(nodes, key=lambda n: n.timestamp, reverse=True)
20
20
  self._ancestors = ancestors or set()
File without changes
File without changes