Loading...
Searching...
No Matches
logging.f90
Go to the documentation of this file.
1!> @file
2!! @defgroup group_logging Logging
3!! Global logging, ANSI-colored diagnostics, and pretty error/warning reporting for fpx
4!!
5!! This module is the central place for all human-readable output in the fpx preprocessor.
6!! It provides:
7!! - Full ANSI color and style support (bold, underline, colors, etc.)
8!! - Structured diagnostic messages with source context, line numbers, and caret markers
9!! - Pretty-printed multi-line error/warning/help/note/info reports
10!! - Label-based highlighting of specific code ranges (like rustc-style diagnostics)
11!! - Recursive sub-diagnostic support for nested explanations
12!!
13!! Designed to produce modern, readable, IDE-friendly output similar to rustc, clang, or cargo.
14!! When `nocolor = .true.` (or terminal does not support ANSI), falls back to plain text.
15!!
16!! @section logging_examples Examples
17!!
18!! 1. Simple colored message (used internally for verbose logging):
19!! @code{.f90}
20!! use fpx_logging
21!!
22!! verbose = .true.
23!! print '(A)', render('Macro expanded: PI = 3.14159')
24!! @endcode
25!!
26!! 2. Full diagnostic report (like a compiler error):
27!! @code{.f90}
28!! character(*), parameter :: input = &
29!! '# This is a TOML document.' // nl // &
30!! 'title = "TOML Example"' // nl // &
31!! '[owner]' // nl // &
32!! 'name = "Tom Preston-Werner"' // nl // &
33!! 'dob = 1979-05-27T07:32:00-08:00 # First class dates' // nl // &
34!! '[database]' // nl // &
35!! 'server = "192.168.1.1"' // nl // &
36!! 'ports = [ 8001, 8001, 8002 ]' // nl // &
37!! 'connection_max = 5000' // nl // &
38!! 'enabled = true' // nl // &
39!! '[servers]' // nl // &
40!! ' # Indentation (tabs and/or spaces) is allowed but not required' // nl // &
41!! ' [servers.alpha]' // nl // &
42!! ' ip = "10.0.0.1"' // nl // &
43!! ' dc = "eqdc10"' // nl // &
44!! ' [servers.beta]' // nl // &
45!! ' ip = "10.0.0.2"' // nl // &
46!! ' dc = "eqdc10"' // nl // &
47!! '[title]' // nl // &
48!! 'data = [ ["gamma", "delta"], [1, 2] ]' // nl // &
49!! '# Line breaks are OK when inside arrays' // nl // &
50!! 'hosts = [' // nl // &
51!! ' "alpha",' // nl // &
52!! ' "omega"' // nl // &
53!! ']'
54!!
55!! print '(A)', render(diagnostic_report(level_error, &
56!! message="duplicated key 'title' found", &
57!! source="example.toml", &
58!! label=[label_type("table 'title' redefined here", 19, 2, 5, .true.), &
59!! label_type("first defined here", 2, 1, 5)]), &
60!! input)
61!! end
62!! @endcode
63!!
64!! Output might look like (colored in terminal):
65!! @code
66!! error: duplicated key 'title' found
67!! --> example.toml:19:2-6
68!! |
69!! 1 | # This is a TOML document.
70!! 2 | title = "TOML Example"
71!! | ----- first defined here
72!! 3 | [owner]
73!! :
74!! 18 | dc = "eqdc10"
75!! 19 | [title]
76!! | ^^^^^ table 'title' redefined here
77!! 20 | data = [ ["gamma", "delta"], [1, 2] ]
78!! |
79!! @endcode
80!!
81!! @par ANSI style & color reference (used internally)
82!! - Styles: BOLD_ON, UNDERLINE_ON, INVERSE_ON, STRIKETHROUGH_ON, ...
83!! - Foreground: RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, ...
84!! - Background: same as foreground but prefixed with BG_
85!!
86!! @note This code is adapted from [pretty-diagnostics](https://github.com/awvwgk/pretty-diagnostics).
87module fpx_logging
88 use iso_c_binding
89
90 implicit none; private
91
92 public :: render, &
93 printf, &
95 label_type, &
96 level_error, &
97 level_warning, &
98 level_help, &
99 level_note, &
100 level_info
101
102 !> @brief Master switch for verbose diagnostic output
103 !! Default value is `.false.` (quiet mode).
104 !! Set to `.true.` to get detailed step-by-step information about
105 !! preprocessing actions. Safe to modify at any time � the change takes
106 !! effect immediately for all subsequent operations.
107 !! @ingroup group_logging
108 logical, public :: verbose
109
110 !> @brief Switch for controling the ANSI color output
111 !! Default value is `.true.` (color mode on).
112 !! Set to `.false.` to get raw string output.
113 !! @ingroup group_logging
114 logical, public :: nocolor = .false.
115
116 character(1), parameter :: nl = new_line('a') !< New line character.
117 character(1), parameter :: escape = achar(27) !< '\' character.
118 character(2), parameter :: code_start = escape // '[' !< Start ansi code, "\‍[".
119 character(1), parameter :: code_end = 'm' !< End ansi code, "m".
120 character(4), parameter :: code_clear = code_start // '0' // code_end !< Clear all styles, "\‍[0m".
121
122 character(17), parameter :: styles(1:2, 1:16) = reshape([ &
123 'BOLD_ON ', '1 ', & ! Bold on.
124 'ITALICS_ON ', '3 ', & ! Italics on.
125 'UNDERLINE_ON ', '4 ', & ! Underline on.
126 'INVERSE_ON ', '7 ', & ! Inverse on: reverse foreground and background colors.
127 'STRIKETHROUGH_ON ', '9 ', & ! Strikethrough on.
128 'BOLD_OFF ', '22 ', & ! Bold off.
129 'ITALICS_OFF ', '23 ', & ! Italics off.
130 'UNDERLINE_OFF ', '24 ', & ! Underline off.
131 'INVERSE_OFF ', '27 ', & ! Inverse off: reverse foreground and background colors.
132 'STRIKETHROUGH_OFF', '29 ', & ! Strikethrough off.
133 'FRAMED_ON ', '51 ', & ! Framed on.
134 'ENCIRCLED_ON ', '52 ', & ! Encircled on.
135 'OVERLINED_ON ', '53 ', & ! Overlined on.
136 'FRAMED_OFF ', '54 ', & ! Framed off.
137 'ENCIRCLED_OFF ', '54 ', & ! Encircled off.
138 'OVERLINED_OFF ', '55 ' & ! Overlined off.
139 ], [2, 16]) !< Styles.
140
141 character(15), parameter :: colors_fg(1:2, 1:17) = reshape([ &
142 'BLACK ', '30 ', & ! Black.
143 'RED ', '31 ', & ! Red.
144 'GREEN ', '32 ', & ! Green.
145 'YELLOW ', '33 ', & ! Yellow.
146 'BLUE ', '34 ', & ! Blue.
147 'MAGENTA ', '35 ', & ! Magenta.
148 'CYAN ', '36 ', & ! Cyan.
149 'WHITE ', '37 ', & ! White.
150 'DEFAULT ', '39 ', & ! Default (white).
151 'BLACK_INTENSE ', '90 ', & ! Black intense.
152 'RED_INTENSE ', '91 ', & ! Red intense.
153 'GREEN_INTENSE ', '92 ', & ! Green intense.
154 'YELLOW_INTENSE ', '93 ', & ! Yellow intense.
155 'BLUE_INTENSE ', '94 ', & ! Blue intense.
156 'MAGENTA_INTENSE', '95 ', & ! Magenta intense.
157 'CYAN_INTENSE ', '96 ', & ! Cyan intense.
158 'WHITE_INTENSE ', '97 ' & ! White intense.
159 ], [2, 17]) !< Foreground colors.
160
161 character(15), parameter :: colors_bg(1:2, 1:17) = reshape([ &
162 'BLACK ', '40 ', & ! Black.
163 'RED ', '41 ', & ! Red.
164 'GREEN ', '42 ', & ! Green.
165 'YELLOW ', '43 ', & ! Yellow.
166 'BLUE ', '44 ', & ! Blue.
167 'MAGENTA ', '45 ', & ! Magenta.
168 'CYAN ', '46 ', & ! Cyan.
169 'WHITE ', '47 ', & ! White.
170 'DEFAULT ', '49 ', & ! Default (black).
171 'BLACK_INTENSE ', '100 ', & ! Black intense.
172 'RED_INTENSE ', '101 ', & ! Red intense.
173 'GREEN_INTENSE ', '102 ', & ! Green intense.
174 'YELLOW_INTENSE ', '103 ', & ! Yellow intense.
175 'BLUE_INTENSE ', '104 ', & ! Blue intense.
176 'MAGENTA_INTENSE', '105 ', & ! Magenta intense.
177 'CYAN_INTENSE ', '106 ', & ! Cyan intense.
178 'WHITE_INTENSE ', '107 ' & ! White intense.
179 ], [2, 17]) !< Background colors.
180
181 !> Interface to render diagnostic messages and labels
182 !!
183 !! @b Remarks
184 !! @ingroup group_logging
185 interface render
186 module procedure :: render_diagnostic
187 module procedure :: render_text
188 module procedure :: render_text_with_label
189 module procedure :: render_text_with_labels
190 end interface
191
192 enum, bind(c)
193 enumerator :: LEVEL_ERROR = 0
194 enumerator :: LEVEL_WARNING = 1
195 enumerator :: LEVEL_HELP = 2
196 enumerator :: LEVEL_NOTE = 3
197 enumerator :: LEVEL_INFO = 4
198 end enum
199
200 !> Represents text as a sequence of ASCII code units.
201 !! The derived type wraps an allocatable character array.
202 !!
203 !! <h2 class="groupheader">Examples</h2>
204 !! @code{.f90}
205 !! type(string) :: s
206 !! s = 'foo'
207 !! @endcode
208 !!
209 !! <h2 class="groupheader">Constructors</h2>
210 !! Initializes a new instance of the label_type class
211 !! <h3>label_type(character(*), integer, integer, (optional) integer)</h3>
212 !! @verbatim type(string) function string(character(*) text, integer first, integer length, (optional) integer level) @endverbatim
213 !!
214 !! @param[in] text Text displayed next to the label
215 !! @param[in] first Position of the label
216 !! @param[in] length Length of the label
217 !! @param[in] level (optional) Level of the label
218 !!
219 !! @b Examples
220 !! @code{.f90}
221 !! type(label_type) :: label
222 !! label = label_type('Syntax error', 5, 7)
223 !! @endcode
224 !! @return The constructed label_type object.
225 !!
226 !! <h3>label_type(integer, character(*), integer, integer, (optional) integer, (optional) logical)</h3>
227 !! @verbatim type(string) function label_type(integer line, character(*) text, integer first, integer length, (optional) integer level, (optional) logical primary) @endverbatim
228 !!
229 !! @param[in] line line number for the label
230 !! @param[in] text Text displayed next to the label
231 !! @param[in] first Position of the label
232 !! @param[in] length Length of the label
233 !! @param[in] level (optional) Level of the label
234 !! @param[in] primary .true. if the label is the primary one
235 !!
236 !! @b Examples
237 !! @code{.f90}
238 !! type(label_type) :: label
239 !! label = label_type(1, 'Syntax error', 5, 7, LEVEL_ERROR, .true.)
240 !! @endcode
241 !! @return The constructed label_type object.
242 !!
243 !! <h2 class="groupheader">Remarks</h2>
244 !!
245 !! @ingroup group_logging
247 !> Level of message
248 integer, allocatable :: level
249 !> Primary message
250 logical :: primary
251 !> Line number of message
252 integer :: line
253 !> First character of message
254 integer :: first
255 !> Last character of message
256 integer :: last
257 !> Message text
258 character(:), allocatable :: text
259 !> Identifier of context
260 character(:), allocatable :: source
261 end type
262
263 interface label_type
264 module procedure :: label_new
265 module procedure :: label_new_with_line
266 end interface
267
268 !> Definition of diagnostic message
270 !> Level of message
271 integer :: level
272 !> Primary message
273 character(:), allocatable :: message
274 !> Context of the diagnostic source
275 character(:), allocatable :: source
276 !> Messages associated with this diagnostic
277 type(label_type), allocatable :: label(:)
278 !> Additional diagnostic information
279 type(diagnostic_report), allocatable :: sub(:)
280 end type
281
282 interface diagnostic_report
283 module procedure diagnostic_new
284 end interface
285
286 !! @private
287 type :: line_token
288 integer :: first, last
289 end type
290
291contains
292
293 !> Colorize and stylize strings, DEFAULT kind.
294 !! @param[in] string Input string.
295 !! @param[in] foreground Foreground color definition.
296 !! @param[in] background Background color definition.
297 !! @param[in] style Style definition.
298 pure function colorize(string, foreground, background, style) result(res)
299 character(*), intent(in) :: string
300 character(*), intent(in), optional :: foreground
301 character(*), intent(in), optional :: background
302 character(*), intent(in), optional :: style
303 character(:), allocatable :: res
304 !private
305 integer :: i
306
307 res = string
308 if (nocolor) return
309 if (present(foreground)) then
310 i = color_index(upper(foreground))
311 if (i > 0) res = code_start // trim(colors_fg(2, i)) // code_end // res // code_clear
312 end if
313 if (present(background)) then
314 i = color_index(upper(background))
315 if (i > 0) res = code_start // trim(colors_bg(2, i)) // code_end // res // code_clear
316 end if
317 if (present(style)) then
318 i = style_index(upper(style))
319 if (i > 0) res = code_start // trim(styles(2, i)) // code_end // res // code_clear
320 end if
321 end function
322
323 !> Return the array-index corresponding to the queried color.
324 !! @note Because Foreground and backround colors lists share the same name,
325 !! no matter what array is used to find the color index.
326 !! Thus, the foreground array is used.
327 elemental integer function color_index(color) result(res)
328 character(*), intent(in) :: color !< Color definition.
329 !private
330 integer :: i
331
332 res = 0
333 do i = 1, size(colors_fg, dim=2)
334 if (trim(colors_fg(1, i)) == trim(adjustl(color))) then
335 res = i
336 exit
337 end if
338 end do
339 end function
340
341 !> Return the array-index corresponding to the queried style.
342 elemental integer function style_index(style) result(res)
343 character(*), intent(in) :: style !< Style definition.
344 !private
345 integer :: i
346
347 res = 0
348 do i = 1, size(styles, dim=2)
349 if (trim(styles(1, i)) == trim(adjustl(style))) then
350 res = i
351 exit
352 end if
353 end do
354 end function
355
356 !> Return a string with all uppercase characters.
357 elemental function upper(string)
358 character(*), intent(in) :: string !< Input string.
359 character(len(string)) :: upper !< Upper case string.
360 !private
361 integer, parameter :: a = iachar('a'), z = iachar('z'), case_diff = iachar('a') - iachar('A')
362 integer :: i, ichar
363
364 do i = 1, len(string)
365 ichar = iachar(string(i:i))
366 if (ichar >= a .and. ichar <= z) ichar = ichar - case_diff
367 upper(i:i) = achar(ichar)
368 end do
369 end function
370
371 type(label_type) pure function label_new(text, first, length, level) result(that)
372 character(*), intent(in) :: text
373 integer, intent(in) :: first
374 integer, intent(in) :: length
375 integer, intent(in), optional :: level
376
377 that%text = text
378 that%line = 1
379 that%first = max(1, first)
380 that%last = that%first + length
381 that%primary = .true.
382 if (present(level)) that%level = level
383 end function
384
385 type(label_type) pure function label_new_with_line(line, text, first, length, primary, level) result(that)
386 integer, intent(in) :: line
387 character(*), intent(in) :: text
388 integer, intent(in) :: first
389 integer, intent(in) :: length
390 logical, intent(in), optional :: primary
391 integer, intent(in), optional :: level
392
393 that%text = text
394 that%line = line
395 that%first = max(1, first)
396 that%last = that%first + length
397 if (present(primary)) then
398 that%primary = primary
399 else
400 that%primary = .true.
401 end if
402 if (present(level)) that%level = level
403 end function
404
405 !> Create new diagnostic message
406 !! @param[in] level Level of message
407 !! @param[in] message Primary message
408 !! @param[in] source Context of the diagnostic source
409 !! @param[in] label Messages associated with this diagnostic
410 !! @param[in] diagnostic Additional diagnostic information
411 type(diagnostic_report) function diagnostic_new(level, message, source, label, diagnostic) result(that)
412 integer, intent(in) :: level
413 character(*), intent(in), optional :: message
414 character(*), intent(in), optional :: source
415 type(label_type), intent(in), optional :: label(..)
416 type(diagnostic_report), intent(in), optional :: diagnostic(:)
417 !private
418 integer :: i
419
420 that%level = level
421 if (present(message)) that%message = message
422 if (present(source)) that%source = source
423 if (present(label)) then
424 if (allocated(that%label)) deallocate(that%label)
425 select rank (label)
426 rank(0)
427 allocate(that%label(1))
428 that%label(1) = label
429 if (.not. allocated(that%label(1)%level)) that%label(1)%level = level
430 rank(1)
431 allocate(that%label, source=label)
432 do i = 1, size(label)
433 if (.not. allocated(that%label(i)%level)) that%label(i)%level = level
434 end do
435 end select
436 end if
437 if (present(diagnostic)) that%sub = diagnostic
438
439 if (allocated(that%label)) then
440 if (.not. any(that%label(:)%primary)) then
441 that%label(1)%primary = .true.
442 end if
443 end if
444 end function
445
446 pure function line_tokens(input) result(res)
447 character(*), intent(in) :: input
448 type(line_token), allocatable :: res(:)
449 !private
450 integer :: first, last
451
452 first = 1
453 last = 0
454 allocate(res(0))
455 do while (first <= len(input))
456 last = index(input(first + 1:), nl) + first - 1
457 if (last < first) then
458 last = len(input)
459 end if
460
461 res = [res, line_token(first, last)]
462
463 first = last + (1 + len(nl))
464 end do
465 end function
466
467 pure recursive function render_diagnostic(diag, input, linemum) result(res)
468 type(diagnostic_report), intent(in) :: diag
469 character(*), intent(in) :: input
470 integer, intent(in), optional :: linemum
471 character(:), allocatable :: res
472 !private
473 integer :: i
474
475 res = render_message(diag%level, diag%message)
476
477 if (allocated(diag%label)) then
478 res = res // nl // render_text_with_labels(input, diag%label, source=diag%source, linemum=linemum)
479 else
480 res = res // nl // render_text_with_labels(input, [label_type('', 1, len_trim(input))], source=diag%source, linemum=&
481 linemum)
482 end if
483
484 if (allocated(diag%sub)) then
485 do i = 1, size(diag%sub)
486 res = res // nl // render_diagnostic(diag%sub(i), input, linemum)
487 end do
488 end if
489 end function
490
491 pure function render_message(level, message) result(res)
492 integer, intent(in) :: level
493 character(*), intent(in), optional :: message
494 character(:), allocatable :: res
495
496 if (present(message)) then
497 res = level_name(level) // colorize(': ' // message, style='bold_on')
498 else
499 res = level_name(level)
500 end if
501 end function
502
503 pure function level_name(level) result(res)
504 integer, intent(in) :: level
505 character(:), allocatable :: res
506
507 select case (level)
508 case (level_error)
509 res = colorize('error', foreground='red', style='bold_on')
510 case (level_warning)
511 res = colorize('warning', foreground='yellow', style='bold_on')
512 case (level_help)
513 res = colorize('help', foreground='cyan', style='bold_on')
514 case (level_note)
515 res = colorize('note', foreground='blue', style='bold_on')
516 case (level_info)
517 res = colorize('info', foreground='magenta', style='bold_on')
518 case default
519 res = colorize('unknown', foreground='blue', style='bold_on')
520 end select
521 end function
522
523 pure function render_source(source, offset) result(res)
524 character(*), intent(in) :: source
525 integer, intent(in) :: offset
526 character(:), allocatable :: res
527
528 res = repeat(' ', offset) // colorize('-->', foreground='blue') // ' ' // source
529 end function
530
531 pure function render_text(input, source, linenum) result(res)
532 character(*), intent(in) :: input
533 character(*), intent(in), optional :: source
534 integer, intent(in), optional :: linenum
535 character(:), allocatable :: res
536 !private
537 integer :: i, offset, iline
538 type(line_token), allocatable :: token(:)
539
540 iline = 1; if (present(linenum)) iline = linenum
541 token = line_tokens(input)
542 offset = integer_width(iline)
543
544 if (present(source)) then
545 res = render_source(source, offset) // nl // &
546 repeat(' ', offset + 1) // colorize('|', foreground='blue')
547 else
548 res = repeat(' ', offset + 1) // colorize('|', foreground='blue')
549 end if
550
551 do i = 1, size(token)
552 res = res // nl // render_line(input(token(i)%first:token(i)%last), to_string(iline + i - 1, offset))
553 end do
554 res = res // nl // repeat(' ', offset + 1) // colorize('|', foreground='blue')
555 end function
556
557 pure function render_text_with_label(input, label, source, linenum) result(res)
558 character(*), intent(in) :: input
559 type(label_type), intent(in) :: label
560 character(*), intent(in), optional :: source
561 integer, intent(in), optional :: linenum
562 character(:), allocatable :: res
563
564 res = render_text_with_labels(input, [label], source, linenum)
565 end function
566
567 pure function render_text_with_labels(input, labels, source, linemum) result(res)
568 character(*), intent(in) :: input
569 type(label_type), intent(in) :: labels(:)
570 character(*), intent(in), optional :: source
571 integer, intent(in), optional :: linemum
572 character(:), allocatable :: res
573 !private
574 integer :: i, j, offset, first, last, iline
575 type(line_token), allocatable :: token(:)
576 logical, allocatable :: display(:)
577
578 token = line_tokens(input)
579 first = max(1, minval(labels%line) - 1)
580 last = min(size(token), maxval(labels%line) + 1)
581 iline = 1; if (present(linemum)) iline = linemum
582 offset = integer_width(iline)
583
584 i = 1 ! Without a primary we use the first label
585 do j = 1, size(labels)
586 if (labels(j)%primary) then
587 i = j
588 exit
589 end if
590 end do
591
592 if (present(source)) then
593 res = render_source(source, offset) // ':' // &
594 to_string(labels(i)%line) // ':' // &
595 to_string(labels(i)%first) // '-' // to_string(labels(i)%last) // nl // &
596 repeat(' ', offset + 1) // colorize('|', foreground='blue')
597 else
598 res = repeat(' ', offset + 1) // colorize('|', foreground='blue')
599 end if
600
601 allocate(display(first:last), source=.false.)
602 do j = 1, size(labels)
603 display(max(first, labels(j)%line - 1):min(last, labels(j)%line + 1)) = .true.
604 end do
605
606 do i = first, last
607 if (.not. display(i)) then
608 if (display(i - 1)) then
609 res = res // nl //&
610 repeat(' ', offset + 1) // colorize(':', foreground='blue')
611 end if
612 cycle
613 end if
614
615 res = res // nl //&
616 & render_line(input(token(i)%first:token(i)%last), &
617 & to_string(iline + i - 1, offset))
618 if (any(i == labels%line)) then
619 do j = 1, size(labels)
620 if (labels(j)%line /= i) cycle
621 res = res // nl //&
622 & repeat(' ', offset + 1) // colorize('|', foreground='blue') // &
623 & render_label(labels(j))
624 end do
625 end if
626 end do
627 res = res // nl // repeat(' ', offset + 1) // colorize('|', foreground='blue')
628 end function
629
630 pure function render_label(label) result(res)
631 type(label_type), intent(in) :: label
632 character(:), allocatable :: res
633 !private
634 integer :: width
635 character(1) :: marker
636 character(:), allocatable :: this_color
637
638 marker = merge('^', '-', label%primary)
639 width = label%last - label%first
640
641 if (allocated(label%level)) then
642 select case (label%level)
643 case (level_error)
644 res = repeat(' ', label%first) // colorize(repeat(marker, width), foreground='red')
645 if (allocated(label%text)) then
646 res = res // ' ' // colorize(label%text, foreground='red')
647 end if
648 case (level_warning)
649 res = repeat(' ', label%first) // colorize(repeat(marker, width), foreground='yellow')
650 if (allocated(label%text)) then
651 res = res // ' ' // colorize(label%text, foreground='yellow')
652 end if
653 case (level_help)
654 res = repeat(' ', label%first) // colorize(repeat(marker, width), foreground='cyan')
655 if (allocated(label%text)) then
656 res = res // ' ' // colorize(label%text, foreground='cyan')
657 end if
658 case (level_info)
659 res = repeat(' ', label%first) // colorize(repeat(marker, width), foreground='magenta')
660 if (allocated(label%text)) then
661 res = res // ' ' // colorize(label%text, foreground='magenta')
662 end if
663 case default
664 res = repeat(' ', label%first) // colorize(repeat(marker, width), foreground='blue')
665 if (allocated(label%text)) then
666 res = res // ' ' // colorize(label%text, foreground='blue')
667 end if
668 end select
669 else
670 res = repeat(' ', label%first) // repeat(marker, width)
671 if (allocated(label%text)) then
672 res = res // ' ' // colorize(label%text, foreground='blue')
673 end if
674 end if
675 end function
676
677 pure function render_line(input, line) result(res)
678 character(*), intent(in) :: input
679 character(*), intent(in) :: line
680 character(:), allocatable :: res
681
682 res = line // ' ' // colorize('|', foreground='blue') // ' ' // input
683 end function
684
685 pure integer function integer_width(input) result(res)
686 integer, value :: input
687
688 res = 0
689 do while (input /= 0)
690 input = input / 10
691 res = res + 1
692 end do
693
694 end function
695
696 !> Represent an integer as character sequence.
697 pure function to_string(val, width) result(res)
698 integer, intent(in) :: val
699 integer, intent(in), optional :: width
700 character(:), allocatable :: res
701 !private
702 integer, parameter :: buffer_len = range(val) + 2
703 character(buffer_len) :: buffer
704 integer :: n, pos
705 character(1), parameter :: numbers(0:9) = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
706
707 if (val == 0) then
708 res = numbers(0)
709 return
710 end if
711
712 n = abs(val)
713 buffer = ''
714
715 pos = buffer_len + 1
716 do while (n > 0)
717 pos = pos - 1
718 buffer(pos:pos) = numbers(mod(n, 10))
719 n = n / 10
720 end do
721 if (val < 0) then
722 pos = pos - 1
723 buffer(pos:pos) = '-'
724 end if
725
726 if (present(width)) then
727 res = repeat(' ', max(width - (buffer_len + 1 - pos), 0)) // buffer(pos:)
728 else
729 res = buffer(pos:)
730 end if
731 end function
732
733 !> Conditional print of the error/warning message.
734 !! @param[in] str Input string.
735 !! @param[in] fmt (optional) print format.
736 subroutine printf(str, fmt, unit)
737 character(*), intent(in) :: str
738 character(*), intent(in), optional :: fmt
739 integer, intent(in), optional :: unit
740
741 if (verbose) then
742 if (present(fmt)) then
743 if (present(unit)) then
744 write(*, fmt) str
745 else
746 write(unit, fmt) str
747 end if
748 else
749 if (present(unit)) then
750 write(*, '(A)') str
751 else
752 write(unit, '(A)') str
753 end if
754 end if
755 end if
756 end subroutine
757
758end module
logical, public nocolor
Switch for controling the ANSI color output Default value is .true. (color mode on)....
Definition logging.f90:114
logical, public verbose
Master switch for verbose diagnostic output Default value is .false. (quiet mode)....
Definition logging.f90:108
Interface to render diagnostic messages and labels.
Definition logging.f90:185
Definition of diagnostic message.
Definition logging.f90:269
Represents text as a sequence of ASCII code units. The derived type wraps an allocatable character ar...
Definition logging.f90:246