|
| 1 | +# -*- mode: Python -*- |
| 2 | + |
| 3 | +# Local Tilt extension: expand glob-like patterns into real file paths for use in deps |
| 4 | +# Note: deps only accepts real paths; this helper uses bash/find to resolve common |
| 5 | +# patterns at Tiltfile load time (e.g., '**/*.ext', 'dir/*.ext', '*.ext'). |
| 6 | + |
| 7 | + |
| 8 | +def _base_dir_from_pattern(pat): |
| 9 | + # Determine a base directory from a pattern for watch_file |
| 10 | + # Treat patterns that start with '**/' (or are '**' variants) as repo root |
| 11 | + if pat.startswith('**/') or pat == '**' or pat == '**/*' or pat.startswith('./**/'): |
| 12 | + return '.' |
| 13 | + # Handle patterns ending with '/**' or '/**/*' -> base dir before the ** |
| 14 | + if pat.endswith('/**') or pat.endswith('/**/*'): |
| 15 | + base = pat.rsplit('/**', 1)[0] |
| 16 | + return base if base else '.' |
| 17 | + # Handle recursive patterns with '/**/' inside |
| 18 | + if '/**/' in pat: |
| 19 | + base = pat.split('/**/', 1)[0] |
| 20 | + return base if base else '.' |
| 21 | + # For simple 'dir/*.ext' or 'dir/name' |
| 22 | + if '/' in pat: |
| 23 | + base = pat.rsplit('/', 1)[0] |
| 24 | + return base if base else '.' |
| 25 | + # For '*.ext' or literal file name, base is current dir |
| 26 | + return '.' |
| 27 | + |
| 28 | + |
| 29 | +def watch_glob(*patterns): |
| 30 | + # Add Tiltfile reload watches for base dirs implied by patterns |
| 31 | + seen = {} |
| 32 | + for pat in patterns: |
| 33 | + base = _base_dir_from_pattern(pat) |
| 34 | + if base not in seen: |
| 35 | + seen[base] = True |
| 36 | + watch_file(base) |
| 37 | + |
| 38 | + |
| 39 | +def files_glob(*glob_patterns, **kwargs): |
| 40 | + """ |
| 41 | + Expand one or more patterns into a list of real file paths using bash/find. |
| 42 | + Supported patterns (good enough for most Tilt workflows): |
| 43 | + - 'dir/**/*.ext' => find dir -type f -name "*.ext" |
| 44 | + - 'dir/*.ext' => find dir -maxdepth 1 -type f -name "*.ext" |
| 45 | + - '*.ext' => find . -maxdepth 1 -type f -name "*.ext" |
| 46 | + - literal file/dir => included as-is |
| 47 | + Returns a de-duplicated list of paths. |
| 48 | + """ |
| 49 | + # Ensure Tiltfile reloads when new files matching these patterns are added |
| 50 | + watch_glob(*glob_patterns) |
| 51 | + results = [] |
| 52 | + for pat in glob_patterns: |
| 53 | + # Handle recursive patterns with '**/' anywhere in the string (e.g., 'dir/**/x', '**/*.go') |
| 54 | + if '/**/' in pat: |
| 55 | + parts = pat.split('/**/', 1) |
| 56 | + base = parts[0] if parts[0] else '.' |
| 57 | + tail = parts[1] if len(parts) > 1 else '' |
| 58 | + if not tail or tail == '*': |
| 59 | + find_cmd = "bash --noprofile --norc -lc 'find " + base + " -type f -print'" |
| 60 | + else: |
| 61 | + if '/' in tail: |
| 62 | + # Fallback to -path for complex tails |
| 63 | + find_cmd = "bash --noprofile --norc -lc 'find " + base + " -type f -path \"*" + tail + "\" -print'" |
| 64 | + else: |
| 65 | + find_cmd = "bash --noprofile --norc -lc 'find " + base + " -type f -name \"" + tail + "\" -print'" |
| 66 | + # Handle patterns that START with '**/' (e.g., '**/*.go') |
| 67 | + elif pat.startswith('**/'): |
| 68 | + tail = pat[len('**/'):] |
| 69 | + if not tail or tail == '*': |
| 70 | + find_cmd = "bash --noprofile --norc -lc 'find . -type f -print'" |
| 71 | + else: |
| 72 | + if '/' in tail: |
| 73 | + find_cmd = "bash --noprofile --norc -lc 'find . -type f -path \"*" + tail + "\" -print'" |
| 74 | + else: |
| 75 | + find_cmd = "bash --noprofile --norc -lc 'find . -type f -name \"" + tail + "\" -print'" |
| 76 | + # Root-level wildcard like '*.go' |
| 77 | + elif pat.startswith('*.') or (pat and '*' in pat and '/' not in pat): |
| 78 | + find_cmd = "bash --noprofile --norc -lc 'find . -maxdepth 1 -type f -name \"" + pat + "\" -print'" |
| 79 | + # Directory-limited wildcard like 'dir/*.templ' |
| 80 | + elif '*' in pat and '/' in pat: |
| 81 | + dir_part, name_pat = pat.rsplit('/', 1) |
| 82 | + find_cmd = "bash --noprofile --norc -lc 'find " + dir_part + " -maxdepth 1 -type f -name \"" + name_pat + "\" -print'" |
| 83 | + else: |
| 84 | + # Literal path (file or dir). deps can watch directories too; include as-is. |
| 85 | + results.append(pat) |
| 86 | + continue |
| 87 | + |
| 88 | + out = local(command=find_cmd, quiet=True) |
| 89 | + s = str(out).strip() |
| 90 | + if s: |
| 91 | + for line in s.split('\n'): |
| 92 | + p = line.strip() |
| 93 | + if p.startswith('./'): |
| 94 | + p = p[2:] |
| 95 | + if p: |
| 96 | + results.append(p) |
| 97 | + |
| 98 | + # Deduplicate while preserving order |
| 99 | + seen = {} |
| 100 | + deduped = [] |
| 101 | + for p in results: |
| 102 | + if p not in seen: |
| 103 | + seen[p] = True |
| 104 | + deduped.append(p) |
| 105 | + return deduped |
| 106 | + |
0 commit comments