Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//******************************************************************************************************
// ToggleVisibilityAttribute.cs - Gbtc
//
// Copyright © 2026, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
// file except in compliance with the License. You may obtain a copy of the License at:
//
// http://opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 02/18/2026 - Preston Crawford
// Generated original version of source code.
//
//******************************************************************************************************

using System;

namespace Gemstone.ComponentModel.DataAnnotations;

/// <summary>
/// Specifies that a property should render a toggle switch in the UI that allows the user to disable and/or reset the properties value.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class ToggleVisibilityAttribute(string label, bool triggerValue = false) : Attribute
{
/// <summary>
/// Gets the label to display on the toggle switch.
/// </summary>
public string Label { get; } = label;

/// <summary>
/// Gets the boolean value of the toggle that triggers the hide/reset behavior.
/// </summary>
public bool TriggerValue { get; } = triggerValue;
}
7 changes: 6 additions & 1 deletion src/Gemstone/Configuration/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ public ConfigurationOperation EnvironmentalVariables
}
}

/// <summary>
/// Gets or sets the option for how to generate values in the INI file when saving settings.
/// </summary>
public INIGenerationOption INIGenerationOption { get; init; } = INIGenerationOption.UncommentedValueIfDifferent;

/// <summary>
/// Gets or sets flag that determines if description lines, e.g., those encoded into an INI file,
/// should be split into multiple lines.
Expand Down Expand Up @@ -423,7 +428,7 @@ private void SaveSections()
continue;

// Handle INI file as a special case, writing entire file contents on save
string contents = Configuration!.GenerateINIFileContents(splitDescriptionLines: SplitDescriptionLines);
string contents = Configuration!.GenerateINIFileContents(INIGenerationOption, SplitDescriptionLines);
string iniFilePath = GetINIFilePath("settings.ini", ConfiguredINIPath);
using TextWriter writer = GetINIFileWriter(iniFilePath);
writer.Write(contents);
Expand Down
132 changes: 132 additions & 0 deletions src/Gemstone/StringExtensions/StringExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2474,4 +2474,136 @@ public static string Interpolate(this string format, IEnumerable<KeyValuePair<st

return string.Format(indexed, parameterValues);
}

/// <summary>
/// Parses the string as a CSV document
/// </summary>
/// <param name="csv">The string to be parsed</param>
/// <returns>An array of rows parsed as CSV data.</returns>
/// <remarks>
/// Excel compatible parsing is used, which includes support for quoted fields and embedded commas and quotes.
/// </remarks>
public static string[][] ParseAsCSV(this string csv)
{
using StringReader reader = new(csv);
List<string[]> rows = [];

while (true)
{
string[]? row = reader.ReadCSVRow();

if (row is null)
break;

rows.Add(row);
}

return [.. rows];
}

/// <summary>
/// Reads characters from the string and returns a single row of CSV data.
/// </summary>
/// <param name="row">Row of CSV data to parse</param>
/// <returns>An array of fields in one row of CSV data or <c>null</c> if the row is empty.</returns>
/// <remarks>
/// Excel compatible parsing is used, which includes support for quoted fields and embedded commas and quotes.
/// </remarks>
public static string[]? ReadCSVRow(this string row)
{
if (string.IsNullOrEmpty(row))
return null;

using StringReader reader = new(row);
return reader.ReadCSVRow();
}

/// <summary>
/// Reads characters from the text reader and returns a single row of CSV data.
/// </summary>
/// <param name="reader">The text reader providing the CSV data</param>
/// <returns>An array of fields in one row of CSV data or <c>null</c> if there is no more data available from the text reader.</returns>
/// <remarks>
/// Excel compatible parsing is used, which includes support for quoted fields and embedded commas and quotes.
/// </remarks>
public static string[]? ReadCSVRow(this TextReader reader)
{
List<string> fields = [];
int c = reader.Read();

if (EOF())
return null;

while (!EOF() && !EOL())
{
fields.Add(Matches('"') ? ReadQuoted() : ReadToComma());

if (!Matches(','))
continue;

Advance();

// Edge case for when the last
// field in a row is empty
if (EOF() || EOL())
fields.Add(string.Empty);
}

// Advance to the next line before returning
if (Matches('\r'))
Advance();

if (Matches('\n'))
Advance();

return [.. fields];

// Gets current character and advances to read the next character
char Advance() => (char)(c, c = reader.Read()).c;

bool Matches(char m) => c == m;
bool EOL() => Matches('\r') || Matches('\n');
bool EOF() => c == -1;

string ReadToComma()
{
StringBuilder token = new();

while (!EOF() && !EOL() && !Matches(','))
token.Append(Advance());

return token.ToString();
}

string ReadQuoted()
{
StringBuilder token = new();

// Skip past the opening quote
Advance();

while (true)
{
while (!EOF() && !Matches('"'))
token.Append(Advance());

// Skip past the end quote
if (!EOF())
Advance();

// Check if it's actually an end quote vs an escaped quote
if (Matches('"'))
token.Append(Advance());
else
break;
}

// Excel treats everything after the
// end quote as if it were not quoted
if (!EOF() && !EOL() && !Matches(','))
token.Append(ReadToComma());

return token.ToString();
}
}
}
10 changes: 5 additions & 5 deletions src/Gemstone/Units/Angle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ public Angle(double value)
/// Gets the <see cref="Angle"/> value in degrees.
/// </summary>
/// <returns>Value of <see cref="Angle"/> in degrees.</returns>
public double ToDegrees()
public readonly double ToDegrees()
{
return m_value / DegreesFactor;
}
Expand All @@ -223,7 +223,7 @@ public double ToDegrees()
/// Gets the <see cref="Angle"/> value in grads.
/// </summary>
/// <returns>Value of <see cref="Angle"/> in grads.</returns>
public double ToGrads()
public readonly double ToGrads()
{
return m_value / GradsFactor;
}
Expand All @@ -232,7 +232,7 @@ public double ToGrads()
/// Gets the <see cref="Angle"/> value in arcminutes.
/// </summary>
/// <returns>Value of <see cref="Angle"/> in arcminutes.</returns>
public double ToArcMinutes()
public readonly double ToArcMinutes()
{
return m_value / ArcMinutesFactor;
}
Expand All @@ -241,7 +241,7 @@ public double ToArcMinutes()
/// Gets the <see cref="Angle"/> value in arcseconds.
/// </summary>
/// <returns>Value of <see cref="Angle"/> in arcseconds.</returns>
public double ToArcSeconds()
public readonly double ToArcSeconds()
{
return m_value / ArcSecondsFactor;
}
Expand All @@ -250,7 +250,7 @@ public double ToArcSeconds()
/// Gets the <see cref="Angle"/> value in angular mil.
/// </summary>
/// <returns>Value of <see cref="Angle"/> in angular mil.</returns>
public double ToAngularMil()
public readonly double ToAngularMil()
{
return m_value / AngularMilFactor;
}
Expand Down
Loading