8cb1f9f479
Publish SDK (PyPI) / publish (push) Has been cancelled
Publish SDK (npm) / publish (@aionui/officecli-sdk) (push) Has been cancelled
SDK smoke / smoke (windows-latest) (push) Has been cancelled
Publish SDK (npm) / publish (@officecli/officecli-sdk) (push) Has been cancelled
Publish SDK (npm) / publish (@officecli/sdk) (push) Has been cancelled
Publish SDK (npm) / publish (officecli-sdk) (push) Has been cancelled
SDK smoke / smoke (macos-latest) (push) Has been cancelled
SDK smoke / smoke (ubuntu-latest) (push) Has been cancelled
Skill parity / diff (push) Has been cancelled
124 lines
6.3 KiB
C#
124 lines
6.3 KiB
C#
// Copyright 2026 OfficeCLI (https://OfficeCLI.AI)
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
using System.Text.Json;
|
|
|
|
namespace OfficeCli.Core;
|
|
|
|
/// <summary>
|
|
/// Public entry point for running a batch against an already-open
|
|
/// <see cref="IDocumentHandler"/> without going through a CLI process or
|
|
/// resident pipe — for embedders that link officecli directly (in-process
|
|
/// hosts). Reuses the same batch-item dispatch and JSON
|
|
/// envelope shape as the CLI `batch` command and the Node/Python SDKs, so a
|
|
/// caller sees one consistent protocol regardless of transport.
|
|
/// </summary>
|
|
public static class BatchExecutor
|
|
{
|
|
/// <summary>
|
|
/// Runs a batch and returns EXACTLY what the CLI `batch` command writes to
|
|
/// stdout, so an in-process host is byte-identical to the CLI (and to
|
|
/// what the watch server relays): with <paramref name="json"/> the
|
|
/// `{success, data:{results, summary}}` envelope, without it the
|
|
/// `[i] ...\n\nBatch complete: ...` text. This is the same output path the
|
|
/// CLI command uses (RunNonResidentBatch → PrintBatchResults → WrapEnvelope,
|
|
/// see CommandBuilder.Batch.cs); the earlier shortcut here emitted a bare
|
|
/// results array with `json` hardcoded, diverging from every other transport.
|
|
/// </summary>
|
|
/// <param name="itemsJson">A JSON array of batch items — the same shape documented
|
|
/// for the CLI `batch` command and the `BatchItem` SDK type.</param>
|
|
/// <param name="json">Mirrors the CLI `--json` toggle: structured envelope vs plain text.</param>
|
|
public static string ExecuteBatch(IDocumentHandler handler, string itemsJson, bool json, bool stopOnError = false)
|
|
{
|
|
try
|
|
{
|
|
var items = JsonSerializer.Deserialize(itemsJson, BatchJsonContext.Default.ListBatchItem) ?? new List<BatchItem>();
|
|
var unrecognizedLatex = new List<string>();
|
|
var results = CommandBuilder.RunNonResidentBatch(handler, items, stopOnError, json, unrecognizedLatex);
|
|
|
|
// Reuse the CLI's own body formatter so both modes are byte-identical.
|
|
using var sw = new System.IO.StringWriter();
|
|
CommandBuilder.PrintBatchResults(results, json, items.Count, sw);
|
|
var inner = sw.ToString().TrimEnd('\n', '\r');
|
|
if (!json) return inner;
|
|
|
|
// Same outer envelope the CLI batch command applies: unrecognized-LaTeX
|
|
// warnings + outer success true only when every step succeeded
|
|
// (CommandBuilder.Batch.cs). Judgment command — a single failed step
|
|
// flips outer success to false.
|
|
var warnings = new List<CliWarning>();
|
|
foreach (var tok in unrecognizedLatex)
|
|
warnings.Add(new CliWarning
|
|
{
|
|
Message = $"unrecognized_latex_command: {tok}",
|
|
Code = "unrecognized_latex_command",
|
|
Suggestion = "Check the command spelling; see https://katex.org/docs/supported.html for supported syntax.",
|
|
});
|
|
var success = results.Count == 0 || !results.Any(r => !r.Success);
|
|
return OutputFormatter.WrapEnvelope(inner, warnings, success);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return RenderTopLevelError(ex, json);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Mirrors the SDKs' <c>send(item)</c> (as distinct from <c>batch(items)</c>):
|
|
/// runs ONE batch-shaped item and returns EXACTLY what the CLI single command
|
|
/// writes to stdout — with <paramref name="json"/> the standalone command's
|
|
/// envelope (WrapEnvelopeText's `{success,data,message}` for text commands
|
|
/// like set/add/remove, WrapEnvelope's `{success,data}` for data commands like
|
|
/// get/query), without it the plain text. A failure throws instead of being
|
|
/// captured per-item, matching the standalone command's exit contract.
|
|
/// </summary>
|
|
/// <param name="itemJson">A single batch item object — the same shape as one
|
|
/// entry in <see cref="ExecuteBatch"/>'s array, or the SDKs' `send(item)` argument.</param>
|
|
/// <param name="json">Mirrors the CLI `--json` toggle: structured envelope vs plain text.</param>
|
|
public static string ExecuteSend(IDocumentHandler handler, string itemJson, bool json)
|
|
{
|
|
try
|
|
{
|
|
var item = JsonSerializer.Deserialize(itemJson, BatchJsonContext.Default.BatchItem)
|
|
?? throw new ArgumentException("send: empty item");
|
|
var inner = CommandBuilder.ExecuteBatchItem(handler, item, json);
|
|
if (!json) return inner;
|
|
|
|
// Match the standalone command's envelope by inner shape — a JSON payload
|
|
// (get/query/view/validate/…) wraps with WrapEnvelope; a plain-text
|
|
// message (set/add/remove/…) wraps with WrapEnvelopeText (which adds the
|
|
// `message` field the CLI emits). Content-based so a new command inherits
|
|
// the right envelope without a hand-kept command→envelope map.
|
|
var trimmed = inner.TrimStart();
|
|
return trimmed.StartsWith('{') || trimmed.StartsWith('[')
|
|
? OutputFormatter.WrapEnvelope(inner)
|
|
: OutputFormatter.WrapEnvelopeText(inner);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return RenderTopLevelError(ex, json);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Mirror of the CLI's shared <c>WriteError(ex, json)</c> (CommandBuilder):
|
|
/// in JSON mode a failed command puts the <c>{success:false, error:{…}}</c>
|
|
/// envelope on stdout — return it byte-identically. In text mode the CLI
|
|
/// writes to stderr and leaves stdout empty; with no stderr channel in-process
|
|
/// we surface the error by propagating it (the embedder host's failure
|
|
/// signal), matching the prior throw-on-failure contract.
|
|
/// </summary>
|
|
private static string RenderTopLevelError(Exception ex, bool json)
|
|
{
|
|
// CONSISTENCY(error-wrap): same friendlier rendering the CLI applies to a
|
|
// bare XmlException from an externally-corrupted OOXML part.
|
|
var rendered = ex is System.Xml.XmlException xe
|
|
? new System.IO.InvalidDataException(
|
|
$"Malformed XML in document part: {xe.Message} " +
|
|
$"(the file appears to have a corrupted OOXML part).", xe)
|
|
: ex;
|
|
if (json) return OutputFormatter.WrapErrorEnvelope(rendered);
|
|
throw rendered;
|
|
}
|
|
}
|