Click here to Skip to main content
14,544,540 members

How To Integrate DocFX API Documentation Into Your GitHub Repository

Rate this:
5.00 (5 votes)
Please Sign up or sign in to vote.
5.00 (5 votes)
17 Sep 2018CPOL
Some claim that it's hard to use DocFX to generate GitHub Pages, but I discovered that it is pretty straightforward, once you mark all the booby traps.


Over the last few years, I have open sourced a great deal of the plumbing that underlies the Microsoft .NET applications that I create for my clients and myself. Since the beginning, I wanted and needed a straightforward way to generate and maintain hypertext documentation. This article shows, step by step, how I accomplished that feat, concluding with a presentation of my road map for integrating this procedure into my development workflow for the other libraries that are already in my GitHub repository, and others that are as yet private.


When I am actively writing code, there is almost always a Web browser window open in the background, usually pointed to the MSDN library or a Stack Overflow topic. Of the two, the MSDN library is most relevant to this topic, because its documentation of the Microsoft .NET Base Class Library and the Win32 API set the standard, largely borrowed from IBM, for good online documentation of an API.

Automatically generating API documentation directly from source code for C# applications is something I have wanted to do for years. The concept is certainly nothing new; among the pioneering applications of the World Wide Web was the documentation of the early versions of Javascript that shipped with middle and late versions of Netscape Navigator. A more recent example that inspired me to revisit the subject is the documentation of the many Javascript packages distributed by the Node Package Manager and hosted on GitHub.

When I resurrected the project, I assumed that I would be installing and using Sandcastle. However, it quickly became apparent that Microsoft and others were abandoning it in favor of a new Microsoft creation called DocFX. Following a week or so of intense study and several false starts, I got some solid traction, and hit pay dirt, in the form of complete documentation of the WizardWrx .NET API GitHub repository, The API documentation is on a separate URL, to which I embedded a link in the repository

The DocFX CLI (command line interface) is distributed as a Chocolatey package. Though there is a NuGet package that supports integration with the project build, I have yet to investigate it. I shall have more to say about it at the end of the article, when I cover my road map.

Using the code

The "code" to which this section refers consists of six major components.

  1. The Chocolatey package manager for Windows.
  2. The DocFX CLI package, delivered and installed via the public Chocolatey repository.
  3. The Windows PowerShell CLI, which both Chocolatey and DocFX use internally. PowerShell is installed on all supported versions of Microsoft Windows.
  4. The Git CLI (Command Line Interface), which I installed alongside Git for Windows a couple of years ago.
  5. Microsoft Visual Studio 2017, which is required for its MSBuild engine. Any edition should work; I am using the Community edition, which does everything I need it to do.
  6. The classic Windows CLI, in the form of cmd.exe, from which you will need mklink, an internal command.

The following subsections cover most of the steps required to generate documentation and integrate it with GitHub as Github Pages. If you have read this far, I assume that you have PowerShell, Visual Studio 2017, and the Git CLI, so I won't cover installing them. Since Chocolatey is relatively new, and is a prerequisite to everything that follows, I shall begin with installing it.

Installing Chocolatey

The main Chocolatey Web site has straightforward installation instructions. These instructions inform you that you need PowerShell 2 or higher and .NET Framework 4 or higher. If your operating system is Windows 7 or greater, you should have both.

The instructions strongly encourage you to use an administrative (elevated) shell to run the installation. After studying the matter a bit, I saw no reason to disregard this recommendation. While this means that you must use an elevated shell to install packages, the result is that they are properly installed and secured in a protected system directory.

Since I am not especially fond of PowerShell, I followed the instructions for the old school command line shell, cmd.exe. Nevertheless, there is no getting around using PowerShell, since the command that you feed to cmd.exe invokes PowerShell, and feeds it a pipeline.

@"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString(''))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"

When the command returns control a few seconds later, the Cholcolatey program file, choco.exe, has been downloaded and installed into directory C:\ProgramData\chocolatey\bin, along with its support DLLs, and C:\ProgramData\chocolatey\bin has been appended to your Windows user PATH list.

Installing DocFX

Though there is a pretty little information page, you can skip it, since the only thing you must know is the following command, for which I hope you kept your elevated shell running. If you closed it, open a new one, and input the following command.

choco install docfx

Given the number of people who are working in the Microsoft .NET ecosystem, and publishing NoGet packages and  open source GitHub repositories of .NET code, I was dismayed to see the small number of downloads reported on the Chocolaty information page for DocFX. Perhaps that's because the user documentation of DocFX leaves a lot to be desired. If so, I hope this article and others like it inspire more people to start using it.

Before you close your administrator command prompt, there is one more command that, while optional, I encourage you to run.

docfx template export default

The above command creates a new directory, _exported_templates, beneath the main DocFX program directory, C:\ProgramData\chocolatey\lib\docfx, and fills it with the template and CSS files from which your documentation is generated. The only item in this directory is another subdirectory, default, which contains the template, CSS, and Javascript files that form the basis of your generated documentation.  Though you can complete the steps that follow without exporting the default template, inspecting it is educational, and was irresistable to someone as curious as I am about what happens under the hood.

There are a few other templates embedded in the package, as shown in the following list.

docfx template list

Existing embedded templates are:

Though I briefely inspected the others, which are exported in the same manner, and occupy like named directories under _exported_templates, I discarded all but the default template. The only other template that looked useful is pdf.default, for which I have no immediate need, since I want HTML documentation, rather than a PDF.

Since the DocFX installer deposits a launcher stub in C:\ProgramData\chocolatey\bin, and everything else is  created in your solution directory, you may close your elevated shell, and perform the remaining tasks in a regular command window. (Though you can run them in the elevated window, why risk it?)

Documenting Your API

