Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
Annlix
ShellCheck
提交
84945091
S
ShellCheck
项目概览
Annlix
/
ShellCheck
与 Fork 源项目一致
Fork自
镜像 / koalaman / ShellCheck
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
S
ShellCheck
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
84945091
编写于
8月 09, 2014
作者:
V
Vidar Holen
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Warn about missing shebangs.
上级
8ba1f2fd
变更
8
隐藏空白更改
内联
并排
Showing
8 changed file
with
117 addition
and
88 deletion
+117
-88
README.md
README.md
+1
-1
ShellCheck.cabal
ShellCheck.cabal
+1
-0
ShellCheck/Analytics.hs
ShellCheck/Analytics.hs
+29
-31
ShellCheck/Options.hs
ShellCheck/Options.hs
+14
-0
ShellCheck/Parser.hs
ShellCheck/Parser.hs
+3
-4
ShellCheck/Simple.hs
ShellCheck/Simple.hs
+17
-12
shellcheck.1.md
shellcheck.1.md
+10
-6
shellcheck.hs
shellcheck.hs
+42
-34
未找到文件。
README.md
浏览文件 @
84945091
...
...
@@ -16,7 +16,7 @@ The goals of ShellCheck are:
-
To point out subtle caveats, corner cases and pitfalls, that may cause an
advanced user's otherwise working script to fail under future circumstances.
ShellCheck is written in Haskell, and requires
at least 1 GB of RAM
to compile.
ShellCheck is written in Haskell, and requires
2 GB of memory
to compile.
## Installing
...
...
ShellCheck.cabal
浏览文件 @
84945091
...
...
@@ -47,6 +47,7 @@ library
ShellCheck.Analytics
ShellCheck.AST
ShellCheck.Data
ShellCheck.Options
ShellCheck.Parser
ShellCheck.Simple
other-modules:
...
...
ShellCheck/Analytics.hs
浏览文件 @
84945091
...
...
@@ -16,7 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
-}
{-# LANGUAGE TemplateHaskell #-}
module
ShellCheck.Analytics
(
AnalysisOption
(
..
)
,
filterByAnnotation
,
runAnalytics
,
shellForExecutable
,
runTests
)
where
module
ShellCheck.Analytics
(
AnalysisOption
s
(
..
),
defaultAnalysisOptions
,
filterByAnnotation
,
runAnalytics
,
shellForExecutable
,
runTests
)
where
import
Control.Arrow
(
first
)
import
Control.Monad
...
...
@@ -29,23 +29,19 @@ import Data.List
import
Data.Maybe
import
Debug.Trace
import
ShellCheck.AST
import
ShellCheck.Options
import
ShellCheck.Data
import
ShellCheck.Parser
hiding
(
runTests
)
import
Text.Regex
import
qualified
Data.Map
as
Map
import
Test.QuickCheck.All
(
quickCheckAll
)
data
Shell
=
Ksh
|
Zsh
|
Sh
|
Bash
deriving
(
Show
,
Eq
)
data
Parameters
=
Parameters
{
variableFlow
::
[
StackData
],
parentMap
::
Map
.
Map
Id
Token
,
shellType
::
Shell
}
data
AnalysisOption
=
ForceShell
Shell
-- Checks that are run on the AST root
treeChecks
::
[
Parameters
->
Token
->
[
Note
]]
treeChecks
=
[
...
...
@@ -55,11 +51,12 @@ treeChecks = [
,
subshellAssignmentCheck
,
checkSpacefulness
,
checkQuotesInLiterals
,
checkShebang
,
checkShebang
Parameters
,
checkFunctionsUsedExternally
,
checkUnusedAssignments
,
checkUnpassedInFunctions
,
checkArrayWithoutIndex
,
checkShebang
]
checksFor
Sh
=
[
...
...
@@ -81,24 +78,19 @@ checksFor Bash = [
,
checkForDecimals
]
runAnalytics
::
[
AnalysisOption
]
->
Token
->
[
Note
]
runAnalytics
::
AnalysisOptions
->
Token
->
[
Note
]
runAnalytics
options
root
=
runList
options
root
treeChecks
runList
options
root
list
=
notes
where
params
=
Parameters
{
shellType
=
getShellOption
,
shellType
=
fromMaybe
(
determineShell
root
)
$
optionShellType
options
,
parentMap
=
getParentTree
root
,
variableFlow
=
getVariableFlow
(
shellType
params
)
(
parentMap
params
)
root
}
notes
=
concatMap
(
\
f
->
f
params
root
)
list
notes
=
filter
(
\
c
->
getCode
c
`
notElem
`
optionExcludes
options
)
$
concatMap
(
\
f
->
f
params
root
)
list
getCode
(
Note
_
_
c
_
)
=
c
getShellOption
=
fromMaybe
(
determineShell
root
)
.
msum
$
map
(
\
option
->
case
option
of
ForceShell
x
->
return
x
)
options
checkList
l
t
=
concatMap
(
\
f
->
f
t
)
l
...
...
@@ -353,21 +345,21 @@ getFlags _ = []
[]
->
Nothing
(
r
:
_
)
->
Just
r
verify
::
(
Parameters
->
Token
->
Writer
[
a
]
()
)
->
String
->
Bool
verify
::
(
Parameters
->
Token
->
Writer
[
Note
]
()
)
->
String
->
Bool
verify
f
s
=
checkNode
f
s
==
Just
True
verifyNot
::
(
Parameters
->
Token
->
Writer
[
a
]
()
)
->
String
->
Bool
verifyNot
::
(
Parameters
->
Token
->
Writer
[
Note
]
()
)
->
String
->
Bool
verifyNot
f
s
=
checkNode
f
s
==
Just
False
verifyTree
::
(
Parameters
->
Token
->
[
a
])
->
String
->
Bool
verifyTree
::
(
Parameters
->
Token
->
[
Note
])
->
String
->
Bool
verifyTree
f
s
=
checkTree
f
s
==
Just
True
verifyNotTree
::
(
Parameters
->
Token
->
[
a
])
->
String
->
Bool
verifyNotTree
::
(
Parameters
->
Token
->
[
Note
])
->
String
->
Bool
verifyNotTree
f
s
=
checkTree
f
s
==
Just
False
checkNode
f
=
checkTree
(
runNodeAnalysis
f
)
checkTree
f
s
=
case
parseShell
"-"
s
of
(
ParseResult
(
Just
(
t
,
m
))
_
)
->
Just
.
not
.
null
$
runList
[]
t
[
f
]
(
ParseResult
(
Just
(
t
,
m
))
_
)
->
Just
.
not
.
null
$
runList
defaultAnalysisOptions
t
[
f
]
_
->
Nothing
...
...
@@ -504,7 +496,7 @@ checkPipePitfalls _ (T_Pipeline id _ commands) = do
\
(
find
:
xargs
:
_
)
->
let
args
=
deadSimple
xargs
++
deadSimple
find
in
unless
(
or
$
map
(
$
args
)
[
unless
(
any
(
$
args
)
[
hasShortParameter
'0'
,
hasParameter
"null"
,
hasParameter
"print0"
,
...
...
@@ -541,9 +533,9 @@ checkPipePitfalls _ (T_Pipeline id _ commands) = do
for'
l
f
=
for
l
(
first
f
)
first
func
(
x
:
_
)
=
func
(
getId
x
)
first
_
_
=
return
()
hasShortParameter
char
list
=
any
(
\
x
->
"-"
`
isPrefixOf
`
x
&&
char
`
elem
`
x
)
list
hasParameter
string
list
=
any
(
isPrefixOf
string
.
dropWhile
(
==
'-'
))
list
hasShortParameter
char
=
any
(
\
x
->
"-"
`
isPrefixOf
`
x
&&
char
`
elem
`
x
)
hasParameter
string
=
any
(
isPrefixOf
string
.
dropWhile
(
==
'-'
))
checkPipePitfalls
_
_
=
return
()
indexOfSublists
sub
=
f
0
...
...
@@ -593,11 +585,17 @@ mayBecomeMultipleArgs t = willBecomeMultipleArgs t || f t
f
(
T_NormalWord
_
parts
)
=
any
f
parts
f
_
=
False
prop_checkShebang
1
=
verifyTree
checkShebang
"#!/usr/bin/env bash -x
\n
echo cow"
prop_checkShebang
2
=
verifyNotTree
checkShebang
"#! /bin/sh -l "
checkShebang
_
(
T_Script
id
sb
_
)
=
prop_checkShebang
Parameters1
=
verifyTree
checkShebangParameters
"#!/usr/bin/env bash -x
\n
echo cow"
prop_checkShebang
Parameters2
=
verifyNotTree
checkShebangParameters
"#! /bin/sh -l "
checkShebang
Parameters
_
(
T_Script
id
sb
_
)
=
[
Note
id
ErrorC
2096
"On most OS, shebangs can only specify a single parameter."
|
length
(
words
sb
)
>
2
]
prop_checkShebang1
=
verifyNotTree
checkShebang
"#!/usr/bin/env bash -x
\n
echo cow"
prop_checkShebang2
=
verifyNotTree
checkShebang
"#! /bin/sh -l "
prop_checkShebang3
=
verifyTree
checkShebang
"ls -l"
checkShebang
params
(
T_Script
id
sb
_
)
=
[
Note
id
ErrorC
2148
"Include a shebang (#!) to specify the shell."
|
sb
==
""
]
prop_checkBashisms
=
verify
checkBashisms
"while read a; do :; done < <(a)"
prop_checkBashisms2
=
verify
checkBashisms
"[ foo -nt bar ]"
prop_checkBashisms3
=
verify
checkBashisms
"echo $((i++))"
...
...
@@ -618,8 +616,8 @@ prop_checkBashisms17= verify checkBashisms "echo $((RANDOM%6+1))"
prop_checkBashisms18
=
verify
checkBashisms
"foo &> /dev/null"
checkBashisms
_
=
bashism
where
errMsg
id
s
=
err
id
2040
$
"
#!/bin/sh was specified, so
"
++
s
++
" not supported, even when sh is actually bash."
warnMsg
id
s
=
warn
id
2039
$
"
#!/bin/sh was specified, but "
++
s
++
" not standar
d."
errMsg
id
s
=
err
id
2040
$
"
In sh,
"
++
s
++
" not supported, even when sh is actually bash."
warnMsg
id
s
=
warn
id
2039
$
"
In POSIX sh, "
++
s
++
" not supporte
d."
bashism
(
T_ProcSub
id
_
_
)
=
errMsg
id
"process substitution is"
bashism
(
T_Extglob
id
_
_
)
=
warnMsg
id
"extglob is"
bashism
(
T_DollarSingleQuoted
id
_
)
=
warnMsg
id
"$'..' is"
...
...
@@ -1412,7 +1410,7 @@ getWordParts (T_NormalWord _ l) = concatMap getWordParts l
getWordParts
(
T_DoubleQuoted
_
l
)
=
l
getWordParts
other
=
[
other
]
isCommand
token
str
=
isCommandMatch
token
(
\
cmd
->
cmd
==
str
||
(
"/"
++
str
)
`
isSuffixOf
`
cmd
)
isCommand
token
str
=
isCommandMatch
token
(
\
cmd
->
cmd
==
str
||
(
'/'
:
str
)
`
isSuffixOf
`
cmd
)
isUnqualifiedCommand
token
str
=
isCommandMatch
token
(
==
str
)
isCommandMatch
token
matcher
=
fromMaybe
False
$
do
...
...
ShellCheck/Options.hs
0 → 100644
浏览文件 @
84945091
module
ShellCheck.Options
where
data
Shell
=
Ksh
|
Zsh
|
Sh
|
Bash
deriving
(
Show
,
Eq
)
data
AnalysisOptions
=
AnalysisOptions
{
optionShellType
::
Maybe
Shell
,
optionExcludes
::
[
Integer
]
}
defaultAnalysisOptions
=
AnalysisOptions
{
optionShellType
=
Nothing
,
optionExcludes
=
[]
}
ShellCheck/Parser.hs
浏览文件 @
84945091
...
...
@@ -1801,8 +1801,7 @@ readLetSuffix = many1 (readIoRedirect <|> try readLetExpression <|> readCmdWord)
-- Get whatever a parser would parse as a string
readStringForParser
parser
=
do
pos
<-
lookAhead
(
parser
>>
getPosition
)
s
<-
readUntil
pos
return
s
readUntil
pos
where
readUntil
endPos
=
anyChar
`
reluctantlyTill
`
(
getPosition
>>=
guard
.
(
==
endPos
))
...
...
@@ -1995,11 +1994,11 @@ readScript = do
return
$
T_Script
id
sb
commands
;
}
<|>
do
{
parseProblem
WarningC
1014
"Couldn't read any commands."
;
return
$
T_Script
id
sb
[
T_EOF
id
];
return
$
T_Script
id
sb
[
]
}
else
do
many
anyChar
return
$
T_Script
id
sb
[
T_EOF
id
];
return
$
T_Script
id
sb
[]
;
where
basename
s
=
reverse
.
takeWhile
(
/=
'/'
)
.
reverse
$
s
...
...
ShellCheck/Simple.hs
浏览文件 @
84945091
...
...
@@ -18,15 +18,16 @@
{-# LANGUAGE TemplateHaskell #-}
module
ShellCheck.Simple
(
shellCheck
,
ShellCheckComment
,
scLine
,
scColumn
,
scSeverity
,
scCode
,
scMessage
,
runTests
)
where
import
ShellCheck.Parser
hiding
(
runTests
)
import
ShellCheck.Analytics
hiding
(
runTests
)
import
Data.Maybe
import
Text.Parsec.Pos
import
Data.List
import
Data.Maybe
import
ShellCheck.Analytics
hiding
(
runTests
)
import
ShellCheck.Options
import
ShellCheck.Parser
hiding
(
runTests
)
import
Test.QuickCheck.All
(
quickCheckAll
)
import
Text.Parsec.Pos
shellCheck
::
String
->
[
AnalysisOption
]
->
[
ShellCheckComment
]
shellCheck
script
options
=
shellCheck
::
AnalysisOptions
->
String
->
[
ShellCheckComment
]
shellCheck
options
script
=
let
(
ParseResult
result
notes
)
=
parseShell
"-"
script
in
let
allNotes
=
notes
++
concat
(
maybeToList
$
do
(
tree
,
posMap
)
<-
result
...
...
@@ -51,21 +52,25 @@ severityToString s =
formatNote
(
ParseNote
pos
severity
code
text
)
=
ShellCheckComment
(
sourceLine
pos
)
(
sourceColumn
pos
)
(
severityToString
severity
)
(
fromIntegral
code
)
text
testCheck
=
shellCheck
defaultAnalysisOptions
{
optionExcludes
=
[
2148
]
}
-- Ignore #! warnings
prop_findsParseIssue
=
let
comments
=
shellCheck
"echo
\"
$12
\"
"
[]
in
let
comments
=
testCheck
"echo
\"
$12
\"
"
in
length
comments
==
1
&&
scCode
(
head
comments
)
==
1037
prop_commentDisablesParseIssue1
=
null
$
shellCheck
"#shellcheck disable=SC1037
\n
echo
\"
$12
\"
"
[]
null
$
testCheck
"#shellcheck disable=SC1037
\n
echo
\"
$12
\"
"
prop_commentDisablesParseIssue2
=
null
$
shellCheck
"#shellcheck disable=SC1037
\n
#lol
\n
echo
\"
$12
\"
"
[]
null
$
testCheck
"#shellcheck disable=SC1037
\n
#lol
\n
echo
\"
$12
\"
"
prop_findsAnalysisIssue
=
let
comments
=
shellCheck
"echo $1"
[]
in
let
comments
=
testCheck
"echo $1"
in
length
comments
==
1
&&
scCode
(
head
comments
)
==
2086
prop_commentDisablesAnalysisIssue1
=
null
$
shellCheck
"#shellcheck disable=SC2086
\n
echo $1"
[]
null
$
testCheck
"#shellcheck disable=SC2086
\n
echo $1"
prop_commentDisablesAnalysisIssue2
=
null
$
shellCheck
"#shellcheck disable=SC2086
\n
#lol
\n
echo $1"
[]
null
$
testCheck
"#shellcheck disable=SC2086
\n
#lol
\n
echo $1"
prop_optionDisablesIssue1
=
null
$
shellCheck
(
defaultAnalysisOptions
{
optionExcludes
=
[
2086
,
2148
]
})
"echo $1"
return
[]
runTests
=
$
quickCheckAll
...
...
shellcheck.1.md
浏览文件 @
84945091
...
...
@@ -18,24 +18,28 @@ corner cases can cause delayed failures.
# OPTIONS
**-f**
*FORMAT*
,
**--format=**
*FORMAT*
: Specify the output format of shellcheck, which prints its results in the
standard output. Subsequent
**-f**
options are ignored, see
**FORMATS**
below for more information.
**-e**
\
*CODE1*
[,
*CODE2*
...],
\
**--exclude=**
*CODE1*
[,
*CODE2*
...]
: Explicitly exclude the specified codes from the report. Subsequent
**-e**
options are cumulative, but all the codes can be specified at once,
comma-separated as a single argument.
**-f**
*FORMAT*
,
**--format=**
*FORMAT*
: Specify the output format of shellcheck, which prints its results in the
standard output. Subsequent
**-f**
options are ignored, see
**FORMATS**
below for more information.
**-s**
\
*shell*
,
\
**--shell=**
*shell*
: Specify Bourne shell dialect. Valid values are
*sh*
,
*bash*
,
*ksh*
and
*zsh*
. The default is to use the file's shebang, or
*bash*
if the target
shell can't be determined.
**-V**
\
*version*
,
\
**--version**
: Print version and exit.
# FORMATS
**tty**
...
...
shellcheck.hs
浏览文件 @
84945091
...
...
@@ -26,6 +26,7 @@ import GHC.Exts
import
GHC.IO.Device
import
Prelude
hiding
(
catch
)
import
ShellCheck.Data
import
ShellCheck.Options
import
ShellCheck.Simple
import
ShellCheck.Analytics
import
System.Console.GetOpt
...
...
@@ -48,10 +49,10 @@ instance Monoid Status where
header
=
"Usage: shellcheck [OPTIONS...] FILES..."
options
=
[
Option
"f"
[
"format"
]
(
ReqArg
(
Flag
"format"
)
"FORMAT"
)
"output format"
,
Option
"e"
[
"exclude"
]
(
ReqArg
(
Flag
"exclude"
)
"CODE1,CODE2.."
)
"exclude types of warnings"
,
Option
"f"
[
"format"
]
(
ReqArg
(
Flag
"format"
)
"FORMAT"
)
"output format"
,
Option
"s"
[
"shell"
]
(
ReqArg
(
Flag
"shell"
)
"SHELLNAME"
)
"Specify dialect (bash,sh,ksh,zsh)"
,
Option
"V"
[
"version"
]
...
...
@@ -74,15 +75,12 @@ instance JSON ShellCheckComment where
parseArguments
::
[
String
]
->
ErrorT
Status
IO
([
Flag
],
[
FilePath
])
parseArguments
argv
=
case
getOpt
Permute
options
argv
of
(
opts
,
files
,
[]
)
->
do
verifyOptions
opts
files
return
(
opts
,
files
)
(
opts
,
files
,
[]
)
->
return
(
opts
,
files
)
(
_
,
_
,
errors
)
->
do
liftIO
.
printErr
$
concat
errors
++
"
\n
"
++
usageInfo
header
options
throwError
SyntaxFailure
formats
::
Map
.
Map
String
(
[
Flag
]
->
[
FilePath
]
->
IO
Status
)
formats
::
Map
.
Map
String
(
AnalysisOptions
->
[
FilePath
]
->
IO
Status
)
formats
=
Map
.
fromList
[
(
"json"
,
forJson
),
(
"gcc"
,
forGcc
),
...
...
@@ -93,7 +91,7 @@ formats = Map.fromList [
toStatus
=
liftM
(
either
id
(
const
NoProblems
))
.
runErrorT
catchExceptions
::
IO
Status
->
IO
Status
catchExceptions
action
=
action
`
catch
`
handler
catchExceptions
action
=
action
-- action
`catch` handler
where
handler
err
=
do
printErr
$
show
(
err
::
SomeException
)
...
...
@@ -101,7 +99,7 @@ catchExceptions action = action `catch` handler
checkComments
comments
=
if
null
comments
then
NoProblems
else
SomeProblems
forTty
::
[
Flag
]
->
[
FilePath
]
->
IO
Status
forTty
::
AnalysisOptions
->
[
FilePath
]
->
IO
Status
forTty
options
files
=
do
output
<-
mapM
doFile
files
return
$
mconcat
output
...
...
@@ -155,14 +153,14 @@ forTty options files = do
return
$
if
term
then
colorComment
else
const
id
-- This totally ignores the filenames. Fixme?
forJson
::
[
Flag
]
->
[
FilePath
]
->
IO
Status
forJson
::
AnalysisOptions
->
[
FilePath
]
->
IO
Status
forJson
options
files
=
catchExceptions
$
do
comments
<-
liftM
concat
$
mapM
(
commentsFor
options
)
files
putStrLn
$
encodeStrict
comments
return
$
checkComments
comments
-- Mimic GCC "file:line:col: (error|warning|note): message" format
forGcc
::
[
Flag
]
->
[
FilePath
]
->
IO
Status
forGcc
::
AnalysisOptions
->
[
FilePath
]
->
IO
Status
forGcc
options
files
=
do
files
<-
mapM
process
files
return
$
mconcat
files
...
...
@@ -187,7 +185,7 @@ forGcc options files = do
]
-- Checkstyle compatible output. A bit of a hack to avoid XML dependencies
forCheckstyle
::
[
Flag
]
->
[
FilePath
]
->
IO
Status
forCheckstyle
::
AnalysisOptions
->
[
FilePath
]
->
IO
Status
forCheckstyle
options
files
=
do
putStrLn
"<?xml version='1.0' encoding='UTF-8'?>"
putStrLn
"<checkstyle version='4.3'>"
...
...
@@ -226,23 +224,13 @@ forCheckstyle options files = do
commentsFor
options
file
=
liftM
(
getComments
options
)
$
readContents
file
getComments
options
contents
=
excludeCodes
(
getExclusions
options
)
$
shellCheck
contents
analysisOptions
where
analysisOptions
=
catMaybes
[
shellOption
]
shellOption
=
do
option
<-
getOption
options
"shell"
sh
<-
shellForExecutable
option
return
$
ForceShell
sh
getComments
=
shellCheck
readContents
::
FilePath
->
IO
String
readContents
file
=
if
file
==
"-"
then
getContents
else
readFile
file
where
force
s
=
foldr
(
flip
const
)
s
s
-- Realign comments from a tabstop of 8 to 1
makeNonVirtual
comments
contents
=
...
...
@@ -300,8 +288,10 @@ statusToCode status =
RuntimeException
->
ExitFailure
2
process
::
[
Flag
]
->
[
FilePath
]
->
ErrorT
Status
IO
()
process
options
files
=
let
format
=
fromMaybe
"tty"
$
getOption
options
"format"
in
process
flags
files
=
do
options
<-
foldM
(
flip
parseOption
)
defaultAnalysisOptions
flags
verifyFiles
files
let
format
=
fromMaybe
"tty"
$
getOption
flags
"format"
case
Map
.
lookup
format
formats
of
Nothing
->
do
liftIO
$
do
...
...
@@ -312,17 +302,35 @@ process options files =
where
write
s
=
" "
++
s
Just
f
->
ErrorT
$
liftM
Left
$
f
options
files
verifyOptions
::
[
Flag
]
->
[
FilePath
]
->
ErrorT
Status
IO
()
verifyOptions
opts
files
=
do
when
(
isJust
$
getOption
opts
"version"
)
$
do
liftIO
printVersion
throwError
NoProblems
parseOption
flag
options
=
case
flag
of
Flag
"shell"
str
->
fromMaybe
(
die
$
"Unknown shell: "
++
str
)
$
do
shell
<-
shellForExecutable
str
return
$
return
options
{
optionShellType
=
Just
shell
}
let
shell
=
getOption
opts
"shell"
in
when
(
isJust
shell
&&
isNothing
(
shell
>>=
shellForExecutable
))
$
do
liftIO
$
printErr
(
"Unknown shell: "
++
fromJust
shell
)
throwError
SupportFailure
Flag
"exclude"
str
->
do
new
<-
mapM
parseNum
$
split
','
str
let
old
=
optionExcludes
options
return
options
{
optionExcludes
=
new
++
old
}
Flag
"version"
_
->
do
liftIO
printVersion
throwError
NoProblems
_
->
return
options
where
die
s
=
do
liftIO
$
printErr
s
throwError
SupportFailure
parseNum
(
'S'
:
'C'
:
str
)
=
parseNum
str
parseNum
num
=
do
unless
(
all
isDigit
num
)
$
do
liftIO
.
printErr
$
"Bad exclusion: "
++
num
throwError
SyntaxFailure
return
(
Prelude
.
read
num
::
Integer
)
verifyFiles
files
=
when
(
null
files
)
$
do
liftIO
$
printErr
"No files specified.
\n
"
liftIO
$
printErr
$
usageInfo
header
options
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录