Skip to content

Instantly share code, notes, and snippets.

@cking
Created November 3, 2020 14:09
Show Gist options
  • Save cking/307603e320e0e5df5645fa90257cd3fe to your computer and use it in GitHub Desktop.
Save cking/307603e320e0e5df5645fa90257cd3fe to your computer and use it in GitHub Desktop.
D#+ with Qmmands and GenericHost
// <copyright file="Program.cs" company="z0ne">
// Copyright (c) z0ne. All rights reserved.
// </copyright>
namespace Maria
{
using System;
using System.Linq;
using CommandLine;
using dotenv.net;
using Microsoft.Extensions.Hosting;
using Serilog;
using Serilog.Core;
using Serilog.Events;
using Serilog.Exceptions;
using Serilog.Sinks.SystemConsole.Themes;
/// <summary>
/// Program entry.
/// </summary>
internal static class Program
{
private static LoggingLevelSwitch ConfigureLogger()
{
// ReSharper disable once UseObjectOrCollectionInitializer
var levelSwitch = new LoggingLevelSwitch();
#if DEBUG
levelSwitch.MinimumLevel = LogEventLevel.Verbose;
#endif
var lc = new LoggerConfiguration().MinimumLevel.ControlledBy(levelSwitch)
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("System", LogEventLevel.Warning)
.MinimumLevel.Override("DSharpPlus", LogEventLevel.Information)
.Enrich.WithExceptionDetails()
.Enrich.FromLogContext()
.WriteTo.Console(
theme: AnsiConsoleTheme.Literate,
outputTemplate:
"[{Level:u1} {Timestamp:HH:mm:ss}] {SourceContext}: {Message:lj}{Properties:j}{NewLine}{Exception}");
Log.Logger = lc.CreateLogger();
return levelSwitch;
}
public static IHostBuilder CreateHostBuilder(string[] args)
{
var startup = new Startup();
return startup.Initialize();
}
private static int Main(string[] args)
{
// support .env files
DotEnv.AutoConfig();
var levelSwitch = ConfigureLogger();
using var parser = Parser.Default;
return parser.ParseArguments<Args>(args)
.MapResult(
options => new Startup(options, levelSwitch).Run(),
errors =>
{
if (errors.Any(e => e is HelpRequestedError || e is VersionRequestedError))
{
return -1;
}
Console.WriteLine("Failed to start.");
return -2;
});
}
}
}
// <copyright file="Startup.cs" company="z0ne">
// Copyright (c) z0ne. All rights reserved.
// </copyright>
namespace Maria
{
using System;
using System.IO;
using Autofac;
using Autofac.Extensions.DependencyInjection;
using DSharpPlus;
using Maria.Configuration;
using Maria.Data;
using Maria.Naruse;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Qmmands;
using Serilog;
using Serilog.Core;
using Serilog.Events;
using Serilog.Extensions.Logging;
using StackExchange.Redis;
internal class Startup
{
private readonly Args args;
private readonly LoggingLevelSwitch loggingLevelSwitch;
public Startup(Args args, LoggingLevelSwitch loggingLevelSwitch)
{
this.args = args;
this.loggingLevelSwitch = loggingLevelSwitch;
}
public Startup()
{
this.args = new Args();
this.loggingLevelSwitch = new LoggingLevelSwitch();
}
public IContainer Container { get; private set; }
public IServiceProvider ServiceProvider { get; private set; }
public void ConfigureAppConfiguration(HostBuilderContext context, IConfigurationBuilder configuration)
{
var env = context.HostingEnvironment;
configuration.SetBasePath(Directory.GetCurrentDirectory());
var configFile = args.ConfigFile;
if (string.IsNullOrEmpty(configFile))
{
configFile = "maria.yml";
}
configuration.AddYamlFile(configFile, false, true)
.AddYamlFile(GetEnvPath(configFile, env.EnvironmentName), true, true);
if (env.IsDevelopment())
{
configuration.AddUserSecrets(typeof(Program).Assembly, true, false);
}
configuration.AddEnvironmentVariables("MARIA_");
configuration.AddInMemoryCollection(args.ToInMemoryCollection());
}
public void ConfigureContainer(ContainerBuilder builder)
{
}
public void ConfigureServices(HostBuilderContext context, IServiceCollection services)
{
// logging
services.AddLogging();
// config
services.AddOptions<RootOptions>().Bind(context.Configuration).ValidateDataAnnotations();
// database
services.AddDbContext<ApplicationContext>(
b => b.UseNpgsql(
context.Configuration.GetConnectionString("Postgres"),
optionsBuilder => optionsBuilder.EnableRetryOnFailure(5)));
services.AddStackExchangeRedisCache(
o => o.Configuration = context.Configuration.GetConnectionString("Redis"));
// discord client
services.AddSingleton(
provider =>
{
var opts = provider.GetService<IOptions<RootOptions>>().Value;
return new DiscordShardedClient(
new DiscordConfiguration
{
AutoReconnect = true,
GatewayCompressionLevel = GatewayCompressionLevel.Stream,
Intents = DiscordIntents.AllUnprivileged,
Token = opts.Discord.Token,
LoggerFactory = new SerilogLoggerFactory(),
});
});
services.AddSingleton(
provider => new CommandService(
new CommandServiceConfiguration
{
StringComparison = StringComparison.InvariantCultureIgnoreCase,
DefaultRunMode = RunMode.Parallel,
}));
// TODO: extract to lib?
services.AddMediatR(typeof(Application));
// TODO: extract to lib
// TODO: move to ConfigureContainer?
//services.AddSingleton<IJukeboxManager, JukeboxManager>();
services.AddTransient<IHostedService, Application>();
}
public IHostBuilder Initialize()
{
if (args.Verbose)
{
if (loggingLevelSwitch.MinimumLevel != LogEventLevel.Verbose)
{
loggingLevelSwitch.MinimumLevel = LogEventLevel.Debug;
}
}
var hostBuilder = new HostBuilder()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureHostConfiguration(config => config.AddEnvironmentVariables(prefix: "DOTNET_"))
.ConfigureAppConfiguration(ConfigureAppConfiguration)
.UseSerilog(dispose: true)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureServices(ConfigureServices)
.ConfigureContainer<ContainerBuilder>(ConfigureContainer)
.UseConsoleLifetime();
return hostBuilder;
}
public int Run()
{
var host = Initialize().Build();
host.Run();
return 0;
}
private static string GetEnvPath(string path, string env)
{
var dir = Path.GetDirectoryName(path) ?? ".";
var file = Path.GetFileNameWithoutExtension(path);
var ext = Path.GetExtension(path);
// ReSharper disable once InvertIf
if (string.IsNullOrEmpty(file))
{
file = ext;
ext = string.Empty;
}
return Path.Combine(dir, $"{file}.{env}{ext}");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment