@@ -41,41 +41,206 @@ def execute(c, sql, suppress_errors=True, theme=theme_no_color):
4141
4242class SqliteInteractiveConsole (InteractiveConsole ):
4343 """A simple SQLite REPL."""
44+ PS1 = "sqlite> "
45+ PS2 = " ... "
46+ ruler = "="
47+ doc_header = "Documented commands (type .help <command>):"
48+ undoc_header = "Undocumented commands:"
4449
4550 def __init__ (self , connection , use_color = False ):
4651 super ().__init__ ()
4752 self ._con = connection
4853 self ._cur = connection .cursor ()
4954 self ._use_color = use_color
55+ self ._theme = get_theme (force_no_color = not use_color )
56+
57+ s = self ._theme .syntax
58+ sys .ps1 = f"{ s .prompt } { self .PS1 } { s .reset } "
59+ sys .ps2 = f"{ s .prompt } { self .PS2 } { s .reset } "
60+
61+ def do_version (self , _ ):
62+ """.version
63+
64+ Show version of the runtime SQLite library.
65+ """
66+ print (sqlite3 .sqlite_version )
67+
68+ def do_help (self , arg ):
69+ """.help [-all] [command]
70+
71+ Without argument, print the list of available commands.
72+ With a command name as argument, print help about that command.
73+ With more command names as arguments, only the first one is used.
74+ """
75+ if not arg :
76+ cmds = sorted (name [3 :] for name in dir (self .__class__ )
77+ if name .startswith ("do_" ))
78+ cmds_doc = []
79+ cmds_undoc = []
80+ for cmd in cmds :
81+ if getattr (self , f"do_{ cmd } " ).__doc__ :
82+ cmds_doc .append (cmd )
83+ else :
84+ cmds_undoc .append (cmd )
85+ self ._print_commands (self .doc_header , cmds_doc , 80 )
86+ self ._print_commands (self .undoc_header , cmds_undoc , 80 )
87+ else :
88+ arg = arg .split ()[0 ]
89+ if arg in ("-all" , "--all" ):
90+ names = sorted (name for name in dir (self .__class__ )
91+ if name .startswith ("do_" ))
92+ print (self ._help_message_from_method_names (names ))
93+ else :
94+ if (method := getattr (self , "do_" + arg , None )) is not None :
95+ print (self ._help_message_from_doc (method .__doc__ ))
96+ else :
97+ self ._error (f"No help for '{ arg } '" )
98+
99+ def do_quit (self , _ ):
100+ """.q(uit)
101+
102+ Exit this program.
103+ """
104+ sys .exit (0 )
105+
106+ do_q = do_quit
107+
108+ def _help_message_from_doc (self , doc ):
109+ # copied from Lib/pdb.py#L2544
110+ lines = [line .strip () for line in doc .rstrip ().splitlines ()]
111+ if not lines :
112+ return "No help message found."
113+ if "" in lines :
114+ usage_end = lines .index ("" )
115+ else :
116+ usage_end = 1
117+ formatted = []
118+ indent = " " * len (self .PS1 )
119+ for i , line in enumerate (lines ):
120+ if i == 0 :
121+ prefix = "Usage: "
122+ elif i < usage_end :
123+ prefix = " "
124+ else :
125+ prefix = ""
126+ formatted .append (indent + prefix + line )
127+ return "\n " .join (formatted )
128+
129+ def _help_message_from_method_names (self , names ):
130+ formatted = []
131+ indent = " " * len (self .PS1 )
132+ for name in names :
133+ if not (doc := getattr (self , name ).__doc__ ):
134+ formatted .append (f".{ name [3 :]} " )
135+ continue
136+ lines = [line .strip () for line in doc .rstrip ().splitlines ()]
137+ if "" in lines :
138+ usage_end = lines .index ("" )
139+ else :
140+ usage_end = 1
141+ for i , line in enumerate (lines ):
142+ # skip method aliases, e.g. do_q for do_quit
143+ if i == 0 and line in formatted :
144+ break
145+ elif i < usage_end :
146+ formatted .append (line )
147+ elif not line and i == usage_end :
148+ continue
149+ else :
150+ formatted .append (indent + line )
151+ return "\n " .join (formatted )
152+
153+ def _error (self , msg ):
154+ t = self ._theme .traceback
155+ self .write (f"{ t .message } { msg } { t .reset } \n " )
156+
157+ def _print_commands (self , header , cmds , maxcol ):
158+ # copied and modified from Lib/cmd.py#L351
159+ if cmds :
160+ print (header )
161+ if self .ruler :
162+ print (self .ruler * len (header ))
163+ self ._columnize (cmds , maxcol - 1 )
164+ print ()
165+
166+ def _columnize (self , strings , displaywidth = 80 ):
167+ """Display a list of strings as a compact set of columns.
168+
169+ Each column is only as wide as necessary.
170+ Columns are separated by two spaces (one was not legible enough).
171+ """
172+ # copied and modified from Lib/cmd.py#L359
173+ if not strings :
174+ print ("<empty>" )
175+ return
176+
177+ size = len (strings )
178+ if size == 1 :
179+ print (strings [0 ])
180+ return
181+ # Try every row count from 1 upwards
182+ for nrows in range (1 , size ):
183+ ncols = (size + nrows - 1 ) // nrows
184+ colwidths = []
185+ totwidth = - 2
186+ for col in range (ncols ):
187+ colwidth = 0
188+ for row in range (nrows ):
189+ i = row + nrows * col
190+ if i >= size :
191+ break
192+ x = strings [i ]
193+ colwidth = max (colwidth , len (x ))
194+ colwidths .append (colwidth )
195+ totwidth += colwidth + 2
196+ if totwidth > displaywidth :
197+ break
198+ if totwidth <= displaywidth :
199+ break
200+ else :
201+ nrows = size
202+ ncols = 1
203+ colwidths = [0 ]
204+ for row in range (nrows ):
205+ texts = []
206+ for col in range (ncols ):
207+ i = row + nrows * col
208+ if i >= size :
209+ x = ""
210+ else :
211+ x = strings [i ]
212+ texts .append (x )
213+ while texts and not texts [- 1 ]:
214+ del texts [- 1 ]
215+ for col in range (len (texts )):
216+ texts [col ] = texts [col ].ljust (colwidths [col ])
217+ print (" " .join (texts ))
50218
51219 def runsource (self , source , filename = "<input>" , symbol = "single" ):
52220 """Override runsource, the core of the InteractiveConsole REPL.
53221
54222 Return True if more input is needed; buffering is done automatically.
55223 Return False if input is a complete statement ready for execution.
56224 """
57- theme = get_theme (force_no_color = not self ._use_color )
58-
59225 if not source or source .isspace ():
60226 return False
61227 if source [0 ] == "." :
62- match source [1 :].strip ():
63- case "version" :
64- print (f"{ sqlite3 .sqlite_version } " )
65- case "help" :
66- print ("Enter SQL code and press enter." )
67- case "quit" :
68- sys .exit (0 )
69- case "" :
70- pass
71- case _ as unknown :
72- t = theme .traceback
228+ if line := source [1 :].strip ():
229+ try :
230+ cmd , arg = line .split (maxsplit = 1 )
231+ except ValueError :
232+ cmd , arg = line , None
233+ if (func := getattr (self , "do_" + cmd , None )) is not None :
234+ func (arg )
235+ else :
236+ t = self ._theme .traceback
73237 self .write (f'{ t .type } Error{ t .reset } :{ t .message } unknown'
74- f'command or invalid arguments: "{ unknown } ".\n { t .reset } ' )
238+ f' command or invalid arguments: "{ line } ".'
239+ f' Enter ".help" for help{ t .reset } \n ' )
75240 else :
76241 if not sqlite3 .complete_statement (source ):
77242 return True
78- execute (self ._cur , source , theme = theme )
243+ execute (self ._cur , source , theme = self . _theme )
79244 return False
80245
81246
@@ -124,10 +289,6 @@ def main(*args):
124289 """ ).strip ()
125290
126291 theme = get_theme ()
127- s = theme .syntax
128-
129- sys .ps1 = f"{ s .prompt } sqlite> { s .reset } "
130- sys .ps2 = f"{ s .prompt } ... { s .reset } "
131292
132293 con = sqlite3 .connect (args .filename , isolation_level = None )
133294 try :
0 commit comments