Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: dotless/dotless
base: 483f4cd45a
...
head fork: dotless/dotless
compare: 22f8fb02af
Checking mergeability… Don't worry, you can still create the pull request.
  • 13 commits
  • 54 files changed
  • 0 commit comments
  • 2 contributors
Showing with 1,952 additions and 315 deletions.
  1. +1 −6 src/dotless.Compiler/CompilerConfiguration.cs
  2. +106 −1 src/dotless.Compiler/Program.cs
  3. +8 −0 src/dotless.Core/ContainerFactory.cs
  4. +82 −63 src/dotless.Core/Engine/LessEngine.cs
  5. +1 −1  src/dotless.Core/EngineFactory.cs
  6. +1 −1  src/dotless.Core/HandlerImpl.cs
  7. +1 −1  src/dotless.Core/Less.cs
  8. +63 −48 src/dotless.Core/Parser/Infrastructure/Env.cs
  9. +0 −12 src/dotless.Core/Parser/Infrastructure/IExtension.cs
  10. +47 −16 src/dotless.Core/Parser/Infrastructure/Nodes/Node.cs
  11. +19 −0 src/dotless.Core/Parser/Infrastructure/Nodes/NodeList.cs
  12. +7 −5 src/dotless.Core/Parser/Parser.cs
  13. +2 −2 src/dotless.Core/Parser/Parsers.cs
  14. +6 −0 src/dotless.Core/Parser/Tree/Call.cs
  15. +2 −2 src/dotless.Core/Parser/Tree/Color.cs
  16. +6 −0 src/dotless.Core/Parser/Tree/Element.cs
  17. +6 −0 src/dotless.Core/Parser/Tree/Expression.cs
  18. +13 −2 src/dotless.Core/Parser/Tree/MixinCall.cs
  19. +8 −1 src/dotless.Core/Parser/Tree/Operation.cs
  20. +47 −7 src/dotless.Core/Parser/Tree/Root.cs
  21. +56 −50 src/dotless.Core/Parser/Tree/Rule.cs
  22. +22 −10 src/dotless.Core/Parser/Tree/Ruleset.cs
  23. +6 −0 src/dotless.Core/Parser/Tree/Selector.cs
  24. +7 −0 src/dotless.Core/Parser/Tree/Shorthand.cs
  25. +6 −0 src/dotless.Core/Parser/Tree/Url.cs
  26. +63 −57 src/dotless.Core/Parser/Tree/Value.cs
  27. +45 −0 src/dotless.Core/Plugins/ColorSpinPlugin.cs
  28. +140 −0 src/dotless.Core/Plugins/GenericPluginConfigurator.cs
  29. +11 −0 src/dotless.Core/Plugins/IFunctionPlugin.cs
  30. +6 −0 src/dotless.Core/Plugins/IPlugin.cs
  31. +20 −0 src/dotless.Core/Plugins/IPluginConfigurator.cs
  32. +17 −0 src/dotless.Core/Plugins/IPluginParameter.cs
  33. +9 −0 src/dotless.Core/Plugins/IVisitor.cs
  34. +21 −0 src/dotless.Core/Plugins/IVisitorPlugin.cs
  35. +114 −0 src/dotless.Core/Plugins/PluginFinder.cs
  36. +64 −0 src/dotless.Core/Plugins/PluginParameter.cs
  37. +282 −0 src/dotless.Core/Plugins/RtlPlugin.cs
  38. +39 −0 src/dotless.Core/Plugins/VisitorPlugin.cs
  39. +15 −2 src/dotless.Core/configuration/DotlessConfiguration.cs
  40. +1 −1  src/dotless.Core/configuration/DotlessConfigurationSectionHandler.cs
  41. +1 −1  src/dotless.Core/configuration/WebConfigConfigurationLoader.cs
  42. +64 −1 src/dotless.Core/configuration/XmlConfigurationInterpreter.cs
  43. +12 −1 src/dotless.Core/dotless.Core.csproj
  44. +6 −1 src/dotless.SampleWeb/Content/Web.config
  45. +39 −0 src/dotless.Test/Plugins/ColorSpinPluginFixture.cs
  46. +132 −0 src/dotless.Test/Plugins/PluginFixture.cs
  47. +277 −0 src/dotless.Test/Plugins/RtlPluginFixture.cs
  48. +4 −1 src/dotless.Test/SpecFixtureBase.cs
  49. +35 −13 src/dotless.Test/Specs/ColorsFixture.cs
  50. +3 −4 src/dotless.Test/Specs/Compression/ColorsFixture.cs
  51. +1 −1  src/dotless.Test/Specs/Css3Fixture.cs
  52. +3 −3 src/dotless.Test/Specs/CssFixture.cs
  53. +1 −1  src/dotless.Test/Specs/OperationsFixture.cs
  54. +4 −0 src/dotless.Test/dotless.Test.csproj
