2012-06-27 22 views
6

Ho un programma C# regex-parser con tre file in essa contenuti, ciascuno contenente una classe statica:Genera file T4 quando i file di codice stessa classe sono stati modificati

1) una classe statica pieno di dizionari stringa

static class MyStringDicts 
{ 
    internal static readonly Dictionary<string, string> USstates = 
     new Dictionary<string, string>() 
     { 
      { "ALABAMA", "AL" }, 
      { "ALASKA", "AK" }, 
      { "AMERICAN SAMOA", "AS" }, 
      { "ARIZONA", "AZ" }, 
      { "ARKANSAS", "AR" } 
      // and so on 
     } 
    // and some other dictionaries 
} 

2) Una classe che compila questi valori in Regex

public static class Patterns 
{  
    Public static readonly string StateUS = 
     @"\b(?<STATE>" + CharTree.GenerateRegex(Enumerable.Union(
      AddrVals.USstates.Keys, 
      AddrVals.USstates.Values)) 
     + @")\b"; 

    //and some more like these 
} 

3) del codice che gestisce le espressioni regolari sulla base di queste stringhe:

public static class Parser 
{ 
    // heavily simplified example 
    public static GroupCollection SearchStringForStates(string str) 
    { 
     return Regex.Match(str, 
      "^" + Patterns.StateUS, 
      RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase).Groups; 
    } 
} 

Mi piacerebbe essere in grado di generare 2) come con un modello T4, come tutto questo concatenamento è identico su ogni esecuzione:

@"\b(?<STATE><#=CharTree.GenerateRegex(Enumerable.Union(
    AddrVals.USstates.Keys, 
    AddrVals.USstates.Values)#>)\b"; 

Questo funziona, ma se creo un nuovo membro di MyStringDicts o aggiungere/rimuovere alcuni valori dai suoi dizionari, il modello T4 non li riconoscerà fino a escludere Patterns.cs dalla compilazione e ricompilare. Dato che Parser dipende da Patterns, questa non è davvero un'opzione: ho bisogno della trasformazione T4 per prendere in considerazione le modifiche ad altri file nella stessa build.

Le cose che non voglio fare fare:

  • Split MyStringDicts in un proprio progetto. Mi piacerebbe mantenere i file in un progetto, in quanto sono un'unità logica.
  • Basta spostare MyStringDicts nella parte superiore di Patterns.cs. Ho bisogno i membri MyStringDicts per altri scopi, anche (per le ricerche del dizionario, o in altri modelli T4, per esempio.)

ho adottato il consiglio here sull'utilizzo di T4Toolbox VolatileAssembly e simili, ma che sembra funzionare solo per la direzione inversa, quando i file di classe devono essere ricompilati dopo aver modificato il modello T4.

È ciò che I desidera possibile?

modificati per chiarezza

+1

Puoi spiegare un po 'di più sul perché lo stai facendo? Riesco a vedere diversi modi per affrontare questo problema, ma è piuttosto difficile sapere quale sia adatto nel tuo scenario senza qualche background. – AVee

+0

Btw, queste sono le idee che ho. Forse il suggerimento è sufficiente per farti andare. Probabilmente la soluzione fornita da FuleSnabel funzionerà, potresti anche fare qualcosa di simile usando lo spazio dei nomi di EnvDte. Ma forse qualcosa di più semplice farà. Potresti prendere in considerazione la possibilità di inserire le classi di cui hai bisogno in T4 in un progetto separato e solo facendo riferimento ai modelli. È anche possibile compilare e eseguire dinamicamente il codice necessario dinamicamente all'interno dei modelli T4. – AVee

+0

@AVee grazie per avermi costretto a fare la cosa giusta e includere il mio caso reale. – Arithmomaniac

risposta

4

Ho appena creato un piccolo modello di test che utilizza EnvDTE (Visual Studio Automazione) e il T4Toolbox a correre attraverso il primo file. Raccoglie il file attraverso il progetto, quindi non è necessario compilare prima di eseguire il modello. Infatti, rileva anche modifiche non salvate ...

Questo è fondamentalmente lo stesso approccio utilizzato da FullSnabel, ma senza la necessità di Roslyn.

<#@ template debug="false" hostspecific="True" language="C#" #> 
<#@ output extension=".cs" #> 
<#@ Assembly Name="System.Core.dll" #> 
<#@ dte processor="T4Toolbox.DteProcessor" #> 
<#@ TransformationContext processor="T4Toolbox.TransformationContextProcessor" #> 
<#@ assembly name="System.Xml" #> 
<#@ assembly name="EnvDTE" #> 
<#@ assembly name="EnvDTE80" #> 
<#@ import namespace="T4Toolbox" #> 
<#@ import namespace="EnvDTE" #> 
<#@ import namespace="EnvDTE80" #> 
<# 
    ProjectItem projectItem = TransformationContext.FindProjectItem("Dictionaries.cs"); 
    FileCodeModel codeModel = projectItem.FileCodeModel; 

    foreach (CodeElement element in codeModel.CodeElements) 
    { 
     CodeNamespace ns = element as CodeNamespace; 
     if(ns != null) 
     { 
      foreach(CodeElement ele in ns.Children) 
      { 
       CodeClass cl = ele as CodeClass; 

       if(cl != null && cl.Name == "Dictionaries") 
       { 
        foreach(CodeElement member in cl.Members) 
        { 
         // Generate stuff... 
         this.WriteLine(member.Name); 
        } 
       } 
      } 
     } 
    } 
