├── .gitignore
├── .nuke
├── build.cmd
├── build.ps1
└── parameters.json
├── .run
├── Nuke Clean.run.xml
├── Nuke Plan.run.xml
└── Nuke.run.xml
├── Build
├── .editorconfig
├── Build.CI.GitHub.cs
├── Build.Clean.cs
├── Build.Compile.cs
├── Build.Configuration.cs
├── Build.Installer.cs
├── Build.Regex.cs
├── Build.cs
├── Build.csproj
└── Build.csproj.DotSettings
├── Changelog.md
├── Installer
├── Installer.Generator.cs
├── Installer.cs
├── Installer.csproj
└── Resources
│ └── Icons
│ ├── BackgroundImage.png
│ ├── BannerImage.png
│ └── ShellIcon.ico
├── OptionsBar.sln
├── OptionsBar
├── Application.cs
├── Commands
│ ├── ShowBarCommand.cs
│ ├── ShowRunningBarCommand.cs
│ └── ShowUtilsBarCommand.cs
├── OptionsBar.addin
├── OptionsBar.csproj
├── Resources
│ └── Icons
│ │ ├── RibbonIcon16.png
│ │ └── RibbonIcon32.png
├── ViewModels
│ ├── OptionsViewModel.cs
│ └── UtilsViewModel.cs
└── Views
│ ├── Controls
│ ├── RunningLine.cs
│ └── RunningLine.xaml
│ ├── OptionsView.xaml
│ ├── OptionsView.xaml.cs
│ ├── RunningLineView.xaml
│ ├── RunningLineView.xaml.cs
│ ├── Utils
│ ├── RibbonController.cs
│ └── VisualUtils.cs
│ ├── UtilsView.xaml
│ └── UtilsView.xaml.cs
├── Readme.md
└── global.json
/.gitignore:
--------------------------------------------------------------------------------
1 | #IDE folders
2 | *.idea/
3 | *.vs/
4 | *.vscode/
5 |
6 | #Project-specific folders
7 | *obj/
8 | *bin/
9 | *temp/
10 |
11 | #Deprecated Nuget folder
12 | /packages/
13 |
14 | #Nuke output folder
15 | /output/
16 |
17 | #Project-specific files
18 | */build.schema.json
19 |
20 | #User-specific files
21 | *.user
--------------------------------------------------------------------------------
/.nuke/build.cmd:
--------------------------------------------------------------------------------
1 | powershell -ExecutionPolicy ByPass -NoProfile -File "%~dp0Build.ps1" %*
2 |
--------------------------------------------------------------------------------
/.nuke/build.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | Param(
3 | [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
4 | [string[]]$BuildArguments
5 | )
6 |
7 | Write-Output "PowerShell $($PSVersionTable.PSEdition) version $($PSVersionTable.PSVersion)"
8 |
9 | Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { Write-Error $_ -ErrorAction Continue; exit 1 }
10 |
11 | ###########################################################################
12 | # CONFIGURATION
13 | ###########################################################################
14 |
15 | $SolutionDirectory = Split-Path $PSScriptRoot -Parent
16 | $BuildProjectFile = "$SolutionDirectory\Build\Build.csproj"
17 | $TempDirectory = "$SolutionDirectory\.nuke\temp"
18 |
19 | $DotNetGlobalFile = "$SolutionDirectory\global.json"
20 | $DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1"
21 | $DotNetChannel = "Current"
22 |
23 | $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1
24 | $env:DOTNET_CLI_TELEMETRY_OPTOUT = 1
25 | $env:DOTNET_MULTILEVEL_LOOKUP = 0
26 |
27 | ###########################################################################
28 | # EXECUTION
29 | ###########################################################################
30 |
31 | function ExecSafe([scriptblock] $cmd) {
32 | & $cmd
33 | if ($LASTEXITCODE) { exit $LASTEXITCODE }
34 | }
35 |
36 | # If dotnet CLI is installed globally and it matches requested version, use for execution
37 | if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and `
38 | $(dotnet --version) -and $LASTEXITCODE -eq 0) {
39 | $env:DOTNET_EXE = (Get-Command "dotnet").Path
40 | }
41 | else {
42 | # Download install script
43 | $DotNetInstallFile = "$TempDirectory\dotnet-install.ps1"
44 | New-Item -ItemType Directory -Path $TempDirectory -Force | Out-Null
45 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
46 | (New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile)
47 |
48 | # If global.json exists, load expected version
49 | if (Test-Path $DotNetGlobalFile) {
50 | $DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json)
51 | if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) {
52 | $DotNetVersion = $DotNetGlobal.sdk.version
53 | }
54 | }
55 |
56 | # Install by channel or version
57 | $DotNetDirectory = "$TempDirectory\dotnet-win"
58 | if (!(Test-Path variable:DotNetVersion)) {
59 | ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath }
60 | } else {
61 | ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath }
62 | }
63 | $env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe"
64 | }
65 |
66 | Write-Output "Microsoft (R) .NET SDK version $(& $env:DOTNET_EXE --version)"
67 |
68 | ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet }
69 | ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments }
--------------------------------------------------------------------------------
/.nuke/parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./build.schema.json",
3 | "Solution": "OptionsBar.sln",
4 | "Verbosity": "Normal"
5 | }
--------------------------------------------------------------------------------
/.run/Nuke Clean.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
8 |
10 |
12 |
14 |
16 |
18 |
20 |
21 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.run/Nuke Plan.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
8 |
10 |
12 |
14 |
16 |
18 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.run/Nuke.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
8 |
10 |
12 |
14 |
16 |
18 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/Build/.editorconfig:
--------------------------------------------------------------------------------
1 | # noinspection EditorConfigKeyCorrectness
2 | [*.cs]
3 | dotnet_style_qualification_for_field = false:warning
4 | dotnet_style_qualification_for_property = false:warning
5 | dotnet_style_qualification_for_method = false:warning
6 | dotnet_style_qualification_for_event = false:warning
7 | dotnet_style_require_accessibility_modifiers = never:warning
8 |
9 | csharp_style_expression_bodied_methods = true:silent
10 | csharp_style_expression_bodied_properties = true:warning
11 | csharp_style_expression_bodied_indexers = true:warning
12 | csharp_style_expression_bodied_accessors = true:warning
13 |
14 | resharper_check_namespace_highlighting = none
15 | resharper_class_never_instantiated_global_highlighting = none
--------------------------------------------------------------------------------
/Build/Build.CI.GitHub.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using System.Text.RegularExpressions;
3 | using Nuke.Common.Git;
4 | using Nuke.Common.Tools.GitHub;
5 | using Octokit;
6 |
7 | sealed partial class Build
8 | {
9 | Target PublishGitHub => _ => _
10 | .TriggeredBy(CreateInstaller)
11 | .Requires(() => GitHubToken)
12 | .Requires(() => GitRepository)
13 | .Requires(() => GitVersion)
14 | .OnlyWhenStatic(() => GitRepository.IsOnMainOrMasterBranch() && IsServerBuild)
15 | .Executes(async () =>
16 | {
17 | GitHubTasks.GitHubClient = new GitHubClient(new ProductHeaderValue(Solution.Name))
18 | {
19 | Credentials = new Credentials(GitHubToken)
20 | };
21 |
22 | var gitHubName = GitRepository.GetGitHubName();
23 | var gitHubOwner = GitRepository.GetGitHubOwner();
24 | var artifacts = Directory.GetFiles(ArtifactsDirectory, "*");
25 |
26 | await CheckTagsAsync(gitHubOwner, gitHubName, Version);
27 | Log.Information("Tag: {Version}", Version);
28 |
29 | var newRelease = new NewRelease(Version)
30 | {
31 | Name = Version,
32 | Body = CreateChangelog(Version),
33 | Draft = true,
34 | TargetCommitish = GitVersion.Sha
35 | };
36 |
37 | var draft = await CreatedDraftAsync(gitHubOwner, gitHubName, newRelease);
38 | await UploadArtifactsAsync(draft, artifacts);
39 | await ReleaseDraftAsync(gitHubOwner, gitHubName, draft);
40 | });
41 |
42 | static async Task CheckTagsAsync(string gitHubOwner, string gitHubName, string version)
43 | {
44 | var gitHubTags = await GitHubTasks.GitHubClient.Repository.GetAllTags(gitHubOwner, gitHubName);
45 | if (gitHubTags.Select(tag => tag.Name).Contains(version))
46 | throw new ArgumentException($"A Release with the specified tag already exists in the repository: {version}");
47 | }
48 |
49 | static async Task CreatedDraftAsync(string gitHubOwner, string gitHubName, NewRelease newRelease) =>
50 | await GitHubTasks.GitHubClient.Repository.Release.Create(gitHubOwner, gitHubName, newRelease);
51 |
52 | static async Task ReleaseDraftAsync(string gitHubOwner, string gitHubName, Release draft) =>
53 | await GitHubTasks.GitHubClient.Repository.Release.Edit(gitHubOwner, gitHubName, draft.Id, new ReleaseUpdate {Draft = false});
54 |
55 | static async Task UploadArtifactsAsync(Release createdRelease, string[] artifacts)
56 | {
57 | foreach (var file in artifacts)
58 | {
59 | var releaseAssetUpload = new ReleaseAssetUpload
60 | {
61 | ContentType = "application/x-binary",
62 | FileName = Path.GetFileName(file),
63 | RawData = File.OpenRead(file)
64 | };
65 |
66 | await GitHubTasks.GitHubClient.Repository.Release.UploadAsset(createdRelease, releaseAssetUpload);
67 | Log.Information("Artifact: {Path}", file);
68 | }
69 | }
70 |
71 | string CreateChangelog(string version)
72 | {
73 | if (!File.Exists(ChangeLogPath))
74 | {
75 | Log.Warning("Unable to locate the changelog file: {Log}", ChangeLogPath);
76 | return string.Empty;
77 | }
78 |
79 | Log.Information("Changelog: {Path}", ChangeLogPath);
80 |
81 | var logBuilder = new StringBuilder();
82 | var changelogLineRegex = new Regex($@"^.*({version})\S*\s?");
83 | const string nextRecordSymbol = "# ";
84 |
85 | foreach (var line in File.ReadLines(ChangeLogPath))
86 | {
87 | if (logBuilder.Length > 0)
88 | {
89 | if (line.StartsWith(nextRecordSymbol)) break;
90 | logBuilder.AppendLine(line);
91 | continue;
92 | }
93 |
94 | if (!changelogLineRegex.Match(line).Success) continue;
95 | var truncatedLine = changelogLineRegex.Replace(line, string.Empty);
96 | logBuilder.AppendLine(truncatedLine);
97 | }
98 |
99 | if (logBuilder.Length == 0) Log.Warning("No version entry exists in the changelog: {Version}", version);
100 | return logBuilder.ToString();
101 | }
102 | }
--------------------------------------------------------------------------------
/Build/Build.Clean.cs:
--------------------------------------------------------------------------------
1 | using Nuke.Common.Tools.DotNet;
2 | using static Nuke.Common.Tools.DotNet.DotNetTasks;
3 |
4 | sealed partial class Build
5 | {
6 | Target Clean => _ => _
7 | .OnlyWhenStatic(() => IsLocalBuild)
8 | .Executes(() =>
9 | {
10 | foreach (var configuration in GlobBuildConfigurations())
11 | DotNetClean(settings => settings
12 | .SetConfiguration(configuration)
13 | .SetVerbosity(DotNetVerbosity.Minimal));
14 |
15 | foreach (var project in Solution.AllProjects.Where(project => project != Solution.Build))
16 | CleanDirectory(project.Directory / "bin");
17 |
18 | CleanDirectory(ArtifactsDirectory);
19 | });
20 |
21 | static void CleanDirectory(AbsolutePath path)
22 | {
23 | Log.Information("Cleaning directory: {Directory}", path);
24 | path.CreateOrCleanDirectory();
25 | }
26 | }
--------------------------------------------------------------------------------
/Build/Build.Compile.cs:
--------------------------------------------------------------------------------
1 | using System.IO.Enumeration;
2 | using Nuke.Common.Tools.DotNet;
3 | using static Nuke.Common.Tools.DotNet.DotNetTasks;
4 |
5 | sealed partial class Build
6 | {
7 | Target Compile => _ => _
8 | .TriggeredBy(Clean)
9 | .Executes(() =>
10 | {
11 | foreach (var configuration in GlobBuildConfigurations())
12 | DotNetBuild(settings => settings
13 | .SetConfiguration(configuration)
14 | .SetVersion(Version)
15 | .SetVerbosity(DotNetVerbosity.Minimal));
16 | });
17 |
18 | List GlobBuildConfigurations()
19 | {
20 | var configurations = Solution.Configurations
21 | .Select(pair => pair.Key)
22 | .Select(config => config.Remove(config.LastIndexOf('|')))
23 | .Where(config => Configurations.Any(wildcard => FileSystemName.MatchesSimpleExpression(wildcard, config)))
24 | .ToList();
25 |
26 | if (configurations.Count == 0)
27 | throw new Exception($"No solution configurations have been found. Pattern: {string.Join(" | ", Configurations)}");
28 |
29 | return configurations;
30 | }
31 | }
--------------------------------------------------------------------------------
/Build/Build.Configuration.cs:
--------------------------------------------------------------------------------
1 | sealed partial class Build
2 | {
3 | const string Version = "1.0.0";
4 | readonly AbsolutePath ArtifactsDirectory = RootDirectory / "output";
5 | readonly AbsolutePath ChangeLogPath = RootDirectory / "Changelog.md";
6 |
7 | protected override void OnBuildInitialized()
8 | {
9 | Configurations = new[]
10 | {
11 | "Release*",
12 | "Installer*"
13 | };
14 |
15 | InstallersMap = new()
16 | {
17 | {Solution.Installer, Solution.OptionsBar}
18 | };
19 | }
20 | }
--------------------------------------------------------------------------------
/Build/Build.Installer.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Diagnostics.CodeAnalysis;
3 | using Nuke.Common.Git;
4 | using Nuke.Common.Utilities;
5 | using Serilog.Events;
6 |
7 | sealed partial class Build
8 | {
9 | Target CreateInstaller => _ => _
10 | .TriggeredBy(Compile)
11 | .OnlyWhenStatic(() => IsLocalBuild || GitRepository.IsOnMainOrMasterBranch())
12 | .Executes(() =>
13 | {
14 | foreach (var (installer, project) in InstallersMap)
15 | {
16 | Log.Information("Project: {Name}", project.Name);
17 |
18 | var exePattern = $"*{installer.Name}.exe";
19 | var exeFile = Directory.EnumerateFiles(installer.Directory, exePattern, SearchOption.AllDirectories).FirstOrDefault();
20 | if (exeFile is null) throw new Exception($"No installer file was found for the project: {installer.Name}");
21 |
22 | var directories = Directory.GetDirectories(project.Directory, "Publish*", SearchOption.AllDirectories);
23 | if (directories.Length == 0) throw new Exception("No files were found to create an installer");
24 |
25 | var proc = new Process();
26 | proc.StartInfo.FileName = exeFile;
27 | proc.StartInfo.Arguments = directories.Select(path => path.DoubleQuoteIfNeeded()).JoinSpace();
28 | proc.StartInfo.RedirectStandardOutput = true;
29 | proc.StartInfo.RedirectStandardError = true;
30 | proc.Start();
31 |
32 | RedirectStream(proc.StandardOutput, LogEventLevel.Information);
33 | RedirectStream(proc.StandardError, LogEventLevel.Error);
34 |
35 | proc.WaitForExit();
36 | if (proc.ExitCode != 0) throw new Exception($"The installer creation failed with ExitCode {proc.ExitCode}");
37 | }
38 | });
39 |
40 | [SuppressMessage("ReSharper", "TemplateIsNotCompileTimeConstantProblem")]
41 | void RedirectStream(StreamReader reader, LogEventLevel eventLevel)
42 | {
43 | while (!reader.EndOfStream)
44 | {
45 | var value = reader.ReadLine();
46 | if (value is null) continue;
47 |
48 | var matches = ArgumentsRegex.Matches(value);
49 | if (matches.Count > 0)
50 | {
51 | var parameters = matches
52 | .Select(match => match.Value.Substring(1, match.Value.Length - 2))
53 | .Cast