WIP Checkpoint - working on UI and testing additions for Firely.Packages.

This commit is contained in:
Gino Canessa 2024-07-12 16:47:17 -05:00
Родитель 0074097dc3
Коммит e32fd30f5c
15 изменённых файлов: 561 добавлений и 16 удалений

Просмотреть файл

@ -0,0 +1,80 @@
// <copyright file="FileNameAttribute.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// </copyright>
using System.Reflection;
using Xunit.Sdk;
namespace Microsoft.Health.Fhir.CodeGen.Tests.Extensions;
public class FileNameAttribute : DataAttribute
{
private readonly List<string> _filePaths = [];
/// <summary>Load file contents as the data source for a theory.</summary>
/// <param name="filePath">The absolute or relative path to the file to load.</param>
public FileNameAttribute(string filePath)
{
_filePaths.Add(filePath);
}
/// <summary>Load file contents as the data source for a theory.</summary>
/// <param name="filePath1">The first file path.</param>
/// <param name="filePath2">The second file path.</param>
public FileNameAttribute(string filePath1, string filePath2)
{
_filePaths.Add(filePath1);
_filePaths.Add(filePath2);
}
/// <summary>Load file contents as the data source for a theory.</summary>
/// <param name="filePath1">The first file path.</param>
/// <param name="filePath2">The second file path.</param>
/// <param name="filePath3">The third file path.</param>
public FileNameAttribute(string filePath1, string filePath2, string filePath3)
{
_filePaths.Add(filePath1);
_filePaths.Add(filePath2);
_filePaths.Add(filePath3);
}
/// <summary>Load file contents as the data source for a theory.</summary>
/// <param name="filePath1">The first file path.</param>
/// <param name="filePath2">The second file path.</param>
/// <param name="filePath3">The third file path.</param>
/// <param name="filePath4">The fourth file path.</param>
public FileNameAttribute(string filePath1, string filePath2, string filePath3, string filePath4)
{
_filePaths.Add(filePath1);
_filePaths.Add(filePath2);
_filePaths.Add(filePath3);
_filePaths.Add(filePath4);
}
/// <inheritDoc />
public override IEnumerable<object[]> GetData(MethodInfo testMethod)
{
ArgumentNullException.ThrowIfNull(testMethod);
List<object> paths = [];
foreach (string filePath in _filePaths)
{
// Get the absolute path to the file
string path = Path.IsPathRooted(filePath)
? filePath
: Path.GetRelativePath(Directory.GetCurrentDirectory(), filePath);
if (!File.Exists(path))
{
throw new ArgumentException($"Could not find file at path: {path}");
}
paths.Add(path);
}
return [[.. paths]];
}
}

Просмотреть файл

@ -57,6 +57,9 @@
</ItemGroup>
<ItemGroup>
<None Update="TestData\Packages\packages-01-empty.ini">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="TestData\R5\expansions\ValueSet-units-of-time.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

Просмотреть файл

@ -0,0 +1,42 @@
// <copyright file="PackagesTests.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// </copyright>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Health.Fhir.CodeGen.Tests.Extensions;
namespace Microsoft.Health.Fhir.CodeGen.Tests;
public class PackagesTests
{
[Theory]
[FileName("TestData/Packages/packages-01-empty.ini")]
public void LoadPackageIni(string path)
{
_ForPackages.IniData iniData = new(path);
iniData.Sections.Count.Should().Be(5);
((_ForPackages.IniData.IniSection?)iniData.Sections[0])?.Name.Should().Be("cache");
((_ForPackages.IniData.IniSection?)iniData.Sections[1])?.Name.Should().Be("urls");
((_ForPackages.IniData.IniSection?)iniData.Sections[2])?.Name.Should().Be("local");
((_ForPackages.IniData.IniSection?)iniData.Sections[3])?.Name.Should().Be("packages");
((_ForPackages.IniData.IniSection?)iniData.Sections[4])?.Name.Should().Be("package-sizes");
iniData.Sections[0].Should().Be(iniData["cache"]);
iniData.Sections[1].Should().Be(iniData["urls"]);
iniData.Sections[2].Should().Be(iniData["local"]);
iniData.Sections[3].Should().Be(iniData["packages"]);
iniData.Sections[4].Should().Be(iniData["package-sizes"]);
((_ForPackages.IniData.IniSection?)iniData.Sections[0])?.Values.Count.Should().NotBe(0);
((_ForPackages.IniData.IniSection?)iniData.Sections[0])?[0]!.Key.Should().Be("version");
((_ForPackages.IniData.IniSection?)iniData.Sections[0])?[0]!.Value.Should().Be("3");
}
}

Просмотреть файл

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.Health.Fhir.CodeGen._ForPackages;
public class DiskPackageCache : Firely.Fhir.Packages.DiskPackageCache
{
private bool _syncCacheIniFile;
public DiskPackageCache(string? rootDirectory = null, bool syncCacheIniFile = false)
: base(rootDirectory)
{
_syncCacheIniFile = syncCacheIniFile;
}
}

Просмотреть файл

@ -0,0 +1,314 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.Health.Fhir.CodeGen._ForPackages;
public class IniData
{
public static IniOptions DefaultOptions { get; } = new IniOptions();
public static IniOptions FhirPackagesOptions { get; } = new IniOptions
{
CommentCharacters = new[] { ';' },
CaseSensitive = true,
KeyValueAssignmentChar = '='
};
public class IniOptions
{
public char[] CommentCharacters { get; set; } = { ';', '#' };
public bool CaseSensitive { get; set; } = false;
public char KeyValueAssignmentChar { get; set; } = '=';
}
public class IniValue
{
public List<string>? BlockComments { get; set; }
public string Key { get; set; }
public string? Value { get; set; }
public string? InlineComment { get; set; }
public IniValue(string key, string? value, List<string>? blockComments, string? inlineComment)
{
Key = key;
Value = value;
BlockComments = blockComments;
InlineComment = inlineComment;
}
}
public class IniSection
{
public List<string>? BlockComments { get; set; }
public string Name { get; set; }
public string? InlineComment { get; set; }
private OrderedDictionary _values = new(StringComparer.OrdinalIgnoreCase);
public IOrderedDictionary Values => _values;
public IniSection(string name, List<string>? blockComments, string? inlineComment)
{
Name = name;
BlockComments = blockComments;
InlineComment = inlineComment;
}
public IniValue? this[string key]
{
get
{
if (_values.Contains(key) && _values[key] is IniValue value)
{
return value;
}
return null;
}
set
{
_values[key] = value;
}
}
public IniValue? this[int index]
{
get
{
if ((_values.Count >= index) && _values[index] is IniValue value)
{
return value;
}
return null;
}
set
{
_values[index] = value;
}
}
}
// default characters that are used to start a comment
private IniOptions _options;
private HashSet<char> _commentCharHash;
private readonly string _filename;
private readonly OrderedDictionary _sections = new(StringComparer.OrdinalIgnoreCase);
public IOrderedDictionary Sections => _sections;
private readonly OrderedDictionary _unsectionedValues = new(StringComparer.OrdinalIgnoreCase);
public IOrderedDictionary UnsectionedValues => _unsectionedValues;
public IniData(string filename, IniOptions? options = null)
{
if (string.IsNullOrEmpty(filename))
{
throw new ArgumentNullException(nameof(filename));
}
_filename = filename;
_options = options ?? new IniOptions();
// ensure the comment characters are valid
if (_options.CommentCharacters.Contains('[') ||
_options.CommentCharacters.Contains(']') ||
_options.CommentCharacters.Contains('=') ||
_options.CommentCharacters.Any(char.IsWhiteSpace))
{
throw new ArgumentException("Comment characters cannot contain '[', ']', '=', or whitespace characters", nameof(options));
}
_commentCharHash = new HashSet<char>(_options.CommentCharacters);
_ = loadFromDisk();
}
public IniSection? this[string key]
{
get
{
if (_sections.Contains(key) && _sections[key] is IniSection section)
{
return section;
}
return null;
}
set
{
_sections[key] = value;
}
}
public IniSection? this[int index]
{
get
{
if ((_sections.Count >= index) && _sections[index] is IniSection section)
{
return section;
}
return null;
}
set
{
_sections[index] = value;
}
}
private bool loadFromDisk()
{
if (!File.Exists(_filename))
{
return false;
}
List<string> blockComments = new();
IniSection? currentSection = null;
using (StreamReader reader = new StreamReader(_filename))
{
while (reader.Peek() >= 0)
{
string? line = reader.ReadLine();
if (line == null)
{
break;
}
string trimmed = line.Trim();
if (string.IsNullOrEmpty(trimmed))
{
continue;
}
// check if this is a block comment
if (_commentCharHash.Contains(trimmed[0]))
{
blockComments.Add(trimmed);
continue;
}
int commentIndex = trimmed.IndexOfAny(_options.CommentCharacters);
string content = commentIndex == -1 ? trimmed : trimmed[..commentIndex];
string? comment = commentIndex == -1 ? null : trimmed[(commentIndex + 1)..];
// check if this is a section
if (content[0] == '[' && content[^1] == ']')
{
// check for existing section
if (currentSection != null)
{
_sections[currentSection.Name] = currentSection;
}
string sectionName = content[1..^1];
currentSection = new IniSection(sectionName, blockComments.Count == 0 ? null : blockComments, comment);
blockComments.Clear();
continue;
}
// parse the content based on the KeyValueAssignmentChar, max of two components: Key and Value
string[] kvp = content.Split([ _options.KeyValueAssignmentChar ], 2);
IniValue value = new IniValue(kvp[0].Trim(), kvp.Length == 2 ? kvp[1].Trim() : null, blockComments.Count == 0 ? null : blockComments, comment);
if (currentSection != null)
{
currentSection[value.Key] = value;
}
else
{
_unsectionedValues[value.Key] = value;
}
blockComments.Clear();
}
// add the final section we were reading, if one exists
if (currentSection != null)
{
_sections[currentSection.Name] = currentSection;
}
}
return true;
}
private bool saveToDisk()
{
using (StreamWriter writer = new StreamWriter(_filename))
{
// start with any unsectioned values
foreach (IniValue value in _unsectionedValues.Values)
{
if (value.BlockComments != null)
{
foreach (string comment in value.BlockComments)
{
writer.WriteLine(comment);
}
}
writer.WriteLine($"{value.Key}{_options.KeyValueAssignmentChar}{value.Value} {value.InlineComment}");
}
// write each section
foreach (IniSection section in _sections.Values)
{
if (section.BlockComments != null)
{
foreach (string comment in section.BlockComments)
{
writer.WriteLine(comment);
}
}
writer.WriteLine($"[{section.Name}] {section.InlineComment}");
foreach (IniValue value in section.Values)
{
if (value.BlockComments != null)
{
foreach (string comment in value.BlockComments)
{
writer.WriteLine(comment);
}
}
writer.WriteLine($"{value.Key}{_options.KeyValueAssignmentChar}{value.Value} {value.InlineComment}");
}
}
}
return true;
}
}
#nullable restore

Просмотреть файл

@ -7,6 +7,7 @@
<Application.Styles>
<FluentTheme />
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
<StyleInclude Source="avares://fhir-codegen/Icons.axaml" />
</Application.Styles>
</Application>

Просмотреть файл

@ -8,7 +8,10 @@
<!-- Add Styles Here -->
<Style>
<!-- For icon geometries, see https://avaloniaui.github.io/icons.html -->
<Style.Resources>
<StreamGeometry x:Key="book_question_mark_regular">M10.9998 8.01752C10.9905 8.42363 10.6584 8.74999 10.25 8.74999C9.5 8.74999 9.5 7.9989 9.5 7.9989L9.5 7.99777L9.50001 7.99539L9.50006 7.99017C9.50032 7.9755 9.50072 7.96084 9.50144 7.94618C9.50262 7.92198 9.50473 7.89159 9.50846 7.8559C9.51591 7.78477 9.52996 7.69092 9.55665 7.58186C9.60973 7.36492 9.71565 7.07652 9.92848 6.78906C10.3825 6.17582 11.1982 5.72727 12.513 5.7501C13.4627 5.76659 14.3059 6.16497 14.834 6.82047C15.371 7.48704 15.5517 8.3902 15.1964 9.27853C14.8342 10.1839 14.0149 10.5437 13.5442 10.7503L13.4932 10.7728C13.2147 10.8957 13.0813 10.9599 13.0013 11.024L13 11.0251L13 11.7492C13.0001 12.1634 12.6643 12.4999 12.2501 12.5C11.8359 12.5 11.5001 12.1643 11.5 11.7501L11.5 11C11.5 10.4769 11.752 10.1029 12.0633 9.85345C12.3134 9.65303 12.6276 9.51483 12.8491 9.4174L12.8875 9.40049C13.4292 9.16137 13.6868 9.01346 13.8036 8.72145C13.9483 8.35977 13.8789 8.02596 13.6659 7.76153C13.4439 7.48604 13.0371 7.25943 12.487 7.24988C11.5518 7.23364 11.2425 7.53509 11.134 7.68162C11.0656 7.77404 11.0309 7.86797 11.0137 7.93838C11.0052 7.973 11.0017 7.99908 11.0003 8.01197L10.9998 8.01752Z M12.25 15.5C12.8023 15.5 13.25 15.0523 13.25 14.5C13.25 13.9477 12.8023 13.5 12.25 13.5C11.6977 13.5 11.25 13.9477 11.25 14.5C11.25 15.0523 11.6977 15.5 12.25 15.5Z M4 4.5C4 3.11929 5.11929 2 6.5 2H18C19.3807 2 20.5 3.11929 20.5 4.5V18.75C20.5 19.1642 20.1642 19.5 19.75 19.5H5.5C5.5 20.0523 5.94772 20.5 6.5 20.5H19.75C20.1642 20.5 20.5 20.8358 20.5 21.25C20.5 21.6642 20.1642 22 19.75 22H6.5C5.11929 22 4 20.8807 4 19.5V4.5ZM5.5 4.5V18H19V4.5C19 3.94772 18.5523 3.5 18 3.5H6.5C5.94772 3.5 5.5 3.94772 5.5 4.5Z</StreamGeometry>
<StreamGeometry x:Key="home_regular">M21.6062 5.85517C23.0048 4.71494 24.9952 4.71494 26.3938 5.85517L39.5688 16.5966C40.4736 17.3342 41 18.4492 41 19.628V39.1134C41 41.2599 39.2875 43 37.175 43H32.075C29.9625 43 28.25 41.2599 28.25 39.1134V29.7492C28.25 29.0337 27.6792 28.4536 26.975 28.4536H21.025C20.3208 28.4536 19.75 29.0337 19.75 29.7492V39.1134C19.75 41.2599 18.0375 43 15.925 43H10.825C8.71251 43 7 41.2599 7 39.1134V19.628C7 18.4493 7.52645 17.3342 8.43124 16.5966L21.6062 5.85517ZM24.7979 7.87612C24.3317 7.49604 23.6683 7.49604 23.2021 7.87612L10.0271 18.6175C9.72548 18.8634 9.55 19.2351 9.55 19.628V39.1134C9.55 39.8289 10.1208 40.4089 10.825 40.4089H15.925C16.6292 40.4089 17.2 39.8289 17.2 39.1134V29.7492C17.2 27.6027 18.9125 25.8626 21.025 25.8626H26.975C29.0875 25.8626 30.8 27.6027 30.8 29.7492V39.1134C30.8 39.8289 31.3708 40.4089 32.075 40.4089H37.175C37.8792 40.4089 38.45 39.8289 38.45 39.1134V19.628C38.45 19.2351 38.2745 18.8634 37.9729 18.6175L24.7979 7.87612Z</StreamGeometry>
<StreamGeometry x:Key="line_horizontal_3_regular">M2 4.5C2 4.22386 2.22386 4 2.5 4H17.5C17.7761 4 18 4.22386 18 4.5C18 4.77614 17.7761 5 17.5 5H2.5C2.22386 5 2 4.77614 2 4.5Z M2 9.5C2 9.22386 2.22386 9 2.5 9H17.5C17.7761 9 18 9.22386 18 9.5C18 9.77614 17.7761 10 17.5 10H2.5C2.22386 10 2 9.77614 2 9.5Z M2.5 14C2.22386 14 2 14.2239 2 14.5C2 14.7761 2.22386 15 2.5 15H17.5C17.7761 15 18 14.7761 18 14.5C18 14.2239 17.7761 14 17.5 14H2.5Z</StreamGeometry>
</Style.Resources>
</Style>

Просмотреть файл

@ -8,12 +8,20 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
namespace fhir_codegen.ViewModels;
public partial class CoreComparisonViewModel : ViewModelBase
public partial class CoreComparisonViewModel : ViewModelBase, INavigableViewModel
{
public static string Label => "Compare FHIR Releases";
public static StreamGeometry? IconGeometry => (Application.Current?.TryGetResource("book_question_mark_regular", out object? icon) ?? false) && icon is StreamGeometry sg
? sg
: null;
[ObservableProperty]
private string _sourcePackageDirective = "";

Просмотреть файл

@ -0,0 +1,21 @@
// <copyright file="INavigablePage.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// </copyright>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Media;
namespace fhir_codegen.ViewModels;
internal interface INavigableViewModel
{
public static string Label { get; } = " - ";
public static StreamGeometry? IconGeometry { get; }
}

Просмотреть файл

@ -9,6 +9,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using fhir_codegen.Views;
@ -17,12 +18,49 @@ namespace fhir_codegen.ViewModels;
public partial class MainWindowViewModel : ViewModelBase
{
[ObservableProperty]
private bool _isPaneOpen;
[ObservableProperty]
private UserControl _currentPage = new WelcomePageView();
[ObservableProperty]
private NavigationItemTemplate? _selectedNavigationItem;
partial void OnSelectedNavigationItemChanged(NavigationItemTemplate? value)
{
if (value == null)
{
return;
}
UserControl? target = (UserControl?)Activator.CreateInstance(value.Target);
if (target == null)
{
return;
}
CurrentPage = target;
}
[ObservableProperty]
private List<NavigationItemTemplate> _navigationItems = new List<NavigationItemTemplate>
{
new NavigationItemTemplate
{
Target = typeof(WelcomePageView),
Label = WelcomePageViewModel.Label,
IconGeometry = WelcomePageViewModel.IconGeometry,
},
new NavigationItemTemplate
{
Target = typeof(CoreComparisonView),
Label = CoreComparisonViewModel.Label,
IconGeometry = CoreComparisonViewModel.IconGeometry,
},
};
[RelayCommand]
private void TriggerPane()
@ -30,3 +68,12 @@ public partial class MainWindowViewModel : ViewModelBase
IsPaneOpen = !IsPaneOpen;
}
}
public class NavigationItemTemplate
{
public required Type Target { get; init; }
public required string Label { get; init; }
public required StreamGeometry? IconGeometry { get; init; }
}

