| #!/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 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 <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" "$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: (<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 |