| 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 | ' Function: GetCLI, (+2 Overloads) GetPrefix, 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 String, String))) 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, Nothing, Nothing) |
| 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 |