Skip to content

Commit 71d5e4f

Browse files
Merge pull request #11 from JasperFx/descriptions
OptionDescriptions & a new TimeSpan.ToDisplay() utility
2 parents baf4800 + dfccbc5 commit 71d5e4f

9 files changed

+377
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
using System.Linq.Expressions;
2+
using JasperFx.Core.Descriptions;
3+
using JasperFx.Core.Reflection;
4+
using Shouldly;
5+
6+
namespace JasperFx.Core.Tests.Descriptions;
7+
8+
public class reading_descriptions
9+
{
10+
public readonly Target theTarget = new();
11+
12+
private OptionsValue read(Expression<Func<Target, object>> expression)
13+
{
14+
return OptionsValue.Read(theTarget, expression);
15+
}
16+
17+
[Fact]
18+
public void read_a_string()
19+
{
20+
// Our dog's name who's sleeping in my office...
21+
theTarget.Name = "Chewie";
22+
23+
var property = read(x => x.Name);
24+
property.Name.ShouldBe("Name");
25+
property.Type.ShouldBe(PropertyType.Text);
26+
property.Subject = $"{typeof(Target).FullNameInCode()}.{nameof(Target.Name)}";
27+
property.RawValue.ShouldBe(theTarget.Name);
28+
property.Value.ShouldBe("Chewie");
29+
}
30+
31+
[Fact]
32+
public void read_a_null_value()
33+
{
34+
theTarget.Name = null;
35+
36+
var property = read(x => x.Name);
37+
property.Name.ShouldBe("Name");
38+
property.Type.ShouldBe(PropertyType.None);
39+
property.Subject = $"{typeof(Target).FullNameInCode()}.{nameof(Target.Name)}";
40+
property.RawValue.ShouldBe(null);
41+
property.Value.ShouldBe("None");
42+
}
43+
44+
[Fact]
45+
public void read_an_integer()
46+
{
47+
var property = read(x => x.Age);
48+
property.Name.ShouldBe("Age");
49+
property.Type.ShouldBe(PropertyType.Numeric);
50+
property.Subject = $"{typeof(Target).FullNameInCode()}.{nameof(Target.Age)}";
51+
property.RawValue.ShouldBe(theTarget.Age);
52+
property.Value.ShouldBe(theTarget.Age.ToString());
53+
}
54+
55+
[Fact]
56+
public void read_an_enum()
57+
{
58+
var property = read(x => x.Color);
59+
property.Name.ShouldBe("Color");
60+
property.Type.ShouldBe(PropertyType.Enum);
61+
property.Subject = $"{typeof(Target).FullNameInCode()}.{nameof(Target.Color)}";
62+
property.RawValue.ShouldBe(theTarget.Color);
63+
property.Value.ShouldBe(theTarget.Color.ToString());
64+
}
65+
66+
[Fact]
67+
public void read_a_boolean()
68+
{
69+
theTarget.IsTrue = true;
70+
var property = read(x => x.IsTrue);
71+
property.Name.ShouldBe("IsTrue");
72+
property.Type.ShouldBe(PropertyType.Boolean);
73+
property.Subject = $"{typeof(Target).FullNameInCode()}.{nameof(Target.IsTrue)}";
74+
property.RawValue.ShouldBe(theTarget.IsTrue);
75+
property.Value.ShouldBe(theTarget.IsTrue.ToString());
76+
}
77+
78+
[Fact]
79+
public void read_a_uri()
80+
{
81+
theTarget.IsTrue = true;
82+
var property = read(x => x.Uri);
83+
property.Name.ShouldBe("Uri");
84+
property.Type.ShouldBe(PropertyType.Uri);
85+
property.Subject = $"{typeof(Target).FullNameInCode()}.{nameof(Target.Uri)}";
86+
property.RawValue.ShouldBe(theTarget.Uri);
87+
property.Value.ShouldBe(theTarget.Uri.ToString());
88+
}
89+
90+
[Fact]
91+
public void read_a_time_span()
92+
{
93+
theTarget.IsTrue = true;
94+
var property = read(x => x.Duration);
95+
property.Name.ShouldBe("Duration");
96+
property.Type.ShouldBe(PropertyType.TimeSpan);
97+
property.Subject = $"{typeof(Target).FullNameInCode()}.{nameof(Target.Duration)}";
98+
property.RawValue.ShouldBe(theTarget.Duration);
99+
property.Value.ShouldBe(theTarget.Duration.ToDisplay());
100+
}
101+
102+
[Fact]
103+
public void read_in_description()
104+
{
105+
theTarget.Name = "Shiner"; // our previous family dog
106+
var description = new OptionsDescription(theTarget);
107+
108+
description.Properties.Select(x => x.Name)
109+
.ToArray()
110+
.ShouldBe(new string[]{"Name", "IsTrue", "Age", "Color", "Uri", "Duration"});
111+
112+
description.Children["YesThis"].Properties.Select(x => x.Name)
113+
.ShouldHaveTheSameElementsAs("Number", "Suffix");
114+
}
115+
}
116+
117+
public enum Color
118+
{
119+
Red, Blue, Green
120+
}
121+
122+
public class Target
123+
{
124+
public string Name { get; set; }
125+
public bool IsTrue { get; set; }
126+
public int Age { get; set; } = 51;
127+
public Color Color { get; set; } = Color.Blue;
128+
public Uri Uri { get; set; } = "local://durable".ToUri();
129+
130+
public TimeSpan Duration { get; set; } = 25.Milliseconds();
131+
132+
// I want this skipped
133+
public string[] Strings { get; set; }
134+
135+
[IgnoreDescription]
136+
public Thing NotThis { get; set; }
137+
138+
[ChildDescription]
139+
public Thing YesThis { get; set; } = new Thing
140+
{
141+
Number = 4, Suffix = "Jr"
142+
};
143+
}
144+
145+
public class Thing
146+
{
147+
public int Number { get; set; } = 5;
148+
public string Suffix { get; set; } = "Esq.";
149+
}

