Skip to content

Commit e141381

Browse files
committed
TemplateLauncher/RoslynCompiler: Support for Task and Task<T> interpolated objects
RoslynCompiler: fixed ILogger auto-add-namespace
1 parent 68eae7d commit e141381

File tree

3 files changed

+149
-51
lines changed

3 files changed

+149
-51
lines changed

README.md

+95-42
Original file line numberDiff line numberDiff line change
@@ -609,20 +609,24 @@ It's possible to interpolate special symbols like `IF/ELSE/ENDIF` or `IIF` to ad
609609
**IF-ENDIF statements**
610610

611611
```cs
612-
using CodegenCS; // besides this...
613-
using static CodegenCS.Symbols; // you also need this
614-
615-
void RenderMyApiClient(bool injectHttpClient)
612+
class MyTemplate
616613
{
617-
w.WriteLine($$"""
618-
public class MyApiClient
619-
{
620-
public MyApiClient({{ IF(injectHttpClient) }}HttpClient httpClient{{ ENDIF }})
621-
{ {{ IF(injectHttpClient) }}
622-
_httpClient = httpClient; {{ ENDIF }}
623-
}
624-
}
625-
""");
614+
void Main(ICodegenOutputFile writer)
615+
{
616+
RenderMyApiClient(writer, true);
617+
}
618+
void RenderMyApiClient(ICodegenOutputFile w, bool injectHttpClient)
619+
{
620+
w.WriteLine($$"""
621+
public class MyApiClient
622+
{
623+
public MyApiClient({{ IF(injectHttpClient) }}HttpClient httpClient{{ ENDIF }})
624+
{ {{ IF(injectHttpClient) }}
625+
_httpClient = httpClient; {{ ENDIF }}
626+
}
627+
}
628+
""");
629+
}
626630
}
627631
```
628632

@@ -641,48 +645,83 @@ public class MyApiClient
641645
**IF-ELSE-ENDIF**
642646

643647
```cs
644-
w.WriteLine($$"""
645-
public class MyApiClient
646-
{
647-
public void InvokeApi()
648+
class MyTemplate
649+
{
650+
void Main(ICodegenOutputFile writer)
651+
{
652+
var settings = new MySettings() { SwallowExceptions = true };
653+
RenderMyApiClient(writer, settings);
654+
}
655+
class MySettings
656+
{
657+
public bool SwallowExceptions;
658+
}
659+
void RenderMyApiClient(ICodegenOutputFile w, MySettings settings)
660+
{
661+
w.WriteLine($$"""
662+
public class MyApiClient
648663
{
649-
try
664+
public void InvokeApi()
650665
{
651-
restApi.Invoke();
652-
}
653-
catch (Exception ex)
654-
{ {{IF(settings.swallowExceptions) }}
655-
Log.Error(ex); {{ ELSE }}
656-
throw; {{ ENDIF }}
666+
try
667+
{
668+
restApi.Invoke();
669+
}
670+
catch (Exception ex)
671+
{ {{IF(settings.SwallowExceptions) }}
672+
Log.Error(ex); {{ ELSE }}
673+
throw; {{ ENDIF }}
674+
}
657675
}
658676
}
659-
}
660-
""");
677+
""");
678+
}
679+
}
661680
```
662681

663682
**Nested IF statements**
664683
```cs
665-
w.WriteLine($$"""
666-
{{ IF(generateConstructor) }}public class MyApiClient
667-
{
668-
public MyApiClient({{ IF(injectHttpClient) }}HttpClient httpClient{{ ENDIF }})
669-
{ {{IF(injectHttpClient) }}
670-
_httpClient = httpClient; {{ ENDIF }}
671-
}}
672-
} {{ ENDIF }}
673-
""");
684+
class MyTemplate
685+
{
686+
void Main(ICodegenOutputFile writer)
687+
{
688+
RenderMyApiClient(writer, true, true);
689+
}
690+
void RenderMyApiClient(ICodegenOutputFile w, bool generateConstructor, bool injectHttpClient)
691+
{
692+
w.WriteLine($$"""
693+
{{ IF(generateConstructor) }}public class MyApiClient
694+
{
695+
public MyApiClient({{ IF(injectHttpClient) }}HttpClient httpClient{{ ENDIF }})
696+
{ {{IF(injectHttpClient) }}
697+
_httpClient = httpClient; {{ ENDIF }}
698+
}}
699+
} {{ ENDIF }}
700+
""");
701+
}
702+
}
674703
```
675704

676705
**IIF (Immediate IF):**
677706

678707
```cs
679-
w.WriteLine($$"""
680-
public class User
681-
{
682-
{{ IIF(isVisibilityPublic, $"public ") }}string FirstName { get; set; }
683-
{{ IIF(isVisibilityPublic, $"public ", $"protected ") }}string FirstName { get; set; }
684-
}
685-
""");
708+
class MyTemplate
709+
{
710+
void Main(ICodegenOutputFile writer)
711+
{
712+
RenderMyApiClient(writer, true);
713+
}
714+
void RenderMyApiClient(ICodegenOutputFile w, bool isVisibilityPublic)
715+
{
716+
w.WriteLine($$"""
717+
public class User
718+
{
719+
{{ IIF(isVisibilityPublic, $"public ") }}string FirstName { get; set; }
720+
{{ IIF(isVisibilityPublic, $"public ", $"protected ") }}string FirstName { get; set; }
721+
}
722+
""");
723+
}
724+
}
686725
```
687726

688727

@@ -998,7 +1037,21 @@ Generated 1 file: 'C:\Users\drizin\MyTemplate.generated.cs'
9981037
Successfully executed template 'MyTemplate.cs'.
9991038
```
10001039

1040+
## Async Support
10011041

1042+
Templates can also be `async Task`, `async Task<FormattableString>` or `async Task<int>`:
1043+
1044+
```cs
1045+
class MyTemplate
1046+
{
1047+
//Task<FormattableString> Main() => Task.FromResult((FormattableString)$"My first template");
1048+
async Task<FormattableString> Main(ILogger logger)
1049+
{
1050+
await logger.WriteLineAsync($"Generating MyTemplate...");
1051+
return $"My first template";
1052+
}
1053+
}
1054+
```
10021055
10031056
# Learn More
10041057

src/Tools/TemplateBuilder/RoslynCompiler.cs

+9-1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public RoslynCompiler(TemplateBuilder builder, ILogger logger, List<string> extr
4545
AddAssembly("netstandard.dll");
4646

4747
AddAssembly("System.Runtime.dll");
48+
AddAssembly("System.Threading.dll");
4849
#endregion
4950

5051
#region System.Linq
@@ -136,6 +137,7 @@ public RoslynCompiler(TemplateBuilder builder, ILogger logger, List<string> extr
136137
if (extraReferences != null)
137138
extraReferences.ForEach(rfc => AddAssembly(MetadataReference.CreateFromFile(rfc)));
138139

140+
_namespaces.Clear(); // TODO: review why these namespaces don't make any difference here - only AddMissingUsing (applied directly to tree) matters
139141

140142
_compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
141143
.WithOverflowChecks(true)
@@ -320,13 +322,19 @@ void AddMissingUsings(List<SyntaxTree> trees)
320322
)
321323
AddMissingUsing(ref rootNode, "CodegenCS.Runtime");
322324
if (Regex.IsMatch(templateSource, @"(?<!\.)\bILogger\b") && Regex.IsMatch(templateSource, @"\bWriteLine(\w*)Async\b"))
323-
AddMissingUsing(ref rootNode, "CodegenCS.Utils");
325+
AddMissingUsing(ref rootNode, "CodegenCS.Runtime");
324326
if (Regex.IsMatch(templateSource, @"(?<!\.)\bIInputModel\b")
325327
|| Regex.IsMatch(templateSource, @"(?<!\.)\bIJsonInputModel\b")
326328
|| Regex.IsMatch(templateSource, @"(?<!\.)\bIValidatableJsonInputModel\b")
327329
|| Regex.IsMatch(templateSource, @"(?<!\.)\bIModelFactory\b")
328330
)
329331
AddMissingUsing(ref rootNode, "CodegenCS.Models");
332+
333+
if (Regex.IsMatch(templateSource, @"(?<!\.)\bTask\b"))
334+
{
335+
AddMissingUsing(ref rootNode, "System.Threading");
336+
AddMissingUsing(ref rootNode, "System.Threading.Tasks");
337+
}
330338

331339
if (Regex.IsMatch(templateSource, @"(?<!\.)\bConfigureCommand\b") || Regex.IsMatch(templateSource, @"(?<!\.)\bParseResult\b"))
332340
AddMissingUsing(ref rootNode, "System.CommandLine"); // System.CommandLine.Command, System.CommandLine.ParseResult

src/Tools/TemplateLauncher/TemplateLauncher.cs

+45-8
Original file line numberDiff line numberDiff line change
@@ -650,25 +650,62 @@ public async Task<int> ExecuteAsync(TemplateLauncherArgs _args, ParseResult pars
650650
object[] entryPointMethodArgs = new object[argTypes.Length];
651651
for (int i = 0; i < argTypes.Length; i++)
652652
entryPointMethodArgs[i] = _ctx.DependencyContainer.Resolve(argTypes[i]);
653-
if (_entryPointMethod.ReturnType == typeof(FormattableString))
653+
654+
Type returnType = _entryPointMethod.ReturnType;
655+
object result = _entryPointMethod.Invoke(instance, entryPointMethodArgs);
656+
Type[] genericTypes;
657+
658+
// Tasks should be executed/awaited
659+
if (IsAssignableToType(returnType, typeof(Task)))
660+
{
661+
var task = (Task)result;
662+
await task;
663+
664+
// and result should be unwrapped
665+
if (IsAssignableToGenericType(returnType, typeof(Task<>)) && (genericTypes = returnType.GetGenericArguments()) != null)
666+
{
667+
if (genericTypes[0]==typeof(FormattableString))
668+
{
669+
returnType = typeof(FormattableString);
670+
result = ((Task<FormattableString>)result).Result;
671+
}
672+
else if (genericTypes[0]==typeof(int))
673+
{
674+
returnType = typeof(int);
675+
result = ((Task<int>)result).Result;
676+
}
677+
}
678+
else
679+
{
680+
returnType = typeof(void);
681+
}
682+
}
683+
684+
if (returnType == typeof(FormattableString))
654685
{
655-
var fs = (FormattableString)_entryPointMethod.Invoke(instance, entryPointMethodArgs);
686+
var fs = (FormattableString)result;
656687
_ctx.DefaultOutputFile.Write(fs);
657688
}
658-
else if (_entryPointMethod.ReturnType == typeof(FormattableString))
689+
else if (returnType == typeof(string))
659690
{
660-
var s = (string)_entryPointMethod.Invoke(instance, entryPointMethodArgs);
691+
var s = (string)result;
661692
_ctx.DefaultOutputFile.Write(s);
662693
}
663-
else
694+
else if (returnType == typeof(int))
664695
{
665-
object result = _entryPointMethod.Invoke(instance, entryPointMethodArgs);
666-
if (_entryPointMethod.ReturnType == typeof(int) && ((int)result) != 0)
667-
{
696+
if (((int)result) != 0)
697+
{
668698
await _logger.WriteLineErrorAsync(ConsoleColor.Red, $"\nExiting with non-zero result code from template ({(int)result}). Nothing saved.");
669699
return (int)result;
670700
}
671701
}
702+
else if (returnType == typeof(void))
703+
{
704+
// a void method should write directly to output streams
705+
}
706+
else
707+
throw new NotImplementedException($"Unsupported Template Return Type {returnType.Name}");
708+
672709
}
673710
#endregion
674711
}

0 commit comments

Comments
 (0)