Skip to content

Commit 4ee7253

Browse files
jmitchel3claude
andcommitted
Add variable substitution with ${{ vars.NAME }} syntax
- Support vars: or variables: section in rav.yaml - Variables substitute in commands, prefix, and working_dir - Environment variables work as fallback - Bump version to 0.4.0 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 7944b51 commit 4ee7253

5 files changed

Lines changed: 52 additions & 4 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "rav"
3-
version = "0.3.0"
3+
version = "0.4.0"
44
authors = [
55
{ name="Justin Mitchel", email="hello@teamcfe.com" },
66
]

rav.sample.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
name: rav
2+
3+
variables:
4+
PROJECT_NAME: myproject
5+
PORT: "8000"
6+
27
scripts:
3-
server: python3 -m http.server
8+
server: python3 -m http.server ${{ vars.PORT }}
9+
echo: echo "Project is ${{ vars.PROJECT_NAME }}"
410

511

612
downloads:

rav/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.3.0"
1+
__version__ = "0.4.0"

rav/project.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import os
12
import pathlib
3+
import re
24
import subprocess
35
import sys
46
import tempfile
@@ -50,6 +52,39 @@ def scripts(self):
5052
or {}
5153
)
5254

55+
def get_variables(self):
56+
"""Get variables from YAML config, with environment variables as fallback.
57+
58+
Variables defined in YAML take precedence over environment variables.
59+
Supports both 'vars' and 'variables' as the key name.
60+
"""
61+
variables = dict(os.environ) # Start with env vars
62+
yaml_vars = self._project.get("vars") or self._project.get("variables") or {}
63+
if yaml_vars:
64+
# Convert all values to strings and update
65+
variables.update({k: str(v) for k, v in yaml_vars.items()})
66+
return variables
67+
68+
def substitute_variables(self, value):
69+
"""Substitute ${{ vars.NAME }} patterns in a string.
70+
71+
Returns the string with all variable references replaced.
72+
Raises an error if a referenced variable is not defined.
73+
"""
74+
if not isinstance(value, str):
75+
return value
76+
77+
pattern = r'\$\{\{\s*vars\.([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}'
78+
variables = self.get_variables()
79+
80+
def replace_var(match):
81+
var_name = match.group(1)
82+
if var_name not in variables:
83+
raise ValueError(f"Undefined variable: {var_name}")
84+
return variables[var_name]
85+
86+
return re.sub(pattern, replace_var, value)
87+
5388
def is_group_definition(self, value):
5489
"""Check if a script entry is a group definition.
5590
@@ -144,10 +179,15 @@ def apply_prefix_and_working_dir(self, commands, prefix=None, working_dir=None):
144179
"""Apply prefix and working_dir to a list of commands.
145180
146181
Returns a single command string ready for execution.
182+
Variables are substituted using ${{ vars.NAME }} syntax.
147183
"""
148184
processed = []
149185
for cmd in commands:
186+
# Substitute variables in the command
187+
cmd = self.substitute_variables(cmd)
150188
if prefix:
189+
# Substitute variables in the prefix too
190+
prefix = self.substitute_variables(prefix)
151191
cmd = f"{prefix} {cmd}"
152192
processed.append(cmd)
153193

@@ -156,6 +196,8 @@ def apply_prefix_and_working_dir(self, commands, prefix=None, working_dir=None):
156196

157197
# Prepend working_dir change if specified
158198
if working_dir:
199+
# Substitute variables in working_dir
200+
working_dir = self.substitute_variables(working_dir)
159201
joined = f"cd {working_dir} && {joined}"
160202

161203
return joined

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)