fdoc

filefdoc_settings.f90

Contents

fdoc_settingsprivate

The project settings: FORD's options, their defaults and their conversion from text.

Source

module fdoc_settings
!! The project settings: FORD's options, their defaults and their conversion from text.
!!
!! Every source of settings (project file, `fpm.toml`, `--config`, command line) goes through
!! [[set_option]], which takes the option's values as a list of strings, the form the project file
!! gives them.
use omp_lib, only: omp_get_num_procs
use json_module, only: json_core, json_value
use ffmt_strings, only: to_s
use fdoc_util
implicit none
private

public :: new_settings, set_option, check_settings, finish_settings, dump_settings, is_option

character(len=*), parameter :: STRING_OPTIONS = &
    ' author author_description author_pic bitbucket creation_date doc_license docmark docmark_alt email encoding'// &
    ' facebook github gitlab gitter_sidecar google_plus license linkedin predocmark predocmark_alt preprocessor'// &
    ' privacy_policy_url project project_bitbucket project_download project_github project_gitlab'// &
    ' project_sourceforge project_url project_website revision sort summary terms_of_service_url twitter version'// &
    ' website year '
character(len=*), parameter :: PATH_OPTIONS = &
    ' css favicon graph_dir logo mathjax_config md_base_dir media_dir output_dir page_dir '
character(len=*), parameter :: LOGICAL_OPTIONS = &
    ' coloured_edges dbg doxygen externalize fixed_length_limit force graph hide_undoc incl_src lower preprocess'// &
    ' print_creation_date proc_internals quiet search show_proc_parent source warn '
character(len=*), parameter :: INTEGER_OPTIONS = &
    ' graph_maxdepth graph_maxnodes graph_project_maxnodes max_frontpage_items parallel '
character(len=*), parameter :: LIST_OPTIONS = &
    ' copy_subdir display exclude exclude_dir extensions extra_vartypes fixed_extensions fpp_extensions'// &
    ' html_template_dir include macro md_extensions src_dir '
character(len=*), parameter :: PATH_LIST_OPTIONS = ' copy_subdir exclude_dir html_template_dir include src_dir '
character(len=*), parameter :: MAP_OPTIONS = ' alias external extra_mods extra_filetypes '

type, public :: map_entry_t
    !! A key and its value.
    character(len=:), allocatable :: key
    character(len=:), allocatable :: value
end type map_entry_t

type, public :: file_type_t
    !! An `extra_filetypes` entry.
    character(len=:), allocatable :: extension
    character(len=:), allocatable :: comment
    character(len=:), allocatable :: lexer      !! not allocated if not given
end type file_type_t

type, public :: settings_t
    !! The project settings. Optional text options are not allocated unless set.
    character(len=:), allocatable :: author, author_description, author_pic, bitbucket, doc_license, email, facebook
    character(len=:), allocatable :: github, gitlab, gitter_sidecar, google_plus, license, linkedin, privacy_policy_url
    character(len=:), allocatable :: project_bitbucket, project_download, project_github, project_gitlab
    character(len=:), allocatable :: project_sourceforge, project_website, revision, summary, terms_of_service_url
    character(len=:), allocatable :: twitter, version, website
    character(len=:), allocatable :: creation_date, docmark, docmark_alt, encoding, predocmark, predocmark_alt
    character(len=:), allocatable :: preprocessor, project, project_url, sort, year
    character(len=:), allocatable :: css, favicon, graph_dir, logo, mathjax_config, md_base_dir, media_dir, output_dir, page_dir
    character(len=:), allocatable :: directory          !! the project's directory
    logical :: coloured_edges = .false., dbg = .true., doxygen = .true., externalize = .true.
    logical :: fixed_length_limit = .true., force = .false., graph = .false., hide_undoc = .false., incl_src = .true.
    logical :: lower = .false., preprocess = .true., print_creation_date = .false., proc_internals = .false.
    logical :: quiet = .false., search = .true., show_proc_parent = .false., source = .false., warn = .false.
    logical :: relative = .true.                        !! true if `project_url` was not given
    integer :: graph_maxdepth = 10000, graph_maxnodes = 1000000000, graph_project_maxnodes = huge(1)
    integer :: max_frontpage_items = 10
    integer :: parallel = 1                             !! threads to use: `new_settings` gives every core
    type(string_t), allocatable :: copy_subdir(:), display(:), exclude(:), exclude_dir(:), extensions(:)
    type(string_t), allocatable :: extra_vartypes(:), fixed_extensions(:), fpp_extensions(:), html_template_dir(:)
    type(string_t), allocatable :: include(:), macro(:), md_extensions(:), src_dir(:)
    type(map_entry_t), allocatable :: alias(:), external(:), extra_mods(:)
    type(file_type_t), allocatable :: extra_filetypes(:)
