Skip to content

Commit

Permalink
Handlebars Compiler Added (#291)
Browse files Browse the repository at this point in the history
* Initial Support for Handlebars added

* Increasing Package Version to force redownload node modules

* Adding node directory to .gitignore file

* Updating extension manifest file

* Source Map file creation support for Handlebars compiler added

* Basic Tests for Handlebars added

* Temporally map file name changed to prevent unintended overwrites

* Handlebars: Spelling mistake fixed
  • Loading branch information
falahati authored and madskristensen committed Feb 15, 2017
1 parent 313198e commit dc1f553
Show file tree
Hide file tree
Showing 27 changed files with 626 additions and 31 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,7 @@ x64/
# NCrunch
*.ncrunchsolution
*.ncrunchproject
_NCrunch_WebCompiler
_NCrunch_WebCompiler#Node

#Node
src/WebCompiler/Node/node_modules/*
1 change: 1 addition & 0 deletions build/build.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ call npm install --quiet ^
less-plugin-csscomb ^
node-sass ^
stylus ^
handlebars ^
> nul

if not exist "node_modules\node-sass\vendor\win32-ia32-14" (
Expand Down
9 changes: 7 additions & 2 deletions src/WebCompiler/Compile/CompilerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ namespace WebCompiler
/// </summary>
public static class CompilerService
{
internal const string Version = "1.4.166";
internal const string Version = "1.4.167";
private static readonly string _path = Path.Combine(Path.GetTempPath(), "WebCompiler" + Version);
private static object _syncRoot = new object(); // Used to lock on the initialize step

/// <summary>A list of allowed file extensions.</summary>
public static readonly string[] AllowedExtensions = new[] { ".LESS", ".SCSS", ".SASS", ".STYL", ".COFFEE", ".ICED", ".JS", ".JSX", ".ES6" };
public static readonly string[] AllowedExtensions = new[] { ".LESS", ".SCSS", ".SASS", ".STYL", ".COFFEE", ".ICED", ".JS", ".JSX", ".ES6", ".HBS", ".HANDLEBARS" };

/// <summary>
/// Test if a file type is supported by the compilers.
Expand All @@ -42,6 +42,11 @@ internal static ICompiler GetCompiler(Config config)
compiler = new LessCompiler(_path);
break;

case ".HANDLEBARS":
case ".HBS":
compiler = new HandlebarsCompiler(_path);
break;

case ".SCSS":
case ".SASS":
compiler = new SassCompiler(_path);
Expand Down
200 changes: 200 additions & 0 deletions src/WebCompiler/Compile/HandlebarsCompiler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

namespace WebCompiler
{
class HandlebarsCompiler : ICompiler
{
private static Regex _errorRx = new Regex("Error: (?<message>.+) on line (?<line>[0-9]+):", RegexOptions.Compiled);
private string _mapPath;
private string _path;
private string _name = string.Empty;
private string _extension = string.Empty;
private string _output = string.Empty;
private string _error = string.Empty;
private bool _partial = false;

public HandlebarsCompiler(string path)
{
_path = path;
}

public CompilerResult Compile(Config config)
{
string baseFolder = Path.GetDirectoryName(config.FileName);
string inputFile = Path.Combine(baseFolder, config.InputFile);

FileInfo info = new FileInfo(inputFile);
string content = File.ReadAllText(info.FullName);

CompilerResult result = new CompilerResult
{
FileName = info.FullName,
OriginalContent = content,
};

var extension = Path.GetExtension(inputFile);
if (!string.IsNullOrWhiteSpace(extension))
{
_extension = extension.Substring(1);
}

var name = Path.GetFileNameWithoutExtension(inputFile);
if (!string.IsNullOrWhiteSpace(name) && name.StartsWith("_"))
{
_name = name.Substring(1);
_partial = true;

// Temporarily Fix
// TODO: Remove after actual fix
var tempFilename = Path.Combine(Path.GetDirectoryName(inputFile), _name + ".handlebarstemp");
info.CopyTo(tempFilename);
info = new FileInfo(tempFilename);
_extension = "handlebarstemp";
}

_mapPath = Path.ChangeExtension(inputFile, ".js.map.tmp");

try
{
RunCompilerProcess(config, info);

result.CompiledContent = _output;

var options = HandlebarsOptions.FromConfig(config);
if (options.SourceMap || config.SourceMap)
{
if (File.Exists(_mapPath))
result.SourceMap = File.ReadAllText(_mapPath);
}

if (_error.Length > 0)
{
CompilerError ce = new CompilerError
{
FileName = inputFile,
Message = _error.Replace(baseFolder, string.Empty),
IsWarning = !string.IsNullOrEmpty(_output)
};

var match = _errorRx.Match(_error);

if (match.Success)
{
ce.Message = match.Groups["message"].Value.Replace(baseFolder, string.Empty);
ce.LineNumber = int.Parse(match.Groups["line"].Value);
ce.ColumnNumber = 0;
}

result.Errors.Add(ce);
}
}
catch (Exception ex)
{
CompilerError error = new CompilerError
{
FileName = inputFile,
Message = string.IsNullOrEmpty(_error) ? ex.Message : _error,
LineNumber = 0,
ColumnNumber = 0,
};

result.Errors.Add(error);
}
finally
{
if (File.Exists(_mapPath))
{
File.Delete(_mapPath);
}
// Temporarily Fix
// TODO: Remove after actual fix
if (info.Extension == ".handlebarstemp")
{
info.Delete();
}
}

return result;
}

private void RunCompilerProcess(Config config, FileInfo info)
{
string arguments = ConstructArguments(config);

ProcessStartInfo start = new ProcessStartInfo
{
WorkingDirectory = info.Directory.FullName,
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true,
FileName = "cmd.exe",
Arguments = $"/c \"\"{Path.Combine(_path, "node_modules\\.bin\\handlebars.cmd")}\" \"{info.FullName}\" {arguments}\"",
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8,
RedirectStandardOutput = true,
RedirectStandardError = true,
};

start.EnvironmentVariables["PATH"] = _path + ";" + start.EnvironmentVariables["PATH"];

using (Process p = Process.Start(start))
{
var stdout = p.StandardOutput.ReadToEndAsync();
var stderr = p.StandardError.ReadToEndAsync();
p.WaitForExit();

_output = stdout.Result.Trim();
_error = stderr.Result.Trim();
}
}

private string ConstructArguments(Config config)
{
string arguments = "";

HandlebarsOptions options = HandlebarsOptions.FromConfig(config);

if (options.AMD)
arguments += " --amd";
else if (!string.IsNullOrEmpty(options.CommonJS))
arguments += $" --commonjs \"{options.CommonJS}\"";

foreach (var knownHelper in options.KnownHelpers)
{
arguments += $" --known \"{knownHelper}\"";
}

if (options.KnownHelpersOnly)
arguments += " --knownOnly";

if (options.ForcePartial || _partial)
arguments += " --partial";

if (options.NoBOM)
arguments += " --bom";

if ((options.SourceMap || config.SourceMap) && !string.IsNullOrWhiteSpace(_mapPath))
arguments += $" --map \"{_mapPath}\"";

if (!string.IsNullOrEmpty(options.TemplateNameSpace))
arguments += $" --namespace \"{options.TemplateNameSpace}\"";

if (!string.IsNullOrEmpty(options.Root))
arguments += $" --root \"{options.Root}\"";

if (!string.IsNullOrEmpty(options.Name))
arguments += $" --name \"{options.Name}\"";
else if (!string.IsNullOrEmpty(_name))
arguments += $" --name \"{_name}\"";

if (!string.IsNullOrEmpty(_extension))
arguments += $" --extension \"{_extension}\"";

return arguments;
}
}
}
124 changes: 124 additions & 0 deletions src/WebCompiler/Compile/HandlebarsOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
using Newtonsoft.Json;
using System.Linq;

namespace WebCompiler
{
/// <summary>
/// Give all options for the Handlebars compiler
/// </summary>
public class HandlebarsOptions : BaseOptions<HandlebarsOptions>
{
private const string trueStr = "true";

/// <summary> Creates a new instance of the class.</summary>
public HandlebarsOptions()
{ }

/// <summary>
/// Load the settings from the config object
/// </summary>
protected override void LoadSettings(Config config)
{
base.LoadSettings(config);

var name = GetValue(config, "name");
if (name != null)
Name = name;

var @namespace = GetValue(config, "namespace");
if (@namespace != null)
TemplateNameSpace = @namespace;

var root = GetValue(config, "root");
if (root != null)
Root = root;

var commonjs = GetValue(config, "commonjs");
if (commonjs != null)
CommonJS = commonjs;

var amd = GetValue(config, "amd");
if (amd != null)
AMD = amd.ToLowerInvariant() == trueStr;

var forcePartial = GetValue(config, "forcePartial");
if (forcePartial != null)
ForcePartial = forcePartial.ToLowerInvariant() == trueStr;

var noBOM = GetValue(config, "noBOM");
if (noBOM != null)
NoBOM = noBOM.ToLowerInvariant() == trueStr;

var knownHelpersOnly = GetValue(config, "knownHelpersOnly");
if (knownHelpersOnly != null)
KnownHelpersOnly = knownHelpersOnly.ToLowerInvariant() == trueStr;

var knownHelpers = GetValue(config, "knownHelpers");
if (knownHelpers != null)
KnownHelpers = knownHelpers.Split(',').Where(s => !string.IsNullOrWhiteSpace(s)).Select(s => s.Trim()).ToArray();
}

/// <summary>
/// The file name should match the compiler name
/// </summary>
protected override string CompilerFileName
{
get { return "hbs"; }
}

/// <summary>
/// Template root. Base value that will be stripped from template names.
/// </summary>
[JsonProperty("root")]
public string Root { get; set; } = "";

/// <summary>
/// Removes the BOM (Byte Order Mark) from the beginning of the templates.
/// </summary>
[JsonProperty("noBOM")]
public bool NoBOM { get; set; } = false;

/// <summary>
/// Name of passed string templates.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; } = "";

/// <summary>
/// Template namespace
/// </summary>
[JsonProperty("namespace")]
public string TemplateNameSpace { get; set; } = "";

/// <summary>
/// Compile with known helpers only
/// </summary>
[JsonProperty("knownHelpersOnly")]
public bool KnownHelpersOnly { get; set; } = false;


/// <summary>
/// Forcing a partial template compilation
/// </summary>
[JsonProperty("forcePartial")]
public bool ForcePartial { get; set; } = false;

/// <summary>
/// List of known helpers for a more optimized output
/// </summary>
[JsonProperty("knownHelpers")]
public string[] KnownHelpers { get; set; } = new string[0];

/// <summary>
/// Path to the Handlebars module to export CommonJS style
/// </summary>
[JsonProperty("commonjs")]
public string CommonJS { get; set; } = "";

/// <summary>
/// Exports amd style (require.js), this option has priority to commonjs.
/// </summary>
[JsonProperty("amd")]
public bool AMD { get; set; } = false;
}
}
1 change: 1 addition & 0 deletions src/WebCompiler/Config/ConfigHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public void CreateDefaultsFile(string fileName)
stylus = new StylusOptions(),
babel = new BabelOptions(),
coffeescript = new IcedCoffeeScriptOptions(),
handlebars = new HandlebarsOptions(),
},
minifiers = new
{
Expand Down
4 changes: 4 additions & 0 deletions src/WebCompiler/Dependencies/DependencyService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ private static DependencyType GetDependencyType(string sourceFile)
case ".ICED":
return DependencyType.None;

case ".HBS":
case ".HANDLEBARS":
return DependencyType.None;

case ".JS":
case ".JSX":
case ".ES6":
Expand Down
2 changes: 2 additions & 0 deletions src/WebCompiler/WebCompiler.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@
<Compile Include="Compile\IcedCoffeeScriptCompiler.cs" />
<Compile Include="Compile\CompilerService.cs" />
<Compile Include="Compile\ICompiler.cs" />
<Compile Include="Compile\HandlebarsCompiler.cs" />
<Compile Include="Compile\HandlebarsOptions.cs" />
<Compile Include="Compile\StylusCompiler.cs" />
<Compile Include="Compile\BabelOptions.cs" />
<Compile Include="Compile\StylusOptions.cs" />
Expand Down
Loading

0 comments on commit dc1f553

Please sign in to comment.