4
4
import os
5
5
import shlex
6
6
import tempfile
7
+ from collections .abc import Mapping , Sequence
8
+ from pathlib import Path , PurePath
9
+ from typing import AnyStr , cast
10
+ from urllib .parse import ParseResult , urlparse , urlunparse
7
11
8
12
import requests
9
13
import yaml
10
14
11
15
CONFIG_DATA = None
12
16
CONFIG_SOURCE = None
13
17
18
+ JsonValue = (
19
+ None | bool | int | float | str | Sequence ["JsonValue" ] | Mapping [str , "JsonValue" ]
20
+ )
21
+ JsonObject = dict [str , JsonValue ]
22
+
14
23
15
24
def init_config (workspace = None ):
16
25
global CONFIG_DATA , CONFIG_SOURCE
@@ -72,7 +81,7 @@ def init_config(workspace=None):
72
81
)
73
82
# manage EXTENDS in configuration
74
83
if "EXTENDS" in runtime_config :
75
- combined_config = {}
84
+ combined_config : JsonObject = {}
76
85
CONFIG_SOURCE = combine_config (
77
86
workspace , runtime_config , combined_config , CONFIG_SOURCE
78
87
)
@@ -82,22 +91,32 @@ def init_config(workspace=None):
82
91
set_config (runtime_config )
83
92
84
93
85
- def combine_config (workspace , config , combined_config , config_source ):
86
- extends = config ["EXTENDS" ]
94
+ def combine_config (
95
+ workspace : str | None ,
96
+ config : JsonObject ,
97
+ combined_config : JsonObject ,
98
+ config_source : str ,
99
+ child_uri : ParseResult | None = None ,
100
+ ) -> str :
101
+ workspace_path = Path (workspace ) if workspace else None
102
+ parsed_uri : ParseResult | None = None
103
+ extends = cast (str | Sequence [str ], config ["EXTENDS" ])
87
104
if isinstance (extends , str ):
88
105
extends = extends .split ("," )
89
106
for extends_item in extends :
90
107
if extends_item .startswith ("http" ):
91
- r = requests .get (extends_item , allow_redirects = True )
92
- assert (
93
- r .status_code == 200
94
- ), f"Unable to retrieve EXTENDS config file { extends_item } "
95
- extends_config_data = yaml .safe_load (r .content )
108
+ parsed_uri = urlparse (extends_item )
109
+ extends_config_data = download_config (extends_item )
96
110
else :
97
- with open (
98
- workspace + os .path .sep + extends_item , "r" , encoding = "utf-8"
99
- ) as f :
100
- extends_config_data = yaml .safe_load (f )
111
+ path = PurePath (extends_item )
112
+ if child_uri :
113
+ parsed_uri = resolve_uri (child_uri , path )
114
+ uri = urlunparse (parsed_uri )
115
+ extends_config_data = download_config (uri )
116
+ else :
117
+ resolved_path = workspace_path / path if workspace_path else Path (path )
118
+ with resolved_path .open ("r" , encoding = "utf-8" ) as f :
119
+ extends_config_data = yaml .safe_load (f )
101
120
combined_config .update (extends_config_data )
102
121
config_source += f"\n [config] - extends from: { extends_item } "
103
122
if "EXTENDS" in extends_config_data :
@@ -106,11 +125,41 @@ def combine_config(workspace, config, combined_config, config_source):
106
125
extends_config_data ,
107
126
combined_config ,
108
127
config_source ,
128
+ parsed_uri ,
109
129
)
110
130
combined_config .update (config )
111
131
return config_source
112
132
113
133
134
+ def download_config (uri : AnyStr ) -> JsonObject :
135
+ r = requests .get (uri , allow_redirects = True )
136
+ assert r .status_code == 200 , f"Unable to retrieve EXTENDS config file { uri !r} "
137
+ return yaml .safe_load (r .content )
138
+
139
+
140
+ def resolve_uri (child_uri : ParseResult , relative_config_path : PurePath ) -> ParseResult :
141
+ match child_uri .netloc :
142
+ case "cdn.jsdelivr.net" | "git.launchpad.net" :
143
+ repo_root_index = 3
144
+ case "code.rhodecode.com" | "git.savannah.gnu.org" | "raw.githubusercontent.com" | "repo.or.cz" :
145
+ repo_root_index = 4
146
+ case "bitbucket.org" | "git.sr.ht" | "gitee.com" | "pagure.io" :
147
+ repo_root_index = 5
148
+ case "codeberg.org" | "gitea.com" | "gitlab.com" | "huggingface.co" | "p.phcdn.net" | "sourceforge.net" :
149
+ repo_root_index = 6
150
+ case _:
151
+ message = (
152
+ f"Unsupported Git repo hosting service: { child_uri .netloc } . "
153
+ "Request support be added to MegaLinter, or use absolute URLs "
154
+ "with EXTENDS in inherited configs rather than relative paths."
155
+ )
156
+ raise ValueError (message )
157
+ child_path = PurePath (child_uri .path )
158
+ repo_root_path = child_path .parts [:repo_root_index ]
159
+ path = PurePath (* repo_root_path , str (relative_config_path ))
160
+ return child_uri ._replace (path = str (path ))
161
+
162
+
114
163
def get_config ():
115
164
global CONFIG_DATA
116
165
if CONFIG_DATA is not None :
0 commit comments