graphitect 0.2.0__py3-none-any.whl
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.
- graphify/__init__.py +30 -0
- graphify/__main__.py +757 -0
- graphify/_minhash.py +107 -0
- graphify/affected.py +318 -0
- graphify/always_on/agents-md.md +12 -0
- graphify/always_on/antigravity-rules.md +14 -0
- graphify/always_on/claude-md.md +9 -0
- graphify/always_on/gemini-md.md +9 -0
- graphify/always_on/kiro-steering.md +5 -0
- graphify/always_on/vscode-instructions.md +17 -0
- graphify/analyze.py +769 -0
- graphify/benchmark.py +152 -0
- graphify/build.py +2300 -0
- graphify/cache.py +1746 -0
- graphify/callflow_html.py +2051 -0
- graphify/cargo_introspect.py +109 -0
- graphify/cli.py +4745 -0
- graphify/cluster.py +409 -0
- graphify/command-kilo.md +15 -0
- graphify/cross_repo_calls.py +216 -0
- graphify/cross_repo_types.py +75 -0
- graphify/csharp_dispatch.py +154 -0
- graphify/dedup.py +1213 -0
- graphify/detect.py +2566 -0
- graphify/diagnostics.py +406 -0
- graphify/export.py +1349 -0
- graphify/exporters/__init__.py +1 -0
- graphify/exporters/base.py +14 -0
- graphify/exporters/graphdb.py +173 -0
- graphify/exporters/html.py +637 -0
- graphify/extract.py +7856 -0
- graphify/extractors/MIGRATION.md +107 -0
- graphify/extractors/__init__.py +66 -0
- graphify/extractors/apex.py +215 -0
- graphify/extractors/base.py +85 -0
- graphify/extractors/bash.py +579 -0
- graphify/extractors/blade.py +53 -0
- graphify/extractors/commonlisp.py +540 -0
- graphify/extractors/csharp.py +448 -0
- graphify/extractors/dart.py +564 -0
- graphify/extractors/dm.py +494 -0
- graphify/extractors/elixir.py +241 -0
- graphify/extractors/engine.py +6509 -0
- graphify/extractors/fortran.py +311 -0
- graphify/extractors/go.py +527 -0
- graphify/extractors/json_config.py +240 -0
- graphify/extractors/julia.py +289 -0
- graphify/extractors/markdown.py +408 -0
- graphify/extractors/models.py +131 -0
- graphify/extractors/objc.py +566 -0
- graphify/extractors/ocaml.py +289 -0
- graphify/extractors/pascal.py +688 -0
- graphify/extractors/pascal_forms.py +196 -0
- graphify/extractors/powershell.py +522 -0
- graphify/extractors/razor.py +192 -0
- graphify/extractors/resolution.py +3584 -0
- graphify/extractors/robot.py +296 -0
- graphify/extractors/rust.py +470 -0
- graphify/extractors/sln.py +92 -0
- graphify/extractors/sql.py +720 -0
- graphify/extractors/terraform.py +181 -0
- graphify/extractors/verilog.py +329 -0
- graphify/extractors/zig.py +181 -0
- graphify/file_slice.py +246 -0
- graphify/global_graph.py +194 -0
- graphify/google_workspace.py +237 -0
- graphify/hooks.py +933 -0
- graphify/ids.py +93 -0
- graphify/ingest.py +358 -0
- graphify/install.py +2366 -0
- graphify/llm.py +3544 -0
- graphify/manifest.py +4 -0
- graphify/manifest_ingest.py +311 -0
- graphify/mcp_ingest.py +386 -0
- graphify/multigraph_compat.py +212 -0
- graphify/pascal_resolution.py +129 -0
- graphify/paths.py +436 -0
- graphify/pg_introspect.py +165 -0
- graphify/prs.py +770 -0
- graphify/querylog.py +80 -0
- graphify/reflect.py +882 -0
- graphify/report.py +346 -0
- graphify/resolver_registry.py +85 -0
- graphify/ruby_resolution.py +242 -0
- graphify/scip_ingest.py +363 -0
- graphify/security.py +460 -0
- graphify/semantic_cleanup.py +336 -0
- graphify/serve.py +2608 -0
- graphify/skill-agents.md +710 -0
- graphify/skill-aider.md +1283 -0
- graphify/skill-amp.md +710 -0
- graphify/skill-claw.md +713 -0
- graphify/skill-codex.md +710 -0
- graphify/skill-copilot.md +713 -0
- graphify/skill-devin.md +1410 -0
- graphify/skill-droid.md +710 -0
- graphify/skill-kilo.md +722 -0
- graphify/skill-kiro.md +713 -0
- graphify/skill-opencode.md +705 -0
- graphify/skill-pi.md +713 -0
- graphify/skill-trae.md +711 -0
- graphify/skill-vscode.md +709 -0
- graphify/skill-windows.md +755 -0
- graphify/skill.md +713 -0
- graphify/skills/agents/references/add-watch.md +56 -0
- graphify/skills/agents/references/exports.md +87 -0
- graphify/skills/agents/references/extraction-spec.md +70 -0
- graphify/skills/agents/references/github-and-merge.md +46 -0
- graphify/skills/agents/references/hooks.md +33 -0
- graphify/skills/agents/references/query.md +311 -0
- graphify/skills/agents/references/transcribe.md +52 -0
- graphify/skills/agents/references/update.md +210 -0
- graphify/skills/amp/references/add-watch.md +56 -0
- graphify/skills/amp/references/exports.md +87 -0
- graphify/skills/amp/references/extraction-spec.md +70 -0
- graphify/skills/amp/references/github-and-merge.md +46 -0
- graphify/skills/amp/references/hooks.md +33 -0
- graphify/skills/amp/references/query.md +311 -0
- graphify/skills/amp/references/transcribe.md +52 -0
- graphify/skills/amp/references/update.md +210 -0
- graphify/skills/claude/references/add-watch.md +56 -0
- graphify/skills/claude/references/exports.md +87 -0
- graphify/skills/claude/references/extraction-spec.md +70 -0
- graphify/skills/claude/references/github-and-merge.md +46 -0
- graphify/skills/claude/references/hooks.md +33 -0
- graphify/skills/claude/references/query.md +311 -0
- graphify/skills/claude/references/transcribe.md +52 -0
- graphify/skills/claude/references/update.md +210 -0
- graphify/skills/claw/references/add-watch.md +56 -0
- graphify/skills/claw/references/exports.md +87 -0
- graphify/skills/claw/references/extraction-spec.md +31 -0
- graphify/skills/claw/references/github-and-merge.md +46 -0
- graphify/skills/claw/references/hooks.md +33 -0
- graphify/skills/claw/references/query.md +311 -0
- graphify/skills/claw/references/transcribe.md +52 -0
- graphify/skills/claw/references/update.md +210 -0
- graphify/skills/codex/references/add-watch.md +56 -0
- graphify/skills/codex/references/exports.md +87 -0
- graphify/skills/codex/references/extraction-spec.md +31 -0
- graphify/skills/codex/references/github-and-merge.md +46 -0
- graphify/skills/codex/references/hooks.md +33 -0
- graphify/skills/codex/references/query.md +311 -0
- graphify/skills/codex/references/transcribe.md +52 -0
- graphify/skills/codex/references/update.md +210 -0
- graphify/skills/copilot/references/add-watch.md +56 -0
- graphify/skills/copilot/references/exports.md +87 -0
- graphify/skills/copilot/references/extraction-spec.md +70 -0
- graphify/skills/copilot/references/github-and-merge.md +46 -0
- graphify/skills/copilot/references/hooks.md +33 -0
- graphify/skills/copilot/references/query.md +311 -0
- graphify/skills/copilot/references/transcribe.md +52 -0
- graphify/skills/copilot/references/update.md +210 -0
- graphify/skills/droid/references/add-watch.md +56 -0
- graphify/skills/droid/references/exports.md +87 -0
- graphify/skills/droid/references/extraction-spec.md +70 -0
- graphify/skills/droid/references/github-and-merge.md +46 -0
- graphify/skills/droid/references/hooks.md +33 -0
- graphify/skills/droid/references/query.md +311 -0
- graphify/skills/droid/references/transcribe.md +52 -0
- graphify/skills/droid/references/update.md +210 -0
- graphify/skills/kilo/references/add-watch.md +56 -0
- graphify/skills/kilo/references/exports.md +87 -0
- graphify/skills/kilo/references/extraction-spec.md +70 -0
- graphify/skills/kilo/references/github-and-merge.md +46 -0
- graphify/skills/kilo/references/hooks.md +33 -0
- graphify/skills/kilo/references/query.md +311 -0
- graphify/skills/kilo/references/transcribe.md +52 -0
- graphify/skills/kilo/references/update.md +210 -0
- graphify/skills/kiro/references/add-watch.md +56 -0
- graphify/skills/kiro/references/exports.md +87 -0
- graphify/skills/kiro/references/extraction-spec.md +31 -0
- graphify/skills/kiro/references/github-and-merge.md +46 -0
- graphify/skills/kiro/references/hooks.md +33 -0
- graphify/skills/kiro/references/query.md +311 -0
- graphify/skills/kiro/references/transcribe.md +52 -0
- graphify/skills/kiro/references/update.md +210 -0
- graphify/skills/opencode/references/add-watch.md +56 -0
- graphify/skills/opencode/references/exports.md +87 -0
- graphify/skills/opencode/references/extraction-spec.md +70 -0
- graphify/skills/opencode/references/github-and-merge.md +46 -0
- graphify/skills/opencode/references/hooks.md +33 -0
- graphify/skills/opencode/references/query.md +311 -0
- graphify/skills/opencode/references/transcribe.md +52 -0
- graphify/skills/opencode/references/update.md +210 -0
- graphify/skills/pi/references/add-watch.md +56 -0
- graphify/skills/pi/references/exports.md +87 -0
- graphify/skills/pi/references/extraction-spec.md +31 -0
- graphify/skills/pi/references/github-and-merge.md +46 -0
- graphify/skills/pi/references/hooks.md +33 -0
- graphify/skills/pi/references/query.md +311 -0
- graphify/skills/pi/references/transcribe.md +52 -0
- graphify/skills/pi/references/update.md +210 -0
- graphify/skills/trae/references/add-watch.md +56 -0
- graphify/skills/trae/references/exports.md +87 -0
- graphify/skills/trae/references/extraction-spec.md +70 -0
- graphify/skills/trae/references/github-and-merge.md +46 -0
- graphify/skills/trae/references/hooks.md +35 -0
- graphify/skills/trae/references/query.md +311 -0
- graphify/skills/trae/references/transcribe.md +52 -0
- graphify/skills/trae/references/update.md +210 -0
- graphify/skills/vscode/references/add-watch.md +56 -0
- graphify/skills/vscode/references/exports.md +87 -0
- graphify/skills/vscode/references/extraction-spec.md +70 -0
- graphify/skills/vscode/references/github-and-merge.md +46 -0
- graphify/skills/vscode/references/hooks.md +33 -0
- graphify/skills/vscode/references/query.md +311 -0
- graphify/skills/vscode/references/transcribe.md +52 -0
- graphify/skills/vscode/references/update.md +210 -0
- graphify/skills/windows/references/add-watch.md +56 -0
- graphify/skills/windows/references/exports.md +87 -0
- graphify/skills/windows/references/extraction-spec.md +70 -0
- graphify/skills/windows/references/github-and-merge.md +46 -0
- graphify/skills/windows/references/hooks.md +33 -0
- graphify/skills/windows/references/query.md +311 -0
- graphify/skills/windows/references/transcribe.md +52 -0
- graphify/skills/windows/references/update.md +210 -0
- graphify/symbol_resolution.py +556 -0
- graphify/transcribe.py +186 -0
- graphify/tree_html.py +603 -0
- graphify/validate.py +95 -0
- graphify/watch.py +2280 -0
- graphify/wiki.py +405 -0
- graphitect/__init__.py +28 -0
- graphitect/__main__.py +4 -0
- graphitect/_vendor/__init__.py +2 -0
- graphitect/_vendor/archify/LICENSE +22 -0
- graphitect/_vendor/archify/SKILL.md +137 -0
- graphitect/_vendor/archify/THIRD_PARTY_NOTICES.md +69 -0
- graphitect/_vendor/archify/assets/JetBrainsMono-OFL.txt +93 -0
- graphitect/_vendor/archify/assets/template.html +14935 -0
- graphitect/_vendor/archify/bin/archify.mjs +2091 -0
- graphitect/_vendor/archify/bin/open-artifact.mjs +86 -0
- graphitect/_vendor/archify/bin/preview.mjs +653 -0
- graphitect/_vendor/archify/bin/visual-check.mjs +829 -0
- graphitect/_vendor/archify/brand-marks/README.md +31 -0
- graphitect/_vendor/archify/brand-marks/catalog.json +131 -0
- graphitect/_vendor/archify/delta/architecture-delta.mjs +1221 -0
- graphitect/_vendor/archify/examples/agent-run.lifecycle.json +60 -0
- graphitect/_vendor/archify/examples/agent-tool-call.workflow.json +94 -0
- graphitect/_vendor/archify/examples/async-job-roundtrip.sequence.json +61 -0
- graphitect/_vendor/archify/examples/brand-aware-delivery.architecture.json +47 -0
- graphitect/_vendor/archify/examples/cache-miss-request.sequence.json +82 -0
- graphitect/_vendor/archify/examples/checkout-platform.base.architecture.json +31 -0
- graphitect/_vendor/archify/examples/checkout-platform.head.architecture.json +31 -0
- graphitect/_vendor/archify/examples/dataflow-product-analytics.html +15045 -0
- graphitect/_vendor/archify/examples/deployment-release.lifecycle.json +49 -0
- graphitect/_vendor/archify/examples/event-stream.dataflow.json +57 -0
- graphitect/_vendor/archify/examples/incident-response.workflow.json +64 -0
- graphitect/_vendor/archify/examples/lifecycle-agent-run.html +14980 -0
- graphitect/_vendor/archify/examples/product-analytics.dataflow.json +76 -0
- graphitect/_vendor/archify/examples/production-deployment.architecture.json +71 -0
- graphitect/_vendor/archify/examples/release-delivery.workflow.json +62 -0
- graphitect/_vendor/archify/examples/sequence-cache-miss-request.html +15060 -0
- graphitect/_vendor/archify/examples/web-app-rendered.html +15009 -0
- graphitect/_vendor/archify/examples/web-app.architecture.json +46 -0
- graphitect/_vendor/archify/examples/workflow-agent-tool-call-rendered.html +15051 -0
- graphitect/_vendor/archify/migrations/workflow-v2.mjs +279 -0
- graphitect/_vendor/archify/package-lock.json +149 -0
- graphitect/_vendor/archify/package.json +39 -0
- graphitect/_vendor/archify/recipes/scenarios.mjs +391 -0
- graphitect/_vendor/archify/references/authoring-contract.md +243 -0
- graphitect/_vendor/archify/references/brand-marks.md +65 -0
- graphitect/_vendor/archify/references/delivery-contract.md +120 -0
- graphitect/_vendor/archify/references/viewer-runtime.md +45 -0
- graphitect/_vendor/archify/renderers/architecture/grid.mjs +62 -0
- graphitect/_vendor/archify/renderers/architecture/render-architecture.mjs +1078 -0
- graphitect/_vendor/archify/renderers/dataflow/README.md +104 -0
- graphitect/_vendor/archify/renderers/dataflow/render-dataflow.mjs +483 -0
- graphitect/_vendor/archify/renderers/lifecycle/README.md +115 -0
- graphitect/_vendor/archify/renderers/lifecycle/render-lifecycle.mjs +561 -0
- graphitect/_vendor/archify/renderers/sequence/README.md +114 -0
- graphitect/_vendor/archify/renderers/sequence/render-sequence.mjs +464 -0
- graphitect/_vendor/archify/renderers/shared/brand-marks.mjs +563 -0
- graphitect/_vendor/archify/renderers/shared/cli.mjs +218 -0
- graphitect/_vendor/archify/renderers/shared/desktop-readability.mjs +26 -0
- graphitect/_vendor/archify/renderers/shared/diagnostics.mjs +127 -0
- graphitect/_vendor/archify/renderers/shared/engineering-profiles.mjs +157 -0
- graphitect/_vendor/archify/renderers/shared/generated-brand-marks.mjs +2003 -0
- graphitect/_vendor/archify/renderers/shared/generated-validators.mjs +13 -0
- graphitect/_vendor/archify/renderers/shared/geometry.mjs +1423 -0
- graphitect/_vendor/archify/renderers/shared/i18n.mjs +595 -0
- graphitect/_vendor/archify/renderers/shared/layout-report.mjs +40 -0
- graphitect/_vendor/archify/renderers/shared/legend.mjs +217 -0
- graphitect/_vendor/archify/renderers/shared/output-path.mjs +340 -0
- graphitect/_vendor/archify/renderers/shared/repository-evidence.mjs +238 -0
- graphitect/_vendor/archify/renderers/shared/repository-location.mjs +58 -0
- graphitect/_vendor/archify/renderers/shared/text-fit.mjs +49 -0
- graphitect/_vendor/archify/renderers/shared/utils.mjs +232 -0
- graphitect/_vendor/archify/renderers/shared/validator.mjs +86 -0
- graphitect/_vendor/archify/renderers/workflow/README.md +223 -0
- graphitect/_vendor/archify/renderers/workflow/render-workflow.mjs +35 -0
- graphitect/_vendor/archify/renderers/workflow/workflow-compiler.mjs +4400 -0
- graphitect/_vendor/archify/renderers/workflow/workflow-migration-geometry.mjs +144 -0
- graphitect/_vendor/archify/schemas/README.md +211 -0
- graphitect/_vendor/archify/schemas/architecture.schema.json +178 -0
- graphitect/_vendor/archify/schemas/common.schema.json +115 -0
- graphitect/_vendor/archify/schemas/dataflow.schema.json +243 -0
- graphitect/_vendor/archify/schemas/lifecycle.schema.json +266 -0
- graphitect/_vendor/archify/schemas/sequence.schema.json +223 -0
- graphitect/_vendor/archify/schemas/workflow.schema.json +428 -0
- graphitect/_vendor/archify/scripts/check-render-output.mjs +836 -0
- graphitect/_vendor/archify/scripts/check-update.mjs +1667 -0
- graphitect/_vendor/archify/scripts/generate-brand-marks.mjs +141 -0
- graphitect/_vendor/archify/scripts/generate-validators.mjs +66 -0
- graphitect/_vendor/archify/scripts/render-examples.mjs +26 -0
- graphitect/_vendor/archify/scripts/update-contract.mjs +182 -0
- graphitect/_vendor/archify/skill-release.json +10 -0
- graphitect/cli.py +981 -0
- graphitect/deliver/__init__.py +5 -0
- graphitect/deliver/archify_adapter.py +1877 -0
- graphitect/deliver/archify_ir.py +160 -0
- graphitect/deliver/archify_repair.py +135 -0
- graphitect/deliver/doc_compiler.py +916 -0
- graphitect/ground/__init__.py +5 -0
- graphitect/ground/describe_source.py +27 -0
- graphitect/ground/fullread_source.py +56 -0
- graphitect/ground/graphify_source.py +107 -0
- graphitect/models.py +118 -0
- graphitect/skill/SKILL.md +80 -0
- graphitect/skill/agents/openai.yaml +4 -0
- graphitect/synthesize/__init__.py +5 -0
- graphitect/synthesize/engine.py +281 -0
- graphitect/synthesize/llm_backend.py +331 -0
- graphitect/synthesize/questions.py +139 -0
- graphitect/synthesize/rubric.py +104 -0
- graphitect-0.2.0.dist-info/METADATA +284 -0
- graphitect-0.2.0.dist-info/RECORD +336 -0
- graphitect-0.2.0.dist-info/WHEEL +5 -0
- graphitect-0.2.0.dist-info/entry_points.txt +2 -0
- graphitect-0.2.0.dist-info/licenses/LICENSE +21 -0
- graphitect-0.2.0.dist-info/licenses/LICENSE-ARCHIFY-MIT +22 -0
- graphitect-0.2.0.dist-info/licenses/LICENSE-GRAPHIFY-APACHE-2.0 +202 -0
- graphitect-0.2.0.dist-info/licenses/LICENSE-GRAPHIFY-MIT +21 -0
- graphitect-0.2.0.dist-info/licenses/NOTICE-ARCHIFY-THIRD-PARTY.md +69 -0
- graphitect-0.2.0.dist-info/licenses/NOTICE-GRAPHIFY +8 -0
- graphitect-0.2.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""Optional Google Workspace shortcut export support.
|
|
2
|
+
|
|
3
|
+
Google Drive for desktop stores native Docs, Sheets, and Slides as small JSON
|
|
4
|
+
shortcut files (.gdoc, .gsheet, .gslides). Those files are pointers, not the
|
|
5
|
+
document content. This module exports them to Markdown sidecars via the
|
|
6
|
+
googleworkspace CLI (`gws`) so Graphify can extract their actual contents.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import hashlib
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import shutil
|
|
15
|
+
import subprocess
|
|
16
|
+
import tempfile
|
|
17
|
+
import urllib.parse
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Callable, Any
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
GOOGLE_WORKSPACE_EXTENSIONS = {".gdoc", ".gsheet", ".gslides"}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def google_workspace_enabled(value: str | None = None) -> bool:
|
|
26
|
+
"""Return True when Google Workspace shortcut export is enabled."""
|
|
27
|
+
raw = value if value is not None else os.environ.get("GRAPHIFY_GOOGLE_WORKSPACE", "")
|
|
28
|
+
return raw.strip().lower() in {"1", "true", "yes", "on"}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _safe_yaml_str(value: str) -> str:
|
|
32
|
+
return value.replace("\\", "\\\\").replace('"', '\\"').replace("\n", " ").replace("\r", " ")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _extract_file_id_from_url(url: str) -> str | None:
|
|
36
|
+
"""Extract a Drive file ID from common Google Docs/Drive URL shapes."""
|
|
37
|
+
if not url:
|
|
38
|
+
return None
|
|
39
|
+
parsed = urllib.parse.urlparse(url)
|
|
40
|
+
query = urllib.parse.parse_qs(parsed.query)
|
|
41
|
+
if query.get("id"):
|
|
42
|
+
return query["id"][0]
|
|
43
|
+
match = re.search(r"/(?:document|spreadsheets|presentation|file)/d/([^/?#]+)", parsed.path)
|
|
44
|
+
if match:
|
|
45
|
+
return match.group(1)
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _extract_resource_key(url: str, data: dict[str, Any]) -> str | None:
|
|
50
|
+
for key in ("resource_key", "resourceKey"):
|
|
51
|
+
value = data.get(key)
|
|
52
|
+
if value:
|
|
53
|
+
return str(value)
|
|
54
|
+
if not url:
|
|
55
|
+
return None
|
|
56
|
+
parsed = urllib.parse.urlparse(url)
|
|
57
|
+
query = urllib.parse.parse_qs(parsed.query)
|
|
58
|
+
if query.get("resourcekey"):
|
|
59
|
+
return query["resourcekey"][0]
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def read_google_shortcut(path: Path) -> dict[str, str | None]:
|
|
64
|
+
"""Read a .gdoc/.gsheet/.gslides shortcut and return export metadata."""
|
|
65
|
+
try:
|
|
66
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
67
|
+
except Exception as exc:
|
|
68
|
+
raise RuntimeError(f"could not read Google Workspace shortcut {path}: {exc}") from exc
|
|
69
|
+
|
|
70
|
+
url = str(data.get("url") or "")
|
|
71
|
+
file_id = (
|
|
72
|
+
data.get("doc_id")
|
|
73
|
+
or data.get("file_id")
|
|
74
|
+
or data.get("fileId")
|
|
75
|
+
or data.get("id")
|
|
76
|
+
or _extract_file_id_from_url(url)
|
|
77
|
+
)
|
|
78
|
+
if not file_id:
|
|
79
|
+
resource_id = str(data.get("resource_id") or "")
|
|
80
|
+
if ":" in resource_id:
|
|
81
|
+
file_id = resource_id.split(":", 1)[1]
|
|
82
|
+
|
|
83
|
+
if not file_id:
|
|
84
|
+
raise RuntimeError(f"Google Workspace shortcut {path} does not include a Drive file ID")
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
"file_id": str(file_id),
|
|
88
|
+
"url": url or None,
|
|
89
|
+
"resource_key": _extract_resource_key(url, data),
|
|
90
|
+
"account": str(data.get("email")) if data.get("email") else None,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _run_gws_export(file_id: str, mime_type: str, output: Path, resource_key: str | None = None) -> None:
|
|
95
|
+
exe = shutil.which("gws")
|
|
96
|
+
if not exe:
|
|
97
|
+
raise RuntimeError(
|
|
98
|
+
"gws is required for Google Workspace export. Install it from "
|
|
99
|
+
"https://github.com/googleworkspace/cli and run `gws auth login -s drive`."
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
params: dict[str, str] = {"fileId": file_id, "mimeType": mime_type}
|
|
103
|
+
# Drive resource keys are sent via X-Goog-Drive-Resource-Keys. The current
|
|
104
|
+
# gws export command has no custom-header flag, so do not pass resourceKey
|
|
105
|
+
# as an unsupported query parameter.
|
|
106
|
+
_ = resource_key
|
|
107
|
+
output = output.resolve()
|
|
108
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
109
|
+
timeout = int(os.environ.get("GRAPHIFY_GOOGLE_WORKSPACE_TIMEOUT", "120"))
|
|
110
|
+
result = subprocess.run(
|
|
111
|
+
[exe, "drive", "files", "export", "--params", json.dumps(params), "-o", output.name],
|
|
112
|
+
capture_output=True,
|
|
113
|
+
cwd=output.parent,
|
|
114
|
+
text=True,
|
|
115
|
+
timeout=timeout,
|
|
116
|
+
)
|
|
117
|
+
if result.returncode != 0:
|
|
118
|
+
stderr = (result.stderr or result.stdout or "").strip()
|
|
119
|
+
if len(stderr) > 1200:
|
|
120
|
+
stderr = stderr[:1200] + "..."
|
|
121
|
+
raise RuntimeError(f"gws export failed for {file_id}: {stderr}")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _sidecar_path(path: Path, out_dir: Path, root: "Path | None" = None) -> Path:
|
|
125
|
+
# Hash the scan-root-relative, NFC-normalized path — not the absolute path.
|
|
126
|
+
# The absolute form salts the sidecar name with the checkout location, so the
|
|
127
|
+
# same shortcut in two clones/worktrees emits differently-named byte-identical
|
|
128
|
+
# sidecars, each ingested as a distinct source doc when graphify-out/ is
|
|
129
|
+
# committed (#2059; mirrors convert_office_file). NFC guards macOS NFD drift
|
|
130
|
+
# (#1226). The relative path still disambiguates same-stem files.
|
|
131
|
+
import unicodedata
|
|
132
|
+
if root is None:
|
|
133
|
+
root = out_dir.parent.parent
|
|
134
|
+
try:
|
|
135
|
+
key = path.resolve().relative_to(Path(root).resolve()).as_posix()
|
|
136
|
+
except (ValueError, OSError):
|
|
137
|
+
key = str(path.resolve())
|
|
138
|
+
name_hash = hashlib.sha256(unicodedata.normalize("NFC", key).encode()).hexdigest()[:8]
|
|
139
|
+
return out_dir / f"{path.stem}_{name_hash}.md"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _with_frontmatter(path: Path, shortcut: dict[str, str | None], body: str, exported_mime_type: str) -> str:
|
|
143
|
+
source_url = shortcut.get("url") or ""
|
|
144
|
+
account = shortcut.get("account") or ""
|
|
145
|
+
account_line = ""
|
|
146
|
+
if account:
|
|
147
|
+
account_hash = hashlib.sha256(account.encode()).hexdigest()[:12]
|
|
148
|
+
account_line = f'google_account_hash: "{account_hash}"\n'
|
|
149
|
+
return (
|
|
150
|
+
"---\n"
|
|
151
|
+
f'source_file: "{_safe_yaml_str(str(path))}"\n'
|
|
152
|
+
'source_type: "google_workspace"\n'
|
|
153
|
+
f'google_file_id: "{_safe_yaml_str(shortcut["file_id"] or "")}"\n'
|
|
154
|
+
f'google_export_mime_type: "{_safe_yaml_str(exported_mime_type)}"\n'
|
|
155
|
+
f'source_url: "{_safe_yaml_str(source_url)}"\n'
|
|
156
|
+
f"{account_line}"
|
|
157
|
+
"---\n\n"
|
|
158
|
+
f"<!-- converted from Google Workspace shortcut: {path.name} -->\n\n"
|
|
159
|
+
f"{body.strip()}\n"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def convert_google_workspace_file(
|
|
164
|
+
path: Path,
|
|
165
|
+
out_dir: Path,
|
|
166
|
+
*,
|
|
167
|
+
xlsx_to_markdown: Callable[[Path], str] | None = None,
|
|
168
|
+
root: "Path | None" = None,
|
|
169
|
+
) -> Path | None:
|
|
170
|
+
"""Export a Google Workspace shortcut to a Markdown sidecar.
|
|
171
|
+
|
|
172
|
+
Returns the converted Markdown path, or None when conversion is unsupported
|
|
173
|
+
or produced no readable content.
|
|
174
|
+
"""
|
|
175
|
+
ext = path.suffix.lower()
|
|
176
|
+
if ext not in GOOGLE_WORKSPACE_EXTENSIONS:
|
|
177
|
+
return None
|
|
178
|
+
|
|
179
|
+
shortcut = read_google_shortcut(path)
|
|
180
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
181
|
+
out_path = _sidecar_path(path, out_dir, root=root)
|
|
182
|
+
|
|
183
|
+
if ext == ".gdoc":
|
|
184
|
+
with tempfile.NamedTemporaryFile("w+b", suffix=".md", delete=False, dir=out_dir) as tmp:
|
|
185
|
+
tmp_path = Path(tmp.name)
|
|
186
|
+
try:
|
|
187
|
+
_run_gws_export(shortcut["file_id"] or "", "text/markdown", tmp_path, shortcut.get("resource_key"))
|
|
188
|
+
body = tmp_path.read_text(encoding="utf-8", errors="replace")
|
|
189
|
+
finally:
|
|
190
|
+
tmp_path.unlink(missing_ok=True)
|
|
191
|
+
if not body.strip():
|
|
192
|
+
return None
|
|
193
|
+
out_path.write_text(_with_frontmatter(path, shortcut, body, "text/markdown"), encoding="utf-8")
|
|
194
|
+
return out_path
|
|
195
|
+
|
|
196
|
+
if ext == ".gslides":
|
|
197
|
+
with tempfile.NamedTemporaryFile("w+b", suffix=".txt", delete=False, dir=out_dir) as tmp:
|
|
198
|
+
tmp_path = Path(tmp.name)
|
|
199
|
+
try:
|
|
200
|
+
_run_gws_export(shortcut["file_id"] or "", "text/plain", tmp_path, shortcut.get("resource_key"))
|
|
201
|
+
body = tmp_path.read_text(encoding="utf-8", errors="replace")
|
|
202
|
+
finally:
|
|
203
|
+
tmp_path.unlink(missing_ok=True)
|
|
204
|
+
if not body.strip():
|
|
205
|
+
return None
|
|
206
|
+
out_path.write_text(_with_frontmatter(path, shortcut, body, "text/plain"), encoding="utf-8")
|
|
207
|
+
return out_path
|
|
208
|
+
|
|
209
|
+
if ext == ".gsheet":
|
|
210
|
+
if xlsx_to_markdown is None:
|
|
211
|
+
raise RuntimeError("Google Sheets export requires the office extra: pip install graphifyy[office,google]")
|
|
212
|
+
with tempfile.NamedTemporaryFile("w+b", suffix=".xlsx", delete=False, dir=out_dir) as tmp:
|
|
213
|
+
tmp_path = Path(tmp.name)
|
|
214
|
+
try:
|
|
215
|
+
_run_gws_export(
|
|
216
|
+
shortcut["file_id"] or "",
|
|
217
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
218
|
+
tmp_path,
|
|
219
|
+
shortcut.get("resource_key"),
|
|
220
|
+
)
|
|
221
|
+
body = xlsx_to_markdown(tmp_path)
|
|
222
|
+
finally:
|
|
223
|
+
tmp_path.unlink(missing_ok=True)
|
|
224
|
+
if not body.strip():
|
|
225
|
+
return None
|
|
226
|
+
out_path.write_text(
|
|
227
|
+
_with_frontmatter(
|
|
228
|
+
path,
|
|
229
|
+
shortcut,
|
|
230
|
+
body,
|
|
231
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
232
|
+
),
|
|
233
|
+
encoding="utf-8",
|
|
234
|
+
)
|
|
235
|
+
return out_path
|
|
236
|
+
|
|
237
|
+
return None
|