@farazirfan/costar-server-executor 1.7.37 → 1.7.39
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.
- package/dist/agent/agent.d.ts +90 -0
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +606 -0
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/pi-embedded-runner/run.d.ts.map +1 -1
- package/dist/agent/pi-embedded-runner/run.js +2 -1
- package/dist/agent/pi-embedded-runner/run.js.map +1 -1
- package/dist/agent/pi-embedded-runner/system-prompt.d.ts.map +1 -1
- package/dist/agent/pi-embedded-runner/system-prompt.js +16 -37
- package/dist/agent/pi-embedded-runner/system-prompt.js.map +1 -1
- package/dist/agent/pi-embedded-runner/tools.d.ts +4 -1
- package/dist/agent/pi-embedded-runner/tools.d.ts.map +1 -1
- package/dist/agent/pi-embedded-runner/tools.js +3 -1
- package/dist/agent/pi-embedded-runner/tools.js.map +1 -1
- package/dist/agent/pi-embedded-runner/types.d.ts +4 -0
- package/dist/agent/pi-embedded-runner/types.d.ts.map +1 -1
- package/dist/cli/env-loader.d.ts.map +1 -1
- package/dist/cli/env-loader.js +1 -0
- package/dist/cli/env-loader.js.map +1 -1
- package/dist/cli/setup.js +2 -2
- package/dist/cli/setup.js.map +1 -1
- package/dist/cron/normalize.d.ts +31 -0
- package/dist/cron/normalize.d.ts.map +1 -0
- package/dist/cron/normalize.js +211 -0
- package/dist/cron/normalize.js.map +1 -0
- package/dist/cron/scheduler.d.ts +33 -3
- package/dist/cron/scheduler.d.ts.map +1 -1
- package/dist/cron/scheduler.js +253 -48
- package/dist/cron/scheduler.js.map +1 -1
- package/dist/heartbeat/runner.d.ts +27 -12
- package/dist/heartbeat/runner.d.ts.map +1 -1
- package/dist/heartbeat/runner.js +82 -104
- package/dist/heartbeat/runner.js.map +1 -1
- package/dist/infra/heartbeat-events-filter.d.ts +29 -0
- package/dist/infra/heartbeat-events-filter.d.ts.map +1 -0
- package/dist/infra/heartbeat-events-filter.js +80 -0
- package/dist/infra/heartbeat-events-filter.js.map +1 -0
- package/dist/infra/index.d.ts +9 -0
- package/dist/infra/index.d.ts.map +1 -0
- package/dist/infra/index.js +9 -0
- package/dist/infra/index.js.map +1 -0
- package/dist/infra/system-events.d.ts +58 -2
- package/dist/infra/system-events.d.ts.map +1 -1
- package/dist/infra/system-events.js +80 -14
- package/dist/infra/system-events.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +6 -1
- package/dist/server.js.map +1 -1
- package/dist/services/platform-keys.d.ts +19 -0
- package/dist/services/platform-keys.d.ts.map +1 -0
- package/dist/services/platform-keys.js +74 -0
- package/dist/services/platform-keys.js.map +1 -0
- package/dist/subagent/registry.d.ts +96 -0
- package/dist/subagent/registry.d.ts.map +1 -0
- package/dist/subagent/registry.js +180 -0
- package/dist/subagent/registry.js.map +1 -0
- package/dist/tools/complete-turn.d.ts +2 -2
- package/dist/tools/complete-turn.js +10 -10
- package/dist/tools/complete-turn.js.map +1 -1
- package/dist/tools/contacts.d.ts +13 -0
- package/dist/tools/contacts.d.ts.map +1 -0
- package/dist/tools/contacts.js +80 -0
- package/dist/tools/contacts.js.map +1 -0
- package/dist/tools/cron.d.ts +17 -2
- package/dist/tools/cron.d.ts.map +1 -1
- package/dist/tools/cron.js +117 -35
- package/dist/tools/cron.js.map +1 -1
- package/dist/tools/google-maps.d.ts +6 -6
- package/dist/tools/google-maps.d.ts.map +1 -1
- package/dist/tools/google-maps.js +207 -262
- package/dist/tools/google-maps.js.map +1 -1
- package/dist/tools/index.d.ts +17 -7
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +40 -9
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/phone-call.d.ts +11 -0
- package/dist/tools/phone-call.d.ts.map +1 -0
- package/dist/tools/phone-call.js +151 -0
- package/dist/tools/phone-call.js.map +1 -0
- package/dist/tools/sessions-spawn.d.ts +33 -0
- package/dist/tools/sessions-spawn.d.ts.map +1 -0
- package/dist/tools/sessions-spawn.js +164 -0
- package/dist/tools/sessions-spawn.js.map +1 -0
- package/dist/tools/spotify.d.ts +12 -0
- package/dist/tools/spotify.d.ts.map +1 -0
- package/dist/tools/spotify.js +251 -0
- package/dist/tools/spotify.js.map +1 -0
- package/dist/tools/subagents.d.ts +23 -0
- package/dist/tools/subagents.d.ts.map +1 -0
- package/dist/tools/subagents.js +209 -0
- package/dist/tools/subagents.js.map +1 -0
- package/dist/tools/whatsapp.d.ts +13 -0
- package/dist/tools/whatsapp.d.ts.map +1 -0
- package/dist/tools/whatsapp.js +215 -0
- package/dist/tools/whatsapp.js.map +1 -0
- package/dist/tools/youtube.d.ts +12 -0
- package/dist/tools/youtube.d.ts.map +1 -0
- package/dist/tools/youtube.js +218 -0
- package/dist/tools/youtube.js.map +1 -0
- package/dist/utils/asterizk-auth.d.ts +43 -0
- package/dist/utils/asterizk-auth.d.ts.map +1 -0
- package/dist/utils/asterizk-auth.js +125 -0
- package/dist/utils/asterizk-auth.js.map +1 -0
- package/dist/web-server.d.ts.map +1 -1
- package/dist/web-server.js +132 -0
- package/dist/web-server.js.map +1 -1
- package/dist/workspace/index.d.ts +3 -4
- package/dist/workspace/index.d.ts.map +1 -1
- package/dist/workspace/index.js +3 -4
- package/dist/workspace/index.js.map +1 -1
- package/dist/workspace/templates.d.ts +8 -7
- package/dist/workspace/templates.d.ts.map +1 -1
- package/dist/workspace/templates.js +18 -127
- package/dist/workspace/templates.js.map +1 -1
- package/dist/workspace/workspace.d.ts +2 -4
- package/dist/workspace/workspace.d.ts.map +1 -1
- package/dist/workspace/workspace.js +7 -16
- package/dist/workspace/workspace.js.map +1 -1
- package/package.json +1 -1
- package/public/index.html +231 -0
- package/skills/docx/SKILL.md +468 -0
- package/skills/docx/scripts/__init__.py +1 -0
- package/skills/docx/scripts/accept_changes.py +181 -0
- package/skills/docx/scripts/comment.py +347 -0
- package/skills/docx/scripts/helpers/__init__.py +0 -0
- package/skills/docx/scripts/helpers/merge_runs.py +231 -0
- package/skills/docx/scripts/helpers/simplify_redlines.py +240 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/docx/scripts/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/skills/docx/scripts/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/skills/docx/scripts/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/skills/docx/scripts/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/skills/docx/scripts/ooxml/schemas/mce/mc.xsd +75 -0
- package/skills/docx/scripts/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/docx/scripts/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/docx/scripts/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/docx/scripts/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/docx/scripts/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/docx/scripts/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/docx/scripts/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/docx/scripts/ooxml/scripts/pack.py +159 -0
- package/skills/docx/scripts/ooxml/scripts/unpack.py +29 -0
- package/skills/docx/scripts/ooxml/scripts/validate.py +106 -0
- package/skills/docx/scripts/ooxml/scripts/validation/__init__.py +15 -0
- package/skills/docx/scripts/ooxml/scripts/validation/base.py +1023 -0
- package/skills/docx/scripts/ooxml/scripts/validation/docx.py +519 -0
- package/skills/docx/scripts/ooxml/scripts/validation/pptx.py +315 -0
- package/skills/docx/scripts/ooxml/scripts/validation/redlining.py +284 -0
- package/skills/docx/scripts/pack.py +166 -0
- package/skills/docx/scripts/templates/comments.xml +3 -0
- package/skills/docx/scripts/templates/commentsExtended.xml +3 -0
- package/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
- package/skills/docx/scripts/templates/commentsIds.xml +3 -0
- package/skills/docx/scripts/templates/people.xml +3 -0
- package/skills/docx/scripts/unpack.py +134 -0
- package/skills/longform-video-generation/SKILL.md +298 -0
- package/skills/longform-video-generation/references/advanced_techniques.md +474 -0
- package/skills/longform-video-generation/references/google_api_guide.md +288 -0
- package/skills/longform-video-generation/scripts/video_generator.py +579 -0
- package/skills/pdf/FORMS.md +305 -0
- package/skills/pdf/REFERENCE.md +612 -0
- package/skills/pdf/SKILL.md +293 -0
- package/skills/pdf/scripts/check_bounding_boxes.py +70 -0
- package/skills/pdf/scripts/check_fillable_fields.py +12 -0
- package/skills/pdf/scripts/convert_pdf_to_images.py +35 -0
- package/skills/pdf/scripts/create_validation_image.py +41 -0
- package/skills/pdf/scripts/extract_form_field_info.py +152 -0
- package/skills/pdf/scripts/extract_form_structure.py +124 -0
- package/skills/pdf/scripts/fill_fillable_fields.py +116 -0
- package/skills/pdf/scripts/fill_pdf_form_with_annotations.py +136 -0
- package/skills/pptx/SKILL.md +171 -0
- package/skills/pptx/editing.md +205 -0
- package/skills/pptx/pptxgenjs.md +377 -0
- package/skills/pptx/scripts/add_slide.py +225 -0
- package/skills/pptx/scripts/clean.py +309 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/pptx/scripts/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/skills/pptx/scripts/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/skills/pptx/scripts/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/skills/pptx/scripts/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/skills/pptx/scripts/ooxml/schemas/mce/mc.xsd +75 -0
- package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/pptx/scripts/ooxml/scripts/pack.py +159 -0
- package/skills/pptx/scripts/ooxml/scripts/unpack.py +29 -0
- package/skills/pptx/scripts/ooxml/scripts/validate.py +106 -0
- package/skills/pptx/scripts/ooxml/scripts/validation/__init__.py +15 -0
- package/skills/pptx/scripts/ooxml/scripts/validation/base.py +1023 -0
- package/skills/pptx/scripts/ooxml/scripts/validation/docx.py +519 -0
- package/skills/pptx/scripts/ooxml/scripts/validation/pptx.py +315 -0
- package/skills/pptx/scripts/ooxml/scripts/validation/redlining.py +284 -0
- package/skills/pptx/scripts/pack.py +168 -0
- package/skills/pptx/scripts/thumbnail.py +318 -0
- package/skills/pptx/scripts/unpack.py +86 -0
- package/skills/xlsx/SKILL.md +291 -0
- package/skills/xlsx/recalc.py +247 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Add a new slide to an unpacked PPTX directory.
|
|
3
|
+
|
|
4
|
+
Usage: python add_slide.py <unpacked_dir> <source>
|
|
5
|
+
|
|
6
|
+
The source can be:
|
|
7
|
+
- A slide file (e.g., slide2.xml) - duplicates the slide
|
|
8
|
+
- A layout file (e.g., slideLayout2.xml) - creates from layout
|
|
9
|
+
|
|
10
|
+
Examples:
|
|
11
|
+
python add_slide.py unpacked/ slide2.xml
|
|
12
|
+
# Duplicates slide2, creates slide5.xml
|
|
13
|
+
|
|
14
|
+
python add_slide.py unpacked/ slideLayout2.xml
|
|
15
|
+
# Creates slide5.xml from slideLayout2.xml
|
|
16
|
+
|
|
17
|
+
To see available layouts: ls unpacked/ppt/slideLayouts/
|
|
18
|
+
|
|
19
|
+
Prints the <p:sldId> element to add to presentation.xml.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import re
|
|
23
|
+
import shutil
|
|
24
|
+
import sys
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_next_slide_number(slides_dir: Path) -> int:
|
|
29
|
+
"""Find the next available slide number."""
|
|
30
|
+
existing = [int(m.group(1)) for f in slides_dir.glob("slide*.xml")
|
|
31
|
+
if (m := re.match(r"slide(\d+)\.xml", f.name))]
|
|
32
|
+
return max(existing) + 1 if existing else 1
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def create_slide_from_layout(unpacked_dir: Path, layout_file: str) -> None:
|
|
36
|
+
"""Create a new slide from a layout template."""
|
|
37
|
+
slides_dir = unpacked_dir / "ppt" / "slides"
|
|
38
|
+
rels_dir = slides_dir / "_rels"
|
|
39
|
+
layouts_dir = unpacked_dir / "ppt" / "slideLayouts"
|
|
40
|
+
|
|
41
|
+
layout_path = layouts_dir / layout_file
|
|
42
|
+
if not layout_path.exists():
|
|
43
|
+
print(f"Error: {layout_path} not found", file=sys.stderr)
|
|
44
|
+
sys.exit(1)
|
|
45
|
+
|
|
46
|
+
# Auto-select destination name
|
|
47
|
+
next_num = get_next_slide_number(slides_dir)
|
|
48
|
+
dest = f"slide{next_num}.xml"
|
|
49
|
+
dest_slide = slides_dir / dest
|
|
50
|
+
dest_rels = rels_dir / f"{dest}.rels"
|
|
51
|
+
|
|
52
|
+
# 1. Create minimal slide XML that references the layout
|
|
53
|
+
slide_xml = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
54
|
+
<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
|
|
55
|
+
<p:cSld>
|
|
56
|
+
<p:spTree>
|
|
57
|
+
<p:nvGrpSpPr>
|
|
58
|
+
<p:cNvPr id="1" name=""/>
|
|
59
|
+
<p:cNvGrpSpPr/>
|
|
60
|
+
<p:nvPr/>
|
|
61
|
+
</p:nvGrpSpPr>
|
|
62
|
+
<p:grpSpPr>
|
|
63
|
+
<a:xfrm>
|
|
64
|
+
<a:off x="0" y="0"/>
|
|
65
|
+
<a:ext cx="0" cy="0"/>
|
|
66
|
+
<a:chOff x="0" y="0"/>
|
|
67
|
+
<a:chExt cx="0" cy="0"/>
|
|
68
|
+
</a:xfrm>
|
|
69
|
+
</p:grpSpPr>
|
|
70
|
+
</p:spTree>
|
|
71
|
+
</p:cSld>
|
|
72
|
+
<p:clrMapOvr>
|
|
73
|
+
<a:masterClrMapping/>
|
|
74
|
+
</p:clrMapOvr>
|
|
75
|
+
</p:sld>'''
|
|
76
|
+
dest_slide.write_text(slide_xml, encoding="utf-8")
|
|
77
|
+
|
|
78
|
+
# 2. Create relationships file pointing to the layout
|
|
79
|
+
rels_dir.mkdir(exist_ok=True)
|
|
80
|
+
rels_xml = f'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
81
|
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
82
|
+
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Target="../slideLayouts/{layout_file}"/>
|
|
83
|
+
</Relationships>'''
|
|
84
|
+
dest_rels.write_text(rels_xml, encoding="utf-8")
|
|
85
|
+
|
|
86
|
+
# 3. Add to [Content_Types].xml
|
|
87
|
+
_add_to_content_types(unpacked_dir, dest)
|
|
88
|
+
|
|
89
|
+
# 4. Add relationship to presentation.xml.rels and get rId
|
|
90
|
+
rid = _add_to_presentation_rels(unpacked_dir, dest)
|
|
91
|
+
|
|
92
|
+
# 5. Get next slide ID
|
|
93
|
+
next_slide_id = _get_next_slide_id(unpacked_dir)
|
|
94
|
+
|
|
95
|
+
# Output
|
|
96
|
+
print(f"Created {dest} from {layout_file}")
|
|
97
|
+
print(f'Add to presentation.xml <p:sldIdLst>: <p:sldId id="{next_slide_id}" r:id="{rid}"/>')
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def duplicate_slide(unpacked_dir: Path, source: str) -> None:
|
|
101
|
+
"""Duplicate a slide and update all references."""
|
|
102
|
+
slides_dir = unpacked_dir / "ppt" / "slides"
|
|
103
|
+
rels_dir = slides_dir / "_rels"
|
|
104
|
+
|
|
105
|
+
source_slide = slides_dir / source
|
|
106
|
+
|
|
107
|
+
# Validate source exists
|
|
108
|
+
if not source_slide.exists():
|
|
109
|
+
print(f"Error: {source_slide} not found", file=sys.stderr)
|
|
110
|
+
sys.exit(1)
|
|
111
|
+
|
|
112
|
+
# Auto-select destination name
|
|
113
|
+
next_num = get_next_slide_number(slides_dir)
|
|
114
|
+
dest = f"slide{next_num}.xml"
|
|
115
|
+
dest_slide = slides_dir / dest
|
|
116
|
+
|
|
117
|
+
source_rels = rels_dir / f"{source}.rels"
|
|
118
|
+
dest_rels = rels_dir / f"{dest}.rels"
|
|
119
|
+
|
|
120
|
+
# 1. Copy slide XML
|
|
121
|
+
shutil.copy2(source_slide, dest_slide)
|
|
122
|
+
|
|
123
|
+
# 2. Copy relationships file (if exists)
|
|
124
|
+
if source_rels.exists():
|
|
125
|
+
shutil.copy2(source_rels, dest_rels)
|
|
126
|
+
|
|
127
|
+
# 3. Remove notes references from new rels file
|
|
128
|
+
rels_content = dest_rels.read_text(encoding="utf-8")
|
|
129
|
+
rels_content = re.sub(
|
|
130
|
+
r'\s*<Relationship[^>]*Type="[^"]*notesSlide"[^>]*/>\s*',
|
|
131
|
+
"\n",
|
|
132
|
+
rels_content,
|
|
133
|
+
)
|
|
134
|
+
dest_rels.write_text(rels_content, encoding="utf-8")
|
|
135
|
+
|
|
136
|
+
# 4. Add to [Content_Types].xml
|
|
137
|
+
_add_to_content_types(unpacked_dir, dest)
|
|
138
|
+
|
|
139
|
+
# 5. Add relationship to presentation.xml.rels
|
|
140
|
+
rid = _add_to_presentation_rels(unpacked_dir, dest)
|
|
141
|
+
|
|
142
|
+
# 6. Get next slide ID
|
|
143
|
+
next_slide_id = _get_next_slide_id(unpacked_dir)
|
|
144
|
+
|
|
145
|
+
# Output
|
|
146
|
+
print(f"Created {dest} from {source}")
|
|
147
|
+
print(f'Add to presentation.xml <p:sldIdLst>: <p:sldId id="{next_slide_id}" r:id="{rid}"/>')
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _add_to_content_types(unpacked_dir: Path, dest: str) -> None:
|
|
151
|
+
"""Add new slide to [Content_Types].xml."""
|
|
152
|
+
content_types_path = unpacked_dir / "[Content_Types].xml"
|
|
153
|
+
content_types = content_types_path.read_text(encoding="utf-8")
|
|
154
|
+
|
|
155
|
+
new_override = f'<Override PartName="/ppt/slides/{dest}" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/>'
|
|
156
|
+
|
|
157
|
+
if f"/ppt/slides/{dest}" not in content_types:
|
|
158
|
+
content_types = content_types.replace("</Types>", f" {new_override}\n</Types>")
|
|
159
|
+
content_types_path.write_text(content_types, encoding="utf-8")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _add_to_presentation_rels(unpacked_dir: Path, dest: str) -> str:
|
|
163
|
+
"""Add relationship to presentation.xml.rels. Returns the new rId."""
|
|
164
|
+
pres_rels_path = unpacked_dir / "ppt" / "_rels" / "presentation.xml.rels"
|
|
165
|
+
pres_rels = pres_rels_path.read_text(encoding="utf-8")
|
|
166
|
+
|
|
167
|
+
rids = [int(m) for m in re.findall(r'Id="rId(\d+)"', pres_rels)]
|
|
168
|
+
next_rid = max(rids) + 1 if rids else 1
|
|
169
|
+
rid = f"rId{next_rid}"
|
|
170
|
+
|
|
171
|
+
new_rel = f'<Relationship Id="{rid}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="slides/{dest}"/>'
|
|
172
|
+
|
|
173
|
+
if f"slides/{dest}" not in pres_rels:
|
|
174
|
+
pres_rels = pres_rels.replace("</Relationships>", f" {new_rel}\n</Relationships>")
|
|
175
|
+
pres_rels_path.write_text(pres_rels, encoding="utf-8")
|
|
176
|
+
|
|
177
|
+
return rid
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _get_next_slide_id(unpacked_dir: Path) -> int:
|
|
181
|
+
"""Get the next available slide ID for presentation.xml."""
|
|
182
|
+
pres_path = unpacked_dir / "ppt" / "presentation.xml"
|
|
183
|
+
pres_content = pres_path.read_text(encoding="utf-8")
|
|
184
|
+
slide_ids = [int(m) for m in re.findall(r'<p:sldId[^>]*id="(\d+)"', pres_content)]
|
|
185
|
+
return max(slide_ids) + 1 if slide_ids else 256
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def parse_source(source: str) -> tuple[str, str | None]:
|
|
189
|
+
"""Parse source argument to determine if it's a slide or layout.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
("slide", None) if source is a slide file like "slide2.xml"
|
|
193
|
+
("layout", filename) if source is a layout like "slideLayout2.xml"
|
|
194
|
+
"""
|
|
195
|
+
if source.startswith("slideLayout") and source.endswith(".xml"):
|
|
196
|
+
return ("layout", source)
|
|
197
|
+
|
|
198
|
+
# Otherwise treat as a slide file
|
|
199
|
+
return ("slide", None)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
if __name__ == "__main__":
|
|
203
|
+
if len(sys.argv) != 3:
|
|
204
|
+
print("Usage: python add_slide.py <unpacked_dir> <source>", file=sys.stderr)
|
|
205
|
+
print("", file=sys.stderr)
|
|
206
|
+
print("Source can be:", file=sys.stderr)
|
|
207
|
+
print(" slide2.xml - duplicate an existing slide", file=sys.stderr)
|
|
208
|
+
print(" slideLayout2.xml - create from a layout template", file=sys.stderr)
|
|
209
|
+
print("", file=sys.stderr)
|
|
210
|
+
print("To see available layouts: ls <unpacked_dir>/ppt/slideLayouts/", file=sys.stderr)
|
|
211
|
+
sys.exit(1)
|
|
212
|
+
|
|
213
|
+
unpacked_dir = Path(sys.argv[1])
|
|
214
|
+
source = sys.argv[2]
|
|
215
|
+
|
|
216
|
+
if not unpacked_dir.exists():
|
|
217
|
+
print(f"Error: {unpacked_dir} not found", file=sys.stderr)
|
|
218
|
+
sys.exit(1)
|
|
219
|
+
|
|
220
|
+
source_type, layout_file = parse_source(source)
|
|
221
|
+
|
|
222
|
+
if source_type == "layout" and layout_file is not None:
|
|
223
|
+
create_slide_from_layout(unpacked_dir, layout_file)
|
|
224
|
+
else:
|
|
225
|
+
duplicate_slide(unpacked_dir, source)
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Remove unreferenced files from an unpacked PPTX directory.
|
|
3
|
+
|
|
4
|
+
Usage: python clean.py <unpacked_dir>
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
python clean.py unpacked/
|
|
8
|
+
|
|
9
|
+
This script removes:
|
|
10
|
+
- Orphaned slides (not in sldIdLst) and their relationships
|
|
11
|
+
- [trash] directory (unreferenced files)
|
|
12
|
+
- Orphaned .rels files for deleted resources
|
|
13
|
+
- Unreferenced media, embeddings, charts, diagrams, drawings, ink files
|
|
14
|
+
- Unreferenced theme files
|
|
15
|
+
- Unreferenced notes slides
|
|
16
|
+
- Content-Type overrides for deleted files
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import sys
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
import defusedxml.minidom
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
import re
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_slides_in_sldidlst(unpacked_dir: Path) -> set[str]:
|
|
29
|
+
"""Get slide filenames referenced in presentation.xml sldIdLst."""
|
|
30
|
+
pres_path = unpacked_dir / "ppt" / "presentation.xml"
|
|
31
|
+
pres_rels_path = unpacked_dir / "ppt" / "_rels" / "presentation.xml.rels"
|
|
32
|
+
|
|
33
|
+
if not pres_path.exists() or not pres_rels_path.exists():
|
|
34
|
+
return set()
|
|
35
|
+
|
|
36
|
+
# Build rId -> slide filename mapping from presentation.xml.rels
|
|
37
|
+
rels_dom = defusedxml.minidom.parse(str(pres_rels_path))
|
|
38
|
+
rid_to_slide = {}
|
|
39
|
+
for rel in rels_dom.getElementsByTagName("Relationship"):
|
|
40
|
+
rid = rel.getAttribute("Id")
|
|
41
|
+
target = rel.getAttribute("Target")
|
|
42
|
+
rel_type = rel.getAttribute("Type")
|
|
43
|
+
if "slide" in rel_type and target.startswith("slides/"):
|
|
44
|
+
rid_to_slide[rid] = target.replace("slides/", "")
|
|
45
|
+
|
|
46
|
+
# Get rIds from sldIdLst in presentation.xml
|
|
47
|
+
pres_content = pres_path.read_text(encoding="utf-8")
|
|
48
|
+
referenced_rids = set(re.findall(r'<p:sldId[^>]*r:id="([^"]+)"', pres_content))
|
|
49
|
+
|
|
50
|
+
# Return slide filenames that are in sldIdLst
|
|
51
|
+
return {rid_to_slide[rid] for rid in referenced_rids if rid in rid_to_slide}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def remove_orphaned_slides(unpacked_dir: Path) -> list[str]:
|
|
55
|
+
"""Remove slides not referenced in sldIdLst."""
|
|
56
|
+
slides_dir = unpacked_dir / "ppt" / "slides"
|
|
57
|
+
slides_rels_dir = slides_dir / "_rels"
|
|
58
|
+
pres_rels_path = unpacked_dir / "ppt" / "_rels" / "presentation.xml.rels"
|
|
59
|
+
|
|
60
|
+
if not slides_dir.exists():
|
|
61
|
+
return []
|
|
62
|
+
|
|
63
|
+
referenced_slides = get_slides_in_sldidlst(unpacked_dir)
|
|
64
|
+
removed = []
|
|
65
|
+
|
|
66
|
+
# Find and remove orphaned slide files
|
|
67
|
+
for slide_file in slides_dir.glob("slide*.xml"):
|
|
68
|
+
if slide_file.name not in referenced_slides:
|
|
69
|
+
# Remove slide file
|
|
70
|
+
rel_path = slide_file.relative_to(unpacked_dir)
|
|
71
|
+
slide_file.unlink()
|
|
72
|
+
removed.append(str(rel_path))
|
|
73
|
+
|
|
74
|
+
# Remove slide rels file
|
|
75
|
+
rels_file = slides_rels_dir / f"{slide_file.name}.rels"
|
|
76
|
+
if rels_file.exists():
|
|
77
|
+
rels_file.unlink()
|
|
78
|
+
removed.append(str(rels_file.relative_to(unpacked_dir)))
|
|
79
|
+
|
|
80
|
+
# Remove relationships from presentation.xml.rels for deleted slides
|
|
81
|
+
if removed and pres_rels_path.exists():
|
|
82
|
+
rels_dom = defusedxml.minidom.parse(str(pres_rels_path))
|
|
83
|
+
changed = False
|
|
84
|
+
|
|
85
|
+
for rel in list(rels_dom.getElementsByTagName("Relationship")):
|
|
86
|
+
target = rel.getAttribute("Target")
|
|
87
|
+
if target.startswith("slides/"):
|
|
88
|
+
slide_name = target.replace("slides/", "")
|
|
89
|
+
if slide_name not in referenced_slides:
|
|
90
|
+
if rel.parentNode:
|
|
91
|
+
rel.parentNode.removeChild(rel)
|
|
92
|
+
changed = True
|
|
93
|
+
|
|
94
|
+
if changed:
|
|
95
|
+
with open(pres_rels_path, "wb") as f:
|
|
96
|
+
f.write(rels_dom.toxml(encoding="utf-8"))
|
|
97
|
+
|
|
98
|
+
return removed
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def remove_trash_directory(unpacked_dir: Path) -> list[str]:
|
|
102
|
+
"""Remove [trash] directory if it exists."""
|
|
103
|
+
trash_dir = unpacked_dir / "[trash]"
|
|
104
|
+
removed = []
|
|
105
|
+
|
|
106
|
+
if trash_dir.exists() and trash_dir.is_dir():
|
|
107
|
+
for file_path in trash_dir.iterdir():
|
|
108
|
+
if file_path.is_file():
|
|
109
|
+
rel_path = file_path.relative_to(unpacked_dir)
|
|
110
|
+
removed.append(str(rel_path))
|
|
111
|
+
file_path.unlink()
|
|
112
|
+
trash_dir.rmdir()
|
|
113
|
+
|
|
114
|
+
return removed
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def get_slide_referenced_files(unpacked_dir: Path) -> set:
|
|
118
|
+
"""Get files referenced directly from slides."""
|
|
119
|
+
referenced = set()
|
|
120
|
+
slides_rels_dir = unpacked_dir / "ppt" / "slides" / "_rels"
|
|
121
|
+
|
|
122
|
+
if not slides_rels_dir.exists():
|
|
123
|
+
return referenced
|
|
124
|
+
|
|
125
|
+
for rels_file in slides_rels_dir.glob("*.rels"):
|
|
126
|
+
dom = defusedxml.minidom.parse(str(rels_file))
|
|
127
|
+
for rel in dom.getElementsByTagName("Relationship"):
|
|
128
|
+
target = rel.getAttribute("Target")
|
|
129
|
+
if not target:
|
|
130
|
+
continue
|
|
131
|
+
target_path = (rels_file.parent.parent / target).resolve()
|
|
132
|
+
try:
|
|
133
|
+
referenced.add(target_path.relative_to(unpacked_dir.resolve()))
|
|
134
|
+
except ValueError:
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
return referenced
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def remove_orphaned_rels_files(unpacked_dir: Path) -> list[str]:
|
|
141
|
+
"""Remove .rels files for unreferenced resources."""
|
|
142
|
+
resource_dirs = ["charts", "diagrams", "drawings"]
|
|
143
|
+
removed = []
|
|
144
|
+
slide_referenced = get_slide_referenced_files(unpacked_dir)
|
|
145
|
+
|
|
146
|
+
for dir_name in resource_dirs:
|
|
147
|
+
rels_dir = unpacked_dir / "ppt" / dir_name / "_rels"
|
|
148
|
+
if not rels_dir.exists():
|
|
149
|
+
continue
|
|
150
|
+
|
|
151
|
+
for rels_file in rels_dir.glob("*.rels"):
|
|
152
|
+
resource_file = rels_dir.parent / rels_file.name.replace(".rels", "")
|
|
153
|
+
try:
|
|
154
|
+
resource_rel_path = resource_file.resolve().relative_to(unpacked_dir.resolve())
|
|
155
|
+
except ValueError:
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
if not resource_file.exists() or resource_rel_path not in slide_referenced:
|
|
159
|
+
rels_file.unlink()
|
|
160
|
+
rel_path = rels_file.relative_to(unpacked_dir)
|
|
161
|
+
removed.append(str(rel_path))
|
|
162
|
+
|
|
163
|
+
return removed
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def get_referenced_files(unpacked_dir: Path) -> set:
|
|
167
|
+
"""Get all files referenced in .rels files."""
|
|
168
|
+
referenced = set()
|
|
169
|
+
|
|
170
|
+
for rels_file in unpacked_dir.rglob("*.rels"):
|
|
171
|
+
dom = defusedxml.minidom.parse(str(rels_file))
|
|
172
|
+
for rel in dom.getElementsByTagName("Relationship"):
|
|
173
|
+
target = rel.getAttribute("Target")
|
|
174
|
+
if not target:
|
|
175
|
+
continue
|
|
176
|
+
target_path = (rels_file.parent.parent / target).resolve()
|
|
177
|
+
try:
|
|
178
|
+
referenced.add(target_path.relative_to(unpacked_dir.resolve()))
|
|
179
|
+
except ValueError:
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
return referenced
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def remove_orphaned_files(unpacked_dir: Path, referenced: set) -> list[str]:
|
|
186
|
+
"""Remove files not in the referenced set."""
|
|
187
|
+
resource_dirs = ["media", "embeddings", "charts", "diagrams", "tags", "drawings", "ink"]
|
|
188
|
+
removed = []
|
|
189
|
+
|
|
190
|
+
for dir_name in resource_dirs:
|
|
191
|
+
dir_path = unpacked_dir / "ppt" / dir_name
|
|
192
|
+
if not dir_path.exists():
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
for file_path in dir_path.glob("*"):
|
|
196
|
+
if not file_path.is_file():
|
|
197
|
+
continue
|
|
198
|
+
rel_path = file_path.relative_to(unpacked_dir)
|
|
199
|
+
if rel_path not in referenced:
|
|
200
|
+
file_path.unlink()
|
|
201
|
+
removed.append(str(rel_path))
|
|
202
|
+
|
|
203
|
+
# Clean up unreferenced theme files
|
|
204
|
+
theme_dir = unpacked_dir / "ppt" / "theme"
|
|
205
|
+
if theme_dir.exists():
|
|
206
|
+
for file_path in theme_dir.glob("theme*.xml"):
|
|
207
|
+
rel_path = file_path.relative_to(unpacked_dir)
|
|
208
|
+
if rel_path not in referenced:
|
|
209
|
+
file_path.unlink()
|
|
210
|
+
removed.append(str(rel_path))
|
|
211
|
+
# Also remove corresponding .rels
|
|
212
|
+
theme_rels = theme_dir / "_rels" / f"{file_path.name}.rels"
|
|
213
|
+
if theme_rels.exists():
|
|
214
|
+
theme_rels.unlink()
|
|
215
|
+
removed.append(str(theme_rels.relative_to(unpacked_dir)))
|
|
216
|
+
|
|
217
|
+
# Remove orphaned notes slides
|
|
218
|
+
notes_dir = unpacked_dir / "ppt" / "notesSlides"
|
|
219
|
+
if notes_dir.exists():
|
|
220
|
+
for file_path in notes_dir.glob("*.xml"):
|
|
221
|
+
if not file_path.is_file():
|
|
222
|
+
continue
|
|
223
|
+
rel_path = file_path.relative_to(unpacked_dir)
|
|
224
|
+
if rel_path not in referenced:
|
|
225
|
+
file_path.unlink()
|
|
226
|
+
removed.append(str(rel_path))
|
|
227
|
+
|
|
228
|
+
notes_rels_dir = notes_dir / "_rels"
|
|
229
|
+
if notes_rels_dir.exists():
|
|
230
|
+
for file_path in notes_rels_dir.glob("*.rels"):
|
|
231
|
+
notes_file = notes_dir / file_path.name.replace(".rels", "")
|
|
232
|
+
if not notes_file.exists():
|
|
233
|
+
file_path.unlink()
|
|
234
|
+
removed.append(str(file_path.relative_to(unpacked_dir)))
|
|
235
|
+
|
|
236
|
+
return removed
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def update_content_types(unpacked_dir: Path, removed_files: list[str]) -> None:
|
|
240
|
+
"""Remove Content-Type overrides for deleted files."""
|
|
241
|
+
ct_path = unpacked_dir / "[Content_Types].xml"
|
|
242
|
+
if not ct_path.exists():
|
|
243
|
+
return
|
|
244
|
+
|
|
245
|
+
dom = defusedxml.minidom.parse(str(ct_path))
|
|
246
|
+
changed = False
|
|
247
|
+
|
|
248
|
+
for override in list(dom.getElementsByTagName("Override")):
|
|
249
|
+
part_name = override.getAttribute("PartName").lstrip("/")
|
|
250
|
+
if part_name in removed_files:
|
|
251
|
+
if override.parentNode:
|
|
252
|
+
override.parentNode.removeChild(override)
|
|
253
|
+
changed = True
|
|
254
|
+
|
|
255
|
+
if changed:
|
|
256
|
+
with open(ct_path, "wb") as f:
|
|
257
|
+
f.write(dom.toxml(encoding="utf-8"))
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def clean_unused_files(unpacked_dir: Path) -> list[str]:
|
|
261
|
+
"""Remove all unreferenced files from the unpacked directory."""
|
|
262
|
+
all_removed = []
|
|
263
|
+
|
|
264
|
+
# Remove orphaned slides first (not in sldIdLst)
|
|
265
|
+
slides_removed = remove_orphaned_slides(unpacked_dir)
|
|
266
|
+
all_removed.extend(slides_removed)
|
|
267
|
+
|
|
268
|
+
# Remove [trash] directory
|
|
269
|
+
trash_removed = remove_trash_directory(unpacked_dir)
|
|
270
|
+
all_removed.extend(trash_removed)
|
|
271
|
+
|
|
272
|
+
# Keep cleaning until nothing more is removed
|
|
273
|
+
while True:
|
|
274
|
+
removed_rels = remove_orphaned_rels_files(unpacked_dir)
|
|
275
|
+
referenced = get_referenced_files(unpacked_dir)
|
|
276
|
+
removed_files = remove_orphaned_files(unpacked_dir, referenced)
|
|
277
|
+
|
|
278
|
+
total_removed = removed_rels + removed_files
|
|
279
|
+
if not total_removed:
|
|
280
|
+
break
|
|
281
|
+
|
|
282
|
+
all_removed.extend(total_removed)
|
|
283
|
+
|
|
284
|
+
if all_removed:
|
|
285
|
+
update_content_types(unpacked_dir, all_removed)
|
|
286
|
+
|
|
287
|
+
return all_removed
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
if __name__ == "__main__":
|
|
291
|
+
if len(sys.argv) != 2:
|
|
292
|
+
print("Usage: python clean.py <unpacked_dir>", file=sys.stderr)
|
|
293
|
+
print("Example: python clean.py unpacked/", file=sys.stderr)
|
|
294
|
+
sys.exit(1)
|
|
295
|
+
|
|
296
|
+
unpacked_dir = Path(sys.argv[1])
|
|
297
|
+
|
|
298
|
+
if not unpacked_dir.exists():
|
|
299
|
+
print(f"Error: {unpacked_dir} not found", file=sys.stderr)
|
|
300
|
+
sys.exit(1)
|
|
301
|
+
|
|
302
|
+
removed = clean_unused_files(unpacked_dir)
|
|
303
|
+
|
|
304
|
+
if removed:
|
|
305
|
+
print(f"Removed {len(removed)} unreferenced files:")
|
|
306
|
+
for f in removed:
|
|
307
|
+
print(f" {f}")
|
|
308
|
+
else:
|
|
309
|
+
print("No unreferenced files found")
|