22
33import inspect
44import os
5+ import re
56import textwrap
7+ from collections import defaultdict
8+ from collections import OrderedDict
69
710from bmipy .bmi import Bmi
811
12+ GROUPS = (
13+ ("initialize" , "initialize" ),
14+ ("update" , "(update|update_until)" ),
15+ ("finalize" , "finalize" ),
16+ ("info" , r"(get_component_name|\w+_var_names|\w+_item_count)" ),
17+ ("var" , r"get_var_\w+" ),
18+ ("time" , r"get_\w*time\w*" ),
19+ ("value" , r"(get|set)_value\w*" ),
20+ ("grid" , r"get_grid_\w+" ),
21+ )
22+
923
1024class Template :
1125 """Create template BMI implementations."""
1226
1327 def __init__ (self , name : str ):
1428 self ._name = name
15- self ._funcs = dict (inspect .getmembers (Bmi , inspect .isfunction ))
1629
17- def render (self ) -> str :
30+ funcs = dict (inspect .getmembers (Bmi , inspect .isfunction ))
31+
32+ names = sort_methods (frozenset (funcs ))
33+
34+ self ._funcs = OrderedDict (
35+ (name , funcs .pop (name )) for name in names
36+ ) | OrderedDict (sorted (funcs .items ()))
37+
38+ def render (self , with_docstring : bool = True ) -> str :
1839 """Render a module that defines a class implementing a Bmi."""
1940 prefix = f"""\
2041 from __future__ import annotations
@@ -30,13 +51,15 @@ def render(self) -> str:
3051class { self ._name } (Bmi):
3152"""
3253 return prefix + (os .linesep * 2 ).join (
33- [self ._render_func (name ) for name in sorted (self ._funcs )]
54+ [
55+ self ._render_func (name , with_docstring = with_docstring )
56+ for name in self ._funcs
57+ ]
3458 )
3559
36- def _render_func (self , name : str ) -> str :
60+ def _render_func (self , name : str , with_docstring : bool = True ) -> str :
3761 annotations = inspect .get_annotations (self ._funcs [name ])
3862 signature = inspect .signature (self ._funcs [name ], eval_str = False )
39-
4063 docstring = textwrap .indent (
4164 '"""' + dedent_docstring (self ._funcs [name ].__doc__ ) + '"""' , " "
4265 )
@@ -47,14 +70,32 @@ def _render_func(self, name: str) -> str:
4770 tuple (signature .parameters ),
4871 annotations ,
4972 width = 84 ,
50- ),
51- docstring ,
52- f" raise NotImplementedError({ name !r} )" .replace ("'" , '"' ),
73+ )
5374 ]
75+ parts .append (docstring ) if with_docstring else None
76+ parts .append (f" raise NotImplementedError({ name !r} )" .replace ("'" , '"' ))
5477
5578 return textwrap .indent (os .linesep .join (parts ), " " )
5679
5780
81+ def sort_methods (funcs : frozenset [str ]) -> list [str ]:
82+ """Sort methods by group type."""
83+ unmatched = set (funcs )
84+ matched = defaultdict (set )
85+
86+ for group , regex in GROUPS :
87+ pattern = re .compile (regex )
88+
89+ matched [group ] = {name for name in unmatched if pattern .match (name )}
90+ unmatched -= matched [group ]
91+
92+ ordered = []
93+ for group , _ in GROUPS :
94+ ordered .extend (sorted (matched [group ]))
95+
96+ return ordered + sorted (unmatched )
97+
98+
5899def dedent_docstring (text : str | None , tabsize : int = 4 ) -> str :
59100 """Dedent a docstring, ignoring indentation of the first line.
60101
0 commit comments