Skip to content

Commit

Permalink
Merge pull request #571 from Fortran-FOSS-Programmers/css-fixups
Browse files Browse the repository at this point in the history
Fix some display issues for type bound procedures
  • Loading branch information
ZedThree authored Oct 13, 2023
2 parents 8b1cfd0 + 0389ea7 commit 710b744
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 53 deletions.
33 changes: 33 additions & 0 deletions example/src/ford_example_type.f90
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ module ford_example_type_mod
implicit none

private
public :: say_interface

type, abstract, public :: say_type_base
!! An abstract base type that has a deferred `say` function
contains
procedure(say_interface), deferred :: say
!! Deferred type-bound procedure with a procedure prototype
end type say_type_base

type, public :: example_type
!! Some type that can say hello.
Expand All @@ -26,6 +34,11 @@ module ford_example_type_mod
!! rendered on the [[example_type]] page.
!!
!! This binding has more documentation beyond the summary
procedure, private, nopass :: say_int
procedure, private, nopass :: say_real
generic :: say_number => say_int, say_real
!! This generic type-bound procedure has two bindings

final :: example_type_finalise
!! This is the finaliser, called when the instance is destroyed.
!!
Expand All @@ -40,6 +53,14 @@ module ford_example_type_mod
module procedure make_new_type
end interface example_type

interface
subroutine say_interface(self)
!! Abstract interface for 'say' functions
import say_type_base
class(say_type_base), intent(inout) :: self
end subroutine say_interface
end interface

contains

subroutine example_type_say(self)
Expand Down Expand Up @@ -69,4 +90,16 @@ subroutine example_type_finalise(self)
write(*, '(a, " says goodbye")') self%name
end subroutine example_type_finalise

subroutine say_int(int)
!! Prints an integer
integer, intent(in) :: int
write(*, '(i0)') int
end subroutine say_int

subroutine say_real(r)
!! Prints a real
real, intent(in) :: r
write(*, '(g0)') r
end subroutine say_real

end module ford_example_type_mod
72 changes: 51 additions & 21 deletions ford/sourceform.py
Original file line number Diff line number Diff line change
Expand Up @@ -2040,14 +2040,9 @@ def correlate(self, project):
# Get total num_lines, including implementations
for proc in self.finalprocs:
self.num_lines_all += proc.procedure.num_lines

for proc in self.boundprocs:
for bind in proc.bindings:
if isinstance(bind, FortranProcedure):
self.num_lines_all += bind.num_lines
elif isinstance(bind, FortranBoundProcedure):
for b in bind.bindings:
if isinstance(b, FortranProcedure):
self.num_lines_all += b.num_lines
self.num_lines_all += proc.num_lines

def prune(self):
"""
Expand Down Expand Up @@ -2375,14 +2370,18 @@ class FortranBoundProcedure(FortranBase):
def _initialize(self, line: re.Match) -> None:
self.attribs: List[str] = []
self.deferred = False
"""Is a deferred procedure"""
self.protomatch = False
"""Prototype has been matched to procedure"""

for attribute in ford.utils.paren_split(",", line["attributes"] or ""):
attribute = attribute.strip()
# Preserve original capitalisation -- TODO: needed?
attribute_lower = attribute.lower()
if attribute_lower in ["public", "private"]:
self.permission = attribute_lower
elif attribute_lower == "deferred":
attribute = attribute.strip().lower()
if not attribute:
continue

if attribute in ["public", "private"]:
self.permission = attribute
elif attribute == "deferred":
self.deferred = True
else:
self.attribs.append(attribute)
Expand All @@ -2393,17 +2392,48 @@ def _initialize(self, line: re.Match) -> None:
self.proto = line["prototype"]
if self.proto:
self.proto = self.proto[1:-1].strip()
self.bindings: List[Union[str, FortranProcedure, FortranBoundProcedure]] = []
if len(split) > 1:
binds = self.SPLIT_RE.split(split[1])
for bind in binds:
self.bindings.append(bind.strip())
else:
self.bindings.append(self.name)

binds = self.SPLIT_RE.split(split[1]) if len(split) > 1 else (self.name,)
self.bindings: List[Union[str, FortranProcedure, FortranBoundProcedure]] = [
bind.strip() for bind in binds
]

@property
def binding_type(self) -> str:
"""String representation of binding type"""
if self.generic:
return "generic"

if not self.proto:
return "procedure"

if self.protomatch and not self.proto.visible:
return f"procedure({self.proto.name})"

return f"procedure({self.proto})"

@property
def full_declaration(self) -> str:
"""String representation of the full declaration line"""

attribute_parts = copy.copy(self.attribs)
attribute_parts.insert(0, self.permission)
if self.deferred:
attribute_parts.insert(1, "deferred")
attributes = ", ".join(attribute_parts)
return f"{self.binding_type}, {attributes}"

@property
def num_lines(self) -> int:
result = 0
for binding in self.bindings:
if isinstance(binding, FortranProcedure):
result += binding.num_lines
return result

def correlate(self, project):
self.all_procs = self.parent.all_procs
self.protomatch = False

if self.proto:
proto_lower = self.proto.lower()
if proto_lower in self.all_procs:
Expand Down
10 changes: 5 additions & 5 deletions ford/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,11 @@
{% endif %}
</ul>
{% if search %}
<form action="{{ project_url }}/search.html" class="navbar-form navbar-right" role="search">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search" name="q" id="tipue_search_input" autocomplete="off" required>
</div>
</form>
<div class="d-flex align-items-end flex-grow-1">
<form action="{{ project_url }}/search.html" role="search" class="ms-auto">
<input type="text" class="form-control" aria-label="Search" placeholder="Search" name="q" id="tipue_search_input" autocomplete="off" required>
</form>
</div>
{% endif %}
</div><!--/.nav-collapse -->
</div>
Expand Down
48 changes: 23 additions & 25 deletions ford/templates/macros.html
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,8 @@ <h3>Contents</h3>
{% if proc.parent and proc.visible %}
{{ meta_list(proc.meta) }}
{% endif %}
{{ proc.doc }}

{{ docstring(proc, False) }}

{{ header(4, small) }}Arguments{{ close_header(4, small) }}
{% if proc.args|length %}
Expand All @@ -320,15 +321,29 @@ <h3>Contents</h3>
{% endif %}
{% endmacro %}


{% macro bound_declaration(tb, link_name=False) %}
{# Type-bound procedure declaration and bindings #}
{{ tb.full_declaration }} ::
<strong>{% if link_name %}{{ tb }}{% else %}{{ tb.name }}{% endif %}</strong>
{%- if tb.generic or (tb.name != tb.bindings[0].name and tb.name != tb.bindings[0]) %} => {{ tb.bindings | join(", ") }}{% endif %}
{% if tb.binding|length == 1 %}<small>{{ tb.bindings[0].proctype }}</small>{% endif %}
{% endmacro %}


{% macro bound_info(tb) %}
<div class="card">
<div class="card-header codesum"><span class="anchor" id="{{ tb.anchor }}"></span><h3>{% if tb.generic -%}generic,{% else %}procedure{% if tb.proto -%}({% if not tb.protomatch or tb.proto.visible -%}{{ tb.proto }}{% else %}{{ tb.proto.name }}{%- endif %}){%- endif %},{%- endif %} {{ tb.permission }}{% if tb.deferred -%}, deferred{%- endif %}{% if tb.attribs -%}, {% for attrib in tb.attribs -%}{{ attrib }}{% if not loop.last -%}, {% endif %}{%- endfor %}{%- endif %} :: <strong>{{ tb.name }}</strong> {% if tb.generic or (tb.name != tb.bindings[0].name and tb.name != tb.bindings[0]) %} => {% for bind in tb.bindings -%}{% if not bind.parent or bind.visible %}{{ bind }}{% else %}{{ bind.name }}{% endif %}{% if not loop.last -%}, {% endif %}{%- endfor %}{% endif %} {% if tb.binding|length == 1 %}<small>{{ tb.bindings[0].proctype }}</small>{% endif %}
<div class="card mt-3">
<div class="card-header codesum">
<span class="anchor" id="{{ tb.anchor }}"></span>
<h3>
{{ bound_declaration(tb) }}
{{ deprecated(tb) }}
</h3></div>
</h3>
</div>
{% if tb.doc or meta_list(tb.meta)|trim|length is more_than_one %}
<div class="card-body">
{{ meta_list(tb.meta) }}
{{ tb.doc }}
{{ docstring(tb) }}
</div>
{% endif %}
<ul class="list-group">
Expand Down Expand Up @@ -460,11 +475,11 @@ <h3>final :: <strong>{{ proc.name }}</strong> {{ deprecated(proc) }}</h3>
</div>
<div class="card-body">
{{ meta_list(proc.meta) }}
{{ proc.doc }}
{{ docstring(proc) }}
</div>
<ul class="list-group">
<li class="list-group-item">
{{ proc_summary(proc.procedure, title=True, full_docstring=True) }}
{{ proc_summary(proc.procedure, title=True, full_docstring=False) }}
</li>
</ul>
</div>
Expand Down Expand Up @@ -552,24 +567,7 @@ <h4>Type-Bound Procedures</h4>
<tbody>
{% for tb in dtype.boundprocs %}
<tr>
<td>
{% if tb.generic -%}
generic,
{% else %}
procedure
{% if tb.proto -%}
({% if not tb.protomatch -%}{{ tb.proto }}{% else %}{{ tb.proto.name }}{%- endif %})
{%- endif %}
,
{%- endif %}
{{ tb.permission }}
{% if tb.attribs -%}, {{ tb.attribs|join(", ") }}{%- endif %}
:: <strong>{{ tb }}</strong>
{% if tb.generic or (tb.name != tb.bindings[0].name and tb.name != tb.bindings[0]) %} =>
{{ tb.bindings|join(", ") }}
{% endif %}
{% if tb.bindings|length == 1 %}<small>{{ tb.bindings[0].proctype }}</small>{% endif %}
</td>
<td>{{ bound_declaration(tb, link_name=True) }}</td>
<td>{{ tb | meta('summary') }}</td>
</tr>
{% endfor %}
Expand Down
4 changes: 3 additions & 1 deletion ford/templates/type_page.html
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ <h2>Finalization Procedures</h2>
<section>
<h2>Type-Bound Procedures</h2>
{% for bp in dtype.boundprocs %}
{{ macros.bound_info(bp) }}
<div class="row">
{{ macros.bound_info(bp) }}
</div>
{% endfor %}
</section>
{% endif %}
Expand Down
2 changes: 1 addition & 1 deletion test/test_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ def test_types_links(example_index):
types_box = index.find(ANY_TEXT, string="Derived Types").parent
types_list = sorted([f.text for f in types_box("li")])

assert types_list == sorted(["bar", "foo", "example_type"])
assert types_list == sorted(["bar", "foo", "example_type", "say_type_base"])


def test_types_type_bound_procedure(example_project):
Expand Down
91 changes: 91 additions & 0 deletions test/test_sourceform.py
Original file line number Diff line number Diff line change
Expand Up @@ -2131,3 +2131,94 @@ def test_generic_source(tmp_path):
]

assert source.doc_list == expected_docs


def test_type_bound_procedure_formatting(parse_fortran_file):
data = """\
module ford_example_type_mod
type, abstract, public :: example_type
contains
procedure :: say_hello => example_type_say
procedure, private, nopass :: say_int
procedure, private, nopass :: say_real
generic :: say_number => say_int, say_real
procedure(say_interface), deferred :: say
final :: example_type_finalise
end type example_type
interface
subroutine say_interface(self)
import say_type_base
class(say_type_base), intent(inout) :: self
end subroutine say_interface
end interface
contains
subroutine example_type_say(self)
class(example_type), intent(inout) :: self
end subroutine example_type_say
subroutine example_type_finalise(self)
type(example_type), intent(inout) :: self
end subroutine example_type_finalise
subroutine say_int(int)
integer, intent(in) :: int
end subroutine say_int
subroutine say_real(r)
real, intent(in) :: r
end subroutine say_real
end module ford_example_type_mod
"""

example_type = parse_fortran_file(data).modules[0].types[0]

assert example_type.boundprocs[0].full_declaration == "procedure, public"
assert example_type.boundprocs[1].full_declaration == "procedure, private, nopass"
assert example_type.boundprocs[2].full_declaration == "procedure, private, nopass"
assert example_type.boundprocs[3].full_declaration == "generic, public"
assert (
example_type.boundprocs[4].full_declaration
== "procedure(say_interface), public, deferred"
)


def test_type_num_lines(parse_fortran_file):
data = """\
module ford_example_type_mod
type, abstract, public :: example_type
contains
procedure :: say_hello => example_type_say
procedure, private, nopass :: say_int
procedure, private, nopass :: say_real
generic :: say_number => say_int, say_real, say_hello
procedure(say_interface), deferred :: say
end type example_type
interface
subroutine say_interface(self)
import say_type_base
class(say_type_base), intent(inout) :: self
end subroutine say_interface
end interface
contains
subroutine example_type_say(self)
class(example_type), intent(inout) :: self
end subroutine example_type_say
subroutine say_int(int)
integer, intent(in) :: int
end subroutine say_int
subroutine say_real(r)
real, intent(in) :: r
end subroutine say_real
end module ford_example_type_mod
"""

project = FakeProject()
module = parse_fortran_file(data).modules[0]
module.correlate(project)

example_type = module.types[0]
assert example_type.num_lines == 8
assert example_type.num_lines_all == 8 + 9

0 comments on commit 710b744

Please sign in to comment.