cstr NEW helper script for creating tests that converts yang and c strings
diff --git a/tests/cstr.sh b/tests/cstr.sh
new file mode 100755
index 0000000..14951b8
--- /dev/null
+++ b/tests/cstr.sh
@@ -0,0 +1,494 @@
+#!/bin/bash
+
+# @file cstr.sh
+# @author Adam Piecek <piecek@cesnet.cz>
+# @brief Helper script for creating tests that converts yang and c strings.
+#
+# Copyright (c) 2020 CESNET, z.s.p.o.
+#
+# This source code is licensed under BSD 3-Clause License (the "License").
+# You may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://opensource.org/licenses/BSD-3-Clause
+
+#---------- Global variables ----------
+
+script_name="$(basename $0)"
+
+# -i <FILE>
+target_file=""
+target_text=""
+
+# -l NUM
+linenum_flag=""
+linenum_default=1
+linenum=$linenum_default
+
+# -v
+verbose=""
+
+# -y <EXE>
+yanglint_flag=""
+yanglint_exe="yanglint -f yang"
+yanglint_run=""
+
+# <FILE>
+input_text=""
+input_file=""
+input_ext=""
+
+tmpfile=""
+
+#---------- Basic functions ----------
+
+function usage(){
+echo "\
+Usage:
+$script_name [<INPUT>] [OPTION]...
+
+Examples:
+From the \"test.c\" file, c-string is converted from line 20 to a yang schema.
+$script_name test.c -l 20
+The yang scheme is converted from the \"a.yang\" file to c-string and inserted into the \"test.c\" file on line 30.
+$script_name a.yang -i test.c -l 30
+
+Note:
+If <INPUT> missing, read standard input. Press ctrl-d to end the entry.
+The c-string is statement starting with character (\") and ending with string (\";).
+This script creates a temporary file in the \"/tmp\" directory with the file name <yang_module_name>.yang,
+which it eventually deletes when it finishes its run.
+"
+echo "\
+OPTION:
+-c, --combinations # Print a help table with all important parameter combinations and a usage comment.
+#
+-i, --insert=FILE # Insert c-string to the FILE.
+ # Option is valid only if <INPUT> is in .yang format and option \"-l\" is set.
+ # Warning: make sure you have a file backed up so you can undo the change.
+ # Don't forget to reload the file in your editor to see the changes.
+#
+-l, --line=NUM # If <INPUT> is in .c format: find c-string on the NUM line.
+ # If <INPUT> is in .yang format: insert c-string on the NUM-th line.
+ # If <INPUT> is from standard input, then the parameter is always $linenum_default
+ # regardless of the value of the --line parameter.
+#
+-v, --verbose # Print debug messages.
+#
+-y, --yanglint=EXEC # Run yang schema formatting by \"EXEC\".
+ # Default value is \"./$yanglint_exe </tmp/TEMPORARY_FILE>\",
+ # but if the local directory does not contain yanglint,
+ # then yanglint is taken from PATH.
+ # Note that parameters must also be entered, eg \"pyang -f tree\".
+" | column -t -s "#"
+}
+
+function combinations(){
+echo "\
+Abbreviations: c -> .c format file, y -> .yang format file
+All important combinations of parameters are: ( -y, -v parameters are ommitted)
+"
+echo "\
+<c> -i -l # Not allowed.
+<c> -i # Not allowed.
+<c> -l # Find c-string on line -l in file/stdin <c>, convert it to .yang and print the result.
+<c> # Get c-string on line $linenum_default in file/stdin <c>, convert it to .yang and print the result.
+<y> -i -l # Get yang schema from file/stdin <y>, convert it to .c format and insert result to the -i file on line -l.
+<y> -i # Get yang schema from file/stdin <y>, convert it to .c format and insert result to the -i file on line $linenum_default.
+<y> -l # Not allowed.
+<y> # Get yang schema from file/stdin <y>, convert it to .c format and print the result.
+" | column -t -s "#"
+}
+
+function print_verbose()
+{
+ if [ -n "$verbose" ] && [ -n "$1" ]; then echo "Verbose: $1" ; fi
+}
+
+function exit_if_error()
+{
+ if [ $? != 0 ]; then
+ if [ -n "$1" ]; then
+ echo "$1" >&2
+ fi
+ exit 1
+ fi
+}
+
+function print_error_then_exit()
+{
+ echo "$1" >&2
+ exit 1
+}
+
+function fill_input() {
+ # check if argument is empty
+ if [ -z "$1" ]; then
+ # read from stdin
+ while IFS= read -r line; do
+ if [ -z $input_text ] ; then
+ input_text="$line"
+ else
+ input_text="$input_text\n$line"
+ fi
+ done
+ # substitute string \n with newline
+ input_text="$(echo -e "$input_text")"
+ print_verbose "Text is loaded from stdin"
+ else
+ # check if file exists and is a regular file
+ if [ -f "$1" ]; then
+ # <INPUT> is readable file
+ input_file="$1"
+ if [ -r $input_file ]; then
+ # read from file is possible
+ input_text=$(cat "$input_file")
+ print_verbose "Text is loaded from \"$input_file\" file"
+ else
+ print_error_then_exit "Error: cannot read \"$input_file\""
+ fi
+ else
+ print_error_then_exit "Error: file \"$1\" cannot open"
+ fi
+ fi
+ # set input_text
+ # set input_file if file name was entered
+}
+
+
+#---------- Getopt ----------
+
+# options may be followed by one colon to indicate they have a required argument
+options=$(getopt -o ci:hl:vy: -l combinations,insert,help,line:,verbose,yanglint: --name "$0" -- "$@")
+exit_if_error "Failed to parse options...exiting."
+
+eval set -- "$options"
+
+# extract options and their arguments into variables.
+while true ; do
+ case "$1" in
+ -c | --combinations )
+ combinations
+ exit 0
+ ;;
+ -i | --insert )
+ target_file="$2"
+ shift 2
+ ;;
+ -h | --help )
+ usage
+ exit 0
+ ;;
+ -l | --line )
+ linenum_flag="true"
+ linenum="$2"
+ shift 2
+ ;;
+ -v | --verbose )
+ verbose="true"
+ shift
+ ;;
+ -y | --yanglint)
+ yanglint_flag="true"
+ yanglint_exe="$2"
+ shift 2
+ ;;
+ -- )
+ # set input_text
+ # set input_file if file name was entered
+ fill_input $2
+ break
+ ;;
+ -* )
+ usage
+ print_error_then_exit "$0: error - unrecognized option $1"
+ ;;
+ *)
+ print_error_then_exit "Internal error!"
+ ;;
+ esac
+done
+
+#---------- Functions for checking parameters ----------
+
+function get_one_line()
+{
+ local text_with_more_lines="$1"
+ local linenum="$2"
+ echo "$(echo "$text_with_more_lines" | sed "${linenum}q;d")"
+}
+
+function recognize_format()
+{
+ local text="$1"
+ local linenum="$2"
+ local line=$(get_one_line "$text" "$linenum")
+ local matched_chars=$(expr match "$line" "^\s*\"")
+ if [ "$matched_chars" == "0" ]; then
+ echo "yang"
+ else
+ echo "c"
+ fi
+}
+
+#---------- Check parameters ----------
+
+# check -y
+exe_name=$(echo "$yanglint_exe" | awk '{print $1;}')
+if [ -n "$yanglint_flag" ]; then
+ # try user's exe
+ command -v "$exe_name" > /dev/null 2>&1
+ if [ $? != 0 ] ; then
+ command -v "./$exe_name" > /dev/null 2>&1
+ exit_if_error "Error: cannot find exe \"$exe_name\""
+ yanglint_run="./$yanglint_exe"
+ else
+ yanglint_run="$yanglint_exe"
+ fi
+ print_verbose "Using user's EXE \"$exe_name\""
+else
+ # try in exe current directory
+ command -v "./$exe_name" > /dev/null 2>&1
+ if [ $? == 0 ] ; then
+ print_verbose "Using default \"$exe_name\" in current directory"
+ yanglint_run="./$yanglint_exe"
+ else
+ # try PATH's exe
+ command -v "$exe_name" > /dev/null 2>&1
+ exit_if_error "Error: \"$exe_name\" wasn't found in the current directory nor is installed"
+ print_verbose "Using default \"$exe_name\" from PATH"
+ yanglint_run="$yanglint_exe"
+ fi
+fi
+# yanglint_run must be set
+yanglint_run="$yanglint_run"" " # add space due to input filename
+
+# check <INPUT>
+# expected that input_text has string
+if [ -n "$input_file" ]; then
+ # get suffix of the <INPUT> file
+ input_ext=${input_file##*.}
+else
+ # <INPUT> is from stdin
+ input_ext=$(recognize_format "$input_text" "1")
+fi
+print_verbose "<INPUT> is in \"$input_ext\" format"
+# input_ext must be set
+
+# check -i
+if [ -n "$target_file" ]; then
+ # if target_file is writeable
+ if [ -w $target_file ]; then
+ print_verbose "target_file $target_file is writeable"
+ else
+ print_error_then_exit "Error: cannot insert text to file \"$target_file\""
+ fi
+ # if <INPUT> is yang then -l must be set
+ if [ "$input_ext" == "yang" ] && [ -n "$linenum_flag" ]; then
+ print_verbose "-i option is valid"
+ else
+ print_error_then_exit "Error: Option -i is valid only if <INPUT> is in .yang format and option \"-l\" is set."
+ fi
+ target_text=$(cat "$target_file")
+fi
+# target_text must be set
+
+# check -l
+if [ -n "$linenum_flag" ]; then
+
+ if [ -z "$input_file" ]; then
+ # reading <INPUT> from stdin
+ print_verbose "-l option is ignored because <INPUT> is from stdin."
+ linenum=$linenum_default
+ else
+ if [ "$linenum" -lt "0" ]; then
+ print_error_then_exit "Error: only positive numbers in --line option are valid"
+ fi
+ if [ "$input_ext" == "yang" ]; then
+ if [ -z "$target_file" ]; then
+ print_error_then_exit "Error: Option -l with <INPUT> format yang is valid only if option -i is set too."
+ fi
+ text4linenum="$target_text"
+ else
+ text4linenum="$input_text"
+ fi
+ # check if linenum is not too big
+ lines_count=$(echo "$text4linenum" | wc -l)
+ if [ "$linenum" -gt "$lines_count" ]; then
+ print_error_then_exit "Error: number in --line option is too big"
+ fi
+ print_verbose "-l option is valid"
+ fi
+else
+ print_verbose "-l option is not set"
+ # rest of restrictions must be checked in option -i
+fi
+
+#---------- Formatting text ----------
+
+# warning: do not call this function in subshell $(formatting_yang_text)
+function formatting_yang_text()
+{
+ # parameters: modify global variable input_text, read yanglint_run, read tmpfile
+ echo "$input_text" > "$tmpfile"
+ # check if <INPUT> is valid yang file, store only stderr to variable
+ yanglint_output=$(eval ""$yanglint_run" "$tmpfile"" 2>&1) # do not add local
+ local yanglint_retval="$?"
+ if [ "$yanglint_retval" != "0" ]; then
+ print_verbose "$yanglint_output"
+ fi
+ $(exit $yanglint_retval)
+ exit_if_error "Error: yang-schema in is not valid."
+ input_text="$yanglint_output"
+}
+
+#---------- Main functions ----------
+
+# called from main run
+function cstring2yang()
+{
+ local ret
+ local input_text="$1"
+ local linenum="$2"
+ # extract statement from c language file from specific line
+ ret=$(echo "$input_text" |
+ awk -v linenum="$linenum" '
+ NR >= linenum {lineflag=1; print}
+ /;\s*$/ {if(lineflag == 1) exit;}
+ ')
+ # substitute special characters - for example \"
+ ret=$(printf "$ret")
+ # remove everything before first " and remove last "
+ ret=$(echo "$ret" | grep -oP '"\K.*' | sed 's/"\s*$//')
+ # but last line is not right due to "; at the end of line
+ # so get last line and remove ";
+ lastline=$(echo "$ret" | tail -n1 | sed -n 's/";\s*$//p')
+ # get everything before last line
+ ret=$(echo "$ret" | head -n -1)
+ # concatenate result
+ ret=$ret$lastline
+ echo "$ret"
+}
+
+# called from main run
+function yang2cstring()
+{
+ local ret
+ local input_text="$1"
+ # backslashing character "
+ ret=${input_text//\"/\\\"}
+ ret=$(echo "$ret" | awk -v q="\"" '{print q$0q}')
+ ret="$ret"";"
+ echo "$ret"
+}
+
+# called from --Create temporary file--
+function get_yang_module_name()
+{
+ local text="$1"
+ local linenum="$2"
+ echo "$(echo "$text" | awk -v linenum="$linenum" 'NR >= linenum' | grep -oP -m 1 "\s*module\s+\K\S+")"
+}
+
+# called from tabs2spaces_in_line and insert_indentation
+function number2spaces()
+{
+ local number="$1"
+ # return string containing <number> spaces
+ echo "$(printf ' %.0s' $(seq 1 $number))"
+}
+
+# called from count_indentation_in_line function
+function tabs2spaces_in_line()
+{
+ local text="$1"
+ local linenum="$2"
+ local tabinspaces="$(number2spaces 4)" # 1 tab = 4 spaces
+ local line="$(get_one_line "$text" "$linenum")"
+ echo "$(echo "$line" | sed "s/\t/$tabinspaces/")"
+}
+
+# called from main run
+function count_indentation_in_line()
+{
+ local text="$1"
+ local linenum="$2"
+ local line="$(tabs2spaces_in_line "$1" "$2")"
+ echo "$(expr match "$line" "^ *")"
+}
+
+# called from main run
+function insert_indentation()
+{
+ local text="$1"
+ local number_of_spaces="$2" # from count_indentation_in_line
+ local spaces="$(number2spaces "$number_of_spaces")"
+ echo "$(echo "$text" | sed -e "s/^/$spaces/")"
+}
+
+# called from main run
+function insert_text2file()
+{
+ local text="$1"
+ local linenum="$2"
+ local filename="$3"
+
+ linenum=$((linenum + 1))
+ awk -i inplace -v text="$text" -v linenum="$linenum" 'NR == linenum {print text} 1' $filename
+}
+
+#---------- Create temporary file ----------
+
+module_name=$(get_yang_module_name "$input_text" "$linenum")
+tmpfile="/tmp/""$module_name"".yang"
+touch "$tmpfile"
+exit_if_error "Error: error while creating temporary file"
+# delete temporary file after script end
+trap 'rm -f -- "$tmpfile"' INT TERM HUP EXIT
+exit_if_error "Error: trap return error"
+
+#---------- Main run ----------
+
+# print new line for clarity
+if [ -z "$input_file" ] && [ -z "$target_file" ]; then
+ echo ""
+fi
+
+if [ "$input_ext" == "yang" ]; then
+ if [ -z "$target_file" ]; then
+ # Options: (<y> -l, <y>, <y-stdin> -l, <y-stdin>)
+ print_verbose "Print c-string to output"
+ formatting_yang_text
+ echo "$(yang2cstring "$input_text")"
+ else
+ # Options: (<y-stdin> -i -l, <y> -i -l, <y> -i)
+ print_verbose "Insert c-string to target_file"
+
+ # formatting and converting
+ formatting_yang_text
+ inserted_text="$(yang2cstring "$input_text")"
+ # add extra backslash
+ inserted_text=${inserted_text//\\/\\\\}
+
+ # indentation
+ indentation="$(count_indentation_in_line "$target_text" "$linenum")"
+ print_verbose "indentation is: $indentation"
+ inserted_text="$(insert_indentation "$inserted_text" "$indentation")"
+
+ # inserting to file
+ insert_text2file "$inserted_text" "$linenum" "$target_file"
+ echo "Done"
+ fi
+elif [ "$input_ext" == "c" ] || [ "$input_ext" == "h" ]; then
+ # Options: (<c-stdin> -l, <c-stdin>, <c> -l, <c>)
+ print_verbose "Print yang to output or from file <c> print yang to output"
+ output="$(cstring2yang "$input_text" "$linenum")"
+ # "input_text" is input and output parameter for formatting_yang_text
+ input_text="$output"
+ formatting_yang_text
+ echo "$input_text"
+else
+ print_error_then_exit "Error: format \"$input_ext\" is not supported"
+fi
+
+exit 0