The following section assumes that you are documenting an API that meets two prerequeisites.
  1. The project is implemented in a programming language that DocFX supports. Since the API that I am documenting is implemented in C#, a supportend language, I easily cleared the first hurdle.
  2. The public classes, interfaces, enumerations, constants, and methods are documented with "triple-slash" XML comment islands. Since I started using these soon after I started writing in C# 13 years ago to generate IntelliSense help, this hurdle was also easy. Creating the necessary comments is beyond the scope of this article.

I shall first cover the steps required to generate basic API document, then explain a handful of refinements that I made, after I show the result of my first attempt.

A documentation set ie essentially a self-contained Web site that can stand on its own or become part of a larger site by inhabiting one of its directories, and being linked into it as you would any other directory of documents. Generating it is a three-step process.

Step 1: Open a command prompt, and make the solution directory the working directory.

Microsoft Windows [Version 10.0.17134.285]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Users\DAG 2018/09/15  0:29:17.93>pushd F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx

The above illustration assumes that you open a command prompt, then change the working directory.

  • If you have the solution directory visible in a File Explorer window, you can simplify this into one operation by placing your cusor (mouse pointer) in its address bar, entering the word cmd, and pressing the Return (Enter) key.
  • In a command prompt, using pushd condenses changing the logged drive and the logged directory into a single command.

Step 2: Initialize the DocFX project by entering the command docfx init, which produces output similar to the following.

F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx 2018/09/04 21:19:31.99>docfx init
Does the website contain API documentation from source code? (Default: Yes)
Choose Answer (Yes/No): Y
Where to save the generated documentation? (Default: _site)
Press ENTER to move to the next question.

