CLI application for Football Stats Generator

I'm trying to build a CLI that runs until the user decides to exit. The functionality of the CLI is to be able to explore a json-file by typing commands into the terminal while the program is running.

Right now commands are checked against if/else statements, but I would like to add so that commands are checked in the manner of the argparse module, but can't figure out how that is done while the program is running. Because I'm unable to figure that out, class methods are mapped to specific commands and executed when the command is specified without having to rely in if/else logic.

The biggest issue is not single commands as -v because they are easy to map to a function with dict. But commands that are -v LEAGUE where LEAGUE or -d LEAGUE SEASON TYPE is much harder to map without creating big dictionaries since the arguments can be vary. Any suggestion how that can be improved is highly appreciated!

import os
import sys

class CLIStats:

def __init__(self):
"""Displays the welcome screen and loads the season-params.json
which contains the avalible leagues and their seasons that
"""
self.description = self.description()
self.commands = self.commands()
self.leagues = {'EN_PR': ['2019/2020', '2018/2019', '2017/2018', '2016/2017'],
'EU_CL': ['2019/2020', '2018/2019', '2017/2018', '2016/2017'],
'ES_PL': ['2019/2020', '2018/2019', '2017/2018', '2016/2017']}

@staticmethod
"""Display's welcome message"""
os.system('clear')
print('\t','*'*60)
print("\t\t***  Welcome - Football Stats generator  ***")
print('\t','*'*60)

@staticmethod
def description():
"""Display's short description"""
print('Type "exit" to terminate shell')

@staticmethod
def commands():
"""Display's avalible commands and how to use them"""
commands = {'View Leagues': '-v',
'View League Seasons': '-v LEAGUE',
'Help' : '-h',}
for key, value in commands.items():
print("{: <25} {}".format(key, value))
print('\n')

def view_leagues(self):
"""Prints out leagues in self.leagues"""
for league in self.leagues.keys():
print("{: <10}".format(league), end="")
print('\n')

def view_seasons(self, league):
"""Prints seasons for a league in self.leagues"""
if league in self.leagues:
seasons = self.leagues[league]
print(league,'seasons:')
for season in seasons:
print("{: <20}".format(season), end="")
print('\n')
else:
print(league, 'is not avalible')
print('\n')

def view_league_args(self):
"""Creates a list with set of args that can be passed
by user to execute view_league()"""
args = []
for league in self.leagues.keys():
args.append('-v' + ' ' +  league)
return args

def main():
"""Runs the interface"""
interface = CLIStats()
cmd = {'-v': interface.view_leagues,
'exit': sys.exit,
'View Stats Type': '-s',
'-h' : 'interface.help', }
while True:
usr_command = input('CLIStats\$ ')
if usr_command in cmd.keys():
cmd.get(usr_command)()

elif usr_command in interface.view_league_args():
league = usr_command.split(' ')[-1]
interface.view_seasons(league)

elif len(usr_command.split(' ')) == 4:
league = usr_command.split(' ')[1]
season = usr_command.split(' ')[2]
stat_type = usr_command.split(' ')[3]

else:
if usr_command.split(' ')[0] not in cmd.keys():
print('Command not valid')
else:
print('Could not find the specified params')

if __name__ == '__main__':
main()

• Take a look at docopt. It is really a way to deal with arguments.
– vnp
Apr 23 '20 at 22:08

cmd.Cmd

Although argparse() could be abused to provide some of the desired functionality, it isn't the right tool. For example, it is designed to exit when an error is detected, such as an unrecognized command or missing argument.

cmd in the standard library is a much better tool for this. Writing a command interpreter is as simple as coding a subclass of cmd.Cmd and defining "do_command" methods for each command you need, e.g. do_view(), do_download(), etc. Here's an example based on your code, but the "do_command()" methods just print something.

import cmd

class StatShell(cmd.Cmd):
intro = '\n'.join([
"\t" + "*"*60,
"\t\t***  Welcome - Football Stats generator  ***",
"\t" + "*"*60,
"",
"\tType help or ? to list commands,",
"\t\tor help command to get help about a command."
])
prompt = "CLIStats: "

arg = arg.split()
if len(arg) == 3:
league, season, type = arg
else:

def do_exit(self, arg):
"""Exit the interpreter."""
print("exiting ...")
return True

def do_view(self, arg):
"""view leagues or seasons: view [LEAGUE] """
if arg:
league = arg[0]
print(league,'seasons: ...')

else:
print('leagues ...')

def main():
StatShell().cmdloop()

• thank you for providing an example, found out about cmd while digging into docopt that was mentioned by @vnp. I will certainly use this approach as it is exactly what I'm looking fot. Thank you for taking your time! Apr 24 '20 at 7:34

League representation

This:

    self.leagues = {'EN_PR': ['2019/2020', '2018/2019', '2017/2018', '2016/2017'],
'EU_CL': ['2019/2020', '2018/2019', '2017/2018', '2016/2017'],
'ES_PL': ['2019/2020', '2018/2019', '2017/2018', '2016/2017']}


seems excessive. At most, you should need to store:

• The minimum and maximum years as int
• The set of league names: {'EN_PR', 'EU_CL', 'ES_PL'}

Everything else can be derived.

Grammar

Display's avalible commands and how to use them


to

Displays available commands and how to use them


and

is not avalible


to

is not available


Generator

    args = []
for league in self.leagues.keys():
args.append('-v' + ' ' +  league)
return args


can be

return ' '.join(f'-v {league}' for league in self.league_names)


assuming that league_names holds the set previously described.

• That you for your comments, self.leagues is normally a json file that is loaded so what you see is just a representation of it's content that is retrieved from an API. Thank you for taking your time to lift these things into light! Apr 24 '20 at 7:32