Using snippets with Swift-DocC

Here's a short update on using Swift snippets within Swift-DocC documentation. While snippets have been available in Xcode and toolchains for a while, a recently tagged release of the DocC plugin project makes it easier to depend on a stable version of the feature from your Package.swift file, e.g.:

package.dependencies += [
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.1.0"),
]

The swift-markdown repo has an example file called Snippets.md that shows how you can import snippets of code from your package right into the docs.

And now the SwiftPackageIndex.com site can generate docs using snippets. You can see the swift-markdown package’s snippets page here for an example.

Thanks to @bitjammer and everyone else that helped ship this integration. It’s really fun to write code that shows off your package via a simple swift run snippetName CLI command, and have that same code show up in your docs.

7 Likes

I'm having a rough time replicating this success, even with the wonderfully recent tagged version of swift-docc-plugin to 1.1.0.

Using the Xcode 14.3 beta, when I open the swift-markdown package, I'm getting tons of errors alongs the lines of:

Topic reference 'swift-markdown/Snippets/Parsing/ParseDocumentString' couldn't be resolved. No local documentation matches this reference.

Is this something that requires swift generation through the CLI currently?

The examples of running the plugins with swift run ... works fine.

As I'm attempting to generate HTML documentation myself, it's likewise ending up with blank segments where I've included @Snippet, and I'm at a loss as to how to debug what's happening there. I'm not seeing any strings within the generated JSON files in the docs/data directory that include any of the snippet content.

Is there, by chance, an additional command-line option that I should be specifying, or an environment variable that needs to be set, in order to enable a local HTML build with snippets?

Hi @Joseph_Heck sorry for the slow response.

Yes, the CLI option is the only way to generate the docs that include snippets, using the SwiftPM plugin. e.g.

`swift package generate-documentation`

After you do that, you'll see a report where the output is located, so you can type something like:

`open /Users/timtr/Documents/swift-markdown/.build/plugins/Swift-DocC/outputs`

This will open Finder to a bunch of .doccarchive files. Double-click to option Markdown.doccarchive and Xcode will launch with that new set of docs open. There is a doc file "Snippets" that includes all these snippets from the package, rendered into the page. We'd like to make the Xcode experience even more integrated in the future.

A common case will be for folks to generate their docs and host in GitHub Pages or on their own website. You can preview the docs from the CLI also, via:

`swift package --disable-sandbox preview-documentation --target Markdown`

And then point your browser to:

`http://localhost:8080/documentation/markdown/snippets`

Hope that helps!

1 Like

@TimTr Brilliant, thank you!

Oh, and brief follow-up -- one reason I mentioned the page on Swift Package Index is because any package they index will get docs generated and hosted. Since SPI is supporting snippets in their pipeline anyone can start adding snippets now, and get the docs published, quite easily.

1 Like

Totally - love that as a service, and I’ve been a supporter for a couple years now. But… I wanted to preview what was getting published before it hit the streets - so your notes were perfect, thank you!

Can those repeated import Markdown in the snippets be avoided? After a few repetitions, they start to lose any efficiency, and eventually become like a gimmick, a little bit embarrassing.

@gwendal.roue I don't think so - each snippet is in effect it's own tiny "executable" - so it's got to come from somewhere... at least if you want to use it.

1 Like

I agree that snippets must be able to execute. That's how documentation writer make sure that the documented sample codes compile.

Now, snippets are also supposed to be documentation support. To be read by people. In this context, I encourage you to look at the demo page not from the point of view of the proud snippet implementor, but from the point of view of the documentation readers who looks for focused information. The 17 occurrences of import Markdown eventually turn into an inelegant and distracting noise. "Oh, import Markdown, yeah, yeah, got it. I already know the name of your package, thanks."

I might try a less dismissive way to say it.

The demo page, as it is currently, displays the DocC snippets feature as a way to embed full scripts or playgrounds, in documentation articles.

While useful, and thanks for that, this completely misses the need of documentation writers to edit their sample code for maximum documentation efficiency. This has been abundantly developed during the review. Documentation writers would love to have their documented sample codes compiled and checked, but the quality of the published document is more important.

