Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for passing a config file to csharpier #794

Merged
merged 9 commits into from
Jan 28, 2023
Merged
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
11 changes: 11 additions & 0 deletions Docs/CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Options:
--skip-write Skip writing changes. Generally used for testing to ensure csharpier doesn't throw any errors or cause syntax tree validation failures.
--write-stdout Write the results of formatting any files to stdout.
--pipe-multiple-files Keep csharpier running so that multiples files can be piped to it via stdin
--config-path Path to the CSharpier configuration file
--version Show version information
-?, -h, --help Show help and usage information

Expand Down Expand Up @@ -122,3 +123,13 @@ public class ClassName
public string Field;
}
```

### --config-path
If your configuration file lives in a location that CSharpier would not normally resolve it (such as in a config folder)
you can pass the path for the configuration file to CSharpier.
```bash
dotnet csharpier . --config-path "./config/.csharpierrc"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe update the test and documentation to something like --config-path ./config/csharpier.yaml?
To make it clear that the file name no longer has to be .csharpierrc if you're specifying the path manually.


# also supports any name for the config file
dotnet csharpier . --config-path "./config/csharpier.yaml"
```
2 changes: 2 additions & 0 deletions Docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ CSharpier has support for a configuration file. You can use any of the following
- A ```.csharpierrc``` file in JSON or YAML.
- A ```.csharpierrc.json``` or ```.csharpierrc.yaml``` file.

The configuration file will be resolved starting from the location of the file being formatted, and searching up the file tree until a config file is (or isn’t) found.

### Configuration Options
JSON
```json
Expand Down
17 changes: 17 additions & 0 deletions Src/CSharpier.Cli.Tests/CliTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,23 @@ public async Task Should_Respect_Ignore_File_With_Subdirectory_When_DirectorOrFi
result.Should().Be(unformattedContent, $"The file at {filePath} should have been ignored");
}

[Test]
public async Task Should_Support_Config_Path()
{
const string fileContent = "var myVariable = someLongValue;";
var fileName = "TooWide.cs";
await this.WriteFileAsync(fileName, fileContent);
await this.WriteFileAsync("config/.csharpierrc", "printWidth: 10");

await new CsharpierProcess()
.WithArguments("--config-path config/.csharpierrc . ")
.ExecuteAsync();

var result = await this.ReadAllTextAsync(fileName);

result.Should().Be("var myVariable =\n someLongValue;\n");
}