View
7 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;
View
107 src/dotless.Compiler/Program.cs
@@ -4,9 +4,11 @@ namespace dotless.Compiler
using System.Collections.Generic;
using System.IO;
using System.Reflection;
+ using System.Linq;
using Core;
using Core.configuration;
using Core.Parameters;
+ using dotless.Core.Plugins;
public class Program
{
@@ -169,6 +171,41 @@ private static string GetAssemblyVersion()
return "v.Unknown";
}
+ private static IEnumerable<IPluginConfigurator> _pluginConfigurators = null;
+ private static IEnumerable<IPluginConfigurator> GetPluginConfigurators()
+ {
+ if (_pluginConfigurators == null)
+ {
+ _pluginConfigurators = PluginFinder.GetConfigurators(true);
+ }
+ return _pluginConfigurators;
+ }
+
+ private static void WritePluginList()
+ {
+ Console.WriteLine("List of plugins");
+ Console.WriteLine();
+ foreach (IPluginConfigurator pluginConfigurator in GetPluginConfigurators())
+ {
+ 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);
+
+ if (!pluginParam.IsMandatory)
+ Console.WriteLine("]");
+ }
+ Console.WriteLine();
+ }
+ }
+
private static void WriteHelp()
{
Console.WriteLine("dotless Compiler {0}", GetAssemblyVersion());
@@ -179,14 +216,21 @@ 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: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<string> arguments)
{
- var configuration = new CompilerConfiguration(DotlessConfiguration.Default);
+ var configuration = new CompilerConfiguration(DotlessConfiguration.GetDefault());
foreach (var arg in arguments)
{
@@ -202,6 +246,12 @@ private static CompilerConfiguration GetConfigurationFromArguments(List<string>
configuration.Help = true;
return configuration;
}
+ else if (arg == "-l" || arg == "--listplugins")
+ {
+ WritePluginList();
+ configuration.Help = true;
+ return configuration;
+ }
else if (arg == "-w" || arg == "--watch")
{
configuration.Watch = true;
@@ -213,6 +263,61 @@ private static CompilerConfiguration GetConfigurationFromArguments(List<string>
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<string> 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);
View
8 src/dotless.Core/ContainerFactory.cs
@@ -11,6 +11,9 @@ namespace dotless.Core
using Parameters;
using Response;
using Stylizers;
+ using dotless.Core.Plugins;
+ using System.Collections.Generic;
+ using dotless.Core.Importers;
public class ContainerFactory
{
@@ -72,6 +75,8 @@ private void RegisterCoreServices(FluentRegistration pandora, DotlessConfigurati
pandora.Service<LogLevel>("error-level").Instance(configuration.LogLevel);
pandora.Service<IStylizer>().Implementor<PlainStylizer>();
+ pandora.Service<IImporter>().Implementor<Importer>();
+
pandora.Service<Parser.Parser>().Implementor<Parser.Parser>().Parameters("optimization").Set("default-optimization").Lifestyle.Transient();
pandora.Service<int>("default-optimization").Instance(configuration.Optimization);
@@ -83,6 +88,9 @@ private void RegisterCoreServices(FluentRegistration pandora, DotlessConfigurati
pandora.Service<ILessEngine>().Implementor<LessEngine>().Parameters("compress").Set("minify-output").Lifestyle.Transient();
pandora.Service<bool>("minify-output").Instance(configuration.MinifyOutput);
+ pandora.Service<ILessEngine>().Implementor<LessEngine>().Parameters("plugins").Set("default-plugins").Lifestyle.Transient();
+ pandora.Service<IEnumerable<IPluginConfigurator>>("default-plugins").Instance(configuration.Plugins);
+
pandora.Service<IFileReader>().Implementor(configuration.LessSource);
}
}
View
145 src/dotless.Core/Engine/LessEngine.cs
@@ -1,64 +1,83 @@
-namespace dotless.Core
-{
- using System.Collections.Generic;
- using System.Linq;
- using Exceptions;
- using Loggers;
- using Parser.Infrastructure;
-
- public class LessEngine : ILessEngine
- {
- public Parser.Parser Parser { get; set; }
- public ILogger Logger { get; set; }
- public bool Compress { get; set; }
- public Env Env { get; set; }
-
- public LessEngine(Parser.Parser parser, ILogger logger, bool compress)
- {
- Parser = parser;
- Logger = logger;
- Compress = compress;
- }
-
- public LessEngine(Parser.Parser parser)
- : this(parser, new ConsoleLogger(LogLevel.Error), false)
- {
- }
-
- public LessEngine()
- : this(new Parser.Parser())
- {
- }
-
- public string TransformToCss(string source, string fileName)
- {
- try
- {
- var tree = Parser.Parse(source, fileName);
-
- var env = Env ?? new Env { Compress = Compress };
-
- var css = tree.ToCSS(env);
-
- return css;
- }
- catch (ParserException e)
- {
- Logger.Error(e.Message);
- }
-
- return "";
- }
-
- public IEnumerable<string> GetImports()
- {
- return Parser.Importer.Imports.Distinct();
- }
-
- public void ResetImports()
- {
- Parser.Importer.Imports.Clear();
- }
-
- }
+using dotless.Core.Plugins;
+
+namespace dotless.Core
+{
+ using System.Collections.Generic;
+ using System.Linq;
+ using Exceptions;
+ using Loggers;
+ using Parser.Infrastructure;
+ using Parser.Tree;
+ using System.IO;
+
+ public class LessEngine : ILessEngine
+ {
+ public Parser.Parser Parser { get; set; }
+ public ILogger Logger { get; set; }
+ public bool Compress { get; set; }
+ public Env Env { get; set; }
+ public IEnumerable<IPluginConfigurator> Plugins { get; set; }
+
+ public LessEngine(Parser.Parser parser, ILogger logger, bool compress, IEnumerable<IPluginConfigurator> 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, null)
+ {
+ }
+
+ public LessEngine()
+ : this(new Parser.Parser())
+ {
+ }
+
+ public string TransformToCss(string source, string fileName)
+ {
+ try
+ {
+ var tree = Parser.Parse(source, 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;
+ }
+ catch (ParserException e)
+ {
+ Logger.Error(e.Message);
+ }
+
+ return "";
+ }
+
+ public IEnumerable<string> GetImports()
+ {
+ return Parser.Importer.Imports.Distinct();
+ }
+
+ public void ResetImports()
+ {
+ Parser.Importer.Imports.Clear();
+ }
+
+ }
}
View
2  src/dotless.Core/EngineFactory.cs
@@ -10,7 +10,7 @@ public EngineFactory(DotlessConfiguration configuration)
{
Configuration = configuration;
}
- public EngineFactory() : this(DotlessConfiguration.Default)
+ public EngineFactory() : this(DotlessConfiguration.GetDefault())
{
}
View
2  src/dotless.Core/HandlerImpl.cs
@@ -24,7 +24,7 @@ public void Execute()
var localPath = Http.Context.Request.Url.LocalPath;
var source = FileReader.GetFileContents(localPath);
-
+
Response.WriteCss(Engine.TransformToCss(source, localPath));
}
}
View
2  src/dotless.Core/Less.cs
@@ -6,7 +6,7 @@ public static class Less
{
public static string Parse(string less)
{
- return Parse(less, DotlessConfiguration.Default);
+ return Parse(less, DotlessConfiguration.GetDefault());
}
public static string Parse(string less, DotlessConfiguration config)
View
111 src/dotless.Core/Parser/Infrastructure/Env.cs
@@ -6,32 +6,34 @@
using System.Reflection;
using System.Text.RegularExpressions;
using Functions;
- using Nodes;
+ using Nodes;
+ using Plugins;
using Tree;
public class Env
{
- private Dictionary<string, Type> _functionTypes;
- private Dictionary<int, IExtension> _extensions;
-
- public Stack<Ruleset> Frames { get; protected set; }
- public bool Compress { get; set; }
- public Node Rule { get; set; }
- public Output Output { get; private set; }
-
- public Env()
+ private readonly Dictionary<string, Type> _functionTypes;
+ private readonly List<IPlugin> _plugins;
+
+ public Stack<Ruleset> Frames { get; protected set; }
+ public bool Compress { get; set; }
+ public Node Rule { get; set; }
+ public Output Output { get; private set; }
+
+ public Env() : this(null, null)
{
- AddCoreFunctions();
- Frames = new Stack<Ruleset>();
- Output = new Output(this);
}
- protected Env(Stack<Ruleset> frames, Dictionary<string, Type> functions, Dictionary<int, IExtension> extensions)
+ protected Env(Stack<Ruleset> frames, Dictionary<string, Type> functions)
{
+ Frames = frames ?? new Stack<Ruleset>();
Output = new Output(this);
- _functionTypes = functions;
- _extensions = extensions;
- Frames = frames;
+
+ _plugins = new List<IPlugin>();
+ _functionTypes = functions ?? new Dictionary<string, Type>();
+
+ if (_functionTypes.Count == 0)
+ AddCoreFunctions();
}
/// <summary>
@@ -39,37 +41,43 @@ protected Env(Stack<Ruleset> frames, Dictionary<string, Type> functions, Diction
/// </summary>
public virtual Env CreateChildEnv(Stack<Ruleset> frames)
{
- return new Env(frames, _functionTypes, _extensions);
+ return new Env(frames, _functionTypes);
}
/// <summary>
- /// Adds an extension to this Env to be used whenever this Env is used
+ /// Adds a plugin to this Env
/// </summary>
- public void AddExension(IExtension extension)
+ public void AddPlugin(IPlugin plugin)
{
- if (_extensions == null)
- {
- _extensions = new Dictionary<int, IExtension>();
- }
+ if (plugin == null) throw new ArgumentNullException("plugin");
- int hashCode = extension.GetType().GetHashCode();
-
- if (_extensions.ContainsKey(hashCode))
+ _plugins.Add(plugin);
+
+ IFunctionPlugin functionPlugin = plugin as IFunctionPlugin;
+ if (functionPlugin != null)
{
- string message = String.Format("Extension type is already loaded: {0}", extension.GetType().FullName);
- throw new InvalidOperationException(message);
+ foreach(KeyValuePair<string, Type> function in functionPlugin.GetFunctions())
+ {
+ string functionName = function.Key.ToLowerInvariant();
+
+ if (_functionTypes.ContainsKey(functionName))
+ {
+ string message = string.Format("Function '{0}' already exists in environment but is added by plugin {1}",
+ functionName, plugin.GetName());
+ throw new InvalidOperationException(message);
+ }
+
+ AddFunction(functionName, function.Value);
+ }
}
-
- _extensions.Add(hashCode, extension);
- extension.Setup(this);
- }
-
- /// <summary>
- /// Returns an extension of this type (if loaded). Otherwise will return null.
- /// </summary>
- public T1 GetExtension<T1>() where T1 : IExtension
- {
- return (T1)_extensions[typeof(T1).GetHashCode()];
+ }
+
+ public IEnumerable<IVisitorPlugin> VisitorPlugins
+ {
+ get
+ {
+ return _plugins.OfType<IVisitorPlugin>();
+ }
}
/// <summary>
@@ -108,15 +116,22 @@ public IEnumerable<Closure> FindRulesets(Selector selector)
}
/// <summary>
- /// Given an assembly, loads all the dotless Functions in that assembly into this Env.
+ /// Adds a Function to this Env object
+ /// </summary>
+ public void AddFunction(string name, Type type)
+ {
+ if (name == null) throw new ArgumentNullException("name");
+ if (type == null) throw new ArgumentNullException("type");
+
+ _functionTypes[name] = type;
+ }
+
+ /// <summary>
+ /// Given an assembly, adds all the dotless Functions in that assembly into this Env.
/// </summary>
public void AddFunctionsFromAssembly(Assembly assembly)
{
- if (_functionTypes == null)
- {
- _functionTypes = new Dictionary<string, Type>();
-
- }
+ if (assembly == null) throw new ArgumentNullException("assembly");
var functionType = typeof (Function);
@@ -126,14 +141,14 @@ public void AddFunctionsFromAssembly(Assembly assembly)
.Where(t => !t.IsAbstract)
.SelectMany<Type, KeyValuePair<string, Type>>(GetFunctionNames))
{
- _functionTypes.Add(func.Key, func.Value);
+ AddFunction(func.Key, func.Value);
}
}
private void AddCoreFunctions()
{
AddFunctionsFromAssembly(Assembly.GetExecutingAssembly());
- _functionTypes["%"] = typeof (CFormatString);
+ AddFunction("%", typeof (CFormatString));
}
/// <summary>
View
12 src/dotless.Core/Parser/Infrastructure/IExtension.cs
@@ -1,12 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
-namespace dotless.Core.Parser.Infrastructure
-{
- public interface IExtension
- {
- void Setup(Env environment);
- }
-}
View
63 src/dotless.Core/Parser/Infrastructure/Nodes/Node.cs
@@ -1,4 +1,6 @@
-namespace dotless.Core.Parser.Infrastructure.Nodes
+using dotless.Core.Plugins;
+
+namespace dotless.Core.Parser.Infrastructure.Nodes
{
using System;
@@ -46,12 +48,12 @@ public abstract class Node
/// <summary>
/// Copies common properties when evaluating multiple nodes into one
/// </summary>
- /// <typeparam name="TN1">Type to return - for convenience</typeparam>
+ /// <typeparam name="T">Type to return - for convenience</typeparam>
/// <param name="nodes">The nodes this new node is derived from</param>
/// <returns>The new node</returns>
- public TN1 ReducedFrom<TN1>(params Node[] nodes) where TN1 : Node
- {
- foreach (Node node in nodes)
+ public T ReducedFrom<T>(params Node[] nodes) where T : Node
+ {
+ foreach (var node in nodes)
{
if (node == this)
{
@@ -84,9 +86,9 @@ public abstract class Node
}
}
}
-
- return (TN1)this;
- }
+
+ return (T)this;
+ }
public virtual void AppendCSS(Env env)
{
@@ -96,13 +98,15 @@ public virtual void AppendCSS(Env env)
Evaluate(env).AppendCSS(env);
}
- public virtual string ToCSS(Env env)
- {
- env.Output.Push()
- .Append(this);
- return env.Output.Pop().ToString();
- }
-
+ public virtual string ToCSS(Env env)
+ {
+ return env.Output
+ .Push()
+ .Append(this)
+ .Pop()
+ .ToString();
+ }
+
public virtual Node Evaluate(Env env)
{
return this;
@@ -113,6 +117,33 @@ public bool IgnoreOutput()
return
this is RegexMatchResult ||
this is CharMatchResult;
- }
+ }
+
+ public virtual void Accept(IVisitor visitor) {}
+
+ /// <summary>
+ /// Visits the node and throw an exception if the replacement mode isn't the right type, or the replacement is null
+ /// </summary>
+ public T VisitAndReplace<T>(T nodeToVisit, IVisitor visitor) where T : Node
+ {
+ return VisitAndReplace(nodeToVisit, visitor, false);
+ }
+
+ /// <summary>
+ /// Visits the node and throw an exception if the replacement mode isn't the right type
+ /// The allowNull parameter determines if a null is allowed to be returned
+ /// </summary>
+ public T VisitAndReplace<T>(T nodeToVisit, IVisitor visitor, bool allowNull) where T : Node
+ {
+ Node replacement = visitor.Visit(nodeToVisit);
+
+ T typedReplacement = replacement as T;
+ if (typedReplacement != null || (allowNull && replacement == null))
+ {
+ return typedReplacement;
+ }
+
+ throw new Exception();
+ }
}
}
View
19 src/dotless.Core/Parser/Infrastructure/Nodes/NodeList.cs
@@ -50,6 +50,25 @@ public override void AppendCSS(Env env)
env.Output.AppendMany(Inner);
}
+ public override void Accept(Plugins.IVisitor visitor)
+ {
+ List<TNode> newInner = new List<TNode>(Inner.Count);
+
+ foreach (TNode inner in Inner)
+ {
+ TNode node = VisitAndReplace<TNode>(inner, visitor, true);
+
+ if (node == null)
+ {
+ continue;
+ }
+
+ newInner.Add(node);
+ }
+
+ Inner = newInner;
+ }
+
public void AddRange(IEnumerable<TNode> nodes)
{
Inner.AddRange(nodes);
View
12 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
@@ -50,8 +52,8 @@ public class Parser
private INodeProvider _nodeProvider;
public INodeProvider NodeProvider
- {
- get { return _nodeProvider??(new DefaultNodeProvider()); }
+ {
+ get { return _nodeProvider ?? (new DefaultNodeProvider()); }
set { _nodeProvider = value; }
}
@@ -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()
View
4 src/dotless.Core/Parser/Parsers.cs
@@ -875,8 +875,8 @@ public Rule Rule(Parser parser)
var preValueComments = GatherAndPullComments(parser);
- if ((name[0] != '@') && (parser.Tokenizer.Peek(@"([^@+\/*`(;{}'""-]*);")))
- value = parser.Tokenizer.Match(@"[^@+\/*`(;{}'""-]*");
+ if ((name[0] != '@') && (parser.Tokenizer.Peek(@"([^#@+\/*`(;{}'""-]*);")))
+ value = parser.Tokenizer.Match(@"[^#@+\/*`(;{}'""-]*");
else if (name == "font")
value = Font(parser);
else
View
6 src/dotless.Core/Parser/Tree/Call.cs
@@ -3,6 +3,7 @@ namespace dotless.Core.Parser.Tree
using System.Linq;
using Infrastructure;
using Infrastructure.Nodes;
+ using Plugins;
public class Call : Node
{
@@ -47,5 +48,10 @@ public override Node Evaluate(Env env)
return new TextNode(css.ToString()).ReducedFrom<Node>(this);
}
+
+ public override void Accept(IVisitor visitor)
+ {
+ Arguments = VisitAndReplace(Arguments, visitor);
+ }
}
}
View
4 src/dotless.Core/Parser/Tree/Color.cs
@@ -40,8 +40,8 @@ static Color()
Html4ColorsReverse = Html4Colors.ToDictionary(x => x.Value, x => x.Key);
}
- public readonly double[] RGB;
- public readonly double Alpha;
+ public readonly double[] RGB;
+ public double Alpha { get; set; }
public Color(double[] rgb) : this(rgb, 1)
{
View
6 src/dotless.Core/Parser/Tree/Element.cs
@@ -2,6 +2,7 @@
{
using Infrastructure;
using Infrastructure.Nodes;
+ using Plugins;
public class Element : Node
{
@@ -20,5 +21,10 @@ public override void AppendCSS(Env env)
.Append(Combinator)
.Append(Value);
}
+
+ public override void Accept(IVisitor visitor)
+ {
+ Combinator = VisitAndReplace(Combinator, visitor);
+ }
}
}
View
6 src/dotless.Core/Parser/Tree/Expression.cs
@@ -4,6 +4,7 @@
using Infrastructure.Nodes;
using System.Linq;
using System.Collections.Generic;
+ using Plugins;
public class Expression : Node
{
@@ -32,5 +33,10 @@ public override void AppendCSS(Env env)
{
env.Output.AppendMany(Value, " ");
}
+
+ public override void Accept(IVisitor visitor)
+ {
+ Value = VisitAndReplace(Value, visitor);
+ }
}
}
View
15 src/dotless.Core/Parser/Tree/MixinCall.cs
@@ -6,7 +6,8 @@ namespace dotless.Core.Parser.Tree
using Exceptions;
using Infrastructure;
using Infrastructure.Nodes;
- using Utils;
+ using Utils;
+ using Plugins;
public class MixinCall : Node
{
@@ -79,6 +80,16 @@ public override Node Evaluate(Env env)
}
return rules;
- }
+ }
+
+ public override void Accept(IVisitor visitor)
+ {
+ Selector = VisitAndReplace(Selector, visitor);
+
+ foreach (var a in Arguments)
+ {
+ a.Value = VisitAndReplace(a.Value, visitor);
+ }
+ }
}
}
View
9 src/dotless.Core/Parser/Tree/Operation.cs
@@ -3,7 +3,8 @@
using System;
using Exceptions;
using Infrastructure;
- using Infrastructure.Nodes;
+ using Infrastructure.Nodes;
+ using Plugins;
public class Operation : Node
{
@@ -71,6 +72,12 @@ public static double Operate(string op, double first, double second)
default:
throw new InvalidOperationException("Unknown operator");
}
+ }
+
+ public override void Accept(IVisitor visitor)
+ {
+ First = VisitAndReplace(First, visitor);
+ Second = VisitAndReplace(Second, visitor);
}
}
}
View
54 src/dotless.Core/Parser/Tree/Root.cs
@@ -1,11 +1,13 @@
namespace dotless.Core.Parser.Tree
{
using System;
- using System.Collections.Generic;
+ using System.Collections.Generic;
+ using System.Linq;
using System.Text.RegularExpressions;
using Exceptions;
using Infrastructure;
- using Infrastructure.Nodes;
+ using Infrastructure.Nodes;
+ using Plugins;
using dotless.Core.Utils;
public class Root : Ruleset
@@ -36,18 +38,56 @@ public override void AppendCSS(Env env)
{
throw Error(e);
}
+ }
+
+ private Root DoVisiting(Root node, Env env, VisitorPluginType pluginType)
+ {
+ return env.VisitorPlugins
+ .Where(p => p.AppliesTo == pluginType)
+ .Aggregate(node, (current, plugin) =>
+ {
+ try
+ {
+ plugin.OnPreVisiting(env);
+ Root r = plugin.Apply(current);
+ plugin.OnPostVisiting(env);
+ return r;
+ }
+ catch (Exception ex)
+ {
+ string message = string.Format("Plugin '{0}' failed during visiting with error '{1}'", plugin.GetName(), ex.Message);
+ throw new ParserException(message, ex);
+ }
+ });
+
}
public override Node Evaluate(Env env)
{
- env = env ?? new Env();
+ if(Evaluated) return this;
- NodeHelper.ExpandNodes<Import>(env, this.Rules);
+ try
+ {
+ env = env ?? new Env();
- Root clone = new Root(new NodeList(Rules), Error, OriginalRuleset).ReducedFrom<Root>(this);
- clone.EvaluateRules(env);
+ NodeHelper.ExpandNodes<Import>(env, Rules);
+
+ var clone = new Root(new NodeList(Rules), Error, OriginalRuleset);
+
+ clone = DoVisiting(clone, env, VisitorPluginType.BeforeEvaluation);
+
+ clone.ReducedFrom<Root>(this);
+ clone.EvaluateRules(env);
+ clone.Evaluated = true;
+
+ clone = DoVisiting(clone, env, VisitorPluginType.AfterEvaluation);
- return clone;
+ return clone;
+ }
+ catch (ParsingException e)
+ {
+ throw Error(e);
+ }
}
}
}
View
106 src/dotless.Core/Parser/Tree/Rule.cs
@@ -1,51 +1,57 @@
-namespace dotless.Core.Parser.Tree
-{
- using Exceptions;
- using Infrastructure;
- using Infrastructure.Nodes;
-
- public class Rule : Node
- {
- public string Name { get; set; }
- public Node Value { get; set; }
- public bool Variable { get; set; }
- public NodeList PostNameComments { get; set; }
-
- public Rule(string name, Node value)
- {
- Name = name;
- Value = value;
- Variable = name != null ? name[0] == '@' : false;
- }
-
- public override Node Evaluate(Env env)
- {
- env.Rule = this;
-
- if (Value == null)
- {
- throw new ParsingException("No value found for rule " + Name, Index);
- }
-
- var rule = new Rule(Name, Value.Evaluate(env)).ReducedFrom<Rule>(this);
- rule.PostNameComments = this.PostNameComments;
-
- env.Rule = null;
-
- return rule;
- }
-
- public override void AppendCSS(Env env)
- {
- if (Variable)
- return;
-
- env.Output
- .Append(Name)
- .Append(PostNameComments)
- .Append(env.Compress ? ":" : ": ")
- .Append(Value)
- .Append(";");
- }
- }
+namespace dotless.Core.Parser.Tree
+{
+ using Exceptions;
+ using Infrastructure;
+ using Infrastructure.Nodes;
+ using Plugins;
+
+ public class Rule : Node
+ {
+ public string Name { get; set; }
+ public Node Value { get; set; }
+ public bool Variable { get; set; }
+ public NodeList PostNameComments { get; set; }
+
+ public Rule(string name, Node value)
+ {
+ Name = name;
+ Value = value;
+ Variable = name != null ? name[0] == '@' : false;
+ }
+
+ public override Node Evaluate(Env env)
+ {
+ env.Rule = this;
+
+ if (Value == null)
+ {
+ throw new ParsingException("No value found for rule " + Name, Index);
+ }
+
+ var rule = new Rule(Name, Value.Evaluate(env)).ReducedFrom<Rule>(this);
+ rule.PostNameComments = this.PostNameComments;
+
+ env.Rule = null;
+
+ return rule;
+ }
+
+ public override void AppendCSS(Env env)
+ {
+ if (Variable)
+ return;
+
+ env.Output
+ .Append(Name)
+ .Append(PostNameComments)
+ .Append(env.Compress ? ":" : ": ")
+ .Append(Value)
+ .Append(";");
+ }
+
+ public override void Accept(IVisitor visitor)
+ {
+ Value = VisitAndReplace(Value, visitor);
+ }
+ }
}
View
32 src/dotless.Core/Parser/Tree/Ruleset.cs
@@ -2,15 +2,17 @@ namespace dotless.Core.Parser.Tree
{
using System.Collections.Generic;
using System.Linq;
+ using System.Text;
using Infrastructure;
using Infrastructure.Nodes;
using Utils;
- using System.Text;
+ using Plugins;
public class Ruleset : Node
{
public NodeList<Selector> Selectors { get; set; }
- public NodeList Rules { get; set; }
+ public NodeList Rules { get; set; }
+ public bool Evaluated { get; protected set; }
/// <summary>
/// The original Ruleset this was cloned from during evaluation
@@ -120,13 +122,24 @@ public virtual bool MatchArguments(List<NamedArgument> arguements, Env env)
public override Node Evaluate(Env env)
{
+ if(Evaluated) return this;
+
// create a clone so it is non destructive
- Ruleset clone = new Ruleset(Selectors, new NodeList(Rules), this.OriginalRuleset)
- .ReducedFrom<Ruleset>(this);
- clone.EvaluateRules(env);
+ var clone = new Ruleset(Selectors, new NodeList(Rules), OriginalRuleset).ReducedFrom<Ruleset>(this);
+
+ clone.EvaluateRules(env);
+ clone.Evaluated = true;
+
return clone;
}
+ public override void Accept(IVisitor visitor)
+ {
+ Selectors = VisitAndReplace(Selectors, visitor);
+
+ Rules = VisitAndReplace(Rules, visitor);
+ }
+
/// <summary>
/// Evaluate Rules. Must only be run on a copy of the ruleset otherwise it will
/// overwrite defintions that might be required later..
@@ -135,7 +148,7 @@ protected void EvaluateRules(Env env)
{
env.Frames.Push(this);
- NodeHelper.ExpandNodes<MixinCall>(env, this.Rules);
+ NodeHelper.ExpandNodes<MixinCall>(env, Rules);
for (var i = 0; i < Rules.Count; i++)
{
@@ -148,10 +161,9 @@ protected void EvaluateRules(Env env)
public override void AppendCSS(Env env)
{
if (!Rules.Any())
- return;
-
- ((Ruleset)Evaluate(env))
- .AppendCSS(env, new Context());
+ return;
+
+ ((Ruleset) Evaluate(env)).AppendCSS(env, new Context());
}
/// <summary>
View
6 src/dotless.Core/Parser/Tree/Selector.cs
@@ -3,6 +3,7 @@
using Infrastructure;
using Infrastructure.Nodes;
using System.Collections.Generic;
+ using Plugins;
public class Selector : Node
{
@@ -44,6 +45,11 @@ public override void AppendCSS(Env env)
env.Output.Append(_css);
}
+ public override void Accept(IVisitor visitor)
+ {
+ Elements = VisitAndReplace(Elements, visitor);
+ }
+
public override string ToString()
{
return ToCSS(new Env());
View
7 src/dotless.Core/Parser/Tree/Shorthand.cs
@@ -2,6 +2,7 @@
{
using Infrastructure;
using Infrastructure.Nodes;
+ using Plugins;
public class Shorthand : Node
{
@@ -21,5 +22,11 @@ public override void AppendCSS(Env env)
.Append("/")
.Append(Second);
}
+
+ public override void Accept(IVisitor visitor)
+ {
+ First = VisitAndReplace(First, visitor);
+ Second = VisitAndReplace(Second, visitor);
+ }
}
}
View
6 src/dotless.Core/Parser/Tree/Url.cs
@@ -7,6 +7,7 @@
using Infrastructure.Nodes;
using Utils;
using Exceptions;
+ using Plugins;
using dotless.Core.Importers;
public class Url : Node
@@ -52,5 +53,10 @@ public override void AppendCSS(Env env)
.Append(Value)
.Append(")");
}
+
+ public override void Accept(IVisitor visitor)
+ {
+ Value = VisitAndReplace(Value, visitor);
+ }
}
}
View
120 src/dotless.Core/Parser/Tree/Value.cs
@@ -1,58 +1,64 @@
-namespace dotless.Core.Parser.Tree
-{
- using System.Collections.Generic;
- using System.Linq;
- using Infrastructure;
- using Infrastructure.Nodes;
-
- public class Value : Node
- {
- public NodeList Values { get; set; }
- public NodeList PreImportantComments { get; set; }
- public string Important { get; set; }
-
- public Value(IEnumerable<Node> values, string important)
- {
- Values = new NodeList(values);
- Important = important;
- }
-
- public override void AppendCSS(Env env)
- {
- env.Output.AppendMany(Values, env.Compress ? "," : ", ");
-
- if (!string.IsNullOrEmpty(Important))
- {
- if (PreImportantComments)
- {
- env.Output.Append(PreImportantComments);
- }
-
- env.Output
- .Append(" ")
- .Append(Important);
- }
- }
-
- public override string ToString()
- {
- return ToCSS(new Env()); // only used during debugging.
- }
-
- public override Node Evaluate(Env env)
- {
- Node returnNode = null;
- Value value;
-
- if (Values.Count == 1 && string.IsNullOrEmpty(Important))
- returnNode = Values[0].Evaluate(env);
- else
- {
- returnNode = value = new Value(Values.Select(n => n.Evaluate(env)), Important);
- value.PreImportantComments = this.PreImportantComments;
- }
-
- return returnNode.ReducedFrom<Node>(this);
- }
- }
+namespace dotless.Core.Parser.Tree
+{
+ using System.Collections.Generic;
+ using System.Linq;
+ using Infrastructure;
+ using Infrastructure.Nodes;
+ using Plugins;
+
+ public class Value : Node
+ {
+ public NodeList Values { get; set; }
+ public NodeList PreImportantComments { get; set; }
+ public string Important { get; set; }
+
+ public Value(IEnumerable<Node> values, string important)
+ {
+ Values = new NodeList(values);
+ Important = important;
+ }
+
+ public override void AppendCSS(Env env)
+ {
+ env.Output.AppendMany(Values, env.Compress ? "," : ", ");
+
+ if (!string.IsNullOrEmpty(Important))
+ {
+ if (PreImportantComments)
+ {
+ env.Output.Append(PreImportantComments);
+ }
+
+ env.Output
+ .Append(" ")
+ .Append(Important);
+ }
+ }
+
+ public override string ToString()
+ {
+ return ToCSS(new Env()); // only used during debugging.
+ }
+
+ public override Node Evaluate(Env env)
+ {
+ Node returnNode = null;
+ Value value;
+
+ if (Values.Count == 1 && string.IsNullOrEmpty(Important))
+ returnNode = Values[0].Evaluate(env);
+ else
+ {
+ returnNode = value = new Value(Values.Select(n => n.Evaluate(env)), Important);
+ value.PreImportantComments = this.PreImportantComments;
+ }
+
+ return returnNode.ReducedFrom<Node>(this);
+ }
+
+ public override void Accept(IVisitor visitor)
+ {
+ Values = VisitAndReplace(Values, visitor);
+ }
+ }
}
View
45 src/dotless.Core/Plugins/ColorSpinPlugin.cs
@@ -0,0 +1,45 @@
+namespace dotless.Core.Plugins
+{
+ using System;
+ using Parser.Infrastructure.Nodes;
+ using Parser.Tree;
+ using Utils;
+ using System.ComponentModel;
+
+ [Description("Automatically spins all colors in a less file"), DisplayName("ColorSpin")]
+ public class ColorSpinPlugin : VisitorPlugin
+ {
+ public double Spin { get; set; }
+
+ public ColorSpinPlugin(double spin)
+ {
+ Spin = spin;
+ }
+
+ public override VisitorPluginType AppliesTo
+ {
+ get { return VisitorPluginType.AfterEvaluation; }
+ }
+
+ public override Node Execute(Node node, out bool visitDeeper)
+ {
+ visitDeeper = true;
+
+ if(node is Color)
+ {
+ var color = node as Color;
+
+ var hslColor = HslColor.FromRgbColor(color);
+ hslColor.Hue += Spin/360.0d;
+ var newColor = hslColor.ToRgbColor();
+
+ //node = new Color(newColor.R, newColor.G, newColor.B);
+ color.R = newColor.R;
+ color.G = newColor.G;
+ color.B = newColor.B;
+ }
+
+ return node;
+ }
+ }
+}
View
140 src/dotless.Core/Plugins/GenericPluginConfigurator.cs
@@ -0,0 +1,140 @@
+namespace dotless.Core.Plugins
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using System.Reflection;
+ using System.ComponentModel;
+
+ public class GenericPluginConfigurator<T> : IPluginConfigurator where T : IPlugin
+ {
+ public string Name
+ {
+ get
+ {
+ return PluginFinder.GetName(typeof(T));
+ }
+ }
+
+ public string Description
+ {
+ get
+ {
+ return PluginFinder.GetDescription(typeof(T));
+ }
+ }
+
+ public Type Configurates
+ {
+ get
+ {
+ return typeof(T);
+ }
+ }
+
+ private Func<IPlugin> _pluginCreator = null;
+ public void SetParameterValues(IEnumerable<IPluginParameter> pluginParameters)
+ {
+ ConstructorInfo defaultConstructor;
+ ConstructorInfo parameterConstructor;
+ GetConstructorInfos(out parameterConstructor, out defaultConstructor);
+
+ if (pluginParameters == null || pluginParameters.Count() == 0 || pluginParameters.All(parameter => parameter.Value == null))
+ {
+ if (defaultConstructor == null)
+ {
+ throw new Exception("No parameters provided but no default constructor");
+ }
+ _pluginCreator = () => (T)defaultConstructor.Invoke(new object[] { });
+ }
+ else
+ {
+ var constructorArguments = parameterConstructor.GetParameters()
+ .OrderBy(parameter => parameter.Position)
+ .Select(parameter =>
+ {
+ var p = pluginParameters.FirstOrDefault(pluginParameter => pluginParameter.Name == parameter.Name);
+ if (p == null)
+ {
+ if (parameter.ParameterType.IsValueType)
+ {
+ return Activator.CreateInstance(parameter.ParameterType);
+ }
+ return null;
+ }
+ return p.Value;
+ })
+ .ToArray();
+
+ _pluginCreator = () => (T)parameterConstructor.Invoke(constructorArguments);
+ }
+ }
+
+ public IPlugin CreatePlugin()
+ {
+ if (_pluginCreator == null)
+ {
+ SetParameterValues(null);
+ }
+
+ return _pluginCreator();
+ }
+
+ private class ConstructorParameterSet
+ {
+ public ParameterInfo[] Parameter { get; set; }
+ public int Count { get; set; }
+ }
+
+ private void GetConstructorInfos(out ConstructorInfo parameterConstructor, out ConstructorInfo defaultConstructor)
+ {
+ List<ConstructorInfo> constructors = typeof(T).GetConstructors()
+ .Where(constructorInfo => constructorInfo.IsPublic && !constructorInfo.IsStatic).ToList();
+
+ if (constructors.Count > 2 || constructors.Count == 0)
+ {
+ 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)
+ {
+ defaultConstructor = constructors[0];
+ parameterConstructor = constructors[1];
+ } else if (constructors[1].GetParameters().Length == 0)
+ {
+ defaultConstructor = constructors[1];
+ parameterConstructor = constructors[0];
+ } else
+ {
+ 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)
+ {
+ defaultConstructor = constructors[0];
+ parameterConstructor = null;
+ }
+ else
+ {
+ defaultConstructor = null;
+ parameterConstructor = constructors[0];
+ }
+ }
+ }
+
+ public IEnumerable<IPluginParameter> GetParameters()
+ {
+ ConstructorInfo defaultConstructor;
+ ConstructorInfo parameterConstructor;
+ GetConstructorInfos(out parameterConstructor, out defaultConstructor);
+ if (parameterConstructor == null)
+ {
+ return new List<IPluginParameter>();
+ }
+
+ return parameterConstructor.GetParameters().Select(parameter => (IPluginParameter)new PluginParameter(
+ parameter.Name, parameter.ParameterType, defaultConstructor == null)).ToList();
+ }
+ }
+}
View
11 src/dotless.Core/Plugins/IFunctionPlugin.cs
@@ -0,0 +1,11 @@
+namespace dotless.Core.Plugins
+{
+ using System;
+ using System.Collections.Generic;
+ using dotless.Core.Parser.Infrastructure;
+
+ public interface IFunctionPlugin : IPlugin
+ {
+ Dictionary<string, Type> GetFunctions();
+ }
+}
View
6 src/dotless.Core/Plugins/IPlugin.cs
@@ -0,0 +1,6 @@
+namespace dotless.Core.Plugins
+{
+ public interface IPlugin
+ {
+ }
+}
View
20 src/dotless.Core/Plugins/IPluginConfigurator.cs
@@ -0,0 +1,20 @@
+namespace dotless.Core.Plugins
+{
+ using System;
+ using System.Collections.Generic;
+
+ public interface IPluginConfigurator
+ {
+ IPlugin CreatePlugin();
+
+ IEnumerable<IPluginParameter> GetParameters();
+
+ void SetParameterValues(IEnumerable<IPluginParameter> parameters);
+
+ string Name { get; }
+
+ string Description { get; }
+
+ Type Configurates { get; }
+ }
+}
View
17 src/dotless.Core/Plugins/IPluginParameter.cs
@@ -0,0 +1,17 @@
+namespace dotless.Core.Plugins
+{
+ using System;
+
+ public interface IPluginParameter
+ {
+ string Name { get; }
+
+ bool IsMandatory { get; }
+
+ object Value { get; }
+
+ string TypeDescription { get; }
+
+ void SetValue(string value);
+ }
+}
View
9 src/dotless.Core/Plugins/IVisitor.cs
@@ -0,0 +1,9 @@
+namespace dotless.Core.Plugins
+{
+ using Parser.Infrastructure.Nodes;
+
+ public interface IVisitor
+ {
+ Node Visit(Node node);
+ }
+}
View
21 src/dotless.Core/Plugins/IVisitorPlugin.cs
@@ -0,0 +1,21 @@
+namespace dotless.Core.Plugins
+{
+ using Parser.Tree;
+ using dotless.Core.Parser.Infrastructure;
+
+ public interface IVisitorPlugin : IPlugin
+ {
+ Root Apply(Root tree);
+
+ VisitorPluginType AppliesTo { get; }
+
+ void OnPreVisiting(Env env);
+ void OnPostVisiting(Env env);
+ }
+
+ public enum VisitorPluginType
+ {
+ BeforeEvaluation,
+ AfterEvaluation
+ }
+}
View
114 src/dotless.Core/Plugins/PluginFinder.cs
@@ -0,0 +1,114 @@
+namespace dotless.Core.Plugins
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using System.Reflection;
+ using System.IO;
+ using System.ComponentModel;
+
+ public static class PluginFinder
+ {
+ /// <summary>
+ /// Gets a plugins name
+ /// </summary>
+ public static string GetName(this IPlugin plugin)
+ {
+ return GetName(plugin.GetType());
+ }
+
+ /// <summary>
+ /// Gets a plugins description
+ /// </summary>
+ public static string GetDescription(this IPlugin plugin)
+ {
+ return GetName(plugin.GetType());
+ }
+
+ /// <summary>
+ /// Gets a plugins description from its type
+ /// </summary>
+ public static string GetDescription(Type pluginType)
+ {
+ DescriptionAttribute description = pluginType
+ .GetCustomAttributes(typeof(DescriptionAttribute), true)
+ .FirstOrDefault() as DescriptionAttribute;
+
+ if (description != null)
+ return description.Description;
+ else
+ return "No Description";
+ }
+
+ /// <summary>
+ /// Gets a plugins name from its type
+ /// </summary>
+ public static string GetName(Type pluginType)
+ {
+ DisplayNameAttribute name = pluginType
+ .GetCustomAttributes(typeof(DisplayNameAttribute), true)
+ .FirstOrDefault() as DisplayNameAttribute;
+
+ if (name != null)
+ return name.DisplayName;
+ else
+ return pluginType.Name;
+ }
+
+ /// <summary>
+ /// Gets plugin configurators for all plugins, optionally scanning referenced assemblies and
+ /// a plugins folder underneath the executing assembly
+ /// </summary>
+ /// <param name="scanPluginsFolder">Look for a plugins folder and if exists, load plugins from it</param>
+ /// <returns></returns>
+ public static IEnumerable<IPluginConfigurator> GetConfigurators(bool scanPluginsFolder)
+ {
+ List<IEnumerable<IPluginConfigurator>> pluginConfigurators = new List<IEnumerable<IPluginConfigurator>>();
+
+ pluginConfigurators.Add(GetConfigurators(Assembly.GetAssembly(typeof(PluginFinder))));
+
+ if (scanPluginsFolder)
+ {
+ string pluginsFolder = Path.Combine(Assembly.GetEntryAssembly().Location, "plugins");
+
+ if (Directory.Exists(pluginsFolder))
+ {
+ DirectoryInfo pluginsFolderDirectoryInfo = new DirectoryInfo(pluginsFolder);
+ foreach(FileInfo pluginAssembly in pluginsFolderDirectoryInfo.GetFiles("*.dll"))
+ {
+ pluginConfigurators.Add(GetConfigurators(Assembly.LoadFile(pluginAssembly.FullName)));
+ }
+ }
+ }
+
+ return pluginConfigurators.Aggregate((group1, group2) => group1.Union(group2));
+ }
+
+ /// <summary>
+ /// Gets plugin configurators for every plugin in an assembly
+ /// </summary>
+ /// <param name="assembly"></param>
+ /// <returns></returns>
+ public static IEnumerable<IPluginConfigurator> GetConfigurators(Assembly assembly)
+ {
+ IEnumerable<Type> types = assembly.GetTypes().Where(
+ type => !type.IsAbstract && !type.IsGenericType && !type.IsInterface);
+
+ IEnumerable<IPluginConfigurator> pluginConfigurators = types
+ .Where(type => typeof(IPluginConfigurator).IsAssignableFrom(type))
+ .Select(type => (IPluginConfigurator)type.GetConstructor(new Type[] {}).Invoke(new object[]{}));
+
+ IEnumerable<Type> pluginsConfigurated = pluginConfigurators.Select(pluginConfigurator => pluginConfigurator.Configurates);
+
+ Type genericPluginConfiguratorType = typeof(GenericPluginConfigurator<>);
+
+ IEnumerable<IPluginConfigurator> plugins = types
+ .Where(type => typeof(IPlugin).IsAssignableFrom(type))
+ .Where(type => !pluginsConfigurated.Contains(type))
+ .Select(type => (IPluginConfigurator)Activator.CreateInstance(genericPluginConfiguratorType.MakeGenericType(type)));
+
+ return plugins.Union(pluginConfigurators);
+ }
+ }
+}
View
64 src/dotless.Core/Plugins/PluginParameter.cs
@@ -0,0 +1,64 @@
+namespace dotless.Core.Plugins
+{
+ using System;
+
+ public class PluginParameter : IPluginParameter
+ {
+ public PluginParameter(string name, Type type, bool isMandatory)
+ {
+ Name = name;
+ IsMandatory = isMandatory;
+ Type = type;
+ }
+
+ public string Name
+ {
+ get;
+ private set;
+ }
+
+ public bool IsMandatory
+ {
+ get;
+ private set;
+ }
+
+ public object Value
+ {
+ get;
+ private set;
+ }
+
+ private Type Type
+ {
+ get;
+ set;
+ }
+
+ public string TypeDescription
+ {
+ get { return Type.Name; }
+ }
+
+ public void SetValue(string stringValue)
+ {
+ 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);
+ }
+ }
+ }
+}
View
282 src/dotless.Core/Plugins/RtlPlugin.cs
@@ -0,0 +1,282 @@
+namespace dotless.Core.Plugins
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using dotless.Core.Parser.Infrastructure.Nodes;
+ using dotless.Core.Parser.Tree;
+ using System.Globalization;
+ 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()