Skip to content

Commit 4b90b15

Browse files
committed
adding more functionality for func --help to work
1 parent 7597084 commit 4b90b15

File tree

9 files changed

+212
-185
lines changed

9 files changed

+212
-185
lines changed

src/Cli/func/ActionAttribute.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,7 @@ internal sealed class ActionAttribute : Attribute
1515
public string HelpText { get; set; } = "placeholder";
1616

1717
public bool ShowInHelp { get; set; } = true;
18+
19+
public string ParentCommandName { get; set; } = string.Empty;
1820
}
1921
}

src/Cli/func/ActionType.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,7 @@ internal class ActionType
1212
public IEnumerable<Context> SubContexts { get; set; }
1313

1414
public IEnumerable<string> Names { get; set; }
15+
16+
public IEnumerable<string> ParentCommandName { get; set; }
1517
}
1618
}

src/Cli/func/Actions/HelpAction.cs

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ public HelpAction(IEnumerable<TypeAttributePair> actions, Func<Type, IAction> cr
4242
Type = type,
4343
Contexts = attributes.Select(a => a.Context),
4444
SubContexts = attributes.Select(a => a.SubContext),
45-
Names = attributes.Select(a => a.Name)
45+
Names = attributes.Select(a => a.Name),
46+
ParentCommandName = attributes.Select(a => a.ParentCommandName)
4647
};
4748
});
4849
}
@@ -187,7 +188,8 @@ private void DisplayGeneralHelp()
187188
.WriteLine("Usage: func [context] <action> [-/--options]")
188189
.WriteLine();
189190
DisplayContextsHelp(contexts);
190-
var actions = _actionTypes.Where(a => a.Contexts.Contains(Context.None));
191+
var actions = _actionTypes
192+
.Where(a => a.Contexts.Contains(Context.None));
191193
DisplayActionsHelp(actions);
192194
}
193195

@@ -211,40 +213,88 @@ private void DisplayActionsHelp(IEnumerable<ActionType> actions)
211213
if (actions.Any())
212214
{
213215
ColoredConsole.WriteLine(TitleColor("Actions: "));
216+
217+
// Group actions by parent command
218+
var parentCommands = actions
219+
.Where(a => a.ParentCommandName.All(p => string.IsNullOrEmpty(p))) // Actions with no parent
220+
.ToList();
221+
222+
var subCommands = actions
223+
.Where(a => a.ParentCommandName.Any(p => !string.IsNullOrEmpty(p))) // Actions with a parent
224+
.ToList();
225+
214226
var longestName = actions.Select(a => a.Names).SelectMany(n => n).Max(n => n.Length);
215227
longestName += 2; // for coloring chars
216-
foreach (var action in actions)
228+
229+
// Display parent commands first
230+
foreach (var parentAction in parentCommands)
217231
{
218-
ColoredConsole.WriteLine(GetActionHelp(action, longestName));
219-
DisplaySwitches(action);
220-
}
232+
// Display parent command
233+
ColoredConsole.WriteLine(GetActionHelp(parentAction, longestName));
234+
DisplaySwitches(parentAction);
221235

222-
ColoredConsole.WriteLine();
236+
// Find and display child commands for this parent
237+
var parentName = parentAction.Names.First();
238+
var childCommands = subCommands
239+
.Where(s => s.ParentCommandName.Any(p => p.Equals(parentName, StringComparison.OrdinalIgnoreCase)))
240+
.ToList();
241+
242+
if (childCommands.Any())
243+
{
244+
ColoredConsole.WriteLine(); // Add spacing before subcommands
245+
246+
foreach (var childCommand in childCommands)
247+
{
248+
DisplaySubCommandHelp(childCommand);
249+
}
250+
}
251+
252+
ColoredConsole.WriteLine();
253+
}
223254
}
224255
}
225256

