grimoirelab-chronicler 0.0.1rc2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- grimoirelab_chronicler-0.0.1rc2/AUTHORS +3 -0
- grimoirelab_chronicler-0.0.1rc2/NEWS +0 -0
- grimoirelab_chronicler-0.0.1rc2/PKG-INFO +86 -0
- grimoirelab_chronicler-0.0.1rc2/README.md +62 -0
- grimoirelab_chronicler-0.0.1rc2/chronicler/__init__.py +0 -0
- grimoirelab_chronicler-0.0.1rc2/chronicler/_version.py +2 -0
- grimoirelab_chronicler-0.0.1rc2/chronicler/chronicler.py +76 -0
- grimoirelab_chronicler-0.0.1rc2/chronicler/eventizer.py +159 -0
- grimoirelab_chronicler-0.0.1rc2/chronicler/events/core/__init__.py +21 -0
- grimoirelab_chronicler-0.0.1rc2/chronicler/events/core/git.py +141 -0
- grimoirelab_chronicler-0.0.1rc2/pyproject.toml +56 -0
|
File without changes
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: grimoirelab-chronicler
|
|
3
|
+
Version: 0.0.1rc2
|
|
4
|
+
Summary: Generator of GrimoireLab events using Perceval data.
|
|
5
|
+
Home-page: https://chaoss.github.io/grimoirelab/
|
|
6
|
+
License: GPL-3.0+
|
|
7
|
+
Keywords: software analytics,events,grimoirelab
|
|
8
|
+
Author: GrimoireLab Developers
|
|
9
|
+
Requires-Python: >=3.11,<4.0
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Information Technology
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Topic :: Software Development
|
|
18
|
+
Requires-Dist: click (>=8.1.7,<9.0.0)
|
|
19
|
+
Requires-Dist: cloudevents (>=1.10.1,<2.0.0)
|
|
20
|
+
Requires-Dist: coverage (>=7.2.3,<8.0.0)
|
|
21
|
+
Project-URL: Repository, https://github.com/chaoss/grimoirelab-chronicler
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# Chronicler
|
|
25
|
+
|
|
26
|
+
Tool and library to convert Perceval data into events.
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
$ chronicler --help
|
|
32
|
+
Usage: chronicler [OPTIONS] DATASOURCE
|
|
33
|
+
|
|
34
|
+
Generates GrimoireLab events from the items obtained by Perceval.
|
|
35
|
+
|
|
36
|
+
The chronicler is a command line tool and a library that converts items
|
|
37
|
+
generated by Perceval into events. The tool reads these events from the
|
|
38
|
+
standard input or from a given file.
|
|
39
|
+
|
|
40
|
+
To run it, you will have to give the type of items the chronicler is
|
|
41
|
+
receiving with DATASOURCE argument (e.g. git, github, gitlab).
|
|
42
|
+
|
|
43
|
+
Output is produced using JSON format. Use the option <json_line>
|
|
44
|
+
to generate JSON object per line.
|
|
45
|
+
|
|
46
|
+
Options:
|
|
47
|
+
--input FILENAME File with perceval items
|
|
48
|
+
--output FILENAME File where events will be written
|
|
49
|
+
--json-line Produce a JSON line for each output item
|
|
50
|
+
--version Show the version and exit.
|
|
51
|
+
--help Show this message and exit.
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
You can connect the **Chronicler** with **Perceval** to generate events.
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
perceval git --json-line https://example.com | chronicler git
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Installation
|
|
61
|
+
|
|
62
|
+
There are several methods to install this tool.
|
|
63
|
+
|
|
64
|
+
### pip
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
pip install grimoirelab-chronicler
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### poetry (source code)
|
|
71
|
+
|
|
72
|
+
We use [poetry](https://python-poetry.org/) for dependency management and
|
|
73
|
+
packaging. You can install **chronicler** and its dependencies on an
|
|
74
|
+
isolated environment provided by **poetry**.
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
poetry install
|
|
78
|
+
poetry shell
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Contributing
|
|
82
|
+
|
|
83
|
+
Chronicler is part of the GrimoireLab project. Please read its
|
|
84
|
+
[Contributing Guidelines](https://github.com/chaoss/grimoirelab/blob/main/CONTRIBUTING.md)
|
|
85
|
+
for more information.
|
|
86
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Chronicler
|
|
2
|
+
|
|
3
|
+
Tool and library to convert Perceval data into events.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
$ chronicler --help
|
|
9
|
+
Usage: chronicler [OPTIONS] DATASOURCE
|
|
10
|
+
|
|
11
|
+
Generates GrimoireLab events from the items obtained by Perceval.
|
|
12
|
+
|
|
13
|
+
The chronicler is a command line tool and a library that converts items
|
|
14
|
+
generated by Perceval into events. The tool reads these events from the
|
|
15
|
+
standard input or from a given file.
|
|
16
|
+
|
|
17
|
+
To run it, you will have to give the type of items the chronicler is
|
|
18
|
+
receiving with DATASOURCE argument (e.g. git, github, gitlab).
|
|
19
|
+
|
|
20
|
+
Output is produced using JSON format. Use the option <json_line>
|
|
21
|
+
to generate JSON object per line.
|
|
22
|
+
|
|
23
|
+
Options:
|
|
24
|
+
--input FILENAME File with perceval items
|
|
25
|
+
--output FILENAME File where events will be written
|
|
26
|
+
--json-line Produce a JSON line for each output item
|
|
27
|
+
--version Show the version and exit.
|
|
28
|
+
--help Show this message and exit.
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
You can connect the **Chronicler** with **Perceval** to generate events.
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
perceval git --json-line https://example.com | chronicler git
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
There are several methods to install this tool.
|
|
40
|
+
|
|
41
|
+
### pip
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
pip install grimoirelab-chronicler
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### poetry (source code)
|
|
48
|
+
|
|
49
|
+
We use [poetry](https://python-poetry.org/) for dependency management and
|
|
50
|
+
packaging. You can install **chronicler** and its dependencies on an
|
|
51
|
+
isolated environment provided by **poetry**.
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
poetry install
|
|
55
|
+
poetry shell
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Contributing
|
|
59
|
+
|
|
60
|
+
Chronicler is part of the GrimoireLab project. Please read its
|
|
61
|
+
[Contributing Guidelines](https://github.com/chaoss/grimoirelab/blob/main/CONTRIBUTING.md)
|
|
62
|
+
for more information.
|
|
File without changes
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) GrimoireLab Developers
|
|
4
|
+
#
|
|
5
|
+
# This program is free software; you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
|
7
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import sys
|
|
21
|
+
|
|
22
|
+
import click
|
|
23
|
+
import cloudevents.conversion
|
|
24
|
+
|
|
25
|
+
from ._version import __version__
|
|
26
|
+
from .eventizer import eventize
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@click.command
|
|
30
|
+
@click.option(
|
|
31
|
+
"--input",
|
|
32
|
+
help="File with perceval items",
|
|
33
|
+
type=click.File("r"),
|
|
34
|
+
default=sys.stdin
|
|
35
|
+
)
|
|
36
|
+
@click.option(
|
|
37
|
+
"--output",
|
|
38
|
+
help="File where events will be written",
|
|
39
|
+
type=click.File("w"),
|
|
40
|
+
default=sys.stdout
|
|
41
|
+
)
|
|
42
|
+
@click.option(
|
|
43
|
+
"--json-line",
|
|
44
|
+
help="Produce a JSON line for each output item",
|
|
45
|
+
is_flag=True,
|
|
46
|
+
show_default=True,
|
|
47
|
+
default=False
|
|
48
|
+
)
|
|
49
|
+
@click.argument('datasource')
|
|
50
|
+
@click.version_option(__version__, message="%(prog)s %(version)s")
|
|
51
|
+
def chronicler(datasource, input, output, json_line):
|
|
52
|
+
"""Generates GrimoireLab events from the items fetched by Perceval.
|
|
53
|
+
|
|
54
|
+
The chronicler is a command line tool and a library that converts
|
|
55
|
+
items generated by Perceval into events. The tool reads
|
|
56
|
+
these events from the standard input or from a given file.
|
|
57
|
+
|
|
58
|
+
To run it, you will have to give the type of items the chronicler
|
|
59
|
+
is receiving with DATASOURCE argument (e.g. git, github, gitlab).
|
|
60
|
+
|
|
61
|
+
Output is produced using JSON format. Use the option <json_line>
|
|
62
|
+
to generate JSON object per line.
|
|
63
|
+
"""
|
|
64
|
+
def _read_input(input):
|
|
65
|
+
for line in input:
|
|
66
|
+
yield json.loads(line)
|
|
67
|
+
|
|
68
|
+
for item in eventize(datasource, _read_input(input)):
|
|
69
|
+
if json_line:
|
|
70
|
+
obj = json.dumps(cloudevents.conversion.to_dict(item),
|
|
71
|
+
separators=(',', ':'), sort_keys=True)
|
|
72
|
+
else:
|
|
73
|
+
obj = json.dumps(cloudevents.conversion.to_dict(item),
|
|
74
|
+
indent=4, sort_keys=True)
|
|
75
|
+
output.write(obj)
|
|
76
|
+
output.write('\n')
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) GrimoireLab Developers
|
|
4
|
+
#
|
|
5
|
+
# This program is free software; you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
|
7
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
import importlib
|
|
20
|
+
import pkgutil
|
|
21
|
+
import os
|
|
22
|
+
|
|
23
|
+
from collections.abc import Iterator, Generator
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
from cloudevents.http import CloudEvent
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Eventizer:
|
|
30
|
+
"""Abstract class to eventize data.
|
|
31
|
+
|
|
32
|
+
Events will be generated calling to the `eventize` method.
|
|
33
|
+
This method will take items produced by `perceval` and
|
|
34
|
+
will convert them in GrimoireLab events.
|
|
35
|
+
|
|
36
|
+
The class can only process items of one type. In other words,
|
|
37
|
+
`git` items can't be mixed with `github` items. However,
|
|
38
|
+
the same eventizer can process different categories of
|
|
39
|
+
the same type. A `github` eventizer would process `issues`,
|
|
40
|
+
`pull requests`, etc.
|
|
41
|
+
|
|
42
|
+
To create your own eventizer, sub-class this class by implementing
|
|
43
|
+
the method `eventize_item`. This method should have the
|
|
44
|
+
logic to given a perceval item, produce the events associated to
|
|
45
|
+
that item.
|
|
46
|
+
"""
|
|
47
|
+
def eventize(self, raw_items: Iterator[dict[str, Any]]) -> Generator[CloudEvent]:
|
|
48
|
+
"""Generate GrimoireLab events.
|
|
49
|
+
|
|
50
|
+
Produce events from the given list of perceval items.
|
|
51
|
+
The items must be of the same type.
|
|
52
|
+
"""
|
|
53
|
+
for raw_item in raw_items:
|
|
54
|
+
yield from self.eventize_item(raw_item)
|
|
55
|
+
|
|
56
|
+
def eventize_item(self, raw_item: dict[str, Any]) -> list[CloudEvent]:
|
|
57
|
+
"""Eventize a item."""
|
|
58
|
+
raise NotImplementedError
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def eventize(name: str, raw_items: Iterator[dict[str, Any]]) -> Generator[CloudEvent]:
|
|
62
|
+
"""Eventize data of a given type.
|
|
63
|
+
|
|
64
|
+
Handy function to produce events from a set of perceval items
|
|
65
|
+
of given type.
|
|
66
|
+
"""
|
|
67
|
+
top_package_name = os.environ.get('CHRONICLER_EVENTIZERS',
|
|
68
|
+
'chronicler.events')
|
|
69
|
+
|
|
70
|
+
eventizers = _find_eventizers(top_package_name)
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
eventizer = eventizers[name]()
|
|
74
|
+
except KeyError:
|
|
75
|
+
raise ValueError(f"Unknown eventizer '{name}'")
|
|
76
|
+
|
|
77
|
+
yield from eventizer.eventize(raw_items)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _find_eventizers(top_package_name: str) -> dict[str, Eventizer]:
|
|
81
|
+
"""Find available eventizers.
|
|
82
|
+
|
|
83
|
+
Look for the `Eventizer` classes under `top_package`
|
|
84
|
+
and its sub-packages. When `top_package` defines a namespace,
|
|
85
|
+
classes under that same namespace will be found too.
|
|
86
|
+
|
|
87
|
+
:param top_package: package storing eventizer classes
|
|
88
|
+
|
|
89
|
+
:returns: a dict with `Eventizer`
|
|
90
|
+
"""
|
|
91
|
+
top_package = importlib.import_module(top_package_name)
|
|
92
|
+
|
|
93
|
+
candidates = pkgutil.walk_packages(top_package.__path__,
|
|
94
|
+
prefix=top_package.__name__ + '.')
|
|
95
|
+
|
|
96
|
+
modules = [name for _, name, is_pkg in candidates if not is_pkg]
|
|
97
|
+
|
|
98
|
+
return _import_eventizers(modules)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _import_eventizers(modules):
|
|
102
|
+
for module in modules:
|
|
103
|
+
importlib.import_module(module)
|
|
104
|
+
|
|
105
|
+
klasses = _find_classes(Eventizer, modules)
|
|
106
|
+
|
|
107
|
+
eventizers = {name: kls for name, kls in klasses}
|
|
108
|
+
|
|
109
|
+
return eventizers
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _find_classes(parent, modules):
|
|
113
|
+
parents = parent.__subclasses__()
|
|
114
|
+
|
|
115
|
+
while parents:
|
|
116
|
+
kls = parents.pop()
|
|
117
|
+
|
|
118
|
+
m = kls.__module__
|
|
119
|
+
|
|
120
|
+
if m not in modules:
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
name = m.split('.')[-1]
|
|
124
|
+
parents.extend(kls.__subclasses__())
|
|
125
|
+
|
|
126
|
+
yield name, kls
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def uuid(*args):
|
|
130
|
+
"""Generate a UUID based on the given parameters.
|
|
131
|
+
|
|
132
|
+
The UUID will be the SHA1 of the concatenation of the values
|
|
133
|
+
from the list. The separator between these values is ':'.
|
|
134
|
+
Each value must be a non-empty string, otherwise, the function
|
|
135
|
+
will raise an exception.
|
|
136
|
+
|
|
137
|
+
:param *args: list of arguments used to generate the UUID
|
|
138
|
+
|
|
139
|
+
:returns: a universal unique identifier
|
|
140
|
+
|
|
141
|
+
:raises ValueError: when anyone of the values is not a string,
|
|
142
|
+
is empty or `None`.
|
|
143
|
+
"""
|
|
144
|
+
import hashlib
|
|
145
|
+
|
|
146
|
+
def check_value(v):
|
|
147
|
+
if not isinstance(v, str):
|
|
148
|
+
raise ValueError("%s value is not a string instance" % str(v))
|
|
149
|
+
elif not v:
|
|
150
|
+
raise ValueError("value cannot be None or empty")
|
|
151
|
+
else:
|
|
152
|
+
return v
|
|
153
|
+
|
|
154
|
+
s = ':'.join(map(check_value, args))
|
|
155
|
+
|
|
156
|
+
sha1 = hashlib.sha1(s.encode('utf-8', errors='surrogateescape'))
|
|
157
|
+
uuid_sha1 = sha1.hexdigest()
|
|
158
|
+
|
|
159
|
+
return uuid_sha1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) GrimoireLab Developers
|
|
4
|
+
#
|
|
5
|
+
# This program is free software; you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
|
7
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
from ..._version import __version__
|
|
20
|
+
|
|
21
|
+
__version__ = __version__
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) GrimoireLab Developers
|
|
4
|
+
#
|
|
5
|
+
# This program is free software; you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
|
7
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
from cloudevents.http import CloudEvent
|
|
22
|
+
|
|
23
|
+
from ...eventizer import Eventizer, uuid
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
GIT_EVENT_COMMIT = "org.grimoirelab.events.git.commit"
|
|
27
|
+
GIT_EVENT_MERGE_COMMIT = "org.grimoirelab.events.git.merge"
|
|
28
|
+
GIT_EVENT_ACTION_ADDED = "org.grimoirelab.events.git.file.added"
|
|
29
|
+
GIT_EVENT_ACTION_MODIFIED = "org.grimoirelab.events.git.file.modified"
|
|
30
|
+
GIT_EVENT_ACTION_DELETED = "org.grimoirelab.events.git.file.deleted"
|
|
31
|
+
GIT_EVENT_ACTION_REPLACED = "org.grimoirelab.events.git.file.replaced"
|
|
32
|
+
GIT_EVENT_ACTION_COPIED = "org.grimoirelab.events.git.file.copied"
|
|
33
|
+
GIT_EVENT_ACTION_TYPE_CHANGED = "org.grimoirelab.events.git.file.typechanged"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class GitEventizer(Eventizer):
|
|
37
|
+
"""Eventize git commits"""
|
|
38
|
+
|
|
39
|
+
def eventize_item(self, raw_item: dict[str, Any]) -> list[dict[str, Any]]:
|
|
40
|
+
events = []
|
|
41
|
+
|
|
42
|
+
item_uuid = raw_item.get('uuid', None)
|
|
43
|
+
|
|
44
|
+
if not item_uuid:
|
|
45
|
+
raise ValueError("'uuid' attribute not found on item.")
|
|
46
|
+
if raw_item['backend_name'].lower() != 'git':
|
|
47
|
+
raise ValueError(f"Item {item_uuid} is not a 'git' item.")
|
|
48
|
+
if raw_item['category'] != 'commit':
|
|
49
|
+
raise ValueError(f"Invalid category '{raw_item['category']}' for '{item_uuid}' item.")
|
|
50
|
+
|
|
51
|
+
if 'Merge' in raw_item['data']:
|
|
52
|
+
event_type = GIT_EVENT_MERGE_COMMIT
|
|
53
|
+
else:
|
|
54
|
+
event_type = GIT_EVENT_COMMIT
|
|
55
|
+
|
|
56
|
+
attributes = {
|
|
57
|
+
"id": item_uuid,
|
|
58
|
+
"type": event_type,
|
|
59
|
+
"source": raw_item['origin'],
|
|
60
|
+
"time": raw_item['updated_on'],
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
event = CloudEvent(attributes, raw_item['data'])
|
|
64
|
+
events.append(event)
|
|
65
|
+
|
|
66
|
+
action_events = self._eventize_commit_actions(event,
|
|
67
|
+
raw_item['data']['files'])
|
|
68
|
+
|
|
69
|
+
events.extend(action_events)
|
|
70
|
+
|
|
71
|
+
return events
|
|
72
|
+
|
|
73
|
+
def _eventize_commit_actions(self, parent_event: CloudEvent, raw_files_data):
|
|
74
|
+
|
|
75
|
+
events = []
|
|
76
|
+
|
|
77
|
+
for file_data in raw_files_data:
|
|
78
|
+
actions = file_data.get('action', None)
|
|
79
|
+
|
|
80
|
+
if not actions and parent_event['type'] == GIT_EVENT_COMMIT:
|
|
81
|
+
raise ValueError(f"No action for commit event {parent_event['id']}")
|
|
82
|
+
elif not actions:
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
if parent_event['type'] == GIT_EVENT_COMMIT:
|
|
86
|
+
action_event = self._process_action(parent_event['source'],
|
|
87
|
+
parent_event['time'],
|
|
88
|
+
parent_event['id'], actions, file_data)
|
|
89
|
+
events.append(action_event)
|
|
90
|
+
else:
|
|
91
|
+
prev_merge_action = None
|
|
92
|
+
for action in actions:
|
|
93
|
+
if action == prev_merge_action:
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
action_event = self._process_action(parent_event['source'],
|
|
97
|
+
parent_event['time'],
|
|
98
|
+
parent_event['id'], action, file_data)
|
|
99
|
+
events.append(action_event)
|
|
100
|
+
prev_merge_action = action
|
|
101
|
+
return events
|
|
102
|
+
|
|
103
|
+
def _process_action(self, source, time, event_uuid, action, file_data):
|
|
104
|
+
if action == 'A':
|
|
105
|
+
event_type = GIT_EVENT_ACTION_ADDED
|
|
106
|
+
elif action == 'M':
|
|
107
|
+
event_type = GIT_EVENT_ACTION_MODIFIED
|
|
108
|
+
elif action == 'D':
|
|
109
|
+
event_type = GIT_EVENT_ACTION_DELETED
|
|
110
|
+
elif action.startswith('R'):
|
|
111
|
+
event_type = GIT_EVENT_ACTION_REPLACED
|
|
112
|
+
elif action.startswith('C'):
|
|
113
|
+
event_type = GIT_EVENT_ACTION_COPIED
|
|
114
|
+
elif action.startswith('T'):
|
|
115
|
+
event_type = GIT_EVENT_ACTION_TYPE_CHANGED
|
|
116
|
+
else:
|
|
117
|
+
raise ValueError(f"No valid action: {action}")
|
|
118
|
+
|
|
119
|
+
event_id = uuid(event_uuid, file_data['file'], action)
|
|
120
|
+
|
|
121
|
+
data = {
|
|
122
|
+
"filename": file_data['file'],
|
|
123
|
+
"modes": file_data['modes'],
|
|
124
|
+
"indexes": file_data['indexes'],
|
|
125
|
+
"similarity": action[1:] if action in ('R', 'C') else None,
|
|
126
|
+
"new_filename": file_data.get('newfile', None),
|
|
127
|
+
"added_lines": file_data.get('added', None),
|
|
128
|
+
"deleted_lines": file_data.get('removed', None)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
attributes = {
|
|
132
|
+
"id": event_id,
|
|
133
|
+
"linked_event": event_uuid,
|
|
134
|
+
"type": event_type,
|
|
135
|
+
"source": source,
|
|
136
|
+
"time": time,
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
event = CloudEvent(attributes, data)
|
|
140
|
+
|
|
141
|
+
return event
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "grimoirelab-chronicler"
|
|
3
|
+
version = "0.0.1-rc.2"
|
|
4
|
+
description = "Generator of GrimoireLab events using Perceval data."
|
|
5
|
+
authors = [
|
|
6
|
+
"GrimoireLab Developers"
|
|
7
|
+
]
|
|
8
|
+
license = "GPL-3.0+"
|
|
9
|
+
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
|
|
12
|
+
homepage = "https://chaoss.github.io/grimoirelab/"
|
|
13
|
+
repository = "https://github.com/chaoss/grimoirelab-chronicler"
|
|
14
|
+
|
|
15
|
+
keywords = [
|
|
16
|
+
"software analytics",
|
|
17
|
+
"events",
|
|
18
|
+
"grimoirelab"
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Development Status :: 3 - Alpha",
|
|
23
|
+
"Intended Audience :: Developers",
|
|
24
|
+
"Intended Audience :: Information Technology",
|
|
25
|
+
"Intended Audience :: Science/Research",
|
|
26
|
+
"Topic :: Software Development",
|
|
27
|
+
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
|
28
|
+
"Programming Language :: Python :: 3"
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
packages = [
|
|
32
|
+
{ include = "chronicler"},
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
include = [
|
|
36
|
+
{ path = "AUTHORS", format = "sdist" },
|
|
37
|
+
{ path = "NEWS", format = "sdist" },
|
|
38
|
+
{ path = "README.md", format = "sdist" },
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[tool.poetry.scripts]
|
|
42
|
+
chronicler = "chronicler.chronicler:chronicler"
|
|
43
|
+
|
|
44
|
+
[tool.poetry.dependencies]
|
|
45
|
+
python = "^3.11"
|
|
46
|
+
|
|
47
|
+
click = "^8.1.7"
|
|
48
|
+
cloudevents = "^1.10.1"
|
|
49
|
+
coverage = "^7.2.3"
|
|
50
|
+
|
|
51
|
+
[tool.poetry.group.dev.dependencies]
|
|
52
|
+
flake8 = "^7.1.1"
|
|
53
|
+
|
|
54
|
+
[build-system]
|
|
55
|
+
requires = ["poetry-core"]
|
|
56
|
+
build-backend = "poetry.core.masonry.api"
|