Coverage for src\report\report.py: 86%
114 statements
« prev ^ index » next coverage.py v7.0.5, created at 2023-01-18 14:51 +0200
« prev ^ index » next coverage.py v7.0.5, created at 2023-01-18 14:51 +0200
1"""Command-line interface for report of Monaco race.
3Class:
4 OrderedNamespace
5 Cli
6"""
7import os
8import sys
9import argparse
10from dataclasses import dataclass, field
11from typing import Any
13from tabulate import tabulate
15import report.constant as c
16from report.race import Race
19class OrderedNamespace(argparse.Namespace):
20 """Namespace with ordered arguments.
22 Adds dictionary which saves order of input arguments in cli.
23 """
25 def __init__(self, **kwargs):
26 """Initialize with new attribute
28 Args:
29 kwargs: arguments in parser
30 """
31 # _order will save input arguments in given order
32 self.__dict__["_order"] = [None]
33 super().__init__(**kwargs)
35 def __setattr__(self, attr: str, value: Any):
36 """Set attributes.
38 _order will contain only attribute names, that was
39 provided in cli in preserved order.
41 Args:
42 attr: Attributes in Namespace.
43 value: Values of attributes in Namespace.
44 """
45 super().__setattr__(attr, value)
46 if attr in self._order:
47 self.__dict__["_order"].clear()
48 self.__dict__["_order"].append(attr)
50 def ordered(self) -> list[tuple]:
51 """Generate values for attributes in _order.
53 Returns:
54 Return _order pairs of attributes and values.
55 """
56 return [(attr, getattr(self, attr)) for attr in self._order
57 if attr != "files"]
59 def only_file_path(self):
60 """Checks argument quantity.
62 Checks if any argument was given beside files path argument.
64 Raises:
65 UserWarning: If only files path was given as argument.
66 """
67 if len(self.__dict__["_order"]) == 1:
68 raise UserWarning("Application should use at least one argument (except 'files' argument!)")
71@dataclass
72class Cli:
73 _print_func: dict = field(init=False, default_factory=dict)
74 args: argparse.Namespace = field(init=False)
75 race: Race = field(init=False)
77 def __post_init__(self):
78 """Initialize print_function variable"""
79 # Save all print functions in dictionary in order to make execution simpler.
80 self._print_func = {"report": self.print_report,
81 "time": self.print_time,
82 "date": self.print_date_of_the_race,
83 "racers_number": self.print_number_of_racers,
84 "driver": self.print_driver,
85 "racers": self.print_racers,
86 "teams": self.print_teams,
87 "team_racers": self.print_racers_in_team}
89 @staticmethod
90 def get_path_to_files(path: str) -> dict[str: str]:
91 """Get path to the log files.
93 Args:
94 path: Path of the folder where log files are located.
96 Returns:
97 Dictionary with file name as key and path to the file as value.
99 Raises:
100 UserWarning: if not all files where found in the folder.
101 NotADirectoryError: if path argument is not a dictionary.
102 FileNotFoundError: if path does not exist.
103 """
104 # Get path of the log files from directory if file name is in lookup list
105 file_path = {os.path.splitext(f)[0]: os.path.join(path, f)
106 for f in os.listdir(path)
107 if f.lower() in c.REPORT_FILES}
108 # Check if dictionary has all necessary files.
109 if len(file_path) != len(c.REPORT_FILES):
110 raise UserWarning("File missing. Make sure that folder contains this files:"
111 f" {c.REPORT_FILES}")
112 return file_path
114 def print_date_of_the_race(self):
115 """Print date of the race."""
116 print("\nDate of the race:")
117 print(f"\t{self.race.get_date_of_race()}")
119 def print_time(self):
120 """Print start time of the 1st qualification."""
121 print("\n1st qualification round started at:")
122 print(f"\t{self.race.get_start_time_of_q1()}")
124 def print_number_of_racers(self):
125 """Print number of racers in the race."""
126 print("\nNumber of the racers in the race:")
127 print(f"\t{self.race.get_number_of_racers()}")
129 def print_report(self):
130 """Print race report.
132 Prints race report in nice table look.
133 """
134 # Build report of the race.
135 race_report = self.race.get_report()
136 racers = []
137 print("\nReport of the race:")
138 # Loop through report sorted by lap time.
139 for num, racer in enumerate(race_report):
140 # Convert time lap to appropriate string format.
141 racer[2] = str(racer[2])[3: -3]
142 # Insert racer's place.
143 racer.insert(0, str(num + 1))
144 racers.append(racer)
145 # Separate first 15 places.
146 if num + 1 == 15:
147 # Calculate separation line length for each column.
148 team_sep = "-" * len(max(self.race.get_teams(), key=len))
149 racer_sep = "-" * len(max(self.race.get_names_of_racers(), key=len))
150 time_sep = "-" * len(racer[3])
151 place_sep = "--"
152 # Add separation line.
153 racers.append([place_sep, racer_sep, team_sep, time_sep])
154 print(tabulate(racers,
155 headers=["", "Full Name", "Team", "Q1 Time"],
156 tablefmt=c.TABLE_FORMAT))
158 def print_driver(self, name: str):
159 """Print driver information.
161 Args:
162 name: Driver full name.
164 Raises:
165 UserWarning: if no racer found with given name.
166 """
167 try:
168 driver = self.race.get_racer(name)
169 print(f"\nInformation about {driver[1]}:")
170 print(tabulate([driver],
171 headers=["Abbr", "Full Name", "Team", "Q1 Time"],
172 tablefmt=c.TABLE_FORMAT))
173 except UserWarning as err:
174 print(err)
176 def print_racers(self, order: str):
177 """Print list of racers in the race.
179 Args:
180 order: defines order of the result list.
181 """
182 racers = [[racer] for racer in
183 self.race.get_names_of_racers(reverse=c.ORDER[order])]
184 print(tabulate(racers,
185 headers=["List of racers:"],
186 tablefmt=c.TABLE_FORMAT))
188 def print_teams(self, order: str):
189 """Print list of teams in the race.
191 Args:
192 order: Flag for racers order.
193 """
194 teams = [[team] for team in self.race.get_teams(reverse=c.ORDER[order])]
195 print(tabulate(teams,
196 headers=["List of teams:"],
197 tablefmt=c.TABLE_FORMAT))
199 def print_racers_in_team(self, team: str):
200 """Print racers in specific team.
202 Args:
203 team: Name of the team.
204 """
205 try:
206 racers = self.race.get_racers_in_team(team)
207 print(f"\nRacers from {team.upper()} team:")
208 print(tabulate(racers,
209 headers=["Abbr", "Full Name", "Team", "Q1 Time"],
210 tablefmt=c.TABLE_FORMAT, ))
211 except UserWarning as err:
212 print(err)
214 def create_race(self, file_path: dict[str: str]):
215 """Create Race instance
217 Args:
218 file_path: path to the files.
219 """
220 self.race = Race(log_files=file_path)
222 def cli(self):
223 """Implement command-line interface
225 Uses argparse to get input from command-line
226 and print according result.
227 """
228 parser = argparse.ArgumentParser(
229 prog="Report of Monaco 2018 racing.",
230 description="Get information about the race from the log files."
231 )
232 parser.add_argument(
233 "-f", "--files", required=True,
234 help="directory path where the race log files are located (required argument)"
235 )
236 parser.add_argument(
237 "-r", "--report", action="store_true",
238 help="report of the 1st qualification results"
239 )
240 parser.add_argument(
241 "-d", "--date", action="store_true",
242 help="date of the 1st qualification"
243 )
244 parser.add_argument(
245 "-t", "--time", action="store_true",
246 help="start time of the 1st qualifications"
247 )
248 parser.add_argument(
249 "--racers-number", action="store_true",
250 help="number of the racers in the race"
251 )
252 parser.add_argument(
253 "--driver",
254 help="information about specific driver"
255 )
256 parser.add_argument(
257 "--racers",
258 const=list(c.ORDER.keys())[0],
259 nargs="?",
260 choices=c.ORDER.keys(),
261 help="""list of all racers in the race in asc or"
262 desc order (default is asc)"""
263 )
264 parser.add_argument(
265 "--teams",
266 const=list(c.ORDER.keys())[0],
267 nargs="?",
268 choices=c.ORDER.keys(),
269 help="""list of all teams in the race in asc "
270 or desc order (default is asc)"""
271 )
272 parser.add_argument(
273 "--team-racers",
274 help="list of all racers from specific team"
275 )
277 self.args = parser.parse_args(namespace=OrderedNamespace())
279 try:
280 # Check if only "files" argument was passed
281 self.args.only_file_path()
282 except UserWarning as err:
283 print(err)
284 parser.print_usage()
285 sys.exit()
287 try:
288 # Get log files from file directory
289 file_path = self.get_path_to_files(self.args.files)
290 except (FileNotFoundError, NotADirectoryError, UserWarning) as err:
291 sys.exit(err)
293 try:
294 # Create race instance.
295 self.create_race(file_path=file_path)
296 except (TypeError, FileNotFoundError, UserWarning) as err:
297 sys.exit(err)
299 # Go through passed arguments and execute according print functions.
300 for arg, value in self.args.ordered():
301 if arg in ["driver", "racers", "teams", "team_racers"]:
302 self._print_func[arg](value)
303 else:
304 self._print_func[arg]()
307if __name__ == "__main__":
308 cli = Cli()
309 cli.cli()