MOM6
MOM_document.F90
Go to the documentation of this file.
2 !***********************************************************************
3 !* GNU General Public License *
4 !* This file is a part of MOM. *
5 !* *
6 !* MOM is free software; you can redistribute it and/or modify it and *
7 !* are expected to follow the terms of the GNU General Public License *
8 !* as published by the Free Software Foundation; either version 2 of *
9 !* the License, or (at your option) any later version. *
10 !* *
11 !* MOM is distributed in the hope that it will be useful, but WITHOUT *
12 !* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY *
13 !* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public *
14 !* License for more details. *
15 !* *
16 !* For the full text of the GNU General Public License, *
17 !* write to: Free Software Foundation, Inc., *
18 !* 675 Mass Ave, Cambridge, MA 02139, USA. *
19 !* or see: http://www.gnu.org/licenses/gpl.html *
20 !***********************************************************************
21 
22 !********+*********+*********+*********+*********+*********+*********+**
23 !* *
24 !* The subroutines here provide hooks for document generation *
25 !* functions at various levels of granularity. *
26 !* *
27 !********+*********+*********+*********+*********+*********+*********+**
28 
29 use mom_time_manager, only : time_type
30 use mom_error_handler, only : mom_error, fatal, warning, is_root_pe
31 
32 implicit none ; private
33 
36 
37 interface doc_param
38  module procedure doc_param_none, &
44 end interface
45 
46 integer, parameter :: mlen = 1240 ! Length of interface/message strings
47 
48 type, public :: doc_type ; private
49  integer :: unitall = -1 ! The open unit number for docFileBase + .all.
50  integer :: unitshort = -1 ! The open unit number for docFileBase + .short.
51  integer :: unitlayout = -1 ! The open unit number for docFileBase + .layout.
52  logical :: filesareopen = .false. ! True if any files were successfully opened.
53  character(len=mLen) :: docfilebase = '' ! The basename of the files where run-time
54  ! parameters, settings and defaults are documented.
55  logical :: complete = .true. ! If true, document all parameters.
56  logical :: minimal = .true. ! If true, document non-default parameters.
57  logical :: layout = .true. ! If true, document layout parameters.
58  logical :: definesyntax = .false. ! If true, use #def syntax instead of a=b syntax
59  logical :: warnonconflicts = .false. ! Cause a WARNING error if defaults differ.
60  integer :: commentcolumn = 32 ! Number of spaces before the comment marker.
61  type(link_msg), pointer :: chain_msg => null() ! Db of messages
62  character(len=240) :: blockprefix = '' ! The full name of the current block.
63 end type doc_type
64 
65 type :: link_msg ; private
66  type(link_msg), pointer :: next => null() ! Facilitates linked list
67  character(len=80) :: name ! Parameter name
68  character(len=620) :: msg ! Parameter value and default
69 end type link_msg
70 
71 character(len=4), parameter :: string_true = 'True'
72 character(len=5), parameter :: string_false = 'False'
73 
74 contains
75 
76 ! ----------------------------------------------------------------------
77 
78 subroutine doc_param_none(doc, varname, desc, units)
79  type(doc_type), pointer :: doc
80  character(len=*), intent(in) :: varname, desc, units
81 ! This subroutine handles parameter documentation with no value.
82  integer :: numspc
83  character(len=mLen) :: mesg
84 
85  if (.not. (is_root_pe() .and. associated(doc))) return
86  call open_doc_file(doc)
87 
88  if (doc%filesAreOpen) then
89  numspc = max(1,doc%commentColumn-8-len_trim(varname))
90  mesg = "#define "//trim(varname)//repeat(" ",numspc)//"!"
91  if (len_trim(units) > 0) mesg = trim(mesg)//" ["//trim(units)//"]"
92 
93  if (mesghasbeendocumented(doc, varname, mesg)) return ! Avoid duplicates
94  call writemessageanddesc(doc, mesg, desc)
95  endif
96 end subroutine doc_param_none
97 
98 subroutine doc_param_logical(doc, varname, desc, units, val, default, layoutParam)
99  type(doc_type), pointer :: doc
100  character(len=*), intent(in) :: varname, desc, units
101  logical, intent(in) :: val
102  logical, optional, intent(in) :: default
103  logical, optional, intent(in) :: layoutParam
104 ! This subroutine handles parameter documentation for logicals.
105  character(len=mLen) :: mesg
106  logical :: equalsDefault
107 
108  if (.not. (is_root_pe() .and. associated(doc))) return
109  call open_doc_file(doc)
110 
111  if (doc%filesAreOpen) then
112  if (val) then
113  mesg = define_string(doc,varname,string_true,units)
114  else
115  mesg = undef_string(doc,varname,units)
116  endif
117 
118  equalsdefault = .false.
119  if (present(default)) then
120  if (val .eqv. default) equalsdefault = .true.
121  if (default) then
122  mesg = trim(mesg)//" default = "//string_true
123  else
124  mesg = trim(mesg)//" default = "//string_false
125  endif
126  endif
127 
128  if (mesghasbeendocumented(doc, varname, mesg)) return ! Avoid duplicates
129  call writemessageanddesc(doc, mesg, desc, equalsdefault, layoutparam=layoutparam)
130  endif
131 end subroutine doc_param_logical
132 
133 subroutine doc_param_logical_array(doc, varname, desc, units, vals, default, layoutParam)
134  type(doc_type), pointer :: doc
135  character(len=*), intent(in) :: varname, desc, units
136  logical, intent(in) :: vals(:)
137  logical, optional, intent(in) :: default
138  logical, optional, intent(in) :: layoutParam
139 ! This subroutine handles parameter documentation for arrays of logicals.
140  integer :: i
141  character(len=mLen) :: mesg
142  character(len=mLen) :: valstring
143  logical :: equalsDefault
144 
145  if (.not. (is_root_pe() .and. associated(doc))) return
146  call open_doc_file(doc)
147 
148  if (doc%filesAreOpen) then
149  if (vals(1)) then ; valstring = string_true ; else ; valstring = string_false ; endif
150  do i=2,min(size(vals),128)
151  if (vals(i)) then
152  valstring = trim(valstring)//", "//string_true
153  else
154  valstring = trim(valstring)//", "//string_false
155  endif
156  enddo
157 
158  mesg = define_string(doc,varname,valstring,units)
159 
160  equalsdefault = .false.
161  if (present(default)) then
162  equalsdefault = .true.
163  do i=1,size(vals) ; if (vals(i) .neqv. default) equalsdefault = .false. ; enddo
164  if (default) then
165  mesg = trim(mesg)//" default = "//string_true
166  else
167  mesg = trim(mesg)//" default = "//string_false
168  endif
169  endif
170 
171  if (mesghasbeendocumented(doc, varname, mesg)) return ! Avoid duplicates
172  call writemessageanddesc(doc, mesg, desc, equalsdefault, layoutparam=layoutparam)
173  endif
174 end subroutine doc_param_logical_array
175 
176 subroutine doc_param_int(doc, varname, desc, units, val, default, layoutParam)
177  type(doc_type), pointer :: doc
178  character(len=*), intent(in) :: varname, desc, units
179  integer, intent(in) :: val
180  integer, optional, intent(in) :: default
181  logical, optional, intent(in) :: layoutParam
182 ! This subroutine handles parameter documentation for integers.
183  character(len=mLen) :: mesg
184  character(len=doc%commentColumn) :: valstring
185  logical :: equalsDefault
186 
187  if (.not. (is_root_pe() .and. associated(doc))) return
188  call open_doc_file(doc)
189 
190  if (doc%filesAreOpen) then
191  valstring = int_string(val)
192  mesg = define_string(doc,varname,valstring,units)
193 
194  equalsdefault = .false.
195  if (present(default)) then
196  if (val == default) equalsdefault = .true.
197  mesg = trim(mesg)//" default = "//(trim(int_string(default)))
198  endif
199 
200  if (mesghasbeendocumented(doc, varname, mesg)) return ! Avoid duplicates
201  call writemessageanddesc(doc, mesg, desc, equalsdefault, layoutparam=layoutparam)
202  endif
203 end subroutine doc_param_int
204 
205 subroutine doc_param_int_array(doc, varname, desc, units, vals, default, layoutParam)
206  type(doc_type), pointer :: doc
207  character(len=*), intent(in) :: varname, desc, units
208  integer, intent(in) :: vals(:)
209  integer, optional, intent(in) :: default
210  logical, optional, intent(in) :: layoutParam
211 ! This subroutine handles parameter documentation for arrays of integers.
212  integer :: i
213  character(len=mLen) :: mesg
214  character(len=mLen) :: valstring
215  logical :: equalsDefault
216 
217  if (.not. (is_root_pe() .and. associated(doc))) return
218  call open_doc_file(doc)
219 
220  if (doc%filesAreOpen) then
221  valstring = int_string(vals(1))
222  do i=2,min(size(vals),128)
223  valstring = trim(valstring)//", "//trim(int_string(vals(i)))
224  enddo
225 
226  mesg = define_string(doc,varname,valstring,units)
227 
228  equalsdefault = .false.
229  if (present(default)) then
230  equalsdefault = .true.
231  do i=1,size(vals) ; if (vals(i) /= default) equalsdefault = .false. ; enddo
232  mesg = trim(mesg)//" default = "//(trim(int_string(default)))
233  endif
234 
235  if (mesghasbeendocumented(doc, varname, mesg)) return ! Avoid duplicates
236  call writemessageanddesc(doc, mesg, desc, equalsdefault, layoutparam=layoutparam)
237  endif
238 
239 end subroutine doc_param_int_array
240 
241 subroutine doc_param_real(doc, varname, desc, units, val, default)
242  type(doc_type), pointer :: doc
243  character(len=*), intent(in) :: varname, desc, units
244  real, intent(in) :: val
245  real, optional, intent(in) :: default
246 ! This subroutine handles parameter documentation for reals.
247  character(len=mLen) :: mesg
248  character(len=doc%commentColumn) :: valstring
249  logical :: equalsDefault
250 
251  if (.not. (is_root_pe() .and. associated(doc))) return
252  call open_doc_file(doc)
253 
254  if (doc%filesAreOpen) then
255  valstring = real_string(val)
256  mesg = define_string(doc,varname,valstring,units)
257 
258  equalsdefault = .false.
259  if (present(default)) then
260  if (val == default) equalsdefault = .true.
261  mesg = trim(mesg)//" default = "//trim(real_string(default))
262  endif
263 
264  if (mesghasbeendocumented(doc, varname, mesg)) return ! Avoid duplicates
265  call writemessageanddesc(doc, mesg, desc, equalsdefault)
266  endif
267 end subroutine doc_param_real
268 
269 subroutine doc_param_real_array(doc, varname, desc, units, vals, default)
270  type(doc_type), pointer :: doc
271  character(len=*), intent(in) :: varname, desc, units
272  real, intent(in) :: vals(:)
273  real, optional, intent(in) :: default
274 ! This subroutine handles parameter documentation for arrays of reals.
275  integer :: i
276  character(len=mLen) :: mesg
277  character(len=mLen) :: valstring
278  logical :: equalsDefault
279 
280  if (.not. (is_root_pe() .and. associated(doc))) return
281  call open_doc_file(doc)
282 
283  if (doc%filesAreOpen) then
284  valstring = trim(real_array_string(vals(:)))
285 
286  mesg = define_string(doc,varname,valstring,units)
287 
288  equalsdefault = .false.
289  if (present(default)) then
290  equalsdefault = .true.
291  do i=1,size(vals) ; if (vals(i) /= default) equalsdefault = .false. ; enddo
292  mesg = trim(mesg)//" default = "//trim(real_string(default))
293  endif
294 
295  if (mesghasbeendocumented(doc, varname, mesg)) return ! Avoid duplicates
296  call writemessageanddesc(doc, mesg, desc, equalsdefault)
297  endif
298 
299 end subroutine doc_param_real_array
300 
301 subroutine doc_param_char(doc, varname, desc, units, val, default, layoutParam)
302  type(doc_type), pointer :: doc
303  character(len=*), intent(in) :: varname, desc, units
304  character(len=*), intent(in) :: val
305  character(len=*), optional, intent(in) :: default
306  logical, optional, intent(in) :: layoutParam
307 ! This subroutine handles parameter documentation for character strings.
308  character(len=mLen) :: mesg
309  logical :: equalsDefault
310 
311  if (.not. (is_root_pe() .and. associated(doc))) return
312  call open_doc_file(doc)
313 
314  if (doc%filesAreOpen) then
315  mesg = define_string(doc,varname,'"'//trim(val)//'"',units)
316 
317  equalsdefault = .false.
318  if (present(default)) then
319  if (trim(val) == trim(default)) equalsdefault = .true.
320  mesg = trim(mesg)//' default = "'//trim(adjustl(default))//'"'
321  endif
322 
323  if (mesghasbeendocumented(doc, varname, mesg)) return ! Avoid duplicates
324  call writemessageanddesc(doc, mesg, desc, equalsdefault, layoutparam=layoutparam)
325  endif
326 
327 end subroutine doc_param_char
328 
329 subroutine doc_openblock(doc, blockName, desc)
330  type(doc_type), pointer :: doc
331  character(len=*), intent(in) :: blockName
332  character(len=*), optional, intent(in) :: desc
333 ! This subroutine handles documentation for opening a parameter block.
334  character(len=mLen) :: mesg
335  character(len=doc%commentColumn) :: valstring
336 
337  if (.not. (is_root_pe() .and. associated(doc))) return
338  call open_doc_file(doc)
339 
340  if (doc%filesAreOpen) then
341  mesg = trim(blockname)//'%'
342 
343  if (present(desc)) then
344  call writemessageanddesc(doc, mesg, desc)
345  else
346  call writemessageanddesc(doc, mesg, '')
347  endif
348  endif
349  doc%blockPrefix = trim(doc%blockPrefix)//trim(blockname)//'%'
350 end subroutine doc_openblock
351 
352 subroutine doc_closeblock(doc, blockName)
353  type(doc_type), pointer :: doc
354  character(len=*), intent(in) :: blockName
355 ! This subroutine handles documentation for closing a parameter block.
356  character(len=mLen) :: mesg
357  character(len=doc%commentColumn) :: valstring
358  integer :: i
359 
360  if (.not. (is_root_pe() .and. associated(doc))) return
361  call open_doc_file(doc)
362 
363  if (doc%filesAreOpen) then
364  mesg = '%'//trim(blockname)
365 
366  call writemessageanddesc(doc, mesg, '')
367  endif
368  i = index(trim(doc%blockPrefix), trim(blockname)//'%', .true.)
369  if (i>1) then
370  doc%blockPrefix = trim(doc%blockPrefix(1:i-1))
371  else
372  doc%blockPrefix = ''
373  endif
374 end subroutine doc_closeblock
375 
376 subroutine doc_param_time(doc, varname, desc, units, val, default, layoutParam)
377  type(doc_type), pointer :: doc
378  character(len=*), intent(in) :: varname, desc, units
379  type(time_type), intent(in) :: val
380  type(time_type), optional, intent(in) :: default
381  logical, optional, intent(in) :: layoutParam
382 ! This subroutine handles parameter documentation for time-type variables.
383 ! ### This needs to be written properly!
384  integer :: numspc
385  character(len=mLen) :: mesg
386  logical :: equalsDefault
387 
388  if (.not. (is_root_pe() .and. associated(doc))) return
389  call open_doc_file(doc)
390 
391  equalsdefault = .false.
392  if (doc%filesAreOpen) then
393  numspc = max(1,doc%commentColumn-18-len_trim(varname))
394  mesg = "#define "//trim(varname)//" Time-type"//repeat(" ",numspc)//"!"
395  if (len_trim(units) > 0) mesg = trim(mesg)//" ["//trim(units)//"]"
396 
397  if (mesghasbeendocumented(doc, varname, mesg)) return ! Avoid duplicates
398  call writemessageanddesc(doc, mesg, desc, equalsdefault, layoutparam=layoutparam)
399  endif
400 
401 end subroutine doc_param_time
402 
403 subroutine writemessageanddesc(doc, vmesg, desc, valueWasDefault, indent, layoutParam)
404  type(doc_type), intent(in) :: doc
405  character(len=*), intent(in) :: vmesg, desc
406  logical, optional, intent(in) :: valueWasDefault
407  integer, optional, intent(in) :: indent
408  logical, optional, intent(in) :: layoutParam
409  character(len=mLen) :: mesg
410  integer :: start_ind = 1, end_ind, indnt, tab, len_tab, len_nl
411  logical :: all, short, layout
412 
413  layout = .false.
414  if (present(layoutparam)) layout = layoutparam
415  all = doc%complete .and. (doc%unitAll > 0) .and. .not. layout
416  short = doc%minimal .and. (doc%unitShort > 0) .and. .not. layout
417  if (present(valuewasdefault)) short = short .and. (.not. valuewasdefault)
418 
419  if (all) write(doc%unitAll, '(a)') trim(vmesg)
420  if (short) write(doc%unitShort, '(a)') trim(vmesg)
421  if (layout) write(doc%unitLayout, '(a)') trim(vmesg)
422 
423  if (len_trim(desc) == 0) return
424 
425  len_tab = len_trim("_\t_") - 2
426  len_nl = len_trim("_\n_") -2
427 
428  indnt = doc%commentColumn ; if (present(indent)) indnt = indent
429  start_ind = 1
430  do
431  if (len_trim(desc(start_ind:)) < 1) exit
432 
433  end_ind = index(desc(start_ind:), "\n")
434 
435  if (end_ind > 0) then
436  mesg = repeat(" ",indnt)//"! "//trim(desc(start_ind:start_ind+end_ind-2))
437  start_ind = start_ind + end_ind - 1 + len_nl
438 
439  do ; tab = index(mesg, "\t")
440  if (tab == 0) exit
441  mesg(tab:) = " "//trim(mesg(tab+len_tab:))
442  enddo
443  if (all) write(doc%unitAll, '(a)') trim(mesg)
444  if (short) write(doc%unitShort, '(a)') trim(mesg)
445  if (layout) write(doc%unitLayout, '(a)') trim(mesg)
446  else
447  mesg = repeat(" ",indnt)//"! "//trim(desc(start_ind:))
448  do ; tab = index(mesg, "\t")
449  if (tab == 0) exit
450  mesg(tab:) = " "//trim(mesg(tab+len_tab:))
451  enddo
452  if (all) write(doc%unitAll, '(a)') trim(mesg)
453  if (short) write(doc%unitShort, '(a)') trim(mesg)
454  if (layout) write(doc%unitLayout, '(a)') trim(mesg)
455  exit
456  endif
457 
458  enddo
459 end subroutine writemessageanddesc
460 
461 ! ----------------------------------------------------------------------
462 
463 function real_string(val)
464  real, intent(in) :: val
465  character(len=32) :: real_string
466 ! This function returns a string with a real formatted like '(G)'
467  integer :: len, ind
468 
469  if ((abs(val) < 1.0e4) .and. (abs(val) >= 1.0e-3)) then
470  write(real_string, '(F30.11)') val
471  if (.not.testformattedfloatisreal(real_string,val)) then
472  write(real_string, '(F30.12)') val
473  if (.not.testformattedfloatisreal(real_string,val)) then
474  write(real_string, '(F30.13)') val
475  if (.not.testformattedfloatisreal(real_string,val)) then
476  write(real_string, '(F30.14)') val
477  if (.not.testformattedfloatisreal(real_string,val)) then
478  write(real_string, '(F30.15)') val
479  if (.not.testformattedfloatisreal(real_string,val)) then
480  write(real_string, '(F30.16)') val
481  endif
482  endif
483  endif
484  endif
485  endif
486  do
487  len = len_trim(real_string)
488  if ((len<2) .or. (real_string(len-1:len) == ".0") .or. &
489  (real_string(len:len) /= "0")) exit
490  real_string(len:len) = " "
491  enddo
492  elseif (val == 0.) then
493  real_string = "0.0"
494  else
495  write(real_string(1:32), '(ES23.14)') val
496  if (.not.testformattedfloatisreal(real_string,val)) then
497  write(real_string(1:32), '(ES23.15)') val
498  endif
499  do
500  ind = index(real_string,"0E")
501  if (ind == 0) exit
502  if (real_string(ind-1:ind-1) == ".") exit
503  real_string = real_string(1:ind-1)//real_string(ind+1:)
504  enddo
505  endif
506  real_string = adjustl(real_string)
507 end function real_string
508 
509 function real_array_string(vals,sep)
510  character(len=1320) :: real_array_string
511  real, intent(in) :: vals(:)
512  character(len=*), optional :: sep
513 ! Returns a character string of a comma-separated, compact formatted, reals
514 ! e.g. "1., 2., 5*3., 5.E2"
515  ! Local variables
516  integer :: j, n, b, ns
517  logical :: doWrite
518  character(len=10) :: separator
519  n=1 ; dowrite=.true. ; real_array_string='' ; b=1
520  if (present(sep)) then
521  separator=sep ; ns=len(sep)
522  else
523  separator=', ' ; ns=2
524  endif
525  do j=1,size(vals)
526  dowrite=.true.
527  if (j<size(vals)) then
528  if (vals(j)==vals(j+1)) then
529  n=n+1
530  dowrite=.false.
531  endif
532  endif
533  if (dowrite) then
534  if (b>1) then ! Write separator if a number has already been written
535  write(real_array_string(b:),'(A)') separator
536  b=b+ns
537  endif
538  if (n>1) then
539  write(real_array_string(b:),'(A,"*",A)') trim(int_string(n)),trim(real_string(vals(j)))
540  else
541  write(real_array_string(b:),'(A)') trim(real_string(vals(j)))
542  endif
543  n=1 ; b=len_trim(real_array_string)+1
544  endif
545  enddo
546 end function real_array_string
547 
548 function testformattedfloatisreal(str, val)
549  character(len=*), intent(in) :: str
550  real, intent(in) :: val
551  logical :: testFormattedFloatIsReal
552  ! Local variables
553  real :: scannedVal
554 
555  read(str(1:),*) scannedval
556  if (scannedval == val) then
557  testformattedfloatisreal=.true.
558  else
559  testformattedfloatisreal=.false.
560  endif
561 end function testformattedfloatisreal
562 
563 function int_string(val)
564  integer, intent(in) :: val
565  character(len=24) :: int_string
566 ! This function returns a string with an integer formatted like '(I)'
567  write(int_string, '(i24)') val
568  int_string = adjustl(int_string)
569 end function int_string
570 
571 function logical_string(val)
572  logical, intent(in) :: val
573  character(len=24) :: logical_string
574 ! This function returns a string with an logical formatted like '(L)'
575  write(logical_string, '(l24)') val
576  logical_string = adjustl(logical_string)
577 end function logical_string
578 
579 function define_string(doc,varName,valString,units)
580  type(doc_type), pointer :: doc
581  character(len=*), intent(in) :: varName, valString, units
582  character(len=mLen) :: define_string
583 ! This function returns a string for formatted parameter assignment
584  integer :: numSpaces
585  define_string = repeat(" ",mlen) ! Blank everything for safety
586  if (doc%defineSyntax) then
587  define_string = "#define "//trim(varname)//" "//valstring
588  else
589  define_string = trim(varname)//" = "//valstring
590  endif
591  numspaces = max(1, doc%commentColumn - len_trim(define_string) )
592  define_string = trim(define_string)//repeat(" ",numspaces)//"!"
593  if (len_trim(units) > 0) define_string = trim(define_string)//" ["//trim(units)//"]"
594 end function define_string
595 
596 function undef_string(doc,varName,units)
597  type(doc_type), pointer :: doc
598  character(len=*), intent(in) :: varName, units
599  character(len=mLen) :: undef_string
600 ! This function returns a string for formatted false logicals
601  integer :: numSpaces
602  undef_string = repeat(" ",240) ! Blank everything for safety
603  undef_string = "#undef "//trim(varname)
604  if (doc%defineSyntax) then
605  undef_string = "#undef "//trim(varname)
606  else
607  undef_string = trim(varname)//" = "//string_false
608  endif
609  numspaces = max(1, doc%commentColumn - len_trim(undef_string) )
610  undef_string = trim(undef_string)//repeat(" ",numspaces)//"!"
611  if (len_trim(units) > 0) undef_string = trim(undef_string)//" ["//trim(units)//"]"
612 end function undef_string
613 
614 ! ----------------------------------------------------------------------
615 
616 subroutine doc_module(doc, modname, desc)
617  type(doc_type), pointer :: doc
618  character(len=*), intent(in) :: modname, desc
619 ! This subroutine handles the module documentation
620  character(len=mLen) :: mesg
621 
622  if (.not. (is_root_pe() .and. associated(doc))) return
623  call open_doc_file(doc)
624 
625  if (doc%filesAreOpen) then
626  call writemessageanddesc(doc, '', '') ! Blank line for delineation
627  mesg = "! === module "//trim(modname)//" ==="
628  call writemessageanddesc(doc, mesg, desc, indent=0)
629  endif
630 end subroutine doc_module
631 
632 subroutine doc_subroutine(doc, modname, subname, desc)
633  type(doc_type), pointer :: doc
634  character(len=*), intent(in) :: modname, subname, desc
635 ! This subroutine handles the subroutine documentation
636  if (.not. (is_root_pe() .and. associated(doc))) return
637  call open_doc_file(doc)
638 
639 end subroutine doc_subroutine
640 
641 subroutine doc_function(doc, modname, fnname, desc)
642  type(doc_type), pointer :: doc
643  character(len=*), intent(in) :: modname, fnname, desc
644 ! This subroutine handles the function documentation
645  if (.not. (is_root_pe() .and. associated(doc))) return
646  call open_doc_file(doc)
647 
648 end subroutine doc_function
649 
650 ! ----------------------------------------------------------------------
651 
652 subroutine doc_init(docFileBase, doc, minimal, complete)
653  character(len=*), intent(in) :: docFileBase
654  type(doc_type), pointer :: doc
655  logical, optional, intent(in) :: minimal, complete
656 ! Arguments: docFileBase - The name of the doc file.
657 ! (inout) doc - The doc_type to populate.
658 
659  if (.not. associated(doc)) then
660  allocate(doc)
661  endif
662 
663  doc%docFileBase = docfilebase
664  if (present(minimal)) doc%minimal = minimal
665  if (present(minimal)) doc%complete = complete
666 end subroutine doc_init
667 
668 subroutine open_doc_file(doc)
669  type(doc_type), pointer :: doc
670 
671  logical :: opened, new_file
672  integer :: ios
673  character(len=240) :: fileName
674 
675  if (.not. (is_root_pe() .and. associated(doc))) return
676 
677  if ((len_trim(doc%docFileBase) > 0) .and. doc%complete .and. (doc%unitAll<0)) then
678  new_file = .true. ; if (doc%unitAll /= -1) new_file = .false.
679  doc%unitAll = find_unused_unit_number()
680 
681  write(filename(1:240),'(a)') trim(doc%docFileBase)//'.all'
682  if (new_file) then
683  open(doc%unitAll, file=trim(filename), access='SEQUENTIAL', form='FORMATTED', &
684  action='WRITE', status='REPLACE', iostat=ios)
685  write(doc%unitAll, '(a)') &
686  '! This file was written by the model and records all non-layout parameters used at run-time.'
687  else ! This file is being reopened, and should be appended.
688  open(doc%unitAll, file=trim(filename), access='SEQUENTIAL', form='FORMATTED', &
689  action='WRITE', status='OLD', position='APPEND', iostat=ios)
690  endif
691  inquire(doc%unitAll, opened=opened)
692  if ((.not.opened) .or. (ios /= 0)) then
693  call mom_error(fatal, "Failed to open doc file "//trim(filename)//".")
694  endif
695  doc%filesAreOpen = .true.
696  endif
697 
698  if ((len_trim(doc%docFileBase) > 0) .and. doc%minimal .and. (doc%unitShort<0)) then
699  new_file = .true. ; if (doc%unitShort /= -1) new_file = .false.
700  doc%unitShort = find_unused_unit_number()
701 
702  write(filename(1:240),'(a)') trim(doc%docFileBase)//'.short'
703  if (new_file) then
704  open(doc%unitShort, file=trim(filename), access='SEQUENTIAL', form='FORMATTED', &
705  action='WRITE', status='REPLACE', iostat=ios)
706  write(doc%unitShort, '(a)') &
707  '! This file was written by the model and records the non-default parameters used at run-time.'
708  else ! This file is being reopened, and should be appended.
709  open(doc%unitShort, file=trim(filename), access='SEQUENTIAL', form='FORMATTED', &
710  action='WRITE', status='OLD', position='APPEND', iostat=ios)
711  endif
712  inquire(doc%unitShort, opened=opened)
713  if ((.not.opened) .or. (ios /= 0)) then
714  call mom_error(fatal, "Failed to open doc file "//trim(filename)//".")
715  endif
716  doc%filesAreOpen = .true.
717  endif
718 
719  if ((len_trim(doc%docFileBase) > 0) .and. doc%layout .and. (doc%unitLayout<0)) then
720  new_file = .true. ; if (doc%unitLayout /= -1) new_file = .false.
721  doc%unitLayout = find_unused_unit_number()
722 
723  write(filename(1:240),'(a)') trim(doc%docFileBase)//'.layout'
724  if (new_file) then
725  open(doc%unitLayout, file=trim(filename), access='SEQUENTIAL', form='FORMATTED', &
726  action='WRITE', status='REPLACE', iostat=ios)
727  write(doc%unitLayout, '(a)') &
728  '! This file was written by the model and records the layout parameters used at run-time.'
729  else ! This file is being reopened, and should be appended.
730  open(doc%unitLayout, file=trim(filename), access='SEQUENTIAL', form='FORMATTED', &
731  action='WRITE', status='OLD', position='APPEND', iostat=ios)
732  endif
733  inquire(doc%unitLayout, opened=opened)
734  if ((.not.opened) .or. (ios /= 0)) then
735  call mom_error(fatal, "Failed to open doc file "//trim(filename)//".")
736  endif
737  doc%filesAreOpen = .true.
738  endif
739 
740 end subroutine open_doc_file
741 
742 function find_unused_unit_number()
743 ! Find an unused unit number.
744 ! Returns >0 if found. FATAL if not.
745  integer :: find_unused_unit_number
746  logical :: opened
747  do find_unused_unit_number=512,42,-1
748  inquire( find_unused_unit_number, opened=opened)
749  if (.not.opened) exit
750  enddo
751  if (opened) call mom_error(fatal, &
752  "doc_init failed to find an unused unit number.")
753 end function find_unused_unit_number
754 
755 subroutine doc_end(doc)
756  type(doc_type), pointer :: doc
757  type(link_msg), pointer :: this, next
758 
759  if (.not.associated(doc)) return
760 
761  if (doc%unitAll > 0) then
762  close(doc%unitAll)
763  doc%unitAll = -2
764  endif
765 
766  if (doc%unitShort > 0) then
767  close(doc%unitShort)
768  doc%unitShort = -2
769  endif
770 
771  if (doc%unitLayout > 0) then
772  close(doc%unitLayout)
773  doc%unitLayout = -2
774  endif
775 
776  doc%filesAreOpen = .false.
777 
778  this => doc%chain_msg
779  do while( associated(this) )
780  next => this%next
781  deallocate(this)
782  this => next
783  enddo
784 end subroutine doc_end
785 
786 ! -----------------------------------------------------------------------------
787 
788 function mesghasbeendocumented(doc,varName,mesg)
789  type(doc_type), pointer :: doc
790  character(len=*), intent(in) :: varName, mesg
791  logical :: mesgHasBeenDocumented
792 ! Returns true if documentation has already been written
793  type(link_msg), pointer :: newLink, this, last
794 
795  mesghasbeendocumented = .false.
796 
797 !!if (mesg(1:1) == '!') return ! Ignore commented parameters
798 
799  ! Search through list for this parameter
800  last => null()
801  this => doc%chain_msg
802  do while( associated(this) )
803  if (trim(doc%blockPrefix)//trim(varname) == trim(this%name)) then
804  mesghasbeendocumented = .true.
805  if (trim(mesg) == trim(this%msg)) return
806  ! If we fail the above test then cause an error
807  if (mesg(1:1) == '!') return ! Do not cause error for commented parameters
808  call mom_error(warning, "Previous msg:"//trim(this%msg))
809  call mom_error(warning, "New message :"//trim(mesg))
810  call mom_error(warning, "Encountered inconsistent documentation line for parameter "&
811  //trim(varname)//"!")
812  endif
813  last => this
814  this => this%next
815  enddo
816 
817  ! Allocate a new link
818  allocate(newlink)
819  newlink%name = trim(doc%blockPrefix)//trim(varname)
820  newlink%msg = trim(mesg)
821  newlink%next => null()
822  if (.not. associated(doc%chain_msg)) then
823  doc%chain_msg => newlink
824  else
825  if (.not. associated(last)) call mom_error(fatal, &
826  "Unassociated LINK in mesgHasBeenDocumented: "//trim(mesg))
827  last%next => newlink
828  endif
829 end function mesghasbeendocumented
830 
831 end module mom_document
character(len=24) function logical_string(val)
subroutine doc_param_int_array(doc, varname, desc, units, vals, default, layoutParam)
character(len=32) function real_string(val)
integer, parameter mlen
subroutine doc_param_none(doc, varname, desc, units)
subroutine, public doc_end(doc)
subroutine, public doc_init(docFileBase, doc, minimal, complete)
subroutine, public doc_subroutine(doc, modname, subname, desc)
character(len=24) function int_string(val)
subroutine doc_param_logical(doc, varname, desc, units, val, default, layoutParam)
character(len=4), parameter string_true
subroutine open_doc_file(doc)
subroutine doc_param_real_array(doc, varname, desc, units, vals, default)
character(len=mlen) function undef_string(doc, varName, units)
character(len=mlen) function define_string(doc, varName, valString, units)
integer function find_unused_unit_number()
subroutine, public doc_closeblock(doc, blockName)
subroutine, public doc_openblock(doc, blockName, desc)
logical function, public is_root_pe()
subroutine writemessageanddesc(doc, vmesg, desc, valueWasDefault, indent, layoutParam)
subroutine doc_param_logical_array(doc, varname, desc, units, vals, default, layoutParam)
subroutine doc_param_int(doc, varname, desc, units, val, default, layoutParam)
subroutine doc_param_char(doc, varname, desc, units, val, default, layoutParam)
subroutine doc_param_time(doc, varname, desc, units, val, default, layoutParam)
character(len=5), parameter string_false
character(len=1320) function real_array_string(vals, sep)
subroutine doc_param_real(doc, varname, desc, units, val, default)
logical function testformattedfloatisreal(str, val)
subroutine, public doc_function(doc, modname, fnname, desc)
subroutine, public mom_error(level, message, all_print)
logical function mesghasbeendocumented(doc, varName, mesg)
subroutine, public doc_module(doc, modname, desc)