tools/cmd/evaluate-docs/main.go

222 lines
5.2 KiB
Go

// Copyright 2021 Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"encoding/csv"
"flag"
"fmt"
"log"
"os"
"path/filepath"
"regexp"
"strings"
)
var maxScore = 15
type testStatus string
var (
testUnknown testStatus = "unknown"
testTableOfContents testStatus = "table-of-contents"
)
var excludeDirs = []string{
"en/about",
"zh/about",
"en/blog",
"zh/blog",
"en/boilerplates",
"zh/boilerplates",
"en/events",
"zh/events",
"en/docs/reference/glossary",
"zh/docs/reference/glossary",
"zh/news",
"en/news",
"en/test",
"zh/test",
}
type FileEntry struct {
fullPath string
relative string
url string
tested testStatus
score int
title string
owner string
notes []string
}
func main() {
var docsPath, outPath, analyticsPath string
flag.StringVar(&docsPath, "docspath", "../istio.io", "points to the cloned path of the istio.io site")
flag.StringVar(&outPath, "outpath", "out.csv", "path to create the spreadsheet CSV at")
flag.StringVar(&analyticsPath, "analyticspath", "analytics.csv", "path to a file containing the istio.io analytics CSV")
flag.Parse()
fmt.Printf("Scoring docs in %s\n", docsPath)
files, err := getAllFiles(docsPath)
if err != nil {
fmt.Printf("Error: %s", err)
}
scorers := []scorer{
getHitsScorer(analyticsPath, 15),
}
for _, scorerInstance := range scorers {
files = scorerInstance.Score(files)
}
if err := writeTestSpreadsheet(files, outPath); err != nil {
fmt.Printf("Failed to write test spreadsheet: %s\n", err.Error())
}
}
func getPriority(file FileEntry) string {
priority := 0
switch score := file.score; {
case score > maxScore*2/3:
priority = 0
case score > maxScore*1/3:
priority = 1
case score > 0:
priority = 2
case score == 0:
priority = 3
}
return fmt.Sprintf("P%d", priority)
}
func writeTestSpreadsheet(files []FileEntry, path string) error {
file, err := os.Create(path)
if err != nil {
return fmt.Errorf("unable to create CSV: %s", err.Error())
}
defer file.Close()
writer := csv.NewWriter(file)
defer writer.Flush()
if err := writer.Write([]string{
"",
"Owner",
"Test Cases",
"Priority",
"Automated",
"In Progress",
"In Progress Last Updated",
"Done By",
"Done By Last Updated",
"GitHub Issue",
"Comments (e.g. env used)",
"Automated Sign Up",
"Automated Last Updated",
"Automation GitHub Issue",
"Generator Notes",
}); err != nil {
return fmt.Errorf("failed to write to CSV: %s", err.Error())
}
for _, value := range files {
err := writer.Write([]string{
"",
value.owner,
fmt.Sprintf("=HYPERLINK(\"%s\",\"%s\")", value.url, strings.ReplaceAll(value.title, "\"", "")),
getPriority(value),
string(value.tested),
"",
"",
"",
"",
"",
"",
"",
"",
"",
strings.Join(value.notes, "\n"),
})
if err != nil {
log.Fatalf("unable to write CSV: %s", err.Error())
}
}
return nil
}
func getURL(path string) string {
path = strings.TrimPrefix(path, "en/")
return fmt.Sprintf("https://preliminary.istio.io/latest/%s", filepath.Dir(path))
}
func getAllFiles(path string) ([]FileEntry, error) {
files := make([]FileEntry, 0)
rootPath := path
err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
relativePath := strings.TrimPrefix(path[len(rootPath):], "/")
if filepath.Ext(path) == ".md" && !skipFile(relativePath) {
fileContent, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("unable to open file %s", path)
}
docTestStatus := getTestStatus(string(fileContent))
// skip table of contents as it has no commands and links and content are autogenerated from docs
if docTestStatus == testTableOfContents {
return nil
}
files = append(files, FileEntry{
fullPath: path,
relative: relativePath,
url: getURL(relativePath),
tested: docTestStatus,
title: getAndTrimField("title", string(fileContent)),
owner: getAndTrimField("owner", string(fileContent)),
notes: []string{fmt.Sprintf("Relative path:%s", relativePath)},
})
}
return nil
})
return files, err
}
func skipFile(path string) bool {
for _, value := range excludeDirs {
if strings.HasPrefix(path, value) {
return true
}
}
return false
}
func getAndTrimField(field string, body string) string {
fieldRegex := regexp.MustCompile(fmt.Sprintf("%s:\\s*[\\w|\\/]*.*", field))
fieldValue := strings.TrimPrefix(fieldRegex.FindString(body), fmt.Sprintf("%s:", field))
fieldValue = strings.TrimPrefix(fieldValue, " ")
return fieldValue
}
func getTestStatus(content string) testStatus {
teststatus := getAndTrimField("test", content)
if teststatus == "" {
teststatus = string(testUnknown)
}
return testStatus(teststatus)
}