Skip to content

Commit e7c01ae

Browse files
Merge pull request #274 from SierraSoftworks/feature/frontmatter-markdown
feat: Add support for rendering Markdown files with frontmatter (closes #273)
2 parents 7dd198f + 135f9de commit e7c01ae

14 files changed

Lines changed: 290 additions & 18 deletions

File tree

.github/workflows/dotnet.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
- name: Setup .NET Core
1414
uses: actions/setup-dotnet@v3
1515
with:
16-
dotnet-version: '5.0.x'
16+
dotnet-version: '7.0.x'
1717
- name: Package DocFX Plugin
1818
run: |
1919
dotnet pack ./tools/docfx

docs/tools/documentation/markdown/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,42 @@ roadmap render md --in roadmap.yml --out roadmap.md
3636
:::
3737
::::
3838

39+
### Frontmatter Support
40+
If you're rendering your roadmap to a target which supports Markdown frontmatter, you can use the
41+
`frontmatter-markdown` format (aka `fmd`) to generate a Markdown file which includes frontmatter fields
42+
for the title, description and any other custom fields you might wish to provide. This is useful for
43+
integrations with tools like [Hugo](https://gohugo.io) or [Jekyll](https://jekyllrb.com).
44+
45+
:::: code-group
46+
47+
::: code-group-item stdout
48+
```sh
49+
roadmap render fmd --in roadmap.yml --frontmatter 'weight: 10, tags: ["roadmap"]'
50+
```
51+
:::
52+
53+
::: code-group-item file
54+
```sh
55+
roadmap render fmd --in roadmap.yml --out roadmap.md --frontmatter 'weight: 10, tags: ["roadmap"]'
56+
```
57+
:::
58+
59+
::::
60+
61+
This will result in your Markdown file starting with the following frontmatter preamble (note
62+
that `title` and `description` are automatically populated from the roadmap's metadata if you
63+
don't specify them).
64+
65+
```yaml
66+
---
67+
title: "Your Roadmap Title"
68+
description: "Your Roadmap's Description"
69+
weight: 10
70+
tags: ["roadmap"]
71+
---
72+
```
73+
74+
3975
## Output
4076
The output generated by this tool is a Markdown file which can then be rendered by any compliant
4177
Markdown renderer with HTML support enabled. It doesn't include any JavaScript and uses

tools/docfx/Roadmap.DotFX.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>netstandard2.0;net472</TargetFrameworks>
4+
<TargetFrameworks>net7.0</TargetFrameworks>
55
<IsPackable>true</IsPackable>
66
<PackageId>DocFX.Plugins.Roadmap</PackageId>
77
<Authors>bpannell</Authors>
@@ -27,7 +27,7 @@
2727

2828
<ItemGroup>
2929
<PackageReference Include="Handlebars.Net" Version="2.1.2" />
30-
<PackageReference Include="Microsoft.Composition" Version="1.0.31" />
30+
<PackageReference Include="System.Composition" Version="7.0.0" />
3131
<PackageReference Include="Microsoft.DocAsCode.Common" Version="$(DocFxVersion)" />
3232
<PackageReference Include="Microsoft.DocAsCode.MarkdigEngine" Version="$(DocFxVersion)" />
3333
<PackageReference Include="Microsoft.DocAsCode.Plugins" Version="$(DocFxVersion)" />

tools/docfx/RoadmapRenderer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public ProcessingPriority GetProcessingPriority(FileAndType file)
3131
public FileModel Load(FileAndType file, ImmutableDictionary<string, object> metadata)
3232
{
3333
var deserializer = new DeserializerBuilder()
34-
.WithNamingConvention(new CamelCaseNamingConvention())
34+
.WithNamingConvention(CamelCaseNamingConvention.Instance)
3535
.Build();
3636

3737
var roadmap = deserializer.Deserialize<Models.Roadmap>(File.ReadAllText(EnvironmentContext.FileAbstractLayer.GetPhysicalPath(file.File)));

tools/roadmap/commands/app.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ var app = &cli.App{
2626
&graphvizRenderCommand,
2727
&htmlRenderCommand,
2828
&mdRenderCommand,
29+
&frontmatterMdRenderCommand,
2930
},
3031
},
3132
&validateCommand,
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package commands
2+
3+
import (
4+
_ "embed"
5+
"fmt"
6+
"os"
7+
"text/template"
8+
9+
"github.com/SierraSoftworks/roadmap"
10+
"github.com/urfave/cli/v2"
11+
yaml "gopkg.in/yaml.v3"
12+
)
13+
14+
//go:embed templates/roadmap.basic.frontmatter.md
15+
var frontmatterMdRoadmapBasicTemplate string
16+
17+
//go:embed templates/roadmap.advanced.frontmatter.md
18+
var frontmatterMdRoadmapAdvancedTemplate string
19+
20+
var frontmatterMdRenderCommand = cli.Command{
21+
Name: "frontmatter-markdown",
22+
Aliases: []string{"fmd"},
23+
Usage: "Generate a Markdown file which can be rendered to visualize your roadmap specification, including a frontmatter preamble.",
24+
Flags: []cli.Flag{
25+
&cli.BoolFlag{
26+
Name: "simple",
27+
Usage: "Emits simplified Markdown for maximum compatibility.",
28+
},
29+
&cli.StringFlag{
30+
Name: "frontmatter",
31+
Aliases: []string{"m"},
32+
Usage: "Additional frontmatter fields to include in the frontmatter, should be provided in YAML format.",
33+
Value: "{}",
34+
},
35+
&cli.StringFlag{
36+
Name: "input",
37+
Aliases: []string{"in"},
38+
Usage: "The input roadmap.yml `FILE`.",
39+
Value: "roadmap.yml",
40+
TakesFile: true,
41+
Required: true,
42+
},
43+
&cli.StringFlag{
44+
Name: "output",
45+
Aliases: []string{"out"},
46+
Usage: "The output `FILE` (defaults to stdout).",
47+
TakesFile: true,
48+
},
49+
},
50+
Action: func(c *cli.Context) error {
51+
f, err := os.ReadFile(c.String("input"))
52+
if err != nil {
53+
return err
54+
}
55+
56+
r, err := roadmap.Parse(f)
57+
if err != nil {
58+
return err
59+
}
60+
61+
tmpl := frontmatterMdRoadmapAdvancedTemplate
62+
if c.Bool("simple") {
63+
tmpl = frontmatterMdRoadmapBasicTemplate
64+
}
65+
66+
dot, err := renderTextTemplate(r, tmpl, template.FuncMap{
67+
"stateColor": func(state string) string {
68+
switch state {
69+
case "TODO":
70+
return "#aaa"
71+
case "DOING":
72+
return "#63B2EB"
73+
case "DONE":
74+
return "#3EAF7C"
75+
case "SKIP":
76+
return "#F65BD2"
77+
default:
78+
return "#aaa"
79+
}
80+
},
81+
"requirementColor": func(requirement string) string {
82+
switch requirement {
83+
case "MUST":
84+
return "#E06446"
85+
case "SHOULD":
86+
return "#E0AF2F"
87+
case "MAY":
88+
return "#3ABDE0"
89+
default:
90+
return "#888"
91+
}
92+
},
93+
"metadata": func() map[string]interface{} {
94+
m := map[string]interface{}{}
95+
if err := yaml.Unmarshal([]byte(c.String("frontmatter")), &m); err != nil {
96+
return map[string]interface{}{
97+
"__parse_error": err.Error(),
98+
}
99+
}
100+
101+
if m["title"] == nil {
102+
m["title"] = r.Title
103+
}
104+
105+
if m["description"] == nil {
106+
m["description"] = r.Description
107+
}
108+
109+
return m
110+
},
111+
})
112+
if err != nil {
113+
return err
114+
}
115+
116+
if c.String("output") != "" {
117+
return os.WriteFile(c.String("output"), []byte(dot), os.ModePerm)
118+
} else {
119+
_, err := fmt.Println(dot)
120+
return err
121+
}
122+
},
123+
}

tools/roadmap/commands/render-graphviz.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package commands
33
import (
44
_ "embed"
55
"fmt"
6-
"io/ioutil"
76
"os"
87
"text/template"
98

@@ -35,7 +34,7 @@ var graphvizRenderCommand = cli.Command{
3534
},
3635
},
3736
Action: func(c *cli.Context) error {
38-
f, err := ioutil.ReadFile(c.String("input"))
37+
f, err := os.ReadFile(c.String("input"))
3938
if err != nil {
4039
return err
4140
}
@@ -66,7 +65,7 @@ var graphvizRenderCommand = cli.Command{
6665
}
6766

6867
if c.String("output") != "" {
69-
return ioutil.WriteFile(c.String("output"), []byte(dot), os.ModePerm)
68+
return os.WriteFile(c.String("output"), []byte(dot), os.ModePerm)
7069
} else {
7170
_, err := fmt.Println(dot)
7271
return err

tools/roadmap/commands/render-html.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
_ "embed"
55
"fmt"
66
"html/template"
7-
"io/ioutil"
87
"os"
98

109
"github.com/SierraSoftworks/roadmap"
@@ -41,7 +40,7 @@ var htmlRenderCommand = cli.Command{
4140
},
4241
},
4342
Action: func(c *cli.Context) error {
44-
f, err := ioutil.ReadFile(c.String("input"))
43+
f, err := os.ReadFile(c.String("input"))
4544
if err != nil {
4645
return err
4746
}
@@ -64,7 +63,7 @@ var htmlRenderCommand = cli.Command{
6463
}
6564

6665
if c.String("output") != "" {
67-
return ioutil.WriteFile(c.String("output"), []byte(dot), os.ModePerm)
66+
return os.WriteFile(c.String("output"), []byte(dot), os.ModePerm)
6867
} else {
6968
_, err := fmt.Println(dot)
7069
return err

tools/roadmap/commands/render-md.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package commands
33
import (
44
_ "embed"
55
"fmt"
6-
"io/ioutil"
76
"os"
87
"text/template"
98

@@ -42,7 +41,7 @@ var mdRenderCommand = cli.Command{
4241
},
4342
},
4443
Action: func(c *cli.Context) error {
45-
f, err := ioutil.ReadFile(c.String("input"))
44+
f, err := os.ReadFile(c.String("input"))
4645
if err != nil {
4746
return err
4847
}
@@ -90,7 +89,7 @@ var mdRenderCommand = cli.Command{
9089
}
9190

9291
if c.String("output") != "" {
93-
return ioutil.WriteFile(c.String("output"), []byte(dot), os.ModePerm)
92+
return os.WriteFile(c.String("output"), []byte(dot), os.ModePerm)
9493
} else {
9594
_, err := fmt.Println(dot)
9695
return err

tools/roadmap/commands/render.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515

1616
func getDefaultTextRenderFunctions() template.FuncMap {
1717
return template.FuncMap{
18-
"json": func(in string) string {
18+
"json": func(in interface{}) string {
1919
out, err := json.Marshal(in)
2020
if err != nil {
2121
log.Fatal(err)

0 commit comments

Comments
 (0)