Skip to content

Commit df0288c

Browse files
committed
Updated to 2.0.0-beta5.25174.1, fix color handling
- Updated to latest daily build 2.0.0-beta5.25174.1 of System.CommandLine. - Fixed: On Unix-like platforms, Console.ForegroundColor and Console.BackgroundColor is unset/unknown so prevent setting them to ConsoleColor.Gray and ConsoleColor.Black on non-Windows platforms when theme color is null (unset). Also will try to fix color visibility by making some assumptions (e.g. on OSX background color is white) as it's impossible to detect terminal colors. - Added: [NO_COLOR](https://no-color.org/) support, i.e. if `NO_COLOR` environment variable is set, the colors will be disabled. - Improved: Added CustomizeLayout method in CliHelpBuilder to mimic the base class HelpBuilder in System.Commandline. A delegate can be passed to customize the default layout sections in help output. - Fixed: System.CommandLine.RootCommand returned by Cli.GetConfiguration should have correct parents. This did not effect the working of Cli, however for metadata purpose we will populate accurate command hierarchy even if Cli.GetConfiguration is called for a command which is in the middle of the hierarchy.
1 parent b737cd3 commit df0288c

File tree

77 files changed

+7232
-80
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+7232
-80
lines changed

docs/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ What if you had an easy class-based layer combined with a good parser?
88

