Generation/SuiHeaderEmitter.cs
using System;
using System.Text.RegularExpressions;
using SboxUiDesigner.Runtime;
namespace SboxUiDesigner.Generation;
/// <summary>
/// Auto-generated header emitter and parser. Every generated file gets a
/// header block with parseable BEGIN/END markers so the manifest checker
/// can verify file ownership before overwriting.
///
/// Format (per PRD doc 10/11):
///
/// SUI:GENERATED:BEGIN ===
/// Generated by Sbox UI Designer.
/// Source: InventoryUI.sui
/// DocumentId: sui_inventoryui_8f31
/// GeneratorVersion: 0.1.0
/// Do not edit this file manually. Changes will be overwritten.
/// SUI:GENERATED:END ===
///
/// Wrapped in Razor `@* ... *@` for .razor and CSS `/* ... */` for .scss.
/// The DocumentId in the header is what makes overwrite safe: the compile
/// step parses the header, sees the DocumentId, and refuses to overwrite
/// when it doesn't match the .sui being compiled.
/// </summary>
public static class SuiHeaderEmitter
{
public const string Marker = "SUI:GENERATED";
public const string GeneratorName = SuiSchemaVersion.CreatedWithTag;
public static string EmitRazorHeader( SuiDocument doc )
{
var sourceName = (doc?.Name ?? "(unknown)") + ".sui";
var docId = doc?.DocumentId ?? "(unknown)";
var version = doc?.DesignerVersion ?? SuiSchemaVersion.DesignerVersion;
return
$"@* {Marker}:BEGIN ============================================================\n" +
$" Generated by {GeneratorName}.\n" +
$" Source: {sourceName}\n" +
$" DocumentId: {docId}\n" +
$" GeneratorVersion: {version}\n" +
$" Do not edit this file manually. Changes will be overwritten.\n" +
$" {Marker}:END ============================================================== *@\n";
}
public static string EmitScssHeader( SuiDocument doc )
{
var sourceName = (doc?.Name ?? "(unknown)") + ".sui";
var docId = doc?.DocumentId ?? "(unknown)";
var version = doc?.DesignerVersion ?? SuiSchemaVersion.DesignerVersion;
return
$"/* {Marker}:BEGIN ===========================================================\n" +
$" * Generated by {GeneratorName}.\n" +
$" * Source: {sourceName}\n" +
$" * DocumentId: {docId}\n" +
$" * GeneratorVersion: {version}\n" +
$" * Do not edit this file manually. Changes will be overwritten.\n" +
$" * {Marker}:END ============================================================= */\n";
}
/// <summary>
/// Parse a generated file's header back into a <see cref="ParsedHeader"/>.
/// Returns null if no valid header block is found; the manifest checker
/// treats null as "not owned" and refuses to overwrite.
/// </summary>
public static ParsedHeader Parse( string content )
{
if ( string.IsNullOrEmpty( content ) ) return null;
var beginMatch = Regex.Match( content, @"SUI:GENERATED:BEGIN\b", RegexOptions.IgnoreCase );
var endMatch = Regex.Match( content, @"SUI:GENERATED:END\b", RegexOptions.IgnoreCase );
if ( !beginMatch.Success || !endMatch.Success || endMatch.Index <= beginMatch.Index )
return null;
var block = content.Substring( beginMatch.Index, endMatch.Index - beginMatch.Index );
string Find( string fieldName )
{
var m = Regex.Match( block, $@"{fieldName}\s*:\s*(.+)", RegexOptions.IgnoreCase );
return m.Success ? m.Groups[1].Value.Trim().TrimEnd( '*', '/', ' ', '\t', '\r', '\n' ) : null;
}
return new ParsedHeader
{
Source = Find( "Source" ),
DocumentId = Find( "DocumentId" ),
GeneratorVersion = Find( "GeneratorVersion" ),
};
}
public sealed class ParsedHeader
{
public string Source { get; set; }
public string DocumentId { get; set; }
public string GeneratorVersion { get; set; }
public bool MatchesDocument( SuiDocument doc )
{
if ( doc == null ) return false;
return string.Equals( DocumentId, doc.DocumentId, StringComparison.OrdinalIgnoreCase );
}
}
}