end type settings_t

contains

!> The defaults.
function new_settings() result(s)
    type(settings_t) :: s
    integer :: values(8)

    call date_and_time(values=values)
    s%creation_date = '%Y-%m-%dT%H:%M:%S.%f%z'
    s%parallel = omp_get_num_procs()
    s%docmark = '!'
    s%docmark_alt = '*'
    s%predocmark = '>'
    s%predocmark_alt = '|'
    s%encoding = 'utf-8'
    s%preprocessor = 'pcpp -D__GFORTRAN__ --passthru-comments'
    s%project = 'Fortran Program'
    s%project_url = ''
    s%sort = 'src'
    s%year = to_s(values(1))
    s%doc_license = ''
    s%license = ''
    s%md_base_dir = '.'
    s%output_dir = './doc'
    s%display = [string_t('public'), string_t('protected')]
    s%src_dir = [string_t('./src')]
    s%extensions = [string_t('f90'), string_t('f95'), string_t('f03'), string_t('f08'), string_t('f15')]
    s%fixed_extensions = [string_t('f'), string_t('for'), string_t('F'), string_t('FOR')]
    s%fpp_extensions = [string_t('F90'), string_t('F95'), string_t('F03'), string_t('F08'), string_t('F15'), &
                        string_t('F'), string_t('FOR')]
    allocate (s%copy_subdir(0), s%exclude(0), s%exclude_dir(0), s%extra_vartypes(0), s%html_template_dir(0))
    allocate (s%include(0), s%macro(0), s%md_extensions(0))
    allocate (s%alias(0), s%external(0), s%extra_mods(0), s%extra_filetypes(0))
end function new_settings