99
DotMake.CommandLine is a library which provides declarative syntax for
1010
[System.CommandLine](https://github.com/dotnet/command-line-api)
11-
via attributes for easy, fast, strongly-typed (no reflection) usage. The library includes includes a source generator
11+
via attributes for easy, fast, strongly-typed (no reflection) usage. The library includes a source generator
1212
which automagically converts your classes to CLI commands and properties to CLI options or CLI arguments.
1313
Supports
1414
[trimming](https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trim-self-contained),
@@ -329,6 +329,9 @@ Cli.Run<RootCliCommand>(args, new CliSettings
329329
}
330330
});
331331
```
332+
333+
Note that [NO_COLOR](https://no-color.org/) is supported, i.e. if `NO_COLOR` environment variable is set, the colors will be disabled.
334+
332335
### Localization
333336

334337
Localizing commands, options and arguments is supported.

src/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<!-- https://learn.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-props#generateassemblyinfo -->
5-
<VersionPrefix>1.9.0</VersionPrefix>
5+
<VersionPrefix>2.0.0</VersionPrefix>
66
<Product>DotMake Command-Line</Product>
77
<Company>DotMake</Company>
88
<!-- Copyright is also used for NuGet metadata -->

src/DotMake.CommandLine.Shared/Attributes/CliArgumentAttribute.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ namespace DotMake.CommandLine
2828
/// <example>
2929
/// <inheritdoc cref="Cli" path="/example/code[@id='gettingStartedDelegate']" />
3030
/// <inheritdoc cref="Cli" path="/example/code[@id='gettingStartedClass']" />
31+
/// <inheritdoc cref="Cli" path="/example/code[@id='gettingStartedClass2']" />
3132
/// <code source="..\TestApp\Commands\ParentCommandAccessorCliCommand.cs" region="ParentCommandAccessorCliCommand" language="cs" />
3233
/// </example>
3334
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter)]

src/DotMake.CommandLine.Shared/Attributes/CliCommandAttribute.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ namespace DotMake.CommandLine
3636
/// </summary>
3737
/// <example>
3838
/// <inheritdoc cref="Cli" path="/example/code[@id='gettingStartedClass']" />
39+
/// <inheritdoc cref="Cli" path="/example/code[@id='gettingStartedClass2']" />
3940
/// <code source="..\TestApp\Commands\WriteFileCliCommand.cs" region="WriteFileCliCommand" language="cs" />
4041
/// <code source="..\TestApp\Commands\ArgumentConverterCliCommand.cs" region="ArgumentConverterCliCommand" language="cs" />
4142
/// <code source="..\TestApp\Commands\EnumerableCliCommand.cs" region="EnumerableCliCommand" language="cs" />

src/DotMake.CommandLine.Shared/Attributes/CliOptionAttribute.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ namespace DotMake.CommandLine
4242
/// <example>
4343
/// <inheritdoc cref="Cli" path="/example/code[@id='gettingStartedDelegate']" />
4444
/// <inheritdoc cref="Cli" path="/example/code[@id='gettingStartedClass']" />
45+
/// <inheritdoc cref="Cli" path="/example/code[@id='gettingStartedClass2']" />
4546
/// <code source="..\TestApp\Commands\RecursiveOptionCliCommand.cs" region="RecursiveOptionCliCommand" language="cs" />
4647
/// <code source="..\TestApp\Commands\ParentCommandAccessorCliCommand.cs" region="ParentCommandAccessorCliCommand" language="cs" />
4748
/// <code source="..\TestApp\Commands\OptionBundlingCliCommand.cs" region="OptionBundlingCliCommand" language="cs" />

src/DotMake.CommandLine.SourceGeneration/CliCommandInfo.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,12 +337,17 @@ public void AppendCSharpDefineString(CodeStringBuilder sb, bool addNamespaceBloc
337337
sb.AppendLine($"{varCommand}.Add({varArgument});");
338338
}
339339

340+
/*
341+
From now on, we will handle this in Cli.GetConfiguration where Build() is called and command is created.
342+
We don't want it to be recursive here, because we will also create the parents.
343+
340344
sb.AppendLine();
341345
sb.AppendLine("// Add nested or external registered children");
342346
using (sb.AppendBlockStart("foreach (var child in Children)"))
343347
{
344348
sb.AppendLine($"{varCommand}.Add(child.Build());");
345349
}
350+
*/
346351

347352
sb.AppendLine();
348353
var varParseResult = "parseResult";

src/DotMake.CommandLine/Cli.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.CommandLine;
34
using System.CommandLine.Completions;
45
using System.CommandLine.Help;
@@ -20,6 +21,7 @@ namespace DotMake.CommandLine
2021
/// <code source="..\TestApp\CliExamples.cs" region="CliRun" language="cs" />
2122
/// <code source="..\TestApp\CliExamples.cs" region="CliParse" language="cs" />
2223
/// </code>
24+
/// <code id="gettingStartedClass2" source="..\TestApp\RootHelpOnEmptyCliCommand.cs" region="RootHelpOnEmptyCliCommand" language="cs" />
2325
/// <code>
2426
/// <code source="..\TestApp\CliExamples.cs" region="CliRunWithReturn" language="cs" />
2527
/// <code source="..\TestApp\CliExamples.cs" region="CliRunAsync" language="cs" />
@@ -83,6 +85,53 @@ public static CommandLineConfiguration GetConfiguration(Type definitionType, Cli
8385
var commandBuilder = CliCommandBuilder.Get(definitionType);
8486
var command = commandBuilder.Build();
8587

88+
// Add nested or external registered parent commands
89+
var currentCommand = command;
90+
foreach (var parent in commandBuilder.Parents)
91+
{
92+
var parentCommand = parent.Build();
93+
parentCommand.Add(currentCommand);
94+
currentCommand = parentCommand;
95+
}
96+
97+
// Add nested or external registered children commands
98+
var queue = new Queue<Tuple<CliCommandBuilder, Command>>();
99+
queue.Enqueue(Tuple.Create(commandBuilder, command));
100+
while (queue.Count > 0)
101+
{
102+
var currentTuple= queue.Dequeue();
103+
var current = currentTuple.Item1;
104+
var currentCommand2 = currentTuple.Item2;
105+
106+
foreach (var child in current.Children)
107+
{
108+
var childCommand = child.Build();
109+
currentCommand2.Add(childCommand);
110+
queue.Enqueue(Tuple.Create(child, childCommand));
111+
}
112+
}
113+
114+
115+
/*
116+
//Testing command hierarchy
117+
var topCommandBuilder = commandBuilder.Parents.LastOrDefault() ?? commandBuilder;
118+
var queue2 = new Queue<CliCommandBuilder>();
119+
queue2.Enqueue(topCommandBuilder);
120+
while (queue2.Count > 0)
121+
{
122+
var current = queue2.Dequeue();
123+
124+
var depth = current.Parents.Count();
125+
Console.Write(new string(' ', 2 * depth));
126+
Console.WriteLine($@"{current.DefinitionType.Name} (depth: {depth})");
127+
128+
foreach (var child in current.Children)
129+
{
130+
queue2.Enqueue(child);
131+
}
132+
}
133+
*/
134+
86135
settings ??= new CliSettings();
87136
var configuration = new CommandLineConfiguration(command)
88137
{

src/DotMake.CommandLine/CliCommandBuilder.cs

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,16 @@ protected CliCommandBuilder()
6363
/// </summary>
6464
public bool ShortFormAutoGenerate { get; protected set; }
6565

66+
/// <summary>
67+
/// Gets the command builders that are nested/external children of this command builder.
68+
/// </summary>
69+
public IEnumerable<CliCommandBuilder> Children => GetChildren(DefinitionType);
70+
71+
/// <summary>
72+
/// Gets the command builders that are nested/external parents of this command builder.
73+
/// </summary>
74+
public IEnumerable<CliCommandBuilder> Parents => GetParents(DefinitionType);
75+
6676
/// <summary>
6777
/// Builds a <see cref="Command"/> instance, populated with sub-commands, options, arguments and settings.
6878
/// </summary>
@@ -89,11 +99,6 @@ public object Bind(ParseResult parseResult)
8999
return Binder(pr);
90100
});
91101
}
92-
93-
/// <summary>
94-
/// Gets the command builders that are nested/external children of this command builder.
95-
/// </summary>
96-
public IEnumerable<CliCommandBuilder> Children => GetChildren(DefinitionType);
97102

98103
/// <summary>
99104
/// Registers this command builder so that it can be found by the definition class,
@@ -110,10 +115,10 @@ public void Register()
110115

111116
#region Static
112117

113-
private static readonly Dictionary<Type, CliCommandBuilder> RegisteredDefinitionTypes =
118+
private static readonly Dictionary<Type, CliCommandBuilder> RegisteredCommandBuilders =
114119
new Dictionary<Type, CliCommandBuilder>();
115120

116-
private static readonly Dictionary<Type, HashSet<CliCommandBuilder>> RegisteredParentDefinitionTypes =
121+
private static readonly Dictionary<Type, HashSet<CliCommandBuilder>> RegisteredChildCommandBuilders =
117122
new Dictionary<Type, HashSet<CliCommandBuilder>>();
118123

119124
/// <summary>
@@ -135,7 +140,7 @@ public static void Register<TDefinition>(CliCommandBuilder commandBuilder)
135140
/// <param name="commandBuilder">A command builder which builds a <see cref="Command"/>.</param>
136141
public static void Register(Type definitionType, CliCommandBuilder commandBuilder)
137142
{
138-
RegisteredDefinitionTypes[definitionType] = commandBuilder;
143+
RegisteredCommandBuilders[definitionType] = commandBuilder;
139144
}
140145

141146
/// <summary>
@@ -157,7 +162,7 @@ public static CliCommandBuilder Get<TDefinition>()
157162
/// <returns>The registered <see cref="CliCommandBuilder" /> instance.</returns>
158163
public static CliCommandBuilder Get(Type definitionType)
159164
{
160-
if (!RegisteredDefinitionTypes.TryGetValue(definitionType, out var commandBuilder))
165+
if (!RegisteredCommandBuilders.TryGetValue(definitionType, out var commandBuilder))
161166
{
162167
if (definitionType.GetCustomAttribute<CliCommandAttribute>() == null)
163168
throw new Exception($"The class '{definitionType.Name}' should have [CliCommand] attribute.");
@@ -194,10 +199,18 @@ public static void RegisterAsChild<TParentDefinition>(CliCommandBuilder childCom
194199
/// <param name="childCommandBuilder">The nested/external child command builder.</param>
195200
public static void RegisterAsChild(Type parentDefinitionType, CliCommandBuilder childCommandBuilder)
196201
{
197-
if (!RegisteredParentDefinitionTypes.TryGetValue(parentDefinitionType, out var children))
198-
RegisteredParentDefinitionTypes[parentDefinitionType] = children = new HashSet<CliCommandBuilder>();
202+
if (!RegisteredChildCommandBuilders.TryGetValue(parentDefinitionType, out var children))
203+
RegisteredChildCommandBuilders[parentDefinitionType] = children = new HashSet<CliCommandBuilder>();
199204

200205
children.Add(childCommandBuilder);
206+
207+
/*
208+
if (childCommandBuilder.ParentDefinitionType != null)
209+
{
210+
if (RegisteredDefinitionTypes.TryGetValue(childCommandBuilder.ParentDefinitionType, out var parent))
211+
RegisteredParentDefinitionTypes[childCommandBuilder.DefinitionType] = parent;
212+
}
213+
*/
201214
}
202215

203216
/// <summary>
@@ -220,12 +233,49 @@ public static IEnumerable<CliCommandBuilder> GetChildren<TParentDefinition>()
220233
public static IEnumerable<CliCommandBuilder> GetChildren(Type parentDefinitionType)
221234
{
222235
if (parentDefinitionType == null
223-
|| !RegisteredParentDefinitionTypes.TryGetValue(parentDefinitionType, out var children))
236+
|| !RegisteredChildCommandBuilders.TryGetValue(parentDefinitionType, out var children))
224237
return Enumerable.Empty<CliCommandBuilder>();
225238

226239
return children;
227240
}
228241

242+
/// <summary>
243+
/// Gets the command builders that are registered as nested/external parents of a child definition.
244+
/// </summary>
245+
/// <typeparam name="TDefinition">The child definition class.</typeparam>
246+
/// <returns>An enumerable whose elements are the <see cref="CliCommandBuilder" /> instances registered as nested/external parents.</returns>
247+
public static IEnumerable<CliCommandBuilder> GetParents<TDefinition>()
248+
{
249+
var definitionType = typeof(TDefinition);
250+
251+
return GetParents(definitionType);
252+
}
253+
254+
255+
/// <summary>
256+
/// Gets the command builders that are registered as nested/external parents of a child definition.
257+
/// </summary>
258+
/// <param name="definitionType">The type of the child definition class.</param>
259+
/// <returns>An enumerable whose elements are the <see cref="CliCommandBuilder" /> instances registered as nested/external parents.</returns>
260+
public static IEnumerable<CliCommandBuilder> GetParents(Type definitionType)
261+
{
262+
while (definitionType != null)
263+
{
264+
if (RegisteredCommandBuilders.TryGetValue(definitionType, out var commandBuilder)
265+
&& commandBuilder.ParentDefinitionType != null
266+
&& RegisteredChildCommandBuilders.TryGetValue(commandBuilder.ParentDefinitionType, out var children)
267+
&& children.Contains(commandBuilder)
268+
&& RegisteredCommandBuilders.TryGetValue(commandBuilder.ParentDefinitionType, out var parentCommandBuilder))
269+
{
270+
yield return parentCommandBuilder;
271+
272+
definitionType = parentCommandBuilder.ParentDefinitionType;
273+
}
274+
else
275+
yield break;
276+
}
277+
}
278+
229279
/// <summary>
230280
/// Gets an argument parser method for an argument type, if it's a collection type.
231281
/// <para>

src/DotMake.CommandLine/CliHelpBuilder.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class CliHelpBuilder : HelpBuilder
1919
{
2020
private const string Indent = " ";
2121
private readonly CliTheme theme;
22-
private Func<HelpContext, IEnumerable<Func<HelpContext, bool>>>? _getLayout;
22+
private Func<HelpContext, IEnumerable<Func<HelpContext, bool>>> customGetLayout;
2323

2424
/// <summary>
2525
/// Initializes a new instance of the <see cref="CliHelpBuilder" /> class.
@@ -63,8 +63,9 @@ public override void Write(HelpContext helpContext)
6363
{
6464
return;
6565
}
66-
var layout = _getLayout ?? GetLayout;
67-
var writeSections = layout(helpContext).ToArray();
66+
67+
var getLayout = customGetLayout ?? GetLayout;
68+
var writeSections = getLayout(helpContext).ToArray();
6869
foreach (var writeSection in writeSections)
6970
{
7071
if (writeSection(helpContext))
@@ -77,7 +78,7 @@ public override void Write(HelpContext helpContext)
7778
/// <param name="getLayout">A delegate that returns the layout sections.</param>
7879
public new void CustomizeLayout(Func<HelpContext, IEnumerable<Func<HelpContext, bool>>> getLayout)
7980
{
80-
_getLayout = getLayout ?? throw new ArgumentNullException(nameof(getLayout));
81+
customGetLayout = getLayout ?? throw new ArgumentNullException(nameof(getLayout));
8182
}
8283

8384
/// <summary>

src/DotMake.CommandLine/CliSession.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@ public CliSession(CliSettings settings)
1717
//otherwise it's not updated until next execution of the app.
1818
//Related: https://stackoverflow.com/questions/45513075/why-does-checking-the-console-outputencoding-take-so-long
1919
ConsoleExtensions.SetOutputEncoding(Encoding.UTF8);
20-
ConsoleExtensions.SetColor(settings.Theme.DefaultColor);
20+
2121
ConsoleExtensions.SetBgColor(settings.Theme.DefaultBgColor);
22+
ConsoleExtensions.SetColor(settings.Theme.DefaultColor);
23+
//Console.WriteLine($@"BackgroundColor: {Console.BackgroundColor}");
24+
//Console.WriteLine($@"ForegroundColor: {Console.ForegroundColor}");
2225
}
2326

2427
AppDomain.CurrentDomain.ProcessExit += OnProcessExit;

0 commit comments

Comments
 (0)