Simplifying Mathematica Documentation
Mathematica’s Documentation Center is a big selling point of the software. For many of the core system functions there’s a rich documentation page with the basic call signatures, details on how the function works, and many useful examples.
This is a powerful resource for people, especially those just getting used to how Mathematica works. On the other hand I don’t think many would accuse it of being “simple”. That applies in a few dimensions. For one, it’s often difficult to find exactly the function you wanted. Some of that comes with the territory—it’s a complicated system and language after all—but it certainly could be easier to look through a là the python docs , even if they don’t have the same breadth of coverage. Another place where this applies is in building out documentation. The pages are all built with a system called DocuTools
which can be seen on the Twitch streams . This is basically a huge palette and a standardized template notebook format. It supports all sorts of rich custom things. It makes standard looking doc pages. And it’s (juding from the streams) buggy, got a steep learning curve, and is basically too much for the average user to want. Oh, it also doesn’t build version independent pages.
SimpleDocs
To get around this I wrote a system I’m calling SimpleDocs . You can easily install it off the Mathematica Paclet Server . If you want to contribute that’s always appreciated and the code is here .
<< https://paclets.github.io/PacletServer/Install.wl
PublicPacletInstall["SimpleDocs"]
(*Out:*)
The idea here is to do as little as possible but still get fully valid documentation pages that integrate into the documentation center. On top of that it should be possible to take this simple written documentation and make a web host for it. Think Sphinx or just the standard python docs .
Getting Started
SimpleDocs
has documentation built in (built with SimpleDocs
). You can always just search for "SimpleDocs"
after installing. But the heart of it all is a stylesheet. Here’s all you have to do:
Now you’re ready to start making some documentation.
Adding Metadata
The only thing we really need to do to get this to integrate into the documentation center is to add metadata. The only metadata you really need to add is a "Context"
where your package lives, a "Type"
of object, and a "Label"
for your page. SimpleDocs
will do the rest. Here’s that in action:
At this point you could do nothing else, save this to a Documentation directory, and this page would be possible to find via the Documentation Center. But we’ll go a bit further.
Creating a Symbol Page
Since this is the most common thing people will want to do I figured I’d make a Symbol page here. What it’ll be the page for I don’t know but we’ll figure that out.
First, we’ll take our fresh new notebook and go to the the little tab and select Symbol from the drop down:
Now we just edit this like a normal notebook. Don’t want to do examples since they take too long? Delete them. Don’t need ‘em. Basically at this point you can do whatever you want and it’ll get cooked into the final result. One big warning, though, if you want this to go to the web only use standard styles and never use 2D input . What I mean by that is that for export to the web you don’t want to do too much crazy stuff that will look unnatural online.
Saving and Projects
We can save this any number of ways or places. I’d reccommend going to the little <pre >
</pre> menu in the top right though and selecting Save to Project. That’ll open a window like this:
You can put whatever you want for the name, but if you want things to be as easy as possible, put the name of a paclet that can be found via PacletFind
. The notebook will be saved in a standard location in that paclet:
The project structure is very flexible though. You can supply a directory to save in and a config file to work off of. I’ll detail the config structure later if I have time, but for now look here and in the surrounding package to get a sense for what I’ve done and am aiming to do.
Finally, the name supplied can be supplied for any future notebooks you save so you only have to type the directory name or config file path once.
Saving to Documentation
Saving the file to the project won’t get it into the Documentation Center, though. For that we’ll want to use the Save to Documentation button. After the documentation indices are rebuilt we can just search for SimpleDocsBlogPost and the page will open. When we see it we’ll see a page like:
The mismatch of the color bar on the top and the symbol page comes from how this is labeled in its Metadata. If we changed that to Symbol and hit Populate again it’d be blue.
Saving Markdown
We can also use SimpleDocs to build Markdown for a page. This is super useful if we want to build a webpage from this afterwards. The basic structure SimpleDocs uses is appropriate for use with Ems or, consequently, pelican . To save our notebook to a .md file we just select Save to Markdown from the menu. After we’ve written all of our documentation we can go to the Build Site menu item and it’ll build out a website for us. Here’s a video of how that works:
Advanced Usage
We don’t need to run the code through the main interface. We can also do it via the package. As an example, we’ll use it to build out documentation for a package programmatically in a separate folder and then deploy these docs both as a website and a paclet that someone could install.
Building Documentation Pages
First, though, we load the package:
<<SimpleDocs`Package`
Then we’ll initialize our project. I’m choosing to use the directory $UserDocumentsDirectory/Wolfram Mathematica/Documentation but you can use whatever you like:
root = FileNameJoin@{$UserDocumentsDirectory, "Wolfram Mathematica",
"Documentation"};
projDir = CreateDocumentationPaclet[root, {"SimpleDocs`"}];
InitializeDocsProject[root, "Documentation_SimpleDocs"];
Viewing what was just made:
SystemOpen[projDir]
We see we’ve gotten a basic structure to fill out. Next we’ll find the functions we need to document:
fns=
Block[{$ContextPath={"System`"}},
Apply[
Join,
Names[#<>"*"]&/@
Select[Contexts["SimpleDocs`Package`*"], StringCount[#, "`"]==2&]
]
];
And then we’ll find the functions that are still to be documented (this will only really make sense on subsequent rebuilds):
toDoc=
Pick[
fns,
Not@*FileExistsQ@
FileNameJoin@{projDir, "project", "docs", "content", "ref", #<>".md"}&/@
StringSplit[fns, "`"][[All, -1]]
];
Then we can create a template notebook for the first of these to see what’s happening:
CreateTemplateNotebook["Symbol", ToExpression@fns[[1]]]
(*Out:*)
We can see we’re mostly there, but it’d be preferable to have the little bar at the top say just SIMPLEDOCS SYMBOL. So we’ll write a function to make that happen
correctTitleBar[nb_]:=
With[{c=Cells[nb, CellStyle->"TitleBar"][[1]]},
NotebookWrite[c, Cell["SimpleDocs Symbol", "TitleBar"]];
];
It’s also not possible to see in the image, but the links at the bottom don’t actually point anywhere useful. To get that working nicely we’ll make a secondary notebook which we’ll use as a template to fill in parameters:
We’ll save that as relatedStuff.nb in the projectDir
folder. Then we’ll write a function to insert it into a notebook:
$relatedCells = First@Get@FileNameJoin@{projDir, "relatedStuff.nb"};
correctRelatedStuff[nb_] :=
Module[{cells, firstCell},
firstCell = Cells[nb, CellStyle -> "SeeAlso"];
cells = Cells[nb];
firstCell = Cells[nb, CellStyle -> "Related"][[1]];
cells = Join @@ SplitBy[cells, # =!= firstCell &][[-2 ;;]];
SelectionMove[cells[[-1]], After, Cell];
NotebookDelete@cells;
NotebookWrite[nb, $relatedCells];
];
Finally, so that we can save it cleanly, we’ll set the project name for the notebook and various metadata components. For good measure we’ll also set the directory, but if we don’t need to reload the notebook at a later time we don’t actually need to do this:
setProjectMetadata[nb_]:=
(
CurrentValue[nb, {TaggingRules, "Metadata", "context"}]=
"SimpleDocs`";
CurrentValue[nb, {TaggingRules, "Metadata", "uri"}]=
StringReplace[
CurrentValue[nb, {TaggingRules, "Metadata", "uri"}],
"SimpleDocsPackage"->"SimpleDocs"
];
CurrentValue[nb, {TaggingRules, "SimpleDocs", "Project", "Name"}]=
"Documentation_SimpleDocs";
CurrentValue[nb, {TaggingRules, "SimpleDocs", "Project", "Directory"}]=
projDir;
)
Now we can package that all up into one function:
documentFunction[fn_]:=
Module[{nb, file, docs, md, expr},
nb=CreateTemplateNotebook["Symbol", fn, Visible->False];
setProjectMetadata[nb];
SaveNotebookToProject[nb];
file = NotebookFileName@nb;
correctTitleBar[nb];
correctRelatedStuff[nb];
NotebookSave@nb;
docs = SaveNotebookToDocumentation[nb];
md = SaveNotebookMarkdown[nb];
expr = NotebookGet[nb];
NotebookClose[nb];
Export[
file,
Append[expr, Visible->True]
];
<|
"Notebook"->file,
"Documentation"->docs,
"Markdown"->md
|>
]
And finally apply that to our functions. Before we do that, we’ll select only the proper functions, as the things with OwnValues
will cause us grief at the moment:
trueFunctions=
ToExpression[
toDoc,
StandardForm,
Function[
Null,
If[Length@OwnValues[#]==0,#,Nothing],
HoldAllComplete
]
];
Now we can build out our pages:
documentFunction/@trueFunctions;
After we do that we can see our docs pages exist now and are in the right place:
Finally, we can create a table of contents for our package (and save it in the same way):
allFns =
ToExpression[
fns,
StandardForm,
Function[
Null,
If[Length@OwnValues[#] == 0, #, Nothing],
HoldAllComplete
]
];
Module[{nb, file, docs, md, expr},
nb =
CreateTemplateNotebook["TableOfContents",
"SimpleDocs Functions" -> allFns,
Visible -> False
];
setProjectMetadata[nb];
SaveNotebookToProject[nb];
file = NotebookFileName@nb;
setProjectMetadata[nb];
correctRelatedStuff[nb];
NotebookSave@nb;
docs = SaveNotebookToDocumentation[nb];
md = SaveNotebookMarkdown[nb];
expr = NotebookGet[nb];
NotebookClose[nb];
Export[
file,
Append[expr, Visible -> True]
];
<|
"Notebook" -> file,
"Documentation" -> docs,
"Markdown" -> md
|>
];
Lastly, we update the paclet info file for the project we’re building:
SetPacletInfo["Documentation_SimpleDocs"];
Distributing the Documentation
Once have have this nice documentation, the question remains as to how we get this to others. The simplest way to do this to my mind is to distribute it as a paclet . The acutal distribution of this file could be done any number of ways, but to make life easier I’ve included a small helper function to pack your paclets:
pac = BundlePaclet["Documentation_SimpleDocs"]
(*Out:*)
"/private/var/folders/9t/tqc70b7d61v753jkdbjkvd640000gp/T/_paclets/Documentation_SimpleDocs-0.0.0.paclet"
You can then send this to people to PacletInstall
or put it on a web server . It’s up to you what you want to do with it.
Creating a Documentation Website
Remember that we also have Markdown for our documentation notebooks. That means we can generate a website for it. Again, you can do this however you like, say using pelican, but to simplify this process I included another helper:
docs = BuildDocsSite["Documentation_SimpleDocs"];
Once we’ve built it out we can view what we’ve got with a dependency function:
OpenLocalDocsSite["Documentation_SimpleDocs"];
You can customize this page all sorts of ways, but we won’t get into that today. For now I’ll just make a few changes to the site config to supply an explicit deployment URL and site name:
OpenDocsSiteConfig["Documentation_SimpleDocs"]
After I make my changes I’ll rebuild and deploy in one step:
BuildDocsSite["Documentation_SimpleDocs", "LastBuild"->None, "AutoDeploy"->True];
And I now have a site where people can see the (currently very sparse and not at all helpful) docs I’ve built out: wolframcloud.com/objects/b3m2a1.docs/SimpleDocs