-
Notifications
You must be signed in to change notification settings - Fork 112
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
159 additions
and
136 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |