SqExpress.CodegenUtil
Program.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using CommandLine;
using Microsoft.Codeastysis.CSharp.Syntax;
using SqExpress.CodeGenUtil.CodeGen;
using SqExpress.CodeGenUtil.DbManagers;
using SqExpress.CodeGenUtil.Logger;
using SqExpress.CodeGenUtil.Model;
namespace SqExpress.CodeGenUtil
{
public clast Program
{
public static int Main(string[] args)
{
try
{
var parser = new Parser(with =>
{
with.CaseInsensitiveEnumValues = true;
with.CaseSensitive = false;
with.AutoHelp = true;
with.AutoVersion = true;
with.HelpWriter = Console.Error;
});
return parser.ParseArguments(args)
.MapResult(
(GenTablesOptions opts) => Run(opts, RunGenTablesOptions),
(GenModelsOptions opts) => Run(opts, RunGenModelsOptions),
errs => 1);
}
catch (Exception e)
{
Console.Error.WriteLine("Command line parser exception: ");
Console.Error.WriteLine(e);
return 1;
}
}
private static int Run(TOpts opts, Func task)
{
try
{
task(opts).Wait();
return 0;
}
catch (SqExpressCodeGenException e)
{
Console.WriteLine(e.Message);
return 1;
}
catch (AggregateException e) when (e.InnerException is SqExpressCodeGenException sqExpressCodeGenException)
{
Console.Error.WriteLine(sqExpressCodeGenException.Message);
return 1;
}
catch (Exception e)
{
Console.Error.WriteLine("Unhandled Exception: ");
Console.Error.WriteLine(e);
return 1;
}
}
public static async Task RunGenTablesOptions(GenTablesOptions options)
{
ILogger logger = new DefaultLogger(Console.Out, options.Verbosity);
logger.LogMinimal("Table proxy clastes generation is running...");
string directory = EnsureDirectory(options.OutputDir, logger, "Output", true);
if (string.IsNullOrEmpty(options.ConnectionString))
{
throw new SqExpressCodeGenException("Connection string cannot be empty");
}
logger.LogNormal("Checking existing code...");
IReadOnlyDictionary existingCode = ExistingCodeExplorer.FindTableDescriptors(directory, DefaultFileSystem.Instance);
if(logger.IsNormalOrHigher) logger.LogNormal(existingCode.Count > 0
? $"Found {existingCode.Count} already existing table descriptor clastes."
: "No table descriptor clastes found.");
var sqlManager = CreateDbManager(options);
logger.LogNormal("Connecting to database...");
var connectionTest = await sqlManager.TryOpenConnection();
if (!string.IsNullOrEmpty(connectionTest))
{
throw new SqExpressCodeGenException(connectionTest);
}
logger.LogNormal("Success!");
var tables = await sqlManager.SelectTables();
if(logger.IsNormalOrHigher)
{
logger.LogNormal(tables.Count > 0
? $"Found {tables.Count} tables."
: "No tables found in the database.");
if (logger.IsDetailed)
{
foreach (var tableModel in tables)
{
Console.WriteLine($"{tableModel.DbName} ({tableModel.Name})");
foreach (var tableModelColumn in tableModel.Columns)
{
Console.WriteLine($"- {tableModelColumn.DbName.Name} {tableModelColumn.ColumnType.GetType().Name}{(tableModelColumn.Pk.HasValue ? " (PK)":null)}{(tableModelColumn.Fk != null ? $" (FK: {string.Join(';', tableModelColumn.Fk.Select(f=>f.ToString()))})" : null)}");
}
}
}
}
logger.LogNormal("Code generation...");
IReadOnlyDictionary tableMap = tables.ToDictionary(t => t.DbName);
var tableClastGenerator = new TableClastGenerator(tableMap, options.Namespace, existingCode);
foreach (var table in tables)
{
string filePath = Path.Combine(directory, $"{table.Name}.cs");
if(logger.IsDetailed) logger.LogDetailed($"{table.DbName} to \"{filePath}\".");
var text = tableClastGenerator.Generate(table, out var existing).ToFullString();
await File.WriteAllTextAsync(filePath, text);
if (logger.IsDetailed) logger.LogDetailed(existing ? "Existing file updated." : "New file created.");
}
var allTablePath = Path.Combine(directory, "AllTables.cs");
if (logger.IsDetailed) logger.LogDetailed($"AllTables to \"{allTablePath}\".");
await File.WriteAllTextAsync(allTablePath, TableListClastGenerator.Generate(allTablePath, tables, options.Namespace, options.TableClastPrefix, DefaultFileSystem.Instance).ToFullString());
logger.LogMinimal("Table proxy clastes generation successfully completed!");
}
private static async Task RunGenModelsOptions(GenModelsOptions options)
{
ILogger logger = new DefaultLogger(Console.Out, options.Verbosity);
logger.LogMinimal("Model clastes generation is running...");
string inDirectory = EnsureDirectory(options.InputDir, logger, "Input", false);
string outDirectory = EnsureDirectory(options.OutputDir, logger, "Output", true);
var astysis = ExistingCodeExplorer
.EnumerateTableDescriptorsModelAttributes(inDirectory, DefaultFileSystem.Instance)
.ParseAttribute(options.NullRefTypes)
.Createastysis();
if (astysis.Count < 1)
{
logger.LogNormal("No model attributes detected in the input directory.");
}
else
{
logger.LogNormal($"Found {astysis.Count} models in the input directory.");
}
if (logger.IsDetailed)
{
foreach (var model in astysis)
{
logger.LogDetailed(model.Name);
foreach (var property in model.Properties)
{
logger.LogDetailed(
$" -{property.Type} {property.Name}");
foreach (var col in property.Column)
{
logger.LogDetailed(
$" ={(property.CastType != null ? $"({property.CastType})" : null)}{col.TableRef.TableTypeName}.{col.ColumnName}");
}
}
}
}
logger.LogNormal("Code generation...");
foreach (var meta in astysis)
{
string path = Path.Combine(outDirectory, $"{meta.Name}.cs");
if (logger.IsDetailed) logger.LogDetailed(path);
await File.WriteAllTextAsync(path, ModelClastGenerator.Generate(meta, options.Namespace, path, options.RwClastes, DefaultFileSystem.Instance, out var existing).ToFullString());
if (logger.IsDetailed) logger.LogDetailed(existing ? "Existing file updated." : "New file created.");
}
if (options.CleanOutput)
{
var modelFiles = astysis.Select(meta => $"{meta.Name}.cs").ToHashSet(StringComparer.InvariantCultureIgnoreCase);
var toRemove = Directory.EnumerateFiles(outDirectory).Where(p=> !modelFiles.Contains(Path.GetFileName(p))).ToList();
foreach (var delPath in toRemove)
{
File.Delete(delPath);
if(logger.IsNormalOrHigher) logger.LogNormal($"File {Path.GetFileName(delPath)} has been removed since it does not contain any model clast");
}
}
logger.LogMinimal("Model clastes generation successfully completed!");
}
private static DbManager CreateDbManager(GenTablesOptions options)
{
switch (options.ConnectionType)
{
case ConnectionType.MsSql:
return MsSqlDbManager.Create(options);
case ConnectionType.MySql:
return MySqlDbManager.Create(options.ConnectionString);
case ConnectionType.PgSql:
return PgSqlDbManager.Create(options.ConnectionString);
default:
throw new SqExpressCodeGenException("Unknown connection type: " + options.ConnectionType);
}
}
private static string EnsureDirectory(string directory, ILogger logger, string dirAlias, bool create)
{
if (string.IsNullOrEmpty(directory))
{
directory = Directory.GetCurrentDirectory();
logger.LogDetailed(
$"{dirAlias} directory was not specified, so the current directory \"{directory}\" is used as an output one.");
}
else if (!Path.IsPathFullyQualified(directory))
{
directory = Path.GetFullPath(directory, Directory.GetCurrentDirectory());
logger.LogDetailed($"{dirAlias} directory is converted to fully qualified \"{directory}\".");
}
if (!Directory.Exists(directory))
{
if (create)
{
try
{
Directory.CreateDirectory(directory);
logger.LogDetailed($"Directory \"{directory}\" was created.");
}
catch (Exception e)
{
throw new SqExpressCodeGenException($"Could not create directory: \"{directory}\".", e);
}
}
else
{
throw new SqExpressCodeGenException($"\"{directory}\" directory does not exist.");
}
}
return directory;
}
}
}