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