| // 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. |
| 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 // Set by us. |
| |
| // Below are defined in package.json |
| Default interface{} `json:"default,omitempty"` |
| MarkdownDescription string `json:"markdownDescription,omitempty"` |
| Description string `json:"description,omitempty"` |
| Type interface{} `json:"type,omitempty"` |
| Enum []string `json:"enum,omitempty"` |
| } |
| |
| 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. |
| `, 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 |
| for name, p := range pkgJSON.Contributes.Configuration.Properties { |
| p.name = name |
| properties = append(properties, p) |
| } |
| sort.Slice(properties, func(i, j int) bool { |
| return properties[i].name < properties[j].name |
| }) |
| indent := " " |
| for i, p := range properties { |
| desc := p.Description |
| if p.MarkdownDescription != "" { |
| desc = p.MarkdownDescription |
| } |
| b.WriteString(fmt.Sprintf("### `%s`\n\n%s", p.name, desc)) |
| if p.Enum != nil { |
| b.WriteString(fmt.Sprintf("\n\nAllowed Values:`%v`", p.Enum)) |
| } |
| 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("\n\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") |
| } |
| } |
| rewrite(filepath.Join(dir, "docs", "settings.md"), b.Bytes()) |
| } |