Skip to content

Commit d1e5357

Browse files
Merge pull request #232 from kingthorin/sorting
sort tech files - add sorting and duplication checks
2 parents fa9b08e + 8b48f2d commit d1e5357

File tree

30 files changed

+30284
-30158
lines changed

30 files changed

+30284
-30158
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import json
2+
import pathlib
3+
from typing import Final, Any
4+
5+
6+
class DuplicateKeyException(Exception):
7+
def __init__(self, msg: str):
8+
super().__init__(msg)
9+
10+
11+
class OrderingException(Exception):
12+
def __init__(self, msg: str):
13+
super().__init__(msg)
14+
15+
16+
class InvalidStructureException(Exception):
17+
def __init__(self, msg: str):
18+
super().__init__(msg)
19+
20+
21+
class OrderValidator:
22+
def __init__(self):
23+
self._SOURCE_DIR: Final[str] = "src"
24+
self._TECH_DIR: Final[str] = "technologies"
25+
self._FULL_TECH_DIR: Final[pathlib.Path] = pathlib.Path(self._SOURCE_DIR).joinpath(self._TECH_DIR)
26+
27+
def validate(self) -> None:
28+
if not self._FULL_TECH_DIR.is_dir():
29+
raise InvalidStructureException(f"{self._FULL_TECH_DIR} is not a valid directory")
30+
for tech_file in sorted(self._FULL_TECH_DIR.iterdir()):
31+
if not tech_file.name.endswith(".json"):
32+
continue
33+
self._check_ordering(tech_file)
34+
35+
def _check_ordering(self, tech_file: pathlib.Path) -> None:
36+
with tech_file.open("r", encoding="utf8") as f:
37+
data: dict = json.load(f, object_pairs_hook=self._duplicate_key_validator)
38+
if not isinstance(data, dict):
39+
raise InvalidStructureException(f"{tech_file.name} root must be an object")
40+
keys: list[str] = list(data.keys())
41+
sorted_keys: list[str] = sorted(keys, key=lambda x: x.lower())
42+
if keys != sorted_keys:
43+
mismatches: list[str] = [
44+
f"Found '{actual}' expected '{expected}'"
45+
for actual, expected in zip(keys, sorted_keys)
46+
if actual != expected
47+
]
48+
raise OrderingException(
49+
f"{tech_file.name} entries are not ordered alphabetically by keys:\n " +
50+
"\n ".join(mismatches)
51+
)
52+
53+
@classmethod
54+
def _duplicate_key_validator(cls, pairs: list[tuple[str, Any]]) -> dict[str, Any]:
55+
result: dict[str, Any] = {}
56+
for key, value in pairs:
57+
if key in result:
58+
raise DuplicateKeyException(f"Duplicate key found: '{key}'")
59+
result[key] = value
60+
return result
61+
62+
63+
if __name__ == '__main__':
64+
OrderValidator().validate()

.github/workflows/validate.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,24 @@ jobs:
1818
- name: run structure validator
1919
run: python3 .github/workflows/scripts/structure_validator.py
2020

21+
validate_order_duplication:
22+
runs-on: ubuntu-22.04
23+
needs: validate_structure
24+
strategy:
25+
matrix:
26+
python-version: [ "3.12" ]
27+
steps:
28+
- name: checkout repository
29+
uses: actions/checkout@v4
30+
31+
- name: set up Python ${{ matrix.python-version }}
32+
uses: actions/setup-python@v5
33+
with:
34+
python-version: ${{ matrix.python-version }}
35+
36+
- name: run order validator
37+
run: python3 .github/workflows/scripts/order_validator.py src/technologies
38+
2139
validate_schema:
2240
runs-on: ubuntu-24.04
2341
needs: validate_structure