Просмотреть файл

@ -8,12 +8,22 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
namespace fhir_codegen.ViewModels;
public partial class WelcomePageViewModel : ViewModelBase
public partial class WelcomePageViewModel : ViewModelBase, INavigableViewModel
{
public static string Label => "Home";
public static StreamGeometry? IconGeometry => (Application.Current?.TryGetResource("home_regular", out object? icon) ?? false) && icon is StreamGeometry sg
? sg
: null;
[ObservableProperty]
private string _header = "Welcome to the FHIR Codegen";
private string _header = "FHIR Codegen - FHIR Cache Content";
}

Просмотреть файл

@ -6,6 +6,7 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="fhir_codegen.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Name="Home"
Title="FHIR Code Generator">
<Design.DataContext>
@ -15,8 +16,8 @@
</Design.DataContext>
<SplitView IsPaneOpen="{Binding IsPaneOpen}"
CompactPaneLength="45"
DisplayMode="CompactInline"
CompactPaneLength="44"
OpenPaneLength="300"
Background="#1e1e1e"
PaneBackground="#1e1e1e">
@ -34,23 +35,17 @@
<PathIcon Data="{StaticResource line_horizontal_3_regular}" />
</Button>
<!--<ListBox Margin="2 0 -100 0" Padding="0" ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedListItem}">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Padding" Value="12 8"></Setter>
</Style>
</ListBox.Styles>
<ListBox ItemsSource="{Binding NavigationItems}"
SelectedItem="{Binding SelectedNavigationItem}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type models:ListItemTemplate}">
<StackPanel Spacing="17" Orientation="Horizontal">
<PathIcon Data="{Binding ListItemIcon}" Width="14" />
<DataTemplate DataType="{x:Type vm:NavigationItemTemplate}">
<StackPanel Spacing="12" Margin="4 4" Orientation="Horizontal">
<PathIcon Data="{Binding IconGeometry}" />
<TextBlock Text="{Binding Label}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>-->
</ListBox>
</StackPanel>
</SplitView.Pane>

Просмотреть файл

@ -4,6 +4,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
Name="Home"
x:Class="fhir_codegen.Views.WelcomePageView"
x:DataType="vm:WelcomePageViewModel">
@ -17,4 +18,5 @@
<Label Content="{Binding Header}"/>
</StackPanel>
</UserControl>

Просмотреть файл

@ -1,6 +1,8 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Media;
using fhir_codegen.ViewModels;
namespace fhir_codegen.Views;

Просмотреть файл

@ -20,6 +20,7 @@
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.11" />
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.0.6" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.11" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.11" />
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.11" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.11" />