src/JasperFx.Core.Tests/TimeSpanExtensionsTester.cs

+11
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,16 @@ public void seconds()
4444
{
4545
8.Seconds().ShouldBe(new TimeSpan(0, 0, 8));
4646
}
47+
48+
[Fact]
49+
public void to_display()
50+
{
51+
4.Seconds().ToDisplay().ShouldBe("4 seconds");
52+
1.Seconds().ToDisplay().ShouldBe("1 second");
53+
2.Hours().ToDisplay().ShouldBe("2 hours");
54+
250.Milliseconds().ToDisplay().ShouldBe("250 milliseconds");
55+
new TimeSpan(1, 2, 3, 4, 5).ToDisplay()
56+
.ShouldBe("1 day, 2 hours, 3 minutes, 4 seconds, 5 milliseconds");
57+
}
4758
}
4859
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace JasperFx.Core.Descriptions;
2+
3+
/// <summary>
4+
/// Just tells the Description to add a child Description to the parent
5+
/// </summary>
6+
[AttributeUsage(AttributeTargets.Property)]
7+
public class ChildDescriptionAttribute : Attribute
8+
{
9+
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace JasperFx.Core.Descriptions;
2+
3+
/// <summary>
4+
/// Just tells the Description to ignore this property when reading property values
5+
/// </summary>
6+
[AttributeUsage(AttributeTargets.Property)]
7+
public class IgnoreDescriptionAttribute : Attribute
8+
{
9+
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using JasperFx.Core.Reflection;
2+
3+
namespace JasperFx.Core.Descriptions;
4+
5+
/// <summary>
6+
/// Just a serializable, readonly view of system configuration to be used for diagnostic purposes
7+
/// </summary>
8+
public class OptionsDescription
9+
{
10+
public string Subject { get; set; }
11+
public List<OptionsValue> Properties { get; set; } = new();
12+
13+
public Dictionary<string, OptionsDescription> Children = new();
14+
15+
// For serialization
16+
public OptionsDescription()
17+
{
18+
}
19+
20+
public OptionsDescription(object subject)
21+
{
22+
if (subject == null)
23+
{
24+
throw new ArgumentNullException(nameof(subject));
25+
}
26+
27+
var type = subject.GetType();
28+
foreach (var property in type.GetProperties().Where(x => !x.HasAttribute<IgnoreDescriptionAttribute>()))
29+
{
30+
if (property.HasAttribute<ChildDescriptionAttribute>())
31+
{
32+
var child = property.GetValue(subject);
33+
if (child == null) continue;
34+
35+
var childDescription = new OptionsDescription(child);
36+
Children[property.Name] = childDescription;
37+
38+
continue;
39+
}
40+
41+
if (property.PropertyType != typeof(string) && property.PropertyType.IsEnumerable()) continue;
42+
Properties.Add(OptionsValue.Read(property, subject));
43+
}
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
using System.Linq.Expressions;
2+
using System.Reflection;
3+
using JasperFx.Core.Reflection;
4+
5+
namespace JasperFx.Core.Descriptions;
6+
7+
public class OptionsValue
8+
{
9+
// For serialization
10+
public OptionsValue()
11+
{
12+
}
13+
14+
// make this containing object full name.name
15+
// Can be deep? That would cover DurabilitySettings
16+
// use this to tag fancier tooltips
17+
public string Subject { get; set; }
18+
public string Name { get; set; }
19+
public PropertyType Type { get; set; }
20+
21+
// Maybe don't serialize this
22+
public object RawValue { get; set; }
23+
public string Value { get; set; }
24+
25+
public static OptionsValue Read<T>(T subject, Expression<Func<T,object>> expression)
26+
{
27+
if (subject == null)
28+
{
29+
throw new ArgumentNullException(nameof(subject));
30+
}
31+
32+
var property = ReflectionHelper.GetProperty(expression);
33+
34+
return Read(property, subject);
35+
}
36+
37+
public static OptionsValue Read(PropertyInfo property, object subject)
38+
{
39+
var value = property.GetValue(subject);
40+
41+
var description = new OptionsValue
42+
{
43+
Subject = $"{subject.GetType().FullNameInCode()}.{property.Name}",
44+
RawValue = value,
45+
Value = value?.ToString(),
46+
Name = property.Name,
47+
Type = PropertyType.Text, // safest guess
48+
};
49+
50+
if (value == null)
51+
{
52+
description.Type = PropertyType.None;
53+
description.Value = "None";
54+
}
55+
else if (value.GetType().IsNumeric())
56+
{
57+
description.Type = PropertyType.Numeric;
58+
}
59+
else if (value.GetType().IsEnum)
60+
{
61+
description.Type = PropertyType.Enum;
62+
}
63+
else if (value is bool)
64+
{
65+
description.Type = PropertyType.Boolean;
66+
}
67+
else if (value is Uri)
68+
{
69+
description.Type = PropertyType.Uri;
70+
}
71+
else if (value is TimeSpan time)
72+
{
73+
description.Type = PropertyType.TimeSpan;
74+
description.Value = time.ToDisplay();
75+
}
76+
77+
return description;
78+
}
79+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace JasperFx.Core.Descriptions;
2+
3+
public enum PropertyType
4+
{
5+
Text,
6+
Numeric,
7+
TimeSpan,
8+
Enum,
9+
Uri,
10+
Boolean,
11+
None
12+
}

src/JasperFx.Core/JasperFx.Core.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<Description>Common extension methods and reflection helpers used by JasperFx projects</Description>
5-
<Version>1.9.0</Version>
5+
<Version>1.10.0</Version>
66
<Authors>Jeremy D. Miller</Authors>
77
<AssemblyName>JasperFx.Core</AssemblyName>
88
<PackageId>JasperFx.Core</PackageId>

0 commit comments

Comments
 (0)