Skip to content

Instantly share code, notes, and snippets.

@baronfel
Last active March 17, 2025 14:22
Show Gist options
  • Save baronfel/36765ce57bfe9687ecb79df8b2db3a78 to your computer and use it in GitHub Desktop.
Save baronfel/36765ce57bfe9687ecb79df8b2db3a78 to your computer and use it in GitHub Desktop.
swapping out stdin with a provided file
using System.CommandLine;
using System.CommandLine.Parsing;
using System.Text;
var fileContent = new Argument<Stream?>("file")
{
Arity = ArgumentArity.ZeroOrOne,
DefaultValueFactory = OpenStream,
Description = "The file to read from. Defaults to stdin if provided",
CustomParser = OpenStream,
};
fileContent.Validators.Add(FileMustExist);
fileContent.Validators.Add(MustBeRedirected);
var encoding = new Option<System.Text.Encoding>("--encoding")
{
Description = "The encoding to use when reading the file",
DefaultValueFactory = (ar) => Encoding.UTF8,
CustomParser = (ar) => Encoding.GetEncoding(ar.Tokens[0].Value),
};
encoding.AcceptOnlyFromAmong(Encoding.GetEncodings().Select(e => e.Name).ToArray());
var c = new RootCommand("read a file in a given encoding")
{ fileContent,
encoding };
c.SetAction(async (ParseResult pr, CancellationToken ctok) =>
{
var fileStream = pr.GetValue(fileContent)!; // will be enforced non-null by validations
var enc = pr.GetValue(encoding)!;
await WriteFileToOutput(fileStream, enc, pr.Configuration.Output, ctok);
return 0;
});
var config = new CommandLineConfiguration(c);
config.ThrowIfInvalid();
config.EnablePosixBundling = false;
return await config.Parse(args).InvokeAsync();
async Task WriteFileToOutput(Stream contents, Encoding encoding, TextWriter output, CancellationToken ctok)
{
using var inputReader = new StreamReader(contents, encoding);
var input = await inputReader.ReadToEndAsync(ctok);
await output.WriteAsync(input);
}
static void FileMustExist(ArgumentResult ar)
{
if (ar.Tokens.Count == 1)
{
var filePath = ar.Tokens[0].Value;
if (filePath == "-")
{
return;
}
else if (!File.Exists(filePath))
{
ar.AddError($"The file {filePath} does not exist");
}
}
}
static void MustBeRedirected(ArgumentResult ar)
{
if (ar.Tokens.Count == 0 && !Console.IsInputRedirected)
{
ar.AddError($"When no file is provided, input must be redirected from stdin");
}
}
static Stream? OpenStream(ArgumentResult ar)
{
return ar.Tokens switch
{
[] when Console.IsInputRedirected => Console.OpenStandardInput(),
[] => null,
[{ Value: "-" }] => Console.OpenStandardInput(),
[{ Value: var file }] => File.OpenRead(file),
_ => throw new InvalidOperationException("Too many arguments"),
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment