mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-08-18 17:55:09 +02:00
Fix variable definition test to detect set_fact and ansible.builtin.set_fact (both block and inline forms)
- Support fully qualified ansible.builtin.set_fact - Parse inline set_fact mappings (e.g. set_fact: { a: 1, b: 2 }) - Continue scanning inside vars/set_fact blocks for Jinja {% set %}, {% for %}, and {% macro %} - Ensures variables defined by set_fact are correctly recognized as defined
This commit is contained in:
parent
2620ee088e
commit
1126765da2
@ -41,24 +41,28 @@ class TestVariableDefinitions(unittest.TestCase):
|
|||||||
# Simple {{ var }} usage with optional Jinja filters after a pipe
|
# Simple {{ var }} usage with optional Jinja filters after a pipe
|
||||||
self.simple_var_pattern = re.compile(r"{{\s*([a-zA-Z_]\w*)\s*(?:\|[^}]*)?}}")
|
self.simple_var_pattern = re.compile(r"{{\s*([a-zA-Z_]\w*)\s*(?:\|[^}]*)?}}")
|
||||||
|
|
||||||
# {% set var = ... %}
|
# {% set var = ... %} (allow trimmed variants)
|
||||||
self.jinja_set_def = re.compile(r'{%\s*-?\s*set\s+([a-zA-Z_]\w*)\s*=')
|
self.jinja_set_def = re.compile(r'{%\s*-?\s*set\s+([a-zA-Z_]\w*)\s*=')
|
||||||
|
|
||||||
# {% for x in ... %} or {% for k, v in ... %}
|
# {% for x in ... %} or {% for k, v in ... %} (allow trimmed variants)
|
||||||
self.jinja_for_def = re.compile(
|
self.jinja_for_def = re.compile(
|
||||||
r'{%\s*-?\s*for\s+([a-zA-Z_]\w*)(?:\s*,\s*([a-zA-Z_]\w*))?\s+in'
|
r'{%\s*-?\s*for\s+([a-zA-Z_]\w*)(?:\s*,\s*([a-zA-Z_]\w*))?\s+in'
|
||||||
)
|
)
|
||||||
|
|
||||||
# {% macro name(param1, param2=..., *varargs, **kwargs) %}
|
# {% macro name(param1, param2=..., *varargs, **kwargs) %} (allow trimmed variants)
|
||||||
self.jinja_macro_def = re.compile(
|
self.jinja_macro_def = re.compile(
|
||||||
r'{%\s*-?\s*macro\s+[a-zA-Z_]\w*\s*\((.*?)\)\s*-?%}'
|
r'{%\s*-?\s*macro\s+[a-zA-Z_]\w*\s*\((.*?)\)\s*-?%}'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Ansible YAML anchors for inline var declarations
|
# Ansible YAML anchors for inline var declarations
|
||||||
self.ansible_set_fact = re.compile(r'^(?:\s*[-]\s*)?set_fact\s*:\s*$')
|
# Support short and FQCN forms, plus inline dict after colon
|
||||||
|
self.ansible_set_fact = re.compile(
|
||||||
|
r'^(?:\s*-\s*)?(?:ansible\.builtin\.)?set_fact\s*:\s*(\{[^}]*\})?\s*$'
|
||||||
|
)
|
||||||
self.ansible_vars_block = re.compile(r'^(?:\s*[-]\s*)?vars\s*:\s*$')
|
self.ansible_vars_block = re.compile(r'^(?:\s*[-]\s*)?vars\s*:\s*$')
|
||||||
self.ansible_loop_var = re.compile(r'^\s*loop_var\s*:\s*([a-zA-Z_]\w*)')
|
self.ansible_loop_var = re.compile(r'^\s*loop_var\s*:\s*([a-zA-Z_]\w*)')
|
||||||
self.mapping_key = re.compile(r'^\s*([a-zA-Z_]\w*)\s*:\s*')
|
self.mapping_key = re.compile(r'^\s*([a-zA-Z_]\w*)\s*:\s*')
|
||||||
|
self.register_pat = re.compile(r'^\s*register\s*:\s*([a-zA-Z_]\w*)')
|
||||||
|
|
||||||
# -----------------------
|
# -----------------------
|
||||||
# Collect "defined" names
|
# Collect "defined" names
|
||||||
@ -85,6 +89,7 @@ class TestVariableDefinitions(unittest.TestCase):
|
|||||||
|
|
||||||
path = os.path.join(root, fn)
|
path = os.path.join(root, fn)
|
||||||
|
|
||||||
|
# Track when we're inside set_fact:/vars: blocks to also extract mapping keys.
|
||||||
in_set_fact = False
|
in_set_fact = False
|
||||||
set_fact_indent = 0
|
set_fact_indent = 0
|
||||||
in_vars_block = False
|
in_vars_block = False
|
||||||
@ -96,63 +101,75 @@ class TestVariableDefinitions(unittest.TestCase):
|
|||||||
stripped = line.lstrip()
|
stripped = line.lstrip()
|
||||||
indent = len(line) - len(stripped)
|
indent = len(line) - len(stripped)
|
||||||
|
|
||||||
# --- set_fact block keys
|
# --- set_fact (short and FQCN), supports inline and block forms
|
||||||
if self.ansible_set_fact.match(stripped):
|
m_sf = self.ansible_set_fact.match(stripped)
|
||||||
in_set_fact = True
|
if m_sf:
|
||||||
set_fact_indent = indent
|
inline_map = m_sf.group(1)
|
||||||
continue
|
if inline_map:
|
||||||
|
# Inline mapping: set_fact: { a: 1, b: 2 }
|
||||||
|
try:
|
||||||
|
data = yaml.safe_load(inline_map)
|
||||||
|
if isinstance(data, dict):
|
||||||
|
self.defined.update(
|
||||||
|
k for k in data.keys() if isinstance(k, str)
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# do not enter block mode if inline present
|
||||||
|
in_set_fact = False
|
||||||
|
else:
|
||||||
|
# Block mapping: keys on subsequent indented lines
|
||||||
|
in_set_fact = True
|
||||||
|
set_fact_indent = indent
|
||||||
|
# continue to next iteration to avoid double-processing this line
|
||||||
|
continue
|
||||||
|
|
||||||
if in_set_fact:
|
if in_set_fact:
|
||||||
# Still inside set_fact child mapping?
|
# Still inside set_fact child mapping?
|
||||||
if indent > set_fact_indent and stripped.strip():
|
if indent > set_fact_indent and stripped.strip():
|
||||||
m = self.mapping_key.match(stripped)
|
m = self.mapping_key.match(stripped)
|
||||||
if m:
|
if m:
|
||||||
self.defined.add(m.group(1))
|
self.defined.add(m.group(1))
|
||||||
continue
|
# do not continue; still scan for Jinja defs below
|
||||||
else:
|
else:
|
||||||
in_set_fact = False
|
# Leaving the block when indentation decreases or a new key at same level appears
|
||||||
|
if indent <= set_fact_indent and stripped:
|
||||||
|
in_set_fact = False
|
||||||
|
|
||||||
# --- vars: block keys
|
# --- vars: block (collect mapping keys)
|
||||||
if self.ansible_vars_block.match(stripped):
|
if self.ansible_vars_block.match(stripped):
|
||||||
in_vars_block = True
|
in_vars_block = True
|
||||||
vars_block_indent = indent
|
vars_block_indent = indent
|
||||||
|
# continue to next line to avoid double-processing this line
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if in_vars_block:
|
if in_vars_block:
|
||||||
# Ignore blank lines inside vars block
|
# Inside vars: collect top-level mapping keys
|
||||||
if not stripped.strip():
|
if indent > vars_block_indent and stripped.strip():
|
||||||
continue
|
|
||||||
# Still inside vars child mapping?
|
|
||||||
if indent > vars_block_indent:
|
|
||||||
m = self.mapping_key.match(stripped)
|
m = self.mapping_key.match(stripped)
|
||||||
if m:
|
if m:
|
||||||
self.defined.add(m.group(1))
|
self.defined.add(m.group(1))
|
||||||
continue
|
# do not continue; still scan for Jinja defs below
|
||||||
else:
|
else:
|
||||||
in_vars_block = False
|
# Leaving vars block
|
||||||
|
if indent <= vars_block_indent and stripped:
|
||||||
|
in_vars_block = False
|
||||||
|
|
||||||
# --- loop_var
|
# --- Always scan every line (including inside blocks) for Jinja definitions
|
||||||
m_loop = self.ansible_loop_var.match(stripped)
|
|
||||||
if m_loop:
|
|
||||||
self.defined.add(m_loop.group(1))
|
|
||||||
|
|
||||||
# --- register: name
|
# {% set var = ... %}
|
||||||
m_reg = re.match(r'^\s*register\s*:\s*([a-zA-Z_]\w*)', stripped)
|
|
||||||
if m_reg:
|
|
||||||
self.defined.add(m_reg.group(1))
|
|
||||||
|
|
||||||
# --- {% set var = ... %}
|
|
||||||
for m in self.jinja_set_def.finditer(line):
|
for m in self.jinja_set_def.finditer(line):
|
||||||
self.defined.add(m.group(1))
|
self.defined.add(m.group(1))
|
||||||
|
|
||||||
# --- {% for x [ , y ] in ... %}
|
# {% for x [, y] in ... %}
|
||||||
for m in self.jinja_for_def.finditer(line):
|
for m in self.jinja_for_def.finditer(line):
|
||||||
self.defined.add(m.group(1))
|
self.defined.add(m.group(1))
|
||||||
if m.group(2):
|
if m.group(2):
|
||||||
self.defined.add(m.group(2))
|
self.defined.add(m.group(2))
|
||||||
|
|
||||||
# --- {% macro name(params...) %} -> collect parameter names
|
# {% macro name(params...) %}
|
||||||
for m in self.jinja_macro_def.finditer(line):
|
for m in self.jinja_macro_def.finditer(line):
|
||||||
params_blob = m.group(1)
|
params_blob = m.group(1)
|
||||||
# Split by comma at top level (macros don't support nested tuples in params)
|
|
||||||
params = [p.strip() for p in params_blob.split(',')]
|
params = [p.strip() for p in params_blob.split(',')]
|
||||||
for p in params:
|
for p in params:
|
||||||
if not p:
|
if not p:
|
||||||
@ -163,6 +180,16 @@ class TestVariableDefinitions(unittest.TestCase):
|
|||||||
name = p.split('=', 1)[0].strip()
|
name = p.split('=', 1)[0].strip()
|
||||||
if re.match(r'^[a-zA-Z_]\w*$', name):
|
if re.match(r'^[a-zA-Z_]\w*$', name):
|
||||||
self.defined.add(name)
|
self.defined.add(name)
|
||||||
|
|
||||||
|
# --- loop_var and register names
|
||||||
|
m_loop = self.ansible_loop_var.match(stripped)
|
||||||
|
if m_loop:
|
||||||
|
self.defined.add(m_loop.group(1))
|
||||||
|
|
||||||
|
m_reg = self.register_pat.match(stripped)
|
||||||
|
if m_reg:
|
||||||
|
self.defined.add(m_reg.group(1))
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
# Ignore unreadable files
|
# Ignore unreadable files
|
||||||
pass
|
pass
|
||||||
|
Loading…
x
Reference in New Issue
Block a user