#> 

Questo dovrebbe funzionare se si desidera attenersi al proprio approccio originale.

Quello che sembra fare è memorizzare i dati in un file di classe. Potresti considerare di memorizzare gli elenchi al di fuori del codice (in un file xml o ini) e generare entrambi i file in base a tali dati. In questo modo si evita il problema tutti insieme, potrebbe anche semplificare la gestione degli elenchi. Se non ti preoccupi troppo delle modifiche alla lista, potresti anche inserire i dizionari all'interno del modello T4 stesso.

Un'altra alternativa potrebbe occuparsi completamente di codice. È possibile creare una sottoclasse di Dizionario che abbia una proprietà 'Pattern' (o la funzione GetPattern()). Il parser utilizzerà quindi AddrVals.USstates.Pattern e la classe patterns non sarà più necessaria. In questo modo non avrai bisogno di alcuna generazione di codice.

Forse un wrapper attorno al dizionario effettivo sarebbe migliore perché consente di nascondere la raccolta effettiva per assicurarsi che non venga modificata in fase di runtime. Vedi Is there a read-only generic dictionary available in .NET? per un esempio.

+0

Secondo l'idea che i dati debbano essere memorizzati in un file "contenuto" anziché in codice. Rende l'intero shebang molto più facile da leggere e mantenere. –

3

Date un'occhiata a roslyn. Consente di compilare i file di origine in alberi di sintassi da cui è possibile esaminare e generare codice. È un CTP, ma ha funzionato abbastanza bene per me.

(Aggiunto un esempio Roslyn).

ho creato un file chiamato Class2.cs nella mia soluzione:

namespace StackOverflow 
{ 
    class Class2 
    { 
     public static int One() { return 8; } 
     public static int Eight(int x, double z) { return 8; } 
    } 
} 

Utilizzando la Roslyn CTP (è necessario il Visual studio SDK pure) Ho creato questo semplice modello T4, che utilizza per analizzare Roslyn Class2.cs e produrre output basato su quello:

<#@ template hostspecific= "true"       #> 
<#@ assembly name  = "System.Core"      #> 
<#@ assembly name  = "Roslyn.Compilers"    #> 
<#@ assembly name  = "Roslyn.Compilers.CSharp"   #> 
<#@ import  namespace = "System.IO"      #> 
<#@ import  namespace = "System.Linq"      #> 
<#@ import  namespace = "Roslyn.Compilers.CSharp"   #> 

<# 

    var host = Path.GetFullPath(Host.ResolvePath(@".\Class2.cs")); 
    var content = File.ReadAllText(host); 

    var tree = SyntaxTree.ParseCompilationUnit(content); 

    var methods = tree 
     .GetRoot() 
     .ChildNodes() 
     .OfType<NamespaceDeclarationSyntax>() 
     .SelectMany(x => x.ChildNodes()) 
     .OfType<ClassDeclarationSyntax>() 
     .SelectMany(x => x.ChildNodes()) 
     .OfType<MethodDeclarationSyntax>() 
     .ToArray() 
     ; 
#>    

namespace StackOverflow 
{ 
    using System; 

    static partial class Program 
    { 
     public static void Main() 
     { 
<# 
    foreach (var method in methods) 
    { 
     var parent = (ClassDeclarationSyntax)method.Parent; 
     var types = method 
      .ParameterList 
      .ChildNodes() 
      .OfType<ParameterSyntax>() 
      .Select(t => t.Type.PlainName) 
      .ToArray() 
      ; 

     var plist = string.Join(", ", types); 
#> 
      Console.WriteLine("<#=parent.Identifier.ValueText#>.<#=method.Identifier.ValueText#>(<#=plist#>).ToString()"); 
<# 
    } 
#> 
     } 
    } 
} 

Questo modello produce il seguente output basato su Class2.cs:

namespace StackOverflow 
{ 
    using System; 

    static partial class Program 
    { 
     public static void Main() 
     { 
       Console.WriteLine("Class2.One().ToString()"); 
       Console.WriteLine("Class2.Eight(int, double).ToString()"); 
      } 
    } 
} 

Spero che questo aiuti

+0

Preferirei davvero una soluzione T4, ma prenderò ciò che posso ottenere. Hai un link ad un esempio funzionante di questo? Senza uno, sarà sicuramente fuori dalla mia portata. – Arithmomaniac

+0

Nota; userete Roslyn per compilare il codice C# nell'albero della sintassi. Quindi utilizzerai T4 per generare il codice. Sfortunatamente non ho un campione funzionante in questo momento. – FuleSnabel

+3

Ho aggiunto un esempio per dimostrare come utilizzare Roslyn da T4. – FuleSnabel

Problemi correlati