Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions claude.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ dotnet test src/Tests --configuration Release --filter "FullyQualifiedName~Tests
- **src/EmptyFiles.Tool/** — CLI global tool (`emptyfile` command). Targets net10.0.
- **src/Tests/** — NUnit tests with Verify snapshot testing. Targets net48 + net8.0 through net11.0.
- **src/EmptyFiles.AotTests/** — AOT/trimming compatibility tests (net9.0, PublishAot).
- **files/** — The actual empty template files organized by category: `archive/`, `document/`, `image/`, `sheet/`, `slide/`, `binary/`.
- **files/** — The actual empty template files organized by category: `archive/`, `document/`, `image/`, `map/`, `sheet/`, `slide/`, `binary/`.
- **buildTransitive/EmptyFiles.targets** — MSBuild targets included in the NuGet package; copies empty files to output directory on build.

## Architecture

Single namespace `EmptyFiles` with four public static API classes:

- **AllFiles** — Main facade. Discovers and loads empty template files from the `files/` directory at static init. Provides file creation (`CreateFile`/`TryCreateFile`), lookup (`GetPathFor`/`TryGetPathFor`), validation (`IsEmptyFile`), and registration (`UseFile`). Files are stored in `FrozenDictionary<string, EmptyFile>` per `Category` enum (Archive, Document, Image, Sheet, Slide, Binary).
- **AllFiles** — Main facade. Discovers and loads empty template files from the `files/` directory at static init. Provides file creation (`CreateFile`/`TryCreateFile`), lookup (`GetPathFor`/`TryGetPathFor`), validation (`IsEmptyFile`), and registration (`UseFile`). Files are stored in `FrozenDictionary<string, EmptyFile>` per `Category` enum (Archive, Document, Image, Map, Sheet, Slide, Binary).
- **FileExtensions** — Text file detection. Maintains a set of ~443 known text extensions and supports custom conventions via `AddTextFileConvention(Func<CharSpan, bool>)`.
- **ContentTypes** — MIME type mapping between extensions and media types (200+ mappings).
- **EmptyFile** — Data class holding `Path`, `LastWriteTime`, and `Category` for each template file.
Expand Down
Binary file added files/map/empty.gpkg
Binary file not shown.
Binary file added files/map/empty.mbtiles
Binary file not shown.
Binary file added files/map/empty.pmtiles
Binary file not shown.
Binary file added files/map/empty.shp
Binary file not shown.
Binary file added files/map/empty.shx
Binary file not shown.
Binary file added index/empty.gpkg
Binary file not shown.
Binary file added index/empty.mbtiles
Binary file not shown.
Binary file added index/empty.pmtiles
Binary file not shown.
Binary file added index/empty.shp
Binary file not shown.
Binary file added index/empty.shx
Binary file not shown.
8 changes: 8 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@ All files: https://github.com/VerifyTests/EmptyFiles/tree/main/files
* .webp (26 bytes)
* .wmp (48 bytes)

### Map

* .gpkg (5 KB)
* .mbtiles (2 KB)
* .pmtiles (130 bytes)
* .shp (100 bytes)
* .shx (100 bytes)

### Sheet

* .ods (683 bytes)
Expand Down
24 changes: 24 additions & 0 deletions src/EmptyFiles/AllFiles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ public static class AllFiles

static FrozenDictionary<string, EmptyFile> images;

public static FrozenDictionary<string, EmptyFile> Maps => maps;

static FrozenDictionary<string, EmptyFile> maps;

public static FrozenDictionary<string, EmptyFile> Sheets => sheets;

static FrozenDictionary<string, EmptyFile> sheets;
Expand All @@ -35,13 +39,15 @@ static AllFiles()
archives = AddCategory(archiveExtensions, Category.Archive);
documents = AddCategory(documentExtensions, Category.Document);
images = AddCategory(imageExtensions, Category.Image);
maps = AddCategory(mapExtensions, Category.Map);
sheets = AddCategory(sheetExtensions, Category.Sheet);
slides = AddCategory(slideExtensions, Category.Slide);
binary = AddCategory(binaryExtensions, Category.Binary);
var all = new Dictionary<string, EmptyFile>(StringComparer.OrdinalIgnoreCase);
Append(archives);
Append(documents);
Append(images);
Append(maps);
Append(sheets);
Append(slides);
Append(binary);
Expand Down Expand Up @@ -101,6 +107,7 @@ static void ExtractAll(string directory)
ExtractCategory(directory, "binary", binary);
ExtractCategory(directory, "document", documents);
ExtractCategory(directory, "image", images);
ExtractCategory(directory, "map", maps);
ExtractCategory(directory, "sheet", sheets);
ExtractCategory(directory, "slide", slides);
}
Expand Down Expand Up @@ -147,6 +154,9 @@ public static void UseFile(Category category, string file)
case Category.Image:
Init(ref images, ref imageExtensions);
break;
case Category.Map:
Init(ref maps, ref mapExtensions);
break;
case Category.Sheet:
Init(ref sheets, ref sheetExtensions);
break;
Expand Down Expand Up @@ -261,6 +271,20 @@ void Init(ref FrozenDictionary<string, EmptyFile> emptyFiles, ref FrozenSet<stri
],
StringComparer.OrdinalIgnoreCase);

public static IEnumerable<string> MapPaths => maps.Values.Select(_ => _.Path);

public static FrozenSet<string> MapExtensions => mapExtensions;

static FrozenSet<string> mapExtensions = FrozenSet.ToFrozenSet(
[
".gpkg",
".mbtiles",
".pmtiles",
".shp",
".shx"
],
StringComparer.OrdinalIgnoreCase);

public static IEnumerable<string> SheetPaths => sheets.Values.Select(_ => _.Path);

public static FrozenSet<string> SheetExtensions => sheetExtensions;
Expand Down
3 changes: 2 additions & 1 deletion src/EmptyFiles/Category.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ public enum Category
Image,
Sheet,
Slide,
Binary
Binary,
Map
}
3 changes: 3 additions & 0 deletions src/EmptyFiles/EmptyFiles.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
<EmbeddedResource Include="$(MSBuildThisFileDirectory)..\..\files\image\empty.*">
<LogicalName>EmptyFiles.image.%(Filename)%(Extension)</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="$(MSBuildThisFileDirectory)..\..\files\map\empty.*">
<LogicalName>EmptyFiles.map.%(Filename)%(Extension)</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="$(MSBuildThisFileDirectory)..\..\files\sheet\empty.*">
<LogicalName>EmptyFiles.sheet.%(Filename)%(Extension)</LogicalName>
</EmbeddedResource>
Expand Down
3 changes: 3 additions & 0 deletions src/EmptyFiles/FileExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -235,13 +235,15 @@ public static void AddTextFileConvention(IsTextFile convention) =>
".ftn",
".gemrc",
".gemspec",
".geojson",
".gitattributes",
".gitconfig",
".gitignore",
".gitkeep",
".gitmodules",
".go",
".gpp",
".gpx",
".gradle",
".graphql",
".groovy",
Expand Down Expand Up @@ -295,6 +297,7 @@ public static void AddTextFileConvention(IsTextFile convention) =>
".jsp",
".jspx",
".jsx",
".kml",
".ksh",
".less",
".lhs",
Expand Down
15 changes: 15 additions & 0 deletions src/Tests/ExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,19 @@ public async Task BicepParametersAreText(string extension)
await Assert.That(FileExtensions.IsTextFile($"main.dev.{extension}")).IsTrue();
await Assert.That(FileExtensions.IsTextFile($"main.dev.{extension}".AsSpan())).IsTrue();
}

[Test]
[Arguments("geojson")]
[Arguments("gpx")]
[Arguments("kml")]
public async Task MapFormatsAreText(string extension)
{
await Assert.That(FileExtensions.IsTextExtension(extension)).IsTrue();
await Assert.That(FileExtensions.IsTextExtension(extension.AsSpan())).IsTrue();
await Assert.That(FileExtensions.IsTextExtension($".{extension}")).IsTrue();
await Assert.That(FileExtensions.IsTextExtension($".{extension}".AsSpan())).IsTrue();
await Assert.That(FileExtensions.IsTextFile($"route.{extension}")).IsTrue();
await Assert.That(AllFiles.TryCreateFile($"route.{extension}", useEmptyStringForTextFiles: true)).IsTrue();
File.Delete($"route.{extension}");
}
}
7 changes: 6 additions & 1 deletion src/Tests/Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ public async Task WriteExtensions()
await WriteCategory(writer, "Archive", AllFiles.Archives);
await WriteCategory(writer, "Document", AllFiles.Documents);
await WriteCategory(writer, "Image", AllFiles.Images);
await WriteCategory(writer, "Map", AllFiles.Maps);
await WriteCategory(writer, "Sheet", AllFiles.Sheets);
await WriteCategory(writer, "Slide", AllFiles.Slides);
await WriteCategory(writer, "Binary", AllFiles.Binary);
Expand All @@ -376,7 +377,11 @@ static async Task WriteCategory(StreamWriter writer, string category, IReadOnlyD
await writer.WriteLineAsync("");
await writer.WriteLineAsync($"### {category}");
await writer.WriteLineAsync("");
foreach (var file in files.OrderBy(_ => _.Key))
// Exclude the file registered by RegisterUseFile; it is a test fixture, not a shipped template.
var shipped = files
.Where(_ => _.Value.Path != useFileTarget)
.OrderBy(_ => _.Key);
foreach (var file in shipped)
{
var size = Size.Suffix(new FileInfo(file.Value.Path).Length);
await writer.WriteLineAsync($" * {file.Key} ({size})");
Expand Down
8 changes: 8 additions & 0 deletions src/extensions.include.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@
* .webp (26 bytes)
* .wmp (48 bytes)

### Map

* .gpkg (5 KB)
* .mbtiles (2 KB)
* .pmtiles (130 bytes)
* .shp (100 bytes)
* .shx (100 bytes)

### Sheet

* .ods (683 bytes)
Expand Down
Loading