-
Notifications
You must be signed in to change notification settings - Fork 76
/
fileinfo.go
123 lines (101 loc) · 3.2 KB
/
fileinfo.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/* Copyright 2019 The Bazel Authors. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sass
import (
"fmt"
"os"
"path/filepath"
"sort"
"github.com/bazelbuild/rules_sass/gazelle/parser"
)
// FileInfo contains metadata extracted from a .sass/.scss file.
type FileInfo struct {
Path, Name string
Imports []string
Errors []string
}
// sassFileInfo takes a dir and file name and parses the .sass file into
// the constituent components, extracting metadata like the set of
// imports that it has. Returns an error object for all errors, if the error is
// fatal FileInfo will be nil.
func sassFileInfo(dir, name string) (FileInfo, error) {
info := FileInfo{
Path: filepath.Join(dir, name),
Name: name,
}
file, err := os.Open(filepath.Join(dir, name))
if err != nil {
return info, err
}
defer file.Close()
s := parser.New(file)
// Abstract the scan so that it doesn't return whitespace.
scan := func() parser.Token {
for {
t := s.Scan()
if _, ok := t.(*parser.WhiteSpace); !ok {
return t
}
}
}
// parseError is a convenience wrapper to print to stderr a parse error message
// in a standardized format.
parseError := func(msg string, args ...interface{}) {
// Prepend the file.Name() so we tell the user which file had the parse error.
args = append([]interface{}{file.Name()}, args...)
info.Errors = append(info.Errors, fmt.Sprintf("%s: "+msg+"\n", args...))
}
for t := scan(); t.Type() != "EOF"; t = scan() {
var imports []string
switch v := t.(type) {
case *parser.At:
if i, ok := v.Ident.(*parser.Ident); ok && i.Value == "import" {
// import_stmt ::= <At <Ident "import">> <String> (<Comma> <String>)+ <Semicolon>
// Scan the next symbol. It should be either a <String>
t = scan()
if s, ok := t.(*parser.String); ok {
imports = append(imports, s.Value)
} else {
parseError("expected a string but got a %v\n", t.Type())
continue
}
// Scan the next symbol. It should be either a <Comma> or a <Semicolon>
t = scan()
// Loop through the next tokens in case the user provided a comma
// separated list of files to include.
for {
if _, ok := t.(*parser.Comma); !ok {
break
}
t = scan()
if s, ok := t.(*parser.String); ok {
imports = append(imports, s.Value)
} else {
break
}
t = scan()
}
if _, ok := t.(*parser.Semicolon); !ok {
// If the last character isn't a semicolon then it isn't a valid
// import statement. Don't add it to the list of imports.
parseError("imports should end in semicolons\n")
continue
}
if len(imports) > 0 {
info.Imports = append(info.Imports, imports...)
}
}
}
}
sort.Strings(info.Imports)
return info, nil
}