Skip to content

Commit 0a267e9

Browse files
committed
Updated to the new interface
1 parent cdab85e commit 0a267e9

File tree

6 files changed

+478
-204
lines changed

6 files changed

+478
-204
lines changed

cli/enry/main.go

+14-7
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ func main() {
2424
log.Fatal(err)
2525
}
2626

27-
enry.LoadGitattributes()
27+
gitAttributes := enry.NewGitAttributes()
28+
reader, err := os.Open(".gitattributes")
29+
if err == nil {
30+
gitAttributes.LoadGitAttributes("", reader)
31+
}
2832

2933
errors := false
3034
out := make(map[string][]string, 0)
@@ -50,8 +54,9 @@ func main() {
5054
relativePath = relativePath + "/"
5155
}
5256

53-
if enry.IsVendor(relativePath) || enry.IsDotFile(relativePath) ||
54-
enry.IsDocumentation(relativePath) || enry.IsConfiguration(relativePath) {
57+
if gitAttributes.IsVendor(relativePath) || enry.IsDotFile(relativePath) ||
58+
gitAttributes.IsDocumentation(relativePath) || enry.IsConfiguration(relativePath) ||
59+
gitAttributes.IsGenerated(path) {
5560
if f.IsDir() {
5661
return filepath.SkipDir
5762
}
@@ -69,10 +74,12 @@ func main() {
6974
log.Println(err)
7075
return nil
7176
}
72-
73-
language := enry.GetLanguage(filepath.Base(path), content)
74-
if language == enry.OtherLanguage {
75-
return nil
77+
language := gitAttributes.GetLanguage(filepath.Base(path))
78+
if len(language) == 0 {
79+
language = enry.GetLanguage(filepath.Base(path), content)
80+
if language == enry.OtherLanguage {
81+
return nil
82+
}
7683
}
7784

7885
out[language] = append(out[language], relativePath)

common.go

+16-11
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package enry
33
import (
44
"bufio"
55
"bytes"
6+
"os"
67
"path/filepath"
78
"regexp"
89
"strings"
@@ -18,7 +19,6 @@ type Strategy func(filename string, content []byte, candidates []string) (langua
1819

1920
// DefaultStrategies is the strategies' sequence GetLanguage uses to detect languages.
2021
var DefaultStrategies = []Strategy{
21-
GetLanguagesByGitattributes,
2222
GetLanguagesByModeline,
2323
GetLanguagesByFilename,
2424
GetLanguagesByShebang,
@@ -99,7 +99,7 @@ func GetLanguageByClassifier(content []byte, candidates []string) (language stri
9999
// GetLanguageByGitattributes returns the language assigned to a file for a given regular expresion in .gitattributes.
100100
// This strategy needs to be initialized calling LoadGitattributes
101101
func GetLanguageByGitattributes(filename string) (language string, safe bool) {
102-
return getLanguageByStrategy(GetLanguagesByGitattributes, filename, nil, nil)
102+
return getLanguageByStrategy(GetLanguagesByGitAttributes, filename, nil, nil)
103103
}
104104

105105
func getLanguageByStrategy(strategy Strategy, filename string, content []byte, candidates []string) (string, bool) {
@@ -450,15 +450,20 @@ func GetLanguageByAlias(alias string) (lang string, ok bool) {
450450
return
451451
}
452452

453-
// GetLanguagesByGitattributes returns a length 1 slice with the language assigned in .gitattributes if the regular expresion
454-
// matchs with the filename. It is comply with the signature to be a Strategy type.
455-
func GetLanguagesByGitattributes(filename string, content []byte, candidates []string) []string {
456-
languages := []string{}
457-
for regExp, language := range languageGitattributes {
458-
if regExp.MatchString(filename) {
459-
return append(languages, language)
460-
}
453+
// GetLanguagesByGitAttributes returns either a string slice with the language if the filename matches with a regExp in .gitattributes
454+
//or returns a empty slice in case no regexp matches the filename. It complies with the signature to be a Strategy type.
455+
func GetLanguagesByGitAttributes(filename string, content []byte, candidates []string) []string {
456+
gitAttributes := NewGitAttributes()
457+
reader, err := os.Open(".gitattributes")
458+
if err != nil {
459+
return nil
461460
}
462461

463-
return languages
462+
gitAttributes.LoadGitAttributes("", reader)
463+
lang := gitAttributes.GetLanguage(filename)
464+
if len(lang) == 0 {
465+
return []string{}
466+
}
467+
468+
return []string{lang}
464469
}

gitattributes.go

+251
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
package enry
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
"io/ioutil"
8+
"regexp"
9+
"strings"
10+
11+
"gopkg.in/src-d/enry.v1/data"
12+
)
13+
14+
type attrType int
15+
16+
const (
17+
vendor attrType = iota
18+
documentation
19+
generated
20+
language
21+
)
22+
23+
const _attrType_name = "vendordocumentationgeneratedlanguage"
24+
25+
var _attrType_index = [...]uint8{0, 6, 19, 28, 36}
26+
27+
func (i attrType) String() string {
28+
if i < 0 || i >= attrType(len(_attrType_index)-1) {
29+
return fmt.Sprintf("attrType(%d)", i)
30+
}
31+
return _attrType_name[_attrType_index[i]:_attrType_index[i+1]]
32+
}
33+
34+
type boolAttribute struct {
35+
kind attrType
36+
matchers []string
37+
attributes map[string]bool
38+
}
39+
40+
type regExpAttribute struct {
41+
kind attrType
42+
matchers []string
43+
attributes map[*regexp.Regexp]string
44+
}
45+
46+
// GitAttributes is a struct that contains two maps, boolAttributes contains all the attributes that works like a boolean condition,
47+
// regExpAttributes contains all the attributes that match a regExp to choose if an attribute is applied or not
48+
type GitAttributes struct {
49+
boolAttributes map[attrType]boolAttribute
50+
regExpAttributes map[attrType]regExpAttribute
51+
}
52+
53+
type overrideError struct {
54+
attribute attrType
55+
path string
56+
}
57+
58+
func (e *overrideError) Error() string {
59+
return fmt.Sprintf("gitattributes: You are overriding a %v attribute of one of your previous lines %s\n", e.attribute, e.path)
60+
}
61+
62+
// IsVendor returns whether or not path is a vendor path.
63+
func (gitAttrs *GitAttributes) IsVendor(path string) bool {
64+
if val, ok := gitAttrs.boolAttributes[vendor].attributes[path]; ok {
65+
return val
66+
}
67+
68+
return data.VendorMatchers.Match(path)
69+
}
70+
71+
// IsVendor returns whether or not path is a documentation path.
72+
func (gitAttrs *GitAttributes) IsDocumentation(path string) bool {
73+
if val, ok := gitAttrs.boolAttributes[documentation].attributes[path]; ok {
74+
return val
75+
}
76+
77+
return data.DocumentationMatchers.Match(path)
78+
}
79+
80+
// IsVendor returns whether or not path is a generated path.
81+
func (gitAttrs *GitAttributes) IsGenerated(path string) bool {
82+
if val, ok := gitAttrs.boolAttributes[generated].attributes[path]; ok {
83+
return val
84+
}
85+
return false
86+
}
87+
88+
// GetLanguage get the language of a file matching the langauge attributes given.
89+
// Returns either a empty string or the language if the regExp matches
90+
func (gitAttrs *GitAttributes) GetLanguage(filename string) string {
91+
for regExp, language := range gitAttrs.regExpAttributes[language].attributes {
92+
if regExp.MatchString(filename) {
93+
return language
94+
}
95+
}
96+
97+
return ""
98+
}
99+
100+
// NewGitAttributes initialize a Gitattributes object
101+
func NewGitAttributes() *GitAttributes {
102+
gitAttrs := GitAttributes{
103+
boolAttributes: map[attrType]boolAttribute{
104+
vendor: boolAttribute{kind: vendor, matchers: []string{"linguist-vendored", "linguist-vendored=false"}, attributes: map[string]bool{}},
105+
documentation: boolAttribute{kind: documentation, matchers: []string{"linguist-documentation", "linguist-documentation=false"}, attributes: map[string]bool{}},
106+
generated: boolAttribute{kind: generated, matchers: []string{"linguist-generated", "linguist-generated=false"}, attributes: map[string]bool{}},
107+
},
108+
regExpAttributes: map[attrType]regExpAttribute{
109+
language: regExpAttribute{kind: language, matchers: []string{"linguist-language="}, attributes: map[*regexp.Regexp]string{}},
110+
},
111+
}
112+
113+
return &gitAttrs
114+
}
115+
116+
// LoadGitattributes reads and parses the file .gitattributes which overrides the standard strategies
117+
// Returns slice of errors that have may ocurred in the load
118+
func (gitAttrs *GitAttributes) LoadGitAttributes(path string, reader io.Reader) []error {
119+
rawAttributes, errArr := loadRawGitAttributes(reader)
120+
if len(rawAttributes) == 0 {
121+
return []error{}
122+
}
123+
124+
return append(gitAttrs.parseAttributes(path, rawAttributes), errArr...)
125+
}
126+
127+
func (gitAttrs *GitAttributes) String() string {
128+
out := ""
129+
for key, val := range gitAttrs.boolAttributes {
130+
out += fmt.Sprintf("Type: %s Attributes: %v\n", key, val.attributes)
131+
}
132+
133+
for key, val := range gitAttrs.regExpAttributes {
134+
out += fmt.Sprintf("Type: %s Attributes: %v\n", key, val.attributes)
135+
}
136+
return out
137+
}
138+
139+
func loadRawGitAttributes(reader io.Reader) (map[string][]string, []error) {
140+
rawAttributes := map[string][]string{}
141+
var errArr []error
142+
data, err := ioutil.ReadAll(reader)
143+
if err != nil {
144+
errArr = append(errArr, err)
145+
return nil, errArr
146+
}
147+
148+
if len(data) > 0 {
149+
lines := strings.Split(string(data), "\n")
150+
for _, line := range lines {
151+
err := loadLine(line, rawAttributes)
152+
if err != nil {
153+
errArr = append(errArr, err)
154+
}
155+
}
156+
}
157+
158+
return rawAttributes, errArr
159+
}
160+
161+
func loadLine(line string, gitattributes map[string][]string) error {
162+
tokens := strings.Fields(line)
163+
if len(tokens) == 2 {
164+
gitattributes[tokens[0]] = append(gitattributes[tokens[0]], tokens[1])
165+
return nil
166+
} else if len(tokens) != 0 {
167+
err := errors.New("gitattributes: Each line only can have a pair of elements E.g. path/to/file attribute")
168+
return err
169+
}
170+
171+
return nil
172+
}
173+
174+
func (gitAttrs *GitAttributes) parseAttributes(path string, attributes map[string][]string) []error {
175+
errArray := []error{}
176+
for key, values := range attributes {
177+
for _, val := range values {
178+
err := gitAttrs.parseAttribute(path+key, val)
179+
if err != nil {
180+
errArray = append(errArray, err)
181+
}
182+
}
183+
}
184+
185+
return errArray
186+
}
187+
188+
func (gitAttrs *GitAttributes) matches(kind attrType, str string) bool {
189+
if bollAttrs, ok := gitAttrs.boolAttributes[kind]; ok && strings.Contains(str, bollAttrs.matchers[0]) {
190+
return true
191+
} else if regExpAttrs, ok := gitAttrs.regExpAttributes[kind]; ok && strings.Contains(str, regExpAttrs.matchers[0]) {
192+
return true
193+
}
194+
195+
return false
196+
}
197+
198+
func (gitAttrs *GitAttributes) parseAttribute(key string, attribute string) error {
199+
var err error
200+
matched := false
201+
for kind := vendor; kind <= language; kind++ {
202+
if gitAttrs.matches(kind, attribute) {
203+
matched = true
204+
if kind < language {
205+
err = gitAttrs.processBoolAttr(kind, key, attribute)
206+
} else {
207+
err = gitAttrs.processRegExpAttr(kind, key, attribute)
208+
}
209+
}
210+
}
211+
212+
if matched == false {
213+
err = errors.New(fmt.Sprintf("gitattributes: The matcher %s doesn't exists\n", attribute))
214+
}
215+
216+
return err
217+
}
218+
219+
func (gitAttrs *GitAttributes) processBoolAttr(kind attrType, key string, attribute string) error {
220+
var err error
221+
if _, ok := gitAttrs.boolAttributes[kind].attributes[key]; ok {
222+
err = &overrideError{attribute: kind, path: key}
223+
}
224+
switch {
225+
case attribute == gitAttrs.boolAttributes[kind].matchers[0]:
226+
gitAttrs.boolAttributes[kind].attributes[key] = true
227+
case attribute == gitAttrs.boolAttributes[kind].matchers[1]:
228+
gitAttrs.boolAttributes[kind].attributes[key] = false
229+
default:
230+
err = errors.New(fmt.Sprintf("gitattributes: The matcher %s doesn't exists\n", attribute))
231+
}
232+
233+
return err
234+
}
235+
236+
func (gitAttrs *GitAttributes) processRegExpAttr(kind attrType, regExpString string, attribute string) error {
237+
tokens := strings.SplitN(attribute, "=", 2)
238+
regExp, err := regexp.Compile(regExpString)
239+
if err != nil {
240+
return err
241+
}
242+
243+
lang, _ := GetLanguageByAlias(tokens[1])
244+
if lang != OtherLanguage {
245+
gitAttrs.regExpAttributes[kind].attributes[regExp] = lang
246+
} else {
247+
gitAttrs.regExpAttributes[kind].attributes[regExp] = tokens[1]
248+
}
249+
250+
return nil
251+
}

0 commit comments

Comments
 (0)