-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #8 from cklutz/feature/cliimp
CLI Improvements
- Loading branch information
Showing
12 changed files
with
366 additions
and
118 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
docdb: Test debug | ||
docdb: Test message | ||
docdb: warning: Test warning | ||
docdb: error: Test error | ||
docdb: Processing Server[@Name='GFT-NmJxHoi15ez']/Database[@Name='AdventureWorks2022'] | ||
docdb: Writing C:\Users\cnkz\source\repos\docdb\samples\AdventureWorks2022\\metadata\database.adventureworks2022.yml | ||
docdb: Processing Server[@Name='GFT-NmJxHoi15ez']/Database[@Name='AdventureWorks2022']/Table[@Name='AWBuildVersion' and @Schema='dbo'] | ||
Terminate batch job (Y/N)? |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
using Spectre.Console.Cli; | ||
using System.ComponentModel; | ||
|
||
|
||
//using YamlDotNet.Serialization; | ||
|
||
namespace DocDB; | ||
|
||
internal class BaseOptions : CommandSettings | ||
{ | ||
[Description("Set log level to verbose")] | ||
[CommandOption("--verbose")] | ||
public bool Verbose { get; set; } | ||
|
||
[Description("Treats warnings as errors")] | ||
[CommandOption("--warnings-as-errors")] | ||
public bool WarningsAsErrors { get; set; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
//using YamlDotNet.Serialization; | ||
namespace DocDB; | ||
|
||
internal static class CommandHelper | ||
{ | ||
public static int Run(BaseOptions options, Func<IOutput, int> run) | ||
{ | ||
var output = new Output(options.Verbose, options.WarningsAsErrors); | ||
int rc = run(output); | ||
return rc == 0 ? output.HasErrors ? 1 : 0 : rc; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,10 @@ | ||
namespace DocDB; | ||
namespace DocDB; | ||
|
||
public interface IOutput | ||
{ | ||
bool IsDebugEnabled { get; } | ||
void Debug(string message); | ||
void Error(string message); | ||
void Message(string message); | ||
void Warning(string message); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
using DocDB.Contracts; | ||
using Microsoft.Data.SqlClient; | ||
using Microsoft.SqlServer.Management.Sdk.Sfc; | ||
using Spectre.Console.Cli; | ||
using YamlDotNet.Serialization; | ||
using YamlDotNet.Serialization.NamingConventions; | ||
using ValidationResult = Spectre.Console.ValidationResult; | ||
|
||
namespace DocDB; | ||
|
||
internal class MetadataCommand : Command<MetadataCommandOptions> | ||
{ | ||
public override ValidationResult Validate(CommandContext context, MetadataCommandOptions options) | ||
{ | ||
if (string.IsNullOrWhiteSpace(options.ConnectionString)) | ||
{ | ||
return ValidationResult.Error("A connection string is required"); | ||
} | ||
else | ||
{ | ||
string? env = Environment.GetEnvironmentVariable(options.ConnectionString); | ||
if (env != null) | ||
{ | ||
options.ConnectionString = env; | ||
} | ||
else if (File.Exists(options.ConnectionString)) | ||
{ | ||
options.ConnectionString = File.ReadAllText(options.ConnectionString).Trim(); | ||
} | ||
|
||
try | ||
{ | ||
_ = new SqlConnectionStringBuilder(options.ConnectionString); | ||
} | ||
catch (Exception ex) | ||
{ | ||
return ValidationResult.Error($"The connection string is invalid: {ex.Message}"); | ||
} | ||
} | ||
|
||
return base.Validate(context, options); | ||
} | ||
|
||
public override int Execute(CommandContext context, MetadataCommandOptions options) | ||
{ | ||
return CommandHelper.Run(options, output => | ||
{ | ||
options.OutputFolder = Path.GetFullPath(options.OutputFolder!); | ||
|
||
return DocumentDatabase(output, options.ConnectionString, options.OutputFolder!, options.DisplayDatabaseName, options.SchemaVersionQuery); | ||
}); | ||
} | ||
|
||
private static int DocumentDatabase(IOutput output, string connectionString, string outputDirectory, string? overrideDatabaseName, string? schemaVersionQuery) | ||
{ | ||
if (!Directory.Exists(outputDirectory)) | ||
{ | ||
output.Debug($"Creating directory {outputDirectory}"); | ||
Directory.CreateDirectory(outputDirectory); | ||
} | ||
|
||
using (var target = new SqlServerTarget(connectionString)) | ||
{ | ||
string? schemaVersion = null; | ||
if (schemaVersionQuery != null) | ||
{ | ||
schemaVersion = target.ExecuteScalar(schemaVersionQuery) as string; | ||
} | ||
|
||
var modelCreator = new ModelCreator(output, target.Database, overrideDatabaseName, schemaVersion); | ||
var objects = new List<DdbObject>(); | ||
|
||
var serializerBuilder = new SerializerBuilder() | ||
.WithAttributeOverride<DdbObject>(o => o.Id, new YamlMemberAttribute { Order = -10 }) | ||
.WithAttributeOverride<DdbObject>(o => o.Type, new YamlMemberAttribute { Order = -11 /*-9*/ }) | ||
.WithAttributeOverride<DdbObject>(o => o.Description!, new YamlMemberAttribute { Order = -8 }) | ||
.WithAttributeOverride<DdbObject>(o => o.Script!, new YamlMemberAttribute { Order = int.MaxValue - 10, ScalarStyle = YamlDotNet.Core.ScalarStyle.Literal }) | ||
.WithAttributeOverride<DdbStoredProcedure>(o => o.Syntax!, new YamlMemberAttribute { ScalarStyle = YamlDotNet.Core.ScalarStyle.Literal }) | ||
.WithAttributeOverride<DdbUserDefinedFunction>(o => o.Syntax!, new YamlMemberAttribute { ScalarStyle = YamlDotNet.Core.ScalarStyle.Literal }) | ||
.WithAttributeOverride<DdbXmlSchemaCollection>(o => o.Schemas!, new YamlMemberAttribute { ScalarStyle = YamlDotNet.Core.ScalarStyle.Literal }) | ||
.WithAttributeOverride<DdbXmlSchemaCollection>(o => o.SchemasError!, new YamlMemberAttribute { ScalarStyle = YamlDotNet.Core.ScalarStyle.Literal }) | ||
.WithAttributeOverride<DdbAssembly>(o => o.SourceCode!, new YamlMemberAttribute { ScalarStyle = YamlDotNet.Core.ScalarStyle.Literal }) | ||
.WithAttributeOverride<NamedDdbObject>(o => o.Name, new YamlMemberAttribute { Order = -5 }) | ||
.WithNamingConvention(CamelCaseNamingConvention.Instance); | ||
|
||
var serializer = serializerBuilder.Build(); | ||
|
||
foreach (Urn urn in target.Objects) | ||
{ | ||
output.Message($"Processing {urn}"); | ||
|
||
var smoObject = target.GetSmoObject(urn); | ||
var dbdObject = modelCreator.CreateObject(smoObject); | ||
if (dbdObject == null) | ||
{ | ||
output.Warning($"Failed to create model for {urn.Value}"); | ||
continue; | ||
} | ||
|
||
var yaml = serializer.Serialize(dbdObject, dbdObject.GetType()); | ||
|
||
string modelId = smoObject.GetModelId(); | ||
string fullPath = Path.Combine(outputDirectory, modelId + ".yml"); | ||
output.Message($"Writing {fullPath}"); | ||
|
||
WriteFile(fullPath, modelId, yaml); | ||
objects.Add(dbdObject); | ||
} | ||
|
||
string tocFile = Path.Combine(outputDirectory, "toc.yml"); | ||
TocWriter.WriteToc(output, modelCreator, tocFile, objects); | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
static void WriteFile(string name, string uid, string? contents) | ||
{ | ||
if (File.Exists(name)) | ||
{ | ||
File.Delete(name); | ||
} | ||
|
||
File.WriteAllText(name, $"### YamlMime:DocDB\r\n"); | ||
File.AppendAllText(name, contents); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
using System.ComponentModel; | ||
using Spectre.Console.Cli; | ||
|
||
namespace DocDB; | ||
|
||
[Description("Generate YAML files from database")] | ||
internal class MetadataCommandOptions : BaseOptions | ||
{ | ||
private const string DefaultOutputFolder = ".\\metadata"; | ||
|
||
[Description($"The output base directory (default: '{DefaultOutputFolder}')")] | ||
[CommandOption("-o|--output")] | ||
public string? OutputFolder { get; set; } = DefaultOutputFolder; | ||
|
||
[Description("An alternative database (display) name")] | ||
[CommandOption("--display-database-name")] | ||
public string? DisplayDatabaseName { get; set; } | ||
|
||
[Description("A SQL (SELECT) statement to get the schema version of the database")] | ||
[CommandOption("--schema-version-query")] | ||
public string? SchemaVersionQuery { get; set; } | ||
|
||
[Description("A literal connection string or a the name of an environment variable or file that contains the connection string")] | ||
[CommandArgument(0, "[connstr]")] | ||
public string ConnectionString { get; set; } = null!; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,133 @@ | ||
using System; | ||
using Spectre.Console; | ||
using Spectre.Console.Cli; | ||
using Spectre.Console.Rendering; | ||
|
||
namespace DocDB; | ||
|
||
public class Output : IOutput | ||
{ | ||
public void Error(string message) => Console.Error.WriteLine($"docdb: error: {message}"); | ||
public void Warning(string message) => Console.Error.WriteLine($"docdb: warning: {message}"); | ||
public void Message(string message) => Console.WriteLine($"docdb: {message}"); | ||
public void Debug(string message) => Console.WriteLine($"docdb: {message}"); | ||
} | ||
private class OutputColors | ||
{ | ||
public OutputColors() | ||
{ | ||
if (!Console.IsOutputRedirected) | ||
{ | ||
var background = Console.BackgroundColor; | ||
var foreground = Console.ForegroundColor; | ||
|
||
if (!IsDarkColor(background)) | ||
{ | ||
ErrorColor = ConsoleColor.DarkRed; | ||
WarningColor = ConsoleColor.DarkYellow; | ||
DebugColor = ConsoleColor.DarkGray; | ||
MessageColor = foreground; | ||
} | ||
} | ||
} | ||
|
||
public Color ErrorColor { get; set; } = ConsoleColor.Red; | ||
public Color WarningColor { get; set; } = ConsoleColor.Yellow; | ||
public Color DebugColor { get; set; } = ConsoleColor.Gray; | ||
public Color MessageColor { get; set; } = ConsoleColor.White; | ||
|
||
private static bool IsDarkColor(Color color) => IsDarkColor(color.R, color.G, color.B); | ||
private static bool IsDarkColor(int r, int g, int b) | ||
{ | ||
double brightness = Math.Sqrt(0.299 * Math.Pow(r, 2) + 0.587 * Math.Pow(g, 2) + 0.114 * Math.Pow(b, 2)); | ||
return brightness < 128; | ||
} | ||
} | ||
|
||
private static readonly Lazy<OutputColors> s_outputColors = new(() => new()); | ||
|
||
|
||
public Output(bool isDebugEnabled, bool warningsAsErrors) | ||
{ | ||
IsDebugEnabled = isDebugEnabled; | ||
WarningsAsErrors = warningsAsErrors; | ||
} | ||
|
||
public bool HasErrors { get; private set; } | ||
public bool IsDebugEnabled { get; } | ||
public bool WarningsAsErrors { get; } | ||
|
||
public void Error(string message) | ||
{ | ||
WriteMessage(s_outputColors.Value.ErrorColor, "error: ", message); | ||
HasErrors = true; | ||
} | ||
|
||
public void Warning(string message) | ||
{ | ||
if (WarningsAsErrors) | ||
{ | ||
HasErrors = true; | ||
WriteMessage(s_outputColors.Value.ErrorColor, "warning: ", message); | ||
} | ||
else | ||
{ | ||
WriteMessage(s_outputColors.Value.WarningColor, "warning: ", message); | ||
} | ||
} | ||
|
||
public void Message(string message) | ||
{ | ||
WriteMessage(s_outputColors.Value.MessageColor, null, message); | ||
} | ||
|
||
public void Debug(string message) | ||
{ | ||
WriteMessage(s_outputColors.Value.DebugColor, null, message); | ||
} | ||
|
||
internal static void WriteError(string message) | ||
{ | ||
WriteMessage(s_outputColors.Value.ErrorColor, "error: ", message); | ||
} | ||
|
||
internal static void WriteException(Exception ex) | ||
{ | ||
if (ex is CommandAppException cae) | ||
{ | ||
if (cae.Pretty is { } pretty) | ||
{ | ||
AnsiConsole.Write(pretty); | ||
} | ||
else | ||
{ | ||
WriteError(ex.Message); | ||
} | ||
} | ||
else | ||
{ | ||
AnsiConsole.WriteException(ex, new ExceptionSettings() | ||
{ | ||
Format = ExceptionFormats.ShortenEverything, | ||
Style = new() | ||
{ | ||
Exception = s_outputColors.Value.MessageColor, | ||
Message = s_outputColors.Value.ErrorColor, | ||
Method = s_outputColors.Value.MessageColor, | ||
Parenthesis = s_outputColors.Value.MessageColor, | ||
ParameterName = s_outputColors.Value.DebugColor, | ||
ParameterType = s_outputColors.Value.DebugColor, | ||
LineNumber = s_outputColors.Value.MessageColor, | ||
Path = s_outputColors.Value.MessageColor | ||
}, | ||
}); | ||
} | ||
} | ||
|
||
private static void WriteMessage(Style? style, string? prefix, string message) | ||
{ | ||
AnsiConsole.Console.Write(new NonBreakingText($"docdb: {prefix}{message}\n", style ?? Style.Plain)); | ||
} | ||
|
||
class NonBreakingText(string text, Style style) : IRenderable | ||
{ | ||
private readonly Paragraph _paragraph = new(text, style); | ||
|
||
public Measurement Measure(RenderOptions options, int maxWidth) => ((IRenderable)_paragraph).Measure(options, int.MaxValue); | ||
public IEnumerable<Segment> Render(RenderOptions options, int maxWidth) => ((IRenderable)_paragraph).Render(options, int.MaxValue); | ||
} | ||
} |
Oops, something went wrong.