I work on Azure devops & in there there is a concept of templates (similar to functions in bash). So, one templates will use other template(s) to perform some actions.
The top most templates (which are not used by any other templates) are exttemplates & the file names have the text exttemplate in them.
I am trying to do build a 'tree' of sorts (like the tree command) indicating which templates are used in exttemplates and then which templates are used in those templates & so on.
I have the function below to recursively look for template references (other templates are always called with the line starting with text - template:)
The input to the function is the array of exttemplates (whcih we can get using the find command). Adding the dot and removing the "\n" is needed because of the way templates are called.
string_template() {
array_of_files_to_search=("$@")
for file_to_search in "${array_of_files_to_search[@]}"; do
if [[ "$file_to_search" = '/'* ]]; then
file_to_search=$(echo ".${file_to_search}" | tr -d '\n')
fi
if [[ $file_to_search = *"exttemplate"* ]]; then
echo -e "\e[34mSearching in $file_to_search & its refereneced templates\e[0m"
else
echo -e "\e[33mSearching in $file_to_search & its refereneced templates\e[0m"
fi
readarray references < <(grep -E "^\s*\- template:.*" "$file_to_search" | sed 's/#.*//' | sed 's/- template://' | awk '{$1=$1;print}')
if [ "${#references[@]}" -gt 0 ]; then
string_template "${references[@]}"
else
echo "This $file_to_search does not use any other templates"
fi
done
}
The function is working fine and all the templates are found. However, what I am not able to accomplish is the generating the tree structure.
What I am getting now is like below. Since the exttemplates have unique name, I can identify them & print them in a different color. But this is not possible for the other templates. How to keep track of the 'levels' while recursing? In the pic (I have hand marked some lines to illustrate), the lines with red mark at the end should be level 2 & the ones with blue mark should be level 3 (assuming the exttemplates are at level 0). How to achieve keeping track of the 'levels'?
Update based on comments
question Can you have the same template used in several other templates at different tree levels? If yes what do you want to do? Repeat it in the tree with all its sub-templates? answer Yes. If this happens, Repeat it in the tree with all its sub-templates.
You can probably do all this with just GNU awk (for its multidimensional associative arrays). The following GNU awk program assumes that your file names do not contain newline characters and that the list of files you want to start from is stored in a text file, one filename per line. It exits with an error message if any of the encountered files cannot be opened. It prints a simple tree with a 2 spaces indentation. If the extra text and ANSI codes of your example are really needed you will have to adapt.
We parse the provided text file and store all filenames found as keys of array to_visit. Then, in the END block:
file in to_visit using a one-iteration for loop over the array keys: for(file in to_visit) { ...; break }.file and parse it. For each line, if the line matches the template definition:
template, removing the comments, the leading and trailing spaces, and prepending a . if it starts with /,template is not the empty string, add an entry in 2 dimensions array includes where the first key is file and the second is template,visited array, add also template to the to_visit array keys, such that we will also parse it.file, add it as a key of array visited (to avoid re-adding it to to_visit if we encounter it again as an included template), delete it from the keys of array to_visit, exit the one-iteration for loop.to_visit array is not empty.For the final printing we use the walk recursive function that takes 2 parameters: a prefix string and a filename file. It prints prefix and file, and for each template template included in file (found in array includes) it calls itself on template with an augmented prefix.
$ cat foo.awk
function walk(prefix, file, template) {
printf("%s%s\n", prefix, file)
for(template in includes[file]) walk(" " prefix, template)
}
{ to_visit[$0] }
END {
while(length(to_visit)) {
for(file in to_visit) {
while ((error = (getline < file)) > 0) {
if($0 ~ /^\s*- template:/) {
gsub(/^\s*- template:\s*|\s*#.*/, "")
template = ($0 ~ /^[/]/ ? "." : "") $0
if(length(template)) includes[file][template]
if(!(template in visited)) to_visit[template]
}
}
if(error < 0) { print file ": cannot open"; exit(1) }
close(file)
visited[file]
delete to_visit[file]
break
}
}
for(file in visited) if(file ~ /exttemplate/) walk("", file)
}
$ awk -f foo.awk files_to_search.txt
foo.exttemplate
b/bar.template
b/2/bar
b/1/bar
a/3/bar
a/foo.template
b/3/foo
c/2/foo
a/1/foo
Note that if you have a loop in the inclusions graph the walk function will enter an infinite loop...