Guest User

Submit Kattis problem

a guest
Sep 13th, 2020
276
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 8.63 KB | None | 0 0
  1. #!/usr/bin/env python3
  2.  
  3. import argparse
  4. import os
  5. import re
  6. import sys
  7. import webbrowser
  8. import configparser
  9.  
  10. import requests
  11. import requests.exceptions
  12.  
  13. _DEFAULT_CONFIG = '/etc/kattis/submit/kattisrc'
  14. _VERSION = 'Version: $Version: $'
  15. _LANGUAGE_GUESS = {
  16.     '.c': 'C',
  17.     '.c#': 'C#',
  18.     '.c++': 'C++',
  19.     '.cc': 'C++',
  20.     '.cpp': 'C++',
  21.     '.cs': 'C#',
  22.     '.cxx': 'C++',
  23.     '.go': 'Go',
  24.     '.h': 'C++',
  25.     '.hs': 'Haskell',
  26.     '.java': 'Java',
  27.     '.js': 'JavaScript',
  28.     '.kt': 'Kotlin',
  29.     '.m': 'Objective-C',
  30.     '.pas': 'Pascal',
  31.     '.php': 'PHP',
  32.     '.pl': 'Prolog',
  33.     '.py': 'Python',
  34.     '.rb': 'Ruby'
  35. }
  36.  
  37. def kotlin_guess_mainclass(problem):
  38.     return problem.capitalize() + 'Kt'
  39.  
  40. def identity(problem):
  41.     return problem
  42.  
  43. _GUESS_MAINCLASS = {
  44.         'Java': identity,
  45.         'Python': identity,
  46.         'Kotlin': kotlin_guess_mainclass
  47.         }
  48.  
  49. _HEADERS = {'User-Agent': 'kattis-cli-submit'}
  50.  
  51.  
  52. class ConfigError(Exception):
  53.     pass
  54.  
  55.  
  56. def get_url(cfg, option, default):
  57.     if cfg.has_option('kattis', option):
  58.         return cfg.get('kattis', option)
  59.     else:
  60.         return 'https://%s/%s' % (cfg.get('kattis', 'hostname'), default)
  61.  
  62.  
  63. def get_config():
  64.     """Returns a ConfigParser object for the .kattisrc file(s)
  65.    """
  66.     cfg = configparser.ConfigParser()
  67.     if os.path.exists(_DEFAULT_CONFIG):
  68.         cfg.read(_DEFAULT_CONFIG)
  69.  
  70.     if not cfg.read([os.path.join(os.getenv('HOME'), '.kattisrc'),
  71.                      os.path.join(os.path.dirname(sys.argv[0]), '.kattisrc')]):
  72.         raise ConfigError('''\
  73. I failed to read in a config file from your home directory or from the
  74. same directory as this script. Please go to your Kattis installation
  75. to download a .kattisrc file.
  76.  
  77. The file should look something like this:
  78. [user]
  79. username: yourusername
  80. token: *********
  81.  
  82. [kattis]
  83. loginurl: https://<kattis>/login
  84. submissionurl: https://<kattis>/submit''')
  85.     return cfg
  86.  
  87.  
  88. def login(login_url, username, password=None, token=None):
  89.     """Log in to Kattis.
  90.  
  91.    At least one of password or token needs to be provided.
  92.  
  93.    Returns a requests.Response with cookies needed to be able to submit
  94.    """
  95.     login_args = {'user': username, 'script': 'true'}
  96.     if password:
  97.         login_args['password'] = password
  98.     if token:
  99.         login_args['token'] = token
  100.  
  101.     return requests.post(login_url, data=login_args, headers=_HEADERS)
  102.  
  103.  
  104. def login_from_config(cfg):
  105.     """Log in to Kattis using the access information in a kattisrc file
  106.  
  107.    Returns a requests.Response with cookies needed to be able to submit
  108.    """
  109.     username = cfg.get('user', 'username')
  110.     password = token = None
  111.     try:
  112.         password = cfg.get('user', 'password')
  113.     except configparser.NoOptionError:
  114.         pass
  115.     try:
  116.         token = cfg.get('user', 'token')
  117.     except configparser.NoOptionError:
  118.         pass
  119.     if password is None and token is None:
  120.         raise ConfigError('''\
  121. Your .kattisrc file appears corrupted. It must provide a token (or a
  122. KATTIS password).
  123.  
  124. Please download a new .kattisrc file''')
  125.  
  126.     loginurl = get_url(cfg, 'loginurl', 'login')
  127.     return login(loginurl, username, password, token)
  128.  
  129.  
  130. def submit(submit_url, cookies, problem, language, files, mainclass='', tag=''):
  131.     """Make a submission.
  132.  
  133.    The url_opener argument is an OpenerDirector object to use (as
  134.    returned by the login() function)
  135.  
  136.    Returns the requests.Result from the submission
  137.    """
  138.  
  139.     data = {'submit': 'true',
  140.             'submit_ctr': 2,
  141.             'language': language,
  142.             'mainclass': mainclass,
  143.             'problem': problem,
  144.             'tag': tag,
  145.             'script': 'true'}
  146.  
  147.     sub_files = []
  148.     for f in files:
  149.         with open(f) as sub_file:
  150.             sub_files.append(('sub_file[]',
  151.                               (os.path.basename(f),
  152.                                sub_file.read(),
  153.                                'application/octet-stream')))
  154.  
  155.     return requests.post(submit_url, data=data, files=sub_files, cookies=cookies, headers=_HEADERS)
  156.  
  157.  
  158. def confirm_or_die(problem, language, files, mainclass, tag):
  159.     print('Problem:', problem)
  160.     print('Language:', language)
  161.     print('Files:', ', '.join(files))
  162.     if mainclass:
  163.         print('Mainclass:', mainclass)
  164.     if tag:
  165.         print('Tag:', tag)
  166.     print('Submit (y/N)?')
  167.     if sys.stdin.readline().upper()[:-1] != 'Y':
  168.         print('Cancelling')
  169.         sys.exit(1)
  170.  
  171.  
  172. def open_submission(submit_response, cfg):
  173.     submissions_url = get_url(cfg, 'submissionsurl', 'submissions')
  174.  
  175.     m = re.search(r'Submission ID: (\d+)', submit_response)
  176.     if m:
  177.         submission_id = m.group(1)
  178.         print('Open in browser (y/N)?')
  179.         if sys.stdin.readline().upper()[:-1] == 'Y':
  180.             url = '%s/%s' % (submissions_url, submission_id)
  181.             webbrowser.open(url)
  182.  
  183.  
  184. def main():
  185.     parser = argparse.ArgumentParser(description='Submit a solution to Kattis')
  186.     parser.add_argument('-p', '--problem',
  187.                    help=''''Which problem to submit to.
  188. Overrides default guess (first part of first filename)''')
  189.     parser.add_argument('-m', '--mainclass',
  190.                    help='''Sets mainclass.
  191. Overrides default guess (first part of first filename)''')
  192.     parser.add_argument('-l', '--language',
  193.                    help='''Sets language.
  194. Overrides default guess (based on suffix of first filename)''')
  195.     parser.add_argument('-t', '--tag',
  196.                    help=argparse.SUPPRESS)
  197.     parser.add_argument('-f', '--force',
  198.                    help='Force, no confirmation prompt before submission',
  199.                    action='store_true')
  200.     parser.add_argument('files', nargs='+')
  201.  
  202.     args = parser.parse_args()
  203.     files = args.files
  204.  
  205.     try:
  206.         cfg = get_config()
  207.     except ConfigError as exc:
  208.         print(exc)
  209.         sys.exit(1)
  210.  
  211.     problem, ext = os.path.splitext(os.path.basename(args.files[0]))
  212.     language = _LANGUAGE_GUESS.get(ext, None)
  213.     mainclass = None
  214.     if language in _GUESS_MAINCLASS:
  215.         mainclass = _GUESS_MAINCLASS[language](problem)
  216.     tag = args.tag
  217.  
  218.     if args.problem:
  219.         problem = args.problem
  220.  
  221.     if args.mainclass is not None:
  222.         mainclass = args.mainclass
  223.  
  224.     if args.language:
  225.         language = args.language
  226.     elif language == 'Python':
  227.         python_version = str(sys.version_info[0])
  228.         try:
  229.             python_version = cfg.get('defaults', 'python-version')
  230.         except configparser.Error:
  231.             pass
  232.  
  233.         if python_version not in ['2', '3']:
  234.             print('python-version in .kattisrc must be 2 or 3')
  235.             sys.exit(1)
  236.         language = 'Python ' + python_version
  237.  
  238.     if language is None:
  239.         print('''\
  240. No language specified, and I failed to guess language from filename
  241. extension "%s"''' % (ext,))
  242.         sys.exit(1)
  243.  
  244.     files = list(set(args.files))
  245.  
  246.     try:
  247.         login_reply = login_from_config(cfg)
  248.     except ConfigError as exc:
  249.         print(exc)
  250.         sys.exit(1)
  251.     except requests.exceptions.RequestException as err:
  252.         print('Login connection failed:', err)
  253.         sys.exit(1)
  254.  
  255.     if not login_reply.status_code == 200:
  256.         print('Login failed.')
  257.         if login_reply.status_code == 403:
  258.             print('Incorrect username or password/token (403)')
  259.         elif login_reply.status_code == 404:
  260.             print('Incorrect login URL (404)')
  261.         else:
  262.             print('Status code:', login_reply.status_code)
  263.         sys.exit(1)
  264.  
  265.     submit_url = get_url(cfg, 'submissionurl', 'submit')
  266.  
  267.     if not args.force:
  268.         confirm_or_die(problem, language, files, mainclass, tag)
  269.  
  270.     try:
  271.         result = submit(submit_url,
  272.                         login_reply.cookies,
  273.                         problem,
  274.                         language,
  275.                         files,
  276.                         mainclass,
  277.                         tag)
  278.     except requests.exceptions.RequestException as err:
  279.         print('Submit connection failed:', err)
  280.         sys.exit(1)
  281.  
  282.     if result.status_code != 200:
  283.         print('Submission failed.')
  284.         if result.status_code == 403:
  285.             print('Access denied (403)')
  286.         elif result.status_code == 404:
  287.             print('Incorrect submit URL (404)')
  288.         else:
  289.             print('Status code:', login_reply.status_code)
  290.         sys.exit(1)
  291.  
  292.     plain_result = result.content.decode('utf-8').replace('<br />', '\n')
  293.     print(plain_result)
  294.  
  295.     try:
  296.         open_submission(plain_result, cfg)
  297.     except configparser.NoOptionError:
  298.         pass
  299.  
  300.  
  301. if __name__ == '__main__':
  302.     main()
Add Comment
Please, Sign In to add comment