226-
private void DisplaySwitches(ActionType actionType)
257+
private void DisplaySubCommandHelp(ActionType subCommand)
258+
{
259+
// Extract the runtime name from the full command name
260+
// E.g., "pack dotnet" -> "Dotnet"
261+
var fullCommandName = subCommand.Names.First();
262+
var parts = fullCommandName.Split(' ', StringSplitOptions.RemoveEmptyEntries);
263+
var runtimeName = parts.Length > 1 ?
264+
char.ToUpper(parts[1][0]) + parts[1].Substring(1).ToLower() :
265+
fullCommandName;
266+
267+
var description = subCommand.Type.GetCustomAttributes<ActionAttribute>()?.FirstOrDefault()?.HelpText;
268+
269+
// Display indented subcommand header
270+
ColoredConsole.WriteLine($" {runtimeName.DarkCyan()} {description}");
271+
272+
// Display subcommand switches with extra indentation
273+
DisplaySwitches(subCommand, true);
274+
}
275+
276+
private void DisplaySwitches(ActionType actionType, bool addExtraIndent = false)
227277
{
228278
var action = _createAction.Invoke(actionType.Type);
229279
try
230280
{
231281
var options = action.ParseArgs(Array.Empty<string>());
232282
if (options.UnMatchedOptions.Any())
233283
{
234-
DisplayOptions(options.UnMatchedOptions);
284+
DisplayOptions(options.UnMatchedOptions, addExtraIndent);
235285
ColoredConsole.WriteLine();
236286
}
237287
}
238288
catch (CliArgumentsException e)
239289
{
240290
if (e.Arguments.Any())
241291
{
242-
DisplayPositionalArguments(e.Arguments);
292+
DisplayPositionalArguments(e.Arguments, addExtraIndent);
243293
}
244294

245295
if (e.ParseResults != null && e.ParseResults.UnMatchedOptions.Any())
246296
{
247-
DisplayOptions(e.ParseResults.UnMatchedOptions);
297+
DisplayOptions(e.ParseResults.UnMatchedOptions, addExtraIndent);
248298
}
249299

250300
ColoredConsole.WriteLine();
@@ -255,13 +305,13 @@ private void DisplaySwitches(ActionType actionType)
255305
}
256306
}
257307

