ThisAssembly
ThisAssembly copied to clipboard
Add ThisAssembly.Resources
Discussed in https://github.com/devlooped/ThisAssembly/discussions/34
Originally posted by viceroypenguin January 23, 2021
Proposal: Add another project for exposing string
and Stream
versions of embedded resources. Accessing the resources is something necessary for most source generators, but also for many other projects; you're accessing embedded resources already in the other ThisAssembly.* projects. It would be nice to be able to have compile-time named access to the embedded resources. Thoughts?
Design
Given the following items in a project:
<PropertyGroup>
<EmbeddedResourceStringExtensions>$(EmbeddedResourceStringExtensions);.sql</EmbeddedResourceStringExtensions>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Foo.txt" />
<EmbeddedResource Include="MyTemplate.template" Type="Text" />
<EmbeddedResource Include="Data\Bar.json" />
<EmbeddedResource Include="..\..\Branding\Icon.png" Link="Branding\Icon.png" Comment="Main app logo" />
<EmbeddedResource Include="Resources.resx" />
</ItemGroup>
The generator would emit a class like the following:
partial class ThisAssembly
{
public static partial class Resources
{
// <summary>Resources\Foo.txt</summary>
public static partial class Foo
{
public static string Text => // return cached string version of the resource
public static byte[] GetBytes() => // read bytes from resource
public static Stream GetStream() => // get the resource stream
}
// <summary>MyTemplate.template</summary>
public static partial class MyTemplate
{
public static string Text => // return cached string version of the resource
public static byte[] GetBytes() => // read bytes from resource
public static Stream GetStream() => // get the resource stream
}
// <summary>Resources.resx</summary>
public static partial class Resources
{
public static byte[] GetBytes() => // read bytes from resource
public static Stream GetStream() => // get the resource stream
}
public partial class Branding
{
// <summary>Main app logo</summary>
// <remarks>Branding\Icon.png</remarks>
public static partial class Icon
{
public static byte[] GetBytes() => // read bytes from resource
public static Stream GetStream() => // get the resource stream
}
}
public partial class Data
{
// <summary>Data\Bar.json</summary>
public static partial class Bar
{
public static string Text => // return cached string version of the resource
public static byte[] GetBytes() => // read bytes from resource
public static Stream GetStream() => // get the resource stream
}
}
}
}
From the example, note:
- All
EmbeddedResource
items get a static class with bothGetBytes
andGetStream
methods for retrieval - Text files (i.e.
.txt
and.json
) also get aText
property with a cached string read from the resource - Only certain file extensions within
EmbeddedResources
items are defaulted toText
type (therefore getting a generatedText
property). By default, this includes.txt;.json
, but can be be extended by appending more extensions to theEmbeddedResourceStringExtensions
property. - A single
EmbeddedResource
can also be annotated with theType="Text"
attribute too, to get theText
property just for that item. - The items' target path (that is,
%(RelativeDir)\%(Filename)%(Extension)
or%(Link)
(for linked files) is used by default as the class<summary />
documentation, unless aComment="...."
is provided in the item, in which case that becomes the<summary />
and the former becomes the<remarks>
. - The relative directory of the item is used in the class structure to nest classes and avoid name collisions (this is prevented by the solution structure itself too at that point). This is similar to the nesting done by ThisAssembly.Constants
What if two embedded resources have the same filename? I wonder if a) we should include the path as part of the class structure? and b) if we should optionally include the extension as well?
Otherwise, LGTM!
Ha, good catch. Yeah, making the path part of the class structure makes sense. Like ThisAssembly.Resources.Branding.Icon
. It's perfect :), edited!