Skip to content

Commit

Permalink
YamlWriter is now disposable
Browse files Browse the repository at this point in the history
  • Loading branch information
anpetroc committed Dec 2, 2023
1 parent 1c9b22c commit 4c2ca75
Showing 1 changed file with 159 additions and 136 deletions.
295 changes: 159 additions & 136 deletions src/PAModel/PAConvert/Yaml/YamlWriter.cs
Original file line number Diff line number Diff line change
@@ -1,192 +1,215 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.IO;

namespace Microsoft.PowerPlatform.Formulas.Tools.Yaml
namespace Microsoft.PowerPlatform.Formulas.Tools.Yaml;

/// <summary>
/// Helper to write out a safe subset of Yaml.
/// Notably, property values are always either multi-line escaped or
/// prefixed with '=' to block yaml from treating it as a yaml expression.
/// </summary>
public class YamlWriter : IDisposable
{
/// <summary>
/// Helper to write out a safe subset of Yaml.
/// Notably, property values are always either multi-line escaped or
/// prefixed with '=' to block yaml from treating it as a yaml expression.
/// </summary>
public class YamlWriter
{
private readonly TextWriter _text;
private int _currentIndent;
private readonly TextWriter _textWriter;
private int _currentIndent;
private bool _isDisposed;
private const string Indent = " ";

private const string Indent = " ";
public YamlWriter(TextWriter text)
{
_textWriter = text ?? throw new ArgumentNullException(nameof(text));
}

public YamlWriter(TextWriter text)
{
_text = text;
}
public void WriteStartObject(string propertyName)
{
WriteIndent();

public void WriteStartObject(string propertyName)
{
WriteIndent();
bool needsEscape = propertyName.IndexOfAny(new char[] { '\"', '\'' }) != -1;
if (needsEscape)
propertyName = $"\"{propertyName.Replace("\"", "\\\"")}\"";

bool needsEscape = propertyName.IndexOfAny(new char[] { '\"', '\'' }) != -1;
if (needsEscape)
propertyName = $"\"{propertyName.Replace("\"", "\\\"")}\"";
_textWriter.Write(propertyName);
_textWriter.WriteLine(":");

_text.Write(propertyName);
_text.WriteLine(":");
_currentIndent++;
}

_currentIndent++;
public void WriteEndObject()
{
_currentIndent--;
if (_currentIndent < 0)
{
throw new InvalidOperationException("No matching start object");
}
public void WriteEndObject()
}

public void WriteProperty(string propertyName, bool value, bool includeEquals = true)
{
WriteIndent();
_textWriter.Write(propertyName);
_textWriter.Write(": ");
if (includeEquals)
{
_currentIndent--;
if (_currentIndent < 0)
{
throw new InvalidOperationException("No matching start object");
}
_textWriter.Write("=");
}
_textWriter.WriteLine(value ? "true" : "false");
}

public void WriteProperty(string propertyName, bool value, bool includeEquals = true)
public void WriteProperty(string propertyName, int value, bool includeEquals = true)
{
WriteIndent();
_textWriter.Write(propertyName);
_textWriter.Write(": ");
if (includeEquals)
{
WriteIndent();
_text.Write(propertyName);
_text.Write(": ");
if (includeEquals)
{
_text.Write("=");
}
_text.WriteLine(value ? "true" : "false");
_textWriter.Write("=");
}
_textWriter.WriteLine(value);
}

public void WriteProperty(string propertyName, int value, bool includeEquals = true)
public void WriteProperty(string propertyName, double value, bool includeEquals = true)
{
WriteIndent();
_textWriter.Write(propertyName);
_textWriter.Write(": ");
if (includeEquals)
{
WriteIndent();
_text.Write(propertyName);
_text.Write(": ");
if (includeEquals)
{
_text.Write("=");
}
_text.WriteLine(value);
_textWriter.Write("=");
}
_textWriter.WriteLine(value);
}

