Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
ProjectTemplate.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Collections.Generic;
5 using System.Dynamic;
6 using System.IO;
7 using System.Linq;
8 using System.Text;
9 using System.Text.RegularExpressions;
10 using Mono.TextTemplating;
11 using SiliconStudio.Core;
12 using SiliconStudio.Core.Diagnostics;
13 using SiliconStudio.Core.IO;
14 using SiliconStudio.Core.Yaml;
15 
16 namespace SiliconStudio.ProjectTemplating
17 {
18  /// <summary>
19  /// Defines a project template that allows automated creation of a project structure with files.
20  /// </summary>
21  [DataContract("ProjectTemplate")]
22  public class ProjectTemplate
23  {
24  /// <summary>
25  /// Initializes a new instance of the <see cref="ProjectTemplate"/> class.
26  /// </summary>
27  public ProjectTemplate()
28  {
29  Files = new List<ProjectTemplateItem>();
30  Assemblies = new List<UFile>();
31  }
32 
33  /// <summary>
34  /// Gets or sets the template file path.
35  /// </summary>
36  /// <value>The template path.</value>
37  public string FilePath { get; private set; }
38 
39  /// <summary>
40  /// Gets a value indicating whether this template description is dynamic (itself requiring T4 parsing before
41  /// generating content files)
42  /// </summary>
43  /// <value><c>true</c> if this instance is a dynamic template; otherwise, <c>false</c>.</value>
44  public bool IsDynamicTemplate { get; private set; }
45 
46  /// <summary>
47  /// Gets or sets the files part of the template.
48  /// </summary>
49  /// <value>The files.</value>
50  public List<ProjectTemplateItem> Files { get; private set; }
51 
52  /// <summary>
53  /// Gets or sets the assemblies.
54  /// </summary>
55  /// <value>The assemblies.</value>
56  public List<UFile> Assemblies { get; private set; }
57 
58  /// <summary>
59  /// Generates this project template to the specified output directory.
60  /// </summary>
61  /// <param name="outputDirectory">The output directory.</param>
62  /// <param name="projectName">Name of the project.</param>
63  /// <param name="projectGuid">The project unique identifier.</param>
64  /// <param name="options">The options arguments that will be made available through the Session property in each template.</param>
65  /// <returns>LoggerResult.</returns>
66  /// <exception cref="System.ArgumentNullException">outputDirectory
67  /// or
68  /// projectName</exception>
69  /// <exception cref="System.InvalidOperationException">FilePath cannot be null on this instance</exception>
70  public LoggerResult Generate(string outputDirectory, string projectName, Guid projectGuid, Dictionary<string, object> options = null)
71  {
72  if (outputDirectory == null) throw new ArgumentNullException("outputDirectory");
73  if (projectName == null) throw new ArgumentNullException("projectName");
74  if (FilePath == null) throw new InvalidOperationException("FilePath cannot be null on this instance");
75 
76  var result = new LoggerResult();
77  Generate(outputDirectory, projectName, projectGuid, result, options);
78  return result;
79  }
80 
81  /// <summary>
82  /// Generates this project template to the specified output directory.
83  /// </summary>
84  /// <param name="outputDirectory">The output directory.</param>
85  /// <param name="projectName">Name of the project.</param>
86  /// <param name="projectGuid">The project unique identifier.</param>
87  /// <param name="log">The log to output errors to.</param>
88  /// <param name="options">The options arguments that will be made available through the Session property in each template.</param>
89  /// <param name="generatedOutputFiles">The generated files.</param>
90  /// <exception cref="System.ArgumentNullException">outputDirectory
91  /// or
92  /// projectName</exception>
93  /// <exception cref="System.InvalidOperationException">FilePath cannot be null on this instance</exception>
94  public void Generate(string outputDirectory, string projectName, Guid projectGuid, ILogger log, Dictionary<string, object> options = null, List<string> generatedOutputFiles = null )
95  {
96  if (outputDirectory == null) throw new ArgumentNullException("outputDirectory");
97  if (projectName == null) throw new ArgumentNullException("projectName");
98  if (log == null) throw new ArgumentNullException("log");
99  if (FilePath == null) throw new InvalidOperationException("FilePath cannot be null on this instance");
100 
101  try
102  {
103  // Check Project template filepath
104  var templateDirectory = new FileInfo(FilePath).Directory;
105  if (templateDirectory == null || !templateDirectory.Exists)
106  {
107  log.Error("Invalid ProjectTemplate directory [{0}]", FilePath);
108  return;
109  }
110 
111  // Creates the output directory
112  var directory = new DirectoryInfo(outputDirectory);
113  if (!directory.Exists)
114  {
115  directory.Create();
116  }
117 
118  // Create expando object from options valid for the whole life of generating a project template
119  var expandoOptions = new ExpandoObject();
120  var expandoOptionsAsDictionary = (IDictionary<string, object>)expandoOptions;
121  expandoOptionsAsDictionary["ProjectName"] = projectName;
122  expandoOptionsAsDictionary["ProjectGuid"] = projectGuid;
123  if (options != null)
124  {
125  foreach (var option in options)
126  {
127  expandoOptionsAsDictionary[option.Key] = option.Value;
128  }
129  }
130 
131  var engine = new TemplatingEngine();
132 
133  // In case this project template is dynamic, we need to generate its content first through T4
134  if (IsDynamicTemplate)
135  {
136  var content = File.ReadAllText(FilePath);
137  var host = new ProjectTemplatingHost(log, FilePath, templateDirectory.FullName, expandoOptions, Assemblies.Select(assembly => assembly.FullPath));
138  var newTemplateAsString = engine.ProcessTemplate(content, host);
139  Files.Clear();
140  using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(newTemplateAsString)))
141  {
142  var newTemplate = (ProjectTemplate)YamlSerializer.Deserialize(stream);
143  Files.AddRange(newTemplate.Files);
144  }
145  }
146 
147  // Iterate on each files
148  foreach (var fileItem in Files)
149  {
150  if (fileItem.Source == null)
151  {
152  log.Warning("Invalid empty file item [{0}] with no source location", fileItem);
153  continue;
154  }
155  var sourceFilePath = System.IO.Path.Combine(templateDirectory.FullName, fileItem.Source);
156  var targetLocation = fileItem.Target ?? fileItem.Source;
157  if (Path.IsPathRooted(targetLocation))
158  {
159  log.Error("Invalid file item [{0}]. TargetLocation must be a relative path", fileItem);
160  continue;
161  }
162 
163  var targetLocationExpanded = Expand(targetLocation, expandoOptionsAsDictionary, log);
164 
165  // If this is a template file, turn template on by default
166  if (fileItem.IsTemplate)
167  {
168  var targetPath = Path.GetDirectoryName(targetLocationExpanded);
169  var targetFileName = Path.GetFileName(targetLocationExpanded);
170  targetLocationExpanded = targetPath != null ? Path.Combine(targetPath, targetFileName) : targetFileName;
171  }
172 
173  var targetFilePath = Path.Combine(outputDirectory, targetLocationExpanded);
174  try
175  {
176  // Make sure that the target directory does exist
177  var targetDirectory = new FileInfo(targetFilePath).Directory;
178  if (!targetDirectory.Exists)
179  {
180  targetDirectory.Create();
181  }
182 
183  bool fileGenerated = false;
184  if (fileItem.IsTemplate)
185  {
186  var content = File.ReadAllText(sourceFilePath);
187  var host = new ProjectTemplatingHost(log, sourceFilePath, templateDirectory.FullName, expandoOptions, Assemblies.Select(assembly => assembly.FullPath));
188  var newContent = engine.ProcessTemplate(content, host);
189  if (newContent != null)
190  {
191  fileGenerated = true;
192  File.WriteAllText(targetFilePath, newContent);
193  }
194  }
195  else
196  {
197  fileGenerated = true;
198  File.Copy(sourceFilePath, targetFilePath, true);
199  }
200 
201  if (generatedOutputFiles != null && fileGenerated)
202  {
203  generatedOutputFiles.Add(targetFilePath);
204  }
205  }
206  catch (Exception ex)
207  {
208 
209  log.Error("Unexpected exception while processing [{0}]", fileItem, ex);
210  }
211  }
212  }
213  catch (Exception ex)
214  {
215  log.Error("Unexpected exception while processing project template [{0}] to directory [{1}]", projectName, outputDirectory, ex);
216  }
217  }
218 
219  public string GeneratePart(string templatePathPart, ILogger log, Dictionary<string, object> options)
220  {
221  if (templatePathPart == null) throw new ArgumentNullException("templatePathPart");
222  if (log == null) throw new ArgumentNullException("log");
223  var expandoOptions = new ExpandoObject();
224  var expandoOptionsAsDictionary = (IDictionary<string, object>)expandoOptions;
225  foreach (var option in options)
226  {
227  expandoOptionsAsDictionary[option.Key] = option.Value;
228  }
229 
230  var templateDirectory = new FileInfo(FilePath).Directory;
231  var sourceFilePath = System.IO.Path.Combine(templateDirectory.FullName, templatePathPart);
232  var content = File.ReadAllText(sourceFilePath);
233 
234  var engine = new TemplatingEngine();
235  var host = new ProjectTemplatingHost(log, sourceFilePath, templateDirectory.FullName, expandoOptions, Assemblies.Select(assembly => assembly.FullPath));
236  return engine.ProcessTemplate(content, host);
237  }
238 
239  private static bool HasT4Extension(string filePath)
240  {
241  return filePath.EndsWith(".tt", StringComparison.InvariantCultureIgnoreCase)
242  || filePath.EndsWith(".t4", StringComparison.InvariantCultureIgnoreCase);
243  }
244 
245  private static readonly Regex ExpandRegex = new Regex(@"\$(\w+)\$");
246 
247  private static string Expand(string str, IDictionary<string, object> properties, ILogger log)
248  {
249  if (str == null) throw new ArgumentNullException("str");
250  if (properties == null) throw new ArgumentNullException("properties");
251 
252  return ExpandRegex.Replace(str, match =>
253  {
254  var propertyName = match.Groups[1].Value;
255  object propertyValue;
256  if (properties.TryGetValue(propertyName, out propertyValue))
257  {
258  return propertyValue == null ? string.Empty : propertyValue.ToString();
259  }
260  log.Warning("Unable to replace property [{0}] not found in options");
261  return match.Value;
262  });
263  }
264 
265  /// <summary>
266  /// Loads the a <see cref="ProjectTemplate"/> from the specified file path.
267  /// </summary>
268  /// <param name="filePath">The project template file.</param>
269  /// <returns>An instance of the project template.</returns>
270  /// <exception cref="System.ArgumentNullException">filePath</exception>
271  public static ProjectTemplate Load(string filePath)
272  {
273  if (filePath == null) throw new ArgumentNullException("filePath");
274 
275  var fullFilePath = System.IO.Path.Combine(Environment.CurrentDirectory, filePath);
276  var projectFile = File.ReadAllText(fullFilePath);
277  ProjectTemplate template;
278  // If this a project template?
279  if (projectFile.StartsWith("<#@"))
280  {
281  template = new ProjectTemplate() { IsDynamicTemplate = true };
282  }
283  else
284  {
285  using (var stream = new FileStream(fullFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
286  {
287  template = (ProjectTemplate)YamlSerializer.Deserialize(stream);
288  }
289  }
290 
291  template.FilePath = fullFilePath;
292  return template;
293  }
294  }
295 }
SiliconStudio.Core.Diagnostics.LoggerResult LoggerResult
A logger that stores messages locally useful for internal log scenarios.
Definition: LoggerResult.cs:14
Defines a project template that allows automated creation of a project structure with files...
System.Text.Encoding Encoding
System.IO.FileMode FileMode
Definition: ScriptSync.cs:33
LoggerResult Generate(string outputDirectory, string projectName, Guid projectGuid, Dictionary< string, object > options=null)
Generates this project template to the specified output directory.
Default Yaml serializer used to serialize assets by default.
void Generate(string outputDirectory, string projectName, Guid projectGuid, ILogger log, Dictionary< string, object > options=null, List< string > generatedOutputFiles=null)
Generates this project template to the specified output directory.
string GeneratePart(string templatePathPart, ILogger log, Dictionary< string, object > options)
ProjectTemplate()
Initializes a new instance of the ProjectTemplate class.
static object Deserialize(Stream stream)
Deserializes an object from the specified stream (expecting a YAML string).
static ProjectTemplate Load(string filePath)
Loads the a ProjectTemplate from the specified file path.
Interface for logging.
Definition: ILogger.cs:8