Skip to content

Commit f281835

Browse files
committed
Update documentation to demonstrate pass-thru args and how to allow for multiple values on parameters
1 parent 82bf18f commit f281835

File tree

7 files changed

+208
-31
lines changed

7 files changed

+208
-31
lines changed

Directory.Build.targets

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,2 @@
1-
<Project>
2-
3-
<PropertyGroup>
4-
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
5-
</PropertyGroup>
6-
7-
<Target Name="UpdateBuildDetails" BeforeTargets="CollectPackageReferences" Condition="'$(APPVEYOR)' == 'true'">
8-
<Exec Command="appveyor UpdateBuild -Version $(PackageVersion)"
9-
IgnoreExitCode="true"
10-
IgnoreStandardErrorWarningFormat="true" />
11-
</Target>
12-
13-
</Project>
1+
<!-- This file is intentionally empty -->
2+
<Project />

docs/docs/arguments.md

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public class Program
6464

6565
***
6666

67-
## Remaining arguments
67+
## Variable numbers of arguments
6868

6969
A common scenario is to allow a command line tool to take in a variable number of arguments.
7070
These arguments are collected into a `string[]` array after all other arguments and options are parsed.
@@ -73,30 +73,25 @@ These arguments are collected into a `string[]` array after all other arguments
7373
cat -b 123 file1.txt file2.txt file3.txt
7474
```
7575

76-
> [!NOTE]
77-
> Normally, unrecognized arguments is an error. To allow this scenario, you must set ThrowOnUnexpectedArgument to false.
78-
79-
See @McMaster.Extensions.CommandLineUtils.Conventions.RemainingArgsPropertyConvention for more details.
80-
8176
### [Using Attributes](#tab/using-attributes)
8277

83-
By default, attribute binding will set a `string[]` or `IList<string>` property named `RemainingArguments` or `RemainingArgs`
84-
to include all values
78+
By default, attribute binding will assume multiple values can be set for properties with the `[Argument]`
79+
attribute and settable to `string[]` or `IEnumerable<string>`.
8580

8681
```c#
87-
[Command(ThrowOnUnexpectedArgument = false)]
8882
public class Program
8983
{
9084
[Option("-b")]
9185
public int BlankLines { get; }
9286

93-
public string[] RemainingArguments { get; } // = { "file1.txt", "file2.txt", "file3.txt" }
87+
[Argument(0)]
88+
public string[] Files { get; } // = { "file1.txt", "file2.txt", "file3.txt" }
9489
9590
private void OnExecute()
9691
{
97-
if (RemainingArguments != null)
92+
if (Files != null)
9893
{
99-
foreach (var file in RemainingArguments)
94+
foreach (var file in Files)
10095
{
10196
// do something
10297
}
@@ -109,24 +104,69 @@ public class Program
109104

110105
### [Using Builder API](#tab/using-builder-api)
111106

107+
To enable this, See @McMaster.Extensions.CommandLineUtils.CommandArgument.MultipleValues must be set to true,
108+
and the argument must be the last one specified.
109+
112110
```c#
113111
public class Program
114112
{
115113
public static int Main(string[] args)
116114
{
117-
var app = new CommandLineApplication(throwOnUnexpectedArg: false);
115+
var app = new CommandLineApplication();
118116
var blankLines = app.Option<int>("-b <LINES>", "Blank lines", CommandOptionType.SingleValue);
117+
var files = app.Argument("Files", "Files to count", multipleValues: true);
119118
app.OnExecute(() =>
120119
{
121-
if (app.RemainingArguments != null)
120+
foreach (var file in files.Values)
122121
{
123-
foreach (var file in app.RemainingArguments)
124-
{
125-
// do something
126-
}
122+
// do something
127123
}
128124
});
129125
return app.Execute(args);
130126
}
131127
}
132128
```
129+
130+
## Pass-thru arguments
131+
132+
Another common scenario is to create a command line tool which wraps another tool. These kinds of command lines
133+
need to collect arguments which are passed to the to the tool they wrap. For example, the Unix command `time`
134+
or the Windows command `cmd` take some arguments, and pass the rest on to the command they invoke.
135+
136+
> [!NOTE]
137+
> Example:
138+
>
139+
> ```
140+
> time -l ls -a -l ./
141+
> ```
142+
>
143+
> In this example, `-l` is an option on `time`. This starts a timer which then invokes `ls` with additional arguments.
144+
> `-l` is also an option on `ls`.
145+
146+
Normally, unrecognized arguments is an error. You must set ThrowOnUnexpectedArgument to `false` to allow the parser
147+
to allow unrecognized arguments and options.
148+
149+
### The double-dash convention `--`
150+
151+
It is common for apps which pass-thru arguments to allow the caller to use `--` to distinguish between the
152+
options on the parent command and all remaining arguments.
153+
154+
```
155+
bash -c -- ls -a -l
156+
```
157+
158+
In this example, the presence of `--` forces bash to stop parsing and treat everything after `--` as an argument
159+
to be passed to the inner command.
160+
161+
The double dash command is enabled by setting @McMaster.Extensions.CommandLineUtils.CommandLineApplication.AllowArgumentSeparator.
162+
163+
### [Using Attributes](#tab/using-attributes)
164+
165+
By default, attribute binding will set a `string[]` or `IList<string>` property named `RemainingArguments` or `RemainingArgs`
166+
to include all values. See @McMaster.Extensions.CommandLineUtils.Conventions.RemainingArgsPropertyConvention for more details.
167+
168+
[!code-csharp[Program](../samples/passthru-args/attributes/Program.cs)]
169+
170+
### [Using Builder API](#tab/using-builder-api)
171+
172+
[!code-csharp[Program](../samples/passthru-args/builder-api/Program.cs)]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>netcoreapp2.1</TargetFramework>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.2.5" />
10+
</ItemGroup>
11+
12+
</Project>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.Linq;
4+
using McMaster.Extensions.CommandLineUtils;
5+
6+
[Command(ThrowOnUnexpectedArgument = false, AllowArgumentSeparator = true)]
7+
public class Program
8+
{
9+
public static int Main(string[] args) => CommandLineApplication.Execute<Program>(args);
10+
11+
[Option("-m", Description = "Show time in milliseconds")]
12+
public bool Milliseconds { get; }
13+
14+
public string[] RemainingArguments { get; } // = { "ls", "-a", "-l" }
15+
16+
private void OnExecute()
17+
{
18+
var timer = Stopwatch.StartNew();
19+
if (RemainingArguments != null && RemainingArguments.Length > 0)
20+
{
21+
var process = new Process
22+
{
23+
StartInfo =
24+
{
25+
FileName = RemainingArguments[0],
26+
Arguments = ArgumentEscaper.EscapeAndConcatenate(RemainingArguments.Skip(1)),
27+
}
28+
};
29+
process.Start();
30+
process.WaitForExit();
31+
}
32+
33+
timer.Stop();
34+
35+
if (Milliseconds)
36+
{
37+
Console.WriteLine($"Time = {timer.Elapsed.TotalMilliseconds:F} ms");
38+
}
39+
else
40+
{
41+
Console.WriteLine($"Time = {timer.Elapsed.TotalSeconds:F}s");
42+
}
43+
}
44+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>netcoreapp2.1</TargetFramework>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.2.5" />
10+
</ItemGroup>
11+
12+
</Project>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.Linq;
4+
using McMaster.Extensions.CommandLineUtils;
5+
6+
public class Program
7+
{
8+
public static int Main(string[] args)
9+
{
10+
var app = new CommandLineApplication(throwOnUnexpectedArg: false)
11+
{
12+
AllowArgumentSeparator = true
13+
};
14+
15+
var showMilliseconds = app.Option<int>("-m", "Show time in milliseconds", CommandOptionType.NoValue);
16+
17+
app.OnExecute(() =>
18+
{
19+
var timer = Stopwatch.StartNew();
20+
if (app.RemainingArguments != null && app.RemainingArguments.Count > 0)
21+
{
22+
var process = new Process
23+
{
24+
StartInfo =
25+
{
26+
FileName = app.RemainingArguments[0],
27+
Arguments = ArgumentEscaper.EscapeAndConcatenate(app.RemainingArguments.Skip(1)),
28+
}
29+
};
30+
process.Start();
31+
process.WaitForExit();
32+
}
33+
34+
timer.Stop();
35+
36+
if (showMilliseconds.HasValue())
37+
{
38+
Console.WriteLine($"Time = {timer.Elapsed.TotalMilliseconds:F} ms");
39+
}
40+
else
41+
{
42+
Console.WriteLine($"Time = {timer.Elapsed.TotalSeconds:F}s");
43+
}
44+
});
45+
46+
return app.Execute(args);
47+
}
48+
}

docs/samples/samples.sln

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pager", "pager\Pager.csproj
4545
EndProject
4646
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "McMaster.Extensions.CommandLineUtils", "..\..\src\CommandLineUtils\McMaster.Extensions.CommandLineUtils.csproj", "{289BEC92-5FB8-4DBB-9B9B-EB9452034121}"
4747
EndProject
48+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "passthru-args", "passthru-args", "{75E74B42-039E-4661-B39C-160363B6884A}"
49+
EndProject
50+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuilderApiPassThru", "passthru-args\builder-api\BuilderApiPassThru.csproj", "{24EFFC95-EA40-4684-8036-5E577E0295F5}"
51+
EndProject
52+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributesPassThru", "passthru-args\attributes\AttributesPassThru.csproj", "{1EE15348-5FF4-4988-AEA4-3E267ED4C288}"
53+
EndProject
4854
Global
4955
GlobalSection(SolutionConfigurationPlatforms) = preSolution
5056
Debug|Any CPU = Debug|Any CPU
@@ -262,6 +268,30 @@ Global
262268
{289BEC92-5FB8-4DBB-9B9B-EB9452034121}.Release|x64.Build.0 = Release|Any CPU
263269
{289BEC92-5FB8-4DBB-9B9B-EB9452034121}.Release|x86.ActiveCfg = Release|Any CPU
264270
{289BEC92-5FB8-4DBB-9B9B-EB9452034121}.Release|x86.Build.0 = Release|Any CPU
271+
{24EFFC95-EA40-4684-8036-5E577E0295F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
272+
{24EFFC95-EA40-4684-8036-5E577E0295F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
273+
{24EFFC95-EA40-4684-8036-5E577E0295F5}.Debug|x64.ActiveCfg = Debug|Any CPU
274+
{24EFFC95-EA40-4684-8036-5E577E0295F5}.Debug|x64.Build.0 = Debug|Any CPU
275+
{24EFFC95-EA40-4684-8036-5E577E0295F5}.Debug|x86.ActiveCfg = Debug|Any CPU
276+
{24EFFC95-EA40-4684-8036-5E577E0295F5}.Debug|x86.Build.0 = Debug|Any CPU
277+
{24EFFC95-EA40-4684-8036-5E577E0295F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
278+
{24EFFC95-EA40-4684-8036-5E577E0295F5}.Release|Any CPU.Build.0 = Release|Any CPU
279+
{24EFFC95-EA40-4684-8036-5E577E0295F5}.Release|x64.ActiveCfg = Release|Any CPU
280+
{24EFFC95-EA40-4684-8036-5E577E0295F5}.Release|x64.Build.0 = Release|Any CPU
281+
{24EFFC95-EA40-4684-8036-5E577E0295F5}.Release|x86.ActiveCfg = Release|Any CPU
282+
{24EFFC95-EA40-4684-8036-5E577E0295F5}.Release|x86.Build.0 = Release|Any CPU
283+
{1EE15348-5FF4-4988-AEA4-3E267ED4C288}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
284+
{1EE15348-5FF4-4988-AEA4-3E267ED4C288}.Debug|Any CPU.Build.0 = Debug|Any CPU
285+
{1EE15348-5FF4-4988-AEA4-3E267ED4C288}.Debug|x64.ActiveCfg = Debug|Any CPU
286+
{1EE15348-5FF4-4988-AEA4-3E267ED4C288}.Debug|x64.Build.0 = Debug|Any CPU
287+
{1EE15348-5FF4-4988-AEA4-3E267ED4C288}.Debug|x86.ActiveCfg = Debug|Any CPU
288+
{1EE15348-5FF4-4988-AEA4-3E267ED4C288}.Debug|x86.Build.0 = Debug|Any CPU
289+
{1EE15348-5FF4-4988-AEA4-3E267ED4C288}.Release|Any CPU.ActiveCfg = Release|Any CPU
290+
{1EE15348-5FF4-4988-AEA4-3E267ED4C288}.Release|Any CPU.Build.0 = Release|Any CPU
291+
{1EE15348-5FF4-4988-AEA4-3E267ED4C288}.Release|x64.ActiveCfg = Release|Any CPU
292+
{1EE15348-5FF4-4988-AEA4-3E267ED4C288}.Release|x64.Build.0 = Release|Any CPU
293+
{1EE15348-5FF4-4988-AEA4-3E267ED4C288}.Release|x86.ActiveCfg = Release|Any CPU
294+
{1EE15348-5FF4-4988-AEA4-3E267ED4C288}.Release|x86.Build.0 = Release|Any CPU
265295
EndGlobalSection
266296
GlobalSection(NestedProjects) = preSolution
267297
{3D6570C6-11DC-40E7-9C96-8936D23C606A} = {E03982B3-80F2-4D55-9501-378EACE93E5B}
@@ -273,5 +303,7 @@ Global
273303
{38D690F0-025F-4A60-B5F9-F22FE9445F62} = {E6049162-CBBC-4A4F-858E-15D8AE3574BF}
274304
{EF23CAC9-C537-4CFB-8263-31632ABF8091} = {C34F685A-0C1F-4FF4-90F0-4A12C98E9067}
275305
{5F46A3E6-C4B5-4664-BD74-C9E3F24F2FA4} = {C34F685A-0C1F-4FF4-90F0-4A12C98E9067}
306+
{24EFFC95-EA40-4684-8036-5E577E0295F5} = {75E74B42-039E-4661-B39C-160363B6884A}
307+
{1EE15348-5FF4-4988-AEA4-3E267ED4C288} = {75E74B42-039E-4661-B39C-160363B6884A}
276308
EndGlobalSection
277309
EndGlobal

0 commit comments

Comments
 (0)