blob: 48975226851bf0b27a5830a13ad3b6ad0151c88d [file] [log] [blame]
// Copyright 2020 The Go Authors. All rights reserved.
// Licensed under the MIT License.
// See LICENSE in the project root for license information.
// Command generate is used to generate documentation from the package.json.
// To run:
// go run tools/generate.go -w
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"sort"
"strings"
)
var (
writeFlag = flag.Bool("w", true, "Write new file contents to disk.")
)
type PackageJSON struct {
Contributes struct {
Commands []Command `json:"commands,omitempty"`
Configuration struct {
Properties map[string]Property `json:"properties,omitempty"`
} `json:"configuration,omitempty"`
} `json:"contributes,omitempty"`
}
type Command struct {
Command string `json:"command,omitempty"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
}
type Property struct {
name string `json:"name,omitempty"` // Set by us.
// Below are defined in package.json
Properties map[string]interface{} `json:"properties,omitempty"`
Default interface{} `json:"default,omitempty"`
MarkdownDescription string `json:"markdownDescription,omitempty"`
Description string `json:"description,omitempty"`
MarkdownDeprecationMessage string `json:"markdownDeprecationMessage,omitempty"`
DeprecationMessage string `json:"deprecationMessage,omitempty"`
Type interface{} `json:"type,omitempty"`
Enum []string `json:"enum,omitempty"`
}
const indent = "  "
func main() {
flag.Parse()
// Assume this is running from the vscode-go directory.
dir, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
// Find the package.json file.
data, err := ioutil.ReadFile(filepath.Join(dir, "package.json"))
if err != nil {
log.Fatal(err)
}
pkgJSON := &PackageJSON{}
if err := json.Unmarshal(data, pkgJSON); err != nil {
log.Fatal(err)
}
rewrite := func(filename string, toAdd []byte) {
oldContent, err := ioutil.ReadFile(filename)
if err != nil {
log.Fatal(err)
}
gen := []byte(`<!-- Everything below this line is generated. DO NOT EDIT. -->`)
split := bytes.Split(oldContent, gen)
if len(split) == 1 {
log.Fatalf("expected to find %q in %s, not found", gen, filename)
}
s := bytes.Join([][]byte{
bytes.TrimSpace(split[0]),
gen,
toAdd,
}, []byte("\n\n"))
newContent := append(s, '\n')
// Return early if the contents are unchanged.
if bytes.Equal(oldContent, newContent) {
return
}
// Either write out new contents or report an error (if in CI).
if *writeFlag {
if err := ioutil.WriteFile(filename, newContent, 0644); err != nil {
log.Fatal(err)
}
fmt.Printf("updated %s\n", filename)
} else {
base := filepath.Join("docs", filepath.Base(filename))
fmt.Printf(`%s have changed in the package.json, but documentation in %s was not updated.
To update the settings, run "go run tools/generate.go -w".
`, strings.TrimSuffix(base, ".md"), base)
os.Exit(1) // causes CI to break.
}
}
var b bytes.Buffer
for i, c := range pkgJSON.Contributes.Commands {
b.WriteString(fmt.Sprintf("### `%s`\n\n%s", c.Title, c.Description))
if i != len(pkgJSON.Contributes.Commands)-1 {
b.WriteString("\n\n")
}
}
rewrite(filepath.Join(dir, "docs", "commands.md"), b.Bytes())
// Clear so that we can rewrite settings.md.
b.Reset()
var properties []Property
var goplsProperty Property
for name, p := range pkgJSON.Contributes.Configuration.Properties {
p.name = name
if name == "gopls" {
goplsProperty = p
}
properties = append(properties, p)
}
sort.Slice(properties, func(i, j int) bool {
return properties[i].name < properties[j].name
})
for i, p := range properties {
if p.name == "gopls" {
desc := "Customize `gopls` behavior by specifying the gopls' settings in this section. " +
"For example, \n```\n\"gopls\" : {\n\t\"build.directoryFilters\": [\"-node_modules\"]\n\t...\n}\n```\n" +
"This section is directly read by `gopls`. See the [`gopls` section](#settings-for-gopls) section " +
"for the full list of `gopls` settings."
b.WriteString(fmt.Sprintf("### `%s`\n\n%s", p.name, desc))
b.WriteString("\n\n")
continue
}
desc := p.Description
if p.MarkdownDescription != "" {
desc = p.MarkdownDescription
}
deprecation := p.DeprecationMessage
if p.MarkdownDeprecationMessage != "" {
deprecation = p.MarkdownDeprecationMessage
}
name := p.name
if deprecation != "" {
name += " (deprecated)"
desc = deprecation + "\n" + desc
}
b.WriteString(fmt.Sprintf("### `%s`\n\n%s", name, desc))
if p.Enum != nil {
b.WriteString(fmt.Sprintf("\n\nAllowed Values:`%v`", p.Enum))
}
switch p.Type {
case "object":
writeSettingsObjectProperties(&b, p.Properties)
x, ok := p.Default.(map[string]interface{})
// do nothing if it is nil
if ok && len(x) > 0 {
keys := []string{}
for k := range x {
keys = append(keys, k)
}
sort.Strings(keys)
b.WriteString("\nDefault:{<br/>\n")
for _, k := range keys {
v := x[k]
output := fmt.Sprintf("%v", v)
if str, ok := v.(string); ok {
output = fmt.Sprintf("%q", str)
}
// if v is an empty string, nothing gets printed
// if v is a map/object, it is printed on one line
// this could be improved at the cost of more code
b.WriteString(fmt.Sprintf("%s`\"%s\": %s`,<br/>\n", indent, k, output))
}
b.WriteString(" }\n")
}
case "boolean", "string", "number":
b.WriteString(fmt.Sprintf("\n\nDefault: `%v`", p.Default))
case "array":
x := p.Default.([]interface{})
if len(x) > 0 {
b.WriteString(fmt.Sprintf("\n\nDefault: `%v`", p.Default))
}
default:
if _, ok := p.Type.([]interface{}); ok {
b.WriteString(fmt.Sprintf("\n\nefault: `%v`", p.Default))
break
}
log.Fatalf("implement default when p.Type is %q in %#v %T", p.Type, p, p.Default)
}
if i != len(properties)-1 {
b.WriteString("\n\n")
}
}
// Write gopls section.
b.WriteString("## Settings for `gopls`\n\n")
writeGoplsSettingsSection(&b, goplsProperty)
rewrite(filepath.Join(dir, "docs", "settings.md"), b.Bytes())
}
func writeGoplsSettingsSection(b *bytes.Buffer, goplsProperty Property) {
desc := goplsProperty.MarkdownDescription
b.WriteString(desc)
b.WriteString("\n\n")
properties := goplsProperty.Properties
var names []string
for name := range properties {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
p, ok := properties[name].(map[string]interface{})
if !ok {
b.WriteString(fmt.Sprintf("### `%s`\n", name))
continue
}
desc := ""
if d := p["description"]; d != nil {
desc = fmt.Sprintf("%v", d)
}
if d := p["markdownDescription"]; d != nil {
desc = fmt.Sprintf("%v", d)
}
deprecation := ""
if d := p["deprecationMessage"]; d != nil {
deprecation = fmt.Sprintf("%v", d)
}
if d := p["markdownDeprecationMessage"]; d != nil {
deprecation = fmt.Sprintf("%v", d)
}
if deprecation != "" {
name += " (deprecated)"
desc = deprecation + "\n" + desc
}
b.WriteString(fmt.Sprintf("### `%s`\n%s", name, desc))
switch p["type"] {
case "object":
x, ok := p["default"].(map[string]interface{})
// do nothing if it is nil
if ok && len(x) > 0 {
keys := []string{}
for k := range x {
keys = append(keys, k)
}
sort.Strings(keys)
b.WriteString(fmt.Sprintf("\nDefault:{<br/>\n"))
for _, k := range keys {
v := x[k]
output := fmt.Sprintf("%v", v)
if str, ok := v.(string); ok {
output = fmt.Sprintf("%q", str)
}
// if v is an empty string, nothing gets printed
// if v is a map/object, it is printed on one line
// this could be improved at the cost of more code
b.WriteString(fmt.Sprintf("%s`\"%s\": %s`,<br/>\n", indent, k, output))
}
b.WriteString(" }\n")
}
case "boolean", "string", "number":
b.WriteString(fmt.Sprintf("\nDefault: `%v`", p["default"]))
case "array":
x, ok := p["default"].([]interface{})
if ok && len(x) > 0 {
b.WriteString(fmt.Sprintf("\nDefault: `%v`", p["default"]))
}
default:
b.WriteString(fmt.Sprintf("\nefault: `%v`", p["default"]))
}
b.WriteString("\n")
}
}
func writeSettingsObjectProperties(b *bytes.Buffer, properties map[string]interface{}) {
if len(properties) == 0 {
return
}
var names []string
for name := range properties {
names = append(names, name)
}
sort.Strings(names)
b.WriteString("\n\n")
b.WriteString("| Properties | Description |\n")
b.WriteString("| --- | --- |\n")
for _, name := range names {
p, ok := properties[name].(map[string]interface{})
if !ok {
b.WriteString(fmt.Sprintf("| `%s` | |\n", name))
continue
}
desc := ""
if d := p["description"]; d != nil {
desc = fmt.Sprintf("%v", d)
}
if d := p["markdownDescription"]; d != nil {
desc = fmt.Sprintf("%v", d)
}
deprecation := ""
if d := p["deprecationMessage"]; d != nil {
deprecation = fmt.Sprintf("%v", d)
}
if d := p["markdownDeprecationMessage"]; d != nil {
deprecation = fmt.Sprintf("%v", d)
}
if deprecation != "" {
name += " (deprecated)"
desc = deprecation + "\n" + desc
}
b.WriteString(fmt.Sprintf("| `%s` | %s |\n", name, desc))
}
b.WriteString("| | |\n")
b.WriteString("\n")
}