Skip to content

Make SyntaxKit render methods filesystem-free (return content); move file IO into skit #163

@leogdion

Description

@leogdion

Summary

Refactor the public Runner render API in SyntaxKit so it neither reads user inputs from disk nor writes rendered outputs to disk. The render methods should take in-memory source and return the rendered Swift source (a String/Data for a single file, a keyed dictionary/collection for a batch). All user-facing filesystem IO — walking the input directory, reading inputs, and mirroring outputs into an output directory — moves into the skit CLI.

Motivation

The SDK boundary is currently inconsistent:

  • Runner.renderFile(input:) → SingleFileRender is pure on output — it returns stdout: Data + stderr and lets the skit CLI write the file (Sources/skit/Render.swift renderSingle). But it still reads the input path from disk (Runner.swift:140, String(contentsOf: inputURL)).
  • Runner.renderDirectory(inputDir:outputDir:) → [FileOutcome] is not pure — it walks inputDir (collectInputs), and in Phase 3 writes every successful render into outputDir via writeOutput (Runner+Directory.swift:66-74). FileOutcome only carries input/stderr/result — the rendered content is written and then discarded, never returned.

Making the SDK filesystem-free at its API boundary gives us:

  • Testability — render logic exercisable with in-memory strings, no temp dirs (this is exactly what tripped Windows/WASM in #).
  • Reusability — callers embedding SyntaxKit (not just the CLI) can render into memory, a buffer, a network response, etc.
  • A single, consistent boundary — "pure render in the SDK; all user IO at the skit edge", aligning with the existing SDK/CLI split (Subprocess/ArgumentParser already live only in skit).

Current behavior (references)

  • Sources/SyntaxKit/Execution/Runner.swift:106renderFile(input:); reads input at :140.
  • Sources/SyntaxKit/Execution/Runner+Directory.swift:42renderDirectory(inputDir:outputDir:); input walk at :55, output write at :66-74.
  • Sources/SyntaxKit/Execution/FileManager+Execution.swiftcollectInputs(at:) (input walk), writeOutput(...) (output mirroring).
  • Sources/SyntaxKit/Execution/RenderTaskResult.swift:50writeOutput(...).
  • Sources/SyntaxKit/Execution/URL+Reroot.swiftrerooted(from:onto:), used only to map an input path under inputDir to its destination under outputDir.
  • Sources/SyntaxKit/Execution/FileOutcome.swiftinput/stderr/result; no rendered content.

Proposed direction

SyntaxKit (pure):

  • Single file: render from in-memory source, e.g. render(source: String, originalPath: String?) async throws(RunError) -> SingleFileRender. originalPath is a label only (for #sourceLocation mapping and stderr path-rewriting — see WrappedSource / processFile), not opened.
  • Batch: render a keyed set of in-memory sources, e.g. render(sources: [String: String]) async -> [String: <render-or-error>] (or an array of outcome values keyed by a caller-supplied identifier). Keep the existing bounded-concurrency task group (processInputs), just operating on in-memory sources keyed by identifier instead of URLs.
  • FileOutcome (or its replacement) gains the rendered content (the stdout that is currently written and dropped) so the caller can write it.

skit (owns all user IO):

  • Walk the input directory and read inputs into memory (move/relocate collectInputs, the directory walk, and input reads).
  • Map each input identifier to its destination and write outputs (move writeOutput + URL+Reroot rerooting + outputDir mirroring out of the SDK).
  • Render.renderSingle/renderBatch already own single-file writing and presentation; extend them to own batch writing too.

Implementation notes / nuances

  • The internal temp compile artifact stays. Rendering compiles via swiftc (processFile spills a wrapped .swift into a per-invocation temp dir, then spawns swift). That scratch file is an implementation detail of the compile-based backend, not user IO — it remains inside the SDK. "Filesystem-free" here means no reading user inputs and no writing user outputs.
  • #sourceLocation / stderr path-rewriting currently key off the absolute input path; preserve that by threading a caller-supplied label (originalPath/identifier) through instead of a real path.
  • This is a breaking public API change to Execution (per project convention these types stay public). Land the SDK signature change and the skit migration together so skit keeps building; consider deprecating the old outputDir-writing entry point for one release if external callers matter.

Tasks

  • Add filesystem-free render entry points to Runner (single + batch) taking in-memory source and an identifier/label.
  • Make the batch result carry rendered content (extend/replace FileOutcome).
  • Relocate input collection/reading (collectInputs, directory walk, input reads) into skit.
  • Relocate output mirroring (writeOutput, URL+Reroot, outputDir rerooting) into skit.
  • Update Sources/skit/Render.swift to collect inputs, call the pure SDK, and own all writes.
  • Remove/deprecate outputDir from the public SDK signatures.
  • Update/extend tests to drive rendering from in-memory strings; drop temp-dir reliance where it only existed to feed the SDK.
  • Update DocC / examples for the new boundary.

Out of scope

  • Changing the compile-based rendering backend or the temp-wrapper mechanism.
  • The swift-subprocess invocation and toolchain verification (already skit-adjacent).

Metadata

Metadata

Assignees

No one assigned

    Labels

    apiAPI integrationcliCommand-line interfaceenhancementNew feature or requestioInput/Output operations

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions