fortplot_doc_utils.f90 Source File


Source Code

module fortplot_doc_utils
    use fortplot_directory_listing, only: list_directory_entries
    implicit none
    private

    public :: build_file_path
    public :: get_file_extension
    public :: replace_extension
    public :: title_case
    public :: lowercase_string
    public :: file_exists
    public :: check_file_exists
    public :: build_readme_path
    public :: build_output_path
    public :: build_fortran_url
    public :: build_local_fortran_path
    public :: get_output_title
    public :: get_fortran_filename
    public :: get_example_run_target

contains

    pure subroutine build_file_path(dir, filename, full_path)
        character(len=*), intent(in) :: dir, filename
        character(len=*), intent(out) :: full_path

        full_path = trim(adjustl(dir)) // '/' // trim(adjustl(filename))
    end subroutine build_file_path

    pure function get_file_extension(filename) result(extension)
        character(len=*), intent(in) :: filename
        character(len=:), allocatable :: extension
        integer :: dot_pos

        dot_pos = index(filename, '.', back=.true.)
        if (dot_pos > 0) then
            extension = filename(dot_pos+1:)
        else
            extension = ''
        end if
    end function get_file_extension

    pure function replace_extension(filename, new_ext) result(new_filename)
        character(len=*), intent(in) :: filename, new_ext
        character(len=:), allocatable :: new_filename
        integer :: dot_pos

        dot_pos = index(filename, '.', back=.true.)
        if (dot_pos > 0) then
            new_filename = filename(1:dot_pos) // trim(new_ext)
        else
            new_filename = trim(filename) // '.' // trim(new_ext)
        end if
    end function replace_extension

    pure function title_case(value) result(output)
        character(len=*), intent(in) :: value
        character(len=:), allocatable :: output
        character(len=1) :: ch
        integer :: i
        logical :: new_word

        output = ''
        new_word = .true.
        do i = 1, len_trim(value)
            ch = value(i:i)
            select case (ch)
            case ('_', '-', ' ')
                if (len(output) > 0) then
                    if (output(len(output):len(output)) /= ' ') then
                        output = output // ' '
                    end if
                end if
                new_word = .true.
            case default
                if (new_word) then
                    output = output // char_upper(ch)
                    new_word = .false.
                else if (len(output) > 0) then
                    if (is_digit(output(len(output):len(output))) .and. &
                        is_alpha(ch)) then
                        output = output // char_upper(ch)
                    else
                        output = output // char_lower(ch)
                    end if
                else
                    output = output // char_lower(ch)
                end if
            end select
        end do

        if (len(output) > 0) then
            if (output(len(output):len(output)) == ' ') then
                output = output(1:len(output)-1)
            end if
        end if
    end function title_case

    pure function lowercase_string(value) result(output)
        character(len=*), intent(in) :: value
        character(len=:), allocatable :: output
        integer :: i

        output = value
        do i = 1, len(output)
            output(i:i) = char_lower(output(i:i))
        end do
    end function lowercase_string

    logical function file_exists(path)
        character(len=*), intent(in) :: path

        inquire(file=trim(path), exist=file_exists)
    end function file_exists

    logical function check_file_exists(dir, filename)
        character(len=*), intent(in) :: dir, filename
        character(len=:), allocatable :: full_path

        full_path = trim(adjustl(dir)) // '/' // trim(adjustl(filename))
        check_file_exists = file_exists(full_path)
    end function check_file_exists

    pure function char_lower(ch) result(out)
        character(len=1), intent(in) :: ch
        character(len=1) :: out
        integer :: code

        code = iachar(ch)
        if (code >= iachar('A') .and. code <= iachar('Z')) then
            out = achar(code + 32)
        else
            out = ch
        end if
    end function char_lower

    pure function char_upper(ch) result(out)
        character(len=1), intent(in) :: ch
        character(len=1) :: out
        integer :: code

        code = iachar(ch)
        if (code >= iachar('a') .and. code <= iachar('z')) then
            out = achar(code - 32)
        else
            out = ch
        end if
    end function char_upper

    pure logical function is_digit(ch)
        character(len=1), intent(in) :: ch
        integer :: code

        code = iachar(ch)
        is_digit = code >= iachar('0') .and. code <= iachar('9')
    end function is_digit

    pure logical function is_alpha(ch)
        character(len=1), intent(in) :: ch
        integer :: code

        code = iachar(char_lower(ch))
        is_alpha = code >= iachar('a') .and. code <= iachar('z')
    end function is_alpha

    subroutine build_readme_path(base_dir, readme_path)
        character(len=*), intent(in) :: base_dir
        character(len=*), intent(out) :: readme_path

        readme_path = trim(adjustl(base_dir)) // '/README.md'
    end subroutine build_readme_path

    subroutine build_output_path(base_dir, output_path)
        character(len=*), intent(in) :: base_dir
        character(len=*), intent(out) :: output_path

        output_path = 'doc/examples/' // trim(adjustl(base_dir)) // '.md'
    end subroutine build_output_path

    subroutine build_fortran_url(example_name, fortran_url)
        character(len=*), intent(in) :: example_name
        character(len=*), intent(out) :: fortran_url
        character(len=256) :: fortran_filename

        fortran_url = &
            'https://github.com/lazy-fortran/fortplot/blob/main/example/fortran/' // &
            trim(adjustl(example_name)) // '/'
        call get_fortran_filename(example_name, fortran_filename)
        fortran_url = trim(fortran_url) // trim(fortran_filename)
    end subroutine build_fortran_url

    subroutine build_local_fortran_path(example_name, fortran_path)
        character(len=*), intent(in) :: example_name
        character(len=*), intent(out) :: fortran_path
        character(len=256) :: fortran_filename

        call get_fortran_filename(example_name, fortran_filename)
        fortran_path = 'example/fortran/' // trim(adjustl(example_name)) // &
                       '/' // trim(fortran_filename)
    end subroutine build_local_fortran_path

    function get_output_title(example_name) result(title)
        character(len=*), intent(in) :: example_name
        character(len=:), allocatable :: title

        title = title_case(example_name)
    end function get_output_title

    subroutine get_fortran_filename(example_name, fortran_filename)
        character(len=*), intent(in) :: example_name
        character(len=*), intent(out) :: fortran_filename

        call resolve_fortran_filename(example_name, fortran_filename)
    end subroutine get_fortran_filename

    subroutine get_example_run_target(example_name, run_target)
        character(len=*), intent(in) :: example_name
        character(len=*), intent(out) :: run_target
        character(len=256) :: fortran_filename
        integer :: dot_pos

        call get_fortran_filename(example_name, fortran_filename)
        dot_pos = index(trim(fortran_filename), '.', back=.true.)
        if (dot_pos > 1) then
            run_target = fortran_filename(1:dot_pos - 1)
        else
            run_target = trim(fortran_filename)
        end if
    end subroutine get_example_run_target

    subroutine resolve_fortran_filename(example_name, fortran_filename)
        character(len=*), intent(in) :: example_name
        character(len=*), intent(out) :: fortran_filename
        character(len=256) :: entries(64)
        character(len=256) :: candidate
        character(len=512) :: example_dir, path
        integer :: count, status, i, matches

        example_dir = 'example/fortran/' // trim(adjustl(example_name))

        candidate = trim(adjustl(example_name)) // '.f90'
        path = trim(example_dir) // '/' // trim(candidate)
        if (file_exists(path)) then
            fortran_filename = trim(candidate)
            return
        end if

        candidate = 'example.f90'
        path = trim(example_dir) // '/' // trim(candidate)
        if (file_exists(path)) then
            fortran_filename = trim(candidate)
            return
        end if

        call list_directory_entries(trim(example_dir), entries, count, status)
        matches = 0
        candidate = ''
        if (status == 0) then
            do i = 1, count
                if (trim(get_file_extension(trim(entries(i)))) /= 'f90') cycle
                matches = matches + 1
                candidate = trim(entries(i))
            end do
        end if

        if (matches == 1) then
            fortran_filename = trim(candidate)
        else
            fortran_filename = trim(adjustl(example_name)) // '.f90'
        end if
    end subroutine resolve_fortran_filename

end module fortplot_doc_utils