Hog v9.61.0
Logger.tcl
Go to the documentation of this file.
1 # Copyright 2018-2025 The University of Birmingham
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 # @file Logger.tcl
16 # Logger functions for the Hog project
17 
18 
19 set DEBUG_MODE 0
20 
21 proc setDebugMode {mode} {
22  global DEBUG_MODE
23  set DEBUG_MODE $mode
24 }
25 
26 proc getDebugMode {} {
27  global DEBUG_MODE
28  return $DEBUG_MODE
29 }
30 
31 proc printDebugMode {} {
32  global DEBUG_MODE
33  if {$DEBUG_MODE} {
34  Msg Info "DEBUG_MODE is set to $DEBUG_MODE"
35  } else {
36  Msg Info "DEBUG_MODE is not set or is 0"
37  }
38 }
39 
40 ## @brief Safely get a value from a dictionary
41 #
42 # @param[in] d The dictionary to search
43 # @param[in] args The keys to look for
44 proc dictSafeGet {d args} {
45  if {[dict exists $d {*}$args]} {
46  return [dict get $d {*}$args]
47  } else {
48  return ""
49  }
50 }
51 
52 ## @brief The Hog Printout Msg function
53 #
54 # @param[in] level The severity level (status, info, warning, critical, error, debug)
55 # @param[in] msg The message to print
56 # @param[in] title The title string to be included in the header of the message [Hog:$title] (default "")
57 proc Msg {level fmsg {title ""}} {
58  # foreach msg [split $fmsg "\n"] {
59  set msg $fmsg
60  set level [string tolower $level]
61  if {$title == ""} {set title [lindex [info level [expr {[info level] - 1}]] 0]}
62  if {$level == 0 || $level == "status" || $level == "extra_info"} {
63  set vlevel {STATUS}
64  set qlevel info
65  } elseif {$level == 1 || $level == "info"} {
66  set vlevel {INFO}
67  set qlevel info
68  } elseif {$level == 2 || $level == "warning"} {
69  set vlevel {WARNING}
70  set qlevel warning
71  } elseif {$level == 3 || [string first "critical" $level] != -1} {
72  set vlevel {CRITICAL WARNING}
73  set qlevel critical_warning
74  } elseif {$level == 4 || $level == "error"} {
75  set vlevel {ERROR}
76  set qlevel error
77  } elseif {$level == 5 || $level == "debug"} {
78  if {([info exists ::DEBUG_MODE] && $::DEBUG_MODE == 1) || (
79  [info exists ::env(HOG_DEBUG_MODE)] && $::env(HOG_DEBUG_MODE) == 1
80  )} {
81  set vlevel {STATUS}
82  set qlevel extra_info
83  set msg "DEBUG: \[Hog:$title\] $msg"
84  } else {
85  return
86  }
87  } else {
88  puts "Hog Error: level $level not defined"
89  exit -1
90  }
91  if {[IsXilinx]} {
92  # Vivado
93  if {[string match "-*" $msg]} {
94  set msg " $msg"
95  }
96  set status [catch {send_msg_id Hog:$title-0 $vlevel "$msg"}]
97  if {$status != 0} {
98  exit $status
99  }
100  } elseif {[IsQuartus]} {
101  # Quartus
102  post_message -type $qlevel "Hog:$title $msg"
103  if {$qlevel == "error"} {
104  exit 1
105  }
106  } else {
107  # Tcl Shell / Libero
108  if {$vlevel != "STATUS"} {
109  puts "$vlevel: \[Hog:$title\] $msg"
110  } else {
111  # temporary solution to avoid removing of leading
112  set HogEnvDict [Hog::LoggerLib::GetTOMLDict]
113  if {
114  ([dictSafeGet $HogEnvDict terminal colored] > 0) ||
115  ([info exists ::env(HOG_COLOR)] &&
116  ([string match "ENABLED" $::env(HOG_COLOR)] ||
117  ([string is integer -strict $::env(HOG_COLOR)] && $::env(HOG_COLOR) > 0)
118  )
119  )||
120  ([dictSafeGet $HogEnvDict terminal logger] > 0) ||
121  ([info exists ::env(HOG_LOGGER)] && ([string match "ENABLED" $::env(HOG_LOGGER)]))
122  } {
123  puts "LogHelp:$msg"
124  } else {
125  puts $msg
126  }
127  }
128  if {$qlevel == "error"} {
129  exit 1
130  }
131  }
132  # }
133 }
134 
135 ## @brief Prints a message with selected severity and optionally write into a log file
136 #
137 # @param[in] msg The message to print
138 # @param[in] severity The severity of the message
139 # @param[in] outFile The path of the output logfile
140 #
141 proc MsgAndLog {msg {severity "CriticalWarning"} {outFile ""}} {
142  Msg $severity $msg
143  if {$outFile != ""} {
144  set directory [file dir $outFile]
145  if {![file exists $directory]} {
146  Msg Info "Creating $directory..."
147  file mkdir $directory
148  }
149 
150  set oF [open "$outFile" a+]
151  puts $oF $msg
152  close $oF
153  }
154 }
155 
156 
157 # @brief Print the Hog Logo
158 #
159 # @param[in] repo_path The main path of the git repository (default .)
160 proc Logo {{repo_path .}} {
161  # Msg Warning "HOG_LOGO_PRINTED : $HOG_LOGO_PRINTED"
162  if {![info exists ::env(HOG_LOGO_PRINTED)] || $::env(HOG_LOGO_PRINTED) eq "0"} {
163  if {
164  [info exists ::env(HOG_COLOR)] && ([string match "ENABLED" $::env(HOG_COLOR)] || [string is integer -strict $::env(HOG_COLOR)] && $::env(HOG_COLOR) > 0)
165  } {
166  set logo_file "$repo_path/Hog/images/hog_logo_color.txt"
167  } else {
168  set logo_file "$repo_path/Hog/images/hog_logo.txt"
169  }
170 
171  cd $repo_path/Hog
172  set ver [Git {describe --always}]
173  set old_path [pwd]
174  # set ver [Git {describe --always}]
175 
176  if {[file exists $logo_file]} {
177  set f [open $logo_file "r"]
178  set data [read $f]
179  close $f
180  set lines [split $data "\n"]
181  foreach l $lines {
182  if {[regexp {(Version:)[ ]+} $l -> prefix]} {
183  set string_len [string length $l]
184 
185  set version_string "* Version: $ver"
186  set version_len [string length $version_string]
187  append version_string [string repeat " " [expr {$string_len - $version_len - 1}]] "*"
188  set l $version_string
189  }
190  Msg Status $l
191  }
192  } {
193  Msg CriticalWarning "Logo file: $logo_file not found"
194  }
195 
196 
197  # Msg Status "Version: $ver"
198  cd $old_path
199  }
200 }
201 
202 # Define the procedure to print the content of a file
203 #
204 # @param[in] filename The name of the file to read and print
205 #
206 # @brief This procedure opens the file, reads its content, and prints it to the console.
207 proc PrintFileContent {filename} {
208  # Open the file for reading
209  set file [open $filename r]
210 
211  # Read the content of the file
212  set content [read $file]
213 
214  # Close the file
215  close $file
216 
217  # Print the content of the file
218  puts $content
219 }
220 
221 
222 
223 ## Print a tree-like structure of Hog list file content
224 #
225 # @param[in] data the list of lines read from a list file
226 # @param[in] repo_path the path of the repository
227 # @param[in] indentation a string containing a number of spaces to indent the tree
228 proc PrintFileTree {{data} {repo_path} {indentation ""}} {
229  # Msg Debug "PrintFileTree called with data: $data, repo_path: $repo_path, indentation: $indentation"
230  set print_list {}
231  set last_printed ""
232  foreach line $data {
233  if {![regexp {^[\t\s]*$} $line] & ![regexp {^[\t\s]*\#} $line]} {
234  lappend print_list "$line"
235  }
236  }
237  set i 0
238 
239  foreach p $print_list {
240  incr i
241  if {$i == [llength $print_list]} {
242  set pad "└──"
243  } else {
244  set pad "├──"
245  }
246  set file_name [lindex [split $p] 0]
247  if {[file exists [file normalize [lindex [glob -nocomplain $repo_path/$file_name] 0]]]} {
248  set exists ""
249  } else {
250  set exists " !!!!! NOT FOUND !!!!!"
251  }
252 
253  Msg Status "$indentation$pad$p$exists"
254  set last_printed $file_name
255  }
256 
257  return $last_printed
258 }
259 
260 
261 
262 
263 namespace eval Hog::LoggerLib {
264 
265  variable toml_dict {}
266  variable fullPath
267 
268  ## @brief gets the full path to the file in the user home folder
269  #
270  # @param[in] filename The name of the file to get the path for
271  #
272  # @returns The full path to the file in the user's home directory, or 0 if file doesn't exist
273  #
274  proc GetUserFilePath {filename} {
275  set homeDir [file normalize ~]
276  set fullPath [file join $homeDir $filename]
277  if {[file exists $fullPath]} {
278  return $fullPath
279  } else {
280  return 0
281  }
282  }
283 
284 
285  ## @brief Parse a TOML format file and return the data as a dictionary
286  #
287  # @param[in] toml_file The path to the TOML file to parse
288  #
289  # @returns A nested dictionary containing the TOML data, or -1 in case of failure
290  #
291  proc ParseTOML {toml_file} {
292  variable toml_dict
293 
294  # set toml_dict [dict create \
295  # terminal [dict create logger 0 colored 0] \
296  # verbose [dict create level 4 pidshow 0 linecounter 0 msgtypeCounter 0] \
297  # ]
298  if {![file exists $toml_file]} {
299  Msg Warning "TOML file $toml_file does not exist"
300  return -1
301  }
302  if {[catch {open $toml_file r} file_handle]} {
303  Msg Error "Cannot open TOML file $toml_file: $file_handle"
304  return -1
305  }
306  # set toml_dict [dict create]
307  set current_section ""
308  set line_number 0
309  set in_multiline_string 0
310  set multiline_buffer ""
311  set multiline_key ""
312  while {[gets $file_handle line] >= 0} {
313  incr line_number
314  # Handle multiline strings
315  if {$in_multiline_string} {
316  if {[string match "*\"\"\"*" $line]} {
317  # End of multiline string
318  set end_pos [string first "\"\"\"" $line]
319  append multiline_buffer [string range $line 0 [expr $end_pos - 1]]
320  if {$current_section eq ""} {
321  dict set toml_dict $multiline_key $multiline_buffer
322  } else {
323  dict set toml_dict $current_section $multiline_key $multiline_buffer
324  }
325  set in_multiline_string 0
326  set multiline_buffer ""
327  set multiline_key ""
328  } else {
329  append multiline_buffer $line "\n"
330  }
331  continue
332  }
333  # Remove comments (but preserve # inside strings)
334  set clean_line ""
335  set in_quotes 0
336  set quote_char ""
337  for {set i 0} {$i < [string length $line]} {incr i} {
338  set char [string index $line $i]
339  if {!$in_quotes && ($char eq "\"" || $char eq "'")} {
340  set in_quotes 1
341  set quote_char $char
342  append clean_line $char
343  } elseif {$in_quotes && $char eq $quote_char} {
344  set in_quotes 0
345  set quote_char ""
346  append clean_line $char
347  } elseif {!$in_quotes && $char eq "#"} {
348  break
349  } else {
350  append clean_line $char
351  }
352  }
353  set line [string trim $clean_line]
354  # Skip empty lines
355  if {$line eq ""} {
356  continue
357  }
358  # Handle section headers [section] or [section.subsection]
359  if {[regexp {^\[([^\]]+)\]$} $line match section_name]} {
360  set current_section $section_name
361  # Initialize section if it doesn't exist
362  if {![dict exists $toml_dict $current_section]} {
363  dict set toml_dict $current_section [dict create]
364  }
365  continue
366  }
367  # Handle key-value pairs
368  if {[regexp {^([^=]+)=(.*)$} $line match raw_key raw_value]} {
369  set key [string trim $raw_key]
370  set value [string trim $raw_value]
371  # Handle multiline strings
372  if {[string match "*\"\"\"*" $value] && ![string match "*\"\"\"*\"\"\"*" $value]} {
373  set start_pos [string first "\"\"\"" $value]
374  set multiline_key $key
375  set multiline_buffer [string range $value [expr $start_pos + 3] end]
376  append multiline_buffer "\n"
377  set in_multiline_string 1
378  continue
379  }
380  # Parse the value
381  set parsed_value [ParseTOMLValue $value]
382  # Handle arrays and nested keys
383  if {[string match "*.*" $key]} {
384  set key_parts [split $key "."]
385  set dict_ref toml_dict
386  if {$current_section ne ""} {
387  lappend dict_ref $current_section
388  }
389  for {set i 0} {$i < [expr [llength $key_parts] - 1]} {incr i} {
390  set part [lindex $key_parts $i]
391  lappend dict_ref $part
392  if {![dict exists {*}$dict_ref]} {
393  dict set {*}$dict_ref [dict create]
394  }
395  }
396  set final_key [lindex $key_parts end]
397  lappend dict_ref $final_key
398  dict set {*}$dict_ref $parsed_value
399  } else {
400  # Simple key
401  if {$current_section eq ""} {
402  dict set toml_dict $key $parsed_value
403  } else {
404  dict set toml_dict $current_section $key $parsed_value
405  }
406  }
407  }
408  }
409  close $file_handle
410  return $toml_dict
411  }
412 
413  ## @brief Parse a TOML value and convert it to appropriate TCL type
414  #
415  # @param[in] value The raw value string from TOML
416  #
417  # @returns The parsed value in appropriate TCL format
418  #
419  proc ParseTOMLValue {value} {
420  set value [string trim $value]
421  # Handle boolean values
422  if {$value eq "true"} {
423  return 1
424  } elseif {$value eq "false"} {
425  return 0
426  }
427  # Handle strings (quoted)
428  if {[regexp {^"(.*)"$} $value match string_content]} {
429  # Handle escape sequences
430  set string_content [string map {\\" \" \\\\ \\ \\n \n \\t \t \\r \r} $string_content]
431  return $string_content
432  } elseif {[regexp {^'(.*)'$} $value match string_content]} {
433  # Single quoted strings (literal)
434  return $string_content
435  }
436  # Handle arrays
437  if {[string match {\[*\]} $value]} {
438  set array_content [string range $value 1 end-1]
439  set array_content [string trim $array_content]
440  if {$array_content eq ""} {
441  return [list]
442  }
443  set elements [list]
444  set current_element ""
445  set bracket_depth 0
446  set in_quotes 0
447  set quote_char ""
448  for {set i 0} {$i < [string length $array_content]} {incr i} {
449  set char [string index $array_content $i]
450  if {!$in_quotes && ($char eq "\"" || $char eq "'")} {
451  set in_quotes 1
452  set quote_char $char
453  append current_element $char
454  } elseif {$in_quotes && $char eq $quote_char} {
455  set in_quotes 0
456  set quote_char ""
457  append current_element $char
458  } elseif {!$in_quotes && $char eq "\["} {
459  incr bracket_depth
460  append current_element $char
461  } elseif {!$in_quotes && $char eq "\]"} {
462  incr bracket_depth -1
463  append current_element $char
464  } elseif {!$in_quotes && $char eq "," && $bracket_depth == 0} {
465  lappend elements [ParseTOMLValue [string trim $current_element]]
466  set current_element ""
467  } else {
468  append current_element $char
469  }
470  }
471  if {$current_element ne ""} {
472  lappend elements [ParseTOMLValue [string trim $current_element]]
473  }
474  return $elements
475  }
476  # Handle numbers (integers and floats)
477  if {[string is integer $value]} {
478  return [expr {int($value)}]
479  } elseif {[string is double $value]} {
480  return [expr {double($value)}]
481  }
482  # Handle dates/times as strings for now
483  if {[regexp {^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}} $value]} {
484  return $value
485  }
486  # Return as string if nothing else matches
487  return $value
488  }
489 
490  ## @brief Get a value from a TOML dictionary using dot notation
491  #
492  # @param[in] toml_dict The dictionary returned by ParseTOML
493  # @param[in] key_path The key path in dot notation (e.g., "section.subsection.key")
494  #
495  # @returns The value if found, or empty string if not found
496  #
497  proc GetTOMLValue {toml_dict key_path} {
498  set key_parts [split $key_path "."]
499  set current_dict $toml_dict
500  foreach part $key_parts {
501  if {[dict exists $current_dict $part]} {
502  set current_dict [dict get $current_dict $part]
503  } else {
504  return ""
505  }
506  }
507  return $current_dict
508  }
509 
510  ## @brief Print a TOML dictionary in a readable format
511  #
512  # @param[in] toml_dict The dictionary to print
513  # @param[in] indent Internal parameter for indentation (default: 0)
514  #
515  proc PrintTOMLDict {toml_dict {indent 0}} {
516  set indent_str [string repeat " " $indent]
517  dict for {key value} $toml_dict {
518  if {[string is list $value] && [llength $value] > 1 && [string is list [lindex $value 0]]} {
519  # This is likely a nested dictionary
520  Msg Debug "${indent_str}${key}:"
521  if {[catch {dict for {subkey subvalue} $value {}} result]} {
522  # Not a dictionary, print as value
523  Msg Debug "${indent_str} $value"
524  } else {
525  PrintTOMLDict $value [expr {$indent + 1}]
526  }
527  } elseif {[string is list $value] && [llength $value] > 0} {
528  # This is an array
529  Msg Debug "${indent_str}${key}: \[list of [llength $value] items\]"
530  foreach item $value {
531  Msg Debug "${indent_str} - $item"
532  }
533  } else {
534  Msg Debug "${indent_str}${key}: $value"
535  }
536  }
537  }
538 
539  ## @brief Access the dictionary of the parsed TOML file
540  #
541  # @returns The dictionary containing the parsed TOML data
542  proc GetTOMLDict {} {
543  variable toml_dict
544  if {[info exists toml_dict]} {
545  return $toml_dict
546  }
547  }
548 
549 
550 
551 }
552 
553