3
3
4
4
"""This module tries to find urls of repositories that match artifacts passed in 'group:artifact:version' form."""
5
5
import logging
6
+ import re
6
7
import typing
7
8
from collections .abc import Iterator
8
9
from xml .etree .ElementTree import Element # nosec
@@ -120,7 +121,7 @@ def find_parent(pom: Element) -> tuple[str, str, str]:
120
121
return "" , "" , ""
121
122
122
123
123
- def find_scm (pom : Element , tags : list [str ]) -> tuple [Iterator [str ], int ]:
124
+ def find_scm (pom : Element , tags : list [str ], resolve_properties : bool = True ) -> tuple [Iterator [str ], int ]:
124
125
"""
125
126
Parse the passed pom and extract the passed tags.
126
127
@@ -130,6 +131,8 @@ def find_scm(pom: Element, tags: list[str]) -> tuple[Iterator[str], int]:
130
131
The parsed POM.
131
132
tags : list[str]
132
133
The list of tags to try extracting from the POM.
134
+ resolve_properties: bool
135
+ Whether to attempt resolution of Maven properties within the POM.
133
136
134
137
Returns
135
138
-------
@@ -141,7 +144,15 @@ def find_scm(pom: Element, tags: list[str]) -> tuple[Iterator[str], int]:
141
144
# Try to match each tag with the contents of the POM.
142
145
for tag in tags :
143
146
element : typing .Optional [Element ] = pom
144
- tag_parts = tag .split ("." )
147
+
148
+ if tag .startswith ("properties." ):
149
+ # Tags under properties are often "." separated
150
+ # These can be safely split into two resulting tags as nested tags are not allowed here
151
+ tag_parts = ["properties" , tag [11 :]]
152
+ else :
153
+ # Other tags can be split into distinct elements via "."
154
+ tag_parts = tag .split ("." )
155
+
145
156
for index , tag_part in enumerate (tag_parts ):
146
157
element = _find_element (element , tag_part )
147
158
if element is None :
@@ -150,9 +161,53 @@ def find_scm(pom: Element, tags: list[str]) -> tuple[Iterator[str], int]:
150
161
# Add the contents of the final tag
151
162
results .append (element .text .strip ())
152
163
164
+ # Resolve any Maven properties within the results
165
+ if resolve_properties :
166
+ results = _resolve_properties (pom , results )
167
+
153
168
return iter (results ), len (results )
154
169
155
170
171
+ def _resolve_properties (pom : Element , values : list [str ]) -> list [str ]:
172
+ """Resolve any Maven properties found within the passed list of values.
173
+
174
+ Maven POM files have five different use cases for properties (see https://maven.apache.org/pom.html).
175
+ Only the two that relate to contents found elsewhere within the same POM file are considered here.
176
+ That is: ${project.x} where x can be a child tag at any depth, or ${x} where x is found at project.properties.x.
177
+ Entries with unresolved properties are not included in the returned list. In the case of chained properties,
178
+ only the top most property is evaluated.
179
+ """
180
+ resolved_values = []
181
+ for value in values :
182
+ replacements : list = []
183
+ # Calculate replacements - matches any number of ${...} entries in the current value
184
+ for match in re .finditer ("\\ $\\ {[^}]+}" , value ):
185
+ text = match .group ().replace ("$" , "" ).replace ("{" , "" ).replace ("}" , "" )
186
+ if text .startswith ("project." ):
187
+ text = text .replace ("project." , "" )
188
+ else :
189
+ text = f"properties.{ text } "
190
+ # Call find_scm with property resolution flag set to False to prevent the possibility of endless looping
191
+ value_iterator , count = find_scm (pom , [text ], False )
192
+ if count == 0 :
193
+ break
194
+ replacements .append ([match .start (), next (value_iterator ), match .end ()])
195
+
196
+ # Apply replacements in reverse order
197
+ # E.g.
198
+ # git@github.com:owner/project${javac.src.version}-${project.inceptionYear}.git
199
+ # ->
200
+ # git@github.com:owner/project${javac.src.version}-2023.git
201
+ # ->
202
+ # git@github.com:owner/project1.8-2023.git
203
+ for replacement in reversed (replacements ):
204
+ value = f"{ value [:replacement [0 ]]} { replacement [1 ]} { value [replacement [2 ]:]} "
205
+
206
+ resolved_values .append (value )
207
+
208
+ return resolved_values
209
+
210
+
156
211
def parse_pom (pom : str ) -> Element | None :
157
212
"""
158
213
Parse the passed POM using defusedxml.
0 commit comments