scripts/fix_order.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import json
2+
import pathlib
3+
from typing import Final, Any
4+
5+
6+
class DuplicateKeyException(Exception):
7+
def __init__(self, msg: str):
8+
super().__init__(msg)
9+
10+
11+
class OrderFixer:
12+
def __init__(self):
13+
self._SOURCE_DIR: Final[str] = "src"
14+
self._TECH_DIR: Final[str] = "technologies"
15+
self._FULL_TECH_DIR: Final[pathlib.Path] = pathlib.Path(self._SOURCE_DIR).joinpath(self._TECH_DIR)
16+
17+
def fix(self) -> None:
18+
for tech_file in sorted(self._FULL_TECH_DIR.iterdir()):
19+
if not tech_file.name.endswith(".json"):
20+
continue
21+
with tech_file.open("r", encoding="utf8") as f:
22+
data: dict = json.load(f, object_pairs_hook=self._duplicate_key_validator)
23+
sorted_data: dict[str, Any] = {
24+
key: data[key] for key in sorted(data.keys(), key=lambda x: x.lower())
25+
}
26+
with tech_file.open("w", encoding="utf8") as f:
27+
json.dump(sorted_data, f, indent=2, ensure_ascii=False)
28+
f.write("\n")
29+
30+
@classmethod
31+
def _duplicate_key_validator(cls, pairs: list[tuple[str, Any]]) -> dict[str, Any]:
32+
result: dict[str, Any] = {}
33+
for key, value in pairs:
34+
if key in result:
35+
raise DuplicateKeyException(f"Duplicate key found: '{key}'")
36+
result[key] = value
37+
return result
38+
39+
40+
if __name__ == '__main__':
41+
OrderFixer().fix()

src/technologies/_.json

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,27 @@
292292
],
293293
"website": "https://www.5centscdn.net"
294294
},
295+
"6sense": {
296+
"cats": [
297+
32,
298+
76
299+
],
300+
"description": "6sense is a B2B predictive intelligence platform for marketing and sales.",
301+
"headers": {
302+
"Content-Security-Policy": "\\.6sc\\.co/"
303+
},
304+
"icon": "6sense.svg",
305+
"pricing": [
306+
"poa",
307+
"high",
308+
"recurring"
309+
],
310+
"saas": true,
311+
"scriptSrc": [
312+
"\\.6sc\\.co/"
313+
],
314+
"website": "https://6sense.com"
315+
},
295316
"6Valley eCommerce CMS": {
296317
"cats": [
297318
1,
@@ -310,26 +331,18 @@
310331
],
311332
"website": "https://6valley.app"
312333
},
313-
"6sense": {
334+
"7moor": {
314335
"cats": [
315336
32,
316-
76
337+
53
317338
],
318-
"description": "6sense is a B2B predictive intelligence platform for marketing and sales.",
319-
"headers": {
320-
"Content-Security-Policy": "\\.6sc\\.co/"
339+
"description": "7moor is an integrated customer service marketing solution that combines communication, customer support, and marketing tools.",
340+
"icon": "7moor.svg",
341+
"js": {
342+
"moor7Source": ""
321343
},
322-
"icon": "6sense.svg",
323-
"pricing": [
324-
"poa",
325-
"high",
326-
"recurring"
327-
],
328344
"saas": true,
329-
"scriptSrc": [
330-
"\\.6sc\\.co/"
331-
],
332-
"website": "https://6sense.com"
345+
"website": "https://www.7moor.com"
333346
},
334347
"7Shifts": {
335348
"cats": [
@@ -348,19 +361,6 @@
348361
"saas": true,
349362
"website": "https://www.7shifts.com"
350363
},
351-
"7moor": {
352-
"cats": [
353-
32,
354-
53
355-
],
356-
"description": "7moor is an integrated customer service marketing solution that combines communication, customer support, and marketing tools.",
357-
"icon": "7moor.svg",
358-
"js": {
359-
"moor7Source": ""
360-
},
361-
"saas": true,
362-
"website": "https://www.7moor.com"
363-
},
364364
"8base": {
365365
"cats": [
366366
3,
@@ -467,4 +467,4 @@
467467
],
468468
"website": "https://hyperscript.org"
469469
}
470-
}
470+
}

0 commit comments

Comments
 (0)