diff --git a/src/dotless.Compiler/CompilerConfiguration.cs b/src/dotless.Compiler/CompilerConfiguration.cs index b4868d9f..12e09c9c 100644 --- a/src/dotless.Compiler/CompilerConfiguration.cs +++ b/src/dotless.Compiler/CompilerConfiguration.cs @@ -5,13 +5,8 @@ namespace dotless.Compiler internal class CompilerConfiguration : DotlessConfiguration { - public CompilerConfiguration(DotlessConfiguration config) + public CompilerConfiguration(DotlessConfiguration config) : base(config) { - LessSource = config.LessSource; - LogLevel = config.LogLevel; - MinifyOutput = config.MinifyOutput; - Optimization = config.Optimization; - CacheEnabled = false; Web = false; Watch = false; diff --git a/src/dotless.Compiler/Program.cs b/src/dotless.Compiler/Program.cs index ea55f23a..b2bfa1ab 100644 --- a/src/dotless.Compiler/Program.cs +++ b/src/dotless.Compiler/Program.cs @@ -4,6 +4,7 @@ namespace dotless.Compiler using System.Collections.Generic; using System.IO; using System.Reflection; + using System.Linq; using Core; using Core.configuration; using Core.Parameters; @@ -170,28 +171,36 @@ private static string GetAssemblyVersion() return "v.Unknown"; } + private static IEnumerable _pluginConfigurators = null; private static IEnumerable GetPluginConfigurators() { - return PluginFinder.GetConfigurators(true, false); + if (_pluginConfigurators == null) + { + _pluginConfigurators = PluginFinder.GetConfigurators(true, false); + } + return _pluginConfigurators; } private static void WritePluginList() { - Console.WriteLine("List of plugins:"); + Console.WriteLine("List of plugins"); + Console.WriteLine(); foreach (IPluginConfigurator pluginConfigurator in GetPluginConfigurators()) { - Console.WriteLine("Name: {0}", pluginConfigurator.Name); - Console.WriteLine("Description: {0}", pluginConfigurator.Description); - Console.Write("Params: "); + Console.WriteLine("{0}", pluginConfigurator.Name); + Console.WriteLine("\t{0}", pluginConfigurator.Description); + Console.WriteLine("\tParams: "); foreach (IPluginParameter pluginParam in pluginConfigurator.GetParameters()) { + Console.Write("\t\t"); + if (!pluginParam.IsMandatory) Console.Write("["); - Console.Write("{0}:{1}", pluginParam.Name, pluginParam.TypeDescription); + Console.Write("{0}={1}", pluginParam.Name, pluginParam.TypeDescription); if (!pluginParam.IsMandatory) - Console.Write("]"); + Console.WriteLine("]"); } Console.WriteLine(); } @@ -207,11 +216,16 @@ private static void WriteHelp() Console.WriteLine("\t\t-m --minify - Output CSS will be compressed"); Console.WriteLine("\t\t-w --watch - Watches .less file for changes"); Console.WriteLine("\t\t-h --help - Displays this dialog"); + Console.WriteLine("\t\t-DKey=Value - prefixes variable to the less"); Console.WriteLine("\t\t-l --listplugins - Lists the plugins available and options"); - Console.WriteLine("\t\t-p --plugin \"plugin name\" \"option:value[,option:value...]\"- adds the named plugin to dotless with the supplied options"); + Console.WriteLine("\t\t-p: --plugin:pluginName[:option=value[,option=value...]] - adds the named plugin to dotless with the supplied options"); Console.WriteLine("\tinputfile: .less file dotless should compile to CSS"); Console.WriteLine("\toutputfile: (optional) desired filename for .css output"); Console.WriteLine("\t\t Defaults to inputfile.css"); + Console.WriteLine(""); + Console.WriteLine("Example:"); + Console.WriteLine("\tdotless.Compiler.exe -m -w \"-p:Rtl:forceRtlTransform=true,onlyReversePrefixedRules=true\""); + Console.WriteLine("\t\tMinify, Watch and add the Rtl plugin"); } private static CompilerConfiguration GetConfigurationFromArguments(List arguments) @@ -249,6 +263,61 @@ private static CompilerConfiguration GetConfigurationFromArguments(List var value = split[1]; ConsoleArgumentParameterSource.ConsoleArguments.Add(key, value); } + else if (arg.StartsWith("-p:") || arg.StartsWith("-plugin:")) + { + var pluginName = arg.Substring(arg.IndexOf(':') + 1); + List pluginArgs = null; + if (pluginName.IndexOf(':') > 0) + { + pluginArgs = pluginName.Substring(pluginName.IndexOf(':') + 1).Split(',').ToList(); + pluginName = pluginName.Substring(0, pluginName.IndexOf(':')); + } + + var pluginConfig = GetPluginConfigurators() + .Where(plugin => String.Compare(plugin.Name, pluginName, true) == 0) + .FirstOrDefault(); + + if (pluginConfig == null) + { + Console.WriteLine("Cannot find plugin {0}.", pluginName); + continue; + } + + var pluginParams = pluginConfig.GetParameters(); + + foreach (var pluginParam in pluginParams) + { + var pluginArg = pluginArgs + .Where(argString => argString.StartsWith(pluginParam.Name + "=")) + .FirstOrDefault(); + + if (pluginArg == null) + { + if (pluginParam.IsMandatory) + { + Console.WriteLine("Missing mandatory argument {0} in plugin {1}.", pluginParam.Name, pluginName); + } + continue; + } + else + { + pluginArgs.Remove(pluginArg); + } + + pluginParam.SetValue(pluginArg.Substring(pluginParam.Name.Length + 1)); + } + + if (pluginArgs.Count > 0) + { + for (int i = 0; i < pluginArgs.Count; i++) + { + Console.WriteLine("Did not recognise argument '{0}'", pluginArgs[i]); + } + } + + pluginConfig.SetParameterValues(pluginParams); + configuration.Plugins.Add(pluginConfig); + } else { Console.WriteLine("Unknown command switch {0}.", arg); diff --git a/src/dotless.Core/ContainerFactory.cs b/src/dotless.Core/ContainerFactory.cs index efb526a1..19c21995 100644 --- a/src/dotless.Core/ContainerFactory.cs +++ b/src/dotless.Core/ContainerFactory.cs @@ -11,6 +11,8 @@ namespace dotless.Core using Parameters; using Response; using Stylizers; + using dotless.Core.Plugins; + using System.Collections.Generic; public class ContainerFactory { @@ -83,6 +85,9 @@ private void RegisterCoreServices(FluentRegistration pandora, DotlessConfigurati pandora.Service().Implementor().Parameters("compress").Set("minify-output").Lifestyle.Transient(); pandora.Service("minify-output").Instance(configuration.MinifyOutput); + pandora.Service().Implementor().Parameters("plugins").Set("default-plugins").Lifestyle.Transient(); + pandora.Service>("default-plugins").Instance(configuration.Plugins); + pandora.Service().Implementor(configuration.LessSource); } } diff --git a/src/dotless.Core/Engine/LessEngine.cs b/src/dotless.Core/Engine/LessEngine.cs index cb127712..d3134449 100644 --- a/src/dotless.Core/Engine/LessEngine.cs +++ b/src/dotless.Core/Engine/LessEngine.cs @@ -15,16 +15,23 @@ public class LessEngine : ILessEngine public ILogger Logger { get; set; } public bool Compress { get; set; } public Env Env { get; set; } + public IEnumerable Plugins { get; set; } - public LessEngine(Parser.Parser parser, ILogger logger, bool compress) + public LessEngine(Parser.Parser parser, ILogger logger, bool compress, IEnumerable plugins) { Parser = parser; Logger = logger; Compress = compress; + Plugins = plugins; + } + + public LessEngine(Parser.Parser parser, ILogger logger, bool compress) + : this(parser, logger, compress, null) + { } public LessEngine(Parser.Parser parser) - : this(parser, new ConsoleLogger(LogLevel.Error), false) + : this(parser, new ConsoleLogger(LogLevel.Error), false, null) { } @@ -41,6 +48,14 @@ public string TransformToCss(string source, string fileName) var env = Env ?? new Env { Compress = Compress }; + if (Plugins != null) + { + foreach (IPluginConfigurator configurator in Plugins) + { + env.AddPlugin(configurator.CreatePlugin()); + } + } + var css = tree.ToCSS(env); return css; diff --git a/src/dotless.Core/Parser/Parser.cs b/src/dotless.Core/Parser/Parser.cs index 1f6503a4..1a21b72b 100644 --- a/src/dotless.Core/Parser/Parser.cs +++ b/src/dotless.Core/Parser/Parser.cs @@ -6,7 +6,9 @@ namespace dotless.Core.Parser using Importers; using Infrastructure; using Stylizers; - using Tree; + using Tree; + using dotless.Core.Plugins; + using System.Collections.Generic; // // less.js - parser @@ -64,8 +66,8 @@ public IImporter Importer _importer = value; _importer.Parser = () => new Parser(Tokenizer.Optimization, Stylizer, _importer) {NodeProvider = this.NodeProvider}; } - } - + } + private const int defaultOptimization = 1; public Parser() diff --git a/src/dotless.Core/Plugins/ColorSpinPlugin.cs b/src/dotless.Core/Plugins/ColorSpinPlugin.cs index 61c83bbb..5537535f 100644 --- a/src/dotless.Core/Plugins/ColorSpinPlugin.cs +++ b/src/dotless.Core/Plugins/ColorSpinPlugin.cs @@ -6,7 +6,7 @@ using Utils; using System.ComponentModel; - [Description("Automatically spins all colors in a less file"), DisplayName("Color Spin Plugin")] + [Description("Automatically spins all colors in a less file"), DisplayName("ColorSpin")] public class ColorSpinPlugin : VisitorPlugin { public double Spin { get; set; } diff --git a/src/dotless.Core/Plugins/GenericPluginConfigurator.cs b/src/dotless.Core/Plugins/GenericPluginConfigurator.cs index 99201400..d1e5dfa6 100644 --- a/src/dotless.Core/Plugins/GenericPluginConfigurator.cs +++ b/src/dotless.Core/Plugins/GenericPluginConfigurator.cs @@ -33,25 +33,40 @@ public Type Configurates } } - public IPlugin CreatePlugin(IEnumerable pluginParameters) + private Func _pluginCreator = null; + public void SetParameterValues(IEnumerable pluginParameters) { ConstructorInfo defaultConstructor; ConstructorInfo parameterConstructor; GetConstructorInfos(out parameterConstructor, out defaultConstructor); - if (pluginParameters.Count() == 0 || pluginParameters.Any(parameter => parameter.Value == null)) + if (pluginParameters == null || pluginParameters.Count() == 0 || pluginParameters.Any(parameter => parameter.Value == null)) { if (defaultConstructor == null) { - throw new Exception(); + throw new Exception("No parameters provided but no default constructor"); } - return (T)defaultConstructor.Invoke(new object[] { }); + _pluginCreator = () => (T)defaultConstructor.Invoke(new object[] { }); + } + else + { + var constructorArguments = parameterConstructor.GetParameters() + .OrderBy(parameter => parameter.Position) + .Select(parameter => pluginParameters.First(pluginParameter => pluginParameter.Name == parameter.Name).Value) + .ToArray(); + + _pluginCreator = () => (T)parameterConstructor.Invoke(constructorArguments); + } + } + + public IPlugin CreatePlugin() + { + if (_pluginCreator == null) + { + SetParameterValues(null); } - return (T)parameterConstructor.Invoke(parameterConstructor.GetParameters() - .OrderBy(parameter => parameter.Position) - .Select(parameter => pluginParameters.First(pluginParameter => pluginParameter.Name == parameter.Name).Value) - .ToArray()); + return _pluginCreator(); } private class ConstructorParameterSet @@ -67,7 +82,7 @@ private void GetConstructorInfos(out ConstructorInfo parameterConstructor, out C if (constructors.Count > 2 || constructors.Count == 0) { - throw new Exception(); + throw new Exception("Generic plugin configurator doesn't support less than 1 or more than 2 constructors. Add your own IPluginConfigurator to the assembly."); } else if (constructors.Count == 2) { if (constructors[0].GetParameters().Length == 0) @@ -80,7 +95,7 @@ private void GetConstructorInfos(out ConstructorInfo parameterConstructor, out C parameterConstructor = constructors[0]; } else { - throw new Exception(); + throw new Exception("Generic plugin configurator only supports 1 parameterless constructor and 1 with parameters. Add your own IPluginConfigurator to the assembly."); } } else { if (constructors[0].GetParameters().Length == 0) @@ -107,7 +122,7 @@ public IEnumerable GetParameters() } return parameterConstructor.GetParameters().Select(parameter => (IPluginParameter)new PluginParameter( - parameter.Name, parameter.ParameterType, defaultConstructor == null)); + parameter.Name, parameter.ParameterType, defaultConstructor == null)).ToList(); } } } diff --git a/src/dotless.Core/Plugins/IPluginConfigurator.cs b/src/dotless.Core/Plugins/IPluginConfigurator.cs index 45bc0bb0..2e990099 100644 --- a/src/dotless.Core/Plugins/IPluginConfigurator.cs +++ b/src/dotless.Core/Plugins/IPluginConfigurator.cs @@ -5,10 +5,12 @@ public interface IPluginConfigurator { - IPlugin CreatePlugin(IEnumerable parameters); + IPlugin CreatePlugin(); IEnumerable GetParameters(); + void SetParameterValues(IEnumerable parameters); + string Name { get; } string Description { get; } diff --git a/src/dotless.Core/Plugins/PluginParameter.cs b/src/dotless.Core/Plugins/PluginParameter.cs index 02c9eac3..e277c9e2 100644 --- a/src/dotless.Core/Plugins/PluginParameter.cs +++ b/src/dotless.Core/Plugins/PluginParameter.cs @@ -42,7 +42,23 @@ public string TypeDescription public void SetValue(string stringValue) { - Value = Convert.ChangeType(stringValue, Type); + if (Type.Equals(typeof(Boolean))) + { + if (stringValue.Equals("true", StringComparison.InvariantCultureIgnoreCase) || + stringValue.Equals("t", StringComparison.InvariantCultureIgnoreCase) || + stringValue.Equals("1", StringComparison.InvariantCultureIgnoreCase)) + { + Value = true; + } + else + { + Value = false; + } + } + else + { + Value = Convert.ChangeType(stringValue, Type); + } } } } diff --git a/src/dotless.Core/Plugins/RtlPlugin.cs b/src/dotless.Core/Plugins/RtlPlugin.cs index f614a8be..e500c765 100644 --- a/src/dotless.Core/Plugins/RtlPlugin.cs +++ b/src/dotless.Core/Plugins/RtlPlugin.cs @@ -7,10 +7,18 @@ using dotless.Core.Parser.Infrastructure.Nodes; using dotless.Core.Parser.Tree; using System.Globalization; -using System.Text.RegularExpressions; + using System.Text.RegularExpressions; + using System.ComponentModel; + [DisplayName("Rtl"), Description("Reverses some css when in rtl mode")] public class RtlPlugin : VisitorPlugin { + public RtlPlugin(bool onlyReversePrefixedRules, bool forceRtlTransform) : this() + { + OnlyReversePrefixedRules = onlyReversePrefixedRules; + ForceRtlTransform = forceRtlTransform; + } + public RtlPlugin() { PropertiesToReverse = new List() @@ -32,6 +40,12 @@ public bool OnlyReversePrefixedRules set; } + public bool ForceRtlTransform + { + get; + set; + } + public IEnumerable PropertiesToReverse { get; @@ -47,7 +61,7 @@ public override void OnPreVisiting(Parser.Infrastructure.Env env) { base.OnPreVisiting(env); - bool isRtl = CultureInfo.CurrentCulture.TextInfo.IsRightToLeft; + bool isRtl = ForceRtlTransform || CultureInfo.CurrentCulture.TextInfo.IsRightToLeft; PrefixesToProcess = new List(); diff --git a/src/dotless.Core/configuration/DotlessConfiguration.cs b/src/dotless.Core/configuration/DotlessConfiguration.cs index 56b96679..506040de 100644 --- a/src/dotless.Core/configuration/DotlessConfiguration.cs +++ b/src/dotless.Core/configuration/DotlessConfiguration.cs @@ -23,6 +23,7 @@ public DotlessConfiguration() Logger = null; LogLevel = LogLevel.Error; Optimization = 1; + Plugins = new List(); } public DotlessConfiguration(DotlessConfiguration config) @@ -34,6 +35,8 @@ public DotlessConfiguration(DotlessConfiguration config) Logger = null; LogLevel = config.LogLevel; Optimization = config.Optimization; + Plugins = new List(); + Plugins.AddRange(config.Plugins); } public bool MinifyOutput { get; set; } @@ -43,6 +46,6 @@ public DotlessConfiguration(DotlessConfiguration config) public Type Logger { get; set; } public LogLevel LogLevel { get; set; } public int Optimization { get; set; } - public List Plugins { get; set; } + public List Plugins { get; private set; } } } \ No newline at end of file diff --git a/src/dotless.Test/Plugins/PluginFixture.cs b/src/dotless.Test/Plugins/PluginFixture.cs index 9391ab26..1c4373e1 100644 --- a/src/dotless.Test/Plugins/PluginFixture.cs +++ b/src/dotless.Test/Plugins/PluginFixture.cs @@ -29,8 +29,19 @@ public TestPlugin2() { } + public string One { get; set; } + public int Two { get; set; } + public decimal Three { get; set; } + public bool Four { get; set; } + public double Five { get; set; } + public TestPlugin2(string one, int two, decimal three, bool four, double five) { + One = one; + Two = two; + Three = three; + Four = four; + Five = five; } public Dictionary GetFunctions() @@ -69,7 +80,8 @@ public void TestGenericConfiguratorParams1() { IPluginConfigurator plugin1 = new GenericPluginConfigurator(); Assert.AreEqual(0, plugin1.GetParameters().Count()); - Assert.IsInstanceOf(plugin1.CreatePlugin(plugin1.GetParameters())); + plugin1.SetParameterValues(plugin1.GetParameters()); + Assert.IsInstanceOf(plugin1.CreatePlugin()); } [Test] @@ -84,7 +96,8 @@ public void TestGenericConfiguratorParams2() TestParam(parameters.ElementAt(2), "three", "Decimal", false); TestParam(parameters.ElementAt(3), "four", "Boolean", false); TestParam(parameters.ElementAt(4), "five", "Double", false); - Assert.IsInstanceOf(plugin2.CreatePlugin(new List())); + plugin2.SetParameterValues(null); + Assert.IsInstanceOf(plugin2.CreatePlugin()); } private void TestParam(IPluginParameter param, string name, string typeDescription, bool isMandatory) @@ -105,7 +118,15 @@ public void TestGenericConfiguratorParams3() parameters.ElementAt(2).SetValue("3.45"); parameters.ElementAt(3).SetValue("true"); parameters.ElementAt(4).SetValue("4.567"); - Assert.IsInstanceOf(plugin2.CreatePlugin(parameters)); + plugin2.SetParameterValues(parameters); + Assert.IsInstanceOf(plugin2.CreatePlugin()); + + TestPlugin2 plugin = plugin2.CreatePlugin() as TestPlugin2; + Assert.AreEqual("string", plugin.One); + Assert.AreEqual(2, plugin.Two); + Assert.AreEqual(3.45m, plugin.Three); + Assert.AreEqual(true, plugin.Four); + Assert.AreEqual(4.567d, plugin.Five); } } }