Hog v10.14.0
Logger.tcl
Go to the documentation of this file.
1 # Copyright 2018-2026 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  puts "$msg"
114  }
115  if {$qlevel == "error"} {
116  exit 1
117  }
118  }
119  # }
120 }
121 
122 ## @brief Prints a message with selected severity and optionally write into a log file
123 #
124 # @param[in] msg The message to print
125 # @param[in] severity The severity of the message
126 # @param[in] outFile The path of the output logfile
127 #
128 proc MsgAndLog {msg {severity "CriticalWarning"} {outFile ""}} {
129  Msg $severity $msg
130  if {$outFile != ""} {
131  set directory [file dir $outFile]
132  if {![file exists $directory]} {
133  Msg Info "Creating $directory..."
134  file mkdir $directory
135  }
136 
137  set oF [open "$outFile" a+]
138  puts $oF $msg
139  close $oF
140  }
141 }
142 
143 
144 # @brief Print the Hog Logo
145 #
146 # @param[in] repo_path The main path of the git repository (default .)
147 proc Logo {{repo_path .}} {
148  # Msg Warning "HOG_LOGO_PRINTED : $HOG_LOGO_PRINTED"
149  if {![info exists ::env(HOG_LOGO_PRINTED)] || $::env(HOG_LOGO_PRINTED) eq "0"} {
150  if {
151  [info exists ::env(HOG_COLOR)] && ([string match "ENABLED" $::env(HOG_COLOR)] || [string is integer -strict $::env(HOG_COLOR)] && $::env(HOG_COLOR) > 0)
152  } {
153  set logo_file "$repo_path/Hog/images/hog_logo_color.txt"
154  } else {
155  set logo_file "$repo_path/Hog/images/hog_logo.txt"
156  }
157 
158  cd $repo_path/Hog
159  set ver [Git {describe --always}]
160  set old_path [pwd]
161  # set ver [Git {describe --always}]
162 
163  if {[file exists $logo_file]} {
164  set f [open $logo_file "r"]
165  set data [read $f]
166  close $f
167  set lines [split $data "\n"]
168  foreach l $lines {
169  if {[regexp {(Version:)[ ]+} $l -> prefix]} {
170  set string_len [string length $l]
171 
172  set version_string "* Version: $ver"
173  set version_len [string length $version_string]
174  append version_string [string repeat " " [expr {$string_len - $version_len - 1}]] "*"
175  set l $version_string
176  }
177  Msg Status $l
178  }
179  } {
180  Msg CriticalWarning "Logo file: $logo_file not found"
181  }
182 
183 
184  # Msg Status "Version: $ver"
185  cd $old_path
186  }
187 }
188 
189 # Define the procedure to print the content of a file
190 #
191 # @param[in] filename The name of the file to read and print
192 #
193 # @brief This procedure opens the file, reads its content, and prints it to the console.
194 proc PrintFileContent {filename} {
195  # Open the file for reading
196  set file [open $filename r]
197 
198  # Read the content of the file
199  set content [read $file]
200 
201  # Close the file
202  close $file
203 
204  # Print the content of the file
205  puts $content
206 }
207 
208 
209 
210 ## Print a tree-like structure of Hog list file content
211 #
212 # @param[in] data the list of lines read from a list file
213 # @param[in] repo_path the path of the repository
214 # @param[in] indentation a string containing a number of spaces to indent the tree
215 proc PrintFileTree {{data} {repo_path} {indentation ""}} {
216  # Msg Debug "PrintFileTree called with data: $data, repo_path: $repo_path, indentation: $indentation"
217  set print_list {}
218  set last_printed ""
219  foreach line $data {
220  if {![regexp {^[\t\s]*$} $line] & ![regexp {^[\t\s]*\#} $line]} {
221  lappend print_list "$line"
222  }
223  }
224  set i 0
225 
226  foreach p $print_list {
227  incr i
228  if {$i == [llength $print_list]} {
229  set pad "└──"
230  } else {
231  set pad "├──"
232  }
233  set file_name [lindex [split $p] 0]
234  if {[file exists [file normalize [lindex [glob -nocomplain $repo_path/$file_name] 0]]]} {
235  set exists ""
236  } else {
237  set exists " !!!!! NOT FOUND !!!!!"
238  }
239 
240  Msg Status "$indentation$pad$p$exists"
241  set last_printed $file_name
242  }
243 
244  return $last_printed
245 }
246 
247 
248 
249 
250 namespace eval Hog::LoggerLib {
251 
252  variable toml_dict {}
253  variable fullPath
254 
255  ## @brief gets the full path to the file in the user home folder
256  #
257  # @param[in] filename The name of the file to get the path for
258  #
259  # @returns The full path to the file in the user's home directory, or 0 if file doesn't exist
260  #
261  proc GetUserFilePath {filename} {
262  set homeDir [file normalize ~]
263  set fullPath [file join $homeDir $filename]
264  if {[file exists $fullPath]} {
265  return $fullPath
266  } else {
267  return 0
268  }
269  }
270 
271 
272  ## @brief Parse a TOML format file and return the data as a dictionary
273  #
274  # @param[in] toml_file The path to the TOML file to parse
275  #
276  # @returns A nested dictionary containing the TOML data, or -1 in case of failure
277  #
278  proc ParseTOML {toml_file} {
279  variable toml_dict
280 
281  # set toml_dict [dict create \
282  # terminal [dict create logger 0 colored 0] \
283  # verbose [dict create level 4 pidshow 0 linecounter 0 msgtypeCounter 0] \
284  # ]
285  if {![file exists $toml_file]} {
286  Msg Warning "TOML file $toml_file does not exist"
287  return -1
288  }
289  if {[catch {open $toml_file r} file_handle]} {
290  Msg Error "Cannot open TOML file $toml_file: $file_handle"
291  return -1
292  }
293  # set toml_dict [dict create]
294  set current_section ""
295  set line_number 0
296  set in_multiline_string 0
297  set multiline_buffer ""
298  set multiline_key ""
299  while {[gets $file_handle line] >= 0} {
300  incr line_number
301  # Handle multiline strings
302  if {$in_multiline_string} {
303  if {[string match "*\"\"\"*" $line]} {
304  # End of multiline string
305  set end_pos [string first "\"\"\"" $line]
306  append multiline_buffer [string range $line 0 [expr $end_pos - 1]]
307  if {$current_section eq ""} {
308  dict set toml_dict $multiline_key $multiline_buffer
309  } else {
310  dict set toml_dict $current_section $multiline_key $multiline_buffer
311  }
312  set in_multiline_string 0
313  set multiline_buffer ""
314  set multiline_key ""
315  } else {
316  append multiline_buffer $line "\n"
317  }
318  continue
319  }
320  # Remove comments (but preserve # inside strings)
321  set clean_line ""
322  set in_quotes 0
323  set quote_char ""
324  for {set i 0} {$i < [string length $line]} {incr i} {
325  set char [string index $line $i]
326  if {!$in_quotes && ($char eq "\"" || $char eq "'")} {
327  set in_quotes 1
328  set quote_char $char
329  append clean_line $char
330  } elseif {$in_quotes && $char eq $quote_char} {
331  set in_quotes 0
332  set quote_char ""
333  append clean_line $char
334  } elseif {!$in_quotes && $char eq "#"} {
335  break
336  } else {
337  append clean_line $char
338  }
339  }
340  set line [string trim $clean_line]
341  # Skip empty lines
342  if {$line eq ""} {
343  continue
344  }
345  # Handle section headers [section] or [section.subsection]
346  if {[regexp {^\[([^\]]+)\]$} $line match section_name]} {
347  set current_section $section_name
348  # Initialize section if it doesn't exist
349  if {![dict exists $toml_dict $current_section]} {
350  dict set toml_dict $current_section [dict create]
351  }
352  continue
353  }
354  # Handle key-value pairs
355  if {[regexp {^([^=]+)=(.*)$} $line match raw_key raw_value]} {
356  set key [string trim $raw_key]
357  set value [string trim $raw_value]
358  # Handle multiline strings
359  if {[string match "*\"\"\"*" $value] && ![string match "*\"\"\"*\"\"\"*" $value]} {
360  set start_pos [string first "\"\"\"" $value]
361  set multiline_key $key
362  set multiline_buffer [string range $value [expr $start_pos + 3] end]
363  append multiline_buffer "\n"
364  set in_multiline_string 1
365  continue
366  }
367  # Parse the value
368  set parsed_value [ParseTOMLValue $value]
369  # Handle arrays and nested keys
370  if {[string match "*.*" $key]} {
371  set key_parts [split $key "."]
372  set dict_ref toml_dict
373  if {$current_section ne ""} {
374  lappend dict_ref $current_section
375  }
376  for {set i 0} {$i < [expr [llength $key_parts] - 1]} {incr i} {
377  set part [lindex $key_parts $i]
378  lappend dict_ref $part
379  if {![dict exists {*}$dict_ref]} {
380  dict set {*}$dict_ref [dict create]
381  }
382  }
383  set final_key [lindex $key_parts end]
384  lappend dict_ref $final_key
385  dict set {*}$dict_ref $parsed_value
386  } else {
387  # Simple key
388  if {$current_section eq ""} {
389  dict set toml_dict $key $parsed_value
390  } else {
391  dict set toml_dict $current_section $key $parsed_value
392  }
393  }
394  }
395  }
396  close $file_handle
397  return $toml_dict
398  }
399 
400  ## @brief Parse a TOML value and convert it to appropriate TCL type
401  #
402  # @param[in] value The raw value string from TOML
403  #
404  # @returns The parsed value in appropriate TCL format
405  #
406  proc ParseTOMLValue {value} {
407  set value [string trim $value]
408  # Handle boolean values
409  if {$value eq "true"} {
410  return 1
411  } elseif {$value eq "false"} {
412  return 0
413  }
414  # Handle strings (quoted)
415  if {[regexp {^"(.*)"$} $value match string_content]} {
416  # Handle escape sequences
417  set string_content [string map {\\" \" \\\\ \\ \\n \n \\t \t \\r \r} $string_content]
418  return $string_content
419  } elseif {[regexp {^'(.*)'$} $value match string_content]} {
420  # Single quoted strings (literal)
421  return $string_content
422  }
423  # Handle arrays
424  if {[string match {\[*\]} $value]} {
425  set array_content [string range $value 1 end-1]
426  set array_content [string trim $array_content]
427  if {$array_content eq ""} {
428  return [list]
429  }
430  set elements [list]
431  set current_element ""
432  set bracket_depth 0
433  set in_quotes 0
434  set quote_char ""
435  for {set i 0} {$i < [string length $array_content]} {incr i} {
436  set char [string index $array_content $i]
437  if {!$in_quotes && ($char eq "\"" || $char eq "'")} {
438  set in_quotes 1
439  set quote_char $char
440  append current_element $char
441  } elseif {$in_quotes && $char eq $quote_char} {
442  set in_quotes 0
443  set quote_char ""
444  append current_element $char
445  } elseif {!$in_quotes && $char eq "\["} {
446  incr bracket_depth
447  append current_element $char
448  } elseif {!$in_quotes && $char eq "\]"} {
449  incr bracket_depth -1
450  append current_element $char
451  } elseif {!$in_quotes && $char eq "," && $bracket_depth == 0} {
452  lappend elements [ParseTOMLValue [string trim $current_element]]
453  set current_element ""
454  } else {
455  append current_element $char
456  }
457  }
458  if {$current_element ne ""} {
459  lappend elements [ParseTOMLValue [string trim $current_element]]
460  }
461  return $elements
462  }
463  # Handle numbers (integers and floats)
464  if {[string is integer $value]} {
465  return [expr {int($value)}]
466  } elseif {[string is double $value]} {
467  return [expr {double($value)}]
468  }
469  # Handle dates/times as strings for now
470  if {[regexp {^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}} $value]} {
471  return $value
472  }
473  # Return as string if nothing else matches
474  return $value
475  }
476 
477  ## @brief Get a value from a TOML dictionary using dot notation
478  #
479  # @param[in] toml_dict The dictionary returned by ParseTOML
480  # @param[in] key_path The key path in dot notation (e.g., "section.subsection.key")
481  #
482  # @returns The value if found, or empty string if not found
483  #
484  proc GetTOMLValue {toml_dict key_path} {
485  set key_parts [split $key_path "."]
486  set current_dict $toml_dict
487  foreach part $key_parts {
488  if {[dict exists $current_dict $part]} {
489  set current_dict [dict get $current_dict $part]
490  } else {
491  return ""
492  }
493  }
494  return $current_dict
495  }
496 
497  ## @brief Print a TOML dictionary in a readable format
498  #
499  # @param[in] toml_dict The dictionary to print
500  # @param[in] indent Internal parameter for indentation (default: 0)
501  #
502  proc PrintTOMLDict {toml_dict {indent 0}} {
503  set indent_str [string repeat " " $indent]
504  dict for {key value} $toml_dict {
505  if {[string is list $value] && [llength $value] > 1 && [string is list [lindex $value 0]]} {
506  # This is likely a nested dictionary
507  Msg Debug "${indent_str}${key}:"
508  if {[catch {dict for {subkey subvalue} $value {}} result]} {
509  # Not a dictionary, print as value
510  Msg Debug "${indent_str} $value"
511  } else {
512  PrintTOMLDict $value [expr {$indent + 1}]
513  }
514  } elseif {[string is list $value] && [llength $value] > 0} {
515  # This is an array
516  Msg Debug "${indent_str}${key}: \[list of [llength $value] items\]"
517  foreach item $value {
518  Msg Debug "${indent_str} - $item"
519  }
520  } else {
521  Msg Debug "${indent_str}${key}: $value"
522  }
523  }
524  }
525 
526  ## @brief Access the dictionary of the parsed TOML file
527  #
528  # @returns The dictionary containing the parsed TOML data
529  proc GetTOMLDict {} {
530  variable toml_dict
531  if {[info exists toml_dict]} {
532  return $toml_dict
533  }
534  }
535 
536 
537 
538 }
539 
540