[Test]
public async Task Should_Return_Error_When_No_DirectoryOrFile_And_Not_Piping_StdIn()
{
Expand Down
15 changes: 10 additions & 5 deletions Src/CSharpier.Cli/CommandLineFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ CancellationToken cancellationToken

var (ignoreFile, printerOptions) = await GetIgnoreFileAndPrinterOptions(
filePath,
commandLineOptions.ConfigPath,
fileSystem,
logger,
cancellationToken
Expand Down Expand Up @@ -133,6 +134,7 @@ CancellationToken cancellationToken

var (ignoreFile, printerOptions) = await GetIgnoreFileAndPrinterOptions(
directoryOrFile,
commandLineOptions.ConfigPath,
fileSystem,
logger,
cancellationToken
Expand Down Expand Up @@ -227,6 +229,7 @@ await FormatPhysicalFile(

private static async Task<(IgnoreFile, PrinterOptions)> GetIgnoreFileAndPrinterOptions(
string directoryOrFile,
string? configPath,
IFileSystem fileSystem,
ILogger logger,
CancellationToken cancellationToken
Expand All @@ -245,11 +248,13 @@ CancellationToken cancellationToken
cancellationToken
);

var printerOptions = ConfigurationFileOptions.CreatePrinterOptions(
baseDirectoryPath,
fileSystem,
logger
);
var printerOptions = configPath is null
? ConfigurationFileOptions.FindPrinterOptionsForDirectory(
baseDirectoryPath,
fileSystem,
logger
)
: ConfigurationFileOptions.CreatePrinterOptionsFromPath(configPath, fileSystem, logger);

return (ignoreFile, printerOptions);
}
Expand Down
6 changes: 6 additions & 0 deletions Src/CSharpier.Cli/CommandLineOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class CommandLineOptions
public bool PipeMultipleFiles { get; init; }
public bool NoCache { get; init; }
public string? StandardInFileContents { get; init; }
public string? ConfigPath { get; init; }
public string[] OriginalDirectoryOrFilePaths { get; init; } = Array.Empty<string>();

internal delegate Task<int> Handler(
Expand All @@ -22,6 +23,7 @@ internal delegate Task<int> Handler(
bool writeStdout,
bool pipeMultipleFiles,
bool noCache,
string config,
CancellationToken cancellationToken
);

Expand Down Expand Up @@ -58,6 +60,10 @@ public static RootCommand Create()
new Option(
new[] { "--pipe-multiple-files" },
"Keep csharpier running so that multiples files can be piped to it via stdin"
),
new Option<string>(
new[] { "--config-path" },
"Path to the CSharpier configuration file"
)
};

Expand Down
52 changes: 39 additions & 13 deletions Src/CSharpier.Cli/ConfigurationFileOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,34 @@ public class ConfigurationFileOptions

private static readonly string[] validExtensions = { ".csharpierrc", ".json", ".yml", ".yaml" };

internal static PrinterOptions CreatePrinterOptions(
internal static PrinterOptions FindPrinterOptionsForDirectory(
string baseDirectoryPath,
IFileSystem fileSystem,
ILogger logger
)
{
DebugLogger.Log("Creating printer options for " + baseDirectoryPath);

var configurationFileOptions = Create(baseDirectoryPath, fileSystem, logger);
var configurationFileOptions = FindForDirectory(baseDirectoryPath, fileSystem, logger);

return ConvertToPrinterOptions(configurationFileOptions);
}

internal static PrinterOptions CreatePrinterOptionsFromPath(
string configPath,
IFileSystem fileSystem,
ILogger logger
)
{
var configurationFileOptions = Create(configPath, fileSystem, logger);

return ConvertToPrinterOptions(configurationFileOptions);
}

private static PrinterOptions ConvertToPrinterOptions(
ConfigurationFileOptions configurationFileOptions
)
{
List<string[]> preprocessorSymbolSets;
if (configurationFileOptions.PreprocessorSymbolSets == null)
{
Expand Down Expand Up @@ -53,7 +71,7 @@ ILogger logger
};
}

public static ConfigurationFileOptions Create(
public static ConfigurationFileOptions FindForDirectory(
string baseDirectoryPath,
IFileSystem fileSystem,
ILogger? logger = null
Expand All @@ -68,25 +86,33 @@ public static ConfigurationFileOptions Create(
.Where(o => validExtensions.Contains(o.Extension, StringComparer.OrdinalIgnoreCase))
.MinBy(o => o.Extension);

if (file == null)
if (file != null)
{
directoryInfo = directoryInfo.Parent;
continue;
return Create(file.FullName, fileSystem, logger);
}

var contents = fileSystem.File.ReadAllText(file.FullName);
directoryInfo = directoryInfo.Parent;
}

if (string.IsNullOrWhiteSpace(contents))
{
logger?.LogWarning("The configuration file at " + file.FullName + " was empty.");
return new ConfigurationFileOptions();
}

return new();
}
public static ConfigurationFileOptions Create(
string configPath,
IFileSystem fileSystem,
ILogger? logger = null
)
{
var contents = fileSystem.File.ReadAllText(configPath);

if (!string.IsNullOrWhiteSpace(contents))
{
return contents.TrimStart().StartsWith("{") ? ReadJson(contents) : ReadYaml(contents);
}

return new ConfigurationFileOptions();
logger?.LogWarning("The configuration file at " + configPath + " was empty.");

return new();
}

private static ConfigurationFileOptions ReadJson(string contents)
Expand Down
15 changes: 11 additions & 4 deletions Src/CSharpier.Cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,23 @@ public static async Task<int> Run(
bool writeStdout,
bool pipeMultipleFiles,
bool noCache,
string configPath,
CancellationToken cancellationToken
)
{
// System.CommandLine passes string.empty instead of null when this isn't supplied even if we use string?
var actualConfigPath = string.IsNullOrEmpty(configPath) ? null : configPath;

DebugLogger.Log("Starting");
var console = new SystemConsole();
var logger = new ConsoleLogger(console);

if (pipeMultipleFiles)
{
return await PipeMultipleFiles(console, logger, cancellationToken);
return await PipeMultipleFiles(console, logger, actualConfigPath, cancellationToken);
}

var directoryOrFileNotProvided = (directoryOrFile is null or { Length: 0 });
var directoryOrFileNotProvided = directoryOrFile is null or { Length: 0 };
var originalDirectoryOrFile = directoryOrFile;

string? standardInFileContents = null;
Expand Down Expand Up @@ -75,7 +79,8 @@ CancellationToken cancellationToken
NoCache = noCache,
Fast = fast,
SkipWrite = skipWrite,
WriteStdout = writeStdout || standardInFileContents != null
WriteStdout = writeStdout || standardInFileContents != null,
ConfigPath = actualConfigPath
};

return await CommandLineFormatter.Format(
Expand All @@ -90,6 +95,7 @@ CancellationToken cancellationToken
private static async Task<int> PipeMultipleFiles(
SystemConsole console,
ILogger logger,
string? configPath,
CancellationToken cancellationToken
)
{
Expand Down Expand Up @@ -146,7 +152,8 @@ CancellationToken cancellationToken
},
StandardInFileContents = stringBuilder.ToString(),
Fast = true,
WriteStdout = true
WriteStdout = true,
ConfigPath = configPath
};

try
Expand Down
10 changes: 2 additions & 8 deletions Src/CSharpier.MsBuild/CSharpier.MsBuild.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,8 @@
make it a dependency in the output package, which causes all sorts of issues as
CSharpier is a net6.0 project with <PackAsTool> and this is neither.
-->
<Target Name="BuildCSharpier" BeforeTargets="Build">
<!--
We run these commands in parallel with two frameworks
If we let dotnet build do the restore then we get a failure with file locks
Doing the restore by itself retries when a file is locked and avoids the failure
-->
<Exec command="dotnet restore" WorkingDirectory="../CSharpier.Cli" />
<Exec command="dotnet build --no-restore -c $(Configuration)" WorkingDirectory="../CSharpier.Cli" />
<Target Name="BuildCSharpier" BeforeTargets="Restore">
<Exec command="dotnet build -c $(Configuration)" WorkingDirectory="../CSharpier.Cli" />
</Target>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion Src/CSharpier.Tests/ConfigurationFileOptionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ private class TestContext
public ConfigurationFileOptions CreateConfigurationOptions(string baseDirectoryPath)
{
this.fileSystem.AddDirectory(baseDirectoryPath);
return ConfigurationFileOptions.Create(baseDirectoryPath, this.fileSystem);
return ConfigurationFileOptions.FindForDirectory(baseDirectoryPath, this.fileSystem);
}

public void WhenAFileExists(string path, string contents)
Expand Down