1 #Region "Microsoft.VisualBasic::14407b1f2769d73e591b8092ffaade2b, Microsoft.VisualBasic.Core\CommandLine\InteropService\CLIBuilder.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 CLIBuildMethod
35     
36     '         FunctionGetCLI, (+2 OverloadsGetPrefix, SimpleBuilder
37     '         Delegate Function
38     
39     '             Function: booleanRule, ClearParameters, pathRule, stringEnumRule, stringRule
40     
41     
42     
43     ' /********************************************************************************/
44
45 #End Region
46
47 Imports System.ComponentModel
48 Imports System.Reflection
49 Imports System.Runtime.CompilerServices
50 Imports System.Text
51 Imports Microsoft.VisualBasic.ApplicationServices
52 Imports Microsoft.VisualBasic.CommandLine.Reflection
53 Imports Microsoft.VisualBasic.ComponentModel.DataSourceModel.SchemaMaps
54 Imports Microsoft.VisualBasic.Language
55
56 Namespace CommandLine.InteropService
57
58     Public Module CLIBuildMethod
59
60         ReadOnly Argument As Type = GetType(Argv)
61
62         <Extension>
63         Public Function GetPrefix([property] As PropertyInfo) As String
64             With [property].GetCustomAttribute(Of Prefix)
65                 If .IsNothing Then
66                     Return Nothing
67                 Else
68                     Return .Value
69                 End If
70             End With
71         End Function
72
73         <Extension>
74         Public Function GetPrefix(Of T As Class)(obj As T) As String
75             With obj.GetType.GetCustomAttribute(Of Prefix)
76                 If .IsNothing Then
77                     Return Nothing
78                 Else
79                     Return .Value
80                 End If
81             End With
82         End Function
83
84         ''' <summary>
85         ''' Generates the command line string value for the invoked target cli program using this interop services object instance.
86         ''' (生成命令行参数)
87         ''' </summary>
88         ''' <typeparam name="TInteropService">
89         ''' A class type object for interaction with a commandline program.
90         ''' (与命令行程序进行交互的模块对象类型)
91         ''' </typeparam>
92         ''' <param name="app">目标交互对象的实例</param>
93         ''' <returns></returns>
94         ''' <remarks>
95         ''' 依照类型<see cref="CLITypes"/>来生成参数字符串
96         ''' 
97         ''' <see cref="CLITypes.Boolean"/>, True => 参数名;
98         ''' <see cref="CLITypes.Double"/>, <see cref="CLITypes.Integer"/>, <see cref="CLITypes.String"/>, => 参数名 + 参数值,假若字符串为空则不添加;
99         ''' (假若是枚举值类型,可能还需要再枚举值之中添加<see cref="DescriptionAttribute"/>属性)
100         ''' <see cref="CLITypes.File"/>, 假若字符串为空则不添加,有空格自动添加双引号,相对路径会自动转换为全路径。
101         ''' </remarks>
102         <Extension>
103         Public Function GetCLI(Of TInteropService As Class)(app As TInteropService, Optional prefix$ = ""As String
104             Dim prop As PropertyInfo = Nothing
105             Dim argv As Argv
106             Dim args As BindProperty(Of Argv)() = LinqAPI.Exec(Of BindProperty(Of Argv)) _
107  _
108                 () <= From [property] As PropertyInfo
109                       In GetType(TInteropService).GetProperties()
110                       Let attrs As Object() = [property].GetCustomAttributes(attributeType:=Argument, inherit:=True)
111                       Where Not attrs.IsNullOrEmpty
112                       Let attr As Argv = DirectCast(attrs.First, Argv)
113                       Select New BindProperty(Of Argv)(
114                           attr:=attr,
115                           prop:=[property],
116                           getName:=Function(a) a.Name
117                       )
118
119             Dim sb As New StringBuilder()
120
121             ' undefined类型需要用户自己进行处理
122             For Each argum As BindProperty(Of Argv) In args.Where(Function(a) a.field.Type <> CLITypes.Undefined)
123                 Dim getCLIToken As getValue = convertMethods(argum.field.Type)
124                 Dim value As Object = argum.GetValue(app)
125
126                 ' integer, double这些类型都用nullable类型
127                 ' 所以如果value是nothing,就说明该属性肯定没有赋值
128                 If value Is Nothing Then
129                     ' 如果是nothing则表示没有赋值
130                     ' 跳过
131                     Continue For
132                 Else
133                     prop = DirectCast(argum.member, PropertyInfo)
134                     argv = argum.field
135                 End If
136
137                 With getCLIToken(value, argv, prop)
138                     ' 有些类型是会返回空字符串结果的
139                     ' 例如boolean类型的数据,false的时候是返回空字符串
140                     ' 所以会需要在这里判断一下
141
142                     If Not .StringEmpty Then
143                         ' 如果prefix参数不为空,则会添加统一的前缀
144                         sb.AppendLine(prefix & .ByRef)
145                     End If
146                 End With
147             Next
148
149             Return sb.ToString.TrimEnd
150         End Function
151
152         ''' <summary>
153         ''' Creates a command line string by using simply fills the name and parameter values
154         ''' </summary>
155         ''' <param name="name"></param>
156         ''' <param name="args"></param>
157         ''' <returns></returns>
158         Public Function SimpleBuilder(name$, args As IEnumerable(Of KeyValuePair(Of StringString))) As String
159             Dim sbr As New StringBuilder(name)
160
161             For Each x In args
162                 If String.IsNullOrEmpty(x.Value) Then
163                     Continue For
164                 End If
165
166                 Call sbr.Append(" ")
167                 Call sbr.Append(x.Key & " ")
168                 Call sbr.Append(x.Value.CLIToken)
169             Next
170
171             Return sbr.ToString
172         End Function
173
174 #Region ""
175
176         ''' <summary>
177         ''' Converts the property value to a CLI token
178         ''' </summary>
179         ReadOnly convertMethods As New Dictionary(Of CLITypes, getValue) From {
180  _
181             {CLITypes.Boolean, AddressOf CLIBuildMethod.booleanRule},
182             {CLITypes.Double, AddressOf CLIBuildMethod.stringRule},
183             {CLITypes.File, AddressOf CLIBuildMethod.pathRule},
184             {CLITypes.Integer, AddressOf CLIBuildMethod.stringRule},
185             {CLITypes.String, AddressOf CLIBuildMethod.stringRule}
186         }
187
188         ''' <summary>
189         ''' Convert property to cli parameter value string
190         ''' </summary>
191         ''' <param name="value"></param>
192         ''' <param name="attr"></param>
193         ''' <param name="prop"></param>
194         ''' <returns></returns>
195         Private Delegate Function getValue(value As Object, attr As Argv, prop As PropertyInfo) As String
196
197         ''' <summary>
198         ''' The different between the String and Path is that applying <see cref="CLIToken"/> or <see cref="CLIPath"/>.
199         ''' </summary>
200         ''' <param name="value">只能是<see cref="System.String"/>类型的</param>
201         ''' <param name="attr"></param>
202         ''' <param name="prop"></param>
203         ''' <returns></returns>
204         Private Function pathRule(value As Object, attr As Argv, prop As PropertyInfo) As String
205             Dim path As String = DirectCast(value, String)
206
207             If Not String.IsNullOrEmpty(path) Then
208                 path = $"{attr.Name} {path.CLIPath}"
209             End If
210
211             Return path
212         End Function
213
214         ''' <summary>
215         ''' 可能包含有枚举值
216         ''' </summary>
217         ''' <param name="value"></param>
218         ''' <param name="attr"></param>
219         ''' <param name="prop"></param>
220         ''' <returns></returns>
221         Private Function stringRule(value As Object, attr As Argv, prop As PropertyInfo) As String
222             If prop.PropertyType.Equals(GetType(String)) Then
223                 Dim str As String = Scripting.ToString(value)
224
225                 If String.IsNullOrEmpty(str) Then
226                     Return ""
227                 Else
228                     Return $"{attr.Name} {str.CLIToken}"
229                 End If
230             ElseIf prop.PropertyType.IsInheritsFrom(GetType([Enum])) Then
231                 Return stringEnumRule(value, attr, prop)
232             Else
233                 Dim str As String = Scripting.ToString(value)
234
235                 Return $"{attr.Name} {str}"
236             End If
237         End Function
238
239         ''' <summary>
240         ''' 将枚举类型的属性值转换为命令行的参数值,这个转换过程与<see cref="Argv.Type"/>的值相关
241         ''' 
242         ''' + <see cref="CLITypes.String"/>的时候,会直接调用ToString生成参数值
243         ''' + <see cref="CLITypes.Integer"/>的时候,会将枚举值的数值作为命令行参数值
244         ''' 
245         ''' </summary>
246         ''' <param name="value"></param>
247         ''' <param name="attr"></param>
248         ''' <param name="prop"></param>
249         ''' <returns></returns>
250         Private Function stringEnumRule(value As Object, attr As Argv, prop As PropertyInfo) As String
251             Dim enumValue As [Enum] = DirectCast(value, [Enum])
252
253             If attr.Type = CLITypes.String Then
254                 Return $"{attr.Name} {enumValue.Description.CLIToken}"
255             ElseIf attr.Type = CLITypes.Integer Then
256                 Return $"{attr.Name} {Convert.ToInt32(enumValue)}"
257             Else
258                 Throw New InvalidCastException($"Unable cast {enumValue.GetType.FullName} enum value to such type: {attr.Type.ToString}")
259             End If
260         End Function
261
262         ''' <summary>
263         ''' Property value to boolean flag in the CLI
264         ''' </summary>
265         ''' <param name="value"></param>
266         ''' <param name="attr"></param>
267         ''' <param name="prop"></param>
268         ''' <returns></returns>
269         Private Function booleanRule(value As Object, attr As Argv, prop As PropertyInfo) As String
270             Dim name As String = attr.Name
271             Dim b As Boolean
272
273             If prop.PropertyType.Equals(GetType(Boolean)) Then
274                 b = DirectCast(value, Boolean)
275             Else
276                 b = Scripting.ToString(value).ParseBoolean
277             End If
278
279             If b Then
280                 Return name
281             Else
282                 Return ""
283             End If
284         End Function
285 #End Region
286
287         ''' <summary>
288         ''' Reset the CLI parameters property in the target class object.
289         ''' </summary>
290         ''' <typeparam name="TInteropService"></typeparam>
291         ''' <param name="inst"></param>
292         ''' <returns>返回所重置的参数的个数</returns>
293         ''' <remarks></remarks>
294         Public Function ClearParameters(Of TInteropService As Class)(inst As TInteropService) As Integer
295             Dim n As Integer
296             Dim properties As PropertyInfo() = inst.GetType().GetProperties()
297
298             Try
299                 For Each [property] As PropertyInfo In properties
300                     Dim attrs As Object() = [property].GetCustomAttributes(Argument, inherit:=False)
301
302                     If Not (attrs Is Nothing OrElse attrs.Length = 0) Then
303                         Call [property].SetValue(inst, NothingNothing)
304                         n += 1
305                     End If
306                 Next
307             Catch ex As Exception
308                 Throw New InvalidOperationException(InvalidOperation)
309             End Try
310
311             Return n
312         End Function
313
314         Const InvalidOperation$ = "The target type information is not the 'System.String'!"
315     End Module
316 End Namespace