Skip to content

Commit 49d5160

Browse files
[16.0][FEAT] cloc Implementation
1 parent 3a889af commit 49d5160

5 files changed

Lines changed: 106 additions & 16 deletions

File tree

glodo_client/__manifest__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"version": "16.0.1.0.0",
88
"depends": ["base"],
99
"external_dependencies": {
10-
"python": ["cryptography"],
10+
"python": ["cryptography", "manifestoo_core"],
1111
},
1212
"license": "Other proprietary",
1313
}

glodo_client/controllers/main.py

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
from odoo.http import Controller, request, route
1818
from odoo.modules.registry import Registry
1919
from odoo.service.db import list_dbs
20-
from odoo.tools import cloc
2120

21+
from ..utils.cloc import CustomCloc
2222
from ..utils.crypto import (
2323
get_client_config,
2424
glodo_authenticated,
@@ -78,7 +78,7 @@ def info(self, **kwargs):
7878
7979
Response includes:
8080
- Instance-level information (Odoo version, etc.)
81-
- List of databases with CLOC and module info
81+
- List of databases with module info and CLOC breakdown
8282
"""
8383
# payload available via request.glodo_payload if needed
8484

@@ -139,27 +139,22 @@ def _get_database_info(self, db_name: str) -> dict:
139139
for m in modules
140140
]
141141

142+
try:
143+
cl = CustomCloc()
144+
cl.count_env(env)
145+
cloc_data = cl.summary()
146+
except Exception as e:
147+
cloc_data = {"error": str(e)}
148+
142149
db_info = {
143150
"name": db_name,
144151
"user_count": user_count,
145152
"expiration_date": expiration_date or None,
146153
"expiration_reason": expiration_reason or None,
147154
"installed_modules": module_list,
148-
"cloc": {},
155+
"cloc": cloc_data,
149156
}
150157

151-
# Try to get CLOC
152-
try:
153-
cl = cloc.Cloc()
154-
cl.count_customization(env)
155-
156-
db_info["cloc"] = {
157-
"output": cl.report(),
158-
"returncode": cl.code,
159-
}
160-
except Exception as e:
161-
db_info["cloc"] = {"error": str(e)}
162-
163158
return db_info
164159

165160
except Exception as e:

glodo_client/utils/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
from . import cloc
12
from . import crypto

glodo_client/utils/cloc.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import ast
2+
import os
3+
import pathlib
4+
5+
from manifestoo_core.core_addons import is_core_addon
6+
from manifestoo_core.odoo_series import OdooSeries, UnsupportedOdooSeries
7+
8+
from odoo import release
9+
from odoo.modules import get_module_path
10+
from odoo.modules.module import MANIFEST_NAMES
11+
from odoo.tools.cloc import DEFAULT_EXCLUDE, MAX_FILE_SIZE, VALID_EXTENSION, Cloc
12+
13+
DEFAULT_EXCLUDE_INCLUDE_TESTS = [
14+
p for p in DEFAULT_EXCLUDE if not p.startswith(("tests/", "static/tests/"))
15+
]
16+
17+
18+
class CustomCloc(Cloc):
19+
def summary(self):
20+
return {
21+
"code": dict(self.code),
22+
"errors": {m: dict(files) for m, files in self.errors.items()},
23+
}
24+
25+
def count_modules(self, env):
26+
try:
27+
major, minor = release.version_info[:2]
28+
series = OdooSeries(f"{major}.{minor}")
29+
except (ValueError, UnsupportedOdooSeries):
30+
series = None
31+
32+
domain = [("state", "=", "installed")]
33+
if env["ir.module.module"]._fields.get("imported"):
34+
domain.append(("imported", "=", False))
35+
module_list = env["ir.module.module"].search(domain).mapped("name")
36+
37+
for module_name in module_list:
38+
if series and is_core_addon(module_name, series):
39+
continue
40+
module_path = os.path.realpath(get_module_path(module_name))
41+
if module_path:
42+
self.count_path(module_path)
43+
44+
# Exact copy of odoo.tools.cloc.Cloc.count_path with DEFAULT_EXCLUDE → DEFAULT_EXCLUDE_INCLUDE_TESTS
45+
46+
# fmt: off
47+
# pylint: disable=broad-except,except-pass
48+
# ruff: noqa: E501
49+
def count_path(self, path, exclude=None):
50+
path = path.rstrip('/')
51+
exclude_list = []
52+
for i in MANIFEST_NAMES:
53+
manifest_path = os.path.join(path, i)
54+
try:
55+
with open(manifest_path, 'rb') as manifest:
56+
exclude_list.extend(DEFAULT_EXCLUDE_INCLUDE_TESTS)
57+
d = ast.literal_eval(manifest.read().decode('latin1'))
58+
for j in ['cloc_exclude', 'demo', 'demo_xml']:
59+
exclude_list.extend(d.get(j, []))
60+
break
61+
except Exception:
62+
pass
63+
if not exclude:
64+
exclude = set()
65+
for i in filter(None, exclude_list):
66+
assert '..' not in i, (
67+
f"Invalid exclusion path '{i}': '..' is not allowed. Use a normalized path."
68+
)
69+
exclude.update(str(p) for p in pathlib.Path(path).glob(i))
70+
71+
module_name = os.path.basename(path)
72+
self.book(module_name)
73+
for root, _dirs, files in os.walk(path):
74+
for file_name in files:
75+
file_path = os.path.join(root, file_name)
76+
77+
if file_path in exclude:
78+
continue
79+
80+
ext = os.path.splitext(file_path)[1].lower()
81+
if ext not in VALID_EXTENSION:
82+
continue
83+
84+
if os.path.getsize(file_path) > MAX_FILE_SIZE:
85+
self.book(module_name, file_path, (-1, "Max file size exceeded"))
86+
continue
87+
88+
with open(file_path, 'rb') as f:
89+
# Decode using latin1 to avoid error that may raise by decoding with utf8
90+
# The chars not correctly decoded in latin1 have no impact on how many lines will be counted
91+
content = f.read().decode('latin1')
92+
self.book(module_name, file_path, self.parse(content, ext))
93+
# fmt: on

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
cryptography
33
human_readable
44
lxml
5+
manifestoo_core
56
mock
67
oauthlib
78
phonenumbers

0 commit comments

Comments
 (0)