44from  __future__ import  annotations 
55
66from  abc  import  ABC , abstractmethod 
7+ from  contextlib  import  contextmanager 
78from  typing  import  TYPE_CHECKING , Any , NoReturn 
89
10+ from  msgspec  import  Struct 
11+ 
912from  dda .utils .process  import  EnvVars 
1013
1114if  TYPE_CHECKING :
15+     from  collections .abc  import  Generator 
1216    from  subprocess  import  CompletedProcess 
13-     from  types  import  TracebackType 
1417
1518    from  dda .cli .application  import  Application 
1619
1720
18- class  Tool ( ABC ):
21+ class  ExecutionContext ( Struct ,  frozen = True ):
1922    """ 
20-     Base class for all tools. A tool is an executable that may require special 
21-     handling to be executed properly. 
22- 
23-     This class supports being used as a context manager and is guaranteed to be entered at all times. 
23+     Configuration for an execution of a tool. 
2424    """ 
2525
26-     def   __init__ ( self ,  app :  Application )  ->   None : 
27-          self . __app   =   app 
26+     command :  list [ str ] 
27+     env_vars :  dict [ str ,  str ] 
2828
29-     @abstractmethod  
30-     def  format_command (self , command : list [str ]) ->  list [str ]:
31-         """ 
32-         Format a command to be executed by the tool. 
3329
34-         Parameters: 
35-             command: The command to format. 
30+ class  Tool (ABC ):
31+     """ 
32+     A tool is an external program that may require special handling to be executed properly. 
33+     """ 
3634
37-         Returns: 
38-             The formatted command. 
39-         """ 
35+     def  __init__ (self , app : Application ) ->  None :
36+         self .__app  =  app 
4037
4138    @property  
4239    def  app (self ) ->  Application :
@@ -45,18 +42,24 @@ def app(self) -> Application:
4542        """ 
4643        return  self .__app 
4744
48-     def  env_vars (self ) ->  dict [str , str ]:  # noqa: PLR6301 
45+     @contextmanager  
46+     @abstractmethod  
47+     def  execution_context (self , command : list [str ]) ->  Generator [ExecutionContext , None , None ]:
4948        """ 
50-         Returns: 
51-             The environment variables to set for the tool. 
49+         A context manager bound to the lifecycle of each tool execution. 
50+ 
51+         Parameters: 
52+             command: The command to execute. 
53+ 
54+         Yields: 
55+             The execution context. 
5256        """ 
53-         return  {}
5457
5558    def  run (self , command : list [str ], ** kwargs : Any ) ->  int :
5659        """ 
57-         Equivalent to [`SubprocessRunner.run`][dda.utils.process.SubprocessRunner.run] with the `command` formatted  
58-         by the tool's [`format_command `][dda.tools.base.Tool.format_command] method and  the environment variables set  
59-         by the tool's [`env_vars`][dda.tools.base.Tool.env_vars] method (if any) . 
60+         Equivalent to [`SubprocessRunner.run`][dda.utils.process.SubprocessRunner.run] with the tool's  
61+         [`execution_context `][dda.tools.base.Tool.execution_context] determining  the final command and  
62+         environment variables . 
6063
6164        Parameters: 
6265            command: The command to execute. 
@@ -65,15 +68,15 @@ def run(self, command: list[str], **kwargs: Any) -> int:
6568            **kwargs: Additional keyword arguments to pass to 
6669                [`SubprocessRunner.run`][dda.utils.process.SubprocessRunner.run]. 
6770        """ 
68-         with  self :
69-             self . __populate_env_vars (kwargs )
70-             return  self .app .subprocess .run (self . format_command ( command ) , ** kwargs )
71+         with  self . execution_context ( command )  as   context :
72+             _populate_env_vars (kwargs ,  context . env_vars )
73+             return  self .app .subprocess .run (context . command , ** kwargs )
7174
7275    def  capture (self , command : list [str ], ** kwargs : Any ) ->  str :
7376        """ 
74-         Equivalent to [`SubprocessRunner.capture`][dda.utils.process.SubprocessRunner.capture] with the `command`  
75-         formatted by the tool's [`format_command `][dda.tools.base.Tool.format_command] method and  the environment  
76-         variables set by the tool's [`env_vars`][dda.tools.base.Tool.env_vars] method (if any) . 
77+         Equivalent to [`SubprocessRunner.capture`][dda.utils.process.SubprocessRunner.capture] with the tool's  
78+         [`execution_context `][dda.tools.base.Tool.execution_context] determining  the final command and  
79+         environment variables . 
7780
7881        Parameters: 
7982            command: The command to execute. 
@@ -82,15 +85,15 @@ def capture(self, command: list[str], **kwargs: Any) -> str:
8285            **kwargs: Additional keyword arguments to pass to 
8386                [`SubprocessRunner.capture`][dda.utils.process.SubprocessRunner.capture]. 
8487        """ 
85-         with  self :
86-             self . __populate_env_vars (kwargs )
87-             return  self .app .subprocess .capture (self . format_command ( command ) , ** kwargs )
88+         with  self . execution_context ( command )  as   context :
89+             _populate_env_vars (kwargs ,  context . env_vars )
90+             return  self .app .subprocess .capture (context . command , ** kwargs )
8891
8992    def  wait (self , command : list [str ], ** kwargs : Any ) ->  None :
9093        """ 
91-         Equivalent to [`SubprocessRunner.wait`][dda.utils.process.SubprocessRunner.wait] with the `command` formatted  
92-         by the tool's [`format_command `][dda.tools.base.Tool.format_command] method and  the environment variables set  
93-         by the tool's [`env_vars`][dda.tools.base.Tool.env_vars] method (if any) . 
94+         Equivalent to [`SubprocessRunner.wait`][dda.utils.process.SubprocessRunner.wait] with the tool's  
95+         [`execution_context `][dda.tools.base.Tool.execution_context] determining  the final command and  
96+         environment variables . 
9497
9598        Parameters: 
9699            command: The command to execute. 
@@ -99,15 +102,15 @@ def wait(self, command: list[str], **kwargs: Any) -> None:
99102            **kwargs: Additional keyword arguments to pass to 
100103                [`SubprocessRunner.wait`][dda.utils.process.SubprocessRunner.wait]. 
101104        """ 
102-         with  self :
103-             self . __populate_env_vars (kwargs )
104-             self .app .subprocess .wait (self . format_command ( command ) , ** kwargs )
105+         with  self . execution_context ( command )  as   context :
106+             _populate_env_vars (kwargs ,  context . env_vars )
107+             self .app .subprocess .wait (context . command , ** kwargs )
105108
106109    def  exit_with (self , command : list [str ], ** kwargs : Any ) ->  NoReturn :
107110        """ 
108-         Equivalent to [`SubprocessRunner.exit_with`][dda.utils.process.SubprocessRunner.exit_with] 
109-         with the `command` formatted by the tool's [`format_command `][dda.tools.base.Tool.format_command] method  and 
110-         the  environment variables set by the tool's [`env_vars`][dda.tools.base.Tool.env_vars] method (if any) . 
111+         Equivalent to [`SubprocessRunner.exit_with`][dda.utils.process.SubprocessRunner.exit_with] with the tool's  
112+         [`execution_context `][dda.tools.base.Tool.execution_context] determining the final command  and 
113+         environment variables. 
111114
112115        Parameters: 
113116            command: The command to execute. 
@@ -116,15 +119,15 @@ def exit_with(self, command: list[str], **kwargs: Any) -> NoReturn:
116119            **kwargs: Additional keyword arguments to pass to 
117120                [`SubprocessRunner.exit_with`][dda.utils.process.SubprocessRunner.exit_with]. 
118121        """ 
119-         with  self :
120-             self . __populate_env_vars (kwargs )
121-             self .app .subprocess .exit_with (self . format_command ( command ) , ** kwargs )
122+         with  self . execution_context ( command )  as   context :
123+             _populate_env_vars (kwargs ,  context . env_vars )
124+             self .app .subprocess .exit_with (context . command , ** kwargs )
122125
123126    def  attach (self , command : list [str ], ** kwargs : Any ) ->  CompletedProcess :
124127        """ 
125-         Equivalent to [`SubprocessRunner.attach`][dda.utils.process.SubprocessRunner.attach] with the `command`  
126-         formatted by the tool's [`format_command `][dda.tools.base.Tool.format_command] method and  the environment  
127-         variables set by the tool's [`env_vars`][dda.tools.base.Tool.env_vars] method (if any) . 
128+         Equivalent to [`SubprocessRunner.attach`][dda.utils.process.SubprocessRunner.attach] with the tool's  
129+         [`execution_context `][dda.tools.base.Tool.execution_context] determining  the final command and  
130+         environment variables . 
128131
129132        Parameters: 
130133            command: The command to execute. 
@@ -133,15 +136,15 @@ def attach(self, command: list[str], **kwargs: Any) -> CompletedProcess:
133136            **kwargs: Additional keyword arguments to pass to 
134137                [`SubprocessRunner.attach`][dda.utils.process.SubprocessRunner.attach]. 
135138        """ 
136-         with  self :
137-             self . __populate_env_vars (kwargs )
138-             return  self .app .subprocess .attach (self . format_command ( command ) , ** kwargs )
139+         with  self . execution_context ( command )  as   context :
140+             _populate_env_vars (kwargs ,  context . env_vars )
141+             return  self .app .subprocess .attach (context . command , ** kwargs )
139142
140143    def  redirect (self , command : list [str ], ** kwargs : Any ) ->  CompletedProcess :
141144        """ 
142-         Equivalent to [`SubprocessRunner.redirect`][dda.utils.process.SubprocessRunner.redirect] with the `command`  
143-         formatted by the tool's [`format_command `][dda.tools.base.Tool.format_command] method and  the environment  
144-         variables set by the tool's [`env_vars`][dda.tools.base.Tool.env_vars] method (if any) . 
145+         Equivalent to [`SubprocessRunner.redirect`][dda.utils.process.SubprocessRunner.redirect] with the tool's  
146+         [`execution_context `][dda.tools.base.Tool.execution_context] determining  the final command and  
147+         environment variables . 
145148
146149        Parameters: 
147150            command: The command to execute. 
@@ -150,23 +153,17 @@ def redirect(self, command: list[str], **kwargs: Any) -> CompletedProcess:
150153            **kwargs: Additional keyword arguments to pass to 
151154                [`SubprocessRunner.redirect`][dda.utils.process.SubprocessRunner.redirect]. 
152155        """ 
153-         with  self :
154-             self .__populate_env_vars (kwargs )
155-             return  self .app .subprocess .redirect (self .format_command (command ), ** kwargs )
156- 
157-     def  __populate_env_vars (self , kwargs : dict [str , Any ]) ->  None :
158-         env_vars  =  self .env_vars ()
159-         if  not  env_vars :
160-             return 
156+         with  self .execution_context (command ) as  context :
157+             _populate_env_vars (kwargs , context .env_vars )
158+             return  self .app .subprocess .redirect (context .command , ** kwargs )
161159
162-         if  isinstance (env  :=  kwargs .get ("env" ), dict ):
163-             for  key , value  in  env_vars .items ():
164-                 env .setdefault (key , value )
165-         else :
166-             kwargs ["env" ] =  EnvVars (env_vars )
167160
168-     def  __enter__ (self ) ->  None : ...
161+ def  _populate_env_vars (kwargs : dict [str , Any ], env_vars : dict [str , str ]) ->  None :
162+     if  not  env_vars :
163+         return 
169164
170-     def  __exit__ (
171-         self , exc_type : type [BaseException ] |  None , exc_value : BaseException  |  None , traceback : TracebackType  |  None 
172-     ) ->  None : ...
165+     if  isinstance (env  :=  kwargs .get ("env" ), dict ):
166+         for  key , value  in  env_vars .items ():
167+             env .setdefault (key , value )
168+     else :
169+         kwargs ["env" ] =  EnvVars (env_vars )
0 commit comments