Skip to content

Commit 32a46ca

Browse files
committed
Extend the subcommand sample to show how to use multiple levels of hierarchy
1 parent 2272271 commit 32a46ca

File tree

3 files changed

+245
-82
lines changed

3 files changed

+245
-82
lines changed

samples/Subcommands/Inheritance.cs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Copyright (c) Nate McMaster.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using McMaster.Extensions.CommandLineUtils;
7+
8+
namespace SubcommandSample
9+
{
10+
/// <summary>
11+
/// In this example, each sub command type inherits from <see cref="GitCommandBase"/>,
12+
/// which provides shared functionality between all the commands.
13+
/// This example also shows you how the subcommands can be linked to their parent types.
14+
/// </summary>
15+
[Command("fake-git")]
16+
[VersionOption("--version", "1.0.0")]
17+
[Subcommand("add", typeof(AddCommand))]
18+
[Subcommand("commit", typeof(CommitCommand))]
19+
class Git : GitCommandBase
20+
{
21+
[Option("--git-dir")]
22+
public string GitDir { get; set; }
23+
24+
protected override int OnExecute(CommandLineApplication app)
25+
{
26+
// this shows help even if the --help option isn't specified
27+
app.ShowHelp();
28+
return 1;
29+
}
30+
31+
public override List<string> CreateArgs()
32+
{
33+
var args = new List<string>();
34+
if (GitDir != null)
35+
{
36+
args.Add("--git-dir=" + GitDir);
37+
}
38+
return args;
39+
}
40+
}
41+
42+
[Command(Description = "Add file contents to the index")]
43+
class AddCommand : GitCommandBase
44+
{
45+
[Argument(0)]
46+
public string[] Files { get; set; }
47+
48+
// You can use this pattern when the parent command may have options or methods you want to
49+
// use from sub-commands.
50+
// This will automatically be set before OnExecute is invoked
51+
private Git Parent { get; set; }
52+
53+
public override List<string> CreateArgs()
54+
{
55+
var args = Parent.CreateArgs();
56+
args.Add("add");
57+
58+
if (Files != null)
59+
{
60+
args.AddRange(Files);
61+
}
62+
63+
return args;
64+
}
65+
}
66+
67+
[Command(Description = "Record changes to the repository")]
68+
class CommitCommand : GitCommandBase
69+
{
70+
[Option("-m")]
71+
public string Message { get; set; }
72+
73+
// This will automatically be set before OnExecute is invoked.
74+
private Git Parent { get; set; }
75+
76+
public override List<string> CreateArgs()
77+
{
78+
var args = Parent.CreateArgs();
79+
args.Add("commit");
80+
81+
if (Message != null)
82+
{
83+
args.Add("-m");
84+
args.Add(Message);
85+
}
86+
return args;
87+
}
88+
}
89+
90+
/// <summary>
91+
/// This base type provides shared functionality.
92+
/// Also, declaring <see cref="HelpOptionAttribute"/> on this type means all types that inherit from it
93+
/// will automatically support '--help'
94+
/// </summary>
95+
[HelpOption("--help")]
96+
abstract class GitCommandBase
97+
{
98+
public abstract List<string> CreateArgs();
99+
100+
protected virtual int OnExecute(CommandLineApplication app)
101+
{
102+
var args = CreateArgs();
103+
104+
Console.WriteLine("Result = git " + ArgumentEscaper.EscapeAndConcatenate(args));
105+
return 0;
106+
}
107+
}
108+
}

