Skip to content

Commit

Permalink
Merge 25a0b15 into b5bbee8
Browse files Browse the repository at this point in the history
  • Loading branch information
helto4real committed Apr 3, 2020
2 parents b5bbee8 + 25a0b15 commit caeb5af
Show file tree
Hide file tree
Showing 15 changed files with 253 additions and 10 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
TestResults
lcov.info
codecover
**.DS_Store
**.DS_Store
.generated
2 changes: 1 addition & 1 deletion src/App/NetDaemon.App/NetDaemon.App.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="JoySoftware.HassClient" Version="0.0.22-alpha" />
<PackageReference Include="JoySoftware.HassClient" Version="0.0.24-alpha" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.1" />
</ItemGroup>

Expand Down
7 changes: 6 additions & 1 deletion src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@ public class NetDaemonHost : INetDaemonHost
{
"light",
"switch",
"input_boolean"
"input_boolean",
"automation",
"input_boolean",
"camera",
"scene",
"script",
};

private bool _stopped;
Expand Down
2 changes: 1 addition & 1 deletion src/Daemon/NetDaemon.Daemon/NetDaemon.Daemon.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

</PropertyGroup>
<ItemGroup>
<PackageReference Include="JoySoftware.HassClient" Version="0.0.22-alpha" />
<PackageReference Include="JoySoftware.HassClient" Version="0.0.24-alpha" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="3.1.1" />
</ItemGroup>
<ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions src/DaemonRunner/DaemonRunner/DaemonRunner.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@

</PropertyGroup>
<ItemGroup>
<PackageReference Include="JoySoftware.HassClient" Version="0.0.22-alpha" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.4.0" />
<PackageReference Include="JoySoftware.HassClient" Version="0.0.24-alpha" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.5.0" />
<!-- <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.5.0-beta2-final" /> -->
<PackageReference Include="Microsoft.Extensions.Hosting" Version="3.1.1" />
<PackageReference Include="YamlDotNet" Version="8.1.0" />
Expand Down
108 changes: 108 additions & 0 deletions src/DaemonRunner/DaemonRunner/Service/App/CodeGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using JoySoftware.HomeAssistant.NetDaemon.DaemonRunner.Service.Config;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("NetDaemon.Daemon.Tests")]

namespace JoySoftware.HomeAssistant.NetDaemon.DaemonRunner.Service.App
{
public class CodeGenerator
{
/// <summary>
/// Mapps the domain to corresponding implemented Fluent API, will be added as
/// more and more entity types are supported
/// </summary>
private static IDictionary<string, (string, string)> _FluentApiMapper = new Dictionary<string, (string, string)>
{
["light"] = ("Entity", "IEntity"),
["script"] = ("Entity", "IEntity"),
["scene"] = ("Entity", "IEntity"),
["switch"] = ("Entity", "IEntity"),
["camera"] = ("Entity", "IEntity"),
["media_player"] = ("MediaPlayer", "IMediaPlayer"),
["automation"] = ("Entity", "IEntity"),
// ["input_boolean"],
// ["remote"],
// ["climate"],
};

public string? GenerateCode(string nameSpace, IEnumerable<string> entities)
{
var code = SyntaxFactory.CompilationUnit();

// Add Usings statements
code = code.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("JoySoftware.HomeAssistant.NetDaemon.Common")));

// Add namespace
var namespaceDeclaration = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName(nameSpace)).NormalizeWhitespace();

// Add support for extensions for entities
var extensionClass = SyntaxFactory.ClassDeclaration("EntityExtension");

extensionClass = extensionClass.AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword),
SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.PartialKeyword));


// Get all available domains, this is used to create the extensionmethods
var domains = GetDomainsFromEntities(entities);

foreach (var domain in domains)
{
if (_FluentApiMapper.ContainsKey(domain))
{
var camelCaseDomain = domain.ToCamelCase();
var method = $@"public static {camelCaseDomain}Entities {camelCaseDomain}Ex(this NetDaemonApp app) => new {camelCaseDomain}Entities(app);";
var methodDeclaration = CSharpSyntaxTree.ParseText(method).GetRoot().ChildNodes().OfType<MethodDeclarationSyntax>().FirstOrDefault();
extensionClass = extensionClass.AddMembers(methodDeclaration);
}

}
namespaceDeclaration = namespaceDeclaration.AddMembers(extensionClass);

