diff --git a/README.md b/README.md index 1038c253f..44e699764 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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) ``` diff --git a/cmd/swag/main.go b/cmd/swag/main.go index 5c2f3b693..6ba970561 100644 --- a/cmd/swag/main.go +++ b/cmd/swag/main.go @@ -33,6 +33,7 @@ const ( overridesFileFlag = "overridesFile" parseGoListFlag = "parseGoList" quietFlag = "quiet" + tagsFlag = "tags" ) var initFlags = []cli.Flag{ @@ -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 { @@ -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, }) } diff --git a/gen/gen.go b/gen/gen.go index a427fb314..8933d03f9 100644 --- a/gen/gen.go +++ b/gen/gen.go @@ -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. @@ -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 diff --git a/parser.go b/parser.go index a36ba6d2d..3c5ab0fc3 100644 --- a/parser.go +++ b/parser.go @@ -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. @@ -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), } @@ -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) { @@ -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 - } } } diff --git a/parser_test.go b/parser_test.go index 9afebfb30..644227f6b 100644 --- a/parser_test.go +++ b/parser_test.go @@ -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) + } + }) + } +}