-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathparser.go
More file actions
118 lines (106 loc) · 3.4 KB
/
Copy pathparser.go
File metadata and controls
118 lines (106 loc) · 3.4 KB
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
package main
import (
"regexp"
"strings"
)
// Rule contains information about a rule
type Rule struct {
target, dependencies string
commands []string
lineNumber int
}
// NewRule constructs a rule given the target, dependencies, commands, and line number in the Makefile
func NewRule(target, dependencies string, commands []string, lineNumber int) *Rule {
return &Rule{target: target, dependencies: dependencies, commands: commands, lineNumber: lineNumber}
}
// ParsedContent contains the content of a Makefile with its parsed rules
type ParsedContent struct {
FilePath string
includeSpecialTargets bool
Content []string
Rules []Rule
}
// NewParsedContent constructs ParsedContent and stages the Makefile for parsing
func NewParsedContent(filePath string, content []string) *ParsedContent {
return &ParsedContent{FilePath: filePath, includeSpecialTargets: false, Content: content, Rules: []Rule{}}
}
// SetIncludeSpecialTargets sets the option to include special targets to the given boolean value
func (parsedContent *ParsedContent) SetIncludeSpecialTargets(value bool) {
parsedContent.includeSpecialTargets = value
}
// Parse parses the content of a Makefile and extracts rules from it
func (parsedContent *ParsedContent) Parse() {
inMultilineComment := false
inTarget := false
multilineCommentRegexp := regexp.MustCompile(`^.*#.*\\$`)
ruleRegexp := regexp.MustCompile(`^([^:\s]+)\s*:\s*([^=].*)?$`)
for lineNumber, line := range parsedContent.Content {
// Handle multiline comments
if inMultilineComment {
inTarget = false
inMultilineComment = len(line) > 0 && line[len(line)-1:] == "\\"
// If currently in multiline comment, the entire line is commented
continue
} else {
// Check if current line is start of multiline comment
multilineMatch := multilineCommentRegexp.MatchString(line)
inMultilineComment = multilineMatch
}
line := stripComments(line)
// Handle rule commands
if inTarget && (len(line) == 0 || line[0] == '\t') {
// Current line is a command
ruleIndex := len(parsedContent.Rules) - 1
parsedContent.Rules[ruleIndex].commands = append(parsedContent.Rules[ruleIndex].commands, strings.TrimSpace(line))
continue
} else if inTarget {
inTarget = false
}
ruleSubmatch := ruleRegexp.FindStringSubmatch(line)
if ruleSubmatch != nil && !(!parsedContent.includeSpecialTargets && isSpecialTarget(ruleSubmatch[1])) {
// Match has been found
newRule := NewRule(ruleSubmatch[1], ruleSubmatch[2], []string{}, lineNumber)
parsedContent.Rules = append(parsedContent.Rules, *newRule)
inTarget = true
}
}
}
func stripComments(line string) string {
inQuotes := false
for index, c := range line {
if c == '"' {
inQuotes = !inQuotes
} else if !inQuotes && c == '#' {
// Slice string from comment delimiter
line = strings.TrimSpace(line[:index])
break
}
}
return line
}
func isSpecialTarget(target string) bool {
// Returns true if the given target is a special built-in target name
specialTargetNames := []string{
".PHONY",
".SUFFIXES",
".DEFAULT",
".PRECIOUS",
".INTERMEDIATE",
".SECONDARY",
".SECONDEXPANSION",
".DELETE_ON_ERROR",
".IGNORE",
".LOW_RESOLUTION_TIME",
".SILENT",
".EXPORT_ALL_VARIABLES",
".NOTPARALLEL",
".ONESHELL",
".POSIX",
}
for _, specialTarget := range specialTargetNames {
if target == specialTarget {
return true
}
}
return false
}