Skip to content

Commit

Permalink
Merge pull request #1 from hikarin522/develop
Browse files Browse the repository at this point in the history
v0.3.0
  • Loading branch information
hikarin522 authored May 19, 2022
2 parents 9ca560b + fc97475 commit f1e42a8
Show file tree
Hide file tree
Showing 16 changed files with 96 additions and 91 deletions.
20 changes: 17 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@ name: Build

on:
push:
branches: [ master, develop ]
pull_request:
branches: [ master ]
workflow_dispatch:
inputs:
publish_github:
type: boolean
description: Publish to GitHub Packages
default: false
publish_nuget:
type: boolean
description: Publish to nuget.org
default: false

env:
T4_MAX_TYPE_PARAM: 16
Expand Down Expand Up @@ -36,6 +44,12 @@ jobs:
name: nupkg
path: artifacts/*.nupkg
- name: NuGet add souce
id: nuget_add_source
if: ${{ github.event.inputs.publish_github }}
run: dotnet nuget add source -u hikarin522 -p ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text -n "github" "https://nuget.pkg.github.com/hikarin522/index.json"
- name: Publish
- name: Publish GitHub Packages
if: ${{ steps.nuget_add_source.conclusion == 'success' }}
run: dotnet nuget push "artifacts/*.nupkg" -k ${{ secrets.GITHUB_TOKEN }} -s "github" --skip-duplicate
- name: Publish NuGet
if: ${{ github.event.inputs.publish_nuget }}
run: dotnet nuget push "artifacts/*.nupkg" -k ${{ secrets.NUGET_OLG_API_KEY }} -s "nuget" --skip-duplicate
3 changes: 2 additions & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>

<Version>0.2.0</Version>
<Version>0.3.0</Version>
<Authors>hikarin522</Authors>
<Copyright>(c) 2022 hikarin522.</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
Expand All @@ -12,5 +12,6 @@
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageTags>variant;union</PackageTags>
<Description>C# source generator to generate efficient and type-safe variant types for unmanaged types.</Description>
</PropertyGroup>
</Project>
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ readonly partial struct SampleVariant: IEquatable<SampleVariant>

private readonly Union Value;

public TypeIndex Index { get; }
public readonly TypeIndex Index;

public int Item1 => Index == TypeIndex.Type1 ? Value.Item1 : throw new InvalidCastException();
public long Item2 => Index == TypeIndex.Type2 ? Value.Item2 : throw new InvalidCastException();
Expand Down
9 changes: 6 additions & 3 deletions ValueVariant.Generator/ValueVariant.Generator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,18 @@
<Compile Update="@(T4->'%(RelativeDir)%(Filename).cs')">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>%(RelativeDir)%(Filename).tt</DependentUpon>
<DependentUpon>%(Filename).tt</DependentUpon>
</Compile>
</ItemGroup>

<Target Name="T4Generate" BeforeTargets="BeforeBuild" Condition="'$(MSBuildRuntimeType)' != 'Full'">
<Exec Command="dotnet t4 &quot;%(T4.Filename).tt&quot; -c &quot;$(RootNamespace).%(T4.Filename)&quot; -o &quot;%(T4.Filename).cs&quot;" WorkingDirectory="$(MSBuildProjectDirectory)\%(T4.RelativeDir)" />
<ItemGroup>
<Compile Remove="@(T4->'%(RelativeDir)%(Filename).cs')" />
<Compile Include="@(T4->'%(RelativeDir)%(Filename).cs')" />
<Compile Include="@(T4->'%(RelativeDir)%(Filename).cs')" Exclude="@(Compile)">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>%(Filename).tt</DependentUpon>
</Compile>
</ItemGroup>
</Target>

Expand Down
57 changes: 14 additions & 43 deletions ValueVariant.Generator/ValueVariantTemplate.tt
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ namespace <#= Namespace #>
using T<#= i #> = global::<#= Types[i - 1] #>;
<# } #>

[StructLayout(LayoutKind.Sequential, Pack = 1)]
<# if (Options.HasFlag(ValueVariantGenerateOptions.MessagePackFormatter)) { #>
[MessagePackFormatter(typeof(<#= TypeName #>.MessagePackFormatter))]
<# } #>
Expand All @@ -60,7 +59,7 @@ namespace <#= Namespace #>

public readonly TypeIndex Index;

byte V2.Details.IValueVariant.TypeIndexByte { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (byte)this.Index; }
byte V2.Details.IValueVariant.TypeIndex { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (byte)this.Index; }

private <#= TypeName #>(in Union value, TypeIndex index)
=> (this.Value, this.Index) = (value, index);
Expand Down Expand Up @@ -116,75 +115,47 @@ namespace <#= Namespace #>

public interface IFuncVisitor<in TA1, out TR> : <#= Join(1, Count, e => $"V2.IValueVariantFuncVisitor<T{e}, TA1, TR>") #> { }

public void Accept(IActionVisitor visitor)
=> this.AcceptActionVisitor(visitor);

public void Accept<TA1>(IActionVisitor<TA1> visitor, TA1 arg1)
=> this.AcceptActionVisitor(visitor, arg1);

public TR Accept<TR>(IFuncVisitor<TR> visitor)
{
this.AcceptFuncVisitor(visitor, out TR result);
this.Accept(visitor, out TR result);
return result;
}

public TR Accept<TA1, TR>(IFuncVisitor<TA1, TR> visitor, TA1 arg1)
{
this.AcceptFuncVisitor(visitor, arg1, out TR result);
return result;
}

public void Accept(V2.IValueVariantGenericActionVisitor visitor)
=> this.AcceptGenericActionVisitor(visitor);

public void Accept<TA1>(V2.IValueVariantGenericActionVisitor<TA1> visitor, TA1 arg1)
=> this.AcceptGenericActionVisitor(visitor, arg1);

public TR Accept<TA1, TR>(V2.IValueVariantGenericFuncVisitor<TA1, TR> visitor, TA1 arg1)
{
this.AcceptGenericFuncVisitor(visitor, arg1, out TR result);
return result;
}

public TR Accept<TR>(V2.IValueVariantGenericFuncVisitor<TR> visitor)
{
this.AcceptGenericFuncVisitor(visitor, out TR result);
this.Accept(visitor, arg1, out TR result);
return result;
}

public void AcceptActionVisitor<TV>(in TV visitor)
public void Accept<TV>(in TV visitor)
where TV : <#= Join(1, Count, e => $"V2.IValueVariantActionVisitor<T{e}>") #>
=> V2.Details.ValueVariant<<#= Join(1, Count, e => $"T{e}") #>>.AcceptActionVisitor(this, visitor);

public void AcceptActionVisitor<TV, TA1>(in TV visitor, TA1 arg1)
public void Accept<TV, TA1>(in TV visitor, TA1 arg1)
where TV : <#= Join(1, Count, e => $"V2.IValueVariantActionVisitor<T{e}, TA1>") #>
=> V2.Details.ValueVariant<<#= Join(1, Count, e => $"T{e}") #>>.AcceptActionVisitor(this, visitor, arg1);

public void AcceptFuncVisitor<TV, TR>(in TV visitor, out TR result)
public void Accept<TV, TR>(in TV visitor, out TR result)
where TV : <#= Join(1, Count, e => $"V2.IValueVariantFuncVisitor<T{e}, TR>") #>
=> result = V2.Details.ValueVariant<<#= Join(1, Count, e => $"T{e}") #>>.AcceptFuncVisitor<T, TV, TR>(this, visitor);

public void AcceptFuncVisitor<TV, TA1, TR>(in TV visitor, TA1 arg1, out TR result)
public void Accept<TV, TA1, TR>(in TV visitor, TA1 arg1, out TR result)
where TV : <#= Join(1, Count, e => $"V2.IValueVariantFuncVisitor<T{e}, TA1, TR>") #>
=> result = V2.Details.ValueVariant<<#= Join(1, Count, e => $"T{e}") #>>.AcceptFuncVisitor<T, TV, TA1, TR>(this, visitor, arg1);

public void AcceptGenericActionVisitor<TV>(in TV visitor)
where TV : V2.IValueVariantGenericActionVisitor
public void Accept(V2.IValueVariantGenericActionVisitor visitor)
=> V2.Details.ValueVariant<<#= Join(1, Count, e => $"T{e}") #>>.AcceptGenericActionVisitor(this, visitor);

public void AcceptGenericActionVisitor<TV, TA1>(in TV visitor, TA1 arg1)
where TV : V2.IValueVariantGenericActionVisitor<TA1>
public void Accept<TA1>(V2.IValueVariantGenericActionVisitor<TA1> visitor, TA1 arg1)
=> V2.Details.ValueVariant<<#= Join(1, Count, e => $"T{e}") #>>.AcceptGenericActionVisitor(this, visitor, arg1);

public void AcceptGenericFuncVisitor<TV, TR>(in TV visitor, out TR result)
where TV : V2.IValueVariantGenericFuncVisitor<TR>
=> result = V2.Details.ValueVariant<<#= Join(1, Count, e => $"T{e}") #>>.AcceptGenericFuncVisitor<T, TV, TR>(this, visitor);
public TR Accept<TR>(V2.IValueVariantGenericFuncVisitor<TR> visitor)
=> V2.Details.ValueVariant<<#= Join(1, Count, e => $"T{e}") #>>.AcceptGenericFuncVisitor<T, V2.IValueVariantGenericFuncVisitor<TR>, TR>(this, visitor);

public void AcceptGenericFuncVisitor<TV, TA1, TR>(in TV visitor, TA1 arg1, out TR result)
where TV : V2.IValueVariantGenericFuncVisitor<TA1, TR>
=> result = V2.Details.ValueVariant<<#= Join(1, Count, e => $"T{e}") #>>.AcceptGenericFuncVisitor<T, TV, TA1, TR>(this, visitor, arg1);
public TR Accept<TA1, TR>(V2.IValueVariantGenericFuncVisitor<TA1, TR> visitor, TA1 arg1)
=> V2.Details.ValueVariant<<#= Join(1, Count, e => $"T{e}") #>>.AcceptGenericFuncVisitor<T, V2.IValueVariantGenericFuncVisitor<TA1, TR>, TA1, TR>(this, visitor, arg1);

public struct DefaultConverter : IFuncVisitor<T>
public readonly struct DefaultConverter : IFuncVisitor<T>
{
<# for (var i = 1; i <= Count; ++i ) { #>
public T Visit(in T<#= i #> value) => value;
Expand Down
16 changes: 8 additions & 8 deletions ValueVariant.Test/TestVariant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ namespace ValueVariant.Test;
[ValueVariant(ValueVariantGenerateOptions.MessagePackFormatter)]
public readonly partial struct TestVariant : IValueVariant<TestVariant, int, TestStruct<Guid>, DateTime> { }

public readonly struct TestVariantVisitor : TestVariant.IFuncVisitor<int>
public readonly struct TestVariantVisitor : TestVariant.IFuncVisitor<Type>
{
public int Visit(in int value)
=> 1;
public Type Visit(in int value)
=> typeof(int);

public int Visit(in TestStruct<Guid> value)
=> 2;
public Type Visit(in TestStruct<Guid> value)
=> typeof(TestStruct<Guid>);

public int Visit(in DateTime value)
=> 3;
public Type Visit(in DateTime value)
=> typeof(DateTime);
}

[ValueVariant]
public readonly partial struct TestVariant2 : IValueVariant<TestVariant2, TestStruct<int>, DateTime, int> { }
public readonly partial struct TestVariant2 : IValueVariant<TestVariant2, TestStruct<Guid>, DateTime, int> { }
21 changes: 17 additions & 4 deletions ValueVariant.Test/UnitTest1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,27 @@ public class UnitTest1
[Fact]
public void Test1()
{
TestVariant v = default(int);
Assert.Equal(v.Accept(new TestVariantVisitor()), (int)v.Index);
TestVariant v;
TestVariant2 v2;
Type type;

v = default(int);
Assert.Equal(typeof(int), v.Accept(new TestVariantVisitor()));
v.Accept(new TestVariant2.DefaultConverter(), out v2);
v2.Accept(new TestVariantVisitor(), out type);
Assert.Equal(typeof(int), type);

v = default(TestStruct<Guid>);
Assert.Equal(v.Accept(new TestVariantVisitor()), (int)v.Index);
Assert.Equal(typeof(TestStruct<Guid>), v.Accept(new TestVariantVisitor()));
v.Accept(new TestVariant2.DefaultConverter(), out v2);
v2.Accept(new TestVariantVisitor(), out type);
Assert.Equal(typeof(TestStruct<Guid>), type);

v = default(DateTime);
Assert.Equal(v.Accept(new TestVariantVisitor()), (int)v.Index);
Assert.Equal(typeof(DateTime), v.Accept(new TestVariantVisitor()));
v.Accept(new TestVariant2.DefaultConverter(), out v2);
v2.Accept(new TestVariantVisitor(), out type);
Assert.Equal(typeof(DateTime), type);
}

[Fact]
Expand Down
10 changes: 5 additions & 5 deletions ValueVariant/Details/IValueVariant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ namespace System.ValueVariant.Details;

public interface IValueVariant
{
byte TypeIndexByte { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
byte TypeIndex { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }

void AcceptGenericActionVisitor<TV>(in TV visitor) where TV : IValueVariantGenericActionVisitor;
void Accept(IValueVariantGenericActionVisitor visitor);

void AcceptGenericActionVisitor<TV, TA1>(in TV visitor, TA1 arg1) where TV : IValueVariantGenericActionVisitor<TA1>;
void Accept<TA1>(IValueVariantGenericActionVisitor<TA1> visitor, TA1 arg1);

void AcceptGenericFuncVisitor<TV, TR>(in TV visitor, out TR result) where TV : IValueVariantGenericFuncVisitor<TR>;
TR Accept<TR>(IValueVariantGenericFuncVisitor<TR> visitor);

void AcceptGenericFuncVisitor<TV, TA1, TR>(in TV visitor, TA1 arg1, out TR result) where TV : IValueVariantGenericFuncVisitor<TA1, TR>;
TR Accept<TA1, TR>(IValueVariantGenericFuncVisitor<TA1, TR> visitor, TA1 arg1);
}

public interface IValueVariantElement<T>: IValueVariant where T: unmanaged { }
8 changes: 4 additions & 4 deletions ValueVariant/Details/IValueVariantT.tt
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ public interface IValueVariant<<#= Join(1, i, e => $"T{e}") #>>:
<#= Join(1, i, e => $"IValueVariantIndex{e}<T{e}>") #>
<#= string.Join(" ", Range(1, i, e => $"where T{e}: unmanaged")) #>
{
void AcceptActionVisitor<TV>(in TV visitor) where TV: <#= Join(1, i, e => $"IValueVariantActionVisitor<T{e}>") #>;
void Accept<TV>(in TV visitor) where TV: <#= Join(1, i, e => $"IValueVariantActionVisitor<T{e}>") #>;

void AcceptActionVisitor<TV, TA1>(in TV visitor, TA1 arg1) where TV: <#= Join(1, i, e => $"IValueVariantActionVisitor<T{e}, TA1>") #>;
void Accept<TV, TA1>(in TV visitor, TA1 arg1) where TV: <#= Join(1, i, e => $"IValueVariantActionVisitor<T{e}, TA1>") #>;

void AcceptFuncVisitor<TV, TR>(in TV visitor, out TR result) where TV: <#= Join(1, i, e => $"IValueVariantFuncVisitor<T{e}, TR>") #>;
void Accept<TV, TR>(in TV visitor, out TR result) where TV: <#= Join(1, i, e => $"IValueVariantFuncVisitor<T{e}, TR>") #>;

void AcceptFuncVisitor<TV, TA1, TR>(in TV visitor, TA1 arg1, out TR result) where TV: <#= Join(1, i, e => $"IValueVariantFuncVisitor<T{e}, TA1, TR>") #>;
void Accept<TV, TA1, TR>(in TV visitor, TA1 arg1, out TR result) where TV: <#= Join(1, i, e => $"IValueVariantFuncVisitor<T{e}, TA1, TR>") #>;
}
<# } #>
4 changes: 2 additions & 2 deletions ValueVariant/Details/ValueVariantT.ActionVisitor.tt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ partial class ValueVariant<<#= Join(1, i, e => $"T{e}") #>>
where T: unmanaged, V2.IValueVariant<T, <#= Join(1, i, e => $"T{e}") #>>
where V: <#= Join(1, i, e => $"IValueVariantActionVisitor<T{e}>") #>
{
switch (@this.TypeIndexByte) {
switch (@this.TypeIndex) {
<# for (var j = 1; j <= i; ++j) { #>
case <#= j #>: visitor.Visit(@this.Item<#= j #>); return;
<# } #>
Expand All @@ -37,7 +37,7 @@ partial class ValueVariant<<#= Join(1, i, e => $"T{e}") #>>
where T: unmanaged, V2.IValueVariant<T, <#= Join(1, i, e => $"T{e}") #>>
where V: <#= Join(1, i, e => $"IValueVariantActionVisitor<T{e}, TA1>") #>
{
switch (@this.TypeIndexByte) {
switch (@this.TypeIndex) {
<# for (var j = 1; j <= i; ++j) { #>
case <#= j #>: visitor.Visit(@this.Item<#= j #>, arg1); return;
<# } #>
Expand Down
4 changes: 2 additions & 2 deletions ValueVariant/Details/ValueVariantT.FuncVisitor.tt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ partial class ValueVariant<<#= Join(1, i, e => $"T{e}") #>>
where T: unmanaged, V2.IValueVariant<T, <#= Join(1, i, e => $"T{e}") #>>
where V: <#= Join(1, i, e => $"IValueVariantFuncVisitor<T{e}, TR>") #>
{
return @this.TypeIndexByte switch {
return @this.TypeIndex switch {
<# for (var j = 1; j <= i; ++j) { #>
<#= j #> => visitor.Visit(@this.Item<#= j #>),
<# } #>
Expand All @@ -37,7 +37,7 @@ partial class ValueVariant<<#= Join(1, i, e => $"T{e}") #>>
where T: unmanaged, V2.IValueVariant<T, <#= Join(1, i, e => $"T{e}") #>>
where V: <#= Join(1, i, e => $"IValueVariantFuncVisitor<T{e}, TA1, TR>") #>
{
return @this.TypeIndexByte switch {
return @this.TypeIndex switch {
<# for (var j = 1; j <= i; ++j) { #>
<#= j #> => visitor.Visit(@this.Item<#= j #>, arg1),
<# } #>
Expand Down
4 changes: 2 additions & 2 deletions ValueVariant/Details/ValueVariantT.GenericActionVisitor.tt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ partial class ValueVariant<<#= Join(1, i, e => $"T{e}") #>>
where T: unmanaged, V2.IValueVariant<T, <#= Join(1, i, e => $"T{e}") #>>
where V: IValueVariantGenericActionVisitor
{
switch (@this.TypeIndexByte) {
switch (@this.TypeIndex) {
<# for (var j = 1; j <= i; ++j) { #>
case <#= j #>: visitor.Visit(@this.Item<#= j #>); return;
<# } #>
Expand All @@ -37,7 +37,7 @@ partial class ValueVariant<<#= Join(1, i, e => $"T{e}") #>>
where T: unmanaged, V2.IValueVariant<T, <#= Join(1, i, e => $"T{e}") #>>
where V: IValueVariantGenericActionVisitor<TA1>
{
switch (@this.TypeIndexByte) {
switch (@this.TypeIndex) {
<# for (var j = 1; j <= i; ++j) { #>
case <#= j #>: visitor.Visit(@this.Item<#= j #>, arg1); return;
<# } #>
Expand Down
4 changes: 2 additions & 2 deletions ValueVariant/Details/ValueVariantT.GenericFuncVisitor.tt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ partial class ValueVariant<<#= Join(1, i, e => $"T{e}") #>>
where T: unmanaged, V2.IValueVariant<T, <#= Join(1, i, e => $"T{e}") #>>
where V: IValueVariantGenericFuncVisitor<TR>
{
return @this.TypeIndexByte switch {
return @this.TypeIndex switch {
<# for (var j = 1; j <= i; ++j) { #>
<#= j #> => visitor.Visit(@this.Item<#= j #>),
<# } #>
Expand All @@ -37,7 +37,7 @@ partial class ValueVariant<<#= Join(1, i, e => $"T{e}") #>>
where T: unmanaged, V2.IValueVariant<T, <#= Join(1, i, e => $"T{e}") #>>
where V: IValueVariantGenericFuncVisitor<TA1, TR>
{
return @this.TypeIndexByte switch {
return @this.TypeIndex switch {
<# for (var j = 1; j <= i; ++j) { #>
<#= j #> => visitor.Visit(@this.Item<#= j #>, arg1),
<# } #>
Expand Down
10 changes: 5 additions & 5 deletions ValueVariant/Details/ValueVariantT.IEquatable.tt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ partial class ValueVariant<<#= Join(1, i, e => $"T{e}") #>>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Equals<T>(in T lhs, in T rhs) where T: unmanaged, V2.IValueVariant<T, <#= Join(1, i, e => $"T{e}") #>>
=> lhs.TypeIndexByte == rhs.TypeIndexByte && lhs.TypeIndexByte switch {
=> lhs.TypeIndex == rhs.TypeIndex && lhs.TypeIndex switch {
<# for (var j = 1; j <= i; ++j) { #>
<#= j #> => EqualityComparer<T<#= j #>>.Default.Equals(lhs.Item<#= j #>, rhs.Item<#= j #>),
<# } #>
Expand All @@ -31,16 +31,16 @@ partial class ValueVariant<<#= Join(1, i, e => $"T{e}") #>>

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetHashCode<T>(in T @this) where T: unmanaged, V2.IValueVariant<T, <#= Join(1, i, e => $"T{e}") #>>
=> @this.TypeIndexByte switch {
=> @this.TypeIndex switch {
<# for (var j = 1; j <= i; ++j) { #>
<#= j #> => HashCode.Combine(@this.TypeIndexByte, @this.Item<#= j #>),
<#= j #> => HashCode.Combine(@this.TypeIndex, @this.Item<#= j #>),
<# } #>
_ => HashCode.Combine(@this.TypeIndexByte)
_ => HashCode.Combine(@this.TypeIndex)
};

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string ToString<T>(in T @this) where T: unmanaged, V2.IValueVariant<T, <#= Join(1, i, e => $"T{e}") #>>
=> @this.TypeIndexByte switch {
=> @this.TypeIndex switch {
<# for (var j = 1; j <= i; ++j) { #>
<#= j #> => @this.Item<#= j #>.ToString(),
<# } #>
Expand Down
Loading

0 comments on commit f1e42a8

Please sign in to comment.