From 308b25ca026500bda4652f8c859c18db2be82299 Mon Sep 17 00:00:00 2001 From: Alberto Spelta Date: Sun, 26 Jan 2025 11:36:37 +0100 Subject: [PATCH] Refactor `browse` and `package` command args for CLI tool (#194) * Update command descriptions to use singular form Updated the description text in `BrowseColumnCommand.cs` and `BrowseRelationshipCommand.cs` to use the singular form of "column" and "relationship" for improved clarity and accuracy. --- .../Commands/Browse/BrowseColumnCommand.cs | 21 +++- .../Browse/BrowseColumnCommandHandler.cs | 113 +++++++++++------- .../Commands/Browse/BrowseCommand.cs | 5 - .../Commands/Browse/BrowseModelCommand.cs | 2 + .../Browse/BrowseModelCommandHandler.cs | 41 +++---- .../Browse/BrowseRelationshipCommand.cs | 11 +- .../BrowseRelationshipCommandHandler.cs | 75 +++++++----- .../Commands/Browse/BrowseTableCommand.cs | 9 +- .../Browse/BrowseTableCommandHandler.cs | 100 ++++++++++------ .../Commands/Browse/CommonOptions.cs | 34 ++++-- src/Dax.Vpax.CLI/Commands/CommandHandler.cs | 36 ++---- src/Dax.Vpax.CLI/Commands/CommonOptions.cs | 9 -- .../Commands/Export/ExportCommand.cs | 8 +- .../Commands/Export/ExportCommandOptions.cs | 46 +++---- .../Commands/Package/PackageCommand.cs | 2 - .../Commands/Package/PackageExtractCommand.cs | 25 ++-- .../Package/PackageExtractCommandHandler.cs | 30 +++-- .../Commands/Package/PackageSetCommand.cs | 22 ---- .../Package/PackageSetCommandHandler.cs | 23 ---- .../Commands/Package/PackageShowCommand.cs | 17 ++- .../Package/PackageShowCommandHandler.cs | 10 +- .../Commands/Package/PackageUnsetCommand.cs | 12 -- .../Package/PackageUnsetCommandHandler.cs | 13 -- src/Dax.Vpax.CLI/GlobalSuppressions.cs | 26 ++++ src/Dax.Vpax.CLI/UserSession.cs | 35 ------ 25 files changed, 377 insertions(+), 348 deletions(-) delete mode 100644 src/Dax.Vpax.CLI/Commands/CommonOptions.cs delete mode 100644 src/Dax.Vpax.CLI/Commands/Package/PackageSetCommand.cs delete mode 100644 src/Dax.Vpax.CLI/Commands/Package/PackageSetCommandHandler.cs delete mode 100644 src/Dax.Vpax.CLI/Commands/Package/PackageUnsetCommand.cs delete mode 100644 src/Dax.Vpax.CLI/Commands/Package/PackageUnsetCommandHandler.cs create mode 100644 src/Dax.Vpax.CLI/GlobalSuppressions.cs delete mode 100644 src/Dax.Vpax.CLI/UserSession.cs diff --git a/src/Dax.Vpax.CLI/Commands/Browse/BrowseColumnCommand.cs b/src/Dax.Vpax.CLI/Commands/Browse/BrowseColumnCommand.cs index 2c7e3eb..792cd71 100644 --- a/src/Dax.Vpax.CLI/Commands/Browse/BrowseColumnCommand.cs +++ b/src/Dax.Vpax.CLI/Commands/Browse/BrowseColumnCommand.cs @@ -1,4 +1,4 @@ -using Dax.Vpax.CLI.Commands.Package; +using static Dax.Vpax.CLI.Commands.Browse.BrowseColumnCommandOptions; namespace Dax.Vpax.CLI.Commands.Browse; @@ -7,9 +7,26 @@ internal sealed class BrowseColumnCommand : Command public static BrowseColumnCommand Instance { get; } = new BrowseColumnCommand(); private BrowseColumnCommand() - : base(name: "column", description: "Display columns information") + : base(name: "column", description: "Display column information") { AddAlias("c"); + AddOption(CommonOptions.VpaxOption); + AddOption(TableOption); + AddOption(CommonOptions.ExcludeHiddenOption); + AddOption(CommonOptions.OrderByOption); + AddOption(CommonOptions.TopOption); + Handler = new BrowseColumnCommandHandler(); } } + +internal static class BrowseColumnCommandOptions +{ + public static readonly Option TableOption = new( + name: "--table", + description: "Specify the table name" + ) + { + ArgumentHelpName = "name" + }; +} diff --git a/src/Dax.Vpax.CLI/Commands/Browse/BrowseColumnCommandHandler.cs b/src/Dax.Vpax.CLI/Commands/Browse/BrowseColumnCommandHandler.cs index caa6733..a34fed0 100644 --- a/src/Dax.Vpax.CLI/Commands/Browse/BrowseColumnCommandHandler.cs +++ b/src/Dax.Vpax.CLI/Commands/Browse/BrowseColumnCommandHandler.cs @@ -1,24 +1,30 @@ -namespace Dax.Vpax.CLI.Commands.Browse; +using static Dax.Vpax.CLI.Commands.Browse.BrowseColumnCommandOptions; + +namespace Dax.Vpax.CLI.Commands.Browse; internal sealed class BrowseColumnCommandHandler : CommandHandler { public override Task InvokeAsync(InvocationContext context) { - var model = GetCurrentModel(context); - if (model is null) - return Task.FromResult(context.ExitCode); + var vpax = context.ParseResult.GetValueForOption(CommonOptions.VpaxOption)!; + + var model = GetModel(context, vpax); + if (model is not null) + { + AnsiConsole.Write(GetView(context, model)); + } - AnsiConsole.Write(GetColumns(context, model)); return Task.FromResult(context.ExitCode); } - private IRenderable GetColumns(InvocationContext context, Metadata.Model model) + private IRenderable GetView(InvocationContext context, Metadata.Model model) { - var top = context.ParseResult.GetValueForOption(CommonOptions.TopOption); var excludeHidden = context.ParseResult.GetValueForOption(CommonOptions.ExcludeHiddenOption); var orderBy = context.ParseResult.GetValueForOption(CommonOptions.OrderByOption); + var top = context.ParseResult.GetValueForOption(CommonOptions.TopOption); + var table = context.ParseResult.GetValueForOption(TableOption); - var table = new Spectre.Console.Table().BorderColor(Color.Yellow) + var view = new Spectre.Console.Table().BorderColor(Color.Yellow) .AddColumn(new TableColumn(new Markup("[yellow]Name[/]").Centered()).NoWrap()) .AddColumn(new TableColumn(new Markup("[yellow]Cardinality[/]").Centered())) .AddColumn(new TableColumn(new Markup("[yellow]Size[/]").Centered())) @@ -29,47 +35,72 @@ private IRenderable GetColumns(InvocationContext context, Metadata.Model model) .AddColumn(new TableColumn(new Markup("[yellow]Encoding[/]").Centered())) .AddColumn(new TableColumn(new Markup("[yellow]Data Type[/]").Centered())); - var query = model.Tables.SelectMany((t) => t.Columns).Where(c => !c.IsRowNumber); - - query = orderBy switch + var totalSize = model.Tables.Sum((t) => t.TableSize); + var query = model.Tables.Where((t) => table is null || table.Equals(t.TableName.Name, StringComparison.OrdinalIgnoreCase)).SelectMany((t) => t.Columns).Where((c) => !c.IsRowNumber).Select((t) => new Row(t, totalSize)); + if (orderBy.HasValue) { - "name" or "n" => query.OrderBy((c) => c.ToDisplayName()), - "cardinality" or "c" => query.OrderByDescending((c) => c.ColumnCardinality), - "size" or "s" => query.OrderByDescending((c) => c.TotalSize), - _ => query - }; - - if (excludeHidden) query = query.Where((c) => !c.IsHidden); + query = orderBy switch + { + -1 => query.OrderByDescending(_ => _.Name), + +1 => query.OrderBy(_ => _.Name), + 2 => query.OrderByDescending(_ => _.Cardinality), + 3 => query.OrderByDescending(_ => _.Size), + 4 => query.OrderByDescending(_ => _.SizePercentage), + 5 => query.OrderByDescending(_ => _.DataSize), + 6 => query.OrderByDescending(_ => _.DictionarySize), + 7 => query.OrderByDescending(_ => _.HierarchiesSize), + -8 => query.OrderByDescending(_ => _.Encoding), + +8 => query.OrderBy(_ => _.Encoding), + -9 => query.OrderByDescending(_ => _.DataType), + +9 => query.OrderBy(_ => _.DataType), + _ => query.OrderByDescending(_ => 0), // ignore invalid order by + }; + } + if (excludeHidden) query = query.Where((r) => !r.IsHidden); if (top.HasValue) query = query.Take(top.Value); - var modelSize = model.Tables.Sum((t) => t.TableSize); - var columns = query.ToArray(); - - foreach (var c in columns) + var rows = query.ToArray(); + foreach (var r in rows) { - var style = c.IsHidden ? new Style(foreground: Color.Grey) : Style.Plain; - var sizePercentage = (double)c.TotalSize / modelSize; + var style = r.IsHidden ? new Style(foreground: Color.Grey) : Style.Plain; - table.AddRow( - new Text(c.ToDisplayName(), style).LeftJustified(), - new Text(c.ColumnCardinality.ToString("N0"), style).RightJustified(), - new Text(c.TotalSize.ToString("N0"), style).RightJustified(), - new Text(sizePercentage.ToString("P2"), style).RightJustified(), - new Text(c.DataSize.ToString("N0"), style).RightJustified(), - new Text(c.DictionarySize.ToString("N0"), style).RightJustified(), - new Text(c.HierarchiesSize.ToString("N0"), style).RightJustified(), - new Text(c.Encoding, style).RightJustified(), - new Text(c.DataType, style).RightJustified() + view.AddRow( + new Text(r.Name, style).LeftJustified(), + new Text(r.Cardinality.ToString("N0"), style).RightJustified(), + new Text(r.Size.ToString("N0"), style).RightJustified(), + new Text(r.SizePercentage.ToString("P2"), style).RightJustified(), + new Text(r.DataSize.ToString("N0"), style).RightJustified(), + new Text(r.DictionarySize.ToString("N0"), style).RightJustified(), + new Text(r.HierarchiesSize.ToString("N0"), style).RightJustified(), + new Text(r.Encoding, style).RightJustified(), + new Text(r.DataType, style).RightJustified() ); } - table.Columns[0].Footer = new Markup($"[grey]{columns.Length:N0} items[/]").LeftJustified(); - table.Columns[1].Footer = new Markup($"[grey]{columns.Sum(_ => _.ColumnCardinality):N0}[/]").RightJustified(); - table.Columns[2].Footer = new Markup($"[grey]{columns.Sum(_ => _.TotalSize).ToSizeString():N0}[/]").RightJustified(); - table.Columns[4].Footer = new Markup($"[grey]{columns.Sum(_ => _.DataSize).ToSizeString():N0}[/]").RightJustified(); - table.Columns[5].Footer = new Markup($"[grey]{columns.Sum(_ => _.DictionarySize).ToSizeString():N0}[/]").RightJustified(); - table.Columns[6].Footer = new Markup($"[grey]{columns.Sum(_ => _.HierarchiesSize).ToSizeString():N0}[/]").RightJustified(); + view.Columns[0].Footer = new Markup($"[grey]{rows.Length:N0} items[/]").LeftJustified(); + view.Columns[1].Footer = new Markup($"[grey]{rows.Sum(_ => _.Cardinality):N0}[/]").RightJustified(); + view.Columns[2].Footer = new Markup($"[grey]{rows.Sum(_ => _.Size).ToSizeString():N0}[/]").RightJustified(); + // table.Columns[3].Footer = // SizePercentage + view.Columns[4].Footer = new Markup($"[grey]{rows.Sum(_ => _.DataSize).ToSizeString():N0}[/]").RightJustified(); + view.Columns[5].Footer = new Markup($"[grey]{rows.Sum(_ => _.DictionarySize).ToSizeString():N0}[/]").RightJustified(); + view.Columns[6].Footer = new Markup($"[grey]{rows.Sum(_ => _.HierarchiesSize).ToSizeString():N0}[/]").RightJustified(); + // table.Columns[7].Footer = // Encoding + // table.Columns[8].Footer = // DataType - return table; + return view; + } + + private sealed record Row(Metadata.Column column, long totalSize) + { + public bool IsHidden => column.IsHidden; + public string Name => column.ToDisplayName(); + public long Cardinality => column.ColumnCardinality; + public long Size => column.TotalSize; + public double SizePercentage => (double)column.TotalSize / totalSize; + public long DataSize => column.DataSize; + public long DictionarySize => column.DictionarySize; + public long HierarchiesSize => column.HierarchiesSize; + public string Encoding => column.Encoding; + public string DataType => column.DataType; } } diff --git a/src/Dax.Vpax.CLI/Commands/Browse/BrowseCommand.cs b/src/Dax.Vpax.CLI/Commands/Browse/BrowseCommand.cs index cc1c3de..3255390 100644 --- a/src/Dax.Vpax.CLI/Commands/Browse/BrowseCommand.cs +++ b/src/Dax.Vpax.CLI/Commands/Browse/BrowseCommand.cs @@ -11,10 +11,5 @@ private BrowseCommand() AddCommand(BrowseTableCommand.Instance); AddCommand(BrowseColumnCommand.Instance); AddCommand(BrowseRelationshipCommand.Instance); - - AddGlobalOption(Commands.CommonOptions.PathOption); - AddGlobalOption(CommonOptions.ExcludeHiddenOption); - AddGlobalOption(CommonOptions.OrderByOption); - AddGlobalOption(CommonOptions.TopOption); } } diff --git a/src/Dax.Vpax.CLI/Commands/Browse/BrowseModelCommand.cs b/src/Dax.Vpax.CLI/Commands/Browse/BrowseModelCommand.cs index 410f9e5..00840a4 100644 --- a/src/Dax.Vpax.CLI/Commands/Browse/BrowseModelCommand.cs +++ b/src/Dax.Vpax.CLI/Commands/Browse/BrowseModelCommand.cs @@ -8,6 +8,8 @@ private BrowseModelCommand() : base(name: "model", description: "Display tabular model information") { AddAlias("m"); + AddOption(CommonOptions.VpaxOption); + Handler = new BrowseModelCommandHandler(); } } diff --git a/src/Dax.Vpax.CLI/Commands/Browse/BrowseModelCommandHandler.cs b/src/Dax.Vpax.CLI/Commands/Browse/BrowseModelCommandHandler.cs index 86c879b..d509444 100644 --- a/src/Dax.Vpax.CLI/Commands/Browse/BrowseModelCommandHandler.cs +++ b/src/Dax.Vpax.CLI/Commands/Browse/BrowseModelCommandHandler.cs @@ -1,26 +1,27 @@ -using Spectre.Console; - -namespace Dax.Vpax.CLI.Commands.Browse; +namespace Dax.Vpax.CLI.Commands.Browse; internal sealed class BrowseModelCommandHandler : CommandHandler { public override Task InvokeAsync(InvocationContext context) { - var model = GetCurrentModel(context); - if (model is null) - return Task.FromResult(context.ExitCode); + var vpax = context.ParseResult.GetValueForOption(CommonOptions.VpaxOption)!; + + var model = GetModel(context, vpax); + if (model is not null) + { + var grid = new Grid() + .AddColumns(1) + .AddRow(GetTableView(model)) + .AddEmptyRow() + .AddRow(GetChartView(model)); - var grid = new Grid() - .AddColumns(1) - .AddRow(GetProperties(model)) - .AddEmptyRow() - .AddRow(GetSizeChart(model)); + AnsiConsole.Write(new Panel(grid)); + } - AnsiConsole.Write(new Panel(grid)); return Task.FromResult(context.ExitCode); } - private IRenderable GetProperties(Metadata.Model model) + private IRenderable GetTableView(Metadata.Model model) { var table = new Spectre.Console.Table().HideHeaders().Expand().BorderColor(Color.Yellow) .AddColumn("Name") @@ -34,26 +35,26 @@ private IRenderable GetProperties(Metadata.Model model) .AddRow("[yellow]Last Process[/]", model.LastProcessed.ToString("o", CultureInfo.InvariantCulture)) .AddRow("[yellow]Last Update[/]", model.LastUpdate.ToString("o", CultureInfo.InvariantCulture)) .AddRow("[yellow]Tables[/]", model.Tables.Count.ToString()) - .AddRow("[yellow]Columns[/]", model.Tables.SelectMany((t) => t.Columns).Where(c => !c.IsRowNumber).Count().ToString()) + .AddRow("[yellow]Columns[/]", model.Tables.SelectMany((t) => t.Columns).Count(c => !c.IsRowNumber).ToString()) .AddRow("[yellow]Size (in memory)[/]", model.Tables.Sum((t) => t.TableSize).ToSizeString()); return table; } - private IRenderable GetSizeChart(Metadata.Model model) + private IRenderable GetChartView(Metadata.Model model) { var dataSize = model.Tables.Sum((t) => t.ColumnsDataSize); - var dictionariesSize = model.Tables.Sum((t) => t.ColumnsDictionarySize); + var dictionarySize = model.Tables.Sum((t) => t.ColumnsDictionarySize); var hierarchiesSize = model.Tables.Sum((t) => t.ColumnsHierarchiesSize); - var totalSize = dataSize + dictionariesSize + hierarchiesSize; + var totalSize = dataSize + dictionarySize + hierarchiesSize; var dataPercentage = Math.Floor((double)dataSize / totalSize * 100); - var dictionariePercentage = Math.Floor((double)dictionariesSize / totalSize * 100); - var hierarchiesPercentage = 100 - dataPercentage - dictionariePercentage; + var dictionaryPercentage = Math.Floor((double)dictionarySize / totalSize * 100); + var hierarchiesPercentage = 100 - dataPercentage - dictionaryPercentage; var chart = new BreakdownChart().ShowPercentage().FullSize() .AddItem("Data", dataPercentage, Color.Red) - .AddItem("Dictionary", dictionariePercentage, Color.Green) + .AddItem("Dictionary", dictionaryPercentage, Color.Green) .AddItem("Hierarchy", hierarchiesPercentage, Color.Blue); return chart; diff --git a/src/Dax.Vpax.CLI/Commands/Browse/BrowseRelationshipCommand.cs b/src/Dax.Vpax.CLI/Commands/Browse/BrowseRelationshipCommand.cs index 4c62cd1..73a28c2 100644 --- a/src/Dax.Vpax.CLI/Commands/Browse/BrowseRelationshipCommand.cs +++ b/src/Dax.Vpax.CLI/Commands/Browse/BrowseRelationshipCommand.cs @@ -1,15 +1,18 @@ -using Dax.Vpax.CLI.Commands.Package; - -namespace Dax.Vpax.CLI.Commands.Browse; +namespace Dax.Vpax.CLI.Commands.Browse; internal sealed class BrowseRelationshipCommand : Command { public static BrowseRelationshipCommand Instance { get; } = new BrowseRelationshipCommand(); private BrowseRelationshipCommand() - : base(name: "relationship", description: "Display relationships information") + : base(name: "relationship", description: "Display relationship information") { AddAlias("r"); + AddOption(CommonOptions.VpaxOption); + AddOption(CommonOptions.ExcludeHiddenOption); + AddOption(CommonOptions.OrderByOption); + AddOption(CommonOptions.TopOption); + Handler = new BrowseRelationshipCommandHandler(); } } diff --git a/src/Dax.Vpax.CLI/Commands/Browse/BrowseRelationshipCommandHandler.cs b/src/Dax.Vpax.CLI/Commands/Browse/BrowseRelationshipCommandHandler.cs index a937059..4cc008e 100644 --- a/src/Dax.Vpax.CLI/Commands/Browse/BrowseRelationshipCommandHandler.cs +++ b/src/Dax.Vpax.CLI/Commands/Browse/BrowseRelationshipCommandHandler.cs @@ -4,19 +4,22 @@ internal sealed class BrowseRelationshipCommandHandler : CommandHandler { public override Task InvokeAsync(InvocationContext context) { - var model = GetCurrentModel(context); - if (model is null) - return Task.FromResult(context.ExitCode); + var vpax = context.ParseResult.GetValueForOption(CommonOptions.VpaxOption)!; + + var model = GetModel(context, vpax); + if (model is not null) + { + AnsiConsole.Write(GetView(context, model)); + } - AnsiConsole.Write(GetRelationshipView(context, model)); return Task.FromResult(context.ExitCode); } - private IRenderable GetRelationshipView(InvocationContext context, Metadata.Model model) + private IRenderable GetView(InvocationContext context, Metadata.Model model) { - var top = context.ParseResult.GetValueForOption(CommonOptions.TopOption); var excludeHidden = context.ParseResult.GetValueForOption(CommonOptions.ExcludeHiddenOption); var orderBy = context.ParseResult.GetValueForOption(CommonOptions.OrderByOption); + var top = context.ParseResult.GetValueForOption(CommonOptions.TopOption); var table = new Spectre.Console.Table().BorderColor(Color.Yellow) .AddColumn(new TableColumn(new Markup("[yellow]Name[/]").Centered()).NoWrap()) @@ -26,39 +29,57 @@ private IRenderable GetRelationshipView(InvocationContext context, Metadata.Mode .AddColumn(new TableColumn(new Markup("[yellow]Missing Keys[/]").Centered())) .AddColumn(new TableColumn(new Markup("[yellow]Invalid Rows[/]").Centered())); - var query = orderBy switch + var query = model.Relationships.Select((r) => new Row(r)); + if (orderBy.HasValue) { - "name" or "n" => model.Relationships.OrderBy((r) => r.ToDisplayName()), - "cardinality" or "c" => model.Relationships.OrderByDescending((r) => r.FromColumn.ColumnCardinality), - "size" or "s" => model.Relationships.OrderByDescending((r) => r.UsedSize), - _ => model.Relationships.AsEnumerable() - }; - - if (excludeHidden) query = query.Where((r) => !r.IsHidden()); + query = orderBy switch + { + -1 => query.OrderByDescending(_ => _.Name), + +1 => query.OrderBy(_ => _.Name), + 2 => query.OrderByDescending(_ => _.Size), + 3 => query.OrderByDescending(_ => _.MaxFromCardinality), + 4 => query.OrderByDescending(_ => _.MaxToCardinality), + 5 => query.OrderByDescending(_ => _.MissingKeys), + 6 => query.OrderByDescending(_ => _.InvalidRows), + _ => query.OrderByDescending(_ => 0), // ignore invalid order by + }; + } + if (excludeHidden) query = query.Where((r) => !r.IsHidden); if (top.HasValue) query = query.Take(top.Value); - var modelSize = model.Tables.Sum((t) => t.TableSize); - var relationships = query.ToArray(); - - foreach (var r in relationships) + var rows = query.ToArray(); + foreach (var r in rows) { - var style = r.IsHidden() ? new Style(foreground: Color.Grey) : Style.Plain; + var style = r.IsHidden ? new Style(foreground: Color.Grey) : Style.Plain; table.AddRow( - new Text(r.ToDisplayName(), style).LeftJustified(), - new Text(r.UsedSize.ToString("N0"), style).RightJustified(), - new Text(r.FromColumn.ColumnCardinality.ToString("N0"), style).RightJustified(), - new Text(r.ToColumn.ColumnCardinality.ToString("N0"), style).RightJustified(), + new Text(r.Name, style).LeftJustified(), + new Text(r.Size.ToString("N0"), style).RightJustified(), + new Text(r.MaxFromCardinality.ToString("N0"), style).RightJustified(), + new Text(r.MaxToCardinality.ToString("N0"), style).RightJustified(), new Text(r.MissingKeys.ToString("N0"), style).RightJustified(), new Text(r.InvalidRows.ToString("N0"), style).RightJustified() ); } - table.Columns[0].Footer = new Markup($"[grey]{relationships.Length:N0} items[/]").LeftJustified(); - table.Columns[1].Footer = new Markup($"[grey]{relationships.Sum((r) => r.UsedSize).ToSizeString():N0}[/]").RightJustified(); - table.Columns[4].Footer = new Markup($"[grey]{relationships.Sum((r) => r.MissingKeys):N0}[/]").RightJustified(); - table.Columns[5].Footer = new Markup($"[grey]{relationships.Sum((r) => r.InvalidRows):N0}[/]").RightJustified(); + table.Columns[0].Footer = new Markup($"[grey]{rows.Length:N0} items[/]").LeftJustified(); + table.Columns[1].Footer = new Markup($"[grey]{rows.Sum(_ => _.Size).ToSizeString():N0}[/]").RightJustified(); + // table.Columns[2].Footer = // MaxFromCardinality + // table.Columns[3].Footer = // MaxToCardinality + table.Columns[4].Footer = new Markup($"[grey]{rows.Sum(_ => _.MissingKeys):N0}[/]").RightJustified(); + table.Columns[5].Footer = new Markup($"[grey]{rows.Sum(_ => _.InvalidRows):N0}[/]").RightJustified(); return table; } + + private sealed record Row(Metadata.Relationship relationship) + { + public bool IsHidden { get; } = relationship.IsHidden(); + public string Name { get; } = relationship.ToDisplayName(); + public long Size { get; } = relationship.UsedSize; + public long MaxFromCardinality { get; } = relationship.FromColumn.ColumnCardinality; + public long MaxToCardinality { get; } = relationship.ToColumn.ColumnCardinality; + public long MissingKeys { get; } = relationship.MissingKeys; + public long InvalidRows { get; } = relationship.InvalidRows; + } } diff --git a/src/Dax.Vpax.CLI/Commands/Browse/BrowseTableCommand.cs b/src/Dax.Vpax.CLI/Commands/Browse/BrowseTableCommand.cs index 74149f8..fc67b26 100644 --- a/src/Dax.Vpax.CLI/Commands/Browse/BrowseTableCommand.cs +++ b/src/Dax.Vpax.CLI/Commands/Browse/BrowseTableCommand.cs @@ -1,6 +1,4 @@ -using Dax.Vpax.CLI.Commands.Package; - -namespace Dax.Vpax.CLI.Commands.Browse; +namespace Dax.Vpax.CLI.Commands.Browse; internal sealed class BrowseTableCommand : Command { @@ -10,6 +8,11 @@ private BrowseTableCommand() : base(name: "table", description: "Display table information") { AddAlias("t"); + AddOption(CommonOptions.VpaxOption); + AddOption(CommonOptions.ExcludeHiddenOption); + AddOption(CommonOptions.OrderByOption); + AddOption(CommonOptions.TopOption); + Handler = new BrowseTableCommandHandler(); } } diff --git a/src/Dax.Vpax.CLI/Commands/Browse/BrowseTableCommandHandler.cs b/src/Dax.Vpax.CLI/Commands/Browse/BrowseTableCommandHandler.cs index bf92774..b45adcf 100644 --- a/src/Dax.Vpax.CLI/Commands/Browse/BrowseTableCommandHandler.cs +++ b/src/Dax.Vpax.CLI/Commands/Browse/BrowseTableCommandHandler.cs @@ -4,19 +4,22 @@ internal sealed class BrowseTableCommandHandler : CommandHandler { public override Task InvokeAsync(InvocationContext context) { - var model = GetCurrentModel(context); - if (model is null) - return Task.FromResult(context.ExitCode); + var vpax = context.ParseResult.GetValueForOption(CommonOptions.VpaxOption)!; + + var model = GetModel(context, vpax); + if (model is not null) + { + AnsiConsole.Write(GetView(context, model)); + } - AnsiConsole.Write(GetTables(context, model)); return Task.FromResult(context.ExitCode); } - private IRenderable GetTables(InvocationContext context, Metadata.Model model) + private IRenderable GetView(InvocationContext context, Metadata.Model model) { - var top = context.ParseResult.GetValueForOption(CommonOptions.TopOption); var excludeHidden = context.ParseResult.GetValueForOption(CommonOptions.ExcludeHiddenOption); var orderBy = context.ParseResult.GetValueForOption(CommonOptions.OrderByOption); + var top = context.ParseResult.GetValueForOption(CommonOptions.TopOption); var table = new Spectre.Console.Table().BorderColor(Color.Yellow) .AddColumn(new TableColumn(new Markup("[yellow]Name[/]").Centered()).NoWrap()) @@ -29,47 +32,70 @@ private IRenderable GetTables(InvocationContext context, Metadata.Model model) .AddColumn(new TableColumn(new Markup("[yellow]Columns[/]").Centered())) .AddColumn(new TableColumn(new Markup("[yellow]Partitions[/]").Centered())); - var query = orderBy switch + var totalSize = model.Tables.Sum((t) => t.TableSize); + var query = model.Tables.Select((t) => new Row(t, totalSize)); + if (orderBy.HasValue) { - "name" or "n" => model.Tables.OrderBy((t) => t.TableName.Name), - "cardinality" or "c" => model.Tables.OrderByDescending((t) => t.RowsCount), - "size" or "s" => model.Tables.OrderByDescending((t) => t.TableSize), - _ => model.Tables.AsEnumerable() - }; - - if (excludeHidden) query = query.Where((t) => !t.IsHidden()); + query = orderBy switch + { + -1 => query.OrderByDescending(_ => _.Name), + +1 => query.OrderBy(_ => _.Name), + 2 => query.OrderByDescending(_ => _.Cardinality), + 3 => query.OrderByDescending(_ => _.Size), + 4 => query.OrderByDescending(_ => _.SizePercentage), + 5 => query.OrderByDescending(_ => _.DataSize), + 6 => query.OrderByDescending(_ => _.DictionarySize), + 7 => query.OrderByDescending(_ => _.HierarchiesSize), + 8 => query.OrderByDescending(_ => _.Columns), + 9 => query.OrderByDescending(_ => _.Partitions), + _ => query.OrderByDescending(_ => 0), // ignore invalid order by + }; + } + if (excludeHidden) query = query.Where((r) => !r.IsHidden); if (top.HasValue) query = query.Take(top.Value); - var modelSize = model.Tables.Sum((t) => t.TableSize); - var tables = query.ToArray(); - - foreach (var t in query) + var rows = query.ToArray(); + foreach (var r in rows) { - var style = t.IsHidden() ? new Style(foreground: Color.Grey) : Style.Plain; - var sizePercentage = (double)t.TableSize / modelSize; + var style = r.IsHidden ? new Style(foreground: Color.Grey) : Style.Plain; table.AddRow( - new Text(t.TableName.Name, style).LeftJustified(), - new Text(t.RowsCount.ToString("N0"), style).RightJustified(), - new Text(t.TableSize.ToString("N0"), style).RightJustified(), - new Text(sizePercentage.ToString("P2"), style).RightJustified(), - new Text(t.ColumnsDataSize.ToString("N0"), style).RightJustified(), - new Text(t.ColumnsDictionarySize.ToString("N0"), style).RightJustified(), - new Text(t.ColumnsHierarchiesSize.ToString("N0"), style).RightJustified(), - new Text(t.Columns.Count.ToString("N0"), style).RightJustified(), - new Text(t.Partitions.Count.ToString("N0"), style).RightJustified() + new Text(r.Name, style).LeftJustified(), + new Text(r.Cardinality.ToString("N0"), style).RightJustified(), + new Text(r.Size.ToString("N0"), style).RightJustified(), + new Text(r.SizePercentage.ToString("P2"), style).RightJustified(), + new Text(r.DataSize.ToString("N0"), style).RightJustified(), + new Text(r.DictionarySize.ToString("N0"), style).RightJustified(), + new Text(r.HierarchiesSize.ToString("N0"), style).RightJustified(), + new Text(r.Columns.ToString("N0"), style).RightJustified(), + new Text(r.Partitions.ToString("N0"), style).RightJustified() ); } - table.Columns[0].Footer = new Markup($"[grey]{tables.Length:N0} items[/]").LeftJustified(); - table.Columns[1].Footer = new Markup($"[grey]{tables.Sum(_ => _.RowsCount):N0}[/]").RightJustified(); - table.Columns[2].Footer = new Markup($"[grey]{tables.Sum(_ => _.TableSize).ToSizeString()}[/]").RightJustified(); - table.Columns[4].Footer = new Markup($"[grey]{tables.Sum(_ => _.ColumnsDataSize).ToSizeString()}[/]").RightJustified(); - table.Columns[5].Footer = new Markup($"[grey]{tables.Sum(_ => _.ColumnsDictionarySize).ToSizeString()}[/]").RightJustified(); - table.Columns[6].Footer = new Markup($"[grey]{tables.Sum(_ => _.ColumnsHierarchiesSize).ToSizeString()}[/]").RightJustified(); - table.Columns[7].Footer = new Markup($"[grey]{tables.Sum(_ => _.Columns.Count):N0}[/]").RightJustified(); - table.Columns[8].Footer = new Markup($"[grey]{tables.Sum(_ => _.Partitions.Count):N0}[/]").RightJustified(); + table.Columns[0].Footer = new Markup($"[grey]{rows.Length:N0} items[/]").LeftJustified(); + table.Columns[1].Footer = new Markup($"[grey]{rows.Sum(_ => _.Cardinality):N0}[/]").RightJustified(); + table.Columns[2].Footer = new Markup($"[grey]{rows.Sum(_ => _.Size).ToSizeString()}[/]").RightJustified(); + // table.Columns[3].Footer = // SizePercentage + table.Columns[4].Footer = new Markup($"[grey]{rows.Sum(_ => _.DataSize).ToSizeString()}[/]").RightJustified(); + table.Columns[5].Footer = new Markup($"[grey]{rows.Sum(_ => _.DictionarySize).ToSizeString()}[/]").RightJustified(); + table.Columns[6].Footer = new Markup($"[grey]{rows.Sum(_ => _.HierarchiesSize).ToSizeString()}[/]").RightJustified(); + table.Columns[7].Footer = new Markup($"[grey]{rows.Sum(_ => _.Columns):N0}[/]").RightJustified(); + table.Columns[8].Footer = new Markup($"[grey]{rows.Sum(_ => _.Partitions):N0}[/]").RightJustified(); return table; } + + private sealed record Row(Metadata.Table table, long totalSize) + { + public bool IsHidden { get; } = table.IsHidden(); + public string Name { get; } = table.TableName.Name; + public long Cardinality { get; } = table.RowsCount; + public long Size { get; } = table.TableSize; + public double SizePercentage { get; } = (double)table.TableSize / totalSize; + public long DataSize { get; } = table.ColumnsDataSize; + public long DictionarySize { get; } = table.ColumnsDictionarySize; + public long HierarchiesSize { get; } = table.ColumnsHierarchiesSize; + public int Columns { get; } = table.Columns.Count; + public int Partitions { get; } = table.Partitions.Count; + } } diff --git a/src/Dax.Vpax.CLI/Commands/Browse/CommonOptions.cs b/src/Dax.Vpax.CLI/Commands/Browse/CommonOptions.cs index 293db67..6cd93d0 100644 --- a/src/Dax.Vpax.CLI/Commands/Browse/CommonOptions.cs +++ b/src/Dax.Vpax.CLI/Commands/Browse/CommonOptions.cs @@ -2,26 +2,38 @@ internal static class CommonOptions { - private static string[] OrderByColumns => new[] {"c", "cardinality", "n", "name", "s", "size" }; + public static readonly Option VpaxOption = new( + name: "--vpax", + description: "Path to the VPAX file" + ) + { + ArgumentHelpName = "path", + IsRequired = true, + }; public static readonly Option ExcludeHiddenOption = new( name: "--exclude-hidden", description: "Specify whether to exclude hidden objects" ); - public static readonly Option OrderByOption = new( - name: "--orderby", - getDefaultValue: () => "size", - description: "Specify the column to sort by" - ); + public static readonly Option OrderByOption = new( + name: "--order-by", + description: "Specify the column number to order by" + ) + { + ArgumentHelpName = "number" + }; public static readonly Option TopOption = new( name: "--top", description: "Specify the maximum number of objects to display" - ); - - static CommonOptions() + ) { - OrderByOption.AddCompletions(OrderByColumns); - } + ArgumentHelpName = "number" + }; + + //static CommonOptions() + //{ + // OrderByOption.AddCompletions(OrderByColumns); + //} } diff --git a/src/Dax.Vpax.CLI/Commands/CommandHandler.cs b/src/Dax.Vpax.CLI/Commands/CommandHandler.cs index 6c40491..76f741f 100644 --- a/src/Dax.Vpax.CLI/Commands/CommandHandler.cs +++ b/src/Dax.Vpax.CLI/Commands/CommandHandler.cs @@ -2,44 +2,24 @@ internal abstract class CommandHandler : ICommandHandler { + public const int ErrorPackageInvalid = 1001; + public int Invoke(InvocationContext context) => throw new NotSupportedException("Use InvokeAsync instead."); - public abstract Task InvokeAsync(InvocationContext context); - protected FileInfo? GetCurrentPackage(InvocationContext context) - { - var path = context.ParseResult.GetValueForOption(CommonOptions.PathOption) ?? UserSession.GetPackagePath(); - if (path is null) - { - AnsiConsole.MarkupLine($"[red]No package set. Use `vpax package set` to set a package.[/]"); - context.ExitCode = 2; - return null; - } - var file = new FileInfo(path); - if (!file.Exists) - { - AnsiConsole.MarkupLine($"[red]Package file does not exist or is not accessible. [[{path}]][/]"); - context.ExitCode = 3; - return null; - } - return file; - } + public abstract Task InvokeAsync(InvocationContext context); - protected Metadata.Model? GetCurrentModel(InvocationContext context) + protected static Metadata.Model? GetModel(InvocationContext context, FileInfo vpax) { - var file = GetCurrentPackage(context); - if (file is null) - return null; - - var model = AnsiConsole.Status().AutoRefresh(true).Spinner(Spinner.Known.Default).Start("[yellow]Loading VPAX package...[/]", (context) => + var model = AnsiConsole.Status().AutoRefresh(true).Spinner(Spinner.Known.Default).Start("[yellow]Loading VPAX file...[/]", (context) => { - var content = VpaxTools.ImportVpax(file.FullName, importDatabase: false); + var content = VpaxTools.ImportVpax(vpax.FullName, importDatabase: false); return content.DaxModel; }); if (model is null) { - AnsiConsole.MarkupLine($"[red]Package does not contain {VpaxFormat.DAXMODEL}. Verify the package and try again.[/]"); - context.ExitCode = 3; + AnsiConsole.MarkupLine($"[red]VPAX file does not contain {VpaxFormat.DAXMODEL}. Verify the package and try again.[/]"); + context.ExitCode = ErrorPackageInvalid; return null; } diff --git a/src/Dax.Vpax.CLI/Commands/CommonOptions.cs b/src/Dax.Vpax.CLI/Commands/CommonOptions.cs deleted file mode 100644 index 6429754..0000000 --- a/src/Dax.Vpax.CLI/Commands/CommonOptions.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Dax.Vpax.CLI.Commands; - -internal static class CommonOptions -{ - public static readonly Option PathOption = new( - name: "--path", - description: "Path to the VPAX package file. You can configure the default package using `vpax package set `" - ); -} diff --git a/src/Dax.Vpax.CLI/Commands/Export/ExportCommand.cs b/src/Dax.Vpax.CLI/Commands/Export/ExportCommand.cs index 1b4cc99..2e3046f 100644 --- a/src/Dax.Vpax.CLI/Commands/Export/ExportCommand.cs +++ b/src/Dax.Vpax.CLI/Commands/Export/ExportCommand.cs @@ -11,12 +11,12 @@ private ExportCommand() { AddArgument(PathArgument); AddArgument(ConnectionStringArgument); - AddOption(OverwriteOption); - AddOption(ExcludeTomOption); - AddOption(ExcludeVpaOption); + AddOption(ColumnBatchSizeOption); AddOption(DirectQueryModeOption); AddOption(DirectLakeModeOption); - AddOption(ColumnBatchSizeOption); + AddOption(ExcludeTomOption); + AddOption(ExcludeVpaOption); + AddOption(OverwriteOption); Handler = new ExportCommandHandler(); } diff --git a/src/Dax.Vpax.CLI/Commands/Export/ExportCommandOptions.cs b/src/Dax.Vpax.CLI/Commands/Export/ExportCommandOptions.cs index 24f0d09..7b13feb 100644 --- a/src/Dax.Vpax.CLI/Commands/Export/ExportCommandOptions.cs +++ b/src/Dax.Vpax.CLI/Commands/Export/ExportCommandOptions.cs @@ -2,6 +2,11 @@ internal static class ExportCommandOptions { + public static readonly Argument PathArgument = new( + name: "path", + description: "Path to write the VPAX file" + ); + public static readonly Argument ConnectionStringArgument = new( name: "connection-string", description: "Connection string to the tabular model", @@ -18,15 +23,22 @@ internal static class ExportCommandOptions return connectionString; // always return the original value }); - public static readonly Argument PathArgument = new( - name: "path", - description: "Path to write the VPAX file" + public static readonly Option ColumnBatchSizeOption = new( + name: "--column-batch-size", + getDefaultValue: () => StatExtractor.DefaultColumnBatchSize, + description: "Number of rows processed at a time during column statistics analysis" ); - public static readonly Option OverwriteOption = new( - name: "--overwrite", - getDefaultValue: () => false, - description: "Overwrite the VPAX file if it already exists" + public static readonly Option DirectQueryModeOption = new( + name: "--direct-query-mode", + getDefaultValue: () => DirectQueryExtractionMode.Full, + description: "Direct Query extraction mode" + ); + + public static readonly Option DirectLakeModeOption = new( + name: "--direct-lake-mode", + getDefaultValue: () => DirectLakeExtractionMode.Full, + description: "Direct Lake extraction mode" ); public static readonly Option ExcludeTomOption = new( @@ -41,22 +53,10 @@ internal static class ExportCommandOptions description: "Exclude the VPA model (DaxVpaView.json) from the export" ); - public static readonly Option DirectQueryModeOption = new( - name: "--direct-query-mode", - getDefaultValue: () => DirectQueryExtractionMode.Full, - description: "Direct Query extraction mode" - ); - - public static readonly Option DirectLakeModeOption = new( - name: "--direct-lake-mode", - getDefaultValue: () => DirectLakeExtractionMode.Full, - description: "Direct Lake extraction mode" - ); - - public static readonly Option ColumnBatchSizeOption = new( - name: "--column-batch-size", - getDefaultValue: () => StatExtractor.DefaultColumnBatchSize, - description: "Number of rows processed at a time during column statistics analysis" + public static readonly Option OverwriteOption = new( + name: "--overwrite", + getDefaultValue: () => false, + description: "Overwrite the VPAX file if it already exists" ); //static ExportCommandOptions() diff --git a/src/Dax.Vpax.CLI/Commands/Package/PackageCommand.cs b/src/Dax.Vpax.CLI/Commands/Package/PackageCommand.cs index d1331a2..7433a1f 100644 --- a/src/Dax.Vpax.CLI/Commands/Package/PackageCommand.cs +++ b/src/Dax.Vpax.CLI/Commands/Package/PackageCommand.cs @@ -8,8 +8,6 @@ private PackageCommand() : base(name: "package", description: "(Experimental) Manage a VPAX package file") { AddCommand(PackageExtractCommand.Instance); - AddCommand(PackageSetCommand.Instance); AddCommand(PackageShowCommand.Instance); - AddCommand(PackageUnsetCommand.Instance); } } diff --git a/src/Dax.Vpax.CLI/Commands/Package/PackageExtractCommand.cs b/src/Dax.Vpax.CLI/Commands/Package/PackageExtractCommand.cs index 4e56af4..5d41bae 100644 --- a/src/Dax.Vpax.CLI/Commands/Package/PackageExtractCommand.cs +++ b/src/Dax.Vpax.CLI/Commands/Package/PackageExtractCommand.cs @@ -1,4 +1,6 @@ -namespace Dax.Vpax.CLI.Commands.Package; +using static Dax.Vpax.CLI.Commands.Package.PackageExtractCommandOptions; + +namespace Dax.Vpax.CLI.Commands.Package; internal sealed class PackageExtractCommand : Command { @@ -7,18 +9,27 @@ internal sealed class PackageExtractCommand : Command private PackageExtractCommand() : base(name: "extract", description: "Extract all files from a VPAX package") { - AddArgument(PackageExtractCommandOptions.PathArgument); - AddOption(Commands.CommonOptions.PathOption); - AddOption(PackageExtractCommandOptions.OverwriteOption); + AddOption(VpaxOption); + AddOption(OutputOption); + AddOption(OverwriteOption); Handler = new PackageExtractCommandHandler(); } } + internal static class PackageExtractCommandOptions { - public static readonly Argument PathArgument = new( - name: "path", - description: "Path to write the extracted files" + public static readonly Option VpaxOption = new( + name: "--vpax", + description: "Path to the VPAX file" + ) + { + IsRequired = true, + }; + + public static readonly Option OutputOption = new( + name: "--output", + description: "Path for the output directory. If not specified, the VPAX directory will be used" ); public static readonly Option OverwriteOption = new( diff --git a/src/Dax.Vpax.CLI/Commands/Package/PackageExtractCommandHandler.cs b/src/Dax.Vpax.CLI/Commands/Package/PackageExtractCommandHandler.cs index 9afb9a6..c77a282 100644 --- a/src/Dax.Vpax.CLI/Commands/Package/PackageExtractCommandHandler.cs +++ b/src/Dax.Vpax.CLI/Commands/Package/PackageExtractCommandHandler.cs @@ -1,34 +1,40 @@ -namespace Dax.Vpax.CLI.Commands.Package; +using static Dax.Vpax.CLI.Commands.Package.PackageExtractCommandOptions; + +namespace Dax.Vpax.CLI.Commands.Package; internal sealed class PackageExtractCommandHandler : CommandHandler { public override Task InvokeAsync(InvocationContext context) { - var file = GetCurrentPackage(context); - if (file is null) - return Task.FromResult(context.ExitCode); + var vpax = context.ParseResult.GetValueForOption(VpaxOption)!; + var overwrite = context.ParseResult.GetValueForOption(OverwriteOption); + var output = context.ParseResult.GetValueForOption(OutputOption); - using var package = System.IO.Packaging.Package.Open(file.FullName, FileMode.Open, FileAccess.Read); + using var package = System.IO.Packaging.Package.Open(vpax.FullName, FileMode.Open, FileAccess.Read); + + if (output is null) + output = new DirectoryInfo(path: Path.Combine(vpax.DirectoryName!, Path.GetFileNameWithoutExtension(vpax.Name))); - var overwrite = context.ParseResult.GetValueForOption(PackageExtractCommandOptions.OverwriteOption); - var path = context.ParseResult.GetValueForArgument(PackageExtractCommandOptions.PathArgument); - _ = Directory.CreateDirectory(path.FullName); + if (!output.Exists) + output.Create(); - AnsiConsole.Status().AutoRefresh(true).Spinner(Spinner.Known.Default).Start($"[yellow]Extracting package {Markup.Escape(file.FullName)}...[/]", (context) => + AnsiConsole.Status().AutoRefresh(true).Spinner(Spinner.Known.Default).Start($"[yellow]Extracting package {Markup.Escape(vpax.Name)}...[/]", (context) => { foreach (var part in package.GetParts()) { var partName = part.Uri.OriginalString.TrimStart('/'); - AnsiConsole.MarkupLine($"[grey]Extracting {Markup.Escape(partName)}...[/]"); + context.Status($"[yellow]Extracting {Markup.Escape(partName)}...[/]"); - var filePath = Path.Combine(path.FullName, partName); + var filePath = Path.Combine(output.FullName, partName); var fileMode = overwrite ? FileMode.Create : FileMode.CreateNew; using var fileStream = new FileStream(filePath, fileMode, FileAccess.Write); using var partStream = part.GetStream(); partStream.CopyTo(fileStream); + + AnsiConsole.MarkupLine($"[green]Extracted {Markup.Escape(partName)}[/]"); } - //AnsiConsole.MarkupLine("[green]Completed[/]"); + AnsiConsole.MarkupLine("[green]Completed[/]"); }); return Task.FromResult(context.ExitCode); diff --git a/src/Dax.Vpax.CLI/Commands/Package/PackageSetCommand.cs b/src/Dax.Vpax.CLI/Commands/Package/PackageSetCommand.cs deleted file mode 100644 index b27b885..0000000 --- a/src/Dax.Vpax.CLI/Commands/Package/PackageSetCommand.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Dax.Vpax.CLI.Commands.Package; - -internal sealed class PackageSetCommand : Command -{ - public static PackageSetCommand Instance { get; } = new PackageSetCommand(); - - private PackageSetCommand() - : base(name: "set", description: "Set a VPAX file to be the current active package") - { - AddArgument(PackageSetCommandOptions.PathArgument); - - Handler = new PackageSetCommandHandler(); - } -} - -internal static class PackageSetCommandOptions -{ - public static readonly Argument PathArgument = new( - name: "path", - description: "Path to the VPAX package file." - ); -} diff --git a/src/Dax.Vpax.CLI/Commands/Package/PackageSetCommandHandler.cs b/src/Dax.Vpax.CLI/Commands/Package/PackageSetCommandHandler.cs deleted file mode 100644 index b40aaab..0000000 --- a/src/Dax.Vpax.CLI/Commands/Package/PackageSetCommandHandler.cs +++ /dev/null @@ -1,23 +0,0 @@ -using static Dax.Vpax.CLI.Commands.Package.PackageSetCommandOptions; - -namespace Dax.Vpax.CLI.Commands.Package; - -internal sealed class PackageSetCommandHandler : CommandHandler -{ - public override Task InvokeAsync(InvocationContext context) - { - var path = context.ParseResult.GetValueForArgument(PathArgument); - - if (!File.Exists(path)) - { - AnsiConsole.MarkupLine($"[red]The package file does not exist or is not accessible. [[{path}]][/]"); - return Task.FromResult(context.ExitCode = 1); - } - - var session = UserSession.Load(); - session.Package.Path = path; - session.Save(); - - return Task.FromResult(context.ExitCode); - } -} diff --git a/src/Dax.Vpax.CLI/Commands/Package/PackageShowCommand.cs b/src/Dax.Vpax.CLI/Commands/Package/PackageShowCommand.cs index a89f34a..20c6406 100644 --- a/src/Dax.Vpax.CLI/Commands/Package/PackageShowCommand.cs +++ b/src/Dax.Vpax.CLI/Commands/Package/PackageShowCommand.cs @@ -1,4 +1,6 @@ -namespace Dax.Vpax.CLI.Commands.Package; +using static Dax.Vpax.CLI.Commands.Package.PackageShowCommandOptions; + +namespace Dax.Vpax.CLI.Commands.Package; internal sealed class PackageShowCommand : Command { @@ -7,8 +9,19 @@ internal sealed class PackageShowCommand : Command private PackageShowCommand() : base(name: "show", description: "Show details of a VPAX package file") { - AddOption(Commands.CommonOptions.PathOption); + AddOption(VpaxOption); Handler = new PackageShowCommandHandler(); } } + +internal static class PackageShowCommandOptions +{ + public static readonly Option VpaxOption = new( + name: "--vpax", + description: "Path to the VPAX file" + ) + { + IsRequired = true, + }; +} diff --git a/src/Dax.Vpax.CLI/Commands/Package/PackageShowCommandHandler.cs b/src/Dax.Vpax.CLI/Commands/Package/PackageShowCommandHandler.cs index 679318f..7c9a521 100644 --- a/src/Dax.Vpax.CLI/Commands/Package/PackageShowCommandHandler.cs +++ b/src/Dax.Vpax.CLI/Commands/Package/PackageShowCommandHandler.cs @@ -1,4 +1,4 @@ -using Spectre.Console; +using static Dax.Vpax.CLI.Commands.Package.PackageShowCommandOptions; namespace Dax.Vpax.CLI.Commands.Package; @@ -6,15 +6,13 @@ internal sealed class PackageShowCommandHandler : CommandHandler { public override Task InvokeAsync(InvocationContext context) { - var file = GetCurrentPackage(context); - if (file is null) - return Task.FromResult(context.ExitCode); + var vpax = context.ParseResult.GetValueForOption(VpaxOption)!; var grid = new Grid() .AddColumns(1) - .AddRow(GetProperties(file)) + .AddRow(GetProperties(vpax)) .AddEmptyRow() - .AddRow(GetContent(file)); + .AddRow(GetContent(vpax)); AnsiConsole.Write(new Panel(grid)); return Task.FromResult(context.ExitCode); diff --git a/src/Dax.Vpax.CLI/Commands/Package/PackageUnsetCommand.cs b/src/Dax.Vpax.CLI/Commands/Package/PackageUnsetCommand.cs deleted file mode 100644 index 8b0f5ca..0000000 --- a/src/Dax.Vpax.CLI/Commands/Package/PackageUnsetCommand.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Dax.Vpax.CLI.Commands.Package; - -internal sealed class PackageUnsetCommand : Command -{ - public static PackageUnsetCommand Instance { get; } = new PackageUnsetCommand(); - - private PackageUnsetCommand() - : base(name: "unset", description: "Unset the current VPAX package file") - { - Handler = new PackageUnsetCommandHandler(); - } -} diff --git a/src/Dax.Vpax.CLI/Commands/Package/PackageUnsetCommandHandler.cs b/src/Dax.Vpax.CLI/Commands/Package/PackageUnsetCommandHandler.cs deleted file mode 100644 index 5e23b64..0000000 --- a/src/Dax.Vpax.CLI/Commands/Package/PackageUnsetCommandHandler.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Dax.Vpax.CLI.Commands.Package; - -internal sealed class PackageUnsetCommandHandler : CommandHandler -{ - public override Task InvokeAsync(InvocationContext context) - { - var session = UserSession.Load(); - session.Package.Path = null; - session.Save(); - - return Task.FromResult(context.ExitCode); - } -} diff --git a/src/Dax.Vpax.CLI/GlobalSuppressions.cs b/src/Dax.Vpax.CLI/GlobalSuppressions.cs new file mode 100644 index 0000000..2f53e21 --- /dev/null +++ b/src/Dax.Vpax.CLI/GlobalSuppressions.cs @@ -0,0 +1,26 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:Dax.Vpax.CLI.Commands.Browse.BrowseRelationshipCommandHandler.GetView(System.CommandLine.Invocation.InvocationContext,Dax.Metadata.Model)~Spectre.Console.Rendering.IRenderable")] +[assembly: SuppressMessage("Performance", "CA1859:Use concrete types when possible for improved performance", Justification = "", Scope = "member", Target = "~M:Dax.Vpax.CLI.Commands.Browse.BrowseRelationshipCommandHandler.GetView(System.CommandLine.Invocation.InvocationContext,Dax.Metadata.Model)~Spectre.Console.Rendering.IRenderable")] +[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:Dax.Vpax.CLI.Commands.Package.PackageShowCommandHandler.GetContent(System.IO.FileInfo)~Spectre.Console.Rendering.IRenderable")] +[assembly: SuppressMessage("Performance", "CA1859:Use concrete types when possible for improved performance", Justification = "", Scope = "member", Target = "~M:Dax.Vpax.CLI.Commands.Package.PackageShowCommandHandler.GetContent(System.IO.FileInfo)~Spectre.Console.Rendering.IRenderable")] +[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:Dax.Vpax.CLI.Commands.Package.PackageShowCommandHandler.GetProperties(System.IO.FileInfo)~Spectre.Console.Rendering.IRenderable")] +[assembly: SuppressMessage("Performance", "CA1859:Use concrete types when possible for improved performance", Justification = "", Scope = "member", Target = "~M:Dax.Vpax.CLI.Commands.Package.PackageShowCommandHandler.GetProperties(System.IO.FileInfo)~Spectre.Console.Rendering.IRenderable")] +[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:Dax.Vpax.CLI.Commands.Browse.BrowseColumnCommandHandler.GetView(System.CommandLine.Invocation.InvocationContext,Dax.Metadata.Model)~Spectre.Console.Rendering.IRenderable")] +[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:Dax.Vpax.CLI.Commands.Browse.BrowseModelCommandHandler.GetTableView(Dax.Metadata.Model)~Spectre.Console.Rendering.IRenderable")] +[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:Dax.Vpax.CLI.Commands.Browse.BrowseModelCommandHandler.GetChartView(Dax.Metadata.Model)~Spectre.Console.Rendering.IRenderable")] +[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:Dax.Vpax.CLI.Commands.Browse.BrowseTableCommandHandler.GetView(System.CommandLine.Invocation.InvocationContext,Dax.Metadata.Model)~Spectre.Console.Rendering.IRenderable")] +[assembly: SuppressMessage("Performance", "CA1859:Use concrete types when possible for improved performance", Justification = "", Scope = "member", Target = "~M:Dax.Vpax.CLI.Commands.Browse.BrowseTableCommandHandler.GetView(System.CommandLine.Invocation.InvocationContext,Dax.Metadata.Model)~Spectre.Console.Rendering.IRenderable")] +[assembly: SuppressMessage("Performance", "CA1859:Use concrete types when possible for improved performance", Justification = "", Scope = "member", Target = "~M:Dax.Vpax.CLI.Commands.Browse.BrowseModelCommandHandler.GetChartView(Dax.Metadata.Model)~Spectre.Console.Rendering.IRenderable")] +[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:Dax.Vpax.CLI.Commands.CommandHandler.GetCurrentVpax(System.CommandLine.Invocation.InvocationContext)~System.IO.FileInfo")] +[assembly: SuppressMessage("Performance", "CA1859:Use concrete types when possible for improved performance", Justification = "", Scope = "member", Target = "~M:Dax.Vpax.CLI.Commands.Browse.BrowseColumnCommandHandler.GetView(System.CommandLine.Invocation.InvocationContext,Dax.Metadata.Model)~Spectre.Console.Rendering.IRenderable")] +[assembly: SuppressMessage("Performance", "CA1859:Use concrete types when possible for improved performance", Justification = "", Scope = "member", Target = "~M:Dax.Vpax.CLI.Commands.Browse.BrowseModelCommandHandler.GetTableView(Dax.Metadata.Model)~Spectre.Console.Rendering.IRenderable")] +[assembly: SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "", Scope = "member", Target = "~M:Dax.Vpax.CLI.Commands.Browse.BrowseModelCommandHandler.GetTableView(Dax.Metadata.Model)~Spectre.Console.Rendering.IRenderable")] +[assembly: SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "", Scope = "member", Target = "~M:Dax.Vpax.CLI.Commands.Browse.BrowseColumnCommandHandler.GetView(System.CommandLine.Invocation.InvocationContext,Dax.Metadata.Model)~Spectre.Console.Rendering.IRenderable")] +[assembly: SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "", Scope = "member", Target = "~M:Dax.Vpax.CLI.Commands.Browse.BrowseRelationshipCommandHandler.GetView(System.CommandLine.Invocation.InvocationContext,Dax.Metadata.Model)~Spectre.Console.Rendering.IRenderable")] +[assembly: SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "", Scope = "member", Target = "~M:Dax.Vpax.CLI.Commands.Browse.BrowseTableCommandHandler.GetView(System.CommandLine.Invocation.InvocationContext,Dax.Metadata.Model)~Spectre.Console.Rendering.IRenderable")] diff --git a/src/Dax.Vpax.CLI/UserSession.cs b/src/Dax.Vpax.CLI/UserSession.cs deleted file mode 100644 index f03023b..0000000 --- a/src/Dax.Vpax.CLI/UserSession.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace Dax.Vpax.CLI; - -internal sealed class UserSession -{ - private static readonly string FilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".vpax", "cli-session.json"); - private static readonly JsonSerializerOptions JsonOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - }; - - public static UserSession Load() - { - UserSession? session = null; - - if (File.Exists(FilePath)) - session = JsonSerializer.Deserialize(json: File.ReadAllText(FilePath), JsonOptions); - - return session ?? new UserSession(); - } - - public static string? GetPackagePath() => Load().Package.Path; - - public Package Package { get; init; } = new(); - - public void Save() - { - _ = Directory.CreateDirectory(Path.GetDirectoryName(FilePath)!); - File.WriteAllText(FilePath, JsonSerializer.Serialize(this, JsonOptions)); - } -} - -internal sealed class Package -{ - public string? Path { get; set; } -}