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