11#!/usr/bin/env python3
2+ import time
3+
24import curses
35import signal
46import threading
5- import time
67import traceback
78from abc import ABC , abstractmethod
89from contextlib import contextmanager
910from enum import Enum
10- from typing import Dict , List , Optional
11-
1211from task_maker .config import Config
1312from task_maker .formats import Task
1413from task_maker .printer import StdoutPrinter , Printer , CursesPrinter
1514from task_maker .source_file import SourceFile
1615from task_maker .task_maker_frontend import Result , ResultStatus , Resources
16+ from typing import Dict , List , Optional
1717
1818
1919class SourceFileCompilationStatus (Enum ):
20+ """
21+ Status of the compilation of a source file
22+ - WAITING: the compilation has not started yet
23+ - COMPILING: the compilation has started
24+ - DONE: the compilation has successfully completed
25+ - FAILURE: the compilation failed
26+ """
2027 WAITING = 0
2128 COMPILING = 1
2229 DONE = 2
2330 FAILURE = 3
2431
2532
2633class SourceFileCompilationResult :
34+ """
35+ Result information about a compilation of a source file
36+ """
2737 def __init__ (self , need_compilation ):
2838 self .need_compilation = need_compilation
2939 self .status = SourceFileCompilationStatus .WAITING
@@ -32,11 +42,23 @@ def __init__(self, need_compilation):
3242
3343
3444class UIInterface :
45+ """
46+ This class is the binding between the frontend and the task format and the
47+ UIs. The format will register the solutions, the frontend will call its
48+ callbacks and the state is stored in this class (and in it's subclasses).
49+ The UI will use the data inside this.
50+ """
3551 def __init__ (self , task : Task , do_print : bool ):
52+ """
53+ :param task: The task this UIInterface is bound to
54+ :param do_print: Whether the logs should be printed to stdout (print
55+ interface)
56+ """
3657 self .task = task
3758 self .non_solutions = dict (
3859 ) # type: Dict[str, SourceFileCompilationResult]
3960 self .solutions = dict () # type: Dict[str, SourceFileCompilationResult]
61+ # all the running tasks: (name, monotonic timestamp of start)
4062 self .running = dict () # type: Dict[str, float]
4163 self .warnings = list () # type: List[str]
4264 self .errors = list () # type: List[str]
@@ -47,11 +69,16 @@ def __init__(self, task: Task, do_print: bool):
4769 self .printer = Printer ()
4870
4971 def add_non_solution (self , source_file : SourceFile ):
72+ """
73+ Add a non-solution file to the ui (ie a generator/checker/...)
74+ """
5075 name = source_file .name
5176 log_prefix = "Compilation of non-solution {} " .format (name ).ljust (50 )
5277 self .non_solutions [name ] = SourceFileCompilationResult (
5378 source_file .language .need_compilation )
5479 self .printer .text (log_prefix + "WAITING\n " )
80+
81+ # TODO: at some point extract those functionality into some wrapper
5582 if source_file .language .need_compilation :
5683
5784 def notifyStartCompiltion ():
@@ -88,6 +115,9 @@ def getStderr(stderr):
88115 self .non_solutions [name ].status = SourceFileCompilationStatus .DONE
89116
90117 def add_solution (self , source_file : SourceFile ):
118+ """
119+ Add a solution to the UI
120+ """
91121 name = source_file .name
92122 log_prefix = "Compilation of solution {} " .format (name ).ljust (50 )
93123 self .solutions [name ] = SourceFileCompilationResult (
@@ -130,18 +160,30 @@ def getStderr(stderr):
130160 self .solutions [name ].status = SourceFileCompilationStatus .DONE
131161
132162 def add_warning (self , message : str ):
163+ """
164+ Add a warning message to the list of warnings
165+ """
133166 self .warnings .append (message )
134167 self .printer .yellow ("WARNING " , bold = True )
135168 self .printer .text (message .strip () + "\n " )
136169
137170 def add_error (self , message : str ):
171+ """
172+ Add an error message to the list of errors, this wont stop anything
173+ """
138174 self .errors .append (message )
139175 self .printer .red ("ERROR " , bold = True )
140176 self .printer .text (message .strip () + "\n " )
141177
142178 @contextmanager
143179 def run_in_ui (self , curses_ui : Optional ["CursesUI" ],
144180 finish_ui : Optional ["FinishUI" ]):
181+ """
182+ Wrap a block in the UI's setup/teardown. A curses UI should be stopped
183+ before the program exists otherwise the terminal is messed up. This
184+ wrapper will start the UIs, yield and stop them after. At the end it
185+ will print with the finish ui
186+ """
145187 if curses_ui :
146188 curses_ui .start ()
147189 try :
@@ -159,6 +201,11 @@ def run_in_ui(self, curses_ui: Optional["CursesUI"],
159201
160202
161203class FinishUI (ABC ):
204+ """
205+ UI used to print the summary of the execution
206+ """
207+ # if the time / memory usage is greater of the limit * LIMITS_MARGIN that
208+ # time/memory is highlighted
162209 LIMITS_MARGIN = 0.8
163210
164211 def __init__ (self , config : Config , interface : Optional [UIInterface ]):
@@ -168,13 +215,22 @@ def __init__(self, config: Config, interface: Optional[UIInterface]):
168215
169216 @abstractmethod
170217 def print (self ):
218+ """
219+ Print the entire result summary
220+ """
171221 pass
172222
173223 @abstractmethod
174224 def print_summary (self ):
225+ """
226+ Print only the summary grid with the overview of the results
227+ """
175228 pass
176229
177230 def print_final_messages (self ):
231+ """
232+ Print the warning and error messages
233+ """
178234 if not self .interface :
179235 return
180236 if sorted (self .interface .warnings ):
@@ -267,34 +323,47 @@ def _print_exec_stat(self, time, memory, time_limit, memory_limit, name):
267323
268324
269325class CursesUI (ABC ):
326+ """
327+ Running interface using the curses library to look nice in the terminal
328+ """
329+ # limit the frame rate
270330 FPS = 30
271331
272332 def __init__ (self , config : Config , interface : UIInterface ):
273333 self .config = config
274334 self .interface = interface
335+ # the ui runs in a different thread
275336 self .thread = threading .Thread (
276337 target = curses .wrapper , args = (self ._wrapper , ))
277338 self .stopped = False
278339 self .errored = False
279340
280341 def start (self ):
342+ """
343+ Start the UI starting the thread and messing up the terminal
344+ """
281345 self .stopped = False
282346 self .thread .start ()
283347
284348 def stop (self ):
349+ """
350+ Stops the thread and wait for it's termination. This will fix the
351+ terminal closing curses
352+ """
285353 self .stopped = True
286354 self .thread .join ()
287355
288356 def _wrapper (self , stdscr ):
289357 try :
290358 curses .start_color ()
291359 curses .use_default_colors ()
292- for i in range (1 , curses .COLORS ):
293- curses .init_pair (i , i , - 1 )
360+ if hasattr (curses , "COLORS" ):
361+ for i in range (1 , curses .COLORS ):
362+ curses .init_pair (i , i , - 1 )
294363 curses .halfdelay (1 )
295364 pad = curses .newpad (10000 , 1000 )
296365 printer = CursesPrinter (pad )
297- loading_chars = "- \\ |/ "
366+ loading_chars = r"◐◓◑◒ "
298367 cur_loading_char = 0
299368 pos_x , pos_y = 0 , 0
300369 while not self .stopped :
@@ -333,6 +402,10 @@ def _wrapper(self, stdscr):
333402
334403 @abstractmethod
335404 def _loop (self , printer : CursesPrinter , loading : str ):
405+ """
406+ The UI should inherit from this class and implement this method to print
407+ to the screen
408+ """
336409 pass
337410
338411 def _print_running_tasks (self , printer : CursesPrinter ):
0 commit comments