Swift-Parsing Library Introduction
The swift-parsing library helps Swift developers parse data. Outside of the library documentation, there isn’t a lot of material on using the library so I am sharing what I learned using swift-parsing in an app I’m developing.
Example
The example in this article parses the output of the Jujutsu version control system’s jj show command that shows details of a change. The output of the jj show command looks similar to the following text:
Commit ID: 73d31d1766b0bc211d22b87f2f911429f3846a61
Change ID: lyrzonnxolkpxytwwykkqpvvlqmvlxqm
Bookmarks: trunk@origin
Author : Bob Ducca <ducca@example.com> (2025-06-23 18:04:09)
Committer: Bob Ducca <ducca@example.com> (2025-06-23 18:04:49)
Add sentence that you have to set a bookmark and push.
Add a paragraph so the change description has multiple paragraphs, which is good for unit testing change descriptions.
Modified regular file GitHub.md:
...
4 4:
5 5: ## Working with Existing GitHub Repo
6 6:
7 7: In most cases you have an existing GitHub repo. This is what you do.
8:
9: You have to set a bookmark and call `jj git push`.
The Change struct stores the change information.
struct Change {
var changeId: String
var author: String
var timestamp: String
var description: String
}
The rest of the article covers how to use swift-parsing to parse the jj show command output into the fields of the Change struct.
Parsing the Change ID
The change ID looks like the following:
Change ID: lyrzonnxolkpxytwwykkqpvvlqmvlxqm
Parsing the change ID involves the following steps:
- Skip everything through the text
Change ID:. - Skip any additional whitespace.
- Capture everything up to the end of the line.
Use the Skip parser to skip text. Use the PrefixThrough parser to go through a certain substring. Use the Whitespace parser to go through whitespace. The following code performs the first two steps:
Skip {
PrefixThrough("Change ID:")
Whitespace()
}
The following code performs the third step:
PrefixUpTo("\n")
.map(String.init)
Let’s show the whole code to create the parser to parse the change ID.
let changeInfo = Parse {
Skip {
PrefixThrough("Change ID:")
Whitespace()
}
PrefixUpTo("\n")
.map(String.init)
}
The parsers in the swift-parsing library return Swift substrings. The .map line in the code listing converts the parser output into a Swift string.
The last thing to do is to get the change ID into the Change struct. Call the parser’s parse function to get the change ID. Supply the string for the parser to parse. The parse function can throw errors so you must wrap the call in a do-catch block.
do {
changeId = try String(changeInfo.parse(commandOutput))
} catch {
print(error)
}
Parsing the Author
The author looks like the following:
Author : Bob Ducca <ducca@example.com> (2025-06-23 18:04:09)
Parsing the author requires the following steps:
- Skip everything through the text
Author. - Skip whitespace.
- Skip the colon.
- Skip more whitespace.
- Capture everything up to the
<character, which marks the start of the author email address.
let authorInfo = Parse {
Skip {
PrefixThrough("Author")
Whitespace()
":"
Whitespace()
}
// Capture everything up to the start of the email address.
PrefixUpTo(" <")
.map(String.init)
}
The following code gets the author into the Change struct:
do {
author = try String(authorInfo.parse(commandOutput))
} catch {
print(error)
} }
Parsing the Timestamp
The timestamp looks like the following:
(2025-06-23 18:04:09)
Parsing the timestamp involves the following steps:
- Skip everything through the first left parenthesis.
- Capture everything up to the next right parenthesis.
Code:
let timestampInfo = Parse {
Skip {
PrefixThrough("(")
}
PrefixUpTo(")")
.map(String.init)
}
The following code gets the timestamp into the Change struct:
do {
timestamp = try String(timestampInfo.parse(commandOutput))
} catch {
print(error)
}
Parsing the Description
The change description starts after the first blank line. The description ends with a blank line and one of the following phrases:
Modified regular fileAdded regular fileRemoved regular file
Start by writing a parser for each possible end of a description.
let modifiedFile = Parse {
PrefixUpTo("\n\nModified regular file")
}
let addedFile = Parse {
PrefixUpTo("\n\nAdded regular file")
}
let removedFile = Parse {
PrefixUpTo("\n\nRemoved regular file")
}
Use the OneOf parser to capture everything until you reach one of the possible ends of the description. The following code parses the change description:
let descriptionInfo = Parse {
// Get to the start of the change description.
Skip {
PrefixThrough("\n\n")
Whitespace()
}
// Capture everything up to one of the
// possible description ends.
OneOf {
modifiedFile
addedFile
removedFile
}
.map(String.init)
}
The following code gets the description into the Change struct:
// Remove the indentation at the start of paragraphs
// and the blank line at the end.
do {
description = try String(descriptionInfo.parse(commandOutput))
.trimmingCharacters(in: .whitespacesAndNewlines)
} catch {
print(error)
}
Skipping the Remaining Text
I ran into a problem when I started parsing change details. The parser returned no matches. Xcode’s console printed the following error message:
error: unexpected input
--> input:1:10
1 | Change ID: lyrzonnxolkpxytwwykkqpvvlqmvlxqm
| ^ expected end of input
I learned that when you have captured everything you want to parse and there’s text left to read, you must run the Rest parser that goes through the rest of the text.
Skip {
Rest()
}