@@ -18,12 +18,11 @@ import (
1818)
1919
2020type parser struct {
21- spec * sloth.Spec
22- sourceFile string
23- sourceContent io.ReadCloser
24- includedDirs []string
25- applicationPackages map [string ]* ast.Package
26- logger * logging.Logger
21+ spec * sloth.Spec
22+ sourceFile string
23+ sourceContent io.ReadCloser
24+ includedDirs []string
25+ logger * logging.Logger
2726}
2827
2928// Options contains the configuration options available to the Parser
@@ -34,55 +33,51 @@ type Options struct {
3433 InputDirectories []string
3534}
3635
36+ func NewOptions () * Options {
37+ l := logging .NewStandardLogger ()
38+ return & Options {
39+ Logger : & l ,
40+ SourceFile : "" ,
41+ SourceContent : nil ,
42+ InputDirectories : nil ,
43+ }
44+ }
45+
3746// NewParser client parser performs all checks at initialization time
38- func NewParser (opts Options ) * parser {
47+ func NewParser (opts * Options ) * parser {
48+ // create default options, these will be overridden
49+ if opts == nil {
50+ opts = NewOptions ()
51+ }
52+
3953 logger := opts .Logger
4054 dirs := opts .InputDirectories
4155 sourceFile := opts .SourceFile
4256 sourceContent := opts .SourceContent
4357
44- pkgs := map [string ]* ast.Package {}
45- for _ , dir := range dirs {
46- if _ , err := os .Stat (dir ); errors .Is (err , os .ErrNotExist ) {
47- // skip if dir doesn't exists
48- continue
49- }
50-
51- foundPkgs , err := getPackages (dir )
52- if err != nil {
53- logger .Warn (err )
54- continue
55- }
56-
57- for pkgName , pkg := range foundPkgs {
58- if _ , ok := pkgs [pkgName ]; ! ok {
59- pkgs [pkgName ] = pkg
60- }
61- }
62- }
63-
6458 return & parser {
6559 spec : & sloth.Spec {
6660 Version : sloth .Version ,
6761 Service : "" ,
6862 Labels : nil ,
6963 SLOs : nil ,
7064 },
71- sourceFile : sourceFile ,
72- sourceContent : sourceContent ,
73- includedDirs : dirs ,
74- applicationPackages : pkgs ,
75- logger : logger ,
65+ sourceFile : sourceFile ,
66+ sourceContent : sourceContent ,
67+ includedDirs : dirs ,
68+ logger : logger ,
7669 }
7770}
7871
79- func getPackages (dir string ) (map [string ]* ast.Package , error ) {
72+ // getAllGoPackages fetches all the available golang packages in the target directory and subdirectories
73+ func getAllGoPackages (dir string ) (map [string ]* ast.Package , error ) {
8074 fset := token .NewFileSet ()
8175 pkgs , err := goparser .ParseDir (fset , dir , nil , goparser .ParseComments )
8276 if err != nil {
8377 return map [string ]* ast.Package {}, err
8478 }
8579
80+ // walk through the directories and parse the not already found go packages
8681 err = filepath .WalkDir (dir , func (path string , d fs.DirEntry , err error ) error {
8782 if d .IsDir () {
8883 foundPkgs , err := goparser .ParseDir (fset , path , nil , goparser .ParseComments )
@@ -97,9 +92,19 @@ func getPackages(dir string) (map[string]*ast.Package, error) {
9792 }
9893 return err
9994 })
100- return pkgs , err
95+ if err != nil {
96+ return nil , err
97+ }
98+
99+ if len (pkgs ) == 0 {
100+ return nil , errors .Errorf ("no go packages were found in the target directory and subdirectories: %s" , dir )
101+ }
102+
103+ return pkgs , nil
101104}
102105
106+ // getFile returns the ast go file struct given filename or an io.Reader. If an io.Reader is passed it will take precedence
107+ // over the filename
103108func getFile (name string , file io.ReadCloser ) (* ast.File , error ) {
104109 fset := token .NewFileSet ()
105110 if file != nil {
@@ -108,41 +113,18 @@ func getFile(name string, file io.ReadCloser) (*ast.File, error) {
108113 return goparser .ParseFile (fset , name , file , goparser .ParseComments )
109114}
110115
111- func (p parser ) Parse (ctx context.Context ) (* sloth.Spec , error ) {
112- // collect all aloe error comments from packages and add them to the spec struct
113- if p .sourceFile != "" || p .sourceContent != nil {
114- file , err := getFile (p .sourceFile , p .sourceContent )
115- if err != nil {
116- return nil , err
117- }
118- if err := p .parseComments (file .Comments ... ); err != nil {
119- return nil , err
120- }
121- return p .spec , nil
122- }
123-
124- if len (p .applicationPackages ) > 0 {
125- for _ , pkg := range p .applicationPackages {
126- for _ , file := range pkg .Files {
127- if err := p .parseComments (file .Comments ... ); err != nil {
128- p .logger .Warn (err )
129- continue
130- }
131- }
132- }
133- }
134-
135- return p .spec , nil
116+ // getSpec returns the parser specification struct
117+ func (p parser ) getSpec () * sloth.Spec {
118+ return p .spec
136119}
137120
138- func (p parser ) parseComments (comments ... * ast.CommentGroup ) error {
121+ // parseSlothAnnotations parses the source code comments for sloth annotations using the sloth grammar.
122+ // It expects only SLO definition per comment group
123+ func (p parser ) parseSlothAnnotations (comments ... * ast.CommentGroup ) error {
139124 for _ , comment := range comments {
140125 newSpec , err := grammar .Eval (strings .TrimSpace (comment .Text ()))
141- switch {
142- case errors .Is (err , grammar .ErrParseSource ):
143- continue
144- case err != nil :
145- p .logger .Warn (err )
126+ if err != nil {
127+ p .warn (err )
146128 continue
147129 }
148130
@@ -164,3 +146,61 @@ func (p parser) parseComments(comments ...*ast.CommentGroup) error {
164146 }
165147 return nil
166148}
149+
150+ // Parse will parse the source code for sloth annotations.
151+ // In case of error during parsing, Parse returns an empty sloth.Spec
152+ func (p parser ) Parse (ctx context.Context ) (* sloth.Spec , error ) {
153+ // collect all sloth annotations from the file and add them to the spec struct
154+ if p .sourceFile != "" || p .sourceContent != nil {
155+ file , err := getFile (p .sourceFile , p .sourceContent )
156+ if err != nil {
157+ // error hard as we can't extract more data for the spec
158+ return nil , err
159+ }
160+ if err := p .parseSlothAnnotations (file .Comments ... ); err != nil {
161+ return nil , err
162+ }
163+ return p .spec , nil
164+ }
165+
166+ applicationPackages := map [string ]* ast.Package {}
167+ for _ , dir := range p .includedDirs {
168+ if _ , err := os .Stat (dir ); errors .Is (err , os .ErrNotExist ) {
169+ // skip if dir doesn't exists
170+ p .warn (err )
171+ continue
172+ }
173+
174+ foundPkgs , err := getAllGoPackages (dir )
175+ if err != nil {
176+ p .warn (err )
177+ continue
178+ }
179+
180+ for pkgName , pkg := range foundPkgs {
181+ if _ , ok := applicationPackages [pkgName ]; ! ok {
182+ applicationPackages [pkgName ] = pkg
183+ }
184+ }
185+ }
186+
187+ // collect all sloth annotations from packages and add them to the spec struct
188+ if len (applicationPackages ) > 0 {
189+ for _ , pkg := range applicationPackages {
190+ for _ , file := range pkg .Files {
191+ if err := p .parseSlothAnnotations (file .Comments ... ); err != nil {
192+ p .warn (err )
193+ continue
194+ }
195+ }
196+ }
197+ }
198+
199+ return p .spec , nil
200+ }
201+
202+ func (p parser ) warn (err error , keyValues ... interface {}) {
203+ if p .logger != nil {
204+ p .logger .Warn (err , keyValues ... )
205+ }
206+ }
0 commit comments