MOM6
MOM_document.F90
1 !> The subroutines here provide hooks for document generation functions at
2 !! various levels of granularity.
4 
5 ! This file is part of MOM6. See LICENSE.md for the license.
6 
7 use mom_time_manager, only : time_type
8 use mom_error_handler, only : mom_error, fatal, warning, is_root_pe
9 
10 implicit none ; private
11 
12 public doc_param, doc_subroutine, doc_function, doc_module, doc_init, doc_end
13 public doc_openblock, doc_closeblock
14 
15 !> Document parameter values
16 interface doc_param
17  module procedure doc_param_none, &
18  doc_param_logical, doc_param_logical_array, &
19  doc_param_int, doc_param_int_array, &
20  doc_param_real, doc_param_real_array, &
21  doc_param_char, &
22  doc_param_time
23 end interface
24 
25 integer, parameter :: mlen = 1240 !< Length of interface/message strings
26 
27 !> A structure that controls where the documentation occurs, its veborsity and formatting.
28 type, public :: doc_type ; private
29  integer :: unitall = -1 !< The open unit number for docFileBase + .all.
30  integer :: unitshort = -1 !< The open unit number for docFileBase + .short.
31  integer :: unitlayout = -1 !< The open unit number for docFileBase + .layout.
32  integer :: unitdebugging = -1 !< The open unit number for docFileBase + .debugging.
33  logical :: filesareopen = .false. !< True if any files were successfully opened.
34  character(len=mLen) :: docfilebase = '' !< The basename of the files where run-time
35  !! parameters, settings and defaults are documented.
36  logical :: complete = .true. !< If true, document all parameters.
37  logical :: minimal = .true. !< If true, document non-default parameters.
38  logical :: layout = .true. !< If true, document layout parameters.
39  logical :: debugging = .true. !< If true, document debugging parameters.
40  logical :: definesyntax = .false. !< If true, use '\#def' syntax instead of a=b syntax
41  logical :: warnonconflicts = .false. !< Cause a WARNING error if defaults differ.
42  integer :: commentcolumn = 32 !< Number of spaces before the comment marker.
43  integer :: max_line_len = 112 !< The maximum length of message lines.
44  type(link_msg), pointer :: chain_msg => null() !< Database of messages
45  character(len=240) :: blockprefix = '' !< The full name of the current block.
46 end type doc_type
47 
48 !> A linked list of the parameter documentation messages that have been issued so far.
49 type :: link_msg ; private
50  type(link_msg), pointer :: next => null() !< Facilitates linked list
51  character(len=80) :: name !< Parameter name
52  character(len=620) :: msg !< Parameter value and default
53 end type link_msg
54 
55 character(len=4), parameter :: string_true = 'True' !< A string for true logicals
56 character(len=5), parameter :: string_false = 'False' !< A string for false logicals
57 
58 contains
59 
60 ! ----------------------------------------------------------------------
61 
62 !> This subroutine handles parameter documentation with no value.
63 subroutine doc_param_none(doc, varname, desc, units)
64  type(doc_type), pointer :: doc !< A pointer to a structure that controls where the
65  !! documentation occurs and its formatting
66  character(len=*), intent(in) :: varname !< The name of the parameter being documented
67  character(len=*), intent(in) :: desc !< A description of the parameter being documented
68  character(len=*), intent(in) :: units !< The units of the parameter being documented
69 ! This subroutine handles parameter documentation with no value.
70  integer :: numspc
71  character(len=mLen) :: mesg
72 
73  if (.not. (is_root_pe() .and. associated(doc))) return
74  call open_doc_file(doc)
75 
76  if (doc%filesAreOpen) then
77  numspc = max(1,doc%commentColumn-8-len_trim(varname))
78  mesg = "#define "//trim(varname)//repeat(" ",numspc)//"!"
79  if (len_trim(units) > 0) mesg = trim(mesg)//" ["//trim(units)//"]"
80 
81  if (mesghasbeendocumented(doc, varname, mesg)) return ! Avoid duplicates
82  call writemessageanddesc(doc, mesg, desc)
83  endif
84 end subroutine doc_param_none
85 
86 !> This subroutine handles parameter documentation for logicals.
87 subroutine doc_param_logical(doc, varname, desc, units, val, default, &
88  layoutParam, debuggingParam)
89  type(doc_type), pointer :: doc !< A pointer to a structure that controls where the
90  !! documentation occurs and its formatting
91  character(len=*), intent(in) :: varname !< The name of the parameter being documented
92  character(len=*), intent(in) :: desc !< A description of the parameter being documented
93  character(len=*), intent(in) :: units !< The units of the parameter being documented
94  logical, intent(in) :: val !< The value of this parameter
95  logical, optional, intent(in) :: default !< The default value of this parameter
96  logical, optional, intent(in) :: layoutParam !< If present and true, this is a layout parameter.
97  logical, optional, intent(in) :: debuggingParam !< If present and true, this is a debugging parameter.
98 ! This subroutine handles parameter documentation for logicals.
99  character(len=mLen) :: mesg
100  logical :: equalsDefault
101 
102  if (.not. (is_root_pe() .and. associated(doc))) return
103  call open_doc_file(doc)
104 
105  if (doc%filesAreOpen) then
106  if (val) then
107  mesg = define_string(doc,varname,string_true,units)
108  else
109  mesg = undef_string(doc,varname,units)
110  endif
111 
112  equalsdefault = .false.
113  if (present(default)) then
114  if (val .eqv. default) equalsdefault = .true.
115  if (default) then
116  mesg = trim(mesg)//" default = "//string_true
117  else
118  mesg = trim(mesg)//" default = "//string_false
119  endif
120  endif
121 
122  if (mesghasbeendocumented(doc, varname, mesg)) return ! Avoid duplicates
123  call writemessageanddesc(doc, mesg, desc, equalsdefault, &
124  layoutparam=layoutparam, debuggingparam=debuggingparam)
125  endif
126 end subroutine doc_param_logical
127 
128 !> This subroutine handles parameter documentation for arrays of logicals.
129 subroutine doc_param_logical_array(doc, varname, desc, units, vals, default, &
130  layoutParam, debuggingParam)
131  type(doc_type), pointer :: doc !< A pointer to a structure that controls where the
132  !! documentation occurs and its formatting
133  character(len=*), intent(in) :: varname !< The name of the parameter being documented
134  character(len=*), intent(in) :: desc !< A description of the parameter being documented
135  character(len=*), intent(in) :: units !< The units of the parameter being documented
136  logical, intent(in) :: vals(:) !< The array of values to record
137  logical, optional, intent(in) :: default !< The default value of this parameter
138  logical, optional, intent(in) :: layoutParam !< If present and true, this is a layout parameter.
139  logical, optional, intent(in) :: debuggingParam !< If present and true, this is a debugging parameter.
140 ! This subroutine handles parameter documentation for arrays of logicals.
141  integer :: i
142  character(len=mLen) :: mesg
143  character(len=mLen) :: valstring
144  logical :: equalsDefault
145 
146  if (.not. (is_root_pe() .and. associated(doc))) return
147  call open_doc_file(doc)
148 
149  if (doc%filesAreOpen) then
150  if (vals(1)) then ; valstring = string_true ; else ; valstring = string_false ; endif
151  do i=2,min(size(vals),128)
152  if (vals(i)) then
153  valstring = trim(valstring)//", "//string_true
154  else
155  valstring = trim(valstring)//", "//string_false
156  endif
157  enddo
158 
159  mesg = define_string(doc,varname,valstring,units)
160 
161  equalsdefault = .false.
162  if (present(default)) then
163  equalsdefault = .true.
164  do i=1,size(vals) ; if (vals(i) .neqv. default) equalsdefault = .false. ; enddo
165  if (default) then
166  mesg = trim(mesg)//" default = "//string_true
167  else
168  mesg = trim(mesg)//" default = "//string_false
169  endif
170  endif
171 
172  if (mesghasbeendocumented(doc, varname, mesg)) return ! Avoid duplicates
173  call writemessageanddesc(doc, mesg, desc, equalsdefault, &
174  layoutparam=layoutparam, debuggingparam=debuggingparam)
175  endif
176 end subroutine doc_param_logical_array
177 
178 !> This subroutine handles parameter documentation for integers.
179 subroutine doc_param_int(doc, varname, desc, units, val, default, &
180  layoutParam, debuggingParam)
181  type(doc_type), pointer :: doc !< A pointer to a structure that controls where the
182  !! documentation occurs and its formatting
183  character(len=*), intent(in) :: varname !< The name of the parameter being documented
184  character(len=*), intent(in) :: desc !< A description of the parameter being documented
185  character(len=*), intent(in) :: units !< The units of the parameter being documented
186  integer, intent(in) :: val !< The value of this parameter
187  integer, optional, intent(in) :: default !< The default value of this parameter
188  logical, optional, intent(in) :: layoutParam !< If present and true, this is a layout parameter.
189  logical, optional, intent(in) :: debuggingParam !< If present and true, this is a debugging parameter.
190 ! This subroutine handles parameter documentation for integers.
191  character(len=mLen) :: mesg
192  character(len=doc%commentColumn) :: valstring
193  logical :: equalsDefault
194 
195  if (.not. (is_root_pe() .and. associated(doc))) return
196  call open_doc_file(doc)
197 
198  if (doc%filesAreOpen) then
199  valstring = int_string(val)
200  mesg = define_string(doc,varname,valstring,units)
201 
202  equalsdefault = .false.
203  if (present(default)) then
204  if (val == default) equalsdefault = .true.
205  mesg = trim(mesg)//" default = "//(trim(int_string(default)))
206  endif
207 
208  if (mesghasbeendocumented(doc, varname, mesg)) return ! Avoid duplicates
209  call writemessageanddesc(doc, mesg, desc, equalsdefault, &
210  layoutparam=layoutparam, debuggingparam=debuggingparam)
211  endif
212 end subroutine doc_param_int
213 
214 !> This subroutine handles parameter documentation for arrays of integers.
215 subroutine doc_param_int_array(doc, varname, desc, units, vals, default, &
216  layoutParam, debuggingParam)
217  type(doc_type), pointer :: doc !< A pointer to a structure that controls where the
218  !! documentation occurs and its formatting
219  character(len=*), intent(in) :: varname !< The name of the parameter being documented
220  character(len=*), intent(in) :: desc !< A description of the parameter being documented
221  character(len=*), intent(in) :: units !< The units of the parameter being documented
222  integer, intent(in) :: vals(:) !< The array of values to record
223  integer, optional, intent(in) :: default !< The default value of this parameter
224  logical, optional, intent(in) :: layoutParam !< If present and true, this is a layout parameter.
225  logical, optional, intent(in) :: debuggingParam !< If present and true, this is a debugging parameter.
226 ! This subroutine handles parameter documentation for arrays of integers.
227  integer :: i
228  character(len=mLen) :: mesg
229  character(len=mLen) :: valstring
230  logical :: equalsDefault
231 
232  if (.not. (is_root_pe() .and. associated(doc))) return
233  call open_doc_file(doc)
234 
235  if (doc%filesAreOpen) then
236  valstring = int_string(vals(1))
237  do i=2,min(size(vals),128)
238  valstring = trim(valstring)//", "//trim(int_string(vals(i)))
239  enddo
240 
241  mesg = define_string(doc,varname,valstring,units)
242 
243  equalsdefault = .false.
244  if (present(default)) then
245  equalsdefault = .true.
246  do i=1,size(vals) ; if (vals(i) /= default) equalsdefault = .false. ; enddo
247  mesg = trim(mesg)//" default = "//(trim(int_string(default)))
248  endif
249 
250  if (mesghasbeendocumented(doc, varname, mesg)) return ! Avoid duplicates
251  call writemessageanddesc(doc, mesg, desc, equalsdefault, &
252  layoutparam=layoutparam, debuggingparam=debuggingparam)
253  endif
254 
255 end subroutine doc_param_int_array
256 
257 !> This subroutine handles parameter documentation for reals.
258 subroutine doc_param_real(doc, varname, desc, units, val, default, debuggingParam)
259  type(doc_type), pointer :: doc !< A pointer to a structure that controls where the
260  !! documentation occurs and its formatting
261  character(len=*), intent(in) :: varname !< The name of the parameter being documented
262  character(len=*), intent(in) :: desc !< A description of the parameter being documented
263  character(len=*), intent(in) :: units !< The units of the parameter being documented
264  real, intent(in) :: val !< The value of this parameter
265  real, optional, intent(in) :: default !< The default value of this parameter
266  logical, optional, intent(in) :: debuggingParam !< If present and true, this is a debugging parameter.
267 ! This subroutine handles parameter documentation for reals.
268  character(len=mLen) :: mesg
269  character(len=doc%commentColumn) :: valstring
270  logical :: equalsDefault
271 
272  if (.not. (is_root_pe() .and. associated(doc))) return
273  call open_doc_file(doc)
274 
275  if (doc%filesAreOpen) then
276  valstring = real_string(val)
277  mesg = define_string(doc,varname,valstring,units)
278 
279  equalsdefault = .false.
280  if (present(default)) then
281  if (val == default) equalsdefault = .true.
282  mesg = trim(mesg)//" default = "//trim(real_string(default))
283  endif
284 
285  if (mesghasbeendocumented(doc, varname, mesg)) return ! Avoid duplicates
286  call writemessageanddesc(doc, mesg, desc, equalsdefault, &
287  debuggingparam=debuggingparam)
288  endif
289 end subroutine doc_param_real
290 
291 !> This subroutine handles parameter documentation for arrays of reals.
292 subroutine doc_param_real_array(doc, varname, desc, units, vals, default, debuggingParam)
293  type(doc_type), pointer :: doc !< A pointer to a structure that controls where the
294  !! documentation occurs and its formatting
295  character(len=*), intent(in) :: varname !< The name of the parameter being documented
296  character(len=*), intent(in) :: desc !< A description of the parameter being documented
297  character(len=*), intent(in) :: units !< The units of the parameter being documented
298  real, intent(in) :: vals(:) !< The array of values to record
299  real, optional, intent(in) :: default !< The default value of this parameter
300  logical, optional, intent(in) :: debuggingParam !< If present and true, this is a debugging parameter.
301 ! This subroutine handles parameter documentation for arrays of reals.
302  integer :: i
303  character(len=mLen) :: mesg
304  character(len=mLen) :: valstring
305  logical :: equalsDefault
306 
307  if (.not. (is_root_pe() .and. associated(doc))) return
308  call open_doc_file(doc)
309 
310  if (doc%filesAreOpen) then
311  valstring = trim(real_array_string(vals(:)))
312 
313  mesg = define_string(doc,varname,valstring,units)
314 
315  equalsdefault = .false.
316  if (present(default)) then
317  equalsdefault = .true.
318  do i=1,size(vals) ; if (vals(i) /= default) equalsdefault = .false. ; enddo
319  mesg = trim(mesg)//" default = "//trim(real_string(default))
320  endif
321 
322  if (mesghasbeendocumented(doc, varname, mesg)) return ! Avoid duplicates
323  call writemessageanddesc(doc, mesg, desc, equalsdefault, &
324  debuggingparam=debuggingparam)
325  endif
326 
327 end subroutine doc_param_real_array
328 
329 !> This subroutine handles parameter documentation for character strings.
330 subroutine doc_param_char(doc, varname, desc, units, val, default, &
331  layoutParam, debuggingParam)
332  type(doc_type), pointer :: doc !< A pointer to a structure that controls where the
333  !! documentation occurs and its formatting
334  character(len=*), intent(in) :: varname !< The name of the parameter being documented
335  character(len=*), intent(in) :: desc !< A description of the parameter being documented
336  character(len=*), intent(in) :: units !< The units of the parameter being documented
337  character(len=*), intent(in) :: val !< The value of the parameter
338  character(len=*), &
339  optional, intent(in) :: default !< The default value of this parameter
340  logical, optional, intent(in) :: layoutParam !< If present and true, this is a layout parameter.
341  logical, optional, intent(in) :: debuggingParam !< If present and true, this is a debugging parameter.
342 ! This subroutine handles parameter documentation for character strings.
343  character(len=mLen) :: mesg
344  logical :: equalsDefault
345 
346  if (.not. (is_root_pe() .and. associated(doc))) return
347  call open_doc_file(doc)
348 
349  if (doc%filesAreOpen) then
350  mesg = define_string(doc,varname,'"'//trim(val)//'"',units)
351 
352  equalsdefault = .false.
353  if (present(default)) then
354  if (trim(val) == trim(default)) equalsdefault = .true.
355  mesg = trim(mesg)//' default = "'//trim(adjustl(default))//'"'
356  endif
357 
358  if (mesghasbeendocumented(doc, varname, mesg)) return ! Avoid duplicates
359  call writemessageanddesc(doc, mesg, desc, equalsdefault, &
360  layoutparam=layoutparam, debuggingparam=debuggingparam)
361  endif
362 
363 end subroutine doc_param_char
364 
365 !> This subroutine handles documentation for opening a parameter block.
366 subroutine doc_openblock(doc, blockName, desc)
367  type(doc_type), pointer :: doc !< A pointer to a structure that controls where the
368  !! documentation occurs and its formatting
369  character(len=*), intent(in) :: blockname !< The name of the parameter block being opened
370  character(len=*), optional, intent(in) :: desc !< A description of the parameter block being opened
371 ! This subroutine handles documentation for opening a parameter block.
372  character(len=mLen) :: mesg
373  character(len=doc%commentColumn) :: valstring
374 
375  if (.not. (is_root_pe() .and. associated(doc))) return
376  call open_doc_file(doc)
377 
378  if (doc%filesAreOpen) then
379  mesg = trim(blockname)//'%'
380 
381  if (present(desc)) then
382  call writemessageanddesc(doc, mesg, desc)
383  else
384  call writemessageanddesc(doc, mesg, '')
385  endif
386  endif
387  doc%blockPrefix = trim(doc%blockPrefix)//trim(blockname)//'%'
388 end subroutine doc_openblock
389 
390 !> This subroutine handles documentation for closing a parameter block.
391 subroutine doc_closeblock(doc, blockName)
392  type(doc_type), pointer :: doc !< A pointer to a structure that controls where the
393  !! documentation occurs and its formatting
394  character(len=*), intent(in) :: blockname !< The name of the parameter block being closed
395 ! This subroutine handles documentation for closing a parameter block.
396  character(len=mLen) :: mesg
397  character(len=doc%commentColumn) :: valstring
398  integer :: i
399 
400  if (.not. (is_root_pe() .and. associated(doc))) return
401  call open_doc_file(doc)
402 
403  if (doc%filesAreOpen) then
404  mesg = '%'//trim(blockname)
405 
406  call writemessageanddesc(doc, mesg, '')
407  endif
408  i = index(trim(doc%blockPrefix), trim(blockname)//'%', .true.)
409  if (i>1) then
410  doc%blockPrefix = trim(doc%blockPrefix(1:i-1))
411  else
412  doc%blockPrefix = ''
413  endif
414 end subroutine doc_closeblock
415 
416 !> This subroutine handles parameter documentation for time-type variables.
417 subroutine doc_param_time(doc, varname, desc, units, val, default, &
418  layoutParam, debuggingParam)
419  type(doc_type), pointer :: doc !< A pointer to a structure that controls where the
420  !! documentation occurs and its formatting
421  character(len=*), intent(in) :: varname !< The name of the parameter being documented
422  character(len=*), intent(in) :: desc !< A description of the parameter being documented
423  character(len=*), intent(in) :: units !< The units of the parameter being documented
424  type(time_type), intent(in) :: val !< The value of the parameter
425  type(time_type), optional, intent(in) :: default !< The default value of this parameter
426  logical, optional, intent(in) :: layoutParam !< If present and true, this is a layout parameter.
427  logical, optional, intent(in) :: debuggingParam !< If present and true, this is a debugging parameter.
428 ! This subroutine handles parameter documentation for time-type variables.
429 ! ### This needs to be written properly!
430  integer :: numspc
431  character(len=mLen) :: mesg
432  logical :: equalsDefault
433 
434  if (.not. (is_root_pe() .and. associated(doc))) return
435  call open_doc_file(doc)
436 
437  equalsdefault = .false.
438  if (doc%filesAreOpen) then
439  numspc = max(1,doc%commentColumn-18-len_trim(varname))
440  mesg = "#define "//trim(varname)//" Time-type"//repeat(" ",numspc)//"!"
441  if (len_trim(units) > 0) mesg = trim(mesg)//" ["//trim(units)//"]"
442 
443  if (mesghasbeendocumented(doc, varname, mesg)) return ! Avoid duplicates
444  call writemessageanddesc(doc, mesg, desc, equalsdefault, &
445  layoutparam=layoutparam, debuggingparam=debuggingparam)
446  endif
447 
448 end subroutine doc_param_time
449 
450 !> This subroutine writes out the message and description to the documetation files.
451 subroutine writemessageanddesc(doc, vmesg, desc, valueWasDefault, indent, &
452  layoutParam, debuggingParam)
453  type(doc_type), intent(in) :: doc !< A pointer to a structure that controls where the
454  !! documentation occurs and its formatting
455  character(len=*), intent(in) :: vmesg !< A message with the parameter name, units, and default value.
456  character(len=*), intent(in) :: desc !< A description of the parameter being documented
457  logical, optional, intent(in) :: valueWasDefault !< If true, this parameter has its default value
458  integer, optional, intent(in) :: indent !< An amount by which to indent this message
459  logical, optional, intent(in) :: layoutParam !< If present and true, this is a layout parameter.
460  logical, optional, intent(in) :: debuggingParam !< If present and true, this is a debugging parameter.
461 
462  ! Local variables
463  character(len=mLen) :: mesg ! A full line of a message including indents.
464  character(len=mLen) :: mesg_text ! A line of message text without preliminary indents.
465  integer :: start_ind = 1 ! The starting index in the description for the next line.
466  integer :: nl_ind, tab_ind, end_ind ! The indices of new-lines, tabs, and the end of a line.
467  integer :: len_text, len_tab, len_nl ! The lengths of the text string, tabs and new-lines.
468  integer :: indnt, msg_pad ! Space counts used to format a message.
469  logical :: msg_done, reset_msg_pad ! Logicals used to format messages.
470  logical :: all, short, layout, debug ! Flags indicating which files to write into.
471 
472  layout = .false. ; if (present(layoutparam)) layout = layoutparam
473  debug = .false. ; if (present(debuggingparam)) debug = debuggingparam
474  all = doc%complete .and. (doc%unitAll > 0) .and. .not. (layout .or. debug)
475  short = doc%minimal .and. (doc%unitShort > 0) .and. .not. (layout .or. debug)
476  if (present(valuewasdefault)) short = short .and. (.not. valuewasdefault)
477 
478  if (all) write(doc%unitAll, '(a)') trim(vmesg)
479  if (short) write(doc%unitShort, '(a)') trim(vmesg)
480  if (layout) write(doc%unitLayout, '(a)') trim(vmesg)
481  if (debug) write(doc%unitDebugging, '(a)') trim(vmesg)
482 
483  if (len_trim(desc) == 0) return
484 
485  len_tab = len_trim("_\t_") - 2
486  len_nl = len_trim("_\n_") - 2
487 
488  indnt = doc%commentColumn ; if (present(indent)) indnt = indent
489  len_text = doc%max_line_len - (indnt + 2)
490  start_ind = 1 ; msg_pad = 0 ; msg_done = .false.
491  do
492  if (len_trim(desc(start_ind:)) < 1) exit
493 
494  nl_ind = index(desc(start_ind:), "\n")
495 
496  end_ind = 0
497  if ((nl_ind > 0) .and. (len_trim(desc(start_ind:start_ind+nl_ind-2)) > len_text-msg_pad)) then
498  ! This line is too long despite the new-line character. Look for an earlier space to break.
499  end_ind = scan(desc(start_ind:start_ind+(len_text-msg_pad)), " ", back=.true.) - 1
500  if (end_ind > 0) nl_ind = 0
501  elseif ((nl_ind == 0) .and. (len_trim(desc(start_ind:)) > len_text-msg_pad)) then
502  ! This line is too long and does not have a new-line character. Look for a space to break.
503  end_ind = scan(desc(start_ind:start_ind+(len_text-msg_pad)), " ", back=.true.) - 1
504  endif
505 
506  reset_msg_pad = .false.
507  if (nl_ind > 0) then
508  mesg_text = trim(desc(start_ind:start_ind+nl_ind-2))
509  start_ind = start_ind + nl_ind + len_nl - 1
510  reset_msg_pad = .true.
511  elseif (end_ind > 0) then
512  mesg_text = trim(desc(start_ind:start_ind+end_ind))
513  start_ind = start_ind + end_ind + 1
514  ! Adjust the starting point to move past leading spaces.
515  start_ind = start_ind + (len_trim(desc(start_ind:)) - len_trim(adjustl(desc(start_ind:))))
516  else
517  mesg_text = trim(desc(start_ind:))
518  msg_done = .true.
519  endif
520 
521  do ; tab_ind = index(mesg_text, "\t") ! Replace \t with 2 spaces.
522  if (tab_ind == 0) exit
523  mesg_text(tab_ind:) = " "//trim(mesg_text(tab_ind+len_tab:))
524  enddo
525 
526  mesg = repeat(" ",indnt)//"! "//repeat(" ",msg_pad)//trim(mesg_text)
527 
528  if (reset_msg_pad) then
529  msg_pad = 0
530  elseif (msg_pad == 0) then ! Indent continuation lines.
531  msg_pad = len_trim(mesg_text) - len_trim(adjustl(mesg_text))
532  ! If already indented, indent an additional 2 spaces.
533  if (msg_pad >= 2) msg_pad = msg_pad + 2
534  endif
535 
536  if (all) write(doc%unitAll, '(a)') trim(mesg)
537  if (short) write(doc%unitShort, '(a)') trim(mesg)
538  if (layout) write(doc%unitLayout, '(a)') trim(mesg)
539  if (debug) write(doc%unitDebugging, '(a)') trim(mesg)
540 
541  if (msg_done) exit
542  enddo
543 
544 end subroutine writemessageanddesc
545 
546 ! ----------------------------------------------------------------------
547 
548 !> This function returns a string with a real formatted like '(G)'
549 function real_string(val)
550  real, intent(in) :: val !< The value being written into a string
551  character(len=32) :: real_string
552 ! This function returns a string with a real formatted like '(G)'
553  integer :: len, ind
554 
555  if ((abs(val) < 1.0e4) .and. (abs(val) >= 1.0e-3)) then
556  write(real_string, '(F30.11)') val
557  if (.not.testformattedfloatisreal(real_string,val)) then
558  write(real_string, '(F30.12)') val
559  if (.not.testformattedfloatisreal(real_string,val)) then
560  write(real_string, '(F30.13)') val
561  if (.not.testformattedfloatisreal(real_string,val)) then
562  write(real_string, '(F30.14)') val
563  if (.not.testformattedfloatisreal(real_string,val)) then
564  write(real_string, '(F30.15)') val
565  if (.not.testformattedfloatisreal(real_string,val)) then
566  write(real_string, '(F30.16)') val
567  endif
568  endif
569  endif
570  endif
571  endif
572  do
573  len = len_trim(real_string)
574  if ((len<2) .or. (real_string(len-1:len) == ".0") .or. &
575  (real_string(len:len) /= "0")) exit
576  real_string(len:len) = " "
577  enddo
578  elseif (val == 0.) then
579  real_string = "0.0"
580  else
581  if ((abs(val) <= 1.0e-100) .or. (abs(val) >= 1.0e100)) then
582  write(real_string(1:32), '(ES24.14E3)') val
583  if (.not.testformattedfloatisreal(real_string,val)) &
584  write(real_string(1:32), '(ES24.15E3)') val
585  else
586  write(real_string(1:32), '(ES23.14)') val
587  if (.not.testformattedfloatisreal(real_string,val)) &
588  write(real_string(1:32), '(ES23.15)') val
589  endif
590  do
591  ind = index(real_string,"0E")
592  if (ind == 0) exit
593  if (real_string(ind-1:ind-1) == ".") exit
594  real_string = real_string(1:ind-1)//real_string(ind+1:)
595  enddo
596  endif
597  real_string = adjustl(real_string)
598 end function real_string
599 
600 !> Returns a character string of a comma-separated, compact formatted, reals
601 !> e.g. "1., 2., 5*3., 5.E2", that give the list of values.
602 function real_array_string(vals, sep)
603  character(len=1320) :: real_array_string !< The output string listing vals
604  real, intent(in) :: vals(:) !< The array of values to record
605  character(len=*), &
606  optional, intent(in) :: sep !< The separator between successive values,
607  !! by default it is ', '.
608 ! Returns a character string of a comma-separated, compact formatted, reals
609 ! e.g. "1., 2., 5*3., 5.E2"
610  ! Local variables
611  integer :: j, n, b, ns
612  logical :: dowrite
613  character(len=10) :: separator
614  n=1 ; dowrite=.true. ; real_array_string='' ; b=1
615  if (present(sep)) then
616  separator=sep ; ns=len(sep)
617  else
618  separator=', ' ; ns=2
619  endif
620  do j=1,size(vals)
621  dowrite=.true.
622  if (j<size(vals)) then
623  if (vals(j)==vals(j+1)) then
624  n=n+1
625  dowrite=.false.
626  endif
627  endif
628  if (dowrite) then
629  if (b>1) then ! Write separator if a number has already been written
630  write(real_array_string(b:),'(A)') separator
631  b=b+ns
632  endif
633  if (n>1) then
634  write(real_array_string(b:),'(A,"*",A)') trim(int_string(n)),trim(real_string(vals(j)))
635  else
636  write(real_array_string(b:),'(A)') trim(real_string(vals(j)))
637  endif
638  n=1 ; b=len_trim(real_array_string)+1
639  endif
640  enddo
641 end function real_array_string
642 
643 !> This function tests whether a real value is encoded in a string.
644 function testformattedfloatisreal(str, val)
645  character(len=*), intent(in) :: str !< The string that match val
646  real, intent(in) :: val !< The value being tested
647  logical :: testformattedfloatisreal
648  ! Local variables
649  real :: scannedval
650 
651  read(str(1:),*) scannedval
652  if (scannedval == val) then
653  testformattedfloatisreal=.true.
654  else
655  testformattedfloatisreal=.false.
656  endif
657 end function testformattedfloatisreal
658 
659 !> This function returns a string with an integer formatted like '(I)'
660 function int_string(val)
661  integer, intent(in) :: val !< The value being written into a string
662  character(len=24) :: int_string
663 ! This function returns a string with an integer formatted like '(I)'
664  write(int_string, '(i24)') val
665  int_string = adjustl(int_string)
666 end function int_string
667 
668 !> This function returns a string with an logical formatted like '(L)'
669 function logical_string(val)
670  logical, intent(in) :: val !< The value being written into a string
671  character(len=24) :: logical_string
672 ! This function returns a string with an logical formatted like '(L)'
673  write(logical_string, '(l24)') val
674  logical_string = adjustl(logical_string)
675 end function logical_string
676 
677 !> This function returns a string for formatted parameter assignment
678 function define_string(doc,varName,valString,units)
679  type(doc_type), pointer :: doc !< A pointer to a structure that controls where the
680  !! documentation occurs and its formatting
681  character(len=*), intent(in) :: varname !< The name of the parameter being documented
682  character(len=*), intent(in) :: valstring !< A string containing the value of the parameter
683  character(len=*), intent(in) :: units !< The units of the parameter being documented
684  character(len=mLen) :: define_string
685 ! This function returns a string for formatted parameter assignment
686  integer :: numspaces
687  define_string = repeat(" ",mlen) ! Blank everything for safety
688  if (doc%defineSyntax) then
689  define_string = "#define "//trim(varname)//" "//valstring
690  else
691  define_string = trim(varname)//" = "//valstring
692  endif
693  numspaces = max(1, doc%commentColumn - len_trim(define_string) )
694  define_string = trim(define_string)//repeat(" ",numspaces)//"!"
695  if (len_trim(units) > 0) define_string = trim(define_string)//" ["//trim(units)//"]"
696 end function define_string
697 
698 !> This function returns a string for formatted false logicals
699 function undef_string(doc,varName,units)
700  type(doc_type), pointer :: doc !< A pointer to a structure that controls where the
701  !! documentation occurs and its formatting
702  character(len=*), intent(in) :: varname !< The name of the parameter being documented
703  character(len=*), intent(in) :: units !< The units of the parameter being documented
704  character(len=mLen) :: undef_string
705 ! This function returns a string for formatted false logicals
706  integer :: numspaces
707  undef_string = repeat(" ",240) ! Blank everything for safety
708  undef_string = "#undef "//trim(varname)
709  if (doc%defineSyntax) then
710  undef_string = "#undef "//trim(varname)
711  else
712  undef_string = trim(varname)//" = "//string_false
713  endif
714  numspaces = max(1, doc%commentColumn - len_trim(undef_string) )
715  undef_string = trim(undef_string)//repeat(" ",numspaces)//"!"
716  if (len_trim(units) > 0) undef_string = trim(undef_string)//" ["//trim(units)//"]"
717 end function undef_string
718 
719 ! ----------------------------------------------------------------------
720 
721 !> This subroutine handles the module documentation
722 subroutine doc_module(doc, modname, desc)
723  type(doc_type), pointer :: doc !< A pointer to a structure that controls where the
724  !! documentation occurs and its formatting
725  character(len=*), intent(in) :: modname !< The name of the module being documented
726  character(len=*), intent(in) :: desc !< A description of the module being documented
727 ! This subroutine handles the module documentation
728  character(len=mLen) :: mesg
729 
730  if (.not. (is_root_pe() .and. associated(doc))) return
731  call open_doc_file(doc)
732 
733  if (doc%filesAreOpen) then
734  call writemessageanddesc(doc, '', '') ! Blank line for delineation
735  mesg = "! === module "//trim(modname)//" ==="
736  call writemessageanddesc(doc, mesg, desc, indent=0)
737  endif
738 end subroutine doc_module
739 
740 !> This subroutine handles the subroutine documentation
741 subroutine doc_subroutine(doc, modname, subname, desc)
742  type(doc_type), pointer :: doc !< A pointer to a structure that controls where the
743  !! documentation occurs and its formatting
744  character(len=*), intent(in) :: modname !< The name of the module being documented
745  character(len=*), intent(in) :: subname !< The name of the subroutine being documented
746  character(len=*), intent(in) :: desc !< A description of the subroutine being documented
747 ! This subroutine handles the subroutine documentation
748  if (.not. (is_root_pe() .and. associated(doc))) return
749  call open_doc_file(doc)
750 
751 end subroutine doc_subroutine
752 
753 !> This subroutine handles the function documentation
754 subroutine doc_function(doc, modname, fnname, desc)
755  type(doc_type), pointer :: doc !< A pointer to a structure that controls where the
756  !! documentation occurs and its formatting
757  character(len=*), intent(in) :: modname !< The name of the module being documented
758  character(len=*), intent(in) :: fnname !< The name of the function being documented
759  character(len=*), intent(in) :: desc !< A description of the function being documented
760 ! This subroutine handles the function documentation
761  if (.not. (is_root_pe() .and. associated(doc))) return
762  call open_doc_file(doc)
763 
764 end subroutine doc_function
765 
766 ! ----------------------------------------------------------------------
767 
768 !> Initialize the parameter documentation
769 subroutine doc_init(docFileBase, doc, minimal, complete, layout, debugging)
770  character(len=*), intent(in) :: docfilebase !< The base file name for this set of parameters,
771  !! for example MOM_parameter_doc
772  type(doc_type), pointer :: doc !< A pointer to a structure that controls where the
773  !! documentation occurs and its formatting
774  logical, optional, intent(in) :: minimal !< If present and true, write out the files (.short) documenting
775  !! those parameters that do not take on their default values.
776  logical, optional, intent(in) :: complete !< If present and true, write out the (.all) files documenting all
777  !! parameters
778  logical, optional, intent(in) :: layout !< If present and true, write out the (.layout) files documenting
779  !! the layout parameters
780  logical, optional, intent(in) :: debugging !< If present and true, write out the (.debugging) files documenting
781  !! the debugging parameters
782 
783  if (.not. associated(doc)) then
784  allocate(doc)
785  endif
786 
787  doc%docFileBase = docfilebase
788  if (present(minimal)) doc%minimal = minimal
789  if (present(complete)) doc%complete = complete
790  if (present(layout)) doc%layout = layout
791  if (present(debugging)) doc%debugging = debugging
792 
793 end subroutine doc_init
794 
795 !> This subroutine allocates and populates a structure that controls where the
796 !! documentation occurs and its formatting, and opens up the files controlled
797 !! by this structure
798 subroutine open_doc_file(doc)
799  type(doc_type), pointer :: doc !< A pointer to a structure that controls where the
800  !! documentation occurs and its formatting
801 
802  logical :: opened, new_file
803  integer :: ios
804  character(len=240) :: fileName
805 
806  if (.not. (is_root_pe() .and. associated(doc))) return
807 
808  if ((len_trim(doc%docFileBase) > 0) .and. doc%complete .and. (doc%unitAll<0)) then
809  new_file = .true. ; if (doc%unitAll /= -1) new_file = .false.
810  doc%unitAll = find_unused_unit_number()
811 
812  write(filename(1:240),'(a)') trim(doc%docFileBase)//'.all'
813  if (new_file) then
814  open(doc%unitAll, file=trim(filename), access='SEQUENTIAL', form='FORMATTED', &
815  action='WRITE', status='REPLACE', iostat=ios)
816  write(doc%unitAll, '(a)') &
817  '! This file was written by the model and records all non-layout '//&
818  'or debugging parameters used at run-time.'
819  else ! This file is being reopened, and should be appended.
820  open(doc%unitAll, file=trim(filename), access='SEQUENTIAL', form='FORMATTED', &
821  action='WRITE', status='OLD', position='APPEND', iostat=ios)
822  endif
823  inquire(doc%unitAll, opened=opened)
824  if ((.not.opened) .or. (ios /= 0)) then
825  call mom_error(fatal, "Failed to open doc file "//trim(filename)//".")
826  endif
827  doc%filesAreOpen = .true.
828  endif
829 
830  if ((len_trim(doc%docFileBase) > 0) .and. doc%minimal .and. (doc%unitShort<0)) then
831  new_file = .true. ; if (doc%unitShort /= -1) new_file = .false.
832  doc%unitShort = find_unused_unit_number()
833 
834  write(filename(1:240),'(a)') trim(doc%docFileBase)//'.short'
835  if (new_file) then
836  open(doc%unitShort, file=trim(filename), access='SEQUENTIAL', form='FORMATTED', &
837  action='WRITE', status='REPLACE', iostat=ios)
838  write(doc%unitShort, '(a)') &
839  '! This file was written by the model and records the non-default parameters used at run-time.'
840  else ! This file is being reopened, and should be appended.
841  open(doc%unitShort, file=trim(filename), access='SEQUENTIAL', form='FORMATTED', &
842  action='WRITE', status='OLD', position='APPEND', iostat=ios)
843  endif
844  inquire(doc%unitShort, opened=opened)
845  if ((.not.opened) .or. (ios /= 0)) then
846  call mom_error(fatal, "Failed to open doc file "//trim(filename)//".")
847  endif
848  doc%filesAreOpen = .true.
849  endif
850 
851  if ((len_trim(doc%docFileBase) > 0) .and. doc%layout .and. (doc%unitLayout<0)) then
852  new_file = .true. ; if (doc%unitLayout /= -1) new_file = .false.
853  doc%unitLayout = find_unused_unit_number()
854 
855  write(filename(1:240),'(a)') trim(doc%docFileBase)//'.layout'
856  if (new_file) then
857  open(doc%unitLayout, file=trim(filename), access='SEQUENTIAL', form='FORMATTED', &
858  action='WRITE', status='REPLACE', iostat=ios)
859  write(doc%unitLayout, '(a)') &
860  '! This file was written by the model and records the layout parameters used at run-time.'
861  else ! This file is being reopened, and should be appended.
862  open(doc%unitLayout, file=trim(filename), access='SEQUENTIAL', form='FORMATTED', &
863  action='WRITE', status='OLD', position='APPEND', iostat=ios)
864  endif
865  inquire(doc%unitLayout, opened=opened)
866  if ((.not.opened) .or. (ios /= 0)) then
867  call mom_error(fatal, "Failed to open doc file "//trim(filename)//".")
868  endif
869  doc%filesAreOpen = .true.
870  endif
871 
872  if ((len_trim(doc%docFileBase) > 0) .and. doc%debugging .and. (doc%unitDebugging<0)) then
873  new_file = .true. ; if (doc%unitDebugging /= -1) new_file = .false.
874  doc%unitDebugging = find_unused_unit_number()
875 
876  write(filename(1:240),'(a)') trim(doc%docFileBase)//'.debugging'
877  if (new_file) then
878  open(doc%unitDebugging, file=trim(filename), access='SEQUENTIAL', form='FORMATTED', &
879  action='WRITE', status='REPLACE', iostat=ios)
880  write(doc%unitDebugging, '(a)') &
881  '! This file was written by the model and records the debugging parameters used at run-time.'
882  else ! This file is being reopened, and should be appended.
883  open(doc%unitDebugging, file=trim(filename), access='SEQUENTIAL', form='FORMATTED', &
884  action='WRITE', status='OLD', position='APPEND', iostat=ios)
885  endif
886  inquire(doc%unitDebugging, opened=opened)
887  if ((.not.opened) .or. (ios /= 0)) then
888  call mom_error(fatal, "Failed to open doc file "//trim(filename)//".")
889  endif
890  doc%filesAreOpen = .true.
891  endif
892 
893 end subroutine open_doc_file
894 
895 !> Find an unused unit number, returning >0 if found, and triggering a FATAL error if not.
896 function find_unused_unit_number()
897 ! Find an unused unit number.
898 ! Returns >0 if found. FATAL if not.
899  integer :: find_unused_unit_number
900  logical :: opened
901  do find_unused_unit_number=512,42,-1
902  inquire( find_unused_unit_number, opened=opened)
903  if (.not.opened) exit
904  enddo
905  if (opened) call mom_error(fatal, &
906  "doc_init failed to find an unused unit number.")
907 end function find_unused_unit_number
908 
909 !> This subroutine closes the the files controlled by doc, and sets flags in
910 !! doc to indicate that parameterization is no longer permitted.
911 subroutine doc_end(doc)
912  type(doc_type), pointer :: doc !< A pointer to a structure that controls where the
913  !! documentation occurs and its formatting
914  type(link_msg), pointer :: this => null(), next => null()
915 
916  if (.not.associated(doc)) return
917 
918  if (doc%unitAll > 0) then
919  close(doc%unitAll)
920  doc%unitAll = -2
921  endif
922 
923  if (doc%unitShort > 0) then
924  close(doc%unitShort)
925  doc%unitShort = -2
926  endif
927 
928  if (doc%unitLayout > 0) then
929  close(doc%unitLayout)
930  doc%unitLayout = -2
931  endif
932 
933  if (doc%unitDebugging > 0) then
934  close(doc%unitDebugging)
935  doc%unitDebugging = -2
936  endif
937 
938  doc%filesAreOpen = .false.
939 
940  this => doc%chain_msg
941  do while( associated(this) )
942  next => this%next
943  deallocate(this)
944  this => next
945  enddo
946 end subroutine doc_end
947 
948 ! -----------------------------------------------------------------------------
949 
950 !> Returns true if documentation has already been written
951 function mesghasbeendocumented(doc,varName,mesg)
952  type(doc_type), pointer :: doc !< A pointer to a structure that controls where the
953  !! documentation occurs and its formatting
954  character(len=*), intent(in) :: varname !< The name of the parameter being documented
955  character(len=*), intent(in) :: mesg !< A message with parameter values, defaults, and descriptions
956  !! to compare with the message that was written previously
957  logical :: mesghasbeendocumented
958 ! Returns true if documentation has already been written
959  type(link_msg), pointer :: newlink => null(), this => null(), last => null()
960 
961  mesghasbeendocumented = .false.
962 
963 !!if (mesg(1:1) == '!') return ! Ignore commented parameters
964 
965  ! Search through list for this parameter
966  last => null()
967  this => doc%chain_msg
968  do while( associated(this) )
969  if (trim(doc%blockPrefix)//trim(varname) == trim(this%name)) then
970  mesghasbeendocumented = .true.
971  if (trim(mesg) == trim(this%msg)) return
972  ! If we fail the above test then cause an error
973  if (mesg(1:1) == '!') return ! Do not cause error for commented parameters
974  call mom_error(warning, "Previous msg:"//trim(this%msg))
975  call mom_error(warning, "New message :"//trim(mesg))
976  call mom_error(warning, "Encountered inconsistent documentation line for parameter "&
977  //trim(varname)//"!")
978  endif
979  last => this
980  this => this%next
981  enddo
982 
983  ! Allocate a new link
984  allocate(newlink)
985  newlink%name = trim(doc%blockPrefix)//trim(varname)
986  newlink%msg = trim(mesg)
987  newlink%next => null()
988  if (.not. associated(doc%chain_msg)) then
989  doc%chain_msg => newlink
990  else
991  if (.not. associated(last)) call mom_error(fatal, &
992  "Unassociated LINK in mesgHasBeenDocumented: "//trim(mesg))
993  last%next => newlink
994  endif
995 end function mesghasbeendocumented
996 
997 end module mom_document
mom_time_manager
Wraps the FMS time manager functions.
Definition: MOM_time_manager.F90:2
mom_document::doc_param
Document parameter values.
Definition: MOM_document.F90:16
mom_document
The subroutines here provide hooks for document generation functions at various levels of granularity...
Definition: MOM_document.F90:3
mom_document::doc_type
A structure that controls where the documentation occurs, its veborsity and formatting.
Definition: MOM_document.F90:28
mom_error_handler
Routines for error handling and I/O management.
Definition: MOM_error_handler.F90:2