├── img ├── hh_main.png ├── hh_report_main.png ├── hh_report_errors.png ├── hh_report_includes.png ├── hh_report_missing.png ├── hh_godot_win.json └── hh_blender_win.json ├── header_hero ├── AppIcon.ico ├── App.axaml ├── App.axaml.cs ├── ProgressDialog.axaml ├── MessageBox3.axaml ├── Program.cs ├── header_hero.sln ├── Data │ ├── SourceFile.cs │ └── Project.cs ├── app.manifest ├── ProgressDialog.axaml.cs ├── MessageBox3.axaml.cs ├── header_hero.csproj ├── Utils │ ├── AppSettings.cs │ └── SystemIncludesLocator.cs ├── Parser │ ├── Analytics.cs │ ├── Parser.cs │ ├── Report.cs │ └── Scanner.cs ├── MainWindow.axaml ├── ReportWindow.axaml.cs ├── MainWindow.axaml.cs └── ReportWindow.axaml ├── .gitignore ├── .github └── workflows │ └── build.yml └── readme.md /img/hh_main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aras-p/header_hero/HEAD/img/hh_main.png -------------------------------------------------------------------------------- /img/hh_report_main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aras-p/header_hero/HEAD/img/hh_report_main.png -------------------------------------------------------------------------------- /header_hero/AppIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aras-p/header_hero/HEAD/header_hero/AppIcon.ico -------------------------------------------------------------------------------- /img/hh_report_errors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aras-p/header_hero/HEAD/img/hh_report_errors.png -------------------------------------------------------------------------------- /img/hh_report_includes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aras-p/header_hero/HEAD/img/hh_report_includes.png -------------------------------------------------------------------------------- /img/hh_report_missing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aras-p/header_hero/HEAD/img/hh_report_missing.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | obj 2 | bin 3 | .vs 4 | *.exe 5 | *.user 6 | *.suo 7 | *.DS_Store 8 | *.pidb 9 | *.userprefs 10 | .idea 11 | -------------------------------------------------------------------------------- /header_hero/App.axaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /img/hh_godot_win.json: -------------------------------------------------------------------------------- 1 | { 2 | "ProjectRoot": "C:\\code\\projects\\other\\godot", 3 | "ScanDirectories": [ 4 | "core", 5 | "drivers", 6 | "editor", 7 | "main", 8 | "modules", 9 | "platform", 10 | "scene", 11 | "servers" 12 | ], 13 | "IncludeDirectories": [ 14 | ".", 15 | "thirdparty\\graphite\\src", 16 | "thirdparty\\jolt_physics", 17 | "thirdparty\\mbedtls\\include" 18 | ], 19 | "PrecompiledHeader": "" 20 | } -------------------------------------------------------------------------------- /header_hero/App.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls.ApplicationLifetimes; 3 | using Avalonia.Markup.Xaml; 4 | 5 | namespace HeaderHero; 6 | 7 | public partial class App : Application 8 | { 9 | public override void Initialize() 10 | { 11 | AvaloniaXamlLoader.Load(this); 12 | } 13 | 14 | public override void OnFrameworkInitializationCompleted() 15 | { 16 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) 17 | { 18 | desktop.MainWindow = new MainWindow(); 19 | } 20 | 21 | base.OnFrameworkInitializationCompleted(); 22 | } 23 | } -------------------------------------------------------------------------------- /header_hero/ProgressDialog.axaml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /header_hero/MessageBox3.axaml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /header_hero/Program.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using System; 3 | 4 | namespace HeaderHero; 5 | 6 | class Program 7 | { 8 | // Initialization code. Don't use any Avalonia, third-party APIs or any 9 | // SynchronizationContext-reliant code before AppMain is called: things aren't initialized 10 | // yet and stuff might break. 11 | [STAThread] 12 | public static void Main(string[] args) => BuildAvaloniaApp() 13 | .StartWithClassicDesktopLifetime(args); 14 | 15 | // Avalonia configuration, don't remove; also used by visual designer. 16 | public static AppBuilder BuildAvaloniaApp() 17 | => AppBuilder.Configure() 18 | .UsePlatformDetect() 19 | .WithInterFont() 20 | .LogToTrace(); 21 | } -------------------------------------------------------------------------------- /header_hero/header_hero.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "header_hero", "header_hero.csproj", "{F0122516-3BC9-48B2-BEE5-4F7D03E0F52A}" 4 | EndProject 5 | Global 6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 7 | Debug|Any CPU = Debug|Any CPU 8 | Release|Any CPU = Release|Any CPU 9 | EndGlobalSection 10 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 11 | {F0122516-3BC9-48B2-BEE5-4F7D03E0F52A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 12 | {F0122516-3BC9-48B2-BEE5-4F7D03E0F52A}.Debug|Any CPU.Build.0 = Debug|Any CPU 13 | {F0122516-3BC9-48B2-BEE5-4F7D03E0F52A}.Release|Any CPU.ActiveCfg = Release|Any CPU 14 | {F0122516-3BC9-48B2-BEE5-4F7D03E0F52A}.Release|Any CPU.Build.0 = Release|Any CPU 15 | EndGlobalSection 16 | EndGlobal 17 | -------------------------------------------------------------------------------- /header_hero/Data/SourceFile.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace HeaderHero.Data; 4 | 5 | public class SourceFile 6 | { 7 | public List LocalIncludes { get; init; } = []; 8 | public List SystemIncludes { get; init; } = []; 9 | public List AbsoluteIncludes { get; set; } = []; 10 | public int Lines { get; init; } 11 | public bool Precompiled { get; init; } 12 | 13 | public static bool IsTranslationUnitExtension(string ext) 14 | { 15 | return ext is ".cpp" or ".c" or ".cc" or ".cxx" or ".mm" or ".m"; 16 | } 17 | public static bool IsHeaderExtension(string ext) 18 | { 19 | return ext is ".h" or ".hh" or ".hpp" or ".hxx" or "" or "."; 20 | } 21 | 22 | public static bool IsTranslationUnitPath(string path) 23 | { 24 | return IsTranslationUnitExtension(System.IO.Path.GetExtension(path)); 25 | } 26 | } -------------------------------------------------------------------------------- /header_hero/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Build Header Hero 3 | 4 | on: 5 | push: 6 | branches: [ "main" ] 7 | pull_request: 8 | branches: [ "main" ] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | fail-fast: true 15 | matrix: 16 | rid: [win-x64, osx-arm64, linux-x64] 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | 22 | - name: Install .NET 23 | uses: actions/setup-dotnet@v4 24 | with: 25 | dotnet-version: 9.0.x 26 | 27 | - name: Restore 28 | working-directory: header_hero 29 | run: dotnet restore 30 | 31 | - name: Publish ${{ matrix.rid }} 32 | working-directory: header_hero 33 | run: | 34 | dotnet publish \ 35 | -c Release \ 36 | -r ${{ matrix.rid }} \ 37 | --self-contained true \ 38 | /p:PublishSingleFile=true \ 39 | /p:PublishTrimmed=true \ 40 | /p:TrimMode=link \ 41 | -o ../out/${{ matrix.rid }} 42 | 43 | - name: Upload artifact 44 | uses: actions/upload-artifact@v4 45 | with: 46 | name: HeaderHero-${{ matrix.rid }}.zip 47 | path: out/${{ matrix.rid }} 48 | -------------------------------------------------------------------------------- /header_hero/ProgressDialog.axaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Avalonia.Controls; 4 | using Avalonia.Threading; 5 | using HeaderHero.Parser; 6 | 7 | namespace HeaderHero; 8 | 9 | public partial class ProgressDialog : Window 10 | { 11 | readonly ProgressFeedback _feedback = new(); 12 | Func _work; 13 | public ProgressDialog() 14 | { 15 | InitializeComponent(); 16 | } 17 | 18 | public void Start(Func work) 19 | { 20 | _work = work; 21 | Opened += async (_, _) => 22 | { 23 | await RunWorkAsync(); 24 | }; 25 | } 26 | 27 | async Task RunWorkAsync() 28 | { 29 | try 30 | { 31 | await Task.Run(() => _work!(_feedback)); 32 | } 33 | catch (Exception ex) 34 | { 35 | await Dispatcher.UIThread.InvokeAsync(() => 36 | { 37 | Console.WriteLine(ex); 38 | }); 39 | } 40 | 41 | // Close the dialog when work is done 42 | await Dispatcher.UIThread.InvokeAsync(Close); 43 | } 44 | 45 | public void Poll() 46 | { 47 | ProgressBar.Maximum = Math.Max(1, _feedback.Count); 48 | ProgressBar.Value = Math.Clamp(_feedback.Item, 0, ProgressBar.Maximum); 49 | 50 | ProgressReportLabel.Text = $"{_feedback.Item}/{_feedback.Count}"; 51 | MessageLabel.Text = _feedback.Message; 52 | Title = _feedback.Title; 53 | } 54 | } -------------------------------------------------------------------------------- /header_hero/MessageBox3.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | using Avalonia.Input; 3 | 4 | namespace HeaderHero; 5 | 6 | public partial class MessageBox3 : Window 7 | { 8 | readonly int cancelIndex; 9 | public MessageBox3(string title, string message, params string[] buttons) 10 | { 11 | InitializeComponent(); 12 | Opened += (_, _) => 13 | { 14 | // otherwise keyboard navigation sometimes stays in the parent window 15 | if (Owner != null) Owner.IsEnabled = false; 16 | }; 17 | 18 | Title = title; 19 | MessageText.Text = message; 20 | 21 | cancelIndex = buttons.Length - 1; 22 | 23 | for (int i = 0; i < buttons.Length; i++) 24 | { 25 | int index = i; 26 | var btn = new Button 27 | { 28 | Content = buttons[i], 29 | MinWidth = 60 30 | }; 31 | btn.Click += (_, _) => Close(index); 32 | if (index == 0) 33 | btn.IsDefault = true; 34 | ButtonsPanel.Children.Add(btn); 35 | } 36 | 37 | Closing += OnClosing; 38 | KeyDown += OnKeyDown; 39 | } 40 | 41 | void OnClosing(object sender, WindowClosingEventArgs e) 42 | { 43 | if (Owner != null) Owner.IsEnabled = true; 44 | if (!e.IsProgrammatic) 45 | Close(cancelIndex); 46 | } 47 | 48 | void OnKeyDown(object sender, KeyEventArgs e) 49 | { 50 | if (e.Key == Key.Escape) 51 | { 52 | if (Owner != null) Owner.IsEnabled = true; 53 | Close(cancelIndex); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /header_hero/header_hero.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | WinExe 4 | net9.0 5 | disable 6 | app.manifest 7 | true 8 | HeaderHero 9 | HeaderHero 10 | AppIcon.ico 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | None 23 | All 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /header_hero/Utils/AppSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text.Json; 4 | 5 | namespace HeaderHero.Utils; 6 | 7 | public sealed class AppSettings 8 | { 9 | public string LastProject { get; set; } 10 | 11 | static readonly Lazy _instance = new(Load); 12 | public static AppSettings Instance => _instance.Value; 13 | 14 | static string SettingsPath => 15 | Path.Combine( 16 | Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), 17 | "HeaderHero", 18 | "settings.json" 19 | ); 20 | 21 | static AppSettings Load() 22 | { 23 | try 24 | { 25 | if (File.Exists(SettingsPath)) 26 | { 27 | var json = File.ReadAllText(SettingsPath); 28 | using var doc = JsonDocument.Parse(json); 29 | if (doc.RootElement.TryGetProperty("LastProject", out var prop)) 30 | { 31 | return new AppSettings {LastProject = prop.GetString() ?? ""}; 32 | } 33 | } 34 | } 35 | catch 36 | { 37 | // ignore errors, return default 38 | } 39 | return new AppSettings(); 40 | } 41 | 42 | public void Save() 43 | { 44 | try 45 | { 46 | var dir = Path.GetDirectoryName(SettingsPath)!; 47 | if (dir != null && !Directory.Exists(dir)) 48 | Directory.CreateDirectory(dir); 49 | using var stream = File.Create(SettingsPath); 50 | using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true }); 51 | writer.WriteStartObject(); 52 | writer.WriteString("LastProject", LastProject); 53 | writer.WriteEndObject(); 54 | } 55 | catch 56 | { 57 | // ignore errors 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /header_hero/Parser/Analytics.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace HeaderHero.Parser; 5 | 6 | public class ItemAnalytics 7 | { 8 | public readonly HashSet AllIncludes = []; 9 | public int TotalIncludeLines; 10 | public readonly HashSet AllIncludedBy = []; 11 | public readonly HashSet TranslationUnitsIncludedBy = []; 12 | public bool Analyzed; 13 | } 14 | 15 | public class Analytics 16 | { 17 | public readonly Dictionary Items = new(); 18 | 19 | public static Analytics Analyze(Data.Project project) 20 | { 21 | Analytics analytics = new Analytics(); 22 | foreach (var kvp in project.Files) 23 | analytics.Analyze(kvp.Key, project); 24 | return analytics; 25 | } 26 | 27 | ItemAnalytics Analyze(string path, Data.Project project) 28 | { 29 | if (!Items.TryGetValue(path, out var a)) 30 | { 31 | a = new ItemAnalytics(); 32 | Items.Add(path, a); 33 | } 34 | if (a.Analyzed) 35 | return a; 36 | a.Analyzed = true; 37 | 38 | Data.SourceFile sf = project.Files[path]; 39 | foreach (string include in sf.AbsoluteIncludes) 40 | { 41 | if (include == path) 42 | continue; 43 | 44 | bool is_tu = Data.SourceFile.IsTranslationUnitPath(path); 45 | 46 | ItemAnalytics ai = Analyze(include, project); 47 | a.AllIncludes.Add(include); 48 | ai.AllIncludedBy.Add(path); 49 | if (is_tu) 50 | ai.TranslationUnitsIncludedBy.Add (path); 51 | 52 | 53 | a.AllIncludes.UnionWith(ai.AllIncludes); 54 | foreach (string inc in ai.AllIncludes) { 55 | Items[inc].AllIncludedBy.Add(path); 56 | if (is_tu) 57 | Items[inc].TranslationUnitsIncludedBy.Add (path); 58 | } 59 | } 60 | 61 | a.TotalIncludeLines = a.AllIncludes.Sum(f => project.Files[f].Lines); 62 | return a; 63 | } 64 | } -------------------------------------------------------------------------------- /header_hero/MainWindow.axaml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |