2019-07-29 02:05:05 +08:00
#Region "Microsoft.VisualBasic::117f5bb9d589f01614330c293222e0f2, Microsoft.VisualBasic.Core\CommandLine\CLITools.vb"
2018-09-08 00:34:19 +08:00
' Author:
'
' asuka (amethyst.asuka@gcmodeller.org)
' xie (genetics@smrucc.org)
' xieguigang (xie.guigang@live.com)
'
' Copyright (c) 2018 GPL3 Licensed
'
'
' GNU GENERAL PUBLIC LICENSE (GPL3)
'
'
' This program is free software: you can redistribute it and/or modify
' it under the terms of the GNU General Public License as published by
' the Free Software Foundation, either version 3 of the License, or
' (at your option) any later version.
'
' This program is distributed in the hope that it will be useful,
' but WITHOUT ANY WARRANTY; without even the implied warranty of
' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
' GNU General Public License for more details.
'
' You should have received a copy of the GNU General Public License
' along with this program. If not, see <http://www.gnu.org/licenses/>.
' /********************************************************************************/
' Summaries:
' Module CLITools
'
' Function: __checkKeyDuplicated, Args, CreateObject, CreateParameterValues, Equals
' GetLogicalFlags, GetTokens, IsPossibleLogicFlag, Join, makesureQuot
' Print, ShellExec, SingleValueOrStdIn, TrimParamPrefix, (+3 Overloads) TryParse
'
' Sub: tupleParser
'
'
' /********************************************************************************/
#End Region
Imports System . IO
Imports System . Runtime . CompilerServices
Imports System . Text
Imports System . Text . RegularExpressions
Imports Microsoft . VisualBasic . ApplicationServices . Terminal
Imports Microsoft . VisualBasic . CommandLine . Reflection
Imports Microsoft . VisualBasic . ComponentModel . DataSourceModel
Imports Microsoft . VisualBasic . Language
Imports Microsoft . VisualBasic . Linq
Imports Microsoft . VisualBasic . Scripting . MetaData
Imports Microsoft . VisualBasic . Text
Imports StringList = System . Collections . Generic . IEnumerable ( Of String )
Imports ValueTuple = System . Collections . Generic . KeyValuePair ( Of String , String )
Namespace CommandLine
''' <summary>
''' CLI parser and <see cref="CommandLine"/> object creates.
''' </summary>
< Package ( " CommandLine " ,
Url : = " http://gcmodeller.org " ,
Publisher : = " xie.guigang@gcmodeller.org " ,
Description : = " " ,
Revision : = 52 ) >
Public Module CLITools
<Extension>
Public Function ShellExec ( cli As IIORedirectAbstract ) As String
Call cli . Run ( )
Return cli . StandardOutput
End Function
<Extension>
Public Function Print ( args As CommandLine , Optional sep As Char = " "c , Optional leftMargin % = 0 ) As String
Dim sb As New StringBuilder ( " ArgumentsOf: ` " & args . Name & " ` " )
Dim device As New StringWriter ( sb )
Call device . WriteLine ( )
Call device . WriteLine ( New String ( " - "c , args . Name . Length * 4 ) )
Call device . WriteLine ( )
Call args _
. ToArgumentVector _
. Print (
device ,
sep ,
trilinearTable : = True ,
leftMargin : = leftMargin )
Return sb . ToString
End Function
''' <summary>
''' Parsing parameters from a specific tokens.
''' (从给定的词组之中解析出参数的结构)
''' </summary>
''' <param name="tokens">个数为偶数的,但是假若含有开关的时候,则可能为奇数了</param>
2019-04-23 20:31:20 +08:00
''' <param name="includeLogicals">返回来的列表之中是否包含有逻辑开关</param>
2018-09-08 00:34:19 +08:00
''' <returns></returns>
''' <remarks></remarks>
2019-04-23 20:31:20 +08:00
<Extension> Public Function CreateParameterValues ( tokens $ ( ) , includeLogicals As Boolean , Optional note $ = Nothing ) As List ( Of NamedValue ( Of String ) )
2018-09-08 00:34:19 +08:00
Dim list As New List ( Of NamedValue ( Of String ) )
2019-07-24 20:26:48 +08:00
Dim key As String
2018-09-08 00:34:19 +08:00
If tokens . IsNullOrEmpty Then
Return list
ElseIf tokens . Length = 1 Then
2019-04-23 20:31:20 +08:00
If IsPossibleLogicFlag ( tokens ( Scan0 ) ) AndAlso includeLogicals Then
2018-09-08 00:34:19 +08:00
list += New NamedValue ( Of String ) With {
. Name = tokens ( Scan0 ) ,
. Value = CStr ( True ) ,
. Description = note
}
Else
Return list
End If
End If
' 下面都是多余或者等于两个元素的情况
' 数目多于一个的
For i As Integer = 0 To tokens . Length - 1
Dim [ next ] As Integer = i + 1
If [ next ] = tokens . Length Then
' 这个元素是开关,已经到达最后则没有了,跳出循环
2019-04-23 20:31:20 +08:00
If IsPossibleLogicFlag ( tokens ( i ) ) AndAlso includeLogicals Then
2018-09-08 00:34:19 +08:00
list += New NamedValue ( Of String ) ( tokens ( i ) , True , note )
End If
Exit For
End If
Dim s As String = tokens ( [ next ] )
' 当前的这个元素是开关,下一个也是开关开头,则本元素肯定是一个开关
2019-07-24 20:26:48 +08:00
If tokens ( i ) . ToLower <> " /@set " AndAlso IsPossibleLogicFlag ( s ) Then
2019-04-23 20:31:20 +08:00
If includeLogicals Then
2018-09-08 00:34:19 +08:00
list += New NamedValue ( Of String ) ( tokens ( i ) , True , note )
End If
Continue For
Else
' 下一个元素不是开关,则当前元素为一个参数名,则跳过下一个元素
2019-07-24 20:26:48 +08:00
key = tokens ( i ) . ToLower
2018-09-08 00:34:19 +08:00
list += New NamedValue ( Of String ) ( key , s , note )
i += 1
End If
Next
Return list
End Function
''' <summary>
''' Get all of the logical parameters from the input tokens.
''' (这个函数所生成的逻辑参数的名称全部都是小写形式的)
''' </summary>
''' <param name="args">要求第一个对象不能够是命令的名称</param>
''' <returns></returns>
2019-04-23 20:31:20 +08:00
<Extension> Public Function GetLogicalFlags ( args As IEnumerable ( Of String ) , ByRef singleValue $ ) As String ( )
2018-09-08 00:34:19 +08:00
Dim tokens $ ( ) = args . SafeQuery . ToArray
If tokens . IsNullOrEmpty Then
Return New String ( ) { }
ElseIf tokens . Length = 1 Then
' 只有一个元素,则肯定为开关
Return { tokens ( 0 ) . ToLower }
End If
Dim tkList As New List ( Of String )
For i As Integer = 0 To tokens . Length - 1 '数目多于一个的
Dim next % = i + 1
If [ next ] = tokens . Length Then
If IsPossibleLogicFlag ( obj : = tokens ( i ) ) Then
tkList += tokens ( i ) '
End If
Exit For
End If
Dim s As String = tokens ( [ next ] )
If IsPossibleLogicFlag ( obj : = s ) Then '当前的这个元素是开关,下一个也是开关开头,则本元素肯定是一个开关
If IsPossibleLogicFlag ( obj : = tokens ( i ) ) Then
tkList += tokens ( i )
Else
If i = 0 Then
2019-04-23 20:31:20 +08:00
singleValue = tokens ( i )
2018-09-08 00:34:19 +08:00
End If
End If
Else '下一个元素不是开关,则当前元素为一个参数名,则跳过下一个元素
i += 1
End If
Next
Return ( From s As String In tkList Select s . ToLower ) . ToArray
End Function
''' <summary>
''' Try parsing the cli command string from the string value.(尝试着从文本行之中解析出命令行参数信息)
''' </summary>
''' <param name="args">The commandline arguments which is user inputs from the terminal.</param>
''' <param name="duplicatedAllows">Allow the duplicated command parameter argument name in the input,
''' default is not allowed the duplication.(是否允许有重复名称的参数名出现,默认是不允许的)</param>
''' <returns></returns>
''' <remarks></remarks>
<ExportAPI("TryParse", Info:="Try parsing the cli command String from the String value.")>
<Extension>
Public Function TryParse ( args As StringList ,
Optional duplicatedAllows As Boolean = False ,
Optional rawInput $ = Nothing ) As CommandLine
Dim tokens $ ( ) = args . SafeQuery . ToArray
Dim singleValue $ = " "
If tokens . Length = 0 Then
Return New CommandLine
End If
Dim bools $ ( ) = tokens _
. Skip ( 1 ) _
. GetLogicalFlags ( singleValue )
Dim cli As New CommandLine With {
. Name = tokens ( Scan0 ) . ToLower ,
. Tokens = tokens ,
. BoolFlags = bools ,
. cliCommandArgvs = Join ( tokens )
}
cli . SingleValue = singleValue
cli . cliCommandArgvs = rawInput
If cli . Parameters . Length = 1 AndAlso
String . IsNullOrEmpty ( cli . SingleValue ) Then
cli . SingleValue = cli . Parameters ( 0 )
End If
If tokens . Length > 1 Then
cli . __arguments = tokens . Skip ( 1 ) . ToArray . CreateParameterValues ( False )
Dim Dk As String ( ) = __checkKeyDuplicated ( cli . __arguments )
If Not duplicatedAllows AndAlso Not Dk . IsNullOrEmpty Then
Dim Key $ = String . Join ( " , " , Dk )
Dim msg $ = String . Format ( KeyDuplicated , Key , String . Join ( " " , tokens . Skip ( 1 ) . ToArray ) )
Throw New Exception ( msg )
End If
End If
Return cli
End Function
Const KeyDuplicated As String = " The command line switch key "" {0} "" Is already been added! Here Is your input data: CMD {1}. "
Private Function __checkKeyDuplicated ( source As IEnumerable ( Of NamedValue ( Of String ) ) ) As String ( )
Dim LQuery = ( From param As NamedValue ( Of String )
In source
Select param . Name . ToLower
Group By ToLower Into Group ) . ToArray
Return LinqAPI . Exec ( Of String ) _
_
( ) <= From group
In LQuery
Where group . Group . Count > 1
Select group . ToLower
End Function
''' <summary>
''' Gets the commandline object for the current program.
''' </summary>
''' <returns></returns>
<ExportAPI("args", Info:="Gets the commandline object for the current program.")>
<MethodImpl(MethodImplOptions.AggressiveInlining)>
Public Function Args ( ) As CommandLine
Return App . CommandLine
End Function
''' <summary>
''' Try parsing the cli command string from the string value.
''' (尝试着从文本行之中解析出命令行参数信息, 假若value里面有空格, 则必须要将value添加双引号)
''' </summary>
''' <param name="CLI">The commandline arguments which is user inputs from the terminal.</param>
''' <param name="duplicateAllowed">Allow the duplicated command parameter argument name in the input,
''' default is not allowed the duplication.(是否允许有重复名称的参数名出现,默认是不允许的)</param>
''' <returns></returns>
''' <remarks></remarks>
'''
<ExportAPI("TryParse", Info:="Try parsing the cli command String from the String value.")>
Public Function TryParse ( < Parameter ( " CLI " , " The CLI arguments that inputs from the console by user. " ) > CLI $ ,
<Parameter("Duplicates.Allowed")> Optional duplicateAllowed As Boolean = False ) As CommandLine
If String . IsNullOrEmpty ( CLI ) Then
Return New CommandLine
Else
#Const DEBUG = False
#If DEBUG Then
Call CLI . __DEBUG_ECHO
#End If
End If
Dim args As CommandLine = CLITools _
. GetTokens ( CLI ) _
. TryParse ( duplicateAllowed , rawInput : = CLI )
Return args
End Function
''' <summary>
''' Is this string tokens is a possible <see cref="Boolean"/> value flag
''' </summary>
''' <param name="obj"></param>
''' <returns></returns>
<ExportAPI("IsPossibleBoolFlag?")>
Public Function IsPossibleLogicFlag ( obj As String ) As Boolean
If obj . Contains ( " " ) Then
Return False
2019-04-23 20:31:20 +08:00
ElseIf IsNumeric ( obj ) Then
2018-09-08 00:34:19 +08:00
Return False
End If
' Linux上面全路径总是从/,即根目录开始的
If obj . Count ( " / "c ) > 1 Then
Return False
End If
2019-04-23 20:31:20 +08:00
Return obj . StartsWith ( " - " ) OrElse obj . StartsWith ( " / " )
2018-09-08 00:34:19 +08:00
End Function
''' <summary>
''' ReGenerate the cli command line argument string text.(重新生成命令行字符串)
''' </summary>
''' <param name="tokens">If the token value have a space character, then this function will be wrap that token with quot character automatically.</param>
''' <returns></returns>
''' <remarks></remarks>
'''
< ExportAPI ( " Join " ,
Info : = " ReGenerate the cli command line argument string text.
NOTE : If the token have a space character , then this function will be wrap that token with quot character automatically . " )>
Public Function Join ( tokens As IEnumerable ( Of String ) ) As String
If tokens Is Nothing Then
Return " "
Else
Return tokens _
. Select ( AddressOf makesureQuot ) _
. JoinBy ( " " )
End If
End Function
Private Function makesureQuot ( token As String ) As String
If InStr ( token , " " ) > 0 Then
If token . First = " "" "c AndAlso token . Last = " "" "c Then
Return token
Else
Return $ " "" {token} "" "
End If
Else
Return token
End If
End Function
''' <summary>
''' A regex expression string that use for split the commandline text.
''' (用于分析命令行字符串的正则表达式)
''' </summary>
''' <remarks></remarks>
Public Const SPLIT_REGX_EXPRESSION As String = " \s+(?=(?:[^ "" ]| "" [^ "" ]* "" )*$) "
''' <summary>
''' Try parse the argument tokens which comes from the user input commandline string.
''' (尝试从用户输入的命令行字符串之中解析出所有的参数)
''' </summary>
''' <param name="CLI"></param>
''' <returns></returns>
''' <remarks></remarks>
'''
<ExportAPI("Tokens")>
Public Function GetTokens ( CLI As String ) As String ( )
If String . IsNullOrEmpty ( CLI ) Then
Return New String ( ) { " " }
Else
CLI = CLI . Trim
End If
' 由于在前面App位置已经将应用程序的路径去除了, 所以这里已经不需要了, 只需要直接解析即可
'If Not Environment.OSVersion.Platform = PlatformID.Win32NT Then
' 'LINUX下面的命令行会将程序集的完整路径也传递进来
' Dim l As Integer = Len(Application.ExecutablePath)
' CLI = Mid(CLI, l + 2).Trim
' If String.IsNullOrEmpty(CLI) Then '在linux下面没有传递进来任何参数, 则返回空集合
' Return New String() {""}
' End If
'End If
Dim tokens $ ( ) = Regex . Split ( CLI , SPLIT_REGX_EXPRESSION )
Dim argv As New List ( Of String )
tokens = tokens _
. TakeWhile ( Function ( Token )
Return Not String . IsNullOrEmpty ( Token . Trim )
End Function ) _
. ToArray
For i As Integer = 0 To tokens . Length - 1
Dim s As String = tokens ( i )
' 消除单词单元中的双引号
If s . First = ASCII . Quot AndAlso s . Last = ASCII . Quot Then
s = Mid ( s , 2 , Len ( s ) - 2 )
End If
' argv='dddddd'
' 键值对语法
If s . Contains ( " = "c ) AndAlso Not s . IsURLPattern Then
If i > 0 AndAlso tokens ( i - 1 ) . TextEquals ( " /@set " ) Then
' 在这里是环境变量,不需要进行解析
argv += s
Else
Call s . tupleParser ( argv )
End If
Else
argv += s
End If
Next
Return argv
End Function
''' <summary>
''' 只取第一个=符号出现的位置,结果会被添加进入<paramref name="argv"/>列表之中
''' </summary>
''' <param name="s$"></param>
''' <param name="argv"></param>
<Extension>
Private Sub tupleParser ( s $ , ByRef argv As List ( Of String ) )
Dim splitIndex % = - 1
For j As Integer = 0 To s . Length - 1
Dim c = s ( j )
If c = " = "c AndAlso splitIndex = - 1 Then
' 如果前一个字符是\转义,则不是键值对
If j > 1 AndAlso s ( j - 1 ) <> " \ "c Then
' 这是第一个符号
splitIndex = j
Exit For
Else
splitIndex = - 1
End If
End If
Next
If splitIndex > - 1 Then
Dim name $ = s . Substring ( 0 , splitIndex )
Dim value = s . Substring ( splitIndex + 1 )
argv += name
argv += value
Else
argv += s
End If
End Sub
''' <summary>
''' 会对%进行替换的
''' </summary>
Const TokenSplitRegex As String = " (?=(?:[^%]|%[^%]*%)*$) "
''' <summary>
''' 尝试从输入的语句之中解析出词法单元,注意,这个函数不是处理从操作系统所传递进入的命令行语句
''' </summary>
''' <param name="CommandLine"></param>
''' <returns></returns>
''' <remarks></remarks>
'''
<ExportAPI("TryParse")>
Public Function TryParse ( CommandLine As String , TokenDelimited As String , InnerDelimited As Char ) As String ( )
If String . IsNullOrEmpty ( CommandLine ) Then
Return New String ( ) { " " }
End If
Dim regxPattern $ = TokenDelimited & TokenSplitRegex . Replace ( " % "c , InnerDelimited )
Dim tokens = Regex . Split ( CommandLine , regxPattern )
For i As Integer = 0 To tokens . Length - 1
Dim s As String = tokens ( i )
If s . First = InnerDelimited AndAlso s . Last = InnerDelimited Then '消除单词单元中的双引号
tokens ( i ) = Mid ( s , 2 , Len ( s ) - 2 )
End If
Next
Return tokens
End Function
''' <summary>
''' Creates command line object from a set obj <see cref="KeyValuePair(Of String, String)"/>
''' </summary>
''' <param name="name"></param>
''' <param name="args"></param>
''' <param name="bFlags"></param>
''' <returns></returns>
<ExportAPI("CreateObject")>
Public Function CreateObject ( name $ , args As IEnumerable ( Of ValueTuple ) , Optional bFlags As IEnumerable ( Of String ) = Nothing ) As CommandLine
Dim parameters As New List ( Of NamedValue ( Of String ) )
Dim tokens As New List ( Of String ) From { name }
For Each tuple As ValueTuple In args
Dim key As String = tuple . Key . ToLower
Dim param As New NamedValue ( Of String ) ( key , tuple . Value )
Call parameters . Add ( param )
Call tokens . AddRange ( New String ( ) { key , tuple . Value } )
Next
Return New CommandLine With {
. Name = name ,
. __arguments = parameters ,
. Tokens = tokens . Join ( bFlags ) . ToArray ,
. BoolFlags = bFlags . SafeQuery . ToArray
}
End Function
''' <summary>
''' Trim the CLI argument name its prefix symbols.
''' (修剪命令行参数名称的前置符号)
''' </summary>
''' <param name="argName"></param>
''' <returns></returns>
<ExportAPI("Trim.Prefix.BoolFlag")>
<Extension>
Public Function TrimParamPrefix ( argName $ ) As String
If argName . StartsWith ( " -- " ) Then
Return Mid ( argName , 3 )
ElseIf argName . StartsWith ( " - " ) OrElse argName . StartsWith ( " \ " ) OrElse argName . StartsWith ( " / " ) Then
Return Mid ( argName , 2 )
Else
Return argName
End If
End Function
''' <summary>
''' 请注意, 这个是有方向性的, 由于是依照参数1来进行比较的, 假若args2里面的参数要多于第一个参数, 但是第一个参数里面的所有参数值都可以被参数2完全比对得上的话, 就认为二者是相等的
''' </summary>
''' <param name="args1"></param>
''' <param name="args2"></param>
''' <returns></returns>
'''
<ExportAPI("CLI.Equals")>
Public Function Equals ( args1 As CommandLine , args2 As CommandLine ) As Boolean
If Not String . Equals ( args1 . Name , args2 . Name , StringComparison . OrdinalIgnoreCase ) Then
Return False
End If
For Each bFlag As String In args1 . BoolFlags
If Not args2 . GetBoolean ( bFlag ) Then
Return False
End If
Next
For Each arg As NamedValue ( Of String ) In args1 . __arguments
Dim value2 As String = args2 ( arg . Name )
If Not String . Equals ( value2 , arg . Value , StringComparison . OrdinalIgnoreCase ) Then
Return False
End If
Next
Return True
End Function
<Extension>
Public Function SingleValueOrStdIn ( args As CommandLine ) As String
If Not String . IsNullOrEmpty ( args . SingleValue ) Then
Return args . SingleValue
Else
Dim reader As New StreamReader ( Console . OpenStandardInput )
Return reader . ReadToEnd
End If
End Function
End Module
End Namespace