44import os
55import shlex
66import 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
711
812import requests
913import yaml
1014
1115CONFIG_DATA = None
1216CONFIG_SOURCE = None
1317
18+ JsonValue = (
19+ None | bool | int | float | str | Sequence ["JsonValue" ] | Mapping [str , "JsonValue" ]
20+ )
21+ JsonObject = dict [str , JsonValue ]
22+
1423
1524def init_config (workspace = None ):
1625 global CONFIG_DATA , CONFIG_SOURCE
@@ -72,7 +81,7 @@ def init_config(workspace=None):
7281 )
7382 # manage EXTENDS in configuration
7483 if "EXTENDS" in runtime_config :
75- combined_config = {}
84+ combined_config : JsonObject = {}
7685 CONFIG_SOURCE = combine_config (
7786 workspace , runtime_config , combined_config , CONFIG_SOURCE
7887 )
@@ -82,22 +91,32 @@ def init_config(workspace=None):
8291 set_config (runtime_config )
8392
8493
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" ])
87104 if isinstance (extends , str ):
88105 extends = extends .split ("," )
89106 for extends_item in extends :
90107 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 )
96110 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 )
101120 combined_config .update (extends_config_data )
102121 config_source += f"\n [config] - extends from: { extends_item } "
103122 if "EXTENDS" in extends_config_data :
@@ -106,11 +125,41 @@ def combine_config(workspace, config, combined_config, config_source):
106125 extends_config_data ,
107126 combined_config ,
108127 config_source ,
128+ parsed_uri ,
109129 )
110130 combined_config .update (config )
111131 return config_source
112132
113133
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+
114163def get_config ():
115164 global CONFIG_DATA
116165 if CONFIG_DATA is not None :
0 commit comments