public void WriteProperty(string propertyName, double value, bool includeEquals = true)
/// <summary>
/// Safely write a property. Based on the value, will chose whether single-line (and prefix with an '=')
/// or multi-line and pick the right the escape.
/// </summary>
/// <param name="propertyName"></param>
/// <param name="value"></param>
public void WriteProperty(string propertyName, string value, bool includeEquals = true)
{
if (value == null)
{
WriteIndent();
_text.Write(propertyName);
_text.Write(": ");
if (includeEquals)
{
_text.Write("=");
}
_text.WriteLine(value);
_textWriter.Write(propertyName);
_textWriter.WriteLine(":");
return;
}

/// <summary>
/// Safely write a property. Based on the value, will chose whether single-line (and prefix with an '=')
/// or multi-line and pick the right the escape.
/// </summary>
/// <param name="propertyName"></param>
/// <param name="value"></param>
public void WriteProperty(string propertyName, string value, bool includeEquals = true)
{
if (value == null)
{
WriteIndent();
_text.Write(propertyName);
_text.WriteLine(":");
return;
}
value = NormalizeNewlines(value);

value = NormalizeNewlines(value);
bool isSingleLine = value.IndexOfAny(new char[] { '#', '\n', ':' }) == -1;

bool isSingleLine = value.IndexOfAny(new char[] { '#', '\n', ':' }) == -1;
// For consistency, both single and multiline PA properties prefix with '='.
// Only single-line actually needs this - to avoid yaml's regular expression escaping.
if (includeEquals)
{
value = '=' + value;
}

// For consistency, both single and multiline PA properties prefix with '='.
// Only single-line actually needs this - to avoid yaml's regular expression escaping.
if (includeEquals)
{
value = '=' + value;
}
if (isSingleLine)
{
WriteIndent();
_textWriter.Write(propertyName);
_textWriter.Write(": ");
_textWriter.WriteLine(value);
}
else
{
WriteIndent();
_textWriter.Write(propertyName);
_textWriter.Write(": ");

if (isSingleLine)
int numNewlines = 0;
for (int i = value.Length - 1; i > 0; i--)
{
WriteIndent();
_text.Write(propertyName);
_text.Write(": ");
_text.WriteLine(value);
}
else
{
WriteIndent();
_text.Write(propertyName);
_text.Write(": ");

int numNewlines = 0;
for (int i = value.Length - 1; i > 0; i--)
if (value[i] == '\n')
{
if (value[i] == '\n')
{
numNewlines++;
}
else
{
break;
}
numNewlines++;
}
switch (numNewlines)
else
{
case 0: _text.WriteLine("|-"); break;
case 1: _text.WriteLine("|"); break;
default: _text.WriteLine("|+"); break;
break;
}
}
switch (numNewlines)
{
case 0: _textWriter.WriteLine("|-"); break;
case 1: _textWriter.WriteLine("|"); break;
default: _textWriter.WriteLine("|+"); break;
}

_currentIndent++;
_currentIndent++;

bool needIndent = true;
foreach (var ch in value)
bool needIndent = true;
foreach (var ch in value)
{
if (needIndent)
{
if (needIndent)
{
WriteIndent();
needIndent = false;
}

if (ch == '\n')
{
_text.WriteLine();
needIndent = true;
continue;
}
_text.Write(ch);
WriteIndent();
needIndent = false;
}

if (numNewlines == 0)
if (ch == '\n')
{
_text.WriteLine();
_textWriter.WriteLine();
needIndent = true;
continue;
}
_textWriter.Write(ch);
}

_currentIndent--;
if (numNewlines == 0)
{
_textWriter.WriteLine();
}

_currentIndent--;
}
}

// Write a newline, for aesthics. since this is inbetween properties,
// it should get ignored on parse.
public void WriteNewline()
{
_textWriter.WriteLine();
}

// Write a newline, for aesthics. since this is inbetween properties,
// it should get ignored on parse.
public void WriteNewline()
private void WriteIndent()
{
for (int i = 0; i < _currentIndent; i++)
{
_text.WriteLine();
_textWriter.Write(Indent);
}
}

private void WriteIndent()
private string NormalizeNewlines(string x)
{
return x.Replace("\r\n", "\n").Replace("\r", "\n");
}

#region IDisposable

protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
for (int i = 0; i < _currentIndent; i++)
if (disposing)
{
_text.Write(Indent);
_textWriter?.Dispose();
}
}

private string NormalizeNewlines(string x)
{
return x.Replace("\r\n", "\n").Replace("\r", "\n");
_isDisposed = true;
}
}

public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}

#endregion
}

0 comments on commit 4c2ca75

Please sign in to comment.