What are the locations of your source code files? (Default: src/**.csproj)
Supported project files could be .sln, .csproj, .vbproj project files, or assembly files .dll, or .cs, .vb source files
You can use glob patterns, e.g. src/**
Press ENTER to move to the next question.

What are the locations of your markdown files overwriting triple slash comments? (Default: apidoc/**.md)
You can specify markdown files with a YAML header to overwrite summary, remarks and description for parameters
You can use glob patterns, e.g. src/**
Press ENTER to move to the next question.

What are the locations of your conceptual files? (Default: articles/**.md,articles/**/toc.yml,toc.yml,*.md)
Supported conceptual files could be any text files. Markdown format is also supported.
You can use glob patterns, e.g. src/**
Press ENTER to move to the next question.

What are the locations of your resource files? (Default: images/**)
The resource files which conceptual files are referencing, e.g. images.
You can use glob patterns, e.g. src/**
Press ENTER to move to the next question.

Do you want to specify external API references?
Supported external API references can be in either JSON or YAML format.
Press ENTER to move to the next question.

What documentation templates do you want to use? (Default: default)
You can define multiple templates in order. The latter one will overwrite the former one if names collide
Predefined templates in docfx are now: default, statictoc
Press ENTER to move to the next question.

Created folder F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx\docfx_project\src
Created folder F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx\docfx_project\api
Created folder F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx\docfx_project\apidoc
Created folder F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx\docfx_project\articles
Created folder F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx\docfx_project\images
Created File F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx\docfx_project\toc.yml
Created File F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx\docfx_project\
Created File F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx\docfx_project\api\toc.yml
Created File F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx\docfx_project\api\
Created File F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx\docfx_project\articles\toc.yml
Created File F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx\docfx_project\articles\
Created File F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx\docfx_project\.gitignore
Created File F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx\docfx_project\api\.gitignore
Created config file F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx\docfx_project\docfx.json
Successfully generated default docfx project to F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx\docfx_project
Please run:
        docfx "F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx\docfx_project\docfx.json" --serve
To generate a default docfx website.

F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx 2018/09/04 21:30:36.66>

The only question for which I overrrode the default response is "What are the locations of your source code files?" While the default response works if you keep your source code in a directory called /src, and put other stuff in the project or solution directory, a standard Visual Studio project is not usually organized this way. Overriding the response with /**.csproj instructs DocFX to scan the entire project directory for the .csproj files that direct the documentation process. Note that I instructed DocFX to ignore the solution file, and look for .csproj files only. For most projects, you are probably safe telling DocFX to ignore the solution file, and go directly to the project configuration files.

There are two important things to note about the above file mask.

  • Note the use of the Unix path separator, /; you must use these in place of the traditional Windows path separator throughout.
  • Note the double asterisk, another common Unix trope.

Step 3: Generate the API documentation.

F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx 2018/09/10 15:23:35.39>docfx docfx_project\docfx.json --metadata
[18-09-10 08:26:29.494]Info:[MetadataCommand.ExtractMetadata]Using msbuild C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin as inner compiler.
[18-09-10 08:26:29.883]Info:[MetadataCommand.ExtractMetadata]Loading projects...
[18-09-10 08:26:34.027]Info:[MetadataCommand.ExtractMetadata]Cache for F:/Source_Code/Visual_Studio/Projects/WizardWrx_Libs/Wizardwrx/ASCIIInfo/ASCIIInfo.csproj,F:/Source_Code/Visual_Studio/Projects/WizardWrx_Libs/Wizardwrx/AssemblyUtils/AssemblyUtils.csproj,F:/Source_Code/Visual_Studio/Projects/WizardWrx_Libs/Wizardwrx/Common/Common.csproj,F:/Source_Code/Visual_Studio/Projects/WizardWrx_Libs/Wizardwrx/ConsoleStreams/ConsoleStreams.csproj,F:/Source_Code/Visual_Studio/Projects/WizardWrx_Libs/Wizardwrx/Core/Core.csproj,F:/Source_Code/Visual_Studio/Projects/WizardWrx_Libs/Wizardwrx/DLLConfigurationManager/DLLConfigurationManager.csproj,F:/Source_Code/Visual_Studio/Projects/WizardWrx_Libs/Wizardwrx/DLLServices2TestStand/DLLServices2TestStand.csproj,F:/Source_Code/Visual_Studio/Projects/WizardWrx_Libs/Wizardwrx/EmbeddedTextFile/EmbeddedTextFile.csproj,F:/Source_Code/Visual_Studio/Projects/WizardWrx_Libs/Wizardwrx/FormatStringEngine/FormatStringEngine.csproj in F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx\ASCIIInfo\obj\xdoc\cache\final\1607483063 is not valid: Could not find file 'F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx\docfx_project\api\.manifest'., rebuild...
[18-09-10 08:26:34.029]Info:[MetadataCommand.ExtractMetadata]Generating metadata for each project...
[18-09-10 08:26:41.803]Info:[MetadataCommand]Completed Scope:MetadataCommand in 12334.6882 milliseconds.
[18-09-10 08:26:41.923]Info:[BuildCommand]6 plug-in(s) loaded.
[18-09-10 08:26:41.947]Info:[BuildCommand]No files are found with glob pattern apidoc/**.md, excluding obj/**,_site/**, under directory "F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx\docfx_project"
[18-09-10 08:26:41.948]Info:[BuildCommand]No files are found with glob pattern images/**, excluding <none>, under directory "F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx\docfx_project"
[18-09-10 08:26:41.965]Info:[BuildCommand]Markdown engine is markdig
[18-09-10 08:26:42.231]Info:[BuildCommand.BuildCore.Build Document]Max parallelism is 8.
[18-09-10 08:26:42.261]Info:[BuildCommand.BuildCore.Build Document.Prepare.CreateIncrementalBuildContext]Build strategy: IsFullBuild
[18-09-10 08:26:43.055]Info:[BuildCommand.BuildCore.Build Document.CompilePhaseHandlerWithIncremental.ConceptualDocumentProcessor]Building 3 file(s) in ConceptualDocumentProcessor(BuildConceptualDocument=>CountWord=>ValidateConceptualDocumentMetadata)...
[18-09-10 08:26:43.057]Info:[BuildCommand.BuildCore.Build Document.CompilePhaseHandlerWithIncremental.TocDocumentProcessor]Building 3 file(s) in TocDocumentProcessor(BuildTocDocument)...
[18-09-10 08:26:43.062]Info:[BuildCommand.BuildCore.Build Document.CompilePhaseHandlerWithIncremental.ManagedReferenceDocumentProcessor]Building 103 file(s) in ManagedReferenceDocumentProcessor(BuildManagedReferenceDocument=>ValidateManagedReferenceDocumentMetadata=>ApplyOverwriteDocumentForMref=>FillReferenceInformation)...
[18-09-10 08:27:03.599]Info:[BuildCommand.BuildCore.Build Document.LinkPhaseHandlerWithIncremental.UpdateContext]0 external references found in 1 xref maps.
[18-09-10 08:27:05.164]Info:[BuildCommand.BuildCore.Build Document.LinkPhaseHandlerWithIncremental.Apply Templates]Applying templates to 109 model(s)...
[18-09-10 08:27:07.463]Info:[BuildCommand.BuildCore.Build Document]XRef map exported.
[18-09-10 08:27:08.157]Info:[BuildCommand.Postprocess]Manifest file saved to manifest.json.
[18-09-10 08:27:08.237]Info:[BuildCommand]Completed building documents in 26309.0396 milliseconds.
[18-09-10 08:27:08.240]Info:[BuildCommand]Completed Scope:BuildCommand in 26434.8885 milliseconds.
[18-09-10 08:27:08.243]Info:Completed in 38776.6216 milliseconds

Build succeeded.
        0 Warning(s)
        0 Error(s)

F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx 2018/09/10 15:27:08.30>

The above output isn't from my first build. I had several false starts before I finally got it right, which I shall discuss next. Notice also that it's not the command suggested by the program as the next step. Since the Web site is composed entirely of static pages, there is nothing to gain from the overhead of a Web server, when you can view the site by double-clicking index.html in the generated /docfx_project/_site directory. Allowing DocFX to spawn a Web server ties up your command prompt, which you need to prepare the site for publication.

Publishing as GitHub Pages

Although the site content is ready to go, there remains one step to perform in your local code base before you commit to your local Git repository and push it to GitHub. There are two options for configuraing a repository for GitHub Pages.
  1. Publish the whole repository. While this is appropriate if the repository is composed entirely of documentation of code that is published elsewhere, such as in a NPM package, it pushes too much content, most of which won't render at all nicely, to your GitHub Pages.
  2. Publish everything in the /docs directory of your repository. This configuration is ideal for publishing the documentation of your API, since it hides the raw source code.

This is all well and good, I hear you say, but the documentation is in /docfx_project/_site; how do I get it into /docs? That's a fair question, and there are at least two options.

  1. The hard way is to copy the contents of /docfx_project/_site into the /docs directory. This is an extra step that must be repeated for each update, and it yields two copies of everything, both of which occupy space in your backup sets. (You are using something besides Git and GitHub to back up your repositories, right?)
  2. The easy way is to create a Windows junction that links /docs to /docfx_project/_site, so that everything that goes into it magically appears in /docs, too. Creating the junction is a one-time operation, and it eliminates the penalty of duplicate copies of the files.

Junctions are nothing new, and recent iterations of Windows use them extensively to implement backwards compatible paths, such as the following five file names, all referring to the same actual file.

"C:\Documents and Settings\All Users\Documents\H_DEASE\AI_PKG_400\WizardWrx.AssemblyPropertyViewer.dll"
"C:\Documents and Settings\Public\Documents\H_DEASE\AI_PKG_400\WizardWrx.AssemblyPropertyViewer.dll"
"C:\Users\All Users\Documents\H_DEASE\AI_PKG_400\WizardWrx.AssemblyPropertyViewer.dll"

Though I learned about junctions many years ago, and have seen them in action since I started working with Windows NT Workstation 4.0 late in the last century, this is the first time I've actually had a practical application for one.

My one-time command to create the required junction and its output are as follows.

mklink /J F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx\docs F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx\docfx_project\_site\
    Junction created for F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx\docs <<===>> F:\Source_Code\Visual_Studio\Projects\WizardWrx_Libs\Wizardwrx\docfx_project\_site

The generic form of the command is as follows.

mklink /J [AbsoluteProjectDirectoryName]docs [AbsoluteProjectDirectoryName]\docfx_project\_site

In the above command, substitute the absolute (fully qualifed) name of the project directory for [AbsoluteProjectDirectoryName].

There are two important things to remember about the mklink command.

  1. Since mklink is an internal command (implemented by cmd.exe), there is no program file called mklink.exe. I wasted a good ten minutes searching for it before I realized that fact.
  2. You specify the destination (in our case, /docs), followed by the target or source, (in our case, \docfx_project\_site). This is the same parameter ordering as you use with fsutil.exe to create a hardlink. If you specify them backwards, you get the following somewhat anbiguous error message: Cannot create a file when that file already exists. FSUtil.exe behaves identically.

If you follow these instructions in the order given, your site content exists, and if you select the /docs directory in the source code tree of your C# project, you'll see exactly the same files as you do when you select the /docfx_project/_site directory. If you don't believe me, empty the /docfx_project/_site directory, then copy an arbitary single file into it. Switch the active file browser view to /docs, and you'll see the file that you just copied into /docfx_project_site. (You can put everything back by either restoring the deleted files from your recycle bin or repeating the last DocFX command to regenerate the site.)

The DocFX command that initialized the /docfx_project directory deposits a .gitignore file there, which you should leave as is, since it tells Git to preserve the template files required to update your API documents, while hiding the /docfx_project/_site directory. This action keeps duplicate files out of your repository, while the /docs junction ensures that your API documentation is included, and appears to be coming from the location where GitHub Pages expects to find it.

Step 1 of 3: Update the local Git repository. Repeat this step as often as necessary.

Assuming that you installed Git Bash and its Explorer extnsion, scroll to the top directory of your project, and choose Git Bash here. Though there are other ways to get to this point, this is by far the easiest, since it eliminates converting the Windows path to a Unix path. However you get there, you next enter the first of three commands to git: git add *. Some text files, such as WizardWrx_NET_API_ABACUS_20180911_013037.LOG, elicit a warning that its line endings will be rewritten. This behavior is governed by the .gitattributes file that is created automatically when you initialize your local git repository.

DAG@ABACUS MINGW64 /f/Source_Code/Visual_Studio/Projects/WizardWrx_Libs/Wizardwrx (master)
$ git add *
warning: LF will be replaced by CRLF in docs/index.html.
The file will have its original line endings in your working directory.
The following paths are ignored by one of your .gitignore files:
Use -f if you really want to add them.
warning: LF will be replaced by CRLF in docs/api/DLLServices2TestStand.Properties.Resources.html.
The file will have its original line endings in your working directory.
warning: LF will be replaced by CRLF in docs/api/DLLServices2TestStand.Properties.html.
The file will have its original line endings in your working directory.


warning: LF will be replaced by CRLF in docs/styles/docfx.vendor.js.
The file will have its original line endings in your working directory.
warning: LF will be replaced by CRLF in docs/styles/lunr.js.
The file will have its original line endings in your working directory.

DAG@ABACUS MINGW64 /f/Source_Code/Visual_Studio/Projects/WizardWrx_Libs/Wizardwrx (master)

The git add command generated dozens of these warnings, of which I preserved only the first three and the last two.

Step 2 of 3: Commit the changes to the local repository. Repeat this step as often as needed.  Enter the folllowing command into your Git Bash prompt:  git commit -m "Amend the test script to output to a new reports directory, and replace the API documentation." The -m switch writes the quoted string that follows into the commit log; this message is thereafter displayed next to each file in the commit until a subsequent commit updates it.

DAG@ABACUS MINGW64 /f/Source_Code/Visual_Studio/Projects/WizardWrx_Libs/Wizardwrx (master)
$ git commit -m "Amend the test script to output to a new reports directory, and replace the API documentation
[master afeb4b9] Amend the test script to output to a new reports directory, and replace the API documentation.
 189 files changed, 48932 insertions(+), 1664 deletions(-)
 rewrite (73%)
 create mode 100644 Test_Data/Application_ENIGMA_20171020_014625.TXT
 create mode 100644 Test_Data/Application_ENIGMA_20171020_015547.TXT
 create mode 100644 Test_Data/Application_ENIGMA_20171028_232630.TXT
 create mode 100644 Test_Data/DependentAssemblyInfoReport.TXT
 create mode 100644 Test_Data/DependentAssemblyInfoReport_20170917_234407.TXT
 create mode 100644 Test_Data/DependentAssemblyInfoReport_DEBUG_20170910_172816.TXT
 create mode 100644 Test_Data/DependentAssemblyInfoReport_RELEASE_20170910_180919.TXT
 create mode 100644 Test_Data/DigestMD5TestCases.TXT
 create mode 100644 Test_Data/MD5_File_Digests.DOCX
 create mode 100644 Test_Data/MaxStringLength.TXT
 create mode 100644 Test_Data/MergeNewItemsIntoArray_Case_01.TXT
 create mode 100644 Test_Data/MergeNewItemsIntoArray_Case_02.TXT
 create mode 100644 Test_Data/MergeNewItemsIntoArray_Case_03.TXT
 create mode 100644 Test_Data/MergeNewItemsIntoArray_Case_04.TXT
 create mode 100644 Test_Data/MergeNewItemsIntoArray_Case_05.TXT
 create mode 100644 Test_Data/MergeNewItemsIntoArray_Case_06.TXT
 create mode 100644 Test_Data/MergeNewItemsIntoArray_Case_07.TXT
 create mode 100644 Test_Data/MergeNewItemsIntoArray_Case_08.TXT
 create mode 100644 Test_Data/MergeNewItemsIntoArray_Case_09.TXT
 create mode 100644 Test_Data/MergeNewItemsIntoArray_Case_10.TXT
 create mode 100644 Test_Data/MergeNewItemsIntoArray_Cases_20140218.ZIPX
 create mode 100644 Test_Data/MergeNewItemsIntoArray_Cases_20140628.ZIPX
 create mode 100644 Test_Data/MergeNewItemsIntoArray_Cases_20140629.ZIPX
 create mode 100644 Test_Data/MergeNewItemsIntoArray_Cases_20140630.ZIPX
 create mode 100644 Test_Data/MergeNewItemsIntoArray_Master.TXT
 create mode 100644 Test_Data/MergeNewItemsIntoArray_Output_01.TXT
 create mode 100644 Test_Data/MergeNewItemsIntoArray_Output_02.TXT
 create mode 100644 Test_Data/MergeNewItemsIntoArray_Output_03.TXT
 create mode 100644 Test_Data/MergeNewItemsIntoArray_Output_04.TXT
 create mode 100644 Test_Data/MergeNewItemsIntoArray_Output_05.TXT
 create mode 100644 Test_Data/MergeNewItemsIntoArray_Output_06.TXT
 create mode 100644 Test_Data/MergeNewItemsIntoArray_Output_07.TXT
 create mode 100644 Test_Data/MergeNewItemsIntoArray_Output_08.TXT
 create mode 100644 Test_Data/MergeNewItemsIntoArray_Output_09.TXT
 create mode 100644 Test_Data/MergeNewItemsIntoArray_Output_10.TXT
 create mode 100644 Test_Data/MergeNewItemsIntoArray_Summary.TXT
 create mode 100644 Test_Data/Plaintext_File_1_Block.TXT
 create mode 100644 Test_Data/Plaintext_File_2_Fractional_Block.TXT
 create mode 100644 Test_Data/Plaintext_File_3_1_Plus_Blocks.TXT
 create mode 100644 Test_Data/Plaintext_File_4_4_Plus_Blocks.TXT
 create mode 100644 Test_Data/Plaintext_File_4_Exactly_8_Blocks.TXT
 create mode 100644 Test_Data/TestCase_Inputs_and_Outputs_20170305_184427.ZIPX
 create mode 100644 Test_Reports/DLLServices2TestStand_Debug_ApplicationEvents_20180913_233040.TXT
 create mode 100644 Test_Reports/DLLServices2TestStand_Release_ApplicationEvents_20180913_233209.TXT
 create mode 100644 WizardWrx_NET_API_ABACUS_20180911_013037.7z.lnk
 create mode 100644 WizardWrx_NET_API_Binaries_Debug.7z
 create mode 100644 WizardWrx_NET_API_Binaries_Release.7z
 copy => docfx_project/api/ (51%)
 rewrite docfx_project/ (100%)
 create mode 100644 docfx_project/templates/C#_Libraries/styles/main.css
 rename docs/{ => api}/DLLServices2TestStand.Properties.Resources.html (59%)
 rename docs/{ => api}/DLLServices2TestStand.Properties.html (100%)
 create mode 100644 docs/api/HelloDocfx.Class1.InnerClass.html
 create mode 100644 docs/api/HelloDocfx.Class1.html
 create mode 100644 docs/api/HelloDocfx.html
 rename docs/{ => api}/WizardWrx.ASCIICharacterDisplayInfo.html (100%)
 rename docs/{ => api}/WizardWrx.ASCII_Character_Display_Table.html (100%)
 rename docs/{ => api}/WizardWrx.ArrayInfo.html (100%)
 rename docs/{ => api}/WizardWrx.AssemblyUtils.AssemblyContainer.html (100%)
 rename docs/{ => api}/WizardWrx.AssemblyUtils.DependentAssemblies.html (100%)
 rename docs/{ => api}/WizardWrx.AssemblyUtils.DependentAssemblyInfo.html (100%)
 rename docs/{ => api}/WizardWrx.AssemblyUtils.PESubsystemInfo.PESubsystemID.html (100%)
 rename docs/{ => api}/WizardWrx.AssemblyUtils.PESubsystemInfo.SubsystemDescription.html (100%)
 rename docs/{ => api}/WizardWrx.AssemblyUtils.PESubsystemInfo.html (100%)
 rename docs/{ => api}/WizardWrx.AssemblyUtils.ReportGenerators.html (100%)
 rename docs/{ => api}/WizardWrx.AssemblyUtils.SortableManagedResourceItem.html (100%)
 rename docs/{ => api}/WizardWrx.AssemblyUtils.html (100%)
 rename docs/{ => api}/WizardWrx.CSVFileInfo.html (100%)
 rename docs/{ => api}/WizardWrx.Common.Properties.Resources.html (100%)
 rename docs/{ => api}/WizardWrx.Common.Properties.html (100%)
 rename docs/{ => api}/WizardWrx.ConsoleStreams.DefaultErrorMessageColors.html (100%)
 rename docs/{ => api}/WizardWrx.ConsoleStreams.ErrorMessagesInColor.ErrorSeverity.html (100%)
 rename docs/{ => api}/WizardWrx.ConsoleStreams.ErrorMessagesInColor.html (100%)
 rename docs/{ => api}/WizardWrx.ConsoleStreams.ErrorMessagesInColorConverter.html (100%)
 rename docs/{ => api}/WizardWrx.ConsoleStreams.MessageInColor.html (100%)
 rename docs/{ => api}/WizardWrx.ConsoleStreams.MessageInColorConverter.html (100%)
 rename docs/{ => api}/WizardWrx.ConsoleStreams.StandardHandleInfo.ConsoleModes.html (100%)
 rename docs/{ => api}/WizardWrx.ConsoleStreams.StandardHandleInfo.ConsoleOutputModes.html (100%)
 rename docs/{ => api}/WizardWrx.ConsoleStreams.StandardHandleInfo.StandardConsoleHandle.html (100%)
 rename docs/{ => api}/WizardWrx.ConsoleStreams.StandardHandleInfo.StandardHandleState.html (100%)
 rename docs/{ => api}/WizardWrx.ConsoleStreams.StandardHandleInfo.html (100%)
 rename docs/{ => api}/WizardWrx.ConsoleStreams.html (100%)
 rename docs/{ => api}/WizardWrx.Core.AgedFileInfo.html (100%)
 rename docs/{ => api}/WizardWrx.Core.AgedFileInfoCollection.html (100%)
 rename docs/{ => api}/WizardWrx.Core.AssemblyLocatorBase.html (100%)
 rename docs/{ => api}/WizardWrx.Core.BasicSystemInfoDisplayMessages.html (100%)
 rename docs/{ => api}/WizardWrx.Core.ByteArrayFormatters.html (100%)
 rename docs/{ => api}/WizardWrx.Core.CmdLneArgsBasic.ArgMatching.html (100%)
 rename docs/{ => api}/WizardWrx.Core.CmdLneArgsBasic.ArgType.html (100%)
 rename docs/{ => api}/WizardWrx.Core.CmdLneArgsBasic.html (100%)
 rename docs/{ => api}/WizardWrx.Core.PropertyDefaults.html (100%)
 rename docs/{ => api}/WizardWrx.Core.RegistryValues.html (100%)
 rename docs/{ => api}/WizardWrx.Core.TimeDisplayFormatter.DateFieldOrder.html (100%)
 rename docs/{ => api}/WizardWrx.Core.TimeDisplayFormatter.HoursFormatType.html (100%)
 rename docs/{ => api}/WizardWrx.Core.TimeDisplayFormatter.TimePrecisionType.html (100%)
 rename docs/{ => api}/WizardWrx.Core.TimeDisplayFormatter.html (100%)
 rename docs/{ => api}/WizardWrx.Core.TraceLogger.html (100%)
 rename docs/{ => api}/WizardWrx.Core.UnmanagedLibrary.html (99%)
 rename docs/{ => api}/WizardWrx.Core.html (100%)
 rename docs/{ => api}/WizardWrx.Cryptography.DigestFile.html (100%)
 rename docs/{ => api}/WizardWrx.Cryptography.DigestString.html (100%)
 rename docs/{ => api}/WizardWrx.Cryptography.html (100%)
 rename docs/{ => api}/WizardWrx.DLLConfigurationManager.ExceptionLogger.ErrorExitOptions.html (100%)
 rename docs/{ => api}/WizardWrx.DLLConfigurationManager.ExceptionLogger.OutputOptions.html (100%)
 rename docs/{ => api}/WizardWrx.DLLConfigurationManager.ExceptionLogger.ScrollUpResult.html (100%)
 rename docs/{ => api}/WizardWrx.DLLConfigurationManager.ExceptionLogger.html (100%)
 rename docs/{ => api}/WizardWrx.DLLConfigurationManager.IniFileReader.html (100%)
 rename docs/{ => api}/WizardWrx.DLLConfigurationManager.Properties.Resources.html (100%)
 rename docs/{ => api}/WizardWrx.DLLConfigurationManager.Properties.html (100%)
 rename docs/{ => api}/WizardWrx.DLLConfigurationManager.StateManager.AssemblyVersionRequest.html (100%)
 rename docs/{ => api}/WizardWrx.DLLConfigurationManager.StateManager.html (100%)
 rename docs/{ => api}/WizardWrx.DLLConfigurationManager.html (100%)
 rename docs/{ => api}/WizardWrx.DisplayFormats.html (100%)
 rename docs/{ => api}/WizardWrx.EmbeddedTextFile.ByteOrderMark.BOMType.html (100%)
 rename docs/{ => api}/WizardWrx.EmbeddedTextFile.ByteOrderMark.html (100%)
 rename docs/{ => api}/WizardWrx.EmbeddedTextFile.Readers.html (100%)
 rename docs/{ => api}/WizardWrx.EmbeddedTextFile.html (100%)
 rename docs/{ => api}/WizardWrx.FileIOFlags.html (100%)
 rename docs/{ => api}/WizardWrx.FileInfoExtension.enmInitialStatus.html (100%)
 rename docs/{ => api}/WizardWrx.FileInfoExtension.html (100%)
 rename docs/{ => api}/WizardWrx.FileInfoExtensionMethods.html (100%)
 rename docs/{ => api}/WizardWrx.FileNameTricks.TerminaBackslash.html (100%)
 rename docs/{ => api}/WizardWrx.FileNameTricks.html (100%)
 rename docs/{ => api}/WizardWrx.FormatStringEngine.FormatItem.Alignment.html (100%)
 rename docs/{ => api}/WizardWrx.FormatStringEngine.FormatItem.html (100%)
 rename docs/{ => api}/WizardWrx.FormatStringEngine.FormatItemsCollection.html (100%)
 rename docs/{ => api}/WizardWrx.FormatStringEngine.FormatStringError.html (100%)
 rename docs/{ => api}/WizardWrx.FormatStringEngine.FormatStringParser.html (100%)
 rename docs/{ => api}/WizardWrx.FormatStringEngine.html (100%)
 rename docs/{ => api}/WizardWrx.GenericSingletonBase-1.html (99%)
 rename docs/{ => api}/WizardWrx.ListHelpers.CompareResult.html (100%)
 rename docs/{ => api}/WizardWrx.ListHelpers.html (99%)
 rename docs/{ => api}/WizardWrx.ListInfo.html (100%)
 rename docs/{ => api}/WizardWrx.Logic.html (100%)
 rename docs/{ => api}/WizardWrx.MagicBooleans.html (100%)
 rename docs/{ => api}/WizardWrx.MagicNumbers.html (100%)
 rename docs/{ => api}/WizardWrx.NumberFormatters.html (100%)
 rename docs/{ => api}/WizardWrx.NumericFormats.HexFormatDecoration.html (100%)
 rename docs/{ => api}/WizardWrx.NumericFormats.html (100%)
 rename docs/{ => api}/WizardWrx.PathPositions.html (100%)
 rename docs/{ => api}/WizardWrx.RegExpSupport.html (100%)
 rename docs/{ => api}/WizardWrx.ReportDetail.ItemDisplayOrder.html (100%)
 rename docs/{ => api}/WizardWrx.ReportDetail.LabelChangedEventArgs.html (100%)
 rename docs/{ => api}/WizardWrx.ReportDetail.State.html (100%)
 rename docs/{ => api}/WizardWrx.ReportDetail.html (100%)
 rename docs/{ => api}/WizardWrx.ReportDetails.html (100%)
 rename docs/{ => api}/WizardWrx.ReportHelpers.Alignment.html (100%)
 rename docs/{ => api}/WizardWrx.ReportHelpers.html (100%)
 rename docs/{ => api}/WizardWrx.SpecialCharacters.html (100%)
 rename docs/{ => api}/WizardWrx.SpecialStrings.html (100%)
 rename docs/{ => api}/WizardWrx.StringExtensions.html (100%)
 rename docs/{ => api}/WizardWrx.StringTricks.html (100%)
 rename docs/{ => api}/WizardWrx.SyncRoot.html (100%)
 rename docs/{ => api}/WizardWrx.SysDateFormatters.html (100%)
 rename docs/{ => api}/WizardWrx.TextBlocks.html (100%)
 rename docs/{ => api}/WizardWrx.html (100%)
 create mode 100644 docs/api/index.html
 copy docs/{ => api}/toc.html (100%)
 create mode 100644 docs/articles/intro.html
 create mode 100644 docs/articles/toc.html
 create mode 100644 docs/favicon.ico
 create mode 100644 docs/fonts/glyphicons-halflings-regular.eot
 create mode 100644 docs/fonts/glyphicons-halflings-regular.svg
 create mode 100644 docs/fonts/glyphicons-halflings-regular.ttf
 create mode 100644 docs/fonts/glyphicons-halflings-regular.woff
 create mode 100644 docs/fonts/glyphicons-halflings-regular.woff2
 rewrite docs/index.html (91%)
 create mode 100644 docs/logo.svg
 create mode 100644 docs/manifest.json
 create mode 100644 docs/search-stopwords.json
 create mode 100644 docs/styles/docfx.css
 create mode 100644 docs/styles/docfx.js
 create mode 100644 docs/styles/docfx.vendor.css
 create mode 100644 docs/styles/docfx.vendor.js
 create mode 100644 docs/styles/lunr.js
 create mode 100644 docs/styles/lunr.min.js
 create mode 100644 docs/styles/main.css
 create mode 100644 docs/styles/main.js
 create mode 100644 docs/styles/search-worker.js
 rewrite docs/toc.html (97%)
 create mode 100644 docs/xrefmap.yml

DAG@ABACUS MINGW64 /f/Source_Code/Visual_Studio/Projects/WizardWrx_Libs/Wizardwrx (master)

To avoid a confusing outcome and the risk of losing changes along the way, do the next command before you issue another commit.

Step 3 of 3: Push the changes from the local Git repository to GitHub. When this step is completed, your API is published, and almost ready for viewing by your users. You do this by entering the following Git command: git push -u origin master, which elicits a response similar to the following.

$ git push -u origin master
Enumerating objects: 124, done.
Counting objects: 100% (124/124), done.
Delta compression using up to 8 threads.
Compressing objects: 100% (102/102), done.
Writing objects: 100% (105/105), 1.80 MiB | 1.02 MiB/s, done.
Total 105 (delta 42), reused 0 (delta 0)
remote: Resolving deltas: 100% (42/42), completed with 15 local objects.
   70e72c6..afeb4b9  master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.

DAG@ABACUS MINGW64 /f/Source_Code/Visual_Studio/Projects/WizardWrx_Libs/Wizardwrx (master)

Though the text of this command never changes,, the response varies depending on the location of the remote repository and the number of items in the commit. One-time commands not shown here established the connection between this local Git repository and this remote GitHub repository. Those commandd were issued as part of the commit that initially populated the repository.

Making the Documentation Visible

The final step required to make everything visible is performed on the GitHub side. Although there may be a Git command to do so, I didn't look into that, since I had the repository displayed in a Web browser window, and it was easy enough to visit the Settings page and make the change. Use Figure 1 to help you find it, at the far right edge of the list of links just below the repository name.

Figure 1: GitHub Repository Settings

Figure 1: GitHub repository settings are available on the last link at the right end of the row of links just below the blue line that displays the repository name.

When you display the settings page, you must scroll down, almost to the bottom, to find the group that controls your GitHub Pages, which initially appear as shown in Figure 2.

Figure 2: Default GitHub Pages settings

Figure 2: By default GitHub Pages are disabled.

For fairly obvious reasons, GitHub Pages are disabled by default for all repositories. Figure 3 shows the single change that I made. I shall explain next why I left themes disabled (off).

Figure 3: GitHub Pages enabled, without themes

Figure 3: I changed GitHub Pages to enabled, but without themes.

The reason that I left themes disabled is that it became immediately apparent that the CSS selectors and classes used by DocFX differ from those employed by the Jekyll themes. Without them, the resulting display was marginally acceptable, as shown in Figure 4.

Figure 4: The default navigation bar has way too little contrast to suit my taste.

Figure 4: The default navigation bar has way too little contrast to suit my taste.

I cannot continue without calling to your attention that this screen was captured from a work in progress. By the time I had the theme issue resolved, I had already populated this page with content that I moved into it from other pages. Hence, the next screen, Figure 5, contains a great deal more text, and the placeholders are gone.

Figure 5: The improved navbar has much better text-to-background color contrast.

Figure 5: The improved navbar has much better text-to-background color contrast.

Taking Control of the Theme

Wrestling control of the theme from the grip of the Bootstrap CSS that underpins the DocFX template turned out to be much easier than I anticipated, thanks to a light bulb that went off in my head and led to a brief expedition into the order in which competing (like named) styles and selectors cascade.

This is the point where access to the default template became essential to solving the puzzle, because it laid bare the source of the three Cascading Style Sheet files that appear in /docfx_project/_site/styles, but are nowhere to be found elsewhere in the scaffolding generated by the docfx init command. 

There are three such files, as follows.

  1. docfx.css is a small CSS file that I gave little attention once I saw what the next one was.
  2. docfx.vendor.css is nothing more, nor less, than good old Bootstrap v3.3.7, courtesy of our friends at Twitter, Inc.
  3. main.css is initially empty, and is intended as a stub for your use in overriding some of the Bootstrap styles.

Creating main.css as an empty file enables the generated document HTML to include a reference to it that resolves at run time, but does nothing. More important, though, is that the generator sets them in the correct order, so that whatever you enter in main.css is guaranteed to override anything above it, including docfx.css and, more important, the far more comprenehsive and pervasive settings defined in docfx.vendor.css,

I got everything I really wanted by overriding only a handful of selectors, of which the most critical, the .navbar-inverse classes, are defined in docfx.vendor.css.

article h1 {
 font-size        : 24pt;
 font-weight      : 300;
 margin-top       : 1em;
 margin-bottom    : 0.5em;
 color            : #9932CC;
 background-color : #ffffff;
article h2 {
 font-size        : 16pt;
 font-weight      : 300;
 margin-top       : 1em;
 margin-bottom    : 0.5em;
 color            : #000080;
 background-color : #ffffff;

code {
 color            : #4B0082;
 background-color : #ffffff;
 border-radius    : 4px

.navbar-inverse .navbar-brand{color:#9932CC}
.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#FFD700;background-color:transparent}
.navbar-inverse .navbar-nav>li>a,.navbar-inverse .navbar-text{color:#fff}
.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#FFD700;background-color:transparent}
.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}
.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}
.navbar-inverse .navbar-toggle{border-color:#333}
.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}
.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}
.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}
.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}

The last tricky bit was figuring out how to make my custom main.css visible to DocFX. My initial reading of the documentation led me to believe that the custom template directory belonged in C:\ProgramData\chocolatey\lib\docfx\templates, but this turned out to be untrue. The correct location for the template is \docfx_project\templates, where it belongs in a directory whose name is that of the template it represents,  which, in my case, is C#_Libraries. Since the structure of C#_Libraries must mirror that of the default template, the CSS file goes into styles, so its path, relative to the DocFX project directory, is \docfx_project\templates\C#_Libraries\styles.

The final piece of the puzzle is a small manual edit to docfx.json, which lives in the root directory, \docfx_project.

"template": [
      "default" ,

Key template is the name of an existing array that initially contains one item, default.  Adding templates/C#_Libraries causes the generator to import our custom main.css, which replaces the empty file of the same name that came from the default template. Note the use of the Unix style path delimiter and the absence of leading and trailing delimiters. Directories specified in docfx.json are assumed to be relative to \docfx_project, and the last name is implicitly that of a directory, since this entry is, by definition, the name of one.

Road Map

  1. The most immediate next step is reproduction of this work in most or all of my other public repositories.
  2. Next on the list is integrating document generation into the project build. This may involve implementing the NoGet package mentioned above, although I might elect to go the route of a post build step, implemented on its own, that uses the CLI tool that I already have.
  3. Finally, I may attempt further refinement of the template to create my own theme. Heading that list is getting rid of that ugly big D in the navbar. I don't mind that every page advertises that it was generated by DocFX; indeed, I am happy for that, but that D looks out of place in the navbar.

Points of Interest

Apart from achieving a long-held goal, I cannot honestly point to any real "points of interest." The only real point of interest that comes to mind is that this application is the first for which I have a legitimate use for NTFS junctions. Up to now, junctions have been more annoyance than help, because they clutter directory listings with duplicate entries that are nearly impossible to safely eradicate, and there is no way of which I am aware to tell the internal dir command to skip them.


Sunday, 16 September 2018 is the first release for publication.

Monday, 17 September 2018 is the obligatory update to fix the image URLs.


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

David A. Gray
Software Developer (Senior)
United States United States
I deliver robust, clean, adaptable, future-ready applications that are properly documented for users and maintainers. I have deep knowledge in multiple technologies and broad familiarity with computer and software technologies of yesterday, today, and tomorrow.

While it isn't perceived as sexy, my focus has always been the back end of the application stack, where data arrives from a multitude of sources, and is converted into reports that express my interpretation of The Fundamental Principle of Tabular Reporting, and are the most visible aspect of the system to senior executives who approve the projects and sign the checks.

While I can design a front end, I prefer to work at the back end, getting data into the system from outside sources, such as other computers, electronic sensors, and so forth, and getting it out of the system, as reports to IDENTIFY and SOLVE problems.

When presented with a problem, I focus on identifying and solving the root problem for the long term.

Specialties: Design: Relational data base design, focusing on reporting; organization and presentation of large document collections such as MSDS libraries

Development: Powerful, imaginative utility programs and scripts for automated systems management and maintenance

Industries: Property management, Employee Health and Safety, Services

Languages: C#, C++, C, Python, VBA, Visual Basic, Perl, WinBatch, SQL, XML, HTML, Javascript

Outside Interests: Great music (mostly, but by no means limited to, classical), viewing and photographing sunsets and clouds, traveling by car on small country roads, attending museum exhibits (fine art, history, science, technology), long walks, especially where there is little or no motor traffic, reading, especially nonfiction and thoughtfully written, thought provoking science fiction

Comments and Discussions

PraiseOutstanding article Pin
Karl Shifflett1-Sep-19 10:54
MemberKarl Shifflett1-Sep-19 10:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Posted 16 Sep 2018

Tagged as


3 bookmarked