258-
private void DisplayPositionalArguments(IEnumerable<CliArgument> arguments)
308+
private void DisplayPositionalArguments(IEnumerable<CliArgument> arguments, bool addExtraIndent)
259309
{
260310
var longestName = arguments.Max(o => o.Name.Length);
261311
longestName += 4; // 4 for coloring and <> characters
262312
foreach (var argument in arguments)
263313
{
264-
var helpLine = string.Format($" {{0, {-longestName}}} {{1}}", $"<{argument.Name}>".DarkGray(), argument.Description);
314+
var helpLine = string.Format($"{(addExtraIndent ? " " : " ")}{{0, {-longestName}}} {{1}}", $"<{argument.Name}>".DarkGray(), argument.Description);
265315
if (helpLine.Length < SafeConsole.BufferWidth)
266316
{
267317
ColoredConsole.WriteLine(helpLine);
@@ -277,7 +327,7 @@ private void DisplayPositionalArguments(IEnumerable<CliArgument> arguments)
277327
}
278328
}
279329

280-
private static void DisplayOptions(IEnumerable<ICommandLineOption> options)
330+
private static void DisplayOptions(IEnumerable<ICommandLineOption> options, bool addExtraIndent = false)
281331
{
282332
var longestName = options.Max(o =>
283333
{
@@ -311,7 +361,7 @@ private static void DisplayOptions(IEnumerable<ICommandLineOption> options)
311361
stringBuilder.Append($" [-{option.ShortName}]");
312362
}
313363

314-
var helpSwitch = string.Format($" {{0, {-longestName}}} ", stringBuilder.ToString().DarkGray());
364+
var helpSwitch = string.Format($"{(addExtraIndent ? " " : " ")}{{0, {-longestName}}} ", stringBuilder.ToString().DarkGray());
315365
var helpSwitchLength = helpSwitch.Length - 2; // helpSwitch contains 2 formatting characters.
316366
var helpText = option.Description;
317367
if (string.IsNullOrWhiteSpace(helpText))

src/Cli/func/Actions/LocalActions/PackAction/DotnetPackSubcommand.cs renamed to src/Cli/func/Actions/LocalActions/PackAction/DotnetPackSubcommandAction.cs

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,43 +5,43 @@
55
using Azure.Functions.Cli.Helpers;
66
using Azure.Functions.Cli.Interfaces;
77
using Colors.Net;
8+
using Fclp;
89
using static Azure.Functions.Cli.Common.OutputTheme;
910

1011
namespace Azure.Functions.Cli.Actions.LocalActions.PackAction
1112
{
12-
[Action(Name = "pack dotnet", CommandType = CommandType.SubCommand, ShowInHelp = true, HelpText = "Internal .NET runtime-specific pack command")]
13-
internal class DotNetPackSubCommand : PackSubCommandBase
13+
[Action(Name = "pack dotnet", ParentCommandName = "pack", ShowInHelp = false, HelpText = ".NET specific arguments")]
14+
internal class DotnetPackSubcommandAction : BaseAction
1415
{
15-
public DotNetPackSubCommand(ISecretsManager secretsManager, PackAction parentAction)
16-
: base(secretsManager, parentAction)
16+
private readonly ISecretsManager _secretsManager;
17+
18+
public DotnetPackSubcommandAction(ISecretsManager secretsManager)
1719
{
20+
_secretsManager = secretsManager;
1821
}
1922

20-
protected override void SetupParser()
23+
public override ICommandLineParserResult ParseArgs(string[] args)
2124
{
2225
// .NET doesn't have any runtime-specific arguments beyond the common ones
23-
Parser
24-
.Setup<bool>("build-native-deps")
25-
.SetDefault(false)
26-
.WithDescription("Build native dependencies in Docker container");
26+
return base.ParseArgs(args);
2727
}
2828

29-
public override async Task RunAsync()
29+
public async Task RunAsync(PackOptions packOptions)
3030
{
31-
var functionAppRoot = ParentAction.ResolveFunctionAppRoot();
31+
var functionAppRoot = PackHelpers.ResolveFunctionAppRoot(packOptions.FolderPath);
3232
string packingRoot = functionAppRoot;
3333

34-
if (ParentAction.NoBuild)
34+
if (packOptions.NoBuild)
3535
{
3636
// For --no-build, treat FolderPath as the build output directory
37-
if (string.IsNullOrEmpty(ParentAction.FolderPath))
37+
if (string.IsNullOrEmpty(packOptions.FolderPath))
3838
{
3939
throw new CliException("When using --no-build for .NET projects, you must specify the path to the build output directory (e.g., ./bin/Release/net8.0/publish)");
4040
}
4141

42-
packingRoot = Path.IsPathRooted(ParentAction.FolderPath)
43-
? ParentAction.FolderPath
44-
: Path.Combine(Environment.CurrentDirectory, ParentAction.FolderPath);
42+
packingRoot = Path.IsPathRooted(packOptions.FolderPath)
43+
? packOptions.FolderPath
44+
: Path.Combine(Environment.CurrentDirectory, packOptions.FolderPath);
4545

4646
if (!Directory.Exists(packingRoot))
4747
{
@@ -52,7 +52,7 @@ public override async Task RunAsync()
5252
}
5353
else
5454
{
55-
ParentAction.ValidateFunctionAppRoot(functionAppRoot);
55+
PackHelpers.ValidateFunctionAppRoot(functionAppRoot);
5656

5757
// Run dotnet publish
5858
ColoredConsole.WriteLine("Building .NET project...");
@@ -62,17 +62,23 @@ public override async Task RunAsync()
6262
packingRoot = Path.Combine(functionAppRoot, "output");
6363
}
6464

65-
var outputPath = ParentAction.ResolveOutputPath(functionAppRoot);
66-
ParentAction.CleanupExistingPackage(outputPath);
65+
var outputPath = PackHelpers.ResolveOutputPath(functionAppRoot, packOptions.OutputPath);
66+
PackHelpers.CleanupExistingPackage(outputPath);
6767

6868
// Install extensions if not in no-build mode
69-
if (!ParentAction.NoBuild)
69+
if (!packOptions.NoBuild)
7070
{
71-
var installExtensionAction = new InstallExtensionAction(SecretsManager, false);
71+
var installExtensionAction = new InstallExtensionAction(_secretsManager, false);
7272
await installExtensionAction.RunAsync();
7373
}
7474

75-
await ParentAction.CreatePackage(packingRoot, outputPath);
75+
await PackHelpers.CreatePackage(packingRoot, outputPath, packOptions.NoBuild, TelemetryCommandEvents);
76+
}
77+
78+
public override Task RunAsync()
79+
{
80+
// Keep this in case the customer tries to run func pack dotnet, since this subcommand is not meant to be run directly.
81+
throw new InvalidOperationException("Invalid command. Please run func pack instead with valid arguments. To see a list of valid arguments, please see func --help.");
7682
}
7783

7884
private void ValidateDotNetPublishDirectory(string path)

src/Cli/func/Actions/LocalActions/PackAction/GenericPackSubCommand.cs

Lines changed: 0 additions & 39 deletions
This file was deleted.

0 commit comments

Comments
 (0)