From 346f42643b1543632239fc960cc45d95799dad21 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 27 May 2024 23:48:22 +0200 Subject: [PATCH] helpers 2.1: move ynh_add_config, ynh_replace_vars, ynh_read_var_in_file, ynh_write_var_in_file and ynh_render_template to a separate 'templating' file --- helpers/helpers.v2.1.d/templating | 367 ++++++++++++++++++++++++++++++ helpers/helpers.v2.1.d/utils | 366 ----------------------------- 2 files changed, 367 insertions(+), 366 deletions(-) create mode 100644 helpers/helpers.v2.1.d/templating diff --git a/helpers/helpers.v2.1.d/templating b/helpers/helpers.v2.1.d/templating new file mode 100644 index 000000000..9ba6f7cff --- /dev/null +++ b/helpers/helpers.v2.1.d/templating @@ -0,0 +1,367 @@ +#!/bin/bash + +# Create a dedicated config file from a template +# +# usage: ynh_add_config --template="template" --destination="destination" +# | arg: -t, --template= - Template config file to use +# | arg: -d, --destination= - Destination of the config file +# +# examples: +# ynh_add_config --template=".env" --destination="$install_dir/.env" use the template file "../conf/.env" +# ynh_add_config --template="/etc/nginx/sites-available/default" --destination="etc/nginx/sites-available/mydomain.conf" +# +# The template can be by default the name of a file in the conf directory +# of a YunoHost Package, a relative path or an absolute path. +# +# The helper will use the template `template` to generate a config file +# `destination` by replacing the following keywords with global variables +# that should be defined before calling this helper : +# ``` +# __PATH__ by $path_url +# __USER__ by $app +# __FINALPATH__ by $final_path +# __PHPVERSION__ by $YNH_PHP_VERSION (packaging v1 only, packaging v2 uses phpversion setting implicitly set by apt resource) +# __YNH_NODE_LOAD_PATH__ by $ynh_node_load_PATH +# ``` +# And any dynamic variables that should be defined before calling this helper like: +# ``` +# __DOMAIN__ by $domain +# __APP__ by $app +# __VAR_1__ by $var_1 +# __VAR_2__ by $var_2 +# ``` +# +# The helper will verify the checksum and backup the destination file +# if it's different before applying the new template. +# +# And it will calculate and store the destination file checksum +# into the app settings when configuration is done. +# +# Requires YunoHost version 4.1.0 or higher. +ynh_add_config() { + # ============ Argument parsing ============= + local -A args_array=([t]=template= [d]=destination=) + local template + local destination + ynh_handle_getopts_args "$@" + # =========================================== + + local template_path + if [ -f "$YNH_APP_BASEDIR/conf/$template" ]; then + template_path="$YNH_APP_BASEDIR/conf/$template" + elif [ -f "$template" ]; then + template_path=$template + else + ynh_die --message="The provided template $template doesn't exist" + fi + + ynh_backup_if_checksum_is_different --file="$destination" + + # Make sure to set the permissions before we copy the file + # This is to cover a case where an attacker could have + # created a file beforehand to have control over it + # (cp won't overwrite ownership / modes by default...) + touch $destination + chown root:root $destination + chmod 640 $destination + + cp -f "$template_path" "$destination" + + _ynh_apply_default_permissions $destination + + ynh_replace_vars --file="$destination" + + ynh_store_file_checksum --file="$destination" +} + +# Replace variables in a file +# +# [internal] +# +# usage: ynh_replace_vars --file="file" +# | arg: -f, --file= - File where to replace variables +# +# The helper will replace the following keywords with global variables +# that should be defined before calling this helper : +# __PATH__ by $path_url +# __USER__ by $app +# __FINALPATH__ by $final_path +# __PHPVERSION__ by $YNH_PHP_VERSION (packaging v1 only, packaging v2 uses phpversion setting implicitly set by apt resource) +# __YNH_NODE_LOAD_PATH__ by $ynh_node_load_PATH +# +# And any dynamic variables that should be defined before calling this helper like: +# __DOMAIN__ by $domain +# __APP__ by $app +# __VAR_1__ by $var_1 +# __VAR_2__ by $var_2 +# +# Requires YunoHost version 4.1.0 or higher. +ynh_replace_vars() { + # ============ Argument parsing ============= + local -A args_array=([f]=file=) + local file + ynh_handle_getopts_args "$@" + # =========================================== + + # Replace specific YunoHost variables + if test -n "${path_url:-}"; then + # path_url_slash_less is path_url, or a blank value if path_url is only '/' + local path_url_slash_less=${path_url%/} + ynh_replace_string --match_string="__PATH__/" --replace_string="$path_url_slash_less/" --target_file="$file" + ynh_replace_string --match_string="__PATH__" --replace_string="$path_url" --target_file="$file" + fi + if test -n "${app:-}"; then + ynh_replace_string --match_string="__USER__" --replace_string="$app" --target_file="$file" + fi + if test -n "${ynh_node_load_PATH:-}"; then + ynh_replace_string --match_string="__YNH_NODE_LOAD_PATH__" --replace_string="$ynh_node_load_PATH" --target_file="$file" + fi + + # Replace others variables + + # List other unique (__ __) variables in $file + local uniques_vars=($(grep -oP '__[A-Z0-9]+?[A-Z0-9_]*?[A-Z0-9]*?__' $file | sort --unique | sed "s@__\([^.]*\)__@\L\1@g")) + + set +o xtrace # set +x + + # Do the replacement + local delimit=@ + for one_var in "${uniques_vars[@]}"; do + # Validate that one_var is indeed defined + # -v checks if the variable is defined, for example: + # -v FOO tests if $FOO is defined + # -v $FOO tests if ${!FOO} is defined + # More info: https://stackoverflow.com/questions/3601515/how-to-check-if-a-variable-is-set-in-bash/17538964#comment96392525_17538964 + [[ -v "${one_var:-}" ]] || ynh_die --message="Variable \$$one_var wasn't initialized when trying to replace __${one_var^^}__ in $file" + + # Escape delimiter in match/replace string + match_string="__${one_var^^}__" + match_string=${match_string//${delimit}/"\\${delimit}"} + replace_string="${!one_var}" + replace_string=${replace_string//\\/\\\\} + replace_string=${replace_string//${delimit}/"\\${delimit}"} + + # Actually replace (sed is used instead of ynh_replace_string to avoid triggering an epic amount of debug logs) + sed --in-place "s${delimit}${match_string}${delimit}${replace_string}${delimit}g" "$file" + done + set -o xtrace # set -x +} + +# Get a value from heterogeneous file (yaml, json, php, python...) +# +# usage: ynh_read_var_in_file --file=PATH --key=KEY +# | arg: -f, --file= - the path to the file +# | arg: -k, --key= - the key to get +# | arg: -a, --after= - the line just before the key (in case of multiple lines with the name of the key in the file) +# +# This helpers match several var affectation use case in several languages +# We don't use jq or equivalent to keep comments and blank space in files +# This helpers work line by line, it is not able to work correctly +# if you have several identical keys in your files +# +# Example of line this helpers can managed correctly +# .yml +# title: YunoHost documentation +# email: 'yunohost@yunohost.org' +# .json +# "theme": "colib'ris", +# "port": 8102 +# "some_boolean": false, +# "user": null +# .ini +# some_boolean = On +# action = "Clear" +# port = 20 +# .php +# $user= +# user => 20 +# .py +# USER = 8102 +# user = 'https://donate.local' +# CUSTOM['user'] = 'YunoHost' +# +# Requires YunoHost version 4.3 or higher. +ynh_read_var_in_file() { + # ============ Argument parsing ============= + local -A args_array=([f]=file= [k]=key= [a]=after=) + local file + local key + local after + ynh_handle_getopts_args "$@" + after="${after:-}" + # =========================================== + + [[ -f $file ]] || ynh_die --message="File $file does not exists" + + set +o xtrace # set +x + + # Get the line number after which we search for the variable + local line_number=1 + if [[ -n "$after" ]]; then + line_number=$(grep -m1 -n $after $file | cut -d: -f1) + if [[ -z "$line_number" ]]; then + set -o xtrace # set -x + return 1 + fi + fi + + local filename="$(basename -- "$file")" + local ext="${filename##*.}" + local endline=',;' + local assign="=>|:|=" + local comments="#" + local string="\"'" + if [[ "$ext" =~ ^ini|env|toml|yml|yaml$ ]]; then + endline='#' + fi + if [[ "$ext" =~ ^ini|env$ ]]; then + comments="[;#]" + fi + if [[ "php" == "$ext" ]] || [[ "$ext" == "js" ]]; then + comments="//" + fi + local list='\[\s*['$string']?\w+['$string']?\]' + local var_part='^\s*((const|var|let)\s+)?\$?(\w+('$list')*(->|\.|\[))*\s*' + var_part+="[$string]?${key}[$string]?" + var_part+='\s*\]?\s*' + var_part+="($assign)" + var_part+='\s*' + + # Extract the part after assignation sign + local expression_with_comment="$((tail +$line_number ${file} | grep -i -o -P $var_part'\K.*$' || echo YNH_NULL) | head -n1)" + if [[ "$expression_with_comment" == "YNH_NULL" ]]; then + set -o xtrace # set -x + echo YNH_NULL + return 0 + fi + + # Remove comments if needed + local expression="$(echo "$expression_with_comment" | sed "s@${comments}[^$string]*\$@@g" | sed "s@\s*[$endline]*\s*]*\$@@")" + + local first_char="${expression:0:1}" + if [[ "$first_char" == '"' ]]; then + echo "$expression" | grep -m1 -o -P '"\K([^"](\\")?)*[^\\](?=")' | head -n1 | sed 's/\\"/"/g' + elif [[ "$first_char" == "'" ]]; then + echo "$expression" | grep -m1 -o -P "'\K([^'](\\\\')?)*[^\\\\](?=')" | head -n1 | sed "s/\\\\'/'/g" + else + echo "$expression" + fi + set -o xtrace # set -x +} + +# Set a value into heterogeneous file (yaml, json, php, python...) +# +# usage: ynh_write_var_in_file --file=PATH --key=KEY --value=VALUE +# | arg: -f, --file= - the path to the file +# | arg: -k, --key= - the key to set +# | arg: -v, --value= - the value to set +# | arg: -a, --after= - the line just before the key (in case of multiple lines with the name of the key in the file) +# +# Requires YunoHost version 4.3 or higher. +ynh_write_var_in_file() { + # ============ Argument parsing ============= + local -A args_array=([f]=file= [k]=key= [v]=value= [a]=after=) + local file + local key + local value + local after + ynh_handle_getopts_args "$@" + after="${after:-}" + # =========================================== + + [[ -f $file ]] || ynh_die --message="File $file does not exists" + + set +o xtrace # set +x + + # Get the line number after which we search for the variable + local after_line_number=1 + if [[ -n "$after" ]]; then + after_line_number=$(grep -m1 -n $after $file | cut -d: -f1) + if [[ -z "$after_line_number" ]]; then + set -o xtrace # set -x + return 1 + fi + fi + + local filename="$(basename -- "$file")" + local ext="${filename##*.}" + local endline=',;' + local assign="=>|:|=" + local comments="#" + local string="\"'" + if [[ "$ext" =~ ^ini|env|toml|yml|yaml$ ]]; then + endline='#' + fi + if [[ "$ext" =~ ^ini|env$ ]]; then + comments="[;#]" + fi + if [[ "php" == "$ext" ]] || [[ "$ext" == "js" ]]; then + comments="//" + fi + local list='\[\s*['$string']?\w+['$string']?\]' + local var_part='^\s*((const|var|let)\s+)?\$?(\w+('$list')*(->|\.|\[))*\s*' + var_part+="[$string]?${key}[$string]?" + var_part+='\s*\]?\s*' + var_part+="($assign)" + var_part+='\s*' + + # Extract the part after assignation sign + local expression_with_comment="$((tail +$after_line_number ${file} | grep -i -o -P $var_part'\K.*$' || echo YNH_NULL) | head -n1)" + if [[ "$expression_with_comment" == "YNH_NULL" ]]; then + set -o xtrace # set -x + return 1 + fi + local value_line_number="$(tail +$after_line_number ${file} | grep -m1 -n -i -P $var_part'\K.*$' | cut -d: -f1)" + value_line_number=$((after_line_number + value_line_number)) + local range="${after_line_number},${value_line_number} " + + # Remove comments if needed + local expression="$(echo "$expression_with_comment" | sed "s@${comments}[^$string]*\$@@g" | sed "s@\s*[$endline]*\s*]*\$@@")" + endline=${expression_with_comment#"$expression"} + endline="$(echo "$endline" | sed 's/\\/\\\\/g')" + value="$(echo "$value" | sed 's/\\/\\\\/g')" + value=${value//&/"\&"} + local first_char="${expression:0:1}" + delimiter=$'\001' + if [[ "$first_char" == '"' ]]; then + # \ and sed is quite complex you need 2 \\ to get one in a sed + # So we need \\\\ to go through 2 sed + value="$(echo "$value" | sed 's/"/\\\\"/g')" + sed -ri "${range}s$delimiter"'(^'"${var_part}"'")([^"]|\\")*("[\s;,]*)(\s*'$comments'.*)?$'$delimiter'\1'"${value}"'"'"${endline}${delimiter}i" ${file} + elif [[ "$first_char" == "'" ]]; then + # \ and sed is quite complex you need 2 \\ to get one in a sed + # However double quotes implies to double \\ to + # So we need \\\\\\\\ to go through 2 sed and 1 double quotes str + value="$(echo "$value" | sed "s/'/\\\\\\\\'/g")" + sed -ri "${range}s$delimiter(^${var_part}')([^']|\\')*('"'[\s,;]*)(\s*'$comments'.*)?$'$delimiter'\1'"${value}'${endline}${delimiter}i" ${file} + else + if [[ "$value" == *"'"* ]] || [[ "$value" == *'"'* ]] || [[ "$ext" =~ ^php|py|json|js$ ]]; then + value='\"'"$(echo "$value" | sed 's/"/\\\\"/g')"'\"' + fi + if [[ "$ext" =~ ^yaml|yml$ ]]; then + value=" $value" + fi + sed -ri "${range}s$delimiter(^${var_part}).*\$$delimiter\1${value}${endline}${delimiter}i" ${file} + fi + set -o xtrace # set -x +} + +# Render templates with Jinja2 +# +# [internal] +# +# Attention : Variables should be exported before calling this helper to be +# accessible inside templates. +# +# usage: ynh_render_template some_template output_path +# | arg: some_template - Template file to be rendered +# | arg: output_path - The path where the output will be redirected to +ynh_render_template() { + local template_path=$1 + local output_path=$2 + mkdir -p "$(dirname $output_path)" + # Taken from https://stackoverflow.com/a/35009576 + python3 -c 'import os, sys, jinja2; sys.stdout.write( + jinja2.Template(sys.stdin.read() + ).render(os.environ));' <$template_path >$output_path +} diff --git a/helpers/helpers.v2.1.d/utils b/helpers/helpers.v2.1.d/utils index 73553983e..dadc478c3 100644 --- a/helpers/helpers.v2.1.d/utils +++ b/helpers/helpers.v2.1.d/utils @@ -409,372 +409,6 @@ ynh_local_curl() { fi } -# Create a dedicated config file from a template -# -# usage: ynh_add_config --template="template" --destination="destination" -# | arg: -t, --template= - Template config file to use -# | arg: -d, --destination= - Destination of the config file -# -# examples: -# ynh_add_config --template=".env" --destination="$install_dir/.env" use the template file "../conf/.env" -# ynh_add_config --template="/etc/nginx/sites-available/default" --destination="etc/nginx/sites-available/mydomain.conf" -# -# The template can be by default the name of a file in the conf directory -# of a YunoHost Package, a relative path or an absolute path. -# -# The helper will use the template `template` to generate a config file -# `destination` by replacing the following keywords with global variables -# that should be defined before calling this helper : -# ``` -# __PATH__ by $path_url -# __USER__ by $app -# __FINALPATH__ by $final_path -# __PHPVERSION__ by $YNH_PHP_VERSION (packaging v1 only, packaging v2 uses phpversion setting implicitly set by apt resource) -# __YNH_NODE_LOAD_PATH__ by $ynh_node_load_PATH -# ``` -# And any dynamic variables that should be defined before calling this helper like: -# ``` -# __DOMAIN__ by $domain -# __APP__ by $app -# __VAR_1__ by $var_1 -# __VAR_2__ by $var_2 -# ``` -# -# The helper will verify the checksum and backup the destination file -# if it's different before applying the new template. -# -# And it will calculate and store the destination file checksum -# into the app settings when configuration is done. -# -# Requires YunoHost version 4.1.0 or higher. -ynh_add_config() { - # ============ Argument parsing ============= - local -A args_array=([t]=template= [d]=destination=) - local template - local destination - ynh_handle_getopts_args "$@" - # =========================================== - - local template_path - if [ -f "$YNH_APP_BASEDIR/conf/$template" ]; then - template_path="$YNH_APP_BASEDIR/conf/$template" - elif [ -f "$template" ]; then - template_path=$template - else - ynh_die --message="The provided template $template doesn't exist" - fi - - ynh_backup_if_checksum_is_different --file="$destination" - - # Make sure to set the permissions before we copy the file - # This is to cover a case where an attacker could have - # created a file beforehand to have control over it - # (cp won't overwrite ownership / modes by default...) - touch $destination - chown root:root $destination - chmod 640 $destination - - cp -f "$template_path" "$destination" - - _ynh_apply_default_permissions $destination - - ynh_replace_vars --file="$destination" - - ynh_store_file_checksum --file="$destination" -} - -# Replace variables in a file -# -# [internal] -# -# usage: ynh_replace_vars --file="file" -# | arg: -f, --file= - File where to replace variables -# -# The helper will replace the following keywords with global variables -# that should be defined before calling this helper : -# __PATH__ by $path_url -# __USER__ by $app -# __FINALPATH__ by $final_path -# __PHPVERSION__ by $YNH_PHP_VERSION (packaging v1 only, packaging v2 uses phpversion setting implicitly set by apt resource) -# __YNH_NODE_LOAD_PATH__ by $ynh_node_load_PATH -# -# And any dynamic variables that should be defined before calling this helper like: -# __DOMAIN__ by $domain -# __APP__ by $app -# __VAR_1__ by $var_1 -# __VAR_2__ by $var_2 -# -# Requires YunoHost version 4.1.0 or higher. -ynh_replace_vars() { - # ============ Argument parsing ============= - local -A args_array=([f]=file=) - local file - ynh_handle_getopts_args "$@" - # =========================================== - - # Replace specific YunoHost variables - if test -n "${path_url:-}"; then - # path_url_slash_less is path_url, or a blank value if path_url is only '/' - local path_url_slash_less=${path_url%/} - ynh_replace_string --match_string="__PATH__/" --replace_string="$path_url_slash_less/" --target_file="$file" - ynh_replace_string --match_string="__PATH__" --replace_string="$path_url" --target_file="$file" - fi - if test -n "${app:-}"; then - ynh_replace_string --match_string="__USER__" --replace_string="$app" --target_file="$file" - fi - if test -n "${ynh_node_load_PATH:-}"; then - ynh_replace_string --match_string="__YNH_NODE_LOAD_PATH__" --replace_string="$ynh_node_load_PATH" --target_file="$file" - fi - - # Replace others variables - - # List other unique (__ __) variables in $file - local uniques_vars=($(grep -oP '__[A-Z0-9]+?[A-Z0-9_]*?[A-Z0-9]*?__' $file | sort --unique | sed "s@__\([^.]*\)__@\L\1@g")) - - set +o xtrace # set +x - - # Do the replacement - local delimit=@ - for one_var in "${uniques_vars[@]}"; do - # Validate that one_var is indeed defined - # -v checks if the variable is defined, for example: - # -v FOO tests if $FOO is defined - # -v $FOO tests if ${!FOO} is defined - # More info: https://stackoverflow.com/questions/3601515/how-to-check-if-a-variable-is-set-in-bash/17538964#comment96392525_17538964 - [[ -v "${one_var:-}" ]] || ynh_die --message="Variable \$$one_var wasn't initialized when trying to replace __${one_var^^}__ in $file" - - # Escape delimiter in match/replace string - match_string="__${one_var^^}__" - match_string=${match_string//${delimit}/"\\${delimit}"} - replace_string="${!one_var}" - replace_string=${replace_string//\\/\\\\} - replace_string=${replace_string//${delimit}/"\\${delimit}"} - - # Actually replace (sed is used instead of ynh_replace_string to avoid triggering an epic amount of debug logs) - sed --in-place "s${delimit}${match_string}${delimit}${replace_string}${delimit}g" "$file" - done - set -o xtrace # set -x -} - -# Get a value from heterogeneous file (yaml, json, php, python...) -# -# usage: ynh_read_var_in_file --file=PATH --key=KEY -# | arg: -f, --file= - the path to the file -# | arg: -k, --key= - the key to get -# | arg: -a, --after= - the line just before the key (in case of multiple lines with the name of the key in the file) -# -# This helpers match several var affectation use case in several languages -# We don't use jq or equivalent to keep comments and blank space in files -# This helpers work line by line, it is not able to work correctly -# if you have several identical keys in your files -# -# Example of line this helpers can managed correctly -# .yml -# title: YunoHost documentation -# email: 'yunohost@yunohost.org' -# .json -# "theme": "colib'ris", -# "port": 8102 -# "some_boolean": false, -# "user": null -# .ini -# some_boolean = On -# action = "Clear" -# port = 20 -# .php -# $user= -# user => 20 -# .py -# USER = 8102 -# user = 'https://donate.local' -# CUSTOM['user'] = 'YunoHost' -# -# Requires YunoHost version 4.3 or higher. -ynh_read_var_in_file() { - # ============ Argument parsing ============= - local -A args_array=([f]=file= [k]=key= [a]=after=) - local file - local key - local after - ynh_handle_getopts_args "$@" - after="${after:-}" - # =========================================== - - [[ -f $file ]] || ynh_die --message="File $file does not exists" - - set +o xtrace # set +x - - # Get the line number after which we search for the variable - local line_number=1 - if [[ -n "$after" ]]; then - line_number=$(grep -m1 -n $after $file | cut -d: -f1) - if [[ -z "$line_number" ]]; then - set -o xtrace # set -x - return 1 - fi - fi - - local filename="$(basename -- "$file")" - local ext="${filename##*.}" - local endline=',;' - local assign="=>|:|=" - local comments="#" - local string="\"'" - if [[ "$ext" =~ ^ini|env|toml|yml|yaml$ ]]; then - endline='#' - fi - if [[ "$ext" =~ ^ini|env$ ]]; then - comments="[;#]" - fi - if [[ "php" == "$ext" ]] || [[ "$ext" == "js" ]]; then - comments="//" - fi - local list='\[\s*['$string']?\w+['$string']?\]' - local var_part='^\s*((const|var|let)\s+)?\$?(\w+('$list')*(->|\.|\[))*\s*' - var_part+="[$string]?${key}[$string]?" - var_part+='\s*\]?\s*' - var_part+="($assign)" - var_part+='\s*' - - # Extract the part after assignation sign - local expression_with_comment="$((tail +$line_number ${file} | grep -i -o -P $var_part'\K.*$' || echo YNH_NULL) | head -n1)" - if [[ "$expression_with_comment" == "YNH_NULL" ]]; then - set -o xtrace # set -x - echo YNH_NULL - return 0 - fi - - # Remove comments if needed - local expression="$(echo "$expression_with_comment" | sed "s@${comments}[^$string]*\$@@g" | sed "s@\s*[$endline]*\s*]*\$@@")" - - local first_char="${expression:0:1}" - if [[ "$first_char" == '"' ]]; then - echo "$expression" | grep -m1 -o -P '"\K([^"](\\")?)*[^\\](?=")' | head -n1 | sed 's/\\"/"/g' - elif [[ "$first_char" == "'" ]]; then - echo "$expression" | grep -m1 -o -P "'\K([^'](\\\\')?)*[^\\\\](?=')" | head -n1 | sed "s/\\\\'/'/g" - else - echo "$expression" - fi - set -o xtrace # set -x -} - -# Set a value into heterogeneous file (yaml, json, php, python...) -# -# usage: ynh_write_var_in_file --file=PATH --key=KEY --value=VALUE -# | arg: -f, --file= - the path to the file -# | arg: -k, --key= - the key to set -# | arg: -v, --value= - the value to set -# | arg: -a, --after= - the line just before the key (in case of multiple lines with the name of the key in the file) -# -# Requires YunoHost version 4.3 or higher. -ynh_write_var_in_file() { - # ============ Argument parsing ============= - local -A args_array=([f]=file= [k]=key= [v]=value= [a]=after=) - local file - local key - local value - local after - ynh_handle_getopts_args "$@" - after="${after:-}" - # =========================================== - - [[ -f $file ]] || ynh_die --message="File $file does not exists" - - set +o xtrace # set +x - - # Get the line number after which we search for the variable - local after_line_number=1 - if [[ -n "$after" ]]; then - after_line_number=$(grep -m1 -n $after $file | cut -d: -f1) - if [[ -z "$after_line_number" ]]; then - set -o xtrace # set -x - return 1 - fi - fi - - local filename="$(basename -- "$file")" - local ext="${filename##*.}" - local endline=',;' - local assign="=>|:|=" - local comments="#" - local string="\"'" - if [[ "$ext" =~ ^ini|env|toml|yml|yaml$ ]]; then - endline='#' - fi - if [[ "$ext" =~ ^ini|env$ ]]; then - comments="[;#]" - fi - if [[ "php" == "$ext" ]] || [[ "$ext" == "js" ]]; then - comments="//" - fi - local list='\[\s*['$string']?\w+['$string']?\]' - local var_part='^\s*((const|var|let)\s+)?\$?(\w+('$list')*(->|\.|\[))*\s*' - var_part+="[$string]?${key}[$string]?" - var_part+='\s*\]?\s*' - var_part+="($assign)" - var_part+='\s*' - - # Extract the part after assignation sign - local expression_with_comment="$((tail +$after_line_number ${file} | grep -i -o -P $var_part'\K.*$' || echo YNH_NULL) | head -n1)" - if [[ "$expression_with_comment" == "YNH_NULL" ]]; then - set -o xtrace # set -x - return 1 - fi - local value_line_number="$(tail +$after_line_number ${file} | grep -m1 -n -i -P $var_part'\K.*$' | cut -d: -f1)" - value_line_number=$((after_line_number + value_line_number)) - local range="${after_line_number},${value_line_number} " - - # Remove comments if needed - local expression="$(echo "$expression_with_comment" | sed "s@${comments}[^$string]*\$@@g" | sed "s@\s*[$endline]*\s*]*\$@@")" - endline=${expression_with_comment#"$expression"} - endline="$(echo "$endline" | sed 's/\\/\\\\/g')" - value="$(echo "$value" | sed 's/\\/\\\\/g')" - value=${value//&/"\&"} - local first_char="${expression:0:1}" - delimiter=$'\001' - if [[ "$first_char" == '"' ]]; then - # \ and sed is quite complex you need 2 \\ to get one in a sed - # So we need \\\\ to go through 2 sed - value="$(echo "$value" | sed 's/"/\\\\"/g')" - sed -ri "${range}s$delimiter"'(^'"${var_part}"'")([^"]|\\")*("[\s;,]*)(\s*'$comments'.*)?$'$delimiter'\1'"${value}"'"'"${endline}${delimiter}i" ${file} - elif [[ "$first_char" == "'" ]]; then - # \ and sed is quite complex you need 2 \\ to get one in a sed - # However double quotes implies to double \\ to - # So we need \\\\\\\\ to go through 2 sed and 1 double quotes str - value="$(echo "$value" | sed "s/'/\\\\\\\\'/g")" - sed -ri "${range}s$delimiter(^${var_part}')([^']|\\')*('"'[\s,;]*)(\s*'$comments'.*)?$'$delimiter'\1'"${value}'${endline}${delimiter}i" ${file} - else - if [[ "$value" == *"'"* ]] || [[ "$value" == *'"'* ]] || [[ "$ext" =~ ^php|py|json|js$ ]]; then - value='\"'"$(echo "$value" | sed 's/"/\\\\"/g')"'\"' - fi - if [[ "$ext" =~ ^yaml|yml$ ]]; then - value=" $value" - fi - sed -ri "${range}s$delimiter(^${var_part}).*\$$delimiter\1${value}${endline}${delimiter}i" ${file} - fi - set -o xtrace # set -x -} - -# Render templates with Jinja2 -# -# [internal] -# -# Attention : Variables should be exported before calling this helper to be -# accessible inside templates. -# -# usage: ynh_render_template some_template output_path -# | arg: some_template - Template file to be rendered -# | arg: output_path - The path where the output will be redirected to -ynh_render_template() { - local template_path=$1 - local output_path=$2 - mkdir -p "$(dirname $output_path)" - # Taken from https://stackoverflow.com/a/35009576 - python3 -c 'import os, sys, jinja2; sys.stdout.write( - jinja2.Template(sys.stdin.read() - ).render(os.environ));' <$template_path >$output_path -} - _acceptable_path_to_delete() { local file=$1