// Add the classes implementing the specific entities
foreach (var domain in GetDomainsFromEntities(entities))
{
if (_FluentApiMapper.ContainsKey(domain))
{
var classDeclaration = $@"public partial class {domain.ToCamelCase()}Entities
{{
private readonly NetDaemonApp _app;
public {domain.ToCamelCase()}Entities(NetDaemonApp app)
{{
_app = app;
}}
}}";
var entityClass = CSharpSyntaxTree.ParseText(classDeclaration).GetRoot().ChildNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault();
foreach (var entity in entities.Where(n => n.StartsWith(domain)))
{
var (fluent, fluentInterface) = _FluentApiMapper[domain];

var propertyCode = $@"public {fluentInterface} {entity[(entity.IndexOf(".") + 1)..].ToCamelCase()} => _app.{fluent}(""{entity}"");";
var propDeclaration = CSharpSyntaxTree.ParseText(propertyCode).GetRoot().ChildNodes().OfType<PropertyDeclarationSyntax>().FirstOrDefault();
entityClass = entityClass.AddMembers(propDeclaration);

}
namespaceDeclaration = namespaceDeclaration.AddMembers(entityClass);

}
}

code = code.AddMembers(namespaceDeclaration);

return code.NormalizeWhitespace(indentation: " ", eol: "\n").ToFullString();
}

/// <summary>
/// Returns a list of domains from all entities
/// </summary>
/// <param name="entities">A list of entities</param>
internal static IEnumerable<string> GetDomainsFromEntities(IEnumerable<string> entities) =>
entities.Select(n => n[0..n.IndexOf(".")]).Distinct();

}
}
4 changes: 4 additions & 0 deletions src/DaemonRunner/DaemonRunner/Service/HostConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,9 @@ public HostConfig()

[JsonPropertyName("source_folder")]
public string? SourceFolder { get; set; } = null;

[JsonPropertyName("generate_entities")]
public bool? GenerateEntitiesOnStartup { get; set; } = false;

}
}
17 changes: 17 additions & 0 deletions src/DaemonRunner/DaemonRunner/Service/RunnerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,23 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
// Generate code if requested
var envGenEntities = Environment.GetEnvironmentVariable("HASS_GEN_ENTITIES");
if (envGenEntities is object)
{
if (envGenEntities == "True")
{
var codeGen = new CodeGenerator();
var source = codeGen.GenerateCode("Netdaemon.Generated.Extensions",
_daemonHost.State.Select(n => n.EntityId).Distinct());
var genDirectory = System.IO.Path.Combine(sourceFolder, ".generated");

if (!System.IO.Directory.Exists(genDirectory))
System.IO.Directory.CreateDirectory(genDirectory);

System.IO.File.WriteAllText(System.IO.Path.Combine(genDirectory, "EntityExtensions.cs"), source);
}
}
using (var codeManager = new CodeManager(sourceFolder, _daemonHost.Logger))
{
await codeManager.EnableApplicationDiscoveryServiceAsync(_daemonHost, discoverServicesOnStartup: true);
Expand Down
3 changes: 2 additions & 1 deletion src/Service/.config/hassio_config.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"log_level": "trace"
"log_level": "trace",
"generate_entities": true
}
11 changes: 11 additions & 0 deletions src/Service/HassioConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Text.Json.Serialization;

