extraslide 0.1.0__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.
- extraslide-0.1.0/.gitignore +45 -0
- extraslide-0.1.0/.python-version +1 -0
- extraslide-0.1.0/CHANGELOG.md +28 -0
- extraslide-0.1.0/CLAUDE.md +178 -0
- extraslide-0.1.0/LICENSE +21 -0
- extraslide-0.1.0/PKG-INFO +272 -0
- extraslide-0.1.0/README.md +238 -0
- extraslide-0.1.0/docs/copy-workflow.md +177 -0
- extraslide-0.1.0/docs/googleslides/concepts/page-elements.md +161 -0
- extraslide-0.1.0/docs/googleslides/concepts/text.md +268 -0
- extraslide-0.1.0/docs/googleslides/concepts/transforms.md +160 -0
- extraslide-0.1.0/docs/googleslides/guides/add-chart.md +193 -0
- extraslide-0.1.0/docs/googleslides/guides/add-image.md +194 -0
- extraslide-0.1.0/docs/googleslides/guides/add-shape.md +233 -0
- extraslide-0.1.0/docs/googleslides/guides/batch.md +292 -0
- extraslide-0.1.0/docs/googleslides/guides/create-slide.md +196 -0
- extraslide-0.1.0/docs/googleslides/guides/field-masks.md +223 -0
- extraslide-0.1.0/docs/googleslides/guides/merge.md +272 -0
- extraslide-0.1.0/docs/googleslides/guides/notes.md +202 -0
- extraslide-0.1.0/docs/googleslides/guides/overview.md +93 -0
- extraslide-0.1.0/docs/googleslides/guides/performance.md +228 -0
- extraslide-0.1.0/docs/googleslides/guides/presentations.md +155 -0
- extraslide-0.1.0/docs/googleslides/guides/styling.md +299 -0
- extraslide-0.1.0/docs/googleslides/guides/transform.md +250 -0
- extraslide-0.1.0/docs/googleslides/index.md +189 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/affine-transform.md +278 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/auto-text.md +33 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/autofit.md +31 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/bullet.md +28 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/color-scheme.md +22 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/color-stop.md +26 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/crop-properties.md +26 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/dimension.md +28 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/group.md +22 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/image-properties.md +40 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/image.md +29 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/index.md +103 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/layout-placeholder-id-mapping.md +26 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/layout-properties.md +22 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/layout-reference.md +37 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/line-connection.md +20 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/line-fill.md +22 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/line-properties.md +83 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/line.md +51 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/link.md +34 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/list.md +20 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/master-properties.md +18 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/nesting-level.md +22 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/notes-properties.md +18 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/opaque-color.md +46 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/optional-color.md +22 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/outline-fill.md +22 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/outline.md +49 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/page-background-fill.md +35 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/page-element-properties.md +27 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/page-element.md +249 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/page-properties.md +25 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/page.md +212 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/paragraph-marker.md +25 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/paragraph-style.md +64 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/placeholder.md +44 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/presentation.md +158 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/range.md +31 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/recolor.md +54 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/rgb-color.md +22 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/shadow.md +68 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/shape-background-fill.md +32 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/shape-properties.md +46 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/shape.md +276 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/sheets-chart-properties.md +22 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/sheets-chart.md +28 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/size.md +24 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/slide-properties.md +28 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/solid-fill.md +24 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/speaker-spotlight-properties.md +25 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/speaker-spotlight.md +22 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/stretched-picture-fill.md +24 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/substring-match-criteria.md +22 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/table-border-cell.md +25 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/table-border-fill.md +22 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/table-border-properties.md +39 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/table-border-row.md +22 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/table-cell-background-fill.md +32 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/table-cell-location.md +20 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/table-cell-properties.md +34 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/table-cell.md +32 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/table-column-properties.md +22 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/table-range.md +26 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/table-row-properties.md +22 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/table-row.md +28 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/table.md +34 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/text-content.md +284 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/text-element.md +32 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/text-run.md +24 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/text-style.md +56 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/theme-color-pair.md +46 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/thumbnail.md +22 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/video-properties.md +30 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/video.md +36 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/weighted-font-family.md +20 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/word-art.md +18 -0
- extraslide-0.1.0/docs/googleslides/reference/objects/write-control.md +18 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/create-image.md +42 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/create-line.md +61 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/create-paragraph-bullets.md +65 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/create-shape.md +202 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/create-sheets-chart.md +53 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/create-slide.md +45 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/create-table.md +44 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/create-video.md +52 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/delete-object.md +179 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/delete-paragraph-bullets.md +43 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/delete-table-column.md +40 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/delete-table-row.md +40 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/delete-text.md +43 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/duplicate-object.md +36 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/group-objects.md +36 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/index.md +81 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/insert-table-columns.md +44 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/insert-table-rows.md +44 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/insert-text.md +231 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/merge-table-cells.md +40 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/refresh-sheets-chart.md +34 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/replace-all-shapes-with-image.md +61 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/replace-all-shapes-with-sheets-chart.md +53 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/replace-all-text.md +42 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/replace-image.md +46 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/reroute-line.md +34 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/ungroup-objects.md +34 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/unmerge-table-cells.md +40 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/update-image-properties.md +42 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/update-line-category.md +45 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/update-line-properties.md +42 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/update-page-element-alt-text.md +38 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/update-page-element-transform.md +50 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/update-page-elements-z-order.md +46 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/update-page-properties.md +42 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/update-paragraph-style.md +48 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/update-shape-properties.md +42 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/update-slide-properties.md +42 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/update-slides-position.md +36 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/update-table-border-properties.md +61 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/update-table-cell-properties.md +45 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/update-table-column-properties.md +44 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/update-table-row-properties.md +44 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/update-text-style.md +275 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/update-transform.md +254 -0
- extraslide-0.1.0/docs/googleslides/reference/requests/update-video-properties.md +42 -0
- extraslide-0.1.0/docs/googleslides/reference/rest-api.md +228 -0
- extraslide-0.1.0/docs/markup-syntax-design.md +2539 -0
- extraslide-0.1.0/docs/sml-reconciliation-spec.md +2237 -0
- extraslide-0.1.0/pyproject.toml +108 -0
- extraslide-0.1.0/scripts/analyze_discovery.py +677 -0
- extraslide-0.1.0/src/extraslide/__init__.py +29 -0
- extraslide-0.1.0/src/extraslide/__main__.py +202 -0
- extraslide-0.1.0/src/extraslide/bounds.py +243 -0
- extraslide-0.1.0/src/extraslide/classes.py +858 -0
- extraslide-0.1.0/src/extraslide/client.py +320 -0
- extraslide-0.1.0/src/extraslide/content_diff.py +520 -0
- extraslide-0.1.0/src/extraslide/content_generator.py +333 -0
- extraslide-0.1.0/src/extraslide/content_parser.py +194 -0
- extraslide-0.1.0/src/extraslide/content_requests.py +1373 -0
- extraslide-0.1.0/src/extraslide/id_manager.py +161 -0
- extraslide-0.1.0/src/extraslide/py.typed +0 -0
- extraslide-0.1.0/src/extraslide/render_tree.py +269 -0
- extraslide-0.1.0/src/extraslide/slide_processor.py +183 -0
- extraslide-0.1.0/src/extraslide/style_extractor.py +439 -0
- extraslide-0.1.0/src/extraslide/transport.py +237 -0
- extraslide-0.1.0/src/extraslide/units.py +88 -0
- extraslide-0.1.0/tests/__init__.py +0 -0
- extraslide-0.1.0/tests/golden/simple_presentation/presentation.json +38505 -0
- extraslide-0.1.0/tests/test_classes.py +519 -0
- extraslide-0.1.0/tests/test_content_diff.py +161 -0
- extraslide-0.1.0/tests/test_slide_processor.py +330 -0
- extraslide-0.1.0/tests/test_transport.py +118 -0
- extraslide-0.1.0/tests/test_units.py +123 -0
- extraslide-0.1.0/uv.lock +712 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Output directory for manual testing
|
|
2
|
+
output/
|
|
3
|
+
|
|
4
|
+
# Python
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.py[cod]
|
|
7
|
+
*$py.class
|
|
8
|
+
*.so
|
|
9
|
+
.Python
|
|
10
|
+
build/
|
|
11
|
+
develop-eggs/
|
|
12
|
+
dist/
|
|
13
|
+
downloads/
|
|
14
|
+
eggs/
|
|
15
|
+
.eggs/
|
|
16
|
+
lib/
|
|
17
|
+
lib64/
|
|
18
|
+
parts/
|
|
19
|
+
sdist/
|
|
20
|
+
var/
|
|
21
|
+
wheels/
|
|
22
|
+
*.egg-info/
|
|
23
|
+
.installed.cfg
|
|
24
|
+
*.egg
|
|
25
|
+
|
|
26
|
+
# Virtual environments
|
|
27
|
+
.venv/
|
|
28
|
+
venv/
|
|
29
|
+
ENV/
|
|
30
|
+
|
|
31
|
+
# IDE
|
|
32
|
+
.idea/
|
|
33
|
+
.vscode/
|
|
34
|
+
*.swp
|
|
35
|
+
*.swo
|
|
36
|
+
|
|
37
|
+
# Testing
|
|
38
|
+
.coverage
|
|
39
|
+
htmlcov/
|
|
40
|
+
.pytest_cache/
|
|
41
|
+
.mypy_cache/
|
|
42
|
+
|
|
43
|
+
# OS
|
|
44
|
+
.DS_Store
|
|
45
|
+
Thumbs.db
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the extraslide library will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.1.0] - 2026-02-11
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Initial release (alpha)
|
|
10
|
+
- **Pull**: Download Google Slides to a local folder with SML (Slide Markup Language) format
|
|
11
|
+
- Per-slide `content.sml` files in minimal XML
|
|
12
|
+
- `styles.json` - Style definitions (position, fill, stroke, text) per element
|
|
13
|
+
- `id_mapping.json` - Clean ID to Google object ID mapping
|
|
14
|
+
- `presentation.json` - Metadata (title, dimensions)
|
|
15
|
+
- `.pristine/` snapshot for diff comparison
|
|
16
|
+
- **Diff**: Compare edited SML against pristine state and generate `batchUpdate` JSON
|
|
17
|
+
- **Push**: Apply changes to Google Slides via the API
|
|
18
|
+
- **Copy-based workflow**: Duplicate elements by repeating XML with same ID but only x,y coordinates (omit w,h to signal copy)
|
|
19
|
+
- Cross-slide and same-slide copy detection
|
|
20
|
+
- Translation-based positioning
|
|
21
|
+
- Full shape support including images with native dimensions
|
|
22
|
+
- Unique suffix generation to prevent ID collisions
|
|
23
|
+
- **Supported operations**:
|
|
24
|
+
- Text replacement and modification
|
|
25
|
+
- Element copying with position translation
|
|
26
|
+
- Content alignment extraction and application
|
|
27
|
+
- Async transport-based architecture with `GoogleSlidesTransport` and `LocalFileTransport` for testing
|
|
28
|
+
- Golden file testing infrastructure
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
## Overview
|
|
2
|
+
|
|
3
|
+
Python library that converts Google Slides to/from SML (Slide Markup Language), an XML-based format optimized for LLM editing. Implements the pull/diff/push workflow.
|
|
4
|
+
|
|
5
|
+
## Key Files
|
|
6
|
+
|
|
7
|
+
| File | Purpose |
|
|
8
|
+
|------|---------|
|
|
9
|
+
| `src/extraslide/client.py` | `SlidesClient` - main API with pull/diff/push |
|
|
10
|
+
| `src/extraslide/slide_processor.py` | Builds render trees, extracts styles |
|
|
11
|
+
| `src/extraslide/content_generator.py` | Generates minimal SML from render trees |
|
|
12
|
+
| `src/extraslide/content_parser.py` | Parses SML content files |
|
|
13
|
+
| `src/extraslide/content_diff.py` | Detects copies, calculates translations |
|
|
14
|
+
| `src/extraslide/content_requests.py` | Generates batchUpdate requests |
|
|
15
|
+
| `src/extraslide/style_extractor.py` | Extracts styles to JSON |
|
|
16
|
+
| `src/extraslide/render_tree.py` | Visual containment hierarchy |
|
|
17
|
+
| `src/extraslide/id_manager.py` | Clean ID assignment and mapping |
|
|
18
|
+
| `src/extraslide/transport.py` | `Transport` ABC, `GoogleSlidesTransport`, `LocalFileTransport` |
|
|
19
|
+
| `src/extraslide/classes.py` | Data classes for slide elements (Color, Fill, Stroke, etc.) |
|
|
20
|
+
| `src/extraslide/credentials.py` | `CredentialsManager` for OAuth token handling |
|
|
21
|
+
| `src/extraslide/units.py` | EMU/pt conversion utilities |
|
|
22
|
+
|
|
23
|
+
## Documentation
|
|
24
|
+
|
|
25
|
+
- `docs/copy-workflow.md` - Copy-based editing workflow (agent guide)
|
|
26
|
+
- `docs/markup-syntax-design.md` - SML format specification
|
|
27
|
+
- `docs/sml-reconciliation-spec.md` - How diff/push reconciles changes
|
|
28
|
+
|
|
29
|
+
## CLI Interface
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Download a presentation to local folder
|
|
33
|
+
python -m extraslide pull <presentation_url_or_id> [output_dir]
|
|
34
|
+
# Output: ./<presentation_id>/ or specified output_dir
|
|
35
|
+
|
|
36
|
+
# Options:
|
|
37
|
+
# --no-raw Don't save raw API response to .raw/ folder
|
|
38
|
+
|
|
39
|
+
# Preview changes (dry run)
|
|
40
|
+
python -m extraslide diff <folder>
|
|
41
|
+
# Output: batchUpdate JSON to stdout
|
|
42
|
+
|
|
43
|
+
# Apply changes to Google Slides
|
|
44
|
+
python -m extraslide push <folder>
|
|
45
|
+
# Output: Success message with change count
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Also works via `uvx extraslide pull/diff/push`.
|
|
49
|
+
|
|
50
|
+
## Folder Structure
|
|
51
|
+
|
|
52
|
+
After `pull`:
|
|
53
|
+
```
|
|
54
|
+
<presentation_id>/
|
|
55
|
+
presentation.json # Metadata (title, presentation ID, dimensions)
|
|
56
|
+
id_mapping.json # clean_id -> google_object_id
|
|
57
|
+
styles.json # clean_id -> styles (position, fill, stroke, text)
|
|
58
|
+
slides/
|
|
59
|
+
01/content.sml # Slide 1 content (minimal XML)
|
|
60
|
+
02/content.sml # Slide 2 content
|
|
61
|
+
...
|
|
62
|
+
.raw/
|
|
63
|
+
presentation.json # Raw API response (for debugging)
|
|
64
|
+
.pristine/
|
|
65
|
+
presentation.zip # Zip of entire folder for diff comparison
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Edit `slides/NN/content.sml` files. To copy elements, duplicate XML with same ID but only x,y (omit w,h). See `docs/copy-workflow.md`.
|
|
69
|
+
|
|
70
|
+
## Development
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
cd extraslide
|
|
74
|
+
uv sync --all-extras
|
|
75
|
+
uv run pytest tests/ -v
|
|
76
|
+
uv run ruff check . && uv run ruff format .
|
|
77
|
+
uv run mypy src/extraslide
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Testing
|
|
81
|
+
|
|
82
|
+
Tests are in `tests/` and focus on:
|
|
83
|
+
- `test_content_diff.py` - Copy detection and diff logic
|
|
84
|
+
- `test_slide_processor.py` - Render tree building and SML generation
|
|
85
|
+
- `test_transport.py` - Transport layer tests
|
|
86
|
+
- `test_classes.py` - Data class conversions
|
|
87
|
+
- `test_units.py` - Unit conversions
|
|
88
|
+
|
|
89
|
+
### Golden File Testing
|
|
90
|
+
|
|
91
|
+
Golden files enable testing without mocking or making real API calls:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
tests/golden/
|
|
95
|
+
<presentation_id>/
|
|
96
|
+
presentation.json # Raw API response
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Use `LocalFileTransport` in tests:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from extraslide import SlidesClient, LocalFileTransport
|
|
103
|
+
|
|
104
|
+
@pytest.fixture
|
|
105
|
+
def client():
|
|
106
|
+
transport = LocalFileTransport(Path("tests/golden"))
|
|
107
|
+
return SlidesClient(transport)
|
|
108
|
+
|
|
109
|
+
@pytest.mark.asyncio
|
|
110
|
+
async def test_pull(client, tmp_path):
|
|
111
|
+
files = await client.pull("simple_presentation", tmp_path)
|
|
112
|
+
assert (tmp_path / "simple_presentation" / "slides" / "01" / "content.sml").exists()
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Creating New Golden Files
|
|
116
|
+
|
|
117
|
+
1. Create a Google Slides file with the features to test
|
|
118
|
+
2. Pull it: `python -m extraslide pull <url>` (raw files saved by default)
|
|
119
|
+
3. Copy `.raw/presentation.json` to `tests/golden/<name>/presentation.json`
|
|
120
|
+
4. Verify the output looks correct
|
|
121
|
+
5. Commit the golden files
|
|
122
|
+
|
|
123
|
+
## Architecture Notes
|
|
124
|
+
|
|
125
|
+
### Transport-Based Design
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
|
129
|
+
│ SlidesClient │────▶│ Transport │────▶│ Google API / │
|
|
130
|
+
│ (orchestration) │ │ (data fetching) │ │ Local Files │
|
|
131
|
+
└─────────────────┘ └──────────────────┘ └─────────────────┘
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
- `Transport` is an abstract base class with `get_presentation()`, `batch_update()`, `close()`
|
|
135
|
+
- `GoogleSlidesTransport` - Production: makes real API calls via `httpx`
|
|
136
|
+
- `LocalFileTransport` - Testing: reads from local golden files
|
|
137
|
+
- Access token is a transport concern, not a client concern
|
|
138
|
+
|
|
139
|
+
### Pull Flow
|
|
140
|
+
|
|
141
|
+
1. **Fetch** - `transport.get_presentation()` gets full presentation JSON
|
|
142
|
+
2. **Process** - `slide_processor.process_presentation()` builds render trees
|
|
143
|
+
3. **Extract styles** - Store styles (fill, stroke, text) in `styles.json`
|
|
144
|
+
4. **Generate SML** - `content_generator` creates minimal XML per slide
|
|
145
|
+
5. **Write** - Save files to `slides/NN/content.sml`
|
|
146
|
+
6. **Save raw** - Optionally save `.raw/presentation.json`
|
|
147
|
+
7. **Pristine copy** - Create `.pristine/presentation.zip` (zip of entire folder)
|
|
148
|
+
|
|
149
|
+
### Diff/Push Flow
|
|
150
|
+
|
|
151
|
+
1. **Read pristine** - Extract files from `.pristine/presentation.zip`
|
|
152
|
+
2. **Read current** - Read `slides/NN/content.sml` files
|
|
153
|
+
3. **Parse both** - `content_parser` converts SML to internal structures
|
|
154
|
+
4. **Diff** - `content_diff.diff_presentation()` detects changes and copies
|
|
155
|
+
5. **Generate requests** - `content_requests.generate_batch_requests()` creates API requests
|
|
156
|
+
6. **Push** (if not dry-run) - Send to `transport.batch_update()`
|
|
157
|
+
|
|
158
|
+
### Copy Detection
|
|
159
|
+
|
|
160
|
+
The copy-based workflow detects copies by:
|
|
161
|
+
- Same element ID appearing multiple times
|
|
162
|
+
- Copy has x,y but **omits w,h** (signals "copy from original")
|
|
163
|
+
- System duplicates the element and moves to new position
|
|
164
|
+
|
|
165
|
+
### Key Design Decisions
|
|
166
|
+
|
|
167
|
+
- **Async-first**: All transport and client methods are async
|
|
168
|
+
- **Single API call for pull**: Slides API returns everything in one call
|
|
169
|
+
- **save_raw=True default**: Always saves raw responses for debugging/testing
|
|
170
|
+
- **No mocking in tests**: Use `LocalFileTransport` with golden files instead
|
|
171
|
+
- **Per-slide files**: Each slide is a separate content.sml for easier editing
|
|
172
|
+
- **Styles in JSON**: Styles stored separately, auto-applied on copy
|
|
173
|
+
|
|
174
|
+
### Dependencies
|
|
175
|
+
|
|
176
|
+
- `httpx` - Async HTTP client for API calls
|
|
177
|
+
- `certifi` - SSL certificates
|
|
178
|
+
- `keyring` - OS keyring for token caching (via credentials.py)
|
extraslide-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Sripathi Krishnan
|
|
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.
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: extraslide
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python library that simplifies editing Google Slides through an HTML-like abstraction
|
|
5
|
+
Project-URL: Homepage, https://github.com/think41/extrasuite
|
|
6
|
+
Project-URL: Documentation, https://extrasuite.think41.com
|
|
7
|
+
Project-URL: Repository, https://github.com/think41/extrasuite
|
|
8
|
+
Project-URL: Issues, https://github.com/think41/extrasuite/issues
|
|
9
|
+
Author-email: Sripathi Krishnan <Sripathi.Krishnan@gmail.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: extrasuite>=0.4.0
|
|
24
|
+
Requires-Dist: httpx>=0.27.0
|
|
25
|
+
Requires-Dist: pyfpgrowth>=1.0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: mypy>=1.0.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pre-commit>=3.0.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# extraslide
|
|
36
|
+
|
|
37
|
+
A Python library that converts Google Slides to/from SML (Slide Markup Language) - an XML-based format optimized for AI agent editing.
|
|
38
|
+
|
|
39
|
+
## Overview
|
|
40
|
+
|
|
41
|
+
extraslide enables AI agents (Claude Code, Codex, etc.) to read and edit Google Slides through a simple workflow:
|
|
42
|
+
|
|
43
|
+
1. **Pull** - Download a presentation as editable SML files
|
|
44
|
+
2. **Edit** - Agent modifies the SML based on user instructions
|
|
45
|
+
3. **Diff** - Preview changes before applying (dry run)
|
|
46
|
+
4. **Push** - Apply changes to Google Slides
|
|
47
|
+
|
|
48
|
+
The library handles all the complexity of the Google Slides API - agents just edit XML.
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install extraslide
|
|
54
|
+
# or
|
|
55
|
+
uvx extraslide --help
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Authentication
|
|
59
|
+
|
|
60
|
+
extraslide uses [extrasuite](https://github.com/think41/extrasuite) for authentication. Each user gets a dedicated service account:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# One-time login (opens browser)
|
|
64
|
+
uvx extrasuite login
|
|
65
|
+
|
|
66
|
+
# Share your Google Slides file with the service account email shown after login
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## The Pull-Edit-Diff-Push Workflow
|
|
70
|
+
|
|
71
|
+
### Step 1: Pull
|
|
72
|
+
|
|
73
|
+
Download a presentation to a local folder:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
uvx extraslide pull "https://docs.google.com/presentation/d/1abc.../edit"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
This creates a folder structure:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
1abc.../
|
|
83
|
+
presentation.json # Metadata (title, ID, dimensions)
|
|
84
|
+
id_mapping.json # clean_id -> google_object_id mapping
|
|
85
|
+
styles.json # Styles for each element (position, fill, stroke, text)
|
|
86
|
+
slides/
|
|
87
|
+
01/content.sml # Slide 1 content
|
|
88
|
+
02/content.sml # Slide 2 content
|
|
89
|
+
...
|
|
90
|
+
.pristine/
|
|
91
|
+
presentation.zip # Original state for diff comparison
|
|
92
|
+
.raw/
|
|
93
|
+
presentation.json # Raw API response (for debugging)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Step 2: Edit
|
|
97
|
+
|
|
98
|
+
The agent edits `slides/NN/content.sml` files based on user instructions. SML uses a minimal XML syntax:
|
|
99
|
+
|
|
100
|
+
```xml
|
|
101
|
+
<Slide>
|
|
102
|
+
<TextBox id="e1" x="100" y="50" w="500" h="80">
|
|
103
|
+
<P><T>Quarterly Report</T></P>
|
|
104
|
+
</TextBox>
|
|
105
|
+
<Rect id="e2" x="50" y="200" w="300" h="150"/>
|
|
106
|
+
</Slide>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Common edits:
|
|
110
|
+
- Change text content inside `<T>` tags
|
|
111
|
+
- Modify positions (`x`, `y`) or sizes (`w`, `h`)
|
|
112
|
+
- Add/delete elements
|
|
113
|
+
- Copy elements (duplicate with same ID, new position, omit w/h)
|
|
114
|
+
|
|
115
|
+
See [copy-workflow.md](./docs/copy-workflow.md) for the copy-based editing guide.
|
|
116
|
+
|
|
117
|
+
### Step 3: Diff (Preview)
|
|
118
|
+
|
|
119
|
+
See what changes will be applied without modifying the original:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
uvx extraslide diff 1abc.../
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
This compares the current `slides/` content against the `.pristine/` copy and outputs the Google Slides API requests that would be generated. No API calls are made.
|
|
126
|
+
|
|
127
|
+
### Step 4: Push
|
|
128
|
+
|
|
129
|
+
Apply the changes to Google Slides:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
uvx extraslide push 1abc.../
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The library sends a `batchUpdate` request to the Google Slides API. All edits appear in Google Drive version history with proper attribution.
|
|
136
|
+
|
|
137
|
+
## SML Format
|
|
138
|
+
|
|
139
|
+
Each slide is stored as a separate `content.sml` file with minimal XML:
|
|
140
|
+
|
|
141
|
+
```xml
|
|
142
|
+
<Slide>
|
|
143
|
+
<TextBox id="e1" x="100" y="50" w="500" h="80">
|
|
144
|
+
<P><T>Title text</T></P>
|
|
145
|
+
</TextBox>
|
|
146
|
+
<Rect id="e2" x="50" y="200" w="300" h="150"/>
|
|
147
|
+
<Group id="g1" x="400" y="100">
|
|
148
|
+
<Ellipse id="e3" x="0" y="0" w="50" h="50"/>
|
|
149
|
+
<Ellipse id="e4" x="60" y="0" w="50" h="50"/>
|
|
150
|
+
</Group>
|
|
151
|
+
</Slide>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Elements
|
|
155
|
+
|
|
156
|
+
| Element | Description |
|
|
157
|
+
|---------|-------------|
|
|
158
|
+
| `<TextBox>` | Text container with paragraphs |
|
|
159
|
+
| `<Rect>`, `<Ellipse>`, `<RoundRect>` | Basic shapes |
|
|
160
|
+
| `<Line>` | Lines and connectors |
|
|
161
|
+
| `<Image>` | Images |
|
|
162
|
+
| `<Table>` | Tables with `<Row>` and `<Cell>` |
|
|
163
|
+
| `<Group>` | Grouped elements (children use relative positions) |
|
|
164
|
+
| `<Video>` | Embedded videos |
|
|
165
|
+
| `<SheetsChart>` | Linked Google Sheets charts |
|
|
166
|
+
|
|
167
|
+
### Text Content
|
|
168
|
+
|
|
169
|
+
Text uses a paragraph (`<P>`) and text run (`<T>`) structure:
|
|
170
|
+
|
|
171
|
+
```xml
|
|
172
|
+
<TextBox id="e1" x="50" y="100" w="400" h="200">
|
|
173
|
+
<P><T>Regular text </T><T>more text</T></P>
|
|
174
|
+
<P><T>Second paragraph</T></P>
|
|
175
|
+
</TextBox>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Positions and Sizes
|
|
179
|
+
|
|
180
|
+
All positions (`x`, `y`) and sizes (`w`, `h`) are in points:
|
|
181
|
+
|
|
182
|
+
```xml
|
|
183
|
+
<Rect id="e1" x="100" y="200" w="300" h="150"/>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
For elements inside groups, positions are relative to the group's origin.
|
|
187
|
+
|
|
188
|
+
### Copying Elements
|
|
189
|
+
|
|
190
|
+
To copy an element, duplicate its XML with:
|
|
191
|
+
- Same `id` as the source element
|
|
192
|
+
- New `x`, `y` position
|
|
193
|
+
- **Omit `w` and `h`** (this signals a copy)
|
|
194
|
+
|
|
195
|
+
```xml
|
|
196
|
+
<!-- Original -->
|
|
197
|
+
<Rect id="e1" x="100" y="100" w="200" h="100"/>
|
|
198
|
+
|
|
199
|
+
<!-- Copy (omit w/h to signal copy) -->
|
|
200
|
+
<Rect id="e1" x="400" y="100"/>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
See [copy-workflow.md](./docs/copy-workflow.md) for details.
|
|
204
|
+
|
|
205
|
+
## CLI Reference
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
# Pull a presentation
|
|
209
|
+
uvx extraslide pull <url_or_id> [output_dir]
|
|
210
|
+
uvx extraslide pull "https://docs.google.com/presentation/d/1abc.../edit"
|
|
211
|
+
uvx extraslide pull "https://docs.google.com/presentation/d/1abc.../edit" ./my-folder
|
|
212
|
+
|
|
213
|
+
# Preview changes (dry run)
|
|
214
|
+
uvx extraslide diff <folder>
|
|
215
|
+
uvx extraslide diff ./1abc.../
|
|
216
|
+
|
|
217
|
+
# Apply changes
|
|
218
|
+
uvx extraslide push <folder>
|
|
219
|
+
uvx extraslide push ./1abc.../
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Also works as a Python module:
|
|
223
|
+
```bash
|
|
224
|
+
python -m extraslide pull ...
|
|
225
|
+
python -m extraslide diff ...
|
|
226
|
+
python -m extraslide push ...
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Important Notes
|
|
230
|
+
|
|
231
|
+
### Re-pull After Push
|
|
232
|
+
|
|
233
|
+
After pushing changes, the local SML becomes stale. Always re-pull before making more edits:
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
uvx extraslide push ./1abc.../
|
|
237
|
+
uvx extraslide pull "https://docs.google.com/presentation/d/1abc.../edit" # Re-pull!
|
|
238
|
+
# Now safe to edit again
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### What Can Be Edited
|
|
242
|
+
|
|
243
|
+
- Text content and styling
|
|
244
|
+
- Shape properties (fill, stroke, position, size)
|
|
245
|
+
- Add/delete slides
|
|
246
|
+
- Add/delete elements
|
|
247
|
+
- Copy existing elements
|
|
248
|
+
- Tables (content and basic styling)
|
|
249
|
+
- Images (can reference existing, limited creation support)
|
|
250
|
+
- Charts (read-only, linked to source spreadsheet)
|
|
251
|
+
|
|
252
|
+
### Styles
|
|
253
|
+
|
|
254
|
+
Element styles (fill, stroke, text formatting) are stored in `styles.json` and automatically applied when copying elements. You don't need to specify styles in the SML - just copy an element and modify the text/position.
|
|
255
|
+
|
|
256
|
+
## Documentation
|
|
257
|
+
|
|
258
|
+
- [Copy-Based Editing Guide](./docs/copy-workflow.md) - How to copy and modify elements
|
|
259
|
+
- [SML Syntax Specification](./docs/markup-syntax-design.md) - Full format reference
|
|
260
|
+
|
|
261
|
+
## Requirements
|
|
262
|
+
|
|
263
|
+
- Python 3.10+
|
|
264
|
+
- Google Slides file shared with your service account
|
|
265
|
+
|
|
266
|
+
## Project Status
|
|
267
|
+
|
|
268
|
+
**Alpha** - The library is functional but still evolving. SML format and CLI may change.
|
|
269
|
+
|
|
270
|
+
## License
|
|
271
|
+
|
|
272
|
+
[MIT License](LICENSE)
|