simdoc 0.1.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.
- simdoc-0.1.1/.gitignore +8 -0
- simdoc-0.1.1/.python-version +1 -0
- simdoc-0.1.1/AGENTS.md +121 -0
- simdoc-0.1.1/LICENSE +21 -0
- simdoc-0.1.1/PKG-INFO +53 -0
- simdoc-0.1.1/README.md +45 -0
- simdoc-0.1.1/docs/handoff_260203_1.md +349 -0
- simdoc-0.1.1/docs/llm-persona.md +44 -0
- simdoc-0.1.1/examples/example_all_blocks.py +85 -0
- simdoc-0.1.1/examples/example_output.md +91 -0
- simdoc-0.1.1/pyproject.toml +26 -0
- simdoc-0.1.1/src/simdoc/__init__.py +6 -0
- simdoc-0.1.1/src/simdoc/_blocks.py +42 -0
- simdoc-0.1.1/src/simdoc/_errors.py +2 -0
- simdoc-0.1.1/src/simdoc/_io.py +11 -0
- simdoc-0.1.1/src/simdoc/_render/__init__.py +1 -0
- simdoc-0.1.1/src/simdoc/_render/markdown.py +130 -0
- simdoc-0.1.1/src/simdoc/_text.py +31 -0
- simdoc-0.1.1/src/simdoc/doc.py +137 -0
- simdoc-0.1.1/tests/conftest.py +8 -0
- simdoc-0.1.1/tests/fixtures/golden/basic.md +10 -0
- simdoc-0.1.1/tests/fixtures/golden/code_fences.md +5 -0
- simdoc-0.1.1/tests/fixtures/golden/lists.md +8 -0
- simdoc-0.1.1/tests/fixtures/golden/tables.md +4 -0
- simdoc-0.1.1/tests/test_code_fences.py +15 -0
- simdoc-0.1.1/tests/test_golden_markdown.py +19 -0
- simdoc-0.1.1/tests/test_lists.py +16 -0
- simdoc-0.1.1/tests/test_tables.py +20 -0
- simdoc-0.1.1/uv.lock +173 -0
simdoc-0.1.1/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
simdoc-0.1.1/AGENTS.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
# Guidance for coding agents working in this repo.
|
|
3
|
+
|
|
4
|
+
## Scope and repo status
|
|
5
|
+
|
|
6
|
+
- This repository is currently a design/spec repository.
|
|
7
|
+
- The only substantive guidance is in `docs/handoff_260203_1.md` and `docs/llm-persona.md`.
|
|
8
|
+
- There is no `src/` or `tests/` directory yet, and no build or lint config files.
|
|
9
|
+
- Follow the specs exactly; do not invent behavior beyond the locked decisions.
|
|
10
|
+
|
|
11
|
+
## Cursor/Copilot rules
|
|
12
|
+
|
|
13
|
+
- No Cursor rules found in `.cursor/rules/` or `.cursorrules`.
|
|
14
|
+
- No Copilot rules found in `.github/copilot-instructions.md`.
|
|
15
|
+
|
|
16
|
+
## Build, lint, and test commands
|
|
17
|
+
|
|
18
|
+
There is no configured tooling yet (no `pyproject.toml`, `pytest.ini`, etc.).
|
|
19
|
+
When implementation lands, prefer the `uv` workflow and `pytest` for tests.
|
|
20
|
+
|
|
21
|
+
### Expected/placeholder commands (use once config exists)
|
|
22
|
+
|
|
23
|
+
- Install deps: `uv sync`
|
|
24
|
+
- Run app/build (if added later): `uv run python -m simdoc` (placeholder)
|
|
25
|
+
- Lint (if ruff is added): `uv run ruff check src tests`
|
|
26
|
+
- Format (if ruff/black is added): `uv run ruff format src tests`
|
|
27
|
+
- Type-check (if mypy is added): `uv run mypy src`
|
|
28
|
+
|
|
29
|
+
### Tests
|
|
30
|
+
|
|
31
|
+
- Run all tests (pytest expected): `uv run pytest`
|
|
32
|
+
- Run a single file: `uv run pytest tests/test_tables.py`
|
|
33
|
+
- Run a single test by name: `uv run pytest -k test_render_table`
|
|
34
|
+
- Run a single test node: `uv run pytest tests/test_tables.py::test_render_table`
|
|
35
|
+
|
|
36
|
+
Note: Update this section when real commands/configs land.
|
|
37
|
+
|
|
38
|
+
## Code style and architecture guidelines
|
|
39
|
+
|
|
40
|
+
### Language and tooling
|
|
41
|
+
|
|
42
|
+
- Python only.
|
|
43
|
+
- Favor a minimal dependency footprint.
|
|
44
|
+
- Use `uv` for packaging and execution once added.
|
|
45
|
+
|
|
46
|
+
### Design constraints from the spec
|
|
47
|
+
|
|
48
|
+
- Document is append-only; no insert/reorder in v0.
|
|
49
|
+
- Public API is explicit convenience methods on the document object.
|
|
50
|
+
- Output is deterministic; spacing and newlines are strictly controlled.
|
|
51
|
+
- No implicit line wrapping; renderer is canonical authority.
|
|
52
|
+
- Markdown is the only export in v0.
|
|
53
|
+
|
|
54
|
+
### Module boundaries (from spec)
|
|
55
|
+
|
|
56
|
+
- Public API is exported from `simdoc/__init__.py` only.
|
|
57
|
+
- Internal modules are private and subject to change.
|
|
58
|
+
- Rendering logic lives in a dedicated renderer module.
|
|
59
|
+
- Blocks are private record types; no public block classes in v0.
|
|
60
|
+
|
|
61
|
+
### Imports
|
|
62
|
+
|
|
63
|
+
- Order: standard library, third-party, local.
|
|
64
|
+
- Keep imports explicit; avoid wildcard imports.
|
|
65
|
+
- Avoid dynamic imports and reflection.
|
|
66
|
+
|
|
67
|
+
### Formatting and layout
|
|
68
|
+
|
|
69
|
+
- Keep code readable and straightforward.
|
|
70
|
+
- Prefer explicit loops and clear control flow over clever expressions.
|
|
71
|
+
- Avoid dense comprehensions that reduce clarity.
|
|
72
|
+
- Keep functions small and single-purpose.
|
|
73
|
+
- Use blank lines to separate logical sections.
|
|
74
|
+
|
|
75
|
+
### Types
|
|
76
|
+
|
|
77
|
+
- Type hints are preferred for public APIs.
|
|
78
|
+
- Use simple, explicit types; avoid complex generics unless necessary.
|
|
79
|
+
- Prefer `Path` or `PathLike` for file paths when I/O is involved.
|
|
80
|
+
|
|
81
|
+
### Naming
|
|
82
|
+
|
|
83
|
+
- Public API names should match the spec: `Doc`, `h1..h6`, `h`, `p`, `ul`, `ol`,
|
|
84
|
+
`code`, `table`, `hr`, `to_markdown`, `save`.
|
|
85
|
+
- Private modules and types should be prefixed or placed in private modules.
|
|
86
|
+
- Use descriptive names; avoid abbreviations unless standard.
|
|
87
|
+
|
|
88
|
+
### Error handling
|
|
89
|
+
|
|
90
|
+
- Use a single custom base exception for document/render issues (per spec).
|
|
91
|
+
- I/O should raise standard Python exceptions.
|
|
92
|
+
- Prefer early validation where it is cheap and deterministic.
|
|
93
|
+
|
|
94
|
+
### Determinism requirements (critical)
|
|
95
|
+
|
|
96
|
+
- Exactly one blank line between top-level blocks.
|
|
97
|
+
- Exactly one trailing newline at document end.
|
|
98
|
+
- Normalize newlines to `\n` internally.
|
|
99
|
+
- No automatic line wrapping.
|
|
100
|
+
- Markdown rendering is the canonical, deterministic output.
|
|
101
|
+
|
|
102
|
+
### Markdown-specific behavior
|
|
103
|
+
|
|
104
|
+
- Headings are ATX only, levels 1..6.
|
|
105
|
+
- Unordered lists use `-`, ordered lists use `1.` for all items.
|
|
106
|
+
- Code fences must be safe; auto-escalate fence length deterministically.
|
|
107
|
+
- Table rendering must be valid GFM; escape pipes and convert newlines to `<br>`.
|
|
108
|
+
- For list-of-dicts tables: headers order is deterministic (sorted if not provided).
|
|
109
|
+
|
|
110
|
+
### Testing conventions (expected)
|
|
111
|
+
|
|
112
|
+
- Golden-file tests should compare output to `tests/fixtures/golden/*.md`.
|
|
113
|
+
- Unit tests should cover fence escalation, table escaping, list rendering, spacing.
|
|
114
|
+
- Keep tests deterministic and focused on renderer invariants.
|
|
115
|
+
|
|
116
|
+
## Contribution notes for agents
|
|
117
|
+
|
|
118
|
+
- Do not add new behavior beyond the locked spec without explicit instruction.
|
|
119
|
+
- Avoid introducing frameworks or heavy abstractions.
|
|
120
|
+
- Keep changes minimal, readable, and aligned with the spec.
|
|
121
|
+
- Update this file if tooling or structure changes.
|
simdoc-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 placerte
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
simdoc-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: simdoc
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: A simple Python package for deterministic Markdown documents
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Requires-Python: >=3.13
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# simple-document
|
|
10
|
+
|
|
11
|
+
Deterministic, append-only Markdown documents with a small, explicit Python API.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
uv add simple-document
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or with pip:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install simple-document
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from simdoc import Doc
|
|
29
|
+
|
|
30
|
+
doc = Doc()
|
|
31
|
+
doc.h1("Simple Document")
|
|
32
|
+
doc.p("First line\nSecond line")
|
|
33
|
+
doc.ul(["alpha", ["beta", "gamma"], "delta"])
|
|
34
|
+
doc.code("print('hi')", lang="py")
|
|
35
|
+
doc.table([
|
|
36
|
+
{"b": "x|y", "a": "1\n2"},
|
|
37
|
+
{"a": None, "b": "ok"},
|
|
38
|
+
])
|
|
39
|
+
doc.hr()
|
|
40
|
+
|
|
41
|
+
markdown = doc.to_markdown()
|
|
42
|
+
doc.save("example.md")
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Example script
|
|
46
|
+
|
|
47
|
+
Run the full example that exercises every block type:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
uv run python examples/example_all_blocks.py
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The script writes `examples/example_output.md`.
|
simdoc-0.1.1/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# simple-document
|
|
2
|
+
|
|
3
|
+
Deterministic, append-only Markdown documents with a small, explicit Python API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv add simple-document
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or with pip:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install simple-document
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from simdoc import Doc
|
|
21
|
+
|
|
22
|
+
doc = Doc()
|
|
23
|
+
doc.h1("Simple Document")
|
|
24
|
+
doc.p("First line\nSecond line")
|
|
25
|
+
doc.ul(["alpha", ["beta", "gamma"], "delta"])
|
|
26
|
+
doc.code("print('hi')", lang="py")
|
|
27
|
+
doc.table([
|
|
28
|
+
{"b": "x|y", "a": "1\n2"},
|
|
29
|
+
{"a": None, "b": "ok"},
|
|
30
|
+
])
|
|
31
|
+
doc.hr()
|
|
32
|
+
|
|
33
|
+
markdown = doc.to_markdown()
|
|
34
|
+
doc.save("example.md")
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Example script
|
|
38
|
+
|
|
39
|
+
Run the full example that exercises every block type:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
uv run python examples/example_all_blocks.py
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The script writes `examples/example_output.md`.
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
# handoff\_260203\_1.md — simdoc v0 (working name) — Locked specs
|
|
2
|
+
|
|
3
|
+
> **Scope:** early specification for the v0 “boring core” of **simdoc**.
|
|
4
|
+
>
|
|
5
|
+
> **Format:** uniquely-identified decisions. Specs are stable unless explicitly re-opened.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Specs
|
|
10
|
+
|
|
11
|
+
### Core mental model
|
|
12
|
+
|
|
13
|
+
- **S-260203\_1-1** — The document is an **append-only** ordered list of blocks.
|
|
14
|
+
- **S-260203\_1-1.1** — All core API methods **append** blocks (no insert/move/reorder in v0).
|
|
15
|
+
- **S-260203\_1-1.2** — No AST/editor model in v0; editing features are out of scope.
|
|
16
|
+
|
|
17
|
+
### Public API shape
|
|
18
|
+
|
|
19
|
+
- **S-260203\_1-2** — Public API uses **explicit convenience methods** on the document object.
|
|
20
|
+
- **S-260203\_1-2.1** — Core methods include: `h1..h6`, `h(level, text)`, `p(text)`, `ul(items)`, `ol(items)`, `code(text, lang=None)`, `table(...)`, `hr()` , plus `to_markdown()`.
|
|
21
|
+
- **S-260203\_1-2.2** — No exposed block classes or `add(Block)` mechanism in v0.
|
|
22
|
+
- **S-260203\_1-2.3** — API favors readability/immediacy; extension points are deferred.
|
|
23
|
+
|
|
24
|
+
### Deterministic formatting rules
|
|
25
|
+
|
|
26
|
+
- **S-260203\_1-3** — simdoc output is fully **deterministic** by default.
|
|
27
|
+
- **S-260203\_1-3.1** — Exactly **one blank line** between all top-level blocks.
|
|
28
|
+
- **S-260203\_1-3.2** — Documents end with **exactly one** trailing newline (`\n`).
|
|
29
|
+
- **S-260203\_1-3.3** — **No automatic line wrapping**.
|
|
30
|
+
- **S-260203\_1-3.4** — Newlines are normalized to `\n` internally.
|
|
31
|
+
|
|
32
|
+
### Internal representation
|
|
33
|
+
|
|
34
|
+
- **S-260203\_1-4** — Internally, a document is an ordered list of **private block records**, not raw Markdown strings.
|
|
35
|
+
- **S-260203\_1-4.1** — Blocks are only created via builder API (no Markdown parsing).
|
|
36
|
+
- **S-260203\_1-4.2** — `to_markdown()` is the canonical renderer in v0; formatting/escaping/spacing is enforced centrally.
|
|
37
|
+
- **S-260203\_1-4.3** — Future exports may either render from block records or use Markdown as an intermediate.
|
|
38
|
+
|
|
39
|
+
### Code blocks
|
|
40
|
+
|
|
41
|
+
- **S-260203\_1-5** — `doc.code(...)` must always render **valid Markdown**, regardless of code content.
|
|
42
|
+
- **S-260203\_1-5.1** — Auto-select a **safe fence** for `to_markdown()`.
|
|
43
|
+
- **S-260203\_1-5.2** — Escalate fence length as needed (deterministic).
|
|
44
|
+
- **S-260203\_1-5.3** — Optional language tag, passed through verbatim (no validation in v0).
|
|
45
|
+
- **S-260203\_1-5.4** — Normalize newlines to `\n`, otherwise preserve code content exactly.
|
|
46
|
+
|
|
47
|
+
### Tables
|
|
48
|
+
|
|
49
|
+
- **S-260203\_1-6.1** — Tables render as **GFM pipe tables**.
|
|
50
|
+
- **S-260203\_1-6.2** — Alignment supported but optional.
|
|
51
|
+
- **S-260203\_1-6.2.1** — Default alignment is **left** for all columns.
|
|
52
|
+
- **S-260203\_1-6.3** — Table rendering must always emit **valid GFM**.
|
|
53
|
+
- **S-260203\_1-6.3.1** — Escape pipes in cells: `| → \|`.
|
|
54
|
+
- **S-260203\_1-6.3.2** — Newlines inside cells become literal `<br>`.
|
|
55
|
+
- **S-260203\_1-6.3.3** — No other mutation (no trimming/wrapping/sanitizing beyond validity).
|
|
56
|
+
- **S-260203\_1-6.3.4** — Non-string cell values stringified via `str(x)`; `None` becomes empty cell.
|
|
57
|
+
- **S-260203\_1-6.4** — `doc.table()` accepts both list-of-lists and list-of-dicts.
|
|
58
|
+
- **S-260203\_1-6.4.1** — For list-of-dicts: if `headers` provided, it defines order; else column keys are **sorted deterministically**.
|
|
59
|
+
|
|
60
|
+
### Lists
|
|
61
|
+
|
|
62
|
+
- **S-260203\_1-7** — Deterministic Markdown lists supported.
|
|
63
|
+
- **S-260203\_1-7.1** — Unordered list marker always `-`.
|
|
64
|
+
- **S-260203\_1-7.2** — Ordered lists always use `1.` for each item.
|
|
65
|
+
- **S-260203\_1-7.3** — Nested lists supported via nested Python lists.
|
|
66
|
+
- **S-260203\_1-7.4** — No blank lines between list items.
|
|
67
|
+
|
|
68
|
+
### Headings
|
|
69
|
+
|
|
70
|
+
- **S-260203\_1-8.1** — Always ATX headings (`#`, `##`, …).
|
|
71
|
+
- **S-260203\_1-8.2** — Support heading levels h1..h6 only.
|
|
72
|
+
- **S-260203\_1-8.3** — Heading text is single-line; internal newlines normalized to spaces.
|
|
73
|
+
- **S-260203\_1-8.4** — Provide both `h1..h6()` and `h(level, text)`.
|
|
74
|
+
- **S-260203\_1-8.4.1** — `h(level, text)` validates level in `[1..6]`.
|
|
75
|
+
|
|
76
|
+
### Paragraphs
|
|
77
|
+
|
|
78
|
+
- **S-260203\_1-9.1** — `p(text)` treats input as a single paragraph; no splitting on blank lines.
|
|
79
|
+
- **S-260203\_1-9.2** — No inline formatting helpers (user supplies inline Markdown).
|
|
80
|
+
- **S-260203\_1-9.3** — Preserve internal newlines (after newline normalization).
|
|
81
|
+
- **S-260203\_1-9.4** — Empty/whitespace-only paragraphs are ignored.
|
|
82
|
+
|
|
83
|
+
### Horizontal rules & callouts
|
|
84
|
+
|
|
85
|
+
- **S-260203\_1-10.1** — Horizontal rules are supported via `doc.hr()`.
|
|
86
|
+
- **S-260203\_1-10.2** — Horizontal rules render using a fixed style: `---`.
|
|
87
|
+
- **S-260203\_1-10.3** — No callout / admonition concept in v0.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
### Export & I/O
|
|
92
|
+
|
|
93
|
+
- **S-260203\_1-11.1** — Core export surface includes:
|
|
94
|
+
|
|
95
|
+
- `to_markdown() -> str` (canonical renderer)
|
|
96
|
+
- `save(path)` convenience method for writing Markdown to disk
|
|
97
|
+
|
|
98
|
+
- **S-260203\_1-11.1.1** — `save(path)` defaults:
|
|
99
|
+
|
|
100
|
+
- UTF-8 encoding
|
|
101
|
+
- `\n` newlines
|
|
102
|
+
- creates parent directories if missing
|
|
103
|
+
- overwrites existing files by default
|
|
104
|
+
|
|
105
|
+
- **S-260203\_1-11.2.1** — Atomic writes are **not** supported in v0 (deferred).
|
|
106
|
+
|
|
107
|
+
- **S-260203\_1-11.2.2** — `save(path)` returns the written **`Path`** on success.
|
|
108
|
+
|
|
109
|
+
- **S-260203\_1-11.2.3** — `save(path)` raises standard Python I/O exceptions on failure.
|
|
110
|
+
|
|
111
|
+
### Export adapters (v0 scope)
|
|
112
|
+
|
|
113
|
+
- **S-260203\_1-12.1** — v0 export scope is **Markdown only**.
|
|
114
|
+
|
|
115
|
+
- No built-in HTML or PDF adapters in v0.
|
|
116
|
+
- Future adapters remain feasible due to the internal block record representation.
|
|
117
|
+
|
|
118
|
+
- **S-260203\_1-12.2** — When adapters are introduced, they will be provided via **optional extras** on the core package (e.g. `simdoc[html]`, `simdoc[pdf]`).
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Implementation
|
|
123
|
+
|
|
124
|
+
### I-260203\_1-1 — Project structure
|
|
125
|
+
|
|
126
|
+
The project follows a **minimal, layered layout** separating public API, internal block models, and rendering logic.
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
simdoc/
|
|
130
|
+
├─ src/
|
|
131
|
+
│ └─ simdoc/
|
|
132
|
+
│ ├─ __init__.py # Public API (Document class, convenience functions)
|
|
133
|
+
│ ├─ document.py # Document builder (append-only, user-facing methods)
|
|
134
|
+
│ ├─ blocks.py # Private block record definitions (internal only)
|
|
135
|
+
│ ├─ render/
|
|
136
|
+
│ │ ├─ __init__.py
|
|
137
|
+
│ │ └─ markdown.py # Markdown renderer (to_markdown implementation)
|
|
138
|
+
│ └─ util/
|
|
139
|
+
│ ├─ __init__.py
|
|
140
|
+
│ └─ text.py # Escaping, fence selection, newline normalization
|
|
141
|
+
│
|
|
142
|
+
├─ tests/
|
|
143
|
+
│ ├─ test_markdown_golden.py
|
|
144
|
+
│ ├─ test_blocks.py
|
|
145
|
+
│ └─ fixtures/
|
|
146
|
+
│ └─ expected_md/
|
|
147
|
+
│
|
|
148
|
+
├─ pyproject.toml
|
|
149
|
+
├─ README.md
|
|
150
|
+
└─ LICENSE
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### I-260203\_1-2 — Module responsibilities
|
|
154
|
+
|
|
155
|
+
- **document.py**
|
|
156
|
+
|
|
157
|
+
- Owns the `Doc` class
|
|
158
|
+
- Implements public builder methods (`h1`, `p`, `code`, `table`, …)
|
|
159
|
+
- Performs light input normalization and validation
|
|
160
|
+
- Appends private block records to the document
|
|
161
|
+
|
|
162
|
+
- **blocks.py**
|
|
163
|
+
|
|
164
|
+
- Defines minimal, immutable block record types (e.g. dataclasses)
|
|
165
|
+
- No rendering logic
|
|
166
|
+
- Not part of the public API
|
|
167
|
+
|
|
168
|
+
- **render/markdown.py**
|
|
169
|
+
|
|
170
|
+
- Pure functions to render block records into Markdown
|
|
171
|
+
- Enforces all deterministic formatting rules
|
|
172
|
+
- Owns fence selection, table formatting, list rendering, spacing rules
|
|
173
|
+
|
|
174
|
+
- **util/text.py**
|
|
175
|
+
|
|
176
|
+
- Small helpers for:
|
|
177
|
+
- newline normalization
|
|
178
|
+
- escaping pipes
|
|
179
|
+
- safe fence computation
|
|
180
|
+
- No knowledge of blocks or document structure
|
|
181
|
+
|
|
182
|
+
### I-260203\_1-3 — Public API boundary
|
|
183
|
+
|
|
184
|
+
- Only symbols exported in `simdoc/__init__.py` are considered public.
|
|
185
|
+
- All other modules are **internal and subject to change**.
|
|
186
|
+
- Rendering helpers and block definitions are explicitly non-public.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Implementation
|
|
191
|
+
|
|
192
|
+
### Project layout
|
|
193
|
+
|
|
194
|
+
- **I-260203\_1-1** — Use a standard `src/` layout.
|
|
195
|
+
|
|
196
|
+
```text
|
|
197
|
+
simdoc/
|
|
198
|
+
├─ pyproject.toml
|
|
199
|
+
├─ README.md
|
|
200
|
+
├─ LICENSE
|
|
201
|
+
├─ src/
|
|
202
|
+
│ └─ simdoc/
|
|
203
|
+
│ ├─ __init__.py
|
|
204
|
+
│ ├─ doc.py
|
|
205
|
+
│ ├─ _blocks.py
|
|
206
|
+
│ ├─ _render/
|
|
207
|
+
│ │ ├─ __init__.py
|
|
208
|
+
│ │ └─ markdown.py
|
|
209
|
+
│ └─ _io.py
|
|
210
|
+
└─ tests/
|
|
211
|
+
├─ test_golden_markdown.py
|
|
212
|
+
├─ test_code_fences.py
|
|
213
|
+
├─ test_tables.py
|
|
214
|
+
├─ test_lists.py
|
|
215
|
+
└─ fixtures/
|
|
216
|
+
└─ golden/
|
|
217
|
+
├─ basic.md
|
|
218
|
+
├─ code_fences.md
|
|
219
|
+
├─ tables.md
|
|
220
|
+
└─ lists.md
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Module responsibilities
|
|
224
|
+
|
|
225
|
+
- **I-260203\_1-2** — `simdoc.doc` contains the public document class and builder methods.
|
|
226
|
+
- **I-260203\_1-3** — `simdoc._blocks` defines private block records (small dataclasses) for:
|
|
227
|
+
- Heading, Paragraph, ListBlock, CodeBlock, TableBlock, Hr
|
|
228
|
+
- **I-260203\_1-4** — `simdoc._render.markdown` implements `render_markdown(blocks) -> str` and all Markdown rules.
|
|
229
|
+
- **I-260203\_1-5** — `simdoc._render.markdown` centralizes escaping/normalization helpers:
|
|
230
|
+
- safe code fence selection + escalation
|
|
231
|
+
- table cell escaping + newline→`<br>`
|
|
232
|
+
- heading newline→space normalization
|
|
233
|
+
- **I-260203\_1-6** — `simdoc._io` implements `save_markdown(text: str, path: PathLike) -> Path` with v0 defaults.
|
|
234
|
+
- **I-260203\_1-7** — `simdoc.__init__` exports only the public surface. Private modules remain private.
|
|
235
|
+
|
|
236
|
+
### Testing approach
|
|
237
|
+
|
|
238
|
+
- **I-260203\_1-8** — Golden-file tests assert deterministic output by comparing to `tests/fixtures/golden/*.md`.
|
|
239
|
+
- **I-260203\_1-9** — Focused unit tests cover:
|
|
240
|
+
- code fence safety (embedded \`\`\` sequences)
|
|
241
|
+
- table escaping (pipes, newlines)
|
|
242
|
+
- nested list rendering
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Definition of Done
|
|
247
|
+
|
|
248
|
+
- **DoD-260203\_1-1** — Public API exists and is documented in README:
|
|
249
|
+
|
|
250
|
+
- `Doc`
|
|
251
|
+
- `h1..h6`, `h(level, text)`
|
|
252
|
+
- `p(text)`
|
|
253
|
+
- `ul(items)`, `ol(items)` (including nested lists)
|
|
254
|
+
- `code(text, lang=None)`
|
|
255
|
+
- `table(...)` (list-of-lists and list-of-dicts)
|
|
256
|
+
- `hr()`
|
|
257
|
+
- `to_markdown() -> str`
|
|
258
|
+
- `save(path) -> Path`
|
|
259
|
+
|
|
260
|
+
- **DoD-260203\_1-2** — Output guarantees are met for all block types:
|
|
261
|
+
|
|
262
|
+
- deterministic formatting
|
|
263
|
+
- exactly one blank line between blocks
|
|
264
|
+
- no automatic line wrapping
|
|
265
|
+
- exactly one trailing `
|
|
266
|
+
`
|
|
267
|
+
- internal newline normalization to `
|
|
268
|
+
`
|
|
269
|
+
|
|
270
|
+
- **DoD-260203\_1-3** — Code fences are always safe:
|
|
271
|
+
|
|
272
|
+
- renderer escalates fence length deterministically
|
|
273
|
+
- resulting Markdown is valid regardless of code content
|
|
274
|
+
|
|
275
|
+
- **DoD-260203\_1-4** — Tables are always valid GFM pipe tables:
|
|
276
|
+
|
|
277
|
+
- pipes escaped (`|` → `\|`)
|
|
278
|
+
- cell newlines become `<br>`
|
|
279
|
+
- deterministic header order for list-of-dicts (sorted keys when headers not provided)
|
|
280
|
+
- `None` becomes empty cell; other values use `str(x)`
|
|
281
|
+
|
|
282
|
+
- **DoD-260203\_1-5** — Lists are deterministic:
|
|
283
|
+
|
|
284
|
+
- unordered marker is always `-`
|
|
285
|
+
- ordered lists always use `1.` per item
|
|
286
|
+
- nested lists render deterministically from nested Python lists
|
|
287
|
+
|
|
288
|
+
- **DoD-260203\_1-6** — Validation and error model is implemented as locked:
|
|
289
|
+
|
|
290
|
+
- permissive-by-default inputs where deterministic rendering is possible
|
|
291
|
+
- builder methods do light normalization / cheap sanity checks
|
|
292
|
+
- renderer is final authority
|
|
293
|
+
- single custom exception base `SimDocError` for document/render issues
|
|
294
|
+
- standard Python exceptions for I/O
|
|
295
|
+
|
|
296
|
+
- **DoD-260203\_1-7** — Mutability & ergonomics:
|
|
297
|
+
|
|
298
|
+
- `Doc` is reusable/appendable after rendering
|
|
299
|
+
- builder methods return `None`
|
|
300
|
+
- `len(doc)` returns block count (optional `block_count` alias)
|
|
301
|
+
- `repr(doc)` is concise/stable (e.g. `Doc(blocks=7)`)
|
|
302
|
+
|
|
303
|
+
- **DoD-260203\_1-8** — Packaging and minimal quality gates:
|
|
304
|
+
|
|
305
|
+
- clean `src/` layout
|
|
306
|
+
- type hints on public API
|
|
307
|
+
- passes unit tests on supported Python versions
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## Tests
|
|
312
|
+
|
|
313
|
+
- **T-260203\_1-1** — Golden Markdown determinism tests
|
|
314
|
+
|
|
315
|
+
- Compare `Doc(...).to_markdown()` against `tests/fixtures/golden/*.md`.
|
|
316
|
+
- Include at least: `basic.md`, `lists.md`, `tables.md`, `code_fences.md`.
|
|
317
|
+
|
|
318
|
+
- **T-260203\_1-2** — Code fence escalation tests
|
|
319
|
+
|
|
320
|
+
- Inputs containing \`\`\` (and longer backtick runs) must render with a longer fence.
|
|
321
|
+
- Ensure determinism: same input always produces same fence length.
|
|
322
|
+
|
|
323
|
+
- **T-260203\_1-3** — Table escaping tests
|
|
324
|
+
|
|
325
|
+
- Pipes in cells are escaped.
|
|
326
|
+
- Newlines in cells become `<br>`.
|
|
327
|
+
- `None` renders as empty.
|
|
328
|
+
- List-of-dicts column ordering is deterministic.
|
|
329
|
+
|
|
330
|
+
- **T-260203\_1-4** — List rendering tests
|
|
331
|
+
|
|
332
|
+
- `ul` uses `-`.
|
|
333
|
+
- `ol` uses `1.` for all items.
|
|
334
|
+
- Nested list structures render correctly and deterministically.
|
|
335
|
+
|
|
336
|
+
- **T-260203\_1-5** — Spacing and newline invariants
|
|
337
|
+
|
|
338
|
+
- Exactly one blank line between blocks.
|
|
339
|
+
- Exactly one trailing newline.
|
|
340
|
+
- No implicit line wrapping.
|
|
341
|
+
|
|
342
|
+
- **T-260203\_1-6** — I/O smoke tests
|
|
343
|
+
|
|
344
|
+
- `save(path)` writes UTF-8 with `
|
|
345
|
+
` newlines.
|
|
346
|
+
- Parent directories are created.
|
|
347
|
+
- Overwrites existing file.
|
|
348
|
+
- Returns `Path`.
|
|
349
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# persona.md
|
|
2
|
+
|
|
3
|
+
## TL;DR (Read this first)
|
|
4
|
+
|
|
5
|
+
Technical, pragmatic user. Python only, simple code, minimal tooling.
|
|
6
|
+
Prefers clarity over elegance, explicit logic over abstractions, and fast iteration over perfect design.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Core Persona
|
|
11
|
+
|
|
12
|
+
- Analytical and systems-oriented
|
|
13
|
+
- Values understanding, control, and transparency
|
|
14
|
+
- Uses the AI as a thinking and exploration partner
|
|
15
|
+
|
|
16
|
+
## Coding Preferences
|
|
17
|
+
|
|
18
|
+
- Language: Python
|
|
19
|
+
- Package / env manager: `uv`
|
|
20
|
+
- Not a software developer → keep code simple and readable
|
|
21
|
+
- Avoid frameworks, patterns, and “clever” architectures
|
|
22
|
+
- Explicit control flow preferred (loops > dense comprehensions)
|
|
23
|
+
- Strong preference for:
|
|
24
|
+
- type hints
|
|
25
|
+
- explicit attributes
|
|
26
|
+
- no string-based magic or reflection (`getattr`, dynamic wiring)
|
|
27
|
+
|
|
28
|
+
## Workflow & Environment
|
|
29
|
+
|
|
30
|
+
- Linux-centric, keyboard-driven
|
|
31
|
+
- Minimal, low-bloat tools
|
|
32
|
+
- CLI / TUI preferred; GUI only when it clearly adds value
|
|
33
|
+
- Favors local, inspectable artifacts:
|
|
34
|
+
- Markdown
|
|
35
|
+
- plain text files
|
|
36
|
+
|
|
37
|
+
## Interaction & Output Style
|
|
38
|
+
|
|
39
|
+
- Direct, informal, pragmatic
|
|
40
|
+
- Show assumptions, tradeoffs, and intermediate reasoning
|
|
41
|
+
- “I don’t know” is acceptable if clearly stated
|
|
42
|
+
- Start simple; expand only when justified
|
|
43
|
+
- End with concise summaries or decisions when possible
|
|
44
|
+
|