diff --git a/src/WebCompiler/Config/Config.cs b/src/WebCompiler/Config/Config.cs
index 95aecee6..09b61c37 100644
--- a/src/WebCompiler/Config/Config.cs
+++ b/src/WebCompiler/Config/Config.cs
@@ -2,6 +2,7 @@
using System.ComponentModel;
using System.IO;
using System.Linq;
+
using Newtonsoft.Json;
namespace WebCompiler
@@ -23,12 +24,25 @@ public class Config
[JsonProperty("outputFile")]
public string OutputFile { get; set; }
+ ///
+ /// The extension to be used for output files - valid when is wildcard extension.
+ ///
+ [JsonIgnore]
+ public string OutputExtension => this.OutputFile.Substring(1);
+
+
///
/// The relative file path to the input file.
///
[JsonProperty("inputFile")]
public string InputFile { get; set; }
+ ///
+ /// The extension to match input files - valid when is a wildcard extension.
+ ///
+ [JsonIgnore]
+ public string InputExtension => this.InputFile.Substring(1);
+
///
/// Settings for the minification.
///
@@ -62,6 +76,19 @@ public class Config
internal string Output { get; set; }
+
+ ///
+ /// Determines if the config is only an extension pattern - not real file to process.
+ ///
+ [JsonIgnore]
+ public bool IsExtensionPattern => this.InputFile?.StartsWith("*") ?? false;
+
+ ///
+ /// Marks that the config is created from the extension expansion and not defined in the compilerconfig file.
+ ///
+ [JsonIgnore]
+ public bool IsFromExtensionPattern { get; set; }
+
///
/// Converts the relative input file to an absolute file path.
///
diff --git a/src/WebCompiler/Config/ConfigFileProcessor.cs b/src/WebCompiler/Config/ConfigFileProcessor.cs
index aedc8dda..612fa051 100644
--- a/src/WebCompiler/Config/ConfigFileProcessor.cs
+++ b/src/WebCompiler/Config/ConfigFileProcessor.cs
@@ -31,6 +31,11 @@ public IEnumerable Process(string configFile, IEnumerable SourceFileChanged(string configFile,
{
string folder = Path.GetDirectoryName(configFile);
List list = new List();
- var configs = ConfigHandler.GetConfigs(configFile);
+ var configs = ConfigHandler.GetConfigs(configFile, sourceFile);
// Compile if the file if it's referenced directly in compilerconfig.json
foreach (Config config in configs)
@@ -161,15 +166,16 @@ private IEnumerable SourceFileChanged(string configFile,
///
/// Returns a collection of config objects that all contain the specified sourceFile
///
- public static IEnumerable IsFileConfigured(string configFile, string sourceFile)
+ /// Set to true so that extension based config is ignored.
+ public static IEnumerable IsFileConfigured(string configFile, string sourceFile, bool ignoreExtensionConfig = false)
{
try
{
- var configs = ConfigHandler.GetConfigs(configFile);
+ var configs = ConfigHandler.GetConfigs(configFile, sourceFile);
string folder = Path.GetDirectoryName(configFile);
List list = new List();
- foreach (Config config in configs)
+ foreach (Config config in configs.Where(x=> !ignoreExtensionConfig || !x.IsFromExtensionPattern))
{
string input = Path.Combine(folder, config.InputFile.Replace("/", "\\"));
diff --git a/src/WebCompiler/Config/ConfigHandler.cs b/src/WebCompiler/Config/ConfigHandler.cs
index ed5e7192..ec39c1e4 100644
--- a/src/WebCompiler/Config/ConfigHandler.cs
+++ b/src/WebCompiler/Config/ConfigHandler.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
@@ -11,14 +12,16 @@ namespace WebCompiler
///
public class ConfigHandler
{
+ private static ConcurrentDictionary> ExtensionBasedConfigs { get; } = new ConcurrentDictionary>();
+
///
/// Adds a config file if no one exist or adds the specified config to an existing config file.
///
/// The file path of the configuration file.
- /// The compiler config object to add to the configration file.
+ /// The compiler config object to add to the configuration file.
public void AddConfig(string fileName, Config config)
{
- IEnumerable existing = GetConfigs(fileName);
+ IEnumerable existing = GetConfigs(fileName, expandExtensions: false);
List configs = new List();
configs.AddRange(existing);
configs.Add(config);
@@ -39,7 +42,7 @@ public void AddConfig(string fileName, Config config)
///
public void RemoveConfig(Config configToRemove)
{
- IEnumerable configs = GetConfigs(configToRemove.FileName);
+ IEnumerable configs = GetConfigs(configToRemove.FileName, expandExtensions: false);
List newConfigs = new List();
if (configs.Contains(configToRemove))
@@ -95,24 +98,85 @@ public void CreateDefaultsFile(string fileName)
/// Get all the config objects in the specified file.
///
/// A relative or absolute file path to the configuration file.
+ /// The name of the source file that is being modified/selected
+ /// The flag that states if wildcard extension config entry should be processed. If true all files that satisfy it would be returned.
/// A list of Config objects.
- public static IEnumerable GetConfigs(string fileName)
+ public static IEnumerable GetConfigs(string fileName, string sourceFile = null, bool expandExtensions = true)
{
FileInfo file = new FileInfo(fileName);
if (!file.Exists)
return Enumerable.Empty();
- string content = File.ReadAllText(fileName);
+ var content = File.ReadAllText(fileName);
var configs = JsonConvert.DeserializeObject>(content);
- string folder = Path.GetDirectoryName(file.FullName);
-
+ var folder = Path.GetDirectoryName(file.FullName);
+ var extensionConfigs = new List();
foreach (Config config in configs)
{
+ if (config.IsExtensionPattern
+ && (sourceFile == null || sourceFile.EndsWith(config.InputExtension))
+ && expandExtensions)
+ {
+ var cacheKey = $"{Path.GetFullPath(fileName)}-{config.InputExtension}";
+
+ ProcessExtensionPattern(fileName, sourceFile, folder, cacheKey, config);
+ extensionConfigs.AddRange(ExtensionBasedConfigs[cacheKey].Values.Where(ec => !configs.Any(c => c.InputFile?.Replace("/", "\\") == ec.InputFile)));
+ }
config.FileName = fileName;
}
- return configs;
+ return configs.Where(c => !c.IsExtensionPattern || !expandExtensions).Concat(extensionConfigs);
+ }
+
+ private static void ProcessExtensionPattern(string fileName, string sourceFile, string folder, string cacheKey, Config config)
+ {
+ if (!ExtensionBasedConfigs.ContainsKey(cacheKey))
+ {
+ var folderLength = folder.Length + 1;
+ var files = Directory.GetFiles(folder, $"{config.InputFile}", SearchOption.AllDirectories);
+ var fileConfigs = files.ToDictionary(f => f, f =>
+ {
+ var inputFile = f.Substring(folderLength);
+ return new Config()
+ {
+ FileName = fileName,
+ InputFile = inputFile,
+ OutputFile = inputFile.Replace(config.InputExtension, config.OutputExtension),
+ Minify = config.Minify,
+ Options = config.Options,
+ SourceMap = config.SourceMap,
+ UseNodeSass = config.UseNodeSass,
+ IncludeInProject = config.IncludeInProject,
+ IsFromExtensionPattern = true
+ };
+ });
+
+ ExtensionBasedConfigs.TryAdd(cacheKey, new ConcurrentDictionary(fileConfigs));
+ }
+ else if (sourceFile != null && !ExtensionBasedConfigs[cacheKey].ContainsKey(sourceFile))
+ {
+ ExtensionBasedConfigs[cacheKey].TryAdd(sourceFile, new Config()
+ {
+ FileName = fileName,
+ InputFile = sourceFile,
+ OutputFile = sourceFile.Replace(config.InputExtension, config.OutputExtension),
+ Minify = config.Minify,
+ Options = config.Options,
+ SourceMap = config.SourceMap,
+ UseNodeSass = config.UseNodeSass,
+ IncludeInProject = config.IncludeInProject,
+ IsFromExtensionPattern = true,
+ });
+ }
+ }
+
+ ///
+ /// Clears the configs based on input extensions.
+ ///
+ public static void ClearExtensionBasedConfigs()
+ {
+ ExtensionBasedConfigs.Clear();
}
}
}
diff --git a/src/WebCompiler/Program.cs b/src/WebCompiler/Program.cs
index 068d49db..29752483 100644
--- a/src/WebCompiler/Program.cs
+++ b/src/WebCompiler/Program.cs
@@ -60,7 +60,7 @@ 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 => c.InputFile.EndsWith(file.Substring(1), StringComparison.OrdinalIgnoreCase));
else
configs = configs.Where(c => c.InputFile.Equals(file, StringComparison.OrdinalIgnoreCase));
}
diff --git a/src/WebCompilerTest/Config/ConfigFileProcessorTest.cs b/src/WebCompilerTest/Config/ConfigFileProcessorTest.cs
new file mode 100644
index 00000000..10d9132f
--- /dev/null
+++ b/src/WebCompilerTest/Config/ConfigFileProcessorTest.cs
@@ -0,0 +1,51 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using System.IO;
+using System.Linq;
+
+using WebCompiler;
+
+namespace WebCompilerTest.Config
+{
+ [TestClass]
+ public class ConfigFileProcessorTest
+ {
+ private const string configFileWithExtensions = "../../artifacts/configwithextensions.json";
+
+ [TestMethod, TestCategory("Config")]
+ public void IsFileConfigured_WhenSourceFileMatchTheExtension_ShouldReturnConfigForThatFile()
+ {
+ var configFile = new FileInfo(configFileWithExtensions);
+ var configFileFolder = new FileInfo(configFileWithExtensions).DirectoryName;
+ var test1FilePath = new FileInfo("../../artifacts/scss/test1.razor.scss");
+ var test2FilePath = new FileInfo("../../artifacts/scss/test2.razor.scss");
+ var expectedTest1InputFile = test1FilePath.FullName.Replace(configFileFolder, "").Substring(1);
+ var expectedTest2InputFile = test2FilePath.FullName.Replace(configFileFolder, "").Substring(1);
+
+
+ var test1Config = ConfigFileProcessor.IsFileConfigured(configFile.FullName, test1FilePath.FullName).FirstOrDefault(x => x.InputFile == expectedTest1InputFile);
+ var test2Config = ConfigFileProcessor.IsFileConfigured(configFile.FullName, test2FilePath.FullName).FirstOrDefault(x => x.InputFile == expectedTest2InputFile);
+
+ Assert.IsNotNull(test1Config);
+ Assert.IsNotNull(test2Config);
+ }
+
+ [TestMethod, TestCategory("Config")]
+ public void IsFileConfigured_WhenSourceFileMatchTheExtensionAndIgnoreExtensionConfigIsTrue_ShouldNotReturnConfigForThatFile()
+ {
+ var configFile = new FileInfo(configFileWithExtensions);
+ var configFileFolder = new FileInfo(configFileWithExtensions).DirectoryName;
+ var test1FilePath = new FileInfo("../../artifacts/scss/test1.razor.scss");
+ var test2FilePath = new FileInfo("../../artifacts/scss/test2.razor.scss");
+ var expectedTest1InputFile = test1FilePath.FullName.Replace(configFileFolder, "").Substring(1);
+ var expectedTest2InputFile = test2FilePath.FullName.Replace(configFileFolder, "").Substring(1);
+
+
+ var test1Config = ConfigFileProcessor.IsFileConfigured(configFile.FullName, test1FilePath.FullName, ignoreExtensionConfig: true).FirstOrDefault(x => x.InputFile == expectedTest1InputFile);
+ var test2Config = ConfigFileProcessor.IsFileConfigured(configFile.FullName, test2FilePath.FullName, ignoreExtensionConfig: true).FirstOrDefault(x => x.InputFile == expectedTest2InputFile);
+
+ Assert.IsNull(test1Config);
+ Assert.IsNull(test2Config);
+ }
+ }
+}
diff --git a/src/WebCompilerTest/Config/ConfigHandlerTest.cs b/src/WebCompilerTest/Config/ConfigHandlerTest.cs
index eb809266..a087e9fc 100644
--- a/src/WebCompilerTest/Config/ConfigHandlerTest.cs
+++ b/src/WebCompilerTest/Config/ConfigHandlerTest.cs
@@ -1,6 +1,8 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
+
using System.IO;
using System.Linq;
+
using WebCompiler;
namespace WebCompilerTest.Config
@@ -13,6 +15,8 @@ public class ConfigHandlerTest
private const string originalConfigFile = "../../artifacts/config/originalcoffeeconfig.json";
private const string processingConfigFile = "../../artifacts/config/coffeeconfig.json";
+ private const string configFileWithExtensions = "../../artifacts/configwithextensions.json";
+
[TestInitialize]
public void Setup()
{
@@ -51,5 +55,68 @@ public void NonExistingConfigFileShouldReturnEmptyList()
Assert.AreEqual(expectedResult, result);
}
+
+ [TestMethod, TestCategory("Config")]
+ public void GetConfig_WhenExpandExtensionsIsNotProvided_ReturnsConfigsIncludingFilesMatchingExtensions()
+ {
+ var configs = ConfigHandler.GetConfigs(configFileWithExtensions);
+ var configFileFolder = new FileInfo(configFileWithExtensions).DirectoryName;
+ var test1FilePath = new FileInfo("../../artifacts/scss/test1.razor.scss");
+ var test2FilePath = new FileInfo("../../artifacts/scss/test2.razor.scss");
+ var expectedTest1InputFile = test1FilePath.FullName.Replace(configFileFolder, "").Substring(1);
+ var expectedTest2InputFile = test2FilePath.FullName.Replace(configFileFolder, "").Substring(1);
+
+ var test1Config = configs.SingleOrDefault(x => x.IsFromExtensionPattern && x.InputFile == expectedTest1InputFile);
+ var test2Config = configs.SingleOrDefault(x => x.IsFromExtensionPattern && x.InputFile == expectedTest2InputFile);
+
+
+ Assert.IsNotNull(test1Config);
+ Assert.IsNotNull(test2Config);
+
+ Assert.IsTrue(test1Config.IsFromExtensionPattern);
+ Assert.IsTrue(test2Config.IsFromExtensionPattern);
+
+ Assert.AreEqual(test1Config.OutputFile ,expectedTest1InputFile.Replace(".scss",".css"));
+ Assert.AreEqual(test2Config.OutputFile , expectedTest2InputFile.Replace(".scss",".css"));
+
+ Assert.IsFalse((bool)test1Config.Minify["enabled"]);
+ Assert.IsFalse((bool)test1Config.Minify["enabled"]);
+
+ Assert.AreEqual(3, configs.Count());
+ Assert.AreEqual(0, configs.Where(x => x.IsExtensionPattern).Count());
+ }
+
+ [TestMethod, TestCategory("Config")]
+ public void GetConfig_WhenExpandExtensionsIsFalse_ReturnsConfigsWithoutFilesMatchingExtensions()
+ {
+ var configs = ConfigHandler.GetConfigs(configFileWithExtensions, expandExtensions: false);
+
+ Assert.AreEqual(2, configs.Count());
+ Assert.AreEqual(1, configs.Where(x => x.IsExtensionPattern).Count());
+ }
+
+ [TestMethod, TestCategory("Config")]
+ public void GetConfig_WhenExpandExtensionsIsFalseAndSourceFileProvided_ReturnsConfigsWithoutFilesMatchingExtensions()
+ {
+ var configs = ConfigHandler.GetConfigs(configFileWithExtensions, sourceFile: "newfile.razor.scss", expandExtensions: false);
+
+ Assert.AreEqual(2, configs.Count());
+ Assert.AreEqual(1, configs.Where(x => x.IsExtensionPattern).Count());
+ }
+
+ [TestMethod, TestCategory("Config")]
+ public void GetConfig_WhenSourceFileWithValidExtensionIsProvidedAndCacheAlreadyFilled_ReturnsConfigsIncludingFile()
+ {
+ var newFile = "newFile.razor.scss";
+
+ // trigger loading existing files to dictionary cache
+ var configs = ConfigHandler.GetConfigs(configFileWithExtensions);
+
+ configs = ConfigHandler.GetConfigs(configFileWithExtensions, newFile);
+
+ Assert.AreEqual(4, configs.Count());
+ Assert.AreEqual(1, configs.Where(x => x.InputFile.Contains(newFile)).Count());
+ }
+
}
}
diff --git a/src/WebCompilerTest/Config/ConfigTest.cs b/src/WebCompilerTest/Config/ConfigTest.cs
index a291400e..4ef69607 100644
--- a/src/WebCompilerTest/Config/ConfigTest.cs
+++ b/src/WebCompilerTest/Config/ConfigTest.cs
@@ -16,6 +16,9 @@ public class ConfigTest
private const string firstLevelDependencyFile = "../../artifacts/config/dependencies/foo.scss";
private const string secondLevelDependencyFile = "../../artifacts/config/dependencies/sub/bar.scss";
+ private const string inputFileWildcardExtension = "*.razor.scss";
+ private const string outputFileWildcardExtension = "*.razor.css";
+
private readonly FileInfo _inputFileInfo = new FileInfo(inputFile);
private readonly FileInfo _outputFileInfo = new FileInfo(outputFile);
private readonly FileInfo _firstLevelDependencyFileInfo = new FileInfo(firstLevelDependencyFile);
@@ -116,5 +119,59 @@ public void CompilationRequired_SecondLevelDependencyNewerThanOutput_RequiresCom
Assert.AreEqual(true, compilationRequired);
}
+
+ [TestMethod, TestCategory("Config")]
+ public void InputFileExtension_WhenInputFileIsWildcardExtension_StripsAsterisk()
+ {
+ var config = new WebCompiler.Config()
+ {
+ InputFile = "*.razor.scss",
+ };
+
+ Assert.AreEqual(".razor.scss", config.InputExtension);
+ }
+
+ [TestMethod, TestCategory("Config")]
+ public void OutputFileExtension_WhenOutputFileIsWildcardExtension_StripsAsterisk()
+ {
+ var config = new WebCompiler.Config()
+ {
+ OutputFile = "*.razor.css",
+ };
+
+ Assert.AreEqual(".razor.css", config.OutputExtension);
+ }
+
+ [TestMethod, TestCategory("Config")]
+ public void IsExtensionPattern_WhenInputFileIsWildcardExtension_ReturnsTrue()
+ {
+ var config = new WebCompiler.Config()
+ {
+ InputFile = "*.razor.scss",
+ };
+
+ Assert.AreEqual(true, config.IsExtensionPattern);
+ }
+
+ [TestMethod, TestCategory("Config")]
+ public void IsExtensionPattern_WhenInputFileIsNotWildcardExtension_ReturnsFalse()
+ {
+ var config = new WebCompiler.Config()
+ {
+ InputFile = "somefile.razor.scss",
+ };
+
+ Assert.AreEqual(false, config.IsExtensionPattern);
+ }
+
+ [TestMethod, TestCategory("Config")]
+ public void IsExtensionPattern_WhenInputFileIsNotSet_ReturnsFalse()
+ {
+ var config = new WebCompiler.Config()
+ {
+ };
+
+ Assert.AreEqual(false, config.IsExtensionPattern);
+ }
}
}
diff --git a/src/WebCompilerTest/artifacts/configWithExtensions.json b/src/WebCompilerTest/artifacts/configWithExtensions.json
new file mode 100644
index 00000000..4d1c598c
--- /dev/null
+++ b/src/WebCompilerTest/artifacts/configWithExtensions.json
@@ -0,0 +1,16 @@
+[
+ {
+ "outputFile": "*.razor.css",
+ "inputFile": "*.razor.scss",
+ "minify": {
+ "enabled": false
+ },
+ "includeInProject": true
+ },
+ {
+ "outputFile": "../scss/test.css",
+ "inputFile": "../scss/test.scss",
+ "minify": {},
+ "includeInProject": true
+ }
+]
\ No newline at end of file
diff --git a/src/WebCompilerTest/artifacts/scss/test1.razor.scss b/src/WebCompilerTest/artifacts/scss/test1.razor.scss
new file mode 100644
index 00000000..46800d16
--- /dev/null
+++ b/src/WebCompilerTest/artifacts/scss/test1.razor.scss
@@ -0,0 +1,2 @@
+body {
+}
diff --git a/src/WebCompilerTest/artifacts/scss/test2.razor.scss b/src/WebCompilerTest/artifacts/scss/test2.razor.scss
new file mode 100644
index 00000000..46800d16
--- /dev/null
+++ b/src/WebCompilerTest/artifacts/scss/test2.razor.scss
@@ -0,0 +1,2 @@
+body {
+}
diff --git a/src/WebCompilerVsix/Commands/RemoveConfig.cs b/src/WebCompilerVsix/Commands/RemoveConfig.cs
index 35f93fd8..4177a4ae 100644
--- a/src/WebCompilerVsix/Commands/RemoveConfig.cs
+++ b/src/WebCompilerVsix/Commands/RemoveConfig.cs
@@ -56,7 +56,7 @@ private void BeforeQueryStatus(object sender, EventArgs e)
string configFile = item.ContainingProject.GetConfigFile();
- _configs = ConfigFileProcessor.IsFileConfigured(configFile, sourceFile);
+ _configs = ConfigFileProcessor.IsFileConfigured(configFile, sourceFile, true);
button.Visible = _configs != null && _configs.Any();
}
diff --git a/src/WebCompilerVsix/TaskRunner/WebCompilerTaskRunner.cs b/src/WebCompilerVsix/TaskRunner/WebCompilerTaskRunner.cs
index 5e86be20..7304a6a4 100644
--- a/src/WebCompilerVsix/TaskRunner/WebCompilerTaskRunner.cs
+++ b/src/WebCompilerVsix/TaskRunner/WebCompilerTaskRunner.cs
@@ -68,7 +68,7 @@ private ITaskRunnerNode LoadHierarchy(string configPath)
private ITaskRunnerNode GetFileType(string configPath, string extension)
{
- var configs = ConfigHandler.GetConfigs(configPath);
+ var configs = ConfigHandler.GetConfigs(configPath, expandExtensions: false);
var types = configs?.Where(c => Path.GetExtension(c.InputFile).Equals(extension, StringComparison.OrdinalIgnoreCase));
if (types == null || !types.Any())