!> True if `key` is one of the blank-separated words of `set`, which starts and ends with a blank.
pure logical function in_set(set, key)
    character(len=*), intent(in) :: set     !! the words
    character(len=*), intent(in) :: key     !! the word to find
    in_set = index(set, ' '//key//' ') > 0
end function in_set

!> True if `key` is the name of an option.
pure logical function is_option(key)
    character(len=*), intent(in) :: key     !! the name
    is_option = len(key) > 0 .and. index(key, ' ') == 0 .and. (in_set(STRING_OPTIONS, key) .or. in_set(PATH_OPTIONS, key) &
                                                               .or. in_set(LOGICAL_OPTIONS, key) .or. &
                                                               in_set(INTEGER_OPTIONS, key) .or. in_set(LIST_OPTIONS, key) &
                                                               .or. in_set(MAP_OPTIONS, key))
end function is_option

!> Set option `key`, which must be one (see [[is_option]]), from its values as text.
subroutine set_option(s, key, values, stat, errmsg)
    type(settings_t), intent(inout) :: s                !! the settings
    character(len=*), intent(in) :: key                 !! the option
    type(string_t), intent(in) :: values(:)             !! its values
    integer, intent(out) :: stat                        !! nonzero on failure
    character(len=:), allocatable, intent(out) :: errmsg  !! what is wrong
    character(len=:), allocatable :: text
    logical :: flag, ok
    integer :: number

    stat = 0
    if (in_set(LOGICAL_OPTIONS, key)) then
        if (size(values) /= 1) then
            call fail("Could not convert option '"//key//"' to bool: expected a single value")
            return
        end if
        call parse_bool(values(1)%s, flag, ok)
        if (.not. ok) then
            call fail("Could not convert option '"//key//"' to bool: expected 'true'/'false', got: "//values(1)%s)
            return
        end if
        call set_logical(s, key, flag)
    else if (in_set(INTEGER_OPTIONS, key)) then
        call parse_int(values(1)%s, number, ok)
        if (.not. ok) then
            call fail("Could not convert option '"//key//"' to an integer: "//values(1)%s)
            return
        end if
        select case (key)
        case ('graph_maxdepth'); s%graph_maxdepth = number
        case ('graph_maxnodes'); s%graph_maxnodes = number
        case ('graph_project_maxnodes'); s%graph_project_maxnodes = number
        case ('max_frontpage_items'); s%max_frontpage_items = number
        case ('parallel'); s%parallel = number
        end select
    else if (in_set(LIST_OPTIONS, key)) then
        call set_list(s, key, values)
    else if (in_set(MAP_OPTIONS, key)) then
        call set_map(s, key, values, stat, errmsg)
    else
        text = join(values, achar(10))
        call set_text(s, key, text)
    end if

contains

    !> Record a failure.
    subroutine fail(msg)
        character(len=*), intent(in) :: msg     !! what is wrong
        stat = 1
        errmsg = msg
    end subroutine fail

end subroutine set_option

!> Set a logical option.
subroutine set_logical(s, key, value)
    type(settings_t), intent(inout) :: s    !! the settings
    character(len=*), intent(in) :: key     !! the option
    logical, intent(in) :: value            !! its value

    select case (key)
    case ('coloured_edges'); s%coloured_edges = value
    case ('dbg'); s%dbg = value
    case ('doxygen'); s%doxygen = value
    case ('externalize'); s%externalize = value
    case ('fixed_length_limit'); s%fixed_length_limit = value
    case ('force'); s%force = value
    case ('graph'); s%graph = value
    case ('hide_undoc'); s%hide_undoc = value
    case ('incl_src'); s%incl_src = value
    case ('lower'); s%lower = value
    case ('preprocess'); s%preprocess = value
    case ('print_creation_date'); s%print_creation_date = value
    case ('proc_internals'); s%proc_internals = value
    case ('quiet'); s%quiet = value
    case ('search'); s%search = value
    case ('show_proc_parent'); s%show_proc_parent = value
    case ('source'); s%source = value
    case ('warn'); s%warn = value
    end select
end subroutine set_logical

!> Set a text or path option.
subroutine set_text(s, key, value)
    type(settings_t), intent(inout) :: s    !! the settings
    character(len=*), intent(in) :: key     !! the option
    character(len=*), intent(in) :: value   !! its value

    select case (key)
    case ('author'); s%author = value
    case ('author_description'); s%author_description = value
    case ('author_pic'); s%author_pic = value
    case ('bitbucket'); s%bitbucket = value
    case ('creation_date'); s%creation_date = value
    case ('doc_license'); s%doc_license = value
    case ('docmark'); s%docmark = value
    case ('docmark_alt'); s%docmark_alt = value
    case ('email'); s%email = value
    case ('encoding'); s%encoding = value
    case ('facebook'); s%facebook = value
    case ('github'); s%github = value
    case ('gitlab'); s%gitlab = value
    case ('gitter_sidecar'); s%gitter_sidecar = value
    case ('google_plus'); s%google_plus = value
    case ('license'); s%license = value
    case ('linkedin'); s%linkedin = value
    case ('predocmark'); s%predocmark = value
    case ('predocmark_alt'); s%predocmark_alt = value
    case ('preprocessor'); s%preprocessor = value
    case ('privacy_policy_url'); s%privacy_policy_url = value
    case ('project'); s%project = value
    case ('project_bitbucket'); s%project_bitbucket = value
    case ('project_download'); s%project_download = value
    case ('project_github'); s%project_github = value
    case ('project_gitlab'); s%project_gitlab = value
    case ('project_sourceforge'); s%project_sourceforge = value
    case ('project_url'); s%project_url = value
    case ('project_website'); s%project_website = value
    case ('revision'); s%revision = value
    case ('sort'); s%sort = value
    case ('summary'); s%summary = value
    case ('terms_of_service_url'); s%terms_of_service_url = value
    case ('twitter'); s%twitter = value
    case ('version'); s%version = value
    case ('website'); s%website = value
    case ('year'); s%year = value
    case ('css'); s%css = value
    case ('favicon'); s%favicon = value
    case ('logo'); s%logo = value
    case ('graph_dir'); s%graph_dir = value
    case ('mathjax_config'); s%mathjax_config = value
    case ('md_base_dir'); s%md_base_dir = value
    case ('media_dir'); s%media_dir = value
    case ('output_dir'); s%output_dir = value
    case ('page_dir'); s%page_dir = value
    end select
end subroutine set_text

!> Set a list option.
subroutine set_list(s, key, values)
    type(settings_t), intent(inout) :: s    !! the settings
    character(len=*), intent(in) :: key     !! the option
    type(string_t), intent(in) :: values(:) !! its values

    select case (key)
    case ('copy_subdir'); s%copy_subdir = values
    case ('display'); s%display = values
    case ('exclude'); s%exclude = values
    case ('exclude_dir'); s%exclude_dir = values
    case ('extensions'); s%extensions = values
    case ('extra_vartypes'); s%extra_vartypes = values
    case ('fixed_extensions'); s%fixed_extensions = values
    case ('fpp_extensions'); s%fpp_extensions = values
    case ('html_template_dir'); s%html_template_dir = values
    case ('include'); s%include = values
    case ('macro'); s%macro = values
    case ('md_extensions'); s%md_extensions = values
    case ('src_dir'); s%src_dir = values
    end select
end subroutine set_list

!> Set a map option from strings of the form `key<sep>value`. Empty strings are ignored.
subroutine set_map(s, key, values, stat, errmsg)
    type(settings_t), intent(inout) :: s                !! the settings
    character(len=*), intent(in) :: key                 !! the option
    type(string_t), intent(in) :: values(:)             !! its values
    integer, intent(out) :: stat                        !! nonzero on failure
    character(len=:), allocatable, intent(out) :: errmsg  !! what is wrong
    type(map_entry_t), allocatable :: entries(:)
    type(file_type_t), allocatable :: files(:)
    type(string_t), allocatable :: words(:)
    character :: sep
    integer :: k, at

    stat = 0
    if (key == 'extra_filetypes') then
        allocate (files(0))
        do k = 1, size(values)
            if (len(values(k)%s) == 0) cycle
            words = split_words(values(k)%s)
            if (size(words) < 2 .or. size(words) > 3) then
                stat = 1
                errmsg = "Unexpected format for 'extra_filetype': expected 'extension comment [lexer]', got "//values(k)%s
                return
            end if
            files = [files, file_type_t(words(1)%s, words(2)%s)]
            if (size(words) == 3) files(size(files))%lexer = words(3)%s
        end do
        call move_alloc(files, s%extra_filetypes)
        return
    end if

    sep = merge(':', '=', key == 'extra_mods')
    allocate (entries(0))
    do k = 1, size(values)
        if (len(values(k)%s) == 0) cycle
        at = index(values(k)%s, sep)
        if (at == 0) then
            stat = 1
            errmsg = "Error setting option '"//key//"': expected '"//sep//"' in '"//values(k)%s//"'"
            return
        end if
        call add_entry(entries, strip(values(k)%s(:at - 1)), strip(values(k)%s(at + 1:)))
    end do
    select case (key)
    case ('alias'); call move_alloc(entries, s%alias)
    case ('external'); call move_alloc(entries, s%external)
    case ('extra_mods'); call move_alloc(entries, s%extra_mods)
    end select
end subroutine set_map

!> Set `key` in `entries`, replacing any entry it has.
subroutine add_entry(entries, key, value)
    type(map_entry_t), allocatable, intent(inout) :: entries(:) !! the map
    character(len=*), intent(in) :: key                         !! the key
    character(len=*), intent(in) :: value                       !! its value
    integer :: k

    do k = 1, size(entries)
        if (entries(k)%key == key) then
            entries(k)%value = value
            return
        end if
    end do
    entries = [entries, map_entry_t(key, value)]
end subroutine add_entry

!> Validate the project file's settings and derive what depends on them, before the command line
!> is applied.
subroutine check_settings(s, stat, errmsg)
    type(settings_t), intent(inout) :: s                !! the settings
    integer, intent(out) :: stat                        !! nonzero on failure
    character(len=:), allocatable, intent(out) :: errmsg  !! what is wrong
    type(string_t), allocatable :: marks(:)
    integer :: k, j

    stat = 0
    s%relative = s%project_url == ''
    do k = 1, size(s%fixed_extensions)
        if (contains_string(s%extensions, s%fixed_extensions(k)%s)) then
            stat = 1
            errmsg = "Fixed-form extension '"//s%fixed_extensions(k)%s//"' also appears in the free-form extension list"
            return
        end if
    end do
    do k = 1, size(s%extra_mods)
        do j = 1, size(s%external)
            if (s%extra_mods(k)%key == s%external(j)%key) then
                stat = 1
                errmsg = "extra-mod '"//s%extra_mods(k)%key//"' also appears in external mods"
                return
            end if
        end do
    end do
    do k = 1, size(s%display)
        s%display(k)%s = lowercase(s%display(k)%s)
    end do
    s%extensions = [s%extensions, s%fpp_extensions]
    call sort_strings(s%extensions)
    call push(s%exclude_dir, s%output_dir)
    call add_intrinsic_modules(s)

    marks = [string_t(s%docmark), string_t(s%predocmark), string_t(s%docmark_alt), string_t(s%predocmark_alt)]
    do k = 1, size(marks)
        do j = k + 1, size(marks)
            if (marks(k)%s == marks(j)%s .and. len(marks(k)%s) > 0) then
                stat = 1
                errmsg = "Doc comment markers are the same: '"//marks(k)%s//"'"
                return
            end if
        end do
    end do
end subroutine check_settings

!> Add the modules that come with compilers and common libraries to `extra_mods`.
subroutine add_intrinsic_modules(s)
    type(settings_t), intent(inout) :: s    !! the settings
    character(len=*), parameter :: WIKI = 'http://fortranwiki.org/fortran/show/'

    call add_entry(s%extra_mods, 'iso_fortran_env', WIKI//'iso_fortran_env')
    call add_entry(s%extra_mods, 'iso_c_binding', WIKI//'iso_c_binding')
    call add_entry(s%extra_mods, 'ieee_arithmetic', WIKI//'ieee_arithmetic')
    call add_entry(s%extra_mods, 'ieee_exceptions', WIKI//'IEEE+arithmetic')
    call add_entry(s%extra_mods, 'ieee_features', WIKI//'IEEE+arithmetic')
    call add_entry(s%extra_mods, 'openacc', &
                   'https://www.openacc.org/sites/default/files/inline-images/Specification/OpenACC.3.0.pdf#page=85')
    call add_entry(s%extra_mods, 'omp_lib', 'https://www.openmp.org/spec-html/5.1/openmpch3.html#x156-1890003')
    call add_entry(s%extra_mods, 'mpi', 'http://www.mpi-forum.org/docs/mpi-3.1/mpi31-report/node410.htm')
    call add_entry(s%extra_mods, 'mpi_f08', 'http://www.mpi-forum.org/docs/mpi-3.1/mpi31-report/node409.htm')
end subroutine add_intrinsic_modules

!> Make paths absolute and format dates, after all settings are applied.
subroutine finish_settings(s, directory, stat, errmsg)
    type(settings_t), intent(inout) :: s                !! the settings
    character(len=*), intent(in) :: directory           !! the project's directory
    integer, intent(out) :: stat                        !! nonzero on failure
    character(len=:), allocatable, intent(out) :: errmsg  !! what is wrong
    integer :: k

    stat = 0
    s%directory = resolve_path(directory, '.')
    if (s%md_base_dir == '.') s%md_base_dir = s%directory
    call normalise(s%css)
    call normalise(s%favicon)
    call normalise(s%graph_dir)
    call normalise(s%logo)
    call normalise(s%mathjax_config)
    call normalise(s%md_base_dir)
    call normalise(s%media_dir)
    call normalise(s%output_dir)
    call normalise(s%page_dir)
    call normalise_list(s%copy_subdir)
    call normalise_list(s%exclude_dir)
    call normalise_list(s%html_template_dir)
    call normalise_list(s%include)
    call normalise_list(s%src_dir)
    if (s%relative) s%project_url = s%output_dir
    s%creation_date = strftime(s%creation_date)

    do k = 1, size(s%src_dir)
        if (s%src_dir(k)%s == s%output_dir .or. starts_with(s%src_dir(k)%s, s%output_dir//'/')) then
            stat = 1
            errmsg = 'Source directory '//s%src_dir(k)%s//' is a subdirectory of output directory '//s%output_dir
            return
        end if
    end do
    if (.not. s%preprocess) s%fpp_extensions = [string_t ::]

contains

    !> Make `path` absolute, if it is set.
    subroutine normalise(path)
        character(len=:), allocatable, intent(inout) :: path    !! the path
        if (allocated(path)) path = resolve_path(s%directory, path)
    end subroutine normalise

    !> Make every path in `list` absolute.
    subroutine normalise_list(list)
        type(string_t), intent(inout) :: list(:)    !! the paths
        integer :: j
        do j = 1, size(list)
            list(j)%s = resolve_path(s%directory, list(j)%s)
        end do
    end subroutine normalise_list

end subroutine finish_settings

!> The settings as a JSON object, for comparison with FORD's. Unset options are `null`.
function dump_settings(s) result(text)
    type(settings_t), intent(in) :: s       !! the settings
    character(len=:), allocatable :: text
    type(json_core) :: json
    type(json_value), pointer :: root, group
    integer :: k

    call json%initialize()
    call json%create_object(root, '')
    call put_text('author', s%author)
    call put_text('author_description', s%author_description)
    call put_text('author_pic', s%author_pic)
    call put_text('bitbucket', s%bitbucket)
    call put_text('css', s%css)
    call put_text('directory', s%directory)
    call put_text('doc_license', s%doc_license)
    call put_text('docmark', s%docmark)
    call put_text('docmark_alt', s%docmark_alt)
    call put_text('email', s%email)
    call put_text('encoding', s%encoding)
    call put_text('facebook', s%facebook)
    call put_text('github', s%github)
    call put_text('gitlab', s%gitlab)
    call put_text('gitter_sidecar', s%gitter_sidecar)
    call put_text('google_plus', s%google_plus)
    call put_text('graph_dir', s%graph_dir)
    call put_text('license', s%license)
    call put_text('logo', s%logo)
    call put_text('linkedin', s%linkedin)
    call put_text('mathjax_config', s%mathjax_config)
    call put_text('md_base_dir', s%md_base_dir)
    call put_text('media_dir', s%media_dir)
    call put_text('output_dir', s%output_dir)
    call put_text('page_dir', s%page_dir)
    call put_text('predocmark', s%predocmark)
    call put_text('predocmark_alt', s%predocmark_alt)
    call put_text('preprocessor', s%preprocessor)
    call put_text('privacy_policy_url', s%privacy_policy_url)
    call put_text('project', s%project)
    call put_text('project_bitbucket', s%project_bitbucket)
    call put_text('project_download', s%project_download)
    call put_text('project_github', s%project_github)
    call put_text('project_gitlab', s%project_gitlab)
    call put_text('project_sourceforge', s%project_sourceforge)
    call put_text('project_url', s%project_url)
    call put_text('project_website', s%project_website)
    call put_text('revision', s%revision)
    call put_text('sort', s%sort)
    call put_text('summary', s%summary)
    call put_text('terms_of_service_url', s%terms_of_service_url)
    call put_text('twitter', s%twitter)
    call put_text('version', s%version)
    call put_text('website', s%website)
    call put_text('year', s%year)
    call put_logical('coloured_edges', s%coloured_edges)
    call put_logical('dbg', s%dbg)
    call put_logical('doxygen', s%doxygen)
    call put_logical('externalize', s%externalize)
    call put_logical('fixed_length_limit', s%fixed_length_limit)
    call put_logical('force', s%force)
    call put_logical('graph', s%graph)
    call put_logical('hide_undoc', s%hide_undoc)
    call put_logical('incl_src', s%incl_src)
    call put_logical('lower', s%lower)
    call put_logical('preprocess', s%preprocess)
    call put_logical('print_creation_date', s%print_creation_date)
    call put_logical('proc_internals', s%proc_internals)
    call put_logical('quiet', s%quiet)
    call put_logical('relative', s%relative)
    call put_logical('search', s%search)
    call put_logical('show_proc_parent', s%show_proc_parent)
    call put_logical('source', s%source)
    call put_logical('warn', s%warn)
    call json%add(root, 'graph_maxdepth', s%graph_maxdepth)
    call json%add(root, 'graph_maxnodes', s%graph_maxnodes)
    call json%add(root, 'max_frontpage_items', s%max_frontpage_items)
    call put_list('copy_subdir', s%copy_subdir)
    call put_list('display', s%display)
    call put_list('exclude', s%exclude)
    call put_list('exclude_dir', s%exclude_dir)
    call put_list('extensions', s%extensions)
    call put_list('extra_vartypes', s%extra_vartypes)
    call put_list('fixed_extensions', s%fixed_extensions)
    call put_list('fpp_extensions', s%fpp_extensions)
    call put_list('html_template_dir', s%html_template_dir)
    call put_list('include', s%include)
    call put_list('macro', s%macro)
    call put_list('md_extensions', s%md_extensions)
    call put_list('src_dir', s%src_dir)
    call put_map('alias', s%alias)
    call put_map('external', s%external)
    call put_map('extra_mods', s%extra_mods)
    call json%create_object(group, 'extra_filetypes')
    call json%add(root, group)
    do k = 1, size(s%extra_filetypes)
        call json%add(group, s%extra_filetypes(k)%extension, s%extra_filetypes(k)%comment)
    end do
    call json%serialize(root, text)
    call json%destroy(root)

contains

    !> Add a text option, `null` if it is not set.
    subroutine put_text(key, value)
        character(len=*), intent(in) :: key                 !! the option
        character(len=:), allocatable, intent(in) :: value  !! its value
        if (allocated(value)) then
            call json%add(root, key, value)
        else
            call json%add(root, key)
        end if
    end subroutine put_text

    !> Add a logical option.
    subroutine put_logical(key, value)
        character(len=*), intent(in) :: key     !! the option
        logical, intent(in) :: value            !! its value
        call json%add(root, key, value)
    end subroutine put_logical

    !> Add a list option.
    subroutine put_list(key, list)
        character(len=*), intent(in) :: key     !! the option
        type(string_t), intent(in) :: list(:)   !! its values
        type(json_value), pointer :: array
        integer :: j
        call json%create_array(array, key)
        call json%add(root, array)
        do j = 1, size(list)
            call json%add(array, '', list(j)%s)
        end do
    end subroutine put_list

    !> Add a map option.
    subroutine put_map(key, map)
        character(len=*), intent(in) :: key         !! the option
        type(map_entry_t), intent(in) :: map(:)     !! its entries
        type(json_value), pointer :: object
        integer :: j
        call json%create_object(object, key)
        call json%add(root, object)
        do j = 1, size(map)
            call json%add(object, map(j)%key, map(j)%value)
        end do
    end subroutine put_map

end function dump_settings

end module fdoc_settings

Graphs

Files this one depends on

Files that depend on this one