This makes the demo page a kind of reminder of the current limitations of the DocC snippets feature. Even a page of this kind, full of medium-sized script-like sample codes, would profit from having a single occurrence of import Markdown in its copy (in the first snippet only).

I apologize that I came came across as dismissive - I didn't intend that, only to share that it was required within the snippet itself, since each snippet was it's own independent executable.

I personally think including the "import ..." it in the viewed snippets is useful, and if you personally don't want to see that repetition within the samples that are shown, there are mechanisms to select and hide portions of the snippet. As you mentioned, it was a prolific part of the review - but that capability (I think) is there - although I haven't verified it yet within any of my own work. I believe you can exclude sections of your snippet code (for those places that you feel just added repetitive boilerplate) with the following codes:

// snippet.hide

and

// snippet.show
2 Likes

Ho, that's great! I really thought this feature wasn't there, because the demo page wasn't using it :sweat_smile: Thank you very much Joseph :-) And… it's hard to make a demo, I know :wink:

Glad to see Joe chimed in here -- yes, the show/hide method is indeed a great way to control what is shown in rendered documentation.

Also, a good idea to get some demos showing off more features of snippets directly within documentation. That is on our short list of next steps, so people will see them used in more places, with more packages relying on these little programs to experiment with, and show within docs.

Thanks for the follow-up!

2 Likes

PSA for anyone using th Swift-DocC Snippets feature:

When you're making a markdown reference to a snippet, you may see a warning akin to:

warning: Topic reference 'Scale/Snippets/ExampleSnippet' couldn't be resolved. No local documentation matches this reference.

The DocC markdown reference that caused it was:

@Snippet(path: "Scale/Snippets/ExampleSnippet")

The first element of path option is NOT the directory name or repository name, but the name of the package that contains the snippet. So make sure the first element matches the Package name.

This can most easily happen when the name of the package doesn't match the name of the repository. I'd originally presumed that path was purely a filesystem reference.

When I changed it to the package name, it built without warnings. There's not a lot of details/docs on this yet - so figured I'd drop it out here for anyone else exploring using Snippets.

5 Likes

One thing I want to add regarding imports–

Although I've posted an example page with "all of the snippets" for Swift Markdown, which includes code only about Swift Markdown, I want to seed a consideration out there that it could be possible for a library's snippets to appear in places other than in your own documentation, whether it's all on one page or not. Just to give some examples: search results across many packages, embeds in Q&A sites, or cases where snippets show a possible combination of APIs across multiple modules. What seems repetitive and redundant here might not be in other contexts. It might be helpful to think of each snippet having an imaginary "start a new project with this code" button.

With that, it's important to separate presentation concerns with the important underlying data. If this is purely a presentation issue for the snippets around one module, I would actually recommend keeping those import statements and not use the hide function–that effectively removes the lines from the resulting JSON output. If someone comes to a page of docs via some circuitous path, then the snippet might not be all that usable without more context.

Perhaps what is needed here is a DocC rendering affordance to automatically fold import statement lines. Along with an automatically generated "here are all of the snippets" index page and copy/paste functionality, I think that would really round out snippets on the presentation side and still allow them to appear whole in other contexts too.

To add to Joe's tip, I should also mention the named slice functionality just in case others aren't already aware. These don't remove lines from the data but allow one to reference potentially many named, non-overlapping regions in the same snippet. Hiding should be reserved for things like license footers, test code, or unique demo setup code when running snippets inside a package–things that authors wouldn't want the user to take with them as a starting point.

// snippet.someid
// snippet.end

// snippet.someotherid
// snippet.end

That allows a reference to the slice with: @Snippet(path: "my-package/Snippets/Path/To/Snippet", slice: "someid")

Hopefully this doesn't make snippets grow too large! :slight_smile:

Finally, I just want to thank you Joe and Gwendal for your thoughts. And I totally acknowledge that more complete documentation for the feature is needed. We'll get there!

3 Likes