Skip to content

Commit adad216

Browse files
committed
*: support multi-directory
1 parent e9ab6a7 commit adad216

File tree

7 files changed

+206
-265
lines changed

7 files changed

+206
-265
lines changed

README.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,18 @@ protodoc generates Protocol Buffer documentation.
88
```
99
go get -v -u github.com/coreos/protodoc
1010
11-
protodoc ./parse/testdata \
11+
protodoc --directory=./parse/testdata \
12+
--parse="service,message" \
1213
--languages="Go,C++,Java,Python" \
13-
--title="testdata" \
14-
--target-path="./sample.md"
14+
--title=testdata \
15+
--output=sample.md
16+
17+
# to combine multiple directories into one
18+
protodoc --directories=./parse/testdata=message,dirA=message_service \
19+
--parse="service,message" \
20+
--languages="Go,C++,Java,Python" \
21+
--title=testdata \
22+
--output=sample.md
1523
```
1624

1725
Note that parser only understands the minimum syntax

main.go

Lines changed: 94 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"fmt"
3030
"log"
3131
"os"
32+
"strings"
3233

3334
"github.com/coreos/protodoc/parse"
3435
"github.com/spf13/cobra"
@@ -41,37 +42,102 @@ var (
4142
RunE: CommandFunc,
4243
}
4344

44-
title string
45-
targetDir string
46-
targetPath string
45+
targetDirectory string
46+
parseOptions []string
4747
languageOptions []string
48+
title string
49+
outputPath string
50+
51+
targetDirectories mapString
4852
)
4953

54+
type mapString map[string][]parse.ParseOption
55+
56+
func newMapString() mapString {
57+
return mapString(make(map[string][]parse.ParseOption))
58+
}
59+
func (m mapString) String() string {
60+
return ""
61+
}
62+
func (m mapString) Set(s string) error {
63+
for _, elem := range strings.Split(s, ",") {
64+
pair := strings.Split(elem, "=")
65+
if len(pair) != 2 {
66+
return fmt.Errorf("invalid format %s", pair)
67+
}
68+
opts := []parse.ParseOption{}
69+
for _, v := range strings.Split(pair[1], "_") {
70+
switch v {
71+
case "message":
72+
opts = append(opts, parse.ParseMessage)
73+
case "service":
74+
opts = append(opts, parse.ParseService)
75+
}
76+
}
77+
m[pair[0]] = opts
78+
}
79+
return nil
80+
}
81+
func (m mapString) Type() string {
82+
return "map[string][]parse.ParseOption"
83+
}
84+
5085
func init() {
5186
cobra.EnablePrefixMatching = true
5287
}
5388

5489
func init() {
90+
rootCommand.PersistentFlags().StringVarP(&targetDirectory, "directory", "d", "", "target directory where Protocol Buffer files are.")
91+
rootCommand.PersistentFlags().StringSliceVarP(&parseOptions, "parse", "p", []string{"service", "message"}, "Protocol Buffer types to parse (message, service)")
92+
rootCommand.PersistentFlags().StringSliceVarP(&languageOptions, "languages", "l", []string{}, "language options in field descriptions (Go, C++, Java, Python, Ruby, C#)")
5593
rootCommand.PersistentFlags().StringVarP(&title, "title", "t", "", "title of documentation")
56-
rootCommand.PersistentFlags().StringVarP(&targetPath, "target-path", "p", "", "file path to save the documentation")
57-
rootCommand.PersistentFlags().StringSliceVarP(&languageOptions, "languages", "o", []string{}, "language options in field descriptions (Go, C++, Java, Python, Ruby, C#)")
94+
rootCommand.PersistentFlags().StringVarP(&outputPath, "output", "o", "", "output file path to save documentation")
95+
96+
rootCommand.PersistentFlags().Var(&targetDirectories, "directories", "comma separated map of target directory to parse options (e.g. 'dirA=message,dirB=message_service')")
5897
}
5998

6099
func CommandFunc(cmd *cobra.Command, args []string) error {
61-
if len(args) != 1 {
62-
return fmt.Errorf("need 1 argument of target directory, got %q", args)
63-
}
64-
targetDir := args[0]
65-
log.Println("opening", targetDir)
66-
proto, err := parse.ReadDir(targetDir)
67-
if err != nil {
68-
return err
100+
var rs string
101+
if len(targetDirectories) == 0 {
102+
log.Println("opening", targetDirectory)
103+
proto, err := parse.ReadDir(targetDirectory)
104+
if err != nil {
105+
return err
106+
}
107+
opts := []parse.ParseOption{}
108+
for _, v := range parseOptions {
109+
switch v {
110+
case "message":
111+
opts = append(opts, parse.ParseMessage)
112+
case "service":
113+
opts = append(opts, parse.ParseService)
114+
}
115+
}
116+
log.Println("converting to markdown", title)
117+
rs, err = proto.Markdown(title, opts, languageOptions...)
118+
if err != nil {
119+
return err
120+
}
121+
} else {
122+
for k, opts := range targetDirectories {
123+
log.Println("opening", k)
124+
proto, err := parse.ReadDir(k)
125+
if err != nil {
126+
return err
127+
}
128+
ms, err := proto.Markdown("", opts, languageOptions...)
129+
if err != nil {
130+
return err
131+
}
132+
rs += ms
133+
}
134+
rs = fmt.Sprintf("### %s\n\n\n", title) + rs
69135
}
70-
err = proto.Markdown(title, targetPath, languageOptions...)
136+
err := toFile(rs, outputPath)
71137
if err != nil {
72138
return err
73139
}
74-
log.Printf("saved at %s", targetPath)
140+
log.Printf("saved at %s", outputPath)
75141
return nil
76142
}
77143

@@ -81,3 +147,16 @@ func main() {
81147
os.Exit(1)
82148
}
83149
}
150+
151+
func toFile(txt, fpath string) error {
152+
f, err := os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0777)
153+
if err != nil {
154+
f, err = os.Create(fpath)
155+
if err != nil {
156+
return err
157+
}
158+
}
159+
defer f.Close()
160+
_, err = f.WriteString(txt)
161+
return err
162+
}

parse/markdown.go

Lines changed: 93 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -20,100 +20,115 @@ import (
2020
"strings"
2121
)
2222

23+
type ParseOption int
24+
25+
const (
26+
ParseService ParseOption = iota
27+
ParseMessage
28+
)
29+
2330
// Markdown saves 'Proto' to markdown documentation.
2431
// lopts are a slice of language options (C++, Java, Python, Go, Ruby, C#).
25-
func (p *Proto) Markdown(title, fpath string, lopts ...string) error {
32+
func (p *Proto) Markdown(title string, parseOpts []ParseOption, lopts ...string) (string, error) {
2633
p.Sort()
2734

2835
buf := new(bytes.Buffer)
29-
buf.WriteString(fmt.Sprintf("### %s\n\n\n", title))
30-
31-
for _, svs := range p.Services {
32-
buf.WriteString(fmt.Sprintf("##### service `%s`\n\n", svs.Name))
33-
if svs.Description != "" {
34-
buf.WriteString(svs.Description)
35-
buf.WriteString("\n\n")
36-
}
37-
38-
if len(svs.Methods) > 0 {
39-
hd1 := "| Method | Request Type | Response Type | Description |"
40-
hd2 := "| ------ | ------------ | ------------- | ----------- |"
41-
buf.WriteString(hd1 + "\n")
42-
buf.WriteString(hd2 + "\n")
43-
for _, elem := range svs.Methods {
44-
line := fmt.Sprintf("| %s | %s | %s | %s |", elem.Name, elem.RequestType, elem.ResponseType, elem.Description)
45-
buf.WriteString(line + "\n")
46-
}
47-
} else {
48-
buf.WriteString("Empty method.\n")
49-
}
50-
51-
buf.WriteString("\n\n<br>\n\n")
36+
if len(title) > 0 {
37+
buf.WriteString(fmt.Sprintf("### %s\n\n\n", title))
5238
}
5339

54-
for _, msg := range p.Messages {
55-
buf.WriteString(fmt.Sprintf("##### message `%s`\n\n", msg.Name))
56-
if msg.Description != "" {
57-
buf.WriteString(msg.Description)
58-
buf.WriteString("\n\n")
59-
}
40+
for _, opt := range parseOpts {
41+
switch opt {
42+
case ParseService:
43+
for _, svs := range p.Services {
44+
buf.WriteString(fmt.Sprintf("##### service `%s`\n\n", svs.Name))
45+
if svs.Description != "" {
46+
buf.WriteString(svs.Description)
47+
buf.WriteString("\n\n")
48+
}
6049

61-
if len(msg.Fields) > 0 {
62-
hd1 := "| Field | Description | Type |"
63-
hd2 := "| ----- | ----------- | ---- |"
64-
for _, lopt := range lopts {
65-
hd1 += fmt.Sprintf(" %s |", lopt)
66-
ds := strings.Repeat("-", len(lopt))
67-
if len(ds) < 3 {
68-
ds = "---"
50+
if len(svs.Methods) > 0 {
51+
hd1 := "| Method | Request Type | Response Type | Description |"
52+
hd2 := "| ------ | ------------ | ------------- | ----------- |"
53+
buf.WriteString(hd1 + "\n")
54+
buf.WriteString(hd2 + "\n")
55+
for _, elem := range svs.Methods {
56+
line := fmt.Sprintf("| %s | %s | %s | %s |", elem.Name, elem.RequestType, elem.ResponseType, elem.Description)
57+
buf.WriteString(line + "\n")
58+
}
59+
} else {
60+
buf.WriteString("Empty method.\n")
6961
}
70-
hd2 += fmt.Sprintf(" %s |", ds)
62+
63+
buf.WriteString("\n\n\n")
7164
}
72-
buf.WriteString(hd1 + "\n")
73-
buf.WriteString(hd2 + "\n")
74-
for _, elem := range msg.Fields {
75-
ts := elem.ProtoType.String()
76-
if elem.UserDefinedProtoType != "" {
77-
ts = elem.UserDefinedProtoType
78-
}
79-
if elem.Repeated {
80-
ts = "(slice of) " + ts
65+
66+
case ParseMessage:
67+
for _, msg := range p.Messages {
68+
buf.WriteString(fmt.Sprintf("##### message `%s`\n\n", msg.Name))
69+
if msg.Description != "" {
70+
buf.WriteString(msg.Description)
71+
buf.WriteString("\n\n")
8172
}
82-
line := fmt.Sprintf("| %s | %s | %s |", elem.Name, elem.Description, ts)
83-
for _, lopt := range lopts {
84-
if elem.UserDefinedProtoType != "" {
85-
line += " |"
86-
continue
87-
}
88-
formatSt := " %s |"
89-
if elem.Repeated {
90-
formatSt = " (slice of) %s |"
73+
74+
if len(msg.Fields) > 0 {
75+
hd1 := "| Field | Description | Type |"
76+
hd2 := "| ----- | ----------- | ---- |"
77+
for _, lopt := range lopts {
78+
hd1 += fmt.Sprintf(" %s |", lopt)
79+
ds := strings.Repeat("-", len(lopt))
80+
if len(ds) < 3 {
81+
ds = "---"
82+
}
83+
hd2 += fmt.Sprintf(" %s |", ds)
9184
}
92-
switch lopt {
93-
case "C++":
94-
line += fmt.Sprintf(formatSt, elem.ProtoType.Cpp())
95-
case "Java":
96-
line += fmt.Sprintf(formatSt, elem.ProtoType.Java())
97-
case "Python":
98-
line += fmt.Sprintf(formatSt, elem.ProtoType.Python())
99-
case "Go":
100-
line += fmt.Sprintf(formatSt, elem.ProtoType.Go())
101-
case "Ruby":
102-
line += fmt.Sprintf(formatSt, elem.ProtoType.Ruby())
103-
case "C#":
104-
line += fmt.Sprintf(formatSt, elem.ProtoType.Csharp())
105-
default:
106-
return fmt.Errorf("%q is unknown (must be C++, Java, Python, Go, Ruby, C#)", lopt)
85+
buf.WriteString(hd1 + "\n")
86+
buf.WriteString(hd2 + "\n")
87+
for _, elem := range msg.Fields {
88+
ts := elem.ProtoType.String()
89+
if elem.UserDefinedProtoType != "" {
90+
ts = elem.UserDefinedProtoType
91+
}
92+
if elem.Repeated {
93+
ts = "(slice of) " + ts
94+
}
95+
line := fmt.Sprintf("| %s | %s | %s |", elem.Name, elem.Description, ts)
96+
for _, lopt := range lopts {
97+
if elem.UserDefinedProtoType != "" {
98+
line += " |"
99+
continue
100+
}
101+
formatSt := " %s |"
102+
if elem.Repeated {
103+
formatSt = " (slice of) %s |"
104+
}
105+
switch lopt {
106+
case "C++":
107+
line += fmt.Sprintf(formatSt, elem.ProtoType.Cpp())
108+
case "Java":
109+
line += fmt.Sprintf(formatSt, elem.ProtoType.Java())
110+
case "Python":
111+
line += fmt.Sprintf(formatSt, elem.ProtoType.Python())
112+
case "Go":
113+
line += fmt.Sprintf(formatSt, elem.ProtoType.Go())
114+
case "Ruby":
115+
line += fmt.Sprintf(formatSt, elem.ProtoType.Ruby())
116+
case "C#":
117+
line += fmt.Sprintf(formatSt, elem.ProtoType.Csharp())
118+
default:
119+
return "", fmt.Errorf("%q is unknown (must be C++, Java, Python, Go, Ruby, C#)", lopt)
120+
}
121+
}
122+
buf.WriteString(line + "\n")
107123
}
124+
} else {
125+
buf.WriteString("Empty field.\n")
108126
}
109-
buf.WriteString(line + "\n")
127+
128+
buf.WriteString("\n\n\n")
110129
}
111-
} else {
112-
buf.WriteString("Empty field.\n")
113130
}
114-
115-
buf.WriteString("\n\n<br>\n\n")
116131
}
117132

118-
return toFile(buf.String(), fpath)
133+
return buf.String(), nil
119134
}

parse/markdown_test.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@ func TestMarkdown(t *testing.T) {
2121
if err != nil {
2222
t.Fatal(err)
2323
}
24-
if err := proto.Markdown("etcdserverpb", "testdata/README.md", "Go", "Java", "Python", "C++"); err != nil {
24+
if txt, err := proto.Markdown("etcdserverpb", []ParseOption{ParseService, ParseMessage}, "Go", "Java", "Python", "C++"); err != nil {
2525
t.Fatal(err)
26+
} else {
27+
err = toFile(txt, "testdata/README.md")
28+
if err != nil {
29+
t.Fatal(err)
30+
}
2631
}
2732
}

0 commit comments

Comments
 (0)