samples/Subcommands/NestedTypes.cs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright (c) Nate McMaster.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using System.ComponentModel.DataAnnotations;
6+
using McMaster.Extensions.CommandLineUtils;
7+
8+
namespace SubcommandSample
9+
{
10+
/// <summary>
11+
/// In this example, each command a nested class type.
12+
/// This isn't required. See the <see cref="Git"/> example for a sample on how to use inheritance.
13+
/// </summary>
14+
[Command(Name ="fake-docker", Description = "A self-sufficient runtime for containers"),
15+
HelpOption,
16+
Subcommand("container", typeof(Containers)),
17+
Subcommand("image", typeof(Images))]
18+
class Docker
19+
{
20+
private int OnExecute(CommandLineApplication app, IConsole console)
21+
{
22+
console.WriteLine("You must specify at a subcommand.");
23+
app.ShowHelp();
24+
return 1;
25+
}
26+
27+
/// <summary>
28+
/// <see cref="HelpOptionAttribute"/> must be declared on each type that supports '--help'.
29+
/// Compare to the inheritance example, in which <see cref="GitCommandBase"/> delcares it
30+
/// once so that all subcommand types automatically support '--help'.
31+
/// </summary>
32+
[Command(Description = "Manage containers"),
33+
HelpOption,
34+
Subcommand("ls", typeof(List)),
35+
Subcommand("run", typeof(Run))]
36+
private class Containers
37+
{
38+
private int OnExecute(IConsole console)
39+
{
40+
console.Error.WriteLine("You must specify an action. See --help for more details.");
41+
return 1;
42+
}
43+
44+
[Command(Description = "List containers"), HelpOption]
45+
private class List
46+
{
47+
[Option(Description = "Show all containers (default shows just running)")]
48+
public bool All { get; }
49+
50+
private void OnExecute(IConsole console)
51+
{
52+
console.WriteLine(string.Join("\n",
53+
"CONTAINERS",
54+
"----------------",
55+
"jubilant_jackson",
56+
"lucid_torvalds"));
57+
}
58+
}
59+
60+
[Command(Description = "Run a command in a new container",
61+
AllowArgumentSeparator = true,
62+
ThrowOnUnexpectedArgument = false),
63+
HelpOption]
64+
private class Run
65+
{
66+
[Required(ErrorMessage = "You must specify the image name")]
67+
[Argument(0, Description = "The image for the new container")]
68+
public string Image { get; }
69+
70+
[Option("--name", Description = "Assign a name to the container")]
71+
public string Name { get; }
72+
73+
/// <summary>
74+
/// When ThrowOnUnexpectedArgument is valids, any unrecognized arguments
75+
/// will be collected and set in this property, when set.
76+
/// </summary>
77+
public string[] RemainingArguments { get; }
78+
79+
private void OnExecute(IConsole console)
80+
{
81+
console.WriteLine($"Would have run {Image} (name = {Name}) with args => {ArgumentEscaper.EscapeAndConcatenate(RemainingArguments)}");
82+
}
83+
}
84+
}
85+
86+
[Command(Description = "Manage images"),
87+
HelpOption,
88+
Subcommand("ls", typeof(List))]
89+
private class Images
90+
{
91+
private int OnExecute(IConsole console)
92+
{
93+
console.Error.WriteLine("You must specify an action. See --help for more details.");
94+
return 1;
95+
}
96+
97+
98+
[Command(Description = "List images",
99+
ThrowOnUnexpectedArgument = false),
100+
HelpOption]
101+
private class List
102+
{
103+
[Option(Description = "Show all containers (default shows just running)")]
104+
public bool All { get; }
105+
106+
private IReadOnlyList<string> RemainingArguments { get; }
107+
108+
private void OnExecute(IConsole console)
109+
{
110+
console.WriteLine(string.Join("\n",
111+
"IMAGES",
112+
"--------------------",
113+
"microsoft/dotnet:2.0"));
114+
}
115+
}
116+
}
117+
}
118+
}

samples/Subcommands/Program.cs

Lines changed: 19 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -2,97 +2,34 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5-
using System.Collections.Generic;
65
using McMaster.Extensions.CommandLineUtils;
76

87
namespace SubcommandSample
98
{
10-
[Command("git")]
11-
[VersionOption("--version", "1.0.0")]
12-
[Subcommand("add", typeof(AddCommand))]
13-
[Subcommand("commit", typeof(CommitCommand))]
14-
class Program : CommandBase
9+
/// <summary>
10+
/// This example is meant to show you how to structure a console application that uses
11+
/// the nested subcommands with options and arguments that vary between each subcommand.
12+
/// </summary>
13+
class Program
1514
{
16-
public static int Main(string[] args) => CommandLineApplication.Execute<Program>(args);
17-
18-
[Option("--git-dir")]
19-
public string GitDir { get; set; }
20-
21-
protected override int OnExecute(CommandLineApplication app)
22-
{
23-
// this shows help even if the --help option isn't specified
24-
app.ShowHelp();
25-
return 1;
26-
}
27-
28-
public override List<string> CreateArgs()
29-
{
30-
var args = new List<string>();
31-
if (GitDir != null)
32-
{
33-
args.Add("--git-dir=" + GitDir);
34-
}
35-
return args;
36-
}
37-
}
38-
39-
[Command(Description = "Add file contents to the index")]
40-
class AddCommand : CommandBase
41-
{
42-
[Argument(0)]
43-
public string[] Files { get; set; }
44-
45-
// this will automatically be set before OnExecute is invoked
46-
private Program Parent { get; set; }
47-
48-
public override List<string> CreateArgs()
49-
{
50-
var args = Parent.CreateArgs();
51-
args.Add("add");
52-
53-
if (Files != null)
54-
{
55-
args.AddRange(Files);
56-
}
57-
58-
return args;
59-
}
60-
}
61-
62-
[Command(Description = "Record changes to the repository")]
63-
class CommitCommand : CommandBase
64-
{
65-
[Option("-m")]
66-
public string Message { get; set; }
67-
68-
// this will automatically be set before OnExecute is invoked
69-
private Program Parent { get; set; }
70-
71-
public override List<string> CreateArgs()
15+
public static int Main(string[] args)
7216
{
73-
var args = Parent.CreateArgs();
74-
args.Add("commit");
17+
const string prompt = @"Which example would you like to run?
18+
1 - Fake Git
19+
2 - Fake Docker
20+
> ";
21+
var option = Prompt.GetInt(prompt);
7522

76-
if (Message != null)
23+
switch (option)
7724
{
78-
args.Add("-m");
79-
args.Add(Message);
25+
case 1:
26+
return CommandLineApplication.Execute<Git>(args);
27+
case 2:
28+
return CommandLineApplication.Execute<Docker>(args);
29+
default:
30+
Console.Error.WriteLine("Unknown option");
31+
return 1;
8032
}
81-
return args;
82-
}
83-
}
84-
85-
[HelpOption("--help")]
86-
abstract class CommandBase
87-
{
88-
public abstract List<string> CreateArgs();
89-
90-
protected virtual int OnExecute(CommandLineApplication app)
91-
{
92-
var args = CreateArgs();
93-
94-
Console.WriteLine("=> git " + ArgumentEscaper.EscapeAndConcatenate(args));
95-
return 0;
9633
}
9734
}
9835
}

0 commit comments

Comments
 (0)