Environment variables (#27)

This commit is contained in:
Federico Paolillo
2019-09-29 19:44:24 +02:00
committed by Alexey Golub
parent a6070332c9
commit 36436e7a4b
25 changed files with 544 additions and 169 deletions

1
.gitignore vendored
View File

@@ -143,6 +143,7 @@ _TeamCity*
_NCrunch_* _NCrunch_*
.*crunch*.local.xml .*crunch*.local.xml
nCrunchTemp_* nCrunchTemp_*
.ncrunchsolution
# MightyMoose # MightyMoose
*.mm.* *.mm.*

View File

@@ -1,8 +1,9 @@
using System; using NUnit.Framework;
using System;
using System.IO; using System.IO;
using CliFx.Services; using CliFx.Services;
using CliFx.Tests.Stubs;
using CliFx.Tests.TestCommands; using CliFx.Tests.TestCommands;
using NUnit.Framework;
namespace CliFx.Tests namespace CliFx.Tests
{ {
@@ -20,8 +21,8 @@ namespace CliFx.Tests
builder builder
.AddCommand(typeof(HelloWorldDefaultCommand)) .AddCommand(typeof(HelloWorldDefaultCommand))
.AddCommandsFrom(typeof(HelloWorldDefaultCommand).Assembly) .AddCommandsFrom(typeof(HelloWorldDefaultCommand).Assembly)
.AddCommands(new[] {typeof(HelloWorldDefaultCommand)}) .AddCommands(new[] { typeof(HelloWorldDefaultCommand) })
.AddCommandsFrom(new[] {typeof(HelloWorldDefaultCommand).Assembly}) .AddCommandsFrom(new[] { typeof(HelloWorldDefaultCommand).Assembly })
.AddCommandsFromThisAssembly() .AddCommandsFromThisAssembly()
.AllowDebugMode() .AllowDebugMode()
.AllowPreviewMode() .AllowPreviewMode()
@@ -30,8 +31,9 @@ namespace CliFx.Tests
.UseVersionText("test") .UseVersionText("test")
.UseDescription("test") .UseDescription("test")
.UseConsole(new VirtualConsole(TextWriter.Null)) .UseConsole(new VirtualConsole(TextWriter.Null))
.UseCommandFactory(schema => (ICommand) Activator.CreateInstance(schema.Type)) .UseCommandFactory(schema => (ICommand)Activator.CreateInstance(schema.Type))
.UseCommandOptionInputConverter(new CommandOptionInputConverter()) .UseCommandOptionInputConverter(new CommandOptionInputConverter())
.UseEnvironmentVariablesProvider(new EnvironmentVariablesProviderStub())
.Build(); .Build();
} }

View File

@@ -1,11 +1,12 @@
using System; using FluentAssertions;
using NUnit.Framework;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using CliFx.Services; using CliFx.Services;
using CliFx.Tests.Stubs;
using CliFx.Tests.TestCommands; using CliFx.Tests.TestCommands;
using FluentAssertions;
using NUnit.Framework;
namespace CliFx.Tests namespace CliFx.Tests
{ {
@@ -17,104 +18,104 @@ namespace CliFx.Tests
private static IEnumerable<TestCaseData> GetTestCases_RunAsync() private static IEnumerable<TestCaseData> GetTestCases_RunAsync()
{ {
yield return new TestCaseData( yield return new TestCaseData(
new[] {typeof(HelloWorldDefaultCommand)}, new[] { typeof(HelloWorldDefaultCommand) },
new string[0], new string[0],
"Hello world." "Hello world."
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {typeof(ConcatCommand)}, new[] { typeof(ConcatCommand) },
new[] {"concat", "-i", "foo", "-i", "bar", "-s", " "}, new[] { "concat", "-i", "foo", "-i", "bar", "-s", " " },
"foo bar" "foo bar"
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {typeof(ConcatCommand)}, new[] { typeof(ConcatCommand) },
new[] {"concat", "-i", "one", "two", "three", "-s", ", "}, new[] { "concat", "-i", "one", "two", "three", "-s", ", " },
"one, two, three" "one, two, three"
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {typeof(DivideCommand)}, new[] { typeof(DivideCommand) },
new[] {"div", "-D", "24", "-d", "8"}, new[] { "div", "-D", "24", "-d", "8" },
"3" "3"
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {typeof(HelloWorldDefaultCommand)}, new[] { typeof(HelloWorldDefaultCommand) },
new[] {"--version"}, new[] { "--version" },
TestVersionText TestVersionText
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {typeof(ConcatCommand)}, new[] { typeof(ConcatCommand) },
new[] {"--version"}, new[] { "--version" },
TestVersionText TestVersionText
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {typeof(HelloWorldDefaultCommand)}, new[] { typeof(HelloWorldDefaultCommand) },
new[] {"-h"}, new[] { "-h" },
null null
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {typeof(HelloWorldDefaultCommand)}, new[] { typeof(HelloWorldDefaultCommand) },
new[] {"--help"}, new[] { "--help" },
null null
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {typeof(ConcatCommand)}, new[] { typeof(ConcatCommand) },
new string[0], new string[0],
null null
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {typeof(ConcatCommand)}, new[] { typeof(ConcatCommand) },
new[] {"-h"}, new[] { "-h" },
null null
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {typeof(ConcatCommand)}, new[] { typeof(ConcatCommand) },
new[] {"--help"}, new[] { "--help" },
null null
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {typeof(ConcatCommand)}, new[] { typeof(ConcatCommand) },
new[] {"concat", "-h"}, new[] { "concat", "-h" },
null null
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {typeof(ExceptionCommand)}, new[] { typeof(ExceptionCommand) },
new[] {"exc", "-h"}, new[] { "exc", "-h" },
null null
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {typeof(CommandExceptionCommand)}, new[] { typeof(CommandExceptionCommand) },
new[] {"exc", "-h"}, new[] { "exc", "-h" },
null null
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {typeof(ConcatCommand)}, new[] { typeof(ConcatCommand) },
new[] {"[preview]"}, new[] { "[preview]" },
null null
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {typeof(ExceptionCommand)}, new[] { typeof(ExceptionCommand) },
new[] {"exc", "[preview]"}, new[] { "exc", "[preview]" },
null null
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {typeof(ConcatCommand)}, new[] { typeof(ConcatCommand) },
new[] {"concat", "[preview]", "-o", "value"}, new[] { "concat", "[preview]", "-o", "value" },
null null
); );
} }
@@ -128,38 +129,38 @@ namespace CliFx.Tests
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {typeof(ConcatCommand)}, new[] { typeof(ConcatCommand) },
new[] {"non-existing"}, new[] { "non-existing" },
null, null null, null
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {typeof(ExceptionCommand)}, new[] { typeof(ExceptionCommand) },
new[] {"exc"}, new[] { "exc" },
null, null null, null
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {typeof(CommandExceptionCommand)}, new[] { typeof(CommandExceptionCommand) },
new[] {"exc"}, new[] { "exc" },
null, null null, null
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {typeof(CommandExceptionCommand)}, new[] { typeof(CommandExceptionCommand) },
new[] {"exc"}, new[] { "exc" },
null, null null, null
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {typeof(CommandExceptionCommand)}, new[] { typeof(CommandExceptionCommand) },
new[] {"exc", "-m", "foo bar"}, new[] { "exc", "-m", "foo bar" },
"foo bar", null "foo bar", null
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {typeof(CommandExceptionCommand)}, new[] { typeof(CommandExceptionCommand) },
new[] {"exc", "-m", "foo bar", "-c", "666"}, new[] { "exc", "-m", "foo bar", "-c", "666" },
"foo bar", 666 "foo bar", 666
); );
} }
@@ -173,11 +174,13 @@ namespace CliFx.Tests
using (var stdoutStream = new StringWriter()) using (var stdoutStream = new StringWriter())
{ {
var console = new VirtualConsole(stdoutStream); var console = new VirtualConsole(stdoutStream);
var environmentVariablesProvider = new EnvironmentVariablesProviderStub();
var application = new CliApplicationBuilder() var application = new CliApplicationBuilder()
.AddCommands(commandTypes) .AddCommands(commandTypes)
.UseVersionText(TestVersionText) .UseVersionText(TestVersionText)
.UseConsole(console) .UseConsole(console)
.UseEnvironmentVariablesProvider(environmentVariablesProvider)
.Build(); .Build();
// Act // Act
@@ -203,10 +206,12 @@ namespace CliFx.Tests
using (var stderrStream = new StringWriter()) using (var stderrStream = new StringWriter())
{ {
var console = new VirtualConsole(TextWriter.Null, stderrStream); var console = new VirtualConsole(TextWriter.Null, stderrStream);
var environmentVariablesProvider = new EnvironmentVariablesProviderStub();
var application = new CliApplicationBuilder() var application = new CliApplicationBuilder()
.AddCommands(commandTypes) .AddCommands(commandTypes)
.UseVersionText(TestVersionText) .UseVersionText(TestVersionText)
.UseEnvironmentVariablesProvider(environmentVariablesProvider)
.UseConsole(console) .UseConsole(console)
.Build(); .Build();

View File

@@ -1,12 +1,13 @@
using System; using FluentAssertions;
using NUnit.Framework;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using CliFx.Exceptions; using CliFx.Exceptions;
using CliFx.Models; using CliFx.Models;
using CliFx.Services; using CliFx.Services;
using CliFx.Tests.TestCommands; using CliFx.Tests.TestCommands;
using FluentAssertions; using CliFx.Tests.Stubs;
using NUnit.Framework;
namespace CliFx.Tests.Services namespace CliFx.Tests.Services
{ {
@@ -14,7 +15,7 @@ namespace CliFx.Tests.Services
public class CommandInitializerTests public class CommandInitializerTests
{ {
private static CommandSchema GetCommandSchema(Type commandType) => private static CommandSchema GetCommandSchema(Type commandType) =>
new CommandSchemaResolver().GetCommandSchemas(new[] {commandType}).Single(); new CommandSchemaResolver().GetCommandSchemas(new[] { commandType }).Single();
private static IEnumerable<TestCaseData> GetTestCases_InitializeCommand() private static IEnumerable<TestCaseData> GetTestCases_InitializeCommand()
{ {
@@ -26,7 +27,7 @@ namespace CliFx.Tests.Services
new CommandOptionInput("dividend", "13"), new CommandOptionInput("dividend", "13"),
new CommandOptionInput("divisor", "8") new CommandOptionInput("divisor", "8")
}), }),
new DivideCommand {Dividend = 13, Divisor = 8} new DivideCommand { Dividend = 13, Divisor = 8 }
); );
yield return new TestCaseData( yield return new TestCaseData(
@@ -37,7 +38,7 @@ namespace CliFx.Tests.Services
new CommandOptionInput("dividend", "13"), new CommandOptionInput("dividend", "13"),
new CommandOptionInput("d", "8") new CommandOptionInput("d", "8")
}), }),
new DivideCommand {Dividend = 13, Divisor = 8} new DivideCommand { Dividend = 13, Divisor = 8 }
); );
yield return new TestCaseData( yield return new TestCaseData(
@@ -48,7 +49,7 @@ namespace CliFx.Tests.Services
new CommandOptionInput("D", "13"), new CommandOptionInput("D", "13"),
new CommandOptionInput("d", "8") new CommandOptionInput("d", "8")
}), }),
new DivideCommand {Dividend = 13, Divisor = 8} new DivideCommand { Dividend = 13, Divisor = 8 }
); );
yield return new TestCaseData( yield return new TestCaseData(
@@ -58,7 +59,7 @@ namespace CliFx.Tests.Services
{ {
new CommandOptionInput("i", new[] {"foo", " ", "bar"}) new CommandOptionInput("i", new[] {"foo", " ", "bar"})
}), }),
new ConcatCommand {Inputs = new[] {"foo", " ", "bar"}} new ConcatCommand { Inputs = new[] { "foo", " ", "bar" } }
); );
yield return new TestCaseData( yield return new TestCaseData(
@@ -69,7 +70,43 @@ namespace CliFx.Tests.Services
new CommandOptionInput("i", new[] {"foo", "bar"}), new CommandOptionInput("i", new[] {"foo", "bar"}),
new CommandOptionInput("s", " ") new CommandOptionInput("s", " ")
}), }),
new ConcatCommand {Inputs = new[] {"foo", "bar"}, Separator = " "} new ConcatCommand { Inputs = new[] { "foo", "bar" }, Separator = " " }
);
//Will read a value from environment variables because none is supplied via CommandInput
yield return new TestCaseData(
new EnvironmentVariableCommand(),
GetCommandSchema(typeof(EnvironmentVariableCommand)),
new CommandInput(null, new CommandOptionInput[0], EnvironmentVariablesProviderStub.EnvironmentVariables),
new EnvironmentVariableCommand { Option = "A" }
);
//Will read multiple values from environment variables because none is supplied via CommandInput
yield return new TestCaseData(
new EnvironmentVariableWithMultipleValuesCommand(),
GetCommandSchema(typeof(EnvironmentVariableWithMultipleValuesCommand)),
new CommandInput(null, new CommandOptionInput[0], EnvironmentVariablesProviderStub.EnvironmentVariables),
new EnvironmentVariableWithMultipleValuesCommand { Option = new[] { "A", "B", "C" } }
);
//Will not read a value from environment variables because one is supplied via CommandInput
yield return new TestCaseData(
new EnvironmentVariableCommand(),
GetCommandSchema(typeof(EnvironmentVariableCommand)),
new CommandInput(null, new[]
{
new CommandOptionInput("opt", new[] { "X" })
},
EnvironmentVariablesProviderStub.EnvironmentVariables),
new EnvironmentVariableCommand { Option = "X" }
);
//Will not split environment variable values because underlying property is not a collection
yield return new TestCaseData(
new EnvironmentVariableWithoutCollectionPropertyCommand(),
GetCommandSchema(typeof(EnvironmentVariableWithoutCollectionPropertyCommand)),
new CommandInput(null, new CommandOptionInput[0], EnvironmentVariablesProviderStub.EnvironmentVariables),
new EnvironmentVariableWithoutCollectionPropertyCommand { Option = "A;B;C;" }
); );
} }