public class HassioConfig
{

[JsonPropertyName("log_level")]
public string? LogLevel { get; set; }

[JsonPropertyName("generate_entities")]
public bool? GenerateEntitiesOnStart { get; set; }
}
11 changes: 8 additions & 3 deletions src/Service/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace Service
{
internal class Program
{
// private const string _hassioConfigPath = "/root/src/src/Service/.config/hassio_config.json";
private const string _hassioConfigPath = "data/options.json";
private static LogLevel LogLevel = LogLevel.Trace;

Expand All @@ -21,11 +22,11 @@ public static async Task Main(string[] args)
///
if (File.Exists(_hassioConfigPath))
{
var hassAddOnSettings = await JsonSerializer.DeserializeAsync<System.Collections.Generic.Dictionary<string, string>>(
var hassAddOnSettings = await JsonSerializer.DeserializeAsync<HassioConfig>(
File.OpenRead(_hassioConfigPath)).ConfigureAwait(false);
if (hassAddOnSettings.ContainsKey("log_level"))
if (hassAddOnSettings.LogLevel is object)
{
Program.LogLevel = hassAddOnSettings["log_level"] switch
Program.LogLevel = hassAddOnSettings.LogLevel switch
{
"info" => LogLevel.Information,
"debug" => LogLevel.Debug,
Expand All @@ -35,6 +36,10 @@ public static async Task Main(string[] args)
_ => LogLevel.Information
};
}
if (hassAddOnSettings.GenerateEntitiesOnStart is object)
{
Environment.SetEnvironmentVariable("HASS_GEN_ENTITIES", hassAddOnSettings.GenerateEntitiesOnStart.ToString());
}
}
}
catch (Exception)
Expand Down
53 changes: 53 additions & 0 deletions tests/NetDaemon.Daemon.Tests/DaemonRunner/App/CodeGenTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System.Linq;
using JoySoftware.HomeAssistant.NetDaemon.DaemonRunner.Service.App;
using Xunit;

namespace NetDaemon.Daemon.Tests.DaemonRunner.App
{
public class CodeGenerationTests
{
[Fact]
public void TestGenerateCode()
{
// ARRANGE
var x = new CodeGenerator();
// ACT
var code = x.GenerateCode("Netdaemon.Generated.Extensions", new string[] { "light.koket_fonster", "media_player.my_player" });

// ASSERT

Assert.Equal(System.IO.File.ReadAllText("DaemonRunner/CodeGen/CodeGenTestFixture.cs"), code);
// System.IO.File.WriteAllText("/root/src/tests/NetDaemon.Daemon.Tests/DaemonRunner/CodeGen/CodeGenTestFixture.cs", code);
}

[Fact]
public void WhenGivenAnArrayOfEntitiesTheDomainShouldReturnCorrectDomains()
{
// ARRANGE
var entities = new string[]
{
"light.the_light",
"light.kitchen",
"media_player.player",
"scene.thescene",
"switch.myswitch",
"switch.myswitch2",
"camera.acamera",
"automation.wowautomation",
"script.myscript"
};
// ACT
var domainsInCamelCase = CodeGenerator.GetDomainsFromEntities(entities);
// ASSERT
Assert.Equal(7, domainsInCamelCase.Count());
Assert.Collection(domainsInCamelCase,
n => Assert.Equal("light", n),
n => Assert.Equal("media_player", n),
n => Assert.Equal("scene", n),
n => Assert.Equal("switch", n),
n => Assert.Equal("camera", n),
n => Assert.Equal("automation", n),
n => Assert.Equal("script", n));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using JoySoftware.HomeAssistant.NetDaemon.Common;

namespace Netdaemon.Generated.Extensions
{
public static partial class EntityExtension
{
public static LightEntities LightEx(this NetDaemonApp app) => new LightEntities(app);
public static MediaPlayerEntities MediaPlayerEx(this NetDaemonApp app) => new MediaPlayerEntities(app);
}

public partial class LightEntities
{
private readonly NetDaemonApp _app;
public LightEntities(NetDaemonApp app)
{
_app = app;
}

public IEntity KoketFonster => _app.Entity("light.koket_fonster");
}

public partial class MediaPlayerEntities
{
private readonly NetDaemonApp _app;
public MediaPlayerEntities(NetDaemonApp app)
{
_app = app;
}

public IMediaPlayer MyPlayer => _app.MediaPlayer("media_player.my_player");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ public void JSonSerializeShouldBeCorrectForConfig()
Host = "host",
Port = 1234,
Ssl = true,
GenerateEntitiesOnStartup = false,
SourceFolder = "somefolder"
};

Expand All @@ -119,6 +120,7 @@ public void JSonSerializeShouldBeCorrectForConfig()
Assert.Contains("port", obj);
Assert.Contains("ssl", obj);
Assert.Contains("source_folder", obj);
Assert.Contains("generate_entities", obj);
}


Expand Down
4 changes: 4 additions & 0 deletions tests/NetDaemon.Daemon.Tests/NetDaemon.Daemon.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,17 @@
</ItemGroup>

<ItemGroup>
<Compile Remove="DaemonRunner/CodeGen/**" />
<Compile Remove="DaemonRunner/Fixtures/**" />
<Compile Remove="DaemonRunner/FaultyApp/**" />
</ItemGroup>
<ItemGroup>
<Content Include="DaemonRunner/Fixtures/**">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="DaemonRunner/CodeGen/**">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="DaemonRunner/App/*.yaml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
Expand Down

0 comments on commit caeb5af

Please sign in to comment.