#!/bin/bash # @file cstr.sh # @author Adam Piecek # @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 target_file="" target_text="" # -l NUM linenum_flag="" linenum_default=1 linenum=$linenum_default # -v verbose="" # -y yanglint_flag="" yanglint_exe="yanglint -f yang" yanglint_run="" # input_text="" input_file="" input_ext="" tmpfile="" #---------- Basic functions ---------- function usage(){ echo "\ Usage: $script_name [] [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 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, 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 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 is in .c format: find c-string on the NUM line. # If is in .yang format: insert c-string on the NUM-th line. # If 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 \", # 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 "\ -i -l # Not allowed. -i # Not allowed. -l # Find c-string on line -l in file/stdin , convert it to .yang and print the result. # Get c-string on line $linenum_default in file/stdin , convert it to .yang and print the result. -i -l # Get yang schema from file/stdin , convert it to .c format and insert result to the -i file on line -l. -i # Get yang schema from file/stdin , convert it to .c format and insert result to the -i file on line $linenum_default. -l # Not allowed. # Get yang schema from file/stdin , 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 # 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 # expected that input_text has string if [ -n "$input_file" ]; then # get suffix of the file input_ext=${input_file##*.} else # is from stdin input_ext=$(recognize_format "$input_text" "1") fi print_verbose " 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 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 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 from stdin print_verbose "-l option is ignored because 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 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 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 s="\"" -v e="\\\n\"" '{print s$0e}') ret="$ret"";" echo "$ret" } # called from --Create temporary file-- function get_yang_module_name() { local text="$1" local linenum="$2" local input_ext="$3" if [ "$input_ext" == "yang" ]; then # module name search on line 1 in .yang file linenum=1 fi # else get module name on line $linenum in .c file 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 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" "$input_ext") if [ -z "$module_name" ]; then print_error_then_exit "Error: module name not found" fi 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: ( -l, , -l, ) print_verbose "Print c-string to output" formatting_yang_text echo "$(yang2cstring "$input_text")" else # Options: ( -i -l, -i -l, -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: ( -l, , -l, ) print_verbose "Print yang to output or from file 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