#!/usr/bin/env python # -*- coding: utf-8 -*- import getpass import io import yaml import json import urllib.request, urllib.parse, urllib.error import os import inspect import sys from bson.son import SON import kpov_random_helpers import random import argparse import locale import readline locale.setlocale(locale.LC_ALL, ['C', 'utf8']) readline.set_completer_delims(readline.get_completer_delims().replace('/', '')) readline.parse_and_bind('tab: complete') # SERVER_URL = "http://localhost/kpov_judge/" #SERVER_URL = "http://kpov.fri.uni-lj.si/kpov_judge/" #TASK_URL = SERVER_URL + '{task_id}/task.py' TASK_URL = "file://" + os.getcwd() + '/tasks' PARAMS_FILE = os.path.expanduser("~/.kpov_params.yaml") DEFAULT_LANGUAGE = 'si' """get the parameters for a task either from the user or from a file.""" def get_params(params, meta, param_name_list=None, language=None): if param_name_list is None: param_name_list = list(meta.keys()) if language is None: language = params.get('language', DEFAULT_LANGUAGE) for name in param_name_list: if name not in meta: pass m = meta.get(name, {}) try: description = m['descriptions'][language] except KeyError: description = name init = params.get(name) or '' try: if name == 'password': s = getpass.getpass('{}: '.format(description)) else: s = input('{} [{}]: '.format(description, init)) if s and m.get('w', True): params[name] = s elif name not in params: params[name] = None except EOFError: print() return params def add_meta_to_argparser(argparser, meta, defaults = {}): language = defaults.get('language', DEFAULT_LANGUAGE) for k, v in meta.items(): try: desc = v['descriptions'][language].encode("utf-8") except: desc = k argparser.add_argument('--'+k, nargs='?', help=desc, type=str, default=defaults.get(k, None)) def http_auth(url, username, password): password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm() password_mgr.add_password(None, url, username, password) handler = urllib.request.HTTPBasicAuthHandler(password_mgr) opener = urllib.request.build_opener(handler) # Install the opener. Now all calls to urllib2.urlopen use our opener. urllib.request.install_opener(opener) def load_task(stream): # the stream should compile into at least the function # task(...) and a dictionary "params_meta". source = stream.read() t = compile(source, 'task.py', 'exec') d = {} exec(t, d) # get a list of arguments for task(...) # args_list = inspect.getargs(task.func_code)[0] return d['task'], d['task_check'], d['params_meta'], d['gen_params'] def locate_task(params, argparser, quiet=False): # first the URL where all tasks are stored url_meta = { 'task_url': {'descriptions': {'si': 'URL z nalogami', 'en': 'Root URL for all tasks'}} } if 'task_url' not in params: params['task_url'] = TASK_URL add_meta_to_argparser(argparser, meta = url_meta, defaults = params) args, unknown_args = argparser.parse_known_args() params['task_url'] = args.task_url if not quiet: params = get_params(params, url_meta) # then the student's ID (and password if neccessarry) fetch_params_meta = { 'username': {'descriptions': {'si': 'Uporabniško ime', 'en': 'Username'}}, } if params['task_url'] is not None and ( params['task_url'].startswith('http')): fetch_params_meta['password'] = {'descriptions': {'si': 'Geslo', 'en': 'Password'}, } # and finally, the name of the task fetch_params_meta['task_name'] = {'descriptions': {'si': 'Ime naloge', 'en': 'Task name'}, } add_meta_to_argparser(argparser, meta = fetch_params_meta, defaults = params) args, unknown_args = argparser.parse_known_args() # update params with the now known args for k, v in fetch_params_meta.items(): params[k] = vars(args).get(k, params.get(k, None)) if not quiet: params = get_params(params, fetch_params_meta) return params def load_params(filename): try: with open(filename) as f: saved_params = yaml.load(f) except: saved_params = dict() if saved_params is None: saved_params = dict() return saved_params if __name__ == '__main__': # get the parameters needed to get to the task, such as the URLs, the name of the task and optionally an ID from the student # start with the the parameters needed for the dialog gui to work argparser = argparse.ArgumentParser(conflict_handler='resolve', add_help=False, description='Get task parameters') argparser.add_argument('-h', '--help', action='store_true') argparser.add_argument('-q', '--quiet', action='store_true', help='disable prompts') argparser.add_argument('-g', '--generate_params', action='store_true', help='generate initial values for the task parameters') argparser.add_argument('-pf','--params_file', nargs='?', default=PARAMS_FILE, help='a local file containing saved param values') basic_args, unknown_args = argparser.parse_known_args() # get default parameters including language params = load_params(basic_args.params_file) argparser.add_argument('-l', '--language', nargs='?', default=params.get('language', DEFAULT_LANGUAGE), help='the language used') basic_args, unknown_args = argparser.parse_known_args() params['language'] = basic_args.language # continue with the parameters needed to get the task params = locate_task(params, argparser, quiet=basic_args.quiet) # TODO: if the task name is missing or invalid, try to get a list of tasks # get task source and generate params if neccessarry try: task_url = params['task_url'] task_name = params['task_name'] if task_url.startswith('http'): http_auth(task_url, params['username'], params['password']) print() print("Fetching {task_url}/{task_name}/task.py…".format(**params)) source = urllib.request.urlopen("{task_url}/{task_name}/task.py".format(**params)) task, task_check, task_params_meta, gen_params = load_task(source) except Exception as e: import traceback traceback.print_exc() print() for k, v in params.items(): if k != 'password': print('{}: {}'.format(k, v)) print() if basic_args.help: argparser.print_help() else: argparser.print_usage() with open(basic_args.params_file, 'w') as f: yaml.dump(params, f) exit(1) # get task parameters params['task_params'] = params.get('task_params', dict()) params['task_params'][params['task_name']] = params['task_params'].get(params['task_name'], dict()) task_params = params['task_params'][params['task_name']] if basic_args.generate_params: #prejema lahko samo stringe in ne številk (potrebno je str(int) # print ("params before: {} {}".format(params, task_params)) task_params.update(gen_params(params['username'], task_params_meta)) # print ("params after: {} {}".format(params, task_params)) if task_url.startswith('http'): response = urllib.request.urlopen('{task_url}/{task_name}/params.json'.format(**params)) web_task_params = json.load(io.TextIOWrapper(response)) task_params.update(web_task_params) params_argparser = argparse.ArgumentParser(parents=[argparser], conflict_handler='resolve', add_help=True) add_meta_to_argparser(params_argparser, task_params_meta, defaults=task_params) args = vars(params_argparser.parse_args()) for k in task_params_meta: if k in args and args[k] is not None: task_params[k] = args[k] if not basic_args.quiet: task_params = get_params(task_params, task_params_meta, language=params['language']) if basic_args.help: print(params_argparser.format_help()) exit(0) # run task.task() public_params = dict() for k in inspect.getargs(task.__code__)[0]: public_params[k] = task_params[k] params['task_params'][params['task_name']] = task_params # save parameters for the next run with open(basic_args.params_file, 'w') as f: # print "dumping", params yaml.dump(params, f) try: print() print('Running task… ', end='') task_result = task(**public_params) print('Checking task… ', end='') score, hints = task_check(task_result, task_params) print('Done!') print('Score: {}'.format(score)) print('Hints:') for hint in hints: print(hint) except Exception as e: import traceback traceback.print_exc()