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