Skip to content

Commit 902c65f

Browse files
committed
git lines of code action plugin works now, its not finished but it behaves in a proper manner so that the engine can be tested; CDE now accepts optional and mandatory args for domains, actions etc; only email addresses will be used as keys (identifieing contributors) from now on, we could add algorithms for username<>email aggregation later; protontypes#164
1 parent f10a9e6 commit 902c65f

File tree

5 files changed

+126
-51
lines changed

5 files changed

+126
-51
lines changed

libreselery/contribution_action_plugins/git_file_contribution_action.py

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,37 @@ def gather_(self, cachedContributors=[]):
6161
"""
6262
contributors = []
6363
scores = []
64-
64+
### execute git commands to identify line contributions for each contributor
65+
### per file under git version control
6566
fileContributions = self.execGit()
67+
### iterate through all cointributions and separate contributors from files
68+
uniqueFileContributions = {}
6669
for filename, fileContributorDict in fileContributions.items():
67-
print("%s" % filename)
68-
self.printFileContributorDict(fileContributorDict)
69-
70+
self.log("%s" % filename)
71+
### extract and log necessary contributor data for the file
72+
contributorData = self.processFileContributorDict(fileContributorDict)
73+
### sum up all contributions from each user in our final
74+
### dict (contributon = lines of code)
75+
for author, count in contributorData.items():
76+
if author in uniqueFileContributions:
77+
uniqueFileContributions[author] += count
78+
else:
79+
uniqueFileContributions[author] = count
80+
### extract contributors and scores list from summed up file contribution data
81+
blob = [*uniqueFileContributions.items()]
82+
contributors, linesOfCode = ([c for c, s in blob], [s for c, s in blob])
83+
### convert linesOfCode to score
84+
### we need to use given metrics for that
85+
### our action was initialized with a metric, we have to use that instead of
86+
### doing something random here
87+
###
88+
### in this simple example, each line of code represents 0.25 score points
89+
### -- this is bad, but it works for now as a reference
90+
### -- this cannot be a magic number, has to be configurable later
91+
scores = [0.25 * loc for loc in linesOfCode]
92+
### done, return our data so that it can be used inside the CDE to
93+
### weight the contributions made
94+
self.log(contributors)
7095
return contributors, scores
7196

7297
### Start User Methods
@@ -101,20 +126,28 @@ def execGit(self):
101126
lines = blame.split("\n")
102127
filename = lines[0]
103128
lines = lines[1:]
104-
#print("Blame > %s [%s]" % (filename, len(lines)))
105129
### put lines through blameParser
106130
fileContributorDict = self.parseBlame(lines)
131+
### filter out unwanted users, for example the one git blame adds
132+
### in case there are uncommitted changes
133+
### "<not.committed.yet>", "Not Committed Yet"
134+
if "not.committed.yet" in fileContributorDict:
135+
del fileContributorDict["not.committed.yet"]
107136
fileContributions[filename] = fileContributorDict
108137
return fileContributions
109138

110-
def printFileContributorDict(self, fcDict):
139+
def processFileContributorDict(self, fcDict):
140+
fileContributions = {}
111141
for author, data in fcDict.items():
112-
print(" %s [%s]" % (author, data["count"]))
142+
contributionCount = data["count"]
143+
self.log(" %s [%s]" % (author, contributionCount))
113144
for stamp, count in data["stamps"].items():
114145
datetimeStr = datetime.fromtimestamp(float(stamp)).strftime(
115146
"%Y-%m-%d/%H:%M:%S"
116147
)
117-
print(" -- %s [%s]" % (datetimeStr, count))
148+
self.log(" -- %s [%s]" % (datetimeStr, count))
149+
fileContributions[author] = contributionCount
150+
return fileContributions
118151

119152
def parseBlame(self, lines):
120153
lineDescriptions = []
@@ -124,7 +157,6 @@ def parseBlame(self, lines):
124157
newEntry = True
125158
for line in lines:
126159
if newEntry:
127-
# print("######################################")
128160
newEntry = False
129161
### commit hash extraction
130162
key = "commit"
@@ -148,10 +180,12 @@ def parseBlame(self, lines):
148180

149181
fileContributions = {}
150182
for d in lineDescriptions:
151-
author = d["author-mail"]
183+
author_mail = d["author-mail"][1:-1] ### strip leading and ending "<>"
184+
author_user = d["author"]
152185
timestamp = d["committer-time"]
153-
key = author
154-
dd = fileContributions.get(author, None)
186+
# key = (author_mail, author_user)
187+
key = author_mail
188+
dd = fileContributions.get(key, None)
155189
if dd:
156190
c = dd["count"]
157191
stamps = dd["stamps"]
@@ -176,6 +210,7 @@ def test():
176210
### define our input configuration (action) which normally comes from .yml configuration
177211
d = {
178212
"contributions_to_code": {
213+
"debug": True,
179214
"type": "git_file_contribution_action", ### type of action (also the name of the plugin _alias_ used!)
180215
"applies_to": [
181216
"*.md",
@@ -199,10 +234,15 @@ def test():
199234
init = action.initialize_()
200235
if init:
201236
### let us do our work
202-
data = action.gather_()
203-
### visualize and evaluate test data
204-
print(data)
205-
success = True
237+
contributors, scores = action.gather_()
238+
### visualize and finalize gathered data
239+
print("Result:")
240+
print("contributors:\n%s" % contributors)
241+
print("scores:\n%s" % scores)
242+
### evaluate test data
243+
if len(contributors) == len(scores):
244+
success = True
245+
### Done
206246
return success
207247

208248

libreselery/contribution_action_plugins/test_action.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def initialize_(self, action):
4343
Returns:
4444
bool: True if successfully initialized
4545
"""
46-
print(" > PLUGIN - INIT")
46+
self.log("INIT")
4747
return True
4848

