From 307a842cb9bc9aaa964188a9e13cc6c647a3d258 Mon Sep 17 00:00:00 2001 From: walterlv Date: Fri, 2 Aug 2019 15:16:55 +0800 Subject: [PATCH 1/8] Add docs. --- README.md | 115 +++++++++++++++++ docs/zh-chs/README.md | 286 +++++++++++++++++++++++++++++++++++++++++ docs/zh-zht/README.md | 287 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 688 insertions(+) create mode 100644 README.md create mode 100644 docs/zh-chs/README.md create mode 100644 docs/zh-zht/README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..f0546cd --- /dev/null +++ b/README.md @@ -0,0 +1,115 @@ +# dotnetCampus.CommandLine + +[English][en]|[简体中文][zh-chs]|[繁體中文][zh-cht] +-|-|-|- + +[en]: /README.md +[zh-chs]: /docs/zh-chs/README.md +[zh-cht]: /docs/zh-cht/README.md + +dotnetCampus.CommandLine is probably the fastest command line parser in all .NET open-source projects. + +Parsing a classical command line only takes 1091ns, thus 10 ticks. + +## Get Started + +For your program `Main` method, write this code below: + +```csharp +class Program +{ + static void Main(string[] args) + { + var commandLine = CommandLine.Parse(args); + var options = commandLine.As(new OptionsParser()); + + // Then, use your Options instance here. + } +} +``` + +You need to define the `Options` class as followed below: + +```csharp +class Options +{ + [Value(0)] + public string FilePath { get; } + + [Option('s', "Silence")] + public bool IsSilence { get; } + + [Option('m', "Mode")] + public string StartMode { get; } + + [Option("StartupSessions")] + public IReadonlyList StartupSessions { get; } + + public Options( + string filePath, + bool isSilence, + string startMode, + IReadonlyList startupSessions) + { + FilePath = filePath; + IsSilence = isSilence; + StartMode = startMode; + StartupSessions = startupSessions; + } +} +``` + +Then you can run your program by passing these kind of command line args: + +Windows style: + +```powershell +> demo.exe "C:\Users\lvyi\Desktop\demo.txt" -s -Mode Edit -StartupSessions A B C +``` + +```cmd +> demo.exe "C:\Users\lvyi\Desktop\demo.txt" /s /Mode Edit /StartupSessions A B C +``` + +Linux style: + +```bash +$ demo.exe "C:/Users/lvyi/Desktop/demo.txt" -s --mode Edit --startup-sessions A B C +``` + +Notice that you cannot use different styles in a single command line. + +For `bool`: + +- You can pass `true` / `True` / `false` / `False` to specify a boolean value; +- You can pass nothing but only a switch. + +It means that `-s true`, `-s True`, `-s` are the same. + +For `ValueAttribute` and `OptionAttribute`: + +- You can specify both on a single property. +- If there is a value without option the property got the value, but if another value with the specified option exists, the new value will override the old one. + +```csharp +[Value(0), Option('f', "File")] +public string FilePath { get; } +``` + +## Engage, Contribute and Provide Feedback + +Thank you very much for firing a new issue and providing new pull requests. + +### Issue + +Click here to file a new issue: + +- [New Issue · dotnet-campus/dotnetCampus.CommandLine](https://github.com/dotnet-campus/dotnetCampus.CommandLine/issues/new) + +### Contributing Guide + +Be kindly. + +## License + +dotnetCampus.CommandLine is licensed under the [MIT license](/LICENSE). diff --git a/docs/zh-chs/README.md b/docs/zh-chs/README.md new file mode 100644 index 0000000..de5bd26 --- /dev/null +++ b/docs/zh-chs/README.md @@ -0,0 +1,286 @@ +# 命令行解析 + +[English][en]|[简体中文][zh-chs]|[繁體中文][zh-cht] +-|-|-|- + +[en]: /README.md +[zh-chs]: /docs/zh-chs/README.md +[zh-cht]: /docs/zh-cht/README.md + +dotnetCampus.CommandLine 中提供了简单而高性能的命令行解析功能,在 dotnetCampus.Cli 命名空间下。 + +## 快速使用 + +```csharp +class Program +{ + static void Main(string[] args) + { + // 从命令行参数创建一个 CommandLine 类型的新实例。 + var commandLine = CommandLine.Parse(args); + + // 将 CommandLine 类型的当作一个命令行参数类型 Options 来使用。 + var options = commandLine.As(new OptionsParser()); + + // 接下来,使用你的 options 对象编写其他的功能。 + } +} +``` + +而这个 `Options` 类型的定义如下: + +```csharp +class Options +{ + [Value(0)] + public string FilePath { get; } + + [Option('s', "Silence")] + public bool IsSilence { get; } + + [Option('m', "Mode")] + public string StartMode { get; } + + [Option("StartupSessions")] + public IReadonlyList StartupSessions { get; } + + public Options( + string filePath, + bool isSilence, + string startMode, + IReadonlyList startupSessions) + { + FilePath = filePath; + IsSilence = isSilence; + StartMode = startMode; + StartupSessions = startupSessions; + } +} +``` + +于是,在命令行中可以使用不同风格的命令填充这个类型的实例。 + +Windows 风格: + +```powershell +> demo.exe "C:\Users\lvyi\Desktop\demo.txt" -s -Mode Edit -StartupSessions A B C +``` + +```cmd +> demo.exe "C:\Users\lvyi\Desktop\demo.txt" /s /Mode Edit /StartupSessions A B C +``` + +Linux 风格: + +```bash +$ demo.exe "C:/Users/lvyi/Desktop/demo.txt" -s --mode Edit --startup-sessions A B C +``` + +以上不同风格不能混合使用。 + +对于 bool 类型的属性,在命令行中既可以在选项后传入 `true` / `True` / `false` / `False` 也可以不传。如果不传,则表示 `true`。 + +另外,`ValueAttribute` 和 `OptionAttribute` 可以出现在同一个属性上。这时如果发现了不带选项的值,将填充到 `ValueAttribute` 的属性上;而一旦之后发现了此 `OptionsAttribute` 指定的短名称或者长名称,会将新的值覆盖再次设置到此属性上。 + +```csharp +[Value(0), Option('f', "File")] +public string FilePath { get; } +``` + +## 需要注意 + +在命令行中输入参数时,无论哪种风格,命令行都是区分大小写的。对于选项(`-`、`/` 或者 `--` 开头)如果大小写错误,此选项和后面附带的值都将被忽略;对于值(不带 `-` 或者 `/` 开头),值将按照命令行中的原生大小写传入 `Options` 类型的实例中。 + +在 `Options` 类型中定义属性时,短名称是可选指定的,但一旦指定则必须是一个字符;长名称是必须指定的,而且命名必须满足 PascalCase 命名规则且不带连字符。详细要求可在编写自己的 `Options` 类型时阅读 `OptionAttribute` 的注释。 + +## 多种命令行参数与谓词 + +你可以为你的命令行参数指定谓词,每一种谓词都可以有自己的一组独特的参数类型。 + +```csharp +var commandLine = CommandLine.Parse(args); +commandLine.AddHandler(options => 0) + .AddHandler(options => 0).Run(); +``` + +而 `EditOptions` 和 `PrintOptions` 的定义如下,区别在于类型标记了谓词。 + +```csharp +[Verb("Edit")] +public class EditOptions +{ + [Value(0), Option('f', "File")] public string FilePath { get; set; } +} + +[Verb("Print")] +public class PrintOptions +{ + [Value(0), Option('f', "File")] public string FilePath { get; set; } + [Option('p', "Printer")] public string Printer { get; set; } +} +``` + +你也可以在 `Handle` 中使用不标谓词的参数类型,但是这样的参数最多只允许有一个,会作为没有任何谓词匹配上时使用的默认参数类型。 + +另外,`Handle` 方法有对应的 `HandleAsync` 异步版本,用于处理异步的任务。 + +## 关于解析器 + +在前面的示例程序中,我们传入了一个解析器的实例 `new OptionsParser()`。如果你不在乎性能(实际上也花不了多少性能),那么不必传入,命令行解析器可以针对你的选项类型自动生成一个运行时解析器。但是如果你在乎性能,那么你可能需要自己编写(将来会自动生成)。本文文末附有各种不同用法的性能数据。 + +如果你打算自己编写了,那么继续阅读这一小节。 + +这个类的编写是标准化的,请按照注释以及下文的规范来操作,不要编写额外的任何校验代码或类型转换代码。因为传入解析器之前所有参数已全部校验完毕;而且将来接入自动生成解析器后,你编写的个性化逻辑可能丢失。 + +解析器的示例代码有两种,一种使用原生接口,性能更好;另一种使用基类,但编写所需的代码更少。建议手写的话使用基类,如果能生成代码,则使用接口。 + +```csharp +public class OptionsParser : ICommandLineOptionParser +{ + private string _filePath; + private bool _isSilence; + private string _startMode; + private IReadonlyList _startupSessions; + + public void SetValue(int index, string value) + { + switch (index) + { + case 0: + _filePath = value; + break; + } + } + + public void SetValue(char shortName, bool value) + { + switch (shortName) + { + case 's': + _isSilence = value; + break; + } + } + + public void SetValue(char shortName, string value) + { + switch (shortName) + { + case 'm': + _startMode = value; + break; + } + } + + public void SetValue(char shortName, IReadOnlyList values) + { + } + + public void SetValue(string longName, bool value) + { + switch (longName) + { + case "Silence": + _isSilence = value; + break; + } + } + + public void SetValue(string longName, string value) + { + switch (longName) + { + case "Mode": + _startMode = value; + break; + } + } + + public void SetValue(string longName, IReadOnlyList values) + { + switch (longName) + { + case "StartupSession": + _startupSession = value; + break; + } + } + + public Options Commit() + { + return new Options(_filePath, _isSilence, _startMode, _startupSession); + } +} +``` + +```csharp +public class SelfWrittenShareOptionsParser : CommandLineOptionParser +{ + public SelfWrittenShareOptionsParser() + { + string filePath = null; + bool isSilence = false; + string startMode = null; + IReadonlyList startupSessions = null; + + AddMatch(0, value => filePath = value); + AddMatch('s', value => isSilence = value); + AddMatch("Silence", value => isSilence = value); + AddMatch('m', value => startMode = value); + AddMatch("Mode", value => startMode = value); + AddMatch("StartupSessions", value => startupSessions = value); + + SetResult(() => new Options(filePath, isSilence, startMode, startupSession)); + } +} +``` + +## 支持协议解析 + +`CommandLine` 支持解析 URL 协议中的字符串。 + +``` +@"dotnetCampus://open/?file=C:\Users\lvyi\Desktop\%E9%87%8D%E5%91%BD%E5%90%8D%E8%AF%95%E9%AA%8C.enbx&mode=Display&silence=true&startupSessions=89EA9D26-6464-4E71-BD04-AA6516063D83" +``` + +请注意,解析 URL 有如下限制: + +1. 你的 `Options` 类型中所有的 `ValueAttribute` 都将无法被赋值; +1. 你的 `Options` 类型中不允许出现标记有 `OptionAttribute` 的字符串集合属性。 + +另外,URL 解析中的选项名称也是大小写敏感的。当你在 `Options` 类型中正确使用 PascalCase 风格定义了选项的长名称后,你在 URL 中既可以使用 PascalCase 风格也可以使用 camelCase 风格。 + +## 性能数据 + +| Method | Mean | Error | StdDev | Ratio | RatioSD | +|----------------------------- |--------------:|-------------:|-------------:|-------:|--------:| +| ParseNoArgs | 95.20 ns | 1.828 ns | 1.956 ns | 0.09 | 0.00 | +| ParseNoArgsAuto | 763.12 ns | 14.702 ns | 19.117 ns | 0.69 | 0.02 | +| ParseWindows | 1,116.76 ns | 24.612 ns | 23.022 ns | 1.00 | 0.00 | +| ParseWindowsAuto | 1,974.78 ns | 37.120 ns | 44.189 ns | 1.76 | 0.06 | +| ParseWindowsRuntime | 96,378.30 ns | 1,900.205 ns | 2,725.217 ns | 86.40 | 2.99 | +| ParseWindowsImmutableRuntime | 96,200.14 ns | 1,677.293 ns | 1,568.941 ns | 86.17 | 2.15 | +| HandleVerbs | 1,530.32 ns | 33.916 ns | 31.725 ns | 1.37 | 0.05 | +| HandleVerbsRuntime | 26,888.69 ns | 660.595 ns | 734.250 ns | 24.18 | 0.71 | +| ParseCmd | 1,153.53 ns | 26.479 ns | 27.192 ns | 1.04 | 0.03 | +| ParseCmdAuto | 1,915.15 ns | 21.508 ns | 17.960 ns | 1.71 | 0.04 | +| ParseLinux | 1,763.82 ns | 33.752 ns | 43.887 ns | 1.58 | 0.05 | +| ParseLinuxAuto | 2,556.28 ns | 47.460 ns | 42.072 ns | 2.29 | 0.06 | +| ParseUrl | 4,800.81 ns | 86.862 ns | 72.534 ns | 4.29 | 0.07 | +| ParseUrlAuto | 6,274.80 ns | 125.106 ns | 205.553 ns | 5.65 | 0.25 | +| CommandLineParser | 136,090.91 ns | 1,072.509 ns | 895.594 ns | 121.71 | 2.66 | + +总结来说:完成一次解析只需要 1091ns,也就是大约 10 tick。 + +说明: + +- NoArgs 表示没有传入参数 +- Auto 表示自动查找 Parser 而不是手动传入 +- Runtime 表示使用运行时解析器 +- Handle 表示进行多谓词匹配 +- CommandLineParser 是使用的 CommandLineParser 库作为对照 +- 测试使用的参数: + - Windows 风格:`"C:\Users\lvyi\Desktop\重命名试验.enbx" -Cloud -Iwb -m Display -s -p Outside -StartupSession 89EA9D26-6464-4E71-BD04-AA6516063D83` + - Cmd 风格:`"C:\Users\lvyi\Desktop\重命名试验.enbx" /Cloud /Iwb /m Display /s /p Outside /StartupSession 89EA9D26-6464-4E71-BD04-AA6516063D83` + - Linux 风格:`"C:\Users\lvyi\Desktop\重命名试验.enbx" --cloud --iwb -m Display -s -p Outside --startup-session 89EA9D26-6464-4E71-BD04-AA6516063D83` + - Url 风格:`walterlv://open/?file=C:\Users\lvyi\Desktop\%E9%87%8D%E5%91%BD%E5%90%8D%E8%AF%95%E9%AA%8C.enbx&cloud=true&iwb=true&silence=true&placement=Outside&startupSession=89EA9D26-6464-4E71-BD04-AA6516063D83` diff --git a/docs/zh-zht/README.md b/docs/zh-zht/README.md new file mode 100644 index 0000000..6eb31df --- /dev/null +++ b/docs/zh-zht/README.md @@ -0,0 +1,287 @@ +# 命令行解析 + +[English][en]|[简体中文][zh-chs]|[繁體中文][zh-cht] +-|-|-|- + +[en]: /README.md +[zh-chs]: /docs/zh-chs/README.md +[zh-cht]: /docs/zh-cht/README.md + +dotnetCampus.CommandLine 中提供了簡單而高性能的命令行解析功能,在 dotnetCampus.Cli 命名空間下。 + +## 快速使用 + +```csharp +class Program +{ + static void Main(string[] args) + { + // 從命令行參數創建一個 CommandLine 類型的新實例。 + var commandLine = CommandLine.Parse(args); + +  // 將 CommandLine 類型的當作一個命令行參數類型選項來使用。 + var options = commandLine.As(new OptionsParser()); + + // 接下來,使用你的選項對象編寫其他的功能。 + } +} +``` + +而這個 `Options` 類型的定義如下: + +```csharp +class Options +{ + [Value(0)] + public string FilePath { get; } + + [Option('s', "Silence")] + public bool IsSilence { get; } + + [Option('m', "Mode")] + public string StartMode { get; } + + [Option("StartupSessions")] + public IReadonlyList StartupSessions { get; } + + public Options( + string filePath, + bool isSilence, + string startMode, + IReadonlyList startupSessions) + { + FilePath = filePath; + IsSilence = isSilence; + StartMode = startMode; + StartupSessions = startupSessions; + } +} +``` + +於是,在命令行中可以使用不同風格的命令填充這個類型的實例。 + +Windows 風格: + +```powershell +> demo.exe "C:\Users\lvyi\Desktop\demo.txt" -s -Mode Edit -StartupSessions A B C +``` + +```cmd +> demo.exe "C:\Users\lvyi\Desktop\demo.txt" /s /Mode Edit /StartupSessions A B C +``` + +Linux 風格: + +```bash +$ demo.exe "C:/Users/lvyi/Desktop/demo.txt" -s --mode Edit --startup-sessions A B C +``` + +以上不同風格不能混合使用。 + +對於 `bool` 類型的屬性,在命令行中既可以在選項後傳入 `true` /`True` /`false` /`False` 也可以不傳。如果不傳,則表示 `true`。 + +另外,`ValueAttribute` 和 `OptionAttribute` 可以出現在同一個屬性上。這時如果發現了不帶選項的值,將填充到 `ValueAttribute` 的屬性上;而一旦之後發現了此 `OptionsAttribute` 指定的短名稱或者長名稱,會將新的值覆蓋再次設置到此屬性上。 + +```csharp +[Value(0), Option('f', "File")] +public string FilePath { get; } +``` + +## 需要注意 + +在命令行中輸入參數時,無論哪種風格,命令行都是區分大小寫的。對於選項(`-`,`/` 或者 `--` 開頭)如果大小寫錯誤,此選項和後面附帶的值都將被忽略;對於值(不帶 `-` 或者 `/` 開頭),值將按照命令行中的原生大小寫傳入 `選項` 類型的實例中。 + +在 `Options` 類型中定義屬性時,短名稱是可選指定的,但一旦指定則必須是一個字符;長名稱是必須指定的,而且命名必須滿足 PascalCase 命名規則且不帶連字符。詳細要求可在編寫自己的 `Options` 類型時閱讀 `OptionAttribute` 的註釋。 + +## 多種命令行參數與謂詞 + +你可以為你的命令行參數指定謂詞,每一種謂詞都可以有自己的一組獨特的參數類型。 + +```csharp +var commandLine = CommandLine.Parse(args); +commandLine.AddHandler(options => 0) + .AddHandler(options => 0).Run(); +``` + +而 `EditOptions` 和 `PrintOptions` 的定義如下,區別在於類型標記了謂詞。 + +```csharp +[Verb("Edit")] +public class EditOptions +{ + [Value(0), Option('f', "File")] public string FilePath { get; set; } +} + +[Verb("Print")] +public class PrintOptions +{ + [Value(0), Option('f', "File")] public string FilePath { get; set; } + [Option('p', "Printer")] public string Printer { get; set; } +} +``` + +你也可以在 `Handle` 中使用不標謂詞的參數類型,但是這樣的參數最多只允許有一個,會作為沒有任何謂詞匹配上時使用的默認參數類型。 + +另外,`Handle` 方法有對應的 `HandleAsync` 異步版本,用於處理異步的任務。 + +## 關於解析器 + +在前面的示例程序中,我們傳入了一個解析器的實例 `new OptionsParser()`。如果你不在乎性能(實際上也花不了多少性能),那麼不必傳入,命令行解析器可以針對你的選項類型自動生成一個運行時解析器。但是如果你在乎性能,那麼你可能需要自己編寫(將來會自動生成)。本文文末附有各種不同用法的性能數據。 + +如果你打算自己編寫了,那麼繼續閱讀這一小節。 + +這個類的編寫是標準化的,請按照註釋以及下文的規範來操作,不要編寫額外的任何校驗代碼或類型轉換代碼因為傳入解析器之前所有參數已全部校驗完畢;而且將來接入自動生成解析器後,你編寫的個性化邏輯可能丟失。 + +解析器的示例代碼有兩種,一種使用原生接口,性能更好;另一種使用基類,但編寫所需的代碼更少建議手寫的話使用基類,如果能生成代碼,則使用接口。 + +```csharp +public class OptionsParser : ICommandLineOptionParser +{ + private string _filePath; + private bool _isSilence; + private string _startMode; + private IReadonlyList _startupSessions; + + public void SetValue(int index, string value) + { + switch (index) + { + case 0: + _filePath = value; + break; + } + } + + public void SetValue(char shortName, bool value) + { + switch (shortName) + { + case 's': + _isSilence = value; + break; + } + } + + public void SetValue(char shortName, string value) + { + switch (shortName) + { + case 'm': + _startMode = value; + break; + } + } + + public void SetValue(char shortName, IReadOnlyList values) + { + } + + public void SetValue(string longName, bool value) + { + switch (longName) + { + case "Silence": + _isSilence = value; + break; + } + } + + public void SetValue(string longName, string value) + { + switch (longName) + { + case "Mode": + _startMode = value; + break; + } + } + + public void SetValue(string longName, IReadOnlyList values) + { + switch (longName) + { + case "StartupSession": + _startupSession = value; + break; + } + } + + public Options Commit() + { + return new Options(_filePath, _isSilence, _startMode, _startupSession); + } +} +``` + +```csharp +public class SelfWrittenShareOptionsParser : CommandLineOptionParser +{ + public SelfWrittenShareOptionsParser() + { + string filePath = null; + bool isSilence = false; + string startMode = null; + IReadonlyList startupSessions = null; + + AddMatch(0, value => filePath = value); + AddMatch('s', value => isSilence = value); + AddMatch("Silence", value => isSilence = value); + AddMatch('m', value => startMode = value); + AddMatch("Mode", value => startMode = value); + AddMatch("StartupSessions", value => startupSessions = value); + + SetResult(() => new Options(filePath, isSilence, startMode, startupSession)); + } +} +``` + +## 支持協議解析 + +`CommandLine` 支持解析URL協議中的字符串。 + +``` +@"dotnetCampus://open/?file=C:\Users\lvyi\Desktop\%E9%87%8D%E5%91%BD%E5%90%8D%E8%AF%95%E9%AA%8C.enbx&mode=Display&silence=true&startupSessions=89EA9D26-6464-4E71-BD04-AA6516063D83" +``` + +請注意,解析URL有如下限制: + +1.你的 `Options` 類型中所有的 `ValueAttribute` 都將無法被賦值; +1.你的 `Options` 類型中不允許出現標記有 `OptionAttribute` 的字符串集合屬性。 + +另外,URL 解析中的選項名稱也是大小寫敏感的。當你在 `Options` 類型中正確使用PascalCase 風格定義了選項的長名稱後,你在 URL 中既可以使用 PascalCase 風格也可以使用 camelCase 風格。 + +## 性能數據 + + +| Method | Mean | Error | StdDev | Ratio | RatioSD | +|----------------------------- |--------------:|-------------:|-------------:|-------:|--------:| +| ParseNoArgs | 95.20 ns | 1.828 ns | 1.956 ns | 0.09 | 0.00 | +| ParseNoArgsAuto | 763.12 ns | 14.702 ns | 19.117 ns | 0.69 | 0.02 | +| ParseWindows | 1,116.76 ns | 24.612 ns | 23.022 ns | 1.00 | 0.00 | +| ParseWindowsAuto | 1,974.78 ns | 37.120 ns | 44.189 ns | 1.76 | 0.06 | +| ParseWindowsRuntime | 96,378.30 ns | 1,900.205 ns | 2,725.217 ns | 86.40 | 2.99 | +| ParseWindowsImmutableRuntime | 96,200.14 ns | 1,677.293 ns | 1,568.941 ns | 86.17 | 2.15 | +| HandleVerbs | 1,530.32 ns | 33.916 ns | 31.725 ns | 1.37 | 0.05 | +| HandleVerbsRuntime | 26,888.69 ns | 660.595 ns | 734.250 ns | 24.18 | 0.71 | +| ParseCmd | 1,153.53 ns | 26.479 ns | 27.192 ns | 1.04 | 0.03 | +| ParseCmdAuto | 1,915.15 ns | 21.508 ns | 17.960 ns | 1.71 | 0.04 | +| ParseLinux | 1,763.82 ns | 33.752 ns | 43.887 ns | 1.58 | 0.05 | +| ParseLinuxAuto | 2,556.28 ns | 47.460 ns | 42.072 ns | 2.29 | 0.06 | +| ParseUrl | 4,800.81 ns | 86.862 ns | 72.534 ns | 4.29 | 0.07 | +| ParseUrlAuto | 6,274.80 ns | 125.106 ns | 205.553 ns | 5.65 | 0.25 | +| CommandLineParser | 136,090.91 ns | 1,072.509 ns | 895.594 ns | 121.71 | 2.66 | + +總結來說:完成一次解析只需要1091ns,也就是大約10 tick。 + +說明: + +- NoArgs 表示沒有傳入參數 +- Auto 表示自動查找 Parser 而不是手動傳入 +- Runtime 表示使用運行時解析器 +- Handle 表示進行多謂詞匹配 +- CommandLineParser 是使用的 CommandLineParser 庫作為對照 +- 測試使用的參數: + - Windows 風格:`"C:\Users\lvyi\Desktop\重命名试验.enbx" -Cloud -Iwb -m Display -s -p Outside -StartupSession 89EA9D26-6464-4E71-BD04-AA6516063D83` + - Cmd 風格:`"C:\Users\lvyi\Desktop\重命名试验.enbx" /Cloud /Iwb /m Display /s /p Outside /StartupSession 89EA9D26-6464-4E71-BD04-AA6516063D83` + - Linux 風格:`"C:\Users\lvyi\Desktop\重命名试验.enbx" --cloud --iwb -m Display -s -p Outside --startup-session 89EA9D26-6464-4E71-BD04-AA6516063D83` + - Url 風格:`walterlv://open/?file=C:\Users\lvyi\Desktop\%E9%87%8D%E5%91%BD%E5%90%8D%E8%AF%95%E9%AA%8C.enbx&cloud=true&iwb=true&silence=true&placement=Outside&startupSession=89EA9D26-6464-4E71-BD04-AA6516063D83` From 63231a796cefab8ff642569acb330e97f5e1acc8 Mon Sep 17 00:00:00 2001 From: walterlv Date: Fri, 2 Aug 2019 15:22:31 +0800 Subject: [PATCH 2/8] Fix link. --- README.md | 2 +- docs/zh-chs/README.md | 2 +- docs/zh-zht/README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f0546cd..9e098e2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # dotnetCampus.CommandLine [English][en]|[简体中文][zh-chs]|[繁體中文][zh-cht] --|-|-|- +-|-|- [en]: /README.md [zh-chs]: /docs/zh-chs/README.md diff --git a/docs/zh-chs/README.md b/docs/zh-chs/README.md index de5bd26..ccbfd84 100644 --- a/docs/zh-chs/README.md +++ b/docs/zh-chs/README.md @@ -1,7 +1,7 @@ # 命令行解析 [English][en]|[简体中文][zh-chs]|[繁體中文][zh-cht] --|-|-|- +-|-|- [en]: /README.md [zh-chs]: /docs/zh-chs/README.md diff --git a/docs/zh-zht/README.md b/docs/zh-zht/README.md index 6eb31df..be68fa5 100644 --- a/docs/zh-zht/README.md +++ b/docs/zh-zht/README.md @@ -1,7 +1,7 @@ # 命令行解析 [English][en]|[简体中文][zh-chs]|[繁體中文][zh-cht] --|-|-|- +-|-|- [en]: /README.md [zh-chs]: /docs/zh-chs/README.md From a249746d9c223f3e635e0da96d0672a11dfbff50 Mon Sep 17 00:00:00 2001 From: walterlv Date: Fri, 2 Aug 2019 15:24:13 +0800 Subject: [PATCH 3/8] Fix translation. --- docs/zh-zht/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh-zht/README.md b/docs/zh-zht/README.md index be68fa5..cbfa8bd 100644 --- a/docs/zh-zht/README.md +++ b/docs/zh-zht/README.md @@ -89,7 +89,7 @@ public string FilePath { get; } ## 需要注意 -在命令行中輸入參數時,無論哪種風格,命令行都是區分大小寫的。對於選項(`-`,`/` 或者 `--` 開頭)如果大小寫錯誤,此選項和後面附帶的值都將被忽略;對於值(不帶 `-` 或者 `/` 開頭),值將按照命令行中的原生大小寫傳入 `選項` 類型的實例中。 +在命令行中輸入參數時,無論哪種風格,命令行都是區分大小寫的。對於選項(`-`,`/` 或者 `--` 開頭)如果大小寫錯誤,此選項和後面附帶的值都將被忽略;對於值(不帶 `-` 或者 `/` 開頭),值將按照命令行中的原生大小寫傳入 `Options` 類型的實例中。 在 `Options` 類型中定義屬性時,短名稱是可選指定的,但一旦指定則必須是一個字符;長名稱是必須指定的,而且命名必須滿足 PascalCase 命名規則且不帶連字符。詳細要求可在編寫自己的 `Options` 類型時閱讀 `OptionAttribute` 的註釋。 From 4e3d2996a465275192dd00b859593b957326acb2 Mon Sep 17 00:00:00 2001 From: walterlv <450711383@qq.com> Date: Fri, 2 Aug 2019 15:26:48 +0800 Subject: [PATCH 4/8] Add licence file. --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8321651 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 dotnet campus + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 2a14d4fd27c56c3af50065076b076bc73464d050 Mon Sep 17 00:00:00 2001 From: walterlv Date: Fri, 2 Aug 2019 15:30:41 +0800 Subject: [PATCH 5/8] Add readme for sln. --- dotnetCampus.CommandLine.sln | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/dotnetCampus.CommandLine.sln b/dotnetCampus.CommandLine.sln index 3c281f7..f00e08f 100644 --- a/dotnetCampus.CommandLine.sln +++ b/dotnetCampus.CommandLine.sln @@ -8,15 +8,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .gitattributes = .gitattributes .gitignore = .gitignore Directory.Build.props = Directory.Build.props + LICENSE = LICENSE + README.md = README.md EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{7DDA8183-3606-4B08-86E3-A4537860448F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnetCampus.CommandLine.Performance", "tests\dotnetCampus.CommandLine.Performance\dotnetCampus.CommandLine.Performance.csproj", "{7B664373-31A0-47AB-9076-15153EF4722A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnetCampus.CommandLine.Performance", "tests\dotnetCampus.CommandLine.Performance\dotnetCampus.CommandLine.Performance.csproj", "{7B664373-31A0-47AB-9076-15153EF4722A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnetCampus.CommandLine", "src\dotnetCampus.CommandLine\dotnetCampus.CommandLine.csproj", "{EE3640D1-C870-4C4E-869E-AE3DCC6CB3C4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnetCampus.CommandLine.Tests", "tests\dotnetCampus.CommandLine.Tests\dotnetCampus.CommandLine.Tests.csproj", "{470A596C-D8C4-4506-A71A-4BEEA08BCC62}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnetCampus.CommandLine.Tests", "tests\dotnetCampus.CommandLine.Tests\dotnetCampus.CommandLine.Tests.csproj", "{470A596C-D8C4-4506-A71A-4BEEA08BCC62}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -24,6 +26,10 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7B664373-31A0-47AB-9076-15153EF4722A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7B664373-31A0-47AB-9076-15153EF4722A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7B664373-31A0-47AB-9076-15153EF4722A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7B664373-31A0-47AB-9076-15153EF4722A}.Release|Any CPU.Build.0 = Release|Any CPU {EE3640D1-C870-4C4E-869E-AE3DCC6CB3C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EE3640D1-C870-4C4E-869E-AE3DCC6CB3C4}.Debug|Any CPU.Build.0 = Debug|Any CPU {EE3640D1-C870-4C4E-869E-AE3DCC6CB3C4}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -32,17 +38,13 @@ Global {470A596C-D8C4-4506-A71A-4BEEA08BCC62}.Debug|Any CPU.Build.0 = Debug|Any CPU {470A596C-D8C4-4506-A71A-4BEEA08BCC62}.Release|Any CPU.ActiveCfg = Release|Any CPU {470A596C-D8C4-4506-A71A-4BEEA08BCC62}.Release|Any CPU.Build.0 = Release|Any CPU - {7B664373-31A0-47AB-9076-15153EF4722A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7B664373-31A0-47AB-9076-15153EF4722A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7B664373-31A0-47AB-9076-15153EF4722A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7B664373-31A0-47AB-9076-15153EF4722A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {470A596C-D8C4-4506-A71A-4BEEA08BCC62} = {7DDA8183-3606-4B08-86E3-A4537860448F} {7B664373-31A0-47AB-9076-15153EF4722A} = {7DDA8183-3606-4B08-86E3-A4537860448F} + {470A596C-D8C4-4506-A71A-4BEEA08BCC62} = {7DDA8183-3606-4B08-86E3-A4537860448F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {52E30C59-C5C8-4517-811A-667BFA2BFABB} From 3ccdc8e6edf6d54d79c627d4e1c97845be44e7db Mon Sep 17 00:00:00 2001 From: walterlv Date: Fri, 2 Aug 2019 19:06:42 +0800 Subject: [PATCH 6/8] Fix unit test. --- tests/dotnetCampus.CommandLine.Tests/Fakes/CommandLineArgs.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/dotnetCampus.CommandLine.Tests/Fakes/CommandLineArgs.cs b/tests/dotnetCampus.CommandLine.Tests/Fakes/CommandLineArgs.cs index ded1b2b..0e9e5f3 100644 --- a/tests/dotnetCampus.CommandLine.Tests/Fakes/CommandLineArgs.cs +++ b/tests/dotnetCampus.CommandLine.Tests/Fakes/CommandLineArgs.cs @@ -3,7 +3,7 @@ internal static class CommandLineArgs { internal const string UrlProtocol = "walterlv"; - internal const string FileValue = @"C:\Users\lvyi\Desktop\testfile.txt"; + internal const string FileValue = @"C:\Users\lvyi\Desktop\重命名试验.txt"; internal const bool CloudValue = true; internal const bool IwbValue = true; internal const string ModeValue = "Display"; @@ -57,7 +57,7 @@ internal static class CommandLineArgs internal static readonly string[] UrlArgs = { - @"walterlv://open/?file=C:\Users\lvyi\Desktop\%E9%87%8D%E5%91%BD%E5%90%8D%E8%AF%95%E9%AA%8C.enbx&cloud=true&iwb=true&mode=Display&silence=true&placement=Outside&startupSession=89EA9D26-6464-4E71-BD04-AA6516063D83", + @"walterlv://open/?file=C:\Users\lvyi\Desktop\%E9%87%8D%E5%91%BD%E5%90%8D%E8%AF%95%E9%AA%8C.txt&cloud=true&iwb=true&mode=Display&silence=true&placement=Outside&startupSession=89EA9D26-6464-4E71-BD04-AA6516063D83", }; internal static readonly string[] EditVerbArgs = From a3efa667ab4dd3c96d897639388752a38246399d Mon Sep 17 00:00:00 2001 From: walterlv Date: Sat, 3 Aug 2019 09:28:20 +0800 Subject: [PATCH 7/8] Fix folder error. --- docs/{zh-zht => zh-cht}/README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/{zh-zht => zh-cht}/README.md (100%) diff --git a/docs/zh-zht/README.md b/docs/zh-cht/README.md similarity index 100% rename from docs/zh-zht/README.md rename to docs/zh-cht/README.md From ffa8d398a66ff3c0eb1984c4b49b21136c0d6c33 Mon Sep 17 00:00:00 2001 From: walterlv Date: Mon, 11 Nov 2019 14:37:44 +0800 Subject: [PATCH 8/8] Fix docs. --- docs/zh-chs/README.md | 25 +++++++++++++------ .../Fakes/CommandLineArgs.cs | 4 +-- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/docs/zh-chs/README.md b/docs/zh-chs/README.md index ccbfd84..fe7c639 100644 --- a/docs/zh-chs/README.md +++ b/docs/zh-chs/README.md @@ -78,7 +78,7 @@ $ demo.exe "C:/Users/lvyi/Desktop/demo.txt" -s --mode Edit --startup-sessions A 以上不同风格不能混合使用。 -对于 bool 类型的属性,在命令行中既可以在选项后传入 `true` / `True` / `false` / `False` 也可以不传。如果不传,则表示 `true`。 +对于 bool 类型的属性,在命令行中既可以在选项后传入 `true` / `True` / `false` / `False` 也可以不传。如果不传,则表示 `true`。举例来说,`-s true`、`-s True` 和 `-s` 都是等价的。 另外,`ValueAttribute` 和 `OptionAttribute` 可以出现在同一个属性上。这时如果发现了不带选项的值,将填充到 `ValueAttribute` 的属性上;而一旦之后发现了此 `OptionsAttribute` 指定的短名称或者长名称,会将新的值覆盖再次设置到此属性上。 @@ -87,6 +87,8 @@ $ demo.exe "C:/Users/lvyi/Desktop/demo.txt" -s --mode Edit --startup-sessions A public string FilePath { get; } ``` +在这段例子中,`demo.exe xxx.txt`、`demo.exe -f xxx.txt` 和 `demo.exe --file xxx.txt` 都会将 `xxx.txt` 赋值给 `FilePath` 这个属性。 + ## 需要注意 在命令行中输入参数时,无论哪种风格,命令行都是区分大小写的。对于选项(`-`、`/` 或者 `--` 开头)如果大小写错误,此选项和后面附带的值都将被忽略;对于值(不带 `-` 或者 `/` 开头),值将按照命令行中的原生大小写传入 `Options` 类型的实例中。 @@ -97,13 +99,16 @@ public string FilePath { get; } 你可以为你的命令行参数指定谓词,每一种谓词都可以有自己的一组独特的参数类型。 +谓词的用法如 `git commit` 和 `git add` 一样,其中的 `commit` 和 `add` 将作为命令行的命令分支逻辑标识,对于命令行谓词之后的参数将会填充到不同的谓词对应的类型。 + ```csharp +const int defaultExitCode = 0; var commandLine = CommandLine.Parse(args); -commandLine.AddHandler(options => 0) +commandLine.AddHandler(options => defaultExitCode) .AddHandler(options => 0).Run(); ``` -而 `EditOptions` 和 `PrintOptions` 的定义如下,区别在于类型标记了谓词。 +而 `EditOptions` 和 `PrintOptions` 的定义如下,区别在于通过 Verb 特性标记了谓词。 ```csharp [Verb("Edit")] @@ -120,6 +125,8 @@ public class PrintOptions } ``` +谓词的用法如 `git commit` 一样,将作为命令行的命令分支逻辑标识。如以上代码可使用 `demo.exe edit -f xx.txt` 和 `demo.exe print -f xx.txt -p xx` 分别调用 `EditOptions` 和 `PrintOptions` 对应逻辑。 + 你也可以在 `Handle` 中使用不标谓词的参数类型,但是这样的参数最多只允许有一个,会作为没有任何谓词匹配上时使用的默认参数类型。 另外,`Handle` 方法有对应的 `HandleAsync` 异步版本,用于处理异步的任务。 @@ -246,7 +253,7 @@ public class SelfWrittenShareOptionsParser : CommandLineOptionParser 请注意,解析 URL 有如下限制: 1. 你的 `Options` 类型中所有的 `ValueAttribute` 都将无法被赋值; -1. 你的 `Options` 类型中不允许出现标记有 `OptionAttribute` 的字符串集合属性。 +1. 你的 `Options` 类型中不允许出现标记有 `OptionAttribute` 的字符串集合属性(即 `OptionAttribute` 标记的属性类型不能是 `IList` 等类型)。 另外,URL 解析中的选项名称也是大小写敏感的。当你在 `Options` 类型中正确使用 PascalCase 风格定义了选项的长名称后,你在 URL 中既可以使用 PascalCase 风格也可以使用 camelCase 风格。 @@ -272,6 +279,8 @@ public class SelfWrittenShareOptionsParser : CommandLineOptionParser 总结来说:完成一次解析只需要 1091ns,也就是大约 10 tick。 +其中,s 是秒,ns 是纳秒。换算关系为 1s = 1,000,000,000 ns。 + 说明: - NoArgs 表示没有传入参数 @@ -280,7 +289,7 @@ public class SelfWrittenShareOptionsParser : CommandLineOptionParser - Handle 表示进行多谓词匹配 - CommandLineParser 是使用的 CommandLineParser 库作为对照 - 测试使用的参数: - - Windows 风格:`"C:\Users\lvyi\Desktop\重命名试验.enbx" -Cloud -Iwb -m Display -s -p Outside -StartupSession 89EA9D26-6464-4E71-BD04-AA6516063D83` - - Cmd 风格:`"C:\Users\lvyi\Desktop\重命名试验.enbx" /Cloud /Iwb /m Display /s /p Outside /StartupSession 89EA9D26-6464-4E71-BD04-AA6516063D83` - - Linux 风格:`"C:\Users\lvyi\Desktop\重命名试验.enbx" --cloud --iwb -m Display -s -p Outside --startup-session 89EA9D26-6464-4E71-BD04-AA6516063D83` - - Url 风格:`walterlv://open/?file=C:\Users\lvyi\Desktop\%E9%87%8D%E5%91%BD%E5%90%8D%E8%AF%95%E9%AA%8C.enbx&cloud=true&iwb=true&silence=true&placement=Outside&startupSession=89EA9D26-6464-4E71-BD04-AA6516063D83` + - Windows 风格:`"C:\Users\lvyi\Desktop\文件.txt" -Cloud -Iwb -m Display -s -p Outside -StartupSession 89EA9D26-6464-4E71-BD04-AA6516063D83` + - Cmd 风格:`"C:\Users\lvyi\Desktop\文件.txt" /Cloud /Iwb /m Display /s /p Outside /StartupSession 89EA9D26-6464-4E71-BD04-AA6516063D83` + - Linux 风格:`"C:\Users\lvyi\Desktop\文件.txt" --cloud --iwb -m Display -s -p Outside --startup-session 89EA9D26-6464-4E71-BD04-AA6516063D83` + - Url 风格:`walterlv://open/?file=C:\Users\lvyi\Desktop\%E6%96%87%E4%BB%B6.txt&cloud=true&iwb=true&silence=true&placement=Outside&startupSession=89EA9D26-6464-4E71-BD04-AA6516063D83` diff --git a/tests/dotnetCampus.CommandLine.Tests/Fakes/CommandLineArgs.cs b/tests/dotnetCampus.CommandLine.Tests/Fakes/CommandLineArgs.cs index 0e9e5f3..2e99c79 100644 --- a/tests/dotnetCampus.CommandLine.Tests/Fakes/CommandLineArgs.cs +++ b/tests/dotnetCampus.CommandLine.Tests/Fakes/CommandLineArgs.cs @@ -3,7 +3,7 @@ internal static class CommandLineArgs { internal const string UrlProtocol = "walterlv"; - internal const string FileValue = @"C:\Users\lvyi\Desktop\重命名试验.txt"; + internal const string FileValue = @"C:\Users\lvyi\Desktop\文件.txt"; internal const bool CloudValue = true; internal const bool IwbValue = true; internal const string ModeValue = "Display"; @@ -57,7 +57,7 @@ internal static class CommandLineArgs internal static readonly string[] UrlArgs = { - @"walterlv://open/?file=C:\Users\lvyi\Desktop\%E9%87%8D%E5%91%BD%E5%90%8D%E8%AF%95%E9%AA%8C.txt&cloud=true&iwb=true&mode=Display&silence=true&placement=Outside&startupSession=89EA9D26-6464-4E71-BD04-AA6516063D83", + @"walterlv://open/?file=C:\Users\lvyi\Desktop\%E6%96%87%E4%BB%B6.txt&cloud=true&iwb=true&mode=Display&silence=true&placement=Outside&startupSession=89EA9D26-6464-4E71-BD04-AA6516063D83", }; internal static readonly string[] EditVerbArgs =