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