Skip to content

Commit

Permalink
check: Verify sidebar navigation for missing links and mismatched lin…
Browse files Browse the repository at this point in the history
…k text (if legacy directory structure) (#29)

Reference: #27
Reference: #28
  • Loading branch information
bflad authored Feb 10, 2020
1 parent 2caa107 commit 3d7d28d
Show file tree
Hide file tree
Showing 14 changed files with 606 additions and 5 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# v0.5.0

ENHANCEMENTS

* check: Verify sidebar navigation for missing links and mismatched link text (if legacy directory structure)

# v0.4.1

BUG FIXES
Expand Down
23 changes: 19 additions & 4 deletions check/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ type CheckOptions struct {

SchemaDataSources map[string]*tfjson.Schema
SchemaResources map[string]*tfjson.Schema

SideNavigation *SideNavigationOptions
}

func NewCheck(opts *CheckOptions) *Check {
Expand Down Expand Up @@ -117,8 +119,11 @@ func (check *Check) Run(directories map[string][]string) error {
}
}

if files, ok := directories[LegacyDataSourcesDirectory]; ok {
if err := NewLegacyDataSourceFileCheck(check.Options.LegacyDataSourceFile).RunAll(files); err != nil {
legacyDataSourcesFiles, legacyDataSourcesOk := directories[LegacyDataSourcesDirectory]
legacyResourcesFiles, legacyResourcesOk := directories[LegacyResourcesDirectory]

if legacyDataSourcesOk {
if err := NewLegacyDataSourceFileCheck(check.Options.LegacyDataSourceFile).RunAll(legacyDataSourcesFiles); err != nil {
result = multierror.Append(result, err)
}
}
Expand All @@ -135,8 +140,18 @@ func (check *Check) Run(directories map[string][]string) error {
}
}

if files, ok := directories[LegacyResourcesDirectory]; ok {
if err := NewLegacyResourceFileCheck(check.Options.LegacyResourceFile).RunAll(files); err != nil {
if legacyResourcesOk {
if err := NewLegacyResourceFileCheck(check.Options.LegacyResourceFile).RunAll(legacyResourcesFiles); err != nil {
result = multierror.Append(result, err)
}
}

if legacyDataSourcesOk || legacyResourcesOk {
if err := SideNavigationLinkCheck(check.Options.SideNavigation); err != nil {
result = multierror.Append(result, err)
}

if err := SideNavigationMismatchCheck(check.Options.SideNavigation, legacyDataSourcesFiles, legacyResourcesFiles); err != nil {
result = multierror.Append(result, err)
}
}
Expand Down
8 changes: 8 additions & 0 deletions check/check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,14 @@ func TestCheck(t *testing.T) {
testCase.Options.RegistryResourceFile.FileOptions = fileOpts
}

if testCase.Options.SideNavigation == nil {
testCase.Options.SideNavigation = &SideNavigationOptions{}
}

if testCase.Options.SideNavigation.FileOptions == nil {
testCase.Options.SideNavigation.FileOptions = fileOpts
}

directories, err := GetDirectories(testCase.BasePath)

if err != nil {
Expand Down
1 change: 1 addition & 0 deletions check/file_extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
)

const (
FileExtensionErb = `.erb`
FileExtensionHtmlMarkdown = `.html.markdown`
FileExtensionHtmlMd = `.html.md`
FileExtensionMarkdown = `.markdown`
Expand Down
72 changes: 72 additions & 0 deletions check/sidenavigation/category.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package sidenavigation

import (
"golang.org/x/net/html"
)

type Category struct {
Name string
Node *html.Node

DataSourceLinks []*Link
ExternalLinks []*Link
GuideLinks []*Link
ResourceLinks []*Link
}

func NewCategory(name string, listNode *html.Node) *Category {
category := &Category{
DataSourceLinks: make([]*Link, 0),
ExternalLinks: make([]*Link, 0),
GuideLinks: make([]*Link, 0),
Name: name,
Node: listNode,
ResourceLinks: make([]*Link, 0),
}

category.processList()

return category
}

func (category *Category) processList() {
for child := category.Node.FirstChild; child != nil; child = child.NextSibling {
if isListItem(child) {
category.processListItem(child)
}
}
}

func (category *Category) processListItem(node *html.Node) {
linkNode := childLinkNode(node)

if linkNode == nil {
return
}

link := NewLink(linkNode)
listNode := childUnorderedListNode(node)

if listNode == nil {
switch link.Type {
case LinkTypeDataSource:
category.DataSourceLinks = append(category.DataSourceLinks, link)
case LinkTypeExternal:
category.ExternalLinks = append(category.ExternalLinks, link)
case LinkTypeGuide:
category.GuideLinks = append(category.GuideLinks, link)
case LinkTypeResource:
category.ResourceLinks = append(category.ResourceLinks, link)
}

return
}

// Categories can contain one single subcategory (e.g. Service Name > Resources)
subCategory := NewCategory(link.Text, listNode)

category.DataSourceLinks = append(category.DataSourceLinks, subCategory.DataSourceLinks...)
category.ExternalLinks = append(category.ExternalLinks, subCategory.ExternalLinks...)
category.GuideLinks = append(category.GuideLinks, subCategory.GuideLinks...)
category.ResourceLinks = append(category.ResourceLinks, subCategory.ResourceLinks...)
}
61 changes: 61 additions & 0 deletions check/sidenavigation/html.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package sidenavigation

import (
"golang.org/x/net/html"
)

func childLinkNode(node *html.Node) *html.Node {
for child := node.FirstChild; child != nil; child = child.NextSibling {
if isLink(child) {
return child
}
}

return nil
}

func childText(node *html.Node) string {
if node.Type == html.TextNode {
return node.Data
}

for child := node.FirstChild; child != nil; child = child.NextSibling {
return childText(child)
}

return ""
}

func childUnorderedListNode(node *html.Node) *html.Node {
for child := node.FirstChild; child != nil; child = child.NextSibling {
if isUnorderedList(child) {
return child
}
}

return nil
}

func isLink(node *html.Node) bool {
if node.Type != html.ElementNode {
return false
}

return node.Data == "a"
}

func isListItem(node *html.Node) bool {
if node.Type != html.ElementNode {
return false
}

return node.Data == "li"
}

func isUnorderedList(node *html.Node) bool {
if node.Type != html.ElementNode {
return false
}

return node.Data == "ul"
}
106 changes: 106 additions & 0 deletions check/sidenavigation/link.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package sidenavigation

import (
"fmt"
"strings"

"golang.org/x/net/html"
)

type Link struct {
Text string
Type LinkType
Url string
}

type LinkType int

const (
LinkTypeAnchor LinkType = iota
LinkTypeExternal
LinkTypeDataSource
LinkTypeGuide
LinkTypeResource
)

func (typ LinkType) String() string {
return [...]string{"Anchor", "External", "Data Source", "Guide", "Resource"}[typ]
}

func NewLink(node *html.Node) *Link {
link := &Link{
Text: childText(node),
}

for _, attr := range node.Attr {
if attr.Key == "href" {
link.Url = attr.Val

if strings.HasPrefix(link.Url, "#") {
link.Type = LinkTypeAnchor
} else if strings.Contains(link.Url, "/d/") {
link.Type = LinkTypeDataSource
} else if strings.Contains(link.Url, "/guides/") {
link.Type = LinkTypeGuide
} else if strings.Contains(link.Url, "/r/") {
link.Type = LinkTypeResource
} else {
link.Type = LinkTypeExternal
}

break
}
}

return link
}

func (link *Link) Validate(expectedProviderName string) error {
if link.Type != LinkTypeDataSource && link.Type != LinkTypeResource {
return nil
}

var linkTypeUrlPart string

switch link.Type {
case LinkTypeDataSource:
linkTypeUrlPart = "d"
case LinkTypeResource:
linkTypeUrlPart = "r"
}

if !strings.Contains(link.Url, "/") {
return fmt.Errorf("link URL (%s) is missing / separators, should be in form: /docs/providers/PROVIDER/%s/NAME.html", link.Url, linkTypeUrlPart)
}

urlParts := strings.Split(link.Url, "/")

if len(urlParts) < 6 {
return fmt.Errorf("link URL (%s) is missing path parts, e.g. /docs/providers/PROVIDER/%s/NAME.html", link.Url, linkTypeUrlPart)
} else if len(urlParts) > 6 {
return fmt.Errorf("link URL (%s) has too many path parts, should be in form: /docs/providers/PROVIDER/%s/NAME.html", link.Url, linkTypeUrlPart)
}

urlProviderName := urlParts[3]

if expectedProviderName != "" && urlProviderName != expectedProviderName {
return fmt.Errorf("link URL (%s) has incorrect provider name (%s), expected: %s", link.Url, urlProviderName, expectedProviderName)
}

urlResourceName := urlParts[len(urlParts)-1]
urlResourceName = strings.TrimSuffix(urlResourceName, ".html")

if expectedProviderName != "" {
expectedText := fmt.Sprintf("%s_%s", expectedProviderName, urlResourceName)
if link.Text != expectedText {
return fmt.Errorf("link URL (%s) has incorrect text (%s), expected: %s", link.Url, link.Text, expectedText)
}
} else {
expectedSuffix := fmt.Sprintf("_%s", urlResourceName)
if !strings.HasSuffix(link.Text, expectedSuffix) {
return fmt.Errorf("link URL (%s) has incorrect text (%s), expected: PROVIDER%s", link.Url, link.Text, expectedSuffix)
}
}

return nil
}
Loading

0 comments on commit 3d7d28d

Please sign in to comment.