Skip to content

Commit

Permalink
Making config and ignore files look in parent directories
Browse files Browse the repository at this point in the history
closes #181
  • Loading branch information
belav committed May 17, 2021
1 parent 57b389d commit 9a3636d
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 115 deletions.
1 change: 1 addition & 0 deletions CSharpier.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=csharpierrc/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Sharpier/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
31 changes: 23 additions & 8 deletions Src/CSharpier.Tests/CommandLineFormatterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,21 @@ public void File_In_Ignore_Skips_Formatting(string fileName, string ignoreConten
lines.FirstOrDefault(o => o.StartsWith("Total files")).Should().Be("Total files: 0 ");
}

[TestCase("SubFolder/File.cs", "*.cs", "SubFolder")]
public void File_In_Ignore_Skips_Formatting(
string fileName,
string ignoreContents,
string rootPath
) {
var unformattedFilePath = fileName;
WhenThereExists(unformattedFilePath, UnformattedClass);
WhenThereExists(".csharpierignore", ignoreContents);

var (_, lines) = this.Format(baseDirectoryPath: Path.Combine(GetRootPath(), rootPath));

lines.FirstOrDefault(o => o.StartsWith("Total files")).Should().Be("Total files: 0 ");
}

[Test]
public void Ignore_Reports_Errors()
{
Expand All @@ -132,20 +147,20 @@ public void Ignore_Reports_Errors()
}

private (int exitCode, IList<string> lines) Format(
string rootPath = null,
string baseDirectoryPath = null,
bool skipWrite = false,
bool check = false
) {
if (rootPath == null)
if (baseDirectoryPath == null)
{
rootPath = GetRootPath();
baseDirectoryPath = GetRootPath();
}

var commandLineFormatter = new TestableCommandLineFormatter(
rootPath,
baseDirectoryPath,
new CommandLineOptions
{
DirectoryOrFile = rootPath,
DirectoryOrFile = baseDirectoryPath,
SkipWrite = skipWrite,
Check = check
},
Expand All @@ -154,7 +169,7 @@ public void Ignore_Reports_Errors()
);

return (
commandLineFormatter.Format(CancellationToken.None).Result,
commandLineFormatter.FormatFiles(CancellationToken.None).Result,
commandLineFormatter.Lines
);
}
Expand All @@ -181,12 +196,12 @@ private class TestableCommandLineFormatter : CommandLineFormatter
public readonly IList<string> Lines = new List<string>();

public TestableCommandLineFormatter(
string rootPath,
string baseDirectoryPath,
CommandLineOptions commandLineOptions,
PrinterOptions printerOptions,
IFileSystem fileSystem
)
: base(rootPath, commandLineOptions, printerOptions, fileSystem) { }
: base(baseDirectoryPath, commandLineOptions, printerOptions, fileSystem) { }

protected override void WriteLine(string line = null)
{
Expand Down
18 changes: 16 additions & 2 deletions Src/CSharpier.Tests/ConfigurationFileOptionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,20 @@ public void Should_Read_ExtensionLess_File(string contents)
result.PrintWidth.Should().Be(10);
}

[TestCase("", "printWidth: 10")]
[TestCase("", "{ \"printWidth\": 10 }")]
[TestCase(".yml", "printWidth: 10")]
[TestCase(".yaml", "printWidth: 10")]
[TestCase(".json", "{ \"printWidth\": 10 }")]
public void Should_Find_Configuration_In_Parent_Directory(string extension, string contents)
{
WhenThereExists($"c:/test/.csharpierrc{extension}", contents);

var result = CreateConfigurationOptions("c:/test/subfolder");

result.PrintWidth.Should().Be(10);
}

