55from dataclasses import dataclass , field
66from pathlib import Path , PurePath
77from typing import cast , Callable , Dict , Set , Generic , Optional , \
8- TypeVar , Tuple , Iterator , Sequence , List , Union
8+ TypeVar , Tuple , Iterator , Sequence , List , Union , \
9+ Match
910from ..flutter import checked
1011from ..types import Diagnostic , Page , EmbeddedRstParser , SerializableType , ProjectConfig
1112
1415logger = logging .getLogger (__name__ )
1516
1617
17- def substitute_text (text : str , replacements : Dict [str , str ]) -> str :
18- return PAT_SUBSTITUTION .sub (lambda match : replacements .get (match .group (1 ), '' ), text )
18+ def substitute_text (text : str ,
19+ replacements : Dict [str , str ],
20+ diagnostics : List [Diagnostic ]) -> str :
21+ def substitute (match : Match [str ]) -> str :
22+ try :
23+ return replacements [match .group (1 )]
24+ except KeyError :
25+ diagnostics .append (Diagnostic .warning (
26+ f'Unknown substitution: "{ match .group (1 )} ". ' +
27+ 'You may intend this substitution to be empty' , 1 ))
28+ return ''
1929
30+ return PAT_SUBSTITUTION .sub (substitute , text )
2031
21- def substitute (obj : _T , replacements : Dict [str , str ]) -> _T :
32+
33+ def substitute (obj : _T ,
34+ replacements : Dict [str , str ],
35+ diagnostics : List [Diagnostic ]) -> _T :
2236 if isinstance (obj , str ):
23- return substitute_text (obj , replacements )
37+ return substitute_text (obj , replacements , diagnostics )
2438
2539 if not dataclasses .is_dataclass (obj ):
2640 return obj
@@ -29,11 +43,11 @@ def substitute(obj: _T, replacements: Dict[str, str]) -> _T:
2943 for obj_field in dataclasses .fields (obj ):
3044 value = getattr (obj , obj_field .name )
3145 if isinstance (value , str ):
32- new_str = substitute_text (value , replacements )
46+ new_str = substitute_text (value , replacements , diagnostics )
3347 if new_str is not value :
3448 changes [obj_field .name ] = new_str
3549 elif dataclasses .is_dataclass (value ):
36- new_value = substitute (value , replacements )
50+ new_value = substitute (value , replacements , diagnostics )
3751 if new_value is not value :
3852 changes [obj_field .name ] = new_value
3953
@@ -65,7 +79,10 @@ class Inheritable(Node):
6579_I = TypeVar ('_I' , bound = Inheritable )
6680
6781
68- def inherit (obj : _I , parent : Optional [_I ]) -> _I :
82+ def inherit (project_config : ProjectConfig ,
83+ obj : _I ,
84+ parent : Optional [_I ],
85+ diagnostics : List [Diagnostic ]) -> _I :
6986 logger .debug ('Inheriting %s' , obj .ref )
7087 changes : Dict [str , object ] = {}
7188
@@ -77,6 +94,11 @@ def inherit(obj: _I, parent: Optional[_I]) -> _I:
7794 if src not in replacement :
7895 replacement [src ] = dest
7996
97+ # Merge in project-wide constants into the giza substitutions system
98+ new_replacement = {k : str (v ) for k , v in project_config .constants .items ()}
99+ new_replacement .update (replacement )
100+ replacement = new_replacement
101+
80102 # Inherit root-level keys
81103 for field_name in (field .name for field in dataclasses .fields (obj )
82104 if field .name not in {'replacement' , 'ref' , 'source' , 'inherit' }):
@@ -87,8 +109,8 @@ def inherit(obj: _I, parent: Optional[_I]) -> _I:
87109 changes [field_name ] = new_value
88110 value = new_value
89111
90- if replacement and value is not None :
91- changes [field_name ] = substitute (value , replacement )
112+ if value is not None :
113+ changes [field_name ] = substitute (value , replacement , diagnostics )
92114
93115 return dataclasses .replace (obj , ** changes ) if changes else obj
94116
@@ -187,7 +209,7 @@ def reify(self, obj: _I, diagnostics: List[Diagnostic]) -> _I:
187209 if obj .ref is None :
188210 obj .ref = ''
189211
190- obj = inherit (obj , parent )
212+ obj = inherit (self . project_config , obj , parent , diagnostics )
191213 return obj
192214
193215 def reify_file_id (self ,
0 commit comments