Skip to content

Commit

Permalink
Merge branch 'docs/upgrade-website-generator'
Browse files Browse the repository at this point in the history
  • Loading branch information
Nick-Lucas committed Jan 8, 2017
2 parents cccfb83 + 990d584 commit 09671eb
Show file tree
Hide file tree
Showing 160 changed files with 17,094 additions and 1,346 deletions.
4 changes: 2 additions & 2 deletions EntryPoint.sln
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Example", "test\Example\Exa
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "EntryPointTests", "test\EntryPointTests\EntryPointTests.xproj", "{16E4BDFF-B4BA-4D70-8AFF-BBD1341F02F4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "website", "website", "{F76E7265-8998-41F0-8A27-DAEF53C9B40B}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Website", "docs-generation/website", "{F76E7265-8998-41F0-8A27-DAEF53C9B40B}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Website", "website\Website.xproj", "{EE85C0CE-3A7E-4B3A-9157-8E9BDD037BFD}"
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Website", "docs-generation/website\Website.xproj", "{EE85C0CE-3A7E-4B3A-9157-8E9BDD037BFD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
[![NuGet](https://img.shields.io/nuget/v/EntryPoint.svg)](https://www.nuget.org/packages/EntryPoint)
[![MIT License](https://img.shields.io/github/license/Nick-Lucas/EntryPoint.svg)](https://github.com/Nick-Lucas/EntryPoint/blob/master/LICENSE)

**Warning: (Version 0.9.6)** EntryPoint is approaching v1.0 but changes to the API may yet come. See the roadmap below for more information
**Warning: (Version 0.9.8)** EntryPoint is approaching v1.0 but changes to the API may yet come. See the roadmap below for more information

## EntryPoint

Lightweight and Composable CLI Argument Parser for all modern .Net platforms
Composable CLI Argument Parser for all modern .Net platforms

Parses arguments in the form `UtilityName [command] [-o | --options] [operands]`

Expand Down
14 changes: 14 additions & 0 deletions docs-generation/BUILD.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
echo "Building documentation"
cd ./website
dotnet restore
dotnet build
dotnet run

echo "Copying files into docfx inputs"
cd ../
cp -f ../README.md ./docfx/index.md
cp -f ./website/www/*.md ./docfx/articles/

echo "Building DoxFX. Will Serve"
cd ./docfx
docfx --serve
13 changes: 13 additions & 0 deletions docs-generation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
### Dependencies

* dotnet cli - https://www.microsoft.com/net/core
* doxfx cli - https://dotnet.github.io/docfx/

### Brief

There are two phases to the documentation build.

1. Generate the custom documentation: see `./Website` which involved building and running the .Net project there. It outputs docs into the necessary docfx folders
2. Generate the docfx site: `./docfx_project` -> `docfx build`

The first stage is added because building the markdown from a real codebase allows for compile time checks on the example code, which makes writing accurate documentation easier.
61 changes: 24 additions & 37 deletions website/Program.cs → docs-generation/Website/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,28 @@ class Program {
static void Main(string[] args) {
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("en");

var layout = File.ReadAllText("./layout.html");
layout = layout.Replace("{{description}}", "Composable CLI Argument Parser");
layout = layout.Replace("{{version}}", GetVersion());
layout = layout.Replace("{{updated_on}}", DateTime.Now.ToString("MMM d, yyyy"));

var headerIdList = new List<string>();
var body = new Markdown().Transform(PreprocessBody());
body = AddHeaderAnchors(body, headerIdList);

layout = layout.Replace("{{body}}", body);
ValidateHeaderAnchors(headerIdList, layout);
File.WriteAllText("./www/index.html", layout);

// Useful if we ever need to copy to the /docs directory again
//var directory = new DirectoryInfo("../www/");
//foreach (var file in directory.GetFiles()) {
// File.Copy(file.FullName, Path.Combine("../../", file.Name), true);
//}
var files = GetFiles();
foreach (var file in files) {
var fileName = GetFileName(file);
ProduceFile(file, fileName);
}
}

static List<string> GetFiles() {
var files = Directory.EnumerateFiles(
"./", "article_*", SearchOption.TopDirectoryOnly);
return files.ToList();
}

static string GetFileName(string file) {
return Path
.GetFileNameWithoutExtension(file)
.Replace("article_", "");
}

static void ProduceFile(string fileName, string outputName) {
var markdownBody = TransformBodyToMarkdown(fileName);
File.WriteAllText($"./www/{outputName}.md", markdownBody);
}

static string GetVersion() {
Expand All @@ -48,8 +52,8 @@ static string GetVersion() {
.TrimEnd(".0".ToCharArray());
}

static string PreprocessBody() {
var source = File.ReadAllLines("./Body.cs");
static string TransformBodyToMarkdown(string fileName) {
var source = File.ReadAllLines(fileName);
var result = new List<string>();
List<string> currentCode = null;

Expand Down Expand Up @@ -95,23 +99,6 @@ static string PreprocessBody() {
return String.Join("\n", result);
}

static string AddHeaderAnchors(string html, List<string> idList) {
return Regex.Replace(html, @"(\<h2)([^>]*\>(.+?)\</h2\>)", m => {
var id = Regex.Replace(m.Groups[3].Value.ToLower(), "\\W+", "-").Trim('-');
idList.Add(id);
return m.Groups[1].Value + " id=\"" + id + "\"" + m.Groups[2].Value;
});
}

static void ValidateHeaderAnchors(List<string> knownIdList, string html) {
var refs = Regex.Matches(html, "href=\"#(.+?)\"").Cast<Match>().Select(m => m.Groups[1].Value);
var wrong = refs.Except(knownIdList);
if(wrong.Any())
// If this throws - Please ensure you've updated any links to page sections after changing header titles.
// "Getting Started: CRUD" would become "#getting-started-crud" as a link
throw new Exception("Wrong header refs found. " + String.Join(" ", wrong));
}

}

}
File renamed without changes.
File renamed without changes.
File renamed without changes.
17 changes: 17 additions & 0 deletions docs-generation/Website/article_api_overview.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#define CODE

using System;
using System.Linq;

using EntryPoint;
using System.Collections.Generic;

namespace Website {
/// ## API Overview
/// There are just a few classes you'll interact with:
///
/// * `Cli` - The main API, which handles all processing
/// * `BaseCliArguments` - An abstract class which you implement to define Options & Operands, known as CliArguments classes
/// * `BaseCliCommands` - An abstract class which you implement to define Commands, known as CliCommands classes
/// * `Attributes` - There are a handful of attributes you can use to define your CliCommands and CliArguments implementations
}
185 changes: 185 additions & 0 deletions docs-generation/Website/article_arguments.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
#define CODE

using System;
using System.Linq;

using EntryPoint;
using System.Collections.Generic;

namespace Website {
class article_arguments {
/// ## Arguments
///
/// For a simple application you may not need Commands; `CliArguments` classes are used
/// to parse command line arguments without consideration of Commands.
///
/// #### Example
///
/// Let's say we want a utility used like: `UtilityName [-s] [--name Bob] [6.1]`
///
/// This has one Option `-s`, one OptionParameter `--name Bob` and a positional Operand `6.1`
#if CODE
class SimpleProgram {
void main(string[] args) {

// One line parsing of any `BaseCliArguments` implementation
var arguments = Cli.Parse<SimpleCliArguments>(args);

// Object oriented access to your arguments
Console.WriteLine("The name is: " + arguments.Name);
Console.WriteLine("Switch flag: " + arguments.Switch);
Console.WriteLine("Positional Operand 1: " + arguments.FirstOperand);
}
}

class SimpleCliArguments : BaseCliArguments {
public SimpleCliArguments() : base("SimpleApp") { }

// Defining a CliArguments class is as easy as adding
// attributes to your POCO properties
[Option(LongName: "switch",
ShortName: 's')]
public bool Switch { get; set; }

// Both Option and OptionParameter attributes support a
// combination of -o and --option style invocations
[OptionParameter(LongName: "name",
ShortName: 'n')]
public string Name { get; set; }

// Operands can be mapped positionally
// But BaseCliArguments also has a .Operands string[] where
// un-mapped operands are stored
[Operand(Position: 1)]
public decimal FirstOperand { get; set; }
}
#endif

/// #### Attributes
///
/// We use Attributes to define CLI functionality
///
/// ##### `[Option(LongName = string, ShortName = char)]`
/// * **Apply to:** Class Properties
/// * **Output Types:** Bool
/// * **Detail:** Defines an On/Off option for use on the CLI
/// * **Argument, LongName:** the case in-sensitive name to be used like `--name`
/// * **Argument, ShortName:** the case sensitive character to be used like `-n`
/// * At least one name needs to be provided
///
/// ##### `[OptionParameter(LongName = string, ShortName = char)]`
/// * **Apply to:** Class Properties
/// * **Output Types:** Primitive Types, Enums
/// * **Detail:** Defines a parameter which can be invoked to provide a value
/// * **Argument, LongName:** the case in-sensitive name to be used like `--name`
/// * **Argument, ShortName:** the case sensitive character to be used like `-n`
/// * At least one name needs to be provided
///
/// ##### `[Operand(position = int)]`
/// * **Apply to:** Class Properties
/// * **Output Types:** Primitive Types, Enums
/// * **Detail:** Maps a positional operand from the end of a CLI command
/// * **Argument, Position:** the 1 based position of the Operand
///
/// ##### `[Required]`
/// * **Apply to:** Option, OptionParameter or Operand properties
/// * **Detail:** Makes an Option or Operand mandatory for the user to provide
///
/// ##### `[Help(detail = string)]`
/// * **Apply to:** Class Properties with any Option or Operand Attribute applied, or an CliArguments Class
/// * **Detail:** Provides custom documentation on an Option, Operand or CliArguments Class, which will be consumed by the help generator
///


/// #### Use case
///
/// The following is an example implementation for use in a simple message sending application
///
/// The following is used like:
///
/// `UtilityName [ -v | --verbose ] [ -s | --subject "your subject" ] [ -i | --importance ] [ normal | high ] ] [message]`
#if CODE
// Usage is as simple as
class MessagingProgram {
void main(string[] args) {
var arguments = Cli.Parse<MessagingCliArguments>(args);

// Use the arguments object...
}
}

class MessagingCliArguments : BaseCliArguments {
public MessagingCliArguments() : base("Message Sender") { }

// Verbose will be a familiar option to most CLI users
[Option(LongName: "verbose",
ShortName: 'v')]
public bool Verbose { get; set; }

// A subject *must* be provided by the user
[Required]
[OptionParameter(LongName: "subject",
ShortName: 's')]
public string Subject { get; set; }

// An enum importance level for the message.
// If not provided this is defaulted to `Normal`
// User can provide the value as a number or string (ie. '2' or 'high')
[OptionParameter(LongName: "importance",
ShortName: 'i')]
public MessageImportanceEnum Importance { get; set; } = MessageImportanceEnum.Normal;

// A list of strings
// Lists support all the same types as any other option parameter
// The Cli expects list values in the form `item1,item2,item3` etc
[OptionParameter(LongName: "recipients")]
public List<string> Recipients { get; set; }

// A message *must* be provided as the first operand
[Required]
[Operand(1)]
public string Message { get; set; }
}

enum MessageImportanceEnum {
Normal = 1,
High = 2
}
#endif

/// #### Value Defaults
///
/// If the user does not provide an non-required option-parameter or operand,
/// it can be useful to configure the application with a default.
///
/// This is easily done using C# property initialisers,
/// and will otherwise use the type's default value
///
class DefaultsExample {
#if CODE
// The following Importance Enum will always be set to 'Normal'
// if the user does not provide a value
[OptionParameter(LongName: "importance",
ShortName: 'i')]
public MessageImportanceEnum Importance { get; set; } = MessageImportanceEnum.Normal;
#endif
}

///
/// #### Supported Types
///
/// Both OptionParameter and Operand arguments can be mapped to a number of different .Net types
///
/// * **Primitive & 'Primitive like' Types**
/// * These are your String, Int, Long, Double, Float, Decimal, Bool, etc...
/// * Should support any simple type which implements `IConvertible`, although this can't be exhaustively tested
///
/// * **Enums**
/// * Parses custom enums from both the numeric value or the string/name for a value
///
/// * **Lists**
/// * Supports the generic collection: `List<T>`
/// * Parses lists from the form `item1,item2,item3`
/// * `T` can be any type supported *above*
}
}
Loading

0 comments on commit 09671eb

Please sign in to comment.