/
metric_dates.upy
156 lines (128 loc) · 4.78 KB
/
metric_dates.upy
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# A Metrics Plugin for Git dates
# This plugin depends on git_util.py
import datetime
import git_util
gitMets = {
"GitDaysSinceCreated" : ["Days since creation", "The number of days since the file's oldest commit in Git"],
"GitDaysSinceLastModified" : ["Days since modified", "The number of days since the file's most recent commit in Git"]
}
def ids():
"""
Required, a list of metric ids that this script provides
For example, CountLineCode is a metric id.
"""
return list(gitMets.keys())
def name(id):
"""
Required, the name of the metric given by id.
For example, CountLineCode -> "Source Lines of Code"
"""
return gitMets.get(id,["",""])[0]
def description(id):
"""
Required, the description of the metric given by id
For example, CountLineCode -> "Number of lines containing source code"
"""
return gitMets.get(id,["",""])[1]
def is_integer(id):
"""
Optional, return True if the metric value is an integer.
If this function it not implemented, it is assumed false, meaning the
value should be represented as a double/float.
"""
return True
# One of the following three test functions should return True.
def test_entity(metric, ent):
"""
Optional, return True if metric can be calculated for the given entity.
"""
return ent.kind().check("file ~unknown ~unresolved") and git_util.hasGit(metric)
def test_architecture(metric, arch):
"""
Optional, return True if metric can be calculated for the given architecture.
"""
return git_util.archHasGit(metric,arch)
def test_global(metric, db):
"""
Optional, return True if metric can be calculated for the given database.
"""
return git_util.hasGit(metric)
def test_available(metric,entkindstr):
"""
Optional, return True if the metric is potentially available.
This is used when there isn't a specific target for the metric, like lists
of metrics available for export, or for a treemap.
Use metric.db() to retrieve the database. If the metric is language specific,
the code might look like this:
return "Ada" in metric.db().language()
entkindstr may be empty. If it is empty, return True as long as the metric
is available for an entity, architecture, or the project as a whole.
If entkindstr is not empty, return True only if the metric is available for
entities matching the provided kind string. Kind checks are performed like
this:
my_kinds = set(understand.Kind.list_entity(myMetricKindString)
test_kinds = set(understand.Kind.list_entity(entkindstr)
return len(my_kinds.intersection(test_kinds)) > 0
"""
return git_util.kindstringHasGit(metric, entkindstr)
def test_line(metric):
"""
Optional, return True if the metric has values for line.
Values per line are returned as a dictionary from line number to line
value from the lines function (see below).
"""
return metric.id() == "GitDaysSinceLastModified" and git_util.hasGit(metric)
def value(metric, target):
"""
Required, return the metric value for the target. The target may be
an entity, architecture, or database depending on which test functions
returned True.
"""
dates = git_util.targetGitValues(metric, target, "dates", "%aI")
if isinstance(dates, set):
dates = list(dates)
dates.sort(reverse=True)
if dates:
today = datetime.date.today()
if metric.id() == "GitDaysSinceCreated":
delta = today - datetime.datetime.fromisoformat(dates[-1]).date()
return delta.days
else:
delta = today - datetime.datetime.fromisoformat(dates[0]).date()
return delta.days
def lines(metric, file):
"""
Optional, return a dictionary from line number to line value.
This method is called if test_line returns True. The dictionary does
not have to include values for every line.
"""
if not file.kind().check("file ~unresolved ~unknown"):
return
blame, wd = git_util.runProcessInDbDir(["git", "blame", "-p", file.longname()], metric.db())
if not blame:
return None
commitToAge = dict()
lineToCommit = dict()
today = datetime.date.today()
nextLineIsHeader = True
curCommit = None
for line in blame.splitlines():
if nextLineIsHeader:
# CommitHash OriginalLineNum CurLineNum OptionalLineCount
parts = line.strip().split()
curCommit = parts[0]
lineToCommit[parts[2]] = curCommit
nextLineIsHeader = False
elif line.startswith('\t'):
nextLineIsHeader = True
elif line.startswith("author-time"):
# Maybe try to parse the time zone? It's not an argument for date
# so it probably isn't necessary for days past
d = datetime.date.fromtimestamp(int(line.split()[-1]))
delta = today - d
commitToAge[curCommit] = delta.days
lineToAge = dict()
for line, commit in lineToCommit.items():
if commit in commitToAge:
lineToAge[line] = commitToAge[commit]
return lineToAge