View File

@@ -1,8 +1,9 @@
using System.Collections.Generic; using FluentAssertions;
using NUnit.Framework;
using System.Collections.Generic;
using CliFx.Models; using CliFx.Models;
using CliFx.Services; using CliFx.Services;
using FluentAssertions; using CliFx.Tests.Stubs;
using NUnit.Framework;
namespace CliFx.Tests.Services namespace CliFx.Tests.Services
{ {
@@ -11,203 +12,238 @@ namespace CliFx.Tests.Services
{ {
private static IEnumerable<TestCaseData> GetTestCases_ParseCommandInput() private static IEnumerable<TestCaseData> GetTestCases_ParseCommandInput()
{ {
yield return new TestCaseData(new string[0], CommandInput.Empty); yield return new TestCaseData(new string[0], CommandInput.Empty, new EmptyEnvironmentVariablesProviderStub());
yield return new TestCaseData( yield return new TestCaseData(
new[] {"--option", "value"}, new[] { "--option", "value" },
new CommandInput(new[] new CommandInput(new[]
{ {
new CommandOptionInput("option", "value") new CommandOptionInput("option", "value")
}) }),
new EmptyEnvironmentVariablesProviderStub()
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {"--option1", "value1", "--option2", "value2"}, new[] { "--option1", "value1", "--option2", "value2" },
new CommandInput(new[] new CommandInput(new[]
{ {
new CommandOptionInput("option1", "value1"), new CommandOptionInput("option1", "value1"),
new CommandOptionInput("option2", "value2") new CommandOptionInput("option2", "value2")
}) }),
new EmptyEnvironmentVariablesProviderStub()
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {"--option", "value1", "value2"}, new[] { "--option", "value1", "value2" },
new CommandInput(new[] new CommandInput(new[]
{ {
new CommandOptionInput("option", new[] {"value1", "value2"}) new CommandOptionInput("option", new[] {"value1", "value2"})
}) }),
new EmptyEnvironmentVariablesProviderStub()
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {"--option", "value1", "--option", "value2"}, new[] { "--option", "value1", "--option", "value2" },
new CommandInput(new[] new CommandInput(new[]
{ {
new CommandOptionInput("option", new[] {"value1", "value2"}) new CommandOptionInput("option", new[] {"value1", "value2"})
}) }),
new EmptyEnvironmentVariablesProviderStub()
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {"-a", "value"}, new[] { "-a", "value" },
new CommandInput(new[] new CommandInput(new[]
{ {
new CommandOptionInput("a", "value") new CommandOptionInput("a", "value")
}) }),
new EmptyEnvironmentVariablesProviderStub()
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {"-a", "value1", "-b", "value2"}, new[] { "-a", "value1", "-b", "value2" },
new CommandInput(new[] new CommandInput(new[]
{ {
new CommandOptionInput("a", "value1"), new CommandOptionInput("a", "value1"),
new CommandOptionInput("b", "value2") new CommandOptionInput("b", "value2")
}) }),
new EmptyEnvironmentVariablesProviderStub()
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {"-a", "value1", "value2"}, new[] { "-a", "value1", "value2" },
new CommandInput(new[] new CommandInput(new[]
{ {
new CommandOptionInput("a", new[] {"value1", "value2"}) new CommandOptionInput("a", new[] {"value1", "value2"})
}) }),
new EmptyEnvironmentVariablesProviderStub()
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {"-a", "value1", "-a", "value2"}, new[] { "-a", "value1", "-a", "value2" },
new CommandInput(new[] new CommandInput(new[]
{ {
new CommandOptionInput("a", new[] {"value1", "value2"}) new CommandOptionInput("a", new[] {"value1", "value2"})
}) }),
new EmptyEnvironmentVariablesProviderStub()
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {"--option1", "value1", "-b", "value2"}, new[] { "--option1", "value1", "-b", "value2" },
new CommandInput(new[] new CommandInput(new[]
{ {
new CommandOptionInput("option1", "value1"), new CommandOptionInput("option1", "value1"),
new CommandOptionInput("b", "value2") new CommandOptionInput("b", "value2")
}) }),
new EmptyEnvironmentVariablesProviderStub()
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {"--switch"}, new[] { "--switch" },
new CommandInput(new[] new CommandInput(new[]
{ {
new CommandOptionInput("switch") new CommandOptionInput("switch")
}) }),
new EmptyEnvironmentVariablesProviderStub()
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {"--switch1", "--switch2"}, new[] { "--switch1", "--switch2" },
new CommandInput(new[] new CommandInput(new[]
{ {
new CommandOptionInput("switch1"), new CommandOptionInput("switch1"),
new CommandOptionInput("switch2") new CommandOptionInput("switch2")
}) }),
new EmptyEnvironmentVariablesProviderStub()
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {"-s"}, new[] { "-s" },
new CommandInput(new[] new CommandInput(new[]
{ {
new CommandOptionInput("s") new CommandOptionInput("s")
}) }),
new EmptyEnvironmentVariablesProviderStub()
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {"-a", "-b"}, new[] { "-a", "-b" },
new CommandInput(new[] new CommandInput(new[]
{ {
new CommandOptionInput("a"), new CommandOptionInput("a"),
new CommandOptionInput("b") new CommandOptionInput("b")
}) }),
new EmptyEnvironmentVariablesProviderStub()
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {"-ab"}, new[] { "-ab" },
new CommandInput(new[] new CommandInput(new[]
{ {
new CommandOptionInput("a"), new CommandOptionInput("a"),
new CommandOptionInput("b") new CommandOptionInput("b")
}) }),
new EmptyEnvironmentVariablesProviderStub()
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {"-ab", "value"}, new[] { "-ab", "value" },
new CommandInput(new[] new CommandInput(new[]
{ {
new CommandOptionInput("a"), new CommandOptionInput("a"),
new CommandOptionInput("b", "value") new CommandOptionInput("b", "value")
}) }),
new EmptyEnvironmentVariablesProviderStub()
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {"command"}, new[] { "command" },
new CommandInput("command") new CommandInput("command"),
new EmptyEnvironmentVariablesProviderStub()
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {"command", "--option", "value"}, new[] { "command", "--option", "value" },
new CommandInput("command", new[] new CommandInput("command", new[]
{ {
new CommandOptionInput("option", "value") new CommandOptionInput("option", "value")
}) }),
new EmptyEnvironmentVariablesProviderStub()
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {"long", "command", "name"}, new[] { "long", "command", "name" },
new CommandInput("long command name") new CommandInput("long command name"),
new EmptyEnvironmentVariablesProviderStub()
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {"long", "command", "name", "--option", "value"}, new[] { "long", "command", "name", "--option", "value" },
new CommandInput("long command name", new[] new CommandInput("long command name", new[]
{ {
new CommandOptionInput("option", "value") new CommandOptionInput("option", "value")
}) }),
new EmptyEnvironmentVariablesProviderStub()
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {"[debug]"}, new[] { "[debug]" },
new CommandInput(null, new CommandInput(null,
new[] {"debug"}, new[] { "debug" },
new CommandOptionInput[0]) new CommandOptionInput[0]),
new EmptyEnvironmentVariablesProviderStub()
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {"[debug]", "[preview]"}, new[] { "[debug]", "[preview]" },
new CommandInput(null, new CommandInput(null,
new[] {"debug", "preview"}, new[] { "debug", "preview" },
new CommandOptionInput[0]) new CommandOptionInput[0]),
new EmptyEnvironmentVariablesProviderStub()
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {"[debug]", "[preview]", "-o", "value"}, new[] { "[debug]", "[preview]", "-o", "value" },
new CommandInput(null, new CommandInput(null,
new[] {"debug", "preview"}, new[] { "debug", "preview" },
new[] new[]
{ {
new CommandOptionInput("o", "value") new CommandOptionInput("o", "value")
}) }),
new EmptyEnvironmentVariablesProviderStub()
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {"command", "[debug]", "[preview]", "-o", "value"}, new[] { "command", "[debug]", "[preview]", "-o", "value" },
new CommandInput("command", new CommandInput("command",
new[] {"debug", "preview"}, new[] { "debug", "preview" },
new[] new[]
{ {
new CommandOptionInput("o", "value") new CommandOptionInput("o", "value")
}) }),
new EmptyEnvironmentVariablesProviderStub()
);
yield return new TestCaseData(
new[] { "command", "[debug]", "[preview]", "-o", "value" },
new CommandInput("command",
new[] { "debug", "preview" },
new[]
{
new CommandOptionInput("o", "value")
},
EnvironmentVariablesProviderStub.EnvironmentVariables),
new EnvironmentVariablesProviderStub()
); );
} }
[Test] [Test]
[TestCaseSource(nameof(GetTestCases_ParseCommandInput))] [TestCaseSource(nameof(GetTestCases_ParseCommandInput))]
public void ParseCommandInput_Test(IReadOnlyList<string> commandLineArguments, public void ParseCommandInput_Test(IReadOnlyList<string> commandLineArguments,
CommandInput expectedCommandInput) CommandInput expectedCommandInput, IEnvironmentVariablesProvider environmentVariablesProvider)
{ {
// Arrange // Arrange
var parser = new CommandInputParser(); var parser = new CommandInputParser(environmentVariablesProvider);
// Act // Act
var commandInput = parser.ParseCommandInput(commandLineArguments); var commandInput = parser.ParseCommandInput(commandLineArguments);

View File

@@ -1,11 +1,11 @@
using System; using FluentAssertions;
using NUnit.Framework;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using CliFx.Exceptions; using CliFx.Exceptions;
using CliFx.Models; using CliFx.Models;
using CliFx.Services; using CliFx.Services;
using CliFx.Tests.TestCommands; using CliFx.Tests.TestCommands;
using FluentAssertions;
using NUnit.Framework;
namespace CliFx.Tests.Services namespace CliFx.Tests.Services
{ {
@@ -15,30 +15,37 @@ namespace CliFx.Tests.Services
private static IEnumerable<TestCaseData> GetTestCases_GetCommandSchemas() private static IEnumerable<TestCaseData> GetTestCases_GetCommandSchemas()
{ {
yield return new TestCaseData( yield return new TestCaseData(
new[] {typeof(DivideCommand), typeof(ConcatCommand)}, new[] { typeof(DivideCommand), typeof(ConcatCommand), typeof(EnvironmentVariableCommand) },
new[] new[]
{ {
new CommandSchema(typeof(DivideCommand), "div", "Divide one number by another.", new CommandSchema(typeof(DivideCommand), "div", "Divide one number by another.",
new[] new[]
{ {
new CommandOptionSchema(typeof(DivideCommand).GetProperty(nameof(DivideCommand.Dividend)), new CommandOptionSchema(typeof(DivideCommand).GetProperty(nameof(DivideCommand.Dividend)),
"dividend", 'D', true, "The number to divide."), "dividend", 'D', true, "The number to divide.", null),
new CommandOptionSchema(typeof(DivideCommand).GetProperty(nameof(DivideCommand.Divisor)), new CommandOptionSchema(typeof(DivideCommand).GetProperty(nameof(DivideCommand.Divisor)),
"divisor", 'd', true, "The number to divide by.") "divisor", 'd', true, "The number to divide by.", null)
}), }),
new CommandSchema(typeof(ConcatCommand), "concat", "Concatenate strings.", new CommandSchema(typeof(ConcatCommand), "concat", "Concatenate strings.",
new[] new[]
{ {
new CommandOptionSchema(typeof(ConcatCommand).GetProperty(nameof(ConcatCommand.Inputs)), new CommandOptionSchema(typeof(ConcatCommand).GetProperty(nameof(ConcatCommand.Inputs)),
null, 'i', true, "Input strings."), null, 'i', true, "Input strings.", null),
new CommandOptionSchema(typeof(ConcatCommand).GetProperty(nameof(ConcatCommand.Separator)), new CommandOptionSchema(typeof(ConcatCommand).GetProperty(nameof(ConcatCommand.Separator)),
null, 's', false, "String separator.") null, 's', false, "String separator.", null)
}) }),
new CommandSchema(typeof(EnvironmentVariableCommand), null, "Reads option values from environment variables.",
new[]
{
new CommandOptionSchema(typeof(EnvironmentVariableCommand).GetProperty(nameof(EnvironmentVariableCommand.Option)),
"opt", null, false, null, "ENV_SINGLE_VALUE")
}
)
} }
); );
yield return new TestCaseData( yield return new TestCaseData(
new[] {typeof(HelloWorldDefaultCommand)}, new[] { typeof(HelloWorldDefaultCommand) },
new[] new[]
{ {
new CommandSchema(typeof(HelloWorldDefaultCommand), null, null, new CommandOptionSchema[0]) new CommandSchema(typeof(HelloWorldDefaultCommand), null, null, new CommandOptionSchema[0])

View File

@@ -0,0 +1,10 @@
using System.Collections.Generic;
using CliFx.Services;
namespace CliFx.Tests.Stubs
{
public class EmptyEnvironmentVariablesProviderStub : IEnvironmentVariablesProvider
{
public IReadOnlyDictionary<string, string> GetEnvironmentVariables() => new Dictionary<string, string>();
}
}

View File

@@ -0,0 +1,18 @@
using System.Collections.Generic;
using System.IO;
using CliFx.Services;
namespace CliFx.Tests.Stubs
{
public class EnvironmentVariablesProviderStub : IEnvironmentVariablesProvider
{
public static readonly Dictionary<string, string> EnvironmentVariables = new Dictionary<string, string>
{
["ENV_SINGLE_VALUE"] = "A",
["ENV_MULTIPLE_VALUES"] = $"A{Path.PathSeparator}B{Path.PathSeparator}C{Path.PathSeparator}",
["ENV_ESCAPED_MULTIPLE_VALUES"] = $"\"A{Path.PathSeparator}B{Path.PathSeparator}C{Path.PathSeparator}\""
};
public IReadOnlyDictionary<string, string> GetEnvironmentVariables() => EnvironmentVariables;
}
}

View File

@@ -0,0 +1,15 @@
using System.Threading.Tasks;
using CliFx.Attributes;
using CliFx.Services;
namespace CliFx.Tests.TestCommands
{
[Command(Description = "Reads option values from environment variables.")]
public class EnvironmentVariableCommand : ICommand
{
[CommandOption("opt", EnvironmentVariableName = "ENV_SINGLE_VALUE")]
public string Option { get; set; }
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
}
}

View File

@@ -0,0 +1,16 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using CliFx.Attributes;
using CliFx.Services;
namespace CliFx.Tests.TestCommands
{
[Command(Description = "Reads multiple option values from environment variables.")]
public class EnvironmentVariableWithMultipleValuesCommand : ICommand
{
[CommandOption("opt", EnvironmentVariableName = "ENV_MULTIPLE_VALUES")]
public IEnumerable<string> Option { get; set; }
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
}
}

View File

@@ -0,0 +1,16 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using CliFx.Attributes;
using CliFx.Services;
namespace CliFx.Tests.TestCommands
{
[Command(Description = "Reads one option value from environment variables because target property is not a collection.")]
public class EnvironmentVariableWithoutCollectionPropertyCommand : ICommand
{
[CommandOption("opt", EnvironmentVariableName = "ENV_MULTIPLE_VALUES")]
public string Option { get; set; }
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
}
}

View File

@@ -28,6 +28,11 @@ namespace CliFx.Attributes
/// </summary> /// </summary>
public string Description { get; set; } public string Description { get; set; }
/// <summary>
/// Optional environment variable name that will be used as fallback value if no option value is specified.
/// </summary>
public string EnvironmentVariableName { get; set; }
/// <summary> /// <summary>
/// Initializes an instance of <see cref="CommandOptionAttribute"/>. /// Initializes an instance of <see cref="CommandOptionAttribute"/>.
/// </summary> /// </summary>
@@ -41,7 +46,7 @@ namespace CliFx.Attributes
/// Initializes an instance of <see cref="CommandOptionAttribute"/>. /// Initializes an instance of <see cref="CommandOptionAttribute"/>.
/// </summary> /// </summary>
public CommandOptionAttribute(string name, char shortName) public CommandOptionAttribute(string name, char shortName)
: this(name, (char?) shortName) : this(name, (char?)shortName)
{ {
} }

View File

@@ -26,6 +26,7 @@ namespace CliFx
private IConsole _console; private IConsole _console;
private ICommandFactory _commandFactory; private ICommandFactory _commandFactory;
private ICommandOptionInputConverter _commandOptionInputConverter; private ICommandOptionInputConverter _commandOptionInputConverter;
private IEnvironmentVariablesProvider _environmentVariablesProvider;
/// <inheritdoc /> /// <inheritdoc />
public ICliApplicationBuilder AddCommand(Type commandType) public ICliApplicationBuilder AddCommand(Type commandType)
@@ -116,6 +117,13 @@ namespace CliFx
return this; return this;
} }
/// <inheritdoc />
public ICliApplicationBuilder UseEnvironmentVariablesProvider(IEnvironmentVariablesProvider environmentVariablesProvider)
{
_environmentVariablesProvider = environmentVariablesProvider.GuardNotNull(nameof(environmentVariablesProvider));
return this;
}
/// <inheritdoc /> /// <inheritdoc />
public ICliApplication Build() public ICliApplication Build()
{ {
@@ -126,14 +134,15 @@ namespace CliFx
_console = _console ?? new SystemConsole(); _console = _console ?? new SystemConsole();
_commandFactory = _commandFactory ?? new CommandFactory(); _commandFactory = _commandFactory ?? new CommandFactory();
_commandOptionInputConverter = _commandOptionInputConverter ?? new CommandOptionInputConverter(); _commandOptionInputConverter = _commandOptionInputConverter ?? new CommandOptionInputConverter();
_environmentVariablesProvider = _environmentVariablesProvider ?? new EnvironmentVariablesProvider();
// Project parameters to expected types // Project parameters to expected types
var metadata = new ApplicationMetadata(_title, _executableName, _versionText, _description); var metadata = new ApplicationMetadata(_title, _executableName, _versionText, _description);
var configuration = new ApplicationConfiguration(_commandTypes.ToArray(), _isDebugModeAllowed, _isPreviewModeAllowed); var configuration = new ApplicationConfiguration(_commandTypes.ToArray(), _isDebugModeAllowed, _isPreviewModeAllowed);
return new CliApplication(metadata, configuration, return new CliApplication(metadata, configuration,
_console, new CommandInputParser(), new CommandSchemaResolver(), _console, new CommandInputParser(_environmentVariablesProvider), new CommandSchemaResolver(),
_commandFactory, new CommandInitializer(_commandOptionInputConverter), new HelpTextRenderer()); _commandFactory, new CommandInitializer(_commandOptionInputConverter, new EnvironmentVariablesParser()), new HelpTextRenderer());
} }
} }

View File

@@ -64,6 +64,11 @@ namespace CliFx
/// </summary> /// </summary>
ICliApplicationBuilder UseCommandOptionInputConverter(ICommandOptionInputConverter converter); ICliApplicationBuilder UseCommandOptionInputConverter(ICommandOptionInputConverter converter);
/// <summary>
/// Configures application to use specified implementation of <see cref="IEnvironmentVariablesProvider"/>.
/// </summary>
ICliApplicationBuilder UseEnvironmentVariablesProvider(IEnvironmentVariablesProvider environmentVariablesProvider);
/// <summary> /// <summary>
/// Creates an instance of <see cref="ICliApplication"/> using configured parameters. /// Creates an instance of <see cref="ICliApplication"/> using configured parameters.
/// Default values are used in place of parameters that were not specified. /// Default values are used in place of parameters that were not specified.

View File

@@ -25,14 +25,36 @@ namespace CliFx.Models
/// </summary> /// </summary>
public IReadOnlyList<CommandOptionInput> Options { get; } public IReadOnlyList<CommandOptionInput> Options { get; }
/// <summary>
/// Environment variables available when the command was parsed
/// </summary>
public IReadOnlyDictionary<string, string> EnvironmentVariables { get; }
/// <summary> /// <summary>
/// Initializes an instance of <see cref="CommandInput"/>. /// Initializes an instance of <see cref="CommandInput"/>.
/// </summary> /// </summary>
public CommandInput(string commandName, IReadOnlyList<string> directives, IReadOnlyList<CommandOptionInput> options) public CommandInput(string commandName, IReadOnlyList<string> directives, IReadOnlyList<CommandOptionInput> options, IReadOnlyDictionary<string, string> environmentVariables)
{ {
CommandName = commandName; // can be null CommandName = commandName; // can be null
Directives = directives.GuardNotNull(nameof(directives)); Directives = directives.GuardNotNull(nameof(directives));
Options = options.GuardNotNull(nameof(options)); Options = options.GuardNotNull(nameof(options));
EnvironmentVariables = environmentVariables.GuardNotNull(nameof(environmentVariables));
}
/// <summary>
/// Initializes an instance of <see cref="CommandInput"/>.
/// </summary>
public CommandInput(string commandName, IReadOnlyList<string> directives, IReadOnlyList<CommandOptionInput> options)
: this(commandName, directives, options, EmptyEnvironmentVariables)
{
}
/// <summary>
/// Initializes an instance of <see cref="CommandInput"/>.
/// </summary>
public CommandInput(string commandName, IReadOnlyList<CommandOptionInput> options, IReadOnlyDictionary<string, string> environmentVariables)
: this(commandName, EmptyDirectives, options, environmentVariables)
{
} }
/// <summary> /// <summary>
@@ -87,6 +109,7 @@ namespace CliFx.Models
{ {
private static readonly IReadOnlyList<string> EmptyDirectives = new string[0]; private static readonly IReadOnlyList<string> EmptyDirectives = new string[0];
private static readonly IReadOnlyList<CommandOptionInput> EmptyOptions = new CommandOptionInput[0]; private static readonly IReadOnlyList<CommandOptionInput> EmptyOptions = new CommandOptionInput[0];
private static readonly IReadOnlyDictionary<string, string> EmptyEnvironmentVariables = new Dictionary<string, string>();
/// <summary> /// <summary>
/// Empty input. /// Empty input.

View File

@@ -34,16 +34,22 @@ namespace CliFx.Models
/// </summary> /// </summary>
public string Description { get; } public string Description { get; }
/// <summary>
/// Optional environment variable name that will be used as fallback value if no option value is specified.
/// </summary>
public string EnvironmentVariableName { get; }
/// <summary> /// <summary>
/// Initializes an instance of <see cref="CommandOptionSchema"/>. /// Initializes an instance of <see cref="CommandOptionSchema"/>.
/// </summary> /// </summary>
public CommandOptionSchema(PropertyInfo property, string name, char? shortName, bool isRequired, string description) public CommandOptionSchema(PropertyInfo property, string name, char? shortName, bool isRequired, string description, string environmentVariableName)
{ {
Property = property; // can be null Property = property; // can be null
Name = name; // can be null Name = name; // can be null
ShortName = shortName; // can be null ShortName = shortName; // can be null
IsRequired = isRequired; IsRequired = isRequired;
Description = description; // can be null Description = description; // can be null
EnvironmentVariableName = environmentVariableName; //can be null
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -75,9 +81,9 @@ namespace CliFx.Models
// ...in CliApplication (when reading) and HelpTextRenderer (when writing). // ...in CliApplication (when reading) and HelpTextRenderer (when writing).
internal static CommandOptionSchema HelpOption { get; } = internal static CommandOptionSchema HelpOption { get; } =
new CommandOptionSchema(null, "help", 'h', false, "Shows help text."); new CommandOptionSchema(null, "help", 'h', false, "Shows help text.", null);
internal static CommandOptionSchema VersionOption { get; } = internal static CommandOptionSchema VersionOption { get; } =
new CommandOptionSchema(null, "version", null, false, "Shows version information."); new CommandOptionSchema(null, "version", null, false, "Shows version information.", null);
} }
} }

View File

@@ -1,7 +1,7 @@
using System; using CliFx.Internal;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using CliFx.Internal;
namespace CliFx.Models namespace CliFx.Models
{ {
@@ -73,14 +73,14 @@ namespace CliFx.Models
} }
/// <summary> /// <summary>
/// Finds an option that matches specified alias, or null if not found. /// Finds an option input that matches the option schema specified, or null if not found.
/// </summary> /// </summary>
public static CommandOptionSchema FindByAlias(this IReadOnlyList<CommandOptionSchema> optionSchemas, string alias) public static CommandOptionInput FindByOptionSchema(this IReadOnlyList<CommandOptionInput> optionInputs, CommandOptionSchema optionSchema)
{ {
optionSchemas.GuardNotNull(nameof(optionSchemas)); optionInputs.GuardNotNull(nameof(optionInputs));
alias.GuardNotNull(nameof(alias)); optionSchema.GuardNotNull(nameof(optionSchema));
return optionSchemas.FirstOrDefault(o => o.MatchesAlias(alias)); return optionInputs.FirstOrDefault(o => optionSchema.MatchesAlias(o.Alias));
} }
/// <summary> /// <summary>

View File

@@ -11,20 +11,30 @@ namespace CliFx.Services
public class CommandInitializer : ICommandInitializer public class CommandInitializer : ICommandInitializer
{ {
private readonly ICommandOptionInputConverter _commandOptionInputConverter; private readonly ICommandOptionInputConverter _commandOptionInputConverter;
private readonly IEnvironmentVariablesParser _environmentVariablesParser;
/// <summary> /// <summary>
/// Initializes an instance of <see cref="CommandInitializer"/>. /// Initializes an instance of <see cref="CommandInitializer"/>.
/// </summary> /// </summary>
public CommandInitializer(ICommandOptionInputConverter commandOptionInputConverter) public CommandInitializer(ICommandOptionInputConverter commandOptionInputConverter, IEnvironmentVariablesParser environmentVariablesParser)
{ {
_commandOptionInputConverter = commandOptionInputConverter.GuardNotNull(nameof(commandOptionInputConverter)); _commandOptionInputConverter = commandOptionInputConverter.GuardNotNull(nameof(commandOptionInputConverter));
_environmentVariablesParser = environmentVariablesParser.GuardNotNull(nameof(environmentVariablesParser));
}
/// <summary>
/// Initializes an instance of <see cref="CommandInitializer"/>.
/// </summary>
public CommandInitializer(IEnvironmentVariablesParser environmentVariablesParser)
: this(new CommandOptionInputConverter(), environmentVariablesParser)
{
} }
/// <summary> /// <summary>
/// Initializes an instance of <see cref="CommandInitializer"/>. /// Initializes an instance of <see cref="CommandInitializer"/>.
/// </summary> /// </summary>
public CommandInitializer() public CommandInitializer()
: this(new CommandOptionInputConverter()) : this(new CommandOptionInputConverter(), new EnvironmentVariablesParser())
{ {
} }
@@ -38,15 +48,28 @@ namespace CliFx.Services
// Keep track of unset required options to report an error at a later stage // Keep track of unset required options to report an error at a later stage
var unsetRequiredOptions = commandSchema.Options.Where(o => o.IsRequired).ToList(); var unsetRequiredOptions = commandSchema.Options.Where(o => o.IsRequired).ToList();
// Set command options //Set command options
foreach (var optionInput in commandInput.Options) foreach (var optionSchema in commandSchema.Options)
{ {
// Find matching option schema for this option input //Find matching option input
var optionSchema = commandSchema.Options.FindByAlias(optionInput.Alias); var optionInput = commandInput.Options.FindByOptionSchema(optionSchema);
if (optionSchema == null)
//If no option input is available fall back to environment variable values
if (optionInput == null && !optionSchema.EnvironmentVariableName.IsNullOrWhiteSpace())
{
var fallbackEnvironmentVariableExists = commandInput.EnvironmentVariables.ContainsKey(optionSchema.EnvironmentVariableName);
//If no environment variable is found or there is no valid value for this option skip it
if (!fallbackEnvironmentVariableExists || commandInput.EnvironmentVariables[optionSchema.EnvironmentVariableName].IsNullOrWhiteSpace())
continue;
optionInput = _environmentVariablesParser.GetCommandOptionInputFromEnvironmentVariable(commandInput.EnvironmentVariables[optionSchema.EnvironmentVariableName], optionSchema);
}
//No fallback available and no option input was specified, skip option
if (optionInput == null)
continue; continue;
// Convert option to the type of the underlying property
var convertedValue = _commandOptionInputConverter.ConvertOptionInput(optionInput, optionSchema.Property.PropertyType); var convertedValue = _commandOptionInputConverter.ConvertOptionInput(optionInput, optionSchema.Property.PropertyType);
// Set value of the underlying property // Set value of the underlying property

View File

@@ -12,6 +12,26 @@ namespace CliFx.Services
/// </summary> /// </summary>
public class CommandInputParser : ICommandInputParser public class CommandInputParser : ICommandInputParser
{ {
private readonly IEnvironmentVariablesProvider _environmentVariablesProvider;
/// <summary>
/// Initializes an instance of <see cref="CommandInputParser"/>
/// </summary>
public CommandInputParser(IEnvironmentVariablesProvider environmentVariablesProvider)
{
environmentVariablesProvider.GuardNotNull(nameof(environmentVariablesProvider));
_environmentVariablesProvider = environmentVariablesProvider;
}
/// <summary>
/// Initializes an instance of <see cref="CommandInputParser"/>
/// </summary>
public CommandInputParser()
: this(new EnvironmentVariablesProvider())
{
}
/// <inheritdoc /> /// <inheritdoc />
public CommandInput ParseCommandInput(IReadOnlyList<string> commandLineArguments) public CommandInput ParseCommandInput(IReadOnlyList<string> commandLineArguments)
{ {
@@ -78,7 +98,9 @@ namespace CliFx.Services
var commandName = commandNameBuilder.Length > 0 ? commandNameBuilder.ToString() : null; var commandName = commandNameBuilder.Length > 0 ? commandNameBuilder.ToString() : null;
var options = optionsDic.Select(p => new CommandOptionInput(p.Key, p.Value)).ToArray(); var options = optionsDic.Select(p => new CommandOptionInput(p.Key, p.Value)).ToArray();
return new CommandInput(commandName, directives, options); var environmentVariables = _environmentVariablesProvider.GetEnvironmentVariables();
return new CommandInput(commandName, directives, options, environmentVariables);
} }
} }
} }

View File

@@ -31,7 +31,8 @@ namespace CliFx.Services
attribute.Name, attribute.Name,
attribute.ShortName, attribute.ShortName,
attribute.IsRequired, attribute.IsRequired,
attribute.Description); attribute.Description,
attribute.EnvironmentVariableName);
// Make sure there are no other options with the same name // Make sure there are no other options with the same name
var existingOptionWithSameName = result var existingOptionWithSameName = result

View File

@@ -0,0 +1,30 @@
using System.IO;
using System.Linq;
using CliFx.Internal;
using CliFx.Models;
namespace CliFx.Services
{
/// <inheritdoct />
public class EnvironmentVariablesParser : IEnvironmentVariablesParser
{
/// <inheritdoct />
public CommandOptionInput GetCommandOptionInputFromEnvironmentVariable(string environmentVariableValue, CommandOptionSchema targetOptionSchema)
{
environmentVariableValue.GuardNotNull(nameof(environmentVariableValue));
targetOptionSchema.GuardNotNull(nameof(targetOptionSchema));
//If the option is not a collection do not split environment variable values
var optionIsCollection = targetOptionSchema.Property.PropertyType.IsCollection();
if (!optionIsCollection) return new CommandOptionInput(targetOptionSchema.EnvironmentVariableName, environmentVariableValue);
//If the option is a collection split the values using System separator, empty values are discarded
var environmentVariableValues = environmentVariableValue.Split(Path.PathSeparator)
.Where(v => !v.IsNullOrWhiteSpace())
.ToList();
return new CommandOptionInput(targetOptionSchema.EnvironmentVariableName, environmentVariableValues);
}
}
}

View File

@@ -0,0 +1,36 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Security;
namespace CliFx.Services
{
/// <inheritdoc />
public class EnvironmentVariablesProvider : IEnvironmentVariablesProvider
{
/// <inheritdoc />
public IReadOnlyDictionary<string, string> GetEnvironmentVariables()
{
try
{
var environmentVariables = Environment.GetEnvironmentVariables();
//Constructing the dictionary manually allows to specify a key comparer that ignores case
//This allows to ignore casing when looking for a fallback environment variable of an option
var environmentVariablesAsDictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
//Type DictionaryEntry must be explicitly used otherwise it will enumerate as a collection of objects
foreach (DictionaryEntry environmentVariable in environmentVariables)
{
environmentVariablesAsDictionary.Add(environmentVariable.Key.ToString(), environmentVariable.Value.ToString());
}
return environmentVariablesAsDictionary;
}
catch (SecurityException)
{
return new Dictionary<string, string>();
}
}
}
}

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using CliFx.Internal; using CliFx.Internal;
namespace CliFx.Services namespace CliFx.Services
@@ -50,5 +51,25 @@ namespace CliFx.Services
console.WithForegroundColor(foregroundColor, () => console.WithBackgroundColor(backgroundColor, action)); console.WithForegroundColor(foregroundColor, () => console.WithBackgroundColor(backgroundColor, action));
} }
/// <summary>
/// Gets wether a string representing an environment variable value is escaped (i.e.: surrounded by double quotation marks)
/// </summary>
public static bool IsEnvironmentVariableEscaped(this string environmentVariableValue)
{
environmentVariableValue.GuardNotNull(nameof(environmentVariableValue));
return environmentVariableValue.StartsWith("\"") && environmentVariableValue.EndsWith("\"");
}
/// <summary>
/// Gets wether the <see cref="Type"/> supplied is a collection implementing <see cref="IEnumerable{T}"/>
/// </summary>
public static bool IsCollection(this Type type)
{
type.GuardNotNull(nameof(type));
return type != typeof(string) && type.GetEnumerableUnderlyingType() != null;
}
} }
} }

View File

@@ -0,0 +1,15 @@
using CliFx.Models;
namespace CliFx.Services
{
/// <summary>
/// Parses environment variable values
/// </summary>
public interface IEnvironmentVariablesParser
{
/// <summary>
/// Parse an environment variable value and converts it to a <see cref="CommandOptionInput"/>
/// </summary>
CommandOptionInput GetCommandOptionInputFromEnvironmentVariable(string environmentVariableValue, CommandOptionSchema targetOptionSchema);
}
}

View File

@@ -0,0 +1,16 @@
using System.Collections.Generic;
namespace CliFx.Services
{
/// <summary>
/// Provides environment variable values
/// </summary>
public interface IEnvironmentVariablesProvider
{
/// <summary>
/// Returns all the environment variables available.
/// </summary>
/// <remarks>If the User is not allowed to read environment variables it will return an empty dictionary.</remarks>
IReadOnlyDictionary<string, string> GetEnvironmentVariables();
}
}