ruff-sync 0.1.4.dev1__tar.gz → 0.1.5.dev2__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 (135) hide show
  1. ruff_sync-0.1.5.dev2/.agents/DEPENDENCIES.md +47 -0
  2. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/.agents/TESTING.md +2 -2
  3. ruff_sync-0.1.5.dev2/.agents/skills/dirty-equals/SKILL.md +37 -0
  4. ruff_sync-0.1.5.dev2/.agents/skills/dirty-equals/references/common-matchers.md +51 -0
  5. ruff_sync-0.1.5.dev2/.agents/skills/dirty-equals/references/toml-matching.md +54 -0
  6. ruff_sync-0.1.5.dev2/.agents/skills/dirty-equals/trigger_eval.json +30 -0
  7. ruff_sync-0.1.5.dev2/.agents/skills/mike/SKILL.md +106 -0
  8. ruff_sync-0.1.5.dev2/.agents/skills/mike/references/commands.md +78 -0
  9. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/.agents/skills/mkdocs-generation/templates/mkdocs.yml +4 -1
  10. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/.agents/skills/ruff-sync-usage/SKILL.md +1 -1
  11. ruff_sync-0.1.5.dev2/.agents/skills/textual/SKILL.md +52 -0
  12. ruff_sync-0.1.5.dev2/.agents/skills/textual/examples/basic_app.py +62 -0
  13. ruff_sync-0.1.5.dev2/.agents/skills/textual/examples/reactive_example.py +51 -0
  14. ruff_sync-0.1.5.dev2/.agents/skills/textual/references/events.md +84 -0
  15. ruff_sync-0.1.5.dev2/.agents/skills/textual/references/styling.md +54 -0
  16. ruff_sync-0.1.5.dev2/.agents/skills/textual/references/testing.md +58 -0
  17. ruff_sync-0.1.5.dev2/.agents/skills/textual/references/widgets.md +59 -0
  18. ruff_sync-0.1.5.dev2/.agents/tui_design.md +140 -0
  19. ruff_sync-0.1.5.dev2/.agents/tui_requirements.md +65 -0
  20. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/.github/workflows/ci.yaml +61 -0
  21. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/.pre-commit-config.yaml +1 -1
  22. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/AGENTS.md +31 -7
  23. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/CONTRIBUTING.md +1 -0
  24. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/PKG-INFO +3 -1
  25. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/docs/gen_ref_pages.py +1 -1
  26. ruff_sync-0.1.5.dev2/docs/overrides/main.html +6 -0
  27. ruff_sync-0.1.5.dev2/docs/overrides/partials/version_warning.html +18 -0
  28. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/mkdocs.yml +11 -0
  29. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/pyproject.toml +8 -1
  30. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/src/ruff_sync/__init__.py +12 -2
  31. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/src/ruff_sync/cli.py +191 -81
  32. ruff_sync-0.1.5.dev2/src/ruff_sync/config_io.py +136 -0
  33. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/src/ruff_sync/constants.py +66 -4
  34. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/src/ruff_sync/core.py +21 -101
  35. ruff_sync-0.1.5.dev2/src/ruff_sync/dependencies.py +38 -0
  36. ruff_sync-0.1.5.dev2/src/ruff_sync/system.py +45 -0
  37. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tasks.py +3 -3
  38. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/test_basic.py +20 -30
  39. ruff_sync-0.1.5.dev2/tests/test_ci_integration.py +34 -0
  40. ruff_sync-0.1.5.dev2/tests/test_ci_validation.py +226 -0
  41. ruff_sync-0.1.5.dev2/tests/test_config_io.py +153 -0
  42. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/test_config_validation.py +33 -0
  43. ruff_sync-0.1.5.dev2/tests/test_dependencies.py +35 -0
  44. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/test_e2e.py +81 -2
  45. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/test_formatters.py +3 -3
  46. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/test_serialization.py +21 -38
  47. ruff_sync-0.1.5.dev2/tests/test_system.py +54 -0
  48. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/uv.lock +212 -56
  49. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/.agents/formatters-architecture.md +0 -0
  50. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/.agents/gitlab-reports.md +0 -0
  51. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/.agents/issue-102-context.md +0 -0
  52. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/.agents/skills/gh-issues/SKILL.md +0 -0
  53. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/.agents/skills/mkdocs-generation/SKILL.md +0 -0
  54. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/.agents/skills/mkdocs-generation/examples.md +0 -0
  55. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/.agents/skills/mkdocs-generation/templates/api-reference.md +0 -0
  56. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/.agents/skills/mkdocs-generation/templates/getting-started.md +0 -0
  57. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/.agents/skills/mkdocs-generation/templates/index.md +0 -0
  58. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/.agents/skills/release-notes-generation/SKILL.md +0 -0
  59. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/.agents/skills/ruff-sync-usage/references/ci-integration.md +0 -0
  60. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/.agents/skills/ruff-sync-usage/references/configuration.md +0 -0
  61. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/.agents/skills/ruff-sync-usage/references/troubleshooting.md +0 -0
  62. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/.agents/workflows/add-test-case.md +0 -0
  63. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/.git-blame-ignore-revs +0 -0
  64. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/.github/dependabot.yml +0 -0
  65. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/.github/workflows/complexity.yaml +0 -0
  66. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/.github/workflows/docs.yaml +0 -0
  67. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/.gitignore +0 -0
  68. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/.pre-commit-hooks.yaml +0 -0
  69. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/LICENSE.md +0 -0
  70. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/README.md +0 -0
  71. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/codecov.yml +0 -0
  72. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/configs/data-science-engineering/ruff.toml +0 -0
  73. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/configs/fastapi/ruff.toml +0 -0
  74. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/configs/kitchen-sink/ruff.toml +0 -0
  75. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/docs/agent-skill.md +0 -0
  76. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/docs/assets/favicon.png +0 -0
  77. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/docs/assets/github-pr-annotation.png +0 -0
  78. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/docs/assets/logo.png +0 -0
  79. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/docs/assets/ruff_sync_banner.png +0 -0
  80. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/docs/best-practices.md +0 -0
  81. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/docs/ci-integration.md +0 -0
  82. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/docs/configuration.md +0 -0
  83. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/docs/contributing.md +0 -0
  84. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/docs/examples/advanced-config.toml +0 -0
  85. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/docs/examples/basic-config.toml +0 -0
  86. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/docs/index.md +0 -0
  87. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/docs/installation.md +0 -0
  88. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/docs/pre-commit.md +0 -0
  89. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/docs/pre-defined-configs.md +0 -0
  90. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/docs/troubleshooting.md +0 -0
  91. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/docs/url-resolution.md +0 -0
  92. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/docs/usage.md +0 -0
  93. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/scripts/check_dogfood.sh +0 -0
  94. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/scripts/gitclone_dogfood.sh +0 -0
  95. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/scripts/pull_dogfood.sh +0 -0
  96. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/skills-lock.json +0 -0
  97. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/src/ruff_sync/__main__.py +0 -0
  98. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/src/ruff_sync/formatters.py +0 -0
  99. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/src/ruff_sync/pre_commit.py +0 -0
  100. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/__init__.py +0 -0
  101. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/conftest.py +0 -0
  102. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/lifecycle_tomls/multi_upstream_final.toml +0 -0
  103. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/lifecycle_tomls/multi_upstream_initial.toml +0 -0
  104. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/lifecycle_tomls/multi_upstream_up1.toml +0 -0
  105. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/lifecycle_tomls/multi_upstream_up2.toml +0 -0
  106. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/lifecycle_tomls/no_changes_final.toml +0 -0
  107. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/lifecycle_tomls/no_changes_initial.toml +0 -0
  108. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/lifecycle_tomls/no_changes_upstream.toml +0 -0
  109. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/lifecycle_tomls/no_dotted_keys_final.toml +0 -0
  110. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/lifecycle_tomls/no_dotted_keys_initial.toml +0 -0
  111. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/lifecycle_tomls/no_dotted_keys_upstream.toml +0 -0
  112. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/lifecycle_tomls/no_ruff_cfg_final.toml +0 -0
  113. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/lifecycle_tomls/no_ruff_cfg_initial.toml +0 -0
  114. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/lifecycle_tomls/no_ruff_cfg_upstream.toml +0 -0
  115. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/lifecycle_tomls/readme_excludes_final.toml +0 -0
  116. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/lifecycle_tomls/readme_excludes_initial.toml +0 -0
  117. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/lifecycle_tomls/readme_excludes_upstream.toml +0 -0
  118. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/lifecycle_tomls/standard_final.toml +0 -0
  119. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/lifecycle_tomls/standard_initial.toml +0 -0
  120. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/lifecycle_tomls/standard_upstream.toml +0 -0
  121. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/ruff.toml +0 -0
  122. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/test_check.py +0 -0
  123. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/test_constants.py +0 -0
  124. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/test_corner_cases.py +0 -0
  125. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/test_deprecation.py +0 -0
  126. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/test_git_fetch.py +0 -0
  127. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/test_pre_commit.py +0 -0
  128. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/test_project.py +0 -0
  129. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/test_scaffold.py +0 -0
  130. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/test_toml_operations.py +0 -0
  131. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/test_url_handling.py +0 -0
  132. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/test_whitespace.py +0 -0
  133. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/w_ruff_sync_cfg/pyproject.toml +0 -0
  134. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/wo_ruff_cfg/pyproject.toml +0 -0
  135. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev2}/tests/wo_ruff_sync_cfg/pyproject.toml +0 -0
@@ -0,0 +1,47 @@
1
+ # Optional Dependencies & Lazy Loading
2
+
3
+ To keep the `ruff-sync` CLI fast and lightweight, while still allowing for extensible features (like a TUI or specialized formatters), we use a standardized pattern for **Optional Dependencies**.
4
+
5
+ ## Requirements
6
+
7
+ 1. **Defensive Coding**: The application must never crash at startup if an optional dependency is missing. Instead, it should fail gracefully ONLY when the specific feature requiring that dependency is invoked.
8
+ 2. **Delayed Import Cycles**: We must avoid expensive imports of optional dependencies during the initial CLI application boot.
9
+ 3. **User-Friendly Errors**: If a dependency is missing, we must provide clear instructions on how the user can install it using `ruff-sync` extras.
10
+
11
+ ## Standard Pattern
12
+
13
+ All code that relies on an optional dependency MUST follow this pattern:
14
+
15
+ ### 1. Define the Extra in `pyproject.toml`
16
+
17
+ Add the dependency to the `[project.optional-dependencies]` section.
18
+
19
+ ```toml
20
+ [project.optional-dependencies]
21
+ tui = ["textual>=8.2.2"]
22
+ ```
23
+
24
+ ### 2. Check and Lazy-Import (Locally)
25
+
26
+ Never import optional dependencies at the top level of a module. All imports must happen inside the function or method that requires them, AFTER a defensive check.
27
+
28
+ ```python
29
+ def run_tui_feature():
30
+ # 1. First, check availability (fast, lightweight)
31
+ from ruff_sync.dependencies import require_dependency
32
+ require_dependency("textual", extra_name="tui")
33
+
34
+ # 2. Then, perform local import (delayed expensive cycle)
35
+ from textual.app import App
36
+ ...
37
+ ```
38
+
39
+ ## The Dependency Helper (`ruff_sync.dependencies`)
40
+
41
+ Use the utilities in `src/ruff_sync/dependencies.py` to handle these checks.
42
+
43
+ - `is_installed(package_name: str) -> bool`: A fast check using `importlib.util.find_spec` that doesn't trigger the package initialization.
44
+ - `require_dependency(package_name: str, extra_name: str) -> None`: Checks if a package is installed and raises a helpful `ImportError` if it is not.
45
+
46
+ ### Example ImportError
47
+ > "The 'textual' package is required for this feature. Install it with: pip install 'ruff-sync[tui]'"
@@ -8,7 +8,7 @@ This document defines the mandatory testing standards and patterns for the `ruff
8
8
  - **No Side Effects**: Tests must be isolated and not touch the actual filesystem or make real network calls.
9
9
  - **Semantic + Structural Assertions**: When testing TOML merges, always verify **both**:
10
10
  1. **Structural/Whitespace**: The file "looks" correct (comments and spacing are preserved).
11
- 2. **Semantic**: The actual data in the merged result matches the expected values.
11
+ 2. **Semantic**: The actual data in the merged result matches the expected values. Use the [dirty-equals](skills/dirty-equals/SKILL.md) Agent Skill for declarative, concise assertions.
12
12
  - **DRY with Fixtures and Parameterization**: Avoid code duplication. Use fixtures for common setups and `@pytest.mark.parametrize` for matrix testing.
13
13
 
14
14
  ## 2. Tooling and Environment
@@ -130,6 +130,6 @@ def test_my_edge_case():
130
130
 
131
131
  ## 6. Code Coverage
132
132
 
133
- We target **high coverage** for `ruff_sync.py`.
133
+ We target **high coverage** for `src/ruff_sync/`.
134
134
  - Run coverage locally: `uv run coverage run -m pytest -vv && uv run coverage report`
135
135
  - New features MUST include unit tests in `tests/test_basic.py` or specialized files like `tests/test_whitespace.py` if they involve formatting logic.
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: dirty-equals
3
+ description: Use this skill when you need to write declarative, readable, and maintainable assertions in Python tests. It is particularly effective for matching complex data structures, validating merged TOML configurations, and performing fuzzy matching on URLs, file paths, or object types. Use this when the user asks to "assert," "check," "verify," or "match" data in a test, even if they don't explicitly mention "dirty-equals."
4
+ ---
5
+
6
+ # `dirty-equals` Skill
7
+
8
+ This skill provides patterns and best practices for writing declarative assertions in the `ruff-sync` project using the `dirty-equals` library.
9
+
10
+ ## Overview
11
+
12
+ Instead of asserting on every field manually, compare against a "dirty" object that matches the expected structure and types.
13
+
14
+ ```python
15
+ from dirty_equals import IsInt, IsPartialDict, IsStr
16
+
17
+ def test_config_logic():
18
+ result = {"status": "active", "version": 1, "extra": "data"}
19
+ # Declarative assertion
20
+ assert result == IsPartialDict({
21
+ "status": IsStr(regex="act.*"),
22
+ "version": IsInt(gt=0),
23
+ })
24
+ ```
25
+
26
+ ## Detailed reference
27
+
28
+ Check these references for project-specific usage and common matchers:
29
+
30
+ - **[Common Matchers](references/common-matchers.md)**: Standard `dirty-equals` matchers like `IsPartialDict`, `IsInstance`, and more.
31
+ - **[Specialized Matching](references/toml-matching.md)**: Handling `tomlkit.unwrap()` and `Arguments._asdict()`.
32
+
33
+ ## Best Practices
34
+
35
+ - **Import Style**: Always use the `from dirty_equals import ...` style at the **module level** of your test files.
36
+ - **Semantic Matching**: Use `dirty-equals` for the semantic part of your test assertions, while using string comparisons or `respx` for structural/whitespace checks where appropriate.
37
+ - **Type Safety**: Prefer `IsInstance(httpx.URL)` or `IsInstance(pathlib.Path)` over custom regex for well-known types in the project.
@@ -0,0 +1,51 @@
1
+ # Common `dirty-equals` Matchers
2
+
3
+ This guide covers the most frequently used `dirty-equals` matchers in the `ruff-sync` project. Standard practice is to import these using the `from` syntax at the module level in test files.
4
+
5
+ ## Structural Matching
6
+
7
+ - **`IsPartialDict`**: Matches a subset of a dictionary. Essential for verifying specific fields in a larger configuration.
8
+ - **`IsDict`**: Matches an entire dictionary exactly (while still allowing fuzzy values).
9
+ - **`IsList`**: Matches a list, allowing fuzzy matching for elements.
10
+
11
+ ## Type and Instance Matching
12
+
13
+ - **`IsInstance(type)`**: Matches an object of a specific class. Used for objects like `httpx.URL` or `pathlib.Path`.
14
+ - **`IsStr()`**: Matches any string. Can also specify regex or prefix/suffix.
15
+ - **`IsInt()`**: Matches any integer.
16
+
17
+ ## Examples
18
+
19
+ ### Using `IsPartialDict` and `IsInstance`
20
+
21
+ ```python
22
+ from dirty_equals import IsInstance, IsPartialDict
23
+ import httpx
24
+ import pathlib
25
+
26
+ # Match a partial dict with mixed types
27
+ assert response_data == IsPartialDict({
28
+ "url": IsInstance(httpx.URL),
29
+ "status": "success",
30
+ "retries": 0,
31
+ })
32
+ ```
33
+
34
+ ### String and Path Matching
35
+
36
+ ```python
37
+ from dirty_equals import IsInstance, IsStr
38
+ import pathlib
39
+
40
+ # Match a path instance
41
+ assert result_path == IsInstance(pathlib.Path)
42
+
43
+ # Match a string with a specific prefix
44
+ assert error_message == IsStr(regex="^ERROR:.*")
45
+ ```
46
+
47
+ ## Logic Matchers
48
+
49
+ - **`~` (Negation)**: Match values that are *not* the given value (e.g., `assert x == ~IsNone`).
50
+ - **`&` (AND)**: Combine matchers (e.g., `IsInt & IsPositive`).
51
+ - **`|` (OR)**: Combine alternatives.
@@ -0,0 +1,54 @@
1
+ # Specialized Matching for `ruff-sync`
2
+
3
+ This reference covers project-specific data structures where `dirty-equals` matchers require particular preparation for reliable results.
4
+
5
+ ## TOML Matching (`tomlkit`)
6
+
7
+ `tomlkit` returns proxy objects (containers and items). To reliably match these with `dirty-equals`, always use `.unwrap()` on the parsed document or table to convert it to plain Python types.
8
+
9
+ ### Correct Pattern
10
+
11
+ ```python
12
+ from dirty_equals import IsPartialDict
13
+ import tomlkit
14
+
15
+ # Parse some TOML
16
+ doc = tomlkit.parse('[tool.ruff]\nline-length = 80')
17
+
18
+ # Match the tool.ruff section
19
+ ruff_config = doc["tool"]["ruff"]
20
+
21
+ # Must use .unwrap()
22
+ assert ruff_config.unwrap() == IsPartialDict({"line-length": 80})
23
+ ```
24
+
25
+ ### Potential Gotcha
26
+ Direct matching of `tomlkit` proxy objects without `.unwrap()` can fail because `dirty-equals` may see the proxy's internal attributes rather than its data.
27
+
28
+ ## CLI `Arguments` matching
29
+
30
+ Our CLI arguments are defined as a `NamedTuple`. To match specific fields without validating the entire object, convert it to a dictionary using `._asdict()`.
31
+
32
+ ### Correct Pattern
33
+
34
+ ```python
35
+ from dirty_equals import IsInstance, IsPartialDict
36
+ import httpx
37
+ import pathlib
38
+ import ruff_sync_cli
39
+
40
+ # Sample Arguments instance
41
+ args = ruff_sync_cli.Arguments(
42
+ command="pull",
43
+ upstream=(httpx.URL("https://example.com"),),
44
+ to=pathlib.Path("."),
45
+ # ... other defaults ...
46
+ )
47
+
48
+ # Convert to dict and match specific fields
49
+ assert args._asdict() == IsPartialDict({
50
+ "command": "pull",
51
+ "upstream": (IsInstance(httpx.URL),),
52
+ "to": IsInstance(pathlib.Path),
53
+ })
54
+ ```
@@ -0,0 +1,30 @@
1
+ [
2
+ {
3
+ "query": "I need to check if the tool.ruff section was merged correctly",
4
+ "should_trigger": true
5
+ },
6
+ {
7
+ "query": "add a unit test that verifies the output",
8
+ "should_trigger": true
9
+ },
10
+ {
11
+ "query": "where should I use dirty-equals",
12
+ "should_trigger": true
13
+ },
14
+ {
15
+ "query": "verify that this URL is from github.com",
16
+ "should_trigger": true
17
+ },
18
+ {
19
+ "query": "assert that the dictionary matches this schema",
20
+ "should_trigger": true
21
+ },
22
+ {
23
+ "query": "fix this syntax error in the CLI",
24
+ "should_trigger": false
25
+ },
26
+ {
27
+ "query": "run the tests with coverage",
28
+ "should_trigger": false
29
+ }
30
+ ]
@@ -0,0 +1,106 @@
1
+ ---
2
+ name: mike
3
+ description: Use this skill to manage multiple versions of documentation for MkDocs-powered sites. Deploy new versions, update aliases (like 'latest' or 'stable'), set the default version for the site root, and manage versioned subdirectories in the deployment branch. Use this whenever the user wants to publish, version, or alias documentation, even if they don't explicitly mention "mike". Also use this when the user wants to troubleshoot or debug versioned documentation.
4
+ ---
5
+
6
+ # mike: MkDocs Versioning
7
+
8
+ `mike` is used in this project to manage a versioned documentation site, allowing side-by-side availability of `dev` (main branch) and stable release docs (e.g., `0.1.4`).
9
+
10
+ ## Prerequisites
11
+
12
+ `mike` is included in the `docs` dependency group.
13
+ ```bash
14
+ # Sync documentation dependencies
15
+ uv sync --group docs
16
+ ```
17
+
18
+ ## Project Strategy
19
+
20
+ This project follows a specific versioning strategy:
21
+ 1. **`dev`**: Represents the current `main` branch.
22
+ 2. **Stable Releases**: Versioned documentation (e.g., `0.1.4`) created upon release.
23
+ 3. **`stable`**: An alias always pointing to the most recent non-dev release.
24
+ 4. **`latest`**: An alias pointing to the most recent release (including dev, if applicable).
25
+
26
+ ## Theme Overrides
27
+
28
+ To support the version switcher and custom banners, the project uses a `custom_dir` override in `mkdocs.yml`:
29
+
30
+ ```yaml
31
+ theme:
32
+ name: material
33
+ custom_dir: docs/overrides
34
+ ```
35
+
36
+ ### Version switcher
37
+
38
+ The version switcher is enabled via `extra.version.provider: mike`:
39
+
40
+ ```yaml
41
+ extra:
42
+ version:
43
+ provider: mike
44
+ ```
45
+
46
+ ## Versioning Banner
47
+
48
+ A custom banner is displayed when users are viewing the `dev` documentation. This is handled via `docs/overrides/main.html` and `docs/overrides/partials/version_warning.html`.
49
+
50
+ ### How it works:
51
+ - `main.html`: Extends the base template and includes the `version_warning.html` partial at the start of the `content` block.
52
+ - `version_warning.html`: Contains an HTML snippet that is hidden by default and shown via JavaScript if the URL path contains `/dev/`.
53
+
54
+ Example in `version_warning.html`:
55
+ ```html
56
+ <div id="version-warning" style="display: none;">
57
+ <div class="admonition warning">
58
+ <p class="admonition-title">Warning</p>
59
+ <p>You are viewing the development version of the documentation.</p>
60
+ </div>
61
+ </div>
62
+ <script>
63
+ if (window.location.pathname.includes("/dev/")) {
64
+ document.getElementById("version-warning").style.display = "block";
65
+ }
66
+ </script>
67
+ ```
68
+
69
+ ### 1. Deploying Development Docs
70
+ Run this from the `main` branch to update the `dev` version:
71
+ ```bash
72
+ mike deploy dev --push --update-aliases
73
+ ```
74
+
75
+ ### 2. Deploying a Stable Release
76
+ When a new version is released (e.g., `0.1.4`), deploy it and update the `stable` alias:
77
+ ```bash
78
+ # Deploy the specific version and update 'stable'
79
+ mike deploy 0.1.4 stable --push --update-aliases
80
+
81
+ # Set 'stable' as the default version for the site root
82
+ mike set-default --push stable
83
+ ```
84
+
85
+ ## Reference Commands
86
+
87
+ | Action | Command |
88
+ | :--- | :--- |
89
+ | **Deploy** | `mike deploy <version> [alias]` |
90
+ | **List** | `mike list` |
91
+ | **Set Default** | `mike set-default <version>` |
92
+ | **Alias** | `mike alias <version> <alias>` |
93
+ | **Delete** | `mike delete <identifier>` |
94
+
95
+ ## CI/CD Integration
96
+
97
+ The deployment logic is automated in [.github/workflows/ci.yaml](.github/workflows/ci.yaml). It automatically:
98
+ - Extracts the version from `pyproject.toml`.
99
+ - Deploys to `dev` if the version contains `.dev`.
100
+ - Deploys to `<version>` and updates `stable` for official releases.
101
+
102
+ > [!IMPORTANT]
103
+ > **Do NOT** use `mike install-gh-pages`. It is deprecated and removed in the version used by this project. `mike deploy` handles branch initialization automatically.
104
+
105
+ > [!TIP]
106
+ > Use `mike serve` locally to preview the version switcher before pushing changes.
@@ -0,0 +1,78 @@
1
+ # Reference: mike CLI commands
2
+
3
+ Detailed parameter and core command reference for `mike`.
4
+
5
+ ## `deploy`
6
+
7
+ ```bash
8
+ mike deploy [options] <version> [alias]...
9
+ ```
10
+
11
+ Build the documentation for the specified version and commit it to the deployment branch.
12
+
13
+ - `<version>`: The version name to deploy (e.g., `1.0`, `v1.2.3`).
14
+ - `[alias]`: Optional aliases to point to this version (e.g., `latest`, `stable`).
15
+
16
+ **Common options:**
17
+ - `-p, --push`: Push the branch to the remote repository.
18
+ - `-b, --branch NAME`: Deployment branch (default: `gh-pages`).
19
+ - `-m, --message MESSAGE`: Custom commit message.
20
+ - `-u, --update-aliases`: Update existing aliases if they already point to a different version.
21
+ - `--alias-type {symlink,redirect,copy}`: Method for creating aliases.
22
+
23
+ ---
24
+
25
+ ## `alias`
26
+
27
+ ```bash
28
+ mike alias [options] <version> <alias>...
29
+ ```
30
+
31
+ Create or update aliases for a version without rebuilding the documentation.
32
+
33
+ ---
34
+
35
+ ## `set-default`
36
+
37
+ ```bash
38
+ mike set-default [options] <version>
39
+ ```
40
+
41
+ Set the default version for the documentation site's root redirect.
42
+
43
+ ---
44
+
45
+ ## `list`
46
+
47
+ ```bash
48
+ mike list [options]
49
+ ```
50
+
51
+ List all versions and aliases deployed to the deployment branch.
52
+
53
+ ---
54
+
55
+ ## `delete`
56
+
57
+ ```bash
58
+ mike delete [options] <identifier>...
59
+ ```
60
+
61
+ Delete one or more versions or aliases.
62
+
63
+ - `<identifier>`: Version or alias name.
64
+
65
+ **Options:**
66
+ - `--all`: Delete all versions/aliases.
67
+
68
+ ---
69
+
70
+ ## `serve`
71
+
72
+ ```bash
73
+ mike serve [options]
74
+ ```
75
+
76
+ Locally serve the documentation from the deployment branch. This is for testing the built versioned site.
77
+
78
+ - `-a, --addr ADDR`: Address to bind to (default: `localhost:8000`).
@@ -10,10 +10,13 @@ repo_name: [GITHUB_USER]/[REPO_NAME]
10
10
  extra:
11
11
  version:
12
12
  provider: mike
13
- default: latest
13
+ social:
14
+ - icon: fontawesome/brands/github
15
+ link: https://github.com/[GITHUB_USER]/[REPO_NAME]
14
16
 
15
17
  theme:
16
18
  name: material
19
+ custom_dir: docs/overrides
17
20
  features:
18
21
  - navigation.sections
19
22
  - navigation.top
@@ -117,7 +117,7 @@ ruff-sync git@github.com:my-org/standards.git # SSH (shallow clone)
117
117
 
118
118
  | Flag | Meaning |
119
119
  |------|---------|
120
- | `--output-format` | `text` (default), `json`, `github` (PR annotations) |
120
+ | `--output-format` | `text` (default), `json`, `github`, `gitlab`, `sarif` (auto-detected in CI) |
121
121
  | `--semantic` | Ignore whitespace/comments in `check` |
122
122
  | `--pre-commit` | Sync `.pre-commit-config.yaml` hook version |
123
123
  | `--save` | Persist CLI args to `pyproject.toml` |
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: textual
3
+ description: Build sophisticated Terminal User Interfaces (TUIs) in Python using an async, CSS-inspired framework.
4
+ ---
5
+
6
+ # Textual TUI Framework (>=8.2.2)
7
+
8
+ Textual is a Python framework for creating interactive, beautiful Terminal User Interfaces (TUIs). It uses an asynchronous engine and a layout system inspired by modern web development (Flexbox/Grid and CSS).
9
+
10
+ ## Quick Start
11
+
12
+ ```python
13
+ from __future__ import annotations
14
+ from textual.app import App, ComposeResult
15
+ from textual.widgets import Header, Footer, Static
16
+
17
+ class SimpleApp(App[None]):
18
+ """A minimal Textual app."""
19
+ BINDINGS = [("q", "quit", "Quit")]
20
+
21
+ def compose(self) -> ComposeResult:
22
+ yield Header()
23
+ yield Static("Hello, [bold blue]Textual[/bold blue]!")
24
+ yield Footer()
25
+
26
+ if __name__ == "__main__":
27
+ SimpleApp().run()
28
+ ```
29
+
30
+ ## Core Workflow
31
+
32
+ 1. **`compose()`**: Define the UI structure by `yield`-ing widgets.
33
+ 2. **Styling**: Use **TCSS** (Textual Cascading Style Sheets) for layout and design.
34
+ 3. **Event Handlers**: Handle interaction via specially named methods (e.g., `on_button_pressed`).
35
+ 4. **Reactivity**: Use `reactive` attributes to automatically update the UI when data changes.
36
+
37
+ ## Progressive Disclosure (Detailed References)
38
+
39
+ - [**Styling & Layout**](references/styling.md): TCSS, Flexbox, Grid, and Units.
40
+ - [**Events & Reactivity**](references/events.md): Message passing, watchers, and state management.
41
+ - [**Widget Library**](references/widgets.md): Common components (DataTable, Input, ListView).
42
+ - [**Testing**](references/testing.md): Unit testing apps with `pilot` and `App.run_test`.
43
+
44
+ ## Gotchas & Breaking Changes (>=8.2.2)
45
+
46
+ > [!WARNING]
47
+ > - **8.2.2 Breaking Changes**:
48
+ > - `Static.renderable` and `Label.renderable` are now **`Static.content`** and **`Label.content`**.
49
+ > - `Select.BLANK` is now **`Select.NULL`**.
50
+ > - **Fractional Units**: Use `fr` for fractional units (e.g. `width: 1fr`). A common typo is `rf`, which is invalid.
51
+ > - **Async Handlers**: Event handlers can be `async def` or `def`. Use `async` if you need to `await` I/O or `post_message`.
52
+ > - **Main Thread**: Do not block the main thread with long-running synchronous code. Use `self.run_worker()` for background tasks.
@@ -0,0 +1,62 @@
1
+ """A minimal Textual app boilerplate."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import ClassVar
6
+
7
+ from textual.app import App, ComposeResult
8
+ from textual.containers import Vertical
9
+ from textual.widgets import Button, Footer, Header, Static
10
+
11
+
12
+ class BasicApp(App[None]):
13
+ """A minimal Textual app boilerplate."""
14
+
15
+ # v8.x.x: Specify a default theme
16
+ theme: ClassVar[str] = "nord"
17
+
18
+ BINDINGS: ClassVar[list[tuple[str, str, str]]] = [
19
+ ("q", "quit", "Quit application"),
20
+ ("d", "toggle_dark", "Toggle Dark Mode"),
21
+ ]
22
+
23
+ CSS = """
24
+ Screen {
25
+ align: center middle;
26
+ }
27
+
28
+ Vertical {
29
+ width: 30;
30
+ height: auto;
31
+ border: solid $accent;
32
+ padding: 1;
33
+ }
34
+
35
+ Static {
36
+ text-align: center;
37
+ width: 100%;
38
+ margin-bottom: 1;
39
+ }
40
+
41
+ /* v8.x.x: New pointer rule */
42
+ Button {
43
+ pointer: pointer;
44
+ }
45
+ """
46
+
47
+ def compose(self) -> ComposeResult:
48
+ """Compose the UI."""
49
+ yield Header()
50
+ with Vertical():
51
+ yield Static("Basic Textual App")
52
+ yield Button("Click Me!", id="hello-btn", variant="primary")
53
+ yield Footer()
54
+
55
+ def on_button_pressed(self, event: Button.Pressed) -> None:
56
+ """Handle button press events."""
57
+ if event.button.id == "hello-btn":
58
+ self.notify("Action Triggered!")
59
+
60
+
61
+ if __name__ == "__main__":
62
+ BasicApp().run()
@@ -0,0 +1,51 @@
1
+ """Demonstrating Textual's reactive system."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from textual.app import App, ComposeResult
6
+ from textual.containers import Container
7
+ from textual.reactive import reactive
8
+ from textual.widgets import Footer, Header, Input, Static
9
+
10
+
11
+ class ReactiveApp(App[None]):
12
+ """Demonstrating Textual's reactive system."""
13
+
14
+ # Reactive attribute: UI can respond to changes automatically
15
+ search_query: reactive[str] = reactive("")
16
+
17
+ CSS = """
18
+ #status {
19
+ background: $primary;
20
+ color: $text;
21
+ /* v8.x.x: Simplified padding for text content */
22
+ text-padding: 1 2;
23
+ margin-top: 1;
24
+ width: 100%;
25
+ text-align: center;
26
+ }
27
+ """
28
+
29
+ def compose(self) -> ComposeResult:
30
+ """Compose the UI."""
31
+ yield Header()
32
+ with Container():
33
+ yield Input(placeholder="Type to search...", id="search-input")
34
+ yield Static("Waiting for input...", id="status")
35
+ yield Footer()
36
+
37
+ def watch_search_query(self, query: str) -> None:
38
+ """Called automatically when search_query changes."""
39
+ status = self.query_one("#status")
40
+ if query:
41
+ status.update(f"Searching for: [bold]{query}[/bold]")
42
+ else:
43
+ status.update("Waiting for input...")
44
+
45
+ def on_input_changed(self, event: Input.Changed) -> None:
46
+ """Update reactive state on input."""
47
+ self.search_query = event.value
48
+
49
+
50
+ if __name__ == "__main__":
51
+ ReactiveApp().run()