diff --git a/src/WebCompiler/Compile/SassCompiler.cs b/src/WebCompiler/Compile/SassCompiler.cs index b25208cd..27134df9 100644 --- a/src/WebCompiler/Compile/SassCompiler.cs +++ b/src/WebCompiler/Compile/SassCompiler.cs @@ -33,6 +33,9 @@ public CompilerResult Compile(Config config) OriginalContent = content, }; + if (config.GlobalMatch && string.IsNullOrWhiteSpace(content) || Path.GetFileName(config.InputFile).StartsWith("_")) + return result; + try { RunCompilerProcess(config, info); diff --git a/src/WebCompiler/Config/Config.cs b/src/WebCompiler/Config/Config.cs index c2465e23..7ddd7038 100644 --- a/src/WebCompiler/Config/Config.cs +++ b/src/WebCompiler/Config/Config.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using Newtonsoft.Json; +using WebCompiler.Helpers; namespace WebCompiler { @@ -56,6 +57,8 @@ public class Config internal string Output { get; set; } + internal bool GlobalMatch { get; set; } + /// /// Converts the relative input file to an absolute file path. /// @@ -188,5 +191,49 @@ private static bool DictionaryEqual( } return true; } + + internal Config Match(string folder, string sourceFile) + { + if (sourceFile == null) + return null; + + string inputFile = Path.Combine(folder, this.InputFile); + + if (!GlobHelper.IsGlobPattern(this.InputFile)) + return sourceFile.Equals(inputFile.Replace('/', '\\'), System.StringComparison.OrdinalIgnoreCase) + ? this : null; + + if (GlobHelper.Glob(sourceFile, inputFile)) + return MakeMatchedConfig(sourceFile); + + return null; + } + + Config MakeMatchedConfig(string sourceFile) + { + string compileExtension = CompileHelper.GetCompiledExtension(sourceFile); + return new Config() + { + InputFile = sourceFile, + OutputFile = Path.ChangeExtension(sourceFile, compileExtension), + FileName = this.FileName, + IncludeInProject = this.IncludeInProject, + Minify = this.Minify, + Options = this.Options, + Output = this.Output, + SourceMap = this.SourceMap + }; + } + + internal IEnumerable Match(string folder) + { + return Directory.EnumerateFiles(folder, this.InputFile, SearchOption.AllDirectories) + .Select(s => + { + Config config = MakeMatchedConfig(s); + config.GlobalMatch = true; + return config; + }); + } } -} +} \ No newline at end of file diff --git a/src/WebCompiler/Config/ConfigFileProcessor.cs b/src/WebCompiler/Config/ConfigFileProcessor.cs index aedc8dda..5fa719a6 100644 --- a/src/WebCompiler/Config/ConfigFileProcessor.cs +++ b/src/WebCompiler/Config/ConfigFileProcessor.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Text; +using WebCompiler.Helpers; namespace WebCompiler { @@ -21,7 +22,7 @@ public class ConfigFileProcessor /// Optional configuration items in the config file /// Forces compilation of all config items. /// A list of compiler results. - public IEnumerable Process(string configFile, IEnumerable configs = null, bool force = false) + public IEnumerable Process(string configFile, Config[] configs = null, bool force = false) { if (_processing.Contains(configFile)) return Enumerable.Empty(); @@ -32,18 +33,30 @@ public IEnumerable Process(string configFile, IEnumerable 0) + OnConfigProcessed(configs.First(), 0, configs.Length); + int i = 0; foreach (Config config in configs) { + ++i; + if (force || config.CompilationRequired()) { - var result = ProcessConfig(info.Directory.FullName, config); - list.Add(result); - OnConfigProcessed(config, list.Count, configs.Count()); + if (GlobHelper.IsGlobPattern(config.InputFile)) + { + foreach (Config matchedConfig in config.Match(directory)) + list.Add(ProcessConfig(directory, matchedConfig)); + } + else + { + list.Add(ProcessConfig(directory, config)); + } + + OnConfigProcessed(config, i, configs.Length); } } } @@ -112,12 +125,11 @@ private IEnumerable SourceFileChanged(string configFile, // Compile if the file if it's referenced directly in compilerconfig.json foreach (Config config in configs) { - string input = Path.Combine(folder, config.InputFile.Replace("/", "\\")); - - if (input.Equals(sourceFile, StringComparison.OrdinalIgnoreCase)) + Config matchingConfig = config.Match(folder, sourceFile); + if (matchingConfig != null) { - list.Add(ProcessConfig(folder, config)); - compiledFiles.Add(input.ToLowerInvariant()); + list.Add(ProcessConfig(folder, matchingConfig)); + compiledFiles.Add(matchingConfig.InputFile.ToLowerInvariant()); } } @@ -191,7 +203,7 @@ private CompilerResult ProcessConfig(string baseFolder, Config config) var result = compiler.Compile(config); - if (result.Errors.Any(e => !e.IsWarning)) + if (result.Errors.Any(e => !e.IsWarning) || string.IsNullOrWhiteSpace(result.CompiledContent)) return result; if (Path.GetExtension(config.OutputFile).Equals(".css", StringComparison.OrdinalIgnoreCase) && AdjustRelativePaths(config)) diff --git a/src/WebCompiler/Config/ConfigHandler.cs b/src/WebCompiler/Config/ConfigHandler.cs index 5b827bde..7ee7d47d 100644 --- a/src/WebCompiler/Config/ConfigHandler.cs +++ b/src/WebCompiler/Config/ConfigHandler.cs @@ -95,16 +95,15 @@ public void CreateDefaultsFile(string fileName) /// /// A relative or absolute file path to the configuration file. /// A list of Config objects. - public static IEnumerable GetConfigs(string fileName) + public static Config[] GetConfigs(string fileName) { FileInfo file = new FileInfo(fileName); if (!file.Exists) - return Enumerable.Empty(); + return new Config[0]; string content = File.ReadAllText(fileName); - var configs = JsonConvert.DeserializeObject>(content); - string folder = Path.GetDirectoryName(file.FullName); + Config[] configs = JsonConvert.DeserializeObject(content); foreach (Config config in configs) { diff --git a/src/WebCompiler/Helpers/CompileHelper.cs b/src/WebCompiler/Helpers/CompileHelper.cs new file mode 100644 index 00000000..9e0b8852 --- /dev/null +++ b/src/WebCompiler/Helpers/CompileHelper.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WebCompiler.Helpers +{ + public static class CompileHelper + { + /// + /// + /// + /// + /// + public static string GetCompiledExtension(string filename) + { + string extension = Path.GetExtension(filename).ToLowerInvariant(); + switch (extension) + { + case ".coffee": + case ".iced": + case ".litcoffee": + case ".jsx": + case ".es6": + case ".hbs": + case ".handlebars": + return ".js"; + + case ".js": + return ".es5.js"; + + default: + return ".css"; + } + } + } +} diff --git a/src/WebCompiler/Helpers/GlobHelper.cs b/src/WebCompiler/Helpers/GlobHelper.cs new file mode 100644 index 00000000..41039797 --- /dev/null +++ b/src/WebCompiler/Helpers/GlobHelper.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace WebCompiler.Helpers +{ + /// + /// Utils for glob matching + /// + public static class GlobHelper + { + /// + /// Returns true if the input text contains a basic glob character: *? + /// + /// + /// + public static bool IsGlobPattern(string text) + { + return text != null && text.LastIndexOfAny(new char[] { '*', '?' }) >= 0; + } + + /// + /// String matching including basic glob patterns: *? + /// + /// string to be matched + /// pattern to match against + /// + public static bool Glob(this string text, string pattern) + { + StringBuilder sb = new StringBuilder(pattern, pattern.Length + 10); + sb.Replace('*', (char)1).Replace('?', (char)2).Replace('/', '\\'); + + pattern = Regex.Escape(sb.ToString()); + + sb.Clear().Append('^').Append(pattern).Replace("\u0001", ".*").Replace("\u0002", ".").Append('$'); + + return Regex.IsMatch(text, sb.ToString(), RegexOptions.IgnoreCase); + } + } +} \ No newline at end of file diff --git a/src/WebCompiler/Program.cs b/src/WebCompiler/Program.cs index 068d49db..e06cafe7 100644 --- a/src/WebCompiler/Program.cs +++ b/src/WebCompiler/Program.cs @@ -50,7 +50,7 @@ private static void EventHookups(ConfigFileProcessor processor, string configPat FileMinifier.AfterWritingGzipFile += (s, e) => { Console.WriteLine($" \x1B[32mGZipped"); }; } - private static IEnumerable GetConfigs(string configPath, string file) + private static Config[] GetConfigs(string configPath, string file) { var configs = ConfigHandler.GetConfigs(configPath); @@ -60,9 +60,9 @@ private static IEnumerable GetConfigs(string configPath, string file) if (file != null) { if (file.StartsWith("*")) - configs = configs.Where(c => Path.GetExtension(c.InputFile).Equals(file.Substring(1), StringComparison.OrdinalIgnoreCase)); + configs = configs.Where(c => Path.GetExtension(c.InputFile).Equals(file.Substring(1), StringComparison.OrdinalIgnoreCase)).ToArray(); else - configs = configs.Where(c => c.InputFile.Equals(file, StringComparison.OrdinalIgnoreCase)); + configs = configs.Where(c => c.InputFile.Equals(file, StringComparison.OrdinalIgnoreCase)).ToArray(); } return configs; diff --git a/src/WebCompilerTest/Config/ConfigHandlerTest.cs b/src/WebCompilerTest/Config/ConfigHandlerTest.cs index eb809266..7109c69e 100644 --- a/src/WebCompilerTest/Config/ConfigHandlerTest.cs +++ b/src/WebCompilerTest/Config/ConfigHandlerTest.cs @@ -45,11 +45,9 @@ public void AddConfig() [TestMethod, TestCategory("Config")] public void NonExistingConfigFileShouldReturnEmptyList() { - var expectedResult = Enumerable.Empty(); - var result = ConfigHandler.GetConfigs("../NonExistingFile.config"); - Assert.AreEqual(expectedResult, result); + Assert.AreEqual(0, result.Length); } } } diff --git a/src/WebCompilerTest/Minify/artifacts/css/minify.less b/src/WebCompilerTest/Minify/artifacts/css/minify.less index 46800d16..64d2da4f 100644 --- a/src/WebCompilerTest/Minify/artifacts/css/minify.less +++ b/src/WebCompilerTest/Minify/artifacts/css/minify.less @@ -1,2 +1,3 @@ body { + background: black } diff --git a/src/WebCompilerVsix/Adornments/AdornmentProvider.cs b/src/WebCompilerVsix/Adornments/AdornmentProvider.cs index 11ba935b..cf6bfa61 100644 --- a/src/WebCompilerVsix/Adornments/AdornmentProvider.cs +++ b/src/WebCompilerVsix/Adornments/AdornmentProvider.cs @@ -9,6 +9,7 @@ using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Utilities; +using WebCompiler.Helpers; namespace WebCompilerVsix { @@ -101,6 +102,11 @@ private void CreateAdornments(ITextDocument document, IWpfTextView textView) foreach (Config config in configs) { + if (GlobHelper.IsGlobPattern(config.InputFile)) + { + continue; + } + if (config.GetAbsoluteOutputFile().FullName.Equals(normalizedFilePath, StringComparison.OrdinalIgnoreCase)) { GeneratedAdornment generated = new GeneratedAdornment(textView, _isVisible, _initOpacity); diff --git a/src/WebCompilerVsix/Commands/CreateConfig.cs b/src/WebCompilerVsix/Commands/CreateConfig.cs index b01b632f..d32c3998 100644 --- a/src/WebCompilerVsix/Commands/CreateConfig.cs +++ b/src/WebCompilerVsix/Commands/CreateConfig.cs @@ -7,6 +7,7 @@ using EnvDTE80; using Microsoft.VisualStudio.Shell; using WebCompiler; +using WebCompiler.Helpers; namespace WebCompilerVsix { @@ -163,16 +164,7 @@ private static Config CreateConfigFile(string inputfile, string outputFile) private static string GetOutputFileName(string inputFile) { - string extension = Path.GetExtension(inputFile).ToLowerInvariant(); - string ext = ".css"; - - if (extension == ".coffee" || extension == ".iced" || extension == ".litcoffee" || extension == ".jsx" || extension == ".es6" || extension == ".hbs" || extension == ".handlebars") - ext = ".js"; - - if (extension == ".js") - ext = ".es5.js"; - - return Path.ChangeExtension(inputFile, ext); + return Path.ChangeExtension(inputFile, CompileHelper.GetCompiledExtension(inputFile)); } } } diff --git a/src/WebCompilerVsix/CompilerService.cs b/src/WebCompilerVsix/CompilerService.cs index 4246b9c2..d876a27c 100644 --- a/src/WebCompilerVsix/CompilerService.cs +++ b/src/WebCompilerVsix/CompilerService.cs @@ -51,7 +51,7 @@ private static void ConfigProcessed(object sender, ConfigProcessedEventArgs e) _dte.StatusBar.Progress(true, "Compiling...", e.AmountProcessed, e.Total); } - public static void Process(string configFile, IEnumerable configs = null, bool force = false) + public static void Process(string configFile, Config[] configs = null, bool force = false) { ThreadPool.QueueUserWorkItem((o) => {