55
66import click
77from prompt_toolkit import PromptSession
8+ from prompt_toolkit .completion import NestedCompleter
89from prompt_toolkit .history import FileHistory
910from rich .console import Console
1011from rich .text import Text
1112
12- from quantflow .data .fmp import FMP
13- from quantflow .data .fred import Fred
1413from quantflow .data .vault import Vault
1514
1615from . import settings
17- from .commands import fred , stocks , vault
18-
19-
20- @click .group ()
21- def qf () -> None :
22- pass
23-
24-
25- @qf .command ()
26- def exit () -> None :
27- """Exit the program"""
28- raise click .Abort ()
29-
30-
31- qf .add_command (vault .vault )
32- qf .add_command (stocks .stocks )
33- qf .add_command (fred .fred )
16+ from .commands import quantflow
17+ from .commands .base import QuantGroup
3418
3519
3620@dataclass
3721class QfApp :
3822 console : Console = field (default_factory = Console )
3923 vault : Vault = field (default_factory = partial (Vault , settings .VAULT_FILE_PATH ))
24+ sections : list [QuantGroup ] = field (default_factory = lambda : [quantflow ])
4025
4126 def __call__ (self ) -> None :
4227 os .makedirs (settings .SETTINGS_DIRECTORY , exist_ok = True )
@@ -49,14 +34,33 @@ def __call__(self) -> None:
4934 try :
5035 while True :
5136 try :
52- text = session .prompt ("quantflow> " )
37+ text = session .prompt (
38+ self .prompt_message (),
39+ completer = self .prompt_completer (),
40+ complete_while_typing = True ,
41+ )
5342 except KeyboardInterrupt :
5443 break
5544 else :
5645 self .handle_command (text )
5746 except click .Abort :
5847 self .console .print (Text ("Bye!" , style = "bold magenta" ))
5948
49+ def prompt_message (self ) -> str :
50+ name = ":" .join ([str (section .name ) for section in self .sections ])
51+ return f"{ name } > "
52+
53+ def prompt_completer (self ) -> NestedCompleter :
54+ return NestedCompleter .from_nested_dict (
55+ {command : None for command in self .sections [- 1 ].commands }
56+ )
57+
58+ def set_section (self , section : QuantGroup ) -> None :
59+ self .sections .append (section )
60+
61+ def back (self ) -> None :
62+ self .sections .pop ()
63+
6064 def print (self , text_alike : Any , style : str = "" ) -> None :
6165 if isinstance (text_alike , str ):
6266 style = style or "cyan"
@@ -67,29 +71,14 @@ def error(self, err: str | Exception) -> None:
6771 self .console .print (Text (f"\n { err } \n " , style = "bold red" ))
6872
6973 def handle_command (self , text : str ) -> None :
70- self .current_command = text .split (" " )[0 ].strip ()
7174 if not text :
7275 return
73- elif text == "help" :
74- return qf .main (["--help" ], standalone_mode = False , obj = self )
75-
76+ command = self .sections [- 1 ]
7677 try :
77- qf .main (text .split (), standalone_mode = False , obj = self )
78- except click .exceptions .MissingParameter as e :
79- self .error (e )
80- except click .exceptions .NoSuchOption as e :
78+ command .main (text .split (), standalone_mode = False , obj = self )
79+ except (
80+ click .exceptions .MissingParameter ,
81+ click .exceptions .NoSuchOption ,
82+ click .exceptions .UsageError ,
83+ ) as e :
8184 self .error (e )
82- except click .exceptions .UsageError as e :
83- self .error (e )
84-
85- def fmp (self ) -> FMP :
86- if key := self .vault .get ("fmp" ):
87- return FMP (key = key )
88- else :
89- raise click .UsageError ("No FMP API key found" )
90-
91- def fred (self ) -> Fred :
92- if key := self .vault .get ("fred" ):
93- return Fred (key = key )
94- else :
95- raise click .UsageError ("No FRED API key found" )
0 commit comments