-
Notifications
You must be signed in to change notification settings - Fork 88
/
Copy pathTemplate.cs
105 lines (94 loc) · 4.16 KB
/
Template.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
using Newtonsoft.Json;
namespace ExampleExtractor;
internal class Template
{
private const string AdditionalFilesDirectory = "additional-files";
private const string ExampleCodeSubstitution = "$example-code";
private const string ExampleNameSubstitution = "$example-name";
private const string FileCommentPrefix = "// File ";
internal string Name { get; }
private readonly Dictionary<string, string> files;
private Template(string name, Dictionary<string, string> files)
{
Name = name;
this.files = files;
}
/// <summary>
/// Applies the given example to this template, writing it out to an output directory.
/// </summary>
/// <param name="example">The example to apply.</param>
/// <param name="rootOutputDirectory">The root output directory. (A subdirectory for the example will be created within this.)</param>
/// <param name="rootTemplateDirectory">The root template directory, used for finding additional files if necessary.</param>
internal void Apply(Example example, string rootOutputDirectory, string rootTemplateDirectory)
{
var outputDirectory = Path.Combine(rootOutputDirectory, example.Name);
Directory.CreateDirectory(outputDirectory);
var code = ExtractExtraFiles(example.Code, outputDirectory);
foreach (var pair in files)
{
string file = Path.Combine(outputDirectory, pair.Key);
string content = pair.Value
.Replace(ExampleCodeSubstitution, code)
.Replace(ExampleNameSubstitution, example.Name);
File.WriteAllText(file, content);
}
if (example.Metadata.AdditionalFiles is List<string> additionalFiles)
{
foreach (var additionalFile in additionalFiles)
{
string sourceFile = Path.Combine(rootTemplateDirectory, AdditionalFilesDirectory, additionalFile);
string destFile = Path.Combine(outputDirectory, additionalFile);
File.Copy(sourceFile, destFile);
}
}
var metadataJson = JsonConvert.SerializeObject(example.Metadata);
File.WriteAllText(Path.Combine(outputDirectory, ExampleMetadata.MetadataFile), metadataJson);
}
/// <summary>
/// Returns all the code before the first "// File:" comment, extracting any additional
/// files into the given directory.
/// </summary>
private string ExtractExtraFiles(string code, string outputDirectory)
{
var lines = code.Split('\n').ToList();
// The implementation is a lot simpler if we know we've got an end marker.
lines.Add(FileCommentPrefix + "IgnoreMe");
string? currentFile = null;
List<string> currentLines = new List<string>();
string? initialCode = null;
foreach (var line in lines)
{
if (line.StartsWith(FileCommentPrefix))
{
if (currentFile is null)
{
// Remember this for later.
initialCode = string.Join("\n", currentLines);
}
else
{
File.WriteAllLines(currentFile, currentLines);
}
currentLines.Clear();
currentFile = Path.Combine(outputDirectory, line[FileCommentPrefix.Length..].TrimEnd(':'));
}
else
{
currentLines.Add(line);
}
}
return initialCode ?? throw new InvalidOperationException($"Never saw a file terminator - bug in {nameof(ExtractExtraFiles)}");
}
internal static Dictionary<string, Template> LoadTemplates(string directory) =>
Directory.GetDirectories(directory)
.Select(LoadTemplate)
.ToDictionary(template => template.Name);
private static Template LoadTemplate(string directory)
{
var name = Path.GetFileName(directory);
var files = Directory.GetFiles(directory)
.Where(file => Path.GetFileName(file) != AdditionalFilesDirectory)
.ToDictionary(file => Path.GetFileName(file), file => File.ReadAllText(file));
return new Template(name, files);
}
}