aPiecek | 65a6e2b | 2020-12-02 10:43:32 +0100 | [diff] [blame] | 1 | #!/bin/bash |
| 2 | |
| 3 | # @file cstr.sh |
| 4 | # @author Adam Piecek <piecek@cesnet.cz> |
| 5 | # @brief Helper script for creating tests that converts yang and c strings. |
| 6 | # |
| 7 | # Copyright (c) 2020 CESNET, z.s.p.o. |
| 8 | # |
| 9 | # This source code is licensed under BSD 3-Clause License (the "License"). |
| 10 | # You may not use this file except in compliance with the License. |
| 11 | # You may obtain a copy of the License at |
| 12 | # |
| 13 | # https://opensource.org/licenses/BSD-3-Clause |
| 14 | |
| 15 | #---------- Global variables ---------- |
| 16 | |
| 17 | script_name="$(basename $0)" |
| 18 | |
| 19 | # -i <FILE> |
| 20 | target_file="" |
| 21 | target_text="" |
| 22 | |
| 23 | # -l NUM |
| 24 | linenum_flag="" |
| 25 | linenum_default=1 |
| 26 | linenum=$linenum_default |
| 27 | |
| 28 | # -v |
| 29 | verbose="" |
| 30 | |
| 31 | # -y <EXE> |
| 32 | yanglint_flag="" |
| 33 | yanglint_exe="yanglint -f yang" |
| 34 | yanglint_run="" |
| 35 | |
| 36 | # <FILE> |
| 37 | input_text="" |
| 38 | input_file="" |
| 39 | input_ext="" |
| 40 | |
| 41 | tmpfile="" |
| 42 | |
| 43 | #---------- Basic functions ---------- |
| 44 | |
| 45 | function usage(){ |
| 46 | echo "\ |
| 47 | Usage: |
| 48 | $script_name [<INPUT>] [OPTION]... |
| 49 | |
| 50 | Examples: |
| 51 | From the \"test.c\" file, c-string is converted from line 20 to a yang schema. |
| 52 | $script_name test.c -l 20 |
| 53 | The yang scheme is converted from the \"a.yang\" file to c-string and inserted into the \"test.c\" file on line 30. |
| 54 | $script_name a.yang -i test.c -l 30 |
| 55 | |
| 56 | Note: |
| 57 | If <INPUT> missing, read standard input. Press ctrl-d to end the entry. |
| 58 | The c-string is statement starting with character (\") and ending with string (\";). |
| 59 | This script creates a temporary file in the \"/tmp\" directory with the file name <yang_module_name>.yang, |
| 60 | which it eventually deletes when it finishes its run. |
| 61 | " |
| 62 | echo "\ |
| 63 | OPTION: |
| 64 | -c, --combinations # Print a help table with all important parameter combinations and a usage comment. |
| 65 | # |
| 66 | -i, --insert=FILE # Insert c-string to the FILE. |
| 67 | # Option is valid only if <INPUT> is in .yang format and option \"-l\" is set. |
| 68 | # Warning: make sure you have a file backed up so you can undo the change. |
| 69 | # Don't forget to reload the file in your editor to see the changes. |
| 70 | # |
| 71 | -l, --line=NUM # If <INPUT> is in .c format: find c-string on the NUM line. |
| 72 | # If <INPUT> is in .yang format: insert c-string on the NUM-th line. |
| 73 | # If <INPUT> is from standard input, then the parameter is always $linenum_default |
| 74 | # regardless of the value of the --line parameter. |
| 75 | # |
| 76 | -v, --verbose # Print debug messages. |
| 77 | # |
| 78 | -y, --yanglint=EXEC # Run yang schema formatting by \"EXEC\". |
| 79 | # Default value is \"./$yanglint_exe </tmp/TEMPORARY_FILE>\", |
| 80 | # but if the local directory does not contain yanglint, |
| 81 | # then yanglint is taken from PATH. |
| 82 | # Note that parameters must also be entered, eg \"pyang -f tree\". |
| 83 | " | column -t -s "#" |
| 84 | } |
| 85 | |
| 86 | function combinations(){ |
| 87 | echo "\ |
| 88 | Abbreviations: c -> .c format file, y -> .yang format file |
| 89 | All important combinations of parameters are: ( -y, -v parameters are ommitted) |
| 90 | " |
| 91 | echo "\ |
| 92 | <c> -i -l # Not allowed. |
| 93 | <c> -i # Not allowed. |
| 94 | <c> -l # Find c-string on line -l in file/stdin <c>, convert it to .yang and print the result. |
| 95 | <c> # Get c-string on line $linenum_default in file/stdin <c>, convert it to .yang and print the result. |
| 96 | <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. |
| 97 | <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. |
| 98 | <y> -l # Not allowed. |
| 99 | <y> # Get yang schema from file/stdin <y>, convert it to .c format and print the result. |
| 100 | " | column -t -s "#" |
| 101 | } |
| 102 | |
| 103 | function print_verbose() |
| 104 | { |
| 105 | if [ -n "$verbose" ] && [ -n "$1" ]; then echo "Verbose: $1" ; fi |
| 106 | } |
| 107 | |
| 108 | function exit_if_error() |
| 109 | { |
| 110 | if [ $? != 0 ]; then |
| 111 | if [ -n "$1" ]; then |
| 112 | echo "$1" >&2 |
| 113 | fi |
| 114 | exit 1 |
| 115 | fi |
| 116 | } |
| 117 | |
| 118 | function print_error_then_exit() |
| 119 | { |
| 120 | echo "$1" >&2 |
| 121 | exit 1 |
| 122 | } |
| 123 | |
| 124 | function fill_input() { |
| 125 | # check if argument is empty |
| 126 | if [ -z "$1" ]; then |
| 127 | # read from stdin |
| 128 | while IFS= read -r line; do |
| 129 | if [ -z $input_text ] ; then |
| 130 | input_text="$line" |
| 131 | else |
| 132 | input_text="$input_text\n$line" |
| 133 | fi |
| 134 | done |
| 135 | # substitute string \n with newline |
| 136 | input_text="$(echo -e "$input_text")" |
| 137 | print_verbose "Text is loaded from stdin" |
| 138 | else |
| 139 | # check if file exists and is a regular file |
| 140 | if [ -f "$1" ]; then |
| 141 | # <INPUT> is readable file |
| 142 | input_file="$1" |
| 143 | if [ -r $input_file ]; then |
| 144 | # read from file is possible |
| 145 | input_text=$(cat "$input_file") |
| 146 | print_verbose "Text is loaded from \"$input_file\" file" |
| 147 | else |
| 148 | print_error_then_exit "Error: cannot read \"$input_file\"" |
| 149 | fi |
| 150 | else |
| 151 | print_error_then_exit "Error: file \"$1\" cannot open" |
| 152 | fi |
| 153 | fi |
| 154 | # set input_text |
| 155 | # set input_file if file name was entered |
| 156 | } |
| 157 | |
| 158 | |
| 159 | #---------- Getopt ---------- |
| 160 | |
| 161 | # options may be followed by one colon to indicate they have a required argument |
| 162 | options=$(getopt -o ci:hl:vy: -l combinations,insert,help,line:,verbose,yanglint: --name "$0" -- "$@") |
| 163 | exit_if_error "Failed to parse options...exiting." |
| 164 | |
| 165 | eval set -- "$options" |
| 166 | |
| 167 | # extract options and their arguments into variables. |
| 168 | while true ; do |
| 169 | case "$1" in |
| 170 | -c | --combinations ) |
| 171 | combinations |
| 172 | exit 0 |
| 173 | ;; |
| 174 | -i | --insert ) |
| 175 | target_file="$2" |
| 176 | shift 2 |
| 177 | ;; |
| 178 | -h | --help ) |
| 179 | usage |
| 180 | exit 0 |
| 181 | ;; |
| 182 | -l | --line ) |
| 183 | linenum_flag="true" |
| 184 | linenum="$2" |
| 185 | shift 2 |
| 186 | ;; |
| 187 | -v | --verbose ) |
| 188 | verbose="true" |
| 189 | shift |
| 190 | ;; |
| 191 | -y | --yanglint) |
| 192 | yanglint_flag="true" |
| 193 | yanglint_exe="$2" |
| 194 | shift 2 |
| 195 | ;; |
| 196 | -- ) |
| 197 | # set input_text |
| 198 | # set input_file if file name was entered |
| 199 | fill_input $2 |
| 200 | break |
| 201 | ;; |
| 202 | -* ) |
| 203 | usage |
| 204 | print_error_then_exit "$0: error - unrecognized option $1" |
| 205 | ;; |
| 206 | *) |
| 207 | print_error_then_exit "Internal error!" |
| 208 | ;; |
| 209 | esac |
| 210 | done |
| 211 | |
| 212 | #---------- Functions for checking parameters ---------- |
| 213 | |
| 214 | function get_one_line() |
| 215 | { |
| 216 | local text_with_more_lines="$1" |
| 217 | local linenum="$2" |
| 218 | echo "$(echo "$text_with_more_lines" | sed "${linenum}q;d")" |
| 219 | } |
| 220 | |
| 221 | function recognize_format() |
| 222 | { |
| 223 | local text="$1" |
| 224 | local linenum="$2" |
| 225 | local line=$(get_one_line "$text" "$linenum") |
| 226 | local matched_chars=$(expr match "$line" "^\s*\"") |
| 227 | if [ "$matched_chars" == "0" ]; then |
| 228 | echo "yang" |
| 229 | else |
| 230 | echo "c" |
| 231 | fi |
| 232 | } |
| 233 | |
| 234 | #---------- Check parameters ---------- |
| 235 | |
| 236 | # check -y |
| 237 | exe_name=$(echo "$yanglint_exe" | awk '{print $1;}') |
| 238 | if [ -n "$yanglint_flag" ]; then |
| 239 | # try user's exe |
| 240 | command -v "$exe_name" > /dev/null 2>&1 |
| 241 | if [ $? != 0 ] ; then |
| 242 | command -v "./$exe_name" > /dev/null 2>&1 |
| 243 | exit_if_error "Error: cannot find exe \"$exe_name\"" |
| 244 | yanglint_run="./$yanglint_exe" |
| 245 | else |
| 246 | yanglint_run="$yanglint_exe" |
| 247 | fi |
| 248 | print_verbose "Using user's EXE \"$exe_name\"" |
| 249 | else |
| 250 | # try in exe current directory |
| 251 | command -v "./$exe_name" > /dev/null 2>&1 |
| 252 | if [ $? == 0 ] ; then |
| 253 | print_verbose "Using default \"$exe_name\" in current directory" |
| 254 | yanglint_run="./$yanglint_exe" |
| 255 | else |
| 256 | # try PATH's exe |
| 257 | command -v "$exe_name" > /dev/null 2>&1 |
| 258 | exit_if_error "Error: \"$exe_name\" wasn't found in the current directory nor is installed" |
| 259 | print_verbose "Using default \"$exe_name\" from PATH" |
| 260 | yanglint_run="$yanglint_exe" |
| 261 | fi |
| 262 | fi |
| 263 | # yanglint_run must be set |
| 264 | yanglint_run="$yanglint_run"" " # add space due to input filename |
| 265 | |
| 266 | # check <INPUT> |
| 267 | # expected that input_text has string |
| 268 | if [ -n "$input_file" ]; then |
| 269 | # get suffix of the <INPUT> file |
| 270 | input_ext=${input_file##*.} |
| 271 | else |
| 272 | # <INPUT> is from stdin |
| 273 | input_ext=$(recognize_format "$input_text" "1") |
| 274 | fi |
| 275 | print_verbose "<INPUT> is in \"$input_ext\" format" |
| 276 | # input_ext must be set |
| 277 | |
| 278 | # check -i |
| 279 | if [ -n "$target_file" ]; then |
| 280 | # if target_file is writeable |
| 281 | if [ -w $target_file ]; then |
| 282 | print_verbose "target_file $target_file is writeable" |
| 283 | else |
| 284 | print_error_then_exit "Error: cannot insert text to file \"$target_file\"" |
| 285 | fi |
| 286 | # if <INPUT> is yang then -l must be set |
| 287 | if [ "$input_ext" == "yang" ] && [ -n "$linenum_flag" ]; then |
| 288 | print_verbose "-i option is valid" |
| 289 | else |
| 290 | print_error_then_exit "Error: Option -i is valid only if <INPUT> is in .yang format and option \"-l\" is set." |
| 291 | fi |
| 292 | target_text=$(cat "$target_file") |
| 293 | fi |
| 294 | # target_text must be set |
| 295 | |
| 296 | # check -l |
| 297 | if [ -n "$linenum_flag" ]; then |
| 298 | |
| 299 | if [ -z "$input_file" ]; then |
| 300 | # reading <INPUT> from stdin |
| 301 | print_verbose "-l option is ignored because <INPUT> is from stdin." |
| 302 | linenum=$linenum_default |
| 303 | else |
| 304 | if [ "$linenum" -lt "0" ]; then |
| 305 | print_error_then_exit "Error: only positive numbers in --line option are valid" |
| 306 | fi |
| 307 | if [ "$input_ext" == "yang" ]; then |
| 308 | if [ -z "$target_file" ]; then |
| 309 | print_error_then_exit "Error: Option -l with <INPUT> format yang is valid only if option -i is set too." |
| 310 | fi |
| 311 | text4linenum="$target_text" |
| 312 | else |
| 313 | text4linenum="$input_text" |
| 314 | fi |
| 315 | # check if linenum is not too big |
| 316 | lines_count=$(echo "$text4linenum" | wc -l) |
| 317 | if [ "$linenum" -gt "$lines_count" ]; then |
| 318 | print_error_then_exit "Error: number in --line option is too big" |
| 319 | fi |
| 320 | print_verbose "-l option is valid" |
| 321 | fi |
| 322 | else |
| 323 | print_verbose "-l option is not set" |
| 324 | # rest of restrictions must be checked in option -i |
| 325 | fi |
| 326 | |
| 327 | #---------- Formatting text ---------- |
| 328 | |
| 329 | # warning: do not call this function in subshell $(formatting_yang_text) |
| 330 | function formatting_yang_text() |
| 331 | { |
| 332 | # parameters: modify global variable input_text, read yanglint_run, read tmpfile |
| 333 | echo "$input_text" > "$tmpfile" |
| 334 | # check if <INPUT> is valid yang file, store only stderr to variable |
| 335 | yanglint_output=$(eval ""$yanglint_run" "$tmpfile"" 2>&1) # do not add local |
| 336 | local yanglint_retval="$?" |
| 337 | if [ "$yanglint_retval" != "0" ]; then |
| 338 | print_verbose "$yanglint_output" |
| 339 | fi |
| 340 | $(exit $yanglint_retval) |
| 341 | exit_if_error "Error: yang-schema in is not valid." |
| 342 | input_text="$yanglint_output" |
| 343 | } |
| 344 | |
| 345 | #---------- Main functions ---------- |
| 346 | |
| 347 | # called from main run |
| 348 | function cstring2yang() |
| 349 | { |
| 350 | local ret |
| 351 | local input_text="$1" |
| 352 | local linenum="$2" |
| 353 | # extract statement from c language file from specific line |
| 354 | ret=$(echo "$input_text" | |
| 355 | awk -v linenum="$linenum" ' |
| 356 | NR >= linenum {lineflag=1; print} |
| 357 | /;\s*$/ {if(lineflag == 1) exit;} |
| 358 | ') |
| 359 | # substitute special characters - for example \" |
| 360 | ret=$(printf "$ret") |
| 361 | # remove everything before first " and remove last " |
| 362 | ret=$(echo "$ret" | grep -oP '"\K.*' | sed 's/"\s*$//') |
| 363 | # but last line is not right due to "; at the end of line |
| 364 | # so get last line and remove "; |
| 365 | lastline=$(echo "$ret" | tail -n1 | sed -n 's/";\s*$//p') |
| 366 | # get everything before last line |
| 367 | ret=$(echo "$ret" | head -n -1) |
| 368 | # concatenate result |
| 369 | ret=$ret$lastline |
| 370 | echo "$ret" |
| 371 | } |
| 372 | |
| 373 | # called from main run |
| 374 | function yang2cstring() |
| 375 | { |
| 376 | local ret |
| 377 | local input_text="$1" |
| 378 | # backslashing character " |
| 379 | ret=${input_text//\"/\\\"} |
| 380 | ret=$(echo "$ret" | awk -v q="\"" '{print q$0q}') |
| 381 | ret="$ret"";" |
| 382 | echo "$ret" |
| 383 | } |
| 384 | |
| 385 | # called from --Create temporary file-- |
| 386 | function get_yang_module_name() |
| 387 | { |
| 388 | local text="$1" |
| 389 | local linenum="$2" |
| 390 | echo "$(echo "$text" | awk -v linenum="$linenum" 'NR >= linenum' | grep -oP -m 1 "\s*module\s+\K\S+")" |
| 391 | } |
| 392 | |
| 393 | # called from tabs2spaces_in_line and insert_indentation |
| 394 | function number2spaces() |
| 395 | { |
| 396 | local number="$1" |
| 397 | # return string containing <number> spaces |
| 398 | echo "$(printf ' %.0s' $(seq 1 $number))" |
| 399 | } |
| 400 | |
| 401 | # called from count_indentation_in_line function |
| 402 | function tabs2spaces_in_line() |
| 403 | { |
| 404 | local text="$1" |
| 405 | local linenum="$2" |
| 406 | local tabinspaces="$(number2spaces 4)" # 1 tab = 4 spaces |
| 407 | local line="$(get_one_line "$text" "$linenum")" |
| 408 | echo "$(echo "$line" | sed "s/\t/$tabinspaces/")" |
| 409 | } |
| 410 | |
| 411 | # called from main run |
| 412 | function count_indentation_in_line() |
| 413 | { |
| 414 | local text="$1" |
| 415 | local linenum="$2" |
| 416 | local line="$(tabs2spaces_in_line "$1" "$2")" |
| 417 | echo "$(expr match "$line" "^ *")" |
| 418 | } |
| 419 | |
| 420 | # called from main run |
| 421 | function insert_indentation() |
| 422 | { |
| 423 | local text="$1" |
| 424 | local number_of_spaces="$2" # from count_indentation_in_line |
| 425 | local spaces="$(number2spaces "$number_of_spaces")" |
| 426 | echo "$(echo "$text" | sed -e "s/^/$spaces/")" |
| 427 | } |
| 428 | |
| 429 | # called from main run |
| 430 | function insert_text2file() |
| 431 | { |
| 432 | local text="$1" |
| 433 | local linenum="$2" |
| 434 | local filename="$3" |
| 435 | |
| 436 | linenum=$((linenum + 1)) |
| 437 | awk -i inplace -v text="$text" -v linenum="$linenum" 'NR == linenum {print text} 1' $filename |
| 438 | } |
| 439 | |
| 440 | #---------- Create temporary file ---------- |
| 441 | |
| 442 | module_name=$(get_yang_module_name "$input_text" "$linenum") |
| 443 | tmpfile="/tmp/""$module_name"".yang" |
| 444 | touch "$tmpfile" |
| 445 | exit_if_error "Error: error while creating temporary file" |
| 446 | # delete temporary file after script end |
| 447 | trap 'rm -f -- "$tmpfile"' INT TERM HUP EXIT |
| 448 | exit_if_error "Error: trap return error" |
| 449 | |
| 450 | #---------- Main run ---------- |
| 451 | |
| 452 | # print new line for clarity |
| 453 | if [ -z "$input_file" ] && [ -z "$target_file" ]; then |
| 454 | echo "" |
| 455 | fi |
| 456 | |
| 457 | if [ "$input_ext" == "yang" ]; then |
| 458 | if [ -z "$target_file" ]; then |
| 459 | # Options: (<y> -l, <y>, <y-stdin> -l, <y-stdin>) |
| 460 | print_verbose "Print c-string to output" |
| 461 | formatting_yang_text |
| 462 | echo "$(yang2cstring "$input_text")" |
| 463 | else |
| 464 | # Options: (<y-stdin> -i -l, <y> -i -l, <y> -i) |
| 465 | print_verbose "Insert c-string to target_file" |
| 466 | |
| 467 | # formatting and converting |
| 468 | formatting_yang_text |
| 469 | inserted_text="$(yang2cstring "$input_text")" |
| 470 | # add extra backslash |
| 471 | inserted_text=${inserted_text//\\/\\\\} |
| 472 | |
| 473 | # indentation |
| 474 | indentation="$(count_indentation_in_line "$target_text" "$linenum")" |
| 475 | print_verbose "indentation is: $indentation" |
| 476 | inserted_text="$(insert_indentation "$inserted_text" "$indentation")" |
| 477 | |
| 478 | # inserting to file |
| 479 | insert_text2file "$inserted_text" "$linenum" "$target_file" |
| 480 | echo "Done" |
| 481 | fi |
| 482 | elif [ "$input_ext" == "c" ] || [ "$input_ext" == "h" ]; then |
| 483 | # Options: (<c-stdin> -l, <c-stdin>, <c> -l, <c>) |
| 484 | print_verbose "Print yang to output or from file <c> print yang to output" |
| 485 | output="$(cstring2yang "$input_text" "$linenum")" |
| 486 | # "input_text" is input and output parameter for formatting_yang_text |
| 487 | input_text="$output" |
| 488 | formatting_yang_text |
| 489 | echo "$input_text" |
| 490 | else |
| 491 | print_error_then_exit "Error: format \"$input_ext\" is not supported" |
| 492 | fi |
| 493 | |
| 494 | exit 0 |