Extract Code Snippets
info
Do you prefer learning through videos? Check out our video on Extract and Generate Code Examples
Annotate Unit Tests
The first step to use Bluehawk is to annotate your unit tests with
Bluehawk tags. Tags are similar to HTML or XML; you
put a start
tag before the code you want to annotate, and an end
tag
after it. Bluehawk reads those start and end tags, and generates output
files based on your annotations. You can use Bluehawk tags in any text file.
The Bluehawk tool has language-specific "comment awareness" - as well as string literal awareness - that allows it to:
- Avoid making snippet files with a closing block comment token at the front
- Parse multi-line, commented out JSON "attribute lists" of tags so they don't create syntax errors in your code
There is not currently a way to find out which languages Bluehawk supports from the CLI. However, you can see the supported file extensions in the Bluehawk source to see if Bluehawk supports your preferred language.
To request support for your language, file an issue in the Bluehawk repository, or make a PR to add support for your preferred language.
Output File Names
When you start a Bluehawk code block tag, you append a descriptive title, similar to:
// :snippet-start: person-model
When you generate Bluehawk output, the output file name concatenates:
- The name of your unit test file
- The word
snippet
- The descriptive name that you put in the
snippet-start
tag - The file type of your unit test file
For this example, the code block is in a file called Models.swift, so
the generated output file name would be: Models.snippet.person-model.swift
.
Example Unit Test Annotation
Snippet start and end
This is a complete code example in the Models.swift file in the Realm Docs iOS Unit Test suite.
For this code snippet, we don't need to show the import
statement in our final
example, so we start the snippet after it. Then, after the code we want
to show in our final example, we end the snippet.
import RealmSwift
// :snippet-start: person-model
class Person: Object {
// Required string property
@Persisted var name: String = ""
// Optional string property
@Persisted var address: String?
// Optional integral type property
@Persisted var age: Int?
}
// :snippet-end:
The output file becomes Models.snippet.person-model.swift. After we use the Bluehawk CLI to extract this code example, the final code example looks like this:
class Person: Object {
// Required string property
@Persisted var name: String = ""
// Optional string property
@Persisted var address: String?
// Optional integral type property
@Persisted var age: Int?
}
The output file includes only the lines of code between the snippet-start
and the snippet-end
tags.
Remove code
Bluehawk lets you to remove code that isn't relevant to your documentation viewers.
This example is from the ManageEmailPasswordUsers.swift file in the Realm Docs iOS Unit Test suite.
This uses the snippet start and end tags, but it also uses // :remove-start:
and // :remove-end:
to remove elements of the code example. Here, we're
removing a test assertion in the catch
block that the documentation viewer
doesn't need to see. You might also use it to remove test setup or teardown code
that isn't relevant to your documentation viewers.
func testPasswordResetFunc() async {
// :snippet-start: password-reset-function
let app = App(id: YOUR_REALM_APP_ID)
let client = app.emailPasswordAuth
let email = "forgot.my.password@example.com"
let newPassword = "mynewpassword12345"
// The password reset function takes any number of
// arguments. You might ask the user to provide answers to
// security questions, for example, to verify the user
// should be able to complete the password reset.
let args: [AnyBSON] = []
// This SDK call maps to the custom password reset
// function that you define in the backend
do {
try await client.callResetPasswordFunction(email: email, password: newPassword, args: args)
print("Password reset successful!")
} catch {
print("Password reset failed: \(error.localizedDescription)")
// :remove-start:
XCTAssertEqual(error.localizedDescription, "user not found")
// :remove-end:
}
// :snippet-end:
}
The final output code example at ManageEmailPasswordUsers.snippet.password-reset-function.swift looks like:
let app = App(id: YOUR_REALM_APP_ID)
let client = app.emailPasswordAuth
let email = "forgot.my.password@example.com"
let newPassword = "mynewpassword12345"
// The password reset function takes any number of
// arguments. You might ask the user to provide answers to
// security questions, for example, to verify the user
// should be able to complete the password reset.
let args: [AnyBSON] = []
// This SDK call maps to the custom password reset
// function that you define in the backend
do {
try await client.callResetPasswordFunction(email: email, password: newPassword, args: args)
print("Password reset successful!")
} catch {
print("Password reset failed: \(error.localizedDescription)")
}
The catch
block does not show the hidden assertion; it only shows the
print line.
Replace
Bluehawk gives you the ability to replace terms with different terms, or even
nothing at all. In the Realm Docs iOS Unit Test suite, we use the replace
tag
to remove awkward names we have to use to avoid namespace collisions. For
example, here are the opening lines of the
ReadWriteData.swift
file:
// :replace-start: {
// "terms": {
// "ReadWriteDataExamples_": ""
// }
// }
import XCTest
import RealmSwift
// :snippet-start: models
class ReadWriteDataExamples_DogToy: Object {
@Persisted var name = ""
}
class ReadWriteDataExamples_Dog: Object {
@Persisted var name = ""
@Persisted var age = 0
@Persisted var color = ""
@Persisted var currentCity = ""
// To-one relationship
@Persisted var favoriteToy: ReadWriteDataExamples_DogToy?
}
class ReadWriteDataExamples_Person: Object {
@Persisted(primaryKey: true) var id = 0
@Persisted var name = ""
// To-many relationship - a person can have many dogs
@Persisted var dogs: List<ReadWriteDataExamples_Dog>
// Inverse relationship - a person can be a member of many clubs
@Persisted(originProperty: "members") var clubs: LinkingObjects<ReadWriteDataExamples_DogClub>
}
class ReadWriteDataExamples_DogClub: Object {
@Persisted var name = ""
@Persisted var members: List<ReadWriteDataExamples_Person>
}
// :snippet-end:
// Many more lines of code examples, until eventually, we end the replace
// :replace-end:
As you can see, the model names such as ReadWriteDataExamples_DogToy
are
very awkward. The ReadWriteDataExamples
is present to avoid namespace
collisions with Dog
or Person
models in other test files. But this
awkward name isn't something we want to show documentation viewers.
Fortunately, replace lets us swap any instance of the term we specify with
some alternative. In this example, we replace ReadWriteDataExamples
with an empty string.
// :replace-start: {
// "terms": {
// "ReadWriteDataExamples_": ""
// }
// }
The output file for the code block above looks like:
class DogToy: Object {
@Persisted var name = ""
}
class Dog: Object {
@Persisted var name = ""
@Persisted var age = 0
@Persisted var color = ""
@Persisted var currentCity = ""
// To-one relationship
@Persisted var favoriteToy: DogToy?
}
class Person: Object {
@Persisted(primaryKey: true) var id = 0
@Persisted var name = ""
// To-many relationship - a person can have many dogs
@Persisted var dogs: List<Dog>
// Inverse relationship - a person can be a member of many clubs
@Persisted(originProperty: "members") var clubs: LinkingObjects<DogClub>
}
class DogClub: Object {
@Persisted var name = ""
@Persisted var members: List<Person>
}
The long, awkward name has been replaced with nothing.
tip
Be careful with replace
. Use VerySpecificNamesAndCharacters_ here so
you can be sure you don't unintentionally delete something common.
Use the CLI to Extract Snippets
After you annotate your code examples with Bluehawk tags, use the Bluehawk CLI to parse the content. The CLI generates output files that contain only the content you specify. The Bluehawk CLI accepts various commands to generate output in the way you want it.
The most common command you'll use is bluehawk snip
. When you snip code
blocks, you pass the output directory and the input file or directory.
bluehawk snip -o <output-directory> <input-directory-or-file>
Extract Code from a Single File
If you're just updating a single code example or tutorial, you can extract code from a single file:
bluehawk snip -o source/examples/generated/code/start/ examples/ios/Examples/ReadWriteData.swift
This example extracts code snippets from the ReadWriteData.swift
file, and
generates output files in source/examples/generated/code/start/
.
Extract Code in a Directory
If you want to generate Bluehawk output for all the files in a directory, you can pass a directory as an input source:
bluehawk snip -o source/examples/generated/code/start/ examples/ios/Examples
In this example, examples/ios/examples
is the directory that contains all
of the Realm docs iOS unit test files. If we create new test files, or
change existing tests, and then run this command, Bluehawk generates
new or updated output files for all changes.
tip
Make an alias
for common Bluehawk commands. If you find yourself always running the same
bluehawk snip
command with the same input and output directories, make
it a command-line alias. I have bluehawkify
and bluehawkify-android
as
aliases for common Bluehawk CLI commands.
Include the Snippets In Your Documentation
After annotating code examples and extracting them with the Bluehawk CLI, you've got code files in an output directory.
Now it's time to include those code blocks in your documentation. How you do that depends on your documentation tooling.
In Realm documentation, our build system uses reStructured Text (rST), which
has an include
option
that looks like:
.. literalinclude:: /examples/generated/code/start/ReadWriteData.snippet.models.swift
:language: swift
In Docusaurus, you would import
the file from a generated file directory and use it in a special block:
import QuickStartTestSnippetUpdateRealmObjectDart from "!!raw-loader!@site/generated/flutter/quick_start_test.snippet.update-realm-object.dart";
<CodeBlock className="language-dart">
{QuickStartTestSnippetUpdateRealmObjectDart}
</CodeBlock>
Consult your documentation tooling for the best way to include external files in your documentation set.