Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/WebCompiler/Config/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.ComponentModel;
using System.IO;
using System.Linq;

using Newtonsoft.Json;

namespace WebCompiler
Expand All @@ -23,12 +24,25 @@ public class Config
[JsonProperty("outputFile")]
public string OutputFile { get; set; }

/// <summary>
/// The extension to be used for output files - valid when <see cref="OutputFile"/> is wildcard extension.
/// </summary>
[JsonIgnore]
public string OutputExtension => this.OutputFile.Substring(1);


/// <summary>
/// The relative file path to the input file.
/// </summary>
[JsonProperty("inputFile")]
public string InputFile { get; set; }

/// <summary>
/// The extension to match input files - valid when <see cref="InputFile"/> is a wildcard extension.
/// </summary>
[JsonIgnore]
public string InputExtension => this.InputFile.Substring(1);

/// <summary>
/// Settings for the minification.
/// </summary>
Expand Down Expand Up @@ -62,6 +76,19 @@ public class Config

internal string Output { get; set; }


/// <summary>
/// Determines if the config is only an extension pattern - not real file to process.
/// </summary>
[JsonIgnore]
public bool IsExtensionPattern => this.InputFile?.StartsWith("*") ?? false;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this instead just search anywhere for the wildcard character? Just thinking of cases like /MyFolder/*.less


/// <summary>
/// Marks that the config is created from the extension expansion and not defined in the compilerconfig file.
/// </summary>
[JsonIgnore]
public bool IsFromExtensionPattern { get; set; }

/// <summary>
/// Converts the relative input file to an absolute file path.
/// </summary>
Expand Down
14 changes: 10 additions & 4 deletions src/WebCompiler/Config/ConfigFileProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ public IEnumerable<CompilerResult> Process(string configFile, IEnumerable<Config

try
{
if (force)
{
ConfigHandler.ClearExtensionBasedConfigs();
}

FileInfo info = new FileInfo(configFile);
configs = configs ?? ConfigHandler.GetConfigs(configFile);

Expand Down Expand Up @@ -107,7 +112,7 @@ private IEnumerable<CompilerResult> SourceFileChanged(string configFile,
{
string folder = Path.GetDirectoryName(configFile);
List<CompilerResult> list = new List<CompilerResult>();
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)
Expand Down Expand Up @@ -161,15 +166,16 @@ private IEnumerable<CompilerResult> SourceFileChanged(string configFile,
/// <summary>
/// Returns a collection of config objects that all contain the specified sourceFile
/// </summary>
public static IEnumerable<Config> IsFileConfigured(string configFile, string sourceFile)
/// <param name="ignoreExtensionConfig">Set to true so that extension based config is ignored.</param>
public static IEnumerable<Config> 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<Config> list = new List<Config>();

foreach (Config config in configs)
foreach (Config config in configs.Where(x=> !ignoreExtensionConfig || !x.IsFromExtensionPattern))
{
string input = Path.Combine(folder, config.InputFile.Replace("/", "\\"));

Expand Down
82 changes: 73 additions & 9 deletions src/WebCompiler/Config/ConfigHandler.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -11,14 +12,16 @@ namespace WebCompiler
/// </summary>
public class ConfigHandler
{
private static ConcurrentDictionary<string, ConcurrentDictionary<string, Config>> ExtensionBasedConfigs { get; } = new ConcurrentDictionary<string, ConcurrentDictionary<string, Config>>();

/// <summary>
/// Adds a config file if no one exist or adds the specified config to an existing config file.
/// </summary>
/// <param name="fileName">The file path of the configuration file.</param>
/// <param name="config">The compiler config object to add to the configration file.</param>
/// <param name="config">The compiler config object to add to the configuration file.</param>
public void AddConfig(string fileName, Config config)
{
IEnumerable<Config> existing = GetConfigs(fileName);
IEnumerable<Config> existing = GetConfigs(fileName, expandExtensions: false);
List<Config> configs = new List<Config>();
configs.AddRange(existing);
configs.Add(config);
Expand All @@ -39,7 +42,7 @@ public void AddConfig(string fileName, Config config)
/// </summary>
public void RemoveConfig(Config configToRemove)
{
IEnumerable<Config> configs = GetConfigs(configToRemove.FileName);
IEnumerable<Config> configs = GetConfigs(configToRemove.FileName, expandExtensions: false);
List<Config> newConfigs = new List<Config>();

if (configs.Contains(configToRemove))
Expand Down Expand Up @@ -95,24 +98,85 @@ public void CreateDefaultsFile(string fileName)
/// Get all the config objects in the specified file.
/// </summary>
/// <param name="fileName">A relative or absolute file path to the configuration file.</param>
/// <param name="sourceFile">The name of the source file that is being modified/selected</param>
/// <param name="expandExtensions">The flag that states if wildcard extension config entry should be processed. If true all files that satisfy it would be returned.</param>
/// <returns>A list of Config objects.</returns>
public static IEnumerable<Config> GetConfigs(string fileName)
public static IEnumerable<Config> GetConfigs(string fileName, string sourceFile = null, bool expandExtensions = true)
{
FileInfo file = new FileInfo(fileName);

if (!file.Exists)
return Enumerable.Empty<Config>();

string content = File.ReadAllText(fileName);
var content = File.ReadAllText(fileName);
var configs = JsonConvert.DeserializeObject<IEnumerable<Config>>(content);
string folder = Path.GetDirectoryName(file.FullName);

var folder = Path.GetDirectoryName(file.FullName);
var extensionConfigs = new List<Config>();
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<string, Config>(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,
});
}
}

/// <summary>
/// Clears the configs based on input extensions.
/// </summary>
public static void ClearExtensionBasedConfigs()
{
ExtensionBasedConfigs.Clear();
}
}
}
2 changes: 1 addition & 1 deletion src/WebCompiler/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ private static IEnumerable<Config> 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));
}
Expand Down
51 changes: 51 additions & 0 deletions src/WebCompilerTest/Config/ConfigFileProcessorTest.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
67 changes: 67 additions & 0 deletions src/WebCompilerTest/Config/ConfigHandlerTest.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;

using System.IO;
using System.Linq;

using WebCompiler;

namespace WebCompilerTest.Config
Expand All @@ -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()
{
Expand Down Expand Up @@ -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());
}

}
}
Loading