diff --git a/SassAndCoffee.AspNet/CompilableFileHandler.cs b/SassAndCoffee.AspNet/CompilableFileHandler.cs
index 677eb04..164439d 100644
--- a/SassAndCoffee.AspNet/CompilableFileHandler.cs
+++ b/SassAndCoffee.AspNet/CompilableFileHandler.cs
@@ -3,7 +3,6 @@
namespace SassAndCoffee.AspNet
{
using System;
- using System.IO;
using System.Web;
using SassAndCoffee.Core;
@@ -25,16 +24,8 @@ public bool IsReusable {
public void ProcessRequest(HttpContext context)
{
- var fi = new FileInfo(context.Request.PhysicalPath);
- var requestedFileName = fi.FullName;
-
- if (fi.Exists) {
- BuildHeaders(context.Response, _contentCompiler.GetOutputMimeType(requestedFileName), fi.LastWriteTimeUtc);
- context.Response.WriteFile(requestedFileName);
- return;
- }
-
- var compilationResult = _contentCompiler.GetCompiledContent(context.Request.Path);
+ VirtualPathCompilerFile file = new VirtualPathCompilerFile(context.Request.Path);
+ var compilationResult = _contentCompiler.GetCompiledContent(file);
if (compilationResult.Compiled == false) {
context.Response.StatusCode = 404;
return;
@@ -49,7 +40,7 @@ static void BuildHeaders(HttpResponse response, string mimeType, DateTime lastMo
response.StatusCode = 200;
response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
response.AddHeader("content-encoding", "gzip");
- response.Cache.VaryByHeaders["Accept-encoding"] = true;
+ response.Cache.VaryByHeaders["Accept-Encoding"] = true;
response.AddHeader("ETag", lastModified.Ticks.ToString("x"));
response.AddHeader("Content-Type", mimeType);
response.AddHeader("Content-Disposition", "inline");
diff --git a/SassAndCoffee.AspNet/CompilableFileModule.cs b/SassAndCoffee.AspNet/CompilableFileModule.cs
index 0f7cba9..8164b6b 100644
--- a/SassAndCoffee.AspNet/CompilableFileModule.cs
+++ b/SassAndCoffee.AspNet/CompilableFileModule.cs
@@ -9,7 +9,7 @@ namespace SassAndCoffee.AspNet
using SassAndCoffee.Core.Caching;
using System.Configuration;
- public class CompilableFileModule : IHttpModule, ICompilerHost
+ public class CompilableFileModule : IHttpModule
{
IContentCompiler _compiler;
IHttpHandler _handler;
@@ -29,7 +29,7 @@ public void Init(HttpApplication context)
_compiler = _compiler ?? initializeCompilerFromSettings(cacheType);
_handler = _handler ?? new CompilableFileHandler(_compiler);
- if (!_compiler.CanCompile(app.Request.Path)) {
+ if (!_compiler.CanCompile(new VirtualPathCompilerFile(app.Request.Path))) {
return;
}
@@ -42,23 +42,19 @@ public string MapPath(string path)
return HttpContext.Current.Server.MapPath(path);
}
- IContentCompiler initializeCompilerFromSettings(string cacheType)
- {
+ IContentCompiler initializeCompilerFromSettings(string cacheType) {
if (string.Equals(cacheType, "NoCache", System.StringComparison.InvariantCultureIgnoreCase)) {
// NoCache
- return new ContentCompiler(this, new NoCache());
- } else if (string.Equals(cacheType, "InMemoryCache", System.StringComparison.InvariantCultureIgnoreCase)) {
+ return new ContentCompiler(new NoCache());
+ }
+ if (string.Equals(cacheType, "InMemoryCache", System.StringComparison.InvariantCultureIgnoreCase)) {
// InMemoryCache
- return new ContentCompiler(this, new InMemoryCache());
- } else {
- // FileCache
- var cachePath = Path.Combine(HostingEnvironment.MapPath("~/App_Data"), "_FileCache");
- if (!Directory.Exists(cachePath)) {
- Directory.CreateDirectory(cachePath);
- }
-
- return new ContentCompiler(this, new FileCache(cachePath));
+ return new ContentCompiler(new InMemoryCache());
}
+ // FileCache
+ var cachePath = Path.Combine(HostingEnvironment.MapPath("~/App_Data"), "_FileCache");
+ Directory.CreateDirectory(cachePath);
+ return new ContentCompiler(new FileCache(cachePath));
}
public void Dispose()
diff --git a/SassAndCoffee.AspNet/SassAndCoffee.AspNet.csproj b/SassAndCoffee.AspNet/SassAndCoffee.AspNet.csproj
index a41210c..8c5bf3d 100644
--- a/SassAndCoffee.AspNet/SassAndCoffee.AspNet.csproj
+++ b/SassAndCoffee.AspNet/SassAndCoffee.AspNet.csproj
@@ -45,6 +45,7 @@
+
diff --git a/SassAndCoffee.AspNet/VirtualPathCompilerFile.cs b/SassAndCoffee.AspNet/VirtualPathCompilerFile.cs
new file mode 100644
index 0000000..726b3e2
--- /dev/null
+++ b/SassAndCoffee.AspNet/VirtualPathCompilerFile.cs
@@ -0,0 +1,58 @@
+using System;
+using System.IO;
+using System.Web.Hosting;
+
+using SassAndCoffee.Core;
+
+namespace SassAndCoffee.AspNet {
+ internal class VirtualPathCompilerFile: ICompilerFile {
+ private static readonly DateTime unknownFileTime = DateTime.UtcNow;
+
+ private readonly string virtualPath;
+
+ public VirtualPathCompilerFile(string virtualPath) {
+ if (string.IsNullOrEmpty(virtualPath)) {
+ throw new ArgumentNullException("virtualPath");
+ }
+ this.virtualPath = virtualPath;
+ }
+
+ public DateTime LastWriteTimeUtc {
+ get {
+ if (!Exists) {
+ return default(DateTime);
+ }
+ using (Stream stream = Open()) {
+ FileStream file = stream as FileStream;
+ if (file != null) {
+ return File.GetLastWriteTimeUtc(file.Name);
+ }
+ }
+ // if the stream is not a file stream, we cannot determine the last write time and take the app startup time instead to avoid caching issues
+ return unknownFileTime;
+ }
+ }
+
+ public Stream Open()
+ {
+ return HostingEnvironment.VirtualPathProvider.GetFile(virtualPath).Open();
+ }
+
+ public string Name {
+ get {
+ return virtualPath;
+ }
+ }
+
+ public bool Exists {
+ get {
+ return HostingEnvironment.VirtualPathProvider.FileExists(virtualPath);
+ }
+ }
+
+ public ICompilerFile GetRelativeFile(string relativePath)
+ {
+ return new VirtualPathCompilerFile(HostingEnvironment.VirtualPathProvider.CombineVirtualPaths(virtualPath, relativePath.Replace('\\', '/')));
+ }
+ }
+}
diff --git a/SassAndCoffee.Core.Tests/SassAndCoffee.Core.Tests.csproj b/SassAndCoffee.Core.Tests/SassAndCoffee.Core.Tests.csproj
index 0c575a0..556b956 100644
--- a/SassAndCoffee.Core.Tests/SassAndCoffee.Core.Tests.csproj
+++ b/SassAndCoffee.Core.Tests/SassAndCoffee.Core.Tests.csproj
@@ -109,6 +109,7 @@
+
diff --git a/SassAndCoffee.Core.Tests/SassFileCompilerTest.cs b/SassAndCoffee.Core.Tests/SassFileCompilerTest.cs
index 6875ca2..dae61bd 100644
--- a/SassAndCoffee.Core.Tests/SassFileCompilerTest.cs
+++ b/SassAndCoffee.Core.Tests/SassFileCompilerTest.cs
@@ -31,15 +31,8 @@ string compileInput(string filename, string input)
{
var fixture = new SassFileCompiler();
- using(var of = File.CreateText(filename)) {
- of.WriteLine(input);
- }
-
try {
-
- // TODO: Fix this
- // fixture.Init(TODO);
- string result = fixture.ProcessFileContent(filename);
+ string result = fixture.ProcessFileContent(new TestCompilerFile(filename, input));
Console.WriteLine(result);
return result;
} finally {
diff --git a/SassAndCoffee.Core.Tests/TestCompilerFile.cs b/SassAndCoffee.Core.Tests/TestCompilerFile.cs
new file mode 100644
index 0000000..85794ac
--- /dev/null
+++ b/SassAndCoffee.Core.Tests/TestCompilerFile.cs
@@ -0,0 +1,59 @@
+using System;
+using System.IO;
+
+namespace SassAndCoffee.Core.Tests {
+ internal class TestCompilerFile: ICompilerFile {
+ private readonly string _fileName;
+ private readonly string _content;
+ private readonly DateTime _lastWriteTimeUtc;
+
+ public TestCompilerFile(string fileName, string content) {
+ this._fileName = fileName;
+ this._content = content;
+ _lastWriteTimeUtc = DateTime.UtcNow;
+ }
+
+ public DateTime LastWriteTimeUtc {
+ get {
+ AssertFileExists();
+ return _lastWriteTimeUtc;
+ }
+ }
+
+ public Stream Open()
+ {
+ AssertFileExists();
+ MemoryStream stream = new MemoryStream();
+ StreamWriter writer = new StreamWriter(stream);
+ writer.Write(_content);
+ writer.Flush();
+ stream.Seek(0, SeekOrigin.Begin);
+ return stream;
+ }
+
+ private void AssertFileExists()
+ {
+ if (!Exists) {
+ throw new FileNotFoundException();
+ }
+ }
+
+ public string Name {
+ get {
+ return _fileName;
+ }
+ }
+
+ public bool Exists {
+ get {
+ return _content != null;
+ }
+ }
+
+ public ICompilerFile GetRelativeFile(string relativePath)
+ {
+ // not really canonicalizing the real path, but that's not needed here
+ return new TestCompilerFile(relativePath, null);
+ }
+ }
+}
diff --git a/SassAndCoffee.Core/Caching/FileCache.cs b/SassAndCoffee.Core/Caching/FileCache.cs
index 0e562c6..74bfb99 100644
--- a/SassAndCoffee.Core/Caching/FileCache.cs
+++ b/SassAndCoffee.Core/Caching/FileCache.cs
@@ -19,10 +19,9 @@ public FileCache(string basePath)
public CompilationResult GetOrAdd(string filename, Func compilationDelegate, string mimeType)
{
var outputFileName = Path.Combine(_basePath, filename);
- FileInfo fi;
+ FileInfo fi = new FileInfo(outputFileName);
- if (File.Exists(outputFileName)) {
- fi = new FileInfo(outputFileName);
+ if (fi.Exists) {
return new CompilationResult(true, File.ReadAllText(outputFileName), mimeType, fi.LastWriteTimeUtc);
}
@@ -32,7 +31,7 @@ public CompilationResult GetOrAdd(string filename, Func _engine;
- public string[] InputFileExtensions {
- get { return new[] { ".coffee" }; }
+ public IEnumerable InputFileExtensions {
+ get { yield return ".coffee"; }
}
public string OutputFileExtension {
@@ -28,20 +31,15 @@ public string OutputMimeType {
public CoffeeScriptFileCompiler(CoffeeScriptCompiler engine = null)
{
- _engine = engine;
- }
-
- public void Init(ICompilerHost host)
- {
- _engine = _engine ?? new CoffeeScriptCompiler();
+ _engine = new Lazy(() => engine ?? new CoffeeScriptCompiler());
}
- public string ProcessFileContent(string inputFileContent)
+ public string ProcessFileContent(ICompilerFile inputFileContent)
{
- return _engine.Compile(File.ReadAllText(inputFileContent));
+ return _engine.Value.Compile(inputFileContent.ReadAllText());
}
- public string GetFileChangeToken(string inputFileContent)
+ public string GetFileChangeToken(ICompilerFile inputFileContent)
{
return "";
}
diff --git a/SassAndCoffee.Core/Compilers/FileConcatenationCompiler.cs b/SassAndCoffee.Core/Compilers/FileConcatenationCompiler.cs
index f1ef85c..045173c 100644
--- a/SassAndCoffee.Core/Compilers/FileConcatenationCompiler.cs
+++ b/SassAndCoffee.Core/Compilers/FileConcatenationCompiler.cs
@@ -1,3 +1,5 @@
+using SassAndCoffee.Core.Extensions;
+
namespace SassAndCoffee.Core.Compilers
{
using System;
@@ -10,11 +12,12 @@ namespace SassAndCoffee.Core.Compilers
public class FileConcatenationCompiler : ISimpleFileCompiler
{
- ICompilerHost _host;
- IContentCompiler _compiler;
+ private static readonly Regex _lineRegex = new Regex(@"(?<=^\s*)(?!=\#|\s)((?!\.combined\s*$).)+?(?=\s*$)", RegexOptions.Compiled|RegexOptions.CultureInvariant|RegexOptions.ExplicitCapture|RegexOptions.IgnoreCase|RegexOptions.Singleline);
+
+ private readonly IContentCompiler _compiler;
- public string[] InputFileExtensions {
- get { return new[] { ".combine" }; }
+ public IEnumerable InputFileExtensions {
+ get { yield return ".combine"; }
}
public string OutputFileExtension {
@@ -25,62 +28,52 @@ public string OutputMimeType {
get { return "text/javascript"; }
}
- static readonly Regex _commentRegex = new Regex("#.*$", RegexOptions.Compiled);
-
- public FileConcatenationCompiler(IContentCompiler compiler)
+ public FileConcatenationCompiler(IContentCompiler compiler)
{
+ if (compiler == null) {
+ throw new ArgumentNullException("compiler");
+ }
_compiler = compiler;
}
- public void Init(ICompilerHost host)
- {
- _host = host;
- }
-
- public string ProcessFileContent(string inputFileContent)
+ public string ProcessFileContent(ICompilerFile inputFileContent)
{
- var combineFileNames = this.GetCombineFileNames(inputFileContent);
-
- var allText = combineFileNames
- .Select( x => _compiler.CanCompile(x) ?
- _compiler.GetCompiledContent(x).Contents : String.Empty)
- .ToArray();
-
+ IEnumerable combineFileNames = GetCombineFileNames(inputFileContent);
+ string[] allText = combineFileNames
+ .Select(x => _compiler.CanCompile(x)
+ ? _compiler.GetCompiledContent(x).Contents
+ : String.Empty)
+ .ToArray();
return allText.Aggregate(new StringBuilder(), (acc, x) => {
- acc.Append(x);
- acc.Append("\n");
- return acc;
- }).ToString();
+ acc.Append(x);
+ acc.Append("\n");
+ return acc;
+ }).ToString();
}
- public string GetFileChangeToken(string inputFileContent)
+ public string GetFileChangeToken(ICompilerFile inputFileContent)
{
- var md5sum = MD5.Create();
-
- var ms = this.GetCombineFileNames(inputFileContent)
- .Select(x => _compiler.GetSourceFileNameFromRequestedFileName(x))
- .Select(x => new FileInfo(x))
- .Where(x => x.Exists)
- .Select(x => x.LastWriteTimeUtc.Ticks)
- .Aggregate(new MemoryStream(), (acc, x) => {
- var buf = BitConverter.GetBytes(x);
- acc.Write(buf, 0, buf.Length);
- return acc;
- });
-
+ MD5 md5sum = MD5.Create();
+ MemoryStream ms = GetCombineFileNames(inputFileContent)
+ .Select(x => _compiler.GetSourceFileNameFromRequestedFileName(x))
+ .Where(x => (x != null) && x.Exists)
+ .Select(x => x.LastWriteTimeUtc.Ticks)
+ .Aggregate(new MemoryStream(), (acc, x) => {
+ byte[] buf = BitConverter.GetBytes(x);
+ acc.Write(buf, 0, buf.Length);
+ return acc;
+ });
return md5sum.ComputeHash(ms.GetBuffer()).Aggregate(new StringBuilder(), (acc, x) => {
- acc.Append(x.ToString("x"));
- return acc;
- }).ToString();
+ acc.Append(x.ToString("x"));
+ return acc;
+ }).ToString();
}
- IEnumerable GetCombineFileNames(string inputFileContent)
- {
- return File.ReadAllLines(inputFileContent)
- .Select(x => _commentRegex.Replace(x, String.Empty))
- .Where(x => !String.IsNullOrWhiteSpace(x))
- .Where(x => !x.ToLowerInvariant().EndsWith(".combine"))
- .ToArray();
+ private IEnumerable GetCombineFileNames(ICompilerFile inputFileContent) {
+ return inputFileContent.ReadLines()
+ .Select(l => _lineRegex.Match(l))
+ .Where(m => m.Success)
+ .Select(m => inputFileContent.GetRelativeFile(m.Value));
}
}
}
diff --git a/SassAndCoffee.Core/Compilers/ISimpleFileCompiler.cs b/SassAndCoffee.Core/Compilers/ISimpleFileCompiler.cs
index cf67d13..b5b9626 100644
--- a/SassAndCoffee.Core/Compilers/ISimpleFileCompiler.cs
+++ b/SassAndCoffee.Core/Compilers/ISimpleFileCompiler.cs
@@ -1,14 +1,16 @@
+using System.Collections.Generic;
+using System.IO;
+
namespace SassAndCoffee.Core.Compilers
{
// TODO: Document me
public interface ISimpleFileCompiler
{
- string[] InputFileExtensions { get; }
+ IEnumerable InputFileExtensions { get; }
string OutputFileExtension { get; }
string OutputMimeType { get; }
- void Init(ICompilerHost host);
- string ProcessFileContent(string inputFileContent);
- string GetFileChangeToken(string inputFileContent);
+ string ProcessFileContent(ICompilerFile inputFileContent);
+ string GetFileChangeToken(ICompilerFile inputFileContent);
}
}
diff --git a/SassAndCoffee.Core/Compilers/JavascriptPassthroughCompiler.cs b/SassAndCoffee.Core/Compilers/JavascriptPassthroughCompiler.cs
index f3b7ff8..00a98cb 100644
--- a/SassAndCoffee.Core/Compilers/JavascriptPassthroughCompiler.cs
+++ b/SassAndCoffee.Core/Compilers/JavascriptPassthroughCompiler.cs
@@ -1,3 +1,7 @@
+using System.Collections.Generic;
+
+using SassAndCoffee.Core.Extensions;
+
namespace SassAndCoffee.Core.Compilers
{
using System.IO;
@@ -5,8 +9,10 @@ namespace SassAndCoffee.Core.Compilers
// TODO: Document why this exists
public class JavascriptPassthroughCompiler : ISimpleFileCompiler
{
- public string[] InputFileExtensions {
- get { return new[] {".js"}; }
+ public IEnumerable InputFileExtensions {
+ get {
+ yield return ".js";
+ }
}
public string OutputFileExtension {
@@ -17,16 +23,12 @@ public string OutputMimeType {
get { return "text/javascript"; }
}
- public void Init(ICompilerHost host)
- {
- }
-
- public string ProcessFileContent(string inputFileContent)
+ public string ProcessFileContent(ICompilerFile inputFileContent)
{
- return File.ReadAllText(inputFileContent);
+ return inputFileContent.ReadAllText();
}
- public string GetFileChangeToken(string inputFileContent)
+ public string GetFileChangeToken(ICompilerFile inputFileContent)
{
return "";
}
diff --git a/SassAndCoffee.Core/Compilers/MinifyingCompiler.cs b/SassAndCoffee.Core/Compilers/MinifyingCompiler.cs
index 72c15f4..3fd7895 100644
--- a/SassAndCoffee.Core/Compilers/MinifyingCompiler.cs
+++ b/SassAndCoffee.Core/Compilers/MinifyingCompiler.cs
@@ -1,4 +1,9 @@
-namespace SassAndCoffee.Core.Compilers
+using System;
+using System.Collections.Generic;
+
+using SassAndCoffee.Core.Extensions;
+
+namespace SassAndCoffee.Core.Compilers
{
using System.IO;
@@ -12,8 +17,11 @@ public class MinifyingFileCompiler : ISimpleFileCompiler
TrashStack _coffeeEngine;
TrashStack _engine;
- public string[] InputFileExtensions {
- get { return new[] {".js", ".coffee"}; }
+ public IEnumerable InputFileExtensions {
+ get {
+ yield return ".js";
+ yield return ".coffee";
+ }
}
public string OutputFileExtension {
@@ -30,29 +38,22 @@ public MinifyingFileCompiler()
_engine = new TrashStack(() => new MinifyingCompiler());
}
- public void Init(ICompilerHost host)
- {
- }
-
- public string ProcessFileContent(string inputFileContent)
+ public string ProcessFileContent(ICompilerFile inputFileContent)
{
- string text = File.ReadAllText(inputFileContent);
-
- if (inputFileContent.ToLowerInvariant().EndsWith(".coffee")) {
- using(var coffeeEngine = _coffeeEngine.Get()) {
- text = coffeeEngine.Value.Compile(text);
- }
- }
-
- string ret;
- using (var engine = _engine.Get()) {
- ret = engine.Value.Compile(text);
- }
-
- return ret;
+ string text = inputFileContent.ReadAllText();
+ if (inputFileContent.Name.EndsWith(".coffee", StringComparison.OrdinalIgnoreCase)) {
+ using (ValueContainer coffeeEngine = _coffeeEngine.Get()) {
+ text = coffeeEngine.Value.Compile(text);
+ }
+ }
+ string ret;
+ using (ValueContainer engine = _engine.Get()) {
+ ret = engine.Value.Compile(text);
+ }
+ return ret;
}
- public string GetFileChangeToken(string inputFileContent)
+ public string GetFileChangeToken(ICompilerFile inputFileContent)
{
return "";
}
diff --git a/SassAndCoffee.Core/Compilers/SassFileCompiler.cs b/SassAndCoffee.Core/Compilers/SassFileCompiler.cs
index e23c8f3..c9af550 100644
--- a/SassAndCoffee.Core/Compilers/SassFileCompiler.cs
+++ b/SassAndCoffee.Core/Compilers/SassFileCompiler.cs
@@ -1,10 +1,14 @@
-namespace SassAndCoffee.Core.Compilers
+using System.Diagnostics;
+using System.Linq.Expressions;
+
+using SassAndCoffee.Core.Extensions;
+
+namespace SassAndCoffee.Core.Compilers
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
- using System.Reflection;
using IronRuby;
@@ -19,39 +23,45 @@ private class SassModule
public dynamic SassOption { get; set; }
public dynamic ScssOption { get; set; }
public Action ExecuteRubyCode { get; set; }
+ public VirtualFilePAL PlatformAdaptationLayer { get; set; }
}
static TrashStack _sassModule;
internal static string RootAppPath;
- ICompilerHost _compilerHost;
static SassFileCompiler()
{
_sassModule = new TrashStack(() => {
- var srs = new ScriptRuntimeSetup() {HostType = typeof (ResourceAwareScriptHost)};
+ var srs = new ScriptRuntimeSetup() {
+ HostType = typeof (ResourceAwareScriptHost),
+ //DebugMode = Debugger.IsAttached
+ };
srs.AddRubySetup();
var runtime = Ruby.CreateRuntime(srs);
var engine = runtime.GetRubyEngine();
// NB: 'R:\' is a garbage path that the PAL override below will
// detect and attempt to find via an embedded Resource file
- engine.SetSearchPaths(new List() {@"R:\lib\ironruby", @"R:\lib\ruby\1.9.1"});
-
- var source = engine.CreateScriptSourceFromString(Utility.ResourceAsString("SassAndCoffee.Core.lib.sass_in_one.rb"), SourceCodeKind.File);
+ engine.SetSearchPaths(new[] {@"R:/lib/ironruby", @"R:/lib/ruby/1.9.1"});
+
+ var source = engine.CreateScriptSourceFromString(Utility.ResourceAsString("SassAndCoffee.Core.lib.sass_in_one.rb"), "R:/lib/sass_in_one.rb", SourceCodeKind.File);
var scope = engine.CreateScope();
source.Execute(scope);
-
- return new SassModule() {
+ return new SassModule {
+ PlatformAdaptationLayer = (VirtualFilePAL)runtime.Host.PlatformAdaptationLayer,
Engine = scope.Engine.Runtime.Globals.GetVariable("Sass"),
- SassOption = engine.Execute("{:syntax => :sass}"),
- ScssOption = engine.Execute("{:syntax => :scss}"),
- ExecuteRubyCode = code => engine.Execute(code, scope),
+ SassOption = engine.Execute(@"{:syntax => :sass, :cache_location => ""C:/""}"),
+ ScssOption = engine.Execute(@"{:syntax => :scss, :cache_location => ""C:/""}"),
+ ExecuteRubyCode = code => engine.Execute(code, scope)
};
});
}
- public string[] InputFileExtensions {
- get { return new[] {".scss", ".sass"}; }
+ public IEnumerable InputFileExtensions {
+ get {
+ yield return ".scss";
+ yield return ".sass";
+ }
}
public string OutputFileExtension {
@@ -62,30 +72,17 @@ public string OutputMimeType {
get { return "text/css"; }
}
- public void Init(ICompilerHost host)
+ public string ProcessFileContent(ICompilerFile inputFileContent)
{
- _compilerHost = host;
- }
-
- public string ProcessFileContent(string inputFileContent)
- {
- // NB: We do this here instead of in Init like we should, because in
- // ASP.NET trying to get the PhysicalAppPath when a request isn't in-flight
- // is verboten, for no good reason.
- RootAppPath = RootAppPath ?? _compilerHost.ApplicationBasePath;
-
using (var sassModule = _sassModule.Get()) {
- dynamic opt = (inputFileContent.ToLowerInvariant().EndsWith("scss") ? sassModule.Value.ScssOption : sassModule.Value.SassOption);
-
- if (!inputFileContent.Contains('\'')) {
- sassModule.Value.ExecuteRubyCode(String.Format("Dir.chdir '{0}'", Path.GetDirectoryName(inputFileContent)));
+ dynamic opt = (inputFileContent.Name.EndsWith(".scss", StringComparison.OrdinalIgnoreCase) ? sassModule.Value.ScssOption : sassModule.Value.SassOption);
+ using (sassModule.Value.PlatformAdaptationLayer.SetCompilerFile(inputFileContent)) {
+ return (string)sassModule.Value.Engine.compile(inputFileContent.ReadAllText(), opt);
}
-
- return (string) sassModule.Value.Engine.compile(File.ReadAllText(inputFileContent), opt);
}
}
- public string GetFileChangeToken(string inputFileContent)
+ public string GetFileChangeToken(ICompilerFile inputFileContent)
{
return "";
}
@@ -93,54 +90,12 @@ public string GetFileChangeToken(string inputFileContent)
public class ResourceAwareScriptHost : ScriptHost
{
- PlatformAdaptationLayer _innerPal = null;
+ private readonly PlatformAdaptationLayer _innerPal = new VirtualFilePAL();
+
public override PlatformAdaptationLayer PlatformAdaptationLayer {
get {
- if (_innerPal == null) {
- _innerPal = new ResourceAwarePAL();
- }
return _innerPal;
}
}
}
-
- public class ResourceAwarePAL : PlatformAdaptationLayer
- {
- public override Stream OpenInputFileStream(string path)
- {
- var ret = Assembly.GetExecutingAssembly().GetManifestResourceStream(pathToResourceName(path));
- if (ret != null) {
- return ret;
- }
-
- if (SassFileCompiler.RootAppPath == null || !path.ToLowerInvariant().StartsWith(SassFileCompiler.RootAppPath)) {
- return null;
- }
-
- return base.OpenInputFileStream(path);
- }
-
- public override bool FileExists(string path)
- {
- if (Assembly.GetExecutingAssembly().GetManifestResourceInfo(pathToResourceName(path)) != null) {
- return true;
- }
-
- if (path.EndsWith("css")) {
- int a = 1;
- }
-
- return base.FileExists(path);
- }
-
- string pathToResourceName(string path)
- {
- var ret = path
- .Replace("1.9.1", "_1._9._1")
- .Replace('\\', '.')
- .Replace('/', '.')
- .Replace("R:", "SassAndCoffee.Core");
- return ret;
- }
- }
}
diff --git a/SassAndCoffee.Core/Compilers/VirtualFilePAL.cs b/SassAndCoffee.Core/Compilers/VirtualFilePAL.cs
new file mode 100644
index 0000000..1f3aab8
--- /dev/null
+++ b/SassAndCoffee.Core/Compilers/VirtualFilePAL.cs
@@ -0,0 +1,216 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+using Microsoft.Scripting;
+
+namespace SassAndCoffee.Core.Compilers {
+ public class VirtualFilePAL: PlatformAdaptationLayer {
+ private enum PathType {
+ None,
+ Resource,
+ Virtual,
+ Cache
+ }
+
+ private static readonly Regex _filenameHeuristic = new Regex(@"/[^./]+(\.[^.]+)+$", RegexOptions.Compiled|RegexOptions.CultureInvariant|RegexOptions.RightToLeft|RegexOptions.Singleline|RegexOptions.ExplicitCapture);
+ private static readonly Regex _pathCanonizer = new Regex(@"[/\\](\.(?=$|[/\\])|[^/\\]+[/\\]\.\.)", RegexOptions.CultureInvariant|RegexOptions.Compiled|RegexOptions.ExplicitCapture);
+ private static readonly Regex _resourcePathEscaper = new Regex(@"\b(?=\d)|[/\\]", RegexOptions.Compiled|RegexOptions.CultureInvariant|RegexOptions.ExplicitCapture);
+ private static readonly Regex _resourcePathTrimmer = new Regex(@"^.*(?=(\.[^.]+){2})", RegexOptions.Compiled|RegexOptions.CultureInvariant|RegexOptions.ExplicitCapture);
+ private static readonly Regex _pathAbsolute = new Regex(@"^((?[a-z]):)?[/\\]", RegexOptions.Compiled|RegexOptions.IgnoreCase|RegexOptions.CultureInvariant|RegexOptions.ExplicitCapture);
+// private static readonly Regex _pathAnalyzer = new Regex(@"^(?(?([a-z]:)?[/\\])([^/\\]+[/\\])*)(?[^/\\]+)$", RegexOptions.Compiled|RegexOptions.CultureInvariant|RegexOptions.IgnoreCase|RegexOptions.ExplicitCapture);
+ private static readonly ICollection _resourceDirectories = new HashSet(typeof(VirtualFilePAL).Assembly.GetManifestResourceNames().Select(s => _resourcePathTrimmer.Match(s).Value));
+
+/* internal static string JoinPaths(string basePath, string relativePath) {
+ Match relativeMatch = _pathAnalyzer.Match(relativePath);
+ if (relativeMatch.Groups["absolute"].Success) {
+ return CanonizePath(relativePath);
+ }
+ return CanonizePath(_pathAnalyzer.Match(basePath).Groups["path"].Value+relativePath);
+ } */
+
+ internal static string CanonizePath(string path) {
+ return _pathCanonizer.Replace(path, "");
+ }
+
+ private readonly string _cachePath;
+ private ICompilerFile _compilerFile;
+ private string _currentDirectory;
+
+ public VirtualFilePAL() {
+ _cachePath = Path.Combine(Path.GetTempPath(), "sass_cache", Guid.NewGuid().ToString("N"));
+ }
+
+ public override string CurrentDirectory {
+ get {
+ return _currentDirectory ?? @"C:\";
+ }
+ set {
+ _currentDirectory = GetFullPath(value);
+ }
+ }
+
+ internal static string GetDriveInternal(string path) {
+ return _pathAbsolute.Match(path).Groups["drive"].Value;
+ }
+
+ public override void CreateDirectory(string path) {
+ switch (ResolvePath(ref path)) {
+ case PathType.Cache:
+ base.CreateDirectory(path);
+ break;
+ default:
+ throw new NotSupportedException();
+ }
+ }
+
+ public override void DeleteDirectory(string path, bool recursive) {
+ switch (ResolvePath(ref path)) {
+ case PathType.Cache:
+ base.DeleteDirectory(path, recursive);
+ break;
+ default:
+ throw new NotSupportedException();
+ }
+ }
+
+ public override void DeleteFile(string path, bool deleteReadOnly) {
+ switch (ResolvePath(ref path)) {
+ case PathType.Cache:
+ base.DeleteFile(path, deleteReadOnly);
+ break;
+ default:
+ throw new NotSupportedException();
+ }
+ }
+
+ public override bool DirectoryExists(string path) {
+ switch (ResolvePath(ref path)) {
+ case PathType.Resource:
+ return _resourceDirectories.Contains(path);
+ case PathType.Virtual:
+ return !(_filenameHeuristic.IsMatch(path) || _compilerFile.GetRelativeFile(path).Exists);
+ case PathType.Cache:
+ return base.DirectoryExists(path);
+ default:
+ throw new NotSupportedException();
+ }
+ }
+
+ public override bool FileExists(string path) {
+ switch (ResolvePath(ref path)) {
+ case PathType.Resource:
+ using (Stream stream = typeof(VirtualFilePAL).Assembly.GetManifestResourceStream(path)) {
+ return stream != null;
+ }
+ case PathType.Virtual:
+ return _compilerFile.GetRelativeFile(path).Exists;
+ case PathType.Cache:
+ return base.FileExists(path);
+ default:
+ throw new NotSupportedException();
+ }
+ }
+
+ public override string[] GetFileSystemEntries(string path, string searchPattern, bool includeFiles, bool includeDirectories) {
+ switch (ResolvePath(ref path)) {
+ case PathType.Cache:
+ return base.GetFileSystemEntries(path, searchPattern, includeFiles, includeDirectories);
+ default:
+ throw new NotSupportedException();
+ }
+ }
+
+ public override string GetFullPath(string path) {
+ Match match = _pathAbsolute.Match(path);
+ if (match.Success) {
+ if (!match.Groups["drive"].Success) {
+ path = GetDriveInternal(path)+':'+path;
+ }
+ } else {
+ path = CombinePaths(CurrentDirectory, path);
+ }
+ return CanonizePath(path);
+ }
+
+ public override void MoveFileSystemEntry(string sourcePath, string destinationPath) {
+ if ((ResolvePath(ref sourcePath) == PathType.Cache) && (ResolvePath(ref destinationPath) == PathType.Cache)) {
+ base.MoveFileSystemEntry(sourcePath, destinationPath);
+ }
+ throw new NotSupportedException();
+ }
+
+ public override Stream OpenInputFileStream(string path) {
+ return OpenInputFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+ }
+
+ public override Stream OpenInputFileStream(string path, FileMode mode, FileAccess access, FileShare share) {
+ bool readOnly = (mode == FileMode.Open) && (access == FileAccess.Read);
+ switch (ResolvePath(ref path)) {
+ case PathType.Resource:
+ if (readOnly) {
+ return typeof(VirtualFilePAL).Assembly.GetManifestResourceStream(path);
+ }
+ break;
+ case PathType.Virtual:
+ if (readOnly) {
+ return _compilerFile.GetRelativeFile(path).Open();
+ }
+ break;
+ case PathType.Cache:
+ return File.Open(Path.Combine(_cachePath, path), mode, access, share);
+ }
+ throw new NotSupportedException();
+ }
+
+ public override Stream OpenInputFileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize) {
+ return OpenInputFileStream(path, mode, access, share);
+ }
+
+ public override Stream OpenOutputFileStream(string path) {
+ switch (ResolvePath(ref path)) {
+ case PathType.Cache:
+ return base.OpenOutputFileStream(path);
+ default:
+ throw new NotSupportedException();
+ }
+ }
+
+ internal IDisposable SetCompilerFile(ICompilerFile file) {
+ if (file == null) {
+ throw new ArgumentNullException("file");
+ }
+ Debug.Assert(_compilerFile == null);
+ _compilerFile = file;
+ _currentDirectory = Path.GetDirectoryName(@"V:"+file.Name);
+ Directory.CreateDirectory(_cachePath);
+ return Disposable.Create(delegate {
+ _compilerFile = null;
+ _currentDirectory = null;
+ Directory.Delete(_cachePath, true);
+ });
+ }
+
+ private PathType ResolvePath(ref string path) {
+ string[] parts = GetFullPath(path).Split(':');
+ if (parts.Length == 2) {
+ path = parts[1];
+ switch (parts[0]) {
+ case "R":
+ path = "SassAndCoffee.Core"+_resourcePathEscaper.Replace(parts[1], m => (m.Length == 0) ? "_" : ".");
+ return PathType.Resource;
+ case "V":
+ path = _compilerFile.GetRelativeFile(parts[1].Replace('\\', '/')).Name;
+ return PathType.Virtual;
+ case "C":
+ path = _cachePath+parts[1].Replace('/', '\\');
+ return PathType.Cache;
+ }
+ }
+ return PathType.None;
+ }
+ }
+}
diff --git a/SassAndCoffee.Core/ContentCompiler.cs b/SassAndCoffee.Core/ContentCompiler.cs
index dc75f37..d1d410e 100644
--- a/SassAndCoffee.Core/ContentCompiler.cs
+++ b/SassAndCoffee.Core/ContentCompiler.cs
@@ -11,15 +11,12 @@
public class ContentCompiler : IContentCompiler
{
- private readonly ICompilerHost _host;
-
private readonly ICompiledCache _cache;
private readonly IEnumerable _compilers;
- public ContentCompiler(ICompilerHost host, ICompiledCache cache)
+ public ContentCompiler(ICompiledCache cache)
{
- _host = host;
_cache = cache;
_compilers = new ISimpleFileCompiler[] {
@@ -29,35 +26,28 @@ public ContentCompiler(ICompilerHost host, ICompiledCache cache)
new SassFileCompiler(),
new JavascriptPassthroughCompiler(),
};
-
- Init();
}
- public ContentCompiler(ICompilerHost host, ICompiledCache cache, IEnumerable compilers)
+ public ContentCompiler(ICompiledCache cache, IEnumerable compilers)
{
- _host = host;
_cache = cache;
_compilers = compilers;
-
- Init();
}
- public bool CanCompile(string requestedFileName)
+ public bool CanCompile(ICompilerFile physicalFileName)
{
- var physicalFileName = _host.MapPath(requestedFileName);
- return _compilers.Any(x => physicalFileName.EndsWith(x.OutputFileExtension) && x.FindInputFileGivenOutput(physicalFileName) != null);
+ return GetMatchingCompiler(physicalFileName) != null;
}
- public CompilationResult GetCompiledContent (string requestedFileName)
+ public CompilationResult GetCompiledContent(ICompilerFile sourceFileName)
{
- var sourceFileName = _host.MapPath(requestedFileName);
var compiler = GetMatchingCompiler(sourceFileName);
if (compiler == null) {
return CompilationResult.Error;
}
var physicalFileName = compiler.FindInputFileGivenOutput(sourceFileName);
- if (!File.Exists(physicalFileName)) {
+ if (!physicalFileName.Exists) {
return CompilationResult.Error;
}
@@ -65,18 +55,17 @@ public CompilationResult GetCompiledContent (string requestedFileName)
return _cache.GetOrAdd(cacheKey, f => CompileContent(physicalFileName, compiler), compiler.OutputMimeType);
}
- public string GetSourceFileNameFromRequestedFileName(string requestedFileName)
+ public ICompilerFile GetSourceFileNameFromRequestedFileName(ICompilerFile physicalFileName)
{
- var physicalFileName = _host.MapPath(requestedFileName);
var compiler = GetMatchingCompiler(physicalFileName);
if (compiler == null) {
- return string.Empty;
+ return null;
}
return compiler.FindInputFileGivenOutput(physicalFileName);
}
- public string GetOutputMimeType(string requestedFileName)
+ public string GetOutputMimeType(ICompilerFile requestedFileName)
{
var compiler = GetMatchingCompiler(requestedFileName);
if (compiler == null) {
@@ -86,33 +75,24 @@ public string GetOutputMimeType(string requestedFileName)
return compiler.OutputMimeType;
}
- private string GetCacheKey(string physicalFileName, ISimpleFileCompiler compiler)
+ private string GetCacheKey(ICompilerFile physicalFileName, ISimpleFileCompiler compiler)
{
- var fi = new FileInfo(physicalFileName);
var token = compiler.GetFileChangeToken(physicalFileName) ?? String.Empty;
return String.Format("{0:yyyyMMddHHmmss}-{1}-{2}{3}",
- fi.LastWriteTimeUtc, token,
- Path.GetFileNameWithoutExtension(physicalFileName),
+ physicalFileName.LastWriteTimeUtc, token,
+ Path.GetFileNameWithoutExtension(physicalFileName.Name),
compiler.OutputFileExtension);
}
- private CompilationResult CompileContent(string physicalFileName, ISimpleFileCompiler compiler)
+ private CompilationResult CompileContent(ICompilerFile physicalFileName, ISimpleFileCompiler compiler)
{
- var fi = new FileInfo(physicalFileName);
- return new CompilationResult(true, compiler.ProcessFileContent(physicalFileName), compiler.OutputMimeType, fi.LastWriteTimeUtc);
- }
-
- private void Init()
- {
- foreach (var simpleFileCompiler in _compilers) {
- simpleFileCompiler.Init(_host);
- }
+ return new CompilationResult(true, compiler.ProcessFileContent(physicalFileName), compiler.OutputMimeType, physicalFileName.LastWriteTimeUtc);
}
- private ISimpleFileCompiler GetMatchingCompiler(string physicalPath)
+ private ISimpleFileCompiler GetMatchingCompiler(ICompilerFile physicalPath)
{
- return _compilers.FirstOrDefault(x => physicalPath.EndsWith(x.OutputFileExtension) && x.FindInputFileGivenOutput(physicalPath) != null);
+ return _compilers.FirstOrDefault(x => physicalPath.Name.EndsWith(x.OutputFileExtension, StringComparison.OrdinalIgnoreCase) && x.FindInputFileGivenOutput(physicalPath) != null);
}
}
}
\ No newline at end of file
diff --git a/SassAndCoffee.Core/Extensions/SimpleFileCompilerExtensions.cs b/SassAndCoffee.Core/Extensions/SimpleFileCompilerExtensions.cs
index 103f72d..03f05ee 100644
--- a/SassAndCoffee.Core/Extensions/SimpleFileCompilerExtensions.cs
+++ b/SassAndCoffee.Core/Extensions/SimpleFileCompilerExtensions.cs
@@ -1,3 +1,6 @@
+using System;
+using System.Collections.Generic;
+
namespace SassAndCoffee.Core.Extensions
{
using System.IO;
@@ -6,20 +9,36 @@ namespace SassAndCoffee.Core.Extensions
public static class SimpleFileCompilerExtensions
{
- public static string FindInputFileGivenOutput(this ISimpleFileCompiler This, string outputFilePath)
- {
- var rootFi = new FileInfo(outputFilePath);
+ public static ICompilerFile FindInputFileGivenOutput(this ISimpleFileCompiler This, ICompilerFile outputFilePath) {
+ var outputName = outputFilePath.Name;
+ if (outputName.EndsWith(This.OutputFileExtension, StringComparison.InvariantCultureIgnoreCase)) {
+ outputName = outputName.Substring(0, outputName.Length-This.OutputFileExtension.Length);
+ foreach (var ext in This.InputFileExtensions) {
+ ICompilerFile result = outputFilePath.GetRelativeFile(outputName+ext);
+ if (result.Exists) {
+ return result;
+ }
+ }
+ }
+ return null;
+ }
- foreach (var ext in This.InputFileExtensions) {
- var fi = new FileInfo(Path.Combine(rootFi.DirectoryName,
- rootFi.FullName.ToLowerInvariant().Replace(This.OutputFileExtension, "") + ext));
+ public static TextReader OpenReader(this ICompilerFile This) {
+ return new StreamReader(This.Open());
+ }
- if (fi.Exists) {
- return fi.FullName;
- }
+ public static string ReadAllText(this ICompilerFile This) {
+ using (var reader = This.OpenReader()) {
+ return reader.ReadToEnd();
}
+ }
- return null;
+ public static IEnumerable ReadLines(this ICompilerFile This) {
+ using (var reader = This.OpenReader()) {
+ for (string line = reader.ReadLine(); line != null; line = reader.ReadLine()) {
+ yield return line;
+ }
+ }
}
}
}
diff --git a/SassAndCoffee.Core/ICompilerFile.cs b/SassAndCoffee.Core/ICompilerFile.cs
new file mode 100644
index 0000000..b7e5bfc
--- /dev/null
+++ b/SassAndCoffee.Core/ICompilerFile.cs
@@ -0,0 +1,23 @@
+using System;
+using System.IO;
+
+namespace SassAndCoffee.Core
+{
+ public interface ICompilerFile {
+ DateTime LastWriteTimeUtc {
+ get;
+ }
+
+ Stream Open();
+
+ string Name {
+ get;
+ }
+
+ bool Exists {
+ get;
+ }
+
+ ICompilerFile GetRelativeFile(string relativePath);
+ }
+}
\ No newline at end of file
diff --git a/SassAndCoffee.Core/ICompilerHost.cs b/SassAndCoffee.Core/ICompilerHost.cs
deleted file mode 100644
index 2d93db8..0000000
--- a/SassAndCoffee.Core/ICompilerHost.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-namespace SassAndCoffee.Core
-{
- using SassAndCoffee.Core.Compilers;
-
- public interface ICompilerHost
- {
- ///
- /// The base file system path for the application
- ///
- string ApplicationBasePath { get; }
-
- ///
- /// Maps a url path to a physical file system path
- ///
- /// Url path
- /// Absolute path to the file
- string MapPath(string path);
- }
-}
\ No newline at end of file
diff --git a/SassAndCoffee.Core/IContentCompiler.cs b/SassAndCoffee.Core/IContentCompiler.cs
index c37a47a..0bc0068 100644
--- a/SassAndCoffee.Core/IContentCompiler.cs
+++ b/SassAndCoffee.Core/IContentCompiler.cs
@@ -2,12 +2,12 @@ namespace SassAndCoffee.Core
{
public interface IContentCompiler
{
- bool CanCompile(string requestedFileName);
+ bool CanCompile(ICompilerFile requestedFileName);
- CompilationResult GetCompiledContent (string requestedFileName);
+ CompilationResult GetCompiledContent(ICompilerFile requestedFileName);
- string GetSourceFileNameFromRequestedFileName(string requestedFileName);
+ ICompilerFile GetSourceFileNameFromRequestedFileName(ICompilerFile requestedFileName);
- string GetOutputMimeType(string requestedFileName);
+ string GetOutputMimeType(ICompilerFile requestedFileName);
}
}
\ No newline at end of file
diff --git a/SassAndCoffee.Core/JavascriptInterop.cs b/SassAndCoffee.Core/JavascriptInterop.cs
index 17f1275..f678d0b 100644
--- a/SassAndCoffee.Core/JavascriptInterop.cs
+++ b/SassAndCoffee.Core/JavascriptInterop.cs
@@ -1,4 +1,6 @@
-namespace SassAndCoffee.Core
+using System.Diagnostics;
+
+namespace SassAndCoffee.Core
{
using System;
using System.Collections.Concurrent;
@@ -136,8 +138,7 @@ public string Compile(string func, string code)
public static class JS
{
- static Lazy _scriptCompilerImpl;
- static object _gate = 42;
+ static readonly Lazy _scriptCompilerImpl;
/* XXX: Why this crazy code is here
*
@@ -171,6 +172,7 @@ static JS()
try {
v8Assembly = Assembly.LoadFile(v8Name);
} catch (Exception ex) {
+ Debug.WriteLine(ex);
Console.Error.WriteLine("*** WARNING: You're on ARM, Mono, Itanium (heaven help you), or another architecture\n" +
"which isn't x86/amd64 on NT. Loading the Jurassic compiler, which is much slower.");
@@ -184,9 +186,7 @@ static JS()
public static IV8ScriptCompiler CreateJavascriptCompiler()
{
- lock(_gate) {
- return Activator.CreateInstance(_scriptCompilerImpl.Value) as IV8ScriptCompiler;
- }
+ return Activator.CreateInstance(_scriptCompilerImpl.Value) as IV8ScriptCompiler;
}
}
}
diff --git a/SassAndCoffee.Core/SassAndCoffee.Core.csproj b/SassAndCoffee.Core/SassAndCoffee.Core.csproj
index a8cad41..64b16e1 100644
--- a/SassAndCoffee.Core/SassAndCoffee.Core.csproj
+++ b/SassAndCoffee.Core/SassAndCoffee.Core.csproj
@@ -103,10 +103,11 @@
+
-
+