[Test]
public void Should_Prefer_No_Extension()
{
Expand Down Expand Up @@ -166,9 +180,9 @@ private void ShouldHaveDefaultOptions(ConfigurationFileOptions configurationFile
configurationFileOptions.EndOfLine.Should().Be(EndOfLine.Auto);
}

private ConfigurationFileOptions CreateConfigurationOptions(string rootPath)
private ConfigurationFileOptions CreateConfigurationOptions(string baseDirectoryPath)
{
return ConfigurationFileOptions.Create(rootPath, fileSystem);
return ConfigurationFileOptions.Create(baseDirectoryPath, fileSystem);
}

private void WhenThereExists(string path, string contents)
Expand Down
3 changes: 2 additions & 1 deletion Src/CSharpier.Tests/LineEndingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ public void Escaped_LineEndings_In_Verbatim_String_Should_Remain(
string escapedNewLine,
EndOfLine endOfLine
) {
var code = @$"class ClassName
var code =
@$"class ClassName
{{
string value = @""one{escapedNewLine}two"";
}}
Expand Down
116 changes: 84 additions & 32 deletions Src/CSharpier/CommandLineFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,67 @@ public class CommandLineFormatter

protected readonly Stopwatch Stopwatch;

protected readonly string RootPath;
protected readonly string BaseDirectoryPath;

protected readonly CommandLineOptions CommandLineOptions;
protected readonly PrinterOptions PrinterOptions;
protected readonly IFileSystem FileSystem;

public global::Ignore.Ignore Ignore { get; set; }
public global::Ignore.Ignore Ignore { get; init; }

public CommandLineFormatter(
string rootPath,
protected CommandLineFormatter(
string baseDirectoryPath,
CommandLineOptions commandLineOptions,
PrinterOptions printerOptions,
IFileSystem fileSystem
) {
this.RootPath = rootPath;
this.BaseDirectoryPath = baseDirectoryPath;
this.PrinterOptions = printerOptions;
this.CommandLineOptions = commandLineOptions;
this.Stopwatch = Stopwatch.StartNew();
this.FileSystem = fileSystem;
this.Ignore = new global::Ignore.Ignore();
}

public async Task<int> Format(CancellationToken cancellationToken)
public static async Task<int> Format(
CommandLineOptions commandLineOptions,
IFileSystem fileSystem,
CancellationToken cancellationToken
) {
var baseDirectoryPath = File.Exists(commandLineOptions.DirectoryOrFile)
? Path.GetDirectoryName(commandLineOptions.DirectoryOrFile)
: commandLineOptions.DirectoryOrFile;

if (baseDirectoryPath == null)
{
throw new Exception(
$"The path of {commandLineOptions.DirectoryOrFile} does not appear to point to a directory or a file."
);
}

var configurationFileOptions = ConfigurationFileOptions.Create(
baseDirectoryPath,
fileSystem
);

var printerOptions = new PrinterOptions
{
TabWidth = configurationFileOptions.TabWidth,
UseTabs = configurationFileOptions.UseTabs,
Width = configurationFileOptions.PrintWidth,
EndOfLine = configurationFileOptions.EndOfLine
};

var commandLineFormatter = new CommandLineFormatter(
baseDirectoryPath,
commandLineOptions,
printerOptions,
fileSystem
);
return await commandLineFormatter.FormatFiles(cancellationToken);
}

public async Task<int> FormatFiles(CancellationToken cancellationToken)
{
var ignoreExitCode = await this.ParseIgnoreFile(cancellationToken);
if (ignoreExitCode != 0)
Expand All @@ -57,7 +95,7 @@ public async Task<int> Format(CancellationToken cancellationToken)
else
{
var tasks = this.FileSystem.Directory.EnumerateFiles(
this.RootPath,
this.BaseDirectoryPath,
"*.cs",
SearchOption.AllDirectories
)
Expand All @@ -83,30 +121,44 @@ public async Task<int> Format(CancellationToken cancellationToken)

private async Task<int> ParseIgnoreFile(CancellationToken cancellationToken)
{
var ignoreFilePath = Path.Combine(this.RootPath, ".csharpierignore");
if (this.FileSystem.File.Exists(ignoreFilePath))
var directoryInfo = this.FileSystem.DirectoryInfo.FromDirectoryName(
this.BaseDirectoryPath
);
var ignoreFilePath = this.FileSystem.Path.Combine(
directoryInfo.FullName,
".csharpierignore"
);
while (!this.FileSystem.File.Exists(ignoreFilePath))
{
foreach (
var line in await this.FileSystem.File.ReadAllLinesAsync(
ignoreFilePath,
cancellationToken
)
) {
try
{
this.Ignore.Add(line);
}
catch (Exception ex)
{
WriteLine(
"The .csharpierignore file at "
+ ignoreFilePath
+ " could not be parsed due to the following line:"
);
WriteLine(line);
WriteLine("Exception: " + ex.Message);
return 1;
}
directoryInfo = directoryInfo.Parent;
if (directoryInfo == null)
{
return 0;
}
ignoreFilePath = this.FileSystem.Path.Combine(
directoryInfo.FullName,
".csharpierignore"
);
}

foreach (
var line in await this.FileSystem.File.ReadAllLinesAsync(
ignoreFilePath,
cancellationToken
)
) {
try
{
this.Ignore.Add(line);
}
catch (Exception ex)
{
WriteLine(
$"The .csharpierignore file at {ignoreFilePath} could not be parsed due to the following line:"
);
WriteLine(line);
WriteLine($"Exception: {ex.Message}");
return 1;
}
}

Expand Down Expand Up @@ -228,7 +280,7 @@ private async Task FormatFile(string file, CancellationToken cancellationToken)

private string GetPath(string file)
{
return PadToSize(file.Substring(this.RootPath.Length));
return PadToSize(file.Substring(this.BaseDirectoryPath.Length));
}

private void PrintResults()
Expand Down Expand Up @@ -284,7 +336,7 @@ private bool IgnoreFile(string filePath)
}

var normalizedFilePath = filePath.Replace("\\", "/")
.Substring(this.RootPath.Length + 1);
.Substring(this.BaseDirectoryPath.Length + 1);

return this.Ignore.IsIgnored(normalizedFilePath);
}
Expand Down
88 changes: 49 additions & 39 deletions Src/CSharpier/ConfigurationFileOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,58 +17,68 @@ public class ConfigurationFileOptions
public bool UseTabs { get; init; }
public EndOfLine EndOfLine { get; init; }

public static ConfigurationFileOptions Create(string rootPath, IFileSystem fileSystem)
{
ConfigurationFileOptions ReadJson(string path)
{
return JsonConvert.DeserializeObject<ConfigurationFileOptions>(
fileSystem.File.ReadAllText(path)
);
}

ConfigurationFileOptions ReadYaml(string path)
public static ConfigurationFileOptions Create(
string baseDirectoryPath,
IFileSystem fileSystem
) {
while (true)
{
var deserializer = new DeserializerBuilder().WithNamingConvention(
CamelCaseNamingConvention.Instance
)
.Build();
var potentialPath = fileSystem.Path.Combine(baseDirectoryPath, ".csharpierrc");

return deserializer.Deserialize<ConfigurationFileOptions>(
fileSystem.File.ReadAllText(path)
);
}
if (fileSystem.File.Exists(potentialPath))
{
var contents = fileSystem.File.ReadAllText(potentialPath);
if (contents.TrimStart().StartsWith("{"))
{
return ReadJson(potentialPath, fileSystem);
}

var potentialPath = fileSystem.Path.Combine(rootPath, ".csharpierrc");
return ReadYaml(potentialPath, fileSystem);
}

if (fileSystem.File.Exists(potentialPath))
{
var contents = fileSystem.File.ReadAllText(potentialPath);
if (contents.TrimStart().StartsWith("{"))
var jsonExtensionPath = potentialPath + ".json";
if (fileSystem.File.Exists(jsonExtensionPath))
{
return ReadJson(potentialPath);
return ReadJson(jsonExtensionPath, fileSystem);
}

return ReadYaml(potentialPath);
}
var yamlExtensionPaths = new[] { potentialPath + ".yaml", potentialPath + ".yml" };
foreach (var yamlExtensionPath in yamlExtensionPaths)
{
if (!fileSystem.File.Exists(yamlExtensionPath))
{
continue;
}

var jsonExtensionPath = potentialPath + ".json";
if (fileSystem.File.Exists(jsonExtensionPath))
{
return ReadJson(jsonExtensionPath);
}
return ReadYaml(yamlExtensionPath, fileSystem);
}

var yamlExtensionPaths = new[] { potentialPath + ".yaml", potentialPath + ".yml" };
foreach (var yamlExtensionPath in yamlExtensionPaths)
{
if (!fileSystem.File.Exists(yamlExtensionPath))
var directoryInfo = fileSystem.DirectoryInfo.FromDirectoryName(baseDirectoryPath);
if (directoryInfo.Parent == null)
{
continue;
return new ConfigurationFileOptions();
}

return ReadYaml(yamlExtensionPath);
baseDirectoryPath = directoryInfo.Parent.FullName;
}
}

private static ConfigurationFileOptions ReadJson(string path, IFileSystem fileSystem)
{
return JsonConvert.DeserializeObject<ConfigurationFileOptions>(
fileSystem.File.ReadAllText(path)
);
}

private static ConfigurationFileOptions ReadYaml(string path, IFileSystem fileSystem)
{
var deserializer = new DeserializerBuilder().WithNamingConvention(
CamelCaseNamingConvention.Instance
)
.Build();

return new ConfigurationFileOptions();
return deserializer.Deserialize<ConfigurationFileOptions>(
fileSystem.File.ReadAllText(path)
);
}
}
}
Loading

0 comments on commit 9a3636d

Please sign in to comment.