1 | #Region "Microsoft.VisualBasic::091f8717219f437a98be357ba3a34200, Microsoft.VisualBasic.Core\CommandLine\Interpreters\View\ManualBuilder.vb" |
2 | |
3 | ' Author: |
4 | ' |
5 | ' asuka (amethyst.asuka@gcmodeller.org) |
6 | ' xie (genetics@smrucc.org) |
7 | ' xieguigang (xie.guigang@live.com) |
8 | ' |
9 | ' Copyright (c) 2018 GPL3 Licensed |
10 | ' |
11 | ' |
12 | ' GNU GENERAL PUBLIC LICENSE (GPL3) |
13 | ' |
14 | ' |
15 | ' This program is free software: you can redistribute it and/or modify |
16 | ' it under the terms of the GNU General Public License as published by |
17 | ' the Free Software Foundation, either version 3 of the License, or |
18 | ' (at your option) any later version. |
19 | ' |
20 | ' This program is distributed in the hope that it will be useful, |
21 | ' but WITHOUT ANY WARRANTY; without even the implied warranty of |
22 | ' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
23 | ' GNU General Public License for more details. |
24 | ' |
25 | ' You should have received a copy of the GNU General Public License |
26 | ' along with this program. If not, see <http://www.gnu.org/licenses/>. |
27 | |
28 | |
29 | |
30 | ' /********************************************************************************/ |
31 | |
32 | ' Summaries: |
33 | |
34 | ' Module ManualBuilder |
35 | ' |
36 | ' Function: APIPrototype, ExampleValue, GetFileExtensions, PrintHelp |
37 | ' |
38 | ' |
39 | ' /********************************************************************************/ |
40 | |
41 | #End Region |
42 | |
43 | Imports System.IO |
44 | Imports System.Runtime.CompilerServices |
45 | Imports Microsoft.VisualBasic.ApplicationServices |
46 | Imports Microsoft.VisualBasic.ApplicationServices.Terminal |
47 | Imports Microsoft.VisualBasic.CommandLine.Reflection |
48 | Imports Microsoft.VisualBasic.CommandLine.Reflection.EntryPoints |
49 | Imports Microsoft.VisualBasic.Language |
50 | Imports Microsoft.VisualBasic.Linq |
51 | Imports Microsoft.VisualBasic.Text |
52 | |
53 | Namespace CommandLine.ManView |
54 | |
55 | ''' <summary> |
56 | ''' 用来生成帮助信息 |
57 | ''' </summary> |
58 | Module ManualBuilder |
59 | |
60 | ''' <summary> |
61 | ''' Prints the formatted help information on the console. |
62 | ''' (用于生成打印在终端上面的命令行帮助信息) |
63 | ''' </summary> |
64 | ''' <param name="api"></param> |
65 | ''' <returns></returns> |
66 | <Extension> Public Function PrintHelp(api As APIEntryPoint) As Integer |
67 | ' 因为在编写帮助信息的时候可能会有多行字符串,则在vb源代码里面会出现前导的空格, |
68 | ' 所以在这里需要将每一行的前导空格删除掉, 否则会破坏掉输出的文本对齐格式。 |
69 | Dim infoLines = api.Info _ |
70 | .LineTokens _ |
71 | .Select(Function(s) s.Trim(" "c, ASCII.TAB)) _ |
72 | .JoinBy(vbCrLf) _ |
73 | .SplitParagraph(90) _ |
74 | .ToArray |
75 | Dim blank$ |
76 | |
77 | If infoLines.IsNullOrEmpty Then |
78 | infoLines = {"Description not available..."} |
79 | End If |
80 | |
81 | ' print API name and description |
82 | Call Console.WriteLine() |
83 | Call Console.WriteLine($" '{api.Name}' - {infoLines.FirstOrDefault}") |
84 | |
85 | If infoLines.Length > 1 Then |
86 | blank = New String( |
87 | " ", |
88 | 3 + ' 三个前导空格 |
89 | 2 + ' 两个命令行名称左右的单引号 |
90 | 3 + ' 空格-空格 |
91 | api.Name.Length) |
92 | |
93 | For Each line$ In infoLines.Skip(1) |
94 | Call Console.WriteLine($"{blank}{line}") |
95 | Next |
96 | End If |
97 | |
98 | ' print usage |
99 | With Console.ForegroundColor |
100 | |
101 | Call Console.WriteLine() |
102 | Call Console.WriteLine($"Usage:") |
103 | Call Console.WriteLine() |
104 | |
105 | Console.ForegroundColor = ConsoleColor.Cyan |
106 | |
107 | Call Console.Write(" ") |
108 | Call Console.Write( |
109 | If(App.Platform = PlatformID.Unix OrElse |
110 | App.Platform = PlatformID.MacOSX, |
111 | App.ExecutablePath.TrimSuffix, |
112 | App.ExecutablePath) & " ") |
113 | |
114 | Console.ForegroundColor = ConsoleColor.Green |
115 | Call Console.WriteLine(api.Usage) |
116 | Console.ForegroundColor = .ByRef |
117 | |
118 | End With |
119 | |
120 | If Not api.Arguments.IsNullOrEmpty Then |
121 | Call Console.WriteLine() |
122 | Call Console.WriteLine(" Command with arguments:") |
123 | Call Console.WriteLine(" ====================================================") |
124 | Call Console.WriteLine() |
125 | |
126 | ' 先计算出可能的最长的前导字符串的组合 |
127 | Dim maxPrefix% = -999 |
128 | Dim s$ |
129 | Dim std_in As Boolean = False |
130 | Dim std_out As Boolean = False |
131 | Dim bool As Boolean = False |
132 | Dim haveOptional As Boolean = False |
133 | Dim boolSeperator As Boolean = False |
134 | Dim stringL$() |
135 | |
136 | ' 先输出必须的参数 |
137 | ' 之后为可选参数,但是可选参数会分为下面的顺序输出 |
138 | ' 1. 文件 |
139 | ' 2. 字符串 |
140 | ' 3. 数值 |
141 | ' 4. 整数 |
142 | ' 5. 逻辑值 |
143 | stringL = api.Arguments _ |
144 | .Select(Function(arg) |
145 | With arg.Value |
146 | |
147 | If .TokenType = CLITypes.Boolean Then |
148 | ' 逻辑值类型的只能够是可选类型 |
149 | s = "(optional) (boolean)" |
150 | bool = True |
151 | Else |
152 | |
153 | If .Pipeline = PipelineTypes.std_in Then |
154 | s = "(*std_in)" |
155 | std_in = True |
156 | ElseIf .Pipeline = PipelineTypes.std_out Then |
157 | s = "(*std_out)" |
158 | std_out = True |
159 | Else |
160 | s = "" |
161 | End If |
162 | |
163 | If .Optional Then |
164 | s &= "(optional)" |
165 | haveOptional = True |
166 | End If |
167 | End If |
168 | End With |
169 | |
170 | Return s |
171 | End Function) _ |
172 | .ToArray |
173 | |
174 | ' println("\n%s", stringL.MaxLengthString) |
175 | |
176 | ' 计算出诸如像(optional) (*std_in) (*std_out) (optional) (boolean)这类开关类型前导的 |
177 | ' 最大长度 |
178 | maxPrefix = stringL.MaxLengthString.Length |
179 | |
180 | ' 这里计算出来的是name usage的最大长度 |
181 | stringL$ = api.Arguments _ |
182 | .Select(Function(x) x.Value.Example) _ |
183 | .ToArray |
184 | |
185 | ' println("\n%s", stringL.MaxLengthString) |
186 | |
187 | Dim l% |
188 | Dim maxLen% = stringL _ |
189 | .MaxLengthString _ |
190 | .Length |
191 | |
192 | ' Call stringL.MaxLengthString.__DEBUG_ECHO |
193 | |
194 | ' 加上开关名字的最大长度就是前面的开关说明部分的最大字符串长度 |
195 | ' 后面的description帮助信息的偏移量都是依据这个值计算出来的 |
196 | Dim helpOffset% = maxPrefix + maxLen |
197 | Dim skipOptionalLine As Boolean = False |
198 | |
199 | ' 必须的参数放在前面,可选的参数都是在后面的位置 |
200 | For Each param As Argument In api.Arguments.Select(Function(x) x.Value) |
201 | |
202 | If param.TokenType = CLITypes.Boolean AndAlso Not boolSeperator Then |
203 | boolSeperator = True |
204 | |
205 | Call Console.WriteLine() |
206 | Call Console.WriteLine(" Options:") |
207 | Call Console.WriteLine() |
208 | End If |
209 | |
210 | ' Dim l% 这个参数就是当前的这个命令的前半部的标识符部分的字符串长度 |
211 | ' helpOffset%的值减去当前的长度l,即可得到当前的命令的help info的 |
212 | ' 偏移量 |
213 | |
214 | If param.[Optional] Then |
215 | Dim fore = Console.ForegroundColor |
216 | |
217 | If Not skipOptionalLine Then |
218 | skipOptionalLine = True |
219 | Call Console.WriteLine() |
220 | End If |
221 | |
222 | Call Console.Write(" (") |
223 | Console.ForegroundColor = ConsoleColor.Green |
224 | Call Console.Write("optional") |
225 | Console.ForegroundColor = fore |
226 | Call Console.Write(") ") |
227 | |
228 | s = param.Example |
229 | Else |
230 | s = param.Example |
231 | Console.Write(" ") |
232 | End If |
233 | |
234 | l = s.Length |
235 | |
236 | With param |
237 | If .Pipeline = PipelineTypes.std_in Then |
238 | s = "(*std_in) " & s |
239 | ElseIf .Pipeline = PipelineTypes.std_out Then |
240 | s = "(*std_out) " & s |
241 | ElseIf .TokenType = CLITypes.Boolean Then |
242 | s = "(boolean) " & s |
243 | Else |
244 | If Not .Optional Then |
245 | s = New String(" "c, maxPrefix + 1) & s |
246 | End If |
247 | End If |
248 | End With |
249 | |
250 | Call Console.Write(s) |
251 | |
252 | If param.TokenType = CLITypes.Boolean Then |
253 | l += 11 |
254 | ElseIf param.Pipeline = PipelineTypes.std_out Then |
255 | l += 11 |
256 | End If |
257 | |
258 | ' 这里的blank调整的是命令开关名称与描述之间的字符间距 |
259 | blank = New String(" "c, helpOffset - l + 2) |
260 | infoLines$ = param.Description _ |
261 | .LineTokens _ |
262 | .Select(Function(str) str.Trim(" "c, ASCII.TAB)) _ |
263 | .JoinBy(vbCrLf) _ |
264 | .SplitParagraph(120) _ |
265 | .ToArray |
266 | |
267 | Call Console.Write(blank) |
268 | Call Console.WriteLine($"{infoLines.FirstOrDefault}") |
269 | |
270 | If infoLines.Length > 1 Then |
271 | Dim d% = 0 |
272 | |
273 | If param.Optional Then |
274 | d = 13 |
275 | End If |
276 | |
277 | blank = New String(" "c, helpOffset + d + 2) |
278 | |
279 | For Each line In infoLines.Skip(1) |
280 | Call Console.WriteLine(blank & line) |
281 | Next |
282 | End If |
283 | Next |
284 | |
285 | If std_in OrElse std_out OrElse bool Then |
286 | Call Console.WriteLine() |
287 | End If |
288 | |
289 | If std_in Then |
290 | If std_out Then |
291 | Call Console.WriteLine(" *std_in: " & PipelineTypes.std_in.Description) |
292 | Else |
293 | Call Console.WriteLine(" *std_in: " & PipelineTypes.std_in.Description) |
294 | End If |
295 | End If |
296 | If std_out Then |
297 | Call Console.WriteLine(" *std_out: " & PipelineTypes.std_out.Description) |
298 | End If |
299 | |
300 | Dim allExts = api.Arguments _ |
301 | .Select(Function(arg) arg.Value.GetFileExtensions) _ |
302 | .IteratesALL _ |
303 | .Distinct _ |
304 | .OrderBy(Function(ext) ext) _ |
305 | .ToArray |
306 | |
307 | If allExts.Length > 0 Then |
308 | Call Console.WriteLine() |
309 | |
310 | Dim allContentTypes = allExts _ |
311 | .Select(Function(ext) (ext:=ext, Type:=ext.GetMIMEDescrib)) _ |
312 | .ToArray |
313 | Dim table$()() = allContentTypes _ |
314 | .Select(Function(content) |
315 | With content.Type |
316 | Return {" " & content.ext & " ", $"({ .MIMEType}) ", .Name} |
317 | End With |
318 | End Function) _ |
319 | .ToArray |
320 | |
321 | Call table.Print(New StreamWriter(Console.OpenStandardOutput)) |
322 | End If |
323 | |
324 | If bool Then |
325 | Call Console.WriteLine() |
326 | Call Console.WriteLine(" " & boolFlag) |
327 | End If |
328 | End If |
329 | |
330 | Return 0 |
331 | End Function |
332 | |
333 | <Extension> |
334 | Public Function GetFileExtensions(arg As Argument) As String() |
335 | If arg.TokenType = CLITypes.File AndAlso Not arg.Extensions.StringEmpty Then |
336 | Dim extensions$() = arg _ |
337 | .Extensions _ |
338 | .Split(","c) _ |
339 | .Select(AddressOf Trim) _ |
340 | .Select(Function(s) |
341 | If InStr(s, "*.") = 1 Then |
342 | Return s |
343 | Else |
344 | Return $"*.{s}" |
345 | End If |
346 | End Function) _ |
347 | .ToArray |
348 | |
349 | Return extensions |
350 | Else |
351 | Return Nothing |
352 | End If |
353 | End Function |
354 | |
355 | ''' <summary> |
356 | ''' (boolean flag does not require of argument value) |
357 | ''' </summary> |
358 | Public Const boolFlag$ = "(boolean flag does not require of argument value)" |
359 | |
360 | <Extension> |
361 | Public Function ExampleValue(arg As Argument) As String |
362 | Dim example$ |
363 | |
364 | Select Case arg.TokenType |
365 | |
366 | Case CLITypes.Double |
367 | example = "<float>" |
368 | Case CLITypes.Integer |
369 | example = "<int32>" |
370 | Case CLITypes.String |
371 | example = "<term_string>" |
372 | Case CLITypes.File |
373 | |
374 | With arg.GetFileExtensions |
375 | If .IsNullOrEmpty Then |
376 | example = "<file/directory>" |
377 | Else |
378 | example = $"<file, { .JoinBy(", ")}>" |
379 | End If |
380 | End With |
381 | |
382 | Case Else |
383 | example = "unknown" |
384 | End Select |
385 | |
386 | Return example |
387 | End Function |
388 | |
389 | Const CLI$ = "(Microsoft.VisualBasic.CommandLine.CommandLine)" |
390 | Const VBStyle_CLI = "(args As Microsoft.VisualBasic.CommandLine.CommandLine)" |
391 | |
392 | <MethodImpl(MethodImplOptions.AggressiveInlining)> |
393 | Public Function APIPrototype(declare$) As String |
394 | Return [declare].Replace(CLI, VBStyle_CLI) |
395 | End Function |
396 | End Module |
397 | End Namespace |