blob: e672bc1f5ad5b7992ec3f80bcbade30e409aed2a [file] [log] [blame]
aPiecek65a6e2b2020-12-02 10:43:32 +01001#!/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
17script_name="$(basename $0)"
18
19# -i <FILE>
20target_file=""
21target_text=""
22
23# -l NUM
24linenum_flag=""
25linenum_default=1
26linenum=$linenum_default
27
28# -v
29verbose=""
30
31# -y <EXE>
32yanglint_flag=""
33yanglint_exe="yanglint -f yang"
34yanglint_run=""
35
36# <FILE>
37input_text=""
38input_file=""
39input_ext=""
40
41tmpfile=""
42
43#---------- Basic functions ----------
44
45function usage(){
46echo "\
47Usage:
48$script_name [<INPUT>] [OPTION]...
49
50Examples:
51From the \"test.c\" file, c-string is converted from line 20 to a yang schema.
52$script_name test.c -l 20
53The 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
56Note:
57If <INPUT> missing, read standard input. Press ctrl-d to end the entry.
58The c-string is statement starting with character (\") and ending with string (\";).
59This script creates a temporary file in the \"/tmp\" directory with the file name <yang_module_name>.yang,
60which it eventually deletes when it finishes its run.
61"
62echo "\
63OPTION:
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
86function combinations(){
87echo "\
88Abbreviations: c -> .c format file, y -> .yang format file
89All important combinations of parameters are: ( -y, -v parameters are ommitted)
90"
91echo "\
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
103function print_verbose()
104{
105 if [ -n "$verbose" ] && [ -n "$1" ]; then echo "Verbose: $1" ; fi
106}
107
108function 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
118function print_error_then_exit()
119{
120 echo "$1" >&2
121 exit 1
122}
123
124function 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
162options=$(getopt -o ci:hl:vy: -l combinations,insert,help,line:,verbose,yanglint: --name "$0" -- "$@")
163exit_if_error "Failed to parse options...exiting."
164
165eval set -- "$options"
166
167# extract options and their arguments into variables.
168while 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
210done
211
212#---------- Functions for checking parameters ----------
213
214function 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
221function 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
237exe_name=$(echo "$yanglint_exe" | awk '{print $1;}')
238if [ -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\""
249else
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
262fi
263# yanglint_run must be set
264yanglint_run="$yanglint_run"" " # add space due to input filename
265
266# check <INPUT>
267# expected that input_text has string
268if [ -n "$input_file" ]; then
269 # get suffix of the <INPUT> file
270 input_ext=${input_file##*.}
271else
272 # <INPUT> is from stdin
273 input_ext=$(recognize_format "$input_text" "1")
274fi
275print_verbose "<INPUT> is in \"$input_ext\" format"
276# input_ext must be set
277
278# check -i
279if [ -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")
293fi
294# target_text must be set
295
296# check -l
297if [ -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
322else
323 print_verbose "-l option is not set"
324 # rest of restrictions must be checked in option -i
325fi
326
327#---------- Formatting text ----------
328
329# warning: do not call this function in subshell $(formatting_yang_text)
330function 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
348function 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
374function yang2cstring()
375{
376 local ret
377 local input_text="$1"
378 # backslashing character "
379 ret=${input_text//\"/\\\"}
aPiecek67d04572021-01-08 11:24:29 +0100380 ret=$(echo "$ret" | awk -v s="\"" -v e="\\\n\"" '{print s$0e}')
aPiecek65a6e2b2020-12-02 10:43:32 +0100381 ret="$ret"";"
382 echo "$ret"
383}
384
385# called from --Create temporary file--
386function get_yang_module_name()
387{
388 local text="$1"
389 local linenum="$2"
aPiecek67d04572021-01-08 11:24:29 +0100390 local input_ext="$3"
391 if [ "$input_ext" == "yang" ]; then
392 # module name search on line 1 in .yang file
393 linenum=1
394 fi
395 # else get module name on line $linenum in .c file
aPiecek65a6e2b2020-12-02 10:43:32 +0100396 echo "$(echo "$text" | awk -v linenum="$linenum" 'NR >= linenum' | grep -oP -m 1 "\s*module\s+\K\S+")"
397}
398
399# called from tabs2spaces_in_line and insert_indentation
400function number2spaces()
401{
402 local number="$1"
403 # return string containing <number> spaces
404 echo "$(printf ' %.0s' $(seq 1 $number))"
405}
406
407# called from count_indentation_in_line function
408function tabs2spaces_in_line()
409{
410 local text="$1"
411 local linenum="$2"
412 local tabinspaces="$(number2spaces 4)" # 1 tab = 4 spaces
413 local line="$(get_one_line "$text" "$linenum")"
414 echo "$(echo "$line" | sed "s/\t/$tabinspaces/")"
415}
416
417# called from main run
418function count_indentation_in_line()
419{
420 local text="$1"
421 local linenum="$2"
422 local line="$(tabs2spaces_in_line "$1" "$2")"
423 echo "$(expr match "$line" "^ *")"
424}
425
426# called from main run
427function insert_indentation()
428{
429 local text="$1"
430 local number_of_spaces="$2" # from count_indentation_in_line
431 local spaces="$(number2spaces "$number_of_spaces")"
432 echo "$(echo "$text" | sed -e "s/^/$spaces/")"
433}
434
435# called from main run
436function insert_text2file()
437{
438 local text="$1"
439 local linenum="$2"
440 local filename="$3"
441
442 linenum=$((linenum + 1))
443 awk -i inplace -v text="$text" -v linenum="$linenum" 'NR == linenum {print text} 1' $filename
444}
445
446#---------- Create temporary file ----------
447
aPiecek67d04572021-01-08 11:24:29 +0100448module_name=$(get_yang_module_name "$input_text" "$linenum" "$input_ext")
449if [ -z "$module_name" ]; then
450 print_error_then_exit "Error: module name not found"
451fi
aPiecek65a6e2b2020-12-02 10:43:32 +0100452tmpfile="/tmp/""$module_name"".yang"
453touch "$tmpfile"
454exit_if_error "Error: error while creating temporary file"
455# delete temporary file after script end
456trap 'rm -f -- "$tmpfile"' INT TERM HUP EXIT
457exit_if_error "Error: trap return error"
458
459#---------- Main run ----------
460
461# print new line for clarity
462if [ -z "$input_file" ] && [ -z "$target_file" ]; then
463 echo ""
464fi
465
466if [ "$input_ext" == "yang" ]; then
467 if [ -z "$target_file" ]; then
468 # Options: (<y> -l, <y>, <y-stdin> -l, <y-stdin>)
469 print_verbose "Print c-string to output"
470 formatting_yang_text
471 echo "$(yang2cstring "$input_text")"
472 else
473 # Options: (<y-stdin> -i -l, <y> -i -l, <y> -i)
474 print_verbose "Insert c-string to target_file"
475
476 # formatting and converting
477 formatting_yang_text
478 inserted_text="$(yang2cstring "$input_text")"
479 # add extra backslash
480 inserted_text=${inserted_text//\\/\\\\}
481
482 # indentation
483 indentation="$(count_indentation_in_line "$target_text" "$linenum")"
484 print_verbose "indentation is: $indentation"
485 inserted_text="$(insert_indentation "$inserted_text" "$indentation")"
486
487 # inserting to file
488 insert_text2file "$inserted_text" "$linenum" "$target_file"
489 echo "Done"
490 fi
491elif [ "$input_ext" == "c" ] || [ "$input_ext" == "h" ]; then
492 # Options: (<c-stdin> -l, <c-stdin>, <c> -l, <c>)
493 print_verbose "Print yang to output or from file <c> print yang to output"
494 output="$(cstring2yang "$input_text" "$linenum")"
495 # "input_text" is input and output parameter for formatting_yang_text
496 input_text="$output"
497 formatting_yang_text
498 echo "$input_text"
499else
500 print_error_then_exit "Error: format \"$input_ext\" is not supported"
501fi
502
503exit 0