我想从git remote -v
的第一行提取存储库名称,通常是以下形式:
origin git@github.com:some-user/some-repo.git (fetch)
我使用parsec
快速制作了以下解析器:
-- | Parse the repository name from the output given by the first line of `git remote -v`.
repoNameFromRemoteP :: Parser String
repoNameFromRemoteP = do
_ <- originPart >> hostPart
_ <- char ':'
firstPart <- many1 alphaNum
_ <- char '/'
secondPart <- many1 alphaNum
_ <- string ".git"
return $ firstPart ++ "/" ++ secondPart
where
originPart = many1 alphaNum >> space
hostPart = many1 alphaNum
>> (string "@" <|> string "://")
>> many1 alphaNum `sepBy` char '.'
但是这个解析器看起来有点尴尬。实际上我只对冒号(":"
)后面的内容感兴趣,如果我能为它编写解析器会更容易。
有没有办法让parsec
在失败的比赛中跳过一个角色,并从下一个位置重新尝试?
答案 0 :(得分:3)
如果我理解了这个问题,请尝试many (noneOf ":")
。这将消耗任何字符,直到它看到':'
,然后停止。
编辑:似乎我没有理解这个问题。您可以使用try
组合器来转换可能会消耗某些字符的解析器,然后再将其转换为在失败时不消耗任何字符的解析器。所以:
skipUntil p = try p <|> (anyChar >> skipUntil p)
请注意,这在运行时(因为它会尝试在每个位置匹配p
)和内存(因为try
阻止p
消耗字符,所以这可能非常昂贵,因此在p
完成之前,输入不能被垃圾收集。您可以通过参数化anyChar
位来缓解这两个问题中的第一个,以便调用者可以选择一些廉价的解析器来查找候选位置; e.g。
skipUntil p skipper = try p <|> (skipper >> skipUntil p skipper)
然后,您可以使用上述many (noneOf ":")
构造仅对以p
开头的位置尝试:
。
答案 1 :(得分:0)
sepCap
来自的组合器
replace-megaparsec
可以在失败的比赛中跳过角色,然后从下一个位置重试。
对于您的特定情况,这也许算是过大了,但是它确实解决了 普遍的问题。
import Replace.Megaparsec
import Text.Megaparsec
import Text.Megaparsec.Char
import Data.Maybe
import Data.Either
username :: Parsec Void String String
username = do
void $ single ':'
some $ alphaNumChar <|> single '-'
listToMaybe . rights =<< parseMaybe (sepCap username)
"origin git@github.com:some-user/some-repo.git (fetch)"
Just "some-user"