diff --git a/.gitignore b/.gitignore index ae36470..0a1ff7e 100644 --- a/.gitignore +++ b/.gitignore @@ -294,3 +294,4 @@ TestResult.xml # NDepend *.ndproj /NDependOut +.vscode/ diff --git a/ConTabs.Tests/BarChartTests.cs b/ConTabs.Tests/BarChartTests.cs new file mode 100644 index 0000000..2ee3b9e --- /dev/null +++ b/ConTabs.Tests/BarChartTests.cs @@ -0,0 +1,179 @@ +using ConTabs.TestData; +using NUnit.Framework; +using Shouldly; +using System; + +namespace ConTabs.Tests +{ + [TestFixture] + public class BarChartTests + { + [Test] + public void GenerateBarChart_ResultsInNewColumnAdded() + { + // Arrange + var data = DataProvider.ListOfMinimalData(); + var table = Table.Create(data); + + // Act + table.Columns.AddBarChart("Chart", table.Columns["IntB"]); + + // Assert + table.Columns.Count.ShouldBe(3); + } + + [Test] + public void GenerateBarChart_ResultsInStringColumnAdded() + { + // Arrange + var data = DataProvider.ListOfMinimalData(); + var table = Table.Create(data); + + // Act + table.Columns.AddBarChart("Chart", table.Columns["IntB"]); + + // Assert + table.Columns[2].SourceType.ShouldBe(typeof(string)); + } + + [Test] + public void GenerateBarChart_WithDefaultParams_BarsAreExpectedSize() + { + // Arrange + var data = DataProvider.ListOfMinimalData(3); // IntB = 3, 9, 27 + var table = Table.Create(data); + + // Act + table.Columns.AddBarChart("Chart", table.Columns["IntB"]); // Max = 27, default width = 25, unit scale = 25/27 = 0.92592... + + // Assert + table.Columns[2].Values[0].ShouldBe("###"); // 0.92592 * 3 = 2.77 = 3 + table.Columns[2].Values[1].ShouldBe("########"); // 0.92592 * 9 = 8.33 = 8 + table.Columns[2].Values[2].ShouldBe("#########################"); // 0.92592 * 27 = 25.00 = 25 + } + + [Test] + public void GenerateBarChart_MaxWidthCanBeSpecified() + { + // Arrange + var data = DataProvider.ListOfMinimalData(1); // single row, IntB = 3 + var table = Table.Create(data); + + // Act + table.Columns.AddBarChart("Chart", table.Columns["IntB"], maxLength: 3); + + // Assert + table.Columns[2].Values[0].ShouldBe("###"); + } + + [Test] + public void GenerateBarChart_ForSingleValues_ResultsInFullWidthBar() + { + // Arrange + var data = DataProvider.ListOfMinimalData(1); // single row, IntB = 3 + var table = Table.Create(data); + + // Act + table.Columns.AddBarChart("Chart", table.Columns["IntB"], maxLength: 10); + + // Assert + table.Columns[2].Values[0].ShouldBe("##########"); // count = 10 = maxLength + } + + [Test] + public void GenerateBarChart_UnitCharCanBeChanged() + { + // Arrange + var data = DataProvider.ListOfMinimalData(1); // single row, IntB = 3 + var table = Table.Create(data); + + // Act + table.Columns.AddBarChart("Chart", table.Columns["IntB"], maxLength:3, unitChar: '>'); + + // Assert + table.Columns[2].Values[0].ShouldBe(">>>"); + } + + [Test] + public void GenerateBarChart_ScaleCanBeSpecified() + { + // Arrange + var data = DataProvider.ListOfMinimalData(1); // single row, IntB = 3 + var table = Table.Create(data); + + // Act + table.Columns.AddBarChart("Chart", table.Columns["IntB"], unitSize: 3); + + // Assert + table.Columns[2].Values[0].ShouldBe("#"); // one unit of size 3, for a value of 3 + } + + [Test] + public void GenerateBarChart_WhenBothScaleAndMaxWidthAreSpecified_ScaleTakesPrecedence() + { + // Arrange + var data = DataProvider.ListOfMinimalData(1); // single row, IntB = 3 + var table = Table.Create(data); + + // Act + table.Columns.AddBarChart("Chart", table.Columns["IntB"], unitSize: 3, maxLength: 9); + + // Assert + table.Columns[2].Values[0].ShouldBe("#"); // one unit of size 3, for a value of 3, rather than 100% of maxLength + } + + [Test] + public void GenerateBarChart_WhenBothScaleAndMaxWidthAreSpecified_NeverExceedsMax() + { + // Arrange + var data = DataProvider.ListOfMinimalData(3); // single row, IntB = 3 + var table = Table.Create(data); + + // Act + table.Columns.AddBarChart("Chart", table.Columns["IntB"], unitSize: 3, maxLength: 5); + + // Assert + table.Columns[2].Values[0].ShouldBe("#"); + table.Columns[2].Values[1].ShouldBe("###"); + table.Columns[2].Values[2].ShouldBe("#####"); // capped at 5 instead of value 27 / scale 3 = 9 units + } + + [Test] + public void GenerateBarChart_NegativeValuesGetHandledGracefully() + { + // Arrange + var data = DataProvider.ListOfMinimalData(1); // single row, IntB = 3 + data[0].IntB = -1; + var table = Table.Create(data); + + // Act + table.Columns.AddBarChart("Chart", table.Columns["IntB"]); + + // Assert + table.Columns[2].Values[0].ShouldBe(""); + } + + [Test] + public void GenerateBarChart_ConformanceTest() + { + // Arrange + var data = DataProvider.ListOfMinimalData(3); // IntB = 3, 9, 27 + var table = Table.Create(data); + + // Act + table.Columns.AddBarChart("Chart", table.Columns["IntB"], maxLength: 10, unitChar: '='); + + // Assert + string expected = ""; + expected += "+------+------+------------+" + Environment.NewLine; + expected += "| IntA | IntB | Chart |" + Environment.NewLine; + expected += "+------+------+------------+" + Environment.NewLine; + expected += "| 1 | 3 | = |" + Environment.NewLine; + expected += "| 2 | 9 | === |" + Environment.NewLine; + expected += "| 3 | 27 | ========== |" + Environment.NewLine; + expected += "+------+------+------------+"; + + table.ToString().ShouldBe(expected); + } + } +} \ No newline at end of file diff --git a/ConTabs.Tests/ColumnAttributeTests.cs b/ConTabs.Tests/ColumnAttributeTests.cs new file mode 100644 index 0000000..0362c35 --- /dev/null +++ b/ConTabs.Tests/ColumnAttributeTests.cs @@ -0,0 +1,93 @@ +using AutoFixture.NUnit3; +using NUnit.Framework; +using Shouldly; +using System.Collections.Generic; +using System.Linq; +using ConTabs.Attributes; +using System.Threading; +using System.Globalization; + +namespace ConTabs.Tests +{ + [TestFixture] + public class ColumnAttributeTests + { + [Test, AutoData] + public void ConTabsColumnHideAttributeShouldResultInColumnBeingHidden(List data) + { + // act + var table = Table.Create(data); + + // assert + table.Columns.Count(c => c.Hide).ShouldBe(1); + table.Columns.Count(c => !c.Hide).ShouldBe(1); + } + + [Test, AutoData] + public void ConTabsColumnPositionAttributeShouldResultInColumnBeingMoved(List data) + { + // act + var table = Table.Create(data); + + // assert + table.Columns[0].ColumnName.ShouldBe("ColumnB"); + table.Columns[1].ColumnName.ShouldBe("ColumnA"); + } + + [Test, AutoData] + public void ConTabsColumnNameAttributeShouldResultInColumnBeingRenamed(List data) + { + // act + var table = Table.Create(data); + + // assert + table.Columns[1].ColumnName.ShouldBe("ColumnX"); + } + + [Test, AutoData] + public void ConTabsColumnFormatStringAttributeShouldResultInFormatBeingApplied(List data) + { + // arrange + Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB"); + + // act + var table = Table.Create(data); + + // assert + table.Columns[1].StringValForCol(79.11d).ShouldBe("079.110"); + } + } + + + public class ClassWithOneHiddenColumn + { + public int ColumnA { get; set; } + + [ConTabsColumnHide] + public int ColumnB { get; set; } + } + + public class ClassWithReorderedColumns + { + public int ColumnA { get; set; } + + [ConTabsColumnPosition(0)] + public int ColumnB { get; set; } + } + + public class ClassWithRenamedColumns + { + public int ColumnA { get; set; } + + [ConTabsColumnName("ColumnX")] + public int ColumnB { get; set; } + } + + public class ClassWithFormattedColumns + { + public int ColumnA { get; set; } + + [ConTabsColumnFormatString("000.000")] + public double ColumnB { get; set; } + } +} diff --git a/ConTabs.Tests/ColumnTests.cs b/ConTabs.Tests/ColumnTests.cs index c8f8091..757b9bc 100644 --- a/ConTabs.Tests/ColumnTests.cs +++ b/ConTabs.Tests/ColumnTests.cs @@ -27,7 +27,7 @@ public void MaxWidthWithNullValuesReturnsColumnNameLength() var column = new Column(typeof(string), "StringColumn"); //Act - var result = column.MaxWidth; + var result = TableStretchStyles.DoNothing.CalculateOptimalWidth(column, 0); //Assert result.ShouldBe(12); @@ -41,7 +41,7 @@ public void MaxWidthWithZeroValuesReturnsColumnNameLength() column.Values = new List(); //Act - var result = column.MaxWidth; + var result = TableStretchStyles.DoNothing.CalculateOptimalWidth(column, 0); //Assert result.ShouldBe(12); @@ -57,7 +57,7 @@ public void MaxWidthWithLongStringBehaviourReturnsLongStringBehaviourWidth() column.LongStringBehaviour.Width = 15; //Act - var result = column.MaxWidth; + var result = TableStretchStyles.DoNothing.CalculateOptimalWidth(column, 0); //Assert result.ShouldBe(15); @@ -71,7 +71,7 @@ public void MaxWidthWithValuesReturnsLengthOfLongestValue() column.Values = new List() { "one", "two", "three", "four" }; //Act - var result = column.MaxWidth; + var result = TableStretchStyles.DoNothing.CalculateOptimalWidth(column, 0); //Assert result.ShouldBe(5); diff --git a/ConTabs.Tests/ConTabs.Tests.csproj b/ConTabs.Tests/ConTabs.Tests.csproj index 3b640db..2428e9f 100644 --- a/ConTabs.Tests/ConTabs.Tests.csproj +++ b/ConTabs.Tests/ConTabs.Tests.csproj @@ -1,6 +1,7 @@  - + + Debug AnyCPU @@ -9,7 +10,7 @@ Properties ConTabs.Tests ConTabs.Tests - v4.6 + v4.6.1 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15.0 @@ -39,16 +40,29 @@ 4 - - ..\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll + + ..\packages\AutoFixture.4.5.0\lib\net452\AutoFixture.dll - - ..\packages\Shouldly.2.8.3\lib\net451\Shouldly.dll + + ..\packages\AutoFixture.NUnit3.4.5.0\lib\net452\AutoFixture.NUnit3.dll + + + ..\packages\Fare.2.1.1\lib\net35\Fare.dll + + + + ..\packages\NUnit.3.11.0\lib\net45\nunit.framework.dll + + + ..\packages\Shouldly.3.0.2\lib\net451\Shouldly.dll + + + @@ -62,6 +76,7 @@ + @@ -80,6 +95,7 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + \ No newline at end of file diff --git a/ConTabs.Tests/ConformanceTests.cs b/ConTabs.Tests/ConformanceTests.cs index deca0dc..f30b41a 100644 --- a/ConTabs.Tests/ConformanceTests.cs +++ b/ConTabs.Tests/ConformanceTests.cs @@ -207,6 +207,69 @@ public void TableStyledAsHeavyShouldLookLikeThis() tableString.ShouldBe(expected); } + [Test] + public void TableStyledAsHashesShouldLookLikeThis() + { + // Arrange + var listOfTestClasses = DataProvider.ListOfMinimalData(1); + var tableObj = Table.Create(listOfTestClasses); + tableObj.TableStyle = Style.Hash; + + // Act + var tableString = tableObj.ToString(); + + // Assert + string expected = ""; + expected += "###############" + Environment.NewLine; + expected += "# IntA # IntB #" + Environment.NewLine; + expected += "###############" + Environment.NewLine; + expected += "# 1 # 3 #" + Environment.NewLine; + expected += "###############"; + tableString.ShouldBe(expected); + } + + [Test] + public void TableStyledAsPlussesShouldLookLikeThis() + { + // Arrange + var listOfTestClasses = DataProvider.ListOfMinimalData(1); + var tableObj = Table.Create(listOfTestClasses); + tableObj.TableStyle = Style.Plus; + + // Act + var tableString = tableObj.ToString(); + + // Assert + string expected = ""; + expected += "+++++++++++++++" + Environment.NewLine; + expected += "+ IntA + IntB +" + Environment.NewLine; + expected += "+++++++++++++++" + Environment.NewLine; + expected += "+ 1 + 3 +" + Environment.NewLine; + expected += "+++++++++++++++"; + tableString.ShouldBe(expected); + } + + [Test] + public void TableStyledAsWhitespaceShouldLookLikeThis() + { + // Arrange + var listOfTestClasses = DataProvider.ListOfMinimalData(1); + var tableObj = Table.Create(listOfTestClasses); + tableObj.TableStyle = Style.Whitespace; + + // Act + var tableString = tableObj.ToString(); + + // Assert + string expected = ""; + expected += " " + Environment.NewLine; + expected += " IntA IntB " + Environment.NewLine; + expected += " " + Environment.NewLine; + expected += " 1 3 " + Environment.NewLine; + expected += " "; + tableString.ShouldBe(expected); + } + [Test] public void TableWithCustomStyleShouldLookLikeThis() { @@ -317,6 +380,100 @@ public void TableStyledAsDotsShouldLookLikeThis() tableString.ShouldBe(expected); } + [Test] + public void TableStyledAsUnicodeDoubleWalledShouldLookLikeThis() + { + // Arrange + var listOfTestClasses = DataProvider.ListOfMinimalData(1); + var tableObj = Table.Create(listOfTestClasses); + tableObj.TableStyle = Style.UnicodeDoubleWalled; + + // Act + var tableString = tableObj.ToString(); + + // Assert + string expected = ""; + expected += "╓──────╥──────╖" + Environment.NewLine; + expected += "║ IntA ║ IntB ║" + Environment.NewLine; + expected += "╟──────╫──────╢" + Environment.NewLine; + expected += "║ 1 ║ 3 ║" + Environment.NewLine; + expected += "╙──────╨──────╜"; + tableString.ShouldBe(expected); + } + + [Test] + public void TableStyledAsUnicodeDoubleWalledWithExplicitPaddingShouldLookLikeThis() + { + // Arrange + var listOfTestClasses = DataProvider.ListOfMinimalData(1); + var tableObj = Table.Create(listOfTestClasses); + tableObj.TableStyle = Style.UnicodeDoubleWalled; + tableObj.Padding = new Padding(1, 2); + + // Act + var tableString = tableObj.ToString(); + + // Assert + string expected = ""; + expected += "╓────────╥────────╖" + Environment.NewLine; + expected += "║ ║ ║" + Environment.NewLine; + expected += "║ IntA ║ IntB ║" + Environment.NewLine; + expected += "║ ║ ║" + Environment.NewLine; + expected += "╟────────╫────────╢" + Environment.NewLine; + expected += "║ ║ ║" + Environment.NewLine; + expected += "║ 1 ║ 3 ║" + Environment.NewLine; + expected += "║ ║ ║" + Environment.NewLine; + expected += "╙────────╨────────╜"; + tableString.ShouldBe(expected); + } + + [Test] + public void TableStyledAsUnicodeDoubleFlooredShouldLookLikeThis() + { + // Arrange + var listOfTestClasses = DataProvider.ListOfMinimalData(1); + var tableObj = Table.Create(listOfTestClasses); + tableObj.TableStyle = Style.UnicodeDoubleFloored; + + // Act + var tableString = tableObj.ToString(); + + // Assert + string expected = ""; + expected += "╒══════╤══════╕" + Environment.NewLine; + expected += "│ IntA │ IntB │" + Environment.NewLine; + expected += "╞══════╪══════╡" + Environment.NewLine; + expected += "│ 1 │ 3 │" + Environment.NewLine; + expected += "╘══════╧══════╛"; + tableString.ShouldBe(expected); + } + + [Test] + public void TableStyledAsUnicodeDoubleFlooredWithExplicitPaddingShouldLookLikeThis() + { + // Arrange + var listOfTestClasses = DataProvider.ListOfMinimalData(1); + var tableObj = Table.Create(listOfTestClasses); + tableObj.TableStyle = Style.UnicodeDoubleFloored; + tableObj.Padding = new Padding(1, 2); + + // Act + var tableString = tableObj.ToString(); + + // Assert + string expected = ""; + expected += "╒════════╤════════╕" + Environment.NewLine; + expected += "│ │ │" + Environment.NewLine; + expected += "│ IntA │ IntB │" + Environment.NewLine; + expected += "│ │ │" + Environment.NewLine; + expected += "╞════════╪════════╡" + Environment.NewLine; + expected += "│ │ │" + Environment.NewLine; + expected += "│ 1 │ 3 │" + Environment.NewLine; + expected += "│ │ │" + Environment.NewLine; + expected += "╘════════╧════════╛"; + tableString.ShouldBe(expected); + } + [Test] public void BasicTableWithWrappedStringShouldLookLikeThis() { @@ -665,5 +822,299 @@ public void BasicTableWithCenterHeaderAlignmentShouldLookLikeThis() expected += "+---------------------------+-----------+----------------+"; tableString.ShouldBe(expected); } + + [Test] + public void BasicTableShouldByDefaultSuppressOverlappingColumn() + { + // Arrange + Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB"); + var listOfTestClasses = DataProvider.ListOfTestData(1); + var tableObj = Table.Create(listOfTestClasses); + + // Act + tableObj.CanvasWidth = 50; + var tableString = tableObj.ToString(); + + // Assert + string expected = ""; + expected += "+--------------+-----------+----------------+" + Environment.NewLine; + expected += "| StringColumn | IntColumn | CurrencyColumn |" + Environment.NewLine; + expected += "+--------------+-----------+----------------+" + Environment.NewLine; + expected += "| AAAA | 999 | 19.95 |" + Environment.NewLine; + expected += "+--------------+-----------+----------------+"; + tableString.ShouldBe(expected); + } + + [Test] + public void BasicTableShouldByDefaultSuppressOverlappingColumnButNotLeaveItAsSuppressed() + { + // Arrange + Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB"); + var listOfTestClasses = DataProvider.ListOfTestData(1); + var tableObj = Table.Create(listOfTestClasses); + + // Act + tableObj.CanvasWidth = 50; + var tableString = tableObj.ToString(); + + // Assert + tableObj.Columns[3].Suppressed.ShouldBe(false); + } + + [TestCase(0)] + [TestCase(1)] + [TestCase(2)] + public void BasicTableWithoutStretchShouldNotBeStretched(int stylesWithoutStretch) + { + // Arrange + Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB"); + var listOfTestClasses = DataProvider.ListOfTestData(1); + var tableObj = Table.Create(listOfTestClasses); + tableObj.Columns[3].Hide = true; // hide date field + + // Act + tableObj.CanvasWidth = 60; + switch (stylesWithoutStretch) + { + case 0: tableObj.TableStretchStyles = TableStretchStyles.DoNothing; break; + case 1: tableObj.TableStretchStyles = TableStretchStyles.SqueezeAllColumnsEvenly; break; + case 2: + { + tableObj.TableStretchStyles = TableStretchStyles.SqueezeLongStrings; + tableObj.Columns[0].LongStringBehaviour = LongStringBehaviour.Truncate; + tableObj.Columns[0].LongStringBehaviour.Width = 0; // autodetect width + break; + } + default: break; + } + var tableString = tableObj.ToString(); + + // Assert + string expected = ""; + expected += "+--------------+-----------+----------------+" + Environment.NewLine; + expected += "| StringColumn | IntColumn | CurrencyColumn |" + Environment.NewLine; + expected += "+--------------+-----------+----------------+" + Environment.NewLine; + expected += "| AAAA | 999 | 19.95 |" + Environment.NewLine; + expected += "+--------------+-----------+----------------+"; + tableString.ShouldBe(expected); + } + + [TestCase(0)] + [TestCase(1)] + [TestCase(2)] + [TestCase(3)] + [TestCase(4)] + [TestCase(5)] + public void BasicTableWithoutCanvasWidthShouldIgnoreTableStretchStyles(int tableStretchStyles) + { + // Arrange + Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB"); + var listOfTestClasses = DataProvider.ListOfTestData(1); + var tableObj = Table.Create(listOfTestClasses); + tableObj.Columns[3].Hide = true; // hide date field + + // Act + tableObj.CanvasWidth = 0; + switch (tableStretchStyles) + { + case 0: tableObj.TableStretchStyles = TableStretchStyles.DoNothing; break; + case 1: tableObj.TableStretchStyles = TableStretchStyles.EvenColumnWidth; break; + case 2: tableObj.TableStretchStyles = TableStretchStyles.StretchOrSqueezeAllColumnsEvenly; break; + case 3: tableObj.TableStretchStyles = TableStretchStyles.SqueezeAllColumnsEvenly; break; + case 4: tableObj.TableStretchStyles = TableStretchStyles.StretchOrSqueezeLongStrings; break; + case 5: tableObj.TableStretchStyles = TableStretchStyles.SqueezeLongStrings; break; + default: break; + } + var tableString = tableObj.ToString(); + + // Assert + string expected = ""; + expected += "+--------------+-----------+----------------+" + Environment.NewLine; + expected += "| StringColumn | IntColumn | CurrencyColumn |" + Environment.NewLine; + expected += "+--------------+-----------+----------------+" + Environment.NewLine; + expected += "| AAAA | 999 | 19.95 |" + Environment.NewLine; + expected += "+--------------+-----------+----------------+"; + tableString.ShouldBe(expected); + } + + [Test] + public void BasicTableWithEvenColumnWidthShouldLookLikeThis() + { + // Arrange + Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB"); + var listOfTestClasses = DataProvider.ListOfTestData(1); + var tableObj = Table.Create(listOfTestClasses); + tableObj.Columns[3].Hide = true; // hide date field + + // Act + tableObj.CanvasWidth = 40; + tableObj.TableStretchStyles = TableStretchStyles.EvenColumnWidth; + var tableString = tableObj.ToString(); + + // Assert + string expected = ""; + expected += "+------------+------------+------------+" + Environment.NewLine; + expected += "| StringColu | IntColumn | CurrencyCo |" + Environment.NewLine; + expected += "+------------+------------+------------+" + Environment.NewLine; + expected += "| AAAA | 999 | 19.95 |" + Environment.NewLine; + expected += "+------------+------------+------------+"; + tableString.ShouldBe(expected); + } + + [Test] + public void BasicTableWithEvenStretchShouldLookLikeThis() + { + // Arrange + Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB"); + var listOfTestClasses = DataProvider.ListOfTestData(1); + var tableObj = Table.Create(listOfTestClasses); + tableObj.Columns[3].Hide = true; // hide date field + + // Act + tableObj.CanvasWidth = 60; + tableObj.TableStretchStyles = TableStretchStyles.StretchOrSqueezeAllColumnsEvenly; + var tableString = tableObj.ToString(); + + // Assert + string expected = ""; + expected += "+-------------------+----------------+---------------------+" + Environment.NewLine; + expected += "| StringColumn | IntColumn | CurrencyColumn |" + Environment.NewLine; + expected += "+-------------------+----------------+---------------------+" + Environment.NewLine; + expected += "| AAAA | 999 | 19.95 |" + Environment.NewLine; + expected += "+-------------------+----------------+---------------------+"; + tableString.ShouldBe(expected); + } + + [TestCase(0)] + [TestCase(1)] + public void BasicTableWithEvenSqueezeShouldLookLikeThis(int evenSqueezeStretchStyles) + { + // Arrange + Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB"); + var listOfTestClasses = DataProvider.ListOfTestData(1); + var tableObj = Table.Create(listOfTestClasses); + tableObj.Columns[3].Hide = true; // hide date field + + // Act + tableObj.CanvasWidth = 40; + switch (evenSqueezeStretchStyles) + { + case 0: tableObj.TableStretchStyles = TableStretchStyles.SqueezeAllColumnsEvenly; break; + case 1: tableObj.TableStretchStyles = TableStretchStyles.StretchOrSqueezeAllColumnsEvenly; break; + default: break; + } + var tableString = tableObj.ToString(); + + // Assert + string expected = ""; + expected += "+------------+---------+---------------+" + Environment.NewLine; + expected += "| StringColu | IntColu | CurrencyColum |" + Environment.NewLine; + expected += "+------------+---------+---------------+" + Environment.NewLine; + expected += "| AAAA | 999 | 19.95 |" + Environment.NewLine; + expected += "+------------+---------+---------------+"; + tableString.ShouldBe(expected); + } + + [Test] + public void BasicTableWithLongStringStretchShouldLookLikeThis() + { + // Arrange + Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB"); + var listOfTestClasses = DataProvider.ListOfTestData(1); + var tableObj = Table.Create(listOfTestClasses); + tableObj.Columns[3].Hide = true; // hide date field + + // Act + tableObj.CanvasWidth = 60; + tableObj.TableStretchStyles = TableStretchStyles.StretchOrSqueezeLongStrings; + tableObj.Columns[0].LongStringBehaviour = LongStringBehaviour.Truncate; // to test long string stretches + var tableString = tableObj.ToString(); + + // Assert + string expected = ""; + expected += "+-----------------------------+-----------+----------------+" + Environment.NewLine; + expected += "| StringColumn | IntColumn | CurrencyColumn |" + Environment.NewLine; + expected += "+-----------------------------+-----------+----------------+" + Environment.NewLine; + expected += "| AAAA | 999 | 19.95 |" + Environment.NewLine; + expected += "+-----------------------------+-----------+----------------+"; + tableString.ShouldBe(expected); + } + + [TestCase(0)] + [TestCase(1)] + public void BasicTableWithLongStringSqueezeShouldLookLikeThis(int evenSqueezeStretchStyles) + { + // Arrange + Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB"); + var listOfTestClasses = DataProvider.ListOfTestData(1); + var tableObj = Table.Create(listOfTestClasses); + tableObj.Columns[3].Hide = true; // hide date field + + // Act + tableObj.CanvasWidth = 40; + switch (evenSqueezeStretchStyles) + { + case 0: tableObj.TableStretchStyles = TableStretchStyles.SqueezeLongStrings; break; + case 1: tableObj.TableStretchStyles = TableStretchStyles.StretchOrSqueezeLongStrings; break; + default: break; + } + tableObj.Columns[0].LongStringBehaviour = LongStringBehaviour.Truncate; // to test long string stretches + var tableString = tableObj.ToString(); + + // Assert + string expected = ""; + expected += "+---------+-----------+----------------+" + Environment.NewLine; + expected += "| StringC | IntColumn | CurrencyColumn |" + Environment.NewLine; + expected += "+---------+-----------+----------------+" + Environment.NewLine; + expected += "| AAAA | 999 | 19.95 |" + Environment.NewLine; + expected += "+---------+-----------+----------------+"; + tableString.ShouldBe(expected); + } + + [Test] + public void BasicTableWithCentralAlignmentShouldLookLikeThis() + { + // Arrange + Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB"); + var listOfTestClasses = DataProvider.ListOfTestData(1); + var tableObj = Table.Create(listOfTestClasses); + + // Act + tableObj.CanvasWidth = 50; + tableObj.TableAlignment = Alignment.Center; + var tableString = tableObj.ToString(); + + // Assert + string expected = ""; + expected += " +--------------+-----------+----------------+" + Environment.NewLine; + expected += " | StringColumn | IntColumn | CurrencyColumn |" + Environment.NewLine; + expected += " +--------------+-----------+----------------+" + Environment.NewLine; + expected += " | AAAA | 999 | 19.95 |" + Environment.NewLine; + expected += " +--------------+-----------+----------------+"; + tableString.ShouldBe(expected); + } + + [Test] + public void BasicTableWithRightAlignmentShouldLookLikeThis() + { + // Arrange + Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB"); + var listOfTestClasses = DataProvider.ListOfTestData(1); + var tableObj = Table.Create(listOfTestClasses); + + // Act + tableObj.CanvasWidth = 50; + tableObj.TableAlignment = Alignment.Right; + var tableString = tableObj.ToString(); + + // Assert + string expected = ""; + expected += " +--------------+-----------+----------------+" + Environment.NewLine; + expected += " | StringColumn | IntColumn | CurrencyColumn |" + Environment.NewLine; + expected += " +--------------+-----------+----------------+" + Environment.NewLine; + expected += " | AAAA | 999 | 19.95 |" + Environment.NewLine; + expected += " +--------------+-----------+----------------+"; + tableString.ShouldBe(expected); + } } } diff --git a/ConTabs.Tests/FormatTests.cs b/ConTabs.Tests/FormatTests.cs index 2dda40e..4397975 100644 --- a/ConTabs.Tests/FormatTests.cs +++ b/ConTabs.Tests/FormatTests.cs @@ -9,26 +9,52 @@ namespace ConTabs.Tests { class FormatTests { - [TestCase("da" , "yyyy/MM/dd", "2018-01-31")] - [TestCase("en-GB", "dd/MM/yy" , "31/01/18" )] - [TestCase("en-US", "MM/dd/yy" , "01/31/18" )] - [TestCase("sk" , "d/M/yyyy" , "31.1.2018" )] - public void DateTimeFieldCanBeFormatted(string culture, string format, string expected) + [TestCase("da-DK")] + [TestCase("en-GB")] + [TestCase("en-US")] + [TestCase("sk-SK")] + [TestCase("zh-CN")] + public void DateTimeFieldCanBeFormattedRegardlessOfCulture(string cultureName) { // Arrange - Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(culture); + var refDate = new DateTime(2018, 01, 31); + var culture = CultureInfo.CreateSpecificCulture(cultureName); + Thread.CurrentThread.CurrentCulture = culture; + + // Act var tableObj = Table.Create(); + tableObj.Columns[3].FormatString = "d-M-yyyy"; + var val = tableObj.Columns[3].StringValForCol(refDate); + + // Assert + val.ShouldBe("31-1-2018"); + } + + [TestCase("da-DK")] + [TestCase("en-GB")] + [TestCase("en-US")] + [TestCase("sk-SK")] + [TestCase("zh-CN")] + public void DateTimeFieldCanBeFormattedUsingCulture(string cultureName) + { + // Arrange + var refDate = new DateTime(2018, 01, 31); + var formatString = "dddd"; + var culture = CultureInfo.CreateSpecificCulture(cultureName); + Thread.CurrentThread.CurrentCulture = culture; + var dateString = refDate.ToString(formatString, culture); // Act - tableObj.Columns[3].FormatString = format; - var val = tableObj.Columns[3].StringValForCol(new DateTime(2018, 01, 31)); - + var tableObj = Table.Create(); + tableObj.Columns[3].FormatString = formatString; + var val = tableObj.Columns[3].StringValForCol(refDate); + // Assert - val.ShouldBe(expected); + val.ShouldBe(dateString); } [TestCase("en-GB", "£1.91")] - [TestCase("sk" , "£1,91")] + [TestCase("sk-SK", "£1,91")] public void CurrencyFieldCanBeFormatted(string culture, string expected) { // Arrange diff --git a/ConTabs.Tests/UsageTests.cs b/ConTabs.Tests/UsageTests.cs index 3dc56ae..19de0eb 100644 --- a/ConTabs.Tests/UsageTests.cs +++ b/ConTabs.Tests/UsageTests.cs @@ -23,6 +23,27 @@ public void OnceFormatSet_FormatCanBeUpdated() result[0].ShouldBe('#'); } + [Test] + public void CustomStyle_OnceSet_AffectsAllElements() + { + // Arrange + var tableObj = Table.Create(); + + // Act + tableObj.TableStyle = new Style('W','F','C'); + + // Assert + tableObj.TableStyle.Corners.CornerBottomLeft.ShouldBe('C'); + tableObj.TableStyle.Corners.CornerBottomRight.ShouldBe('C'); + tableObj.TableStyle.Corners.CornerTopLeft.ShouldBe('C'); + tableObj.TableStyle.Corners.CornerTopRight.ShouldBe('C'); + tableObj.TableStyle.Corners.Intersection.ShouldBe('C'); + tableObj.TableStyle.Corners.TeeNoDown.ShouldBe('C'); + tableObj.TableStyle.Corners.TeeNoLeft.ShouldBe('C'); + tableObj.TableStyle.Corners.TeeNoRight.ShouldBe('C'); + tableObj.TableStyle.Corners.TeeNoUp.ShouldBe('C'); + } + [Test] public void OnceFormatSet_CornerCanBeUpdated() { diff --git a/ConTabs.Tests/app.config b/ConTabs.Tests/app.config new file mode 100644 index 0000000..f9c6ff3 --- /dev/null +++ b/ConTabs.Tests/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/ConTabs.Tests/packages.config b/ConTabs.Tests/packages.config index 5176a44..f5459c2 100644 --- a/ConTabs.Tests/packages.config +++ b/ConTabs.Tests/packages.config @@ -1,15 +1,18 @@  + + - - - - - - - - - + + + + + + + + + + - + \ No newline at end of file diff --git a/ConTabs.sln b/ConTabs.sln index 15f0197..11f65f4 100644 --- a/ConTabs.sln +++ b/ConTabs.sln @@ -1,17 +1,21 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26403.7 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.32407.337 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConTabs", "ConTabs\ConTabs.csproj", "{D9A96908-33E7-4D02-B713-A894D862B5AA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConTabsDemo-DotNetCore", "ConTabsDemo-DotNetCore\ConTabsDemo-DotNetCore.csproj", "{5C495FC2-0E9A-4767-8D92-EB44D612EA12}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConTabsDemo-DotNetCore", "ConTabsDemo-DotNetCore\ConTabsDemo-DotNetCore.csproj", "{5C495FC2-0E9A-4767-8D92-EB44D612EA12}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConTabsDemo-DotNetFramework", "ConTabsDemo-DotNetFramework\ConTabsDemo-DotNetFramework.csproj", "{16584C0D-E2C3-4653-9DC6-E480660FA112}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConTabs.Tests", "ConTabs.Tests\ConTabs.Tests.csproj", "{B2347DC8-CBDE-42E3-BCA0-484DB2B3ED34}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConTabsTestData", "ConTabsTestData\ConTabsTestData.csproj", "{70B98506-64D7-4822-B461-0A6BC66A2656}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConTabsTestData", "ConTabsTestData\ConTabsTestData.csproj", "{70B98506-64D7-4822-B461-0A6BC66A2656}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConTabsDemo-Attributes", "ConTabsDemo-Attributes\ConTabsDemo-Attributes.csproj", "{A68D182D-96B2-44B4-8495-99236461FD04}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConTabsDemo-DotNet5", "ConTabsDemo-DotNet5\ConTabsDemo-DotNet5.csproj", "{DF87919E-55C0-4F6E-8FAA-6DC7A95863E2}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -39,10 +43,21 @@ Global {70B98506-64D7-4822-B461-0A6BC66A2656}.Debug|Any CPU.Build.0 = Debug|Any CPU {70B98506-64D7-4822-B461-0A6BC66A2656}.Release|Any CPU.ActiveCfg = Release|Any CPU {70B98506-64D7-4822-B461-0A6BC66A2656}.Release|Any CPU.Build.0 = Release|Any CPU + {A68D182D-96B2-44B4-8495-99236461FD04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A68D182D-96B2-44B4-8495-99236461FD04}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A68D182D-96B2-44B4-8495-99236461FD04}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A68D182D-96B2-44B4-8495-99236461FD04}.Release|Any CPU.Build.0 = Release|Any CPU + {DF87919E-55C0-4F6E-8FAA-6DC7A95863E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF87919E-55C0-4F6E-8FAA-6DC7A95863E2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF87919E-55C0-4F6E-8FAA-6DC7A95863E2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF87919E-55C0-4F6E-8FAA-6DC7A95863E2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {0E0686CF-72D2-4CF3-B98F-4C9E9D987A84} + EndGlobalSection GlobalSection(NDepend) = preSolution Project = ".\ConTabs.ndproj" EndGlobalSection diff --git a/ConTabs.sln.DotSettings b/ConTabs.sln.DotSettings new file mode 100644 index 0000000..11f2c26 --- /dev/null +++ b/ConTabs.sln.DotSettings @@ -0,0 +1,3 @@ + + <data><IncludeFilters /><ExcludeFilters /></data> + <data /> \ No newline at end of file diff --git a/ConTabs/Alignment.cs b/ConTabs/Alignment.cs index f856c22..7dde1b1 100644 --- a/ConTabs/Alignment.cs +++ b/ConTabs/Alignment.cs @@ -46,7 +46,7 @@ private static string AlignCenter(string input, int colMaxWidth) private static string GetPaddingSpaces(int amount) { - return new string(' ', amount); + return new string(' ', amount > 0 ? amount : 0); } /// @@ -61,6 +61,11 @@ public string ProcessString(string input, int colMaxWidth) { GetPaddingSpaces(colMaxWidth); } + else if (input.Length > colMaxWidth) + { + //todo: maybe default long string behavior? + input = input.Substring(0, colMaxWidth); + } return Method(input, colMaxWidth); } @@ -70,9 +75,7 @@ public string ProcessString(string input, int colMaxWidth) /// public override bool Equals(object obj) { - var comp = obj as Alignment; - - return comp != null && Method.Equals(comp.Method); + return obj is Alignment comp && Method.Equals(comp.Method); } } } \ No newline at end of file diff --git a/ConTabs/Attributes/Attributes.cs b/ConTabs/Attributes/Attributes.cs new file mode 100644 index 0000000..a24fb29 --- /dev/null +++ b/ConTabs/Attributes/Attributes.cs @@ -0,0 +1,66 @@ +using System; + +namespace ConTabs.Attributes +{ + public interface IConTabsColumnAttribute + { + void ActOnColumn(Column column); + } + + [AttributeUsage(AttributeTargets.Property)] + public class ConTabsColumnHide : Attribute, IConTabsColumnAttribute + { + public void ActOnColumn(Column column) + { + column.Hide = true; + } + } + + [AttributeUsage(AttributeTargets.Property)] + public class ConTabsColumnName : Attribute, IConTabsColumnAttribute + { + public string ColumnName { get; } + + public ConTabsColumnName(string name) + { + ColumnName = name; + } + + public void ActOnColumn(Column column) + { + column.ColumnName = ColumnName; + } + } + + [AttributeUsage(AttributeTargets.Property)] + public class ConTabsColumnPosition : Attribute, IConTabsColumnAttribute + { + public int ColumnPosition { get; } + + public ConTabsColumnPosition(int position) + { + ColumnPosition = position; + } + + public void ActOnColumn(Column column) + { + column.InitialPosition = ColumnPosition; + } + } + + [AttributeUsage(AttributeTargets.Property)] + public class ConTabsColumnFormatString : Attribute, IConTabsColumnAttribute + { + public string FormatString { get; } + + public ConTabsColumnFormatString(string formatString) + { + FormatString = formatString; + } + + public void ActOnColumn(Column column) + { + column.FormatString = FormatString; + } + } +} diff --git a/ConTabs/Column.cs b/ConTabs/Column.cs index af61cf5..9905e3a 100644 --- a/ConTabs/Column.cs +++ b/ConTabs/Column.cs @@ -3,14 +3,11 @@ using System.Diagnostics; using System.Linq; using System.Reflection; +using ConTabs.Attributes; namespace ConTabs { [DebuggerDisplay("Column for '{PropertyName}'")] - - /// - /// Acts as a column within a table - /// public class Column { /// @@ -37,6 +34,12 @@ public class Column /// A control to show/hide the column /// public bool Hide { get; set; } + + + /// + /// A control to suppress the columns if they would not fit on a canvas + /// + public bool Suppressed { get; internal set; } /// /// The method the column uses to display long strings @@ -48,28 +51,41 @@ public class Column /// public Alignment Alignment { get; set; } - private readonly MethodInfo toStringMethod; + private readonly MethodInfo _toStringMethod; + internal int? InitialPosition; /// /// A List of the values stored /// public List Values { get; set; } - public int MaxWidth - { - get - { - if (Values == null || Values.Count() == 0) return ColumnName.Length; - if (LongStringBehaviour.Width > 0) return LongStringBehaviour.Width; + /// + /// Constructor used by the main table creation process, using reflection + /// + /// Information reflected from the public property of a Table + public Column(PropertyInfo propertyInfo) + { + LongStringBehaviour = LongStringBehaviour.Default; + Alignment = Alignment.Default; + SourceType = propertyInfo.PropertyType; + PropertyName = propertyInfo.Name; + ColumnName = propertyInfo.Name; + _toStringMethod = GetToStringMethod(); + Suppressed = false; - return Values - .Select(v => StringValForCol(v)) - .Union(new List { ColumnName }) - .Select(v => v.Length) - .Max(); + // check for each of the attributes and act accordingly + var attributes = propertyInfo.GetCustomAttributes(); + foreach (var attribute in attributes) + { + if (attribute is IConTabsColumnAttribute castedAttribute) castedAttribute.ActOnColumn(this); } } + /// + /// Constructor used when adding additional columns to a table + /// + /// The Type of the data in the column + /// A name for the column (shown in the header row) public Column(Type type, string name) { LongStringBehaviour = LongStringBehaviour.Default; @@ -77,7 +93,8 @@ public Column(Type type, string name) SourceType = type; PropertyName = name; ColumnName = name; - toStringMethod = GetToStringMethod(); + _toStringMethod = GetToStringMethod(); + Suppressed = false; } public string StringValForCol(Object o) @@ -89,13 +106,13 @@ public string StringValForCol(Object o) } else { - if (toStringMethod == null) + if (_toStringMethod == null) { return (casted ?? string.Empty).ToString(); } else { - return (string)toStringMethod.Invoke(o, new object[] { FormatString }); + return (string)_toStringMethod.Invoke(o, new object[] { FormatString }); } } } diff --git a/ConTabs/Columns.cs b/ConTabs/Columns.cs index 906da4b..82e97c8 100644 --- a/ConTabs/Columns.cs +++ b/ConTabs/Columns.cs @@ -1,6 +1,7 @@ using ConTabs.Exceptions; using System; using System.Collections.Generic; +using System.Linq; namespace ConTabs { @@ -16,6 +17,11 @@ public class Columns : List public Columns(List columns) { AddRange(columns); + + if(columns.Any(c=>c.InitialPosition.HasValue)) + { + TryToSetInitialPositions(); + } } /// @@ -73,8 +79,22 @@ public void MoveColumn(string name, int newPosition) private void MoveColumn(Column col, int newPos) { - this.Remove(col); - this.Insert(newPos, col); + Remove(col); + Insert(newPos, col); + } + + private void TryToSetInitialPositions() + { + var movesToTry = this + .Where(c => c.InitialPosition.HasValue) + .Select(c => new { name = c.PropertyName, pos = c.InitialPosition.Value }) + .OrderBy(a=>a.pos); + + foreach (var moveToTry in movesToTry) + { + var pos = (moveToTry.pos >= Count) ? Count - 1 : moveToTry.pos; + MoveColumn(moveToTry.name, pos); + } } private Column FindColByName(string name) @@ -104,10 +124,10 @@ public void AddGeneratedColumn(Func expression var results = new List(); - for (int i = 0; i < column.Values.Count; i++) + for (var i = 0; i < column.Values.Count; i++) results.Add(expression((TInput)column.Values[i])); - this.Add(new Column(typeof(TOutput), columnName) { Values = results }); + Add(new Column(typeof(TOutput), columnName) { Values = results }); } /// @@ -127,7 +147,8 @@ public void AddGeneratedColumn(Func ex /// /// Adds a new column to the table of computed values. /// - /// Parameter Type + /// Parameter Type + /// Parameter Type /// Output Type /// The expression used to compute values /// The name of the new column @@ -142,10 +163,10 @@ public void AddGeneratedColumn(Func(); - for (int i = 0; i < column1.Values.Count; i++) + for (var i = 0; i < column1.Values.Count; i++) results.Add(expression((TInput1)column1.Values[i], (TInput2)column2.Values[i])); - this.Add(new Column(typeof(TOutput), columnName) { Values = results }); + Add(new Column(typeof(TOutput), columnName) { Values = results }); } /// @@ -159,7 +180,7 @@ public void AddGeneratedColumnFromRange(Func, TOutput> exp { var results = new List(); - for (int i = 0; i < columns[0].Values.Count; i++) + for (var i = 0; i < columns[0].Values.Count; i++) { var operands = new List(); @@ -169,7 +190,48 @@ public void AddGeneratedColumnFromRange(Func, TOutput> exp results.Add(expression(operands)); } - this.Add(new Column(typeof(TOutput), columnName) { Values = results }); + Add(new Column(typeof(TOutput), columnName) { Values = results }); + } + + /// + /// Adds a special bar chart column based on another numeric column. + /// + /// Type of source column (should be numeric) + /// The name of the new column + /// The column from which to derive the bar chart + /// The character used to build the bar (Optional; default = '#') + /// The value of each unit (Optional; defaults to being dynamic based on the max value) + /// The maximum width of a bar (Optional; defaults to 25) + public void AddBarChart (string columnName, Column sourceColumn, char unitChar = '#', double? unitSize = null, int maxLength = 25) + where TInput : unmanaged, IComparable, IEquatable // from https://stackoverflow.com/a/60022011/50151 (better support coming in future versions of .NET) + { + if (unitSize is null) + { + var max = sourceColumn.Values.Select(v => Convert.ToDouble(v)).Max(); + unitSize = max / maxLength; + } + + AddGeneratedColumn( + d => + { + var d_casted = Convert.ToDouble(d); + var size = (int)Math.Round(d_casted / unitSize.Value); + + if (size < 0 || d_casted < 0) + { + size = 0; + } + + if (size > maxLength) + { + size = maxLength; + } + + return new string(unitChar, size); + }, + columnName, + sourceColumn + ); } } } \ No newline at end of file diff --git a/ConTabs/ConTabs.csproj b/ConTabs/ConTabs.csproj index a20a0d6..0f85102 100644 --- a/ConTabs/ConTabs.csproj +++ b/ConTabs/ConTabs.csproj @@ -1,15 +1,15 @@  - netstandard1.3;netstandard2.0;net45 + netstandard2.0 ConTabs.tdwright - 1.3.0 tdwright + 2.0.0 + 2.0.0 Simple yet flexible ascii tables for your console output. false - This release sees the introduction of computed columns. These can be easily added to your table with values derived from values in other columns. + Version 2.0.0 of ConTabs, featuring table-level width and alignment controls Copyright 2018 (c) tdwright. All rights reserved. See license. ascii tables console - 1.3.0 https://github.com/tdwright/contabs https://github.com/tdwright/contabs/blob/master/LICENSE https://raw.githubusercontent.com/tdwright/contabs/master/RepoAssets/contabs%20logo%20small.png diff --git a/ConTabs/Corners.cs b/ConTabs/Corners.cs index a0a4769..6416a3f 100644 --- a/ConTabs/Corners.cs +++ b/ConTabs/Corners.cs @@ -2,7 +2,7 @@ { public class Corners { - private readonly char[,] cornerChars = new char[3, 3]; + private readonly char[,] _cornerChars = new char[3, 3]; /* * ╔═══╤═════╤═════╤═════╗ @@ -20,53 +20,53 @@ public class Corners /// /// The character representing the top-left corner /// - public char CornerTopLeft { get { return cornerChars[0, 0]; } set { cornerChars[0, 0] = value; } } + public char CornerTopLeft { get { return _cornerChars[0, 0]; } set { _cornerChars[0, 0] = value; } } /// /// The character representing the top-right corner /// - public char CornerTopRight { get { return cornerChars[2, 0]; } set { cornerChars[2, 0] = value; } } + public char CornerTopRight { get { return _cornerChars[2, 0]; } set { _cornerChars[2, 0] = value; } } /// /// The character representing the bottom-left corner /// - public char CornerBottomLeft { get { return cornerChars[0, 2]; } set { cornerChars[0, 2] = value; } } + public char CornerBottomLeft { get { return _cornerChars[0, 2]; } set { _cornerChars[0, 2] = value; } } /// /// The character representing the bottom-right corner /// - public char CornerBottomRight { get { return cornerChars[2, 2]; } set { cornerChars[2, 2] = value; } } + public char CornerBottomRight { get { return _cornerChars[2, 2]; } set { _cornerChars[2, 2] = value; } } /// /// The character representing intersections /// - public char Intersection { get { return cornerChars[1, 1]; } set { cornerChars[1, 1] = value; } } + public char Intersection { get { return _cornerChars[1, 1]; } set { _cornerChars[1, 1] = value; } } /// /// The character representing the intersections on the top /// - public char TeeNoUp { get { return cornerChars[1, 0]; } set { cornerChars[1, 0] = value; } } + public char TeeNoUp { get { return _cornerChars[1, 0]; } set { _cornerChars[1, 0] = value; } } /// /// The character representing the intersections on the right /// - public char TeeNoRight { get { return cornerChars[2, 1]; } set { cornerChars[2, 1] = value; } } + public char TeeNoRight { get { return _cornerChars[2, 1]; } set { _cornerChars[2, 1] = value; } } /// /// The character representing the intersections on the bottom /// - public char TeeNoDown { get { return cornerChars[1, 2]; } set { cornerChars[1, 2] = value; } } + public char TeeNoDown { get { return _cornerChars[1, 2]; } set { _cornerChars[1, 2] = value; } } /// /// The character representing the intersections on the left /// - public char TeeNoLeft { get { return cornerChars[0, 1]; } set { cornerChars[0, 1] = value; } } + public char TeeNoLeft { get { return _cornerChars[0, 1]; } set { _cornerChars[0, 1] = value; } } public char this[int i, int j] { get { - return cornerChars[i, j]; + return _cornerChars[i, j]; } } @@ -76,11 +76,11 @@ public class Corners /// The character to set all corners to public void SetAllCorners(char corner) { - for (int i = 0; i < cornerChars.GetLength(0); i++) + for (var i = 0; i < _cornerChars.GetLength(0); i++) { - for (int j = 0; j < cornerChars.GetLength(1); j++) + for (var j = 0; j < _cornerChars.GetLength(1); j++) { - cornerChars[i, j] = corner; + _cornerChars[i, j] = corner; } } } diff --git a/ConTabs/Exceptions/ColumnNotFoundException.cs b/ConTabs/Exceptions/ColumnNotFoundException.cs index d73b051..8bcd340 100644 --- a/ConTabs/Exceptions/ColumnNotFoundException.cs +++ b/ConTabs/Exceptions/ColumnNotFoundException.cs @@ -4,7 +4,7 @@ namespace ConTabs.Exceptions { public class ColumnNotFoundException : Exception { - private readonly bool _named = false; + private readonly bool _named; private readonly string _colName; private readonly int _index; diff --git a/ConTabs/LongStringBehaviour.cs b/ConTabs/LongStringBehaviour.cs index f453058..eb32b46 100644 --- a/ConTabs/LongStringBehaviour.cs +++ b/ConTabs/LongStringBehaviour.cs @@ -12,10 +12,23 @@ public class LongStringBehaviour { public Func Method { get; set; } + private int _width; /// /// The width of the string /// - public int Width { get; set; } + public int Width + { + get => _width; set + { + _width = value; + DisplayWidth = value; + } + } + + /// + /// A property to store the correct width of column to be displayed + /// + internal int DisplayWidth { get; set; } /// /// The ellipsis to use when the behvaiour is set to TruncateWithEliipsis @@ -30,7 +43,7 @@ public class LongStringBehaviour /// /// Does not interpret the string /// - public static LongStringBehaviour DoNothing => new LongStringBehaviour { Method = PassThrough }; + public static LongStringBehaviour DoNothing => new LongStringBehaviour { Method = PassThrough, Width = 0 }; /// /// Shortens the string to LongstringBehaviour.Width @@ -61,8 +74,7 @@ private static string TruncateString(string input, string ellipsis, int width) private static string WrapString(string input, string ellipsis, int width) { - if (input.Length <= width) return input; - return LongStringBehaviour.WordWrap(input, width); + return input.Length <= width ? input : WordWrap(input, width); } /// @@ -72,7 +84,7 @@ private static string WrapString(string input, string ellipsis, int width) /// A new formatted string public string ProcessString(string input) { - return Method(input, EllipsisString, Width); + return Method(input, EllipsisString, DisplayWidth); } // The following word wrapping methods inspired by an SO answer by "ICR" @@ -82,13 +94,13 @@ public string ProcessString(string input) private static string WordWrap(string str, int width) { - string[] words = Explode(str, SplitChars); + var words = Explode(str, SplitChars); - int curLineLength = 0; - StringBuilder strBuilder = new StringBuilder(); - for (int i = 0; i < words.Length; i += 1) + var curLineLength = 0; + var strBuilder = new StringBuilder(); + for (var i = 0; i < words.Length; i += 1) { - string word = words[i]; + var word = words[i]; // If adding the new word to the current line would be too long, // then put it on a new line (and split it up if it's too long). if (curLineLength + word.Length > width) @@ -124,11 +136,11 @@ private static string WordWrap(string str, int width) private static string[] Explode(string str, char[] splitChars) { - List parts = new List(); - int startIndex = 0; + var parts = new List(); + var startIndex = 0; while (true) { - int index = str.IndexOfAny(splitChars, startIndex); + var index = str.IndexOfAny(splitChars, startIndex); if (index == -1) { @@ -136,8 +148,8 @@ private static string[] Explode(string str, char[] splitChars) return parts.ToArray(); } - string word = str.Substring(startIndex, index - startIndex); - char nextChar = str.Substring(index, 1)[0]; + var word = str.Substring(startIndex, index - startIndex); + var nextChar = str.Substring(index, 1)[0]; // Dashes and the likes should stick to the word occuring before it. Whitespace doesn't have to. if (char.IsWhiteSpace(nextChar)) { diff --git a/ConTabs/OutputBuilder.cs b/ConTabs/OutputBuilder.cs index 73c8988..bac5ac7 100644 --- a/ConTabs/OutputBuilder.cs +++ b/ConTabs/OutputBuilder.cs @@ -15,6 +15,7 @@ private sealed class OutputBuilder where T2 : class private readonly StringBuilder sb; private readonly Table table; private readonly Style style; + private readonly int leftOffset; internal static string BuildOutput(Table t, Style s) { @@ -28,6 +29,19 @@ private OutputBuilder(Table t, Style s) style = s; sb = new StringBuilder(); + int tableWidth = GetOptimalTableWidth(); + + if (table.CanvasWidth > 0) + { + leftOffset = + table.TableAlignment.Equals(Alignment.Right) ? table.CanvasWidth - tableWidth : + table.TableAlignment.Equals(Alignment.Center) ? (table.CanvasWidth - tableWidth) / 2 : + 0; + + sb.Append(new string(' ', leftOffset)); + } + else leftOffset = 0; + HLine(TopMidBot.Top); InsertVerticalPadding(table.Padding.Top, style.Wall); NewLine(); Headers(); @@ -42,7 +56,7 @@ private OutputBuilder(Table t, Style s) } else { - for (int i = 0; i < table.Data.Count(); i++) + for (var i = 0; i < table.Data.Count(); i++) { InsertVerticalPadding(table.Padding.Top, style.Wall); NewLine(); DataRow(i); @@ -50,19 +64,26 @@ private OutputBuilder(Table t, Style s) InsertVerticalPadding(table.Padding.Bottom, style.Wall); NewLine(); } HLine(TopMidBot.Bot); + + //release suppressed + var suppressedCols = table.Columns.Where(c => c.Suppressed); + foreach (var col in suppressedCols) + { + col.Suppressed = false; + } } private void InsertVerticalPadding(byte padding, char columnSeparator) { - for (int paddingLevel = 0; paddingLevel < padding; paddingLevel++) + for (var paddingLevel = 0; paddingLevel < padding; paddingLevel++) { NewLine(); sb.Append(style.Wall); - for (int i = 0; i < table._colsShown.Count; i++) + for (var i = 0; i < table.ColsShown.Count; i++) { - sb.Append(new string(' ', table._colsShown[i].MaxWidth + (table.Padding.Left + table.Padding.Right))); + sb.Append(new string(' ', table.ColsShown[i].LongStringBehaviour.DisplayWidth + (table.Padding.Left + table.Padding.Right))); - if (i < table._colsShown.Count - 1) + if (i < table.ColsShown.Count - 1) { sb.Append(columnSeparator); } @@ -74,17 +95,18 @@ private void InsertVerticalPadding(byte padding, char columnSeparator) private void NewLine() { sb.Append(Environment.NewLine); + sb.Append(new string(' ', leftOffset)); } private void HLine(TopMidBot v) { sb.Append(GetCorner(v, LeftCentreRight.Left)); - for (int i = 0; i < table._colsShown.Count; i++) + for (var i = 0; i < table.ColsShown.Count; i++) { - sb.Append(new string(style.Floor, table._colsShown[i].MaxWidth + (table.Padding.Left + table.Padding.Right))); + sb.Append(new string(style.Floor, table.ColsShown[i].LongStringBehaviour.DisplayWidth + (table.Padding.Left + table.Padding.Right))); - if (i < table._colsShown.Count - 1) + if (i < table.ColsShown.Count - 1) { sb.Append(GetCorner(v, LeftCentreRight.Centre)); } @@ -95,29 +117,29 @@ private void HLine(TopMidBot v) private void NoDataLine() { var noDataText = "no data"; - int colWidths = table._colsShown.Sum(c => c.MaxWidth); - int innerWidth = colWidths + (table._colsShown.Count * (table.Padding.Left + table.Padding.Right)) + table._colsShown.Count - 1; - int leftPad = (innerWidth - noDataText.Length) / 2; - int rightPad = innerWidth - (leftPad + noDataText.Length); + var colWidths = table.ColsShown.Sum(c => c.LongStringBehaviour.DisplayWidth); + var innerWidth = colWidths + (table.ColsShown.Count * table.Padding.GetHorizontalPadding()) + table.ColsShown.Count - 1; + var leftPad = (innerWidth - noDataText.Length) / 2; + var rightPad = innerWidth - (leftPad + noDataText.Length); sb.Append(style.Wall + new String(' ', leftPad) + noDataText + new string(' ', rightPad) + style.Wall); } private void Headers() { sb.Append(style.Wall); - foreach (var col in table._colsShown) + foreach (var col in table.ColsShown) { - sb.Append(GetPaddingString(table.Padding.Left) + table.HeaderAlignment.ProcessString(col.ColumnName, col.MaxWidth) + GetPaddingString(table.Padding.Right) + style.Wall); + sb.Append(GetPaddingString(table.Padding.Left) + table.HeaderAlignment.ProcessString(col.ColumnName, col.LongStringBehaviour.DisplayWidth) + GetPaddingString(table.Padding.Right) + style.Wall); } } private void DataRow(int i) { - var cols = table._colsShown.Select(c => new CellParts(c.StringValForCol(c.Values[i]), c.MaxWidth, c.Alignment)).ToList(); + var cols = table.ColsShown.Select(c => new CellParts(c.StringValForCol(c.Values[i]), c.LongStringBehaviour.DisplayWidth, c.Alignment)).ToList(); var maxLines = cols.Max(c => c.LineCount); - for (int j = 0; j < maxLines; j++) + for (var j = 0; j < maxLines; j++) { DataLine(cols, j); if (j != maxLines - 1) @@ -132,7 +154,7 @@ private void DataLine(List parts, int line) sb.Append(style.Wall); foreach (var part in parts) { - string val = part.GetLine(line); + var val = part.GetLine(line); sb.Append(GetPaddingString(table.Padding.Left) + part.Alignment.ProcessString(val, part.ColMaxWidth) + GetPaddingString(table.Padding.Right) @@ -140,6 +162,46 @@ private void DataLine(List parts, int line) } } + private int GetWidthOfPaddingAndBorders() + { + return table.ColsShown.Count * table.Padding.GetHorizontalPadding() + table.ColsShown.Count + 1; + } + + private int GetTableWidthAfterApplyingStretchStyles() + { + int colWidths = 0; + foreach (Column column in table.ColsShown) + { + int colWidth = table.TableStretchStyles.CalculateOptimalWidth(column, table.CanvasWidth); + column.LongStringBehaviour.DisplayWidth = colWidth; + colWidths += colWidth; + } + int realWidth = colWidths + GetWidthOfPaddingAndBorders(); + int adaptedWidth = table.TableStretchStyles.CalculateAdditionalWidth(table.ColsShown, realWidth, table.CanvasWidth); + return adaptedWidth + GetWidthOfPaddingAndBorders(); + } + + private int GetOptimalTableWidth() + { + int tableWidth = GetTableWidthAfterApplyingStretchStyles(); + + bool recalculationNeeded = false; + + while (table.CanvasWidth > 0 && table.CanvasWidth < tableWidth && table.ColsShown.Count > 0) + { + var columnToSuppress = table.ColsShown[table.ColsShown.Count - 1]; + tableWidth -= columnToSuppress.LongStringBehaviour.DisplayWidth + table.Padding.GetHorizontalPadding() + 1; + columnToSuppress.Suppressed = true; + recalculationNeeded = true; + } + + if (recalculationNeeded) //if we hid some of the columns, we should adapt the display widths again + { + tableWidth = GetTableWidthAfterApplyingStretchStyles(); + } + return tableWidth; + } + private enum TopMidBot { Top, diff --git a/ConTabs/Padding.cs b/ConTabs/Padding.cs index 8136a89..9837716 100644 --- a/ConTabs/Padding.cs +++ b/ConTabs/Padding.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace ConTabs +namespace ConTabs { public class Padding { @@ -65,5 +61,13 @@ public Padding(byte top = 0, byte right = 1, byte bottom = 0, byte left = 1) Bottom = bottom; Left = left; } + + /// + /// Returns total horizontal padding + /// + public int GetHorizontalPadding() + { + return Left + Right; + } } } \ No newline at end of file diff --git a/ConTabs/Styles.cs b/ConTabs/Styles.cs index b939308..e92d17c 100644 --- a/ConTabs/Styles.cs +++ b/ConTabs/Styles.cs @@ -1,6 +1,4 @@ -using System; - -namespace ConTabs +namespace ConTabs { /// /// The properties used to define a table's visual styling @@ -60,6 +58,11 @@ public Style(char wall, char floor, Corners corners) /// public static Style Heavy => new Style('#', '=', '#'); + /// + /// Built-in style + /// + public static Style Whitespace => new Style(' ', ' ', ' '); + /// /// Built-in style /// @@ -78,6 +81,17 @@ public Style(char wall, char floor, Corners corners) TeeNoRight = '╣' }); + /// + /// Built-in style + /// + public static Style Hash => new Style('#', '#', '#'); + + + /// + /// Built-in style + /// + public static Style Plus => new Style('+', '+', '+'); + /// /// Built-in style /// @@ -130,47 +144,40 @@ public Style(char wall, char floor, Corners corners) TeeNoRight = ':' }); - // Deprecated setters/getters - [Obsolete("Use Corners.CornerTopLeft")] - public char CornerTopLeft { get { return Corners[0, 0]; } set { Corners.CornerTopLeft = value; } } - [Obsolete("Use Corners.CornerTopRight")] - public char CornerTopRight { get { return Corners[2, 0]; } set { Corners.CornerTopRight = value; } } - [Obsolete("Use Corners.CornerBottomLeft")] - public char CornerBottomLeft { get { return Corners[0, 2]; } set { Corners.CornerBottomLeft = value; } } - [Obsolete("Use Corners.CornerBottomRight")] - public char CornerBottomRight { get { return Corners[2, 2]; } set { Corners.CornerBottomRight = value; } } - [Obsolete("Use Corners.Intersection")] - public char Intersection { get { return Corners[1, 1]; } set { Corners.Intersection = value; } } - [Obsolete("Use Corners.TeeNoUp")] - public char TeeNoUp { get { return Corners[1, 0]; } set { Corners.TeeNoUp = value; } } - [Obsolete("Use Corners.TeeNoRight")] - public char TeeNoRight { get { return Corners[2, 1]; } set { Corners.TeeNoRight = value; } } - [Obsolete("Use Corners.TeeNoDown")] - public char TeeNoDown { get { return Corners[1, 2]; } set { Corners.TeeNoDown = value; } } - [Obsolete("Use Corners.TeeNoLeft")] - public char TeeNoLeft { get { return Corners[0, 1]; } set { Corners.TeeNoLeft = value; } } - - // Old constructor with too many params - [Obsolete] - - /// - /// Creates a new style using the given characters - /// - public Style(char wall, char floor, char tl, char tr, char bl, char br, char i, char tnu, char tnr, char tnd, char tnl) + /// + /// Built-in style + /// + /// *May require Console.OutputEncoding = Encoding.Unicode; + /// + public static Style UnicodeDoubleWalled => new Style('║', '─', new Corners { - Wall = wall; - Floor = floor; - - Corners.CornerTopLeft = tl; - Corners.CornerTopRight = tr; - Corners.CornerBottomLeft = bl; - Corners.CornerBottomRight = br; - Corners.Intersection = i; - Corners.TeeNoUp = tnu; - Corners.TeeNoRight = tnr; - Corners.TeeNoDown = tnd; - Corners.TeeNoLeft = tnl; - } + CornerTopLeft = '╓', + CornerTopRight = '╖', + CornerBottomLeft = '╙', + CornerBottomRight = '╜', + Intersection = '╫', + TeeNoUp = '╥', + TeeNoLeft = '╟', + TeeNoDown = '╨', + TeeNoRight = '╢' + }); + /// + /// Built-in style + /// + /// *May require Console.OutputEncoding = Encoding.Unicode; + /// + public static Style UnicodeDoubleFloored => new Style('│', '═', new Corners + { + CornerTopLeft = '╒', + CornerTopRight = '╕', + CornerBottomLeft = '╘', + CornerBottomRight = '╛', + Intersection = '╪', + TeeNoUp = '╤', + TeeNoLeft = '╞', + TeeNoDown = '╧', + TeeNoRight = '╡' + }); } } \ No newline at end of file diff --git a/ConTabs/Table.cs b/ConTabs/Table.cs index 07c95ed..464834e 100644 --- a/ConTabs/Table.cs +++ b/ConTabs/Table.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Reflection; using ConTabs.Exceptions; @@ -8,10 +7,6 @@ namespace ConTabs { [DebuggerDisplay("Table with {Columns.Count} available columns")] - - /// - /// A static class used to create new tables. - /// public sealed partial class Table where T : class { /// @@ -22,35 +17,29 @@ public sealed partial class Table where T : class /// /// The collection of columns within the table /// - public Columns Columns { get; set; } + public Columns Columns { get; } /// /// The horizontal alignment of the column titles /// public Alignment HeaderAlignment { get; set; } - private Alignment _columnAlignment { get; set; } + private Alignment _columnAlignment; /// /// The horizontal alignment of the cell values /// public Alignment ColumnAlignment { - get - { - return _columnAlignment; - } + get => _columnAlignment; set { _columnAlignment = value; - if (Columns != null) - { - Columns.ForEach(c => c.Alignment = _columnAlignment); - } + Columns?.ForEach(c => c.Alignment = _columnAlignment); } } - internal List _colsShown => Columns.Where(c => !c.Hide).ToList(); + private List ColsShown => Columns.Where(c => !(c.Hide || c.Suppressed)).ToList(); /// /// The table's display properties @@ -58,27 +47,40 @@ public Alignment ColumnAlignment public Style TableStyle { get; set; } private IEnumerable _data; - public IEnumerable Data + + private IEnumerable Data { - get - { - return _data; - } + get => _data; set { _data = value; foreach (var col in Columns) { - col.Values = _data.Select(d => + col.Values = _data.ToList().Select(d => { - Type t = typeof(T); - PropertyInfo p = t.GetRuntimeProperty(col.PropertyName); + var t = typeof(T); + var p = t.GetRuntimeProperty(col.PropertyName); return p.GetValue(d); }).ToList(); } } } + /// + /// Width of canvas where table will be outputted + /// + public int CanvasWidth { get; set; } + + /// + /// Alignment of the table in the console. Ignored if CanvasWidth is zero or negative. + /// + public Alignment TableAlignment { get; set; } + + /// + /// Stretching or fitting tables in the console. Uses DoNothing if CanvasWidth is zero or negative. + /// + public TableStretchStyles TableStretchStyles { get; set; } + /// /// Creates an empty table /// @@ -91,13 +93,13 @@ public static Table Create() /// /// Creates a new table /// - /// The collection of objects to place into the table + /// The collection of objects to place into the table /// A new table - public static Table Create(IEnumerable Source) + public static Table Create(IEnumerable source) { return new Table() { - Data = Source + Data = source }; } @@ -110,11 +112,14 @@ private Table() TableStyle = Style.Default; HeaderAlignment = Alignment.Default; ColumnAlignment = Alignment.Default; + TableAlignment = Alignment.Default; + CanvasWidth = 0; + TableStretchStyles = TableStretchStyles.Default; var props = GetDeclaredAndInheritedProperties(typeof(T).GetTypeInfo()); var cols = props .Where(p => p.GetMethod.IsPublic) - .Select(p => new Column(p.PropertyType, p.Name)) + .Select(p => new Column(p)) .ToList(); Columns = new Columns(cols); @@ -125,10 +130,11 @@ private Table() /// /// Formats the table for output /// + /// /// The table as a string public override string ToString() { - if (_colsShown.Count == 0) + if (ColsShown != null && ColsShown.Count == 0) throw new EmptyTableException(this.GetType()); return OutputBuilder.BuildOutput(this, TableStyle); diff --git a/ConTabs/TableStretchStyles.cs b/ConTabs/TableStretchStyles.cs new file mode 100644 index 0000000..81be3cb --- /dev/null +++ b/ConTabs/TableStretchStyles.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace ConTabs +{ + /// + /// Defines how should table be stretched / squeezed on a canvas + /// + public class TableStretchStyles + { + private static readonly int MIN_WIDTH = 2; //if set to 1, word wrap in long strings would fail + + public Func CalculateOptimalWidth { get; set; } + public Func, int, int, int> CalculateAdditionalWidth { get; set; } + + /// + /// Does not stretch / squeeze the table + /// + public static TableStretchStyles Default => DoNothing; + + /// + /// Does not stretch / squeeze the table + /// + public static TableStretchStyles DoNothing => new TableStretchStyles { CalculateOptimalWidth = GetOptimalColumnWidth, CalculateAdditionalWidth = UseDefaultDisplayWidths }; + + /// + /// Sets all columns to the same width (approximately) + /// + public static TableStretchStyles EvenColumnWidth => new TableStretchStyles { CalculateOptimalWidth = GetUniformColumnWidth, CalculateAdditionalWidth = StretchOrSqueezeDisplayWidths }; + + /// + /// Stretches / squeezes all columns by approximately the same width + /// + public static TableStretchStyles StretchOrSqueezeAllColumnsEvenly => new TableStretchStyles { CalculateOptimalWidth = GetOptimalColumnWidth, CalculateAdditionalWidth = StretchOrSqueezeDisplayWidths }; + + /// + /// Stretches / squeezes all columns by approximately the same width + /// + public static TableStretchStyles SqueezeAllColumnsEvenly => new TableStretchStyles { CalculateOptimalWidth = GetOptimalColumnWidth, CalculateAdditionalWidth = SqueezeDisplayWidths }; + + /// + /// Stretches / squeezes long strings by approximately the same width + /// + public static TableStretchStyles StretchOrSqueezeLongStrings => new TableStretchStyles { CalculateOptimalWidth = GetOptimalColumnWidth, CalculateAdditionalWidth = StretchOrSqueezeLongStringDisplayWidths }; + + /// + /// Stretches / squeezes long strings by approximately the same width + /// + public static TableStretchStyles SqueezeLongStrings => new TableStretchStyles { CalculateOptimalWidth = GetOptimalColumnWidth, CalculateAdditionalWidth = SqueezeLongStringDisplayWidths }; + + private static int UseDefaultDisplayWidths(List columns, int totalWidth, int canvasWidth) + { + for (int i = 0; i < columns.Count; i++) + { + columns[i].LongStringBehaviour.DisplayWidth = GetOptimalColumnWidth(columns[i], canvasWidth); + } + return columns + .Select(v => v.LongStringBehaviour.DisplayWidth) + .Sum(); + } + + private static int StretchOrSqueezeDisplayWidths(List columns, int totalWidth, int canvasWidth) + { + int difference = canvasWidth > 0 ? canvasWidth - totalWidth : 0; + if (difference > 0) + { + for (int i = 0; i < columns.Count; i++) + { + columns[i].LongStringBehaviour.DisplayWidth += difference / columns.Count; + if (i < difference % columns.Count) + { + columns[i].LongStringBehaviour.DisplayWidth++; + } + } + } + else if (difference < 0) + { + List squeezableColumns = columns.Where(c => c.LongStringBehaviour.DisplayWidth > MIN_WIDTH).ToList(); + var columnIndex = 0; + while (difference < 0 && squeezableColumns.Count > 0) + { + squeezableColumns[columnIndex].LongStringBehaviour.DisplayWidth--; + difference++; + if (squeezableColumns[columnIndex].LongStringBehaviour.DisplayWidth == MIN_WIDTH) + { + squeezableColumns.RemoveAt(columnIndex); + } + else + { + columnIndex++; + } + if (columnIndex >= squeezableColumns.Count) columnIndex = 0; + } + } + return columns + .Select(v => v.LongStringBehaviour.DisplayWidth) + .Sum(); + } + + private static int StretchOrSqueezeLongStringDisplayWidths(List columns, int totalWidth, int canvasWidth) + { + var longStringColumns = columns.Where(c => c.LongStringBehaviour.Width > 0).ToList(); + StretchOrSqueezeDisplayWidths(longStringColumns, totalWidth, canvasWidth); + return columns + .Select(v => v.LongStringBehaviour.DisplayWidth) + .Sum(); + } + + private static int SqueezeLongStringDisplayWidths(List columns, int totalWidth, int canvasWidth) + { + var longStringColumns = columns.Where(c => c.LongStringBehaviour.Width > 0).ToList(); + SqueezeDisplayWidths(longStringColumns, totalWidth, canvasWidth); + return columns + .Select(v => v.LongStringBehaviour.DisplayWidth) + .Sum(); + } + + private static int SqueezeDisplayWidths(List columns, int totalWidth, int canvasWidth) + { + int difference = canvasWidth > 0 ? canvasWidth - totalWidth : 0; + Console.WriteLine(difference); + return difference >= 0 + ? UseDefaultDisplayWidths(columns, totalWidth, canvasWidth) + : StretchOrSqueezeDisplayWidths(columns, totalWidth, canvasWidth); + } + + private static int GetOptimalColumnWidth(Column column, int canvasWidth) + { + if (column.Values == null || column.Values.Count == 0) return column.ColumnName.Length; + + if (column.LongStringBehaviour.Width > 0) return column.LongStringBehaviour.Width; + + return column.Values + .Select(v => column.StringValForCol(v)) + .Union(new List { column.ColumnName }) + .Select(v => v.Length) + .Max(); + } + + private static int GetUniformColumnWidth(Column column, int canvasWidth) + { + return canvasWidth > 0 ? byte.MinValue : GetOptimalColumnWidth(column, canvasWidth); + } + } +} diff --git a/ConTabsDemo-Attributes/ConTabsDemo-Attributes.csproj b/ConTabsDemo-Attributes/ConTabsDemo-Attributes.csproj new file mode 100644 index 0000000..5345df6 --- /dev/null +++ b/ConTabsDemo-Attributes/ConTabsDemo-Attributes.csproj @@ -0,0 +1,13 @@ + + + + Exe + netcoreapp2.1 + ConTabsDemo_Attributes + + + + + + + diff --git a/ConTabsDemo-Attributes/Data.cs b/ConTabsDemo-Attributes/Data.cs new file mode 100644 index 0000000..7c55d64 --- /dev/null +++ b/ConTabsDemo-Attributes/Data.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using ConTabs.Attributes; + +namespace ConTabsDemo_Attributes +{ + public static class DemoDataProvider + { + public static List ListOfDemoData() + { + return new List + { + new Planet{Name="Mercury", DistanceFromSun=57909227, OrbitalPeriod=88F, Diameter=4879, Fact="Mercury is the smallest planet."}, + new Planet{Name="Venus", DistanceFromSun=108209475, OrbitalPeriod=225F, Diameter=12104, Fact="Venus is the hottest planet."}, + new Planet{Name="Earth", DistanceFromSun=149598262, OrbitalPeriod=365.24F, Diameter=12742, Fact="Earth is where we live"}, + new Planet{Name="Mars", DistanceFromSun=227943824, OrbitalPeriod=693.5F, Diameter=6779, Fact="Mars is also often described as the “Red Planet” due to its reddish appearance."}, + new Planet{Name="Jupiter", DistanceFromSun=778340821, OrbitalPeriod=4343.5F, Diameter=139822, Fact="Jupiter is the largest planet."}, + new Planet{Name="Saturn", DistanceFromSun=1426666422, OrbitalPeriod=10767.5F, Diameter=116464, Fact="Saturn is best known for its fabulous ring system that was first observed in 1610 by the astronomer Galileo Galilei."}, + new Planet{Name="Uranus", DistanceFromSun=2870658186, OrbitalPeriod=30660F, Diameter=50724, Fact="Uranus became the first planet discovered with the use of a telescope."}, + new Planet{Name="Neptune", DistanceFromSun=4498396441, OrbitalPeriod=60152F, Diameter=49244, Fact="On average Neptune is the coldest planet"} + }; + } + } + + public class Planet + { + public string Name { get; set; } + + [ConTabsColumnPosition(3)] + [ConTabsColumnFormatString("###,###,###0 km")] + public long DistanceFromSun { get; set; } + + public int Diameter { get; set; } + + [ConTabsColumnName("Year length")] + public float OrbitalPeriod { get; set; } + + [ConTabsColumnHide()] + public string Fact { get; set; } + } +} diff --git a/ConTabsDemo-Attributes/Program.cs b/ConTabsDemo-Attributes/Program.cs new file mode 100644 index 0000000..0df9d62 --- /dev/null +++ b/ConTabsDemo-Attributes/Program.cs @@ -0,0 +1,22 @@ +using ConTabs; +using System; + +namespace ConTabsDemo_Attributes +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("CONTABS ATTRIBUTE DEMO"); + + // Get some data (can be an IEnumerable of anything) + var Data = DemoDataProvider.ListOfDemoData(); + + // Write it to the console (in one line!) + Console.WriteLine(Table.Create(Data)); + + Console.WriteLine("Press return to exit..."); + Console.ReadLine(); + } + } +} diff --git a/ConTabsDemo-DotNet5/ConTabsDemo-DotNet5.csproj b/ConTabsDemo-DotNet5/ConTabsDemo-DotNet5.csproj new file mode 100644 index 0000000..1e563f7 --- /dev/null +++ b/ConTabsDemo-DotNet5/ConTabsDemo-DotNet5.csproj @@ -0,0 +1,14 @@ + + + + Exe + net5.0 + ConTabsDemo_DotNet5 + + + + + + + + diff --git a/ConTabsDemo-DotNet5/Program.cs b/ConTabsDemo-DotNet5/Program.cs new file mode 100644 index 0000000..0f6cac1 --- /dev/null +++ b/ConTabsDemo-DotNet5/Program.cs @@ -0,0 +1,71 @@ +using System; +using ConTabs; +using ConTabs.TestData; +using System.Text; + +namespace ConTabsDemo_DotNetFramework +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("CONTABS .NET 5 DEMO"); + + // Get some data (can be an IEnumerable of anything) + var Data = DemoDataProvider.ListOfDemoData(); + + // Create a table object + var table = Table.Create(Data); + + /* + * + * Everything that follows is optional. + * You could just skip to a Console.Writeline here. + * + */ + + // Set the style (and enable Unicode for the console) + table.TableStyle = Style.UnicodeArcs; + Console.OutputEncoding = Encoding.Unicode; + + // Hide the diameter column + table.Columns["Diameter"].Hide = true; + + // Add a computed column for the radius + table.Columns.AddGeneratedColumn(d => d / 2, "Radius", table.Columns["Diameter"]); + + // Move the orbital period column next to the name + table.Columns.MoveColumn("OrbitalPeriod", 1); + + // Rename the orbital period column + table.Columns["OrbitalPeriod"].ColumnName = "Year length"; + + // Add a format string to orbital period + table.Columns["Year length"].FormatString = "# days"; + + // Right-align the distance from sun column (and format it nicely) + table.Columns["DistanceFromSun"].Alignment = Alignment.Right; + table.Columns["DistanceFromSun"].FormatString = "###,###,###0 km"; + + // Handle the length of the fact + table.Columns["Fact"].LongStringBehaviour = LongStringBehaviour.Wrap; + table.Columns["Fact"].LongStringBehaviour.Width = 25; + + // Add some padding + table.Padding = new Padding(1, 1); + + // Stretch long string columns to fit console + table.CanvasWidth = Console.WindowWidth - 1; + table.TableStretchStyles = TableStretchStyles.StretchOrSqueezeLongStrings; + + // Center the outputted table + table.TableAlignment = Alignment.Center; + + // Finally, spit out the finished table + Console.WriteLine(table); + + Console.WriteLine("Press return to exit..."); + Console.ReadLine(); + } + } +} \ No newline at end of file diff --git a/ConTabsDemo-DotNetCore/ConTabsDemo-DotNetCore.csproj b/ConTabsDemo-DotNetCore/ConTabsDemo-DotNetCore.csproj index 4f2912d..257e410 100644 --- a/ConTabsDemo-DotNetCore/ConTabsDemo-DotNetCore.csproj +++ b/ConTabsDemo-DotNetCore/ConTabsDemo-DotNetCore.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp1.1 + netcoreapp2.1 diff --git a/ConTabsDemo-DotNetCore/Program.cs b/ConTabsDemo-DotNetCore/Program.cs index 85b8e1f..c13c16f 100644 --- a/ConTabsDemo-DotNetCore/Program.cs +++ b/ConTabsDemo-DotNetCore/Program.cs @@ -54,6 +54,13 @@ static void Main(string[] args) // Add some padding table.Padding = new Padding(1, 1); + // Stretch long string columns to fit console + table.CanvasWidth = Console.WindowWidth - 1; + table.TableStretchStyles = TableStretchStyles.StretchOrSqueezeLongStrings; + + // Center the outputted table + table.TableAlignment = Alignment.Center; + // Finally, spit out the finished table Console.WriteLine(table); diff --git a/ConTabsDemo-DotNetFramework/ConTabsDemo-DotNetFramework.csproj b/ConTabsDemo-DotNetFramework/ConTabsDemo-DotNetFramework.csproj index c856c9d..f6be1f5 100644 --- a/ConTabsDemo-DotNetFramework/ConTabsDemo-DotNetFramework.csproj +++ b/ConTabsDemo-DotNetFramework/ConTabsDemo-DotNetFramework.csproj @@ -8,7 +8,7 @@ Exe ConTabsDemo_DotNetFramework ConTabsDemo-DotNetFramework - v4.6 + v4.6.1 512 true diff --git a/ConTabsDemo-DotNetFramework/Program.cs b/ConTabsDemo-DotNetFramework/Program.cs index f286182..1258b18 100644 --- a/ConTabsDemo-DotNetFramework/Program.cs +++ b/ConTabsDemo-DotNetFramework/Program.cs @@ -16,7 +16,7 @@ static void Main(string[] args) // Create a table object var table = Table.Create(Data); - + /* * * Everything that follows is optional. @@ -25,7 +25,7 @@ static void Main(string[] args) */ // Set the style (and enable Unicode for the console - table.TableStyle = Style.UnicodePipes; + table.TableStyle = Style.Hash; Console.OutputEncoding = Encoding.Unicode; // Hide the diameter column @@ -53,7 +53,14 @@ static void Main(string[] args) // Add some padding table.Padding = new Padding(1, 1); - + + // Stretch long string columns to fit console + table.CanvasWidth = Console.WindowWidth - 1; + table.TableStretchStyles = TableStretchStyles.StretchOrSqueezeLongStrings; + + // Center the outputted table + table.TableAlignment = Alignment.Center; + // Finally, spit out the finished table Console.WriteLine(table); @@ -61,4 +68,4 @@ static void Main(string[] args) Console.ReadLine(); } } -} +} \ No newline at end of file diff --git a/ConTabsTestData/ConTabsTestData.csproj b/ConTabsTestData/ConTabsTestData.csproj index 1536775..eb7e066 100644 --- a/ConTabsTestData/ConTabsTestData.csproj +++ b/ConTabsTestData/ConTabsTestData.csproj @@ -1,7 +1,7 @@  - netstandard1.0 + netstandard2.0 \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index d03bbb6..9bf8baf 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ version: 1.0.{build} -image: Visual Studio 2017 +image: Visual Studio 2019 Preview before_build: - cmd: nuget restore @@ -11,10 +11,15 @@ environment: build: project: ConTabs.sln verbosity: minimal + +test: + assemblies: + only: + - ConTabs.Tests.dll after_test: # Tell OpenCover to generate coverage.xml - - cmd: packages\OpenCover.4.6.519\tools\OpenCover.Console.exe -register:user -filter:"+[ConTabs]*" -target:"packages\NUnit.ConsoleRunner.3.7.0\tools\nunit3-console.exe" -targetargs:"/domain:single ConTabs.Tests/bin/debug/ConTabs.Tests.dll" -output:coverage.xml -excludebyattribute:"System.ObsoleteAttribute" + - cmd: packages\OpenCover.4.6.519\tools\OpenCover.Console.exe -register:user -filter:"+[ConTabs]*" -target:"packages\NUnit.ConsoleRunner.3.9.0\tools\nunit3-console.exe" -targetargs:"/domain:single ConTabs.Tests/bin/debug/ConTabs.Tests.dll" -output:coverage.xml -excludebyattribute:"System.ObsoleteAttribute" # Send coverage.xml to Coveralls - cmd: packages\coveralls.io.1.4.2\tools\coveralls.net.exe --opencover coverage.xml -r %COVERALLS_REPO_TOKEN%