From 1a9c371910a57c5f492cf749703f494e17ab8ded Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 7 Feb 2017 17:31:22 -0500 Subject: [PATCH 1/2] Adds Sass template processing This more or less copies the LESS module but adds Sass processing using LibSass.Net. LibSass.Net is a wrapper around the C library LibSass which worried me a bit, but everything seems to wire up just fine. The test project is pretty simplistic. I would have liked to have a few test cases for testing out that I got the include functionality right, but I couldn't wrap my head around how to do that easily. But basic verification of a successful call and a failing call are there for now. --- Wyam.sln | 14 ++ .../KnownExtensionGenerated.cs | 1 + .../Wyam.Sass/Properties/AssemblyInfo.cs | 8 + src/extensions/Wyam.Sass/Sass.cs | 156 ++++++++++++++++++ src/extensions/Wyam.Sass/Wyam.Sass.csproj | 79 +++++++++ src/extensions/Wyam.Sass/Wyam.Sass.nuspec | 18 ++ src/extensions/Wyam.Sass/packages.config | 4 + .../Properties/AssemblyInfo.cs | 8 + .../extensions/Wyam.Sass.Tests/SassFixture.cs | 68 ++++++++ .../Wyam.Sass.Tests/Wyam.Sass.Tests.csproj | 86 ++++++++++ tests/extensions/Wyam.Sass.Tests/app.config | 11 ++ .../Wyam.Sass.Tests/packages.config | 5 + 12 files changed, 458 insertions(+) create mode 100644 src/extensions/Wyam.Sass/Properties/AssemblyInfo.cs create mode 100644 src/extensions/Wyam.Sass/Sass.cs create mode 100644 src/extensions/Wyam.Sass/Wyam.Sass.csproj create mode 100644 src/extensions/Wyam.Sass/Wyam.Sass.nuspec create mode 100644 src/extensions/Wyam.Sass/packages.config create mode 100644 tests/extensions/Wyam.Sass.Tests/Properties/AssemblyInfo.cs create mode 100644 tests/extensions/Wyam.Sass.Tests/SassFixture.cs create mode 100644 tests/extensions/Wyam.Sass.Tests/Wyam.Sass.Tests.csproj create mode 100644 tests/extensions/Wyam.Sass.Tests/app.config create mode 100644 tests/extensions/Wyam.Sass.Tests/packages.config diff --git a/Wyam.sln b/Wyam.sln index c11108892..4fb7dc89e 100644 --- a/Wyam.sln +++ b/Wyam.sln @@ -122,6 +122,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wyam.Hosting", "src\core\Wy EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wyam.Hosting.Tests", "tests\core\Wyam.Hosting.Tests\Wyam.Hosting.Tests.csproj", "{9C59DA3D-6A41-4DC4-A4BB-0927C6978367}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wyam.Sass", "src\extensions\Wyam.Sass\Wyam.Sass.csproj", "{90A0AB19-0146-4931-BEFA-D895B192E050}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wyam.Sass.Tests", "tests\extensions\Wyam.Sass.Tests\Wyam.Sass.Tests.csproj", "{A6B7B1A6-7BE5-4E98-9286-D03A0F936E98}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "misc", "misc", "{9B01F618-6AB3-4DCD-A044-4CA58EEF6419}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig @@ -332,6 +336,14 @@ Global {9C59DA3D-6A41-4DC4-A4BB-0927C6978367}.Debug|Any CPU.Build.0 = Debug|Any CPU {9C59DA3D-6A41-4DC4-A4BB-0927C6978367}.Release|Any CPU.ActiveCfg = Release|Any CPU {9C59DA3D-6A41-4DC4-A4BB-0927C6978367}.Release|Any CPU.Build.0 = Release|Any CPU + {90A0AB19-0146-4931-BEFA-D895B192E050}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90A0AB19-0146-4931-BEFA-D895B192E050}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90A0AB19-0146-4931-BEFA-D895B192E050}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90A0AB19-0146-4931-BEFA-D895B192E050}.Release|Any CPU.Build.0 = Release|Any CPU + {A6B7B1A6-7BE5-4E98-9286-D03A0F936E98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A6B7B1A6-7BE5-4E98-9286-D03A0F936E98}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A6B7B1A6-7BE5-4E98-9286-D03A0F936E98}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A6B7B1A6-7BE5-4E98-9286-D03A0F936E98}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -393,5 +405,7 @@ Global {95080CB0-DC4E-4617-8F73-6E06A6CA2F5F} = {4813B17D-EFCA-401D-B0E6-7B24B3946A70} {A3041B22-8A32-4ADB-89F3-A808AC60899B} = {4813B17D-EFCA-401D-B0E6-7B24B3946A70} {9C59DA3D-6A41-4DC4-A4BB-0927C6978367} = {12931CA5-E734-4CD9-883D-27D897512183} + {90A0AB19-0146-4931-BEFA-D895B192E050} = {5A431149-2B88-40C3-9717-CA6CCF214317} + {A6B7B1A6-7BE5-4E98-9286-D03A0F936E98} = {2E5842F3-4797-4BB5-9A55-F3A005B434F8} EndGlobalSection EndGlobal diff --git a/src/core/Wyam.Configuration/KnownExtensionGenerated.cs b/src/core/Wyam.Configuration/KnownExtensionGenerated.cs index 3584b6310..3f9d7b8d1 100644 --- a/src/core/Wyam.Configuration/KnownExtensionGenerated.cs +++ b/src/core/Wyam.Configuration/KnownExtensionGenerated.cs @@ -24,6 +24,7 @@ public partial class KnownExtension public static readonly KnownExtension Markdown = new KnownExtension("Wyam.Markdown"); public static readonly KnownExtension Minification = new KnownExtension("Wyam.Minification"); public static readonly KnownExtension Razor = new KnownExtension("Wyam.Razor"); + public static readonly KnownExtension Sass = new KnownExtension("Wyam.Sass"); public static readonly KnownExtension SearchIndex = new KnownExtension("Wyam.SearchIndex"); public static readonly KnownExtension Tables = new KnownExtension("Wyam.Tables"); public static readonly KnownExtension TextGeneration = new KnownExtension("Wyam.TextGeneration"); diff --git a/src/extensions/Wyam.Sass/Properties/AssemblyInfo.cs b/src/extensions/Wyam.Sass/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..3ef339347 --- /dev/null +++ b/src/extensions/Wyam.Sass/Properties/AssemblyInfo.cs @@ -0,0 +1,8 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Wyam.Sass")] +[assembly: AssemblyDescription("")] +[assembly: ComVisible(false)] +[assembly: Guid("90a0ab19-0146-4931-befa-d895b192e050")] \ No newline at end of file diff --git a/src/extensions/Wyam.Sass/Sass.cs b/src/extensions/Wyam.Sass/Sass.cs new file mode 100644 index 000000000..b8f79f64e --- /dev/null +++ b/src/extensions/Wyam.Sass/Sass.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using LibSass.Compiler; +using LibSass.Compiler.Options; +using Wyam.Common.Documents; +using Wyam.Common.Execution; +using Wyam.Common.IO; +using Wyam.Common.Meta; +using Wyam.Common.Modules; +using Wyam.Common.Tracing; +using Wyam.Common.Util; + +namespace Wyam.Sass +{ + /// + /// Compiles Sass CSS files to CSS stylesheets. + /// + /// + /// The content of the input document is compiled to CSS and the content of the output document contains the compiled CSS stylesheet. + /// + /// + /// This is a pipeline that compiles two Sass CSS files, one for Bootstrap (which contains a lot of includes) and a second for custom CSS. + /// + /// Pipelines.Add("Sass", + /// ReadFiles("master.scss"), + /// Concat(ReadFiles("foundation.scss")), + /// Sass().WithCompactOutputStyle(), + /// WriteFiles(".css") + /// ); + /// + /// + /// Templates + public class Sass : IModule + { + private readonly List _includePaths = new List(); + private bool _includeSourceComments = true; + private SassOutputStyle _outputStyle = SassOutputStyle.Compact; + + /// + /// Adds a list of paths to search while processing includes. + /// + /// The paths to include. + /// The current instance. + public Sass WithIncludePaths(IEnumerable paths) + { + _includePaths.AddRange(paths); + return this; + } + + /// + /// Adds a path to search while processing includes. + /// + /// The path to include. + /// The current instance. + public Sass WithIncludePath(string path) + { + _includePaths.Add(path); + return this; + } + + /// + /// Sets whether the source comments are included + /// + /// The default value is true + /// The current instance. + public Sass IncludeSourceComments(bool includeSourceComments = true) + { + _includeSourceComments = includeSourceComments; + return this; + } + + /// + /// Sets the output style to compact. + /// + /// The current instance. + public Sass WithCompactOutputStyle() + { + _outputStyle = SassOutputStyle.Compact; + return this; + } + + + /// + /// Sets the output style to expanded. + /// + /// The current instance. + public Sass WithExpandedOutputStyle() + { + _outputStyle = SassOutputStyle.Expanded; + return this; + } + + /// + /// Sets the output style to compressed. + /// + /// The current instance. + public Sass WithCompressedOutputStyle() + { + _outputStyle = SassOutputStyle.Compressed; + return this; + } + + /// + /// Sets the output style to nested. + /// + /// The current instance. + public Sass WithNestedOutputStyle() + { + _outputStyle = SassOutputStyle.Nested; + return this; + } + + public IEnumerable Execute(IReadOnlyList inputs, IExecutionContext context) + { + return inputs.AsParallel().Select(context, input => + { + Trace.Verbose("Processing Sass for {0}", input.SourceString()); + + FilePath path = input.FilePath(Keys.SourceFilePath); + if (path != null) + { + _includePaths.Add(path.Directory.FullPath); + } + + SassOptions sassOptions = new SassOptions + { + Data = input.Content, + IncludePaths = _includePaths.ToArray(), + IncludeSourceComments = _includeSourceComments, + OutputStyle = _outputStyle + }; + + SassCompiler sassCompiler = new SassCompiler(sassOptions); + SassResult sassResult ; + try + { + sassResult = sassCompiler.Compile(); + } + catch (Exception ex) + { + Trace.Warning("Exception while compiling sass file {0}: {}", input.SourceString(), ex.ToString()); + return context.GetDocument(input, input.SourceString()); + } + + if (sassResult.ErrorStatus != 0) + { + Trace.Warning("Exception while compiling sass file {0}: {1}", input.SourceString(), sassResult.ErrorMessage); + return context.GetDocument(input, input.SourceString()); + } + + return context.GetDocument(input, sassResult.Output); + }); + } + } +} diff --git a/src/extensions/Wyam.Sass/Wyam.Sass.csproj b/src/extensions/Wyam.Sass/Wyam.Sass.csproj new file mode 100644 index 000000000..abba2dcb1 --- /dev/null +++ b/src/extensions/Wyam.Sass/Wyam.Sass.csproj @@ -0,0 +1,79 @@ + + + + + Debug + AnyCPU + {90A0AB19-0146-4931-BEFA-D895B192E050} + Library + Properties + Wyam.Sass + Wyam.Sass + v4.6.2 + 512 + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\..\packages\libsassnet.3.3.7\lib\net40\LibSass.NET.dll + True + + + + + + + + + + + + + + + Properties\SolutionInfo.cs + + + + + {F40B73E9-C0CC-465C-925E-B51E686C1D5C} + Wyam.Common + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/src/extensions/Wyam.Sass/Wyam.Sass.nuspec b/src/extensions/Wyam.Sass/Wyam.Sass.nuspec new file mode 100644 index 000000000..db450f534 --- /dev/null +++ b/src/extensions/Wyam.Sass/Wyam.Sass.nuspec @@ -0,0 +1,18 @@ + + + + Wyam.Sass + $version$ + Wyam.Sass + Dave Glick + Dave Glick + https://github.com/Wyamio/Wyam/blob/master/LICENSE + https://wyam.io/Content/images/logo-square-64.png + https://wyam.io + false + Wyam is a simple to use, highly modular, and extremely configurable static content generator. This library provides support for parsing Sass CSS content. + Copyright 2016 + Wyam Static StaticContent StaticSite Blog BlogEngine CSS Sass SassCSS + + + \ No newline at end of file diff --git a/src/extensions/Wyam.Sass/packages.config b/src/extensions/Wyam.Sass/packages.config new file mode 100644 index 000000000..76d15b603 --- /dev/null +++ b/src/extensions/Wyam.Sass/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/tests/extensions/Wyam.Sass.Tests/Properties/AssemblyInfo.cs b/tests/extensions/Wyam.Sass.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..e0b93c576 --- /dev/null +++ b/tests/extensions/Wyam.Sass.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,8 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Wyam.Sass.Tests")] +[assembly: AssemblyDescription("")] +[assembly: ComVisible(false)] +[assembly: Guid("a6b7b1a6-7be5-4e98-9286-d03a0f936e98")] \ No newline at end of file diff --git a/tests/extensions/Wyam.Sass.Tests/SassFixture.cs b/tests/extensions/Wyam.Sass.Tests/SassFixture.cs new file mode 100644 index 000000000..97ebb8717 --- /dev/null +++ b/tests/extensions/Wyam.Sass.Tests/SassFixture.cs @@ -0,0 +1,68 @@ +using System; +using System.Linq; +using NSubstitute; +using NUnit.Framework; +using Wyam.Common.Documents; +using Wyam.Common.Execution; +using Wyam.Testing; + +namespace Wyam.Sass.Tests +{ + [TestFixture] + [Parallelizable(ParallelScope.Self | ParallelScope.Children)] + public class SassFixture : BaseFixture + { + [Test] + public void Convert() + { + string input = @" +$font-stack: Helvetica, sans-serif; +$primary-color: #333; + +body { + font: 100% $font-stack; + color: $primary-color; +}"; + + string output = "body { font: 100% Helvetica, sans-serif; color: #333; }\n"; + + IExecutionContext context = Substitute.For(); + IDocument document = Substitute.For(); + document.Content.Returns(input); + + Sass sass = new Sass().IncludeSourceComments(false).WithCompactOutputStyle(); + + // When + sass.Execute(new[] { document }, context).ToList(); // Make sure to materialize the result list + + // Then + context.Received(1).GetDocument(Arg.Any(), Arg.Any()); + context.Received().GetDocument(document, output); + } + + [Test] + public void ConvertingBadSassFails() + { + string input = @" +$font-stack: Helvetica, sans-serif +$primary-color: #333 + +body { + font: 100% $font-stack; + color: $primary-color; +}"; + + IExecutionContext context = Substitute.For(); + IDocument document = Substitute.For(); + document.Content.Returns(input); + + Sass sass = new Sass(); + + // That + Assert.Catch(() => + { + sass.Execute(new[] { document }, context).ToList(); // Make sure to materialize the result list + }); + } + } +} diff --git a/tests/extensions/Wyam.Sass.Tests/Wyam.Sass.Tests.csproj b/tests/extensions/Wyam.Sass.Tests/Wyam.Sass.Tests.csproj new file mode 100644 index 000000000..dbe25afaa --- /dev/null +++ b/tests/extensions/Wyam.Sass.Tests/Wyam.Sass.Tests.csproj @@ -0,0 +1,86 @@ + + + + + Debug + AnyCPU + {A6B7B1A6-7BE5-4E98-9286-D03A0F936E98} + Library + Properties + Wyam.Sass.Tests + Wyam.Sass.Tests + v4.6.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\..\packages\NSubstitute.1.9.2.0\lib\net45\NSubstitute.dll + True + + + ..\..\..\packages\NUnit.3.0.1\lib\net45\nunit.framework.dll + True + + + + + + + + + + + + Properties\SolutionInfo.cs + + + + + + + {F40B73E9-C0CC-465C-925E-B51E686C1D5C} + Wyam.Common + + + {BC8C5DE2-6692-4E39-A0BB-689C316AEFF1} + Wyam.Core + + + {7726D040-4192-49C7-B9EA-E192397127C3} + Wyam.Testing + + + {90a0ab19-0146-4931-befa-d895b192e050} + Wyam.Sass + + + + + + + + + \ No newline at end of file diff --git a/tests/extensions/Wyam.Sass.Tests/app.config b/tests/extensions/Wyam.Sass.Tests/app.config new file mode 100644 index 000000000..a7c224230 --- /dev/null +++ b/tests/extensions/Wyam.Sass.Tests/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tests/extensions/Wyam.Sass.Tests/packages.config b/tests/extensions/Wyam.Sass.Tests/packages.config new file mode 100644 index 000000000..2e211cc36 --- /dev/null +++ b/tests/extensions/Wyam.Sass.Tests/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file From e67aeb541d7432113dfdeefc4ae3caabee80fb63 Mon Sep 17 00:00:00 2001 From: Phil Date: Fri, 3 Mar 2017 23:35:44 -0500 Subject: [PATCH 2/2] Reworks sass include paths Original code was modifying the class level list on each run in parallel. Not smart. This creates a new array if it is needed rather than adding to the class level instance. --- src/extensions/Wyam.Sass/Sass.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/extensions/Wyam.Sass/Sass.cs b/src/extensions/Wyam.Sass/Sass.cs index b8f79f64e..b0f42cca6 100644 --- a/src/extensions/Wyam.Sass/Sass.cs +++ b/src/extensions/Wyam.Sass/Sass.cs @@ -116,19 +116,16 @@ public IEnumerable Execute(IReadOnlyList inputs, IExecutio return inputs.AsParallel().Select(context, input => { Trace.Verbose("Processing Sass for {0}", input.SourceString()); - - FilePath path = input.FilePath(Keys.SourceFilePath); - if (path != null) - { - _includePaths.Add(path.Directory.FullPath); - } + FilePath path = input.FilePath(Keys.SourceFilePath); SassOptions sassOptions = new SassOptions - { + { Data = input.Content, - IncludePaths = _includePaths.ToArray(), IncludeSourceComments = _includeSourceComments, - OutputStyle = _outputStyle + OutputStyle = _outputStyle, + IncludePaths = path == null + ? _includePaths.ToArray() + : new List(_includePaths) { path.Directory.FullPath }.ToArray() }; SassCompiler sassCompiler = new SassCompiler(sassOptions);