Last active
January 28, 2025 17:58
-
-
Save Fasteroid/217e8f2b2784ae2f4f8a63be108fe29e to your computer and use it in GitHub Desktop.
The ultimate Typewriter template for your MVC controllers!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
${ | |
using Typewriter.Extensions.Types; | |
using System.Text.RegularExpressions; | |
using System.Reflection; | |
/* | |
__TypewriterControllers.tst v1.1.1 - https://gist.github.com/Fasteroid | |
Features: | |
- Automatically imports type dependencies from the same assembly | |
- Handles generics and arrays | |
- Handles nullable types | |
- Leaves casing alone | |
- Nullables are translated as `?` instead of `| null` | |
- Developer can implement `trimmed` to reduce bandwidth usage | |
- Developer can implement APIUtils to wrap API calls | |
*/ | |
static string AssemblyName = "my-api"; | |
string MethodPath(Method m){ | |
String path = (String) m.Attributes.First(a => a.Name.StartsWith("Http")).Arguments.First().Value; | |
if( path == "[action]" ){ | |
path = m.name; | |
} | |
return path; | |
} | |
string ClassPath(Class c){ | |
String path = (String) c.Attributes.First(a => a.Name.StartsWith("Route")).Arguments.First().Value; | |
if( path == "[controller]" ){ | |
path = c.Name.Replace("Controller",""); | |
} | |
return path; | |
} | |
string ClassWithGenerics(Class c){ | |
return ( (Type) c ).Name; | |
} | |
string TypeToString(Type t){ | |
var ret = t.Name; | |
return ret; | |
} | |
Type ReturnType(Method m){ | |
Type t = m.Type; | |
if( t.Name.StartsWith("ActionResult") ){ | |
t = t.TypeArguments.FirstOrDefault(); | |
} | |
return t; | |
} | |
string ReturnTypeAsString(Method m){ | |
return TypeToString( ReturnType(m) ); | |
} | |
string OriginalName(Type t){ | |
return t.OriginalName.Replace("?",""); | |
} | |
IEnumerable<Type> GetMetaClasses(Type t){ | |
var meta = new List<Type>(){t}; | |
if( t.IsGeneric || t.IsEnumerable ){ | |
meta.AddRange( t.TypeArguments.SelectMany(GetMetaClasses) ); | |
} | |
return meta.Where( IsIncludable ).ToList(); | |
} | |
bool IsIncludable(Type t){ | |
return (t.AssemblyName == AssemblyName) && (t.ContainingClass == null); // no containing class = not a generic placeholder | |
} | |
IEnumerable<Type> GetIncludableTypes( IEnumerable<Type> types, Class parent ){ | |
return types.SelectMany(t => GetMetaClasses(t)) | |
.Where(t => IsIncludable(t)) | |
.Where(t => OriginalName(t) != parent.Name); | |
} | |
string LastNPaths(string path, int n){ | |
return string.Join("\\", path.Split('\\').Reverse().Take(n).Reverse()); | |
} | |
string FileSpecificImports(File f){ | |
List<Type> includes = new List<Type>(); | |
foreach( Class c in f.Classes ){ | |
var validMethods = c.Methods.Where( m => MethodType(m) != "unknown" ); | |
includes.AddRange( GetIncludableTypes( validMethods.Select( ReturnType ).Where( t => t != null ), c ) ); | |
includes.AddRange( GetIncludableTypes( validMethods.SelectMany(m => m.Parameters).Select(p => p.Type), c ) ); | |
includes.AddRange( GetIncludableTypes( c.Properties.Select(p => p.Type), c ) ); | |
} | |
return string.Join(Environment.NewLine, includes.Select((t) => { | |
var path = LastNPaths( t.FileLocations.First(), 2 ); | |
path = path.Replace("\\","/"); // fix for windows paths | |
path = path.Substring(0, path.LastIndexOf('.')); // remove file extension | |
return $"import {{ {OriginalName(t)} }} from '../{path.Replace("\\","/")}';"; | |
}).Distinct()); | |
} | |
string APIPath( Method m ){ | |
Class c = (Class) m.Parent; | |
return $"/{ClassPath(c)}/{MethodPath(m)}"; | |
} | |
string MethodType( Method m ){ | |
if( m.Attributes.Any(a => a.Name.StartsWith("HttpGet")) ){ | |
return "get"; | |
} | |
if( m.Attributes.Any(a => a.Name.StartsWith("HttpPost")) ){ | |
return "post"; | |
} | |
return "unknown"; | |
} | |
bool TypewriterIgnore( Method m ){ | |
return m.Attributes.Any(a => a.Name == "TypewriterIgnore"); | |
} | |
string ParamType( Method m ){ | |
try { | |
return m.Parameters.First().Type.Name; | |
} | |
catch { | |
return "void"; | |
} | |
} | |
void CollectTypeProperties( Type t, List<string> props ){ | |
props.AddRange( t.Properties.Where(p => p.Attributes.All(a => a.Name != "JsonIgnore")).Select( p => "'" + p.Name + "'" ).ToList() ); | |
if( t.BaseClass != null ){ | |
CollectTypeProperties(t.BaseClass, props); | |
} | |
} | |
string ParamProperties( Method m ){ | |
try { | |
var t = m.Parameters.First(); | |
List<string> props = new List<string>(); | |
CollectTypeProperties(t.Type, props); | |
return "[" + props.Aggregate((a, b) => a + ", " + b) + "]"; | |
} catch { | |
return "[ /* Dear dev, this POST has no arguments. Please use a GET instead. */ ]"; | |
} | |
} | |
} | |
///////////////////////////////////////////////////////////////////////////////////////////////////// | |
// WARNING: THIS IS AN AUTO-GENERATED FILE - DO NOT MODIFY IT! YOUR CHANGES WILL BE OVERWRITTEN! // | |
// MODIFY THE TEMPLATE (.tst) FILE INSTEAD! // | |
// https://github.com/Fasteroid/Typewriter/ // | |
///////////////////////////////////////////////////////////////////////////////////////////////////// | |
import { Injectable } from '@angular/core'; | |
import { environment } from '../../../../environments/environment'; | |
import { APIUtils } from 'services/apiutils.service'; | |
import { trimmed } from 'shared/misc/Utils'; | |
import { Observable } from 'rxjs'; | |
$FileSpecificImports | |
const BASE = environment.apiUrlBase; | |
$Classes(:CustomControllerBase)[ | |
@Injectable({ | |
providedIn: 'root' | |
}) | |
export class $Name { | |
constructor( private utils: APIUtils ) { } | |
$Methods(m => MethodType(m) == "get" && TypewriterIgnore(m) == false)[ | |
$name(): Observable<$ReturnTypeAsString> { | |
return this.utils.get<$ReturnTypeAsString>(`${BASE}$APIPath`); | |
}] | |
$Methods(m => MethodType(m) == "post" && TypewriterIgnore(m) == false)[ | |
$name(body: $ParamType): Observable<$ReturnTypeAsString> { | |
return this.utils.post<$ReturnTypeAsString>(`${BASE}$APIPath`, trimmed(body, $ParamProperties)); | |
}] | |
]} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment