Skip to content

Commit

Permalink
feat: add flag to generate documentation only for specific tags (#1379)
Browse files Browse the repository at this point in the history
  • Loading branch information
Trial97 committed Nov 20, 2022
1 parent bdfec2b commit e6723fe
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 11 deletions.
5 changes: 4 additions & 1 deletion README.md
Expand Up @@ -85,6 +85,7 @@ USAGE:
swag init [command options] [arguments...]

OPTIONS:
--quiet, -q Make the logger quiet. (default: false)
--generalInfo value, -g value Go file path in which 'swagger general API Info' is written (default: "main.go")
--dir value, -d value Directories you want to parse,comma separated and general-info file must be in the first one (default: "./")
--exclude value Exclude directories and files when searching, comma separated
Expand All @@ -97,10 +98,12 @@ OPTIONS:
--codeExampleFiles value, --cef value Parse folder containing code example files to use for the x-codeSamples extension, disabled by default
--parseInternal Parse go files in internal packages, disabled by default (default: false)
--generatedTime Generate timestamp at the top of docs.go, disabled by default (default: false)
--requiredByDefault Set validation required for all fields by default (default: false)
--parseDepth value Dependency parse depth (default: 100)
--requiredByDefault Set validation required for all fields by default (default: false)
--instanceName value This parameter can be used to name different swagger document instances. It is optional.
--overridesFile value File to read global type overrides from. (default: ".swaggo")
--parseGoList Parse dependency via 'go list' (default: true)
--tags value, -t value A comma-separated list of tags to filter the APIs for which the documentation is generated.Special case if the tag is prefixed with the '!' character then the APIs with that tag will be excluded
--help, -h show help (default: false)
```
Expand Down
8 changes: 8 additions & 0 deletions cmd/swag/main.go
Expand Up @@ -33,6 +33,7 @@ const (
overridesFileFlag = "overridesFile"
parseGoListFlag = "parseGoList"
quietFlag = "quiet"
tagsFlag = "tags"
)

var initFlags = []cli.Flag{
Expand Down Expand Up @@ -128,6 +129,12 @@ var initFlags = []cli.Flag{
Value: true,
Usage: "Parse dependency via 'go list'",
},
&cli.StringFlag{
Name: tagsFlag,
Aliases: []string{"t"},
Value: "",
Usage: "A comma-separated list of tags to filter the APIs for which the documentation is generated.Special case if the tag is prefixed with the '!' character then the APIs with that tag will be excluded",
},
}

func initAction(ctx *cli.Context) error {
Expand Down Expand Up @@ -166,6 +173,7 @@ func initAction(ctx *cli.Context) error {
InstanceName: ctx.String(instanceNameFlag),
OverridesFile: ctx.String(overridesFileFlag),
ParseGoList: ctx.Bool(parseGoListFlag),
Tags: ctx.String(tagsFlag),
Debugger: logger,
})
}
Expand Down
4 changes: 4 additions & 0 deletions gen/gen.go
Expand Up @@ -120,6 +120,9 @@ type Config struct {

// ParseGoList whether swag use go list to parse dependency
ParseGoList bool

// include only tags mentioned when searching, comma separated
Tags string
}

// Build builds swagger json file for given searchDir and mainAPIFile. Returns json.
Expand Down Expand Up @@ -166,6 +169,7 @@ func (g *Gen) Build(config *Config) error {
swag.SetStrict(config.Strict),
swag.SetOverrides(overrides),
swag.ParseUsingGoList(config.ParseGoList),
swag.SetTags(config.Tags),
)

p.PropNamingStrategy = config.PropNamingStrategy
Expand Down
72 changes: 62 additions & 10 deletions parser.go
Expand Up @@ -148,6 +148,9 @@ type Parser struct {

// parseGoList whether swag use go list to parse dependency
parseGoList bool

// tags to filter the APIs after
tags map[string]struct{}
}

// FieldParserFactory create FieldParser.
Expand Down Expand Up @@ -199,6 +202,7 @@ func New(options ...func(*Parser)) *Parser {
parsedSchemas: make(map[*TypeSpecDef]*Schema),
outputSchemas: make(map[*TypeSpecDef]*Schema),
excludes: make(map[string]struct{}),
tags: make(map[string]struct{}),
fieldParserFactory: newTagBaseFieldParser,
Overrides: make(map[string]string),
}
Expand Down Expand Up @@ -237,6 +241,18 @@ func SetExcludedDirsAndFiles(excludes string) func(*Parser) {
}
}

// SetTags sets the tags to be included
func SetTags(include string) func(*Parser) {
return func(p *Parser) {
for _, f := range strings.Split(include, ",") {
f = strings.TrimSpace(f)
if f != "" {
p.tags[f] = struct{}{}
}
}
}
}

// SetStrict sets whether swag should error or warn when it detects cases which are most likely user errors.
func SetStrict(strict bool) func(*Parser) {
return func(p *Parser) {
Expand Down Expand Up @@ -762,24 +778,60 @@ func isExistsScope(scope string) (bool, error) {
return strings.Contains(scope, scopeAttrPrefix), nil
}

func getTagsFromComment(comment string) (tags []string) {
commentLine := strings.TrimSpace(strings.TrimLeft(comment, "/"))
if len(commentLine) == 0 {
return nil
}

attribute := strings.Fields(commentLine)[0]
lineRemainder, lowerAttribute := strings.TrimSpace(commentLine[len(attribute):]), strings.ToLower(attribute)

if lowerAttribute == tagsAttr {
for _, tag := range strings.Split(lineRemainder, ",") {
tags = append(tags, strings.TrimSpace(tag))
}
}
return

}

func (parser *Parser) matchTags(comments []*ast.Comment) (match bool) {
if len(parser.tags) != 0 {
for _, comment := range comments {
for _, tag := range getTagsFromComment(comment.Text) {
if _, has := parser.tags["!"+tag]; has {
return false
}
if _, has := parser.tags[tag]; has {
match = true // keep iterating as it may contain a tag that is excluded
}
}
}
return
}
return true
}

// ParseRouterAPIInfo parses router api info for given astFile.
func (parser *Parser) ParseRouterAPIInfo(fileName string, astFile *ast.File) error {
for _, astDescription := range astFile.Decls {
astDeclaration, ok := astDescription.(*ast.FuncDecl)
if ok && astDeclaration.Doc != nil && astDeclaration.Doc.List != nil {
// for per 'function' comment, create a new 'Operation' object
operation := NewOperation(parser, SetCodeExampleFilesDirectory(parser.codeExampleFilesDir))
for _, comment := range astDeclaration.Doc.List {
err := operation.ParseComment(comment.Text, astFile)
if parser.matchTags(astDeclaration.Doc.List) {
// for per 'function' comment, create a new 'Operation' object
operation := NewOperation(parser, SetCodeExampleFilesDirectory(parser.codeExampleFilesDir))
for _, comment := range astDeclaration.Doc.List {
err := operation.ParseComment(comment.Text, astFile)
if err != nil {
return fmt.Errorf("ParseComment error in file %s :%+v", fileName, err)
}
}
err := processRouterOperation(parser, operation)
if err != nil {
return fmt.Errorf("ParseComment error in file %s :%+v", fileName, err)
return err
}
}

err := processRouterOperation(parser, operation)
if err != nil {
return err
}
}
}

Expand Down
79 changes: 79 additions & 0 deletions parser_test.go
Expand Up @@ -3830,3 +3830,82 @@ func TestTryAddDescription(t *testing.T) {
})
}
}

func Test_getTagsFromComment(t *testing.T) {
type args struct {
comment string
}
tests := []struct {
name string
args args
wantTags []string
}{
{
name: "no tags comment",
args: args{
comment: "//@name Student",
},
wantTags: nil,
},
{
name: "empty comment",
args: args{
comment: "//",
},
wantTags: nil,
},
{
name: "tags comment",
args: args{
comment: "//@Tags tag1,tag2,tag3",
},
wantTags: []string{"tag1", "tag2", "tag3"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if gotTags := getTagsFromComment(tt.args.comment); !reflect.DeepEqual(gotTags, tt.wantTags) {
t.Errorf("getTagsFromComment() = %v, want %v", gotTags, tt.wantTags)
}
})
}
}

func TestParser_matchTags(t *testing.T) {

type args struct {
comments []*ast.Comment
}
tests := []struct {
name string
parser *Parser
args args
wantMatch bool
}{
{
name: "no tags filter",
parser: New(),
args: args{comments: []*ast.Comment{{Text: "//@Tags tag1,tag2,tag3"}}},
wantMatch: true,
},
{
name: "with tags filter but no match",
parser: New(SetTags("tag4,tag5,!tag1")),
args: args{comments: []*ast.Comment{{Text: "//@Tags tag1,tag2,tag3"}}},
wantMatch: false,
},
{
name: "with tags filter but match",
parser: New(SetTags("tag4,tag5,tag1")),
args: args{comments: []*ast.Comment{{Text: "//@Tags tag1,tag2,tag3"}}},
wantMatch: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if gotMatch := tt.parser.matchTags(tt.args.comments); gotMatch != tt.wantMatch {
t.Errorf("Parser.matchTags() = %v, want %v", gotMatch, tt.wantMatch)
}
})
}
}

0 comments on commit e6723fe

Please sign in to comment.