This document describes the internal architecture and API of make-completion-enhanced.
- Architecture Overview
- Annotation Syntax
- Parsing Logic
- Completion Flow
- Cache Format
- Extending the System
┌─────────────────┐
│ Makefile │
│ (Annotations) │
└────────┬────────┘
│
│ Parse on first use or
│ when Makefile changes
▼
┌────────────┐
│ AWK Parser │
└─────┬──────┘
│
│ Generate
▼
┌──────────────────┐
│ Cache File │
│ (name|target| │
│ values) │
└────────┬─────────┘
│
│ Read during
│ completion
▼
┌──────────────────┐
│ Completion │
│ Function │
│ (Bash/Zsh) │
└────────┬─────────┘
│
│ Generate
▼
┌──────────────────┐
│ COMPREPLY │
│ (Suggestions) │
└──────────────────┘
Defines a parameter with possible values:
## PARAM <name>: <value1> <value2> ... <valueN>
## PARAM <name> TYPE=<type> [REQUIRED] [DEFAULT=<value>]| Component | Required | Description | Example |
|---|---|---|---|
name |
Yes | Parameter name | env, debug, region |
: |
Yes (1st line) | Separator between name and values | : |
values |
Yes (1st line) | Space-separated possible values | dev stage prod |
TYPE=<type> |
No | Parameter type | TYPE=enum, TYPE=bool |
REQUIRED |
No | Mark parameter as required | REQUIRED |
DEFAULT=<value> |
No | Default value | DEFAULT=False |
# Minimal
## PARAM env: dev prod
# With type
## PARAM env: dev stage prod
## PARAM env TYPE=enum
# Required parameter
## PARAM env: dev stage prod
## PARAM env TYPE=enum REQUIRED
# With default
## PARAM debug: True False
## PARAM debug TYPE=bool DEFAULT=False
# Multiple values
## PARAM region: us-east-1 us-west-2 eu-west-1 ap-southeast-1Defines the scope for subsequent PARAM annotations:
## TARGET <target-name>
## PARAM <param>: <values>- Parameters before any
TARGETannotation are global - Parameters after a
TARGETannotation apply only to that target - A new
TARGETannotation starts a new scope
## Global parameters
## PARAM env: dev prod
## TARGET deploy
## PARAM region: us-east-1 eu-west-1
# region is only available for 'make deploy'
## TARGET test
## PARAM coverage: True False
# coverage is only available for 'make test'Defines positional argument completions for a target. N is the 1-based
position of the argument after the target name.
## ARGS <N>: <value1> <value2> ... <valueN>| Component | Required | Description | Example |
|---|---|---|---|
N |
Yes | 1-based argument position | 1, 2, 3 |
: |
Yes | Separator | : |
values |
Yes | Space-separated completion values | host1 host2 |
# Single positional argument
## TARGET run
## ARGS 1: api worker scheduler
# Multiple positions
## TARGET ssh
## ARGS 1: host1.example.com host2.example.com
## ARGS 2: bash htop journalctl
# Combined with PARAM
## TARGET deploy
## PARAM env: dev prod
## ARGS 1: us-east-1 eu-west-1make run <TAB> # → api worker scheduler
make ssh <TAB> # → host1.example.com host2.example.com
make ssh host1 <TAB> # → bash htop journalctlPositional completions are output as plain words (no = sign),
whereas ## PARAM completions are output as name=value.
## ARGS N: entries are stored with a special name pattern:
__args_N__|target|values
Example:
__args_1__|ssh|host1.example.com host2.example.com
__args_2__|ssh|bash htop journalctl
Defines CLI-style flags and options (--flag, -f, --flag value, --flag=value).
## OPT <flag>[: <value1> <value2> ...]| Component | Required | Description | Example |
|---|---|---|---|
flag |
Yes | Flag name (must start with -) |
--verbose, -d, --log-level: |
: |
No | Append to flag name to indicate it accepts a value | --log-level: |
values |
No | Space-separated completion values | debug info warning |
# Boolean flag (no value)
## OPT --verbose
## OPT -v
# Flag with fixed values
## OPT --log-level: debug info warning error
# Short flag with values
## OPT -l: debug info warning error
# Flag with free-form value (no completion suggestions)
## OPT --log-file:
## OPT -f:make run -<TAB> # → --verbose -v --log-level --log-file
make run --log-level <TAB> # → debug info warning error (space style)
make run --log-level=<TAB> # → --log-level=debug --log-level=info ...
make run --log-file <TAB> # → (no suggestions, free-form value)For long options (--) with values, the completion also suggests --opt=val style
so the user can pick either style. Short options (-) are only completed in space style.
## OPT entries share the same cache format as ## PARAM.
They are distinguished by the flag name starting with -:
--log-level|run| debug info warning error
--verbose|run|
-v|run|
--log-file|run|
Internally, during completion, entries whose name starts with - are handled
as CLI options rather than Make parameters.
The core parsing logic in both Bash and Zsh versions:
BEGIN {
tgt="__global__" # Start with global scope
}
/^## TARGET / {
tgt=$3 # Switch to target-specific scope
}
/^## PARAM / {
name=$3
sub(":", "", name) # Remove trailing colon
vals=""
# Collect values, skipping modifiers
for (i=4; i<=NF; i++) {
if ($i !~ /TYPE=|REQUIRED|DEFAULT=/) {
vals = vals " " $i
}
}
# Output: name|target|values
print name "|" tgt "|" vals
}
/^## ARGS / {
pos=$3
sub(":", "", pos) # Remove trailing colon from position number
vals=""
for (i=4; i<=NF; i++) {
vals = vals " " $i
}
# Output: __args_N__|target|values
print "__args_" pos "__|" tgt "|" vals
}
/^## OPT / {
name=$3
sub(":", "", name) # Remove trailing colon (e.g. "--flag:" → "--flag")
vals=""
for (i=4; i<=NF; i++) {
vals = vals " " $i
}
# Output: --flag|target|values (same format as PARAM)
print name "|" tgt "|" vals
}- Initialization: Set scope to
__global__ - Process Each Line:
- If line matches
## TARGET <name>: Update scope to target name - If line matches
## PARAM <spec>: Extract parameter info - If line matches
## ARGS <N>: <values>: Extract positional arg info - If line matches
## OPT <flag>[: <values>]: Extract CLI option info
- If line matches
- Parameter Extraction:
- Extract parameter name (field 3)
- Remove trailing colon from name
- Collect values (fields 4+) excluding TYPE/REQUIRED/DEFAULT
- Positional Arg Extraction:
- Extract position number (field 3, strip colon)
- Collect values (fields 4+)
- Store under special key
__args_N__
- CLI Option Extraction:
- Extract flag name (field 3, strip colon)
- Collect values (fields 4+) — empty means boolean flag
- Store under the flag name (starts with
-)
- Output: Write
name|target|valuesto cache
Input Makefile:
## PARAM env: dev prod
## TARGET deploy
## PARAM region: us-east-1 eu-west-1
## TARGET ssh
## ARGS 1: host1 host2
## ARGS 2: bash htopOutput Cache:
env|__global__|dev prod
region|deploy|us-east-1 eu-west-1
__args_1__|ssh|host1 host2
__args_2__|ssh|bash htop
_make_completion_enhanced() {
# 1. Get current word and target
local cur="${COMP_WORDS[COMP_CWORD]}"
local target="${COMP_WORDS[1]}"
# 2. Check cache validity
if [[ ! -f "$cache" || Makefile -nt "$cache" ]]; then
# Regenerate cache
awk '...' Makefile > "$cache"
fi
# 3. Complete target name (position 1)
if [[ $COMP_CWORD -eq 1 ]]; then
COMPREPLY=( $(compgen -W "$(awk -F: '/^[a-zA-Z0-9_.-]+:/{print $1}' Makefile)" -- "$cur") )
return
fi
# 4. Complete parameters or positional args (position 2+)
local pos=$(( COMP_CWORD - 1 ))
COMPREPLY=( $(awk -F'|' -v t="$target" -v pos="$pos" '
($2=="__global__"||$2==t) {
split($3,v," ")
if ($1=="__args_"pos"__") { for(i in v) print v[i] }
else if ($1!~/^__args_/) { for(i in v) print $1"="v[i] }
}
' "$cache" | compgen -W "$(cat)" -- "$cur") )
}_make() {
# 1. Get target and cache location
local target="${words[2]}"
local cache="$HOME/.cache/make-completion-enhanced.cache"
# 2. Check cache validity
if [[ ! -f "$cache" || Makefile -nt "$cache" ]]; then
# Regenerate cache
awk '...' Makefile > "$cache"
fi
# 3. Complete target (position 2)
if (( CURRENT == 2 )); then
_describe targets "${(@f)$(awk -F: '/^[a-zA-Z0-9_.-]+:/{print $1}' Makefile)}"
else
# 4. Complete parameters or positional args (position 3+)
local pos=$(( CURRENT - 2 ))
_describe params "${(@f)$(awk -F'|' -v t="$target" -v pos="$pos" '
($2=="__global__"||$2==t) {
split($3,v," ")
if ($1=="__args_"pos"__") { for(i in v) print v[i] }
else if ($1!~/^__args_/) { for(i in v) print $1"="v[i] }
}
' "$cache")}"
fi
}User types: make deploy region=<TAB>
-
Word Detection:
cur= "region="target= "deploy"COMP_CWORD= 2
-
Cache Check:
- Check if
~/.cache/make-completion-enhanced.cacheexists - Check if Makefile is newer than cache
- Regenerate if needed
- Check if
-
Scope Resolution:
- Read cache file
- Filter lines where target is "deploy" or "global"
-
Value Extraction:
- Find parameter "region"
- Extract values: "us-east-1 eu-west-1"
- Format as: "region=us-east-1 region=eu-west-1"
-
Filtering:
- Use
compgento filter matches starting with "region=" - Return matches to shell
- Use
-
Display:
- Shell shows:
us-east-1 eu-west-1
- Shell shows:
$HOME/.cache/make-completion-enhanced.cache
Each line in the cache:
<parameter_name>|<target_name>|<value1> <value2> ... <valueN>
| Field | Description | Example |
|---|---|---|
| Parameter name | Name of the parameter without colon | env, debug, region |
| Target name | Target scope or __global__ |
deploy, __global__ |
| Values | Space-separated possible values | dev stage prod |
env|__global__|dev stage prod
debug|__global__|True False
verbose|__global__|True False
app|run|api worker scheduler
region|deploy|us-east-1 us-west-2 eu-west-1
replicas|deploy|1 2 3 5
Cache is regenerated when:
- Cache file doesn't exist
- Makefile modification time is newer than cache
if [[ ! -f "$cache" || Makefile -nt "$cache" ]]; then
# Regenerate
fiTo add a new parameter type (e.g., TYPE=number):
- Update AWK Parser:
/^## PARAM / {
# Existing logic...
# Add type handling
type=""
for (i=4; i<=NF; i++) {
if ($i ~ /TYPE=number/) {
type="number"
}
}
# Store type in cache if needed
}- Update Completion Logic:
# In completion function
if [[ $type == "number" ]]; then
# Generate numeric completions
COMPREPLY=( $(seq 1 10) )
fiTo support dynamic values (e.g., from files, commands):
- Extend Annotation Syntax:
## PARAM branch SOURCE=git_branches
## PARAM file SOURCE=ls_files- Add Source Resolution:
case "$source" in
git_branches)
values=$(git branch --format='%(refname:short)')
;;
ls_files)
values=$(ls -1)
;;
esacFor hierarchical parameters:
## PARAM service: api frontend
## PARAM service.api.env: dev prod
## PARAM service.frontend.env: staging prodUpdate parser to handle dot notation and conditional scoping.
Add validation hooks:
## PARAM port TYPE=number VALIDATE=port_rangeImplement validator:
validate_port_range() {
local value=$1
[[ $value -ge 1024 && $value -le 65535 ]]
}Support multiple values for a single parameter:
## PARAM tags MULTIUpdate completion to allow multiple selections:
if [[ $multi == "true" ]]; then
# Don't complete if already selected
# Allow space-separated values
fiMain completion function registered with complete.
Variables:
cur: Current word being completedtarget: The make target (first argument)cache: Path to cache file
Returns: Sets COMPREPLY array
Main completion function registered with compdef.
Variables:
words: Array of words in current command lineCURRENT: Index of current wordtarget: The make target
Returns: Calls _describe to set completions
Used in the parsing script:
| Variable | Type | Description |
|---|---|---|
tgt |
String | Current target scope |
name |
String | Parameter name |
vals |
String | Space-separated values |
NF |
Number | Number of fields in line |
$1, $2, ... |
String | Field values |
- Cache is read once per completion invocation
- Avoids re-parsing Makefile for every completion
- Typical cache size: < 1 KB
- Read time: < 1ms
- AWK parsing: O(n) where n is number of lines
- Typical Makefile: < 1000 lines
- Parse time: < 10ms
- Minimize Makefile Size: Keep annotations concise
- Limit Value Count: < 100 values per parameter
- Cache Location: Use fast filesystem (SSD)
- Avoid Redundancy: Don't duplicate global parameters
# Bash
set -x
_make_completion_enhanced
set +x
# Zsh
setopt xtrace
_make
unsetopt xtracecat ~/.cache/make-completion-enhanced.cacheawk '
BEGIN { tgt="__global__" }
/^## TARGET / { tgt=$3 }
/^## PARAM / {
name=$3; sub(":", "", name)
vals=""
for (i=4;i<=NF;i++) if ($i !~ /TYPE=|REQUIRED|DEFAULT=/) vals=vals" "$i
print name"|"tgt"|"vals
}' Makefile# Bash
complete -p make
# Zsh
whence -v _make- 4.0+: Full support
- 3.x: Limited support (no associative arrays if extended)
- 5.0+: Full support
- 4.x: May work with limited features
- GNU AWK (gawk): Full support
- mawk: Full support
- BSD awk: Full support
- busybox awk: Basic support
Planned features:
- Conditional Completions: Values based on other parameters
- Dynamic Values: From command output or files
- Validation: Pre-completion value validation
- Description Support: Help text for parameters
- Aliases: Alternative names for parameters
- Deprecation Warnings: Mark old parameters