4949
def gather_(self, cachedContributors=[]):
@@ -59,8 +59,9 @@ def gather_(self, cachedContributors=[]):
5959
Returns:
6060
tuple: (list of contributors, list of scores)
6161
"""
62-
print(" > PLUGIN - GATHER")
63-
contributors = ["kikass13", "otherUser"]
62+
self.log("GATHER")
63+
# contributors = [("nickfiege999@gmail.com", "kikass13"), ("randomEmail@random.rand", "otherUser")]
64+
contributors = ["nickfiege999@gmail.com", "randomEmail@random.rand"]
6465
scores = [1337.0, 500.0]
6566
return contributors, scores
6667

@@ -81,6 +82,7 @@ def test():
8182
### define our input configuration (action) which normally comes from .yml configuration
8283
d = {
8384
"test_action_id": {
85+
"debug": True,
8486
"type": "test_action", ### type of action (also the name of the plugin _alias_ used!)
8587
}
8688
}
@@ -92,10 +94,15 @@ def test():
9294
init = action.initialize_()
9395
if init:
9496
### let us do our work
95-
data = action.gather_()
96-
### visualize and evaluate test data
97-
print(data)
98-
success = True
97+
contributors, scores = action.gather_()
98+
### visualize and finalize gathered data
99+
print("Result:")
100+
print("contributors:\n%s" % contributors)
101+
print("scores:\n%s" % scores)
102+
### evaluate test data
103+
if len(contributors) == len(scores):
104+
success = True
105+
### Done
99106
return success
100107

101108

libreselery/contribution_distribution_engine_types.py

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,26 @@ def softmax(x):
2424

2525

2626
def applyLookupDict(LOOKUP_DICT, content, targetInst):
27-
for k, v in content.items():
28-
f = LOOKUP_DICT.get(k)
29-
if f:
27+
### apply mandatory parameters
28+
for k, f in LOOKUP_DICT["mandatory"].items():
29+
v = content.get(k, None)
30+
if v:
3031
obj = f(v)
3132
setattr(targetInst, k, obj)
33+
else:
34+
raise KeyError(
35+
"Configuration parameter %s was not found in given config" % k
36+
)
37+
### apply optional parameters
38+
for k, f in LOOKUP_DICT["optional"].items():
39+
expr, default = f
40+
v = content.get(k, None)
41+
if v:
42+
obj = expr(v)
43+
setattr(targetInst, k, obj)
44+
else:
45+
obj = default
46+
setattr(targetInst, k, obj)
3247

3348

3449
def simpleDictRepr(obj):
@@ -117,6 +132,7 @@ def __repr__(self):
117132
class ContributionActionPlugin(object):
118133
def __init__(self):
119134
super(ContributionActionPlugin, self).__init__()
135+
self.debug = False
120136

121137
@pluginlib.abstractmethod
122138
def initialize_(self, action):
@@ -126,6 +142,13 @@ def initialize_(self, action):
126142
def gather_(self, cachedContributors=[]):
127143
pass
128144

145+
def setDebug_(self, debug):
146+
self.debug = debug
147+
148+
def log(self, msg):
149+
if self.debug:
150+
print("\t[.] Plugin [%s]: '%s'" % (self._alias_, msg))
151+
129152

130153
class ContributionAction(object):
131154
def __init__(self, d):
@@ -150,6 +173,9 @@ def initialize_(self):
150173
self.plugin = plugins.action.get(
151174
pluginName
152175
)() ### plugins.<pluginlib.Parent>.<plugin_alias>
176+
### dirty little debug flag set for newly instanced plugin
177+
### this has to be dne in a better way but works for now
178+
self.plugin.setDebug_(self.debug)
153179
### initialize plugin
154180
pluginInitSuccess = self.plugin.initialize_(self)
155181
return pluginInitSuccess
@@ -194,12 +220,20 @@ def __repr__(self):
194220

195221

196222
DOMAIN_LOOKUP_TYPES = {
197-
"weight": float,
198-
"actions": lambda l: [ContributionAction(d) for d in l],
223+
"mandatory": {
224+
"weight": float,
225+
"actions": lambda l: [ContributionAction(d) for d in l],
226+
},
227+
"optional": {},
199228
}
200229

201230
ACTION_LOOKUP_TYPES = {
202-
"type": ContributionType,
203-
"applies_to": lambda l: [ContributionTarget(d) for d in l],
204-
"metrics": lambda l: [ContributionMetric(d) for d in l],
231+
"mandatory": {
232+
"type": ContributionType,
233+
},
234+
"optional": {
235+
"debug": (bool, False),
236+
"applies_to": (lambda l: [ContributionTarget(d) for d in l], []),
237+
"metrics": (lambda l: [ContributionMetric(d) for d in l], []),
238+
},
205239
}

libreselery/libreselery.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,14 +180,23 @@ def gather(self):
180180
toolingContributors = []
181181

182182
contributorData_scored = self.cde.gather_()
183-
print("1________________")
183+
print(
184+
"1______________________________________________________________________________"
185+
)
184186
print(contributorData_scored["gather"])
185-
print("2________________")
187+
print(
188+
"2______________________________________________________________________________"
189+
)
186190
domainContributors_weighted = self.cde.weight_(contributorData_scored)
187191
print(domainContributors_weighted["weight"])
188-
print("3________________")
192+
print(
193+
"3.1______________________________________________________________________________"
194+
)
189195
domainContributors_merged = self.cde.merge_(domainContributors_weighted)
190196
print(domainContributors_merged["merge"])
197+
print(
198+
"3.2______________________________________________________________________________"
199+
)
191200
print(domainContributors_merged["merge_norm"])
192201

193202
# projectUrl = git_utils.grabLocalProject(self.config.directory)

selery.yml

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,10 @@ contribution_domains:
3636
-
3737
test_action:
3838
type: test_action
39-
applies_to:
40-
- "*.md"
41-
- "docs/"
42-
metrics:
43-
-
44-
UNIFORM: ### every contributor is weighted equally if they touch the files defined
45-
degradation_type: linear ### could be exponential as well
46-
degradation_value: 1 ### gradient in days
4739
-
4840
test_action2:
49-
type: test_action
41+
debug: True
42+
type: git_file_contribution_action
5043
applies_to:
5144
- "*.md"
5245
- "docs/"
@@ -62,14 +55,6 @@ contribution_domains:
6255
-
6356
test_action:
6457
type: test_action
65-
applies_to:
66-
- "*.md"
67-
- "docs/"
68-
metrics:
69-
-
70-
UNIFORM: ### every contributor is weighted equally if they touch the files defined
71-
degradation_type: linear ### could be exponential as well
72-
degradation_value: 1 ### gradient in days
7358

7459

7560
############################################################################################

0 commit comments

Comments
 (0)