Skip to content

Instantly share code, notes, and snippets.

@happygrizzly
Forked from Crisfole/CoffeeCompiler.cs
Created December 22, 2015 10:03
Show Gist options
  • Save happygrizzly/ccc2f751dd90f617e166 to your computer and use it in GitHub Desktop.
Save happygrizzly/ccc2f751dd90f617e166 to your computer and use it in GitHub Desktop.
CoffeeScript and Less
using System;
using System.Collections.Generic;
using System.IO;
using MsieJavaScriptEngine;
public class CoffeeScriptCompiler : ContentCompiler {
public CoffeeScriptCompiler() {
Javascript.Engine.RegisterCompiler("CoffeeScript", LoadCoffeeScript, MakeCompiler);
}
private static void LoadCoffeeScript(MsieJsEngine engine) {
engine.Execute(Properties.Resources.Coffeescript);
}
private static Func<string, string> MakeCompiler(MsieJsEngine engine) {
return input => {
engine.SetVariableValue("coffeeInput", input);
engine.Execute(@"try {
coffeeOutput = CoffeeScript.compile(coffeeInput);
}
catch (_error) {
error = _error;
}");
if (engine.HasVariable("error")) {
var startLine = engine.Evaluate<int>("error.location.first_line") + 1;
var endLine = engine.Evaluate<int>("error.location.last_line") + 1;
var startColumn = engine.Evaluate<int>("error.location.first_column") + 1;
var endColumn = engine.Evaluate<int>("error.location.last_column") + 1;
var stack = engine.Evaluate<string>("error.stack");
throw new ResourceCompilerError(startLine, endLine, startColumn, endColumn, stack);
}
return engine.GetVariableValue<string>("coffeeOutput");
};
}
public override string[] InputExtensions {
get { return new[] {"coffee"}; }
}
public override string[] OutputExtensions {
get { return new[] {"js"}; }
}
public override IEnumerable<string> Dependencies(string input) {
yield return input;
}
public override string GetSourceFilename(string virtualPath) {
var toReturn = Path.ChangeExtension(virtualPath, "coffee");
return !File.Exists(toReturn) ? null : toReturn;
}
public override void Compile(string sourceFile, string destFile, bool throwErrors = false) {
string result;
try {
result = Javascript.Engine.Compile("CoffeeScript", File.ReadAllText(sourceFile));
}
catch (Exception error) {
result = error.Message;
if (throwErrors) {
throw;
}
}
File.WriteAllText(destFile, result);
}
public override string GetCacheFilename(string virtualPath) {
return Path.ChangeExtension(virtualPath, ".js");
}
}
public abstract class ContentCompiler {
public abstract string GetSourceFilename(string virtualPath);
public abstract void Compile(string sourceFile, string destFile, bool throwErrors = false);
public abstract string GetCacheFilename(string virtualPath);
public bool NeedsRecompilation(string input, string output) {
var lastCompiled = File.GetLastWriteTimeUtc(output);
return !File.Exists(output) || Dependencies(input).Any(dep => File.GetLastWriteTimeUtc(dep) > lastCompiled);
}
public abstract string[] InputExtensions { get; }
public abstract string[] OutputExtensions { get; }
public abstract IEnumerable<string> Dependencies(string input);
public virtual bool CanHandle(string requestedPath) {
return !this.OutputExtensions.Any() || this.OutputExtensions.Any(requestedPath.EndsWith);
}
}
using System;
using System.Collections.Generic;
using MsieJavaScriptEngine;
internal class Javascript {
private readonly MsieJsEngine _engine = new MsieJsEngine();
private readonly Dictionary<string, object> _locks = new Dictionary<string, object>();
private readonly Dictionary<string, Func<string, string>> _compilers = new Dictionary<string, Func<string, string>>();
private Javascript() { }
public static readonly Javascript Engine = new Javascript();
internal void RegisterCompiler(string name, Action<MsieJsEngine> setup,
Func<MsieJsEngine, Func<string, string>> makeCompileFunc) {
if (this._locks.ContainsKey(name)) {
return;
}
this._locks[name] = new object();
setup(this._engine);
this._compilers[name] = makeCompileFunc(this._engine);
}
internal string Compile(string compiler, string source) {
lock (this._locks[compiler]) {
return this._compilers[compiler](source);
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using dotless.Core;
using dotless.Core.configuration;
using dotless.Core.Loggers;
using Nancy;
public class LessCompiler : ContentCompiler
{
public override string[] OutputExtensions {
get { return new[] { "css" }; }
}
public override IEnumerable<string> Dependencies(string input) {
var deps = new List<string> {
input,
};
using ((Pushd) Path.GetDirectoryName(input)) {
var engine = LessEngine;
engine.TransformToCss(input);
deps.AddRange(engine.GetImports().Select(Path.GetFullPath));
}
return deps;
}
public override string GetSourceFilename(string virtualPath) {
var toReturn = Path.ChangeExtension(virtualPath, "less");
return !File.Exists(toReturn) ? null : toReturn;
}
public override string GetCacheFilename(string virtualPath) {
return Path.ChangeExtension(virtualPath, "css");
}
public override string[] InputExtensions
{
get { return new[] {"less"}; }
}
public override void Compile(string sourceFilename, string destFilename, bool throwErrors = false) {
using ((Pushd)Path.GetDirectoryName(sourceFilename)) {
var engine = LessEngine;
var css = engine.TransformToCss(sourceFilename);
if (engine.LastTransformationSuccessful) {
File.WriteAllText(destFilename, css);
}
else if (throwErrors) {
var lines = engine.LogContents.Split('\n').Skip(1).ToList();
var details = Regex.Split(lines.First(), @" on line | in file ");
var msg = details.First();
var lineNo = int.Parse(details.Skip(1).First());
var colNo = lines.First(l => l.StartsWith(" ") && l.Contains("^")).Count(c => c == '-') + 1;
throw new ResourceCompilerError(lineNo, lineNo, colNo, colNo, msg);
}
else if (StaticConfiguration.IsRunningDebug) {
File.WriteAllText(destFilename, engine.LogContents);
}
}
}
private static LoggingLessEngine LessEngine {
get {
var conf = DotlessConfiguration.GetDefault();
conf.KeepFirstSpecialComment = true;
conf.Debug = StaticConfiguration.IsRunningDebug;
conf.MinifyOutput = !StaticConfiguration.IsRunningDebug;
conf.InlineCssFiles = true;
conf.Logger = typeof (StringLogger);
return new LoggingLessEngine(new EngineFactory(conf).GetEngine());
}
}
private class StringLogger : Logger {
private readonly List<string> messages = new List<string>();
public StringLogger(LogLevel level) : base(level) {
}
protected override void Log(string message) {
this.messages.Add(message);
}
public string LogContents {
get { return string.Join("\n", this.messages); }
}
}
// Directly from https://github.com/dotless/dotless/blob/master/src/dotless.Compiler/FixImportPathDecorator.cs
// Renamed to better reflect its purpose
private class LoggingLessEngine : ILessEngine {
public LoggingLessEngine(ILessEngine underlying) {
this.Underlying = underlying;
}
public string TransformToCss(string source, string fileName) {
return this.Underlying.TransformToCss(source, fileName);
}
public string TransformToCss(string inFile) {
return TransformToCss(File.ReadAllText(inFile), inFile);
}
public void ResetImports() {
this.Underlying.ResetImports();
}
public IEnumerable<string> GetImports() {
return this.Underlying.GetImports().Select(import => import.Replace("/", "\\"));
}
public bool LastTransformationSuccessful {
get { return this.Underlying.LastTransformationSuccessful; }
}
public string LogContents {
get { return this.Logger.LogContents; }
}
private StringLogger Logger {
get {
var parameterDecorator = this.Underlying as ParameterDecorator;
if (parameterDecorator != null) {
var cacheDecorator = parameterDecorator.Underlying as CacheDecorator;
if (cacheDecorator != null) {
return cacheDecorator.Logger as StringLogger;
}
}
return null;
}
}
public ILessEngine Underlying { get; private set; }
}
private class Pushd : IDisposable {
private readonly string _currDir;
private Pushd(string dir) {
this._currDir = Directory.GetCurrentDirectory();
Directory.SetCurrentDirectory(dir);
}
public static implicit operator Pushd(string d) {
return new Pushd(d);
}
public void Dispose() {
Directory.SetCurrentDirectory(this._currDir);
}
}
}
using System;
public class ResourceCompilerError : Exception {
public ResourceCompilerError(int startLine, int endLine, int startColumn, int endColumn, string details) {
this.StartLine = startLine;
this.EndLine = endLine;
this.StartColumn = startColumn;
this.EndColumn = endColumn;
this.Details = details;
this.LineNumbers = true;
}
public ResourceCompilerError(string details) {
Details = details;
this.LineNumbers = false;
}
public bool LineNumbers { get; set; }
public string Details { get; set; }
public int StartLine { get; set; }
public int EndLine { get; set; }
public int StartColumn { get; set; }
public int EndColumn { get; set; }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment